mirror of
https://gitlab.freedesktop.org/pipewire/helvum
synced 2026-03-15 19:46:10 +08:00
When dragging a node, don't snap its top-left corner to the cursor
Revamp the node dragging implementation, moving it into the GraphView widget. When a drag is initiated, the node widget's current position is stored. Whenever the drag gesture is updated, the node widget's position is set by adding the relative drag vector to the position at the start of the drag. A drag gesture on the node widget rather than the GraphView widget was considered, but this seems to lead to a weird flickering effect when the node is moved while the drag gesture on the node is active. To avoid interfering with the drag handlers on the ports, check if the GraphView drag gesture targets a port, in which case the handler does nothing.
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
use super::Node;
|
||||
use super::{Node, Port};
|
||||
|
||||
use gtk::{gdk, glib, graphene, gsk, prelude::*, subclass::prelude::*};
|
||||
use gtk::{
|
||||
glib::{self, clone},
|
||||
graphene, gsk,
|
||||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
@@ -15,7 +20,6 @@ mod imp {
|
||||
pub struct GraphView {
|
||||
pub(super) nodes: RefCell<HashMap<u32, Node>>,
|
||||
pub(super) links: RefCell<HashMap<u32, crate::PipewireLink>>,
|
||||
pub(super) dragged: Rc<RefCell<Option<gtk::Widget>>>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
@@ -34,28 +38,47 @@ mod imp {
|
||||
fn constructed(&self, obj: &Self::Type) {
|
||||
self.parent_constructed(obj);
|
||||
|
||||
// Move the Node that is currently being dragged to the cursor position as long as Mouse Button 1 is held.
|
||||
let motion_controller = gtk::EventControllerMotion::new();
|
||||
motion_controller.connect_motion(|controller, x, y| {
|
||||
let instance = controller
|
||||
.widget()
|
||||
.unwrap()
|
||||
.dynamic_cast::<Self::Type>()
|
||||
.unwrap();
|
||||
let this = imp::GraphView::from_instance(&instance);
|
||||
let drag_state = Rc::new(RefCell::new(None));
|
||||
let drag_controller = gtk::GestureDrag::new();
|
||||
|
||||
if let Some(ref widget) = *this.dragged.borrow() {
|
||||
if controller
|
||||
.current_event()
|
||||
.unwrap()
|
||||
.modifier_state()
|
||||
.contains(gdk::ModifierType::BUTTON1_MASK)
|
||||
{
|
||||
instance.move_node(&widget, x as f32, y as f32);
|
||||
drag_controller.connect_drag_begin(
|
||||
clone!(@strong drag_state => move |drag_controller, x, y| {
|
||||
let mut drag_state = drag_state.borrow_mut();
|
||||
let widget = drag_controller
|
||||
.widget()
|
||||
.expect("drag-begin event has no widget")
|
||||
.dynamic_cast::<Self::Type>()
|
||||
.expect("drag-begin event is not on the GraphView");
|
||||
// pick() should at least return the widget itself.
|
||||
let target = widget.pick(x, y, gtk::PickFlags::DEFAULT).expect("drag-begin pick() did not return a widget");
|
||||
*drag_state = if target.ancestor(Port::static_type()).is_some() {
|
||||
// The user targeted a port, so the dragging should be handled by the Port
|
||||
// component instead of here.
|
||||
None
|
||||
} else if let Some(target) = target.ancestor(Node::static_type()) {
|
||||
// The user targeted a Node without targeting a specific Port.
|
||||
// Drag the Node around the screen.
|
||||
let (x, y) = widget.get_node_position(&target);
|
||||
Some((target, x, y))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}));
|
||||
drag_controller.connect_drag_update(
|
||||
clone!(@strong drag_state => move |drag_controller, x, y| {
|
||||
let widget = drag_controller
|
||||
.widget()
|
||||
.expect("drag-update event has no widget")
|
||||
.dynamic_cast::<Self::Type>()
|
||||
.expect("drag-update event is not on the GraphView");
|
||||
let drag_state = drag_state.borrow();
|
||||
if let Some((ref node, x1, y1)) = *drag_state {
|
||||
widget.move_node(node, x1 + x as f32, y1 + y as f32);
|
||||
}
|
||||
};
|
||||
});
|
||||
obj.add_controller(&motion_controller);
|
||||
}
|
||||
),
|
||||
);
|
||||
obj.add_controller(&drag_controller);
|
||||
}
|
||||
|
||||
fn dispose(&self, _obj: &Self::Type) {
|
||||
@@ -249,8 +272,20 @@ impl GraphView {
|
||||
self.queue_draw();
|
||||
}
|
||||
|
||||
pub(super) fn set_dragged(&self, widget: Option<gtk::Widget>) {
|
||||
*imp::GraphView::from_instance(self).dragged.borrow_mut() = widget;
|
||||
pub(super) fn get_node_position(&self, node: >k::Widget) -> (f32, f32) {
|
||||
let layout_manager = self
|
||||
.layout_manager()
|
||||
.expect("Failed to get layout manager")
|
||||
.dynamic_cast::<gtk::FixedLayout>()
|
||||
.expect("Failed to cast to FixedLayout");
|
||||
|
||||
let node = layout_manager
|
||||
.layout_child(node)
|
||||
.expect("Could not get layout child")
|
||||
.dynamic_cast::<gtk::FixedLayoutChild>()
|
||||
.expect("Could not cast to FixedLayoutChild");
|
||||
let transform = node.transform().unwrap_or_default();
|
||||
transform.to_translate()
|
||||
}
|
||||
|
||||
pub(super) fn move_node(&self, node: >k::Widget, x: f32, y: f32) {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use super::graph_view::GraphView;
|
||||
|
||||
use gtk::{glib, prelude::*, subclass::prelude::*};
|
||||
use pipewire::spa::Direction;
|
||||
|
||||
@@ -34,35 +32,6 @@ mod imp {
|
||||
|
||||
grid.attach(&label, 0, 0, 2, 1);
|
||||
|
||||
let motion_controller = gtk::EventControllerMotion::new();
|
||||
motion_controller.connect_enter(|controller, _, _| {
|
||||
// Tell the graphview that the Node is the target of a drag when the mouse enters its label
|
||||
let widget = controller
|
||||
.widget()
|
||||
.expect("Controller with enter event has no widget")
|
||||
.ancestor(super::Node::static_type())
|
||||
.expect("Node label does not have a node ancestor widget");
|
||||
widget
|
||||
.ancestor(GraphView::static_type())
|
||||
.expect("Node with enter event is not on graph")
|
||||
.dynamic_cast::<GraphView>()
|
||||
.unwrap()
|
||||
.set_dragged(Some(widget));
|
||||
});
|
||||
motion_controller.connect_leave(|controller| {
|
||||
// Tell the graphview that the Node is no longer the target of a drag when the mouse leaves.
|
||||
// FIXME: Check that we are the current target before setting none.
|
||||
controller
|
||||
.widget()
|
||||
.expect("Controller with leave event has no widget")
|
||||
.ancestor(GraphView::static_type())
|
||||
.expect("Node with leave event is not on graph")
|
||||
.dynamic_cast::<GraphView>()
|
||||
.unwrap()
|
||||
.set_dragged(None);
|
||||
});
|
||||
label.add_controller(&motion_controller);
|
||||
|
||||
// Display a grab cursor when the mouse is over the label so the user knows the node can be dragged.
|
||||
label.set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user