diff --git a/src/features/empty_session.rs b/src/features/empty_session.rs index 86a6390..07e4e16 100644 --- a/src/features/empty_session.rs +++ b/src/features/empty_session.rs @@ -1,9 +1,6 @@ -use crate::{ - gui::App, - util::{ - consts::{ENHANCED, LEGACY}, - countdown::Countdown, - }, +use crate::util::{ + consts::{ENHANCED, LEGACY}, + countdown::Countdown, }; use std::time::{Duration, Instant}; use sysinfo::System; @@ -49,22 +46,22 @@ fn get_gta_pid(sysinfo: &mut System) -> u32 { u32::MAX } -pub fn activate(app: &mut App) { - let pid = get_gta_pid(&mut app.sysinfo); +pub fn activate(handle: &mut HANDLE, sysinfo: &mut System) { + let pid = get_gta_pid(sysinfo); if pid == u32::MAX { return; } unsafe { - app.game_handle = OpenProcess(PROCESS_SUSPEND_RESUME, false, pid).unwrap(); - let _ = NtSuspendProcess(app.game_handle); + *handle = OpenProcess(PROCESS_SUSPEND_RESUME, false, pid).unwrap(); + let _ = NtSuspendProcess(*handle); } } -pub fn deactivate(app: &mut App) { +pub fn deactivate(handle: &mut HANDLE) { unsafe { - if !app.game_handle.is_invalid() { - let _ = NtResumeProcess(app.game_handle); - let _ = CloseHandle(app.game_handle); + if !handle.is_invalid() { + let _ = NtResumeProcess(*handle); + let _ = CloseHandle(*handle); } } } diff --git a/src/gui/app.rs b/src/gui/app.rs new file mode 100644 index 0000000..3f418a9 --- /dev/null +++ b/src/gui/app.rs @@ -0,0 +1,347 @@ +use crate::{ + features::{ + self, anti_afk::AntiAfk, empty_session::EmptySession, force_close::ForceClose, + launch::Launch, + }, + gui::{ + settings::{self, Settings}, + tools, + }, + util::{ + self, + consts::{APP_STORAGE_PATH, ENHANCED, GTA_WINDOW_TITLE, LEGACY}, + meta::Meta, + persistent_state::PersistentState, + }, +}; +use eframe::egui; +use std::time::{Duration, Instant}; + +pub const WINDOW_SIZE: [f32; 2] = [240.0, 240.0]; + +#[derive(Debug, Default, PartialEq, Eq)] +pub enum Stage { + #[default] + Main, + Settings, + About, +} + +#[allow(clippy::struct_excessive_bools)] +#[derive(Debug, Default)] +pub struct Flags { + pub elevated: bool, + debug: bool, + closing: bool, + current_frame: bool, +} + +#[derive(Debug)] +pub struct App { + meta: Meta, + pub settings: Settings, + stage: Stage, + pub flags: Flags, + pub sysinfo: sysinfo::System, + pub game_handle: windows::Win32::Foundation::HANDLE, + pub launch: Launch, + force_close: ForceClose, + empty_session: EmptySession, + anti_afk: AntiAfk, +} + +impl Default for App { + fn default() -> Self { + Self { + meta: Meta::default(), + settings: Settings::default(), + stage: Stage::default(), + flags: Flags::default(), + sysinfo: sysinfo::System::new_all(), + game_handle: windows::Win32::Foundation::HANDLE::default(), + launch: Launch::default(), + force_close: ForceClose::default(), + empty_session: EmptySession::default(), + anti_afk: AntiAfk::default(), + } + } +} + +impl eframe::App for App { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + catppuccin_egui::set_theme(ctx, self.settings.theme.into()); + self.run_timers(); + egui::TopBottomPanel::bottom("bottom_panel") + .exact_height(25.0) + .show(ctx, |ui| { + ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { + ui.selectable_value(&mut self.stage, Stage::Main, "Main"); + ui.selectable_value(&mut self.stage, Stage::Settings, "Settings"); + ui.selectable_value(&mut self.stage, Stage::About, "About"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let button = ui + .add_enabled(!self.flags.elevated, egui::Button::new("Elevate")) + .on_hover_text("Relaunch ourselves as administrator.") + .on_disabled_hover_text("We are already running elevated."); + if button.clicked() { + util::win::elevate(util::win::ElevationExitMethod::Gentle( + &mut self.flags.closing, + )); + } + }); + }); + }); + egui::CentralPanel::default().show(ctx, |ui| { + egui::ScrollArea::vertical() + .auto_shrink([false, true]) + .show(ui, |ui| match self.stage { + Stage::Main => { + self.show_game(ctx, ui); + self.show_session(ctx, ui); + self.show_network(ctx, ui); + } + Stage::Settings => self.show_settings(ctx, ui), + Stage::About => self.show_about(ctx, ui), + }); + }); + if tools::check_debug_keycombo_pressed(ctx) { + self.flags.debug = !self.flags.debug; + } + if tools::check_debug_viewport_close_button_pressed(ctx) { + self.flags.debug = false; + } + if self.flags.debug { + let main_rect = ctx.input(|i| { + i.viewport() + .clone() + .outer_rect + .unwrap_or(egui::Rect::EVERYTHING) + }); + let position = [main_rect.right(), main_rect.min.y]; + ctx.show_viewport_immediate( + egui::ViewportId::from_hash_of("debug_viewport"), + egui::ViewportBuilder::default() + .with_title("GTA Tools Debug") + .with_minimize_button(false) + .with_maximize_button(false) + .with_inner_size(WINDOW_SIZE) + .with_position(position) + .with_icon(tools::load_icon()), + |ctx, _class| { + if tools::check_debug_keycombo_pressed(ctx) { + self.flags.debug = !self.flags.debug; + } + egui::CentralPanel::default().show(ctx, |ui| { + egui::ScrollArea::both() + .auto_shrink([false, true]) + .show(ui, |ui| { + self.show_debug(ctx, ui); + }); + }); + }, + ); + } + ctx.request_repaint_after(Duration::from_millis(100)); + if self.flags.closing { + ctx.send_viewport_cmd(egui::ViewportCommand::Close); + } + } +} + +impl App { + fn show_game(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { + tools::header(ui, "Game"); + ui.horizontal(|ui| { + if ui.button("Launch").clicked() { + features::launch::launch(&self.launch.selected); + } + tools::build_combo_box::( + ui, + &mut self.launch.selected, + "Launch", + ); + }); + let force_close_button = ui.add_sized( + [104.0, 0.0], + egui::Button::new(&self.force_close.button_text), + ); + if force_close_button.clicked() && !self.force_close.prompting { + self.force_close.prompting(); + self.flags.current_frame = true; + } + if self.force_close.prompting + && self.force_close.interval.elapsed() <= Duration::from_secs(3) + { + if force_close_button.clicked() && !self.flags.current_frame { + features::force_close::activate(&mut self.sysinfo); + self.force_close = ForceClose::default(); + } + } else { + self.force_close = ForceClose::default(); + } + if self.flags.current_frame { + self.flags.current_frame = false; + } + } + + fn show_session(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { + tools::header(ui, "Session"); + ui.add_enabled_ui(!self.empty_session.disabled, |ui| { + ui.horizontal(|ui| { + if ui.button("Empty current session").clicked() { + self.empty_session.interval = Instant::now(); + self.empty_session.disabled = true; + features::empty_session::activate(&mut self.game_handle, &mut self.sysinfo); + } + ui.label(&self.empty_session.countdown.i_string); + }); + }); + ui.horizontal(|ui| { + ui.checkbox(&mut self.anti_afk.enabled, "Anti AFK") + .on_hover_text("You should be tabbed in\nfor this to work."); + if self.anti_afk.enabled { + ui.add_space(8.0); + ui.add_enabled_ui(false, |ui| { + ui.label(if util::win::is_window_focused(GTA_WINDOW_TITLE) { + "GTA is focused." + } else { + "GTA is not focused!" + }) + }); + } + }); + if self.anti_afk.enabled && self.anti_afk.interval.elapsed() >= features::anti_afk::INTERVAL + { + self.anti_afk.activate(); + } + } + + fn show_network(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { + tools::header(ui, "Network"); + egui::Frame::new() + .outer_margin(egui::vec2(0.0, -2.0)) + .show(ui, |ui| { + let response = ui.add_enabled_ui(self.flags.elevated, |ui| { + let label = ui.label("Game's network access"); + ui.horizontal(|ui| { + let available_width = label.rect.width(); + let spacing = ui.spacing().item_spacing.x; + let button_width = (available_width - spacing) / 2.0; + if ui + .add_sized([button_width, 18.0], egui::Button::new("Block")) + .clicked() + { + features::game_networking::block_all(&mut self.sysinfo); + } + if ui + .add_sized([button_width, 18.0], egui::Button::new("Unblock")) + .clicked() + { + features::game_networking::unblock_all(); + } + }); + }); + response.response.on_disabled_hover_text( + "This requires administrator.\nUse the Elevate button.", + ); + }); + } + + fn show_about(&self, _ctx: &egui::Context, ui: &mut egui::Ui) { + ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { + ui.horizontal(|ui| { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 0.0; + ui.label("with "); + ui.hyperlink_to("❤", "https://codeberg.org/futile/gta-tools"); + ui.label(" from "); + ui.hyperlink_to("futile", "http://futile.eu"); + }); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.label(format!( + "v{} {}", + self.meta.current_version, + if cfg!(debug_assertions) { "(dev)" } else { "" } + )); + let button = ui.add_enabled_ui(self.meta.newer_version_available, |ui| { + ui.style_mut().spacing.button_padding = egui::Vec2::new(3.0, 0.0); + ui.button("⬇") + .on_disabled_hover_text("Already up to date.") + .on_hover_text("New version available!") + }); + if button.inner.clicked() { + open::that(&self.meta.latest_release.download_url).unwrap(); + } + }); + }); + ui.add(egui::Image::new(egui::include_image!( + "../../assets/icon.png" + ))); + }); + } + + fn show_settings(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { + ui.horizontal(|ui| { + ui.label("Theme"); + tools::build_combo_box::(ui, &mut self.settings.theme, "Theme"); + }); + ui.checkbox(&mut self.settings.start_elevated, "Always start elevated"); + } + + fn show_debug(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { + if ui.button("open storage path").clicked() { + open::that_detached(APP_STORAGE_PATH.as_path()).unwrap(); + } + ui.collapsing("anti afk", |ui| { + ui.label(format!( + "timer: {}", + self.anti_afk.interval.elapsed().as_secs() + )); + ui.label(format!( + "can activate: {}", + features::anti_afk::can_activate() + )); + }); + ui.collapsing("sysinfo", |ui| { + if ui.button("refresh all").clicked() { + self.sysinfo.refresh_all(); + } + let pid = self + .sysinfo + .processes() + .iter() + .find(|(_, p)| p.name() == ENHANCED || p.name() == LEGACY) + .map_or_else( + || "no pid found!".to_string(), + |(pid, _)| pid.as_u32().to_string(), + ); + ui.label(format!("gta pid: {pid}")); + }); + ui.collapsing("app state", |ui| ui.label(format!("{self:#?}"))); + } + + fn run_timers(&mut self) { + if self.empty_session.disabled { + self.empty_session.countdown.count(); + } else { + self.empty_session.countdown.reset(); + } + if self.empty_session.interval.elapsed() >= features::empty_session::INTERVAL { + features::empty_session::deactivate(&mut self.game_handle); + self.empty_session.disabled = false; + } + } +} + +impl Drop for App { + fn drop(&mut self) { + // save any persistent state to config file + let persistent_state = PersistentState { + launcher: self.launch.selected, + settings: self.settings.clone(), + }; + persistent_state.set(); + // make sure we are not suspending game + features::empty_session::deactivate(&mut self.game_handle); + } +} diff --git a/src/gui/mod.rs b/src/gui/mod.rs index d3ee72b..c8fc509 100644 --- a/src/gui/mod.rs +++ b/src/gui/mod.rs @@ -1,453 +1,4 @@ +mod app; +pub mod run; pub mod settings; - -use crate::{ - features::{ - self, anti_afk::AntiAfk, empty_session::EmptySession, force_close::ForceClose, - launch::Launch, - }, - gui::settings::Settings, - util::{ - self, - consts::{APP_STORAGE_PATH, ENHANCED, GTA_WINDOW_TITLE, LEGACY}, - meta::Meta, - persistent_state::PersistentState, - }, -}; -use eframe::egui; -use std::{ - fs, - io::Write, - time::{Duration, Instant}, -}; - -const WINDOW_SIZE: [f32; 2] = [240.0, 240.0]; - -#[derive(Debug, Default, PartialEq, Eq)] -pub enum Stage { - #[default] - Main, - Settings, - About, -} - -#[allow(clippy::struct_excessive_bools)] -#[derive(Debug, Default)] -pub struct Flags { - elevated: bool, - debug: bool, - closing: bool, - current_frame: bool, -} - -#[derive(Debug)] -pub struct App { - meta: Meta, - settings: Settings, - stage: Stage, - flags: Flags, - pub sysinfo: sysinfo::System, - pub game_handle: windows::Win32::Foundation::HANDLE, - launch: Launch, - force_close: ForceClose, - empty_session: EmptySession, - anti_afk: AntiAfk, -} - -impl Default for App { - fn default() -> Self { - Self { - meta: Meta::default(), - settings: Settings::default(), - stage: Stage::default(), - flags: Flags::default(), - sysinfo: sysinfo::System::new_all(), - game_handle: windows::Win32::Foundation::HANDLE::default(), - launch: Launch::default(), - force_close: ForceClose::default(), - empty_session: EmptySession::default(), - anti_afk: AntiAfk::default(), - } - } -} - -impl eframe::App for App { - fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { - catppuccin_egui::set_theme(ctx, self.settings.theme.into()); - self.run_timers(); - egui::TopBottomPanel::bottom("bottom_panel") - .exact_height(25.0) - .show(ctx, |ui| { - ui.with_layout(egui::Layout::left_to_right(egui::Align::Center), |ui| { - ui.selectable_value(&mut self.stage, Stage::Main, "Main"); - ui.selectable_value(&mut self.stage, Stage::Settings, "Settings"); - ui.selectable_value(&mut self.stage, Stage::About, "About"); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - let button = ui - .add_enabled(!self.flags.elevated, egui::Button::new("Elevate")) - .on_hover_text("Relaunch ourselves as administrator.") - .on_disabled_hover_text("We are already running elevated."); - if button.clicked() { - util::win::elevate(util::win::ElevationExitMethod::Gentle( - &mut self.flags.closing, - )); - } - }); - }); - }); - egui::CentralPanel::default().show(ctx, |ui| { - egui::ScrollArea::vertical() - .auto_shrink([false, true]) - .show(ui, |ui| match self.stage { - Stage::Main => { - self.show_game(ctx, ui); - self.show_session(ctx, ui); - self.show_network(ctx, ui); - } - Stage::Settings => self.show_settings(ctx, ui), - Stage::About => self.show_about(ctx, ui), - }); - }); - if check_debug_keycombo_pressed(ctx) { - self.flags.debug = !self.flags.debug; - } - if check_debug_viewport_close_button_pressed(ctx) { - self.flags.debug = false; - } - if self.flags.debug { - let main_rect = ctx.input(|i| { - i.viewport() - .clone() - .outer_rect - .unwrap_or(egui::Rect::EVERYTHING) - }); - let position = [main_rect.right(), main_rect.min.y]; - ctx.show_viewport_immediate( - egui::ViewportId::from_hash_of("debug_viewport"), - egui::ViewportBuilder::default() - .with_title("GTA Tools Debug") - .with_minimize_button(false) - .with_maximize_button(false) - .with_inner_size(WINDOW_SIZE) - .with_position(position) - .with_icon(load_icon()), - |ctx, _class| { - if check_debug_keycombo_pressed(ctx) { - self.flags.debug = !self.flags.debug; - } - egui::CentralPanel::default().show(ctx, |ui| { - egui::ScrollArea::both() - .auto_shrink([false, true]) - .show(ui, |ui| { - self.show_debug(ctx, ui); - }); - }); - }, - ); - } - ctx.request_repaint_after(Duration::from_millis(100)); - if self.flags.closing { - ctx.send_viewport_cmd(egui::ViewportCommand::Close); - } - } -} - -impl App { - fn show_game(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { - header(ui, "Game"); - ui.horizontal(|ui| { - if ui.button("Launch").clicked() { - features::launch::launch(&self.launch.selected); - } - build_combo_box::(ui, &mut self.launch.selected, "Launch"); - }); - let force_close_button = ui.add_sized( - [104.0, 0.0], - egui::Button::new(&self.force_close.button_text), - ); - if force_close_button.clicked() && !self.force_close.prompting { - self.force_close.prompting(); - self.flags.current_frame = true; - } - if self.force_close.prompting - && self.force_close.interval.elapsed() <= Duration::from_secs(3) - { - if force_close_button.clicked() && !self.flags.current_frame { - features::force_close::activate(&mut self.sysinfo); - self.force_close = ForceClose::default(); - } - } else { - self.force_close = ForceClose::default(); - } - if self.flags.current_frame { - self.flags.current_frame = false; - } - } - - fn show_session(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { - header(ui, "Session"); - ui.add_enabled_ui(!self.empty_session.disabled, |ui| { - ui.horizontal(|ui| { - if ui.button("Empty current session").clicked() { - self.empty_session.interval = Instant::now(); - self.empty_session.disabled = true; - features::empty_session::activate(self); - } - ui.label(&self.empty_session.countdown.i_string); - }); - }); - ui.horizontal(|ui| { - ui.checkbox(&mut self.anti_afk.enabled, "Anti AFK") - .on_hover_text("You should be tabbed in\nfor this to work."); - if self.anti_afk.enabled { - ui.add_space(8.0); - ui.add_enabled_ui(false, |ui| { - ui.label(if util::win::is_window_focused(GTA_WINDOW_TITLE) { - "GTA is focused." - } else { - "GTA is not focused!" - }) - }); - } - }); - if self.anti_afk.enabled && self.anti_afk.interval.elapsed() >= features::anti_afk::INTERVAL - { - self.anti_afk.activate(); - } - } - - fn show_network(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { - header(ui, "Network"); - egui::Frame::new() - .outer_margin(egui::vec2(0.0, -2.0)) - .show(ui, |ui| { - let response = ui.add_enabled_ui(self.flags.elevated, |ui| { - let label = ui.label("Game's network access"); - ui.horizontal(|ui| { - let available_width = label.rect.width(); - let spacing = ui.spacing().item_spacing.x; - let button_width = (available_width - spacing) / 2.0; - if ui - .add_sized([button_width, 18.0], egui::Button::new("Block")) - .clicked() - { - features::game_networking::block_all(&mut self.sysinfo); - } - if ui - .add_sized([button_width, 18.0], egui::Button::new("Unblock")) - .clicked() - { - features::game_networking::unblock_all(); - } - }); - }); - response.response.on_disabled_hover_text( - "This requires administrator.\nUse the Elevate button.", - ); - }); - } - - fn show_about(&self, _ctx: &egui::Context, ui: &mut egui::Ui) { - ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { - ui.horizontal(|ui| { - ui.horizontal(|ui| { - ui.spacing_mut().item_spacing.x = 0.0; - ui.label("with "); - ui.hyperlink_to("❤", "https://codeberg.org/futile/gta-tools"); - ui.label(" from "); - ui.hyperlink_to("futile", "http://futile.eu"); - }); - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - ui.label(format!( - "v{} {}", - self.meta.current_version, - if cfg!(debug_assertions) { "(dev)" } else { "" } - )); - let button = ui.add_enabled_ui(self.meta.newer_version_available, |ui| { - ui.style_mut().spacing.button_padding = egui::Vec2::new(3.0, 0.0); - ui.button("⬇") - .on_disabled_hover_text("Already up to date.") - .on_hover_text("New version available!") - }); - if button.inner.clicked() { - open::that(&self.meta.latest_release.download_url).unwrap(); - } - }); - }); - ui.add(egui::Image::new(egui::include_image!( - "../../assets/icon.png" - ))); - }); - } - - fn show_settings(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { - ui.horizontal(|ui| { - ui.label("Theme"); - build_combo_box::(ui, &mut self.settings.theme, "Theme"); - }); - ui.checkbox(&mut self.settings.start_elevated, "Always start elevated"); - } - - fn show_debug(&mut self, _ctx: &egui::Context, ui: &mut egui::Ui) { - if ui.button("open storage path").clicked() { - open::that_detached(APP_STORAGE_PATH.as_path()).unwrap(); - } - ui.collapsing("anti afk", |ui| { - ui.label(format!( - "timer: {}", - self.anti_afk.interval.elapsed().as_secs() - )); - ui.label(format!( - "can activate: {}", - features::anti_afk::can_activate() - )); - }); - ui.collapsing("sysinfo", |ui| { - if ui.button("refresh all").clicked() { - self.sysinfo.refresh_all(); - } - let pid = self - .sysinfo - .processes() - .iter() - .find(|(_, p)| p.name() == ENHANCED || p.name() == LEGACY) - .map_or_else( - || "no pid found!".to_string(), - |(pid, _)| pid.as_u32().to_string(), - ); - ui.label(format!("gta pid: {pid}")); - }); - ui.collapsing("app state", |ui| ui.label(format!("{self:#?}"))); - } - - fn run_timers(&mut self) { - if self.empty_session.disabled { - self.empty_session.countdown.count(); - } else { - self.empty_session.countdown.reset(); - } - if self.empty_session.interval.elapsed() >= features::empty_session::INTERVAL { - features::empty_session::deactivate(self); - self.empty_session.disabled = false; - } - } -} - -impl Drop for App { - fn drop(&mut self) { - // save any persistent state to config file - let persistent_state = PersistentState { - launcher: self.launch.selected, - settings: self.settings.clone(), - }; - persistent_state.set(); - // make sure we are not suspending game - features::empty_session::deactivate(self); - } -} - -fn header(ui: &mut egui::Ui, text: &str) { - ui.horizontal(|ui| { - ui.label(egui::RichText::new(text).font(egui::FontId::new( - 12.5, - egui::FontFamily::Name("Ubuntu-Regular".into()), - ))); - ui.add(egui::Separator::default().horizontal()); - }); -} - -fn build_combo_box(ui: &mut egui::Ui, current_value: &mut E, label: impl std::hash::Hash) -where - E: strum::IntoEnumIterator + std::fmt::Display + std::cmp::PartialEq + Copy, -{ - egui::ComboBox::from_id_salt(label) - .selected_text(current_value.to_string()) - .show_ui(ui, |ui| { - E::iter().for_each(|v| { - ui.selectable_value(current_value, v, v.to_string()); - }); - }); -} - -fn check_debug_keycombo_pressed(ctx: &egui::Context) -> bool { - ctx.input(|i| i.modifiers.all() && i.key_pressed(egui::Key::D)) -} - -fn check_debug_viewport_close_button_pressed(ctx: &egui::Context) -> bool { - ctx.input(|i| { - i.raw - .viewports - .get(&egui::ViewportId::from_hash_of("debug_viewport")) - .filter(|vp| vp.close_requested()) - .is_some() - }) -} - -fn load_icon() -> egui::IconData { - let icon = include_bytes!("../../assets/icon.png"); - let image = image::load_from_memory(icon).unwrap().into_rgba8(); - let (width, height) = image.dimensions(); - egui::IconData { - rgba: image.into_raw(), - width, - height, - } -} - -fn panic_hook(panic_info: &std::panic::PanicHookInfo<'_>) { - let log_path = APP_STORAGE_PATH.join("panic.log"); - let mut file = fs::File::options() - .create(true) - .append(true) - .open(log_path) - .unwrap(); - let timestamp = chrono::Local::now().to_rfc3339(); - let backtrace = std::backtrace::Backtrace::force_capture(); - let message = format!("[{timestamp}]\n{panic_info}\n\n{backtrace}\n\n"); - file.write_all(message.as_bytes()).unwrap(); -} - -#[allow(clippy::unnecessary_wraps)] -fn app_creator( - cc: &eframe::CreationContext<'_>, -) -> Result, Box> { - std::panic::set_hook(Box::new(panic_hook)); - let mut app = Box::::default(); - if let Some(persistent_state) = PersistentState::get() { - app.launch.selected = persistent_state.launcher; - app.settings = persistent_state.settings; - } - let elevated = util::win::is_elevated(); - if app.settings.start_elevated && !elevated { - util::win::elevate(util::win::ElevationExitMethod::Forced); - } - app.flags.elevated = elevated; - egui_extras::install_image_loaders(&cc.egui_ctx); - cc.egui_ctx.style_mut(|style| { - style.spacing.item_spacing = egui::vec2(4.0, 4.0); - style.interaction.selectable_labels = false; - }); - let mut fonts = egui::FontDefinitions::default(); - fonts.font_data.insert( - "Ubuntu-Regular".to_string(), - egui::FontData::from_static(include_bytes!("../../assets/Ubuntu-Regular.ttf")).into(), - ); - fonts.families.insert( - egui::FontFamily::Name("Ubuntu-Regular".into()), - vec!["Ubuntu-Regular".to_string()], - ); - cc.egui_ctx.set_fonts(fonts); - Ok(app) -} - -pub fn run() { - let options = eframe::NativeOptions { - viewport: egui::ViewportBuilder::default() - .with_resizable(false) - .with_maximize_button(false) - .with_inner_size(WINDOW_SIZE) - .with_icon(load_icon()), - centered: true, - ..Default::default() - }; - eframe::run_native("GTA Tools", options, Box::new(app_creator)).unwrap(); -} +mod tools; diff --git a/src/gui/run.rs b/src/gui/run.rs new file mode 100644 index 0000000..7ae8201 --- /dev/null +++ b/src/gui/run.rs @@ -0,0 +1,68 @@ +use crate::{ + gui::{ + app::{App, WINDOW_SIZE}, + tools, + }, + util::{self, consts::APP_STORAGE_PATH, persistent_state::PersistentState}, +}; +use eframe::egui; +use std::{fs, io::Write}; + +fn panic_hook(panic_info: &std::panic::PanicHookInfo<'_>) { + let log_path = APP_STORAGE_PATH.join("panic.log"); + let mut file = fs::File::options() + .create(true) + .append(true) + .open(log_path) + .unwrap(); + let timestamp = chrono::Local::now().to_rfc3339(); + let backtrace = std::backtrace::Backtrace::force_capture(); + let message = format!("[{timestamp}]\n{panic_info}\n\n{backtrace}\n\n"); + file.write_all(message.as_bytes()).unwrap(); +} + +#[allow(clippy::unnecessary_wraps)] +fn app_creator( + cc: &eframe::CreationContext<'_>, +) -> Result, Box> { + std::panic::set_hook(Box::new(panic_hook)); + let mut app = Box::::default(); + if let Some(persistent_state) = PersistentState::get() { + app.launch.selected = persistent_state.launcher; + app.settings = persistent_state.settings; + } + let elevated = util::win::is_elevated(); + if app.settings.start_elevated && !elevated { + util::win::elevate(util::win::ElevationExitMethod::Forced); + } + app.flags.elevated = elevated; + egui_extras::install_image_loaders(&cc.egui_ctx); + cc.egui_ctx.style_mut(|style| { + style.spacing.item_spacing = egui::vec2(4.0, 4.0); + style.interaction.selectable_labels = false; + }); + let mut fonts = egui::FontDefinitions::default(); + fonts.font_data.insert( + "Ubuntu-Regular".to_string(), + egui::FontData::from_static(include_bytes!("../../assets/Ubuntu-Regular.ttf")).into(), + ); + fonts.families.insert( + egui::FontFamily::Name("Ubuntu-Regular".into()), + vec!["Ubuntu-Regular".to_string()], + ); + cc.egui_ctx.set_fonts(fonts); + Ok(app) +} + +pub fn run() { + let options = eframe::NativeOptions { + viewport: egui::ViewportBuilder::default() + .with_resizable(false) + .with_maximize_button(false) + .with_inner_size(WINDOW_SIZE) + .with_icon(tools::load_icon()), + centered: true, + ..Default::default() + }; + eframe::run_native("GTA Tools", options, Box::new(app_creator)).unwrap(); +} diff --git a/src/gui/tools.rs b/src/gui/tools.rs new file mode 100644 index 0000000..ca189c7 --- /dev/null +++ b/src/gui/tools.rs @@ -0,0 +1,49 @@ +use eframe::egui; + +pub fn load_icon() -> egui::IconData { + let icon = include_bytes!("../../assets/icon.png"); + let image = image::load_from_memory(icon).unwrap().into_rgba8(); + let (width, height) = image.dimensions(); + egui::IconData { + rgba: image.into_raw(), + width, + height, + } +} + +pub fn header(ui: &mut egui::Ui, text: &str) { + ui.horizontal(|ui| { + ui.label(egui::RichText::new(text).font(egui::FontId::new( + 12.5, + egui::FontFamily::Name("Ubuntu-Regular".into()), + ))); + ui.add(egui::Separator::default().horizontal()); + }); +} + +pub fn build_combo_box(ui: &mut egui::Ui, current_value: &mut E, label: impl std::hash::Hash) +where + E: strum::IntoEnumIterator + std::fmt::Display + std::cmp::PartialEq + Copy, +{ + egui::ComboBox::from_id_salt(label) + .selected_text(current_value.to_string()) + .show_ui(ui, |ui| { + E::iter().for_each(|v| { + ui.selectable_value(current_value, v, v.to_string()); + }); + }); +} + +pub fn check_debug_keycombo_pressed(ctx: &egui::Context) -> bool { + ctx.input(|i| i.modifiers.all() && i.key_pressed(egui::Key::D)) +} + +pub fn check_debug_viewport_close_button_pressed(ctx: &egui::Context) -> bool { + ctx.input(|i| { + i.raw + .viewports + .get(&egui::ViewportId::from_hash_of("debug_viewport")) + .filter(|vp| vp.close_requested()) + .is_some() + }) +} diff --git a/src/main.rs b/src/main.rs index c244338..f7e5c2c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,5 +5,5 @@ mod gui; mod util; fn main() { - gui::run(); + gui::run::run(); }