Skip to main content

ui_scaling/
ui_scaling.rs

1//! This example illustrates the [`UiScale`] resource from `bevy_ui`.
2
3use bevy::{color::palettes::css::*, prelude::*};
4
5use core::time::Duration;
6
7const SCALE_TIME: u64 = 400;
8
9fn main() {
10    App::new()
11        .add_plugins(DefaultPlugins)
12        .insert_resource(TargetScale {
13            start_scale: 1.0,
14            target_scale: 1.0,
15            target_time: Timer::new(Duration::from_millis(SCALE_TIME), TimerMode::Once),
16        })
17        .add_systems(Startup, setup)
18        .add_systems(
19            Update,
20            (change_scaling, apply_scaling.after(change_scaling)),
21        )
22        .run();
23}
24
25fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
26    commands.spawn(Camera2d);
27
28    let text_font = TextFont {
29        font_size: FontSize::Px(13.),
30        ..default()
31    };
32
33    commands
34        .spawn((
35            Node {
36                width: percent(50),
37                height: percent(50),
38                position_type: PositionType::Absolute,
39                left: percent(25),
40                top: percent(25),
41                justify_content: JustifyContent::SpaceAround,
42                align_items: AlignItems::Center,
43                ..default()
44            },
45            BackgroundColor(ANTIQUE_WHITE.into()),
46        ))
47        .with_children(|parent| {
48            parent
49                .spawn((
50                    Node {
51                        width: px(40),
52                        height: px(40),
53                        ..default()
54                    },
55                    BackgroundColor(RED.into()),
56                ))
57                .with_children(|parent| {
58                    parent.spawn((Text::new("Size!"), text_font, TextColor::BLACK));
59                });
60            parent.spawn((
61                Node {
62                    width: percent(15),
63                    height: percent(15),
64                    ..default()
65                },
66                BackgroundColor(BLUE.into()),
67            ));
68            parent.spawn((
69                ImageNode::new(asset_server.load("branding/icon.png")),
70                Node {
71                    width: px(30),
72                    height: px(30),
73                    ..default()
74                },
75            ));
76        });
77}
78
79/// System that changes the scale of the ui when pressing up or down on the keyboard.
80fn change_scaling(input: Res<ButtonInput<KeyCode>>, mut ui_scale: ResMut<TargetScale>) {
81    if input.just_pressed(KeyCode::ArrowUp) {
82        let scale = (ui_scale.target_scale * 2.0).min(8.);
83        ui_scale.set_scale(scale);
84        info!("Scaling up! Scale: {}", ui_scale.target_scale);
85    }
86    if input.just_pressed(KeyCode::ArrowDown) {
87        let scale = (ui_scale.target_scale / 2.0).max(1. / 8.);
88        ui_scale.set_scale(scale);
89        info!("Scaling down! Scale: {}", ui_scale.target_scale);
90    }
91}
92
93#[derive(Resource)]
94struct TargetScale {
95    start_scale: f32,
96    target_scale: f32,
97    target_time: Timer,
98}
99
100impl TargetScale {
101    fn set_scale(&mut self, scale: f32) {
102        self.start_scale = self.current_scale();
103        self.target_scale = scale;
104        self.target_time.reset();
105    }
106
107    fn current_scale(&self) -> f32 {
108        let completion = self.target_time.fraction();
109        let t = ease_in_expo(completion);
110        self.start_scale.lerp(self.target_scale, t)
111    }
112
113    fn tick(&mut self, delta: Duration) -> &Self {
114        self.target_time.tick(delta);
115        self
116    }
117
118    fn already_completed(&self) -> bool {
119        self.target_time.is_finished() && !self.target_time.just_finished()
120    }
121}
122
123fn apply_scaling(
124    time: Res<Time>,
125    mut target_scale: ResMut<TargetScale>,
126    mut ui_scale: ResMut<UiScale>,
127) {
128    if target_scale.tick(time.delta()).already_completed() {
129        return;
130    }
131
132    ui_scale.0 = target_scale.current_scale();
133}
134
135fn ease_in_expo(x: f32) -> f32 {
136    if x == 0. {
137        0.
138    } else {
139        ops::powf(2.0f32, 5. * x - 5.)
140    }
141}