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 }