From 62c3344d3ca2e12d8e77a09200755acf847abb66 Mon Sep 17 00:00:00 2001 From: Nathaniel Ledford Date: Thu, 29 Oct 2026 13:35:47 -0400 Subject: [PATCH] LR-BEHAV-TITLEBAR: a dialog leaves the integrated title bar undimmed On macOS the window buttons are drawn by the OS above our content, so a backdrop over the top bar left them bright on a dimmed bar. As under a sheet, the backdrop now stops below the bar, the bar keeps its color with its controls inactive, a click on it is not a click outside the dialog, and its empty space still moves the window. --- crates/ui-egui/src/dialogs.rs | 44 ++++++++++- crates/ui-egui/src/dialogs/frame.rs | 4 +- crates/ui-egui/src/panels/topbar.rs | 13 +++- crates/ui-egui/src/tests/titlebar.rs | 107 +++++++++++++++++++++++++++ docs/parity-checklist.md | 5 +- docs/whats-new.md | 8 ++ 6 files changed, 173 insertions(+), 8 deletions(-) diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 646288220..ad7ce7a82 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -93,8 +93,11 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context, blocked: bool) { let t = Tokens::get(ctx); let screen = ctx.content_rect(); // The backdrop is an area below the dialog window (a bare `Middle` layer painter would be - // painted after every area — i.e. over the dialog too). + // painted after every area — i.e. over the dialog too). Like a macOS sheet, the dialog leaves + // the integrated title bar lit: the window buttons the OS draws over it do not dim. + let title_bar = title_bar_band(app); egui::Area::new(egui::Id::unique("dialog-dim")).order(egui::Order::Middle).fixed_pos(screen.min).interactable(false).show(ctx, |ui| { + let screen = below_title_bar(screen, title_bar); ui.painter().rect_filled(screen, 0.0, egui::Color32::from_black_alpha(140)); }); let mut close = false; @@ -189,7 +192,7 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context, blocked: bool) { } if let Some(w) = &shown { // (the import review keeps its hand-picked photos however the click lands) - if !raw_blocked && app.ui.settings.close_dialogs_outside && !import && clicked_backdrop(ctx, w.response.rect, popup_was_open) { + if !raw_blocked && app.ui.settings.close_dialogs_outside && !import && clicked_backdrop(ctx, w.response.rect, title_bar, popup_was_open) { close = true; } else if raw_blocked { // A press made under the prompt or library window must not be remembered as a @@ -208,6 +211,9 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context, blocked: bool) { } ctx.move_to_top(w.response.layer_id); crate::widgets::register_response("dialog:window", w.response.rect, &w.response); + if let Some(band) = title_bar { + title_bar_handle(ctx, band, w.response.rect); + } } // (while the shortcuts editor records a key, it takes Esc itself to cancel; an open popup in // the dialog, such as a date picker's calendar, takes it to close itself) @@ -228,6 +234,40 @@ pub fn show(app: &mut LightcraftApp, ctx: &egui::Context, blocked: bool) { app.ui.dialog = if close { None } else { Some(dlg) }; } +/// The band of the top bar that doubles as the window's title bar, as drawn last frame: `None` +/// without the integrated title bar (Windows, Linux, the web), in macOS full screen, and while the +/// bar isn't on screen (the full-screen photo view). +fn title_bar_band(app: &LightcraftApp) -> Option { + if !app.integrated_titlebar || app.window_is_fullscreen { + return None; + } + app.widgets.iter().find(|(id, _)| id == "region:titlebar").map(|(_, r)| *r) +} + +/// `screen` less the title bar band across its top. +fn below_title_bar(screen: egui::Rect, band: Option) -> egui::Rect { + band.map_or(screen, |b| egui::Rect::from_min_max(egui::pos2(screen.min.x, b.max.y.clamp(screen.min.y, screen.max.y)), screen.max)) +} + +/// The bar's controls are disabled under a dialog, and the modal layer takes the hits of the layers +/// below it: this keeps the bar a window handle (drag to move, double-click to zoom), above the +/// modal like a sheet's title bar. A tall dialog reaching into the band keeps its own clicks: the +/// handle is the band on either side of it, and isn't run while the pointer is on the dialog (each +/// handle records whether the window may move, and one the pointer isn't in would say yes). +fn title_bar_handle(ctx: &egui::Context, band: egui::Rect, dialog: egui::Rect) { + if ctx.pointer_hover_pos().is_some_and(|p| dialog.contains(p)) { + return; + } + let sides = + if band.intersects(dialog) { [(band.min.x, dialog.min.x), (dialog.max.x, band.max.x)] } else { [(band.min.x, band.max.x), (0.0, 0.0)] }; + for (i, (from, to)) in sides.into_iter().enumerate().filter(|(_, (from, to))| to - from > 1.0) { + let part = egui::Rect::from_x_y_ranges(from..=to, band.y_range()); + egui::Area::new(egui::Id::unique("dialog-title-bar").with(i)).order(egui::Order::Foreground).fixed_pos(part.min).show(ctx, |ui| { + craft_window::title_bar::empty_area(ui, part); + }); + } +} + /// The title of a dialog. fn dialog_title(app: &LightcraftApp, dlg: &Dialog) -> String { match dlg { diff --git a/crates/ui-egui/src/dialogs/frame.rs b/crates/ui-egui/src/dialogs/frame.rs index 007c733cb..9bca526ce 100644 --- a/crates/ui-egui/src/dialogs/frame.rs +++ b/crates/ui-egui/src/dialogs/frame.rs @@ -8,7 +8,7 @@ use super::{BACKDROP_SHOWN, ControlSpec, Dialog, LightcraftApp, Tokens}; /// while the dialog is shown counts: in the frame a dialog appears, its input is the click that /// opened it (a quick click on a slow frame presses and releases in that one frame), so it never /// closes it. (Read from the raw events: egui forgets the press origin on release.) -pub(super) fn clicked_backdrop(ctx: &egui::Context, window: egui::Rect, popup_was_open: bool) -> bool { +pub(super) fn clicked_backdrop(ctx: &egui::Context, window: egui::Rect, title_bar: Option, popup_was_open: bool) -> bool { let press_id = egui::Id::unique("dialog-backdrop-press"); let frame = ctx.cumulative_frame_nr(); // the same frame too: a second pass of it gets no input @@ -27,7 +27,7 @@ pub(super) fn clicked_backdrop(ctx: &egui::Context, window: egui::Rect, popup_wa (at(true), at(false), i.pointer.primary_clicked()) }); if let Some(p) = press { - let outside = !window.contains(p) && !popup_was_open; + let outside = !window.contains(p) && !title_bar.is_some_and(|b| b.contains(p)) && !popup_was_open; ctx.data_mut(|d| d.insert_temp(press_id, outside)); } let Some(release) = release else { return false }; diff --git a/crates/ui-egui/src/panels/topbar.rs b/crates/ui-egui/src/panels/topbar.rs index 5e3fea8a1..0f2ffcc0c 100644 --- a/crates/ui-egui/src/panels/topbar.rs +++ b/crates/ui-egui/src/panels/topbar.rs @@ -28,9 +28,15 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) { (true, true) => 16, (false, _) => 10, }; - egui::Panel::top("top_bar") + // A dialog fades the disabled bar's controls, and its fill with them. A sheet leaves the title bar's + // color alone (the window buttons the OS draws over it stay lit), so the fill is painted unfaded + // beneath, in a slot reserved before the bar's own shapes. + let sheet = app.integrated_titlebar && !app.window_is_fullscreen && app.ui.dialog.is_some(); + let slot = sheet.then(|| ui.ctx().layer_painter(ui.layer_id()).add(egui::Shape::Noop)); + let fill = if sheet { egui::Color32::TRANSPARENT } else { t.chrome }; + let panel = egui::Panel::top("top_bar") .exact_size(t.top_bar_h) - .frame(egui::Frame::NONE.fill(t.chrome).inner_margin(egui::Margin { left, right: 12, top: 0, bottom: 0 })) + .frame(egui::Frame::NONE.fill(fill).inner_margin(egui::Margin { left, right: 12, top: 0, bottom: 0 })) .show(ui, |ui| { let full = ui.max_rect(); // The whole bar, its margins included. The search field is centred on this and not on @@ -57,6 +63,9 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) { let x = right_icons(app, ui, &t, full); appearance_button(app, ui, &t, full, x); }); + if let Some(slot) = slot { + ui.ctx().layer_painter(ui.layer_id()).set(slot, egui::Shape::rect_filled(panel.response.rect, 0.0, t.chrome)); + } } /// Sidebar toggle, back/forward and, without a native menu bar, the in-window menus. diff --git a/crates/ui-egui/src/tests/titlebar.rs b/crates/ui-egui/src/tests/titlebar.rs index 8d40f212e..a8eaa33f1 100644 --- a/crates/ui-egui/src/tests/titlebar.rs +++ b/crates/ui-egui/src/tests/titlebar.rs @@ -191,3 +191,110 @@ fn a_panel_floating_over_the_bar_keeps_the_window_from_moving() { play(&mut h); assert!(!hover(&mut h, empty), "the floating panel covers the empty bar there"); } + +// A dialog on the integrated title bar behaves like a macOS sheet: the backdrop leaves the bar +// undimmed (the window buttons AppKit draws over it stay lit beside a bar that looks live), while +// the bar's own controls stay inactive. + +fn pixel(img: &egui::ColorImage, p: egui::Pos2) -> egui::Color32 { + img.pixels[p.y as usize * img.size[0] + p.x as usize] +} + +fn with_export_dialog(mut h: Headless) -> Headless { + h.app.run("dialog.export", json!({})).unwrap(); + h.step(); + h.step(); + h.settle(T); + h +} + +// Scenario: the dialog's backdrop does not dim the integrated title bar +#[test] +fn a_dialog_leaves_the_integrated_title_bar_undimmed() { + let mut h = integrated(); + let spot = empty_spot(&h); + let below = egui::pos2(spot.x, widget(&h, "region:titlebar").bottom() + 60.0); + let closed = h.snapshot(T); + let mut h = with_export_dialog(h); + let open = h.snapshot(T); + assert_eq!(pixel(&open, spot), pixel(&closed, spot), "the bar keeps its color under a dialog"); + assert_ne!(pixel(&open, below), pixel(&closed, below), "the window below the bar is dimmed"); +} + +// Scenario: a window with its native title bar dims everything, the top bar included +#[test] +fn a_dialog_dims_the_top_bar_without_the_integrated_title_bar() { + let services = Services { png: None, ..Default::default() }; + let app = LightcraftApp::new(lightcraft_engine::Session::with_demo(), services); + let mut h = Headless::new(app, [1200.0, 800.0], 1.0); + h.settle(Duration::from_secs(120)); + let spot = egui::pos2(20.0, 20.0); + let closed = h.snapshot(T); + let mut h = with_export_dialog(h); + let open = h.snapshot(T); + assert_ne!(pixel(&open, spot), pixel(&closed, spot), "the top bar is dimmed"); +} + +// Scenario: a click on the undimmed bar is not a click outside the dialog +#[test] +fn a_click_on_the_title_bar_keeps_the_dialog_open() { + let mut h = integrated(); + h.app.ui.settings.close_dialogs_outside = true; + let mut h = with_export_dialog(h); + let at = empty_spot(&h); + h.request("ui.click", json!({"x": at.x, "y": at.y}), T); + play(&mut h); + assert!(h.app.ui.dialog.is_some(), "the bar is not the backdrop"); + let below = egui::pos2(at.x, widget(&h, "region:titlebar").bottom() + 60.0); + h.request("ui.click", json!({"x": below.x, "y": below.y}), T); + play(&mut h); + assert!(h.app.ui.dialog.is_none(), "the backdrop below it still closes the dialog"); +} + +// Scenario: the bar's controls take no clicks under a dialog +#[test] +fn the_title_bar_buttons_are_inactive_under_a_dialog() { + let mut h = with_export_dialog(integrated()); + let left = h.app.ui.left_panel; + let at = widget(&h, "icon:sidebar").center(); + h.request("ui.click", json!({"x": at.x, "y": at.y}), T); + play(&mut h); + assert_eq!(h.app.ui.left_panel, left, "the sidebar button does nothing"); + assert!(h.app.ui.dialog.is_some()); +} + +// Scenario: dragging the empty bar still moves the window under a dialog, whether the dialog is +// tall enough to reach into the bar (Export) or not (Copy Settings) +#[test] +fn dragging_the_empty_bar_moves_the_window_under_a_dialog() { + for command in ["dialog.export", "dialog.copySettings"] { + let mut h = integrated(); + h.app.run(command, json!({})).unwrap(); + h.step(); + h.step(); + h.settle(T); + let p = empty_spot(&h); + h.window_commands.clear(); + h.request("ui.drag", json!({"x": p.x, "y": p.y, "toX": p.x + 120.0, "toY": p.y + 40.0}), T); + play(&mut h); + assert_eq!(h.window_commands, vec![ViewportCommand::StartDrag], "{command}"); + } +} + +// Scenario: a tall dialog reaching into the bar keeps a press on itself from moving the window +// (AppKit's `movable` is off while the pointer is over it), the bar beside it still moves it +#[test] +fn a_dialog_inside_the_bar_keeps_the_window_from_moving() { + let mut h = with_export_dialog(integrated()); + let bar = widget(&h, "region:titlebar"); + let window = widget(&h, "dialog:window"); + assert!(window.top() < bar.bottom(), "the dialog reaches into the bar"); + let hover = |h: &mut Headless, p: egui::Pos2| { + h.events.push(egui::Event::PointerMoved(p)); + play(h); + craft_window::title_bar::window_movable(&h.view.ctx) + }; + let spot = empty_spot(&h); + assert!(hover(&mut h, spot), "the bar beside the dialog moves the window"); + assert!(!hover(&mut h, egui::pos2(window.center().x, bar.center().y)), "the dialog over the bar does not"); +} diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 90cfc248f..61b32a2f9 100644 --- a/docs/parity-checklist.md +++ b/docs/parity-checklist.md @@ -1,6 +1,6 @@ # Lightroom parity checklist -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-25 · **Change:** minor (LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-29 · **Change:** minor (LR-BEHAV-TITLEBAR: a dialog leaves the title bar undimmed, as a macOS sheet does; LR-BEHAV-TITLEBAR: a double-click follows the system setting, and a panel over the bar keeps the window still; KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 go to the next / previous keyword set; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings; KEYC-METADATA: ⌘K / Ctrl+K Add Keywords; LRC-DEV-HISTOGRAM-DRAG: the histogram's five tonal zones — hover for the value, drag to adjust the Basic control the press locked, double-click to reset; LR-VIEW-HISTOGRAM note updated; MENU-BAR: on macOS a menu item that yields its key to a focused text field or to shortcut recording really lets it go; LR-MERGE-HDR / LR-MERGE-PANO: Lanczos-3 warps; LR-IMP-LOCAL: browsing from the sidebar records the folder; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LR-IMP-ADD-DIALOG: hierarchical import album destination reconciled with main, #2362; KEY-SLIDER-NUDGE: vertical wheel nudges need a click or drag in the row; leaving it disarms them; LR-EDIT-OPTICS-PROFILE: Fujifilm nine-knot lens tables read at the plain radius; LR-VIEW-ZOOM: only a full-size drag within 20 ms clears a slow-drag verdict; LRC-LIB-LIBFOLDERS, LR-BEHAV-BG-TASKS: Synchronize Folder's scan shows its folder listing and stops it on ✕; LRC-LIB-LIBFOLDERS: Synchronize Folder says a folder is up to date; LR-LIB-STACK: filmstrip stack badge click; LRC-LIB-REFVIEW / CATALOG-SWITCH: Escape preserves edits; catalog-local pins; deleted and malformed references refused; LRC-LIB-COMPARE: the Info panel labels the active photo while culling, #2398; KEYC-RATING: `\` shows / hides the Filter Bar in the grids; LR-VIEW-PHOTOGRID: the grid reads only the photos an import batch or an edit touched, not the whole list; earlier: LRC-PRINT-PAGE: Print Page Options; LR-META-XMP: fewer false unimported-XMP warnings) · **Target:** Adobe Lightroom Classic 15.5 / Lightroom 9.6 The measured, row-by-row checklist: one row per Lightroom feature, menu item and shortcut, what we have and what is missing. It answers *does the feature exist?* (feature breadth). Whether it is good @@ -485,7 +485,7 @@ differences and missing bindings are in [ui-parity.md](ui-parity.md#shortcuts). | LR-BEHAV-LEARN | Tutorials | OOS | 🚫 | | | | LR-BEHAV-WHATSNEW | What's new | P2 | ✅ | `cmd:app.whatsNew`, `docs/whats-new.md` | Help ▸ What's New: release highlights | | LR-BEHAV-AI-EA | Early-access badges | P2 | ⬜ | | | -| LR-BEHAV-TITLEBAR | Title bar drag and double-click zoom (macOS) | P2 | 🟡 | `crates/ui-egui/src/panels/topbar.rs`, `crates/ui-egui/src/tests/titlebar.rs` | the native title bar is hidden on macOS, so the top bar's empty space (including the traffic-light margin) moves the window and a double-click does what System Settings › “Double-click a window's title bar to” says (zoom or restore, minimize, nothing; craft-window's `title_bar`); buttons and the search field keep their clicks; ignored in full screen, where the traffic lights are gone and the bar's left buttons move into their space (#1763). A press that starts on a button, the search field or a panel floating over the bar never moves the window (AppKit's `movable` is off while the pointer is there). Known gap: egui-winit reads the zoomed state only at start-up and after our own `Maximized` command, so a window zoomed another way (green button, Window ▸ Zoom, snapping) makes the first double-click a no-op before the toggle catches up. Checked by hand on a real macOS window (drag, zoom, restore) | +| LR-BEHAV-TITLEBAR | Title bar drag and double-click zoom (macOS) | P2 | 🟡 | `crates/ui-egui/src/panels/topbar.rs`, `crates/ui-egui/src/tests/titlebar.rs` | the native title bar is hidden on macOS, so the top bar's empty space (including the traffic-light margin) moves the window and a double-click does what System Settings › “Double-click a window's title bar to” says (zoom or restore, minimize, nothing; craft-window's `title_bar`); buttons and the search field keep their clicks; ignored in full screen, where the traffic lights are gone and the bar's left buttons move into their space (#1763). A press that starts on a button, the search field or a panel floating over the bar never moves the window (AppKit's `movable` is off while the pointer is there). While a dialog is open the bar is not dimmed, so it stays level with the window buttons AppKit draws over it (as under a macOS sheet): its controls are inactive, a click on it does not close the dialog, and its empty space still moves the window; not on a window with its native title bar, nor in full screen. Known gap: egui-winit reads the zoomed state only at start-up and after our own `Maximized` command, so a window zoomed another way (green button, Window ▸ Zoom, snapping) makes the first double-click a no-op before the toggle catches up. Checked by hand on a real macOS window (drag, zoom, restore) | ## Y. Menus @@ -959,3 +959,4 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove | 2026-10-23 | patch | LR-BEHAV-ACCESS: the name field of New Album, New Smart Album, Rename and the other one-field dialogs takes the keyboard focus once when the dialog opens instead of every frame, so Tab and Shift+Tab reach the check box, Cancel and OK; status unchanged (🟡) | | 2026-10-22 | minor | LR-CROP-AUTO: Auto straighten turns by at most 10° and only on at least 3 agreeing lines, and says when it finds nothing to level by; measured on 68 photos straightened by hand and 300 left as shot, unwanted turns of more than 1° fell from 79 to 19 of the 300. Downgraded ✅ → 🟡: it still leaves most tilted photos alone | | 2026-10-23 | patch | LRC-LIB-FILTERBAR: showing an album (`library.source`) while the filter holds an album drops the filter's album, as showing a library folder already dropped its folder filter; before, the old album stayed in the filter unseen (the filter bar has no album chip) and narrowed the next source such as All Photos; the row's note says so; status unchanged (✅) | +| 2026-10-29 | patch | LR-BEHAV-TITLEBAR: a dialog's backdrop leaves the integrated title bar undimmed, as a macOS sheet does (the bar's controls stay inactive, a click on it is not a click outside the dialog, dragging its empty space still moves the window); status unchanged (🟡) | diff --git a/docs/whats-new.md b/docs/whats-new.md index c6c259200..47f7746ae 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -1,5 +1,13 @@ # What's new in LightCraft +## 2026-10-29 + +### A dialog no longer dims the macOS title bar +- On macOS the top bar is the window's title bar, and the window buttons drawn over it stayed bright while a dialog dimmed + the bar under them. As under a macOS sheet, the dialog's backdrop now leaves the bar alone: its buttons are inactive, a + click on it does not close the dialog, and dragging its empty space still moves the window. Windows, Linux and full + screen are unchanged. + ## 2026-10-24 ### Relative-date filters and smart albums follow the clock