bevy/examples/ui/ui_scaling.rs
Carter Anderson dcc03724a5 Base Sets (#7466)
# Objective

NOTE: This depends on #7267 and should not be merged until #7267 is merged. If you are reviewing this before that is merged, I highly recommend viewing the Base Sets commit instead of trying to find my changes amongst those from #7267.

"Default sets" as described by the [Stageless RFC](https://github.com/bevyengine/rfcs/pull/45) have some [unfortunate consequences](https://github.com/bevyengine/bevy/discussions/7365).

## Solution

This adds "base sets" as a variant of `SystemSet`:

A set is a "base set" if `SystemSet::is_base` returns `true`. Typically this will be opted-in to using the `SystemSet` derive:

```rust
#[derive(SystemSet, Clone, Hash, Debug, PartialEq, Eq)]
#[system_set(base)]
enum MyBaseSet {
  A,
  B,
}
``` 

**Base sets are exclusive**: a system can belong to at most one "base set". Adding a system to more than one will result in an error. When possible we fail immediately during system-config-time with a nice file + line number. For the more nested graph-ey cases, this will fail at the final schedule build. 

**Base sets cannot belong to other sets**: this is where the word "base" comes from

Systems and Sets can only be added to base sets using `in_base_set`. Calling `in_set` with a base set will fail. As will calling `in_base_set` with a normal set.

```rust
app.add_system(foo.in_base_set(MyBaseSet::A))
       // X must be a normal set ... base sets cannot be added to base sets
       .configure_set(X.in_base_set(MyBaseSet::A))
```

Base sets can still be configured like normal sets:

```rust
app.add_system(MyBaseSet::B.after(MyBaseSet::Ap))
``` 

The primary use case for base sets is enabling a "default base set":

```rust
schedule.set_default_base_set(CoreSet::Update)
  // this will belong to CoreSet::Update by default
  .add_system(foo)
  // this will override the default base set with PostUpdate
  .add_system(bar.in_base_set(CoreSet::PostUpdate))
```

This allows us to build apis that work by default in the standard Bevy style. This is a rough analog to the "default stage" model, but it use the new "stageless sets" model instead, with all of the ordering flexibility (including exclusive systems) that it provides.

---

## Changelog

- Added "base sets" and ported CoreSet to use them.

## Migration Guide

TODO
2023-02-06 03:10:08 +00:00

145 lines
4.3 KiB
Rust

//! This example illustrates the [`UIScale`] resource from `bevy_ui`.
use bevy::{prelude::*, text::TextSettings, utils::Duration};
const SCALE_TIME: u64 = 400;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.insert_resource(TextSettings {
allow_dynamic_font_size: true,
..default()
})
.insert_resource(TargetScale {
start_scale: 1.0,
target_scale: 1.0,
target_time: Timer::new(Duration::from_millis(SCALE_TIME), TimerMode::Once),
})
.add_startup_system(setup)
.add_system(change_scaling)
.add_system(apply_scaling.after(change_scaling))
.run();
}
fn setup(mut commands: Commands, asset_server: ResMut<AssetServer>) {
commands.spawn(Camera2dBundle::default());
let text_style = TextStyle {
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
font_size: 16.,
color: Color::BLACK,
};
commands
.spawn(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()
},
background_color: Color::ANTIQUE_WHITE.into(),
..default()
})
.with_children(|parent| {
parent
.spawn(NodeBundle {
style: Style {
size: Size::new(Val::Px(40.), Val::Px(40.)),
..default()
},
background_color: Color::RED.into(),
..default()
})
.with_children(|parent| {
parent.spawn(TextBundle::from_section("Size!", text_style));
});
parent.spawn(NodeBundle {
style: Style {
size: Size::new(Val::Percent(15.), Val::Percent(15.)),
..default()
},
background_color: Color::BLUE.into(),
..default()
});
parent.spawn(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.)
}
}