1use super::AccessLabel;
9use kas::prelude::*;
10use kas::theme::Feature;
11use std::fmt::Debug;
12use std::time::Instant;
13
14#[impl_self]
15mod CheckBox {
16 #[autoimpl(Debug ignore self.state_fn, self.on_toggle)]
24 #[widget]
25 pub struct CheckBox<A> {
26 core: widget_core!(),
27 state: bool,
28 editable: bool,
29 last_change: Option<Instant>,
30 state_fn: Box<dyn Fn(&ConfigCx, &A) -> bool>,
31 on_toggle: Option<Box<dyn Fn(&mut EventCx, &A, bool)>>,
32 }
33
34 impl Layout for Self {
35 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
36 sizer.feature(Feature::CheckBox, axis)
37 }
38
39 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
40 let rect = cx.align_feature(Feature::CheckBox, rect, hints.complete_center());
41 widget_set_rect!(rect);
42 }
43
44 fn draw(&self, mut draw: DrawCx) {
45 draw.check_box(self.rect(), self.state, self.last_change);
46 }
47 }
48
49 impl Tile for Self {
50 fn navigable(&self) -> bool {
51 true
52 }
53
54 fn role(&self, _: &mut dyn RoleCx) -> Role<'_> {
55 Role::CheckBox(self.state)
56 }
57 }
58
59 impl Events for Self {
60 const REDRAW_ON_MOUSE_OVER: bool = true;
61
62 type Data = A;
63
64 fn update(&mut self, cx: &mut ConfigCx, data: &A) {
65 let new_state = (self.state_fn)(cx, data);
66 if self.state != new_state {
67 self.state = new_state;
68 self.last_change = Some(Instant::now());
69 cx.redraw(self);
70 }
71 }
72
73 fn handle_event(&mut self, cx: &mut EventCx, data: &A, event: Event) -> IsUsed {
74 event.on_click(cx, self.id(), |cx| self.toggle(cx, data))
75 }
76
77 fn handle_messages(&mut self, cx: &mut EventCx, data: &Self::Data) {
78 if let Some(kas::messages::Activate(code)) = cx.try_pop() {
79 self.toggle(cx, data);
80 cx.depress_with_key(&self, code);
81 }
82 }
83 }
84
85 impl Self {
86 #[inline]
90 pub fn new(state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static) -> Self {
91 CheckBox {
92 core: Default::default(),
93 state: false,
94 editable: true,
95 last_change: None,
96 state_fn: Box::new(state_fn),
97 on_toggle: None,
98 }
99 }
100
101 #[inline]
103 #[must_use]
104 pub fn with(mut self, f: impl Fn(&mut EventCx, &A, bool) + 'static) -> Self {
105 debug_assert!(self.on_toggle.is_none());
106 self.on_toggle = Some(Box::new(f));
107 self
108 }
109
110 #[inline]
112 #[must_use]
113 pub fn with_msg<M>(self, f: impl Fn(bool) -> M + 'static) -> Self
114 where
115 M: std::fmt::Debug + 'static,
116 {
117 self.with(move |cx, _, state| cx.push(f(state)))
118 }
119
120 #[inline]
125 pub fn new_msg<M: Debug + 'static>(
126 state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static,
127 msg_fn: impl Fn(bool) -> M + 'static,
128 ) -> Self {
129 CheckBox::new(state_fn).with_msg(msg_fn)
130 }
131
132 #[inline]
134 #[must_use]
135 pub fn with_editable(mut self, editable: bool) -> Self {
136 self.editable = editable;
137 self
138 }
139
140 #[inline]
142 pub fn is_editable(&self) -> bool {
143 self.editable
144 }
145
146 #[inline]
148 pub fn set_editable(&mut self, editable: bool) {
149 self.editable = editable;
150 }
151
152 pub fn toggle(&mut self, cx: &mut EventCx, data: &A) {
154 self.state = !self.state;
156 if let Some(f) = self.on_toggle.as_ref() {
157 f(cx, data, self.state);
159 }
160
161 self.last_change = Some(Instant::now());
163 cx.redraw(self);
164 }
165 }
166}
167
168pub(crate) fn shrink_to_text(rect: &mut Rect, direction: Direction, label: &AccessLabel) {
173 if let Ok(bb) = label.text().bounding_box() {
174 match direction {
175 Direction::Right => {
176 let offset = label.rect().pos.0 - rect.pos.0;
177 let text_right: i32 = ((bb.1).0).cast_ceil();
178 rect.size.0 = offset + text_right;
179 }
180 Direction::Left => {
181 let text_left: i32 = ((bb.0).0).cast_floor();
182 rect.pos.0 += text_left;
183 rect.size.0 -= text_left
184 }
185 _ => (),
186 }
187 }
188}
189
190#[impl_self]
191mod CheckButton {
192 #[widget]
200 #[layout(list![self.inner, self.label].with_direction(self.direction()))]
201 pub struct CheckButton<A> {
202 core: widget_core!(),
203 #[widget]
204 inner: CheckBox<A>,
205 #[widget(&())]
206 label: AccessLabel,
207 }
208
209 impl Layout for Self {
210 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
211 kas::MacroDefinedLayout::set_rect(self, cx, rect, hints);
212 let dir = self.direction();
213 shrink_to_text(&mut self.rect(), dir, &self.label);
214 }
215 }
216
217 impl Tile for Self {
218 fn role_child_properties(&self, cx: &mut dyn RoleCx, index: usize) {
219 if index == widget_index!(self.inner) {
220 cx.set_label(self.label.id());
221 }
222 }
223
224 fn nav_next(&self, _: bool, from: Option<usize>) -> Option<usize> {
225 from.xor(Some(widget_index!(self.inner)))
226 }
227
228 fn probe(&self, _: Coord) -> Id {
229 self.inner.id()
230 }
231 }
232
233 impl Events for Self {
234 type Data = A;
235
236 fn configure_recurse(&mut self, cx: &mut ConfigCx, data: &Self::Data) {
237 let id = self.make_child_id(widget_index!(self.inner));
238 if id.is_valid() {
239 cx.configure(self.inner.as_node(data), id);
240 }
241
242 let id = self.make_child_id(widget_index!(self.label));
243 if id.is_valid() {
244 cx.configure(self.label.as_node(&()), id);
245 self.label.set_target(self.inner.id());
246 }
247 }
248
249 fn handle_messages(&mut self, cx: &mut EventCx, data: &Self::Data) {
250 if let Some(kas::messages::Activate(code)) = cx.try_pop() {
251 self.inner.toggle(cx, data);
252 cx.depress_with_key(self.inner.id(), code);
253 }
254 }
255 }
256
257 impl Self {
258 #[inline]
263 pub fn new(
264 label: impl Into<AccessString>,
265 state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static,
266 ) -> Self {
267 CheckButton {
268 core: Default::default(),
269 inner: CheckBox::new(state_fn),
270 label: AccessLabel::new(label.into()),
271 }
272 }
273
274 #[inline]
276 #[must_use]
277 pub fn with(self, f: impl Fn(&mut EventCx, &A, bool) + 'static) -> Self {
278 CheckButton {
279 core: self.core,
280 inner: self.inner.with(f),
281 label: self.label,
282 }
283 }
284
285 #[inline]
287 #[must_use]
288 pub fn with_msg<M>(self, f: impl Fn(bool) -> M + 'static) -> Self
289 where
290 M: std::fmt::Debug + 'static,
291 {
292 self.with(move |cx, _, state| cx.push(f(state)))
293 }
294
295 #[inline]
301 pub fn new_msg<M: Debug + 'static>(
302 label: impl Into<AccessString>,
303 state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static,
304 msg_fn: impl Fn(bool) -> M + 'static,
305 ) -> Self {
306 CheckButton::new(label, state_fn).with_msg(msg_fn)
307 }
308
309 #[inline]
311 #[must_use]
312 pub fn editable(mut self, editable: bool) -> Self {
313 self.inner = self.inner.with_editable(editable);
314 self
315 }
316
317 #[inline]
319 pub fn is_editable(&self) -> bool {
320 self.inner.is_editable()
321 }
322
323 #[inline]
325 pub fn set_editable(&mut self, editable: bool) {
326 self.inner.set_editable(editable);
327 }
328
329 fn direction(&self) -> Direction {
330 match self.label.text().text_is_rtl() {
331 false => Direction::Right,
332 true => Direction::Left,
333 }
334 }
335 }
336}