Skip to content

Minimal responsive FontSize support#22614

Merged
alice-i-cecile merged 40 commits into
bevyengine:mainfrom
ickshonpe:font-size
Feb 2, 2026
Merged

Minimal responsive FontSize support#22614
alice-i-cecile merged 40 commits into
bevyengine:mainfrom
ickshonpe:font-size

Conversation

@ickshonpe

@ickshonpe ickshonpe commented Jan 20, 2026

Copy link
Copy Markdown
Contributor

Objective

Add responsive font sizes supporting rem and viewport units to bevy_text with minimal changes to the APIs and systems.

Solution

Introduce a new FontSize enum:

pub enum FontSize {
    /// Font Size in logical pixels.
    Px(f32),
    /// Font size as a percentage of the viewport width.
    Vw(f32),
    /// Font size as a percentage of the viewport height.
    Vh(f32),
    /// Font size as a percentage of the smaller of the viewport width and height.
    VMin(f32),
    /// Font size as a percentage of the larger of the viewport width and height.
    VMax(f32),
    /// Font Size relative to the value of the `RemSize` resource.
    Rem(f32),
}

This replaces the f32 value of TextFont's font_size field.

The viewport variants work the same way as their respective Val counterparts.

Rem values are multiplied by the value of the RemSize resource (which newtypes an f32).

FontSize provides an eval method that takes a logical viewport size and rem base size and returns an f32 logical font size. The resolved logical font size is then written into the Attributes passed to Cosmic Text by TextPipeline::update_buffer.

Any text implementation using bevy_text must now provide viewport and rem base values when calling TextPipeline::update_buffer or create_measure.

Text2d uses the size of the primary window to resolve viewport values (or Vec2::splat(1000) if no primary window is found). This is a deliberate compromise, a single Text2d can be rendered to multiple viewports using RenderLayers, so it's difficult to find a rule for which viewport size should be chosen.

Change detection

ComputedTextBlock has two new fields: uses_viewport_sizes and uses_rem_sizes, which are set to true in TextPipeline::update_buffer iff any text section in the block uses viewport or rem font sizes, respectively.

The ComputedTextBlock::needs_rerender method has been modified to take take two bool parameters:

    pub fn needs_rerender(
        &self,
        is_viewport_size_changed: bool,
        is_rem_size_changed: bool,
    ) -> bool {
        self.needs_rerender
            || (is_viewport_size_changed && self.uses_viewport_sizes)
            || (is_rem_size_changed && self.uses_rem_sizes)
    }

This ensures that text reupdates will also be scheduled if one of the text section's uses a viewport font size and the local viewport size changed, or if one of the text section's uses a rem font size and the rem size changed.

Limitations

There are some limitations because we don't have any sort of font style inheritance yet:

  • "rem" units aren't proper rem units, and just based on the value of a resource.
  • "em" units are resolved based on inherited font size, so can't be implemented without inheritance support.

Notes

  • This PR is quite small and not very technical. Reviewers don't need to be especially familiar with bevy_text. Most of the changes are to the examples.

  • We could consider using Val instead of FontSize, then we could use Val's constructor functions which would be much nicer, but some variants might not have sensible interpretations in both UI and Text2d contexts. Also we'd have to make Val accessible to bevy_text.

Testing

The changes to the text systems are relatively trivial and easy to understand. I already added a minor change to the text example to use Vh font size for the "hello bevy" text in the bottom right corner. If you change the size of the window, you should see the text change size in response. The text initially flickers before it updates because of some unrelated asset/image changes that mean that font textures aren't ready until the frame after the text update that changes the font size.

Most of the example migrations were automated using regular expressions, and there are bound to be mistakes in those changes. It's infeasible to check every single example thoroughly, but it's early enough in the release cycle that I don't think we should be too worried if a few bugs slip in.

@ickshonpe ickshonpe added C-Feature A new feature, making something new possible A-UI Graphical user interfaces, styles, layouts, and widgets A-Text Rendering and layout for characters M-Deliberate-Rendering-Change An intentional change to how tests and examples are rendered labels Jan 20, 2026
@alice-i-cecile alice-i-cecile added M-Release-Note Work that should be called out in the blog due to impact S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jan 20, 2026
@ickshonpe ickshonpe added the D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes label Jan 20, 2026
@mnmaita

mnmaita commented Jan 21, 2026

Copy link
Copy Markdown
Member

Wondering if this PR supersedes #9524?

@ickshonpe

ickshonpe commented Jan 21, 2026

Copy link
Copy Markdown
Contributor Author

Wondering if this PR supersedes #9524?

Yeah got a bunch of open PRs that I need to clean up. Closed it.

@kfc35 kfc35 self-requested a review January 27, 2026 05:28

@kfc35 kfc35 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic looks good to me, just minor comments.

For other reviewers: This change is really not too bad to review!

Comment thread release-content/release-notes/new_font_features.md Outdated
Comment thread examples/ui/text/text.rs
Comment thread examples/dev_tools/fps_overlay.rs Outdated

