1use serde::{Deserialize, Serialize};
2
3use crate::canvas::{Canvas, Pos};
4use crate::color::RgbColor;
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
7pub enum CanvasOp {
8 PaintCell {
9 pos: Pos,
10 ch: char,
11 fg: RgbColor,
12 },
13 ClearCell {
14 pos: Pos,
15 },
16 PaintRegion {
17 cells: Vec<CellWrite>,
18 },
19 ShiftRow {
20 y: usize,
21 kind: RowShift,
22 },
23 ShiftCol {
24 x: usize,
25 kind: ColShift,
26 },
27 Replace {
32 canvas: Canvas,
33 },
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37pub enum CellWrite {
38 Paint { pos: Pos, ch: char, fg: RgbColor },
39 Clear { pos: Pos },
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
43pub enum RowShift {
44 PushLeft { to_x: usize },
45 PushRight { from_x: usize },
46 PullFromLeft { to_x: usize },
47 PullFromRight { from_x: usize },
48}
49
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51pub enum ColShift {
52 PushUp { to_y: usize },
53 PushDown { from_y: usize },
54 PullFromUp { to_y: usize },
55 PullFromDown { from_y: usize },
56}
57
58impl Canvas {
59 pub fn apply(&mut self, op: &CanvasOp) {
60 match op {
61 CanvasOp::PaintCell { pos, ch, fg } => {
62 let _ = self.put_glyph_colored(*pos, *ch, *fg);
63 }
64 CanvasOp::ClearCell { pos } => self.clear_cell(*pos),
65 CanvasOp::PaintRegion { cells } => {
66 for write in cells {
67 match write {
68 CellWrite::Paint { pos, ch, fg } => {
69 let _ = self.put_glyph_colored(*pos, *ch, *fg);
70 }
71 CellWrite::Clear { pos } => self.clear_cell(*pos),
72 }
73 }
74 }
75 CanvasOp::ShiftRow { y, kind } => match kind {
76 RowShift::PushLeft { to_x } => self.push_left(*y, *to_x),
77 RowShift::PushRight { from_x } => self.push_right(*y, *from_x),
78 RowShift::PullFromLeft { to_x } => self.pull_from_left(*y, *to_x),
79 RowShift::PullFromRight { from_x } => self.pull_from_right(*y, *from_x),
80 },
81 CanvasOp::ShiftCol { x, kind } => match kind {
82 ColShift::PushUp { to_y } => self.push_up(*x, *to_y),
83 ColShift::PushDown { from_y } => self.push_down(*x, *from_y),
84 ColShift::PullFromUp { to_y } => self.pull_from_up(*x, *to_y),
85 ColShift::PullFromDown { from_y } => self.pull_from_down(*x, *from_y),
86 },
87 CanvasOp::Replace { canvas } => *self = canvas.clone(),
88 }
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95
96 fn red() -> RgbColor {
97 RgbColor::new(255, 0, 0)
98 }
99
100 #[test]
101 fn paint_cell_op_writes_colored_glyph() {
102 let mut canvas = Canvas::with_size(8, 4);
103 canvas.apply(&CanvasOp::PaintCell {
104 pos: Pos { x: 2, y: 1 },
105 ch: 'A',
106 fg: red(),
107 });
108 assert_eq!(canvas.get(Pos { x: 2, y: 1 }), 'A');
109 assert_eq!(canvas.fg(Pos { x: 2, y: 1 }), Some(red()));
110 }
111
112 #[test]
113 fn paint_region_applies_paint_and_clear_entries() {
114 let mut canvas = Canvas::with_size(8, 4);
115 canvas.set_colored(Pos { x: 0, y: 0 }, 'Q', red());
116 canvas.apply(&CanvasOp::PaintRegion {
117 cells: vec![
118 CellWrite::Clear {
119 pos: Pos { x: 0, y: 0 },
120 },
121 CellWrite::Paint {
122 pos: Pos { x: 1, y: 0 },
123 ch: 'Z',
124 fg: red(),
125 },
126 ],
127 });
128 assert_eq!(canvas.get(Pos { x: 0, y: 0 }), ' ');
129 assert_eq!(canvas.get(Pos { x: 1, y: 0 }), 'Z');
130 }
131
132 #[test]
133 fn shift_row_dispatches_to_push_left() {
134 let mut canvas = Canvas::with_size(8, 4);
135 canvas.set(Pos { x: 0, y: 0 }, 'A');
136 canvas.set(Pos { x: 1, y: 0 }, 'B');
137 canvas.set(Pos { x: 2, y: 0 }, 'C');
138 canvas.apply(&CanvasOp::ShiftRow {
139 y: 0,
140 kind: RowShift::PushLeft { to_x: 1 },
141 });
142 assert_eq!(canvas.get(Pos { x: 0, y: 0 }), 'B');
143 assert_eq!(canvas.get(Pos { x: 1, y: 0 }), ' ');
144 assert_eq!(canvas.get(Pos { x: 2, y: 0 }), 'C');
145 }
146
147 #[test]
148 fn canvas_op_serde_roundtrip() {
149 let op = CanvasOp::PaintRegion {
150 cells: vec![CellWrite::Paint {
151 pos: Pos { x: 1, y: 2 },
152 ch: '🌱',
153 fg: red(),
154 }],
155 };
156 let j = serde_json::to_string(&op).unwrap();
157 let back: CanvasOp = serde_json::from_str(&j).unwrap();
158 assert_eq!(op, back);
159 }
160}