wip: impl_draw, optional drawing

This commit is contained in:
facile pop culture reference 2026-07-06 05:35:48 +03:00
parent 0b9e9c0696
commit bf16288884
12 changed files with 213 additions and 229 deletions

View file

@ -35,11 +35,18 @@ pub trait Screen: Xy<Self::Unit> + Wh<Self::Unit> + Send + Sync + Sized {
/// todo!("your draw logic") /// todo!("your draw logic")
/// }); /// });
/// ``` /// ```
#[macro_export] macro_rules! impl_draw ((| #[macro_export] macro_rules! impl_draw (
($(<$($T:ident: $Trait:path,)+>)?|
$self:ident:$Self:path, $to:ident:$To:ty
|$draw:block)=>{ impl$(<$($T:$Trait),+>)? Draw<$To> for $Self {
fn draw ($self, $to: &mut $To) -> Perhaps<XYWH<<$To as Screen>::Unit>> $draw
} };
($(<$($T:ident: $Trait:path,)+>)?|
$self:ident:$Self:ty, $to:ident:$To:ty $self:ident:$Self:ty, $to:ident:$To:ty
|$draw:block)=>{ impl Draw<$To> for $Self { |$draw:block)=>{ impl$(<$($T:$Trait),+>)? Draw<$To> for $Self {
fn draw ($self, $to: &mut $To) -> Usually<XYWH<<$To as Screen>::Unit>> $draw fn draw ($self, $to: &mut $To) -> Perhaps<XYWH<<$To as Screen>::Unit>> $draw
} }); } }
);
/// Drawable that supports dynamic dispatch. /// Drawable that supports dynamic dispatch.
/// ///
@ -57,38 +64,36 @@ pub trait Screen: Xy<Self::Unit> + Wh<Self::Unit> + Send + Sync + Sized {
/// use tengri::{*, draw::*, term::*}; /// use tengri::{*, draw::*, term::*};
/// struct MyWidget(bool); /// struct MyWidget(bool);
/// impl Draw<Tui> for MyWidget { /// impl Draw<Tui> for MyWidget {
/// fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> { /// fn draw (self, to: &mut Tui) -> Perhaps<XYWH<u16>> {
/// todo!("your draw logic") /// todo!("your draw logic")
/// } /// }
/// } /// }
/// ``` /// ```
pub trait Draw<S: Screen> { pub trait Draw<S: Screen> {
fn draw (self, to: &mut S) -> Usually<XYWH<S::Unit>>; fn draw (self, to: &mut S) -> Perhaps<XYWH<S::Unit>>;
fn layout (&self, area: XYWH<S::Unit>) -> Perhaps<XYWH<S::Unit>> { fn layout (&self, area: XYWH<S::Unit>) -> Perhaps<XYWH<S::Unit>> {
Ok(Some(area)) Ok(Some(area))
} }
} }
impl<S: Screen> Draw<S> for () { impl<S: Screen> Draw<S> for () {
fn draw (self, __: &mut S) -> Usually<XYWH<S::Unit>> { fn draw (self, _: &mut S) -> Perhaps<XYWH<S::Unit>> {
Ok(Default::default()) Ok(None)
} }
} }
impl<S: Screen, D: Draw<S>> Draw<S> for Option<D> { impl_draw!(<S: Screen, D: Draw<S>,>|self: Option<D>, to: S|{
fn draw (self, to: &mut S) -> Usually<XYWH<S::Unit>> {
self.map(|it|it.draw(to)).transpose().map(Option::unwrap_or_default) self.map(|it|it.draw(to)).transpose().map(Option::unwrap_or_default)
} });
}
//impl<S: Screen, D: Draw<S>> Draw<S> for RwLock<D> { //impl<S: Screen, D: Draw<S>> Draw<S> for RwLock<D> {
//fn draw (self, __: &mut S) -> Usually<XYWH<S::Unit>> { //fn draw (self, __: &mut S) -> Perhaps<XYWH<S::Unit>> {
//todo!() //todo!()
//} //}
//} //}
//impl<T: Screen, D: Draw<T>> Draw<T> for Arc<D> { //impl<T: Screen, D: Draw<T>> Draw<T> for Arc<D> {
//fn draw (self, __: &mut T) -> Usually<XYWH<T::Unit>> { //fn draw (self, __: &mut T) -> Perhaps<XYWH<T::Unit>> {
//todo!() //todo!()
//} //}
//} //}
@ -101,19 +106,19 @@ pub trait View<T: Screen> {
} }
impl<T: Screen, V: View<T>> Draw<T> for &V { impl<T: Screen, V: View<T>> Draw<T> for &V {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> { fn draw (self, to: &mut T) -> Perhaps<XYWH<T::Unit>> {
self.view().draw(to) self.view().draw(to)
} }
} }
/// Because we can't implement [Draw] for `F: FnOnce...` without conflicts. /// Because we can't implement [Draw] for `F: FnOnce...` without conflicts.
pub struct Thunk<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>>( pub struct Thunk<T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>>(
pub F, pub F,
std::marker::PhantomData<T> std::marker::PhantomData<T>
); );
impl<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> { impl<T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>> Draw<T> for Thunk<T, F> {
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> { fn draw (self, to: &mut T) -> Perhaps<XYWH<T::Unit>> {
(self.0)(to) (self.0)(to)
} }
} }
@ -126,7 +131,7 @@ impl<T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> Draw<T> for Thunk<T,
/// thunk(|to: &mut Tui|Ok(to.1)) /// thunk(|to: &mut Tui|Ok(to.1))
/// # } /// # }
/// ``` /// ```
pub const fn thunk <T: Screen, F: FnOnce(&mut T)->Usually<XYWH<T::Unit>>> ( pub const fn thunk <T: Screen, F: FnOnce(&mut T)->Perhaps<XYWH<T::Unit>>> (
draw: F draw: F
) -> Thunk<T, F> { ) -> Thunk<T, F> {
Thunk(draw, std::marker::PhantomData) Thunk(draw, std::marker::PhantomData)

View file

@ -114,22 +114,23 @@ pub fn eval_view <'a, O: Screen + 'a, S> (
).draw(output), ).draw(output),
// Second `frags.next()` calls returns the namespace member. // Second `frags.next()` calls returns the namespace member.
Some("bsp") => eval_enum!( Some("bsp") => {
"bsp", output, state, frags.next(), arg0, Split { let direction = eval_enum!("bsp", output, state, frags.next(), arg0, Split {
"n" => North, "s" => South, "e" => East, "w" => West, "a" => Above, "b" => Below "n" => North, "s" => South, "e" => East, "w" => West, "a" => Above, "b" => Below
} });
).half( direction.half(
thunk(move|output: &mut O|state.interpret(output, &arg0)), thunk(move|output: &mut O|state.interpret(output, &arg0)),
thunk(move|output: &mut O|state.interpret(output, &arg1)), thunk(move|output: &mut O|state.interpret(output, &arg1)),
).draw(output), ).draw(output)
},
Some("align") => eval_enum!( Some("align") => {
"align", output, state, frags.next(), arg0, Origin { let alignment = eval_enum!("align", output, state, frags.next(), arg0, Origin {
"c" => C, "n" => N, "s" => S, "e" => E, "w" => W, "x" => X, "y" => Y "c" => C, "n" => N, "s" => S, "e" => E, "w" => W, "x" => X, "y" => Y
} });
).align( let content = thunk(move|output: &mut O|state.interpret(output, &arg0));
thunk(move|output: &mut O|state.interpret(output, &arg0)) align(alignment, content).draw(output)
).draw(output), },
Some("exact") => eval_xy!( Some("exact") => eval_xy!(
"exact", expr, head, output, state, frags.next(), "exact", expr, head, output, state, frags.next(),
@ -191,11 +192,11 @@ pub fn eval_view_tui <'a, S> (
let arg0 = args.head(); let arg0 = args.head();
let tail0 = args.tail(); let tail0 = args.tail();
let arg1 = tail0.head(); let arg1 = tail0.head();
Ok(Some(match frags.next() { match frags.next() {
Some("text") => { Some("text") => {
if let Some(src) = args?.src()? { if let Some(src) = args?.src()? {
output.show(src)? output.show(src)
} else { } else {
return Ok(None) return Ok(None)
} }
@ -208,7 +209,7 @@ pub fn eval_view_tui <'a, S> (
state.interpret(output, &arg1)?; state.interpret(output, &arg1)?;
// FIXME?: don't max out the used area? // FIXME?: don't max out the used area?
Ok(output.area().into()) Ok(output.area().into())
})))? })))
}, },
Some("bg") => { Some("bg") => {
@ -218,10 +219,10 @@ pub fn eval_view_tui <'a, S> (
state.interpret(output, &arg1)?; state.interpret(output, &arg1)?;
// FIXME?: don't max out the used area? // FIXME?: don't max out the used area?
Ok(output.area().into()) Ok(output.area().into())
})))? })))
}, },
_ => return Ok(None) _ => return Ok(None)
})) }
} }

