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