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