From 0fe33264f4fad794bb7ddd08604af89d2247cef1 Mon Sep 17 00:00:00 2001 From: Nicolai Syvertsen Date: Mon, 25 Dec 2023 16:09:57 +0100 Subject: [PATCH] Use PwProfileRow widget for profile dropdown and add checkmark for selected item --- data/resources/resources.gresource.xml | 1 + data/resources/ui/profilerow.ui | 23 +++++++ src/ui/mod.rs | 2 + src/ui/profile_dropdown.rs | 72 ++++++++++----------- src/ui/profilerow.rs | 86 ++++++++++++++++++++++++++ 5 files changed, 146 insertions(+), 38 deletions(-) create mode 100644 data/resources/ui/profilerow.ui create mode 100644 src/ui/profilerow.rs diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml index 2383610..d2e1f99 100644 --- a/data/resources/resources.gresource.xml +++ b/data/resources/resources.gresource.xml @@ -11,6 +11,7 @@ ui/outputbox.ui ui/profile-dropdown.ui ui/devicebox.ui + ui/profilerow.ui ../icons/com.saivert.pwvucontrol.svg diff --git a/data/resources/ui/profilerow.ui b/data/resources/ui/profilerow.ui new file mode 100644 index 0000000..06dc86c --- /dev/null +++ b/data/resources/ui/profilerow.ui @@ -0,0 +1,23 @@ + + + + + + + \ No newline at end of file diff --git a/src/ui/mod.rs b/src/ui/mod.rs index af61ee0..624567e 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -8,6 +8,7 @@ mod sinkbox; mod outputbox; mod profile_dropdown; mod devicebox; +mod profilerow; pub use window::PwvucontrolWindow; pub use window::PwvucontrolWindowView; @@ -19,3 +20,4 @@ pub use sinkbox::PwSinkBox; pub use channelbox::PwChannelBox; pub use levelprovider::LevelbarProvider; pub use outputbox::PwOutputBox; +pub use profilerow::PwProfileRow; diff --git a/src/ui/profile_dropdown.rs b/src/ui/profile_dropdown.rs index 7fdc3c7..6ede430 100644 --- a/src/ui/profile_dropdown.rs +++ b/src/ui/profile_dropdown.rs @@ -1,12 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-or-later -use crate::{backend::PwDeviceObject, backend::{PwProfileObject, ProfileAvailability}}; +use crate::backend::PwDeviceObject; use glib::closure_local; use gtk::{self, prelude::*, subclass::prelude::*}; use glib::clone; use wp::pw::ProxyExt; use std::cell::{Cell, RefCell}; use wireplumber as wp; +use crate::ui::PwProfileRow; mod imp { use super::*; @@ -148,50 +149,45 @@ mod imp { fn setup_handler(item: &glib::Object, list: bool) { let item: >k::ListItem = item.downcast_ref().expect("ListItem"); - let box_ = gtk::Box::new(gtk::Orientation::Horizontal, 6); - let unavailable_icon = gtk::Image::from_icon_name("action-unavailable-symbolic"); - let label = gtk::Label::new(None); - box_.append(&label); - box_.append(&unavailable_icon); - label.set_xalign(0.0); - if !list { - label.set_ellipsize(gtk::pango::EllipsizeMode::End); - } - label.set_use_markup(true); - - item.property_expression("item") - .chain_property::("description") - .bind(&label, "label", gtk::Widget::NONE); - - // let opacity_closure = closure_local!(|_: Option, availability: u32| { - // match availability { - // 2 => 1.0f32, - // _ => 0.5f32 - // } - // }); - - // item.property_expression("item") - // .chain_property::("availability") - // .chain_closure::(opacity_closure) - // .bind(&label, "opacity", glib::Object::NONE); - - let icon_closure = closure_local!(|_: Option, availability: ProfileAvailability| { - availability == ProfileAvailability::No - }); - - item.property_expression("item") - .chain_property::("availability") - .chain_closure::(icon_closure) - .bind(&unavailable_icon, "visible", glib::Object::NONE); - - item.set_child(Some(&box_)); + let profilerow = PwProfileRow::new(); + + profilerow.setup(item, list); + item.set_child(Some(&profilerow)); } + fn bind_handler(item: &glib::Object, dropdown: >k::DropDown) { + let item: >k::ListItem = item.downcast_ref().expect("ListItem"); + let profilerow = item + .child() + .and_downcast::() + .expect("PwProfileRow child"); + + let signal = dropdown.connect_selected_item_notify(clone!(@weak item => move |dropdown| { + let profilerow = item + .child() + .and_downcast::() + .expect("PwProfileRow child"); + profilerow.set_selected(dropdown.selected_item() == item.item()); + })); + profilerow.set_handlerid(Some(signal)); + } + + let dropdown = self.profile_dropdown.get(); + let factory = gtk::SignalListItemFactory::new(); factory.connect_setup(|_, item| setup_handler(item, false)); let list_factory = gtk::SignalListItemFactory::new(); list_factory.connect_setup(|_, item| setup_handler(item, true)); + list_factory.connect_bind(clone!(@weak dropdown => move |_, item| bind_handler(item, &dropdown))); + list_factory.connect_unbind(clone!(@weak dropdown => move |_, item| { + let item: >k::ListItem = item.downcast_ref().expect("ListItem"); + let profilerow = item + .child() + .and_downcast::() + .expect("The child has to be a `PwProfileRow`."); + profilerow.set_handlerid(None); + })); self.profile_dropdown.set_factory(Some(&factory)); self.profile_dropdown.set_list_factory(Some(&list_factory)); diff --git a/src/ui/profilerow.rs b/src/ui/profilerow.rs new file mode 100644 index 0000000..4a38be7 --- /dev/null +++ b/src/ui/profilerow.rs @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +use glib::closure_local; +use gtk::{prelude::*, subclass::prelude::*}; +use std::cell::RefCell; + +use crate::backend::{PwProfileObject, ProfileAvailability}; + +mod imp { + use super::*; + + #[derive(Default, gtk::CompositeTemplate)] + #[template(resource = "/com/saivert/pwvucontrol/gtk/profilerow.ui")] + pub struct PwProfileRow { + #[template_child] + pub label: TemplateChild, + #[template_child] + pub unavailable_icon: TemplateChild, + #[template_child] + pub checkmark_icon: TemplateChild, + + pub(super) signalid: RefCell>, + } + + #[glib::object_subclass] + impl ObjectSubclass for PwProfileRow { + const NAME: &'static str = "PwProfileRow"; + type Type = super::PwProfileRow; + type ParentType = gtk::Box; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + impl ObjectImpl for PwProfileRow {} + impl WidgetImpl for PwProfileRow {} + impl BoxImpl for PwProfileRow {} + impl PwProfileRow {} +} + +glib::wrapper! { + pub struct PwProfileRow(ObjectSubclass) + @extends gtk::Box, gtk::Widget, + @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable; +} + +impl PwProfileRow { + pub(crate) fn new() -> Self { + glib::Object::builder().build() + } + + pub fn setup(&self, item: >k::ListItem, list: bool) { + let label = self.imp().label.get(); + let unavailable_icon = self.imp().unavailable_icon.get(); + + if !list { + label.set_ellipsize(gtk::pango::EllipsizeMode::End); + } + + item.property_expression("item") + .chain_property::("description") + .bind(&label, "label", gtk::Widget::NONE); + + let icon_closure = closure_local!(|_: Option, availability: ProfileAvailability| { + availability == ProfileAvailability::No + }); + + item.property_expression("item") + .chain_property::("availability") + .chain_closure::(icon_closure) + .bind(&unavailable_icon, "visible", glib::Object::NONE); + } + + pub fn set_selected(&self, selected: bool) { + self.imp().checkmark_icon.set_opacity(if selected {1.0} else {0.0}); + } + + pub fn set_handlerid(&self, id: Option) { + self.imp().signalid.replace(id); + } +}