diff --git a/Cargo.lock b/Cargo.lock index a9a326ab8..492fef141 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1211,6 +1211,17 @@ dependencies = [ "zip 9.1.0", ] +[[package]] +name = "craft-window" +version = "0.1.0" +source = "git+https://github.com/storytold/craft-libs?rev=1e6f127844ec61b3b8afdcc2bb76e7cfe470153f#1e6f127844ec61b3b8afdcc2bb76e7cfe470153f" +dependencies = [ + "egui", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", + "wayland-client", +] + [[package]] name = "crc32fast" version = "1.5.3" @@ -1799,7 +1810,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -3714,6 +3725,7 @@ version = "0.14.0" dependencies = [ "craft-ui", "craft-update", + "craft-window", "egui", "egui_kittest", "lightcraft-catalog", @@ -4612,7 +4624,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5550,7 +5562,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -5607,7 +5619,7 @@ dependencies = [ "security-framework", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -6174,10 +6186,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.3", "once_cell", "rustix 1.1.5", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -7077,7 +7089,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 71fd2566c..b68c6e6ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ repository = "https://github.com/storytold/lightcraft" [workspace.dependencies] craft-ui = { git = "https://github.com/storytold/craft-libs", rev = "25da1314d443171b4adeaad2cd9454b273f35c6f", features = ["serde"] } craft-docpath = { version = "0.1.0", git = "https://github.com/storytold/craft-libs", rev = "82a24b006eefa036b43c09a67fb9a3fbd17ffe63" } +craft-window = { git = "https://github.com/storytold/craft-libs", rev = "1e6f127844ec61b3b8afdcc2bb76e7cfe470153f", features = ["egui"] } craft-update = { git = "https://github.com/storytold/craft-libs", version = "=0.1.0", rev = "495e5971bc024aa96e6ed318cb6e7cd3a0103184", features = ["egui"] } # internal lightcraft-denoise-core = { path = "crates/denoise-core" } diff --git a/crates/ui-egui/Cargo.toml b/crates/ui-egui/Cargo.toml index 8dab2b967..8e7b77f42 100644 --- a/crates/ui-egui/Cargo.toml +++ b/crates/ui-egui/Cargo.toml @@ -11,6 +11,7 @@ workspace = true [dependencies] craft-ui = { workspace = true } +craft-window = { workspace = true } lightcraft-geom = { workspace = true } lightcraft-meta = { workspace = true } lightcraft-color = { workspace = true } diff --git a/crates/ui-egui/src/lib.rs b/crates/ui-egui/src/lib.rs index be1a9e879..e53989fca 100644 --- a/crates/ui-egui/src/lib.rs +++ b/crates/ui-egui/src/lib.rs @@ -49,7 +49,6 @@ pub mod sync; pub mod tasks; pub mod text_field; pub mod theme; -pub mod titlebar; pub mod updates_ui; pub mod widgets; pub mod window_place; @@ -395,6 +394,8 @@ pub struct LightcraftApp { pub perf: Perf, /// macOS: the host draws the traffic lights over our top bar. pub integrated_titlebar: bool, + /// The `movable` flag last given to the window (macOS, `craft_window::title_bar::sync_movable`). + pub window_movable: Option, /// The host installed a native menu bar (no in-window menus then). pub native_menu: bool, /// Shortcuts the native menu bar currently handles (`Cmd+Z`, `G`…): the egui shortcut handler @@ -611,6 +612,7 @@ impl LightcraftApp { perf: Perf::default(), caches: Caches::default(), integrated_titlebar: false, + window_movable: None, native_menu: false, native_shortcuts: Default::default(), recording_shortcut: None, @@ -2674,6 +2676,10 @@ impl LightcraftApp { panels::keyword_list::drag_feedback(self, &ctx); panels::toast(self, &ctx); drop_overlay::show(self, &ctx); + // AppKit moves the window on a drag anywhere in the title-bar band unless the window isn't + // movable: it stops being movable while the pointer is on a bar control or a panel over it + #[cfg(target_os = "macos")] + craft_window::title_bar::sync_movable(&ctx, &mut self.window_movable); if notice_blocks { // Keep the real press/release state for the next pass: an ordinary click spans host // frames, and resetting it above must only hide it from this pass's dialogs. diff --git a/crates/ui-egui/src/panels/topbar.rs b/crates/ui-egui/src/panels/topbar.rs index c1c645a1d..2ba807dcc 100644 --- a/crates/ui-egui/src/panels/topbar.rs +++ b/crates/ui-egui/src/panels/topbar.rs @@ -9,40 +9,11 @@ use crate::theme::Tokens; 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, bar: Rect, search_rect: Rect) { - use crate::titlebar::{Gesture, WindowState, command_for}; +/// double-click acts as the system setting says. Made before the bar's widgets so they win the click. +fn window_handle(ui: &mut egui::Ui, bar: Rect) { register(ui, "region:titlebar", bar); - let handle = egui::Id::unique("titlebar-handle"); - let resp = ui.interact(bar, handle, Sense::click_and_drag()); + let resp = craft_window::title_bar::empty_area(ui, bar); crate::widgets::announce(&resp, crate::i18n::tr("Title bar")); - // a press that began on a button or the search field is theirs: dragging off it must not move the window - // (while pressing, egui hovers every widget under the pointer; once the drag starts, only the dragged one) - let ctx = ui.ctx().clone(); - let memo = egui::Id::unique("titlebar-press-on-widget"); - if resp.is_pointer_button_down_on() && ctx.input(|i| i.pointer.primary_pressed()) { - let hovered = ctx.interaction_snapshot(|s| s.hovered.clone()); - let on_widget = - hovered.iter().any(|id| *id != handle && ctx.read_response(*id).is_some_and(|r| r.sense.senses_click() || r.sense.senses_drag())); - ctx.data_mut(|d| d.insert_temp(memo, on_widget)); - } - let on_widget = ctx.data(|d| d.get_temp::(memo)).unwrap_or(false); - // The search field is drawn after this handle. Its hit-test response may not exist - // yet when the press is recorded; the press origin still identifies text selection. - let on_search = ui.input(|i| i.pointer.press_origin().is_some_and(|p| search_rect.contains(p))); - let gesture = if resp.double_clicked_by(egui::PointerButton::Primary) && !on_search { - Some(Gesture::DoubleClicked) - } else if resp.drag_started_by(egui::PointerButton::Primary) && !on_widget && !on_search { - Some(Gesture::DragStarted) - } else { - None - }; - if let Some(g) = gesture { - let window = WindowState { maximized: ui.input(|i| i.viewport().maximized).unwrap_or(false), fullscreen: app.window_is_fullscreen }; - if let Some(cmd) = command_for(g, window) { - ui.ctx().send_viewport_cmd(cmd); - } - } } /// Draw the top bar: the titlebar drag area, the in-window menus when there is no native menu @@ -78,7 +49,7 @@ pub fn show(app: &mut LightcraftApp, ui: &mut egui::Ui) { 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, bar, sr); + window_handle(ui, bar); } 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 36598190c..8d40f212e 100644 --- a/crates/ui-egui/src/tests_titlebar.rs +++ b/crates/ui-egui/src/tests_titlebar.rs @@ -169,3 +169,25 @@ fn full_screen_moves_the_left_buttons_into_the_traffic_lights_space() { assert!(!h.app.window_is_fullscreen); assert_eq!(widget(&h, "icon:sidebar").left(), windowed, "leaving full screen restores the room for the traffic lights"); } + +// Scenario: a panel floating over the bar keeps a drag on it from moving the window (macOS's +// title-bar band would otherwise take the drag); a bar control does too +#[test] +fn a_panel_floating_over_the_bar_keeps_the_window_from_moving() { + let mut h = integrated(); + let bar = widget(&h, "region:titlebar"); + let empty = empty_spot(&h); + 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) + }; + assert!(hover(&mut h, empty), "the empty bar moves the window"); + let help = widget(&h, "icon:help").center(); + assert!(!hover(&mut h, help), "a bar control"); + let params = json!({"operation": "float", "panel": "tools", "rect": [bar.left(), bar.top() + 4.0, 330.0, 500.0]}); + let r = h.request("engine.execute", json!({"command": "ui.dock", "params": params}), T); + assert_eq!(r["ok"], true, "{r}"); + play(&mut h); + assert!(!hover(&mut h, empty), "the floating panel covers the empty bar there"); +} diff --git a/crates/ui-egui/src/titlebar.rs b/crates/ui-egui/src/titlebar.rs deleted file mode 100644 index 40c8975cd..000000000 --- a/crates/ui-egui/src/titlebar.rs +++ /dev/null @@ -1,76 +0,0 @@ -//! The integrated title bar's window gestures (macOS: the native title bar is hidden and the top -//! bar takes its place, so dragging and double-click-to-zoom have to be done here). -//! -//! Pure policy, no egui widgets: [`command_for`] maps a gesture on the bar's empty space and the -//! window's current state to the viewport command the host should run. - -use egui::ViewportCommand; - -/// What the user did on the bar's empty space. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub enum Gesture { - /// A primary-button drag began: the window follows the pointer. - DragStarted, - /// A primary-button double-click: zoom the window, or restore it when already zoomed. - DoubleClicked, -} - -/// The window as the host reports it. -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] -pub struct WindowState { - /// Zoomed (macOS) / maximized (elsewhere). - pub maximized: bool, - /// In the OS's full-screen mode. - pub fullscreen: bool, -} - -/// The viewport command for `gesture`, if any. Full screen has no title bar to drag or zoom. -pub fn command_for(gesture: Gesture, window: WindowState) -> Option { - if window.fullscreen { - return None; - } - match gesture { - Gesture::DragStarted => Some(ViewportCommand::StartDrag), - Gesture::DoubleClicked => Some(ViewportCommand::Maximized(!window.maximized)), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - const NORMAL: WindowState = WindowState { maximized: false, fullscreen: false }; - const ZOOMED: WindowState = WindowState { maximized: true, fullscreen: false }; - const FULL: WindowState = WindowState { maximized: false, fullscreen: true }; - - // Scenario: dragging the title bar moves the window - #[test] - fn given_a_normal_window_when_dragging_then_the_window_follows_the_pointer() { - assert_eq!(command_for(Gesture::DragStarted, NORMAL), Some(ViewportCommand::StartDrag)); - } - - // Scenario: a zoomed window can still be dragged (macOS lets you pull it free) - #[test] - fn given_a_zoomed_window_when_dragging_then_the_window_follows_the_pointer() { - assert_eq!(command_for(Gesture::DragStarted, ZOOMED), Some(ViewportCommand::StartDrag)); - } - - // Scenario: double-click zooms - #[test] - fn given_a_normal_window_when_double_clicking_then_it_zooms() { - assert_eq!(command_for(Gesture::DoubleClicked, NORMAL), Some(ViewportCommand::Maximized(true))); - } - - // Scenario: double-click again restores - #[test] - fn given_a_zoomed_window_when_double_clicking_then_it_is_restored() { - assert_eq!(command_for(Gesture::DoubleClicked, ZOOMED), Some(ViewportCommand::Maximized(false))); - } - - // Scenario: full screen ignores both - #[test] - fn given_a_full_screen_window_then_neither_gesture_does_anything() { - assert_eq!(command_for(Gesture::DragStarted, FULL), None); - assert_eq!(command_for(Gesture::DoubleClicked, FULL), None); - } -} diff --git a/docs/parity-checklist.md b/docs/parity-checklist.md index 080680b9f..afbed8675 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-24 · **Change:** minor (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-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 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/titlebar.rs`, `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 zooms or restores it; 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). Zoom is winit's `Maximized`: it does not follow System Settings › “Double-click a window's title bar to” (Fill / Minimize). a press that starts on a button or the search field never moves the window. 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). 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 @@ -799,6 +799,7 @@ Latin stays upright. Advanced Japanese composition remains open. Regression cove | Date | Change | Summary | |---|---|---| +| 2026-10-25 | patch | LR-BEHAV-TITLEBAR: the top bar's title-bar gestures come from craft-window's `title_bar`: a double-click follows the system setting, and a press on a button, the search field or a panel floating over the bar never moves the window; status counts unchanged | | 2026-10-24 | minor | KEYC-METADATA, LRC-LIB-KEYWORDS: ⌥0 / ⌥⇧0 (`keyword.nextSet`, `keyword.previousSet`) go to the next / previous keyword set; status counts unchanged (KEYC-METADATA stays 🟡) | | 2026-10-24 | minor | KEYC-METADATA: ⌘K / Ctrl+K (`panel.addKeywords`, Add Keywords) shows the Keywords panel, opens Keywording and puts the cursor in the Add keyword box; status counts unchanged (KEYC-METADATA stays 🟡) | | 2026-10-24 | patch | LR-LIB-RATING, LR-LIB-DELETE: a slider drag still open when a rating, flag or label takes the photo out of the view, or when the photo is deleted, ends as its own undo step before the selection moves on, as moving to the next photo does; LR-BEHAV-LOCALIZE lists all 18 languages; status unchanged |