Text rendering breaks when UiTransform.scale is increased and overflow is not `Visible`.
- Dominant language
- Rust
- Stars
- 48.2k
- Forks
- 4.8k
- Avg merge
- 3d 16h
- Merged PRs (30d)
- 171
Description
## Bevy version and features
Bug exists on 0.17.2 and the current main ([be4114b](https://github.com/bevyengine/bevy/commit/be4114bb9e054578de409d15955c8eb50a990bab) atm)
## Relevant system information
```ignore
SystemInfo { os: "macOS 15.6.1 Sequoia", kernel: "24.6.0", cpu: "Apple M1 Max", core_count: "10", memory: "64.0 GiB" }
AdapterInfo { name: "Apple M1 Max", vendor: 0, device: 0, device_type: IntegratedGpu, driver: "", driver_info: "", backend: Metal }
GPU preprocessing is fully supported on this device.
```
## What went wrong
Working on a graph-based editor, I wanted to implement zooming using `UiTransform.scale`. It works fine or nodes, but completely breaks on text when overflow is set to `Clip`, `Hidden` or `Scroll`. The text gets clipped in a strange way, but also distorted at scale >= 1.5 .
I've created a minimal example that has a root UI node containing a text node that demonstrates this.
## Additional Information
What badly clipped text looks like:
## Example
Show
```rust
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_systems(Startup, setup)
.add_systems(Update, handle_user_input)
.run();
}
#[derive(Component)]
struct RootNode;
#[derive(Component)]
struct DescriptionText;
#[derive(Resource)]
struct OverflowSettings(OverflowAxis);
fn setup(mut commands: Commands) {
// Camera
commands.spawn(Camera2d);
let overflow_settings = OverflowSettings(OverflowAxis::Clip);
commands.spawn((
RootNode,
Node {
left: Val::Px(100.0),
top: Val::Px(100.0),
width: Val::Px(100.0),
height: Val::Px(50.0),
overflow: Overflow::clip(),
..default()
},
BackgroundColor(Color::srgb(0.8, 0.9, 0.6)),
))
.with_children(|parent| {
parent.spawn((
Text::new("Hello World"),
TextFont::from_font_size(12.0),
TextColor(Color::srgb(0.4, 0.4, 0.4)),
TextLayout::new_with_justify(Justify::Center),
));
});
commands.spawn((
DescriptionText,
Text::new(description_node_text(&overflow_settings, 1.0)),
TextFont::from_font_size(16.0),
TextColor(Color::WHITE),
Node {
position_type: PositionType::Absolute,
right: Val::Px(16.0),
top: Val::Px(16.0),
..default()
}
));
commands.insert_resource(overflow_settings);
}
fn handle_user_input(
keys: Res>,
root_node_q: Single<(&mut UiTransform, &mut Node), With>,
// inner_node_q: Single<&mut Node, (With, Without)>,
mut overflow_settings: ResMut,
mut description_text_q: Single<&mut Text, With>,
) {
let (mut root_node_transform, mut root_node) = root_node_q.into_inner();
// let mut inner_node = inner_node_q.into_inner();
if keys.just_pressed(KeyCode::KeyA) {
root_node_transform.scale += Vec2::splat(0.125);
}
if keys.just_pressed(KeyCode::KeyZ) {
root_node_transform.scale = (root_node_transform.scale - Vec2::splat(0.125)).max(Vec2::splat(0.125));
}
if keys.just_pressed(KeyCode::KeyR) {
overflow_settings.0 = next_overflow_axis(overflow_settings.0);
root_node.overflow = Overflow { x: overflow_settings.0, y: overflow_settings.0 };
}
let mut description_text = description_text_q.into_inner();
*description_text = Text::new(description_node_text(&overflow_settings, root_node_transform.scale.x));
}
fn description_node_text(overflow_settings: &OverflowSettings, scale: f32) -> String {
format!(
"Press A/Z to scale the root UI node. \n\
R to change root node overflow.\n\
Current overflow: {:?}.\n\
Current Scale: {:.3}\n\
It gets really weird at 1.5x and above.",
overflow_settings.0,
scale,
)
}
fn next_overflow_axis(current: OverflowAxis) -> OverflowAxis {
match current {
OverflowAxis::Clip => OverflowAxis::Hidden,
OverflowAxis::Hidden => OverflowAxis::Scroll,
OverflowAxis::Scroll => OverflowAxis::Visible,
OverflowAxis::Visible => OverflowAxis::Clip,
}
}
```
Contributor guide
Research direction
Start with the minimal Rust example in the issue and reproduce the problem by changing the root UiTransform.scale and cycling Node overflow between Clip, Hidden, Scroll, and Visible. Trace the UI text rendering and clipping path for scaled nodes; done means text remains correctly shaped and clipped for non-Visible overflow at scales of 1.5 and above.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- game-dev
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100