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`.
This commit is contained in:
ickshonpe 2024-10-13 18:06:22 +01:00 committed by GitHub
parent 6521e759ea
commit 6f7d0e5725
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
83 changed files with 752 additions and 641 deletions

View file

@ -14,7 +14,7 @@ use bevy_ecs::{
}; };
use bevy_hierarchy::{BuildChildren, ChildBuild}; use bevy_hierarchy::{BuildChildren, ChildBuild};
use bevy_render::view::Visibility; use bevy_render::view::Visibility;
use bevy_text::{Font, TextSpan, TextStyle}; use bevy_text::{Font, TextColor, TextFont, TextSpan};
use bevy_ui::{ use bevy_ui::{
node_bundles::NodeBundle, node_bundles::NodeBundle,
widget::{Text, UiTextWriter}, widget::{Text, UiTextWriter},
@ -62,7 +62,9 @@ impl Plugin for FpsOverlayPlugin {
#[derive(Resource, Clone)] #[derive(Resource, Clone)]
pub struct FpsOverlayConfig { pub struct FpsOverlayConfig {
/// Configuration of text in the overlay. /// 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. /// Displays the FPS overlay if true.
pub enabled: bool, pub enabled: bool,
} }
@ -70,12 +72,12 @@ pub struct FpsOverlayConfig {
impl Default for FpsOverlayConfig { impl Default for FpsOverlayConfig {
fn default() -> Self { fn default() -> Self {
FpsOverlayConfig { FpsOverlayConfig {
text_config: TextStyle { text_config: TextFont {
font: Handle::<Font>::default(), font: Handle::<Font>::default(),
font_size: 32.0, font_size: 32.0,
color: Color::WHITE,
..default() ..default()
}, },
text_color: Color::WHITE,
enabled: true, enabled: true,
} }
} }
@ -102,6 +104,7 @@ fn setup(mut commands: Commands, overlay_config: Res<FpsOverlayConfig>) {
p.spawn(( p.spawn((
Text::new("FPS: "), Text::new("FPS: "),
overlay_config.text_config.clone(), overlay_config.text_config.clone(),
TextColor(overlay_config.text_color),
FpsText, FpsText,
)) ))
.with_child((TextSpan::default(), overlay_config.text_config.clone())); .with_child((TextSpan::default(), overlay_config.text_config.clone()));
@ -128,9 +131,10 @@ fn customize_text(
mut writer: UiTextWriter, mut writer: UiTextWriter,
) { ) {
for entity in &query { for entity in &query {
writer.for_each_style(entity, |mut style| { writer.for_each_font(entity, |mut font| {
*style = overlay_config.text_config.clone(); *font = overlay_config.text_config.clone();
}); });
writer.for_each_color(entity, |mut color| color.0 = overlay_config.text_color);
} }
} }

View file

@ -60,9 +60,9 @@ pub struct FontAtlasKey(pub u32, pub FontSmoothing);
/// A `FontAtlasSet` is an [`Asset`]. /// A `FontAtlasSet` is an [`Asset`].
/// ///
/// There is one `FontAtlasSet` for each font: /// 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`. /// 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`.~ /// 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`.*) /// (*Note that system fonts are not currently supported by the `TextPipeline`.*)
/// ///

View file

@ -65,8 +65,8 @@ pub use text_access::*;
pub mod prelude { pub mod prelude {
#[doc(hidden)] #[doc(hidden)]
pub use crate::{ pub use crate::{
Font, JustifyText, LineBreak, Text2d, TextError, TextLayout, TextReader2d, TextSpan, Font, JustifyText, LineBreak, Text2d, TextColor, TextError, TextFont, TextLayout,
TextStyle, TextWriter2d, TextReader2d, TextSpan, TextWriter2d,
}; };
} }

View file

