mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 12:43:34 +00:00
Add UI scaling (#5814)
# Objective - Allow users to change the scaling of the UI - Adopted from #2808 ## Solution - This is an accessibility feature for fixed-size UI elements, allowing the developer to expose a range of UI scales for the player to set a scale that works for their needs. > - The user can modify the UiScale struct to change the scaling at runtime. This multiplies the Px values by the scale given, while not touching any others. > - The example showcases how this even allows for fluid transitions > Here's how the example looks like: https://user-images.githubusercontent.com/1631166/132979069-044161a9-8e85-45ab-9e93-fcf8e3852c2b.mp4 --- ## Changelog - Added a `UiScale` which can be used to scale all of UI Co-authored-by: Andreas Weibye <13300393+Weibye@users.noreply.github.com> Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
parent
f68f5cd2a5
commit
4fadd26168
6 changed files with 194 additions and 14 deletions
10
Cargo.toml
10
Cargo.toml
|
@ -1452,6 +1452,16 @@ description = "Illustrates various features of Bevy UI"
|
||||||
category = "UI (User Interface)"
|
category = "UI (User Interface)"
|
||||||
wasm = true
|
wasm = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "ui_scaling"
|
||||||
|
path = "examples/ui/scaling.rs"
|
||||||
|
|
||||||
|
[package.metadata.example.ui_scaling]
|
||||||
|
name = "UI Scaling"
|
||||||
|
description = "Illustrates how to scale the UI"
|
||||||
|
category = "UI (User Interface)"
|
||||||
|
wasm = true
|
||||||
|
|
||||||
# Window
|
# Window
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "clear_color"
|
name = "clear_color"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
mod convert;
|
mod convert;
|
||||||
|
|
||||||
use crate::{CalculatedSize, Node, Style};
|
use crate::{CalculatedSize, Node, Style, UiScale};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::EventReader,
|
event::EventReader,
|
||||||
|
@ -196,6 +196,7 @@ pub enum FlexError {
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub fn flex_node_system(
|
pub fn flex_node_system(
|
||||||
windows: Res<Windows>,
|
windows: Res<Windows>,
|
||||||
|
ui_scale: Res<UiScale>,
|
||||||
mut scale_factor_events: EventReader<WindowScaleFactorChanged>,
|
mut scale_factor_events: EventReader<WindowScaleFactorChanged>,
|
||||||
mut flex_surface: ResMut<FlexSurface>,
|
mut flex_surface: ResMut<FlexSurface>,
|
||||||
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
||||||
|
@ -215,15 +216,12 @@ pub fn flex_node_system(
|
||||||
|
|
||||||
// assume one window for time being...
|
// assume one window for time being...
|
||||||
let logical_to_physical_factor = windows.scale_factor(WindowId::primary());
|
let logical_to_physical_factor = windows.scale_factor(WindowId::primary());
|
||||||
|
let scale_factor = logical_to_physical_factor * ui_scale.scale;
|
||||||
|
|
||||||
if scale_factor_events.iter().next_back().is_some() {
|
if scale_factor_events.iter().next_back().is_some() || ui_scale.is_changed() {
|
||||||
update_changed(
|
update_changed(&mut *flex_surface, scale_factor, full_node_query);
|
||||||
&mut *flex_surface,
|
|
||||||
logical_to_physical_factor,
|
|
||||||
full_node_query,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
update_changed(&mut *flex_surface, logical_to_physical_factor, node_query);
|
update_changed(&mut *flex_surface, scale_factor, node_query);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_changed<F: WorldQuery>(
|
fn update_changed<F: WorldQuery>(
|
||||||
|
@ -243,7 +241,7 @@ pub fn flex_node_system(
|
||||||
}
|
}
|
||||||
|
|
||||||
for (entity, style, calculated_size) in &changed_size_query {
|
for (entity, style, calculated_size) in &changed_size_query {
|
||||||
flex_surface.upsert_leaf(entity, style, *calculated_size, logical_to_physical_factor);
|
flex_surface.upsert_leaf(entity, style, *calculated_size, scale_factor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: handle removed nodes
|
// TODO: handle removed nodes
|
||||||
|
|
|
@ -22,11 +22,14 @@ pub use ui_node::*;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use crate::{entity::*, geometry::*, ui_node::*, widget::Button, Interaction};
|
pub use crate::{entity::*, geometry::*, ui_node::*, widget::Button, Interaction, UiScale};
|
||||||
}
|
}
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel};
|
use bevy_ecs::{
|
||||||
|
schedule::{ParallelSystemDescriptorCoercion, SystemLabel},
|
||||||
|
system::Resource,
|
||||||
|
};
|
||||||
use bevy_input::InputSystem;
|
use bevy_input::InputSystem;
|
||||||
use bevy_transform::TransformSystem;
|
use bevy_transform::TransformSystem;
|
||||||
use bevy_window::ModifiesWindows;
|
use bevy_window::ModifiesWindows;
|
||||||
|
@ -47,10 +50,27 @@ pub enum UiSystem {
|
||||||
Focus,
|
Focus,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The current scale of the UI.
|
||||||
|
///
|
||||||
|
/// A multiplier to fixed-sized ui values.
|
||||||
|
/// **Note:** This will only affect fixed ui values like [`Val::Px`]
|
||||||
|
#[derive(Debug, Resource)]
|
||||||
|
pub struct UiScale {
|
||||||
|
/// The scale to be applied.
|
||||||
|
pub scale: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for UiScale {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { scale: 1.0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Plugin for UiPlugin {
|
impl Plugin for UiPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_plugin(ExtractComponentPlugin::<UiCameraConfig>::default())
|
app.add_plugin(ExtractComponentPlugin::<UiCameraConfig>::default())
|
||||||
.init_resource::<FlexSurface>()
|
.init_resource::<FlexSurface>()
|
||||||
|
.init_resource::<UiScale>()
|
||||||
.register_type::<AlignContent>()
|
.register_type::<AlignContent>()
|
||||||
.register_type::<AlignItems>()
|
.register_type::<AlignItems>()
|
||||||
.register_type::<AlignSelf>()
|
.register_type::<AlignSelf>()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{CalculatedSize, Size, Style, Val};
|
use crate::{CalculatedSize, Size, Style, UiScale, Val};
|
||||||
use bevy_asset::Assets;
|
use bevy_asset::Assets;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
|
@ -9,7 +9,7 @@ use bevy_math::Vec2;
|
||||||
use bevy_render::texture::Image;
|
use bevy_render::texture::Image;
|
||||||
use bevy_sprite::TextureAtlas;
|
use bevy_sprite::TextureAtlas;
|
||||||
use bevy_text::{DefaultTextPipeline, Font, FontAtlasSet, Text, TextError};
|
use bevy_text::{DefaultTextPipeline, Font, FontAtlasSet, Text, TextError};
|
||||||
use bevy_window::{WindowId, Windows};
|
use bevy_window::Windows;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct QueuedText {
|
pub struct QueuedText {
|
||||||
|
@ -43,6 +43,7 @@ pub fn text_system(
|
||||||
mut textures: ResMut<Assets<Image>>,
|
mut textures: ResMut<Assets<Image>>,
|
||||||
fonts: Res<Assets<Font>>,
|
fonts: Res<Assets<Font>>,
|
||||||
windows: Res<Windows>,
|
windows: Res<Windows>,
|
||||||
|
ui_scale: Res<UiScale>,
|
||||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||||
mut font_atlas_set_storage: ResMut<Assets<FontAtlasSet>>,
|
mut font_atlas_set_storage: ResMut<Assets<FontAtlasSet>>,
|
||||||
mut text_pipeline: ResMut<DefaultTextPipeline>,
|
mut text_pipeline: ResMut<DefaultTextPipeline>,
|
||||||
|
@ -52,7 +53,13 @@ pub fn text_system(
|
||||||
Query<(&Text, &Style, &mut CalculatedSize)>,
|
Query<(&Text, &Style, &mut CalculatedSize)>,
|
||||||
)>,
|
)>,
|
||||||
) {
|
) {
|
||||||
let scale_factor = windows.scale_factor(WindowId::primary());
|
// TODO: This should support window-independent scale settings.
|
||||||
|
// See https://github.com/bevyengine/bevy/issues/5621
|
||||||
|
let scale_factor = if let Some(window) = windows.get_primary() {
|
||||||
|
window.scale_factor() * ui_scale.scale
|
||||||
|
} else {
|
||||||
|
ui_scale.scale
|
||||||
|
};
|
||||||
|
|
||||||
let inv_scale_factor = 1. / scale_factor;
|
let inv_scale_factor = 1. / scale_factor;
|
||||||
|
|
||||||
|
|
|
@ -313,6 +313,7 @@ Example | Description
|
||||||
[Text Debug](../examples/ui/text_debug.rs) | An example for debugging text layout
|
[Text Debug](../examples/ui/text_debug.rs) | An example for debugging text layout
|
||||||
[Transparency UI](../examples/ui/transparency_ui.rs) | Demonstrates transparency for UI
|
[Transparency UI](../examples/ui/transparency_ui.rs) | Demonstrates transparency for UI
|
||||||
[UI](../examples/ui/ui.rs) | Illustrates various features of Bevy UI
|
[UI](../examples/ui/ui.rs) | Illustrates various features of Bevy UI
|
||||||
|
[UI Scaling](../examples/ui/scaling.rs) | Illustrates how to scale the UI
|
||||||
|
|
||||||
## Window
|
## Window
|
||||||
|
|
||||||
|
|
144
examples/ui/scaling.rs
Normal file
144
examples/ui/scaling.rs
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
//! This example illustrates the [`UIScale`] resource from `bevy_ui`.
|
||||||
|
|
||||||
|
use bevy::{prelude::*, utils::Duration};
|
||||||
|
|
||||||
|
const SCALE_TIME: u64 = 400;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, SystemLabel)]
|
||||||
|
struct ApplyScaling;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
App::new()
|
||||||
|
.add_plugins(DefaultPlugins)
|
||||||
|
.insert_resource(TargetScale {
|
||||||
|
start_scale: 1.0,
|
||||||
|
target_scale: 1.0,
|
||||||
|
target_time: Timer::new(Duration::from_millis(SCALE_TIME), false),
|
||||||
|
})
|
||||||
|
.add_startup_system(setup)
|
||||||
|
.add_system(apply_scaling.label(ApplyScaling))
|
||||||
|
.add_system(change_scaling.before(ApplyScaling))
|
||||||
|
.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(mut commands: Commands, asset_server: ResMut<AssetServer>) {
|
||||||
|
commands.spawn_bundle(Camera2dBundle::default());
|
||||||
|
|
||||||
|
let text_style = TextStyle {
|
||||||
|
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||||
|
font_size: 16.,
|
||||||
|
color: Color::BLACK,
|
||||||
|
};
|
||||||
|
|
||||||
|
commands
|
||||||
|
.spawn_bundle(NodeBundle {
|
||||||
|
style: Style {
|
||||||
|
size: Size::new(Val::Percent(50.0), Val::Percent(50.0)),
|
||||||
|
position_type: PositionType::Absolute,
|
||||||
|
position: UiRect {
|
||||||
|
left: Val::Percent(25.),
|
||||||
|
top: Val::Percent(25.),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
justify_content: JustifyContent::SpaceAround,
|
||||||
|
align_items: AlignItems::Center,
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
color: Color::ANTIQUE_WHITE.into(),
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.with_children(|parent| {
|
||||||
|
parent
|
||||||
|
.spawn_bundle(NodeBundle {
|
||||||
|
style: Style {
|
||||||
|
size: Size::new(Val::Px(40.), Val::Px(40.)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
color: Color::RED.into(),
|
||||||
|
..default()
|
||||||
|
})
|
||||||
|
.with_children(|parent| {
|
||||||
|
parent.spawn_bundle(TextBundle::from_section("Size!", text_style));
|
||||||
|
});
|
||||||
|
parent.spawn_bundle(NodeBundle {
|
||||||
|
style: Style {
|
||||||
|
size: Size::new(Val::Percent(15.), Val::Percent(15.)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
color: Color::BLUE.into(),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
parent.spawn_bundle(ImageBundle {
|
||||||
|
style: Style {
|
||||||
|
size: Size::new(Val::Px(30.0), Val::Px(30.0)),
|
||||||
|
..default()
|
||||||
|
},
|
||||||
|
image: asset_server.load("branding/icon.png").into(),
|
||||||
|
..default()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// System that changes the scale of the ui when pressing up or down on the keyboard.
|
||||||
|
fn change_scaling(input: Res<Input<KeyCode>>, mut ui_scale: ResMut<TargetScale>) {
|
||||||
|
if input.just_pressed(KeyCode::Up) {
|
||||||
|
let scale = (ui_scale.target_scale * 2.0).min(8.);
|
||||||
|
ui_scale.set_scale(scale);
|
||||||
|
info!("Scaling up! Scale: {}", ui_scale.target_scale);
|
||||||
|
}
|
||||||
|
if input.just_pressed(KeyCode::Down) {
|
||||||
|
let scale = (ui_scale.target_scale / 2.0).max(1. / 8.);
|
||||||
|
ui_scale.set_scale(scale);
|
||||||
|
info!("Scaling down! Scale: {}", ui_scale.target_scale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Resource)]
|
||||||
|
struct TargetScale {
|
||||||
|
start_scale: f64,
|
||||||
|
target_scale: f64,
|
||||||
|
target_time: Timer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TargetScale {
|
||||||
|
fn set_scale(&mut self, scale: f64) {
|
||||||
|
self.start_scale = self.current_scale();
|
||||||
|
self.target_scale = scale;
|
||||||
|
self.target_time.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_scale(&self) -> f64 {
|
||||||
|
let completion = self.target_time.percent();
|
||||||
|
let multiplier = ease_in_expo(completion as f64);
|
||||||
|
self.start_scale + (self.target_scale - self.start_scale) * multiplier
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tick(&mut self, delta: Duration) -> &Self {
|
||||||
|
self.target_time.tick(delta);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn already_completed(&self) -> bool {
|
||||||
|
self.target_time.finished() && !self.target_time.just_finished()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_scaling(
|
||||||
|
time: Res<Time>,
|
||||||
|
mut target_scale: ResMut<TargetScale>,
|
||||||
|
mut ui_scale: ResMut<UiScale>,
|
||||||
|
) {
|
||||||
|
if target_scale.tick(time.delta()).already_completed() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui_scale.scale = target_scale.current_scale();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ease_in_expo(x: f64) -> f64 {
|
||||||
|
if x == 0. {
|
||||||
|
0.
|
||||||
|
} else {
|
||||||
|
(2.0f64).powf(5. * x - 5.)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue