1use super::AccessLabel;
9use kas::prelude::*;
10use kas::theme::Feature;
11use std::fmt::Debug;
12use std::time::Instant;
13
14impl_scope! {
15 #[autoimpl(Debug ignore self.state_fn, self.on_toggle)]
19 #[widget{
20 navigable = true;
21 hover_highlight = true;
22 }]
23 pub struct CheckBox<A> {
24 core: widget_core!(),
25 state: bool,
26 editable: bool,
27 last_change: Option<Instant>,
28 state_fn: Box<dyn Fn(&ConfigCx, &A) -> bool>,
29 on_toggle: Option<Box<dyn Fn(&mut EventCx, &A, bool)>>,
30 }
31
32 impl Events for Self {
33 type Data = A;
34
35 fn update(&mut self, cx: &mut ConfigCx, data: &A) {
36 let new_state = (self.state_fn)(cx, data);
37 if self.state != new_state {
38 self.state = new_state;
39 self.last_change = Some(Instant::now());
40 cx.redraw(self);
41 }
42 }
43
44 fn handle_event(&mut self, cx: &mut EventCx, data: &A, event: Event) -> IsUsed {
45 event.on_activate(cx, self.id(), |cx| {
46 self.toggle(cx, data);
47 Used
48 })
49 }
50 }
51
52 impl Layout for Self {
53 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
54 sizer.feature(Feature::CheckBox, axis)
55 }
56
57 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
58 let rect = cx.align_feature(Feature::CheckBox, rect, hints.complete_center());
59 self.core.rect = rect;
60 }
61
62 fn draw(&mut self, mut draw: DrawCx) {
63 draw.check_box(self.rect(), self.state, self.last_change);
64 }
65 }
66
67 impl Self {
68 #[inline]
72 pub fn new(state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static) -> Self {
73 CheckBox {
74 core: Default::default(),
75 state: false,
76 editable: true,
77 last_change: None,
78 state_fn: Box::new(state_fn),
79 on_toggle: None,
80 }
81 }
82
83 #[inline]
85 #[must_use]
86 pub fn with(mut self, f: impl Fn(&mut EventCx, &A, bool) + 'static) -> Self {
87 debug_assert!(self.on_toggle.is_none());
88 self.on_toggle = Some(Box::new(f));
89 self
90 }
91
92 #[inline]
94 #[must_use]
95 pub fn with_msg<M>(self, f: impl Fn(bool) -> M + 'static) -> Self
96 where
97 M: std::fmt::Debug + 'static,
98 {
99 self.with(move |cx, _, state| cx.push(f(state)))
100 }
101
102 #[inline]
107 pub fn new_msg<M: Debug + 'static>(
108 state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static,
109 msg_fn: impl Fn(bool) -> M + 'static,
110 ) -> Self {
111 CheckBox::new(state_fn).with_msg(msg_fn)
112 }
113
114 #[inline]
116 #[must_use]
117 pub fn with_editable(mut self, editable: bool) -> Self {
118 self.editable = editable;
119 self
120 }
121
122 #[inline]
124 pub fn is_editable(&self) -> bool {
125 self.editable
126 }
127
128 #[inline]
130 pub fn set_editable(&mut self, editable: bool) {
131 self.editable = editable;
132 }
133
134 pub fn toggle(&mut self, cx: &mut EventCx, data: &A) {
136 self.state = !self.state;
138 if let Some(f) = self.on_toggle.as_ref() {
139 f(cx, data, self.state);
141 }
142
143 self.last_change = Some(Instant::now());
145 cx.redraw(self);
146 }
147 }
148}
149
150pub(crate) fn shrink_to_text(rect: &mut Rect, direction: Direction, label: &AccessLabel) {
155 if let Ok(bb) = label.text().bounding_box() {
156 match direction {
157 Direction::Right => {
158 let offset = label.rect().pos.0 - rect.pos.0;
159 let text_right: i32 = ((bb.1).0).cast_ceil();
160 rect.size.0 = offset + text_right;
161 }
162 Direction::Left => {
163 let text_left: i32 = ((bb.0).0).cast_floor();
164 rect.pos.0 += text_left;
165 rect.size.0 -= text_left
166 }
167 _ => (),
168 }
169 }
170}
171
172impl_scope! {
173 #[widget{
177 layout = list!(self.direction(), [self.inner, non_navigable!(self.label)]);
178 }]
179 pub struct CheckButton<A> {
180 core: widget_core!(),
181 #[widget]
182 inner: CheckBox<A>,
183 #[widget(&())]
184 label: AccessLabel,
185 }
186
187 impl Layout for Self {
188 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
189 self.core.rect = rect;
190 self.layout_visitor().set_rect(cx, rect, hints);
191 let dir = self.direction();
192 shrink_to_text(&mut self.core.rect, dir, &self.label);
193 }
194
195 fn find_id(&mut self, coord: Coord) -> Option<Id> {
196 self.rect().contains(coord).then(|| self.inner.id())
197 }
198 }
199
200 impl Events for Self {
201 type Data = A;
202
203 fn handle_messages(&mut self, cx: &mut EventCx, data: &Self::Data) {
204 if let Some(kas::messages::Activate(code)) = cx.try_pop() {
205 self.inner.toggle(cx, data);
206 cx.depress_with_key(self.inner.id(), code);
207 }
208 }
209 }
210
211 impl Self {
212 #[inline]
217 pub fn new(
218 label: impl Into<AccessString>,
219 state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static,
220 ) -> Self {
221 CheckButton {
222 core: Default::default(),
223 inner: CheckBox::new(state_fn),
224 label: AccessLabel::new(label.into()),
225 }
226 }
227
228 #[inline]
230 #[must_use]
231 pub fn with(self, f: impl Fn(&mut EventCx, &A, bool) + 'static) -> Self {
232 CheckButton {
233 core: self.core,
234 inner: self.inner.with(f),
235 label: self.label,
236 }
237 }
238
239 #[inline]
241 #[must_use]
242 pub fn with_msg<M>(self, f: impl Fn(bool) -> M + 'static) -> Self
243 where
244 M: std::fmt::Debug + 'static,
245 {
246 self.with(move |cx, _, state| cx.push(f(state)))
247 }
248
249 #[inline]
255 pub fn new_msg<M: Debug + 'static>(
256 label: impl Into<AccessString>,
257 state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static,
258 msg_fn: impl Fn(bool) -> M + 'static,
259 ) -> Self {
260 CheckButton::new(label, state_fn).with_msg(msg_fn)
261 }
262
263 #[inline]
265 #[must_use]
266 pub fn editable(mut self, editable: bool) -> Self {
267 self.inner = self.inner.with_editable(editable);
268 self
269 }
270
271 #[inline]
273 pub fn is_editable(&self) -> bool {
274 self.inner.is_editable()
275 }
276
277 #[inline]
279 pub fn set_editable(&mut self, editable: bool) {
280 self.inner.set_editable(editable);
281 }
282
283 fn direction(&self) -> Direction {
284 match self.label.text().text_is_rtl() {
285 false => Direction::Right,
286 true => Direction::Left,
287 }
288 }
289 }
290}