mirror of
https://github.com/encounter/objdiff.git
synced 2025-12-18 17:35:24 +00:00
Refactor state & config structs, various cleanup
This commit is contained in:
394
src/app.rs
394
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<String>,
|
||||
// pub mapped_symbols: HashMap<String, String>,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
#[serde(default)]
|
||||
pub struct ViewConfig {
|
||||
pub ui_font: FontId,
|
||||
pub code_font: FontId,
|
||||
pub diff_colors: Vec<Color32>,
|
||||
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<Box<ObjDiffResult>>,
|
||||
#[serde(skip)]
|
||||
pub highlighted_symbol: Option<String>,
|
||||
#[serde(skip)]
|
||||
pub selected_symbol: Option<SymbolReference>,
|
||||
#[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<Box<CheckUpdateResult>>,
|
||||
// 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<String>,
|
||||
// WSL2 settings
|
||||
#[serde(skip)]
|
||||
pub available_wsl_distros: Option<Vec<String>>,
|
||||
pub selected_wsl_distro: Option<String>,
|
||||
// Split obj
|
||||
pub project_dir: Option<PathBuf>,
|
||||
pub target_obj_dir: Option<PathBuf>,
|
||||
pub base_obj_dir: Option<PathBuf>,
|
||||
pub obj_path: Option<String>,
|
||||
pub build_target: bool,
|
||||
// Whole binary
|
||||
pub left_obj: Option<PathBuf>,
|
||||
pub right_obj: Option<PathBuf>,
|
||||
|
||||
#[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<Glob>,
|
||||
#[serde(skip)]
|
||||
pub load_error: Option<String>,
|
||||
|
||||
#[serde(skip)]
|
||||
pub units: Vec<ProjectUnit>,
|
||||
#[serde(skip)]
|
||||
pub unit_nodes: Vec<ProjectUnitNode>,
|
||||
#[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<RwLock<AppConfig>>,
|
||||
#[serde(skip)]
|
||||
modified: Arc<AtomicBool>,
|
||||
#[serde(skip)]
|
||||
config_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 {
|
||||
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::<Appearance>(storage, APPEARANCE_KEY) {
|
||||
app.appearance = appearance;
|
||||
}
|
||||
if let Some(mut config) = eframe::get_value::<AppConfig>(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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user