wip: refactor(tengri): routines!

This commit is contained in:
same mf who else 2026-03-09 02:06:30 +02:00
parent 8f0a2accce
commit 4e8d58d793
9 changed files with 1495 additions and 1669 deletions

View file

@ -1,4 +1,6 @@
use crate::*;
use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
use rand::distributions::uniform::UniformSampler;
#[cfg(feature = "tui")] pub use self::tui_fns::*;
@ -198,3 +200,772 @@ pub fn eval_view_tui <'a, S> (
}
};
}
impl Coord for u16 { fn plus (self, other: Self) -> Self { self.saturating_add(other) } }
impl_from!(BigBuffer: |size:(usize, usize)| Self::new(size.0, size.1));
impl_from!(ItemTheme: |base: ItemColor| Self::from_item_color(base));
impl_from!(ItemTheme: |base: Color| Self::from_tui_color(base));
impl_from!(ItemColor: |rgb: Color| Self { rgb, okhsl: rgb_to_okhsl(rgb) });
impl_from!(ItemColor: |okhsl: Okhsl<f32>| Self { okhsl, rgb: okhsl_to_rgb(okhsl) });
impl_debug!(BigBuffer |self, f| { write!(f, "[BB {}x{} ({})]", self.width, self.height, self.content.len()) });
impl Screen for Buffer { type Unit = u16; }
impl TuiOut for Buffer { fn tui_out (&mut self) -> &mut Buffer { self } }
impl TuiOut for Buffer { fn tui_out (&mut self) -> &mut Buffer { self } }
//impl<T: AsMut<Buffer>> TuiOut for T { fn tui_out (&mut self) -> &mut Buffer { self.as_mut() } }
impl Tui {
/// True if done
pub fn exited (&self) -> bool { self.exited.fetch_and(true, Relaxed) }
/// Prepare before run
pub fn setup (&self) -> Usually<()> { tui_setup(&mut*self.backend.write().unwrap()) }
/// Clean up after run
pub fn teardown (&self) -> Usually<()> { tui_teardown(&mut*self.backend.write().unwrap()) }
/// Apply changes to the display buffer.
pub fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { tui_flip(self, self.buffer, buffer, size) }
/// Create the engine.
pub fn new (output: Box<dyn Write + Send + Sync>) -> Usually<Self> {
let backend = CrosstermBackend::new(output);
let Size { width, height } = backend.size()?;
Ok(Self {
exited: Arc::new(AtomicBool::new(false)),
buffer: Buffer::empty(Rect { x: 0, y: 0, width, height }).into(),
area: XYWH(0, 0, width, height),
perf: Default::default(),
backend: backend.into(),
event: None,
error: None,
})
}
/// Run an app in the engine.
pub fn run <T> (mut self, join: bool, state: &Arc<RwLock<T>>) -> Usually<Arc<Self>> where
T: Act<Tui, T> + Draw<Buffer> + Send + Sync + 'static
{
self.setup()?;
let tui = Arc::new(self);
let input_poll = Duration::from_millis(100);
let output_sleep = Duration::from_millis(10); // == 1/MAXFPS (?)
let _input_thread = tui_input(&tui, state, input_poll)?;
let render_thread = tui_output(&tui, state, output_sleep)?;
if join { // Run until render thread ends:
let result = render_thread.join();
tui.teardown()?;
match result {
Ok(result) => println!("\n\rRan successfully: {result:?}\n\r"),
Err(error) => panic!("\n\rDraw thread failed: error={error:?}.\n\r"),
}
}
Ok(tui)
}
}
impl TuiEvent {
pub fn from_crossterm (event: Event) -> Self { Self(event) }
#[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Perhaps<Self> {
Ok(TuiKey::from_dsl(dsl)?.to_crossterm().map(Self))
}
}
impl Ord for TuiEvent {
fn cmp (&self, other: &Self) -> std::cmp::Ordering {
self.partial_cmp(other)
.unwrap_or_else(||format!("{:?}", self).cmp(&format!("{other:?}"))) // FIXME perf
}
}
impl From<Event> for TuiEvent {
fn from (e: Event) -> Self {
Self(e)
}
}
impl From<char> for TuiEvent {
fn from (c: char) -> Self {
Self(Event::Key(KeyEvent::new(KeyCode::Char(c), KeyModifiers::NONE)))
}
}
impl TuiKey {
const SPLIT: char = '/';
#[cfg(feature = "dsl")] pub fn from_dsl (dsl: impl Language) -> Usually<Self> {
if let Some(word) = dsl.word()? {
let word = word.trim();
Ok(if word == ":char" {
Self(None, KeyModifiers::NONE)
} else if word.chars().nth(0) == Some('@') {
let mut key = None;
let mut modifiers = KeyModifiers::NONE;
let mut tokens = word[1..].split(Self::SPLIT).peekable();
while let Some(token) = tokens.next() {
if tokens.peek().is_some() {
match token {
"ctrl" | "Ctrl" | "c" | "C" => modifiers |= KeyModifiers::CONTROL,
"alt" | "Alt" | "m" | "M" => modifiers |= KeyModifiers::ALT,
"shift" | "Shift" | "s" | "S" => {
modifiers |= KeyModifiers::SHIFT;
// + TODO normalize character case, BackTab, etc.
},
_ => panic!("unknown modifier {token}"),
}
} else {
key = if token.len() == 1 {
Some(KeyCode::Char(token.chars().next().unwrap()))
} else {
Some(named_key(token).unwrap_or_else(||panic!("unknown character {token}")))
}
}
}
Self(key, modifiers)
} else {
return Err(format!("TuiKey: unexpected: {word}").into())
})
} else {
return Err(format!("TuiKey: unspecified").into())
}
}
pub fn to_crossterm (&self) -> Option<Event> {
self.0.map(|code|Event::Key(KeyEvent {
code,
modifiers: self.1,
kind: KeyEventKind::Press,
state: KeyEventState::NONE,
}))
}
}
impl BigBuffer {
pub fn new (width: usize, height: usize) -> Self {
Self { width, height, content: vec![Cell::default(); width*height] }
}
pub fn get (&self, x: usize, y: usize) -> Option<&Cell> {
let i = self.index_of(x, y);
self.content.get(i)
}
pub fn get_mut (&mut self, x: usize, y: usize) -> Option<&mut Cell> {
let i = self.index_of(x, y);
self.content.get_mut(i)
}
pub fn index_of (&self, x: usize, y: usize) -> usize {
y * self.width + x
}
}
impl PerfModel {
fn cycle <F: Fn(&Self)->T, T> (&self, call: &F) -> T {
let t0 = self.get_t0();
let result = call(self);
let _t1 = self.get_t1(t0).unwrap();
result
}
}
impl<T> Phat<T> {
pub const LO: &'static str = "";
pub const HI: &'static str = "";
/// A phat line
pub fn lo (fg: Color, bg: Color) -> impl Draw<Buffer> {
Fixed::Y(1, Tui::fg_bg(fg, bg, Repeat::X(Self::LO)))
}
/// A phat line
pub fn hi (fg: Color, bg: Color) -> impl Draw<Buffer> {
Fixed::Y(1, Tui::fg_bg(fg, bg, Repeat::X(Self::HI)))
}
}
impl Scrollbar {
const ICON_DEC_V: &[char] = &['▲'];
const ICON_INC_V: &[char] = &['▼'];
const ICON_DEC_H: &[char] = &[' ', '🞀', ' '];
const ICON_INC_H: &[char] = &[' ', '🞂', ' '];
}
impl Layout<Tui> for &str {
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
to.centered_xy([width_chars_max(to.w(), self), 1])
}
}
impl Layout<Tui> for String {
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
self.as_str().layout(to)
}
}
impl Layout<Tui> for Arc<str> {
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
self.as_ref().layout(to)
}
}
impl<'a, T: AsRef<str>> Layout<Tui> for TrimString<T> {
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
Layout::layout(&self.as_ref(), to)
}
}
impl<'a, T: AsRef<str>> Layout<Tui> for TrimStringRef<'a, T> {
fn layout (&self, to: XYWH<u16>) -> XYWH<u16> {
XYWH(to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h())
}
}
impl<T: Screen> Draw<T> for u64 {
fn draw (&self, _to: &mut T) { todo!() }
}
impl Draw<Buffer> for f64 {
fn draw (&self, _to: &mut Buffer) {
todo!()
}
}
impl Draw<Buffer> for Repeat<'_> {
fn draw (&self, to: &mut Buffer) {
let XYWH(x, y, w, h) = to.area();
let mut buf = to.buffer.write().unwrap();
match self {
Self::X(c) => {
for x in x..x+w {
if let Some(cell) = buf.cell_mut(Position::from((x, y))) {
cell.set_symbol(&c);
}
}
},
Self::Y(c) => {
for y in y..y+h {
if let Some(cell) = buf.cell_mut(Position::from((x, y))) {
cell.set_symbol(&c);
}
}
},
Self::XY(c) => {
let a = c.len();
for (_v, y) in (y..y+h).enumerate() {
for (u, x) in (x..x+w).enumerate() {
if let Some(cell) = buf.cell_mut(Position::from((x, y))) {
let u = u % a;
cell.set_symbol(&c[u..u+1]);
}
}
}
},
}
}
}
impl Draw<Buffer> for Scrollbar {
fn draw (&self, to: &mut Buffer) {
let XYWH(x1, y1, w, h) = to.area();
let mut buf = to.buffer.write().unwrap();
match self {
Self::X { .. } => {
let x2 = x1 + w;
for (i, x) in (x1..=x2).enumerate() {
if let Some(cell) = buf.cell_mut(Position::from((x, y1))) {
if i < (Self::ICON_DEC_H.len()) {
cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Rgb(0, 0, 0));
cell.set_char(Self::ICON_DEC_H[i as usize]);
} else if i > (w as usize - Self::ICON_INC_H.len()) {
cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Rgb(0, 0, 0));
cell.set_char(Self::ICON_INC_H[w as usize - i]);
} else if false {
cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Reset);
cell.set_char('━');
} else {
cell.set_fg(Rgb(0, 0, 0));
cell.set_bg(Reset);
cell.set_char('╌');
}
}
}
},
Self::Y { .. } => {
let y2 = y1 + h;
for (i, y) in (y1..=y2).enumerate() {
if let Some(cell) = buf.cell_mut(Position::from((x1, y))) {
if (i as usize) < (Self::ICON_DEC_V.len()) {
cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Rgb(0, 0, 0));
cell.set_char(Self::ICON_DEC_V[i as usize]);
} else if (i as usize) > (h as usize - Self::ICON_INC_V.len()) {
cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Rgb(0, 0, 0));
cell.set_char(Self::ICON_INC_V[h as usize - i]);
} else if false {
cell.set_fg(Rgb(255, 255, 255));
cell.set_bg(Reset);
cell.set_char('‖'); // ━
} else {
cell.set_fg(Rgb(0, 0, 0));
cell.set_bg(Reset);
cell.set_char('╎'); // ━
}
}
}
},
}
}
}
impl Draw<Buffer> for &str {
fn draw (&self, to: &mut Buffer) {
let XYWH(x, y, w, ..) = self.layout(to.area());
to.text(&self, x, y, w)
}
}
impl Draw<Buffer> for String {
fn draw (&self, to: &mut Buffer) {
self.as_str().draw(to)
}
}
impl Draw<Buffer> for Arc<str> {
fn draw (&self, to: &mut Buffer) { self.as_ref().draw(to) }
}
impl<T: Draw<Buffer>> Draw<Buffer> for Foreground<Color, T> {
fn draw (&self, to: &mut Buffer) {
let area = self.layout(to.area());
to.fill_fg(area, self.0);
to.show(area, &self.1);
}
}
impl<T: Draw<Buffer>> Draw<Buffer> for Background<Color, T> {
fn draw (&self, to: &mut Buffer) {
let area = self.layout(to.area());
to.fill_bg(area, self.0);
to.show(area, &self.1);
}
}
impl<T: Draw<Buffer>> Draw<Buffer> for Modify<T> {
fn draw (&self, to: &mut Buffer) {
to.fill_mod(to.area(), self.0, self.1);
self.2.draw(to)
}
}
impl<T: Draw<Buffer>> Draw<Buffer> for Styled<T> {
fn draw (&self, to: &mut Buffer) {
to.place(&self.1);
// TODO write style over area
}
}
impl<S: BorderStyle> Draw<Buffer> for Border<S> {
fn draw (&self, to: &mut Buffer) {
let Border(enabled, style) = self;
if *enabled {
let area = to.area();
if area.w() > 0 && area.y() > 0 {
to.blit(&style.border_nw(), area.x(), area.y(), style.style());
to.blit(&style.border_ne(), area.x() + area.w() - 1, area.y(), style.style());
to.blit(&style.border_sw(), area.x(), area.y() + area.h() - 1, style.style());
to.blit(&style.border_se(), area.x() + area.w() - 1, area.y() + area.h() - 1, style.style());
for x in area.x()+1..area.x()+area.w()-1 {
to.blit(&style.border_n(), x, area.y(), style.style());
to.blit(&style.border_s(), x, area.y() + area.h() - 1, style.style());
}
for y in area.y()+1..area.y()+area.h()-1 {
to.blit(&style.border_w(), area.x(), y, style.style());
to.blit(&style.border_e(), area.x() + area.w() - 1, y, style.style());
}
}
}
}
}
impl<'a, T: AsRef<str>> Draw<Buffer> for TrimString<T> {
fn draw (&self, to: &mut Buffer) { Draw::draw(&self.as_ref(), to) }
}
impl<T: AsRef<str>> Draw<Buffer> for TrimStringRef<'_, T> {
fn draw (&self, target: &mut Buffer) {
let area = target.area();
let mut buf = target.buffer.write().unwrap();
let mut width: u16 = 1;
let mut chars = self.1.as_ref().chars();
while let Some(c) = chars.next() {
if width > self.0 || width > area.w() {
break
}
if let Some(cell) = buf.cell_mut(Position {
x: area.x() + width - 1,
y: area.y()
}) {
cell.set_char(c);
}
width += c.width().unwrap_or(0) as u16;
}
}
}
/// TUI helper defs.
impl Tui {
pub const fn fg <T> (color: Color, w: T) -> Foreground<Color, T> { Foreground(color, w) }
pub const fn bg <T> (color: Color, w: T) -> Background<Color, T> { Background(color, w) }
pub const fn fg_bg <T> (fg: Color, bg: Color, w: T) -> Background<Color, Foreground<Color, T>> { Background(bg, Foreground(fg, w)) }
pub const fn modify <T> (enable: bool, modifier: Modifier, w: T) -> Modify<T> { Modify(enable, modifier, w) }
pub const fn bold <T> (enable: bool, w: T) -> Modify<T> { Self::modify(enable, Modifier::BOLD, w) }
pub const fn border <S, T> (enable: bool, style: S, w: T) -> Bordered<S, T> { Bordered(enable, style, w) }
pub const fn null () -> Color { Color::Reset }
pub const fn red () -> Color { Color::Rgb(255,0, 0) }
pub const fn orange () -> Color { Color::Rgb(255,128,0) }
pub const fn yellow () -> Color { Color::Rgb(255,255,0) }
pub const fn brown () -> Color { Color::Rgb(128,255,0) }
pub const fn green () -> Color { Color::Rgb(0,255,0) }
pub const fn electric () -> Color { Color::Rgb(0,255,128) }
pub const fn g (g: u8) -> Color { Color::Rgb(g, g, g) }
//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 (f: bool) -> Color { if f { Self::bo1() } else { Self::bo2() } }
//fn title_fg (f: bool) -> Color { if f { 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) }
}
pub fn named_key (token: &str) -> Option<KeyCode> {
use KeyCode::*;
Some(match token {
"up" => Up,
"down" => Down,
"left" => Left,
"right" => Right,
"esc" | "escape" => Esc,
"enter" | "return" => Enter,
"delete" | "del" => Delete,
"backspace" => Backspace,
"tab" => Tab,
"space" => Char(' '),
"comma" => Char(','),
"period" => Char('.'),
"plus" => Char('+'),
"minus" | "dash" => Char('-'),
"equal" | "equals" => Char('='),
"underscore" => Char('_'),
"backtick" => Char('`'),
"lt" => Char('<'),
"gt" => Char('>'),
"cbopen" | "openbrace" => Char('{'),
"cbclose" | "closebrace" => Char('}'),
"bropen" | "openbracket" => Char('['),
"brclose" | "closebracket" => Char(']'),
"pgup" | "pageup" => PageUp,
"pgdn" | "pagedown" => PageDown,
"f1" => F(1),
"f2" => F(2),
"f3" => F(3),
"f4" => F(4),
"f5" => F(5),
"f6" => F(6),
"f7" => F(7),
"f8" => F(8),
"f9" => F(9),
"f10" => F(10),
"f11" => F(11),
"f12" => F(12),
_ => return None,
})
}
impl<O: Screen, Color, Item: Layout<O>> Layout<O> for Foreground<Color, Item> {
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> { self.1.layout(to) }
}
impl<O: Screen, Color, Item: Layout<O>> Layout<O> for Background<Color, Item> {
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> { self.1.layout(to) }
}
impl Tryptich<(), (), ()> {
pub fn center (h: u16) -> Self {
Self { h, top: false, left: (0, ()), middle: (0, ()), right: (0, ()) }
}
pub fn top (h: u16) -> Self {
Self { h, top: true, left: (0, ()), middle: (0, ()), right: (0, ()) }
}
}
impl<A, B, C> Tryptich<A, B, C> {
pub fn left <D> (self, w: u16, content: D) -> Tryptich<D, B, C> {
Tryptich { left: (w, content), ..self }
}
pub fn middle <D> (self, w: u16, content: D) -> Tryptich<A, D, C> {
Tryptich { middle: (w, content), ..self }
}
pub fn right <D> (self, w: u16, content: D) -> Tryptich<A, B, D> {
Tryptich { right: (w, content), ..self }
}
}
/// ```
/// let _ = tengri::button_2("", "", true);
/// let _ = tengri::button_2("", "", false);
/// ```
pub fn button_2 <'a> (key: impl Draw<Buffer>, label: impl Draw<Buffer>, editing: bool) -> impl Draw<Buffer> {
Tui::bold(true, Bsp::e(
Tui::fg_bg(Tui::orange(), Tui::g(0), Bsp::e(Tui::fg(Tui::g(0), &""), Bsp::e(key, Tui::fg(Tui::g(96), &"")))),
When::new(!editing, Tui::fg_bg(Tui::g(255), Tui::g(96), label))))
}
/// ```
/// let _ = tengri::button_3("", "", "", true);
/// let _ = tengri::button_3("", "", "", false);
/// ```
pub fn button_3 <'a> (
key: impl Draw<Buffer>, label: impl Draw<Buffer>, value: impl Draw<Buffer>, editing: bool,
) -> impl Draw<Buffer> {
Tui::bold(true, Bsp::e(
Tui::fg_bg(Tui::orange(), Tui::g(0),
Bsp::e(Tui::fg(Tui::g(0), &""), Bsp::e(key, Tui::fg(if editing { Tui::g(128) } else { Tui::g(96) }, "")))),
Bsp::e(
When::new(!editing, Bsp::e(Tui::fg_bg(Tui::g(255), Tui::g(96), label), Tui::fg_bg(Tui::g(128), Tui::g(96), &""),)),
Bsp::e(Tui::fg_bg(Tui::g(224), Tui::g(128), value), Tui::fg_bg(Tui::g(128), Reset, &""), ))))
}
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 Layout<Tui> for $T {}
impl Draw<Buffer> for $T {
fn draw (&self, to: &mut Buffer) {
if self.enabled() { let _ = BorderStyle::draw(self, 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) }
},
Thick {
"" "" ""
"" ""
"" "" ""
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) }
}
}
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")
}
}
// A single color within item theme parameters, in OKHSL and RGB representations.
impl ItemColor {
#[cfg(feature = "tui")] pub const fn from_tui (rgb: Color) -> Self {
Self { rgb, 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()
}
}
impl ItemTheme {
#[cfg(feature = "tui")] pub const G: [Self;256] = {
let mut builder = 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 },
rgb: Color::Rgb(0, 0, 0)
};
Self {
base: color,
light: color,
lighter: color,
lightest: color,
dark: color,
darker: color,
darkest: color,
}
};
#[cfg(feature = "tui")] 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(),
}
}
}