extract tui support code to tek_tui

This commit is contained in:
🪞👃🪞 2025-01-05 10:50:32 +01:00
parent 1a9077427c
commit 1faf5bb6df
22 changed files with 477 additions and 450 deletions

View file

@ -1,248 +0,0 @@
use crate::*;
pub struct Bordered<S: BorderStyle, W: Content<Tui>>(pub S, pub W);
render!(Tui: (self: Bordered<S: BorderStyle, W: Content<Tui>>) => {
Fill::xy(lay!(Border(self.0), Padding::xy(1, 1, &self.1)))
});
pub struct Border<S: BorderStyle>(pub S);
render!(Tui: |self: Border<S: BorderStyle>, to| {
let area = to.area();
if area.w() > 0 && area.y() > 0 {
to.blit(&self.0.nw(), area.x(), area.y(), self.0.style());
to.blit(&self.0.ne(), area.x() + area.w() - 1, area.y(), self.0.style());
to.blit(&self.0.sw(), area.x(), area.y() + area.h() - 1, self.0.style());
to.blit(&self.0.se(), area.x() + area.w() - 1, area.y() + area.h() - 1, self.0.style());
for x in area.x()+1..area.x()+area.w()-1 {
to.blit(&self.0.n(), x, area.y(), self.0.style());
to.blit(&self.0.s(), x, area.y() + area.h() - 1, self.0.style());
}
for y in area.y()+1..area.y()+area.h()-1 {
to.blit(&self.0.w(), area.x(), y, self.0.style());
to.blit(&self.0.e(), area.x() + area.w() - 1, y, self.0.style());
}
}
});
pub trait BorderStyle: Send + Sync + Copy {
fn wrap <W: Content<Tui>> (self, w: W) -> Bordered<Self, W> {
Bordered(self, w)
}
fn enclose <W: Content<Tui>> (self, w: W) -> impl Content<Tui> {
lay!(Fill::xy(Border(self)), w)
}
fn enclose_bg <W: Content<Tui>> (self, w: W) -> impl Content<Tui> {
Tui::bg(self.style().unwrap().bg.unwrap_or(Color::Reset), lay!(
Fill::xy(Border(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<()> {
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)*
}
#[derive(Copy, Clone)]
pub struct $T(pub Style);
impl Content<Tui> for $T {
fn render (&self, to: &mut TuiOut) { self.draw(to); }
}
)+}
}
border! {
Square {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.0) }
},
SquareBold {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.0) }
},
TabLike {
"" "" ""
"" ""
"" " " "" fn style (&self) -> Option<Style> { Some(self.0) }
},
Lozenge {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.0) }
},
Brace {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.0) }
},
LozengeDotted {
"" "" ""
"" ""
"" "" "" fn style (&self) -> Option<Style> { Some(self.0) }
},
Quarter {
"" "" "🮇"
"" "🮇"
"" "" "🮇" fn style (&self) -> Option<Style> { Some(self.0) }
},
QuarterV {
"" "" "🮇"
"" "🮇"
"" "" "🮇" fn style (&self) -> Option<Style> { Some(self.0) }
},
Chamfer {
"🭂" "" "🭍"
"" "🮇"
"🭓" "" "🭞" fn style (&self) -> Option<Style> { Some(self.0) }
},
Corners {
"🬆" "" "🬊" // 🬴 🬸
"" ""
"🬱" "" "🬵" fn style (&self) -> Option<Style> { Some(self.0) }
},
CornersTall {
"🭽" "" "🭾"
"" ""
"🭼" "" "🭿" fn style (&self) -> Option<Style> { Some(self.0) }
},
Outer {
"🭽" "" "🭾"
"" ""
"🭼" "" "🭿"
const W0: &'static str = "[";
const E0: &'static str = "]";
const N0: &'static str = "";
const S0: &'static str = "";
fn style (&self) -> Option<Style> { Some(self.0) }
},
Brackets {
"" "" ""
"" ""
"" "" ""
const W0: &'static str = "[";
const E0: &'static str = "]";
const N0: &'static str = "";
const S0: &'static str = "";
fn style (&self) -> Option<Style> { Some(self.0) }
},
Reticle {
"" "" ""
"" ""
"" "" ""
const W0: &'static str = "";
const E0: &'static str = "";
const N0: &'static str = "";
const S0: &'static str = "";
fn style (&self) -> Option<Style> { Some(self.0) }
}
}
pub const CORNERS: Brackets = Brackets(Style {
fg: Some(Color::Rgb(96, 255, 32)),
bg: None,
underline_color: None,
add_modifier: Modifier::empty(),
sub_modifier: Modifier::DIM
});
pub const RETICLE: Reticle = Reticle(Style {
fg: Some(Color::Rgb(96, 255, 32)),
bg: None,
underline_color: None,
add_modifier: Modifier::empty(),
sub_modifier: Modifier::DIM
});

