use std::{
f32::consts::PI,
fmt::{self, Formatter},
};
use bevy::{
ecs::system::EntityCommands,
pbr::CascadeShadowConfigBuilder,
prelude::*,
render::view::{ColorGrading, ColorGradingGlobal, ColorGradingSection},
};
use std::fmt::Display;
static FONT_PATH: &str = "fonts/FiraMono-Medium.ttf";
const OPTION_ADJUSTMENT_SPEED: f32 = 0.003;
#[derive(Clone, Copy, PartialEq)]
enum SelectedColorGradingSection {
Highlights,
Midtones,
Shadows,
}
#[derive(Clone, Copy, PartialEq, Default)]
enum SelectedGlobalColorGradingOption {
#[default]
Exposure,
Temperature,
Tint,
Hue,
}
#[derive(Clone, Copy, PartialEq)]
enum SelectedSectionColorGradingOption {
Saturation,
Contrast,
Gamma,
Gain,
Lift,
}
#[derive(Clone, Copy, PartialEq, Resource)]
enum SelectedColorGradingOption {
Global(SelectedGlobalColorGradingOption),
Section(
SelectedColorGradingSection,
SelectedSectionColorGradingOption,
),
}
impl Default for SelectedColorGradingOption {
fn default() -> Self {
Self::Global(default())
}
}
#[derive(Clone, Copy, PartialEq, Component)]
enum ColorGradingOptionWidgetType {
Button,
Label,
Value,
}
#[derive(Clone, Copy, Component)]
struct ColorGradingOptionWidget {
widget_type: ColorGradingOptionWidgetType,
option: SelectedColorGradingOption,
}
#[derive(Clone, Copy, Component)]
struct HelpText;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.init_resource::<SelectedColorGradingOption>()
.add_systems(Startup, setup)
.add_systems(
Update,
(
handle_button_presses,
adjust_color_grading_option,
update_ui_state,
)
.chain(),
)
.run();
}
fn setup(
mut commands: Commands,
currently_selected_option: Res<SelectedColorGradingOption>,
asset_server: Res<AssetServer>,
) {
add_basic_scene(&mut commands, &asset_server);
let font = asset_server.load(FONT_PATH);
let color_grading = ColorGrading::default();
add_buttons(
&mut commands,
¤tly_selected_option,
&font,
&color_grading,
);
add_help_text(&mut commands, &font, ¤tly_selected_option);
add_camera(&mut commands, &asset_server, color_grading);
}
fn add_buttons(
commands: &mut Commands,
currently_selected_option: &SelectedColorGradingOption,
font: &Handle<Font>,
color_grading: &ColorGrading,
) {
commands
.spawn(NodeBundle {
style: Style {
flex_direction: FlexDirection::Column,
position_type: PositionType::Absolute,
row_gap: Val::Px(6.0),
left: Val::Px(10.0),
bottom: Val::Px(10.0),
..default()
},
..default()
})
.with_children(|parent| {
add_buttons_for_global_controls(
parent,
*currently_selected_option,
color_grading,
font,
);
for section in [
SelectedColorGradingSection::Highlights,
SelectedColorGradingSection::Midtones,
SelectedColorGradingSection::Shadows,
] {
add_buttons_for_section(
parent,
section,
*currently_selected_option,
color_grading,
font,
);
}
});
}
fn add_buttons_for_global_controls(
parent: &mut ChildBuilder,
currently_selected_option: SelectedColorGradingOption,
color_grading: &ColorGrading,
font: &Handle<Font>,
) {
parent
.spawn(NodeBundle {
style: Style::default(),
..default()
})
.with_children(|parent| {
parent.spawn(NodeBundle {
style: Style {
width: Val::Px(125.0),
..default()
},
..default()
});
for option in [
SelectedGlobalColorGradingOption::Exposure,
SelectedGlobalColorGradingOption::Temperature,
SelectedGlobalColorGradingOption::Tint,
SelectedGlobalColorGradingOption::Hue,
] {
add_button_for_value(
parent,
SelectedColorGradingOption::Global(option),
currently_selected_option,
color_grading,
font,
);
}
});
}
fn add_buttons_for_section(
parent: &mut ChildBuilder,
section: SelectedColorGradingSection,
currently_selected_option: SelectedColorGradingOption,
color_grading: &ColorGrading,
font: &Handle<Font>,
) {
parent
.spawn(NodeBundle {
style: Style {
align_items: AlignItems::Center,
..default()
},
..default()
})
.with_children(|parent| {
add_text(parent, §ion.to_string(), font, Color::WHITE).insert(Style {
width: Val::Px(125.0),
..default()
});
for option in [
SelectedSectionColorGradingOption::Saturation,
SelectedSectionColorGradingOption::Contrast,
SelectedSectionColorGradingOption::Gamma,
SelectedSectionColorGradingOption::Gain,
SelectedSectionColorGradingOption::Lift,
] {
add_button_for_value(
parent,
SelectedColorGradingOption::Section(section, option),
currently_selected_option,
color_grading,
font,
);
}
});
}
fn add_button_for_value(
parent: &mut ChildBuilder,
option: SelectedColorGradingOption,
currently_selected_option: SelectedColorGradingOption,
color_grading: &ColorGrading,
font: &Handle<Font>,
) {
let is_selected = currently_selected_option == option;
let (bg_color, fg_color) = if is_selected {
(Color::WHITE, Color::BLACK)
} else {
(Color::BLACK, Color::WHITE)
};
parent
.spawn(ButtonBundle {
style: Style {
border: UiRect::all(Val::Px(1.0)),
width: Val::Px(200.0),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
padding: UiRect::axes(Val::Px(12.0), Val::Px(6.0)),
margin: UiRect::right(Val::Px(12.0)),
..default()
},
border_color: BorderColor(Color::WHITE),
border_radius: BorderRadius::MAX,
image: UiImage::default().with_color(bg_color),
..default()
})
.insert(ColorGradingOptionWidget {
widget_type: ColorGradingOptionWidgetType::Button,
option,
})
.with_children(|parent| {
let label = match option {
SelectedColorGradingOption::Global(option) => option.to_string(),
SelectedColorGradingOption::Section(_, option) => option.to_string(),
};
add_text(parent, &label, font, fg_color).insert(ColorGradingOptionWidget {
widget_type: ColorGradingOptionWidgetType::Label,
option,
});
parent.spawn(NodeBundle {
style: Style {
flex_grow: 1.0,
..default()
},
..default()
});
add_text(
parent,
&format!("{:.3}", option.get(color_grading)),
font,
fg_color,
)
.insert(ColorGradingOptionWidget {
widget_type: ColorGradingOptionWidgetType::Value,
option,
});
});
}
fn add_help_text(
commands: &mut Commands,
font: &Handle<Font>,
currently_selected_option: &SelectedColorGradingOption,
) {
commands
.spawn(TextBundle {
style: Style {
position_type: PositionType::Absolute,
left: Val::Px(10.0),
top: Val::Px(10.0),
..default()
},
..TextBundle::from_section(
create_help_text(currently_selected_option),
TextStyle {
font: font.clone(),
font_size: 24.0,
color: Color::WHITE,
},
)
})
.insert(HelpText);
}
fn add_text<'a>(
parent: &'a mut ChildBuilder,
label: &str,
font: &Handle<Font>,
color: Color,
) -> EntityCommands<'a> {
parent.spawn(TextBundle::from_section(
label,
TextStyle {
font: font.clone(),
font_size: 18.0,
color,
},
))
}
fn add_camera(commands: &mut Commands, asset_server: &AssetServer, color_grading: ColorGrading) {
commands.spawn((
Camera3dBundle {
camera: Camera {
hdr: true,
..default()
},
transform: Transform::from_xyz(0.7, 0.7, 1.0)
.looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::Y),
color_grading,
..default()
},
FogSettings {
color: Color::srgb_u8(43, 44, 47),
falloff: FogFalloff::Linear {
start: 1.0,
end: 8.0,
},
..default()
},
EnvironmentMapLight {
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
intensity: 2000.0,
},
));
}
fn add_basic_scene(commands: &mut Commands, asset_server: &AssetServer) {
commands.spawn(SceneBundle {
scene: asset_server.load("models/TonemappingTest/TonemappingTest.gltf#Scene0"),
..default()
});
commands.spawn(SceneBundle {
scene: asset_server.load("models/FlightHelmet/FlightHelmet.gltf#Scene0"),
transform: Transform::from_xyz(0.5, 0.0, -0.5)
.with_rotation(Quat::from_rotation_y(-0.15 * PI)),
..default()
});
commands.spawn(DirectionalLightBundle {
directional_light: DirectionalLight {
illuminance: 15000.0,
shadows_enabled: true,
..default()
},
transform: Transform::from_rotation(Quat::from_euler(
EulerRot::ZYX,
0.0,
PI * -0.15,
PI * -0.15,
)),
cascade_shadow_config: CascadeShadowConfigBuilder {
maximum_distance: 3.0,
first_cascade_far_bound: 0.9,
..default()
}
.into(),
..default()
});
}
impl Display for SelectedGlobalColorGradingOption {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let name = match *self {
SelectedGlobalColorGradingOption::Exposure => "Exposure",
SelectedGlobalColorGradingOption::Temperature => "Temperature",
SelectedGlobalColorGradingOption::Tint => "Tint",
SelectedGlobalColorGradingOption::Hue => "Hue",
};
f.write_str(name)
}
}
impl Display for SelectedColorGradingSection {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let name = match *self {
SelectedColorGradingSection::Highlights => "Highlights",
SelectedColorGradingSection::Midtones => "Midtones",
SelectedColorGradingSection::Shadows => "Shadows",
};
f.write_str(name)
}
}
impl Display for SelectedSectionColorGradingOption {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let name = match *self {
SelectedSectionColorGradingOption::Saturation => "Saturation",
SelectedSectionColorGradingOption::Contrast => "Contrast",
SelectedSectionColorGradingOption::Gamma => "Gamma",
SelectedSectionColorGradingOption::Gain => "Gain",
SelectedSectionColorGradingOption::Lift => "Lift",
};
f.write_str(name)
}
}
impl Display for SelectedColorGradingOption {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
SelectedColorGradingOption::Global(option) => write!(f, "\"{}\"", option),
SelectedColorGradingOption::Section(section, option) => {
write!(f, "\"{}\" for \"{}\"", option, section)
}
}
}
}
impl SelectedSectionColorGradingOption {
fn get(&self, section: &ColorGradingSection) -> f32 {
match *self {
SelectedSectionColorGradingOption::Saturation => section.saturation,
SelectedSectionColorGradingOption::Contrast => section.contrast,
SelectedSectionColorGradingOption::Gamma => section.gamma,
SelectedSectionColorGradingOption::Gain => section.gain,
SelectedSectionColorGradingOption::Lift => section.lift,
}
}
fn set(&self, section: &mut ColorGradingSection, value: f32) {
match *self {
SelectedSectionColorGradingOption::Saturation => section.saturation = value,
SelectedSectionColorGradingOption::Contrast => section.contrast = value,
SelectedSectionColorGradingOption::Gamma => section.gamma = value,
SelectedSectionColorGradingOption::Gain => section.gain = value,
SelectedSectionColorGradingOption::Lift => section.lift = value,
}
}
}
impl SelectedGlobalColorGradingOption {
fn get(&self, global: &ColorGradingGlobal) -> f32 {
match *self {
SelectedGlobalColorGradingOption::Exposure => global.exposure,
SelectedGlobalColorGradingOption::Temperature => global.temperature,
SelectedGlobalColorGradingOption::Tint => global.tint,
SelectedGlobalColorGradingOption::Hue => global.hue,
}
}
fn set(&self, global: &mut ColorGradingGlobal, value: f32) {
match *self {
SelectedGlobalColorGradingOption::Exposure => global.exposure = value,
SelectedGlobalColorGradingOption::Temperature => global.temperature = value,
SelectedGlobalColorGradingOption::Tint => global.tint = value,
SelectedGlobalColorGradingOption::Hue => global.hue = value,
}
}
}
impl SelectedColorGradingOption {
fn get(&self, color_grading: &ColorGrading) -> f32 {
match self {
SelectedColorGradingOption::Global(option) => option.get(&color_grading.global),
SelectedColorGradingOption::Section(
SelectedColorGradingSection::Highlights,
option,
) => option.get(&color_grading.highlights),
SelectedColorGradingOption::Section(SelectedColorGradingSection::Midtones, option) => {
option.get(&color_grading.midtones)
}
SelectedColorGradingOption::Section(SelectedColorGradingSection::Shadows, option) => {
option.get(&color_grading.shadows)
}
}
}
fn set(&self, color_grading: &mut ColorGrading, value: f32) {
match self {
SelectedColorGradingOption::Global(option) => {
option.set(&mut color_grading.global, value);
}
SelectedColorGradingOption::Section(
SelectedColorGradingSection::Highlights,
option,
) => option.set(&mut color_grading.highlights, value),
SelectedColorGradingOption::Section(SelectedColorGradingSection::Midtones, option) => {
option.set(&mut color_grading.midtones, value);
}
SelectedColorGradingOption::Section(SelectedColorGradingSection::Shadows, option) => {
option.set(&mut color_grading.shadows, value);
}
}
}
}
fn handle_button_presses(
mut interactions: Query<(&Interaction, &ColorGradingOptionWidget), Changed<Interaction>>,
mut currently_selected_option: ResMut<SelectedColorGradingOption>,
) {
for (interaction, widget) in interactions.iter_mut() {
if widget.widget_type == ColorGradingOptionWidgetType::Button
&& *interaction == Interaction::Pressed
{
*currently_selected_option = widget.option;
}
}
}
fn update_ui_state(
mut buttons: Query<(&mut UiImage, &ColorGradingOptionWidget)>,
mut button_text: Query<(&mut Text, &ColorGradingOptionWidget), Without<HelpText>>,
mut help_text: Query<&mut Text, With<HelpText>>,
cameras: Query<&ColorGrading>,
currently_selected_option: Res<SelectedColorGradingOption>,
) {
for (mut image, widget) in buttons.iter_mut() {
image.color = if *currently_selected_option == widget.option {
Color::WHITE
} else {
Color::BLACK
};
}
let value_label = cameras
.iter()
.next()
.map(|color_grading| format!("{:.3}", currently_selected_option.get(color_grading)));
for (mut text, widget) in button_text.iter_mut() {
let color = if *currently_selected_option == widget.option {
Color::BLACK
} else {
Color::WHITE
};
for section in &mut text.sections {
section.style.color = color;
}
if widget.widget_type == ColorGradingOptionWidgetType::Value
&& *currently_selected_option == widget.option
{
if let Some(ref value_label) = value_label {
for section in &mut text.sections {
section.value.clone_from(value_label);
}
}
}
}
for mut help_text in help_text.iter_mut() {
for section in &mut help_text.sections {
section.value = create_help_text(¤tly_selected_option);
}
}
}
fn create_help_text(currently_selected_option: &SelectedColorGradingOption) -> String {
format!("Press Left/Right to adjust {}", currently_selected_option)
}
fn adjust_color_grading_option(
mut cameras: Query<&mut ColorGrading>,
input: Res<ButtonInput<KeyCode>>,
currently_selected_option: Res<SelectedColorGradingOption>,
) {
let mut delta = 0.0;
if input.pressed(KeyCode::ArrowLeft) {
delta -= OPTION_ADJUSTMENT_SPEED;
}
if input.pressed(KeyCode::ArrowRight) {
delta += OPTION_ADJUSTMENT_SPEED;
}
for mut color_grading in cameras.iter_mut() {
let new_value = currently_selected_option.get(&color_grading) + delta;
currently_selected_option.set(&mut color_grading, new_value);
}
}