View file

@ -2,7 +2,6 @@ use crate::*;
mod align; pub use self::align::*; mod align; pub use self::align::*;
mod area; pub use self::area::*; mod area; pub use self::area::*;
mod origin; pub use self::origin::*;
mod split; pub use self::split::*; mod split; pub use self::split::*;
pub enum Layout<T: Screen, X: Into<Option<T::Unit>>, I: Draw<T>> { pub enum Layout<T: Screen, X: Into<Option<T::Unit>>, I: Draw<T>> {
@ -21,12 +20,11 @@ pub enum Layout<T: Screen, X: Into<Option<T::Unit>>, I: Draw<T>> {
ClipWH(X, X, I), ClipWH(X, X, I),
} }
impl< impl_draw!(<
T: Screen, T: Screen,
X: Into<Option<T::Unit>>, X: Into<Option<T::Unit>>,
I: Draw<T> I: Draw<T>,
> Draw<T> for Layout<T, X, I> { >|self: Layout<T, X, I>, to: T|{
fn draw (self, to: &mut T) -> Usually<XYWH<T::Unit>> {
use Layout::*; use Layout::*;
match self { match self {
__(_) => unreachable!(), __(_) => unreachable!(),
@ -83,5 +81,4 @@ impl<
it.draw(to) it.draw(to)
}, },
} }
} });
}

