Skip to main content

ferroui_dynamic_data/binding/
sort_expression_comparer.rs

1//! `SortExpressionComparer<T>` and `SortExpression<T>` (`Binding/SortExpressionComparer.cs`,
2//! `Binding/SortExpression.cs`).
3
4use super::SortDirection;
5use std::cmp::Ordering;
6use std::fmt;
7use std::rc::Rc;
8
9/// One key of a sort: a value read from each item, and the direction (`SortExpression<T>`).
10///
11/// Deviation (DEVIATIONS.md, DynamicData): upstream holds the getter `Func<T, IComparable>`; Rust
12/// has no common type of the values of different getters, so the expression is held as the
13/// comparison of the values it reads. The values need `PartialOrd`; `Option` puts `None` first,
14/// as upstream puts `null` first, and two values without an order (a `NaN`) compare as equal.
15pub struct SortExpression<T> {
16    direction: SortDirection,
17    expression: Rc<dyn Fn(&T, &T) -> Ordering>,
18}
19
20impl<T> Clone for SortExpression<T> {
21    fn clone(&self) -> Self {
22        Self { direction: self.direction, expression: self.expression.clone() }
23    }
24}
25
26impl<T> fmt::Debug for SortExpression<T> {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        f.debug_struct("SortExpression").field("direction", &self.direction).finish_non_exhaustive()
29    }
30}
31
32impl<T: 'static> SortExpression<T> {
33    /// The key `expression` in `direction` (`SortExpression(Func<T, IComparable>, SortDirection)`).
34    pub fn new<V: PartialOrd + 'static>(expression: impl Fn(&T) -> V + 'static, direction: SortDirection) -> Self {
35        Self { direction, expression: Rc::new(move |x: &T, y: &T| expression(x).partial_cmp(&expression(y)).unwrap_or(Ordering::Equal)) }
36    }
37
38    /// The key `expression`, ascending (`SortExpression(Func<T, IComparable>)`).
39    pub fn ascending<V: PartialOrd + 'static>(expression: impl Fn(&T) -> V + 'static) -> Self {
40        Self::new(expression, SortDirection::Ascending)
41    }
42}
43
44impl<T> SortExpression<T> {
45    /// The direction (`Direction`).
46    pub fn direction(&self) -> SortDirection {
47        self.direction
48    }
49
50    /// The comparison of the values that the expression reads from `x` and `y`, ascending
51    /// (`Expression(x).CompareTo(Expression(y))`).
52    pub fn compare_values(&self, x: &T, y: &T) -> Ordering {
53        (self.expression)(x, y)
54    }
55}
56
57/// A comparer of items by a list of keys, each ascending or descending
58/// (`SortExpressionComparer<T>`): the first key that differs decides.
59///
60/// It builds as upstream's does, `SortExpressionComparer::ascending(|p: &Person| p.age())
61/// .then_by_ascending(|p| p.name())`, and becomes the comparer that the operators take,
62/// `Rc<dyn Fn(&T, &T) -> Ordering>`, with [`comparer`](Self::comparer) or `into()`.
63pub struct SortExpressionComparer<T> {
64    expressions: Vec<SortExpression<T>>,
65}
66
67impl<T> Clone for SortExpressionComparer<T> {
68    fn clone(&self) -> Self {
69        Self { expressions: self.expressions.clone() }
70    }
71}
72
73impl<T> Default for SortExpressionComparer<T> {
74    /// A comparer without keys, which finds every pair equal (`new SortExpressionComparer<T>()`).
75    fn default() -> Self {
76        Self { expressions: Vec::new() }
77    }
78}
79
80impl<T> fmt::Debug for SortExpressionComparer<T> {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        f.debug_list().entries(self.expressions.iter()).finish()
83    }
84}
85
86impl<T: 'static> SortExpressionComparer<T> {
87    /// A comparer by `expression`, ascending (`SortExpressionComparer<T>.Ascending`).
88    pub fn ascending<V: PartialOrd + 'static>(expression: impl Fn(&T) -> V + 'static) -> Self {
89        Self { expressions: vec![SortExpression::new(expression, SortDirection::Ascending)] }
90    }
91
92    /// A comparer by `expression`, descending (`SortExpressionComparer<T>.Descending`).
93    pub fn descending<V: PartialOrd + 'static>(expression: impl Fn(&T) -> V + 'static) -> Self {
94        Self { expressions: vec![SortExpression::new(expression, SortDirection::Descending)] }
95    }
96
97    /// The comparer, then by `expression`, ascending (`ThenByAscending`).
98    pub fn then_by_ascending<V: PartialOrd + 'static>(mut self, expression: impl Fn(&T) -> V + 'static) -> Self {
99        self.expressions.push(SortExpression::new(expression, SortDirection::Ascending));
100        self
101    }
102
103    /// The comparer, then by `expression`, descending (`ThenByDescending`).
104    pub fn then_by_descending<V: PartialOrd + 'static>(mut self, expression: impl Fn(&T) -> V + 'static) -> Self {
105        self.expressions.push(SortExpression::new(expression, SortDirection::Descending));
106        self
107    }
108
109    /// The comparer as the function that the operators take (`IComparer<T>`).
110    pub fn comparer(&self) -> Rc<dyn Fn(&T, &T) -> Ordering> {
111        let comparer = self.clone();
112        Rc::new(move |x: &T, y: &T| comparer.compare(x, y))
113    }
114}
115
116impl<T> SortExpressionComparer<T> {
117    /// Adds a key (`Add`, of the list upstream's comparer is).
118    pub fn push(&mut self, expression: SortExpression<T>) {
119        self.expressions.push(expression);
120    }
121
122    /// The keys, in order.
123    pub fn expressions(&self) -> &[SortExpression<T>] {
124        &self.expressions
125    }
126
127    /// Compares `x` and `y` by the keys in order (`Compare`).
128    pub fn compare(&self, x: &T, y: &T) -> Ordering {
129        for item in &self.expressions {
130            let result = item.compare_values(x, y);
131            if result == Ordering::Equal {
132                continue;
133            }
134            return match item.direction() {
135                SortDirection::Ascending => result,
136                SortDirection::Descending => result.reverse(),
137            };
138        }
139        Ordering::Equal
140    }
141}
142
143impl<T: 'static> From<SortExpressionComparer<T>> for Rc<dyn Fn(&T, &T) -> Ordering> {
144    fn from(comparer: SortExpressionComparer<T>) -> Self {
145        Rc::new(move |x: &T, y: &T| comparer.compare(x, y))
146    }
147}