diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 3e197efaf..3116a4da9 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -1044,7 +1044,9 @@ pub fn show(app: &mut SheetApp, ctx: &egui::Context) { .collapsible(false) .resizable(false) .default_width(width) - .anchor(egui::Align2::CENTER_CENTER, vec2(0.0, -40.0)) + // A default position, not an anchor: egui anchors can't be dragged, and dialogs move by their title bar. + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(ctx.content_rect().center() + vec2(0.0, -40.0)) .open(&mut win_open); let window = if d.name == "insertPicture" { window.default_height(240.0) } else { window }; window.show(ctx, |ui| { diff --git a/crates/ui-egui/tests/dialog_move.rs b/crates/ui-egui/tests/dialog_move.rs new file mode 100644 index 000000000..7548342c7 --- /dev/null +++ b/crates/ui-egui/tests/dialog_move.rs @@ -0,0 +1,70 @@ +//! Dialogs can be dragged by their title bar, like Excel's, and stay where they are dropped. + +use egui::{Event, Modifiers, PointerButton, vec2}; +use gridcraft_engine::Session; +use gridcraft_ui_egui::SheetApp; +use serde_json::json; + +type Harness = egui_kittest::Harness<'static, SheetApp>; + +fn harness() -> Harness { + let mut session = Session::new(); + session.new_workbook(); + let mut h = egui_kittest::Harness::builder().with_size(vec2(1200.0, 800.0)).build_ui_state( + |ui, app: &mut SheetApp| { + app.logic(&ui.ctx().clone()); + app.ui(ui); + }, + SheetApp::new(session, Default::default()), + ); + h.run_steps(6); + h +} + +fn dialog_rect(h: &Harness, name: &str) -> egui::Rect { + h.ctx.memory(|m| m.area_rect(egui::Id::new("dialog").with(name))).unwrap() +} + +fn button(h: &mut Harness, pos: egui::Pos2, pressed: bool) { + h.input_mut().events.push(Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE }); + h.run_steps(2); +} + +fn drag_title_bar(h: &mut Harness, name: &str, by: egui::Vec2) { + let start = dialog_rect(h, name).center_top() + vec2(0.0, 10.0); + h.input_mut().events.push(Event::PointerMoved(start)); + h.run_steps(2); + button(h, start, true); + for step in 1..=4 { + h.input_mut().events.push(Event::PointerMoved(start + by * (step as f32 / 4.0))); + h.run_steps(1); + } + button(h, start + by, false); +} + +#[test] +fn dragging_the_find_and_replace_title_bar_moves_the_dialog_and_it_stays_there() { + let mut h = harness(); + h.state_mut().open_dialog("find", json!({})); + h.run_steps(6); + let before = dialog_rect(&h, "find"); + drag_title_bar(&mut h, "find", vec2(120.0, 80.0)); + h.run_steps(10); + let after = dialog_rect(&h, "find"); + assert_eq!(after.min - before.min, vec2(120.0, 80.0), "dialog went from {before:?} to {after:?}"); + assert!(h.state().dialog.is_some(), "the drag does not close the dialog"); +} + +#[test] +fn go_to_format_cells_and_sort_can_be_dragged_too() { + for name in ["goTo", "formatCells", "sort"] { + let mut h = harness(); + h.state_mut().open_dialog(name, json!({})); + h.run_steps(6); + let before = dialog_rect(&h, name); + drag_title_bar(&mut h, name, vec2(-100.0, 60.0)); + h.run_steps(10); + let after = dialog_rect(&h, name); + assert_eq!(after.min - before.min, vec2(-100.0, 60.0), "{name} went from {before:?} to {after:?}"); + } +} diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 34cb502f5..bcb0520d7 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -1,6 +1,6 @@ # UI parity -> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-19 · **Change:** minor (Ctrl+Enter fill gives each cell the automatic number format typing gives it; keytip letters typed with Alt held, #1846) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 +> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-19 · **Change:** minor (Ctrl+Enter fill gives each cell the automatic number format typing gives it; keytip letters typed with Alt held, #1846; dialogs move by their title bar, #1250) · **Target:** Microsoft Excel (Microsoft 365), Excel for Mac 16.113.4 Interaction, controls and feel, against Excel. Part of [target-app-parity.md](target-app-parity.md). **UI/UX fidelity: ~55% ready, 50–80 h** (estimated). Ribbon command coverage is measured @@ -14,6 +14,7 @@ Sources: `crates/ui-egui/src` (ribbon, grid, editor, formula bar, dialogs, panes | Element | Excel | GridCraft | Status | |---|---|---|---| +| 2026-10-14 | minor | Dialogs (Find and Replace, Go To, Format Cells, Sort and the rest) move by their title bar instead of staying pinned to the centre (#1250) | | Title bar, Quick Access Toolbar, AutoSave toggle | yes | yes | done; macOS window controls misaligned (#170), Dock icon size (#169) | | Ribbon: tabs, groups, split buttons, galleries, contextual tabs | yes | yes (Table Design, Chart Design, Format, PivotTable, Header & Footer tabs) | done; missing contextual tabs for pictures, shapes, sparklines, slicers | | Ribbon collapse, simplified ribbon, customize ribbon/QAT | yes | partial | customization missing (File ▸ Options is the one missing File command) | @@ -137,7 +138,8 @@ Format Cells, Insert Function/Formula Builder, Find and Replace, Go To and Go To Name Manager, Data Validation, Paste Special, Conditional Formatting manager, Page Setup, Goal Seek, Scenario Manager, Data Analysis, Forecast Sheet and others exist. Missing or partial: Options, Solver, Power Query editor, Insert Object, Equation editor, Protect/Allow Edit Ranges, -Workbook Links, Accessibility checker. +Workbook Links, Accessibility checker. Like Excel's, every dialog opens centred and moves by its +title bar, keeping its place while it is open (#1250). ## Accessibility