View file

@ -1,7 +1,5 @@
use crate::*; use crate::*;
pub struct Align<T>(Option<Origin>, T);
/// ``` /// ```
/// use tengri::*; /// use tengri::*;
/// let _ = align(None, "unaligned"); /// let _ = align(None, "unaligned");
@ -11,3 +9,7 @@ pub struct Align<T>(Option<Origin>, T);
pub fn align <S: Screen, T: Draw<S>, U: Into<Option<Origin>>> (origin: U, it: T) -> Align<T> { pub fn align <S: Screen, T: Draw<S>, U: Into<Option<Origin>>> (origin: U, it: T) -> Align<T> {
Align(origin.into(), it) Align(origin.into(), it)
} }
pub struct Align<T>(Option<Origin>, T);
impl_draw!(<S: Screen, T: Draw<S>,>|self: Align<T>, to: S|{ todo!() });

View file

@ -1,7 +1,5 @@
use crate::*; use crate::*;
pub struct Area<S: Screen, T: Draw<S>>(Option<XYWH<S::Unit>>, T);
/// ``` /// ```
/// use tengri::*; /// use tengri::*;
/// let _ = area(None, "unareaed"); /// let _ = area(None, "unareaed");
@ -13,3 +11,7 @@ pub fn area <S: Screen, T: Draw<S>, U: Into<Option<XYWH<S::Unit>>>> (
) -> Area<S, T> { ) -> Area<S, T> {
Area(origin.into(), it) Area(origin.into(), it)
} }
pub struct Area<S: Screen, T: Draw<S>>(Option<XYWH<S::Unit>>, T);
impl_draw!(<S: Screen, T: Draw<S>,>|self: Area<S, T>, to: S|{ todo!() });

