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::{
12    events::Events,
13    history::History,
14    world,
15    world::{MetaDataAccess, World},
16};
17use crate::{InstanceLabelDisplay, ShapeI};
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 ild = DA::get_core_options(&world)
362                .map(|o| o.instance_label_display)
363                .unwrap_or_default();
364            let shape_orig = ShapeI::from_im(world.data.im_background());
365            let paste_annos = |a: &mut InstanceAnnotations<T>| {
366                a.extend(
367                    cb_bbs.iter().cloned(),
368                    clipboard.cat_idxs().iter().copied(),
369                    shape_orig,
370                    ild,
371                );
372            };
373            change_annos::<T, DA, IA>(&mut world, paste_annos);
374        }
375        let options_mut = DA::get_core_options_mut(&mut world);
376        if let Some(options_mut) = options_mut {
377            options_mut.visible = true;
378        }
379        let visible = DA::get_core_options_mut(&mut world).map(|o| o.visible) == Some(true);
380        let vis = vis_from_lfoption(DA::get_label_info(&world), visible);
381        world.request_redraw_annotations(actor, vis);
382        history.push(Record::new(world.clone(), actor));
383    }
384
385    (world, history)
386}
387pub fn deselect_all<T, DA, IA>(mut world: World, actor: &'static str) -> World
388where
389    T: InstanceAnnotate,
390    DA: MetaDataAccess,
391    IA: InstanceAnnoAccess<T>,
392{
393    // Deselect all
394    if let Some(a) = IA::get_annos_mut(&mut world) {
395        a.deselect_all();
396    };
397    let vis = vis_from_lfoption(DA::get_label_info(&world), true);
398    world.request_redraw_annotations(actor, vis);
399    world
400}
401
402pub(super) fn instance_label_display_sort<T, DA, IA>(
403    mut world: World,
404    instance_label_display: InstanceLabelDisplay,
405    actor: &'static str,
406) -> World
407where
408    T: InstanceAnnotate,
409    DA: MetaDataAccess,
410    IA: InstanceAnnoAccess<T>,
411{
412    let annos = IA::get_annos_mut(&mut world);
413    if let Some(annos) = annos {
414        *annos = instance_label_display.sort(mem::take(annos));
415    }
416
417    let vis = vis_from_lfoption(DA::get_label_info(&world), true);
418    world.request_redraw_annotations(actor, vis);
419
420    world
421}
422pub(super) fn check_instance_label_display_change<T, DA, IA>(
423    mut world: World,
424    key: ReleasedKey,
425    actor: &'static str,
426) -> World
427where
428    T: InstanceAnnotate,
429    DA: MetaDataAccess,
430    IA: InstanceAnnoAccess<T>,
431{
432    if let ReleasedKey::L = key {
433        // update instance label display
434        let options = DA::get_core_options_mut(&mut world);
435        if let Some(options) = options {
436            options.instance_label_display = options.instance_label_display.next();
437            tracing::info!(
438                "instance label display changed to {}",
439                options.instance_label_display
440            );
441        }
442
443        // sort the annotations
444        let ild = DA::get_core_options(&world)
445            .map(|o| o.instance_label_display)
446            .unwrap_or_default();
447        world = instance_label_display_sort::<_, DA, IA>(world, ild, actor);
448    }
449    world
450}
451
452pub(super) fn on_selection_keys<T, DA, IA>(
453    mut world: World,
454    mut history: History,
455    key: ReleasedKey,
456    is_ctrl_held: bool,
457    actor: &'static str,
458) -> (World, History)
459where
460    T: InstanceAnnotate,
461    DA: MetaDataAccess,
462    IA: InstanceAnnoAccess<T>,
463{
464    match key {
465        ReleasedKey::A if is_ctrl_held => {
466            // Select all visible
467            let current_active_idx = DA::get_label_info(&world).and_then(|li| {
468                if li.show_only_current {
469                    Some(li.cat_idx_current)
470                } else {
471                    None
472                }
473            });
474            let options = DA::get_core_options_mut(&mut world);
475            if options.map(|o| o.visible) == Some(true) {
476                if let (Some(current_active), Some(a)) =
477                    (current_active_idx, IA::get_annos_mut(&mut world))
478                {
479                    let relevant_indices = a
480                        .cat_idxs()
481                        .iter()
482                        .enumerate()
483                        .filter(|(_, cat_idx)| **cat_idx == current_active)
484                        .map(|(i, _)| i)
485                        .collect::<Vec<_>>();
486                    a.select_multi(relevant_indices.into_iter());
487                } else if let Some(a) = IA::get_annos_mut(&mut world) {
488                    a.select_all();
489                };
490                let vis = vis_from_lfoption(DA::get_label_info(&world), true);
491                world.request_redraw_annotations(actor, vis);
492            }
493        }
494        ReleasedKey::D if is_ctrl_held => {
495            world = deselect_all::<_, DA, IA>(world, actor);
496        }
497        ReleasedKey::C if is_ctrl_held => {
498            // Copy to clipboard
499            let clipboard_data_new =
500                IA::get_annos(&world).map(|d| ClipboardData::from_annotations(d));
501            IA::set_clipboard(&mut world, clipboard_data_new);
502            let vis = vis_from_lfoption(DA::get_label_info(&world), true);
503            world.request_redraw_annotations(actor, vis);
504        }
505        ReleasedKey::V if is_ctrl_held => {
506            let clipboard_data = IA::get_clipboard(&world).cloned();
507            (world, history) = paste::<_, DA, IA>(world, history, actor, clipboard_data.as_ref());
508        }
509        ReleasedKey::V if !is_ctrl_held => {
510            let clipboard_data = IA::get_clipboard(&world).cloned();
511            if let Some(options_mut) = DA::get_core_options_mut(&mut world) {
512                options_mut.auto_paste = !options_mut.auto_paste;
513                if options_mut.auto_paste {
514                    (world, history) = replace_annotations_with_clipboard::<T, DA, IA>(
515                        world,
516                        history,
517                        actor,
518                        clipboard_data.as_ref(),
519                    );
520                }
521            }
522        }
523        ReleasedKey::Delete | ReleasedKey::Back => {
524            // Remove selected
525            let del_annos = |annos: &mut InstanceAnnotations<T>| {
526                if !annos.selected_mask().is_empty() {
527                    annos.remove_selected();
528                }
529            };
530            change_annos::<T, DA, IA>(&mut world, del_annos);
531            let vis = vis_from_lfoption(DA::get_label_info(&world), true);
532            world.request_redraw_annotations(actor, vis);
533            history.push(Record::new(world.clone(), actor));
534        }
535        _ => (),
536    }
537    (world, history)
538}
539
540pub trait Manipulate {
541    fn new() -> Self
542    where
543        Self: Sized;
544
545    fn on_activate(&mut self, world: World) -> World {
546        world
547    }
548    fn on_deactivate(&mut self, world: World) -> World {
549        world
550    }
551    fn on_filechange(&mut self, world: World, history: History) -> (World, History) {
552        (world, history)
553    }
554    fn on_always_active_zoom(&mut self, world: World, history: History) -> (World, History) {
555        (world, history)
556    }
557    /// None -> the tool does not tell you if it has been used
558    /// Some(true) -> the tool has been used
559    /// Some(false) -> the tool has not been used
560    fn has_been_used(&self, _: &Events) -> Option<bool> {
561        None
562    }
563    /// All events that are used by a tool are implemented in here. Use the macro [`make_tool_transform`](make_tool_transform). See, e.g.,
564    /// [`Zoom::events_tf`](crate::tools::Zoom::events_tf).
565    fn events_tf(&mut self, world: World, history: History, events: &Events) -> (World, History);
566
567    fn get_visibility(&self, _world: &World) -> Visibility {
568        Visibility::None
569    }
570}
571
572const N_HIST_ELTS: usize = 8;
573
574#[derive(Clone, Copy, Debug)]
575pub struct Mover {
576    mouse_pos_start: Option<PtF>,
577    mouse_pos_history: [Option<PtF>; N_HIST_ELTS],
578    idx_next_history_update: usize,
579}
580impl Mover {
581    pub fn new() -> Self {
582        Self {
583            mouse_pos_start: None,
584            mouse_pos_history: [None; N_HIST_ELTS],
585            idx_next_history_update: 0,
586        }
587    }
588    pub fn move_mouse_held<T, F: FnOnce(PtF, PtF) -> T>(
589        &mut self,
590        f_move: F,
591        mouse_pos: Option<PtF>,
592    ) -> Option<T> {
593        let res = if let (Some(mp_start), Some(mp)) = (self.mouse_pos_start, mouse_pos) {
594            if self.mouse_pos_history.contains(&mouse_pos) {
595                None
596            } else {
597                let mpo_from = Some(mp_start);
598                let mpo_to = Some(mp);
599                match (mpo_from, mpo_to) {
600                    (Some(mp_from), Some(mp_to)) => Some(f_move(mp_from, mp_to)),
601                    _ => None,
602                }
603            }
604        } else {
605            None
606        };
607        self.mouse_pos_history[self.idx_next_history_update % N_HIST_ELTS] = self.mouse_pos_start;
608        self.mouse_pos_start = mouse_pos;
609        self.idx_next_history_update = self.idx_next_history_update.wrapping_add(1);
610        res
611    }
612    pub fn move_mouse_pressed(&mut self, mouse_pos: Option<PtF>) {
613        if mouse_pos.is_some() {
614            self.mouse_pos_start = mouse_pos;
615        }
616    }
617}
618
619// applies the tool transformation to the world
620#[macro_export]
621macro_rules! make_tool_transform {
622    (
623        $self:expr,
624        $world:expr,
625        $history:expr,
626        $events:expr,
627        [$(($key_event:ident, $key_btn:expr, $method_name:ident)),*]
628    ) => {
629        if false {
630            ($world, $history)
631        }
632        $(else if $events.$key_event($key_btn) {
633            $self.$method_name($events, $world, $history)
634        })*
635        else {
636            ($world, $history)
637        }
638    };
639}