use bevy::{
anti_alias::{
fxaa::Fxaa,
smaa::{Smaa, SmaaPreset},
taa::TemporalAntiAliasing,
},
feathers::{
controls::{
FeathersMenu, FeathersMenuButton, FeathersMenuItem, FeathersMenuPopup, FeathersSlider,
},
dark_theme::create_dark_theme,
theme::{ThemedText, UiTheme},
FeathersPlugins,
},
input_focus::tab_navigation::TabGroup,
prelude::*,
state::state::FreelyMutableState,
ui_widgets::{
popover::{Popover, PopoverAlign, PopoverPlacement, PopoverSide},
slider_self_update, Activate, SliderPrecision, SliderStep, SliderValue,
},
};
use bevy_mod_outline::*;
#[bevy_main]
fn main() {
App::new()
.insert_resource(ClearColor(Color::BLACK))
.add_plugins((
DefaultPlugins,
OutlinePlugin::EXTRUDE_VERTEX,
FeathersPlugins,
))
.insert_resource(UiTheme(create_dark_theme()))
.init_state::<AAMode>()
.init_state::<OutlineAAMode>()
.init_state::<OutlineKind>()
.add_systems(Startup, (setup, ui.spawn()))
.add_systems(
Update,
(
bounce,
update_label::<AAMode, AaLabel>.run_if(state_changed::<AAMode>),
update_label::<OutlineAAMode, OutlineMsaaLabel>
.run_if(state_changed::<OutlineAAMode>),
update_label::<OutlineKind, OutlineKindLabel>.run_if(state_changed::<OutlineKind>),
apply_scene_aa.run_if(state_changed::<AAMode>),
apply_outline_msaa.run_if(state_changed::<OutlineAAMode>),
apply_outline_kind.run_if(state_changed::<OutlineKind>),
),
)
.run();
}
#[derive(Component)]
struct Bounce;
#[derive(Component)]
struct TheCamera;
#[derive(Component)]
struct TheOutline;
#[derive(Component, Default, Clone)]
struct AaLabel;
#[derive(Component, Default, Clone)]
struct OutlineMsaaLabel;
#[derive(Component, Default, Clone)]
struct OutlineKindLabel;
#[derive(Component, Default, Clone)]
struct SpeedSlider;
#[derive(Copy, Clone, States, Default, PartialEq, Eq, Hash)]
enum AAMode {
#[default]
None,
Msaa(Msaa),
Fxaa,
Smaa,
Taa,
}
impl std::fmt::Debug for AAMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
AAMode::None => write!(f, "None"),
AAMode::Msaa(msaa) => write!(f, "MSAA x{}", msaa.samples()),
AAMode::Fxaa => write!(f, "FXAA"),
AAMode::Smaa => write!(f, "SMAA"),
AAMode::Taa => write!(f, "TAA"),
}
}
}
#[derive(Copy, Clone, States, Debug, Default, PartialEq, Eq, Hash)]
enum OutlineAAMode {
#[default]
Auto,
Off,
Sample2,
Sample4,
Sample8,
}
#[derive(Copy, Clone, States, Debug, Default, PartialEq, Eq, Hash)]
enum OutlineKind {
#[default]
Extrude,
Flood,
}
fn setup(
mut commands: Commands,
mut meshes: ResMut<Assets<Mesh>>,
mut materials: ResMut<Assets<StandardMaterial>>,
) {
commands.spawn((
Mesh3d(meshes.add(Sphere::new(1.0).mesh().ico(9).unwrap())),
MeshMaterial3d(materials.add(StandardMaterial::from(Color::srgb(0.5, 0.5, 0.5)))),
Transform::from_translation(Vec3::new(-1.5, 0.0, 0.0)),
OutlineVolume {
visible: true,
width: 25.0,
colour: Color::srgb(1.0, 1.0, 0.0),
},
TheOutline,
Bounce,
));
commands.spawn((
Mesh3d(meshes.add(Sphere::new(1.0).mesh().ico(20).unwrap())),
MeshMaterial3d(materials.add(StandardMaterial::from(Color::srgb(0.5, 0.5, 0.5)))),
Transform::from_translation(Vec3::new(1.5, 0.0, 0.0)),
Bounce,
));
commands.spawn((
PointLight {
shadow_maps_enabled: true,
..default()
},
Transform::from_xyz(4.0, 8.0, 4.0),
));
commands.spawn((
Camera3d::default(),
Transform::from_xyz(0.0, 0.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
Msaa::Off,
TheCamera,
));
}
fn ui() -> impl SceneList {
bsn_list![(
Node {
position_type: PositionType::Absolute,
top: px(8),
left: px(8),
flex_direction: FlexDirection::Row,
align_items: AlignItems::Center,
column_gap: px(8),
}
TabGroup
Children [
aa_menu(),
outline_msaa_menu(),
outline_method_menu(),
(Text("Speed") ThemedText),
(
@FeathersSlider {
@min: 0.0,
@max: 2.0,
@value: 1.0,
}
Node { width: px(220) }
SliderStep(0.1)
SliderPrecision(1)
SpeedSlider
on(slider_self_update)
),
]
)]
}
fn menu(caption: impl SceneList + 'static, items: impl SceneList + 'static) -> impl Scene {
bsn! {
@FeathersMenu
Children [
(@FeathersMenuButton {
@caption: {Box::new(caption) as Box<dyn SceneList>}
}),
(@FeathersMenuPopup Children [ {Box::new(items) as Box<dyn SceneList>} ])
]
}
}
fn aa_menu() -> impl Scene {
menu(
bsn_list![
(Text("Anti-aliasing: ") ThemedText),
(Text("") ThemedText AaLabel),
],
bsn_list![
menu_item(AAMode::None),
msaa_submenu(),
menu_item(AAMode::Fxaa),
menu_item(AAMode::Smaa),
menu_item(AAMode::Taa),
],
)
}
fn msaa_submenu() -> impl Scene {
bsn! {
@FeathersMenu
Children [
(@FeathersMenuButton {
@caption: {bsn! { Text("MSAA") ThemedText }}
}),
(
@FeathersMenuPopup
Popover {
positions: vec![
PopoverPlacement {
side: PopoverSide::Right,
align: PopoverAlign::Start,
gap: 2.0,
},
PopoverPlacement {
side: PopoverSide::Left,
align: PopoverAlign::Start,
gap: 2.0,
},
],
window_margin: 10.0,
}
Children [
(menu_item(AAMode::Msaa(Msaa::Sample2))),
(menu_item(AAMode::Msaa(Msaa::Sample4))),
(menu_item(AAMode::Msaa(Msaa::Sample8))),
]
)
]
}
}
fn outline_msaa_menu() -> impl Scene {
menu(
bsn_list![
(Text("Outline MSAA: ") ThemedText),
(Text("") ThemedText OutlineMsaaLabel),
],
bsn_list![
menu_item(OutlineAAMode::Auto),
menu_item(OutlineAAMode::Off),
menu_item(OutlineAAMode::Sample2),
menu_item(OutlineAAMode::Sample4),
menu_item(OutlineAAMode::Sample8),
],
)
}
fn outline_method_menu() -> impl Scene {
menu(
bsn_list![
(Text("Outline method: ") ThemedText),
(Text("") ThemedText OutlineKindLabel),
],
bsn_list![
menu_item(OutlineKind::Extrude),
menu_item(OutlineKind::Flood),
],
)
}
fn menu_item<S: FreelyMutableState + Copy>(value: S) -> impl Scene {
let label = format!("{value:?}");
bsn! {
@FeathersMenuItem {
@caption: {bsn! { Text(label) ThemedText }}
}
on(move |_: On<Activate>, mut next: ResMut<NextState<S>>| {
next.set(value);
})
}
}
fn bounce(
mut query: Query<&mut Transform, With<Bounce>>,
slider: Option<Single<&SliderValue, With<SpeedSlider>>>,
timer: Res<Time>,
mut t: Local<f32>,
) {
let speed = slider.map_or(1.0, |s| s.0);
*t = (*t + timer.delta_secs() * speed) % 4.0;
let y = (*t - 2.0).abs() - 1.0;
for mut transform in query.iter_mut() {
transform.translation.y = y;
}
}
fn update_label<S: States + std::fmt::Debug, L: Component>(
state: Res<State<S>>,
mut query: Query<&mut Text, With<L>>,
) {
for mut text in query.iter_mut() {
text.0 = format!("{:?}", state.get());
}
}
fn apply_scene_aa(
state: Res<State<AAMode>>,
camera: Single<Entity, With<TheCamera>>,
mut commands: Commands,
) {
let mut entity = commands.entity(*camera);
entity
.remove::<Fxaa>()
.remove::<Smaa>()
.remove::<TemporalAntiAliasing>();
match *state.get() {
AAMode::None => {
entity.insert(Msaa::Off);
}
AAMode::Msaa(msaa) => {
entity.insert(msaa);
}
AAMode::Fxaa => {
entity.insert((Msaa::Off, Fxaa::default()));
}
AAMode::Smaa => {
entity.insert((
Msaa::Off,
Smaa {
preset: SmaaPreset::Ultra,
},
));
}
AAMode::Taa => {
entity.insert((Msaa::Off, TemporalAntiAliasing::default()));
}
}
}
fn apply_outline_msaa(
state: Res<State<OutlineAAMode>>,
camera: Single<Entity, With<TheCamera>>,
mut commands: Commands,
) {
let outline_msaa = match state.get() {
OutlineAAMode::Auto => OutlineMsaa::Auto,
OutlineAAMode::Off => OutlineMsaa::Msaa(Msaa::Off),
OutlineAAMode::Sample2 => OutlineMsaa::Msaa(Msaa::Sample2),
OutlineAAMode::Sample4 => OutlineMsaa::Msaa(Msaa::Sample4),
OutlineAAMode::Sample8 => OutlineMsaa::Msaa(Msaa::Sample8),
};
commands.entity(*camera).insert(outline_msaa);
}
fn apply_outline_kind(
state: Res<State<OutlineKind>>,
outline: Single<Entity, With<TheOutline>>,
mut commands: Commands,
) {
let mode = match state.get() {
OutlineKind::Extrude => OutlineMode::ExtrudeFlat,
OutlineKind::Flood => OutlineMode::FloodFlat,
};
commands.entity(*outline).insert(mode);
}