diff --git a/src/ui/app.rs b/src/ui/app.rs index 7256b3e4..ea0b8bc0 100644 --- a/src/ui/app.rs +++ b/src/ui/app.rs @@ -298,7 +298,11 @@ pub(crate) fn tile_trailing_inset_sm() -> f32 { (CONTENT_INSET - TILE_PAD_SM).max(TILE_EDGE_GAP) } -pub(crate) const TITLE_BAR_LEAD: f32 = if cfg!(target_os = "macos") { 80. } else { 12. }; +pub(crate) const TITLE_BAR_LEAD: f32 = if cfg!(target_os = "macos") { + crate::ui::theme::TRAFFIC_LIGHTS_LEAD +} else { + 12. +}; pub(crate) const WINDOW_CONTROLS_W: f32 = if cfg!(target_os = "macos") { 0. } else { 102. }; @@ -9498,8 +9502,11 @@ impl Render for Tty7App { // the chrome tiles and the docked document's header. let title_bar = if window_controls_drawn(window.is_fullscreen()) || cfg!(target_os = "macos") { + // `TitleBar` pads its own fixed 80pt; the lead is ours, since + // it follows where the traffic lights are put. TitleBar::new() .h(px(TITLE_BAR_HEIGHT)) + .pl(px(TITLE_BAR_LEAD)) .bg(cx.theme().transparent) .border_color(cx.theme().transparent) .child(strip) diff --git a/src/ui/tab_strip.rs b/src/ui/tab_strip.rs index 6217a380..2bd41af0 100644 --- a/src/ui/tab_strip.rs +++ b/src/ui/tab_strip.rs @@ -775,10 +775,10 @@ const TITLEBAR_SEARCH_CLEAR_GAP: f32 = 8.; /// is drawn inside. /// /// The strip does not start at the terminal column's left edge. The title bar -/// leaves a lead before it — 80pt on macOS for the traffic lights, whether or +/// leaves a lead before it — `TRAFFIC_LIGHTS_LEAD` on macOS, for the traffic lights, whether or /// not the rail is standing in front of them, plus the bar's own inset in /// fullscreen; 12 elsewhere — and a box centred on the strip was centred that -/// much too far right by half: 40pt on macOS, which is plainly visible with +/// much too far right by half: 40pt on macOS then, which is plainly visible with /// the rail collapsed and the box standing alone in an empty bar (#1033). /// So the band reaches back over the lead, and its left edge is the column's. #[derive(Clone, Copy, Debug, PartialEq)] @@ -828,7 +828,7 @@ pub(crate) fn search_band( } /// How far the title bar's lead reaches before the tab strip starts: the room -/// `TitleBar` leaves for the traffic lights on macOS, and the bar's own inset +/// we have `TitleBar` leave for the traffic lights on macOS, and the bar's own inset /// that gpui-component adds to it in fullscreen, where the lights are gone but /// the room is not; `TITLE_BAR_LEAD` elsewhere, in both of the rows the strip /// can sit in. @@ -2319,7 +2319,9 @@ impl Tty7App { 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.)) + (window.viewport_size().width + - px(crate::ui::app::TITLE_BAR_LEAD + panel_w + document_w)) + .max(px(160.)) } else { (window.viewport_size().width - px(crate::ui::app::TITLE_BAR_LEAD + controls_w)) .max(px(140.)) @@ -3113,6 +3115,7 @@ mod ssh_host_row_tests { #[cfg(test)] mod tests { use super::*; + use crate::ui::theme::TRAFFIC_LIGHTS_LEAD; use gpui::TestAppContext; use std::path::Path; use unicode_segmentation::UnicodeSegmentation; @@ -3124,11 +3127,11 @@ mod tests { } /// #1033, with the reporter's window: 1946pt wide, the rail collapsed. - /// The strip starts 80pt in, after the traffic lights, and centred on it + /// The strip started 80pt in, after the traffic lights, and centred on it /// the box sat at 1013 — 40pt right of the window's middle at 973. #[test] fn with_the_rail_collapsed_on_macos_the_search_box_centres_on_the_window() { - let (w, lead) = (1946., 80.); + let (w, lead) = (1946., TRAFFIC_LIGHTS_LEAD); let band = search_band(true, lead, 0., 0., Some(58.), Some(49.)); let (l, r) = band_in_column(band, lead, w); assert_eq!((l, r), (0., w)); @@ -3139,12 +3142,12 @@ mod tests { } /// With the rail open, the title bar sits in the terminal column but - /// still leaves the traffic lights' 80pt before the strip, over nothing + /// still leaves the traffic lights' lead before the strip, over nothing /// — the lights are over the rail. The band reaches back over that too, /// and centres on the column. #[test] fn with_the_rail_open_on_macos_the_search_box_centres_on_the_terminal_column() { - let (column, lead) = (1200., 80.); + let (column, lead) = (1200., TRAFFIC_LIGHTS_LEAD); let band = search_band(true, lead, 0., 0., None, Some(49.)); assert_eq!(band_in_column(band, lead, column), (0., column)); assert_eq!(band.clear, 49. + TITLEBAR_SEARCH_CLEAR_GAP); @@ -3156,10 +3159,10 @@ mod tests { assert_eq!(docked.clear, 0.); } - /// Fullscreen keeps `TitleBar`'s 80pt and adds the bar's own inset. + /// Fullscreen keeps the traffic lights' lead and adds the bar's own inset. #[test] fn fullscreen_on_macos_reaches_back_over_the_bars_extra_inset_as_well() { - let lead = 80. + 12.; + let lead = TRAFFIC_LIGHTS_LEAD + 12.; let band = search_band(true, lead, 0., 0., Some(58.), Some(49.)); assert_eq!(band_in_column(band, lead, 1440.), (0., 1440.)); } diff --git a/src/ui/theme.rs b/src/ui/theme.rs index 1261a233..57da91a1 100644 --- a/src/ui/theme.rs +++ b/src/ui/theme.rs @@ -19,11 +19,8 @@ use crate::ui::presets::Fill; use std::sync::OnceLock; /// Where macOS puts the window's traffic lights over the tab strip: centred on -/// the row of chrome tiles they share the bar with, and as far in from the left -/// edge as they sit below the top, which is where AppKit's own toolbar windows -/// put them (19 and 19 in a 52-point unified bar, 12 and 13 in a 40-point -/// compact one). Nine points in is the plain 28-point title bar's inset, and -/// in a 48-point bar it crowds the lights into the corner. +/// the row of chrome tiles they share the bar with. `x` matches `y`, as AppKit's +/// unified toolbar does (19 and 19 in a 52-point bar). /// /// `y` is the gap between the window's top edge and the *top* of the button /// frame, and macOS draws those buttons 14 points tall — so the lights' centre @@ -35,15 +32,24 @@ use std::sync::OnceLock; /// to 48, without the border in it, and left the lights half a point — one /// device pixel on a Retina panel — below everything beside them. pub(crate) fn traffic_light_position() -> Point { - // AppKit's own button frame, and the border `TitleBar` adds to the bar it - // wraps. Neither moves with the bar's height. - const BUTTON_H: f32 = 14.; - const BAR_BORDER: f32 = 1.; - - let top = (TITLE_BAR_HEIGHT - BAR_BORDER - BUTTON_H) / 2.; - point(px(top), px(top)) + point(px(TRAFFIC_LIGHT_INSET), px(TRAFFIC_LIGHT_INSET)) } +// AppKit's own button frame, and the border `TitleBar` adds to the bar it +// wraps. Neither moves with the bar's height. +const TRAFFIC_LIGHT_H: f32 = 14.; +const TITLE_BAR_BORDER: f32 = 1.; + +const TRAFFIC_LIGHT_INSET: f32 = (TITLE_BAR_HEIGHT - TITLE_BAR_BORDER - TRAFFIC_LIGHT_H) / 2.; + +/// Close to zoom: three 14-point buttons on a 20-point pitch. +const TRAFFIC_LIGHTS_W: f32 = 54.; + +/// The room the lights take at the bar's leading edge on macOS: their span, +/// with the inset they sit at on either side. +pub(crate) const TRAFFIC_LIGHTS_LEAD: f32 = + TRAFFIC_LIGHT_INSET + TRAFFIC_LIGHTS_W + TRAFFIC_LIGHT_INSET; + pub(crate) fn set_menus(cx: &mut App) { cx.set_menus([ Menu::new("tty7").items([ @@ -1098,10 +1104,18 @@ mod tests { #[test] fn the_traffic_lights_sit_on_the_chrome_rows_centre() { let position = traffic_light_position(); - assert_eq!(position.x, position.y); assert_eq!(position.y + px(7.), px((TITLE_BAR_HEIGHT - 1.) / 2.)); } + /// The strip, editor header and search band start at the lead, so the + /// lights have to end, with their inset after them, before it does. + #[test] + #[cfg(target_os = "macos")] + fn the_title_bar_lead_clears_the_traffic_lights() { + let right = traffic_light_position().x + px(TRAFFIC_LIGHTS_W); + assert!(right + px(TRAFFIC_LIGHT_INSET) <= px(crate::ui::app::TITLE_BAR_LEAD)); + } + #[gpui::test] fn effective_preset_follows_the_cached_system_appearance(cx: &mut TestAppContext) { cx.update(|cx| {