From a7a67402e8e915c5ff6aee1db59b46f38233e10a Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Wed, 20 May 2026 10:21:52 +0100 Subject: [PATCH 1/5] In `update_editable_text_styles`, reresolve `FontSource`s on changes to the font assets. When FontSource resolution failed because an asset wasn't yet loaded, `update_editable_text_styles` didn't resolve the FontSource again the next frame. --- crates/bevy_ui/src/widget/text_input_layout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/widget/text_input_layout.rs b/crates/bevy_ui/src/widget/text_input_layout.rs index 4522911024ace..d849af16aca73 100644 --- a/crates/bevy_ui/src/widget/text_input_layout.rs +++ b/crates/bevy_ui/src/widget/text_input_layout.rs @@ -188,7 +188,7 @@ pub fn update_editable_text_styles( )); } - if text_font.is_changed() { + if text_font.is_changed() || fonts.is_changed() { let Ok(font_family) = resolve_font_source(&text_font.font, fonts.as_ref()) else { continue; }; From 9f729e24d8e9750b00446eaabe5885d2ab7d324b Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 22 May 2026 15:04:20 +0100 Subject: [PATCH 2/5] In `load_font_assets_into_font_collection` set the family name from the loaded collection, instead of the asset path. Store the changed family ids and asset paths, and set the any TextFonts that refer to them as changed. --- crates/bevy_text/src/font.rs | 91 +++++++++++++++++++++++++++--------- 1 file changed, 68 insertions(+), 23 deletions(-) diff --git a/crates/bevy_text/src/font.rs b/crates/bevy_text/src/font.rs index 3c0f804305d42..a66230192b253 100644 --- a/crates/bevy_text/src/font.rs +++ b/crates/bevy_text/src/font.rs @@ -1,16 +1,16 @@ -use crate::ComputedTextBlock; 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::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; use parley::fontique::Blob; -use parley::fontique::FontInfoOverride; use smol_str::SmolStr; /// An [`Asset`] that contains the data for a loaded font, if loaded as an asset. @@ -29,8 +29,9 @@ use smol_str::SmolStr; pub struct Font { /// Content of a font file as bytes pub data: Blob, - /// Font family name. - /// If the font file is a collection with multiple families, the first family name from the last font is used. + /// Font family name used to resolve this asset when referenced by handle. + /// If the font file is a collection with multiple families, this is the family name from the + /// first font face in the collection. pub family_name: SmolStr, } @@ -46,32 +47,76 @@ impl Font { /// Add new font assets to the internal font collection. pub fn load_font_assets_into_font_collection( - fonts: Res>, + mut fonts: ResMut>, mut loaded_fonts: Local>>, mut font_cx: ResMut, - mut text_block_query: Query<&mut ComputedTextBlock>, + mut text_font_query: Query<&mut TextFont>, ) { - let mut new_fonts_added = false; - loaded_fonts.retain(|id| fonts.contains(*id)); - for (id, font) in fonts.iter() { - if loaded_fonts.insert(id) { - font_cx.0.collection.register_fonts( - font.data.clone(), - Some(FontInfoOverride { - family_name: Some(font.family_name.as_str()), - ..Default::default() - }), - ); - new_fonts_added = true; + let new_asset_ids: Vec<_> = fonts.ids().filter(|id| loaded_fonts.insert(*id)).collect(); + + if new_asset_ids.is_empty() { + return; + } + + let mut new_family_ids = Vec::new(); + for asset_id in new_asset_ids.iter() { + let font_data = fonts + .get(*asset_id) + .expect("AssetId should have a corresponding asset") + .data + .clone(); + + let new_fonts = font_cx.collection.register_fonts(font_data, None); + + if let Some((_, family_id)) = new_fonts + .iter() + .flat_map(|(family_id, fonts)| { + fonts + .iter() + .map(move |font_info| (font_info.index(), *family_id)) + }) + .min_by_key(|(index, _)| *index) + && let Some(family_name) = font_cx.0.collection.family_name(family_id) + && let Some(font) = fonts.get_mut_untracked(*asset_id) + { + font.family_name = family_name.into(); + new_family_ids.extend(new_fonts.iter().map(|(family_id, _)| *family_id)); } } - // Whenever new fonts are added, update all text blocks so they use the new fonts. - if new_fonts_added { - for mut block in text_block_query.iter_mut() { - block.needs_rerender = true; + for mut text_font in text_font_query.iter_mut() { + if match &text_font.font { + FontSource::Handle(handle) => new_asset_ids.contains(&handle.id()), + FontSource::Family(name) => font_cx + .collection + .family_id(name) + .is_some_and(|id| new_family_ids.contains(&id)), + generic_source => { + let generic_family = match generic_source { + FontSource::Handle(_) | FontSource::Family(_) => unreachable!(), + FontSource::Serif => parley::GenericFamily::Serif, + FontSource::SansSerif => parley::GenericFamily::SansSerif, + FontSource::Cursive => parley::GenericFamily::Cursive, + FontSource::Fantasy => parley::GenericFamily::Fantasy, + FontSource::Monospace => parley::GenericFamily::Monospace, + FontSource::SystemUi => parley::GenericFamily::SystemUi, + FontSource::UiSerif => parley::GenericFamily::UiSerif, + FontSource::UiSansSerif => parley::GenericFamily::UiSansSerif, + FontSource::UiMonospace => parley::GenericFamily::UiMonospace, + FontSource::UiRounded => parley::GenericFamily::UiRounded, + FontSource::Emoji => parley::GenericFamily::Emoji, + FontSource::Math => parley::GenericFamily::Math, + FontSource::FangSong => parley::GenericFamily::FangSong, + }; + font_cx + .collection + .generic_families(generic_family) + .any(|id| new_family_ids.contains(&id)) + } + } { + text_font.set_changed(); } } } From 4416de9e5b99b899108df6e5f5be1c5deb87d469 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 22 May 2026 15:08:44 +0100 Subject: [PATCH 3/5] reverteded `text_layout_info` changes --- crates/bevy_ui/src/widget/text_input_layout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_ui/src/widget/text_input_layout.rs b/crates/bevy_ui/src/widget/text_input_layout.rs index d849af16aca73..4522911024ace 100644 --- a/crates/bevy_ui/src/widget/text_input_layout.rs +++ b/crates/bevy_ui/src/widget/text_input_layout.rs @@ -188,7 +188,7 @@ pub fn update_editable_text_styles( )); } - if text_font.is_changed() || fonts.is_changed() { + if text_font.is_changed() { let Ok(font_family) = resolve_font_source(&text_font.font, fonts.as_ref()) else { continue; }; From 57c1026c2c7ad5e85ffeca14fe607b34c28a76fc Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 29 May 2026 01:06:10 +0100 Subject: [PATCH 4/5] Removed the mutable deref of `EditableText` at the start of `update_editable_text_styles`. Only mutably access the editor when updates need to be made. --- crates/bevy_ui/src/widget/text_input_layout.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/bevy_ui/src/widget/text_input_layout.rs b/crates/bevy_ui/src/widget/text_input_layout.rs index 4935f06366d4c..dbf348151646f 100644 --- a/crates/bevy_ui/src/widget/text_input_layout.rs +++ b/crates/bevy_ui/src/widget/text_input_layout.rs @@ -170,10 +170,8 @@ pub fn update_editable_text_styles( for (mut editable_text, text_font, line_height, target, text_layout) in editable_text_query.iter_mut() { - let editor = editable_text.editor_mut(); - - if f32::EPSILON < (target.scale_factor() - editor.get_scale()).abs() { - editor.set_scale(target.scale_factor()); + if f32::EPSILON < (target.scale_factor() - editable_text.editor.get_scale()).abs() { + editable_text.editor.set_scale(target.scale_factor()); } if text_font.is_changed() @@ -183,9 +181,12 @@ pub fn update_editable_text_styles( FontSize::Vw(_) | FontSize::Vh(_) | FontSize::VMin(_) | FontSize::VMax(_) ) && target.is_changed() { - editor.edit_styles().insert(StyleProperty::FontSize( - text_font.font_size.eval(target.logical_size(), rem_size.0), - )); + editable_text + .editor + .edit_styles() + .insert(StyleProperty::FontSize( + text_font.font_size.eval(target.logical_size(), rem_size.0), + )); } if text_font.is_changed() { From 00d985589b5994070facc4d1cb09a792758923ee Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Fri, 29 May 2026 02:16:01 +0100 Subject: [PATCH 5/5] Editable text measure funcs are no longer updated directly on font asset changes in `update_editable_text_content_size`. The `text_font.is_changed()` is sufficient by itself as `load_font_assets_into_font_collection` sets `TextFont` components to changed when new font assets can affect font resolution. --- crates/bevy_ui/src/widget/text_input_layout.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/bevy_ui/src/widget/text_input_layout.rs b/crates/bevy_ui/src/widget/text_input_layout.rs index dbf348151646f..0bc6dc2797792 100644 --- a/crates/bevy_ui/src/widget/text_input_layout.rs +++ b/crates/bevy_ui/src/widget/text_input_layout.rs @@ -78,7 +78,6 @@ pub fn update_editable_text_content_size( || text_font.is_changed() || line_height.is_changed() || target.is_changed() - || fonts.is_changed() || rem_size.is_changed()) { continue;