use crate::{*, lang::*}; use ratatui::style::Color; /// 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) } }}); /// 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 ( // Valueless variant: // (fill/x ...) // (fill/y ...) // (fill/xy ...) ( $name:expr => $expr:expr, $head:expr, $output:ident, $state:ident, $variant:expr, $xy:ident, $x:ident, $y:ident, $arg: ident, ) => {{ // frags.next(): 2nd slash-delimited fragment: /x, /y, /xy let variant = $variant; let thunk = thunk(move|screen|$state.interpret(screen, &$arg)); match variant { // X variant Some("x") => thunk.$x().draw($output), // Y variant Some("y") => thunk.$y().draw($output), // XY variant (can be omitted) Some("xy") | None => thunk.$xy().draw($output), // Other namespace members are invalid frag => invalid_variant($name, frag, $expr, $head) } }}; // Variadic variant: // (push/x n ...) // (push/y n ...) // (push/xy n m ...) ( $name:expr => $expr:expr, $head:expr, $output:ident, $state:ident, $variant:expr, $xy:ident, $x:ident, $y:ident, $arg0: ident, $arg1: ident, $arg2: ident, ) => {{ // frags.next(): 2nd slash-delimited fragment: /x, /y, /xy let variant = $variant; let thunk = thunk(move|screen|$state.interpret(screen, &match variant { Some("x") | Some("y") => $arg1, Some("xy") | None => $arg2, _ => panic!("{}: unsupported axis {variant:?}; try /x, /y, /xy", $name) })); match variant { // X variant Some("x") => thunk.$x($state.namespace($arg0?)?) .draw($output), // Y variant Some("y") => thunk.$y($state.namespace($arg0?)?) .draw($output), // XY variant (can be omitted) Some("xy") | None => thunk.$xy($state.namespace($arg0?)?, $state.namespace($arg1?)?) .draw($output), // Other namespace members are invalid frag => invalid_variant($name, frag, $expr, $head) } }}; ); fn invalid_variant ( name: &str, frag: impl Language, expr: impl Language, head: impl Language, ) -> Usually { unimplemented!( "{name}/{frag:?} ({expr:?}) ({head:?}) ({:?})", head.src()?.unwrap_or_default().split("/").next() ) } /// 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), 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") => thunk(move|output: &mut O|{ state.interpret(output, &arg0) }).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(), exact_wh, exact_w, exact_h, arg0, arg1, arg2, ), Some("fixed") => eval_xy!( "fixed" => expr, head, output, state, frags.next(), exact_wh, exact_w, exact_h, arg0, arg1, arg2, ), Some("min") => eval_xy!( "min" => expr, head, output, state, frags.next(), min_wh, min_w, min_h, arg0, arg1, arg2, ), Some("max") => eval_xy!( "max" => expr, head, output, state, frags.next(), max_wh, max_w, max_h, arg0, arg1, arg2, ), Some("push") => eval_xy!( "push" => expr, head, output, state, frags.next(), push_xy, push_x, push_y, arg0, arg1, arg2, ), Some("fill") => eval_xy!( "fill" => expr, head, output, state, frags.next(), full_wh, full_w, full_h, arg0, ), _ => 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) } }