mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +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)"
|
||||
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
|
||||
[[example]]
|
||||
name = "clear_color"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
mod convert;
|
||||
|
||||
use crate::{CalculatedSize, Node, Style};
|
||||
use crate::{CalculatedSize, Node, Style, UiScale};
|
||||
use bevy_ecs::{
|
||||
entity::Entity,
|
||||
event::EventReader,
|
||||
|
@ -196,6 +196,7 @@ pub enum FlexError {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn flex_node_system(
|
||||
windows: Res<Windows>,
|
||||
ui_scale: Res<UiScale>,
|
||||
mut scale_factor_events: EventReader<WindowScaleFactorChanged>,
|
||||
mut flex_surface: ResMut<FlexSurface>,
|
||||
root_node_query: Query<Entity, (With<Node>, Without<Parent>)>,
|
||||
|
@ -215,15 +216,12 @@ pub fn flex_node_system(
|
|||
|
||||
// assume one window for time being...
|
||||
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() {
|
||||
update_changed(
|
||||
&mut *flex_surface,
|
||||
logical_to_physical_factor,
|
||||
full_node_query,
|
||||
);
|
||||
if scale_factor_events.iter().next_back().is_some() || ui_scale.is_changed() {
|
||||
update_changed(&mut *flex_surface, scale_factor, full_node_query);
|
||||
} 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>(
|
||||
|
@ -243,7 +241,7 @@ pub fn flex_node_system(
|
|||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -22,11 +22,14 @@ pub use ui_node::*;
|
|||
#[doc(hidden)]
|
||||
pub mod prelude {
|
||||
#[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_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel};
|
||||
use bevy_ecs::{
|
||||
schedule::{ParallelSystemDescriptorCoercion, SystemLabel},
|
||||
system::Resource,
|
||||
};
|
||||
use bevy_input::InputSystem;
|
||||
use bevy_transform::TransformSystem;
|
||||
use bevy_window::ModifiesWindows;
|
||||
|
@ -47,10 +50,27 @@ pub enum UiSystem {
|
|||
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 {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugin(ExtractComponentPlugin::<UiCameraConfig>::default())
|
||||
.init_resource::<FlexSurface>()
|
||||
.init_resource::<UiScale>()
|
||||
.register_type::<AlignContent>()
|
||||
.register_type::<AlignItems>()
|
||||
.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_ecs::{
|
||||
entity::Entity,
|
||||
|
@ -9,7 +9,7 @@ use bevy_math::Vec2;
|
|||
use bevy_render::texture::Image;
|
||||
use bevy_sprite::TextureAtlas;
|
||||
use bevy_text::{DefaultTextPipeline, Font, FontAtlasSet, Text, TextError};
|
||||
use bevy_window::{WindowId, Windows};
|
||||
use bevy_window::Windows;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct QueuedText {
|
||||
|
@ -43,6 +43,7 @@ pub fn text_system(
|
|||
mut textures: ResMut<Assets<Image>>,
|
||||
fonts: Res<Assets<Font>>,
|
||||
windows: Res<Windows>,
|
||||
ui_scale: Res<UiScale>,
|
||||
mut texture_atlases: ResMut<Assets<TextureAtlas>>,
|
||||
mut font_atlas_set_storage: ResMut<Assets<FontAtlasSet>>,
|
||||
mut text_pipeline: ResMut<DefaultTextPipeline>,
|
||||
|
@ -52,7 +53,13 @@ pub fn text_system(
|
|||
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;
|
||||
|
||||
|
|
|
@ -313,6 +313,7 @@ Example | Description
|
|||
[Text Debug](../examples/ui/text_debug.rs) | An example for debugging text layout
|
||||
[Transparency UI](../examples/ui/transparency_ui.rs) | Demonstrates transparency for 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
|
||||
|
||||
|
|
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