suzy/widgets/
togglebutton.rs1use std::rc::Rc;
6
7use drying_paint::{Watched, WatchedCell};
8
9use crate::platform::{DefaultRenderPlatform, RenderPlatform};
10use crate::pointer::{PointerAction, PointerEvent};
11use crate::selectable::{Selectable, SelectionState, SelectionStateV1};
12use crate::widget::{
13 Widget, WidgetChildReceiver, WidgetContent, WidgetExtra,
14 WidgetGraphicReceiver, WidgetId, WidgetInit,
15};
16
17pub struct ToggleButtonGroup<V = ()> {
23 ptr: Rc<WatchedCell<Option<V>>>,
24}
25
26impl<V> ToggleButtonGroup<V> {
27 pub fn new() -> Self {
29 Self {
30 ptr: Rc::new(WatchedCell::new(None)),
31 }
32 }
33
34 pub fn take(&self) -> Option<V> {
39 self.ptr.take()
40 }
41
42 fn set(&self, value: V) {
43 self.ptr.set(Some(value));
44 }
45
46 fn unset(&self) {
47 self.ptr.set(None);
48 }
49
50 fn private_clone(&self) -> Self {
51 Self {
52 ptr: Rc::clone(&self.ptr),
53 }
54 }
55}
56
57impl<V: Copy> ToggleButtonGroup<V> {
58 pub fn value(&self) -> Option<V> {
63 self.ptr.get()
64 }
65}
66
67impl<V> Default for ToggleButtonGroup<V> {
68 fn default() -> Self {
69 Self::new()
70 }
71}
72
73pub trait ToggleButtonValue<V> {
76 fn get_value(&self, extra: &WidgetExtra) -> V;
78}
79
80impl<T> ToggleButtonValue<()> for T {
81 fn get_value(&self, _extra: &WidgetExtra) {}
82}
83
84impl<T> ToggleButtonValue<WidgetId> for T {
85 fn get_value(&self, extra: &WidgetExtra) -> WidgetId {
86 extra.id()
87 }
88}
89
90pub struct ToggleButtonContent<T, V = ()> {
91 state: Watched<SelectionState>,
92 group: Watched<Option<ToggleButtonGroup<V>>>,
93 allow_unselect: bool,
94 interactable: Watched<bool>,
95 pointers_down: usize,
96 just_clicked: bool,
97 currently_selected: bool,
98 content: T,
99}
100
101impl<T, V> ToggleButtonContent<T, V> {
102 pub fn content(&self) -> &T {
103 &self.content
104 }
105
106 pub fn content_mut(&mut self) -> &mut T {
107 &mut self.content
108 }
109
110 pub fn state(&self) -> SelectionState {
111 *self.state
112 }
113
114 pub fn add_to_group(&mut self, group: &ToggleButtonGroup<V>) {
115 if let Some(existing) = &*self.group {
116 if Rc::ptr_eq(&existing.ptr, &group.ptr) {
117 return;
118 }
119 }
120 *self.group = Some(group.private_clone());
121 group.unset();
122 }
123
124 fn base_state(&self) -> SelectionState {
125 if self.currently_selected {
126 SelectionState::active()
127 } else {
128 SelectionState::normal()
129 }
130 }
131}
132
133impl<T, V, P> WidgetContent<P> for ToggleButtonContent<T, V>
134where
135 P: RenderPlatform,
136 T: Selectable + WidgetContent<P> + ToggleButtonValue<V>,
137 V: 'static + std::fmt::Debug + Copy,
138{
139 fn init(mut init: impl WidgetInit<Self, P>) {
140 init.init_child_inline(|button| &mut button.content);
141 init.watch(|button, _rect| {
142 button.content.selection_changed(*button.state);
143 });
144 init.watch(|button, _rect| {
145 if !*button.interactable {
146 *button.state = button.base_state();
147 }
148 });
149 init.watch(|button, _rect| {
150 if let Some(group) = &*button.group {
151 group.ptr.watched();
152 if button.just_clicked {
155 button.just_clicked = false;
156 } else if button.currently_selected {
157 let at_base = button.state == button.base_state();
158 button.currently_selected = false;
159 if at_base {
160 *button.state = button.base_state();
161 }
162 }
163 }
164 });
165 }
166
167 fn children(&mut self, receiver: impl WidgetChildReceiver<P>) {
168 self.content.children(receiver);
169 }
170
171 fn graphics(&mut self, receiver: impl WidgetGraphicReceiver<P>) {
172 self.content.graphics(receiver);
173 }
174
175 fn hittest(&self, extra: &mut WidgetExtra<'_>, point: (f32, f32)) -> bool {
176 self.content.hittest(extra, point)
177 }
178
179 fn pointer_event(
180 &mut self,
181 extra: &mut WidgetExtra<'_>,
182 event: &mut PointerEvent,
183 ) -> bool {
184 match event.action() {
185 PointerAction::Down => {
186 let grabbed = self.hittest(extra, event.pos())
187 && event.try_grab(extra.id());
188 if grabbed {
189 eprintln!("down: {:?}", self.content.get_value(&extra));
190 self.pointers_down += 1;
191 if *self.interactable {
192 *self.state = SelectionState::pressed();
193 }
194 }
195 grabbed
196 }
197 PointerAction::Move(_, _) => {
198 let ungrabbed = !self.hittest(extra, event.pos())
199 && event.try_ungrab(extra);
200 if ungrabbed {
201 self.pointers_down -= 1;
202 if self.pointers_down == 0 {
203 *self.state = self.base_state();
204 }
205 }
206 ungrabbed
207 }
208 PointerAction::GrabStolen => {
209 self.pointers_down -= 1;
210 if self.pointers_down == 0 {
211 *self.state = self.base_state();
212 }
213 true
214 }
215 PointerAction::Up => {
216 let ungrabbed = event.try_ungrab(extra.id());
217 if ungrabbed {
218 self.pointers_down -= 1;
219 if self.pointers_down == 0 {
220 if !self.currently_selected {
221 self.just_clicked = true;
222 if let Some(group) = &*self.group {
223 group.set(self.content.get_value(extra));
224 }
225 self.currently_selected = true;
226 } else if self.allow_unselect {
227 if let Some(group) = &*self.group {
228 group.unset();
229 }
230 self.currently_selected = false;
231 }
232 *self.state = self.base_state();
233 }
234 }
235 ungrabbed
236 }
237 PointerAction::Hover(_, _) => {
238 match (self.state.v1(), self.hittest(extra, event.pos())) {
239 (SelectionStateV1::Normal, true) => {
240 let grabbed = event.try_grab(extra);
241 if grabbed && *self.interactable {
242 *self.state = SelectionState::hover();
243 }
244 grabbed
245 }
246 (SelectionStateV1::Hover, false) => {
247 let ungrabbed = event.try_ungrab(extra);
248 if ungrabbed {
249 *self.state = self.base_state();
250 }
251 ungrabbed
252 }
253 _ => false,
254 }
255 }
256 _ => false,
257 }
258 }
259}
260
261impl<T: Default, V> Default for ToggleButtonContent<T, V> {
262 fn default() -> Self {
263 Self {
264 state: Watched::default(),
265 group: Watched::new(None),
266 allow_unselect: true,
267 interactable: Watched::new(true),
268 pointers_down: 0,
269 just_clicked: false,
270 currently_selected: false,
271 content: T::default(),
272 }
273 }
274}
275
276pub type ToggleButton<T, V = (), P = DefaultRenderPlatform> =
278 Widget<ToggleButtonContent<T, V>, P>;