use crate::app::AppEvent; use params::ParamId; use std::f32::consts::PI; use vizia::prelude::*; use vizia::vg; const ARC_START: f32 = PI * 0.75; const ARC_RANGE: f32 = PI * 1.5; pub struct TenkoKnob { pub param: ParamId, value: f32, drag_origin_y: Option, drag_origin_v: f32, } impl TenkoKnob { pub fn new(cx: &mut Context, param: ParamId) -> Handle { let init = cx .data::() .map(|d| d.params[param as usize]) .unwrap_or(param.default_value()); Self { param, value: init, drag_origin_y: None, drag_origin_v: 0.0, } .build(cx, |_| {}) .bind( crate::app::AppData::params.idx(param as usize), |h, lens| { let v = lens.get(&h); h.modify(|k: &mut TenkoKnob| k.value = v); }, ) .width(Pixels(46.0)) .height(Pixels(46.0)) .cursor(CursorIcon::NsResize) } } impl View for TenkoKnob { fn element(&self) -> Option<&'static str> { Some("tenko-knob") } fn event(&mut self, cx: &mut EventContext, event: &mut Event) { event.map(|we: &WindowEvent, _| match we { WindowEvent::MouseDown(MouseButton::Left) => { cx.capture(); self.drag_origin_y = Some(cx.mouse().cursor_y); self.drag_origin_v = self.value; } WindowEvent::MouseUp(MouseButton::Left) => { cx.release(); self.drag_origin_y = None; } WindowEvent::MouseMove(_, y) => { if let Some(oy) = self.drag_origin_y { let sens = if cx.modifiers().shift() { 0.0015 } else { 0.004 }; let new_v = (self.drag_origin_v + (oy - y) * sens).clamp(0.0, 1.0); cx.emit(AppEvent::SetParam(self.param, new_v)); } } WindowEvent::MouseDoubleClick(MouseButton::Left) => { cx.emit(AppEvent::SetParam(self.param, self.param.default_value())); } _ => {} }); } fn draw(&self, cx: &mut DrawContext, canvas: &Canvas) { let b = cx.bounds(); let kx = b.x + b.w * 0.5; let ky = b.y + b.h * 0.5; let r = b.w.min(b.h) * 0.5 - 5.0; let val_end = ARC_START + self.value * ARC_RANGE; let mut paint = vg::Paint::default(); paint.set_anti_alias(true); paint.set_style(vg::PaintStyle::Stroke); paint.set_stroke_width(3.5); paint.set_stroke_cap(vg::paint::Cap::Round); paint.set_color(vg::Color::from_argb(255, 45, 45, 65)); let track_rect = vg::Rect::from_xywh(kx - r, ky - r, r * 2.0, r * 2.0); let mut path = vg::Path::new(); path.add_arc(track_rect, ARC_START.to_degrees(), ARC_RANGE.to_degrees()); canvas.draw_path(&path, &paint); if self.value > 0.001 { paint.set_color(vg::Color::from_argb(255, 255, 120, 48)); let mut vpath = vg::Path::new(); vpath.add_arc( track_rect, ARC_START.to_degrees(), (self.value * ARC_RANGE).to_degrees(), ); canvas.draw_path(&vpath, &paint); } paint.set_style(vg::PaintStyle::Fill); paint.set_color(vg::Color::from_argb(255, 30, 30, 48)); canvas.draw_circle((kx, ky), r - 6.0, &paint); let px = kx + val_end.cos() * (r - 9.0); let py = ky + val_end.sin() * (r - 9.0); paint.set_style(vg::PaintStyle::Stroke); paint.set_stroke_width(2.0); paint.set_color(vg::Color::from_argb(255, 220, 220, 235)); let mut ptr = vg::Path::new(); ptr.move_to((kx, ky)); ptr.line_to((px, py)); canvas.draw_path(&ptr, &paint); } } pub fn labeled_knob(cx: &mut Context, param: ParamId) { VStack::new(cx, |cx| { TenkoKnob::new(cx, param); Label::new(cx, param.label()) .class("knob-label") .text_align(TextAlign::Center) .width(Stretch(1.0)); }) .width(Pixels(50.0)) .height(Pixels(62.0)) .padding(Pixels(2.0)); }