unify some modules and implement edn_command for sampler

This commit is contained in:
🪞👃🪞 2025-01-17 00:11:49 +01:00
parent 3a6202464c
commit d4f962fbfa
19 changed files with 1008 additions and 1084 deletions

View file

@ -1,35 +1,32 @@
pub use ::tek_input;
pub(crate) use tek_input::*;
pub use ::tek_output;
pub(crate) use tek_output::*;
pub use ::tek_edn;
pub(crate) use ::tek_edn::*;
pub use ::tek_time;
pub(crate) use ::tek_time::*;
mod tui_engine; pub use self::tui_engine::*;
mod tui_buffer; pub use self::tui_buffer::*;
mod tui_color; pub use self::tui_color::*;
mod tui_content; pub use self::tui_content::*;
mod tui_engine; pub use self::tui_engine::*;
mod tui_file; pub use self::tui_file::*;
mod tui_input; pub use self::tui_input::*;
mod tui_output; pub use self::tui_output::*;
mod tui_run; pub use self::tui_run::*;
mod tui_color; pub use self::tui_color::*;
mod tui_style; pub use self::tui_style::*;
mod tui_theme; pub use self::tui_theme::*;
mod tui_border; pub use self::tui_border::*;
mod tui_field; pub use self::tui_field::*;
mod tui_buffer; pub use self::tui_buffer::*;
mod tui_file; pub use self::tui_file::*;
pub use ::tek_edn; pub(crate) use ::tek_edn::*;
pub use ::tek_time; pub(crate) use ::tek_time::*;
pub use ::tek_input; pub(crate) use tek_input::*;
pub use ::tek_output; pub(crate) use tek_output::*;
pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity};
pub use ::palette; pub(crate) use ::palette::{*, convert::*, okhsl::*};
pub use ::crossterm; pub(crate) use crossterm::{
ExecutableCommand,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
};
pub use ::ratatui; pub(crate) use ratatui::{
prelude::{Color, Style, Buffer},
style::Modifier,
backend::{Backend, CrosstermBackend, ClearType},
layout::{Size, Rect},
buffer::Cell
};
pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}};
pub(crate) use std::io::{stdout, Stdout};
pub(crate) use std::path::PathBuf;
pub(crate) use std::ffi::OsString;
/// Prototypal case of implementor macro.
/// Saves 4loc per data pats.
#[macro_export] macro_rules! from {
($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => {
impl $(<$($lt),+>)? From<$Source> for $Target {
@ -37,26 +34,6 @@ pub(crate) use std::ffi::OsString;
}
};
}
pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity};
pub use ::palette; pub(crate) use ::palette::{*, convert::*, okhsl::*};
pub use ::crossterm;
pub(crate) use crossterm::{
ExecutableCommand,
terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode},
event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventKind, KeyEventState},
};
pub use ::ratatui;
pub(crate) use ratatui::{
prelude::{Color, Style, Buffer},
style::Modifier,
backend::{Backend, CrosstermBackend, ClearType},
layout::{Size, Rect},
buffer::Cell
};
#[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> {
use crate::*;
use std::sync::{Arc, RwLock};

View file

