tirbofish/dropbear · commit
2f1a9ea9fd2a3e97a2b075e41ee780da5f2925b4
knocked off a lot of issues and features and bugs:
worked on issues:
- #55: Fix directional lighting
- #57: Splitting AdoptedEntity into a Mesh type and a Material type to use as components
- #58: Asset Server
- #60 - Cannot rebuild project/enter play mode after gradle build error
- #63: Error console dock
also added an about screen
i reckon its ready to merge back to main...
Signature present but could not be verified.
Unverified
@@ -61,6 +61,7 @@ indexmap = "2.11" sha2 = "0.10" wesl = "0.2" dashmap = "6.1" +open = "5.3" [workspace.dependencies.image] version = "0.25" @@ -4,10 +4,10 @@ use dashmap::DashMap; use crate::model::{Material, MaterialComponent, Mesh, MeshComponent}; -/// A typedef for a Asset handle. -pub type Handle = u64; +/// A typedef for a Asset handle. +pub type Handle = u64; -/// A cache that holds all the assets loaded at that moment in time. +/// A cache that holds all the assets loaded at that moment in time. pub struct AssetCache { materials: DashMap<MaterialComponent, Arc<Material>>, meshes: DashMap<MeshComponent, Arc<Mesh>>, @@ -21,10 +21,14 @@ impl AssetCache { } } - /// Fetches the material based off the handle. - /// - /// If it doesn't exist, it will run the loader as a function. - pub fn get_or_load_material<F>(&self, handle: MaterialComponent, loader: F) -> anyhow::Result<Arc<Material>> + /// Fetches the material based off the handle. + /// + /// If it doesn't exist, it will run the loader as a function. + pub fn get_or_load_material<F>( + &self, + handle: MaterialComponent, + loader: F, + ) -> anyhow::Result<Arc<Material>> where F: FnOnce() -> anyhow::Result<Material>, { @@ -43,8 +47,8 @@ impl AssetCache { } } - /// Fetches the model based off the handle. - /// + /// Fetches the model based off the handle. + /// /// If it doesn't exist, it will run the loader as a function. pub fn get_or_load_mesh<F>(&self, handle: MeshComponent, loader: F) -> anyhow::Result<Arc<Mesh>> where @@ -70,4 +74,4 @@ impl AssetCache { self.meshes.clear(); log::debug!("Cleared everything in the asset cache"); } -} +} @@ -3,6 +3,7 @@ use std::sync::Arc; use glam::{DMat4, DQuat, DVec3, Mat4}; +use serde::{Deserialize, Serialize}; use wgpu::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, Buffer, BufferBindingType, ShaderStages, @@ -19,6 +20,30 @@ pub const OPENGL_TO_WGPU_MATRIX: [[f64; 4]; 4] = [ [0.0, 0.0, 0.5, 1.0], ]; +/// Shared tuning data for camera movement and projection. +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct CameraSettings { + pub speed: f64, + pub sensitivity: f64, + pub fov_y: f64, +} + +impl CameraSettings { + pub const fn new(speed: f64, sensitivity: f64, fov_y: f64) -> Self { + Self { + speed, + sensitivity, + fov_y, + } + } +} + +impl Default for CameraSettings { + fn default() -> Self { + Self::new(1.0, 0.1, 45.0) + } +} + /// The basic values of a Camera. #[derive(Default, Debug, Clone)] pub struct Camera { @@ -33,8 +58,6 @@ pub struct Camera { pub up: DVec3, /// Aspect ratio pub aspect: f64, - /// FOV of camera - pub fov_y: f64, /// Near buffer? pub znear: f64, /// Far buffer? @@ -44,6 +67,9 @@ pub struct Camera { /// Pitch (rotation) pub pitch: f64, + /// Tuning values that control movement and projection + pub settings: CameraSettings, + /// Uniform/interface for Rust and the GPU pub uniform: CameraUniform, buffer: Option<Buffer>, @@ -51,11 +77,6 @@ pub struct Camera { layout: Option<BindGroupLayout>, bind_group: Option<BindGroup>, - /// Speed of the camera - pub speed: f64, - /// Sensitivity of the mouse for the camera - pub sensitivity: f64, - /// View matrix pub view_mat: DMat4, /// Projection Matrix @@ -68,11 +89,9 @@ pub struct CameraBuilder { pub target: DVec3, pub up: DVec3, pub aspect: f64, - pub fov_y: f64, pub znear: f64, pub zfar: f64, - pub speed: f64, - pub sensitivity: f64, + pub settings: CameraSettings, } impl Camera { @@ -88,17 +107,15 @@ impl Camera { target: builder.target, up: builder.up, aspect: builder.aspect, - fov_y: builder.fov_y, znear: builder.znear, zfar: builder.zfar, uniform, buffer: None, layout: None, bind_group: None, - speed: builder.speed, yaw: 0.0, pitch: 0.0, - sensitivity: builder.sensitivity, + settings: builder.settings, label: if let Some(l) = label { l.to_string() } else { @@ -123,11 +140,9 @@ impl Camera { target: DVec3::new(0.0, 0.0, 0.0), up: DVec3::Y, aspect: (graphics.screen_size.0 / graphics.screen_size.1).into(), - fov_y: 45.0, znear: 0.1, zfar: 100.0, - speed: 1.0, - sensitivity: 0.002, + settings: CameraSettings::new(1.0, 0.002, 45.0), }, label, ) @@ -166,7 +181,7 @@ impl Camera { log::debug!(" Eye: {:?}", camera.eye); log::debug!(" Target: {:?}", camera.target); log::debug!(" Up: {:?}", camera.up); - log::debug!(" FOV Y: {}", camera.fov_y); + log::debug!(" FOV Y: {}", camera.settings.fov_y); log::debug!(" Aspect: {}", camera.aspect); log::debug!(" Z Near: {}", camera.znear); log::debug!(" Proj Mat finite: {}", camera.proj_mat.is_finite()); @@ -176,7 +191,7 @@ impl Camera { fn build_vp(&mut self) -> DMat4 { let view = DMat4::look_at_lh(self.eye, self.target, self.up); let proj = DMat4::perspective_infinite_reverse_lh( - self.fov_y.to_radians(), + self.settings.fov_y.to_radians(), self.aspect, self.znear, ); @@ -237,45 +252,45 @@ impl Camera { pub fn move_forwards(&mut self) { let forward = (self.target - self.eye).normalize(); - self.eye += forward * self.speed; - self.target += forward * self.speed; + self.eye += forward * self.settings.speed; + self.target += forward * self.settings.speed; } pub fn move_back(&mut self) { let forward = (self.target - self.eye).normalize(); - self.eye -= forward * self.speed; - self.target -= forward * self.speed; + self.eye -= forward * self.settings.speed; + self.target -= forward * self.settings.speed; } pub fn move_right(&mut self) { let forward = (self.target - self.eye).normalize(); // LH: right = up.cross(forward) let right = self.up.cross(forward).normalize(); - self.eye += right * self.speed; - self.target += right * self.speed; + self.eye += right * self.settings.speed; + self.target += right * self.settings.speed; } pub fn move_left(&mut self) { let forward = (self.target - self.eye).normalize(); let right = self.up.cross(forward).normalize(); - self.eye -= right * self.speed; - self.target -= right * self.speed; + self.eye -= right * self.settings.speed; + self.target -= right * self.settings.speed; } pub fn move_up(&mut self) { let up = self.up.normalize(); - self.eye += up * self.speed; - self.target += up * self.speed; + self.eye += up * self.settings.speed; + self.target += up * self.settings.speed; } pub fn move_down(&mut self) { let up = self.up.normalize(); - self.eye -= up * self.speed; - self.target -= up * self.speed; + self.eye -= up * self.settings.speed; + self.target -= up * self.settings.speed; } pub fn track_mouse_delta(&mut self, dx: f64, dy: f64) { - let sensitivity = self.sensitivity; + let sensitivity = self.settings.sensitivity; self.yaw -= dx * sensitivity; self.pitch -= dy * sensitivity; let max_pitch = std::f64::consts::FRAC_PI_2 - 0.01; @@ -1,9 +1,6 @@ use glam::{DMat4, DQuat, DVec3, Mat4}; use serde::{Deserialize, Serialize}; -use std::{ - path::Path, - sync::Arc, -}; +use std::{path::Path, sync::Arc}; use crate::{ graphics::{Instance, SharedGraphicsContext}, @@ -150,15 +150,25 @@ impl Default for LightComponent { } impl LightComponent { + pub fn default_direction() -> DVec3 { + let dir = DVec3::new(-0.35, -1.0, -0.25); + dir.normalize() + } + pub fn new( colour: DVec3, light_type: LightType, intensity: f32, attenuation: Option<Attenuation>, ) -> Self { + let direction = match light_type { + LightType::Directional | LightType::Spot => Self::default_direction(), + LightType::Point => DVec3::ZERO, + }; + Self { position: Default::default(), - direction: Default::default(), + direction, colour, light_type, intensity, @@ -144,9 +144,7 @@ impl PlaneBuilder { id: ModelId(hash), }); - MODEL_CACHE - .lock() - .insert(label, Arc::clone(&model)); + MODEL_CACHE.lock().insert(label, Arc::clone(&model)); let handle = LoadedModel::new(model); Ok(MeshRenderer::from_handle(handle)) @@ -1,12 +1,10 @@ -use dropbear_engine::camera::Camera; +use dropbear_engine::camera::{Camera, CameraSettings}; use glam::DVec3; use serde::{Deserialize, Serialize}; #[derive(Debug, Clone)] pub struct CameraComponent { - pub speed: f64, - pub sensitivity: f64, - pub fov_y: f64, + pub settings: CameraSettings, pub camera_type: CameraType, pub starting_camera: bool, } @@ -20,18 +18,14 @@ impl Default for CameraComponent { impl CameraComponent { pub fn new() -> Self { Self { - speed: 5.0, - sensitivity: 0.1, - fov_y: 60.0, + settings: CameraSettings::new(5.0, 0.1, 60.0), camera_type: CameraType::Normal, starting_camera: false, } } pub fn update(&mut self, camera: &mut Camera) { - camera.speed = self.speed; - camera.sensitivity = self.sensitivity; - camera.fov_y = self.fov_y; + camera.settings = self.settings; } // setting camera offset is just adding the CameraFollowTarget struct @@ -12,6 +12,9 @@ pub mod window; pub use egui; +/// The appdata directory for storing any information. +/// +/// By default, most of its items are located in [`app_dirs2::AppDataType::UserData`]. pub const APP_INFO: app_dirs2::AppInfo = app_dirs2::AppInfo { name: "Eucalyptus", author: "4tkbytes", @@ -1253,13 +1253,13 @@ pub fn Java_com_dropbear_ffi_JNINative_getCamera( JValue::Object(&target), JValue::Object(&up), JValue::Double(cam.aspect), - JValue::Double(cam.fov_y), + JValue::Double(cam.settings.fov_y), JValue::Double(cam.znear), JValue::Double(cam.zfar), JValue::Double(cam.yaw), JValue::Double(cam.pitch), - JValue::Double(cam.speed), - JValue::Double(cam.sensitivity), + JValue::Double(cam.settings.speed), + JValue::Double(cam.settings.sensitivity), ], ) { v @@ -1380,13 +1380,13 @@ pub fn Java_com_dropbear_ffi_JNINative_getAttachedCamera( JValue::Object(&target), JValue::Object(&up), JValue::Double(cam.aspect), - JValue::Double(cam.fov_y), + JValue::Double(cam.settings.fov_y), JValue::Double(cam.znear), JValue::Double(cam.zfar), JValue::Double(cam.yaw), JValue::Double(cam.pitch), - JValue::Double(cam.speed), - JValue::Double(cam.sensitivity), + JValue::Double(cam.settings.speed), + JValue::Double(cam.settings.sensitivity), ], ) { v @@ -1619,13 +1619,13 @@ pub fn Java_com_dropbear_ffi_JNINative_setCamera( cam.eye = eye.as_dvec3(); cam.target = target.as_dvec3(); cam.up = up.as_dvec3(); - cam.fov_y = fov_y; + cam.settings.fov_y = fov_y; cam.znear = znear; cam.zfar = zfar; cam.yaw = yaw; cam.pitch = pitch; - cam.speed = speed; - cam.sensitivity = sensitivity; + cam.settings.speed = speed; + cam.settings.sensitivity = sensitivity; } else { eprintln!( "[Java_com_dropbear_ffi_JNINative_setCamera] [ERROR] Entity does not have a Camera component" @@ -829,13 +829,13 @@ pub unsafe extern "C" fn dropbear_get_camera( }; (*out_camera).aspect = cam.aspect; - (*out_camera).fov_y = cam.fov_y; + (*out_camera).fov_y = cam.settings.fov_y; (*out_camera).znear = cam.znear; (*out_camera).zfar = cam.zfar; (*out_camera).yaw = cam.yaw; (*out_camera).pitch = cam.pitch; - (*out_camera).speed = cam.speed; - (*out_camera).sensitivity = cam.sensitivity; + (*out_camera).speed = cam.settings.speed; + (*out_camera).sensitivity = cam.settings.sensitivity; } return 0; @@ -897,13 +897,13 @@ pub unsafe extern "C" fn dropbear_get_attached_camera( }; (*out_camera).aspect = cam.aspect; - (*out_camera).fov_y = cam.fov_y; + (*out_camera).fov_y = cam.settings.fov_y; (*out_camera).znear = cam.znear; (*out_camera).zfar = cam.zfar; (*out_camera).yaw = cam.yaw; (*out_camera).pitch = cam.pitch; - (*out_camera).speed = cam.speed; - (*out_camera).sensitivity = cam.sensitivity; + (*out_camera).speed = cam.settings.speed; + (*out_camera).sensitivity = cam.settings.sensitivity; } 0 @@ -955,13 +955,13 @@ pub unsafe extern "C" fn dropbear_set_camera( ); cam.aspect = cam_data.aspect; - cam.fov_y = cam_data.fov_y; + cam.settings.fov_y = cam_data.fov_y; cam.znear = cam_data.znear; cam.zfar = cam_data.zfar; cam.yaw = cam_data.yaw; cam.pitch = cam_data.pitch; - cam.speed = cam_data.speed; - cam.sensitivity = cam_data.sensitivity; + cam.settings.speed = cam_data.speed; + cam.settings.sensitivity = cam_data.sensitivity; 0 } @@ -1,7 +1,7 @@ use crate::camera::{CameraComponent, CameraType}; use crate::utils::PROTO_TEXTURE; use chrono::Utc; -use dropbear_engine::camera::{Camera, CameraBuilder}; +use dropbear_engine::camera::{Camera, CameraBuilder, CameraSettings}; use dropbear_engine::entity::{MeshRenderer, Transform}; use dropbear_engine::graphics::SharedGraphicsContext; use dropbear_engine::lighting::{Light, LightComponent}; @@ -10,7 +10,7 @@ use dropbear_engine::procedural::plane::PlaneBuilder; use dropbear_engine::utils::{ResourceReference, ResourceReferenceType}; use egui::Ui; use egui_dock_fork::DockState; -use glam::DVec3; +use glam::{DQuat, DVec3}; use once_cell::sync::Lazy; use parking_lot::RwLock; use rayon::prelude::*; @@ -586,7 +586,7 @@ impl EntityNode { children.push(EntityNode::Camera { id, name: camera.label.clone(), - camera_type: component.camera_type, + camera_type: component.camera_type, }); } @@ -706,8 +706,8 @@ impl Default for CameraConfig { // follow_offset: None, label: String::new(), camera_type: CameraType::Normal, - speed: default.speed as f32, - sensitivity: default.sensitivity as f32, + speed: default.settings.speed as f32, + sensitivity: default.settings.sensitivity as f32, starting_camera: false, } } @@ -726,11 +726,11 @@ impl CameraConfig { camera_type: component.camera_type, up: camera.up.to_array(), aspect: camera.aspect, - fov: camera.fov_y as f32, + fov: camera.settings.fov_y as f32, near: camera.znear as f32, far: camera.zfar as f32, - speed: component.speed as f32, - sensitivity: component.sensitivity as f32, + speed: component.settings.speed as f32, + sensitivity: component.settings.sensitivity as f32, // follow_target_entity_label: follow_target.map(|target| target.follow_target.clone()), // follow_offset: follow_target.map(|target| target.offset.to_array()), starting_camera: component.starting_camera, @@ -1016,9 +1016,12 @@ impl SceneConfig { reference ); - let mut renderer = - MeshRenderer::from_path(graphics.clone(), &path, Some(&entity_config.label)) - .await?; + let mut renderer = MeshRenderer::from_path( + graphics.clone(), + &path, + Some(&entity_config.label), + ) + .await?; let transform = entity_config.transform; renderer.update(&transform); @@ -1030,19 +1033,23 @@ impl SceneConfig { target: DVec3::from_array(camera_config.target), up: DVec3::from_array(camera_config.up), aspect: camera_config.aspect, - fov_y: camera_config.fov as f64, znear: camera_config.near as f64, zfar: camera_config.far as f64, - speed: camera_config.speed as f64, - sensitivity: camera_config.sensitivity as f64, + settings: CameraSettings::new( + camera_config.speed as f64, + camera_config.sensitivity as f64, + camera_config.fov as f64, + ), }, Some(&camera_config.label), ); let camera_component = CameraComponent { - speed: camera_config.speed as f64, - sensitivity: camera_config.sensitivity as f64, - fov_y: camera_config.fov as f64, + settings: CameraSettings::new( + camera_config.speed as f64, + camera_config.sensitivity as f64, + camera_config.fov as f64, + ), camera_type: camera_config.camera_type, starting_camera: camera_config.starting_camera, }; @@ -1072,7 +1079,12 @@ impl SceneConfig { let script = ScriptComponent { tags: script_config.tags.clone(), }; - world.spawn((renderer, transform, script, entity_config.properties.clone())) + world.spawn(( + renderer, + transform, + script, + entity_config.properties.clone(), + )) } else { world.spawn((renderer, transform, entity_config.properties.clone())) }; @@ -1103,19 +1115,23 @@ impl SceneConfig { target: DVec3::from_array(camera_config.target), up: DVec3::from_array(camera_config.up), aspect: camera_config.aspect, - fov_y: camera_config.fov as f64, znear: camera_config.near as f64, zfar: camera_config.far as f64, - speed: camera_config.speed as f64, - sensitivity: camera_config.sensitivity as f64, + settings: CameraSettings::new( + camera_config.speed as f64, + camera_config.sensitivity as f64, + camera_config.fov as f64, + ), }, Some(&camera_config.label), ); let camera_component = CameraComponent { - speed: camera_config.speed as f64, - sensitivity: camera_config.sensitivity as f64, - fov_y: camera_config.fov as f64, + settings: CameraSettings::new( + camera_config.speed as f64, + camera_config.sensitivity as f64, + camera_config.fov as f64, + ), camera_type: camera_config.camera_type, starting_camera: camera_config.starting_camera, }; @@ -1203,19 +1219,23 @@ impl SceneConfig { target: DVec3::from_array(camera_config.target), up: DVec3::from_array(camera_config.up), aspect: camera_config.aspect, - fov_y: camera_config.fov as f64, znear: camera_config.near as f64, zfar: camera_config.far as f64, - speed: camera_config.speed as f64, - sensitivity: camera_config.sensitivity as f64, + settings: CameraSettings::new( + camera_config.speed as f64, + camera_config.sensitivity as f64, + camera_config.fov as f64, + ), }, Some(&camera_config.label), ); let camera_component = CameraComponent { - speed: camera_config.speed as f64, - sensitivity: camera_config.sensitivity as f64, - fov_y: camera_config.fov as f64, + settings: CameraSettings::new( + camera_config.speed as f64, + camera_config.sensitivity as f64, + camera_config.fov as f64, + ), camera_type: camera_config.camera_type, starting_camera: camera_config.starting_camera, }; @@ -1322,19 +1342,23 @@ impl SceneConfig { target: DVec3::from_array(camera_config.target), up: DVec3::from_array(camera_config.up), aspect: camera_config.aspect, - fov_y: camera_config.fov as f64, znear: camera_config.near as f64, zfar: camera_config.far as f64, - speed: camera_config.speed as f64, - sensitivity: camera_config.sensitivity as f64, + settings: CameraSettings::new( + camera_config.speed as f64, + camera_config.sensitivity as f64, + camera_config.fov as f64, + ), }, Some(&camera_config.label), ); let component = CameraComponent { - speed: camera_config.speed as f64, - sensitivity: camera_config.sensitivity as f64, - fov_y: camera_config.fov as f64, + settings: CameraSettings::new( + camera_config.speed as f64, + camera_config.sensitivity as f64, + camera_config.fov as f64, + ), camera_type: camera_config.camera_type, starting_camera: camera_config.starting_camera, }; @@ -1362,8 +1386,12 @@ impl SceneConfig { }); } let comp = LightComponent::directional(glam::DVec3::ONE, 1.0); + let light_direction = LightComponent::default_direction(); + let rotation = + DQuat::from_rotation_arc(DVec3::new(0.0, 0.0, -1.0), light_direction); let trans = Transform { position: glam::DVec3::new(2.0, 4.0, 2.0), + rotation, ..Default::default() }; let light = @@ -1486,6 +1514,7 @@ pub enum EditorTab { ResourceInspector, // left side, ModelEntityList, // right side, Viewport, // middle, + ErrorConsole, Plugin(usize), } @@ -37,6 +37,8 @@ tokio.workspace = true crossbeam-channel.workspace = true libloading.workspace = true indexmap.workspace = true +open.workspace = true +rustc_version_runtime.workspace = true [target.'cfg(not(target_os = "android"))'.dependencies] rfd.workspace = true @@ -103,7 +103,7 @@ impl InspectableComponent for CameraComponent { if !matches!(self.camera_type, CameraType::Player) { egui::ComboBox::from_id_salt( "i aint r kelly the way i take the piss ; \ - but im mj coz my shots don't eva miss", + but im mj, my shots don't eva miss", ) .selected_text(format!("{:?}", self.camera_type)) .show_ui(ui, |ui| { @@ -119,7 +119,7 @@ impl InspectableComponent for CameraComponent { ui.horizontal(|ui| { ui.label("Speed:"); ui.add( - egui::DragValue::new(&mut self.speed) + egui::DragValue::new(&mut self.settings.speed) .speed(0.1) .range(0.1..=20.0), ); @@ -128,7 +128,7 @@ impl InspectableComponent for CameraComponent { ui.horizontal(|ui| { ui.label("Sensitivity:"); ui.add( - egui::DragValue::new(&mut self.sensitivity) + egui::DragValue::new(&mut self.settings.sensitivity) .speed(0.0001) .range(0.0001..=1.0), ); @@ -136,7 +136,9 @@ impl InspectableComponent for CameraComponent { ui.horizontal(|ui| { ui.label("FOV:"); - ui.add(egui::Slider::new(&mut self.fov_y, 10.0..=120.0).suffix("°")); + ui.add( + egui::Slider::new(&mut self.settings.fov_y, 10.0..=120.0).suffix("°"), + ); }); }); }); @@ -2,6 +2,7 @@ use super::*; use crate::editor::ViewportMode; use std::{ collections::{HashMap, HashSet}, + path::PathBuf, sync::LazyLock, }; @@ -12,8 +13,7 @@ use dropbear_engine::{ entity::{MeshRenderer, Transform}, lighting::{Light, LightComponent}, }; -use egui; -use egui::CollapsingHeader; +use egui::{self, CollapsingHeader, Margin, RichText}; use egui_dock_fork::TabViewer; use egui_extras; use eucalyptus_core::APP_INFO; @@ -37,6 +37,7 @@ pub struct EditorTabViewer<'a> { pub editor_mode: &'a mut EditorState, pub active_camera: &'a mut Arc<Mutex<Option<Entity>>>, pub plugin_registry: &'a mut PluginRegistry, + pub build_logs: &'a mut Vec<String>, // "wah wah its unsafe, its using raw pointers" shut the fuck up if it breaks i will know pub editor: *mut Editor, } @@ -125,6 +126,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> { "Unknown Plugin Name".into() } } + EditorTab::ErrorConsole => "Error Console".into(), } } @@ -231,7 +233,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> { ); let proj_matrix = glam::DMat4::perspective_lh( - camera.fov_y.to_radians(), + camera.settings.fov_y.to_radians(), camera.aspect, camera.znear, camera.zfar, @@ -1067,6 +1069,141 @@ impl<'a> TabViewer for EditorTabViewer<'a> { ); } } + EditorTab::ErrorConsole => { + enum ErrorLevel { + Warn, + Error, + } + + struct ConsoleItem { + id: u64, + error_level: ErrorLevel, + msg: String, + file_location: Option<PathBuf>, + line_ref: Option<String>, + } + + fn analyse_error(log: &Vec<String>) -> Vec<ConsoleItem> { + fn parse_compiler_location( + line: &str, + ) -> Option<(ErrorLevel, PathBuf, String)> { + let trimmed = line.trim_start(); + let (error_level, rest) = + if let Some(r) = trimmed.strip_prefix("e: file:///") { + (ErrorLevel::Error, r) + } else if let Some(r) = trimmed.strip_prefix("w: file:///") { + (ErrorLevel::Warn, r) + } else { + return None; + }; + + let location = rest.split_whitespace().next()?; + + let mut segments = location.rsplitn(3, ':'); + let column = segments.next()?; + let row = segments.next()?; + let path = segments.next()?; + + Some((error_level, PathBuf::from(path), format!("{row}:{column}"))) + } + + let mut list: Vec<ConsoleItem> = Vec::new(); + let index = 0; + for line in log { + if line.contains("The required library") { + list.push(ConsoleItem { + error_level: ErrorLevel::Error, + msg: line.clone(), + file_location: None, + line_ref: None, + id: index + 1, + }); + } + + if let Some((error_level, path, loc)) = parse_compiler_location(line) { + list.push(ConsoleItem { + error_level, + msg: line.clone(), + file_location: Some(path), + line_ref: Some(loc), + id: index + 1, + }); + } + + // thats it for now + } + list + } + + let logs = analyse_error(&self.build_logs); + + egui::ScrollArea::vertical() + .auto_shrink([false, false]) + .show(ui, |ui| { + if logs.is_empty() { + ui.label("Build output will appear here once available."); + return; + } + + for item in &logs { + let (bg_color, text_color) = match item.error_level { + ErrorLevel::Error => ( + egui::Color32::from_rgb(60, 20, 20), + egui::Color32::from_rgb(255, 200, 200), + ), + ErrorLevel::Warn => ( + egui::Color32::from_rgb(40, 40, 10), + egui::Color32::from_rgb(255, 255, 200), + ), + }; + + let available_width = ui.available_width(); + let frame = egui::Frame::new() + .inner_margin(Margin::symmetric(8, 6)) + .fill(bg_color) + .stroke(egui::Stroke::new(1.0, text_color)); + + let response = frame + .show(ui, |ui| { + ui.set_width(available_width - 10.0); + ui.horizontal(|ui| { + ui.label(RichText::new(&item.msg).color(text_color)); + }); + }) + .response; + + if response.clicked() { + log::debug!("Log item clicked: {}", &item.id); + if let (Some(path), Some(loc)) = + (&item.file_location, &item.line_ref) + { + let location_arg = format!("{}:{}", path.display(), loc); + + match std::process::Command::new("code") + .args(["-g", &location_arg]) + .spawn() + .map(|_| ()) + { + Ok(()) => { + log::info!( + "Launched Visual Studio Code at the error: {}", + &location_arg + ); + } + Err(e) => { + warn!( + "Failed to open '{}' in VS Code: {}", + location_arg, e + ); + } + } + } + } + + ui.add_space(4.0); + } + }); + } } let mut menu_action: Option<EditorTabMenuAction> = None; @@ -1117,6 +1254,10 @@ impl<'a> TabViewer for EditorTabViewer<'a> { menu_action = Some(EditorTabMenuAction::ViewportOption); } } + EditorTab::ErrorConsole => { + ui.set_min_width(150.0); + ui.label("No actions available"); + } EditorTab::Plugin(dock_info) => { if self.editor.is_null() { panic!("Editor pointer is null, unexpected behaviour"); @@ -1207,8 +1348,7 @@ impl<'a> TabViewer for EditorTabViewer<'a> { && q.get().is_some() { log::debug!("Queried selected entity, it is a light"); - *self.signal = - Signal::AddComponent(*entity, EntityType::Light); + *self.signal = Signal::AddComponent(*entity, EntityType::Light); } } } else { @@ -273,7 +273,10 @@ impl Mouse for Editor { && let Some((camera, _)) = q.get() { if let Some((dx, dy)) = delta { - camera.track_mouse_delta(dx * camera.sensitivity, dy * camera.sensitivity); + camera.track_mouse_delta( + dx * camera.settings.sensitivity, + dy * camera.settings.sensitivity, + ); self.input_state.mouse_delta = Some((dx, dy)); } else { log_once::warn_once!("Unable to track mouse delta, attempting fallback"); @@ -281,7 +284,10 @@ impl Mouse for Editor { if let Some(old_mouse_pos) = self.input_state.last_mouse_pos { let dx = position.x - old_mouse_pos.0; let dy = position.y - old_mouse_pos.1; - camera.track_mouse_delta(dx * camera.sensitivity, dy * camera.sensitivity); + camera.track_mouse_delta( + dx * camera.settings.sensitivity, + dy * camera.settings.sensitivity, + ); self.input_state.mouse_delta = Some((dx, dy)); log_once::debug_once!("Fallback mouse tracking used"); } else { @@ -23,6 +23,7 @@ use dropbear_engine::{ }; use egui::{self, Context}; use egui_dock_fork::{DockArea, DockState, NodeIndex, Style}; +use eucalyptus_core::APP_INFO; use eucalyptus_core::{ camera::{CameraComponent, CameraType, DebugCamera}, fatal, info, @@ -131,6 +132,9 @@ pub struct Editor { current_scene_name: Option<String>, pending_scene_load: Option<PendingSceneLoad>, pending_scene_creation: Option<String>, + + // about + show_about: bool, } impl Editor { @@ -220,6 +224,7 @@ impl Editor { current_scene_name: None, pending_scene_load: None, pending_scene_creation: None, + show_about: false, }) } @@ -753,6 +758,15 @@ impl Editor { } success!("Successfully saved project"); } + if ui.button("Reveal project").clicked() { + let project_path = { + PROJECT.read().project_path.clone() + }; + match open::that(project_path) { + Ok(()) => info!("Revealed project"), + Err(e) => warn!("Unable to open project: {}", e), + } + } if ui.button("Project Settings").clicked() {}; if matches!(self.editor_state, EditorState::Playing) { if ui.button("Stop").clicked() { @@ -852,12 +866,42 @@ impl Editor { if ui_window.button("Open Viewport").clicked() { self.dock_state.push_to_focused_leaf(EditorTab::Viewport); } + if ui_window.button("Open Error Console").clicked() { + self.dock_state.push_to_focused_leaf(EditorTab::ErrorConsole); + } + if self.plugin_registry.plugins.len() == 0 { + ui_window.label( + egui::RichText::new("No plugins ") + .color(ui_window.visuals().weak_text_color()) + ); + } for (i, (_, plugin)) in self.plugin_registry.plugins.iter().enumerate() { if ui_window.button(format!("Open {}", plugin.display_name())).clicked() { self.dock_state.push_to_focused_leaf(EditorTab::Plugin(i)); } } }); + + ui.menu_button("Help", |ui| { + if ui.button("Show AppData folder").clicked() { + match app_dirs2::app_root(app_dirs2::AppDataType::UserData, &APP_INFO) { + Ok(val) => { + match open::that(&val) { + Ok(()) => info!("Opened logs folder"), + Err(e) => fatal!("Unable to open {}: {}", val.display(), e) + } + }, + Err(e) => { + fatal!("Unable to show logs: {}", e); + }, + }; + } + + if ui.button("About").clicked() { + self.show_about = true + } + }); + { let cfg = PROJECT.read(); if cfg.editor_settings.is_debug_menu_shown { @@ -889,6 +933,7 @@ impl Editor { editor_mode: &mut self.editor_state, plugin_registry: &mut self.plugin_registry, editor: editor_ptr, + build_logs: &mut self.build_logs, }, ); }); @@ -905,6 +950,47 @@ impl Editor { }, ); + egui::Window::new("About") + .resizable(false) + .collapsible(false) + .open(&mut self.show_about) + .show(&ctx, |ui| { + ui.vertical_centered(|ui| { + ui.add_space(8.0); + + ui.heading("eucalyptus editor"); + ui.label(egui::RichText::new("Built on the dropbear engine").weak()); + + ui.add_space(12.0); + + ui.label("Made with love by 4tkbytes ♥️"); + + ui.add_space(12.0); + + ui.horizontal(|ui| { + ui.label("Check out the repository at"); + if ui.label("https://github.com/4tkbytes/dropbear").clicked() { + let _ = open::that("https://github.com/4tkbytes/dropbear"); + } + }); + + ui.add_space(12.0); + + ui.label( + egui::RichText::new(format!( + "Built on commit {} with rustc {}", + env!("GIT_HASH"), + rustc_version_runtime::version_meta().short_version_string + )) + .weak() + .italics() + .small(), + ); + + ui.add_space(8.0); + }); + }); + if self.pending_scene_switch { self.scene_command = SceneCommand::SwitchScene("editor".to_string()); self.pending_scene_switch = false; @@ -1229,6 +1315,7 @@ impl Editor { Ok(()) } else { self.signal = Signal::None; + self.editor_state = EditorState::Editing; fatal!("Unable to build: No initial camera set"); Err(anyhow::anyhow!("Unable to build: No initial camera set")) } @@ -1462,7 +1549,7 @@ impl UndoableAction { world.query_one::<(&mut Camera, &mut CameraComponent)>(*entity) && let Some((cam, comp)) = q.get() { - comp.speed = *speed; + comp.settings.speed = *speed; comp.update(cam); } } @@ -1471,7 +1558,7 @@ impl UndoableAction { world.query_one::<(&mut Camera, &mut CameraComponent)>(*entity) && let Some((cam, comp)) = q.get() { - comp.sensitivity = *sensitivity; + comp.settings.sensitivity = *sensitivity; comp.update(cam); } } @@ -1480,7 +1567,7 @@ impl UndoableAction { world.query_one::<(&mut Camera, &mut CameraComponent)>(*entity) && let Some((cam, comp)) = q.get() { - comp.fov_y = *fov; + comp.settings.fov_y = *fov; comp.update(cam); } } @@ -10,7 +10,7 @@ use egui::{Align2, Image}; use eucalyptus_core::camera::{CameraComponent, CameraType}; use eucalyptus_core::scripting::{BuildStatus, build_jvm}; use eucalyptus_core::spawn::{PendingSpawn, push_pending_spawn}; -use eucalyptus_core::states::{ModelProperties, PROJECT, ScriptComponent, Value}; +use eucalyptus_core::states::{EditorTab, ModelProperties, PROJECT, ScriptComponent, Value}; use eucalyptus_core::{fatal, info, success, success_without_console, warn, warn_without_console}; use std::path::PathBuf; use std::sync::Arc; @@ -259,12 +259,18 @@ impl SignalController for Editor { self.editor_state = EditorState::Editing; } } - BuildStatus::Failed(e) => { - let error_msg = format!("Build failed: {}", e); + BuildStatus::Failed(_e) => { + let error_msg = format!("Build failed, check logs"); self.build_logs.push(error_msg.clone()); self.build_progress = 0.0; - fatal!("Failed to build gradle: {}", e); + fatal!("Failed to build gradle, check logs"); + + self.signal = Signal::None; + self.show_build_window = false; + self.editor_state = EditorState::Editing; + self.dock_state + .push_to_focused_leaf(EditorTab::ErrorConsole); } } } @@ -46,12 +46,8 @@ impl PendingSpawnController for Editor { _guard.project_path.clone() }; let resource = path.join("resources").join(file); - MeshRenderer::from_path( - graphics_clone, - resource, - Some(&asset_name), - ) - .await + MeshRenderer::from_path(graphics_clone, resource, Some(&asset_name)) + .await } ResourceReferenceType::Bytes(bytes) => { let model = Model::load_from_memory(