view: ports: Improve linking between ports.

Links can now created by dragging in both directions, so now from an input port to an output port, too.
The drag-n-drop handlers now use dedicated types for each direction, so that no mismatched things can be dropped on each other.
This commit is contained in:
Tom A. Wagner
2021-06-28 13:54:53 +02:00
parent 58794fe123
commit 9445e173f9

View File

@@ -1,6 +1,6 @@
use gtk::{ use gtk::{
gdk, gdk,
glib::{self, subclass::Signal}, glib::{self, clone, subclass::Signal},
prelude::*, prelude::*,
subclass::prelude::*, subclass::prelude::*,
}; };
@@ -9,6 +9,18 @@ use pipewire::spa::Direction;
use crate::MediaType; use crate::MediaType;
/// A helper struct for linking a output port to an input port.
/// It carries the output ports id.
#[derive(Clone, Debug, glib::GBoxed)]
#[gboxed(type_name = "HelvumForwardLink")]
struct ForwardLink(u32);
/// A helper struct for linking an input to an output port.
/// It carries the input ports id.
#[derive(Clone, Debug, glib::GBoxed)]
#[gboxed(type_name = "HelvumReversedLink")]
struct ReversedLink(u32);
mod imp { mod imp {
use once_cell::{sync::Lazy, unsync::OnceCell}; use once_cell::{sync::Lazy, unsync::OnceCell};
use pipewire::spa::Direction; use pipewire::spa::Direction;
@@ -67,46 +79,74 @@ impl Port {
res.set_child(Some(&gtk::Label::new(Some(name)))); res.set_child(Some(&gtk::Label::new(Some(name))));
// Add either a drag source or drop target controller depending on direction, // Add a drag source and drop target controller with the type depending on direction,
// they will be responsible for link creation by dragging an output port onto an input port. // they will be responsible for link creation by dragging an output port onto an input port or the other way around.
//
// FIXME: The type used for dragging is simply a u32. // FIXME: We should protect against different media types, e.g. it should not be possible to drop a video port on an audio port.
// This means that anything that provides a u32 could be dragged onto a input port,
// leading to that port trying to create a link to an invalid output port.
// We should use a newtype instead of a plain u32.
// Additionally, this does not protect against e.g. dropping an outgoing audio port on an ingoing video port.
match direction { match direction {
Direction::Input => { Direction::Input => {
let drop_target = gtk::DropTarget::new(u32::static_type(), gdk::DragAction::COPY); // The port will simply provide its pipewire id to the drag target.
let this = res.clone(); let drag_src = gtk::DragSourceBuilder::new()
drop_target.connect_drop(move |drop_target, val, _, _| { .content(&gdk::ContentProvider::for_value(
if let Ok(source_id) = val.get::<u32>() { &(ReversedLink(id).to_value()),
// Get the callback registered in the widget and call it ))
drop_target .build();
.widget() res.add_controller(&drag_src);
.expect("Drop target has no widget")
.emit_by_name("port-toggled", &[&source_id, &this.id()])
.expect("Failed to send signal");
} else {
warn!("Invalid type dropped on ingoing port");
}
true let drop_target =
}); gtk::DropTarget::new(ForwardLink::static_type(), gdk::DragAction::COPY);
drop_target.connect_drop(
clone!(@weak res as this => @default-panic, move |drop_target, val, _, _| {
if let Ok(ForwardLink(source_id)) = val.get::<ForwardLink>() {
// Get the callback registered in the widget and call it
drop_target
.widget()
.expect("Drop target has no widget")
.emit_by_name("port-toggled", &[&source_id, &this.id()])
.expect("Failed to send signal");
} else {
warn!("Invalid type dropped on ingoing port");
}
true
}),
);
res.add_controller(&drop_target); res.add_controller(&drop_target);
} }
Direction::Output => { Direction::Output => {
// The port will simply provide its pipewire id to the drag target. // The port will simply provide its pipewire id to the drag target.
let drag_src = gtk::DragSourceBuilder::new() let drag_src = gtk::DragSourceBuilder::new()
.content(&gdk::ContentProvider::for_value(&(id.to_value()))) .content(&gdk::ContentProvider::for_value(
&(ForwardLink(id).to_value()),
))
.build(); .build();
res.add_controller(&drag_src); res.add_controller(&drag_src);
// Display a grab cursor when the mouse is over the port so the user knows it can be dragged to another port. let drop_target =
res.set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref()); gtk::DropTarget::new(ReversedLink::static_type(), gdk::DragAction::COPY);
drop_target.connect_drop(
clone!(@weak res as this => @default-panic, move |drop_target, val, _, _| {
if let Ok(ReversedLink(target_id)) = val.get::<ReversedLink>() {
// Get the callback registered in the widget and call it
drop_target
.widget()
.expect("Drop target has no widget")
.emit_by_name("port-toggled", &[&this.id(), &target_id])
.expect("Failed to send signal");
} else {
warn!("Invalid type dropped on outgoing port");
}
true
}),
);
res.add_controller(&drop_target);
} }
} }
// 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. // Color the port according to its media type.
match media_type { match media_type {
Some(MediaType::Video) => res.add_css_class("video"), Some(MediaType::Video) => res.add_css_class("video"),