feat: groom profiles (#986)

* upd new/rename profile

* upd rule window

* upd margins

* switch activation tab to listboxes

* update hooks tab
This commit is contained in:
Roman Makarov
2026-04-24 23:21:38 +03:00
committed by GitHub
parent 8ea8628732
commit 892b1b927a
5 changed files with 431 additions and 377 deletions
+3 -6
View File
@@ -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::<gtk::Widget>()))
.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::<gtk::Widget>()))
.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::<gtk::Widget>(),
};
let rule_window = ProfileRuleWindow::builder().launch(params).into_stream();
+53 -37
View File
@@ -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<SimpleComboBox<ProfileBase>>,
parent: gtk::Widget,
}
#[derive(Debug)]
@@ -19,49 +20,54 @@ pub enum NewProfileDialogMsg {
#[relm4::component(pub)]
impl Component for NewProfileDialog {
type Init = Vec<String>;
type Init = (Vec<String>, 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 = &gtk::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<Self>,
) -> ComponentParts<Self> {
@@ -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<Self>, root: &Self::Root) {
fn update_with_view(
&mut self,
widgets: &mut Self::Widgets,
msg: Self::Input,
sender: ComponentSender<Self>,
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);
}
}
@@ -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 = &gtk::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<Self>,
) -> ComponentParts<Self> {
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<Self>,
root: &Self::Root,
) {
match msg {
ProfileRenameDialogMsg::Save => {
sender
.output(widgets.name_entry.text().to_string())
.unwrap();
root.close();
}
}
self.update_view(widgets, sender);
}
}
+161 -162
View File
@@ -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 = &gtk::StackSwitcher {
set_stack: Some(&stack),
#[wrap(Some)]
set_child = &adw::ToolbarView {
add_top_bar = &adw::HeaderBar {
#[wrap(Some)]
set_title_widget = &gtk::StackSwitcher {
set_stack: Some(&stack),
},
},
#[name = "stack"]
append = &gtk::Stack {
add_titled[None, &fl!(I18N, "profile-activation")] = &gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_margin_all: 5,
#[wrap(Some)]
set_content = &gtk::Stack {
add_titled[None, &fl!(I18N, "profile-activation")] = &adw::PreferencesPage {
gtk::Label {
#[watch]
set_markup: &format!("<span font_desc='11'><b>{}</b></span>", 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 = &gtk::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!(
"<b>{}</b>",
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!(
"<b>{}</b>",
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 = &gtk::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")] = &gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_margin_all: 5,
gtk::Label {
#[watch]
set_markup: &format!("<span font_desc='11'><b>{}</b></span>", 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();
}
}
@@ -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 = &gtk::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 = &gtk::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")] = &gtk::Grid {
set_row_spacing: 5,
set_column_spacing: 5,
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
attach[0, 0, 1, 1] = &gtk::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] = &gtk::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")] = &gtk::Grid {
set_row_spacing: 5,
set_column_spacing: 5,
attach[3, 0, 1, 1] = &gtk::MenuButton {
set_icon_name: "view-list-symbolic",
attach[0, 0, 1, 1] = &gtk::Label {
set_label: &fl!(I18N, "profile-rule-process-name"),
set_halign: gtk::Align::Start,
},
#[wrap(Some)]
set_popover: process_filter_popover = &gtk::Popover {
gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 5,
attach[2, 0, 1, 1] = &gtk::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] = &gtk::MenuButton {
set_icon_name: "view-list-symbolic",
#[wrap(Some)]
set_popover: process_filter_popover = &gtk::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] = &gtk::Label {
set_label: &fl!(I18N, "profile-rule-args-contain"),
set_halign: gtk::Align::Start,
},
attach[1, 1, 1, 1]: filter_by_args_checkbutton = &gtk::CheckButton {
connect_toggled => ProfileRuleRowMsg::Changed,
add_binding: (&self.filter_by_args, "active"),
},
attach[2, 1, 2, 1]: args_entry = &gtk::Entry {
set_buffer: &self.args_buffer,
set_hexpand: true,
set_sensitive: false,
add_binding: (&self.filter_by_args, "sensitive"),
},
},
attach[0, 1, 1, 1] = &gtk::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")] = &gtk::Grid {
set_row_spacing: 5,
set_column_spacing: 10,
attach[0, 0, 1, 1] = &gtk::Label {
set_label: &fl!(I18N, "profile-rule-specific-process"),
set_halign: gtk::Align::Start,
},
attach[1, 0, 1, 1]: gamemode_filter_by_process_checkbutton = &gtk::CheckButton {
connect_toggled => ProfileRuleRowMsg::Changed,
add_binding: (&self.gamemode_filter_by_process, "active"),
},
attach[2, 0, 1, 1]: gamemode_process_name_entry = &gtk::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] = &gtk::Label {
set_label: &fl!(I18N, "profile-rule-args-contain"),
set_halign: gtk::Align::Start,
},
attach[1, 1, 1, 1]: gamemode_filter_by_args_checkbutton = &gtk::CheckButton {
connect_toggled => ProfileRuleRowMsg::Changed,
add_binding: (&self.filter_by_args, "active"),
},
attach[2, 1, 1, 1]: gamemode_args_entry = &gtk::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 = &gtk::CheckButton {
connect_toggled => ProfileRuleRowMsg::Changed,
add_binding: (&self.filter_by_args, "active"),
},
attach[2, 1, 2, 1]: args_entry = &gtk::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")] = &gtk::Grid {
set_row_spacing: 5,
set_column_spacing: 10,
attach[0, 0, 1, 1] = &gtk::Label {
set_label: &fl!(I18N, "profile-rule-specific-process"),
set_halign: gtk::Align::Start,
},
attach[1, 0, 1, 1]: gamemode_filter_by_process_checkbutton = &gtk::CheckButton {
connect_toggled => ProfileRuleRowMsg::Changed,
add_binding: (&self.gamemode_filter_by_process, "active"),
},
attach[2, 0, 1, 1]: gamemode_process_name_entry = &gtk::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] = &gtk::Label {
set_label: &fl!(I18N, "profile-rule-args-contain"),
set_halign: gtk::Align::Start,
},
attach[1, 1, 1, 1]: gamemode_filter_by_args_checkbutton = &gtk::CheckButton {
connect_toggled => ProfileRuleRowMsg::Changed,
add_binding: (&self.filter_by_args, "active"),
},
attach[2, 1, 1, 1]: gamemode_args_entry = &gtk::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()));
}
},
},
}
}