Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions crates/bevy_feathers/src/font_styles.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//! A framework for inheritable font styles.
use bevy_app::{Propagate, PropagateOver};
use bevy_asset::{AssetServer, Handle};
use bevy_ecs::{
component::Component,
Expand All @@ -17,7 +16,7 @@ use crate::{handle_or_path::HandleOrPath, theme::ThemedText};
/// downward to any child text entity that has the [`ThemedText`] marker.
#[derive(Component, Default, Clone, Debug, Reflect)]
#[reflect(Component, Default)]
#[require(ThemedText, PropagateOver::<TextFont>::default())]
#[require(ThemedText)]
pub struct InheritableFont {
/// The font handle or path.
pub font: HandleOrPath<Font>,
Expand Down Expand Up @@ -57,10 +56,10 @@ pub(crate) fn on_changed_font(
HandleOrPath::Path(ref p) => Some(assets.load::<Font>(p)),
}
{
commands.entity(insert.entity).insert(Propagate(TextFont {
commands.entity(insert.entity).insert(TextFont {
font,
font_size: style.font_size,
..Default::default()
}));
});
}
}
18 changes: 2 additions & 16 deletions crates/bevy_feathers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,17 @@
//! Please report issues, submit fixes and propose changes.
//! Thanks for stress-testing; let's build something better together.

use bevy_app::{
HierarchyPropagatePlugin, Plugin, PluginGroup, PluginGroupBuilder, PostUpdate, PropagateSet,
};
use bevy_app::{Plugin, PluginGroup, PluginGroupBuilder, PostUpdate};
use bevy_asset::embedded_asset;
use bevy_ecs::{query::With, schedule::IntoScheduleConfigs};
use bevy_input_focus::{tab_navigation::TabNavigationPlugin, InputDispatchPlugin};
use bevy_text::{TextColor, TextFont};
use bevy_ui::UiSystems;
use bevy_ui_render::UiMaterialPlugin;
use bevy_ui_widgets::UiWidgetsPlugins;

use crate::{
alpha_pattern::{AlphaPatternMaterial, AlphaPatternResource},
controls::ControlsPlugin,
cursor::{CursorIconPlugin, DefaultCursor, EntityCursor},
theme::{ThemedText, UiTheme},
theme::UiTheme,
};

mod alpha_pattern;
Expand Down Expand Up @@ -68,18 +63,9 @@ impl Plugin for FeathersPlugin {
app.add_plugins((
ControlsPlugin,
CursorIconPlugin,
HierarchyPropagatePlugin::<TextColor, With<ThemedText>>::new(PostUpdate),
HierarchyPropagatePlugin::<TextFont, With<ThemedText>>::new(PostUpdate),
UiMaterialPlugin::<AlphaPatternMaterial>::default(),
));

// This needs to run in UiSystems::Propagate so the fonts are up-to-date for `measure_text_system`
// and `detect_text_needs_rerender` in UiSystems::Content
app.configure_sets(
PostUpdate,
PropagateSet::<TextFont>::default().in_set(UiSystems::Propagate),
);

app.insert_resource(DefaultCursor(EntityCursor::System(
bevy_window::SystemCursorIcon::Default,
)));
Expand Down
7 changes: 2 additions & 5 deletions crates/bevy_feathers/src/theme.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//! A framework for theming.
use bevy_app::{Propagate, PropagateOver};
use bevy_color::{palettes, Color};
use bevy_ecs::{
change_detection::DetectChanges,
Expand Down Expand Up @@ -105,7 +104,7 @@ pub struct ThemeBorderColor(pub ThemeToken);
#[component(immutable)]
#[derive(Reflect)]
#[reflect(Component, Clone)]
#[require(ThemedText, PropagateOver::<TextColor>::default())]
#[require(ThemedText)]
pub struct ThemeFontColor(pub ThemeToken);

/// A marker component that is used to indicate that the text entity wants to opt-in to using
Expand Down Expand Up @@ -167,8 +166,6 @@ pub(crate) fn on_changed_font_color(
) {
if let Ok(token) = font_color.get(insert.entity) {
let color = theme.color(&token.0);
commands
.entity(insert.entity)
.insert(Propagate(TextColor(color)));
commands.entity(insert.entity).insert(TextColor(color));
}
}
2 changes: 2 additions & 0 deletions crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ use bevy_camera::{
visibility::VisibilitySystems,
};
use bevy_mesh::{Mesh, Mesh2d};

#[cfg(feature = "bevy_sprite_picking_backend")]
pub use picking_backend::*;
pub use sprite::*;
Expand Down Expand Up @@ -84,6 +85,7 @@ impl Plugin for SpritePlugin {
app.add_systems(
PostUpdate,
(
resolve_2d_computed_text_styles,
bevy_text::detect_text_needs_rerender::<Text2d>,
update_text2d_layout
.after(bevy_camera::CameraUpdateSystems)
Expand Down
145 changes: 93 additions & 52 deletions crates/bevy_sprite/src/text2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use bevy_camera::visibility::{
use bevy_camera::Camera;
use bevy_color::Color;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::change_detection::DetectChangesMut;
use bevy_ecs::entity::EntityHashSet;
use bevy_ecs::hierarchy::ChildOf;
use bevy_ecs::query::With;
use bevy_ecs::{
change_detection::{DetectChanges, Ref},
component::Component,
Expand All @@ -24,7 +27,8 @@ use bevy_text::{
TextColor, TextError, TextFont, TextLayout, TextLayoutInfo, TextPipeline, TextReader, TextRoot,
TextSpanAccess, TextWriter,
};
use bevy_transform::components::Transform;
use bevy_text::{ComputedTextStyle, DefaultTextStyle};
use bevy_transform::components::{GlobalTransform, Transform};
use core::any::TypeId;

/// The top-level 2D text component.
Expand Down Expand Up @@ -81,12 +85,11 @@ use core::any::TypeId;
#[reflect(Component, Default, Debug, Clone)]
#[require(
TextLayout,
TextFont,
TextColor,
TextBounds,
Anchor,
Visibility,
VisibilityClass,
ComputedTextStyle,
Transform
)]
#[component(on_add = visibility::add_visibility_class::<Sprite>)]
Expand Down Expand Up @@ -158,67 +161,27 @@ impl Default for Text2dShadow {
/// [`ResMut<Assets<Image>>`](Assets<Image>) -- This system only adds new [`Image`] assets.
/// It does not modify or observe existing ones.
pub fn update_text2d_layout(
mut target_scale_factors: Local<Vec<(f32, RenderLayers)>>,
// Text items which should be reprocessed again, generally when the font hasn't loaded yet.
mut queue: Local<EntityHashSet>,
mut textures: ResMut<Assets<Image>>,
fonts: Res<Assets<Font>>,
camera_query: Query<(&Camera, &VisibleEntities, Option<&RenderLayers>)>,
mut texture_atlases: ResMut<Assets<TextureAtlasLayout>>,
mut font_atlas_sets: ResMut<FontAtlasSets>,
mut text_pipeline: ResMut<TextPipeline>,
mut text_query: Query<(
Entity,
Option<&RenderLayers>,
Ref<TextLayout>,
Ref<TextBounds>,
&mut TextLayoutInfo,
&mut ComputedTextBlock,
&ComputedTextStyle,
)>,
mut text_reader: Text2dReader,
mut font_system: ResMut<CosmicFontSystem>,
mut swash_cache: ResMut<SwashCache>,
) {
target_scale_factors.clear();
target_scale_factors.extend(
camera_query
.iter()
.filter(|(_, visible_entities, _)| {
!visible_entities.get(TypeId::of::<Sprite>()).is_empty()
})
.filter_map(|(camera, _, maybe_camera_mask)| {
camera.target_scaling_factor().map(|scale_factor| {
(scale_factor, maybe_camera_mask.cloned().unwrap_or_default())
})
}),
);

let mut previous_scale_factor = 0.;
let mut previous_mask = &RenderLayers::none();

for (entity, maybe_entity_mask, block, bounds, text_layout_info, mut computed) in
&mut text_query
{
let entity_mask = maybe_entity_mask.unwrap_or_default();

let scale_factor = if entity_mask == previous_mask && 0. < previous_scale_factor {
previous_scale_factor
} else {
// `Text2d` only supports generating a single text layout per Text2d entity. If a `Text2d` entity has multiple
// render targets with different scale factors, then we use the maximum of the scale factors.
let Some((scale_factor, mask)) = target_scale_factors
.iter()
.filter(|(_, camera_mask)| camera_mask.intersects(entity_mask))
.max_by_key(|(scale_factor, _)| FloatOrd(*scale_factor))
else {
continue;
};
previous_scale_factor = *scale_factor;
previous_mask = mask;
*scale_factor
};

if scale_factor != text_layout_info.scale_factor
for (entity, block, bounds, text_layout_info, mut computed, style) in &mut text_query {
if style.scale_factor != text_layout_info.scale_factor
|| computed.needs_rerender()
|| bounds.is_changed()
|| (!queue.is_empty() && queue.remove(&entity))
Expand All @@ -227,17 +190,17 @@ pub fn update_text2d_layout(
width: if block.linebreak == LineBreak::NoWrap {
None
} else {
bounds.width.map(|width| width * scale_factor)
bounds.width.map(|width| width * style.scale_factor)
},
height: bounds.height.map(|height| height * scale_factor),
height: bounds.height.map(|height| height * style.scale_factor),
};

let text_layout_info = text_layout_info.into_inner();
match text_pipeline.queue_text(
text_layout_info,
&fonts,
text_reader.iter(entity),
scale_factor as f64,
style.scale_factor as f64,
&block,
text_bounds,
&mut font_atlas_sets,
Expand All @@ -256,8 +219,8 @@ pub fn update_text2d_layout(
panic!("Fatal error when processing text: {e}.");
}
Ok(()) => {
text_layout_info.scale_factor = scale_factor;
text_layout_info.size *= scale_factor.recip();
text_layout_info.scale_factor = style.scale_factor;
text_layout_info.size *= style.scale_factor.recip();
}
}
}
Expand Down Expand Up @@ -301,6 +264,82 @@ pub fn calculate_bounds_text2d(
}
}

/// Update the `ComputedTextStyle` for each `Text2d` entity from the
/// `TextFont`s and `TextColor`s of its nearest ancestors, or from [`DefaultTextStyle`] if none are found.
pub fn resolve_2d_computed_text_styles(
mut target_scale_factors: Local<Vec<(f32, RenderLayers)>>,
default_text_style: Res<DefaultTextStyle>,
mut computed_text_query: Query<
(Entity, &mut ComputedTextStyle, Option<&RenderLayers>),
With<GlobalTransform>,
>,
parent_query: Query<&ChildOf>,
font_query: Query<(Option<&TextFont>, Option<&TextColor>)>,
camera_query: Query<(&Camera, &VisibleEntities, Option<&RenderLayers>)>,
) {
target_scale_factors.clear();
target_scale_factors.extend(
camera_query
.iter()
.filter(|(_, visible_entities, _)| {
!visible_entities.get(TypeId::of::<Sprite>()).is_empty()
})
.filter_map(|(camera, _, maybe_camera_mask)| {
camera.target_scaling_factor().map(|scale_factor| {
(scale_factor, maybe_camera_mask.cloned().unwrap_or_default())
})
}),
);

let mut previous_scale_factor = 0.;
let mut previous_mask = &RenderLayers::none();

for (start, mut style, maybe_entity_mask) in computed_text_query.iter_mut() {
let entity_mask = maybe_entity_mask.unwrap_or_default();

let scale_factor = if entity_mask == previous_mask && 0. < previous_scale_factor {
previous_scale_factor
} else {
// `Text2d` only supports generating a single text layout per Text2d entity. If a `Text2d` entity has multiple
// render targets with different scale factors, then we use the maximum of the scale factors.
let Some((scale_factor, mask)) = target_scale_factors
.iter()
.filter(|(_, camera_mask)| camera_mask.intersects(entity_mask))
.max_by_key(|(scale_factor, _)| FloatOrd(*scale_factor))
else {
continue;
};
previous_scale_factor = *scale_factor;
previous_mask = mask;
*scale_factor
};

let (mut font, mut color) = font_query.get(start).unwrap();
let mut ancestors = parent_query.iter_ancestors(start);

while (font.is_none() || color.is_none())
&& let Some(ancestor) = ancestors.next()
{
let (next_font, next_color) = font_query.get(ancestor).unwrap();
font = font.or(next_font);
color = color.or(next_color);
}

let new_style = ComputedTextStyle {
font: font.unwrap_or(&default_text_style.font).clone(),
color: color.map(|t| t.0).unwrap_or(default_text_style.color),
scale_factor,
};

if new_style.font != style.font || new_style.scale_factor != style.scale_factor {
*style = new_style;
} else {
// bypass change detection, we don't need to do any updates if only the text color has changed
style.bypass_change_detection().color = new_style.color;
}
}
}

#[cfg(test)]
mod tests {

Expand All @@ -309,7 +348,7 @@ mod tests {
use bevy_camera::{ComputedCameraValues, RenderTargetInfo};
use bevy_ecs::schedule::IntoScheduleConfigs;
use bevy_math::UVec2;
use bevy_text::{detect_text_needs_rerender, TextIterScratch};
use bevy_text::{detect_text_needs_rerender, DefaultTextStyle, TextIterScratch};

use super::*;

Expand All @@ -326,9 +365,11 @@ mod tests {
.init_resource::<CosmicFontSystem>()
.init_resource::<SwashCache>()
.init_resource::<TextIterScratch>()
.init_resource::<DefaultTextStyle>()
.add_systems(
Update,
(
resolve_2d_computed_text_styles,
detect_text_needs_rerender::<Text2d>,
update_text2d_layout,
calculate_bounds_text2d,
Expand Down
7 changes: 4 additions & 3 deletions crates/bevy_sprite_render/src/text2d/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ use bevy_render::sync_world::TemporaryRenderEntity;
use bevy_render::Extract;
use bevy_sprite::{Anchor, Text2dShadow};
use bevy_text::{
ComputedTextBlock, PositionedGlyph, TextBackgroundColor, TextBounds, TextColor, TextLayoutInfo,
ComputedTextBlock, ComputedTextStyle, PositionedGlyph, TextBackgroundColor, TextBounds,
TextLayoutInfo,
};
use bevy_transform::prelude::GlobalTransform;

Expand All @@ -37,7 +38,7 @@ pub fn extract_text2d_sprite(
&GlobalTransform,
)>,
>,
text_colors: Extract<Query<&TextColor>>,
text_colors: Extract<Query<&ComputedTextStyle>>,
text_background_colors_query: Extract<Query<&TextBackgroundColor>>,
) {
let mut start = extracted_slices.slices.len();
Expand Down Expand Up @@ -170,7 +171,7 @@ pub fn extract_text2d_sprite(
.map(|t| t.entity)
.unwrap_or(Entity::PLACEHOLDER),
)
.map(|text_color| LinearRgba::from(text_color.0))
.map(|style| LinearRgba::from(style.color()))
.unwrap_or_default();
current_span = *span_index;
}
Expand Down
Loading
Loading