summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-17 18:58:06 +0200
committersalmonik <salmonik@tuta.io>2026-08-17 18:58:06 +0200
commit0e405aad6bc9f631b9b7a766fb1147fef943f492 (patch)
treed9c10ed2e3fae589aabff3375d53bbf66139add2
parentWindowed GUI: wm module, input routing, and the paint app (diff)
downloadrs-dos-0e405aad6bc9f631b9b7a766fb1147fef943f492.tar.gz
rs-dos-0e405aad6bc9f631b9b7a766fb1147fef943f492.zip
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
-rw-r--r--docs/adr/0004-windowed-gui-and-compositor.md4
-rw-r--r--rsnix/src/wm.rs126
2 files changed, 108 insertions, 22 deletions
diff --git a/docs/adr/0004-windowed-gui-and-compositor.md b/docs/adr/0004-windowed-gui-and-compositor.md
index 86e8b30..3476fab 100644
--- a/docs/adr/0004-windowed-gui-and-compositor.md
+++ b/docs/adr/0004-windowed-gui-and-compositor.md
@@ -62,8 +62,8 @@ Introduce an overlapping-window GUI:
62 console (app 0) never closes. 62 console (app 0) never closes.
63- **Placement** — new windows cascade (+24px each, wrapping) and are 63- **Placement** — new windows cascade (+24px each, wrapping) and are
64 focused and raised on creation; the console is full-canvas at (0,0). 64 focused and raised on creation; the console is full-canvas at (0,0).
65 Windows are fixed-position and fixed-size in v1: move is the first 65 Windows are movable by dragging the title bar (clamped to the canvas);
66 post-v1 feature and resize is deferred. 66 resize is deferred.
67 67
68## Consequences 68## Consequences
69 69
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 {
526 Hit::Title 526 Hit::Title
527} 527}
528 528
529/// What an in-progress mouse gesture is doing (started on a press).
530#[derive(Clone, Copy, PartialEq, Eq)]
531enum Drag {
532 None,
533 Move, // dragging a window by its title bar
534 App, // a stroke/drag inside a window's content
535}
536
537static mut DRAG: Drag = Drag::None;
538static mut DRAG_ID: usize = usize::MAX;
539static mut DRAG_DX: i32 = 0; // cursor - window.x at drag start
540static mut DRAG_DY: i32 = 0; // cursor - window.y at drag start
541
529/// Route one mouse event (absolute canvas coords, button bitmask, kind). 542/// Route one mouse event (absolute canvas coords, button bitmask, kind).
530pub fn route_mouse(x: i32, y: i32, buttons: u8, kind: MouseKind) { 543pub fn route_mouse(x: i32, y: i32, buttons: u8, kind: MouseKind) {
531 if kind == MouseKind::Press { 544 match kind {
532 if x < 0 || y < 0 { 545 MouseKind::Press => mouse_press(x, y, buttons),
533 return; 546 MouseKind::Move => mouse_move(x, y, buttons),
534 } 547 MouseKind::Release => mouse_release(x, y, buttons),
535 let (cx, cy) = (x as usize, y as usize); 548 }
536 if let Some(id) = window_at(cx, cy) { 549}
537 match hit_test(id, cx, cy) { 550
538 Hit::Close => request_close(id), 551fn mouse_press(x: i32, y: i32, buttons: u8) {
539 Hit::Minimize => minimize(id), 552 if x < 0 || y < 0 {
540 Hit::Title => focus(id), 553 return;
541 Hit::Content => { 554 }
542 focus(id); 555 let (cx, cy) = (x as usize, y as usize);
543 deliver_mouse(id, cx, cy, buttons, kind); 556 if let Some(id) = window_at(cx, cy) {
557 match hit_test(id, cx, cy) {
558 Hit::Close => request_close(id),
559 Hit::Minimize => minimize(id),
560 Hit::Title => {
561 focus(id);
562 if let Some(w) = get(id) {
563 unsafe {
564 DRAG = Drag::Move;
565 DRAG_ID = id;
566 DRAG_DX = cx as i32 - w.x as i32;
567 DRAG_DY = cy as i32 - w.y as i32;
568 }
544 } 569 }
545 Hit::None => {}
546 } 570 }
547 } else { 571 Hit::Content => {
548 focus_console(); 572 focus(id);
573 unsafe {
574 DRAG = Drag::App;
575 DRAG_ID = id;
576 }
577 deliver_mouse(id, cx, cy, buttons, MouseKind::Press);
578 }
579 Hit::None => {}
549 } 580 }
550 } else if let Some(id) = focused() { 581 } else {
551 deliver_mouse(id, x as usize, y as usize, buttons, kind); 582 focus_console();
583 }
584}
585
586fn mouse_move(x: i32, y: i32, buttons: u8) {
587 match unsafe { DRAG } {
588 Drag::Move => {
589 let id = unsafe { DRAG_ID };
590 let Some(w) = get(id) else {
591 unsafe { DRAG = Drag::None }
592 return;
593 };
594 let nx = (x - unsafe { DRAG_DX }).max(0) as usize;
595 let ny = (y - unsafe { DRAG_DY }).max(0) as usize;
596 if nx != w.x || ny != w.y {
597 move_window(id, nx, ny);
598 }
599 }
600 Drag::App => {
601 let id = unsafe { DRAG_ID };
602 deliver_mouse(id, x as usize, y as usize, buttons, MouseKind::Move);
603 }
604 Drag::None => {}
605 }
606}
607
608fn mouse_release(x: i32, y: i32, buttons: u8) {
609 if unsafe { DRAG } == Drag::App {
610 let id = unsafe { DRAG_ID };
611 deliver_mouse(id, x as usize, y as usize, buttons, MouseKind::Release);
612 }
613 unsafe {
614 DRAG = Drag::None;
615 DRAG_ID = usize::MAX;
552 } 616 }
553} 617}
554 618
@@ -558,8 +622,10 @@ fn deliver_mouse(id: usize, cx: usize, cy: usize, buttons: u8, kind: MouseKind)
558 }; 622 };
559 let rx = cx as i32 - w.x as i32; 623 let rx = cx as i32 - w.x as i32;
560 let ry = cy as i32 - (w.y + TITLE_BAR_H) as i32; 624 let ry = cy as i32 - (w.y + TITLE_BAR_H) as i32;
561 if rx < 0 || ry < 0 || rx >= w.w as i32 || ry >= w.h as i32 { 625 // Suppress out-of-bounds Press/Move, but always deliver Release so a
562 return; // out of bounds — suppressed 626 // stroke that ends outside the window still terminates.
627 if kind != MouseKind::Release && (rx < 0 || ry < 0 || rx >= w.w as i32 || ry >= w.h as i32) {
628 return;
563 } 629 }
564 let ev = AppMouseEvent { 630 let ev = AppMouseEvent {
565 x: rx, 631 x: rx,
@@ -570,6 +636,26 @@ fn deliver_mouse(id: usize, cx: usize, cy: usize, buttons: u8, kind: MouseKind)
570 let _ = crate::sched::send(w.owner, TaskMsg::AppMouse(ev)); 636 let _ = crate::sched::send(w.owner, TaskMsg::AppMouse(ev));
571} 637}
572 638
639/// Move a window to (nx, ny), clamping to the canvas, and repaint old+new.
640fn move_window(id: usize, nx: usize, ny: usize) {
641 let Some(w) = get(id) else {
642 return;
643 };
644 let (cw, ch) = gop::pixel_dims();
645 let nx = nx.min(cw.saturating_sub(w.w));
646 let ny = ny.min(ch.saturating_sub(w.h + TITLE_BAR_H));
647 if nx == w.x && ny == w.y {
648 return;
649 }
650 let (ox, oy) = (w.x, w.y);
651 unsafe {
652 WINDOWS[id].x = nx;
653 WINDOWS[id].y = ny;
654 }
655 composite_rect(ox, oy, w.w, w.h + TITLE_BAR_H);
656 composite_rect(nx, ny, w.w, w.h + TITLE_BAR_H);
657}
658
573/// Spawn the keyboard pump task (routes keys to the focused GUI window). 659/// Spawn the keyboard pump task (routes keys to the focused GUI window).
574pub fn spawn_input_pump() -> Option<usize> { 660pub fn spawn_input_pump() -> Option<usize> {
575 crate::sched::spawn(pump_main, "wm-input") 661 crate::sched::spawn(pump_main, "wm-input")