Skip to content

Commit 6ce7ce2

Browse files
committed
Change UI coordinate system to have origin at top left corner (#6000)
# Objective Fixes #5572 ## Solution Approach is to invert the Y-axis of the UI Camera by changing the UI projection matrix to render the UI upside down. After that I'm trying to fix all issues, that pop up: - interaction expected the "old" position - images and text were displayed upside-down - baseline of text was based on the top of the glyph instead of bottom ... probably a lot more. --- Result when running examples: <details> <summary>Button example</summary> main branch: ![button main](https://user-images.githubusercontent.com/4232644/190856087-61dd1d98-42b5-4238-bd97-149744ddfeba.png) this pr: ![button pr](https://user-images.githubusercontent.com/4232644/190856097-3f4bc97a-ed15-4e97-b7f1-2b2dd6bb8b14.png) </details> <details> <summary>Text example</summary> m ![text main](https://user-images.githubusercontent.com/4232644/192142831-4cf19aa1-f49a-485e-af7b-374d6f5c396c.png) ain branch: this pr: ![text pr fixed](https://user-images.githubusercontent.com/4232644/192142829-c433db3b-32e1-4ee8-b493-0b4a4d9c8e70.png) </details> <details> <summary>Text debug example</summary> main branch: ![text_debug main](https://user-images.githubusercontent.com/4232644/192142822-940aefa6-e502-410b-8da4-5570f77b5df2.png) this pr: ![text_debug pr fixed](https://user-images.githubusercontent.com/4232644/194547010-8c968f5c-5a71-4ffc-871d-790c06d48016.png) </details> <details> <summary>Transparency UI example</summary> main branch: ![transparency_ui main](https://user-images.githubusercontent.com/4232644/190856172-328c60fe-3622-4598-97d5-2f1595db13b3.png) this pr: ![transperency_ui pr](https://user-images.githubusercontent.com/4232644/190856179-a2dafb99-41ea-45a9-9dd6-400fa3ef24b9.png) </details> <details> <summary>UI example</summary> **ui example** main branch: ![ui main](https://user-images.githubusercontent.com/4232644/192142812-e20ba31a-6841-46d9-a785-4198cf22dc99.png) this pr: ![ui pr fixed](https://user-images.githubusercontent.com/4232644/192142788-cc0b74e0-7710-4faa-b5a2-60270a5da77c.png) </details> ## Changelog UI coordinate system and cursor position was changed from bottom left origin, y+ up to top left origin, y+ down. ## Migration Guide All flex layout should be inverted (ColumnReverse => Column, FlexStart => FlexEnd, WrapReverse => Wrap) System where dealing with cursor position should be changed to account for cursor position being based on the top left instead of bottom left
1 parent 13dcdba commit 6ce7ce2

File tree

11 files changed

+62
-70
lines changed

11 files changed

+62
-70
lines changed

crates/bevy_text/src/glyph_brush.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,17 @@ impl GlyphBrush {
7373
})
7474
.collect::<Result<Vec<_>, _>>()?;
7575

76-
let mut max_y = std::f32::MIN;
7776
let mut min_x = std::f32::MAX;
77+
let mut min_y = std::f32::MAX;
7878
for sg in &glyphs {
7979
let glyph = &sg.glyph;
80+
8081
let scaled_font = sections_data[sg.section_index].3;
81-
max_y = max_y.max(glyph.position.y - scaled_font.descent());
8282
min_x = min_x.min(glyph.position.x);
83+
min_y = min_y.min(glyph.position.y - scaled_font.ascent());
8384
}
84-
max_y = max_y.floor();
8585
min_x = min_x.floor();
86+
min_y = min_y.floor();
8687

