use crate::*; /// Collection of custom view definitions. pub type Views = Arc, Arc>>>>; /// Custom view definition is a boxed closure emitting a [Draw]able from state `S`. pub struct View { pub source: Arc, pub render: ArcDrawn + Send + Sync>> } impl_debug!( View |self, w| { write!(w, "View({})", self.source) }); impl_display!( View |self, w| { write!(w, "View({})", self.source) }); impl View { pub fn new (source: impl AsRef) -> Usually { Ok(Self { source: source.as_ref().into(), render: Self::compile(source)? }) } fn boxed Drawn + Send + Sync + 'static> (f: F) -> BoxDrawn + Send + Sync + 'static> { Box::new(f) } fn compile (source: impl AsRef) -> UsuallyDrawn + 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(format!("not word/expr:\n{source:?}").into()) }; Ok(Arc::new(Box::new(move|state, screen|layer(state, screen)))) } fn compile_expr (expr: Arc) -> UsuallyDrawn + Send + Sync>>> { macro_rules! arg { ($src:expr, $head:expr, $index:expr, $name:expr) => { $src.nth($index)? .ok_or_else(||Box::::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::::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::::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(format!("invalid split: {split:?}").into()) }; 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(format!("invalid split: {split:?}").into()) }; let size = Arc::from(arg!(expr, head, 1, "size")); let size = move|state: &App|state.namespace(&size)?.ok_or_else(||Box::::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(format!("invalid azimuth: {azimuth:?}").into()) }; 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" | "fill" => { 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" => { 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 { "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 { "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 { "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 = Arc::from(expr.tail()?.unwrap_or_default()); Self::boxed(move|_state, screen|{ text.draw(screen) }) }, _ => return Err(format!("compile_expr: unexpected: {expr:?}").into()) } } else { return Err(format!("compile_expr: invalid expression: {expr:?}").into()) })) } fn compile_word (word: Arc) -> UsuallyDrawn + Send + Sync>>> { macro_rules! draw { (|$state:ident|$body:expr)=>{Self::boxed(move|$state, to|$body.draw(to))} } Ok(Arc::new(match word.split("/").next() { //Some(":logo") => view_logo().draw(to), Some(":meters") => match word.split("/").skip(1).next() { Some("input") => Self::boxed(move|_, to|bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to)), Some("output") => Self::boxed(move|_, to|bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to)), _ => panic!() }, Some(":tracks") => match &word[(":tracks".len()+1)..] { "" => Self::boxed(move|_, to|"TODO tracks".draw(to)), "names" => draw!(|app|app.project.view_track_names(app.color.clone())), "names/count" => draw!(|app|app.project.view_track_names_count()), "names/cells" => draw!(|app|app.project.view_track_names_cells()), "names/add" => draw!(|app|app.project.view_track_names_add()), "devices" => draw!(|app|app.project.view_track_devices(app.color.clone())), "devices/count" => draw!(|app|app.project.view_track_device_count()), "devices/cells" => draw!(|app|app.project.view_track_device_cells(app.tracks_devices_height())), "devices/add" => draw!(|app|app.project.view_track_device_add()), "inputs" => draw!(|app|app.project.view_track_inputs(app.color.clone())), "inputs/count" => draw!(|app|app.project.view_track_input_count()), "inputs/cells" => draw!(|app|app.project.view_track_input_cells(app.tracks_inputs_height())), "inputs/add" => draw!(|app|app.project.view_track_input_add()), "outputs" => draw!(|app|app.project.view_track_outputs(app.color.clone(), 0)), "outputs/count" => draw!(|app|app.project.view_track_output_count()), "outputs/cells" => draw!(|app|app.project.view_track_output_cells()), "outputs/add" => draw!(|app|app.project.view_track_output_add()), _ => panic!("unknown: {word}") }, Some(":scenes") => match word.split("/").skip(1).next() { Some("clips") => draw!(|app|app.view_scenes_clips()), Some("names") => draw!(|app|app.view_scenes_names()), _ => panic!() }, Some(":templates") => draw!(|app|view_templates(app)), Some(":browse/title") => draw!(|app|view_browse_title(app)), Some(":device") => draw!(|app|view_device(app)), Some(":dialog") => Self::boxed(move|state, to|draw_dialog(to, word.split("/").skip(1), state)), Some(":sessions") => Self::boxed(move|_, to|view_sessions().draw(to)), Some(":status") => Self::boxed(move|state, to|draw_status_bar(to, state)), Some(":editor") => Self::boxed(move|_, to|"TODO Editor".draw(to)), Some(":transport") => Self::boxed(move|_, to|view_transport(true, "", "", "").draw(to)), Some(":debug") => Self::boxed(move|_, to|format!("[{:?}]", to.area()).exact_h(1).draw(to)), Some(_) => Self::boxed(move|state, to|if let Some(view) = state.config.get_view(word.as_ref()) { (view.render)(state, to) } else { fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to) }), _ => unreachable!() })) } } /// Load custom view definition. pub fn views_add <'a> (views: &Views, expr: impl Language) -> UsuallyRef<'a, ()> { views.try_write().unwrap().insert( expr.head()?.ok_or("view: missing name")?.into(), View::new(expr.tail()?.ok_or("view: missing body")?)?.into() ); Ok(()) }