From 34e731f111166caa5c6680017ef47c5e8786d3bb Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 30 Dec 2024 19:07:43 +0100 Subject: [PATCH] wip: big flat pt.4: extract layout crate --- Cargo.toml | 2 + engine/src/engine.rs | 51 ++- engine/src/output.rs | 92 ++--- engine/src/tui.rs | 26 +- layout/Cargo.lock | 673 ++++++++++++++++++++++++++++++++ layout/Cargo.toml | 7 + layout/src/collection.rs | 139 +++++++ layout/src/collection/bsp.rs | 99 +++++ layout/src/collection/layers.rs | 56 +++ layout/src/collection/split.rs | 169 ++++++++ layout/src/collection/stack.rs | 65 +++ layout/src/direction.rs | 30 ++ layout/src/layers.rs | 1 + layout/src/lib.rs | 18 + layout/src/logic.rs | 43 ++ layout/src/space.rs | 317 +++++++++++++++ layout/src/transform.rs | 372 ++++++++++++++++++ layout/src/tui.rs | 42 ++ src/lib.rs | 2 - src/space/cond.rs | 1 - src/tui.rs | 3 - 21 files changed, 2125 insertions(+), 83 deletions(-) create mode 100644 layout/Cargo.lock create mode 100644 layout/Cargo.toml create mode 100644 layout/src/collection.rs create mode 100644 layout/src/collection/bsp.rs create mode 100644 layout/src/collection/layers.rs create mode 100644 layout/src/collection/split.rs create mode 100644 layout/src/collection/stack.rs create mode 100644 layout/src/direction.rs create mode 100644 layout/src/layers.rs create mode 100644 layout/src/lib.rs create mode 100644 layout/src/logic.rs create mode 100644 layout/src/space.rs create mode 100644 layout/src/transform.rs create mode 100644 layout/src/tui.rs delete mode 100644 src/tui.rs diff --git a/Cargo.toml b/Cargo.toml index ae5af5fa..e09d8a9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ version = "0.2.0" [dependencies] tek_engine = { path = "./engine" } +tek_layout = { path = "./layout" } + atomic_float = "1.0.0" backtrace = "0.3.72" clap = { version = "4.5.4", features = [ "derive" ] } diff --git a/engine/src/engine.rs b/engine/src/engine.rs index 59f332a9..f1e8d6a7 100644 --- a/engine/src/engine.rs +++ b/engine/src/engine.rs @@ -36,14 +36,14 @@ pub trait Coordinate: Send + Sync + Copy + Into + Into { - fn minus (self, other: Self) -> Self { + #[inline] fn minus (self, other: Self) -> Self { if self >= other { self - other } else { 0.into() } } - fn zero () -> Self { + #[inline] fn zero () -> Self { 0.into() } } @@ -65,11 +65,21 @@ pub trait Size { } } +impl Size for (N, N) { + #[inline] fn x (&self) -> N { self.0 } + #[inline] fn y (&self) -> N { self.1 } +} + +impl Size for [N;2] { + #[inline] fn x (&self) -> N { self[0] } + #[inline] fn y (&self) -> N { self[1] } +} + pub trait Area: Copy { - fn x (&self) -> N; - fn y (&self) -> N; - fn w (&self) -> N; - fn h (&self) -> N; + fn x (&self) -> N; + fn y (&self) -> N; + fn w (&self) -> N; + fn h (&self) -> N; #[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> { if self.w() < w || self.h() < h { Err(format!("min {w}x{h}").into()) @@ -77,4 +87,33 @@ pub trait Area: Copy { Ok(self) } } + #[inline] fn wh (&self) -> [N;2] { + [self.w(), self.h()] + } + #[inline] fn xywh (&self) -> [N;4] { + [self.x(), self.y(), self.w(), self.h()] + } + #[inline] fn clip_h (&self, h: N) -> [N;4] { + [self.x(), self.y(), self.w(), self.h().min(h)] + } + #[inline] fn clip_w (&self, w: N) -> [N;4] { + [self.x(), self.y(), self.w().min(w), self.h()] + } + #[inline] fn clip (&self, wh: impl Size) -> [N;4] { + [self.x(), self.y(), wh.w(), wh.h()] + } +} + +impl Area for (N, N, N, N) { + #[inline] fn x (&self) -> N { self.0 } + #[inline] fn y (&self) -> N { self.1 } + #[inline] fn w (&self) -> N { self.2 } + #[inline] fn h (&self) -> N { self.3 } +} + +impl Area for [N;4] { + #[inline] fn x (&self) -> N { self[0] } + #[inline] fn y (&self) -> N { self[1] } + #[inline] fn w (&self) -> N { self[2] } + #[inline] fn h (&self) -> N { self[3] } } diff --git a/engine/src/output.rs b/engine/src/output.rs index c41272fc..42412e75 100644 --- a/engine/src/output.rs +++ b/engine/src/output.rs @@ -1,6 +1,38 @@ use crate::*; use std::sync::{Arc, Mutex, RwLock}; +/// Define custom content for a struct. +#[macro_export] macro_rules! render { + + // Implement for all engines + (|$self:ident:$Struct:ident$(<$($L:lifetime),*E$(,$T:ident$(:$U:path)?)*$(,)?>)?|$cb:expr) => { + impl Content for $Struct $(<$($L,)* E, $($T),*>)? { + fn content (&$self) -> Option> { + Some($cb) + } + } + }; + + // Implement for a specific engine + (<$E:ty>|$self:ident:$Struct:ident$(< + $($($L:lifetime),+)? + $($($T:ident$(:$U:path)?),+)? + >)?|$cb:expr) => { + impl $(< + $($($L),+)? + $($($T$(:$U)?),+)? + >)? Content<$E> for $Struct $(< + $($($L),+)? + $($($T),+)? + >)? { + fn content (&$self) -> Option> { + Some($cb) + } + } + } + +} + /// Write content to output buffer. pub trait Render: Send + Sync { /// Minimum size to use @@ -18,18 +50,18 @@ pub trait Content: Send + Sync { fn content (&self) -> Option>; } -impl> Render for C { - /// Minimum size to use - fn min_size (&self, to: E::Size) -> Perhaps { - self.content().map(|content|content.min_size(to)) - .unwrap_or(Ok(None)) - } - /// Draw to output render target - fn render (&self, to: &mut E::Output) -> Usually<()> { - self.content().map(|content|content.render(to)) - .unwrap_or(Ok(())) - } -} +//impl> Render for C { + ///// Minimum size to use + //fn min_size (&self, to: E::Size) -> Perhaps { + //self.content().map(|content|content.min_size(to)) + //.unwrap_or(Ok(None)) + //} + ///// Draw to output render target + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //self.content().map(|content|content.render(to)) + //.unwrap_or(Ok(())) + //} +//} /// Rendering target pub trait Output { @@ -44,9 +76,9 @@ pub trait Output { //impl Render for &dyn Render {} //impl Render for &mut dyn Render {} //impl Render for Box> {} -//impl> Render for &R {} +impl> Render for &R {} //impl> Render for &mut R {} -//impl> Render for Option {} +impl> Render for Option {} //impl> Render for Arc {} //impl> Render for Mutex {} //impl> Render for RwLock {} @@ -101,38 +133,6 @@ impl> Render for C { //} -/// Define custom content for a struct. -#[macro_export] macro_rules! render { - - // Implement for all engines - (|$self:ident:$Struct:ident$(<$($L:lifetime),*E$(,$T:ident$(:$U:path)?)*$(,)?>)?|$cb:expr) => { - impl Content for $Struct $(<$($L,)* E, $($T),*>)? { - fn content (&$self) -> Option> { - Some($cb) - } - } - }; - - // Implement for a specific engine - (<$E:ty>|$self:ident:$Struct:ident$(< - $($($L:lifetime),+)? - $($($T:ident$(:$U:path)?),+)? - >)?|$cb:expr) => { - impl $(< - $($($L),+)? - $($($T$(:$U)?),+)? - >)? Content<$E> for $Struct $(< - $($($L),+)? - $($($T),+)? - >)? { - fn content (&$self) -> Option> { - Some($cb) - } - } - } - -} - impl> Render for &R { fn min_size (&self, to: E::Size) -> Perhaps { (*self).min_size(to) diff --git a/engine/src/tui.rs b/engine/src/tui.rs index 11670ce9..2f1d2925 100644 --- a/engine/src/tui.rs +++ b/engine/src/tui.rs @@ -25,18 +25,6 @@ pub(crate) use ratatui::{ impl Coordinate for u16 {} -impl Size for [u16;2] { - fn x (&self) -> u16 { self[0] } - fn y (&self) -> u16 { self[1] } -} - -impl Area for [u16;4] { - fn x (&self) -> u16 { self[0] } - fn y (&self) -> u16 { self[1] } - fn w (&self) -> u16 { self[2] } - fn h (&self) -> u16 { self[3] } -} - pub struct Tui { pub exited: Arc, pub buffer: Buffer, @@ -349,19 +337,7 @@ pub fn half_block (lower: bool, upper: bool) -> Option { //impl> Render for T {} -impl Render for &str { - fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { - // TODO: line breaks - Ok(Some([self.chars().count() as u16, 1])) - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - let [x, y, ..] = to.area(); - //let [w, h] = self.min_size(to.area().wh())?.unwrap(); - Ok(to.blit(&self, x, y, None)) - } -} - -impl Render for &String { +impl Render for str { fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { // TODO: line breaks Ok(Some([self.chars().count() as u16, 1])) diff --git a/layout/Cargo.lock b/layout/Cargo.lock new file mode 100644 index 00000000..b49cd71f --- /dev/null +++ b/layout/Cargo.lock @@ -0,0 +1,673 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "better-panic" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036" +dependencies = [ + "backtrace", + "console", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "itertools 0.12.1", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tek_engine" +version = "0.2.0" +dependencies = [ + "better-panic", + "crossterm", + "ratatui", +] + +[[package]] +name = "tek_layout" +version = "0.2.0" +dependencies = [ + "tek_engine", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/layout/Cargo.toml b/layout/Cargo.toml new file mode 100644 index 00000000..a4af16d0 --- /dev/null +++ b/layout/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "tek_layout" +edition = "2021" +version = "0.2.0" + +[dependencies] +tek_engine = { path = "../engine" } diff --git a/layout/src/collection.rs b/layout/src/collection.rs new file mode 100644 index 00000000..51c57096 --- /dev/null +++ b/layout/src/collection.rs @@ -0,0 +1,139 @@ +use crate::*; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +mod bsp; pub use self::bsp::*; +mod layers; pub use self::layers::*; +mod split; pub use self::split::*; +mod stack; pub use self::stack::*; + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/// A function or closure that emits renderables. +pub trait CollectCallback: Send + + Sync + + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +{} + +/// Any function or closure that emits renderables for the given engine matches [CollectCallback]. +impl CollectCallback for F +where + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +{} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +pub enum Collect<'a, E: Engine, const N: usize> { + Callback(CallbackCollection<'a, E>), + //Iterator(IteratorCollection<'a, E>), + Array(ArrayCollection<'a, E, N>), + Slice(SliceCollection<'a, E>), +} + +impl<'a, E: Engine, const N: usize> Collect<'a, E, N> { + pub fn iter (&'a self) -> CollectIterator<'a, E, N> { + CollectIterator(0, &self) + } +} + +impl<'a, E: Engine, const N: usize> From> for Collect<'a, E, N> { + fn from (callback: CallbackCollection<'a, E>) -> Self { + Self::Callback(callback) + } +} + +impl<'a, E: Engine, const N: usize> From> for Collect<'a, E, N> { + fn from (slice: SliceCollection<'a, E>) -> Self { + Self::Slice(slice) + } +} + +impl<'a, E: Engine, const N: usize> From> for Collect<'a, E, N>{ + fn from (array: ArrayCollection<'a, E, N>) -> Self { + Self::Array(array) + } +} + +type CallbackCollection<'a, E> = + &'a dyn Fn(&'a mut dyn FnMut(&dyn Render)->Usually<()>); + +//type IteratorCollection<'a, E> = + //&'a mut dyn Iterator>; + +type SliceCollection<'a, E> = + &'a [&'a dyn Render]; + +type ArrayCollection<'a, E, const N: usize> = + [&'a dyn Render; N]; + +pub struct CollectIterator<'a, E: Engine, const N: usize>(usize, &'a Collect<'a, E, N>); + +impl<'a, E: Engine, const N: usize> Iterator for CollectIterator<'a, E, N> { + type Item = &'a dyn Render; + fn next (&mut self) -> Option { + match self.1 { + Collect::Callback(callback) => { + todo!() + }, + //Collection::Iterator(iterator) => { + //iterator.next() + //}, + Collect::Array(array) => { + if let Some(item) = array.get(self.0) { + self.0 += 1; + //Some(item) + None + } else { + None + } + } + Collect::Slice(slice) => { + if let Some(item) = slice.get(self.0) { + self.0 += 1; + //Some(item) + None + } else { + None + } + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +pub struct Map, R: Render, F: Fn(T)->R>( + PhantomData, + I, + F +); + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +pub struct Reduce, R: Render, F: Fn(&dyn Render, T)->R>( + PhantomData<(E, R)>, + I, + F +); + +impl+Send+Sync, R: Render, F: Fn(&dyn Render, T)->R+Send+Sync> Render for Reduce { + fn min_size (&self, to: E::Size) -> Perhaps { + todo!() + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + todo!() + } +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +#[cfg(test)] #[test] fn test_bsp () { + // TODO +} diff --git a/layout/src/collection/bsp.rs b/layout/src/collection/bsp.rs new file mode 100644 index 00000000..181f7fbd --- /dev/null +++ b/layout/src/collection/bsp.rs @@ -0,0 +1,99 @@ +use crate::*; + +pub enum Bsp, Y: Render> { + /// X is north of Y + N(Option, Option), + /// X is south of Y + S(Option, Option), + /// X is east of Y + E(Option, Option), + /// X is west of Y + W(Option, Option), + /// X is above Y + A(Option, Option), + /// X is below Y + B(Option, Option), + /// Should be avoided. + Null(PhantomData), +} + +impl, Y: Render> Bsp { + pub fn new (x: X) -> Self { Self::A(Some(x), None) } + pub fn n (x: X, y: Y) -> Self { Self::N(Some(x), Some(y)) } + pub fn s (x: X, y: Y) -> Self { Self::S(Some(x), Some(y)) } + pub fn e (x: X, y: Y) -> Self { Self::E(Some(x), Some(y)) } + pub fn w (x: X, y: Y) -> Self { Self::W(Some(x), Some(y)) } + pub fn a (x: X, y: Y) -> Self { Self::A(Some(x), Some(y)) } + pub fn b (x: X, y: Y) -> Self { Self::B(Some(x), Some(y)) } +} + +impl, Y: Render> Default for Bsp { + fn default () -> Self { + Self::Null(Default::default()) + } +} + +impl, Y: Render> Render for Bsp { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(Some(match self { + Self::Null(_) => [0.into(), 0.into()].into(), + Self::S(a, b) => { + let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + [a.w().max(b.w()), a.h() + b.h()].into() + }, + Self::E(a, b) => { + let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + [a.w() + b.w(), a.h().max(b.h())].into() + }, + Self::W(a, b) => { + let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + [a.w() + b.w(), a.h().max(b.h())].into() + }, + Self::N(a, b) => { + let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + [a.w().max(b.w()), a.h() + b.h()].into() + }, + _ => todo!() + })) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + let n = [0.into(), 0.into()].into(); + let s = to.area().wh().into(); + Ok(match self { + Self::Null(_) => {}, + Self::S(a, b) => { + let s_a = a.min_size(s)?.unwrap_or(n); + let _ = b.min_size(s)?.unwrap_or(n); + let h = s_a.h().into(); + to.render_in(to.area().clip_h(h).into(), a)?; + to.render_in(to.area().shrink_y(h).push_y(h).into(), b)?; + }, + Self::E(a, b) => { + let s_a = a.min_size(s)?.unwrap_or(n); + let _ = b.min_size(s)?.unwrap_or(n); + let w = s_a.w().into(); + to.render_in(to.area().clip_w(w).into(), a)?; + to.render_in(to.area().push_x(w).shrink_x(w).into(), b)?; + }, + Self::W(a, b) => { + let s_a = a.min_size(s)?.unwrap_or(n); + let _ = b.min_size(s)?.unwrap_or(n); + let w = (to.area().w() - s_a.w()).into(); + to.render_in(to.area().push_x(w).into(), a)?; + to.render_in(to.area().shrink_x(w).into(), b)?; + }, + Self::N(a, b) => { + let s_a = a.min_size(s)?.unwrap_or(n); + let _ = b.min_size(s)?.unwrap_or(n); + let h = to.area().h() - s_a.h(); + to.render_in(to.area().push_y(h).into(), a)?; + to.render_in(to.area().shrink_y(h).into(), b)?; + }, + _ => todo!() + }) + } +} diff --git a/layout/src/collection/layers.rs b/layout/src/collection/layers.rs new file mode 100644 index 00000000..5173dd2a --- /dev/null +++ b/layout/src/collection/layers.rs @@ -0,0 +1,56 @@ +use crate::*; + +/// Renders multiple things on top of each other, +/// in the order they are provided by the callback. +/// Total size is largest width x largest height. +pub struct Layers>(pub F, PhantomData); + +/// Shorthand for defining an instance of [Layers]. +#[macro_export] macro_rules! lay { + ([$($expr:expr),* $(,)?]) => { + Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + (![$($expr:expr),* $(,)?]) => { + Layers::new(|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + ($expr:expr) => { + Layers::new($expr) + }; +} + +impl< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +> Layers { + #[inline] + pub fn new (build: F) -> Self { + Self(build, Default::default()) + } +} + +impl Render for Layers +where + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +{ + fn min_size (&self, area: E::Size) -> Perhaps { + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |layer| { + if let Some(layer_area) = layer.min_size(area)? { + w = w.max(layer_area.w()); + h = h.max(layer_area.h()); + } + Ok(()) + })?; + Ok(Some([w, h].into())) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + if let Some(size) = self.min_size(to.area().wh().into())? { + (self.0)(&mut |layer|to.render_in(to.area().clip(size).into(), layer)) + } else { + Ok(()) + } + } +} + + diff --git a/layout/src/collection/split.rs b/layout/src/collection/split.rs new file mode 100644 index 00000000..8672bbb5 --- /dev/null +++ b/layout/src/collection/split.rs @@ -0,0 +1,169 @@ +use crate::*; + +/// A binary split with fixed proportion +pub struct Split(pub bool, pub Direction, pub E::Unit, A, B, PhantomData) +where E: Engine, A: Render, B: Render; + +impl, B: Render> Split { + #[inline] pub fn new (flip: bool, direction: Direction, proportion: E::Unit, a: A, b: B) -> Self { + Self(flip, direction, proportion, a, b, Default::default()) + } + #[inline] pub fn n (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + Self::new(flip, North, proportion, a, b) + } + #[inline] pub fn s (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + Self::new(flip, South, proportion, a, b) + } + #[inline] pub fn e (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + Self::new(flip, West, proportion, a, b) + } + #[inline] pub fn w (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + Self::new(flip, East, proportion, a, b) + } +} + +impl, B: Render> Render for Split { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(Some(to)) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + let (a, b) = self.1.split_fixed(self.2, self.3); + Ok(if self.0 { + to.render_in(a.into(), &self.4)?; + to.render_in(b.into(), &self.3)?; + } else { + to.render_in(a.into(), &self.3)?; + to.render_in(b.into(), &self.4)?; + }) + } +} + +impl Render for Stack +where + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +{ + fn min_size (&self, to: E::Size) -> Perhaps { + match self.1 { + + South => { + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + let max = to.h().minus(h); + if max > E::Unit::zero() { + let item = Max::y(max, Push::y(h, component)); + let size = item.min_size(to)?.map(|size|size.wh()); + if let Some([width, height]) = size { + h = h + height.into(); + w = w.max(width); + } + } + Ok(()) + })?; + Ok(Some([w, h].into())) + }, + + East => { + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + let max = to.w().minus(w); + if max > E::Unit::zero() { + let item = Max::x(max, Push::x(h, component)); + let size = item.min_size(to)?.map(|size|size.wh()); + if let Some([width, height]) = size { + w = w + width.into(); + h = h.max(height); + } + } + Ok(()) + })?; + Ok(Some([w, h].into())) + }, + + North => { + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + let max = to.h().minus(h); + if max > E::Unit::zero() { + let item = Max::y(to.h() - h, component); + let size = item.min_size(to)?.map(|size|size.wh()); + if let Some([width, height]) = size { + h = h + height.into(); + w = w.max(width); + } + } + Ok(()) + })?; + Ok(Some([w, h].into())) + }, + + West => { + let w: E::Unit = 0.into(); + let h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + if w < to.w() { + todo!(); + } + Ok(()) + })?; + Ok(Some([w, h].into())) + }, + } + } + + fn render (&self, to: &mut E::Output) -> Usually<()> { + let area = to.area(); + let mut w = 0.into(); + let mut h = 0.into(); + match self.1 { + South => { + (self.0)(&mut |item| { + if h < area.h() { + let item = Max::y(area.h() - h, Push::y(h, item)); + let show = item.min_size(area.wh().into())?.map(|s|s.wh()); + if let Some([width, height]) = show { + item.render(to)?; + h = h + height; + if width > w { w = width } + }; + } + Ok(()) + })?; + }, + East => { + (self.0)(&mut |item| { + if w < area.w() { + let item = Max::x(area.w() - w, Push::x(w, item)); + let show = item.min_size(area.wh().into())?.map(|s|s.wh()); + if let Some([width, height]) = show { + item.render(to)?; + w = width + w; + if height > h { h = height } + }; + } + Ok(()) + })?; + }, + North => { + (self.0)(&mut |item| { + if h < area.h() { + let show = item.min_size([area.w(), area.h().minus(h)].into())?.map(|s|s.wh()); + if let Some([width, height]) = show { + Shrink::y(height, Push::y(area.h() - height, item)) + .render(to)?; + h = h + height; + if width > w { w = width } + }; + } + Ok(()) + })?; + }, + _ => todo!() + }; + Ok(()) + } +} + + diff --git a/layout/src/collection/stack.rs b/layout/src/collection/stack.rs new file mode 100644 index 00000000..a5594a0e --- /dev/null +++ b/layout/src/collection/stack.rs @@ -0,0 +1,65 @@ +use crate::*; + +pub struct Stack>(pub F, pub Direction, PhantomData); + +impl> Stack { + #[inline] pub fn new (direction: Direction, build: F) -> Self { + Self(build, direction, Default::default()) + } + #[inline] pub fn right (build: F) -> Self { + Self::new(East, build) + } + #[inline] pub fn down (build: F) -> Self { + Self::new(South, build) + } + #[inline] pub fn up (build: F) -> Self { + Self::new(North, build) + } +} + +#[macro_export] macro_rules! col { + ([$($expr:expr),* $(,)?]) => { + Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + (![$($expr:expr),* $(,)?]) => { + Stack::down(|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + ($expr:expr) => { + Stack::down($expr) + }; + ($pat:pat in $collection:expr => $item:expr) => { + Stack::down(move|add|{ for $pat in $collection { add(&$item)?; } Ok(()) }) + }; +} + +#[macro_export] macro_rules! col_up { + ([$($expr:expr),* $(,)?]) => { + Stack::up(move|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + (![$($expr:expr),* $(,)?]) => { + Stack::up(|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + ($expr:expr) => { + Stack::up(expr) + }; + ($pat:pat in $collection:expr => $item:expr) => { + Stack::up(move |add|{ for $pat in $collection { add(&$item)?; } Ok(()) }) + }; +} + +#[macro_export] macro_rules! row { + ([$($expr:expr),* $(,)?]) => { + Stack::right(move|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + (![$($expr:expr),* $(,)?]) => { + Stack::right(|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + ($expr:expr) => { + Stack::right($expr) + }; + ($pat:pat in $collection:expr => $item:expr) => { + Stack::right(move|add|{ for $pat in $collection { add(&$item)?; } Ok(()) }) + }; +} + + diff --git a/layout/src/direction.rs b/layout/src/direction.rs new file mode 100644 index 00000000..4c379515 --- /dev/null +++ b/layout/src/direction.rs @@ -0,0 +1,30 @@ +use crate::*; + +/// A cardinal direction. +#[derive(Copy, Clone, PartialEq)] +pub enum Direction { North, South, West, East, } +pub use self::Direction::*; + +impl Direction { + #[inline] + pub fn split_fixed (self, area: impl Area, a: N) -> ([N;4],[N;4]) { + match self { + North => ( + [area.x(), (area.y()+area.h()).minus(a), area.w(), a], + [area.x(), area.y(), area.w(), area.h().minus(a)], + ), + South => ( + [area.x(), area.y(), area.w(), a], + [area.x(), area.y() + a, area.w(), area.h().minus(a)], + ), + East => ( + [area.x(), area.y(), a, area.h()], + [area.x() + a, area.y(), area.w().minus(a), area.h()], + ), + West => ( + [area.x() + area.w() - a, area.y(), a, area.h()], + [area.x(), area.y(), area.w() - a, area.h()], + ), + } + } +} diff --git a/layout/src/layers.rs b/layout/src/layers.rs new file mode 100644 index 00000000..c7b7e813 --- /dev/null +++ b/layout/src/layers.rs @@ -0,0 +1 @@ +use crate::*; diff --git a/layout/src/lib.rs b/layout/src/lib.rs new file mode 100644 index 00000000..2d34185a --- /dev/null +++ b/layout/src/lib.rs @@ -0,0 +1,18 @@ +pub use ::tek_engine; +pub(crate) use ::tek_engine::*; + +pub(crate) use std::ops::{Add, Sub, Mul, Div}; +pub(crate) use std::fmt::{Display, Debug}; +pub(crate) use std::marker::PhantomData; +pub(crate) use std::sync::atomic::Ordering::*; + +mod collection; pub use self::collection::*; +mod direction; pub use self::direction::*; +mod layers; pub use self::layers::*; +mod logic; pub use self::logic::*; +mod space; pub use self::space::*; +mod transform; pub use self::transform::*; + +#[cfg(test)] #[test] fn test_layout () -> Usually<()> { + Ok(()) +} diff --git a/layout/src/logic.rs b/layout/src/logic.rs new file mode 100644 index 00000000..235d17e6 --- /dev/null +++ b/layout/src/logic.rs @@ -0,0 +1,43 @@ +use crate::*; + +/// Conditional rendering, in unary and binary forms. +pub struct Cond; + +impl Cond { + /// Render `item` when `cond` is true. + pub fn when > (cond: bool, item: A) -> When { + When(cond, item, Default::default()) + } + /// Render `item` if `cond` is true, otherwise render `other`. + pub fn either , B: Render> (cond: bool, item: A, other: B) -> Either { + Either(cond, item, other, Default::default()) + } +} + +/// Renders `self.1` when `self.0` is true. +pub struct When>(bool, A, PhantomData); + +impl> Render for When { + fn min_size (&self, to: E::Size) -> Perhaps { + let Self(cond, item, ..) = self; + if *cond { item.min_size(to) } else { Ok(Some([0.into(), 0.into()].into())) } + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + let Self(cond, item, ..) = self; + if *cond { item.render(to) } else { Ok(()) } + } +} + +/// Renders `self.1` when `self.0` is true, otherwise renders `self.2` +pub struct Either, B: Render>(bool, A, B, PhantomData); + +impl, B: Render> Render for Either { + fn min_size (&self, to: E::Size) -> Perhaps { + let Self(cond, item, other, ..) = self; + if *cond { item.min_size(to) } else { other.min_size(to) } + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + let Self(cond, item, other, ..) = self; + if *cond { item.render(to) } else { other.render(to) } + } +} diff --git a/layout/src/space.rs b/layout/src/space.rs new file mode 100644 index 00000000..7807d5e9 --- /dev/null +++ b/layout/src/space.rs @@ -0,0 +1,317 @@ +use crate::*; +use std::sync::{Arc, atomic::AtomicUsize}; + +impl Direction { + pub fn is_north (&self) -> bool { matches!(self, Self::North) } + pub fn is_south (&self) -> bool { matches!(self, Self::South) } + pub fn is_east (&self) -> bool { matches!(self, Self::West) } + pub fn is_west (&self) -> bool { matches!(self, Self::East) } + /// Return next direction clockwise + pub fn cw (&self) -> Self { + match self { + Self::North => Self::East, + Self::South => Self::West, + Self::West => Self::North, + Self::East => Self::South, + } + } + /// Return next direction counterclockwise + pub fn ccw (&self) -> Self { + match self { + Self::North => Self::West, + Self::South => Self::East, + Self::West => Self::South, + Self::East => Self::North, + } + } +} + +trait AreaMod: Area { + fn x2 (&self) -> N { + self.x() + self.w() + } + fn y2 (&self) -> N { + self.y() + self.h() + } + #[inline] fn xywh (&self) -> [N;4] { + [self.x(), self.y(), self.w(), self.h()] + } + #[inline] fn lrtb (&self) -> [N;4] { + [self.x(), self.x2(), self.y(), self.y2()] + } + #[inline] fn push_x (&self, x: N) -> [N;4] { + [self.x() + x, self.y(), self.w(), self.h()] + } + #[inline] fn push_y (&self, y: N) -> [N;4] { + [self.x(), self.y() + y, self.w(), self.h()] + } + #[inline] fn shrink_x (&self, x: N) -> [N;4] { + [self.x(), self.y(), self.w().minus(x), self.h()] + } + #[inline] fn shrink_y (&self, y: N) -> [N;4] { + [self.x(), self.y(), self.w(), self.h().minus(y)] + } + #[inline] fn set_w (&self, w: N) -> [N;4] { + [self.x(), self.y(), w, self.h()] + } + #[inline] fn set_h (&self, h: N) -> [N;4] { + [self.x(), self.y(), self.w(), h] + } +} + +impl> AreaMod for A {} + +pub trait HasSize { + fn size (&self) -> &Measure; +} + +#[macro_export] macro_rules! has_size { + (<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? { + fn size (&$self) -> &Measure<$E> { $cb } + } + } +} + +/// A widget that tracks its render width and height +#[derive(Default)] +pub struct Measure { + _engine: PhantomData, + pub x: Arc, + pub y: Arc, +} + +impl> Shrink { + fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + } + } +} + +impl> Grow { + fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + } + } +} + +impl> Render for Shrink { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(self.inner().min_size(to)?.map(|to|match *self { + Self::X(w, _) => [ + if to.w() > w { to.w() - w } else { 0.into() }, + to.h() + ], + Self::Y(h, _) => [ + to.w(), + if to.h() > h { to.h() - h } else { 0.into() } + ], + Self::XY(w, h, _) => [ + if to.w() > w { to.w() - w } else { 0.into() }, + if to.h() > h { to.h() - h } else { 0.into() } + ], + }.into())) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(self.min_size(to.area().wh().into())? + .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) + .transpose()?.unwrap_or(())) + } +} + +impl> Render for Grow { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(self.inner().min_size(to)?.map(|to|match *self { + Self::X(w, _) => [to.w() + w, to.h()], + Self::Y(h, _) => [to.w(), to.h() + h], + Self::XY(w, h, _) => [to.w() + w, to.h() + h], + }.into())) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(self.min_size(to.area().wh().into())? + .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) + .transpose()?.unwrap_or(())) + } +} + +impl> Fill { + fn inner (&self) -> &W { + match self { + Self::X(inner) => &inner, + Self::Y(inner) => &inner, + Self::XY(inner) => &inner, + _ => unreachable!(), + } + } + pub fn w (fill: W) -> Self { + Self::X(fill) + } + pub fn h (fill: W) -> Self { + Self::Y(fill) + } + pub fn wh (fill: W) -> Self { + Self::XY(fill) + } +} + +impl> Render for Fill { + fn min_size (&self, to: E::Size) -> Perhaps { + let area = self.inner().min_size(to.into())?; + if let Some(area) = area { + Ok(Some(match self { + Self::X(_) => [to.w().into(), area.h()], + Self::Y(_) => [area.w(), to.h().into()], + Self::XY(_) => [to.w().into(), to.h().into()], + _ => unreachable!(), + }.into())) + } else { + Ok(None) + } + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.inner().render(to) + } +} + +impl> Fixed { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + _ => unreachable!(), + } + } + pub fn w (x: E::Unit, w: T) -> Self { + Self::X(x, w) + } + pub fn h (y: E::Unit, w: T) -> Self { + Self::Y(y, w) + } + pub fn wh (x: E::Unit, y: E::Unit, w: T) -> Self { + Self::XY(x, y, w) + } +} + +impl> Render for Fixed { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(match self { + Self::X(w, _) => + if to.w() >= *w { Some([*w, to.h()].into()) } else { None }, + Self::Y(h, _) => + if to.h() >= *h { Some([to.w(), *h].into()) } else { None }, + Self::XY(w, h, _) + => if to.w() >= *w && to.h() >= *h { Some([*w, *h].into()) } else { None }, + _ => unreachable!(), + }) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + // 🡘 🡙 ←🡙→ + if let Some(size) = self.min_size(to.area().wh().into())? { + to.render_in(to.area().clip(size).into(), self.inner()) + } else { + Ok(()) + } + } +} + +impl> Min { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + } + } +} + +impl> Render for Min { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(self.inner().min_size(to)?.map(|to|match *self { + Self::X(w, _) => [to.w().max(w), to.h()], + Self::Y(h, _) => [to.w(), to.h().max(h)], + Self::XY(w, h, _) => [to.w().max(w), to.h().max(h)], + }.into())) + } + // TODO: 🡘 🡙 ←🡙→ + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(self.min_size(to.area().wh().into())? + .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) + .transpose()?.unwrap_or(())) + } +} + +impl> Max { + fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + } + } +} + +impl> Render for Max { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(self.inner().min_size(to)?.map(|to|match *self { + Self::X(w, _) => [to.w().min(w), to.h()], + Self::Y(h, _) => [to.w(), to.h().min(h)], + Self::XY(w, h, _) => [to.w().min(w), to.h().min(h)], + }.into())) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(self.min_size(to.area().wh().into())? + .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) + .transpose()?.unwrap_or(())) + } +} + +impl Clone for Measure { + fn clone (&self) -> Self { + Self { + _engine: Default::default(), + x: self.x.clone(), + y: self.y.clone(), + } + } +} + +impl std::fmt::Debug for Measure { + 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 Measure { + pub fn w (&self) -> usize { self.x.load(Relaxed) } + pub fn h (&self) -> usize { self.y.load(Relaxed) } + pub fn wh (&self) -> [usize;2] { [self.w(), self.h()] } + pub fn set_w (&self, w: impl Into) { self.x.store(w.into(), Relaxed) } + pub fn set_h (&self, h: impl Into) { self.y.store(h.into(), Relaxed) } + pub fn set_wh (&self, w: impl Into, h: impl Into) { self.set_w(w); self.set_h(h); } + pub fn format (&self) -> String { format!("{}x{}", self.w(), self.h()) } + pub fn new () -> Self { + Self { + _engine: PhantomData::default(), + x: Arc::new(0.into()), + y: Arc::new(0.into()), + } + } +} + +//pub trait LayoutDebug { + //fn debug > (other: W) -> DebugOverlay { + //DebugOverlay(Default::default(), other) + //} +//} + +//impl LayoutDebug for E {} diff --git a/layout/src/transform.rs b/layout/src/transform.rs new file mode 100644 index 00000000..135699d0 --- /dev/null +++ b/layout/src/transform.rs @@ -0,0 +1,372 @@ +use crate::*; + +macro_rules! by_axis { + (!$Enum:ident) => { + impl> $Enum { + pub fn x (item: T) -> Self { + Self::X(item) + } + pub fn y (item: T) -> Self { + Self::Y(item) + } + pub fn xy (item: T) -> Self { + Self::XY(item) + } + } + }; + (+$Enum:ident) => { + impl> $Enum { + pub fn x (x: E::Unit, item: T) -> Self { + Self::X(x, item) + } + pub fn y (y: E::Unit, item: T) -> Self { + Self::Y(y, item) + } + pub fn xy (x: E::Unit, y: E::Unit, item: T) -> Self { + Self::XY(x, y, item) + } + } + }; +} + +/// Shrink drawing area +pub enum Shrink> { + /// Decrease width + X(E::Unit, T), + /// Decrease height + Y(E::Unit, T), + /// Decrease width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Shrink); + +/// Expand drawing area +pub enum Grow> { + /// Increase width + X(E::Unit, T), + /// Increase height + Y(E::Unit, T), + /// Increase width and height + XY(E::Unit, E::Unit, T) +} + +by_axis!(+Grow); + +pub enum Fill> { + _Unused(PhantomData), + /// Maximize width + X(W), + /// Maximize height + Y(W), + /// Maximize width and height + XY(W), +} + +by_axis!(!Fill); + +/// Enforce fixed size of drawing area +pub enum Fixed> { + /// Set width + X(E::Unit, T), + /// Set height + Y(E::Unit, T), + /// Set width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Fixed); + +/// Enforce minimum size of drawing area +pub enum Min> { + /// Enforce minimum width + X(E::Unit, T), + /// Enforce minimum height + Y(E::Unit, T), + /// Enforce minimum width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Min); + +/// Enforce maximum size of drawing area +pub enum Max> { + /// Enforce maximum width + X(E::Unit, T), + /// Enforce maximum height + Y(E::Unit, T), + /// Enforce maximum width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Max); + +/// Increment origin point of drawing area +pub enum Push> { + /// Move origin to the right + X(E::Unit, T), + /// Move origin downwards + Y(E::Unit, T), + /// Move origin to the right and downwards + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Push); + +/// Decrement origin point of drawing area +pub enum Pull> { + /// Move origin to the right + X(E::Unit, T), + /// Move origin downwards + Y(E::Unit, T), + /// Move origin to the right and downwards + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Pull); + +/// Shrink from each side +pub enum Padding { + /// Decrease width + X(E::Unit, T), + /// Decrease height + Y(E::Unit, T), + /// Decrease width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Padding); + +/// Grow on each side +pub enum Margin> { + /// Increase width + X(E::Unit, T), + /// Increase height + Y(E::Unit, T), + /// Increase width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Margin); + +/// A scrollable area. +pub struct Scroll(pub F, pub Direction, pub u64, PhantomData) +where + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()>; + +/// Override X and Y coordinates, aligning to corner, side, or center of area +pub enum Align> { + _Unused(PhantomData), + /// Draw at center of container + Center(T), + /// Draw at center of X axis + X(T), + /// Draw at center of Y axis + Y(T), + /// Draw at upper left corner of contaier + NW(T), + /// Draw at center of upper edge of container + N(T), + /// Draw at right left corner of contaier + NE(T), + /// Draw at center of left edge of container + W(T), + /// Draw at center of right edge of container + E(T), + /// Draw at lower left corner of container + SW(T), + /// Draw at center of lower edge of container + S(T), + /// Draw at lower right edge of container + SE(T) +} + +impl> Push { + pub fn inner (&self) -> &T { + use Push::*; + match self { X(_, i) => i, Y(_, i) => i, XY(_, _, i) => i, } + } + pub fn dx (&self) -> E::Unit { + use Push::*; + match self { X(x, _) => *x, Y(_, _) => E::Unit::default(), XY(x, _, _) => *x, } + } + pub fn dy (&self) -> E::Unit { + use Push::*; + match self { X(_, _) => E::Unit::default(), Y(y, _) => *y, XY(_, y, _) => *y, } + } +} + +impl> Render for Push { + fn min_size (&self, to: E::Size) -> Perhaps { + self.inner().min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + let area = to.area(); + Ok(self.min_size(area.wh().into())? + .map(|size|to.render_in(match *self { + Self::X(x, _) => [area.x() + x, area.y(), size.w(), size.h()], + Self::Y(y, _) => [area.x(), area.y() + y, size.w(), size.h()], + Self::XY(x, y, _) => [area.x() + x, area.y() + y, size.w(), size.h()], + }.into(), self.inner())).transpose()?.unwrap_or(())) + } +} + +impl> Pull { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + _ => unreachable!(), + } + } + pub fn dx (&self) -> E::Unit { + match self { + Self::X(x, _) => *x, + Self::Y(_, _) => E::Unit::default(), + Self::XY(x, _, _) => *x, + _ => unreachable!(), + } + } + pub fn dy (&self) -> E::Unit { + match self { + Self::X(_, _) => E::Unit::default(), + Self::Y(y, _) => *y, + Self::XY(_, y, _) => *y, + _ => unreachable!(), + } + } +} + +impl> Render for Pull { + fn min_size (&self, to: E::Size) -> Perhaps { + self.inner().min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + let area = to.area(); + Ok(self.min_size(area.wh().into())? + .map(|size|to.render_in(match *self { + Self::X(x, _) => [area.x().minus(x), area.y(), size.w(), size.h()], + Self::Y(y, _) => [area.x(), area.y().minus(y), size.w(), size.h()], + Self::XY(x, y, _) => [area.x().minus(x), area.y().minus(y), size.w(), size.h()], + _ => unreachable!(), + }.into(), self.inner())).transpose()?.unwrap_or(())) + } +} + +impl> Padding { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + } + } +} + +impl> Render for Padding { + fn render (&self, to: &mut E::Output) -> Usually<()> { + match self { + Self::X(x, inner) => Push::x(*x, Shrink::x(*x, inner)), + Self::Y(y, inner) => Push::y(*y, Shrink::y(*y, inner)), + Self::XY(x, y, inner) => Push::xy(*x, *y, Shrink::xy(*x, *y, inner)), + }.render(to) + } +} + + +impl> Margin { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + } + } +} + +impl> Render for Margin { + fn min_size (&self, to: E::Size) -> Perhaps { + match *self { + Self::X(x, ref inner) => Grow::x(x + x, inner), + Self::Y(y, ref inner) => Grow::y(y + y, inner), + Self::XY(x, y, ref inner) => Grow::xy(x + x, y + y, inner), + }.min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + match *self { + Self::X(x, ref inner) => Push::x(x, inner), + Self::Y(y, ref inner) => Push::y(y, inner), + Self::XY(x, y, ref inner) => Push::xy(x, y, inner), + }.render(to) + } +} + +impl> Align { + pub fn c (w: T) -> Self { Self::Center(w) } + pub fn x (w: T) -> Self { Self::X(w) } + pub fn y (w: T) -> Self { Self::Y(w) } + pub fn n (w: T) -> Self { Self::N(w) } + pub fn s (w: T) -> Self { Self::S(w) } + pub fn e (w: T) -> Self { Self::E(w) } + pub fn w (w: T) -> Self { Self::W(w) } + pub fn nw (w: T) -> Self { Self::NW(w) } + pub fn sw (w: T) -> Self { Self::SW(w) } + pub fn ne (w: T) -> Self { Self::NE(w) } + pub fn se (w: T) -> Self { Self::SE(w) } + pub fn inner (&self) -> &T { + match self { + Self::Center(inner) => inner, + Self::X(inner) => inner, + Self::Y(inner) => inner, + Self::NW(inner) => inner, + Self::N(inner) => inner, + Self::NE(inner) => inner, + Self::W(inner) => inner, + Self::E(inner) => inner, + Self::SW(inner) => inner, + Self::S(inner) => inner, + Self::SE(inner) => inner, + _ => unreachable!(), + } + } +} + +fn align, N: Coordinate, R: Area + From<[N;4]>> (align: &Align, outer: R, inner: R) -> Option { + if outer.w() < inner.w() || outer.h() < inner.h() { + None + } else { + let [ox, oy, ow, oh] = outer.xywh(); + let [ix, iy, iw, ih] = inner.xywh(); + Some(match align { + Align::Center(_) => [ox + (ow - iw) / 2.into(), oy + (oh - ih) / 2.into(), iw, ih,].into(), + Align::X(_) => [ox + (ow - iw) / 2.into(), iy, iw, ih,].into(), + Align::Y(_) => [ix, oy + (oh - ih) / 2.into(), iw, ih,].into(), + Align::NW(_) => [ox, oy, iw, ih,].into(), + Align::N(_) => [ox + (ow - iw) / 2.into(), oy, iw, ih,].into(), + Align::NE(_) => [ox + ow - iw, oy, iw, ih,].into(), + Align::W(_) => [ox, oy + (oh - ih) / 2.into(), iw, ih,].into(), + Align::E(_) => [ox + ow - iw, oy + (oh - ih) / 2.into(), iw, ih,].into(), + Align::SW(_) => [ox, oy + oh - ih, iw, ih,].into(), + Align::S(_) => [ox + (ow - iw) / 2.into(), oy + oh - ih, iw, ih,].into(), + Align::SE(_) => [ox + ow - iw, oy + oh - ih, iw, ih,].into(), + _ => unreachable!() + }) + } +} + +impl> Render for Align { + fn min_size (&self, outer_area: E::Size) -> Perhaps { + self.inner().min_size(outer_area) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + let outer_area = to.area(); + Ok(if let Some(inner_size) = self.min_size(outer_area.wh().into())? { + let inner_area = outer_area.clip(inner_size); + if let Some(aligned) = align(&self, outer_area.into(), inner_area.into()) { + to.render_in(aligned, self.inner())? + } + }) + } +} diff --git a/layout/src/tui.rs b/layout/src/tui.rs new file mode 100644 index 00000000..7064b922 --- /dev/null +++ b/layout/src/tui.rs @@ -0,0 +1,42 @@ +use crate::*; +use ratatui::prelude::{Style, Color}; + +impl Render for Measure { + fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { + Ok(Some([0u16.into(), 0u16.into()].into())) + } + fn render (&self, to: &mut TuiOutput) -> Usually<()> { + self.set_w(to.area().w()); + self.set_h(to.area().h()); + Ok(()) + } +} + +impl Measure { + pub fn debug (&self) -> ShowMeasure { + ShowMeasure(&self) + } +} + +render!(|self: ShowMeasure<'a>|render(|to: &mut TuiOutput|Ok({ + let w = self.0.w(); + let h = self.0.h(); + to.blit(&format!(" {w} x {h} "), to.area.x(), to.area.y(), Some( + Style::default().bold().italic().bg(Color::Rgb(255, 0, 255)).fg(Color::Rgb(0,0,0)) + )) +}))); + +pub struct ShowMeasure<'a>(&'a Measure); + +pub struct DebugOverlay>(PhantomData, pub W); + +impl> Render for DebugOverlay { + fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { + self.1.min_size(to) + } + fn render (&self, to: &mut TuiOutput) -> Usually<()> { + let [x, y, w, h] = to.area(); + self.1.render(to)?; + Ok(to.blit(&format!("{w}x{h}+{x}+{y}"), x, y, Some(Style::default().green()))) + } +} diff --git a/src/lib.rs b/src/lib.rs index 6ea60adf..74ca3593 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,13 +47,11 @@ pub mod plugin; pub use self::plugin::*; pub mod pool; pub use self::pool::*; pub mod sampler; pub use self::sampler::*; pub mod sequencer; pub use self::sequencer::*; -pub mod space; pub use self::space::*; pub mod status; pub use self::status::*; pub mod style; pub use self::theme::*; pub mod theme; pub use self::theme::*; pub mod time; pub use self::time::*; pub mod transport; pub use self::transport::*; -pub mod tui; pub use self::tui::*; pub use ::atomic_float; pub(crate) use atomic_float::*; diff --git a/src/space/cond.rs b/src/space/cond.rs index 33dc6e52..a3682f1a 100644 --- a/src/space/cond.rs +++ b/src/space/cond.rs @@ -1,5 +1,4 @@ use crate::*; -use super::*; impl> Render for When { fn min_size (&self, to: E::Size) -> Perhaps { diff --git a/src/tui.rs b/src/tui.rs deleted file mode 100644 index 3ca47c29..00000000 --- a/src/tui.rs +++ /dev/null @@ -1,3 +0,0 @@ -use crate::*; - -////////////////////////////////////////////////////////