add window icon

This commit is contained in:
Chance 2025-04-03 01:37:53 -04:00 committed by BitSyndicate
parent 81e6b5cc4b
commit ed93baa404
13 changed files with 659 additions and 113 deletions

View file

@ -34,6 +34,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"] }

View file

@ -1,3 +1,3 @@
fn main() {
built::write_built_file().expect("Failed to write build information");
}
}

View file

@ -0,0 +1 @@

View file

@ -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<dyn Error>> {
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() {

View file

@ -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<f32>,
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<u32>,
model_versions: Vec<u32>,
}
struct FontState {
@ -370,6 +370,8 @@ impl<'window> Renderer<'window> {
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 font_bytes = include_bytes!("DejaVuSans.ttf");
let font = FontRef::try_from_slice(font_bytes).map_err(|e| {
@ -385,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,
@ -430,7 +430,7 @@ impl<'window> Renderer<'window> {
scale,
color,
},
model_versions: vec![]
model_versions: vec![],
})
}
@ -475,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;
}
}
@ -604,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;
}

View file

@ -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<Option<Icon>, 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) {

View file

@ -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;

View file

@ -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<RwLock<CommandManager>> = LazyLock::new(|| { RwLock::new(CommandManager::init()) });
pub static COMMAND_MANAGER: LazyLock<RwLock<CommandManager>> =
LazyLock::new(|| RwLock::new(CommandManager::init()));
#[macro_export]
macro_rules! commands {

View file

@ -6,9 +6,6 @@ pub mod commands;
pub mod handler;
pub mod input;
pub fn setup() {
commands!(
HelpCommand,

View file

@ -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<std::io::Error> 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");

View file

@ -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");
}
}

View file

@ -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<String>,
}
impl GPU {
pub fn current() -> Vec<Self> {
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());
}
}
}