1#![warn(missing_docs)]
26
27use crate::{
28 border::BorderBuilder,
29 brush::Brush,
30 core::{
31 color::Color,
32 pool::{Handle, ObjectOrVariant},
33 reflect::prelude::*,
34 type_traits::prelude::*,
35 variable::InheritableVariable,
36 visitor::prelude::*,
37 },
38 grid::{Column, GridBuilder, Row},
39 image::ImageBuilder,
40 message::{KeyCode, MessageData, UiMessage},
41 resources,
42 style::{resource::StyleResourceExt, Style},
43 widget::{Widget, WidgetBuilder, WidgetMessage},
44 BuildContext, Control, MouseButton, Thickness, UiNode, UserInterface, VerticalAlignment,
45};
46use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
47
48#[derive(Debug, Clone, PartialEq, Eq)]
50pub enum CheckBoxMessage {
51 Check(Option<bool>),
53}
54impl MessageData for CheckBoxMessage {}
55
56#[derive(Default, Clone, Debug, Visit, Reflect, TypeUuidProvider, ComponentProvider)]
153#[type_uuid(id = "3a866ba8-7682-4ce7-954a-46360f5837dc")]
154#[reflect(derived_type = "UiNode")]
155pub struct CheckBox {
156 pub widget: Widget,
158 pub checked: InheritableVariable<Option<bool>>,
160 pub check_mark: InheritableVariable<Handle<UiNode>>,
162 pub uncheck_mark: InheritableVariable<Handle<UiNode>>,
164 pub undefined_mark: InheritableVariable<Handle<UiNode>>,
166}
167
168impl CheckBox {
169 pub const CORNER_RADIUS: &'static str = "CheckBox.CornerRadius";
171 pub const BORDER_THICKNESS: &'static str = "CheckBox.BorderThickness";
173 pub const CHECK_MARK_SIZE: &'static str = "CheckBox.CheckMarkSize";
175
176 pub fn style() -> Style {
178 Style::default()
179 .with(Self::CORNER_RADIUS, 4.0f32)
180 .with(Self::BORDER_THICKNESS, Thickness::uniform(1.0))
181 .with(Self::CHECK_MARK_SIZE, 12.0f32)
182 }
183}
184
185impl ConstructorProvider<UiNode, UserInterface> for CheckBox {
186 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
187 GraphNodeConstructor::new::<Self>()
188 .with_variant("CheckBox", |ui| {
189 CheckBoxBuilder::new(WidgetBuilder::new().with_name("CheckBox"))
190 .build(&mut ui.build_ctx())
191 .to_base()
192 .into()
193 })
194 .with_group("Input")
195 }
196}
197
198crate::define_widget_deref!(CheckBox);
199
200impl Control for CheckBox {
201 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
202 self.widget.handle_routed_message(ui, message);
203
204 if let Some(msg) = message.data::<WidgetMessage>() {
205 match msg {
206 WidgetMessage::MouseDown { button, .. } => {
207 if *button == MouseButton::Left
208 && (message.destination() == self.handle()
209 || self.widget.has_descendant(message.destination(), ui))
210 {
211 ui.capture_mouse(self.handle());
212 }
213 }
214 WidgetMessage::MouseUp { button, .. } => {
215 if *button == MouseButton::Left
216 && (message.destination() == self.handle()
217 || self.widget.has_descendant(message.destination(), ui))
218 {
219 ui.release_mouse_capture();
220
221 if let Some(value) = *self.checked {
222 ui.send(self.handle(), CheckBoxMessage::Check(Some(!value)));
224 } else {
225 ui.send(self.handle(), CheckBoxMessage::Check(Some(true)));
227 }
228 }
229 }
230 WidgetMessage::KeyDown(key_code) => {
231 if !message.handled() && *key_code == KeyCode::Space {
232 let checked = self.checked.map(|checked| !checked);
233 ui.send(self.handle, CheckBoxMessage::Check(checked));
234 message.set_handled(true);
235 }
236 }
237 _ => (),
238 }
239 } else if let Some(&CheckBoxMessage::Check(value)) = message.data_for(self.handle) {
240 if *self.checked != value {
241 self.checked.set_value_and_mark_modified(value);
242
243 ui.try_send_response(message);
244
245 if self.check_mark.is_some() {
246 match value {
247 None => {
248 ui.send(*self.check_mark, WidgetMessage::Visibility(false));
249 ui.send(*self.uncheck_mark, WidgetMessage::Visibility(false));
250 ui.send(*self.undefined_mark, WidgetMessage::Visibility(true));
251 }
252 Some(value) => {
253 ui.send(*self.check_mark, WidgetMessage::Visibility(value));
254 ui.send(*self.uncheck_mark, WidgetMessage::Visibility(!value));
255 ui.send(*self.undefined_mark, WidgetMessage::Visibility(false));
256 }
257 }
258 }
259 }
260 }
261 }
262}
263
264pub struct CheckBoxBuilder {
266 widget_builder: WidgetBuilder,
267 checked: Option<bool>,
268 check_mark: Option<Handle<UiNode>>,
269 uncheck_mark: Option<Handle<UiNode>>,
270 undefined_mark: Option<Handle<UiNode>>,
271 background: Option<Handle<UiNode>>,
272 content: Handle<UiNode>,
273}
274
275impl CheckBoxBuilder {
276 pub fn new(widget_builder: WidgetBuilder) -> Self {
278 Self {
279 widget_builder,
280 checked: Some(false),
281 check_mark: None,
282 uncheck_mark: None,
283 undefined_mark: None,
284 content: Handle::NONE,
285 background: None,
286 }
287 }
288
289 pub fn checked(mut self, value: Option<bool>) -> Self {
291 self.checked = value;
292 self
293 }
294
295 pub fn with_check_mark(mut self, check_mark: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
297 self.check_mark = Some(check_mark.to_base());
298 self
299 }
300
301 pub fn with_uncheck_mark(mut self, uncheck_mark: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
303 self.uncheck_mark = Some(uncheck_mark.to_base());
304 self
305 }
306
307 pub fn with_undefined_mark(
309 mut self,
310 undefined_mark: Handle<impl ObjectOrVariant<UiNode>>,
311 ) -> Self {
312 self.undefined_mark = Some(undefined_mark.to_base());
313 self
314 }
315
316 pub fn with_content(mut self, content: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
318 self.content = content.to_base();
319 self
320 }
321
322 pub fn with_background(mut self, background: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
325 self.background = Some(background.to_base());
326 self
327 }
328
329 pub fn build(self, ctx: &mut BuildContext) -> Handle<CheckBox> {
331 let check_mark = self.check_mark.unwrap_or_else(|| {
332 let size = *ctx.style.property(CheckBox::CHECK_MARK_SIZE);
333 BorderBuilder::new(
334 WidgetBuilder::new()
335 .with_background(ctx.style.property(Style::BRUSH_BRIGHT_BLUE))
336 .with_child(
337 ImageBuilder::new(WidgetBuilder::new().with_width(size).with_height(size))
338 .with_opt_texture(resources::CHECK.clone())
339 .build(ctx),
340 ),
341 )
342 .with_pad_by_corner_radius(false)
343 .with_corner_radius(ctx.style.property(CheckBox::CORNER_RADIUS))
344 .with_stroke_thickness(Thickness::uniform(0.0).into())
345 .build(ctx)
346 .to_base()
347 });
348 ctx[check_mark].set_visibility(self.checked.unwrap_or(false));
349
350 let uncheck_mark = self.uncheck_mark.unwrap_or_else(|| {
351 BorderBuilder::new(
352 WidgetBuilder::new()
353 .with_margin(Thickness::uniform(3.0))
354 .with_width(10.0)
355 .with_height(9.0)
356 .with_background(Brush::Solid(Color::TRANSPARENT).into())
357 .with_foreground(Brush::Solid(Color::TRANSPARENT).into()),
358 )
359 .with_pad_by_corner_radius(false)
360 .with_corner_radius(ctx.style.property(CheckBox::CORNER_RADIUS))
361 .with_stroke_thickness(Thickness::uniform(0.0).into())
362 .build(ctx)
363 .to_base()
364 });
365 ctx[uncheck_mark].set_visibility(!self.checked.unwrap_or(true));
366
367 let undefined_mark = self.undefined_mark.unwrap_or_else(|| {
368 BorderBuilder::new(
369 WidgetBuilder::new()
370 .with_margin(Thickness::uniform(4.0))
371 .with_background(ctx.style.property(Style::BRUSH_BRIGHT))
372 .with_foreground(Brush::Solid(Color::TRANSPARENT).into()),
373 )
374 .with_pad_by_corner_radius(false)
375 .with_corner_radius(ctx.style.property(CheckBox::CORNER_RADIUS))
376 .build(ctx)
377 .to_base()
378 });
379 ctx[undefined_mark].set_visibility(self.checked.is_none());
380
381 if self.content.is_some() {
382 ctx[self.content].set_row(0).set_column(1);
383 }
384
385 let background = self.background.unwrap_or_else(|| {
386 BorderBuilder::new(
387 WidgetBuilder::new()
388 .with_vertical_alignment(VerticalAlignment::Center)
389 .with_background(ctx.style.property(Style::BRUSH_DARKEST))
390 .with_foreground(ctx.style.property(Style::BRUSH_LIGHT)),
391 )
392 .with_pad_by_corner_radius(false)
393 .with_corner_radius(ctx.style.property(CheckBox::CORNER_RADIUS))
394 .with_stroke_thickness(ctx.style.property(CheckBox::BORDER_THICKNESS))
395 .build(ctx)
396 .to_base()
397 });
398
399 let background_ref = &mut ctx[background];
400 background_ref.set_row(0).set_column(0);
401 if background_ref.min_width() < 0.01 {
402 background_ref.set_min_width(18.0);
403 }
404 if background_ref.min_height() < 0.01 {
405 background_ref.set_min_height(18.0);
406 }
407
408 ctx.link(check_mark, background);
409 ctx.link(uncheck_mark, background);
410 ctx.link(undefined_mark, background);
411
412 let grid = GridBuilder::new(
413 WidgetBuilder::new()
414 .with_child(background)
415 .with_child(self.content),
416 )
417 .add_row(Row::stretch())
418 .add_column(Column::auto())
419 .add_column(Column::stretch())
420 .build(ctx);
421
422 let cb = CheckBox {
423 widget: self
424 .widget_builder
425 .with_accepts_input(true)
426 .with_child(grid)
427 .build(ctx),
428 checked: self.checked.into(),
429 check_mark: check_mark.into(),
430 uncheck_mark: uncheck_mark.into(),
431 undefined_mark: undefined_mark.into(),
432 };
433 ctx.add(cb)
434 }
435}
436
437#[cfg(test)]
438mod test {
439 use crate::message::UiMessage;
440 use crate::{
441 check_box::{CheckBoxBuilder, CheckBoxMessage},
442 widget::WidgetBuilder,
443 UserInterface,
444 };
445 use fyrox_core::algebra::Vector2;
446
447 #[test]
448 fn check_box() {
449 let mut ui = UserInterface::new(Vector2::new(100.0, 100.0));
450
451 assert_eq!(ui.poll_message(), None);
452
453 let check_box = CheckBoxBuilder::new(WidgetBuilder::new()).build(&mut ui.build_ctx());
454
455 assert_eq!(ui.poll_message(), None);
456
457 let input_message = UiMessage::for_widget(check_box, CheckBoxMessage::Check(Some(true)));
459
460 ui.send_message(input_message.clone());
461
462 assert_eq!(ui.poll_message(), Some(input_message.clone()));
464 assert_eq!(ui.poll_message(), Some(input_message.reverse()));
466 }
467
468 use crate::test::test_widget_deletion;
469
470 #[test]
471 fn test_deletion() {
472 test_widget_deletion(|ctx| CheckBoxBuilder::new(WidgetBuilder::new()).build(ctx));
473 }
474}