Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: New `default_source` parameter on `FontSource::resolve_font_family`
pull_requests: [25847]
---

The `FontSource::resolve_font_family` method now takes a `default_source` argument that is used to resolve `FontSource::Default`.
Comment thread
ickshonpe marked this conversation as resolved.

You should set the value of the `DefaultFontSource` resource to set the default font for your app now.
9 changes: 6 additions & 3 deletions crates/bevy_sprite/src/text2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ use bevy_image::prelude::*;
use bevy_math::{FloatOrd, Vec2, Vec3};
use bevy_reflect::{prelude::ReflectDefault, Reflect};
use bevy_text::{
ComputedTextBlock, Font, FontAtlasSet, FontCx, FontHinting, LayoutCx, LetterSpacing, LineBreak,
LineHeight, RemSize, ScaleCx, TextBounds, TextColor, TextError, TextFont, TextLayout,
TextLayoutInfo, TextPipeline, TextReader, TextSection, TextWriter,
ComputedTextBlock, DefaultFontSource, Font, FontAtlasSet, FontCx, FontHinting, LayoutCx,
LetterSpacing, LineBreak, LineHeight, RemSize, ScaleCx, TextBounds, TextColor, TextError,
TextFont, TextLayout, TextLayoutInfo, TextPipeline, TextReader, TextSection, TextWriter,
};
use bevy_transform::components::Transform;
use bevy_window::{PrimaryWindow, Window};
Expand Down Expand Up @@ -171,6 +171,7 @@ pub fn update_text2d_layout(
mut reprocess_queue: Local<EntityHashSet>,
mut textures: ResMut<Assets<Image>>,
fonts: Res<Assets<Font>>,
default_font_source: Res<DefaultFontSource>,
camera_query: Query<(&Camera, &VisibleEntities, Option<&RenderLayers>)>,
mut font_atlas_set: ResMut<FontAtlasSet>,
mut text_pipeline: ResMut<TextPipeline>,
Expand Down Expand Up @@ -278,6 +279,7 @@ pub fn update_text2d_layout(
&mut layout_cx,
logical_viewport_size,
*rem_size,
&default_font_source.0,
) {
Err(
TextError::NoSuchFont
Expand Down Expand Up @@ -453,6 +455,7 @@ mod tests {
.init_resource::<ScaleCx>()
.init_resource::<TextIterScratch>()
.init_resource::<RemSize>()
.init_resource::<DefaultFontSource>()
.add_systems(
Update,
(
Expand Down
64 changes: 61 additions & 3 deletions crates/bevy_text/src/font.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use crate::DefaultFontSource;
use crate::FontCx;
use crate::FontSource;
use crate::TextFont;
use bevy_asset::Asset;
use bevy_asset::AssetId;
use bevy_asset::Assets;
use bevy_ecs::change_detection::DetectChangesMut;
use bevy_ecs::change_detection::{DetectChanges, DetectChangesMut};
use bevy_ecs::system::Local;
use bevy_ecs::system::Query;
use bevy_ecs::system::Res;
use bevy_ecs::system::ResMut;
use bevy_platform::collections::HashSet;
use bevy_reflect::TypePath;
Expand Down Expand Up @@ -54,6 +56,7 @@ pub fn load_font_assets_into_font_collection(
mut loaded_fonts: Local<HashSet<AssetId<Font>>>,
mut font_cx: ResMut<FontCx>,
mut text_font_query: Query<&mut TextFont>,
default_font_source: Res<DefaultFontSource>,
) {
let font_removed = loaded_fonts.iter().any(|id| !fonts.contains(*id));
let new_asset_ids: Vec<_> = if font_removed {
Expand All @@ -66,7 +69,7 @@ pub fn load_font_assets_into_font_collection(
fonts.ids().filter(|id| loaded_fonts.insert(*id)).collect()
};

if new_asset_ids.is_empty() && !font_removed {
if new_asset_ids.is_empty() && !font_removed && !default_font_source.is_changed() {
return;
}

Expand Down Expand Up @@ -110,6 +113,7 @@ pub fn load_font_assets_into_font_collection(
.flatten()
.into_iter()
.any(|source| match source {
FontSource::Default => true,
FontSource::Handle(handle) => new_asset_ids.contains(&handle.id()),
FontSource::Family(name) => font_cx
.collection
Expand Down Expand Up @@ -145,14 +149,66 @@ pub fn load_font_assets_into_font_collection(
mod tests {
use bevy_app::{App, Update};
use bevy_asset::Assets;
use bevy_ecs::change_detection::DetectChanges;

use super::*;

#[test]
fn default_font_changes_invalidate_default_text_fonts() {
let mut app = App::new();
app.init_resource::<Assets<Font>>()
.init_resource::<DefaultFontSource>()
.init_resource::<FontCx>()
.add_systems(Update, load_font_assets_into_font_collection);
let default_entity = app.world_mut().spawn(TextFont::default()).id();
let nested_entity = app
.world_mut()
.spawn(TextFont {
font: FontSource::list([FontSource::list([FontSource::Default])]),
..Default::default()
})
.id();
let explicit_entity = app.world_mut().spawn(TextFont::from("Other")).id();
let entities = [default_entity, nested_entity, explicit_entity];
app.update();

for insert_asset in [false, true] {
let ticks = entities.map(|entity| {
app.world()
.entity(entity)
.get_ref::<TextFont>()
.unwrap()
.last_changed()
});
if insert_asset {
app.world_mut()
.resource_mut::<Assets<Font>>()
.add(Font::from_bytes(
include_bytes!("FiraMono-subset.ttf").to_vec(),
));
} else {
app.world_mut().resource_mut::<DefaultFontSource>().0 =
FontSource::family("Fira Mono");
}
app.update();
for (entity, tick) in entities.into_iter().zip(ticks) {
assert_eq!(
tick != app
.world()
.entity(entity)
.get_ref::<TextFont>()
.unwrap()
.last_changed(),
entity != explicit_entity
);
}
}
}

#[test]
fn font_asset_registration_and_cleanup() {
let mut app = App::new();
app.init_resource::<Assets<Font>>()
.init_resource::<DefaultFontSource>()
.init_resource::<FontCx>()
.add_systems(Update, load_font_assets_into_font_collection);

Expand Down Expand Up @@ -202,6 +258,7 @@ mod tests {
fn text_font_is_set_changed_when_its_font_asset_is_inserted() {
let mut app = App::new();
app.init_resource::<Assets<Font>>()
.init_resource::<DefaultFontSource>()
.init_resource::<FontCx>()
.add_systems(Update, load_font_assets_into_font_collection);

Expand Down Expand Up @@ -252,6 +309,7 @@ mod tests {
fn textfonts_are_not_set_changed_when_a_font_asset_is_inserted_for_other_textfonts() {
let mut app = App::new();
app.init_resource::<Assets<Font>>()
.init_resource::<DefaultFontSource>()
.init_resource::<FontCx>()
.add_systems(Update, load_font_assets_into_font_collection);

Expand Down
1 change: 1 addition & 0 deletions crates/bevy_text/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ impl Plugin for TextPlugin {
.init_resource::<ScaleCx>()
.init_resource::<TextIterScratch>()
.init_resource::<RemSize>()
.init_resource::<DefaultFontSource>()
.add_systems(
PostUpdate,
(
Expand Down
5 changes: 4 additions & 1 deletion crates/bevy_text/src/parley_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,11 @@ impl FontCx {
pub fn get_family<'a>(&'a mut self, source: &'a FontSource) -> Option<&'a str> {
let generic_family = match source {
FontSource::Family(family) => return Some(family.as_str()),
FontSource::Handle(_) | FontSource::Families(_) | FontSource::List(_) => return None,
FontSource::Generic(generic_family) => *generic_family,
FontSource::Handle(_)
| FontSource::Families(_)
| FontSource::List(_)
| FontSource::Default => return None,
};

let family_id = self
Expand Down
12 changes: 10 additions & 2 deletions crates/bevy_text/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ impl TextPipeline {
layout_cx: &mut LayoutCx,
logical_viewport_size: Vec2,
base_rem_size: RemSize,
default_font_source: &FontSource,
) -> Result<(), TextError> {
computed.entities.clear();
computed.needs_rerender = false;
Expand Down Expand Up @@ -126,7 +127,10 @@ impl TextPipeline {
}

if matches!(text_font.font, FontSource::Handle(_))
&& text_font.font.resolve_font_family(fonts).is_err()
&& text_font
.font
.resolve_font_family(fonts, default_font_source)
.is_err()
{
return Err(TextError::NoSuchFont);
}
Expand Down Expand Up @@ -227,7 +231,9 @@ impl TextPipeline {
continue;
}

let resolved_family = text_font.font.resolve_font_family(fonts)?;
let resolved_family = text_font
.font
.resolve_font_family(fonts, default_font_source)?;

builder.push(StyleProperty::FontFamily(resolved_family), range.clone());
builder.push(
Expand Down Expand Up @@ -307,6 +313,7 @@ impl TextPipeline {
layout_cx: &mut LayoutCx,
logical_viewport_size: Vec2,
base_rem_size: RemSize,
default_font_source: &FontSource,
) -> Result<TextMeasureInfo, TextError> {
const MIN_WIDTH_CONTENT_BOUNDS: TextBounds = TextBounds::new_horizontal(0.0);

Expand All @@ -324,6 +331,7 @@ impl TextPipeline {
layout_cx,
logical_viewport_size,
base_rem_size,
default_font_source,
)?;

let layout_buffer = &mut computed.layout;
Expand Down
53 changes: 42 additions & 11 deletions crates/bevy_text/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,16 +280,15 @@ impl From<Justify> for parley::Alignment {
///
/// You can check which font family is used for a given [`FontSource`]
/// by calling [`FontCx::get_family`](crate::FontCx::get_family).
#[derive(Clone, Debug, Reflect, PartialEq, FromTemplate)]
#[derive(Clone, Debug, Reflect, PartialEq, FromTemplate, Default)]
pub enum FontSource {
/// Use a specific font face referenced by a [`Font`] asset handle.
///
/// If the default font handle is used, then
/// * if `default_font` feature is enabled (enabled by default in `bevy` crate),
/// `FiraMono-subset.ttf` compiled into the library is used.
/// * otherwise no text will be rendered, unless a custom font is loaded into the default font
/// handle.
#[default]
/// handle.
Handle(Handle<Font>),
/// Resolve the font by family name using the font database.
Family(SmolStr),
Expand All @@ -301,6 +300,9 @@ pub enum FontSource {
List(#[template(built_in)] Vec<FontSource>),
/// Resolve the font using a generic font family.
Generic(GenericFontFamily),
/// Use the default font source from the [`DefaultFontSource`] resource.
#[default]
Default,
}

impl FontSource {
Expand Down Expand Up @@ -444,8 +446,20 @@ impl FontSource {
pub fn resolve_font_family<'a>(
&'a self,
fonts: &'a Assets<Font>,
default_source: &'a Self,
) -> Result<FontFamily<'a>, TextError> {
if matches!(self, FontSource::Default) && !matches!(default_source, FontSource::Default) {
return default_source.resolve_font_family(fonts, &FontSource::Default);
}

Ok(match self {
FontSource::Default => FontFamily::named(
fonts
.get(Handle::default().id())
.ok_or(TextError::NoSuchFont)?
.alias
.as_str(),
),
FontSource::Handle(handle) => {
FontFamily::Single(parley::FontFamilyName::Named(Cow::Borrowed(
fonts
Expand All @@ -462,6 +476,7 @@ impl FontSource {
match font_sources.as_slice() {
[] => FontFamily::List(Cow::Owned(Vec::new())),
[source] => match *source {
FontSource::Default => source.resolve_font_family(fonts, default_source)?,
FontSource::Handle(handle) => {
FontFamily::Single(parley::FontFamilyName::Named(Cow::Borrowed(
fonts
Expand Down Expand Up @@ -494,6 +509,23 @@ impl FontSource {
let mut families = Vec::new();
for source in font_sources.iter().copied() {
match source {
FontSource::Default => {
match source.resolve_font_family(fonts, default_source)? {
FontFamily::Single(family) => families.push(family),
FontFamily::List(list) => {
families.extend(list.into_owned());
}
FontFamily::Source(source) => {
for family in parley::FontFamilyName::parse_css_list(
source.as_ref(),
)
.map_while(Result::ok)
{
families.push(family.into_owned());
}
}
}
}
FontSource::Handle(handle) => {
families.push(parley::FontFamilyName::Named(Cow::Borrowed(
fonts
Expand Down Expand Up @@ -543,12 +575,6 @@ impl FontSource {
}
}

impl Default for FontSource {
fn default() -> Self {
Self::Handle(Handle::default())
}
}

impl From<Handle<Font>> for FontSource {
fn from(handle: Handle<Font>) -> Self {
Self::Handle(handle)
Expand Down Expand Up @@ -591,6 +617,11 @@ impl<const N: usize> From<[FontSource; N]> for FontSource {
}
}

/// The default font source used to resolve `FontSource::Default`.
/// `DefaultFontSource(FontSource::Default)` is mapped to the default Font asset.
#[derive(Clone, Debug, Reflect, PartialEq, Default, Resource, Deref, DerefMut)]
pub struct DefaultFontSource(pub FontSource);

/// Generic font families that are resolved through Parley's font database.
#[derive(Default, Clone, Copy, Debug, Reflect, PartialEq, Eq, Hash)]
#[repr(u8)]
Expand Down Expand Up @@ -1728,7 +1759,7 @@ mod tests {
let list = FontSource::List(Vec::new());
assert!(list.flatten().is_empty());
assert_eq!(
list.resolve_font_family(&Assets::<Font>::default()),
list.resolve_font_family(&Assets::<Font>::default(), &FontSource::Default),
Ok(FontFamily::List(Cow::Owned(Vec::new())))
);
}
Expand All @@ -1742,7 +1773,7 @@ mod tests {
let source = FontSource::list([FontSource::from(dummy_font_handle)]);

assert_eq!(
source.resolve_font_family(&fonts),
source.resolve_font_family(&fonts, &FontSource::Default),
Ok(FontFamily::Single(parley::FontFamilyName::Named(
Cow::Borrowed("Dummy Font")
)))
Expand Down
9 changes: 6 additions & 3 deletions crates/bevy_ui/src/widget/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ use bevy_log::warn_once;
use bevy_math::Vec2;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_text::{
ComputedTextBlock, Font, FontAtlasSet, FontCx, FontHinting, LayoutCx, LetterSpacing, LineBreak,
LineHeight, RemSize, ScaleCx, TextBounds, TextColor, TextError, TextFont, TextLayout,
TextLayoutInfo, TextMeasureInfo, TextPipeline, TextReader, TextSection, TextWriter,
ComputedTextBlock, DefaultFontSource, Font, FontAtlasSet, FontCx, FontHinting, LayoutCx,
LetterSpacing, LineBreak, LineHeight, RemSize, ScaleCx, TextBounds, TextColor, TextError,
TextFont, TextLayout, TextLayoutInfo, TextMeasureInfo, TextPipeline, TextReader, TextSection,
TextWriter,
};
use taffy::{style::AvailableSpace, MaybeMath, ResolveOrZero};
use tracing::error;
Expand Down Expand Up @@ -305,6 +306,7 @@ pub fn measure_text_system(
mut font_system: ResMut<FontCx>,
mut layout_cx: ResMut<LayoutCx>,
rem_size: Res<RemSize>,
default_font_source: Res<DefaultFontSource>,
) {
for (
entity,
Expand Down Expand Up @@ -340,6 +342,7 @@ pub fn measure_text_system(
&mut layout_cx,
computed_target.logical_size(),
*rem_size,
&default_font_source.0,
) {
Ok(measure) => {
if block.linebreak == LineBreak::NoWrap {
Expand Down
Loading
Loading