View file

@ -1,104 +0,0 @@
use crate::*;
use rand::{thread_rng, distributions::uniform::UniformSampler};
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 }
}
}
}
/// A color in OKHSL and RGB representations.
#[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 {
pub base: ItemColor,
pub light: ItemColor,
pub lighter: ItemColor,
pub lightest: ItemColor,
pub dark: ItemColor,
pub darker: ItemColor,
pub darkest: ItemColor,
}
from!(|okhsl: Okhsl<f32>|ItemColor = Self { okhsl, rgb: okhsl_to_rgb(okhsl) });
from!(|rgb: Color|ItemColor = Self { rgb, okhsl: rgb_to_okhsl(rgb) });
// A single color within item theme parameters, in OKHSL and RGB representations.
impl ItemColor {
pub fn random () -> Self {
let mut rng = 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 = 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()
}
}
from!(|base: Color|ItemPalette = Self::from(ItemColor::from(base)));
from!(|base: ItemColor|ItemPalette = {
let mut light = base.okhsl;
light.lightness = (light.lightness * 1.3).min(Okhsl::<f32>::max_lightness());
let mut lighter = light;
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());
let mut darker = dark;
darker.lightness = (darker.lightness * 0.66).max(Okhsl::<f32>::min_lightness());
darker.saturation = (darker.saturation * 0.66).max(Okhsl::<f32>::min_saturation());
let mut darkest = darker;
darkest.lightness = 0.1;
darkest.saturation = (darkest.saturation * 0.50).max(Okhsl::<f32>::min_saturation());
Self {
base,
light: light.into(),
lighter: lighter.into(),
lightest: lightest.into(),
dark: dark.into(),
darker: darker.into(),
darkest: darkest.into(),
}
});
impl ItemPalette {
pub fn random () -> Self {
ItemColor::random().into()
}
pub fn random_near (color: Self, distance: f32) -> Self {
color.base.mix(ItemColor::random(), distance).into()
}
}
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")
}
}

View file

