use crate::WispConfig;
use crate::asset::{Wisp, WispHandle};
use crate::error::WispErrors;
use crate::inputs::{WispInputs, WispValue};
use crate::schema::{ParamField, UiHints, WispSchema};
use bevy_asset::prelude::{AssetServer, Assets};
use bevy_ecs::prelude::*;
use bevy_egui::EguiContexts;
use bevy_egui::egui;
use bevy_math::{Vec2, Vec3, Vec4};
pub(crate) fn wisp_ui(
config: Res<WispConfig>,
mut contexts: EguiContexts,
wisps: Res<Assets<Wisp>>,
errors: Res<WispErrors>,
asset_server: Res<AssetServer>,
mut cameras: Query<(&WispHandle, &mut WispInputs)>,
) {
if !config.ui_window {
return;
}
let Ok(ctx) = contexts.ctx_mut() else {
return;
};
egui::Window::new("wisp")
.default_width(300.0)
.show(ctx, |ui| {
errors_ui(ui, &errors);
for (handle, mut inputs) in cameras.iter_mut() {
let Some(wisp) = wisps.get(&**handle) else {
continue;
};
if let Some(path) = asset_server.get_path(handle.id()) {
ui.strong(path.to_string());
}
if !wisp.schema.description.is_empty() {
ui.label(&wisp.schema.description);
}
params_ui(ui, &wisp.schema, &mut inputs);
ui.separator();
}
});
}
pub fn params_ui(ui: &mut egui::Ui, schema: &WispSchema, inputs: &mut WispInputs) {
let Some(params) = &schema.params else {
return;
};
egui::Grid::new("wisp_params")
.num_columns(2)
.striped(true)
.show(ui, |ui| {
for field in ¶ms.fields {
if let Some(value) = inputs.get_mut(&field.name) {
field_row(ui, field, value);
ui.end_row();
}
}
});
}
pub fn errors_ui(ui: &mut egui::Ui, errors: &WispErrors) {
if errors.is_empty() {
return;
}
let header = egui::RichText::new("errors")
.color(egui::Color32::RED)
.strong();
egui::CollapsingHeader::new(header)
.default_open(true)
.show(ui, |ui| {
for (source, message) in errors.load.iter().chain(&errors.pipeline) {
ui.colored_label(egui::Color32::LIGHT_RED, source);
ui.monospace(message);
ui.separator();
}
});
}
fn field_row(ui: &mut egui::Ui, field: &ParamField, value: &mut WispValue) {
let label = field.ui.label.as_deref().unwrap_or(&field.name);
let hints = &field.ui;
let label_response = ui.label(label);
let response = match value {
WispValue::F32(v) => float_widget(ui, hints, v),
WispValue::Bool(v) => ui.checkbox(v, ""),
WispValue::I32(v) => int_widget(ui, &field.name, hints, v),
WispValue::U32(v) => int_widget(ui, &field.name, hints, v),
WispValue::Vec2(v) => {
let mut components = v.to_array();
let response = drag_widget(ui, hints, &mut components);
*v = Vec2::from_array(components);
response
}
WispValue::Vec3(v) if hints.color => {
let mut rgb = v.to_array();
let response = ui.color_edit_button_rgb(&mut rgb);
*v = Vec3::from_array(rgb);
response
}
WispValue::Vec3(v) => {
let mut components = v.to_array();
let response = drag_widget(ui, hints, &mut components);
*v = Vec3::from_array(components);
response
}
WispValue::Vec4(v) if hints.color => {
let mut rgba = v.to_array();
let response = ui.color_edit_button_rgba_unmultiplied(&mut rgba);
*v = Vec4::from_array(rgba);
response
}
WispValue::Vec4(v) => {
let mut components = v.to_array();
let response = drag_widget(ui, hints, &mut components);
*v = Vec4::from_array(components);
response
}
WispValue::Image(_) => ui.weak("(image input)"),
};
if !hints.description.is_empty() {
label_response.on_hover_text(&hints.description);
response.on_hover_text(&hints.description);
}
}
fn float_widget(ui: &mut egui::Ui, hints: &UiHints, value: &mut f32) -> egui::Response {
if let (Some(min), Some(max)) = (hints.min, hints.max) {
let mut slider = egui::Slider::new(value, min as f32..=max as f32);
if let Some(step) = hints.step {
slider = slider.step_by(step);
}
return ui.add(slider);
}
let mut drag = egui::DragValue::new(value).speed(hints.step.unwrap_or(0.01));
if let (Some(min), Some(max)) = (hints.min, hints.max) {
drag = drag.range(min..=max);
}
ui.add(drag)
}
fn int_widget<T: egui::emath::Numeric>(
ui: &mut egui::Ui,
id_salt: &str,
hints: &UiHints,
value: &mut T,
) -> egui::Response {
if hints.values.is_empty() {
let mut drag = egui::DragValue::new(value).speed(hints.step.unwrap_or(1.0));
if let (Some(min), Some(max)) = (hints.min, hints.max) {
drag = drag.range(min..=max);
}
return ui.add(drag);
}
let display = |index: usize, value: i64| {
hints
.labels
.get(index)
.cloned()
.unwrap_or_else(|| value.to_string())
};
let current = value.to_f64() as i64;
let selected_text = hints
.values
.iter()
.position(|v| *v == current)
.map(|index| display(index, current))
.unwrap_or_else(|| current.to_string());
egui::ComboBox::from_id_salt(id_salt)
.selected_text(selected_text)
.show_ui(ui, |ui| {
for (index, v) in hints.values.iter().enumerate() {
ui.selectable_value(value, T::from_f64(*v as f64), display(index, *v));
}
})
.response
}
fn drag_widget(ui: &mut egui::Ui, hints: &UiHints, components: &mut [f32]) -> egui::Response {
ui.horizontal(|ui| {
for component in components.iter_mut() {
ui.add(egui::DragValue::new(component).speed(hints.step.unwrap_or(0.01)));
}
})
.response
}