mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-08-07 14:16:56 +02:00
big flat
This commit is contained in:
parent
94c26f06cc
commit
fc01fd6ad3
40 changed files with 2075 additions and 1807 deletions
|
|
@ -5,10 +5,11 @@ version = "0.15.0"
|
|||
description = "UI metaframework."
|
||||
|
||||
[features]
|
||||
default = ["lang", "sing", "midi", "draw", "play", "term", "text", "time", "rand", "okhsl"]
|
||||
default = ["lang", "sing", "midi", "draw", "play", "term", "text", "time", "rand", "okhsl", "eval"]
|
||||
bumpalo = ["dep:bumpalo"]
|
||||
draw = []
|
||||
gui = ["draw", "dep:winit"]
|
||||
eval = []
|
||||
lang = ["dep:dizzle"]
|
||||
midi = ["dep:midly"]
|
||||
okhsl = ["dep:palette"]
|
||||
|
|
|
|||
8
Justfile
8
Justfile
|
|
@ -29,9 +29,5 @@ doc:
|
|||
CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' RUSTDOCFLAGS='-Cinstrument-coverage' \
|
||||
cargo doc
|
||||
|
||||
mode-00:
|
||||
cargo run --example mode_00
|
||||
mode-01:
|
||||
cargo run --example mode_01
|
||||
mode-02:
|
||||
cargo run --example mode_02
|
||||
mode MODE:
|
||||
cargo run --example "mode_{{MODE}}"
|
||||
|
|
|
|||
2
dizzle
2
dizzle
|
|
@ -1 +1 @@
|
|||
Subproject commit 0f06571f7fc7e5f87aadb82d531726d24bf769e8
|
||||
Subproject commit e768064b3002dbf1aeaa143a45d54030305b4beb
|
||||
|
|
@ -1,21 +1,22 @@
|
|||
//! Mode 00: Direct draw, direct control
|
||||
|
||||
use ::std::sync::{Arc, RwLock};
|
||||
use ::crossterm::event::{Event::*, KeyEvent, KeyCode::*};
|
||||
use ::ratatui::style::Color;
|
||||
use ::tengri::{*, lang::*};
|
||||
use ::tengri::{
|
||||
*,
|
||||
lang::*,
|
||||
crossterm::event::{KeyEvent, Event::*, KeyCode::*},
|
||||
ratatui::style::Color,
|
||||
};
|
||||
|
||||
tui_app!(State {
|
||||
/** User-controllable value. */
|
||||
cursor: usize,
|
||||
counter: usize,
|
||||
});
|
||||
|
||||
tui_view!(self: State {
|
||||
thunk(|to: &mut Tui|{
|
||||
let cursor = format!("Cursor: {}", self.cursor);
|
||||
let _ = "DEMO [MODE 00]".align_sw().draw(to);
|
||||
let _ = ShowSize.align_se().draw(to);
|
||||
let _ = format!("Cursor: {}", self.cursor).align_c().draw(to);
|
||||
let _ = format!("Counter:\n{}", self.counter).align_c().draw(to);
|
||||
Ok(Some(to.area()))
|
||||
})
|
||||
});
|
||||
|
|
@ -24,11 +25,11 @@ tui_keys!(self: State, input {
|
|||
Ok(if let Key(KeyEvent { code, .. }) = input.0 {
|
||||
match code {
|
||||
Up | Right => {
|
||||
self.cursor = (self.cursor + 1) % 10;
|
||||
self.counter = (self.counter + 1) % 10;
|
||||
()
|
||||
},
|
||||
Down | Left => {
|
||||
self.cursor = if self.cursor > 0 { self.cursor - 1 } else { 10 - 1 };
|
||||
self.counter = if self.counter > 0 { self.counter - 1 } else { 10 - 1 };
|
||||
()
|
||||
},
|
||||
_ => {}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
//! Mode 01: Direct view, actions with history
|
||||
|
||||
use ::std::sync::{Arc, RwLock};
|
||||
use ::crossterm::event::{Event::*, KeyEvent, KeyCode::*};
|
||||
use ::ratatui::style::Color;
|
||||
use ::tengri::{*, lang::*};
|
||||
use itertools::Itertools;
|
||||
use ::tengri::{
|
||||
*,
|
||||
lang::*,
|
||||
crossterm::event::{Event::*, KeyEvent, KeyCode::*},
|
||||
ratatui::style::Color,
|
||||
};
|
||||
|
||||
tui_app!(State {
|
||||
/** Command history (undo/redo). */
|
||||
|
|
@ -29,7 +31,7 @@ tui_view!(self: State {
|
|||
let title = "Demo Mode 00";
|
||||
let items = self.history.iter().take(10).map(|x|format!("{x:?}")).join("\n");
|
||||
let history = format!("History: {}\n{items}", self.history.len());
|
||||
let cursor = format!("Cursor: {}", self.cursor);
|
||||
let cursor = format!("Counter: {}", self.cursor);
|
||||
north(
|
||||
east(title.align_sw(), ShowSize.align_se()),
|
||||
east(history.align_c(), cursor.align_c()),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
//! Mode 02: Inline Dizzle config
|
||||
|
||||
use ::std::sync::{Arc, RwLock};
|
||||
use ::crossterm::event::{Event::*, KeyEvent, KeyCode::*};
|
||||
use ::ratatui::style::Color;
|
||||
use ::tengri::{*, lang::*};
|
||||
use ::tengri::{
|
||||
*,
|
||||
lang::*,
|
||||
crossterm::event::{Event::*, KeyEvent, KeyCode::*},
|
||||
ratatui::style::Color,
|
||||
};
|
||||
|
||||
tui_app!(State {
|
||||
/** Command history (undo/redo). */
|
||||
history: Vec<Action>,
|
||||
|
|
@ -12,47 +15,18 @@ tui_app!(State {
|
|||
/** Rendered window size. */
|
||||
size: Sizer,
|
||||
});
|
||||
tui_keys!(self: State, input {
|
||||
Ok(if let Key(KeyEvent { code, .. }) = input.0 {
|
||||
match code {
|
||||
Up | Right => { self.next()?.map(|x|self.history.push(x)); },
|
||||
Down | Left => { self.prev()?.map(|x|self.history.push(x)); },
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
tui_view!(self: State {
|
||||
let index = self.cursor + 1;
|
||||
let wh = (self.size.w(), self.size.h());
|
||||
let src = VIEWS.get(self.cursor).unwrap_or(&"");
|
||||
let heading = format!("State {}/{} in {:?}", index, VIEWS.len(), &wh);
|
||||
let title = bg(Color::Rgb(60, 10, 10), heading.align_n().push_y(1));
|
||||
let code = bg(Color::Rgb(10, 60, 10), format!("{}", src).align_n().push_y(2));
|
||||
let title = bg(Color::Rgb(60, 10, 10), heading.align_n().push_xy(2, 1)).exact_h(3);
|
||||
let code = bg(Color::Rgb(10, 60, 10), format!("Source:\n{}", src).align_n().push_xy(2, 1)).min_h(4);
|
||||
let widget = thunk(move|to: &mut Tui|self.interpret(to, &src));
|
||||
self.size.of(south(title, north(code, widget)))
|
||||
self.size.of(east(south(title, code).max_w(40), widget))
|
||||
});
|
||||
impl Interpret<Tui, Color> for State {
|
||||
fn interpret_expr (&self, to: &mut Tui, expr: &impl Language) -> Usually<Color> {
|
||||
let expr = expr.expr()?;
|
||||
match expr.head()? {
|
||||
Some("g") if let Some(tail) = expr.tail()? => {
|
||||
Color::new_g(tail.head()?, try_to_u8)
|
||||
},
|
||||
Some("rgb") if let Some(tail) = expr.tail()? => {
|
||||
Color::new_rgb(tail.head()?, try_to_u8)
|
||||
},
|
||||
_ => Err(format!("not a color").into())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn try_to_u8 (src: Perhaps<&str>) -> Perhaps<u8> {
|
||||
use std::str::FromStr;
|
||||
if let Some(src) = src? {
|
||||
Ok(Some(u8::from_str(src)?))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl Interpret<Tui, Option<XYWH<u16>>> for State {
|
||||
fn interpret_word (&self, to: &mut Tui, sym: &impl Language) -> Perhaps<XYWH<u16>> {
|
||||
match sym.src()? {
|
||||
|
|
@ -65,13 +39,24 @@ impl Interpret<Tui, Option<XYWH<u16>>> for State {
|
|||
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) = eval_view_tui(self, to, src)? {
|
||||
} else if let Some(area) = Tui::eval_view(self, to, src)? {
|
||||
area
|
||||
} else {
|
||||
return Err(format!("App::interpret_expr: unexpected: {src:?}").into())
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
tui_keys!(self: State, input {
|
||||
Ok(if let Key(KeyEvent { code, .. }) = input.0 {
|
||||
match code {
|
||||
Up | Right => { self.next()?.map(|x|self.history.push(x)); },
|
||||
Down | Left => { self.prev()?.map(|x|self.history.push(x)); },
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
impl State {
|
||||
fn next (&mut self) -> Perhaps<Action> {
|
||||
self.cursor = (self.cursor + 1) % VIEWS.len();
|
||||
|
|
@ -82,22 +67,30 @@ impl State {
|
|||
Ok(Some(Action::Next))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Action {
|
||||
/** Increment cursor */ Next,
|
||||
/** Decrement cursor */ Prev,
|
||||
}
|
||||
|
||||
impl Action {
|
||||
fn eval (&self, state: &mut State) -> Perhaps<Self> {
|
||||
use Action::*;
|
||||
match self { Next => state.next(), Prev => state.prev(), }
|
||||
}
|
||||
}
|
||||
|
||||
const VIEWS: &'static [&'static str] = &[
|
||||
stringify! { :foobar },
|
||||
stringify! { (text FOOBAR) },
|
||||
stringify! { (bg (g 8) :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)) },
|
||||
stringify! { (bsp/s (max/xy 20 10 (bg (g 16) :foo)) (max/y 5 (bg (g 32) :bar))) },
|
||||
stringify! { (bsp/e (bg (g 16) :foo) (bg (g 32) :bar)) },
|
||||
stringify! { (fixed/xy 20 10 :foobar) },
|
||||
stringify! { (bsp/s (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) },
|
||||
stringify! { (bsp/e (fixed/xy 5 6 :foo) (fixed/xy 7 8 :bar)) },
|
||||
|
|
@ -155,6 +148,7 @@ const VIEWS: &'static [&'static str] = &[
|
|||
(bg/behind :bg3 (border/around :border3 (margin/xy 6 3 :label3)))))))
|
||||
},
|
||||
];
|
||||
|
||||
//handle!(TuiIn: |self: State, input|Action::from(input).eval(self).map(|_|None));
|
||||
//view!(State: Tui: [ evaluate_output_expression, evaluate_output_expression_tui ]);
|
||||
//draw!(State: Tui: [ draw_example ]);
|
||||
|
|
|
|||
0
src/color.rs
Normal file
0
src/color.rs
Normal file
40
src/coord.rs
Normal file
40
src/coord.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use super::*;
|
||||
|
||||
/// A numeric type that can be used as coordinate.
|
||||
///
|
||||
/// FIXME: Replace with `num` crate?
|
||||
/// FIXME: Use AsRef/AsMut?
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::*;
|
||||
/// let a: u16 = Coord::zero();
|
||||
/// let b: u16 = a.plus(1);
|
||||
/// let c: u16 = a.minus(2);
|
||||
/// let d = a.atomic();
|
||||
/// ```
|
||||
pub trait Coord: Send + Sync + Copy
|
||||
+ Add<Self, Output=Self>
|
||||
+ Sub<Self, Output=Self>
|
||||
+ Mul<Self, Output=Self>
|
||||
+ Div<Self, Output=Self>
|
||||
+ Ord + PartialEq + Eq
|
||||
+ Debug + Display + Default
|
||||
+ From<u16> + Into<u16>
|
||||
+ Into<usize>
|
||||
+ Into<f64>
|
||||
//+ std::iter::Step
|
||||
{
|
||||
/// Zero in own type.
|
||||
fn zero () -> Self { 0.into() }
|
||||
/// Addition.
|
||||
fn plus (self, other: Self) -> Self;
|
||||
/// Saturating subtraction.
|
||||
fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } }
|
||||
/// Convert to [AtomicUsize].
|
||||
fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) }
|
||||
}
|
||||
|
||||
/// TUI works in u16 coordinates.
|
||||
impl Coord for u16 {
|
||||
fn plus (self, other: Self) -> Self { self.saturating_add(other) }
|
||||
}
|
||||
161
src/draw.rs
161
src/draw.rs
|
|
@ -1,161 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// Output target.
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::*;
|
||||
/// struct TestOut { w: u16, h: u16 };
|
||||
/// impl Wide<u16> for TestOut {}
|
||||
/// impl Tall<u16> for TestOut {}
|
||||
/// impl Xy<u16> for TestOut {
|
||||
/// fn x (&self) -> u16 { 0 }
|
||||
/// fn y (&self) -> u16 { 0 }
|
||||
/// }
|
||||
/// impl Screen for TestOut {
|
||||
/// type Unit = u16;
|
||||
/// fn show (&mut self, _: impl Draw<Self>) -> Perhaps<XYWH<u16>> {
|
||||
/// println!("placed");
|
||||
/// Ok(None)
|
||||
/// }
|
||||
/// fn area (&self) -> XYWH<Self::Unit> {
|
||||
/// Default::default()
|
||||
/// }
|
||||
/// fn clip <T> (
|
||||
/// &mut self,
|
||||
/// area: impl Into<Option<XYWH<u16>>>,
|
||||
/// draw: impl FnOnce(&mut Self)->T
|
||||
/// ) -> T {
|
||||
/// draw(self)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl_draw!(|self: String, to: TestOut|{
|
||||
/// to.w = self.len() as u16;
|
||||
/// Ok(None)
|
||||
/// });
|
||||
/// ```
|
||||
pub trait Screen: Xy<Self::Unit> + Wh<Self::Unit> + Send + Sync + Sized {
|
||||
type Unit: Coord;
|
||||
/// Render drawable in subarea specified by `area`
|
||||
fn show (&mut self, content: impl Draw<Self>) -> Perhaps<XYWH<Self::Unit>>;
|
||||
/// Get current clipping area
|
||||
fn area (&self) -> XYWH<Self::Unit>;
|
||||
/// Set clipping area
|
||||
fn clip <T> (
|
||||
&mut self,
|
||||
area: impl Into<Option<XYWH<Self::Unit>>>,
|
||||
draw: impl FnOnce(&mut Self)->T
|
||||
) -> T;
|
||||
}
|
||||
|
||||
/// Implement the [Draw] trait for a particular drawable and [Screen].
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::*;
|
||||
/// struct MyDrawable;
|
||||
/// impl_draw!(|self: MyDrawable, to: Tui|{
|
||||
/// todo!("your draw logic")
|
||||
/// });
|
||||
/// ```
|
||||
#[macro_export] macro_rules! impl_draw (
|
||||
($(<$($T:ident: $Trait:path,)+>)?|
|
||||
$self:ident:$Self:path, $to:ident:$To:ty
|
||||
|$draw:block)=>{ impl$(<$($T:$Trait),+>)? Draw<$To> for $Self {
|
||||
fn draw ($self, $to: &mut $To) -> Perhaps<XYWH<<$To as Screen>::Unit>> $draw
|
||||
} };
|
||||
($(<$($T:ident: $Trait:path,)+>)?|
|
||||
$self:ident:$Self:ty, $to:ident:$To:ty
|
||||
|$draw:block)=>{ impl$(<$($T:$Trait),+>)? Draw<$To> for $Self {
|
||||
fn draw ($self, $to: &mut $To) -> Perhaps<XYWH<<$To as Screen>::Unit>> $draw
|
||||
} }
|
||||
);
|
||||
|
||||
/// Drawable that supports dynamic dispatch.
|
||||
///
|
||||
/// Drawables are composable, e.g. the [when] and [either] conditionals
|
||||
/// or the layout constraints.
|
||||
///
|
||||
/// Drawables are consumable, i.e. the [Draw::draw] method receives an
|
||||
/// owned `self` and does not return it, consuming the drawable.
|
||||
///
|
||||
/// To draw a thing multiple times, instead of explicitly constructing it
|
||||
/// every time, implement the [View] trait instead, which will construct
|
||||
/// a [Draw]able.
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::*;
|
||||
/// struct MyWidget(bool);
|
||||
/// impl Draw<Tui> for MyWidget {
|
||||
/// fn draw (self, to: &mut Tui) -> Perhaps<XYWH<u16>> {
|
||||
/// todo!("your draw logic")
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Draw<S: Screen> {
|
||||
fn draw (self, to: &mut S) -> Drawn<S::Unit>;
|
||||
fn layout (&self, area: XYWH<S::Unit>) -> Drawn<S::Unit> {
|
||||
Ok(Some(area))
|
||||
}
|
||||
}
|
||||
|
||||
/// The opposite of [thunk]?
|
||||
pub fn draw <S: Screen, T: Draw<S>> (item: T) -> impl FnOnce(&mut S) -> Perhaps<XYWH<S::Unit>> {
|
||||
move|to: &mut S|item.draw(to)
|
||||
}
|
||||
|
||||
pub type Drawn<U> = Perhaps<XYWH<U>>;
|
||||
|
||||
impl<S: Screen> Draw<S> for () {
|
||||
fn draw (self, _: &mut S) -> Drawn<S::Unit> {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl_draw!(<S: Screen, D: Draw<S>,>|self: Option<D>, to: S|{
|
||||
self.map(|it|it.draw(to)).transpose().map(Option::unwrap_or_default)
|
||||
});
|
||||
|
||||
//impl<S: Screen, D: Draw<S>> Draw<S> for RwLock<D> {
|
||||
//fn draw (self, __: &mut S) -> Drawn<S::Unit> {
|
||||
//todo!()
|
||||
//}
|
||||
//}
|
||||
|
||||
//impl<T: Screen, D: Draw<T>> Draw<T> for Arc<D> {
|
||||
//fn draw (self, __: &mut T) -> Perhaps<XYWH<T::Unit>> {
|
||||
//todo!()
|
||||
//}
|
||||
//}
|
||||
|
||||
/// Emit a [Draw]able.
|
||||
///
|
||||
/// Speculative. How to avoid conflicts with [Draw] proper?
|
||||
pub trait View<T: Screen> {
|
||||
fn view (&self) -> impl Draw<T>;
|
||||
}
|
||||
|
||||
impl<T: Screen> View<T> for () {
|
||||
fn view (&self) -> impl Draw<T> {
|
||||
()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Screen, V: View<T>> Draw<T> for &V {
|
||||
fn draw (self, to: &mut T) -> Perhaps<XYWH<T::Unit>> {
|
||||
self.view().draw(to)
|
||||
}
|
||||
}
|
||||
|
||||
features! {
|
||||
"draw": [
|
||||
color,
|
||||
coord,
|
||||
iter,
|
||||
layout,
|
||||
lrtb,
|
||||
sizer,
|
||||
split,
|
||||
thunk,
|
||||
xywh
|
||||
]
|
||||
}
|
||||
|
|
@ -1,163 +0,0 @@
|
|||
use crate::lang::impl_from;
|
||||
use ::ratatui::style::Color;
|
||||
use ::rand::distributions::uniform::UniformSampler;
|
||||
pub(crate) use ::palette::{
|
||||
Okhsl, Srgb, OklabHue, Mix, okhsl::UniformOkhsl,
|
||||
convert::{FromColor, FromColorUnclamped}
|
||||
};
|
||||
|
||||
pub fn rgb (r: u8, g: u8, b: u8) -> ItemColor {
|
||||
let term = Color::Rgb(r, g, b);
|
||||
ItemColor { okhsl: rgb_to_okhsl(term), term }
|
||||
}
|
||||
|
||||
pub fn g (g: u8) -> Color {
|
||||
Color::Rgb(g, g, g)
|
||||
}
|
||||
|
||||
pub fn okhsl_to_rgb (color: Okhsl<f32>) -> Color {
|
||||
let Srgb { red, green, blue, .. }: Srgb<f32> = Srgb::from_color_unclamped(color);
|
||||
Color::Rgb((red * 255.0) as u8, (green * 255.0) as u8, (blue * 255.0) as u8,)
|
||||
}
|
||||
|
||||
pub fn rgb_to_okhsl (color: Color) -> Okhsl<f32> {
|
||||
if let Color::Rgb(r, g, b) = color {
|
||||
Okhsl::from_color(Srgb::new(r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0))
|
||||
} else {
|
||||
unreachable!("only Color::Rgb is supported")
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasColor { fn color (&self) -> ItemColor; }
|
||||
|
||||
#[macro_export] macro_rules! has_color {
|
||||
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => {
|
||||
impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? {
|
||||
fn color (&$self) -> ItemColor { $cb }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
pub struct ItemColor {
|
||||
pub term: Color,
|
||||
pub okhsl: Okhsl<f32>
|
||||
}
|
||||
|
||||
impl_from!(ItemColor: |term: Color| Self { term, okhsl: rgb_to_okhsl(term) });
|
||||
|
||||
impl_from!(ItemColor: |okhsl: Okhsl<f32>| Self { okhsl, term: okhsl_to_rgb(okhsl) });
|
||||
|
||||
// A single color within item theme parameters, in OKHSL and RGB representations.
|
||||
impl ItemColor {
|
||||
#[cfg(feature = "term")] pub const fn from_tui (term: Color) -> Self {
|
||||
Self { term, okhsl: Okhsl::new_const(OklabHue::new(0.0), 0.0, 0.0) }
|
||||
}
|
||||
pub fn random () -> Self {
|
||||
let mut rng = ::rand::thread_rng();
|
||||
let lo = Okhsl::new(-180.0, 0.01, 0.25);
|
||||
let hi = Okhsl::new( 180.0, 0.9, 0.5);
|
||||
UniformOkhsl::new(lo, hi).sample(&mut rng).into()
|
||||
}
|
||||
pub fn random_dark () -> Self {
|
||||
let mut rng = ::rand::thread_rng();
|
||||
let lo = Okhsl::new(-180.0, 0.025, 0.075);
|
||||
let hi = Okhsl::new( 180.0, 0.5, 0.150);
|
||||
UniformOkhsl::new(lo, hi).sample(&mut rng).into()
|
||||
}
|
||||
pub fn random_near (color: Self, distance: f32) -> Self {
|
||||
color.mix(Self::random(), distance)
|
||||
}
|
||||
pub fn mix (&self, other: Self, distance: f32) -> Self {
|
||||
if distance > 1.0 { panic!("color mixing takes distance between 0.0 and 1.0"); }
|
||||
self.okhsl.mix(other.okhsl, distance).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq)]
|
||||
pub struct ItemTheme {
|
||||
pub base: ItemColor,
|
||||
pub light: ItemColor,
|
||||
pub lighter: ItemColor,
|
||||
pub lightest: ItemColor,
|
||||
pub dark: ItemColor,
|
||||
pub darker: ItemColor,
|
||||
pub darkest: ItemColor,
|
||||
}
|
||||
|
||||
impl_from!(ItemTheme: |base: ItemColor| Self::from_item_color(base));
|
||||
|
||||
impl_from!(ItemTheme: |base: Color| Self::from_tui_color(base));
|
||||
|
||||
impl ItemTheme {
|
||||
#[cfg(feature = "term")] pub const G: [Self;256] = {
|
||||
let mut builder = dizzle::konst::array::ArrayBuilder::new();
|
||||
while !builder.is_full() {
|
||||
let index = builder.len() as u8;
|
||||
let light = (index as f64 * 1.15) as u8;
|
||||
let lighter = (index as f64 * 1.7) as u8;
|
||||
let lightest = (index as f64 * 1.85) as u8;
|
||||
let dark = (index as f64 * 0.9) as u8;
|
||||
let darker = (index as f64 * 0.6) as u8;
|
||||
let darkest = (index as f64 * 0.3) as u8;
|
||||
builder.push(ItemTheme {
|
||||
base: ItemColor::from_tui(Color::Rgb(index, index, index )),
|
||||
light: ItemColor::from_tui(Color::Rgb(light, light, light, )),
|
||||
lighter: ItemColor::from_tui(Color::Rgb(lighter, lighter, lighter, )),
|
||||
lightest: ItemColor::from_tui(Color::Rgb(lightest, lightest, lightest, )),
|
||||
dark: ItemColor::from_tui(Color::Rgb(dark, dark, dark, )),
|
||||
darker: ItemColor::from_tui(Color::Rgb(darker, darker, darker, )),
|
||||
darkest: ItemColor::from_tui(Color::Rgb(darkest, darkest, darkest, )),
|
||||
});
|
||||
}
|
||||
builder.build()
|
||||
};
|
||||
pub fn random () -> Self { ItemColor::random().into() }
|
||||
pub fn random_near (color: Self, distance: f32) -> Self {
|
||||
color.base.mix(ItemColor::random(), distance).into()
|
||||
}
|
||||
pub const G00: Self = {
|
||||
let color: ItemColor = ItemColor {
|
||||
okhsl: Okhsl { hue: OklabHue::new(0.0), lightness: 0.0, saturation: 0.0 },
|
||||
term: Color::Rgb(0, 0, 0)
|
||||
};
|
||||
Self {
|
||||
base: color,
|
||||
light: color,
|
||||
lighter: color,
|
||||
lightest: color,
|
||||
dark: color,
|
||||
darker: color,
|
||||
darkest: color,
|
||||
}
|
||||
};
|
||||
#[cfg(feature = "term")] pub fn from_tui_color (base: Color) -> Self {
|
||||
Self::from_item_color(ItemColor::from_tui(base))
|
||||
}
|
||||
pub fn from_item_color (base: ItemColor) -> Self {
|
||||
let mut light = base.okhsl;
|
||||
light.lightness = (light.lightness * 1.3).min(1.0);
|
||||
let mut lighter = light;
|
||||
lighter.lightness = (lighter.lightness * 1.3).min(1.0);
|
||||
let mut lightest = base.okhsl;
|
||||
lightest.lightness = 0.95;
|
||||
let mut dark = base.okhsl;
|
||||
dark.lightness = (dark.lightness * 0.75).max(0.0);
|
||||
dark.saturation = (dark.saturation * 0.75).max(0.0);
|
||||
let mut darker = dark;
|
||||
darker.lightness = (darker.lightness * 0.66).max(0.0);
|
||||
darker.saturation = (darker.saturation * 0.66).max(0.0);
|
||||
let mut darkest = darker;
|
||||
darkest.lightness = 0.1;
|
||||
darkest.saturation = (darkest.saturation * 0.50).max(0.0);
|
||||
Self {
|
||||
base,
|
||||
light: light.into(),
|
||||
lighter: lighter.into(),
|
||||
lightest: lightest.into(),
|
||||
dark: dark.into(),
|
||||
darker: darker.into(),
|
||||
darkest: darkest.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +1 @@
|
|||
use super::*;
|
||||
|
||||
/// A numeric type that can be used as coordinate.
|
||||
///
|
||||
/// FIXME: Replace with `num` crate?
|
||||
/// FIXME: Use AsRef/AsMut?
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::*;
|
||||
/// let a: u16 = Coord::zero();
|
||||
/// let b: u16 = a.plus(1);
|
||||
/// let c: u16 = a.minus(2);
|
||||
/// let d = a.atomic();
|
||||
/// ```
|
||||
pub trait Coord: Send + Sync + Copy
|
||||
+ Add<Self, Output=Self>
|
||||
+ Sub<Self, Output=Self>
|
||||
+ Mul<Self, Output=Self>
|
||||
+ Div<Self, Output=Self>
|
||||
+ Ord + PartialEq + Eq
|
||||
+ Debug + Display + Default
|
||||
+ From<u16> + Into<u16>
|
||||
+ Into<usize>
|
||||
+ Into<f64>
|
||||
//+ std::iter::Step
|
||||
{
|
||||
/// Zero in own type.
|
||||
fn zero () -> Self { 0.into() }
|
||||
/// Addition.
|
||||
fn plus (self, other: Self) -> Self;
|
||||
/// Saturating subtraction.
|
||||
fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } }
|
||||
/// Convert to [AtomicUsize].
|
||||
fn atomic (self) -> AtomicUsize { AtomicUsize::new(self.into()) }
|
||||
}
|
||||
|
||||
/// TUI works in u16 coordinates.
|
||||
impl Coord for u16 {
|
||||
fn plus (self, other: Self) -> Self { self.saturating_add(other) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use super::*;
|
||||
use crate::layout::*;
|
||||
|
||||
/// Iterate over a collection of the same kind of [Draw]able:
|
||||
pub fn iter <'a, S: Screen, D: 'a, I: Iterator<Item = D>, U: Draw<S>> (
|
||||
|
|
|
|||
|
|
@ -1,437 +0,0 @@
|
|||
#![allow(unused)]
|
||||
|
||||
use crate::*;
|
||||
|
||||
impl<S: Screen, T: Draw<S>> Layout<S> for T {}
|
||||
|
||||
pub trait Layout<S: Screen>: Draw<S> + Sized {
|
||||
fn full_w (self) -> impl Draw<S> {
|
||||
Full::W(self)
|
||||
}
|
||||
fn full_h (self) -> impl Draw<S> {
|
||||
Full::H(self)
|
||||
}
|
||||
fn full_wh (self) -> impl Draw<S> {
|
||||
Full::WH(self)
|
||||
}
|
||||
|
||||
/// (bsp/e (exact/w 10 "Hello") "World")
|
||||
fn exact_w <N: Into<Option<S::Unit>>> (self, x: N) -> impl Draw<S> {
|
||||
Exact::W(self, x.into())
|
||||
}
|
||||
/// (bsp/s (exact/h 10 "Hello") "World")
|
||||
fn exact_h <N: Into<Option<S::Unit>>> (self, y: N) -> impl Draw<S> {
|
||||
Exact::H(self, y.into())
|
||||
}
|
||||
/// (exact/wh 10 2 "Hello World")
|
||||
fn exact_wh <N: Into<Option<S::Unit>>> (self, x: N, y: N) -> impl Draw<S> {
|
||||
Exact::WH(self, x.into(), y.into())
|
||||
}
|
||||
|
||||
fn min_w <N: Into<Option<S::Unit>>> (self, x: N) -> impl Draw<S> {
|
||||
Min::W(self, x.into())
|
||||
}
|
||||
fn min_h <N: Into<Option<S::Unit>>> (self, y: N) -> impl Draw<S> {
|
||||
Min::H(self, y.into())
|
||||
}
|
||||
fn min_wh <N: Into<Option<S::Unit>>> (self, x: N, y: N) -> impl Draw<S> {
|
||||
Min::WH(self, x.into(), y.into())
|
||||
}
|
||||
|
||||
fn max_w <N: Into<Option<S::Unit>>> (self, x: N) -> impl Draw<S> {
|
||||
Max::W(self, x.into())
|
||||
}
|
||||
fn max_h <N: Into<Option<S::Unit>>> (self, y: N) -> impl Draw<S> {
|
||||
Max::H(self, y.into())
|
||||
}
|
||||
fn max_wh <N: Into<Option<S::Unit>>> (self, x: N, y: N) -> impl Draw<S> {
|
||||
Max::WH(self, x.into(), y.into())
|
||||
}
|
||||
|
||||
fn pad_w <N: Into<Option<S::Unit>>> (self, x: N) -> impl Draw<S> {
|
||||
Pad::W(self, x.into())
|
||||
}
|
||||
fn pad_h <N: Into<Option<S::Unit>>> (self, y: N) -> impl Draw<S> {
|
||||
Pad::H(self, y.into())
|
||||
}
|
||||
fn pad_wh <N: Into<Option<S::Unit>>> (self, x: N, y: N) -> impl Draw<S> {
|
||||
Pad::WH(self, x.into(), y.into())
|
||||
}
|
||||
|
||||
fn pull_x <N: Into<Option<S::Unit>>> (self, x: N) -> impl Draw<S> {
|
||||
Pull::X(self, x.into())
|
||||
}
|
||||
fn pull_y <N: Into<Option<S::Unit>>> (self, y: N) -> impl Draw<S> {
|
||||
Pull::Y(self, y.into())
|
||||
}
|
||||
fn pull_xy <N: Into<Option<S::Unit>>> (self, x: N, y: N) -> impl Draw<S> {
|
||||
Pull::XY(self, x.into(), y.into())
|
||||
}
|
||||
|
||||
fn push_x <N: Into<Option<S::Unit>>> (self, x: N) -> impl Draw<S> {
|
||||
Push::X(self, x.into())
|
||||
}
|
||||
fn push_y <N: Into<Option<S::Unit>>> (self, y: N) -> impl Draw<S> {
|
||||
Push::Y(self, y.into())
|
||||
}
|
||||
fn push_xy <N: Into<Option<S::Unit>>> (self, x: N, y: N) -> impl Draw<S> {
|
||||
Push::XY(self, x.into(), y.into())
|
||||
}
|
||||
|
||||
fn align (self, azimuth: impl Into<Option<Azimuth>>) -> Align<Self> {
|
||||
Align(azimuth.into(), self)
|
||||
}
|
||||
fn align_c (self) -> Align<Self> {
|
||||
Align(Some(Azimuth::C), self)
|
||||
}
|
||||
fn align_x (self) -> Align<Self> {
|
||||
Align(Some(Azimuth::X), self)
|
||||
}
|
||||
fn align_y (self) -> Align<Self> {
|
||||
Align(Some(Azimuth::Y), self)
|
||||
}
|
||||
|
||||
fn align_n (self) -> impl Draw<S> {
|
||||
Align(Some(Azimuth::N), self)
|
||||
}
|
||||
fn align_s (self) -> impl Draw<S> {
|
||||
Align(Some(Azimuth::S), self)
|
||||
}
|
||||
fn align_e (self) -> impl Draw<S> {
|
||||
Align(Some(Azimuth::E), self)
|
||||
}
|
||||
fn align_w (self) -> impl Draw<S> {
|
||||
Align(Some(Azimuth::W), self)
|
||||
}
|
||||
|
||||
fn align_ne (self) -> impl Draw<S> {
|
||||
Align(Some(Azimuth::NE), self)
|
||||
}
|
||||
fn align_se (self) -> impl Draw<S> {
|
||||
Align(Some(Azimuth::SE), self)
|
||||
}
|
||||
fn align_nw (self) -> impl Draw<S> {
|
||||
Align(Some(Azimuth::NW), self)
|
||||
}
|
||||
fn align_sw (self) -> impl Draw<S> {
|
||||
Align(Some(Azimuth::SW), self)
|
||||
}
|
||||
|
||||
fn origin (self, azimuth: impl Into<Option<Azimuth>>) -> impl Draw<S> {
|
||||
Origin(azimuth.into(), self)
|
||||
}
|
||||
fn origin_c (self) -> impl Draw<S> {
|
||||
Origin(Some(Azimuth::C), self)
|
||||
}
|
||||
fn origin_x (self) -> impl Draw<S> {
|
||||
Origin(Some(Azimuth::X), self)
|
||||
}
|
||||
fn origin_y (self) -> impl Draw<S> {
|
||||
Origin(Some(Azimuth::Y), self)
|
||||
}
|
||||
|
||||
fn origin_n (self) -> impl Draw<S> {
|
||||
Origin(Some(Azimuth::N), self)
|
||||
}
|
||||
fn origin_s (self) -> impl Draw<S> {
|
||||
Origin(Some(Azimuth::S), self)
|
||||
}
|
||||
fn origin_e (self) -> impl Draw<S> {
|
||||
Origin(Some(Azimuth::E), self)
|
||||
}
|
||||
fn origin_w (self) -> impl Draw<S> {
|
||||
Origin(Some(Azimuth::W), self)
|
||||
}
|
||||
|
||||
fn origin_ne (self) -> impl Draw<S> {
|
||||
Origin(Some(Azimuth::NE), self)
|
||||
}
|
||||
fn origin_se (self) -> impl Draw<S> {
|
||||
Origin(Some(Azimuth::SE), self)
|
||||
}
|
||||
fn origin_nw (self) -> impl Draw<S> {
|
||||
Origin(Some(Azimuth::NW), self)
|
||||
}
|
||||
fn origin_sw (self) -> impl Draw<S> {
|
||||
Origin(Some(Azimuth::SW), self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Use whole drawing area along one or both axes.
|
||||
///
|
||||
/// ```
|
||||
/// # fn doctest_layout_full () -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use tengri::{Layout, Draw, XYWH};
|
||||
/// let area = XYWH(0u16, 0, 80, 25);
|
||||
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
||||
/// assert_eq!("1".full_w().layout(area)?, Some(XYWH(0u16, 0, 80, 1)));
|
||||
/// assert_eq!("1".full_h().layout(area)?, Some(XYWH(0u16, 0, 1, 25)));
|
||||
/// assert_eq!("1".full_wh().layout(area)?, Some(XYWH(0u16, 0, 80, 25)));
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub enum Full<T: Screen, I: Draw<T>> {
|
||||
__(PhantomData<T>),
|
||||
W(I),
|
||||
H(I),
|
||||
WH(I),
|
||||
}
|
||||
impl_draw!(<T: Screen, I: Draw<T>,>|self: Full<T, I>, to: T|{
|
||||
let XYWH(x0, y0, w0, h0) = to.area();
|
||||
match self {
|
||||
Self::W(item) => if let Some(XYWH(_, y, _, h)) = item.layout(to.area())? {
|
||||
to.clip(XYWH(x0, y, w0, h), |to|item.draw(to))
|
||||
} else {
|
||||
Ok(None)
|
||||
},
|
||||
Self::H(item) => if let Some(XYWH(x, _, w, _)) = item.layout(to.area())? {
|
||||
to.clip(XYWH(x, y0, w, h0), |to|item.draw(to))
|
||||
} else {
|
||||
Ok(None)
|
||||
},
|
||||
Self::WH(item) => if let Some(XYWH(..)) = item.layout(to.area())? {
|
||||
to.clip(XYWH(x0, y0, w0, h0), |to|item.draw(to))
|
||||
} else {
|
||||
Ok(None)
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
|
||||
/// Move content in the positive direction of one or both axes.
|
||||
///
|
||||
/// ```
|
||||
/// # fn doctest_layout_push () -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use tengri::{Layout, Draw, XYWH};
|
||||
/// let area = XYWH(0u16, 0, 80, 25);
|
||||
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
||||
/// assert_eq!("1".push_x(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1)));
|
||||
/// assert_eq!("1".push_y(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1)));
|
||||
/// assert_eq!("1".push_xy(1, 1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub enum Push<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
||||
__(PhantomData<T>),
|
||||
X(I, X),
|
||||
Y(I, X),
|
||||
XY(I, X, X),
|
||||
}
|
||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Push<T, I, X>, to: T|{
|
||||
match self {
|
||||
Self::__(_) => unreachable!(),
|
||||
Self::X(item, x1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
||||
to.clip(XYWH(
|
||||
x + x1.into().unwrap_or_default(), y, w, h
|
||||
), |to|item.draw(to))
|
||||
},
|
||||
Self::Y(item, y1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
||||
to.clip(XYWH(
|
||||
x, y + y1.into().unwrap_or_default(), w, h
|
||||
), |to|item.draw(to))
|
||||
},
|
||||
Self::XY(item, x1, y1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
||||
to.clip(XYWH(
|
||||
x + x1.into().unwrap_or_default(), y + y1.into().unwrap_or_default(), w, h
|
||||
), |to|item.draw(to))
|
||||
},
|
||||
_ => Ok(None)
|
||||
}
|
||||
});
|
||||
|
||||
/// Move content in the negative direction of one or both axes.
|
||||
///
|
||||
/// ```
|
||||
/// # fn doctest_layout_pull () -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use tengri::{Layout, Draw, XYWH};
|
||||
/// let area = XYWH(1u16, 1, 80, 25);
|
||||
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
||||
/// assert_eq!("1".pull_x(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1)));
|
||||
/// assert_eq!("1".pull_y(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1)));
|
||||
/// assert_eq!("1".pull_xy(1, 1).layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub enum Pull<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
||||
__(PhantomData<T>),
|
||||
X(I, X),
|
||||
Y(I, X),
|
||||
XY(I, X, X),
|
||||
}
|
||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Pull<T, I, X>, _to: T|{
|
||||
todo!()
|
||||
});
|
||||
|
||||
/// Only draw content if area is above a certain size.
|
||||
///
|
||||
/// ```
|
||||
/// # fn doctest_layout_min () -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use tengri::{Layout, Draw, XYWH};
|
||||
/// let area = XYWH(1u16, 1, 80, 25);
|
||||
/// assert_eq!("1".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 5, 1)));
|
||||
/// assert_eq!("1".min_h(5).layout(area)?, Some(XYWH(1u16, 1, 1, 5)));
|
||||
/// assert_eq!("1".min_wh(5, 5).layout(area)?, Some(XYWH(1u16, 1, 5, 5)));
|
||||
/// assert_eq!("123456".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 6, 1)));
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub enum Min<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
||||
__(PhantomData<T>),
|
||||
W(I, X),
|
||||
H(I, X),
|
||||
WH(I, X, X),
|
||||
}
|
||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Min<T, I, X>, _to: T|{
|
||||
todo!()
|
||||
});
|
||||
|
||||
/// Set maximum size of of drawing area.
|
||||
///
|
||||
/// ```
|
||||
/// # fn doctest_layout_max () -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use tengri::{Layout, Draw, XYWH};
|
||||
/// let area = XYWH(1u16, 1, 80, 25);
|
||||
/// assert_eq!("12345".max_w(1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
|
||||
/// assert_eq!("12345".max_h(1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
|
||||
/// assert_eq!("12345".max_wh(1, 1).layout(area)?, Some(XYWH(1u16, 1, 5, 1)));
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub enum Max<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
||||
__(PhantomData<T>),
|
||||
W(I, X),
|
||||
H(I, X),
|
||||
WH(I, X, X),
|
||||
}
|
||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Max<T, I, X>, to: T|{
|
||||
let area: XYWH<T::Unit> = to.area();
|
||||
let (item, area) = match self {
|
||||
Self::W(item, max_w) => (item, XYWH(
|
||||
area.0, area.1, max_w.into().map(|max|max.min(area.2)).unwrap_or(area.2),
|
||||
area.3
|
||||
)),
|
||||
Self::H(item, max_h) => (item, XYWH(
|
||||
area.0, area.1, area.2,
|
||||
max_h.into().map(|max|max.min(area.3)).unwrap_or(area.3)
|
||||
)),
|
||||
Self::WH(item, max_w, max_h) => (item, XYWH(
|
||||
area.0, area.1, max_w.into().map(|max|max.min(area.2)).unwrap_or(area.2),
|
||||
max_h.into().map(|max|max.min(area.3)).unwrap_or(area.3),
|
||||
)),
|
||||
_ => return Ok(None)
|
||||
};
|
||||
to.clip(area, draw(item))
|
||||
});
|
||||
|
||||
/// Set size of of drawing area.
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::Layout;
|
||||
/// let _ = "".exact_w(1);
|
||||
/// let _ = "".exact_h(1);
|
||||
/// let _ = "".exact_wh(1, 1);
|
||||
/// ```
|
||||
pub enum Exact<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
||||
__(PhantomData<T>),
|
||||
W(I, X),
|
||||
H(I, X),
|
||||
WH(I, X, X),
|
||||
}
|
||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Exact<T, I, X>, to: T|{
|
||||
let area: XYWH<T::Unit> = to.area();
|
||||
let (item, area) = match self {
|
||||
Self::W(item, w1) =>
|
||||
(item, XYWH(area.0, area.1, w1.into().unwrap_or(area.2), area.3)),
|
||||
Self::H(item, h1) =>
|
||||
(item, XYWH(area.0, area.1, area.2, h1.into().unwrap_or(area.3))),
|
||||
Self::WH(item, w1, h1) =>
|
||||
(item, XYWH(area.0, area.1, w1.into().unwrap_or(area.2), h1.into().unwrap_or(area.3))),
|
||||
_ => return Ok(None)
|
||||
};
|
||||
to.clip(area, |to|item.draw(to))
|
||||
});
|
||||
|
||||
/// Define inner drawing area.
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::Layout;
|
||||
/// let _ = "".pad_w(1);
|
||||
/// let _ = "".pad_h(1);
|
||||
/// let _ = "".pad_wh(1, 1);
|
||||
/// ```
|
||||
pub enum Pad<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
||||
__(PhantomData<T>),
|
||||
W(I, X),
|
||||
H(I, X),
|
||||
WH(I, X, X),
|
||||
}
|
||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Pad<T, I, X>, _to: T|{
|
||||
todo!()
|
||||
});
|
||||
|
||||
pub struct Align<T>(Option<Azimuth>, T);
|
||||
impl_draw!(<S: Screen, T: Draw<S>,>|self: Align<T>, to: S|{
|
||||
use Azimuth::*;
|
||||
let XYWH(x0, y0, w0, h0) = to.area();
|
||||
if let Some(XYWH(x, y, w, h)) = self.1.layout(to.area())? {
|
||||
to.clip(match self.0 {
|
||||
Some(NW) => XYWH(x0, y0, w, h),
|
||||
Some(N) => XYWH(x0 + w0.minus(w) / 2.into(), y0, w, h),
|
||||
Some(NE) => XYWH((x0 + w0).minus(w), y0, w, h),
|
||||
Some(W) => XYWH(x0, y0 + h0.minus(h) / 2.into(), w, h),
|
||||
Some(C) => XYWH(x0 + w0.minus(w) / 2.into(), y0 + h0.minus(h) / 2.into(), w, h),
|
||||
Some(E) => XYWH((x0 + w0).minus(w), y0 + h0.minus(h) / 2.into(), w, h),
|
||||
Some(SW) => XYWH(x0, (y0 + h0).minus(h), w, h),
|
||||
Some(S) => XYWH(x0 + w0.minus(w) / 2.into(), (y0 + h0).minus(h), w, h),
|
||||
Some(SE) => XYWH((x0 + w0).minus(w), (y0 + h0).minus(h), w, h),
|
||||
Some(X) => XYWH(x0 + w0.minus(w) / 2.into(), y, w, h),
|
||||
Some(Y) => XYWH(x, y0 + h0.minus(h) / 2.into(), w, h),
|
||||
None => to.area()
|
||||
}, |to|self.1.draw(to))
|
||||
} else {
|
||||
self.1.draw(to)
|
||||
}
|
||||
});
|
||||
|
||||
/// Where is [0, 0] located?
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::*;
|
||||
/// use Azimuth::*;
|
||||
/// let _ = "".align(NW);
|
||||
/// ```
|
||||
#[cfg_attr(test, derive(Arbitrary))]
|
||||
#[derive(Debug, Copy, Clone, Default)] pub enum Azimuth {
|
||||
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
|
||||
}
|
||||
|
||||
/// ```
|
||||
/// use tengri::*;
|
||||
/// let _ = area(None, "unareaed");
|
||||
/// let _ = area(XYWH(1, 2, 3, 4), "southeast");
|
||||
/// let _ = area(Some(XYWH(1, 2, 3, 4)), "southeast");
|
||||
/// ```
|
||||
pub fn area <S: Screen, T: Draw<S>, U: Into<Option<XYWH<S::Unit>>>> (
|
||||
origin: U, it: T
|
||||
) -> Area<S, T> {
|
||||
Area(origin.into(), it)
|
||||
}
|
||||
|
||||
pub struct Area<S: Screen, T: Draw<S>>(
|
||||
pub Option<XYWH<S::Unit>>,
|
||||
pub T
|
||||
);
|
||||
impl_draw!(<S: Screen, T: Draw<S>,>|self: Area<S, T>, to: S|{
|
||||
to.clip(self.0, |to|self.1.draw(to))
|
||||
});
|
||||
|
||||
pub struct Origin<T>(Option<Azimuth>, T);
|
||||
impl_draw!(<S: Screen, T: Draw<S>,>|self: Origin<T>, _to: S|{
|
||||
todo!()
|
||||
});
|
||||
|
||||
/// Something that has `[0, 0]` at a particular point.
|
||||
pub trait HasOrigin {
|
||||
fn origin (&self) -> Azimuth;
|
||||
}
|
||||
|
||||
impl<T: AsRef<Azimuth>> HasOrigin for T {
|
||||
fn origin (&self) -> Azimuth {
|
||||
*self.as_ref()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::*;
|
||||
use crate::{*, layout::*, draw::*};
|
||||
use Azimuth::*;
|
||||
|
||||
impl<N: Coord, T: Xywh<N>> Lrtb<N> for T {}
|
||||
|
|
|
|||
|
|
@ -29,17 +29,3 @@ impl Sizer {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ShowSize;
|
||||
|
||||
impl Draw<Tui> for ShowSize {
|
||||
fn layout (&self, area: XYWH<u16>) -> Perhaps<XYWH<u16>> {
|
||||
let info = format!("{area:?}");
|
||||
Ok(Some(XYWH(area.0, area.1, info.len() as u16, 1)))
|
||||
}
|
||||
fn draw (self, to: &mut Tui) -> Drawn<u16> {
|
||||
let area = to.area();
|
||||
let info = format!("{area:?}");
|
||||
to.text(&info, area.0, area.1, info.len() as u16)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,34 @@
|
|||
use super::*;
|
||||
use super::{*, layout::*};
|
||||
|
||||
#[macro_export] macro_rules! north {
|
||||
($head:expr $(,)?) => { $head };
|
||||
($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) };
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! south {
|
||||
($head:expr $(,)?) => { $head };
|
||||
($head:expr, $($tail:expr),* $(,)?) => { south($head, south!($($tail,)*)) };
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! east {
|
||||
($head:expr $(,)?) => { $head };
|
||||
($head:expr, $($tail:expr),* $(,)?) => { east($head, east!($($tail,)*)) };
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! west {
|
||||
($head:expr $(,)?) => { $head };
|
||||
($head:expr $(, $tail:expr)* $(,)?) => { west($head, west!($($tail,)*)) };
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! above {
|
||||
($head:expr $(,)?) => { $head };
|
||||
($head:expr $(, $tail:expr)* $(,)?) => { above($head, above!($($tail,)*)) };
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! below {
|
||||
($head:expr $(,)?) => { $head };
|
||||
($head:expr, $($tail:expr),* $(,)?) => { below($head, below!($($tail,)*)) };
|
||||
}
|
||||
|
||||
pub const fn east <S: Screen, A: Draw<S>, B: Draw<S>> (a: A, b: B) -> impl Draw<S> {
|
||||
Split::East.half(a, b)
|
||||
|
|
@ -141,7 +171,7 @@ fn draw_stacks <S: Screen> (
|
|||
})
|
||||
}
|
||||
|
||||
fn stack_areas <S: Screen> (
|
||||
pub fn stack_areas <S: Screen> (
|
||||
split: &Split,
|
||||
area: XYWH<S::Unit>,
|
||||
a: &impl Draw<S>,
|
||||
|
|
@ -152,9 +182,7 @@ fn stack_areas <S: Screen> (
|
|||
Split::South => (
|
||||
area_a,
|
||||
if let Some(used) = area_a {
|
||||
b.layout(XYWH(
|
||||
area.x(), area.y() + used.h(), area.w(), area.h().minus(used.h())
|
||||
))?
|
||||
b.layout(XYWH(area.x(), area.y() + used.h(), area.w(), area.h().minus(used.h())))?
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -162,9 +190,7 @@ fn stack_areas <S: Screen> (
|
|||
Split::East => (
|
||||
area_a,
|
||||
if let Some(used) = area_a {
|
||||
b.layout(XYWH(
|
||||
area.x() + used.w(), area.y(), area.w().minus(used.w()), area.h()
|
||||
))?
|
||||
b.layout(XYWH(area.x() + used.w(), area.y(), area.w().minus(used.w()), area.h()))?
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
@ -176,9 +202,7 @@ fn stack_areas <S: Screen> (
|
|||
None
|
||||
},
|
||||
if let Some(used) = area_a {
|
||||
b.layout(XYWH(
|
||||
area.x(), area.y(), area.w(), area.h().minus(used.h())
|
||||
))?
|
||||
b.layout(XYWH(area.x(), area.y(), area.w(), area.h().minus(used.h())))?
|
||||
} else {
|
||||
b.layout(area)?.map(|area_b|XYWH(
|
||||
area_b.x(), area_b.y() + area_b.h(), area_b.w(), area_b.h()
|
||||
|
|
@ -192,9 +216,7 @@ fn stack_areas <S: Screen> (
|
|||
None
|
||||
},
|
||||
if let Some(used) = area_a {
|
||||
b.layout(XYWH(
|
||||
area.x(), area.y(), area.w().minus(used.w()), area.h()
|
||||
))?
|
||||
b.layout(XYWH(area.x(), area.y(), area.w().minus(used.w()), area.h()))?
|
||||
} else {
|
||||
b.layout(area)?.map(|area_b|XYWH(
|
||||
area_b.x() + area_b.w(), area_b.y(), area_b.w(), area_b.h()
|
||||
|
|
@ -231,32 +253,18 @@ fn stack_drawn <S: Coord> (
|
|||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! north {
|
||||
($head:expr $(,)?) => { $head };
|
||||
($head:expr, $($tail:expr),* $(,)?) => { north($head, north!($($tail,)*)) };
|
||||
#[cfg(test)] mod test {
|
||||
use crate::*;
|
||||
#[test] fn test_stack_areas () -> Usually<()> {
|
||||
let area = XYWH(0u16, 0, 80, 25);
|
||||
assert_eq!(stack_areas(&Split::East, area, &"foo", &"bar")?, (
|
||||
Some(XYWH(0u16, 0, 3, 1)),
|
||||
Some(XYWH(3u16, 0, 3, 1)),
|
||||
));
|
||||
assert_eq!(stack_areas(&Split::South, area, &"foo", &"bar")?, (
|
||||
Some(XYWH(0u16, 0, 3, 1)),
|
||||
Some(XYWH(0u16, 1, 3, 1)),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! south {
|
||||
($head:expr $(,)?) => { $head };
|
||||
($head:expr, $($tail:expr),* $(,)?) => { south($head, south!($($tail,)*)) };
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! east {
|
||||
($head:expr $(,)?) => { $head };
|
||||
($head:expr, $($tail:expr),* $(,)?) => { east($head, east!($($tail,)*)) };
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! west {
|
||||
($head:expr $(,)?) => { $head };
|
||||
($head:expr $(, $tail:expr)* $(,)?) => { west($head, west!($($tail,)*)) };
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! above {
|
||||
($head:expr $(,)?) => { $head };
|
||||
($head:expr $(, $tail:expr)* $(,)?) => { above($head, above!($($tail,)*)) };
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! below {
|
||||
($head:expr $(,)?) => { $head };
|
||||
($head:expr, $($tail:expr),* $(,)?) => { below($head, below!($($tail,)*)) };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
use crate::*;
|
||||
use super::*;
|
||||
|
||||
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
|
||||
pub struct Thunk<T: Screen, F>(pub F, std::marker::PhantomData<T>);
|
||||
|
|
|
|||
298
src/eval.rs
298
src/eval.rs
|
|
@ -1,298 +0,0 @@
|
|||
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
|
||||
}).stack(
|
||||
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, "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)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Interpret TUI-specific layout operation.
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::{*, lang::*, 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> {
|
||||
/// if let Some(expr) = src.expr()? {
|
||||
/// match (expr.head()?, expr.tail()?) {
|
||||
/// (Some("g"), Some(tail)) => {
|
||||
/// let n: u8 = state.namespace(tail.head().map_err(Into::into))?.ok_or(LanguageError::Domain("not gray"))?;
|
||||
/// Ok(Some(Color::Rgb(n, n, n)))
|
||||
/// },
|
||||
/// (Some("rgb"), Some(tail)) => {
|
||||
/// let r: u8 = state.namespace(tail.head().map_err(Into::into))?
|
||||
/// .ok_or(LanguageError::Domain("not red"))?;
|
||||
/// let g: u8 = state.namespace(tail.tail().head().map_err(Into::into))?
|
||||
/// .ok_or(LanguageError::Domain("not green"))?;
|
||||
/// let b: u8 = state.namespace(tail.tail().tail().head().map_err(Into::into))?
|
||||
/// .ok_or(LanguageError::Domain("not blue"))?;
|
||||
/// Ok(Some(Color::Rgb(r, g, b)))
|
||||
/// },
|
||||
/// (Some(_), _) => return Err(format!("not a color expression: {expr}").into()),
|
||||
/// (None, _) => return Err(format!("not a color expression: {expr}").into()),
|
||||
/// }
|
||||
/// } else if let Ok(Some(sym)) = src.word() {
|
||||
/// Ok(match sym {
|
||||
/// ":color/bg" => Some(Color::Rgb(28, 32, 36)),
|
||||
/// ":color/fg" => Some(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);
|
||||
/// 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, 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>
|
||||
{
|
||||
use crate::term::*;
|
||||
// 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()? {
|
||||
to.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)? {
|
||||
fg(color, thunk(move|to: &mut Tui|{
|
||||
state.interpret(to, &arg1)?;
|
||||
Ok(Some(to.area().into())) // FIXME?: don't max out the used area?
|
||||
})).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, thunk(move|to: &mut Tui|{
|
||||
state.interpret(to, &arg1)?;
|
||||
Ok(Some(to.area().into())) // FIXME?: don't max out the used area?
|
||||
})).draw(to)
|
||||
} else {
|
||||
return Err(format!("bg: {arg0:?}: not a color").into())
|
||||
}
|
||||
},
|
||||
|
||||
_ => return Ok(None)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
use crate::*;
|
||||
use std::sync::{Arc, atomic::AtomicBool};
|
||||
use crossterm::event::*;
|
||||
|
||||
#[derive(Clone)] pub struct Exit(Arc<AtomicBool>);
|
||||
|
||||
|
|
|
|||
39
src/layout/align.rs
Normal file
39
src/layout/align.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
use crate::{*, draw::*, layout::*};
|
||||
use Azimuth::*;
|
||||
|
||||
pub struct Align<T>(
|
||||
pub(crate) Option<Azimuth>,
|
||||
pub(crate) T,
|
||||
);
|
||||
|
||||
impl<S: Screen, T: Draw<S>> Draw<S> for Align<T> {
|
||||
fn layout (&self, area: XYWH<S::Unit>) -> Perhaps<XYWH<S::Unit>> {
|
||||
let XYWH(x0, y0, w0, h0) = area;
|
||||
Ok(align::<S>(area, self.1.layout(area)?, self.0))
|
||||
}
|
||||
fn draw (self, to: &mut S) -> Perhaps<XYWH<S::Unit>> {
|
||||
Ok(self.layout(to.area())?.map(|area|to.clip(area, |to|self.1.draw(to))).transpose()?.flatten())
|
||||
}
|
||||
}
|
||||
|
||||
fn align <S: Screen> (
|
||||
area0: XYWH<S::Unit>, area: Option<XYWH<S::Unit>>, azimuth: Option<Azimuth>
|
||||
) -> Option<XYWH<S::Unit>> {
|
||||
area.map(|XYWH(x, y, w, h)|{
|
||||
let XYWH(x0, y0, w0, h0) = area0;
|
||||
match azimuth {
|
||||
Some(NW) => XYWH(x0, y0, w, h),
|
||||
Some(N) => XYWH(x0 + w0.minus(w) / 2.into(), y0, w, h),
|
||||
Some(NE) => XYWH((x0 + w0).minus(w), y0, w, h),
|
||||
Some(W) => XYWH(x0, y0 + h0.minus(h) / 2.into(), w, h),
|
||||
Some(C) => XYWH(x0 + w0.minus(w) / 2.into(), y0 + h0.minus(h) / 2.into(), w, h),
|
||||
Some(E) => XYWH((x0 + w0).minus(w), y0 + h0.minus(h) / 2.into(), w, h),
|
||||
Some(SW) => XYWH(x0, (y0 + h0).minus(h), w, h),
|
||||
Some(S) => XYWH(x0 + w0.minus(w) / 2.into(), (y0 + h0).minus(h), w, h),
|
||||
Some(SE) => XYWH((x0 + w0).minus(w), (y0 + h0).minus(h), w, h),
|
||||
Some(X) => XYWH(x0 + w0.minus(w) / 2.into(), y, w, h),
|
||||
Some(Y) => XYWH(x, y0 + h0.minus(h) / 2.into(), w, h),
|
||||
None => XYWH(x, y, w, h)
|
||||
}
|
||||
})
|
||||
}
|
||||
21
src/layout/area.rs
Normal file
21
src/layout/area.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use crate::{*, draw::*};
|
||||
|
||||
/// ```
|
||||
/// use tengri::*;
|
||||
/// let _ = area(None, "unareaed");
|
||||
/// let _ = area(XYWH(1, 2, 3, 4), "southeast");
|
||||
/// let _ = area(Some(XYWH(1, 2, 3, 4)), "southeast");
|
||||
/// ```
|
||||
pub fn area <S: Screen, T: Draw<S>, U: Into<Option<XYWH<S::Unit>>>> (
|
||||
origin: U, it: T
|
||||
) -> Area<S, T> {
|
||||
Area(origin.into(), it)
|
||||
}
|
||||
|
||||
pub struct Area<S: Screen, T: Draw<S>>(
|
||||
pub Option<XYWH<S::Unit>>,
|
||||
pub T
|
||||
);
|
||||
impl_draw!(<S: Screen, T: Draw<S>,>|self: Area<S, T>, to: S|{
|
||||
to.clip(self.0, |to|self.1.draw(to))
|
||||
});
|
||||
29
src/layout/exact.rs
Normal file
29
src/layout/exact.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use crate::{*, draw::*};
|
||||
|
||||
/// Set size of of drawing area.
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::Layout;
|
||||
/// let _ = "".exact_w(1);
|
||||
/// let _ = "".exact_h(1);
|
||||
/// let _ = "".exact_wh(1, 1);
|
||||
/// ```
|
||||
pub enum Exact<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
||||
__(PhantomData<T>),
|
||||
W(I, X),
|
||||
H(I, X),
|
||||
WH(I, X, X),
|
||||
}
|
||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Exact<T, I, X>, to: T|{
|
||||
let area: XYWH<T::Unit> = to.area();
|
||||
let (item, area) = match self {
|
||||
Self::W(item, w1) =>
|
||||
(item, XYWH(area.0, area.1, w1.into().unwrap_or(area.2), area.3)),
|
||||
Self::H(item, h1) =>
|
||||
(item, XYWH(area.0, area.1, area.2, h1.into().unwrap_or(area.3))),
|
||||
Self::WH(item, w1, h1) =>
|
||||
(item, XYWH(area.0, area.1, w1.into().unwrap_or(area.2), h1.into().unwrap_or(area.3))),
|
||||
_ => return Ok(None)
|
||||
};
|
||||
to.clip(area, |to|item.draw(to))
|
||||
});
|
||||
41
src/layout/full.rs
Normal file
41
src/layout/full.rs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
use crate::{*, draw::*};
|
||||
|
||||
/// Use whole drawing area along one or both axes.
|
||||
///
|
||||
/// ```
|
||||
/// # fn doctest_layout_full () -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use tengri::{Layout, Draw, XYWH};
|
||||
/// let area = XYWH(0u16, 0, 80, 25);
|
||||
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
||||
/// assert_eq!("1".full_w().layout(area)?, Some(XYWH(0u16, 0, 80, 1)));
|
||||
/// assert_eq!("1".full_h().layout(area)?, Some(XYWH(0u16, 0, 1, 25)));
|
||||
/// assert_eq!("1".full_wh().layout(area)?, Some(XYWH(0u16, 0, 80, 25)));
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub enum Full<T: Screen, I: Draw<T>> {
|
||||
__(PhantomData<T>),
|
||||
W(I),
|
||||
H(I),
|
||||
WH(I),
|
||||
}
|
||||
impl_draw!(<T: Screen, I: Draw<T>,>|self: Full<T, I>, to: T|{
|
||||
let XYWH(x0, y0, w0, h0) = to.area();
|
||||
match self {
|
||||
Self::W(item) => if let Some(XYWH(_, y, _, h)) = item.layout(to.area())? {
|
||||
to.clip(XYWH(x0, y, w0, h), |to|item.draw(to))
|
||||
} else {
|
||||
Ok(None)
|
||||
},
|
||||
Self::H(item) => if let Some(XYWH(x, _, w, _)) = item.layout(to.area())? {
|
||||
to.clip(XYWH(x, y0, w, h0), |to|item.draw(to))
|
||||
} else {
|
||||
Ok(None)
|
||||
},
|
||||
Self::WH(item) => if let Some(XYWH(..)) = item.layout(to.area())? {
|
||||
to.clip(XYWH(x0, y0, w0, h0), |to|item.draw(to))
|
||||
} else {
|
||||
Ok(None)
|
||||
},
|
||||
_ => unreachable!(),
|
||||
}
|
||||
});
|
||||
55
src/layout/max.rs
Normal file
55
src/layout/max.rs
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
use crate::{*, draw::*};
|
||||
|
||||
/// Set maximum size of of drawing area.
|
||||
///
|
||||
/// ```
|
||||
/// # fn doctest_layout_max () -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use tengri::{Layout, Draw, XYWH};
|
||||
/// let area = XYWH(1u16, 1, 80, 25);
|
||||
/// assert_eq!("12345".max_w(1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
|
||||
/// assert_eq!("12345".max_h(1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
|
||||
/// assert_eq!("12345".max_wh(1, 1).layout(area)?, Some(XYWH(1u16, 1, 5, 1)));
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub enum Max<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>> + Copy> {
|
||||
__(PhantomData<T>),
|
||||
W(I, X),
|
||||
H(I, X),
|
||||
WH(I, X, X),
|
||||
}
|
||||
|
||||
impl<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>> + Copy> Draw<T> for Max<T, I, X> {
|
||||
fn layout (&self, area: XYWH<T::Unit>) -> Perhaps<XYWH<T::Unit>> {
|
||||
Ok(Some(match self {
|
||||
Self::W(_, max_w) => XYWH(
|
||||
area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2),
|
||||
area.3),
|
||||
Self::H(_, max_h) => XYWH(
|
||||
area.0, area.1, area.2,
|
||||
(*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3)),
|
||||
Self::WH(_, max_w, max_h) => XYWH(
|
||||
area.0, area.1, (*max_w).into().map(|max|max.min(area.2)).unwrap_or(area.2),
|
||||
(*max_h).into().map(|max|max.min(area.3)).unwrap_or(area.3)),
|
||||
_ => return Ok(None)
|
||||
}))
|
||||
}
|
||||
fn draw (self, to: &mut T) -> Perhaps<XYWH<T::Unit>> {
|
||||
let area: XYWH<T::Unit> = to.area();
|
||||
let (item, area) = match self {
|
||||
Self::W(item, max_w) => (item, XYWH(
|
||||
area.0, area.1, max_w.into().map(|max|max.min(area.2)).unwrap_or(area.2),
|
||||
area.3
|
||||
)),
|
||||
Self::H(item, max_h) => (item, XYWH(
|
||||
area.0, area.1, area.2,
|
||||
max_h.into().map(|max|max.min(area.3)).unwrap_or(area.3)
|
||||
)),
|
||||
Self::WH(item, max_w, max_h) => (item, XYWH(
|
||||
area.0, area.1, max_w.into().map(|max|max.min(area.2)).unwrap_or(area.2),
|
||||
max_h.into().map(|max|max.min(area.3)).unwrap_or(area.3),
|
||||
)),
|
||||
_ => return Ok(None)
|
||||
};
|
||||
to.clip(area, draw(item))
|
||||
}
|
||||
}
|
||||
40
src/layout/min.rs
Normal file
40
src/layout/min.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use crate::{*, draw::*};
|
||||
|
||||
/// Only draw content if area is above a certain size.
|
||||
///
|
||||
/// ```
|
||||
/// # fn doctest_layout_min () -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use tengri::{Layout, Draw, XYWH};
|
||||
/// let area = XYWH(1u16, 1, 80, 25);
|
||||
/// assert_eq!("1".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 5, 1)));
|
||||
/// assert_eq!("1".min_h(5).layout(area)?, Some(XYWH(1u16, 1, 1, 5)));
|
||||
/// assert_eq!("1".min_wh(5, 5).layout(area)?, Some(XYWH(1u16, 1, 5, 5)));
|
||||
/// assert_eq!("123456".min_w(5).layout(area)?, Some(XYWH(1u16, 1, 6, 1)));
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub enum Min<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
||||
__(PhantomData<T>),
|
||||
W(I, X),
|
||||
H(I, X),
|
||||
WH(I, X, X),
|
||||
}
|
||||
|
||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Min<T, I, X>, to: T|{
|
||||
match self {
|
||||
Self::__(_) => unreachable!(),
|
||||
Self::W(item, w1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
||||
let w = w1.into().map(|w1|w.max(w1)).unwrap_or(w);
|
||||
to.clip(XYWH(x, y, w, h), |to|item.draw(to))
|
||||
},
|
||||
Self::H(item, h1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
||||
let h = h1.into().map(|h1|h.max(h1)).unwrap_or(h);
|
||||
to.clip(XYWH(x, y, w, h), |to|item.draw(to))
|
||||
},
|
||||
Self::WH(item, w1, h1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
||||
let w = w1.into().map(|w1|w.max(w1)).unwrap_or(w);
|
||||
let h = h1.into().map(|h1|h.max(h1)).unwrap_or(h);
|
||||
to.clip(XYWH(x, y, w, h), |to|item.draw(to))
|
||||
},
|
||||
_ => Ok(None)
|
||||
}
|
||||
});
|
||||
21
src/layout/origin.rs
Normal file
21
src/layout/origin.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use crate::{*, draw::*, layout::*};
|
||||
|
||||
pub struct Origin<T>(
|
||||
pub(crate) Option<Azimuth>,
|
||||
pub(crate) T
|
||||
);
|
||||
|
||||
impl_draw!(<S: Screen, T: Draw<S>,>|self: Origin<T>, _to: S|{
|
||||
todo!()
|
||||
});
|
||||
|
||||
/// Something that has `[0, 0]` at a particular point.
|
||||
pub trait HasOrigin {
|
||||
fn origin (&self) -> Azimuth;
|
||||
}
|
||||
|
||||
impl<T: AsRef<Azimuth>> HasOrigin for T {
|
||||
fn origin (&self) -> Azimuth {
|
||||
*self.as_ref()
|
||||
}
|
||||
}
|
||||
37
src/layout/pad.rs
Normal file
37
src/layout/pad.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
use crate::{*, draw::*};
|
||||
|
||||
/// Define inner drawing area.
|
||||
///
|
||||
/// ```
|
||||
/// use tengri::Layout;
|
||||
/// let _ = "".pad_w(1);
|
||||
/// let _ = "".pad_h(1);
|
||||
/// let _ = "".pad_wh(1, 1);
|
||||
/// ```
|
||||
pub enum Pad<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
||||
__(PhantomData<T>),
|
||||
W(I, X),
|
||||
H(I, X),
|
||||
WH(I, X, X),
|
||||
}
|
||||
|
||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Pad<T, I, X>, to: T|{
|
||||
let area = to.area();
|
||||
let (item, area) = match self {
|
||||
Self::W(item, w1) => {
|
||||
let w1 = w1.into().unwrap_or(T::Unit::zero());
|
||||
(item, XYWH(area.0 + w1, area.1, area.2.minus(w1 + w1), area.3))
|
||||
},
|
||||
Self::H(item, h1) => {
|
||||
let h1 = h1.into().unwrap_or(T::Unit::zero());
|
||||
(item, XYWH(area.0, area.1 + h1, area.2, area.3.minus(h1 + h1)))
|
||||
},
|
||||
Self::WH(item, w1, h1) => {
|
||||
let w1 = w1.into().unwrap_or(T::Unit::zero());
|
||||
let h1 = h1.into().unwrap_or(T::Unit::zero());
|
||||
(item, XYWH(area.0 + w1, area.1 + h1, area.2.minus(w1 + w1), area.3.minus(h1 + h1)))
|
||||
},
|
||||
_ => return Ok(None)
|
||||
};
|
||||
item.draw(to)
|
||||
});
|
||||
24
src/layout/pull.rs
Normal file
24
src/layout/pull.rs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
use crate::{*, draw::*};
|
||||
|
||||
/// Move content in the negative direction of one or both axes.
|
||||
///
|
||||
/// ```
|
||||
/// # fn doctest_layout_pull () -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use tengri::{Layout, Draw, XYWH};
|
||||
/// let area = XYWH(1u16, 1, 80, 25);
|
||||
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
||||
/// assert_eq!("1".pull_x(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1)));
|
||||
/// assert_eq!("1".pull_y(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1)));
|
||||
/// assert_eq!("1".pull_xy(1, 1).layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub enum Pull<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
||||
__(PhantomData<T>),
|
||||
X(I, X),
|
||||
Y(I, X),
|
||||
XY(I, X, X),
|
||||
}
|
||||
|
||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Pull<T, I, X>, _to: T|{
|
||||
todo!()
|
||||
});
|
||||
42
src/layout/push.rs
Normal file
42
src/layout/push.rs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
use crate::{*, draw::*};
|
||||
|
||||
/// Move content in the positive direction of one or both axes.
|
||||
///
|
||||
/// ```
|
||||
/// # fn doctest_layout_push () -> Result<(), Box<dyn std::error::Error>> {
|
||||
/// use tengri::{Layout, Draw, XYWH};
|
||||
/// let area = XYWH(0u16, 0, 80, 25);
|
||||
/// assert_eq!("1".layout(area)?, Some(XYWH(0u16, 0, 1, 1)));
|
||||
/// assert_eq!("1".push_x(1).layout(area)?, Some(XYWH(1u16, 0, 1, 1)));
|
||||
/// assert_eq!("1".push_y(1).layout(area)?, Some(XYWH(0u16, 1, 1, 1)));
|
||||
/// assert_eq!("1".push_xy(1, 1).layout(area)?, Some(XYWH(1u16, 1, 1, 1)));
|
||||
/// # Ok(()) }
|
||||
/// ```
|
||||
pub enum Push<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>> {
|
||||
__(PhantomData<T>),
|
||||
X(I, X),
|
||||
Y(I, X),
|
||||
XY(I, X, X),
|
||||
}
|
||||
|
||||
impl_draw!(<T: Screen, I: Draw<T>, X: Into<Option<T::Unit>>,>|self: Push<T, I, X>, to: T|{
|
||||
match self {
|
||||
Self::__(_) => unreachable!(),
|
||||
Self::X(item, x1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
||||
to.clip(XYWH(
|
||||
x + x1.into().unwrap_or_default(), y, w, h
|
||||
), |to|item.draw(to))
|
||||
},
|
||||
Self::Y(item, y1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
||||
to.clip(XYWH(
|
||||
x, y + y1.into().unwrap_or_default(), w, h
|
||||
), |to|item.draw(to))
|
||||
},
|
||||
Self::XY(item, x1, y1) if let Some(XYWH(x, y, w, h)) = item.layout(to.area())? => {
|
||||
to.clip(XYWH(
|
||||
x + x1.into().unwrap_or_default(), y + y1.into().unwrap_or_default(), w, h
|
||||
), |to|item.draw(to))
|
||||
},
|
||||
_ => Ok(None)
|
||||
}
|
||||
});
|
||||
1594
src/lib.rs
1594
src/lib.rs
File diff suppressed because it is too large
Load diff
349
src/term.rs
349
src/term.rs
|
|
@ -1,349 +0,0 @@
|
|||
#[macro_export] macro_rules! tui_app {
|
||||
($Struct:ident { $($fields:tt)* }) => {
|
||||
#[dizzle::namespace(bool)]
|
||||
#[dizzle::namespace(u16)]
|
||||
#[dizzle::namespace(Option<u16>)]
|
||||
#[dizzle::namespace(Color)]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct $Struct { $($fields)* }
|
||||
tui_main!($Struct { ..Default::default() });
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement standard [main] entrypoint for TUI apps.
|
||||
#[macro_export] macro_rules! tui_main {
|
||||
($state:expr) => {
|
||||
pub fn main () -> Usually<()> {
|
||||
Tui::setup_panic();
|
||||
Tui::run_main(Arc::new(RwLock::new($state)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Enable TUI output for state struct.
|
||||
#[macro_export] macro_rules! tui_view {
|
||||
($self:ident: $State:ty $body:block) => {
|
||||
impl View<Tui> for $State {
|
||||
fn view (&$self) -> impl Draw<Tui> $body
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! tui_interpret {
|
||||
($self:ident: $State:ident, $to:pat, $pat:ident -> $Result:ty { $($body:tt)+ }) => {
|
||||
impl Interpret<Tui, $Result> for $State {
|
||||
fn interpret_word <'a> (&'a $self, $to: &mut Tui, $pat: &'a impl Symbol) -> Usually<$Result> {
|
||||
$($body)+
|
||||
}
|
||||
fn interpret_expr <'a> (&'a self, to: &mut Tui, src: &'a impl Expression) -> Usually<$Result> {
|
||||
Ok(Some(if let Some(area) = eval_view(self, to, src)? {
|
||||
area
|
||||
} else if let Some(area) = eval_view_tui(self, to, src)? {
|
||||
area
|
||||
} else {
|
||||
return Err(format!("App::interpret_expr: unexpected: {src:?}").into())
|
||||
}))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Enable TUI keyboard input for main state struct.
|
||||
#[macro_export] macro_rules! tui_keys {
|
||||
($self:ident:$State:ty,$input:ident $($body:tt)+) => {
|
||||
impl Apply<TuiEvent, Usually<()>> for $State {
|
||||
fn apply (&mut $self, $input: &TuiEvent) -> Usually<()> $($body)+
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
use crate::{*, lang::*};
|
||||
mod border; pub use self::border::*;
|
||||
mod event; pub use self::event::*;
|
||||
mod keys; pub use self::keys::*;
|
||||
mod buffer; pub use self::buffer::*;
|
||||
mod repeat; pub use self::repeat::*;
|
||||
mod scroll; pub use self::scroll::*;
|
||||
mod colors; pub use self::colors::*;
|
||||
mod phat; pub use self::phat::*;
|
||||
mod button; pub use self::button::*;
|
||||
|
||||
//use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
||||
//use rand::distributions::uniform::UniformSampler;
|
||||
pub(crate) use ::{
|
||||
std::{
|
||||
io::{stdout, Write},
|
||||
time::Duration,
|
||||
ops::{Deref, DerefMut},
|
||||
},
|
||||
ratatui::{
|
||||
prelude::{Style, Position, Backend, Color},
|
||||
style::{Modifier, Color::*},
|
||||
backend::{CrosstermBackend, ClearType},
|
||||
layout::{Size, Rect},
|
||||
buffer::{Buffer, Cell},
|
||||
crossterm::{
|
||||
ExecutableCommand,
|
||||
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
|
||||
//event::{poll, read, Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
|
||||
}
|
||||
},
|
||||
crossterm::event::{
|
||||
read, Event, KeyEvent, KeyModifiers, KeyCode, KeyEventKind, KeyEventState
|
||||
},
|
||||
};
|
||||
|
||||
impl Deref for Tui { type Target = Buffer; fn deref (&self) -> &Buffer { &self.0 } }
|
||||
impl DerefMut for Tui { fn deref_mut (&mut self) -> &mut Buffer { &mut self.0 } }
|
||||
impl AsMut<Buffer> for Tui { fn as_mut (&mut self) -> &mut Buffer { &mut self.0 } }
|
||||
impl Wide<u16> for Tui { fn w (&self) -> u16 { self.1.2 } }
|
||||
impl Tall<u16> for Tui { fn h (&self) -> u16 { self.1.3 } }
|
||||
impl HasOrigin for Tui { fn origin (&self) -> Azimuth { Azimuth::NW } }
|
||||
impl Xy<u16> for Tui { fn x (&self) -> u16 { self.1.0 } fn y (&self) -> u16 { self.1.1 } }
|
||||
/// Terminal output.
|
||||
pub struct Tui(
|
||||
/// Ratatui buffer; area is screen size
|
||||
pub Buffer,
|
||||
/// Current draw area
|
||||
pub XYWH<u16>
|
||||
);
|
||||
impl Tui {
|
||||
pub fn setup_panic () {
|
||||
use ::std::panic::{set_hook, PanicHookInfo};
|
||||
use ::better_panic::{Settings, Verbosity};
|
||||
let panic = Settings::auto()
|
||||
.verbosity(Verbosity::Full)
|
||||
.create_panic_handler();
|
||||
set_hook(Box::new(move |info: &PanicHookInfo|{
|
||||
let _ = Tui::teardown(&mut stdout());
|
||||
panic(info);
|
||||
}));
|
||||
}
|
||||
pub fn run_main <T> (state: Arc<RwLock<T>>) -> Usually<()> where
|
||||
T: View<Tui> + Apply<TuiEvent, Usually<()>> + Send + Sync + 'static
|
||||
{
|
||||
Exit::run(|exit|{
|
||||
let scan = Duration::from_millis(100);
|
||||
let frame = Duration::from_millis(10);
|
||||
let (_input, output) = Tui::io(exit.as_ref(), &state, scan, frame, std::io::stdout())?;
|
||||
let _ = output.join();
|
||||
Tui::teardown(&mut stdout())
|
||||
})
|
||||
}
|
||||
/// Spawn the TUI input and output threadsl.
|
||||
pub fn io <
|
||||
T: View<Tui> + Apply<TuiEvent, Usually<()>> + Send + Sync + 'static,
|
||||
W: Write + Send + Sync + 'static,
|
||||
> (
|
||||
exited: &Arc<AtomicBool>,
|
||||
state: &Arc<RwLock<T>>,
|
||||
poll: Duration,
|
||||
sleep: Duration,
|
||||
output: W,
|
||||
) -> Result<(Task, Task), Box<dyn std::error::Error>> {
|
||||
Ok((
|
||||
Tui::input(exited, state, poll)?,
|
||||
Tui::output(exited, state, sleep, output)?,
|
||||
))
|
||||
}
|
||||
/// Spawn the TUI input thread which reads keys from the terminal.
|
||||
pub fn input <T: Apply<TuiEvent, Usually<()>> + Send + Sync + 'static> (
|
||||
exited: &Arc<AtomicBool>, state: &Arc<RwLock<T>>, poll: Duration
|
||||
) -> Result<Task, std::io::Error> {
|
||||
let exited = exited.clone();
|
||||
let state = state.clone();
|
||||
Task::new_poll(exited.clone(), poll, move |_| {
|
||||
let event = read().unwrap();
|
||||
if Exit::is(&event) {
|
||||
exited.store(true, Relaxed);
|
||||
} else if let Err(e) = state.write().unwrap().apply(&TuiEvent(event)) {
|
||||
panic!("{e}")
|
||||
}
|
||||
})
|
||||
}
|
||||
pub fn teardown <W: Write> (backend: &mut W) -> Usually<()> {
|
||||
use ::ratatui::backend::Backend;
|
||||
stdout().execute(LeaveAlternateScreen)?;
|
||||
CrosstermBackend::new(backend).show_cursor()?;
|
||||
disable_raw_mode().map_err(Into::into)
|
||||
}
|
||||
pub fn new (width: u16, height: u16) -> Self {
|
||||
Self(Buffer::empty(Rect { x: 0, y: 0, width, height }), XYWH(0, 0, width, height))
|
||||
}
|
||||
pub fn resize <W: Write> (&mut self, back: &mut CrosstermBackend<W>, width: u16, height: u16) {
|
||||
let size = Rect { x: 0, y: 0, width, height };
|
||||
if self.0.area != size {
|
||||
back.clear_region(ClearType::All).unwrap();
|
||||
self.0.resize(size);
|
||||
self.0.reset();
|
||||
}
|
||||
}
|
||||
pub fn redraw <'b, W: Write> (
|
||||
&'b mut self,
|
||||
back: &mut CrosstermBackend<W>,
|
||||
mut next: &'b mut Self
|
||||
) {
|
||||
let updates = self.0.diff(&next.0);
|
||||
back.draw(updates.into_iter()).expect("failed to render");
|
||||
Backend::flush(back).expect("failed to flush output new");
|
||||
std::mem::swap(self, &mut next);
|
||||
next.0.reset();
|
||||
}
|
||||
pub fn update (&mut self, callback: &impl Fn(&mut Cell, u16, u16)) -> XYWH<u16> {
|
||||
for row in 0..self.h() {
|
||||
let y = self.y() + row;
|
||||
for col in 0..self.w() {
|
||||
let x = self.x() + col;
|
||||
if x < self.0.area.width && y < self.0.area.height {
|
||||
if let Some(cell) = self.0.cell_mut(Position { x, y }) {
|
||||
callback(cell, col, row);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.xywh()
|
||||
}
|
||||
pub fn blit (&mut self, text: &impl AsRef<str>, x: u16, y: u16, style: Option<Style>) {
|
||||
let text = text.as_ref();
|
||||
let style = style.unwrap_or(Style::default());
|
||||
if x < self.0.area.width && y < self.0.area.height {
|
||||
self.0.set_string(x, y, text, style);
|
||||
}
|
||||
}
|
||||
pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
|
||||
for cell in self.0.content.iter_mut() {
|
||||
cell.fg = fg;
|
||||
cell.bg = bg;
|
||||
cell.modifier = modifier;
|
||||
}
|
||||
}
|
||||
/// Spawn the TUI output thread which writes colored characters to the terminal.
|
||||
///
|
||||
/// ```
|
||||
/// let state = std::sync::Arc::new(std::sync::RwLock::new(()));
|
||||
/// let _ = tengri::Exit::run(|exit|{
|
||||
/// tengri::Tui::output(
|
||||
/// exit.as_ref(),
|
||||
/// &state,
|
||||
/// std::time::Duration::from_millis(10),
|
||||
/// std::io::stdout()
|
||||
/// )
|
||||
/// });
|
||||
/// ```
|
||||
pub fn output <
|
||||
W: Write + Send + Sync + 'static, T: View<Tui> + Send + Sync + 'static
|
||||
> (
|
||||
exited: &Arc<AtomicBool>,
|
||||
state: &Arc<RwLock<T>>,
|
||||
sleep: Duration,
|
||||
output: W,
|
||||
) -> Usually<Task> {
|
||||
let state = state.clone();
|
||||
stdout().execute(EnterAlternateScreen)?;
|
||||
CrosstermBackend::new(stdout()).hide_cursor()?;
|
||||
enable_raw_mode()?;
|
||||
let mut backend = CrosstermBackend::new(output);
|
||||
let Size { width, height } = backend.size().expect("get size failed");
|
||||
let mut prev = Tui::new(width, height);
|
||||
let mut next = Tui::new(width, height);
|
||||
Ok(Task::new_sleep(exited.clone(), sleep, move |perf| {
|
||||
let Size { width, height } = backend.size().expect("get size failed");
|
||||
if let Ok(state) = state.try_read() {
|
||||
prev.resize(&mut backend, width, height);
|
||||
state.view().draw(&mut next).expect("draw failed"); // TODO draw error
|
||||
prev.redraw(&mut backend, &mut next);
|
||||
}
|
||||
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
|
||||
prev.set_string(0, 0, &timer, Style::default());
|
||||
})?)
|
||||
}
|
||||
/// Draw TUI content or its error message.
|
||||
///
|
||||
/// ```
|
||||
/// for variant in [
|
||||
/// Ok(Some("hello")),
|
||||
/// Ok(None),
|
||||
/// Err("fail".into()),
|
||||
/// ] {
|
||||
/// let _ = tengri::Tui::catcher(variant);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn catcher <T: Draw<Tui>> (result: Usually<T>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|match result {
|
||||
Ok(content) => content.draw(to),
|
||||
Err(e) => {
|
||||
let err_fg = Color::Rgb(255,224,244);
|
||||
let err_bg = Color::Rgb(96, 24, 24);
|
||||
let title = east(bold(true, "upsi daisy. "), "rendering failed.");
|
||||
let error = east("\"why?\" ", bold(true, format!("{e}")));
|
||||
fg(err_fg, bg(err_bg, south(title, error))).draw(to)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Screen for Tui {
|
||||
type Unit = u16;
|
||||
/// Render drawable in subarea specified by `area`
|
||||
fn show (&mut self, content: impl Draw<Self>) -> Perhaps<XYWH<u16>> {
|
||||
let previous_area = self.1;
|
||||
Ok(if let Some(area) = content.layout(self.1)? {
|
||||
self.1 = area;
|
||||
if let Some(result_area) = content.draw(self)? {
|
||||
self.1 = previous_area;
|
||||
Some(result_area)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
/// Get current clipping area
|
||||
fn area (&self) -> XYWH<Self::Unit> {
|
||||
self.1
|
||||
}
|
||||
|
||||
fn clip <T> (
|
||||
&mut self,
|
||||
area: impl Into<Option<XYWH<u16>>>,
|
||||
draw: impl FnOnce(&mut Self)->T
|
||||
) -> T {
|
||||
let prev = self.1;
|
||||
if let Some(area) = area.into() {
|
||||
self.1 = area.into();
|
||||
}
|
||||
let result = draw(self);
|
||||
self.1 = prev;
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn fill_char (c: char) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|Ok(Some(to.update(&|cell,_,_|{
|
||||
cell.set_char(c);
|
||||
}))))
|
||||
}
|
||||
|
||||
/// Draw contents with modifier applied.
|
||||
pub const fn modify (on: bool, modifier: Modifier, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
fill_mod(on, modifier).draw(to)?;
|
||||
draw.draw(to)
|
||||
})
|
||||
}
|
||||
|
||||
pub const fn fill_mod (on: bool, modifier: Modifier) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|Ok(Some({
|
||||
if on {
|
||||
to.update(&|cell,_,_|cell.modifier.insert(modifier))
|
||||
} else {
|
||||
to.update(&|cell,_,_|cell.modifier.remove(modifier))
|
||||
}
|
||||
})))
|
||||
}
|
||||
|
||||
/// Draw contents with bold modifier applied.
|
||||
pub const fn bold (on: bool, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
modify(on, Modifier::BOLD, draw)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
use super::*;
|
||||
use super::{*, draw::*};
|
||||
|
||||
/// Draw border around item shrunk by 1 on each side.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
use crate::lang::*;
|
||||
use ::ratatui::buffer::Cell;
|
||||
|
||||
/// TUI buffer sized by `usize` instead of `u16`.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
use crate::*;
|
||||
|
||||
use super::{*, draw::*, Color::*};
|
||||
|
||||
/// ```
|
||||
/// let _ = tengri::button_2("", "", true);
|
||||
|
|
|
|||
|
|
@ -1,83 +0,0 @@
|
|||
use crate::*;
|
||||
use ratatui::prelude::Color;
|
||||
use dizzle::{Expression, LanguageError::*};
|
||||
|
||||
pub trait ColorDsl<T>: Sized {
|
||||
fn new_g (expr: T, try_to_u8: impl Fn(Perhaps<&str>)->Perhaps<u8>) -> Usually<Self>;
|
||||
fn new_rgb (expr: T, try_to_u8: impl Fn(Perhaps<&str>)->Perhaps<u8>) -> Usually<Self>;
|
||||
}
|
||||
|
||||
impl<T: Expression> ColorDsl<T> for Color {
|
||||
fn new_g (expr: T, try_to_u8: impl Fn(Perhaps<&str>)->Perhaps<u8>) -> Usually<Self> {
|
||||
let n = try_to_u8(expr.tail().map_err(Into::into))?.ok_or(Domain("not gray"))?;
|
||||
Ok(Self::Rgb(n, n, n))
|
||||
}
|
||||
fn new_rgb (expr: T, try_to_u8: impl Fn(Perhaps<&str>)->Perhaps<u8>) -> Usually<Self> {
|
||||
let r = try_to_u8(expr.tail().map_err(Into::into))?
|
||||
.ok_or(Domain("not red"))?;
|
||||
let g = try_to_u8(expr.tail().tail().head().map_err(Into::into))?
|
||||
.ok_or(Domain("not green"))?;
|
||||
let b = try_to_u8(expr.tail().tail().tail().head().map_err(Into::into))?
|
||||
.ok_or(Domain("not blue"))?;
|
||||
Ok(Color::Rgb(r, g, b))
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply foreground color.
|
||||
pub const fn fg (fg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
to.update(&|cell,_,_|{ cell.set_fg(fg); });
|
||||
draw.draw(to)
|
||||
})
|
||||
}
|
||||
|
||||
/// Apply background color.
|
||||
pub const fn bg (bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
to.update(&|cell,_,_|{ cell.set_bg(bg); });
|
||||
draw.draw(to)
|
||||
})
|
||||
}
|
||||
|
||||
pub const fn fg_bg (fg: Color, bg: Color, draw: impl Draw<Tui>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|{
|
||||
to.update(&|cell,_,_|{ cell.set_fg(fg); cell.set_bg(bg); });
|
||||
draw.draw(to)
|
||||
})
|
||||
}
|
||||
|
||||
pub const fn fill_ul (color: Option<Color>) -> impl Draw<Tui> {
|
||||
thunk(move|to: &mut Tui|Ok(Some(if let Some(color) = color {
|
||||
to.update(&|cell,_,_|{
|
||||
cell.modifier.insert(Modifier::UNDERLINED);
|
||||
cell.underline_color = color;
|
||||
})
|
||||
} else {
|
||||
to.update(&|cell,_,_|{
|
||||
cell.modifier.remove(Modifier::UNDERLINED);
|
||||
cell.underline_color = Reset;
|
||||
})
|
||||
})))
|
||||
}
|
||||
|
||||
pub const fn tui_color_bg () -> Color { Color::Rgb(28, 35, 25) }
|
||||
pub const fn tui_bg0 () -> Color { Color::Rgb(20, 20, 20) }
|
||||
pub const fn tui_bo1 () -> Color { Color::Rgb(100, 110, 40) }
|
||||
pub const fn tui_bo2 () -> Color { Color::Rgb(70, 80, 50) }
|
||||
pub const fn tui_border_bg () -> Color { Color::Rgb(40, 50, 30) }
|
||||
pub const fn tui_border_fg (f: bool) -> Color { if f { tui_bo1() } else { tui_bo2() } }
|
||||
pub const fn tui_brown () -> Color { Color::Rgb(128,255,0) }
|
||||
pub const fn tui_electric () -> Color { Color::Rgb(0,255,128) }
|
||||
pub const fn tui_g (g: u8) -> Color { Color::Rgb(g, g, g) }
|
||||
pub const fn tui_green () -> Color { Color::Rgb(0,255,0) }
|
||||
pub const fn tui_mode_bg () -> Color { Color::Rgb(150, 160, 90) }
|
||||
pub const fn tui_mode_fg () -> Color { Color::Rgb(255, 255, 255) }
|
||||
pub const fn tui_null () -> Color { Color::Reset }
|
||||
pub const fn tui_orange () -> Color { Color::Rgb(255,128,0) }
|
||||
pub const fn tui_red () -> Color { Color::Rgb(255,0, 0) }
|
||||
pub const fn tui_separator_fg (_: bool) -> Color { Color::Rgb(0, 0, 0) }
|
||||
pub const fn tui_status_bar_bg () -> Color { Color::Rgb(28, 35, 25) }
|
||||
pub const fn tui_ti1 () -> Color { Color::Rgb(150, 160, 90) }
|
||||
pub const fn tui_ti2 () -> Color { Color::Rgb(120, 130, 100) }
|
||||
pub const fn tui_title_fg (f: bool) -> Color { if f { tui_ti1() } else { tui_ti2() } }
|
||||
pub const fn tui_yellow () -> Color { Color::Rgb(255,255,0) }
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
use ::dizzle::impl_from;
|
||||
use ::crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers};
|
||||
|
||||
/// TUI input loop event.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
use super::{*, draw::*, Color::*};
|
||||
|
||||
/// Stackably padded.
|
||||
///
|
||||
/// ```
|
||||
|
|
@ -9,13 +11,16 @@ pub fn phat (w: u16, h: u16, [fg, bg, hi, lo]: [Color;4], draw: impl Draw<Tui>)
|
|||
let draw = fg_bg(fg, bg, draw);
|
||||
south(top, north(low, draw)).min_wh(w, h)
|
||||
}
|
||||
use super::*;
|
||||
|
||||
pub const LO: &'static str = "▄";
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::*;
|
||||
use super::{*, draw::*};
|
||||
use ratatui::{prelude::{Position}};
|
||||
|
||||
pub const fn x_repeat (c: &str) -> impl Draw<Tui> {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use crate::*;
|
||||
use super::{*, draw::*, Color::*};
|
||||
use ratatui::{prelude::{Position}};
|
||||
|
||||
pub const ICON_DEC_V: &[char] = &['▲'];
|
||||
|
|
|
|||
124
src/text.rs
124
src/text.rs
|
|
@ -1,124 +0,0 @@
|
|||
#![allow(unused)]
|
||||
|
||||
use crate::*;
|
||||
pub(crate) use ::unicode_width::*;
|
||||
|
||||
#[cfg(feature = "term")] mod impl_term {
|
||||
use super::*;
|
||||
use ratatui::prelude::Position;
|
||||
|
||||
impl_draw!(|self: String, to: Tui|{self.as_str().draw(to)});
|
||||
impl_draw!(|self: std::sync::Arc<str>, to: Tui|{self.as_ref().draw(to)});
|
||||
impl_draw!(|self: &std::sync::Arc<str>, to: Tui|{self.as_ref().draw(to)});
|
||||
impl Draw<Tui> for &str {
|
||||
fn layout (&self, area: XYWH<u16>) -> Perhaps<XYWH<u16>> {
|
||||
let XYWH(x, y, ..) = area;
|
||||
let mut max_w = 0u16;
|
||||
let mut max_h = 0u16;
|
||||
for line in self.split("\n") {
|
||||
max_h += 1;
|
||||
max_w = max_w.max(line.len() as u16);
|
||||
}
|
||||
Ok(Some(XYWH(x, y, max_w, max_h)))
|
||||
}
|
||||
fn draw (self, to: &mut Tui) -> Drawn<u16> {
|
||||
let area = self.layout(to.area())?.unwrap();
|
||||
////let info = format!("{area:?}");
|
||||
//to.text(&self, area.0, area.1, self.len() as u16)
|
||||
for (index, line) in self.split("\n").enumerate() {
|
||||
let _ = to.text(&line, area.0, area.1 + index as u16, width_chars_max(area.2, line) as u16)?;
|
||||
}
|
||||
Ok(Some(area))
|
||||
}
|
||||
}
|
||||
|
||||
impl_draw!(<T: AsRef<str>,>|self: TrimString<T>, to: Tui|{self.as_ref().draw(to)});
|
||||
impl_draw!(<T: AsRef<str>,>|self: TrimStringRef<'_, T>, to: Tui|{
|
||||
let XYWH(x, y, w, ..) = to.1;
|
||||
let mut width: u16 = 1;
|
||||
let mut chars = self.1.as_ref().chars();
|
||||
while let Some(c) = chars.next() {
|
||||
if width > self.0 || width > w {
|
||||
break
|
||||
}
|
||||
let pos = Position { x: x + width - 1, y };
|
||||
if let Some(cell) = to.0.cell_mut(pos) {
|
||||
cell.set_char(c);
|
||||
}
|
||||
width += c.width().unwrap_or(0) as u16;
|
||||
}
|
||||
let XYWH(x, y, w, ..) = XYWH(to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h());
|
||||
to.text(&self.as_ref(), x, y, w)
|
||||
});
|
||||
|
||||
impl Tui {
|
||||
/// Write a line of text
|
||||
///
|
||||
/// TODO: do a paragraph (handle newlines)
|
||||
pub fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16) -> Perhaps<XYWH<u16>> {
|
||||
let text = text.as_ref();
|
||||
let mut string_width: u16 = 0;
|
||||
for character in text.chars() {
|
||||
let x = x0 + string_width;
|
||||
let character_width = character.width().unwrap_or(0) as u16;
|
||||
string_width += character_width;
|
||||
if string_width > max_width {
|
||||
break
|
||||
}
|
||||
if let Some(cell) = self.0.cell_mut(ratatui::prelude::Position { x, y }) {
|
||||
cell.set_char(character);
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
Ok(Some(XYWH(x0, y, string_width, 1)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trim string with [unicode_width].
|
||||
pub fn trim_string (max_width: usize, input: impl AsRef<str>) -> String {
|
||||
let input = input.as_ref();
|
||||
let mut output = Vec::with_capacity(input.len());
|
||||
let mut width: usize = 1;
|
||||
let mut chars = input.chars();
|
||||
while let Some(c) = chars.next() {
|
||||
if width > max_width {
|
||||
break
|
||||
}
|
||||
output.push(c);
|
||||
width += c.width().unwrap_or(0);
|
||||
}
|
||||
return output.into_iter().collect()
|
||||
}
|
||||
|
||||
/// Displays an owned [str]-like with fixed maximum width.
|
||||
///
|
||||
/// Width is computed using [unicode_width].
|
||||
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
|
||||
impl<T: AsRef<str>> AsRef<str> for TrimString<T> { fn as_ref (&self) -> &str { self.1.as_ref() } }
|
||||
impl<'a, T: AsRef<str>> TrimString<T> {
|
||||
fn to_ref (&self) -> TrimStringRef<'_, T> {
|
||||
TrimStringRef(self.0, &self.1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays a borrowed [str]-like with fixed maximum width
|
||||
///
|
||||
/// Width is computed using [unicode_width].
|
||||
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
|
||||
impl<T: AsRef<str>> AsRef<str> for TrimStringRef<'_, T> {
|
||||
fn as_ref (&self) -> &str { self.1.as_ref() }
|
||||
}
|
||||
|
||||
pub(crate) fn width_chars_max (max: u16, text: impl AsRef<str>) -> u16 {
|
||||
let mut width: u16 = 0;
|
||||
let mut chars = text.as_ref().chars();
|
||||
while let Some(c) = chars.next() {
|
||||
width += c.width().unwrap_or(0) as u16;
|
||||
if width > max {
|
||||
break
|
||||
}
|
||||
}
|
||||
return width
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue