From 5ce6fca2752919274c26dcca841a60c048f7fd12 Mon Sep 17 00:00:00 2001 From: Shadow Cat Date: Sat, 23 Aug 2025 21:15:39 -0400 Subject: [PATCH] initial text impl --- Cargo.lock | 208 ++++++++++++++++++++- Cargo.toml | 1 + src/core/frame.rs | 2 +- src/core/image.rs | 3 +- src/core/mod.rs | 4 +- src/core/rect.rs | 2 +- src/core/sense.rs | 2 +- src/core/span.rs | 2 +- src/core/stack.rs | 2 +- src/core/text.rs | 17 ++ src/core/trait_fns.rs | 4 +- src/layout/id.rs | 6 +- src/layout/mod.rs | 10 +- src/layout/num.rs | 21 +++ src/{core/num.rs => layout/orientation.rs} | 33 ++-- src/layout/painter.rs | 39 +++- src/layout/{region.rs => pos.rs} | 67 +++---- src/layout/sense.rs | 11 +- src/layout/text.rs | 57 ++++++ src/layout/texture.rs | 33 +++- src/layout/ui.rs | 30 ++- src/layout/vec2.rs | 26 ++- src/layout/widget.rs | 5 +- src/lib.rs | 11 +- src/render/data.rs | 2 +- src/render/mod.rs | 2 +- src/render/primitive.rs | 6 +- src/render/texture.rs | 5 +- src/testing/app.rs | 2 +- src/testing/input.rs | 2 +- src/testing/mod.rs | 29 +-- src/testing/render/mod.rs | 2 +- src/util/refcount.rs | 1 - 33 files changed, 530 insertions(+), 117 deletions(-) create mode 100644 src/core/text.rs create mode 100644 src/layout/num.rs rename src/{core/num.rs => layout/orientation.rs} (59%) rename src/layout/{region.rs => pos.rs} (86%) create mode 100644 src/layout/text.rs diff --git a/Cargo.lock b/Cargo.lock index c9a68c0..3d1b66e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -430,6 +430,29 @@ dependencies = [ "libc", ] +[[package]] +name = "cosmic-text" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da46a9d5a8905cc538a4a5bceb6a4510de7a51049c5588c0114efce102bcbbe8" +dependencies = [ + "bitflags 2.9.1", + "fontdb", + "log", + "rangemap", + "rustc-hash", + "rustybuzz", + "self_cell", + "smol_str", + "swash", + "sys-locale", + "ttf-parser 0.21.1", + "unicode-bidi", + "unicode-linebreak", + "unicode-script", + "unicode-segmentation", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -594,6 +617,38 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "font-types" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02a596f5713680923a2080d86de50fe472fb290693cf0f701187a1c8b36996b7" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +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 0.20.0", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -1469,7 +1524,7 @@ version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" dependencies = [ - "ttf-parser", + "ttf-parser 0.25.1", ] [[package]] @@ -1714,6 +1769,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d6831663a5098ea164f89cff59c6284e95f4e3c76ce9848d4529f5ccca9bde" +[[package]] +name = "rangemap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7e49bb0bf967717f7bd674458b3d6b0c5f48ec7e3038166026a69fc22223" + [[package]] name = "rav1e" version = "0.7.1" @@ -1790,6 +1851,16 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "read-fonts" +version = "0.29.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04ca636dac446b5664bd16c069c00a9621806895b8bb02c2dc68542b23b8f25d" +dependencies = [ + "bytemuck", + "font-types", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -1820,6 +1891,12 @@ version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce" +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + [[package]] name = "rustc-hash" version = "1.1.0" @@ -1858,6 +1935,23 @@ version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" +[[package]] +name = "rustybuzz" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfb9cf8877777222e4a3bc7eb247e398b56baba500c38c1c46842431adc8b55c" +dependencies = [ + "bitflags 2.9.1", + "bytemuck", + "libm", + "smallvec", + "ttf-parser 0.21.1", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-properties", + "unicode-script", +] + [[package]] name = "same-file" version = "1.0.6" @@ -1892,6 +1986,12 @@ dependencies = [ "tiny-skia", ] +[[package]] +name = "self_cell" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" + [[package]] name = "serde" version = "1.0.219" @@ -1942,6 +2042,16 @@ dependencies = [ "quote", ] +[[package]] +name = "skrifa" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbeb4ca4399663735553a09dd17ce7e49a0a0203f03b706b39628c4d913a8607" +dependencies = [ + "bytemuck", + "read-fonts", +] + [[package]] name = "slab" version = "0.4.11" @@ -2018,6 +2128,17 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +[[package]] +name = "swash" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f745de914febc7c9ab4388dfaf94bbc87e69f57bb41133a9b0c84d4be49856f3" +dependencies = [ + "skrifa", + "yazi", + "zeno", +] + [[package]] name = "syn" version = "2.0.104" @@ -2029,6 +2150,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sys-locale" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eab9a99a024a169fe8a903cf9d4a3b3601109bcc13bd9e3c6fff259138626c4" +dependencies = [ + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -2133,6 +2263,21 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +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 = "toml" version = "0.8.23" @@ -2183,6 +2328,18 @@ version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +[[package]] +name = "ttf-parser" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + [[package]] name = "ttf-parser" version = "0.25.1" @@ -2194,18 +2351,55 @@ name = "ui" version = "0.1.0" dependencies = [ "bytemuck", + "cosmic-text", "image", "pollster", "wgpu", "winit", ] +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-bidi-mirroring" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cb788ffebc92c5948d0e997106233eeb1d8b9512f93f41651f52b6c5f5af86" + +[[package]] +name = "unicode-ccc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df77b101bcc4ea3d78dafc5ad7e4f58ceffe0b2b16bf446aeb50b6cb4157656" + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" + +[[package]] +name = "unicode-script" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" + [[package]] name = "unicode-segmentation" version = "1.12.0" @@ -3112,6 +3306,18 @@ version = "0.8.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fd8403733700263c6eb89f192880191f1b83e332f7a20371ddcf421c4a337c7" +[[package]] +name = "yazi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" + +[[package]] +name = "zeno" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" + [[package]] name = "zerocopy" version = "0.8.26" diff --git a/Cargo.toml b/Cargo.toml index 2101c85..5ed8e2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,5 @@ winit = "0.30.11" wgpu = "26.0.1" bytemuck = "1.23.1" image = "0.25.6" +cosmic-text = "0.14.2" diff --git a/src/core/frame.rs b/src/core/frame.rs index a3f7a94..122b13d 100644 --- a/src/core/frame.rs +++ b/src/core/frame.rs @@ -1,4 +1,4 @@ -use crate::{Painter, UiNum, UiRegion, Widget, WidgetId}; +use crate::prelude::*; pub struct Regioned { pub region: UiRegion, diff --git a/src/core/image.rs b/src/core/image.rs index 0ac9c8f..b752657 100644 --- a/src/core/image.rs +++ b/src/core/image.rs @@ -1,7 +1,6 @@ +use crate::prelude::*; use image::DynamicImage; -use crate::{Color, Painter, TextureHandle, Widget, WidgetFnRet, render::RectPrimitive}; - pub struct Image { handle: Option, } diff --git a/src/core/mod.rs b/src/core/mod.rs index 741f72f..b301b6c 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -1,17 +1,17 @@ mod frame; mod image; -mod num; mod rect; mod sense; mod span; mod stack; +mod text; mod trait_fns; pub use frame::*; pub use image::*; -pub use num::*; pub use rect::*; pub use sense::*; pub use span::*; pub use stack::*; +pub use text::*; pub use trait_fns::*; diff --git a/src/core/rect.rs b/src/core/rect.rs index 0787073..56f3bc0 100644 --- a/src/core/rect.rs +++ b/src/core/rect.rs @@ -1,4 +1,4 @@ -use crate::{Painter, UiColor, UiNum, Widget, render::RectPrimitive}; +use crate::prelude::*; #[derive(Clone, Copy)] pub struct Rect { diff --git a/src/core/sense.rs b/src/core/sense.rs index 7ca8817..5fadd3a 100644 --- a/src/core/sense.rs +++ b/src/core/sense.rs @@ -1,4 +1,4 @@ -use crate::{Sense, SenseCtx, SenseFn, Sensor, Widget, WidgetId, WidgetIdFnRet, WidgetLike}; +use crate::prelude::*; pub trait Sensable { fn on(self, sense: Sense, f: impl SenseFn) -> WidgetIdFnRet!(W, Ctx); diff --git a/src/core/span.rs b/src/core/span.rs index 14398dd..f09af63 100644 --- a/src/core/span.rs +++ b/src/core/span.rs @@ -1,4 +1,4 @@ -use crate::{Dir, Painter, Sign, UIScalar, UiNum, UiRegion, Widget, WidgetId}; +use crate::prelude::*; pub struct Span { pub children: Vec<(WidgetId, SpanLen)>, diff --git a/src/core/stack.rs b/src/core/stack.rs index 1bd9d13..73bbc5b 100644 --- a/src/core/stack.rs +++ b/src/core/stack.rs @@ -1,4 +1,4 @@ -use crate::{Painter, Widget, WidgetId}; +use crate::prelude::*; pub struct Stack { pub children: Vec, diff --git a/src/core/text.rs b/src/core/text.rs new file mode 100644 index 0000000..93b8bb2 --- /dev/null +++ b/src/core/text.rs @@ -0,0 +1,17 @@ +use crate::prelude::*; + +pub struct Text { + content: String, +} + +impl Widget for Text { + fn draw(&self, painter: &mut Painter) { + painter.draw_text(&self.content); + } +} + +pub fn text(text: impl Into) -> Text { + Text { + content: text.into(), + } +} diff --git a/src/core/trait_fns.rs b/src/core/trait_fns.rs index b93d7cb..49be97c 100644 --- a/src/core/trait_fns.rs +++ b/src/core/trait_fns.rs @@ -1,5 +1,5 @@ use super::*; -use crate::{UiRegion, Vec2, WidgetArrLike, WidgetFnRet, WidgetLike}; +use crate::layout::{Dir, UiPos, UiRegion, Vec2, WidgetArrLike, WidgetFnRet, WidgetLike}; pub trait CoreWidget { fn pad(self, padding: impl Into) -> WidgetFnRet!(Regioned, Ctx); @@ -17,7 +17,7 @@ impl, Ctx: 'static, Tag> CoreWidget fn center(self, size: impl Into) -> WidgetFnRet!(Regioned, Ctx) { |ui| Regioned { - region: UiRegion::center().size(size.into()), + region: UiPos::center().expand(size.into()), inner: self.add(ui).erase_type(), } } diff --git a/src/layout/id.rs b/src/layout/id.rs index 6d6ca9f..5d3341d 100644 --- a/src/layout/id.rs +++ b/src/layout/id.rs @@ -1,7 +1,7 @@ use std::{any::TypeId, marker::PhantomData}; use crate::{ - FnTag, Ui, UiMsg, UiMsgSender, Widget, WidgetLike, WidgetTag, + layout::{FnTag, Ui, UiMsg, UiMsgSender, Widget, WidgetLike, WidgetTag}, util::{Id, RefCounter}, }; @@ -83,10 +83,10 @@ pub struct IdTag; // pub trait WidgetIdFn = FnOnce(&mut Ui) -> WidgetId; macro_rules! WidgetIdFnRet { ($W:ty, $Ctx:ty) => { - impl FnOnce(&mut $crate::Ui<$Ctx>) -> $crate::WidgetId<$W> + impl FnOnce(&mut $crate::layout::Ui<$Ctx>) -> $crate::layout::WidgetId<$W> }; ($W:ty, $Ctx:ty, $($use:tt)*) => { - impl FnOnce(&mut $crate::Ui<$Ctx>) -> $crate::WidgetId<$W> + use<$($use)*> + impl FnOnce(&mut $crate::layout::Ui<$Ctx>) -> $crate::layout::WidgetId<$W> + use<$($use)*> }; } pub(crate) use WidgetIdFnRet; diff --git a/src/layout/mod.rs b/src/layout/mod.rs index 116f28b..9d10513 100644 --- a/src/layout/mod.rs +++ b/src/layout/mod.rs @@ -1,8 +1,11 @@ mod color; mod id; +mod num; +mod orientation; mod painter; -mod region; +mod pos; mod sense; +mod text; mod texture; mod ui; mod vec2; @@ -10,9 +13,12 @@ mod widget; pub use color::*; pub use id::*; +pub use num::*; +pub use orientation::*; pub use painter::*; -pub use region::*; +pub use pos::*; pub use sense::*; +pub use text::*; pub use texture::*; pub use ui::*; pub use vec2::*; diff --git a/src/layout/num.rs b/src/layout/num.rs new file mode 100644 index 0000000..d45f701 --- /dev/null +++ b/src/layout/num.rs @@ -0,0 +1,21 @@ +pub trait UiNum { + fn to_f32(self) -> f32; +} + +impl UiNum for f32 { + fn to_f32(self) -> f32 { + self + } +} + +impl UiNum for u32 { + fn to_f32(self) -> f32 { + self as f32 + } +} + +impl UiNum for i32 { + fn to_f32(self) -> f32 { + self as f32 + } +} diff --git a/src/core/num.rs b/src/layout/orientation.rs similarity index 59% rename from src/core/num.rs rename to src/layout/orientation.rs index b690cfd..66b7244 100644 --- a/src/core/num.rs +++ b/src/layout/orientation.rs @@ -1,22 +1,21 @@ -pub trait UiNum { - fn to_f32(self) -> f32; +use crate::layout::{Vec2, vec2}; + +#[derive(Clone, Copy, Debug)] +pub enum Corner { + TopLeft, + TopRight, + BotLeft, + BotRight, } -impl UiNum for f32 { - fn to_f32(self) -> f32 { - self - } -} - -impl UiNum for u32 { - fn to_f32(self) -> f32 { - self as f32 - } -} - -impl UiNum for i32 { - fn to_f32(self) -> f32 { - self as f32 +impl Corner { + pub const fn anchor(&self) -> Vec2 { + match self { + Corner::TopLeft => vec2(0.0, 0.0), + Corner::TopRight => vec2(1.0, 0.0), + Corner::BotLeft => vec2(0.0, 1.0), + Corner::BotRight => vec2(1.0, 1.0), + } } } diff --git a/src/layout/painter.rs b/src/layout/painter.rs index da3078c..8518dd6 100644 --- a/src/layout/painter.rs +++ b/src/layout/painter.rs @@ -1,5 +1,10 @@ +use image::GenericImageView; + use crate::{ - ActiveSensors, SensorMap, TextureHandle, UiRegion, WidgetId, Widgets, + layout::{ + ActiveSensors, SensorMap, TextData, TextureHandle, Textures, UiPos, UiRegion, Vec2, + WidgetId, Widgets, + }, render::{Primitive, Primitives}, }; @@ -9,16 +14,22 @@ pub struct Painter<'a, Ctx: 'static> { sensors_map: &'a SensorMap, active_sensors: &'a mut ActiveSensors, primitives: &'a mut Primitives, + textures: &'a mut Textures, + text: &'a mut TextData, region: UiRegion, + screen_size: Vec2, } impl<'a, Ctx> Painter<'a, Ctx> { - pub fn new( + pub(crate) fn new( nodes: &'a Widgets, primitives: &'a mut Primitives, ctx: &'a mut Ctx, sensors_map: &'a SensorMap, active_sensors: &'a mut ActiveSensors, + text: &'a mut TextData, + textures: &'a mut Textures, + screen_size: Vec2, ) -> Self { Self { nodes, @@ -26,7 +37,10 @@ impl<'a, Ctx> Painter<'a, Ctx> { active_sensors, sensors_map, primitives, + text, region: UiRegion::full(), + textures, + screen_size, } } @@ -69,9 +83,30 @@ impl<'a, Ctx> Painter<'a, Ctx> { } pub fn draw_texture(&mut self, handle: &TextureHandle) { + self.primitives.drawn_textures.push(handle.clone()); self.write(handle.inner); } + pub fn draw_texture_at(&mut self, handle: &TextureHandle, region: UiRegion) { + let old = self.region; + self.region = region; + self.draw_texture(handle); + self.region = old; + } + + pub fn draw_text(&mut self, content: &str) { + let handle = self.text.draw(content, self.textures); + let dims: Vec2 = self.textures[&handle].dimensions().into(); + let center = self.region.center().snap(self.screen_size); + let top_left = center - (dims / 2.0).floor(); + let bot_right = center + (dims / 2.0).ceil(); + let region = UiRegion { + top_left: UiPos::offset(top_left), + bot_right: UiPos::offset(bot_right), + }; + self.draw_texture_at(&handle, region); + } + pub fn region(&self) -> UiRegion { self.region } diff --git a/src/layout/region.rs b/src/layout/pos.rs similarity index 86% rename from src/layout/region.rs rename to src/layout/pos.rs index 22e0b89..8cf30be 100644 --- a/src/layout/region.rs +++ b/src/layout/pos.rs @@ -1,6 +1,5 @@ use crate::{ - Axis, Vec2, - layout::vec2, + layout::{Axis, Corner, Vec2, vec2}, util::{F32Util, impl_op}, }; @@ -12,6 +11,15 @@ pub struct UiPos { } impl UiPos { + /// expands this position into a sized region centered at self + pub fn expand(&self, size: impl Into) -> UiRegion { + let size = size.into(); + UiRegion { + top_left: self.shifted(-size / 2.0), + bot_right: self.shifted(size / 2.0), + } + } + pub const fn center() -> Self { Self::anchor(vec2(0.5, 0.5)) } @@ -23,6 +31,13 @@ impl UiPos { } } + pub const fn offset(offset: Vec2) -> Self { + Self { + anchor: Vec2::ZERO, + offset, + } + } + pub const fn corner(corner: Corner) -> Self { Self::anchor(corner.anchor()) } @@ -69,6 +84,15 @@ impl UiPos { self.flip(); self } + + pub fn to_size(&self, size: Vec2) -> Vec2 { + self.anchor * size + self.offset + } + + /// snaps this to a specific screen size to get actual pixel coordinates + pub fn snap(&self, size: Vec2) -> Vec2 { + self.to_size(size).round() + } } #[derive(Clone, Copy, Debug)] @@ -122,21 +146,12 @@ impl UiRegion { bot_right: UiPos::corner(Corner::BotRight), } } - pub fn center() -> Self { - Self::anchor(Vec2::new(0.5, 0.5)) - } pub fn anchor(anchor: Vec2) -> Self { Self { top_left: UiPos::anchor(anchor), bot_right: UiPos::anchor(anchor), } } - pub fn size(mut self, size: impl Into) -> Self { - let size = size.into(); - self.top_left = self.top_left.shifted(-size / 2.0); - self.bot_right = self.bot_right.shifted(size / 2.0); - self - } pub fn within(&self, parent: &Self) -> Self { Self { top_left: self.top_left.within(parent), @@ -170,19 +185,16 @@ impl UiRegion { self } - pub fn corner(corner: Corner) -> Self { - Self { - top_left: UiPos::corner(corner), - bot_right: UiPos::corner(corner), - } - } - pub fn to_screen(&self, size: Vec2) -> ScreenRect { ScreenRect { top_left: self.top_left.anchor * size + self.top_left.offset, bot_right: self.bot_right.anchor * size + self.bot_right.offset, } } + + pub fn center(&self) -> UiPos { + UiPos::center().within(self) + } } #[derive(Debug)] @@ -221,22 +233,3 @@ impl UIScalarView<'_> { } } } - -#[derive(Clone, Copy, Debug)] -pub enum Corner { - TopLeft, - TopRight, - BotLeft, - BotRight, -} - -impl Corner { - pub const fn anchor(&self) -> Vec2 { - match self { - Corner::TopLeft => vec2(0.0, 0.0), - Corner::TopRight => vec2(1.0, 0.0), - Corner::BotLeft => vec2(0.0, 1.0), - Corner::BotRight => vec2(1.0, 1.0), - } - } -} diff --git a/src/layout/sense.rs b/src/layout/sense.rs index 9df7752..7fc8659 100644 --- a/src/layout/sense.rs +++ b/src/layout/sense.rs @@ -1,4 +1,8 @@ -use crate::{HashMap, Ui, UiRegion, Vec2, WidgetId, util::Id}; +use crate::{ + HashMap, + layout::{Ui, UiRegion, Vec2, WidgetId}, + util::Id, +}; #[derive(Clone, Copy, PartialEq)] pub enum Sense { @@ -81,10 +85,7 @@ impl Ui { for sensor in &mut group.sensors { if should_run(sensor.sense, group.cursor, group.hover) { - (sensor.f.box_clone())(SenseCtx { - ui: self, - app: ctx, - }); + (sensor.f.box_clone())(SenseCtx { ui: self, app: ctx }); } } } diff --git a/src/layout/text.rs b/src/layout/text.rs new file mode 100644 index 0000000..8cf3ee9 --- /dev/null +++ b/src/layout/text.rs @@ -0,0 +1,57 @@ +use cosmic_text::{Attrs, Buffer, FontSystem, Metrics, Shaping, SwashCache}; +use image::{Rgba, RgbaImage}; + +use crate::{ + HashMap, + layout::{TextureHandle, Textures}, +}; + +pub(crate) struct TextData { + font_system: FontSystem, + cache: SwashCache, +} + +impl Default for TextData { + fn default() -> Self { + Self { + font_system: FontSystem::new(), + cache: SwashCache::new(), + } + } +} + +impl TextData { + pub fn draw(&mut self, content: &str, textures: &mut Textures) -> TextureHandle { + let metrics = Metrics::new(30.0, 30.0); + let mut buffer = Buffer::new(&mut self.font_system, metrics); + let mut buffer = buffer.borrow_with(&mut self.font_system); + buffer.set_text(content, &Attrs::new(), Shaping::Advanced); + + // dawg what is this api ??? + let mut pixels = HashMap::new(); + let mut min_x = 0; + let mut min_y = 0; + let mut max_x = 0; + let mut max_y = 0; + buffer.draw( + &mut self.cache, + cosmic_text::Color::rgb(0xff, 0xff, 0xff), + |x, y, _, _, color| { + min_x = min_x.min(x); + min_y = min_y.min(y); + max_x = max_x.max(x); + max_y = max_y.max(y); + pixels.insert((x, y), Rgba(color.as_rgba())); + }, + ); + let width = (max_x - min_x + 1) as u32; + let height = (max_y - min_y + 1) as u32; + let mut image = RgbaImage::new(width, height); + for ((x, y), color) in pixels { + let x = (x - min_x) as u32; + let y = (y - min_y) as u32; + image.put_pixel(x, y, color); + } + textures.add(image) + } +} diff --git a/src/layout/texture.rs b/src/layout/texture.rs index 91f71c7..3fbef99 100644 --- a/src/layout/texture.rs +++ b/src/layout/texture.rs @@ -1,7 +1,14 @@ +use std::ops::Index; + use image::DynamicImage; -use crate::{UiMsg, UiMsgSender, render::TexturePrimitive, util::RefCounter}; +use crate::{ + layout::{UiMsg, UiMsgSender}, + render::TexturePrimitive, + util::RefCounter, +}; +#[derive(Clone)] pub struct TextureHandle { pub inner: TexturePrimitive, counter: RefCounter, @@ -10,11 +17,11 @@ pub struct TextureHandle { /// a texture manager for a ui /// note that this is heavily oriented towards wgpu's renderer so the primitives don't need mapped -#[derive(Default)] pub struct Textures { free: Vec, images: Vec>, updates: Vec, + send: UiMsgSender, } pub enum TextureUpdate<'a> { @@ -30,8 +37,16 @@ enum Update { } impl Textures { - pub fn add(&mut self, image: DynamicImage, send: UiMsgSender) -> TextureHandle { - let view_idx = self.push(image); + pub fn new(send: UiMsgSender) -> Self { + Self { + free: Vec::new(), + images: Vec::new(), + updates: Vec::new(), + send, + } + } + pub fn add(&mut self, image: impl Into) -> TextureHandle { + let view_idx = self.push(image.into()); // 0 == default in renderer; TODO: actually create samplers here let sampler_idx = 0; TextureHandle { @@ -40,7 +55,7 @@ impl Textures { sampler_idx, }, counter: RefCounter::new(), - send, + send: self.send.clone(), } } @@ -79,3 +94,11 @@ impl Drop for TextureHandle { } } } + +impl Index<&TextureHandle> for Textures { + type Output = DynamicImage; + + fn index(&self, index: &TextureHandle) -> &Self::Output { + self.images[index.inner.view_idx as usize].as_ref().unwrap() + } +} diff --git a/src/layout/ui.rs b/src/layout/ui.rs index 531d63d..0d666a5 100644 --- a/src/layout/ui.rs +++ b/src/layout/ui.rs @@ -1,8 +1,11 @@ use image::DynamicImage; use crate::{ - ActiveSensors, HashMap, Painter, SensorMap, TextureHandle, Textures, Widget, WidgetId, - WidgetLike, + HashMap, + layout::{ + ActiveSensors, Painter, SensorMap, TextData, TextureHandle, Textures, Vec2, Widget, + WidgetId, WidgetLike, + }, render::Primitives, util::{Id, IdTracker}, }; @@ -18,8 +21,11 @@ pub struct Ui { updates: Vec, recv: Receiver, send: UiMsgSender, + size: Vec2, + // TODO: make these non pub(crate) pub(crate) primitives: Primitives, pub(crate) textures: Textures, + pub(crate) text: TextData, full_redraw: bool, pub(super) active_sensors: ActiveSensors, @@ -85,7 +91,12 @@ impl Ui { } pub fn add_texture(&mut self, image: DynamicImage) -> TextureHandle { - self.textures.add(image, self.send.clone()) + self.textures.add(image) + } + + pub fn resize(&mut self, size: impl Into) { + self.size = size.into(); + self.full_redraw = true; } pub fn redraw_all(&mut self, ctx: &mut Ctx) @@ -100,6 +111,9 @@ impl Ui { ctx, &self.sensor_map, &mut self.active_sensors, + &mut self.text, + &mut self.textures, + self.size, ); if let Some(base) = &self.base { painter.draw(base); @@ -217,18 +231,20 @@ impl dyn Widget { impl Default for Ui { fn default() -> Self { - let (del_send, del_recv) = channel(); + let (send, recv) = channel(); Self { base: Default::default(), widgets: Widgets::new(), updates: Default::default(), primitives: Default::default(), - textures: Textures::default(), + textures: Textures::new(send.clone()), + text: TextData::default(), full_redraw: false, active_sensors: Default::default(), sensor_map: Default::default(), - send: del_send, - recv: del_recv, + send, + recv, + size: Vec2::ZERO, } } } diff --git a/src/layout/vec2.rs b/src/layout/vec2.rs index 2e96d9e..f51b96e 100644 --- a/src/layout/vec2.rs +++ b/src/layout/vec2.rs @@ -1,4 +1,7 @@ -use crate::{util::{impl_op, F32Util}, UiNum}; +use crate::{ + layout::UiNum, + util::{F32Util, impl_op}, +}; use std::ops::*; #[repr(C)] @@ -28,6 +31,27 @@ impl Vec2 { y: self.y.lerp(from.y, to.y), } } + + pub const fn round(self) -> Self { + Self { + x: self.x.round(), + y: self.y.round(), + } + } + + pub const fn floor(self) -> Self { + Self { + x: self.x.floor(), + y: self.y.floor(), + } + } + + pub const fn ceil(self) -> Self { + Self { + x: self.x.ceil(), + y: self.y.ceil(), + } + } } impl const From for Vec2 { diff --git a/src/layout/widget.rs b/src/layout/widget.rs index 8894f47..863ae3f 100644 --- a/src/layout/widget.rs +++ b/src/layout/widget.rs @@ -1,4 +1,5 @@ -use crate::{Painter, Ui, WidgetId, WidgetIdFnRet}; +use crate::layout::{Painter, Ui, WidgetId, WidgetIdFnRet}; + use std::{any::Any, marker::PhantomData}; pub trait Widget: Any { @@ -33,7 +34,7 @@ pub trait WidgetLike { /// currently a macro for rust analyzer (doesn't support trait aliases atm) macro_rules! WidgetFnRet { ($W:ty, $Ctx:ty) => { - impl FnOnce(&mut $crate::Ui<$Ctx>) -> $W + impl FnOnce(&mut $crate::layout::Ui<$Ctx>) -> $W }; } pub(crate) use WidgetFnRet; diff --git a/src/lib.rs b/src/lib.rs index f89f25a..d5754c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,13 +5,16 @@ #![feature(trait_alias)] #![feature(negative_impls)] -mod core; -mod layout; +pub mod core; +pub mod layout; pub mod render; mod util; -pub use core::*; -pub use layout::*; +pub mod prelude { + pub use crate::core::*; + pub use crate::layout::*; + pub use crate::render::*; +} pub type HashMap = std::collections::HashMap; pub type HashSet = std::collections::HashSet; diff --git a/src/render/data.rs b/src/render/data.rs index 907b3a4..27fbebf 100644 --- a/src/render/data.rs +++ b/src/render/data.rs @@ -1,6 +1,6 @@ use wgpu::VertexAttribute; -use crate::UiRegion; +use crate::layout::UiRegion; #[repr(C)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable, Default)] diff --git a/src/render/mod.rs b/src/render/mod.rs index b5e66f8..14b61b3 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,7 +1,7 @@ use std::num::NonZero; use crate::{ - Ui, + layout::Ui, render::{data::PrimitiveInstance, texture::GpuTextures, util::ArrBuf}, }; use data::WindowUniform; diff --git a/src/render/primitive.rs b/src/render/primitive.rs index d022264..8d5dc6d 100644 --- a/src/render/primitive.rs +++ b/src/render/primitive.rs @@ -1,5 +1,5 @@ use crate::{ - Color, UiRegion, + layout::{Color, TextureHandle, UiRegion}, render::{ArrBuf, data::PrimitiveInstance}, }; use bytemuck::Pod; @@ -8,6 +8,8 @@ use wgpu::*; pub struct Primitives { pub(super) instances: Vec, pub(super) data: PrimitiveData, + // ensure drawn textures don't get freed + pub(crate) drawn_textures: Vec, pub updated: bool, } @@ -16,6 +18,7 @@ impl Default for Primitives { Self { instances: Default::default(), data: Default::default(), + drawn_textures: Default::default(), updated: true, } } @@ -98,6 +101,7 @@ impl Primitives { self.updated = true; self.instances.clear(); self.data.clear(); + self.drawn_textures.clear(); } } diff --git a/src/render/texture.rs b/src/render/texture.rs index 658ffc8..2bc3468 100644 --- a/src/render/texture.rs +++ b/src/render/texture.rs @@ -1,7 +1,7 @@ use image::{DynamicImage, EncodableLayout}; use wgpu::{util::DeviceExt, *}; -use crate::{TextureUpdate, Textures}; +use crate::layout::{TextureUpdate, Textures}; pub struct GpuTextures { device: Device, @@ -23,6 +23,9 @@ impl GpuTextures { TextureUpdate::Free(i) => self.free(i), } } + // if changed { + // println!("{}", self.views.len()); + // } changed } fn set(&mut self, i: u32, image: &DynamicImage) { diff --git a/src/testing/app.rs b/src/testing/app.rs index 1b7ad89..6466a26 100644 --- a/src/testing/app.rs +++ b/src/testing/app.rs @@ -1,4 +1,4 @@ -use ui::Ui; +use ui::layout::Ui; use winit::{ application::ApplicationHandler, event::WindowEvent, diff --git a/src/testing/input.rs b/src/testing/input.rs index 67c836a..ab1fe97 100644 --- a/src/testing/input.rs +++ b/src/testing/input.rs @@ -1,4 +1,4 @@ -use ui::{CursorState, Vec2}; +use ui::layout::{CursorState, Vec2}; use winit::event::WindowEvent; use crate::testing::Client; diff --git a/src/testing/mod.rs b/src/testing/mod.rs index 794e0f7..f8704fb 100644 --- a/src/testing/mod.rs +++ b/src/testing/mod.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use app::App; use render::Renderer; -use ui::*; +use ui::prelude::*; use winit::{event::WindowEvent, event_loop::ActiveEventLoop, window::Window}; use crate::testing::input::Input; @@ -80,10 +80,11 @@ impl Client { color: UiColor, main: &WidgetId, to: &WidgetId, - ) -> impl WidgetLike { + label: impl Into, + ) -> impl WidgetLike { let main = main.clone(); let to = to.clone().erase_type(); - Rect::new(color) + let rect = Rect::new(color) .id_on(Sense::PressStart, move |id, ctx| { ctx.ui[&main].inner = to.clone(); ctx.ui[id].color = color.add_rgb(-0.2); @@ -96,14 +97,15 @@ impl Client { }) .edit_on(Sense::HoverEnd, move |r, _| { r.color = color; - }) + }); + (rect, text(label)).stack() } let tabs = ui.add( ( - switch_button(UiColor::RED, &main, &pad_test), - switch_button(UiColor::GREEN, &main, &span_test), - switch_button(UiColor::BLUE, &main, &span_add_test), + switch_button(UiColor::RED, &main, &pad_test, "pad test"), + switch_button(UiColor::GREEN, &main, &span_test, "span test"), + switch_button(UiColor::BLUE, &main, &span_add_test, "span add test"), ) .span(Dir::RIGHT, [1, 1, 1]), ); @@ -113,8 +115,8 @@ impl Client { println!("{}", ctx.ui.num_widgets()); }) .region( - UiRegion::corner(Corner::BotRight) - .size((150, 150)) + UiPos::corner(Corner::BotRight) + .expand((150, 150)) .shifted((-75, -75)), ); @@ -125,8 +127,8 @@ impl Client { ctx.ui[&s].children.pop(); }) .region( - UiRegion::corner(Corner::BotLeft) - .size((150, 150)) + UiPos::corner(Corner::BotLeft) + .expand((150, 150)) .shifted((75, -75)), ); ui.set_base( @@ -160,7 +162,10 @@ impl Client { self.renderer.update(ui); self.renderer.draw() } - WindowEvent::Resized(size) => self.renderer.resize(&size), + WindowEvent::Resized(size) => { + ui.resize((size.width, size.height)); + self.renderer.resize(&size) + } WindowEvent::KeyboardInput { event, .. } => { if event.state.is_pressed() { let child = ui diff --git a/src/testing/render/mod.rs b/src/testing/render/mod.rs index 9da158c..9b993b2 100644 --- a/src/testing/render/mod.rs +++ b/src/testing/render/mod.rs @@ -1,7 +1,7 @@ use pollster::FutureExt; use std::sync::Arc; use ui::{ - Ui, + layout::Ui, render::{UiLimits, UiRenderer}, }; use wgpu::{util::StagingBelt, *}; diff --git a/src/util/refcount.rs b/src/util/refcount.rs index 064a7a1..7e695bb 100644 --- a/src/util/refcount.rs +++ b/src/util/refcount.rs @@ -1,7 +1,6 @@ use std::sync::{ Arc, atomic::{AtomicU32, Ordering}, - mpsc::Sender, }; pub struct RefCounter(Arc);