mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-21 16:02:20 +00:00
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.
This commit is contained in:
+82
-34
@@ -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::<FullscreenHint>(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.
|
||||
|
||||
+1
-1
@@ -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."
|
||||
|
||||
+3
-1
@@ -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} 個のワークスペースがバックグラウンドで実行中です。サイドバーから開き直せます。"
|
||||
}
|
||||
|
||||
+1
-1
@@ -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} 个工作区在后台运行,可从侧边栏重新打开。"
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
+7
-6
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user