diff --git a/src/ui/build_window.rs b/src/ui/build_window.rs index 20dff89..e2332b4 100644 --- a/src/ui/build_window.rs +++ b/src/ui/build_window.rs @@ -21,8 +21,6 @@ pub struct BuildWindow { #[tracker::do_not_track] pub win: Option, #[tracker::do_not_track] - build_status_label: Option, - #[tracker::do_not_track] term: TermWidget, #[tracker::do_not_track] parent: gtk::Widget, @@ -87,7 +85,6 @@ impl SimpleComponent for BuildWindow { set_hexpand: true, set_halign: gtk::Align::Center, set_spacing: 12, - #[name(build_status_label)] gtk::Label { #[track = "model.changed(BuildWindow::build_status())"] set_markup: match &model.build_status { @@ -103,6 +100,18 @@ impl SimpleComponent for BuildWindow { BuildStatus::Done | BuildStatus::Error(_) => true, }, add_css_class: "title-2", + add_css_class: "success", + add_css_class: "error", + #[track = "model.changed(BuildWindow::build_status())"] + set_class_active: ( + "success", + model.build_status == BuildStatus::Done, + ), + #[track = "model.changed(BuildWindow::build_status())"] + set_class_active: ( + "error", + matches!(model.build_status, BuildStatus::Error(_)), + ), set_wrap: true, set_wrap_mode: gtk::pango::WrapMode::Word, set_justify: gtk::Justification::Center, @@ -163,18 +172,13 @@ impl SimpleComponent for BuildWindow { } } Self::Input::UpdateBuildStatus(status) => { - let label = self.build_status_label.as_ref().unwrap(); - label.remove_css_class("success"); - label.remove_css_class("error"); match status { BuildStatus::Done => { - label.add_css_class("success"); sender.input(BuildWindowMsg::UpdateContent(vec![ TermColor::Blue.colorize("Build completed!\n"), ])); } BuildStatus::Error(_) => { - label.add_css_class("error"); sender.input(BuildWindowMsg::UpdateContent(vec![ TermColor::Blue.colorize("Build failed!\n"), ])); @@ -203,7 +207,6 @@ impl SimpleComponent for BuildWindow { can_close: false, build_status: BuildStatus::Building, win: None, - build_status_label: None, parent: init.parent, term: { let t = TermWidget::new(); @@ -215,7 +218,6 @@ impl SimpleComponent for BuildWindow { }; let widgets = view_output!(); model.win = Some(widgets.win.clone()); - model.build_status_label = Some(widgets.build_status_label.clone()); ComponentParts { model, widgets } } } diff --git a/src/ui/debug_view.rs b/src/ui/debug_view.rs index 71fe41d..e4db1d9 100644 --- a/src/ui/debug_view.rs +++ b/src/ui/debug_view.rs @@ -40,7 +40,7 @@ pub enum SearchDirection { pub enum DebugViewMsg { LogUpdated(Vec), ClearLog, - DoSearch, + DoSearch(Option), SearchFindMatch(SearchDirection), LogLevelChanged(LogLevel), XRServiceActiveChanged(bool), @@ -78,10 +78,6 @@ pub enum DebugViewOutMsg { pub struct DebugView { xrservice_active: bool, #[tracker::do_not_track] - searchbar: Option, - #[tracker::do_not_track] - search_entry: Option, - #[tracker::do_not_track] dropdown: Option, #[tracker::do_not_track] log_level: LogLevel, @@ -97,12 +93,6 @@ pub struct DebugView { profile_export_action: gtk::gio::SimpleAction, #[tracker::do_not_track] profile_editor: Option>, - #[tracker::do_not_track] - profile_not_editable_dialog: adw::AlertDialog, - #[tracker::do_not_track] - profile_delete_confirm_dialog: adw::AlertDialog, - #[tracker::do_not_track] - query_profile_rebuild_dialog: adw::AlertDialog, #[no_eq] profile: Profile, profiles: Vec, @@ -197,11 +187,16 @@ impl AsyncComponent for DebugView { set_sensitive: !model.xrservice_active }, }, - add_top_bar: searchbar = >k::SearchBar { + add_top_bar: _searchbar = >k::SearchBar { set_margin_start: 1, set_hexpand: true, #[chain(flags(gtk::glib::BindingFlags::BIDIRECTIONAL).build())] bind_property: ("search-mode-enabled", &search_toggle, "active"), + connect_search_mode_enabled_notify[sender] => move |searchbar| { + if !searchbar.is_search_mode() { + sender.input(Self::Input::DoSearch(None)); + } + }, #[wrap(Some)] set_child: searchbox = >k::Box { set_orientation: gtk::Orientation::Horizontal, @@ -210,8 +205,11 @@ impl AsyncComponent for DebugView { #[name(search_entry)] gtk::SearchEntry { set_hexpand: true, - connect_changed[sender] => move |_| { - sender.input(Self::Input::DoSearch); + connect_changed[sender] => move |e| { + let txt = e.text().to_string(); + sender.input(Self::Input::DoSearch( + if txt.is_empty() { None } else { Some(txt) } + )); }, connect_activate[sender] => move |_| { sender.input(Self::Input::SearchFindMatch(SearchDirection::Forward)); @@ -305,15 +303,8 @@ impl AsyncComponent for DebugView { Self::Input::LogLevelChanged(lvl) => { self.log_level = lvl; } - Self::Input::DoSearch => { - let searchbar = self.searchbar.as_ref().unwrap().clone(); - let search_entry = self.search_entry.as_ref().unwrap().clone(); - let search_text = search_entry.text().to_string(); - if searchbar.is_search_mode() && !search_text.is_empty() { - self.term.set_search_term(Some(&search_text)); - } else { - self.term.set_search_term(None); - } + Self::Input::DoSearch(txt) => { + self.term.set_search_term(txt.as_deref()); } Self::Input::SearchFindMatch(direction) => match direction { SearchDirection::Forward => { @@ -400,16 +391,75 @@ impl AsyncComponent for DebugView { if self.profile.editable { sender.input(Self::Input::OpenProfileEditor(self.profile.clone())); } else { - self.profile_not_editable_dialog - .present(Some(&self.root_win)); + let dialog = adw::AlertDialog::builder() + .heading("This profile is not editable") + .body(concat!( + "You can duplicate it and edit the new copy. ", + "Do you want to duplicate the current profile?" + )) + .build(); + dialog.add_response("no", "_No"); + dialog.add_response("yes", "_Yes"); + dialog.set_response_appearance("yes", ResponseAppearance::Suggested); + + dialog.connect_response( + None, + clone!( + #[strong] + sender, + move |_, res| { + if res == "yes" { + sender.input(Self::Input::DuplicateProfile); + } + } + ), + ); + dialog.present(Some(&self.root_win)); } } Self::Input::CreateProfile => { sender.input(Self::Input::OpenProfileEditor(Profile::default())); } Self::Input::DeleteProfile => { - self.profile_delete_confirm_dialog - .present(Some(&self.root_win)); + let delete_confirm_dialog = adw::AlertDialog::builder() + .heading("Are you sure you want to delete this profile?") + .extra_child( + >k::CheckButton::builder() + .label("Delete all files and folders associated with profile") + .halign(gtk::Align::Center) + .hexpand(true) + .build(), + ) + .build(); + delete_confirm_dialog.add_response("no", "_No"); + delete_confirm_dialog.add_response("yes", "_Yes"); + delete_confirm_dialog + .set_response_appearance("yes", ResponseAppearance::Destructive); + + delete_confirm_dialog.connect_response( + None, + clone!( + #[strong] + sender, + move |dialog, res| { + let delete_files_checkbox = dialog + .extra_child() + .and_then(|child| child.downcast::().ok()); + let delete_files = delete_files_checkbox + .as_ref() + .is_some_and(|c| c.is_active()); + if let Some(check) = delete_files_checkbox { + check.set_active(false); + } + if res == "yes" { + sender + .output(Self::Output::DeleteProfile(delete_files)) + .expect("Sender output failed"); + } + } + ), + ); + delete_confirm_dialog.present(Some(&self.root_win)); } Self::Input::SaveProfile(prof) => { sender @@ -537,8 +587,29 @@ impl AsyncComponent for DebugView { .emit(ProfileEditorMsg::Present); } Self::Input::QueryProfileRebuild => { - self.query_profile_rebuild_dialog - .present(Some(&self.root_win)); + let rebuild_dialog = adw::AlertDialog::builder() + .heading("Do you want to build this profile now?") + .body("This will trigger a clean build") + .build(); + rebuild_dialog.add_response("no", "_No"); + rebuild_dialog.add_response("yes", "_Yes"); + rebuild_dialog.set_response_appearance("yes", ResponseAppearance::Suggested); + + rebuild_dialog.connect_response( + None, + clone!( + #[strong] + sender, + move |_, res| { + if res == "yes" { + sender + .output(Self::Output::BuildProfile(true)) + .expect(SENDER_IO_ERR_MSG); + } + } + ), + ); + rebuild_dialog.present(Some(&self.root_win)); } Self::Input::ProfilesEnabledChanged(enabled) => { self.set_profiles_enabled(enabled); @@ -555,92 +626,6 @@ impl AsyncComponent for DebugView { root: Self::Root, sender: AsyncComponentSender, ) -> AsyncComponentParts { - let profile_not_editable_dialog = adw::AlertDialog::builder() - .heading("This profile is not editable") - .body(concat!( - "You can duplicate it and edit the new copy. ", - "Do you want to duplicate the current profile?" - )) - .build(); - profile_not_editable_dialog.add_response("no", "_No"); - profile_not_editable_dialog.add_response("yes", "_Yes"); - profile_not_editable_dialog.set_response_appearance("yes", ResponseAppearance::Suggested); - - profile_not_editable_dialog.connect_response( - None, - clone!( - #[strong] - sender, - move |_, res| { - if res == "yes" { - sender.input(Self::Input::DuplicateProfile); - } - } - ), - ); - - let query_profile_rebuild_dialog = adw::AlertDialog::builder() - .heading("Do you want to build this profile now?") - .body("This will trigger a clean build") - .build(); - query_profile_rebuild_dialog.add_response("no", "_No"); - query_profile_rebuild_dialog.add_response("yes", "_Yes"); - query_profile_rebuild_dialog.set_response_appearance("yes", ResponseAppearance::Suggested); - - query_profile_rebuild_dialog.connect_response( - None, - clone!( - #[strong] - sender, - move |_, res| { - if res == "yes" { - sender - .output(Self::Output::BuildProfile(true)) - .expect(SENDER_IO_ERR_MSG); - } - } - ), - ); - - let profile_delete_confirm_dialog = adw::AlertDialog::builder() - .heading("Are you sure you want to delete this profile?") - .extra_child( - >k::CheckButton::builder() - .label("Delete all files and folders associated with profile") - .halign(gtk::Align::Center) - .hexpand(true) - .build(), - ) - .build(); - profile_delete_confirm_dialog.add_response("no", "_No"); - profile_delete_confirm_dialog.add_response("yes", "_Yes"); - profile_delete_confirm_dialog - .set_response_appearance("yes", ResponseAppearance::Destructive); - - profile_delete_confirm_dialog.connect_response( - None, - clone!( - #[strong] - sender, - move |dialog, res| { - let delete_files_checkbox = dialog - .extra_child() - .and_then(|child| child.downcast::().ok()); - let delete_files = delete_files_checkbox - .as_ref() - .is_some_and(|c| c.is_active()); - if let Some(check) = delete_files_checkbox { - check.set_active(false); - } - if res == "yes" { - sender - .output(Self::Output::DeleteProfile(delete_files)) - .expect("Sender output failed"); - } - } - ), - ); - let log_level_dropdown = gtk::DropDown::from_strings( LogLevel::iter() .map(|lvl| lvl.to_string()) @@ -753,8 +738,6 @@ impl AsyncComponent for DebugView { let mut model = Self { xrservice_active: false, tracker: 0, - searchbar: None, - search_entry: None, dropdown: None, log_level: LogLevel::Trace, term: TermWidget::new(), @@ -770,9 +753,6 @@ impl AsyncComponent for DebugView { profile_delete_action, profile_export_action, profile_editor: None, - profile_not_editable_dialog, - profile_delete_confirm_dialog, - query_profile_rebuild_dialog, root_win: init.root_win, profiles_enabled: init.profiles_enabled, }; @@ -787,8 +767,6 @@ impl AsyncComponent for DebugView { )); let widgets = view_output!(); - model.searchbar = Some(widgets.searchbar.clone()); - model.search_entry = Some(widgets.search_entry.clone()); model.dropdown = Some(log_level_dropdown.clone()); model.profiles_dropdown = Some(widgets.profiles_dropdown.clone());