From 836596df8cc680cae198175ee892923581eddfda Mon Sep 17 00:00:00 2001 From: Freja Roberts Date: Thu, 14 Mar 2024 15:45:46 +0100 Subject: [PATCH] chore: make grow 2D --- .env | 2 +- Cargo.lock | 4 +- Cargo.toml | 6 + examples/row.rs | 4 +- recipes.json | 3 + violet-core/src/constraints.rs | 10 +- violet-core/src/layout/cache.rs | 55 ++++-- violet-core/src/layout/flow.rs | 11 +- violet-core/src/layout/mod.rs | 196 +++++++++---------- violet-core/src/layout/stack.rs | 13 +- violet-core/src/state/map.rs | 16 ++ violet-core/src/state/mod.rs | 12 ++ violet-core/src/style/mod.rs | 6 +- violet-core/src/systems.rs | 3 +- violet-core/src/types.rs | 6 + violet-core/src/unit.rs | 13 +- violet-core/src/widget/container.rs | 17 +- violet-core/src/widget/interactive/input.rs | 6 +- violet-demo/src/lib.rs | 201 +++++++++++--------- violet-wgpu/src/renderer/debug_renderer.rs | 9 +- violet-wgpu/src/renderer/window_renderer.rs | 8 +- violet-wgpu/src/text.rs | 24 +-- 22 files changed, 340 insertions(+), 285 deletions(-) diff --git a/.env b/.env index 9d0069a..72ec0ad 100644 --- a/.env +++ b/.env @@ -1,2 +1,2 @@ -RUST_LOG="info" +# RUST_LOG="info" # RUST_BACKTRACE=1 diff --git a/Cargo.lock b/Cargo.lock index cad2dae..7f8132e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -630,7 +630,7 @@ dependencies = [ [[package]] name = "flax" version = "0.6.2" -source = "git+https://github.com/ten3roberts/flax#39c01757b9ea5f142faeaf9d8b1473e1745ffcca" +source = "git+https://github.com/ten3roberts/flax#bb23e6eaf91195371b96d8725cfef42efdc4b19c" dependencies = [ "anyhow", "atomic_refcell", @@ -648,7 +648,7 @@ dependencies = [ [[package]] name = "flax-derive" version = "0.6.0" -source = "git+https://github.com/ten3roberts/flax#39c01757b9ea5f142faeaf9d8b1473e1745ffcca" +source = "git+https://github.com/ten3roberts/flax#bb23e6eaf91195371b96d8725cfef42efdc4b19c" dependencies = [ "itertools 0.11.0", "proc-macro-crate", diff --git a/Cargo.toml b/Cargo.toml index 422bd34..a0ac819 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -106,6 +106,12 @@ opt-level = 2 [profile.dev.package.parking_lot] opt-level = 2 +[profile.dev.package.puffin] +opt-level = 2 + +[profile.dev.package.puffin_server] +opt-level = 2 + [patch.crates-io] palette = { git = "https://github.com/Ogeon/palette" } diff --git a/examples/row.rs b/examples/row.rs index 46a92bc..400b479 100644 --- a/examples/row.rs +++ b/examples/row.rs @@ -45,7 +45,9 @@ impl Widget for MainApp { row(( column(( Rectangle::new(JADE_DEFAULT).with_size(Unit::px2(900.0, 40.0)), - Rectangle::new(JADE_400).with_size(Unit::px2(900.0, 40.0)), + Rectangle::new(JADE_400) + .with_size(Unit::px2(900.0, 40.0)) + .with_min_size(Unit::px2(400.0, 40.0)), )), Rectangle::new(LION_DEFAULT).with_size(Unit::px2(900.0, 40.0)), )), diff --git a/recipes.json b/recipes.json index e4397fb..71f4881 100644 --- a/recipes.json +++ b/recipes.json @@ -5,6 +5,9 @@ "run demo": { "cmd": "cargo run --package violet-demo" }, + "run demo release": { + "cmd": "cargo run --package violet-demo --release" + }, "run basic": { "cmd": "cargo run --package violet --example basic" }, diff --git a/violet-core/src/constraints.rs b/violet-core/src/constraints.rs index 367a08d..d48c845 100644 --- a/violet-core/src/constraints.rs +++ b/violet-core/src/constraints.rs @@ -1,4 +1,4 @@ -use glam::{vec2, Vec2}; +use glam::{vec2, BVec2, Vec2}; use crate::layout::{Direction, LayoutLimits, SizeResolver, SizingHints}; @@ -42,8 +42,8 @@ impl SizeResolver for FixedAreaConstraint { min * self.unit_size, vec2(size.x, (self.area / size.x).ceil()) * self.unit_size, SizingHints { - can_grow: true, - fixed_size: false, + can_grow: BVec2::TRUE, + relative_size: BVec2::TRUE, }, ) } @@ -53,11 +53,11 @@ impl SizeResolver for FixedAreaConstraint { _: &flax::EntityRef, _: Vec2, limits: crate::layout::LayoutLimits, - ) -> (Vec2, bool) { + ) -> (Vec2, BVec2) { let width = (limits.max_size.x / self.unit_size).floor().max(1.0); let height = (self.area / width).ceil(); - (vec2(width, height) * self.unit_size, true) + (vec2(width, height) * self.unit_size, BVec2::TRUE) } } diff --git a/violet-core/src/layout/cache.rs b/violet-core/src/layout/cache.rs index ca61e2b..9d45c0a 100644 --- a/violet-core/src/layout/cache.rs +++ b/violet-core/src/layout/cache.rs @@ -1,7 +1,7 @@ use flax::{component, components::child_of, Entity, FetchExt, RelationExt, World}; -use glam::Vec2; +use glam::{BVec2, Vec2}; -use super::{flow::Row, Block, Direction, LayoutLimits, Sizing, SizingHints}; +use super::{flow::Row, Block, Direction, LayoutLimits, Sizing}; #[derive(Debug)] pub struct CachedValue { @@ -34,7 +34,7 @@ pub struct LayoutCache { pub(crate) query_row: Option>, pub(crate) layout: Option>, on_invalidated: Option>, - pub(crate) fixed_size: bool, + pub(crate) relative_size: BVec2, } impl LayoutCache { @@ -44,7 +44,7 @@ impl LayoutCache { query_row: None, layout: None, on_invalidated, - fixed_size: false, + relative_size: BVec2::TRUE, } } @@ -86,15 +86,12 @@ impl LayoutCache { pub fn query(&self) -> &[Option>; 2] { &self.query } - - pub fn fixed_size(&self) -> bool { - self.fixed_size - } } /// Invalidates a widgets layout cache along with its ancestors pub(crate) fn invalidate_widget(world: &World, id: Entity) { let entity = world.entity(id).unwrap(); + tracing::info!(%entity, "invalidating widget"); let query = (layout_cache().as_mut(), child_of.first_relation().opt()); let mut query = entity.query(&query); @@ -117,7 +114,14 @@ pub(crate) fn validate_cached_query( let min_size = value.min.size(); let preferred_size = value.preferred.size(); - tracing::debug!( ?preferred_size, %cache.limits.max_size, %limits.max_size, "validate_cached_query"); + // tracing::debug!( ?preferred_size, %cache.limits.max_size, %limits.max_size, "validate_cached_query"); + + let hints = &value.hints; + if hints.can_grow.x && cache.limits.max_size.x < limits.max_size.x + || (hints.can_grow.x && cache.limits.max_size.y < limits.max_size.y) + { + tracing::info!(%hints.can_grow, ?cache.limits.max_size, %limits.max_size, "invalidated by can_grow"); + } min_size.x >= limits.min_size.x - LAYOUT_TOLERANCE && min_size.y >= limits.min_size.y - LAYOUT_TOLERANCE @@ -126,8 +130,10 @@ pub(crate) fn validate_cached_query( && min_size.y <= limits.max_size.y + LAYOUT_TOLERANCE && preferred_size.x <= limits.max_size.x + LAYOUT_TOLERANCE && preferred_size.y <= limits.max_size.y + LAYOUT_TOLERANCE - && (!value.hints.can_grow || cache.limits.max_size.abs_diff_eq(limits.max_size, LAYOUT_TOLERANCE)) - && (value.hints.fixed_size || cache.content_area.abs_diff_eq(content_area, LAYOUT_TOLERANCE)) + && (!hints.can_grow.x || cache.limits.max_size.x >= limits.max_size.x - LAYOUT_TOLERANCE) + && (!hints.can_grow.y || cache.limits.max_size.y >= limits.max_size.y - LAYOUT_TOLERANCE) + && (!hints.relative_size.x || (cache.content_area.x - content_area.x).abs() < LAYOUT_TOLERANCE) + && (!hints.relative_size.y || (cache.content_area.y - content_area.y).abs() < LAYOUT_TOLERANCE) } pub(crate) fn validate_cached_layout( @@ -135,21 +141,29 @@ pub(crate) fn validate_cached_layout( limits: LayoutLimits, content_area: Vec2, // Calculated from the query stage - fixed_size: bool, + relative_size: BVec2, ) -> bool { let value = &cache.value; - let size = value.rect.size(); + let size = value.rect.size().min(cache.limits.max_size); + + // tracing::debug!( ?size, %cache.limits.max_size, %limits.max_size, "validate_cached_layout"); - tracing::debug!( ?size, %cache.limits.max_size, %limits.max_size, "validate_cached_layout"); + if value.can_grow.x && cache.limits.max_size.x < limits.max_size.x + || (value.can_grow.x && cache.limits.max_size.y < limits.max_size.y) + { + tracing::info!(%value.can_grow, ?cache.limits.max_size, %limits.max_size, "invalidated layout by can_grow"); + } size.x >= limits.min_size.x - LAYOUT_TOLERANCE && size.y >= limits.min_size.y - LAYOUT_TOLERANCE // Min may be larger than preferred for the orthogonal optimization direction && size.x <= limits.max_size.x + LAYOUT_TOLERANCE && size.y <= limits.max_size.y + LAYOUT_TOLERANCE - && (!value.can_grow || cache.limits.max_size.abs_diff_eq(limits.max_size, LAYOUT_TOLERANCE)) - && (fixed_size || cache.content_area.abs_diff_eq(content_area, LAYOUT_TOLERANCE)) + && (!value.can_grow.x || cache.limits.max_size.x >= limits.max_size.x - LAYOUT_TOLERANCE) + && (!value.can_grow.y || cache.limits.max_size.y >= limits.max_size.y - LAYOUT_TOLERANCE) + && (!relative_size.x || (cache.content_area.x - content_area.x).abs() < LAYOUT_TOLERANCE) + && (!relative_size.y || (cache.content_area.y - content_area.y).abs() < LAYOUT_TOLERANCE) } pub(crate) fn validate_cached_row( @@ -161,8 +175,9 @@ pub(crate) fn validate_cached_row( let min_size = value.min.size(); let preferred_size = value.preferred.size(); + let hints = value.hints; - tracing::debug!( ?preferred_size, %cache.limits.max_size, %limits.max_size, "validate_cached_row"); + // tracing::debug!( ?preferred_size, %cache.limits.max_size, %limits.max_size, "validate_cached_row"); min_size.x >= limits.min_size.x - LAYOUT_TOLERANCE && min_size.y >= limits.min_size.y - LAYOUT_TOLERANCE @@ -171,8 +186,10 @@ pub(crate) fn validate_cached_row( && min_size.y <= limits.max_size.y + LAYOUT_TOLERANCE && preferred_size.x <= limits.max_size.x + LAYOUT_TOLERANCE && preferred_size.y <= limits.max_size.y + LAYOUT_TOLERANCE - && (!value.hints.can_grow || cache.limits.max_size.abs_diff_eq(limits.max_size, LAYOUT_TOLERANCE)) - && (value.hints.fixed_size || cache.content_area.abs_diff_eq(content_area, LAYOUT_TOLERANCE)) + && (!hints.can_grow.x || cache.limits.max_size.x >= limits.max_size.x - LAYOUT_TOLERANCE) + && (!hints.can_grow.y || cache.limits.max_size.y >= limits.max_size.y - LAYOUT_TOLERANCE) + && (!hints.relative_size.x || (cache.content_area.x - content_area.x).abs() < LAYOUT_TOLERANCE) + && (!hints.relative_size.y || (cache.content_area.y - content_area.y).abs() < LAYOUT_TOLERANCE) } component! { diff --git a/violet-core/src/layout/flow.rs b/violet-core/src/layout/flow.rs index ea8b2e2..e14e515 100644 --- a/violet-core/src/layout/flow.rs +++ b/violet-core/src/layout/flow.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use flax::{Entity, EntityRef, World}; -use glam::{vec2, Vec2}; +use glam::{vec2, BVec2, Vec2}; use itertools::Itertools; use crate::{ @@ -260,7 +260,7 @@ impl FlowLayout { let cross_size = row.preferred.size().max(preferred_size).dot(cross_axis); - let mut can_grow = false; + let mut can_grow = BVec2::FALSE; // Distribute the size to the widgets and apply their layout let blocks = row .blocks @@ -328,7 +328,7 @@ impl FlowLayout { // let local_rect = widget_outer_bounds(world, &child, size); let block = update_subtree(world, &entity, content_area.size(), child_limits); - can_grow = can_grow || block.can_grow; + can_grow |= block.can_grow; tracing::debug!(?block, "updated subtree"); // block.rect = block @@ -462,10 +462,7 @@ impl FlowLayout { let mut sum = 0.0; let cross_size = row.preferred.size().max(preferred_size).dot(cross_axis); - let mut hints = SizingHints { - fixed_size: true, - can_grow: false, - }; + let mut hints = SizingHints::default(); // Distribute the size to the widgets and apply their layout row diff --git a/violet-core/src/layout/mod.rs b/violet-core/src/layout/mod.rs index d16adf9..d1d567d 100644 --- a/violet-core/src/layout/mod.rs +++ b/violet-core/src/layout/mod.rs @@ -5,7 +5,7 @@ mod stack; use std::fmt::{Display, Formatter}; use flax::{Entity, EntityRef, FetchExt, World}; -use glam::{vec2, Vec2}; +use glam::{vec2, BVec2, Vec2}; use crate::{ components::{ @@ -120,6 +120,14 @@ impl Layout { } } +#[derive(Debug, Clone)] +pub(crate) struct QueryArgs { + limits: LayoutLimits, + content_area: Vec2, + distribute: bool, + direction: Direction, +} + #[derive(Debug, Clone, Copy)] pub struct Sizing { min: Rect, @@ -149,43 +157,49 @@ pub struct LayoutLimits { pub max_size: Vec2, } +impl Display for LayoutLimits { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "min: {}, max: {}", self.min_size, self.max_size) + } +} + /// A block is a rectangle and surrounding support such as margin #[derive(Debug, Clone, Copy, Default)] pub struct Block { pub(crate) rect: Rect, pub(crate) margin: Edges, - /// See: [Sizing::clamped] - pub can_grow: bool, + /// See: [SizingHints::can_grow] + pub can_grow: BVec2, } impl Block { - pub(crate) fn new(rect: Rect, margin: Edges, clamped: bool) -> Self { + pub(crate) fn new(rect: Rect, margin: Edges, can_grow: BVec2) -> Self { Self { rect, margin, - can_grow: clamped, + can_grow, } } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct SizingHints { /// Size does not depend on the size of the parent - pub can_grow: bool, + pub can_grow: BVec2, /// The widget size is clamped given the provided size limits, and could be larger. /// /// If this is true, giving *more* space to a widget may cause it to grow. /// /// This is used for an optimization to avoid invalidating the layout when the available size /// increases - pub fixed_size: bool, + pub relative_size: BVec2, } impl Default for SizingHints { fn default() -> Self { Self { - can_grow: false, - fixed_size: true, + can_grow: BVec2::FALSE, + relative_size: BVec2::FALSE, } } } @@ -193,8 +207,8 @@ impl Default for SizingHints { impl SizingHints { pub fn combine(self, other: Self) -> Self { Self { - can_grow: self.can_grow || other.can_grow, - fixed_size: self.fixed_size && other.fixed_size, + can_grow: self.can_grow | other.can_grow, + relative_size: self.relative_size | other.relative_size, } } } @@ -274,6 +288,7 @@ pub(crate) fn query_size( tracing::debug_span!("query_size", name=entity.name().as_deref(), ?limits, %content_area) .entered(); + // tracing::info!(name=entity.name().as_deref(), ?limits, %content_area, ?direction, "query_size"); let query = ( layout_cache().as_mut(), components::margin().opt_or_default(), @@ -290,12 +305,15 @@ pub(crate) fn query_size( let (cache, &margin, &padding, min_size, max_size, size, size_resolver, children, layout) = query.get().unwrap(); - let fixed_boundary_size = min_size.is_fixed() && max_size.map(|v| v.is_fixed()).unwrap_or(true); + let fixed_boundary_size = + min_size.is_relative() | max_size.map(|v| v.is_relative()).unwrap_or(BVec2::FALSE); let min_size = min_size.resolve(content_area); let max_size = max_size.map(|v| v.resolve(content_area)); limits.min_size = limits.min_size.max(min_size); + let external_max_size = limits.max_size; + // Minimum size is *always* respected, even if that entails overflowing limits.max_size = limits.max_size.max(limits.min_size); @@ -306,12 +324,7 @@ pub(crate) fn query_size( // Check if cache is valid if let Some(cache) = &cache.query[direction as usize] { if validate_cached_query(cache, limits, content_area) { - // if cache.is_valid(limits, content_area) { - let _span = tracing::trace_span!("cached").entered(); - // validate_sizing(entity, &cache.value, limits); - tracing::debug!(%entity, "found valid cached query"); return cache.value; - // } } } @@ -324,10 +337,17 @@ pub(crate) fn query_size( let resolved_size = size.resolve(content_area); let hints = SizingHints { - fixed_size: fixed_boundary_size && size.is_fixed(), - can_grow: resolved_size.x > limits.max_size.x || resolved_size.y > limits.max_size.y, + relative_size: fixed_boundary_size | size.is_relative(), + can_grow: BVec2::new( + resolved_size.x > external_max_size.x, + resolved_size.y > external_max_size.y, + ), }; + // if hints != Default::default() { + // tracing::info!(%entity, ?resolved_size, ?external_max_size, "can grow"); + // } + // Clamp max size here since we ensure it is > min_size let resolved_size = resolved_size.clamp(limits.min_size, limits.max_size); @@ -388,22 +408,21 @@ pub(crate) fn query_size( sizing.min = sizing.min.translate(min_offset); sizing.preferred = sizing.preferred.translate(offset); - // Widget size is limited by itself and is not affected by the size of the parent - if let Some(max_size) = max_size { - if sizing - .preferred - .size() - .abs_diff_eq(max_size, LAYOUT_TOLERANCE) - { - sizing.hints.can_grow = false; - } - } + // // Widget size is limited by itself and is not affected by the size of the parent + // if let Some(max_size) = max_size { + // if sizing + // .preferred + // .size() + // .abs_diff_eq(max_size, LAYOUT_TOLERANCE) + // { + // sizing.hints.can_grow = false; + // } + // } // validate_sizing(entity, &sizing, limits); - tracing::debug!(%sizing); cache.insert_query(direction, CachedValue::new(limits, content_area, sizing)); - cache.fixed_size = sizing.hints.fixed_size; + cache.relative_size = sizing.hints.relative_size; sizing } @@ -442,7 +461,9 @@ pub(crate) fn update_subtree( query.get().unwrap(); let min_size = min_size.resolve(content_area); let max_size = max_size.map(|v| v.resolve(content_area)); + let external_max_size = limits.max_size; + tracing::debug!(%min_size, ?max_size, %limits); limits.min_size = limits.min_size.max(min_size); limits.max_size = limits.max_size.max(limits.min_size); @@ -453,10 +474,12 @@ pub(crate) fn update_subtree( // Check if cache is still valid if let Some(value) = &cache.layout { - if validate_cached_layout(value, limits, content_area, cache.fixed_size) { - tracing::debug!(%entity, ?value, "found valid cached layout"); - // validate_block(entity, &value.value, limits); + if validate_cached_layout(value, limits, content_area, cache.relative_size) { + tracing::debug!(%entity, %value.value.rect, %value.value.can_grow, "found valid cached layout"); + return value.value; + } else { + tracing::debug!(%entity, ?value, "invalid cached layout"); } } @@ -471,7 +494,14 @@ pub(crate) fn update_subtree( let resolved_size = size.resolve(content_area); - let can_grow = resolved_size.x > limits.max_size.x || resolved_size.y > limits.max_size.y; + let can_grow = BVec2::new( + resolved_size.x > external_max_size.x, + resolved_size.y > external_max_size.y, + ); + + if can_grow.any() { + tracing::info!(%entity, ?resolved_size, ?external_max_size, "can grow"); + } let resolved_size = resolved_size.clamp(limits.min_size, limits.max_size); @@ -505,7 +535,7 @@ pub(crate) fn update_subtree( let (intrinsic_size, instrinsic_can_grow) = size_resolver .map(|v| v.apply(entity, content_area, limits)) - .unwrap_or((Vec2::ZERO, false)); + .unwrap_or((Vec2::ZERO, BVec2::TRUE)); let size = intrinsic_size.max(resolved_size); @@ -514,10 +544,20 @@ pub(crate) fn update_subtree( Block { rect, margin, - can_grow: instrinsic_can_grow || can_grow, + can_grow: instrinsic_can_grow | can_grow, } }; + // if block.rect.size().x > limits.max_size.x || block.rect.size().y > limits.max_size.y { + // tracing::error!( + // %entity, + // rect_size = %block.rect.size(), + // %limits.max_size, + // "Widget size exceeds constraints", + // ); + // panic!(""); + // } + let constraints = Constraints::from_entity(entity); block.rect = block.rect.with_size(constraints.apply(block.rect.size())); @@ -527,11 +567,11 @@ pub(crate) fn update_subtree( entity.update_dedup(components::layout_bounds(), block.rect.size()); // Widget size is limited by itself and is not affected by the size of the parent - if let Some(max_size) = max_size { - if block.rect.size().abs_diff_eq(max_size, LAYOUT_TOLERANCE) { - block.can_grow = false; - } - } + // if let Some(max_size) = max_size { + // if block.rect.size().abs_diff_eq(max_size, LAYOUT_TOLERANCE) { + // block.can_grow = BVec2::FALSE; + // } + // } // if block.rect.size().x > limits.max_size.x || block.rect.size().y > limits.max_size.y { // tracing::error!( @@ -542,6 +582,7 @@ pub(crate) fn update_subtree( // validate_block(entity, &block, limits); + tracing::debug!(%limits, %content_area, %block.can_grow, %block.rect, "caching layout"); cache.insert_layout(CachedValue::new(limits, content_area, block)); block @@ -568,7 +609,7 @@ pub trait SizeResolver: Send + Sync { entity: &EntityRef, content_area: Vec2, limits: LayoutLimits, - ) -> (Vec2, bool); + ) -> (Vec2, BVec2); } #[derive(Debug)] @@ -599,73 +640,6 @@ impl Constraints { } } -// fn query_constraints( -// entity: &EntityRef, -// content_area: Vec2, -// limits: LayoutLimits, -// squeeze: Direction, -// ) -> (Vec2, Vec2, SizingHints) { -// let (mut size, constraints, fixed_size) = resolve_base_size(entity, content_area); - -// let clamped = size.x > limits.max_size.x || size.y > limits.max_size.y; -// let mut min_size = limits.min_size; - -// if let Ok(mut resolver) = entity.get_mut(components::size_resolver()) { -// let (resolved_min, resolved_size, hints) = -// resolver.query(entity, content_area, limits, squeeze); - -// let optimize_axis = squeeze.to_axis(); -// if resolved_min.dot(optimize_axis) > resolved_size.dot(optimize_axis) { -// panic!("Size resolver returned a minimum size that is larger than the preferred size for the given optimization\n\nmin: {}, size: {}, widget: {}", resolved_min.dot(optimize_axis), resolved_size.dot(optimize_axis), entity); -// } - -// min_size = resolved_min; -// size = resolved_size.max(size); - -// ( -// constraints.resolve(min_size.clamp(limits.min_size, limits.max_size)), -// constraints.resolve(size.clamp(limits.min_size, limits.max_size)), -// SizingHints { -// fixed_size: fixed_size && hints.fixed_size, -// can_grow: clamped || hints.can_grow, -// }, -// ) -// } else { -// // tracing::info!(?min_size, ?size, ?limits, "query_constraints"); - -// ( -// constraints.resolve(min_size.clamp(limits.min_size, limits.max_size)), -// constraints.resolve(size.clamp(limits.min_size, limits.max_size)), -// SizingHints { -// can_grow: clamped, -// fixed_size, -// }, -// ) -// } -// } - -// fn apply_constraints(entity: &EntityRef, content_area: Vec2, limits: LayoutLimits) -> (Vec2, bool) { -// let (size, constraints, _) = resolve_base_size(entity, content_area); - -// let clamped = size.x > limits.max_size.x || size.y > limits.max_size.y; - -// if let Ok(mut resolver) = entity.get_mut(components::size_resolver()) { -// let (resolved_size, resolved_clamped) = resolver.apply(entity, content_area, limits); - -// let size = resolved_size.max(size); - -// ( -// constraints.resolve(size.clamp(limits.min_size, limits.max_size)), -// clamped || resolved_clamped, -// ) -// } else { -// ( -// constraints.resolve(size.clamp(limits.min_size, limits.max_size)), -// clamped, -// ) -// } -// } - /// Resolves a widgets position relative to its own bounds fn resolve_pos(entity: &EntityRef, parent_size: Vec2, self_size: Vec2) -> Vec2 { let query = (offset().opt_or_default(), anchor().opt_or_default()); diff --git a/violet-core/src/layout/stack.rs b/violet-core/src/layout/stack.rs index 783a6a5..6a240c5 100644 --- a/violet-core/src/layout/stack.rs +++ b/violet-core/src/layout/stack.rs @@ -1,5 +1,5 @@ use flax::{Entity, EntityRef, World}; -use glam::{vec2, Vec2}; +use glam::{vec2, BVec2, Vec2}; use itertools::Itertools; use crate::{ @@ -98,7 +98,7 @@ impl StackLayout { preferred_size: Vec2, ) -> Block { puffin::profile_function!(); - let _span = tracing::info_span!("StackLayout::apply").entered(); + let _span = tracing::debug_span!("StackLayout::apply").entered(); let mut bounds = Rect { min: Vec2::MAX, @@ -132,7 +132,7 @@ impl StackLayout { let mut aligned_bounds = StackableBounds::from_rect(Rect::from_size_pos(preferred_size, content_area.min)); - let mut can_grow = false; + let mut can_grow = BVec2::FALSE; let offset = resolve_pos(entity, content_area.size(), size); for (entity, block) in blocks { @@ -151,7 +151,7 @@ impl StackLayout { block.margin, )); - can_grow = can_grow || block.can_grow; + can_grow |= block.can_grow; // entity.update_dedup(components::rect(), block.rect.translate(offset)); entity.update_dedup(components::rect(), block.rect); @@ -183,10 +183,7 @@ impl StackLayout { let mut min_bounds = StackableBounds::from_rect(min_rect); let mut preferred_bounds = StackableBounds::from_rect(min_rect); - let mut hints = SizingHints { - fixed_size: true, - can_grow: false, - }; + let mut hints = SizingHints::default(); for &child in children.iter() { let entity = world.entity(child).expect("invalid child"); diff --git a/violet-core/src/state/map.rs b/violet-core/src/state/map.rs index 9dd5e0c..5dd1b7d 100644 --- a/violet-core/src/state/map.rs +++ b/violet-core/src/state/map.rs @@ -11,6 +11,7 @@ use super::{State, StateOwned, StateSink, StateStream}; /// /// However, as this does not assume the derived state is contained withing the original state is /// does not allow in-place mutation. + pub struct Map { inner: C, conv_to: Arc, @@ -18,6 +19,21 @@ pub struct Map { _marker: PhantomData, } +impl Clone for Map +where + C: Clone, + G: Clone, +{ + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + conv_to: self.conv_to.clone(), + conv_from: self.conv_from.clone(), + _marker: PhantomData, + } + } +} + impl State for Map { type Item = U; } diff --git a/violet-core/src/state/mod.rs b/violet-core/src/state/mod.rs index 1f5999c..accce60 100644 --- a/violet-core/src/state/mod.rs +++ b/violet-core/src/state/mod.rs @@ -22,6 +22,8 @@ pub trait State { type Item; /// Map a state from one type to another through reference projection + /// + /// This an be used to target a specific field of a struct or item in an array to transform. fn map_ref &U, G: Fn(&mut Self::Item) -> &mut U, U>( self, f: F, @@ -122,8 +124,18 @@ pub trait StateSink: State { } /// Allows sending and receiving a value to a state +/// +/// +/// This is the most common form of state and is used for both reading state updates, and sending +/// new state. +/// +/// Notably, this does not allow to directly read the state, as it may not always be available due +/// to filtered states. Instead, you can subscribe to changes and use [`WatchState`] to hold on to +/// the latest known state. pub trait StateDuplex: StateStream + StateSink {} +pub type DynStateDuplex = Box>; + impl StateDuplex for T where T: StateStream + StateSink {} impl State for Mutable { diff --git a/violet-core/src/style/mod.rs b/violet-core/src/style/mod.rs index 3db7190..c2d2925 100644 --- a/violet-core/src/style/mod.rs +++ b/violet-core/src/style/mod.rs @@ -15,8 +15,8 @@ use crate::{ }; use self::colors::{ - EERIE_BLACK_600, EERIE_BLACK_700, EERIE_BLACK_DEFAULT, JADE_400, JADE_600, JADE_DEFAULT, - LION_DEFAULT, PLATINUM_DEFAULT, REDWOOD_DEFAULT, + EERIE_BLACK_300, EERIE_BLACK_600, EERIE_BLACK_700, EERIE_BLACK_DEFAULT, JADE_400, JADE_600, + JADE_DEFAULT, LION_DEFAULT, PLATINUM_DEFAULT, REDWOOD_DEFAULT, }; #[macro_export] @@ -258,7 +258,7 @@ pub fn setup_stylesheet() -> EntityBuilder { // colors .set(primary_background(), EERIE_BLACK_DEFAULT) .set(primary_item(), PLATINUM_DEFAULT) - .set(secondary_background(), EERIE_BLACK_600) + .set(secondary_background(), EERIE_BLACK_300) .set(accent_background(), EERIE_BLACK_DEFAULT) .set(accent_item(), JADE_DEFAULT) .set(success_background(), EERIE_BLACK_DEFAULT) diff --git a/violet-core/src/systems.rs b/violet-core/src/systems.rs index 4526a90..b7d20b4 100644 --- a/violet-core/src/systems.rs +++ b/violet-core/src/systems.rs @@ -16,6 +16,7 @@ use flax::{ QueryBorrow, System, World, }; use glam::Vec2; +use tracing::info; use crate::{ components::{ @@ -180,7 +181,6 @@ pub fn layout_system() -> BoxedSystem { /// Updates the apparent screen position of entities based on the hierarchy pub fn transform_system() -> BoxedSystem { - puffin::profile_function!(); System::builder() .with_query( Query::new(( @@ -192,7 +192,6 @@ pub fn transform_system() -> BoxedSystem { .with_strategy(Dfs::new(child_of)), ) .build(|mut query: DfsBorrow<_>| { - puffin::profile_scope!("transform_system"); query.traverse( &Vec2::ZERO, |(pos, screen_rect, rect, local_pos): (&mut Vec2, &mut Rect, &Rect, &Vec2), diff --git a/violet-core/src/types.rs b/violet-core/src/types.rs index cf38e93..ef8e201 100644 --- a/violet-core/src/types.rs +++ b/violet-core/src/types.rs @@ -131,6 +131,12 @@ pub struct Rect { pub max: Vec2, } +impl Display for Rect { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!("({:?},{:?})", self.min, self.max)) + } +} + impl Rect { pub const ZERO: Self = Self { min: Vec2::ZERO, diff --git a/violet-core/src/unit.rs b/violet-core/src/unit.rs index 3b643f4..9fdc0e5 100644 --- a/violet-core/src/unit.rs +++ b/violet-core/src/unit.rs @@ -3,7 +3,7 @@ use std::{ ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}, }; -use glam::{IVec2, Vec2}; +use glam::{BVec2, IVec2, Vec2}; /// Represents a unit of measurement #[derive(Debug, Clone, Copy, PartialEq)] @@ -37,13 +37,6 @@ impl Unit { pub fn rel(rel: T) -> Self { Self { px: T::ZERO, rel } } - - pub(crate) fn is_fixed(&self) -> bool - where - T: PartialEq, - { - self.rel == T::ZERO - } } impl Unit { @@ -60,6 +53,10 @@ impl Unit { rel: Vec2::new(x, y), } } + + pub(crate) fn is_relative(&self) -> BVec2 { + self.rel.cmpgt(Vec2::ZERO) + } } impl Unit { diff --git a/violet-core/src/widget/container.rs b/violet-core/src/widget/container.rs index e31e859..7ef8743 100644 --- a/violet-core/src/widget/container.rs +++ b/violet-core/src/widget/container.rs @@ -8,7 +8,8 @@ use crate::{ layout::{Alignment, Direction, FlowLayout, Layout, StackLayout}, style::{ colors::{EERIE_BLACK_300, EERIE_BLACK_400}, - Background, SizeExt, StyleExt, WidgetSize, + primary_background, secondary_background, spacing_medium, spacing_small, Background, + SizeExt, StyleExt, WidgetSize, }, unit::Unit, Edges, Frame, Scope, Widget, WidgetCollection, @@ -246,15 +247,15 @@ pub fn centered(widget: W) -> Stack { pub fn card(widget: W) -> Stack { Stack::new(widget) // TODO: semantic color and sizing increment - .with_background(Background::new(EERIE_BLACK_400)) - .with_padding(Edges::even(4.0)) - .with_margin(Edges::even(4.0)) + .with_background(Background::new(secondary_background())) + .with_padding(spacing_medium()) + .with_margin(spacing_medium()) } -pub fn card2(widget: W) -> Stack { +pub fn pill(widget: W) -> Stack { Stack::new(widget) // TODO: semantic color and sizing increment - .with_background(Background::new(EERIE_BLACK_300)) - .with_padding(Edges::even(4.0)) - .with_margin(Edges::even(4.0)) + .with_background(Background::new(primary_background())) + .with_padding(spacing_small()) + .with_margin(spacing_small()) } diff --git a/violet-core/src/widget/interactive/input.rs b/violet-core/src/widget/interactive/input.rs index d071d37..66ad76c 100644 --- a/violet-core/src/widget/interactive/input.rs +++ b/violet-core/src/widget/interactive/input.rs @@ -15,9 +15,9 @@ use crate::{ components::{self, screen_rect}, editor::{CursorMove, EditAction, EditorAction, TextEditor}, input::{focus_sticky, focusable, on_focus, on_keyboard_input, on_mouse_input, KeyboardInput}, - state::{State, StateDuplex, StateSink, StateStream}, + state::{StateDuplex, StateSink, StateStream}, style::{ - colors::EERIE_BLACK_300, interactive_active, spacing_small, Background, SizeExt, StyleExt, + interactive_active, interactive_inactive, spacing_small, Background, SizeExt, StyleExt, ValueOrRef, WidgetSize, }, text::{LayoutGlyphs, TextSegment}, @@ -39,7 +39,7 @@ impl Default for TextInputStyle { fn default() -> Self { Self { cursor_color: interactive_active().into(), - background: Background::new(EERIE_BLACK_300), + background: Background::new(interactive_inactive()), font_size: 16.0, } } diff --git a/violet-demo/src/lib.rs b/violet-demo/src/lib.rs index e76c6c3..16c2a51 100644 --- a/violet-demo/src/lib.rs +++ b/violet-demo/src/lib.rs @@ -9,30 +9,22 @@ use tracing_subscriber::{ use tracing_web::{performance_layer, MakeWebConsoleWriter}; use violet::{ core::{ - components, - layout::{Alignment, Direction}, - state::{Map, MapRef, State, StateMut, StateStream, StateStreamRef}, - style::{ - colors::{ - EERIE_BLACK_400, EERIE_BLACK_DEFAULT, JADE_200, JADE_DEFAULT, LION_DEFAULT, - REDWOOD_DEFAULT, - }, - danger_item, success_item, Background, SizeExt, StyleExt, ValueOrRef, - }, - text::Wrap, + layout::Alignment, + state::{DynStateDuplex, State, StateMut, StateStream, StateStreamRef}, + style::{primary_background, Background, SizeExt, ValueOrRef}, to_owned, unit::Unit, - utils::{zip_latest, zip_latest_ref}, + utils::zip_latest_ref, widget::{ - card, column, label, row, Button, ButtonStyle, Checkbox, List, Rectangle, SignalWidget, - SliderWithLabel, Stack, StreamWidget, Text, WidgetExt, + card, column, label, pill, row, Button, Checkbox, Rectangle, SliderWithLabel, Stack, + StreamWidget, Text, TextInput, WidgetExt, }, - Edges, Scope, Widget, WidgetCollection, + Edges, Scope, Widget, }, - flax::components::name, - futures_signals::signal::{Mutable, SignalExt}, + futures_signals::signal::Mutable, glam::vec2, - palette::{FromColor, IntoColor, Oklch, Srgb, Srgba}, + palette::{FromColor, IntoColor, OklabHue, Oklch, Srgb}, + wgpu::renderer::RendererConfig, }; use wasm_bindgen::prelude::*; @@ -72,18 +64,33 @@ fn setup() { pub fn run() { setup(); - violet::wgpu::App::new().run(MainApp).unwrap(); + violet::wgpu::App::new() + .with_renderer_config(RendererConfig { debug_mode: true }) + .run(MainApp) + .unwrap(); } struct MainApp; +const DEFAULT_FALLOFF: f32 = 15.0; + impl Widget for MainApp { fn mount(self, scope: &mut Scope<'_>) { - let palette_item = Mutable::new(Vec::new()); + let palette_item = Mutable::new( + (0..8) + .map(|i| { + Mutable::new(PaletteColor { + color: Oklch::new(0.5, 0.27, (i as f32 * 60.0) % 360.0), + falloff: DEFAULT_FALLOFF, + }) + }) + .collect(), + ); - card((Palettes::new(palette_item),)) - .with_margin(Edges::even(4.0)) + column((Palettes::new(palette_item),)) .with_size(Unit::rel2(1.0, 1.0)) + .with_background(Background::new(primary_background())) + .contain_margins(true) .mount(scope); } } @@ -113,12 +120,12 @@ impl Widget for Tints { ..self.base }; - Stack::new(column(( - Rectangle::new(ValueOrRef::value(color.into_color())) - .with_min_size(Unit::px2(60.0, 60.0)), - // Text::new(format!("{:.2}", f)), - ))) + Stack::new(column((Rectangle::new(ValueOrRef::value( + color.into_color(), + )) + .with_size(Unit::px2(80.0, 60.0)),))) .with_margin(Edges::even(4.0)) + .with_name("Tint") }) .collect_vec()) .mount(scope) @@ -155,34 +162,40 @@ impl Widget for Palettes { .danger() }; - let current_choice = Mutable::new(None as Option); + let current_choice = Mutable::new(Some(0)); let editor = zip_latest_ref( self.items.stream(), current_choice.stream(), - |items, i: &Option| i.and_then(|i| items.get(i).cloned()).map(OklchEditor::new), + |items, i: &Option| { + i.and_then(|i| items.get(i).cloned()) + .map(PaletteEditor::new) + }, ); - let palettes = StreamWidget(self.items.stream_ref(move |items| { - let items = items - .iter() - .enumerate() - .map({ - to_owned![current_choice]; - let discard = &discard; - move |(i, item)| { - let checkbox = Checkbox::new( - current_choice - .clone() - .map(move |v| v == Some(i), move |state| state.then_some(i)), - ); - - card(row((checkbox, discard(i), StreamWidget(item.stream())))) - } - }) - .collect_vec(); - - column(items) + let palettes = StreamWidget(self.items.stream_ref({ + to_owned![current_choice]; + move |items| { + let items = items + .iter() + .enumerate() + .map({ + to_owned![current_choice]; + let discard = &discard; + move |(i, item)| { + let checkbox = Checkbox::new( + current_choice + .clone() + .map(move |v| v == Some(i), move |state| state.then_some(i)), + ); + + card(row((checkbox, discard(i), StreamWidget(item.stream())))) + } + }) + .collect_vec(); + + column(items) + } })); let items = self.items.clone(); @@ -191,7 +204,13 @@ impl Widget for Palettes { StreamWidget(editor), palettes, Button::label("+").on_press(move |_, _| { - items.write_mut(|v| v.push(Mutable::new(PaletteColor::default()))) + items.write_mut(|v| { + v.push(Mutable::new(PaletteColor { + color: Oklch::new(0.5, 0.27, (v.len() as f32 * 60.0) % 360.0), + falloff: DEFAULT_FALLOFF, + })); + current_choice.set(Some(v.len() - 1)); + }) }), )) .mount(scope) @@ -200,63 +219,51 @@ impl Widget for Palettes { #[derive(Debug, Clone, Copy)] pub struct PaletteColor { - color: Vec3, + color: Oklch, falloff: f32, } -impl Default for PaletteColor { - fn default() -> Self { - Self { - color: Vec3::new(0.5, 0.27, 153.0), - falloff: 10.0, - } - } -} - impl Widget for PaletteColor { fn mount(self, scope: &mut Scope<'_>) { - let oklch_color = Oklch::new(self.color.x, self.color.y, self.color.z); - column(( - row((Tints::new(oklch_color, self.falloff),)), - label(color_hex(oklch_color)), + Stack::new(( + row((Tints::new(self.color, self.falloff),)), + pill(label(color_hex(self.color))), )) - .with_cross_align(Alignment::Center) + .with_vertical_alignment(Alignment::End) + .with_horizontal_alignment(Alignment::Center) .mount(scope) } } -pub struct OklchEditor { +pub struct PaletteEditor { color: Mutable, } -impl OklchEditor { +impl PaletteEditor { pub fn new(color: Mutable) -> Self { Self { color } } } -impl Widget for OklchEditor { +impl Widget for PaletteEditor { fn mount(self, scope: &mut Scope<'_>) { let color = Arc::new(self.color.clone().map_ref(|v| &v.color, |v| &mut v.color)); let falloff = self.color.map_ref(|v| &v.falloff, |v| &mut v.falloff); - let color_oklch = Map::new( - color.clone(), - |v| Oklch::new(v.x, v.y, v.z), - |v| Vec3::new(v.l, v.chroma, v.hue.into_positive_degrees()), - ); - - let lightness = color.clone().map_ref(|v| &v.x, |v| &mut v.x); - let chroma = color.clone().map_ref(|v| &v.y, |v| &mut v.y); - let hue = color.clone().map_ref(|v| &v.z, |v| &mut v.z); + let lightness = color.clone().map_ref(|v| &v.l, |v| &mut v.l); + let chroma = color.clone().map_ref(|v| &v.chroma, |v| &mut v.chroma); + let hue = color + .clone() + .map_ref(|v| &v.hue, |v| &mut v.hue) + .map(|v| v.into_positive_degrees(), OklabHue::new); let color_rect = color.stream().map(|v| { - let color = Oklch::new(v.x, v.y, v.z).into_color(); - Rectangle::new(ValueOrRef::value(color)) + Rectangle::new(ValueOrRef::value(v.into_color())) .with_size(Unit::new(vec2(0.0, 100.0), vec2(1.0, 0.0))) + .with_name("ColorPreview") }); - column(( + card(column(( row(( Text::new("Lightness"), SliderWithLabel::new(lightness, 0.0, 1.0) @@ -275,14 +282,9 @@ impl Widget for OklchEditor { .editable(true) .round(1.0), )), - StreamWidget(color.stream_ref(|v| { - let hex: Srgb = Srgb::from_color(Oklch::new(v.x, v.y, v.z)).into_format(); - Text::new(format!( - "#{:0>2x}{:0>2x}{:0>2x}", - hex.red, hex.green, hex.blue - )) - })), - StreamWidget(color.stream().map(|v| Text::new(format!("{}", v)))), + ColorHexEditor { + color: Box::new(color.clone()), + }, StreamWidget(color_rect), row(( Text::new("Chroma falloff"), @@ -290,7 +292,28 @@ impl Widget for OklchEditor { .editable(true) .round(1.0), )), - )) + ))) + .with_name("PaletteEditor") .mount(scope) } } + +pub struct ColorHexEditor { + color: DynStateDuplex, +} + +impl Widget for ColorHexEditor { + fn mount(self, scope: &mut Scope<'_>) { + let value = self.color.prevent_feedback().filter_map( + |v| Some(color_hex(v)), + |v| { + let v: Srgb = v.trim().parse().ok()?; + + let v = Oklch::from_color(v.into_format()); + Some(v) + }, + ); + + TextInput::new(value).mount(scope) + } +} diff --git a/violet-wgpu/src/renderer/debug_renderer.rs b/violet-wgpu/src/renderer/debug_renderer.rs index 0619e5f..81653f3 100644 --- a/violet-wgpu/src/renderer/debug_renderer.rs +++ b/violet-wgpu/src/renderer/debug_renderer.rs @@ -213,11 +213,12 @@ impl DebugRenderer { self.objects.clear(); self.objects.extend(objects); - self.layout_changes.retain(|_, lifetime| { - *lifetime -= 1; + self.layout_changes.clear(); + // self.layout_changes.retain(|_, lifetime| { + // *lifetime -= 1; - *lifetime > 0 - }); + // *lifetime > 0 + // }); } pub fn draw_commands(&self) -> &[(DrawCommand, ObjectData)] { diff --git a/violet-wgpu/src/renderer/window_renderer.rs b/violet-wgpu/src/renderer/window_renderer.rs index cff3a4d..3b2fc71 100644 --- a/violet-wgpu/src/renderer/window_renderer.rs +++ b/violet-wgpu/src/renderer/window_renderer.rs @@ -4,6 +4,7 @@ use anyhow::Context; use flax::Entity; use glam::Mat4; use parking_lot::Mutex; +use puffin::profile_scope; use wgpu::{Operations, RenderPassDescriptor, StoreOp, SurfaceError}; use winit::dpi::PhysicalSize; @@ -110,8 +111,11 @@ impl WindowRenderer { .context("Failed to draw shapes")?; } - self.ctx.gpu.queue.submit([encoder.finish()]); - target.present(); + { + profile_scope!("submit"); + self.ctx.gpu.queue.submit([encoder.finish()]); + target.present(); + } Ok(()) } diff --git a/violet-wgpu/src/text.rs b/violet-wgpu/src/text.rs index 1fabb36..03e2bf8 100644 --- a/violet-wgpu/src/text.rs +++ b/violet-wgpu/src/text.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use cosmic_text::{ fontdb::Source, Attrs, Buffer, FontSystem, LayoutGlyph, Metrics, Shaping, SwashCache, }; -use glam::{vec2, Vec2}; +use glam::{vec2, BVec2, Vec2}; use itertools::Itertools; use palette::Srgba; use parking_lot::Mutex; @@ -73,8 +73,8 @@ impl SizeResolver for TextSizeResolver { let line_height = state.buffer.metrics().line_height; - // If preferred is clamped, so is min - let (min, _clamped) = Self::resolve_text_size( + // If preferred is can_grow, so is min + let (min, _can_grow) = Self::resolve_text_size( state, text_system, font_size, @@ -84,7 +84,7 @@ impl SizeResolver for TextSizeResolver { }, ); - let (preferred, clamped) = Self::resolve_text_size( + let (preferred, can_grow) = Self::resolve_text_size( state, text_system, font_size, @@ -99,8 +99,8 @@ impl SizeResolver for TextSizeResolver { min, preferred, SizingHints { - can_grow: clamped, - fixed_size: true, + can_grow, + relative_size: BVec2::TRUE, }, ) } @@ -110,7 +110,7 @@ impl SizeResolver for TextSizeResolver { entity: &flax::EntityRef, content_area: Vec2, limits: LayoutLimits, - ) -> (Vec2, bool) { + ) -> (Vec2, BVec2) { puffin::profile_scope!("TextSizeResolver::apply"); let _span = tracing::debug_span!("TextSizeResolver::apply", ?content_area).entered(); @@ -122,7 +122,7 @@ impl SizeResolver for TextSizeResolver { let text_system = &mut *self.text_system.lock(); let line_height = state.buffer.metrics().line_height; - let (size, clamped) = Self::resolve_text_size( + let (size, can_grow) = Self::resolve_text_size( state, text_system, font_size, @@ -135,7 +135,7 @@ impl SizeResolver for TextSizeResolver { // tracing::error!(%entity, text=?state.text(), %size, %limits.max_size, "Text overflowed"); } - (size, clamped) + (size, can_grow) } } @@ -149,7 +149,7 @@ impl TextSizeResolver { text_system: &mut TextSystem, font_size: f32, size: Vec2, - ) -> (Vec2, bool) { + ) -> (Vec2, BVec2) { // let _span = tracing::debug_span!("resolve_text_size", font_size, ?text, ?limits).entered(); let mut buffer = state.buffer.borrow_with(&mut text_system.font_system); @@ -168,7 +168,7 @@ fn glyph_bounds(glyph: &LayoutGlyph) -> (f32, f32) { (glyph.x, glyph.x + glyph.w) } -fn measure(buffer: &Buffer) -> (Vec2, bool) { +fn measure(buffer: &Buffer) -> (Vec2, BVec2) { let (width, total_lines) = buffer .layout_runs() @@ -193,7 +193,7 @@ fn measure(buffer: &Buffer) -> (Vec2, bool) { ( vec2(width, total_lines as f32 * buffer.metrics().line_height), - total_lines > buffer.lines.len(), + BVec2::new(total_lines > buffer.lines.len(), false), ) }