mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-07-17 07:56:56 +02:00
277 lines
9.9 KiB
Rust
277 lines
9.9 KiB
Rust
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 <T> (
|
|
name: &str,
|
|
frag: impl Language,
|
|
expr: impl Language,
|
|
head: impl Language,
|
|
) -> Usually<T> {
|
|
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<u16>> for State {}
|
|
/// impl Interpret<Tui, Option<XYWH<u16>>> 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<XYWH<O::Unit>> where
|
|
S: Interpret<O, Option<XYWH<O::Unit>>>
|
|
+ for<'b> Namespace<'b, bool>
|
|
+ for<'b> Namespace<'b, O::Unit>
|
|
+ for<'b> Namespace<'b, Option<O::Unit>>
|
|
{
|
|
// 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<XYWH<u16>>: 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<XYWH<u16>>{
|
|
/// expression = {
|
|
/// "text" (...rest) => { todo!() }
|
|
/// }
|
|
/// });
|
|
/// impl Interpret<Tui, Option<XYWH<u16>>> for State {
|
|
/// fn interpret_expr <'a> (&'a self, _: &mut Tui, lang: &'a impl Expression)
|
|
/// -> Usually<Option<XYWH<u16>>>
|
|
/// {
|
|
/// 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<XYWH<u16>> where
|
|
S: Interpret<Tui, Option<XYWH<u16>>>
|
|
+ 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)
|
|
|
|
}
|
|
}
|