From 91d11c83d6a21c5266b33c1f310053b6a8a30c57 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Wed, 9 Aug 2023 21:53:04 -0400 Subject: [PATCH] Refactor state & config structs, various cleanup --- src/app.rs | 394 +++++----------------- src/diff.rs | 3 +- src/jobs/bindiff.rs | 44 --- src/jobs/mod.rs | 8 +- src/jobs/objdiff.rs | 9 +- src/views/appearance.rs | 121 +++++-- src/views/config.rs | 646 +++++++++++++++++++------------------ src/views/data_diff.rs | 70 ++-- src/views/demangle.rs | 26 +- src/views/function_diff.rs | 170 +++++----- src/views/jobs.rs | 10 +- src/views/mod.rs | 1 + src/views/symbol_diff.rs | 110 ++++--- 13 files changed, 736 insertions(+), 876 deletions(-) delete mode 100644 src/jobs/bindiff.rs diff --git a/src/app.rs b/src/app.rs index 02fc6eb..0b0d599 100644 --- a/src/app.rs +++ b/src/app.rs @@ -9,258 +9,73 @@ use std::{ time::Duration, }; -use egui::{Color32, FontFamily, FontId, TextStyle}; use globset::{Glob, GlobSet, GlobSetBuilder}; use notify::{RecursiveMode, Watcher}; -use time::{OffsetDateTime, UtcOffset}; +use time::UtcOffset; use crate::{ config::{build_globset, load_project_config, ProjectUnit, ProjectUnitNode, CONFIG_FILENAMES}, jobs::{ - check_update::{start_check_update, CheckUpdateResult}, - objdiff::{start_build, BuildStatus, ObjDiffResult}, - Job, JobQueue, JobResult, JobStatus, + check_update::start_check_update, objdiff::start_build, Job, JobQueue, JobResult, JobStatus, }, views::{ - appearance::{appearance_window, DEFAULT_COLOR_ROTATION}, - config::{config_ui, project_window}, + appearance::{appearance_window, Appearance}, + config::{config_ui, project_window, ConfigViewState}, data_diff::data_diff_ui, - demangle::demangle_window, + demangle::{demangle_window, DemangleViewState}, function_diff::function_diff_ui, jobs::jobs_ui, - symbol_diff::symbol_diff_ui, + symbol_diff::{symbol_diff_ui, DiffViewState, View}, }, }; -#[allow(clippy::enum_variant_names)] -#[derive(Default, Eq, PartialEq)] -pub enum View { - #[default] - SymbolDiff, - FunctionDiff, - DataDiff, -} - -#[derive(Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)] -pub enum DiffKind { - #[default] - SplitObj, - WholeBinary, -} - -#[derive(Default, Clone)] -pub struct DiffConfig { - // TODO - // pub stripped_symbols: Vec, - // pub mapped_symbols: HashMap, -} - -#[derive(serde::Deserialize, serde::Serialize)] -#[serde(default)] -pub struct ViewConfig { - pub ui_font: FontId, - pub code_font: FontId, - pub diff_colors: Vec, - pub reverse_fn_order: bool, - pub theme: eframe::Theme, - #[serde(skip)] - pub text_color: Color32, // GRAY - #[serde(skip)] - pub emphasized_text_color: Color32, // LIGHT_GRAY - #[serde(skip)] - pub deemphasized_text_color: Color32, // DARK_GRAY - #[serde(skip)] - pub highlight_color: Color32, // WHITE - #[serde(skip)] - pub replace_color: Color32, // LIGHT_BLUE - #[serde(skip)] - pub insert_color: Color32, // GREEN - #[serde(skip)] - pub delete_color: Color32, // RED -} - -impl Default for ViewConfig { - fn default() -> Self { - Self { - ui_font: FontId { size: 12.0, family: FontFamily::Proportional }, - code_font: FontId { size: 14.0, family: FontFamily::Monospace }, - diff_colors: DEFAULT_COLOR_ROTATION.to_vec(), - reverse_fn_order: false, - theme: eframe::Theme::Dark, - text_color: Color32::GRAY, - emphasized_text_color: Color32::LIGHT_GRAY, - deemphasized_text_color: Color32::DARK_GRAY, - highlight_color: Color32::WHITE, - replace_color: Color32::LIGHT_BLUE, - insert_color: Color32::GREEN, - delete_color: Color32::from_rgb(200, 40, 41), - } - } -} - -pub struct SymbolReference { - pub symbol_name: String, - pub section_name: String, -} - -#[derive(serde::Deserialize, serde::Serialize)] -#[serde(default)] +#[derive(Default)] pub struct ViewState { - #[serde(skip)] pub jobs: JobQueue, - #[serde(skip)] - pub build: Option>, - #[serde(skip)] - pub highlighted_symbol: Option, - #[serde(skip)] - pub selected_symbol: Option, - #[serde(skip)] - pub current_view: View, - #[serde(skip)] - pub show_view_config: bool, - #[serde(skip)] - pub show_project_config: bool, - #[serde(skip)] + pub show_appearance_config: bool, + pub demangle_state: DemangleViewState, pub show_demangle: bool, - #[serde(skip)] - pub demangle_text: String, - #[serde(skip)] - pub watch_pattern_text: String, - #[serde(skip)] - pub diff_config: DiffConfig, - #[serde(skip)] - pub search: String, - #[serde(skip)] - pub utc_offset: UtcOffset, - #[serde(skip)] - pub check_update: Option>, - // Config - pub diff_kind: DiffKind, - pub view_config: ViewConfig, + pub diff_state: DiffViewState, + pub config_state: ConfigViewState, + pub show_project_config: bool, } -impl Default for ViewState { - fn default() -> Self { - Self { - jobs: Default::default(), - build: None, - highlighted_symbol: None, - selected_symbol: None, - current_view: Default::default(), - show_view_config: false, - show_project_config: false, - show_demangle: false, - demangle_text: String::new(), - watch_pattern_text: String::new(), - diff_config: Default::default(), - search: Default::default(), - utc_offset: UtcOffset::UTC, - check_update: None, - diff_kind: Default::default(), - view_config: Default::default(), - } - } -} - -#[derive(Clone, serde::Deserialize, serde::Serialize)] -#[serde(default)] +#[derive(Default, Clone, serde::Deserialize, serde::Serialize)] pub struct AppConfig { pub custom_make: Option, - // WSL2 settings - #[serde(skip)] - pub available_wsl_distros: Option>, pub selected_wsl_distro: Option, - // Split obj pub project_dir: Option, pub target_obj_dir: Option, pub base_obj_dir: Option, pub obj_path: Option, pub build_target: bool, - // Whole binary - pub left_obj: Option, - pub right_obj: Option, - - #[serde(skip)] - pub watcher_change: bool, pub watcher_enabled: bool, - #[serde(skip)] - pub queue_update_check: bool, pub auto_update_check: bool, - // Project config - #[serde(skip)] - pub config_change: bool, - #[serde(skip)] pub watch_patterns: Vec, - #[serde(skip)] - pub load_error: Option, + #[serde(skip)] pub units: Vec, #[serde(skip)] pub unit_nodes: Vec, #[serde(skip)] - pub config_window_open: bool, + pub watcher_change: bool, + #[serde(skip)] + pub config_change: bool, } -impl Default for AppConfig { - fn default() -> Self { - Self { - custom_make: None, - available_wsl_distros: None, - selected_wsl_distro: None, - project_dir: None, - target_obj_dir: None, - base_obj_dir: None, - obj_path: None, - build_target: false, - left_obj: None, - right_obj: None, - config_change: false, - watcher_change: false, - watcher_enabled: true, - queue_update_check: false, - auto_update_check: false, - watch_patterns: vec![], - load_error: None, - units: vec![], - unit_nodes: vec![], - config_window_open: false, - } - } -} - -/// We derive Deserialize/Serialize so we can persist app state on shutdown. -#[derive(serde::Deserialize, serde::Serialize)] -#[serde(default)] +#[derive(Default)] pub struct App { + appearance: Appearance, view_state: ViewState, - #[serde(skip)] config: Arc>, - #[serde(skip)] modified: Arc, - #[serde(skip)] config_modified: Arc, - #[serde(skip)] watcher: Option, - #[serde(skip)] relaunch_path: Rc>>, - #[serde(skip)] should_relaunch: bool, } -impl Default for App { - fn default() -> Self { - Self { - view_state: ViewState::default(), - config: Arc::new(Default::default()), - modified: Arc::new(Default::default()), - config_modified: Arc::new(Default::default()), - watcher: None, - relaunch_path: Default::default(), - should_relaunch: false, - } - } -} - +const APPEARANCE_KEY: &str = "appearance"; const CONFIG_KEY: &str = "app_config"; impl App { @@ -275,25 +90,24 @@ impl App { // Load previous app state (if any). // Note that you must enable the `persistence` feature for this to work. + let mut app = Self::default(); if let Some(storage) = cc.storage { - let mut app: App = eframe::get_value(storage, eframe::APP_KEY).unwrap_or_default(); - let mut config: AppConfig = eframe::get_value(storage, CONFIG_KEY).unwrap_or_default(); - if config.project_dir.is_some() { - config.config_change = true; - config.watcher_change = true; - app.modified.store(true, Ordering::Relaxed); + if let Some(appearance) = eframe::get_value::(storage, APPEARANCE_KEY) { + app.appearance = appearance; + } + if let Some(mut config) = eframe::get_value::(storage, CONFIG_KEY) { + if config.project_dir.is_some() { + config.config_change = true; + config.watcher_change = true; + app.modified.store(true, Ordering::Relaxed); + } + app.view_state.config_state.queue_update_check = config.auto_update_check; + app.config = Arc::new(RwLock::new(config)); } - config.queue_update_check = config.auto_update_check; - app.config = Arc::new(RwLock::new(config)); - app.view_state.utc_offset = utc_offset; - app.relaunch_path = relaunch_path; - app - } else { - let mut app = Self::default(); - app.view_state.utc_offset = utc_offset; - app.relaunch_path = relaunch_path; - app } + app.appearance.utc_offset = utc_offset; + app.relaunch_path = relaunch_path; + app } } @@ -306,52 +120,24 @@ impl eframe::App for App { return; } - let Self { config, view_state, .. } = self; + let Self { config, appearance, view_state, .. } = self; + ctx.set_style(appearance.apply(ctx.style().as_ref())); - { - let config = &mut view_state.view_config; - let mut style = (*ctx.style()).clone(); - style.text_styles.insert(TextStyle::Body, FontId { - size: (config.ui_font.size * 0.75).floor(), - family: config.ui_font.family.clone(), - }); - style.text_styles.insert(TextStyle::Body, config.ui_font.clone()); - style.text_styles.insert(TextStyle::Button, config.ui_font.clone()); - style.text_styles.insert(TextStyle::Heading, FontId { - size: (config.ui_font.size * 1.5).floor(), - family: config.ui_font.family.clone(), - }); - style.text_styles.insert(TextStyle::Monospace, config.code_font.clone()); - match config.theme { - eframe::Theme::Dark => { - style.visuals = egui::Visuals::dark(); - config.text_color = Color32::GRAY; - config.emphasized_text_color = Color32::LIGHT_GRAY; - config.deemphasized_text_color = Color32::DARK_GRAY; - config.highlight_color = Color32::WHITE; - config.replace_color = Color32::LIGHT_BLUE; - config.insert_color = Color32::GREEN; - config.delete_color = Color32::from_rgb(200, 40, 41); - } - eframe::Theme::Light => { - style.visuals = egui::Visuals::light(); - config.text_color = Color32::GRAY; - config.emphasized_text_color = Color32::DARK_GRAY; - config.deemphasized_text_color = Color32::LIGHT_GRAY; - config.highlight_color = Color32::BLACK; - config.replace_color = Color32::DARK_BLUE; - config.insert_color = Color32::DARK_GREEN; - config.delete_color = Color32::from_rgb(200, 40, 41); - } - } - ctx.set_style(style); - } + let ViewState { + jobs, + show_appearance_config, + demangle_state, + show_demangle, + diff_state, + config_state, + show_project_config, + } = view_state; egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { egui::menu::bar(ui, |ui| { ui.menu_button("File", |ui| { if ui.button("Appearance…").clicked() { - view_state.show_view_config = !view_state.show_view_config; + *show_appearance_config = !*show_appearance_config; } if ui.button("Quit").clicked() { frame.close(); @@ -359,58 +145,46 @@ impl eframe::App for App { }); ui.menu_button("Tools", |ui| { if ui.button("Demangle…").clicked() { - view_state.show_demangle = !view_state.show_demangle; + *show_demangle = !*show_demangle; } }); }); }); - if view_state.current_view == View::FunctionDiff - && matches!(&view_state.build, Some(b) if b.first_status.success && b.second_status.success) + if diff_state.current_view == View::FunctionDiff + && matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success) { - // egui::SidePanel::left("side_panel").show(ctx, |ui| { - // if ui.button("Back").clicked() { - // view_state.current_view = View::SymbolDiff; - // } - // ui.separator(); - // jobs_ui(ui, view_state); - // }); - egui::CentralPanel::default().show(ctx, |ui| { - if function_diff_ui(ui, view_state) { - view_state - .jobs - .push(start_build(config.clone(), view_state.diff_config.clone())); + if function_diff_ui(ui, jobs, diff_state, appearance) { + jobs.push(start_build(config.clone())); } }); - } else if view_state.current_view == View::DataDiff - && matches!(&view_state.build, Some(b) if b.first_status.success && b.second_status.success) + } else if diff_state.current_view == View::DataDiff + && matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success) { egui::CentralPanel::default().show(ctx, |ui| { - if data_diff_ui(ui, view_state) { - view_state - .jobs - .push(start_build(config.clone(), view_state.diff_config.clone())); + if data_diff_ui(ui, jobs, diff_state, appearance) { + jobs.push(start_build(config.clone())); } }); } else { egui::SidePanel::left("side_panel").show(ctx, |ui| { - config_ui(ui, config, view_state); - jobs_ui(ui, view_state); + config_ui(ui, config, jobs, show_project_config, config_state, appearance); + jobs_ui(ui, jobs, appearance); }); egui::CentralPanel::default().show(ctx, |ui| { - symbol_diff_ui(ui, view_state); + symbol_diff_ui(ui, diff_state, appearance); }); } - project_window(ctx, config, view_state); - appearance_window(ctx, view_state); - demangle_window(ctx, view_state); + project_window(ctx, config, show_project_config, config_state, appearance); + appearance_window(ctx, show_appearance_config, appearance); + demangle_window(ctx, show_demangle, demangle_state, appearance); // Windows + request_repaint_after breaks dialogs: // https://github.com/emilk/egui/issues/2003 - if cfg!(windows) || view_state.jobs.any_running() { + if cfg!(windows) || jobs.any_running() { ctx.request_repaint(); } else { ctx.request_repaint_after(Duration::from_millis(100)); @@ -422,11 +196,13 @@ impl eframe::App for App { if let Ok(config) = self.config.read() { eframe::set_value(storage, CONFIG_KEY, &*config); } - eframe::set_value(storage, eframe::APP_KEY, self); + eframe::set_value(storage, APPEARANCE_KEY, &self.appearance); } fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &eframe::Frame) { - for (job, result) in self.view_state.jobs.iter_finished() { + let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state; + + for (job, result) in jobs.iter_finished() { match result { Ok(result) => { log::info!("Job {} finished", job.id); @@ -437,19 +213,10 @@ impl eframe::App for App { } } JobResult::ObjDiff(state) => { - self.view_state.build = Some(state); - } - JobResult::BinDiff(state) => { - self.view_state.build = Some(Box::new(ObjDiffResult { - first_status: BuildStatus { success: true, log: "".to_string() }, - second_status: BuildStatus { success: true, log: "".to_string() }, - first_obj: Some(state.first_obj), - second_obj: Some(state.second_obj), - time: OffsetDateTime::now_utc(), - })); + diff_state.build = Some(state); } JobResult::CheckUpdate(state) => { - self.view_state.check_update = Some(state); + config_state.check_update = Some(state); } JobResult::Update(state) => { if let Ok(mut guard) = self.relaunch_path.lock() { @@ -483,7 +250,7 @@ impl eframe::App for App { } } } - self.view_state.jobs.clear_finished(); + jobs.clear_finished(); if let Ok(mut config) = self.config.write() { let config = &mut *config; @@ -494,9 +261,12 @@ impl eframe::App for App { if config.config_change { config.config_change = false; - if let Err(e) = load_project_config(config) { - log::error!("Failed to load project config: {e}"); - config.load_error = Some(format!("{e}")); + match load_project_config(config) { + Ok(()) => config_state.load_error = None, + Err(e) => { + log::error!("Failed to load project config: {e}"); + config_state.load_error = Some(format!("{e}")); + } } } @@ -526,16 +296,14 @@ impl eframe::App for App { if config.obj_path.is_some() && self.modified.swap(false, Ordering::Relaxed) - && !self.view_state.jobs.is_running(Job::ObjDiff) + && !jobs.is_running(Job::ObjDiff) { - self.view_state - .jobs - .push(start_build(self.config.clone(), self.view_state.diff_config.clone())); + jobs.push(start_build(self.config.clone())); } - if config.queue_update_check { - self.view_state.jobs.push(start_check_update()); - config.queue_update_check = false; + if config_state.queue_update_check { + jobs.push(start_check_update()); + config_state.queue_update_check = false; } } } diff --git a/src/diff.rs b/src/diff.rs index 1a79a57..d373207 100644 --- a/src/diff.rs +++ b/src/diff.rs @@ -3,7 +3,6 @@ use std::{collections::BTreeMap, mem::take}; use anyhow::Result; use crate::{ - app::DiffConfig, editops::{editops_find, LevEditType}, obj::{ mips, ppc, ObjArchitecture, ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjInsArg, @@ -373,7 +372,7 @@ fn find_section_and_symbol(obj: &ObjInfo, name: &str) -> Option<(usize, usize)> None } -pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo, _diff_config: &DiffConfig) -> Result<()> { +pub fn diff_objs(left: &mut ObjInfo, right: &mut ObjInfo) -> Result<()> { for left_section in &mut left.sections { if left_section.kind == ObjSectionKind::Code { for left_symbol in &mut left_section.symbols { diff --git a/src/jobs/bindiff.rs b/src/jobs/bindiff.rs deleted file mode 100644 index b8e1f75..0000000 --- a/src/jobs/bindiff.rs +++ /dev/null @@ -1,44 +0,0 @@ -use std::sync::{mpsc::Receiver, Arc, RwLock}; - -use anyhow::{Error, Result}; - -use crate::{ - app::{AppConfig, DiffConfig}, - diff::diff_objs, - jobs::{start_job, update_status, Job, JobResult, JobState, Status}, - obj::{elf, ObjInfo}, -}; - -pub struct BinDiffResult { - pub first_obj: ObjInfo, - pub second_obj: ObjInfo, -} - -fn run_build( - status: &Status, - cancel: Receiver<()>, - config: Arc>, -) -> Result> { - let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone(); - let target_path = - config.left_obj.as_ref().ok_or_else(|| Error::msg("Missing target obj path"))?; - let base_path = config.right_obj.as_ref().ok_or_else(|| Error::msg("Missing base obj path"))?; - - update_status(status, "Loading target obj".to_string(), 0, 3, &cancel)?; - let mut left_obj = elf::read(target_path)?; - - update_status(status, "Loading base obj".to_string(), 1, 3, &cancel)?; - let mut right_obj = elf::read(base_path)?; - - update_status(status, "Performing diff".to_string(), 2, 3, &cancel)?; - diff_objs(&mut left_obj, &mut right_obj, &DiffConfig::default() /* TODO */)?; - - update_status(status, "Complete".to_string(), 3, 3, &cancel)?; - Ok(Box::new(BinDiffResult { first_obj: left_obj, second_obj: right_obj })) -} - -pub fn start_bindiff(config: Arc>) -> JobState { - start_job("Binary diff", Job::BinDiff, move |status, cancel| { - run_build(status, cancel, config).map(JobResult::BinDiff) - }) -} diff --git a/src/jobs/mod.rs b/src/jobs/mod.rs index fac3534..27d05e5 100644 --- a/src/jobs/mod.rs +++ b/src/jobs/mod.rs @@ -9,12 +9,8 @@ use std::{ use anyhow::Result; -use crate::jobs::{ - bindiff::BinDiffResult, check_update::CheckUpdateResult, objdiff::ObjDiffResult, - update::UpdateResult, -}; +use crate::jobs::{check_update::CheckUpdateResult, objdiff::ObjDiffResult, update::UpdateResult}; -pub mod bindiff; pub mod check_update; pub mod objdiff; pub mod update; @@ -22,7 +18,6 @@ pub mod update; #[derive(Debug, Eq, PartialEq, Copy, Clone)] pub enum Job { ObjDiff, - BinDiff, CheckUpdate, Update, } @@ -105,7 +100,6 @@ pub struct JobStatus { pub enum JobResult { None, ObjDiff(Box), - BinDiff(Box), CheckUpdate(Box), Update(Box), } diff --git a/src/jobs/objdiff.rs b/src/jobs/objdiff.rs index e44ffee..6546b82 100644 --- a/src/jobs/objdiff.rs +++ b/src/jobs/objdiff.rs @@ -9,7 +9,7 @@ use anyhow::{Context, Error, Result}; use time::OffsetDateTime; use crate::{ - app::{AppConfig, DiffConfig}, + app::AppConfig, diff::diff_objs, jobs::{start_job, update_status, Job, JobResult, JobState, Status}, obj::{elf, ObjInfo}, @@ -79,7 +79,6 @@ fn run_build( status: &Status, cancel: Receiver<()>, config: Arc>, - diff_config: DiffConfig, ) -> Result> { let config = config.read().map_err(|_| Error::msg("Failed to lock app config"))?.clone(); let obj_path = config.obj_path.as_ref().ok_or_else(|| Error::msg("Missing obj path"))?; @@ -129,15 +128,15 @@ fn run_build( if let (Some(first_obj), Some(second_obj)) = (&mut first_obj, &mut second_obj) { update_status(status, "Performing diff".to_string(), 4, total, &cancel)?; - diff_objs(first_obj, second_obj, &diff_config)?; + diff_objs(first_obj, second_obj)?; } update_status(status, "Complete".to_string(), total, total, &cancel)?; Ok(Box::new(ObjDiffResult { first_status, second_status, first_obj, second_obj, time })) } -pub fn start_build(config: Arc>, diff_config: DiffConfig) -> JobState { +pub fn start_build(config: Arc>) -> JobState { start_job("Object diff", Job::ObjDiff, move |status, cancel| { - run_build(status, cancel, config, diff_config).map(JobResult::ObjDiff) + run_build(status, cancel, config).map(JobResult::ObjDiff) }) } diff --git a/src/views/appearance.rs b/src/views/appearance.rs index b7f2d9e..56c7fa4 100644 --- a/src/views/appearance.rs +++ b/src/views/appearance.rs @@ -1,6 +1,95 @@ -use egui::Color32; +use egui::{Color32, FontFamily, FontId, TextStyle}; +use time::UtcOffset; -use crate::app::ViewState; +#[derive(serde::Deserialize, serde::Serialize)] +#[serde(default)] +pub struct Appearance { + pub ui_font: FontId, + pub code_font: FontId, + pub diff_colors: Vec, + pub reverse_fn_order: bool, + pub theme: eframe::Theme, + + // Applied by theme + #[serde(skip)] + pub text_color: Color32, // GRAY + #[serde(skip)] + pub emphasized_text_color: Color32, // LIGHT_GRAY + #[serde(skip)] + pub deemphasized_text_color: Color32, // DARK_GRAY + #[serde(skip)] + pub highlight_color: Color32, // WHITE + #[serde(skip)] + pub replace_color: Color32, // LIGHT_BLUE + #[serde(skip)] + pub insert_color: Color32, // GREEN + #[serde(skip)] + pub delete_color: Color32, // RED + + // Global + #[serde(skip)] + pub utc_offset: UtcOffset, +} + +impl Default for Appearance { + fn default() -> Self { + Self { + ui_font: FontId { size: 12.0, family: FontFamily::Proportional }, + code_font: FontId { size: 14.0, family: FontFamily::Monospace }, + diff_colors: DEFAULT_COLOR_ROTATION.to_vec(), + reverse_fn_order: false, + theme: eframe::Theme::Dark, + text_color: Color32::GRAY, + emphasized_text_color: Color32::LIGHT_GRAY, + deemphasized_text_color: Color32::DARK_GRAY, + highlight_color: Color32::WHITE, + replace_color: Color32::LIGHT_BLUE, + insert_color: Color32::GREEN, + delete_color: Color32::from_rgb(200, 40, 41), + utc_offset: UtcOffset::UTC, + } + } +} + +impl Appearance { + pub fn apply(&mut self, style: &egui::Style) -> egui::Style { + let mut style = style.clone(); + style.text_styles.insert(TextStyle::Body, FontId { + size: (self.ui_font.size * 0.75).floor(), + family: self.ui_font.family.clone(), + }); + style.text_styles.insert(TextStyle::Body, self.ui_font.clone()); + style.text_styles.insert(TextStyle::Button, self.ui_font.clone()); + style.text_styles.insert(TextStyle::Heading, FontId { + size: (self.ui_font.size * 1.5).floor(), + family: self.ui_font.family.clone(), + }); + style.text_styles.insert(TextStyle::Monospace, self.code_font.clone()); + match self.theme { + eframe::Theme::Dark => { + style.visuals = egui::Visuals::dark(); + self.text_color = Color32::GRAY; + self.emphasized_text_color = Color32::LIGHT_GRAY; + self.deemphasized_text_color = Color32::DARK_GRAY; + self.highlight_color = Color32::WHITE; + self.replace_color = Color32::LIGHT_BLUE; + self.insert_color = Color32::GREEN; + self.delete_color = Color32::from_rgb(200, 40, 41); + } + eframe::Theme::Light => { + style.visuals = egui::Visuals::light(); + self.text_color = Color32::GRAY; + self.emphasized_text_color = Color32::DARK_GRAY; + self.deemphasized_text_color = Color32::LIGHT_GRAY; + self.highlight_color = Color32::BLACK; + self.replace_color = Color32::DARK_BLUE; + self.insert_color = Color32::DARK_GREEN; + self.delete_color = Color32::from_rgb(200, 40, 41); + } + } + style + } +} pub const DEFAULT_COLOR_ROTATION: [Color32; 9] = [ Color32::from_rgb(255, 0, 255), @@ -14,31 +103,27 @@ pub const DEFAULT_COLOR_ROTATION: [Color32; 9] = [ Color32::from_rgb(213, 138, 138), ]; -pub fn appearance_window(ctx: &egui::Context, view_state: &mut ViewState) { - egui::Window::new("Appearance").open(&mut view_state.show_view_config).show(ctx, |ui| { +pub fn appearance_window(ctx: &egui::Context, show: &mut bool, appearance: &mut Appearance) { + egui::Window::new("Appearance").open(show).show(ctx, |ui| { egui::ComboBox::from_label("Theme") - .selected_text(format!("{:?}", view_state.view_config.theme)) + .selected_text(format!("{:?}", appearance.theme)) .show_ui(ui, |ui| { - ui.selectable_value(&mut view_state.view_config.theme, eframe::Theme::Dark, "Dark"); - ui.selectable_value( - &mut view_state.view_config.theme, - eframe::Theme::Light, - "Light", - ); + ui.selectable_value(&mut appearance.theme, eframe::Theme::Dark, "Dark"); + ui.selectable_value(&mut appearance.theme, eframe::Theme::Light, "Light"); }); ui.label("UI font:"); - egui::introspection::font_id_ui(ui, &mut view_state.view_config.ui_font); + egui::introspection::font_id_ui(ui, &mut appearance.ui_font); ui.separator(); ui.label("Code font:"); - egui::introspection::font_id_ui(ui, &mut view_state.view_config.code_font); + egui::introspection::font_id_ui(ui, &mut appearance.code_font); ui.separator(); ui.label("Diff colors:"); if ui.button("Reset").clicked() { - view_state.view_config.diff_colors = DEFAULT_COLOR_ROTATION.to_vec(); + appearance.diff_colors = DEFAULT_COLOR_ROTATION.to_vec(); } let mut remove_at: Option = None; - let num_colors = view_state.view_config.diff_colors.len(); - for (idx, color) in view_state.view_config.diff_colors.iter_mut().enumerate() { + let num_colors = appearance.diff_colors.len(); + for (idx, color) in appearance.diff_colors.iter_mut().enumerate() { ui.horizontal(|ui| { ui.color_edit_button_srgba(color); if num_colors > 1 && ui.small_button("-").clicked() { @@ -47,10 +132,10 @@ pub fn appearance_window(ctx: &egui::Context, view_state: &mut ViewState) { }); } if let Some(idx) = remove_at { - view_state.view_config.diff_colors.remove(idx); + appearance.diff_colors.remove(idx); } if ui.small_button("+").clicked() { - view_state.view_config.diff_colors.push(Color32::BLACK); + appearance.diff_colors.push(Color32::BLACK); } }); } diff --git a/src/views/config.rs b/src/views/config.rs index 9a2f483..007ea05 100644 --- a/src/views/config.rs +++ b/src/views/config.rs @@ -1,7 +1,7 @@ #[cfg(windows)] use std::string::FromUtf16Error; use std::{ - path::PathBuf, + path::{PathBuf, MAIN_SEPARATOR}, sync::{Arc, RwLock}, }; @@ -16,12 +16,23 @@ use globset::Glob; use self_update::cargo_crate_version; use crate::{ - app::{AppConfig, DiffKind, ViewConfig, ViewState}, + app::AppConfig, config::{ProjectUnit, ProjectUnitNode}, - jobs::{bindiff::start_bindiff, objdiff::start_build, update::start_update}, + jobs::{check_update::CheckUpdateResult, objdiff::start_build, update::start_update, JobQueue}, update::RELEASE_URL, + views::appearance::Appearance, }; +#[derive(Default)] +pub struct ConfigViewState { + pub check_update: Option>, + pub watch_pattern_text: String, + pub queue_update_check: bool, + pub load_error: Option, + #[cfg(windows)] + pub available_wsl_distros: Option>, +} + const DEFAULT_WATCH_PATTERNS: &[&str] = &[ "*.c", "*.cp", "*.cpp", "*.cxx", "*.h", "*.hp", "*.hpp", "*.hxx", "*.s", "*.S", "*.asm", "*.inc", "*.py", "*.yml", "*.txt", "*.json", @@ -60,17 +71,20 @@ fn fetch_wsl2_distros() -> Vec { .unwrap_or_default() } -pub fn config_ui(ui: &mut egui::Ui, config: &Arc>, view_state: &mut ViewState) { +pub fn config_ui( + ui: &mut egui::Ui, + config: &Arc>, + jobs: &mut JobQueue, + show_config_window: &mut bool, + state: &mut ConfigViewState, + appearance: &Appearance, +) { let mut config_guard = config.write().unwrap(); let AppConfig { - available_wsl_distros, selected_wsl_distro, target_obj_dir, base_obj_dir, obj_path, - left_obj, - right_obj, - queue_update_check, auto_update_check, units, unit_nodes, @@ -80,7 +94,7 @@ pub fn config_ui(ui: &mut egui::Ui, config: &Arc>, view_state: ui.heading("Updates"); ui.checkbox(auto_update_check, "Check for updates on startup"); if ui.button("Check now").clicked() { - *queue_update_check = true; + state.queue_update_check = true; } ui.label(format!("Current version: {}", cargo_crate_version!())).on_hover_ui_at_pointer(|ui| { ui.label(formatcp!("Git branch: {}", env!("VERGEN_GIT_BRANCH"))); @@ -88,10 +102,10 @@ pub fn config_ui(ui: &mut egui::Ui, config: &Arc>, view_state: ui.label(formatcp!("Build target: {}", env!("VERGEN_CARGO_TARGET_TRIPLE"))); ui.label(formatcp!("Debug: {}", env!("VERGEN_CARGO_DEBUG"))); }); - if let Some(state) = &view_state.check_update { + if let Some(state) = &state.check_update { ui.label(format!("Latest version: {}", state.latest_release.version)); if state.update_available { - ui.colored_label(view_state.view_config.insert_color, "Update available"); + ui.colored_label(appearance.insert_color, "Update available"); ui.horizontal(|ui| { if state.found_binary && ui @@ -101,7 +115,7 @@ pub fn config_ui(ui: &mut egui::Ui, config: &Arc>, view_state: ) .clicked() { - view_state.jobs.push(start_update()); + jobs.push(start_update()); } if ui .button("Manual") @@ -121,14 +135,14 @@ pub fn config_ui(ui: &mut egui::Ui, config: &Arc>, view_state: #[cfg(windows)] { ui.heading("Build"); - if available_wsl_distros.is_none() { - *available_wsl_distros = Some(fetch_wsl2_distros()); + if state.available_wsl_distros.is_none() { + state.available_wsl_distros = Some(fetch_wsl2_distros()); } egui::ComboBox::from_label("Run in WSL2") .selected_text(selected_wsl_distro.as_ref().unwrap_or(&"None".to_string())) .show_ui(ui, |ui| { ui.selectable_value(selected_wsl_distro, None, "None"); - for distro in available_wsl_distros.as_ref().unwrap() { + for distro in state.available_wsl_distros.as_ref().unwrap() { ui.selectable_value(selected_wsl_distro, Some(distro.clone()), distro); } }); @@ -136,94 +150,63 @@ pub fn config_ui(ui: &mut egui::Ui, config: &Arc>, view_state: } #[cfg(not(windows))] { - let _ = available_wsl_distros; let _ = selected_wsl_distro; } ui.horizontal(|ui| { ui.heading("Project"); if ui.button(RichText::new("Settings")).clicked() { - view_state.show_project_config = true; + *show_config_window = true; } }); - if view_state.diff_kind == DiffKind::SplitObj { - if let (Some(base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) { - let mut new_build_obj = obj_path.clone(); - if units.is_empty() { - if ui.button("Select obj").clicked() { - if let Some(path) = rfd::FileDialog::new() - .set_directory(&target_dir) - .add_filter("Object file", &["o", "elf"]) - .pick_file() - { - if let Ok(obj_path) = path.strip_prefix(&base_dir) { - new_build_obj = Some(obj_path.display().to_string()); - } else if let Ok(obj_path) = path.strip_prefix(&target_dir) { - new_build_obj = Some(obj_path.display().to_string()); - } + if let (Some(base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) { + let mut new_build_obj = obj_path.clone(); + if units.is_empty() { + if ui.button("Select obj").clicked() { + if let Some(path) = rfd::FileDialog::new() + .set_directory(&target_dir) + .add_filter("Object file", &["o", "elf"]) + .pick_file() + { + if let Ok(obj_path) = path.strip_prefix(&base_dir) { + new_build_obj = Some(obj_path.display().to_string()); + } else if let Ok(obj_path) = path.strip_prefix(&target_dir) { + new_build_obj = Some(obj_path.display().to_string()); } } - if let Some(obj) = obj_path { - ui.label(&*obj); + } + if let Some(obj) = obj_path { + ui.label(&*obj); + } + } else { + CollapsingHeader::new(RichText::new("Objects").font(FontId { + size: appearance.ui_font.size, + family: appearance.code_font.family.clone(), + })) + .default_open(true) + .show(ui, |ui| { + for node in unit_nodes { + display_node(ui, &mut new_build_obj, node, appearance); } - } else { - CollapsingHeader::new(RichText::new("Objects").font(FontId { - size: view_state.view_config.ui_font.size, - family: view_state.view_config.code_font.family.clone(), - })) - .default_open(true) - .show(ui, |ui| { - for node in unit_nodes { - display_node(ui, &mut new_build_obj, node, &view_state.view_config); - } - }); - } - - let mut build = false; - if new_build_obj != *obj_path { - *obj_path = new_build_obj; - // TODO apply reverse_fn_order - build = true; - } - if obj_path.is_some() && ui.button("Build").clicked() { - build = true; - } - if build { - view_state.jobs.push(start_build(config.clone(), view_state.diff_config.clone())); - } - } - } else if view_state.diff_kind == DiffKind::WholeBinary { - if ui.button("Select left obj").clicked() { - if let Some(path) = - rfd::FileDialog::new().add_filter("Object file", &["o", "elf"]).pick_file() - { - *left_obj = Some(path); - } - } - if let Some(obj) = left_obj { - ui.label(obj.to_string_lossy()); + }); } - if ui.button("Select right obj").clicked() { - if let Some(path) = - rfd::FileDialog::new().add_filter("Object file", &["o", "elf"]).pick_file() - { - *right_obj = Some(path); - } + let mut build = false; + if new_build_obj != *obj_path { + *obj_path = new_build_obj; + // TODO apply reverse_fn_order + build = true; } - if let Some(obj) = right_obj { - ui.label(obj.to_string_lossy()); + if obj_path.is_some() && ui.button("Build").clicked() { + build = true; } - - if let (Some(_), Some(_)) = (left_obj, right_obj) { - if ui.button("Build").clicked() { - view_state.jobs.push(start_bindiff(config.clone())); - } + if build { + jobs.push(start_build(config.clone())); } } - // ui.checkbox(&mut view_state.view_config.reverse_fn_order, "Reverse function order (deferred)"); + // ui.checkbox(&mut view_config.reverse_fn_order, "Reverse function order (deferred)"); ui.separator(); } @@ -232,15 +215,15 @@ fn display_unit( obj_path: &mut Option, name: &str, unit: &ProjectUnit, - view_config: &ViewConfig, + appearance: &Appearance, ) { let path_string = unit.path.to_string_lossy().to_string(); let selected = matches!(obj_path, Some(path) if path == &path_string); if SelectableLabel::new( selected, RichText::new(name).font(FontId { - size: view_config.ui_font.size, - family: view_config.code_font.family.clone(), + size: appearance.ui_font.size, + family: appearance.code_font.family.clone(), }), ) .ui(ui) @@ -254,21 +237,21 @@ fn display_node( ui: &mut egui::Ui, obj_path: &mut Option, node: &ProjectUnitNode, - view_config: &ViewConfig, + appearance: &Appearance, ) { match node { ProjectUnitNode::File(name, unit) => { - display_unit(ui, obj_path, name, unit, view_config); + display_unit(ui, obj_path, name, unit, appearance); } ProjectUnitNode::Dir(name, children) => { CollapsingHeader::new(RichText::new(name).font(FontId { - size: view_config.ui_font.size, - family: view_config.code_font.family.clone(), + size: appearance.ui_font.size, + family: appearance.code_font.family.clone(), })) .default_open(false) .show(ui, |ui| { for node in children { - display_node(ui, obj_path, node, view_config); + display_node(ui, obj_path, node, appearance); } }); } @@ -277,18 +260,76 @@ fn display_node( const HELP_ICON: &str = "ℹ"; -fn subheading(ui: &mut egui::Ui, text: &str, view_config: &ViewConfig) { +fn subheading(ui: &mut egui::Ui, text: &str, appearance: &Appearance) { ui.label( - RichText::new(text).size(view_config.ui_font.size).color(view_config.emphasized_text_color), + RichText::new(text).size(appearance.ui_font.size).color(appearance.emphasized_text_color), ); } +fn format_path(path: &Option, appearance: &Appearance) -> RichText { + let mut color = appearance.replace_color; + let text = if let Some(dir) = path { + if let Some(rel) = dirs::home_dir().and_then(|home| dir.strip_prefix(&home).ok()) { + format!("~{}{}", MAIN_SEPARATOR, rel.display()) + } else { + format!("{}", dir.display()) + } + } else { + color = appearance.delete_color; + "[none]".to_string() + }; + RichText::new(text).color(color).family(FontFamily::Monospace) +} + +fn pick_folder_ui( + ui: &mut egui::Ui, + dir: &mut Option, + label: &str, + tooltip: impl FnOnce(&mut egui::Ui), + clicked: impl FnOnce(&mut Option), + appearance: &Appearance, +) { + ui.horizontal(|ui| { + subheading(ui, label, appearance); + ui.link(HELP_ICON).on_hover_ui(tooltip); + if ui.button("Select").clicked() { + clicked(dir); + } + }); + ui.label(format_path(dir, appearance)); +} + pub fn project_window( ctx: &egui::Context, config: &Arc>, - view_state: &mut ViewState, + show: &mut bool, + state: &mut ConfigViewState, + appearance: &Appearance, ) { let mut config_guard = config.write().unwrap(); + + egui::Window::new("Project").open(show).show(ctx, |ui| { + split_obj_config_ui(ui, &mut config_guard, state, appearance); + }); + + if let Some(error) = &state.load_error { + let mut open = true; + egui::Window::new("Error").open(&mut open).show(ctx, |ui| { + ui.label("Failed to load project config:"); + ui.colored_label(appearance.delete_color, error); + }); + if !open { + state.load_error = None; + } + } +} + +fn split_obj_config_ui( + ui: &mut egui::Ui, + config: &mut AppConfig, + state: &mut ConfigViewState, + appearance: &Appearance, +) { let AppConfig { custom_make, project_dir, @@ -300,239 +341,204 @@ pub fn project_window( watcher_change, watcher_enabled, watch_patterns, - load_error, .. - } = &mut *config_guard; + } = config; - egui::Window::new("Project").open(&mut view_state.show_project_config).show(ctx, |ui| { - let text_format = TextFormat::simple( - view_state.view_config.ui_font.clone(), - view_state.view_config.text_color, - ); - let code_format = TextFormat::simple( - FontId { - size: view_state.view_config.ui_font.size, - family: view_state.view_config.code_font.family.clone(), - }, - view_state.view_config.emphasized_text_color, - ); + let text_format = TextFormat::simple(appearance.ui_font.clone(), appearance.text_color); + let code_format = TextFormat::simple( + FontId { size: appearance.ui_font.size, family: appearance.code_font.family.clone() }, + appearance.emphasized_text_color, + ); - fn pick_folder_ui( - ui: &mut egui::Ui, - dir: &mut Option, - label: &str, - tooltip: impl FnOnce(&mut egui::Ui), - clicked: impl FnOnce(&mut Option), - view_config: &ViewConfig, - ) { - ui.horizontal(|ui| { - subheading(ui, label, view_config); - ui.link(HELP_ICON).on_hover_ui(tooltip); - if ui.button("Select").clicked() { - clicked(dir); - } - }); - if let Some(dir) = dir { - if let Some(home) = dirs::home_dir() { - if let Ok(rel) = dir.strip_prefix(&home) { - ui.label(RichText::new(format!("~/{}", rel.display())).color(view_config.replace_color).family(FontFamily::Monospace)); - return; - } - } - ui.label(RichText::new(format!("{}", dir.display())).color(view_config.replace_color).family(FontFamily::Monospace)); - } else { - ui.label(RichText::new("[none]").color(view_config.delete_color).family(FontFamily::Monospace)); - } - } - - if view_state.diff_kind == DiffKind::SplitObj { - pick_folder_ui( - ui, - project_dir, - "Project directory", - |ui| { - let mut job = LayoutJob::default(); - job.append( - "The root project directory.\n\n", - 0.0, - text_format.clone() - ); - job.append( - "If a configuration file exists, it will be loaded automatically.", - 0.0, - text_format.clone(), - ); - ui.label(job); - }, - |project_dir| { - if let Some(path) = rfd::FileDialog::new().pick_folder() { - *project_dir = Some(path); - *config_change = true; - *watcher_change = true; - *target_obj_dir = None; - *base_obj_dir = None; - *obj_path = None; - } - }, - &view_state.view_config, + pick_folder_ui( + ui, + project_dir, + "Project directory", + |ui| { + let mut job = LayoutJob::default(); + job.append("The root project directory.\n\n", 0.0, text_format.clone()); + job.append( + "If a configuration file exists, it will be loaded automatically.", + 0.0, + text_format.clone(), ); - ui.separator(); - - ui.horizontal(|ui| { - subheading(ui, "Custom make program", &view_state.view_config); - ui.link(HELP_ICON).on_hover_ui(|ui| { - let mut job = LayoutJob::default(); - job.append("By default, objdiff will build with ", 0.0, text_format.clone()); - job.append("make", 0.0, code_format.clone()); - job.append( - ".\nIf the project uses a different build system (e.g. ", - 0.0, - text_format.clone(), - ); - job.append("ninja", 0.0, code_format.clone()); - job.append( - "), specify it here.\nThe program must be in your ", - 0.0, - text_format.clone(), - ); - job.append("PATH", 0.0, code_format.clone()); - job.append(".", 0.0, text_format.clone()); - ui.label(job); - }); - }); - let mut custom_make_str = custom_make.clone().unwrap_or_default(); - if ui.text_edit_singleline(&mut custom_make_str).changed() { - if custom_make_str.is_empty() { - *custom_make = None; - } else { - *custom_make = Some(custom_make_str); - } - } - ui.separator(); - - if let Some(project_dir) = project_dir { - pick_folder_ui( - ui, - target_obj_dir, - "Target build directory", - |ui| { - let mut job = LayoutJob::default(); - job.append( - "This contains the \"target\" or \"expected\" objects, which are the intended result of the match.\n\n", - 0.0, - text_format.clone(), - ); - job.append( - "These are usually created by the project's build system or assembled.", - 0.0, - text_format.clone(), - ); - ui.label(job); - }, - |target_obj_dir| { - if let Some(path) = - rfd::FileDialog::new().set_directory(&project_dir).pick_folder() - { - *target_obj_dir = Some(path); - *obj_path = None; - } - }, - &view_state.view_config, - ); - ui.checkbox(build_target, "Build target objects").on_hover_ui(|ui| { - let mut job = LayoutJob::default(); - job.append("Tells the build system to produce the target object.\n", 0.0, text_format.clone()); - job.append("For example, this would call ", 0.0, text_format.clone()); - job.append("make path/to/target.o", 0.0, code_format.clone()); - job.append(".\n\n", 0.0, text_format.clone()); - job.append("This is useful if the target objects are not already built\n", 0.0, text_format.clone()); - job.append("or if they can change based on project configuration,\n", 0.0, text_format.clone()); - job.append("but requires that the build system is configured correctly.", 0.0, text_format.clone()); - ui.label(job); - }); - ui.separator(); - - pick_folder_ui( - ui, - base_obj_dir, - "Base build directory", - |ui| { - let mut job = LayoutJob::default(); - job.append( - "This contains the objects built from your decompiled code.", - 0.0, - text_format.clone(), - ); - ui.label(job); - }, - |base_obj_dir| { - if let Some(path) = - rfd::FileDialog::new().set_directory(&project_dir).pick_folder() - { - *base_obj_dir = Some(path); - *obj_path = None; - } - }, - &view_state.view_config, - ); - ui.separator(); - } - - subheading(ui, "Watch settings", &view_state.view_config); - let response = ui.checkbox(watcher_enabled, "Rebuild on changes").on_hover_ui(|ui| { - let mut job = LayoutJob::default(); - job.append("Automatically re-run the build & diff when files change.", 0.0, text_format.clone()); - ui.label(job); - }); - if response.changed() { + ui.label(job); + }, + |project_dir| { + if let Some(path) = rfd::FileDialog::new().pick_folder() { + *project_dir = Some(path); + *config_change = true; *watcher_change = true; - }; - - ui.horizontal(|ui| { - ui.label(RichText::new("File Patterns").color(view_state.view_config.text_color)); - if ui.button("Reset").clicked() { - *watch_patterns = DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(); - *watcher_change = true; - } - }); - let mut remove_at: Option = None; - for (idx, glob) in watch_patterns.iter().enumerate() { - ui.horizontal(|ui| { - ui.label(RichText::new(format!("{}", glob)) - .color(view_state.view_config.text_color) - .family(FontFamily::Monospace)); - if ui.small_button("-").clicked() { - remove_at = Some(idx); - } - }); + *target_obj_dir = None; + *base_obj_dir = None; + *obj_path = None; } - if let Some(idx) = remove_at { - watch_patterns.remove(idx); - *watcher_change = true; - } - ui.horizontal(|ui| { - egui::TextEdit::singleline(&mut view_state.watch_pattern_text) - .desired_width(100.0) - .show(ui); - if ui.small_button("+").clicked() { - if let Ok(glob) = Glob::new(&view_state.watch_pattern_text) { - watch_patterns.push(glob); - *watcher_change = true; - view_state.watch_pattern_text.clear(); - } - } - }); - } - }); + }, + appearance, + ); + ui.separator(); - if let Some(error) = &load_error { - let mut open = true; - egui::Window::new("Error").open(&mut open).show(ctx, |ui| { - ui.label("Failed to load project config:"); - ui.colored_label(view_state.view_config.delete_color, error); + ui.horizontal(|ui| { + subheading(ui, "Custom make program", appearance); + ui.link(HELP_ICON).on_hover_ui(|ui| { + let mut job = LayoutJob::default(); + job.append("By default, objdiff will build with ", 0.0, text_format.clone()); + job.append("make", 0.0, code_format.clone()); + job.append( + ".\nIf the project uses a different build system (e.g. ", + 0.0, + text_format.clone(), + ); + job.append("ninja", 0.0, code_format.clone()); + job.append( + "), specify it here.\nThe program must be in your ", + 0.0, + text_format.clone(), + ); + job.append("PATH", 0.0, code_format.clone()); + job.append(".", 0.0, text_format.clone()); + ui.label(job); }); - if !open { - *load_error = None; + }); + let mut custom_make_str = custom_make.clone().unwrap_or_default(); + if ui.text_edit_singleline(&mut custom_make_str).changed() { + if custom_make_str.is_empty() { + *custom_make = None; + } else { + *custom_make = Some(custom_make_str); } } + ui.separator(); + + if let Some(project_dir) = project_dir { + pick_folder_ui( + ui, + target_obj_dir, + "Target build directory", + |ui| { + let mut job = LayoutJob::default(); + job.append( + "This contains the \"target\" or \"expected\" objects, which are the intended result of the match.\n\n", + 0.0, + text_format.clone(), + ); + job.append( + "These are usually created by the project's build system or assembled.", + 0.0, + text_format.clone(), + ); + ui.label(job); + }, + |target_obj_dir| { + if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() + { + *target_obj_dir = Some(path); + *obj_path = None; + } + }, + appearance, + ); + ui.checkbox(build_target, "Build target objects").on_hover_ui(|ui| { + let mut job = LayoutJob::default(); + job.append( + "Tells the build system to produce the target object.\n", + 0.0, + text_format.clone(), + ); + job.append("For example, this would call ", 0.0, text_format.clone()); + job.append("make path/to/target.o", 0.0, code_format.clone()); + job.append(".\n\n", 0.0, text_format.clone()); + job.append( + "This is useful if the target objects are not already built\n", + 0.0, + text_format.clone(), + ); + job.append( + "or if they can change based on project configuration,\n", + 0.0, + text_format.clone(), + ); + job.append( + "but requires that the build system is configured correctly.", + 0.0, + text_format.clone(), + ); + ui.label(job); + }); + ui.separator(); + + pick_folder_ui( + ui, + base_obj_dir, + "Base build directory", + |ui| { + let mut job = LayoutJob::default(); + job.append( + "This contains the objects built from your decompiled code.", + 0.0, + text_format.clone(), + ); + ui.label(job); + }, + |base_obj_dir| { + if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() + { + *base_obj_dir = Some(path); + *obj_path = None; + } + }, + appearance, + ); + ui.separator(); + } + + subheading(ui, "Watch settings", appearance); + let response = ui.checkbox(watcher_enabled, "Rebuild on changes").on_hover_ui(|ui| { + let mut job = LayoutJob::default(); + job.append( + "Automatically re-run the build & diff when files change.", + 0.0, + text_format.clone(), + ); + ui.label(job); + }); + if response.changed() { + *watcher_change = true; + }; + + ui.horizontal(|ui| { + ui.label(RichText::new("File Patterns").color(appearance.text_color)); + if ui.button("Reset").clicked() { + *watch_patterns = + DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(); + *watcher_change = true; + } + }); + let mut remove_at: Option = None; + for (idx, glob) in watch_patterns.iter().enumerate() { + ui.horizontal(|ui| { + ui.label( + RichText::new(format!("{}", glob)) + .color(appearance.text_color) + .family(FontFamily::Monospace), + ); + if ui.small_button("-").clicked() { + remove_at = Some(idx); + } + }); + } + if let Some(idx) = remove_at { + watch_patterns.remove(idx); + *watcher_change = true; + } + ui.horizontal(|ui| { + egui::TextEdit::singleline(&mut state.watch_pattern_text).desired_width(100.0).show(ui); + if ui.small_button("+").clicked() { + if let Ok(glob) = Glob::new(&state.watch_pattern_text) { + watch_patterns.push(glob); + *watcher_change = true; + state.watch_pattern_text.clear(); + } + } + }); } diff --git a/src/views/data_diff.rs b/src/views/data_diff.rs index 2f0a324..5301d33 100644 --- a/src/views/data_diff.rs +++ b/src/views/data_diff.rs @@ -5,10 +5,13 @@ use egui_extras::{Column, TableBuilder}; use time::format_description; use crate::{ - app::{SymbolReference, View, ViewConfig, ViewState}, - jobs::Job, + jobs::{Job, JobQueue}, obj::{ObjDataDiff, ObjDataDiffKind, ObjInfo, ObjSection}, - views::write_text, + views::{ + appearance::Appearance, + symbol_diff::{DiffViewState, SymbolReference, View}, + write_text, + }, }; const BYTES_PER_ROW: usize = 16; @@ -17,29 +20,29 @@ fn find_section<'a>(obj: &'a ObjInfo, selected_symbol: &SymbolReference) -> Opti obj.sections.iter().find(|section| section.name == selected_symbol.section_name) } -fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config: &ViewConfig) { +fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], appearance: &Appearance) { if diffs.iter().any(|d| d.kind != ObjDataDiffKind::None) { ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color); } let mut job = LayoutJob::default(); write_text( format!("{address:08X}: ").as_str(), - config.text_color, + appearance.text_color, &mut job, - config.code_font.clone(), + appearance.code_font.clone(), ); let mut cur_addr = 0usize; for diff in diffs { let base_color = match diff.kind { - ObjDataDiffKind::None => config.text_color, - ObjDataDiffKind::Replace => config.replace_color, - ObjDataDiffKind::Delete => config.delete_color, - ObjDataDiffKind::Insert => config.insert_color, + ObjDataDiffKind::None => appearance.text_color, + ObjDataDiffKind::Replace => appearance.replace_color, + ObjDataDiffKind::Delete => appearance.delete_color, + ObjDataDiffKind::Insert => appearance.insert_color, }; if diff.data.is_empty() { let mut str = " ".repeat(diff.len); str.push_str(" ".repeat(diff.len / 8).as_str()); - write_text(str.as_str(), base_color, &mut job, config.code_font.clone()); + write_text(str.as_str(), base_color, &mut job, appearance.code_font.clone()); cur_addr += diff.len; } else { let mut text = String::new(); @@ -50,7 +53,7 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config: text.push(' '); } } - write_text(text.as_str(), base_color, &mut job, config.code_font.clone()); + write_text(text.as_str(), base_color, &mut job, appearance.code_font.clone()); } } if cur_addr < BYTES_PER_ROW { @@ -58,22 +61,22 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config: let mut str = " ".to_string(); str.push_str(" ".repeat(n).as_str()); str.push_str(" ".repeat(n / 8).as_str()); - write_text(str.as_str(), config.text_color, &mut job, config.code_font.clone()); + write_text(str.as_str(), appearance.text_color, &mut job, appearance.code_font.clone()); } - write_text(" ", config.text_color, &mut job, config.code_font.clone()); + write_text(" ", appearance.text_color, &mut job, appearance.code_font.clone()); for diff in diffs { let base_color = match diff.kind { - ObjDataDiffKind::None => config.text_color, - ObjDataDiffKind::Replace => config.replace_color, - ObjDataDiffKind::Delete => config.delete_color, - ObjDataDiffKind::Insert => config.insert_color, + ObjDataDiffKind::None => appearance.text_color, + ObjDataDiffKind::Replace => appearance.replace_color, + ObjDataDiffKind::Delete => appearance.delete_color, + ObjDataDiffKind::Insert => appearance.insert_color, }; if diff.data.is_empty() { write_text( " ".repeat(diff.len).as_str(), base_color, &mut job, - config.code_font.clone(), + appearance.code_font.clone(), ); } else { let mut text = String::new(); @@ -85,7 +88,7 @@ fn data_row_ui(ui: &mut egui::Ui, address: usize, diffs: &[ObjDataDiff], config: text.push('.'); } } - write_text(text.as_str(), base_color, &mut job, config.code_font.clone()); + write_text(text.as_str(), base_color, &mut job, appearance.code_font.clone()); } } ui.add(Label::new(job).sense(Sense::click())); @@ -133,7 +136,7 @@ fn data_table_ui( left_obj: &ObjInfo, right_obj: &ObjInfo, selected_symbol: &SymbolReference, - config: &ViewConfig, + config: &Appearance, ) -> Option<()> { let left_section = find_section(left_obj, selected_symbol)?; let right_section = find_section(right_obj, selected_symbol)?; @@ -161,10 +164,14 @@ fn data_table_ui( Some(()) } -pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool { +pub fn data_diff_ui( + ui: &mut egui::Ui, + jobs: &JobQueue, + state: &mut DiffViewState, + appearance: &Appearance, +) -> bool { let mut rebuild = false; - let (Some(result), Some(selected_symbol)) = (&view_state.build, &view_state.selected_symbol) - else { + let (Some(result), Some(selected_symbol)) = (&state.build, &state.selected_symbol) else { return rebuild; }; @@ -183,16 +190,13 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool { ui.set_width(column_width); if ui.button("Back").clicked() { - view_state.current_view = View::SymbolDiff; + state.current_view = View::SymbolDiff; } ui.scope(|ui| { ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().wrap = Some(false); - ui.colored_label( - view_state.view_config.highlight_color, - &selected_symbol.symbol_name, - ); + ui.colored_label(appearance.highlight_color, &selected_symbol.symbol_name); ui.label("Diff target:"); }); }, @@ -212,8 +216,8 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool { ui.scope(|ui| { ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().wrap = Some(false); - if view_state.jobs.is_running(Job::ObjDiff) { - ui.colored_label(view_state.view_config.replace_color, "Building…"); + if jobs.is_running(Job::ObjDiff) { + ui.colored_label(appearance.replace_color, "Building…"); } else { ui.label("Last built:"); let format = @@ -221,7 +225,7 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool { ui.label( result .time - .to_offset(view_state.utc_offset) + .to_offset(appearance.utc_offset) .format(&format) .unwrap(), ); @@ -251,7 +255,7 @@ pub fn data_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool { .resizable(false) .auto_shrink([false, false]) .min_scrolled_height(available_height); - data_table_ui(table, left_obj, right_obj, selected_symbol, &view_state.view_config); + data_table_ui(table, left_obj, right_obj, selected_symbol, appearance); } rebuild diff --git a/src/views/demangle.rs b/src/views/demangle.rs index 88f268b..b361bea 100644 --- a/src/views/demangle.rs +++ b/src/views/demangle.rs @@ -1,17 +1,25 @@ use egui::TextStyle; -use crate::app::ViewState; +use crate::views::appearance::Appearance; -pub fn demangle_window(ctx: &egui::Context, view_state: &mut ViewState) { - egui::Window::new("Demangle").open(&mut view_state.show_demangle).show(ctx, |ui| { - ui.text_edit_singleline(&mut view_state.demangle_text); +#[derive(Default)] +pub struct DemangleViewState { + pub text: String, +} + +pub fn demangle_window( + ctx: &egui::Context, + show: &mut bool, + state: &mut DemangleViewState, + appearance: &Appearance, +) { + egui::Window::new("Demangle").open(show).show(ctx, |ui| { + ui.text_edit_singleline(&mut state.text); ui.add_space(10.0); - if let Some(demangled) = - cwdemangle::demangle(&view_state.demangle_text, &Default::default()) - { + if let Some(demangled) = cwdemangle::demangle(&state.text, &Default::default()) { ui.scope(|ui| { ui.style_mut().override_text_style = Some(TextStyle::Monospace); - ui.colored_label(view_state.view_config.replace_color, &demangled); + ui.colored_label(appearance.replace_color, &demangled); }); if ui.button("Copy").clicked() { ui.output_mut(|output| output.copied_text = demangled); @@ -19,7 +27,7 @@ pub fn demangle_window(ctx: &egui::Context, view_state: &mut ViewState) { } else { ui.scope(|ui| { ui.style_mut().override_text_style = Some(TextStyle::Monospace); - ui.colored_label(view_state.view_config.replace_color, "[invalid]"); + ui.colored_label(appearance.replace_color, "[invalid]"); }); } }); diff --git a/src/views/function_diff.rs b/src/views/function_diff.rs index dce83e4..259bcf7 100644 --- a/src/views/function_diff.rs +++ b/src/views/function_diff.rs @@ -8,13 +8,16 @@ use ppc750cl::Argument; use time::format_description; use crate::{ - app::{SymbolReference, View, ViewConfig, ViewState}, - jobs::Job, + jobs::{Job, JobQueue}, obj::{ ObjInfo, ObjIns, ObjInsArg, ObjInsArgDiff, ObjInsDiff, ObjInsDiffKind, ObjReloc, ObjRelocKind, ObjSymbol, }, - views::{symbol_diff::match_color_for_symbol, write_text}, + views::{ + appearance::Appearance, + symbol_diff::{match_color_for_symbol, DiffViewState, SymbolReference, View}, + write_text, + }, }; fn write_reloc_name( @@ -22,10 +25,10 @@ fn write_reloc_name( color: Color32, job: &mut LayoutJob, font_id: FontId, - config: &ViewConfig, + appearance: &Appearance, ) { let name = reloc.target.demangled_name.as_ref().unwrap_or(&reloc.target.name); - write_text(name, config.emphasized_text_color, job, font_id.clone()); + write_text(name, appearance.emphasized_text_color, job, font_id.clone()); match reloc.target.addend.cmp(&0i64) { Ordering::Greater => { write_text(&format!("+{:#X}", reloc.target.addend), color, job, font_id) @@ -42,52 +45,52 @@ fn write_reloc( color: Color32, job: &mut LayoutJob, font_id: FontId, - config: &ViewConfig, + appearance: &Appearance, ) { match reloc.kind { ObjRelocKind::PpcAddr16Lo => { - write_reloc_name(reloc, color, job, font_id.clone(), config); + write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_text("@l", color, job, font_id); } ObjRelocKind::PpcAddr16Hi => { - write_reloc_name(reloc, color, job, font_id.clone(), config); + write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_text("@h", color, job, font_id); } ObjRelocKind::PpcAddr16Ha => { - write_reloc_name(reloc, color, job, font_id.clone(), config); + write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_text("@ha", color, job, font_id); } ObjRelocKind::PpcEmbSda21 => { - write_reloc_name(reloc, color, job, font_id.clone(), config); + write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_text("@sda21", color, job, font_id); } ObjRelocKind::MipsHi16 => { write_text("%hi(", color, job, font_id.clone()); - write_reloc_name(reloc, color, job, font_id.clone(), config); + write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_text(")", color, job, font_id); } ObjRelocKind::MipsLo16 => { write_text("%lo(", color, job, font_id.clone()); - write_reloc_name(reloc, color, job, font_id.clone(), config); + write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_text(")", color, job, font_id); } ObjRelocKind::MipsGot16 => { write_text("%got(", color, job, font_id.clone()); - write_reloc_name(reloc, color, job, font_id.clone(), config); + write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_text(")", color, job, font_id); } ObjRelocKind::MipsCall16 => { write_text("%call16(", color, job, font_id.clone()); - write_reloc_name(reloc, color, job, font_id.clone(), config); + write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_text(")", color, job, font_id); } ObjRelocKind::MipsGpRel16 => { write_text("%gp_rel(", color, job, font_id.clone()); - write_reloc_name(reloc, color, job, font_id.clone(), config); + write_reloc_name(reloc, color, job, font_id.clone(), appearance); write_text(")", color, job, font_id); } ObjRelocKind::PpcRel24 | ObjRelocKind::PpcRel14 | ObjRelocKind::Mips26 => { - write_reloc_name(reloc, color, job, font_id, config); + write_reloc_name(reloc, color, job, font_id, appearance); } ObjRelocKind::Absolute | ObjRelocKind::MipsGpRel32 => { write_text("[INVALID]", color, job, font_id); @@ -101,51 +104,51 @@ fn write_ins( args: &[Option], base_addr: u32, job: &mut LayoutJob, - config: &ViewConfig, + appearance: &Appearance, ) { let base_color = match diff_kind { ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => { - config.text_color + appearance.text_color } - ObjInsDiffKind::Replace => config.replace_color, - ObjInsDiffKind::Delete => config.delete_color, - ObjInsDiffKind::Insert => config.insert_color, + ObjInsDiffKind::Replace => appearance.replace_color, + ObjInsDiffKind::Delete => appearance.delete_color, + ObjInsDiffKind::Insert => appearance.insert_color, }; write_text( &format!("{:<11}", ins.mnemonic), match diff_kind { - ObjInsDiffKind::OpMismatch => config.replace_color, + ObjInsDiffKind::OpMismatch => appearance.replace_color, _ => base_color, }, job, - config.code_font.clone(), + appearance.code_font.clone(), ); let mut writing_offset = false; for (i, arg) in ins.args.iter().enumerate() { if i == 0 { - write_text(" ", base_color, job, config.code_font.clone()); + write_text(" ", base_color, job, appearance.code_font.clone()); } if i > 0 && !writing_offset { - write_text(", ", base_color, job, config.code_font.clone()); + write_text(", ", base_color, job, appearance.code_font.clone()); } let color = if let Some(diff) = args.get(i).and_then(|a| a.as_ref()) { - config.diff_colors[diff.idx % config.diff_colors.len()] + appearance.diff_colors[diff.idx % appearance.diff_colors.len()] } else { base_color }; match arg { ObjInsArg::PpcArg(arg) => match arg { Argument::Offset(val) => { - write_text(&format!("{val}"), color, job, config.code_font.clone()); - write_text("(", base_color, job, config.code_font.clone()); + write_text(&format!("{val}"), color, job, appearance.code_font.clone()); + write_text("(", base_color, job, appearance.code_font.clone()); writing_offset = true; continue; } Argument::Uimm(_) | Argument::Simm(_) => { - write_text(&format!("{arg}"), color, job, config.code_font.clone()); + write_text(&format!("{arg}"), color, job, appearance.code_font.clone()); } _ => { - write_text(&format!("{arg}"), color, job, config.code_font.clone()); + write_text(&format!("{arg}"), color, job, appearance.code_font.clone()); } }, ObjInsArg::Reloc => { @@ -153,8 +156,8 @@ fn write_ins( ins.reloc.as_ref().unwrap(), base_color, job, - config.code_font.clone(), - config, + appearance.code_font.clone(), + appearance, ); } ObjInsArg::RelocWithBase => { @@ -162,10 +165,10 @@ fn write_ins( ins.reloc.as_ref().unwrap(), base_color, job, - config.code_font.clone(), - config, + appearance.code_font.clone(), + appearance, ); - write_text("(", base_color, job, config.code_font.clone()); + write_text("(", base_color, job, appearance.code_font.clone()); writing_offset = true; continue; } @@ -174,7 +177,7 @@ fn write_ins( str.strip_prefix('$').unwrap_or(str), color, job, - config.code_font.clone(), + appearance.code_font.clone(), ); } ObjInsArg::MipsArgWithBase(str) => { @@ -182,25 +185,25 @@ fn write_ins( str.strip_prefix('$').unwrap_or(str), color, job, - config.code_font.clone(), + appearance.code_font.clone(), ); - write_text("(", base_color, job, config.code_font.clone()); + write_text("(", base_color, job, appearance.code_font.clone()); writing_offset = true; continue; } ObjInsArg::BranchOffset(offset) => { let addr = offset + ins.address as i32 - base_addr as i32; - write_text(&format!("{addr:x}"), color, job, config.code_font.clone()); + write_text(&format!("{addr:x}"), color, job, appearance.code_font.clone()); } } if writing_offset { - write_text(")", base_color, job, config.code_font.clone()); + write_text(")", base_color, job, appearance.code_font.clone()); writing_offset = false; } } } -fn ins_hover_ui(ui: &mut egui::Ui, ins: &ObjIns, config: &ViewConfig) { +fn ins_hover_ui(ui: &mut egui::Ui, ins: &ObjIns, appearance: &Appearance) { ui.scope(|ui| { ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().wrap = Some(false); @@ -226,16 +229,19 @@ fn ins_hover_ui(ui: &mut egui::Ui, ins: &ObjIns, config: &ViewConfig) { if let Some(reloc) = &ins.reloc { ui.label(format!("Relocation type: {:?}", reloc.kind)); - ui.colored_label(config.highlight_color, format!("Name: {}", reloc.target.name)); + ui.colored_label(appearance.highlight_color, format!("Name: {}", reloc.target.name)); if let Some(section) = &reloc.target_section { - ui.colored_label(config.highlight_color, format!("Section: {section}")); + ui.colored_label(appearance.highlight_color, format!("Section: {section}")); ui.colored_label( - config.highlight_color, + appearance.highlight_color, format!("Address: {:x}", reloc.target.address), ); - ui.colored_label(config.highlight_color, format!("Size: {:x}", reloc.target.size)); + ui.colored_label( + appearance.highlight_color, + format!("Size: {:x}", reloc.target.size), + ); } else { - ui.colored_label(config.highlight_color, "Extern".to_string()); + ui.colored_label(appearance.highlight_color, "Extern".to_string()); } } }); @@ -306,7 +312,12 @@ fn find_symbol<'a>(obj: &'a ObjInfo, selected_symbol: &SymbolReference) -> Optio }) } -fn asm_row_ui(ui: &mut egui::Ui, ins_diff: &ObjInsDiff, symbol: &ObjSymbol, config: &ViewConfig) { +fn asm_row_ui( + ui: &mut egui::Ui, + ins_diff: &ObjInsDiff, + symbol: &ObjSymbol, + appearance: &Appearance, +) { if ins_diff.kind != ObjInsDiffKind::None { ui.painter().rect_filled(ui.available_rect_before_wrap(), 0.0, ui.visuals().faint_bg_color); } @@ -318,45 +329,50 @@ fn asm_row_ui(ui: &mut egui::Ui, ins_diff: &ObjInsDiff, symbol: &ObjSymbol, conf let base_color = match ins_diff.kind { ObjInsDiffKind::None | ObjInsDiffKind::OpMismatch | ObjInsDiffKind::ArgMismatch => { - config.text_color + appearance.text_color } - ObjInsDiffKind::Replace => config.replace_color, - ObjInsDiffKind::Delete => config.delete_color, - ObjInsDiffKind::Insert => config.insert_color, + ObjInsDiffKind::Replace => appearance.replace_color, + ObjInsDiffKind::Delete => appearance.delete_color, + ObjInsDiffKind::Insert => appearance.insert_color, }; let mut pad = 6; if let Some(line) = ins.line { let line_str = format!("{line} "); - write_text(&line_str, config.deemphasized_text_color, &mut job, config.code_font.clone()); + write_text( + &line_str, + appearance.deemphasized_text_color, + &mut job, + appearance.code_font.clone(), + ); pad = 12 - line_str.len(); } write_text( &format!("{:<1$}", format!("{:x}: ", ins.address - symbol.address as u32), pad), base_color, &mut job, - config.code_font.clone(), + appearance.code_font.clone(), ); if let Some(branch) = &ins_diff.branch_from { write_text( "~> ", - config.diff_colors[branch.branch_idx % config.diff_colors.len()], + appearance.diff_colors[branch.branch_idx % appearance.diff_colors.len()], &mut job, - config.code_font.clone(), + appearance.code_font.clone(), ); } else { - write_text(" ", base_color, &mut job, config.code_font.clone()); + write_text(" ", base_color, &mut job, appearance.code_font.clone()); } - write_ins(ins, &ins_diff.kind, &ins_diff.arg_diff, symbol.address as u32, &mut job, config); + write_ins(ins, &ins_diff.kind, &ins_diff.arg_diff, symbol.address as u32, &mut job, appearance); if let Some(branch) = &ins_diff.branch_to { write_text( " ~>", - config.diff_colors[branch.branch_idx % config.diff_colors.len()], + appearance.diff_colors[branch.branch_idx % appearance.diff_colors.len()], &mut job, - config.code_font.clone(), + appearance.code_font.clone(), ); } ui.add(Label::new(job).sense(Sense::click())) - .on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins, config)) + .on_hover_ui_at_pointer(|ui| ins_hover_ui(ui, ins, appearance)) .context_menu(|ui| ins_context_menu(ui, ins)); } @@ -365,21 +381,21 @@ fn asm_table_ui( left_obj: &ObjInfo, right_obj: &ObjInfo, selected_symbol: &SymbolReference, - config: &ViewConfig, + appearance: &Appearance, ) -> Option<()> { let left_symbol = find_symbol(left_obj, selected_symbol); let right_symbol = find_symbol(right_obj, selected_symbol); let instructions_len = left_symbol.or(right_symbol).map(|s| s.instructions.len())?; table.body(|body| { - body.rows(config.code_font.size, instructions_len, |row_index, mut row| { + body.rows(appearance.code_font.size, instructions_len, |row_index, mut row| { row.col(|ui| { if let Some(symbol) = left_symbol { - asm_row_ui(ui, &symbol.instructions[row_index], symbol, config); + asm_row_ui(ui, &symbol.instructions[row_index], symbol, appearance); } }); row.col(|ui| { if let Some(symbol) = right_symbol { - asm_row_ui(ui, &symbol.instructions[row_index], symbol, config); + asm_row_ui(ui, &symbol.instructions[row_index], symbol, appearance); } }); }); @@ -387,10 +403,14 @@ fn asm_table_ui( Some(()) } -pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool { +pub fn function_diff_ui( + ui: &mut egui::Ui, + jobs: &JobQueue, + state: &mut DiffViewState, + appearance: &Appearance, +) -> bool { let mut rebuild = false; - let (Some(result), Some(selected_symbol)) = (&view_state.build, &view_state.selected_symbol) - else { + let (Some(result), Some(selected_symbol)) = (&state.build, &state.selected_symbol) else { return rebuild; }; @@ -409,15 +429,15 @@ pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool { ui.set_width(column_width); if ui.button("Back").clicked() { - view_state.current_view = View::SymbolDiff; + state.current_view = View::SymbolDiff; } let demangled = demangle(&selected_symbol.symbol_name, &Default::default()); let name = demangled.as_deref().unwrap_or(&selected_symbol.symbol_name); let mut job = LayoutJob::simple( name.to_string(), - view_state.view_config.code_font.clone(), - view_state.view_config.highlight_color, + appearance.code_font.clone(), + appearance.highlight_color, column_width, ); job.wrap.break_anywhere = true; @@ -445,8 +465,8 @@ pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool { ui.scope(|ui| { ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().wrap = Some(false); - if view_state.jobs.is_running(Job::ObjDiff) { - ui.colored_label(view_state.view_config.replace_color, "Building…"); + if jobs.is_running(Job::ObjDiff) { + ui.colored_label(appearance.replace_color, "Building…"); } else { ui.label("Last built:"); let format = @@ -454,7 +474,7 @@ pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool { ui.label( result .time - .to_offset(view_state.utc_offset) + .to_offset(appearance.utc_offset) .format(&format) .unwrap(), ); @@ -471,7 +491,7 @@ pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool { .and_then(|symbol| symbol.match_percent) { ui.colored_label( - match_color_for_symbol(match_percent, &view_state.view_config), + match_color_for_symbol(match_percent, appearance), &format!("{match_percent:.0}%"), ); } else { @@ -495,7 +515,7 @@ pub fn function_diff_ui(ui: &mut egui::Ui, view_state: &mut ViewState) -> bool { .resizable(false) .auto_shrink([false, false]) .min_scrolled_height(available_height); - asm_table_ui(table, left_obj, right_obj, selected_symbol, &view_state.view_config); + asm_table_ui(table, left_obj, right_obj, selected_symbol, appearance); } rebuild } diff --git a/src/views/jobs.rs b/src/views/jobs.rs index 3eb8ecd..13a5598 100644 --- a/src/views/jobs.rs +++ b/src/views/jobs.rs @@ -1,12 +1,12 @@ use egui::{ProgressBar, Widget}; -use crate::app::ViewState; +use crate::{jobs::JobQueue, views::appearance::Appearance}; -pub fn jobs_ui(ui: &mut egui::Ui, view_state: &mut ViewState) { +pub fn jobs_ui(ui: &mut egui::Ui, jobs: &mut JobQueue, appearance: &Appearance) { ui.label("Jobs"); let mut remove_job: Option = None; - for job in view_state.jobs.iter_mut() { + for job in jobs.iter_mut() { let Ok(status) = job.status.read() else { continue; }; @@ -33,7 +33,7 @@ pub fn jobs_ui(ui: &mut egui::Ui, view_state: &mut ViewState) { if let Some(err) = &status.error { let err_string = err.to_string(); ui.colored_label( - view_state.view_config.delete_color, + appearance.delete_color, if err_string.len() > STATUS_LENGTH - 10 { format!("Error: {}…", &err_string[0..STATUS_LENGTH - 10]) } else { @@ -51,6 +51,6 @@ pub fn jobs_ui(ui: &mut egui::Ui, view_state: &mut ViewState) { } if let Some(idx) = remove_job { - view_state.jobs.remove(idx); + jobs.remove(idx); } } diff --git a/src/views/mod.rs b/src/views/mod.rs index 613785b..daa49a5 100644 --- a/src/views/mod.rs +++ b/src/views/mod.rs @@ -8,6 +8,7 @@ pub(crate) mod function_diff; pub(crate) mod jobs; pub(crate) mod symbol_diff; +#[inline] fn write_text(str: &str, color: Color32, job: &mut LayoutJob, font_id: FontId) { job.append(str, 0.0, TextFormat::simple(font_id, color)); } diff --git a/src/views/symbol_diff.rs b/src/views/symbol_diff.rs index 8eb7848..539c1ee 100644 --- a/src/views/symbol_diff.rs +++ b/src/views/symbol_diff.rs @@ -5,19 +5,41 @@ use egui::{ use egui_extras::{Size, StripBuilder}; use crate::{ - app::{SymbolReference, View, ViewConfig, ViewState}, - jobs::objdiff::BuildStatus, + jobs::objdiff::{BuildStatus, ObjDiffResult}, obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, ObjSymbolFlags}, - views::write_text, + views::{appearance::Appearance, write_text}, }; -pub fn match_color_for_symbol(match_percent: f32, config: &ViewConfig) -> Color32 { +pub struct SymbolReference { + pub symbol_name: String, + pub section_name: String, +} + +#[allow(clippy::enum_variant_names)] +#[derive(Default, Eq, PartialEq)] +pub enum View { + #[default] + SymbolDiff, + FunctionDiff, + DataDiff, +} + +#[derive(Default)] +pub struct DiffViewState { + pub build: Option>, + pub current_view: View, + pub highlighted_symbol: Option, + pub selected_symbol: Option, + pub search: String, +} + +pub fn match_color_for_symbol(match_percent: f32, appearance: &Appearance) -> Color32 { if match_percent == 100.0 { - config.insert_color + appearance.insert_color } else if match_percent >= 50.0 { - config.replace_color + appearance.replace_color } else { - config.delete_color + appearance.delete_color } } @@ -39,17 +61,20 @@ fn symbol_context_menu_ui(ui: &mut Ui, symbol: &ObjSymbol) { }); } -fn symbol_hover_ui(ui: &mut Ui, symbol: &ObjSymbol, config: &ViewConfig) { +fn symbol_hover_ui(ui: &mut Ui, symbol: &ObjSymbol, appearance: &Appearance) { ui.scope(|ui| { ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().wrap = Some(false); - ui.colored_label(config.highlight_color, format!("Name: {}", symbol.name)); - ui.colored_label(config.highlight_color, format!("Address: {:x}", symbol.address)); + ui.colored_label(appearance.highlight_color, format!("Name: {}", symbol.name)); + ui.colored_label(appearance.highlight_color, format!("Address: {:x}", symbol.address)); if symbol.size_known { - ui.colored_label(config.highlight_color, format!("Size: {:x}", symbol.size)); + ui.colored_label(appearance.highlight_color, format!("Size: {:x}", symbol.size)); } else { - ui.colored_label(config.highlight_color, format!("Size: {:x} (assumed)", symbol.size)); + ui.colored_label( + appearance.highlight_color, + format!("Size: {:x} (assumed)", symbol.size), + ); } }); } @@ -61,7 +86,7 @@ fn symbol_ui( highlighted_symbol: &mut Option, selected_symbol: &mut Option, current_view: &mut View, - config: &ViewConfig, + appearance: &Appearance, ) { let mut job = LayoutJob::default(); let name: &str = @@ -70,38 +95,38 @@ fn symbol_ui( if let Some(sym) = highlighted_symbol { selected = sym == &symbol.name; } - write_text("[", config.text_color, &mut job, config.code_font.clone()); + write_text("[", appearance.text_color, &mut job, appearance.code_font.clone()); if symbol.flags.0.contains(ObjSymbolFlags::Common) { write_text( "c", - config.replace_color, /* Color32::from_rgb(0, 255, 255) */ + appearance.replace_color, /* Color32::from_rgb(0, 255, 255) */ &mut job, - config.code_font.clone(), + appearance.code_font.clone(), ); } else if symbol.flags.0.contains(ObjSymbolFlags::Global) { - write_text("g", config.insert_color, &mut job, config.code_font.clone()); + write_text("g", appearance.insert_color, &mut job, appearance.code_font.clone()); } else if symbol.flags.0.contains(ObjSymbolFlags::Local) { - write_text("l", config.text_color, &mut job, config.code_font.clone()); + write_text("l", appearance.text_color, &mut job, appearance.code_font.clone()); } if symbol.flags.0.contains(ObjSymbolFlags::Weak) { - write_text("w", config.text_color, &mut job, config.code_font.clone()); + write_text("w", appearance.text_color, &mut job, appearance.code_font.clone()); } - write_text("] ", config.text_color, &mut job, config.code_font.clone()); + write_text("] ", appearance.text_color, &mut job, appearance.code_font.clone()); if let Some(match_percent) = symbol.match_percent { - write_text("(", config.text_color, &mut job, config.code_font.clone()); + write_text("(", appearance.text_color, &mut job, appearance.code_font.clone()); write_text( &format!("{match_percent:.0}%"), - match_color_for_symbol(match_percent, config), + match_color_for_symbol(match_percent, appearance), &mut job, - config.code_font.clone(), + appearance.code_font.clone(), ); - write_text(") ", config.text_color, &mut job, config.code_font.clone()); + write_text(") ", appearance.text_color, &mut job, appearance.code_font.clone()); } - write_text(name, config.highlight_color, &mut job, config.code_font.clone()); + write_text(name, appearance.highlight_color, &mut job, appearance.code_font.clone()); let response = SelectableLabel::new(selected, job) .ui(ui) .context_menu(|ui| symbol_context_menu_ui(ui, symbol)) - .on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, symbol, config)); + .on_hover_ui_at_pointer(|ui| symbol_hover_ui(ui, symbol, appearance)); if response.clicked() { if let Some(section) = section { if section.kind == ObjSectionKind::Code { @@ -141,7 +166,7 @@ fn symbol_list_ui( selected_symbol: &mut Option, current_view: &mut View, lower_search: &str, - config: &ViewConfig, + appearance: &Appearance, ) { ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| { ui.scope(|ui| { @@ -158,7 +183,7 @@ fn symbol_list_ui( highlighted_symbol, selected_symbol, current_view, - config, + appearance, ); } }); @@ -168,7 +193,7 @@ fn symbol_list_ui( CollapsingHeader::new(format!("{} ({:x})", section.name, section.size)) .default_open(true) .show(ui, |ui| { - if section.kind == ObjSectionKind::Code && config.reverse_fn_order { + if section.kind == ObjSectionKind::Code && appearance.reverse_fn_order { for symbol in section.symbols.iter().rev() { if !symbol_matches_search(symbol, lower_search) { continue; @@ -180,7 +205,7 @@ fn symbol_list_ui( highlighted_symbol, selected_symbol, current_view, - config, + appearance, ); } } else { @@ -195,7 +220,7 @@ fn symbol_list_ui( highlighted_symbol, selected_symbol, current_view, - config, + appearance, ); } } @@ -205,25 +230,20 @@ fn symbol_list_ui( }); } -fn build_log_ui(ui: &mut Ui, status: &BuildStatus, config: &ViewConfig) { +fn build_log_ui(ui: &mut Ui, status: &BuildStatus, appearance: &Appearance) { ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| { ui.scope(|ui| { ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); ui.style_mut().wrap = Some(false); - ui.colored_label(config.replace_color, &status.log); + ui.colored_label(appearance.replace_color, &status.log); }); }); } -pub fn symbol_diff_ui(ui: &mut Ui, view_state: &mut ViewState) { - let (Some(result), highlighted_symbol, selected_symbol, current_view, search) = ( - &view_state.build, - &mut view_state.highlighted_symbol, - &mut view_state.selected_symbol, - &mut view_state.current_view, - &mut view_state.search, - ) else { +pub fn symbol_diff_ui(ui: &mut Ui, state: &mut DiffViewState, appearance: &Appearance) { + let DiffViewState { build, current_view, highlighted_symbol, selected_symbol, search } = state; + let Some(result) = build else { return; }; @@ -297,11 +317,11 @@ pub fn symbol_diff_ui(ui: &mut Ui, view_state: &mut ViewState) { selected_symbol, current_view, &lower_search, - &view_state.view_config, + appearance, ); } } else { - build_log_ui(ui, &result.first_status, &view_state.view_config); + build_log_ui(ui, &result.first_status, appearance); } }); }); @@ -316,11 +336,11 @@ pub fn symbol_diff_ui(ui: &mut Ui, view_state: &mut ViewState) { selected_symbol, current_view, &lower_search, - &view_state.view_config, + appearance, ); } } else { - build_log_ui(ui, &result.second_status, &view_state.view_config); + build_log_ui(ui, &result.second_status, appearance); } }); });