1use super::AccessLabel;
9use kas::prelude::*;
10use kas::theme::Feature;
11use std::fmt::Debug;
12use std::time::Instant;
13
14#[impl_self]
15mod RadioBox {
16 #[autoimpl(Debug ignore self.state_fn, self.on_select)]
24 #[widget]
25 pub struct RadioBox<A> {
26 core: widget_core!(),
27 state: bool,
28 last_change: Option<Instant>,
29 state_fn: Box<dyn Fn(&ConfigCx, &A) -> bool>,
30 on_select: Option<Box<dyn Fn(&mut EventCx, &A)>>,
31 }
32
33 impl Layout for Self {
34 fn size_rules(&mut self, sizer: SizeCx, axis: AxisInfo) -> SizeRules {
35 sizer.feature(Feature::RadioBox, axis)
36 }
37
38 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
39 let align = hints.complete_center();
40 let rect = cx.align_feature(Feature::RadioBox, rect, align);
41 widget_set_rect!(rect);
42 }
43
44 fn draw(&self, mut draw: DrawCx) {
45 draw.radio_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::RadioButton(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: &Self::Data, event: Event) -> IsUsed {
74 event.on_click(cx, self.id(), |cx| self.select(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.select(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 RadioBox {
92 core: Default::default(),
93 state: false,
94 last_change: None,
95 state_fn: Box::new(state_fn),
96 on_select: None,
97 }
98 }
99
100 #[inline]
104 #[must_use]
105 pub fn with(mut self, f: impl Fn(&mut EventCx, &A) + 'static) -> Self {
106 debug_assert!(self.on_select.is_none());
107 self.on_select = Some(Box::new(f));
108 self
109 }
110
111 #[inline]
116 pub fn new_msg<M: Debug + 'static>(
117 state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static,
118 msg_fn: impl Fn() -> M + 'static,
119 ) -> Self {
120 RadioBox::new(state_fn).with(move |cx, _| cx.push(msg_fn()))
121 }
122
123 #[inline]
129 pub fn new_value(value: A) -> Self
130 where
131 A: Clone + Debug + Eq + 'static,
132 {
133 let v2 = value.clone();
134 Self::new(move |_, data| *data == value).with(move |cx, _| cx.push(v2.clone()))
135 }
136
137 fn select(&mut self, cx: &mut EventCx, data: &A) {
138 self.state = true;
139 if let Some(ref f) = self.on_select {
140 f(cx, data);
141 }
142
143 self.last_change = Some(Instant::now());
144 cx.redraw(self);
145 }
146 }
147}
148
149#[impl_self]
150mod RadioButton {
151 #[widget]
159 #[layout(list![self.inner, self.label].with_direction(self.direction()))]
160 pub struct RadioButton<A> {
161 core: widget_core!(),
162 #[widget]
163 inner: RadioBox<A>,
164 #[widget(&())]
165 label: AccessLabel,
166 }
167
168 impl Layout for Self {
169 fn set_rect(&mut self, cx: &mut ConfigCx, rect: Rect, hints: AlignHints) {
170 kas::MacroDefinedLayout::set_rect(self, cx, rect, hints);
171 let dir = self.direction();
172 crate::check_box::shrink_to_text(&mut self.rect(), dir, &self.label);
173 }
174 }
175
176 impl Tile for Self {
177 fn role_child_properties(&self, cx: &mut dyn RoleCx, index: usize) {
178 if index == widget_index!(self.inner) {
179 cx.set_label(self.label.id());
180 }
181 }
182
183 fn nav_next(&self, _: bool, from: Option<usize>) -> Option<usize> {
184 from.xor(Some(widget_index!(self.inner)))
185 }
186
187 fn probe(&self, _: Coord) -> Id {
188 self.inner.id()
189 }
190 }
191
192 impl Events for Self {
193 type Data = A;
194
195 fn configure_recurse(&mut self, cx: &mut ConfigCx, data: &Self::Data) {
196 let id = self.make_child_id(widget_index!(self.inner));
197 if id.is_valid() {
198 cx.configure(self.inner.as_node(data), id);
199 }
200
201 let id = self.make_child_id(widget_index!(self.label));
202 if id.is_valid() {
203 cx.configure(self.label.as_node(&()), id);
204 self.label.set_target(self.inner.id());
205 }
206 }
207
208 fn handle_messages(&mut self, cx: &mut EventCx, data: &Self::Data) {
209 if let Some(kas::messages::Activate(code)) = cx.try_pop() {
210 self.inner.select(cx, data);
211 cx.depress_with_key(self.inner.id(), code);
212 }
213 }
214 }
215
216 impl Self {
217 #[inline]
222 pub fn new(
223 label: impl Into<AccessString>,
224 state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static,
225 ) -> Self {
226 RadioButton {
227 core: Default::default(),
228 inner: RadioBox::new(state_fn),
229 label: AccessLabel::new(label.into()),
230 }
231 }
232
233 #[inline]
237 #[must_use]
238 pub fn with(self, f: impl Fn(&mut EventCx, &A) + 'static) -> Self {
239 RadioButton {
240 core: self.core,
241 inner: self.inner.with(f),
242 label: self.label,
243 }
244 }
245
246 #[inline]
252 pub fn new_msg<M: Debug + 'static>(
253 label: impl Into<AccessString>,
254 state_fn: impl Fn(&ConfigCx, &A) -> bool + 'static,
255 msg_fn: impl Fn() -> M + 'static,
256 ) -> Self {
257 RadioButton::new(label, state_fn).with(move |cx, _| cx.push(msg_fn()))
258 }
259
260 #[inline]
266 pub fn new_value(label: impl Into<AccessString>, value: A) -> Self
267 where
268 A: Clone + Debug + Eq + 'static,
269 {
270 let v2 = value.clone();
271 Self::new(label, move |_, data| *data == value).with(move |cx, _| cx.push(v2.clone()))
272 }
273
274 fn direction(&self) -> Direction {
275 match self.label.text().text_is_rtl() {
276 false => Direction::Right,
277 true => Direction::Left,
278 }
279 }
280 }
281}