feat: add unsaved changes confirmation prompts for window close and navigation using gtk::AlertDialog

This commit is contained in:
2026-08-03 23:03:24 -04:00
parent 2db5ca2b2e
commit e9b80817f9
3 changed files with 120 additions and 28 deletions
+3 -1
View File
@@ -6,6 +6,7 @@ pub struct CompositeIcon {
pub emblem_x: f64,
pub emblem_y: f64,
pub emblem_size: f64,
pub is_modified: bool,
}
impl CompositeIcon {
@@ -16,6 +17,7 @@ impl CompositeIcon {
emblem_x: 32.0,
emblem_y: 32.0,
emblem_size: 32.0,
is_modified: false,
}
}
@@ -104,7 +106,7 @@ impl CompositeIcon {
mapped_x, mapped_y, scale_x, scale_y, emblem_inner
);
let mut base_svg = strip_dimensions(&self.bg.content);
let base_svg = strip_dimensions(&self.bg.content);
if let Some(idx) = base_svg.rfind("</svg>") {
let mut new_svg = base_svg[..idx].to_string();
new_svg.push_str(&emblem_group);
+86 -19
View File
@@ -46,10 +46,43 @@ fn main() -> glib::ExitCode {
let stack = Stack::new();
window.set_child(Some(&stack));
build_picker(&stack, &window, breeze_path.clone());
let current_composite: Rc<RefCell<Option<Rc<RefCell<CompositeIcon>>>>> = Rc::new(RefCell::new(None));
{
let current_composite = Rc::clone(&current_composite);
let window_clone = window.clone();
window.connect_close_request(move |_| {
if let Some(comp) = current_composite.borrow().as_ref() {
if comp.borrow().is_modified {
let dialog = gtk::AlertDialog::builder()
.message("You have unsaved changes. Are you sure you want to close?")
.detail("All unsaved changes will be lost.")
.buttons(["Cancel", "Discard"])
.cancel_button(0)
.default_button(1)
.build();
let window_clone2 = window_clone.clone();
let comp_ref = Rc::clone(&current_composite);
dialog.choose(Some(&window_clone), gtk::gio::Cancellable::NONE, move |res| {
if let Ok(1) = res {
if let Some(c) = comp_ref.borrow().as_ref() {
c.borrow_mut().is_modified = false;
}
window_clone2.close();
}
});
return glib::Propagation::Stop;
}
}
glib::Propagation::Proceed
});
}
build_picker(&stack, &window, breeze_path.clone(), Rc::clone(&current_composite));
if let Some(Ok(path)) = initial_icon {
build_editor(&stack, &window, path, breeze_path);
build_editor(&stack, &window, path, breeze_path, Rc::clone(&current_composite));
}
window.present();
@@ -81,19 +114,20 @@ fn resolve_icon_arg(arg: &str, breeze_path: &Path) -> anyhow::Result<PathBuf> {
Ok(under_breeze.canonicalize()?)
}
fn build_picker(stack: &Stack, window: &ApplicationWindow, breeze_path: PathBuf) {
fn build_picker(stack: &Stack, window: &ApplicationWindow, breeze_path: PathBuf, current_composite: Rc<RefCell<Option<Rc<RefCell<CompositeIcon>>>>>) {
let stack_for_callback = stack.clone();
let window_for_callback = window.clone();
let breeze_for_editor = breeze_path.clone();
let comp_callback = Rc::clone(&current_composite);
let widget = ui::picker::build_picker_page(breeze_path, move |icon_path| {
build_editor(&stack_for_callback, &window_for_callback, icon_path, breeze_for_editor.clone());
build_editor(&stack_for_callback, &window_for_callback, icon_path, breeze_for_editor.clone(), Rc::clone(&comp_callback));
});
stack.add_named(&widget, Some("picker"));
stack.set_visible_child_name("picker");
}
fn build_editor(stack: &Stack, window: &ApplicationWindow, icon_path: PathBuf, breeze_path: PathBuf) {
fn build_editor(stack: &Stack, window: &ApplicationWindow, icon_path: PathBuf, breeze_path: PathBuf, current_composite: Rc<RefCell<Option<Rc<RefCell<CompositeIcon>>>>>) {
let mut scheme = match ColorScheme::load(&icon_path) {
Ok(s) => s,
Err(e) => {
@@ -104,6 +138,7 @@ fn build_editor(stack: &Stack, window: &ApplicationWindow, icon_path: PathBuf, b
scheme.auto_save = false;
let composite = Rc::new(RefCell::new(CompositeIcon::new(scheme)));
*current_composite.borrow_mut() = Some(Rc::clone(&composite));
let editor_container = gtk::Box::new(gtk::Orientation::Vertical, 0);
@@ -112,6 +147,7 @@ fn build_editor(stack: &Stack, window: &ApplicationWindow, icon_path: PathBuf, b
let stack = stack.clone();
let window = window.clone();
let editor_container = editor_container.clone();
let current_comp = Rc::clone(&current_composite);
Rc::new(move || {
while let Some(child) = editor_container.first_child() {
@@ -122,29 +158,57 @@ fn build_editor(stack: &Stack, window: &ApplicationWindow, icon_path: PathBuf, b
let stack_emblem = stack.clone();
let window_clone = window.clone();
let comp = Rc::clone(&composite);
let comp_for_back = Rc::clone(&comp);
let current_comp_for_back = Rc::clone(&current_comp);
let page = ui::editor::build_editor_page(
Rc::clone(&composite),
move || {
stack_clone.set_visible_child_name("picker");
if comp_for_back.borrow().is_modified {
let dialog = gtk::AlertDialog::builder()
.message("You have unsaved changes. Are you sure you want to discard them?")
.detail("All unsaved changes will be lost.")
.buttons(["Cancel", "Discard"])
.cancel_button(0)
.default_button(1)
.build();
let stack_clone = stack_clone.clone();
let comp_ref = Rc::clone(&current_comp_for_back);
dialog.choose(Some(&window_clone), gtk::gio::Cancellable::NONE, move |res| {
if let Ok(1) = res {
*comp_ref.borrow_mut() = None;
stack_clone.set_visible_child_name("picker");
}
});
} else {
*current_comp_for_back.borrow_mut() = None;
stack_clone.set_visible_child_name("picker");
}
},
move || {
stack_emblem.set_visible_child_name("emblem_picker");
},
move || {
let dialog = gtk::FileDialog::builder()
.title("Save Composite Icon As...")
.build();
let c = Rc::clone(&comp);
dialog.save(Some(&window_clone), gtk::gio::Cancellable::NONE, move |res| {
if let Ok(file) = res {
if let Some(path) = file.path() {
if let Err(e) = std::fs::write(&path, c.borrow().render()) {
eprintln!("Failed to save composite to {}: {e}", path.display());
{
let window_clone = window.clone();
let comp = Rc::clone(&comp);
move || {
let dialog = gtk::FileDialog::builder()
.title("Save Composite Icon As...")
.build();
let c = Rc::clone(&comp);
dialog.save(Some(&window_clone), gtk::gio::Cancellable::NONE, move |res| {
if let Ok(file) = res {
if let Some(path) = file.path() {
if let Err(e) = std::fs::write(&path, c.borrow().render()) {
eprintln!("Failed to save composite to {}: {e}", path.display());
} else {
c.borrow_mut().is_modified = false;
}
}
}
}
});
});
}
}
);
editor_container.append(&page);
@@ -165,7 +229,10 @@ fn build_editor(stack: &Stack, window: &ApplicationWindow, icon_path: PathBuf, b
let emblem_picker = ui::picker::build_picker_page(breeze_path, move |emblem_path| {
if let Ok(mut emblem_scheme) = ColorScheme::load_with_namespace(&emblem_path, "Emblem") {
emblem_scheme.auto_save = false;
comp_for_picker.borrow_mut().set_emblem(emblem_scheme);
let mut c = comp_for_picker.borrow_mut();
c.set_emblem(emblem_scheme);
c.is_modified = true;
drop(c);
rebuild_editor_clone();
stack_emblem_callback.set_visible_child_name("editor");
} else {
+31 -8
View File
@@ -129,24 +129,36 @@ pub fn build_editor_page(
let composite = Rc::clone(&composite);
let refresh_preview = refresh_preview.clone();
scale_x.connect_value_changed(move |s| {
composite.borrow_mut().emblem_x = s.value();
refresh_preview();
let mut comp = composite.borrow_mut();
if (comp.emblem_x - s.value()).abs() > f64::EPSILON {
comp.emblem_x = s.value();
comp.is_modified = true;
refresh_preview();
}
});
}
{
let composite = Rc::clone(&composite);
let refresh_preview = refresh_preview.clone();
scale_y.connect_value_changed(move |s| {
composite.borrow_mut().emblem_y = s.value();
refresh_preview();
let mut comp = composite.borrow_mut();
if (comp.emblem_y - s.value()).abs() > f64::EPSILON {
comp.emblem_y = s.value();
comp.is_modified = true;
refresh_preview();
}
});
}
{
let composite = Rc::clone(&composite);
let refresh_preview = refresh_preview.clone();
scale_size.connect_value_changed(move |s| {
composite.borrow_mut().emblem_size = s.value();
refresh_preview();
let mut comp = composite.borrow_mut();
if (comp.emblem_size - s.value()).abs() > f64::EPSILON {
comp.emblem_size = s.value();
comp.is_modified = true;
refresh_preview();
}
});
}
@@ -197,11 +209,16 @@ pub fn build_editor_page(
);
let changed = {
let mut comp = composite.borrow_mut();
let res;
if is_emblem {
comp.emblem.as_mut().unwrap().update_color(&tag, &new_hex).unwrap_or(false)
res = comp.emblem.as_mut().unwrap().update_color(&tag, &new_hex).unwrap_or(false);
} else {
comp.bg.update_color(&tag, &new_hex).unwrap_or(false)
res = comp.bg.update_color(&tag, &new_hex).unwrap_or(false);
}
if res {
comp.is_modified = true;
}
res
};
if changed {
refresh_preview();
@@ -230,6 +247,9 @@ pub fn build_editor_page(
if let Some(emblem) = &mut comp.emblem {
changed |= emblem.undo().unwrap_or(false);
}
if changed {
comp.is_modified = true;
}
changed
};
if changed {
@@ -249,6 +269,9 @@ pub fn build_editor_page(
if let Some(emblem) = &mut comp.emblem {
changed |= emblem.redo().unwrap_or(false);
}
if changed {
comp.is_modified = true;
}
changed
};
if changed {