2022-09-08 21:19:20 +00:00
|
|
|
use std::{
|
|
|
|
default::Default,
|
2023-10-07 18:48:34 +00:00
|
|
|
fs,
|
2022-09-08 21:19:20 +00:00
|
|
|
path::{Path, PathBuf},
|
2022-12-06 22:53:32 +00:00
|
|
|
rc::Rc,
|
2022-09-08 21:19:20 +00:00
|
|
|
sync::{
|
|
|
|
atomic::{AtomicBool, Ordering},
|
2022-12-06 22:53:32 +00:00
|
|
|
Arc, Mutex, RwLock,
|
2022-09-08 21:19:20 +00:00
|
|
|
},
|
2024-09-28 16:55:22 +00:00
|
|
|
time::Instant,
|
2022-09-08 21:19:20 +00:00
|
|
|
};
|
|
|
|
|
2023-10-07 18:48:34 +00:00
|
|
|
use filetime::FileTime;
|
|
|
|
use globset::{Glob, GlobSet};
|
2022-09-08 21:19:20 +00:00
|
|
|
use notify::{RecursiveMode, Watcher};
|
2024-03-17 05:30:27 +00:00
|
|
|
use objdiff_core::{
|
|
|
|
config::{
|
2024-10-10 03:44:18 +00:00
|
|
|
build_globset, save_project_config, ProjectConfig, ProjectConfigInfo, ProjectObject,
|
|
|
|
ScratchConfig, SymbolMappings, DEFAULT_WATCH_PATTERNS,
|
2024-03-17 05:30:27 +00:00
|
|
|
},
|
|
|
|
diff::DiffObjConfig,
|
2024-02-28 01:47:51 +00:00
|
|
|
};
|
2023-08-10 01:53:04 +00:00
|
|
|
use time::UtcOffset;
|
2022-09-08 21:19:20 +00:00
|
|
|
|
|
|
|
use crate::{
|
2023-09-10 03:43:12 +00:00
|
|
|
app_config::{deserialize_config, AppConfigVersion},
|
2024-02-28 01:47:51 +00:00
|
|
|
config::{load_project_config, ProjectObjectNode},
|
2023-10-07 18:48:34 +00:00
|
|
|
jobs::{
|
|
|
|
objdiff::{start_build, ObjDiffConfig},
|
|
|
|
Job, JobQueue, JobResult, JobStatus,
|
2022-09-08 21:19:20 +00:00
|
|
|
},
|
|
|
|
views::{
|
2023-08-10 01:53:04 +00:00
|
|
|
appearance::{appearance_window, Appearance},
|
2024-03-17 05:30:27 +00:00
|
|
|
config::{
|
2024-05-22 00:06:14 +00:00
|
|
|
arch_config_window, config_ui, project_window, ConfigViewState, CONFIG_DISABLED_TEXT,
|
2024-03-17 05:30:27 +00:00
|
|
|
},
|
2023-08-08 00:11:56 +00:00
|
|
|
data_diff::data_diff_ui,
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
debug::debug_window,
|
2023-08-10 01:53:04 +00:00
|
|
|
demangle::{demangle_window, DemangleViewState},
|
2024-07-22 04:25:54 +00:00
|
|
|
extab_diff::extab_diff_ui,
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
frame_history::FrameHistory,
|
2023-08-08 00:11:56 +00:00
|
|
|
function_diff::function_diff_ui,
|
2024-06-06 00:00:37 +00:00
|
|
|
graphics::{graphics_window, GraphicsConfig, GraphicsViewState},
|
2024-09-28 18:13:46 +00:00
|
|
|
jobs::{jobs_menu_ui, jobs_window},
|
2024-07-21 23:56:46 +00:00
|
|
|
rlwinm::{rlwinm_decode_window, RlwinmDecodeViewState},
|
2024-10-10 03:44:18 +00:00
|
|
|
symbol_diff::{symbol_diff_ui, DiffViewAction, DiffViewNavigation, DiffViewState, View},
|
2022-09-08 21:19:20 +00:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
pub struct ViewState {
|
2023-08-09 23:39:06 +00:00
|
|
|
pub jobs: JobQueue,
|
2023-08-12 18:18:09 +00:00
|
|
|
pub config_state: ConfigViewState,
|
2023-08-10 01:53:04 +00:00
|
|
|
pub demangle_state: DemangleViewState,
|
2024-07-21 23:56:46 +00:00
|
|
|
pub rlwinm_decode_state: RlwinmDecodeViewState,
|
2023-08-10 01:53:04 +00:00
|
|
|
pub diff_state: DiffViewState,
|
2024-06-06 00:00:37 +00:00
|
|
|
pub graphics_state: GraphicsViewState,
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
pub frame_history: FrameHistory,
|
2023-08-12 18:18:09 +00:00
|
|
|
pub show_appearance_config: bool,
|
|
|
|
pub show_demangle: bool,
|
2024-07-21 23:56:46 +00:00
|
|
|
pub show_rlwinm_decode: bool,
|
2023-08-10 01:53:04 +00:00
|
|
|
pub show_project_config: bool,
|
2024-05-22 00:06:14 +00:00
|
|
|
pub show_arch_config: bool,
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
pub show_debug: bool,
|
2024-06-06 00:00:37 +00:00
|
|
|
pub show_graphics: bool,
|
2024-09-28 18:13:46 +00:00
|
|
|
pub show_jobs: bool,
|
2024-10-08 01:46:16 +00:00
|
|
|
pub show_side_panel: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for ViewState {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
jobs: Default::default(),
|
|
|
|
config_state: Default::default(),
|
|
|
|
demangle_state: Default::default(),
|
|
|
|
rlwinm_decode_state: Default::default(),
|
|
|
|
diff_state: Default::default(),
|
|
|
|
graphics_state: Default::default(),
|
|
|
|
frame_history: Default::default(),
|
|
|
|
show_appearance_config: false,
|
|
|
|
show_demangle: false,
|
|
|
|
show_rlwinm_decode: false,
|
|
|
|
show_project_config: false,
|
|
|
|
show_arch_config: false,
|
|
|
|
show_debug: false,
|
|
|
|
show_graphics: false,
|
|
|
|
show_jobs: false,
|
|
|
|
show_side_panel: true,
|
|
|
|
}
|
|
|
|
}
|
2022-09-20 23:04:32 +00:00
|
|
|
}
|
|
|
|
|
2023-09-03 13:28:22 +00:00
|
|
|
/// The configuration for a single object file.
|
2024-10-10 03:44:18 +00:00
|
|
|
#[derive(Default, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
|
2023-09-03 13:28:22 +00:00
|
|
|
pub struct ObjectConfig {
|
|
|
|
pub name: String,
|
2023-09-10 03:43:12 +00:00
|
|
|
pub target_path: Option<PathBuf>,
|
|
|
|
pub base_path: Option<PathBuf>,
|
2023-09-03 13:28:22 +00:00
|
|
|
pub reverse_fn_order: Option<bool>,
|
2023-09-10 03:43:12 +00:00
|
|
|
pub complete: Option<bool>,
|
2024-01-21 05:53:40 +00:00
|
|
|
pub scratch: Option<ScratchConfig>,
|
2024-09-28 16:55:25 +00:00
|
|
|
pub source_path: Option<String>,
|
2024-10-10 03:44:18 +00:00
|
|
|
#[serde(default)]
|
|
|
|
pub symbol_mappings: SymbolMappings,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<&ProjectObject> for ObjectConfig {
|
|
|
|
fn from(object: &ProjectObject) -> Self {
|
|
|
|
Self {
|
|
|
|
name: object.name().to_string(),
|
|
|
|
target_path: object.target_path.clone(),
|
|
|
|
base_path: object.base_path.clone(),
|
|
|
|
reverse_fn_order: object.reverse_fn_order(),
|
|
|
|
complete: object.complete(),
|
|
|
|
scratch: object.scratch.clone(),
|
|
|
|
source_path: object.source_path().cloned(),
|
|
|
|
symbol_mappings: object.symbol_mappings.clone().unwrap_or_default(),
|
|
|
|
}
|
|
|
|
}
|
2023-09-03 13:28:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn bool_true() -> bool { true }
|
|
|
|
|
2023-10-05 03:52:00 +00:00
|
|
|
#[inline]
|
|
|
|
fn default_watch_patterns() -> Vec<Glob> {
|
|
|
|
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect()
|
|
|
|
}
|
|
|
|
|
2024-09-28 16:55:22 +00:00
|
|
|
pub struct AppState {
|
|
|
|
pub config: AppConfig,
|
|
|
|
pub objects: Vec<ProjectObject>,
|
|
|
|
pub object_nodes: Vec<ProjectObjectNode>,
|
|
|
|
pub watcher_change: bool,
|
|
|
|
pub config_change: bool,
|
|
|
|
pub obj_change: bool,
|
|
|
|
pub queue_build: bool,
|
|
|
|
pub queue_reload: bool,
|
2024-10-10 03:44:18 +00:00
|
|
|
pub current_project_config: Option<ProjectConfig>,
|
2024-09-28 16:55:22 +00:00
|
|
|
pub project_config_info: Option<ProjectConfigInfo>,
|
|
|
|
pub last_mod_check: Instant,
|
2024-10-10 03:44:18 +00:00
|
|
|
/// The right object symbol name that we're selecting a left symbol for
|
|
|
|
pub selecting_left: Option<String>,
|
|
|
|
/// The left object symbol name that we're selecting a right symbol for
|
|
|
|
pub selecting_right: Option<String>,
|
|
|
|
pub config_error: Option<String>,
|
2024-09-28 16:55:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for AppState {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
config: Default::default(),
|
|
|
|
objects: vec![],
|
|
|
|
object_nodes: vec![],
|
|
|
|
watcher_change: false,
|
|
|
|
config_change: false,
|
|
|
|
obj_change: false,
|
|
|
|
queue_build: false,
|
|
|
|
queue_reload: false,
|
2024-10-10 03:44:18 +00:00
|
|
|
current_project_config: None,
|
2024-09-28 16:55:22 +00:00
|
|
|
project_config_info: None,
|
|
|
|
last_mod_check: Instant::now(),
|
2024-10-10 03:44:18 +00:00
|
|
|
selecting_left: None,
|
|
|
|
selecting_right: None,
|
|
|
|
config_error: None,
|
2024-09-28 16:55:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-10 03:43:12 +00:00
|
|
|
#[derive(Clone, serde::Deserialize, serde::Serialize)]
|
2022-09-08 21:19:20 +00:00
|
|
|
pub struct AppConfig {
|
2023-09-10 03:43:12 +00:00
|
|
|
// TODO: https://github.com/ron-rs/ron/pull/455
|
|
|
|
// #[serde(flatten)]
|
|
|
|
// pub version: AppConfigVersion,
|
|
|
|
pub version: u32,
|
2023-10-05 03:52:00 +00:00
|
|
|
#[serde(default)]
|
2022-12-06 22:53:32 +00:00
|
|
|
pub custom_make: Option<String>,
|
2023-10-05 03:52:00 +00:00
|
|
|
#[serde(default)]
|
2024-05-16 00:53:14 +00:00
|
|
|
pub custom_args: Option<Vec<String>>,
|
|
|
|
#[serde(default)]
|
2022-09-12 00:31:58 +00:00
|
|
|
pub selected_wsl_distro: Option<String>,
|
2023-10-05 03:52:00 +00:00
|
|
|
#[serde(default)]
|
2022-09-08 21:19:20 +00:00
|
|
|
pub project_dir: Option<PathBuf>,
|
2023-10-05 03:52:00 +00:00
|
|
|
#[serde(default)]
|
2022-09-13 23:52:25 +00:00
|
|
|
pub target_obj_dir: Option<PathBuf>,
|
2023-10-05 03:52:00 +00:00
|
|
|
#[serde(default)]
|
2022-09-13 23:52:25 +00:00
|
|
|
pub base_obj_dir: Option<PathBuf>,
|
2023-10-05 03:52:00 +00:00
|
|
|
#[serde(default)]
|
2023-09-03 13:28:22 +00:00
|
|
|
pub selected_obj: Option<ObjectConfig>,
|
2023-10-07 18:48:34 +00:00
|
|
|
#[serde(default = "bool_true")]
|
|
|
|
pub build_base: bool,
|
2023-10-05 03:52:00 +00:00
|
|
|
#[serde(default)]
|
2022-09-13 23:52:25 +00:00
|
|
|
pub build_target: bool,
|
2023-09-03 13:28:22 +00:00
|
|
|
#[serde(default = "bool_true")]
|
|
|
|
pub rebuild_on_changes: bool,
|
2023-10-05 03:52:00 +00:00
|
|
|
#[serde(default)]
|
2022-12-06 22:53:32 +00:00
|
|
|
pub auto_update_check: bool,
|
2023-10-05 03:52:00 +00:00
|
|
|
#[serde(default = "default_watch_patterns")]
|
2023-08-08 00:11:56 +00:00
|
|
|
pub watch_patterns: Vec<Glob>,
|
2023-10-05 03:52:00 +00:00
|
|
|
#[serde(default)]
|
2023-10-03 17:52:16 +00:00
|
|
|
pub recent_projects: Vec<PathBuf>,
|
2023-11-21 16:48:18 +00:00
|
|
|
#[serde(default)]
|
2024-03-17 05:30:27 +00:00
|
|
|
pub diff_obj_config: DiffObjConfig,
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
|
|
|
|
2023-09-10 03:43:12 +00:00
|
|
|
impl Default for AppConfig {
|
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
|
|
|
version: AppConfigVersion::default().version,
|
|
|
|
custom_make: None,
|
2024-05-16 00:53:14 +00:00
|
|
|
custom_args: None,
|
2023-09-10 03:43:12 +00:00
|
|
|
selected_wsl_distro: None,
|
|
|
|
project_dir: None,
|
|
|
|
target_obj_dir: None,
|
|
|
|
base_obj_dir: None,
|
|
|
|
selected_obj: None,
|
2023-10-07 18:48:34 +00:00
|
|
|
build_base: true,
|
2023-09-10 03:43:12 +00:00
|
|
|
build_target: false,
|
|
|
|
rebuild_on_changes: true,
|
|
|
|
auto_update_check: true,
|
|
|
|
watch_patterns: DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(),
|
2023-10-03 17:52:16 +00:00
|
|
|
recent_projects: vec![],
|
2024-03-17 05:30:27 +00:00
|
|
|
diff_obj_config: Default::default(),
|
2023-09-10 03:43:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-28 16:55:22 +00:00
|
|
|
impl AppState {
|
2023-08-11 05:36:22 +00:00
|
|
|
pub fn set_project_dir(&mut self, path: PathBuf) {
|
2024-09-28 16:55:22 +00:00
|
|
|
self.config.recent_projects.retain(|p| p != &path);
|
|
|
|
if self.config.recent_projects.len() > 9 {
|
|
|
|
self.config.recent_projects.truncate(9);
|
2023-10-03 17:52:16 +00:00
|
|
|
}
|
2024-09-28 16:55:22 +00:00
|
|
|
self.config.recent_projects.insert(0, path.clone());
|
|
|
|
self.config.project_dir = Some(path);
|
|
|
|
self.config.target_obj_dir = None;
|
|
|
|
self.config.base_obj_dir = None;
|
|
|
|
self.config.selected_obj = None;
|
|
|
|
self.config.build_target = false;
|
2023-08-12 18:18:09 +00:00
|
|
|
self.objects.clear();
|
|
|
|
self.object_nodes.clear();
|
2023-08-11 05:36:22 +00:00
|
|
|
self.watcher_change = true;
|
|
|
|
self.config_change = true;
|
|
|
|
self.obj_change = true;
|
2023-08-12 18:18:09 +00:00
|
|
|
self.queue_build = false;
|
2024-10-10 03:44:18 +00:00
|
|
|
self.current_project_config = None;
|
2023-10-07 18:48:34 +00:00
|
|
|
self.project_config_info = None;
|
2024-10-10 03:44:18 +00:00
|
|
|
self.selecting_left = None;
|
|
|
|
self.selecting_right = None;
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_target_obj_dir(&mut self, path: PathBuf) {
|
2024-09-28 16:55:22 +00:00
|
|
|
self.config.target_obj_dir = Some(path);
|
|
|
|
self.config.selected_obj = None;
|
2023-08-11 05:36:22 +00:00
|
|
|
self.obj_change = true;
|
2023-08-12 18:18:09 +00:00
|
|
|
self.queue_build = false;
|
2024-10-10 03:44:18 +00:00
|
|
|
self.selecting_left = None;
|
|
|
|
self.selecting_right = None;
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_base_obj_dir(&mut self, path: PathBuf) {
|
2024-09-28 16:55:22 +00:00
|
|
|
self.config.base_obj_dir = Some(path);
|
|
|
|
self.config.selected_obj = None;
|
2023-08-11 05:36:22 +00:00
|
|
|
self.obj_change = true;
|
2023-08-12 18:18:09 +00:00
|
|
|
self.queue_build = false;
|
2024-10-10 03:44:18 +00:00
|
|
|
self.selecting_left = None;
|
|
|
|
self.selecting_right = None;
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
|
|
|
|
2024-10-10 03:44:18 +00:00
|
|
|
pub fn set_selected_obj(&mut self, config: ObjectConfig) {
|
2024-10-11 04:31:04 +00:00
|
|
|
let mut unit_changed = true;
|
|
|
|
if let Some(existing) = self.config.selected_obj.as_ref() {
|
|
|
|
if existing == &config {
|
|
|
|
// Don't reload the object if there were no changes
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if existing.name == config.name {
|
|
|
|
unit_changed = false;
|
|
|
|
}
|
2024-10-10 03:44:18 +00:00
|
|
|
}
|
|
|
|
self.config.selected_obj = Some(config);
|
2024-10-11 04:31:04 +00:00
|
|
|
if unit_changed {
|
|
|
|
self.obj_change = true;
|
|
|
|
self.queue_build = false;
|
|
|
|
self.selecting_left = None;
|
|
|
|
self.selecting_right = None;
|
|
|
|
} else {
|
|
|
|
self.queue_build = true;
|
|
|
|
}
|
2024-10-10 03:44:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clear_selected_obj(&mut self) {
|
|
|
|
self.config.selected_obj = None;
|
2023-08-11 05:36:22 +00:00
|
|
|
self.obj_change = true;
|
2023-08-12 18:18:09 +00:00
|
|
|
self.queue_build = false;
|
2024-10-10 03:44:18 +00:00
|
|
|
self.selecting_left = None;
|
|
|
|
self.selecting_right = None;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_selecting_left(&mut self, right: &str) {
|
|
|
|
let Some(object) = self.config.selected_obj.as_mut() else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
object.symbol_mappings.remove_by_right(right);
|
|
|
|
self.selecting_left = Some(right.to_string());
|
|
|
|
self.queue_reload = true;
|
|
|
|
self.save_config();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_selecting_right(&mut self, left: &str) {
|
|
|
|
let Some(object) = self.config.selected_obj.as_mut() else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
object.symbol_mappings.remove_by_left(left);
|
|
|
|
self.selecting_right = Some(left.to_string());
|
|
|
|
self.queue_reload = true;
|
|
|
|
self.save_config();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_symbol_mapping(&mut self, left: String, right: String) {
|
|
|
|
let Some(object) = self.config.selected_obj.as_mut() else {
|
|
|
|
log::warn!("No selected object");
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
self.selecting_left = None;
|
|
|
|
self.selecting_right = None;
|
|
|
|
if left == right {
|
|
|
|
object.symbol_mappings.remove_by_left(&left);
|
|
|
|
object.symbol_mappings.remove_by_right(&right);
|
|
|
|
} else {
|
|
|
|
object.symbol_mappings.insert(left.clone(), right.clone());
|
|
|
|
}
|
|
|
|
self.queue_reload = true;
|
|
|
|
self.save_config();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clear_selection(&mut self) {
|
|
|
|
self.selecting_left = None;
|
|
|
|
self.selecting_right = None;
|
|
|
|
self.queue_reload = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clear_mappings(&mut self) {
|
|
|
|
self.selecting_left = None;
|
|
|
|
self.selecting_right = None;
|
|
|
|
if let Some(object) = self.config.selected_obj.as_mut() {
|
|
|
|
object.symbol_mappings.clear();
|
|
|
|
}
|
|
|
|
self.queue_reload = true;
|
|
|
|
self.save_config();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn is_selecting_symbol(&self) -> bool {
|
|
|
|
self.selecting_left.is_some() || self.selecting_right.is_some()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn save_config(&mut self) {
|
|
|
|
let (Some(config), Some(info)) =
|
|
|
|
(self.current_project_config.as_mut(), self.project_config_info.as_mut())
|
|
|
|
else {
|
|
|
|
return;
|
|
|
|
};
|
|
|
|
// Update the project config with the current state
|
|
|
|
if let Some(object) = self.config.selected_obj.as_ref() {
|
|
|
|
if let Some(existing) = config.units.as_mut().and_then(|v| {
|
|
|
|
v.iter_mut().find(|u| u.name.as_ref().is_some_and(|n| n == &object.name))
|
|
|
|
}) {
|
|
|
|
existing.symbol_mappings = if object.symbol_mappings.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(object.symbol_mappings.clone())
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if let Some(existing) =
|
|
|
|
self.objects.iter_mut().find(|u| u.name.as_ref().is_some_and(|n| n == &object.name))
|
|
|
|
{
|
|
|
|
existing.symbol_mappings = if object.symbol_mappings.is_empty() {
|
|
|
|
None
|
|
|
|
} else {
|
|
|
|
Some(object.symbol_mappings.clone())
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Save the updated project config
|
|
|
|
match save_project_config(config, info) {
|
|
|
|
Ok(new_info) => *info = new_info,
|
|
|
|
Err(e) => {
|
|
|
|
log::error!("Failed to save project config: {e}");
|
|
|
|
self.config_error = Some(format!("Failed to save project config: {e}"));
|
|
|
|
}
|
|
|
|
}
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
2022-09-08 21:19:20 +00:00
|
|
|
}
|
|
|
|
|
2024-09-28 16:55:22 +00:00
|
|
|
pub type AppStateRef = Arc<RwLock<AppState>>;
|
2023-08-12 18:18:09 +00:00
|
|
|
|
2023-08-10 01:53:04 +00:00
|
|
|
#[derive(Default)]
|
2022-09-08 21:19:20 +00:00
|
|
|
pub struct App {
|
2023-08-10 01:53:04 +00:00
|
|
|
appearance: Appearance,
|
2022-09-08 21:19:20 +00:00
|
|
|
view_state: ViewState,
|
2024-09-28 16:55:22 +00:00
|
|
|
state: AppStateRef,
|
2022-09-08 21:19:20 +00:00
|
|
|
modified: Arc<AtomicBool>,
|
|
|
|
watcher: Option<notify::RecommendedWatcher>,
|
2024-06-06 00:00:37 +00:00
|
|
|
app_path: Option<PathBuf>,
|
2022-12-06 22:53:32 +00:00
|
|
|
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
|
|
|
|
should_relaunch: bool,
|
2022-09-08 21:19:20 +00:00
|
|
|
}
|
|
|
|
|
2023-09-10 03:43:12 +00:00
|
|
|
pub const APPEARANCE_KEY: &str = "appearance";
|
|
|
|
pub const CONFIG_KEY: &str = "app_config";
|
2022-09-08 21:19:20 +00:00
|
|
|
|
|
|
|
impl App {
|
|
|
|
/// Called once before the first frame.
|
2022-12-06 22:53:32 +00:00
|
|
|
pub fn new(
|
|
|
|
cc: &eframe::CreationContext<'_>,
|
|
|
|
utc_offset: UtcOffset,
|
|
|
|
relaunch_path: Rc<Mutex<Option<PathBuf>>>,
|
2024-06-06 00:00:37 +00:00
|
|
|
app_path: Option<PathBuf>,
|
|
|
|
graphics_config: GraphicsConfig,
|
|
|
|
graphics_config_path: Option<PathBuf>,
|
2022-12-06 22:53:32 +00:00
|
|
|
) -> Self {
|
2022-09-08 21:19:20 +00:00
|
|
|
// Load previous app state (if any).
|
|
|
|
// Note that you must enable the `persistence` feature for this to work.
|
2023-08-10 01:53:04 +00:00
|
|
|
let mut app = Self::default();
|
2022-09-08 21:19:20 +00:00
|
|
|
if let Some(storage) = cc.storage {
|
2023-08-10 01:53:04 +00:00
|
|
|
if let Some(appearance) = eframe::get_value::<Appearance>(storage, APPEARANCE_KEY) {
|
|
|
|
app.appearance = appearance;
|
|
|
|
}
|
2024-09-28 16:55:22 +00:00
|
|
|
if let Some(config) = deserialize_config(storage) {
|
|
|
|
let mut state = AppState { config, ..Default::default() };
|
|
|
|
if state.config.project_dir.is_some() {
|
|
|
|
state.config_change = true;
|
|
|
|
state.watcher_change = true;
|
2023-11-21 16:49:26 +00:00
|
|
|
}
|
2024-09-28 16:55:22 +00:00
|
|
|
if state.config.selected_obj.is_some() {
|
|
|
|
state.queue_build = true;
|
2023-08-10 01:53:04 +00:00
|
|
|
}
|
2024-09-28 16:55:22 +00:00
|
|
|
app.view_state.config_state.queue_check_update = state.config.auto_update_check;
|
|
|
|
app.state = Arc::new(RwLock::new(state));
|
2022-09-08 21:19:20 +00:00
|
|
|
}
|
|
|
|
}
|
2023-11-22 04:56:30 +00:00
|
|
|
app.appearance.init_fonts(&cc.egui_ctx);
|
2023-08-10 01:53:04 +00:00
|
|
|
app.appearance.utc_offset = utc_offset;
|
2024-06-06 00:00:37 +00:00
|
|
|
app.app_path = app_path;
|
2023-08-10 01:53:04 +00:00
|
|
|
app.relaunch_path = relaunch_path;
|
2024-06-06 00:00:37 +00:00
|
|
|
#[cfg(feature = "wgpu")]
|
|
|
|
if let Some(wgpu_render_state) = &cc.wgpu_render_state {
|
|
|
|
use eframe::egui_wgpu::wgpu::Backend;
|
|
|
|
let info = wgpu_render_state.adapter.get_info();
|
|
|
|
app.view_state.graphics_state.active_backend = match info.backend {
|
|
|
|
Backend::Empty => "Unknown",
|
|
|
|
Backend::Vulkan => "Vulkan",
|
|
|
|
Backend::Metal => "Metal",
|
|
|
|
Backend::Dx12 => "DirectX 12",
|
|
|
|
Backend::Gl => "OpenGL",
|
|
|
|
Backend::BrowserWebGpu => "WebGPU",
|
|
|
|
}
|
|
|
|
.to_string();
|
|
|
|
app.view_state.graphics_state.active_device.clone_from(&info.name);
|
|
|
|
}
|
|
|
|
#[cfg(feature = "glow")]
|
|
|
|
if let Some(gl) = &cc.gl {
|
|
|
|
use eframe::glow::HasContext;
|
2024-08-11 19:33:10 +00:00
|
|
|
app.view_state.graphics_state.active_backend = "OpenGL (Fallback)".to_string();
|
2024-06-06 00:00:37 +00:00
|
|
|
app.view_state.graphics_state.active_device =
|
|
|
|
unsafe { gl.get_parameter_string(0x1F01) }; // GL_RENDERER
|
|
|
|
}
|
|
|
|
app.view_state.graphics_state.graphics_config = graphics_config;
|
|
|
|
app.view_state.graphics_state.graphics_config_path = graphics_config_path;
|
2023-08-10 01:53:04 +00:00
|
|
|
app
|
2022-09-08 21:19:20 +00:00
|
|
|
}
|
2023-08-11 05:36:22 +00:00
|
|
|
|
2023-11-22 04:56:30 +00:00
|
|
|
fn pre_update(&mut self, ctx: &egui::Context) {
|
|
|
|
self.appearance.pre_update(ctx);
|
|
|
|
|
2023-08-11 05:36:22 +00:00
|
|
|
let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state;
|
|
|
|
|
2023-08-12 18:18:09 +00:00
|
|
|
let mut results = vec![];
|
2023-08-11 05:36:22 +00:00
|
|
|
for (job, result) in jobs.iter_finished() {
|
|
|
|
match result {
|
|
|
|
Ok(result) => {
|
|
|
|
log::info!("Job {} finished", job.id);
|
|
|
|
match result {
|
|
|
|
JobResult::None => {
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
if let Some(err) = &job.context.status.read().unwrap().error {
|
2023-08-11 05:36:22 +00:00
|
|
|
log::error!("{:?}", err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
JobResult::Update(state) => {
|
|
|
|
if let Ok(mut guard) = self.relaunch_path.lock() {
|
|
|
|
*guard = Some(state.exe_path);
|
2024-06-06 00:00:37 +00:00
|
|
|
self.should_relaunch = true;
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
|
|
|
}
|
2023-08-12 18:18:09 +00:00
|
|
|
_ => results.push(result),
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(err) => {
|
|
|
|
let err = if let Some(msg) = err.downcast_ref::<&'static str>() {
|
|
|
|
anyhow::Error::msg(*msg)
|
|
|
|
} else if let Some(msg) = err.downcast_ref::<String>() {
|
|
|
|
anyhow::Error::msg(msg.clone())
|
|
|
|
} else {
|
|
|
|
anyhow::Error::msg("Thread panicked")
|
|
|
|
};
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
let result = job.context.status.write();
|
2023-08-11 05:36:22 +00:00
|
|
|
if let Ok(mut guard) = result {
|
|
|
|
guard.error = Some(err);
|
|
|
|
} else {
|
|
|
|
drop(result);
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
job.context.status = Arc::new(RwLock::new(JobStatus {
|
2023-08-11 05:36:22 +00:00
|
|
|
title: "Error".to_string(),
|
|
|
|
progress_percent: 0.0,
|
|
|
|
progress_items: None,
|
2024-03-17 18:06:18 +00:00
|
|
|
status: String::new(),
|
2023-08-11 05:36:22 +00:00
|
|
|
error: Some(err),
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-08-12 18:18:09 +00:00
|
|
|
jobs.results.append(&mut results);
|
2023-08-11 05:36:22 +00:00
|
|
|
jobs.clear_finished();
|
2023-08-12 18:18:09 +00:00
|
|
|
|
2024-09-28 16:55:22 +00:00
|
|
|
diff_state.pre_update(jobs, &self.state);
|
|
|
|
config_state.pre_update(jobs, &self.state);
|
2023-08-12 18:18:09 +00:00
|
|
|
debug_assert!(jobs.results.is_empty());
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
|
|
|
|
2024-10-10 03:44:18 +00:00
|
|
|
fn post_update(&mut self, ctx: &egui::Context, action: Option<DiffViewAction>) {
|
2023-11-22 04:56:30 +00:00
|
|
|
self.appearance.post_update(ctx);
|
|
|
|
|
2024-06-06 00:00:37 +00:00
|
|
|
let ViewState { jobs, diff_state, config_state, graphics_state, .. } = &mut self.view_state;
|
2024-09-28 16:55:22 +00:00
|
|
|
config_state.post_update(ctx, jobs, &self.state);
|
2024-10-10 03:44:18 +00:00
|
|
|
diff_state.post_update(action, ctx, jobs, &self.state);
|
2023-08-12 18:18:09 +00:00
|
|
|
|
2024-09-28 16:55:22 +00:00
|
|
|
let Ok(mut state) = self.state.write() else {
|
2023-08-11 05:36:22 +00:00
|
|
|
return;
|
|
|
|
};
|
2024-09-28 16:55:22 +00:00
|
|
|
let state = &mut *state;
|
2023-08-11 05:36:22 +00:00
|
|
|
|
2024-10-10 03:44:18 +00:00
|
|
|
let mut mod_check = false;
|
|
|
|
if state.last_mod_check.elapsed().as_millis() >= 500 {
|
|
|
|
state.last_mod_check = Instant::now();
|
|
|
|
mod_check = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if mod_check {
|
|
|
|
if let Some(info) = &state.project_config_info {
|
|
|
|
if let Some(last_ts) = info.timestamp {
|
|
|
|
if file_modified(&info.path, last_ts) {
|
|
|
|
state.config_change = true;
|
|
|
|
}
|
|
|
|
}
|
2023-10-07 18:48:34 +00:00
|
|
|
}
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
|
|
|
|
2024-09-28 16:55:22 +00:00
|
|
|
if state.config_change {
|
|
|
|
state.config_change = false;
|
|
|
|
match load_project_config(state) {
|
2024-10-10 03:44:18 +00:00
|
|
|
Ok(()) => state.config_error = None,
|
2023-08-11 05:36:22 +00:00
|
|
|
Err(e) => {
|
|
|
|
log::error!("Failed to load project config: {e}");
|
2024-10-10 03:44:18 +00:00
|
|
|
state.config_error = Some(format!("{e}"));
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-28 16:55:22 +00:00
|
|
|
if state.watcher_change {
|
2023-08-11 05:36:22 +00:00
|
|
|
drop(self.watcher.take());
|
|
|
|
|
2024-09-28 16:55:22 +00:00
|
|
|
if let Some(project_dir) = &state.config.project_dir {
|
|
|
|
match build_globset(&state.config.watch_patterns)
|
|
|
|
.map_err(anyhow::Error::new)
|
|
|
|
.and_then(|globset| {
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
create_watcher(ctx.clone(), self.modified.clone(), project_dir, globset)
|
2023-08-11 05:36:22 +00:00
|
|
|
.map_err(anyhow::Error::new)
|
2024-09-28 16:55:22 +00:00
|
|
|
}) {
|
2023-10-07 18:48:34 +00:00
|
|
|
Ok(watcher) => self.watcher = Some(watcher),
|
|
|
|
Err(e) => log::error!("Failed to create watcher: {e}"),
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
2024-09-28 16:55:22 +00:00
|
|
|
state.watcher_change = false;
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-28 16:55:22 +00:00
|
|
|
if state.obj_change {
|
2023-08-11 05:36:22 +00:00
|
|
|
*diff_state = Default::default();
|
2024-09-28 16:55:22 +00:00
|
|
|
if state.config.selected_obj.is_some() {
|
|
|
|
state.queue_build = true;
|
2023-08-12 18:18:09 +00:00
|
|
|
}
|
2024-09-28 16:55:22 +00:00
|
|
|
state.obj_change = false;
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
|
|
|
|
2024-09-28 16:55:22 +00:00
|
|
|
if self.modified.swap(false, Ordering::Relaxed) && state.config.rebuild_on_changes {
|
|
|
|
state.queue_build = true;
|
2023-08-12 18:18:09 +00:00
|
|
|
}
|
|
|
|
|
2023-10-07 18:48:34 +00:00
|
|
|
if let Some(result) = &diff_state.build {
|
2024-10-10 03:44:18 +00:00
|
|
|
if mod_check {
|
2024-09-28 16:55:22 +00:00
|
|
|
if let Some((obj, _)) = &result.first_obj {
|
|
|
|
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
|
|
|
|
if file_modified(path, timestamp) {
|
|
|
|
state.queue_reload = true;
|
|
|
|
}
|
2024-08-21 03:40:32 +00:00
|
|
|
}
|
2023-10-07 18:48:34 +00:00
|
|
|
}
|
2024-09-28 16:55:22 +00:00
|
|
|
if let Some((obj, _)) = &result.second_obj {
|
|
|
|
if let (Some(path), Some(timestamp)) = (&obj.path, obj.timestamp) {
|
|
|
|
if file_modified(path, timestamp) {
|
|
|
|
state.queue_reload = true;
|
|
|
|
}
|
2024-08-21 03:40:32 +00:00
|
|
|
}
|
2023-10-07 18:48:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-12 18:18:09 +00:00
|
|
|
// Don't clear `queue_build` if a build is running. A file may have been modified during
|
|
|
|
// the build, so we'll start another build after the current one finishes.
|
2024-09-28 16:55:22 +00:00
|
|
|
if state.queue_build
|
|
|
|
&& state.config.selected_obj.is_some()
|
|
|
|
&& !jobs.is_running(Job::ObjDiff)
|
|
|
|
{
|
2024-10-10 03:44:18 +00:00
|
|
|
jobs.push(start_build(ctx, ObjDiffConfig::from_state(state)));
|
2024-09-28 16:55:22 +00:00
|
|
|
state.queue_build = false;
|
|
|
|
state.queue_reload = false;
|
|
|
|
} else if state.queue_reload && !jobs.is_running(Job::ObjDiff) {
|
2024-10-10 03:44:18 +00:00
|
|
|
let mut diff_config = ObjDiffConfig::from_state(state);
|
2023-10-07 18:48:34 +00:00
|
|
|
// Don't build, just reload the current files
|
|
|
|
diff_config.build_base = false;
|
|
|
|
diff_config.build_target = false;
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
jobs.push(start_build(ctx, diff_config));
|
2024-09-28 16:55:22 +00:00
|
|
|
state.queue_reload = false;
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
2024-06-06 00:00:37 +00:00
|
|
|
|
|
|
|
if graphics_state.should_relaunch {
|
|
|
|
if let Some(app_path) = &self.app_path {
|
|
|
|
if let Ok(mut guard) = self.relaunch_path.lock() {
|
|
|
|
*guard = Some(app_path.clone());
|
|
|
|
self.should_relaunch = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-08-11 05:36:22 +00:00
|
|
|
}
|
2022-09-08 21:19:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl eframe::App for App {
|
|
|
|
/// Called each time the UI needs repainting, which may be many times per second.
|
|
|
|
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
|
2022-12-06 22:53:32 +00:00
|
|
|
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
|
|
|
if self.should_relaunch {
|
2023-12-11 18:36:00 +00:00
|
|
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
2022-12-06 22:53:32 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-11-22 04:56:30 +00:00
|
|
|
self.pre_update(ctx);
|
2023-08-11 05:36:22 +00:00
|
|
|
|
2024-09-28 16:55:22 +00:00
|
|
|
let Self { state, appearance, view_state, .. } = self;
|
2023-08-10 01:53:04 +00:00
|
|
|
let ViewState {
|
|
|
|
jobs,
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
config_state,
|
2023-08-10 01:53:04 +00:00
|
|
|
demangle_state,
|
2024-07-21 23:56:46 +00:00
|
|
|
rlwinm_decode_state,
|
2023-08-10 01:53:04 +00:00
|
|
|
diff_state,
|
2024-06-06 00:00:37 +00:00
|
|
|
graphics_state,
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
frame_history,
|
|
|
|
show_appearance_config,
|
|
|
|
show_demangle,
|
2024-07-21 23:56:46 +00:00
|
|
|
show_rlwinm_decode,
|
2023-08-10 01:53:04 +00:00
|
|
|
show_project_config,
|
2024-05-22 00:06:14 +00:00
|
|
|
show_arch_config,
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
show_debug,
|
2024-06-06 00:00:37 +00:00
|
|
|
show_graphics,
|
2024-09-28 18:13:46 +00:00
|
|
|
show_jobs,
|
2024-10-08 01:46:16 +00:00
|
|
|
show_side_panel,
|
2023-08-10 01:53:04 +00:00
|
|
|
} = view_state;
|
2022-12-06 22:53:32 +00:00
|
|
|
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
frame_history.on_new_frame(ctx.input(|i| i.time), frame.info().cpu_usage);
|
|
|
|
|
2024-10-08 01:46:16 +00:00
|
|
|
let side_panel_available = diff_state.current_view == View::SymbolDiff;
|
|
|
|
|
2022-09-08 21:19:20 +00:00
|
|
|
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
|
|
|
egui::menu::bar(ui, |ui| {
|
2024-10-08 01:46:16 +00:00
|
|
|
if ui
|
|
|
|
.add_enabled(
|
|
|
|
side_panel_available,
|
|
|
|
egui::Button::new(if *show_side_panel { "⏴" } else { "⏵" }),
|
|
|
|
)
|
|
|
|
.on_hover_text("Toggle side panel")
|
|
|
|
.clicked()
|
|
|
|
{
|
|
|
|
*show_side_panel = !*show_side_panel;
|
|
|
|
}
|
|
|
|
ui.separator();
|
2022-09-08 21:19:20 +00:00
|
|
|
ui.menu_button("File", |ui| {
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
#[cfg(debug_assertions)]
|
|
|
|
if ui.button("Debug…").clicked() {
|
|
|
|
*show_debug = !*show_debug;
|
|
|
|
ui.close_menu();
|
|
|
|
}
|
2023-10-07 17:27:12 +00:00
|
|
|
if ui.button("Project…").clicked() {
|
|
|
|
*show_project_config = !*show_project_config;
|
|
|
|
ui.close_menu();
|
|
|
|
}
|
2024-09-28 16:55:22 +00:00
|
|
|
let recent_projects = if let Ok(guard) = state.read() {
|
|
|
|
guard.config.recent_projects.clone()
|
2023-10-03 17:52:16 +00:00
|
|
|
} else {
|
|
|
|
vec![]
|
|
|
|
};
|
|
|
|
if recent_projects.is_empty() {
|
2023-10-07 17:27:12 +00:00
|
|
|
ui.add_enabled(false, egui::Button::new("Recent projects…"));
|
2023-10-03 17:52:16 +00:00
|
|
|
} else {
|
|
|
|
ui.menu_button("Recent Projects…", |ui| {
|
2023-10-07 17:27:12 +00:00
|
|
|
if ui.button("Clear").clicked() {
|
2024-09-28 16:55:22 +00:00
|
|
|
state.write().unwrap().config.recent_projects.clear();
|
2023-10-07 17:27:12 +00:00
|
|
|
};
|
|
|
|
ui.separator();
|
2023-10-03 17:52:16 +00:00
|
|
|
for path in recent_projects {
|
|
|
|
if ui.button(format!("{}", path.display())).clicked() {
|
2024-09-28 16:55:22 +00:00
|
|
|
state.write().unwrap().set_project_dir(path);
|
2023-10-03 17:52:16 +00:00
|
|
|
ui.close_menu();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2023-08-08 00:11:56 +00:00
|
|
|
if ui.button("Appearance…").clicked() {
|
2023-08-10 01:53:04 +00:00
|
|
|
*show_appearance_config = !*show_appearance_config;
|
2023-08-11 05:36:22 +00:00
|
|
|
ui.close_menu();
|
2022-12-06 22:53:32 +00:00
|
|
|
}
|
2024-06-06 00:00:37 +00:00
|
|
|
if ui.button("Graphics…").clicked() {
|
|
|
|
*show_graphics = !*show_graphics;
|
|
|
|
ui.close_menu();
|
|
|
|
}
|
2022-09-08 21:19:20 +00:00
|
|
|
if ui.button("Quit").clicked() {
|
2023-12-11 18:36:00 +00:00
|
|
|
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
|
2022-09-08 21:19:20 +00:00
|
|
|
}
|
2022-12-06 22:53:32 +00:00
|
|
|
});
|
|
|
|
ui.menu_button("Tools", |ui| {
|
2023-08-08 00:11:56 +00:00
|
|
|
if ui.button("Demangle…").clicked() {
|
2023-08-10 01:53:04 +00:00
|
|
|
*show_demangle = !*show_demangle;
|
2023-08-11 05:36:22 +00:00
|
|
|
ui.close_menu();
|
2022-09-11 17:52:55 +00:00
|
|
|
}
|
2024-07-21 23:56:46 +00:00
|
|
|
if ui.button("Rlwinm Decoder…").clicked() {
|
|
|
|
*show_rlwinm_decode = !*show_rlwinm_decode;
|
|
|
|
ui.close_menu();
|
|
|
|
}
|
2022-09-08 21:19:20 +00:00
|
|
|
});
|
2023-10-07 17:27:12 +00:00
|
|
|
ui.menu_button("Diff Options", |ui| {
|
2024-05-22 00:06:14 +00:00
|
|
|
if ui.button("Arch Settings…").clicked() {
|
|
|
|
*show_arch_config = !*show_arch_config;
|
2024-03-17 05:30:27 +00:00
|
|
|
ui.close_menu();
|
|
|
|
}
|
2024-09-28 16:55:22 +00:00
|
|
|
let mut state = state.write().unwrap();
|
2023-10-07 17:27:12 +00:00
|
|
|
let response = ui
|
2024-09-28 16:55:22 +00:00
|
|
|
.checkbox(&mut state.config.rebuild_on_changes, "Rebuild on changes")
|
2023-10-07 17:27:12 +00:00
|
|
|
.on_hover_text("Automatically re-run the build & diff when files change.");
|
|
|
|
if response.changed() {
|
2024-09-28 16:55:22 +00:00
|
|
|
state.watcher_change = true;
|
2023-10-07 17:27:12 +00:00
|
|
|
};
|
|
|
|
ui.add_enabled(
|
|
|
|
!diff_state.symbol_state.disable_reverse_fn_order,
|
|
|
|
egui::Checkbox::new(
|
|
|
|
&mut diff_state.symbol_state.reverse_fn_order,
|
|
|
|
"Reverse function order (-inline deferred)",
|
|
|
|
),
|
|
|
|
)
|
2024-01-22 06:58:10 +00:00
|
|
|
.on_disabled_hover_text(CONFIG_DISABLED_TEXT);
|
2023-10-07 17:27:12 +00:00
|
|
|
ui.checkbox(
|
|
|
|
&mut diff_state.symbol_state.show_hidden_symbols,
|
|
|
|
"Show hidden symbols",
|
|
|
|
);
|
2024-01-22 07:14:03 +00:00
|
|
|
if ui
|
2024-03-17 05:30:27 +00:00
|
|
|
.checkbox(
|
2024-09-28 16:55:22 +00:00
|
|
|
&mut state.config.diff_obj_config.relax_reloc_diffs,
|
2024-03-17 05:30:27 +00:00
|
|
|
"Relax relocation diffs",
|
|
|
|
)
|
2024-01-22 07:14:03 +00:00
|
|
|
.on_hover_text(
|
|
|
|
"Ignores differences in relocation targets. (Address, name, etc)",
|
|
|
|
)
|
|
|
|
.changed()
|
|
|
|
{
|
2024-09-28 16:55:22 +00:00
|
|
|
state.queue_reload = true;
|
2024-01-22 07:14:03 +00:00
|
|
|
}
|
2024-03-17 05:30:27 +00:00
|
|
|
if ui
|
|
|
|
.checkbox(
|
2024-09-28 16:55:22 +00:00
|
|
|
&mut state.config.diff_obj_config.space_between_args,
|
2024-03-17 05:30:27 +00:00
|
|
|
"Space between args",
|
|
|
|
)
|
|
|
|
.changed()
|
|
|
|
{
|
2024-09-28 16:55:22 +00:00
|
|
|
state.queue_reload = true;
|
2024-03-17 05:30:27 +00:00
|
|
|
}
|
2024-06-19 04:05:24 +00:00
|
|
|
if ui
|
|
|
|
.checkbox(
|
2024-09-28 16:55:22 +00:00
|
|
|
&mut state.config.diff_obj_config.combine_data_sections,
|
2024-06-19 04:05:24 +00:00
|
|
|
"Combine data sections",
|
|
|
|
)
|
|
|
|
.on_hover_text("Combines data sections with equal names.")
|
|
|
|
.changed()
|
|
|
|
{
|
2024-09-28 16:55:22 +00:00
|
|
|
state.queue_reload = true;
|
2024-06-19 04:05:24 +00:00
|
|
|
}
|
2024-10-10 03:44:18 +00:00
|
|
|
if ui.button("Clear custom symbol mappings").clicked() {
|
|
|
|
state.clear_mappings();
|
|
|
|
diff_state.post_build_nav = Some(DiffViewNavigation::symbol_diff());
|
|
|
|
state.queue_reload = true;
|
|
|
|
}
|
2023-10-07 17:27:12 +00:00
|
|
|
});
|
2024-09-28 18:13:46 +00:00
|
|
|
ui.separator();
|
|
|
|
if jobs_menu_ui(ui, jobs, appearance) {
|
|
|
|
*show_jobs = !*show_jobs;
|
|
|
|
}
|
2022-09-08 21:19:20 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2024-10-08 01:46:16 +00:00
|
|
|
if side_panel_available {
|
|
|
|
egui::SidePanel::left("side_panel").show_animated(ctx, *show_side_panel, |ui| {
|
2023-08-11 05:36:22 +00:00
|
|
|
egui::ScrollArea::both().show(ui, |ui| {
|
2024-09-28 16:55:22 +00:00
|
|
|
config_ui(ui, state, show_project_config, config_state, appearance);
|
2023-08-11 05:36:22 +00:00
|
|
|
});
|
2022-09-08 21:19:20 +00:00
|
|
|
});
|
2024-10-08 01:46:16 +00:00
|
|
|
}
|
2022-09-08 21:19:20 +00:00
|
|
|
|
2024-10-10 03:44:18 +00:00
|
|
|
let mut action = None;
|
2024-10-08 01:46:16 +00:00
|
|
|
egui::CentralPanel::default().show(ctx, |ui| {
|
|
|
|
let build_success = matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success);
|
2024-10-10 03:44:18 +00:00
|
|
|
action = if diff_state.current_view == View::FunctionDiff && build_success {
|
|
|
|
function_diff_ui(ui, diff_state, appearance)
|
2024-10-08 01:46:16 +00:00
|
|
|
} else if diff_state.current_view == View::DataDiff && build_success {
|
2024-10-10 03:44:18 +00:00
|
|
|
data_diff_ui(ui, diff_state, appearance)
|
2024-10-08 01:46:16 +00:00
|
|
|
} else if diff_state.current_view == View::ExtabDiff && build_success {
|
2024-10-10 03:44:18 +00:00
|
|
|
extab_diff_ui(ui, diff_state, appearance)
|
2024-10-08 01:46:16 +00:00
|
|
|
} else {
|
2024-10-10 03:44:18 +00:00
|
|
|
symbol_diff_ui(ui, diff_state, appearance)
|
|
|
|
};
|
2024-10-08 01:46:16 +00:00
|
|
|
});
|
2022-09-08 21:19:20 +00:00
|
|
|
|
2024-09-28 16:55:22 +00:00
|
|
|
project_window(ctx, state, show_project_config, config_state, appearance);
|
2023-08-10 01:53:04 +00:00
|
|
|
appearance_window(ctx, show_appearance_config, appearance);
|
|
|
|
demangle_window(ctx, show_demangle, demangle_state, appearance);
|
2024-07-21 23:56:46 +00:00
|
|
|
rlwinm_decode_window(ctx, show_rlwinm_decode, rlwinm_decode_state, appearance);
|
2024-09-28 16:55:22 +00:00
|
|
|
arch_config_window(ctx, state, show_arch_config, appearance);
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
debug_window(ctx, show_debug, frame_history, appearance);
|
2024-06-06 00:00:37 +00:00
|
|
|
graphics_window(ctx, show_graphics, frame_history, graphics_state, appearance);
|
2024-09-28 18:13:46 +00:00
|
|
|
jobs_window(ctx, show_jobs, jobs, appearance);
|
2022-09-11 17:52:55 +00:00
|
|
|
|
2024-10-10 03:44:18 +00:00
|
|
|
self.post_update(ctx, action);
|
2022-09-08 21:19:20 +00:00
|
|
|
}
|
|
|
|
|
2024-10-10 03:44:18 +00:00
|
|
|
/// Called by the framework to save state before shutdown.
|
2022-09-08 21:19:20 +00:00
|
|
|
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
2024-09-28 16:55:22 +00:00
|
|
|
if let Ok(state) = self.state.read() {
|
|
|
|
eframe::set_value(storage, CONFIG_KEY, &state.config);
|
2022-09-08 21:19:20 +00:00
|
|
|
}
|
2023-08-10 01:53:04 +00:00
|
|
|
eframe::set_value(storage, APPEARANCE_KEY, &self.appearance);
|
2022-09-08 21:19:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn create_watcher(
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
ctx: egui::Context,
|
2022-09-08 21:19:20 +00:00
|
|
|
modified: Arc<AtomicBool>,
|
|
|
|
project_dir: &Path,
|
2023-08-08 00:11:56 +00:00
|
|
|
patterns: GlobSet,
|
2022-09-08 21:19:20 +00:00
|
|
|
) -> notify::Result<notify::RecommendedWatcher> {
|
2023-08-14 03:56:13 +00:00
|
|
|
let base_dir = project_dir.to_owned();
|
2022-09-08 21:19:20 +00:00
|
|
|
let mut watcher =
|
|
|
|
notify::recommended_watcher(move |res: notify::Result<notify::Event>| match res {
|
|
|
|
Ok(event) => {
|
2023-08-14 03:56:13 +00:00
|
|
|
if matches!(
|
|
|
|
event.kind,
|
|
|
|
notify::EventKind::Modify(..)
|
|
|
|
| notify::EventKind::Create(..)
|
|
|
|
| notify::EventKind::Remove(..)
|
|
|
|
) {
|
2023-08-08 00:11:56 +00:00
|
|
|
for path in &event.paths {
|
2023-08-14 03:56:13 +00:00
|
|
|
let Ok(path) = path.strip_prefix(&base_dir) else {
|
|
|
|
continue;
|
|
|
|
};
|
2023-10-07 18:48:34 +00:00
|
|
|
if patterns.is_match(path) {
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
log::info!("File modified: {}", path.display());
|
2023-08-08 00:11:56 +00:00
|
|
|
modified.store(true, Ordering::Relaxed);
|
Repaint rework: more responsive, less energy
Previously, we repainted every frame on Windows at full refresh rate.
This is an enormous waste, as the UI will be static most of the time.
This was to work around a bug with `rfd` + `eframe`.
On other platforms, we only repainted every frame when a job was running,
which was better, but still not ideal. We also had a 100ms deadline, so
we'd repaint at ~10fps minimum to catch new events (file watcher, jobs).
This removes all repaint logic from the main loop and moves it into the
individual places where we change state from another thread.
For example, the file watcher thread will now immediately notify egui
to repaint, rather than relying on the 100ms deadline we had previously.
Jobs, when updating their status, also notify egui to repaint.
For `rfd` file dialogs, this migrates to using the async API built on top of
a polling thread + `pollster`. This interacts better with `eframe` on Windows.
Overall, this should reduce repaints and improve responsiveness to
file changes and background tasks.
2023-11-21 19:34:26 +00:00
|
|
|
ctx.request_repaint();
|
2023-08-08 00:11:56 +00:00
|
|
|
}
|
2022-09-08 21:19:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-07-06 14:37:57 +00:00
|
|
|
Err(e) => log::error!("watch error: {e:?}"),
|
2022-09-08 21:19:20 +00:00
|
|
|
})?;
|
|
|
|
watcher.watch(project_dir, RecursiveMode::Recursive)?;
|
|
|
|
Ok(watcher)
|
|
|
|
}
|
2023-10-07 18:48:34 +00:00
|
|
|
|
|
|
|
#[inline]
|
|
|
|
fn file_modified(path: &Path, last_ts: FileTime) -> bool {
|
|
|
|
if let Ok(metadata) = fs::metadata(path) {
|
|
|
|
FileTime::from_last_modification_time(&metadata) != last_ts
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|