use params::{ParamId, ParamStore}; use vizia::prelude::*; use crate::widgets::piano::PianoKeyboard; #[derive(Clone, Copy, Debug, PartialEq, Data)] pub enum Panel { Osc, Env, Lfo, Filter, Fx, ModMatrix, } impl Panel { pub const ALL: &'static [Self] = &[ Self::Osc, Self::Env, Self::Lfo, Self::Filter, Self::Fx, Self::ModMatrix, ]; pub fn label(self) -> &'static str { match self { Self::Osc => "OSC", Self::Env => "ENV", Self::Lfo => "LFO", Self::Filter => "FLT", Self::Fx => "FX", Self::ModMatrix => "MOD", } } } #[derive(Lens, Clone)] pub struct AppData { pub params: Vec, pub active_panel: Panel, pub preset_name: String, pub voice_count: u8, pub cpu_load: f32, pub host_bpm: f32, #[lens(ignore)] pub store: ParamStore, pub held_notes: Vec, pub octave: i32, } impl AppData { pub fn new(store: ParamStore) -> Self { let params = (0..ParamId::COUNT) .map(|i| store.get(unsafe { std::mem::transmute::(i) })) .collect(); Self { params, store, active_panel: Panel::Osc, preset_name: "Init".into(), voice_count: 0, cpu_load: 0.0, host_bpm: 120.0, held_notes: Vec::new(), octave: 4, } } } #[derive(Debug)] pub enum AppEvent { SetParam(ParamId, f32), SetPanel(Panel), UpdateMetrics { voices: u8, cpu: f32 }, NoteOn(u8, u8), NoteOff(u8), OctaveUp, OctaveDown, } impl Model for AppData { fn event(&mut self, cx: &mut EventContext, event: &mut Event) { event.map(|e: &AppEvent, _| match e { AppEvent::SetParam(id, val) => { let v = val.clamp(0.0, 1.0); self.params[*id as usize] = v; self.store.set(*id, v); } AppEvent::SetPanel(p) => self.active_panel = *p, AppEvent::UpdateMetrics { voices, cpu } => { self.voice_count = *voices; self.cpu_load = *cpu; } AppEvent::NoteOn(note, _vel) => { if !self.held_notes.contains(note) { self.held_notes.push(*note); } } AppEvent::NoteOff(note) => self.held_notes.retain(|n| n != note), AppEvent::OctaveUp => self.octave = (self.octave + 1).min(8), AppEvent::OctaveDown => self.octave = (self.octave - 1).max(0), }); event.map(|we: &WindowEvent, _| { crate::widgets::piano::handle_kbd(cx, we, self.octave); }); } } pub fn build_root(cx: &mut Context) { VStack::new(cx, |cx| { crate::panels::header::build(cx); HStack::new(cx, |cx| { VStack::new(cx, |cx| { for &p in Panel::ALL { tab_button(cx, p); } }) .width(Pixels(56.0)) .height(Stretch(1.0)) .background_color(Color::rgb(14, 14, 26)); Binding::new(cx, AppData::active_panel, |cx, panel_lens| { VStack::new(cx, |cx| match panel_lens.get(cx) { Panel::Osc => crate::panels::osc::build(cx), Panel::Env => crate::panels::env_panel::build(cx), Panel::Lfo => crate::panels::lfo::build(cx), Panel::Filter => crate::panels::filter::build(cx), Panel::Fx => crate::panels::fx::build(cx), Panel::ModMatrix => crate::panels::mod_matrix::build(cx), }) .class("panel") .width(Stretch(1.0)) .height(Stretch(1.0)); }); crate::panels::macro_bar::build(cx); }) .height(Stretch(1.0)); PianoKeyboard::new(cx); }) .background_color(Color::rgb(18, 18, 28)) .width(Stretch(1.0)) .height(Stretch(1.0)); } fn tab_button(cx: &mut Context, p: Panel) { Binding::new(cx, AppData::active_panel, move |cx, active_lens| { let al = active_lens.get(cx) == p; Label::new(cx, p.label()) .class("tab") .checked(al) .on_press(move |cx| cx.emit(AppEvent::SetPanel(p))) .width(Stretch(1.0)) .height(Pixels(44.0)) .text_align(TextAlign::Center); }); }