rvlib/tools/
core.rs

1use tracing::{info, warn};
2
3use super::attributes;
4use crate::history::Record;
5use crate::result::trace_ok_err;
6use crate::tools_data::annotations::{ClipboardData, InstanceAnnotations};
7use crate::tools_data::attributes_data::{self, AttrVal};
8use crate::tools_data::{vis_from_lfoption, InstanceAnnotate, LabelInfo};
9use crate::util::Visibility;
10use crate::world::InstanceAnnoAccess;
11use crate::ShapeI;
12use crate::{
13    events::Events,
14    history::History,
15    world,
16    world::{MetaDataAccess, World},
17};
18use rvimage_domain::PtF;
19use std::mem;
20
21pub(super) fn make_track_changes_str(actor: &'static str) -> String {
22    format!("{actor}_TRACK_CHANGE")
23}
24pub(super) fn insert_attribute(
25    mut world: World,
26    name: &str,
27    value: AttrVal,
28    default_value: AttrVal,
29    filepath: Option<&str>,
30) -> World {
31    let mut old_attr_name = String::new();
32    let mut old_attr_val = AttrVal::Bool(false);
33
34    if let Ok(attr_data) = world::get_mut(&mut world, attributes::ACTOR_NAME, "Attr data missing") {
35        // does the new attribute already exist?
36        let populate_new_attr = attr_data.specifics.attributes().map(|a| {
37            a.attr_names()
38                .iter()
39                .any(|attr_name| attr_name.as_str() == name)
40        }) != Ok(true);
41
42        // set the attribute data to addtion
43        trace_ok_err(attr_data.specifics.attributes_mut().map(|d| {
44            let attr_options = attributes_data::Options {
45                is_export_triggered: false,
46                is_addition_triggered: populate_new_attr,
47                rename_src_idx: None,
48                is_update_triggered: false,
49                export_only_opened_folder: false,
50                removal_idx: None,
51            };
52            old_attr_name.clone_from(&d.new_attr_name);
53            old_attr_val.clone_from(&d.new_attr_val);
54            d.new_attr_name = name.to_string();
55            d.new_attr_val = default_value;
56            d.options = attr_options;
57        }));
58    }
59
60    // actually add the attribute
61    (world, _) = attributes::Attributes {}.events_tf(world, History::default(), &Events::default());
62
63    // insert the attribute's value to the attribute map of the current file
64    if let Ok(attr_data) = world::get_mut(&mut world, attributes::ACTOR_NAME, "Attr data missing") {
65        let attr_options = attributes_data::Options {
66            is_export_triggered: false,
67            is_addition_triggered: false,
68            rename_src_idx: None,
69            is_update_triggered: true,
70            export_only_opened_folder: false,
71            removal_idx: None,
72        };
73        trace_ok_err(attr_data.specifics.attributes_mut().map(|d| {
74            d.options = attr_options;
75            let attr_map = if let Some(filepath) = filepath {
76                d.attr_map_mut(filepath)
77            } else {
78                d.current_attr_map.as_mut()
79            };
80            if let Some(attr_map) = attr_map {
81                attr_map.insert(name.to_string(), value);
82            } else {
83                warn!("no attrmap found");
84            }
85        }));
86    }
87    (world, _) = attributes::Attributes {}.events_tf(world, History::default(), &Events::default());
88
89    if let Ok(attr_data) = world::get_mut(&mut world, attributes::ACTOR_NAME, "Attr data missing") {
90        // reset the state of the attribute data
91        trace_ok_err(attr_data.specifics.attributes_mut().map(|d| {
92            d.new_attr_name = old_attr_name;
93            d.new_attr_val = old_attr_val;
94        }));
95    }
96
97    world
98}
99
100pub(super) fn change_annos<T, DA, IA>(
101    world: &mut World,
102    f_change: impl FnOnce(&mut InstanceAnnotations<T>),
103) where
104    T: InstanceAnnotate,
105    DA: MetaDataAccess,
106    IA: InstanceAnnoAccess<T>,
107{
108    if let Some(annos) = IA::get_annos_mut(world) {
109        f_change(annos);
110    }
111    let track_changes_str = DA::get_track_changes_str(world);
112    if let Some(track_changes_str) = track_changes_str {
113        *world = insert_attribute(
114            mem::take(world),
115            track_changes_str,
116            AttrVal::Bool(true),
117            AttrVal::Bool(false),
118            None,
119        );
120    }
121}
122pub(super) fn check_trigger_redraw<DC>(mut world: World, name: &'static str) -> World
123where
124    DC: MetaDataAccess,
125{
126    let core_options = DC::get_core_options(&world).copied();
127    let is_redraw_triggered = core_options.map(|o| o.is_redraw_annos_triggered);
128    if is_redraw_triggered == Some(true) {
129        let visibility = vis_from_lfoption(
130            DC::get_label_info(&world),
131            core_options.map(|o| o.visible) == Some(true),
132        );
133        world.request_redraw_annotations(name, visibility);
134        let core_options_mut = DC::get_core_options_mut(&mut world);
135        if let Some(core_options_mut) = core_options_mut {
136            core_options_mut.is_redraw_annos_triggered = false;
137        }
138    }
139    world
140}
141
142pub(super) fn check_trigger_history_update<DC>(
143    mut world: World,
144    mut history: History,
145    name: &'static str,
146) -> (World, History)
147where
148    DC: MetaDataAccess,
149{
150    let core_options = DC::get_core_options_mut(&mut world).copied();
151    let is_history_update_triggered = core_options.map(|o| o.is_history_update_triggered);
152    if is_history_update_triggered == Some(true) {
153        let core_options_mut = DC::get_core_options_mut(&mut world);
154        if let Some(core_options_mut) = core_options_mut {
155            core_options_mut.is_history_update_triggered = false;
156        }
157        history.push(Record::new(world.clone(), name));
158    }
159    (world, history)
160}
161
162macro_rules! event_2_actionenum {
163    ($name:ident, $func:ident, $map_func:ident, $($key:ident),*) => {
164        #[derive(Debug, Clone, Copy)]
165        pub(super) enum $name {
166            None,
167            $($key,)*
168        }
169        pub(super) fn $map_func(event: &Events) -> $name {
170            if false {
171                $name::None
172            } $(else if event.$func($crate::KeyCode::$key) {
173                $name::$key
174            })*
175            else {
176                $name::None
177            }
178        }
179    };
180}
181
182event_2_actionenum!(
183    ReleasedKey,
184    released,
185    map_released_key,
186    A,
187    D,
188    E,
189    H,
190    C,
191    I,
192    T,
193    V,
194    L,
195    Key0,
196    Key1,
197    Key2,
198    Key3,
199    Key4,
200    Key5,
201    Key6,
202    Key7,
203    Key8,
204    Key9,
205    Delete,
206    Back,
207    Left,
208    Right,
209    Up,
210    Down
211);
212event_2_actionenum!(HeldKey, held, map_held_key, I, T);
213macro_rules! set_cat_current {
214    ($num:expr, $label_info:expr) => {
215        if $num < $label_info.cat_ids().len() + 1 {
216            if $label_info.cat_idx_current == $num - 1 {
217                $label_info.show_only_current = !$label_info.show_only_current;
218            } else {
219                $label_info.cat_idx_current = $num - 1;
220                $label_info.show_only_current = false;
221            }
222            true
223        } else {
224            false
225        }
226    };
227}
228
229fn replace_annotations_with_clipboard<T, DA, IA>(
230    mut world: World,
231    history: History,
232    actor: &'static str,
233    clipboard: Option<&ClipboardData<T>>,
234) -> (World, History)
235where
236    T: InstanceAnnotate,
237    DA: MetaDataAccess,
238    IA: InstanceAnnoAccess<T>,
239{
240    let annos = IA::get_annos_mut(&mut world);
241    if let Some(annos) = annos {
242        let all = (0..annos.elts().len()).collect::<Vec<_>>();
243        annos.remove_multiple(&all);
244    }
245    paste::<T, DA, IA>(world, history, actor, clipboard)
246}
247pub(super) fn check_autopaste<T, DA, IA>(
248    mut world: World,
249    mut history: History,
250    actor: &'static str,
251) -> (World, History)
252where
253    T: InstanceAnnotate,
254    DA: MetaDataAccess,
255    IA: InstanceAnnoAccess<T>,
256{
257    let clipboard_data = IA::get_clipboard(&world).cloned();
258    let auto_paste = DA::get_core_options_mut(&mut world).is_some_and(|o| o.auto_paste);
259    if world.data.meta_data.flags.is_loading_screen_active == Some(false) && auto_paste {
260        history.push(Record::new(world.clone(), actor));
261        replace_annotations_with_clipboard::<T, DA, IA>(
262            world,
263            history,
264            actor,
265            clipboard_data.as_ref(),
266        )
267    } else {
268        (world, history)
269    }
270}
271pub fn check_erase_mode<AC>(
272    released_key: ReleasedKey,
273    set_visible: impl Fn(&mut World),
274    mut world: World,
275) -> World
276where
277    AC: MetaDataAccess,
278{
279    if let (ReleasedKey::E, Some(core_options)) =
280        (released_key, AC::get_core_options_mut(&mut world))
281    {
282        if core_options.erase {
283            info!("stop erase via shortcut");
284        } else {
285            info!("start erase via shortcut");
286        }
287        core_options.visible = true;
288        core_options.erase = !core_options.erase;
289        set_visible(&mut world);
290    }
291    world
292}
293
294pub fn check_recolorboxes<AC>(mut world: World, actor: &'static str) -> World
295where
296    AC: MetaDataAccess,
297{
298    let is_colorchange_triggered =
299        AC::get_core_options_mut(&mut world).map(|o| o.is_colorchange_triggered);
300    if is_colorchange_triggered == Some(true) {
301        let core_options = AC::get_core_options_mut(&mut world);
302        if let Some(core_options) = core_options {
303            core_options.is_colorchange_triggered = false;
304            core_options.visible = true;
305        }
306        if let Some(label_info) = AC::get_label_info_mut(&mut world) {
307            label_info.new_random_colors();
308        }
309        let visibility = vis_from_lfoption(AC::get_label_info_mut(&mut world).map(|x| &*x), true);
310        world.request_redraw_annotations(actor, visibility);
311    }
312    world
313}
314pub(super) fn label_change_key(key: ReleasedKey, mut label_info: LabelInfo) -> (LabelInfo, bool) {
315    let label_change = match key {
316        ReleasedKey::Key1 => {
317            set_cat_current!(1, label_info)
318        }
319        ReleasedKey::Key2 => {
320            set_cat_current!(2, label_info)
321        }
322        ReleasedKey::Key3 => {
323            set_cat_current!(3, label_info)
324        }
325        ReleasedKey::Key4 => {
326            set_cat_current!(4, label_info)
327        }
328        ReleasedKey::Key5 => {
329            set_cat_current!(5, label_info)
330        }
331        ReleasedKey::Key6 => {
332            set_cat_current!(6, label_info)
333        }
334        ReleasedKey::Key7 => {
335            set_cat_current!(7, label_info)
336        }
337        ReleasedKey::Key8 => {
338            set_cat_current!(8, label_info)
339        }
340        ReleasedKey::Key9 => {
341            set_cat_current!(9, label_info)
342        }
343        _ => false,
344    };
345    (label_info, label_change)
346}
347pub(super) fn paste<T, DA, IA>(
348    mut world: World,
349    mut history: History,
350    actor: &'static str,
351    clipboard: Option<&ClipboardData<T>>,
352) -> (World, History)
353where
354    T: InstanceAnnotate,
355    DA: MetaDataAccess,
356    IA: InstanceAnnoAccess<T>,
357{
358    if let Some(clipboard) = clipboard {
359        let cb_bbs = clipboard.elts();
360        if !cb_bbs.is_empty() {
361            let shape_orig = ShapeI::from_im(world.data.im_background());
362            let paste_annos = |a: &mut InstanceAnnotations<T>| {
363                a.extend(
364                    cb_bbs.iter().cloned(),
365                    clipboard.cat_idxs().iter().copied(),
366                    shape_orig,
367                );
368            };
369            change_annos::<T, DA, IA>(&mut world, paste_annos);
370        }
371        let options_mut = DA::get_core_options_mut(&mut world);
372        if let Some(options_mut) = options_mut {
373            options_mut.visible = true;
374        }
375        let visible = DA::get_core_options_mut(&mut world).map(|o| o.visible) == Some(true);
376        let vis = vis_from_lfoption(DA::get_label_info(&world), visible);
377        world.request_redraw_annotations(actor, vis);
378        history.push(Record::new(world.clone(), actor));
379    }
380
381    (world, history)
382}
383pub fn deselect_all<T, DA, IA>(mut world: World, actor: &'static str) -> World
384where
385    T: InstanceAnnotate,
386    DA: MetaDataAccess,
387    IA: InstanceAnnoAccess<T>,
388{
389    // Deselect all
390    if let Some(a) = IA::get_annos_mut(&mut world) {
391        a.deselect_all();
392    };
393    let vis = vis_from_lfoption(DA::get_label_info(&world), true);
394    world.request_redraw_annotations(actor, vis);
395    world
396}
397
398pub(super) fn on_selection_keys<T, DA, IA>(
399    mut world: World,
400    mut history: History,
401    key: ReleasedKey,
402    is_ctrl_held: bool,
403    actor: &'static str,
404) -> (World, History)
405where
406    T: InstanceAnnotate,
407    DA: MetaDataAccess,
408    IA: InstanceAnnoAccess<T>,
409{
410    match key {
411        ReleasedKey::A if is_ctrl_held => {
412            // Select all visible
413            let current_active_idx = DA::get_label_info(&world).and_then(|li| {
414                if li.show_only_current {
415                    Some(li.cat_idx_current)
416                } else {
417                    None
418                }
419            });
420            let options = DA::get_core_options_mut(&mut world);
421            if options.map(|o| o.visible) == Some(true) {
422                if let (Some(current_active), Some(a)) =
423                    (current_active_idx, IA::get_annos_mut(&mut world))
424                {
425                    let relevant_indices = a
426                        .cat_idxs()
427                        .iter()
428                        .enumerate()
429                        .filter(|(_, cat_idx)| **cat_idx == current_active)
430                        .map(|(i, _)| i)
431                        .collect::<Vec<_>>();
432                    a.select_multi(relevant_indices.into_iter());
433                } else if let Some(a) = IA::get_annos_mut(&mut world) {
434                    a.select_all();
435                };
436                let vis = vis_from_lfoption(DA::get_label_info(&world), true);
437                world.request_redraw_annotations(actor, vis);
438            }
439        }
440        ReleasedKey::D if is_ctrl_held => {
441            world = deselect_all::<_, DA, IA>(world, actor);
442        }
443        ReleasedKey::C if is_ctrl_held => {
444            // Copy to clipboard
445            let clipboard_data_new =
446                IA::get_annos(&world).map(|d| ClipboardData::from_annotations(d));
447            IA::set_clipboard(&mut world, clipboard_data_new);
448            let vis = vis_from_lfoption(DA::get_label_info(&world), true);
449            world.request_redraw_annotations(actor, vis);
450        }
451        ReleasedKey::V if is_ctrl_held => {
452            let clipboard_data = IA::get_clipboard(&world).cloned();
453            (world, history) = paste::<_, DA, IA>(world, history, actor, clipboard_data.as_ref());
454        }
455        ReleasedKey::V if !is_ctrl_held => {
456            let clipboard_data = IA::get_clipboard(&world).cloned();
457            if let Some(options_mut) = DA::get_core_options_mut(&mut world) {
458                options_mut.auto_paste = !options_mut.auto_paste;
459                if options_mut.auto_paste {
460                    (world, history) = replace_annotations_with_clipboard::<T, DA, IA>(
461                        world,
462                        history,
463                        actor,
464                        clipboard_data.as_ref(),
465                    );
466                }
467            }
468        }
469        ReleasedKey::Delete | ReleasedKey::Back => {
470            // Remove selected
471            let del_annos = |annos: &mut InstanceAnnotations<T>| {
472                if !annos.selected_mask().is_empty() {
473                    annos.remove_selected();
474                }
475            };
476            change_annos::<T, DA, IA>(&mut world, del_annos);
477            let vis = vis_from_lfoption(DA::get_label_info(&world), true);
478            world.request_redraw_annotations(actor, vis);
479            history.push(Record::new(world.clone(), actor));
480        }
481        _ => (),
482    }
483    (world, history)
484}
485
486pub trait Manipulate {
487    fn new() -> Self
488    where
489        Self: Sized;
490
491    fn on_activate(&mut self, world: World) -> World {
492        world
493    }
494    fn on_deactivate(&mut self, world: World) -> World {
495        world
496    }
497    fn on_filechange(&mut self, world: World, history: History) -> (World, History) {
498        (world, history)
499    }
500    fn on_always_active_zoom(&mut self, world: World, history: History) -> (World, History) {
501        (world, history)
502    }
503    /// None -> the tool does not tell you if it has been used
504    /// Some(true) -> the tool has been used
505    /// Some(false) -> the tool has not been used
506    fn has_been_used(&self, _: &Events) -> Option<bool> {
507        None
508    }
509    /// All events that are used by a tool are implemented in here. Use the macro [`make_tool_transform`](make_tool_transform). See, e.g.,
510    /// [`Zoom::events_tf`](crate::tools::Zoom::events_tf).
511    fn events_tf(&mut self, world: World, history: History, events: &Events) -> (World, History);
512
513    fn get_visibility(&self, _world: &World) -> Visibility {
514        Visibility::None
515    }
516}
517
518const N_HIST_ELTS: usize = 8;
519
520#[derive(Clone, Copy, Debug)]
521pub struct Mover {
522    mouse_pos_start: Option<PtF>,
523    mouse_pos_history: [Option<PtF>; N_HIST_ELTS],
524    idx_next_history_update: usize,
525}
526impl Mover {
527    pub fn new() -> Self {
528        Self {
529            mouse_pos_start: None,
530            mouse_pos_history: [None; N_HIST_ELTS],
531            idx_next_history_update: 0,
532        }
533    }
534    pub fn move_mouse_held<T, F: FnOnce(PtF, PtF) -> T>(
535        &mut self,
536        f_move: F,
537        mouse_pos: Option<PtF>,
538    ) -> Option<T> {
539        let res = if let (Some(mp_start), Some(mp)) = (self.mouse_pos_start, mouse_pos) {
540            if self.mouse_pos_history.contains(&mouse_pos) {
541                None
542            } else {
543                let mpo_from = Some(mp_start);
544                let mpo_to = Some(mp);
545                match (mpo_from, mpo_to) {
546                    (Some(mp_from), Some(mp_to)) => Some(f_move(mp_from, mp_to)),
547                    _ => None,
548                }
549            }
550        } else {
551            None
552        };
553        self.mouse_pos_history[self.idx_next_history_update % N_HIST_ELTS] = self.mouse_pos_start;
554        self.mouse_pos_start = mouse_pos;
555        self.idx_next_history_update = self.idx_next_history_update.wrapping_add(1);
556        res
557    }
558    pub fn move_mouse_pressed(&mut self, mouse_pos: Option<PtF>) {
559        if mouse_pos.is_some() {
560            self.mouse_pos_start = mouse_pos;
561        }
562    }
563}
564
565// applies the tool transformation to the world
566#[macro_export]
567macro_rules! make_tool_transform {
568    (
569        $self:expr,
570        $world:expr,
571        $history:expr,
572        $events:expr,
573        [$(($key_event:ident, $key_btn:expr, $method_name:ident)),*]
574    ) => {
575        if false {
576            ($world, $history)
577        }
578        $(else if $events.$key_event($key_btn) {
579            $self.$method_name($events, $world, $history)
580        })*
581        else {
582            ($world, $history)
583        }
584    };
585}