use std::mem;
use std::sync::Arc;
use std::time::Duration;
use egui::special_emojis::GITHUB;
use egui::{ClippedMesh, Color32, CtxRef, DragValue, Frame, Id, Texture, TextureId, Ui};
use egui_wgpu_backend::ScreenDescriptor;
use egui_winit_platform::Platform;
use image::ImageFormat;
use log::debug;
use winit::event::Event;
use nuance::Slider;
use crate::app::Nuance;
pub struct Gui {
pub egui_platform: Platform,
pub ui_width: u32,
pub profiling_window: bool,
export_window: bool,
}
impl Gui {
pub fn new(egui_platform: Platform, ui_width: u32) -> Self {
egui_platform.context().set_pixels_per_point(2.0);
Self {
egui_platform,
ui_width,
profiling_window: false,
export_window: false,
}
}
pub fn handle_event(&mut self, event: &Event<()>) {
self.egui_platform.handle_event(event);
}
pub fn update_time(&mut self, time: f64) {
self.egui_platform.update_time(time);
}
pub fn render(app: &mut Nuance, window: &ScreenDescriptor) -> Vec<ClippedMesh> {
puffin::profile_scope!("create gui");
app.gui.egui_platform.begin_frame();
let mut framerate = (1.0 / app.settings.target_framerate.as_secs_f32()).round() as u32;
egui::SidePanel::left("params").show(&app.gui.context(), |ui| {
ui.set_width(app.gui.ui_width as f32);
ui.label(format!(
"resolution : {:.0}x{:.0} px",
app.globals.resolution.x, app.globals.resolution.y
))
.on_hover_text("The resolution of the GPU output (on the right)");
ui.label(format!(
"mouse : ({:.0}, {:.0}) px",
app.globals.mouse.x, app.globals.mouse.y
))
.on_hover_text("The position of the mouse pointer as sent to the shader");
ui.label(format!("mouse wheel : {:.1}", app.globals.mouse_wheel))
.on_hover_text("The current value of the mouse wheel global");
ui.label(format!("time : {:.3} s", app.globals.time))
.on_hover_text("Time elapsed since the start of the shader execution");
ui.label(format!("frame : {}", app.globals.frame))
.on_hover_text("Number of frames rendered since the start of the shader execution");
if ui
.small_button("Reset")
.on_hover_text("Reset the shader globals")
.clicked()
{
app.reset_globals();
}
ui.separator();
ui.label("Settings");
ui.add(
DragValue::new(&mut framerate)
.prefix("framerate : ")
.clamp_range(4.0..=120.0)
.max_decimals(0)
.speed(0.1),
)
.on_hover_text("This is the framerate limit of the whole application.");
ui.add(
DragValue::new(&mut app.settings.mouse_wheel_step)
.prefix("mouse wheel inc : ")
.clamp_range(-100.0..=100.0)
.max_decimals(3)
.speed(0.01),
)
.on_hover_text("The rate of change of the mouse wheel global");
ui.separator();
ui.horizontal(|ui| {
if ui.button("Load").on_hover_text("Load a new shader").clicked() {
app.ask_to_load();
}
if app.shader_loaded() {
if ui.button("Reload").on_hover_text("Reload this shader").clicked() {
app.reload_shader();
}
if ui.checkbox(&mut app.watching, "watch").on_hover_text("Watch for changes (on the filesystem) and reload the shader when necessary").changed() {
if app.watching {
app.watch();
} else {
app.unwatch();
}
}
if ui.button("Export").on_hover_text("Opens a window to export an image").clicked() {
app.gui.export_window = true;
}
}
});
if let Some(shader) = app.shader.as_ref() {
ui.colored_label(Color32::GREEN, shader.main.to_str().unwrap());
} else {
ui.colored_label(Color32::RED, "No shader");
}
if app.shader_loaded() && ui.selectable_label(app.is_paused(), "Pause").on_hover_text("Pause the current shader execution").clicked() {
if app.is_paused() {
app.resume();
} else {
app.pause();
}
}
let mut should_reset_params = false;
if let Some(metadata) = app.shader_metadata_mut() {
ui.separator();
ui.horizontal(|ui| {
ui.label("Params").on_hover_text("Params are special values you can declare in your shader and tweak in this panel");
if ui.button("Reset").on_hover_text("Reset all params to their default values").clicked() {
should_reset_params = true;
}
});
let sliders = &mut metadata.sliders;
egui::Grid::new("params grid")
.striped(true)
.show(ui, |ui| {
for slider in sliders {
draw_slider(slider, ui);
ui.end_row();
}
});
}
if should_reset_params {
app.reset_params();
}
ui.add_space(ui.available_size().y - 2.0 * ui.spacing().item_spacing.y - 30.0);
ui.vertical_centered(|ui| {
ui.hyperlink_to(
format!("{} Manual", GITHUB),
"https://github.com/Gui-Yom/nuance/blob/master/MANUAL.md",
);
ui.hyperlink_to(
format!("{} source code", GITHUB),
"https://github.com/Gui-Yom/nuance",
);
});
});
egui::CentralPanel::default()
.frame(Frame::none())
.show(&app.gui.context(), |ui| {
ui.image(
TextureId::User(0),
egui::Vec2::new(
window.physical_width as f32 / window.scale_factor
- app.gui.ui_width as f32,
window.physical_height as f32 / window.scale_factor,
),
);
});
let mut should_ask_export = false;
let format_ref = &mut app.export_data.format;
let size_x_ref = &mut app.export_data.size.x;
let size_y_ref = &mut app.export_data.size.y;
egui::Window::new("Export image")
.id(Id::new("export image window"))
.open(&mut app.gui.export_window)
.collapsible(false)
.resizable(false)
.scroll(false)
.show(&app.gui.egui_platform.context(), |ui| {
egui::ComboBox::from_label("format")
.selected_text(format_ref.extensions_str()[0])
.show_ui(ui, |ui| {
ui.selectable_value(format_ref, ImageFormat::Png, "PNG");
ui.selectable_value(format_ref, ImageFormat::Bmp, "BMP");
ui.selectable_value(format_ref, ImageFormat::Gif, "GIF");
ui.selectable_value(format_ref, ImageFormat::Jpeg, "JPEG");
});
ui.horizontal(|ui| {
ui.label("Size :");
ui.add(DragValue::new(size_x_ref).suffix("px"));
ui.label("x");
ui.add(DragValue::new(size_y_ref).suffix("px"));
});
if *size_x_ref % 64 != 0 {
ui.colored_label(Color32::RED, "× Image width must be a multiple of 64");
}
if ui.button("export").clicked() {
should_ask_export = true;
}
});
if should_ask_export {
app.ask_to_export();
}
if app.gui.profiling_window {
app.gui.profiling_window = puffin_egui::profiler_window(&app.gui.context());
}
let (_, paint_commands) = app.gui.egui_platform.end_frame(Some(&app.window));
app.settings.target_framerate = Duration::from_secs_f32(1.0 / framerate as f32);
app.gui.context().tessellate(paint_commands)
}
pub fn context(&self) -> CtxRef {
self.egui_platform.context()
}
pub fn texture(&self) -> Arc<Texture> {
self.egui_platform.context().texture()
}
}
fn draw_slider(slider: &mut Slider, ui: &mut Ui) {
match slider {
Slider::Float {
name,
min,
max,
value,
..
} => {
ui.label(name.as_str());
ui.add(
DragValue::new(value)
.clamp_range(*min..=*max)
.speed((*max - *min) / ui.available_width())
.max_decimals(3),
);
}
Slider::Vec2 { name, value, .. } => {
ui.label(name.as_str());
ui.spacing_mut().item_spacing.x = 2.0;
ui.columns(2, |columns| {
columns[0].add(DragValue::new(&mut value.x).speed(0.01).max_decimals(3));
columns[1].add(DragValue::new(&mut value.y).speed(0.01).max_decimals(3));
});
}
Slider::Vec3 { name, value, .. } => {
ui.label(name.as_str());
ui.spacing_mut().item_spacing.x = 2.0;
ui.columns(3, |columns| {
columns[0].add(DragValue::new(&mut value.x).speed(0.01).max_decimals(3));
columns[1].add(DragValue::new(&mut value.y).speed(0.01).max_decimals(3));
columns[2].add(DragValue::new(&mut value.z).speed(0.01).max_decimals(3));
});
}
Slider::Color { name, value, .. } => {
ui.label(name.as_str());
let ref_mut = unsafe { mem::transmute(value) };
ui.color_edit_button_rgb(ref_mut);
}
Slider::Bool { name, value, .. } => {
ui.label(name.as_str());
let mut val = *value != 0;
if ui.checkbox(&mut val, "").changed() {
*value = if val { 1 } else { 0 };
}
}
}
}