From dc6265f837453d78872ac6ecc976bc2442b41c1d Mon Sep 17 00:00:00 2001 From: Joscha Date: Thu, 7 Mar 2024 19:00:20 +0100 Subject: [PATCH] Add Text widget --- Cargo.lock | 223 +++++++++++++++++++++++++++- showbits-common/Cargo.toml | 1 + showbits-common/src/lib.rs | 1 + showbits-common/src/view.rs | 12 ++ showbits-common/src/widget.rs | 17 ++- showbits-common/src/widgets.rs | 3 + showbits-common/src/widgets/text.rs | 120 +++++++++++++++ 7 files changed, 373 insertions(+), 4 deletions(-) create mode 100644 showbits-common/src/widgets.rs create mode 100644 showbits-common/src/widgets/text.rs diff --git a/Cargo.lock b/Cargo.lock index 75c8e49..683e594 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,6 +185,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + [[package]] name = "bytemuck" version = "1.14.3" @@ -267,6 +273,29 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "cosmic-text" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c578f2b9abb4d5f3fbb12aba4008084d435dc6a8425c195cfe0b3594bfea0c25" +dependencies = [ + "bitflags 2.4.2", + "fontdb", + "libm", + "log", + "rangemap", + "rustc-hash", + "rustybuzz", + "self_cell", + "swash", + "sys-locale", + "ttf-parser", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + [[package]] name = "crc32fast" version = "1.4.0" @@ -396,6 +425,35 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "font-types" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd7f3ea17572640b606b35df42cfb6ecdf003704b062580e59918692190b73d" + +[[package]] +name = "fontconfig-parser" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a595cb550439a117696039dfc69830492058211b771a2a165379f2a1a53d84d" +dependencies = [ + "roxmltree", +] + +[[package]] +name = "fontdb" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0299020c3ef3f60f526a4f64ab4a3d4ce116b1acbf24cdd22da0068e5d81dc3" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "slotmap", + "tinyvec", + "ttf-parser", +] + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -656,6 +714,12 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "lock_api" version = "0.4.11" @@ -684,6 +748,15 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + [[package]] name = "mime" version = "0.3.17" @@ -877,7 +950,7 @@ version = "0.17.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" dependencies = [ - "bitflags", + "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", @@ -926,6 +999,12 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "rangemap" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60fcc7d6849342eff22c4350c8b9a989ee8ceabc4b481253e8946b9fe83d684" + [[package]] name = "rayon" version = "1.9.0" @@ -946,27 +1025,65 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "read-fonts" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea23eedb4d938031b6d4343222444608727a6aa68ec355e13588d9947ffe92" +dependencies = [ + "font-types", +] + [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ - "bitflags", + "bitflags 1.3.2", ] +[[package]] +name = "roxmltree" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd14fd5e3b777a7422cca79358c57a8f6e3a703d9ac187448d0daf220c2407f" + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustversion" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" +[[package]] +name = "rustybuzz" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ae5692c5beaad6a9e22830deeed7874eae8a4e3ba4076fb48e12c56856222c" +dependencies = [ + "bitflags 2.4.2", + "bytemuck", + "libm", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.17" @@ -979,6 +1096,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "self_cell" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bf37232d3bb9a2c4e641ca2a11d83b5062066f88df7fed36c28772046d65ba" + [[package]] name = "serde" version = "1.0.197" @@ -1037,6 +1160,7 @@ name = "showbits-common" version = "0.0.0" dependencies = [ "anyhow", + "cosmic-text", "image", "palette", "taffy", @@ -1126,6 +1250,17 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +[[package]] +name = "swash" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06ff4664af8923625604261c645f5c4cc610cc83c84bec74b50d76237089de7" +dependencies = [ + "read-fonts", + "yazi", + "zeno", +] + [[package]] name = "syn" version = "2.0.52" @@ -1143,6 +1278,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sys-locale" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0" +dependencies = [ + "libc", +] + [[package]] name = "taffy" version = "0.4.0" @@ -1167,6 +1311,21 @@ dependencies = [ "weezl", ] +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.36.0" @@ -1259,12 +1418,60 @@ dependencies = [ "once_cell", ] +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-properties" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" + +[[package]] +name = "unicode-script" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8d71f5726e5f285a935e9fe8edfd53f0491eb6e9a5774097fdabee7cd8c9cd" + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + [[package]] name = "utf8parse" version = "0.2.1" @@ -1421,6 +1628,18 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +[[package]] +name = "yazi" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c94451ac9513335b5e23d7a8a2b61a7102398b8cca5160829d313e84c9d98be1" + +[[package]] +name = "zeno" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd15f8e0dbb966fd9245e7498c7e9e5055d9e5c8b676b95bd67091cd11a1e697" + [[package]] name = "zune-inflate" version = "0.2.54" diff --git a/showbits-common/Cargo.toml b/showbits-common/Cargo.toml index d51cb43..76d3752 100644 --- a/showbits-common/Cargo.toml +++ b/showbits-common/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true [dependencies] anyhow.workspace = true +cosmic-text = "0.11.2" image.workspace = true palette.workspace = true diff --git a/showbits-common/src/lib.rs b/showbits-common/src/lib.rs index 7cca6f2..c3084df 100644 --- a/showbits-common/src/lib.rs +++ b/showbits-common/src/lib.rs @@ -6,3 +6,4 @@ mod render; mod vec2; mod view; mod widget; +pub mod widgets; diff --git a/showbits-common/src/view.rs b/showbits-common/src/view.rs index 38dd52c..8f9ad0d 100644 --- a/showbits-common/src/view.rs +++ b/showbits-common/src/view.rs @@ -50,4 +50,16 @@ impl<'a> View<'a> { pixel.0 = [color.red, color.green, color.blue]; } } + + // More complicated drawing primitives + + pub fn rect(&mut self, area: Rect, color: Srgb) { + let nw = area.corner_nw(); + let se = area.corner_se(); + for y in nw.y..=se.y { + for x in nw.x..=se.x { + self.set(Vec2::new(x, y), color); + } + } + } } diff --git a/showbits-common/src/widget.rs b/showbits-common/src/widget.rs index c39e71c..4b072bd 100644 --- a/showbits-common/src/widget.rs +++ b/showbits-common/src/widget.rs @@ -3,6 +3,8 @@ use taffy::{AvailableSpace, Size}; use crate::{Node, View}; pub trait Widget { + /// Used for the measure function in + /// [`taffy::TaffyTree::compute_layout_with_measure`]. #[allow(unused_variables)] fn size( &mut self, @@ -13,8 +15,19 @@ pub trait Widget { Size::ZERO } - fn draw_below(&mut self, ctx: &mut C, view: &mut View<'_>) -> anyhow::Result<()>; - fn draw_above(&mut self, ctx: &mut C, view: &mut View<'_>) -> anyhow::Result<()>; + /// Called before all children are drawn. + /// + /// Prefer this over [`Self::draw_above`] when implementing a leaf widget. + #[allow(unused_variables)] + fn draw_below(&mut self, ctx: &mut C, view: &mut View<'_>) -> anyhow::Result<()> { + Ok(()) + } + + /// Called after all children are drawn. + #[allow(unused_variables)] + fn draw_above(&mut self, ctx: &mut C, view: &mut View<'_>) -> anyhow::Result<()> { + Ok(()) + } } pub type BoxedWidget = Box>; diff --git a/showbits-common/src/widgets.rs b/showbits-common/src/widgets.rs new file mode 100644 index 0000000..ced299a --- /dev/null +++ b/showbits-common/src/widgets.rs @@ -0,0 +1,3 @@ +pub use text::*; + +mod text; diff --git a/showbits-common/src/widgets/text.rs b/showbits-common/src/widgets/text.rs new file mode 100644 index 0000000..33d0629 --- /dev/null +++ b/showbits-common/src/widgets/text.rs @@ -0,0 +1,120 @@ +use cosmic_text::{Attrs, Buffer, Color, FontSystem, Metrics, Shaping, SwashCache}; +use palette::Srgb; +use taffy::prelude::{AvailableSpace, Size}; + +use crate::{Rect, Vec2, View, Widget}; + +// https://github.com/DioxusLabs/taffy/blob/main/examples/cosmic_text.rs + +fn srgb_to_color(color: Srgb) -> Color { + let color = color.into_format::(); + let (r, g, b) = color.into_components(); + Color::rgb(r, g, b) +} + +fn color_to_srgb(color: Color) -> Srgb { + let (r, g, b, _) = color.as_rgba_tuple(); + Srgb::new(r, g, b).into_format() +} + +pub trait HasFontStuff { + fn font_system_and_swash_cache_mut(&mut self) -> (&mut FontSystem, &mut SwashCache); +} + +pub struct Text { + buffer: Buffer, + color: Srgb, +} + +impl Text { + /// Default text color. + const COLOR: Srgb = Srgb::new(0.0, 0.0, 0.0); + + // Default shaping strategy. + const SHAPING: Shaping = Shaping::Advanced; + + pub fn simple( + font_system: &mut FontSystem, + metrics: Metrics, + attrs: Attrs<'_>, + text: &str, + ) -> Self { + let mut buffer = Buffer::new_empty(metrics); + buffer.set_size(font_system, f32::INFINITY, f32::INFINITY); + buffer.set_text(font_system, text, attrs, Self::SHAPING); + + Self { + buffer, + color: Self::COLOR, + } + } + + pub fn rich<'r, 's, I>( + font_system: &mut FontSystem, + metrics: Metrics, + default_attrs: Attrs<'_>, + spans: I, + ) -> Self + where + I: IntoIterator)>, + { + let mut buffer = Buffer::new_empty(metrics); + buffer.set_size(font_system, f32::INFINITY, f32::INFINITY); + buffer.set_rich_text(font_system, spans, default_attrs, Self::SHAPING); + + Self { + buffer, + color: Self::COLOR, + } + } + + pub fn color(mut self, color: Srgb) -> Self { + self.color = color; + self + } +} + +impl Widget for Text { + fn size( + &mut self, + ctx: &mut C, + known: Size>, + available: Size, + ) -> Size { + let width = known.width.unwrap_or(match available.width { + AvailableSpace::Definite(width) => width, + AvailableSpace::MinContent => 0.0, + AvailableSpace::MaxContent => f32::INFINITY, + }); + + let (fs, _) = ctx.font_system_and_swash_cache_mut(); + self.buffer.set_size(fs, width, f32::INFINITY); + self.buffer.shape_until_scroll(fs, false); + + let runs = self.buffer.layout_runs(); + let height = runs.len() as f32 * self.buffer.metrics().line_height; + let width = runs + .map(|run| run.line_w) + .max_by(|a, b| a.partial_cmp(b).unwrap()) + .unwrap_or(0.0); + + Size { width, height } + } + + fn draw_below(&mut self, ctx: &mut C, view: &mut View<'_>) -> anyhow::Result<()> { + let size = view.size(); + + let (fs, sc) = ctx.font_system_and_swash_cache_mut(); + self.buffer.set_size(fs, size.x as f32, size.y as f32); + self.buffer.shape_until_scroll(fs, true); + + let color = srgb_to_color(self.color); + self.buffer.draw(fs, sc, color, |x, y, w, h, color| { + let color = color_to_srgb(color); + let area = Rect::from_nw(Vec2::new(x, y), Vec2::from_u32(w, h)); + view.rect(area, color); + }); + + todo!() + } +}