maycoon_widgets/
checkbox.rs1use crate::ext::WidgetLayoutExt;
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::state::{State, Val};
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;
11use maycoon_core::window::{ElementState, MouseButton};
12use maycoon_theme::id::WidgetId;
13use maycoon_theme::theme::Theme;
14use nalgebra::Vector2;
15
16pub struct Checkbox<S: State> {
25 layout_style: Val<S, LayoutStyle>,
26 on_change: Box<dyn FnMut(&mut S) -> Update>,
27 value: Val<S, bool>,
28}
29
30impl<S: State> Checkbox<S> {
31 pub fn new(value: Val<S, bool>) -> Self {
35 Self {
36 layout_style: Val::new_val(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 on_change: Box::new(|_| Update::empty()),
47 value,
48 }
49 }
50
51 pub fn with_on_change(mut self, on_change: impl FnMut(&mut S) -> Update + 'static) -> Self {
55 self.on_change = Box::new(on_change);
56 self
57 }
58
59 pub fn with_value(mut self, value: impl Into<Val<S, bool>>) -> Self {
63 self.value = value.into();
64 self
65 }
66}
67
68impl<S: State> WidgetLayoutExt<S> for Checkbox<S> {
69 fn set_layout_style(&mut self, layout_style: impl Into<Val<S, LayoutStyle>>) {
70 self.layout_style = layout_style.into();
71 }
72}
73
74impl<S: State> Widget<S> for Checkbox<S> {
75 fn render(
76 &mut self,
77 scene: &mut Scene,
78 theme: &mut dyn Theme,
79 _: &AppInfo,
80 layout_node: &LayoutNode,
81 state: &S,
82 ) {
83 let checked = *self.value.get_ref(state);
84
85 let color = if let Some(style) = theme.of(self.widget_id()) {
86 if checked {
87 style.get_color("color_checked").unwrap()
88 } else {
89 style.get_color("color_unchecked").unwrap()
90 }
91 } else if checked {
92 theme.defaults().interactive().active()
93 } else {
94 theme.defaults().interactive().inactive()
95 };
96
97 scene.stroke(
98 &Stroke::new(3.0),
99 Affine::default(),
100 &Brush::Solid(color),
101 None,
102 &RoundedRect::from_rect(
103 Rect::new(
104 layout_node.layout.location.x as f64,
105 layout_node.layout.location.y as f64,
106 (layout_node.layout.location.x + layout_node.layout.size.width) as f64,
107 (layout_node.layout.location.y + layout_node.layout.size.height) as f64,
108 ),
109 RoundedRectRadii::from_single_radius(5.0),
110 ),
111 );
112
113 if checked {
114 scene.fill(
115 Fill::NonZero,
116 Affine::default(),
117 &Brush::Solid(color),
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 fn layout_style(&mut self, state: &S) -> StyleNode {
135 StyleNode {
136 style: self.layout_style.get_ref(state).clone(),
137 children: Vec::new(),
138 }
139 }
140
141 fn update(&mut self, layout: &LayoutNode, state: &mut S, info: &AppInfo) -> Update {
142 self.value.invalidate();
143 self.layout_style.invalidate();
144
145 let mut update = Update::empty();
146
147 if let Some(cursor) = &info.cursor_pos {
148 if cursor.x as f32 >= layout.layout.location.x
149 && cursor.x as f32 <= layout.layout.location.x + layout.layout.size.width
150 && cursor.y as f32 >= layout.layout.location.y
151 && cursor.y as f32 <= layout.layout.location.y + layout.layout.size.height
152 {
153 for (_, btn, el) in &info.buttons {
154 if btn == &MouseButton::Left && *el == ElementState::Released {
155 update |= (self.on_change)(state);
156 }
157 }
158 }
159 }
160
161 update
162 }
163
164 fn widget_id(&self) -> WidgetId {
165 WidgetId::new("maycoon-widgets", "Checkbox")
166 }
167}