1#![deny(clippy::all)]
2#![forbid(unsafe_code)]
3
4mod color;
5pub mod doc;
6mod painter;
7
8use crate::color::*;
9use crate::doc::{MutSketch, OwnedSketch};
10use crate::painter::Painter;
11use serde::{Deserialize, Serialize};
12use std::borrow::BorrowMut;
13use std::collections::{HashMap, VecDeque};
14
15#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq, Hash)]
16pub struct PenId(u32);
17
18impl From<u32> for PenId {
19 fn from(x: u32) -> Self {
20 Self(x)
21 }
22}
23
24#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
25pub enum PenCommand {
26 ChangeColor(Color),
27 ChangeSize(PenSize),
28 MoveCursor(CursorPos),
29 Pressed,
30 Released,
31}
32
33#[derive(Debug, Serialize, Deserialize, Copy, Clone)]
34pub enum Command {
35 ClearAll,
36 ResumeLastSnapshot,
37 TakeSnapshot,
38 PenCommand(PenId, PenCommand),
39}
40
41pub trait CommandSender {
42 fn send_command(&mut self, cmd: Command);
43}
44
45#[derive(Default)]
46struct Pen {
47 cursor: Cursor,
48 prev_cursor: Cursor,
49 color: Color,
50 size: PenSize,
51}
52
53#[derive(Default)]
54struct Pens(HashMap<PenId, Pen>);
55
56impl Pens {
57 fn select(&mut self, id: PenId) -> &mut Pen {
58 self.0.entry(id).or_insert_with(Pen::default).borrow_mut()
59 }
60}
61
62pub struct App {
63 pen_id: PenId,
65 pens: Pens,
67 commands: VecDeque<Command>,
69 palette: ColorSelector,
71 snapshots: Vec<OwnedSketch>,
73 chained_command_sender: Option<Box<dyn FnMut(Command)>>,
75}
76
77#[derive(Default, Debug, Copy, Clone)]
78struct Cursor {
79 pressed: bool,
80 pos: CursorPos,
81}
82
83#[derive(Default, Debug, Copy, Clone, Serialize, Deserialize)]
84pub struct CursorPos {
85 pub x: isize,
86 pub y: isize,
87}
88
89impl App {
90 pub fn new(pen_id: PenId) -> Self {
91 App {
92 pens: Pens::default(),
93 commands: VecDeque::with_capacity(10),
94 palette: ColorSelector::new(&PALETTE),
95 snapshots: Vec::new(),
96 chained_command_sender: Default::default(),
97 pen_id,
98 }
99 }
100
101 pub fn update(&mut self, mut sketch: MutSketch) {
102 while let Some(command) = self.commands.pop_front() {
103 match command {
104 Command::ClearAll => {
105 self.snapshots.push(sketch.to_owned());
106 sketch.frame.fill(0x00);
107 }
108 Command::TakeSnapshot => {
109 self.snapshots.push(sketch.to_owned());
110 }
111 Command::ResumeLastSnapshot => {
112 if let Some(backup) = &self.snapshots.pop() {
113 sketch.copy_from(&backup.as_sketch());
114 }
115 }
116 Command::PenCommand(pen_id, cmd) => {
117 self.handle_pen_command(pen_id, cmd);
118 }
119 }
120 }
121
122 let mut painter = Painter::new(sketch);
123
124 painter.set_color(self.pens.select(self.pen_id).color);
125 draw_current_color_icon(&mut painter);
126
127 for (_, pen) in self.pens.0.iter_mut() {
128 painter.set_color(pen.color);
129 painter.set_size(pen.size);
130
131 if pen.cursor.pressed && pen.prev_cursor.pressed {
132 painter.draw_line(pen.prev_cursor.pos, pen.cursor.pos);
133 }
134
135 pen.prev_cursor = pen.cursor;
136 }
137 }
138
139 pub fn clear_all(&mut self) {
140 self.send_command_chained(Command::ClearAll);
141 }
142
143 pub fn resume_last_snapshot(&mut self) {
144 self.send_command_chained(Command::ResumeLastSnapshot);
145 }
146
147 pub fn take_snapshot(&mut self) {
148 self.send_command_chained(Command::TakeSnapshot);
149 }
150
151 pub fn move_cursor(&mut self, x: isize, y: isize) {
152 self.send_pen_command(PenCommand::MoveCursor(CursorPos { x, y }));
153 }
154
155 pub fn press(&mut self) {
156 self.send_pen_command(PenCommand::Pressed);
157 }
158
159 pub fn release(&mut self) {
160 self.send_pen_command(PenCommand::Released);
161 }
162
163 pub fn change_color(&mut self) {
164 if let Some(color) = self.palette.next() {
165 self.send_pen_command(PenCommand::ChangeColor(color));
166 }
167 }
168
169 pub fn grow_pen(&mut self) {
170 let mut size = self.my_pen().size;
171 size.grow();
172 self.send_pen_command(PenCommand::ChangeSize(size));
173 }
174
175 pub fn shrink_pen(&mut self) {
176 let mut size = self.my_pen().size;
177 size.shrink();
178 self.send_pen_command(PenCommand::ChangeSize(size));
179 }
180
181 pub fn needs_update(&self) -> bool {
182 !self.commands.is_empty()
183 }
184
185 pub fn connect_command_sender(&mut self, chained: Box<dyn FnMut(Command)>) {
186 self.chained_command_sender = Some(chained);
187 }
188
189 fn send_command_chained(&mut self, cmd: Command) {
190 self.commands.push_back(cmd);
191
192 if let Some(chained) = self.chained_command_sender.as_mut() {
193 chained(cmd);
194 }
195 }
196
197 fn send_pen_command(&mut self, cmd: PenCommand) {
198 let cmd = Command::PenCommand(self.pen_id, cmd);
199 self.send_command_chained(cmd);
200 }
201
202 pub fn force_release(&mut self) {
203 self.send_pen_command(PenCommand::Released);
204 }
205
206 fn handle_pen_command(&mut self, pen_id: PenId, cmd: PenCommand) {
207 let pen = self.pens.select(pen_id);
208 match cmd {
209 PenCommand::ChangeColor(color) => {
210 pen.color = color;
211 }
212 PenCommand::ChangeSize(size) => {
213 pen.size = size;
214 }
215 PenCommand::MoveCursor(pos) => {
216 pen.cursor.pos = pos;
217 }
218 PenCommand::Pressed => {
219 pen.cursor.pressed = true;
220 }
221 PenCommand::Released => {
222 pen.cursor.pressed = false;
223 }
224 }
225 }
226
227 fn my_pen(&mut self) -> &mut Pen {
228 self.pens.select(self.pen_id)
229 }
230}
231
232impl CommandSender for App {
233 fn send_command(&mut self, cmd: Command) {
234 self.commands.push_back(cmd);
235 }
236}
237
238fn draw_current_color_icon(painter: &mut Painter) {
239 const SQUARE_SIZE: isize = 10;
240
241 for x in 0..SQUARE_SIZE {
242 for y in 0..(SQUARE_SIZE - x) {
243 painter.draw_pixel(CursorPos { x, y });
244 }
245 }
246}
247
248#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
249pub struct PenSize(u32);
250
251impl Default for PenSize {
252 fn default() -> Self {
253 Self(1)
254 }
255}
256
257impl PenSize {
258 fn grow(&mut self) {
259 self.0 = (self.0 * 2).clamp(1, 32);
260 }
261 fn shrink(&mut self) {
262 self.0 = (self.0 / 2).clamp(1, 32);
263 }
264}