mirror of https://github.com/encounter/objdiff.git
Updates to Objects pane & config improvements
This commit is contained in:
parent
91d11c83d6
commit
eaf0fabc2d
330
src/app.rs
330
src/app.rs
|
@ -61,6 +61,40 @@ pub struct AppConfig {
|
|||
pub watcher_change: bool,
|
||||
#[serde(skip)]
|
||||
pub config_change: bool,
|
||||
#[serde(skip)]
|
||||
pub obj_change: bool,
|
||||
}
|
||||
|
||||
impl AppConfig {
|
||||
pub fn set_project_dir(&mut self, path: PathBuf) {
|
||||
self.project_dir = Some(path);
|
||||
self.target_obj_dir = None;
|
||||
self.base_obj_dir = None;
|
||||
self.obj_path = None;
|
||||
self.build_target = false;
|
||||
self.units.clear();
|
||||
self.unit_nodes.clear();
|
||||
self.watcher_change = true;
|
||||
self.config_change = true;
|
||||
self.obj_change = true;
|
||||
}
|
||||
|
||||
pub fn set_target_obj_dir(&mut self, path: PathBuf) {
|
||||
self.target_obj_dir = Some(path);
|
||||
self.obj_path = None;
|
||||
self.obj_change = true;
|
||||
}
|
||||
|
||||
pub fn set_base_obj_dir(&mut self, path: PathBuf) {
|
||||
self.base_obj_dir = Some(path);
|
||||
self.obj_path = None;
|
||||
self.obj_change = true;
|
||||
}
|
||||
|
||||
pub fn set_obj_path(&mut self, path: String) {
|
||||
self.obj_path = Some(path);
|
||||
self.obj_change = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -109,97 +143,8 @@ impl App {
|
|||
app.relaunch_path = relaunch_path;
|
||||
app
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for App {
|
||||
/// Called each time the UI needs repainting, which may be many times per second.
|
||||
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
if self.should_relaunch {
|
||||
frame.close();
|
||||
return;
|
||||
}
|
||||
|
||||
let Self { config, appearance, view_state, .. } = self;
|
||||
ctx.set_style(appearance.apply(ctx.style().as_ref()));
|
||||
|
||||
let ViewState {
|
||||
jobs,
|
||||
show_appearance_config,
|
||||
demangle_state,
|
||||
show_demangle,
|
||||
diff_state,
|
||||
config_state,
|
||||
show_project_config,
|
||||
} = view_state;
|
||||
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("File", |ui| {
|
||||
if ui.button("Appearance…").clicked() {
|
||||
*show_appearance_config = !*show_appearance_config;
|
||||
}
|
||||
if ui.button("Quit").clicked() {
|
||||
frame.close();
|
||||
}
|
||||
});
|
||||
ui.menu_button("Tools", |ui| {
|
||||
if ui.button("Demangle…").clicked() {
|
||||
*show_demangle = !*show_demangle;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if diff_state.current_view == View::FunctionDiff
|
||||
&& matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success)
|
||||
{
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if function_diff_ui(ui, jobs, diff_state, appearance) {
|
||||
jobs.push(start_build(config.clone()));
|
||||
}
|
||||
});
|
||||
} else if diff_state.current_view == View::DataDiff
|
||||
&& matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success)
|
||||
{
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if data_diff_ui(ui, jobs, diff_state, appearance) {
|
||||
jobs.push(start_build(config.clone()));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
egui::SidePanel::left("side_panel").show(ctx, |ui| {
|
||||
config_ui(ui, config, jobs, show_project_config, config_state, appearance);
|
||||
jobs_ui(ui, jobs, appearance);
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
symbol_diff_ui(ui, diff_state, appearance);
|
||||
});
|
||||
}
|
||||
|
||||
project_window(ctx, config, show_project_config, config_state, appearance);
|
||||
appearance_window(ctx, show_appearance_config, appearance);
|
||||
demangle_window(ctx, show_demangle, demangle_state, appearance);
|
||||
|
||||
// Windows + request_repaint_after breaks dialogs:
|
||||
// https://github.com/emilk/egui/issues/2003
|
||||
if cfg!(windows) || jobs.any_running() {
|
||||
ctx.request_repaint();
|
||||
} else {
|
||||
ctx.request_repaint_after(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
||||
/// Called by the frame work to save state before shutdown.
|
||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||
if let Ok(config) = self.config.read() {
|
||||
eframe::set_value(storage, CONFIG_KEY, &*config);
|
||||
}
|
||||
eframe::set_value(storage, APPEARANCE_KEY, &self.appearance);
|
||||
}
|
||||
|
||||
fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &eframe::Frame) {
|
||||
fn pre_update(&mut self) {
|
||||
let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state;
|
||||
|
||||
for (job, result) in jobs.iter_finished() {
|
||||
|
@ -251,61 +196,168 @@ impl eframe::App for App {
|
|||
}
|
||||
}
|
||||
jobs.clear_finished();
|
||||
}
|
||||
|
||||
if let Ok(mut config) = self.config.write() {
|
||||
let config = &mut *config;
|
||||
fn post_update(&mut self) {
|
||||
let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state;
|
||||
let Ok(mut config) = self.config.write() else {
|
||||
return;
|
||||
};
|
||||
let config = &mut *config;
|
||||
|
||||
if self.config_modified.swap(false, Ordering::Relaxed) {
|
||||
config.config_change = true;
|
||||
}
|
||||
if self.config_modified.swap(false, Ordering::Relaxed) {
|
||||
config.config_change = true;
|
||||
}
|
||||
|
||||
if config.config_change {
|
||||
config.config_change = false;
|
||||
match load_project_config(config) {
|
||||
Ok(()) => config_state.load_error = None,
|
||||
Err(e) => {
|
||||
log::error!("Failed to load project config: {e}");
|
||||
config_state.load_error = Some(format!("{e}"));
|
||||
}
|
||||
if config.config_change {
|
||||
config.config_change = false;
|
||||
match load_project_config(config) {
|
||||
Ok(()) => config_state.load_error = None,
|
||||
Err(e) => {
|
||||
log::error!("Failed to load project config: {e}");
|
||||
config_state.load_error = Some(format!("{e}"));
|
||||
}
|
||||
}
|
||||
|
||||
if config.watcher_change {
|
||||
drop(self.watcher.take());
|
||||
|
||||
if let Some(project_dir) = &config.project_dir {
|
||||
if !config.watch_patterns.is_empty() {
|
||||
match build_globset(&config.watch_patterns)
|
||||
.map_err(anyhow::Error::new)
|
||||
.and_then(|globset| {
|
||||
create_watcher(
|
||||
self.modified.clone(),
|
||||
self.config_modified.clone(),
|
||||
project_dir,
|
||||
globset,
|
||||
)
|
||||
.map_err(anyhow::Error::new)
|
||||
}) {
|
||||
Ok(watcher) => self.watcher = Some(watcher),
|
||||
Err(e) => log::error!("Failed to create watcher: {e}"),
|
||||
}
|
||||
}
|
||||
config.watcher_change = false;
|
||||
}
|
||||
}
|
||||
|
||||
if config.obj_path.is_some()
|
||||
&& self.modified.swap(false, Ordering::Relaxed)
|
||||
&& !jobs.is_running(Job::ObjDiff)
|
||||
{
|
||||
jobs.push(start_build(self.config.clone()));
|
||||
}
|
||||
|
||||
if config_state.queue_update_check {
|
||||
jobs.push(start_check_update());
|
||||
config_state.queue_update_check = false;
|
||||
}
|
||||
}
|
||||
|
||||
if config.watcher_change {
|
||||
drop(self.watcher.take());
|
||||
|
||||
if let Some(project_dir) = &config.project_dir {
|
||||
if !config.watch_patterns.is_empty() {
|
||||
match build_globset(&config.watch_patterns)
|
||||
.map_err(anyhow::Error::new)
|
||||
.and_then(|globset| {
|
||||
create_watcher(
|
||||
self.modified.clone(),
|
||||
self.config_modified.clone(),
|
||||
project_dir,
|
||||
globset,
|
||||
)
|
||||
.map_err(anyhow::Error::new)
|
||||
}) {
|
||||
Ok(watcher) => self.watcher = Some(watcher),
|
||||
Err(e) => log::error!("Failed to create watcher: {e}"),
|
||||
}
|
||||
}
|
||||
config.watcher_change = false;
|
||||
}
|
||||
}
|
||||
|
||||
if config.obj_path.is_some()
|
||||
&& self.modified.swap(false, Ordering::Relaxed)
|
||||
&& !jobs.is_running(Job::ObjDiff)
|
||||
{
|
||||
jobs.push(start_build(self.config.clone()));
|
||||
}
|
||||
|
||||
if config.obj_change {
|
||||
*diff_state = Default::default();
|
||||
jobs.push(start_build(self.config.clone()));
|
||||
config.obj_change = false;
|
||||
}
|
||||
|
||||
if config_state.queue_update_check {
|
||||
jobs.push(start_check_update());
|
||||
config_state.queue_update_check = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for App {
|
||||
/// Called each time the UI needs repainting, which may be many times per second.
|
||||
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
if self.should_relaunch {
|
||||
frame.close();
|
||||
return;
|
||||
}
|
||||
|
||||
self.pre_update();
|
||||
|
||||
let Self { config, appearance, view_state, .. } = self;
|
||||
ctx.set_style(appearance.apply(ctx.style().as_ref()));
|
||||
|
||||
let ViewState {
|
||||
jobs,
|
||||
show_appearance_config,
|
||||
demangle_state,
|
||||
show_demangle,
|
||||
diff_state,
|
||||
config_state,
|
||||
show_project_config,
|
||||
} = view_state;
|
||||
|
||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
||||
egui::menu::bar(ui, |ui| {
|
||||
ui.menu_button("File", |ui| {
|
||||
if ui.button("Appearance…").clicked() {
|
||||
*show_appearance_config = !*show_appearance_config;
|
||||
ui.close_menu();
|
||||
}
|
||||
if ui.button("Quit").clicked() {
|
||||
frame.close();
|
||||
}
|
||||
});
|
||||
ui.menu_button("Tools", |ui| {
|
||||
if ui.button("Demangle…").clicked() {
|
||||
*show_demangle = !*show_demangle;
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
if diff_state.current_view == View::FunctionDiff
|
||||
&& matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success)
|
||||
{
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if function_diff_ui(ui, jobs, diff_state, appearance) {
|
||||
jobs.push(start_build(config.clone()));
|
||||
}
|
||||
});
|
||||
} else if diff_state.current_view == View::DataDiff
|
||||
&& matches!(&diff_state.build, Some(b) if b.first_status.success && b.second_status.success)
|
||||
{
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if data_diff_ui(ui, jobs, diff_state, appearance) {
|
||||
jobs.push(start_build(config.clone()));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
egui::SidePanel::left("side_panel").show(ctx, |ui| {
|
||||
egui::ScrollArea::both().show(ui, |ui| {
|
||||
config_ui(ui, config, jobs, show_project_config, config_state, appearance);
|
||||
jobs_ui(ui, jobs, appearance);
|
||||
});
|
||||
});
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
symbol_diff_ui(ui, diff_state, appearance);
|
||||
});
|
||||
}
|
||||
|
||||
project_window(ctx, config, show_project_config, config_state, appearance);
|
||||
appearance_window(ctx, show_appearance_config, appearance);
|
||||
demangle_window(ctx, show_demangle, demangle_state, appearance);
|
||||
|
||||
self.post_update();
|
||||
|
||||
// Windows + request_repaint_after breaks dialogs:
|
||||
// https://github.com/emilk/egui/issues/2003
|
||||
if cfg!(windows) || self.view_state.jobs.any_running() {
|
||||
ctx.request_repaint();
|
||||
} else {
|
||||
ctx.request_repaint_after(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
|
||||
/// Called by the frame work to save state before shutdown.
|
||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||
if let Ok(config) = self.config.read() {
|
||||
eframe::set_value(storage, CONFIG_KEY, &*config);
|
||||
}
|
||||
eframe::set_value(storage, APPEARANCE_KEY, &self.appearance);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#[cfg(windows)]
|
||||
use std::string::FromUtf16Error;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
path::{PathBuf, MAIN_SEPARATOR},
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
|
@ -29,6 +30,7 @@ pub struct ConfigViewState {
|
|||
pub watch_pattern_text: String,
|
||||
pub queue_update_check: bool,
|
||||
pub load_error: Option<String>,
|
||||
pub unit_search: String,
|
||||
#[cfg(windows)]
|
||||
pub available_wsl_distros: Option<Vec<String>>,
|
||||
}
|
||||
|
@ -139,9 +141,9 @@ pub fn config_ui(
|
|||
state.available_wsl_distros = Some(fetch_wsl2_distros());
|
||||
}
|
||||
egui::ComboBox::from_label("Run in WSL2")
|
||||
.selected_text(selected_wsl_distro.as_ref().unwrap_or(&"None".to_string()))
|
||||
.selected_text(selected_wsl_distro.as_ref().unwrap_or(&"Disabled".to_string()))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(selected_wsl_distro, None, "None");
|
||||
ui.selectable_value(selected_wsl_distro, None, "Disabled");
|
||||
for distro in state.available_wsl_distros.as_ref().unwrap() {
|
||||
ui.selectable_value(selected_wsl_distro, Some(distro.clone()), distro);
|
||||
}
|
||||
|
@ -163,7 +165,7 @@ pub fn config_ui(
|
|||
if let (Some(base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
|
||||
let mut new_build_obj = obj_path.clone();
|
||||
if units.is_empty() {
|
||||
if ui.button("Select obj").clicked() {
|
||||
if ui.button("Select object").clicked() {
|
||||
if let Some(path) = rfd::FileDialog::new()
|
||||
.set_directory(&target_dir)
|
||||
.add_filter("Object file", &["o", "elf"])
|
||||
|
@ -177,33 +179,81 @@ pub fn config_ui(
|
|||
}
|
||||
}
|
||||
if let Some(obj) = obj_path {
|
||||
ui.label(&*obj);
|
||||
ui.label(
|
||||
RichText::new(&*obj)
|
||||
.color(appearance.replace_color)
|
||||
.family(FontFamily::Monospace),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
CollapsingHeader::new(RichText::new("Objects").font(FontId {
|
||||
let had_search = !state.unit_search.is_empty();
|
||||
egui::TextEdit::singleline(&mut state.unit_search).hint_text("Filter").ui(ui);
|
||||
|
||||
let mut root_open = None;
|
||||
let mut node_open = NodeOpen::Default;
|
||||
ui.horizontal(|ui| {
|
||||
if ui.small_button("⏶").on_hover_text_at_pointer("Collapse all").clicked() {
|
||||
root_open = Some(false);
|
||||
node_open = NodeOpen::Close;
|
||||
}
|
||||
if ui.small_button("⏷").on_hover_text_at_pointer("Expand all").clicked() {
|
||||
root_open = Some(true);
|
||||
node_open = NodeOpen::Open;
|
||||
}
|
||||
if ui
|
||||
.add_enabled(obj_path.is_some(), egui::Button::new("⌖").small())
|
||||
.on_hover_text_at_pointer("Current object")
|
||||
.clicked()
|
||||
{
|
||||
root_open = Some(true);
|
||||
node_open = NodeOpen::Object;
|
||||
}
|
||||
});
|
||||
if state.unit_search.is_empty() {
|
||||
if had_search {
|
||||
root_open = Some(true);
|
||||
node_open = NodeOpen::Object;
|
||||
}
|
||||
} else if !had_search {
|
||||
root_open = Some(true);
|
||||
node_open = NodeOpen::Open;
|
||||
}
|
||||
|
||||
CollapsingHeader::new(RichText::new("🗀 Objects").font(FontId {
|
||||
size: appearance.ui_font.size,
|
||||
family: appearance.code_font.family.clone(),
|
||||
}))
|
||||
.open(root_open)
|
||||
.default_open(true)
|
||||
.show(ui, |ui| {
|
||||
for node in unit_nodes {
|
||||
display_node(ui, &mut new_build_obj, node, appearance);
|
||||
let mut nodes = Cow::Borrowed(unit_nodes);
|
||||
if !state.unit_search.is_empty() {
|
||||
let search = state.unit_search.to_ascii_lowercase();
|
||||
nodes = Cow::Owned(
|
||||
unit_nodes.iter().filter_map(|node| filter_node(node, &search)).collect(),
|
||||
);
|
||||
}
|
||||
|
||||
ui.style_mut().wrap = Some(false);
|
||||
for node in nodes.iter() {
|
||||
display_node(ui, &mut new_build_obj, node, appearance, node_open);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let mut build = false;
|
||||
if new_build_obj != *obj_path {
|
||||
*obj_path = new_build_obj;
|
||||
// TODO apply reverse_fn_order
|
||||
build = true;
|
||||
if let Some(obj) = new_build_obj {
|
||||
// Will set obj_changed, which will trigger a rebuild
|
||||
config_guard.set_obj_path(obj);
|
||||
// TODO apply reverse_fn_order
|
||||
}
|
||||
}
|
||||
if obj_path.is_some() && ui.button("Build").clicked() {
|
||||
build = true;
|
||||
}
|
||||
if build {
|
||||
if config_guard.obj_path.is_some() && ui.button("Build").clicked() {
|
||||
// Rebuild immediately
|
||||
jobs.push(start_build(config.clone()));
|
||||
}
|
||||
} else {
|
||||
ui.colored_label(appearance.delete_color, "Missing project settings");
|
||||
}
|
||||
|
||||
// ui.checkbox(&mut view_config.reverse_fn_order, "Reverse function order (deferred)");
|
||||
|
@ -221,10 +271,12 @@ fn display_unit(
|
|||
let selected = matches!(obj_path, Some(path) if path == &path_string);
|
||||
if SelectableLabel::new(
|
||||
selected,
|
||||
RichText::new(name).font(FontId {
|
||||
size: appearance.ui_font.size,
|
||||
family: appearance.code_font.family.clone(),
|
||||
}),
|
||||
RichText::new(name)
|
||||
.font(FontId {
|
||||
size: appearance.ui_font.size,
|
||||
family: appearance.code_font.family.clone(),
|
||||
})
|
||||
.color(appearance.text_color),
|
||||
)
|
||||
.ui(ui)
|
||||
.clicked()
|
||||
|
@ -233,31 +285,91 @@ fn display_unit(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Copy, Clone, PartialEq, Eq, Debug)]
|
||||
enum NodeOpen {
|
||||
#[default]
|
||||
Default,
|
||||
Open,
|
||||
Close,
|
||||
Object,
|
||||
}
|
||||
|
||||
fn display_node(
|
||||
ui: &mut egui::Ui,
|
||||
obj_path: &mut Option<String>,
|
||||
node: &ProjectUnitNode,
|
||||
appearance: &Appearance,
|
||||
node_open: NodeOpen,
|
||||
) {
|
||||
match node {
|
||||
ProjectUnitNode::File(name, unit) => {
|
||||
display_unit(ui, obj_path, name, unit, appearance);
|
||||
}
|
||||
ProjectUnitNode::Dir(name, children) => {
|
||||
CollapsingHeader::new(RichText::new(name).font(FontId {
|
||||
size: appearance.ui_font.size,
|
||||
family: appearance.code_font.family.clone(),
|
||||
}))
|
||||
.default_open(false)
|
||||
let contains_obj = obj_path.as_ref().map(|path| contains_node(node, path));
|
||||
let open = match node_open {
|
||||
NodeOpen::Default => None,
|
||||
NodeOpen::Open => Some(true),
|
||||
NodeOpen::Close => Some(false),
|
||||
NodeOpen::Object => contains_obj,
|
||||
};
|
||||
let color = if contains_obj == Some(true) {
|
||||
appearance.replace_color
|
||||
} else {
|
||||
appearance.text_color
|
||||
};
|
||||
CollapsingHeader::new(
|
||||
RichText::new(name)
|
||||
.font(FontId {
|
||||
size: appearance.ui_font.size,
|
||||
family: appearance.code_font.family.clone(),
|
||||
})
|
||||
.color(color),
|
||||
)
|
||||
.open(open)
|
||||
.show(ui, |ui| {
|
||||
for node in children {
|
||||
display_node(ui, obj_path, node, appearance);
|
||||
display_node(ui, obj_path, node, appearance, node_open);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn contains_node(node: &ProjectUnitNode, path: &str) -> bool {
|
||||
match node {
|
||||
ProjectUnitNode::File(_, unit) => {
|
||||
let path_string = unit.path.to_string_lossy().to_string();
|
||||
path == path_string
|
||||
}
|
||||
ProjectUnitNode::Dir(_, children) => children.iter().any(|node| contains_node(node, path)),
|
||||
}
|
||||
}
|
||||
|
||||
fn filter_node(node: &ProjectUnitNode, search: &str) -> Option<ProjectUnitNode> {
|
||||
match node {
|
||||
ProjectUnitNode::File(name, _) => {
|
||||
if name.to_ascii_lowercase().contains(search) {
|
||||
Some(node.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
ProjectUnitNode::Dir(name, children) => {
|
||||
if name.to_ascii_lowercase().contains(search) {
|
||||
return Some(node.clone());
|
||||
}
|
||||
let new_children =
|
||||
children.iter().filter_map(|child| filter_node(child, search)).collect::<Vec<_>>();
|
||||
if !new_children.is_empty() {
|
||||
Some(ProjectUnitNode::Dir(name.clone(), new_children))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const HELP_ICON: &str = "ℹ";
|
||||
|
||||
fn subheading(ui: &mut egui::Ui, text: &str, appearance: &Appearance) {
|
||||
|
@ -283,20 +395,18 @@ fn format_path(path: &Option<PathBuf>, appearance: &Appearance) -> RichText {
|
|||
|
||||
fn pick_folder_ui(
|
||||
ui: &mut egui::Ui,
|
||||
dir: &mut Option<PathBuf>,
|
||||
dir: &Option<PathBuf>,
|
||||
label: &str,
|
||||
tooltip: impl FnOnce(&mut egui::Ui),
|
||||
clicked: impl FnOnce(&mut Option<PathBuf>),
|
||||
appearance: &Appearance,
|
||||
) {
|
||||
ui.horizontal(|ui| {
|
||||
) -> egui::Response {
|
||||
let response = ui.horizontal(|ui| {
|
||||
subheading(ui, label, appearance);
|
||||
ui.link(HELP_ICON).on_hover_ui(tooltip);
|
||||
if ui.button("Select").clicked() {
|
||||
clicked(dir);
|
||||
}
|
||||
ui.button("Select")
|
||||
});
|
||||
ui.label(format_path(dir, appearance));
|
||||
response.inner
|
||||
}
|
||||
|
||||
pub fn project_window(
|
||||
|
@ -330,29 +440,15 @@ fn split_obj_config_ui(
|
|||
state: &mut ConfigViewState,
|
||||
appearance: &Appearance,
|
||||
) {
|
||||
let AppConfig {
|
||||
custom_make,
|
||||
project_dir,
|
||||
target_obj_dir,
|
||||
base_obj_dir,
|
||||
obj_path,
|
||||
build_target,
|
||||
config_change,
|
||||
watcher_change,
|
||||
watcher_enabled,
|
||||
watch_patterns,
|
||||
..
|
||||
} = config;
|
||||
|
||||
let text_format = TextFormat::simple(appearance.ui_font.clone(), appearance.text_color);
|
||||
let code_format = TextFormat::simple(
|
||||
FontId { size: appearance.ui_font.size, family: appearance.code_font.family.clone() },
|
||||
appearance.emphasized_text_color,
|
||||
);
|
||||
|
||||
pick_folder_ui(
|
||||
let response = pick_folder_ui(
|
||||
ui,
|
||||
project_dir,
|
||||
&config.project_dir,
|
||||
"Project directory",
|
||||
|ui| {
|
||||
let mut job = LayoutJob::default();
|
||||
|
@ -364,18 +460,13 @@ fn split_obj_config_ui(
|
|||
);
|
||||
ui.label(job);
|
||||
},
|
||||
|project_dir| {
|
||||
if let Some(path) = rfd::FileDialog::new().pick_folder() {
|
||||
*project_dir = Some(path);
|
||||
*config_change = true;
|
||||
*watcher_change = true;
|
||||
*target_obj_dir = None;
|
||||
*base_obj_dir = None;
|
||||
*obj_path = None;
|
||||
}
|
||||
},
|
||||
appearance,
|
||||
);
|
||||
if response.clicked() {
|
||||
if let Some(path) = rfd::FileDialog::new().pick_folder() {
|
||||
config.set_project_dir(path);
|
||||
}
|
||||
}
|
||||
ui.separator();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
|
@ -400,20 +491,20 @@ fn split_obj_config_ui(
|
|||
ui.label(job);
|
||||
});
|
||||
});
|
||||
let mut custom_make_str = custom_make.clone().unwrap_or_default();
|
||||
let mut custom_make_str = config.custom_make.clone().unwrap_or_default();
|
||||
if ui.text_edit_singleline(&mut custom_make_str).changed() {
|
||||
if custom_make_str.is_empty() {
|
||||
*custom_make = None;
|
||||
config.custom_make = None;
|
||||
} else {
|
||||
*custom_make = Some(custom_make_str);
|
||||
config.custom_make = Some(custom_make_str);
|
||||
}
|
||||
}
|
||||
ui.separator();
|
||||
|
||||
if let Some(project_dir) = project_dir {
|
||||
pick_folder_ui(
|
||||
if let Some(project_dir) = config.project_dir.clone() {
|
||||
let response = pick_folder_ui(
|
||||
ui,
|
||||
target_obj_dir,
|
||||
&config.target_obj_dir,
|
||||
"Target build directory",
|
||||
|ui| {
|
||||
let mut job = LayoutJob::default();
|
||||
|
@ -429,16 +520,14 @@ fn split_obj_config_ui(
|
|||
);
|
||||
ui.label(job);
|
||||
},
|
||||
|target_obj_dir| {
|
||||
if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder()
|
||||
{
|
||||
*target_obj_dir = Some(path);
|
||||
*obj_path = None;
|
||||
}
|
||||
},
|
||||
appearance,
|
||||
);
|
||||
ui.checkbox(build_target, "Build target objects").on_hover_ui(|ui| {
|
||||
if response.clicked() {
|
||||
if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() {
|
||||
config.set_target_obj_dir(path);
|
||||
}
|
||||
}
|
||||
ui.checkbox(&mut config.build_target, "Build target objects").on_hover_ui(|ui| {
|
||||
let mut job = LayoutJob::default();
|
||||
job.append(
|
||||
"Tells the build system to produce the target object.\n",
|
||||
|
@ -467,9 +556,9 @@ fn split_obj_config_ui(
|
|||
});
|
||||
ui.separator();
|
||||
|
||||
pick_folder_ui(
|
||||
let response = pick_folder_ui(
|
||||
ui,
|
||||
base_obj_dir,
|
||||
&config.base_obj_dir,
|
||||
"Base build directory",
|
||||
|ui| {
|
||||
let mut job = LayoutJob::default();
|
||||
|
@ -480,42 +569,41 @@ fn split_obj_config_ui(
|
|||
);
|
||||
ui.label(job);
|
||||
},
|
||||
|base_obj_dir| {
|
||||
if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder()
|
||||
{
|
||||
*base_obj_dir = Some(path);
|
||||
*obj_path = None;
|
||||
}
|
||||
},
|
||||
appearance,
|
||||
);
|
||||
if response.clicked() {
|
||||
if let Some(path) = rfd::FileDialog::new().set_directory(&project_dir).pick_folder() {
|
||||
config.set_base_obj_dir(path);
|
||||
}
|
||||
}
|
||||
ui.separator();
|
||||
}
|
||||
|
||||
subheading(ui, "Watch settings", appearance);
|
||||
let response = ui.checkbox(watcher_enabled, "Rebuild on changes").on_hover_ui(|ui| {
|
||||
let mut job = LayoutJob::default();
|
||||
job.append(
|
||||
"Automatically re-run the build & diff when files change.",
|
||||
0.0,
|
||||
text_format.clone(),
|
||||
);
|
||||
ui.label(job);
|
||||
});
|
||||
let response =
|
||||
ui.checkbox(&mut config.watcher_enabled, "Rebuild on changes").on_hover_ui(|ui| {
|
||||
let mut job = LayoutJob::default();
|
||||
job.append(
|
||||
"Automatically re-run the build & diff when files change.",
|
||||
0.0,
|
||||
text_format.clone(),
|
||||
);
|
||||
ui.label(job);
|
||||
});
|
||||
if response.changed() {
|
||||
*watcher_change = true;
|
||||
config.watcher_change = true;
|
||||
};
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(RichText::new("File Patterns").color(appearance.text_color));
|
||||
ui.label(RichText::new("File patterns").color(appearance.text_color));
|
||||
if ui.button("Reset").clicked() {
|
||||
*watch_patterns =
|
||||
config.watch_patterns =
|
||||
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect();
|
||||
*watcher_change = true;
|
||||
config.watcher_change = true;
|
||||
}
|
||||
});
|
||||
let mut remove_at: Option<usize> = None;
|
||||
for (idx, glob) in watch_patterns.iter().enumerate() {
|
||||
for (idx, glob) in config.watch_patterns.iter().enumerate() {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(
|
||||
RichText::new(format!("{}", glob))
|
||||
|
@ -528,15 +616,15 @@ fn split_obj_config_ui(
|
|||
});
|
||||
}
|
||||
if let Some(idx) = remove_at {
|
||||
watch_patterns.remove(idx);
|
||||
*watcher_change = true;
|
||||
config.watch_patterns.remove(idx);
|
||||
config.watcher_change = true;
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
egui::TextEdit::singleline(&mut state.watch_pattern_text).desired_width(100.0).show(ui);
|
||||
if ui.small_button("+").clicked() {
|
||||
if let Ok(glob) = Glob::new(&state.watch_pattern_text) {
|
||||
watch_patterns.push(glob);
|
||||
*watcher_change = true;
|
||||
config.watch_patterns.push(glob);
|
||||
config.watcher_change = true;
|
||||
state.watch_pattern_text.clear();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue