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;
33use std::ops::{Deref, DerefMut};
34
35fn make_numeric_input<T: NumericType>(
36    ctx: &mut BuildContext,
37    column: usize,
38    row: usize,
39    value: T,
40    min: T,
41    max: T,
42    step: T,
43    editable: bool,
44    precision: usize,
45) -> Handle<UiNode> {
46    NumericUpDownBuilder::new(
47        WidgetBuilder::new()
48            .on_row(row)
49            .on_column(column)
50            .with_margin(Thickness {
51                left: 1.0,
52                top: 0.0,
53                right: 1.0,
54                bottom: 0.0,
55            }),
56    )
57    .with_precision(precision)
58    .with_value(value)
59    .with_min_value(min)
60    .with_max_value(max)
61    .with_step(step)
62    .with_editable(editable)
63    .build(ctx)
64}
65
66#[derive(Debug, Clone, PartialEq)]
67pub enum MatrixEditorMessage<const R: usize, const C: usize, T>
68where
69    T: NumericType,
70{
71    Value(SMatrix<T, R, C>),
72}
73
74impl<const R: usize, const C: usize, T> MatrixEditorMessage<R, C, T>
75where
76    T: NumericType,
77{
78    define_constructor!(MatrixEditorMessage:Value => fn value(SMatrix<T, R, C>), layout: false);
79}
80
81#[derive(Clone, Visit, Reflect, Debug, ComponentProvider)]
82pub struct MatrixEditor<const R: usize, const C: usize, T>
83where
84    T: NumericType,
85{
86    pub widget: Widget,
87    pub fields: Vec<Handle<UiNode>>,
88    #[reflect(hidden)]
89    #[visit(skip)]
90    pub value: SMatrix<T, R, C>,
91    #[reflect(hidden)]
92    #[visit(skip)]
93    pub min: SMatrix<T, R, C>,
94    #[reflect(hidden)]
95    #[visit(skip)]
96    pub max: SMatrix<T, R, C>,
97    #[reflect(hidden)]
98    #[visit(skip)]
99    pub step: SMatrix<T, R, C>,
100}
101
102impl<const R: usize, const C: usize, T> Default for MatrixEditor<R, C, T>
103where
104    T: NumericType,
105{
106    fn default() -> Self {
107        Self {
108            widget: Default::default(),
109            fields: Default::default(),
110            value: SMatrix::repeat(T::zero()),
111            min: SMatrix::repeat(T::min_value()),
112            max: SMatrix::repeat(T::max_value()),
113            step: SMatrix::repeat(T::one()),
114        }
115    }
116}
117
118impl<const R: usize, const C: usize, T> Deref for MatrixEditor<R, C, T>
119where
120    T: NumericType,
121{
122    type Target = Widget;
123
124    fn deref(&self) -> &Self::Target {
125        &self.widget
126    }
127}
128
129impl<const R: usize, const C: usize, T> DerefMut for MatrixEditor<R, C, T>
130where
131    T: NumericType,
132{
133    fn deref_mut(&mut self) -> &mut Self::Target {
134        &mut self.widget
135    }
136}
137
138impl<const R: usize, const C: usize, T: NumericType> TypeUuidProvider for MatrixEditor<R, C, T> {
139    fn type_uuid() -> Uuid {
140        let r_id = Uuid::from_u64_pair(R as u64, R as u64);
141        let c_id = Uuid::from_u64_pair(C as u64, C as u64);
142        combine_uuids(
143            c_id,
144            combine_uuids(
145                r_id,
146                combine_uuids(
147                    uuid!("9f05427a-5862-4574-bb21-ebaf52aa8c72"),
148                    T::type_uuid(),
149                ),
150            ),
151        )
152    }
153}
154
155impl<const R: usize, const C: usize, T> Control for MatrixEditor<R, C, T>
156where
157    T: NumericType,
158{
159    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
160        self.widget.handle_routed_message(ui, message);
161
162        if let Some(&NumericUpDownMessage::Value(value)) = message.data::<NumericUpDownMessage<T>>()
163        {
164            if message.direction() == MessageDirection::FromWidget {
165                for (i, field) in self.fields.iter().enumerate() {
166                    if message.destination() == *field {
167                        let mut new_value = self.value;
168                        new_value[i] = value;
169                        ui.send_message(MatrixEditorMessage::value(
170                            self.handle(),
171                            MessageDirection::ToWidget,
172                            new_value,
173                        ));
174                    }
175                }
176            }
177        } else if let Some(&MatrixEditorMessage::Value(new_value)) =
178            message.data::<MatrixEditorMessage<R, C, T>>()
179        {
180            if message.direction() == MessageDirection::ToWidget {
181                let mut changed = false;
182
183                for i in 0..self.fields.len() {
184                    let editor = self.fields[i];
185                    let current = &mut self.value[i];
186                    let min = self.min[i];
187                    let max = self.max[i];
188                    let new = num_traits::clamp(new_value[i], min, max);
189
190                    if *current != new {
191                        *current = new;
192                        ui.send_message(NumericUpDownMessage::value(
193                            editor,
194                            MessageDirection::ToWidget,
195                            new,
196                        ));
197                        changed = true;
198                    }
199                }
200
201                if changed {
202                    ui.send_message(message.reverse());
203                }
204            }
205        }
206    }
207}
208
209pub struct MatrixEditorBuilder<const R: usize, const C: usize, T>
210where
211    T: NumericType,
212{
213    widget_builder: WidgetBuilder,
214    value: SMatrix<T, R, C>,
215    editable: bool,
216    min: SMatrix<T, R, C>,
217    max: SMatrix<T, R, C>,
218    step: SMatrix<T, R, C>,
219    precision: usize,
220}
221
222impl<const R: usize, const C: usize, T> MatrixEditorBuilder<R, C, T>
223where
224    T: NumericType,
225{
226    pub fn new(widget_builder: WidgetBuilder) -> Self {
227        Self {
228            widget_builder,
229            value: SMatrix::identity(),
230            editable: true,
231            min: SMatrix::repeat(T::min_value()),
232            max: SMatrix::repeat(T::max_value()),
233            step: SMatrix::repeat(T::one()),
234            precision: 3,
235        }
236    }
237
238    pub fn with_value(mut self, value: SMatrix<T, R, C>) -> Self {
239        self.value = value;
240        self
241    }
242
243    pub fn with_editable(mut self, editable: bool) -> Self {
244        self.editable = editable;
245        self
246    }
247
248    pub fn with_min(mut self, min: SMatrix<T, R, C>) -> Self {
249        self.min = min;
250        self
251    }
252
253    pub fn with_max(mut self, max: SMatrix<T, R, C>) -> Self {
254        self.max = max;
255        self
256    }
257
258    pub fn with_step(mut self, step: SMatrix<T, R, C>) -> Self {
259        self.step = step;
260        self
261    }
262
263    pub fn with_precision(mut self, precision: usize) -> Self {
264        self.precision = precision;
265        self
266    }
267
268    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
269        let mut fields = Vec::new();
270        let mut children = Vec::new();
271
272        for row in 0..R {
273            for column in 0..C {
274                let field = make_numeric_input(
275                    ctx,
276                    column,
277                    row,
278                    self.value[(row, column)],
279                    self.min[(row, column)],
280                    self.max[(row, column)],
281                    self.step[(row, column)],
282                    self.editable,
283                    self.precision,
284                );
285                children.push(field);
286                fields.push(field);
287            }
288        }
289
290        let grid = GridBuilder::new(WidgetBuilder::new().with_children(children))
291            .add_rows(vec![Row::stretch(); R])
292            .add_columns(vec![Column::stretch(); C])
293            .build(ctx);
294
295        let node = MatrixEditor {
296            widget: self.widget_builder.with_child(grid).build(ctx),
297            fields,
298            value: self.value,
299            min: self.min,
300            max: self.max,
301            step: self.step,
302        };
303
304        ctx.add_node(UiNode::new(node))
305    }
306}
307
308#[cfg(test)]
309mod test {
310    use crate::matrix::MatrixEditorBuilder;
311    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
312
313    #[test]
314    fn test_deletion() {
315        test_widget_deletion(|ctx| {
316            MatrixEditorBuilder::<2, 2, f32>::new(WidgetBuilder::new()).build(ctx)
317        });
318    }
319}