@ -1,253 +0,0 @@
use crate::*;
pub struct Bordered<S: BorderStyle, W: Content<TuiOut>>(pub bool, pub S, pub W);
content!(TuiOut: |self: Bordered<S: BorderStyle, W: Content<TuiOut>>|Fill::xy(
lay!(When::new(self.0, Border(self.0, self.1)), Padding::xy(1, 1, &self.2))
));
pub struct Border<S: BorderStyle>(pub bool, pub S);
render!(TuiOut: |self: Border<S: BorderStyle>, to| {
if self.0 {
let area = to.area();
if area.w() > 0 && area.y() > 0 {
to.blit(&self.1.nw(), area.x(), area.y(), self.1.style());
to.blit(&self.1.ne(), area.x() + area.w() - 1, area.y(), self.1.style());
to.blit(&self.1.sw(), area.x(), area.y() + area.h() - 1, self.1.style());
to.blit(&self.1.se(), area.x() + area.w() - 1, area.y() + area.h() - 1, self.1.style());
for x in area.x()+1..area.x()+area.w()-1 {
to.blit(&self.1.n(), x, area.y(), self.1.style());
to.blit(&self.1.s(), x, area.y() + area.h() - 1, self.1.style());
}
for y in area.y()+1..area.y()+area.h()-1 {
to.blit(&self.1.w(), area.x(), y, self.1.style());
to.blit(&self.1.e(), area.x() + area.w() - 1, y, self.1.style());
}
}
}
});
pub trait BorderStyle: Send + Sync + Copy {
fn enabled (&self) -> bool;
fn enclose <W: Content<TuiOut>> (self, w: W) -> impl Content<TuiOut> {
Bsp::b(Fill::xy(Border(self.enabled(), self)), w)
}
fn enclose2 <W: Content<TuiOut>> (self, w: W) -> impl Content<TuiOut> {
Bsp::b(Margin::xy(1, 1, Fill::xy(Border(self.enabled(), self))), w)
}
fn enclose_bg <W: Content<TuiOut>> (self, w: W) -> impl Content<TuiOut> {
Tui::bg(self.style().unwrap().bg.unwrap_or(Color::Reset),
Bsp::b(Fill::xy(Border(self.enabled(), self)), w))
}
const NW: &'static str = "";
const N: &'static str = "";
const NE: &'static str = "";
const E: &'static str = "";
const SE: &'static str = "";
const S: &'static str = "";
const SW: &'static str = "";
const W: &'static str = "";
const N0: &'static str = "";
const S0: &'static str = "";
const W0: &'static str = "";
const E0: &'static str = "";
fn n (&self) -> &str { Self::N }
fn s (&self) -> &str { Self::S }
fn e (&self) -> &str { Self::E }
fn w (&self) -> &str { Self::W }
fn nw (&self) -> &str { Self::NW }
fn ne (&self) -> &str { Self::NE }
fn sw (&self) -> &str { Self::SW }
fn se (&self) -> &str { Self::SE }
#[inline] fn draw <'a> (
&self, to: &mut TuiOut
) -> Usually<()> {
if self.enabled() {
self.draw_horizontal(to, None)?;
self.draw_vertical(to, None)?;
self.draw_corners(to, None)?;
}
Ok(())
}
#[inline] fn draw_horizontal (
&self, to: &mut TuiOut, style: Option<Style>
) -> Usually<[u16;4]> {
let area = to.area();
let style = style.or_else(||self.style_horizontal());
let [x, x2, y, y2] = area.lrtb();
for x in x..x2.saturating_sub(1) {
to.blit(&Self::N, x, y, style);
to.blit(&Self::S, x, y2.saturating_sub(1), style)
}
Ok(area)
}
#[inline] fn draw_vertical (
&self, to: &mut TuiOut, style: Option<Style>
) -> Usually<[u16;4]> {
let area = to.area();
let style = style.or_else(||self.style_vertical());
let [x, x2, y, y2] = area.lrtb();
let h = y2 - y;
if h > 1 {
for y in y..y2.saturating_sub(1) {
to.blit(&Self::W, x, y, style);
to.blit(&Self::E, x2.saturating_sub(1), y, style);
}
} else if h > 0 {
to.blit(&Self::W0, x, y, style);
to.blit(&Self::E0, x2.saturating_sub(1), y, style);
}
Ok(area)
}
#[inline] fn draw_corners (
&self, to: &mut TuiOut, style: Option<Style>
) -> Usually<[u16;4]> {
let area = to.area();
let style = style.or_else(||self.style_corners());
let [x, y, width, height] = area.xywh();
if width > 1 && height > 1 {
to.blit(&Self::NW, x, y, style);
to.blit(&Self::NE, x + width - 1, y, style);
to.blit(&Self::SW, x, y + height - 1, style);
to.blit(&Self::SE, x + width - 1, y + height - 1, style);
}
Ok(area)
}
#[inline] fn style (&self) -> Option<Style> { None }
#[inline] fn style_horizontal (&self) -> Option<Style> { self.style() }
#[inline] fn style_vertical (&self) -> Option<Style> { self.style() }
#[inline] fn style_corners (&self) -> Option<Style> { self.style() }
}
macro_rules! border {
($($T:ident {
$nw:literal $n:literal $ne:literal $w:literal $e:literal $sw:literal $s:literal $se:literal
$($x:tt)*
}),+) => {$(
impl BorderStyle for $T {
const NW: &'static str = $nw;
const N: &'static str = $n;
const NE: &'static str = $ne;
const W: &'static str = $w;
const E: &'static str = $e;
const SW: &'static str = $sw;
const S: &'static str = $s;
const SE: &'static str = $se;
$($x)*
fn enabled (&self) -> bool { self.0 }
}
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
impl Content<TuiOut> for $T {
fn render (&self, to: &mut TuiOut) {
if self.enabled() { let _ = self.draw(to); }
}
}
)+}
}
border! {
Square {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.1) }
},
SquareBold {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.1) }
},
TabLike {
"" "" ""
"" ""
"" " " "" fn style (&self) -> Option<Style> { Some(self.1) }
},
Lozenge {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.1) }
},
Brace {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.1) }
},
LozengeDotted {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.1) }
},
Quarter {
"" "" "🮇"
"" "🮇"
"" "" "🮇" fn style (&self) -> Option<Style> { Some(self.1) }
},
QuarterV {
"" "" "🮇"
"" "🮇"
"" "" "🮇" fn style (&self) -> Option<Style> { Some(self.1) }
},
Chamfer {
"🭂" "" "🭍"
"" "🮇"
"🭓" "" "🭞" fn style (&self) -> Option<Style> { Some(self.1) }
},
Corners {
"🬆" "" "🬊" // 🬴 🬸
"" ""
"🬱" "" "🬵" fn style (&self) -> Option<Style> { Some(self.1) }
},
CornersTall {
"🭽" "" "🭾"
"" ""
"🭼" "" "🭿" fn style (&self) -> Option<Style> { Some(self.1) }
},
Outer {
"🭽" "" "🭾"
"" ""
"🭼" "" "🭿"
const W0: &'static str = "[";
const E0: &'static str = "]";
const N0: &'static str = "";
const S0: &'static str = "";
fn style (&self) -> Option<Style> { Some(self.1) }
},
Phat {
"" "" ""
"" ""
"" "" ""
fn style (&self) -> Option<Style> { Some(self.1) }
},
Rugged {
"" "" ""
"" ""
"" "🮂" ""
fn style (&self) -> Option<Style> { Some(self.1) }
},
Skinny {
"" "" ""
"" ""
"" "" ""
fn style (&self) -> Option<Style> { Some(self.1) }
},
Brackets {
"" "" ""
"" ""
"" "" ""
const W0: &'static str = "[";
const E0: &'static str = "]";
const N0: &'static str = "";
const S0: &'static str = "";
fn style (&self) -> Option<Style> { Some(self.1) }
},
Reticle {
"" "" ""
"" ""
"" "" ""
const W0: &'static str = "";
const E0: &'static str = "";
const N0: &'static str = "";
const S0: &'static str = "";
fn style (&self) -> Option<Style> { Some(self.1) }
}
}

