modularize eval
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
facile pop culture reference 2026-08-02 15:18:56 +03:00
parent 64c83b5460
commit eb028c85fc
12 changed files with 322 additions and 216 deletions

View file

@ -26,28 +26,41 @@ tui_view!(self: State {
let widget = draw(move|to: &mut Tui|self.interpret(to, &src)); // FIXME this forcefills
let sidebar = bg(Color::Rgb(20, 20, 20), north(code, title)).max_w(40);
let content = bg(Color::Rgb(64, 64, 64), widget);
east(sidebar, content)
east(sidebar, ShowSizeOf(content))
//self.size.of(bg(Color::Rgb(10, 10, 10), south(east(sidebar, content), code)).full_w())
//self.size.of(bg(Color::Rgb(10, 10, 10), east(sidebar, content)).full_w())
});
impl_keywords!(Tui, XYWH<u16>, State [
kw_when,
kw_either,
kw_split,
kw_align,
kw_exact,
kw_fixed,
kw_min,
kw_max,
kw_push,
kw_tui_text,
kw_tui_fg,
kw_tui_bg
]);
impl Interpret<Tui, Option<XYWH<u16>>> for State {
fn interpret_word (&self, to: &mut Tui, sym: &impl Language) -> Perhaps<XYWH<u16>> {
match sym.src()? {
Some(":foo") => "foo".draw(to),
Some(":bar") => "bar".draw(to),
Some(":foobar") => "FOOBAR".draw(to),
_ => todo!()
src => todo!("src: {src:?}")
}
}
fn interpret_expr (&self, to: &mut Tui, src: &impl Expression) -> Perhaps<XYWH<u16>> {
Ok(Some(if let Some(area) = eval_view(self, to, src)? {
area
} else if let Some(area) = Tui::eval_view(self, to, src)? {
area
if let Some(area) = self.keyword(to, src)? {
Ok(Some(area))
} else {
return Err(format!("App::interpret_expr: unexpected: {src:?}").into())
}))
Err(format!("App::interpret_expr: unexpected: {src:?}").into())
}
}
}
@ -89,7 +102,7 @@ const VIEWS: &'static [&'static str] = &[
stringify! { :foobar },
stringify! { (text FOOBAR) },
stringify! { (bg (g 8) :foobar) },
stringify! { (fill/xy :foobar) },
//stringify! { (fill/xy :foobar) },
stringify! { (bsp/s (text foo) (text bar)) },
stringify! { (bsp/s :foo :bar) },
stringify! { (bsp/s (bg (g 16) :foo) (bg (g 32) :bar)) },

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*, layout::*};
use crate::*;
use Azimuth::*;
pub struct Align<T>(

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*, layout::*};
use crate::*;
pub struct Origin<T>(
pub(crate) Option<Azimuth>,

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*};
use crate::*;
/// Define inner drawing area.
///

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*};
use crate::*;
/// Move content in the negative direction of one or both axes.
///

View file

@ -1,4 +1,4 @@
use crate::{*, draw::*};
use crate::*;
/// Move content in the positive direction of one or both axes.
///

View file