8788
let mut positioned_glyphs = Vec::new();
8889
for sg in glyphs {
@@ -119,7 +120,7 @@ impl GlyphBrush {
119120
let size = Vec2::new(glyph_rect.width(), glyph_rect.height());
120121

121122
let x = bounds.min.x + size.x / 2.0 - min_x;
122-
let y = max_y - bounds.max.y + size.y / 2.0;
123+
let y = bounds.min.y + size.y / 2.0 - min_y;
123124
let position = adjust.position(Vec2::new(x, y));
124125

125126
positioned_glyphs.push(PositionedGlyph {

crates/bevy_ui/src/flex/convert.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ pub fn from_rect(
1010
taffy::geometry::Rect {
1111
start: from_val(scale_factor, rect.left),
1212
end: from_val(scale_factor, rect.right),
13-
// NOTE: top and bottom are intentionally flipped. stretch has a flipped y-axis
14-
top: from_val(scale_factor, rect.bottom),
15-
bottom: from_val(scale_factor, rect.top),
13+
top: from_val(scale_factor, rect.top),
14+
bottom: from_val(scale_factor, rect.bottom),
1615
}
1716
}
1817

crates/bevy_ui/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! This crate contains Bevy's UI system, which can be used to create UI for both 2D and 3D games
22
//! # Basic usage
33
//! Spawn UI elements with [`entity::ButtonBundle`], [`entity::ImageBundle`], [`entity::TextBundle`] and [`entity::NodeBundle`]
4-
//! This UI is laid out with the Flexbox paradigm (see <https://cssreference.io/flexbox/> ) except the vertical axis is inverted
4+
//! This UI is laid out with the Flexbox paradigm (see <https://cssreference.io/flexbox/>)
55
mod flex;
66
mod focus;
77
mod geometry;

crates/bevy_ui/src/render/mod.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use bevy_ecs::prelude::*;
1212
use bevy_math::{Mat4, Rect, UVec4, Vec2, Vec3, Vec4Swizzles};
1313
use bevy_reflect::TypeUuid;
1414
use bevy_render::{
15-
camera::{Camera, CameraProjection, OrthographicProjection, WindowOrigin},
15+
camera::Camera,
1616
color::Color,
1717
render_asset::RenderAssets,
1818
render_graph::{RenderGraph, RunGraphOnViewNode, SlotInfo, SlotType},
@@ -243,15 +243,12 @@ pub fn extract_default_ui_camera_view<T: Component>(
243243
camera.physical_viewport_rect(),
244244
camera.physical_viewport_size(),
245245
) {
246-
let mut projection = OrthographicProjection {
247-
far: UI_CAMERA_FAR,
248-
window_origin: WindowOrigin::BottomLeft,
249-
..Default::default()
250-
};
251-
projection.update(logical_size.x, logical_size.y);
246+
// use a projection matrix with the origin in the top left instead of the bottom left that comes with OrthographicProjection
247+
let projection_matrix =
248+
Mat4::orthographic_rh(0.0, logical_size.x, logical_size.y, 0.0, 0.0, UI_CAMERA_FAR);
252249
let default_camera_view = commands
253250
.spawn(ExtractedView {
254-
projection: projection.get_projection_matrix(),
251+
projection: projection_matrix,
255252
transform: GlobalTransform::from_xyz(
256253
0.0,
257254
0.0,
@@ -464,24 +461,23 @@ pub fn prepare_uinodes(
464461
}
465462
}
466463

467-
// Clip UVs (Note: y is reversed in UV space)
468464
let atlas_extent = extracted_uinode.atlas_size.unwrap_or(uinode_rect.max);
469465
let uvs = [
470466
Vec2::new(
471-
uinode_rect.min.x + positions_diff[0].x,
472-
uinode_rect.max.y - positions_diff[0].y,
473-
),
474-
Vec2::new(
475-
uinode_rect.max.x + positions_diff[1].x,
476-
uinode_rect.max.y - positions_diff[1].y,
467+
uinode_rect.min.x + positions_diff[3].x,
468+
uinode_rect.min.y - positions_diff[3].y,
477469
),
478470
Vec2::new(
479471
uinode_rect.max.x + positions_diff[2].x,
480472
uinode_rect.min.y - positions_diff[2].y,
481473
),
482474
Vec2::new(
483-
uinode_rect.min.x + positions_diff[3].x,
484-
uinode_rect.min.y - positions_diff[3].y,
475+
uinode_rect.max.x + positions_diff[1].x,
476+
uinode_rect.max.y - positions_diff[1].y,
477+
),
478+
Vec2::new(
479+
uinode_rect.min.x + positions_diff[0].x,
480+
uinode_rect.max.y - positions_diff[0].y,
485481
),
486482
]
487483
.map(|pos| pos / atlas_extent);

crates/bevy_ui/src/ui_node.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -303,11 +303,11 @@ pub enum FlexDirection {
303303
/// Same way as text direction along the main axis
304304
#[default]
305305
Row,
306-
/// Flex from bottom to top
306+
/// Flex from top to bottom
307307
Column,
308308
/// Opposite way as text direction along the main axis
309309
RowReverse,
310-
/// Flex from top to bottom
310+
/// Flex from bottom to top
311311
ColumnReverse,
312312
}
313313

crates/bevy_winit/src/lib.rs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -149,12 +149,9 @@ fn change_window(
149149
}
150150
bevy_window::WindowCommand::SetCursorPosition { position } => {
151151
let window = winit_windows.get_window(id).unwrap();
152-
let inner_size = window.inner_size().to_logical::<f32>(window.scale_factor());
152+
153153
window
154-
.set_cursor_position(LogicalPosition::new(
155-
position.x,
156-
inner_size.height - position.y,
157-
))
154+
.set_cursor_position(LogicalPosition::new(position.x, position.y))
158155
.unwrap_or_else(|e| error!("Unable to set cursor position: {}", e));
159156
}
160157
bevy_window::WindowCommand::SetMaximized { maximized } => {
@@ -431,13 +428,8 @@ pub fn winit_runner_with(mut app: App) {
431428
}
432429
WindowEvent::CursorMoved { position, .. } => {
433430
let mut cursor_moved_events = world.resource_mut::<Events<CursorMoved>>();
434-
let winit_window = winit_windows.get_window(window_id).unwrap();
435-
let inner_size = winit_window.inner_size();
436-
437-
// move origin to bottom left
438-
let y_position = inner_size.height as f64 - position.y;
439431

440-
let physical_position = DVec2::new(position.x, y_position);
432+
let physical_position = DVec2::new(position.x, position.y);
441433
window
442434
.update_cursor_physical_position_from_backend(Some(physical_position));
443435

examples/games/game_menu.rs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,8 @@ mod game {
152152
style: Style {
153153
// This will center the current node
154154
margin: UiRect::all(Val::Auto),
155-
// This will display its children in a column, from top to bottom. Unlike
156-
// in Flexbox, Bevy origin is on bottom left, so the vertical axis is reversed
157-
flex_direction: FlexDirection::ColumnReverse,
155+
// This will display its children in a column, from top to bottom
156+
flex_direction: FlexDirection::Column,
158157
// `align_items` will align children on the cross axis. Here the main axis is
159158
// vertical (column), so the cross axis is horizontal. This will center the
160159
// children
@@ -420,7 +419,7 @@ mod menu {
420419
NodeBundle {
421420
style: Style {
422421
margin: UiRect::all(Val::Auto),
423-
flex_direction: FlexDirection::ColumnReverse,
422+
flex_direction: FlexDirection::Column,
424423
align_items: AlignItems::Center,
425424
..default()
426425
},
@@ -533,7 +532,7 @@ mod menu {
533532
NodeBundle {
534533
style: Style {
535534
margin: UiRect::all(Val::Auto),
536-
flex_direction: FlexDirection::ColumnReverse,
535+
flex_direction: FlexDirection::Column,
537536
align_items: AlignItems::Center,
538537
..default()
539538
},
@@ -587,7 +586,7 @@ mod menu {
587586
NodeBundle {
588587
style: Style {
589588
margin: UiRect::all(Val::Auto),
590-
flex_direction: FlexDirection::ColumnReverse,
589+
flex_direction: FlexDirection::Column,
591590
align_items: AlignItems::Center,
592591
..default()
593592
},
@@ -678,7 +677,7 @@ mod menu {
678677
NodeBundle {
679678
style: Style {
680679
margin: UiRect::all(Val::Auto),
681-
flex_direction: FlexDirection::ColumnReverse,
680+
flex_direction: FlexDirection::Column,
682681
align_items: AlignItems::Center,
683682
..default()
684683
},

examples/ui/font_atlas_debug.rs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,27 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut state: ResM
8282
let font_handle = asset_server.load("fonts/FiraSans-Bold.ttf");
8383
state.handle = font_handle.clone();
8484
commands.spawn(Camera2dBundle::default());
85-
commands.spawn(TextBundle::from_section(
86-
"a",
87-
TextStyle {
88-
font: font_handle,
89-
font_size: 60.0,
90-
color: Color::YELLOW,
91-
},
92-
));
85+
commands
86+
.spawn(NodeBundle {
87+
background_color: Color::NONE.into(),
88+
style: Style {
89+
position_type: PositionType::Absolute,
90+
position: UiRect {
91+
bottom: Val::Px(0.0),
92+
..default()
93+
},
94+
..default()
95+
},
96+
..default()
97+
})
98+
.with_children(|parent| {
99+
parent.spawn(TextBundle::from_section(
100+
"a",
101+
TextStyle {
102+
font: font_handle,
103+
font_size: 60.0,
104+
color: Color::YELLOW,
105+
},
106+
));
107+
});
93108
}

examples/ui/text.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
4444
.with_text_alignment(TextAlignment::TOP_CENTER)
4545
// Set the style of the TextBundle itself.
4646
.with_style(Style {
47-
align_self: AlignSelf::FlexEnd,
4847
position_type: PositionType::Absolute,
4948
position: UiRect {
5049
bottom: Val::Px(5.0),
@@ -72,11 +71,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
7271
font_size: 60.0,
7372
color: Color::GOLD,
7473
}),
75-
])
76-
.with_style(Style {
77-
align_self: AlignSelf::FlexEnd,
78-
..default()
79-
}),
74+
]),
8075
FpsText,
8176
));
8277
}

examples/ui/text_debug.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
3535
},
3636
)
3737
.with_style(Style {
38-
align_self: AlignSelf::FlexEnd,
3938
position_type: PositionType::Absolute,
4039
position: UiRect {
4140
top: Val::Px(5.0),
@@ -55,7 +54,6 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
5554
)
5655
.with_text_alignment(TextAlignment::CENTER)
5756
.with_style(Style {
58-
align_self: AlignSelf::FlexEnd,
5957
position_type: PositionType::Absolute,
6058
position: UiRect {
6159
top: Val::Px(5.0),
@@ -115,7 +113,6 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
115113
),
116114
])
117115
.with_style(Style {
118-
align_self: AlignSelf::FlexEnd,
119116
position_type: PositionType::Absolute,
120117
position: UiRect {
121118
bottom: Val::Px(5.0),

examples/ui/ui.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
4848
.spawn(NodeBundle {
4949
style: Style {
5050
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
51-
align_items: AlignItems::FlexEnd,
5251
..default()
5352
},
5453
background_color: Color::rgb(0.15, 0.15, 0.15).into(),
@@ -76,7 +75,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
7675
parent
7776
.spawn(NodeBundle {
7877
style: Style {
79-
flex_direction: FlexDirection::ColumnReverse,
78+
flex_direction: FlexDirection::Column,
8079
justify_content: JustifyContent::Center,
8180
size: Size::new(Val::Px(200.0), Val::Percent(100.0)),
8281
..default()
@@ -109,7 +108,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
109108
parent
110109
.spawn(NodeBundle {
111110
style: Style {
112-
flex_direction: FlexDirection::ColumnReverse,
111+
flex_direction: FlexDirection::Column,
113112
align_self: AlignSelf::Center,
114113
size: Size::new(Val::Percent(100.0), Val::Percent(50.0)),
115114
overflow: Overflow::Hidden,
@@ -124,7 +123,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
124123
.spawn((
125124
NodeBundle {
126125
style: Style {
127-
flex_direction: FlexDirection::ColumnReverse,
126+
flex_direction: FlexDirection::Column,
128127
flex_grow: 1.0,
129128
max_size: Size::UNDEFINED,
130129
..default()
@@ -161,7 +160,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
161160
});
162161
});
163162
});
164-
// absolute positioning
165163
parent
166164
.spawn(NodeBundle {
167165
style: Style {
@@ -277,7 +275,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
277275
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
278276
position_type: PositionType::Absolute,
279277
justify_content: JustifyContent::Center,
280-
align_items: AlignItems::FlexEnd,
278+
align_items: AlignItems::FlexStart,
281279
..default()
282280
},
283281
..default()
@@ -318,8 +316,8 @@ fn mouse_scroll(
318316
MouseScrollUnit::Line => mouse_wheel_event.y * 20.,
319317
MouseScrollUnit::Pixel => mouse_wheel_event.y,
320318
};
321-
scrolling_list.position += dy;
322-
scrolling_list.position = scrolling_list.position.clamp(-max_scroll, 0.);
319+
scrolling_list.position -= dy;
320+
scrolling_list.position = scrolling_list.position.clamp(0., max_scroll);
323321
style.position.top = Val::Px(scrolling_list.position);
324322
}
325323
}

0 commit comments

Comments
 (0)