#![warn(missing_docs)]
use crate::{
border::BorderBuilder,
brush::Brush,
core::{
color::Color,
pool::{Handle, ObjectOrVariant},
reflect::prelude::*,
type_traits::prelude::*,
variable::InheritableVariable,
visitor::prelude::*,
},
grid::{Column, GridBuilder, Row},
image::ImageBuilder,
message::{KeyCode, MessageData, UiMessage},
resources,
style::{resource::StyleResourceExt, Style},
widget::{Widget, WidgetBuilder, WidgetMessage},
BuildContext, Control, MouseButton, Thickness, UiNode, UserInterface, VerticalAlignment,
};
use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CheckBoxMessage {
Check(Option<bool>),
}
impl MessageData for CheckBoxMessage {}
#[derive(Default, Clone, Debug, Visit, Reflect, TypeUuidProvider, ComponentProvider)]
#[type_uuid(id = "3a866ba8-7682-4ce7-954a-46360f5837dc")]
#[reflect(derived_type = "UiNode")]
pub struct CheckBox {
pub widget: Widget,
pub checked: InheritableVariable<Option<bool>>,
pub check_mark: InheritableVariable<Handle<UiNode>>,
pub uncheck_mark: InheritableVariable<Handle<UiNode>>,
pub undefined_mark: InheritableVariable<Handle<UiNode>>,
}
impl CheckBox {
pub const CORNER_RADIUS: &'static str = "CheckBox.CornerRadius";
pub const BORDER_THICKNESS: &'static str = "CheckBox.BorderThickness";
pub const CHECK_MARK_SIZE: &'static str = "CheckBox.CheckMarkSize";
pub fn style() -> Style {
Style::default()
.with(Self::CORNER_RADIUS, 4.0f32)
.with(Self::BORDER_THICKNESS, Thickness::uniform(1.0))
.with(Self::CHECK_MARK_SIZE, 12.0f32)
}
}
impl ConstructorProvider<UiNode, UserInterface> for CheckBox {
fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
GraphNodeConstructor::new::<Self>()
.with_variant("CheckBox", |ui| {
CheckBoxBuilder::new(WidgetBuilder::new().with_name("CheckBox"))
.build(&mut ui.build_ctx())
.to_base()
.into()
})
.with_group("Input")
}
}
crate::define_widget_deref!(CheckBox);
impl Control for CheckBox {
fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
self.widget.handle_routed_message(ui, message);
if let Some(msg) = message.data::<WidgetMessage>() {
match msg {
WidgetMessage::MouseDown { button, .. } => {
if *button == MouseButton::Left
&& (message.destination() == self.handle()
|| self.widget.has_descendant(message.destination(), ui))
{
ui.capture_mouse(self.handle());
}
}
WidgetMessage::MouseUp { button, .. } => {
if *button == MouseButton::Left
&& (message.destination() == self.handle()
|| self.widget.has_descendant(message.destination(), ui))
{
ui.release_mouse_capture();
if let Some(value) = *self.checked {
ui.send(self.handle(), CheckBoxMessage::Check(Some(!value)));
} else {
ui.send(self.handle(), CheckBoxMessage::Check(Some(true)));
}
}
}
WidgetMessage::KeyDown(key_code) => {
if !message.handled() && *key_code == KeyCode::Space {
let checked = self.checked.map(|checked| !checked);
ui.send(self.handle, CheckBoxMessage::Check(checked));
message.set_handled(true);
}
}
_ => (),
}
} else if let Some(&CheckBoxMessage::Check(value)) = message.data_for(self.handle) {
if *self.checked != value {
self.checked.set_value_and_mark_modified(value);
ui.try_send_response(message);
if self.check_mark.is_some() {
match value {
None => {
ui.send(*self.check_mark, WidgetMessage::Visibility(false));
ui.send(*self.uncheck_mark, WidgetMessage::Visibility(false));
ui.send(*self.undefined_mark, WidgetMessage::Visibility(true));
}
Some(value) => {
ui.send(*self.check_mark, WidgetMessage::Visibility(value));
ui.send(*self.uncheck_mark, WidgetMessage::Visibility(!value));
ui.send(*self.undefined_mark, WidgetMessage::Visibility(false));
}
}
}
}
}
}
}
pub struct CheckBoxBuilder {
widget_builder: WidgetBuilder,
checked: Option<bool>,
check_mark: Option<Handle<UiNode>>,
uncheck_mark: Option<Handle<UiNode>>,
undefined_mark: Option<Handle<UiNode>>,
background: Option<Handle<UiNode>>,
content: Handle<UiNode>,
}
impl CheckBoxBuilder {
pub fn new(widget_builder: WidgetBuilder) -> Self {
Self {
widget_builder,
checked: Some(false),
check_mark: None,
uncheck_mark: None,
undefined_mark: None,
content: Handle::NONE,
background: None,
}
}
pub fn checked(mut self, value: Option<bool>) -> Self {
self.checked = value;
self
}
pub fn with_check_mark(mut self, check_mark: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
self.check_mark = Some(check_mark.to_base());
self
}
pub fn with_uncheck_mark(mut self, uncheck_mark: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
self.uncheck_mark = Some(uncheck_mark.to_base());
self
}
pub fn with_undefined_mark(
mut self,
undefined_mark: Handle<impl ObjectOrVariant<UiNode>>,
) -> Self {
self.undefined_mark = Some(undefined_mark.to_base());
self
}
pub fn with_content(mut self, content: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
self.content = content.to_base();
self
}
pub fn with_background(mut self, background: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
self.background = Some(background.to_base());
self
}
pub fn build(self, ctx: &mut BuildContext) -> Handle<CheckBox> {
let check_mark = self.check_mark.unwrap_or_else(|| {
let size = *ctx.style.property(CheckBox::CHECK_MARK_SIZE);
BorderBuilder::new(
WidgetBuilder::new()
.with_background(ctx.style.property(Style::BRUSH_BRIGHT_BLUE))
.with_child(
ImageBuilder::new(WidgetBuilder::new().with_width(size).with_height(size))
.with_opt_texture(resources::CHECK.clone())
.build(ctx),
),
)
.with_pad_by_corner_radius(false)
.with_corner_radius(ctx.style.property(CheckBox::CORNER_RADIUS))
.with_stroke_thickness(Thickness::uniform(0.0).into())
.build(ctx)
.to_base()
});
ctx[check_mark].set_visibility(self.checked.unwrap_or(false));
let uncheck_mark = self.uncheck_mark.unwrap_or_else(|| {
BorderBuilder::new(
WidgetBuilder::new()
.with_margin(Thickness::uniform(3.0))
.with_width(10.0)
.with_height(9.0)
.with_background(Brush::Solid(Color::TRANSPARENT).into())
.with_foreground(Brush::Solid(Color::TRANSPARENT).into()),
)
.with_pad_by_corner_radius(false)
.with_corner_radius(ctx.style.property(CheckBox::CORNER_RADIUS))
.with_stroke_thickness(Thickness::uniform(0.0).into())
.build(ctx)
.to_base()
});
ctx[uncheck_mark].set_visibility(!self.checked.unwrap_or(true));
let undefined_mark = self.undefined_mark.unwrap_or_else(|| {
BorderBuilder::new(
WidgetBuilder::new()
.with_margin(Thickness::uniform(4.0))
.with_background(ctx.style.property(Style::BRUSH_BRIGHT))
.with_foreground(Brush::Solid(Color::TRANSPARENT).into()),
)
.with_pad_by_corner_radius(false)
.with_corner_radius(ctx.style.property(CheckBox::CORNER_RADIUS))
.build(ctx)
.to_base()
});
ctx[undefined_mark].set_visibility(self.checked.is_none());
if self.content.is_some() {
ctx[self.content].set_row(0).set_column(1);
}
let background = self.background.unwrap_or_else(|| {
BorderBuilder::new(
WidgetBuilder::new()
.with_vertical_alignment(VerticalAlignment::Center)
.with_background(ctx.style.property(Style::BRUSH_DARKEST))
.with_foreground(ctx.style.property(Style::BRUSH_LIGHT)),
)
.with_pad_by_corner_radius(false)
.with_corner_radius(ctx.style.property(CheckBox::CORNER_RADIUS))
.with_stroke_thickness(ctx.style.property(CheckBox::BORDER_THICKNESS))
.build(ctx)
.to_base()
});
let background_ref = &mut ctx[background];
background_ref.set_row(0).set_column(0);
if background_ref.min_width() < 0.01 {
background_ref.set_min_width(18.0);
}
if background_ref.min_height() < 0.01 {
background_ref.set_min_height(18.0);
}
ctx.link(check_mark, background);
ctx.link(uncheck_mark, background);
ctx.link(undefined_mark, background);
let grid = GridBuilder::new(
WidgetBuilder::new()
.with_child(background)
.with_child(self.content),
)
.add_row(Row::stretch())
.add_column(Column::auto())
.add_column(Column::stretch())
.build(ctx);
let cb = CheckBox {
widget: self
.widget_builder
.with_accepts_input(true)
.with_child(grid)
.build(ctx),
checked: self.checked.into(),
check_mark: check_mark.into(),
uncheck_mark: uncheck_mark.into(),
undefined_mark: undefined_mark.into(),
};
ctx.add(cb)
}
}
#[cfg(test)]
mod test {
use crate::message::UiMessage;
use crate::{
check_box::{CheckBoxBuilder, CheckBoxMessage},
widget::WidgetBuilder,
UserInterface,
};
use fyrox_core::algebra::Vector2;
#[test]
fn check_box() {
let mut ui = UserInterface::new(Vector2::new(100.0, 100.0));
assert_eq!(ui.poll_message(), None);
let check_box = CheckBoxBuilder::new(WidgetBuilder::new()).build(&mut ui.build_ctx());
assert_eq!(ui.poll_message(), None);
let input_message = UiMessage::for_widget(check_box, CheckBoxMessage::Check(Some(true)));
ui.send_message(input_message.clone());
assert_eq!(ui.poll_message(), Some(input_message.clone()));
assert_eq!(ui.poll_message(), Some(input_message.reverse()));
}
use crate::test::test_widget_deletion;
#[test]
fn test_deletion() {
test_widget_deletion(|ctx| CheckBoxBuilder::new(WidgetBuilder::new()).build(ctx));
}
}