Merge pull request #5837 from mockydevs/fix/move-cursor-5246

Dialogs: the header that moves a dialog keeps the arrow cursor until dragged (#5246)
This commit is contained in:
BFlat
2026-11-06 01:44:53 +07:00
committed by GitHub
2 changed files with 31 additions and 5 deletions
+6 -5
View File
@@ -1,8 +1,9 @@
//! The modal dialog window: a backdrop that keeps the rest of the app from taking clicks, and a
//! window with no title bar that opens centred and moves by dragging its heading (the band from its
//! top edge down to the heading), as a title bar would. Where it was moved to stays, per window, for
//! the session, and the window never leaves the screen. Rows too many for the screen's height
//! scroll inside it ([`scrolled_rows`]), so its heading and buttons stay in view.
//! top edge down to the heading), as a title bar would, showing the arrow until it is dragged. Where
//! it was moved to stays, per window, for the session, and the window never leaves the screen. Rows
//! too many for the screen's height scroll inside it ([`scrolled_rows`]), so its heading and buttons
//! stay in view.
use egui::{Id, Rect, Vec2};
@@ -60,10 +61,10 @@ pub(crate) fn drag_band(ui: &mut egui::Ui, bottom: f32) {
let band = Rect::from_x_y_ranges(outer.x_range(), outer.top()..=bottom.max(outer.top()));
// Not focusable (`Sense::drag()` is): Tab goes through the dialog's controls, not the band.
let r = ui.interact(band, handle(id), egui::Sense::DRAG);
// Hovered, it keeps the arrow, as a title bar does (the grab cursor is Windows' four-way move
// arrows: #5246); only while it is dragged does it show the window being carried.
if r.dragged() {
ui.ctx().set_cursor_icon(egui::CursorIcon::Grabbing);
} else if r.hovered() {
ui.ctx().set_cursor_icon(egui::CursorIcon::Grab);
}
}
+25
View File
@@ -118,6 +118,31 @@ fn dialogs_move_by_their_heading_stay_on_screen_and_where_they_were_moved() {
}
}
/// #5246: the band that moves a dialog keeps the arrow when hovered, as a title bar does (the grab
/// cursor is the four-way move arrows on Windows); it shows the window held only while dragged.
#[test]
fn the_band_that_moves_a_dialog_keeps_the_arrow_until_dragged() {
let opens: [(&str, Open, egui::Id); 2] = [
("Offset Path", |app| crate::menus::invoke(app, "object.path.offsetPath", json!({})), egui::Id::unique(("dialog", "offsetPath"))),
("New Document", open_new_document, egui::Id::unique(("dialog", "newDocument"))),
];
for (name, open, id) in opens {
let (ctx, mut app) = (ctx(), app_with_selection());
open(&mut app);
settle(&ctx, &mut app);
let at = rect_of(&ctx, id).left_top() + vec2(40.0, 6.0);
let cursor = |ctx: &egui::Context, app: &mut VectorcraftApp, events: Vec<Event>| {
let input = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, SCREEN)), events, ..Default::default() };
ctx.run_ui(input, |ui| show(app, ui.ctx())).platform_output.cursor_icon
};
cursor(&ctx, &mut app, vec![Event::PointerMoved(at)]);
assert_eq!(cursor(&ctx, &mut app, vec![]), egui::CursorIcon::Default, "{name}: hovering the band");
cursor(&ctx, &mut app, vec![button(at, true)]);
assert_eq!(cursor(&ctx, &mut app, vec![Event::PointerMoved(at + vec2(20.0, 10.0))]), egui::CursorIcon::Grabbing, "{name}: dragging it");
cursor(&ctx, &mut app, vec![button(at + vec2(20.0, 10.0), false)]);
}
}
/// Tab goes through a dialog's controls and does not stop on the band that moves the window.
#[test]
fn tab_skips_the_band_that_moves_a_dialog() {