@ -5,39 +5,34 @@
#![feature(impl_trait_in_assoc_type)]
#![feature(associated_type_defaults)]
pub use ::tek_layout;
pub use ::tek_layout::tek_engine;
pub(crate) use ::tek_layout::{
pub use ::tek_tui::{self, tek_engine, tek_layout};
pub(crate) use ::tek_tui::{
*,
tek_edn::*,
tek_layout::*,
tek_engine::{
from,
Usually, Perhaps,
Output, Content, Render, Thunk, render, Engine, Size, Area,
Input, handle, Handle, command, Command, input_to_command, InputToCommand,
keymap, kexp, kpat, EventMap,
tui::{
Tui,
TuiIn, key, ctrl, shift, alt,
TuiOut,
crossterm::{
self,
event::{
Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers,
KeyCode::{self, *},
}
},
ratatui::{
self,
prelude::{Color, Style, Stylize, Buffer, Modifier},
buffer::Cell,
}
Input, handle, Handle, command, Command, input_to_command, InputToCommand, keymap, EventMap,
},
Tui,
TuiIn, key, ctrl, shift, alt, kexp, kpat,
TuiOut,
crossterm::{
self,
event::{
Event, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers,
KeyCode::{self, *},
}
},
ratatui::{
self,
prelude::{Color, Style, Stylize, Buffer, Modifier},
buffer::Cell,
}
};
pub use ::tek_edn;
pub(crate) use ::tek_edn::*;
pub(crate) use std::cmp::{Ord, Eq, PartialEq};
pub(crate) use std::collections::BTreeMap;
pub(crate) use std::error::Error;
@ -53,9 +48,7 @@ pub(crate) use std::thread::{spawn, JoinHandle};
pub(crate) use std::time::Duration;
pub mod arranger; pub use self::arranger::*;
pub mod border; pub use self::border::*;
pub mod clock; pub use self::clock::*;
pub mod color; pub use self::color::*;
pub mod field; pub use self::field::*;
pub mod file; pub use self::file::*;
pub mod focus; pub use self::focus::*;
@ -70,8 +63,6 @@ pub mod pool; pub use self::pool::*;
pub mod sampler; pub use self::sampler::*;
pub mod sequencer; pub use self::sequencer::*;
pub mod status; pub use self::status::*;
pub mod style; pub use self::style::*;
pub mod theme; pub use self::theme::*;
pub use ::atomic_float;
pub(crate) use atomic_float::*;
@ -84,13 +75,6 @@ pub(crate) use ::midly::{
live::LiveEvent,
};
pub use ::palette;
pub(crate) use ::palette::{
*,
convert::*,
okhsl::*
};
testmod! { test }
/// Define test modules.
@ -98,16 +82,6 @@ testmod! { test }
($($name:ident)*) => { $(#[cfg(test)] mod $name;)* };
}
/// 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 {
fn from ($state:$Source) -> Self { $cb }
}
};
}
pub trait Gettable<T> {
/// Returns current value
fn get (&self) -> T;

View file

@ -1,86 +0,0 @@
use crate::*;
pub trait TuiStyle {
fn fg <R: Content<Tui>> (color: Color, w: R) -> Foreground<R> {
Foreground(color, w)
}
fn bg <R: Content<Tui>> (color: Color, w: R) -> Background<R> {
Background(color, w)
}
fn fg_bg <R: Content<Tui>> (fg: Color, bg: Color, w: R) -> Background<Foreground<R>> {
Background(bg, Foreground(fg, w))
}
fn bold <R: Content<Tui>> (on: bool, w: R) -> Bold<R> {
Bold(on, w)
}
fn border <R: Content<Tui>, S: BorderStyle> (style: S, w: R) -> Bordered<S, R> {
Bordered(style, w)
}
}
impl TuiStyle for Tui {}
pub struct Bold<R: Content<Tui>>(pub bool, R);
impl<R: Content<Tui>> Content<Tui> for Bold<R> {
fn content (&self) -> impl Render<Tui> { &self.1 }
fn render (&self, to: &mut TuiOut) {
to.fill_bold(to.area(), self.0);
self.1.render(to)
}
}
pub struct Foreground<R: Content<Tui>>(pub Color, R);
impl<R: Content<Tui>> Content<Tui> for Foreground<R> {
fn content (&self) -> impl Render<Tui> { &self.1 }
fn render (&self, to: &mut TuiOut) {
to.fill_fg(to.area(), self.0);
self.1.render(to)
}
}
pub struct Background<R: Content<Tui>>(pub Color, R);
impl<R: Content<Tui>> Content<Tui> for Background<R> {
fn content (&self) -> impl Render<Tui> { &self.1 }
fn render (&self, to: &mut TuiOut) {
to.fill_bg(to.area(), self.0);
self.1.render(to)
}
}
pub struct Styled<R: Content<Tui>>(pub Option<Style>, pub R);
impl Content<Tui> for Styled<&str> {
fn content (&self) -> impl Render<Tui> { &self.1 }
fn render (&self, to: &mut TuiOut) {
// FIXME
let [x, y, ..] = to.area();
//let [w, h] = self.min_size(to.area().wh())?.unwrap();
to.blit(&self.1, x, y, None)
}
}
//pub trait TuiStyle: Render<Tui> + Sized {
//fn fg (self, color: Color) -> impl Render<Tui> {
//Layers::new(move |add|{ add(&Foreground(color))?; add(&self) })
//}
//fn bg (self, color: Color) -> impl Render<Tui> {
//Layers::new(move |add|{ add(&Background(color))?; add(&self) })
//}
//fn bold (self, on: bool) -> impl Render<Tui> {
//Layers::new(move |add|{ add(&Bold(on))?; add(&self) })
//}
//fn border <S: BorderStyle> (self, style: S) -> impl Render<Tui> {
//Bordered(style, self)
//}
//}
//impl<R: Content<Tui>> TuiStyle for R {}
//impl<S: BorderStyle> Content<Tui> for Border<S> {
//}
//impl<S: BorderStyle, R: Content<Tui>> Content<Tui> for Bordered<S, R> {
//fn content (&self) -> impl Render<Tui> {
//let content: &dyn Content<Tui> = &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)
}
}