1#![allow(dead_code)]
2#![allow(unused_imports)]
3#![allow(unused_variables)]
4use crate::vg;
5use accesskit::ActionData;
6use morphorm::Units;
7
8use crate::prelude::*;
9
10static DEFAULT_DRAG_SCALAR: f32 = 0.0042;
11static DEFAULT_WHEEL_SCALAR: f32 = 0.005;
12static DEFAULT_ARROW_SCALAR: f32 = 0.1;
13static DEFAULT_MODIFIER_SCALAR: f32 = 0.04;
14
15use std::{default, f32::consts::PI};
16
17pub struct Knob<T> {
19 value: T,
20 default_normal: f32,
21
22 is_dragging: bool,
23 prev_drag_y: f32,
24 continuous_normal: f32,
25
26 drag_scalar: f32,
27 wheel_scalar: f32,
28 arrow_scalar: f32,
29 modifier_scalar: f32,
30
31 on_changing: Option<Box<dyn Fn(&mut EventContext, f32)>>,
32}
33
34impl<R: Res<f32> + Clone + 'static> Knob<R> {
35 pub fn new(
37 cx: &mut Context,
38 normalized_default: impl Res<f32>,
39 value: R,
40 centered: bool,
41 ) -> Handle<Self> {
42 let value_for_track = value.clone().to_signal(cx);
43 let value_for_head = value.clone().to_signal(cx);
44
45 Self {
46 value: value.clone(),
47 default_normal: normalized_default.get_value(cx),
48
49 is_dragging: false,
50 prev_drag_y: 0.0,
51 continuous_normal: value.get_value(cx),
52
53 drag_scalar: DEFAULT_DRAG_SCALAR,
54 wheel_scalar: DEFAULT_WHEEL_SCALAR,
55 arrow_scalar: DEFAULT_ARROW_SCALAR,
56 modifier_scalar: DEFAULT_MODIFIER_SCALAR,
57
58 on_changing: None,
59 }
60 .build(cx, move |cx| {
61 ZStack::new(cx, move |cx| {
62 ArcTrack::new(
63 cx,
64 centered,
65 Percentage(100.0),
66 Percentage(15.0),
67 -240.,
68 60.,
69 KnobMode::Continuous,
70 )
71 .value(value_for_track)
72 .class("knob-track");
73
74 HStack::new(cx, |cx| {
75 Element::new(cx).class("knob-tick");
76 })
77 .bind(value_for_head, move |handle| {
78 let value = value_for_head.get();
79 handle.rotate(Angle::Deg(value * 300.0 - 150.0));
80 })
81 .class("knob-head");
82 });
83 })
84 .navigable(true)
85 .role(Role::Slider)
86 .numeric_value(value_for_track.map(|val| (*val as f64 * 100.0).round()))
87 }
88}
89
90impl<R: Res<f32> + Clone + 'static> Knob<R> {
91 pub fn custom<F, V: View>(
93 cx: &mut Context,
94 default_normal: f32,
95 value: R,
96 content: F,
97 ) -> Handle<'_, Self>
98 where
99 F: 'static + Fn(&mut Context, R) -> Handle<V>,
100 {
101 let value_for_content = value.clone();
102
103 Self {
104 value: value.clone(),
105 default_normal,
106
107 is_dragging: false,
108 prev_drag_y: 0.0,
109 continuous_normal: value.get_value(cx),
110
111 drag_scalar: DEFAULT_DRAG_SCALAR,
112 wheel_scalar: DEFAULT_WHEEL_SCALAR,
113 arrow_scalar: DEFAULT_ARROW_SCALAR,
114 modifier_scalar: DEFAULT_MODIFIER_SCALAR,
115
116 on_changing: None,
117 }
118 .build(cx, move |cx| {
119 ZStack::new(cx, move |cx| {
120 (content)(cx, value_for_content.clone())
121 .width(Percentage(100.0))
122 .height(Percentage(100.0));
123 });
124 })
125 }
126}
127
128impl<T: Res<f32> + 'static> Handle<'_, Knob<T>> {
129 pub fn on_change<F>(self, callback: F) -> Self
131 where
132 F: 'static + Fn(&mut EventContext, f32),
133 {
134 if let Some(view) = self.cx.views.get_mut(&self.entity) {
135 if let Some(knob) = view.downcast_mut::<Knob<T>>() {
136 knob.on_changing = Some(Box::new(callback));
137 }
138 }
139
140 self
141 }
142}
143
144impl<T: Res<f32> + 'static> View for Knob<T> {
145 fn element(&self) -> Option<&'static str> {
146 Some("knob")
147 }
148
149 fn accessibility(&self, _cx: &mut AccessContext, node: &mut AccessNode) {
150 node.set_min_numeric_value(0.0);
151 node.set_max_numeric_value(100.0);
152 }
153
154 fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
155 let move_virtual_slider = |self_ref: &mut Self, cx: &mut EventContext, new_normal: f32| {
156 self_ref.continuous_normal = new_normal;
157
158 if let Some(callback) = &self_ref.on_changing {
159 (callback)(cx, self_ref.continuous_normal.clamp(0.0, 1.0));
160 }
161 };
162
163 event.map(|window_event, _| match window_event {
164 WindowEvent::MouseDown(button) if *button == MouseButton::Left => {
165 self.is_dragging = true;
166 self.prev_drag_y = cx.mouse.left.pos_down.1;
167
168 cx.capture();
169 cx.focus_with_visibility(false);
170
171 self.continuous_normal = self.value.get_value(cx);
172 }
173
174 WindowEvent::MouseUp(button) if *button == MouseButton::Left => {
175 self.is_dragging = false;
176
177 self.continuous_normal = self.value.get_value(cx);
178
179 cx.release();
180 }
181
182 WindowEvent::MouseMove(_, y) => {
183 if self.is_dragging && !cx.is_disabled() {
184 let mut delta_normal = (*y - self.prev_drag_y) * self.drag_scalar;
185
186 self.prev_drag_y = *y;
187
188 if cx.modifiers.shift() {
189 delta_normal *= self.modifier_scalar;
190 }
191
192 let new_normal = self.continuous_normal - delta_normal;
193
194 move_virtual_slider(self, cx, new_normal);
195 }
196 }
197
198 WindowEvent::MouseScroll(_, y) => {
199 if *y != 0.0 {
200 let delta_normal = -*y * self.wheel_scalar;
201
202 let new_normal = self.continuous_normal - delta_normal;
203
204 move_virtual_slider(self, cx, new_normal);
205 }
206 }
207
208 WindowEvent::MouseDoubleClick(button) if *button == MouseButton::Left => {
209 self.is_dragging = false;
210
211 move_virtual_slider(self, cx, self.default_normal);
212 }
213
214 WindowEvent::KeyDown(Code::ArrowUp | Code::ArrowRight, _) => {
215 self.continuous_normal = self.value.get_value(cx);
216 move_virtual_slider(self, cx, self.continuous_normal + self.arrow_scalar);
217 }
218
219 WindowEvent::KeyDown(Code::ArrowDown | Code::ArrowLeft, _) => {
220 self.continuous_normal = self.value.get_value(cx);
221 move_virtual_slider(self, cx, self.continuous_normal - self.arrow_scalar);
222 }
223
224 WindowEvent::ActionRequest(action) => match action.action {
225 Action::Increment => {
226 self.continuous_normal = self.value.get_value(cx);
227 move_virtual_slider(self, cx, self.continuous_normal + self.arrow_scalar);
228 }
229
230 Action::Decrement => {
231 self.continuous_normal = self.value.get_value(cx);
232 move_virtual_slider(self, cx, self.continuous_normal - self.arrow_scalar);
233 }
234
235 Action::SetValue => {
236 if let Some(ActionData::NumericValue(val)) = action.data {
237 let val = (val as f32).clamp(0.0, 1.0);
238 move_virtual_slider(self, cx, val);
239 }
240 }
241
242 _ => {}
243 },
244
245 _ => {}
246 });
247 }
248}
249
250pub struct ArcTrack {
252 angle_start: f32,
253 angle_end: f32,
254 radius: Units,
255 span: Units,
256 normalized_value: f32,
257
258 center: bool,
259 mode: KnobMode,
260}
261
262impl ArcTrack {
263 pub fn new(
265 cx: &mut Context,
266 center: bool,
267 radius: Units,
268 span: Units,
269 angle_start: f32,
270 angle_end: f32,
271 mode: KnobMode,
272 ) -> Handle<Self> {
273 Self {
274 angle_start,
277 angle_end,
278 radius,
279 span,
280
281 normalized_value: 0.5,
282
283 center,
284 mode,
285 }
286 .build(cx, |_| {})
287 }
288}
289
290impl View for ArcTrack {
291 fn element(&self) -> Option<&'static str> {
292 Some("arctrack")
293 }
294
295 fn draw(&self, cx: &mut DrawContext, canvas: &Canvas) {
296 let opacity = cx.opacity();
297
298 let foreground_color = cx.font_color();
299
300 let background_color = cx.background_color();
301
302 let bounds = cx.bounds();
303
304 let centerx = bounds.x + 0.5 * bounds.w;
306 let centery = bounds.y + 0.5 * bounds.h;
307
308 let start = self.angle_start;
310 let end = self.angle_end;
311
312 let parent = cx.tree.get_parent(cx.current).unwrap();
313
314 let parent_width = cx.cache.get_width(parent);
315
316 let radius = self.radius.to_px(parent_width / 2.0, 0.0);
318 let span = self.span.to_px(radius, 0.0);
320
321 let path = vg::Path::new();
323 let oval = vg::Rect::new(bounds.left(), bounds.top(), bounds.right(), bounds.bottom());
325
326 let mut paint = vg::Paint::default();
327 paint.set_color(background_color);
328 paint.set_stroke_width(span);
329 paint.set_stroke_cap(vg::PaintCap::Round);
330 paint.set_style(vg::PaintStyle::Stroke);
331 canvas.draw_arc(oval, start, end - start, true, &paint);
333
334 let mut path = vg::PathBuilder::new();
336
337 let value = match self.mode {
338 KnobMode::Continuous => self.normalized_value,
339 KnobMode::Discrete(steps) => {
341 (self.normalized_value * (steps - 1) as f32).floor() / (steps - 1) as f32
342 }
343 };
344
345 if self.center {
346 let center = -90.0;
347
348 if value <= 0.5 {
349 let current = value * 2.0 * (center - start) + start;
350 path.arc_to(oval.with_inset((span / 2.0, span / 2.0)), start, current, false);
351 } else {
352 let current = (value * 2.0 - 1.0) * (end - center);
353 path.arc_to(oval.with_inset((span / 2.0, span / 2.0)), center, current, false);
354 }
355 } else {
356 let current = value * (end - start) + start;
357 path.arc_to(oval.with_inset((span / 2.0, span / 2.0)), start, current - start, false);
358 }
359
360 let mut paint = vg::Paint::default();
361 paint.set_color(foreground_color);
362 paint.set_stroke_width(span);
363 paint.set_stroke_cap(vg::PaintCap::Round);
364 paint.set_style(vg::PaintStyle::Stroke);
365 paint.set_anti_alias(true);
366 let path = path.detach();
367 canvas.draw_path(&path, &paint);
368 }
369}
370
371impl Handle<'_, ArcTrack> {
372 pub fn value<R: Res<f32>>(self, value: R) -> Self {
373 let entity = self.entity;
374 value.set_or_bind(self.cx, move |cx, value| {
375 let value = Res::get_value(&value, cx);
376 if let Some(view) = cx.views.get_mut(&entity) {
377 if let Some(knob) = view.downcast_mut::<ArcTrack>() {
378 knob.normalized_value = value;
379 cx.needs_redraw(entity);
380 }
381 }
382 });
383
384 self
385 }
386}
387
388#[derive(Debug, Default, Copy, Clone, PartialEq)]
389pub enum KnobMode {
390 Discrete(usize),
391 #[default]
392 Continuous,
393}
394
395pub struct Ticks {
399 angle_start: f32,
400 angle_end: f32,
401 radius: Units,
402 tick_len: Units,
404 tick_width: Units,
405 mode: KnobMode,
407}
408impl Ticks {
409 pub fn new(
411 cx: &mut Context,
412 radius: Units,
413 tick_len: Units,
414 tick_width: Units,
415 arc_len: f32,
416 mode: KnobMode,
417 ) -> Handle<Self> {
418 Self {
419 angle_start: -arc_len / 2.0,
422 angle_end: arc_len / 2.0,
423 radius,
424 tick_len,
425 tick_width,
426 mode,
427 }
428 .build(cx, |_| {})
429 }
430}
431
432impl View for Ticks {
433 fn element(&self) -> Option<&'static str> {
434 Some("ticks")
435 }
436 fn draw(&self, cx: &mut DrawContext, canvas: &Canvas) {
437 let opacity = cx.opacity();
438 let foreground_color = cx.background_color();
441 let bounds = cx.bounds();
444 let centerx = bounds.x + 0.5 * bounds.w;
446 let centery = bounds.y + 0.5 * bounds.h;
447 let start = self.angle_start.to_radians() - PI / 2.0;
449 let end = self.angle_end.to_radians() - PI / 2.0;
450 let parent = cx.tree.get_parent(cx.current).unwrap();
451 let parent_width = cx.cache.get_width(parent);
452 let radius = self.radius.to_px(parent_width / 2.0, 0.0);
454 let tick_len = self.tick_len.to_px(radius, 0.0);
456 let line_width = self.tick_width.to_px(radius, 0.0);
457 let mut path = vg::PathBuilder::new();
459 match self.mode {
460 KnobMode::Continuous => return,
462 KnobMode::Discrete(steps) => {
463 for n in 0..steps {
464 let a = n as f32 / (steps - 1) as f32;
465 let angle = start + (end - start) * a;
466 path.move_to((
467 centerx + angle.cos() * (radius - tick_len),
468 centery + angle.sin() * (radius - tick_len),
469 ));
470 path.line_to((
471 centerx + angle.cos() * (radius - line_width / 2.0),
472 centery + angle.sin() * (radius - line_width / 2.0),
473 ));
474 }
475 }
476 }
477 let mut paint = vg::Paint::default();
478 paint.set_color(foreground_color);
479 paint.set_stroke_width(line_width);
480 paint.set_stroke_cap(vg::PaintCap::Round);
481 paint.set_style(vg::PaintStyle::Stroke);
482 let path = path.detach();
483 canvas.draw_path(&path, &paint);
484 }
485}
486
487pub struct TickKnob {
489 angle_start: f32,
490 angle_end: f32,
491 radius: Units,
492 tick_width: Units,
493 tick_len: Units,
494 normalized_value: f32,
495 mode: KnobMode,
496}
497impl TickKnob {
498 pub fn new(
500 cx: &mut Context,
501 radius: Units,
502 tick_width: Units,
503 tick_len: Units,
504 arc_len: f32,
505 mode: KnobMode,
507 ) -> Handle<Self> {
508 Self {
509 angle_start: -arc_len / 2.0,
512 angle_end: arc_len / 2.0,
513 radius,
514 tick_width,
515 tick_len,
516 normalized_value: 0.5,
517 mode,
518 }
519 .build(cx, |_| {})
520 }
521}
522
523impl View for TickKnob {
524 fn element(&self) -> Option<&'static str> {
525 Some("tickknob")
526 }
527 fn draw(&self, cx: &mut DrawContext, canvas: &Canvas) {
528 let opacity = cx.opacity();
529 let foreground_color = cx.font_color();
532 let background_color = cx.background_color();
533 let bounds = cx.bounds();
535 let centerx = bounds.x + 0.5 * bounds.w;
537 let centery = bounds.y + 0.5 * bounds.h;
538 let start = self.angle_start.to_radians() - PI / 2.0;
540 let end = self.angle_end.to_radians() - PI / 2.0;
541 let parent = cx.tree.get_parent(cx.current).unwrap();
542 let parent_width = cx.cache.get_width(parent);
543 let radius = self.radius.to_px(parent_width / 2.0, 0.0);
545 let tick_width = self.tick_width.to_px(radius, 0.0);
546 let tick_len = self.tick_len.to_px(radius, 0.0);
547 let mut path = vg::PathBuilder::new();
549 path.add_circle((centerx, centery), radius, None);
550 let mut paint = vg::Paint::default();
552 paint.set_color(background_color);
553 paint.set_stroke_width(tick_width);
554 paint.set_stroke_cap(vg::PaintCap::Round);
555 paint.set_style(vg::PaintStyle::Stroke);
556 let path = path.detach();
557 canvas.draw_path(&path, &paint);
558 let mut path = vg::PathBuilder::new();
560 let angle = match self.mode {
561 KnobMode::Continuous => start + (end - start) * self.normalized_value,
562 KnobMode::Discrete(steps) => {
564 start
565 + (end - start) * (self.normalized_value * (steps - 1) as f32).floor()
566 / (steps - 1) as f32
567 }
568 };
569 path.move_to(
570 (
572 centerx + angle.cos() * (radius - tick_len),
573 centery + angle.sin() * (radius - tick_len),
574 ),
575 );
576 path.line_to((
577 centerx + angle.cos() * (radius - tick_width / 2.0),
578 centery + angle.sin() * (radius - tick_width / 2.0),
579 ));
580 let mut paint = vg::Paint::default();
581 paint.set_color(foreground_color);
582 paint.set_stroke_width(tick_width);
583 paint.set_stroke_cap(vg::PaintCap::Round);
584 paint.set_style(vg::PaintStyle::Stroke);
585 let path = path.detach();
586 canvas.draw_path(&path, &paint);
587 }
588}
589
590impl Handle<'_, TickKnob> {
591 pub fn value<R: Res<f32>>(self, value: R) -> Self {
592 let entity = self.entity;
593 value.set_or_bind(self.cx, move |cx, value| {
594 let value = Res::get_value(&value, cx);
595 if let Some(view) = cx.views.get_mut(&entity) {
596 if let Some(knob) = view.downcast_mut::<TickKnob>() {
597 knob.normalized_value = value;
598 cx.needs_redraw(entity);
599 }
600 }
601 });
602 self
603 }
604}