diff --git a/src/proj.rs b/src/proj.rs index bea3a92d..dc3cc155 100644 --- a/src/proj.rs +++ b/src/proj.rs @@ -46,7 +46,7 @@ use crate::*; /// List of scenes #[cfg(feature = "scene")] pub scenes: Vec, /// Scroll offset of scenes - #[cfg(feature = "scene")] pub scene_scroll: usize, + #[cfg(feature = "scene")] pub scenes_scroll: usize, } impl Project { @@ -110,7 +110,6 @@ impl_has!(Jack<'static>: |self: Project| self.jack); impl_has!(Sizer: |self: Project| self.size); impl_has!(Clock: |self: Project| self.clock); impl_has!(Selection: |self: Project| self.selection); -impl_has!(Vec: |self: Project| self.scenes); impl_as_ref_opt!(Scene: |self: Project| self.selected_scene()); impl_as_mut_opt!(Scene: |self: Project| self.selected_scene_mut()); impl_as_ref!(Option: |self: Project| &self.editor); @@ -261,8 +260,16 @@ impl HasTrackScroll for Project { } } -impl HasSceneScroll for Project { - fn scene_scroll (&self) -> usize { self.scene_scroll } +impl HasScenes for Project { + fn scenes_scroll (&self) -> usize { + self.scenes_scroll + } + fn scenes (&self) -> &Vec { + &self.scenes + } + fn scenes_mut (&mut self) -> &mut Vec { + &mut self.scenes + } } def_sizes_iter!(PortsSizes => Arc, [Connect]); diff --git a/src/scene.rs b/src/scene.rs index e3a91663..cbfe9d66 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -51,7 +51,5 @@ impl Scene { pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T); def_sizes_iter!(ScenesSizes => Scene); -impl_as_ref!(Vec: |self: App| self.project.as_ref()); -impl_as_mut!(Vec: |self: App| self.project.as_mut()); -impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt()); -impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt()); +impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt()); +impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt()); diff --git a/src/tek.rs b/src/tek.rs index 3424e428..97c57252 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -397,9 +397,15 @@ impl ScenesView for App { (self.size.h() as u16).saturating_sub(20) } } -impl HasSceneScroll for App { - fn scene_scroll (&self) -> usize { - self.project.scene_scroll() +impl HasScenes for App { + fn scenes_scroll (&self) -> usize { + self.project.scenes_scroll() + } + fn scenes (&self) -> &Vec { + self.project.scenes() + } + fn scenes_mut (&mut self) -> &mut Vec { + self.project.scenes_mut() } } impl HasTrackScroll for App { diff --git a/src/tek_bind.rs b/src/tek_bind.rs index bbf16675..ce54e960 100644 --- a/src/tek_bind.rs +++ b/src/tek_bind.rs @@ -12,7 +12,10 @@ use crate::*; /// binds.add_axis(stringify!(X @left @right)).unwrap(); /// ``` #[derive(Default, Clone, Debug)] -pub struct Binds(pub Arc, Arc>>>>>); +pub struct Binds( + pub Arc, Arc>>>>>, + pub Arc, (Arc, Arc)>>>, +); /// An map of input events (e.g. [TuiEvent]) to [Binding]s. /// @@ -69,55 +72,8 @@ pub struct Bind( #[derive(Clone)] pub struct Condition(pub Arcbool + Send + Sync>>); -/// A control axis that may be bound to a [DataAxis]. -/// -/// ``` -/// let axis = tek::ControlAxis::X; -/// ``` -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub enum ControlAxis { - /// Horizontal - X, - /// Vertical - Y, - /// Depth or zoom - Z, - /// Index - I, - /// Page - P, - /// Width - W, - /// Height - H, -} - -/// A data axis that may be controlled by a bound [ControlAxis]. -#[derive(Debug, Copy, Clone)] -pub enum DataAxis { - /// Note pitch up/down - Pitch, - /// Time cursor position increment/decrement - Time, - /// Time cursor length increment/decrement - Length, - /// Zoom in/out - Zoom, - /// Move select cursor up/down - Index, - /// Move text cursor left/right - Input, - /// Increment/decrement width - Width, - /// Increment/decrement height - Height, - /// Next/previous track - Track, - /// Next/previous scene - Scene, - /// Next/previous page - Page, -} +/// Named control axis. +pub struct ControlAxis(pub Arc); impl Binds { pub fn add_keys (&self, expr: impl Language) -> Usually<()> { diff --git a/src/tek_conf.rs b/src/tek_conf.rs index 1c47ef67..10285556 100644 --- a/src/tek_conf.rs +++ b/src/tek_conf.rs @@ -20,7 +20,7 @@ use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as Notify /// config.modes.add(stringify!(Menu /// (name Menu) /// (info Mode selector.) -/// (axis Y Index) +/// (axis Y Prev Next) /// (bind Confirm) /// (view (bg (g 30) (bar/s 7 Logo (full/xy Dialog)))) /// )).unwrap(); @@ -31,14 +31,14 @@ use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as Notify #[derive(Default, Debug)] pub struct Config { /// XDG base directories of running user. - pub dirs: BaseDirectories, + pub dirs: BaseDirectories, /// Active collection of interaction modes. pub modes: Modes, /// Active collection of event bindings. pub binds: Binds, /// Active collection of view definitions. pub views: Views, - /// Error caught during reloading + /// Error caught during (re)loading pub error: RwLock>>, /// Timestamp pub stamp: AtomicU64, diff --git a/src/tek_ctrl.rs b/src/tek_ctrl.rs index 8779dfbb..0d2c9a99 100644 --- a/src/tek_ctrl.rs +++ b/src/tek_ctrl.rs @@ -6,20 +6,33 @@ tui_keys!(self: App, input { }); impl App { - fn input_cmds (&mut self, input: &TuiEvent) -> Usually<()> { - if let Some(bindings) = Self::input_cmds_find(self, input) { - return Self::input_cmds_exec(self, bindings) + pub fn input_cmds (&mut self, input: &TuiEvent) -> Usually<()> { + if let Some(bindings) = self.input_cmds_find(input) { + return self.input_cmds_exec(bindings) } Ok(()) } - fn input_cmds_find (state: &Self, input: &TuiEvent) -> Option>]>> { - for name in state.modes.read().iter().rev() { - let mode = state.config.get_mode(name).as_ref().map(Arc::clone); + /// ``` + /// let app = tek::App::default(); + /// tek::Config::load(&app.config, stringify!( + /// (axis Y @down @up) + /// (mode Menu (axis Y prev next) (bind (@enter confirm))) + /// )).unwrap(); + /// panic!("{app:#?}"); + /// assert!(app.input_cmds_find(&"left".into()).is_none()); + /// assert!(app.input_cmds_find(&"enter".into()).is_some()); + /// assert!(app.input_cmds_find(&"up".into()).is_some()); + /// assert!(app.input_cmds_find(&"down".into()).is_some()); + /// ``` + pub fn input_cmds_find (&self, input: &TuiEvent) -> Option>]>> { + for name in self.modes.read().iter().rev() { + let mode = self.config.get_mode(name).as_ref().map(Arc::clone); if let Some(mode) = mode { - for (ctrl, data) in mode.axis.iter() { + for (ctrl, (dec, inc)) in mode.axis.iter() { + panic!("{ctrl:?} {dec:?} {inc:?}"); } for id in mode.keys.iter() { - if let Some(event_map) = state.config.binds.find(id) + if let Some(event_map) = self.config.binds.find(id) && let Some(bindings) = event_map.query(input) { return Some(bindings.into()) } @@ -28,31 +41,32 @@ impl App { } None } - fn input_cmds_exec (state: &mut Self, bindings: Arc<[Binding>]>) -> Usually<()> { + + pub fn input_cmds_exec (&mut self, bindings: Arc<[Binding>]>) -> Usually<()> { for binding in bindings.iter() { for src in binding.commands.iter() { - if let Some(command) = Namespace::::namespace(state, &src)? { - Self::cmd_dispatch(state, command)?; + if let Some(command) = Namespace::::namespace(self, &src)? { + self.cmd_dispatch(command)?; } } } Ok(()) } - fn cmd_dispatch (state: &mut Self, command: AppCmd) -> Usually<()> { - match command.clone().dispatch(state) { + + pub fn cmd_dispatch (&mut self, command: AppCmd) -> Usually<()> { + match command.clone().dispatch(self) { Ok(undo) => { - state.history.push((command, undo)); + self.history.push((command, undo)); Ok(()) }, Err(err) => { - state.history.push((command, None)); + self.history.push((command, None)); Err(err) } } } } -impl_from!(AppCmd: |x: AxisCmd| AppCmd::Axis { command: x }); impl_from!(AppCmd: |x: BrowseCmd| AppCmd::Browse { command: x }); impl_from!(AppCmd: |x: MidiClipCmd| AppCmd::MidiClip { command: x }); impl_from!(AppCmd: |x: MidiEditCmd| AppCmd::MidiEdit { command: x }); @@ -93,9 +107,29 @@ impl_from!(AppCmd: |x: TracksCmd| AppCmd::Tracks { command: x }); Ok(None) } } - /// Navigate along a contextual axis. - #[command(Axis = "axis")] pub fn cmd_axis (&mut self, command: AxisCmd) -> Perhaps { - Ok(command.dispatch(self)?.map(Into::into)) + /// Increment the given data axis. + #[command(Inc = "inc")] pub fn inc (&mut self, axis: Arc) -> Perhaps { + panic!("inc {axis:?}"); + //Ok(match (&self.dialog, axis) { + //(Dialog::None, _) => todo!(), + //(Dialog::Menu(_, _), ControlAxis::Y) => { + //AppCmd::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?; + //None + //}, + //_ => todo!() + //}) + } + /// Decrement the given data axis. + #[command(Dec = "dec")] pub fn dec (&mut self, axis: Arc) -> Perhaps { + panic!("dec {axis:?}"); + //Ok(match (&self.dialog, axis) { + //(Dialog::None, _) => None, + //(Dialog::Menu(_, _), ControlAxis::Y) => { + //AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?; + //None + //}, + //_ => todo!() + //}) } /// Cancel current selection. #[command(Cancel = "cancel")] pub fn cmd_cancel (&mut self) -> Perhaps { @@ -737,30 +771,6 @@ impl BrowseControl for T where T: } #[tek_proc::commands(AxisCmd)] impl App { - /// Increment the given data axis. - #[command(Inc = "inc")] pub fn inc (&mut self, axis: ControlAxis) -> Perhaps { - panic!("inc {axis:?}"); - Ok(match (&self.dialog, axis) { - (Dialog::None, _) => todo!(), - (Dialog::Menu(_, _), ControlAxis::Y) => { - AppCmd::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?; - None - }, - _ => todo!() - }) - } - /// Decrement the given data axis. - #[command(Dec = "dec")] pub fn dec (&mut self, axis: ControlAxis) -> Perhaps { - panic!("dec {axis:?}"); - Ok(match (&self.dialog, axis) { - (Dialog::None, _) => None, - (Dialog::Menu(_, _), ControlAxis::Y) => { - AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?; - None - }, - _ => todo!() - }) - } } #[tek_proc::command(AudioInput)] diff --git a/src/tek_has.rs b/src/tek_has.rs index 48c8209d..4581dd37 100644 --- a/src/tek_has.rs +++ b/src/tek_has.rs @@ -1,10 +1,15 @@ use crate::*; +/// Has a stack of currently active interaction modes. pub trait HasModeStack { + /// Readable mode stack. fn modes (&self) -> RwLockReadGuard<'_, Vec>>; + /// Writable mode stack. fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec>>; } +/// Has a MIDI editor. +/// /// ``` /// use tek::{*, tengri::*}; /// @@ -32,11 +37,13 @@ pub trait HasEditor: } } +/// Has a pair of buffers for MIDI IO. pub trait HasMidiBuffers { fn note_buf_mut (&mut self) -> &mut Vec; fn midi_buf_mut (&mut self) -> &mut Vec>>; } +/// Can have a currently playing clip. pub trait HasPlayClip: HasClock { fn reset (&self) -> bool; fn reset_mut (&mut self) -> &mut bool; @@ -46,6 +53,7 @@ pub trait HasPlayClip: HasClock { fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)>; } +/// Has a MIDI sequencer. impl HasSequencer for T where T: AsRef + AsMut {} pub trait HasSequencer: @@ -62,6 +70,7 @@ pub trait HasSequencer: impl HasClock for T where T: AsRef + AsMut {} +/// Has a clock. pub trait HasClock: AsRef + AsMut { @@ -73,6 +82,7 @@ pub trait HasClock: } } +/// Has a chain of devices. pub trait HasDevices: AsRef> + AsMut> { @@ -88,14 +98,11 @@ pub trait HasProject: AsRef> + AsMut> {} +/// Has a loaded project. impl HasProject for T where T: AsRef> + AsMut> {} -pub trait HasSceneScroll: HasScenes { - fn scene_scroll (&self) -> usize; -} - impl HasScene for T where T: AsRefOpt + AsMutOpt + Send + Sync {} @@ -111,16 +118,11 @@ pub trait HasScene: } } -impl HasScenes for T where T: - AsRef> + AsMut> -{} - -pub trait HasScenes: AsRef> + AsMut> { - fn scenes (&self) -> &Vec { - self.as_ref() - } - fn scenes_mut (&mut self) -> &mut Vec { - self.as_mut() +pub trait HasScenes { + fn scenes (&self) -> &Vec; + fn scenes_mut (&mut self) -> &mut Vec; + fn scenes_scroll (&self) -> usize { + 0 } } diff --git a/src/tek_mode.rs b/src/tek_mode.rs index 737d17b0..4aa492b5 100644 --- a/src/tek_mode.rs +++ b/src/tek_mode.rs @@ -28,7 +28,7 @@ pub struct Mode { pub info: Vec>, pub view: Vec>>, pub keys: Vec>, - pub axis: HashMap, + pub axis: BTreeMap, (Arc, Arc)>, pub modes: Modes, } @@ -82,16 +82,16 @@ impl Mode { Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() { let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); match head { - "self" => self.as_mut().add_mode(tail)?, - "bind" => self.as_mut().add_bind(tail)?, - "axis" => self.as_mut().add_axis(tail)?, - "name" => { self.as_mut().name.push(tail.into()); self }, - "info" => { self.as_mut().info.push(tail.into()); self }, - "view" => { self.as_mut().view.push(View::new(tail)?.into()); self }, - _ => { self.as_mut().view.push(View::new(tail)?.into()); self }, + "self" => self.add_mode(tail)?, + "bind" => self.add_bind(tail)?, + "axis" => self.add_axis(tail)?, + "name" => { self.name.push(tail.into()); self }, + "info" => { self.info.push(tail.into()); self }, + "view" => { self.view.push(View::new(tail)?.into()); self }, + _ => { self.view.push(View::new(tail)?.into()); self }, } } else if let Ok(Some(word)) = dsl.word() { - self.as_mut().view.push(View::new(word)?.into()); + self.view.push(View::new(word)?.into()); self } else { return Err(format!("Mode::add: unexpected: {dsl:?}").into()); @@ -110,57 +110,36 @@ impl Mode { fn add_bind (&mut self, tail: &str) -> Usually<&mut Self> { tail.each(self, |mode: &mut Self, expr: &str|{ - mode.as_mut().keys.push(expr.trim().into()); + mode.keys.push(expr.trim().into()); Ok(mode) }) } fn add_axis (&mut self, tail: &str) -> Usually<&mut Self> { - let mut ctrl: Option> = None; - let mut data: Option> = None; + let mut axis: Option> = None; + let mut dec: Option> = None; + let mut inc: Option> = None; tail.each(0, |index: usize, src: &str|if index == 0 { - ctrl = Some(src.into()); + axis = Some(src.into()); Ok(1) } else if index == 1 { - data = Some(src.into()); + dec = Some(src.into()); Ok(2) + } else if index == 2 { + inc = Some(src.into()); + Ok(3) } else { - err!("(mode (axis)) takes 2 keysyms, control and data") + err!("expected (mode (axis [Name] [command] [command])), got: {tail}") })?; - // TODO: get inc/dec keys from axis? - //mode.as_mut().keys.push(format!("({inc} (inc ({axis})))").into()); - //mode.as_mut().keys.push(format!("({dec} (dec ({axis})))").into()); - let ctrl = ctrl.map(|ctrl|match ctrl.as_ref() { - "H" => Some(ControlAxis::H), - "I" => Some(ControlAxis::I), - "P" => Some(ControlAxis::P), - "W" => Some(ControlAxis::W), - "X" => Some(ControlAxis::X), - "Y" => Some(ControlAxis::Y), - "Z" => Some(ControlAxis::Z), - _ => None, - }).flatten(); - let data = data.map(|data|match data.as_ref() { - "Height" => Some(DataAxis::Height), - "Index" => Some(DataAxis::Index), - "Input" => Some(DataAxis::Input), - "Length" => Some(DataAxis::Length), - "Page" => Some(DataAxis::Page), - "Pitch" => Some(DataAxis::Pitch), - "Scene" => Some(DataAxis::Scene), - "Time" => Some(DataAxis::Time), - "Track" => Some(DataAxis::Track), - "Width" => Some(DataAxis::Width), - "Zoom" => Some(DataAxis::Zoom), - _ => None, - }).flatten(); - if let Some(ctrl) = ctrl && let Some(data) = data { - self.as_mut().axis.insert(ctrl, data); + if let Some(axis) = axis && let Some(dec) = dec && let Some(inc) = inc { + self.keys.push(format!("((dec {axis}) {dec})").into()); + self.keys.push(format!("((inc {axis}) {inc})").into()); Ok(self) } else { - err!("invalid axis: ctrl {ctrl:?} + data {data:?}") + err!("expected (mode (axis X Decrement Increment)), got: {tail}") } } + } impl AsMut for Mode { @@ -171,14 +150,6 @@ impl AsMut for Mode { impl ControlAxis { pub fn get_axis <'a> (_: &App, src: impl Language) -> Perhaps { - eval_word(src, |word|Ok(match word { - - "X" | "XFine" | "X2" => ControlAxis::X, - "Y" | "YFine" | "Y2" => ControlAxis::Y, - "Z" | "ZFine" | "Z2" => ControlAxis::Z, - - _ => return err!("unknown axis {word}") - - })) + eval_word(src, |word|Ok(ControlAxis(word.into()))) } } diff --git a/src/tek_view.rs b/src/tek_view.rs index 68d98866..c6b314eb 100644 --- a/src/tek_view.rs +++ b/src/tek_view.rs @@ -671,7 +671,7 @@ impl Draw for MidiEditor { } impl TracksView for T where T: - HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack + HasTracks + HasScenes + ScenesView + HasMidiIns + HasMidiOuts + HasTrack {} fn iter_tracks_with_sizes <'t, T> (state: &'t T) -> impl TracksSizes<'t> where @@ -694,7 +694,7 @@ fn iter_tracks_with_sizes <'t, T> (state: &'t T) -> impl TracksSizes<'t> where } pub trait TracksView: - HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack + HasTracks + HasScenes + ScenesView + HasMidiIns + HasMidiOuts + HasTrack { /// Iterate over tracks with their corresponding sizes. fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { @@ -897,7 +897,7 @@ fn view_n_of_m (key: &str, name: &str, index: Option, count: usize) -> im button_3(key, name, east(index, count), false) } -pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync { +pub trait ScenesView: HasScenes + HasEditor + HasSelection + HasClipsSize + Send + Sync { fn h_scenes (&self) -> u16; fn w_side (&self) -> u16; fn w_mid (&self) -> u16; @@ -915,10 +915,10 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + } fn iter_scenes_with_sizes <'s, T> (state: &'s T) -> impl ScenesSizes<'s> where T: - HasScenes + HasSceneScroll + HasEditor + HasSelection + HasClipsSize + ?Sized + HasScenes + HasEditor + HasSelection + HasClipsSize + ?Sized { let mut y = 0; - let skip = state.scene_scroll(); + let skip = state.scenes_scroll(); let selected = state.selection(); let editor = state.editor(); let clips_size = state.clips_size(); diff --git a/tengri b/tengri index 50c0ed1c..ab8c8cb1 160000 --- a/tengri +++ b/tengri @@ -1 +1 @@ -Subproject commit 50c0ed1c66c9db97b83b95eb23a7b0bf00c5a760 +Subproject commit ab8c8cb1c4f2bed9ef4e333d5fc091e586ff0202