View file

@ -1,5 +1,4 @@
use crate::*;
pub fn buffer_update (buf: &mut Buffer, area: [u16;4], callback: &impl Fn(&mut Cell, u16, u16)) {
for row in 0..area.h() {
let y = area.y() + row;
@ -11,14 +10,11 @@ pub fn buffer_update (buf: &mut Buffer, area: [u16;4], callback: &impl Fn(&mut C
}
}
}
#[derive(Default)]
pub struct BigBuffer {
#[derive(Default)] pub struct BigBuffer {
pub width: usize,
pub height: usize,
pub content: Vec<Cell>
}
impl BigBuffer {
pub fn new (width: usize, height: usize) -> Self {
Self { width, height, content: vec![Cell::default(); width*height] }
@ -35,5 +31,4 @@ impl BigBuffer {
y * self.width + x
}
}
from!(|size:(usize, usize)| BigBuffer = Self::new(size.0, size.1));

View file

@ -1,10 +1,6 @@
use crate::*;
use rand::{thread_rng, distributions::uniform::UniformSampler};
pub trait HasColor {
fn color (&self) -> ItemColor;
}
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),*>)? {
@ -12,16 +8,13 @@ pub trait HasColor {
}
}
}
/// A color in OKHSL and RGB representations.
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct ItemColor {
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor {
pub okhsl: Okhsl<f32>,
pub rgb: Color,
}
/// A color in OKHSL and RGB with lighter and darker variants.
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub struct ItemPalette {
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemPalette {
pub base: ItemColor,
pub light: ItemColor,
pub lighter: ItemColor,
@ -62,7 +55,6 @@ from!(|base: ItemColor|ItemPalette = {
lighter.lightness = (lighter.lightness * 1.3).min(Okhsl::<f32>::max_lightness());
let mut lightest = base.okhsl;
lightest.lightness = 0.95;
let mut dark = base.okhsl;
dark.lightness = (dark.lightness * 0.75).max(Okhsl::<f32>::min_lightness());
dark.saturation = (dark.saturation * 0.75).max(Okhsl::<f32>::min_saturation());
@ -72,7 +64,6 @@ from!(|base: ItemColor|ItemPalette = {
let mut darkest = darker;
darkest.lightness = 0.1;
darkest.saturation = (darkest.saturation * 0.50).max(Okhsl::<f32>::min_saturation());
Self {
base,
light: light.into(),
@ -84,9 +75,7 @@ from!(|base: ItemColor|ItemPalette = {
}
});
impl ItemPalette {
pub fn random () -> Self {
ItemColor::random().into()
}
pub fn random () -> Self { ItemColor::random().into() }
pub fn random_near (color: Self, distance: f32) -> Self {
color.base.mix(ItemColor::random(), distance).into()
}

View file

@ -29,6 +29,101 @@ impl Content<TuiOut> for Arc<str> {
}
}
pub struct Field<T, U>(pub ItemPalette, pub T, pub U)
where T: AsRef<str> + Send + Sync, U: AsRef<str> + Send + Sync;
impl<T, U> Content<TuiOut> for Field<T, U>
where T: AsRef<str> + Send + Sync, U: AsRef<str> + Send + Sync
{
fn content (&self) -> impl Render<TuiOut> {
let ItemPalette { darkest, dark, lighter, lightest, .. } = self.0;
row!(
Tui::fg_bg(dark.rgb, darkest.rgb, ""),
Tui::fg_bg(lighter.rgb, dark.rgb, Tui::bold(true, format!("{}", self.1.as_ref()))),
Tui::fg_bg(dark.rgb, darkest.rgb, ""),
Tui::fg_bg(lightest.rgb, darkest.rgb, format!("{} ", self.2.as_ref()))
)
}
}
pub struct FieldV<T, U>(pub ItemPalette, pub T, pub U)
where T: AsRef<str> + Send + Sync, U: AsRef<str> + Send + Sync;
impl<T, U> Content<TuiOut> for FieldV<T, U>
where T: AsRef<str> + Send + Sync, U: AsRef<str> + Send + Sync
{
fn content (&self) -> impl Render<TuiOut> {
let ItemPalette { darkest, dark, lighter, lightest, .. } = self.0;
let sep1 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, ""));
let sep2 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, ""));
let name = Tui::bg(dark.rgb, Tui::fg(lighter.rgb,
Tui::bold(true, format!("{}", self.1.as_ref()))));
let value = Tui::bg(darkest.rgb, Tui::fg(lightest.rgb,
format!(" {} ", self.2.as_ref())));
Bsp::e(Bsp::s(row!(sep1, name, sep2), value), " ")
}
}
#[derive(Copy,Clone)]
pub struct TuiTheme;
impl Theme for TuiTheme {}
pub trait Theme {
const HOTKEY_FG: Color = Color::Rgb(255, 255, 0);
fn null () -> Color {
Color::Reset
}
fn bg0 () -> Color {
Color::Rgb(20, 20, 20)
}
fn bg () -> Color {
Color::Rgb(28, 35, 25)
}
fn border_bg () -> Color {
Color::Rgb(40, 50, 30)
}
fn border_fg (focused: bool) -> Color {
if focused { Self::bo1() } else { Self::bo2() }
}
fn title_fg (focused: bool) -> Color {
if focused { Self::ti1() } else { Self::ti2() }
}
fn separator_fg (_: bool) -> Color {
Color::Rgb(0, 0, 0)
}
fn mode_bg () -> Color {
Color::Rgb(150, 160, 90)
}
fn mode_fg () -> Color {
Color::Rgb(255, 255, 255)
}
fn status_bar_bg () -> Color {
Color::Rgb(28, 35, 25)
}
fn bo1 () -> Color {
Color::Rgb(100, 110, 40)
}
fn bo2 () -> Color {
Color::Rgb(70, 80, 50)
}
fn ti1 () -> Color {
Color::Rgb(150, 160, 90)
}
fn ti2 () -> Color {
Color::Rgb(120, 130, 100)
}
fn orange () -> Color {
Color::Rgb(255,128,0)
}
fn yellow () -> Color {
Color::Rgb(255,255,0)
}
fn g (g: u8) -> Color {
Color::Rgb(g, g, g)
}
}
pub struct Repeat<'a>(pub &'a str);
impl Content<TuiOut> for Repeat<'_> {
@ -122,3 +217,338 @@ pub fn phat_sel_3 <T: Content<TuiOut>> (
)
)
}
pub trait TuiStyle {
fn fg <R: Content<TuiOut>> (color: Color, w: R) -> Foreground<R> {
Foreground(color, w)
}
fn bg <R: Content<TuiOut>> (color: Color, w: R) -> Background<R> {
Background(color, w)
}
fn fg_bg <R: Content<TuiOut>> (fg: Color, bg: Color, w: R) -> Background<Foreground<R>> {
Background(bg, Foreground(fg, w))
}
fn bold <R: Content<TuiOut>> (enable: bool, w: R) -> Bold<R> {
Bold(enable, w)
}
fn border <R: Content<TuiOut>, S: BorderStyle> (enable: bool, style: S, w: R) -> Bordered<S, R> {
Bordered(enable, style, w)
}
}
impl TuiStyle for Tui {}
pub struct Bold<R: Content<TuiOut>>(pub bool, R);
impl<R: Content<TuiOut>> Content<TuiOut> for Bold<R> {
fn content (&self) -> impl Render<TuiOut> { &self.1 }
fn render (&self, to: &mut TuiOut) {
to.fill_bold(to.area(), self.0);
self.1.render(to)
}
}
pub struct Foreground<R: Content<TuiOut>>(pub Color, R);
impl<R: Content<TuiOut>> Content<TuiOut> for Foreground<R> {
fn content (&self) -> impl Render<TuiOut> { &self.1 }
fn render (&self, to: &mut TuiOut) {
to.fill_fg(to.area(), self.0);
self.1.render(to)
}
}
pub struct Background<R: Content<TuiOut>>(pub Color, R);
impl<R: Content<TuiOut>> Content<TuiOut> for Background<R> {
fn content (&self) -> impl Render<TuiOut> { &self.1 }
fn render (&self, to: &mut TuiOut) {
to.fill_bg(to.area(), self.0);
self.1.render(to)
}
}
pub struct Styled<R: Content<TuiOut>>(pub Option<Style>, pub R);
impl<R: Content<TuiOut>> Content<TuiOut> for Styled<R> {
fn content (&self) -> impl Render<TuiOut> { &self.1 }
fn render (&self, to: &mut TuiOut) {
to.place(self.content().layout(to.area()), &self.content());
// TODO write style over area
}
}
//pub trait TuiStyle: Render<TuiOut> + Sized {
//fn fg (self, color: Color) -> impl Render<TuiOut> {
//Layers::new(move |add|{ add(&Foreground(color))?; add(&self) })
//}
//fn bg (self, color: Color) -> impl Render<TuiOut> {
//Layers::new(move |add|{ add(&Background(color))?; add(&self) })
//}
//fn bold (self, on: bool) -> impl Render<TuiOut> {
//Layers::new(move |add|{ add(&Bold(on))?; add(&self) })
//}
//fn border <S: BorderStyle> (self, style: S) -> impl Render<TuiOut> {
//Bordered(style, self)
//}
//}
//impl<R: Content<TuiOut>> TuiStyle for R {}
//impl<S: BorderStyle> Content<TuiOut> for Border<S> {
//}
//impl<S: BorderStyle, R: Content<TuiOut>> Content<TuiOut> for Bordered<S, R> {
//fn content (&self) -> impl Render<TuiOut> {
//let content: &dyn Content<TuiOut> = &self.1;
//lay! { content.padding_xy(1, 1), Border(self.0) }.fill_xy()
//}
//}
pub struct Bordered<S: BorderStyle, W: Content<TuiOut>>(pub bool, pub S, pub W);
content!(TuiOut: |self: Bordered<S: BorderStyle, W: Content<TuiOut>>|Fill::xy(
lay!(When::new(self.0, Border(self.0, self.1)), Padding::xy(1, 1, &self.2))
));
pub struct Border<S: BorderStyle>(pub bool, pub S);
render!(TuiOut: |self: Border<S: BorderStyle>, to| {
if self.0 {
let area = to.area();
if area.w() > 0 && area.y() > 0 {
to.blit(&self.1.nw(), area.x(), area.y(), self.1.style());
to.blit(&self.1.ne(), area.x() + area.w() - 1, area.y(), self.1.style());
to.blit(&self.1.sw(), area.x(), area.y() + area.h() - 1, self.1.style());
to.blit(&self.1.se(), area.x() + area.w() - 1, area.y() + area.h() - 1, self.1.style());
for x in area.x()+1..area.x()+area.w()-1 {
to.blit(&self.1.n(), x, area.y(), self.1.style());
to.blit(&self.1.s(), x, area.y() + area.h() - 1, self.1.style());
}
for y in area.y()+1..area.y()+area.h()-1 {
to.blit(&self.1.w(), area.x(), y, self.1.style());
to.blit(&self.1.e(), area.x() + area.w() - 1, y, self.1.style());
}
}
}
});
pub trait BorderStyle: Send + Sync + Copy {
fn enabled (&self) -> bool;
fn enclose <W: Content<TuiOut>> (self, w: W) -> impl Content<TuiOut> {
Bsp::b(Fill::xy(Border(self.enabled(), self)), w)
}
fn enclose2 <W: Content<TuiOut>> (self, w: W) -> impl Content<TuiOut> {
Bsp::b(Margin::xy(1, 1, Fill::xy(Border(self.enabled(), self))), w)
}
fn enclose_bg <W: Content<TuiOut>> (self, w: W) -> impl Content<TuiOut> {
Tui::bg(self.style().unwrap().bg.unwrap_or(Color::Reset),
Bsp::b(Fill::xy(Border(self.enabled(), self)), w))
}
const NW: &'static str = "";
const N: &'static str = "";
const NE: &'static str = "";
const E: &'static str = "";
const SE: &'static str = "";
const S: &'static str = "";
const SW: &'static str = "";
const W: &'static str = "";
const N0: &'static str = "";
const S0: &'static str = "";
const W0: &'static str = "";
const E0: &'static str = "";
fn n (&self) -> &str { Self::N }
fn s (&self) -> &str { Self::S }
fn e (&self) -> &str { Self::E }
fn w (&self) -> &str { Self::W }
fn nw (&self) -> &str { Self::NW }
fn ne (&self) -> &str { Self::NE }
fn sw (&self) -> &str { Self::SW }
fn se (&self) -> &str { Self::SE }
#[inline] fn draw <'a> (
&self, to: &mut TuiOut
) -> Usually<()> {
if self.enabled() {
self.draw_horizontal(to, None)?;
self.draw_vertical(to, None)?;
self.draw_corners(to, None)?;
}
Ok(())
}
#[inline] fn draw_horizontal (
&self, to: &mut TuiOut, style: Option<Style>
) -> Usually<[u16;4]> {
let area = to.area();
let style = style.or_else(||self.style_horizontal());
let [x, x2, y, y2] = area.lrtb();
for x in x..x2.saturating_sub(1) {
to.blit(&Self::N, x, y, style);
to.blit(&Self::S, x, y2.saturating_sub(1), style)
}
Ok(area)
}
#[inline] fn draw_vertical (
&self, to: &mut TuiOut, style: Option<Style>
) -> Usually<[u16;4]> {
let area = to.area();
let style = style.or_else(||self.style_vertical());
let [x, x2, y, y2] = area.lrtb();
let h = y2 - y;
if h > 1 {
for y in y..y2.saturating_sub(1) {
to.blit(&Self::W, x, y, style);
to.blit(&Self::E, x2.saturating_sub(1), y, style);
}
} else if h > 0 {
to.blit(&Self::W0, x, y, style);
to.blit(&Self::E0, x2.saturating_sub(1), y, style);
}
Ok(area)
}
#[inline] fn draw_corners (
&self, to: &mut TuiOut, style: Option<Style>
) -> Usually<[u16;4]> {
let area = to.area();
let style = style.or_else(||self.style_corners());
let [x, y, width, height] = area.xywh();
if width > 1 && height > 1 {
to.blit(&Self::NW, x, y, style);
to.blit(&Self::NE, x + width - 1, y, style);
to.blit(&Self::SW, x, y + height - 1, style);
to.blit(&Self::SE, x + width - 1, y + height - 1, style);
}
Ok(area)
}
#[inline] fn style (&self) -> Option<Style> { None }
#[inline] fn style_horizontal (&self) -> Option<Style> { self.style() }
#[inline] fn style_vertical (&self) -> Option<Style> { self.style() }
#[inline] fn style_corners (&self) -> Option<Style> { self.style() }
}
macro_rules! border {
($($T:ident {
$nw:literal $n:literal $ne:literal $w:literal $e:literal $sw:literal $s:literal $se:literal
$($x:tt)*
}),+) => {$(
impl BorderStyle for $T {
const NW: &'static str = $nw;
const N: &'static str = $n;
const NE: &'static str = $ne;
const W: &'static str = $w;
const E: &'static str = $e;
const SW: &'static str = $sw;
const S: &'static str = $s;
const SE: &'static str = $se;
$($x)*
fn enabled (&self) -> bool { false }
}
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
impl Content<TuiOut> for $T {
fn render (&self, to: &mut TuiOut) {
if self.enabled() { let _ = self.draw(to); }
}
}
)+}
}
border! {
Square {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.1) }
},
SquareBold {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.1) }
},
TabLike {
"" "" ""
"" ""
"" " " "" fn style (&self) -> Option<Style> { Some(self.1) }
},
Lozenge {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.1) }
},
Brace {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.1) }
},
LozengeDotted {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.1) }
},
Quarter {
"" "" "🮇"
"" "🮇"
"" "" "🮇" fn style (&self) -> Option<Style> { Some(self.1) }
},
QuarterV {
"" "" "🮇"
"" "🮇"
"" "" "🮇" fn style (&self) -> Option<Style> { Some(self.1) }
},
Chamfer {
"🭂" "" "🭍"
"" "🮇"
"🭓" "" "🭞" fn style (&self) -> Option<Style> { Some(self.1) }
},
Corners {
"🬆" "" "🬊" // 🬴 🬸
"" ""
"🬱" "" "🬵" fn style (&self) -> Option<Style> { Some(self.1) }
},
CornersTall {
"🭽" "" "🭾"
"" ""
"🭼" "" "🭿" fn style (&self) -> Option<Style> { Some(self.1) }
},
Outer {
"🭽" "" "🭾"
"" ""
"🭼" "" "🭿"
const W0: &'static str = "[";
const E0: &'static str = "]";
const N0: &'static str = "";
const S0: &'static str = "";
fn style (&self) -> Option<Style> { Some(self.1) }
},
Phat {
"" "" ""
"" ""
"" "" ""
fn style (&self) -> Option<Style> { Some(self.1) }
},
Rugged {
"" "" ""
"" ""
"" "🮂" ""
fn style (&self) -> Option<Style> { Some(self.1) }
},
Skinny {
"" "" ""
"" ""
"" "" ""
fn style (&self) -> Option<Style> { Some(self.1) }
},
Brackets {
"" "" ""
"" ""
"" "" ""
const W0: &'static str = "[";
const E0: &'static str = "]";
const N0: &'static str = "";
const S0: &'static str = "";
fn style (&self) -> Option<Style> { Some(self.1) }
},
Reticle {
"" "" ""
"" ""
"" "" ""
const W0: &'static str = "";
const E0: &'static str = "";
const N0: &'static str = "";
const S0: &'static str = "";
fn style (&self) -> Option<Style> { Some(self.1) }
}
}

