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