From 9088d0aa49ab4c971f270b0b2e59e4a4d52fee6d Mon Sep 17 00:00:00 2001 From: Caznix Date: Thu, 3 Apr 2025 01:37:53 -0400 Subject: [PATCH] add window icon --- engine/Cargo.toml | 1 + engine/build.rs | 2 +- engine/src/core/ecs/mod.rs | 1 + engine/src/core/panic.rs | 6 +-- engine/src/core/render/ctx.rs | 29 ++++++------ engine/src/core/render/mod.rs | 65 +++++++++++++++++++++----- engine/src/core/repl/commands.rs | 2 +- engine/src/core/repl/handler.rs | 6 ++- engine/src/core/repl/mod.rs | 3 -- engine/src/error.rs | 62 ++++++++++--------------- engine/src/main.rs | 26 ++++------- engine/src/metadata.rs | 79 ++++++++++++++++++++++---------- 12 files changed, 165 insertions(+), 117 deletions(-) diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 484b415..f0a98ea 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -35,6 +35,7 @@ serde = { version = "1.0.219", features = ["derive"] } native-dialog = "0.7.0" sysinfo = "0.34.2" raw-cpuid = "11.5.0" +image = "0.25.6" [build-dependencies] built = { version = "0.7.7", features = ["chrono"] } diff --git a/engine/build.rs b/engine/build.rs index a7315d0..206e189 100644 --- a/engine/build.rs +++ b/engine/build.rs @@ -1,3 +1,3 @@ fn main() { built::write_built_file().expect("Failed to write build information"); -} \ No newline at end of file +} diff --git a/engine/src/core/ecs/mod.rs b/engine/src/core/ecs/mod.rs index e69de29..8b13789 100644 --- a/engine/src/core/ecs/mod.rs +++ b/engine/src/core/ecs/mod.rs @@ -0,0 +1 @@ + diff --git a/engine/src/core/panic.rs b/engine/src/core/panic.rs index b76d9a9..92d8ae1 100644 --- a/engine/src/core/panic.rs +++ b/engine/src/core/panic.rs @@ -1,7 +1,7 @@ -use std::str::FromStr; -use std::{error::Error, path::PathBuf}; use std::fmt::Write as FmtWrite; use std::mem; +use std::str::FromStr; +use std::{error::Error, path::PathBuf}; use backtrace::Backtrace; use native_dialog::{MessageDialog, MessageType}; @@ -25,9 +25,9 @@ pub fn set_panic_hook() { } fn process_panic(info: &std::panic::PanicHookInfo<'_>) -> Result<(), Box> { + use std::io::Write; use colored::Colorize; - use std::io::Write; let log_dir = PathBuf::from_str("./").expect("wtf, The current directory no longer exists?"); if !log_dir.exists() { diff --git a/engine/src/core/render/ctx.rs b/engine/src/core/render/ctx.rs index ed5168b..596f49b 100644 --- a/engine/src/core/render/ctx.rs +++ b/engine/src/core/render/ctx.rs @@ -13,8 +13,9 @@ use wgpu_text::glyph_brush::ab_glyph::FontRef; use wgpu_text::glyph_brush::{HorizontalAlign, Layout, OwnedSection, OwnedText, VerticalAlign}; use wgpu_text::{BrushBuilder, TextBrush}; use winit::window::Window; -use crate::error::{ZenyxError, ZenyxErrorKind}; + use crate::error::Result; +use crate::error::{ZenyxError, ZenyxErrorKind}; const SHADER_SRC: &str = include_str!("shader.wgsl"); @@ -130,7 +131,7 @@ struct Model { bind_group: wgpu::BindGroup, index_count: u32, transform: Matrix4, - version: u32, + version: u32, } impl Model { @@ -174,7 +175,7 @@ impl Model { bind_group, index_count: indices.len() as u32, transform: Matrix4::identity(), - version: 1 + version: 1, } } @@ -190,7 +191,6 @@ impl Model { self.version += 1; } } - } pub struct Renderer<'window> { @@ -211,7 +211,7 @@ pub struct Renderer<'window> { frame_count: u32, fps: f32, font_state: FontState, - model_versions: Vec, + model_versions: Vec, } struct FontState { @@ -368,11 +368,8 @@ impl<'window> Renderer<'window> { desired_maximum_frame_latency: 3, }; surface.configure(&device, &surface_config); - let (depth_texture, depth_texture_view) = create_depth_texture( - &device, - surface_config.width, - surface_config.height, - ); + let (depth_texture, depth_texture_view) = + create_depth_texture(&device, surface_config.width, surface_config.height); let (depth_texture, depth_texture_view) = create_depth_texture(&device, surface_config.width, surface_config.height); @@ -390,8 +387,6 @@ impl<'window> Renderer<'window> { let scale = base_scale * (surface_config.width as f32 / base_width as f32).clamp(0.5, 2.0); let color = wgpu::Color::WHITE; - - let section = OwnedSection::default() .add_text(OwnedText::new("FPS: 0.00").with_scale(scale).with_color([ color.r as f32, @@ -435,7 +430,7 @@ impl<'window> Renderer<'window> { scale, color, }, - model_versions: vec![] + model_versions: vec![], }) } @@ -480,16 +475,16 @@ impl<'window> Renderer<'window> { for (i, model) in self.models.iter_mut().enumerate() { let angle = Rad(elapsed * 0.8 + i as f32 * 0.3); if i % 2 == 0 { - model.set_transform(Matrix4::from_angle_y(angle)); + model.set_transform(Matrix4::from_angle_y(angle)); } else { - model.set_transform(Matrix4::from_angle_x(angle) * Matrix4::from_angle_y(angle)); + model.set_transform(Matrix4::from_angle_x(angle) * Matrix4::from_angle_y(angle)); } } for (i, model) in self.models.iter().enumerate() { if model.version > self.model_versions[i] { model.update(&self.queue); #[cfg(debug_assertions)] - trace!("Updating model: {:#?}",model); + trace!("Updating model: {:#?}", model); self.model_versions[i] = model.version; } } @@ -609,9 +604,11 @@ impl<'window> Renderer<'window> { pub fn bg_color(&self) -> &wgpu::Color { &self.bg_color } + pub fn text_color(&self) -> &wgpu::Color { &self.font_state.color } + pub fn set_text_color(&mut self, color: wgpu::Color) { self.font_state.color = color; } diff --git a/engine/src/core/render/mod.rs b/engine/src/core/render/mod.rs index d841521..313e8dc 100644 --- a/engine/src/core/render/mod.rs +++ b/engine/src/core/render/mod.rs @@ -1,21 +1,26 @@ +use std::env; +use std::fs; +use std::io::Cursor; use std::ops::Deref; +use std::path::PathBuf; use std::sync::Arc; use ctx::{Renderer, Vertex}; -use winit::dpi::LogicalSize; -use winit::dpi::Size; -use std::env; -use std::fs; -use std::path::PathBuf; +use image::ImageDecoder; +use image::ImageFormat; use tobj::Mesh; use tobj::{LoadOptions, Model}; use tracing::{debug, error, info, trace, warn}; use wgpu::rwh::HasWindowHandle; use winit::application::ApplicationHandler; +use winit::dpi::LogicalSize; +use winit::dpi::Size; use winit::event::{KeyEvent, WindowEvent}; use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop}; use winit::monitor::MonitorHandle; +use winit::platform::windows::WindowAttributesExtWindows; use winit::window::Fullscreen; +use winit::window::Icon; use winit::window::Window; use winit::window::WindowId; @@ -90,7 +95,14 @@ f 6/11/6 5/10/6 1/1/6 2/13/6 impl App<'_> { fn create_main_window(&mut self, event_loop: &ActiveEventLoop) { - let win_attr = Window::default_attributes().with_title("Zenyx").with_min_inner_size(Size::Logical(LogicalSize::new(100.0, 100.0))); + let icon = self.load_icon_from_bytes(Self::ICON).unwrap(); + + let win_attr = Window::default_attributes() + .with_title("Zenyx") + .with_min_inner_size(Size::Logical(LogicalSize::new(100.0, 100.0))) + .with_window_icon(icon.clone()) + .with_taskbar_icon(icon); + match event_loop.create_window(win_attr) { Ok(window) => { let window = Arc::new(window); @@ -160,7 +172,7 @@ impl App<'_> { ) { if !key_event.state.is_pressed() || key_event.repeat { return; - } + } match key_event.physical_key { winit::keyboard::PhysicalKey::Code(code) => match code { winit::keyboard::KeyCode::Space => { @@ -199,6 +211,7 @@ impl App<'_> { warn!("No window context for toggling background: {:?}", window_id); } } + fn toggle_fullscreen(&mut self, window_id: WindowId) { if let Some(ctx) = self.windows.get_mut(&window_id) { let is_fullscreen = ctx.window.fullscreen().is_some(); @@ -216,13 +229,43 @@ impl App<'_> { warn!("No window found for fullscreen toggle: {:?}", window_id); } } + fn load_icon_from_bytes(&self, bytes: &[u8]) -> Result, String> { + const IMAGE_DIR: &str = env!("CARGO_MANIFEST_DIR"); + let cursor = Cursor::new(bytes); + let format = image::guess_format(bytes).map_err(|_| "Failed to guess image format")?; + let decoder = match format { + ImageFormat::Png => image::codecs::png::PngDecoder::new(cursor).map_err(|e| format!("Failed to decode PNG: {}", e))?, + _ => { + let img = image::load_from_memory(bytes).map_err(|e| format!("Failed to load image: {}", e))?.into_rgba8(); + let (width, height) = img.dimensions(); + return Icon::from_rgba(img.into_raw(), width, height) + .map(Some) + .map_err(|e| format!("Failed to create icon from bytes: {}", e)); + } + }; + let (width, height) = decoder.dimensions(); + let mut image_data = vec![0; decoder.total_bytes() as usize]; + decoder.read_image(&mut image_data).map_err(|e| format!("Failed to read image data: {}", e))?; + + Icon::from_rgba(image_data, width, height) + .map(Some) + .map_err(|e| format!("Failed to create icon from bytes: {}", e)) + } + + const ICON: &'static [u8] = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../assets/Badge.png")); fn spawn_child_window(&mut self, event_loop: &ActiveEventLoop) { if let Some(main_ctx) = self.windows.values().find(|ctx| ctx.is_main_window()) { let title = format!("Zenyx - New Window {}", self.windows.len()); - // TODO: Verify that this is safe instead of matching on it + let icon = self.load_icon_from_bytes(Self::ICON).unwrap(); + let win_attr = unsafe { - let base = Window::default_attributes().with_title(title).with_min_inner_size(Size::Logical(LogicalSize::new(100.0, 100.0))); + let base = Window::default_attributes() + .with_title(title) + .with_min_inner_size(Size::Logical(LogicalSize::new(100.0, 100.0))) + .with_window_icon(icon.clone()) + .with_taskbar_icon(icon); + match main_ctx.window_handle() { Ok(handle) => { if !cfg!(target_os = "windows") { @@ -230,11 +273,11 @@ impl App<'_> { } else { base } - }, + } Err(e) => { error!("{e}"); base - }, + } } }; match event_loop.create_window(win_attr) { diff --git a/engine/src/core/repl/commands.rs b/engine/src/core/repl/commands.rs index d34227e..9e9a3ec 100644 --- a/engine/src/core/repl/commands.rs +++ b/engine/src/core/repl/commands.rs @@ -5,7 +5,7 @@ use regex::Regex; use super::{handler::Command, input::tokenize}; use crate::core::repl::handler::COMMAND_MANAGER; -use crate::error::{ZenyxError,ZenyxErrorKind}; +use crate::error::{ZenyxError, ZenyxErrorKind}; #[derive(Default)] pub struct HelpCommand; diff --git a/engine/src/core/repl/handler.rs b/engine/src/core/repl/handler.rs index e9b70b0..45784ff 100644 --- a/engine/src/core/repl/handler.rs +++ b/engine/src/core/repl/handler.rs @@ -1,11 +1,13 @@ use std::collections::HashMap; +use std::sync::LazyLock; use colored::Colorize; use parking_lot::RwLock; -use std::sync::LazyLock; + use crate::error::{ZenyxError, ZenyxErrorKind}; -pub static COMMAND_MANAGER: LazyLock> = LazyLock::new(|| { RwLock::new(CommandManager::init()) }); +pub static COMMAND_MANAGER: LazyLock> = + LazyLock::new(|| RwLock::new(CommandManager::init())); #[macro_export] macro_rules! commands { diff --git a/engine/src/core/repl/mod.rs b/engine/src/core/repl/mod.rs index 21e1524..c850436 100644 --- a/engine/src/core/repl/mod.rs +++ b/engine/src/core/repl/mod.rs @@ -6,9 +6,6 @@ pub mod commands; pub mod handler; pub mod input; - - - pub fn setup() { commands!( HelpCommand, diff --git a/engine/src/error.rs b/engine/src/error.rs index b39bb3e..3595964 100644 --- a/engine/src/error.rs +++ b/engine/src/error.rs @@ -1,9 +1,9 @@ -use colored::Colorize; -use thiserror::Error; use std::fmt::Write; +use colored::Colorize; +use thiserror::Error; -#[derive(Debug, Error,PartialEq)] +#[derive(Debug, Error, PartialEq)] pub enum ZenyxErrorKind { #[error("Surface creation failed")] SurfaceCreation, @@ -48,6 +48,7 @@ impl ZenyxError { source: None, } } + pub fn kind(&self) -> &ZenyxErrorKind { &self.kind } @@ -87,45 +88,21 @@ impl ZenyxError { if let Some(msg) = &self.message { let line_padding = " ".repeat(padding_spaces); - writeln!( - output, - "{}>> {}\x1b[0m", - line_padding, - msg.bright_white() - ) - .unwrap(); + writeln!(output, "{}>> {}\x1b[0m", line_padding, msg.bright_white()).unwrap(); } if let Some(ctx) = &self.context { let line_padding = " ".repeat(padding_spaces); writeln!(output, "{}│\x1b[0m", line_padding.bright_white().bold()).unwrap(); - writeln!( - output, - "{}╰─ Note: {}\x1b[0m", - line_padding, - ctx - ) - .unwrap(); + writeln!(output, "{}╰─ Note: {}\x1b[0m", line_padding, ctx).unwrap(); } if let Some(source) = &self.source { let line_padding = " ".repeat(padding_spaces); - writeln!( - output, - "{}╰─ Caused by: {}\x1b[0m", - line_padding, - source - ) - .unwrap(); + writeln!(output, "{}╰─ Caused by: {}\x1b[0m", line_padding, source).unwrap(); let mut current = source.source(); let mut depth = 1; while let Some(err) = current { let indent = " ".repeat(padding_spaces * depth); - writeln!( - output, - "{}↳ {}\x1b[0m", - indent, - err - ) - .unwrap(); + writeln!(output, "{}↳ {}\x1b[0m", indent, err).unwrap(); depth += 1; current = err.source(); } @@ -136,22 +113,31 @@ impl ZenyxError { impl std::fmt::Display for ZenyxError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Error: {}{}{}{}", + write!( + f, + "Error: {}{}{}{}", self.kind, - self.message.as_ref().map_or("".to_string(), |msg| format!(" - {}", msg)), - self.context.as_ref().map_or("".to_string(), |ctx| format!(" [{}]", ctx)), - self.source.as_ref().map_or("".to_string(), |src| format!(" - caused by: {}", src)) + self.message + .as_ref() + .map_or("".to_string(), |msg| format!(" - {}", msg)), + self.context + .as_ref() + .map_or("".to_string(), |ctx| format!(" [{}]", ctx)), + self.source + .as_ref() + .map_or("".to_string(), |src| format!(" - caused by: {}", src)) ) } } impl std::error::Error for ZenyxError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - self.source.as_ref().map(|s| s.as_ref() as &(dyn std::error::Error + 'static)) + self.source + .as_ref() + .map(|s| s.as_ref() as &(dyn std::error::Error + 'static)) } } - impl From for ZenyxError { fn from(err: std::io::Error) -> Self { Self::builder(ZenyxErrorKind::Io) @@ -230,7 +216,6 @@ mod tests { assert!(error.source.is_some()); } - #[test] fn test_from_rustyline_error() { let readline_error = rustyline::error::ReadlineError::Interrupted; @@ -256,7 +241,6 @@ mod tests { error.pretty_print(); } - #[test] fn test_error_source_chain() { let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "File not found"); diff --git a/engine/src/main.rs b/engine/src/main.rs index 070d3f6..c654a72 100644 --- a/engine/src/main.rs +++ b/engine/src/main.rs @@ -1,5 +1,5 @@ use core::{panic::set_panic_hook, repl::setup, splash}; -use thiserror::Error; + use colored::Colorize; use tokio::runtime; #[allow(unused_imports)] @@ -29,31 +29,26 @@ async fn main() { init_logger(); let sysinfo = crate::metadata::SystemMetadata::current(); - set_panic_hook(); setup(); - - splash::print_splash(); if !cfg!(debug_assertions) { info!("{}", "Debug mode disabled".bright_blue()); set_panic_hook(); } else { - println!("{}",sysinfo.verbose_summary()); + println!("{}", sysinfo.verbose_summary()); } - + info!("Type 'help' for a list of commands."); let repl_thread = std::thread::spawn(|| { - let rt = match runtime::Builder::new_current_thread() - .enable_all() - .build() { - Ok(rt) => rt, - Err(e) => { - error!("A fatal error has occured: {e}"); - std::process::exit(1) - }, - }; + let rt = match runtime::Builder::new_current_thread().enable_all().build() { + Ok(rt) => rt, + Err(e) => { + error!("A fatal error has occured: {e}"); + std::process::exit(1) + } + }; rt.block_on(core::repl::input::handle_repl()) }); splash::print_splash(); @@ -71,5 +66,4 @@ async fn main() { if let Err(_) = repl_thread.join() { error!("REPL thread panicked"); } - } diff --git a/engine/src/metadata.rs b/engine/src/metadata.rs index dadbd01..1530d1d 100644 --- a/engine/src/metadata.rs +++ b/engine/src/metadata.rs @@ -1,9 +1,10 @@ +use std::collections::HashSet; use std::fmt; use std::str::FromStr; -use sysinfo::{CpuRefreshKind, RefreshKind, System}; + use raw_cpuid::CpuId; +use sysinfo::{CpuRefreshKind, RefreshKind, System}; use wgpu::DeviceType; -use std::collections::HashSet; mod build_info { include!(concat!(env!("OUT_DIR"), "/built.rs")); @@ -24,11 +25,15 @@ impl Memory { } pub const fn from_mb(mb: u64) -> Self { - Self { bytes: mb * 1024 * 1024 } + Self { + bytes: mb * 1024 * 1024, + } } pub const fn from_gb(gb: u64) -> Self { - Self { bytes: gb * 1024 * 1024 * 1024 } + Self { + bytes: gb * 1024 * 1024 * 1024, + } } pub const fn as_bytes(&self) -> u64 { @@ -185,18 +190,24 @@ pub struct CPU { impl CPU { pub fn current() -> Self { - let mut sys = System::new_with_specifics(RefreshKind::default().with_cpu(CpuRefreshKind::everything())); + let mut sys = System::new_with_specifics( + RefreshKind::default().with_cpu(CpuRefreshKind::everything()), + ); sys.refresh_cpu_all(); let cpu_opt = sys.cpus().first(); - let brand = cpu_opt.map(|cpu| cpu.brand().into()) + let brand = cpu_opt + .map(|cpu| cpu.brand().into()) .unwrap_or(CPUBrand::Other("unknown".into())); - let name = cpu_opt.map(|cpu| cpu.name().to_string()) + let name = cpu_opt + .map(|cpu| cpu.name().to_string()) .unwrap_or_else(|| "unknown".into()); - let vendor_id = cpu_opt.map(|cpu| cpu.vendor_id().to_string()) + let vendor_id = cpu_opt + .map(|cpu| cpu.vendor_id().to_string()) .unwrap_or_else(|| "unknown".into()); - let max_clock_speed = cpu_opt.map(|cpu| ClockSpeed(cpu.frequency() as u32)) + let max_clock_speed = cpu_opt + .map(|cpu| ClockSpeed(cpu.frequency() as u32)) .unwrap_or(ClockSpeed(0)); let current_clock_speed = max_clock_speed; @@ -214,20 +225,26 @@ impl CPU { let size = cache.physical_line_partitions() * cache.coherency_line_size() * cache.associativity(); - if size > 0 { l1_cache = Some(Memory::from_bytes(size.try_into().unwrap())); } - }, + if size > 0 { + l1_cache = Some(Memory::from_bytes(size.try_into().unwrap())); + } + } 2 => { let size = cache.physical_line_partitions() * cache.coherency_line_size() * cache.associativity(); - if size > 0 { l2_cache = Some(Memory::from_bytes(size.try_into().unwrap())); } - }, + if size > 0 { + l2_cache = Some(Memory::from_bytes(size.try_into().unwrap())); + } + } 3 => { let size = (cache.physical_line_partitions() as u64) * (cache.coherency_line_size() as u64) * (cache.associativity() as u64); - if size > 0 { l3_cache = Some(Memory::from_bytes(size)); } - }, + if size > 0 { + l3_cache = Some(Memory::from_bytes(size)); + } + } _ => {} } } @@ -235,7 +252,9 @@ impl CPU { Self { brand, - arch: std::env::consts::ARCH.parse().unwrap_or(CPUArch::Other("unknown".into())), + arch: std::env::consts::ARCH + .parse() + .unwrap_or(CPUArch::Other("unknown".into())), name, vendor_id, physical_cores, @@ -257,7 +276,7 @@ impl CPU { } pub fn is_arm(&self) -> bool { - matches!(self.brand,CPUBrand::Intel) + matches!(self.brand, CPUBrand::Intel) } pub fn is_high_clock(&self) -> bool { @@ -278,13 +297,23 @@ impl CPU { self.arch, self.name, self.vendor_id, - self.physical_cores.map(|c| c.to_string()).unwrap_or_else(|| "unknown".into()), - self.logical_cores.map(|c| c.to_string()).unwrap_or_else(|| "unknown".into()), + self.physical_cores + .map(|c| c.to_string()) + .unwrap_or_else(|| "unknown".into()), + self.logical_cores + .map(|c| c.to_string()) + .unwrap_or_else(|| "unknown".into()), self.max_clock_speed, self.current_clock_speed, - self.l1_cache.map(|c| c.format_human()).unwrap_or_else(|| "unknown".into()), - self.l2_cache.map(|c| c.format_human()).unwrap_or_else(|| "unknown".into()), - self.l3_cache.map(|c| c.format_human()).unwrap_or_else(|| "unknown".into()), + self.l1_cache + .map(|c| c.format_human()) + .unwrap_or_else(|| "unknown".into()), + self.l2_cache + .map(|c| c.format_human()) + .unwrap_or_else(|| "unknown".into()), + self.l3_cache + .map(|c| c.format_human()) + .unwrap_or_else(|| "unknown".into()), ) } } @@ -332,7 +361,6 @@ pub struct GPU { pub driver_version: Option, } - impl GPU { pub fn current() -> Vec { let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { @@ -363,6 +391,7 @@ impl GPU { pub fn is_dedicated(&self) -> bool { !self.is_integrated() } + pub fn is_mobile(&self) -> bool { let lower_name = self.name.to_lowercase(); lower_name.contains("adreno") @@ -490,6 +519,7 @@ impl SystemMetadata { compile_info: CompileInfo::current(), } } + pub fn main_gpu(&self) -> Option<&GPU> { self.gpus .iter() @@ -498,7 +528,6 @@ impl SystemMetadata { .or_else(|| self.gpus.first()) } - pub fn verbose_summary(&self) -> String { let main_gpu = self.main_gpu(); let main_gpu_info = main_gpu @@ -556,4 +585,4 @@ mod tests { assert!(metadata.memory.total.as_bytes() > 0); assert!(!metadata.compile_info.pkg_version.is_empty()); } -} \ No newline at end of file +}