View file

@ -1,5 +1,5 @@
use crate::*;
use std::time::Duration;
pub struct Tui {
pub exited: Arc<AtomicBool>,
pub backend: CrosstermBackend<Stdout>,
@ -7,7 +7,6 @@ pub struct Tui {
pub area: [u16;4],
pub perf: PerfModel,
}
impl Tui {
/// Construct a new TUI engine and wrap it for shared ownership.
pub fn new () -> Usually<Arc<RwLock<Self>>> {
@ -59,3 +58,25 @@ impl Tui {
disable_raw_mode().map_err(Into::into)
}
}
pub trait TuiRun<R: Render<TuiOut> + Handle<TuiIn> + 'static> {
/// Run an app in the main loop.
fn run (&self, state: &Arc<RwLock<R>>) -> Usually<()>;
}
impl<T: Render<TuiOut> + Handle<TuiIn> + 'static> TuiRun<T> for Arc<RwLock<Tui>> {
fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()> {
let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100));
self.write().unwrap().setup()?;
let render_thread = TuiOut::run_output(self, state, Duration::from_millis(10));
match render_thread.join() {
Ok(result) => {
self.write().unwrap().teardown()?;
println!("\n\rRan successfully: {result:?}\n\r");
},
Err(error) => {
self.write().unwrap().teardown()?;
panic!("\n\rRender thread failed: {error:?}.\n\r")
},
}
Ok(())
}
}

