pass doctests

This commit is contained in:
i do not exist 2026-09-25 08:39:16 +03:00
parent 057d09cfdf
commit 2adf22257e
8 changed files with 383 additions and 269 deletions

View file

@ -6,7 +6,7 @@ pub extern crate tengri;
pub(crate) use ::{ pub(crate) use ::{
std::{ std::{
cmp::Ord, cmp::Ord,
collections::BTreeMap, collections::{BTreeMap, HashMap},
error::Error, error::Error,
ffi::OsString, ffi::OsString,
fmt::{Write, Debug, Formatter}, fmt::{Write, Debug, Formatter},

View file

@ -2,7 +2,9 @@
(axis Y @up @down ) (axis Y @up @down )
(axis Z @minus @equal ) (axis Z @minus @equal )
(axis I @comma @period ) (axis I @comma @period )
(axis P @pgup @pgdn )
(axis W @openbracket @closebracket) (axis W @openbracket @closebracket)
(axis H @openbrace @closebrace )
(axis XFine @shift/left @shift/right ) (axis XFine @shift/left @shift/right )
(axis YFine @shift/up @shift/down ) (axis YFine @shift/up @shift/down )
(axis ZFine @underscore @plus ) (axis ZFine @underscore @plus )
@ -19,45 +21,42 @@
(mode Help (mode Help
(axis Y Index) (axis Y Index)
(axis P Page) (axis P Page)
(view (bg (g 32) (fg (g 64) (fill/xy (bg (g 48) (fg (g 224) (view (bg (g 32) (fg (g 64) (full/xy (align/c (bg (g 48) (fg (g 224)
(align/c (bar/s 2 (expand/wh 2 2 (text Nobody helps! )) Config))))))))) (bar/s 2 (text Help yourself! ) (align/c 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 <file browser here>))))))))))
(mode History (mode History
(axis Y Index) (axis Y Index)
(view (bg (g 32) (fg (g 64) (fill/xy (axis P Page)
(bg (g 48) (fg (g 224) (align/c (bar/s 1 (view (bg (g 32) (fg (g 64) (full/xy (bg (g 48) (fg (g 224)
(expand/wh 2 2 (text Undo/redo history )) (align/c (bar/s 1 (text Project history ) HistoryList)))))))))
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 <file browser here>))))))))))
(mode Load (mode Load
(axis X Cursor) (axis X Input)
(axis Y Index) (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)
(bg (g 48) (fg (g 224) (align/c (bar/s 1 (align/c (bar/s 1 (expand/wh 2 2 (text Load from: )) (text <file browser here>))))))))))
(expand/wh 2 2 (text Load from: ))
(text <file browser here>))))))))))
(mode Rename (mode Rename
(axis X Cursor) (axis X Input)
(axis Y Index) (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)
(bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Rename item )))))))))) (align/c (expand/wh 2 2 (text Rename item ))))))))))
(mode Options (mode Options
(axis Y Index) (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 )))))))))) (bg (g 48) (fg (g 224) (align/c (expand/wh 2 2 (text Options ))))))))))
(mode Transport (name Transport) (info JACK transport controller.) (mode Transport (name Transport) (info JACK transport controller.)
@ -65,9 +64,9 @@
(view Transport)) (view Transport))
(mode Arranger (name Arranger) (info Launch grid.) (mode Arranger (name Arranger) (info Launch grid.)
(bind Global Clock Tracks Scenes Clip Launch Color History)
(axis X Track) (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) (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/w 20 (bar/s 2 (bar/w 4 TracksOutputsAdd TracksOutputsCount)
(bar/s 2 (bar/w 4 TracksDevicesAdd TracksDevicesCount) (bar/s 2 (bar/w 4 TracksDevicesAdd TracksDevicesCount)
@ -86,13 +85,13 @@
(bind Global Clock I IFine Y Page EditorView EditorAdd EditorDel) (bind Global Clock I IFine Y Page EditorView EditorAdd EditorDel)
(view (bsp/s (exact/h 1 Transport) (view (bsp/s (exact/h 1 Transport)
(bsp/n (exact/h 1 StatusBar) (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.) (mode Sampler (info Sample player.)
(bind SamplerDirs SamplerRec SamplerPlay) (bind SamplerDirs SamplerRec SamplerPlay)
(view (bsp/s (exact/h 1 Transport) (view (bsp/s (exact/h 1 Transport)
(bsp/n (exact/h 1 StatusBar) (bsp/n (exact/h 1 StatusBar)
(fill/xy SampleGrid))))) (full/xy SampleGrid)))))
(mode Groovebox (info Sequencer with sampler.) (mode Groovebox (info Sequencer with sampler.)
(bind Global Clock Sampler I IFine Y Page EditorView EditorAdd EditorDel) (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 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 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 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 GrooveEditor (bsp/n GrooveSample GrooveSequence))
(view GrooveSample (exact/h SampleHeight (bsp/e (fill/y (exact/w 20 (align/nw SampleStatus))) SampleViewer))) (view GrooveSample (exact/h SampleHeight (bsp/e (full/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 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 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)))) (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)))) (bar/e 24 (bg (g 42) (full/x (pad/x 1 (bar/s 1 (text Mode) ModeList))))

View file

@ -368,6 +368,14 @@ impl_as_ref!(Option<Project>: |self: App|todo!());
impl_as_mut!(Option<Project>: |self: App|todo!()); impl_as_mut!(Option<Project>: |self: App|todo!());
impl_as_ref!(Option<MidiEditor>: |self: App|self.project.as_ref()); impl_as_ref!(Option<MidiEditor>: |self: App|self.project.as_ref());
impl_as_mut!(Option<MidiEditor>: |self: App|self.project.as_mut()); impl_as_mut!(Option<MidiEditor>: |self: App|self.project.as_mut());
impl HasModeStack for App {
fn modes (&self) -> RwLockReadGuard<'_, Vec<Arc<str>>> {
self.modes.read()
}
fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec<Arc<str>>> {
self.modes.write()
}
}
impl HasClip for App { impl HasClip for App {
fn clip_slot (&self) -> Option<Slot<MidiClip>> { fn clip_slot (&self) -> Option<Slot<MidiClip>> {
self.project.clip_slot() self.project.clip_slot()
@ -389,30 +397,20 @@ impl ScenesView for App {
(self.size.h() as u16).saturating_sub(20) (self.size.h() as u16).saturating_sub(20)
} }
} }
impl HasSceneScroll for App {
/// A control axis. fn scene_scroll (&self) -> usize {
/// self.project.scene_scroll()
/// ``` }
/// let axis = tek::ControlAxis::X;
/// ```
#[derive(Debug, Copy, Clone)] pub enum ControlAxis {
X, XFine,
Y, YFine,
Z, ZFine,
I, IFine,
} }
impl HasTrackScroll for App {
impl ControlAxis { fn track_scroll (&self) -> usize {
fn get_axis <'a> (_: &App, src: impl Language) -> Perhaps<ControlAxis> { self.project.track_scroll()
eval_word(src, |word|Ok(match word { }
fn tracks_last (&self) -> usize {
"X" => ControlAxis::X, "XFine" | "X2" => ControlAxis::XFine, self.project.track_last
"Y" => ControlAxis::Y, "YFine" | "Y2" => ControlAxis::YFine, }
"Z" => ControlAxis::Z, "ZFine" | "Z2" => ControlAxis::ZFine, fn tracks_last_mut (&mut self) -> &mut usize {
&mut self.project.track_last
_ => return err!("unknown axis {word}")
}))
} }
} }
@ -594,8 +592,7 @@ pub const fn wrap_dec (index: usize, count: usize) -> usize {
/// ``` /// ```
#[derive(Clone)] pub struct MenuItem(pub Arc<str>, pub Arc<[AppCmd]>); #[derive(Clone)] pub struct MenuItem(pub Arc<str>, 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_default!(MenuItem: Self::new("", []));
impl PartialEq for MenuItem { 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(crate) struct EntriesIterator<'a, S: Screen> {
//pub browser: &'a Browse, //pub browser: &'a Browse,

View file

@ -1,21 +1,139 @@
use crate::*; use crate::*;
/// Collection of input bindings. /// 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)] #[derive(Default, Clone, Debug)]
pub struct Binds(pub Arc<RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>>>); pub struct Binds(pub Arc<RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>>>);
/// 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::<TuiEvent, Arc<str>>::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::<TuiEvent, Arc<str>>::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<E, C>(
/// Map of each event (e.g. key combination) to
/// all command expressions bound to it by
/// all loaded input layers.
pub BTreeMap<E, Vec<Binding<C>>>
);
/// 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<Arc<str>> = Default::default();
/// ```
#[derive(Debug, Clone)] pub struct Binding<C> {
pub commands: Arc<[C]>,
pub condition: Option<Condition>,
pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>,
}
/// 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 Arc<Box<dyn Fn()->bool + 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 { impl Binds {
pub fn add_keys <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> { pub fn add_keys (&self, expr: impl Language) -> Usually<()> {
let name = expr.head()?.ok_or("bind: missing name")?; self.0.write().insert(
let body = expr.tail()?.unwrap_or(""); expr.head()?.ok_or("bind: missing name")?.into(),
self.0.try_write().unwrap().insert(name.into(), Bind::load_keys(body)?.into()); Bind::load_keys(expr.tail()?.unwrap_or(""))?.into()
);
Ok(()) 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 name = expr.head()?.ok_or("axis: missing name")?;
let body = expr.tail()?.unwrap_or(""); self.0.write().insert(
self.0.try_write().unwrap().insert(name.into(), Bind::load_axis(name, body)?.into()); name.into(),
Bind::load_axis(name, expr.tail()?.unwrap_or(""))?.into()
);
Ok(()) 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::<TuiEvent, Arc<str>>::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<E, C>(
/// Map of each event (e.g. key combination) to
/// all command expressions bound to it by
/// all loaded input layers.
pub BTreeMap<E, Vec<Binding<C>>>
);
/// 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<C> {
pub commands: Arc<[C]>,
pub condition: Option<Condition>,
pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>,
}
/// Default is always empty map regardless if `E` and `C` implement [Default]. /// Default is always empty map regardless if `E` and `C` implement [Default].
impl<E, C> Default for Bind<E, C> { impl<E, C> Default for Bind<E, C> {
fn default () -> Self { Self(Default::default()) } fn default () -> Self { Self(Default::default()) }
@ -99,13 +186,13 @@ impl Bind<TuiEvent, Arc<str>> {
pub fn load_axis (name: &str, body: impl Language) -> Usually<Self> { pub fn load_axis (name: &str, body: impl Language) -> Usually<Self> {
let mut bind: Bind<TuiEvent, Arc<str>> = Bind::new(); let mut bind: Bind<TuiEvent, Arc<str>> = Bind::new();
body.each(0, |index, key: &str|if index == 0 { 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) Ok(1)
} else if index == 1 { } else if index == 1 {
bind.add_def(&format!("({key} (axis (dec {name})))"))?; bind.add_def(&format!("({key} (axis (inc {name})))"))?;
Ok(2) Ok(2)
} else { } else {
err!("top-level (axis) takes 2 keysyms: inc and dec") err!("top-level (axis) takes 2 keysyms: dec and inc")
})?; })?;
Ok(bind) Ok(bind)
} }
@ -121,12 +208,7 @@ impl Bind<TuiEvent, Arc<str>> {
if let Some(event) = TuiKey::from_dsl(&head)?.to_crossterm() { if let Some(event) = TuiKey::from_dsl(&head)?.to_crossterm() {
let mut commands: Vec<Arc<str>> = vec![]; let mut commands: Vec<Arc<str>> = vec![];
item.expr()?.tail()?.each((), |_, def|Ok(commands.push(def.into())))?; item.expr()?.tail()?.each((), |_, def|Ok(commands.push(def.into())))?;
self.add(TuiEvent(event), Binding { self.add(TuiEvent(event), Binding { commands: commands.into(), ..Default::default() });
commands: commands.into(),
condition: None,
description: None,
source: None
});
Ok(()) Ok(())
} else if Some(":char") == item.expr()?.head()? { } else if Some(":char") == item.expr()?.head()? {
// TODO // TODO
@ -167,17 +249,7 @@ impl<E: Clone + Ord, C> Bind<E, C> {
} }
} }
/// Condition that must evaluate to true in order to enable an input binding. impl_debug!(Condition: |self, w| { write!(w, "*") });
///
/// ```
/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true})));
/// ```
#[derive(Clone)]
pub struct Condition(pub Arc<Box<dyn Fn()->bool + Send + Sync>>);
impl_debug!(Condition |self, w| {
write!(w, "*")
});
//take!(DeviceCmd|state: Project, iter|state.selected_device().as_ref() //take!(DeviceCmd|state: Project, iter|state.selected_device().as_ref()
//.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten())); //.map(|t|Take::take(t, iter)).transpose().map(|x|x.flatten()));

View file

@ -5,13 +5,28 @@ use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as Notify
/// Configuration: mode, view, and bind definitions. /// Configuration: mode, view, and bind definitions.
/// ///
/// ``` /// ```
/// use ::{std::sync::Arc, tek::{Config, Modes, Mode}}; /// # use ::std::sync::Arc;
/// let config: Config = Default::default(); ///
/// let mode = stringify!(mode Menu (name Menu) (info Mode selector.) (keys Y Confirm) /// // Default empty config:
/// (view (bg (g 0) (bsp/s PortsOut (bsp/n PortsIn /// let config: tek::Config = Default::default();
/// (bg (g 30) (bsp/s (exact/y 7 Logo) (fill Dialog)))))))); ///
/// Modes::add(&config.modes, mode).unwrap(); /// // Define a binding:
/// let mode: Arc<Mode> = config.get_mode("Menu").unwrap(); /// 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<tek::Mode> = config.get_mode("Menu").unwrap();
/// ``` /// ```
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct Config { pub struct Config {

View file

@ -16,9 +16,10 @@ impl App {
for name in state.modes.read().iter().rev() { for name in state.modes.read().iter().rev() {
let mode = state.config.get_mode(name).as_ref().map(Arc::clone); let mode = state.config.get_mode(name).as_ref().map(Arc::clone);
if let Some(mode) = mode { if let Some(mode) = mode {
let binds = state.config.binds.clone(); for (ctrl, data) in mode.axis.iter() {
}
for id in mode.keys.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) { && let Some(bindings) = event_map.query(input) {
return Some(bindings.into()) return Some(bindings.into())
} }

View file

@ -3,22 +3,43 @@ use crate::*;
/// Collection of UI modes. /// Collection of UI modes.
/// ///
/// ``` /// ```
/// // Empty mode registry:
/// let mut modes = tek::Modes::default(); /// let mut modes = tek::Modes::default();
/// assert_eq!(modes.len(), 0); /// 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_eq!(modes.len(), 1);
/// assert!(modes.get("Foo").is_some());
/// ``` /// ```
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct Modes(pub Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode>>>>); pub struct Modes(pub Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode>>>>);
/// Group of view and keys definitions.
///
/// ```
/// let mode = tek::Mode::default();
/// ```
#[derive(Default, Debug)]
pub struct Mode {
pub path: PathBuf,
pub name: Vec<Arc<str>>,
pub info: Vec<Arc<str>>,
pub view: Vec<Arc<View<Tui, App>>>,
pub keys: Vec<Arc<str>>,
pub axis: HashMap<ControlAxis, DataAxis>,
pub modes: Modes,
}
impl Modes { impl Modes {
/// Register a mode. /// Register a mode.
pub fn add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> { pub fn add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> {
let name = expr.head()?.ok_or("mode: missing name")?; let name = expr.head()?.ok_or("mode: missing name")?;
let body = expr.tail()?.ok_or("mode: missing body")?; let body = expr.tail()?.ok_or("mode: missing body")?;
let mode = Mode::default(); let mut mode = Mode::default();
let mode = body.each(mode, |c, s|Mode::add(c, s))?; body.each(&mut mode, |c, s|c.add(s))?;
self.0.write().insert(name.into(), Arc::new(mode)); self.0.write().insert(name.into(), Arc::new(mode));
Ok(()) 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<Arc<str>>,
pub info: Vec<Arc<str>>,
pub view: Vec<Arc<View<Tui, App>>>,
pub keys: Vec<Arc<str>>,
pub modes: Modes,
}
impl Mode { impl Mode {
/// Add a definition to the mode. /// Add a definition to the mode.
/// ///
/// Supported definitions: /// Supported definitions:
@ -69,57 +76,91 @@ impl Mode {
/// - ... -> view /// - ... -> 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 <T: AsMut<Mode>> (mut mode: T, dsl: impl Language) -> Usually<T> { 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() { 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(""); let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
match head { match head {
"mode" => { "self" => self.as_mut().add_mode(tail)?,
let name = tail.head()?.ok_or("submode: missing name")?; "bind" => self.as_mut().add_bind(tail)?,
let body = tail.tail()?.ok_or("submode: missing body")?; "axis" => self.as_mut().add_axis(tail)?,
let submode = Mode::default(); "name" => { self.as_mut().name.push(tail.into()); self },
let submode = body.each(submode, |c,s|Mode::add(c,s))?; "info" => { self.as_mut().info.push(tail.into()); self },
let modes = mode.as_mut().modes.clone(); "view" => { self.as_mut().view.push(View::new(tail)?.into()); self },
modes.0.write().insert(name.into(), Arc::new(submode)); _ => { self.as_mut().view.push(View::new(tail)?.into()); self },
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<Arc<str>> = None;
let mut data: Option<Arc<str>> = 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 },
} }
} else if let Ok(Some(word)) = dsl.word() { } else if let Ok(Some(word)) = dsl.word() {
mode.as_mut().view.push(View::new(word)?.into()); self.as_mut().view.push(View::new(word)?.into());
mode self
} else { } else {
return Err(format!("Mode::add: unexpected: {dsl:?}").into()); 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<Arc<str>> = None;
let mut data: Option<Arc<str>> = 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<Mode> for Mode { impl AsMut<Mode> for Mode {
@ -128,11 +169,16 @@ impl AsMut<Mode> for Mode {
} }
} }
impl HasModeStack for App { impl ControlAxis {
fn modes (&self) -> RwLockReadGuard<'_, Vec<Arc<str>>> { pub fn get_axis <'a> (_: &App, src: impl Language) -> Perhaps<ControlAxis> {
self.modes.read() eval_word(src, |word|Ok(match word {
}
fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec<Arc<str>>> { "X" | "XFine" | "X2" => ControlAxis::X,
self.modes.write() "Y" | "YFine" | "Y2" => ControlAxis::Y,
"Z" | "ZFine" | "Z2" => ControlAxis::Z,
_ => return err!("unknown axis {word}")
}))
} }
} }

View file

@ -1,5 +1,15 @@
use crate::*; use crate::*;
/// Collection of custom view definitions.
#[derive(Default, Clone, Debug)]
pub struct Views(pub Arc<RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>>>);
/// Custom view definition is a boxed closure emitting a [Draw]able from state `S`.
pub struct View<S: Screen, T> {
pub source: Arc<str>,
pub render: Arc<Box<dyn Fn(&T, &mut S)->Drawn<S::Unit> + Send + Sync>>
}
/// The [Draw] implementation for [App] handles the loaded view, /// The [Draw] implementation for [App] handles the loaded view,
/// which is defined in terms of [dizzle] DSL. /// 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); state.size.1.store(h as usize, Relaxed);
}, },
Err(e) => { Err(e) => {
let message = format!( *state.error.try_write().unwrap() = Some(format!(
"Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}", "Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{}",
&mode.name, &mode.name,
&view.source &view.source
); ).into());
*state.error.try_write().unwrap() = Some(message.into());
error = true; error = true;
break; break;
} }
@ -77,13 +86,6 @@ pub fn draw_mode_stack (to: &mut Tui, state: &App) -> Usually<()> {
Ok(()) Ok(())
} }
/// Collection of custom view definitions.
#[derive(Default, Clone, Debug)]
pub struct Views(pub Arc<RwLock<BTreeMap<
Arc<str>,
Arc<View<Tui, App>>
>>>);
impl Views { impl Views {
/// Load custom view definition. /// Load custom view definition.
pub fn add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> { 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`. impl_debug!(<S: Screen, T> View<S, T>: |self, w| { write!(w, "View({})", self.source) });
pub struct View<S: Screen, T> {
pub source: Arc<str>,
pub render: Arc<Box<dyn Fn(&T, &mut S)->Drawn<S::Unit> + Send + Sync>>
}
impl_debug!(<S: Screen, T> View<S, T> |self, w| { write!(w, "View({})", self.source) }); impl_display!(<S: Screen, T> View<S, T>: |self, w| { write!(w, "View({})", self.source) });
impl_display!(<S: Screen, T> View<S, T> |self, w| { write!(w, "View({})", self.source) });
impl View<Tui, App> { impl View<Tui, App> {
@ -257,7 +253,7 @@ impl View<Tui, App> {
}) })
}, },
"full" | "fill" => { "full" => {
let thunk = Self::compile(arg!(expr, head, 1, "content"))?; let thunk = Self::compile(arg!(expr, head, 1, "content"))?;
match head.split('/').skip(1).next() { match head.split('/').skip(1).next() {
Some("w") | Some("x") => Self::boxed(move|state, screen|{ Some("w") | Some("x") => Self::boxed(move|state, screen|{
@ -429,55 +425,60 @@ pub fn view_menu (app: &App) -> impl Draw<Tui> {
} }
pub fn view_history_list (app: &App) -> impl Draw<Tui> { pub fn view_history_list (app: &App) -> impl Draw<Tui> {
above( let border = Outer(true, Style::default().fg(Color::Rgb(0, 192, 0)));
Outer(true, Style::default().fg(Color::Rgb(0, 192, 0))), let background = xy_repeat(" ");
draw(|to: &mut Tui|{ below(border, above(background, draw(|to: &mut Tui|{
let area = to.area(); let area = to.area();
let XYWH(x0, mut y0, w, h) = area; let XYWH(x0, y0, w, h) = area;
for (done, undo) in app.history.iter().rev() { let mut y = 0;
to.clip(XYWH(x0 + 1, y0, w, 1), &|to|format!("{done:?}").draw(to))?; for (done, undo) in app.history.iter().rev() {
y0 += 1; 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<Tui> { pub fn view_config (app: &App) -> impl Draw<Tui> {
above( macro_rules! next_line { ($h:ident, $y:ident, $n:expr) => { $y += $n; if $y > $h { break } }; }
Outer(true, Style::default().fg(Color::Rgb(0, 192, 0))), let border = Outer(true, Style::default().fg(Color::Rgb(0, 192, 0)));
draw(|to: &mut Tui|{ let background = xy_repeat(" ");
let area = to.area(); below(border, above(background, draw(|to: &mut Tui|{
let XYWH(x0, mut y0, w, h) = area; let area = to.area();
for key in app.modes.read().iter().rev() { let XYWH(x0, y0, w, h) = area;
if let Some(mode) = app.config.modes.get(key) { let mut y = 0;
to.clip(XYWH(x0 + 1, y0, w, 1), &|to|Split::East.bar(16, key, mode.info.get(0)).draw(to))?; for key in app.modes.read().iter().rev() {
y0 += 1; if let Some(mode) = app.config.modes.get(key) {
for bind in mode.keys.iter() { to.clip(XYWH(x0 + 1, y0 + y, w, 1), &|to|Split::East.bar(16,
to.clip(XYWH(x0 + 3, y0, w, 1), &|to|bind.draw(to))?; key,
y0 += 1; mode.info.get(0)).draw(to))?;
if let Some(bind) = app.config.binds.find(bind) { next_line!(h, y, 1);
for (key, binding) in bind.0.iter() { for (ctrl, data) in mode.axis.iter() {
to.clip(XYWH(x0 + 5, y0, 40, 1), to.clip(XYWH(x0 + 3, y0 + y, w, 1), &|to|Split::East.bar(4,
&|to|Split::East.bar(20, format!("{:?}", key.0), "").draw(to))?; format!("{ctrl:?}"),
y0 += 1; 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, Ok(Some(area))
//key, mode.info.get(0), })))
//).draw(to))?;
//}
Ok(Some(area))
})
)
}
pub fn view_config_mode (mode: &Mode) -> impl Draw<Tui> {
} }
/// ``` /// ```