graph: Refactor graph item management into new graph_manager object

The graph widgets management (watching a glib receiver, adding and removing
Nodes, Ports and Links) currently done in the `Application` and `GraphView`
objects has been extracted into a new GraphManager object, which watches the
receiver instead, pushes changes directly to the widgets, and reacts to their signals.

This seperates widget logic and management logic cleanly instead of both
being mixed into the GraphView, and also reduces the code size for the
Application object.
This commit is contained in:
Tom A. Wagner
2023-08-01 08:50:51 +02:00
parent a9ad1cccf0
commit 0b3b124cdf
6 changed files with 342 additions and 243 deletions

View File

@@ -22,9 +22,8 @@ use gtk::{
prelude::*,
subclass::prelude::*,
};
use log::{error, warn};
use std::{cmp::Ordering, collections::HashMap};
use std::cmp::Ordering;
use super::{Link, Node, Port};
use crate::NodeType;
@@ -35,6 +34,7 @@ mod imp {
use super::*;
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet};
use gtk::{
gdk::{self, RGBA},
@@ -56,9 +56,9 @@ mod imp {
#[derive(Default)]
pub struct GraphView {
/// Stores nodes and their positions.
pub(super) nodes: RefCell<HashMap<u32, (Node, Point)>>,
pub(super) nodes: RefCell<HashMap<Node, Point>>,
/// Stores the link and whether it is currently active.
pub(super) links: RefCell<HashMap<u32, Link>>,
pub(super) links: RefCell<HashSet<Link>>,
pub hadjustment: RefCell<Option<gtk::Adjustment>>,
pub vadjustment: RefCell<Option<gtk::Adjustment>>,
pub zoom_factor: Cell<f64>,
@@ -95,7 +95,7 @@ mod imp {
fn dispose(&self) {
self.nodes
.borrow()
.values()
.iter()
.for_each(|(node, _)| node.unparent())
}
@@ -152,7 +152,7 @@ mod imp {
fn size_allocate(&self, _width: i32, _height: i32, baseline: i32) {
let widget = &*self.obj();
for (node, point) in self.nodes.borrow().values() {
for (node, point) in self.nodes.borrow().iter() {
let (_, natural_size) = node.preferred_size();
let transform = self
@@ -184,7 +184,7 @@ mod imp {
// Draw all visible children
self.nodes
.borrow()
.values()
.iter()
// Cull nodes from rendering when they are outside the visible canvas area
.filter(|(node, _)| alloc.intersect(&node.allocation()).is_some())
.for_each(|(node, _)| widget.snapshot_child(node, snapshot));
@@ -430,7 +430,7 @@ mod imp {
rgba.alpha().into(),
);
for link in self.links.borrow().values() {
for link in self.links.borrow().iter() {
// TODO: Do not draw links when they are outside the view
if let Some((from_x, from_y, to_x, to_y)) = self.get_link_coordinates(link) {
link_cr.move_to(from_x, from_y);
@@ -605,7 +605,7 @@ impl GraphView {
self.set_property("zoom-factor", zoom_factor);
}
pub fn add_node(&self, id: u32, node: Node, node_type: Option<NodeType>) {
pub fn add_node(&self, node: Node, node_type: Option<NodeType>) {
let imp = self.imp();
node.set_parent(self);
@@ -622,7 +622,7 @@ impl GraphView {
let y = imp
.nodes
.borrow()
.values()
.iter()
.map(|node| {
// Map nodes to their locations
let point = self.node_position(&node.0.clone().upcast()).unwrap();
@@ -638,68 +638,33 @@ impl GraphView {
})
.map_or(20_f32, |(_x, y)| y + 120.0);
imp.nodes.borrow_mut().insert(id, (node, Point::new(x, y)));
imp.nodes.borrow_mut().insert(node, Point::new(x, y));
}
pub fn remove_node(&self, id: u32) {
pub fn remove_node(&self, node: &Node) {
let mut nodes = self.imp().nodes.borrow_mut();
if let Some((node, _)) = nodes.remove(&id) {
if nodes.remove(node).is_some() {
node.unparent();
} else {
warn!("Tried to remove non-existant node (id={}) from graph", id);
log::warn!("Tried to remove non-existant node widget from graph");
}
}
pub fn add_port(&self, node_id: u32, port_id: u32, port: Port) {
if let Some((node, _)) = self.imp().nodes.borrow_mut().get_mut(&node_id) {
node.add_port(port_id, port);
} else {
error!(
"Node with id {} not found when trying to add port with id {} to graph",
node_id, port_id
);
}
}
pub fn remove_port(&self, id: u32, node_id: u32) {
let nodes = self.imp().nodes.borrow();
if let Some((node, _)) = nodes.get(&node_id) {
node.remove_port(id);
}
}
pub fn add_link(&self, link_id: u32, link: crate::PipewireLink, active: bool) {
let nodes = self.imp().nodes.borrow();
let output_port = nodes
.get(&link.node_from)
.and_then(|(node, _)| node.get_port(link.port_from));
let input_port = nodes
.get(&link.node_to)
.and_then(|(node, _)| node.get_port(link.port_to));
let link = Link::new();
link.set_input_port(input_port.as_ref());
link.set_output_port(output_port.as_ref());
link.set_active(active);
self.imp().links.borrow_mut().insert(link_id, link);
pub fn add_link(&self, link: Link) {
link.connect_notify_local(
Some("active"),
glib::clone!(@weak self as graph => move |_, _| {
graph.queue_draw();
}),
);
self.imp().links.borrow_mut().insert(link);
self.queue_draw();
}
pub fn set_link_state(&self, link_id: u32, active: bool) {
if let Some(link) = self.imp().links.borrow_mut().get_mut(&link_id) {
link.set_active(active);
self.queue_draw();
} else {
warn!("Link state changed on unknown link (id={})", link_id);
}
}
pub fn remove_link(&self, id: u32) {
pub fn remove_link(&self, link: &Link) {
let mut links = self.imp().links.borrow_mut();
links.remove(&id);
links.remove(link);
self.queue_draw();
}
@@ -708,30 +673,22 @@ impl GraphView {
///
/// The returned position is in canvas-space (non-zoomed, (0, 0) fixed in the middle of the canvas).
pub(super) fn node_position(&self, node: &Node) -> Option<Point> {
self.imp()
.nodes
.borrow()
.get(&node.pipewire_id())
.map(|(_, point)| *point)
self.imp().nodes.borrow().get(node).copied()
}
pub(super) fn move_node(&self, widget: &Node, point: &Point) {
let mut nodes = self.imp().nodes.borrow_mut();
let node = nodes
.get_mut(&widget.pipewire_id())
.expect("Node is not on the graph");
let node_point = nodes.get_mut(widget).expect("Node is not on the graph");
// Clamp the new position to within the graph, so a node can't be moved outside it and be lost.
node.1 = Point::new(
point.x().clamp(
-(CANVAS_SIZE / 2.0) as f32,
(CANVAS_SIZE / 2.0) as f32 - widget.width() as f32,
),
point.y().clamp(
-(CANVAS_SIZE / 2.0) as f32,
(CANVAS_SIZE / 2.0) as f32 - widget.height() as f32,
),
);
node_point.set_x(point.x().clamp(
-(CANVAS_SIZE / 2.0) as f32,
(CANVAS_SIZE / 2.0) as f32 - widget.width() as f32,
));
node_point.set_y(point.y().clamp(
-(CANVAS_SIZE / 2.0) as f32,
(CANVAS_SIZE / 2.0) as f32 - widget.height() as f32,
));
self.queue_allocate();
}

View File

@@ -17,14 +17,15 @@
use gtk::{glib, prelude::*, subclass::prelude::*};
use pipewire::spa::Direction;
use std::collections::HashMap;
use super::Port;
mod imp {
use super::*;
use std::cell::{Cell, RefCell};
use std::{
cell::{Cell, RefCell},
collections::HashSet,
};
#[derive(glib::Properties)]
#[properties(wrapper_type = super::Node)]
@@ -41,7 +42,7 @@ mod imp {
}
)]
pub(super) label: gtk::Label,
pub(super) ports: RefCell<HashMap<u32, Port>>,
pub(super) ports: RefCell<HashSet<Port>>,
pub(super) num_ports_in: Cell<i32>,
pub(super) num_ports_out: Cell<i32>,
}
@@ -74,7 +75,7 @@ mod imp {
pipewire_id: Cell::new(0),
grid,
label,
ports: RefCell::new(HashMap::new()),
ports: RefCell::new(HashSet::new()),
num_ports_in: Cell::new(0),
num_ports_out: Cell::new(0),
}
@@ -120,7 +121,7 @@ impl Node {
.build()
}
pub fn add_port(&mut self, id: u32, port: Port) {
pub fn add_port(&self, port: Port) {
let imp = self.imp();
match port.direction() {
@@ -134,22 +135,20 @@ impl Node {
}
}
imp.ports.borrow_mut().insert(id, port);
imp.ports.borrow_mut().insert(port);
}
pub fn get_port(&self, id: u32) -> Option<Port> {
self.imp().ports.borrow_mut().get(&id).cloned()
}
pub fn remove_port(&self, id: u32) {
pub fn remove_port(&self, port: &Port) {
let imp = self.imp();
if let Some(port) = imp.ports.borrow_mut().remove(&id) {
if imp.ports.borrow_mut().remove(port) {
match port.direction() {
Direction::Input => imp.num_ports_in.set(imp.num_ports_in.get() - 1),
Direction::Output => imp.num_ports_in.set(imp.num_ports_out.get() - 1),
}
port.unparent();
} else {
log::warn!("Tried to remove non-existant port widget from node");
}
}
}