mirror of
				https://github.com/encounter/objdiff.git
				synced 2025-10-25 19:20:36 +00:00 
			
		
		
		
	Diff view refactor
This commit is contained in:
		
							parent
							
								
									3e6efb7736
								
							
						
					
					
						commit
						d938988d43
					
				| @ -18,4 +18,4 @@ authors = ["Luke Street <luke@street.dev>"] | ||||
| edition = "2021" | ||||
| license = "MIT OR Apache-2.0" | ||||
| repository = "https://github.com/encounter/objdiff" | ||||
| rust-version = "1.81" | ||||
| rust-version = "1.82" | ||||
|  | ||||
| @ -33,16 +33,14 @@ use crate::{ | ||||
|             arch_config_window, config_ui, general_config_ui, project_window, ConfigViewState, | ||||
|             CONFIG_DISABLED_TEXT, | ||||
|         }, | ||||
|         data_diff::data_diff_ui, | ||||
|         debug::debug_window, | ||||
|         demangle::{demangle_window, DemangleViewState}, | ||||
|         extab_diff::extab_diff_ui, | ||||
|         diff::diff_view_ui, | ||||
|         frame_history::FrameHistory, | ||||
|         function_diff::function_diff_ui, | ||||
|         graphics::{graphics_window, GraphicsConfig, GraphicsViewState}, | ||||
|         jobs::{jobs_menu_ui, jobs_window}, | ||||
|         rlwinm::{rlwinm_decode_window, RlwinmDecodeViewState}, | ||||
|         symbol_diff::{symbol_diff_ui, DiffViewAction, DiffViewNavigation, DiffViewState, View}, | ||||
|         symbol_diff::{DiffViewAction, DiffViewNavigation, DiffViewState, View}, | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| @ -753,16 +751,7 @@ impl eframe::App for App { | ||||
| 
 | ||||
|         let mut action = None; | ||||
|         egui::CentralPanel::default().show(ctx, |ui| { | ||||
|             let build_success = matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success); | ||||
|             action = if diff_state.current_view == View::FunctionDiff && build_success { | ||||
|                 function_diff_ui(ui, diff_state, appearance) | ||||
|             } else if diff_state.current_view == View::DataDiff && build_success { | ||||
|                 data_diff_ui(ui, diff_state, appearance) | ||||
|             } else if diff_state.current_view == View::ExtabDiff && build_success { | ||||
|                 extab_diff_ui(ui, diff_state, appearance) | ||||
|             } else { | ||||
|                 symbol_diff_ui(ui, diff_state, appearance) | ||||
|             }; | ||||
|             action = diff_view_ui(ui, diff_state, appearance); | ||||
|         }); | ||||
| 
 | ||||
|         project_window(ctx, state, show_project_config, config_state, appearance); | ||||
|  | ||||
| @ -4,28 +4,15 @@ use std::{ | ||||
|     mem::take, | ||||
| }; | ||||
| 
 | ||||
| use egui::{text::LayoutJob, Id, Label, RichText, Sense, Widget}; | ||||
| use egui::{text::LayoutJob, Label, Sense, Widget}; | ||||
| use objdiff_core::{ | ||||
|     diff::{ObjDataDiff, ObjDataDiffKind, ObjDataRelocDiff, ObjDiff}, | ||||
|     diff::{ObjDataDiff, ObjDataDiffKind, ObjDataRelocDiff}, | ||||
|     obj::ObjInfo, | ||||
| }; | ||||
| use time::format_description; | ||||
| 
 | ||||
| use crate::{ | ||||
|     hotkeys, | ||||
|     views::{ | ||||
|         appearance::Appearance, | ||||
|         column_layout::{render_header, render_table}, | ||||
|         symbol_diff::{DiffViewAction, DiffViewNavigation, DiffViewState}, | ||||
|         write_text, | ||||
|     }, | ||||
| }; | ||||
| use crate::views::{appearance::Appearance, write_text}; | ||||
| 
 | ||||
| const BYTES_PER_ROW: usize = 16; | ||||
| 
 | ||||
| fn find_section(obj: &ObjInfo, section_name: &str) -> Option<usize> { | ||||
|     obj.sections.iter().position(|section| section.name == section_name) | ||||
| } | ||||
| pub(crate) const BYTES_PER_ROW: usize = 16; | ||||
| 
 | ||||
