From f1b66d9c53bbed9a92c83593170f3559ec4896ae Mon Sep 17 00:00:00 2001 From: "Tom A. Wagner" Date: Thu, 7 Jan 2021 17:42:37 +0100 Subject: [PATCH] Turn Node into a gtk::Frame subclass. --- src/view/graph_view.rs | 20 +++-- src/view/node.rs | 180 +++++++++++++++++++++++++++-------------- 2 files changed, 132 insertions(+), 68 deletions(-) diff --git a/src/view/graph_view.rs b/src/view/graph_view.rs index b11336e..07f4e32 100644 --- a/src/view/graph_view.rs +++ b/src/view/graph_view.rs @@ -1,13 +1,13 @@ use super::Node; -use gtk::{glib, prelude::*, subclass::prelude::ObjectSubclass, WidgetExt}; +use gtk::{glib, prelude::*, subclass::prelude::*, WidgetExt}; use std::collections::HashMap; mod imp { use super::*; - use gtk::{gdk, graphene, gsk, subclass::prelude::*, WidgetExt}; + use gtk::{gdk, graphene, gsk, WidgetExt}; use std::{cell::RefCell, rc::Rc}; @@ -70,7 +70,7 @@ mod imp { self.nodes .borrow() .values() - .for_each(|node| node.widget.unparent()) + .for_each(|node| node.unparent()) } } @@ -132,7 +132,7 @@ mod imp { self.nodes .borrow() .values() - .for_each(|node| self.get_instance().snapshot_child(&node.widget, snapshot)); + .for_each(|node| self.get_instance().snapshot_child(node, snapshot)); } } @@ -143,7 +143,7 @@ mod imp { let x = (self.nodes.borrow().len() / 4) as f32 * 400.0; let y = self.nodes.borrow().len() as f32 % 4.0 * 100.0; - self.move_node(&node.widget.clone().upcast(), x, y); + self.move_node(&node.clone().upcast(), x, y); self.nodes.borrow_mut().insert(id, node); } @@ -209,7 +209,9 @@ mod imp { width: fw, height: fh, } = from_port.get_allocation(); - let from_node = from_port.get_ancestor(gtk::Grid::static_type()).unwrap(); + let from_node = from_port + .get_ancestor(Node::static_type()) + .expect("Port is not a child of a node"); let gtk::Allocation { x: fnx, y: fny, .. } = from_node.get_allocation(); fx += fnx + fw; fy += fny + (fh / 2); @@ -221,7 +223,9 @@ mod imp { height: th, .. } = to_port.get_allocation(); - let to_node = to_port.get_ancestor(gtk::Grid::static_type()).unwrap(); + let to_node = to_port + .get_ancestor(Node::static_type()) + .expect("Port is not a child of a node"); let gtk::Allocation { x: tnx, y: tny, .. } = to_node.get_allocation(); tx += tnx; ty += tny + (th / 2); @@ -242,7 +246,7 @@ impl GraphView { } pub fn add_node(&self, id: u32, node: Node) { - node.widget.set_parent(self); + node.set_parent(self); imp::GraphView::from_instance(self).add_node(id, node) } diff --git a/src/view/node.rs b/src/view/node.rs index a0f44a8..dbe978e 100644 --- a/src/view/node.rs +++ b/src/view/node.rs @@ -1,85 +1,145 @@ use super::graph_view::GraphView; -use gtk::prelude::*; +use gtk::{glib, prelude::*, subclass::prelude::*, WidgetExt}; use pipewire::port::Direction; -use std::collections::HashMap; +use std::{collections::HashMap, rc::Rc}; -pub struct Node { - pub(super) widget: gtk::Grid, - label: gtk::Label, - ports: HashMap, - num_ports_in: u32, - num_ports_out: u32, +mod imp { + use super::*; + + use std::cell::{Cell, RefCell}; + + pub struct Node { + pub(super) grid: gtk::Grid, + pub(super) label: gtk::Label, + pub(super) ports: RefCell>>, + pub(super) num_ports_in: Cell, + pub(super) num_ports_out: Cell, + } + + impl ObjectSubclass for Node { + const NAME: &'static str = "Node"; + type Type = super::Node; + type ParentType = gtk::Frame; + type Instance = glib::subclass::simple::InstanceStruct; + type Class = glib::subclass::simple::ClassStruct; + + glib::object_subclass!(); + + fn class_init(klass: &mut Self::Class) { + klass.set_layout_manager_type::(); + } + + fn new() -> Self { + let grid = gtk::Grid::new(); + let label = gtk::Label::new(None); + + 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 + .get_widget() + .expect("Controller with enter event has no widget") + .get_ancestor(super::Node::static_type()) + .expect("Node label does not have a node ancestor widget"); + widget + .get_ancestor(GraphView::static_type()) + .expect("Node with enter event is not on graph") + .dynamic_cast::() + .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 + .get_widget() + .expect("Controller with leave event has no widget") + .get_ancestor(GraphView::static_type()) + .expect("Node with leave event is not on graph") + .dynamic_cast::() + .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()); + + Self { + grid, + label, + ports: RefCell::new(HashMap::new()), + num_ports_in: Cell::new(0), + num_ports_out: Cell::new(0), + } + } + } + + impl ObjectImpl for Node { + fn constructed(&self, obj: &Self::Type) { + self.parent_constructed(obj); + self.grid.set_parent(obj); + } + + fn dispose(&self, _obj: &Self::Type) { + self.grid.unparent(); + } + } + + impl FrameImpl for Node {} + impl WidgetImpl for Node {} +} + +glib::wrapper! { + pub struct Node(ObjectSubclass) + @extends gtk::Widget; } impl Node { pub fn new(name: &str) -> Self { - let result = Self { - widget: gtk::Grid::new(), - label: gtk::Label::new(Some(name)), - ports: HashMap::new(), - num_ports_in: 0, - num_ports_out: 0, - }; + let res: Self = glib::Object::new(&[]).expect("Failed to create Node"); + let private = imp::Node::from_instance(&res); - let motion_controller = gtk::EventControllerMotion::new(); - // Tell the graphview that the Node is the target of a drag when the mouse enters its label - motion_controller.connect_enter(|controller, _, _| { - let widget = controller - .get_widget() - .expect("Controller with enter event has no widget") - .get_ancestor(gtk::Grid::static_type()) - .unwrap(); - widget - .get_ancestor(GraphView::static_type()) - .unwrap() - .dynamic_cast::() - .unwrap() - .set_dragged(Some(widget)); - }); - // Tell the graphview that the Node is no longer the target of a drag when the mouse leaves. - motion_controller.connect_leave(|controller| { - // FIXME: Check that we are the current target before setting none. - controller - .get_widget() - .expect("Controller with leave event has no widget") - .get_ancestor(GraphView::static_type()) - .unwrap() - .dynamic_cast::() - .unwrap() - .set_dragged(None); - }); - result.label.add_controller(&motion_controller); + private.label.set_text(name); - // Display a grab cursor when the mouse is over the label so the user knows the node can be dragged. - result - .label - .set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref()); - - result.widget.attach(&result.label, 0, 0, 2, 1); - - result + res } pub fn add_port(&mut self, id: u32, port: super::port::Port) { + let private = imp::Node::from_instance(self); + match port.direction { Direction::Input => { - self.widget - .attach(&port.widget, 0, (self.num_ports_in + 1) as i32, 1, 1); - self.num_ports_in += 1; + private + .grid + .attach(&port.widget, 0, private.num_ports_in.get() as i32 + 1, 1, 1); + private.num_ports_in.set(private.num_ports_in.get() + 1); } Direction::Output => { - self.widget - .attach(&port.widget, 1, (self.num_ports_out + 1) as i32, 1, 1); - self.num_ports_out += 1; + private.grid.attach( + &port.widget, + 1, + private.num_ports_out.get() as i32 + 1, + 1, + 1, + ); + private.num_ports_out.set(private.num_ports_out.get() + 1); } } - self.ports.insert(id, port); + private.ports.borrow_mut().insert(id, Rc::new(port)); } - pub fn get_port(&self, id: u32) -> Option<&super::port::Port> { - self.ports.get(&id) + pub fn get_port(&self, id: u32) -> Option> { + let private = imp::Node::from_instance(self); + private + .ports + .borrow_mut() + .get(&id) + .map(|port_rc| port_rc.clone()) } }