diff --git a/examples/basic.rs b/examples/basic.rs index 8a4b2f7..9db974c 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -88,5 +88,4 @@ fn app() -> impl Widget { })), ))), )) - .contain_margins(true) } diff --git a/examples/offset.rs b/examples/offset.rs new file mode 100644 index 0000000..4f4aa85 --- /dev/null +++ b/examples/offset.rs @@ -0,0 +1,37 @@ +use tracing_subscriber::{ + prelude::__tracing_subscriber_SubscriberExt, registry, util::SubscriberInitExt, EnvFilter, +}; +use tracing_tree::HierarchicalLayer; +use violet::core::{ + style::{colors::EMERALD_500, secondary_background, spacing_medium, Background, SizeExt}, + unit::Unit, + widget::{Positioned, Rectangle, Stack}, + Widget, +}; + +pub fn main() -> anyhow::Result<()> { + registry() + .with( + HierarchicalLayer::default() + .with_deferred_spans(true) + .with_span_retrace(true) + .with_indent_lines(true), + ) + .with(EnvFilter::from_default_env()) + .init(); + + violet_wgpu::AppBuilder::new().run(app()) +} + +fn app() -> impl Widget { + Stack::new( + Positioned::new( + Rectangle::new(EMERALD_500) + .with_min_size(Unit::px2(100.0, 100.0)) + .with_margin(spacing_medium()), + ) + .with_offset(Unit::px2(10.0, 10.0)), + ) + .with_padding(spacing_medium()) + .with_background(Background::new(secondary_background())) +} diff --git a/examples/scroll.rs b/examples/scroll.rs new file mode 100644 index 0000000..ca04c38 --- /dev/null +++ b/examples/scroll.rs @@ -0,0 +1,62 @@ +use flax::{components::name, FetchExt, Query}; +use futures::StreamExt; +use futures_signals::signal::Mutable; +use glam::{vec2, Vec2}; +use itertools::Itertools; +use palette::{FromColor, Hsva, IntoColor, Oklch, Oklcha, Srgba}; +use std::time::Duration; +use tracing_subscriber::{ + prelude::__tracing_subscriber_SubscriberExt, registry, util::SubscriberInitExt, EnvFilter, +}; +use tracing_tree::HierarchicalLayer; +use violet::core::{ + components::{self, rect, size, text}, + layout::{Alignment, Direction}, + style::StyleExt, + text::{FontFamily, Style, TextSegment, Weight, Wrap}, + time::interval, + unit::Unit, + widget::{Button, Image, List, Rectangle, Stack, Text, WidgetExt}, + Scope, StreamEffect, Widget, +}; +use violet_core::{ + state::{State, StateStream}, + style::{ + colors::{AMBER_500, EMERALD_500, TEAL_500}, + danger_background, danger_item, primary_background, secondary_background, spacing_medium, + spacing_small, Background, SizeExt, ValueOrRef, + }, + widget::{ + card, col, label, pill, row, ContainerStyle, SliderWithLabel, StreamWidget, TextInput, + }, +}; + +pub fn main() -> anyhow::Result<()> { + registry() + .with( + HierarchicalLayer::default() + .with_deferred_spans(true) + .with_span_retrace(true) + .with_indent_lines(true), + ) + .with(EnvFilter::from_default_env()) + .init(); + + violet_wgpu::AppBuilder::new().run(app()) +} + +fn app() -> impl Widget { + col((0..32) + .map(|v| { + Rectangle::new(Srgba::from_color(Oklcha::new( + 0.5, + 0.37, + v as f32 * 5.0, + 1.0, + ))) + .with_margin(spacing_small()) + .with_min_size(Unit::px2(100.0, 50.0)) + .with_maximize(Vec2::X) + }) + .collect_vec()) +} diff --git a/violet-core/src/components.rs b/violet-core/src/components.rs index 7f83022..b88e654 100644 --- a/violet-core/src/components.rs +++ b/violet-core/src/components.rs @@ -1,7 +1,7 @@ use std::time::Duration; use flax::{component, Debuggable, Entity, EntityRef, Exclusive}; -use glam::Vec2; +use glam::{Mat4, Vec2}; use image::DynamicImage; use palette::Srgba; @@ -21,19 +21,23 @@ component! { /// Defines the outer bounds of a widget relative to its position pub rect: Rect => [ Debuggable ], - pub screen_rect: Rect => [ Debuggable ], - /// Position relative to parent + /// Position relative to parent for layout position. pub local_position: Vec2 => [ Debuggable ], - /// Specifies in screen space where the widget rect upper left corner is - pub screen_position: Vec2 => [ Debuggable ], - pub rotation: f32 => [ Debuggable ], - /// Offset the widget from its original position + /// Offset the widget from its original position. + /// + /// This influences the layout bounds and the final position of the widget, and will move other + /// widgets around in flow layouts. pub offset: Unit => [ Debuggable ], + /// Optional transform of the widget. Applied after layout + pub transform: Mat4, + + pub screen_transform: Mat4, + /// Explicit widget size. This will override the intrinsic size of the widget. /// /// The final size may be smaller if there is not enough space. diff --git a/violet-core/src/input.rs b/violet-core/src/input.rs index 07e8914..cef9552 100644 --- a/violet-core/src/input.rs +++ b/violet-core/src/input.rs @@ -2,7 +2,7 @@ use flax::{ component, components::child_of, entity_ids, fetch::Satisfied, filter::All, Component, Entity, EntityIds, EntityRef, Fetch, FetchExt, Query, Topo, World, }; -use glam::Vec2; +use glam::{Mat4, Vec2, Vec3Swizzles}; /// NOTE: maybe redefine these types ourselves pub use winit::{event, keyboard}; @@ -12,7 +12,7 @@ use winit::{ }; use crate::{ - components::{rect, screen_position, screen_rect}, + components::{rect, screen_transform}, scope::ScopeRef, Frame, Rect, }; @@ -23,7 +23,7 @@ pub struct Input {} struct IntersectQuery { id: EntityIds, rect: Component, - screen_pos: Component, + screen_transform: Component, sticky: Satisfied>, focusable: Component<()>, } @@ -33,7 +33,7 @@ impl IntersectQuery { Self { id: entity_ids(), rect: rect(), - screen_pos: screen_position(), + screen_transform: screen_transform(), sticky: focus_sticky().satisfied(), focusable: focusable(), } @@ -71,9 +71,14 @@ impl InputState { .borrow(frame.world()) .iter() .filter_map(|item| { - let local_pos = cursor_pos - *item.screen_pos; + let local_pos = item + .screen_transform + .inverse() + .transform_point3(cursor_pos.extend(0.0)) + .xy(); + if item.rect.contains_point(local_pos) { - Some((item.id, (*item.screen_pos + item.rect.min))) + Some((item.id, local_pos)) } else { None } @@ -94,13 +99,13 @@ impl InputState { // Send the event to the intersected entity - if let Some((id, origin)) = intersect { + if let Some((id, local_pos)) = intersect { let entity = frame.world().entity(id).unwrap(); let cursor = CursorMove { modifiers: self.modifiers, absolute_pos: self.pos, - local_pos: self.pos - origin, + local_pos, }; if let Ok(mut on_input) = entity.get_mut(on_mouse_input()) { let s = ScopeRef::new(frame, entity); @@ -121,7 +126,7 @@ impl InputState { self.pos = pos; if let Some(entity) = &self.focused(&frame.world) { - let screen_rect = entity.get_copy(screen_rect()).unwrap_or_default(); + let transform = entity.get_copy(screen_transform()).unwrap_or_default(); if let Ok(mut on_input) = entity.get_mut(on_cursor_move()) { let s = ScopeRef::new(frame, *entity); on_input( @@ -129,7 +134,7 @@ impl InputState { CursorMove { modifiers: self.modifiers, absolute_pos: pos, - local_pos: pos - screen_rect.min, + local_pos: transform.inverse().transform_point3(pos.extend(0.0)).xy(), }, ); } @@ -206,6 +211,13 @@ pub struct CursorMove { pub local_pos: Vec2, } +#[derive(Debug, Clone)] +pub struct Scroll { + pub scroll_x: f32, + pub scroll_y: f32, + pub modifiers: ModifiersState, +} + pub struct KeyboardInput { pub modifiers: ModifiersState, @@ -221,4 +233,5 @@ component! { pub on_cursor_move: InputEventHandler, pub on_mouse_input: InputEventHandler, pub on_keyboard_input: InputEventHandler, + pub on_scroll: InputEventHandler, } diff --git a/violet-core/src/layout/mod.rs b/violet-core/src/layout/mod.rs index d3e80d2..7e15494 100644 --- a/violet-core/src/layout/mod.rs +++ b/violet-core/src/layout/mod.rs @@ -442,7 +442,7 @@ pub(crate) fn apply_layout( if let Some(value) = &cache.layout { if validate_cached_layout(value, limits, content_area, cache.hints.relative_size) { - tracing::debug!(%entity, %value.value.rect, %value.value.can_grow, "found valid cached layout"); + tracing::trace!(%entity, %value.value.rect, %value.value.can_grow, "found valid cached layout"); return value.value; } diff --git a/violet-core/src/layout/stack.rs b/violet-core/src/layout/stack.rs index a674fef..88f6c75 100644 --- a/violet-core/src/layout/stack.rs +++ b/violet-core/src/layout/stack.rs @@ -135,6 +135,7 @@ impl StackLayout { let mut can_grow = BVec2::FALSE; let offset = resolve_pos(entity, content_area.size(), size); + for (entity, block) in blocks { let block_size = block.rect.size(); let offset = content_area.min @@ -144,8 +145,6 @@ impl StackLayout { self.vertical_alignment.align_offset(size.y, block_size.y), ); - tracing::debug!(?offset, %entity); - aligned_bounds = aligned_bounds.merge(&StackableBounds::new( block.rect.translate(offset), block.margin, diff --git a/violet-core/src/style/mod.rs b/violet-core/src/style/mod.rs index 889db34..f0dd489 100644 --- a/violet-core/src/style/mod.rs +++ b/violet-core/src/style/mod.rs @@ -279,7 +279,7 @@ pub fn setup_stylesheet() -> EntityBuilder { .set(danger_item(), REDWOOD_400) .set(interactive_active(), EMERALD_500) .set(interactive_passive(), ZINC_800) - .set(interactive_hover(), EMERALD_800) + .set(interactive_hover(), EMERALD_400) .set(interactive_pressed(), EMERALD_500) .set(interactive_inactive(), ZINC_700) // spacing diff --git a/violet-core/src/systems.rs b/violet-core/src/systems.rs index 3f943c2..7126e08 100644 --- a/violet-core/src/systems.rs +++ b/violet-core/src/systems.rs @@ -1,7 +1,6 @@ use std::{ collections::HashSet, sync::{Arc, Weak}, - thread::scope, }; use atomic_refcell::AtomicRefCell; @@ -12,22 +11,20 @@ use flax::{ entity_ids, events::{EventData, EventSubscriber}, filter::Or, - query::TopoBorrow, BoxedSystem, CommandBuffer, Dfs, DfsBorrow, Entity, EntityBuilder, Fetch, FetchExt, FetchItem, - Query, QueryBorrow, System, Topo, World, + Query, QueryBorrow, System, World, }; -use glam::Vec2; +use glam::{Mat4, Vec2}; use crate::{ components::{ - self, children, layout_bounds, local_position, rect, screen_position, screen_rect, text, + self, children, layout_bounds, local_position, rect, screen_transform, text, transform, }, layout::{ apply_layout, cache::{invalidate_widget, layout_cache, LayoutCache, LayoutUpdate}, LayoutLimits, }, - Rect, }; pub fn hydrate_text() -> BoxedSystem { @@ -46,9 +43,9 @@ pub fn hydrate_text() -> BoxedSystem { pub fn widget_template(entity: &mut EntityBuilder, name: String) { entity .set(flax::components::name(), name) - .set_default(screen_position()) + .set_default(screen_transform()) + .set_default(transform()) .set_default(local_position()) - .set_default(screen_rect()) .set_default(rect()); } @@ -187,22 +184,19 @@ pub fn transform_system() -> BoxedSystem { System::builder() .with_query( Query::new(( - screen_position().as_mut(), - screen_rect().as_mut(), - rect(), + screen_transform().as_mut(), local_position(), + transform().opt_or_default(), )) .with_strategy(Dfs::new(child_of)), ) .build(|mut query: DfsBorrow<_>| { query.traverse( - &Vec2::ZERO, - |(pos, screen_rect, rect, local_pos): (&mut Vec2, &mut Rect, &Rect, &Vec2), - _, - parent_pos| { - *pos = *parent_pos + *local_pos; - *screen_rect = rect.translate(*pos); - *pos + &Mat4::IDENTITY, + |(screen_trans, local_pos, trans): (&mut Mat4, &Vec2, &Mat4), _, parent| { + *screen_trans = + *parent * *trans * Mat4::from_translation(local_pos.extend(0.0)); + *screen_trans }, ); }) diff --git a/violet-core/src/widget/interactive/input.rs b/violet-core/src/widget/interactive/input.rs index 48b880a..6b7c1d6 100644 --- a/violet-core/src/widget/interactive/input.rs +++ b/violet-core/src/widget/interactive/input.rs @@ -3,7 +3,7 @@ use std::{fmt::Display, future::ready, str::FromStr, sync::Arc}; use futures::StreamExt; use futures_signals::signal::{self, Mutable, SignalExt}; -use glam::{vec2, Vec2}; +use glam::{vec2, Mat4, Vec2, Vec3, Vec3Swizzles}; use itertools::Itertools; use palette::{Srgba, WithAlpha}; use web_time::Duration; @@ -13,7 +13,7 @@ use winit::{ }; use crate::{ - components::{self, screen_rect}, + components::{self, screen_transform}, editor::{CursorMove, EditAction, EditorAction, TextEditor}, input::{ focus_sticky, focusable, on_cursor_move, on_focus, on_keyboard_input, on_mouse_input, @@ -22,8 +22,8 @@ use crate::{ io, state::{State, StateDuplex, StateSink, StateStream}, style::{ - interactive_active, interactive_hover, interactive_inactive, interactive_passive, - spacing_small, Background, SizeExt, StyleExt, ValueOrRef, WidgetSize, + interactive_active, interactive_hover, interactive_passive, spacing_small, Background, + SizeExt, StyleExt, ValueOrRef, WidgetSize, }, text::{CursorLocation, LayoutGlyphs, TextSegment}, time::sleep, @@ -98,7 +98,7 @@ impl Widget for TextInput { .style .selection_color .resolve(stylesheet) - .with_alpha(0.2); + .with_alpha(0.5); let (tx, rx) = flume::unbounded(); @@ -110,7 +110,7 @@ impl Widget for TextInput { let mut editor = TextEditor::new(); let layout_glyphs = Mutable::new(None); - let text_bounds: Mutable> = Mutable::new(None); + let text_bounds: Mutable> = Mutable::new(None); editor.set_cursor_at_end(); @@ -271,7 +271,8 @@ impl Widget for TextInput { if let (Some(glyphs), Some(text_bounds)) = (&*glyphs, &*text_bounds.lock_ref()) { if input.state == ElementState::Pressed { - let text_pos = input.cursor.absolute_pos - text_bounds.min; + let text_pos = input.cursor.absolute_pos + - text_bounds.transform_point3(Vec3::ZERO).xy(); if let Some(hit) = glyphs.hit(text_pos) { dragging.set(Some(hit)); @@ -331,7 +332,7 @@ impl Widget for TextInput { Text::rich([TextSegment::new(v)]) .with_font_size(self.style.font_size) .monitor_signal(components::layout_glyphs(), layout_glyphs.clone()) - .monitor_signal(screen_rect(), text_bounds.clone()) + .monitor_signal(screen_transform(), text_bounds.clone()) })), Float::new(SignalWidget(editor_props_rx)), )) diff --git a/violet-core/src/widget/interactive/slider.rs b/violet-core/src/widget/interactive/slider.rs index 5ce72c6..a1b5adb 100644 --- a/violet-core/src/widget/interactive/slider.rs +++ b/violet-core/src/widget/interactive/slider.rs @@ -176,6 +176,7 @@ impl Widget for SliderHandle { })); Positioned::new(Rectangle::new(self.handle_color).with_min_size(self.handle_size)) + .with_offset(Unit::px2(100.0, 10.0)) .with_anchor(Unit::rel2(0.5, 0.0)) .mount(scope) } diff --git a/violet-core/src/widget/mod.rs b/violet-core/src/widget/mod.rs index c997a60..156da62 100644 --- a/violet-core/src/widget/mod.rs +++ b/violet-core/src/widget/mod.rs @@ -3,6 +3,7 @@ mod basic; mod container; mod future; mod interactive; +mod scroll; pub use basic::*; pub use container::*; diff --git a/violet-core/src/widget/scroll.rs b/violet-core/src/widget/scroll.rs new file mode 100644 index 0000000..0664a54 --- /dev/null +++ b/violet-core/src/widget/scroll.rs @@ -0,0 +1,63 @@ +use futures_signals::signal::Mutable; +use glam::{vec2, Mat4, Vec2}; + +use crate::{ + components::{rect, transform}, + input::on_scroll, + state::StateStream, + to_owned, Scope, Widget, +}; + +use super::Stack; + +pub struct Scroll { + items: W, +} + +impl Scroll { + pub fn new(items: W) -> Self { + Self { items } + } +} + +impl Widget for Scroll { + fn mount(self, scope: &mut Scope<'_>) { + let size = Mutable::new(Vec2::ZERO); + + let scroll_pos = Mutable::new(Vec2::ZERO); + scope.on_event(on_scroll(), { + to_owned![scroll_pos]; + move |scope, scroll| { + scroll_pos.set(vec2(scroll.scroll_x, scroll.scroll_y)); + } + }); + + Stack::new(ScrolledContent { + items: self.items, + scroll_pos, + size, + }) + .mount(scope) + } +} + +struct ScrolledContent { + items: W, + scroll_pos: Mutable, + size: Mutable, +} + +impl Widget for ScrolledContent { + fn mount(self, scope: &mut Scope<'_>) { + scope.monitor(rect(), move |v| { + if let Some(v) = v { + self.size.set(v.size()); + } + }); + scope.spawn_stream(self.scroll_pos.stream(), |scope, v| { + scope.set(transform(), Mat4::from_translation(-v.extend(0.0))); + }); + + Stack::new(self.items).mount(scope) + } +} diff --git a/violet-wgpu/src/app.rs b/violet-wgpu/src/app.rs index a1935b2..5e5d2b5 100644 --- a/violet-wgpu/src/app.rs +++ b/violet-wgpu/src/app.rs @@ -15,7 +15,7 @@ use winit::{ use violet_core::{ animation::update_animations, assets::AssetCache, - components::{self, local_position, rect, screen_position}, + components::{self, local_position, rect}, executor::Executor, input::InputState, io::{self, Clipboard}, @@ -46,16 +46,7 @@ impl Widget for Canvas { fn mount(self, scope: &mut Scope<'_>) { scope .set(name(), "Canvas".into()) - .set(stylesheet(self.stylesheet), ()) - .set( - rect(), - Rect { - min: Vec2::ZERO, - max: self.size, - }, - ) - .set_default(screen_position()) - .set_default(local_position()); + .set(stylesheet(self.stylesheet), ()); col(self.root) .contain_margins(true) @@ -217,7 +208,7 @@ impl AppBuilder { .borrow(&instance.frame.world) .iter() .count(); - tracing::info!(archetype_count = archetypes.len(), entity_count, pruned); + tracing::debug!(archetype_count = archetypes.len(), entity_count, pruned); // let report = instance.?stats.report(); // window.set_title(&format!( diff --git a/violet-wgpu/src/renderer/debug_renderer.rs b/violet-wgpu/src/renderer/debug_renderer.rs index 9cb7dfc..b00d1c5 100644 --- a/violet-wgpu/src/renderer/debug_renderer.rs +++ b/violet-wgpu/src/renderer/debug_renderer.rs @@ -6,11 +6,8 @@ use image::DynamicImage; use itertools::Itertools; use violet_core::{ assets::Asset, - components::screen_rect, - layout::{ - cache::{layout_cache, LayoutUpdate}, - Direction, - }, + components::{rect, transform}, + layout::cache::{layout_cache, LayoutUpdate}, stored::{self, Handle}, Frame, }; @@ -201,13 +198,15 @@ impl DebugRenderer { }); let objects = objects.filter_map(|(entity, shader, color)| { - let screen_rect = entity.get(screen_rect()).ok()?.align_to_grid(); - - let model_matrix = Mat4::from_scale_rotation_translation( - screen_rect.size().extend(1.0), - Quat::IDENTITY, - screen_rect.pos().extend(0.2), - ); + let rect = entity.get_copy(rect()).ok()?.align_to_grid(); + let transform = entity.get_copy(transform()).ok()?; + + let model_matrix = transform * Mat4::from_scale(rect.size().extend(1.0)); + // let model_matrix = Mat4::from_scale_rotation_translation( + // screen_rect.size().extend(1.0), + // Quat::IDENTITY, + // screen_rect.pos().extend(0.2), + // ); let object_data = ObjectData { model_matrix, diff --git a/violet-wgpu/src/renderer/rect_renderer.rs b/violet-wgpu/src/renderer/rect_renderer.rs index 915eafa..bfe9b27 100644 --- a/violet-wgpu/src/renderer/rect_renderer.rs +++ b/violet-wgpu/src/renderer/rect_renderer.rs @@ -5,14 +5,14 @@ use flax::{ filter::{All, With}, CommandBuffer, Component, EntityIds, Fetch, FetchExt, Mutable, Opt, OptOr, Query, }; -use glam::{vec2, vec3, Mat4, Quat, Vec2, Vec4}; +use glam::{vec2, vec3, Mat4, Quat, Vec2, Vec3Swizzles, Vec4}; use image::{DynamicImage, ImageBuffer}; use palette::Srgba; use wgpu::{BindGroup, BindGroupLayout, SamplerDescriptor, ShaderStages, TextureFormat}; use violet_core::{ assets::{map::HandleMap, Asset, AssetCache, AssetKey}, - components::{anchor, color, draw_shape, image, rotation, screen_rect}, + components::{anchor, color, draw_shape, image, rect, rotation, screen_transform}, shape::{self, shape_rectangle}, stored::{self, WeakHandle}, unit::Unit, @@ -63,8 +63,10 @@ impl AssetKey for ImageFromColor { #[derive(Fetch)] struct RectObjectQuery { - screen_rect: Component, - rotation: OptOr, f32>, + transform: Component, + rect: Component, + // screen_rect: Component, + // rotation: OptOr, f32>, anchor: OptOr>, Unit>, // pos: Component, // local_pos: Component, @@ -75,8 +77,10 @@ struct RectObjectQuery { impl RectObjectQuery { fn new() -> Self { Self { - screen_rect: screen_rect(), - rotation: rotation().opt_or(0.0), + // screen_rect: screen_rect(), + // rotation: rotation().opt_or(0.0), + rect: rect(), + transform: screen_transform(), anchor: anchor().opt_or_default(), object_data: object_data().as_mut(), color: color().opt_or(Srgba::new(1.0, 1.0, 1.0, 1.0)), @@ -228,20 +232,19 @@ impl RectRenderer { .borrow(&frame.world) .iter() .for_each(|item| { - tracing::debug!(color=%srgba_to_vec4(*item.color), ?item.screen_rect); - let rect = item.screen_rect.align_to_grid(); + let rect = item.rect.align_to_grid(); // if rect.size().x < 0.01 || rect.size().y < 0.01 { // tracing::warn!("rect too small to render"); // return; // } - let anchor = item.anchor.resolve(rect.size()).extend(0.0); - - let model_matrix = Mat4::from_translation(rect.pos().extend(0.1) + anchor) - * Mat4::from_rotation_z(*item.rotation) - * Mat4::from_translation(-anchor) - * Mat4::from_scale(rect.size().extend(1.0)); + let model_matrix = *item.transform + * Mat4::from_scale_rotation_translation( + rect.size().extend(1.0), + Quat::IDENTITY, + rect.pos().extend(0.0), + ); *item.object_data = ObjectData { model_matrix, diff --git a/violet-wgpu/src/renderer/text_renderer.rs b/violet-wgpu/src/renderer/text_renderer.rs index 09701d4..c0d2e25 100644 --- a/violet-wgpu/src/renderer/text_renderer.rs +++ b/violet-wgpu/src/renderer/text_renderer.rs @@ -16,7 +16,7 @@ use wgpu::{BindGroup, BindGroupLayout, Sampler, SamplerDescriptor, ShaderStages, use violet_core::{ assets::AssetCache, components::{ - color, draw_shape, font_size, layout_bounds, layout_glyphs, rect, screen_position, text, + color, draw_shape, font_size, layout_bounds, layout_glyphs, rect, screen_transform, text, }, shape::shape_text, stored::{self, Handle}, @@ -43,7 +43,7 @@ use super::{DrawCommand, ObjectData, RendererStore}; struct ObjectQuery { draw_shape: With, rect: Component, - pos: Component, + transform: Component, object_data: Mutable, color: OptOr, Srgba>, } @@ -53,7 +53,7 @@ impl ObjectQuery { Self { draw_shape: draw_shape(shape_text()).with(), rect: rect(), - pos: screen_position(), + transform: screen_transform(), object_data: object_data().as_mut(), color: color().opt_or(Srgba::new(1.0, 1.0, 1.0, 1.0)), } @@ -409,12 +409,8 @@ impl TextRenderer { .borrow(&frame.world) .iter() .for_each(|item| { - let rect = item.rect.translate(*item.pos).align_to_grid(); - let model_matrix = Mat4::from_scale_rotation_translation( - Vec3::ONE, - Quat::IDENTITY, - rect.pos().extend(0.1), - ); + let rect = item.rect.align_to_grid(); + let model_matrix = *item.transform; *item.object_data = ObjectData { model_matrix,