text input: retry the font family resolve when the font asset lands - #25474
text input: retry the font family resolve when the font asset lands#25474Cyannide wants to merge 2 commits into
Conversation
update_editable_text_styles pushed the resolved family only on `text_font.is_changed()`. On the frame an EditableText field spawns, a FontSource::Handle is usually still loading, so resolve_font_family fails -- and TextFont is never `is_changed()` again, so nothing retries. The asset finishes loading, gets registered into the FontContext, and the editor is never told about it: the field renders in parley's default family for the rest of its life, while an equivalent non-editable Text node picks the font up. Attempt the resolve when Assets<Font> changes as well, which is when a late handle actually becomes resolvable.
alice-i-cecile
left a comment
There was a problem hiding this comment.
Can you make this comment briefer? I have a hard time piecing together what it means in the current form. Logic seems fine though :)
I didn't really manage to make it shorter, but at least it reads better...
I tried, but I think I just made it longer 🤣 |
ickshonpe
left a comment
There was a problem hiding this comment.
I thought about adding this fonts changed check in #25450, but there is already a mechanism to set TextFonts changed when fonts are loaded, so it seemed like it would be okay without it. Maybe there is some weakness in the font loading detection logic, it is quite convoluted and confounded by system ordering etc.
It isn't ideal to update all EditableText's on any font asset changes, but it seems fine for now as a defensive measure. It's not like fonts assets are updated constantly, and apps don't tend to have more than a handful of active input fields at once anyway.
|
This test passes on main: #[test]
fn test_update_editable_text_styles_reresolves_family_once_font_loads() {
let mut app = App::new();
app.init_resource::<Assets<Font>>()
.init_resource::<FontCx>()
.init_resource::<RemSize>()
.add_systems(
Update,
(
load_font_assets_into_font_collection,
update_editable_text_styles,
)
.chain(),
);
let font_handle = app.world().resource::<Assets<Font>>().reserve_handle();
let entity = app
.world_mut()
.spawn((
EditableText::new("some text"),
TextFont::from(font_handle.clone()),
ComputedUiRenderTargetInfo::default(),
))
.id();
app.update();
assert!(!app
.world()
.entity(entity)
.get::<EditableText>()
.unwrap()
.editor
.get_styles()
.inner()
.values()
.any(|style| matches!(style, StyleProperty::FontFamily(_))));
app.world_mut()
.resource_mut::<Assets<Font>>()
.insert(
font_handle.id(),
Font::from_bytes(
include_bytes!("../../../bevy_text/src/FiraMono-subset.ttf").to_vec(),
),
)
.unwrap();
app.update();
assert!(app
.world()
.entity(entity)
.get::<EditableText>()
.unwrap()
.editor
.get_styles()
.inner()
.values()
.any(|style| matches!(
style,
StyleProperty::FontFamily(parley::FontFamily::Single(
parley::FontFamilyName::Named(name)
)) if name.as_ref() ==
&app
.world()
.resource::<Assets<Font>>()
.get(&font_handle)
.unwrap()
.alias
)));
}So I'm not sure how it would fail to reresolve on loading, do you have a reproduction? |
It may not even be an issue anymore because of the let-chain fix...it was reproducible before the let-chain with continue in place, but now the line height and alignment are properly handled at spawn even when the font hasn't resolved yet. |
Objective
An
EditableTextfield whoseTextFontnames aFontSource::Handlecan end uprendering in parley's default family for the rest of its life.
update_editable_text_stylespushes the resolved family only whentext_font.is_changed():On the frame a field spawns, its handle is usually still loading, so
resolve_font_familyfails.TextFontis notis_changed()again after thatframe, so nothing ever retries: the font asset finishes loading, gets registered
into the
FontContext, and the editor is never told to use it.The field is left with whatever family parley defaults to, while an equivalent
non-editable
Textnode picks the font up correctly.Solution
Also attempt the resolve when
Assets<Font>changes, which is exactly when alate handle becomes resolvable. One extra
insert(FontFamily)per editablefield per font-asset change; it is skipped entirely once the resolve succeeds
and no further font assets arrive.
A narrower alternative would be to read
AssetEvent<Font>and match theAdded/Modifiedids against each field's handle. That is more code for asystem that already runs every frame and whose body is a handful of style
inserts, so this takes the simpler condition.
Testing
Found while chasing a related bug in a fork of 0.19, where the same skip also
swallowed
LineHeightandTextLayoutsyncing (that code usedcontinuerather than a let-chain, so an unresolved font skipped the rest of the loop
body; main's let-chain refactor already fixed that half). With the retry, a
field whose font handle resolves a frame or two after spawn renders in the
requested family; without it, it never does.