From 69919aa73e01d93dddce2cca1be310535efe1d41 Mon Sep 17 00:00:00 2001 From: timasoft Date: Fri, 24 Apr 2026 22:38:57 +0600 Subject: [PATCH 1/3] Create show_confirmation_dialog function . --- locales/gui.yml | 4 ++++ src/gui.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/locales/gui.yml b/locales/gui.yml index f85660c..1705edd 100644 --- a/locales/gui.yml +++ b/locales/gui.yml @@ -299,6 +299,10 @@ gui.togtkbox_test: en: "ToGtkBox Test" ru: "Тест ToGtkBox" zh-CN: "ToGtkBox 测试" +gui.confirm: + en: "Confirm" + ru: "Подтвердить" + zh-CN: "确认" gui.history_manager: en: "History Manager" ru: "Менеджер истории" diff --git a/src/gui.rs b/src/gui.rs index 58464d6..1db6363 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1132,6 +1132,37 @@ along with this program; if not, see ); } + pub fn show_confirmation_dialog( + &self, + title: &str, + text: &str, + on_confirm: F, + on_cancel: G, + ) where + F: FnOnce() + 'static, + G: FnOnce() + 'static, + { + let dialog = AlertDialog::builder() + .message(title) + .detail(text) + .buttons([&*t!("gui.cancel"), &*t!("gui.confirm")]) + .modal(true) + .build(); + + dialog.choose( + Some(&self.window), + None::<&gio::Cancellable>, + move |res: Result| { + // GTK returns the index of the pressed button: 0 = Cancel, 1 = Confirm + if res.ok() == Some(1) { + on_confirm(); + } else { + on_cancel(); + } + }, + ); + } + pub fn show_history_manager(gui: Rc>) { let window = Window::builder() .title(t!("gui.history_manager")) From 6b0340c0b032345b7ec338d81d0a65bf5b75d6ee Mon Sep 17 00:00:00 2001 From: timasoft Date: Sat, 25 Apr 2026 00:47:33 +0600 Subject: [PATCH 2/3] Add confirmation dialog for Hyprland config modifications --- locales/main.yml | 16 ++++++++ src/main.rs | 103 ++++++++++++++++++++++++++++++++++------------- 2 files changed, 92 insertions(+), 27 deletions(-) diff --git a/locales/main.yml b/locales/main.yml index c2de917..694eade 100644 --- a/locales/main.yml +++ b/locales/main.yml @@ -43,6 +43,14 @@ main.failed_to_create__: en: "Failed to create %{file}: %{error}" ru: "Не удалось создать %{file}: %{error}" zh-CN: "无法创建 %{file}: %{error}" +main.confirm_modify_main_config: + en: "Modify Hyprland Config" + ru: "Изменить конфигурацию Hyprland" + zh-CN: "修改 Hyprland 配置" +main.confirm_modify_main_config_text: + en: "HyprViz needs to add or update a 'source' line in your main hyprland.conf to apply settings. This will reload Hyprland. Proceed?" + ru: "HyprViz необходимо добавить или обновить строку 'source' в вашем основном hyprland.conf для применения настроек. Это перезагрузит конфигурацию Hyprland. Продолжить?" + zh-CN: "HyprViz 需要在主 hyprland.conf 中添加或更新 'source' 行以应用设置。这将重载 Hyprland 配置。是否继续?" main.saving_failed: en: "Saving failed" ru: "Не удалось сохранить" @@ -51,6 +59,14 @@ main.failed_to_add_source_line_to__: en: "Failed to add 'source = ./hyprviz.conf' to %{file}: %{error}" ru: "Не удалось добавить 'source = ./hyprviz.conf' в %{file}: %{error}" zh-CN: "无法将 'source = ./hyprviz.conf' 添加到 %{file}: %{error}" +main.integration_skipped: + en: "Integration Skipped" + ru: "Интеграция пропущена" + zh-CN: "集成已跳过" +main.integration_skipped_text: + en: "The 'source' line was not added to hyprland.conf. HyprViz settings will be saved to hyprviz.conf but will NOT take effect in Hyprland until you manually add 'source = ./hyprviz.conf' or confirm this dialog on next launch." + ru: "Строка 'source' не была добавлена в hyprland.conf. Настройки HyprViz будут сохраняться в hyprviz.conf, но НЕ вступят в силу в Hyprland, пока вы вручную не добавите 'source = ./hyprviz.conf' или не подтвердите этот диалог при следующем запуске." + zh-CN: "未将 'source' 行添加到 hyprland.conf。HyprViz 设置将保存到 hyprviz.conf,但在您手动添加 'source = ./hyprviz.conf' 或在下次启动时确认此对话框之前,它们不会在 Hyprland 中生效。" main.profile_not_found: en: "Profile not found" ru: "Профиль не найден" diff --git a/src/main.rs b/src/main.rs index 9f2b231..339ad0e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -62,6 +62,9 @@ fn build_ui(app: &Application) { let gui = Rc::new(RefCell::new(ConfigGUI::new(app))); gui::ConfigGUI::setup_ui_events(Rc::clone(&gui)); + let is_programmatic_switch = Rc::new(std::cell::Cell::new(false)); + let last_confirmed_index = Rc::new(std::cell::Cell::new(0u32)); + let config_path_full = get_config_path(false, "Default"); if !config_path_full.exists() { @@ -154,22 +157,35 @@ fn build_ui(app: &Application) { } } - match atomic_write(&config_path_full, &updated_config_str) { - Ok(_) => { - println!("Added 'source = ./hyprviz.conf' to ~/{}", CONFIG_PATH); - reload_hyprland(); - } - Err(e) => { - gui.borrow().custom_error_popup_critical( - &t!("main.saving_failed"), - &t!( - "main.failed_to_add_source_line_to__", - file = CONFIG_PATH, - error = e - ), + let config_path_full_clone = config_path_full.clone(); + let gui_clone_confirm = Rc::clone(&gui); + let gui_clone_cancel = Rc::clone(&gui); + gui.borrow().show_confirmation_dialog( + &t!("main.confirm_modify_main_config"), + &t!("main.confirm_modify_main_config_text"), + move || match atomic_write(&config_path_full_clone, &updated_config_str) { + Ok(()) => { + println!("Added 'source = ./hyprviz.conf' to ~/{}", CONFIG_PATH); + reload_hyprland(); + } + Err(e) => { + gui_clone_confirm.borrow().custom_error_popup_critical( + &t!("main.saving_failed"), + &t!( + "main.failed_to_add_source_line_to__", + file = CONFIG_PATH, + error = e + ), + ); + } + }, + move || { + gui_clone_cancel.borrow().custom_info_popup( + &t!("main.integration_skipped"), + &t!("main.integration_skipped_text"), ); - } - } + }, + ); } let profile = get_current_profile(&config_str); @@ -261,23 +277,56 @@ fn build_ui(app: &Application) { } let gui_clone = Rc::clone(&gui); + let is_programmatic_switch_clone = Rc::clone(&is_programmatic_switch); + let last_confirmed_index_clone = Rc::clone(&last_confirmed_index); + let config_path_full_clone = config_path_full.clone(); gui.borrow() .profile_dropdown .connect_selected_notify(move |dropdown| { - let selected_index = dropdown.selected(); + if is_programmatic_switch_clone.get() { + return; + } + + let new_index = dropdown.selected(); + let prev_index = last_confirmed_index_clone.get(); let model = dropdown.model().unwrap(); - if let Some(item) = model.item(selected_index) + let profile_name = if let Some(item) = model.item(new_index) && let Some(string_object) = item.downcast_ref::() { - let profile_name = string_object.string().as_str().to_string(); - match update_source_line(&config_path_full, &profile_name) { + string_object.string().as_str().to_string() + } else { + return; + }; + + let last_confirmed_index_clone_clone = Rc::clone(&last_confirmed_index_clone); + let config_path_full_clone_clone = config_path_full_clone.clone(); + let gui_clone_clone = Rc::clone(&gui_clone); + let is_programmatic_switch_clone_clone_confirm = + Rc::clone(&is_programmatic_switch_clone); + let dropdown_clone_confirm = dropdown.clone(); + + let is_programmatic_switch_clone_clone_cancel = + Rc::clone(&is_programmatic_switch_clone); + let dropdown_clone_cancel = dropdown.clone(); + + gui_clone.borrow().show_confirmation_dialog( + &t!("main.confirm_modify_main_config"), + &t!("main.confirm_modify_main_config_text"), + move || match update_source_line(&config_path_full_clone_clone, &profile_name) { Ok(_) => { println!("Updated source line to profile: {}", profile_name); reload_hyprland(); + last_confirmed_index_clone_clone.set(new_index); + glib::MainContext::default().spawn_local(async move { + gui_clone_clone.borrow_mut().reload_ui(true); + }); } Err(e) => { - gui_clone.borrow().custom_error_popup( + is_programmatic_switch_clone_clone_confirm.set(true); + dropdown_clone_confirm.set_selected(prev_index); + is_programmatic_switch_clone_clone_confirm.set(false); + gui_clone_clone.borrow().custom_error_popup( &t!("main.profile_switch_failed"), &t!( "main.failed_to_update_config_for_profile__", @@ -286,13 +335,13 @@ fn build_ui(app: &Application) { ), ); } - } - let gui = Rc::clone(&gui_clone); - - glib::MainContext::default().spawn_local(async move { - gui.borrow_mut().reload_ui(true); - }); - } + }, + move || { + is_programmatic_switch_clone_clone_cancel.set(true); + dropdown_clone_cancel.set_selected(prev_index); + is_programmatic_switch_clone_clone_cancel.set(false); + }, + ); }); } From e8240c2f9faee25363f4d201a256a348cdc1d17b Mon Sep 17 00:00:00 2001 From: timasoft Date: Sat, 25 Apr 2026 00:49:02 +0600 Subject: [PATCH 3/3] Update readme.md --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index feb2649..9dd236c 100644 --- a/readme.md +++ b/readme.md @@ -125,7 +125,7 @@ If you manage your NixOS configuration with flakes, add hyprviz as an input in y - [x] Add fancy editors for all options - [x] Add symlink support - [x] Add CTRL-Z/CTRL-Y support -- [ ] Add confirmation dialog for editing main hyprland.conf +- [x] Add confirmation dialog for editing main hyprland.conf - [ ] Add row actions (discard/remove) for individual options - [ ] Add support for waybar, swaync, hyprlock... - [x] Improve GUI