mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-18 01:15:36 +00:00
Version 0.2.0
- Update checker & auto-updater - Configure font sizes and diff colors - Data diffing bug fixes & improvements - Bug fix for low match percent - Improvements to Jobs UI (cancel, dismiss errors) - "Demangle" tool Closes #6, #13, #17, #19
This commit is contained in:
228
src/app.rs
228
src/app.rs
@@ -2,22 +2,23 @@ use std::{
|
||||
default::Default,
|
||||
ffi::OsStr,
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, RwLock,
|
||||
Arc, Mutex, RwLock,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use eframe::Frame;
|
||||
use egui::Widget;
|
||||
use egui::{Color32, FontFamily, FontId, TextStyle};
|
||||
use notify::{RecursiveMode, Watcher};
|
||||
use time::{OffsetDateTime, UtcOffset};
|
||||
|
||||
use crate::{
|
||||
jobs::{
|
||||
check_update::{queue_check_update, CheckUpdateResult},
|
||||
objdiff::{queue_build, BuildStatus, ObjDiffResult},
|
||||
Job, JobResult, JobState,
|
||||
Job, JobResult, JobState, JobStatus,
|
||||
},
|
||||
views::{
|
||||
config::config_ui, data_diff::data_diff_ui, function_diff::function_diff_ui, jobs::jobs_ui,
|
||||
@@ -48,6 +49,36 @@ pub struct DiffConfig {
|
||||
// pub mapped_symbols: HashMap<String, String>,
|
||||
}
|
||||
|
||||
const DEFAULT_COLOR_ROTATION: [Color32; 9] = [
|
||||
Color32::from_rgb(255, 0, 255),
|
||||
Color32::from_rgb(0, 255, 255),
|
||||
Color32::from_rgb(0, 128, 0),
|
||||
Color32::from_rgb(255, 0, 0),
|
||||
Color32::from_rgb(255, 255, 0),
|
||||
Color32::from_rgb(255, 192, 203),
|
||||
Color32::from_rgb(0, 0, 255),
|
||||
Color32::from_rgb(0, 255, 0),
|
||||
Color32::from_rgb(128, 128, 128),
|
||||
];
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct ViewConfig {
|
||||
pub ui_font: FontId,
|
||||
pub code_font: FontId,
|
||||
pub diff_colors: Vec<Color32>,
|
||||
}
|
||||
|
||||
impl Default for ViewConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
ui_font: FontId { size: 14.0, family: FontFamily::Proportional },
|
||||
code_font: FontId { size: 14.0, family: FontFamily::Monospace },
|
||||
diff_colors: DEFAULT_COLOR_ROTATION.to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct ViewState {
|
||||
@@ -64,14 +95,21 @@ pub struct ViewState {
|
||||
#[serde(skip)]
|
||||
pub show_config: bool,
|
||||
#[serde(skip)]
|
||||
pub show_demangle: bool,
|
||||
#[serde(skip)]
|
||||
pub demangle_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<Box<CheckUpdateResult>>,
|
||||
// Config
|
||||
pub diff_kind: DiffKind,
|
||||
pub reverse_fn_order: bool,
|
||||
pub view_config: ViewConfig,
|
||||
}
|
||||
|
||||
impl Default for ViewState {
|
||||
@@ -83,11 +121,15 @@ impl Default for ViewState {
|
||||
selected_symbol: None,
|
||||
current_view: Default::default(),
|
||||
show_config: false,
|
||||
show_demangle: false,
|
||||
demangle_text: String::new(),
|
||||
diff_config: Default::default(),
|
||||
search: Default::default(),
|
||||
utc_offset: UtcOffset::UTC,
|
||||
check_update: None,
|
||||
diff_kind: Default::default(),
|
||||
reverse_fn_order: false,
|
||||
view_config: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,7 +137,7 @@ impl Default for ViewState {
|
||||
#[derive(Default, Clone, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct AppConfig {
|
||||
pub custom_make: String,
|
||||
pub custom_make: Option<String>,
|
||||
// WSL2 settings
|
||||
#[serde(skip)]
|
||||
pub available_wsl_distros: Option<Vec<String>>,
|
||||
@@ -111,6 +153,19 @@ pub struct AppConfig {
|
||||
pub right_obj: Option<PathBuf>,
|
||||
#[serde(skip)]
|
||||
pub project_dir_change: bool,
|
||||
#[serde(skip)]
|
||||
pub queue_update_check: bool,
|
||||
pub auto_update_check: bool,
|
||||
}
|
||||
|
||||
#[derive(Default, Clone, serde::Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct ProjectConfig {
|
||||
pub custom_make: Option<String>,
|
||||
pub project_dir: Option<PathBuf>,
|
||||
pub target_obj_dir: Option<PathBuf>,
|
||||
pub base_obj_dir: Option<PathBuf>,
|
||||
pub build_target: bool,
|
||||
}
|
||||
|
||||
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
|
||||
@@ -124,6 +179,10 @@ pub struct App {
|
||||
modified: Arc<AtomicBool>,
|
||||
#[serde(skip)]
|
||||
watcher: Option<notify::RecommendedWatcher>,
|
||||
#[serde(skip)]
|
||||
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
|
||||
#[serde(skip)]
|
||||
should_relaunch: bool,
|
||||
}
|
||||
|
||||
impl Default for App {
|
||||
@@ -133,6 +192,8 @@ impl Default for App {
|
||||
config: Arc::new(Default::default()),
|
||||
modified: Arc::new(Default::default()),
|
||||
watcher: None,
|
||||
relaunch_path: Default::default(),
|
||||
should_relaunch: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,7 +202,11 @@ const CONFIG_KEY: &str = "app_config";
|
||||
|
||||
impl App {
|
||||
/// Called once before the first frame.
|
||||
pub fn new(cc: &eframe::CreationContext<'_>, utc_offset: UtcOffset) -> Self {
|
||||
pub fn new(
|
||||
cc: &eframe::CreationContext<'_>,
|
||||
utc_offset: UtcOffset,
|
||||
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
|
||||
) -> Self {
|
||||
// This is also where you can customized the look at feel of egui using
|
||||
// `cc.egui_ctx.set_visuals` and `cc.egui_ctx.set_fonts`.
|
||||
|
||||
@@ -153,11 +218,16 @@ impl App {
|
||||
if config.project_dir.is_some() {
|
||||
config.project_dir_change = true;
|
||||
}
|
||||
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 {
|
||||
Self::default()
|
||||
let mut app = Self::default();
|
||||
app.view_state.utc_offset = utc_offset;
|
||||
app.relaunch_path = relaunch_path;
|
||||
app
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -165,17 +235,44 @@ impl App {
|
||||
impl eframe::App for App {
|
||||
/// Called each time the UI needs repainting, which may be many times per second.
|
||||
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut Frame) {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
if self.should_relaunch {
|
||||
frame.close();
|
||||
return;
|
||||
}
|
||||
|
||||
let Self { config, view_state, .. } = self;
|
||||
|
||||
{
|
||||
let config = &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());
|
||||
ctx.set_style(style);
|
||||
}
|
||||
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("File", |ui| {
|
||||
if ui.button("Show config").clicked() {
|
||||
view_state.show_config = !view_state.show_config;
|
||||
}
|
||||
if ui.button("Quit").clicked() {
|
||||
frame.close();
|
||||
}
|
||||
if ui.button("Show config").clicked() {
|
||||
view_state.show_config = !view_state.show_config;
|
||||
});
|
||||
ui.menu_button("Tools", |ui| {
|
||||
if ui.button("Demangle").clicked() {
|
||||
view_state.show_demangle = !view_state.show_demangle;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -221,31 +318,55 @@ impl eframe::App for App {
|
||||
}
|
||||
|
||||
egui::Window::new("Config").open(&mut view_state.show_config).show(ctx, |ui| {
|
||||
ui.label("Diff type:");
|
||||
|
||||
if egui::RadioButton::new(
|
||||
view_state.diff_kind == DiffKind::SplitObj,
|
||||
"Split object diff",
|
||||
)
|
||||
.ui(ui)
|
||||
.on_hover_text("Compare individual object files")
|
||||
.clicked()
|
||||
{
|
||||
view_state.diff_kind = DiffKind::SplitObj;
|
||||
}
|
||||
|
||||
if egui::RadioButton::new(
|
||||
view_state.diff_kind == DiffKind::WholeBinary,
|
||||
"Whole binary diff",
|
||||
)
|
||||
.ui(ui)
|
||||
.on_hover_text("Compare two full binaries")
|
||||
.clicked()
|
||||
{
|
||||
view_state.diff_kind = DiffKind::WholeBinary;
|
||||
}
|
||||
|
||||
ui.label("UI font:");
|
||||
egui::introspection::font_id_ui(ui, &mut view_state.view_config.ui_font);
|
||||
ui.separator();
|
||||
ui.label("Code font:");
|
||||
egui::introspection::font_id_ui(ui, &mut view_state.view_config.code_font);
|
||||
ui.separator();
|
||||
ui.label("Diff colors:");
|
||||
if ui.button("Reset").clicked() {
|
||||
view_state.view_config.diff_colors = DEFAULT_COLOR_ROTATION.to_vec();
|
||||
}
|
||||
let mut remove_at: Option<usize> = None;
|
||||
let num_colors = view_state.view_config.diff_colors.len();
|
||||
for (idx, color) in view_state.view_config.diff_colors.iter_mut().enumerate() {
|
||||
ui.horizontal(|ui| {
|
||||
ui.color_edit_button_srgba(color);
|
||||
if num_colors > 1 {
|
||||
if ui.small_button("-").clicked() {
|
||||
remove_at = Some(idx);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Some(idx) = remove_at {
|
||||
view_state.view_config.diff_colors.remove(idx);
|
||||
}
|
||||
if ui.small_button("+").clicked() {
|
||||
view_state.view_config.diff_colors.push(Color32::BLACK);
|
||||
}
|
||||
});
|
||||
|
||||
egui::Window::new("Demangle").open(&mut view_state.show_demangle).show(ctx, |ui| {
|
||||
ui.text_edit_singleline(&mut view_state.demangle_text);
|
||||
ui.add_space(10.0);
|
||||
if let Some(demangled) =
|
||||
cwdemangle::demangle(&view_state.demangle_text, &Default::default())
|
||||
{
|
||||
ui.scope(|ui| {
|
||||
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
|
||||
ui.colored_label(Color32::LIGHT_BLUE, &demangled);
|
||||
});
|
||||
if ui.button("Copy").clicked() {
|
||||
ui.output().copied_text = demangled;
|
||||
}
|
||||
} else {
|
||||
ui.scope(|ui| {
|
||||
ui.style_mut().override_text_style = Some(TextStyle::Monospace);
|
||||
ui.colored_label(Color32::LIGHT_RED, "[invalid]");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Windows + request_repaint_after breaks dialogs:
|
||||
@@ -272,7 +393,7 @@ impl eframe::App for App {
|
||||
eframe::set_value(storage, eframe::APP_KEY, self);
|
||||
}
|
||||
|
||||
fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &Frame) {
|
||||
fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &eframe::Frame) {
|
||||
for job in &mut self.view_state.jobs {
|
||||
if let Some(handle) = &job.handle {
|
||||
if !handle.is_finished() {
|
||||
@@ -305,10 +426,38 @@ impl eframe::App for App {
|
||||
time: OffsetDateTime::now_utc(),
|
||||
}));
|
||||
}
|
||||
JobResult::CheckUpdate(state) => {
|
||||
self.view_state.check_update = Some(state);
|
||||
}
|
||||
JobResult::Update(state) => {
|
||||
if let Ok(mut guard) = self.relaunch_path.lock() {
|
||||
*guard = Some(state.exe_path);
|
||||
}
|
||||
self.should_relaunch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to join job handle: {:?}", e);
|
||||
Err(err) => {
|
||||
let err = if let Some(msg) = err.downcast_ref::<&'static str>() {
|
||||
anyhow::Error::msg(*msg)
|
||||
} else if let Some(msg) = err.downcast_ref::<String>() {
|
||||
anyhow::Error::msg(msg.clone())
|
||||
} else {
|
||||
anyhow::Error::msg("Thread panicked")
|
||||
};
|
||||
let result = job.status.write();
|
||||
if let Ok(mut guard) = result {
|
||||
guard.error = Some(err);
|
||||
} else {
|
||||
drop(result);
|
||||
job.status = Arc::new(RwLock::new(JobStatus {
|
||||
title: "Error".to_string(),
|
||||
progress_percent: 0.0,
|
||||
progress_items: None,
|
||||
status: "".to_string(),
|
||||
error: Some(err),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -355,6 +504,11 @@ impl eframe::App for App {
|
||||
}
|
||||
self.modified.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
if config.queue_update_check {
|
||||
self.view_state.jobs.push(queue_check_update());
|
||||
config.queue_update_check = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user