@alice-i-cecile alice-i-cecile left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Surprisingly straightforward. Very happy about this!

@alice-i-cecile alice-i-cecile changed the title Minimal FontSize support Minimal responsive FontSize support Feb 2, 2026
@alice-i-cecile alice-i-cecile added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Feb 2, 2026
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Feb 2, 2026
Merged via the queue into bevyengine:main with commit 6ca4769 Feb 2, 2026
40 checks passed
@ickshonpe ickshonpe mentioned this pull request Feb 3, 2026
viridia pushed a commit to viridia/bevy that referenced this pull request Feb 3, 2026
# Objective

Add responsive font sizes supporting rem and viewport units to
`bevy_text` with minimal changes to the APIs and systems.

## Solution

Introduce a new `FontSize` enum:

```rust
pub enum FontSize {
    /// Font Size in logical pixels.
    Px(f32),
    /// Font size as a percentage of the viewport width.
    Vw(f32),
    /// Font size as a percentage of the viewport height.
    Vh(f32),
    /// Font size as a percentage of the smaller of the viewport width and height.
    VMin(f32),
    /// Font size as a percentage of the larger of the viewport width and height.
    VMax(f32),
    /// Font Size relative to the value of the `RemSize` resource.
    Rem(f32),
}
```

This replaces the `f32` value of `TextFont`'s `font_size` field.

The viewport variants work the same way as their respective `Val`
counterparts.

`Rem` values are multiplied by the value of the `RemSize` resource
(which newtypes an `f32`).

`FontSize` provides an `eval` method that takes a logical viewport size
and rem base size and returns an `f32` logical font size. The resolved
logical font size is then written into the `Attributes` passed to Cosmic
Text by `TextPipeline::update_buffer`.

Any text implementation using `bevy_text` must now provide viewport and
rem base values when calling `TextPipeline::update_buffer` or
`create_measure`.

`Text2d` uses the size of the primary window to resolve viewport values
(or `Vec2::splat(1000)` if no primary window is found). This is a
deliberate compromise, a single `Text2d` can be rendered to multiple
viewports using `RenderLayers`, so it's difficult to find a rule for
which viewport size should be chosen.

### Change detection 

`ComputedTextBlock` has two new fields: `uses_viewport_sizes` and
`uses_rem_sizes`, which are set to true in `TextPipeline::update_buffer`
iff any text section in the block uses viewport or rem font sizes,
respectively.

The `ComputedTextBlock::needs_rerender` method has been modified to take
take two bool parameters:
```rust
    pub fn needs_rerender(
        &self,
        is_viewport_size_changed: bool,
        is_rem_size_changed: bool,
    ) -> bool {
        self.needs_rerender
            || (is_viewport_size_changed && self.uses_viewport_sizes)
            || (is_rem_size_changed && self.uses_rem_sizes)
    }
 ```
This ensures that text reupdates will also be scheduled if one of the text section's uses a viewport font size and the local viewport size changed, or if one of the text section's uses a rem font size and the rem size changed.

#### Limitations

There are some limitations because we don't have any sort of font style inheritance yet:

* "rem" units aren't proper rem units, and just based on the value of a resource. 
* "em" units are resolved based on inherited font size, so can't be implemented without inheritance support.

#### Notes

* This PR is quite small and not very technical. Reviewers don't need to be especially familiar with `bevy_text`. Most of the changes are to the examples.

* We could consider using `Val` instead of `FontSize`, then we could use `Val`'s constructor functions which would be much nicer, but some variants might not have sensible interpretations in both UI and Text2d contexts. Also we'd have to make `Val` accessible to `bevy_text`.

## Testing

The changes to the text systems are relatively trivial and easy to understand.  I already added a minor change to the `text` example to use `Vh` font size for the "hello bevy" text in the bottom right corner. If you change the size of the window, you should see the text change size in response. The text initially flickers before it updates because of some unrelated asset/image changes that mean that font textures aren't ready until the frame after the text update that changes the font size.

Most of the example migrations were automated using regular expressions, and there are bound to be mistakes in those changes. It's infeasible to check every single example thoroughly, but it's early enough in the release cycle that I don't think we should be too worried if a few bugs slip in.

---------

Co-authored-by: Kevin Chen <chen.kevin.f@gmail.com>
.map(|window| window.resolution.size())
.unwrap_or(Vec2::splat(1000.));

let viewport_size_changed = *last_logical_viewport_size == logical_viewport_size;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks inverted: viewport_size_changed is computed with ==, so it becomes true when the viewport is unchanged and false on the actual resize frame. I think this should be !=, otherwise viewport-based text may rerender unnecessarily and resize updates get delayed by a frame.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Text Rendering and layout for characters A-UI Graphical user interfaces, styles, layouts, and widgets C-Feature A new feature, making something new possible D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes M-Deliberate-Rendering-Change An intentional change to how tests and examples are rendered M-Release-Note Work that should be called out in the blog due to impact S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants