diff --git a/src/layout.rs b/src/layout.rs index 108f013..1bd7906 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -3,6 +3,16 @@ use crate::*; impl> Layout for T {} pub trait Layout: Draw + Sized { + fn full_w (self) -> impl Draw { + Full::W(self) + } + fn full_h (self) -> impl Draw { + Full::H(self) + } + fn full_wh (self) -> impl Draw { + Full::WH(self) + } + fn origin (self, azimuth: impl Into>) -> impl Draw { Origin(azimuth.into(), self) } @@ -86,106 +96,7 @@ impl Sizer { } } -/// Some layout operations exist in multiple variants that take a single argument. -/// Their handling in [eval_view] is uniform and goes like this: -#[macro_export] macro_rules! eval_enum (( - $name:literal, $output:ident, $state:ident, $value:expr, $Enum:ident { - $($v:literal => $V:ident),* $(,)? - } -) => {{ - match $value { - $(Some($v) => $Enum::$V,)* - frag => return Err(format!("invalid variant: {}/{frag:?}", $name).into()) - } -}}); - -/// Some layout operations exist in XY, X, and Y variants that take 3 or 2 arguments. -/// Their handling in [eval_view] is uniform and goes like this: -#[macro_export] macro_rules! eval_xy ( - // Valueless variant: - // (fill/x ...) - // (fill/y ...) - // (fill/xy ...) - ( - $name:expr => $expr:expr, $head:expr, $output:ident, $state:ident, - $variant:expr, $xy:ident, $x:ident, $y:ident, $arg:expr, - ) => {{ - // frags.next(): 2nd slash-delimited fragment: /x, /y, /xy - let variant = $variant; - let thunk = draw(move|screen|$state.interpret(screen, &$arg)); - match variant { - // X variant - Some("x") => thunk.$x().draw($output), - // Y variant - Some("y") => thunk.$y().draw($output), - // XY variant (can be omitted) - Some("xy") | None => thunk.$xy().draw($output), - // Other namespace members are invalid - frag => invalid_variant($name, frag, $expr, $head) - } - }}; - - // Variadic variant: - // (push/x n ...) - // (push/y n ...) - // (push/xy n m ...) - ( - $name:expr => $expr:expr, $head:expr, $output:ident, $state:ident, - $variant:expr, $xy:ident, $x:ident, $y:ident, $arg0:expr, $arg1:expr, $arg2:expr, - ) => {{ - // frags.next(): 2nd slash-delimited fragment: /x, /y, /xy - let variant = $variant; - let thunk = draw(move|screen|$state.interpret(screen, &match variant { - Some("x") | Some("y") => $arg1, - Some("xy") | None => $arg2, - _ => panic!("{}: unsupported axis {variant:?}; try /x, /y, /xy", $name) - })); - match variant { - // X variant - Some("x") => thunk.$x($state.namespace($arg0?)?) - .draw($output), - // Y variant - Some("y") => thunk.$y($state.namespace($arg0?)?) - .draw($output), - // XY variant (can be omitted) - Some("xy") | None => thunk.$xy($state.namespace($arg0?)?, $state.namespace($arg1?)?) - .draw($output), - // Other namespace members are invalid - frag => invalid_variant($name, frag, $expr, $head) - } - }}; -); - -#[macro_export] macro_rules! fn_kw_layout { - ($name:ident |$state:ident, $output: ident, $expr:ident| $body:block) => { - #[cfg(all(feature = "draw", feature = "eval"))] - pub fn $name ( - $state: &S, $output: &mut O, $expr: &str - ) -> Perhaps> where - S: Interpret>> - + for<'b> Namespace<'b, bool> - + for<'b> Namespace<'b, O::Unit> - + for<'b> Namespace<'b, Option> - $body - } -} - -#[macro_export] macro_rules! fn_kw_layout_tui { - ($name:ident |$state:ident, $output: ident, $expr:ident| $body:block) => { - #[cfg(all(feature = "draw", feature = "eval", feature = "term"))] - pub fn $name ( - $state: &S, $output: &mut Tui, $expr: &str - ) -> Perhaps> where - S: Interpret>> - + for<'b> Namespace<'b, bool> - + for<'b> Namespace<'b, u16> - + for<'b> Namespace<'b, Option> - + for<'b> Namespace<'b, Color> - $body - } -} - -#[macro_export] macro_rules! def_layout_modifier { +macro_rules! def_layout_modifier { ( $Trait:ident ($fn_x:ident $fn_y:ident $fn_xy:ident), $Struct:ident { $X:ident $Y:ident $XY:ident } $kw_name:ident $name:literal @@ -207,13 +118,15 @@ impl Sizer { } impl , X: Into> + Copy> Draw for $Struct { - fn draw (&$self, $to: &mut S) -> Perhaps> { $body } + fn draw (&$self, $to: &mut S) -> Perhaps> { + $body + } } fn_kw_layout!($kw_name |state, output, expr| { let head = expr.head()?; let mut frags = head.src()?.unwrap_or_default().split("/"); - Ok(matches!(frags.next(), Some($name)).then(||{ + Ok(matches!(expr.head()?, Some("exact")).then(||{ let args = expr.tail(); let arg0 = args.head(); let tail0 = args.tail(); @@ -230,43 +143,6 @@ impl Sizer { } } -#[macro_export] macro_rules! def_layout_modifier_flat { - ( - $Trait:ident ($fn_x:ident $fn_y:ident $fn_xy:ident), - $Struct:ident { $X:ident $Y:ident $XY:ident } $kw_name:ident $name:literal - |$self:ident, $to:ident| $body:block - ) => { - impl> $Trait for T {} - - pub trait $Trait: Draw + Sized { - fn $fn_x (self) -> $Struct { $Struct::$X(self) } - fn $fn_y (self) -> $Struct { $Struct::$Y(self) } - fn $fn_xy (self) -> $Struct { $Struct::$XY(self) } - } - - pub enum $Struct> { - __(PhantomData), $X(I), $Y(I), $XY(I), - } - - impl > Draw for $Struct { - fn draw (&$self, $to: &mut S) -> Perhaps> { $body } - } - - fn_kw_layout!($kw_name |state, output, expr| { - let head = expr.head()?; - let mut frags = head.src()?.unwrap_or_default().split("/"); - Ok(matches!(frags.next(), Some($name)).then(||{ - let args = expr.tail(); - let arg0 = args.head(); - eval_xy!( - $name => expr, head, output, state, frags.next(), - $fn_xy, $fn_x, $fn_y, arg0, - ) - }).transpose()?.flatten()) - }); - } -} - mod area; pub use self::area::*; mod axis; pub use self::axis::*; mod azimuth; pub use self::azimuth::*; diff --git a/src/layout/axis.rs b/src/layout/axis.rs index 28002d5..2625744 100644 --- a/src/layout/axis.rs +++ b/src/layout/axis.rs @@ -1,23 +1,28 @@ use crate::*; -def_layout_modifier_flat!( - LayoutFull (full_w full_h full_wh), - Full { W H WH } kw_full "full" |self, to| { - let XYWH(x0, y0, w0, h0) = to.area(); - let item = match self { - Self::W(i) => i, Self::H(i) => i, Self::WH(i) => i, _ => unreachable!() - }; - let (x1, y1, w1, h1) = match self { - Self::W(..) => (Some(x0), None, Some(w0), None), - Self::H(..) => (None, Some(y0), None, Some(h0)), - Self::WH(..) => (Some(x0), Some(y0), Some(w0), Some(h0)), - _ => unreachable!() - }; - Ok(to.draw(to.area(), item)?.map(|XYWH(x2, y2, w2, h2)|XYWH( - x1.unwrap_or(x2), y1.unwrap_or(y2), w1.unwrap_or(w2), h1.unwrap_or(h2), - ))) - } -); +/// Use whole drawing area along one or both axes. +pub enum Full> { + __(PhantomData), + W(I), + H(I), + WH(I), +} + +impl_draw!(,>|self: Full, to: T|{ + let XYWH(x0, y0, w0, h0) = to.area(); + let item = match self { + Self::W(i) => i, Self::H(i) => i, Self::WH(i) => i, _ => unreachable!() + }; + let (x1, y1, w1, h1) = match self { + Self::W(..) => (Some(x0), None, Some(w0), None), + Self::H(..) => (None, Some(y0), None, Some(h0)), + Self::WH(..) => (Some(x0), Some(y0), Some(w0), Some(h0)), + _ => unreachable!() + }; + Ok(to.draw(to.area(), item)?.map(|XYWH(x2, y2, w2, h2)|XYWH( + x1.unwrap_or(x2), y1.unwrap_or(y2), w1.unwrap_or(w2), h1.unwrap_or(h2), + ))) +}); #[cfg(test)] #[test] fn test_layout_full () -> Usually<()> { assert_eq!(check_layout("1")?, Some(XYWH(1u16, 1, 1, 1))); @@ -30,23 +35,14 @@ def_layout_modifier_flat!( def_layout_modifier!( LayoutExact (exact_w exact_h exact_wh), Exact { W H WH } kw_exact "exact" |self, to| { - let XYWH(x0, y0, w0, h0) = to.area(); - let (item, w1, h1) = match self { - Self::W(item, w1) => (item, (*w1).into(), None), - Self::H(item, h1) => (item, None, (*h1).into()), - Self::WH(item, w1, h1) => (item, (*w1).into(), (*h1).into()), - _ => unreachable!() + let XYWH(x, y, w0, h0) = to.area(); + let (item, w, h) = match self { + Self::W(item, w) => (item, (*w).into(), None), + Self::H(item, h) => (item, None, (*h).into()), + Self::WH(item, w, h) => (item, (*w).into(), (*h).into()), + _ => return Ok(None) }; - let w1 = w1.unwrap_or(w0); - let h1 = h1.unwrap_or(h0); - let area = XYWH(x0, y0, w1, h1); - let area = to.draw(area, item)?; - Ok(area.map(|XYWH(x2, y2, w2, h2)|match self { - Self::W(..) => XYWH(x0, y2, w1, h2), - Self::H(..) => XYWH(x2, y0, w2, h1), - Self::WH(..) => XYWH(x0, y0, w1, h1), - _ => unreachable!() - })) + to.draw(XYWH(x, y, w.unwrap_or(w0), h.unwrap_or(h0)), item) } ); diff --git a/src/layout/azimuth.rs b/src/layout/azimuth.rs index bfb4d80..eb8d038 100644 --- a/src/layout/azimuth.rs +++ b/src/layout/azimuth.rs @@ -192,13 +192,15 @@ fn draw_stacks ( area_b: impl Into>>, origin_b: impl Into>, ) -> Usually<(Option>, Option>)> { - let draw_a = |to: &mut S|Ok::<_, Box>(match origin_a.into() { - Some(origin_a) => to.draw(area_a.into(), a.align(origin_a))?, - None => to.draw(area_a.into(), a)? + let draw_a = |to: &mut S|Ok::<_, Box>(if let Some(origin_a) = origin_a.into() { + to.draw(area_a.into(), a.align(origin_a))? + } else { + to.draw(area_a.into(), a)? }); - let draw_b = |to: &mut S|Ok::<_, Box>(match origin_b.into() { - Some(origin_b) => to.draw(area_b.into(), b.align(origin_b))?, - None => to.draw(area_b.into(), b)? + let draw_b = |to: &mut S|Ok::<_, Box>(if let Some(origin_b) = origin_b.into() { + to.draw(area_b.into(), b.align(origin_b))? + } else { + to.draw(area_b.into(), b)? }); Ok(if matches!(split, Split::Below) { let drawn_b = draw_b(to)?; @@ -215,7 +217,7 @@ pub fn stack_areas ( a: impl Draw, b: impl Draw, ) -> Usually<(Option>, Option>)> { - let area_a = to.size(None, a)?; + let area_a = to.draw(None, a)?; Ok(match split { Split::South => ( area_a, diff --git a/src/layout/iter.rs b/src/layout/iter.rs index 1662c0c..b43a9d0 100644 --- a/src/layout/iter.rs +++ b/src/layout/iter.rs @@ -1,49 +1,5 @@ use super::*; -pub fn iter_south <'a, S: Screen, D: Draw, I: Iterator> ( - iter: impl Fn()->I -) -> impl Draw { - draw(move|to: &mut S|{ - let XYWH(x, y, w, h) = to.area(); - let mut y_next = y; - let mut h_used = S::Unit::zero(); - for item in iter() { - let area = XYWH(x, y_next, w, h.minus(h_used)); - if let Some(used) = to.draw(area, item)? { - let used_h = used.h(); - y_next = y_next.plus(used_h); - h_used = h_used.plus(used_h); - if h_used >= h { - break; - } - } - } - Ok(Some(XYWH(x, y, w, h_used))) - }) -} - -pub fn iter_east <'a, S: Screen, D: Draw, I: Iterator> ( - iter: impl Fn()->I -) -> impl Draw { - draw(move|to: &mut S|{ - let XYWH(x, y, w, h) = to.area(); - let mut x_next = x; - let mut w_used = S::Unit::zero(); - for item in iter() { - let area = XYWH(x_next, y, w.minus(w_used), h); - if let Some(used) = to.draw(area, item)? { - let used_w = used.w(); - x_next = x_next.plus(used_w); - w_used = w_used.plus(used_w); - if w_used >= w { - break; - } - } - } - Ok(Some(XYWH(x, y, w_used, h))) - }) -} - /// Iterate over a collection of the same kind of [Draw]able: pub fn iter <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, @@ -75,12 +31,40 @@ pub fn iter_north <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( draw(move|_to: &mut S|{ todo!() }) } +pub fn iter_east <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( + _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, +) -> impl Draw { + draw(move|_to: &mut S|{ todo!() }) +} + pub fn iter_east_fixed <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( _height: S::Unit, _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, ) -> impl Draw { draw(move|_to: &mut S|{ todo!() }) } +pub fn iter_south <'a, S: Screen, D: Draw, I: Iterator> ( + iter: impl Fn()->I +) -> impl Draw { + draw(move|to: &mut S|{ + let XYWH(x, y, w, h) = to.area(); + let mut y_next = y; + let mut h_used = S::Unit::zero(); + for item in iter() { + let area = XYWH(x, y_next, w, h.minus(h_used)); + if let Some(used) = to.draw(area, item)? { + let used_h = used.h(); + y_next = y_next.plus(used_h); + h_used = h_used.plus(used_h); + if h_used >= h { + break; + } + } + } + Ok(Some(XYWH(x, y, w, h_used))) + }) +} + pub fn iter_south_fixed <'a, S: Screen, D: 'a, I: Iterator, U: Draw> ( _height: S::Unit, _iter: impl Fn()->I, _draw: impl Fn(D, usize)->U, ) -> impl Draw { diff --git a/src/lib.rs b/src/lib.rs index 494a73b..892f0a4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,37 @@ pub(crate) use ::{ std::marker::PhantomData }; +#[cfg(all(feature = "draw", feature = "eval"))] +macro_rules! fn_kw_layout { + ($name:ident |$state:ident, $output: ident, $expr:ident| $body:block) => { + #[cfg(all(feature = "draw", feature = "eval"))] + pub fn $name ( + $state: &S, $output: &mut O, $expr: &str + ) -> Perhaps> where + S: Interpret>> + + for<'b> Namespace<'b, bool> + + for<'b> Namespace<'b, O::Unit> + + for<'b> Namespace<'b, Option> + $body + } +} + +#[cfg(all(feature = "draw", feature = "eval", feature = "term"))] +macro_rules! fn_kw_layout_tui { + ($name:ident |$state:ident, $output: ident, $expr:ident| $body:block) => { + #[cfg(all(feature = "draw", feature = "eval", feature = "term"))] + pub fn $name ( + $state: &S, $output: &mut Tui, $expr: &str + ) -> Perhaps> where + S: Interpret>> + + for<'b> Namespace<'b, bool> + + for<'b> Namespace<'b, u16> + + for<'b> Namespace<'b, Option> + + for<'b> Namespace<'b, Color> + $body + } +} + #[cfg(feature = "lang")] pub use ::dizzle::{Usually, Perhaps}; #[cfg(feature = "lang")] use ::dizzle::*; @@ -148,6 +179,76 @@ pub trait AsMutOpt { fn as_mut_opt (&mut self) -> Option<&mut T>; } } ); +/// Some layout operations exist in multiple variants that take a single argument. +/// Their handling in [eval_view] is uniform and goes like this: +macro_rules! eval_enum (( + $name:literal, $output:ident, $state:ident, $value:expr, $Enum:ident { + $($v:literal => $V:ident),* $(,)? + } +) => {{ + match $value { + $(Some($v) => $Enum::$V,)* + frag => unimplemented!("{}/{frag:?}", $name) + } +}}); + +/// Some layout operations exist in XY, X, and Y variants that take 3 or 2 arguments. +/// Their handling in [eval_view] is uniform and goes like this: +macro_rules! eval_xy ( + // Valueless variant: + // (fill/x ...) + // (fill/y ...) + // (fill/xy ...) + ( + $name:expr => $expr:expr, $head:expr, $output:ident, $state:ident, + $variant:expr, $xy:ident, $x:ident, $y:ident, $arg:expr, + ) => {{ + // frags.next(): 2nd slash-delimited fragment: /x, /y, /xy + let variant = $variant; + let thunk = draw(move|screen|$state.interpret(screen, &$arg)); + match variant { + // X variant + Some("x") => thunk.$x().draw($output), + // Y variant + Some("y") => thunk.$y().draw($output), + // XY variant (can be omitted) + Some("xy") | None => thunk.$xy().draw($output), + // Other namespace members are invalid + frag => invalid_variant($name, frag, $expr, $head) + } + }}; + + // Variadic variant: + // (push/x n ...) + // (push/y n ...) + // (push/xy n m ...) + ( + $name:expr => $expr:expr, $head:expr, $output:ident, $state:ident, + $variant:expr, $xy:ident, $x:ident, $y:ident, $arg0:expr, $arg1:expr, $arg2:expr, + ) => {{ + // frags.next(): 2nd slash-delimited fragment: /x, /y, /xy + let variant = $variant; + let thunk = draw(move|screen|$state.interpret(screen, &match variant { + Some("x") | Some("y") => $arg1, + Some("xy") | None => $arg2, + _ => panic!("{}: unsupported axis {variant:?}; try /x, /y, /xy", $name) + })); + match variant { + // X variant + Some("x") => thunk.$x($state.namespace($arg0?)?) + .draw($output), + // Y variant + Some("y") => thunk.$y($state.namespace($arg0?)?) + .draw($output), + // XY variant (can be omitted) + Some("xy") | None => thunk.$xy($state.namespace($arg0?)?, $state.namespace($arg1?)?) + .draw($output), + // Other namespace members are invalid + frag => invalid_variant($name, frag, $expr, $head) + } + }}; +); + #[cfg(feature = "exit")] pub use self::exit::*; #[cfg(feature = "exit")] mod exit { use crate::*; diff --git a/src/term.rs b/src/term.rs index e9060b1..bfbe0e0 100644 --- a/src/term.rs +++ b/src/term.rs @@ -24,6 +24,7 @@ pub(crate) use ::{ mod border; pub use self::border::*; mod buffer; pub use self::buffer::*; +mod button; pub use self::button::*; mod event; pub use self::event::*; mod keys; pub use self::keys::*; mod modify; pub use self::modify::*; diff --git a/src/term/button.rs b/src/term/button.rs new file mode 100644 index 0000000..85c7887 --- /dev/null +++ b/src/term/button.rs @@ -0,0 +1,28 @@ +use crate::{*, Color::*}; + +/// ``` +/// let _ = tengri::button_2("", "", true); +/// let _ = tengri::button_2("", "", false); +/// ``` +pub const fn button_2 <'a> (key: impl Draw, label: impl Draw, hide: bool) -> impl Draw { + let c1 = tui_orange(); + let c2 = tui_g(0); + let c3 = tui_g(96); + let c4 = tui_g(255); + bold(true, fg_bg(c1, c2, east(fg(c2, east(key, fg(c3, "▐"))), when(!hide, fg_bg(c4, c3, label))))) +} + +/// ``` +/// let _ = tengri::button_3("", "", "", true); +/// let _ = tengri::button_3("", "", "", false); +/// ``` +pub const fn button_3 <'a> ( + key: impl Draw, label: impl Draw, value: impl Draw, editing: bool, +) -> impl Draw { + bold(true, east( + fg_bg(tui_orange(), tui_g(0), + east(fg(tui_g(0), "▐"), east(key, fg(if editing { tui_g(128) } else { tui_g(96) }, "▐")))), + east( + when(!editing, east(fg_bg(tui_g(255), tui_g(96), label), fg_bg(tui_g(128), tui_g(96), "▐"),)), + east(fg_bg(tui_g(224), tui_g(128), value), fg_bg(tui_g(128), Reset, "▌"), )))) +}