From 0e405aad6bc9f631b9b7a766fb1147fef943f492 Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 17 Aug 2026 18:58:06 +0200 Subject: wm: title-bar drag-to-move; stop delivering title-bar gestures to apps - Track the active mouse gesture (none / window-move / app-stroke) so a press on the title bar becomes a move drag instead of leaking Move events into the app's content - Add move_window (clamped to the canvas, repaint old + new regions) - Always deliver MouseKind::Release so a stroke that ends outside the window still terminates --- rsnix/src/wm.rs | 126 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 106 insertions(+), 20 deletions(-) (limited to 'rsnix/src/wm.rs') diff --git a/rsnix/src/wm.rs b/rsnix/src/wm.rs index a39702d..be74f9a 100644 --- a/rsnix/src/wm.rs +++ b/rsnix/src/wm.rs @@ -526,29 +526,93 @@ fn hit_test(id: usize, x: usize, y: usize) -> Hit { Hit::Title } +/// What an in-progress mouse gesture is doing (started on a press). +#[derive(Clone, Copy, PartialEq, Eq)] +enum Drag { + None, + Move, // dragging a window by its title bar + App, // a stroke/drag inside a window's content +} + +static mut DRAG: Drag = Drag::None; +static mut DRAG_ID: usize = usize::MAX; +static mut DRAG_DX: i32 = 0; // cursor - window.x at drag start +static mut DRAG_DY: i32 = 0; // cursor - window.y at drag start + /// Route one mouse event (absolute canvas coords, button bitmask, kind). pub fn route_mouse(x: i32, y: i32, buttons: u8, kind: MouseKind) { - if kind == MouseKind::Press { - if x < 0 || y < 0 { - return; - } - let (cx, cy) = (x as usize, y as usize); - if let Some(id) = window_at(cx, cy) { - match hit_test(id, cx, cy) { - Hit::Close => request_close(id), - Hit::Minimize => minimize(id), - Hit::Title => focus(id), - Hit::Content => { - focus(id); - deliver_mouse(id, cx, cy, buttons, kind); + match kind { + MouseKind::Press => mouse_press(x, y, buttons), + MouseKind::Move => mouse_move(x, y, buttons), + MouseKind::Release => mouse_release(x, y, buttons), + } +} + +fn mouse_press(x: i32, y: i32, buttons: u8) { + if x < 0 || y < 0 { + return; + } + let (cx, cy) = (x as usize, y as usize); + if let Some(id) = window_at(cx, cy) { + match hit_test(id, cx, cy) { + Hit::Close => request_close(id), + Hit::Minimize => minimize(id), + Hit::Title => { + focus(id); + if let Some(w) = get(id) { + unsafe { + DRAG = Drag::Move; + DRAG_ID = id; + DRAG_DX = cx as i32 - w.x as i32; + DRAG_DY = cy as i32 - w.y as i32; + } } - Hit::None => {} } - } else { - focus_console(); + Hit::Content => { + focus(id); + unsafe { + DRAG = Drag::App; + DRAG_ID = id; + } + deliver_mouse(id, cx, cy, buttons, MouseKind::Press); + } + Hit::None => {} } - } else if let Some(id) = focused() { - deliver_mouse(id, x as usize, y as usize, buttons, kind); + } else { + focus_console(); + } +} + +fn mouse_move(x: i32, y: i32, buttons: u8) { + match unsafe { DRAG } { + Drag::Move => { + let id = unsafe { DRAG_ID }; + let Some(w) = get(id) else { + unsafe { DRAG = Drag::None } + return; + }; + let nx = (x - unsafe { DRAG_DX }).max(0) as usize; + let ny = (y - unsafe { DRAG_DY }).max(0) as usize; + if nx != w.x || ny != w.y { + move_window(id, nx, ny); + } + } + Drag::App => { + let id = unsafe { DRAG_ID }; + deliver_mouse(id, x as usize, y as usize, buttons, MouseKind::Move); + } + Drag::None => {} + } +} + +fn mouse_release(x: i32, y: i32, buttons: u8) { + if unsafe { DRAG } == Drag::App { + let id = unsafe { DRAG_ID }; + deliver_mouse(id, x as usize, y as usize, buttons, MouseKind::Release); + } + unsafe { + DRAG = Drag::None; + DRAG_ID = usize::MAX; } } @@ -558,8 +622,10 @@ fn deliver_mouse(id: usize, cx: usize, cy: usize, buttons: u8, kind: MouseKind) }; let rx = cx as i32 - w.x as i32; let ry = cy as i32 - (w.y + TITLE_BAR_H) as i32; - if rx < 0 || ry < 0 || rx >= w.w as i32 || ry >= w.h as i32 { - return; // out of bounds — suppressed + // Suppress out-of-bounds Press/Move, but always deliver Release so a + // stroke that ends outside the window still terminates. + if kind != MouseKind::Release && (rx < 0 || ry < 0 || rx >= w.w as i32 || ry >= w.h as i32) { + return; } let ev = AppMouseEvent { x: rx, @@ -570,6 +636,26 @@ fn deliver_mouse(id: usize, cx: usize, cy: usize, buttons: u8, kind: MouseKind) let _ = crate::sched::send(w.owner, TaskMsg::AppMouse(ev)); } +/// Move a window to (nx, ny), clamping to the canvas, and repaint old+new. +fn move_window(id: usize, nx: usize, ny: usize) { + let Some(w) = get(id) else { + return; + }; + let (cw, ch) = gop::pixel_dims(); + let nx = nx.min(cw.saturating_sub(w.w)); + let ny = ny.min(ch.saturating_sub(w.h + TITLE_BAR_H)); + if nx == w.x && ny == w.y { + return; + } + let (ox, oy) = (w.x, w.y); + unsafe { + WINDOWS[id].x = nx; + WINDOWS[id].y = ny; + } + composite_rect(ox, oy, w.w, w.h + TITLE_BAR_H); + composite_rect(nx, ny, w.w, w.h + TITLE_BAR_H); +} + /// Spawn the keyboard pump task (routes keys to the focused GUI window). pub fn spawn_input_pump() -> Option { crate::sched::spawn(pump_main, "wm-input") -- cgit v1.2.3