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