@ -41,6 +41,33 @@ macro_rules! features {
}
}
macro_rules! fn_kw_layout {
($name:ident |$state:ident, $output: ident, $expr:ident| $body:block) => {
pub fn $name <O: Screen, S> (
$state: &S, $output: &mut O, $expr: &str
) -> 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>>
$body
}
}
macro_rules! fn_kw_layout_tui {
($name:ident |$state:ident, $output: ident, $expr:ident| $body:block) => {
pub fn $name <S> (
$state: &S, $output: &mut Tui, $expr: &str
) -> Perhaps<XYWH<u16>> where
S: Interpret<Tui, Option<XYWH<u16>>>
+ for<'b> Namespace<'b, bool>
+ for<'b> Namespace<'b, u16>
+ for<'b> Namespace<'b, Option<u16>>
+ for<'b> Namespace<'b, Color>
$body
}
}
#[cfg(feature = "lang")] pub use ::dizzle::{Usually, Perhaps};
#[cfg(feature = "lang")] use ::dizzle::*;
@ -168,7 +195,7 @@ pub trait AsMutOpt<T> { fn as_mut_opt (&mut self) -> Option<&mut T>; }
/// 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 {
$name:literal, $output:ident, $state:ident, $value:expr, $arg0:expr, $Enum:ident {
$($v:literal => $V:ident),* $(,)?
}
) => {{
@ -187,7 +214,7 @@ macro_rules! eval_xy (
// (fill/xy ...)
(
$name:expr => $expr:expr, $head:expr, $output:ident, $state:ident,
$variant:expr, $xy:ident, $x:ident, $y:ident, $arg: ident,
$variant:expr, $xy:ident, $x:ident, $y:ident, $arg:expr,
) => {{
// frags.next(): 2nd slash-delimited fragment: /x, /y, /xy
let variant = $variant;
@ -210,7 +237,7 @@ macro_rules! eval_xy (
// (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,
$variant:expr, $xy:ident, $x:ident, $y:ident, $arg0:expr, $arg1:expr, $arg2:expr,
) => {{
// frags.next(): 2nd slash-delimited fragment: /x, /y, /xy
let variant = $variant;
@ -483,7 +510,7 @@ macro_rules! eval_xy (
///
/// ```
/// # use tengri::*;
/// let _ = draw(|to: &mut Tui|Ok(Some(to.1)));
/// let _ = draw(|to: &mut Tui|Ok(Some(to.area()))); // draws nothing
/// ```
pub const fn draw <T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>> (
item: F
@ -1550,96 +1577,6 @@ macro_rules! eval_xy (
})
}
/// Interpret TUI-specific layout operation.
///
/// ```
/// use tengri::{*, dizzle::*, ratatui::prelude::Color};
///
/// #[namespace(bool)]
/// #[namespace(u8)]
/// #[namespace(u16)]
/// #[namespace(Color get_color)]
/// struct State;
///
/// 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 get_color (state: &State, src: impl Language) -> Perhaps<Color> {
/// Ok(if let Some(color) = Tui::eval_color_expr(state, &src)? {
/// Some(color)
/// } else if let Some(sym) = src.word()? {
/// Some(match sym {
/// ":color/bg" => Color::Rgb(28, 32, 36),
/// ":color/fg" => Color::Rgb(98, 92, 96),
/// _ => return Err(format!("not a color: {sym}").into())
/// })
/// } else {
/// return Err(format!("not a color: {:?}", src.src()?).into())
/// })
/// }
///
/// # fn main () -> tengri::Usually<()> {
/// let state = State;
/// let mut out = Tui::new(80, 25);
/// Tui::eval_view(&state, &mut out, "")?;
/// Tui::eval_view(&state, &mut out, "text Hello world!")?;
/// Tui::eval_view(&state, &mut out, "fg (g 0) (text Hello world!)")?;
/// Tui::eval_view(&state, &mut out, "bg (g 2) (text Hello world!)")?;
/// Tui::eval_view(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?;
/// # Ok(()) }
/// ```
pub fn eval_view <'a, S> (
state: &S, to: &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()? {
src.draw(to)
} else {
return Ok(None)
}
},
Some("fg") => {
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
if let Some(color) = Namespace::namespace(state, arg0)? {
fg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(to)
} 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)? {
bg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(to)
} else {
return Err(format!("bg: {arg0:?}: not a color").into())
}
},
_ => return Ok(None)
}
}
pub fn eval_color_expr (state: &impl for<'a> Namespace<'a, u8>, src: impl Language)
-> Perhaps<Color>
{
@ -1971,115 +1908,271 @@ macro_rules! eval_xy (
#[cfg(feature = "eval")] mod eval {
use crate::*;
/// Interpret layout operation.
///
/// ```
/// # use tengri::{*, dizzle::*};
/// # use ::tengri::{*, dizzle::*, ratatui::prelude::Color};
///
/// 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 {}
/// #[namespace(bool)]
/// #[namespace(u8)]
/// #[namespace(u16)]
/// #[namespace(Option<u16>)]
/// #[namespace(Color get_color)]
/// struct App;
///
/// fn get_color (state: &App, src: impl Language) -> Perhaps<Color> {
/// Ok(if let Some(color) = Tui::eval_color_expr(state, &src)? {
/// Some(color)
/// } else if let Some(sym) = src.word()? {
/// Some(match sym {
/// ":color/bg" => Color::Rgb(28, 32, 36),
/// ":color/fg" => Color::Rgb(98, 92, 96),
/// _ => return Err(format!("not a color: {sym}").into())
/// })
/// } else {
/// return Err(format!("not a color: {:?}", src.src()?).into())
/// })
/// }
///
/// impl Interpret<Tui, Option<XYWH<u16>>> for App {
/// fn interpret_expr <'a> (&'a self, _: &mut Tui, lang: &'a impl Expression)
/// -> Usually<Option<XYWH<u16>>>
/// {
/// Ok(None)
/// }
/// }
///
/// impl Keywords<Tui, XYWH<u16>> for App {
/// fn keywords () -> impl Iterator<Item = fn(&Self, &mut Tui, &str) -> Perhaps<XYWH<u16>>> {
/// [
/// kw_when, kw_either, kw_split, kw_align,
/// kw_exact, kw_fixed, kw_min, kw_max, kw_push,
/// kw_tui_text, kw_tui_fg, kw_tui_bg
/// ].into_iter()
/// }
/// }
///
/// # 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
/// let state = App;
/// let mut out = Tui::new(80, 25);
/// Tui::eval_view(&state, &mut out, "")?;
/// Tui::eval_view(&state, &mut out, "text Hello world!")?;
/// Tui::eval_view(&state, &mut out, "fg (g 0) (text Hello world!)")?;
/// Tui::eval_view(&state, &mut out, "bg (g 2) (text Hello world!)")?;
/// Tui::eval_view(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?;
/// # 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("/");
pub trait Keywords <U, V>: 'static {
fn keywords () -> impl Iterator<Item = fn(&Self, &mut U, &str) -> Perhaps<V>>;
fn keyword (&self, to: &mut U, expr: &impl Expression) -> Perhaps<V> {
if let Some(expr) = expr.src()? {
for keyword in Self::keywords() {
if let Some(result) = keyword(self, to, &expr)? {
return Ok(Some(result))
}
}
}
Ok(None)
}
}
// 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.
#[macro_export] macro_rules! impl_keywords {
($T:ty, $U:ty, $V:ty [ $($kw:ident),* ]) => {
impl Keywords<$T, $U> for $V {
fn keywords () -> impl Iterator<Item = fn(&Self, &mut $T, &str) -> Perhaps<$U>> {
[ $($kw),* ].into_iter()
}
}
}
}
fn_kw_layout!(kw_when |state, output, expr| {
Ok(matches!(expr.head()?, Some("when")).then(||{
when(state.namespace(expr.tail().head())?.unwrap(),
draw(move|output: &mut O|{state.interpret(output, &expr.tail().tail().head())})
).draw(output)
}).transpose()?.flatten())
});
fn_kw_layout!(kw_either |state, output, expr| {
Ok(matches!(expr.head()?, Some("either")).then(||{
either(state.namespace(expr.tail().head()?)?.unwrap(),
draw(move|output: &mut O|{
state.interpret(output, &expr.tail().tail().head()?)
}),
draw(move|output: &mut O|{
state.interpret(output, &expr.tail().tail().tail().head()?)
}),
).draw(output)
}).transpose()?.flatten())
});
fn_kw_layout!(kw_split |state, output, expr| {
let head = expr.head();
let mut frags = head.src()?.unwrap_or_default().split("/");
Ok(matches!(frags.next(), Some("bsp")).then(||{
eval_enum!("bsp", output, state, frags.next(), expr.tail().head()?, Split {
"n" => North,
"s" => South,
"e" => East,
"w" => West,
"a" => Above,
"b" => Below
}).stack(
draw(move|output: &mut O|{
state.interpret(output, &expr.tail().tail().head()?)
}),
draw(move|output: &mut O|{
state.interpret(output, &expr.tail().tail().tail().head()?)
}),
).draw(output)
}).transpose()?.flatten())
});
fn_kw_layout!(kw_align |state, output, expr| {
Ok(matches!(expr.head()?, Some("align")).then(||{
draw(move|output: &mut O|{state.interpret(output, &expr.tail().head())}).align(
eval_enum!("align", output, state,
expr.head().src()?.unwrap_or_default().split("/").skip(1).next(),
expr.tail().head(),
Azimuth {
"c" => C, "x" => X, "y" => Y,
"n" => N, "s" => S, "e" => E, "w" => W,
"nw" => NW, "sw" => SW, "ne" => NE, "se" => SE,
})
).draw(output)
}).transpose()?.flatten())
});
fn_kw_layout!(kw_exact |state, output, expr| {
Ok(matches!(expr.head()?, Some("exact")).then(||{
let head = expr.head()?;
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() {
let mut frags = head.src()?.unwrap_or_default().split("/").skip(1);
eval_xy!("exact" => expr, head, output, state, frags.next(), exact_wh, exact_w, exact_h,
arg0, arg1, arg2,)
}).transpose()?.flatten())
});
Some("when") => when(
state.namespace(arg0?)?.unwrap(),
draw(move|output: &mut O|{state.interpret(output, &arg1)})
).draw(output),
fn_kw_layout!(kw_fixed |state, output, expr| {
Ok(matches!(expr.head()?, Some("exact")).then(||{
let head = expr.head()?;
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
let mut frags = head.src()?.unwrap_or_default().split("/").skip(1);
eval_xy!("fixed" => expr, head, output, state, frags.next(), exact_wh, exact_w, exact_h,
arg0, arg1, arg2,)
}).transpose()?.flatten())
});
Some("either") => either(
state.namespace(arg0?)?.unwrap(),
draw(move|output: &mut O|{state.interpret(output, &arg1)}),
draw(move|output: &mut O|{state.interpret(output, &arg2)}),
).draw(output),
fn_kw_layout!(kw_min |state, output, expr| {
Ok(matches!(expr.head()?, Some("exact")).then(||{
let head = expr.head()?;
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
let mut frags = head.src()?.unwrap_or_default().split("/").skip(1);
eval_xy!("min" => expr, head, output, state, frags.next(), min_wh, min_w, min_h,
arg0, arg1, arg2,)
}).transpose()?.flatten())
});
Some("bsp") => eval_enum!("bsp", output, state, frags.next(), arg0, Split {
"n" => North, "s" => South, "e" => East, "w" => West, "a" => Above, "b" => Below
}).stack(
draw(move|output: &mut O|{state.interpret(output, &arg0)}),
draw(move|output: &mut O|{state.interpret(output, &arg1)}),
).draw(output),
fn_kw_layout!(kw_max |state, output, expr| {
Ok(matches!(expr.head()?, Some("exact")).then(||{
let head = expr.head()?;
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
let mut frags = head.src()?.unwrap_or_default().split("/").skip(1);
eval_xy!("max" => expr, head, output, state, frags.next(), max_wh, max_w, max_h,
arg0, arg1, arg2,)
}).transpose()?.flatten())
});
Some("align") => draw(move|output: &mut O|{state.interpret(output, &arg0)})
.align(eval_enum!("align", output, state, frags.next(), arg0, Azimuth {
"c" => C, "x" => X, "y" => Y,
"n" => N, "s" => S, "e" => E, "w" => W,
"nw" => NW, "sw" => SW, "ne" => NE, "se" => SE,
})).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)
fn_kw_layout!(kw_push |state, output, expr| {
Ok(matches!(expr.head()?, Some("push")).then(||{
let head = expr.head()?;
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let tail1 = tail0.tail();
let arg2 = tail1.head();
let mut frags = head.src()?.unwrap_or_default().split("/").skip(1);
eval_xy!("push" => expr, head, output, state, frags.next(), push_xy, push_x, push_y,
arg0, arg1, arg2,)
}).transpose()?.flatten())
});
#[cfg(feature = "term")] fn_kw_layout_tui!(kw_tui_text |state, output, expr| {
Ok(matches!(expr.head()?, Some("text")).then(||{
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
if let Some(src) = args?.src()? {
src.draw(output)
} else {
return Ok(None)
}
}
}).transpose()?.flatten())
});
fn invalid_variant <T> (
#[cfg(feature = "term")] fn_kw_layout_tui!(kw_tui_fg |state, output, expr| {
Ok(matches!(expr.head()?, Some("fg")).then(||{
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
if let Some(color) = Namespace::namespace(
state, arg0?.expect("fg: expected arg 0 (color)")
)? {
fg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(output)
} else {
return Err(format!("fg: {arg0:?}: not a color").into())
}
}).transpose()?.flatten())
});
#[cfg(feature = "term")] fn_kw_layout_tui!(kw_tui_bg |state, output, expr| {
Ok(matches!(expr.head()?, Some("bg")).then(||{
let head = expr.head()?;
let args = expr.tail();
let arg0 = args.head();
let tail0 = args.tail();
let arg1 = tail0.head();
let mut frags = head.src()?.unwrap_or_default().split("/");
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
if let Some(color) = Namespace::namespace(state, arg0)? {
bg(color, draw(move|to: &mut Tui|state.interpret(to, &arg1))).draw(output)
} else {
return Err(format!("bg: {arg0:?}: not a color").into())
}
}).transpose()?.flatten())
});
}
pub fn invalid_variant <T> (
name: &str,
frag: impl Language,
expr: impl Language,
head: impl Language,
) -> Usually<T> {
) -> Usually<T> {
unimplemented!(
"{name}/{frag:?} ({expr:?}) ({head:?}) ({:?})",
head.src()?.unwrap_or_default().split("/").next()
)
}
}

