simplify scenes traits; wip axis binds

This commit is contained in:
i do not exist 2026-10-01 16:16:32 +03:00
parent 2adf22257e
commit 24c01bf133
10 changed files with 131 additions and 181 deletions

View file

@ -46,7 +46,7 @@ use crate::*;
/// List of scenes
#[cfg(feature = "scene")] pub scenes: Vec<Scene>,
/// Scroll offset of scenes
#[cfg(feature = "scene")] pub scene_scroll: usize,
#[cfg(feature = "scene")] pub scenes_scroll: usize,
}
impl Project {
@ -110,7 +110,6 @@ impl_has!(Jack<'static>: |self: Project| self.jack);
impl_has!(Sizer: |self: Project| self.size);
impl_has!(Clock: |self: Project| self.clock);
impl_has!(Selection: |self: Project| self.selection);
impl_has!(Vec<Scene>: |self: Project| self.scenes);
impl_as_ref_opt!(Scene: |self: Project| self.selected_scene());
impl_as_mut_opt!(Scene: |self: Project| self.selected_scene_mut());
impl_as_ref!(Option<MidiEditor>: |self: Project| &self.editor);
@ -261,8 +260,16 @@ impl HasTrackScroll for Project {
}
}
impl HasSceneScroll for Project {
fn scene_scroll (&self) -> usize { self.scene_scroll }
impl HasScenes for Project {
fn scenes_scroll (&self) -> usize {
self.scenes_scroll
}
fn scenes (&self) -> &Vec<Scene> {
&self.scenes
}
fn scenes_mut (&mut self) -> &mut Vec<Scene> {
&mut self.scenes
}
}
def_sizes_iter!(PortsSizes => Arc<str>, [Connect]);

View file

@ -51,7 +51,5 @@ impl Scene {
pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T);
def_sizes_iter!(ScenesSizes => Scene);
impl_as_ref!(Vec<Scene>: |self: App| self.project.as_ref());
impl_as_mut!(Vec<Scene>: |self: App| self.project.as_mut());
impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt());
impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt());

View file

