rvimage 0.6.8

A remote image viewer with a labeling tool
Documentation
use tracing::info;

use crate::{file_util::PathPair, world::World};
use std::fmt::Debug;

const MAX_HISTORY: usize = 20;

#[derive(Clone)]
pub struct Record {
    pub world: World,
    pub actor: &'static str,
    pub file_label_idx: Option<usize>,
    pub opened_folder: Option<String>,
}

impl Record {
    pub fn new(world: World, actor: &'static str) -> Self {
        let folder = world
            .data
            .meta_data
            .opened_folder
            .as_ref()
            .map(|folder| folder.path_absolute().to_string());
        let file_selected_idx = world.data.meta_data.file_selected_idx;

        Self {
            world,
            actor,
            file_label_idx: file_selected_idx,
            opened_folder: folder,
        }
    }

    fn convert_to_im_idx_pair(self) -> (World, Option<usize>) {
        (self.world, self.file_label_idx)
    }
}

#[derive(Clone, Default)]
pub struct History {
    records: Vec<Record>,
    current_idx: Option<usize>,
}

impl History {
    fn clear_on_folder_change(&mut self, current_folder: &Option<String>) {
        if let Some(cf) = current_folder {
            let folder_in_history = self
                .records
                .iter()
                .enumerate()
                .find(|(_, r)| r.opened_folder.as_ref() == Some(cf));
            if let Some((i, _)) = folder_in_history {
                self.records.drain(0..i);
            } else {
                self.current_idx = None;
                self.records.clear();
            }
        }
    }

    pub fn current_record(&self) -> Option<Record> {
        self.current_idx
            .and_then(|idx| self.records.get(idx).cloned())
    }

    pub fn push(&mut self, record: Record) {
        info!("{} added to history", record.actor);
        self.clear_on_folder_change(&record.opened_folder);
        match self.current_idx {
            None => {
                self.current_idx = Some(0);
                if !self.records.is_empty() {
                    self.records.clear();
                }
                self.records.push(record);
            }
            Some(idx) => {
                if idx < self.records.len() - 1 {
                    self.records.truncate(idx + 1);
                }
                self.current_idx = Some(idx + 1);
                self.records.push(record);
            }
        }
        if self.records.len() > MAX_HISTORY {
            self.records.remove(0);
            self.current_idx = self.current_idx.map(|idx| idx - 1);
        }
    }

    fn change_world<F1, F2>(
        &mut self,
        idx_change: F1,
        pred: F2,
        opened_folder: &Option<String>,
    ) -> Option<(World, Option<usize>)>
    where
        F1: Fn(usize) -> usize,
        F2: FnOnce(usize) -> bool,
    {
        self.clear_on_folder_change(opened_folder);
        match self.current_idx {
            Some(idx) if pred(idx) => {
                self.current_idx = Some(idx_change(idx));
                self.records
                    .get(idx_change(idx))
                    .cloned()
                    .map(|r| r.convert_to_im_idx_pair())
            }
            _ => None,
        }
    }

    pub fn prev_world(
        &mut self,
        opened_folder: &Option<PathPair>,
    ) -> Option<(World, Option<usize>)> {
        self.change_world(
            |idx| idx - 1,
            |idx| idx > 0,
            &opened_folder
                .as_ref()
                .map(|of| of.path_absolute().to_string()),
        )
    }

    pub fn next_world(
        &mut self,
        opened_folder: &Option<PathPair>,
    ) -> Option<(World, Option<usize>)> {
        let n_recs = self.records.len();
        self.change_world(
            |idx| idx + 1,
            |idx| idx < n_recs - 1,
            &opened_folder
                .as_ref()
                .map(|of| of.path_absolute().to_string()),
        )
    }
}

impl Debug for History {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "(record idx {:?}, {:#?})",
            self.current_idx,
            self.records
                .iter()
                .map(|r| format!(
                    "actor {}, file label idx {:?}, {:?}, folder label {:?}",
                    r.actor,
                    r.file_label_idx,
                    &r.world.data.shape(),
                    r.opened_folder
                ))
                .collect::<Vec<_>>()
        )
    }
}

#[cfg(test)]
use {
    crate::{tools_data::ToolsDataMap, types::ThumbIms, types::ViewImage},
    image::DynamicImage,
    rvimage_domain::RvResult,
    std::path::Path,
};
#[test]
fn test_history() -> RvResult<()> {
    let im = ViewImage::new(64, 64);
    let prj_path = Path::new("");
    let world = World::from_real_im(
        DynamicImage::ImageRgb8(im),
        ThumbIms::default(),
        ToolsDataMap::new(),
        None,
        None,
        prj_path,
        None,
    );
    let mut hist = History::default();

    hist.push(Record {
        world: world.clone(),
        actor: "",
        file_label_idx: None,
        opened_folder: None,
    });
    let world = World::from_real_im(
        DynamicImage::ImageRgb8(ViewImage::new(32, 32)),
        ThumbIms::default(),
        ToolsDataMap::new(),
        None,
        None,
        prj_path,
        None,
    );
    hist.push(Record {
        world: world.clone(),
        actor: "",
        file_label_idx: None,
        opened_folder: None,
    });
    assert_eq!(hist.records.len(), 2);
    assert_eq!(hist.records[0].world.data.shape().w, 64);
    assert_eq!(hist.records[1].world.data.shape().w, 32);
    hist.prev_world(&None);
    let world = World::from_real_im(
        DynamicImage::ImageRgb8(ViewImage::new(16, 16)),
        ThumbIms::default(),
        ToolsDataMap::new(),
        None,
        None,
        prj_path,
        None,
    );
    hist.push(Record {
        world: world.clone(),
        actor: "",
        file_label_idx: None,
        opened_folder: None,
    });
    assert_eq!(hist.records.len(), 2);
    assert_eq!(hist.records[0].world.data.shape().w, 64);
    assert_eq!(hist.records[1].world.data.shape().w, 16);

    hist.push(Record {
        world: world.clone(),
        actor: "",
        file_label_idx: None,
        opened_folder: Some("folder1".to_string()),
    });
    assert_eq!(hist.records.len(), 1);

    hist.push(Record {
        world: world.clone(),
        actor: "",
        file_label_idx: None,
        opened_folder: Some("folder2".to_string()),
    });
    hist.push(Record {
        world: world.clone(),
        actor: "",
        file_label_idx: None,
        opened_folder: None,
    });
    hist.push(Record {
        world: world.clone(),
        actor: "",
        file_label_idx: None,
        opened_folder: Some("folder2".to_string()),
    });

    assert_eq!(hist.records.len(), 3);
    assert_eq!(hist.records[0].opened_folder, Some("folder2".to_string()));
    assert_eq!(hist.records[1].opened_folder, None);
    assert_eq!(hist.records[2].opened_folder, Some("folder2".to_string()));

    Ok(())
}