From d1f224f6345fcb1e97314c0e766f66ffdef4da0b Mon Sep 17 00:00:00 2001 From: hhdebb Date: Thu, 10 Sep 2026 18:29:00 +0800 Subject: [PATCH 1/2] fix(window): hide the title bar in fullscreen where it is the app's to draw MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Windows and Linux a fullscreen window has no caption. The platform asks what is under the pointer through `WM_NCHITTEST`, gpui answers from the window control hitboxes the frame registered, and fullscreen clears `WS_CAPTION` — there is nothing left to answer with. Measured on a fullscreen tty7: `GetWindowLong` reports `WS_CAPTION` clear, and every point along the top of the window comes back `HTCLIENT`, where the same window a moment earlier answered `HTCAPTION`, `HTMINBUTTON` and `HTCLOSE`. The bar was drawn anyway. `WindowControls` renders minimize, maximize and close whenever the target is not macOS, without asking whether the window is fullscreen, so all three sat there taking hover styling — gpui's own dispatch reaches them fine — and doing nothing at all when clicked. Dragging the bar did nothing either. So on those two the bar goes. It is the app's own chrome there: a caption to move the window by and the controls at its end, none of which a fullscreen window has. Drawing chrome that cannot work is worse than drawing none. Not on macOS, and the reason is not that the bug is milder there — it is that the premise does not hold at all. `WindowControls` draws none of the three on macOS; the ones that go dead elsewhere are the system's traffic lights, and the system hides them itself. What that bar does in fullscreen is hold the band the system reserves: the traffic lights land on it when the menu bar is revealed, and so does the translucent strip drawn under the menu bar. Take the bar away and that strip lands on the terminal and covers its first row instead — measured, and the difference is exactly `TITLE_BAR_HEIGHT`. Fullscreen belongs to the system on macOS, and the bar is part of how the system dresses the window rather than something broken. Nothing on the bar becomes unreachable where it goes. Its controls are actions first, dispatched from the window's root rather than from the bar, and each has a chord or a seat in the palette, which has one; `what_the_title_bar_offers_is_reachable_without_it` is that in a test. Worth noting for anyone reading it: `ToggleTabSidebar` ships with no chord, so in fullscreen the palette is how it is reached. Entering says how to leave, because entering is the instant the bar disappears — so only where it does, and only through the action: a window that starts fullscreen because the setting says so is not a surprise anybody needs explaining, and `startup_mode` is untouched by the toggle either way. The chord comes from the keymap rather than from a string, so it reads `F11` or whatever it was rebound to. The notice carries an id, which is what keeps a held-down `F11` to one notice rather than a column of identical ones: pushing under an id already on screen replaces that one. Leaving through the action takes it back as well. Leaving some other way lets it time out instead — a second or two of a stale notice, which is not worth a per-frame watch on a state that lies: `toggle_fullscreen` is spawned onto the executor on every backend, so `is_fullscreen` still reports the old value when the action returns, and a render-time test for "not fullscreen now" can take the notice back before it has been seen. Verified on Windows 11 26200, and on macOS 26.5.2 by a second pair of hands: the macOS half of this is the reason the change is not applied there. Linux is reasoned about rather than measured — it draws its own chrome the way Windows does, and the same `WM_NCHITTEST`-shaped question is answered through gpui's window control hitboxes. --- src/ui/app.rs | 89 ++++++++++++++++++++++++++++++++++++++++------ src/ui/i18n/en.rs | 4 +++ src/ui/i18n/ja.rs | 2 ++ src/ui/i18n/mod.rs | 2 ++ src/ui/i18n/zh.rs | 2 ++ src/ui/keymap.rs | 49 +++++++++++++++++++++++++ 6 files changed, 137 insertions(+), 11 deletions(-) diff --git a/src/ui/app.rs b/src/ui/app.rs index 8409a7c5..2a2c7a57 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -1166,6 +1166,22 @@ fn clear_window_override_values(config: &mut Config, backdrop_is_local: bool) { } } +/// The id the fullscreen hint is pushed under, so that entering again replaces +/// it and leaving takes it away. +struct FullscreenHint; + +/// Whether fullscreen takes the title bar away on this platform. +/// +/// Not on macOS, where fullscreen belongs to the system rather than to the app. +/// The traffic lights live on that bar, and revealing the menu bar draws a +/// translucent strip over the same band, which the bar absorbs; without it the +/// strip lands on the terminal instead and covers its first row. There is also +/// nothing there to fix: `WindowControls` draws no minimize, maximize or close +/// on macOS — the three that are dead in fullscreen elsewhere are the system's +/// there, and it hides them itself. So the bar is not broken chrome on macOS, +/// it is part of how the system dresses a fullscreen window. +const FULLSCREEN_TAKES_THE_TITLE_BAR: bool = !cfg!(target_os = "macos"); + impl Tty7App { pub fn for_workspace( id: Option, @@ -3526,6 +3542,46 @@ impl Tty7App { remember_leaf_in(&mut self.tabs, leaf); } + /// Toggle fullscreen, and say how to leave it on the way in. + /// + /// Only on the way in, and only from the action: entering is an instant in + /// which the title bar disappears, and a window that starts fullscreen + /// because the setting says so is not a surprise anybody needs explaining. + /// The chord comes from the keymap rather than from a string, because it is + /// `F11` on Windows and Linux, `Cmd+Enter` on macOS, and either of them may + /// have been rebound. + /// + /// The hint carries an id of its own, which is what keeps a held-down + /// `F11` to one notice rather than a column of identical ones: pushing + /// under an id already on screen replaces that one. Leaving through the + /// action takes it back too, so a quick in-and-out does not leave the way + /// out on screen after it has been taken. Leaving some other way — a + /// window manager with a chord of its own — just lets it time out, which + /// is a second or two of a stale notice and not worth watching every + /// frame for. + fn toggle_fullscreen(&self, window: &mut Window, cx: &mut App) { + let entering = !window.is_fullscreen(); + window.toggle_fullscreen(); + window.remove_notification::(cx); + // Nothing disappeared where the bar stays, so there is nothing to + // explain. + if !entering || !FULLSCREEN_TAKES_THE_TITLE_BAR { + return; + } + let hint = match crate::ui::home::key_hint("ToggleFullscreen", cx) { + Some(chord) => t_fmt(L10nKey::AppFullscreenEntered, &[("key", &chord)]), + // Rebound to nothing at all: still worth saying the bar is gone, + // just without naming a key that would not work. + None => t(L10nKey::AppFullscreenEnteredNoKey).to_string(), + }; + window.push_notification( + gpui_component::notification::Notification::new() + .id::() + .message(hint), + cx, + ); + } + fn focus_leaf(&self, leaf: &PaneSlot, window: &mut Window, cx: &mut App) { let handle = leaf.focus_handle(cx); window.focus(&handle, cx); @@ -5358,7 +5414,7 @@ impl Tty7App { NextTab => self.cycle_tab(true, window, cx), PrevTab => self.cycle_tab(false, window, cx), ToggleMaximizePane => self.toggle_maximize(window, cx), - ToggleFullscreen => window.toggle_fullscreen(), + ToggleFullscreen => self.toggle_fullscreen(window, cx), ToggleTabSidebar => self.toggle_tab_sidebar(cx), ToggleLeftPanel => self.toggle_left_panel(cx), ToggleRightPanel => self.toggle_right_panel(cx), @@ -7609,11 +7665,22 @@ impl Render for Tty7App { } }; - let title_bar = TitleBar::new() - .h(px(TITLE_BAR_HEIGHT)) - .bg(cx.theme().transparent) - .border_color(cx.theme().transparent) - .child(strip); + // No title bar in fullscreen. The bar is window chrome — a caption to + // drag the window by and the three controls at its end — and a + // fullscreen window has none of that to offer: it has no caption for + // the platform to hit-test, so the buttons draw, light up under the + // pointer and do nothing at all when clicked. Drawing chrome that + // cannot work is worse than drawing none, and taking it away is also + // what the mode is for. + let fullscreen = window.is_fullscreen(); + let bar_is_gone = fullscreen && FULLSCREEN_TAKES_THE_TITLE_BAR; + let title_bar = (!bar_is_gone).then(|| { + TitleBar::new() + .h(px(TITLE_BAR_HEIGHT)) + .bg(cx.theme().transparent) + .border_color(cx.theme().transparent) + .child(strip) + }); let body_area = div() .flex_1() .relative() @@ -7717,9 +7784,9 @@ impl Render for Tty7App { let panel_below_title_bar = (right_panel.is_some() || document_column.is_some()) && !cfg!(target_os = "macos"); let (column_title_bar, spanning_title_bar) = if panel_below_title_bar { - (None, Some(title_bar)) + (None, title_bar) } else { - (Some(title_bar), None) + (title_bar, None) }; let (column_overlays, hoisted_overlays) = if panel_below_title_bar { (Vec::new(), overlays) @@ -8012,9 +8079,9 @@ impl Render for Tty7App { .on_action(cx.listener(|this, _: &ToggleMaximizePane, window, cx| { this.toggle_maximize(window, cx) })) - .on_action( - cx.listener(|_, _: &ToggleFullscreen, window, _cx| window.toggle_fullscreen()), - ) + .on_action(cx.listener(|this, _: &ToggleFullscreen, window, cx| { + this.toggle_fullscreen(window, cx) + })) .on_action(cx.listener(|this, _: &ToggleTabSidebar, _window, cx| { this.toggle_tab_sidebar(cx) })) diff --git a/src/ui/i18n/en.rs b/src/ui/i18n/en.rs index 4884935d..516967b2 100644 --- a/src/ui/i18n/en.rs +++ b/src/ui/i18n/en.rs @@ -1577,6 +1577,10 @@ pub fn translate_en(key: L10nKey) -> &'static str { L10nKey::AppReopenTabFailed => "Could not reopen the tab: no terminal started", L10nKey::AppOpenTerminalFailed => "Could not open a terminal: {error}", L10nKey::AppTabsNotRestored => "{count} tabs from last time could not be reopened", + L10nKey::AppFullscreenEntered => "Fullscreen — press {key} to leave", + L10nKey::AppFullscreenEnteredNoKey => { + "Fullscreen — the title bar is hidden until you leave" + } L10nKey::LaunchWorkspacesLeftRunning => { "Only this window was restored — {count} workspaces are still running in the background. Reopen them from the sidebar." } diff --git a/src/ui/i18n/ja.rs b/src/ui/i18n/ja.rs index d4e30d71..c9e4c8a2 100644 --- a/src/ui/i18n/ja.rs +++ b/src/ui/i18n/ja.rs @@ -1638,6 +1638,8 @@ pub fn translate_ja(key: L10nKey) -> Option<&'static str> { L10nKey::AppReopenTabFailed => "タブを開き直せませんでした: ターミナルが起動しませんでした", L10nKey::AppOpenTerminalFailed => "ターミナルを開けませんでした: {error}", L10nKey::AppTabsNotRestored => "前回のタブ {count} 個を開き直せませんでした", + L10nKey::AppFullscreenEntered => "全画面表示 — 解除するには {key}", + L10nKey::AppFullscreenEnteredNoKey => "全画面表示 — 解除するまでタイトルバーは非表示です", L10nKey::LaunchWorkspacesLeftRunning => { "このウィンドウだけを復元しました — あと {count} 個のワークスペースがバックグラウンドで実行中です。サイドバーから開き直せます。" } diff --git a/src/ui/i18n/mod.rs b/src/ui/i18n/mod.rs index 627f7c2c..a4a585af 100644 --- a/src/ui/i18n/mod.rs +++ b/src/ui/i18n/mod.rs @@ -1276,6 +1276,8 @@ l10n_keys! { AppReopenTabFailed, AppOpenTerminalFailed, AppTabsNotRestored, + AppFullscreenEntered, + AppFullscreenEnteredNoKey, LaunchWorkspacesLeftRunning, AppSshConnectionFailed, AppSshReconnectFailed, diff --git a/src/ui/i18n/zh.rs b/src/ui/i18n/zh.rs index 8b53c7a7..77b0b9d2 100644 --- a/src/ui/i18n/zh.rs +++ b/src/ui/i18n/zh.rs @@ -1493,6 +1493,8 @@ pub fn translate_zh(key: L10nKey) -> Option<&'static str> { L10nKey::AppReopenTabFailed => "无法重新打开标签页:没有启动终端", L10nKey::AppOpenTerminalFailed => "无法打开终端:{error}", L10nKey::AppTabsNotRestored => "上次的 {count} 个标签页没能重新打开", + L10nKey::AppFullscreenEntered => "已进入全屏 —— 按 {key} 退出", + L10nKey::AppFullscreenEnteredNoKey => "已进入全屏 —— 标题栏在退出前会一直隐藏", L10nKey::LaunchWorkspacesLeftRunning => { "只恢复了这个窗口——还有 {count} 个工作区在后台运行,可从侧边栏重新打开。" } diff --git a/src/ui/keymap.rs b/src/ui/keymap.rs index 6fb2d808..3acdbb31 100644 --- a/src/ui/keymap.rs +++ b/src/ui/keymap.rs @@ -1339,6 +1339,55 @@ mod tests { use super::*; use gpui::Action as _; + /// Everything the title bar offers a button for stays reachable from the + /// keyboard, because on Windows and Linux the title bar is not drawn in + /// fullscreen at all — it is window chrome there, and a fullscreen window + /// has no chrome for the platform to hit-test, so its buttons would light + /// up under the pointer and do nothing when clicked. (On macOS the bar + /// stays: fullscreen is the system's there, and the bar is where it puts + /// the traffic lights.) + /// + /// Reachable means either a chord of its own or a seat in the palette, + /// which has one; both are hands-free, and the palette is how the sidebar + /// toggle is reached, since it ships without a chord. What this pins is + /// that a control on that bar is never mouse-only — if one ever is, + /// hiding the bar would take a feature away with it, and this is where + /// that should be noticed. + #[test] + fn what_the_title_bar_offers_is_reachable_without_it() { + let defaults = default_bindings(); + let chord = |action: &str| { + defaults + .iter() + .any(|(name, keystroke)| *name == action && !keystroke.is_empty()) + }; + // The palette is the fallback, so it is the one that must not be. + assert!( + chord("TogglePalette"), + "the fallback needs a chord of its own" + ); + for action in [ + "NewTab", + "ToggleTabSidebar", + "OpenSettings", + "ToggleFullscreen", + "ToggleSwitcher", + ] { + assert!( + chord(action) || palette_lists(action), + "{action} would be mouse-only once the bar is hidden" + ); + } + } + + /// Whether the palette lists `action` under a name somebody wrote, which is + /// what having a real seat there means: `action_entry` answers for every + /// action, falling back to a name split on capitals, and a fallback name is + /// not evidence that anyone meant the action to be found. + fn palette_lists(action: &str) -> bool { + authored_entry(action).is_some() + } + /// The actions a keymap built from `action_bindings` dispatches for `keys` /// typed in `context`, in precedence order — the same lookup gpui performs /// on a real keypress. From 1f189e2ee0b91cea8fdaaaa09ff5a7c9433fabac Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Mon, 14 Sep 2026 18:35:15 +0800 Subject: [PATCH 2/2] fix(window): keep the title bar row in fullscreen, drop only its window buttons Hiding the whole title bar took the tab strip with it: with tabs on top every chip, the New Tab tile and the panel/menu tiles vanished in fullscreen, the docked document header (drawn only over the spanning bar) disappeared, and the strip's drop band kept claiming a row that was now terminal. What is actually dead in fullscreen is minimize/maximize/close. The row now stays; in fullscreen off macOS the strip goes into a plain row of the same geometry instead of `TitleBar`, which always draws those buttons, and the room reserved for them (strip width, chrome band over the panel, document header padding) comes back. The notice text says the window buttons are hidden rather than the title bar, and the keymap test whose premise was the bar disappearing is replaced by one pinning the controls width. --- src/ui/app.rs | 116 +++++++++++++++++++++++++++++++------------- src/ui/i18n/en.rs | 2 +- src/ui/i18n/ja.rs | 4 +- src/ui/i18n/zh.rs | 2 +- src/ui/keymap.rs | 49 ------------------- src/ui/tab_strip.rs | 13 ++--- 6 files changed, 94 insertions(+), 92 deletions(-) diff --git a/src/ui/app.rs b/src/ui/app.rs index 2a2c7a57..0bce6917 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -1170,17 +1170,26 @@ fn clear_window_override_values(config: &mut Config, backdrop_is_local: bool) { /// it and leaving takes it away. struct FullscreenHint; -/// Whether fullscreen takes the title bar away on this platform. +/// Whether the title bar carries minimize, maximize and close right now. /// -/// Not on macOS, where fullscreen belongs to the system rather than to the app. -/// The traffic lights live on that bar, and revealing the menu bar draws a -/// translucent strip over the same band, which the bar absorbs; without it the -/// strip lands on the terminal instead and covers its first row. There is also -/// nothing there to fix: `WindowControls` draws no minimize, maximize or close -/// on macOS — the three that are dead in fullscreen elsewhere are the system's -/// there, and it hides them itself. So the bar is not broken chrome on macOS, -/// it is part of how the system dresses a fullscreen window. -const FULLSCREEN_TAKES_THE_TITLE_BAR: bool = !cfg!(target_os = "macos"); +/// Not in fullscreen. A fullscreen window has no caption: Windows clears +/// `WS_CAPTION` and answers `HTCLIENT` along the whole top edge, so the three +/// buttons would draw, light up under the pointer and do nothing when clicked. +/// The row they sit at the end of stays, because it is also the tab strip. +/// +/// Never on macOS, which draws no buttons of its own: those are the system's +/// traffic lights, and the system hides them itself. +pub(crate) fn window_controls_drawn(fullscreen: bool) -> bool { + !cfg!(target_os = "macos") && !fullscreen +} + +/// How much of the title bar's trailing end the window buttons take. +pub(crate) fn window_controls_w(fullscreen: bool) -> f32 { + match window_controls_drawn(fullscreen) { + true => WINDOW_CONTROLS_W, + false => 0., + } +} impl Tty7App { pub fn for_workspace( @@ -3545,7 +3554,7 @@ impl Tty7App { /// Toggle fullscreen, and say how to leave it on the way in. /// /// Only on the way in, and only from the action: entering is an instant in - /// which the title bar disappears, and a window that starts fullscreen + /// which the window buttons disappear, and a window that starts fullscreen /// because the setting says so is not a surprise anybody needs explaining. /// The chord comes from the keymap rather than from a string, because it is /// `F11` on Windows and Linux, `Cmd+Enter` on macOS, and either of them may @@ -3563,14 +3572,14 @@ impl Tty7App { let entering = !window.is_fullscreen(); window.toggle_fullscreen(); window.remove_notification::(cx); - // Nothing disappeared where the bar stays, so there is nothing to - // explain. - if !entering || !FULLSCREEN_TAKES_THE_TITLE_BAR { + // Nothing disappeared where there were no buttons to begin with, so + // there is nothing to explain. + if !entering || !window_controls_drawn(false) { return; } let hint = match crate::ui::home::key_hint("ToggleFullscreen", cx) { Some(chord) => t_fmt(L10nKey::AppFullscreenEntered, &[("key", &chord)]), - // Rebound to nothing at all: still worth saying the bar is gone, + // Rebound to nothing at all: still worth saying the buttons are gone, // just without naming a key that would not work. None => t(L10nKey::AppFullscreenEnteredNoKey).to_string(), }; @@ -7665,22 +7674,41 @@ impl Render for Tty7App { } }; - // No title bar in fullscreen. The bar is window chrome — a caption to - // drag the window by and the three controls at its end — and a - // fullscreen window has none of that to offer: it has no caption for - // the platform to hit-test, so the buttons draw, light up under the - // pointer and do nothing at all when clicked. Drawing chrome that - // cannot work is worse than drawing none, and taking it away is also - // what the mode is for. - let fullscreen = window.is_fullscreen(); - let bar_is_gone = fullscreen && FULLSCREEN_TAKES_THE_TITLE_BAR; - let title_bar = (!bar_is_gone).then(|| { - TitleBar::new() - .h(px(TITLE_BAR_HEIGHT)) - .bg(cx.theme().transparent) - .border_color(cx.theme().transparent) - .child(strip) - }); + // No window buttons in fullscreen, where they cannot work: the window + // has no caption for the platform to hit-test, so they would draw, + // light up under the pointer and do nothing when clicked. `TitleBar` + // always draws them, so the strip goes into a plain row of the same + // geometry instead — the row itself stays, since it holds the tabs, + // the chrome tiles and the docked document's header. + let title_bar = + if window_controls_drawn(window.is_fullscreen()) || cfg!(target_os = "macos") { + TitleBar::new() + .h(px(TITLE_BAR_HEIGHT)) + .bg(cx.theme().transparent) + .border_color(cx.theme().transparent) + .child(strip) + .into_any_element() + } else { + div() + .flex_shrink_0() + .flex() + .flex_row() + .items_center() + .h(px(TITLE_BAR_HEIGHT)) + .pl(px(TITLE_BAR_LEAD)) + .border_b_1() + .border_color(cx.theme().transparent) + .child( + div() + .flex() + .flex_row() + .items_center() + .h_full() + .flex_1() + .child(strip), + ) + .into_any_element() + }; let body_area = div() .flex_1() .relative() @@ -7784,9 +7812,9 @@ impl Render for Tty7App { let panel_below_title_bar = (right_panel.is_some() || document_column.is_some()) && !cfg!(target_os = "macos"); let (column_title_bar, spanning_title_bar) = if panel_below_title_bar { - (None, title_bar) + (None, Some(title_bar)) } else { - (title_bar, None) + (Some(title_bar), None) }; let (column_overlays, hoisted_overlays) = if panel_below_title_bar { (Vec::new(), overlays) @@ -7882,7 +7910,9 @@ impl Render for Tty7App { .right(px(panel_px)) .w(px(document_px)) .when(panel_px <= 0., |d| { - d.pr(px(crate::ui::tab_strip::trailing_chrome_w())) + d.pr(px(crate::ui::tab_strip::trailing_chrome_w( + window.is_fullscreen(), + ))) }) .child(header), ) @@ -9468,6 +9498,24 @@ mod tests { assert_eq!(band.size.height, px(TITLE_BAR_HEIGHT)); } + /// Fullscreen takes the window buttons and nothing else: the strip keeps + /// its row, and the room reserved for the buttons at its end comes back. + /// macOS never had any to take. + #[test] + fn fullscreen_drops_the_window_buttons_but_not_their_row() { + assert!(!super::window_controls_drawn(true)); + assert_eq!(super::window_controls_w(true), 0.); + assert_eq!( + super::window_controls_drawn(false), + !cfg!(target_os = "macos") + ); + assert_eq!(super::window_controls_w(false), super::WINDOW_CONTROLS_W); + assert_eq!( + crate::ui::tab_strip::trailing_chrome_w(true), + crate::ui::tab_strip::trailing_chrome_tiles_w() + ); + } + /// A surface narrower than its own shadow is only reachable mid-resize, but /// a negative width would make `Bounds::contains` answer for a rectangle /// that is inside out. diff --git a/src/ui/i18n/en.rs b/src/ui/i18n/en.rs index 47f63081..fd769eba 100644 --- a/src/ui/i18n/en.rs +++ b/src/ui/i18n/en.rs @@ -1582,7 +1582,7 @@ pub fn translate_en(key: L10nKey) -> &'static str { L10nKey::AppTabsNotRestored => "{count} tabs from last time could not be reopened", L10nKey::AppFullscreenEntered => "Fullscreen — press {key} to leave", L10nKey::AppFullscreenEnteredNoKey => { - "Fullscreen — the title bar is hidden until you leave" + "Fullscreen — the window buttons are hidden until you leave" } L10nKey::LaunchWorkspacesLeftRunning => { "Only this window was restored — {count} workspaces are still running in the background. Reopen them from the sidebar." diff --git a/src/ui/i18n/ja.rs b/src/ui/i18n/ja.rs index 59eefef9..b254e184 100644 --- a/src/ui/i18n/ja.rs +++ b/src/ui/i18n/ja.rs @@ -1644,7 +1644,9 @@ pub fn translate_ja(key: L10nKey) -> Option<&'static str> { L10nKey::AppOpenTerminalFailed => "ターミナルを開けませんでした: {error}", L10nKey::AppTabsNotRestored => "前回のタブ {count} 個を開き直せませんでした", L10nKey::AppFullscreenEntered => "全画面表示 — 解除するには {key}", - L10nKey::AppFullscreenEnteredNoKey => "全画面表示 — 解除するまでタイトルバーは非表示です", + L10nKey::AppFullscreenEnteredNoKey => { + "全画面表示 — 解除するまでウィンドウボタンは非表示です" + } L10nKey::LaunchWorkspacesLeftRunning => { "このウィンドウだけを復元しました — あと {count} 個のワークスペースがバックグラウンドで実行中です。サイドバーから開き直せます。" } diff --git a/src/ui/i18n/zh.rs b/src/ui/i18n/zh.rs index efce02fb..faa8a4e0 100644 --- a/src/ui/i18n/zh.rs +++ b/src/ui/i18n/zh.rs @@ -1497,7 +1497,7 @@ pub fn translate_zh(key: L10nKey) -> Option<&'static str> { L10nKey::AppOpenTerminalFailed => "无法打开终端:{error}", L10nKey::AppTabsNotRestored => "上次的 {count} 个标签页没能重新打开", L10nKey::AppFullscreenEntered => "已进入全屏 —— 按 {key} 退出", - L10nKey::AppFullscreenEnteredNoKey => "已进入全屏 —— 标题栏在退出前会一直隐藏", + L10nKey::AppFullscreenEnteredNoKey => "已进入全屏 —— 窗口按钮在退出前会一直隐藏", L10nKey::LaunchWorkspacesLeftRunning => { "只恢复了这个窗口——还有 {count} 个工作区在后台运行,可从侧边栏重新打开。" } diff --git a/src/ui/keymap.rs b/src/ui/keymap.rs index 3acdbb31..6fb2d808 100644 --- a/src/ui/keymap.rs +++ b/src/ui/keymap.rs @@ -1339,55 +1339,6 @@ mod tests { use super::*; use gpui::Action as _; - /// Everything the title bar offers a button for stays reachable from the - /// keyboard, because on Windows and Linux the title bar is not drawn in - /// fullscreen at all — it is window chrome there, and a fullscreen window - /// has no chrome for the platform to hit-test, so its buttons would light - /// up under the pointer and do nothing when clicked. (On macOS the bar - /// stays: fullscreen is the system's there, and the bar is where it puts - /// the traffic lights.) - /// - /// Reachable means either a chord of its own or a seat in the palette, - /// which has one; both are hands-free, and the palette is how the sidebar - /// toggle is reached, since it ships without a chord. What this pins is - /// that a control on that bar is never mouse-only — if one ever is, - /// hiding the bar would take a feature away with it, and this is where - /// that should be noticed. - #[test] - fn what_the_title_bar_offers_is_reachable_without_it() { - let defaults = default_bindings(); - let chord = |action: &str| { - defaults - .iter() - .any(|(name, keystroke)| *name == action && !keystroke.is_empty()) - }; - // The palette is the fallback, so it is the one that must not be. - assert!( - chord("TogglePalette"), - "the fallback needs a chord of its own" - ); - for action in [ - "NewTab", - "ToggleTabSidebar", - "OpenSettings", - "ToggleFullscreen", - "ToggleSwitcher", - ] { - assert!( - chord(action) || palette_lists(action), - "{action} would be mouse-only once the bar is hidden" - ); - } - } - - /// Whether the palette lists `action` under a name somebody wrote, which is - /// what having a real seat there means: `action_entry` answers for every - /// action, falling back to a name split on capitals, and a fallback name is - /// not evidence that anyone meant the action to be found. - fn palette_lists(action: &str) -> bool { - authored_entry(action).is_some() - } - /// The actions a keymap built from `action_bindings` dispatches for `keys` /// typed in `context`, in precedence order — the same lookup gpui performs /// on a real keypress. diff --git a/src/ui/tab_strip.rs b/src/ui/tab_strip.rs index 48676609..7ef322b1 100644 --- a/src/ui/tab_strip.rs +++ b/src/ui/tab_strip.rs @@ -626,8 +626,8 @@ pub(crate) fn trailing_chrome_tiles_w() -> f32 { /// Anything else drawn into that end of the title bar has to stop short of it — /// which for the hoisted document header means the case where the detail panel /// is closed and the document column runs to the window's right edge. -pub(crate) fn trailing_chrome_w() -> f32 { - trailing_chrome_tiles_w() + crate::ui::app::WINDOW_CONTROLS_W +pub(crate) fn trailing_chrome_w(fullscreen: bool) -> f32 { + trailing_chrome_tiles_w() + crate::ui::app::window_controls_w(fullscreen) } pub(crate) fn chrome_tile_sized( @@ -1812,14 +1812,15 @@ impl Tty7App { // instead: that header carries no fill of its own, and a chip left // under it showed through the file name while staying clickable. let document_w = self.document_dock_px(window, cx).unwrap_or(0.); + let controls_w = crate::ui::app::window_controls_w(window.is_fullscreen()); let strip_w = if cfg!(target_os = "macos") { (window.viewport_size().width - px(80. + panel_w + document_w)).max(px(160.)) } else { - (window.viewport_size().width - px(114.)).max(px(140.)) + (window.viewport_size().width - px(crate::ui::app::TITLE_BAR_LEAD + controls_w)) + .max(px(140.)) }; - let chrome_band_w = (!cfg!(target_os = "macos") && self.right_panel_open(cx)).then(|| { - (self.right_panel_px(window, cx) - crate::ui::app::WINDOW_CONTROLS_W - 1.).max(0.) - }); + let chrome_band_w = (!cfg!(target_os = "macos") && self.right_panel_open(cx)) + .then(|| (self.right_panel_px(window, cx) - controls_w - 1.).max(0.)); // `corner_w` reserves the trailing window chrome. With the panel open on // macOS that chrome belongs to the panel's own header, which the strip // now stops short of, so reserving for it here would charge the chips