From 892b1b927ac8992c640eb20412d04485caa7a5d3 Mon Sep 17 00:00:00 2001 From: Roman Makarov Date: Fri, 24 Apr 2026 22:21:38 +0200 Subject: [PATCH] feat: groom profiles (#986) * upd new/rename profile * upd rule window * upd margins * switch activation tab to listboxes * update hooks tab --- lact-gui/src/app/profiles.rs | 9 +- .../src/app/profiles/new_profile_dialog.rs | 90 +++-- .../src/app/profiles/profile_rename_dialog.rs | 119 ++++--- .../src/app/profiles/profile_rule_window.rs | 323 +++++++++--------- .../profile_rule_window/profile_rule_row.rs | 267 ++++++++------- 5 files changed, 431 insertions(+), 377 deletions(-) diff --git a/lact-gui/src/app/profiles.rs b/lact-gui/src/app/profiles.rs index dee1fc63..b9856ed2 100644 --- a/lact-gui/src/app/profiles.rs +++ b/lact-gui/src/app/profiles.rs @@ -240,7 +240,7 @@ impl Component for ProfileSelector { sender.input(ProfileSelectorMsg::ClosePopover); let diag_controller = NewProfileDialog::builder() - .launch(self.custom_profiles()) + .launch((self.custom_profiles(), root.clone().upcast::())) .forward(sender.output_sender(), |(name, base)| { AppMsg::CreateProfile(name, base) }); @@ -258,10 +258,7 @@ impl Component for ProfileSelector { let sender = sender.clone(); if let ProfileRowType::Profile { name, .. } = profile.row.clone() { let stream = ProfileRenameDialog::builder() - .launch(( - name.clone(), - root.toplevel_window().expect("Widget not in a window"), - )) + .launch((name.clone(), root.clone().upcast::())) .into_stream(); sender.clone().oneshot_command(async move { @@ -299,7 +296,7 @@ impl Component for ProfileSelector { rule: rule.clone().unwrap_or_default(), hooks: hooks.clone(), auto_switch: *auto, - root_window: root.toplevel_window().expect("Widget not in a window"), + parent: root.clone().upcast::(), }; let rule_window = ProfileRuleWindow::builder().launch(params).into_stream(); diff --git a/lact-gui/src/app/profiles/new_profile_dialog.rs b/lact-gui/src/app/profiles/new_profile_dialog.rs index c33fc7f8..9e43308e 100644 --- a/lact-gui/src/app/profiles/new_profile_dialog.rs +++ b/lact-gui/src/app/profiles/new_profile_dialog.rs @@ -1,15 +1,16 @@ use crate::I18N; +use adw::prelude::{AdwDialogExt, EntryRowExt, PreferencesPageExt, PreferencesRowExt}; use gtk::prelude::*; use i18n_embed_fl::fl; use lact_schema::request::ProfileBase; use relm4::{ - Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmWidgetExt, + Component, ComponentController, ComponentParts, ComponentSender, Controller, RelmWidgetExt, css, }; use relm4_components::simple_combo_box::SimpleComboBox; pub struct NewProfileDialog { - name_buffer: gtk::EntryBuffer, base_selector: Controller>, + parent: gtk::Widget, } #[derive(Debug)] @@ -19,49 +20,54 @@ pub enum NewProfileDialogMsg { #[relm4::component(pub)] impl Component for NewProfileDialog { - type Init = Vec; + type Init = (Vec, gtk::Widget); type Input = NewProfileDialogMsg; type Output = (String, ProfileBase); type CommandOutput = (); view! { - gtk::Window { - set_default_size: (250, 130), - set_title: Some(&fl!(I18N, "create-profile")), - set_hide_on_close: true, + #[root] + adw::Dialog { + set_content_width: 420, + set_title: &fl!(I18N, "create-profile"), - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_spacing: 5, - set_margin_all: 10, + #[wrap(Some)] + set_child = &adw::ToolbarView { + add_top_bar = &adw::HeaderBar {}, - gtk::Entry { - set_placeholder_text: Some(&fl!(I18N, "name")), - set_buffer: &model.name_buffer, - }, + #[wrap(Some)] + set_content = &adw::PreferencesPage { + add = &adw::PreferencesGroup { + #[name = "name_entry"] + adw::EntryRow { + set_title: &fl!(I18N, "name"), + connect_entry_activated => NewProfileDialogMsg::Create, + }, - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_spacing: 5, + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 10, + set_margin_top: 10, - gtk::Label { - set_label: &fl!(I18N, "profile-copy-from"), - }, + gtk::Label { + set_label: &fl!(I18N, "profile-copy-from"), + set_hexpand: true, + set_xalign: 0.0, + }, - #[local_ref] - base_selector -> gtk::ComboBoxText { - set_margin_horizontal: 5, - set_hexpand: true, - set_halign: gtk::Align::End, + #[local_ref] + base_selector -> gtk::ComboBoxText { + set_valign: gtk::Align::Center, + }, + }, }, }, - gtk::Box { + add_bottom_bar = >k::Box { set_orientation: gtk::Orientation::Horizontal, - set_spacing: 5, - set_hexpand: true, - set_vexpand: true, - set_valign: gtk::Align::End, + set_spacing: 10, + set_margin_horizontal: 10, + set_margin_bottom: 10, gtk::Button { set_label: &fl!(I18N, "cancel"), @@ -77,6 +83,7 @@ impl Component for NewProfileDialog { gtk::Button { set_label: &fl!(I18N, "create"), set_hexpand: true, + add_css_class: css::SUGGESTED_ACTION, connect_clicked => NewProfileDialogMsg::Create, }, @@ -86,7 +93,7 @@ impl Component for NewProfileDialog { } fn init( - current_profiles: Self::Init, + (current_profiles, parent): Self::Init, root: Self::Root, sender: ComponentSender, ) -> ComponentParts { @@ -102,32 +109,41 @@ impl Component for NewProfileDialog { let model = Self { base_selector, - name_buffer: gtk::EntryBuffer::default(), + parent, }; let base_selector = model.base_selector.widget(); let widgets = view_output!(); - root.present(); + root.set_focus(Some(&widgets.name_entry)); + root.present(Some(&model.parent)); ComponentParts { model, widgets } } - fn update(&mut self, msg: Self::Input, sender: ComponentSender, root: &Self::Root) { + fn update_with_view( + &mut self, + widgets: &mut Self::Widgets, + msg: Self::Input, + sender: ComponentSender, + root: &Self::Root, + ) { match msg { NewProfileDialogMsg::Create => { - if self.name_buffer.length() != 0 + if !widgets.name_entry.text().is_empty() && let Some(selected) = self.base_selector.model().active_index { let base = self.base_selector.model().variants[selected].clone(); sender - .output((self.name_buffer.text().to_string(), base)) + .output((widgets.name_entry.text().to_string(), base)) .unwrap(); root.close(); } } } + + self.update_view(widgets, sender); } } diff --git a/lact-gui/src/app/profiles/profile_rename_dialog.rs b/lact-gui/src/app/profiles/profile_rename_dialog.rs index 3b34adff..98d27260 100644 --- a/lact-gui/src/app/profiles/profile_rename_dialog.rs +++ b/lact-gui/src/app/profiles/profile_rename_dialog.rs @@ -1,69 +1,106 @@ use crate::I18N; -use gtk::prelude::{ - BoxExt, DialogExt, DialogExtManual, EditableExt, EntryExt, GtkWindowExt, OrientableExt, - WidgetExt, -}; +use adw::prelude::{AdwDialogExt, EntryRowExt, PreferencesPageExt, PreferencesRowExt}; +use gtk::prelude::{BoxExt, ButtonExt, EditableExt, ObjectExt, OrientableExt, WidgetExt}; use i18n_embed_fl::fl; -use relm4::{ComponentParts, ComponentSender, RelmWidgetExt}; +use relm4::{ComponentParts, ComponentSender, RelmWidgetExt, css}; -pub struct ProfileRenameDialog {} +pub struct ProfileRenameDialog { + old_name: String, + parent: gtk::Widget, +} + +#[derive(Debug)] +pub enum ProfileRenameDialogMsg { + Save, +} #[relm4::component(pub)] -impl relm4::SimpleComponent for ProfileRenameDialog { - type Init = (String, gtk::Window); - type Input = (); +impl relm4::Component for ProfileRenameDialog { + type Init = (String, gtk::Widget); + type Input = ProfileRenameDialogMsg; type Output = String; + type CommandOutput = (); view! { - gtk::Dialog { - set_default_size: (400, 50), - set_title: Some(&fl!(I18N, "rename-profile")), - set_transient_for: Some(&root_window), - set_hide_on_close: true, - connect_response[root, sender, name_entry] => move |_, response| { - match response { - gtk::ResponseType::Accept => { - sender.output(name_entry.text().to_string()).unwrap(); - root.close(); - } - gtk::ResponseType::Cancel => root.close(), - _ => (), - } - }, - add_buttons: &[(&fl!(I18N, "cancel"), gtk::ResponseType::Cancel), (&fl!(I18N, "save"), gtk::ResponseType::Accept)], + #[root] + adw::Dialog { + set_content_width: 420, + set_title: &fl!(I18N, "rename-profile"), - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_margin_all: 5, - set_spacing: 5, + #[wrap(Some)] + set_child = &adw::ToolbarView { + add_top_bar = &adw::HeaderBar {}, - gtk::Label { - set_markup: &fl!(I18N, "rename-profile-from", old_name = old_name.clone()), + #[wrap(Some)] + set_content = &adw::PreferencesPage { + add = &adw::PreferencesGroup { + #[name = "name_entry"] + adw::EntryRow { + set_title: &fl!(I18N, "name"), + set_text: &model.old_name, + connect_entry_activated => ProfileRenameDialogMsg::Save, + }, + }, }, - #[name = "name_entry"] - gtk::Entry { - set_text: &old_name, - set_hexpand: true, - connect_activate[root] => move |_| { - root.response(gtk::ResponseType::Accept); - } + add_bottom_bar = >k::Box { + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 10, + set_margin_horizontal: 10, + set_margin_bottom: 10, + + gtk::Button { + set_label: &fl!(I18N, "cancel"), + set_hexpand: true, + connect_clicked[root = root.downgrade()] => move |_| { + if let Some(root) = root.upgrade() { + root.close(); + } + }, + }, + + gtk::Button { + set_label: &fl!(I18N, "save"), + set_hexpand: true, + add_css_class: css::SUGGESTED_ACTION, + connect_clicked => ProfileRenameDialogMsg::Save, + }, }, } }, } fn init( - (old_name, root_window): Self::Init, + (old_name, parent): Self::Init, root: Self::Root, sender: ComponentSender, ) -> ComponentParts { - let model = Self {}; + let model = Self { old_name, parent }; let widgets = view_output!(); - root.present(); + root.set_focus(Some(&widgets.name_entry)); + root.present(Some(&model.parent)); ComponentParts { widgets, model } } + + fn update_with_view( + &mut self, + widgets: &mut Self::Widgets, + msg: Self::Input, + sender: ComponentSender, + root: &Self::Root, + ) { + match msg { + ProfileRenameDialogMsg::Save => { + sender + .output(widgets.name_entry.text().to_string()) + .unwrap(); + root.close(); + } + } + + self.update_view(widgets, sender); + } } diff --git a/lact-gui/src/app/profiles/profile_rule_window.rs b/lact-gui/src/app/profiles/profile_rule_window.rs index eb94da6b..6e5ef3ef 100644 --- a/lact-gui/src/app/profiles/profile_rule_window.rs +++ b/lact-gui/src/app/profiles/profile_rule_window.rs @@ -2,12 +2,11 @@ pub mod profile_rule_row; use crate::I18N; use crate::app::{APP_BROKER, msg::AppMsg}; -use gtk::prelude::ObjectExt; +use adw::prelude::{AdwDialogExt, ObjectExt, PreferencesGroupExt, PreferencesPageExt}; use gtk::{ pango, prelude::{ - BoxExt, ButtonExt, CheckButtonExt, DialogExt, DialogExtManual, EntryBufferExtManual, - EntryExt, GtkWindowExt, OrientableExt, WidgetExt, + BoxExt, ButtonExt, CheckButtonExt, EntryBufferExtManual, EntryExt, OrientableExt, WidgetExt, }, }; use i18n_embed_fl::fl; @@ -16,6 +15,7 @@ use profile_rule_row::ProfileRuleRow; use relm4::{ ComponentParts, ComponentSender, RelmObjectExt, RelmWidgetExt, binding::BoolBinding, + css, prelude::{DynamicIndex, FactoryVecDeque}, tokio::time::sleep, }; @@ -41,7 +41,7 @@ pub struct ProfileEditParams { pub rule: ProfileRule, pub hooks: ProfileHooks, pub auto_switch: bool, - pub root_window: gtk::Window, + pub parent: gtk::Widget, } #[derive(Debug)] @@ -61,59 +61,38 @@ impl relm4::Component for ProfileRuleWindow { type CommandOutput = (); view! { - gtk::Dialog { - set_default_size: (600, 300), - set_title: Some(&fl!(I18N, "profile-rules")), - set_transient_for: Some(&root_window), - connect_response[root = root.downgrade(), sender] => move |_, response| { - if let Some(root) = root.upgrade() { - match response { - gtk::ResponseType::Accept => { - sender.input(ProfileRuleWindowMsg::Save); - root.close(); - } - gtk::ResponseType::Cancel => root.close(), - _ => (), - } - } - }, + #[root] + adw::Dialog { + set_content_width: 640, + set_follows_content_size: true, + set_title: &fl!(I18N, "profile-rules"), - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_margin_all: 5, - - append = >k::StackSwitcher { - set_stack: Some(&stack), + #[wrap(Some)] + set_child = &adw::ToolbarView { + add_top_bar = &adw::HeaderBar { + #[wrap(Some)] + set_title_widget = >k::StackSwitcher { + set_stack: Some(&stack), + }, }, #[name = "stack"] - append = >k::Stack { - add_titled[None, &fl!(I18N, "profile-activation")] = >k::Box { - set_orientation: gtk::Orientation::Vertical, - set_margin_all: 5, + #[wrap(Some)] + set_content = >k::Stack { + add_titled[None, &fl!(I18N, "profile-activation")] = &adw::PreferencesPage { - gtk::Label { - #[watch] - set_markup: &format!("{}", fl!(I18N, "profile-activation-desc", name = model.profile_name.as_str())), - set_halign: gtk::Align::Start, - set_margin_all: 10, - }, + add = &adw::PreferencesGroup { + set_title: &fl!(I18N, "profile-activation-desc", name = model.profile_name.as_str()), - gtk::Separator {}, - - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_expand: true, - - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_margin_all: 10, - set_spacing: 10, + gtk::ListBox { + set_selection_mode: gtk::SelectionMode::None, + add_css_class: css::BOXED_LIST, #[name = "multi_or_checkbutton"] gtk::CheckButton { set_label: Some(&fl!(I18N, "any-rules-matched")), set_active: !matches!(rule, ProfileRule::And(_)), + set_margin_all: 10, connect_toggled => ProfileRuleWindowMsg::Evaluate, }, @@ -122,133 +101,152 @@ impl relm4::Component for ProfileRuleWindow { set_label: Some(&fl!(I18N, "all-rules-matched")), set_group: Some(&multi_or_checkbutton), set_active: matches!(rule, ProfileRule::And(_)), + set_margin_all: 10, connect_toggled => ProfileRuleWindowMsg::Evaluate, }, + }, + }, - gtk::Separator {}, + add = &adw::PreferencesGroup { + set_title: &fl!(I18N, "profile-rules"), - #[local_ref] - sub_rules_listview -> gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_spacing: 5, + #[wrap(Some)] + set_header_suffix = >k::Button { + set_icon_name: "list-add-symbolic", + set_tooltip: &fl!(I18N, "profile-rules"), + add_css_class: "flat", + connect_clicked => ProfileRuleWindowMsg::AddSubrule, + }, + + #[local_ref] + sub_rules_listview -> gtk::ListBox { + set_selection_mode: gtk::SelectionMode::None, + add_css_class: css::BOXED_LIST, + }, + }, + + add = &adw::PreferencesGroup { + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 5, + + gtk::Image { + #[watch] + set_icon_name: match model.currently_matches { + true => Some("object-select-symbolic"), + false => Some("list-remove-symbolic"), + }, }, - gtk::Button { - set_icon_name: "list-add-symbolic", + gtk::Label { + #[watch] + set_markup: &if model.auto_switch { + fl!(I18N, "activation-settings-status", matched = model.currently_matches.to_string()) + } else { + format!( + "{}", + fl!(I18N, "activation-auto-switching-disabled") + ) + }, + }, + }, + }, + }, + + add_titled[None, &fl!(I18N, "profile-hooks")] = &adw::PreferencesPage { + + add = &adw::PreferencesGroup { + set_description: Some(&fl!(I18N, "profile-hook-command", cmd = model.profile_name.as_str())), + + gtk::ListBox { + set_selection_mode: gtk::SelectionMode::None, + add_css_class: css::BOXED_LIST, + + gtk::ListBoxRow { + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 10, + set_margin_all: 10, + + gtk::CheckButton { + set_label: Some(&fl!(I18N, "profile-hook-activated")), + add_binding: (&model.activated_hook_enabled, "active"), + set_size_group: &hook_command_size_group, + }, + + gtk::Entry { + add_binding: (&model.activated_hook_enabled, "sensitive"), + set_buffer: &model.activated_hook, + set_hexpand: true, + }, + }, + }, + + gtk::ListBoxRow { + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 10, + set_margin_all: 10, + + gtk::CheckButton { + set_label: Some(&fl!(I18N, "profile-hook-deactivated")), + add_binding: (&model.deactivated_hook_enabled, "active"), + set_size_group: &hook_command_size_group, + }, + + gtk::Entry { + add_binding: (&model.deactivated_hook_enabled, "sensitive"), + set_buffer: &model.deactivated_hook, + set_hexpand: true, + }, + }, + }, + }, + }, + + add = &adw::PreferencesGroup { + gtk::Box { + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 5, + + gtk::Image { + set_icon_name: Some("dialog-warning-symbolic"), + }, + + gtk::Label { + set_label: &fl!(I18N, "profile-hook-note"), + set_wrap: true, + set_wrap_mode: pango::WrapMode::Word, + add_css_class: "caption-heading", set_hexpand: true, - set_halign: gtk::Align::End, - connect_clicked => ProfileRuleWindowMsg::AddSubrule, }, - - gtk::Separator {}, - - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_spacing: 5, - - gtk::Label { - #[watch] - set_markup: &if model.auto_switch { - fl!(I18N, "activation-settings-status", matched = model.currently_matches.to_string()) - } else { - format!( - "{}", - fl!(I18N, "activation-auto-switching-disabled") - ) - }, - }, - - gtk::Image { - #[watch] - set_icon_name: match model.currently_matches { - true => Some("object-select-symbolic"), - false => Some("list-remove-symbolic"), - }, - }, - } - }, + } }, + }, + }, - gtk::Separator {}, + add_bottom_bar = >k::Box { + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 10, + set_margin_all: 10, + set_halign: gtk::Align::End, + + gtk::Button { + set_label: &fl!(I18N, "cancel"), + connect_clicked[root = root.downgrade()] => move |_| { + if let Some(root) = root.upgrade() { + root.close(); + } + }, }, - add_titled[None, &fl!(I18N, "profile-hooks")] = >k::Box { - set_orientation: gtk::Orientation::Vertical, - set_margin_all: 5, - - gtk::Label { - #[watch] - set_markup: &format!("{}", fl!(I18N, "profile-hook-command", cmd = model.profile_name.as_str())), - set_halign: gtk::Align::Start, - set_margin_all: 10, - }, - - gtk::Separator {}, - - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_spacing: 5, - set_margin_vertical: 5, - set_margin_horizontal: 10, - - - gtk::CheckButton { - set_label: Some(&fl!(I18N, "profile-hook-activated")), - add_binding: (&model.activated_hook_enabled, "active"), - set_size_group: &hook_command_size_group, - }, - - gtk::Entry { - add_binding: (&model.activated_hook_enabled, "sensitive"), - set_buffer: &model.activated_hook, - set_hexpand: true, - }, - }, - - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_spacing: 5, - set_margin_vertical: 5, - set_margin_horizontal: 10, - - gtk::CheckButton { - set_label: Some(&fl!(I18N, "profile-hook-deactivated")), - add_binding: (&model.deactivated_hook_enabled, "active"), - set_size_group: &hook_command_size_group, - }, - - gtk::Entry { - add_binding: (&model.deactivated_hook_enabled, "sensitive"), - set_buffer: &model.deactivated_hook, - set_hexpand: true, - }, - }, - - gtk::Separator {}, - - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_spacing: 5, - set_margin_vertical: 5, - set_margin_horizontal: 10, - - gtk::Image { - set_icon_name: Some("dialog-warning-symbolic"), - }, - - gtk::Label { - set_label: &fl!(I18N, "profile-hook-note"), - set_wrap: true, - set_wrap_mode: pango::WrapMode::Word, - add_css_class: "caption-heading", - set_hexpand: true, - }, - } + gtk::Button { + set_label: &fl!(I18N, "save"), + add_css_class: css::SUGGESTED_ACTION, + connect_clicked => ProfileRuleWindowMsg::Save, }, - } - }, - - add_buttons: &[("Cancel", gtk::ResponseType::Cancel), ("Save", gtk::ResponseType::Accept)], + }, + } } } @@ -263,7 +261,7 @@ impl relm4::Component for ProfileRuleWindow { rule, hooks, auto_switch, - root_window, + parent, } = params; sender.command(move |_, shutdown| { @@ -307,7 +305,7 @@ impl relm4::Component for ProfileRuleWindow { let hook_command_size_group = gtk::SizeGroup::new(gtk::SizeGroupMode::Horizontal); let widgets = view_output!(); - root.present(); + root.present(Some(&parent)); ComponentParts { model, widgets } } @@ -347,6 +345,7 @@ impl relm4::Component for ProfileRuleWindow { self.get_hooks(), )) .unwrap(); + root.close(); } } diff --git a/lact-gui/src/app/profiles/profile_rule_window/profile_rule_row.rs b/lact-gui/src/app/profiles/profile_rule_window/profile_rule_row.rs index 60f7094b..0dbf4dd1 100644 --- a/lact-gui/src/app/profiles/profile_rule_window/profile_rule_row.rs +++ b/lact-gui/src/app/profiles/profile_rule_window/profile_rule_row.rs @@ -49,167 +49,172 @@ impl relm4::factory::FactoryComponent for ProfileRuleRow { type Input = ProfileRuleRowMsg; type Output = ProfileRuleWindowMsg; type CommandOutput = (); - type ParentWidget = gtk::Box; + type ParentWidget = gtk::ListBox; view! { - gtk::Box { - gtk::Label { - set_hexpand: true, - set_halign: gtk::Align::Start, - set_ellipsize: pango::EllipsizeMode::End, - #[watch] - set_markup: &format_rule(&self.get_configured_rule()), - }, + gtk::ListBoxRow { + gtk::Box { + set_spacing: 10, + set_margin_all: 12, - gtk::MenuButton { - set_icon_name: "open-menu-symbolic", - set_tooltip: &fl!(I18N, "edit-rule"), + gtk::Label { + set_hexpand: true, + set_halign: gtk::Align::Start, + set_ellipsize: pango::EllipsizeMode::End, + #[watch] + set_markup: &format_rule(&self.get_configured_rule()), + }, - #[wrap(Some)] - set_popover: main_popover = >k::Popover { - gtk::Box { - set_orientation: gtk::Orientation::Horizontal, - set_spacing: 10, - set_size_request: (300, 120), - - gtk::StackSidebar { - set_stack: &stack, - }, + gtk::MenuButton { + set_icon_name: "open-menu-symbolic", + set_tooltip: &fl!(I18N, "edit-rule"), + #[wrap(Some)] + set_popover: main_popover = >k::Popover { gtk::Box { - set_orientation: gtk::Orientation::Vertical, + set_orientation: gtk::Orientation::Horizontal, + set_spacing: 10, + set_size_request: (300, 120), - #[name = "stack"] - gtk::Stack { - connect_visible_child_name_notify => ProfileRuleRowMsg::Changed, + gtk::StackSidebar { + set_stack: &stack, + }, - add_titled[Some(PROCESS_PAGE), &fl!(I18N, "profile-rule-process-tab")] = >k::Grid { - set_row_spacing: 5, - set_column_spacing: 5, + gtk::Box { + set_orientation: gtk::Orientation::Vertical, - attach[0, 0, 1, 1] = >k::Label { - set_label: &fl!(I18N, "profile-rule-process-name"), - set_halign: gtk::Align::Start, - }, + #[name = "stack"] + gtk::Stack { + connect_visible_child_name_notify => ProfileRuleRowMsg::Changed, - attach[2, 0, 1, 1] = >k::Entry { - set_buffer: &self.process_name_buffer, - set_hexpand: true, - set_placeholder_text: Some("Cyberpunk2077.exe"), - }, + add_titled[Some(PROCESS_PAGE), &fl!(I18N, "profile-rule-process-tab")] = >k::Grid { + set_row_spacing: 5, + set_column_spacing: 5, - attach[3, 0, 1, 1] = >k::MenuButton { - set_icon_name: "view-list-symbolic", + attach[0, 0, 1, 1] = >k::Label { + set_label: &fl!(I18N, "profile-rule-process-name"), + set_halign: gtk::Align::Start, + }, - #[wrap(Some)] - set_popover: process_filter_popover = >k::Popover { - gtk::Box { - set_orientation: gtk::Orientation::Vertical, - set_spacing: 5, + attach[2, 0, 1, 1] = >k::Entry { + set_buffer: &self.process_name_buffer, + set_hexpand: true, + set_placeholder_text: Some("Cyberpunk2077.exe"), + }, - gtk::SearchEntry { - connect_search_changed[sender] => move |entry| { - sender.input(ProfileRuleRowMsg::ProcessFilterChanged(entry.text())); + attach[3, 0, 1, 1] = >k::MenuButton { + set_icon_name: "view-list-symbolic", + + #[wrap(Some)] + set_popover: process_filter_popover = >k::Popover { + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 5, + + gtk::SearchEntry { + connect_search_changed[sender] => move |entry| { + sender.input(ProfileRuleRowMsg::ProcessFilterChanged(entry.text())); + }, }, + + gtk::ScrolledWindow { + set_size_request: (400, 350), + + self.process_listview.view.clone() -> gtk::ListView { + set_show_separators: true, + }, + } }, - gtk::ScrolledWindow { - set_size_request: (400, 350), - - self.process_listview.view.clone() -> gtk::ListView { - set_show_separators: true, - }, - } + connect_visible_notify[sender] => move |_| { + debug!("requesting profile watcher state"); + APP_BROKER.send(AppMsg::ReloadProfiles { state_sender: Some(sender.input_sender().clone())}); + }, }, + }, - connect_visible_notify[sender] => move |_| { - debug!("requesting profile watcher state"); - APP_BROKER.send(AppMsg::ReloadProfiles { state_sender: Some(sender.input_sender().clone())}); - }, + attach[0, 1, 1, 1] = >k::Label { + set_label: &fl!(I18N, "profile-rule-args-contain"), + set_halign: gtk::Align::Start, + }, + + + attach[1, 1, 1, 1]: filter_by_args_checkbutton = >k::CheckButton { + connect_toggled => ProfileRuleRowMsg::Changed, + add_binding: (&self.filter_by_args, "active"), + }, + + attach[2, 1, 2, 1]: args_entry = >k::Entry { + set_buffer: &self.args_buffer, + set_hexpand: true, + set_sensitive: false, + add_binding: (&self.filter_by_args, "sensitive"), }, }, - attach[0, 1, 1, 1] = >k::Label { - set_label: &fl!(I18N, "profile-rule-args-contain"), - set_halign: gtk::Align::Start, + add_titled[Some(GAMEMODE_PAGE), &fl!(I18N, "profile-rule-gamemode-tab")] = >k::Grid { + set_row_spacing: 5, + set_column_spacing: 10, + + attach[0, 0, 1, 1] = >k::Label { + set_label: &fl!(I18N, "profile-rule-specific-process"), + set_halign: gtk::Align::Start, + }, + + attach[1, 0, 1, 1]: gamemode_filter_by_process_checkbutton = >k::CheckButton { + connect_toggled => ProfileRuleRowMsg::Changed, + add_binding: (&self.gamemode_filter_by_process, "active"), + }, + + attach[2, 0, 1, 1]: gamemode_process_name_entry = >k::Entry { + set_buffer: &self.process_name_buffer, + set_hexpand: true, + set_placeholder_text: Some("Cyberpunk2077.exe"), + set_sensitive: false, + add_binding: (&self.gamemode_filter_by_process, "sensitive"), + }, + + attach[0, 1, 1, 1] = >k::Label { + set_label: &fl!(I18N, "profile-rule-args-contain"), + set_halign: gtk::Align::Start, + }, + + attach[1, 1, 1, 1]: gamemode_filter_by_args_checkbutton = >k::CheckButton { + connect_toggled => ProfileRuleRowMsg::Changed, + add_binding: (&self.filter_by_args, "active"), + }, + + attach[2, 1, 1, 1]: gamemode_args_entry = >k::Entry { + set_buffer: &self.args_buffer, + set_hexpand: true, + set_sensitive: false, + add_binding: (&self.filter_by_args, "sensitive"), + }, }, - - attach[1, 1, 1, 1]: filter_by_args_checkbutton = >k::CheckButton { - connect_toggled => ProfileRuleRowMsg::Changed, - add_binding: (&self.filter_by_args, "active"), - }, - - attach[2, 1, 2, 1]: args_entry = >k::Entry { - set_buffer: &self.args_buffer, - set_hexpand: true, - set_sensitive: false, - add_binding: (&self.filter_by_args, "sensitive"), - }, + add_binding: (&self.selected_page, "visible-child-name"), }, - add_titled[Some(GAMEMODE_PAGE), &fl!(I18N, "profile-rule-gamemode-tab")] = >k::Grid { - set_row_spacing: 5, - set_column_spacing: 10, - - attach[0, 0, 1, 1] = >k::Label { - set_label: &fl!(I18N, "profile-rule-specific-process"), - set_halign: gtk::Align::Start, - }, - - attach[1, 0, 1, 1]: gamemode_filter_by_process_checkbutton = >k::CheckButton { - connect_toggled => ProfileRuleRowMsg::Changed, - add_binding: (&self.gamemode_filter_by_process, "active"), - }, - - attach[2, 0, 1, 1]: gamemode_process_name_entry = >k::Entry { - set_buffer: &self.process_name_buffer, - set_hexpand: true, - set_placeholder_text: Some("Cyberpunk2077.exe"), - set_sensitive: false, - add_binding: (&self.gamemode_filter_by_process, "sensitive"), - }, - - attach[0, 1, 1, 1] = >k::Label { - set_label: &fl!(I18N, "profile-rule-args-contain"), - set_halign: gtk::Align::Start, - }, - - attach[1, 1, 1, 1]: gamemode_filter_by_args_checkbutton = >k::CheckButton { - connect_toggled => ProfileRuleRowMsg::Changed, - add_binding: (&self.filter_by_args, "active"), - }, - - attach[2, 1, 1, 1]: gamemode_args_entry = >k::Entry { - set_buffer: &self.args_buffer, - set_hexpand: true, - set_sensitive: false, - add_binding: (&self.filter_by_args, "sensitive"), - }, - }, - - add_binding: (&self.selected_page, "visible-child-name"), - }, - - gtk::Button { - set_label: "OK", - set_align: gtk::Align::End, - set_expand: true, - connect_clicked[main_popover] => move |_| { - main_popover.popdown(); + gtk::Button { + set_label: "OK", + set_align: gtk::Align::End, + set_expand: true, + connect_clicked[main_popover] => move |_| { + main_popover.popdown(); + } } - } + }, }, }, }, - }, - gtk::Button { - set_icon_name: "list-remove-symbolic", - set_tooltip: &fl!(I18N, "remove-rule"), - connect_clicked[sender, index] => move |_| { - let _ = sender.output(ProfileRuleWindowMsg::RemoveSubrule(index.clone())); - } + gtk::Button { + set_icon_name: "list-remove-symbolic", + set_tooltip: &fl!(I18N, "remove-rule"), + connect_clicked[sender, index] => move |_| { + let _ = sender.output(ProfileRuleWindowMsg::RemoveSubrule(index.clone())); + } + }, }, } }