@ -397,9 +397,15 @@ impl ScenesView for App {
(self.size.h() as u16).saturating_sub(20)
}
}
impl HasSceneScroll for App {
fn scene_scroll (&self) -> usize {
self.project.scene_scroll()
impl HasScenes for App {
fn scenes_scroll (&self) -> usize {
self.project.scenes_scroll()
}
fn scenes (&self) -> &Vec<Scene> {
self.project.scenes()
}
fn scenes_mut (&mut self) -> &mut Vec<Scene> {
self.project.scenes_mut()
}
}
impl HasTrackScroll for App {

View file

@ -12,7 +12,10 @@ use crate::*;
/// 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>>>>>>);
pub struct Binds(
pub Arc<RwLock<BTreeMap<Arc<str>, Arc<Bind<TuiEvent, Arc<str>>>>>>,
pub Arc<RwLock<BTreeMap<Arc<str>, (Arc<str>, Arc<str>)>>>,
);
/// An map of input events (e.g. [TuiEvent]) to [Binding]s.
///
@ -69,55 +72,8 @@ pub struct Bind<E, C>(
#[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,
}
/// Named control axis.
pub struct ControlAxis(pub Arc<str>);
impl Binds {
pub fn add_keys (&self, expr: impl Language) -> Usually<()> {

View file

@ -20,7 +20,7 @@ use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as Notify
/// config.modes.add(stringify!(Menu
/// (name Menu)
/// (info Mode selector.)
/// (axis Y Index)
/// (axis Y Prev Next)
/// (bind Confirm)
/// (view (bg (g 30) (bar/s 7 Logo (full/xy Dialog))))
/// )).unwrap();
@ -38,7 +38,7 @@ pub struct Config {
pub binds: Binds,
/// Active collection of view definitions.
pub views: Views,
/// Error caught during reloading
/// Error caught during (re)loading
pub error: RwLock<Option<Arc<str>>>,
/// Timestamp
pub stamp: AtomicU64,

View file

@ -6,20 +6,33 @@ tui_keys!(self: App, input {
});
impl App {
fn input_cmds (&mut self, input: &TuiEvent) -> Usually<()> {
if let Some(bindings) = Self::input_cmds_find(self, input) {
return Self::input_cmds_exec(self, bindings)
pub fn input_cmds (&mut self, input: &TuiEvent) -> Usually<()> {
if let Some(bindings) = self.input_cmds_find(input) {
return self.input_cmds_exec(bindings)
}
Ok(())
}
fn input_cmds_find (state: &Self, input: &TuiEvent) -> Option<Arc<[Binding<Arc<str>>]>> {
for name in state.modes.read().iter().rev() {
let mode = state.config.get_mode(name).as_ref().map(Arc::clone);
/// ```
/// let app = tek::App::default();
/// tek::Config::load(&app.config, stringify!(
/// (axis Y @down @up)
/// (mode Menu (axis Y prev next) (bind (@enter confirm)))
/// )).unwrap();
/// panic!("{app:#?}");
/// assert!(app.input_cmds_find(&"left".into()).is_none());
/// assert!(app.input_cmds_find(&"enter".into()).is_some());
/// assert!(app.input_cmds_find(&"up".into()).is_some());
/// assert!(app.input_cmds_find(&"down".into()).is_some());
/// ```
pub fn input_cmds_find (&self, input: &TuiEvent) -> Option<Arc<[Binding<Arc<str>>]>> {
for name in self.modes.read().iter().rev() {
let mode = self.config.get_mode(name).as_ref().map(Arc::clone);
if let Some(mode) = mode {
for (ctrl, data) in mode.axis.iter() {
for (ctrl, (dec, inc)) in mode.axis.iter() {
panic!("{ctrl:?} {dec:?} {inc:?}");
}
for id in mode.keys.iter() {
if let Some(event_map) = state.config.binds.find(id)
if let Some(event_map) = self.config.binds.find(id)
&& let Some(bindings) = event_map.query(input) {
return Some(bindings.into())
}
@ -28,31 +41,32 @@ impl App {
}
None
}
fn input_cmds_exec (state: &mut Self, bindings: Arc<[Binding<Arc<str>>]>) -> Usually<()> {
pub fn input_cmds_exec (&mut self, bindings: Arc<[Binding<Arc<str>>]>) -> Usually<()> {
for binding in bindings.iter() {
for src in binding.commands.iter() {
if let Some(command) = Namespace::<AppCmd>::namespace(state, &src)? {
Self::cmd_dispatch(state, command)?;
if let Some(command) = Namespace::<AppCmd>::namespace(self, &src)? {
self.cmd_dispatch(command)?;
}
}
}
Ok(())
}
fn cmd_dispatch (state: &mut Self, command: AppCmd) -> Usually<()> {
match command.clone().dispatch(state) {
pub fn cmd_dispatch (&mut self, command: AppCmd) -> Usually<()> {
match command.clone().dispatch(self) {
Ok(undo) => {
state.history.push((command, undo));
self.history.push((command, undo));
Ok(())
},
Err(err) => {
state.history.push((command, None));
self.history.push((command, None));
Err(err)
}
}
}
}
impl_from!(AppCmd: |x: AxisCmd| AppCmd::Axis { command: x });
impl_from!(AppCmd: |x: BrowseCmd| AppCmd::Browse { command: x });
impl_from!(AppCmd: |x: MidiClipCmd| AppCmd::MidiClip { command: x });
impl_from!(AppCmd: |x: MidiEditCmd| AppCmd::MidiEdit { command: x });
@ -93,9 +107,29 @@ impl_from!(AppCmd: |x: TracksCmd| AppCmd::Tracks { command: x });
Ok(None)
}
}
/// Navigate along a contextual axis.
#[command(Axis = "axis")] pub fn cmd_axis (&mut self, command: AxisCmd) -> Perhaps<AppCmd> {
Ok(command.dispatch(self)?.map(Into::into))
/// Increment the given data axis.
#[command(Inc = "inc")] pub fn inc (&mut self, axis: Arc<str>) -> Perhaps<AppCmd> {
panic!("inc {axis:?}");
//Ok(match (&self.dialog, axis) {
//(Dialog::None, _) => todo!(),
//(Dialog::Menu(_, _), ControlAxis::Y) => {
//AppCmd::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?;
//None
//},
//_ => todo!()
//})
}
/// Decrement the given data axis.
#[command(Dec = "dec")] pub fn dec (&mut self, axis: Arc<str>) -> Perhaps<AppCmd> {
panic!("dec {axis:?}");
//Ok(match (&self.dialog, axis) {
//(Dialog::None, _) => None,
//(Dialog::Menu(_, _), ControlAxis::Y) => {
//AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?;
//None
//},
//_ => todo!()
//})
}
/// Cancel current selection.
#[command(Cancel = "cancel")] pub fn cmd_cancel (&mut self) -> Perhaps<AppCmd> {
@ -737,30 +771,6 @@ impl<T> BrowseControl for T where T:
}
#[tek_proc::commands(AxisCmd)] impl App {
/// Increment the given data axis.
#[command(Inc = "inc")] pub fn inc (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
panic!("inc {axis:?}");
Ok(match (&self.dialog, axis) {
(Dialog::None, _) => todo!(),
(Dialog::Menu(_, _), ControlAxis::Y) => {
AppCmd::Dialog { dialog: self.dialog.menu_next() }.dispatch(self)?;
None
},
_ => todo!()
})
}
/// Decrement the given data axis.
#[command(Dec = "dec")] pub fn dec (&mut self, axis: ControlAxis) -> Perhaps<AxisCmd> {
panic!("dec {axis:?}");
Ok(match (&self.dialog, axis) {
(Dialog::None, _) => None,
(Dialog::Menu(_, _), ControlAxis::Y) => {
AppCmd::Dialog { dialog: self.dialog.menu_prev() }.dispatch(self)?;
None
},
_ => todo!()
})
}
}
#[tek_proc::command(AudioInput)]

View file

@ -1,10 +1,15 @@
use crate::*;
/// Has a stack of currently active interaction modes.
pub trait HasModeStack {
/// Readable mode stack.
fn modes (&self) -> RwLockReadGuard<'_, Vec<Arc<str>>>;
/// Writable mode stack.
fn modes_mut (&mut self) -> RwLockWriteGuard<'_, Vec<Arc<str>>>;
}
/// Has a MIDI editor.
///
/// ```
/// use tek::{*, tengri::*};
///
@ -32,11 +37,13 @@ pub trait HasEditor:
}
}
/// Has a pair of buffers for MIDI IO.
pub trait HasMidiBuffers {
fn note_buf_mut (&mut self) -> &mut Vec<u8>;
fn midi_buf_mut (&mut self) -> &mut Vec<Vec<Vec<u8>>>;
}
/// Can have a currently playing clip.
pub trait HasPlayClip: HasClock {
fn reset (&self) -> bool;
fn reset_mut (&mut self) -> &mut bool;
@ -46,6 +53,7 @@ pub trait HasPlayClip: HasClock {
fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option<Arc<RwLock<MidiClip>>>)>;
}
/// Has a MIDI sequencer.
impl <T> HasSequencer for T where T:
AsRef<Sequencer> + AsMut<Sequencer> {}
pub trait HasSequencer:
@ -62,6 +70,7 @@ pub trait HasSequencer:
impl<T> HasClock for T where T:
AsRef<Clock> + AsMut<Clock> {}
/// Has a clock.
pub trait HasClock:
AsRef<Clock> + AsMut<Clock>
{
@ -73,6 +82,7 @@ pub trait HasClock:
}
}
/// Has a chain of devices.
pub trait HasDevices:
AsRef<Vec<Device>> + AsMut<Vec<Device>>
{
@ -88,14 +98,11 @@ pub trait HasProject:
AsRef<Option<Project>> + AsMut<Option<Project>>
{}
/// Has a loaded project.
impl<T> HasProject for T where T:
AsRef<Option<Project>> + AsMut<Option<Project>>
{}
pub trait HasSceneScroll: HasScenes {
fn scene_scroll (&self) -> usize;
}
impl<T> HasScene for T where T:
AsRefOpt<Scene> + AsMutOpt<Scene> + Send + Sync
{}
@ -111,16 +118,11 @@ pub trait HasScene:
}
}
impl<T> HasScenes for T where T:
AsRef<Vec<Scene>> + AsMut<Vec<Scene>>
{}
pub trait HasScenes: AsRef<Vec<Scene>> + AsMut<Vec<Scene>> {
fn scenes (&self) -> &Vec<Scene> {
self.as_ref()
}
fn scenes_mut (&mut self) -> &mut Vec<Scene> {
self.as_mut()
pub trait HasScenes {
fn scenes (&self) -> &Vec<Scene>;
fn scenes_mut (&mut self) -> &mut Vec<Scene>;
fn scenes_scroll (&self) -> usize {
0
}
}

View file

@ -28,7 +28,7 @@ pub struct Mode {
pub info: Vec<Arc<str>>,
pub view: Vec<Arc<View<Tui, App>>>,
pub keys: Vec<Arc<str>>,
pub axis: HashMap<ControlAxis, DataAxis>,
pub axis: BTreeMap<Arc<str>, (Arc<str>, Arc<str>)>,
pub modes: Modes,
}
@ -82,16 +82,16 @@ impl Mode {
Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() {
let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
match head {
"self" => self.as_mut().add_mode(tail)?,
"bind" => self.as_mut().add_bind(tail)?,
"axis" => self.as_mut().add_axis(tail)?,
"name" => { self.as_mut().name.push(tail.into()); self },
"info" => { self.as_mut().info.push(tail.into()); self },
"view" => { self.as_mut().view.push(View::new(tail)?.into()); self },
_ => { self.as_mut().view.push(View::new(tail)?.into()); self },
"self" => self.add_mode(tail)?,
"bind" => self.add_bind(tail)?,
"axis" => self.add_axis(tail)?,
"name" => { self.name.push(tail.into()); self },
"info" => { self.info.push(tail.into()); self },
"view" => { self.view.push(View::new(tail)?.into()); self },
_ => { self.view.push(View::new(tail)?.into()); self },
}
} else if let Ok(Some(word)) = dsl.word() {
self.as_mut().view.push(View::new(word)?.into());
self.view.push(View::new(word)?.into());
self
} else {
return Err(format!("Mode::add: unexpected: {dsl:?}").into());
@ -110,57 +110,36 @@ impl Mode {
fn add_bind (&mut self, tail: &str) -> Usually<&mut Self> {
tail.each(self, |mode: &mut Self, expr: &str|{
mode.as_mut().keys.push(expr.trim().into());
mode.keys.push(expr.trim().into());
Ok(mode)
})
}
fn add_axis (&mut self, tail: &str) -> Usually<&mut Self> {
let mut ctrl: Option<Arc<str>> = None;
let mut data: Option<Arc<str>> = None;
let mut axis: Option<Arc<str>> = None;
let mut dec: Option<Arc<str>> = None;
let mut inc: Option<Arc<str>> = None;
tail.each(0, |index: usize, src: &str|if index == 0 {
ctrl = Some(src.into());
axis = Some(src.into());
Ok(1)
} else if index == 1 {
data = Some(src.into());
dec = Some(src.into());
Ok(2)
} else if index == 2 {
inc = Some(src.into());
Ok(3)
} else {
err!("(mode (axis)) takes 2 keysyms, control and data")
err!("expected (mode (axis [Name] [command] [command])), got: {tail}")
})?;
// TODO: get inc/dec keys from axis?
//mode.as_mut().keys.push(format!("({inc} (inc ({axis})))").into());
//mode.as_mut().keys.push(format!("({dec} (dec ({axis})))").into());
let ctrl = ctrl.map(|ctrl|match ctrl.as_ref() {
"H" => Some(ControlAxis::H),
"I" => Some(ControlAxis::I),
"P" => Some(ControlAxis::P),
"W" => Some(ControlAxis::W),
"X" => Some(ControlAxis::X),
"Y" => Some(ControlAxis::Y),
"Z" => Some(ControlAxis::Z),
_ => None,
}).flatten();
let data = data.map(|data|match data.as_ref() {
"Height" => Some(DataAxis::Height),
"Index" => Some(DataAxis::Index),
"Input" => Some(DataAxis::Input),
"Length" => Some(DataAxis::Length),
"Page" => Some(DataAxis::Page),
"Pitch" => Some(DataAxis::Pitch),
"Scene" => Some(DataAxis::Scene),
"Time" => Some(DataAxis::Time),
"Track" => Some(DataAxis::Track),
"Width" => Some(DataAxis::Width),
"Zoom" => Some(DataAxis::Zoom),
_ => None,
}).flatten();
if let Some(ctrl) = ctrl && let Some(data) = data {
self.as_mut().axis.insert(ctrl, data);
if let Some(axis) = axis && let Some(dec) = dec && let Some(inc) = inc {
self.keys.push(format!("((dec {axis}) {dec})").into());
self.keys.push(format!("((inc {axis}) {inc})").into());
Ok(self)
} else {
err!("invalid axis: ctrl {ctrl:?} + data {data:?}")
err!("expected (mode (axis X Decrement Increment)), got: {tail}")
}
}
}
impl AsMut<Mode> for Mode {
@ -171,14 +150,6 @@ impl AsMut<Mode> for Mode {
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}")
}))
eval_word(src, |word|Ok(ControlAxis(word.into())))
}
}

View file

@ -671,7 +671,7 @@ impl Draw<Tui> for MidiEditor {
}
impl<T> TracksView for T where T:
HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
HasTracks + HasScenes + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
{}
fn iter_tracks_with_sizes <'t, T> (state: &'t T) -> impl TracksSizes<'t> where
@ -694,7 +694,7 @@ fn iter_tracks_with_sizes <'t, T> (state: &'t T) -> impl TracksSizes<'t> where
}
pub trait TracksView:
HasTracks + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
HasTracks + HasScenes + ScenesView + HasMidiIns + HasMidiOuts + HasTrack
{
/// Iterate over tracks with their corresponding sizes.
fn tracks_with_sizes (&self) -> impl TracksSizes<'_> {
@ -897,7 +897,7 @@ fn view_n_of_m (key: &str, name: &str, index: Option<usize>, count: usize) -> im
button_3(key, name, east(index, count), false)
}
pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync {
pub trait ScenesView: HasScenes + HasEditor + HasSelection + HasClipsSize + Send + Sync {
fn h_scenes (&self) -> u16;
fn w_side (&self) -> u16;
fn w_mid (&self) -> u16;
@ -915,10 +915,10 @@ pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize +
}
fn iter_scenes_with_sizes <'s, T> (state: &'s T) -> impl ScenesSizes<'s> where T:
HasScenes + HasSceneScroll + HasEditor + HasSelection + HasClipsSize + ?Sized
HasScenes + HasEditor + HasSelection + HasClipsSize + ?Sized
{
let mut y = 0;
let skip = state.scene_scroll();
let skip = state.scenes_scroll();
let selected = state.selection();
let editor = state.editor();
let clips_size = state.clips_size();

2
tengri

@ -1 +1 @@
Subproject commit 50c0ed1c66c9db97b83b95eb23a7b0bf00c5a760
Subproject commit ab8c8cb1c4f2bed9ef4e333d5fc091e586ff0202