Color links and ports according to their formats

Format params for links and ports are now being watched for in the pipewire connection code.

The parsed media type is then set on the port widget / link object
and they are colored accordingly.

For ports, which were already colored before, this new method of determining the media type
should be more reliable and accurate as this uses the real Format/EnumFormat
params instead of parsing optional properties.
This commit is contained in:
Tom A. Wagner
2023-07-30 12:09:08 +02:00
parent ba73d8cdcc
commit 7f754b207c
8 changed files with 362 additions and 148 deletions

View File

@@ -43,8 +43,27 @@ mod imp {
};
use log::warn;
use once_cell::sync::Lazy;
use pipewire::spa::format::MediaType;
use pipewire::spa::Direction;
pub struct Colors {
audio: gdk::RGBA,
video: gdk::RGBA,
midi: gdk::RGBA,
unknown: gdk::RGBA,
}
impl Colors {
pub fn color_for_media_type(&self, media_type: MediaType) -> &gdk::RGBA {
match media_type {
MediaType::Audio => &self.audio,
MediaType::Video => &self.video,
MediaType::Stream | MediaType::Application => &self.midi,
_ => &self.unknown,
}
}
}
pub struct DragState {
node: glib::WeakRef<Node>,
/// This stores the offset of the pointer to the origin of the node,
@@ -510,6 +529,7 @@ mod imp {
output_anchor: &Point,
input_anchor: &Point,
active: bool,
color: &gdk::RGBA,
) {
let output_x: f64 = output_anchor.x().into();
let output_y: f64 = output_anchor.y().into();
@@ -523,6 +543,13 @@ mod imp {
link_cr.set_dash(&[10.0, 5.0], 0.0);
}
link_cr.set_source_rgba(
color.red().into(),
color.green().into(),
color.blue().into(),
color.alpha().into(),
);
// If the output port is farther right than the input port and they have
// a similar y coordinate, apply a y offset to the control points
// so that the curve sticks out a bit.
@@ -551,7 +578,7 @@ mod imp {
};
}
fn draw_dragged_link(&self, port: &Port, link_cr: &cairo::Context) {
fn draw_dragged_link(&self, port: &Port, link_cr: &cairo::Context, colors: &Colors) {
let Some(port_anchor) = port.compute_point(&*self.obj(), &port.link_anchor()) else {
return;
};
@@ -579,7 +606,9 @@ mod imp {
_ => unreachable!(),
};
self.draw_link(link_cr, output_anchor, input_anchor, false);
let color = &colors.color_for_media_type(MediaType::from_raw(port.media_type()));
self.draw_link(link_cr, output_anchor, input_anchor, false, color);
}
fn snapshot_links(&self, widget: &super::GraphView, snapshot: &gtk::Snapshot) {
@@ -594,30 +623,45 @@ mod imp {
link_cr.set_line_width(2.0 * self.zoom_factor.get());
let rgba = widget
.style_context()
.lookup_color("graphview-link")
.unwrap_or(gtk::gdk::RGBA::BLACK);
link_cr.set_source_rgba(
rgba.red().into(),
rgba.green().into(),
rgba.blue().into(),
rgba.alpha().into(),
);
let colors = Colors {
audio: widget
.style_context()
.lookup_color("media-type-audio")
.expect("color not found"),
video: widget
.style_context()
.lookup_color("media-type-video")
.expect("color not found"),
midi: widget
.style_context()
.lookup_color("media-type-midi")
.expect("color not found"),
unknown: widget
.style_context()
.lookup_color("media-type-unknown")
.expect("color not found"),
};
for link in self.links.borrow().iter() {
let color = &colors.color_for_media_type(link.media_type());
// TODO: Do not draw links when they are outside the view
let Some((output_anchor, input_anchor)) = self.get_link_coordinates(link) else {
warn!("Could not get allocation of ports of link: {:?}", link);
continue;
};
self.draw_link(&link_cr, &output_anchor, &input_anchor, link.active());
self.draw_link(
&link_cr,
&output_anchor,
&input_anchor,
link.active(),
color,
);
}
if let Some(port) = self.dragged_port.upgrade() {
self.draw_dragged_link(&port, &link_cr);
self.draw_dragged_link(&port, &link_cr, &colors);
}
}
@@ -793,6 +837,12 @@ impl GraphView {
graph.queue_draw();
}),
);
link.connect_notify_local(
Some("media-type"),
glib::clone!(@weak self as graph => move |_, _| {
graph.queue_draw();
}),
);
self.imp().links.borrow_mut().insert(link);
self.queue_draw();
}

