rvlib/control/
paths_navigator.rs1use std::{fmt::Debug, path::Path};
2
3use super::filter::FilterExpr;
4use crate::{
5 file_util::PathPair,
6 paths_selector::PathsSelector,
7 result::{ignore_error, trace_ok_err},
8 sort_params::{SortParams, SortType},
9 tools_data::ToolsDataMap,
10};
11use exmex::prelude::*;
12use rvimage_domain::RvResult;
13
14fn next(file_selected_idx: usize, files_len: usize) -> usize {
15 if file_selected_idx < files_len - 1 {
16 file_selected_idx + 1
17 } else {
18 files_len - 1
19 }
20}
21
22fn prev(file_selected_idx: usize, files_len: usize) -> usize {
23 if file_selected_idx >= files_len {
24 files_len - 1
25 } else if file_selected_idx > 0 {
26 file_selected_idx - 1
27 } else {
28 0
29 }
30}
31#[derive(Default)]
32pub struct PathsNavigator {
33 file_label_selected_idx: Option<usize>,
34 paths_selector: Option<PathsSelector>,
35 scroll_to_selected_label: bool,
36}
37impl PathsNavigator {
38 pub fn new(
39 mut paths_selector: Option<PathsSelector>,
40 sort_params: SortParams,
41 ) -> RvResult<Self> {
42 if let Some(ps) = &mut paths_selector {
43 match sort_params.kind {
44 SortType::Natural => ps.natural_sort(sort_params.sort_by_filename)?,
45 SortType::Alphabetical => ps.alphabetical_sort(sort_params.sort_by_filename)?,
46 }
47 };
48 Ok(Self {
49 file_label_selected_idx: None,
50 paths_selector,
51 scroll_to_selected_label: false,
52 })
53 }
54 fn pn(&mut self, f: fn(usize, usize) -> usize) {
55 if let Some(idx) = self.file_label_selected_idx {
56 if let Some(ps) = &self.paths_selector {
57 self.file_label_selected_idx = Some(f(idx, ps.len_filtered()));
58 self.scroll_to_selected_label = true;
59 }
60 }
61 }
62 pub fn next(&mut self) {
63 self.pn(next);
64 }
65 pub fn prev(&mut self) {
66 self.pn(prev);
67 }
68 pub fn file_label_selected_idx(&self) -> Option<usize> {
69 self.file_label_selected_idx
70 }
71 pub fn len_filtered(&self) -> Option<usize> {
72 self.paths_selector.as_ref().map(|ps| ps.len_filtered())
73 }
74 pub fn scroll_to_selected_label(&self) -> bool {
75 self.scroll_to_selected_label
76 }
77 pub fn activate_scroll_to_selected_label(&mut self) {
78 self.scroll_to_selected_label = true;
79 }
80 pub fn deactivate_scroll_to_selected_label(&mut self) {
81 self.scroll_to_selected_label = false;
82 }
83 pub fn select_label_idx(&mut self, filtered_label_idx: Option<usize>) {
85 if let (Some(idx), Some(ps)) = (filtered_label_idx, self.paths_selector()) {
86 if idx < ps.len_filtered() {
87 self.file_label_selected_idx = Some(idx);
88 }
89 }
90 }
91 pub fn export_filtered_filelist<P>(&self, export_path: P, filter_str: &str) -> RvResult<()>
92 where
93 P: AsRef<Path> + Debug,
94 {
95 if let Some(ps) = self.paths_selector() {
96 ps.export_filtered(export_path, filter_str)?
97 };
98 Ok(())
99 }
100
101 fn idx_of_file_label(&self, file_label: &str) -> Option<usize> {
102 match self.paths_selector() {
103 Some(ps) => ps.filteredidx_of_file_label(file_label),
104 None => None,
105 }
106 }
107
108 pub fn select_file_label(&mut self, file_label: &str) {
109 self.select_label_idx(self.idx_of_file_label(file_label));
110 }
111
112 pub fn paths_selector(&self) -> Option<&PathsSelector> {
113 self.paths_selector.as_ref()
114 }
115
116 fn filter_by_pred(&mut self, filter_predicate: impl FnMut(&str) -> bool) -> RvResult<()> {
117 if let Some(ps) = &mut self.paths_selector {
118 let unfiltered_idx_before_filter =
119 if let Some(filtered_idx) = self.file_label_selected_idx {
120 self.scroll_to_selected_label = true;
121 let (unfiltered_idx, _) = ps.filtered_idx_file_label_pairs(filtered_idx);
122 Some(unfiltered_idx)
123 } else {
124 None
125 };
126 ps.filter(filter_predicate)?;
127 self.file_label_selected_idx = match unfiltered_idx_before_filter {
128 Some(unfiltered_idx) => ps
129 .filtered_iter()
130 .enumerate()
131 .find(|(_, (uidx, _))| *uidx == unfiltered_idx)
132 .map(|(fidx, _)| fidx),
133 None => None,
134 };
135 }
136 Ok(())
137 }
138
139 pub fn filter(
140 &mut self,
141 s: &str,
142 tools_data_map: &ToolsDataMap,
143 active_tool_name: Option<&str>,
144 ) -> RvResult<()> {
145 if let Some(filter_pred) =
146 ignore_error(FilterExpr::parse(s).and_then(|expr| expr.eval(&[])))
147 {
148 let filter_pred_wrapper = |path: &str| {
149 trace_ok_err(filter_pred.apply(path, Some(tools_data_map), active_tool_name))
150 .unwrap_or(true)
151 };
152 self.filter_by_pred(filter_pred_wrapper)?;
153 } else {
154 let trimmed = s.trim();
155 let filter_pred = |path: &str| {
156 if path.is_empty() {
157 true
158 } else {
159 path.contains(trimmed)
160 }
161 };
162 self.filter_by_pred(filter_pred)?;
163 }
164 Ok(())
165 }
166
167 pub fn folder_label(&self) -> Option<&str> {
168 self.paths_selector().as_ref().map(|ps| ps.folder_label())
169 }
170
171 pub fn file_path(&self, file_idx: usize) -> Option<&PathPair> {
172 self.paths_selector()
173 .as_ref()
174 .and_then(|ps| ps.file_selected_path(file_idx))
175 }
176}
177
178#[test]
179fn test_prev_next() {
180 assert_eq!(next(3, 4), 3);
181 assert_eq!(next(2, 4), 3);
182 assert_eq!(next(5, 4), 3);
183 assert_eq!(next(1, 4), 2);
184 assert_eq!(prev(3, 4), 2);
185 assert_eq!(prev(2, 3), 1);
186 assert_eq!(prev(3, 3), 2);
187 assert_eq!(prev(4, 3), 2);
188 assert_eq!(prev(9, 3), 2);
189}