View file

@ -1,23 +0,0 @@
use super::*;
/// Where is [0, 0] located?
///
/// ```
/// use tengri::draw::Origin;
/// let _ = Origin::NW.align(());
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Debug, Copy, Clone, Default)] pub enum Origin {
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
}
/// Something that has `[0, 0]` at a particular point.
pub trait HasOrigin {
fn origin (&self) -> Origin;
}
impl<T: AsRef<Origin>> HasOrigin for T {
fn origin (&self) -> Origin {
*self.as_ref()
}
}

View file

@ -5,3 +5,4 @@ mod coord; pub use self::coord::*;
mod iter; pub use self::iter::*; mod iter; pub use self::iter::*;
mod sizer; pub use self::sizer::*; mod sizer; pub use self::sizer::*;
mod xywh; pub use self::xywh::*; mod xywh; pub use self::xywh::*;
mod lrtb; pub use self::lrtb::*;

47
src/space/lrtb.rs Normal file
View file

@ -0,0 +1,47 @@
use crate::*;
use Origin::*;
impl<N: Coord, T: Xywh<N>> Lrtb<N> for T {}
pub trait Lrtb<N: Coord>: Xywh<N> {
fn lrtb (&self) -> [N;4] {
// FIXME: factor origin
[self.x(), self.y(), self.x()+self.w(), self.y()+self.h()]
}
fn iter_x (&self) -> impl Iterator<Item = N> where Self: HasOrigin {
self.x_west()..self.x_east()
}
fn x_west (&self) -> N where Self: HasOrigin {
let w = self.w();
let a = self.origin();
let d = match a { NW|W|SW => 0.into(), N|X|C|Y|S => w/2.into(), NE|E|SE => w };
self.x().minus(d)
}
fn x_east (&self) -> N where Self: HasOrigin {
let w = self.w();
let a = self.origin();
let d = match a { NW|W|SW => w, N|X|C|Y|S => w/2.into(), NE|E|SE => 0.into() };
self.x().plus(d)
}
fn x_center (&self) -> N where Self: HasOrigin {
todo!()
}
fn iter_y (&self) -> impl Iterator<Item = N> where Self: HasOrigin {
self.y_north()..self.y_south()
}
fn y_north (&self) -> N where Self: HasOrigin {
let a = self.origin();
let h = self.h();
let d = match a { NW|N|NE => 0.into(), W|X|C|Y|E => h/2.into(), SW|S|SE => h };
self.y().minus(d)
}
fn y_south (&self) -> N where Self: HasOrigin {
let a = self.origin();
let h = self.h();
let d = match a { NW|N|NE => h, W|X|C|Y|E => h/2.into(), SW|S|SE => 0.into() };
self.y().plus(d)
}
fn y_center (&self) -> N where Self: HasOrigin {
todo!()
}
}

View file