View file

@ -1,36 +0,0 @@
use crate::*;
pub struct Field<T, U>(pub ItemPalette, pub T, pub U)
where T: AsRef<str> + Send + Sync, U: AsRef<str> + Send + Sync;
impl<T, U> Content<TuiOut> for Field<T, U>
where T: AsRef<str> + Send + Sync, U: AsRef<str> + Send + Sync
{
fn content (&self) -> impl Render<TuiOut> {
let ItemPalette { darkest, dark, lighter, lightest, .. } = self.0;
row!(
Tui::fg_bg(dark.rgb, darkest.rgb, ""),
Tui::fg_bg(lighter.rgb, dark.rgb, Tui::bold(true, format!("{}", self.1.as_ref()))),
Tui::fg_bg(dark.rgb, darkest.rgb, ""),
Tui::fg_bg(lightest.rgb, darkest.rgb, format!("{} ", self.2.as_ref()))
)
}
}
pub struct FieldV<T, U>(pub ItemPalette, pub T, pub U)
where T: AsRef<str> + Send + Sync, U: AsRef<str> + Send + Sync;
impl<T, U> Content<TuiOut> for FieldV<T, U>
where T: AsRef<str> + Send + Sync, U: AsRef<str> + Send + Sync
{
fn content (&self) -> impl Render<TuiOut> {
let ItemPalette { darkest, dark, lighter, lightest, .. } = self.0;
let sep1 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, ""));
let sep2 = Tui::bg(darkest.rgb, Tui::fg(dark.rgb, ""));
let name = Tui::bg(dark.rgb, Tui::fg(lighter.rgb,
Tui::bold(true, format!("{}", self.1.as_ref()))));
let value = Tui::bg(darkest.rgb, Tui::fg(lightest.rgb,
format!(" {} ", self.2.as_ref())));
Bsp::e(Bsp::s(row!(sep1, name, sep2), value), " ")
}
}

