Skip to main content

tui_treelistview/
model.rs

1use std::cmp::Ordering;
2use std::hash::Hash;
3use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
4
5static NEXT_QUERY_POLICY_GENERATION: AtomicU64 = AtomicU64::new(1);
6
7/// The state of a node's child list.
8///
9/// Unlike an empty slice, `Unloaded` and `Loading` preserve the fact that a node is a branch
10/// whose children may be loaded asynchronously.
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum TreeChildren<'a, Id> {
13    /// The node is known to be a leaf.
14    Leaf,
15    /// Children exist or may exist, but have not been loaded yet.
16    Unloaded,
17    /// Children are currently loading.
18    Loading,
19    /// Children are loaded and exposed as a stable slice.
20    Loaded(&'a [Id]),
21}
22
23impl<'a, Id> TreeChildren<'a, Id> {
24    /// Creates a loaded state, converting an empty slice into a leaf.
25    #[must_use]
26    pub const fn loaded(children: &'a [Id]) -> Self {
27        if children.is_empty() {
28            Self::Leaf
29        } else {
30            Self::Loaded(children)
31        }
32    }
33
34    /// Returns the loaded children or an empty slice.
35    #[must_use]
36    pub const fn loaded_slice(self) -> &'a [Id] {
37        match self {
38            Self::Loaded(children) => children,
39            Self::Leaf | Self::Unloaded | Self::Loading => &[],
40        }
41    }
42
43    /// Returns `true` when the node is a potentially expandable branch.
44    #[must_use]
45    pub const fn is_branch(self) -> bool {
46        !matches!(self, Self::Leaf)
47    }
48}
49
50/// Controls how roots are projected.
51#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
52pub enum TreeRootVisibility {
53    /// Every root is displayed as a regular row.
54    #[default]
55    Visible,
56    /// Roots are synthetic; their loaded children are displayed at level `0`.
57    Hidden,
58}
59
60/// Selection policy used when the selected node disappears from the projection.
61#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
62pub enum TreeSelectionFallback {
63    /// Prefer the nearest visible ancestor, then the nearest row.
64    #[default]
65    ParentThenNearest,
66    /// Select the row nearest to the previous index.
67    Nearest,
68    /// Clear the selection.
69    Clear,
70}
71
72/// Tree filtering configuration.
73#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
74pub enum TreeFilterConfig {
75    /// Filtering is disabled.
76    #[default]
77    Disabled,
78    /// Keep matching nodes and the paths leading to them.
79    Enabled {
80        /// Force filtered paths to expand.
81        auto_expand: bool,
82    },
83}
84
85impl TreeFilterConfig {
86    /// Enables filtering with automatic path expansion.
87    #[must_use]
88    pub const fn enabled() -> Self {
89        Self::Enabled { auto_expand: true }
90    }
91
92    /// Enables filtering with manual path expansion.
93    #[must_use]
94    pub const fn enabled_manual_expand() -> Self {
95        Self::Enabled { auto_expand: false }
96    }
97}
98
99/// A monotonically increasing revision of model, filter, or sorting data.
100///
101/// The projection cache is rebuilt whenever any participating revision changes. A
102/// [`TreeModel`] implementation must return a new value after changing its contents.
103#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
104pub struct TreeRevision(u64);
105
106impl TreeRevision {
107    /// The initial revision of an immutable value.
108    pub const INITIAL: Self = Self(0);
109
110    /// Creates a revision from an external counter.
111    #[must_use]
112    pub const fn new(value: u64) -> Self {
113        Self(value)
114    }
115
116    /// Returns the numeric revision value.
117    #[must_use]
118    pub const fn get(self) -> u64 {
119        self.0
120    }
121
122    /// Returns the next revision.
123    #[must_use]
124    pub const fn next(self) -> Self {
125        Self(self.0.wrapping_add(1))
126    }
127
128    /// Advances the counter to the next revision.
129    pub const fn advance(&mut self) {
130        *self = self.next();
131    }
132}
133
134impl From<u64> for TreeRevision {
135    fn from(value: u64) -> Self {
136        Self::new(value)
137    }
138}
139
140/// A filter that matches every node.
141#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
142pub struct NoFilter;
143
144impl<T: TreeModel> TreeFilter<T> for NoFilter {
145    #[inline]
146    fn is_match(&self, _model: &T, _id: T::Id) -> bool {
147        true
148    }
149}
150
151/// No sorting; preserves model order.
152#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
153pub struct NoSort;
154
155impl<T: TreeModel> TreeSort<T> for NoSort {
156    fn compare(&self, _model: &T, _left: T::Id, _right: T::Id) -> Ordering {
157        Ordering::Equal
158    }
159
160    fn is_enabled(&self) -> bool {
161        false
162    }
163}
164
165/// A complete query for building the visible projection.
166#[derive(Clone, Debug)]
167pub struct TreeQuery<F = NoFilter, S = NoSort> {
168    filter: QueryPolicy<F>,
169    sort: QueryPolicy<S>,
170    filter_config: TreeFilterConfig,
171    root_visibility: TreeRootVisibility,
172    selection_fallback: TreeSelectionFallback,
173}
174
175impl TreeQuery {
176    /// Creates a query without filtering or sorting.
177    #[must_use]
178    pub const fn new() -> Self {
179        Self {
180            filter: QueryPolicy::new(NoFilter, TreeRevision::INITIAL),
181            sort: QueryPolicy::new(NoSort, TreeRevision::INITIAL),
182            filter_config: TreeFilterConfig::Disabled,
183            root_visibility: TreeRootVisibility::Visible,
184            selection_fallback: TreeSelectionFallback::ParentThenNearest,
185        }
186    }
187}
188
189impl<F, S> TreeQuery<F, S> {
190    /// Sets the filter and its current revision.
191    #[must_use]
192    pub fn with_filter<NF>(
193        self,
194        filter: NF,
195        config: TreeFilterConfig,
196        revision: TreeRevision,
197    ) -> TreeQuery<NF, S> {
198        TreeQuery {
199            filter: QueryPolicy::replacement(filter, revision),
200            sort: self.sort,
201            filter_config: config,
202            root_visibility: self.root_visibility,
203            selection_fallback: self.selection_fallback,
204        }
205    }
206
207    /// Sets the sorting policy and its current revision.
208    #[must_use]
209    pub fn with_sort<NS>(self, sort: NS, revision: TreeRevision) -> TreeQuery<F, NS> {
210        TreeQuery {
211            filter: self.filter,
212            sort: QueryPolicy::replacement(sort, revision),
213            filter_config: self.filter_config,
214            root_visibility: self.root_visibility,
215            selection_fallback: self.selection_fallback,
216        }
217    }
218
219    /// Sets the filtering mode while preserving the filter itself.
220    #[must_use]
221    pub const fn with_filter_config(mut self, config: TreeFilterConfig) -> Self {
222        self.filter_config = config;
223        self
224    }
225
226    /// Sets how roots are displayed.
227    #[must_use]
228    pub const fn with_root_visibility(mut self, visibility: TreeRootVisibility) -> Self {
229        self.root_visibility = visibility;
230        self
231    }
232
233    /// Sets the selection fallback policy.
234    #[must_use]
235    pub const fn with_selection_fallback(mut self, fallback: TreeSelectionFallback) -> Self {
236        self.selection_fallback = fallback;
237        self
238    }
239
240    /// Returns the filter policy.
241    #[must_use]
242    pub const fn filter(&self) -> &F {
243        &self.filter.value
244    }
245
246    /// Returns the mutable filter and automatically advances its revision.
247    pub const fn filter_mut(&mut self) -> &mut F {
248        self.filter.value_mut()
249    }
250
251    /// Returns the sibling sorting policy.
252    #[must_use]
253    pub const fn sort(&self) -> &S {
254        &self.sort.value
255    }
256
257    /// Returns the mutable sorting policy and automatically advances its revision.
258    pub const fn sort_mut(&mut self) -> &mut S {
259        self.sort.value_mut()
260    }
261
262    /// Explicitly advances the filter revision, for example after captured data changes.
263    pub const fn touch_filter(&mut self) {
264        self.filter.touch();
265    }
266
267    /// Explicitly advances the sort revision, for example after captured data changes.
268    pub const fn touch_sort(&mut self) {
269        self.sort.touch();
270    }
271
272    /// Changes the filtering mode while preserving the filter itself.
273    pub fn set_filter_config(&mut self, config: TreeFilterConfig) -> bool {
274        if self.filter_config == config {
275            return false;
276        }
277        self.filter_config = config;
278        true
279    }
280
281    /// Changes how roots are displayed.
282    pub fn set_root_visibility(&mut self, visibility: TreeRootVisibility) -> bool {
283        let changed = self.root_visibility != visibility;
284        self.root_visibility = visibility;
285        changed
286    }
287
288    /// Changes the selection fallback policy.
289    pub fn set_selection_fallback(&mut self, fallback: TreeSelectionFallback) -> bool {
290        let changed = self.selection_fallback != fallback;
291        self.selection_fallback = fallback;
292        changed
293    }
294
295    /// Returns the current filtering mode.
296    #[must_use]
297    pub const fn filter_config(&self) -> TreeFilterConfig {
298        self.filter_config
299    }
300
301    /// Returns the current root display mode.
302    #[must_use]
303    pub const fn root_visibility(&self) -> TreeRootVisibility {
304        self.root_visibility
305    }
306
307    /// Returns the current selection fallback policy.
308    #[must_use]
309    pub const fn selection_fallback(&self) -> TreeSelectionFallback {
310        self.selection_fallback
311    }
312
313    /// Returns the current filter-data revision.
314    #[must_use]
315    pub const fn filter_revision(&self) -> TreeRevision {
316        self.filter.revision
317    }
318
319    /// Returns the current sort-data revision.
320    #[must_use]
321    pub const fn sort_revision(&self) -> TreeRevision {
322        self.sort.revision
323    }
324
325    pub(crate) const fn filter_generation(&self) -> TreeRevision {
326        self.filter.generation
327    }
328
329    pub(crate) const fn sort_generation(&self) -> TreeRevision {
330        self.sort.generation
331    }
332}
333
334impl Default for TreeQuery {
335    fn default() -> Self {
336        Self::new()
337    }
338}
339#[derive(Clone, Debug)]
340struct QueryPolicy<P> {
341    value: P,
342    revision: TreeRevision,
343    generation: TreeRevision,
344}
345
346impl<P> QueryPolicy<P> {
347    const fn new(value: P, revision: TreeRevision) -> Self {
348        Self {
349            value,
350            revision,
351            generation: TreeRevision::INITIAL,
352        }
353    }
354
355    fn replacement(value: P, revision: TreeRevision) -> Self {
356        Self {
357            value,
358            revision,
359            generation: next_query_policy_generation(),
360        }
361    }
362
363    const fn value_mut(&mut self) -> &mut P {
364        self.revision.advance();
365        &mut self.value
366    }
367
368    const fn touch(&mut self) {
369        self.revision.advance();
370    }
371}
372
373/// Минимальный контракт источника дерева, леса или корневого ациклического графа.
374///
375/// Общие дочерние вершины допустимы и создают отдельные вхождения видимых строк. Циклы, повторные
376/// корни и повторные идентификаторы в одном списке детей недопустимы. Идентификаторы должны быть
377/// стабильными и дешёвыми для копирования. Каждый идентификатор из `roots` или `children` должен
378/// оставаться корректным для последующих вызовов методов модели.
379pub trait TreeModel {
380    /// The node identifier type.
381    type Id: Copy + Eq + Hash;
382
383    /// Returns forest roots in deterministic order.
384    fn roots(&self) -> impl Iterator<Item = Self::Id> + '_;
385
386    /// Returns the node's child state and loaded children.
387    fn children(&self, id: Self::Id) -> TreeChildren<'_, Self::Id>;
388
389    /// Returns the revision of the model structure and display data.
390    fn revision(&self) -> TreeRevision;
391
392    /// Returns an approximate number of available nodes.
393    fn size_hint(&self) -> usize {
394        0
395    }
396}
397
398/// A node visibility filter.
399pub trait TreeFilter<T: TreeModel> {
400    /// Returns `true` when the node directly matches the filter.
401    fn is_match(&self, model: &T, id: T::Id) -> bool;
402}
403
404impl<T, F> TreeFilter<T> for F
405where
406    T: TreeModel,
407    F: Fn(&T, T::Id) -> bool,
408{
409    #[inline]
410    fn is_match(&self, model: &T, id: T::Id) -> bool {
411        self(model, id)
412    }
413}
414
415/// A policy for sorting sibling nodes.
416pub trait TreeSort<T: TreeModel> {
417    /// Compares two sibling nodes.
418    fn compare(&self, model: &T, left: T::Id, right: T::Id) -> Ordering;
419
420    /// Returns `true` when sorting should be applied.
421    fn is_enabled(&self) -> bool {
422        true
423    }
424}
425
426impl<T, F> TreeSort<T> for F
427where
428    T: TreeModel,
429    F: Fn(&T, T::Id, T::Id) -> Ordering,
430{
431    fn compare(&self, model: &T, left: T::Id, right: T::Id) -> Ordering {
432        self(model, left, right)
433    }
434}
435
436fn next_query_policy_generation() -> TreeRevision {
437    TreeRevision::new(NEXT_QUERY_POLICY_GENERATION.fetch_add(1, AtomicOrdering::Relaxed))
438}