rg3d_ui/
check_box.rs

1use crate::{
2    border::BorderBuilder,
3    brush::Brush,
4    core::{color::Color, pool::Handle},
5    define_constructor,
6    grid::{Column, GridBuilder, Row},
7    message::{MessageDirection, UiMessage},
8    vector_image::{Primitive, VectorImageBuilder},
9    widget::{Widget, WidgetBuilder, WidgetMessage},
10    BuildContext, Control, HorizontalAlignment, NodeHandleMapping, Thickness, UiNode,
11    UserInterface, VerticalAlignment, BRUSH_BRIGHT, BRUSH_DARK, BRUSH_LIGHT, BRUSH_TEXT,
12};
13use rg3d_core::algebra::Vector2;
14use std::{
15    any::{Any, TypeId},
16    ops::{Deref, DerefMut},
17};
18
19#[derive(Debug, Clone, PartialEq)]
20pub enum CheckBoxMessage {
21    Check(Option<bool>),
22}
23
24impl CheckBoxMessage {
25    define_constructor!(CheckBoxMessage:Check => fn checked(Option<bool>), layout: false);
26}
27
28#[derive(Clone)]
29pub struct CheckBox {
30    pub widget: Widget,
31    pub checked: Option<bool>,
32    pub check_mark: Handle<UiNode>,
33    pub uncheck_mark: Handle<UiNode>,
34    pub undefined_mark: Handle<UiNode>,
35}
36
37crate::define_widget_deref!(CheckBox);
38
39impl Control for CheckBox {
40    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
41        if type_id == TypeId::of::<Self>() {
42            Some(self)
43        } else {
44            None
45        }
46    }
47
48    fn resolve(&mut self, node_map: &NodeHandleMapping) {
49        node_map.resolve(&mut self.check_mark);
50        node_map.resolve(&mut self.uncheck_mark);
51        node_map.resolve(&mut self.undefined_mark);
52    }
53
54    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
55        self.widget.handle_routed_message(ui, message);
56
57        if let Some(msg) = message.data::<WidgetMessage>() {
58            match msg {
59                WidgetMessage::MouseDown { .. } => {
60                    if message.destination() == self.handle()
61                        || self.widget.has_descendant(message.destination(), ui)
62                    {
63                        ui.capture_mouse(self.handle());
64                    }
65                }
66                WidgetMessage::MouseUp { .. } => {
67                    if message.destination() == self.handle()
68                        || self.widget.has_descendant(message.destination(), ui)
69                    {
70                        ui.release_mouse_capture();
71
72                        if let Some(value) = self.checked {
73                            // Invert state if it is defined.
74                            ui.send_message(CheckBoxMessage::checked(
75                                self.handle(),
76                                MessageDirection::ToWidget,
77                                Some(!value),
78                            ));
79                        } else {
80                            // Switch from undefined state to checked.
81                            ui.send_message(CheckBoxMessage::checked(
82                                self.handle(),
83                                MessageDirection::ToWidget,
84                                Some(true),
85                            ));
86                        }
87                    }
88                }
89                _ => (),
90            }
91        } else if let Some(&CheckBoxMessage::Check(value)) = message.data::<CheckBoxMessage>() {
92            if message.direction() == MessageDirection::ToWidget
93                && message.destination() == self.handle()
94                && self.checked != value
95            {
96                self.checked = value;
97
98                ui.send_message(message.reverse());
99
100                if self.check_mark.is_some() {
101                    match value {
102                        None => {
103                            ui.send_message(WidgetMessage::visibility(
104                                self.check_mark,
105                                MessageDirection::ToWidget,
106                                false,
107                            ));
108                            ui.send_message(WidgetMessage::visibility(
109                                self.uncheck_mark,
110                                MessageDirection::ToWidget,
111                                false,
112                            ));
113                            ui.send_message(WidgetMessage::visibility(
114                                self.undefined_mark,
115                                MessageDirection::ToWidget,
116                                true,
117                            ));
118                        }
119                        Some(value) => {
120                            ui.send_message(WidgetMessage::visibility(
121                                self.check_mark,
122                                MessageDirection::ToWidget,
123                                value,
124                            ));
125                            ui.send_message(WidgetMessage::visibility(
126                                self.uncheck_mark,
127                                MessageDirection::ToWidget,
128                                !value,
129                            ));
130                            ui.send_message(WidgetMessage::visibility(
131                                self.undefined_mark,
132                                MessageDirection::ToWidget,
133                                false,
134                            ));
135                        }
136                    }
137                }
138            }
139        }
140    }
141}
142
143pub struct CheckBoxBuilder {
144    widget_builder: WidgetBuilder,
145    checked: Option<bool>,
146    check_mark: Option<Handle<UiNode>>,
147    uncheck_mark: Option<Handle<UiNode>>,
148    undefined_mark: Option<Handle<UiNode>>,
149    background: Option<Handle<UiNode>>,
150    content: Handle<UiNode>,
151}
152
153impl CheckBoxBuilder {
154    pub fn new(widget_builder: WidgetBuilder) -> Self {
155        Self {
156            widget_builder,
157            checked: Some(false),
158            check_mark: None,
159            uncheck_mark: None,
160            undefined_mark: None,
161            content: Handle::NONE,
162            background: None,
163        }
164    }
165
166    pub fn checked(mut self, value: Option<bool>) -> Self {
167        self.checked = value;
168        self
169    }
170
171    pub fn with_check_mark(mut self, check_mark: Handle<UiNode>) -> Self {
172        self.check_mark = Some(check_mark);
173        self
174    }
175
176    pub fn with_uncheck_mark(mut self, uncheck_mark: Handle<UiNode>) -> Self {
177        self.uncheck_mark = Some(uncheck_mark);
178        self
179    }
180
181    pub fn with_undefined_mark(mut self, undefined_mark: Handle<UiNode>) -> Self {
182        self.undefined_mark = Some(undefined_mark);
183        self
184    }
185
186    pub fn with_content(mut self, content: Handle<UiNode>) -> Self {
187        self.content = content;
188        self
189    }
190
191    pub fn with_background(mut self, background: Handle<UiNode>) -> Self {
192        self.background = Some(background);
193        self
194    }
195
196    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
197        let check_mark = self.check_mark.unwrap_or_else(|| {
198            VectorImageBuilder::new(
199                WidgetBuilder::new()
200                    .with_vertical_alignment(VerticalAlignment::Center)
201                    .with_horizontal_alignment(HorizontalAlignment::Center)
202                    .with_foreground(BRUSH_TEXT),
203            )
204            .with_primitives(vec![
205                Primitive::Line {
206                    begin: Vector2::new(0.0, 6.0),
207                    end: Vector2::new(6.0, 12.0),
208                    thickness: 2.0,
209                },
210                Primitive::Line {
211                    begin: Vector2::new(6.0, 12.0),
212                    end: Vector2::new(12.0, 0.0),
213                    thickness: 2.0,
214                },
215            ])
216            .build(ctx)
217        });
218        ctx[check_mark].set_visibility(self.checked.unwrap_or(false));
219
220        let uncheck_mark = self.uncheck_mark.unwrap_or_else(|| {
221            BorderBuilder::new(
222                WidgetBuilder::new()
223                    .with_background(Brush::Solid(Color::TRANSPARENT))
224                    .with_foreground(Brush::Solid(Color::TRANSPARENT)),
225            )
226            .build(ctx)
227        });
228        ctx[uncheck_mark].set_visibility(!self.checked.unwrap_or(true));
229
230        let undefined_mark = self.undefined_mark.unwrap_or_else(|| {
231            BorderBuilder::new(
232                WidgetBuilder::new()
233                    .with_margin(Thickness::uniform(1.0))
234                    .with_background(BRUSH_BRIGHT)
235                    .with_foreground(Brush::Solid(Color::TRANSPARENT)),
236            )
237            .build(ctx)
238        });
239        ctx[undefined_mark].set_visibility(self.checked.is_none());
240
241        if self.content.is_some() {
242            ctx[self.content].set_row(0).set_column(1);
243        }
244
245        let background = self.background.unwrap_or_else(|| {
246            BorderBuilder::new(
247                WidgetBuilder::new()
248                    .with_background(BRUSH_DARK)
249                    .with_foreground(BRUSH_LIGHT),
250            )
251            .with_stroke_thickness(Thickness::uniform(1.0))
252            .build(ctx)
253        });
254
255        let background_ref = &mut ctx[background];
256        background_ref.set_row(0).set_column(0);
257        if background_ref.min_width() < 0.01 {
258            background_ref.set_min_width(16.0);
259        }
260        if background_ref.min_height() < 0.01 {
261            background_ref.set_min_height(16.0);
262        }
263
264        ctx.link(check_mark, background);
265        ctx.link(uncheck_mark, background);
266        ctx.link(undefined_mark, background);
267
268        let grid = GridBuilder::new(
269            WidgetBuilder::new()
270                .with_child(background)
271                .with_child(self.content),
272        )
273        .add_row(Row::stretch())
274        .add_column(Column::auto())
275        .add_column(Column::auto())
276        .build(ctx);
277
278        let cb = CheckBox {
279            widget: self.widget_builder.with_child(grid).build(),
280            checked: self.checked,
281            check_mark,
282            uncheck_mark,
283            undefined_mark,
284        };
285        ctx.add_node(UiNode::new(cb))
286    }
287}
288
289#[cfg(test)]
290mod test {
291    use crate::check_box::CheckBoxMessage;
292    use crate::{
293        check_box::CheckBoxBuilder, core::algebra::Vector2, message::MessageDirection,
294        widget::WidgetBuilder, UserInterface,
295    };
296
297    #[test]
298    fn check_box() {
299        let mut ui = UserInterface::new(Vector2::new(1000.0, 1000.0));
300
301        assert_eq!(ui.poll_message(), None);
302
303        let check_box = CheckBoxBuilder::new(WidgetBuilder::new()).build(&mut ui.build_ctx());
304
305        assert_eq!(ui.poll_message(), None);
306
307        // Check messages
308        let input_message =
309            CheckBoxMessage::checked(check_box, MessageDirection::ToWidget, Some(true));
310
311        ui.send_message(input_message.clone());
312
313        // This message that we just send.
314        assert_eq!(ui.poll_message(), Some(input_message.clone()));
315        // We must get response from check box.
316        assert_eq!(ui.poll_message(), Some(input_message.reverse()));
317    }
318}