2022-05-15 21:55:30 +00:00
|
|
|
use crate::winit;
|
2022-05-16 05:29:34 +00:00
|
|
|
use crossbeam_channel::Sender;
|
2022-05-15 21:30:05 +00:00
|
|
|
use cyborg::camera::Flycam;
|
2022-05-16 05:29:34 +00:00
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub enum FileEvent {
|
|
|
|
Save,
|
|
|
|
SaveAs(PathBuf),
|
|
|
|
Import(ImportKind, PathBuf),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
|
|
pub enum ImportKind {
|
|
|
|
Stl,
|
|
|
|
}
|
2022-05-15 21:30:05 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub enum Workspace {
|
|
|
|
Scene,
|
|
|
|
NodeEditor,
|
|
|
|
}
|
|
|
|
|
2022-05-15 17:31:22 +00:00
|
|
|
pub struct UserInterface {
|
2022-05-16 05:29:34 +00:00
|
|
|
file_sender: Sender<FileEvent>,
|
2022-05-15 17:31:22 +00:00
|
|
|
developer_mode: bool,
|
|
|
|
show_profiler: bool,
|
|
|
|
quit: bool,
|
|
|
|
show_about: bool,
|
2022-05-18 22:15:35 +00:00
|
|
|
show_log: bool,
|
2022-05-15 17:31:22 +00:00
|
|
|
log_contents: String,
|
2022-05-18 22:15:35 +00:00
|
|
|
workspace: Workspace,
|
2022-05-15 17:31:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl UserInterface {
|
2022-05-16 05:29:34 +00:00
|
|
|
pub fn new(file_sender: Sender<FileEvent>) -> Self {
|
2022-05-15 17:31:22 +00:00
|
|
|
Self {
|
2022-05-16 05:29:34 +00:00
|
|
|
file_sender,
|
2022-05-15 17:31:22 +00:00
|
|
|
developer_mode: true,
|
|
|
|
show_profiler: false,
|
2022-05-18 22:15:35 +00:00
|
|
|
show_log: false,
|
2022-05-15 17:31:22 +00:00
|
|
|
quit: false,
|
|
|
|
show_about: false,
|
|
|
|
log_contents: "Hello logging!\n".to_string(),
|
2022-05-18 22:15:35 +00:00
|
|
|
workspace: Workspace::Scene,
|
2022-05-15 17:31:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn should_quit(&self) -> bool {
|
|
|
|
self.quit
|
|
|
|
}
|
|
|
|
|
2022-05-17 00:59:04 +00:00
|
|
|
pub fn run(
|
|
|
|
&mut self,
|
|
|
|
ctx: &egui::Context,
|
|
|
|
viewport: &mut ViewportWidget,
|
|
|
|
objects: &mut [ObjectWidget],
|
|
|
|
) {
|
2022-05-15 17:31:22 +00:00
|
|
|
egui::TopBottomPanel::top("menu_bar").show(ctx, |ui| {
|
2022-05-18 22:15:35 +00:00
|
|
|
egui::menu::bar(ui, |ui| self.ui_menu_bar(ui));
|
|
|
|
});
|
2022-05-15 17:31:22 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
if self.show_profiler {
|
|
|
|
self.show_profiler = puffin_egui::profiler_window(ctx);
|
|
|
|
}
|
2022-05-15 17:31:22 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
if self.show_about {
|
|
|
|
egui::Window::new("About")
|
|
|
|
.open(&mut self.show_about)
|
|
|
|
.resizable(false)
|
|
|
|
.collapsible(false)
|
|
|
|
.show(ctx, |ui| {
|
|
|
|
ui.vertical_centered(|ui| {
|
|
|
|
ui.heading("Cyborg Editor");
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2022-05-16 05:29:34 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
if self.show_log {
|
|
|
|
egui::TopBottomPanel::bottom("info_panel")
|
|
|
|
.resizable(true)
|
|
|
|
.show(ctx, |ui| {
|
|
|
|
ui.heading("Log Output");
|
|
|
|
|
|
|
|
egui::containers::ScrollArea::vertical()
|
|
|
|
.auto_shrink([false, false])
|
|
|
|
.max_width(f32::INFINITY)
|
|
|
|
.max_height(f32::INFINITY)
|
|
|
|
.show(ui, |ui| {
|
|
|
|
let text_edit = egui::TextEdit::multiline(&mut self.log_contents)
|
|
|
|
.desired_width(f32::INFINITY)
|
|
|
|
.frame(false);
|
|
|
|
ui.add(text_edit);
|
2022-05-16 05:29:34 +00:00
|
|
|
});
|
2022-05-18 22:15:35 +00:00
|
|
|
});
|
|
|
|
}
|
2022-05-15 17:31:22 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
match self.workspace {
|
|
|
|
Workspace::Scene => self.ui_scene(ctx, viewport, objects),
|
|
|
|
Workspace::NodeEditor => {
|
|
|
|
egui::CentralPanel::default().show(ctx, |ui| self.ui_node_editor(ui));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-05-16 05:29:34 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
pub fn ui_menu_bar(&mut self, ui: &mut egui::Ui) {
|
|
|
|
ui.menu_button("File", |ui| {
|
|
|
|
if self.developer_mode {
|
|
|
|
if ui.button("fuck").clicked() {
|
|
|
|
println!("fuck");
|
|
|
|
}
|
|
|
|
}
|
2022-05-15 17:31:22 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
if ui.button("Save").clicked() {
|
|
|
|
ui.close_menu();
|
|
|
|
self.file_sender.send(FileEvent::Save).unwrap();
|
|
|
|
}
|
2022-05-15 17:31:22 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
if ui.button("Save as...").clicked() {
|
|
|
|
ui.close_menu();
|
2022-05-15 17:31:22 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
let file_sender = self.file_sender.to_owned();
|
|
|
|
std::thread::spawn(move || {
|
|
|
|
if let Some(path) = rfd::FileDialog::new().save_file() {
|
|
|
|
file_sender.send(FileEvent::SaveAs(path)).unwrap();
|
2022-05-15 17:31:22 +00:00
|
|
|
}
|
|
|
|
});
|
2022-05-18 22:15:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ui.menu_button("Import...", |ui| {
|
|
|
|
if ui.button("STL").clicked() {
|
|
|
|
ui.close_menu();
|
|
|
|
|
|
|
|
let import_kind = ImportKind::Stl;
|
|
|
|
let file_sender = self.file_sender.to_owned();
|
|
|
|
std::thread::spawn(move || {
|
|
|
|
let dialog = rfd::FileDialog::new().add_filter("STL", &["stl"]);
|
|
|
|
if let Some(paths) = dialog.pick_files() {
|
|
|
|
for path in paths.iter() {
|
|
|
|
let event = FileEvent::Import(import_kind, path.into());
|
|
|
|
file_sender.send(event).unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
2022-05-15 17:31:22 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
if ui.button("Open...").clicked() {
|
|
|
|
println!("Opening!");
|
|
|
|
ui.close_menu();
|
|
|
|
}
|
2022-05-15 17:55:57 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
if ui.button("Quit").clicked() {
|
|
|
|
println!("Quitting!");
|
|
|
|
ui.close_menu();
|
|
|
|
self.quit = true;
|
|
|
|
}
|
|
|
|
});
|
2022-05-15 17:31:22 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
ui.menu_button("Edit", |ui| {
|
|
|
|
if ui.button("Undo").clicked() {
|
|
|
|
println!("Undoing!");
|
|
|
|
}
|
|
|
|
|
|
|
|
if ui.button("Redo").clicked() {
|
|
|
|
println!("Redoing!");
|
|
|
|
}
|
2022-05-15 17:31:22 +00:00
|
|
|
});
|
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
ui.menu_button("View", |ui| {
|
|
|
|
ui.checkbox(&mut self.show_log, "Log");
|
|
|
|
ui.checkbox(&mut self.developer_mode, "Developer mode");
|
|
|
|
|
|
|
|
if ui.checkbox(&mut self.show_profiler, "Profiler").changed() {
|
|
|
|
puffin_egui::puffin::set_scopes_on(self.show_profiler);
|
|
|
|
}
|
2022-05-15 17:31:22 +00:00
|
|
|
});
|
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
ui.menu_button("Help", |ui| {
|
|
|
|
if ui.button("About").clicked() {
|
|
|
|
self.show_about = true;
|
|
|
|
ui.close_menu();
|
|
|
|
}
|
|
|
|
});
|
2022-05-15 17:55:57 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
ui.separator();
|
|
|
|
|
|
|
|
self.ui_select_workspace(ui);
|
|
|
|
}
|
2022-05-15 17:31:22 +00:00
|
|
|
|
2022-05-18 22:15:35 +00:00
|
|
|
pub fn ui_select_workspace(&mut self, ui: &mut egui::Ui) {
|
|
|
|
ui.selectable_value(&mut self.workspace, Workspace::Scene, "Scene");
|
|
|
|
ui.selectable_value(&mut self.workspace, Workspace::NodeEditor, "Node Editor");
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn ui_scene(
|
|
|
|
&mut self,
|
|
|
|
ctx: &egui::Context,
|
|
|
|
viewport: &mut ViewportWidget,
|
|
|
|
objects: &mut [ObjectWidget],
|
|
|
|
) {
|
2022-05-16 23:46:01 +00:00
|
|
|
egui::SidePanel::left("objects_panel")
|
|
|
|
.resizable(true)
|
|
|
|
.show(ctx, |ui| {
|
2022-05-17 00:59:04 +00:00
|
|
|
for object in objects.iter_mut() {
|
|
|
|
object.ui(ui);
|
|
|
|
}
|
2022-05-16 23:46:01 +00:00
|
|
|
});
|
|
|
|
|
2022-05-15 20:25:17 +00:00
|
|
|
egui::CentralPanel::default().show(ctx, |ui| {
|
2022-05-15 21:30:05 +00:00
|
|
|
ui.add(viewport);
|
2022-05-15 20:25:17 +00:00
|
|
|
ui.heading("Viewport");
|
|
|
|
});
|
|
|
|
}
|
2022-05-18 22:15:35 +00:00
|
|
|
|
|
|
|
pub fn ui_node_editor(&mut self, ui: &mut egui::Ui) {
|
|
|
|
ui.label("Node editor goes here!");
|
|
|
|
}
|
2022-05-15 20:25:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct ViewportWidget {
|
2022-05-15 21:30:05 +00:00
|
|
|
pub texture: egui::TextureId,
|
|
|
|
pub flycam: Flycam,
|
2022-05-15 22:06:42 +00:00
|
|
|
pub width: u32,
|
|
|
|
pub height: u32,
|
2022-05-15 20:25:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ViewportWidget {
|
2022-05-15 20:49:50 +00:00
|
|
|
pub fn new(texture: egui::TextureId) -> Self {
|
2022-05-15 21:30:05 +00:00
|
|
|
Self {
|
|
|
|
texture,
|
|
|
|
flycam: Flycam::new(0.002, 10.0, 0.25),
|
2022-05-15 22:06:42 +00:00
|
|
|
width: 640,
|
|
|
|
height: 480,
|
2022-05-15 21:30:05 +00:00
|
|
|
}
|
2022-05-15 20:25:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl egui::Widget for &mut ViewportWidget {
|
|
|
|
fn ui(self, ui: &mut egui::Ui) -> egui::Response {
|
|
|
|
ui.style_mut().spacing.window_margin = egui::style::Margin::same(0.0);
|
|
|
|
let rect = ui.max_rect();
|
|
|
|
let id = egui::Id::new("viewport_widget");
|
|
|
|
let sense = egui::Sense::click_and_drag();
|
|
|
|
let response = ui.interact(rect, id, sense);
|
|
|
|
|
2022-05-15 22:06:42 +00:00
|
|
|
self.width = rect.width().round() as u32;
|
|
|
|
self.height = rect.height().round() as u32;
|
|
|
|
|
2022-05-15 21:30:05 +00:00
|
|
|
use egui::{pos2, Color32, Mesh, Rect, Shape};
|
2022-05-15 20:49:50 +00:00
|
|
|
let mut mesh = Mesh::with_texture(self.texture);
|
2022-05-15 20:25:17 +00:00
|
|
|
let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0));
|
|
|
|
let tint = Color32::WHITE;
|
|
|
|
mesh.add_rect_with_uv(rect, uv, tint);
|
|
|
|
ui.painter().add(Shape::mesh(mesh));
|
|
|
|
|
2022-05-15 21:30:05 +00:00
|
|
|
if response.dragged() {
|
|
|
|
let delta = response.drag_delta();
|
|
|
|
self.flycam.process_mouse(delta.x as f64, delta.y as f64);
|
2022-05-15 21:55:30 +00:00
|
|
|
|
|
|
|
for event in ui.input().events.iter() {
|
|
|
|
match event {
|
|
|
|
egui::Event::Key { key, pressed, .. } => {
|
|
|
|
use winit::event::{ElementState, VirtualKeyCode};
|
|
|
|
|
|
|
|
let key = match key {
|
|
|
|
egui::Key::W => Some(VirtualKeyCode::W),
|
|
|
|
egui::Key::A => Some(VirtualKeyCode::A),
|
|
|
|
egui::Key::S => Some(VirtualKeyCode::S),
|
|
|
|
egui::Key::D => Some(VirtualKeyCode::D),
|
|
|
|
// TODO remap from shift key somehow?
|
|
|
|
egui::Key::E => Some(VirtualKeyCode::E),
|
|
|
|
egui::Key::Q => Some(VirtualKeyCode::Q),
|
|
|
|
_ => None,
|
|
|
|
};
|
|
|
|
|
|
|
|
let state = if *pressed {
|
|
|
|
ElementState::Pressed
|
|
|
|
} else {
|
|
|
|
ElementState::Released
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Some(key) = key {
|
|
|
|
self.flycam.process_keyboard(key, state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if response.drag_released() {
|
|
|
|
self.flycam.defocus();
|
2022-05-15 21:30:05 +00:00
|
|
|
}
|
|
|
|
|
2022-05-15 22:06:42 +00:00
|
|
|
self.flycam.resize(self.width, self.height);
|
2022-05-15 21:30:05 +00:00
|
|
|
self.flycam.update();
|
|
|
|
|
2022-05-15 20:25:17 +00:00
|
|
|
response
|
2022-05-15 17:31:22 +00:00
|
|
|
}
|
|
|
|
}
|
2022-05-16 23:46:01 +00:00
|
|
|
|
|
|
|
pub struct ObjectWidget {
|
|
|
|
pub name: String,
|
|
|
|
pub position: [f32; 3],
|
|
|
|
pub rotation: [f32; 3],
|
|
|
|
pub scale: f32,
|
2022-05-17 00:59:04 +00:00
|
|
|
pub entity: legion::Entity,
|
2022-05-16 23:46:01 +00:00
|
|
|
pub dirty: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ObjectWidget {
|
|
|
|
pub fn ui(&mut self, ui: &mut egui::Ui) {
|
|
|
|
egui::CollapsingHeader::new(&self.name)
|
|
|
|
.default_open(true)
|
|
|
|
.show(ui, |ui| {
|
|
|
|
self.ui_self(ui);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn ui_self(&mut self, ui: &mut egui::Ui) {
|
|
|
|
egui::Grid::new("root_object")
|
|
|
|
.num_columns(4)
|
|
|
|
.striped(true)
|
|
|
|
.show(ui, |ui| {
|
|
|
|
ui.label("Position: ");
|
|
|
|
self.ui_position(ui);
|
|
|
|
ui.end_row();
|
|
|
|
|
|
|
|
ui.label("Rotation: ");
|
|
|
|
self.ui_rotation(ui);
|
|
|
|
ui.end_row();
|
|
|
|
|
|
|
|
ui.label("Scale: ");
|
|
|
|
self.ui_scale(ui);
|
|
|
|
ui.end_row();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn ui_position(&mut self, ui: &mut egui::Ui) {
|
|
|
|
let speed = 0.1 * self.scale;
|
|
|
|
for axis in self.position.iter_mut() {
|
|
|
|
let drag = egui::DragValue::new(axis).speed(speed);
|
|
|
|
if ui.add(drag).changed() {
|
|
|
|
self.dirty = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn ui_rotation(&mut self, ui: &mut egui::Ui) {
|
|
|
|
for axis in self.rotation.iter_mut() {
|
|
|
|
if ui.drag_angle(axis).changed() {
|
|
|
|
self.dirty = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn ui_scale(&mut self, ui: &mut egui::Ui) {
|
|
|
|
let scale_speed = self.scale * 0.01;
|
|
|
|
let drag = egui::DragValue::new(&mut self.scale)
|
|
|
|
.clamp_range(0.0..=f32::INFINITY)
|
|
|
|
.speed(scale_speed);
|
|
|
|
if ui.add(drag).changed() {
|
|
|
|
self.dirty = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn flush_dirty(&mut self, mut f: impl FnMut(&mut Self)) {
|
|
|
|
if self.dirty {
|
|
|
|
f(self);
|
|
|
|
self.dirty = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|