mirror of
https://github.com/storytold/lightcraft.git
synced 2026-11-01 14:07:34 -06:00
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.
This commit is contained in:
@@ -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<egui::Rect> {
|
||||
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>) -> 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 {
|
||||
|
||||
@@ -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<egui::Rect>, 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 };
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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 (🟡) |
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user