@ -2,6 +2,28 @@ use super::*;
use Origin::*; use Origin::*;
use Split::*; use Split::*;
/// Where is [0, 0] located?
///
/// ```
/// use tengri::draw::Origin;
/// let _ = Origin::NW.align(());
/// ```
#[cfg_attr(test, derive(Arbitrary))]
#[derive(Debug, Copy, Clone, Default)] pub enum Origin {
#[default] C, X, Y, NW, N, NE, E, SE, S, SW, W
}
/// Something that has `[0, 0]` at a particular point.
pub trait HasOrigin {
fn origin (&self) -> Origin;
}
impl<T: AsRef<Origin>> HasOrigin for T {
fn origin (&self) -> Origin {
*self.as_ref()
}
}
pub trait Xy<N: Coord> { pub trait Xy<N: Coord> {
fn x (&self) -> N; fn x (&self) -> N;
fn y (&self) -> N; fn y (&self) -> N;
@ -17,59 +39,16 @@ pub trait Xywh<N: Coord>: Xy<N> + Wh<N> {
} }
} }
pub trait Lrtb<N: Coord> { pub trait Wide<N: Coord>: Xy<N> {
// FIXME: factor origin
fn lrtb (&self) -> [N;4] {
[self.x(), self.y(), self.x()+self.w(), self.y()+self.h()]
}
}
pub trait Wide<N: Coord> {
fn w (&self) -> N { N::zero() } fn w (&self) -> N { N::zero() }
fn w_min (&self) -> N { self.w() } fn w_min (&self) -> N { self.w() }
fn w_max (&self) -> N { self.w() } fn w_max (&self) -> N { self.w() }
fn iter_x (&self) -> impl Iterator<Item = N> where Self: HasOrigin {
self.x_west()..self.x_east()
}
fn x_west (&self) -> N where Self: HasOrigin {
let w = self.w();
let a = self.origin();
let d = match a { NW|W|SW => 0.into(), N|X|C|Y|S => w/2.into(), NE|E|SE => w };
self.x().minus(d)
}
fn x_east (&self) -> N where Self: HasOrigin {
let w = self.w();
let a = self.origin();
let d = match a { NW|W|SW => w, N|X|C|Y|S => w/2.into(), NE|E|SE => 0.into() };
self.x().plus(d)
}
fn x_center (&self) -> N where Self: HasOrigin {
todo!()
}
} }
pub trait Tall<N: Coord> { pub trait Tall<N: Coord> {
fn h (&self) -> N { N::zero() } fn h (&self) -> N { N::zero() }
fn h_min (&self) -> N { self.h() } fn h_min (&self) -> N { self.h() }
fn h_max (&self) -> N { self.h() } fn h_max (&self) -> N { self.h() }
fn iter_y (&self) -> impl Iterator<Item = N> where Self: HasOrigin {
self.y_north()..self.y_south()
}
fn y_north (&self) -> N where Self: HasOrigin {
let a = self.origin();
let h = self.h();
let d = match a { NW|N|NE => 0.into(), W|X|C|Y|E => h/2.into(), SW|S|SE => h };
self.y().minus(d)
}
fn y_south (&self) -> N where Self: HasOrigin {
let a = self.origin();
let h = self.h();
let d = match a { NW|N|NE => h, W|X|C|Y|E => h/2.into(), SW|S|SE => 0.into() };
self.y().plus(d)
}
fn y_center (&self) -> N where Self: HasOrigin {
todo!()
}
} }
/// Point with size. /// Point with size.

View file

