diff --git a/flake.nix b/flake.nix index 59923e4..78d1911 100644 --- a/flake.nix +++ b/flake.nix @@ -23,6 +23,7 @@ pkg-config rust-analyzer rustfmt + rustc cargo clippy ]; diff --git a/src/daw.rs b/src/daw.rs index 6f6bc5f..b338b78 100644 --- a/src/daw.rs +++ b/src/daw.rs @@ -1,4 +1,6 @@ -use gpui::{AppContext, Context, ScrollHandle, Task, point, px}; +use gpui::{AppContext, Context, Entity, ScrollHandle, Task, point, px}; + +use crate::ui::core::slider::Slider; use std::time::{Duration, Instant}; use crate::audio::{AudioEngine, SampleType}; @@ -25,10 +27,12 @@ pub struct Daw { pub bpm_drag_origin: Option<(f32, u32)>, /// Scroll handle for the pattern editor — used to auto-follow the playhead. pub pattern_scroll: ScrollHandle, + pub master_vol: f32, + pub master_slider: Entity, } impl Daw { - pub fn new() -> Self { + pub fn new(cx: &mut gpui::Context) -> Self { let audio = AudioEngine::start(); if audio.is_none() { eprintln!("warning: no audio device found, sequencer will be silent"); @@ -98,6 +102,18 @@ impl Daw { selected_track: None, bpm_drag_origin: None, pattern_scroll: ScrollHandle::default(), + master_vol: 0.72, + master_slider: { + let daw_entity = cx.entity(); + cx.new(|_| { + Slider::new(0.72).on_change(move |val, app| { + app.update_entity(&daw_entity, |daw, cx| { + daw.master_vol = val; + cx.notify(); + }); + }) + }) + }, tracks: vec![ Track { name: "Kick".into(), diff --git a/src/main.rs b/src/main.rs index 6dcf695..fdc2a74 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,7 +24,7 @@ fn main() { ..Default::default() }, move |_, cx| { - let entity = cx.new(|_| Daw::new()); + let entity = cx.new(|cx| Daw::new(cx)); *slot_fill.borrow_mut() = Some(entity.clone()); entity }, diff --git a/src/ui/core/mod.rs b/src/ui/core/mod.rs new file mode 100644 index 0000000..1e33ba1 --- /dev/null +++ b/src/ui/core/mod.rs @@ -0,0 +1 @@ +pub mod slider; diff --git a/src/ui/core/slider.rs b/src/ui/core/slider.rs new file mode 100644 index 0000000..7945270 --- /dev/null +++ b/src/ui/core/slider.rs @@ -0,0 +1,91 @@ +use std::sync::Arc; + +use gpui::{ + App, AppContext as _, DragMoveEvent, Empty, Entity, InteractiveElement as _, IntoElement, + ParentElement as _, Render, StatefulInteractiveElement as _, Styled as _, Window, div, px, + relative, rgb, +}; + +use crate::theme::{ACCENT_DIM, BORDER}; + +struct SliderDrag; + +pub struct Slider { + value: f32, // 0.0..=1.0 + on_change: Option>, +} + +impl Slider { + pub fn new(value: f32) -> Self { + Self { + value: value.clamp(0.0, 1.0), + on_change: None, + } + } + + pub fn on_change(mut self, f: impl Fn(f32, &mut App) + Send + Sync + 'static) -> Self { + self.on_change = Some(Arc::new(f)); + self + } + + pub fn value(&self) -> f32 { + self.value + } +} + +impl Render for Slider { + fn render( + &mut self, + _window: &mut gpui::Window, + cx: &mut gpui::Context, + ) -> impl IntoElement { + let slider_entity: Entity = cx.entity(); + + div() + .id(("slider", slider_entity.entity_id())) + .w_full() + .h(px(20.)) + .flex() + .items_center() + .cursor_pointer() + .on_drag(SliderDrag, |_, _, _, cx| cx.new(|_| Empty)) + .on_drag_move( + move |ev: &DragMoveEvent, _window: &mut Window, app: &mut App| { + let x = ev.event.position.x; + let bounds = ev.bounds; + + let width = f32::from(bounds.size.width); + if width <= 0.0 { + return; + } + let local_x = f32::from(x - bounds.left()); + let new_val = (local_x / width).clamp(0.0, 1.0); + + let cb = app.update_entity(&slider_entity, |slider, cx| { + slider.value = new_val; + cx.notify(); + slider.on_change.clone() + }); + + if let Some(cb) = cb { + cb(new_val, app); + } + }, + ) + .child( + div() + .w_full() + .h_2() + .bg(rgb(BORDER)) + .rounded_full() + .overflow_hidden() + .child( + div() + .w(relative(self.value)) + .h_full() + .bg(rgb(ACCENT_DIM)) + .rounded_full(), + ), + ) + } +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 0e4a10c..38452fa 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -1,4 +1,5 @@ mod beat; +pub mod core; mod headers; mod pattern; mod transport; diff --git a/src/ui/transport.rs b/src/ui/transport.rs index ff64d83..969c964 100644 --- a/src/ui/transport.rs +++ b/src/ui/transport.rs @@ -228,15 +228,7 @@ impl Daw { .items_center() .gap_2() .child(dim("MASTER")) - .child( - div() - .w_24() - .h_2() - .bg(rgb(BORDER)) - .rounded_full() - .overflow_hidden() - .child(div().w(px(72.)).h_full().bg(rgb(ACCENT_DIM)).rounded_full()), - ), + .child(div().w_24().child(self.master_slider.clone())), ) } }