Updates to Objects pane & config improvements

This commit is contained in:
Luke Street 2023-08-11 01:36:22 -04:00
parent 91d11c83d6
commit eaf0fabc2d
2 changed files with 378 additions and 238 deletions

View File

@ -61,6 +61,40 @@ pub struct AppConfig {
pub watcher_change: bool, pub watcher_change: bool,
#[serde(skip)] #[serde(skip)]
pub config_change: bool, 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)] #[derive(Default)]
@ -109,97 +143,8 @@ impl App {
app.relaunch_path = relaunch_path; app.relaunch_path = relaunch_path;
app app
} }
}
impl eframe::App for App { fn pre_update(&mut self) {
/// 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) {
let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state; let ViewState { jobs, diff_state, config_state, .. } = &mut self.view_state;
for (job, result) in jobs.iter_finished() { for (job, result) in jobs.iter_finished() {
@ -251,8 +196,13 @@ impl eframe::App for App {
} }
} }
jobs.clear_finished(); jobs.clear_finished();
}
if let Ok(mut config) = self.config.write() { 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; let config = &mut *config;
if self.config_modified.swap(false, Ordering::Relaxed) { if self.config_modified.swap(false, Ordering::Relaxed) {
@ -301,12 +251,114 @@ impl eframe::App for App {
jobs.push(start_build(self.config.clone())); 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 { if config_state.queue_update_check {
jobs.push(start_check_update()); jobs.push(start_check_update());
config_state.queue_update_check = false; 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);
}
} }
fn create_watcher( fn create_watcher(

View File

@ -1,6 +1,7 @@
#[cfg(windows)] #[cfg(windows)]
use std::string::FromUtf16Error; use std::string::FromUtf16Error;
use std::{ use std::{
borrow::Cow,
path::{PathBuf, MAIN_SEPARATOR}, path::{PathBuf, MAIN_SEPARATOR},
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
@ -29,6 +30,7 @@ pub struct ConfigViewState {
pub watch_pattern_text: String, pub watch_pattern_text: String,
pub queue_update_check: bool, pub queue_update_check: bool,
pub load_error: Option<String>, pub load_error: Option<String>,
pub unit_search: String,
#[cfg(windows)] #[cfg(windows)]
pub available_wsl_distros: Option<Vec<String>>, pub available_wsl_distros: Option<Vec<String>>,
} }
@ -139,9 +141,9 @@ pub fn config_ui(
state.available_wsl_distros = Some(fetch_wsl2_distros()); state.available_wsl_distros = Some(fetch_wsl2_distros());
} }
egui::ComboBox::from_label("Run in WSL2") 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| { .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() { for distro in state.available_wsl_distros.as_ref().unwrap() {
ui.selectable_value(selected_wsl_distro, Some(distro.clone()), distro); 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) { if let (Some(base_dir), Some(target_dir)) = (base_obj_dir, target_obj_dir) {
let mut new_build_obj = obj_path.clone(); let mut new_build_obj = obj_path.clone();
if units.is_empty() { if units.is_empty() {
if ui.button("Select obj").clicked() { if ui.button("Select object").clicked() {
if let Some(path) = rfd::FileDialog::new() if let Some(path) = rfd::FileDialog::new()
.set_directory(&target_dir) .set_directory(&target_dir)
.add_filter("Object file", &["o", "elf"]) .add_filter("Object file", &["o", "elf"])
@ -177,33 +179,81 @@ pub fn config_ui(
} }
} }
if let Some(obj) = obj_path { if let Some(obj) = obj_path {
ui.label(&*obj); ui.label(
RichText::new(&*obj)
.color(appearance.replace_color)
.family(FontFamily::Monospace),
);
} }
} else { } 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, size: appearance.ui_font.size,
family: appearance.code_font.family.clone(), family: appearance.code_font.family.clone(),
})) }))
.open(root_open)
.default_open(true) .default_open(true)
.show(ui, |ui| { .show(ui, |ui| {
for node in unit_nodes { let mut nodes = Cow::Borrowed(unit_nodes);
display_node(ui, &mut new_build_obj, node, appearance); 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 { if new_build_obj != *obj_path {
*obj_path = new_build_obj; 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 // TODO apply reverse_fn_order
build = true;
} }
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())); 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)"); // 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); let selected = matches!(obj_path, Some(path) if path == &path_string);
if SelectableLabel::new( if SelectableLabel::new(
selected, selected,
RichText::new(name).font(FontId { RichText::new(name)
.font(FontId {
size: appearance.ui_font.size, size: appearance.ui_font.size,
family: appearance.code_font.family.clone(), family: appearance.code_font.family.clone(),
}), })
.color(appearance.text_color),
) )
.ui(ui) .ui(ui)
.clicked() .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( fn display_node(
ui: &mut egui::Ui, ui: &mut egui::Ui,
obj_path: &mut Option<String>, obj_path: &mut Option<String>,
node: &ProjectUnitNode, node: &ProjectUnitNode,
appearance: &Appearance, appearance: &Appearance,
node_open: NodeOpen,
) { ) {
match node { match node {
ProjectUnitNode::File(name, unit) => { ProjectUnitNode::File(name, unit) => {
display_unit(ui, obj_path, name, unit, appearance); display_unit(ui, obj_path, name, unit, appearance);
} }
ProjectUnitNode::Dir(name, children) => { ProjectUnitNode::Dir(name, children) => {
CollapsingHeader::new(RichText::new(name).font(FontId { 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, size: appearance.ui_font.size,
family: appearance.code_font.family.clone(), family: appearance.code_font.family.clone(),
})) })
.default_open(false) .color(color),
)
.open(open)
.show(ui, |ui| { .show(ui, |ui| {
for node in children { 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 = ""; const HELP_ICON: &str = "";
fn subheading(ui: &mut egui::Ui, text: &str, appearance: &Appearance) { 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( fn pick_folder_ui(
ui: &mut egui::Ui, ui: &mut egui::Ui,
dir: &mut Option<PathBuf>, dir: &Option<PathBuf>,
label: &str, label: &str,
tooltip: impl FnOnce(&mut egui::Ui), tooltip: impl FnOnce(&mut egui::Ui),
clicked: impl FnOnce(&mut Option<PathBuf>),
appearance: &Appearance, appearance: &Appearance,
) { ) -> egui::Response {
ui.horizontal(|ui| { let response = ui.horizontal(|ui| {
subheading(ui, label, appearance); subheading(ui, label, appearance);
ui.link(HELP_ICON).on_hover_ui(tooltip); ui.link(HELP_ICON).on_hover_ui(tooltip);
if ui.button("Select").clicked() { ui.button("Select")
clicked(dir);
}
}); });
ui.label(format_path(dir, appearance)); ui.label(format_path(dir, appearance));
response.inner
} }
pub fn project_window( pub fn project_window(
@ -330,29 +440,15 @@ fn split_obj_config_ui(
state: &mut ConfigViewState, state: &mut ConfigViewState,
appearance: &Appearance, 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 text_format = TextFormat::simple(appearance.ui_font.clone(), appearance.text_color);
let code_format = TextFormat::simple( let code_format = TextFormat::simple(
FontId { size: appearance.ui_font.size, family: appearance.code_font.family.clone() }, FontId { size: appearance.ui_font.size, family: appearance.code_font.family.clone() },
appearance.emphasized_text_color, appearance.emphasized_text_color,
); );
pick_folder_ui( let response = pick_folder_ui(
ui, ui,
project_dir, &config.project_dir,
"Project directory", "Project directory",
|ui| { |ui| {
let mut job = LayoutJob::default(); let mut job = LayoutJob::default();
@ -364,18 +460,13 @@ fn split_obj_config_ui(
); );
ui.label(job); 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, appearance,
); );
if response.clicked() {
if let Some(path) = rfd::FileDialog::new().pick_folder() {
config.set_project_dir(path);
}
}
ui.separator(); ui.separator();
ui.horizontal(|ui| { ui.horizontal(|ui| {
@ -400,20 +491,20 @@ fn split_obj_config_ui(
ui.label(job); 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 ui.text_edit_singleline(&mut custom_make_str).changed() {
if custom_make_str.is_empty() { if custom_make_str.is_empty() {
*custom_make = None; config.custom_make = None;
} else { } else {
*custom_make = Some(custom_make_str); config.custom_make = Some(custom_make_str);
} }
} }
ui.separator(); ui.separator();
if let Some(project_dir) = project_dir { if let Some(project_dir) = config.project_dir.clone() {
pick_folder_ui( let response = pick_folder_ui(
ui, ui,
target_obj_dir, &config.target_obj_dir,
"Target build directory", "Target build directory",
|ui| { |ui| {
let mut job = LayoutJob::default(); let mut job = LayoutJob::default();
@ -429,16 +520,14 @@ fn split_obj_config_ui(
); );
ui.label(job); 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, 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(); let mut job = LayoutJob::default();
job.append( job.append(
"Tells the build system to produce the target object.\n", "Tells the build system to produce the target object.\n",
@ -467,9 +556,9 @@ fn split_obj_config_ui(
}); });
ui.separator(); ui.separator();
pick_folder_ui( let response = pick_folder_ui(
ui, ui,
base_obj_dir, &config.base_obj_dir,
"Base build directory", "Base build directory",
|ui| { |ui| {
let mut job = LayoutJob::default(); let mut job = LayoutJob::default();
@ -480,20 +569,19 @@ fn split_obj_config_ui(
); );
ui.label(job); 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, 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(); ui.separator();
} }
subheading(ui, "Watch settings", appearance); subheading(ui, "Watch settings", appearance);
let response = ui.checkbox(watcher_enabled, "Rebuild on changes").on_hover_ui(|ui| { let response =
ui.checkbox(&mut config.watcher_enabled, "Rebuild on changes").on_hover_ui(|ui| {
let mut job = LayoutJob::default(); let mut job = LayoutJob::default();
job.append( job.append(
"Automatically re-run the build & diff when files change.", "Automatically re-run the build & diff when files change.",
@ -503,19 +591,19 @@ fn split_obj_config_ui(
ui.label(job); ui.label(job);
}); });
if response.changed() { if response.changed() {
*watcher_change = true; config.watcher_change = true;
}; };
ui.horizontal(|ui| { 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() { if ui.button("Reset").clicked() {
*watch_patterns = config.watch_patterns =
DEFAULT_WATCH_PATTERNS.iter().map(|s| Glob::new(s).unwrap()).collect(); 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; 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.horizontal(|ui| {
ui.label( ui.label(
RichText::new(format!("{}", glob)) RichText::new(format!("{}", glob))
@ -528,15 +616,15 @@ fn split_obj_config_ui(
}); });
} }
if let Some(idx) = remove_at { if let Some(idx) = remove_at {
watch_patterns.remove(idx); config.watch_patterns.remove(idx);
*watcher_change = true; config.watcher_change = true;
} }
ui.horizontal(|ui| { ui.horizontal(|ui| {
egui::TextEdit::singleline(&mut state.watch_pattern_text).desired_width(100.0).show(ui); egui::TextEdit::singleline(&mut state.watch_pattern_text).desired_width(100.0).show(ui);
if ui.small_button("+").clicked() { if ui.small_button("+").clicked() {
if let Ok(glob) = Glob::new(&state.watch_pattern_text) { if let Ok(glob) = Glob::new(&state.watch_pattern_text) {
watch_patterns.push(glob); config.watch_patterns.push(glob);
*watcher_change = true; config.watcher_change = true;
state.watch_pattern_text.clear(); state.watch_pattern_text.clear();
} }
} }