From 5558e3509d020a0e021a77f0617044497b2f2aee Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 17 Aug 2026 18:56:04 +0200 Subject: Windowed GUI: wm module, input routing, and the paint app - Add the wm window-manager facility per ADR 0004: overlapping pixel-surface windows, damage-rectangle compositing, WM-drawn title bar, focus/minimize/close, and F1-F4 keybindings - Add gop shadow compositing primitives and vt::repaint_region so the console stays layer 0 beneath windows - Route mouse (absolute coords, press/release edges) and keyboard (focus-aware input pump) to the focused window - Add the rspaint drawing-program crate (pen, 8 colors, clear) and shell draw/windows/restore commands - Extend TaskMsg with AppMouse/Focus/Close and document the design in CONTEXT.md + ADR 0004 --- rspaint/src/lib.rs | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 rspaint/src/lib.rs (limited to 'rspaint/src/lib.rs') diff --git a/rspaint/src/lib.rs b/rspaint/src/lib.rs new file mode 100644 index 0000000..7c2e2d2 --- /dev/null +++ b/rspaint/src/lib.rs @@ -0,0 +1,122 @@ +#![no_std] + +//! Paint — the first GUI app: a freehand pen on a raster canvas. + +use rsnix::ps2::{KeyEvent, MouseKind}; +use rsnix::{sched, wm}; + +const W: usize = 640; +const H: usize = 480; + +/// 8-colour palette selected by keys 1–8. +const PALETTE: [(u8, u8, u8); 8] = [ + (255, 255, 255), // 1 white + (0, 0, 0), // 2 black + (170, 0, 0), // 3 red + (0, 170, 0), // 4 green + (0, 0, 170), // 5 blue + (170, 170, 0), // 6 yellow + (0, 170, 170), // 7 cyan + (170, 0, 170), // 8 magenta +]; + +/// Run the paint program as a spawned task: create a window, then loop on +/// events (pen strokes from the mouse, colour/clear from keys) until `Close`. +pub fn run() { + let Some(id) = wm::create_window("paint", W, H) else { + return; + }; + let Some((buf, _, _)) = wm::buffer(id) else { + return; + }; + + let mut color: usize = 0; // white + let mut last: Option<(i32, i32)> = None; + + loop { + match sched::recv_blocking() { + sched::TaskMsg::Close => break, + sched::TaskMsg::AppMouse(m) => match m.kind { + MouseKind::Press => { + plot(buf, m.x, m.y, color); + last = Some((m.x, m.y)); + wm::flush(id); + } + MouseKind::Move => { + if m.buttons & 1 != 0 { + if let Some((lx, ly)) = last { + line(buf, lx, ly, m.x, m.y, color); + wm::flush(id); + } + } + last = Some((m.x, m.y)); + } + MouseKind::Release => { + last = None; + } + }, + sched::TaskMsg::Key(k) => { + if let KeyEvent::Char(c) = k { + match c { + '1'..='8' => color = (c as u8 - b'1') as usize, + 'c' => { + fill(buf, 1); // clear to black + wm::flush(id); + } + _ => {} + } + } + } + _ => {} + } + } + wm::destroy(id); +} + +fn pack(idx: usize) -> u32 { + let (r, g, b) = PALETTE[idx.min(PALETTE.len() - 1)]; + rsnix::gop::rgb(r, g, b) +} + +fn plot(buf: *mut u32, x: i32, y: i32, color: usize) { + if x < 0 || y < 0 || x >= W as i32 || y >= H as i32 { + return; + } + unsafe { + *buf.add(y as usize * W + x as usize) = pack(color); + } +} + +/// Bresenham line between two points. +fn line(buf: *mut u32, x0: i32, y0: i32, x1: i32, y1: i32, color: usize) { + let mut x = x0; + let mut y = y0; + let dx = (x1 - x0).abs(); + let sx = if x0 < x1 { 1 } else { -1 }; + let dy = -(y1 - y0).abs(); + let sy = if y0 < y1 { 1 } else { -1 }; + let mut err = dx + dy; + loop { + plot(buf, x, y, color); + if x == x1 && y == y1 { + break; + } + let e2 = 2 * err; + if e2 >= dy { + err += dy; + x += sx; + } + if e2 <= dx { + err += dx; + y += sy; + } + } +} + +fn fill(buf: *mut u32, color: usize) { + unsafe { + for i in 0..(W * H) { + *buf.add(i) = pack(color); + } + } +} -- cgit v1.2.3