select_imp.rs - icy_draw - icy_draw is the successor to mystic draw. fork / mirror
(HTM) git clone https://git.drkhsh.at/icy_draw.git
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
select_imp.rs (14453B)
---
1 use eframe::egui;
2 use i18n_embed_fl::fl;
3 use icy_engine::{editor::AtomicUndoGuard, AddType, Rectangle};
4 use icy_engine_egui::TerminalCalc;
5
6 use crate::{to_message, AnsiEditor, Message};
7
8 use super::{Event, Position, Tool};
9
10 #[derive(Default)]
11 enum SelectionDrag {
12 #[default]
13 None,
14 Move,
15 Left,
16 Right,
17 Top,
18 Bottom,
19
20 TopLeft,
21 TopRight,
22 BottomLeft,
23 BottomRight,
24 }
25
26 #[derive(Default, PartialEq, Copy, Clone)]
27 enum SelectionMode {
28 #[default]
29 Normal,
30 Character,
31 Attribute,
32 Foreground,
33 Background,
34 }
35 enum SelectionModifier {
36 Replace,
37 Add,
38 Remove,
39 }
40 impl SelectionModifier {
41 fn get_response(&self, ch: bool) -> Option<bool> {
42 match self {
43 SelectionModifier::Replace => Some(ch),
44 SelectionModifier::Add => {
45 if ch {
46 Some(true)
47 } else {
48 None
49 }
50 }
51 SelectionModifier::Remove => {
52 if ch {
53 Some(false)
54 } else {
55 None
56 }
57 }
58 }
59 }
60 }
61
62 #[derive(Default)]
63 pub struct SelectTool {
64 start_selection: Rectangle,
65 selection_drag: SelectionDrag,
66 mode: SelectionMode,
67 undo_op: Option<AtomicUndoGuard>,
68 }
69
70 impl Tool for SelectTool {
71 fn get_icon(&self) -> &egui::Image<'static> {
72 &super::icons::SELECT_SVG
73 }
74
75 fn tool_name(&self) -> String {
76 fl!(crate::LANGUAGE_LOADER, "tool-select_name")
77 }
78
79 fn tooltip(&self) -> String {
80 fl!(crate::LANGUAGE_LOADER, "tool-select_tooltip")
81 }
82
83 fn use_caret(&self, _editor: &AnsiEditor) -> bool {
84 false
85 }
86
87 fn show_ui(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui, _editor_opt: Option<&mut AnsiEditor>) -> Option<Message> {
88 ui.label(fl!(crate::LANGUAGE_LOADER, "tool-select-label"));
89 ui.radio_value(&mut self.mode, SelectionMode::Normal, fl!(crate::LANGUAGE_LOADER, "tool-select-normal"));
90 ui.radio_value(&mut self.mode, SelectionMode::Character, fl!(crate::LANGUAGE_LOADER, "tool-select-character"));
91 ui.radio_value(&mut self.mode, SelectionMode::Attribute, fl!(crate::LANGUAGE_LOADER, "tool-select-attribute"));
92 ui.radio_value(&mut self.mode, SelectionMode::Foreground, fl!(crate::LANGUAGE_LOADER, "tool-select-foreground"));
93
94 ui.radio_value(&mut self.mode, SelectionMode::Background, fl!(crate::LANGUAGE_LOADER, "tool-select-background"));
95 ui.add_space(8.0);
96 ui.vertical_centered(|ui| {
97 ui.small(fl!(crate::LANGUAGE_LOADER, "tool-select-description"));
98 });
99
100 None
101 }
102
103 fn handle_click(&mut self, editor: &mut AnsiEditor, button: i32, pos: Position, cur_abs: Position, response: &egui::Response) -> Option<Message> {
104 let cur_ch = editor.get_char_from_cur_layer(pos);
105
106 let selection_mode = if response.ctx.input(|i| i.modifiers.shift_only()) {
107 SelectionModifier::Add
108 } else if response.ctx.input(|i| i.modifiers.command_only()) {
109 SelectionModifier::Remove
110 } else {
111 SelectionModifier::Replace
112 };
113 match self.mode {
114 SelectionMode::Normal => {
115 if button == 1 && !is_inside_selection(editor, cur_abs) {
116 let lock = &mut editor.buffer_view.lock();
117 let _ = lock.get_edit_state_mut().add_selection_to_mask();
118 let _ = lock.get_edit_state_mut().deselect();
119 }
120 }
121 SelectionMode::Character => editor
122 .buffer_view
123 .lock()
124 .get_edit_state_mut()
125 .enumerate_selections(|_, ch, _| selection_mode.get_response(ch.ch == cur_ch.ch)),
126 SelectionMode::Attribute => editor
127 .buffer_view
128 .lock()
129 .get_edit_state_mut()
130 .enumerate_selections(|_, ch, _| selection_mode.get_response(ch.attribute == cur_ch.attribute)),
131 SelectionMode::Foreground => editor
132 .buffer_view
133 .lock()
134 .get_edit_state_mut()
135 .enumerate_selections(|_, ch, _| selection_mode.get_response(ch.attribute.get_foreground() == cur_ch.attribute.get_foreground())),
136 SelectionMode::Background => editor
137 .buffer_view
138 .lock()
139 .get_edit_state_mut()
140 .enumerate_selections(|_, ch, _| selection_mode.get_response(ch.attribute.get_background() == cur_ch.attribute.get_background())),
141 }
142 None
143 }
144
145 fn handle_drag_begin(&mut self, editor: &mut AnsiEditor, response: &egui::Response) -> Event {
146 self.undo_op = Some(editor.begin_atomic_undo(fl!(crate::LANGUAGE_LOADER, "undo-select")));
147 if self.mode != SelectionMode::Normal {
148 return Event::None;
149 }
150
151 self.selection_drag = get_selection_drag(editor, editor.drag_pos.start_abs);
152 if !matches!(self.selection_drag, SelectionDrag::None) {
153 if let Some(selection) = editor.buffer_view.lock().get_selection() {
154 self.start_selection = selection.as_rectangle();
155 }
156 } else if !response.ctx.input(|i| i.modifiers.shift_only() || i.modifiers.command_only()) {
157 let _ = editor.buffer_view.lock().get_edit_state_mut().clear_selection();
158 }
159 Event::None
160 }
161
162 fn handle_drag(&mut self, _ui: &egui::Ui, response: egui::Response, editor: &mut AnsiEditor, _calc: &TerminalCalc) -> egui::Response {
163 if self.mode != SelectionMode::Normal {
164 return response;
165 }
166 let mut rect = if let Some(selection) = editor.buffer_view.lock().get_selection() {
167 selection.as_rectangle()
168 } else {
169 Rectangle::from_coords(0, 0, 0, 0)
170 };
171
172 match self.selection_drag {
173 SelectionDrag::Move => {
174 rect.start = self.start_selection.top_left() - editor.drag_pos.start_abs + editor.drag_pos.cur_abs;
175 editor.buffer_view.lock().set_selection(rect);
176 }
177 SelectionDrag::Left => {
178 self.move_left(editor, &mut rect);
179 editor.buffer_view.lock().set_selection(rect);
180 }
181 SelectionDrag::Right => {
182 self.move_right(editor, &mut rect);
183 editor.buffer_view.lock().set_selection(rect);
184 }
185 SelectionDrag::Top => {
186 self.move_top(editor, &mut rect);
187 editor.buffer_view.lock().set_selection(rect);
188 }
189 SelectionDrag::Bottom => {
190 self.move_bottom(editor, &mut rect);
191 editor.buffer_view.lock().set_selection(rect);
192 }
193 SelectionDrag::TopLeft => {
194 self.move_left(editor, &mut rect);
195 self.move_top(editor, &mut rect);
196 editor.buffer_view.lock().set_selection(rect);
197 }
198 SelectionDrag::TopRight => {
199 self.move_right(editor, &mut rect);
200 self.move_top(editor, &mut rect);
201 editor.buffer_view.lock().set_selection(rect);
202 }
203 SelectionDrag::BottomLeft => {
204 self.move_left(editor, &mut rect);
205 self.move_bottom(editor, &mut rect);
206 editor.buffer_view.lock().set_selection(rect);
207 }
208 SelectionDrag::BottomRight => {
209 self.move_right(editor, &mut rect);
210 self.move_bottom(editor, &mut rect);
211 editor.buffer_view.lock().set_selection(rect);
212 }
213
214 SelectionDrag::None => {
215 if editor.drag_pos.start == editor.drag_pos.cur {
216 let _ = editor.buffer_view.lock().get_edit_state_mut().deselect();
217 } else {
218 editor.buffer_view.lock().set_selection(Rectangle::from(
219 editor.drag_pos.start_abs.x.min(editor.drag_pos.cur_abs.x),
220 editor.drag_pos.start_abs.y.min(editor.drag_pos.cur_abs.y),
221 (editor.drag_pos.cur_abs.x - editor.drag_pos.start_abs.x).abs(),
222 (editor.drag_pos.cur_abs.y - editor.drag_pos.start_abs.y).abs(),
223 ));
224 }
225 }
226 }
227
228 let lock = &mut editor.buffer_view.lock();
229 if let Some(mut selection) = lock.get_selection() {
230 if response.ctx.input(|i| i.modifiers.command_only()) {
231 selection.add_type = AddType::Subtract;
232 }
233 if response.ctx.input(|i| i.modifiers.shift_only()) {
234 selection.add_type = AddType::Add;
235 }
236 lock.set_selection(selection);
237 }
238 response
239 }
240
241 fn handle_hover(&mut self, ui: &egui::Ui, response: egui::Response, editor: &mut AnsiEditor, _cur: Position, cur_abs: Position) -> egui::Response {
242 if self.mode != SelectionMode::Normal {
243 return response.on_hover_cursor(egui::CursorIcon::Crosshair);
244 }
245
246 match get_selection_drag(editor, cur_abs) {
247 SelectionDrag::None => ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::Crosshair),
248 SelectionDrag::Move => ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::Move),
249 SelectionDrag::Left => ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeWest),
250 SelectionDrag::Right => ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeEast),
251 SelectionDrag::Top => ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeNorth),
252 SelectionDrag::Bottom => {
253 ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeSouth);
254 }
255 SelectionDrag::TopLeft => {
256 ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeNorthWest);
257 }
258 SelectionDrag::TopRight => {
259 ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeNorthEast);
260 }
261 SelectionDrag::BottomLeft => {
262 ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeSouthWest);
263 }
264 SelectionDrag::BottomRight => {
265 ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeSouthEast);
266 }
267 }
268 response
269 }
270
271 fn handle_drag_end(&mut self, editor: &mut AnsiEditor) -> Option<Message> {
272 if self.mode != SelectionMode::Normal {
273 self.undo_op = None;
274 return None;
275 }
276
277 if !matches!(self.selection_drag, SelectionDrag::None) {
278 self.selection_drag = SelectionDrag::None;
279 self.undo_op = None;
280 return None;
281 }
282
283 let mut cur = editor.drag_pos.cur;
284 if editor.drag_pos.start < cur {
285 cur += Position::new(1, 1);
286 }
287
288 if editor.drag_pos.start == cur {
289 let _ = editor.buffer_view.lock().get_edit_state_mut().deselect();
290 }
291
292 let lock = &mut editor.buffer_view.lock();
293 self.undo_op = None;
294
295 to_message(lock.get_edit_state_mut().add_selection_to_mask())
296 }
297 }
298
299 impl SelectTool {
300 fn move_left(&mut self, editor: &AnsiEditor, rect: &mut Rectangle) {
301 let delta = editor.drag_pos.start_abs.x - editor.drag_pos.cur_abs.x;
302 rect.start.x = self.start_selection.left() - delta;
303 rect.size.width = self.start_selection.get_width() + delta;
304
305 if rect.size.width < 0 {
306 rect.size.width = rect.start.x - self.start_selection.right();
307 rect.start.x = self.start_selection.right();
308 }
309 }
310
311 fn move_right(&mut self, editor: &AnsiEditor, rect: &mut Rectangle) {
312 rect.size.width = self.start_selection.get_width() - editor.drag_pos.start_abs.x + editor.drag_pos.cur_abs.x;
313 if rect.size.width < 0 {
314 rect.start.x = self.start_selection.left() + rect.size.width;
315 rect.size.width = self.start_selection.left() - rect.start.x;
316 }
317 }
318
319 fn move_top(&mut self, editor: &AnsiEditor, rect: &mut Rectangle) {
320 let delta = editor.drag_pos.start_abs.y - editor.drag_pos.cur_abs.y;
321 rect.start.y = self.start_selection.top() - delta;
322 rect.size.height = self.start_selection.get_height() + delta;
323
324 if rect.size.height < 0 {
325 rect.size.height = rect.start.y - self.start_selection.bottom();
326 rect.start.y = self.start_selection.bottom();
327 }
328 }
329
330 fn move_bottom(&mut self, editor: &AnsiEditor, rect: &mut Rectangle) {
331 rect.size.height = self.start_selection.get_height() - editor.drag_pos.start_abs.y + editor.drag_pos.cur_abs.y;
332 if rect.size.height < 0 {
333 rect.start.y = self.start_selection.top() + rect.size.height;
334 rect.size.height = self.start_selection.top() - rect.start.y;
335 }
336 }
337 }
338
339 fn is_inside_selection(editor: &AnsiEditor, cur_abs: Position) -> bool {
340 if let Some(selection) = editor.buffer_view.lock().get_selection() {
341 return selection.is_inside(cur_abs);
342 }
343 false
344 }
345
346 fn get_selection_drag(editor: &AnsiEditor, cur_abs: Position) -> SelectionDrag {
347 if let Some(selection) = editor.buffer_view.lock().get_selection() {
348 let rect = selection.as_rectangle();
349
350 if rect.is_inside(cur_abs) {
351 let left = cur_abs.x - rect.left() < 2;
352 let top = cur_abs.y - rect.top() < 2;
353 let right = rect.right() - cur_abs.x < 2;
354 let bottom = rect.bottom() - cur_abs.y < 2;
355
356 if left && top {
357 return SelectionDrag::TopLeft;
358 }
359
360 if right && top {
361 return SelectionDrag::TopRight;
362 }
363 if left && bottom {
364 return SelectionDrag::BottomLeft;
365 }
366
367 if right && bottom {
368 return SelectionDrag::BottomRight;
369 }
370
371 if left {
372 return SelectionDrag::Left;
373 }
374 if right {
375 return SelectionDrag::Right;
376 }
377
378 if top {
379 return SelectionDrag::Top;
380 }
381 if bottom {
382 return SelectionDrag::Bottom;
383 }
384
385 return SelectionDrag::Move;
386 }
387 }
388 SelectionDrag::None
389 }