bevyengine / bevyengine/bevy

Text rendering breaks when UiTransform.scale is increased and overflow is not `Visible`.

Open
#21,775 5 comments 0 reactions 0 assignees View on GitHub
A-Text A-UI C-Bug S-Needs-Design
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:

Image

## 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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.