more shuffling; testing dispatch

This commit is contained in:
i do not exist 2026-10-05 02:26:01 +03:00
parent 56df5c5b78
commit 02dc7bc9f8
12 changed files with 1339 additions and 1359 deletions

View file

@ -19,12 +19,6 @@ impl_default!(ClockView: {
}
});
/// 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>,
@ -75,8 +69,31 @@ pub fn view_debug (perf: &PerfModel, size: &Sizer) -> impl Draw<Tui> {
)
}
/// ```
/// let x = "";
/// let _ = tek::view_status(None, x.as_ref(), x.as_ref(), x.as_ref());
/// let _ = tek::view_status(Some("".into()), x.as_ref(), x.as_ref(), x.as_ref());
/// ```
pub fn view_status <'a> (sel: Option<&str>, sr: &str, buf: &str, lat: &str) -> impl Draw<Tui> {
let theme = ItemTheme::G[96];
let sr = field_h(theme, "SR", sr);
let buf = field_h(theme, "Buf", buf);
let lat = field_h(theme, "Lat", lat);
bg(Black, east!(above(
sel.map(|sel|field_h(theme, "Selected", sel)).align_w().full_wh(),
east!(sr, buf, lat).align_e().full_wh(),
)))
}
pub fn draw_status_bar (to: &mut Tui, state: &App) -> Drawn<u16> {
bar(Split::South, 1u16,
dim(true, "Mode"),
bold(true, state.mode_stack().read().get(0))
).push_x(1).draw(to)
}
pub fn draw_mode_stack (to: &mut Tui, state: &App) -> Usually<()> {
for name in state.modes.read().iter() {
for name in state.mode_stack().read().iter() {
let mut error = false;
if let Some(mode) = state.modes_get(name).as_ref().map(Arc::clone) {
for (index, view) in mode.view.iter().enumerate() {
@ -107,303 +124,6 @@ pub fn draw_mode_stack (to: &mut Tui, state: &App) -> Usually<()> {
Ok(())
}
impl View<Tui, App> {
pub fn new (source: impl AsRef<str>) -> Usually<Self> {
Ok(Self {
source: source.as_ref().into(),
render: Self::compile(source)?
})
}
fn boxed <F: Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync + 'static> (f: F)
-> Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync + 'static>
{
Box::new(f)
}
fn compile (source: impl AsRef<str>) ->
Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{
let source = source.as_ref();
let layer = if let Ok(Some(expr)) = source.expr() {
Self::compile_expr(expr.into())?
} else if let Ok(Some(word)) = source.word() {
Self::compile_word(word.into())?
} else {
return err!("not word/expr:\n{source:?}")
};
Ok(Arc::new(Box::new(move|state, screen|layer(state, screen))))
}
fn compile_expr (expr: Arc<str>) ->
Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{
macro_rules! arg {
($src:expr, $head:expr, $index:expr, $name:expr) => {
$src.nth($index)?
.ok_or_else(||Box::<dyn Error>::from(format!(
"{}: no arg #{} ({}) in {}", $head, $index, $name, $src
)))?
};
}
Ok(Arc::new(if let Some(head) = expr.head()? && let Some(ns) = head.split('/').next() {
match ns {
"when" => {
let cond = Arc::from(arg!(expr, head, 1, "condition"));
let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("when: no condition value"));
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
Self::boxed(move|state, screen|{
when(
cond(state)?,
draw(|screen|thunk(state, screen))
).draw(screen)
})
},
"either" => {
let cond = Arc::from(arg!(expr, head, 1, "condition"));
let cond = move|state: &App|state.namespace(&cond)?.ok_or_else(||Box::<dyn Error>::from("either: no condition value"));
let a = Self::compile(arg!(expr, head, 2, "content A"))?;
let b = Self::compile(arg!(expr, head, 3, "content B"))?;
Self::boxed(move|state, screen|{
either(
cond(state)?,
draw(|screen|a(state, screen)),
draw(|screen|b(state, screen)),
).draw(screen)
})
},
"bsp" | "split" | "stack" => {
let split = head.split('/').skip(1).next();
let split = match split {
Some("n") => Split::North,
Some("s") => Split::South,
Some("e") => Split::East,
Some("w") => Split::West,
Some("a") => Split::Above,
Some("b") => Split::Below,
_ => return err!("invalid split: {split:?}")
};
let a = Self::compile(arg!(expr, head, 1, "content A"))?;
let b = Self::compile(arg!(expr, head, 2, "content B"))?;
Self::boxed(move|state, screen|split.stack(
draw(|screen|a(state, screen)),
draw(|screen|b(state, screen)),
).draw(screen))
},
"bar" => {
let split = head.split('/').skip(1).next();
let split = match split {
Some("n") => Split::North,
Some("s") => Split::South,
Some("e") => Split::East,
Some("w") => Split::West,
Some("a") => Split::Above,
Some("b") => Split::Below,
_ => return err!("invalid split: {split:?}")
};
let size = Arc::from(arg!(expr, head, 1, "size"));
let size = move|state: &App|state.namespace(&size)?.ok_or_else(||Box::<dyn Error>::from("bar: no size"));
let a = Self::compile(arg!(expr, head, 2, "content A"))?;
let b = Self::compile(arg!(expr, head, 3, "content B"))?;
Self::boxed(move|state, screen|split.bar(
size(state)?,
draw(|screen|a(state, screen)),
draw(|screen|b(state, screen)),
).draw(screen))
},
"align" => {
let azimuth = head.split('/').skip(1).next();
let azimuth = match azimuth {
Some("n") => Azimuth::N,
Some("s") => Azimuth::S,
Some("e") => Azimuth::E,
Some("w") => Azimuth::W,
Some("ne") => Azimuth::NE,
Some("se") => Azimuth::SE,
Some("nw") => Azimuth::NW,
Some("sw") => Azimuth::SW,
Some("c") => Azimuth::C,
Some("x") => Azimuth::X,
Some("y") => Azimuth::Y,
_ => return err!("invalid azimuth: {azimuth:?}")
};
let thunk = Self::compile(arg!(expr, head, 1, "content"))?;
Self::boxed(move|state, screen|{
Align(
Some(azimuth),
draw(|screen|thunk(state, screen))
).draw(screen)
})
},
"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|{
Full::W(draw(|screen|thunk(state, screen))).draw(screen)
}),
Some("h") | Some("y") => Self::boxed(move|state, screen|{
Full::H(draw(|screen|thunk(state, screen))).draw(screen)
}),
Some("wh") | Some("xy") | None => Self::boxed(move|state, screen|{
Full::WH(draw(|screen|thunk(state, screen))).draw(screen)
}),
_ => unreachable!()
}
},
"exact" | "min" | "max" | "push" | "pull" | "pad" | "shrink" | "expand" => {
match head.split('/').skip(1).next() {
Some("w") | Some("x") => {
let value = Arc::from(arg!(expr, head, 1, "value"));
let value = move|state: &App|state.namespace(&value);
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
match ns {
"shrink" => Self::boxed(move|state, screen|Shrink::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"expand" => Self::boxed(move|state, screen|Expand::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"exact" => Self::boxed(move|state, screen|Exact::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"push" => Self::boxed(move|state, screen|Push::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"pull" => Self::boxed(move|state, screen|Pull::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"pad" => Self::boxed(move|state, screen|Pad::X(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"min" => Self::boxed(move|state, screen|Min::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"max" => Self::boxed(move|state, screen|Max::W(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
_ => unreachable!()
}
},
Some("h") | Some("y") => {
let value = Arc::from(arg!(expr, head, 1, "value"));
let value = move|state: &App|state.namespace(&value);
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
match ns {
"shrink" => Self::boxed(move|state, screen|Shrink::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"expand" => Self::boxed(move|state, screen|Expand::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"exact" => Self::boxed(move|state, screen|Exact::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"push" => Self::boxed(move|state, screen|Push::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"pull" => Self::boxed(move|state, screen|Pull::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"pad" => Self::boxed(move|state, screen|Pad::Y(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"min" => Self::boxed(move|state, screen|Min::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
"max" => Self::boxed(move|state, screen|Max::H(draw(|screen|thunk(state, screen)), value(state)?).draw(screen)),
_ => unreachable!()
}
},
Some("wh") | Some("xy") | None => {
let value1 = Arc::from(arg!(expr, head, 1, "value"));
let value1 = move|state: &App|state.namespace(&value1);
let value2 = Arc::from(arg!(expr, head, 2, "value"));
let value2 = move|state: &App|state.namespace(&value2);
let thunk = Self::compile(arg!(expr, head, 3, "content"))?;
match ns {
"shrink" => Self::boxed(move|state, screen|Shrink::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
"expand" => Self::boxed(move|state, screen|Expand::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
"exact" => Self::boxed(move|state, screen|Exact::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
"push" => Self::boxed(move|state, screen|Push::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
"pull" => Self::boxed(move|state, screen|Pull::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
"pad" => Self::boxed(move|state, screen|Pad::XY(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
"min" => Self::boxed(move|state, screen|Min::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
"max" => Self::boxed(move|state, screen|Max::WH(draw(|screen|thunk(state, screen)), value1(state)?, value2(state)?).draw(screen)),
_ => unreachable!()
}
},
_ => unreachable!()
}
},
"fg" | "bg" => {
let color = Arc::from(arg!(expr, head, 1, "color"));
let color = move|state: &App|state.namespace(&color);
let thunk = Self::compile(arg!(expr, head, 2, "content"))?;
match ns {
"fg" => Self::boxed(move|state, screen|fg(
color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen))
).draw(screen)),
"bg" => Self::boxed(move|state, screen|bg(
color(state)?.unwrap_or_default(), draw(|screen|thunk(state, screen))
).draw(screen)),
_ => unreachable!()
}
},
"text" => {
let text: Arc<str> = Arc::from(expr.tail()?.unwrap_or_default());
Self::boxed(move|_state, screen|{
text.draw(screen)
})
},
_ => return err!("compile_expr: unexpected: {expr:?}")
}
} else {
return err!("compile_expr: invalid expression: {expr:?}")
}))
}
fn compile_word (word: Arc<str>)
-> Usually<Arc<Box<dyn Fn(&App, &mut Tui)->Drawn<u16> + Send + Sync>>>
{
macro_rules! view {
(|$state:ident|$body:expr)=>{Box::new(move|$state, to|$body.draw(to))};
($const:expr)=>{Box::new(move|_, to|$const.draw(to))};
()=>{Box::new(move|_, __|Ok(None))};
}
Ok(Arc::new(match word.split("/").next() {
Some("Beat") => view!(),
Some("Bpm") => view!(),
Some("BrowseTitle") => view!(|app|view_browse_title(app)),
Some("Config") => view!(|app|view_config(app)),
Some("Debug") => Box::new(move|_, to|format!("[{:?}]", to.area()).exact_h(1).draw(to)),
Some("Device") => view!(|app|view_device(app)),
Some("Menu") => view!(|app|view_menu(app)),
Some("Editor") => view!(|app|app.editor()),
Some("MetersIn") => view!(|___|bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h())),
Some("MetersOut") => view!(|___|bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h())),
Some("ModeList") => view!(|app|view_modes(app)),
Some("HistoryCount") => view!(|app|app.history.len()),
Some("HistoryList") => view!(|app|view_history_list(app)),
Some("ScenesClips") => view!(|app|view_scenes_clips(app)),
Some("ScenesNames") => view!(|app|app.view_scenes_names()),
Some("Sessions") => view!(|___|view_sessions()),
Some("Selected") => view!(|app|view_selection(app)),
Some("Templates") => view!(|app|view_templates(app)),
Some("Time") => view!(),
Some("Tracks") => view!("TODO tracks"),
Some("TracksDevices") => view!(|app|app.project.view_track_devices(app.color.clone())),
Some("TracksDevicesAdd") => view!(|app|app.project.view_track_device_add()),
Some("TracksDevicesCells") => view!(|app|app.project.view_track_device_cells(app.tracks_devices_height())),
Some("TracksDevicesCount") => view!(|app|app.project.view_track_device_count()),
Some("TracksInputs") => view!(|app|app.project.view_track_inputs(app.color.clone())),
Some("TracksInputsAdd") => view!(|app|app.project.view_track_input_add()),
Some("TracksInputsCells") => view!(|app|app.project.view_track_input_cells(app.tracks_inputs_height())),
Some("TracksInputsCount") => view!(|app|app.project.view_track_input_count()),
Some("TracksNames") => view!(|app|app.project.view_track_names(app.color.clone())),
Some("TracksNamesAdd") => view!(|app|app.project.view_track_names_add()),
Some("TracksNamesCells") => view!(|app|app.project.view_track_names_cells()),
Some("TracksNamesCount") => view!(|app|app.project.view_track_names_count()),
Some("TracksOutputs") => view!(|app|app.project.view_track_outputs(app.color.clone(), 0)),
Some("TracksOutputsAdd") => view!(|app|app.project.view_track_output_add()),
Some("TracksOutputsCells") => view!(|app|app.project.view_track_output_cells()),
Some("TracksOutputsCount") => view!(|app|app.project.view_track_output_count()),
Some(_) => Box::new(move|state, to|if let Some(view) = state.views_get(word.as_ref()) {
(view.render)(state, to)
} else {
fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to)
}),
_ => unreachable!()
}))
}
}
pub fn view_menu (app: &App) -> impl Draw<Tui> {
if let Dialog::Menu(selected, items) = &app.dialog {
Some(iter_south(move||items.iter().enumerate().map(move|(index, MenuItem(item, _))|{
@ -442,7 +162,7 @@ pub fn view_config (app: &App) -> impl Draw<Tui> {
let area = to.area();
let XYWH(x0, y0, w, h) = area;
let mut y = 0;
for key in app.modes.read().iter().rev() {
for key in app.stack.read().iter().rev() {
if let Some(mode) = app.modes_get(key) {
to.clip(XYWH(x0 + 1, y0 + y, w, 1), &|to|Split::East.bar(16,
key,
@ -933,7 +653,7 @@ fn iter_scenes_with_sizes <'s, T> (state: &'s T) -> impl ScenesSizes<'s> where T
})
}
fn view_modes <'a, S: HasModeStack> (state: &'a S) -> impl Draw<Tui> + 'a {
pub fn view_modes <'a, S: HasModeStack> (state: &'a S) -> impl Draw<Tui> + 'a {
draw(move|to: &mut Tui|{
let XYWH(x, y, w, h) = to.area();
let mut x_next = x;
@ -1081,29 +801,6 @@ pub fn draw_lv2_header (state: &Lv2, to: &mut Tui, x: u16, y: u16, w: u16) {
//Ok(Rect { x, y, width: w, height: 1 })
}
pub fn draw_status_bar (to: &mut Tui, state: &App) -> Drawn<u16> {
bar(Split::South, 1u16,
dim(true, "Mode"),
bold(true, state.modes.read().get(0))
).push_x(1).draw(to)
}
/// ```
/// let x = "";
/// let _ = tek::view_status(None, x.as_ref(), x.as_ref(), x.as_ref());
/// let _ = tek::view_status(Some("".into()), x.as_ref(), x.as_ref(), x.as_ref());
/// ```
pub fn view_status <'a> (sel: Option<&str>, sr: &str, buf: &str, lat: &str) -> impl Draw<Tui> {
let theme = ItemTheme::G[96];
let sr = field_h(theme, "SR", sr);
let buf = field_h(theme, "Buf", buf);
let lat = field_h(theme, "Lat", lat);
bg(Black, east!(above(
sel.map(|sel|field_h(theme, "Selected", sel)).align_w().full_wh(),
east!(sr, buf, lat).align_e().full_wh(),
)))
}
pub fn view_device <'a> (state: &'a App) -> impl Draw<Tui> {
let selected = state.dialog.device_kind().unwrap();
south(
@ -1157,7 +854,7 @@ pub fn view_scene_name <'a> (
}
/// Draw clips per scene
fn view_scenes_clips <S: TracksView + ScenesView> (state: &S) -> impl Draw<Tui> {
pub fn view_scenes_clips <S: TracksView + ScenesView> (state: &S) -> impl Draw<Tui> {
let select = state.selection();
let editor = state.editor();
let editing = state.is_editing();
@ -1281,10 +978,11 @@ pub fn view_selection <'a> (state: &'a App) -> impl Draw<Tui> + use<'a> {
}
pub fn view_templates <'a> (state: &'a App) -> impl Draw<Tui> + use<'a> {
let height = (state.config.modes_count() * 2) as u16;
let config: &Config = state.config();
let height = (config.modes_count() * 2) as u16;
draw(move |to: &mut Tui|{
let mut index = 0;
state.config.modes_for_each(&mut |id: &str, profile: &Mode| {
config.modes_for_each(&mut |id: &str, profile: &Mode| {
let b = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) };
let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or("<no name>");
let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
@ -1691,39 +1389,6 @@ impl ClockView {
}
}
pub trait HasViews {
fn views (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>>;
/// Load custom view definition.
fn views_add <'a> (&self, expr: impl Language) -> UsuallyRef<'a, ()> {
self.views().write().insert(
expr.head()?.ok_or("view: missing name")?.into(),
View::new(expr.tail()?.ok_or("view: missing body")?)?.into()
);
Ok(())
}
fn views_clear (&self) {
*self.views().write() = Default::default();
}
fn views_get (&self, name: impl AsRef<str>) -> Option<Arc<View<Tui, App>>> {
self.views().read().get(name.as_ref()).cloned()
}
fn views_for_each <T> (&self, cb: impl Fn(&Arc<str>, &Arc<View<Tui, App>>)->T) -> () {
for (k, v) in self.views().read().iter() {
cb(k, v);
}
}
}
impl HasViews for App {
fn views (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>> {
&self.config.config.views.0
}
}
impl HasViews for Config {
fn views (&self) -> &RwLock<BTreeMap<Arc<str>, Arc<View<Tui, App>>>> {
&self.views.0
}
}
#[cfg(test)] mod test_view {
use super::*;
use proptest::prelude::*;