From 6f7d0e57252801c0fecd334cd694d6626d99abdf Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Sun, 13 Oct 2024 18:06:22 +0100 Subject: [PATCH] split up `TextStyle` (#15857) # Objective Currently text is recomputed unnecessarily on any changes to its color, which is extremely expensive. ## Solution Split up `TextStyle` into two separate components `TextFont` and `TextColor`. ## Testing I added this system to `many_buttons`: ```rust fn set_text_colors_changed(mut colors: Query<&mut TextColor>) { for mut text_color in colors.iter_mut() { text_color.set_changed(); } } ``` reports ~4fps on main, ~50fps with this PR. ## Migration Guide `TextStyle` has been renamed to `TextFont` and its `color` field has been moved to a separate component named `TextColor` which newtypes `Color`. --- crates/bevy_dev_tools/src/fps_overlay.rs | 16 +- crates/bevy_text/src/font_atlas_set.rs | 4 +- crates/bevy_text/src/lib.rs | 4 +- crates/bevy_text/src/pipeline.rs | 68 ++++--- crates/bevy_text/src/text.rs | 118 +++++++----- crates/bevy_text/src/text2d.rs | 73 +++---- crates/bevy_text/src/text_access.rs | 178 +++++++++++++----- crates/bevy_ui/src/accessibility.rs | 4 +- crates/bevy_ui/src/render/mod.rs | 6 +- crates/bevy_ui/src/ui_node.rs | 2 +- crates/bevy_ui/src/widget/text.rs | 74 ++++---- errors/B0005.md | 2 +- examples/2d/sprite_slice.rs | 4 +- examples/2d/text2d.rs | 22 +-- examples/2d/texture_atlas.rs | 6 +- examples/3d/auto_exposure.rs | 2 +- examples/3d/blend_modes.rs | 12 +- examples/3d/color_grading.rs | 10 +- examples/3d/load_gltf_extras.rs | 2 +- examples/3d/pbr.rs | 6 +- examples/3d/tonemapping.rs | 4 +- examples/animation/animated_ui.rs | 10 +- examples/animation/animation_events.rs | 18 +- examples/animation/animation_graph.rs | 4 +- examples/animation/animation_masks.rs | 29 ++- examples/animation/easing_functions.rs | 8 +- examples/asset/multi_asset_sync.rs | 4 +- .../external_source_external_thread.rs | 3 - examples/dev_tools/fps_overlay.rs | 12 +- examples/ecs/observers.rs | 4 - examples/ecs/one_shot_systems.rs | 5 +- examples/games/alien_cake_addict.rs | 8 +- examples/games/breakout.rs | 8 +- examples/games/contributors.rs | 6 +- examples/games/desk_toy.rs | 2 +- examples/games/game_menu.rs | 77 +++++--- examples/games/loading_screen.rs | 4 +- examples/games/stepping.rs | 20 +- examples/helpers/widgets.rs | 10 +- examples/input/text_input.rs | 6 +- examples/math/cubic_splines.rs | 2 +- examples/mobile/src/lib.rs | 4 +- .../movement/physics_in_fixed_timestep.rs | 2 +- examples/picking/simple_picking.rs | 12 +- examples/scene/scene.rs | 2 +- examples/shader/shader_prepass.rs | 4 +- examples/state/computed_states.rs | 40 ++-- examples/state/custom_transitions.rs | 4 +- examples/state/states.rs | 4 +- examples/state/sub_states.rs | 8 +- examples/stress_tests/bevymark.rs | 43 +++-- examples/stress_tests/many_buttons.rs | 13 +- examples/stress_tests/many_glyphs.rs | 9 +- examples/stress_tests/text_pipeline.rs | 8 +- examples/time/virtual_time.rs | 10 +- examples/tools/gamepad_viewer.rs | 4 +- .../tools/scene_viewer/morph_viewer_plugin.rs | 4 +- examples/ui/borders.rs | 8 +- examples/ui/button.rs | 4 +- examples/ui/display_and_visibility.rs | 42 +++-- examples/ui/flex_layout.rs | 7 +- examples/ui/font_atlas_debug.rs | 4 +- examples/ui/ghost_nodes.rs | 6 +- examples/ui/grid.rs | 11 +- examples/ui/overflow.rs | 2 +- examples/ui/overflow_debug.rs | 8 +- examples/ui/relative_cursor_position.rs | 10 +- examples/ui/render_ui_to_texture.rs | 4 +- examples/ui/scroll.rs | 16 +- examples/ui/size_constraints.rs | 62 +++--- examples/ui/text.rs | 42 +++-- examples/ui/text_debug.rs | 56 +++--- examples/ui/text_wrap_debug.rs | 4 +- examples/ui/transparency_ui.rs | 12 +- examples/ui/ui.rs | 10 +- examples/ui/ui_scaling.rs | 5 +- examples/ui/ui_texture_atlas.rs | 14 +- examples/ui/ui_texture_atlas_slice.rs | 4 +- examples/ui/ui_texture_slice.rs | 4 +- examples/ui/window_fallthrough.rs | 2 +- examples/window/low_power.rs | 24 +-- examples/window/scale_factor_override.rs | 2 +- examples/window/window_resizing.rs | 2 +- 83 files changed, 752 insertions(+), 641 deletions(-) diff --git a/crates/bevy_dev_tools/src/fps_overlay.rs b/crates/bevy_dev_tools/src/fps_overlay.rs index fc8b7c8516..c846fff492 100644 --- a/crates/bevy_dev_tools/src/fps_overlay.rs +++ b/crates/bevy_dev_tools/src/fps_overlay.rs @@ -14,7 +14,7 @@ use bevy_ecs::{ }; use bevy_hierarchy::{BuildChildren, ChildBuild}; use bevy_render::view::Visibility; -use bevy_text::{Font, TextSpan, TextStyle}; +use bevy_text::{Font, TextColor, TextFont, TextSpan}; use bevy_ui::{ node_bundles::NodeBundle, widget::{Text, UiTextWriter}, @@ -62,7 +62,9 @@ impl Plugin for FpsOverlayPlugin { #[derive(Resource, Clone)] pub struct FpsOverlayConfig { /// Configuration of text in the overlay. - pub text_config: TextStyle, + pub text_config: TextFont, + /// Color of text in the overlay. + pub text_color: Color, /// Displays the FPS overlay if true. pub enabled: bool, } @@ -70,12 +72,12 @@ pub struct FpsOverlayConfig { impl Default for FpsOverlayConfig { fn default() -> Self { FpsOverlayConfig { - text_config: TextStyle { + text_config: TextFont { font: Handle::::default(), font_size: 32.0, - color: Color::WHITE, ..default() }, + text_color: Color::WHITE, enabled: true, } } @@ -102,6 +104,7 @@ fn setup(mut commands: Commands, overlay_config: Res) { p.spawn(( Text::new("FPS: "), overlay_config.text_config.clone(), + TextColor(overlay_config.text_color), FpsText, )) .with_child((TextSpan::default(), overlay_config.text_config.clone())); @@ -128,9 +131,10 @@ fn customize_text( mut writer: UiTextWriter, ) { for entity in &query { - writer.for_each_style(entity, |mut style| { - *style = overlay_config.text_config.clone(); + writer.for_each_font(entity, |mut font| { + *font = overlay_config.text_config.clone(); }); + writer.for_each_color(entity, |mut color| color.0 = overlay_config.text_color); } } diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index 3092c9c008..3be8cf74b7 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -60,9 +60,9 @@ pub struct FontAtlasKey(pub u32, pub FontSmoothing); /// A `FontAtlasSet` is an [`Asset`]. /// /// There is one `FontAtlasSet` for each font: -/// - When a [`Font`] is loaded as an asset and then used in [`TextStyle`](crate::TextStyle), +/// - When a [`Font`] is loaded as an asset and then used in [`TextFont`](crate::TextFont), /// a `FontAtlasSet` asset is created from a weak handle to the `Font`. -/// - ~When a font is loaded as a system font, and then used in [`TextStyle`](crate::TextStyle), +/// - ~When a font is loaded as a system font, and then used in [`TextFont`](crate::TextFont), /// a `FontAtlasSet` asset is created and stored with a strong handle to the `FontAtlasSet`.~ /// (*Note that system fonts are not currently supported by the `TextPipeline`.*) /// diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index f367b130fe..da68e5aecb 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -65,8 +65,8 @@ pub use text_access::*; pub mod prelude { #[doc(hidden)] pub use crate::{ - Font, JustifyText, LineBreak, Text2d, TextError, TextLayout, TextReader2d, TextSpan, - TextStyle, TextWriter2d, + Font, JustifyText, LineBreak, Text2d, TextColor, TextError, TextFont, TextLayout, + TextReader2d, TextSpan, TextWriter2d, }; } diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index 57ced919b1..6f29af8895 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -1,6 +1,7 @@ use alloc::sync::Arc; use bevy_asset::{AssetId, Assets}; +use bevy_color::Color; use bevy_ecs::{ component::Component, entity::Entity, @@ -17,7 +18,7 @@ use cosmic_text::{Attrs, Buffer, Family, Metrics, Shaping, Wrap}; use crate::{ error::TextError, ComputedTextBlock, Font, FontAtlasSets, FontSmoothing, JustifyText, - LineBreak, PositionedGlyph, TextBounds, TextEntity, TextLayout, TextStyle, YAxisOrientation, + LineBreak, PositionedGlyph, TextBounds, TextEntity, TextFont, TextLayout, YAxisOrientation, }; /// A wrapper resource around a [`cosmic_text::FontSystem`] @@ -70,7 +71,7 @@ pub struct TextPipeline { /// Buffered vec for collecting spans. /// /// See [this dark magic](https://users.rust-lang.org/t/how-to-cache-a-vectors-capacity/94478/10). - spans_buffer: Vec<(usize, &'static str, &'static TextStyle, FontFaceInfo)>, + spans_buffer: Vec<(usize, &'static str, &'static TextFont, FontFaceInfo)>, /// Buffered vec for collecting info for glyph assembly. glyph_info: Vec<(AssetId, FontSmoothing)>, } @@ -83,7 +84,7 @@ impl TextPipeline { pub fn update_buffer<'a>( &mut self, fonts: &Assets, - text_spans: impl Iterator, + text_spans: impl Iterator, linebreak: LineBreak, justify: JustifyText, bounds: TextBounds, @@ -96,22 +97,22 @@ impl TextPipeline { // Collect span information into a vec. This is necessary because font loading requires mut access // to FontSystem, which the cosmic-text Buffer also needs. let mut font_size: f32 = 0.; - let mut spans: Vec<(usize, &str, &TextStyle, FontFaceInfo)> = + let mut spans: Vec<(usize, &str, &TextFont, FontFaceInfo, Color)> = core::mem::take(&mut self.spans_buffer) .into_iter() - .map(|_| -> (usize, &str, &TextStyle, FontFaceInfo) { unreachable!() }) + .map(|_| -> (usize, &str, &TextFont, FontFaceInfo, Color) { unreachable!() }) .collect(); computed.entities.clear(); - for (span_index, (entity, depth, span, style)) in text_spans.enumerate() { + for (span_index, (entity, depth, span, text_font, color)) in text_spans.enumerate() { // Return early if a font is not loaded yet. - if !fonts.contains(style.font.id()) { + if !fonts.contains(text_font.font.id()) { spans.clear(); self.spans_buffer = spans .into_iter() .map( - |_| -> (usize, &'static str, &'static TextStyle, FontFaceInfo) { + |_| -> (usize, &'static str, &'static TextFont, FontFaceInfo) { unreachable!() }, ) @@ -124,17 +125,21 @@ impl TextPipeline { computed.entities.push(TextEntity { entity, depth }); // Get max font size for use in cosmic Metrics. - font_size = font_size.max(style.font_size); + font_size = font_size.max(text_font.font_size); // Load Bevy fonts into cosmic-text's font system. - let face_info = - load_font_to_fontdb(style, font_system, &mut self.map_handle_to_font_id, fonts); + let face_info = load_font_to_fontdb( + text_font, + font_system, + &mut self.map_handle_to_font_id, + fonts, + ); // Save spans that aren't zero-sized. - if scale_factor <= 0.0 || style.font_size <= 0.0 { + if scale_factor <= 0.0 || text_font.font_size <= 0.0 { continue; } - spans.push((span_index, span, style, face_info)); + spans.push((span_index, span, text_font, face_info, color)); } let line_height = font_size * 1.2; @@ -151,12 +156,14 @@ impl TextPipeline { // The section index is stored in the metadata of the spans, and could be used // to look up the section the span came from and is not used internally // in cosmic-text. - let spans_iter = spans.iter().map(|(span_index, span, style, font_info)| { - ( - *span, - get_attrs(*span_index, style, font_info, scale_factor), - ) - }); + let spans_iter = spans + .iter() + .map(|(span_index, span, text_font, font_info, color)| { + ( + *span, + get_attrs(*span_index, text_font, *color, font_info, scale_factor), + ) + }); // Update the buffer. let buffer = &mut computed.buffer; @@ -186,7 +193,7 @@ impl TextPipeline { spans.clear(); self.spans_buffer = spans .into_iter() - .map(|_| -> (usize, &'static str, &'static TextStyle, FontFaceInfo) { unreachable!() }) + .map(|_| -> (usize, &'static str, &'static TextFont, FontFaceInfo) { unreachable!() }) .collect(); Ok(()) @@ -201,7 +208,7 @@ impl TextPipeline { &mut self, layout_info: &mut TextLayoutInfo, fonts: &Assets, - text_spans: impl Iterator, + text_spans: impl Iterator, scale_factor: f64, layout: &TextLayout, bounds: TextBounds, @@ -222,8 +229,8 @@ impl TextPipeline { // Extract font ids from the iterator while traversing it. let mut glyph_info = core::mem::take(&mut self.glyph_info); glyph_info.clear(); - let text_spans = text_spans.inspect(|(_, _, _, style)| { - glyph_info.push((style.font.id(), style.font_smoothing)); + let text_spans = text_spans.inspect(|(_, _, _, text_font, _)| { + glyph_info.push((text_font.font.id(), text_font.font_smoothing)); }); let update_result = self.update_buffer( @@ -335,7 +342,7 @@ impl TextPipeline { &mut self, entity: Entity, fonts: &Assets, - text_spans: impl Iterator, + text_spans: impl Iterator, scale_factor: f64, layout: &TextLayout, computed: &mut ComputedTextBlock, @@ -427,12 +434,12 @@ impl TextMeasureInfo { } fn load_font_to_fontdb( - style: &TextStyle, + text_font: &TextFont, font_system: &mut cosmic_text::FontSystem, map_handle_to_font_id: &mut HashMap, (cosmic_text::fontdb::ID, Arc)>, fonts: &Assets, ) -> FontFaceInfo { - let font_handle = style.font.clone(); + let font_handle = text_font.font.clone(); let (face_id, family_name) = map_handle_to_font_id .entry(font_handle.id()) .or_insert_with(|| { @@ -461,10 +468,11 @@ fn load_font_to_fontdb( } } -/// Translates [`TextStyle`] to [`Attrs`]. +/// Translates [`TextFont`] to [`Attrs`]. fn get_attrs<'a>( span_index: usize, - style: &TextStyle, + text_font: &TextFont, + color: Color, face_info: &'a FontFaceInfo, scale_factor: f64, ) -> Attrs<'a> { @@ -474,8 +482,8 @@ fn get_attrs<'a>( .stretch(face_info.stretch) .style(face_info.style) .weight(face_info.weight) - .metrics(Metrics::relative(style.font_size, 1.2).scale(scale_factor as f32)) - .color(cosmic_text::Color(style.color.to_linear().as_u32())); + .metrics(Metrics::relative(text_font.font_size, 1.2).scale(scale_factor as f32)) + .color(cosmic_text::Color(color.to_linear().as_u32())); attrs } diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index 97a2cbe587..8543daea3d 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -58,11 +58,11 @@ pub struct ComputedTextBlock { /// /// Includes: /// - [`TextLayout`] changes. - /// - [`TextStyle`] or `Text2d`/`Text`/`TextSpan` changes anywhere in the block's entity hierarchy. + /// - [`TextFont`] or `Text2d`/`Text`/`TextSpan` changes anywhere in the block's entity hierarchy. // TODO: This encompasses both structural changes like font size or justification and non-structural // changes like text color and font smoothing. This field currently causes UI to 'remeasure' text, even if // the actual changes are non-structural and can be handled by only rerendering and not remeasuring. A full - // solution would probably require splitting TextLayout and TextStyle into structural/non-structural + // solution would probably require splitting TextLayout and TextFont into structural/non-structural // components for more granular change detection. A cost/benefit analysis is needed. pub(crate) needs_rerender: bool, } @@ -70,7 +70,7 @@ pub struct ComputedTextBlock { impl ComputedTextBlock { /// Accesses entities in this block. /// - /// Can be used to look up [`TextStyle`] components for glyphs in [`TextLayoutInfo`] using the `span_index` + /// Can be used to look up [`TextFont`] components for glyphs in [`TextLayoutInfo`] using the `span_index` /// stored there. pub fn entities(&self) -> &[TextEntity] { &self.entities @@ -97,7 +97,7 @@ impl Default for ComputedTextBlock { /// Component with text format settings for a block of text. /// -/// A block of text is composed of text spans, which each have a separate string value and [`TextStyle`]. Text +/// A block of text is composed of text spans, which each have a separate string value and [`TextFont`]. Text /// spans associated with a text block are collected into [`ComputedTextBlock`] for layout, and then inserted /// to [`TextLayoutInfo`] for rendering. /// @@ -159,38 +159,39 @@ impl TextLayout { /// /// Spans are collected in hierarchy traversal order into a [`ComputedTextBlock`] for layout. /// -/* -``` -# use bevy_asset::Handle; -# use bevy_color::Color; -# use bevy_color::palettes::basic::{RED, BLUE}; -# use bevy_ecs::World; -# use bevy_text::{Font, TextLayout, TextStyle, TextSection}; - -# let font_handle: Handle = Default::default(); -# let mut world = World::default(); -# -world.spawn(( - TextLayout::default(), - TextStyle { - font: font_handle.clone().into(), - font_size: 60.0, - color: BLUE.into(), - } -)) -.with_child(( - TextSpan::new("Hello!"), - TextStyle { - font: font_handle.into(), - font_size: 60.0, - color: RED.into(), - } -)); -``` -*/ +/// ``` +/// # use bevy_asset::Handle; +/// # use bevy_color::Color; +/// # use bevy_color::palettes::basic::{RED, BLUE}; +/// # use bevy_ecs::world::World; +/// # use bevy_text::{Font, TextLayout, TextFont, TextSpan, TextColor}; +/// # use bevy_hierarchy::BuildChildren; +/// +/// # let font_handle: Handle = Default::default(); +/// # let mut world = World::default(); +/// # +/// world.spawn(( +/// TextLayout::default(), +/// TextFont { +/// font: font_handle.clone().into(), +/// font_size: 60.0, +/// ..Default::default() +/// }, +/// TextColor(BLUE.into()), +/// )) +/// .with_child(( +/// TextSpan::new("Hello!"), +/// TextFont { +/// font: font_handle.into(), +/// font_size: 60.0, +/// ..Default::default() +/// }, +/// TextColor(RED.into()), +/// )); +/// ``` #[derive(Component, Debug, Default, Clone, Deref, DerefMut, Reflect)] #[reflect(Component, Default, Debug)] -#[require(TextStyle)] +#[require(TextFont, TextColor)] pub struct TextSpan(pub String); impl TextSpan { @@ -259,11 +260,11 @@ impl From for cosmic_text::Align { } } -/// `TextStyle` determines the style of a text span within a [`ComputedTextBlock`], specifically +/// `TextFont` determines the style of a text span within a [`ComputedTextBlock`], specifically /// the font face, the font size, and the color. #[derive(Component, Clone, Debug, Reflect)] #[reflect(Component, Default, Debug)] -pub struct TextStyle { +pub struct TextFont { /// The specific font face to use, as a `Handle` to a [`Font`] asset. /// /// If the `font` is not specified, then @@ -280,31 +281,52 @@ pub struct TextStyle { /// A new font atlas is generated for every combination of font handle and scaled font size /// which can have a strong performance impact. pub font_size: f32, - /// The color of the text for this section. - pub color: Color, /// The antialiasing method to use when rendering text. pub font_smoothing: FontSmoothing, } -impl TextStyle { - /// Returns this [`TextStyle`] with the specified [`FontSmoothing`]. +impl TextFont { + /// Returns this [`TextFont`] with the specified [`FontSmoothing`]. pub const fn with_font_smoothing(mut self, font_smoothing: FontSmoothing) -> Self { self.font_smoothing = font_smoothing; self } } -impl Default for TextStyle { +impl Default for TextFont { fn default() -> Self { Self { font: Default::default(), font_size: 20.0, - color: Color::WHITE, font_smoothing: Default::default(), } } } +/// The color of the text for this section. +#[derive(Component, Copy, Clone, Debug, Deref, DerefMut, Reflect)] +#[reflect(Component, Default, Debug)] +pub struct TextColor(pub Color); + +impl Default for TextColor { + fn default() -> Self { + Self::WHITE + } +} + +impl> From for TextColor { + fn from(color: T) -> Self { + Self(color.into()) + } +} + +impl TextColor { + /// Black colored text + pub const BLACK: Self = TextColor(Color::BLACK); + /// White colored text + pub const WHITE: Self = TextColor(Color::WHITE); +} + /// Determines how lines will be broken when preventing text from running out of bounds. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Reflect, Serialize, Deserialize)] #[reflect(Serialize, Deserialize)] @@ -359,12 +381,12 @@ pub fn detect_text_needs_rerender( ( Or<( Changed, - Changed, + Changed, Changed, Changed, )>, With, - With, + With, With, ), >, @@ -373,13 +395,13 @@ pub fn detect_text_needs_rerender( ( Or<( Changed, - Changed, + Changed, Changed, Changed, // Included to detect broken text block hierarchies. Added, )>, With, - With, + With, ), >, mut computed: Query<( @@ -390,7 +412,7 @@ pub fn detect_text_needs_rerender( ) { // Root entity: // - Root component changed. - // - TextStyle on root changed. + // - TextFont on root changed. // - TextLayout changed. // - Root children changed (can include additions and removals). for root in changed_roots.iter() { @@ -404,7 +426,7 @@ pub fn detect_text_needs_rerender( // Span entity: // - Span component changed. - // - Span TextStyle changed. + // - Span TextFont changed. // - Span children changed (can include additions and removals). for (entity, maybe_span_parent, has_text_block) in changed_spans.iter() { if has_text_block { diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 03fa387e05..ad4eacd25b 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -1,8 +1,8 @@ use crate::pipeline::CosmicFontSystem; use crate::{ ComputedTextBlock, Font, FontAtlasSets, LineBreak, PositionedGlyph, SwashCache, TextBounds, - TextError, TextLayout, TextLayoutInfo, TextPipeline, TextReader, TextRoot, TextSpanAccess, - TextStyle, TextWriter, YAxisOrientation, + TextColor, TextError, TextFont, TextLayout, TextLayoutInfo, TextPipeline, TextReader, TextRoot, + TextSpanAccess, TextWriter, YAxisOrientation, }; use bevy_asset::Assets; use bevy_color::LinearRgba; @@ -44,42 +44,43 @@ use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged}; /// relative position, which is controlled by the [`Anchor`] component. /// This means that for a block of text consisting of only one line that doesn't wrap, the `justify` field will have no effect. /// -/* -``` -# use bevy_asset::Handle; -# use bevy_color::Color; -# use bevy_color::palettes::basic::BLUE; -# use bevy_ecs::World; -# use bevy_text::{Font, JustifyText, Text2d, TextLayout, TextStyle}; -# -# let font_handle: Handle = Default::default(); -# let mut world = World::default(); -# -// Basic usage. -world.spawn(Text2d::new("hello world!")); - -// With non-default style. -world.spawn(( - Text2d::new("hello world!"), - TextStyle { - font: font_handle.clone().into(), - font_size: 60.0, - color: BLUE.into(), - } -)); - -// With text justification. -world.spawn(( - Text2d::new("hello world\nand bevy!"), - TextLayout::new_with_justify(JustifyText::Center) -)); -``` -*/ +/// +/// ``` +/// # use bevy_asset::Handle; +/// # use bevy_color::Color; +/// # use bevy_color::palettes::basic::BLUE; +/// # use bevy_ecs::world::World; +/// # use bevy_text::{Font, JustifyText, Text2d, TextLayout, TextFont, TextColor}; +/// # +/// # let font_handle: Handle = Default::default(); +/// # let mut world = World::default(); +/// # +/// // Basic usage. +/// world.spawn(Text2d::new("hello world!")); +/// +/// // With non-default style. +/// world.spawn(( +/// Text2d::new("hello world!"), +/// TextFont { +/// font: font_handle.clone().into(), +/// font_size: 60.0, +/// ..Default::default() +/// }, +/// TextColor(BLUE.into()), +/// )); +/// +/// // With text justification. +/// world.spawn(( +/// Text2d::new("hello world\nand bevy!"), +/// TextLayout::new_with_justify(JustifyText::Center) +/// )); +/// ``` #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect)] #[reflect(Component, Default, Debug)] #[require( TextLayout, - TextStyle, + TextFont, + TextColor, TextBounds, Anchor, SpriteSource, @@ -141,7 +142,7 @@ pub fn extract_text2d_sprite( &GlobalTransform, )>, >, - text_styles: Extract>, + text_styles: Extract>, ) { // TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621 let scale_factor = windows @@ -186,7 +187,7 @@ pub fn extract_text2d_sprite( .map(|t| t.entity) .unwrap_or(Entity::PLACEHOLDER), ) - .map(|style| LinearRgba::from(style.color)) + .map(|(_, text_color)| LinearRgba::from(text_color.0)) .unwrap_or_default(); current_span = *span_index; } diff --git a/crates/bevy_text/src/text_access.rs b/crates/bevy_text/src/text_access.rs index 7ece5533af..e5699328f8 100644 --- a/crates/bevy_text/src/text_access.rs +++ b/crates/bevy_text/src/text_access.rs @@ -1,10 +1,11 @@ +use bevy_color::Color; use bevy_ecs::{ prelude::*, system::{Query, SystemParam}, }; use bevy_hierarchy::Children; -use crate::{TextSpan, TextStyle}; +use crate::{TextColor, TextFont, TextSpan}; /// Helper trait for using the [`TextReader`] and [`TextWriter`] system params. pub trait TextSpanAccess: Component { @@ -49,13 +50,23 @@ impl TextIterScratch { pub struct TextReader<'w, 's, R: TextRoot> { // This is a local to avoid system ambiguities when TextReaders run in parallel. scratch: Local<'s, TextIterScratch>, - roots: Query<'w, 's, (&'static R, &'static TextStyle, Option<&'static Children>)>, + roots: Query< + 'w, + 's, + ( + &'static R, + &'static TextFont, + &'static TextColor, + Option<&'static Children>, + ), + >, spans: Query< 'w, 's, ( &'static TextSpan, - &'static TextStyle, + &'static TextFont, + &'static TextColor, Option<&'static Children>, ), >, @@ -80,18 +91,24 @@ impl<'w, 's, R: TextRoot> TextReader<'w, 's, R> { &mut self, root_entity: Entity, index: usize, - ) -> Option<(Entity, usize, &str, &TextStyle)> { + ) -> Option<(Entity, usize, &str, &TextFont, Color)> { self.iter(root_entity).nth(index) } /// Gets the text value of a text span within a text block at a specific index in the flattened span list. pub fn get_text(&mut self, root_entity: Entity, index: usize) -> Option<&str> { - self.get(root_entity, index).map(|(_, _, text, _)| text) + self.get(root_entity, index).map(|(_, _, text, _, _)| text) } - /// Gets the [`TextStyle`] of a text span within a text block at a specific index in the flattened span list. - pub fn get_style(&mut self, root_entity: Entity, index: usize) -> Option<&TextStyle> { - self.get(root_entity, index).map(|(_, _, _, style)| style) + /// Gets the [`TextFont`] of a text span within a text block at a specific index in the flattened span list. + pub fn get_font(&mut self, root_entity: Entity, index: usize) -> Option<&TextFont> { + self.get(root_entity, index).map(|(_, _, _, font, _)| font) + } + + /// Gets the [`TextColor`] of a text span within a text block at a specific index in the flattened span list. + pub fn get_color(&mut self, root_entity: Entity, index: usize) -> Option { + self.get(root_entity, index) + .map(|(_, _, _, _, color)| color) } /// Gets the text value of a text span within a text block at a specific index in the flattened span list. @@ -101,11 +118,18 @@ impl<'w, 's, R: TextRoot> TextReader<'w, 's, R> { self.get_text(root_entity, index).unwrap() } - /// Gets the [`TextStyle`] of a text span within a text block at a specific index in the flattened span list. + /// Gets the [`TextFont`] of a text span within a text block at a specific index in the flattened span list. /// /// Panics if there is no span at the requested index. - pub fn style(&mut self, root_entity: Entity, index: usize) -> &TextStyle { - self.get_style(root_entity, index).unwrap() + pub fn font(&mut self, root_entity: Entity, index: usize) -> &TextFont { + self.get_font(root_entity, index).unwrap() + } + + /// Gets the [`TextColor`] of a text span within a text block at a specific index in the flattened span list. + /// + /// Panics if there is no span at the requested index. + pub fn color(&mut self, root_entity: Entity, index: usize) -> Color { + self.get_color(root_entity, index).unwrap() } } @@ -119,13 +143,23 @@ pub struct TextSpanIter<'a, R: TextRoot> { root_entity: Option, /// Stack of (children, next index into children). stack: Vec<(&'a Children, usize)>, - roots: &'a Query<'a, 'a, (&'static R, &'static TextStyle, Option<&'static Children>)>, + roots: &'a Query< + 'a, + 'a, + ( + &'static R, + &'static TextFont, + &'static TextColor, + Option<&'static Children>, + ), + >, spans: &'a Query< 'a, 'a, ( &'static TextSpan, - &'static TextStyle, + &'static TextFont, + &'static TextColor, Option<&'static Children>, ), >, @@ -133,15 +167,15 @@ pub struct TextSpanIter<'a, R: TextRoot> { impl<'a, R: TextRoot> Iterator for TextSpanIter<'a, R> { /// Item = (entity in text block, hierarchy depth in the block, span text, span style). - type Item = (Entity, usize, &'a str, &'a TextStyle); + type Item = (Entity, usize, &'a str, &'a TextFont, Color); fn next(&mut self) -> Option { // Root if let Some(root_entity) = self.root_entity.take() { - if let Ok((text, style, maybe_children)) = self.roots.get(root_entity) { + if let Ok((text, style, color, maybe_children)) = self.roots.get(root_entity) { if let Some(children) = maybe_children { self.stack.push((children, 0)); } - return Some((root_entity, 0, text.read_span(), style)); + return Some((root_entity, 0, text.read_span(), style, color.0)); } return None; } @@ -159,7 +193,7 @@ impl<'a, R: TextRoot> Iterator for TextSpanIter<'a, R> { *idx += 1; let entity = *child; - let Ok((span, style, maybe_children)) = self.spans.get(entity) else { + let Ok((span, style, color, maybe_children)) = self.spans.get(entity) else { continue; }; @@ -167,7 +201,7 @@ impl<'a, R: TextRoot> Iterator for TextSpanIter<'a, R> { if let Some(children) = maybe_children { self.stack.push((children, 0)); } - return Some((entity, depth, span.read_span(), style)); + return Some((entity, depth, span.read_span(), style, color.0)); } // All children at this stack entry have been iterated. @@ -191,8 +225,26 @@ impl<'a, R: TextRoot> Drop for TextSpanIter<'a, R> { pub struct TextWriter<'w, 's, R: TextRoot> { // This is a resource because two TextWriters can't run in parallel. scratch: ResMut<'w, TextIterScratch>, - roots: Query<'w, 's, (&'static mut R, &'static mut TextStyle), Without>, - spans: Query<'w, 's, (&'static mut TextSpan, &'static mut TextStyle), Without>, + roots: Query< + 'w, + 's, + ( + &'static mut R, + &'static mut TextFont, + &'static mut TextColor, + ), + Without, + >, + spans: Query< + 'w, + 's, + ( + &'static mut TextSpan, + &'static mut TextFont, + &'static mut TextColor, + ), + Without, + >, children: Query<'w, 's, &'static Children>, } @@ -202,15 +254,16 @@ impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> { &mut self, root_entity: Entity, index: usize, - ) -> Option<(Entity, usize, Mut, Mut)> { + ) -> Option<(Entity, usize, Mut, Mut, Mut)> { // Root if index == 0 { - let (text, style) = self.roots.get_mut(root_entity).ok()?; + let (text, font, color) = self.roots.get_mut(root_entity).ok()?; return Some(( root_entity, 0, text.map_unchanged(|t| t.write_span()), - style, + font, + color, )); } @@ -257,18 +310,30 @@ impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> { }; // Note: We do this outside the loop due to borrow checker limitations. - let (text, style) = self.spans.get_mut(entity).unwrap(); - Some((entity, depth, text.map_unchanged(|t| t.write_span()), style)) + let (text, font, color) = self.spans.get_mut(entity).unwrap(); + Some(( + entity, + depth, + text.map_unchanged(|t| t.write_span()), + font, + color, + )) } /// Gets the text value of a text span within a text block at a specific index in the flattened span list. pub fn get_text(&mut self, root_entity: Entity, index: usize) -> Option> { - self.get(root_entity, index).map(|(_, _, text, _)| text) + self.get(root_entity, index).map(|(_, _, text, ..)| text) } - /// Gets the [`TextStyle`] of a text span within a text block at a specific index in the flattened span list. - pub fn get_style(&mut self, root_entity: Entity, index: usize) -> Option> { - self.get(root_entity, index).map(|(_, _, _, style)| style) + /// Gets the [`TextFont`] of a text span within a text block at a specific index in the flattened span list. + pub fn get_font(&mut self, root_entity: Entity, index: usize) -> Option> { + self.get(root_entity, index).map(|(_, _, _, font, _)| font) + } + + /// Gets the [`TextColor`] of a text span within a text block at a specific index in the flattened span list. + pub fn get_color(&mut self, root_entity: Entity, index: usize) -> Option> { + self.get(root_entity, index) + .map(|(_, _, _, _, color)| color) } /// Gets the text value of a text span within a text block at a specific index in the flattened span list. @@ -278,40 +343,54 @@ impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> { self.get_text(root_entity, index).unwrap() } - /// Gets the [`TextStyle`] of a text span within a text block at a specific index in the flattened span list. + /// Gets the [`TextFont`] of a text span within a text block at a specific index in the flattened span list. /// /// Panics if there is no span at the requested index. - pub fn style(&mut self, root_entity: Entity, index: usize) -> Mut { - self.get_style(root_entity, index).unwrap() + pub fn font(&mut self, root_entity: Entity, index: usize) -> Mut { + self.get_font(root_entity, index).unwrap() + } + + /// Gets the [`TextColor`] of a text span within a text block at a specific index in the flattened span list. + /// + /// Panics if there is no span at the requested index. + pub fn color(&mut self, root_entity: Entity, index: usize) -> Mut { + self.get_color(root_entity, index).unwrap() } /// Invokes a callback on each span in a text block, starting with the root entity. pub fn for_each( &mut self, root_entity: Entity, - mut callback: impl FnMut(Entity, usize, Mut, Mut), + mut callback: impl FnMut(Entity, usize, Mut, Mut, Mut), ) { - self.for_each_until(root_entity, |a, b, c, d| { - (callback)(a, b, c, d); + self.for_each_until(root_entity, |a, b, c, d, e| { + (callback)(a, b, c, d, e); true }); } /// Invokes a callback on each span's string value in a text block, starting with the root entity. pub fn for_each_text(&mut self, root_entity: Entity, mut callback: impl FnMut(Mut)) { - self.for_each(root_entity, |_, _, text, _| { + self.for_each(root_entity, |_, _, text, _, _| { (callback)(text); }); } - /// Invokes a callback on each span's [`TextStyle`] in a text block, starting with the root entity. - pub fn for_each_style( + /// Invokes a callback on each span's [`TextFont`] in a text block, starting with the root entity. + pub fn for_each_font(&mut self, root_entity: Entity, mut callback: impl FnMut(Mut)) { + self.for_each(root_entity, |_, _, _, font, _| { + (callback)(font); + }); + } + + /// Invokes a callback on each span's [`TextColor`] in a text block, starting with the root entity. + pub fn for_each_color( &mut self, root_entity: Entity, - mut callback: impl FnMut(Mut), + mut callback: impl FnMut(Mut), ) { - self.for_each(root_entity, |_, _, _, style| { - (callback)(style); + self.for_each(root_entity, |_, _, _, _, color| { + (callback)(color); }); } @@ -322,17 +401,18 @@ impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> { pub fn for_each_until( &mut self, root_entity: Entity, - mut callback: impl FnMut(Entity, usize, Mut, Mut) -> bool, + mut callback: impl FnMut(Entity, usize, Mut, Mut, Mut) -> bool, ) { // Root - let Ok((text, style)) = self.roots.get_mut(root_entity) else { + let Ok((text, font, color)) = self.roots.get_mut(root_entity) else { return; }; if !(callback)( root_entity, 0, text.map_unchanged(|t| t.write_span()), - style, + font, + color, ) { return; } @@ -362,11 +442,17 @@ impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> { *idx += 1; let entity = *child; - let Ok((text, style)) = self.spans.get_mut(entity) else { + let Ok((text, font, color)) = self.spans.get_mut(entity) else { continue; }; - if !(callback)(entity, depth, text.map_unchanged(|t| t.write_span()), style) { + if !(callback)( + entity, + depth, + text.map_unchanged(|t| t.write_span()), + font, + color, + ) { self.scratch.recover(stack); return; } diff --git a/crates/bevy_ui/src/accessibility.rs b/crates/bevy_ui/src/accessibility.rs index 451cc2637c..69c1b824ed 100644 --- a/crates/bevy_ui/src/accessibility.rs +++ b/crates/bevy_ui/src/accessibility.rs @@ -26,7 +26,7 @@ fn calc_name( for child in children { let values = text_reader .iter(child) - .map(|(_, _, text, _)| text.into()) + .map(|(_, _, text, _, _)| text.into()) .collect::>(); if !values.is_empty() { name = Some(values.join(" ")); @@ -120,7 +120,7 @@ fn label_changed( for (entity, accessible) in &mut query { let values = text_reader .iter(entity) - .map(|(_, _, text, _)| text.into()) + .map(|(_, _, text, _, _)| text.into()) .collect::>(); let name = Some(values.join(" ").into_boxed_str()); if let Some(mut accessible) = accessible { diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 645356b5d6..b07bd748ac 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -41,7 +41,7 @@ use bevy_render::{ use bevy_sprite::TextureAtlasLayout; use bevy_sprite::{BorderRect, ImageScaleMode, SpriteAssetEvents, TextureAtlas}; #[cfg(feature = "bevy_text")] -use bevy_text::{ComputedTextBlock, PositionedGlyph, TextLayoutInfo, TextStyle}; +use bevy_text::{ComputedTextBlock, PositionedGlyph, TextColor, TextLayoutInfo}; use bevy_transform::components::GlobalTransform; use bevy_utils::HashMap; use box_shadow::BoxShadowPlugin; @@ -606,7 +606,7 @@ pub fn extract_text_sections( &TextLayoutInfo, )>, >, - text_styles: Extract>, + text_styles: Extract>, mapping: Extract>, ) { let mut start = 0; @@ -681,7 +681,7 @@ pub fn extract_text_sections( .map(|t| t.entity) .unwrap_or(Entity::PLACEHOLDER), ) - .map(|style| LinearRgba::from(style.color)) + .map(|text_color| LinearRgba::from(text_color.0)) .unwrap_or_default(); current_span = *span_index; } diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 2d6e763bc0..58a1de6b9c 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -2520,7 +2520,7 @@ impl<'w, 's> DefaultUiCamera<'w, 's> { /// Marker for controlling whether Ui is rendered with or without anti-aliasing /// in a camera. By default, Ui is always anti-aliased. /// -/// **Note:** This does not affect text anti-aliasing. For that, use the `font_smoothing` property of the [`TextStyle`](bevy_text::TextStyle) component. +/// **Note:** This does not affect text anti-aliasing. For that, use the `font_smoothing` property of the [`TextFont`](bevy_text::TextFont) component. /// /// ``` /// use bevy_core_pipeline::prelude::*; diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 72b3df30e4..b51c875f3b 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -3,6 +3,7 @@ use crate::{ NodeMeasure, Style, TargetCamera, UiScale, ZIndex, }; use bevy_asset::Assets; +use bevy_color::Color; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ change_detection::DetectChanges, @@ -19,8 +20,8 @@ use bevy_render::{camera::Camera, texture::Image, view::Visibility}; use bevy_sprite::TextureAtlasLayout; use bevy_text::{ scale_value, ComputedTextBlock, CosmicFontSystem, Font, FontAtlasSets, LineBreak, SwashCache, - TextBounds, TextError, TextLayout, TextLayoutInfo, TextMeasureInfo, TextPipeline, TextReader, - TextRoot, TextSpanAccess, TextStyle, TextWriter, YAxisOrientation, + TextBounds, TextColor, TextError, TextFont, TextLayout, TextLayoutInfo, TextMeasureInfo, + TextPipeline, TextReader, TextRoot, TextSpanAccess, TextWriter, YAxisOrientation, }; use bevy_transform::components::Transform; use bevy_utils::{tracing::error, Entry}; @@ -56,43 +57,44 @@ impl Default for TextNodeFlags { /// /// Note that [`Transform`] on this entity is managed automatically by the UI layout system. /// -/* -``` -# use bevy_asset::Handle; -# use bevy_color::Color; -# use bevy_color::palettes::basic::BLUE; -# use bevy_ecs::World; -# use bevy_text::{Font, JustifyText, TextLayout, TextStyle}; -# use bevy_ui::Text; -# -# let font_handle: Handle = Default::default(); -# let mut world = World::default(); -# -// Basic usage. -world.spawn(Text::new("hello world!")); - -// With non-default style. -world.spawn(( - Text::new("hello world!"), - TextStyle { - font: font_handle.clone().into(), - font_size: 60.0, - color: BLUE.into(), - } -)); - -// With text justification. -world.spawn(( - Text::new("hello world\nand bevy!"), - TextLayout::new_with_justify(JustifyText::Center) -)); -``` -*/ +/// +/// ``` +/// # use bevy_asset::Handle; +/// # use bevy_color::Color; +/// # use bevy_color::palettes::basic::BLUE; +/// # use bevy_ecs::world::World; +/// # use bevy_text::{Font, JustifyText, TextLayout, TextFont, TextColor}; +/// # use bevy_ui::prelude::Text; +/// # +/// # let font_handle: Handle = Default::default(); +/// # let mut world = World::default(); +/// # +/// // Basic usage. +/// world.spawn(Text::new("hello world!")); +/// +/// // With non-default style. +/// world.spawn(( +/// Text::new("hello world!"), +/// TextFont { +/// font: font_handle.clone().into(), +/// font_size: 60.0, +/// ..Default::default() +/// }, +/// TextColor(BLUE.into()), +/// )); +/// +/// // With text justification. +/// world.spawn(( +/// Text::new("hello world\nand bevy!"), +/// TextLayout::new_with_justify(JustifyText::Center) +/// )); +/// ``` #[derive(Component, Debug, Default, Clone, Deref, DerefMut, Reflect)] #[reflect(Component, Default, Debug)] #[require( TextLayout, - TextStyle, + TextFont, + TextColor, TextNodeFlags, Node, Style, // TODO: Remove when Node uses required components. @@ -204,7 +206,7 @@ fn create_text_measure<'a>( entity: Entity, fonts: &Assets, scale_factor: f64, - spans: impl Iterator, + spans: impl Iterator, block: Ref, text_pipeline: &mut TextPipeline, mut content_size: Mut, diff --git a/errors/B0005.md b/errors/B0005.md index 57fa6e94a6..7fecfd11a1 100644 --- a/errors/B0005.md +++ b/errors/B0005.md @@ -2,7 +2,7 @@ A runtime warning. -Separate font atlases are created for each font and font size. This is expensive, and the memory is never reclaimed when e.g. interpolating `TextStyle::font_size` or `UiScale::scale`. +Separate font atlases are created for each font and font size. This is expensive, and the memory is never reclaimed when e.g. interpolating `TextFont::font_size` or `UiScale::scale`. If you need to smoothly scale font size, use `Transform::scale`. diff --git a/examples/2d/sprite_slice.rs b/examples/2d/sprite_slice.rs index 7ed097bdb6..0dae09dce5 100644 --- a/examples/2d/sprite_slice.rs +++ b/examples/2d/sprite_slice.rs @@ -14,7 +14,7 @@ fn spawn_sprites( texture_handle: Handle, mut position: Vec3, slice_border: f32, - style: TextStyle, + style: TextFont, gap: f32, ) { let cases = [ @@ -100,7 +100,7 @@ fn spawn_sprites( fn setup(mut commands: Commands, asset_server: Res) { commands.spawn(Camera2d); let font = asset_server.load("fonts/FiraSans-Bold.ttf"); - let style = TextStyle { + let style = TextFont { font: font.clone(), ..default() }; diff --git a/examples/2d/text2d.rs b/examples/2d/text2d.rs index 47a5f16e9b..aefde87e6d 100644 --- a/examples/2d/text2d.rs +++ b/examples/2d/text2d.rs @@ -35,7 +35,7 @@ struct AnimateScale; fn setup(mut commands: Commands, asset_server: Res) { let font = asset_server.load("fonts/FiraSans-Bold.ttf"); - let text_style = TextStyle { + let text_font = TextFont { font: font.clone(), font_size: 50.0, ..default() @@ -46,27 +46,27 @@ fn setup(mut commands: Commands, asset_server: Res) { // Demonstrate changing translation commands.spawn(( Text2d::new("translation"), - text_style.clone(), + text_font.clone(), TextLayout::new_with_justify(text_justification), AnimateTranslation, )); // Demonstrate changing rotation commands.spawn(( Text2d::new("rotation"), - text_style.clone(), + text_font.clone(), TextLayout::new_with_justify(text_justification), AnimateRotation, )); // Demonstrate changing scale commands.spawn(( Text2d::new("scale"), - text_style, + text_font, TextLayout::new_with_justify(text_justification), Transform::from_translation(Vec3::new(400.0, 0.0, 0.0)), AnimateScale, )); // Demonstrate text wrapping - let slightly_smaller_text_style = TextStyle { + let slightly_smaller_text_font = TextFont { font, font_size: 35.0, ..default() @@ -81,7 +81,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .with_children(|builder| { builder.spawn(( Text2d::new("this text wraps in the box\n(Unicode linebreaks)"), - slightly_smaller_text_style.clone(), + slightly_smaller_text_font.clone(), TextLayout::new(JustifyText::Left, LineBreak::WordBoundary), // Wrap text in the rectangle TextBounds::from(box_size), @@ -100,7 +100,7 @@ fn setup(mut commands: Commands, asset_server: Res) { .with_children(|builder| { builder.spawn(( Text2d::new("this text wraps in the box\n(AnyCharacter linebreaks)"), - slightly_smaller_text_style.clone(), + slightly_smaller_text_font.clone(), TextLayout::new(JustifyText::Left, LineBreak::AnyCharacter), // Wrap text in the rectangle TextBounds::from(other_box_size), @@ -112,7 +112,7 @@ fn setup(mut commands: Commands, asset_server: Res) { // Demonstrate font smoothing off commands.spawn(( Text2d::new("FontSmoothing::None"), - slightly_smaller_text_style + slightly_smaller_text_font .clone() .with_font_smoothing(FontSmoothing::None), Transform::from_translation(Vec3::new(-400.0, -250.0, 0.0)), @@ -126,10 +126,8 @@ fn setup(mut commands: Commands, asset_server: Res) { ] { commands.spawn(( Text2d::new(format!(" Anchor::{text_anchor:?} ")), - TextStyle { - color, - ..slightly_smaller_text_style.clone() - }, + slightly_smaller_text_font.clone(), + TextColor(color), Transform::from_translation(250. * Vec3::Y), text_anchor, )); diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index d6b391ee6c..532903c0ad 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -121,7 +121,7 @@ fn setup( let font = asset_server.load("fonts/FiraSans-Bold.ttf"); // padding label text style - let text_style: TextStyle = TextStyle { + let text_style: TextFont = TextFont { font: font.clone(), font_size: 42.0, ..default() @@ -184,7 +184,7 @@ fn setup( ]; // label text style - let sampling_label_style = TextStyle { + let sampling_label_style = TextFont { font, font_size: 25.0, ..default() @@ -275,7 +275,7 @@ fn create_label( commands: &mut Commands, translation: (f32, f32, f32), text: &str, - text_style: TextStyle, + text_style: TextFont, ) { commands.spawn(( Text2d::new(text), diff --git a/examples/3d/auto_exposure.rs b/examples/3d/auto_exposure.rs index 3d6caea03f..21615b57bd 100644 --- a/examples/3d/auto_exposure.rs +++ b/examples/3d/auto_exposure.rs @@ -127,7 +127,7 @@ fn setup( ..default() }); - let text_style = TextStyle::default(); + let text_style = TextFont::default(); commands.spawn((Text::new("Left / Right - Rotate Camera\nC - Toggle Compensation Curve\nM - Toggle Metering Mask\nV - Visualize Metering Mask"), text_style.clone(), Style { diff --git a/examples/3d/blend_modes.rs b/examples/3d/blend_modes.rs index 6257eec42f..347f02799b 100644 --- a/examples/3d/blend_modes.rs +++ b/examples/3d/blend_modes.rs @@ -160,18 +160,12 @@ fn setup( // Controls Text // We need the full version of this font so we can use box drawing characters. - let font = asset_server.load("fonts/FiraMono-Medium.ttf"); - - let text_style = TextStyle { - font: font.clone(), + let text_style = TextFont { + font: asset_server.load("fonts/FiraMono-Medium.ttf"), ..default() }; - let label_text_style = TextStyle { - font, - color: ORANGE.into(), - ..default() - }; + let label_text_style = (text_style.clone(), TextColor(ORANGE.into())); commands.spawn((Text::new("Up / Down — Increase / Decrease Alpha\nLeft / Right — Rotate Camera\nH - Toggle HDR\nSpacebar — Toggle Unlit\nC — Randomize Colors"), text_style.clone(), diff --git a/examples/3d/color_grading.rs b/examples/3d/color_grading.rs index 7fbfffeed4..42b3811e53 100644 --- a/examples/3d/color_grading.rs +++ b/examples/3d/color_grading.rs @@ -316,7 +316,7 @@ fn add_help_text( ) { commands.spawn(( Text::new(create_help_text(currently_selected_option)), - TextStyle { + TextFont { font: font.clone(), ..default() }, @@ -339,12 +339,12 @@ fn add_text<'a>( ) -> EntityCommands<'a> { parent.spawn(( Text::new(label), - TextStyle { + TextFont { font: font.clone(), font_size: 15.0, - color, ..default() }, + TextColor(color), )) } @@ -598,8 +598,8 @@ fn update_ui_state( Color::WHITE }; - writer.for_each_style(entity, |mut style| { - style.color = color; + writer.for_each_color(entity, |mut text_color| { + text_color.0 = color; }); // Update the displayed value, if this is the currently-selected option. diff --git a/examples/3d/load_gltf_extras.rs b/examples/3d/load_gltf_extras.rs index e591b513c9..221f89d365 100644 --- a/examples/3d/load_gltf_extras.rs +++ b/examples/3d/load_gltf_extras.rs @@ -35,7 +35,7 @@ fn setup(mut commands: Commands, asset_server: Res) { // a place to display the extras on screen commands.spawn(( Text::default(), - TextStyle { + TextFont { font_size: 15., ..default() }, diff --git a/examples/3d/pbr.rs b/examples/3d/pbr.rs index fb06e666c5..65c16207f0 100644 --- a/examples/3d/pbr.rs +++ b/examples/3d/pbr.rs @@ -60,7 +60,7 @@ fn setup( // labels commands.spawn(( Text::new("Perceptual Roughness"), - TextStyle { + TextFont { font_size: 30.0, ..default() }, @@ -74,7 +74,7 @@ fn setup( commands.spawn(( Text::new("Metallic"), - TextStyle { + TextFont { font_size: 30.0, ..default() }, @@ -92,7 +92,7 @@ fn setup( commands.spawn(( Text::new("Loading Environment Map..."), - TextStyle { + TextFont { font_size: 30.0, ..default() }, diff --git a/examples/3d/tonemapping.rs b/examples/3d/tonemapping.rs index b8aef70c2f..daea8705fc 100644 --- a/examples/3d/tonemapping.rs +++ b/examples/3d/tonemapping.rs @@ -172,11 +172,11 @@ fn setup_image_viewer_scene( commands.spawn(( Text::new("Drag and drop an HDR or EXR file"), - TextStyle { + TextFont { font_size: 36.0, - color: Color::BLACK, ..default() }, + TextColor(Color::BLACK), TextLayout::new_with_justify(JustifyText::Center), Style { align_self: AlignSelf::Center, diff --git a/examples/animation/animated_ui.rs b/examples/animation/animated_ui.rs index 888ba2c860..76a68c735d 100644 --- a/examples/animation/animated_ui.rs +++ b/examples/animation/animated_ui.rs @@ -40,7 +40,7 @@ fn main() { } impl AnimatableProperty for FontSizeProperty { - type Component = TextStyle; + type Component = TextFont; type Property = f32; @@ -50,12 +50,12 @@ impl AnimatableProperty for FontSizeProperty { } impl AnimatableProperty for TextColorProperty { - type Component = TextStyle; + type Component = TextColor; type Property = Srgba; fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> { - match component.color { + match component.0 { Color::Srgba(ref mut color) => Some(color), _ => None, } @@ -172,12 +172,12 @@ fn setup( builder .spawn(( Text::new("Bevy"), - TextStyle { + TextFont { font: asset_server.load("fonts/FiraSans-Bold.ttf"), font_size: 24.0, - color: Color::Srgba(Srgba::RED), ..default() }, + TextColor(Color::Srgba(Srgba::RED)), TextLayout::new_with_justify(JustifyText::Center), )) // Mark as an animation target. diff --git a/examples/animation/animation_events.rs b/examples/animation/animation_events.rs index da468e8833..c2359b0397 100644 --- a/examples/animation/animation_events.rs +++ b/examples/animation/animation_events.rs @@ -37,12 +37,12 @@ impl AnimationEvent for MessageEvent { fn edit_message( mut event_reader: EventReader, - text: Single<(&mut Text2d, &mut TextStyle), With>, + text: Single<(&mut Text2d, &mut TextColor), With>, ) { - let (mut text, mut style) = text.into_inner(); + let (mut text, mut color) = text.into_inner(); for event in event_reader.read() { text.0 = event.value.clone(); - style.color = event.color; + color.0 = event.color; } } @@ -69,11 +69,11 @@ fn setup( commands.spawn(( MessageText, Text2d::default(), - TextStyle { + TextFont { font_size: 119.0, - color: Color::NONE, ..default() }, + TextColor(Color::NONE), )); // Create a new animation clip. @@ -108,9 +108,9 @@ fn setup( } // Slowly fade out the text opacity. -fn animate_text_opacity(mut styles: Query<&mut TextStyle>, time: Res