1use std::time::Duration;
2
3use crate::{
4 animation::{easing, Animation},
5 layout::Axis,
6 math::{lerp, Pos2, Size, Space},
7 renderer::{Pixel, Rgba},
8 view::{
9 Builder, Elements, EventCtx, Handled, Interest, Layout, Palette, Render, StyleKind, Ui,
10 View, ViewEvent,
11 },
12};
13
14#[derive(Debug, Copy, Clone)]
15pub struct ToggleStyle {
16 pub track: char,
17 pub track_color: Rgba,
18 pub track_hovered: Option<Rgba>,
19
20 pub on_knob: char,
21 pub on_knob_color: Rgba,
22
23 pub off_knob: char,
24 pub off_knob_color: Rgba,
25
26 pub on_knob_hovered: Option<Rgba>,
27 pub off_knob_hovered: Option<Rgba>,
28}
29
30impl ToggleStyle {
31 pub fn default(palette: &Palette, axis: Axis, _toggled: bool) -> Self {
32 Self {
33 track: axis.main((Elements::MEDIUM_RECT, Elements::LARGE_RECT)),
34 track_color: palette.outline,
35 track_hovered: None,
36 on_knob: Elements::LARGE_RECT,
37 on_knob_color: palette.primary,
38 off_knob: Elements::LARGE_RECT,
39 off_knob_color: palette.secondary,
40 on_knob_hovered: None,
41 off_knob_hovered: None,
42 }
43 }
44
45 pub fn large(palette: &Palette, axis: Axis, toggled: bool) -> Self {
46 Self::default(palette, axis, toggled)
47 }
48
49 pub fn small_rounded(palette: &Palette, axis: Axis, toggled: bool) -> Self {
50 Self {
51 track: axis.main((
52 Elements::THICK_HORIZONTAL_LINE,
53 Elements::THICK_VERTICAL_LINE,
54 )),
55 on_knob: Elements::CIRCLE,
56 off_knob: Elements::CIRCLE,
57 ..Self::default(palette, axis, toggled)
58 }
59 }
60
61 pub fn small_diamond(palette: &Palette, axis: Axis, toggled: bool) -> Self {
62 Self {
63 track: axis.main((
64 Elements::THICK_HORIZONTAL_LINE,
65 Elements::THICK_VERTICAL_LINE,
66 )),
67 on_knob: Elements::DIAMOND,
68 off_knob: Elements::DIAMOND,
69 ..Self::default(palette, axis, toggled)
70 }
71 }
72
73 pub fn small_square(palette: &Palette, axis: Axis, toggled: bool) -> Self {
74 Self {
75 track: axis.main((
76 Elements::THICK_HORIZONTAL_LINE,
77 Elements::THICK_VERTICAL_LINE,
78 )),
79 on_knob: Elements::MEDIUM_RECT,
80 off_knob: Elements::MEDIUM_RECT,
81 ..Self::default(palette, axis, toggled)
82 }
83 }
84}
85
86pub type ToggleClass = fn(&Palette, Axis, bool) -> ToggleStyle;
87
88#[derive(Copy, Clone, Debug, PartialEq, Default)]
89pub struct ToggleResponse {
90 pub(super) changed: bool,
91}
92
93impl ToggleResponse {
94 pub const fn changed(&self) -> bool {
95 self.changed
96 }
97}
98
99#[must_use = "a view does nothing unless `show()` or `show_children()` is called"]
100pub struct ToggleSwitch<'a> {
101 value: &'a mut bool,
102 axis: Axis,
103 class: StyleKind<ToggleClass, ToggleStyle>,
104}
105
106impl<'a> ToggleSwitch<'a> {
107 pub fn new(value: &'a mut bool) -> Self {
108 Self {
109 value,
110 axis: Axis::Horizontal,
111 class: StyleKind::deferred(ToggleStyle::default),
112 }
113 }
114
115 pub fn horizontal(mut self) -> Self {
116 self.axis = Axis::Horizontal;
117 self
118 }
119
120 pub fn vertical(mut self) -> Self {
121 self.axis = Axis::Vertical;
122 self
123 }
124
125 pub fn axis(mut self, axis: Axis) -> Self {
126 self.axis = axis;
127 self
128 }
129
130 pub const fn class(mut self, class: ToggleClass) -> Self {
131 self.class = StyleKind::Deferred(class);
132 self
133 }
134
135 pub const fn style(mut self, style: ToggleStyle) -> Self {
136 self.class = StyleKind::Direct(style);
137 self
138 }
139}
140
141impl<'v> Builder<'v> for ToggleSwitch<'v> {
142 type View = ToggleSwitchView;
143}
144
145#[derive(Debug)]
146pub struct ToggleSwitchView {
147 value: bool,
148 changed: bool,
149 axis: Axis,
150 class: StyleKind<ToggleClass, ToggleStyle>,
151}
152
153impl View for ToggleSwitchView {
154 type Args<'v> = ToggleSwitch<'v>;
155 type Response = ToggleResponse;
156
157 fn create(args: Self::Args<'_>) -> Self {
158 Self {
159 value: *args.value,
160 changed: false,
161 axis: args.axis,
162 class: args.class,
163 }
164 }
165
166 fn update(&mut self, args: Self::Args<'_>, _: &Ui) -> Self::Response {
167 self.axis = args.axis;
168 self.class = args.class;
169
170 let changed = self.changed;
171 if std::mem::take(&mut self.changed) {
172 *args.value = self.value;
173 } else if self.value != *args.value {
174 self.value = *args.value;
175 };
176 ToggleResponse { changed }
177 }
178
179 fn interests(&self) -> Interest {
180 Interest::MOUSE_INSIDE
181 }
182
183 fn event(&mut self, event: ViewEvent, ctx: EventCtx) -> Handled {
184 match event {
185 ViewEvent::MouseClicked { .. } => {
186 self.value = !self.value;
187 self.changed = true;
188
189 ctx.animation.add_once(ctx.current, || {
190 Animation::new()
191 .oneshot(true)
192 .with(easing::sine_in_out)
193 .schedule(Duration::from_millis(150))
194 .unwrap()
195 });
196 }
197
198 ViewEvent::MouseDrag { delta, .. }
199 if (self.value && self.axis.main::<i32>(delta).is_negative())
200 || (!self.value && self.axis.main::<i32>(delta).is_positive()) =>
201 {
202 self.value = !self.value;
203 self.changed = true;
204
205 ctx.animation.add_once(ctx.current, || {
206 Animation::new()
207 .oneshot(true)
208 .with(easing::sine_in_out)
209 .schedule(Duration::from_millis(50))
210 .unwrap()
211 });
212 }
213
214 _ => return Handled::Bubble,
215 };
216
217 Handled::Sink
218 }
219
220 fn layout(&mut self, _: Layout, space: Space) -> Size {
221 let main = self.axis.main((4.0, 2.0));
222 space.fit(self.axis.pack(main, 1.0))
223 }
224
225 fn draw(&mut self, mut render: Render) {
226 let rect = render.rect();
227
228 let selected = self.value;
229
230 let style = match self.class {
231 StyleKind::Deferred(style) => (style)(render.palette, self.axis, selected),
232 StyleKind::Direct(style) => style,
233 };
234
235 let color = if render.is_hovered() {
236 style.track_hovered.unwrap_or(style.track_color)
237 } else {
238 style.track_color
239 };
240
241 render.fill_with(Pixel::new(style.track).fg(color));
242
243 let extent = self.axis.main::<f32>(rect.size()) - 1.0;
244
245 let x = match render.animation.get_mut(render.current) {
246 Some(animation) if selected => lerp(0.0, extent, *animation.value),
247 Some(animation) if !selected => lerp(extent, 0.0, *animation.value),
248 _ if selected => extent,
249 _ => 0.0,
250 };
251
252 let color = match (render.is_hovered(), selected) {
253 (true, true) => style.on_knob_hovered.unwrap_or(style.on_knob_color),
254 (true, false) => style.off_knob_hovered.unwrap_or(style.off_knob_color),
255 (false, true) => style.on_knob_color,
256 (false, false) => style.off_knob_color,
257 };
258
259 let knob = if selected {
260 style.on_knob
261 } else {
262 style.off_knob
263 };
264
265 let pos: Pos2 = self.axis.pack(x, 0.0);
266 render.set(pos, Pixel::new(knob).fg(color));
267 }
268}
269
270pub fn toggle_switch(value: &mut bool) -> ToggleSwitch<'_> {
271 ToggleSwitch::new(value)
272}