1use std::f32::consts::PI;
2
3use glam::Vec2;
4use ori_graphics::{Curve, Quad, Rect};
5use ori_macro::Build;
6
7use crate::{
8 BoxConstraints, Context, DrawContext, Event, EventContext, LayoutContext, PointerEvent,
9 SharedSignal, Style, View,
10};
11
12#[derive(Clone, Debug, Build)]
13pub struct Knob {
14 #[bind]
15 value: SharedSignal<f32>,
16 #[prop]
17 max: f32,
18 #[prop]
19 min: f32,
20 #[prop]
21 label: String,
22}
23
24impl Default for Knob {
25 fn default() -> Self {
26 Self {
27 value: SharedSignal::new(0.0),
28 max: 1.0,
29 min: 0.0,
30 label: String::new(),
31 }
32 }
33}
34
35impl View for Knob {
36 type State = Option<Vec2>;
37
38 fn build(&self) -> Self::State {
39 None
40 }
41
42 fn style(&self) -> Style {
43 Style::new("knob")
44 }
45
46 fn event(&self, state: &mut Self::State, cx: &mut EventContext, event: &Event) {
47 if let Some(pointer_event) = event.get::<PointerEvent>() {
48 if pointer_event.is_press() && cx.hovered() {
49 cx.activate();
50 } else if pointer_event.is_release() && cx.active() {
51 *state = None;
52 cx.deactivate();
53 }
54
55 if cx.active() {
56 if let Some(prev_position) = *state {
57 let delta = pointer_event.position - prev_position;
58 let delta = delta.x - delta.y * delta.length();
59 let range = self.max - self.min;
60 let delta = delta / cx.rect().width() * range * 0.15;
61 let value = self.value.get();
62 let new_value = f32::clamp(*value + delta, self.min, self.max);
63 self.value.set(new_value);
64 }
65
66 *state = Some(pointer_event.position);
67 cx.request_redraw();
68 }
69 }
70 }
71
72 fn layout(&self, _state: &mut Self::State, cx: &mut LayoutContext, bc: BoxConstraints) -> Vec2 {
73 let size = cx.style_range("size", bc.min.max_element()..bc.max.min_element());
74 bc.constrain(Vec2::splat(size))
75 }
76
77 fn draw(&self, _state: &mut Self::State, cx: &mut DrawContext) {
78 let size = cx.rect().size().min_element();
79
80 let diameter = size * 0.7;
81
82 let circle = Quad {
83 rect: Rect::center_size(cx.rect().center(), Vec2::splat(diameter)),
84 background: cx.style("background-color"),
85 border_radius: [diameter * 0.5; 4],
86 border_width: cx.style_range("border-width", 0.0..diameter * 0.5),
87 border_color: cx.style("border-color"),
88 };
89
90 cx.draw(circle);
91
92 let curve = Curve::arc(cx.rect().center(), diameter * 0.65, -PI * 1.25, PI * 0.25);
93 let mesh = curve.rounded_mesh(diameter * 0.075, cx.style("background-color"));
94 cx.draw(mesh);
95
96 let range = self.max - self.min;
97 let value = self.value.get();
98 let angle = (*value - self.min) / range;
99 let angle = -PI * 1.25 + angle * PI * 1.5;
100
101 let curve = Curve::arc(cx.rect().center(), diameter * 0.65, -PI * 1.25, angle);
102 let mesh = curve.rounded_mesh(diameter * 0.075, cx.style("color"));
103 cx.draw(mesh);
104
105 let mut arm = Curve::new();
106 arm.add_point(cx.rect().center());
107 arm.add_point(cx.rect().center() + Vec2::new(angle.cos(), angle.sin()) * diameter * 0.65);
108
109 let mesh = arm.rounded_mesh(diameter * 0.075, cx.style("color"));
110 cx.draw(mesh);
111 }
112}