Skip to main content

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::{ImportExportTrigger, InstanceAnnotate, LabelInfo, vis_from_lfoption};
9use crate::util::Visibility;
10use crate::world::InstanceAnnoAccess;
11use crate::{InstanceLabelDisplay, ShapeI};
12use crate::{
13    events::Events,
14    history::History,
15    parameters::ParamVal,
16    world,
17    world::{MetaDataAccess, World},
18};
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    is_shift_held: bool,
459    actor: &'static str,
460) -> (World, History)
461where
462    T: InstanceAnnotate,
463    DA: MetaDataAccess,
464    IA: InstanceAnnoAccess<T>,
465{
466    match key {
467        ReleasedKey::A if is_ctrl_held => {
468            // Select all visible
469            let current_active_idx = DA::get_label_info(&world).and_then(|li| {
470                if li.show_only_current {
471                    Some(li.cat_idx_current)
472                } else {
473                    None
474                }
475            });
476            let options = DA::get_core_options_mut(&mut world);
477            if options.map(|o| o.visible) == Some(true) {
478                let zoom_box = *world.zoom_box();
479                if let Some(a) = IA::get_annos_mut(&mut world) {
480                    let relevant_idxs = a
481                        .cat_idxs()
482                        .iter()
483                        .zip(a.elts().iter())
484                        .enumerate()
485                        .filter(|(_, (cat_idx, elt))| {
486                            current_active_idx
487                                .map(|caidx| caidx == **cat_idx)
488                                .unwrap_or(true)
489                                && (zoom_box
490                                    .map(|zb| zb.contains_bb(elt.enclosing_bb()))
491                                    .unwrap_or(true)
492                                    || !is_shift_held)
493                        })
494                        .map(|(idx, _)| idx)
495                        .collect::<Vec<_>>();
496                    a.select_multi(relevant_idxs.into_iter());
497                }
498
499                let vis = vis_from_lfoption(DA::get_label_info(&world), true);
500                world.request_redraw_annotations(actor, vis);
501            }
502        }
503        ReleasedKey::D if is_ctrl_held => {
504            world = deselect_all::<_, DA, IA>(world, actor);
505        }
506        ReleasedKey::C if is_ctrl_held => {
507            // Copy to clipboard
508            let clipboard_data_new =
509                IA::get_annos(&world).map(|d| ClipboardData::from_annotations(d));
510            IA::set_clipboard(&mut world, clipboard_data_new);
511            let vis = vis_from_lfoption(DA::get_label_info(&world), true);
512            world.request_redraw_annotations(actor, vis);
513        }
514        ReleasedKey::V if is_ctrl_held => {
515            let clipboard_data = IA::get_clipboard(&world).cloned();
516            (world, history) = paste::<_, DA, IA>(world, history, actor, clipboard_data.as_ref());
517        }
518        ReleasedKey::V if !is_ctrl_held => {
519            let clipboard_data = IA::get_clipboard(&world).cloned();
520            if let Some(options_mut) = DA::get_core_options_mut(&mut world) {
521                options_mut.auto_paste = !options_mut.auto_paste;
522                if options_mut.auto_paste {
523                    (world, history) = replace_annotations_with_clipboard::<T, DA, IA>(
524                        world,
525                        history,
526                        actor,
527                        clipboard_data.as_ref(),
528                    );
529                }
530            }
531        }
532        ReleasedKey::Delete | ReleasedKey::Back => {
533            // Remove selected
534            let del_annos = |annos: &mut InstanceAnnotations<T>| {
535                if !annos.selected_mask().is_empty() {
536                    annos.remove_selected();
537                }
538            };
539            change_annos::<T, DA, IA>(&mut world, del_annos);
540            let vis = vis_from_lfoption(DA::get_label_info(&world), true);
541            world.request_redraw_annotations(actor, vis);
542            history.push(Record::new(world.clone(), actor));
543        }
544        _ => (),
545    }
546    (world, history)
547}
548
549pub trait Manipulate {
550    fn new() -> Self
551    where
552        Self: Sized;
553
554    fn on_activate(&mut self, world: World) -> World {
555        world
556    }
557    fn on_deactivate(&mut self, world: World) -> World {
558        world
559    }
560    fn on_filechange(&mut self, world: World, history: History) -> (World, History) {
561        (world, history)
562    }
563    fn on_always_active_zoom(&mut self, world: World, history: History) -> (World, History) {
564        (world, history)
565    }
566    /// None -> the tool does not tell you if it has been used
567    /// Some(true) -> the tool has been used
568    /// Some(false) -> the tool has not been used
569    fn has_been_used(&self, _: &Events) -> Option<bool> {
570        None
571    }
572    /// All events that are used by a tool are implemented in here. Use the macro [`make_tool_transform`](make_tool_transform). See, e.g.,
573    /// [`Zoom::events_tf`](crate::tools::Zoom::events_tf).
574    fn events_tf(&mut self, world: World, history: History, events: &Events) -> (World, History);
575
576    fn get_visibility(&self, _world: &World) -> Visibility {
577        Visibility::None
578    }
579}
580
581const N_HIST_ELTS: usize = 8;
582
583#[derive(Clone, Copy, Debug)]
584pub struct Mover {
585    mouse_pos_start: Option<PtF>,
586    mouse_pos_history: [Option<PtF>; N_HIST_ELTS],
587    idx_next_history_update: usize,
588}
589impl Mover {
590    pub fn new() -> Self {
591        Self {
592            mouse_pos_start: None,
593            mouse_pos_history: [None; N_HIST_ELTS],
594            idx_next_history_update: 0,
595        }
596    }
597    pub fn move_mouse_held<T, F: FnOnce(PtF, PtF) -> T>(
598        &mut self,
599        f_move: F,
600        mouse_pos: Option<PtF>,
601    ) -> Option<T> {
602        let res = if let (Some(mp_start), Some(mp)) = (self.mouse_pos_start, mouse_pos) {
603            if self.mouse_pos_history.contains(&mouse_pos) {
604                None
605            } else {
606                let mpo_from = Some(mp_start);
607                let mpo_to = Some(mp);
608                match (mpo_from, mpo_to) {
609                    (Some(mp_from), Some(mp_to)) => Some(f_move(mp_from, mp_to)),
610                    _ => None,
611                }
612            }
613        } else {
614            None
615        };
616        if let Some(mph) = self
617            .mouse_pos_history
618            .get_mut(self.idx_next_history_update % N_HIST_ELTS)
619        {
620            *mph = self.mouse_pos_start;
621        }
622        self.mouse_pos_start = mouse_pos;
623        self.idx_next_history_update = self.idx_next_history_update.wrapping_add(1);
624        res
625    }
626    pub fn move_mouse_pressed(&mut self, mouse_pos: Option<PtF>) {
627        if mouse_pos.is_some() {
628            self.mouse_pos_start = mouse_pos;
629        }
630    }
631}
632
633// applies the tool transformation to the world
634#[macro_export]
635macro_rules! make_tool_transform {
636    (
637        $self:expr,
638        $world:expr,
639        $history:expr,
640        $events:expr,
641        [$(($key_event:ident, $key_btn:expr, $method_name:ident)),*]
642    ) => {
643        if false {
644            ($world, $history)
645        }
646        $(else if $events.$key_event($key_btn) {
647            $self.$method_name($events, $world, $history)
648        })*
649        else {
650            ($world, $history)
651        }
652    };
653}