View File

@@ -15,6 +15,7 @@
// SPDX-License-Identifier: GPL-3.0-only
use gtk::{glib, prelude::*, subclass::prelude::*};
use pipewire::spa::format::MediaType;
use super::Port;
@@ -25,11 +26,22 @@ mod imp {
use once_cell::sync::Lazy;
#[derive(Default)]
pub struct Link {
pub output_port: glib::WeakRef<Port>,
pub input_port: glib::WeakRef<Port>,
pub active: Cell<bool>,
pub media_type: Cell<MediaType>,
}
impl Default for Link {
fn default() -> Self {
Self {
output_port: glib::WeakRef::default(),
input_port: glib::WeakRef::default(),
active: Cell::default(),
media_type: Cell::new(MediaType::Unknown),
}
}
}
#[glib::object_subclass]
@@ -53,6 +65,10 @@ mod imp {
.default_value(false)
.flags(glib::ParamFlags::READWRITE)
.build(),
glib::ParamSpecUInt::builder("media-type")
.default_value(MediaType::Unknown.as_raw())
.flags(glib::ParamFlags::READWRITE)
.build(),
]
});
@@ -64,6 +80,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(),
_ => unimplemented!(),
}
}
@@ -73,6 +90,9 @@ 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())),
_ => unimplemented!(),
}
}
@@ -111,6 +131,14 @@ impl Link {
pub fn set_active(&self, active: bool) {
self.set_property("active", active);
}
pub fn media_type(&self) -> MediaType {
MediaType::from_raw(self.property("media-type"))
}
pub fn set_media_type(&self, media_type: MediaType) {
self.set_property("media-type", media_type.as_raw())
}
}
impl Default for Link {

View File

@@ -23,20 +23,26 @@ use gtk::{
};
use pipewire::spa::Direction;
use crate::MediaType;
mod imp {
use super::*;
use std::cell::Cell;
use once_cell::{sync::Lazy, unsync::OnceCell};
use pipewire::spa::Direction;
use pipewire::spa::{format::MediaType, Direction};
/// Graphical representation of a pipewire port.
#[derive(Default, glib::Properties)]
#[derive(glib::Properties)]
#[properties(wrapper_type = super::Port)]
pub struct Port {
#[property(get, set, construct_only)]
pub(super) pipewire_id: OnceCell<u32>,
#[property(
type = u32,
get = |_| self.media_type.get().as_raw(),
set = Self::set_media_type
)]
pub(super) media_type: Cell<MediaType>,
#[property(
name = "name", type = String,
get = |this: &Self| this.label.text().to_string(),
@@ -49,6 +55,17 @@ mod imp {
pub(super) direction: OnceCell<Direction>,
}
impl Default for Port {
fn default() -> Self {
Self {
pipewire_id: OnceCell::default(),
media_type: Cell::new(MediaType::Unknown),
label: gtk::Label::default(),
direction: OnceCell::default(),
}
}
}
#[glib::object_subclass]
impl ObjectSubclass for Port {
const NAME: &'static str = "HelvumPort";
@@ -184,6 +201,26 @@ mod imp {
obj.add_controller(drop_target);
}
}
impl Port {
fn set_media_type(&self, media_type: u32) {
let media_type = MediaType::from_raw(media_type);
self.media_type.set(media_type);
for css_class in ["video", "audio", "midi"] {
self.obj().remove_css_class(css_class)
}
// Color the port according to its media type.
match media_type {
MediaType::Video => self.obj().add_css_class("video"),
MediaType::Audio => self.obj().add_css_class("audio"),
MediaType::Application | MediaType::Stream => self.obj().add_css_class("midi"),
_ => {}
}
}
}
}
glib::wrapper! {
@@ -192,7 +229,7 @@ glib::wrapper! {
}
impl Port {
pub fn new(id: u32, name: &str, direction: Direction, media_type: Option<MediaType>) -> Self {
pub fn new(id: u32, name: &str, direction: Direction) -> Self {
// Create the widget and initialize needed fields
let res: Self = glib::Object::builder()
.property("pipewire-id", id)
@@ -208,14 +245,6 @@ impl Port {
// Display a grab cursor when the mouse is over the port so the user knows it can be dragged to another port.
res.set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref());
// Color the port according to its media type.
match media_type {
Some(MediaType::Video) => res.add_css_class("video"),
Some(MediaType::Audio) => res.add_css_class("audio"),
Some(MediaType::Midi) => res.add_css_class("midi"),
None => {}
}
res
}