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 ui.send_message(CheckBoxMessage::checked(
75 self.handle(),
76 MessageDirection::ToWidget,
77 Some(!value),
78 ));
79 } else {
80 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 let input_message =
309 CheckBoxMessage::checked(check_box, MessageDirection::ToWidget, Some(true));
310
311 ui.send_message(input_message.clone());
312
313 assert_eq!(ui.poll_message(), Some(input_message.clone()));
315 assert_eq!(ui.poll_message(), Some(input_message.reverse()));
317 }
318}