@ -1,6 +1,7 @@
use alloc::sync::Arc; use alloc::sync::Arc;
use bevy_asset::{AssetId, Assets}; use bevy_asset::{AssetId, Assets};
use bevy_color::Color;
use bevy_ecs::{ use bevy_ecs::{
component::Component, component::Component,
entity::Entity, entity::Entity,
@ -17,7 +18,7 @@ use cosmic_text::{Attrs, Buffer, Family, Metrics, Shaping, Wrap};
use crate::{ use crate::{
error::TextError, ComputedTextBlock, Font, FontAtlasSets, FontSmoothing, JustifyText, 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`] /// A wrapper resource around a [`cosmic_text::FontSystem`]
@ -70,7 +71,7 @@ pub struct TextPipeline {
/// Buffered vec for collecting spans. /// Buffered vec for collecting spans.
/// ///
/// See [this dark magic](https://users.rust-lang.org/t/how-to-cache-a-vectors-capacity/94478/10). /// 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. /// Buffered vec for collecting info for glyph assembly.
glyph_info: Vec<(AssetId<Font>, FontSmoothing)>, glyph_info: Vec<(AssetId<Font>, FontSmoothing)>,
} }
@ -83,7 +84,7 @@ impl TextPipeline {
pub fn update_buffer<'a>( pub fn update_buffer<'a>(
&mut self, &mut self,
fonts: &Assets<Font>, fonts: &Assets<Font>,
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextStyle)>, text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextFont, Color)>,
linebreak: LineBreak, linebreak: LineBreak,
justify: JustifyText, justify: JustifyText,
bounds: TextBounds, bounds: TextBounds,
@ -96,22 +97,22 @@ impl TextPipeline {
// Collect span information into a vec. This is necessary because font loading requires mut access // Collect span information into a vec. This is necessary because font loading requires mut access
// to FontSystem, which the cosmic-text Buffer also needs. // to FontSystem, which the cosmic-text Buffer also needs.
let mut font_size: f32 = 0.; 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) core::mem::take(&mut self.spans_buffer)
.into_iter() .into_iter()
.map(|_| -> (usize, &str, &TextStyle, FontFaceInfo) { unreachable!() }) .map(|_| -> (usize, &str, &TextFont, FontFaceInfo, Color) { unreachable!() })
.collect(); .collect();
computed.entities.clear(); 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. // Return early if a font is not loaded yet.
if !fonts.contains(style.font.id()) { if !fonts.contains(text_font.font.id()) {
spans.clear(); spans.clear();
self.spans_buffer = spans self.spans_buffer = spans
.into_iter() .into_iter()
.map( .map(
|_| -> (usize, &'static str, &'static TextStyle, FontFaceInfo) { |_| -> (usize, &'static str, &'static TextFont, FontFaceInfo) {
unreachable!() unreachable!()
}, },
) )
@ -124,17 +125,21 @@ impl TextPipeline {
computed.entities.push(TextEntity { entity, depth }); computed.entities.push(TextEntity { entity, depth });
// Get max font size for use in cosmic Metrics. // 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. // Load Bevy fonts into cosmic-text's font system.
let face_info = let face_info = load_font_to_fontdb(
load_font_to_fontdb(style, font_system, &mut self.map_handle_to_font_id, fonts); text_font,
font_system,
&mut self.map_handle_to_font_id,
fonts,
);
// Save spans that aren't zero-sized. // 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; 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; let line_height = font_size * 1.2;
@ -151,10 +156,12 @@ impl TextPipeline {
// The section index is stored in the metadata of the spans, and could be used // 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 // to look up the section the span came from and is not used internally
// in cosmic-text. // in cosmic-text.
let spans_iter = spans.iter().map(|(span_index, span, style, font_info)| { let spans_iter = spans
.iter()
.map(|(span_index, span, text_font, font_info, color)| {
( (
*span, *span,
get_attrs(*span_index, style, font_info, scale_factor), get_attrs(*span_index, text_font, *color, font_info, scale_factor),
) )
}); });
@ -186,7 +193,7 @@ impl TextPipeline {
spans.clear(); spans.clear();
self.spans_buffer = spans self.spans_buffer = spans
.into_iter() .into_iter()
.map(|_| -> (usize, &'static str, &'static TextStyle, FontFaceInfo) { unreachable!() }) .map(|_| -> (usize, &'static str, &'static TextFont, FontFaceInfo) { unreachable!() })
.collect(); .collect();
Ok(()) Ok(())
@ -201,7 +208,7 @@ impl TextPipeline {
&mut self, &mut self,
layout_info: &mut TextLayoutInfo, layout_info: &mut TextLayoutInfo,
fonts: &Assets<Font>, fonts: &Assets<Font>,
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextStyle)>, text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextFont, Color)>,
scale_factor: f64, scale_factor: f64,
layout: &TextLayout, layout: &TextLayout,
bounds: TextBounds, bounds: TextBounds,
@ -222,8 +229,8 @@ impl TextPipeline {
// Extract font ids from the iterator while traversing it. // Extract font ids from the iterator while traversing it.
let mut glyph_info = core::mem::take(&mut self.glyph_info); let mut glyph_info = core::mem::take(&mut self.glyph_info);
glyph_info.clear(); glyph_info.clear();
let text_spans = text_spans.inspect(|(_, _, _, style)| { let text_spans = text_spans.inspect(|(_, _, _, text_font, _)| {
glyph_info.push((style.font.id(), style.font_smoothing)); glyph_info.push((text_font.font.id(), text_font.font_smoothing));
}); });
let update_result = self.update_buffer( let update_result = self.update_buffer(
@ -335,7 +342,7 @@ impl TextPipeline {
&mut self, &mut self,
entity: Entity, entity: Entity,
fonts: &Assets<Font>, fonts: &Assets<Font>,
text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextStyle)>, text_spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextFont, Color)>,
scale_factor: f64, scale_factor: f64,
layout: &TextLayout, layout: &TextLayout,
computed: &mut ComputedTextBlock, computed: &mut ComputedTextBlock,
@ -427,12 +434,12 @@ impl TextMeasureInfo {
} }
fn load_font_to_fontdb( fn load_font_to_fontdb(
style: &TextStyle, text_font: &TextFont,
font_system: &mut cosmic_text::FontSystem, font_system: &mut cosmic_text::FontSystem,
map_handle_to_font_id: &mut HashMap<AssetId<Font>, (cosmic_text::fontdb::ID, Arc<str>)>, map_handle_to_font_id: &mut HashMap<AssetId<Font>, (cosmic_text::fontdb::ID, Arc<str>)>,
fonts: &Assets<Font>, fonts: &Assets<Font>,
) -> FontFaceInfo { ) -> FontFaceInfo {
let font_handle = style.font.clone(); let font_handle = text_font.font.clone();
let (face_id, family_name) = map_handle_to_font_id let (face_id, family_name) = map_handle_to_font_id
.entry(font_handle.id()) .entry(font_handle.id())
.or_insert_with(|| { .or_insert_with(|| {
@ -461,10 +468,11 @@ fn load_font_to_fontdb(
} }
} }
/// Translates [`TextStyle`] to [`Attrs`]. /// Translates [`TextFont`] to [`Attrs`].
fn get_attrs<'a>( fn get_attrs<'a>(
span_index: usize, span_index: usize,
style: &TextStyle, text_font: &TextFont,
color: Color,
face_info: &'a FontFaceInfo, face_info: &'a FontFaceInfo,
scale_factor: f64, scale_factor: f64,
) -> Attrs<'a> { ) -> Attrs<'a> {
@ -474,8 +482,8 @@ fn get_attrs<'a>(
.stretch(face_info.stretch) .stretch(face_info.stretch)
.style(face_info.style) .style(face_info.style)
.weight(face_info.weight) .weight(face_info.weight)
.metrics(Metrics::relative(style.font_size, 1.2).scale(scale_factor as f32)) .metrics(Metrics::relative(text_font.font_size, 1.2).scale(scale_factor as f32))
.color(cosmic_text::Color(style.color.to_linear().as_u32())); .color(cosmic_text::Color(color.to_linear().as_u32()));
attrs attrs
} }

View file

@ -58,11 +58,11 @@ pub struct ComputedTextBlock {
/// ///
/// Includes: /// Includes:
/// - [`TextLayout`] changes. /// - [`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 // 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 // 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 // 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. // components for more granular change detection. A cost/benefit analysis is needed.
pub(crate) needs_rerender: bool, pub(crate) needs_rerender: bool,
} }
@ -70,7 +70,7 @@ pub struct ComputedTextBlock {
impl ComputedTextBlock { impl ComputedTextBlock {
/// Accesses entities in this block. /// 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. /// stored there.
pub fn entities(&self) -> &[TextEntity] { pub fn entities(&self) -> &[TextEntity] {
&self.entities &self.entities
@ -97,7 +97,7 @@ impl Default for ComputedTextBlock {
/// Component with text format settings for a block of text. /// 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 /// spans associated with a text block are collected into [`ComputedTextBlock`] for layout, and then inserted
/// to [`TextLayoutInfo`] for rendering. /// to [`TextLayoutInfo`] for rendering.
/// ///
@ -159,38 +159,39 @@ impl TextLayout {
/// ///
/// Spans are collected in hierarchy traversal order into a [`ComputedTextBlock`] for layout. /// Spans are collected in hierarchy traversal order into a [`ComputedTextBlock`] for layout.
/// ///
/* /// ```
``` /// # use bevy_asset::Handle;
# use bevy_asset::Handle; /// # use bevy_color::Color;
# use bevy_color::Color; /// # use bevy_color::palettes::basic::{RED, BLUE};
# use bevy_color::palettes::basic::{RED, BLUE}; /// # use bevy_ecs::world::World;
# use bevy_ecs::World; /// # use bevy_text::{Font, TextLayout, TextFont, TextSpan, TextColor};
# use bevy_text::{Font, TextLayout, TextStyle, TextSection}; /// # use bevy_hierarchy::BuildChildren;
///
# let font_handle: Handle<Font> = Default::default(); /// # let font_handle: Handle<Font> = Default::default();
# let mut world = World::default(); /// # let mut world = World::default();
# /// #
world.spawn(( /// world.spawn((
TextLayout::default(), /// TextLayout::default(),
TextStyle { /// TextFont {
font: font_handle.clone().into(), /// font: font_handle.clone().into(),
font_size: 60.0, /// font_size: 60.0,
color: BLUE.into(), /// ..Default::default()
} /// },
)) /// TextColor(BLUE.into()),
.with_child(( /// ))
TextSpan::new("Hello!"), /// .with_child((
TextStyle { /// TextSpan::new("Hello!"),
font: font_handle.into(), /// TextFont {
font_size: 60.0, /// font: font_handle.into(),
color: RED.into(), /// font_size: 60.0,
} /// ..Default::default()
)); /// },
``` /// TextColor(RED.into()),
*/ /// ));
/// ```
#[derive(Component, Debug, Default, Clone, Deref, DerefMut, Reflect)] #[derive(Component, Debug, Default, Clone, Deref, DerefMut, Reflect)]
#[reflect(Component, Default, Debug)] #[reflect(Component, Default, Debug)]
#[require(TextStyle)] #[require(TextFont, TextColor)]
pub struct TextSpan(pub String); pub struct TextSpan(pub String);
impl TextSpan { impl TextSpan {
@ -259,11 +260,11 @@ impl From<JustifyText> 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. /// the font face, the font size, and the color.
#[derive(Component, Clone, Debug, Reflect)] #[derive(Component, Clone, Debug, Reflect)]
#[reflect(Component, Default, Debug)] #[reflect(Component, Default, Debug)]
pub struct TextStyle { pub struct TextFont {
/// The specific font face to use, as a `Handle` to a [`Font`] asset. /// The specific font face to use, as a `Handle` to a [`Font`] asset.
/// ///
/// If the `font` is not specified, then /// 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 /// A new font atlas is generated for every combination of font handle and scaled font size
/// which can have a strong performance impact. /// which can have a strong performance impact.
pub font_size: f32, pub font_size: f32,
/// The color of the text for this section.
pub color: Color,
/// The antialiasing method to use when rendering text. /// The antialiasing method to use when rendering text.
pub font_smoothing: FontSmoothing, pub font_smoothing: FontSmoothing,
} }
impl TextStyle { impl TextFont {
/// Returns this [`TextStyle`] with the specified [`FontSmoothing`]. /// Returns this [`TextFont`] with the specified [`FontSmoothing`].
pub const fn with_font_smoothing(mut self, font_smoothing: FontSmoothing) -> Self { pub const fn with_font_smoothing(mut self, font_smoothing: FontSmoothing) -> Self {
self.font_smoothing = font_smoothing; self.font_smoothing = font_smoothing;
self self
} }
} }
impl Default for TextStyle { impl Default for TextFont {
fn default() -> Self { fn default() -> Self {
Self { Self {
font: Default::default(), font: Default::default(),
font_size: 20.0, font_size: 20.0,
color: Color::WHITE,
font_smoothing: Default::default(), 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<T: Into<Color>> From<T> 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. /// 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Reflect, Serialize, Deserialize)]
#[reflect(Serialize, Deserialize)] #[reflect(Serialize, Deserialize)]
@ -359,12 +381,12 @@ pub fn detect_text_needs_rerender<Root: Component>(
( (
Or<( Or<(
Changed<Root>, Changed<Root>,
Changed<TextStyle>, Changed<TextFont>,
Changed<TextLayout>, Changed<TextLayout>,
Changed<Children>, Changed<Children>,
)>, )>,
With<Root>, With<Root>,
With<TextStyle>, With<TextFont>,
With<TextLayout>, With<TextLayout>,
), ),
>, >,
@ -373,13 +395,13 @@ pub fn detect_text_needs_rerender<Root: Component>(
( (
Or<( Or<(
Changed<TextSpan>, Changed<TextSpan>,
Changed<TextStyle>, Changed<TextFont>,
Changed<Children>, Changed<Children>,
Changed<Parent>, // Included to detect broken text block hierarchies. Changed<Parent>, // Included to detect broken text block hierarchies.
Added<TextLayout>, Added<TextLayout>,
)>, )>,
With<TextSpan>, With<TextSpan>,
With<TextStyle>, With<TextFont>,
), ),
>, >,
mut computed: Query<( mut computed: Query<(
@ -390,7 +412,7 @@ pub fn detect_text_needs_rerender<Root: Component>(
) { ) {
// Root entity: // Root entity:
// - Root component changed. // - Root component changed.
// - TextStyle on root changed. // - TextFont on root changed.
// - TextLayout changed. // - TextLayout changed.
// - Root children changed (can include additions and removals). // - Root children changed (can include additions and removals).
for root in changed_roots.iter() { for root in changed_roots.iter() {
@ -404,7 +426,7 @@ pub fn detect_text_needs_rerender<Root: Component>(
// Span entity: // Span entity:
// - Span component changed. // - Span component changed.
// - Span TextStyle changed. // - Span TextFont changed.
// - Span children changed (can include additions and removals). // - Span children changed (can include additions and removals).
for (entity, maybe_span_parent, has_text_block) in changed_spans.iter() { for (entity, maybe_span_parent, has_text_block) in changed_spans.iter() {
if has_text_block { if has_text_block {

View file

@ -1,8 +1,8 @@
use crate::pipeline::CosmicFontSystem; use crate::pipeline::CosmicFontSystem;
use crate::{ use crate::{
ComputedTextBlock, Font, FontAtlasSets, LineBreak, PositionedGlyph, SwashCache, TextBounds, ComputedTextBlock, Font, FontAtlasSets, LineBreak, PositionedGlyph, SwashCache, TextBounds,
TextError, TextLayout, TextLayoutInfo, TextPipeline, TextReader, TextRoot, TextSpanAccess, TextColor, TextError, TextFont, TextLayout, TextLayoutInfo, TextPipeline, TextReader, TextRoot,
TextStyle, TextWriter, YAxisOrientation, TextSpanAccess, TextWriter, YAxisOrientation,
}; };
use bevy_asset::Assets; use bevy_asset::Assets;
use bevy_color::LinearRgba; use bevy_color::LinearRgba;
@ -44,42 +44,43 @@ use bevy_window::{PrimaryWindow, Window, WindowScaleFactorChanged};
/// relative position, which is controlled by the [`Anchor`] component. /// 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. /// 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_asset::Handle;
# use bevy_color::Color; /// # use bevy_color::Color;
# use bevy_color::palettes::basic::BLUE; /// # use bevy_color::palettes::basic::BLUE;
# use bevy_ecs::World; /// # use bevy_ecs::world::World;
# use bevy_text::{Font, JustifyText, Text2d, TextLayout, TextStyle}; /// # use bevy_text::{Font, JustifyText, Text2d, TextLayout, TextFont, TextColor};
# /// #
# let font_handle: Handle<Font> = Default::default(); /// # let font_handle: Handle<Font> = Default::default();
# let mut world = World::default(); /// # let mut world = World::default();
# /// #
// Basic usage. /// // Basic usage.
world.spawn(Text2d::new("hello world!")); /// world.spawn(Text2d::new("hello world!"));
///
// With non-default style. /// // With non-default style.
world.spawn(( /// world.spawn((
Text2d::new("hello world!"), /// Text2d::new("hello world!"),
TextStyle { /// TextFont {
font: font_handle.clone().into(), /// font: font_handle.clone().into(),
font_size: 60.0, /// font_size: 60.0,
color: BLUE.into(), /// ..Default::default()
} /// },
)); /// TextColor(BLUE.into()),
/// ));
// With text justification. ///
world.spawn(( /// // With text justification.
Text2d::new("hello world\nand bevy!"), /// world.spawn((
TextLayout::new_with_justify(JustifyText::Center) /// Text2d::new("hello world\nand bevy!"),
)); /// TextLayout::new_with_justify(JustifyText::Center)
``` /// ));
*/ /// ```
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect)] #[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect)]
#[reflect(Component, Default, Debug)] #[reflect(Component, Default, Debug)]
#[require( #[require(
TextLayout, TextLayout,
TextStyle, TextFont,
TextColor,
TextBounds, TextBounds,
Anchor, Anchor,
SpriteSource, SpriteSource,
@ -141,7 +142,7 @@ pub fn extract_text2d_sprite(
&GlobalTransform, &GlobalTransform,
)>, )>,
>, >,
text_styles: Extract<Query<&TextStyle>>, text_styles: Extract<Query<(&TextFont, &TextColor)>>,
) { ) {
// TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621 // TODO: Support window-independent scaling: https://github.com/bevyengine/bevy/issues/5621
let scale_factor = windows let scale_factor = windows
@ -186,7 +187,7 @@ pub fn extract_text2d_sprite(
.map(|t| t.entity) .map(|t| t.entity)
.unwrap_or(Entity::PLACEHOLDER), .unwrap_or(Entity::PLACEHOLDER),
) )
.map(|style| LinearRgba::from(style.color)) .map(|(_, text_color)| LinearRgba::from(text_color.0))
.unwrap_or_default(); .unwrap_or_default();
current_span = *span_index; current_span = *span_index;
} }

View file

@ -1,10 +1,11 @@
use bevy_color::Color;
use bevy_ecs::{ use bevy_ecs::{
prelude::*, prelude::*,
system::{Query, SystemParam}, system::{Query, SystemParam},
}; };
use bevy_hierarchy::Children; use bevy_hierarchy::Children;
use crate::{TextSpan, TextStyle}; use crate::{TextColor, TextFont, TextSpan};
/// Helper trait for using the [`TextReader`] and [`TextWriter`] system params. /// Helper trait for using the [`TextReader`] and [`TextWriter`] system params.
pub trait TextSpanAccess: Component { pub trait TextSpanAccess: Component {
@ -49,13 +50,23 @@ impl TextIterScratch {
pub struct TextReader<'w, 's, R: TextRoot> { pub struct TextReader<'w, 's, R: TextRoot> {
// This is a local to avoid system ambiguities when TextReaders run in parallel. // This is a local to avoid system ambiguities when TextReaders run in parallel.
scratch: Local<'s, TextIterScratch>, 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< spans: Query<
'w, 'w,
's, 's,
( (
&'static TextSpan, &'static TextSpan,
&'static TextStyle, &'static TextFont,
&'static TextColor,
Option<&'static Children>, Option<&'static Children>,
), ),
>, >,
@ -80,18 +91,24 @@ impl<'w, 's, R: TextRoot> TextReader<'w, 's, R> {
&mut self, &mut self,
root_entity: Entity, root_entity: Entity,
index: usize, index: usize,
) -> Option<(Entity, usize, &str, &TextStyle)> { ) -> Option<(Entity, usize, &str, &TextFont, Color)> {
self.iter(root_entity).nth(index) 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. /// 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> { 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. /// Gets the [`TextFont`] 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> { pub fn get_font(&mut self, root_entity: Entity, index: usize) -> Option<&TextFont> {
self.get(root_entity, index).map(|(_, _, _, style)| style) 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<Color> {
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. /// 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() 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. /// Panics if there is no span at the requested index.
pub fn style(&mut self, root_entity: Entity, index: usize) -> &TextStyle { pub fn font(&mut self, root_entity: Entity, index: usize) -> &TextFont {
self.get_style(root_entity, index).unwrap() 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<Entity>, root_entity: Option<Entity>,
/// Stack of (children, next index into children). /// Stack of (children, next index into children).
stack: Vec<(&'a Children, usize)>, 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< spans: &'a Query<
'a, 'a,
'a, 'a,
( (
&'static TextSpan, &'static TextSpan,
&'static TextStyle, &'static TextFont,
&'static TextColor,
Option<&'static Children>, Option<&'static Children>,
), ),
>, >,
@ -133,15 +167,15 @@ pub struct TextSpanIter<'a, R: TextRoot> {
impl<'a, R: TextRoot> Iterator for TextSpanIter<'a, R> { impl<'a, R: TextRoot> Iterator for TextSpanIter<'a, R> {
/// Item = (entity in text block, hierarchy depth in the block, span text, span style). /// 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<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
// Root // Root
if let Some(root_entity) = self.root_entity.take() { 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 { if let Some(children) = maybe_children {
self.stack.push((children, 0)); 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; return None;
} }
@ -159,7 +193,7 @@ impl<'a, R: TextRoot> Iterator for TextSpanIter<'a, R> {
*idx += 1; *idx += 1;
let entity = *child; 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; continue;
}; };
@ -167,7 +201,7 @@ impl<'a, R: TextRoot> Iterator for TextSpanIter<'a, R> {
if let Some(children) = maybe_children { if let Some(children) = maybe_children {
self.stack.push((children, 0)); 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. // 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> { pub struct TextWriter<'w, 's, R: TextRoot> {
// This is a resource because two TextWriters can't run in parallel. // This is a resource because two TextWriters can't run in parallel.
scratch: ResMut<'w, TextIterScratch>, scratch: ResMut<'w, TextIterScratch>,
roots: Query<'w, 's, (&'static mut R, &'static mut TextStyle), Without<TextSpan>>, roots: Query<
spans: Query<'w, 's, (&'static mut TextSpan, &'static mut TextStyle), Without<R>>, 'w,
's,
(
&'static mut R,
&'static mut TextFont,
&'static mut TextColor,
),
Without<TextSpan>,
>,
spans: Query<
'w,
's,
(
&'static mut TextSpan,
&'static mut TextFont,
&'static mut TextColor,
),
Without<R>,
>,
children: Query<'w, 's, &'static Children>, children: Query<'w, 's, &'static Children>,
} }
@ -202,15 +254,16 @@ impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> {
&mut self, &mut self,
root_entity: Entity, root_entity: Entity,
index: usize, index: usize,
) -> Option<(Entity, usize, Mut<String>, Mut<TextStyle>)> { ) -> Option<(Entity, usize, Mut<String>, Mut<TextFont>, Mut<TextColor>)> {
// Root // Root
if index == 0 { 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(( return Some((
root_entity, root_entity,
0, 0,
text.map_unchanged(|t| t.write_span()), 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. // Note: We do this outside the loop due to borrow checker limitations.
let (text, style) = self.spans.get_mut(entity).unwrap(); let (text, font, color) = self.spans.get_mut(entity).unwrap();
Some((entity, depth, text.map_unchanged(|t| t.write_span()), style)) 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. /// 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<Mut<String>> { pub fn get_text(&mut self, root_entity: Entity, index: usize) -> Option<Mut<String>> {
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. /// Gets the [`TextFont`] 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<Mut<TextStyle>> { pub fn get_font(&mut self, root_entity: Entity, index: usize) -> Option<Mut<TextFont>> {
self.get(root_entity, index).map(|(_, _, _, style)| style) 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<Mut<TextColor>> {
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. /// 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() 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. /// Panics if there is no span at the requested index.
pub fn style(&mut self, root_entity: Entity, index: usize) -> Mut<TextStyle> { pub fn font(&mut self, root_entity: Entity, index: usize) -> Mut<TextFont> {
self.get_style(root_entity, index).unwrap() 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<TextColor> {
self.get_color(root_entity, index).unwrap()
} }
/// Invokes a callback on each span in a text block, starting with the root entity. /// Invokes a callback on each span in a text block, starting with the root entity.
pub fn for_each( pub fn for_each(
&mut self, &mut self,
root_entity: Entity, root_entity: Entity,
mut callback: impl FnMut(Entity, usize, Mut<String>, Mut<TextStyle>), mut callback: impl FnMut(Entity, usize, Mut<String>, Mut<TextFont>, Mut<TextColor>),
) { ) {
self.for_each_until(root_entity, |a, b, c, d| { self.for_each_until(root_entity, |a, b, c, d, e| {
(callback)(a, b, c, d); (callback)(a, b, c, d, e);
true true
}); });
} }
/// Invokes a callback on each span's string value in a text block, starting with the root entity. /// 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<String>)) { pub fn for_each_text(&mut self, root_entity: Entity, mut callback: impl FnMut(Mut<String>)) {
self.for_each(root_entity, |_, _, text, _| { self.for_each(root_entity, |_, _, text, _, _| {
(callback)(text); (callback)(text);
}); });
} }
/// Invokes a callback on each span's [`TextStyle`] in a text block, starting with the root entity. /// Invokes a callback on each span's [`TextFont`] in a text block, starting with the root entity.
pub fn for_each_style( pub fn for_each_font(&mut self, root_entity: Entity, mut callback: impl FnMut(Mut<TextFont>)) {
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, &mut self,
root_entity: Entity, root_entity: Entity,
mut callback: impl FnMut(Mut<TextStyle>), mut callback: impl FnMut(Mut<TextColor>),
) { ) {
self.for_each(root_entity, |_, _, _, style| { self.for_each(root_entity, |_, _, _, _, color| {
(callback)(style); (callback)(color);
}); });
} }
@ -322,17 +401,18 @@ impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> {
pub fn for_each_until( pub fn for_each_until(
&mut self, &mut self,
root_entity: Entity, root_entity: Entity,
mut callback: impl FnMut(Entity, usize, Mut<String>, Mut<TextStyle>) -> bool, mut callback: impl FnMut(Entity, usize, Mut<String>, Mut<TextFont>, Mut<TextColor>) -> bool,
) { ) {
// Root // 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; return;
}; };
if !(callback)( if !(callback)(
root_entity, root_entity,
0, 0,
text.map_unchanged(|t| t.write_span()), text.map_unchanged(|t| t.write_span()),
style, font,
color,
) { ) {
return; return;
} }
@ -362,11 +442,17 @@ impl<'w, 's, R: TextRoot> TextWriter<'w, 's, R> {
*idx += 1; *idx += 1;
let entity = *child; 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; 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); self.scratch.recover(stack);
return; return;
} }

View file

@ -26,7 +26,7 @@ fn calc_name(
for child in children { for child in children {
let values = text_reader let values = text_reader
.iter(child) .iter(child)
.map(|(_, _, text, _)| text.into()) .map(|(_, _, text, _, _)| text.into())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
if !values.is_empty() { if !values.is_empty() {
name = Some(values.join(" ")); name = Some(values.join(" "));
@ -120,7 +120,7 @@ fn label_changed(
for (entity, accessible) in &mut query { for (entity, accessible) in &mut query {
let values = text_reader let values = text_reader
.iter(entity) .iter(entity)
.map(|(_, _, text, _)| text.into()) .map(|(_, _, text, _, _)| text.into())
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let name = Some(values.join(" ").into_boxed_str()); let name = Some(values.join(" ").into_boxed_str());
if let Some(mut accessible) = accessible { if let Some(mut accessible) = accessible {

View file

@ -41,7 +41,7 @@ use bevy_render::{
use bevy_sprite::TextureAtlasLayout; use bevy_sprite::TextureAtlasLayout;
use bevy_sprite::{BorderRect, ImageScaleMode, SpriteAssetEvents, TextureAtlas}; use bevy_sprite::{BorderRect, ImageScaleMode, SpriteAssetEvents, TextureAtlas};
#[cfg(feature = "bevy_text")] #[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_transform::components::GlobalTransform;
use bevy_utils::HashMap; use bevy_utils::HashMap;
use box_shadow::BoxShadowPlugin; use box_shadow::BoxShadowPlugin;
@ -606,7 +606,7 @@ pub fn extract_text_sections(
&TextLayoutInfo, &TextLayoutInfo,
)>, )>,
>, >,
text_styles: Extract<Query<&TextStyle>>, text_styles: Extract<Query<&TextColor>>,
mapping: Extract<Query<&RenderEntity>>, mapping: Extract<Query<&RenderEntity>>,
) { ) {
let mut start = 0; let mut start = 0;
@ -681,7 +681,7 @@ pub fn extract_text_sections(
.map(|t| t.entity) .map(|t| t.entity)
.unwrap_or(Entity::PLACEHOLDER), .unwrap_or(Entity::PLACEHOLDER),
) )
.map(|style| LinearRgba::from(style.color)) .map(|text_color| LinearRgba::from(text_color.0))
.unwrap_or_default(); .unwrap_or_default();
current_span = *span_index; current_span = *span_index;
} }

View file

@ -2520,7 +2520,7 @@ impl<'w, 's> DefaultUiCamera<'w, 's> {
/// Marker for controlling whether Ui is rendered with or without anti-aliasing /// Marker for controlling whether Ui is rendered with or without anti-aliasing
/// in a camera. By default, Ui is always anti-aliased. /// 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::*; /// use bevy_core_pipeline::prelude::*;

View file

@ -3,6 +3,7 @@ use crate::{
NodeMeasure, Style, TargetCamera, UiScale, ZIndex, NodeMeasure, Style, TargetCamera, UiScale, ZIndex,
}; };
use bevy_asset::Assets; use bevy_asset::Assets;
use bevy_color::Color;
use bevy_derive::{Deref, DerefMut}; use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{ use bevy_ecs::{
change_detection::DetectChanges, change_detection::DetectChanges,
@ -19,8 +20,8 @@ use bevy_render::{camera::Camera, texture::Image, view::Visibility};
use bevy_sprite::TextureAtlasLayout; use bevy_sprite::TextureAtlasLayout;
use bevy_text::{ use bevy_text::{
scale_value, ComputedTextBlock, CosmicFontSystem, Font, FontAtlasSets, LineBreak, SwashCache, scale_value, ComputedTextBlock, CosmicFontSystem, Font, FontAtlasSets, LineBreak, SwashCache,
TextBounds, TextError, TextLayout, TextLayoutInfo, TextMeasureInfo, TextPipeline, TextReader, TextBounds, TextColor, TextError, TextFont, TextLayout, TextLayoutInfo, TextMeasureInfo,
TextRoot, TextSpanAccess, TextStyle, TextWriter, YAxisOrientation, TextPipeline, TextReader, TextRoot, TextSpanAccess, TextWriter, YAxisOrientation,
}; };
use bevy_transform::components::Transform; use bevy_transform::components::Transform;
use bevy_utils::{tracing::error, Entry}; 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. /// Note that [`Transform`] on this entity is managed automatically by the UI layout system.
/// ///
/* ///
``` /// ```
# use bevy_asset::Handle; /// # use bevy_asset::Handle;
# use bevy_color::Color; /// # use bevy_color::Color;
# use bevy_color::palettes::basic::BLUE; /// # use bevy_color::palettes::basic::BLUE;
# use bevy_ecs::World; /// # use bevy_ecs::world::World;
# use bevy_text::{Font, JustifyText, TextLayout, TextStyle}; /// # use bevy_text::{Font, JustifyText, TextLayout, TextFont, TextColor};
# use bevy_ui::Text; /// # use bevy_ui::prelude::Text;
# /// #
# let font_handle: Handle<Font> = Default::default(); /// # let font_handle: Handle<Font> = Default::default();
# let mut world = World::default(); /// # let mut world = World::default();
# /// #
// Basic usage. /// // Basic usage.
world.spawn(Text::new("hello world!")); /// world.spawn(Text::new("hello world!"));
///
// With non-default style. /// // With non-default style.
world.spawn(( /// world.spawn((
Text::new("hello world!"), /// Text::new("hello world!"),
TextStyle { /// TextFont {
font: font_handle.clone().into(), /// font: font_handle.clone().into(),
font_size: 60.0, /// font_size: 60.0,
color: BLUE.into(), /// ..Default::default()
} /// },
)); /// TextColor(BLUE.into()),
/// ));
// With text justification. ///
world.spawn(( /// // With text justification.
Text::new("hello world\nand bevy!"), /// world.spawn((
TextLayout::new_with_justify(JustifyText::Center) /// Text::new("hello world\nand bevy!"),
)); /// TextLayout::new_with_justify(JustifyText::Center)
``` /// ));
*/ /// ```
#[derive(Component, Debug, Default, Clone, Deref, DerefMut, Reflect)] #[derive(Component, Debug, Default, Clone, Deref, DerefMut, Reflect)]
#[reflect(Component, Default, Debug)] #[reflect(Component, Default, Debug)]
#[require( #[require(
TextLayout, TextLayout,
TextStyle, TextFont,
TextColor,
TextNodeFlags, TextNodeFlags,
Node, Node,
Style, // TODO: Remove when Node uses required components. Style, // TODO: Remove when Node uses required components.
@ -204,7 +206,7 @@ fn create_text_measure<'a>(
entity: Entity, entity: Entity,
fonts: &Assets<Font>, fonts: &Assets<Font>,
scale_factor: f64, scale_factor: f64,
spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextStyle)>, spans: impl Iterator<Item = (Entity, usize, &'a str, &'a TextFont, Color)>,
block: Ref<TextLayout>, block: Ref<TextLayout>,
text_pipeline: &mut TextPipeline, text_pipeline: &mut TextPipeline,
mut content_size: Mut<ContentSize>, mut content_size: Mut<ContentSize>,

View file

@ -2,7 +2,7 @@
A runtime warning. 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`. If you need to smoothly scale font size, use `Transform::scale`.

View file

@ -14,7 +14,7 @@ fn spawn_sprites(
texture_handle: Handle<Image>, texture_handle: Handle<Image>,
mut position: Vec3, mut position: Vec3,
slice_border: f32, slice_border: f32,
style: TextStyle, style: TextFont,
gap: f32, gap: f32,
) { ) {
let cases = [ let cases = [
@ -100,7 +100,7 @@ fn spawn_sprites(
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d); commands.spawn(Camera2d);
let font = asset_server.load("fonts/FiraSans-Bold.ttf"); let font = asset_server.load("fonts/FiraSans-Bold.ttf");
let style = TextStyle { let style = TextFont {
font: font.clone(), font: font.clone(),
..default() ..default()
}; };

View file

@ -35,7 +35,7 @@ struct AnimateScale;
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let font = asset_server.load("fonts/FiraSans-Bold.ttf"); let font = asset_server.load("fonts/FiraSans-Bold.ttf");
let text_style = TextStyle { let text_font = TextFont {
font: font.clone(), font: font.clone(),
font_size: 50.0, font_size: 50.0,
..default() ..default()
@ -46,27 +46,27 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Demonstrate changing translation // Demonstrate changing translation
commands.spawn(( commands.spawn((
Text2d::new("translation"), Text2d::new("translation"),
text_style.clone(), text_font.clone(),
TextLayout::new_with_justify(text_justification), TextLayout::new_with_justify(text_justification),
AnimateTranslation, AnimateTranslation,
)); ));
// Demonstrate changing rotation // Demonstrate changing rotation
commands.spawn(( commands.spawn((
Text2d::new("rotation"), Text2d::new("rotation"),
text_style.clone(), text_font.clone(),
TextLayout::new_with_justify(text_justification), TextLayout::new_with_justify(text_justification),
AnimateRotation, AnimateRotation,
)); ));
// Demonstrate changing scale // Demonstrate changing scale
commands.spawn(( commands.spawn((
Text2d::new("scale"), Text2d::new("scale"),
text_style, text_font,
TextLayout::new_with_justify(text_justification), TextLayout::new_with_justify(text_justification),
Transform::from_translation(Vec3::new(400.0, 0.0, 0.0)), Transform::from_translation(Vec3::new(400.0, 0.0, 0.0)),
AnimateScale, AnimateScale,
)); ));
// Demonstrate text wrapping // Demonstrate text wrapping
let slightly_smaller_text_style = TextStyle { let slightly_smaller_text_font = TextFont {
font, font,
font_size: 35.0, font_size: 35.0,
..default() ..default()
@ -81,7 +81,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.with_children(|builder| { .with_children(|builder| {
builder.spawn(( builder.spawn((
Text2d::new("this text wraps in the box\n(Unicode linebreaks)"), 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), TextLayout::new(JustifyText::Left, LineBreak::WordBoundary),
// Wrap text in the rectangle // Wrap text in the rectangle
TextBounds::from(box_size), TextBounds::from(box_size),
@ -100,7 +100,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.with_children(|builder| { .with_children(|builder| {
builder.spawn(( builder.spawn((
Text2d::new("this text wraps in the box\n(AnyCharacter linebreaks)"), 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), TextLayout::new(JustifyText::Left, LineBreak::AnyCharacter),
// Wrap text in the rectangle // Wrap text in the rectangle
TextBounds::from(other_box_size), TextBounds::from(other_box_size),
@ -112,7 +112,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Demonstrate font smoothing off // Demonstrate font smoothing off
commands.spawn(( commands.spawn((
Text2d::new("FontSmoothing::None"), Text2d::new("FontSmoothing::None"),
slightly_smaller_text_style slightly_smaller_text_font
.clone() .clone()
.with_font_smoothing(FontSmoothing::None), .with_font_smoothing(FontSmoothing::None),
Transform::from_translation(Vec3::new(-400.0, -250.0, 0.0)), Transform::from_translation(Vec3::new(-400.0, -250.0, 0.0)),
@ -126,10 +126,8 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
] { ] {
commands.spawn(( commands.spawn((
Text2d::new(format!(" Anchor::{text_anchor:?} ")), Text2d::new(format!(" Anchor::{text_anchor:?} ")),
TextStyle { slightly_smaller_text_font.clone(),
color, TextColor(color),
..slightly_smaller_text_style.clone()
},
Transform::from_translation(250. * Vec3::Y), Transform::from_translation(250. * Vec3::Y),
text_anchor, text_anchor,
)); ));

View file

@ -121,7 +121,7 @@ fn setup(
let font = asset_server.load("fonts/FiraSans-Bold.ttf"); let font = asset_server.load("fonts/FiraSans-Bold.ttf");
// padding label text style // padding label text style
let text_style: TextStyle = TextStyle { let text_style: TextFont = TextFont {
font: font.clone(), font: font.clone(),
font_size: 42.0, font_size: 42.0,
..default() ..default()
@ -184,7 +184,7 @@ fn setup(
]; ];
// label text style // label text style
let sampling_label_style = TextStyle { let sampling_label_style = TextFont {
font, font,
font_size: 25.0, font_size: 25.0,
..default() ..default()
@ -275,7 +275,7 @@ fn create_label(
commands: &mut Commands, commands: &mut Commands,
translation: (f32, f32, f32), translation: (f32, f32, f32),
text: &str, text: &str,
text_style: TextStyle, text_style: TextFont,
) { ) {
commands.spawn(( commands.spawn((
Text2d::new(text), Text2d::new(text),

View file

@ -127,7 +127,7 @@ fn setup(
..default() ..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"), commands.spawn((Text::new("Left / Right - Rotate Camera\nC - Toggle Compensation Curve\nM - Toggle Metering Mask\nV - Visualize Metering Mask"),
text_style.clone(), Style { text_style.clone(), Style {

View file

@ -160,18 +160,12 @@ fn setup(
// Controls Text // Controls Text
// We need the full version of this font so we can use box drawing characters. // 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 = TextFont {
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
let text_style = TextStyle {
font: font.clone(),
..default() ..default()
}; };
let label_text_style = TextStyle { let label_text_style = (text_style.clone(), TextColor(ORANGE.into()));
font,
color: ORANGE.into(),
..default()
};
commands.spawn((Text::new("Up / Down — Increase / Decrease Alpha\nLeft / Right — Rotate Camera\nH - Toggle HDR\nSpacebar — Toggle Unlit\nC — Randomize Colors"), 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(), text_style.clone(),

View file

@ -316,7 +316,7 @@ fn add_help_text(
) { ) {
commands.spawn(( commands.spawn((
Text::new(create_help_text(currently_selected_option)), Text::new(create_help_text(currently_selected_option)),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
..default() ..default()
}, },
@ -339,12 +339,12 @@ fn add_text<'a>(
) -> EntityCommands<'a> { ) -> EntityCommands<'a> {
parent.spawn(( parent.spawn((
Text::new(label), Text::new(label),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 15.0, font_size: 15.0,
color,
..default() ..default()
}, },
TextColor(color),
)) ))
} }
@ -598,8 +598,8 @@ fn update_ui_state(
Color::WHITE Color::WHITE
}; };
writer.for_each_style(entity, |mut style| { writer.for_each_color(entity, |mut text_color| {
style.color = color; text_color.0 = color;
}); });
// Update the displayed value, if this is the currently-selected option. // Update the displayed value, if this is the currently-selected option.

View file

@ -35,7 +35,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// a place to display the extras on screen // a place to display the extras on screen
commands.spawn(( commands.spawn((
Text::default(), Text::default(),
TextStyle { TextFont {
font_size: 15., font_size: 15.,
..default() ..default()
}, },

View file

@ -60,7 +60,7 @@ fn setup(
// labels // labels
commands.spawn(( commands.spawn((
Text::new("Perceptual Roughness"), Text::new("Perceptual Roughness"),
TextStyle { TextFont {
font_size: 30.0, font_size: 30.0,
..default() ..default()
}, },
@ -74,7 +74,7 @@ fn setup(
commands.spawn(( commands.spawn((
Text::new("Metallic"), Text::new("Metallic"),
TextStyle { TextFont {
font_size: 30.0, font_size: 30.0,
..default() ..default()
}, },
@ -92,7 +92,7 @@ fn setup(
commands.spawn(( commands.spawn((
Text::new("Loading Environment Map..."), Text::new("Loading Environment Map..."),
TextStyle { TextFont {
font_size: 30.0, font_size: 30.0,
..default() ..default()
}, },

View file

@ -172,11 +172,11 @@ fn setup_image_viewer_scene(
commands.spawn(( commands.spawn((
Text::new("Drag and drop an HDR or EXR file"), Text::new("Drag and drop an HDR or EXR file"),
TextStyle { TextFont {
font_size: 36.0, font_size: 36.0,
color: Color::BLACK,
..default() ..default()
}, },
TextColor(Color::BLACK),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
Style { Style {
align_self: AlignSelf::Center, align_self: AlignSelf::Center,

View file

@ -40,7 +40,7 @@ fn main() {
} }
impl AnimatableProperty for FontSizeProperty { impl AnimatableProperty for FontSizeProperty {
type Component = TextStyle; type Component = TextFont;
type Property = f32; type Property = f32;
@ -50,12 +50,12 @@ impl AnimatableProperty for FontSizeProperty {
} }
impl AnimatableProperty for TextColorProperty { impl AnimatableProperty for TextColorProperty {
type Component = TextStyle; type Component = TextColor;
type Property = Srgba; type Property = Srgba;
fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> { fn get_mut(component: &mut Self::Component) -> Option<&mut Self::Property> {
match component.color { match component.0 {
Color::Srgba(ref mut color) => Some(color), Color::Srgba(ref mut color) => Some(color),
_ => None, _ => None,
} }
@ -172,12 +172,12 @@ fn setup(
builder builder
.spawn(( .spawn((
Text::new("Bevy"), Text::new("Bevy"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 24.0, font_size: 24.0,
color: Color::Srgba(Srgba::RED),
..default() ..default()
}, },
TextColor(Color::Srgba(Srgba::RED)),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
)) ))
// Mark as an animation target. // Mark as an animation target.

View file

@ -37,12 +37,12 @@ impl AnimationEvent for MessageEvent {
fn edit_message( fn edit_message(
mut event_reader: EventReader<MessageEvent>, mut event_reader: EventReader<MessageEvent>,
text: Single<(&mut Text2d, &mut TextStyle), With<MessageText>>, text: Single<(&mut Text2d, &mut TextColor), With<MessageText>>,
) { ) {
let (mut text, mut style) = text.into_inner(); let (mut text, mut color) = text.into_inner();
for event in event_reader.read() { for event in event_reader.read() {
text.0 = event.value.clone(); text.0 = event.value.clone();
style.color = event.color; color.0 = event.color;
} }
} }
@ -69,11 +69,11 @@ fn setup(
commands.spawn(( commands.spawn((
MessageText, MessageText,
Text2d::default(), Text2d::default(),
TextStyle { TextFont {
font_size: 119.0, font_size: 119.0,
color: Color::NONE,
..default() ..default()
}, },
TextColor(Color::NONE),
)); ));
// Create a new animation clip. // Create a new animation clip.
@ -108,9 +108,9 @@ fn setup(
} }
// Slowly fade out the text opacity. // Slowly fade out the text opacity.
fn animate_text_opacity(mut styles: Query<&mut TextStyle>, time: Res<Time>) { fn animate_text_opacity(mut colors: Query<&mut TextColor>, time: Res<Time>) {
for mut style in &mut styles { for mut color in &mut colors {
let a = style.color.alpha(); let a = color.0.alpha();
style.color.set_alpha(a - time.delta_seconds()); color.0.set_alpha(a - time.delta_seconds());
} }
} }

View file

@ -272,11 +272,11 @@ fn setup_node_rects(commands: &mut Commands) {
let text = commands let text = commands
.spawn(( .spawn((
Text::new(node_string), Text::new(node_string),
TextStyle { TextFont {
font_size: 16.0, font_size: 16.0,
color: ANTIQUE_WHITE.into(),
..default() ..default()
}, },
TextColor(ANTIQUE_WHITE.into()),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
)) ))
.id(); .id();

View file

@ -237,19 +237,18 @@ fn setup_ui(mut commands: Commands) {
// The button will automatically become a child of the parent that owns the // The button will automatically become a child of the parent that owns the
// given `ChildBuilder`. // given `ChildBuilder`.
fn add_mask_group_control(parent: &mut ChildBuilder, label: &str, width: Val, mask_group_id: u32) { fn add_mask_group_control(parent: &mut ChildBuilder, label: &str, width: Val, mask_group_id: u32) {
let button_text_style = TextStyle { let button_text_style = (
TextFont {
font_size: 14.0, font_size: 14.0,
color: Color::WHITE,
..default() ..default()
}; },
let selected_button_text_style = TextStyle { TextColor::WHITE,
color: Color::BLACK, );
..button_text_style.clone() let selected_button_text_style = (button_text_style.0.clone(), TextColor::BLACK);
}; let label_text_style = (
let label_text_style = TextStyle { button_text_style.0.clone(),
color: Color::Srgba(LIGHT_GRAY), TextColor(Color::Srgba(LIGHT_GRAY)),
..button_text_style.clone() );
};
parent parent
.spawn(NodeBundle { .spawn(NodeBundle {
@ -493,8 +492,8 @@ fn update_ui(
continue; continue;
}; };
writer.for_each_style(text, |mut style| { writer.for_each_color(text, |mut color| {
style.color = if enabled { Color::BLACK } else { Color::WHITE }; color.0 = if enabled { Color::BLACK } else { Color::WHITE };
}); });
} }
} }

View file

@ -16,7 +16,7 @@ fn main() {
fn setup(mut commands: Commands) { fn setup(mut commands: Commands) {
commands.spawn(Camera2d); commands.spawn(Camera2d);
let text_style = TextStyle { let text_font = TextFont {
font_size: 10.0, font_size: 10.0,
..default() ..default()
}; };
@ -64,10 +64,8 @@ fn setup(mut commands: Commands) {
commands commands
.spawn(( .spawn((
Text2d(format!("{:?}", function)), Text2d(format!("{:?}", function)),
TextStyle { text_font.clone(),
color, TextColor(color),
..text_style.clone()
},
Transform::from_xyz( Transform::from_xyz(
i as f32 * 113.0 - 1280.0 / 2.0 + 25.0, i as f32 * 113.0 - 1280.0 / 2.0 + 25.0,
-100.0 - ((j as f32 * 250.0) - 300.0), -100.0 - ((j as f32 * 250.0) - 300.0),

View file

@ -182,11 +182,11 @@ fn setup_ui(mut commands: Commands) {
.with_children(|b| { .with_children(|b| {
b.spawn(( b.spawn((
Text::new("Loading...".to_owned()), Text::new("Loading...".to_owned()),
TextStyle { TextFont {
font_size: 53.0, font_size: 53.0,
color: Color::BLACK,
..Default::default() ..Default::default()
}, },
TextColor(Color::BLACK),
TextLayout::new_with_justify(JustifyText::Right), TextLayout::new_with_justify(JustifyText::Right),
LoadingText, LoadingText,
)); ));

View file

@ -54,12 +54,9 @@ fn read_stream(receiver: Res<StreamReceiver>, mut events: EventWriter<StreamEven
} }
fn spawn_text(mut commands: Commands, mut reader: EventReader<StreamEvent>) { fn spawn_text(mut commands: Commands, mut reader: EventReader<StreamEvent>) {
let text_style = TextStyle::default();
for (per_frame, event) in reader.read().enumerate() { for (per_frame, event) in reader.read().enumerate() {
commands.spawn(( commands.spawn((
Text2d::new(event.0.to_string()), Text2d::new(event.0.to_string()),
text_style.clone(),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
Transform::from_xyz(per_frame as f32 * 100.0, 300.0, 0.0), Transform::from_xyz(per_frame as f32 * 100.0, 300.0, 0.0),
)); ));

View file

@ -19,16 +19,16 @@ fn main() {
DefaultPlugins, DefaultPlugins,
FpsOverlayPlugin { FpsOverlayPlugin {
config: FpsOverlayConfig { config: FpsOverlayConfig {
text_config: TextStyle { text_config: TextFont {
// Here we define size of our overlay // Here we define size of our overlay
font_size: 42.0, font_size: 42.0,
// We can also change color of the overlay
color: OverlayColor::GREEN,
// If we want, we can use a custom font // If we want, we can use a custom font
font: default(), font: default(),
// We could also disable font smoothing, // We could also disable font smoothing,
font_smoothing: FontSmoothing::default(), font_smoothing: FontSmoothing::default(),
}, },
// We can also change color of the overlay
text_color: OverlayColor::GREEN,
enabled: true, enabled: true,
}, },
}, },
@ -67,10 +67,10 @@ fn setup(mut commands: Commands) {
fn customize_config(input: Res<ButtonInput<KeyCode>>, mut overlay: ResMut<FpsOverlayConfig>) { fn customize_config(input: Res<ButtonInput<KeyCode>>, mut overlay: ResMut<FpsOverlayConfig>) {
if input.just_pressed(KeyCode::Digit1) { if input.just_pressed(KeyCode::Digit1) {
// Changing resource will affect overlay // Changing resource will affect overlay
if overlay.text_config.color == OverlayColor::GREEN { if overlay.text_color == OverlayColor::GREEN {
overlay.text_config.color = OverlayColor::RED; overlay.text_color = OverlayColor::RED;
} else { } else {
overlay.text_config.color = OverlayColor::GREEN; overlay.text_color = OverlayColor::GREEN;
} }
} }
if input.just_pressed(KeyCode::Digit2) { if input.just_pressed(KeyCode::Digit2) {

View file

@ -76,10 +76,6 @@ fn setup(mut commands: Commands) {
"Click on a \"Mine\" to trigger it.\n\ "Click on a \"Mine\" to trigger it.\n\
When it explodes it will trigger all overlapping mines.", When it explodes it will trigger all overlapping mines.",
), ),
TextStyle {
color: Color::WHITE,
..default()
},
Style { Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
top: Val::Px(12.), top: Val::Px(12.),

View file

@ -106,10 +106,7 @@ fn setup_ui(mut commands: Commands) {
p.spawn(TextSpan::new("Last Triggered: ")); p.spawn(TextSpan::new("Last Triggered: "));
p.spawn(( p.spawn((
TextSpan::new("-"), TextSpan::new("-"),
TextStyle { TextColor(bevy::color::palettes::css::ORANGE.into()),
color: bevy::color::palettes::css::ORANGE.into(),
..default()
},
)); ));
}); });
} }

View file

@ -177,11 +177,11 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut game: ResMu
// scoreboard // scoreboard
commands.spawn(( commands.spawn((
Text::new("Score:"), Text::new("Score:"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.5, 0.5, 1.0),
..default() ..default()
}, },
TextColor(Color::srgb(0.5, 0.5, 1.0)),
Style { Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
top: Val::Px(5.0), top: Val::Px(5.0),
@ -405,10 +405,10 @@ fn display_score(mut commands: Commands, game: Res<Game>) {
}) })
.with_child(( .with_child((
Text::new(format!("Cake eaten: {}", game.cake_eaten)), Text::new(format!("Cake eaten: {}", game.cake_eaten)),
TextStyle { TextFont {
font_size: 67.0, font_size: 67.0,
color: Color::srgb(0.5, 0.5, 1.0),
..default() ..default()
}, },
TextColor(Color::srgb(0.5, 0.5, 1.0)),
)); ));
} }

View file

@ -219,11 +219,11 @@ fn setup(
commands commands
.spawn(( .spawn((
Text::new("Score: "), Text::new("Score: "),
TextStyle { TextFont {
font_size: SCOREBOARD_FONT_SIZE, font_size: SCOREBOARD_FONT_SIZE,
color: TEXT_COLOR,
..default() ..default()
}, },
TextColor(TEXT_COLOR),
ScoreboardUi, ScoreboardUi,
Style { Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
@ -234,11 +234,11 @@ fn setup(
)) ))
.with_child(( .with_child((
TextSpan::default(), TextSpan::default(),
TextStyle { TextFont {
font_size: SCOREBOARD_FONT_SIZE, font_size: SCOREBOARD_FONT_SIZE,
color: SCORE_COLOR,
..default() ..default()
}, },
TextColor(SCORE_COLOR),
)); ));
// Walls // Walls

View file

@ -128,7 +128,7 @@ fn setup_contributor_selection(mut commands: Commands, asset_server: Res<AssetSe
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d); commands.spawn(Camera2d);
let text_style = TextStyle { let text_style = TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 60.0, font_size: 60.0,
..default() ..default()
@ -148,7 +148,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
)) ))
.with_child(( .with_child((
TextSpan::default(), TextSpan::default(),
TextStyle { TextFont {
font_size: 30., font_size: 30.,
..text_style ..text_style
}, },
@ -216,7 +216,7 @@ fn select(
contributor.num_commits, contributor.num_commits,
if contributor.num_commits > 1 { "s" } else { "" } if contributor.num_commits > 1 { "s" } else { "" }
); );
writer.style(entity, 0).color = sprite.color; writer.color(entity, 0).0 = sprite.color;
} }
/// Change the tint color to the "deselected" color and push /// Change the tint color to the "deselected" color and push

View file

@ -107,7 +107,7 @@ fn setup(
// Spawn the text instructions // Spawn the text instructions
let font = asset_server.load("fonts/FiraSans-Bold.ttf"); let font = asset_server.load("fonts/FiraSans-Bold.ttf");
let text_style = TextStyle { let text_style = TextFont {
font: font.clone(), font: font.clone(),
font_size: 25.0, font_size: 25.0,
..default() ..default()

View file

@ -176,11 +176,11 @@ mod game {
.with_children(|p| { .with_children(|p| {
p.spawn(( p.spawn((
Text::new("Will be back to the menu shortly..."), Text::new("Will be back to the menu shortly..."),
TextStyle { TextFont {
font_size: 67.0, font_size: 67.0,
color: TEXT_COLOR,
..default() ..default()
}, },
TextColor(TEXT_COLOR),
Style { Style {
margin: UiRect::all(Val::Px(50.0)), margin: UiRect::all(Val::Px(50.0)),
..default() ..default()
@ -196,27 +196,27 @@ mod game {
.with_children(|p| { .with_children(|p| {
p.spawn(( p.spawn((
TextSpan(format!("quality: {:?}", *display_quality)), TextSpan(format!("quality: {:?}", *display_quality)),
TextStyle { TextFont {
font_size: 50.0, font_size: 50.0,
color: BLUE.into(),
..default() ..default()
}, },
TextColor(BLUE.into()),
)); ));
p.spawn(( p.spawn((
TextSpan::new(" - "), TextSpan::new(" - "),
TextStyle { TextFont {
font_size: 50.0, font_size: 50.0,
color: TEXT_COLOR,
..default() ..default()
}, },
TextColor(TEXT_COLOR),
)); ));
p.spawn(( p.spawn((
TextSpan(format!("volume: {:?}", *volume)), TextSpan(format!("volume: {:?}", *volume)),
TextStyle { TextFont {
font_size: 50.0, font_size: 50.0,
color: LIME.into(),
..default() ..default()
}, },
TextColor(LIME.into()),
)); ));
}); });
}); });
@ -398,9 +398,8 @@ mod menu {
left: Val::Px(10.0), left: Val::Px(10.0),
..default() ..default()
}; };
let button_text_style = TextStyle { let button_text_font = TextFont {
font_size: 33.0, font_size: 33.0,
color: TEXT_COLOR,
..default() ..default()
}; };
@ -433,11 +432,11 @@ mod menu {
// Display the game name // Display the game name
parent.spawn(( parent.spawn((
Text::new("Bevy Game Menu UI"), Text::new("Bevy Game Menu UI"),
TextStyle { TextFont {
font_size: 67.0, font_size: 67.0,
color: TEXT_COLOR,
..default() ..default()
}, },
TextColor(TEXT_COLOR),
Style { Style {
margin: UiRect::all(Val::Px(50.0)), margin: UiRect::all(Val::Px(50.0)),
..default() ..default()
@ -464,7 +463,11 @@ mod menu {
image: UiImage::new(icon), image: UiImage::new(icon),
..default() ..default()
}); });
parent.spawn((Text::new("New Game"), button_text_style.clone())); parent.spawn((
Text::new("New Game"),
button_text_font.clone(),
TextColor(TEXT_COLOR),
));
}); });
parent parent
.spawn(( .spawn((
@ -482,7 +485,11 @@ mod menu {
image: UiImage::new(icon), image: UiImage::new(icon),
..default() ..default()
}); });
parent.spawn((Text::new("Settings"), button_text_style.clone())); parent.spawn((
Text::new("Settings"),
button_text_font.clone(),
TextColor(TEXT_COLOR),
));
}); });
parent parent
.spawn(( .spawn((
@ -500,7 +507,11 @@ mod menu {
image: UiImage::new(icon), image: UiImage::new(icon),
..default() ..default()
}); });
parent.spawn((Text::new("Quit"), button_text_style)); parent.spawn((
Text::new("Quit"),
button_text_font,
TextColor(TEXT_COLOR),
));
}); });
}); });
}); });
@ -516,11 +527,13 @@ mod menu {
..default() ..default()
}; };
let button_text_style = TextStyle { let button_text_style = (
TextFont {
font_size: 33.0, font_size: 33.0,
color: TEXT_COLOR,
..default() ..default()
}; },
TextColor(TEXT_COLOR),
);
commands commands
.spawn(( .spawn((
@ -579,11 +592,13 @@ mod menu {
align_items: AlignItems::Center, align_items: AlignItems::Center,
..default() ..default()
}; };
let button_text_style = TextStyle { let button_text_style = (
TextFont {
font_size: 33.0, font_size: 33.0,
color: TEXT_COLOR,
..default() ..default()
}; },
TextColor(TEXT_COLOR),
);
commands commands
.spawn(( .spawn((
@ -683,11 +698,13 @@ mod menu {
align_items: AlignItems::Center, align_items: AlignItems::Center,
..default() ..default()
}; };
let button_text_style = TextStyle { let button_text_style = (
TextFont {
font_size: 33.0, font_size: 33.0,
color: TEXT_COLOR,
..default() ..default()
}; },
TextColor(TEXT_COLOR),
);
commands commands
.spawn(( .spawn((

View file

@ -77,7 +77,7 @@ fn setup(mut commands: Commands) {
commands.insert_resource(level_data); commands.insert_resource(level_data);
// Spawns the UI that will show the user prompts. // Spawns the UI that will show the user prompts.
let text_style = TextStyle { let text_style = TextFont {
font_size: 42.0, font_size: 42.0,
..default() ..default()
}; };
@ -239,7 +239,7 @@ struct LoadingScreen;
// Spawns the necessary components for the loading screen. // Spawns the necessary components for the loading screen.
fn load_loading_screen(mut commands: Commands) { fn load_loading_screen(mut commands: Commands) {
let text_style = TextStyle { let text_style = TextFont {
font_size: 67.0, font_size: 67.0,
..default() ..default()
}; };

View file

@ -116,11 +116,11 @@ fn build_ui(
let schedule = schedules.get(*label).unwrap(); let schedule = schedules.get(*label).unwrap();
text_spans.push(( text_spans.push((
TextSpan(format!("{label:?}\n")), TextSpan(format!("{label:?}\n")),
TextStyle { TextFont {
font: asset_server.load(FONT_BOLD), font: asset_server.load(FONT_BOLD),
color: FONT_COLOR,
..default() ..default()
}, },
TextColor(FONT_COLOR),
)); ));
// grab the list of systems in the schedule, in the order the // grab the list of systems in the schedule, in the order the
@ -144,19 +144,15 @@ fn build_ui(
// Add a text section for displaying the cursor for this system // Add a text section for displaying the cursor for this system
text_spans.push(( text_spans.push((
TextSpan::new(" "), TextSpan::new(" "),
TextStyle { TextFont::default(),
color: FONT_COLOR, TextColor(FONT_COLOR),
..default()
},
)); ));
// add the name of the system to the ui // add the name of the system to the ui
text_spans.push(( text_spans.push((
TextSpan(format!("{}\n", system.name())), TextSpan(format!("{}\n", system.name())),
TextStyle { TextFont::default(),
color: FONT_COLOR, TextColor(FONT_COLOR),
..default()
},
)); ));
} }
} }
@ -196,11 +192,11 @@ fn build_stepping_hint(mut commands: Commands) {
// stepping description box // stepping description box
commands.spawn(( commands.spawn((
Text::new(hint_text), Text::new(hint_text),
TextStyle { TextFont {
font_size: 15.0, font_size: 15.0,
color: FONT_COLOR,
..default() ..default()
}, },
TextColor(FONT_COLOR),
Style { Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
bottom: Val::Px(5.0), bottom: Val::Px(5.0),

View file

@ -134,11 +134,11 @@ pub fn spawn_ui_text<'a>(
) -> EntityCommands<'a> { ) -> EntityCommands<'a> {
parent.spawn(( parent.spawn((
Text::new(label), Text::new(label),
TextStyle { TextFont {
font_size: 18.0, font_size: 18.0,
color,
..default() ..default()
}, },
TextColor(color),
)) ))
} }
@ -166,12 +166,12 @@ pub fn update_ui_radio_button(background_color: &mut BackgroundColor, selected:
background_color.0 = if selected { Color::WHITE } else { Color::BLACK }; background_color.0 = if selected { Color::WHITE } else { Color::BLACK };
} }
/// Updates the style of the label of a radio button to reflect its selected /// Updates the color of the label of a radio button to reflect its selected
/// status. /// status.
pub fn update_ui_radio_button_text(entity: Entity, writer: &mut UiTextWriter, selected: bool) { pub fn update_ui_radio_button_text(entity: Entity, writer: &mut UiTextWriter, selected: bool) {
let text_color = if selected { Color::BLACK } else { Color::WHITE }; let text_color = if selected { Color::BLACK } else { Color::WHITE };
writer.for_each_style(entity, |mut style| { writer.for_each_color(entity, |mut color| {
style.color = text_color; color.0 = text_color;
}); });
} }

View file

@ -55,7 +55,7 @@ fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
p.spawn(TextSpan::new("IME Buffer: ")); p.spawn(TextSpan::new("IME Buffer: "));
p.spawn(( p.spawn((
TextSpan::new("\n"), TextSpan::new("\n"),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
..default() ..default()
}, },
@ -64,7 +64,7 @@ fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(( commands.spawn((
Text2d::new(""), Text2d::new(""),
TextStyle { TextFont {
font, font,
font_size: 100.0, font_size: 100.0,
..default() ..default()
@ -137,7 +137,7 @@ fn listen_ime_events(
fn listen_keyboard_input_events( fn listen_keyboard_input_events(
mut commands: Commands, mut commands: Commands,
mut events: EventReader<KeyboardInput>, mut events: EventReader<KeyboardInput>,
mut edit_text: Query<(&mut Text2d, &TextStyle), (Without<Node>, Without<Bubble>)>, mut edit_text: Query<(&mut Text2d, &TextFont), (Without<Node>, Without<Bubble>)>,
) { ) {
for event in events.read() { for event in events.read() {
// Only trigger changes when the key is first pressed. // Only trigger changes when the key is first pressed.

View file

@ -75,7 +75,7 @@ fn setup(mut commands: Commands) {
C: Toggle cyclic curve construction"; C: Toggle cyclic curve construction";
let spline_mode_text = format!("Spline: {spline_mode}"); let spline_mode_text = format!("Spline: {spline_mode}");
let cycling_mode_text = format!("{cycling_mode}"); let cycling_mode_text = format!("{cycling_mode}");
let style = TextStyle::default(); let style = TextFont::default();
commands commands
.spawn(NodeBundle { .spawn(NodeBundle {

View file

@ -132,11 +132,11 @@ fn setup_scene(
}) })
.with_child(( .with_child((
Text::new("Test Button"), Text::new("Test Button"),
TextStyle { TextFont {
font_size: 30.0, font_size: 30.0,
color: Color::BLACK,
..default() ..default()
}, },
TextColor::BLACK,
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
)); ));
} }

View file

@ -156,7 +156,7 @@ fn spawn_text(mut commands: Commands) {
}) })
.with_child(( .with_child((
Text::new("Move the player with WASD"), Text::new("Move the player with WASD"),
TextStyle { TextFont {
font_size: 25.0, font_size: 25.0,
..default() ..default()
}, },

View file

@ -42,15 +42,15 @@ fn setup(
}, },
) )
.observe( .observe(
|evt: Trigger<Pointer<Out>>, mut texts: Query<&mut TextStyle>| { |evt: Trigger<Pointer<Out>>, mut texts: Query<&mut TextColor>| {
let mut style = texts.get_mut(evt.entity()).unwrap(); let mut color = texts.get_mut(evt.entity()).unwrap();
style.color = Color::WHITE; color.0 = Color::WHITE;
}, },
) )
.observe( .observe(
|evt: Trigger<Pointer<Over>>, mut texts: Query<&mut TextStyle>| { |evt: Trigger<Pointer<Over>>, mut texts: Query<&mut TextColor>| {
let mut style = texts.get_mut(evt.entity()).unwrap(); let mut color = texts.get_mut(evt.entity()).unwrap();
style.color = CYAN_400.into(); color.0 = CYAN_400.into();
}, },
); );
// circular base // circular base

View file

@ -148,7 +148,7 @@ fn infotext_system(mut commands: Commands) {
commands.spawn(Camera2d); commands.spawn(Camera2d);
commands.spawn(( commands.spawn((
Text::new("Nothing to see in this window! Check the console output!"), Text::new("Nothing to see in this window! Check the console output!"),
TextStyle { TextFont {
font_size: 42.0, font_size: 42.0,
..default() ..default()
}, },

View file

@ -229,8 +229,8 @@ fn toggle_prepass_view(
}; };
let text = text.single(); let text = text.single();
*writer.text(text, 1) = format!("Prepass Output: {label}\n"); *writer.text(text, 1) = format!("Prepass Output: {label}\n");
writer.for_each_style(text, |mut style| { writer.for_each_color(text, |mut color| {
style.color = Color::WHITE; color.0 = Color::WHITE;
}); });
let handle = material_handle.single(); let handle = material_handle.single();

View file

@ -370,11 +370,11 @@ mod ui {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Play"), Text::new("Play"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.9, 0.9),
..default() ..default()
}, },
TextColor(Color::srgb(0.9, 0.9, 0.9)),
)); ));
}); });
@ -402,11 +402,11 @@ mod ui {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Tutorial"), Text::new("Tutorial"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.9, 0.9),
..default() ..default()
}, },
TextColor(Color::srgb(0.9, 0.9, 0.9)),
)); ));
}); });
}) })
@ -500,11 +500,11 @@ mod ui {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Paused"), Text::new("Paused"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.9, 0.9),
..default() ..default()
}, },
TextColor(Color::srgb(0.9, 0.9, 0.9)),
)); ));
}); });
}); });
@ -532,11 +532,11 @@ mod ui {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("TURBO MODE"), Text::new("TURBO MODE"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.3, 0.1),
..default() ..default()
}, },
TextColor(Color::srgb(0.9, 0.3, 0.1)),
)); ));
}); });
} }
@ -574,37 +574,37 @@ mod ui {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Move the bevy logo with the arrow keys"), Text::new("Move the bevy logo with the arrow keys"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.3, 0.3, 0.7),
..default() ..default()
}, },
TextColor(Color::srgb(0.3, 0.3, 0.7)),
)); ));
parent.spawn(( parent.spawn((
Text::new("Press T to enter TURBO MODE"), Text::new("Press T to enter TURBO MODE"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.3, 0.3, 0.7),
..default() ..default()
}, },
TextColor(Color::srgb(0.3, 0.3, 0.7)),
)); ));
parent.spawn(( parent.spawn((
Text::new("Press SPACE to pause"), Text::new("Press SPACE to pause"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.3, 0.3, 0.7),
..default() ..default()
}, },
TextColor(Color::srgb(0.3, 0.3, 0.7)),
)); ));
parent.spawn(( parent.spawn((
Text::new("Press ESCAPE to return to the menu"), Text::new("Press ESCAPE to return to the menu"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.3, 0.3, 0.7),
..default() ..default()
}, },
TextColor(Color::srgb(0.3, 0.3, 0.7)),
)); ));
}); });
} }
@ -631,20 +631,20 @@ mod ui {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Press SPACE to resume"), Text::new("Press SPACE to resume"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.3, 0.3, 0.7),
..default() ..default()
}, },
TextColor(Color::srgb(0.3, 0.3, 0.7)),
)); ));
parent.spawn(( parent.spawn((
Text::new("Press ESCAPE to return to the menu"), Text::new("Press ESCAPE to return to the menu"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.3, 0.3, 0.7),
..default() ..default()
}, },
TextColor(Color::srgb(0.3, 0.3, 0.7)),
)); ));
}); });
} }

View file

@ -272,11 +272,11 @@ fn setup_menu(mut commands: Commands) {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Play"), Text::new("Play"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.9, 0.9),
..default() ..default()
}, },
TextColor(Color::srgb(0.9, 0.9, 0.9)),
)); ));
}); });
}) })

View file

@ -80,11 +80,11 @@ fn setup_menu(mut commands: Commands) {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Play"), Text::new("Play"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.9, 0.9),
..default() ..default()
}, },
TextColor(Color::srgb(0.9, 0.9, 0.9)),
)); ));
}); });
}) })

View file

@ -185,11 +185,11 @@ mod ui {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Play"), Text::new("Play"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.9, 0.9),
..default() ..default()
}, },
TextColor(Color::srgb(0.9, 0.9, 0.9)),
)); ));
}); });
}) })
@ -237,11 +237,11 @@ mod ui {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Paused"), Text::new("Paused"),
TextStyle { TextFont {
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.9, 0.9),
..default() ..default()
}, },
TextColor(Color::srgb(0.9, 0.9, 0.9)),
)); ));
}); });
}); });

View file

@ -255,16 +255,9 @@ fn setup(
transform_rng: ChaCha8Rng::seed_from_u64(42), transform_rng: ChaCha8Rng::seed_from_u64(42),
}; };
let lime_text = TextStyle { let font = TextFont {
font_size: 40.0, font_size: 40.0,
color: LIME.into(), ..Default::default()
..default()
};
let aqua_text = TextStyle {
font_size: 40.0,
color: LIME.into(),
..default()
}; };
commands.spawn(Camera2d); commands.spawn(Camera2d);
@ -283,14 +276,30 @@ fn setup(
)) ))
.with_children(|p| { .with_children(|p| {
p.spawn((Text::default(), StatsText)).with_children(|p| { p.spawn((Text::default(), StatsText)).with_children(|p| {
p.spawn((TextSpan::new("Bird Count: "), lime_text.clone())); p.spawn((
p.spawn((TextSpan::new(""), aqua_text.clone())); TextSpan::new("Bird Count: "),
p.spawn((TextSpan::new("\nFPS (raw): "), lime_text.clone())); font.clone(),
p.spawn((TextSpan::new(""), aqua_text.clone())); TextColor(LIME.into()),
p.spawn((TextSpan::new("\nFPS (SMA): "), lime_text.clone())); ));
p.spawn((TextSpan::new(""), aqua_text.clone())); p.spawn((TextSpan::new(""), font.clone(), TextColor(AQUA.into())));
p.spawn((TextSpan::new("\nFPS (EMA): "), lime_text.clone())); p.spawn((
p.spawn((TextSpan::new(""), aqua_text.clone())); TextSpan::new("\nFPS (raw): "),
font.clone(),
TextColor(LIME.into()),
));
p.spawn((TextSpan::new(""), font.clone(), TextColor(AQUA.into())));
p.spawn((
TextSpan::new("\nFPS (SMA): "),
font.clone(),
TextColor(LIME.into()),
));
p.spawn((TextSpan::new(""), font.clone(), TextColor(AQUA.into())));
p.spawn((
TextSpan::new("\nFPS (EMA): "),
font.clone(),
TextColor(LIME.into()),
));
p.spawn((TextSpan::new(""), font.clone(), TextColor(AQUA.into())));
}); });
}); });

View file

@ -5,6 +5,7 @@ use bevy::{
color::palettes::css::ORANGE_RED, color::palettes::css::ORANGE_RED,
diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin}, diagnostic::{FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin},
prelude::*, prelude::*,
text::TextColor,
window::{PresentMode, WindowResolution}, window::{PresentMode, WindowResolution},
winit::{UpdateMode, WinitSettings}, winit::{UpdateMode, WinitSettings},
}; };
@ -69,7 +70,7 @@ fn main() {
focused_mode: UpdateMode::Continuous, focused_mode: UpdateMode::Continuous,
unfocused_mode: UpdateMode::Continuous, unfocused_mode: UpdateMode::Continuous,
}) })
.add_systems(Update, button_system); .add_systems(Update, (button_system, set_text_colors_changed));
if args.grid { if args.grid {
app.add_systems(Startup, setup_grid); app.add_systems(Startup, setup_grid);
@ -96,6 +97,12 @@ fn main() {
app.insert_resource(args).run(); app.insert_resource(args).run();
} }
fn set_text_colors_changed(mut colors: Query<&mut TextColor>) {
for mut text_color in colors.iter_mut() {
text_color.set_changed();
}
}
#[derive(Component)] #[derive(Component)]
struct IdleColor(Color); struct IdleColor(Color);
@ -264,11 +271,11 @@ fn spawn_button(
builder.with_children(|parent| { builder.with_children(|parent| {
parent.spawn(( parent.spawn((
Text(format!("{column}, {row}")), Text(format!("{column}, {row}")),
TextStyle { TextFont {
font_size: FONT_SIZE, font_size: FONT_SIZE,
color: Color::srgb(0.2, 0.2, 0.2),
..default() ..default()
}, },
TextColor(Color::srgb(0.2, 0.2, 0.2)),
)); ));
}); });
} }

View file

@ -46,7 +46,7 @@ fn setup(mut commands: Commands) {
commands.spawn(Camera2d); commands.spawn(Camera2d);
let text_string = "0123456789".repeat(10_000); let text_string = "0123456789".repeat(10_000);
let text_style = TextStyle { let text_font = TextFont {
font_size: 4., font_size: 4.,
..Default::default() ..Default::default()
}; };
@ -74,15 +74,12 @@ fn setup(mut commands: Commands) {
}, },
..Default::default() ..Default::default()
}) })
.with_child((Text(text_string.clone()), text_style.clone(), text_block)); .with_child((Text(text_string.clone()), text_font.clone(), text_block));
}); });
commands.spawn(( commands.spawn((
Text2d::new(text_string), Text2d::new(text_string),
TextStyle { TextColor(RED.into()),
color: RED.into(),
..text_style
},
bevy::sprite::Anchor::Center, bevy::sprite::Anchor::Center,
TextBounds::new_horizontal(1000.), TextBounds::new_horizontal(1000.),
text_block, text_block,

View file

@ -44,21 +44,21 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
[ [
( (
TextSpan("text".repeat(i)), TextSpan("text".repeat(i)),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraMono-Medium.ttf"), font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: (4 + i % 10) as f32, font_size: (4 + i % 10) as f32,
color: BLUE.into(),
..Default::default() ..Default::default()
}, },
TextColor(BLUE.into()),
), ),
( (
TextSpan("pipeline".repeat(i)), TextSpan("pipeline".repeat(i)),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: (4 + i % 11) as f32, font_size: (4 + i % 11) as f32,
color: YELLOW.into(),
..default() ..default()
}, },
TextColor(YELLOW.into()),
), ),
] ]
}; };

View file

@ -93,7 +93,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut time: ResMu
// real time info // real time info
builder.spawn(( builder.spawn((
Text::default(), Text::default(),
TextStyle { TextFont {
font_size, font_size,
..default() ..default()
}, },
@ -103,22 +103,22 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut time: ResMu
// keybindings // keybindings
builder.spawn(( builder.spawn((
Text::new("CONTROLS\nUn/Pause: Space\nSpeed+: Up\nSpeed-: Down"), Text::new("CONTROLS\nUn/Pause: Space\nSpeed+: Up\nSpeed-: Down"),
TextStyle { TextFont {
font_size, font_size,
color: Color::srgb(0.85, 0.85, 0.85),
..default() ..default()
}, },
TextColor(Color::srgb(0.85, 0.85, 0.85)),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
)); ));
// virtual time info // virtual time info
builder.spawn(( builder.spawn((
Text::default(), Text::default(),
TextStyle { TextFont {
font_size, font_size,
color: virtual_color,
..default() ..default()
}, },
TextColor(virtual_color),
TextLayout::new_with_justify(JustifyText::Right), TextLayout::new_with_justify(JustifyText::Right),
VirtualTime, VirtualTime,
)); ));

View file

@ -297,7 +297,7 @@ fn setup_sticks(
Transform::from_xyz(dead_mid, dead_mid, 3.), Transform::from_xyz(dead_mid, dead_mid, 3.),
)); ));
// text // text
let style = TextStyle { let style = TextFont {
font_size: 13., font_size: 13.,
..default() ..default()
}; };
@ -362,7 +362,7 @@ fn setup_triggers(
parent.spawn(( parent.spawn((
Transform::from_xyz(0., 0., 1.), Transform::from_xyz(0., 0., 1.),
Text(format!("{:.3}", 0.)), Text(format!("{:.3}", 0.)),
TextStyle { TextFont {
font_size: 13., font_size: 13.,
..default() ..default()
}, },

View file

@ -122,7 +122,7 @@ impl fmt::Display for Target {
} }
} }
impl Target { impl Target {
fn text_span(&self, key: &str, style: TextStyle) -> (String, TextStyle) { fn text_span(&self, key: &str, style: TextFont) -> (String, TextFont) {
(format!("[{key}] {self}\n"), style) (format!("[{key}] {self}\n"), style)
} }
fn new( fn new(
@ -253,7 +253,7 @@ fn detect_morphs(
detected.extend(targets); detected.extend(targets);
} }
detected.truncate(AVAILABLE_KEYS.len()); detected.truncate(AVAILABLE_KEYS.len());
let style = TextStyle { let style = TextFont {
font_size: 13.0, font_size: 13.0,
..default() ..default()
}; };

View file

@ -161,7 +161,7 @@ fn setup(mut commands: Commands) {
let label_node = commands let label_node = commands
.spawn(( .spawn((
Text::new(label), Text::new(label),
TextStyle { TextFont {
font_size: 9.0, font_size: 9.0,
..Default::default() ..Default::default()
}, },
@ -230,7 +230,7 @@ fn setup(mut commands: Commands) {
let label_node = commands let label_node = commands
.spawn(( .spawn((
Text::new(label), Text::new(label),
TextStyle { TextFont {
font_size: 9.0, font_size: 9.0,
..Default::default() ..Default::default()
}, },
@ -267,7 +267,7 @@ fn setup(mut commands: Commands) {
.with_children(|builder| { .with_children(|builder| {
builder.spawn(( builder.spawn((
Text::new("Borders"), Text::new("Borders"),
TextStyle { TextFont {
font_size: 20.0, font_size: 20.0,
..Default::default() ..Default::default()
}, },
@ -292,7 +292,7 @@ fn setup(mut commands: Commands) {
.with_children(|builder| { .with_children(|builder| {
builder.spawn(( builder.spawn((
Text::new("Borders Rounded"), Text::new("Borders Rounded"),
TextStyle { TextFont {
font_size: 20.0, font_size: 20.0,
..Default::default() ..Default::default()
}, },

View file

@ -85,12 +85,12 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
}) })
.with_child(( .with_child((
Text::new("Button"), Text::new("Button"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.9, 0.9),
..default() ..default()
}, },
TextColor(Color::srgb(0.9, 0.9, 0.9)),
)); ));
}); });
} }

View file

@ -76,7 +76,7 @@ impl TargetUpdate for Target<Visibility> {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
let palette: [Color; 4] = PALETTE.map(|hex| Srgba::hex(hex).unwrap().into()); let palette: [Color; 4] = PALETTE.map(|hex| Srgba::hex(hex).unwrap().into());
let text_style = TextStyle { let text_font = TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
..default() ..default()
}; };
@ -95,7 +95,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..Default::default() ..Default::default()
}).with_children(|parent| { }).with_children(|parent| {
parent.spawn((Text::new("Use the panel on the right to change the Display and Visibility properties for the respective nodes of the panel on the left"), parent.spawn((Text::new("Use the panel on the right to change the Display and Visibility properties for the respective nodes of the panel on the left"),
text_style.clone(), text_font.clone(),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
Style { Style {
margin: UiRect::bottom(Val::Px(10.)), margin: UiRect::bottom(Val::Px(10.)),
@ -133,7 +133,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
}, },
..Default::default() ..Default::default()
}).with_children(|parent| { }).with_children(|parent| {
spawn_right_panel(parent, text_style, &palette, target_ids); spawn_right_panel(parent, text_font, &palette, target_ids);
}); });
}); });
@ -147,21 +147,23 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
}, },
..default() }) ..default() })
.with_children(|builder| { .with_children(|builder| {
let text_style = TextStyle { let text_font = TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
..default() ..default()
}; };
builder.spawn((Text::new("Display::None\nVisibility::Hidden\nVisibility::Inherited"), builder.spawn((Text::new("Display::None\nVisibility::Hidden\nVisibility::Inherited"),
TextStyle { color: HIDDEN_COLOR, ..text_style.clone() }, text_font.clone(),
TextColor(HIDDEN_COLOR),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
)); ));
builder.spawn((Text::new("-\n-\n-"), builder.spawn((Text::new("-\n-\n-"),
TextStyle { color: DARK_GRAY.into(), ..text_style.clone() }, text_font.clone(),
TextColor(DARK_GRAY.into()),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
)); ));
builder.spawn((Text::new("The UI Node and its descendants will not be visible and will not be allotted any space in the UI layout.\nThe UI Node will not be visible but will still occupy space in the UI layout.\nThe UI node will inherit the visibility property of its parent. If it has no parent it will be visible."), builder.spawn((Text::new("The UI Node and its descendants will not be visible and will not be allotted any space in the UI layout.\nThe UI Node will not be visible but will still occupy space in the UI layout.\nThe UI node will inherit the visibility property of its parent. If it has no parent it will be visible."),
text_style text_font
)); ));
}); });
}); });
@ -275,13 +277,13 @@ fn spawn_left_panel(builder: &mut ChildBuilder, palette: &[Color; 4]) -> Vec<Ent
fn spawn_right_panel( fn spawn_right_panel(
parent: &mut ChildBuilder, parent: &mut ChildBuilder,
text_style: TextStyle, text_font: TextFont,
palette: &[Color; 4], palette: &[Color; 4],
mut target_ids: Vec<Entity>, mut target_ids: Vec<Entity>,
) { ) {
let spawn_buttons = |parent: &mut ChildBuilder, target_id| { let spawn_buttons = |parent: &mut ChildBuilder, target_id| {
spawn_button::<Display>(parent, text_style.clone(), target_id); spawn_button::<Display>(parent, text_font.clone(), target_id);
spawn_button::<Visibility>(parent, text_style.clone(), target_id); spawn_button::<Visibility>(parent, text_font.clone(), target_id);
}; };
parent parent
.spawn(NodeBundle { .spawn(NodeBundle {
@ -392,7 +394,7 @@ fn spawn_right_panel(
}); });
} }
fn spawn_button<T>(parent: &mut ChildBuilder, text_style: TextStyle, target: Entity) fn spawn_button<T>(parent: &mut ChildBuilder, text_font: TextFont, target: Entity)
where where
T: Default + std::fmt::Debug + Send + Sync + 'static, T: Default + std::fmt::Debug + Send + Sync + 'static,
Target<T>: TargetUpdate, Target<T>: TargetUpdate,
@ -413,7 +415,7 @@ where
.with_children(|builder| { .with_children(|builder| {
builder.spawn(( builder.spawn((
Text(format!("{}::{:?}", Target::<T>::NAME, T::default())), Text(format!("{}::{:?}", Target::<T>::NAME, T::default())),
text_style, text_font,
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
)); ));
}); });
@ -422,7 +424,7 @@ where
fn buttons_handler<T>( fn buttons_handler<T>(
mut left_panel_query: Query<&mut <Target<T> as TargetUpdate>::TargetComponent>, mut left_panel_query: Query<&mut <Target<T> as TargetUpdate>::TargetComponent>,
mut visibility_button_query: Query<(&Target<T>, &Interaction, &Children), Changed<Interaction>>, mut visibility_button_query: Query<(&Target<T>, &Interaction, &Children), Changed<Interaction>>,
mut text_query: Query<(&mut Text, &mut TextStyle)>, mut text_query: Query<(&mut Text, &mut TextColor)>,
) where ) where
T: Send + Sync, T: Send + Sync,
Target<T>: TargetUpdate + Component, Target<T>: TargetUpdate + Component,
@ -431,9 +433,9 @@ fn buttons_handler<T>(
if matches!(interaction, Interaction::Pressed) { if matches!(interaction, Interaction::Pressed) {
let mut target_value = left_panel_query.get_mut(target.id).unwrap(); let mut target_value = left_panel_query.get_mut(target.id).unwrap();
for &child in children { for &child in children {
if let Ok((mut text, mut style)) = text_query.get_mut(child) { if let Ok((mut text, mut text_color)) = text_query.get_mut(child) {
**text = target.update_target(target_value.as_mut()); **text = target.update_target(target_value.as_mut());
style.color = if text.contains("None") || text.contains("Hidden") { text_color.0 = if text.contains("None") || text.contains("Hidden") {
Color::srgb(1.0, 0.7, 0.7) Color::srgb(1.0, 0.7, 0.7)
} else { } else {
Color::WHITE Color::WHITE
@ -446,24 +448,24 @@ fn buttons_handler<T>(
fn text_hover( fn text_hover(
mut button_query: Query<(&Interaction, &mut BackgroundColor, &Children), Changed<Interaction>>, mut button_query: Query<(&Interaction, &mut BackgroundColor, &Children), Changed<Interaction>>,
mut text_query: Query<(&Text, &mut TextStyle)>, mut text_query: Query<(&Text, &mut TextColor)>,
) { ) {
for (interaction, mut color, children) in button_query.iter_mut() { for (interaction, mut color, children) in button_query.iter_mut() {
match interaction { match interaction {
Interaction::Hovered => { Interaction::Hovered => {
*color = Color::BLACK.with_alpha(0.6).into(); *color = Color::BLACK.with_alpha(0.6).into();
for &child in children { for &child in children {
if let Ok((_, mut style)) = text_query.get_mut(child) { if let Ok((_, mut text_color)) = text_query.get_mut(child) {
// Bypass change detection to avoid recomputation of the text when only changing the color // Bypass change detection to avoid recomputation of the text when only changing the color
style.bypass_change_detection().color = YELLOW.into(); text_color.bypass_change_detection().0 = YELLOW.into();
} }
} }
} }
_ => { _ => {
*color = Color::BLACK.with_alpha(0.5).into(); *color = Color::BLACK.with_alpha(0.5).into();
for &child in children { for &child in children {
if let Ok((text, mut style)) = text_query.get_mut(child) { if let Ok((text, mut text_color)) = text_query.get_mut(child) {
style.bypass_change_detection().color = text_color.bypass_change_detection().0 =
if text.contains("None") || text.contains("Hidden") { if text.contains("None") || text.contains("Hidden") {
HIDDEN_COLOR HIDDEN_COLOR
} else { } else {

View file

@ -175,11 +175,8 @@ fn spawn_nested_text_bundle(
.with_children(|builder| { .with_children(|builder| {
builder.spawn(( builder.spawn((
Text::new(text), Text::new(text),
TextStyle { TextFont { font, ..default() },
font, TextColor::BLACK,
color: Color::BLACK,
..default()
},
)); ));
}); });
} }

View file

@ -98,12 +98,12 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut state: ResM
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("a"), Text::new("a"),
TextStyle { TextFont {
font: font_handle, font: font_handle,
font_size: 50.0, font_size: 50.0,
color: YELLOW.into(),
..default() ..default()
}, },
TextColor(YELLOW.into()),
)); ));
}); });
// We're seeding the PRNG here to make this example deterministic for testing purposes. // We're seeding the PRNG here to make this example deterministic for testing purposes.

View file

@ -87,15 +87,15 @@ fn create_button() -> ButtonBundle {
} }
} }
fn create_label(text: &str, font: Handle<Font>) -> (Text, TextStyle) { fn create_label(text: &str, font: Handle<Font>) -> (Text, TextFont, TextColor) {
( (
Text::new(text), Text::new(text),
TextStyle { TextFont {
font, font,
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.9, 0.9),
..default() ..default()
}, },
TextColor(Color::srgb(0.9, 0.9, 0.9)),
) )
} }

View file

@ -137,13 +137,13 @@ fn spawn_layout(mut commands: Commands, asset_server: Res<AssetServer>) {
}) })
.with_children(|builder| { .with_children(|builder| {
builder.spawn((Text::new("Sidebar"), builder.spawn((Text::new("Sidebar"),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
..default() ..default()
}, },
)); ));
builder.spawn((Text::new("A paragraph of text which ought to wrap nicely. A paragraph of text which ought to wrap nicely. A paragraph of text which ought to wrap nicely. A paragraph of text which ought to wrap nicely. A paragraph of text which ought to wrap nicely. A paragraph of text which ought to wrap nicely. A paragraph of text which ought to wrap nicely."), builder.spawn((Text::new("A paragraph of text which ought to wrap nicely. A paragraph of text which ought to wrap nicely. A paragraph of text which ought to wrap nicely. A paragraph of text which ought to wrap nicely. A paragraph of text which ought to wrap nicely. A paragraph of text which ought to wrap nicely. A paragraph of text which ought to wrap nicely."),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 13.0, font_size: 13.0,
..default() ..default()
@ -210,10 +210,7 @@ fn item_rect(builder: &mut ChildBuilder, color: Srgba) {
fn spawn_nested_text_bundle(builder: &mut ChildBuilder, font: Handle<Font>, text: &str) { fn spawn_nested_text_bundle(builder: &mut ChildBuilder, font: Handle<Font>, text: &str) {
builder.spawn(( builder.spawn((
Text::new(text), Text::new(text),
TextStyle { TextFont { font, ..default() },
font, TextColor::BLACK,
color: Color::BLACK,
..default()
},
)); ));
} }

View file

@ -15,7 +15,7 @@ fn main() {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d); commands.spawn(Camera2d);
let text_style = TextStyle::default(); let text_style = TextFont::default();
let image = asset_server.load("branding/icon.png"); let image = asset_server.load("branding/icon.png");

View file

@ -81,14 +81,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Instructions // Instructions
let text_style = TextStyle::default(); let text_font = TextFont::default();
commands commands
.spawn(( .spawn((
Text::new( Text::new(
"Next Overflow Setting (O)\nNext Container Size (S)\nToggle Animation (space)\n\n", "Next Overflow Setting (O)\nNext Container Size (S)\nToggle Animation (space)\n\n",
), ),
text_style.clone(), text_font.clone(),
Style { Style {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
top: Val::Px(12.0), top: Val::Px(12.0),
@ -99,7 +99,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
)) ))
.with_child(( .with_child((
TextSpan::new(format!("{:?}", Overflow::clip())), TextSpan::new(format!("{:?}", Overflow::clip())),
text_style.clone(), text_font.clone(),
)); ));
// Overflow Debug // Overflow Debug
@ -168,7 +168,7 @@ fn spawn_text(
spawn_container(parent, update_transform, |parent| { spawn_container(parent, update_transform, |parent| {
parent.spawn(( parent.spawn((
Text::new("Bevy"), Text::new("Bevy"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 100.0, font_size: 100.0,
..default() ..default()

View file

@ -56,12 +56,12 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
parent.spawn(( parent.spawn((
Text::new("(0.0, 0.0)"), Text::new("(0.0, 0.0)"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.9, 0.9),
..default() ..default()
}, },
TextColor(Color::srgb(0.9, 0.9, 0.9)),
)); ));
}); });
} }
@ -69,11 +69,11 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
/// This systems polls the relative cursor position and displays its value in a text component. /// This systems polls the relative cursor position and displays its value in a text component.
fn relative_cursor_position_system( fn relative_cursor_position_system(
relative_cursor_position_query: Query<&RelativeCursorPosition>, relative_cursor_position_query: Query<&RelativeCursorPosition>,
mut output_query: Query<(&mut Text, &mut TextStyle)>, mut output_query: Query<(&mut Text, &mut TextColor)>,
) { ) {
let relative_cursor_position = relative_cursor_position_query.single(); let relative_cursor_position = relative_cursor_position_query.single();
let (mut output, mut style) = output_query.single_mut(); let (mut output, mut text_color) = output_query.single_mut();
**output = if let Some(relative_cursor_position) = relative_cursor_position.normalized { **output = if let Some(relative_cursor_position) = relative_cursor_position.normalized {
format!( format!(
@ -84,7 +84,7 @@ fn relative_cursor_position_system(
"unknown".to_string() "unknown".to_string()
}; };
style.color = if relative_cursor_position.mouse_over() { text_color.0 = if relative_cursor_position.mouse_over() {
Color::srgb(0.1, 0.9, 0.1) Color::srgb(0.1, 0.9, 0.1)
} else { } else {
Color::srgb(0.9, 0.1, 0.1) Color::srgb(0.9, 0.1, 0.1)

View file

@ -83,11 +83,11 @@ fn setup(
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("This is a cube"), Text::new("This is a cube"),
TextStyle { TextFont {
font_size: 40.0, font_size: 40.0,
color: Color::BLACK,
..default() ..default()
}, },
TextColor::BLACK,
)); ));
}); });

View file

@ -56,7 +56,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// header // header
parent.spawn(( parent.spawn((
Text::new("Horizontally Scrolling list (Ctrl + Mousewheel)"), Text::new("Horizontally Scrolling list (Ctrl + Mousewheel)"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: FONT_SIZE, font_size: FONT_SIZE,
..default() ..default()
@ -80,7 +80,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.with_children(|parent| { .with_children(|parent| {
for i in 0..100 { for i in 0..100 {
parent.spawn((Text(format!("Item {i}")), parent.spawn((Text(format!("Item {i}")),
TextStyle { TextFont {
font: asset_server font: asset_server
.load("fonts/FiraSans-Bold.ttf"), .load("fonts/FiraSans-Bold.ttf"),
..default() ..default()
@ -138,7 +138,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Title // Title
parent.spawn(( parent.spawn((
Text::new("Vertically Scrolling List"), Text::new("Vertically Scrolling List"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: FONT_SIZE, font_size: FONT_SIZE,
..default() ..default()
@ -178,7 +178,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
parent parent
.spawn(( .spawn((
Text(format!("Item {i}")), Text(format!("Item {i}")),
TextStyle { TextFont {
font: asset_server font: asset_server
.load("fonts/FiraSans-Bold.ttf"), .load("fonts/FiraSans-Bold.ttf"),
..default() ..default()
@ -213,7 +213,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Title // Title
parent.spawn(( parent.spawn((
Text::new("Bidirectionally Scrolling List"), Text::new("Bidirectionally Scrolling List"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: FONT_SIZE, font_size: FONT_SIZE,
..default() ..default()
@ -251,7 +251,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
parent parent
.spawn(( .spawn((
Text(format!("Item {}", (oi * 25) + i)), Text(format!("Item {}", (oi * 25) + i)),
TextStyle { TextFont {
font: asset_server.load( font: asset_server.load(
"fonts/FiraSans-Bold.ttf", "fonts/FiraSans-Bold.ttf",
), ),
@ -288,7 +288,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Title // Title
parent.spawn(( parent.spawn((
Text::new("Nested Scrolling Lists"), Text::new("Nested Scrolling Lists"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: FONT_SIZE, font_size: FONT_SIZE,
..default() ..default()
@ -333,7 +333,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
parent parent
.spawn(( .spawn((
Text(format!("Item {}", (oi * 25) + i)), Text(format!("Item {}", (oi * 25) + i)),
TextStyle { TextFont {
font: asset_server.load( font: asset_server.load(
"fonts/FiraSans-Bold.ttf", "fonts/FiraSans-Bold.ttf",
), ),

View file

@ -42,12 +42,14 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// ui camera // ui camera
commands.spawn(Camera2d); commands.spawn(Camera2d);
let text_style = TextStyle { let text_font = (
TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.9, 0.9), ..Default::default()
..default() },
}; TextColor(Color::srgb(0.9, 0.9, 0.9)),
);
commands commands
.spawn(NodeBundle { .spawn(NodeBundle {
@ -75,7 +77,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Size Constraints Example"), Text::new("Size Constraints Example"),
text_style.clone(), text_font.clone(),
Style { Style {
margin: UiRect::bottom(Val::Px(25.)), margin: UiRect::bottom(Val::Px(25.)),
..Default::default() ..Default::default()
@ -103,7 +105,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
Constraint::Width, Constraint::Width,
Constraint::MaxWidth, Constraint::MaxWidth,
] { ] {
spawn_button_row(parent, constraint, text_style.clone()); spawn_button_row(parent, constraint, text_font.clone());
} }
}); });
}); });
@ -150,7 +152,11 @@ fn spawn_bar(parent: &mut ChildBuilder) {
}); });
} }
fn spawn_button_row(parent: &mut ChildBuilder, constraint: Constraint, text_style: TextStyle) { fn spawn_button_row(
parent: &mut ChildBuilder,
constraint: Constraint,
text_style: (TextFont, TextColor),
) {
let label = match constraint { let label = match constraint {
Constraint::FlexBasis => "flex_basis", Constraint::FlexBasis => "flex_basis",
Constraint::Width => "size", Constraint::Width => "size",
@ -229,7 +235,7 @@ fn spawn_button(
constraint: Constraint, constraint: Constraint,
action: ButtonValue, action: ButtonValue,
label: String, label: String,
text_style: TextStyle, text_style: (TextFont, TextColor),
active: bool, active: bool,
) { ) {
parent parent
@ -271,14 +277,12 @@ fn spawn_button(
}) })
.with_child(( .with_child((
Text::new(label), Text::new(label),
TextStyle { text_style.0,
color: if active { TextColor(if active {
ACTIVE_TEXT_COLOR ACTIVE_TEXT_COLOR
} else { } else {
UNHOVERED_TEXT_COLOR UNHOVERED_TEXT_COLOR
}, }),
..text_style
},
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
)); ));
}); });
@ -290,7 +294,7 @@ fn update_buttons(
Changed<Interaction>, Changed<Interaction>,
>, >,
mut bar_query: Query<&mut Style, With<Bar>>, mut bar_query: Query<&mut Style, With<Bar>>,
mut text_query: Query<&mut TextStyle>, mut text_query: Query<&mut TextColor>,
children_query: Query<&Children>, children_query: Query<&Children>,
mut button_activated_event: EventWriter<ButtonActivatedEvent>, mut button_activated_event: EventWriter<ButtonActivatedEvent>,
) { ) {
@ -319,9 +323,9 @@ fn update_buttons(
for &child in children { for &child in children {
if let Ok(grand_children) = children_query.get(child) { if let Ok(grand_children) = children_query.get(child) {
for &grandchild in grand_children { for &grandchild in grand_children {
if let Ok(mut style) = text_query.get_mut(grandchild) { if let Ok(mut text_color) = text_query.get_mut(grandchild) {
if style.color != ACTIVE_TEXT_COLOR { if text_color.0 != ACTIVE_TEXT_COLOR {
style.color = HOVERED_TEXT_COLOR; text_color.0 = HOVERED_TEXT_COLOR;
} }
} }
} }
@ -334,9 +338,9 @@ fn update_buttons(
for &child in children { for &child in children {
if let Ok(grand_children) = children_query.get(child) { if let Ok(grand_children) = children_query.get(child) {
for &grandchild in grand_children { for &grandchild in grand_children {
if let Ok(mut style) = text_query.get_mut(grandchild) { if let Ok(mut text_color) = text_query.get_mut(grandchild) {
if style.color != ACTIVE_TEXT_COLOR { if text_color.0 != ACTIVE_TEXT_COLOR {
style.color = UNHOVERED_TEXT_COLOR; text_color.0 = UNHOVERED_TEXT_COLOR;
} }
} }
} }
@ -353,14 +357,14 @@ fn update_radio_buttons_colors(
button_query: Query<(Entity, &Constraint, &Interaction)>, button_query: Query<(Entity, &Constraint, &Interaction)>,
mut border_query: Query<&mut BorderColor>, mut border_query: Query<&mut BorderColor>,
mut color_query: Query<&mut BackgroundColor>, mut color_query: Query<&mut BackgroundColor>,
mut text_query: Query<&mut TextStyle>, mut text_query: Query<&mut TextColor>,
children_query: Query<&Children>, children_query: Query<&Children>,
) { ) {
for &ButtonActivatedEvent(button_id) in event_reader.read() { for &ButtonActivatedEvent(button_id) in event_reader.read() {
let (_, target_constraint, _) = button_query.get(button_id).unwrap(); let (_, target_constraint, _) = button_query.get(button_id).unwrap();
for (id, constraint, interaction) in button_query.iter() { for (id, constraint, interaction) in button_query.iter() {
if target_constraint == constraint { if target_constraint == constraint {
let (border_color, inner_color, text_color) = if id == button_id { let (border_color, inner_color, label_color) = if id == button_id {
(ACTIVE_BORDER_COLOR, ACTIVE_INNER_COLOR, ACTIVE_TEXT_COLOR) (ACTIVE_BORDER_COLOR, ACTIVE_INNER_COLOR, ACTIVE_TEXT_COLOR)
} else { } else {
( (
@ -378,8 +382,8 @@ fn update_radio_buttons_colors(
for &child in children_query.get(id).into_iter().flatten() { for &child in children_query.get(id).into_iter().flatten() {
color_query.get_mut(child).unwrap().0 = inner_color; color_query.get_mut(child).unwrap().0 = inner_color;
for &grandchild in children_query.get(child).into_iter().flatten() { for &grandchild in children_query.get(child).into_iter().flatten() {
if let Ok(mut style) = text_query.get_mut(grandchild) { if let Ok(mut text_color) = text_query.get_mut(grandchild) {
style.color = text_color; text_color.0 = label_color;
} }
} }
} }

View file

@ -32,7 +32,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(( commands.spawn((
// Accepts a `String` or any type that converts into a `String`, such as `&str` // Accepts a `String` or any type that converts into a `String`, such as `&str`
Text::new("hello\nbevy!"), Text::new("hello\nbevy!"),
TextStyle { TextFont {
// This font is loaded and will be used instead of the default font. // This font is loaded and will be used instead of the default font.
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 67.0, font_size: 67.0,
@ -55,7 +55,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.spawn(( .spawn((
// Create a Text with multiple child spans. // Create a Text with multiple child spans.
Text::new("FPS: "), Text::new("FPS: "),
TextStyle { TextFont {
// This font is loaded and will be used instead of the default font. // This font is loaded and will be used instead of the default font.
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 42.0, font_size: 42.0,
@ -65,20 +65,24 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.with_child(( .with_child((
TextSpan::default(), TextSpan::default(),
if cfg!(feature = "default_font") { if cfg!(feature = "default_font") {
TextStyle { (
TextFont {
font_size: 33.0, font_size: 33.0,
color: GOLD.into(),
// If no font is specified, the default font (a minimal subset of FiraMono) will be used. // If no font is specified, the default font (a minimal subset of FiraMono) will be used.
..default() ..default()
} },
TextColor(GOLD.into()),
)
} else { } else {
(
// "default_font" feature is unavailable, load a font to use instead. // "default_font" feature is unavailable, load a font to use instead.
TextStyle { TextFont {
font: asset_server.load("fonts/FiraMono-Medium.ttf"), font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 33.0, font_size: 33.0,
color: GOLD.into(), ..Default::default()
..default() },
} TextColor(GOLD.into()),
)
}, },
FpsText, FpsText,
)); ));
@ -99,7 +103,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
#[cfg(not(feature = "default_font"))] #[cfg(not(feature = "default_font"))]
commands.spawn(( commands.spawn((
Text::new("Default font disabled"), Text::new("Default font disabled"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraMono-Medium.ttf"), font: asset_server.load("fonts/FiraMono-Medium.ttf"),
..default() ..default()
}, },
@ -112,12 +116,12 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
)); ));
} }
fn text_color_system(time: Res<Time>, mut query: Query<&mut TextStyle, With<ColorText>>) { fn text_color_system(time: Res<Time>, mut query: Query<&mut TextColor, With<ColorText>>) {
for mut style in &mut query { for mut text_color in &mut query {
let seconds = time.elapsed_seconds(); let seconds = time.elapsed_seconds();
// Update the color of the ColorText span. // Update the color of the ColorText span.
style.color = Color::srgb( text_color.0 = Color::srgb(
ops::sin(1.25 * seconds) / 2.0 + 0.5, ops::sin(1.25 * seconds) / 2.0 + 0.5,
ops::sin(0.75 * seconds) / 2.0 + 0.5, ops::sin(0.75 * seconds) / 2.0 + 0.5,
ops::sin(0.50 * seconds) / 2.0 + 0.5, ops::sin(0.50 * seconds) / 2.0 + 0.5,

View file

@ -60,7 +60,7 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
}).with_children(|builder| { }).with_children(|builder| {
builder.spawn(( builder.spawn((
Text::new("This is\ntext with\nline breaks\nin the top left."), Text::new("This is\ntext with\nline breaks\nin the top left."),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 25.0, font_size: 25.0,
..default() ..default()
@ -71,12 +71,12 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
Text::new( Text::new(
"This text is right-justified. The `JustifyText` component controls the horizontal alignment of the lines of multi-line text relative to each other, and does not affect the text node's position in the UI layout.", "This text is right-justified. The `JustifyText` component controls the horizontal alignment of the lines of multi-line text relative to each other, and does not affect the text node's position in the UI layout.",
), ),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 25.0, font_size: 25.0,
color: YELLOW.into(),
..default() ..default()
}, },
TextColor(YELLOW.into()),
TextLayout::new_with_justify(JustifyText::Right), TextLayout::new_with_justify(JustifyText::Right),
Style { Style {
max_width: Val::Px(300.), max_width: Val::Px(300.),
@ -87,7 +87,7 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
builder.spawn(( builder.spawn((
Text::new( Text::new(
"This\ntext has\nline breaks and also a set width in the bottom left."), "This\ntext has\nline breaks and also a set width in the bottom left."),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 25.0, font_size: 25.0,
..default() ..default()
@ -115,12 +115,12 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
builder.spawn((Text::new( builder.spawn((Text::new(
"This text is very long, has a limited width, is center-justified, is positioned in the top right and is also colored pink."), "This text is very long, has a limited width, is center-justified, is positioned in the top right and is also colored pink."),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.8, 0.2, 0.7),
..default() ..default()
}, },
TextColor(Color::srgb(0.8, 0.2, 0.7)),
TextLayout::new_with_justify(JustifyText::Center), TextLayout::new_with_justify(JustifyText::Center),
Style { Style {
max_width: Val::Px(400.), max_width: Val::Px(400.),
@ -130,12 +130,12 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
builder.spawn((Text::new( builder.spawn((Text::new(
"This text is left-justified and is vertically positioned to distribute the empty space equally above and below it."), "This text is left-justified and is vertically positioned to distribute the empty space equally above and below it."),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 29.0, font_size: 29.0,
color: YELLOW.into(),
..default() ..default()
}, },
TextColor(YELLOW.into()),
TextLayout::new_with_justify(JustifyText::Left), TextLayout::new_with_justify(JustifyText::Left),
Style { Style {
max_width: Val::Px(300.), max_width: Val::Px(300.),
@ -146,13 +146,13 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
builder.spawn(( builder.spawn((
Text::new("This text is fully justified and is positioned in the same way."), Text::new("This text is fully justified and is positioned in the same way."),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 29.0, font_size: 29.0,
color: GREEN_YELLOW.into(),
..default() ..default()
}, },
TextLayout::new_with_justify(JustifyText::Justified), TextLayout::new_with_justify(JustifyText::Justified),
TextColor(GREEN_YELLOW.into()),
Style { Style {
max_width: Val::Px(300.), max_width: Val::Px(300.),
..default() ..default()
@ -163,7 +163,7 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
builder.spawn(( builder.spawn((
Text::default(), Text::default(),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 21.0, font_size: 21.0,
..default() ..default()
@ -174,7 +174,7 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
.with_children(|p| { .with_children(|p| {
p.spawn(( p.spawn((
TextSpan::new("\nThis text changes in the bottom right"), TextSpan::new("\nThis text changes in the bottom right"),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 21.0, font_size: 21.0,
..default() ..default()
@ -182,66 +182,66 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
)); ));
p.spawn(( p.spawn((
TextSpan::new(" this text has zero fontsize"), TextSpan::new(" this text has zero fontsize"),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 0.0, font_size: 0.0,
color: BLUE.into(),
..default() ..default()
}, },
TextColor(BLUE.into()),
)); ));
p.spawn(( p.spawn((
TextSpan::new("\nThis text changes in the bottom right - "), TextSpan::new("\nThis text changes in the bottom right - "),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 21.0, font_size: 21.0,
color: RED.into(),
..default() ..default()
}, },
TextColor(RED.into()),
)); ));
p.spawn(( p.spawn((
TextSpan::default(), TextSpan::default(),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 21.0, font_size: 21.0,
color: ORANGE_RED.into(),
..default() ..default()
} },
TextColor(ORANGE_RED.into()),
)); ));
p.spawn(( p.spawn((
TextSpan::new(" fps, "), TextSpan::new(" fps, "),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 10.0, font_size: 10.0,
color: YELLOW.into(),
..default() ..default()
}, },
TextColor(YELLOW.into()),
)); ));
p.spawn(( p.spawn((
TextSpan::default(), TextSpan::default(),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 21.0, font_size: 21.0,
color: LIME.into(),
..default() ..default()
} },
TextColor(LIME.into()),
)); ));
p.spawn(( p.spawn((
TextSpan::new(" ms/frame"), TextSpan::new(" ms/frame"),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: 42.0, font_size: 42.0,
color: BLUE.into(),
..default() ..default()
}, },
TextColor(BLUE.into()),
)); ));
p.spawn(( p.spawn((
TextSpan::new(" this text has negative fontsize"), TextSpan::new(" this text has negative fontsize"),
TextStyle { TextFont {
font: font.clone(), font: font.clone(),
font_size: -42.0, font_size: -42.0,
color: BLUE.into(),
..default() ..default()
}, },
TextColor(BLUE.into()),
)); ));
}); });
}) })

View file

@ -45,7 +45,7 @@ fn main() {
fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) { fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d); commands.spawn(Camera2d);
let text_style = TextStyle { let text_font = TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 12.0, font_size: 12.0,
..default() ..default()
@ -121,7 +121,7 @@ fn spawn(mut commands: Commands, asset_server: Res<AssetServer>) {
for (j, message) in messages.into_iter().enumerate() { for (j, message) in messages.into_iter().enumerate() {
commands.entity(column_id).with_child(( commands.entity(column_id).with_child((
Text(message.clone()), Text(message.clone()),
text_style.clone(), text_font.clone(),
TextLayout::new(JustifyText::Left, linebreak), TextLayout::new(JustifyText::Left, linebreak),
BackgroundColor(Color::srgb(0.8 - j as f32 * 0.2, 0., 0.)), BackgroundColor(Color::srgb(0.8 - j as f32 * 0.2, 0., 0.)),
)); ));

View file

@ -43,13 +43,13 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Button 1"), Text::new("Button 1"),
TextStyle { TextFont {
font: font_handle.clone(), font: font_handle.clone(),
font_size: 33.0, font_size: 33.0,
// Alpha channel of the color controls transparency.
color: Color::srgba(1.0, 1.0, 1.0, 0.2),
..default() ..default()
}, },
// Alpha channel of the color controls transparency.
TextColor(Color::srgba(1.0, 1.0, 1.0, 0.2)),
)); ));
}); });
@ -70,13 +70,13 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Button 2"), Text::new("Button 2"),
TextStyle { TextFont {
font: font_handle.clone(), font: font_handle.clone(),
font_size: 33.0, font_size: 33.0,
// Alpha channel of the color controls transparency.
color: Color::srgba(1.0, 1.0, 1.0, 0.2),
..default() ..default()
}, },
// Alpha channel of the color controls transparency.
TextColor(Color::srgba(1.0, 1.0, 1.0, 0.2)),
)); ));
}); });
}); });

View file

@ -75,7 +75,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// text // text
parent.spawn(( parent.spawn((
Text::new("Text Example"), Text::new("Text Example"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 25.0, font_size: 25.0,
..default() ..default()
@ -90,7 +90,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Debug overlay text // Debug overlay text
parent.spawn(( parent.spawn((
Text::new("Press Space to enable debug outlines."), Text::new("Press Space to enable debug outlines."),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
..default() ..default()
}, },
@ -100,7 +100,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
#[cfg(not(feature = "bevy_dev_tools"))] #[cfg(not(feature = "bevy_dev_tools"))]
parent.spawn(( parent.spawn((
Text::new("Try enabling feature \"bevy_dev_tools\"."), Text::new("Try enabling feature \"bevy_dev_tools\"."),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
..default() ..default()
}, },
@ -124,7 +124,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
// Title // Title
parent.spawn(( parent.spawn((
Text::new("Scrolling list"), Text::new("Scrolling list"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 21., font_size: 21.,
..default() ..default()
@ -150,7 +150,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
parent parent
.spawn(( .spawn((
Text(format!("Item {i}")), Text(format!("Item {i}")),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
..default() ..default()
}, },

View file

@ -23,9 +23,8 @@ fn main() {
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) { fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(Camera2d); commands.spawn(Camera2d);
let text_style = TextStyle { let text_font = TextFont {
font_size: 13., font_size: 13.,
color: Color::BLACK,
..default() ..default()
}; };
@ -56,7 +55,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
..default() ..default()
}) })
.with_children(|parent| { .with_children(|parent| {
parent.spawn((Text::new("Size!"), text_style)); parent.spawn((Text::new("Size!"), text_font, TextColor::BLACK));
}); });
parent.spawn(NodeBundle { parent.spawn(NodeBundle {
style: Style { style: Style {

View file

@ -25,7 +25,7 @@ fn setup(
// Camera // Camera
commands.spawn(Camera2d); commands.spawn(Camera2d);
let text_style = TextStyle::default(); let text_font = TextFont::default();
let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png"); let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png");
let texture_atlas = TextureAtlasLayout::from_grid(UVec2::splat(24), 7, 1, None, None); let texture_atlas = TextureAtlasLayout::from_grid(UVec2::splat(24), 7, 1, None, None);
@ -40,7 +40,7 @@ fn setup(
flex_direction: FlexDirection::Column, flex_direction: FlexDirection::Column,
justify_content: JustifyContent::Center, justify_content: JustifyContent::Center,
align_items: AlignItems::Center, align_items: AlignItems::Center,
row_gap: Val::Px(text_style.font_size * 2.), row_gap: Val::Px(text_font.font_size * 2.),
..default() ..default()
}, },
..default() ..default()
@ -61,15 +61,13 @@ fn setup(
Outline::new(Val::Px(8.0), Val::ZERO, CRIMSON.into()), Outline::new(Val::Px(8.0), Val::ZERO, CRIMSON.into()),
)); ));
parent parent
.spawn((Text::new("press "), text_style.clone())) .spawn((Text::new("press "), text_font.clone()))
.with_child(( .with_child((
TextSpan::new("space"), TextSpan::new("space"),
TextStyle { TextColor(YELLOW.into()),
color: YELLOW.into(), text_font.clone(),
..text_style.clone()
},
)) ))
.with_child((TextSpan::new(" to advance frames"), text_style)); .with_child((TextSpan::new(" to advance frames"), text_font));
}); });
} }

View file

@ -104,12 +104,12 @@ fn setup(
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Button"), Text::new("Button"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.9, 0.9),
..default() ..default()
}, },
TextColor(Color::srgb(0.9, 0.9, 0.9)),
)); ));
}); });
} }

View file

@ -88,12 +88,12 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Text::new("Button"), Text::new("Button"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 33.0, font_size: 33.0,
color: Color::srgb(0.9, 0.9, 0.9),
..default() ..default()
}, },
TextColor(Color::srgb(0.9, 0.9, 0.9)),
)); ));
}); });
} }

View file

@ -29,7 +29,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
commands.spawn(( commands.spawn((
// Accepts a `String` or any type that converts into a `String`, such as `&str` // Accepts a `String` or any type that converts into a `String`, such as `&str`
Text::new("Hit 'P' then scroll/click around!"), Text::new("Hit 'P' then scroll/click around!"),
TextStyle { TextFont {
font: asset_server.load("fonts/FiraSans-Bold.ttf"), font: asset_server.load("fonts/FiraSans-Bold.ttf"),
font_size: 83.0, // Nice and big so you can see it! font_size: 83.0, // Nice and big so you can see it!
..default() ..default()

View file

@ -200,27 +200,9 @@ pub(crate) mod test_setup {
)) ))
.with_children(|p| { .with_children(|p| {
p.spawn(TextSpan::new("Press space bar to cycle modes\n")); p.spawn(TextSpan::new("Press space bar to cycle modes\n"));
p.spawn(( p.spawn((TextSpan::default(), TextColor(LIME.into())));
TextSpan::default(), p.spawn((TextSpan::new("\nFrame: "), TextColor(YELLOW.into())));
TextStyle { p.spawn((TextSpan::new(""), TextColor(YELLOW.into())));
color: LIME.into(),
..default()
},
));
p.spawn((
TextSpan::new("\nFrame: "),
TextStyle {
color: YELLOW.into(),
..default()
},
));
p.spawn((
TextSpan::new(""),
TextStyle {
color: YELLOW.into(),
..default()
},
));
}); });
} }
} }

View file

@ -53,7 +53,7 @@ fn setup(mut commands: Commands) {
.with_child(( .with_child((
CustomText, CustomText,
Text::new("Example text"), Text::new("Example text"),
TextStyle { TextFont {
font_size: 25.0, font_size: 25.0,
..default() ..default()
}, },

View file

@ -45,7 +45,7 @@ fn setup_ui(mut commands: Commands) {
// Text where we display current resolution // Text where we display current resolution
.with_child(( .with_child((
Text::new("Resolution"), Text::new("Resolution"),
TextStyle { TextFont {
font_size: 42.0, font_size: 42.0,
..default() ..default()
}, },