lavagna_core/
lib.rs

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    /// This instance pen id
64    pen_id: PenId,
65    /// All collaborative pens, included the owned one
66    pens: Pens,
67    /// A queue of commands to be performed
68    commands: VecDeque<Command>,
69    /// Palette of colors
70    palette: ColorSelector,
71    /// A history of snapshots
72    snapshots: Vec<OwnedSketch>,
73    /// A component in charge of sending commands to collaborators
74    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}