1use std::collections::BTreeSet;
2
3use crate::{buffer::TextPosition, editor::Editor};
4
5#[derive(Debug, Clone)]
6pub enum Marker {
7 Stroke(StrokeMarker),
8 Line(LineMarker),
9 Rect(RectMarker),
10 FilledRect(FilledRectMarker),
11 Fill(FillMarker),
12}
13
14impl Marker {
15 pub fn new_stroke(editor: &Editor) -> Self {
16 Self::Stroke(StrokeMarker::new(editor))
17 }
18
19 pub fn new_line(editor: &Editor) -> Self {
20 Self::Line(LineMarker::new(editor))
21 }
22
23 pub fn new_rect(editor: &Editor) -> Self {
24 Self::Rect(RectMarker::new(editor))
25 }
26
27 pub fn new_fill(editor: &Editor) -> Self {
28 Self::Fill(FillMarker::new(editor))
29 }
30
31 pub fn new_filled_rect(editor: &Editor) -> Self {
32 Self::FilledRect(FilledRectMarker::new(editor))
33 }
34
35 pub fn name(&self) -> &'static str {
36 match self {
37 Marker::Stroke(_) => "MARK(STROKE)",
38 Marker::Line(_) => "MARK(LINE)",
39 Marker::Rect(_) => "MARK(RECT)",
40 Marker::FilledRect(_) => "MARK(FILLED_RECT)",
41 Marker::Fill(_) => "MARK(FILL)",
42 }
43 }
44
45 pub fn marked_positions(&self) -> Box<dyn '_ + Iterator<Item = TextPosition>> {
46 match self {
47 Marker::Stroke(m) => Box::new(m.positions.iter().copied()),
48 Marker::Line(m) => Box::new(m.marked_positions()),
49 Marker::Rect(m) => Box::new(m.marked_positions()),
50 Marker::FilledRect(m) => Box::new(m.marked_positions()),
51 Marker::Fill(m) => Box::new(m.filled_positions.iter().copied()),
52 }
53 }
54
55 pub fn handle_cursor_move(&mut self, editor: &Editor) {
56 match self {
57 Marker::Stroke(m) => m.handle_cursor_move(editor),
58 Marker::Line(m) => m.handle_cursor_move(editor),
59 Marker::Rect(m) => m.handle_cursor_move(editor),
60 Marker::FilledRect(m) => m.handle_cursor_move(editor),
61 Marker::Fill(m) => m.handle_cursor_move(editor),
62 }
63 }
64}
65
66#[derive(Debug, Clone)]
67pub struct StrokeMarker {
68 positions: BTreeSet<TextPosition>,
69}
70
71impl StrokeMarker {
72 fn new(editor: &Editor) -> Self {
73 Self {
74 positions: [editor.cursor].into_iter().collect(),
75 }
76 }
77
78 fn handle_cursor_move(&mut self, editor: &Editor) {
79 self.positions.insert(editor.cursor);
80 }
81}
82
83#[derive(Debug, Clone)]
84pub struct LineMarker {
85 start: TextPosition,
86 end: TextPosition,
87}
88
89impl LineMarker {
90 fn new(editor: &Editor) -> Self {
91 Self {
92 start: editor.cursor,
93 end: editor.cursor,
94 }
95 }
96
97 fn handle_cursor_move(&mut self, editor: &Editor) {
98 self.end = editor.cursor;
99 }
100
101 fn marked_positions(&self) -> impl Iterator<Item = TextPosition> + '_ {
102 let start = self.start;
103 let end = self.end;
104
105 let dx = end.col as i32 - start.col as i32;
107 let dy = end.row as i32 - start.row as i32;
108
109 let steps = std::cmp::max(dx.abs(), dy.abs()) as usize;
110
111 (0..=steps).map(move |i| {
112 if steps == 0 {
113 return start;
114 }
115
116 let t = i as f64 / steps as f64;
117 let col = start.col as f64 + t * dx as f64;
118 let row = start.row as f64 + t * dy as f64;
119
120 TextPosition {
121 row: row.round() as usize,
122 col: col.round() as usize,
123 }
124 })
125 }
126}
127
128#[derive(Debug, Clone)]
129pub struct RectMarker {
130 start: TextPosition,
131 end: TextPosition,
132}
133
134impl RectMarker {
135 fn new(editor: &Editor) -> Self {
136 Self {
137 start: editor.cursor,
138 end: editor.cursor,
139 }
140 }
141
142 fn handle_cursor_move(&mut self, editor: &Editor) {
143 self.end = editor.cursor;
144 }
145
146 fn marked_positions(&self) -> impl Iterator<Item = TextPosition> + '_ {
147 let start = self.start;
148 let end = self.end;
149
150 let min_row = std::cmp::min(start.row, end.row);
152 let max_row = std::cmp::max(start.row, end.row);
153 let min_col = std::cmp::min(start.col, end.col);
154 let max_col = std::cmp::max(start.col, end.col);
155
156 (min_row..=max_row).flat_map(move |row| {
158 (min_col..=max_col).filter_map(move |col| {
159 if row == min_row || row == max_row || col == min_col || col == max_col {
161 Some(TextPosition { row, col })
162 } else {
163 None
164 }
165 })
166 })
167 }
168}
169
170#[derive(Debug, Clone)]
171pub struct FilledRectMarker {
172 start: TextPosition,
173 end: TextPosition,
174}
175
176impl FilledRectMarker {
177 fn new(editor: &Editor) -> Self {
178 Self {
179 start: editor.cursor,
180 end: editor.cursor,
181 }
182 }
183
184 fn handle_cursor_move(&mut self, editor: &Editor) {
185 self.end = editor.cursor;
186 }
187
188 fn marked_positions(&self) -> impl Iterator<Item = TextPosition> + '_ {
189 let start = self.start;
190 let end = self.end;
191
192 let min_row = std::cmp::min(start.row, end.row);
194 let max_row = std::cmp::max(start.row, end.row);
195 let min_col = std::cmp::min(start.col, end.col);
196 let max_col = std::cmp::max(start.col, end.col);
197
198 (min_row..=max_row)
200 .flat_map(move |row| (min_col..=max_col).map(move |col| TextPosition { row, col }))
201 }
202}
203
204#[derive(Debug, Clone)]
205pub struct FillMarker {
206 position: TextPosition,
207 target_char: Option<char>,
208 filled_positions: BTreeSet<TextPosition>,
209}
210
211impl FillMarker {
212 fn new(editor: &Editor) -> Self {
213 let mut marker = Self {
214 position: editor.cursor,
215 target_char: None, filled_positions: BTreeSet::new(),
217 };
218 marker.update_filled_positions(editor);
219 marker
220 }
221
222 fn handle_cursor_move(&mut self, editor: &Editor) {
223 self.position = editor.cursor;
224 self.update_filled_positions(editor);
225 }
226
227 fn update_filled_positions(&mut self, editor: &Editor) {
228 self.filled_positions.clear();
229
230 let start_pos = self.position;
231
232 let target_char = editor.buffer.get_char_at(start_pos);
234
235 if self.target_char != target_char {
237 self.target_char = target_char;
238 }
239
240 if self.target_char.is_none() {
241 self.filled_positions.clear();
242 return;
243 }
244
245 self.flood_fill(editor, start_pos, self.target_char);
247 }
248
249 fn flood_fill(&mut self, editor: &Editor, start_pos: TextPosition, target_char: Option<char>) {
250 let mut stack = vec![start_pos];
251
252 while let Some(current_pos) = stack.pop() {
253 let current_char = editor.buffer.get_char_at(current_pos);
255
256 if current_char != target_char || self.filled_positions.contains(¤t_pos) {
258 continue;
259 }
260
261 self.filled_positions.insert(current_pos);
263
264 if current_pos.row > 0 {
267 stack.push(TextPosition {
268 row: current_pos.row - 1,
269 col: current_pos.col,
270 });
271 }
272 stack.push(TextPosition {
274 row: current_pos.row + 1,
275 col: current_pos.col,
276 });
277 if current_pos.col > 0 {
279 stack.push(TextPosition {
280 row: current_pos.row,
281 col: current_pos.col - 1, });
283 }
284 stack.push(TextPosition {
286 row: current_pos.row,
287 col: current_pos.col + 1, });
289 }
290 }
291}