Use typesafe GEnums

This commit is contained in:
Naveen Prashanth 2026-05-06 20:08:13 +05:30
parent 8a75db1987
commit 9d290793e7
6 changed files with 181 additions and 80 deletions

View file

@ -239,7 +239,7 @@ mod imp {
return;
};
port.set_media_type(media_type.as_raw())
port.set_media_type(media_type)
}
/// Remove the port with the id `id` from the node with the id `node_id`

View file

@ -114,8 +114,10 @@ pub enum PipewireMessage {
Disconnected,
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, glib::Enum)]
#[enum_type(name = "HelvumNodeType")]
pub enum NodeType {
#[default]
Input,
Output,
}

View file

@ -39,9 +39,9 @@ mod imp {
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet};
use adw::gtk::gdk::{self};
use adw::gtk::gdk;
use libspa::param::format::MediaType;
use libspa::utils::Direction;
use crate::ui::graph::PortDirection;
use log::warn;
use std::sync::LazyLock;
@ -617,13 +617,12 @@ mod imp {
});
let other_anchor = picked_port_anchor.unwrap_or(drag_cursor);
let (output_anchor, input_anchor) = match Direction::from_raw(port.direction()) {
Direction::Output => (&port_anchor, &other_anchor),
Direction::Input => (&other_anchor, &port_anchor),
_ => unreachable!(),
let (output_anchor, input_anchor) = match port.port_direction() {
PortDirection::Output => (&port_anchor, &other_anchor),
PortDirection::Input => (&other_anchor, &port_anchor),
};
let mut media_type = MediaType::from_raw(port.media_type());
let mut media_type = MediaType::from(port.media_type());
if media_type == MediaType::Unknown {
media_type = MediaType::Unknown;
}
@ -642,14 +641,14 @@ mod imp {
};
for link in self.links.borrow().iter() {
let mut media_type = link.media_type();
let mut media_type = MediaType::from(link.media_type());
// If link media type is unknown, try to fall back to port media types.
if media_type == MediaType::Unknown {
if let Some(output_port) = link.output_port() {
media_type = MediaType::from_raw(output_port.media_type());
media_type = MediaType::from(output_port.media_type());
} else if let Some(input_port) = link.input_port() {
media_type = MediaType::from_raw(input_port.media_type());
media_type = MediaType::from(input_port.media_type());
}
}

View file

@ -17,7 +17,7 @@
use adw::{glib, prelude::*, subclass::prelude::*};
use libspa::param::format::MediaType;
use super::Port;
use super::{Port, PortMediaType};
mod imp {
use super::*;
@ -30,7 +30,7 @@ mod imp {
pub output_port: glib::WeakRef<Port>,
pub input_port: glib::WeakRef<Port>,
pub active: Cell<bool>,
pub media_type: Cell<MediaType>,
pub media_type: Cell<PortMediaType>,
}
impl Default for Link {
@ -39,7 +39,7 @@ mod imp {
output_port: glib::WeakRef::default(),
input_port: glib::WeakRef::default(),
active: Cell::default(),
media_type: Cell::new(MediaType::Unknown),
media_type: Cell::new(PortMediaType::Unknown),
}
}
}
@ -65,8 +65,7 @@ mod imp {
.default_value(false)
.flags(glib::ParamFlags::READWRITE)
.build(),
glib::ParamSpecUInt::builder("media-type")
.default_value(MediaType::Unknown.as_raw())
glib::ParamSpecEnum::builder::<PortMediaType>("media-type")
.flags(glib::ParamFlags::READWRITE)
.build(),
]
@ -80,7 +79,7 @@ mod imp {
"output-port" => self.output_port.upgrade().to_value(),
"input-port" => self.input_port.upgrade().to_value(),
"active" => self.active.get().to_value(),
"media-type" => self.media_type.get().as_raw().to_value(),
"media-type" => self.media_type.get().to_value(),
_ => unimplemented!(),
}
}
@ -90,9 +89,7 @@ mod imp {
"output-port" => self.output_port.set(value.get().unwrap()),
"input-port" => self.input_port.set(value.get().unwrap()),
"active" => self.active.set(value.get().unwrap()),
"media-type" => self
.media_type
.set(MediaType::from_raw(value.get().unwrap())),
"media-type" => self.media_type.set(value.get().expect("Value should be a PortMediaType")),
_ => unimplemented!(),
}
}
@ -132,12 +129,12 @@ impl Link {
self.set_property("active", active);
}
pub fn media_type(&self) -> MediaType {
MediaType::from_raw(self.property("media-type"))
pub fn media_type(&self) -> PortMediaType {
self.property("media-type")
}
pub fn set_media_type(&self, media_type: MediaType) {
self.set_property("media-type", media_type.as_raw())
self.set_property("media-type", PortMediaType::from(media_type))
}
}

View file

