-
-
Notifications
You must be signed in to change notification settings - Fork 5
feat(ui): Flyout tray panel #103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
080f8e7
feat(ui): Flyout tray panel
YueMiyuki 3cb11a9
feat(ui): refine flyout UI - consistent border-radius, no menu bar ga…
YueMiyuki 84091b1
Accept suggestions from AI
YueMiyuki 2c3842a
Accept suggestions from AI
YueMiyuki b1fe40a
Accept suggestions from AI
YueMiyuki File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "identifier": "tray-panel", | ||
| "description": "Minimal capabilities for the tray flyout panel.", | ||
| "windows": ["tray-panel"], | ||
| "permissions": [ | ||
| "core:default", | ||
| "core:window:allow-show", | ||
| "core:window:allow-hide", | ||
| "core:window:allow-set-focus", | ||
| "core:window:allow-start-dragging", | ||
| "store:default" | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| ] | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| //! Tray "Quick Panel" flyout window | ||
|
|
||
| #[cfg(not(target_os = "android"))] | ||
| use tauri::{ | ||
| AppHandle, Emitter, Manager, PhysicalPosition, WebviewUrl, WebviewWindow, WebviewWindowBuilder, | ||
| }; | ||
|
|
||
| #[cfg(target_os = "android")] | ||
| use tauri::AppHandle; | ||
|
|
||
| #[cfg(not(target_os = "android"))] | ||
| pub const FLYOUT_LABEL: &str = "tray-panel"; | ||
|
|
||
| /// Logical size of the flyout window | ||
| #[cfg(not(target_os = "android"))] | ||
| const FLYOUT_WIDTH: f64 = 396.0; | ||
| #[cfg(not(target_os = "android"))] | ||
| const FLYOUT_HEIGHT: f64 = 540.0; | ||
|
|
||
| /// Gap in physical pixels between the tray icon and the flyout edge | ||
| #[cfg(not(target_os = "android"))] | ||
| const FLYOUT_GAP: i32 = 0; | ||
|
|
||
| /// Create the flyout window | ||
| #[cfg(not(target_os = "android"))] | ||
| pub fn setup_flyout(app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> { | ||
| let handle = app.handle(); | ||
| // Skip if it somehow already exists | ||
| if handle.get_webview_window(FLYOUT_LABEL).is_some() { | ||
| return Ok(()); | ||
| } | ||
|
|
||
| let mut builder = | ||
| WebviewWindowBuilder::new(app, FLYOUT_LABEL, WebviewUrl::App("tray.html".into())) | ||
| .title("Risuko Quick Panel") | ||
| .inner_size(FLYOUT_WIDTH, FLYOUT_HEIGHT) | ||
| .decorations(false) | ||
| .always_on_top(true) | ||
| .skip_taskbar(true) | ||
| .resizable(false) | ||
| .visible(false) | ||
| .focused(false) | ||
| .transparent(true) | ||
| .shadow(false) | ||
| .accept_first_mouse(true); | ||
|
|
||
| #[cfg(target_os = "macos")] | ||
| { | ||
| builder = builder.visible_on_all_workspaces(true); | ||
| } | ||
|
|
||
| builder.build()?; | ||
| Ok(()) | ||
| } | ||
|
|
||
| #[cfg(target_os = "android")] | ||
| pub fn setup_flyout(_app: &tauri::App) -> Result<(), Box<dyn std::error::Error>> { | ||
| Ok(()) | ||
| } | ||
|
|
||
| /// Cache the tray icon rect | ||
| #[cfg(not(target_os = "android"))] | ||
| pub fn cache_tray_rect(app: &AppHandle, rect: &tauri::Rect, scale_factor: f64) { | ||
| let pos = rect.position.to_physical::<f64>(scale_factor); | ||
| let size = rect.size.to_physical::<f64>(scale_factor); | ||
| if let Some(state) = app.try_state::<crate::state::AppState>() { | ||
| if let Ok(mut anchor) = state.tray_anchor.lock() { | ||
| *anchor = Some((pos.x, pos.y, size.width, size.height)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Toggle the flyout | ||
| #[cfg(not(target_os = "android"))] | ||
| pub fn toggle_flyout(app: &AppHandle) { | ||
| let Some(window) = app.get_webview_window(FLYOUT_LABEL) else { | ||
| log::warn!("[Risuko] flyout window not found"); | ||
| return; | ||
| }; | ||
|
|
||
| if window.is_visible().unwrap_or(false) { | ||
| #[cfg(target_os = "macos")] | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
| { | ||
| // Only set Accessory policy when in tray mode | ||
| let run_mode = crate::utils::run_mode::current_run_mode(app); | ||
| if crate::utils::run_mode::is_tray_mode(run_mode) { | ||
| let _ = app.set_activation_policy(tauri::ActivationPolicy::Accessory); | ||
| } | ||
| } | ||
| let _ = window.hide(); | ||
| return; | ||
| } | ||
|
|
||
| position_flyout(app, &window); | ||
| let _ = window.show(); | ||
| let _ = window.set_focus(); | ||
| // Wake the webview's polling loop so the panel is fresh on open. | ||
| let _ = window.emit("flyout:show", ()); | ||
| } | ||
|
|
||
| #[cfg(target_os = "android")] | ||
| pub fn toggle_flyout(_app: &AppHandle) {} | ||
|
|
||
| /// Position the flyout near the cached tray anchor | ||
| #[cfg(not(target_os = "android"))] | ||
| fn position_flyout(app: &AppHandle, window: &WebviewWindow) { | ||
| // Resolve the anchor point (icon center) in physical pixels | ||
| let anchor = app | ||
| .try_state::<crate::state::AppState>() | ||
| .and_then(|state| state.tray_anchor.lock().ok().and_then(|guard| *guard)); | ||
|
|
||
| let (icon_x, icon_y, icon_w, icon_h) = match anchor { | ||
| Some(rect) => rect, | ||
| None => { | ||
| // No tray rect was ever delivered | ||
| match app.cursor_position() { | ||
| Ok(pos) => (pos.x, pos.y, 0.0, 0.0), | ||
| Err(_) => (0.0, 0.0, 0.0, 0.0), | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| let icon_center_x = icon_x + icon_w / 2.0; | ||
| let icon_center_y = icon_y + icon_h / 2.0; | ||
|
|
||
| // Find the monitor under the icon; fall back to primary | ||
| let monitor = app | ||
| .monitor_from_point(icon_center_x, icon_center_y) | ||
| .ok() | ||
| .flatten() | ||
| .or_else(|| app.primary_monitor().ok().flatten()); | ||
|
|
||
| let Some(monitor) = monitor else { | ||
| // Last resort: drop it at the anchor with no clamping | ||
| let _ = window.set_position(PhysicalPosition::new(icon_x as i32, icon_y as i32)); | ||
| return; | ||
| }; | ||
|
|
||
| let scale = monitor.scale_factor(); | ||
| let work = monitor.work_area(); | ||
| let wa_x = work.position.x as f64; | ||
| let wa_y = work.position.y as f64; | ||
| let wa_w = work.size.width as f64; | ||
| let wa_h = work.size.height as f64; | ||
|
|
||
| // Window size in physical pixels | ||
| let win_w = FLYOUT_WIDTH * scale; | ||
| let win_h = FLYOUT_HEIGHT * scale; | ||
| let gap = FLYOUT_GAP as f64 * scale; | ||
|
|
||
| // Decide above vs below using the icon center relative to the work area | ||
| let in_top_half = icon_center_y < wa_y + wa_h / 2.0; | ||
| let mut y = if in_top_half { | ||
| // Place below the icon | ||
| icon_y + icon_h + gap | ||
| } else { | ||
| // Place above the icon | ||
| icon_y - win_h - gap | ||
| }; | ||
|
|
||
| // Horizontally center on the icon | ||
| let mut x = icon_center_x - win_w / 2.0; | ||
|
|
||
| // Clamp into the work area | ||
| let max_x = wa_x + wa_w - win_w; | ||
| let max_y = wa_y + wa_h - win_h; | ||
| if x < wa_x { | ||
| x = wa_x; | ||
| } else if x > max_x { | ||
| x = max_x.max(wa_x); | ||
| } | ||
| if y < wa_y { | ||
| y = wa_y; | ||
| } else if y > max_y { | ||
| y = max_y.max(wa_y); | ||
| } | ||
|
|
||
| let _ = window.set_position(PhysicalPosition::new(x.round() as i32, y.round() as i32)); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| pub mod flyout; | ||
| pub mod menu; | ||
| pub mod tray; | ||
| pub mod vault; | ||
|
|
||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P2: Misleading security description:
tray-panel.jsonclaims "No filesystem, shell, process, OS, or dialog access", butdefault.jsonalready includes"tray-panel"in itswindowslist and grants all of those permissions. In Tauri v2, capability permissions are unioned — so the restrictive intent is entirely defeated. Remove"tray-panel"fromdefault.json's windows array if the intent is genuine restriction, or update the description to match reality.Prompt for AI agents