summaryrefslogtreecommitdiffstats
path: root/rspaint
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--rspaint/Cargo.toml10
-rw-r--r--rspaint/src/lib.rs122
2 files changed, 132 insertions, 0 deletions
diff --git a/rspaint/Cargo.toml b/rspaint/Cargo.toml
new file mode 100644
index 0000000..66364a2
--- /dev/null
+++ b/rspaint/Cargo.toml
@@ -0,0 +1,10 @@
1[package]
2name = "rspaint"
3version = "0.1.0"
4edition = "2024"
5
6[lints]
7workspace = true
8
9[dependencies]
10rsnix = { path = "../rsnix" }
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
5use rsnix::ps2::{KeyEvent, MouseKind};
6use rsnix::{sched, wm};
7
8const W: usize = 640;
9const H: usize = 480;
10
11/// 8-colour palette selected by keys 1–8.
12const 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`.
25pub 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
76fn pack(idx: usize) -> u32 {
77 let (r, g, b) = PALETTE[idx.min(PALETTE.len() - 1)];
78 rsnix::gop::rgb(r, g, b)
79}
80
81fn 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.
91fn 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
116fn fill(buf: *mut u32, color: usize) {
117 unsafe {
118 for i in 0..(W * H) {
119 *buf.add(i) = pack(color);
120 }
121 }
122}