Skip to content

Commit

Permalink
Use PwProfileRow widget for profile dropdown and add checkmark for se…
Browse files Browse the repository at this point in the history
…lected item
  • Loading branch information
saivert committed Dec 25, 2023
1 parent 090883d commit 0fe3326
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 38 deletions.
1 change: 1 addition & 0 deletions data/resources/resources.gresource.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<file preprocess="xml-stripblanks" alias="gtk/outputbox.ui">ui/outputbox.ui</file>
<file preprocess="xml-stripblanks" alias="gtk/profile-dropdown.ui">ui/profile-dropdown.ui</file>
<file preprocess="xml-stripblanks" alias="gtk/devicebox.ui">ui/devicebox.ui</file>
<file preprocess="xml-stripblanks" alias="gtk/profilerow.ui">ui/profilerow.ui</file>
</gresource>
<gresource prefix="/com/saivert/pwvucontrol/icons/scalable/actions">
<file preprocess="xml-stripblanks" alias="com.saivert.pwvucontrol.svg">../icons/com.saivert.pwvucontrol.svg</file>
Expand Down
23 changes: 23 additions & 0 deletions data/resources/ui/profilerow.ui
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- SPDX-License-Identifier: GPL-3.0-or-later -->
<interface>
<requires lib="gtk" version="4.0" />
<requires lib="Adw" version="1.0" />
<template class="PwProfileRow" parent="GtkBox">
<child>
<object class="GtkLabel" id="label">
<property name="xalign">0.0</property>
</object>
</child>
<child>
<object class="GtkImage" id="unavailable_icon">
<property name="icon-name">action-unavailable-symbolic</property>
</object>
</child>
<child>
<object class="GtkImage" id="checkmark_icon">
<property name="icon-name">object-select-symbolic</property>
</object>
</child>
</template>
</interface>
2 changes: 2 additions & 0 deletions src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod sinkbox;
mod outputbox;
mod profile_dropdown;
mod devicebox;
mod profilerow;

pub use window::PwvucontrolWindow;
pub use window::PwvucontrolWindowView;
Expand All @@ -19,3 +20,4 @@ pub use sinkbox::PwSinkBox;
pub use channelbox::PwChannelBox;
pub use levelprovider::LevelbarProvider;
pub use outputbox::PwOutputBox;
pub use profilerow::PwProfileRow;
72 changes: 34 additions & 38 deletions src/ui/profile_dropdown.rs
Original file line number Diff line number Diff line change
@@ -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::*;
Expand Down Expand Up @@ -148,50 +149,45 @@ mod imp {

fn setup_handler(item: &glib::Object, list: bool) {
let item: &gtk::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::<PwProfileObject>("description")
.bind(&label, "label", gtk::Widget::NONE);

// let opacity_closure = closure_local!(|_: Option<glib::Object>, availability: u32| {
// match availability {
// 2 => 1.0f32,
// _ => 0.5f32
// }
// });

// item.property_expression("item")
// .chain_property::<PwProfileObject>("availability")
// .chain_closure::<f32>(opacity_closure)
// .bind(&label, "opacity", glib::Object::NONE);

let icon_closure = closure_local!(|_: Option<glib::Object>, availability: ProfileAvailability| {
availability == ProfileAvailability::No
});

item.property_expression("item")
.chain_property::<PwProfileObject>("availability")
.chain_closure::<bool>(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: &gtk::DropDown) {
let item: &gtk::ListItem = item.downcast_ref().expect("ListItem");
let profilerow = item
.child()
.and_downcast::<PwProfileRow>()
.expect("PwProfileRow child");

let signal = dropdown.connect_selected_item_notify(clone!(@weak item => move |dropdown| {
let profilerow = item
.child()
.and_downcast::<PwProfileRow>()
.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: &gtk::ListItem = item.downcast_ref().expect("ListItem");
let profilerow = item
.child()
.and_downcast::<PwProfileRow>()
.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));
Expand Down
86 changes: 86 additions & 0 deletions src/ui/profilerow.rs
Original file line number Diff line number Diff line change
@@ -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<gtk::Label>,
#[template_child]
pub unavailable_icon: TemplateChild<gtk::Image>,
#[template_child]
pub checkmark_icon: TemplateChild<gtk::Image>,

pub(super) signalid: RefCell<Option<glib::SignalHandlerId>>,
}

#[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<Self>) {
obj.init_template();
}
}

impl ObjectImpl for PwProfileRow {}
impl WidgetImpl for PwProfileRow {}
impl BoxImpl for PwProfileRow {}
impl PwProfileRow {}
}

glib::wrapper! {
pub struct PwProfileRow(ObjectSubclass<imp::PwProfileRow>)
@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: &gtk::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::<PwProfileObject>("description")
.bind(&label, "label", gtk::Widget::NONE);

let icon_closure = closure_local!(|_: Option<glib::Object>, availability: ProfileAvailability| {
availability == ProfileAvailability::No
});

item.property_expression("item")
.chain_property::<PwProfileObject>("availability")
.chain_closure::<bool>(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<glib::SignalHandlerId>) {
self.imp().signalid.replace(id);
}
}

0 comments on commit 0fe3326

Please sign in to comment.