use crate::{*, lang::*}; use ratatui::style::Color; /// Some layout operations exist in XY, X, and Y variants that take 3 or 2 arguments. /// Their handling in [eval_view] is uniform and goes like this: macro_rules! eval_xy (( $name:expr, $expr:expr, $head:expr, $output:ident, $state:ident, $variant:expr, $arg0: ident, $arg1: ident, $arg2: ident, $xy:ident, $x:ident, $y:ident ) => {{ let variant = $variant; let cb = thunk(move|output: &mut O|$state.interpret(output, &match variant { Some("x") | Some("y") => $arg1, Some("xy") | None => $arg2, _ => panic!("{}: unsupported axis {variant:?}; try /x, /y, /xy", $name) })); match variant { // XY variant (can be omitted) Some("xy") | None => cb.push_xy( $state.namespace($arg0?)?, $state.namespace($arg1?)?, ).draw($output), // X variant Some("x") => cb.push_x( $state.namespace($arg0?)?, ).draw($output), // Y variant Some("y") => cb.push_y( $state.namespace($arg0?)?, ).draw($output), // Other namespace members are invalid frag => { let name = $name; let expr = $expr; let head = $head; unimplemented!( "{name}/{frag:?} ({expr:?}) ({head:?}) ({:?})", head.src()?.unwrap_or_default().split("/").next() ) } } }}); /// Some layout operations exist in multiple variants that take a single argument. /// Their handling in [eval_view] is uniform and goes like this: macro_rules! eval_enum (( $name:literal, $output:ident, $state:ident, $value:expr, $arg0: ident, $Enum:ident { $($v:literal => $V:ident),* $(,)? } ) => {{ match $value { $(Some($v) => $Enum::$V,)* frag => unimplemented!("{}/{frag:?}", $name) } }}); /// Interpret layout operation. /// /// ``` /// # use tengri::{*, lang::*}; /// /// struct State {/*app-specific*/} /// impl<'b> Namespace<'b, u16> for State {} /// impl<'b> Namespace<'b, bool> for State {} /// impl<'b> Namespace<'b, Option> for State {} /// impl Interpret>> for State {} /// /// # fn main () -> tengri::Usually<()> { /// let state = State {}; /// let mut target = Tui::new(80, 25); /// eval_view(&state, &mut target, &"")?; /// eval_view(&state, &mut target, &"(whe true (text hello))")?; /// eval_view(&state, &mut target, &"(either true (text hello) (text world))")?; /// // TODO test all /// # Ok(()) } /// ``` pub fn eval_view <'a, O: Screen + 'a, S> ( state: &S, output: &mut O, expr: &'a impl Expression ) -> Perhaps> where S: Interpret>> + for<'b> Namespace<'b, bool> + for<'b> Namespace<'b, O::Unit> + for<'b> Namespace<'b, Option> { // First element of expression is name of the operation. // These are quasi-namespaced using the separator character, `/`. let head = expr.head()?; let mut frags = head.src()?.unwrap_or_default().split("/"); // The rest of the tokens in the expr are arguments. // Their meanings depend on the dispatched operation // Here we just reference them, so that they are in scope. // Dereferencing them happens in the dispatch branch. let args = expr.tail(); let arg0 = args.head(); let tail0 = args.tail(); let arg1 = tail0.head(); let tail1 = tail0.tail(); let arg2 = tail1.head(); // First `frags.next()` calls returns the namespace. match frags.next() { Some("when") => when( state.namespace(arg0?)?.unwrap(), thunk(move|output: &mut O|{state.interpret(output, &arg1)}) ).draw(output), Some("either") => either( state.namespace(arg0?)?.unwrap(), thunk(move|output: &mut O|{state.interpret(output, &arg1)}), thunk(move|output: &mut O|{state.interpret(output, &arg2)}), ).draw(output), // Second `frags.next()` calls returns the namespace member. Some("bsp") => eval_enum!("bsp", output, state, frags.next(), arg0, Split { "n" => North, "s" => South, "e" => East, "w" => West, "a" => Above, "b" => Below }).half( thunk(move|output: &mut O|{state.interpret(output, &arg0)}), thunk(move|output: &mut O|{state.interpret(output, &arg1)}), ).draw(output), Some("align") => { let content = thunk(move|output: &mut O|{state.interpret(output, &arg0)}); content.align(eval_enum!("align", output, state, frags.next(), arg0, Azimuth { "c" => C, "n" => N, "s" => S, "e" => E, "w" => W, "x" => X, "y" => Y })).draw(output) }, Some("exact") => eval_xy!( "exact", expr, head, output, state, frags.next(), arg0, arg1, arg2, wh_exact, w_exact, h_exact ), Some("min") => eval_xy!( "min", expr, head, output, state, frags.next(), arg0, arg1, arg2, wh_min, w_min, h_min ), Some("max") => eval_xy!( "max", expr, head, output, state, frags.next(), arg0, arg1, arg2, wh_max, w_max, h_max ), Some("push") => eval_xy!( "push", expr, head, output, state, frags.next(), arg0, arg1, arg2, xy_push, x_push, y_push ), _ => return Ok(None) } } /// Interpret TUI-specific layout operation. /// /// ``` /// use tengri::{*, lang::*, ratatui::prelude::Color}; /// /// #[namespace(bool {})] /// #[namespace(u8: try_to_u8)] /// #[namespace(u16: try_to_u16)] /// #[namespace(Color: try_to_color)] /// #[interpret(Tui -> Option>: try_eval_tui)] /// struct State; /// tengri::lang::primitive!(u8: try_to_u8); /// tengri::lang::primitive!(u16: try_to_u16); /// tengri::lang::namespace!(State: bool { /// }); /// tengri::lang::namespace!(State: u8 { /// literal = |x|try_to_u8(x); /// }); /// tengri::lang::namespace!(State: u16 { /// literal = |x|try_to_u16(x); /// }); /// tengri::lang::namespace!(State: Color { /// expression = |_state| { /// "g" (x: u8) => { ItemTheme::G[x as usize].base.term } /// }; /// }); /// tengri::lang::interpret!(|self: State, context: Tui, lang|->Option>{ /// expression = { /// "text" (...rest) => { todo!() } /// } /// }); /// impl Interpret>> for State { /// fn interpret_expr <'a> (&'a self, _: &mut Tui, lang: &'a impl Expression) /// -> Usually>> /// { /// Ok(None) /// } /// } /// /// # fn main () -> tengri::Usually<()> { /// let state = State; /// let mut out = Tui::new(80, 25); /// eval_view_tui(&state, &mut out, "")?; /// eval_view_tui(&state, &mut out, "text Hello world!")?; /// eval_view_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?; /// eval_view_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?; /// eval_view_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?; /// # Ok(()) } /// ``` pub fn eval_view_tui <'a, S> ( state: &S, output: &mut Tui, expr: impl Expression + 'a ) -> Perhaps> where S: Interpret>> + for<'b>Namespace<'b, bool> + for<'b>Namespace<'b, u16> + for<'b>Namespace<'b, Color> { // See `tengri::eval_view` let head = expr.head()?; let mut frags = head.src()?.unwrap_or_default().split("/"); let args = expr.tail(); let arg0 = args.head(); let tail0 = args.tail(); let arg1 = tail0.head(); match frags.next() { Some("text") => { if let Some(src) = args?.src()? { output.show(src) } else { return Ok(None) } }, Some("fg") => { let arg0 = arg0?.expect("fg: expected arg 0 (color)"); if let Some(color) = Namespace::namespace(state, arg0)? { output.show(fg(color, thunk(move|output: &mut Tui|{ state.interpret(output, &arg1)?; // FIXME?: don't max out the used area? Ok(Some(output.area().into())) }))) } else { return Err(format!("fg: {arg0:?}: not a color").into()) } }, Some("bg") => { let arg0 = arg0?.expect("bg: expected arg 0 (color)"); if let Some(color) = Namespace::namespace(state, arg0)? { output.show(bg(color, thunk(move|output: &mut Tui|{ state.interpret(output, &arg1)?; // FIXME?: don't max out the used area? Ok(Some(output.area().into())) }))) } else { return Err(format!("bg: {arg0:?}: not a color").into()) } }, _ => return Ok(None) } }