tengri/src/tengri_struct.rs
same mf who else ecba0cc64f
Some checks failed
/ build (push) Has been cancelled
TuiThread factors out poll/sleep/perf details
2026-03-06 12:59:07 +02:00

510 lines
15 KiB
Rust

pub use self::logical::*; mod logical {
use crate::*;
/// Thunks can be natural error boundaries!
///
/// ```
/// let _ = tengri::ErrorBoundary::<tengri::Tui, &'static str>::new(Ok(Some("hello")));
/// let _ = tengri::ErrorBoundary::<tengri::Tui, &'static str>::new(Ok(None));
/// let _ = tengri::ErrorBoundary::<tengri::Tui, &'static str>::new(Err("draw fail".into()));
/// ```
pub struct ErrorBoundary<O: Out, T: Draw<O>>(
pub Perhaps<T>,
pub PhantomData<O>,
);
/// Late-evaluate a rendering.
///
/// ```
/// let _ = tengri::Thunk::<tengri::Tui, _>::new(|out|{});
/// ```
pub struct Thunk<O: Out, F: Fn(&mut O)>(
pub F,
pub PhantomData<O>,
);
/// Memoize a rendering.
///
/// ```
/// let _ = tengri::Memo::new((), ());
/// ```
#[derive(Debug, Default)] pub struct Memo<T, U> {
pub value: T,
pub view: Arc<RwLock<U>>
}
}
pub use self::spatial::*; mod spatial {
use crate::*;
/// A binary split or layer.
///
/// ```
/// let _ = tengri::Bsp::n((), ());
/// let _ = tengri::Bsp::s((), ());
/// let _ = tengri::Bsp::e((), ());
/// let _ = tengri::Bsp::w((), ());
/// let _ = tengri::Bsp::a((), ());
/// let _ = tengri::Bsp::b((), ());
/// ```
pub struct Bsp<Head, Tail>(
/// Direction of split
pub(crate) Direction,
/// First element.
pub(crate) Head,
/// Second element.
pub(crate) Tail,
);
/// A point (X, Y).
///
/// ```
/// let xy = tengri::XY(0u16, 0);
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XY<C: Coord>(
pub C, pub C
);
/// A size (Width, Height).
///
/// ```
/// let wh = tengri::WH(0u16, 0);
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct WH<C: Coord>(
pub C, pub C
);
/// Point with size.
///
/// ```
/// let xywh = tengri::XYWH(0u16, 0, 0, 0);
/// assert_eq!(tengri::XYWH(10u16, 10, 20, 20).center(), tengri::XY(20, 20));
/// ```
///
/// * [ ] TODO: anchor field (determines at which corner/side is X0 Y0)
///
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, Debug, Default, PartialEq)] pub struct XYWH<C: Coord>(
pub C, pub C, pub C, pub C
);
/// A cardinal direction.
///
/// ```
/// let direction = tengri::Direction::Above;
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Copy, Clone, PartialEq, Debug)] pub enum Direction {
North, South, East, West, Above, Below
}
/// 9th of area to place.
///
/// ```
/// let alignment = tengri::Alignment::Center;
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Debug, Copy, Clone, Default)] pub enum Alignment {
#[default] Center, X, Y, NW, N, NE, E, SE, S, SW, W
}
/// A widget that tracks its rendered width and height.
///
/// ```
/// let measure = tengri::Measure::<tengri::Tui>::default();
/// ```
#[derive(Default)] pub struct Measure<O: Out> {
pub __: PhantomData<O>,
pub x: Arc<AtomicUsize>,
pub y: Arc<AtomicUsize>,
}
/// Show an item only when a condition is true.
///
/// ```
/// fn test () -> impl tengri::Draw<tengri::Tui> {
/// tengri::when(true, "Yes")
/// }
/// ```
pub struct When<O, T>(pub bool, pub T, pub PhantomData<O>);
pub const fn when<O, T>(condition: bool, content: T) -> When<O, T> {
When(condition, content, PhantomData)
}
/// Show one item if a condition is true and another if the condition is false.
///
/// ```
/// fn test () -> impl tengri::Draw<tengri::Tui> {
/// tengri::either(true, "Yes", "No")
/// }
/// ```
pub struct Either<E, A, B>(pub bool, pub A, pub B, pub PhantomData<E>);
pub const fn either<E, A, B>(condition: bool, content_a: A, content_b: B) -> Either<E, A, B> {
Either(condition, content_a, content_b, PhantomData)
}
/// Increment X and/or Y coordinate.
///
/// ```
/// let pushed = tengri::Push::XY(2, 2, "Hello");
/// ```
pub enum Push<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Decrement X and/or Y coordinate.
///
/// ```
/// let pulled = tengri::Pull::XY(2, 2, "Hello");
/// ```
pub enum Pull<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Set the content to fill the container.
///
/// ```
/// let filled = tengri::Fill::XY("Hello");
/// ```
pub enum Fill<A> { X(A), Y(A), XY(A) }
/// Set fixed size for content.
///
/// ```
/// let fixed = tengri::Fixed::XY(3, 5, "Hello"); // 3x5
/// ```
pub enum Fixed<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Set the maximum width and/or height of the content.
///
/// ```
/// let maximum = tengri::Max::XY(3, 5, "Hello"); // 3x1
/// ```
pub enum Max<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Set the minimum width and/or height of the content.
///
/// ```
/// let minimum = tengri::Min::XY(3, 5, "Hello"); // 5x5
/// ```
pub enum Min<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Decrease the width and/or height of the content.
///
/// ```
/// let shrunk = tengri::Shrink::XY(2, 0, "Hello"); // 1x1
/// ```
pub enum Shrink<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Increaase the width and/or height of the content.
///
/// ```
/// let expanded = tengri::Expand::XY(5, 3, "HELLO"); // 15x3
/// ```
pub enum Expand<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
/// Align position of inner area to middle, side, or corner of outer area.
///
///
/// ```
/// use ::tengri::*;
/// let area = XYWH(10u16, 10, 20, 20);
/// fn test (area: XYWH<u16>, item: &impl Draw<Tui>, expected: [u16;4]) {
/// //assert_eq!(Lay::layout(item, area), expected);
/// //assert_eq!(Draw::layout(item, area), expected);
/// };
///
/// let four = ||Fixed::XY(4, 4, "");
/// test(area, &Align::nw(four()), [10, 10, 4, 4]);
/// test(area, &Align::n(four()), [18, 10, 4, 4]);
/// test(area, &Align::ne(four()), [26, 10, 4, 4]);
/// test(area, &Align::e(four()), [26, 18, 4, 4]);
/// test(area, &Align::se(four()), [26, 26, 4, 4]);
/// test(area, &Align::s(four()), [18, 26, 4, 4]);
/// test(area, &Align::sw(four()), [10, 26, 4, 4]);
/// test(area, &Align::w(four()), [10, 18, 4, 4]);
///
/// let two_by_four = ||Fixed::XY(4, 2, "");
/// test(area, &Align::nw(two_by_four()), [10, 10, 4, 2]);
/// test(area, &Align::n(two_by_four()), [18, 10, 4, 2]);
/// test(area, &Align::ne(two_by_four()), [26, 10, 4, 2]);
/// test(area, &Align::e(two_by_four()), [26, 19, 4, 2]);
/// test(area, &Align::se(two_by_four()), [26, 28, 4, 2]);
/// test(area, &Align::s(two_by_four()), [18, 28, 4, 2]);
/// test(area, &Align::sw(two_by_four()), [10, 28, 4, 2]);
/// test(area, &Align::w(two_by_four()), [10, 19, 4, 2]);
/// ```
pub struct Align<T>(pub Alignment, pub T);
// TODO DOCUMENTME
pub enum Pad<U, A> { X(U, A), Y(U, A), XY(U, U, A), }
// TODO DOCUMENTME
pub struct Bordered<S, W>(pub bool, pub S, pub W);
// TODO DOCUMENTME
pub struct Border<S>(pub bool, pub S);
/// TODO DOCUMENTME
///
/// ```
/// use tengri::{Bounded, XYWH};
/// let area = XYWH(0, 0, 0, 0);
/// let content = "";
/// let bounded: Bounded<tengri::Tui, _> = Bounded(area, content);
/// ```
pub struct Bounded<O: Out, D>(pub XYWH<O::Unit>, pub D);
/// Draws items from an iterator.
///
/// ```
/// // FIXME let map = tengri::Map(||[].iter(), |_|{});
/// ```
pub struct Map<O, A, B, I, F, G>
where
I: Iterator<Item = A> + Send + Sync,
F: Fn() -> I + Send + Sync,
{
/// Function that returns iterator over stacked components
pub get_iter: F,
/// Function that returns each stacked component
pub get_item: G,
pub __: PhantomData<(O, B)>,
}
/// A color in OKHSL and RGB representations.
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor {
pub okhsl: Okhsl<f32>,
#[cfg(feature = "tui")] pub rgb: Color,
}
/// A color in OKHSL and RGB with lighter and darker variants.
#[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemTheme {
pub base: ItemColor,
pub light: ItemColor,
pub lighter: ItemColor,
pub lightest: ItemColor,
pub dark: ItemColor,
pub darker: ItemColor,
pub darkest: ItemColor,
}
// TODO DOCUMENTME
pub struct FieldH<Theme, Label, Value>(pub Theme, pub Label, pub Value);
// TODO DOCUMENTME
pub struct FieldV<Theme, Label, Value>(pub Theme, pub Label, pub Value);
// TODO:
pub struct Field<C, T, U> {
pub direction: Direction,
pub label: Option<T>,
pub label_fg: Option<C>,
pub label_bg: Option<C>,
pub label_align: Option<Direction>,
pub value: Option<U>,
pub value_fg: Option<C>,
pub value_bg: Option<C>,
pub value_align: Option<Direction>,
}
}
#[cfg(feature = "tui")] pub use self::terminal::*;
#[cfg(feature = "tui")] mod terminal {
use crate::*;
/// The TUI engine.
///
/// ```
/// # fn main () -> tengri::Usually<()> {
/// let tui = tengri::Tui::new(Box::new(vec![0u8;0]))?;
/// # Ok(()) }
/// ```
pub struct Tui {
/// Exit flag
pub exited: Arc<AtomicBool>,
/// Error message
pub error: Option<Arc<str>>,
/// Performance counter
pub perf: PerfModel,
/// Ratatui backend
pub backend: RwLock<CrosstermBackend<Box<dyn Write + Send + Sync>>>,
/// Current tnput event
pub event: Option<TuiEvent>,
/// Current output buffer
pub buffer: RwLock<Buffer>,
/// FIXME unused?
pub area: XYWH<u16>,
}
#[derive(Debug)] pub struct TuiThread {
pub exit: Arc<AtomicBool>,
pub perf: Arc<PerfModel>,
pub join: JoinHandle<()>,
}
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiEvent(
pub Event
);
#[derive(Debug, Clone, Eq, PartialEq, PartialOrd)] pub struct TuiKey(
pub Option<KeyCode>,
pub KeyModifiers
);
/// TUI buffer sized by `usize` instead of `u16`.
#[derive(Default)] pub struct BigBuffer {
pub width: usize,
pub height: usize,
pub content: Vec<Cell>
}
// TODO DOCUMENTME
pub struct Foreground<Color, Item>(pub Color, pub Item);
// TODO DOCUMENTME
pub struct Background<Color, Item>(pub Color, pub Item);
pub struct Modify<T>(pub bool, pub Modifier, pub T);
pub struct Styled<T>(pub Option<Style>, pub T);
/// A cell that takes up 3 rows on its own,
/// but stacks, giving (N+1)*2 rows per N cells.
pub struct Phat<T> {
pub width: u16,
pub height: u16,
pub content: T,
pub colors: [Color;4],
}
/// A three-column layout.
pub struct Tryptich<A, B, C> {
pub top: bool,
pub h: u16,
pub left: (u16, A),
pub middle: (u16, B),
pub right: (u16, C),
}
/// Repeat a string, e.g. for background
pub enum Repeat<'a> {
X(&'a str),
Y(&'a str),
XY(&'a str)
}
/// Scroll indicator
pub enum Scrollbar {
/// Horizontal scrollbar
X { offset: usize, length: usize, total: usize, },
/// Vertical scrollbar
Y { offset: usize, length: usize, total: usize, }
}
}
pub use self::textual::*;
mod textual {
/// Displays an owned [str]-like with fixed maximum width.
///
/// Width is computed using [unicode_width].
pub struct TrimString<T: AsRef<str>>(pub u16, pub T);
/// Displays a borrowed [str]-like with fixed maximum width
///
/// Width is computed using [unicode_width].
pub struct TrimStringRef<'a, T: AsRef<str>>(pub u16, pub &'a T);
}
#[cfg(feature = "jack")] pub use self::aural::*;
#[cfg(feature = "jack")] mod aural {
use crate::*;
use ::jack::*;
/// Wraps [JackState], and through it [jack::Client] when connected.
///
/// ```
/// let jack = tengri::Jack::default();
/// ```
#[derive(Clone, Debug, Default)] pub struct Jack<'j> (
pub(crate) Arc<RwLock<JackState<'j>>>
);
/// This is a connection which may be [Inactive], [Activating], or [Active].
/// In the [Active] and [Inactive] states, [JackState::client] returns a
/// [jack::Client], which you can use to talk to the JACK API.
///
/// ```
/// let state = tengri::JackState::default();
/// ```
#[derive(Debug, Default)] pub enum JackState<'j> {
/// Unused
#[default] Inert,
/// Before activation.
Inactive(Client),
/// During activation.
Activating,
/// After activation. Must not be dropped for JACK thread to persist.
Active(DynamicAsyncClient<'j>),
}
/// Event enum for JACK events.
///
/// ```
/// let event = tengri::JackEvent::XRun;
/// ```
#[derive(Debug, Clone, PartialEq)] pub enum JackEvent {
ThreadInit,
Shutdown(ClientStatus, Arc<str>),
Freewheel(bool),
SampleRate(Frames),
ClientRegistration(Arc<str>, bool),
PortRegistration(PortId, bool),
PortRename(PortId, Arc<str>, Arc<str>),
PortsConnected(PortId, PortId, bool),
GraphReorder,
XRun,
}
/// Generic notification handler that emits [JackEvent]
///
/// ```
/// let notify = tengri::JackNotify(|_|{});
/// ```
pub struct JackNotify<T: Fn(JackEvent) + Send>(pub T);
/// Running JACK [AsyncClient] with maximum type erasure.
///
/// One [Box] contains function that handles [JackEvent]s.
///
/// Another [Box] containing a function that handles realtime IO.
///
/// That's all it knows about them.
pub type DynamicAsyncClient<'j>
= AsyncClient<DynamicNotifications<'j>, DynamicAudioHandler<'j>>;
/// Notification handler wrapper for [BoxedAudioHandler].
pub type DynamicAudioHandler<'j> =
::jack::contrib::ClosureProcessHandler<(), BoxedAudioHandler<'j>>;
/// Boxed realtime callback.
pub type BoxedAudioHandler<'j> =
Box<dyn FnMut(&Client, &ProcessScope) -> Control + Send + Sync + 'j>;
/// Notification handler wrapper for [BoxedJackEventHandler].
pub type DynamicNotifications<'j> =
JackNotify<BoxedJackEventHandler<'j>>;
/// Boxed [JackEvent] callback.
pub type BoxedJackEventHandler<'j> =
Box<dyn Fn(JackEvent) + Send + Sync + 'j>;
}
pub use self::temporal::*; mod temporal {
use crate::*;
/// Performance counter
#[derive(Debug)]
pub struct PerfModel {
pub clock: quanta::Clock,
/// Measurement has a small cost. Disable it here.
pub enabled: bool,
// In nanoseconds. Time used by last iteration.
pub used: AtomicF64,
// In microseconds. Max prescribed time for iteration (frame, chunk...).
pub window: AtomicF64,
}
}