View file

@ -1,24 +0,0 @@
use crate::*;
use std::time::Duration;
pub trait TuiRun<R: Render<TuiOut> + Handle<TuiIn> + 'static> {
/// Run an app in the main loop.
fn run (&self, state: &Arc<RwLock<R>>) -> Usually<()>;
}
impl<T: Render<TuiOut> + Handle<TuiIn> + 'static> TuiRun<T> for Arc<RwLock<Tui>> {
fn run (&self, state: &Arc<RwLock<T>>) -> Usually<()> {
let _input_thread = TuiIn::run_input(self, state, Duration::from_millis(100));
self.write().unwrap().setup()?;
let render_thread = TuiOut::run_output(self, state, Duration::from_millis(10));
match render_thread.join() {
Ok(result) => {
self.write().unwrap().teardown()?;
println!("\n\rRan successfully: {result:?}\n\r");
},
Err(error) => {
self.write().unwrap().teardown()?;
panic!("\n\rRender thread failed: {error:?}.\n\r")
},
}
Ok(())
}
}

View file

@ -1,84 +0,0 @@
use crate::*;
pub trait TuiStyle {
fn fg <R: Content<TuiOut>> (color: Color, w: R) -> Foreground<R> {
Foreground(color, w)
}
fn bg <R: Content<TuiOut>> (color: Color, w: R) -> Background<R> {
Background(color, w)
}
fn fg_bg <R: Content<TuiOut>> (fg: Color, bg: Color, w: R) -> Background<Foreground<R>> {
Background(bg, Foreground(fg, w))
}
fn bold <R: Content<TuiOut>> (enable: bool, w: R) -> Bold<R> {
Bold(enable, w)
}
fn border <R: Content<TuiOut>, S: BorderStyle> (enable: bool, style: S, w: R) -> Bordered<S, R> {
Bordered(enable, style, w)
}
}
impl TuiStyle for Tui {}
pub struct Bold<R: Content<TuiOut>>(pub bool, R);
impl<R: Content<TuiOut>> Content<TuiOut> for Bold<R> {
fn content (&self) -> impl Render<TuiOut> { &self.1 }
fn render (&self, to: &mut TuiOut) {
to.fill_bold(to.area(), self.0);
self.1.render(to)
}
}
pub struct Foreground<R: Content<TuiOut>>(pub Color, R);
impl<R: Content<TuiOut>> Content<TuiOut> for Foreground<R> {
fn content (&self) -> impl Render<TuiOut> { &self.1 }
fn render (&self, to: &mut TuiOut) {
to.fill_fg(to.area(), self.0);
self.1.render(to)
}
}
pub struct Background<R: Content<TuiOut>>(pub Color, R);
impl<R: Content<TuiOut>> Content<TuiOut> for Background<R> {
fn content (&self) -> impl Render<TuiOut> { &self.1 }
fn render (&self, to: &mut TuiOut) {
to.fill_bg(to.area(), self.0);
self.1.render(to)
}
}
pub struct Styled<R: Content<TuiOut>>(pub Option<Style>, pub R);
impl<R: Content<TuiOut>> Content<TuiOut> for Styled<R> {
fn content (&self) -> impl Render<TuiOut> { &self.1 }
fn render (&self, to: &mut TuiOut) {
to.place(self.content().layout(to.area()), &self.content());
// TODO write style over area
}
}
//pub trait TuiStyle: Render<TuiOut> + Sized {
//fn fg (self, color: Color) -> impl Render<TuiOut> {
//Layers::new(move |add|{ add(&Foreground(color))?; add(&self) })
//}
//fn bg (self, color: Color) -> impl Render<TuiOut> {
//Layers::new(move |add|{ add(&Background(color))?; add(&self) })
//}
//fn bold (self, on: bool) -> impl Render<TuiOut> {
//Layers::new(move |add|{ add(&Bold(on))?; add(&self) })
//}
//fn border <S: BorderStyle> (self, style: S) -> impl Render<TuiOut> {
//Bordered(style, self)
//}
//}
//impl<R: Content<TuiOut>> TuiStyle for R {}
//impl<S: BorderStyle> Content<TuiOut> for Border<S> {
//}
//impl<S: BorderStyle, R: Content<TuiOut>> Content<TuiOut> for Bordered<S, R> {
//fn content (&self) -> impl Render<TuiOut> {
//let content: &dyn Content<TuiOut> = &self.1;
//lay! { content.padding_xy(1, 1), Border(self.0) }.fill_xy()
//}
//}

