fyrox_ui/
matrix.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use crate::{
22    core::{
23        num_traits, pool::Handle, reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*,
24    },
25    define_constructor,
26    grid::{Column, GridBuilder, Row},
27    message::{MessageDirection, UiMessage},
28    numeric::{NumericType, NumericUpDownBuilder, NumericUpDownMessage},
29    widget::WidgetBuilder,
30    BuildContext, Control, Thickness, UiNode, UserInterface, Widget,
31};
32use fyrox_core::algebra::SMatrix;
33
34use std::ops::{Deref, DerefMut};
35
36fn make_numeric_input<T: NumericType>(
37    ctx: &mut BuildContext,
38    column: usize,
39    row: usize,
40    value: T,
41    min: T,
42    max: T,
43    step: T,
44    editable: bool,
45    precision: usize,
46) -> Handle<UiNode> {
47    NumericUpDownBuilder::new(
48        WidgetBuilder::new()
49            .on_row(row)
50            .on_column(column)
51            .with_margin(Thickness {
52                left: 1.0,
53                top: 0.0,
54                right: 1.0,
55                bottom: 0.0,
56            }),
57    )
58    .with_precision(precision)
59    .with_value(value)
60    .with_min_value(min)
61    .with_max_value(max)
62    .with_step(step)
63    .with_editable(editable)
64    .build(ctx)
65}
66
67#[derive(Debug, Clone, PartialEq)]
68pub enum MatrixEditorMessage<const R: usize, const C: usize, T>
69where
70    T: NumericType,
71{
72    Value(SMatrix<T, R, C>),
73}
74
75impl<const R: usize, const C: usize, T> MatrixEditorMessage<R, C, T>
76where
77    T: NumericType,
78{
79    define_constructor!(MatrixEditorMessage:Value => fn value(SMatrix<T, R, C>), layout: false);
80}
81
82#[derive(Clone, Visit, Reflect, Debug, ComponentProvider)]
83#[reflect(derived_type = "UiNode")]
84pub struct MatrixEditor<const R: usize, const C: usize, T>
85where
86    T: NumericType,
87{
88    pub widget: Widget,
89    pub fields: Vec<Handle<UiNode>>,
90    #[reflect(hidden)]
91    #[visit(skip)]
92    pub value: SMatrix<T, R, C>,
93    #[reflect(hidden)]
94    #[visit(skip)]
95    pub min: SMatrix<T, R, C>,
96    #[reflect(hidden)]
97    #[visit(skip)]
98    pub max: SMatrix<T, R, C>,
99    #[reflect(hidden)]
100    #[visit(skip)]
101    pub step: SMatrix<T, R, C>,
102}
103
104impl<const R: usize, const C: usize, T> Default for MatrixEditor<R, C, T>
105where
106    T: NumericType,
107{
108    fn default() -> Self {
109        Self {
110            widget: Default::default(),
111            fields: Default::default(),
112            value: SMatrix::repeat(T::zero()),
113            min: SMatrix::repeat(T::min_value()),
114            max: SMatrix::repeat(T::max_value()),
115            step: SMatrix::repeat(T::one()),
116        }
117    }
118}
119
120impl<const R: usize, const C: usize, T> Deref for MatrixEditor<R, C, T>
121where
122    T: NumericType,
123{
124    type Target = Widget;
125
126    fn deref(&self) -> &Self::Target {
127        &self.widget
128    }
129}
130
131impl<const R: usize, const C: usize, T> DerefMut for MatrixEditor<R, C, T>
132where
133    T: NumericType,
134{
135    fn deref_mut(&mut self) -> &mut Self::Target {
136        &mut self.widget
137    }
138}
139
140impl<const R: usize, const C: usize, T: NumericType> TypeUuidProvider for MatrixEditor<R, C, T> {
141    fn type_uuid() -> Uuid {
142        let r_id = Uuid::from_u64_pair(R as u64, R as u64);
143        let c_id = Uuid::from_u64_pair(C as u64, C as u64);
144        combine_uuids(
145            c_id,
146            combine_uuids(
147                r_id,
148                combine_uuids(
149                    uuid!("9f05427a-5862-4574-bb21-ebaf52aa8c72"),
150                    T::type_uuid(),
151                ),
152            ),
153        )
154    }
155}
156
157impl<const R: usize, const C: usize, T> Control for MatrixEditor<R, C, T>
158where
159    T: NumericType,
160{
161    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
162        self.widget.handle_routed_message(ui, message);
163
164        if let Some(&NumericUpDownMessage::Value(value)) = message.data::<NumericUpDownMessage<T>>()
165        {
166            if message.direction() == MessageDirection::FromWidget {
167                for (i, field) in self.fields.iter().enumerate() {
168                    if message.destination() == *field {
169                        let mut new_value = self.value;
170                        new_value[i] = value;
171                        ui.send_message(MatrixEditorMessage::value(
172                            self.handle(),
173                            MessageDirection::ToWidget,
174                            new_value,
175                        ));
176                    }
177                }
178            }
179        } else if let Some(&MatrixEditorMessage::Value(new_value)) =
180            message.data::<MatrixEditorMessage<R, C, T>>()
181        {
182            if message.direction() == MessageDirection::ToWidget {
183                let mut changed = false;
184
185                for i in 0..self.fields.len() {
186                    let editor = self.fields[i];
187                    let current = &mut self.value[i];
188                    let min = self.min[i];
189                    let max = self.max[i];
190                    let new = num_traits::clamp(new_value[i], min, max);
191
192                    if *current != new {
193                        *current = new;
194                        ui.send_message(NumericUpDownMessage::value(
195                            editor,
196                            MessageDirection::ToWidget,
197                            new,
198                        ));
199                        changed = true;
200                    }
201                }
202
203                if changed {
204                    ui.send_message(message.reverse());
205                }
206            }
207        }
208    }
209}
210
211pub struct MatrixEditorBuilder<const R: usize, const C: usize, T>
212where
213    T: NumericType,
214{
215    widget_builder: WidgetBuilder,
216    value: SMatrix<T, R, C>,
217    editable: bool,
218    min: SMatrix<T, R, C>,
219    max: SMatrix<T, R, C>,
220    step: SMatrix<T, R, C>,
221    precision: usize,
222}
223
224impl<const R: usize, const C: usize, T> MatrixEditorBuilder<R, C, T>
225where
226    T: NumericType,
227{
228    pub fn new(widget_builder: WidgetBuilder) -> Self {
229        Self {
230            widget_builder,
231            value: SMatrix::identity(),
232            editable: true,
233            min: SMatrix::repeat(T::min_value()),
234            max: SMatrix::repeat(T::max_value()),
235            step: SMatrix::repeat(T::one()),
236            precision: 3,
237        }
238    }
239
240    pub fn with_value(mut self, value: SMatrix<T, R, C>) -> Self {
241        self.value = value;
242        self
243    }
244
245    pub fn with_editable(mut self, editable: bool) -> Self {
246        self.editable = editable;
247        self
248    }
249
250    pub fn with_min(mut self, min: SMatrix<T, R, C>) -> Self {
251        self.min = min;
252        self
253    }
254
255    pub fn with_max(mut self, max: SMatrix<T, R, C>) -> Self {
256        self.max = max;
257        self
258    }
259
260    pub fn with_step(mut self, step: SMatrix<T, R, C>) -> Self {
261        self.step = step;
262        self
263    }
264
265    pub fn with_precision(mut self, precision: usize) -> Self {
266        self.precision = precision;
267        self
268    }
269
270    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
271        let mut fields = Vec::new();
272        let mut children = Vec::new();
273
274        for row in 0..R {
275            for column in 0..C {
276                let field = make_numeric_input(
277                    ctx,
278                    column,
279                    row,
280                    self.value[(row, column)],
281                    self.min[(row, column)],
282                    self.max[(row, column)],
283                    self.step[(row, column)],
284                    self.editable,
285                    self.precision,
286                );
287                children.push(field);
288                fields.push(field);
289            }
290        }
291
292        let grid = GridBuilder::new(WidgetBuilder::new().with_children(children))
293            .add_rows(vec![Row::stretch(); R])
294            .add_columns(vec![Column::stretch(); C])
295            .build(ctx);
296
297        let node = MatrixEditor {
298            widget: self.widget_builder.with_child(grid).build(ctx),
299            fields,
300            value: self.value,
301            min: self.min,
302            max: self.max,
303            step: self.step,
304        };
305
306        ctx.add_node(UiNode::new(node))
307    }
308}
309
310#[cfg(test)]
311mod test {
312    use crate::matrix::MatrixEditorBuilder;
313    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
314
315    #[test]
316    fn test_deletion() {
317        test_widget_deletion(|ctx| {
318            MatrixEditorBuilder::<2, 2, f32>::new(WidgetBuilder::new()).build(ctx)
319        });
320    }
321}