@ -15,7 +15,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use adw::{glib, gtk, prelude::*, subclass::prelude::*};
use libspa::utils::Direction;
use crate::ui::graph::PortDirection;
use crate::NodeId;
use super::Port;
@ -120,14 +120,13 @@ mod imp {
ports
.iter()
.for_each(|port| match Direction::from_raw(port.direction()) {
Direction::Output => {
.for_each(|port| match port.port_direction() {
PortDirection::Output => {
ports_out.push(port);
}
Direction::Input => {
PortDirection::Input => {
ports_in.push(port);
}
_ => unreachable!(),
});
ports_out.sort_unstable_by_key(|port| port.name());

View file

@ -21,8 +21,9 @@ use adw::{
prelude::*,
subclass::prelude::*,
};
pub use imp::{PortDirection, PortMediaType};
use crate::PortId;
use libspa::utils::Direction;
use libspa::{param::format::MediaType, utils::Direction};
use super::PortHandle;
@ -31,37 +32,78 @@ mod imp {
use std::cell::Cell;
use libspa::{param::format::MediaType, utils::Direction};
use libspa::param::format::MediaType;
use std::sync::LazyLock;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, glib::Enum)]
#[enum_type(name = "HelvumPortDirection")]
pub enum PortDirection {
Input,
#[default]
Output,
}
impl From<Direction> for PortDirection {
fn from(direction: Direction) -> Self {
match direction {
Direction::Input => PortDirection::Input,
Direction::Output => PortDirection::Output,
_ => PortDirection::Input,
}
}
}
impl From<PortDirection> for Direction {
fn from(direction: PortDirection) -> Self {
match direction {
PortDirection::Input => Direction::Input,
PortDirection::Output => Direction::Output,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, glib::Enum)]
#[enum_type(name = "HelvumPortMediaType")]
pub enum PortMediaType {
#[default]
Unknown,
Audio,
Video,
Application,
Stream,
}
impl From<MediaType> for PortMediaType {
fn from(m: MediaType) -> Self {
match m {
MediaType::Audio => PortMediaType::Audio,
MediaType::Video => PortMediaType::Video,
MediaType::Application => PortMediaType::Application,
MediaType::Stream => PortMediaType::Stream,
_ => PortMediaType::Unknown,
}
}
}
impl From<PortMediaType> for MediaType {
fn from(m: PortMediaType) -> Self {
match m {
PortMediaType::Audio => MediaType::Audio,
PortMediaType::Video => MediaType::Video,
PortMediaType::Application => MediaType::Application,
PortMediaType::Stream => MediaType::Stream,
_ => MediaType::Unknown,
}
}
}
/// Graphical representation of a pipewire port.
#[derive(gtk::CompositeTemplate, glib::Properties)]
#[properties(wrapper_type = super::Port)]
#[derive(gtk::CompositeTemplate)]
#[template(file = "port.ui")]
pub struct Port {
#[property(get, set, construct_only)]
pub(super) pipewire_id: Cell<u32>,
#[property(
type = u32,
get = |_| self.media_type.get().as_raw(),
set = Self::set_media_type
)]
pub(super) media_type: Cell<MediaType>,
#[property(
type = u32,
get = |_| self.direction.get().as_raw(),
set = Self::set_direction,
construct_only
)]
pub(super) direction: Cell<Direction>,
#[property(
name = "name", type = String,
get = |this: &Self| this.label.text().to_string(),
set = |this: &Self, val| {
this.label.set_text(val);
this.label.set_tooltip_text(Some(val));
}
)]
pub(super) media_type: Cell<PortMediaType>,
pub(super) direction: Cell<PortDirection>,
#[template_child]
pub(super) label: TemplateChild<gtk::Label>,
#[template_child]
@ -72,8 +114,8 @@ mod imp {
fn default() -> Self {
Self {
pipewire_id: Cell::new(0),
media_type: Cell::new(MediaType::Unknown),
direction: Cell::new(Direction::Output),
media_type: Cell::new(PortMediaType::Unknown),
direction: Cell::new(PortDirection::Output),
label: TemplateChild::default(),
handle: TemplateChild::default(),
}
@ -97,13 +139,15 @@ mod imp {
}
}
#[glib::derived_properties]
impl ObjectImpl for Port {
fn constructed(&self) {
self.parent_constructed();
// Force left-to-right direction for the ports grid to avoid messed up UI when defaulting to right-to-left
self.obj().set_direction(gtk::TextDirection::Ltr);
gtk::prelude::WidgetExt::set_direction(&*self.obj(), gtk::TextDirection::Ltr);
// Initial UI update
self.update_ui_for_direction();
// Display a grab cursor when the mouse is over the port so the user knows it can be dragged to another port.
self.obj()
@ -122,6 +166,57 @@ mod imp {
SIGNALS.as_ref()
}
fn properties() -> &'static [glib::ParamSpec] {
static PROPERTIES: LazyLock<Vec<glib::ParamSpec>> = LazyLock::new(|| {
vec![
glib::ParamSpecEnum::builder::<PortDirection>("port-direction")
.construct_only()
.build(),
glib::ParamSpecUInt::builder("pipewire-id")
.construct_only()
.build(),
glib::ParamSpecEnum::builder::<PortMediaType>("media-type")
.build(),
glib::ParamSpecString::builder("name")
.build(),
]
});
PROPERTIES.as_ref()
}
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
match pspec.name() {
"port-direction" => {
let val = value.get().expect("Value should be a PortDirection");
self.direction.set(val);
self.update_ui_for_direction();
}
"pipewire-id" => {
self.pipewire_id.set(value.get().expect("Value should be a u32"));
}
"media-type" => {
let val = value.get().expect("Value should be a PortMediaType");
self.set_media_type(val);
}
"name" => {
let val: String = value.get().expect("Value should be a String");
self.label.set_text(&val);
self.label.set_tooltip_text(Some(&val));
}
_ => unimplemented!(),
}
}
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
match pspec.name() {
"port-direction" => self.direction.get().to_value(),
"pipewire-id" => self.pipewire_id.get().to_value(),
"media-type" => self.media_type.get().to_value(),
"name" => self.label.text().to_string().to_value(),
_ => unimplemented!(),
}
}
}
impl WidgetImpl for Port {
@ -164,8 +259,8 @@ mod imp {
let (_, nat_handle_width, _, _) =
self.handle.measure(gtk::Orientation::Horizontal, width);
match Direction::from_raw(self.obj().direction()) {
Direction::Input => {
match self.obj().port_direction() {
PortDirection::Input => {
let alloc = gtk::Allocation::new(
-nat_handle_width / 2,
(height - nat_handle_height) / 2,
@ -182,7 +277,7 @@ mod imp {
);
self.label.size_allocate(&alloc, -1);
}
Direction::Output => {
PortDirection::Output => {
let alloc = gtk::Allocation::new(
width - (nat_handle_width / 2),
(height - nat_handle_height) / 2,
@ -194,7 +289,6 @@ mod imp {
let alloc = gtk::Allocation::new(0, 0, width - (nat_handle_width / 2), height);
self.label.size_allocate(&alloc, -1);
}
_ => unreachable!(),
}
}
}
@ -277,10 +371,9 @@ mod imp {
return false;
}
let (output_port, input_port) = match Direction::from_raw(port.direction()) {
Direction::Output => (&port, &other_port),
Direction::Input => (&other_port, &port),
_ => unreachable!(),
let (output_port, input_port) = match port.port_direction() {
PortDirection::Output => (&port, &other_port),
PortDirection::Input => (&other_port, &port),
};
port.emit_by_name::<()>(
@ -293,9 +386,7 @@ mod imp {
obj.add_controller(drop_target);
}
fn set_media_type(&self, media_type: u32) {
let media_type = MediaType::from_raw(media_type);
fn set_media_type(&self, media_type: PortMediaType) {
self.media_type.set(media_type);
for css_class in ["video", "audio", "midi"] {
@ -303,7 +394,7 @@ mod imp {
}
// Color the port according to its media type.
match media_type {
match MediaType::from(media_type) {
MediaType::Video => self.handle.add_css_class("video"),
MediaType::Audio => self.handle.add_css_class("audio"),
MediaType::Application | MediaType::Stream => self.handle.add_css_class("midi"),
@ -311,21 +402,18 @@ mod imp {
}
}
fn set_direction(&self, direction: u32) {
let direction = Direction::from_raw(direction);
self.direction.set(direction);
fn update_ui_for_direction(&self) {
let direction = self.direction.get();
match direction {
Direction::Input => {
PortDirection::Input => {
self.obj().set_halign(gtk::Align::Start);
self.label.set_halign(gtk::Align::Start);
}
Direction::Output => {
PortDirection::Output => {
self.obj().set_halign(gtk::Align::End);
self.label.set_halign(gtk::Align::End);
}
_ => unreachable!(),
}
}
}
@ -340,11 +428,27 @@ impl Port {
pub fn new(id: PortId, name: &str, direction: Direction) -> Self {
glib::Object::builder()
.property("pipewire-id", id.0)
.property("direction", direction.as_raw())
.property("port-direction", PortDirection::from(direction))
.property("name", name)
.build()
}
pub fn port_direction(&self) -> PortDirection {
self.property("port-direction")
}
pub fn name(&self) -> String {
self.property("name")
}
pub fn set_media_type(&self, media_type: MediaType) {
self.set_property("media-type", PortMediaType::from(media_type));
}
pub fn media_type(&self) -> PortMediaType {
self.property("media-type")
}
pub fn pw_id(&self) -> PortId {
PortId(self.property("pipewire-id"))
}
@ -360,6 +464,6 @@ impl Port {
}
pub fn is_linkable_to(&self, other_port: &Self) -> bool {
self.direction() != other_port.direction()
self.port_direction() != other_port.port_direction()
}
}