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.clamp(0.0, 1.0);
137
138 if let Some(callback) = &self_ref.on_changing {
139 (callback)(cx, self_ref.continuous_normal);
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::Path::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 canvas.draw_path(&path, &paint);
326 }
327}
328
329impl Handle<'_, ArcTrack> {
330 pub fn value<L: Lens<Target = f32>>(self, lens: L) -> Self {
331 let entity = self.entity;
332 Binding::new(self.cx, lens, move |cx, value| {
333 let value = value.get(cx);
334 if let Some(view) = cx.views.get_mut(&entity) {
335 if let Some(knob) = view.downcast_mut::<ArcTrack>() {
336 knob.normalized_value = value;
337 cx.needs_redraw(entity);
338 }
339 }
340 });
341
342 self
343 }
344}
345
346#[derive(Debug, Default, Copy, Clone, PartialEq)]
347pub enum KnobMode {
348 Discrete(usize),
349 #[default]
350 Continuous,
351}
352
353pub struct Ticks {
357 angle_start: f32,
358 angle_end: f32,
359 radius: Units,
360 tick_len: Units,
362 tick_width: Units,
363 mode: KnobMode,
365}
366impl Ticks {
367 pub fn new(
369 cx: &mut Context,
370 radius: Units,
371 tick_len: Units,
372 tick_width: Units,
373 arc_len: f32,
374 mode: KnobMode,
375 ) -> Handle<Self> {
376 Self {
377 angle_start: -arc_len / 2.0,
380 angle_end: arc_len / 2.0,
381 radius,
382 tick_len,
383 tick_width,
384 mode,
385 }
386 .build(cx, |_| {})
387 }
388}
389
390impl View for Ticks {
391 fn element(&self) -> Option<&'static str> {
392 Some("ticks")
393 }
394 fn draw(&self, cx: &mut DrawContext, canvas: &Canvas) {
395 let opacity = cx.opacity();
396 let foreground_color = cx.background_color();
399 let bounds = cx.bounds();
402 let centerx = bounds.x + 0.5 * bounds.w;
404 let centery = bounds.y + 0.5 * bounds.h;
405 let start = self.angle_start.to_radians() - PI / 2.0;
407 let end = self.angle_end.to_radians() - PI / 2.0;
408 let parent = cx.tree.get_parent(cx.current).unwrap();
409 let parent_width = cx.cache.get_width(parent);
410 let radius = self.radius.to_px(parent_width / 2.0, 0.0);
412 let tick_len = self.tick_len.to_px(radius, 0.0);
414 let line_width = self.tick_width.to_px(radius, 0.0);
415 let mut path = vg::Path::new();
417 match self.mode {
418 KnobMode::Continuous => return,
420 KnobMode::Discrete(steps) => {
421 for n in 0..steps {
422 let a = n as f32 / (steps - 1) as f32;
423 let angle = start + (end - start) * a;
424 path.move_to((
425 centerx + angle.cos() * (radius - tick_len),
426 centery + angle.sin() * (radius - tick_len),
427 ));
428 path.line_to((
429 centerx + angle.cos() * (radius - line_width / 2.0),
430 centery + angle.sin() * (radius - line_width / 2.0),
431 ));
432 }
433 }
434 }
435 let mut paint = vg::Paint::default();
436 paint.set_color(foreground_color);
437 paint.set_stroke_width(line_width);
438 paint.set_stroke_cap(vg::PaintCap::Round);
439 paint.set_style(vg::PaintStyle::Stroke);
440 canvas.draw_path(&path, &paint);
441 }
442}
443
444pub struct TickKnob {
446 angle_start: f32,
447 angle_end: f32,
448 radius: Units,
449 tick_width: Units,
450 tick_len: Units,
451 normalized_value: f32,
452 mode: KnobMode,
453}
454impl TickKnob {
455 pub fn new(
457 cx: &mut Context,
458 radius: Units,
459 tick_width: Units,
460 tick_len: Units,
461 arc_len: f32,
462 mode: KnobMode,
464 ) -> Handle<Self> {
465 Self {
466 angle_start: -arc_len / 2.0,
469 angle_end: arc_len / 2.0,
470 radius,
471 tick_width,
472 tick_len,
473 normalized_value: 0.5,
474 mode,
475 }
476 .build(cx, |_| {})
477 }
478}
479
480impl View for TickKnob {
481 fn element(&self) -> Option<&'static str> {
482 Some("tickknob")
483 }
484 fn draw(&self, cx: &mut DrawContext, canvas: &Canvas) {
485 let opacity = cx.opacity();
486 let foreground_color = cx.font_color();
489 let background_color = cx.background_color();
490 let bounds = cx.bounds();
492 let centerx = bounds.x + 0.5 * bounds.w;
494 let centery = bounds.y + 0.5 * bounds.h;
495 let start = self.angle_start.to_radians() - PI / 2.0;
497 let end = self.angle_end.to_radians() - PI / 2.0;
498 let parent = cx.tree.get_parent(cx.current).unwrap();
499 let parent_width = cx.cache.get_width(parent);
500 let radius = self.radius.to_px(parent_width / 2.0, 0.0);
502 let tick_width = self.tick_width.to_px(radius, 0.0);
503 let tick_len = self.tick_len.to_px(radius, 0.0);
504 let mut path = vg::Path::new();
506 path.add_circle((centerx, centery), radius, None);
507 let mut paint = vg::Paint::default();
509 paint.set_color(background_color);
510 paint.set_stroke_width(tick_width);
511 paint.set_stroke_cap(vg::PaintCap::Round);
512 paint.set_style(vg::PaintStyle::Stroke);
513 canvas.draw_path(&path, &paint);
514 let mut path = vg::Path::new();
516 let angle = match self.mode {
517 KnobMode::Continuous => start + (end - start) * self.normalized_value,
518 KnobMode::Discrete(steps) => {
520 start
521 + (end - start) * (self.normalized_value * (steps - 1) as f32).floor()
522 / (steps - 1) as f32
523 }
524 };
525 path.move_to(
526 (
528 centerx + angle.cos() * (radius - tick_len),
529 centery + angle.sin() * (radius - tick_len),
530 ),
531 );
532 path.line_to((
533 centerx + angle.cos() * (radius - tick_width / 2.0),
534 centery + angle.sin() * (radius - tick_width / 2.0),
535 ));
536 let mut paint = vg::Paint::default();
537 paint.set_color(foreground_color);
538 paint.set_stroke_width(tick_width);
539 paint.set_stroke_cap(vg::PaintCap::Round);
540 paint.set_style(vg::PaintStyle::Stroke);
541 canvas.draw_path(&path, &paint);
542 }
543}
544
545impl Handle<'_, TickKnob> {
546 pub fn value<L: Lens<Target = f32>>(self, lens: L) -> Self {
547 let entity = self.entity;
548 Binding::new(self.cx, lens, move |cx, value| {
549 let value = value.get(cx);
550 if let Some(view) = cx.views.get_mut(&entity) {
551 if let Some(knob) = view.downcast_mut::<TickKnob>() {
552 knob.normalized_value = value;
553 cx.needs_redraw(entity);
554 }
555 }
556 });
557 self
558 }
559}