From 472808e6061ecf946bc3c27f5f1679a26ad11f14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sat, 11 Sep 2021 22:51:05 +0200 Subject: [PATCH 01/11] Allow for ui scaling --- Cargo.toml | 4 + crates/bevy_ui/src/flex/mod.rs | 20 +++-- crates/bevy_ui/src/lib.rs | 28 +++++- crates/bevy_ui/src/widget/text.rs | 5 +- examples/ui/scaling.rs | 142 ++++++++++++++++++++++++++++++ 5 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 examples/ui/scaling.rs diff --git a/Cargo.toml b/Cargo.toml index 0fafd91be7970..598071dfa7593 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -437,6 +437,10 @@ path = "examples/ui/text_debug.rs" name = "ui" path = "examples/ui/ui.rs" +[[example]] +name = "scaling" +path = "examples/ui/scaling.rs" + # Window [[example]] name = "clear_color" diff --git a/crates/bevy_ui/src/flex/mod.rs b/crates/bevy_ui/src/flex/mod.rs index 0bb7a573d302a..b82e970c34ad0 100644 --- a/crates/bevy_ui/src/flex/mod.rs +++ b/crates/bevy_ui/src/flex/mod.rs @@ -1,6 +1,6 @@ mod convert; -use crate::{CalculatedSize, Node, Style}; +use crate::{CalculatedSize, Node, Style, UiScale}; use bevy_app::EventReader; use bevy_ecs::{ entity::Entity, @@ -198,6 +198,7 @@ pub enum FlexError { #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub fn flex_node_system( windows: Res, + ui_scale: Res, mut scale_factor_events: EventReader, mut flex_surface: ResMut, root_node_query: Query, Without)>, @@ -222,14 +223,18 @@ pub fn flex_node_system( 1. }; - 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, 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( @@ -251,7 +256,12 @@ pub fn flex_node_system( } for (entity, style, calculated_size) in changed_size_query.iter() { - 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 diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index e9ca5b3b328f8..44e5de88c72ba 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -18,7 +18,9 @@ pub use ui_node::*; pub mod prelude { #[doc(hidden)] - pub use crate::{entity::*, ui_node::*, widget::Button, Anchors, Interaction, Margins}; + pub use crate::{ + entity::*, ui_node::*, widget::Button, Anchors, Interaction, Margins, UiScale, + }; } use bevy_app::prelude::*; @@ -39,9 +41,33 @@ pub enum UiSystem { Focus, } +#[derive(Debug)] +/// 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 +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.init_resource::() + .init_resource::() .register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index c59230b4b323a..75a17ad27626c 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -1,4 +1,4 @@ -use crate::{CalculatedSize, Node, Style, Val}; +use crate::{CalculatedSize, Node, Style, UiScale, Val}; use bevy_asset::Assets; use bevy_ecs::{ entity::Entity, @@ -49,6 +49,7 @@ pub fn text_system( mut textures: ResMut>, fonts: Res>, windows: Res, + ui_scale: Res, mut texture_atlases: ResMut>, mut font_atlas_set_storage: ResMut>, mut text_pipeline: ResMut, @@ -62,7 +63,7 @@ pub fn text_system( window.scale_factor() } else { 1. - }; + } * ui_scale.scale; let inv_scale_factor = 1. / scale_factor; diff --git a/examples/ui/scaling.rs b/examples/ui/scaling.rs new file mode 100644 index 0000000000000..ad0b21c7136f2 --- /dev/null +++ b/examples/ui/scaling.rs @@ -0,0 +1,142 @@ +use bevy::{prelude::*, utils::Duration}; + +const SCALE_TIME: u64 = 400; + +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, SystemLabel)] +struct ApplyScaling; + +/// This example illustrates the various features of Bevy UI. +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .insert_resource(TargetScale { + target_scale: 1.0, + start_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, + mut materials: ResMut>, +) { + // ui camera + commands.spawn_bundle(UiCameraBundle::default()); + + let text_style = TextStyle { + font: asset_server.load("fonts/FiraMono-Medium.ttf"), + font_size: 16., + color: Color::BLACK, + }; + + // root node + commands + .spawn_bundle(NodeBundle { + style: Style { + size: Size::new(Val::Percent(50.0), Val::Percent(50.0)), + position_type: PositionType::Absolute, + position: Rect { + left: Val::Percent(25.), + top: Val::Percent(25.), + ..Default::default() + }, + justify_content: JustifyContent::SpaceAround, + align_items: AlignItems::Center, + ..Default::default() + }, + material: materials.add(Color::ANTIQUE_WHITE.into()), + ..Default::default() + }) + .with_children(|parent| { + parent + .spawn_bundle(NodeBundle { + style: Style { + size: Size::new(Val::Px(40.), Val::Px(40.)), + ..Default::default() + }, + material: materials.add(Color::RED.into()), + ..Default::default() + }) + .with_children(|parent| { + parent.spawn_bundle(TextBundle { + text: Text::with_section("Size!", text_style, TextAlignment::default()), + ..Default::default() + }); + }); + parent.spawn_bundle(NodeBundle { + style: Style { + size: Size::new(Val::Percent(15.), Val::Percent(15.)), + ..Default::default() + }, + material: materials.add(Color::BLUE.into()), + ..Default::default() + }); + parent.spawn_bundle(ImageBundle { + style: Style { + size: Size::new(Val::Px(30.0), Val::Px(30.0)), + ..Default::default() + }, + material: materials.add(asset_server.load("branding/icon.png").into()), + ..Default::default() + }); + }); +} + +fn change_scaling(input: Res>, mut ui_scale: ResMut) { + 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); + } +} + +struct TargetScale { + target_scale: f64, + start_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 apply_scaling( + time: Res