mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2026-03-13 12:10:44 +01:00
2129 lines
88 KiB
Rust
2129 lines
88 KiB
Rust
use crate::*;
|
|
use Alignment::*;
|
|
use Direction::*;
|
|
use rand::{thread_rng, distributions::uniform::UniformSampler};
|
|
impl<O: Out, T: Draw<O> + Layout<O>> Content<O> for T {}
|
|
impl<'a, O: Out> AsRef<dyn Draw<O> + 'a> for dyn Content<O> + 'a {
|
|
fn as_ref (&self) -> &(dyn Draw<O> + 'a) { self }
|
|
}
|
|
impl<'a, O: Out> AsRef<dyn Layout<O> + 'a> for dyn Content<O> + 'a {
|
|
fn as_ref (&self) -> &(dyn Layout<O> + 'a) { self }
|
|
}
|
|
impl<O: Out> Draw<O> for () {
|
|
fn draw (&self, _: &mut O) {}
|
|
}
|
|
impl<O: Out> Draw<O> for fn(&mut O) {
|
|
fn draw (&self, to: &mut O) { (*self)(to) }
|
|
}
|
|
impl<O: Out> Draw<O> for Box<dyn Draw<O>> {
|
|
fn draw (&self, to: &mut O) { (**self).draw(to) }
|
|
}
|
|
impl<O: Out, D: Draw<O>> Draw<O> for &D {
|
|
fn draw (&self, to: &mut O) { (*self).draw(to) }
|
|
}
|
|
impl<O: Out, D: Draw<O>> Draw<O> for &mut D {
|
|
fn draw (&self, to: &mut O) { (**self).draw(to) }
|
|
}
|
|
impl<O: Out, D: Draw<O>> Draw<O> for Arc<D> {
|
|
fn draw (&self, to: &mut O) { (**self).draw(to) }
|
|
}
|
|
impl<O: Out, D: Draw<O>> Draw<O> for RwLock<D> {
|
|
fn draw (&self, to: &mut O) { self.read().unwrap().draw(to) }
|
|
}
|
|
impl<O: Out, D: Draw<O>> Draw<O> for Option<D> {
|
|
fn draw (&self, to: &mut O) { if let Some(draw) = self { draw.draw(to) } }
|
|
}
|
|
impl<'a, T: AsRef<str>> TrimString<T> {
|
|
fn as_ref (&self) -> TrimStringRef<'_, T> { TrimStringRef(self.0, &self.1) }
|
|
}
|
|
impl<O: Out, T: Draw<O>> ErrorBoundary<O, T> {
|
|
pub fn new (content: Perhaps<T>) -> Self { Self(content, Default::default()) }
|
|
}
|
|
impl<O: Out, T, L: Content<O>, V: Content<O>> HasContent<O> for FieldH<T, L, V> {
|
|
fn content (&self) -> impl Content<O> { Bsp::e(&self.1, &self.2) }
|
|
}
|
|
impl<O: Out, T, L: Content<O>, V: Content<O>> Layout<O> for FieldH<T, L, V> {
|
|
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> { self.content().layout(to) }
|
|
}
|
|
impl<O: Out, T, L: Content<O>, V: Content<O>> Draw<O> for FieldH<T, L, V> {
|
|
fn draw (&self, to: &mut O) { self.content().draw(to) }
|
|
}
|
|
impl<O: Out, T, L: Content<O>, V: Content<O>> HasContent<O> for FieldV<T, L, V> {
|
|
fn content (&self) -> impl Content<O> { Bsp::s(&self.1, &self.2) }
|
|
}
|
|
impl<O: Out, T, L: Content<O>, V: Content<O>> Layout<O> for FieldV<T, L, V> {
|
|
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> { self.content().layout(to) }
|
|
}
|
|
impl<O: Out, T, L: Content<O>, V: Content<O>> Draw<O> for FieldV<T, L, V> {
|
|
fn draw (&self, to: &mut O) { self.content().draw(to) }
|
|
}
|
|
impl<O: Out, S: Layout<O>> Layout<O> for Border<S> {
|
|
fn layout (&self, area: XYWH<O::Unit>) -> XYWH<O::Unit> { self.1.layout(area) }
|
|
}
|
|
impl<C, T, U> Field<C, T, U> {
|
|
pub fn new (direction: Direction) -> Field<C, (), ()> {
|
|
Field::<C, (), ()> {
|
|
direction,
|
|
label: None, label_fg: None, label_bg: None, label_align: None,
|
|
value: None, value_fg: None, value_bg: None, value_align: None,
|
|
}
|
|
}
|
|
pub fn label <L> (
|
|
self, label: Option<L>, align: Option<Direction>, fg: Option<C>, bg: Option<C>
|
|
) -> Field<C, L, U> {
|
|
Field::<C, L, U> { label, label_fg: fg, label_bg: bg, label_align: align, ..self }
|
|
}
|
|
pub fn value <V> (
|
|
self, value: Option<V>, align: Option<Direction>, fg: Option<C>, bg: Option<C>
|
|
) -> Field<C, T, V> {
|
|
Field::<C, T, V> { value, value_fg: fg, value_bg: bg, value_align: align, ..self }
|
|
}
|
|
}
|
|
impl<S, T: Command<S>> Command<S> for Option<T> {
|
|
fn execute (&self, _: &mut S) -> Perhaps<Self> {
|
|
Ok(None)
|
|
}
|
|
fn delegate <U> (&self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps<U>
|
|
where Self: Sized
|
|
{
|
|
Ok(None)
|
|
}
|
|
}
|
|
impl<O: Out> Layout<O> for Measure<O> {}
|
|
impl<E: Out, T: AsRef<Measure<E>>> Measured<E> for T {
|
|
fn measure (&self) -> &Measure<E> { self.as_ref() }
|
|
}
|
|
impl<O: Out> Clone for Measure<O> {
|
|
fn clone (&self) -> Self {
|
|
Self { __: Default::default(), x: self.x.clone(), y: self.y.clone(), }
|
|
}
|
|
}
|
|
impl<O: Out, F: Fn(&mut O)> Thunk<O, F> {
|
|
pub const fn new (draw: F) -> Self { Self(draw, PhantomData) }
|
|
}
|
|
impl<O: Out, F: Fn(&mut O)> Draw<O> for Thunk<O, F> {
|
|
fn draw (&self, to: &mut O) { (self.0)(to) }
|
|
}
|
|
impl<O: Out, F: Fn(&mut O)> Layout<O> for Thunk<O, F> {}
|
|
impl<T: PartialEq, U> Memo<T, U> {
|
|
pub fn new (value: T, view: U) -> Self {
|
|
Self { value, view: Arc::new(view.into()) }
|
|
}
|
|
pub fn update <R> (&mut self, newval: T, draw: impl Fn(&mut U, &T, &T)->R) -> Option<R> {
|
|
if newval != self.value {
|
|
let result = draw(&mut*self.view.write().unwrap(), &newval, &self.value);
|
|
self.value = newval;
|
|
return Some(result);
|
|
}
|
|
None
|
|
}
|
|
}
|
|
impl Direction {
|
|
pub fn split_fixed <N: Coord> (self, area: XYWH<N>, a: N) -> (XYWH<N>, XYWH<N>) {
|
|
let XYWH(x, y, w, h) = area;
|
|
match self {
|
|
North => (XYWH(x, y.plus(h).minus(a), w, a), XYWH(x, y, w, h.minus(a))),
|
|
South => (XYWH(x, y, w, a), XYWH(x, y.plus(a), w, h.minus(a))),
|
|
East => (XYWH(x, y, a, h), XYWH(x.plus(a), y, w.minus(a), h)),
|
|
West => (XYWH(x.plus(w).minus(a), y, a, h), XYWH(x, y, w.minus(a), h)),
|
|
Above | Below => (area, area)
|
|
}
|
|
}
|
|
}
|
|
impl<O: Out> PartialEq for Measure<O> {
|
|
fn eq (&self, other: &Self) -> bool {
|
|
self.x.load(Relaxed) == other.x.load(Relaxed) &&
|
|
self.y.load(Relaxed) == other.y.load(Relaxed)
|
|
}
|
|
}
|
|
// TODO: 🡘 🡙 ←🡙→ indicator to expand window when too small
|
|
impl<O: Out> Draw<O> for Measure<O> {
|
|
fn draw (&self, to: &mut O) {
|
|
self.x.store(to.area().w().into(), Relaxed);
|
|
self.y.store(to.area().h().into(), Relaxed);
|
|
}
|
|
}
|
|
impl<O: Out> Debug for Measure<O> {
|
|
fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
|
f.debug_struct("Measure").field("width", &self.x).field("height", &self.y).finish()
|
|
}
|
|
}
|
|
impl<O: Out> Measure<O> {
|
|
pub fn set_w (&self, w: impl Into<usize>) -> &Self { self.x.store(w.into(), Relaxed); self }
|
|
pub fn set_h (&self, h: impl Into<usize>) -> &Self { self.y.store(h.into(), Relaxed); self }
|
|
pub fn set_wh (&self, w: impl Into<usize>, h: impl Into<usize>) -> &Self { self.set_w(w); self.set_h(h); self }
|
|
pub fn format (&self) -> Arc<str> { format!("{}x{}", self.w(), self.h()).into() }
|
|
pub fn of <T: Draw<O>> (&self, item: T) -> Bsp<Fill<&Self>, T> { Bsp::b(Fill::XY(self), item) }
|
|
pub fn new (x: O::Unit, y: O::Unit) -> Self {
|
|
Self { __: PhantomData::default(), x: Arc::new(x.atomic()), y: Arc::new(y.atomic()), }
|
|
}
|
|
}
|
|
impl<O: Out> From<WH<O::Unit>> for Measure<O> {
|
|
fn from (WH(x, y): WH<O::Unit>) -> Self { Self::new(x, y) }
|
|
}
|
|
impl<O: Out> HasWH<O::Unit> for Measure<O> { /// FIXME don't convert to u16 specifically
|
|
fn w (&self) -> O::Unit { (self.x.load(Relaxed) as u16).into() }
|
|
fn h (&self) -> O::Unit { (self.y.load(Relaxed) as u16).into() }
|
|
}
|
|
impl<O: Out> Layout<O> for () {
|
|
fn layout_x (&self, a: XYWH<O::Unit>) -> O::Unit { a.x() }
|
|
fn layout_y (&self, a: XYWH<O::Unit>) -> O::Unit { a.y() }
|
|
fn layout_w (&self, _: XYWH<O::Unit>) -> O::Unit { 0.into() }
|
|
fn layout_w_min (&self, _: XYWH<O::Unit>) -> O::Unit { 0.into() }
|
|
fn layout_w_max (&self, _: XYWH<O::Unit>) -> O::Unit { 0.into() }
|
|
fn layout_h (&self, _: XYWH<O::Unit>) -> O::Unit { 0.into() }
|
|
fn layout_h_min (&self, _: XYWH<O::Unit>) -> O::Unit { 0.into() }
|
|
fn layout_h_max (&self, _: XYWH<O::Unit>) -> O::Unit { 0.into() }
|
|
fn layout (&self, a: XYWH<O::Unit>) -> XYWH<O::Unit> { XYWH(a.x(), a.y(), 0.into(), 0.into()) }
|
|
}
|
|
impl<O: Out, L: Layout<O>> Layout<O> for &L {
|
|
fn layout_x (&self, a: XYWH<O::Unit>) -> O::Unit { (*self).layout_x(a) }
|
|
fn layout_y (&self, a: XYWH<O::Unit>) -> O::Unit { (*self).layout_y(a) }
|
|
fn layout_w (&self, a: XYWH<O::Unit>) -> O::Unit { (*self).layout_w(a) }
|
|
fn layout_w_min (&self, a: XYWH<O::Unit>) -> O::Unit { (*self).layout_w_min(a) }
|
|
fn layout_w_max (&self, a: XYWH<O::Unit>) -> O::Unit { (*self).layout_w_max(a) }
|
|
fn layout_h (&self, a: XYWH<O::Unit>) -> O::Unit { (*self).layout_h(a) }
|
|
fn layout_h_min (&self, a: XYWH<O::Unit>) -> O::Unit { (*self).layout_h_min(a) }
|
|
fn layout_h_max (&self, a: XYWH<O::Unit>) -> O::Unit { (*self).layout_h_max(a) }
|
|
fn layout (&self, a: XYWH<O::Unit>) -> XYWH<O::Unit> { (*self).layout(a) }
|
|
}
|
|
|
|
impl<O: Out, L: Layout<O>> Layout<O> for &mut L {
|
|
fn layout_x (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_x(a) }
|
|
fn layout_y (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_y(a) }
|
|
fn layout_w (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_w(a) }
|
|
fn layout_w_min (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_w_min(a) }
|
|
fn layout_w_max (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_w_max(a) }
|
|
fn layout_h (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_h(a) }
|
|
fn layout_h_min (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_h_min(a) }
|
|
fn layout_h_max (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_h_max(a) }
|
|
fn layout (&self, a: XYWH<O::Unit>) -> XYWH<O::Unit> { (**self).layout(a) }
|
|
}
|
|
|
|
impl<O: Out, L: Layout<O>> Layout<O> for Arc<L> {
|
|
fn layout_x (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_x(a) }
|
|
fn layout_y (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_y(a) }
|
|
fn layout_w (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_w(a) }
|
|
fn layout_w_min (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_w_min(a) }
|
|
fn layout_w_max (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_w_max(a) }
|
|
fn layout_h (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_h(a) }
|
|
fn layout_h_min (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_h_min(a) }
|
|
fn layout_h_max (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_h_max(a) }
|
|
fn layout (&self, a: XYWH<O::Unit>) -> XYWH<O::Unit> { (**self).layout(a) }
|
|
}
|
|
impl<O: Out> Layout<O> for Box<dyn Layout<O>> {
|
|
fn layout_x (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_x(a) }
|
|
fn layout_y (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_y(a) }
|
|
fn layout_w (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_w(a) }
|
|
fn layout_w_min (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_w_min(a) }
|
|
fn layout_w_max (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_w_max(a) }
|
|
fn layout_h (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_h(a) }
|
|
fn layout_h_min (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_h_min(a) }
|
|
fn layout_h_max (&self, a: XYWH<O::Unit>) -> O::Unit { (**self).layout_h_max(a) }
|
|
fn layout (&self, a: XYWH<O::Unit>) -> XYWH<O::Unit> { (**self).layout(a) }
|
|
}
|
|
impl<O: Out, L: Layout<O>> Layout<O> for RwLock<L> {
|
|
fn layout_x (&self, a: XYWH<O::Unit>) -> O::Unit { self.read().unwrap().layout_x(a) }
|
|
fn layout_y (&self, a: XYWH<O::Unit>) -> O::Unit { self.read().unwrap().layout_y(a) }
|
|
fn layout_w (&self, a: XYWH<O::Unit>) -> O::Unit { self.read().unwrap().layout_w(a) }
|
|
fn layout_w_min (&self, a: XYWH<O::Unit>) -> O::Unit { self.read().unwrap().layout_w_min(a) }
|
|
fn layout_w_max (&self, a: XYWH<O::Unit>) -> O::Unit { self.read().unwrap().layout_w_max(a) }
|
|
fn layout_h (&self, a: XYWH<O::Unit>) -> O::Unit { self.read().unwrap().layout_h(a) }
|
|
fn layout_h_min (&self, a: XYWH<O::Unit>) -> O::Unit { self.read().unwrap().layout_h_min(a) }
|
|
fn layout_h_max (&self, a: XYWH<O::Unit>) -> O::Unit { self.read().unwrap().layout_h_max(a) }
|
|
fn layout (&self, a: XYWH<O::Unit>) -> XYWH<O::Unit> { self.read().unwrap().layout(a) }
|
|
}
|
|
impl<O: Out, L: Layout<O>> Layout<O> for Option<L> {
|
|
fn layout_x (&self, to: XYWH<O::Unit>) -> O::Unit { self.as_ref().map(|c|c.layout_x(to)).unwrap_or(to.x()) }
|
|
fn layout_y (&self, to: XYWH<O::Unit>) -> O::Unit { self.as_ref().map(|c|c.layout_y(to)).unwrap_or(to.y()) }
|
|
fn layout_w_min (&self, to: XYWH<O::Unit>) -> O::Unit { self.as_ref().map(|c|c.layout_w_min(to)).unwrap_or(0.into()) }
|
|
fn layout_w_max (&self, to: XYWH<O::Unit>) -> O::Unit { self.as_ref().map(|c|c.layout_w_max(to)).unwrap_or(0.into()) }
|
|
fn layout_w (&self, to: XYWH<O::Unit>) -> O::Unit { self.as_ref().map(|c|c.layout_w(to)).unwrap_or(0.into()) }
|
|
fn layout_h_min (&self, to: XYWH<O::Unit>) -> O::Unit { self.as_ref().map(|c|c.layout_h_min(to)).unwrap_or(0.into()) }
|
|
fn layout_h_max (&self, to: XYWH<O::Unit>) -> O::Unit { self.as_ref().map(|c|c.layout_h_max(to)).unwrap_or(0.into()) }
|
|
fn layout_h (&self, to: XYWH<O::Unit>) -> O::Unit { self.as_ref().map(|c|c.layout_h(to)).unwrap_or(0.into()) }
|
|
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> {
|
|
let xywh = XYWH(self.layout_x(to), self.layout_y(to), self.layout_w(to), self.layout_h(to));
|
|
self.as_ref().map(|c|c.layout(xywh)).unwrap_or(XYWH(to.x(), to.y(), 0.into(), 0.into()))
|
|
}
|
|
}
|
|
impl<O: Out, D: Content<O>> HasContent<O> for Bounded<O, D> {
|
|
fn content (&self) -> impl Content<O> { &self.1 }
|
|
}
|
|
impl<O: Out, T: Draw<O>> Draw<O> for Bounded<O, T> {
|
|
fn draw (&self, to: &mut O) {
|
|
let area = to.area();
|
|
*to.area_mut() = self.0;
|
|
self.1.draw(to);
|
|
*to.area_mut() = area;
|
|
}
|
|
}
|
|
impl<O: Out, T: Content<O>> When<O, T> {
|
|
/// Create a binary condition.
|
|
pub const fn new (c: bool, a: T) -> Self { Self(c, a, PhantomData) }
|
|
}
|
|
impl<O: Out, T: Layout<O>> Layout<O> for When<O, T> {
|
|
fn layout (&self, to: XYWH<O::Unit>) -> XYWH<O::Unit> {
|
|
let Self(cond, item, ..) = self;
|
|
if *cond { item.layout(to) } else { XYWH::<O::Unit>::zero().into() }
|
|
}
|
|
}
|
|
impl<O: Out, T: Content<O>> Draw<O> for When<O, T> {
|
|
fn draw (&self, to: &mut O) {
|
|
let Self(cond, item, ..) = self;
|
|
if *cond { Bounded(self.layout(to.area()), item).draw(to) }
|
|
}
|
|
}
|
|
impl<E: Out, A: Content<E>, B: Content<E>> Either<E, A, B> {
|
|
/// Create a ternary view condition.
|
|
pub const fn new (c: bool, a: A, b: B) -> Self {
|
|
Self(c, a, b, PhantomData)
|
|
}
|
|
}
|
|
impl<E: Out, A: Layout<E>, B: Layout<E>> Layout<E> for Either<E, A, B> {
|
|
fn layout (&self, to: XYWH<E::Unit>) -> XYWH<E::Unit> {
|
|
let Self(cond, a, b, ..) = self;
|
|
if *cond { a.layout(to) } else { b.layout(to) }
|
|
}
|
|
}
|
|
impl<E: Out, A: Content<E>, B: Content<E>> Draw<E> for Either<E, A, B> {
|
|
fn draw (&self, to: &mut E) {
|
|
let Self(cond, a, b, ..) = self;
|
|
let area = self.layout(to.area());
|
|
if *cond { Bounded(area, a).draw(to) } else { Bounded(area, b).draw(to) }
|
|
}
|
|
}
|
|
|
|
macro_rules! layout_op_xy (
|
|
// Variant for layout ops that take no coordinates
|
|
(0: $T: ident) => {
|
|
impl<A> $T<A> {
|
|
#[inline] pub const fn inner (&self) -> &A {
|
|
match self { Self::X(c) | Self::Y(c) | Self::XY(c) => c }
|
|
}
|
|
}
|
|
impl<O: Out, T: Content<O>> Draw<O> for $T<T> {
|
|
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
|
}
|
|
};
|
|
// Variant for layout ops that take one coordinate
|
|
(1: $T: ident) => {
|
|
impl<U, A> $T<U, A> {
|
|
#[inline] pub const fn inner (&self) -> &A {
|
|
match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c, }
|
|
}
|
|
}
|
|
impl<O: Out, T: Content<O>> Draw<O> for $T<O::Unit, T> {
|
|
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
|
}
|
|
impl<U: Coord, A> $T<U, A> {
|
|
#[inline] pub fn dx (&self) -> U {
|
|
match self { Self::X(x, _) | Self::XY(x, ..) => *x, _ => 0.into() }
|
|
}
|
|
#[inline] pub fn dy (&self) -> U {
|
|
match self { Self::Y(y, _) | Self::XY(y, ..) => *y, _ => 0.into() }
|
|
}
|
|
}
|
|
};
|
|
(1 opt: $T: ident) => {
|
|
impl<U, A> $T<U, A> {
|
|
#[inline] pub const fn inner (&self) -> &A {
|
|
match self { Self::X(_, c) | Self::Y(_, c) | Self::XY(_, _, c) => c, }
|
|
}
|
|
}
|
|
impl<O: Out, T: Content<O>> Draw<O> for $T<O::Unit, T> {
|
|
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
|
}
|
|
impl<U: Coord, A> $T<U, A> {
|
|
#[inline] pub const fn dx (&self) -> Option<U> {
|
|
match self { Self::X(x, _) | Self::XY(x, ..) => Some(*x), _ => None }
|
|
}
|
|
#[inline] pub const fn dy (&self) -> Option<U> {
|
|
match self { Self::Y(y, _) | Self::XY(y, ..) => Some(*y), _ => None }
|
|
}
|
|
}
|
|
};
|
|
);
|
|
// Implement layout op that increments X and/or Y by fixed amount.
|
|
macro_rules! push_pull(($T:ident: $method: ident)=>{
|
|
layout_op_xy!(1: $T);
|
|
impl<O: Out, T: Layout<O>> Layout<O> for $T<O::Unit, T> {
|
|
fn layout_x (&self, area: XYWH<O::Unit>) -> O::Unit { area.x().$method(self.dx()) }
|
|
fn layout_y (&self, area: XYWH<O::Unit>) -> O::Unit { area.y().$method(self.dy()) }
|
|
}
|
|
});
|
|
push_pull!(Push: plus);
|
|
push_pull!(Pull: minus);
|
|
|
|
layout_op_xy!(0: Fill);
|
|
|
|
impl<O: Out, T: Layout<O>> Layout<O> for Fill<T> {
|
|
fn layout_x (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dx() { area.x() } else { self.inner().layout_x(area) } }
|
|
fn layout_y (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dy() { area.y() } else { self.inner().layout_y(area) } }
|
|
fn layout_w (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dx() { area.w() } else { self.inner().layout_w(area) } }
|
|
fn layout_w_min (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dx() { area.w() } else { self.inner().layout_w_min(area) } }
|
|
fn layout_w_max (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dx() { area.w() } else { self.inner().layout_w_max(area) } }
|
|
fn layout_h (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dy() { area.h() } else { self.inner().layout_h(area) } }
|
|
fn layout_h_min (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dy() { area.h() } else { self.inner().layout_h_min(area) } }
|
|
fn layout_h_max (&self, area: XYWH<O::Unit>) -> O::Unit { if self.dy() { area.h() } else { self.inner().layout_h_max(area) } }
|
|
}
|
|
|
|
impl<A> Fill<A> {
|
|
#[inline] pub const fn dx (&self) -> bool { matches!(self, Self::X(_) | Self::XY(_)) }
|
|
#[inline] pub const fn dy (&self) -> bool { matches!(self, Self::Y(_) | Self::XY(_)) }
|
|
}
|
|
|
|
layout_op_xy!(1 opt: Fixed);
|
|
|
|
impl<O: Out, T: Layout<O>> Layout<O> for Fixed<O::Unit, T> {
|
|
fn layout_w (&self, area: XYWH<O::Unit>) -> O::Unit { self.dx().unwrap_or(self.inner().layout_w(area)) }
|
|
fn layout_w_min (&self, area: XYWH<O::Unit>) -> O::Unit { self.dx().unwrap_or(self.inner().layout_w_min(area)) }
|
|
fn layout_w_max (&self, area: XYWH<O::Unit>) -> O::Unit { self.dx().unwrap_or(self.inner().layout_w_max(area)) }
|
|
fn layout_h (&self, area: XYWH<O::Unit>) -> O::Unit { self.dy().unwrap_or(self.inner().layout_h(area)) }
|
|
fn layout_h_min (&self, area: XYWH<O::Unit>) -> O::Unit { self.dy().unwrap_or(self.inner().layout_h_min(area)) }
|
|
fn layout_h_max (&self, area: XYWH<O::Unit>) -> O::Unit { self.dy().unwrap_or(self.inner().layout_h_max(area)) }
|
|
}
|
|
|
|
layout_op_xy!(1 opt: Max);
|
|
|
|
impl<E: Out, T: Layout<E>> Layout<E> for Max<E::Unit, T> {
|
|
fn layout (&self, area: XYWH<E::Unit>) -> XYWH<E::Unit> {
|
|
let XYWH(x, y, w, h) = self.inner().layout(area);
|
|
match self {
|
|
Self::X(mw, _) => XYWH(x, y, w.min(*mw), h ),
|
|
Self::Y(mh, _) => XYWH(x, y, w, h.min(*mh)),
|
|
Self::XY(mw, mh, _) => XYWH(x, y, w.min(*mw), h.min(*mh)),
|
|
}
|
|
}
|
|
}
|
|
|
|
layout_op_xy!(1 opt: Min);
|
|
|
|
impl<E: Out, T: Layout<E>> Layout<E> for Min<E::Unit, T> {
|
|
fn layout (&self, area: XYWH<E::Unit>) -> XYWH<E::Unit> {
|
|
let XYWH(x, y, w, h) = self.inner().layout(area);
|
|
match self {
|
|
Self::X(mw, _) => XYWH(x, y, w.max(*mw), h),
|
|
Self::Y(mh, _) => XYWH(x, y, w, h.max(*mh)),
|
|
Self::XY(mw, mh, _) => XYWH(x, y, w.max(*mw), h.max(*mh)),
|
|
}
|
|
}
|
|
}
|
|
|
|
layout_op_xy!(1 opt: Expand);
|
|
|
|
impl<O: Out, T: Layout<O>> Layout<O> for Expand<O::Unit, T> {
|
|
fn layout_w (&self, to: XYWH<O::Unit>) -> O::Unit {
|
|
self.inner().layout_w(to).plus(self.dx().unwrap_or_default())
|
|
}
|
|
fn layout_h (&self, to: XYWH<O::Unit>) -> O::Unit {
|
|
self.inner().layout_w(to).plus(self.dy().unwrap_or_default())
|
|
}
|
|
}
|
|
|
|
// FIXME: why they differ?
|
|
|
|
layout_op_xy!(1 opt: Shrink);
|
|
|
|
impl<E: Out, T: Layout<E>> Layout<E> for Shrink<E::Unit, T> {
|
|
fn layout (&self, to: XYWH<E::Unit>) -> XYWH<E::Unit> {
|
|
let area = self.inner().layout(to);
|
|
let dx = self.dx().unwrap_or_default();
|
|
let dy = self.dy().unwrap_or_default();
|
|
XYWH(area.x(), area.y(), area.w().minus(dx), area.h().minus(dy))
|
|
}
|
|
}
|
|
|
|
impl<T> Align<T> {
|
|
#[inline] pub const fn c (a: T) -> Self { Self(Alignment::Center, a) }
|
|
#[inline] pub const fn x (a: T) -> Self { Self(Alignment::X, a) }
|
|
#[inline] pub const fn y (a: T) -> Self { Self(Alignment::Y, a) }
|
|
#[inline] pub const fn n (a: T) -> Self { Self(Alignment::N, a) }
|
|
#[inline] pub const fn s (a: T) -> Self { Self(Alignment::S, a) }
|
|
#[inline] pub const fn e (a: T) -> Self { Self(Alignment::E, a) }
|
|
#[inline] pub const fn w (a: T) -> Self { Self(Alignment::W, a) }
|
|
#[inline] pub const fn nw (a: T) -> Self { Self(Alignment::NW, a) }
|
|
#[inline] pub const fn sw (a: T) -> Self { Self(Alignment::SW, a) }
|
|
#[inline] pub const fn ne (a: T) -> Self { Self(Alignment::NE, a) }
|
|
#[inline] pub const fn se (a: T) -> Self { Self(Alignment::SE, a) }
|
|
}
|
|
|
|
impl<O: Out, T: Content<O>> Draw<O> for Align<T> {
|
|
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), &self.1).draw(to) }
|
|
}
|
|
|
|
impl<O: Out, T: Layout<O>> Layout<O> for Align<T> {
|
|
fn layout_x (&self, to: XYWH<O::Unit>) -> O::Unit {
|
|
match self.0 {
|
|
NW | W | SW => to.x(),
|
|
N | Center | S => to.x().plus(to.w() / 2.into()).minus(self.1.layout_w(to) / 2.into()),
|
|
NE | E | SE => to.x().plus(to.w()).minus(self.1.layout_w(to)),
|
|
_ => todo!(),
|
|
}
|
|
}
|
|
fn layout_y (&self, to: XYWH<O::Unit>) -> O::Unit {
|
|
match self.0 {
|
|
NW | N | NE => to.y(),
|
|
W | Center | E => to.y().plus(to.h() / 2.into()).minus(self.1.layout_h(to) / 2.into()),
|
|
SW | S | SE => to.y().plus(to.h()).minus(self.1.layout_h(to)),
|
|
_ => todo!(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<U, A> Pad<U, A> {
|
|
#[inline] pub const fn inner (&self) -> &A {
|
|
use Pad::*;
|
|
match self { X(_, c) | Y(_, c) | XY(_, _, c) => c, }
|
|
}
|
|
}
|
|
|
|
impl<U: Coord, T> Pad<U, T> {
|
|
#[inline] pub fn dx (&self) -> U {
|
|
use Pad::*;
|
|
match self { X(x, _) => *x, Y(_, _) => 0.into(), XY(x, _, _) => *x, }
|
|
}
|
|
#[inline] pub fn dy (&self) -> U {
|
|
use Pad::*;
|
|
match self { X(_, _) => 0.into(), Y(y, _) => *y, XY(_, y, _) => *y, }
|
|
}
|
|
}
|
|
|
|
impl<O: Out, T: Content<O>> Draw<O> for Pad<O::Unit, T> {
|
|
fn draw (&self, to: &mut O) { Bounded(self.layout(to.area()), self.inner()).draw(to) }
|
|
}
|
|
|
|
impl<O: Out, T: Layout<O>> Layout<O> for Pad<O::Unit, T> {
|
|
fn layout_x (&self, area: XYWH<O::Unit>) -> O::Unit { area.x().plus(self.dx()) }
|
|
fn layout_y (&self, area: XYWH<O::Unit>) -> O::Unit { area.x().plus(self.dx()) }
|
|
fn layout_w (&self, area: XYWH<O::Unit>) -> O::Unit { area.w().minus(self.dx() * 2.into()) }
|
|
fn layout_h (&self, area: XYWH<O::Unit>) -> O::Unit { area.h().minus(self.dy() * 2.into()) }
|
|
}
|
|
|
|
impl<Head, Tail> Bsp<Head, Tail> {
|
|
#[inline] pub const fn n (a: Head, b: Tail) -> Self { Self(North, a, b) }
|
|
#[inline] pub const fn s (a: Head, b: Tail) -> Self { Self(South, a, b) }
|
|
#[inline] pub const fn e (a: Head, b: Tail) -> Self { Self(East, a, b) }
|
|
#[inline] pub const fn w (a: Head, b: Tail) -> Self { Self(West, a, b) }
|
|
#[inline] pub const fn a (a: Head, b: Tail) -> Self { Self(Above, a, b) }
|
|
#[inline] pub const fn b (a: Head, b: Tail) -> Self { Self(Below, a, b) }
|
|
}
|
|
|
|
impl<O: Out, Head: Content<O>, Tail: Content<O>> Draw<O> for Bsp<Head, Tail> {
|
|
fn draw (&self, to: &mut O) {
|
|
let [a, b, _] = bsp_areas(to.area(), self.0, &self.1, &self.2);
|
|
//panic!("{a:?} {b:?}");
|
|
if self.0 == Below {
|
|
to.place_at(a, &self.1);
|
|
to.place_at(b, &self.2);
|
|
} else {
|
|
to.place_at(b, &self.2);
|
|
to.place_at(a, &self.1);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<O: Out, Head: Layout<O>, Tail: Layout<O>> Layout<O> for Bsp<Head, Tail> {
|
|
fn layout_w (&self, area: XYWH<O::Unit>) -> O::Unit {
|
|
match self.0 {
|
|
Above | Below | North | South => self.1.layout_w(area).max(self.2.layout_w(area)),
|
|
East | West => self.1.layout_w_min(area).plus(self.2.layout_w(area)),
|
|
}
|
|
}
|
|
fn layout_w_min (&self, area: XYWH<O::Unit>) -> O::Unit {
|
|
match self.0 {
|
|
Above | Below | North | South => self.1.layout_w_min(area).max(self.2.layout_w_min(area)),
|
|
East | West => self.1.layout_w_min(area).plus(self.2.layout_w_min(area)),
|
|
}
|
|
}
|
|
fn layout_w_max (&self, area: XYWH<O::Unit>) -> O::Unit {
|
|
match self.0 {
|
|
Above | Below | North | South => self.1.layout_w_max(area).max(self.2.layout_w_max(area)),
|
|
East | West => self.1.layout_w_max(area).plus(self.2.layout_w_max(area)),
|
|
}
|
|
}
|
|
fn layout_h (&self, area: XYWH<O::Unit>) -> O::Unit {
|
|
match self.0 {
|
|
Above | Below | East | West => self.1.layout_h(area).max(self.2.layout_h(area)),
|
|
North | South => self.1.layout_h(area).plus(self.2.layout_h(area)),
|
|
}
|
|
}
|
|
fn layout_h_min (&self, area: XYWH<O::Unit>) -> O::Unit {
|
|
match self.0 {
|
|
Above | Below | East | West => self.1.layout_h_min(area).max(self.2.layout_h_min(area)),
|
|
North | South => self.1.layout_h_min(area).plus(self.2.layout_h_min(area)),
|
|
}
|
|
}
|
|
fn layout_h_max (&self, area: XYWH<O::Unit>) -> O::Unit {
|
|
match self.0 {
|
|
Above | Below | North | South => self.1.layout_h_max(area).max(self.2.layout_h_max(area)),
|
|
East | West => self.1.layout_h_max(area).plus(self.2.layout_h_max(area)),
|
|
}
|
|
}
|
|
fn layout (&self, area: XYWH<O::Unit>) -> XYWH<O::Unit> {
|
|
bsp_areas(area, self.0, &self.1, &self.2)[2]
|
|
}
|
|
}
|
|
|
|
fn bsp_areas <O: Out, A: Layout<O>, B: Layout<O>> (
|
|
area: XYWH<O::Unit>,
|
|
direction: Direction,
|
|
a: &A,
|
|
b: &B,
|
|
) -> [XYWH<O::Unit>;3] {
|
|
let XYWH(x, y, w, h) = area;
|
|
let WH(aw, ah) = a.layout(area).wh();
|
|
let WH(bw, bh) = b.layout(match direction {
|
|
South => XYWH(x, y + ah, w, h.minus(ah)),
|
|
North => XYWH(x, y, w, h.minus(ah)),
|
|
East => XYWH(x + aw, y, w.minus(aw), h),
|
|
West => XYWH(x, y, w.minus(aw), h),
|
|
Above => area,
|
|
Below => area,
|
|
}).wh();
|
|
match direction {
|
|
Above | Below => {
|
|
let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah.max(bh)]);
|
|
let a = XYWH((x + w/2.into()).minus(aw/2.into()), (y + h/2.into()).minus(ah/2.into()), aw, ah);
|
|
let b = XYWH((x + w/2.into()).minus(bw/2.into()), (y + h/2.into()).minus(bh/2.into()), bw, bh);
|
|
[a.into(), b.into(), XYWH(x, y, w, h)]
|
|
},
|
|
South => {
|
|
let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah + bh]);
|
|
let a = XYWH((x + w/2.into()).minus(aw/2.into()), y, aw, ah);
|
|
let b = XYWH((x + w/2.into()).minus(bw/2.into()), y + ah, bw, bh);
|
|
[a.into(), b.into(), XYWH(x, y, w, h)]
|
|
},
|
|
North => {
|
|
let XYWH(x, y, w, h) = area.centered_xy([aw.max(bw), ah + bh]);
|
|
let a = XYWH((x + (w/2.into())).minus(aw/2.into()), y + bh, aw, ah);
|
|
let b = XYWH((x + (w/2.into())).minus(bw/2.into()), y, bw, bh);
|
|
[a.into(), b.into(), XYWH(x, y, w, h)]
|
|
},
|
|
East => {
|
|
let XYWH(x, y, w, h) = area.centered_xy([aw + bw, ah.max(bh)]);
|
|
let a = XYWH(x, (y + h/2.into()).minus(ah/2.into()), aw, ah);
|
|
let b = XYWH(x + aw, (y + h/2.into()).minus(bh/2.into()), bw, bh);
|
|
[a.into(), b.into(), XYWH(x, y, w, h)]
|
|
},
|
|
West => {
|
|
let XYWH(x, y, w, h) = area.centered_xy([aw + bw, ah.max(bh)]);
|
|
let a = XYWH(x + bw, (y + h/2.into()).minus(ah/2.into()), aw, ah);
|
|
let b = XYWH(x, (y + h/2.into()).minus(bh/2.into()), bw, bh);
|
|
[a.into(), b.into(), XYWH(x, y, w, h)]
|
|
},
|
|
}
|
|
}
|
|
|
|
impl<'a, O, A, B, I, F, G> Map<O, A, B, I, F, G> where
|
|
I: Iterator<Item = A> + Send + Sync + 'a,
|
|
F: Fn() -> I + Send + Sync + 'a,
|
|
{
|
|
pub const fn new (get_iter: F, get_item: G) -> Self {
|
|
Self {
|
|
__: PhantomData,
|
|
get_iter,
|
|
get_item
|
|
}
|
|
}
|
|
}
|
|
|
|
macro_rules! impl_map_direction (($name:ident, $axis:ident, $align:ident)=>{
|
|
impl<'a, O, A, B, I, F> Map<
|
|
O, A, Push<O::Unit, Align<Fixed<O::Unit, Fill<B>>>>, I, F, fn(A, usize)->B
|
|
> where
|
|
O: Out,
|
|
B: Draw<O>,
|
|
I: Iterator<Item = A> + Send + Sync + 'a,
|
|
F: Fn() -> I + Send + Sync + 'a
|
|
{
|
|
pub const fn $name (
|
|
size: O::Unit,
|
|
get_iter: F,
|
|
get_item: impl Fn(A, usize)->B + Send + Sync
|
|
) -> Map<
|
|
O, A,
|
|
Push<O::Unit, Align<Fixed<O::Unit, B>>>,
|
|
I, F,
|
|
impl Fn(A, usize)->Push<O::Unit, Align<Fixed<O::Unit, B>>> + Send + Sync
|
|
> {
|
|
Map {
|
|
__: PhantomData,
|
|
get_iter,
|
|
get_item: move |item: A, index: usize|{
|
|
// FIXME: multiply
|
|
let mut push: O::Unit = O::Unit::from(0u16);
|
|
for _ in 0..index {
|
|
push = push + size;
|
|
}
|
|
Push::$axis(push, Align::$align(Fixed::$axis(size, get_item(item, index))))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
impl_map_direction!(east, X, w);
|
|
impl_map_direction!(south, Y, n);
|
|
impl_map_direction!(west, X, e);
|
|
impl_map_direction!(north, Y, s);
|
|
|
|
impl<'a, O, A, B, I, F, G> Layout<O> for Map<O, A, B, I, F, G> where
|
|
O: Out,
|
|
B: Layout<O>,
|
|
I: Iterator<Item = A> + Send + Sync + 'a,
|
|
F: Fn() -> I + Send + Sync + 'a,
|
|
G: Fn(A, usize)->B + Send + Sync
|
|
{
|
|
fn layout (&self, area: XYWH<O::Unit>) -> XYWH<O::Unit> {
|
|
let Self { get_iter, get_item, .. } = self;
|
|
let mut index = 0;
|
|
let XY(mut min_x, mut min_y) = area.centered();
|
|
let XY(mut max_x, mut max_y) = area.center();
|
|
for item in get_iter() {
|
|
let XYWH(x, y, w, h) = get_item(item, index).layout(area);
|
|
min_x = min_x.min(x);
|
|
min_y = min_y.min(y);
|
|
max_x = max_x.max(x + w);
|
|
max_y = max_y.max(y + h);
|
|
index += 1;
|
|
}
|
|
let w = max_x - min_x;
|
|
let h = max_y - min_y;
|
|
//[min_x.into(), min_y.into(), w.into(), h.into()].into()
|
|
area.centered_xy([w.into(), h.into()])
|
|
}
|
|
}
|
|
|
|
impl<'a, O, A, B, I, F, G> Draw<O> for Map<O, A, B, I, F, G> where
|
|
O: Out,
|
|
B: Content<O>,
|
|
I: Iterator<Item = A> + Send + Sync + 'a,
|
|
F: Fn() -> I + Send + Sync + 'a,
|
|
G: Fn(A, usize)->B + Send + Sync
|
|
{
|
|
fn draw (&self, to: &mut O) {
|
|
let Self { get_iter, get_item, .. } = self;
|
|
let mut index = 0;
|
|
let area = self.layout(to.area());
|
|
for item in get_iter() {
|
|
let item = get_item(item, index);
|
|
//to.place_at(area.into(), &item);
|
|
to.place_at(item.layout(area), &item);
|
|
index += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
impl_default!(PerfModel: Self {
|
|
enabled: true,
|
|
clock: quanta::Clock::new(),
|
|
used: Default::default(),
|
|
window: Default::default(),
|
|
});
|
|
|
|
impl PerfModel {
|
|
pub fn get_t0 (&self) -> Option<u64> {
|
|
if self.enabled {
|
|
Some(self.clock.raw())
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
pub fn get_t1 (&self, t0: Option<u64>) -> Option<std::time::Duration> {
|
|
if let Some(t0) = t0 {
|
|
if self.enabled {
|
|
Some(self.clock.delta(t0, self.clock.raw()))
|
|
} else {
|
|
None
|
|
}
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
pub fn update (&self, t0: Option<u64>, microseconds: f64) {
|
|
if let Some(t0) = t0 {
|
|
let t1 = self.clock.raw();
|
|
self.used.store(self.clock.delta_as_nanos(t0, t1) as f64, Relaxed);
|
|
self.window.store(microseconds, Relaxed,);
|
|
}
|
|
}
|
|
pub fn percentage (&self) -> Option<f64> {
|
|
let window = self.window.load(Relaxed) * 1000.0;
|
|
if window > 0.0 {
|
|
let used = self.used.load(Relaxed);
|
|
Some(100.0 * used / window)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
mod xywh {
|
|
use crate::*;
|
|
impl<N: Coord> HasXY<N> for XY<N> {
|
|
fn x (&self) -> N { self.0 }
|
|
fn y (&self) -> N { self.1 }
|
|
}
|
|
impl<N: Coord> HasXY<N> for XYWH<N> {
|
|
fn x (&self) -> N { self.0 }
|
|
fn y (&self) -> N { self.1 }
|
|
}
|
|
impl<O: Out> HasXY<O::Unit> for O {
|
|
// X coordinate of output area
|
|
#[inline] fn x (&self) -> O::Unit { self.area().x() }
|
|
// Y coordinate of output area
|
|
#[inline] fn y (&self) -> O::Unit { self.area().y() }
|
|
}
|
|
impl<N: Coord> HasWH<N> for WH<N> {
|
|
fn w (&self) -> N { self.0 }
|
|
fn h (&self) -> N { self.1 }
|
|
}
|
|
impl<N: Coord> HasWH<N> for XYWH<N> {
|
|
fn w (&self) -> N { self.2 }
|
|
fn h (&self) -> N { self.3 }
|
|
}
|
|
impl<O: Out> HasWH<O::Unit> for O {
|
|
// Width of output area
|
|
#[inline] fn w (&self) -> O::Unit { self.area().w() }
|
|
// Height of output area
|
|
#[inline] fn h (&self) -> O::Unit { self.area().h() }
|
|
}
|
|
impl<N: Coord> WH<N> {
|
|
pub fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] }
|
|
pub fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] }
|
|
pub fn expect_min (&self, w: N, h: N) -> Usually<&Self> {
|
|
if self.w() < w || self.h() < h { return Err(format!("min {w}x{h}").into()) }
|
|
Ok(self)
|
|
}
|
|
}
|
|
impl<N: Coord> XYWH<N> {
|
|
pub fn zero () -> Self {
|
|
Self(0.into(), 0.into(), 0.into(), 0.into())
|
|
}
|
|
pub fn x2 (&self) -> N {
|
|
self.x().plus(self.w())
|
|
}
|
|
pub fn y2 (&self) -> N {
|
|
self.y().plus(self.h())
|
|
}
|
|
pub fn with_w (&self, w: N) -> XYWH<N> {
|
|
Self(self.x(), self.y(), w, self.h())
|
|
}
|
|
pub fn with_h (&self, h: N) -> XYWH<N> {
|
|
Self(self.x(), self.y(), self.w(), h)
|
|
}
|
|
pub fn lrtb (&self) -> [N;4] {
|
|
[self.x(), self.x2(), self.y(), self.y2()]
|
|
}
|
|
pub fn clipped_w (&self, w: N) -> XYWH<N> {
|
|
Self(self.x(), self.y(), self.w().min(w), self.h())
|
|
}
|
|
pub fn clipped_h (&self, h: N) -> XYWH<N> {
|
|
Self(self.x(), self.y(), self.w(), self.h().min(h))
|
|
}
|
|
pub fn clipped (&self, wh: WH<N>) -> XYWH<N> {
|
|
Self(self.x(), self.y(), wh.w(), wh.h())
|
|
}
|
|
/// Iterate over every covered X coordinate.
|
|
pub fn iter_x (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
|
let Self(x, _, w, _) = *self;
|
|
x..(x+w)
|
|
}
|
|
/// Iterate over every covered Y coordinate.
|
|
pub fn iter_y (&self) -> impl Iterator<Item = N> where N: std::iter::Step {
|
|
let Self(_, y, _, h) = *self;
|
|
y..(y+h)
|
|
}
|
|
pub fn center (&self) -> XY<N> {
|
|
let Self(x, y, w, h) = self;
|
|
XY(self.x().plus(self.w()/2.into()), self.y().plus(self.h()/2.into()))
|
|
}
|
|
pub fn centered (&self) -> XY<N> {
|
|
let Self(x, y, w, h) = *self;
|
|
XY(x.minus(w/2.into()), y.minus(h/2.into()))
|
|
}
|
|
pub fn centered_x (&self, n: N) -> XYWH<N> {
|
|
let Self(x, y, w, h) = *self;
|
|
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), y.plus(h / 2.into()), n, 1.into())
|
|
}
|
|
pub fn centered_y (&self, n: N) -> XYWH<N> {
|
|
let Self(x, y, w, h) = *self;
|
|
XYWH(x.plus(w / 2.into()), (y.plus(h / 2.into())).minus(n / 2.into()), 1.into(), n)
|
|
}
|
|
pub fn centered_xy (&self, [n, m]: [N;2]) -> XYWH<N> {
|
|
let Self(x, y, w, h) = *self;
|
|
XYWH((x.plus(w / 2.into())).minus(n / 2.into()), (y.plus(h / 2.into())).minus(m / 2.into()), n, m)
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "tui")] pub use self::tui_impls::*;
|
|
#[cfg(feature = "tui")] mod tui_impls {
|
|
use crate::*;
|
|
use unicode_width::{UnicodeWidthStr, UnicodeWidthChar};
|
|
use rand::distributions::uniform::UniformSampler;
|
|
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 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_resized(&mut*self.backend.write().unwrap(), &mut*self.buffer.write().unwrap(), size);
|
|
tui_redrawn(&mut*self.backend.write().unwrap(), &mut*self.buffer.write().unwrap(), &mut buffer);
|
|
buffer
|
|
}
|
|
/// 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 amm in the engine.
|
|
pub fn run <T> (mut self, join: bool, state: &Arc<RwLock<T>>) -> Usually<Arc<Self>> where
|
|
T: Handle<Tui> + Draw<Tui> + Send + Sync + 'static
|
|
{
|
|
self.setup()?;
|
|
let tui = Arc::new(self);
|
|
let _input_thread = tui_input(&tui, state, Duration::from_millis(100))?;
|
|
let render_thread = tui_output(&tui, state, Duration::from_millis(10))?;
|
|
if join {
|
|
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)
|
|
}
|
|
}
|
|
|
|
/// Spawn the input thread.
|
|
pub fn tui_input <T: Handle<Tui> + Send + Sync + 'static> (
|
|
engine: &Arc<Tui>, state: &Arc<RwLock<T>>, poll: Duration
|
|
) -> Result<TuiThread, std::io::Error> {
|
|
let state = state.clone();
|
|
let engine = engine.clone();
|
|
TuiThread::new_poll(engine.exited.clone(), poll, move |_| {
|
|
let event = read().unwrap();
|
|
match event {
|
|
Event::Key(KeyEvent {
|
|
modifiers: KeyModifiers::CONTROL,
|
|
code: KeyCode::Char('c'),
|
|
kind: KeyEventKind::Press,
|
|
state: KeyEventState::NONE
|
|
}) => {
|
|
engine.exited.store(true, Relaxed);
|
|
},
|
|
_ => {
|
|
let event = TuiEvent::from_crossterm(event);
|
|
if let Err(e) = state.write().unwrap().handle(&engine) {
|
|
panic!("{e}")
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Spawn the output thread.
|
|
pub fn tui_output <T: Draw<Tui> + Send + Sync + 'static> (
|
|
engine: &Arc<Tui>, state: &Arc<RwLock<T>>, sleep: Duration
|
|
) -> Result<TuiThread, std::io::Error> {
|
|
let state = state.clone();
|
|
let mut engine = engine.clone();
|
|
let WH(width, height) = tui_wh(&engine);
|
|
let mut buffer = Buffer::empty(Rect { x: 0, y: 0, width, height });
|
|
TuiThread::new_sleep(engine.exited.clone(), sleep, move |perf| {
|
|
let WH(width, height) = tui_wh(&engine);
|
|
if let Ok(state) = state.try_read() {
|
|
let size = Rect { x: 0, y: 0, width, height };
|
|
if buffer.area != size {
|
|
engine.backend.write().unwrap()
|
|
.clear_region(ClearType::All).expect("pre-frame clear region failed");
|
|
buffer.resize(size);
|
|
buffer.reset();
|
|
}
|
|
state.draw(&engine);
|
|
buffer = engine.flip(*engine.buffer.write().unwrap(), size);
|
|
}
|
|
let timer = format!("{:>3.3}ms", perf.used.load(Relaxed));
|
|
buffer.set_string(0, 0, &timer, Style::default());
|
|
})
|
|
}
|
|
|
|
fn tui_wh (engine: &Tui) -> WH<u16> {
|
|
let Size { width, height } = engine.backend.read().unwrap().size().expect("get size failed");
|
|
WH(width, height)
|
|
}
|
|
|
|
impl Input for Tui {
|
|
type Event = TuiEvent;
|
|
type Handled = bool;
|
|
fn event (&self) -> &TuiEvent {
|
|
self.event.as_ref().expect("input.event called outside of input handler")
|
|
}
|
|
}
|
|
|
|
impl Done for Tui {
|
|
fn done (&self) { self.exited.store(true, Relaxed); }
|
|
fn is_done (&self) -> bool { self.exited.fetch_and(true, Relaxed) }
|
|
}
|
|
|
|
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 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 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 Out for Tui {
|
|
type Unit = u16;
|
|
#[inline] fn area (&self) -> XYWH<u16> { self.area }
|
|
#[inline] fn area_mut (&mut self) -> &mut XYWH<u16> { &mut self.area }
|
|
#[inline] fn place_at <'t, T: Draw<Self> + ?Sized> (&mut self, area: XYWH<u16>, content: &'t T) {
|
|
let last = self.area();
|
|
*self.area_mut() = area;
|
|
content.draw(self);
|
|
*self.area_mut() = last;
|
|
}
|
|
}
|
|
|
|
impl Tui {
|
|
#[inline] pub fn with_rect (&mut self, area: XYWH<u16>) -> &mut Self { self.area = area; self }
|
|
pub fn update (&mut self, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)) {
|
|
tui_update(&mut*self.buffer.write().unwrap(), area, callback);
|
|
}
|
|
pub fn fill_char (&mut self, area: XYWH<u16>, c: char) { self.update(area, &|cell,_,_|{cell.set_char(c);}) }
|
|
pub fn fill_bg (&mut self, area: XYWH<u16>, color: Color) { self.update(area, &|cell,_,_|{cell.set_bg(color);}) }
|
|
pub fn fill_fg (&mut self, area: XYWH<u16>, color: Color) { self.update(area, &|cell,_,_|{cell.set_fg(color);}) }
|
|
pub fn fill_mod (&mut self, area: XYWH<u16>, on: bool, modifier: Modifier) {
|
|
if on {
|
|
self.update(area, &|cell,_,_|cell.modifier.insert(modifier))
|
|
} else {
|
|
self.update(area, &|cell,_,_|cell.modifier.remove(modifier))
|
|
}
|
|
}
|
|
pub fn fill_bold (&mut self, area: XYWH<u16>, on: bool) { self.fill_mod(area, on, Modifier::BOLD) }
|
|
pub fn fill_reversed (&mut self, area: XYWH<u16>, on: bool) { self.fill_mod(area, on, Modifier::REVERSED) }
|
|
pub fn fill_crossed_out (&mut self, area: XYWH<u16>, on: bool) { self.fill_mod(area, on, Modifier::CROSSED_OUT) }
|
|
pub fn fill_ul (&mut self, area: XYWH<u16>, color: Option<Color>) {
|
|
if let Some(color) = color {
|
|
self.update(area, &|cell,_,_|{
|
|
cell.modifier.insert(ratatui::prelude::Modifier::UNDERLINED);
|
|
cell.underline_color = color;
|
|
})
|
|
} else {
|
|
self.update(area, &|cell,_,_|{
|
|
cell.modifier.remove(ratatui::prelude::Modifier::UNDERLINED);
|
|
})
|
|
}
|
|
}
|
|
pub fn tint_all (&mut self, fg: Color, bg: Color, modifier: Modifier) {
|
|
for cell in self.buffer.write().unwrap().content.iter_mut() {
|
|
cell.fg = fg;
|
|
cell.bg = bg;
|
|
cell.modifier = modifier;
|
|
}
|
|
}
|
|
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());
|
|
let mut buf = self.buffer.write().unwrap();
|
|
if x < buf.area.width && y < buf.area.height {
|
|
buf.set_string(x, y, text, style);
|
|
}
|
|
}
|
|
/// 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) {
|
|
let text = text.as_ref();
|
|
let mut buf = &mut self.buffer;
|
|
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) = buf.write().unwrap().cell_mut(ratatui::prelude::Position { x, y }) {
|
|
cell.set_char(character);
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
pub fn tui_setup <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> {
|
|
let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler();
|
|
std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{
|
|
stdout().execute(LeaveAlternateScreen).unwrap();
|
|
CrosstermBackend::new(stdout()).show_cursor().unwrap();
|
|
disable_raw_mode().unwrap();
|
|
better_panic_handler(info);
|
|
}));
|
|
stdout().execute(EnterAlternateScreen)?;
|
|
backend.hide_cursor()?;
|
|
enable_raw_mode().map_err(Into::into)
|
|
}
|
|
|
|
pub fn tui_teardown <W: Write> (backend: &mut CrosstermBackend<W>) -> Usually<()> {
|
|
stdout().execute(LeaveAlternateScreen)?;
|
|
backend.show_cursor()?;
|
|
disable_raw_mode().map_err(Into::into)
|
|
}
|
|
|
|
pub fn tui_resized <W: Write> (
|
|
backend: &mut CrosstermBackend<W>,
|
|
buffer: &mut Buffer,
|
|
size: ratatui::prelude::Rect
|
|
) {
|
|
if buffer.area != size {
|
|
backend.clear_region(ClearType::All).unwrap();
|
|
buffer.resize(size);
|
|
buffer.reset();
|
|
}
|
|
}
|
|
|
|
pub fn tui_redrawn <'b, W: Write> (
|
|
backend: &mut CrosstermBackend<W>,
|
|
mut prev_buffer: &'b mut Buffer,
|
|
mut next_buffer: &'b mut Buffer
|
|
) {
|
|
let updates = prev_buffer.diff(&next_buffer);
|
|
backend.draw(updates.into_iter()).expect("failed to render");
|
|
Backend::flush(backend).expect("failed to flush output new_buffer");
|
|
std::mem::swap(&mut prev_buffer, &mut next_buffer);
|
|
next_buffer.reset();
|
|
}
|
|
|
|
pub fn tui_update (
|
|
buf: &mut Buffer, area: XYWH<u16>, callback: &impl Fn(&mut Cell, u16, u16)
|
|
) {
|
|
for row in 0..area.h() {
|
|
let y = area.y() + row;
|
|
for col in 0..area.w() {
|
|
let x = area.x() + col;
|
|
if x < buf.area.width && y < buf.area.height {
|
|
if let Some(cell) = buf.cell_mut(ratatui::prelude::Position { x, y }) {
|
|
callback(cell, col, row);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl TuiThread {
|
|
/// Spawn a TUI thread that runs `callt least one, then repeats until `exit`.
|
|
pub fn new <F> (exit: Arc<AtomicBool>, call: F) -> Result<Self, std::io::Error>
|
|
where F: Fn(&PerfModel)->() + Send + Sync + 'static
|
|
{
|
|
let perf = Arc::new(PerfModel::default());
|
|
Ok(Self {
|
|
exit: exit.clone(),
|
|
perf: perf.clone(),
|
|
join: std::thread::Builder::new().name("tengri tui output".into()).spawn(move || {
|
|
while !exit.fetch_and(true, Relaxed) {
|
|
let _ = perf.cycle(&call);
|
|
}
|
|
})?.into()
|
|
})
|
|
}
|
|
|
|
/// Spawn a TUI thread that runs `callt least one, then repeats
|
|
/// until `exit`, sleeping for `time` msec after every iteration.
|
|
pub fn new_sleep <F> (
|
|
exit: Arc<AtomicBool>, time: Duration, call: F
|
|
) -> Result<Self, std::io::Error>
|
|
where F: Fn(&PerfModel)->() + Send + Sync + 'static
|
|
{
|
|
Self::new(exit, move |perf| { let _ = call(perf); std::thread::sleep(time); })
|
|
}
|
|
|
|
/// Spawn a TUI thread that runs `callt least one, then repeats
|
|
/// until `exit`, using polling to run every `time` msec.
|
|
pub fn new_poll <F> (
|
|
exit: Arc<AtomicBool>, time: Duration, call: F
|
|
) -> Result<Self, std::io::Error>
|
|
where F: Fn(&PerfModel)->() + Send + Sync + 'static
|
|
{
|
|
Self::new(exit, move |perf| { if poll(time).is_ok() { let _ = call(perf); } })
|
|
}
|
|
|
|
pub fn join (self) -> Result<(), Box<dyn std::any::Any + Send>> {
|
|
self.join.join()
|
|
}
|
|
}
|
|
|
|
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 Content<Tui> {
|
|
Fixed::Y(1, Tui::fg_bg(fg, bg, Repeat::X(Self::LO)))
|
|
}
|
|
/// A phat line
|
|
pub fn hi (fg: Color, bg: Color) -> impl Content<Tui> {
|
|
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<S: BorderStyle, W: Content<Tui>> HasContent<Tui> for Bordered<S, W> {
|
|
fn content (&self) -> impl Content<Tui> {
|
|
Fill::XY(lay!( When::new(self.0, Border(self.0, self.1)), Pad::XY(1, 1, &self.2) ))
|
|
}
|
|
}
|
|
impl<
|
|
A: Content<Tui>,
|
|
B: Content<Tui>,
|
|
C: Content<Tui>,
|
|
> HasContent<Tui> for Tryptich<A, B, C> {
|
|
fn content (&self) -> impl Content<Tui> {
|
|
let Self { top, h, left: (w_a, ref a), middle: (w_b, ref b), right: (w_c, ref c) } = *self;
|
|
Fixed::Y(h, if top {
|
|
Bsp::a(
|
|
Fill::X(Align::n(Fixed::X(w_b, Align::x(Tui::bg(Color::Reset, b))))),
|
|
Bsp::a(
|
|
Fill::X(Align::nw(Fixed::X(w_a, Tui::bg(Color::Reset, a)))),
|
|
Fill::X(Align::ne(Fixed::X(w_c, Tui::bg(Color::Reset, c)))),
|
|
),
|
|
)
|
|
} else {
|
|
Bsp::a(
|
|
Fill::XY(Align::c(Fixed::X(w_b, Align::x(Tui::bg(Color::Reset, b))))),
|
|
Bsp::a(
|
|
Fill::XY(Align::w(Fixed::X(w_a, Tui::bg(Color::Reset, a)))),
|
|
Fill::XY(Align::e(Fixed::X(w_c, Tui::bg(Color::Reset, c)))),
|
|
),
|
|
)
|
|
})
|
|
}
|
|
}
|
|
impl<T: Content<Tui>> HasContent<Tui> for Phat<T> {
|
|
fn content (&self) -> impl Content<Tui> {
|
|
let [fg, bg, hi, lo] = self.colors;
|
|
let top = Fixed::Y(1, Self::lo(bg, hi));
|
|
let low = Fixed::Y(1, Self::hi(bg, lo));
|
|
let content = Tui::fg_bg(fg, bg, &self.content);
|
|
Min::XY(self.width, self.height, Bsp::s(top, Bsp::n(low, Fill::XY(content))))
|
|
}
|
|
}
|
|
impl<T: Content<Tui>> Layout<Tui> for Modify<T> {}
|
|
impl<T: Content<Tui>> Layout<Tui> for Styled<T> {}
|
|
impl Layout<Tui> for Repeat<'_> {}
|
|
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: Draw<Tui>> Draw<Tui> for ErrorBoundary<Tui, T> {
|
|
fn draw (&self, to: &mut Tui) {
|
|
match self.0.as_ref() {
|
|
Ok(Some(content)) => content.draw(to),
|
|
Ok(None) => to.blit(&"empty?", 0, 0, Some(Style::default().yellow())),
|
|
Err(e) => {
|
|
let err_fg = Color::Rgb(255,224,244);
|
|
let err_bg = Color::Rgb(96,24,24);
|
|
let title = Bsp::e(Tui::bold(true, "oops. "), "rendering failed.");
|
|
let error = Bsp::e("\"why?\" ", Tui::bold(true, format!("{e}")));
|
|
to.place(&Tui::fg_bg(err_fg, err_bg, Bsp::s(title, error)))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Draw<Tui> for u64 {
|
|
fn draw (&self, _to: &mut Tui) {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
impl Draw<Tui> for f64 {
|
|
fn draw (&self, _to: &mut Tui) {
|
|
todo!()
|
|
}
|
|
}
|
|
|
|
impl Draw<Tui> for Repeat<'_> {
|
|
fn draw (&self, to: &mut Tui) {
|
|
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<Tui> for Scrollbar {
|
|
fn draw (&self, to: &mut Tui) {
|
|
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<Tui> for &str {
|
|
fn draw (&self, to: &mut Tui) {
|
|
let XYWH(x, y, w, ..) = self.layout(to.area());
|
|
to.text(&self, x, y, w)
|
|
}
|
|
}
|
|
|
|
impl Draw<Tui> for String {
|
|
fn draw (&self, to: &mut Tui) {
|
|
self.as_str().draw(to)
|
|
}
|
|
}
|
|
|
|
impl Draw<Tui> for Arc<str> {
|
|
fn draw (&self, to: &mut Tui) { self.as_ref().draw(to) }
|
|
}
|
|
|
|
impl<T: Content<Tui>> Draw<Tui> for Foreground<Color, T> {
|
|
fn draw (&self, to: &mut Tui) {
|
|
let area = self.layout(to.area());
|
|
to.fill_fg(area, self.0);
|
|
to.place_at(area, &self.1);
|
|
}
|
|
}
|
|
|
|
impl<T: Content<Tui>> Draw<Tui> for Background<Color, T> {
|
|
fn draw (&self, to: &mut Tui) {
|
|
let area = self.layout(to.area());
|
|
to.fill_bg(area, self.0);
|
|
to.place_at(area, &self.1);
|
|
}
|
|
}
|
|
|
|
impl<T: Content<Tui>> Draw<Tui> for Modify<T> {
|
|
fn draw (&self, to: &mut Tui) {
|
|
to.fill_mod(to.area(), self.0, self.1);
|
|
self.2.draw(to)
|
|
}
|
|
}
|
|
|
|
impl<T: Content<Tui>> Draw<Tui> for Styled<T> {
|
|
fn draw (&self, to: &mut Tui) {
|
|
to.place(&self.1);
|
|
// TODO write style over area
|
|
}
|
|
}
|
|
|
|
impl<S: BorderStyle> Draw<Tui> for Border<S> {
|
|
fn draw (&self, to: &mut Tui) {
|
|
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<Tui> for TrimString<T> {
|
|
fn draw (&self, to: &mut Tui) { Draw::draw(&self.as_ref(), to) }
|
|
}
|
|
|
|
impl<T: AsRef<str>> Draw<Tui> for TrimStringRef<'_, T> {
|
|
fn draw (&self, target: &mut Tui) {
|
|
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: Out, 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: Out, 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 }
|
|
}
|
|
}
|
|
/// Should be impl something or other...
|
|
///
|
|
/// ```
|
|
/// use tengri::{Namespace, Understand, Tui, ratatui::prelude::Color};
|
|
///
|
|
/// struct State;
|
|
/// impl<'b> Namespace<'b, bool> for State {}
|
|
/// impl<'b> Namespace<'b, u16> for State {}
|
|
/// impl<'b> Namespace<'b, Color> for State {}
|
|
/// impl Understand<Tui, ()> for State {}
|
|
/// # fn main () -> tengri::Usually<()> {
|
|
/// let state = State;
|
|
/// let mut out = Tui::default();
|
|
/// tengri::evaluate_output_expression_tui(&state, &mut out, "")?;
|
|
/// tengri::evaluate_output_expression_tui(&state, &mut out, "text Hello world!")?;
|
|
/// tengri::evaluate_output_expression_tui(&state, &mut out, "fg (g 0) (text Hello world!)")?;
|
|
/// tengri::evaluate_output_expression_tui(&state, &mut out, "bg (g 2) (text Hello world!)")?;
|
|
/// tengri::evaluate_output_expression_tui(&state, &mut out, "(bg (g 3) (fg (g 4) (text Hello world!)))")?;
|
|
/// # Ok(()) }
|
|
/// ```
|
|
pub fn evaluate_output_expression_tui <'a, S> (
|
|
state: &S, output: &mut Tui, expr: impl Expression + 'a
|
|
) -> Usually<bool> where
|
|
S: Understand<Tui, ()>
|
|
+ for<'b>Namespace<'b, bool>
|
|
+ for<'b>Namespace<'b, u16>
|
|
+ for<'b>Namespace<'b, Color>
|
|
{
|
|
// See `tengri::evaluate_output_expression`
|
|
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();
|
|
let tail1 = tail0.tail();
|
|
let arg2 = tail1.head();
|
|
match frags.next() {
|
|
|
|
Some("text") => {
|
|
if let Some(src) = args?.src()? { output.place(&src) }
|
|
},
|
|
|
|
Some("fg") => {
|
|
let arg0 = arg0?.expect("fg: expected arg 0 (color)");
|
|
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("fg: {arg0:?}: not a color"));
|
|
let thunk = Thunk::new(move|output: &mut Tui|state.understand(output, &arg1).unwrap());
|
|
output.place(&Tui::fg(color, thunk))
|
|
},
|
|
|
|
Some("bg") => {
|
|
//panic!("expr: {expr:?}\nhead: {head:?}\nfrags: {frags:?}\nargs: {args:?}\narg0: {arg0:?}\ntail0: {tail0:?}\narg1: {arg1:?}\ntail1: {tail1:?}\narg2: {arg2:?}");
|
|
//panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");;
|
|
//panic!("head: {head:?}\narg0: {arg0:?}\narg1: {arg1:?}\narg2: {arg2:?}");
|
|
let arg0 = arg0?.expect("bg: expected arg 0 (color)");
|
|
let color = Namespace::namespace(state, arg0)?.unwrap_or_else(||panic!("bg: {arg0:?}: not a color"));
|
|
let thunk = Thunk::new(move|output: &mut Tui|state.understand(output, &arg1).unwrap());
|
|
output.place(&Tui::bg(color, thunk))
|
|
},
|
|
|
|
_ => return Ok(false)
|
|
|
|
};
|
|
Ok(true)
|
|
}
|
|
|
|
/// ```
|
|
/// let _ = tengri::button_2("", "", true);
|
|
/// let _ = tengri::button_2("", "", false);
|
|
/// ```
|
|
pub fn button_2 <'a> (key: impl Content<Tui>, label: impl Content<Tui>, editing: bool) -> impl Content<Tui> {
|
|
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 Content<Tui>, label: impl Content<Tui>, value: impl Content<Tui>, editing: bool,
|
|
) -> impl Content<Tui> {
|
|
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<Tui> for $T {
|
|
fn draw (&self, to: &mut Tui) {
|
|
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(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "jack")] mod jack {
|
|
use crate::*;
|
|
use ::jack::*;
|
|
use JackState::*;
|
|
|
|
impl<'j> HasJack<'j> for Jack<'j> { fn jack (&self) -> &Jack<'j> { self } }
|
|
impl<'j> HasJack<'j> for &Jack<'j> { fn jack (&self) -> &Jack<'j> { self } }
|
|
/// Implement [Jack] constructor and methods
|
|
impl<'j> Jack<'j> {
|
|
/// Register new [Client] and wrap it for shared use.
|
|
pub fn new_run <T: HasJack<'j> + Audio + Send + Sync + 'static> (
|
|
name: &impl AsRef<str>,
|
|
init: impl FnOnce(Jack<'j>)->Usually<T>
|
|
) -> Usually<Arc<RwLock<T>>> {
|
|
Jack::new(name)?.run(init)
|
|
}
|
|
|
|
pub fn new (name: &impl AsRef<str>) -> Usually<Self> {
|
|
let client = Client::new(name.as_ref(), ClientOptions::NO_START_SERVER)?.0;
|
|
Ok(Jack(Arc::new(RwLock::new(JackState::Inactive(client)))))
|
|
}
|
|
|
|
pub fn run <T: HasJack<'j> + Audio + Send + Sync + 'static>
|
|
(self, init: impl FnOnce(Self)->Usually<T>) -> Usually<Arc<RwLock<T>>>
|
|
{
|
|
let client_state = self.0.clone();
|
|
let app: Arc<RwLock<T>> = Arc::new(RwLock::new(init(self)?));
|
|
let mut state = Activating;
|
|
std::mem::swap(&mut*client_state.write().unwrap(), &mut state);
|
|
if let Inactive(client) = state {
|
|
// This is the misc notifications handler. It's a struct that wraps a [Box]
|
|
// which performs type erasure on a callback that takes [JackEvent], which is
|
|
// one of the available misc notifications.
|
|
let notify = JackNotify(Box::new({
|
|
let app = app.clone();
|
|
move|event|(&mut*app.write().unwrap()).handle(event)
|
|
}) as BoxedJackEventHandler);
|
|
// This is the main processing handler. It's a struct that wraps a [Box]
|
|
// which performs type erasure on a callback that takes [Client] and [ProcessScope]
|
|
// and passes them down to the `app`'s `process` callback, which in turn
|
|
// implements audio and MIDI input and output on a realtime basis.
|
|
let process = ::jack::contrib::ClosureProcessHandler::new(Box::new({
|
|
let app = app.clone();
|
|
move|c: &_, s: &_|if let Ok(mut app) = app.write() {
|
|
app.process(c, s)
|
|
} else {
|
|
Control::Quit
|
|
}
|
|
}) as BoxedAudioHandler);
|
|
// Launch a client with the two handlers.
|
|
*client_state.write().unwrap() = Active(
|
|
client.activate_async(notify, process)?
|
|
);
|
|
} else {
|
|
unreachable!();
|
|
}
|
|
Ok(app)
|
|
}
|
|
|
|
/// Run something with the client.
|
|
pub fn with_client <T> (&self, op: impl FnOnce(&Client)->T) -> T {
|
|
match &*self.0.read().unwrap() {
|
|
Inert => panic!("jack client not activated"),
|
|
Inactive(client) => op(client),
|
|
Activating => panic!("jack client has not finished activation"),
|
|
Active(client) => op(client.as_client()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Fn(JackEvent) + Send> NotificationHandler for JackNotify<T> {
|
|
fn thread_init(&self, _: &Client) {
|
|
self.0(JackEvent::ThreadInit);
|
|
}
|
|
unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) {
|
|
self.0(JackEvent::Shutdown(status, reason.into()));
|
|
}
|
|
fn freewheel(&mut self, _: &Client, enabled: bool) {
|
|
self.0(JackEvent::Freewheel(enabled));
|
|
}
|
|
fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control {
|
|
self.0(JackEvent::SampleRate(frames));
|
|
Control::Quit
|
|
}
|
|
fn client_registration(&mut self, _: &Client, name: &str, reg: bool) {
|
|
self.0(JackEvent::ClientRegistration(name.into(), reg));
|
|
}
|
|
fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) {
|
|
self.0(JackEvent::PortRegistration(id, reg));
|
|
}
|
|
fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control {
|
|
self.0(JackEvent::PortRename(id, old.into(), new.into()));
|
|
Control::Continue
|
|
}
|
|
fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) {
|
|
self.0(JackEvent::PortsConnected(a, b, are));
|
|
}
|
|
fn graph_reorder(&mut self, _: &Client) -> Control {
|
|
self.0(JackEvent::GraphReorder);
|
|
Control::Continue
|
|
}
|
|
fn xrun(&mut self, _: &Client) -> Control {
|
|
self.0(JackEvent::XRun);
|
|
Control::Continue
|
|
}
|
|
}
|
|
|
|
impl JackPerfModel for PerfModel {
|
|
fn update_from_jack_scope (&self, t0: Option<u64>, scope: &ProcessScope) {
|
|
if let Some(t0) = t0 {
|
|
let t1 = self.clock.raw();
|
|
self.used.store(
|
|
self.clock.delta_as_nanos(t0, t1) as f64,
|
|
Relaxed,
|
|
);
|
|
self.window.store(
|
|
scope.cycle_times().unwrap().period_usecs as f64,
|
|
Relaxed,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|