| fn data_row_hover_ui( | ||||
|     ui: &mut egui::Ui, | ||||
| @ -122,7 +109,7 @@ fn get_color_for_diff_kind(diff_kind: ObjDataDiffKind, appearance: &Appearance) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn data_row_ui( | ||||
| pub(crate) fn data_row_ui( | ||||
|     ui: &mut egui::Ui, | ||||
|     obj: Option<&ObjInfo>, | ||||
|     address: usize, | ||||
| @ -212,7 +199,7 @@ fn data_row_ui( | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn split_diffs( | ||||
| pub(crate) fn split_diffs( | ||||
|     diffs: &[ObjDataDiff], | ||||
|     reloc_diffs: &[ObjDataRelocDiff], | ||||
| ) -> Vec<Vec<(ObjDataDiff, Vec<ObjDataRelocDiff>)>> { | ||||
| @ -273,169 +260,3 @@ fn split_diffs( | ||||
|     } | ||||
|     split_diffs | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Copy)] | ||||
| struct SectionDiffContext<'a> { | ||||
|     obj: &'a ObjInfo, | ||||
|     diff: &'a ObjDiff, | ||||
|     section_index: Option<usize>, | ||||
| } | ||||
| 
 | ||||
| impl<'a> SectionDiffContext<'a> { | ||||
|     pub fn new(obj: Option<&'a (ObjInfo, ObjDiff)>, section_name: Option<&str>) -> Option<Self> { | ||||
|         obj.map(|(obj, diff)| Self { | ||||
|             obj, | ||||
|             diff, | ||||
|             section_index: section_name.and_then(|section_name| find_section(obj, section_name)), | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     #[inline] | ||||
|     pub fn has_section(&self) -> bool { self.section_index.is_some() } | ||||
| } | ||||
| 
 | ||||
| fn data_table_ui( | ||||
|     ui: &mut egui::Ui, | ||||
|     available_width: f32, | ||||
|     left_ctx: Option<SectionDiffContext<'_>>, | ||||
|     right_ctx: Option<SectionDiffContext<'_>>, | ||||
|     config: &Appearance, | ||||
| ) -> Option<()> { | ||||
|     let left_obj = left_ctx.map(|ctx| ctx.obj); | ||||
|     let right_obj = right_ctx.map(|ctx| ctx.obj); | ||||
|     let left_section = left_ctx | ||||
|         .and_then(|ctx| ctx.section_index.map(|i| (&ctx.obj.sections[i], &ctx.diff.sections[i]))); | ||||
|     let right_section = right_ctx | ||||
|         .and_then(|ctx| ctx.section_index.map(|i| (&ctx.obj.sections[i], &ctx.diff.sections[i]))); | ||||
|     let total_bytes = left_section | ||||
|         .or(right_section)? | ||||
|         .1 | ||||
|         .data_diff | ||||
|         .iter() | ||||
|         .fold(0usize, |accum, item| accum + item.len); | ||||
|     if total_bytes == 0 { | ||||
|         return None; | ||||
|     } | ||||
|     let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1; | ||||
| 
 | ||||
|     let left_diffs = | ||||
|         left_section.map(|(_, section)| split_diffs(§ion.data_diff, §ion.reloc_diff)); | ||||
|     let right_diffs = | ||||
|         right_section.map(|(_, section)| split_diffs(§ion.data_diff, §ion.reloc_diff)); | ||||
| 
 | ||||
|     hotkeys::check_scroll_hotkeys(ui, true); | ||||
| 
 | ||||
|     render_table(ui, available_width, 2, config.code_font.size, total_rows, |row, column| { | ||||
|         let i = row.index(); | ||||
|         let address = i * BYTES_PER_ROW; | ||||
|         row.col(|ui| { | ||||
|             if column == 0 { | ||||
|                 if let Some(left_diffs) = &left_diffs { | ||||
|                     data_row_ui(ui, left_obj, address, &left_diffs[i], config); | ||||
|                 } | ||||
|             } else if column == 1 { | ||||
|                 if let Some(right_diffs) = &right_diffs { | ||||
|                     data_row_ui(ui, right_obj, address, &right_diffs[i], config); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
|     Some(()) | ||||
| } | ||||
| 
 | ||||
| #[must_use] | ||||
| pub fn data_diff_ui( | ||||
|     ui: &mut egui::Ui, | ||||
|     state: &DiffViewState, | ||||
|     appearance: &Appearance, | ||||
| ) -> Option<DiffViewAction> { | ||||
|     let mut ret = None; | ||||
|     let Some(result) = &state.build else { | ||||
|         return ret; | ||||
|     }; | ||||
| 
 | ||||
|     let section_name = | ||||
|         state.symbol_state.left_symbol.as_ref().and_then(|s| s.section_name.as_deref()).or_else( | ||||
|             || state.symbol_state.right_symbol.as_ref().and_then(|s| s.section_name.as_deref()), | ||||
|         ); | ||||
|     let left_ctx = SectionDiffContext::new(result.first_obj.as_ref(), section_name); | ||||
|     let right_ctx = SectionDiffContext::new(result.second_obj.as_ref(), section_name); | ||||
| 
 | ||||
|     // If both sides are missing a symbol, switch to symbol diff view
 | ||||
|     if !right_ctx.is_some_and(|ctx| ctx.has_section()) | ||||
|         && !left_ctx.is_some_and(|ctx| ctx.has_section()) | ||||
|     { | ||||
|         return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); | ||||
|     } | ||||
| 
 | ||||
|     // Header
 | ||||
|     let available_width = ui.available_width(); | ||||
|     render_header(ui, available_width, 2, |ui, column| { | ||||
|         if column == 0 { | ||||
|             // Left column
 | ||||
|             if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) { | ||||
|                 ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); | ||||
|             } | ||||
| 
 | ||||
|             if let Some(section) = | ||||
|                 left_ctx.and_then(|ctx| ctx.section_index.map(|i| &ctx.obj.sections[i])) | ||||
|             { | ||||
|                 ui.label( | ||||
|                     RichText::new(section.name.clone()) | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.highlight_color), | ||||
|                 ); | ||||
|             } else { | ||||
|                 ui.label( | ||||
|                     RichText::new("Missing") | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.replace_color), | ||||
|                 ); | ||||
|             } | ||||
|         } else if column == 1 { | ||||
|             // Right column
 | ||||
|             ui.horizontal(|ui| { | ||||
|                 if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() { | ||||
|                     ret = Some(DiffViewAction::Build); | ||||
|                 } | ||||
|                 ui.scope(|ui| { | ||||
|                     ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); | ||||
|                     if state.build_running { | ||||
|                         ui.colored_label(appearance.replace_color, "Building…"); | ||||
|                     } else { | ||||
|                         ui.label("Last built:"); | ||||
|                         let format = format_description::parse("[hour]:[minute]:[second]").unwrap(); | ||||
|                         ui.label( | ||||
|                             result.time.to_offset(appearance.utc_offset).format(&format).unwrap(), | ||||
|                         ); | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
| 
 | ||||
|             if let Some(section) = | ||||
|                 right_ctx.and_then(|ctx| ctx.section_index.map(|i| &ctx.obj.sections[i])) | ||||
|             { | ||||
|                 ui.label( | ||||
|                     RichText::new(section.name.clone()) | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.highlight_color), | ||||
|                 ); | ||||
|             } else { | ||||
|                 ui.label( | ||||
|                     RichText::new("Missing") | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.replace_color), | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     // Table
 | ||||
|     let id = | ||||
|         Id::new(state.symbol_state.left_symbol.as_ref().and_then(|s| s.section_name.as_deref())) | ||||
|             .with(state.symbol_state.right_symbol.as_ref().and_then(|s| s.section_name.as_deref())); | ||||
|     ui.push_id(id, |ui| { | ||||
|         data_table_ui(ui, available_width, left_ctx, right_ctx, appearance); | ||||
|     }); | ||||
|     ret | ||||
| } | ||||
|  | ||||
							
								
								
									
										741
									
								
								objdiff-gui/src/views/diff.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										741
									
								
								objdiff-gui/src/views/diff.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,741 @@ | ||||
| use egui::{Id, Layout, RichText, ScrollArea, TextEdit, Ui, Widget}; | ||||
| use objdiff_core::{ | ||||
|     build::BuildStatus, | ||||
|     diff::{ObjDiff, ObjSectionDiff, ObjSymbolDiff}, | ||||
|     obj::{ObjInfo, ObjSection, ObjSectionKind, ObjSymbol, SymbolRef}, | ||||
| }; | ||||
| use time::format_description; | ||||
| 
 | ||||
| use crate::{ | ||||
|     hotkeys, | ||||
|     views::{ | ||||
|         appearance::Appearance, | ||||
|         column_layout::{render_header, render_strips, render_table}, | ||||
|         data_diff::{data_row_ui, split_diffs, BYTES_PER_ROW}, | ||||
|         extab_diff::extab_ui, | ||||
|         function_diff::{asm_col_ui, FunctionDiffContext}, | ||||
|         symbol_diff::{ | ||||
|             match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation, | ||||
|             DiffViewState, SymbolDiffContext, SymbolFilter, SymbolRefByName, View, | ||||
|         }, | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| #[derive(Clone, Copy)] | ||||
| enum SelectedSymbol { | ||||
|     Symbol(SymbolRef), | ||||
|     Section(usize), | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Copy)] | ||||
| struct DiffColumnContext<'a> { | ||||
|     status: &'a BuildStatus, | ||||
|     obj: Option<&'a (ObjInfo, ObjDiff)>, | ||||
|     section: Option<(&'a ObjSection, &'a ObjSectionDiff)>, | ||||
|     symbol: Option<(&'a ObjSymbol, &'a ObjSymbolDiff)>, | ||||
| } | ||||
| 
 | ||||
| impl<'a> DiffColumnContext<'a> { | ||||
|     pub fn new( | ||||
|         view: View, | ||||
|         status: &'a BuildStatus, | ||||
|         obj: Option<&'a (ObjInfo, ObjDiff)>, | ||||
|         selected_symbol: Option<&SymbolRefByName>, | ||||
|     ) -> Self { | ||||
|         let selected_symbol = match view { | ||||
|             View::SymbolDiff => None, | ||||
|             View::FunctionDiff | View::ExtabDiff => match (obj, selected_symbol) { | ||||
|                 (Some(obj), Some(s)) => find_symbol(&obj.0, s).map(SelectedSymbol::Symbol), | ||||
|                 _ => None, | ||||
|             }, | ||||
|             View::DataDiff => match (obj, selected_symbol) { | ||||
|                 (Some(obj), Some(SymbolRefByName { section_name: Some(section_name), .. })) => { | ||||
|                     find_section(&obj.0, section_name).map(SelectedSymbol::Section) | ||||
|                 } | ||||
|                 _ => None, | ||||
|             }, | ||||
|         }; | ||||
|         let (section, symbol) = match (obj, selected_symbol) { | ||||
|             (Some((obj, obj_diff)), Some(SelectedSymbol::Symbol(symbol_ref))) => { | ||||
|                 let (section, symbol) = obj.section_symbol(symbol_ref); | ||||
|                 ( | ||||
|                     section.map(|s| (s, obj_diff.section_diff(symbol_ref.section_idx))), | ||||
|                     Some((symbol, obj_diff.symbol_diff(symbol_ref))), | ||||
|                 ) | ||||
|             } | ||||
|             (Some((obj, obj_diff)), Some(SelectedSymbol::Section(section_idx))) => { | ||||
|                 (Some((&obj.sections[section_idx], obj_diff.section_diff(section_idx))), None) | ||||
|             } | ||||
|             _ => (None, None), | ||||
|         }; | ||||
|         Self { status, obj, section, symbol } | ||||
|     } | ||||
| 
 | ||||
|     #[inline] | ||||
|     pub fn has_symbol(&self) -> bool { self.section.is_some() || self.symbol.is_some() } | ||||
| 
 | ||||
|     #[inline] | ||||
|     pub fn id(&self) -> Option<&str> { | ||||
|         self.symbol | ||||
|             .map(|(symbol, _)| symbol.name.as_str()) | ||||
|             .or_else(|| self.section.map(|(section, _)| section.name.as_str())) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[must_use] | ||||
| pub fn diff_view_ui( | ||||
|     ui: &mut Ui, | ||||
|     state: &DiffViewState, | ||||
|     appearance: &Appearance, | ||||
| ) -> Option<DiffViewAction> { | ||||
|     let mut ret = None; | ||||
|     let Some(result) = &state.build else { | ||||
|         return ret; | ||||
|     }; | ||||
| 
 | ||||
|     let left_ctx = DiffColumnContext::new( | ||||
|         state.current_view, | ||||
|         &result.first_status, | ||||
|         result.first_obj.as_ref(), | ||||
|         state.symbol_state.left_symbol.as_ref(), | ||||
|     ); | ||||
|     let right_ctx = DiffColumnContext::new( | ||||
|         state.current_view, | ||||
|         &result.second_status, | ||||
|         result.second_obj.as_ref(), | ||||
|         state.symbol_state.right_symbol.as_ref(), | ||||
|     ); | ||||
| 
 | ||||
|     // Check if we need to perform any navigation
 | ||||
|     let current_navigation = DiffViewNavigation { | ||||
|         view: state.current_view, | ||||
|         left_symbol: state.symbol_state.left_symbol.clone(), | ||||
|         right_symbol: state.symbol_state.right_symbol.clone(), | ||||
|     }; | ||||
|     let mut navigation = current_navigation.clone(); | ||||
|     if let Some((_symbol, symbol_diff)) = left_ctx.symbol { | ||||
|         // If a matching symbol appears, select it
 | ||||
|         if !right_ctx.has_symbol() { | ||||
|             if let Some(target_symbol_ref) = symbol_diff.target_symbol { | ||||
|                 let (target_section, target_symbol) = | ||||
|                     right_ctx.obj.unwrap().0.section_symbol(target_symbol_ref); | ||||
|                 navigation.right_symbol = Some(SymbolRefByName::new(target_symbol, target_section)); | ||||
|             } | ||||
|         } | ||||
|     } else if navigation.left_symbol.is_some() | ||||
|         && left_ctx.obj.is_some() | ||||
|         && left_ctx.section.is_none() | ||||
|     { | ||||
|         // Clear selection if symbol goes missing
 | ||||
|         navigation.left_symbol = None; | ||||
|     } | ||||
|     if let Some((_symbol, symbol_diff)) = right_ctx.symbol { | ||||
|         // If a matching symbol appears, select it
 | ||||
|         if !left_ctx.has_symbol() { | ||||
|             if let Some(target_symbol_ref) = symbol_diff.target_symbol { | ||||
|                 let (target_section, target_symbol) = | ||||
|                     left_ctx.obj.unwrap().0.section_symbol(target_symbol_ref); | ||||
|                 navigation.left_symbol = Some(SymbolRefByName::new(target_symbol, target_section)); | ||||
|             } | ||||
|         } | ||||
|     } else if navigation.right_symbol.is_some() | ||||
|         && right_ctx.obj.is_some() | ||||
|         && right_ctx.section.is_none() | ||||
|     { | ||||
|         // Clear selection if symbol goes missing
 | ||||
|         navigation.right_symbol = None; | ||||
|     } | ||||
|     // If both sides are missing a symbol, switch to symbol diff view
 | ||||
|     if navigation.left_symbol.is_none() && navigation.right_symbol.is_none() { | ||||
|         navigation.view = View::SymbolDiff; | ||||
|     } | ||||
|     // Execute navigation if it changed
 | ||||
|     if navigation != current_navigation && !state.post_build_nav.is_some() { | ||||
|         ret = Some(DiffViewAction::Navigate(navigation)); | ||||
|     } | ||||
| 
 | ||||
|     let available_width = ui.available_width(); | ||||
|     let mut open_sections = (None, None); | ||||
| 
 | ||||
|     render_header(ui, available_width, 2, |ui, column| { | ||||
|         if column == 0 { | ||||
|             // Left column
 | ||||
| 
 | ||||
|             // First row
 | ||||
|             if state.current_view == View::SymbolDiff { | ||||
|                 ui.label(RichText::new("Target object").text_style(egui::TextStyle::Monospace)); | ||||
|             } else { | ||||
|                 ui.horizontal(|ui| { | ||||
|                     if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) { | ||||
|                         ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); | ||||
|                     } | ||||
| 
 | ||||
|                     if let Some((symbol, _)) = left_ctx.symbol { | ||||
|                         ui.separator(); | ||||
|                         if ui | ||||
|                             .add_enabled( | ||||
|                                 !state.scratch_running | ||||
|                                     && state.scratch_available | ||||
|                                     && left_ctx.has_symbol(), | ||||
|                                 egui::Button::new("📲 decomp.me"), | ||||
|                             ) | ||||
|                             .on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)") | ||||
|                             .on_disabled_hover_text("Scratch configuration missing") | ||||
|                             .clicked() | ||||
|                         { | ||||
|                             ret = Some(DiffViewAction::CreateScratch(symbol.name.clone())); | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             // Second row
 | ||||
|             if !left_ctx.status.success { | ||||
|                 ui.label( | ||||
|                     RichText::new("Fail") | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.delete_color), | ||||
|                 ); | ||||
|             } else if state.current_view == View::SymbolDiff { | ||||
|                 if left_ctx.obj.is_some() { | ||||
|                     ui.label( | ||||
|                         RichText::new(state.object_name.clone()) | ||||
|                             .font(appearance.code_font.clone()) | ||||
|                             .color(appearance.highlight_color), | ||||
|                     ); | ||||
|                 } else { | ||||
|                     ui.label( | ||||
|                         RichText::new("Missing") | ||||
|                             .font(appearance.code_font.clone()) | ||||
|                             .color(appearance.replace_color), | ||||
|                     ); | ||||
|                 } | ||||
|             } else if let Some((symbol, _)) = left_ctx.symbol { | ||||
|                 ui.label( | ||||
|                     RichText::new(symbol.demangled_name.as_deref().unwrap_or(&symbol.name)) | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.highlight_color), | ||||
|                 ); | ||||
|             } else if let Some((section, _)) = left_ctx.section { | ||||
|                 ui.label( | ||||
|                     RichText::new(section.name.clone()) | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.highlight_color), | ||||
|                 ); | ||||
|             } else if right_ctx.has_symbol() { | ||||
|                 ui.label( | ||||
|                     RichText::new("Choose target symbol") | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.replace_color), | ||||
|                 ); | ||||
|             } else { | ||||
|                 ui.label( | ||||
|                     RichText::new("Missing") | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.replace_color), | ||||
|                 ); | ||||
|             } | ||||
| 
 | ||||
|             // Third row
 | ||||
|             if left_ctx.has_symbol() && right_ctx.has_symbol() { | ||||
|                 if state.current_view == View::FunctionDiff | ||||
|                     && ui | ||||
|                         .button("Change target") | ||||
|                         .on_hover_text_at_pointer("Choose a different symbol to use as the target") | ||||
|                         .clicked() | ||||
|                     || hotkeys::consume_change_target_shortcut(ui.ctx()) | ||||
|                 { | ||||
|                     if let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref() { | ||||
|                         ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone())); | ||||
|                     } | ||||
|                 } | ||||
|             } else if left_ctx.status.success && !left_ctx.has_symbol() { | ||||
|                 ui.horizontal(|ui| { | ||||
|                     let mut search = state.search.clone(); | ||||
|                     let response = | ||||
|                         TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui); | ||||
|                     if hotkeys::consume_symbol_filter_shortcut(ui.ctx()) { | ||||
|                         response.request_focus(); | ||||
|                     } | ||||
|                     if response.changed() { | ||||
|                         ret = Some(DiffViewAction::SetSearch(search)); | ||||
|                     } | ||||
| 
 | ||||
|                     ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| { | ||||
|                         if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() { | ||||
|                             open_sections.0 = Some(true); | ||||
|                         } | ||||
|                         if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked() | ||||
|                         { | ||||
|                             open_sections.0 = Some(false); | ||||
|                         } | ||||
|                     }) | ||||
|                 }); | ||||
|             } | ||||
|         } else if column == 1 { | ||||
|             // Right column
 | ||||
| 
 | ||||
|             // First row
 | ||||
|             ui.horizontal(|ui| { | ||||
|                 if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() { | ||||
|                     ret = Some(DiffViewAction::Build); | ||||
|                 } | ||||
|                 if state.build_running { | ||||
|                     ui.colored_label( | ||||
|                         appearance.replace_color, | ||||
|                         RichText::new("Building…").text_style(egui::TextStyle::Monospace), | ||||
|                     ); | ||||
|                 } else { | ||||
|                     ui.label(RichText::new("Last built:").text_style(egui::TextStyle::Monospace)); | ||||
|                     let format = format_description::parse("[hour]:[minute]:[second]").unwrap(); | ||||
|                     ui.label( | ||||
|                         RichText::new( | ||||
|                             result.time.to_offset(appearance.utc_offset).format(&format).unwrap(), | ||||
|                         ) | ||||
|                         .text_style(egui::TextStyle::Monospace), | ||||
|                     ); | ||||
|                 } | ||||
|                 ui.separator(); | ||||
|                 if ui | ||||
|                     .add_enabled(state.source_path_available, egui::Button::new("🖹 Source file")) | ||||
|                     .on_hover_text_at_pointer("Open the source file in the default editor") | ||||
|                     .on_disabled_hover_text("Source file metadata missing") | ||||
|                     .clicked() | ||||
|                 { | ||||
|                     ret = Some(DiffViewAction::OpenSourcePath); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             // Second row
 | ||||
|             if !right_ctx.status.success { | ||||
|                 ui.label( | ||||
|                     RichText::new("Fail") | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.delete_color), | ||||
|                 ); | ||||
|             } else if state.current_view == View::SymbolDiff { | ||||
|                 if right_ctx.obj.is_some() { | ||||
|                     if left_ctx.obj.is_some() { | ||||
|                         ui.label(RichText::new("Base object").font(appearance.code_font.clone())); | ||||
|                     } else { | ||||
|                         ui.label( | ||||
|                             RichText::new(state.object_name.clone()) | ||||
|                                 .font(appearance.code_font.clone()) | ||||
|                                 .color(appearance.highlight_color), | ||||
|                         ); | ||||
|                     } | ||||
|                 } else { | ||||
|                     ui.label( | ||||
|                         RichText::new("Missing") | ||||
|                             .font(appearance.code_font.clone()) | ||||
|                             .color(appearance.replace_color), | ||||
|                     ); | ||||
|                 } | ||||
|             } else if let Some((symbol, _)) = right_ctx.symbol { | ||||
|                 ui.label( | ||||
|                     RichText::new(symbol.demangled_name.as_deref().unwrap_or(&symbol.name)) | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.highlight_color), | ||||
|                 ); | ||||
|             } else if let Some((section, _)) = right_ctx.section { | ||||
|                 ui.label( | ||||
|                     RichText::new(section.name.clone()) | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.highlight_color), | ||||
|                 ); | ||||
|             } else if left_ctx.has_symbol() { | ||||
|                 ui.label( | ||||
|                     RichText::new("Choose base symbol") | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.replace_color), | ||||
|                 ); | ||||
|             } else { | ||||
|                 ui.label( | ||||
|                     RichText::new("Missing") | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.replace_color), | ||||
|                 ); | ||||
|             } | ||||
| 
 | ||||
|             // Third row
 | ||||
|             ui.horizontal(|ui| { | ||||
|                 if let Some((_, symbol_diff)) = right_ctx.symbol { | ||||
|                     if let Some(match_percent) = symbol_diff.match_percent { | ||||
|                         ui.label( | ||||
|                             RichText::new(format!("{:.0}%", match_percent.floor())) | ||||
|                                 .font(appearance.code_font.clone()) | ||||
|                                 .color(match_color_for_symbol(match_percent, appearance)), | ||||
|                         ); | ||||
|                     } | ||||
|                     if state.current_view == View::FunctionDiff && left_ctx.has_symbol() { | ||||
|                         ui.separator(); | ||||
|                         if ui | ||||
|                             .button("Change base") | ||||
|                             .on_hover_text_at_pointer( | ||||
|                                 "Choose a different symbol to use as the base", | ||||
|                             ) | ||||
|                             .clicked() | ||||
|                             || hotkeys::consume_change_base_shortcut(ui.ctx()) | ||||
|                         { | ||||
|                             if let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref() { | ||||
|                                 ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone())); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } else if right_ctx.status.success && !right_ctx.has_symbol() { | ||||
|                     let mut search = state.search.clone(); | ||||
|                     let response = | ||||
|                         TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui); | ||||
|                     if hotkeys::consume_symbol_filter_shortcut(ui.ctx()) { | ||||
|                         response.request_focus(); | ||||
|                     } | ||||
|                     if response.changed() { | ||||
|                         ret = Some(DiffViewAction::SetSearch(search)); | ||||
|                     } | ||||
| 
 | ||||
|                     ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| { | ||||
|                         if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() { | ||||
|                             open_sections.1 = Some(true); | ||||
|                         } | ||||
|                         if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked() | ||||
|                         { | ||||
|                             open_sections.1 = Some(false); | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     // Table
 | ||||
|     ui.push_id(Id::new(left_ctx.id()).with(right_ctx.id()), |ui| { | ||||
|         if let ( | ||||
|             View::FunctionDiff, | ||||
|             Some((left_obj, left_diff)), | ||||
|             Some((right_obj, right_diff)), | ||||
|             Some((_, left_symbol_diff)), | ||||
|             Some((_, right_symbol_diff)), | ||||
|         ) = (state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.symbol, right_ctx.symbol) | ||||
|         { | ||||
|             // Joint diff view
 | ||||
|             hotkeys::check_scroll_hotkeys(ui, true); | ||||
|             if left_symbol_diff.instructions.len() != right_symbol_diff.instructions.len() { | ||||
|                 ui.label("Instruction count mismatch"); | ||||
|                 return; | ||||
|             } | ||||
|             let instructions_len = left_symbol_diff.instructions.len(); | ||||
|             render_table( | ||||
|                 ui, | ||||
|                 available_width, | ||||
|                 2, | ||||
|                 appearance.code_font.size, | ||||
|                 instructions_len, | ||||
|                 |row, column| { | ||||
|                     if column == 0 { | ||||
|                         if let Some(action) = asm_col_ui( | ||||
|                             row, | ||||
|                             FunctionDiffContext { | ||||
|                                 obj: left_obj, | ||||
|                                 diff: left_diff, | ||||
|                                 symbol_ref: Some(left_symbol_diff.symbol_ref), | ||||
|                             }, | ||||
|                             appearance, | ||||
|                             &state.function_state, | ||||
|                             column, | ||||
|                         ) { | ||||
|                             ret = Some(action); | ||||
|                         } | ||||
|                     } else if column == 1 { | ||||
|                         if let Some(action) = asm_col_ui( | ||||
|                             row, | ||||
|                             FunctionDiffContext { | ||||
|                                 obj: right_obj, | ||||
|                                 diff: right_diff, | ||||
|                                 symbol_ref: Some(right_symbol_diff.symbol_ref), | ||||
|                             }, | ||||
|                             appearance, | ||||
|                             &state.function_state, | ||||
|                             column, | ||||
|                         ) { | ||||
|                             ret = Some(action); | ||||
|                         } | ||||
|                         if row.response().clicked() { | ||||
|                             ret = Some(DiffViewAction::ClearDiffHighlight); | ||||
|                         } | ||||
|                     } | ||||
|                 }, | ||||
|             ); | ||||
|         } else if let ( | ||||
|             View::DataDiff, | ||||
|             Some((left_obj, _left_diff)), | ||||
|             Some((right_obj, _right_diff)), | ||||
|             Some((_left_section, left_section_diff)), | ||||
|             Some((_right_section, right_section_diff)), | ||||
|         ) = | ||||
|             (state.current_view, left_ctx.obj, right_ctx.obj, left_ctx.section, right_ctx.section) | ||||
|         { | ||||
|             // Joint diff view
 | ||||
|             let left_total_bytes = | ||||
|                 left_section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len); | ||||
|             let right_total_bytes = | ||||
|                 right_section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len); | ||||
|             if left_total_bytes != right_total_bytes { | ||||
|                 ui.label("Data size mismatch"); | ||||
|                 return; | ||||
|             } | ||||
|             if left_total_bytes == 0 { | ||||
|                 return; | ||||
|             } | ||||
|             let total_rows = (left_total_bytes - 1) / BYTES_PER_ROW + 1; | ||||
|             let left_diffs = | ||||
|                 split_diffs(&left_section_diff.data_diff, &left_section_diff.reloc_diff); | ||||
|             let right_diffs = | ||||
|                 split_diffs(&right_section_diff.data_diff, &right_section_diff.reloc_diff); | ||||
|             render_table( | ||||
|                 ui, | ||||
|                 available_width, | ||||
|                 2, | ||||
|                 appearance.code_font.size, | ||||
|                 total_rows, | ||||
|                 |row, column| { | ||||
|                     let i = row.index(); | ||||
|                     let address = i * BYTES_PER_ROW; | ||||
|                     row.col(|ui| { | ||||
|                         if column == 0 { | ||||
|                             data_row_ui(ui, Some(left_obj), address, &left_diffs[i], appearance); | ||||
|                         } else if column == 1 { | ||||
|                             data_row_ui(ui, Some(right_obj), address, &right_diffs[i], appearance); | ||||
|                         } | ||||
|                     }); | ||||
|                 }, | ||||
|             ); | ||||
|         } else { | ||||
|             // Split view
 | ||||
|             render_strips(ui, available_width, 2, |ui, column| { | ||||
|                 if column == 0 { | ||||
|                     if let Some(action) = diff_col_ui( | ||||
|                         ui, | ||||
|                         state, | ||||
|                         appearance, | ||||
|                         column, | ||||
|                         left_ctx, | ||||
|                         right_ctx, | ||||
|                         available_width, | ||||
|                         open_sections.0, | ||||
|                     ) { | ||||
|                         ret = Some(action); | ||||
|                     } | ||||
|                 } else if column == 1 { | ||||
|                     if let Some(action) = diff_col_ui( | ||||
|                         ui, | ||||
|                         state, | ||||
|                         appearance, | ||||
|                         column, | ||||
|                         right_ctx, | ||||
|                         left_ctx, | ||||
|                         available_width, | ||||
|                         open_sections.1, | ||||
|                     ) { | ||||
|                         ret = Some(action); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     ret | ||||
| } | ||||
| 
 | ||||
| #[must_use] | ||||
| #[allow(clippy::too_many_arguments)] | ||||
| fn diff_col_ui( | ||||
|     ui: &mut Ui, | ||||
|     state: &DiffViewState, | ||||
|     appearance: &Appearance, | ||||
|     column: usize, | ||||
|     ctx: DiffColumnContext, | ||||
|     other_ctx: DiffColumnContext, | ||||
|     available_width: f32, | ||||
|     open_sections: Option<bool>, | ||||
| ) -> Option<DiffViewAction> { | ||||
|     let mut ret = None; | ||||
|     if !ctx.status.success { | ||||
|         build_log_ui(ui, ctx.status, appearance); | ||||
|     } else if let Some((obj, diff)) = ctx.obj { | ||||
|         if let Some((_symbol, symbol_diff)) = ctx.symbol { | ||||
|             hotkeys::check_scroll_hotkeys(ui, false); | ||||
|             let ctx = FunctionDiffContext { obj, diff, symbol_ref: Some(symbol_diff.symbol_ref) }; | ||||
|             if state.current_view == View::ExtabDiff { | ||||
|                 extab_ui(ui, ctx, appearance, column); | ||||
|             } else { | ||||
|                 render_table( | ||||
|                     ui, | ||||
|                     available_width / 2.0, | ||||
|                     1, | ||||
|                     appearance.code_font.size, | ||||
|                     symbol_diff.instructions.len(), | ||||
|                     |row, column| { | ||||
|                         if let Some(action) = | ||||
|                             asm_col_ui(row, ctx, appearance, &state.function_state, column) | ||||
|                         { | ||||
|                             ret = Some(action); | ||||
|                         } | ||||
|                         if row.response().clicked() { | ||||
|                             ret = Some(DiffViewAction::ClearDiffHighlight); | ||||
|                         } | ||||
|                     }, | ||||
|                 ); | ||||
|             } | ||||
|         } else if let Some((_section, section_diff)) = ctx.section { | ||||
|             hotkeys::check_scroll_hotkeys(ui, false); | ||||
|             let total_bytes = | ||||
|                 section_diff.data_diff.iter().fold(0usize, |accum, item| accum + item.len); | ||||
|             if total_bytes == 0 { | ||||
|                 return ret; | ||||
|             } | ||||
|             let total_rows = (total_bytes - 1) / BYTES_PER_ROW + 1; | ||||
|             let diffs = split_diffs(§ion_diff.data_diff, §ion_diff.reloc_diff); | ||||
|             render_table( | ||||
|                 ui, | ||||
|                 available_width / 2.0, | ||||
|                 1, | ||||
|                 appearance.code_font.size, | ||||
|                 total_rows, | ||||
|                 |row, _column| { | ||||
|                     let i = row.index(); | ||||
|                     let address = i * BYTES_PER_ROW; | ||||
|                     row.col(|ui| { | ||||
|                         data_row_ui(ui, Some(obj), address, &diffs[i], appearance); | ||||
|                     }); | ||||
|                 }, | ||||
|             ); | ||||
|         } else if let ( | ||||
|             Some((other_section, _other_section_diff)), | ||||
|             Some((other_symbol, other_symbol_diff)), | ||||
|         ) = (other_ctx.section, other_ctx.symbol) | ||||
|         { | ||||
|             if let Some(action) = symbol_list_ui( | ||||
|                 ui, | ||||
|                 SymbolDiffContext { obj, diff }, | ||||
|                 None, | ||||
|                 &state.symbol_state, | ||||
|                 SymbolFilter::Mapping(other_symbol_diff.symbol_ref, None), | ||||
|                 appearance, | ||||
|                 column, | ||||
|                 open_sections, | ||||
|             ) { | ||||
|                 match (column, action) { | ||||
|                     ( | ||||
|                         0, | ||||
|                         DiffViewAction::Navigate(DiffViewNavigation { | ||||
|                             left_symbol: Some(left_symbol_ref), | ||||
|                             .. | ||||
|                         }), | ||||
|                     ) => { | ||||
|                         ret = Some(DiffViewAction::SetMapping( | ||||
|                             match other_section.kind { | ||||
|                                 ObjSectionKind::Code => View::FunctionDiff, | ||||
|                                 _ => View::SymbolDiff, | ||||
|                             }, | ||||
|                             left_symbol_ref, | ||||
|                             SymbolRefByName::new(other_symbol, Some(other_section)), | ||||
|                         )); | ||||
|                     } | ||||
|                     ( | ||||
|                         1, | ||||
|                         DiffViewAction::Navigate(DiffViewNavigation { | ||||
|                             right_symbol: Some(right_symbol_ref), | ||||
|                             .. | ||||
|                         }), | ||||
|                     ) => { | ||||
|                         ret = Some(DiffViewAction::SetMapping( | ||||
|                             match other_section.kind { | ||||
|                                 ObjSectionKind::Code => View::FunctionDiff, | ||||
|                                 _ => View::SymbolDiff, | ||||
|                             }, | ||||
|                             SymbolRefByName::new(other_symbol, Some(other_section)), | ||||
|                             right_symbol_ref, | ||||
|                         )); | ||||
|                     } | ||||
|                     (_, action) => { | ||||
|                         ret = Some(action); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             let filter = match &state.search_regex { | ||||
|                 Some(regex) => SymbolFilter::Search(regex), | ||||
|                 _ => SymbolFilter::None, | ||||
|             }; | ||||
|             if let Some(result) = symbol_list_ui( | ||||
|                 ui, | ||||
|                 SymbolDiffContext { obj, diff }, | ||||
|                 other_ctx.obj.map(|(obj, diff)| SymbolDiffContext { obj, diff }), | ||||
|                 &state.symbol_state, | ||||
|                 filter, | ||||
|                 appearance, | ||||
|                 column, | ||||
|                 open_sections, | ||||
|             ) { | ||||
|                 ret = Some(result); | ||||
|             } | ||||
|         } | ||||
|     } else { | ||||
|         missing_obj_ui(ui, appearance); | ||||
|     } | ||||
| 
 | ||||
|     ret | ||||
| } | ||||
| 
 | ||||
| fn build_log_ui(ui: &mut Ui, status: &BuildStatus, appearance: &Appearance) { | ||||
|     ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| { | ||||
|         ui.horizontal(|ui| { | ||||
|             if !status.cmdline.is_empty() && ui.button("Copy command").clicked() { | ||||
|                 ui.output_mut(|output| output.copied_text.clone_from(&status.cmdline)); | ||||
|             } | ||||
|             if ui.button("Copy log").clicked() { | ||||
|                 ui.output_mut(|output| { | ||||
|                     output.copied_text = format!("{}\n{}", status.stdout, status.stderr) | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|         ui.scope(|ui| { | ||||
|             ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); | ||||
|             ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); | ||||
| 
 | ||||
|             if !status.cmdline.is_empty() { | ||||
|                 ui.label(&status.cmdline); | ||||
|             } | ||||
|             if !status.stdout.is_empty() { | ||||
|                 ui.colored_label(appearance.replace_color, &status.stdout); | ||||
|             } | ||||
|             if !status.stderr.is_empty() { | ||||
|                 ui.colored_label(appearance.delete_color, &status.stderr); | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| fn missing_obj_ui(ui: &mut Ui, appearance: &Appearance) { | ||||
|     ui.scope(|ui| { | ||||
|         ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); | ||||
|         ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); | ||||
| 
 | ||||
|         ui.colored_label(appearance.replace_color, "No object configured"); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| fn find_symbol(obj: &ObjInfo, selected_symbol: &SymbolRefByName) -> Option<SymbolRef> { | ||||
|     for (section_idx, section) in obj.sections.iter().enumerate() { | ||||
|         for (symbol_idx, symbol) in section.symbols.iter().enumerate() { | ||||
|             if symbol.name == selected_symbol.symbol_name { | ||||
|                 return Some(SymbolRef { section_idx, symbol_idx }); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     None | ||||
| } | ||||
| 
 | ||||
| fn find_section(obj: &ObjInfo, section_name: &str) -> Option<usize> { | ||||
|     obj.sections.iter().position(|section| section.name == section_name) | ||||
| } | ||||
| @ -1,22 +1,10 @@ | ||||
| use egui::{RichText, ScrollArea}; | ||||
| use egui::ScrollArea; | ||||
| use objdiff_core::{ | ||||
|     arch::ppc::ExceptionInfo, | ||||
|     obj::{ObjInfo, ObjSymbol}, | ||||
| }; | ||||
| use time::format_description; | ||||
| 
 | ||||
| use crate::{ | ||||
|     hotkeys, | ||||
|     views::{ | ||||
|         appearance::Appearance, | ||||
|         column_layout::{render_header, render_strips}, | ||||
|         function_diff::FunctionDiffContext, | ||||
|         symbol_diff::{ | ||||
|             match_color_for_symbol, DiffViewAction, DiffViewNavigation, DiffViewState, | ||||
|             SymbolRefByName, View, | ||||
|         }, | ||||
|     }, | ||||
| }; | ||||
| use crate::views::{appearance::Appearance, function_diff::FunctionDiffContext}; | ||||
| 
 | ||||
| fn decode_extab(extab: &ExceptionInfo) -> String { | ||||
|     let mut text = String::from(""); | ||||
| @ -57,7 +45,7 @@ fn extab_text_ui( | ||||
|     None | ||||
| } | ||||
| 
 | ||||
| fn extab_ui( | ||||
| pub(crate) fn extab_ui( | ||||
|     ui: &mut egui::Ui, | ||||
|     ctx: FunctionDiffContext<'_>, | ||||
|     appearance: &Appearance, | ||||
| @ -76,178 +64,3 @@ fn extab_ui( | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| #[must_use] | ||||
| pub fn extab_diff_ui( | ||||
|     ui: &mut egui::Ui, | ||||
|     state: &DiffViewState, | ||||
|     appearance: &Appearance, | ||||
| ) -> Option<DiffViewAction> { | ||||
|     let mut ret = None; | ||||
|     let Some(result) = &state.build else { | ||||
|         return ret; | ||||
|     }; | ||||
| 
 | ||||
|     let mut left_ctx = FunctionDiffContext::new( | ||||
|         result.first_obj.as_ref(), | ||||
|         state.symbol_state.left_symbol.as_ref(), | ||||
|     ); | ||||
|     let mut right_ctx = FunctionDiffContext::new( | ||||
|         result.second_obj.as_ref(), | ||||
|         state.symbol_state.right_symbol.as_ref(), | ||||
|     ); | ||||
| 
 | ||||
|     // If one side is missing a symbol, but the diff process found a match, use that symbol
 | ||||
|     let left_diff_symbol = left_ctx.and_then(|ctx| { | ||||
|         ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol) | ||||
|     }); | ||||
|     let right_diff_symbol = right_ctx.and_then(|ctx| { | ||||
|         ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol) | ||||
|     }); | ||||
|     if left_diff_symbol.is_some() && right_ctx.is_some_and(|ctx| !ctx.has_symbol()) { | ||||
|         let (right_section, right_symbol) = | ||||
|             right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap()); | ||||
|         let symbol_ref = SymbolRefByName::new(right_symbol, right_section); | ||||
|         right_ctx = FunctionDiffContext::new(result.second_obj.as_ref(), Some(&symbol_ref)); | ||||
|         ret = Some(DiffViewAction::Navigate(DiffViewNavigation { | ||||
|             view: Some(View::FunctionDiff), | ||||
|             left_symbol: state.symbol_state.left_symbol.clone(), | ||||
|             right_symbol: Some(symbol_ref), | ||||
|         })); | ||||
|     } else if right_diff_symbol.is_some() && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) { | ||||
|         let (left_section, left_symbol) = | ||||
|             left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap()); | ||||
|         let symbol_ref = SymbolRefByName::new(left_symbol, left_section); | ||||
|         left_ctx = FunctionDiffContext::new(result.first_obj.as_ref(), Some(&symbol_ref)); | ||||
|         ret = Some(DiffViewAction::Navigate(DiffViewNavigation { | ||||
|             view: Some(View::FunctionDiff), | ||||
|             left_symbol: Some(symbol_ref), | ||||
|             right_symbol: state.symbol_state.right_symbol.clone(), | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     // If both sides are missing a symbol, switch to symbol diff view
 | ||||
|     if right_ctx.is_some_and(|ctx| !ctx.has_symbol()) | ||||
|         && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) | ||||
|     { | ||||
|         return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); | ||||
|     } | ||||
| 
 | ||||
|     // Header
 | ||||
|     let available_width = ui.available_width(); | ||||
|     render_header(ui, available_width, 2, |ui, column| { | ||||
|         if column == 0 { | ||||
|             // Left column
 | ||||
|             ui.horizontal(|ui| { | ||||
|                 if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) { | ||||
|                     ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); | ||||
|                 } | ||||
|                 ui.separator(); | ||||
|                 if ui | ||||
|                     .add_enabled( | ||||
|                         !state.scratch_running | ||||
|                             && state.scratch_available | ||||
|                             && left_ctx.is_some_and(|ctx| ctx.has_symbol()), | ||||
|                         egui::Button::new("📲 decomp.me"), | ||||
|                     ) | ||||
|                     .on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)") | ||||
|                     .on_disabled_hover_text("Scratch configuration missing") | ||||
|                     .clicked() | ||||
|                 { | ||||
|                     if let Some((_section, symbol)) = left_ctx.and_then(|ctx| { | ||||
|                         ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref)) | ||||
|                     }) { | ||||
|                         ret = Some(DiffViewAction::CreateScratch(symbol.name.clone())); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             if let Some((_section, symbol)) = left_ctx | ||||
|                 .and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref))) | ||||
|             { | ||||
|                 let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name); | ||||
|                 ui.label( | ||||
|                     RichText::new(name) | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.highlight_color), | ||||
|                 ); | ||||
|             } else { | ||||
|                 ui.label( | ||||
|                     RichText::new("Missing") | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.replace_color), | ||||
|                 ); | ||||
|             } | ||||
|         } else if column == 1 { | ||||
|             // Right column
 | ||||
|             ui.horizontal(|ui| { | ||||
|                 if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() { | ||||
|                     ret = Some(DiffViewAction::Build); | ||||
|                 } | ||||
|                 ui.scope(|ui| { | ||||
|                     ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); | ||||
|                     if state.build_running { | ||||
|                         ui.colored_label(appearance.replace_color, "Building…"); | ||||
|                     } else { | ||||
|                         ui.label("Last built:"); | ||||
|                         let format = format_description::parse("[hour]:[minute]:[second]").unwrap(); | ||||
|                         ui.label( | ||||
|                             result.time.to_offset(appearance.utc_offset).format(&format).unwrap(), | ||||
|                         ); | ||||
|                     } | ||||
|                 }); | ||||
|                 ui.separator(); | ||||
|                 if ui | ||||
|                     .add_enabled(state.source_path_available, egui::Button::new("🖹 Source file")) | ||||
|                     .on_hover_text_at_pointer("Open the source file in the default editor") | ||||
|                     .on_disabled_hover_text("Source file metadata missing") | ||||
|                     .clicked() | ||||
|                 { | ||||
|                     ret = Some(DiffViewAction::OpenSourcePath); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             if let Some(((_section, symbol), symbol_diff)) = right_ctx.and_then(|ctx| { | ||||
|                 ctx.symbol_ref.map(|symbol_ref| { | ||||
|                     (ctx.obj.section_symbol(symbol_ref), ctx.diff.symbol_diff(symbol_ref)) | ||||
|                 }) | ||||
|             }) { | ||||
|                 let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name); | ||||
|                 ui.label( | ||||
|                     RichText::new(name) | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.highlight_color), | ||||
|                 ); | ||||
|                 if let Some(match_percent) = symbol_diff.match_percent { | ||||
|                     ui.label( | ||||
|                         RichText::new(format!("{:.0}%", match_percent.floor())) | ||||
|                             .font(appearance.code_font.clone()) | ||||
|                             .color(match_color_for_symbol(match_percent, appearance)), | ||||
|                     ); | ||||
|                 } | ||||
|             } else { | ||||
|                 ui.label( | ||||
|                     RichText::new("Missing") | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.replace_color), | ||||
|                 ); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     hotkeys::check_scroll_hotkeys(ui, true); | ||||
| 
 | ||||
|     // Table
 | ||||
|     render_strips(ui, available_width, 2, |ui, column| { | ||||
|         if column == 0 { | ||||
|             if let Some(ctx) = left_ctx { | ||||
|                 extab_ui(ui, ctx, appearance, column); | ||||
|             } | ||||
|         } else if column == 1 { | ||||
|             if let Some(ctx) = right_ctx { | ||||
|                 extab_ui(ui, ctx, appearance, column); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|     ret | ||||
| } | ||||
|  | ||||
| @ -1,30 +1,16 @@ | ||||
| use std::{cmp::Ordering, default::Default}; | ||||
| 
 | ||||
| use egui::{text::LayoutJob, Id, Label, Layout, Response, RichText, Sense, Widget}; | ||||
| use egui::{text::LayoutJob, Label, Response, Sense, Widget}; | ||||
| use egui_extras::TableRow; | ||||
| use objdiff_core::{ | ||||
|     diff::{ | ||||
|         display::{display_diff, DiffText, HighlightKind}, | ||||
|         ObjDiff, ObjInsDiff, ObjInsDiffKind, | ||||
|     }, | ||||
|     obj::{ | ||||
|         ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjSection, ObjSectionKind, ObjSymbol, | ||||
|         SymbolRef, | ||||
|     }, | ||||
|     obj::{ObjInfo, ObjIns, ObjInsArg, ObjInsArgValue, ObjSection, ObjSymbol, SymbolRef}, | ||||
| }; | ||||
| use time::format_description; | ||||
| 
 | ||||
| use crate::{ | ||||
|     hotkeys, | ||||
|     views::{ | ||||
|         appearance::Appearance, | ||||
|         column_layout::{render_header, render_strips, render_table}, | ||||
|         symbol_diff::{ | ||||
|             match_color_for_symbol, symbol_list_ui, DiffViewAction, DiffViewNavigation, | ||||
|             DiffViewState, SymbolDiffContext, SymbolFilter, SymbolRefByName, SymbolViewState, View, | ||||
|         }, | ||||
|     }, | ||||
| }; | ||||
| use crate::views::{appearance::Appearance, symbol_diff::DiffViewAction}; | ||||
| 
 | ||||
| #[derive(Default)] | ||||
| pub struct FunctionViewState { | ||||
| @ -245,17 +231,6 @@ fn ins_context_menu( | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| fn find_symbol(obj: &ObjInfo, selected_symbol: &SymbolRefByName) -> Option<SymbolRef> { | ||||
|     for (section_idx, section) in obj.sections.iter().enumerate() { | ||||
|         for (symbol_idx, symbol) in section.symbols.iter().enumerate() { | ||||
|             if symbol.name == selected_symbol.symbol_name { | ||||
|                 return Some(SymbolRef { section_idx, symbol_idx }); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     None | ||||
| } | ||||
| 
 | ||||
| #[must_use] | ||||
| #[expect(clippy::too_many_arguments)] | ||||
| fn diff_text_ui( | ||||
| @ -400,7 +375,7 @@ fn asm_row_ui( | ||||
| } | ||||
| 
 | ||||
| #[must_use] | ||||
| fn asm_col_ui( | ||||
| pub(crate) fn asm_col_ui( | ||||
|     row: &mut TableRow<'_, '_>, | ||||
|     ctx: FunctionDiffContext<'_>, | ||||
|     appearance: &Appearance, | ||||
| @ -433,464 +408,9 @@ fn asm_col_ui( | ||||
|     ret | ||||
| } | ||||
| 
 | ||||
| #[must_use] | ||||
| #[expect(clippy::too_many_arguments)] | ||||
| fn asm_table_ui( | ||||
|     ui: &mut egui::Ui, | ||||
|     available_width: f32, | ||||
|     left_ctx: Option<FunctionDiffContext<'_>>, | ||||
|     right_ctx: Option<FunctionDiffContext<'_>>, | ||||
|     appearance: &Appearance, | ||||
|     ins_view_state: &FunctionViewState, | ||||
|     symbol_state: &SymbolViewState, | ||||
|     open_sections: (Option<bool>, Option<bool>), | ||||
| ) -> Option<DiffViewAction> { | ||||
|     let mut ret = None; | ||||
|     let left_len = left_ctx.and_then(|ctx| { | ||||
|         ctx.symbol_ref.map(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).instructions.len()) | ||||
|     }); | ||||
|     let right_len = right_ctx.and_then(|ctx| { | ||||
|         ctx.symbol_ref.map(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).instructions.len()) | ||||
|     }); | ||||
|     let instructions_len = match (left_len, right_len) { | ||||
|         (Some(left_len), Some(right_len)) => { | ||||
|             if left_len != right_len { | ||||
|                 ui.label("Instruction count mismatch"); | ||||
|                 return None; | ||||
|             } | ||||
|             left_len | ||||
|         } | ||||
|         (Some(left_len), None) => left_len, | ||||
|         (None, Some(right_len)) => right_len, | ||||
|         (None, None) => { | ||||
|             ui.label("No symbol selected"); | ||||
|             return None; | ||||
|         } | ||||
|     }; | ||||
|     if left_len.is_some() && right_len.is_some() { | ||||
|         // Joint view
 | ||||
|         hotkeys::check_scroll_hotkeys(ui, true); | ||||
|         render_table( | ||||
|             ui, | ||||
|             available_width, | ||||
|             2, | ||||
|             appearance.code_font.size, | ||||
|             instructions_len, | ||||
|             |row, column| { | ||||
|                 if column == 0 { | ||||
|                     if let Some(ctx) = left_ctx { | ||||
|                         if let Some(action) = | ||||
|                             asm_col_ui(row, ctx, appearance, ins_view_state, column) | ||||
|                         { | ||||
|                             ret = Some(action); | ||||
|                         } | ||||
|                     } | ||||
|                 } else if column == 1 { | ||||
|                     if let Some(ctx) = right_ctx { | ||||
|                         if let Some(action) = | ||||
|                             asm_col_ui(row, ctx, appearance, ins_view_state, column) | ||||
|                         { | ||||
|                             ret = Some(action); | ||||
|                         } | ||||
|                     } | ||||
|                     if row.response().clicked() { | ||||
|                         ret = Some(DiffViewAction::ClearDiffHighlight); | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|         ); | ||||
|     } else { | ||||
|         // Split view, one side is the symbol list
 | ||||
|         render_strips(ui, available_width, 2, |ui, column| { | ||||
|             if column == 0 { | ||||
|                 if let Some(ctx) = left_ctx { | ||||
|                     if ctx.has_symbol() { | ||||
|                         hotkeys::check_scroll_hotkeys(ui, false); | ||||
|                         render_table( | ||||
|                             ui, | ||||
|                             available_width / 2.0, | ||||
|                             1, | ||||
|                             appearance.code_font.size, | ||||
|                             instructions_len, | ||||
|                             |row, column| { | ||||
|                                 if let Some(action) = | ||||
|                                     asm_col_ui(row, ctx, appearance, ins_view_state, column) | ||||
|                                 { | ||||
|                                     ret = Some(action); | ||||
|                                 } | ||||
|                                 if row.response().clicked() { | ||||
|                                     ret = Some(DiffViewAction::ClearDiffHighlight); | ||||
|                                 } | ||||
|                             }, | ||||
|                         ); | ||||
|                     } else if let Some((right_ctx, right_symbol_ref)) = | ||||
|                         right_ctx.and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| (ctx, symbol_ref))) | ||||
|                     { | ||||
|                         if let Some(action) = symbol_list_ui( | ||||
|                             ui, | ||||
|                             SymbolDiffContext { obj: ctx.obj, diff: ctx.diff }, | ||||
|                             None, | ||||
|                             symbol_state, | ||||
|                             SymbolFilter::Mapping(right_symbol_ref), | ||||
|                             appearance, | ||||
|                             column, | ||||
|                             open_sections.0, | ||||
|                         ) { | ||||
|                             match action { | ||||
|                                 DiffViewAction::Navigate(DiffViewNavigation { | ||||
|                                     left_symbol: Some(left_symbol_ref), | ||||
|                                     .. | ||||
|                                 }) => { | ||||
|                                     let (right_section, right_symbol) = | ||||
|                                         right_ctx.obj.section_symbol(right_symbol_ref); | ||||
|                                     ret = Some(DiffViewAction::SetMapping( | ||||
|                                         match right_section.map(|s| s.kind) { | ||||
|                                             Some(ObjSectionKind::Code) => View::FunctionDiff, | ||||
|                                             _ => View::SymbolDiff, | ||||
|                                         }, | ||||
|                                         left_symbol_ref, | ||||
|                                         SymbolRefByName::new(right_symbol, right_section), | ||||
|                                     )); | ||||
|                                 } | ||||
|                                 _ => { | ||||
|                                     ret = Some(action); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } else { | ||||
|                     ui.label("No left object"); | ||||
|                 } | ||||
|             } else if column == 1 { | ||||
|                 if let Some(ctx) = right_ctx { | ||||
|                     if ctx.has_symbol() { | ||||
|                         hotkeys::check_scroll_hotkeys(ui, false); | ||||
|                         render_table( | ||||
|                             ui, | ||||
|                             available_width / 2.0, | ||||
|                             1, | ||||
|                             appearance.code_font.size, | ||||
|                             instructions_len, | ||||
|                             |row, column| { | ||||
|                                 if let Some(action) = | ||||
|                                     asm_col_ui(row, ctx, appearance, ins_view_state, column) | ||||
|                                 { | ||||
|                                     ret = Some(action); | ||||
|                                 } | ||||
|                                 if row.response().clicked() { | ||||
|                                     ret = Some(DiffViewAction::ClearDiffHighlight); | ||||
|                                 } | ||||
|                             }, | ||||
|                         ); | ||||
|                     } else if let Some((left_ctx, left_symbol_ref)) = | ||||
|                         left_ctx.and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| (ctx, symbol_ref))) | ||||
|                     { | ||||
|                         if let Some(action) = symbol_list_ui( | ||||
|                             ui, | ||||
|                             SymbolDiffContext { obj: ctx.obj, diff: ctx.diff }, | ||||
|                             None, | ||||
|                             symbol_state, | ||||
|                             SymbolFilter::Mapping(left_symbol_ref), | ||||
|                             appearance, | ||||
|                             column, | ||||
|                             open_sections.1, | ||||
|                         ) { | ||||
|                             match action { | ||||
|                                 DiffViewAction::Navigate(DiffViewNavigation { | ||||
|                                     right_symbol: Some(right_symbol_ref), | ||||
|                                     .. | ||||
|                                 }) => { | ||||
|                                     let (left_section, left_symbol) = | ||||
|                                         left_ctx.obj.section_symbol(left_symbol_ref); | ||||
|                                     ret = Some(DiffViewAction::SetMapping( | ||||
|                                         match left_section.map(|s| s.kind) { | ||||
|                                             Some(ObjSectionKind::Code) => View::FunctionDiff, | ||||
|                                             _ => View::SymbolDiff, | ||||
|                                         }, | ||||
|                                         SymbolRefByName::new(left_symbol, left_section), | ||||
|                                         right_symbol_ref, | ||||
|                                     )); | ||||
|                                 } | ||||
|                                 _ => { | ||||
|                                     ret = Some(action); | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 } else { | ||||
|                     ui.label("No right object"); | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|     ret | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Copy)] | ||||
| pub struct FunctionDiffContext<'a> { | ||||
|     pub obj: &'a ObjInfo, | ||||
|     pub diff: &'a ObjDiff, | ||||
|     pub symbol_ref: Option<SymbolRef>, | ||||
| } | ||||
| 
 | ||||
| impl<'a> FunctionDiffContext<'a> { | ||||
|     pub fn new( | ||||
|         obj: Option<&'a (ObjInfo, ObjDiff)>, | ||||
|         selected_symbol: Option<&SymbolRefByName>, | ||||
|     ) -> Option<Self> { | ||||
|         obj.map(|(obj, diff)| Self { | ||||
|             obj, | ||||
|             diff, | ||||
|             symbol_ref: selected_symbol.and_then(|s| find_symbol(obj, s)), | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     #[inline] | ||||
|     pub fn has_symbol(&self) -> bool { self.symbol_ref.is_some() } | ||||
| } | ||||
| 
 | ||||
| #[must_use] | ||||
| pub fn function_diff_ui( | ||||
|     ui: &mut egui::Ui, | ||||
|     state: &DiffViewState, | ||||
|     appearance: &Appearance, | ||||
| ) -> Option<DiffViewAction> { | ||||
|     let mut ret = None; | ||||
|     let Some(result) = &state.build else { | ||||
|         return ret; | ||||
|     }; | ||||
| 
 | ||||
|     let mut left_ctx = FunctionDiffContext::new( | ||||
|         result.first_obj.as_ref(), | ||||
|         state.symbol_state.left_symbol.as_ref(), | ||||
|     ); | ||||
|     let mut right_ctx = FunctionDiffContext::new( | ||||
|         result.second_obj.as_ref(), | ||||
|         state.symbol_state.right_symbol.as_ref(), | ||||
|     ); | ||||
| 
 | ||||
|     // If one side is missing a symbol, but the diff process found a match, use that symbol
 | ||||
|     let left_diff_symbol = left_ctx.and_then(|ctx| { | ||||
|         ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol) | ||||
|     }); | ||||
|     let right_diff_symbol = right_ctx.and_then(|ctx| { | ||||
|         ctx.symbol_ref.and_then(|symbol_ref| ctx.diff.symbol_diff(symbol_ref).target_symbol) | ||||
|     }); | ||||
|     if left_diff_symbol.is_some() && right_ctx.is_some_and(|ctx| !ctx.has_symbol()) { | ||||
|         let (right_section, right_symbol) = | ||||
|             right_ctx.unwrap().obj.section_symbol(left_diff_symbol.unwrap()); | ||||
|         let symbol_ref = SymbolRefByName::new(right_symbol, right_section); | ||||
|         right_ctx = FunctionDiffContext::new(result.second_obj.as_ref(), Some(&symbol_ref)); | ||||
|         ret = Some(DiffViewAction::Navigate(DiffViewNavigation { | ||||
|             view: Some(View::FunctionDiff), | ||||
|             left_symbol: state.symbol_state.left_symbol.clone(), | ||||
|             right_symbol: Some(symbol_ref), | ||||
|         })); | ||||
|     } else if right_diff_symbol.is_some() && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) { | ||||
|         let (left_section, left_symbol) = | ||||
|             left_ctx.unwrap().obj.section_symbol(right_diff_symbol.unwrap()); | ||||
|         let symbol_ref = SymbolRefByName::new(left_symbol, left_section); | ||||
|         left_ctx = FunctionDiffContext::new(result.first_obj.as_ref(), Some(&symbol_ref)); | ||||
|         ret = Some(DiffViewAction::Navigate(DiffViewNavigation { | ||||
|             view: Some(View::FunctionDiff), | ||||
|             left_symbol: Some(symbol_ref), | ||||
|             right_symbol: state.symbol_state.right_symbol.clone(), | ||||
|         })); | ||||
|     } | ||||
| 
 | ||||
|     // If both sides are missing a symbol, switch to symbol diff view
 | ||||
|     if right_ctx.is_some_and(|ctx| !ctx.has_symbol()) | ||||
|         && left_ctx.is_some_and(|ctx| !ctx.has_symbol()) | ||||
|     { | ||||
|         return Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); | ||||
|     } | ||||
| 
 | ||||
|     // Header
 | ||||
|     let available_width = ui.available_width(); | ||||
|     let mut open_sections = (None, None); | ||||
|     render_header(ui, available_width, 2, |ui, column| { | ||||
|         if column == 0 { | ||||
|             // Left column
 | ||||
|             ui.horizontal(|ui| { | ||||
|                 if ui.button("⏴ Back").clicked() || hotkeys::back_pressed(ui.ctx()) { | ||||
|                     ret = Some(DiffViewAction::Navigate(DiffViewNavigation::symbol_diff())); | ||||
|                 } | ||||
|                 ui.separator(); | ||||
|                 if ui | ||||
|                     .add_enabled( | ||||
|                         !state.scratch_running | ||||
|                             && state.scratch_available | ||||
|                             && left_ctx.is_some_and(|ctx| ctx.has_symbol()), | ||||
|                         egui::Button::new("📲 decomp.me"), | ||||
|                     ) | ||||
|                     .on_hover_text_at_pointer("Create a new scratch on decomp.me (beta)") | ||||
|                     .on_disabled_hover_text("Scratch configuration missing") | ||||
|                     .clicked() | ||||
|                 { | ||||
|                     if let Some((_section, symbol)) = left_ctx.and_then(|ctx| { | ||||
|                         ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref)) | ||||
|                     }) { | ||||
|                         ret = Some(DiffViewAction::CreateScratch(symbol.name.clone())); | ||||
|                     } | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             if let Some((_section, symbol)) = left_ctx | ||||
|                 .and_then(|ctx| ctx.symbol_ref.map(|symbol_ref| ctx.obj.section_symbol(symbol_ref))) | ||||
|             { | ||||
|                 let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name); | ||||
|                 ui.label( | ||||
|                     RichText::new(name) | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.highlight_color), | ||||
|                 ); | ||||
|                 if right_ctx.is_some_and(|m| m.has_symbol()) | ||||
|                     && (ui | ||||
|                         .button("Change target") | ||||
|                         .on_hover_text_at_pointer("Choose a different symbol to use as the target") | ||||
|                         .clicked() | ||||
|                         || hotkeys::consume_change_target_shortcut(ui.ctx())) | ||||
|                 { | ||||
|                     if let Some(symbol_ref) = state.symbol_state.right_symbol.as_ref() { | ||||
|                         ret = Some(DiffViewAction::SelectingLeft(symbol_ref.clone())); | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 ui.label( | ||||
|                     RichText::new("Missing") | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.replace_color), | ||||
|                 ); | ||||
| 
 | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label( | ||||
|                         RichText::new("Choose target symbol") | ||||
|                             .font(appearance.code_font.clone()) | ||||
|                             .color(appearance.highlight_color), | ||||
|                     ); | ||||
| 
 | ||||
|                     ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| { | ||||
|                         if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() { | ||||
|                             open_sections.0 = Some(true); | ||||
|                         } | ||||
|                         if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked() | ||||
|                         { | ||||
|                             open_sections.0 = Some(false); | ||||
|                         } | ||||
|                     }) | ||||
|                 }); | ||||
|             } | ||||
|         } else if column == 1 { | ||||
|             // Right column
 | ||||
|             ui.horizontal(|ui| { | ||||
|                 if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() { | ||||
|                     ret = Some(DiffViewAction::Build); | ||||
|                 } | ||||
|                 ui.scope(|ui| { | ||||
|                     ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); | ||||
|                     if state.build_running { | ||||
|                         ui.colored_label(appearance.replace_color, "Building…"); | ||||
|                     } else { | ||||
|                         ui.label("Last built:"); | ||||
|                         let format = format_description::parse("[hour]:[minute]:[second]").unwrap(); | ||||
|                         ui.label( | ||||
|                             result.time.to_offset(appearance.utc_offset).format(&format).unwrap(), | ||||
|                         ); | ||||
|                     } | ||||
|                 }); | ||||
|                 ui.separator(); | ||||
|                 if ui | ||||
|                     .add_enabled(state.source_path_available, egui::Button::new("🖹 Source file")) | ||||
|                     .on_hover_text_at_pointer("Open the source file in the default editor") | ||||
|                     .on_disabled_hover_text("Source file metadata missing") | ||||
|                     .clicked() | ||||
|                 { | ||||
|                     ret = Some(DiffViewAction::OpenSourcePath); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             if let Some(((_section, symbol), symbol_diff)) = right_ctx.and_then(|ctx| { | ||||
|                 ctx.symbol_ref.map(|symbol_ref| { | ||||
|                     (ctx.obj.section_symbol(symbol_ref), ctx.diff.symbol_diff(symbol_ref)) | ||||
|                 }) | ||||
|             }) { | ||||
|                 let name = symbol.demangled_name.as_deref().unwrap_or(&symbol.name); | ||||
|                 ui.label( | ||||
|                     RichText::new(name) | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.highlight_color), | ||||
|                 ); | ||||
|                 ui.horizontal(|ui| { | ||||
|                     if let Some(match_percent) = symbol_diff.match_percent { | ||||
|                         ui.label( | ||||
|                             RichText::new(format!("{:.0}%", match_percent.floor())) | ||||
|                                 .font(appearance.code_font.clone()) | ||||
|                                 .color(match_color_for_symbol(match_percent, appearance)), | ||||
|                         ); | ||||
|                     } | ||||
|                     if left_ctx.is_some_and(|m| m.has_symbol()) { | ||||
|                         ui.separator(); | ||||
|                         if ui | ||||
|                             .button("Change base") | ||||
|                             .on_hover_text_at_pointer( | ||||
|                                 "Choose a different symbol to use as the base", | ||||
|                             ) | ||||
|                             .clicked() | ||||
|                             || hotkeys::consume_change_base_shortcut(ui.ctx()) | ||||
|                         { | ||||
|                             if let Some(symbol_ref) = state.symbol_state.left_symbol.as_ref() { | ||||
|                                 ret = Some(DiffViewAction::SelectingRight(symbol_ref.clone())); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 }); | ||||
|             } else { | ||||
|                 ui.label( | ||||
|                     RichText::new("Missing") | ||||
|                         .font(appearance.code_font.clone()) | ||||
|                         .color(appearance.replace_color), | ||||
|                 ); | ||||
| 
 | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label( | ||||
|                         RichText::new("Choose base symbol") | ||||
|                             .font(appearance.code_font.clone()) | ||||
|                             .color(appearance.highlight_color), | ||||
|                     ); | ||||
| 
 | ||||
|                     ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| { | ||||
|                         if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() { | ||||
|                             open_sections.1 = Some(true); | ||||
|                         } | ||||
|                         if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked() | ||||
|                         { | ||||
|                             open_sections.1 = Some(false); | ||||
|                         } | ||||
|                     }) | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     // Table
 | ||||
|     let id = Id::new(state.symbol_state.left_symbol.as_ref().map(|s| s.symbol_name.as_str())) | ||||
|         .with(state.symbol_state.right_symbol.as_ref().map(|s| s.symbol_name.as_str())); | ||||
|     if let Some(action) = ui | ||||
|         .push_id(id, |ui| { | ||||
|             asm_table_ui( | ||||
|                 ui, | ||||
|                 available_width, | ||||
|                 left_ctx, | ||||
|                 right_ctx, | ||||
|                 appearance, | ||||
|                 &state.function_state, | ||||
|                 &state.symbol_state, | ||||
|                 open_sections, | ||||
|             ) | ||||
|         }) | ||||
|         .inner | ||||
|     { | ||||
|         ret = Some(action); | ||||
|     } | ||||
|     ret | ||||
| } | ||||
|  | ||||
| @ -6,6 +6,7 @@ pub(crate) mod config; | ||||
| pub(crate) mod data_diff; | ||||
| pub(crate) mod debug; | ||||
| pub(crate) mod demangle; | ||||
| pub(crate) mod diff; | ||||
| pub(crate) mod extab_diff; | ||||
| pub(crate) mod file; | ||||
| pub(crate) mod frame_history; | ||||
|  | ||||
| @ -1,12 +1,11 @@ | ||||
| use std::{collections::BTreeMap, mem::take, ops::Bound}; | ||||
| 
 | ||||
| use egui::{ | ||||
|     style::ScrollAnimation, text::LayoutJob, CollapsingHeader, Color32, Id, Layout, OpenUrl, | ||||
|     ScrollArea, SelectableLabel, TextEdit, Ui, Widget, | ||||
|     style::ScrollAnimation, text::LayoutJob, CollapsingHeader, Color32, Id, OpenUrl, ScrollArea, | ||||
|     SelectableLabel, Ui, Widget, | ||||
| }; | ||||
| use objdiff_core::{ | ||||
|     arch::ObjArch, | ||||
|     build::BuildStatus, | ||||
|     diff::{display::HighlightKind, ObjDiff, ObjSymbolDiff}, | ||||
|     jobs::{create_scratch::CreateScratchResult, objdiff::ObjDiffResult, Job, JobQueue, JobResult}, | ||||
|     obj::{ | ||||
| @ -19,15 +18,10 @@ use crate::{ | ||||
|     app::AppStateRef, | ||||
|     hotkeys, | ||||
|     jobs::{is_create_scratch_available, start_create_scratch}, | ||||
|     views::{ | ||||
|         appearance::Appearance, | ||||
|         column_layout::{render_header, render_strips}, | ||||
|         function_diff::FunctionViewState, | ||||
|         write_text, | ||||
|     }, | ||||
|     views::{appearance::Appearance, function_diff::FunctionViewState, write_text}, | ||||
| }; | ||||
| 
 | ||||
| #[derive(Debug, Clone)] | ||||
| #[derive(Debug, Clone, Eq, PartialEq)] | ||||
| pub struct SymbolRefByName { | ||||
|     pub symbol_name: String, | ||||
|     pub section_name: Option<String>, | ||||
| @ -79,16 +73,16 @@ pub enum DiffViewAction { | ||||
|     SetShowMappedSymbols(bool), | ||||
| } | ||||
| 
 | ||||
| #[derive(Debug, Clone, Default)] | ||||
| #[derive(Debug, Clone, Default, Eq, PartialEq)] | ||||
| pub struct DiffViewNavigation { | ||||
|     pub view: Option<View>, | ||||
|     pub view: View, | ||||
|     pub left_symbol: Option<SymbolRefByName>, | ||||
|     pub right_symbol: Option<SymbolRefByName>, | ||||
| } | ||||
| 
 | ||||
| impl DiffViewNavigation { | ||||
|     pub fn symbol_diff() -> Self { | ||||
|         Self { view: Some(View::SymbolDiff), left_symbol: None, right_symbol: None } | ||||
|         Self { view: View::SymbolDiff, left_symbol: None, right_symbol: None } | ||||
|     } | ||||
| 
 | ||||
|     pub fn with_symbols( | ||||
| @ -107,8 +101,24 @@ impl DiffViewNavigation { | ||||
|             }) | ||||
|         }); | ||||
|         match column { | ||||
|             0 => Self { view: Some(view), left_symbol: symbol1, right_symbol: symbol2 }, | ||||
|             1 => Self { view: Some(view), left_symbol: symbol2, right_symbol: symbol1 }, | ||||
|             0 => Self { view, left_symbol: symbol1, right_symbol: symbol2 }, | ||||
|             1 => Self { view, left_symbol: symbol2, right_symbol: symbol1 }, | ||||
|             _ => unreachable!("Invalid column index"), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn data_diff(section: &ObjSection, column: usize) -> Self { | ||||
|         let symbol = Some(SymbolRefByName { | ||||
|             symbol_name: "".to_string(), | ||||
|             section_name: Some(section.name.clone()), | ||||
|         }); | ||||
|         match column { | ||||
|             0 => Self { | ||||
|                 view: View::DataDiff, | ||||
|                 left_symbol: symbol.clone(), | ||||
|                 right_symbol: symbol.clone(), | ||||
|             }, | ||||
|             1 => Self { view: View::DataDiff, left_symbol: symbol.clone(), right_symbol: symbol }, | ||||
|             _ => unreachable!("Invalid column index"), | ||||
|         } | ||||
|     } | ||||
| @ -151,9 +161,7 @@ impl DiffViewState { | ||||
| 
 | ||||
|                 // TODO: where should this go?
 | ||||
|                 if let Some(result) = self.post_build_nav.take() { | ||||
|                     if let Some(view) = result.view { | ||||
|                         self.current_view = view; | ||||
|                     } | ||||
|                     self.current_view = result.view; | ||||
|                     self.symbol_state.left_symbol = result.left_symbol; | ||||
|                     self.symbol_state.right_symbol = result.right_symbol; | ||||
|                 } | ||||
| @ -219,7 +227,7 @@ impl DiffViewState { | ||||
|                 }; | ||||
|                 if (nav.left_symbol.is_some() && nav.right_symbol.is_some()) | ||||
|                     || (nav.left_symbol.is_none() && nav.right_symbol.is_none()) | ||||
|                     || nav.view != Some(View::FunctionDiff) | ||||
|                     || nav.view != View::FunctionDiff | ||||
|                 { | ||||
|                     // Regular navigation
 | ||||
|                     if state.is_selecting_symbol() { | ||||
| @ -228,9 +236,7 @@ impl DiffViewState { | ||||
|                         self.post_build_nav = Some(nav); | ||||
|                     } else { | ||||
|                         // Navigate immediately
 | ||||
|                         if let Some(view) = nav.view { | ||||
|                             self.current_view = view; | ||||
|                         } | ||||
|                         self.current_view = nav.view; | ||||
|                         self.symbol_state.left_symbol = nav.left_symbol; | ||||
|                         self.symbol_state.right_symbol = nav.right_symbol; | ||||
|                     } | ||||
| @ -301,7 +307,7 @@ impl DiffViewState { | ||||
|                 }; | ||||
|                 state.set_selecting_left(&right_ref.symbol_name); | ||||
|                 self.post_build_nav = Some(DiffViewNavigation { | ||||
|                     view: Some(View::FunctionDiff), | ||||
|                     view: View::FunctionDiff, | ||||
|                     left_symbol: None, | ||||
|                     right_symbol: Some(right_ref), | ||||
|                 }); | ||||
| @ -316,7 +322,7 @@ impl DiffViewState { | ||||
|                 }; | ||||
|                 state.set_selecting_right(&left_ref.symbol_name); | ||||
|                 self.post_build_nav = Some(DiffViewNavigation { | ||||
|                     view: Some(View::FunctionDiff), | ||||
|                     view: View::FunctionDiff, | ||||
|                     left_symbol: Some(left_ref), | ||||
|                     right_symbol: None, | ||||
|                 }); | ||||
| @ -337,7 +343,7 @@ impl DiffViewState { | ||||
|                     self.post_build_nav = Some(DiffViewNavigation::symbol_diff()); | ||||
|                 } else { | ||||
|                     self.post_build_nav = Some(DiffViewNavigation { | ||||
|                         view: Some(view), | ||||
|                         view, | ||||
|                         left_symbol: Some(left_ref), | ||||
|                         right_symbol: Some(right_ref), | ||||
|                     }); | ||||
| @ -409,13 +415,13 @@ fn symbol_context_menu_ui( | ||||
|                 let symbol_ref = SymbolRefByName::new(symbol, Some(section)); | ||||
|                 if column == 0 { | ||||
|                     ret = Some(DiffViewNavigation { | ||||
|                         view: Some(View::FunctionDiff), | ||||
|                         view: View::FunctionDiff, | ||||
|                         left_symbol: Some(symbol_ref), | ||||
|                         right_symbol: None, | ||||
|                     }); | ||||
|                 } else { | ||||
|                     ret = Some(DiffViewNavigation { | ||||
|                         view: Some(View::FunctionDiff), | ||||
|                         view: View::FunctionDiff, | ||||
|                         left_symbol: None, | ||||
|                         right_symbol: Some(symbol_ref), | ||||
|                     }); | ||||
| @ -552,13 +558,8 @@ fn symbol_ui( | ||||
|                     ))); | ||||
|                 } | ||||
|                 ObjSectionKind::Data => { | ||||
|                     ret = Some(DiffViewAction::Navigate(DiffViewNavigation::with_symbols( | ||||
|                         View::DataDiff, | ||||
|                         other_ctx, | ||||
|                         symbol, | ||||
|                         section, | ||||
|                         symbol_diff, | ||||
|                         column, | ||||
|                     ret = Some(DiffViewAction::Navigate(DiffViewNavigation::data_diff( | ||||
|                         section, column, | ||||
|                     ))); | ||||
|                 } | ||||
|                 ObjSectionKind::Bss => {} | ||||
| @ -591,9 +592,15 @@ fn symbol_matches_filter( | ||||
|         SymbolFilter::None => true, | ||||
|         SymbolFilter::Search(regex) => { | ||||
|             regex.is_match(&symbol.name) | ||||
|                 || symbol.demangled_name.as_ref().map(|s| regex.is_match(s)).unwrap_or(false) | ||||
|                 || symbol.demangled_name.as_deref().is_some_and(|s| regex.is_match(s)) | ||||
|         } | ||||
|         SymbolFilter::Mapping(symbol_ref, regex) => { | ||||
|             diff.target_symbol == Some(symbol_ref) | ||||
|                 && regex.is_none_or(|r| { | ||||
|                     r.is_match(&symbol.name) | ||||
|                         || symbol.demangled_name.as_deref().is_some_and(|s| r.is_match(s)) | ||||
|                 }) | ||||
|         } | ||||
|         SymbolFilter::Mapping(symbol_ref) => diff.target_symbol == Some(symbol_ref), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -601,7 +608,7 @@ fn symbol_matches_filter( | ||||
| pub enum SymbolFilter<'a> { | ||||
|     None, | ||||
|     Search(&'a Regex), | ||||
|     Mapping(SymbolRef), | ||||
|     Mapping(SymbolRef, Option<&'a Regex>), | ||||
| } | ||||
| 
 | ||||
| #[must_use] | ||||
| @ -619,21 +626,23 @@ pub fn symbol_list_ui( | ||||
|     let mut ret = None; | ||||
|     ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| { | ||||
|         let mut mapping = BTreeMap::new(); | ||||
|         if let SymbolFilter::Mapping(target_ref) = filter { | ||||
|         if let SymbolFilter::Mapping(_, _) = filter { | ||||
|             let mut show_mapped_symbols = state.show_mapped_symbols; | ||||
|             if ui.checkbox(&mut show_mapped_symbols, "Show mapped symbols").changed() { | ||||
|                 ret = Some(DiffViewAction::SetShowMappedSymbols(show_mapped_symbols)); | ||||
|             } | ||||
|             for mapping_diff in &ctx.diff.mapping_symbols { | ||||
|                 if mapping_diff.target_symbol == Some(target_ref) { | ||||
|                     if !show_mapped_symbols { | ||||
|                         let symbol_diff = ctx.diff.symbol_diff(mapping_diff.symbol_ref); | ||||
|                         if symbol_diff.target_symbol.is_some() { | ||||
|                             continue; | ||||
|                         } | ||||
|                     } | ||||
|                     mapping.insert(mapping_diff.symbol_ref, mapping_diff); | ||||
|                 let symbol = ctx.obj.section_symbol(mapping_diff.symbol_ref).1; | ||||
|                 if !symbol_matches_filter(symbol, mapping_diff, filter) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 if !show_mapped_symbols { | ||||
|                     let symbol_diff = ctx.diff.symbol_diff(mapping_diff.symbol_ref); | ||||
|                     if symbol_diff.target_symbol.is_some() { | ||||
|                         continue; | ||||
|                     } | ||||
|                 } | ||||
|                 mapping.insert(mapping_diff.symbol_ref, mapping_diff); | ||||
|             } | ||||
|         } else { | ||||
|             for (symbol, diff) in ctx.obj.common.iter().zip(&ctx.diff.common) { | ||||
| @ -819,206 +828,8 @@ pub fn symbol_list_ui( | ||||
|     ret | ||||
| } | ||||
| 
 | ||||
| fn build_log_ui(ui: &mut Ui, status: &BuildStatus, appearance: &Appearance) { | ||||
|     ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| { | ||||
|         ui.horizontal(|ui| { | ||||
|             if !status.cmdline.is_empty() && ui.button("Copy command").clicked() { | ||||
|                 ui.output_mut(|output| output.copied_text.clone_from(&status.cmdline)); | ||||
|             } | ||||
|             if ui.button("Copy log").clicked() { | ||||
|                 ui.output_mut(|output| { | ||||
|                     output.copied_text = format!("{}\n{}", status.stdout, status.stderr) | ||||
|                 }); | ||||
|             } | ||||
|         }); | ||||
|         ui.scope(|ui| { | ||||
|             ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); | ||||
|             ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); | ||||
| 
 | ||||
|             if !status.cmdline.is_empty() { | ||||
|                 ui.label(&status.cmdline); | ||||
|             } | ||||
|             if !status.stdout.is_empty() { | ||||
|                 ui.colored_label(appearance.replace_color, &status.stdout); | ||||
|             } | ||||
|             if !status.stderr.is_empty() { | ||||
|                 ui.colored_label(appearance.delete_color, &status.stderr); | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| fn missing_obj_ui(ui: &mut Ui, appearance: &Appearance) { | ||||
|     ui.scope(|ui| { | ||||
|         ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); | ||||
|         ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); | ||||
| 
 | ||||
|         ui.colored_label(appearance.replace_color, "No object configured"); | ||||
|     }); | ||||
| } | ||||
| 
 | ||||
| #[derive(Copy, Clone)] | ||||
| pub struct SymbolDiffContext<'a> { | ||||
|     pub obj: &'a ObjInfo, | ||||
|     pub diff: &'a ObjDiff, | ||||
| } | ||||
| 
 | ||||
| #[must_use] | ||||
| pub fn symbol_diff_ui( | ||||
|     ui: &mut Ui, | ||||
|     state: &mut DiffViewState, | ||||
|     appearance: &Appearance, | ||||
| ) -> Option<DiffViewAction> { | ||||
|     let mut ret = None; | ||||
|     let Some(result) = &state.build else { | ||||
|         return ret; | ||||
|     }; | ||||
| 
 | ||||
|     // Header
 | ||||
|     let available_width = ui.available_width(); | ||||
|     let mut open_sections = (None, None); | ||||
|     render_header(ui, available_width, 2, |ui, column| { | ||||
|         if column == 0 { | ||||
|             // Left column
 | ||||
|             ui.scope(|ui| { | ||||
|                 ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); | ||||
| 
 | ||||
|                 ui.label("Target object"); | ||||
|                 if result.first_status.success { | ||||
|                     if result.first_obj.is_none() { | ||||
|                         ui.colored_label(appearance.replace_color, "Missing"); | ||||
|                     } else { | ||||
|                         ui.colored_label(appearance.highlight_color, state.object_name.clone()); | ||||
|                     } | ||||
|                 } else { | ||||
|                     ui.colored_label(appearance.delete_color, "Fail"); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             ui.horizontal(|ui| { | ||||
|                 let mut search = state.search.clone(); | ||||
|                 let response = TextEdit::singleline(&mut search).hint_text("Filter symbols").ui(ui); | ||||
|                 if hotkeys::consume_symbol_filter_shortcut(ui.ctx()) { | ||||
|                     response.request_focus(); | ||||
|                 } | ||||
|                 if response.changed() { | ||||
|                     ret = Some(DiffViewAction::SetSearch(search)); | ||||
|                 } | ||||
| 
 | ||||
|                 ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| { | ||||
|                     if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() { | ||||
|                         open_sections.0 = Some(true); | ||||
|                     } | ||||
|                     if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked() { | ||||
|                         open_sections.0 = Some(false); | ||||
|                     } | ||||
|                 }) | ||||
|             }); | ||||
|         } else if column == 1 { | ||||
|             // Right column
 | ||||
|             ui.horizontal(|ui| { | ||||
|                 ui.scope(|ui| { | ||||
|                     ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); | ||||
|                     ui.label("Base object"); | ||||
|                 }); | ||||
|                 ui.separator(); | ||||
|                 if ui | ||||
|                     .add_enabled(state.source_path_available, egui::Button::new("🖹 Source file")) | ||||
|                     .on_hover_text_at_pointer("Open the source file in the default editor") | ||||
|                     .on_disabled_hover_text("Source file metadata missing") | ||||
|                     .clicked() | ||||
|                 { | ||||
|                     ret = Some(DiffViewAction::OpenSourcePath); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             ui.scope(|ui| { | ||||
|                 ui.style_mut().override_text_style = Some(egui::TextStyle::Monospace); | ||||
|                 if result.second_status.success { | ||||
|                     if result.second_obj.is_none() { | ||||
|                         ui.colored_label(appearance.replace_color, "Missing"); | ||||
|                     } else { | ||||
|                         ui.colored_label(appearance.highlight_color, "OK"); | ||||
|                     } | ||||
|                 } else { | ||||
|                     ui.colored_label(appearance.delete_color, "Fail"); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             ui.horizontal(|ui| { | ||||
|                 if ui.add_enabled(!state.build_running, egui::Button::new("Build")).clicked() { | ||||
|                     ret = Some(DiffViewAction::Build); | ||||
|                 } | ||||
| 
 | ||||
|                 ui.with_layout(Layout::right_to_left(egui::Align::TOP), |ui| { | ||||
|                     if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() { | ||||
|                         open_sections.1 = Some(true); | ||||
|                     } | ||||
|                     if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked() { | ||||
|                         open_sections.1 = Some(false); | ||||
|                     } | ||||
|                 }) | ||||
|             }); | ||||
|         } | ||||
|     }); | ||||
| 
 | ||||
|     // Table
 | ||||
|     let filter = match &state.search_regex { | ||||
|         Some(regex) => SymbolFilter::Search(regex), | ||||
|         _ => SymbolFilter::None, | ||||
|     }; | ||||
|     render_strips(ui, available_width, 2, |ui, column| { | ||||
|         if column == 0 { | ||||
|             // Left column
 | ||||
|             if result.first_status.success { | ||||
|                 if let Some((obj, diff)) = &result.first_obj { | ||||
|                     if let Some(result) = symbol_list_ui( | ||||
|                         ui, | ||||
|                         SymbolDiffContext { obj, diff }, | ||||
|                         result | ||||
|                             .second_obj | ||||
|                             .as_ref() | ||||
|                             .map(|(obj, diff)| SymbolDiffContext { obj, diff }), | ||||
|                         &state.symbol_state, | ||||
|                         filter, | ||||
|                         appearance, | ||||
|                         column, | ||||
|                         open_sections.0, | ||||
|                     ) { | ||||
|                         ret = Some(result); | ||||
|                     } | ||||
|                 } else { | ||||
|                     missing_obj_ui(ui, appearance); | ||||
|                 } | ||||
|             } else { | ||||
|                 build_log_ui(ui, &result.first_status, appearance); | ||||
|             } | ||||
|         } else if column == 1 { | ||||
|             // Right column
 | ||||
|             if result.second_status.success { | ||||
|                 if let Some((obj, diff)) = &result.second_obj { | ||||
|                     if let Some(result) = symbol_list_ui( | ||||
|                         ui, | ||||
|                         SymbolDiffContext { obj, diff }, | ||||
|                         result | ||||
|                             .first_obj | ||||
|                             .as_ref() | ||||
|                             .map(|(obj, diff)| SymbolDiffContext { obj, diff }), | ||||
|                         &state.symbol_state, | ||||
|                         filter, | ||||
|                         appearance, | ||||
|                         column, | ||||
|                         open_sections.1, | ||||
|                     ) { | ||||
|                         ret = Some(result); | ||||
|                     } | ||||
|                 } else { | ||||
|                     missing_obj_ui(ui, appearance); | ||||
|                 } | ||||
|             } else { | ||||
|                 build_log_ui(ui, &result.second_status, appearance); | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|     ret | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user