From afa27661c4157cc8545c6a499f7724f2978ce416 Mon Sep 17 00:00:00 2001 From: tecari0 <121329615+tecari0@users.noreply.github.com> Date: Mon, 19 Oct 2026 17:48:48 -0700 Subject: [PATCH] #2365: centre the search field on the window, not inside the bar's margins The top bar's left margin holds the traffic lights' room on macOS, and full screen gives that room back (#1763). The search field was centred on the rect inside the margins, so it sat 33 px right of centre in a window and 2 px right of it in full screen, and its width changed by 62 px as well: the field jumped sideways whenever that margin changed. The field is now placed against the whole bar, so neither its centre nor its width depends on the traffic lights. The left buttons still move into their space in full screen. `window_handle` takes that bar rect instead of building the same rect from the content and its margins. Co-Authored-By: Claude Opus 5 --- crates/ui-egui/src/panels/topbar.rs | 22 +++++++++++++--------- crates/ui-egui/src/tests_titlebar.rs | 22 ++++++++++++++++++++++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/crates/ui-egui/src/panels/topbar.rs b/crates/ui-egui/src/panels/topbar.rs index 7e40af7d5..bcbbd4a4e 100644 --- a/crates/ui-egui/src/panels/topbar.rs +++ b/crates/ui-egui/src/panels/topbar.rs @@ -10,9 +10,8 @@ use crate::widgets::{icon_button, register}; /// The bar doubles as the window's title bar: dragging its empty space moves the window and a /// double-click zooms or restores it. Registered before the bar's widgets so they win the click. -fn window_handle(app: &LightcraftApp, ui: &mut egui::Ui, content: Rect, margin_left: f32, margin_right: f32, search_rect: Rect) { +fn window_handle(app: &LightcraftApp, ui: &mut egui::Ui, bar: Rect, search_rect: Rect) { use crate::titlebar::{Gesture, WindowState, command_for}; - let bar = Rect::from_min_max(pos2(content.left() - margin_left, content.top()), pos2(content.right() + margin_right, content.bottom())); register(ui.ctx(), "region:titlebar", bar); let handle = egui::Id::new("titlebar-handle"); let resp = ui.interact(bar, handle, Sense::click_and_drag()); @@ -60,18 +59,23 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) { .frame(egui::Frame::NONE.fill(t.chrome).inner_margin(egui::Margin { left, right: 12, top: 0, bottom: 0 })) .show(ui, |ui| { let full = ui.max_rect(); - // room for the filter icon and the seven icons on the right - // (the field is centred, so each icon on the right takes its width from both sides) - let mut sw = 640.0f32.min(full.width() - 620.0).max(190.0); + // The whole bar, its margins included. The search field is centred on this and not on + // `full`, which is only what the margins leave: on macOS the left margin holds the + // traffic lights' room, and full screen gives that room back. Centring on `full` moved + // the field, and changed its width, every time that room changed (issue #2365). + let bar = Rect::from_min_max(pos2(full.left() - left as f32, full.top()), pos2(full.right() + 12.0, full.bottom())); + // The field stops short of the filter icon and the seven icons on the right; centred, + // it is twice the room between the window's centre and them. + let mut sw = 640.0f32.min(2.0 * (full.right() - 310.0 - bar.center().x)).max(190.0); if !app.native_menu { // leave room for the in-window menus left of the (centred) search field let menus_right = full.left() + 140.0 + crate::menubar::bar_width(ui) + 24.0; - sw = sw.min(2.0 * (full.center().x - menus_right)).max(190.0); + sw = sw.min(2.0 * (bar.center().x - menus_right)).max(190.0); } - let search_left = full.center().x - sw / 2.0; - let sr = Rect::from_center_size(pos2(full.center().x, full.center().y), vec2(sw, 28.0)); + let search_left = bar.center().x - sw / 2.0; + let sr = Rect::from_center_size(pos2(bar.center().x, full.center().y), vec2(sw, 28.0)); if app.integrated_titlebar { - window_handle(app, ui, full, left as f32, 12.0, sr); + window_handle(app, ui, bar, sr); } ui.horizontal_centered(|ui| { ui.spacing_mut().item_spacing.x = 4.0; diff --git a/crates/ui-egui/src/tests_titlebar.rs b/crates/ui-egui/src/tests_titlebar.rs index 68c04f9ec..36598190c 100644 --- a/crates/ui-egui/src/tests_titlebar.rs +++ b/crates/ui-egui/src/tests_titlebar.rs @@ -130,6 +130,28 @@ fn without_the_integrated_title_bar_the_bar_is_not_a_window_handle() { assert!(bar.is_none()); } +// Scenario: the search field is centred on the window, whatever room the traffic lights take (#2365) +#[test] +fn the_search_field_stays_centred_on_the_window() { + let mut h = integrated(); + let window_centre = 1200.0 / 2.0; + let windowed = widget(&h, "field:search"); + assert_eq!(windowed.center().x, window_centre, "windowed, the field is centred on the window"); + // the filter icon rides with the field, so it moves whenever the field does + let filter = widget(&h, "icon:filter").center().x; + h.request("ui.key", json!({"key": "f", "cmd": true, "shift": true}), T); + play(&mut h); + assert!(h.app.window_is_fullscreen); + let full_screen = widget(&h, "field:search"); + assert_eq!(full_screen.center().x, window_centre, "full screen leaves it centred"); + assert_eq!(full_screen.width(), windowed.width(), "and the same width"); + assert_eq!(widget(&h, "icon:filter").center().x, filter); + h.request("ui.key", json!({"key": "f", "cmd": true, "shift": true}), T); + play(&mut h); + assert!(!h.app.window_is_fullscreen); + assert_eq!(widget(&h, "field:search").center().x, window_centre, "and so does leaving it"); +} + // Scenario: in full screen the traffic lights are gone, so the bar's buttons move into their space #[test] fn full_screen_moves_the_left_buttons_into_the_traffic_lights_space() {