click_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
---
click_imp.rs (19570B)
---
1 use std::sync::Arc;
2
3 use eframe::egui;
4 use egui::mutex::Mutex;
5 use i18n_embed_fl::fl;
6 use icy_engine::{editor::AtomicUndoGuard, AddType, Rectangle, TextPane};
7 use icy_engine_egui::TerminalCalc;
8
9 use crate::{model::MKey, AnsiEditor, CharTableToolWindow, Document, Message};
10
11 use super::{Event, MModifiers, Position, Tool};
12
13 #[derive(Default)]
14 enum SelectionDrag {
15 #[default]
16 None,
17 Move,
18 Left,
19 Right,
20 Top,
21 Bottom,
22
23 TopLeft,
24 TopRight,
25 BottomLeft,
26 BottomRight,
27 }
28
29 #[derive(Default)]
30 pub struct ClickTool {
31 start_selection: Rectangle,
32 selection_drag: SelectionDrag,
33 undo_op: Option<AtomicUndoGuard>,
34 char_table: Option<CharTableToolWindow>,
35 }
36
37 pub const VALID_OUTLINE_CHARS: &str = "ABCDEFGHIJKLMNO@&\u{F7} ";
38
39 impl Tool for ClickTool {
40 fn get_icon(&self) -> &'static egui::Image<'static> {
41 &super::icons::TEXT_SVG
42 }
43
44 fn tool_name(&self) -> String {
45 fl!(crate::LANGUAGE_LOADER, "tool-click_name")
46 }
47
48 fn tooltip(&self) -> String {
49 fl!(crate::LANGUAGE_LOADER, "tool-click_tooltip")
50 }
51
52 fn use_caret(&self, editor: &AnsiEditor) -> bool {
53 let is_selected = editor.buffer_view.lock().get_edit_state().is_something_selected();
54 !is_selected
55 }
56
57 fn show_ui(&mut self, ctx: &egui::Context, ui: &mut egui::Ui, editor_opt: Option<&mut AnsiEditor>) -> Option<Message> {
58 if self.char_table.is_none() {
59 self.char_table = Some(CharTableToolWindow::new(ctx, 16));
60 }
61 let mut msg = None;
62 if let Some(editor) = editor_opt {
63 editor.color_mode.show_ui(ui);
64
65 ui.vertical(|ui| {
66 ui.set_height(16.0 * 256.0 * 2.0);
67 msg = self.char_table.as_mut().unwrap().show_char_table(ui, editor);
68 });
69 }
70 msg
71 }
72
73 fn show_doc_ui(&mut self, ctx: &egui::Context, ui: &mut egui::Ui, doc: Arc<Mutex<Box<dyn Document>>>) -> Option<Message> {
74 if !doc.lock().can_paste_char() {
75 return None;
76 }
77 if self.char_table.is_none() {
78 self.char_table = Some(CharTableToolWindow::new(ctx, 16));
79 }
80 ui.vertical(|ui| {
81 ui.set_height(16.0 * 256.0 * 2.0);
82 let ch: Option<char> = self.char_table.as_mut().unwrap().show_plain_char_table(ui);
83
84 if let Some(ch) = ch {
85 doc.lock().paste_char(ui, ch);
86 }
87 });
88
89 None
90 }
91
92 fn handle_click(&mut self, editor: &mut AnsiEditor, button: i32, pos: Position, cur_abs: Position, _response: &egui::Response) -> Option<Message> {
93 if button == 1 && !is_inside_selection(editor, cur_abs) {
94 editor.set_caret_position(pos);
95 editor.buffer_view.lock().clear_selection();
96 }
97 None
98 }
99
100 fn handle_drag_begin(&mut self, editor: &mut AnsiEditor, _response: &egui::Response) -> Event {
101 self.selection_drag = get_selection_drag(editor, editor.drag_pos.start_abs);
102
103 if !matches!(self.selection_drag, SelectionDrag::None) {
104 if let Some(selection) = editor.buffer_view.lock().get_selection() {
105 self.start_selection = selection.as_rectangle();
106 }
107 }
108 self.undo_op = Some(editor.begin_atomic_undo(fl!(crate::LANGUAGE_LOADER, "undo-select")));
109
110 Event::None
111 }
112 fn handle_drag(&mut self, _ui: &egui::Ui, response: egui::Response, editor: &mut AnsiEditor, _calc: &TerminalCalc) -> egui::Response {
113 let mut rect = if let Some(selection) = editor.buffer_view.lock().get_selection() {
114 selection.as_rectangle()
115 } else {
116 Rectangle::from_coords(0, 0, 0, 0)
117 };
118
119 match self.selection_drag {
120 SelectionDrag::Move => {
121 rect.start = self.start_selection.top_left() - editor.drag_pos.start_abs + editor.drag_pos.cur_abs;
122 editor.buffer_view.lock().set_selection(rect);
123 }
124 SelectionDrag::Left => {
125 self.move_left(editor, &mut rect);
126 editor.buffer_view.lock().set_selection(rect);
127 }
128 SelectionDrag::Right => {
129 self.move_right(editor, &mut rect);
130 editor.buffer_view.lock().set_selection(rect);
131 }
132 SelectionDrag::Top => {
133 self.move_top(editor, &mut rect);
134 editor.buffer_view.lock().set_selection(rect);
135 }
136 SelectionDrag::Bottom => {
137 self.move_bottom(editor, &mut rect);
138 editor.buffer_view.lock().set_selection(rect);
139 }
140 SelectionDrag::TopLeft => {
141 self.move_left(editor, &mut rect);
142 self.move_top(editor, &mut rect);
143 editor.buffer_view.lock().set_selection(rect);
144 }
145 SelectionDrag::TopRight => {
146 self.move_right(editor, &mut rect);
147 self.move_top(editor, &mut rect);
148 editor.buffer_view.lock().set_selection(rect);
149 }
150 SelectionDrag::BottomLeft => {
151 self.move_left(editor, &mut rect);
152 self.move_bottom(editor, &mut rect);
153 editor.buffer_view.lock().set_selection(rect);
154 }
155 SelectionDrag::BottomRight => {
156 self.move_right(editor, &mut rect);
157 self.move_bottom(editor, &mut rect);
158 editor.buffer_view.lock().set_selection(rect);
159 }
160
161 SelectionDrag::None => {
162 if editor.drag_pos.start == editor.drag_pos.cur {
163 editor.buffer_view.lock().clear_selection();
164 } else {
165 editor.buffer_view.lock().set_selection(Rectangle::from(
166 editor.drag_pos.start_abs.x.min(editor.drag_pos.cur_abs.x),
167 editor.drag_pos.start_abs.y.min(editor.drag_pos.cur_abs.y),
168 (editor.drag_pos.cur_abs.x - editor.drag_pos.start_abs.x).abs(),
169 (editor.drag_pos.cur_abs.y - editor.drag_pos.start_abs.y).abs(),
170 ));
171 }
172 }
173 }
174
175 let lock = &mut editor.buffer_view.lock();
176 if let Some(mut selection) = lock.get_selection() {
177 if response.ctx.input(|i| i.modifiers.command_only()) {
178 selection.add_type = AddType::Subtract;
179 }
180 if response.ctx.input(|i| i.modifiers.shift_only()) {
181 selection.add_type = AddType::Add;
182 }
183 lock.set_selection(selection);
184 }
185
186 response
187 }
188
189 fn handle_hover(&mut self, ui: &egui::Ui, response: egui::Response, editor: &mut AnsiEditor, _cur: Position, cur_abs: Position) -> egui::Response {
190 match get_selection_drag(editor, cur_abs) {
191 SelectionDrag::None => ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::Text),
192 SelectionDrag::Move => ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::Move),
193 SelectionDrag::Left => ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeWest),
194 SelectionDrag::Right => ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeEast),
195 SelectionDrag::Top => ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeNorth),
196 SelectionDrag::Bottom => {
197 ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeSouth);
198 }
199 SelectionDrag::TopLeft => {
200 ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeNorthWest);
201 }
202 SelectionDrag::TopRight => {
203 ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeNorthEast);
204 }
205 SelectionDrag::BottomLeft => {
206 ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeSouthWest);
207 }
208 SelectionDrag::BottomRight => {
209 ui.output_mut(|o| o.cursor_icon = egui::CursorIcon::ResizeSouthEast);
210 }
211 }
212 response
213 }
214
215 fn handle_drag_end(&mut self, editor: &mut AnsiEditor) -> Option<Message> {
216 if !matches!(self.selection_drag, SelectionDrag::None) {
217 self.selection_drag = SelectionDrag::None;
218 self.undo_op = None;
219 return None;
220 }
221
222 let mut cur = editor.drag_pos.cur;
223 if editor.drag_pos.start < cur {
224 cur += Position::new(1, 1);
225 }
226
227 if editor.drag_pos.start == cur {
228 editor.buffer_view.lock().clear_selection();
229 }
230 self.undo_op = None;
231
232 None
233 }
234
235 fn handle_key(&mut self, editor: &mut AnsiEditor, key: MKey, modifier: MModifiers) -> Event {
236 // TODO Keys:
237 // ctrl+pgup - upper left corner
238 // ctrl+pgdn - lower left corner
239 let pos = editor.buffer_view.lock().get_caret().get_position();
240 match key {
241 MKey::Down => {
242 if matches!(modifier, MModifiers::None) {
243 editor.set_caret(pos.x, pos.y + 1);
244 }
245 }
246 MKey::Up => {
247 if matches!(modifier, MModifiers::None) {
248 editor.set_caret(pos.x, pos.y - 1);
249 }
250 }
251 MKey::Left => {
252 if matches!(modifier, MModifiers::None) {
253 editor.set_caret(pos.x - 1, pos.y);
254 }
255 }
256 MKey::Right => {
257 if matches!(modifier, MModifiers::None) {
258 editor.set_caret(pos.x + 1, pos.y);
259 }
260 }
261 MKey::PageDown => {
262 let height = editor.buffer_view.lock().calc.terminal_rect.height();
263 let char_height = editor.buffer_view.lock().calc.char_size.y;
264 let pg_size = (height / char_height) as i32;
265 editor.set_caret(pos.x, pos.y + pg_size);
266 }
267 MKey::PageUp => {
268 let height = editor.buffer_view.lock().calc.terminal_rect.height();
269 let char_height = editor.buffer_view.lock().calc.char_size.y;
270 let pg_size = (height / char_height) as i32;
271 editor.set_caret(pos.x, pos.y - pg_size);
272 }
273
274 MKey::Escape => {
275 editor.buffer_view.lock().clear_selection();
276 }
277
278 MKey::Tab => {
279 let tab_size = 8;
280 if let MModifiers::Shift = modifier {
281 let tabs = ((pos.x / tab_size) - 1).max(0);
282 let next_tab = tabs * tab_size;
283 editor.set_caret(next_tab, pos.y);
284 } else {
285 let tabs = 1 + pos.x / tab_size;
286 let next_tab = (editor.buffer_view.lock().get_buffer().get_width() - 1).min(tabs * tab_size);
287 editor.set_caret(next_tab, pos.y);
288 }
289 }
290
291 MKey::Return => {
292 editor.set_caret(0, pos.y + 1);
293 }
294 MKey::Insert => {
295 let insert_mode = editor.buffer_view.lock().get_caret().insert_mode;
296 editor.buffer_view.lock().get_caret_mut().insert_mode = !insert_mode;
297 }
298 MKey::Backspace => {
299 editor.backspace();
300 }
301
302 MKey::Delete => {
303 if editor.buffer_view.lock().get_selection().is_none() {
304 editor.delete();
305 }
306 }
307
308 MKey::Home => {
309 let mut pos = editor.get_caret_position();
310 pos.x = 0;
311
312 if let MModifiers::Control = modifier {
313 pos.y = 0;
314 }
315 editor.set_caret(pos.x, pos.y);
316 }
317 MKey::End => {
318 let mut pos = editor.get_caret_position();
319 pos.x = i32::MAX;
320 if let MModifiers::Control = modifier {
321 pos.y = i32::MAX;
322 }
323 editor.set_caret(pos.x, pos.y);
324 }
325
326 MKey::Character(ch) => {
327 let typed_char = unsafe { char::from_u32_unchecked(ch as u32) };
328 if editor.outline_font_mode {
329 let typed_char = typed_char.to_ascii_uppercase();
330 if VALID_OUTLINE_CHARS.contains(typed_char) {
331 editor.type_key(typed_char);
332 } else if let '1'..='8' = typed_char {
333 editor.type_key(VALID_OUTLINE_CHARS.chars().nth(10 + typed_char as usize - b'1' as usize).unwrap());
334 }
335 } else {
336 editor.type_key(typed_char);
337 }
338 }
339
340 MKey::F1 => {
341 if matches!(modifier, MModifiers::None) {
342 if editor.outline_font_mode {
343 editor.type_key(VALID_OUTLINE_CHARS.chars().next().unwrap());
344 } else {
345 editor.type_char_set_key(0);
346 }
347 }
348 }
349 MKey::F2 => {
350 if matches!(modifier, MModifiers::None) {
351 if editor.outline_font_mode {
352 editor.type_key(VALID_OUTLINE_CHARS.chars().nth(1).unwrap());
353 } else {
354 editor.type_char_set_key(1);
355 }
356 }
357 }
358 MKey::F3 => {
359 if matches!(modifier, MModifiers::None) {
360 if editor.outline_font_mode {
361 editor.type_key(VALID_OUTLINE_CHARS.chars().nth(2).unwrap());
362 } else {
363 editor.type_char_set_key(2);
364 }
365 }
366 }
367 MKey::F4 => {
368 if matches!(modifier, MModifiers::None) {
369 if editor.outline_font_mode {
370 editor.type_key(VALID_OUTLINE_CHARS.chars().nth(3).unwrap());
371 } else {
372 editor.type_char_set_key(3);
373 }
374 }
375 }
376 MKey::F5 => {
377 if matches!(modifier, MModifiers::None) {
378 if editor.outline_font_mode {
379 editor.type_key(VALID_OUTLINE_CHARS.chars().nth(4).unwrap());
380 } else {
381 editor.type_char_set_key(4);
382 }
383 }
384 }
385 MKey::F6 => {
386 if matches!(modifier, MModifiers::None) {
387 if editor.outline_font_mode {
388 editor.type_key(VALID_OUTLINE_CHARS.chars().nth(5).unwrap());
389 } else {
390 editor.type_char_set_key(5);
391 }
392 }
393 }
394 MKey::F7 => {
395 if matches!(modifier, MModifiers::None) {
396 if editor.outline_font_mode {
397 editor.type_key(VALID_OUTLINE_CHARS.chars().nth(6).unwrap());
398 } else {
399 editor.type_char_set_key(6);
400 }
401 }
402 }
403 MKey::F8 => {
404 if matches!(modifier, MModifiers::None) {
405 if editor.outline_font_mode {
406 editor.type_key(VALID_OUTLINE_CHARS.chars().nth(7).unwrap());
407 } else {
408 editor.type_char_set_key(7);
409 }
410 }
411 }
412 MKey::F9 => {
413 if matches!(modifier, MModifiers::None) {
414 if editor.outline_font_mode {
415 editor.type_key(VALID_OUTLINE_CHARS.chars().nth(8).unwrap());
416 } else {
417 editor.type_char_set_key(8);
418 }
419 }
420 }
421 MKey::F10 => {
422 if matches!(modifier, MModifiers::None) {
423 if editor.outline_font_mode {
424 editor.type_key(VALID_OUTLINE_CHARS.chars().nth(9).unwrap());
425 } else {
426 editor.type_char_set_key(9);
427 }
428 }
429 }
430 _ => {}
431 }
432 Event::None
433 }
434 }
435
436 impl ClickTool {
437 fn move_left(&mut self, editor: &AnsiEditor, rect: &mut Rectangle) {
438 let delta = editor.drag_pos.start_abs.x - editor.drag_pos.cur_abs.x;
439 rect.start.x = self.start_selection.left() - delta;
440 rect.size.width = self.start_selection.get_width() + delta;
441
442 if rect.size.width < 0 {
443 rect.size.width = rect.start.x - self.start_selection.right();
444 rect.start.x = self.start_selection.right();
445 }
446 }
447
448 fn move_right(&mut self, editor: &AnsiEditor, rect: &mut Rectangle) {
449 rect.size.width = self.start_selection.get_width() - editor.drag_pos.start_abs.x + editor.drag_pos.cur_abs.x;
450 if rect.size.width < 0 {
451 rect.start.x = self.start_selection.left() + rect.size.width;
452 rect.size.width = self.start_selection.left() - rect.start.x;
453 }
454 }
455
456 fn move_top(&mut self, editor: &AnsiEditor, rect: &mut Rectangle) {
457 let delta = editor.drag_pos.start_abs.y - editor.drag_pos.cur_abs.y;
458 rect.start.y = self.start_selection.top() - delta;
459 rect.size.height = self.start_selection.get_height() + delta;
460
461 if rect.size.height < 0 {
462 rect.size.height = rect.start.y - self.start_selection.bottom();
463 rect.start.y = self.start_selection.bottom();
464 }
465 }
466
467 fn move_bottom(&mut self, editor: &AnsiEditor, rect: &mut Rectangle) {
468 rect.size.height = self.start_selection.get_height() - editor.drag_pos.start_abs.y + editor.drag_pos.cur_abs.y;
469 if rect.size.height < 0 {
470 rect.start.y = self.start_selection.top() + rect.size.height;
471 rect.size.height = self.start_selection.top() - rect.start.y;
472 }
473 }
474 }
475
476 fn is_inside_selection(editor: &AnsiEditor, cur_abs: Position) -> bool {
477 if let Some(selection) = editor.buffer_view.lock().get_selection() {
478 return selection.is_inside(cur_abs);
479 }
480 false
481 }
482
483 fn get_selection_drag(editor: &AnsiEditor, cur_abs: Position) -> SelectionDrag {
484 if let Some(selection) = editor.buffer_view.lock().get_selection() {
485 let rect = selection.as_rectangle();
486
487 if rect.is_inside(cur_abs) {
488 let left = cur_abs.x - rect.left() < 2;
489 let top = cur_abs.y - rect.top() < 2;
490 let right = rect.right() - cur_abs.x < 2;
491 let bottom = rect.bottom() - cur_abs.y < 2;
492
493 if left && top {
494 return SelectionDrag::TopLeft;
495 }
496
497 if right && top {
498 return SelectionDrag::TopRight;
499 }
500 if left && bottom {
501 return SelectionDrag::BottomLeft;
502 }
503
504 if right && bottom {
505 return SelectionDrag::BottomRight;
506 }
507
508 if left {
509 return SelectionDrag::Left;
510 }
511 if right {
512 return SelectionDrag::Right;
513 }
514
515 if top {
516 return SelectionDrag::Top;
517 }
518 if bottom {
519 return SelectionDrag::Bottom;
520 }
521
522 return SelectionDrag::Move;
523 }
524 }
525 SelectionDrag::None
526 }