mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
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
This commit is contained in:
parent
13dcdba05f
commit
6ce7ce208e
11 changed files with 62 additions and 70 deletions
|
@ -73,16 +73,17 @@ impl GlyphBrush {
|
|||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
let mut max_y = std::f32::MIN;
|
||||
let mut min_x = std::f32::MAX;
|
||||
let mut min_y = std::f32::MAX;
|
||||
for sg in &glyphs {
|
||||
let glyph = &sg.glyph;
|
||||
|
||||
let scaled_font = sections_data[sg.section_index].3;
|
||||
max_y = max_y.max(glyph.position.y - scaled_font.descent());
|
||||
min_x = min_x.min(glyph.position.x);
|
||||
min_y = min_y.min(glyph.position.y - scaled_font.ascent());
|
||||
}
|
||||
max_y = max_y.floor();
|
||||
min_x = min_x.floor();
|
||||
min_y = min_y.floor();
|
||||
|
||||
let mut positioned_glyphs = Vec::new();
|
||||
for sg in glyphs {
|
||||
|
@ -119,7 +120,7 @@ impl GlyphBrush {
|
|||
let size = Vec2::new(glyph_rect.width(), glyph_rect.height());
|
||||
|
||||
let x = bounds.min.x + size.x / 2.0 - min_x;
|
||||
let y = max_y - bounds.max.y + size.y / 2.0;
|
||||
let y = bounds.min.y + size.y / 2.0 - min_y;
|
||||
let position = adjust.position(Vec2::new(x, y));
|
||||
|
||||
positioned_glyphs.push(PositionedGlyph {
|
||||
|
|
|
@ -10,9 +10,8 @@ pub fn from_rect(
|
|||
taffy::geometry::Rect {
|
||||
start: from_val(scale_factor, rect.left),
|
||||
end: from_val(scale_factor, rect.right),
|
||||
// NOTE: top and bottom are intentionally flipped. stretch has a flipped y-axis
|
||||
top: from_val(scale_factor, rect.bottom),
|
||||
bottom: from_val(scale_factor, rect.top),
|
||||
top: from_val(scale_factor, rect.top),
|
||||
bottom: from_val(scale_factor, rect.bottom),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! This crate contains Bevy's UI system, which can be used to create UI for both 2D and 3D games
|
||||
//! # Basic usage
|
||||
//! Spawn UI elements with [`entity::ButtonBundle`], [`entity::ImageBundle`], [`entity::TextBundle`] and [`entity::NodeBundle`]
|
||||
//! This UI is laid out with the Flexbox paradigm (see <https://cssreference.io/flexbox/> ) except the vertical axis is inverted
|
||||
//! This UI is laid out with the Flexbox paradigm (see <https://cssreference.io/flexbox/>)
|
||||
mod flex;
|
||||
mod focus;
|
||||
mod geometry;
|
||||
|
|
|
@ -12,7 +12,7 @@ use bevy_ecs::prelude::*;
|
|||
use bevy_math::{Mat4, Rect, UVec4, Vec2, Vec3, Vec4Swizzles};
|
||||
use bevy_reflect::TypeUuid;
|
||||
use bevy_render::{
|
||||
camera::{Camera, CameraProjection, OrthographicProjection, WindowOrigin},
|
||||
camera::Camera,
|
||||
color::Color,
|
||||
render_asset::RenderAssets,
|
||||
render_graph::{RenderGraph, RunGraphOnViewNode, SlotInfo, SlotType},
|
||||
|
@ -243,15 +243,12 @@ pub fn extract_default_ui_camera_view<T: Component>(
|
|||
camera.physical_viewport_rect(),
|
||||
camera.physical_viewport_size(),
|
||||
) {
|
||||
let mut projection = OrthographicProjection {
|
||||
far: UI_CAMERA_FAR,
|
||||
window_origin: WindowOrigin::BottomLeft,
|
||||
..Default::default()
|
||||
};
|
||||
projection.update(logical_size.x, logical_size.y);
|
||||
// use a projection matrix with the origin in the top left instead of the bottom left that comes with OrthographicProjection
|
||||
let projection_matrix =
|
||||
Mat4::orthographic_rh(0.0, logical_size.x, logical_size.y, 0.0, 0.0, UI_CAMERA_FAR);
|
||||
let default_camera_view = commands
|
||||
.spawn(ExtractedView {
|
||||
projection: projection.get_projection_matrix(),
|
||||
projection: projection_matrix,
|
||||
transform: GlobalTransform::from_xyz(
|
||||
0.0,
|
||||
0.0,
|
||||
|
@ -464,24 +461,23 @@ pub fn prepare_uinodes(
|
|||
}
|
||||
}
|
||||
|
||||
// Clip UVs (Note: y is reversed in UV space)
|
||||
let atlas_extent = extracted_uinode.atlas_size.unwrap_or(uinode_rect.max);
|
||||
let uvs = [
|
||||
Vec2::new(
|
||||
uinode_rect.min.x + positions_diff[0].x,
|
||||
uinode_rect.max.y - positions_diff[0].y,
|
||||
),
|
||||
Vec2::new(
|
||||
uinode_rect.max.x + positions_diff[1].x,
|
||||
uinode_rect.max.y - positions_diff[1].y,
|
||||
uinode_rect.min.x + positions_diff[3].x,
|
||||
uinode_rect.min.y - positions_diff[3].y,
|
||||
),
|
||||
Vec2::new(
|
||||
uinode_rect.max.x + positions_diff[2].x,
|
||||
uinode_rect.min.y - positions_diff[2].y,
|
||||
),
|
||||
Vec2::new(
|
||||
uinode_rect.min.x + positions_diff[3].x,
|
||||
uinode_rect.min.y - positions_diff[3].y,
|
||||
uinode_rect.max.x + positions_diff[1].x,
|
||||
uinode_rect.max.y - positions_diff[1].y,
|
||||
),
|
||||
Vec2::new(
|
||||
uinode_rect.min.x + positions_diff[0].x,
|
||||
uinode_rect.max.y - positions_diff[0].y,
|
||||
),
|
||||
]
|
||||
.map(|pos| pos / atlas_extent);
|
||||
|
|
|
@ -303,11 +303,11 @@ pub enum FlexDirection {
|
|||
/// Same way as text direction along the main axis
|
||||
#[default]
|
||||
Row,
|
||||
/// Flex from bottom to top
|
||||
/// Flex from top to bottom
|
||||
Column,
|
||||
/// Opposite way as text direction along the main axis
|
||||
RowReverse,
|
||||
/// Flex from top to bottom
|
||||
/// Flex from bottom to top
|
||||
ColumnReverse,
|
||||
}
|
||||
|
||||
|
|
|
@ -149,12 +149,9 @@ fn change_window(
|
|||
}
|
||||
bevy_window::WindowCommand::SetCursorPosition { position } => {
|
||||
let window = winit_windows.get_window(id).unwrap();
|
||||
let inner_size = window.inner_size().to_logical::<f32>(window.scale_factor());
|
||||
|
||||
window
|
||||
.set_cursor_position(LogicalPosition::new(
|
||||
position.x,
|
||||
inner_size.height - position.y,
|
||||
))
|
||||
.set_cursor_position(LogicalPosition::new(position.x, position.y))
|
||||
.unwrap_or_else(|e| error!("Unable to set cursor position: {}", e));
|
||||
}
|
||||
bevy_window::WindowCommand::SetMaximized { maximized } => {
|
||||
|
@ -431,13 +428,8 @@ pub fn winit_runner_with(mut app: App) {
|
|||
}
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
let mut cursor_moved_events = world.resource_mut::<Events<CursorMoved>>();
|
||||
let winit_window = winit_windows.get_window(window_id).unwrap();
|
||||
let inner_size = winit_window.inner_size();
|
||||
|
||||
// move origin to bottom left
|
||||
let y_position = inner_size.height as f64 - position.y;
|
||||
|
||||
let physical_position = DVec2::new(position.x, y_position);
|
||||
let physical_position = DVec2::new(position.x, position.y);
|
||||
window
|
||||
.update_cursor_physical_position_from_backend(Some(physical_position));
|
||||
|
||||
|
|
|
@ -152,9 +152,8 @@ mod game {
|
|||
style: Style {
|
||||
// This will center the current node
|
||||
margin: UiRect::all(Val::Auto),
|
||||
// This will display its children in a column, from top to bottom. Unlike
|
||||
// in Flexbox, Bevy origin is on bottom left, so the vertical axis is reversed
|
||||
flex_direction: FlexDirection::ColumnReverse,
|
||||
// This will display its children in a column, from top to bottom
|
||||
flex_direction: FlexDirection::Column,
|
||||
// `align_items` will align children on the cross axis. Here the main axis is
|
||||
// vertical (column), so the cross axis is horizontal. This will center the
|
||||
// children
|
||||
|
@ -420,7 +419,7 @@ mod menu {
|
|||
NodeBundle {
|
||||
style: Style {
|
||||
margin: UiRect::all(Val::Auto),
|
||||
flex_direction: FlexDirection::ColumnReverse,
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
|
@ -533,7 +532,7 @@ mod menu {
|
|||
NodeBundle {
|
||||
style: Style {
|
||||
margin: UiRect::all(Val::Auto),
|
||||
flex_direction: FlexDirection::ColumnReverse,
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
|
@ -587,7 +586,7 @@ mod menu {
|
|||
NodeBundle {
|
||||
style: Style {
|
||||
margin: UiRect::all(Val::Auto),
|
||||
flex_direction: FlexDirection::ColumnReverse,
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
|
@ -678,7 +677,7 @@ mod menu {
|
|||
NodeBundle {
|
||||
style: Style {
|
||||
margin: UiRect::all(Val::Auto),
|
||||
flex_direction: FlexDirection::ColumnReverse,
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_items: AlignItems::Center,
|
||||
..default()
|
||||
},
|
||||
|
|
|
@ -82,7 +82,21 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut state: ResM
|
|||
let font_handle = asset_server.load("fonts/FiraSans-Bold.ttf");
|
||||
state.handle = font_handle.clone();
|
||||
commands.spawn(Camera2dBundle::default());
|
||||
commands.spawn(TextBundle::from_section(
|
||||
commands
|
||||
.spawn(NodeBundle {
|
||||
background_color: Color::NONE.into(),
|
||||
style: Style {
|
||||
position_type: PositionType::Absolute,
|
||||
position: UiRect {
|
||||
bottom: Val::Px(0.0),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
})
|
||||
.with_children(|parent| {
|
||||
parent.spawn(TextBundle::from_section(
|
||||
"a",
|
||||
TextStyle {
|
||||
font: font_handle,
|
||||
|
@ -90,4 +104,5 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>, mut state: ResM
|
|||
color: Color::YELLOW,
|
||||
},
|
||||
));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
.with_text_alignment(TextAlignment::TOP_CENTER)
|
||||
// Set the style of the TextBundle itself.
|
||||
.with_style(Style {
|
||||
align_self: AlignSelf::FlexEnd,
|
||||
position_type: PositionType::Absolute,
|
||||
position: UiRect {
|
||||
bottom: Val::Px(5.0),
|
||||
|
@ -72,11 +71,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
font_size: 60.0,
|
||||
color: Color::GOLD,
|
||||
}),
|
||||
])
|
||||
.with_style(Style {
|
||||
align_self: AlignSelf::FlexEnd,
|
||||
..default()
|
||||
}),
|
||||
]),
|
||||
FpsText,
|
||||
));
|
||||
}
|
||||
|
|
|
@ -35,7 +35,6 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
},
|
||||
)
|
||||
.with_style(Style {
|
||||
align_self: AlignSelf::FlexEnd,
|
||||
position_type: PositionType::Absolute,
|
||||
position: UiRect {
|
||||
top: Val::Px(5.0),
|
||||
|
@ -55,7 +54,6 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
)
|
||||
.with_text_alignment(TextAlignment::CENTER)
|
||||
.with_style(Style {
|
||||
align_self: AlignSelf::FlexEnd,
|
||||
position_type: PositionType::Absolute,
|
||||
position: UiRect {
|
||||
top: Val::Px(5.0),
|
||||
|
@ -115,7 +113,6 @@ fn infotext_system(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
),
|
||||
])
|
||||
.with_style(Style {
|
||||
align_self: AlignSelf::FlexEnd,
|
||||
position_type: PositionType::Absolute,
|
||||
position: UiRect {
|
||||
bottom: Val::Px(5.0),
|
||||
|
|
|
@ -48,7 +48,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
|
||||
align_items: AlignItems::FlexEnd,
|
||||
..default()
|
||||
},
|
||||
background_color: Color::rgb(0.15, 0.15, 0.15).into(),
|
||||
|
@ -76,7 +75,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
parent
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
flex_direction: FlexDirection::ColumnReverse,
|
||||
flex_direction: FlexDirection::Column,
|
||||
justify_content: JustifyContent::Center,
|
||||
size: Size::new(Val::Px(200.0), Val::Percent(100.0)),
|
||||
..default()
|
||||
|
@ -109,7 +108,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
parent
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
flex_direction: FlexDirection::ColumnReverse,
|
||||
flex_direction: FlexDirection::Column,
|
||||
align_self: AlignSelf::Center,
|
||||
size: Size::new(Val::Percent(100.0), Val::Percent(50.0)),
|
||||
overflow: Overflow::Hidden,
|
||||
|
@ -124,7 +123,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
.spawn((
|
||||
NodeBundle {
|
||||
style: Style {
|
||||
flex_direction: FlexDirection::ColumnReverse,
|
||||
flex_direction: FlexDirection::Column,
|
||||
flex_grow: 1.0,
|
||||
max_size: Size::UNDEFINED,
|
||||
..default()
|
||||
|
@ -161,7 +160,6 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
});
|
||||
});
|
||||
});
|
||||
// absolute positioning
|
||||
parent
|
||||
.spawn(NodeBundle {
|
||||
style: Style {
|
||||
|
@ -277,7 +275,7 @@ fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
|
|||
size: Size::new(Val::Percent(100.0), Val::Percent(100.0)),
|
||||
position_type: PositionType::Absolute,
|
||||
justify_content: JustifyContent::Center,
|
||||
align_items: AlignItems::FlexEnd,
|
||||
align_items: AlignItems::FlexStart,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
|
@ -318,8 +316,8 @@ fn mouse_scroll(
|
|||
MouseScrollUnit::Line => mouse_wheel_event.y * 20.,
|
||||
MouseScrollUnit::Pixel => mouse_wheel_event.y,
|
||||
};
|
||||
scrolling_list.position += dy;
|
||||
scrolling_list.position = scrolling_list.position.clamp(-max_scroll, 0.);
|
||||
scrolling_list.position -= dy;
|
||||
scrolling_list.position = scrolling_list.position.clamp(0., max_scroll);
|
||||
style.position.top = Val::Px(scrolling_list.position);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue