mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-10-08 18:56:42 +02:00
pass doctests
This commit is contained in:
parent
057d09cfdf
commit
2adf22257e
8 changed files with 383 additions and 269 deletions
|
|
@ -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},
|
||||
|
|
|
|||
67
src/tek.edn
67
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 <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))))
|
||||
|
|
|
|||
64
src/tek.rs
64
src/tek.rs
|
|
@ -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,
|
||||
|
|
|
|||
188
src/tek_bind.rs
188
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<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()));
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
146
src/tek_mode.rs
146
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<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}")
|
||||
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
})))
|
||||
}
|
||||
|
||||
/// ```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue