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 ::{
std::{
cmp::Ord,
collections::BTreeMap,
collections::{BTreeMap, HashMap},
error::Error,
ffi::OsString,
fmt::{Write, Debug, Formatter},

View file

@ -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 <file browser here>))))))))))
(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 <file browser here>))))))))))
(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 <file browser here>))))))))))
(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 <file browser here>))))))))))
(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))))

View file

@ -368,6 +368,14 @@ impl_as_ref!(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_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 {
fn clip_slot (&self) -> Option<Slot<MidiClip>> {
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<ControlAxis> {
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<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 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,

View file

@ -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<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 {
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::<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].
impl<E, C> Default for Bind<E, C> {
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> {
let mut bind: Bind<TuiEvent, Arc<str>> = 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<TuiEvent, Arc<str>> {
if let Some(event) = TuiKey::from_dsl(&head)?.to_crossterm() {
let mut commands: Vec<Arc<str>> = 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<E: Clone + Ord, C> Bind<E, C> {
}
}
/// 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>>);
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()));

View file

@ -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<Mode> = 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<tek::Mode> = config.get_mode("Menu").unwrap();
/// ```
#[derive(Default, Debug)]
pub struct Config {

View file

@ -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())
}

View file

@ -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<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 {
/// 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<Arc<str>>,
pub info: Vec<Arc<str>>,
pub view: Vec<Arc<View<Tui, App>>>,
pub keys: Vec<Arc<str>>,
pub modes: Modes,
}
impl Mode {
/// Add a definition to the mode.
///
/// Supported definitions:
@ -69,29 +76,46 @@ 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 <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() {
//println!("Mode::add: {head} {:?}", expr.tail());
let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
match head {
"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() {
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 submode = Mode::default();
let submode = body.each(submode, |c,s|Mode::add(c,s))?;
let modes = mode.as_mut().modes.clone();
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));
mode
},
"bind" => {
tail.each(mode, |mut mode: T, expr: &str|{
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)
})?
},
"axis" => {
})
}
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 {
@ -106,19 +130,36 @@ impl Mode {
// 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() {
mode.as_mut().view.push(View::new(word)?.into());
mode
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 {
return Err(format!("Mode::add: unexpected: {dsl:?}").into());
})
err!("invalid axis: ctrl {ctrl:?} + data {data:?}")
}
}
}
@ -128,11 +169,16 @@ impl AsMut<Mode> for Mode {
}
}
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 ControlAxis {
pub fn get_axis <'a> (_: &App, src: impl Language) -> Perhaps<ControlAxis> {
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}")
}))
}
}

View file

@ -1,5 +1,15 @@
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,
/// 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<RwLock<BTreeMap<
Arc<str>,
Arc<View<Tui, App>>
>>>);
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<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_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> {
@ -257,7 +253,7 @@ impl View<Tui, App> {
})
},
"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<Tui> {
}
pub fn view_history_list (app: &App) -> impl Draw<Tui> {
above(
Outer(true, Style::default().fg(Color::Rgb(0, 192, 0))),
draw(|to: &mut Tui|{
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, mut y0, w, h) = 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, w, 1), &|to|format!("{done:?}").draw(to))?;
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))
})
)
})))
}
pub fn view_config (app: &App) -> impl Draw<Tui> {
above(
Outer(true, Style::default().fg(Color::Rgb(0, 192, 0))),
draw(|to: &mut Tui|{
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, mut y0, w, h) = 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, w, 1), &|to|Split::East.bar(16, key, mode.info.get(0)).draw(to))?;
y0 += 1;
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, w, 1), &|to|bind.draw(to))?;
y0 += 1;
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, 40, 1),
&|to|Split::East.bar(20, format!("{:?}", key.0), "").draw(to))?;
y0 += 1;
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<Tui> {
})))
}
/// ```