View file

@ -1,61 +0,0 @@
use crate::*;
#[derive(Copy,Clone)]
pub struct TuiTheme;
impl Theme for TuiTheme {}
pub trait Theme {
const HOTKEY_FG: Color = Color::Rgb(255, 255, 0);
fn null () -> Color {
Color::Reset
}
fn bg0 () -> Color {
Color::Rgb(20, 20, 20)
}
fn bg () -> Color {
Color::Rgb(28, 35, 25)
}
fn border_bg () -> Color {
Color::Rgb(40, 50, 30)
}
fn border_fg (focused: bool) -> Color {
if focused { Self::bo1() } else { Self::bo2() }
}
fn title_fg (focused: bool) -> Color {
if focused { Self::ti1() } else { Self::ti2() }
}
fn separator_fg (_: bool) -> Color {
Color::Rgb(0, 0, 0)
}
fn mode_bg () -> Color {
Color::Rgb(150, 160, 90)
}
fn mode_fg () -> Color {
Color::Rgb(255, 255, 255)
}
fn status_bar_bg () -> Color {
Color::Rgb(28, 35, 25)
}
fn bo1 () -> Color {
Color::Rgb(100, 110, 40)
}
fn bo2 () -> Color {
Color::Rgb(70, 80, 50)
}
fn ti1 () -> Color {
Color::Rgb(150, 160, 90)
}
fn ti2 () -> Color {
Color::Rgb(120, 130, 100)
}
fn orange () -> Color {
Color::Rgb(255,128,0)
}
fn yellow () -> Color {
Color::Rgb(255,255,0)
}
fn g (g: u8) -> Color {
Color::Rgb(g, g, g)
}
}