diff options
Diffstat (limited to '')
| -rw-r--r-- | rspaint/src/lib.rs | 122 |
1 files changed, 122 insertions, 0 deletions
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 @@ | |||
| 1 | #![no_std] | ||
| 2 | |||
| 3 | //! Paint — the first GUI app: a freehand pen on a raster canvas. | ||
| 4 | |||
| 5 | use rsnix::ps2::{KeyEvent, MouseKind}; | ||
| 6 | use rsnix::{sched, wm}; | ||
| 7 | |||
| 8 | const W: usize = 640; | ||
| 9 | const H: usize = 480; | ||
| 10 | |||
| 11 | /// 8-colour palette selected by keys 1–8. | ||
| 12 | const PALETTE: [(u8, u8, u8); 8] = [ | ||
| 13 | (255, 255, 255), // 1 white | ||
| 14 | (0, 0, 0), // 2 black | ||
| 15 | (170, 0, 0), // 3 red | ||
| 16 | (0, 170, 0), // 4 green | ||
| 17 | (0, 0, 170), // 5 blue | ||
| 18 | (170, 170, 0), // 6 yellow | ||
| 19 | (0, 170, 170), // 7 cyan | ||
| 20 | (170, 0, 170), // 8 magenta | ||
| 21 | ]; | ||
| 22 | |||
| 23 | /// Run the paint program as a spawned task: create a window, then loop on | ||
| 24 | /// events (pen strokes from the mouse, colour/clear from keys) until `Close`. | ||
| 25 | pub fn run() { | ||
| 26 | let Some(id) = wm::create_window("paint", W, H) else { | ||
| 27 | return; | ||
| 28 | }; | ||
| 29 | let Some((buf, _, _)) = wm::buffer(id) else { | ||
| 30 | return; | ||
| 31 | }; | ||
| 32 | |||
| 33 | let mut color: usize = 0; // white | ||
| 34 | let mut last: Option<(i32, i32)> = None; | ||
| 35 | |||
| 36 | loop { | ||
| 37 | match sched::recv_blocking() { | ||
| 38 | sched::TaskMsg::Close => break, | ||
| 39 | sched::TaskMsg::AppMouse(m) => match m.kind { | ||
| 40 | MouseKind::Press => { | ||
| 41 | plot(buf, m.x, m.y, color); | ||
| 42 | last = Some((m.x, m.y)); | ||
| 43 | wm::flush(id); | ||
| 44 | } | ||
| 45 | MouseKind::Move => { | ||
| 46 | if m.buttons & 1 != 0 { | ||
| 47 | if let Some((lx, ly)) = last { | ||
| 48 | line(buf, lx, ly, m.x, m.y, color); | ||
| 49 | wm::flush(id); | ||
| 50 | } | ||
| 51 | } | ||
| 52 | last = Some((m.x, m.y)); | ||
| 53 | } | ||
| 54 | MouseKind::Release => { | ||
| 55 | last = None; | ||
| 56 | } | ||
| 57 | }, | ||
| 58 | sched::TaskMsg::Key(k) => { | ||
| 59 | if let KeyEvent::Char(c) = k { | ||
| 60 | match c { | ||
| 61 | '1'..='8' => color = (c as u8 - b'1') as usize, | ||
| 62 | 'c' => { | ||
| 63 | fill(buf, 1); // clear to black | ||
| 64 | wm::flush(id); | ||
| 65 | } | ||
| 66 | _ => {} | ||
| 67 | } | ||
| 68 | } | ||
| 69 | } | ||
| 70 | _ => {} | ||
| 71 | } | ||
| 72 | } | ||
| 73 | wm::destroy(id); | ||
| 74 | } | ||
| 75 | |||
| 76 | fn pack(idx: usize) -> u32 { | ||
| 77 | let (r, g, b) = PALETTE[idx.min(PALETTE.len() - 1)]; | ||
| 78 | rsnix::gop::rgb(r, g, b) | ||
| 79 | } | ||
| 80 | |||
| 81 | fn plot(buf: *mut u32, x: i32, y: i32, color: usize) { | ||
| 82 | if x < 0 || y < 0 || x >= W as i32 || y >= H as i32 { | ||
| 83 | return; | ||
| 84 | } | ||
| 85 | unsafe { | ||
| 86 | *buf.add(y as usize * W + x as usize) = pack(color); | ||
| 87 | } | ||
| 88 | } | ||
| 89 | |||
| 90 | /// Bresenham line between two points. | ||
| 91 | fn line(buf: *mut u32, x0: i32, y0: i32, x1: i32, y1: i32, color: usize) { | ||
| 92 | let mut x = x0; | ||
| 93 | let mut y = y0; | ||
| 94 | let dx = (x1 - x0).abs(); | ||
| 95 | let sx = if x0 < x1 { 1 } else { -1 }; | ||
| 96 | let dy = -(y1 - y0).abs(); | ||
| 97 | let sy = if y0 < y1 { 1 } else { -1 }; | ||
| 98 | let mut err = dx + dy; | ||
| 99 | loop { | ||
| 100 | plot(buf, x, y, color); | ||
| 101 | if x == x1 && y == y1 { | ||
| 102 | break; | ||
| 103 | } | ||
| 104 | let e2 = 2 * err; | ||
| 105 | if e2 >= dy { | ||
| 106 | err += dy; | ||
| 107 | x += sx; | ||
| 108 | } | ||
| 109 | if e2 <= dx { | ||
| 110 | err += dx; | ||
| 111 | y += sy; | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | fn fill(buf: *mut u32, color: usize) { | ||
| 117 | unsafe { | ||
| 118 | for i in 0..(W * H) { | ||
| 119 | *buf.add(i) = pack(color); | ||
| 120 | } | ||
| 121 | } | ||
| 122 | } | ||