@ -325,13 +325,8 @@ impl Coord for u16 {
} }
} }
impl Draw<Tui> for u64 { impl_draw!(|self: u64, _to: Tui|{ todo!() });
fn draw (self, _to: &mut Tui) -> Usually<XYWH<u16>> { todo!() } impl_draw!(|self: f64, _to: Tui|{ todo!() });
}
impl Draw<Tui> for f64 {
fn draw (self, _to: &mut Tui) -> Usually<XYWH<u16>> { todo!() }
}
mod phat { mod phat {
use super::*; use super::*;

View file

@ -23,7 +23,7 @@ pub const fn border (on: bool, style: impl BorderStyle, draw: impl Draw<Tui>) ->
to.blit(&style.border_e(), x + w - 1, y, style.style()); to.blit(&style.border_e(), x + w - 1, y, style.style());
} }
} }
Ok(XYWH(x, y, w, h)) Ok(Some(XYWH(x, y, w, h)))
})); }));
above(outline, content) above(outline, content)
} }
@ -48,7 +48,7 @@ macro_rules! border {
#[derive(Copy, Clone)] pub struct $T(pub bool, pub Style); #[derive(Copy, Clone)] pub struct $T(pub bool, pub Style);
//impl Layout<Tui> for $T {} //impl Layout<Tui> for $T {}
impl Draw<Tui> for $T { impl Draw<Tui> for $T {
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> { fn draw (self, to: &mut Tui) -> Perhaps<XYWH<u16>> {
when(self.enabled(), thunk(|to: &mut Tui|BorderStyle::draw(self, to))).draw(to) when(self.enabled(), thunk(|to: &mut Tui|BorderStyle::draw(self, to))).draw(to)
} }
} }

View file

@ -3,40 +3,19 @@ pub(crate) use ::unicode_width::*;
#[cfg(feature = "term")] mod impl_term { #[cfg(feature = "term")] mod impl_term {
use super::*; use super::*;
use crate::term::Tui; use crate::*;
use ratatui::prelude::Position; use ratatui::prelude::Position;
impl Draw<Tui> for &str { impl_draw!(|self: String, to: Tui|{self.as_str().draw(to)});
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> { impl_draw!(|self: std::sync::Arc<str>, to: Tui|{self.as_ref().draw(to)});
impl_draw!(|self: &std::sync::Arc<str>, to: Tui|{self.as_ref().draw(to)});
impl_draw!(|self: &str, to: Tui|{
let XYWH(x, y, w, ..) = to.1.centered_xy([width_chars_max(to.w(), self), 1]); let XYWH(x, y, w, ..) = to.1.centered_xy([width_chars_max(to.w(), self), 1]);
to.text(&self, x, y, w) to.text(&self, x, y, w)
} });
}
impl Draw<Tui> for String { impl_draw!(<T: AsRef<str>,>|self: TrimString<T>, to: Tui|{self.as_ref().draw(to)});
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> { impl_draw!(<T: AsRef<str>,>|self: TrimStringRef<'_, T>, to: Tui|{
self.as_str().draw(to)
}
}
impl Draw<Tui> for std::sync::Arc<str> {
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
self.as_ref().draw(to)
}
}
impl Draw<Tui> for &std::sync::Arc<str> {
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
self.as_ref().draw(to)
}
}
impl<T: AsRef<str>> Draw<Tui> for TrimString<T> {
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> { self.as_ref().draw(to) }
}
impl<T: AsRef<str>> Draw<Tui> for TrimStringRef<'_, T> {
fn draw (self, to: &mut Tui) -> Usually<XYWH<u16>> {
let XYWH(x, y, w, ..) = to.1; let XYWH(x, y, w, ..) = to.1;
let mut width: u16 = 1; let mut width: u16 = 1;
let mut chars = self.1.as_ref().chars(); let mut chars = self.1.as_ref().chars();
@ -52,14 +31,13 @@ pub(crate) use ::unicode_width::*;
} }
let XYWH(x, y, w, ..) = XYWH(to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h()); let XYWH(x, y, w, ..) = XYWH(to.x(), to.y(), to.w().min(self.0).min(self.1.as_ref().width() as u16), to.h());
to.text(&self.as_ref(), x, y, w) to.text(&self.as_ref(), x, y, w)
} });
}
impl Tui { impl Tui {
/// Write a line of text /// Write a line of text
/// ///
/// TODO: do a paragraph (handle newlines) /// TODO: do a paragraph (handle newlines)
pub fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16) -> Usually<XYWH<u16>> { pub fn text (&mut self, text: &impl AsRef<str>, x0: u16, y: u16, max_width: u16) -> Perhaps<XYWH<u16>> {
let text = text.as_ref(); let text = text.as_ref();
let mut string_width: u16 = 0; let mut string_width: u16 = 0;
for character in text.chars() { for character in text.chars() {
@ -75,7 +53,7 @@ pub(crate) use ::unicode_width::*;
break break
} }
} }
Ok(XYWH(x0, y, string_width, 1)) Ok(Some(XYWH(x0, y, string_width, 1)))
} }
} }
} }