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