Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Add UI scaling #5814

Closed
wants to merge 12 commits into from
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
20 changes: 15 additions & 5 deletions crates/bevy_ui/src/flex/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
mod convert;

use crate::{CalculatedSize, Node, Style};
use crate::{CalculatedSize, Node, Style, UiScale};
use bevy_ecs::{
entity::Entity,
event::EventReader,
Expand Down Expand Up @@ -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>)>,
Expand All @@ -216,14 +217,18 @@ pub fn flex_node_system(
// assume one window for time being...
let logical_to_physical_factor = windows.scale_factor(WindowId::primary());

if scale_factor_events.iter().next_back().is_some() {
if scale_factor_events.iter().next_back().is_some() || ui_scale.is_changed() {
update_changed(
&mut *flex_surface,
logical_to_physical_factor,
logical_to_physical_factor * ui_scale.scale,
cart marked this conversation as resolved.
Show resolved Hide resolved
full_node_query,
);
} else {
update_changed(&mut *flex_surface, logical_to_physical_factor, node_query);
update_changed(
&mut *flex_surface,
logical_to_physical_factor * ui_scale.scale,
node_query,
);
}

fn update_changed<F: WorldQuery>(
Expand All @@ -243,7 +248,12 @@ 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,
logical_to_physical_factor * ui_scale.scale,
);
}

// TODO: handle removed nodes
Expand Down
31 changes: 29 additions & 2 deletions crates/bevy_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -47,10 +50,34 @@ pub enum UiSystem {
Focus,
}

/// The current scale of the UI for all windows
///
/// ## Note
/// This is purely about the logical scale, and can
/// be considered like a zoom
///
/// This only affects pixel sizes, so a percent size will stay at that
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Phrasing:

Suggested change
/// This only affects pixel sizes, so a percent size will stay at that
/// This only affects pixel sizes, so a percent size will stay the same

#[derive(Debug, Resource)]
pub struct UiScale {
/// The scale to be applied
///
/// # Example
///
/// A scale of `2.` will make every pixel size twice as large.
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>()
Expand Down
9 changes: 7 additions & 2 deletions crates/bevy_ui/src/widget/text.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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>,
Expand All @@ -52,7 +53,11 @@ pub fn text_system(
Query<(&Text, &Style, &mut CalculatedSize)>,
)>,
) {
let scale_factor = windows.scale_factor(WindowId::primary());
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;

Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
144 changes: 144 additions & 0 deletions examples/ui/scaling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
//! This example illustrates the UIScale resource from bevy_ui
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's UiScale, it would also be nice if this would link to the element (or does this not work in examples?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really sure. I'll make the change regardless


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.)
}
}