From a8a15f6a8b5696fa2e0a6a4657c548f906d2cba7 Mon Sep 17 00:00:00 2001 From: iamazy Date: Sun, 23 Aug 2026 17:12:24 +0800 Subject: [PATCH 1/8] fix: focus terminal on hover --- crates/gpui_term/src/view/render.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/gpui_term/src/view/render.rs b/crates/gpui_term/src/view/render.rs index e259674..ffeed40 100644 --- a/crates/gpui_term/src/view/render.rs +++ b/crates/gpui_term/src/view/render.rs @@ -87,6 +87,12 @@ impl TerminalView { .on_action(cx.listener(TerminalView::stop_cast_recording)) .on_action(cx.listener(TerminalView::toggle_cast_recording)) .on_key_down(cx.listener(Self::key_down)) + .on_mouse_move(cx.listener(|this, _, window, cx| { + // Hovering a visible terminal should make it the active input target. The + // terminal view only receives this event when it is the topmost hit element, so + // covered terminals cannot steal focus from the panel above them. + window.focus(&this.focus_handle, cx); + })) } fn terminal_view_root_mouse_handlers( From 7fcf745ac7331fe1ff1932260e3e3b0f6a65b6a0 Mon Sep 17 00:00:00 2001 From: iamazy Date: Sun, 23 Aug 2026 17:44:53 +0800 Subject: [PATCH 2/8] feat(menu): support disable auto collapse --- crates/menubar/src/lib.rs | 29 ++++++++++++++++++++++++++++ crates/menubar/src/menu_bar.rs | 20 ++++++++++++++++--- locales/en.yml | 1 + locales/zh-CN.yml | 1 + termua/src/settings.rs | 18 ++++++++++++++++- termua/src/window/settings/meta.rs | 8 ++++++++ termua/src/window/settings/render.rs | 1 + termua/src/window/settings/state.rs | 11 +++++++++++ termua/src/window/settings/tests.rs | 9 ++++++++- termua/src/window/settings/view.rs | 10 ++++++++++ 10 files changed, 103 insertions(+), 5 deletions(-) diff --git a/crates/menubar/src/lib.rs b/crates/menubar/src/lib.rs index 3ae1404..2121e36 100644 --- a/crates/menubar/src/lib.rs +++ b/crates/menubar/src/lib.rs @@ -1,5 +1,34 @@ use gpui::App; +#[derive(Clone, Copy, Debug)] +pub struct MenuBarSettings { + pub auto_collapse: bool, +} + +impl Default for MenuBarSettings { + fn default() -> Self { + Self { + auto_collapse: true, + } + } +} + +impl gpui::Global for MenuBarSettings {} + +pub fn set_auto_collapse(auto_collapse: bool, cx: &mut App) { + if cx.has_global::() { + cx.global_mut::().auto_collapse = auto_collapse; + } else { + cx.set_global(MenuBarSettings { auto_collapse }); + } + cx.refresh_windows(); +} + +pub fn auto_collapse(cx: &App) -> bool { + cx.try_global::() + .map_or(true, |settings| settings.auto_collapse) +} + rust_i18n::i18n!("../../locales"); mod menu_bar; diff --git a/crates/menubar/src/menu_bar.rs b/crates/menubar/src/menu_bar.rs index 50b7041..29fd626 100644 --- a/crates/menubar/src/menu_bar.rs +++ b/crates/menubar/src/menu_bar.rs @@ -13,7 +13,7 @@ use gpui_component::{ }; use rust_i18n::t; -use crate::state::MenuBarState; +use crate::{auto_collapse, state::MenuBarState}; const CONTEXT: &str = "FoldableAppMenuBar"; @@ -237,7 +237,15 @@ impl FoldableAppMenuBar { window.prevent_default(); cx.stop_propagation(); - self.state.on_fold_click(); + if auto_collapse(cx) { + self.state.on_fold_click(); + } else if self.state.selected_ix == Some(0) { + // With a permanently expanded menubar, clicking the first menu toggles only its + // popup; the menubar itself must remain visible. + self.state.selected_ix = None; + } else { + self.state.selected_ix = Some(0); + } self.set_selected_index(self.state.selected_ix, window, cx); } @@ -246,7 +254,7 @@ impl FoldableAppMenuBar { return; } // Don't expand/open from hover when folded. - if !self.state.expanded { + if !auto_collapse(cx) || !self.state.expanded { return; } // Switch from other top-level menus back to the fold/app menu when the menubar is active. @@ -314,6 +322,12 @@ impl Render for FoldableAppMenuBar { self.sync_menus_from_app(window, cx); + if !auto_collapse(cx) { + self.state.expanded = true; + } else if self.state.selected_ix.is_none() { + self.state.expanded = false; + } + let fold_name: SharedString = self .fold_menu .as_ref() diff --git a/locales/en.yml b/locales/en.yml index d93e217..1e8523b 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -382,6 +382,7 @@ Settings: Appearance: Theme: "Theme" Language: "Language" + Menu: "Menu" Terminal: Terminal: "Terminal" Behavior: "Behavior" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 2601407..91cdbec 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -382,6 +382,7 @@ Settings: Appearance: Theme: "主题" Language: "语言" + Menu: "菜单" Terminal: Terminal: "终端" Behavior: "行为" diff --git a/termua/src/settings.rs b/termua/src/settings.rs index 90ac148..f36dde4 100644 --- a/termua/src/settings.rs +++ b/termua/src/settings.rs @@ -142,7 +142,7 @@ where language } -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] #[serde(default)] pub struct AppearanceSettings { pub theme: ThemeMode, @@ -151,6 +151,20 @@ pub struct AppearanceSettings { pub light_theme: Option, /// Name of the selected dark theme config (from ThemeRegistry). None = registry default. pub dark_theme: Option, + /// Whether the in-window application menu collapses to the menu icon. + pub menu_auto_collapse: bool, +} + +impl Default for AppearanceSettings { + fn default() -> Self { + Self { + theme: ThemeMode::default(), + language: Language::default(), + light_theme: None, + dark_theme: None, + menu_auto_collapse: true, + } + } } #[derive(Clone, Debug, Serialize, Deserialize)] @@ -740,6 +754,7 @@ impl SettingsFile { set_language(self.appearance.language, cx); set_theme_mode(self.appearance.theme, window, cx); + menubar::set_auto_collapse(self.appearance.menu_auto_collapse, cx); } pub fn apply_assistant_settings(&self, cx: &mut App) { @@ -1100,6 +1115,7 @@ mod tests { let settings = SettingsFile::load_from_str_lenient("{}").unwrap(); assert_eq!(settings.terminal.copy_on_select, true); assert_eq!(settings.terminal.option_as_meta, false); + assert!(settings.appearance.menu_auto_collapse); assert_eq!(settings.terminal.ligatures_enabled(), true); } diff --git a/termua/src/window/settings/meta.rs b/termua/src/window/settings/meta.rs index 385c680..eb2bf06 100644 --- a/termua/src/window/settings/meta.rs +++ b/termua/src/window/settings/meta.rs @@ -78,6 +78,14 @@ static ALL_SETTINGS_META: &[SettingMeta] = &[ section: SettingsNavSection::Appearance, page: SettingsPage::AppearanceLanguage, }, + SettingMeta { + id: "appearance.menu_auto_collapse", + title: "Auto-collapse menu", + description: "Collapse the application menu to the Menu button when it is inactive.", + keywords: &["appearance", "menu", "menubar", "collapse", "fold"], + section: SettingsNavSection::Appearance, + page: SettingsPage::AppearanceMenu, + }, SettingMeta { id: "terminal.default_backend", title: "Default backend", diff --git a/termua/src/window/settings/render.rs b/termua/src/window/settings/render.rs index cf22666..9fdfbc0 100644 --- a/termua/src/window/settings/render.rs +++ b/termua/src/window/settings/render.rs @@ -400,6 +400,7 @@ impl SettingsWindow { ) -> AnyElement { match page { SettingsPage::AppearanceTheme => self.render_appearance_theme_page_heading(heading, cx), + SettingsPage::AppearanceMenu => div().child(heading).into_any_element(), _ => self.render_simple_page_heading(heading, cx), } } diff --git a/termua/src/window/settings/state.rs b/termua/src/window/settings/state.rs index fe03426..d38a168 100644 --- a/termua/src/window/settings/state.rs +++ b/termua/src/window/settings/state.rs @@ -122,6 +122,7 @@ impl TerminalKeybinding { pub enum SettingsPage { AppearanceTheme, AppearanceLanguage, + AppearanceMenu, Terminal, TerminalFont, TerminalKeyBindings, @@ -172,6 +173,15 @@ const SETTINGS_PAGE_SPECS: &[SettingsPageSpec] = &[ hint_key: None, is_sidebar_item: true, }, + SettingsPageSpec { + section: SettingsNavSection::Appearance, + item_label_key: "Settings.Appearance.Menu", + page: SettingsPage::AppearanceMenu, + nav_item_id: "nav.page.appearance.menu", + heading_key: "Settings.Appearance.Menu", + hint_key: None, + is_sidebar_item: true, + }, // `SettingsPage::Terminal` maps to the group row (`nav.group.terminal`) and should not show as // a child item in the sidebar. SettingsPageSpec { @@ -346,6 +356,7 @@ fn nav_item_sort_key(page: SettingsPage) -> &'static str { match page { SettingsPage::AppearanceTheme => "Theme", SettingsPage::AppearanceLanguage => "Language", + SettingsPage::AppearanceMenu => "Menu", SettingsPage::Terminal => "Terminal", SettingsPage::TerminalFont => "Font", SettingsPage::TerminalKeyBindings => "Key Bindings", diff --git a/termua/src/window/settings/tests.rs b/termua/src/window/settings/tests.rs index d88079a..1e35c2a 100644 --- a/termua/src/window/settings/tests.rs +++ b/termua/src/window/settings/tests.rs @@ -2366,6 +2366,7 @@ fn setting_metadata_covers_all_controls() { assert!(ids.contains("appearance.light_theme")); assert!(ids.contains("appearance.dark_theme")); assert!(ids.contains("appearance.language")); + assert!(ids.contains("appearance.menu_auto_collapse")); // Terminal / Font assert!(ids.contains("terminal.font_family")); @@ -2452,7 +2453,7 @@ fn sidebar_nav_specs_match_settings_nav_requirements() { .iter() .find(|group| group.section == SettingsNavSection::Appearance) .expect("expected Appearance group"); - assert_eq!(appearance.items.len(), 2); + assert_eq!(appearance.items.len(), 3); assert!( appearance .items @@ -2465,6 +2466,12 @@ fn sidebar_nav_specs_match_settings_nav_requirements() { .iter() .any(|item| item.page == SettingsPage::AppearanceLanguage) ); + assert!( + appearance + .items + .iter() + .any(|item| item.page == SettingsPage::AppearanceMenu) + ); assert!( specs diff --git a/termua/src/window/settings/view.rs b/termua/src/window/settings/view.rs index ab70fa6..9c5ccd4 100644 --- a/termua/src/window/settings/view.rs +++ b/termua/src/window/settings/view.rs @@ -56,6 +56,7 @@ macro_rules! settings_supported_id_matches { | "lock_screen.timeout_secs" | "appearance.theme" | "appearance.language" + | "appearance.menu_auto_collapse" | "appearance.light_theme" | "appearance.dark_theme" | "terminal.default_backend" @@ -1587,6 +1588,15 @@ impl SettingsWindow { match id { "appearance.theme" => Some(self.render_appearance_theme_control(cx)), "appearance.language" => Some(self.render_appearance_language_control(cx)), + "appearance.menu_auto_collapse" => Some(self.render_bool_switch( + "settings-appearance-menu-auto-collapse", + self.settings.appearance.menu_auto_collapse, + |this, checked, window, cx| { + this.settings.appearance.menu_auto_collapse = checked; + this.apply_and_save(window, cx); + }, + cx, + )), "appearance.light_theme" => { Some(self.render_theme_dropdown(ThemeDropdownKind::Light, window, cx)) } From 3182d4fa4906d58851b9ad0b5295713cf529ca54 Mon Sep 17 00:00:00 2001 From: iamazy Date: Sun, 23 Aug 2026 17:54:54 +0800 Subject: [PATCH 3/8] Update render.rs --- termua/src/window/settings/render.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/termua/src/window/settings/render.rs b/termua/src/window/settings/render.rs index 9fdfbc0..cf22666 100644 --- a/termua/src/window/settings/render.rs +++ b/termua/src/window/settings/render.rs @@ -400,7 +400,6 @@ impl SettingsWindow { ) -> AnyElement { match page { SettingsPage::AppearanceTheme => self.render_appearance_theme_page_heading(heading, cx), - SettingsPage::AppearanceMenu => div().child(heading).into_any_element(), _ => self.render_simple_page_heading(heading, cx), } } From 22e1c857ccea732013b2b716078950d3c99666b5 Mon Sep 17 00:00:00 2001 From: iamazy Date: Sun, 23 Aug 2026 18:46:08 +0800 Subject: [PATCH 4/8] fix: keep selection focus within terminal --- crates/gpui_term/src/element.rs | 23 +++++++++++++++++++++++ crates/gpui_term/src/lib.rs | 5 +++++ crates/gpui_term/src/view/render.rs | 6 ++++++ 3 files changed, 34 insertions(+) diff --git a/crates/gpui_term/src/element.rs b/crates/gpui_term/src/element.rs index dc725d1..865c6c9 100644 --- a/crates/gpui_term/src/element.rs +++ b/crates/gpui_term/src/element.rs @@ -558,6 +558,17 @@ impl InteractiveElement for TerminalElement { impl StatefulInteractiveElement for TerminalElement {} impl TerminalElement { + fn owns_selection_drag(terminal_view: &Entity, cx: &App) -> bool { + cx.try_global::() + .is_some_and(|owner| owner.0 == Some(terminal_view.entity_id())) + } + + fn clear_selection_drag_owner(terminal_view: &Entity, cx: &mut App) { + if Self::owns_selection_drag(terminal_view, cx) { + cx.set_global(crate::TerminalSelectionOwner(None)); + } + } + pub fn new( terminal: Entity, terminal_view: Entity, @@ -693,6 +704,9 @@ impl TerminalElement { terminal_view.update(cx, |view: &mut TerminalView, _| { view.set_mouse_left_down_in_terminal(true); }); + cx.set_global(crate::TerminalSelectionOwner(Some( + terminal_view.entity_id(), + ))); let scroll_top = terminal_view.read(cx).scroll_top(); terminal.update(cx, |terminal, cx| { @@ -759,6 +773,7 @@ impl TerminalElement { ); cx.notify(); }); + Self::clear_selection_drag_owner(terminal_view, cx); true } @@ -774,6 +789,10 @@ impl TerminalElement { window: &mut Window, cx: &mut App, ) { + if !Self::owns_selection_drag(terminal_view, cx) { + return; + } + // If the drag started in this terminal view, keep updating the selection even if the // cursor leaves the terminal hitbox (or focus changes). if !e.dragging() @@ -943,6 +962,7 @@ impl TerminalElement { }); if was_scrollbar_dragging { + Self::clear_selection_drag_owner(&terminal_view, cx); terminal_view.update(cx, |_, view_cx| view_cx.notify()); return; } @@ -951,6 +971,7 @@ impl TerminalElement { terminal.mouse_up(e, cx); cx.notify(); }); + Self::clear_selection_drag_owner(&terminal_view, cx); } }); } @@ -983,6 +1004,7 @@ impl TerminalElement { }); if was_scrollbar_dragging { + Self::clear_selection_drag_owner(&terminal_view, cx); terminal_view.update(cx, |_, view_cx| view_cx.notify()); return; } @@ -991,6 +1013,7 @@ impl TerminalElement { terminal.mouse_up(e, cx); cx.notify(); }); + Self::clear_selection_drag_owner(&terminal_view, cx); } }); } diff --git a/crates/gpui_term/src/lib.rs b/crates/gpui_term/src/lib.rs index c585132..5c44aeb 100644 --- a/crates/gpui_term/src/lib.rs +++ b/crates/gpui_term/src/lib.rs @@ -4,6 +4,11 @@ use bitflags::bitflags; rust_i18n::i18n!("../../locales"); +#[derive(Clone, Copy, Default)] +pub(crate) struct TerminalSelectionOwner(pub(crate) Option); + +impl gpui::Global for TerminalSelectionOwner {} + mod backends; mod builder; pub mod cast; diff --git a/crates/gpui_term/src/view/render.rs b/crates/gpui_term/src/view/render.rs index ffeed40..214fb48 100644 --- a/crates/gpui_term/src/view/render.rs +++ b/crates/gpui_term/src/view/render.rs @@ -91,6 +91,12 @@ impl TerminalView { // Hovering a visible terminal should make it the active input target. The // terminal view only receives this event when it is the topmost hit element, so // covered terminals cannot steal focus from the panel above them. + if cx + .try_global::() + .is_some_and(|owner| owner.0.is_some()) + { + return; + } window.focus(&this.focus_handle, cx); })) } From 351ad8a010c2d3c7d7291234599ce7017113a408 Mon Sep 17 00:00:00 2001 From: iamazy Date: Sun, 23 Aug 2026 20:51:29 +0800 Subject: [PATCH 5/8] Update meta.rs --- termua/src/window/settings/meta.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/termua/src/window/settings/meta.rs b/termua/src/window/settings/meta.rs index eb2bf06..6d1c445 100644 --- a/termua/src/window/settings/meta.rs +++ b/termua/src/window/settings/meta.rs @@ -81,7 +81,7 @@ static ALL_SETTINGS_META: &[SettingMeta] = &[ SettingMeta { id: "appearance.menu_auto_collapse", title: "Auto-collapse menu", - description: "Collapse the application menu to the Menu button when it is inactive.", + description: "Whether to automatically hide the application menu.", keywords: &["appearance", "menu", "menubar", "collapse", "fold"], section: SettingsNavSection::Appearance, page: SettingsPage::AppearanceMenu, From 35458c54da44ca96b7432c0e98068734ebdd7b82 Mon Sep 17 00:00:00 2001 From: iamazy Date: Sun, 23 Aug 2026 21:00:24 +0800 Subject: [PATCH 6/8] fix --- locales/en.yml | 3 +++ locales/zh-CN.yml | 3 +++ 2 files changed, 6 insertions(+) diff --git a/locales/en.yml b/locales/en.yml index 1e8523b..b6c1cfd 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -448,6 +448,9 @@ Settings: language: Title: "Language" Description: "Choose the application UI language." + menu_auto_collapse: + Title: "Auto-Collapse Menu" + Description: "Whether to automatically collapse the application menu." terminal: default_backend: Title: "Default backend" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 91cdbec..12f7844 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -448,6 +448,9 @@ Settings: language: Title: "语言" Description: "选择应用界面语言。" + menu_auto_collapse: + Title: "自动折叠菜单" + Description: "是否自动折叠菜单。" terminal: default_backend: Title: "默认后端" From e89b1059b6207c585cb9fa8fae3c0ed701628b8e Mon Sep 17 00:00:00 2001 From: iamazy Date: Sun, 23 Aug 2026 22:27:28 +0800 Subject: [PATCH 7/8] fix dialog --- termua/src/panel/assistant_panel.rs | 69 ++++++++++++++------ termua/src/window/main_window/actions.rs | 41 +++++++++--- termua/src/window/main_window/actions/ssh.rs | 18 +++-- termua/src/window/main_window/tests.rs | 10 +++ 4 files changed, 103 insertions(+), 35 deletions(-) diff --git a/termua/src/panel/assistant_panel.rs b/termua/src/panel/assistant_panel.rs index 83bdef0..c71c9be 100644 --- a/termua/src/panel/assistant_panel.rs +++ b/termua/src/panel/assistant_panel.rs @@ -8,6 +8,7 @@ use gpui_common::TermuaIcon; use gpui_component::{ ActiveTheme as _, Disableable as _, Icon, IconName, Sizable as _, button::{Button, ButtonVariants as _}, + dialog::{DialogAction, DialogClose, DialogFooter}, h_flex, input::{Textarea, TextareaState}, menu::{DropdownMenu as _, PopupMenu, PopupMenuItem}, @@ -1038,19 +1039,27 @@ impl AssistantPanelView { cx: &mut Context, ) { root.open_dialog( - move |dialog, _window, _app| { + move |dialog, _window, app| { + let cancel_button = Button::new("termua-assistant-run-command-cancel") + .label(t!("Assistant.Dialog.RunInTerminalCancel").to_string()) + .debug_selector(|| "termua-assistant-run-command-cancel".to_string()); + let run_button = Button::new("termua-assistant-run-command-run") + .label(t!("Assistant.Dialog.RunInTerminalOk").to_string()) + .primary() + .debug_selector(|| "termua-assistant-run-command-run".to_string()); + dialog .title(t!("Assistant.Dialog.RunInTerminalTitle").to_string()) .w(px(720.)) .child(Self::run_command_dialog_body( target_label.clone(), &command, + app, )) - .button_props( - gpui_component::dialog::DialogButtonProps::default() - .ok_text(t!("Assistant.Dialog.RunInTerminalOk").to_string()) - .cancel_text(t!("Assistant.Dialog.RunInTerminalCancel").to_string()) - .show_cancel(true), + .footer( + DialogFooter::new() + .child(DialogClose::new().child(cancel_button)) + .child(DialogAction::new().child(run_button)), ) .on_ok({ let this = this.clone(); @@ -1073,15 +1082,25 @@ impl AssistantPanelView { ); } - fn run_command_dialog_body(target_label: String, command: &str) -> AnyElement { + fn run_command_dialog_body(target_label: String, command: &str, app: &App) -> AnyElement { let command_md = format!("```sh\n{command}\n```"); + let field_label_color = app.theme().muted_foreground; + let card_border = app.theme().border.opacity(0.8); + let card_background = app.theme().border.opacity(0.12); + v_flex() - .gap_2() + .pt_2() + .gap_4() .child( h_flex() - .gap_2() - .items_start() - .child(div().child(t!("Assistant.Label.Target").to_string())) + .gap_3() + .items_center() + .child( + div() + .text_xs() + .text_color(field_label_color) + .child(t!("Assistant.Label.Target").to_string()), + ) .child( div().min_w_0().child( TextView::markdown("termua-assistant-run-target", target_label) @@ -1090,15 +1109,27 @@ impl AssistantPanelView { ), ) .child( - h_flex() - .gap_2() - .items_start() - .child(div().child(t!("Assistant.Label.Command").to_string())) + v_flex() + .gap_1() .child( - div().min_w_0().child( - TextView::markdown("termua-assistant-run-command", command_md) - .selectable(true), - ), + div() + .text_xs() + .text_color(field_label_color) + .child(t!("Assistant.Label.Command").to_string()), + ) + .child( + div() + .w_full() + .min_w_0() + .p_3() + .rounded_lg() + .border_1() + .border_color(card_border) + .bg(card_background) + .child( + TextView::markdown("termua-assistant-run-command", command_md) + .selectable(true), + ), ), ) .into_any_element() diff --git a/termua/src/window/main_window/actions.rs b/termua/src/window/main_window/actions.rs index 470569b..d1fcee4 100644 --- a/termua/src/window/main_window/actions.rs +++ b/termua/src/window/main_window/actions.rs @@ -133,20 +133,41 @@ impl TermuaWindow { gpui_component::Root::update(window, app, |root, window, cx| { root.open_dialog( move |dialog, _window, _app| { + let cancel_button = Button::new("termua-quit-confirm-cancel") + .label(t!("MainWindow.QuitConfirm.Button.Cancel").to_string()) + .debug_selector(|| "termua-quit-confirm-cancel".to_string()); + let quit_button = Button::new("termua-quit-confirm-quit") + .label(t!("MainWindow.QuitConfirm.Button.Quit").to_string()) + .primary() + .debug_selector(|| "termua-quit-confirm-quit".to_string()); + dialog - .title(t!("MainWindow.QuitConfirm.Title").to_string()) + .title( + h_flex() + .gap_2() + .items_center() + .child( + Icon::default() + .path(TermuaIcon::AlertCircle) + .text_color(_app.theme().warning), + ) + .child(t!("MainWindow.QuitConfirm.Title").to_string()), + ) .child( - div() + h_flex() + .gap_3() + .items_start() .debug_selector(|| "termua-quit-confirm-body".to_string()) - .child(t!("MainWindow.QuitConfirm.Body").to_string()), + .child( + div() + .flex_1() + .child(t!("MainWindow.QuitConfirm.Body").to_string()), + ), ) - .button_props( - gpui_component::dialog::DialogButtonProps::default() - .ok_text(t!("MainWindow.QuitConfirm.Button.Quit").to_string()) - .cancel_text( - t!("MainWindow.QuitConfirm.Button.Cancel").to_string(), - ) - .show_cancel(true), + .footer( + DialogFooter::new() + .child(DialogClose::new().child(cancel_button)) + .child(DialogAction::new().child(quit_button)), ) .on_ok(|_, _window, app| { app.quit(); diff --git a/termua/src/window/main_window/actions/ssh.rs b/termua/src/window/main_window/actions/ssh.rs index 3d25e73..89d6aff 100644 --- a/termua/src/window/main_window/actions/ssh.rs +++ b/termua/src/window/main_window/actions/ssh.rs @@ -8,7 +8,7 @@ use gpui_common::TermuaIcon; use gpui_component::{ Icon, button::{Button, ButtonVariants}, - dialog::DialogFooter, + dialog::{DialogAction, DialogClose, DialogFooter}, h_flex, v_flex, }; use gpui_dock::{DockPlacement, PanelView}; @@ -50,16 +50,22 @@ impl TermuaWindow { move |dialog, _window, app| { let decision_tx_ok = decision_tx.clone(); let decision_tx_cancel = decision_tx.clone(); + let reject_button = Button::new("termua-ssh-host-verify-reject") + .label(t!("SshHostVerify.Button.Reject").to_string()) + .debug_selector(|| "termua-ssh-host-verify-reject".to_string()); + let trust_button = Button::new("termua-ssh-host-verify-trust") + .label(t!("SshHostVerify.Button.TrustContinue").to_string()) + .primary() + .debug_selector(|| "termua-ssh-host-verify-trust".to_string()); dialog .title(Self::ssh_host_verification_dialog_title(app)) .w(px(720.)) .child(Self::ssh_host_verification_dialog_body(&target, &message)) - .button_props( - gpui_component::dialog::DialogButtonProps::default() - .ok_text(t!("SshHostVerify.Button.TrustContinue").to_string()) - .cancel_text(t!("SshHostVerify.Button.Reject").to_string()) - .show_cancel(true), + .footer( + DialogFooter::new() + .child(DialogClose::new().child(reject_button)) + .child(DialogAction::new().child(trust_button)), ) .on_ok(move |_, _window, _app| { let _ = decision_tx_ok.try_send(true); diff --git a/termua/src/window/main_window/tests.rs b/termua/src/window/main_window/tests.rs index 8481bf5..7f07af0 100644 --- a/termua/src/window/main_window/tests.rs +++ b/termua/src/window/main_window/tests.rs @@ -1337,6 +1337,16 @@ fn request_quit_with_open_tabs_requires_confirmation(cx: &mut gpui::TestAppConte window_cx.debug_bounds("termua-quit-confirm-body").is_some(), "expected quit confirmation dialog when tabs are open" ); + assert!( + window_cx + .debug_bounds("termua-quit-confirm-cancel") + .is_some(), + "expected quit confirmation dialog to render a Cancel button" + ); + assert!( + window_cx.debug_bounds("termua-quit-confirm-quit").is_some(), + "expected quit confirmation dialog to render a Quit button" + ); } #[cfg_attr(target_os = "macos", ignore)] From 0d5d84fe107888f44877757da0f9a6750c15ffcc Mon Sep 17 00:00:00 2001 From: iamazy Date: Mon, 24 Aug 2026 08:35:29 +0800 Subject: [PATCH 8/8] fix: ignore menu settings on macos --- termua/src/window/settings/meta.rs | 1 + termua/src/window/settings/state.rs | 1 + termua/src/window/settings/tests.rs | 12 ++++++++++++ termua/src/window/settings/view.rs | 3 +-- 4 files changed, 15 insertions(+), 2 deletions(-) diff --git a/termua/src/window/settings/meta.rs b/termua/src/window/settings/meta.rs index 6d1c445..edccd38 100644 --- a/termua/src/window/settings/meta.rs +++ b/termua/src/window/settings/meta.rs @@ -78,6 +78,7 @@ static ALL_SETTINGS_META: &[SettingMeta] = &[ section: SettingsNavSection::Appearance, page: SettingsPage::AppearanceLanguage, }, + #[cfg(not(target_os = "macos"))] SettingMeta { id: "appearance.menu_auto_collapse", title: "Auto-collapse menu", diff --git a/termua/src/window/settings/state.rs b/termua/src/window/settings/state.rs index d38a168..0b18305 100644 --- a/termua/src/window/settings/state.rs +++ b/termua/src/window/settings/state.rs @@ -173,6 +173,7 @@ const SETTINGS_PAGE_SPECS: &[SettingsPageSpec] = &[ hint_key: None, is_sidebar_item: true, }, + #[cfg(not(target_os = "macos"))] SettingsPageSpec { section: SettingsNavSection::Appearance, item_label_key: "Settings.Appearance.Menu", diff --git a/termua/src/window/settings/tests.rs b/termua/src/window/settings/tests.rs index 1e35c2a..23b2bdb 100644 --- a/termua/src/window/settings/tests.rs +++ b/termua/src/window/settings/tests.rs @@ -2366,6 +2366,7 @@ fn setting_metadata_covers_all_controls() { assert!(ids.contains("appearance.light_theme")); assert!(ids.contains("appearance.dark_theme")); assert!(ids.contains("appearance.language")); + #[cfg(not(target_os = "macos"))] assert!(ids.contains("appearance.menu_auto_collapse")); // Terminal / Font @@ -2453,7 +2454,10 @@ fn sidebar_nav_specs_match_settings_nav_requirements() { .iter() .find(|group| group.section == SettingsNavSection::Appearance) .expect("expected Appearance group"); + #[cfg(not(target_os = "macos"))] assert_eq!(appearance.items.len(), 3); + #[cfg(target_os = "macos")] + assert_eq!(appearance.items.len(), 2); assert!( appearance .items @@ -2466,12 +2470,20 @@ fn sidebar_nav_specs_match_settings_nav_requirements() { .iter() .any(|item| item.page == SettingsPage::AppearanceLanguage) ); + #[cfg(not(target_os = "macos"))] assert!( appearance .items .iter() .any(|item| item.page == SettingsPage::AppearanceMenu) ); + #[cfg(target_os = "macos")] + assert!( + !appearance + .items + .iter() + .any(|item| item.page == SettingsPage::AppearanceMenu) + ); assert!( specs diff --git a/termua/src/window/settings/view.rs b/termua/src/window/settings/view.rs index 9c5ccd4..c0d32be 100644 --- a/termua/src/window/settings/view.rs +++ b/termua/src/window/settings/view.rs @@ -56,7 +56,6 @@ macro_rules! settings_supported_id_matches { | "lock_screen.timeout_secs" | "appearance.theme" | "appearance.language" - | "appearance.menu_auto_collapse" | "appearance.light_theme" | "appearance.dark_theme" | "terminal.default_backend" @@ -100,7 +99,7 @@ macro_rules! settings_supported_id_matches { | "assistant.provider_timeout_secs" | "assistant.extra_headers" | "assistant.api_key" - ) + ) || (cfg!(not(target_os = "macos")) && $id == "appearance.menu_auto_collapse") }; }