View file

@ -1,4 +1,4 @@
use super::{*, draw::*};
use crate::*;
/// Draw border around item shrunk by 1 on each side.
///

View file

@ -1,4 +1,4 @@
use super::{*, draw::*, Color::*};
use crate::{*, Color::*};
/// ```
/// let _ = tengri::button_2("", "", true);

View file

@ -1,4 +1,4 @@
use super::{*, draw::*, Color::*};
use crate::*;
/// Stackably padded.
///
@ -18,10 +18,10 @@ pub const HI: &'static str = "▀";
/// A phat line
fn phat_lo (fg: Color, bg: Color) -> impl Draw<Tui> {
fg_bg(fg, bg, x_repeat(self::phat::LO)).exact_h(1)
fg_bg(fg, bg, x_repeat(LO)).exact_h(1)
}
/// A phat line
fn phat_hi (fg: Color, bg: Color) -> impl Draw<Tui> {
fg_bg(fg, bg, x_repeat(self::phat::HI)).exact_h(1)
fg_bg(fg, bg, x_repeat(HI)).exact_h(1)
}

View file

@ -1,4 +1,4 @@
use super::{*, draw::*};
use crate::*;
use ratatui::{prelude::{Position}};
pub const fn x_repeat (c: &str) -> impl Draw<Tui> {

View file

@ -1,4 +1,4 @@
use super::{*, draw::*, Color::*};
use crate::{*, Color::*};
use ratatui::{prelude::{Position}};
pub const ICON_DEC_V: &[char] = &['▲'];