From 2adf22257e4bf2bd717fb398fc6d9d5cd1aefced Mon Sep 17 00:00:00 2001 From: i do not exist Date: Fri, 25 Sep 2026 08:39:16 +0300 Subject: [PATCH] pass doctests --- src/deps.rs | 2 +- src/tek.edn | 67 +++++++++-------- src/tek.rs | 64 ++++++----------- src/tek_bind.rs | 188 +++++++++++++++++++++++++++++++++--------------- src/tek_conf.rs | 29 ++++++-- src/tek_ctrl.rs | 5 +- src/tek_mode.rs | 176 ++++++++++++++++++++++++++++----------------- src/tek_view.rs | 121 +++++++++++++++---------------- 8 files changed, 383 insertions(+), 269 deletions(-) diff --git a/src/deps.rs b/src/deps.rs index 0e6ae476..964a91e1 100644 --- a/src/deps.rs +++ b/src/deps.rs @@ -6,7 +6,7 @@ pub extern crate tengri; pub(crate) use ::{ std::{ cmp::Ord, - collections::BTreeMap, + collections::{BTreeMap, HashMap}, error::Error, ffi::OsString, fmt::{Write, Debug, Formatter}, diff --git a/src/tek.edn b/src/tek.edn index 83743a9f..0d26225d 100644 --- a/src/tek.edn +++ b/src/tek.edn @@ -2,7 +2,9 @@ (axis Y @up @down ) (axis Z @minus @equal ) (axis I @comma @period ) +(axis P @pgup @pgdn ) (axis W @openbracket @closebracket) +(axis H @openbrace @closebrace ) (axis XFine @shift/left @shift/right ) (axis YFine @shift/up @shift/down ) (axis ZFine @underscore @plus ) @@ -19,45 +21,42 @@ (mode Help (axis Y Index) (axis P Page) - (view (bg (g 32) (fg (g 64) (fill/xy (bg (g 48) (fg (g 224) - (align/c (bar/s 2 (expand/wh 2 2 (text Nobody helps! )) Config))))))))) - -(mode Quit - (bind (@n leave) (@y quit)) - (view (bg (g 32) (fg (g 64) (fill/xy (bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Are you sure? )))))))))) - -(mode Save - (axis X Cursor) - (axis Y Index) - (view (bg (g 32) (fg (g 64) (fill/xy - (bg (g 48) (fg (g 224) (align/c (bar/s 1 - (expand/wh 2 2 (text Save to: )) - (text )))))))))) + (view (bg (g 32) (fg (g 64) (full/xy (align/c (bg (g 48) (fg (g 224) + (bar/s 2 (text Help yourself! ) (align/c Config)))))))))) (mode History (axis Y Index) - (view (bg (g 32) (fg (g 64) (fill/xy - (bg (g 48) (fg (g 224) (align/c (bar/s 1 - (expand/wh 2 2 (text Undo/redo history )) - HistoryList))))))))) + (axis P Page) + (view (bg (g 32) (fg (g 64) (full/xy (bg (g 48) (fg (g 224) + (align/c (bar/s 1 (text Project history ) HistoryList))))))))) + +(mode Quit + (bind (@n leave) (@y quit)) + (view (bg (g 32) (fg (g 64) (full/xy (bg (g 48) (fg (g 224) + (align/c (expand/wh 2 2 (bar/s 1 (text Are you sure? ) + (bsp/e (push/x 1 (text Yes)) (align/ne (pull/x 2 (text No)))))))))))))) + +(mode Save + (axis X Input) + (axis Y Index) + (view (bg (g 32) (fg (g 64) (full/xy (bg (g 48) (fg (g 224) + (align/c (bar/s 1 (expand/wh 2 2 (text Save to: )) (text )))))))))) (mode Load - (axis X Cursor) + (axis X Input) (axis Y Index) - (view (bg (g 32) (fg (g 64) (fill/xy - (bg (g 48) (fg (g 224) (align/c (bar/s 1 - (expand/wh 2 2 (text Load from: )) - (text )))))))))) + (view (bg (g 32) (fg (g 64) (full/xy (bg (g 48) (fg (g 224) + (align/c (bar/s 1 (expand/wh 2 2 (text Load from: )) (text )))))))))) (mode Rename - (axis X Cursor) + (axis X Input) (axis Y Index) - (view (bg (g 32) (fg (g 64) (fill/xy - (bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Rename item )))))))))) + (view (bg (g 32) (fg (g 64) (full/xy (bg (g 48) (fg (g 224) + (align/c (expand/wh 2 2 (text Rename item )))))))))) (mode Options (axis Y Index) - (view (bg (g 32) (fg (g 64) (fill/xy + (view (bg (g 32) (fg (g 64) (full/xy (bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Options )))))))))) (mode Transport (name Transport) (info JACK transport controller.) @@ -65,9 +64,9 @@ (view Transport)) (mode Arranger (name Arranger) (info Launch grid.) - (bind Global Clock Tracks Scenes Clip Launch Color History) (axis X Track) - (axis Y Clip) + (axis Y Scene) + (bind Global Clock Tracks Scenes Clip Launch Color History) (view (bar/n 2 StatusBar (bar/s 8 (bar/e 16 (align/nw Transport) (bar/w 20 (bar/s 2 (bar/w 4 TracksOutputsAdd TracksOutputsCount) (bar/s 2 (bar/w 4 TracksDevicesAdd TracksDevicesCount) @@ -86,13 +85,13 @@ (bind Global Clock I IFine Y Page EditorView EditorAdd EditorDel) (view (bsp/s (exact/h 1 Transport) (bsp/n (exact/h 1 StatusBar) - (fill/xy (bsp/a (fill/xy (align/e Pool)) Editor)))))) + (full/xy (bsp/a (full/xy (align/e Pool)) Editor)))))) (mode Sampler (info Sample player.) (bind SamplerDirs SamplerRec SamplerPlay) (view (bsp/s (exact/h 1 Transport) (bsp/n (exact/h 1 StatusBar) - (fill/xy SampleGrid))))) + (full/xy SampleGrid))))) (mode Groovebox (info Sequencer with sampler.) (bind Global Clock Sampler I IFine Y Page EditorView EditorAdd EditorDel) @@ -102,10 +101,10 @@ (view Browse (bsp/s (pad/xy 3 1 BrowseTitle) (fg (g 96) browser))) (view PortsOut (bsp/s (align/w (text L-AUDIO-OUT)) (bsp/e (text MIDI-OUT) (align/e (text AUDIO-OUT-R))))) (view PortsIn (bsp/s (align/w (text L-AUDIO-IN)) (bsp/e (text MIDI-IN) (align/e (text AUDIO-IN-R))))) -(view GrooveMeta (fill/y (align/n (stack/s Pool MidiInsStatus MidiOutsStatus AudioInsStatus AudioOutsStatus)))) +(view GrooveMeta (full/y (align/n (stack/s Pool MidiInsStatus MidiOutsStatus AudioInsStatus AudioOutsStatus)))) (view GrooveEditor (bsp/n GrooveSample GrooveSequence)) -(view GrooveSample (exact/h SampleHeight (bsp/e (fill/y (exact/w 20 (align/nw SampleStatus))) SampleViewer))) -(view GrooveSequence (bsp/e (fill/y (align/n (bsp/s Status/v EditorStatus))) (bsp/e SamplesKeys Editor))) +(view GrooveSample (exact/h SampleHeight (bsp/e (full/y (exact/w 20 (align/nw SampleStatus))) SampleViewer))) +(view GrooveSequence (bsp/e (full/y (align/n (bsp/s Status/v EditorStatus))) (bsp/e SamplesKeys Editor))) (view Transport (bg (g 0) (pad/x 1 (bar/e 4 PlayPause (bar/e 16 Bpm (bar/e 16 Beat Time)))))) (view StatusBar (bar/e 24 (bg (g 50) (full/x (pad/x 1 (bar/s 1 (text Selected) Selected)))) (bar/e 24 (bg (g 42) (full/x (pad/x 1 (bar/s 1 (text Mode) ModeList)))) diff --git a/src/tek.rs b/src/tek.rs index d6abec23..3424e428 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -368,6 +368,14 @@ impl_as_ref!(Option: |self: App|todo!()); impl_as_mut!(Option: |self: App|todo!()); impl_as_ref!(Option: |self: App|self.project.as_ref()); impl_as_mut!(Option: |self: App|self.project.as_mut()); +impl HasModeStack for App { + fn modes (&self) -> RwLockReadGuard<'_, Vec>> { + self.modes.read() + } + fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec>> { + self.modes.write() + } +} impl HasClip for App { fn clip_slot (&self) -> Option> { self.project.clip_slot() @@ -389,30 +397,20 @@ impl ScenesView for App { (self.size.h() as u16).saturating_sub(20) } } - -/// A control axis. -/// -/// ``` -/// let axis = tek::ControlAxis::X; -/// ``` -#[derive(Debug, Copy, Clone)] pub enum ControlAxis { - X, XFine, - Y, YFine, - Z, ZFine, - I, IFine, +impl HasSceneScroll for App { + fn scene_scroll (&self) -> usize { + self.project.scene_scroll() + } } - -impl ControlAxis { - fn get_axis <'a> (_: &App, src: impl Language) -> Perhaps { - eval_word(src, |word|Ok(match word { - - "X" => ControlAxis::X, "XFine" | "X2" => ControlAxis::XFine, - "Y" => ControlAxis::Y, "YFine" | "Y2" => ControlAxis::YFine, - "Z" => ControlAxis::Z, "ZFine" | "Z2" => ControlAxis::ZFine, - - _ => return err!("unknown axis {word}") - - })) +impl HasTrackScroll for App { + fn track_scroll (&self) -> usize { + self.project.track_scroll() + } + fn tracks_last (&self) -> usize { + self.project.track_last + } + fn tracks_last_mut (&mut self) -> &mut usize { + &mut self.project.track_last } } @@ -594,8 +592,7 @@ pub const fn wrap_dec (index: usize, count: usize) -> usize { /// ``` #[derive(Clone)] pub struct MenuItem(pub Arc, pub Arc<[AppCmd]>); -impl_debug!(MenuItem |self, w| { write!(w, "{}", &self.0) }); - +impl_debug!(MenuItem: |self, w| { write!(w, "{}", &self.0) }); impl_default!(MenuItem: Self::new("", [])); impl PartialEq for MenuItem { @@ -715,23 +712,6 @@ impl App { } } -impl HasTrackScroll for App { - fn track_scroll (&self) -> usize { - self.project.track_scroll() - } - fn tracks_last (&self) -> usize { - self.project.track_last - } - fn tracks_last_mut (&mut self) -> &mut usize { - &mut self.project.track_last - } -} - -impl HasSceneScroll for App { - fn scene_scroll (&self) -> usize { - self.project.scene_scroll() - } -} //pub(crate) struct EntriesIterator<'a, S: Screen> { //pub browser: &'a Browse, diff --git a/src/tek_bind.rs b/src/tek_bind.rs index b6fe4159..bbf16675 100644 --- a/src/tek_bind.rs +++ b/src/tek_bind.rs @@ -1,21 +1,139 @@ use crate::*; /// Collection of input bindings. +/// +/// ``` +/// let binds = tek::Binds::default(); +/// +/// /// Key binding maps keys to actions: +/// binds.add_keys(stringify!(Global (@f1 help) (@f2 rename))).unwrap(); +/// +/// /// Axis binding names a pair of keys to control a mode-dependent axis: +/// binds.add_axis(stringify!(X @left @right)).unwrap(); +/// ``` #[derive(Default, Clone, Debug)] pub struct Binds(pub Arc, Arc>>>>>); +/// An map of input events (e.g. [TuiEvent]) to [Binding]s. +/// +/// ``` +/// use ::{std::sync::Arc, tek::{Bind, tengri::TuiEvent}}; +/// +/// let src = stringify!((@x (nop)) (@y (nop) (nop))); +/// let keys = Bind::>::load_keys(&src).unwrap(); +/// +/// // Key 'w' is not mapped: +/// assert_eq!(keys.query(&'w'.into()).map(|x|x.len()), None); +/// +/// // Key 'x' has one binding containing a command: +/// assert_eq!(keys.query(&'x'.into()).map(|x|x.len()), Some(1)); +/// assert_eq!(keys.query(&'x'.into()).map(|x|x[0].commands.len()), Some(1)); +/// +/// // Key 'y' has one binding containing multiple commands: +/// assert_eq!(keys.query(&'y'.into()).map(|x|x.len()), Some(1)); +/// assert_eq!(keys.query(&'y'.into()).map(|x|x[0].commands.len()), Some(2)); +/// +/// let src = stringify!(@up @down); +/// let axis = Bind::>::load_axis("Y", &src).unwrap(); +/// assert_eq!(axis.query(&"left".into()).map(|x|x.len()), None); +/// assert_eq!(axis.query(&"up".into()).map(|x|x.len()), Some(1)); +/// assert_eq!(axis.query(&"down".into()).map(|x|x.len()), Some(1)); +/// ``` +#[derive(Debug)] +pub struct Bind( + /// Map of each event (e.g. key combination) to + /// all command expressions bound to it by + /// all loaded input layers. + pub BTreeMap>> +); + +/// A sequence of zero or more commands (e.g. [AppCmd]), +/// optionally filtered by [Condition] to form layers. +/// +/// ``` +/// # use std::sync::Arc; +/// let binding: tek::Binding> = Default::default(); +/// ``` +#[derive(Debug, Clone)] pub struct Binding { + pub commands: Arc<[C]>, + pub condition: Option, + pub description: Option>, + pub source: Option>, +} + +/// Condition that must evaluate to true in order to enable an input binding. +/// +/// ``` +/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true}))); +/// ``` +#[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, +} + impl Binds { - pub fn add_keys <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> { - let name = expr.head()?.ok_or("bind: missing name")?; - let body = expr.tail()?.unwrap_or(""); - self.0.try_write().unwrap().insert(name.into(), Bind::load_keys(body)?.into()); + pub fn add_keys (&self, expr: impl Language) -> Usually<()> { + self.0.write().insert( + expr.head()?.ok_or("bind: missing name")?.into(), + Bind::load_keys(expr.tail()?.unwrap_or(""))?.into() + ); Ok(()) } - pub fn add_axis <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> { + pub fn add_axis (&self, expr: impl Language) -> Usually<()> { let name = expr.head()?.ok_or("axis: missing name")?; - let body = expr.tail()?.unwrap_or(""); - self.0.try_write().unwrap().insert(name.into(), Bind::load_axis(name, body)?.into()); + self.0.write().insert( + name.into(), + Bind::load_axis(name, expr.tail()?.unwrap_or(""))?.into() + ); Ok(()) } @@ -36,37 +154,6 @@ impl Binds { } } -/// An map of input events (e.g. [TuiEvent]) to [Binding]s. -/// -/// ``` -/// use ::{std::sync::Arc, tek::{Bind, tengri::TuiEvent}}; -/// let src = stringify!((@x (nop)) (@y (nop) (nop))); -/// let bind = Bind::>::load_keys(&src).unwrap(); -/// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1)); -/// assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2)); -/// ``` -#[derive(Debug)] -pub struct Bind( - /// Map of each event (e.g. key combination) to - /// all command expressions bound to it by - /// all loaded input layers. - pub BTreeMap>> -); - -/// A sequence of zero or more commands (e.g. [AppCmd]), -/// optionally filtered by [Condition] to form layers. -/// -/// ``` -/// //FIXME: Why does it overflow? -/// //let binding: Binding<()> = tek::Binding { ..Default::default() }; -/// ``` -#[derive(Debug, Clone)] pub struct Binding { - pub commands: Arc<[C]>, - pub condition: Option, - pub description: Option>, - pub source: Option>, -} - /// Default is always empty map regardless if `E` and `C` implement [Default]. impl Default for Bind { fn default () -> Self { Self(Default::default()) } @@ -99,13 +186,13 @@ impl Bind> { pub fn load_axis (name: &str, body: impl Language) -> Usually { let mut bind: Bind> = Bind::new(); body.each(0, |index, key: &str|if index == 0 { - bind.add_def(&format!("({key} (axis (inc {name})))"))?; + bind.add_def(&format!("({key} (axis (dec {name})))"))?; Ok(1) } else if index == 1 { - bind.add_def(&format!("({key} (axis (dec {name})))"))?; + bind.add_def(&format!("({key} (axis (inc {name})))"))?; Ok(2) } else { - err!("top-level (axis) takes 2 keysyms: inc and dec") + err!("top-level (axis) takes 2 keysyms: dec and inc") })?; Ok(bind) } @@ -121,12 +208,7 @@ impl Bind> { if let Some(event) = TuiKey::from_dsl(&head)?.to_crossterm() { let mut commands: Vec> = vec![]; item.expr()?.tail()?.each((), |_, def|Ok(commands.push(def.into())))?; - self.add(TuiEvent(event), Binding { - commands: commands.into(), - condition: None, - description: None, - source: None - }); + self.add(TuiEvent(event), Binding { commands: commands.into(), ..Default::default() }); Ok(()) } else if Some(":char") == item.expr()?.head()? { // TODO @@ -167,17 +249,7 @@ impl Bind { } } -/// Condition that must evaluate to true in order to enable an input binding. -/// -/// ``` -/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true}))); -/// ``` -#[derive(Clone)] -pub struct Condition(pub Arcbool + Send + Sync>>); - -impl_debug!(Condition |self, w| { - write!(w, "*") -}); +impl_debug!(Condition: |self, w| { write!(w, "*") }); //take!(DeviceCmd|state: Project, iter|state.selected_device().as_ref() //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); diff --git a/src/tek_conf.rs b/src/tek_conf.rs index 467c9638..1c47ef67 100644 --- a/src/tek_conf.rs +++ b/src/tek_conf.rs @@ -5,13 +5,28 @@ use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as Notify /// Configuration: mode, view, and bind definitions. /// /// ``` -/// use ::{std::sync::Arc, tek::{Config, Modes, Mode}}; -/// let config: Config = Default::default(); -/// let mode = stringify!(mode Menu (name Menu) (info Mode selector.) (keys Y Confirm) -/// (view (bg (g 0) (bsp/s PortsOut (bsp/n PortsIn -/// (bg (g 30) (bsp/s (exact/y 7 Logo) (fill Dialog)))))))); -/// Modes::add(&config.modes, mode).unwrap(); -/// let mode: Arc = config.get_mode("Menu").unwrap(); +/// # use ::std::sync::Arc; +/// +/// // Default empty config: +/// let config: tek::Config = Default::default(); +/// +/// // Define a binding: +/// config.binds.add_keys(stringify!(Confirm (@enter confirm))).unwrap(); +/// +/// // Define an axis: +/// config.binds.add_axis(stringify!(Y @up @down)).unwrap(); +/// +/// // Define a mode: +/// config.modes.add(stringify!(Menu +/// (name Menu) +/// (info Mode selector.) +/// (axis Y Index) +/// (bind Confirm) +/// (view (bg (g 30) (bar/s 7 Logo (full/xy Dialog)))) +/// )).unwrap(); +/// +/// // Find the mode: +/// let mode: Arc = config.get_mode("Menu").unwrap(); /// ``` #[derive(Default, Debug)] pub struct Config { diff --git a/src/tek_ctrl.rs b/src/tek_ctrl.rs index d31a84cf..8779dfbb 100644 --- a/src/tek_ctrl.rs +++ b/src/tek_ctrl.rs @@ -16,9 +16,10 @@ impl App { for name in state.modes.read().iter().rev() { let mode = state.config.get_mode(name).as_ref().map(Arc::clone); if let Some(mode) = mode { - let binds = state.config.binds.clone(); + for (ctrl, data) in mode.axis.iter() { + } for id in mode.keys.iter() { - if let Some(event_map) = binds.find(id) + if let Some(event_map) = state.config.binds.find(id) && let Some(bindings) = event_map.query(input) { return Some(bindings.into()) } diff --git a/src/tek_mode.rs b/src/tek_mode.rs index 68244c9b..737d17b0 100644 --- a/src/tek_mode.rs +++ b/src/tek_mode.rs @@ -3,22 +3,43 @@ use crate::*; /// Collection of UI modes. /// /// ``` +/// // Empty mode registry: /// let mut modes = tek::Modes::default(); /// assert_eq!(modes.len(), 0); -/// let _ = modes.add(&"mode Foo (name Foo) (info Bar)").unwrap(); +/// assert!(modes.get("Foo").is_none()); +/// +/// // Add a mode: +/// let _ = modes.add(&"Foo (name Foo) (info Bar)").unwrap(); /// assert_eq!(modes.len(), 1); +/// assert!(modes.get("Foo").is_some()); /// ``` #[derive(Default, Debug, Clone)] pub struct Modes(pub Arc, Arc>>>); +/// Group of view and keys definitions. +/// +/// ``` +/// let mode = tek::Mode::default(); +/// ``` +#[derive(Default, Debug)] +pub struct Mode { + pub path: PathBuf, + pub name: Vec>, + pub info: Vec>, + pub view: Vec>>, + pub keys: Vec>, + pub axis: HashMap, + pub modes: Modes, +} + impl Modes { /// Register a mode. pub fn add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> { let name = expr.head()?.ok_or("mode: missing name")?; let body = expr.tail()?.ok_or("mode: missing body")?; - let mode = Mode::default(); - let mode = body.each(mode, |c, s|Mode::add(c, s))?; + let mut mode = Mode::default(); + body.each(&mut mode, |c, s|c.add(s))?; self.0.write().insert(name.into(), Arc::new(mode)); Ok(()) } @@ -42,22 +63,8 @@ impl Modes { } -/// Group of view and keys definitions. -/// -/// ``` -/// let mode = tek::Mode::default(); -/// ``` -#[derive(Default, Debug)] -pub struct Mode { - pub path: PathBuf, - pub name: Vec>, - pub info: Vec>, - pub view: Vec>>, - pub keys: Vec>, - pub modes: Modes, -} - impl Mode { + /// Add a definition to the mode. /// /// Supported definitions: @@ -69,57 +76,91 @@ impl Mode { /// - ... -> view /// /// ``` - /// let mut mode: tek::Mode = tek::Mode::add(tek::Mode::default(), "(name hello)").unwrap(); + /// let mut mode = tek::Mode::default().add("(name hello)").unwrap(); /// ``` - pub fn add > (mut mode: T, dsl: impl Language) -> Usually { + pub fn add (&mut self, dsl: impl Language) -> Usually<&mut Self> { Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() { - //println!("Mode::add: {head} {:?}", expr.tail()); let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); match head { - "mode" => { - let name = tail.head()?.ok_or("submode: missing name")?; - let body = tail.tail()?.ok_or("submode: missing body")?; - let submode = Mode::default(); - let submode = body.each(submode, |c,s|Mode::add(c,s))?; - let modes = mode.as_mut().modes.clone(); - modes.0.write().insert(name.into(), Arc::new(submode)); - mode - }, - "bind" => { - tail.each(mode, |mut mode: T, expr: &str|{ - mode.as_mut().keys.push(expr.trim().into()); - Ok(mode) - })? - }, - "axis" => { - let mut ctrl: Option> = None; - let mut data: Option> = None; - tail.each(0, |index: usize, src: &str|if index == 0 { - ctrl = Some(src.into()); - Ok(1) - } else if index == 1 { - data = Some(src.into()); - Ok(2) - } else { - err!("(mode (axis)) takes 2 keysyms, control and data") - })?; - // 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()); - mode - }, - "name" => { mode.as_mut().name.push(tail.into()); mode }, - "info" => { mode.as_mut().info.push(tail.into()); mode }, - "view" => { mode.as_mut().view.push(View::new(tail)?.into()); mode }, - _ => { mode.as_mut().view.push(View::new(tail)?.into()); mode }, + "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 }, } } else if let Ok(Some(word)) = dsl.word() { - mode.as_mut().view.push(View::new(word)?.into()); - mode + self.as_mut().view.push(View::new(word)?.into()); + self } else { return Err(format!("Mode::add: unexpected: {dsl:?}").into()); }) } + + fn add_mode (&mut self, tail: &str) -> Usually<&mut Self> { + let name = tail.head()?.ok_or("submode: missing name")?; + let body = tail.tail()?.ok_or("submode: missing body")?; + let mut submode = Mode::default(); + body.each(&mut submode, |c, s|c.add(s))?; + let modes = self.modes.clone(); + modes.0.write().insert(name.into(), Arc::new(submode)); + Ok(self) + } + + 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()); + Ok(mode) + }) + } + + fn add_axis (&mut self, tail: &str) -> Usually<&mut Self> { + let mut ctrl: Option> = None; + let mut data: Option> = None; + tail.each(0, |index: usize, src: &str|if index == 0 { + ctrl = Some(src.into()); + Ok(1) + } else if index == 1 { + data = Some(src.into()); + Ok(2) + } else { + err!("(mode (axis)) takes 2 keysyms, control and data") + })?; + // 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); + Ok(self) + } else { + err!("invalid axis: ctrl {ctrl:?} + data {data:?}") + } + } } impl AsMut for Mode { @@ -128,11 +169,16 @@ impl AsMut for Mode { } } -impl HasModeStack for App { - fn modes (&self) -> RwLockReadGuard<'_, Vec>> { - self.modes.read() - } - fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec>> { - self.modes.write() +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}") + + })) } } diff --git a/src/tek_view.rs b/src/tek_view.rs index b5b59835..68d98866 100644 --- a/src/tek_view.rs +++ b/src/tek_view.rs @@ -1,5 +1,15 @@ use crate::*; +/// Collection of custom view definitions. +#[derive(Default, Clone, Debug)] +pub struct Views(pub Arc, Arc>>>>); + +/// Custom view definition is a boxed closure emitting a [Draw]able from state `S`. +pub struct View { + pub source: Arc, + pub render: ArcDrawn + Send + Sync>> +} + /// The [Draw] implementation for [App] handles the loaded view, /// which is defined in terms of [dizzle] DSL. /// @@ -56,12 +66,11 @@ pub fn draw_mode_stack (to: &mut Tui, state: &App) -> Usually<()> { state.size.1.store(h as usize, Relaxed); }, Err(e) => { - let message = format!( + *state.error.try_write().unwrap() = Some(format!( "Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}", &mode.name, &view.source - ); - *state.error.try_write().unwrap() = Some(message.into()); + ).into()); error = true; break; } @@ -77,13 +86,6 @@ pub fn draw_mode_stack (to: &mut Tui, state: &App) -> Usually<()> { Ok(()) } -/// Collection of custom view definitions. -#[derive(Default, Clone, Debug)] -pub struct Views(pub Arc, - Arc> ->>>); - impl Views { /// Load custom view definition. pub fn add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> { @@ -109,15 +111,9 @@ impl Views { } } -/// Custom view definition is a boxed closure emitting a [Draw]able from state `S`. -pub struct View { - pub source: Arc, - pub render: ArcDrawn + Send + Sync>> -} +impl_debug!( View: |self, w| { write!(w, "View({})", self.source) }); -impl_debug!( View |self, w| { write!(w, "View({})", self.source) }); - -impl_display!( View |self, w| { write!(w, "View({})", self.source) }); +impl_display!( View: |self, w| { write!(w, "View({})", self.source) }); impl View { @@ -257,7 +253,7 @@ impl View { }) }, - "full" | "fill" => { + "full" => { let thunk = Self::compile(arg!(expr, head, 1, "content"))?; match head.split('/').skip(1).next() { Some("w") | Some("x") => Self::boxed(move|state, screen|{ @@ -429,55 +425,60 @@ pub fn view_menu (app: &App) -> impl Draw { } pub fn view_history_list (app: &App) -> impl Draw { - above( - Outer(true, Style::default().fg(Color::Rgb(0, 192, 0))), - draw(|to: &mut Tui|{ - let area = to.area(); - let XYWH(x0, mut y0, w, h) = area; - for (done, undo) in app.history.iter().rev() { - to.clip(XYWH(x0 + 1, y0, w, 1), &|to|format!("{done:?}").draw(to))?; - y0 += 1; + let border = Outer(true, Style::default().fg(Color::Rgb(0, 192, 0))); + let background = xy_repeat(" "); + below(border, above(background, draw(|to: &mut Tui|{ + let area = to.area(); + let XYWH(x0, y0, w, h) = area; + let mut y = 0; + for (done, undo) in app.history.iter().rev() { + to.clip(XYWH(x0 + 1, y0 + y, w, 1), &|to|format!("{done:?}").draw(to))?; + y += 1; + if y >= h { + break } - Ok(Some(area)) - }) - ) + } + Ok(Some(area)) + }))) } pub fn view_config (app: &App) -> impl Draw { - above( - Outer(true, Style::default().fg(Color::Rgb(0, 192, 0))), - draw(|to: &mut Tui|{ - let area = to.area(); - let XYWH(x0, mut y0, w, h) = area; - for key in app.modes.read().iter().rev() { - if let Some(mode) = app.config.modes.get(key) { - to.clip(XYWH(x0 + 1, y0, w, 1), &|to|Split::East.bar(16, key, mode.info.get(0)).draw(to))?; - y0 += 1; - for bind in mode.keys.iter() { - to.clip(XYWH(x0 + 3, y0, w, 1), &|to|bind.draw(to))?; - y0 += 1; - if let Some(bind) = app.config.binds.find(bind) { - for (key, binding) in bind.0.iter() { - to.clip(XYWH(x0 + 5, y0, 40, 1), - &|to|Split::East.bar(20, format!("{:?}", key.0), "").draw(to))?; - y0 += 1; - } + macro_rules! next_line { ($h:ident, $y:ident, $n:expr) => { $y += $n; if $y > $h { break } }; } + let border = Outer(true, Style::default().fg(Color::Rgb(0, 192, 0))); + let background = xy_repeat(" "); + below(border, above(background, draw(|to: &mut Tui|{ + let area = to.area(); + let XYWH(x0, y0, w, h) = area; + let mut y = 0; + for key in app.modes.read().iter().rev() { + if let Some(mode) = app.config.modes.get(key) { + to.clip(XYWH(x0 + 1, y0 + y, w, 1), &|to|Split::East.bar(16, + key, + mode.info.get(0)).draw(to))?; + next_line!(h, y, 1); + for (ctrl, data) in mode.axis.iter() { + to.clip(XYWH(x0 + 3, y0 + y, w, 1), &|to|Split::East.bar(4, + format!("{ctrl:?}"), + format!("{data:?}")).draw(to))?; + next_line!(h, y, 1); + } + for bind in mode.keys.iter() { + to.clip(XYWH(x0 + 3, y0 + y, w, 1), &|to|bind.draw(to))?; + next_line!(h, y, 1); + if let Some(bind) = app.config.binds.find(bind) { + for (key, binding) in bind.0.iter() { + to.clip(XYWH(x0 + 5, y0 + y, 40, 1), &|to|Split::East.bar(40, + format!("{}", key), //binding.description.get(0) + "").draw(to))?; + next_line!(h, y, 1); } } - y0 += 1; } + next_line!(h, y, 1); } - //for (index, (key, mode)) in app.config.modes.0.read().iter().enumerate() { - //to.clip(XYWH(x0, y0 + index as u16, w, 1), &|to|Split::East.bar(16, - //key, mode.info.get(0), - //).draw(to))?; - //} - Ok(Some(area)) - }) - ) -} - -pub fn view_config_mode (mode: &Mode) -> impl Draw { + } + Ok(Some(area)) + }))) } /// ```