maycoon_widgets/
checkbox.rs1use maycoon_core::app::context::AppContext;
2use maycoon_core::app::info::AppInfo;
3use maycoon_core::app::update::Update;
4use maycoon_core::layout;
5use maycoon_core::layout::{Dimension, LayoutNode, LayoutStyle, LengthPercentageAuto, StyleNode};
6use maycoon_core::signal::MaybeSignal;
7use maycoon_core::vgi::kurbo::{Rect, RoundedRect, RoundedRectRadii, Stroke};
8use maycoon_core::vgi::{Brush, Scene};
9use maycoon_core::widget::{Widget, WidgetLayoutExt};
10use maycoon_core::window::{ElementState, MouseButton};
11use maycoon_theme::id::WidgetId;
12use maycoon_theme::theme::Theme;
13use nalgebra::Vector2;
14
15pub struct Checkbox {
24 layout_style: MaybeSignal<LayoutStyle>,
25 value: MaybeSignal<bool>,
26 on_change: MaybeSignal<Update>,
27}
28
29impl Checkbox {
30 pub fn new(value: impl Into<MaybeSignal<bool>>) -> Self {
34 Self {
35 layout_style: LayoutStyle {
36 size: Vector2::<Dimension>::new(Dimension::length(20.0), Dimension::length(20.0)),
37 margin: layout::Rect::<LengthPercentageAuto> {
38 left: LengthPercentageAuto::length(0.5),
39 right: LengthPercentageAuto::length(0.5),
40 top: LengthPercentageAuto::length(0.5),
41 bottom: LengthPercentageAuto::length(0.5),
42 },
43 ..Default::default()
44 }
45 .into(),
46 value: value.into(),
47 on_change: Update::empty().into(),
48 }
49 }
50
51 pub fn with_value(mut self, value: impl Into<MaybeSignal<bool>>) -> Self {
53 self.value = value.into();
54 self
55 }
56
57 pub fn with_on_change(mut self, on_change: impl Into<MaybeSignal<Update>>) -> Self {
59 self.on_change = on_change.into();
60 self
61 }
62}
63
64impl WidgetLayoutExt for Checkbox {
65 fn set_layout_style(&mut self, layout_style: impl Into<MaybeSignal<LayoutStyle>>) {
66 self.layout_style = layout_style.into();
67 }
68}
69
70impl Widget for Checkbox {
71 fn render(
72 &mut self,
73 scene: &mut dyn Scene,
74 theme: &mut dyn Theme,
75 layout_node: &LayoutNode,
76 _: &AppInfo,
77 _: AppContext,
78 ) {
79 let checked = *self.value.get();
80
81 let color = if let Some(style) = theme.of(self.widget_id()) {
82 if checked {
83 style.get_color("color_checked").unwrap()
84 } else {
85 style.get_color("color_unchecked").unwrap()
86 }
87 } else if checked {
88 theme.defaults().interactive().active()
89 } else {
90 theme.defaults().interactive().inactive()
91 };
92
93 scene.draw_rounded_rect(
94 &Brush::Solid(color),
95 None,
96 Some(&Stroke::new(3.0)),
97 &RoundedRect::from_rect(
98 Rect::new(
99 layout_node.layout.location.x as f64,
100 layout_node.layout.location.y as f64,
101 (layout_node.layout.location.x + layout_node.layout.size.width) as f64,
102 (layout_node.layout.location.y + layout_node.layout.size.height) as f64,
103 ),
104 RoundedRectRadii::from_single_radius(5.0),
105 ),
106 );
107
108 if checked {
109 scene.draw_rounded_rect(
110 &Brush::Solid(color),
111 None,
112 None,
113 &RoundedRect::from_rect(
114 Rect::new(
115 layout_node.layout.location.x as f64 + 5.0,
116 layout_node.layout.location.y as f64 + 5.0,
117 (layout_node.layout.location.x + layout_node.layout.size.width) as f64
118 - 5.0,
119 (layout_node.layout.location.y + layout_node.layout.size.height) as f64
120 - 5.0,
121 ),
122 RoundedRectRadii::from_single_radius(2.5),
123 ),
124 );
125 }
126 }
127
128 fn layout_style(&self) -> StyleNode {
129 StyleNode {
130 style: self.layout_style.get().clone(),
131 children: Vec::new(),
132 }
133 }
134
135 fn update(&mut self, layout: &LayoutNode, _: AppContext, info: &AppInfo) -> Update {
136 let mut update = Update::empty();
137
138 if let Some(cursor) = &info.cursor_pos
139 && (cursor.x as f32 >= layout.layout.location.x
140 && cursor.x as f32 <= layout.layout.location.x + layout.layout.size.width
141 && cursor.y as f32 >= layout.layout.location.y
142 && cursor.y as f32 <= layout.layout.location.y + layout.layout.size.height)
143 {
144 for (_, btn, el) in &info.buttons {
145 if btn == &MouseButton::Left && *el == ElementState::Released {
146 update |= *self.on_change.get();
147 update |= Update::DRAW;
148
149 if let Some(sig) = self.value.as_signal() {
150 let checked = *sig.get();
151 sig.set(!checked);
152 }
153 }
154 }
155 }
156
157 update
158 }
159
160 fn widget_id(&self) -> WidgetId {
161 WidgetId::new("maycoon-widgets", "Checkbox")
162 }
163}