mirror of
https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio.git
synced 2024-11-25 02:21:00 +00:00
graphview: can now select nodes and links
Nodes and links can be selected and deleted
This commit is contained in:
parent
7d08abaca8
commit
a0bb503b27
9 changed files with 409 additions and 93 deletions
6
TODO.md
6
TODO.md
|
@ -19,9 +19,9 @@ TODO:
|
|||
- [x] Run the pipeline with GStreamer
|
||||
- [x] Control the pipeline with GStreamer
|
||||
- [x] Define the license
|
||||
- [] check that that a node accept to create a port on request (input/output)
|
||||
- [] select nodes/links with a Trait Selectable
|
||||
- [] be able to remove a link by selecting it
|
||||
- [] check that a node accept to create a port on request (input/output)
|
||||
- [x] select nodes/links with a Trait Selectable
|
||||
- [x] be able to remove a link by selecting it
|
||||
- [] Connect the logs to the window
|
||||
- [] Create a window for the video output
|
||||
- [] Add multiple graphviews with tabs.
|
||||
|
|
14
src/app.rs
14
src/app.rs
|
@ -232,6 +232,18 @@ impl GPSApp {
|
|||
application.add_action(&action);
|
||||
application.set_accels_for_action("app.save", &["<primary>s"]);
|
||||
|
||||
let action = gio::SimpleAction::new("delete", None);
|
||||
application.set_accels_for_action("app.delete", &["<primary>d", "Delete"]);
|
||||
let app_weak = self.downgrade();
|
||||
action.connect_activate({
|
||||
move |_, _| {
|
||||
let app = upgrade_weak!(app_weak);
|
||||
let graph_view = app.graphview.borrow();
|
||||
graph_view.delete_selected();
|
||||
}
|
||||
});
|
||||
application.add_action(&action);
|
||||
|
||||
let action = gio::SimpleAction::new("quit", None);
|
||||
action.connect_activate({
|
||||
let app = application.downgrade();
|
||||
|
@ -437,7 +449,7 @@ impl GPSApp {
|
|||
let app = upgrade_weak!(app_weak);
|
||||
println!("node.request-pad-output {}", node_id);
|
||||
let mut node = app.graphview.borrow_mut().node(&node_id).unwrap();
|
||||
let port_id = app.graphview.borrow().next_port_id();
|
||||
let port_id = app.graphview.borrow_mut().next_port_id();
|
||||
node.add_port(port_id, "out", PortDirection::Output);
|
||||
pop_menu.unparent();
|
||||
}));
|
||||
|
|
|
@ -40,8 +40,7 @@
|
|||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes" comments="Node menu entry delete the element">_Delete node</attribute>
|
||||
<attribute name="action">app.node.delete</attribute>
|
||||
<attribute name="accel"><primary>n</attribute>
|
||||
<attribute name="action">app.delete</attribute>
|
||||
</item>
|
||||
<submenu>
|
||||
<attribute name="label" translatable="yes" comments="Node menu entry request pad">_Request pad</attribute>
|
||||
|
|
|
@ -1,11 +1,29 @@
|
|||
@define-color graphview-link #808080;
|
||||
|
||||
node-button {
|
||||
color: black;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
transition: all 250ms ease-in;
|
||||
border: 1px transparent solid;
|
||||
button.node {
|
||||
color: rgb(0, 0, 255);
|
||||
background: rgb(170, 255, 170);
|
||||
}
|
||||
|
||||
button.node-selected {
|
||||
border-color: rgb(255, 0, 0);
|
||||
background: rgb(170, 255, 170);
|
||||
}
|
||||
|
||||
button.port {
|
||||
color: rgb(0, 0, 255);
|
||||
}
|
||||
|
||||
button.port-selected {
|
||||
border-color: rgb(255, 0, 0);
|
||||
}
|
||||
|
||||
button.port-out {
|
||||
background: rgb(255, 170, 170);
|
||||
}
|
||||
|
||||
button.port-in {
|
||||
background: rgb(170, 170, 255);
|
||||
}
|
||||
|
||||
graphview {
|
||||
|
|
|
@ -24,7 +24,7 @@ use xml::reader::XmlEvent as XMLREvent;
|
|||
use xml::writer::EmitterConfig;
|
||||
use xml::writer::XmlEvent as XMLWEvent;
|
||||
|
||||
use super::{node::Node, node::NodeType, port::Port, port::PortDirection};
|
||||
use super::{link::Link, node::Node, node::NodeType, port::Port, port::PortDirection};
|
||||
use glib::subclass::Signal;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::fs::File;
|
||||
|
@ -41,15 +41,6 @@ use log::{error, warn};
|
|||
|
||||
use std::cell::RefMut;
|
||||
use std::{cmp::Ordering, collections::HashMap, error};
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NodeLink {
|
||||
pub id: u32,
|
||||
pub node_from: u32,
|
||||
pub node_to: u32,
|
||||
pub port_from: u32,
|
||||
pub port_to: u32,
|
||||
pub active: bool,
|
||||
}
|
||||
|
||||
static GRAPHVIEW_STYLE: &str = include_str!("graphview.css");
|
||||
|
||||
|
@ -66,7 +57,7 @@ mod imp {
|
|||
#[derive(Default)]
|
||||
pub struct GraphView {
|
||||
pub(super) nodes: RefCell<HashMap<u32, Node>>,
|
||||
pub(super) links: RefCell<HashMap<u32, NodeLink>>,
|
||||
pub(super) links: RefCell<HashMap<u32, Link>>,
|
||||
pub(super) current_node_id: Cell<u32>,
|
||||
pub(super) current_port_id: Cell<u32>,
|
||||
pub(super) current_link_id: Cell<u32>,
|
||||
|
@ -152,8 +143,27 @@ mod imp {
|
|||
obj.emit_by_name("port-right-clicked", &[&port.id(), &node.id(), &graphene::Point::new(x as f32,y as f32)]).expect("unable to send signal");
|
||||
} else if let Some(target) = target.ancestor(Node::static_type()) {
|
||||
let node = target.dynamic_cast::<Node>().expect("click event is not on the Node");
|
||||
widget.unselect_all();
|
||||
node.set_selected(true);
|
||||
obj.emit_by_name("node-right-clicked", &[&node.id(), &graphene::Point::new(x as f32,y as f32)]).expect("unable to send signal");
|
||||
}
|
||||
} else if gesture.current_button() == BUTTON_PRIMARY {
|
||||
let widget = drag_controller.widget().expect("click event has no widget")
|
||||
.dynamic_cast::<Self::Type>()
|
||||
.expect("click event is not on the GraphView");
|
||||
let target = widget.pick(x, y, gtk::PickFlags::DEFAULT).expect("port pick() did not return a widget");
|
||||
if let Some(target) = target.ancestor(Port::static_type()) {
|
||||
let port = target.dynamic_cast::<Port>().expect("click event is not on the Node");
|
||||
widget.unselect_all();
|
||||
port.toggle_selected();
|
||||
} else if let Some(target) = target.ancestor(Node::static_type()) {
|
||||
let node = target.dynamic_cast::<Node>().expect("click event is not on the Node");
|
||||
widget.unselect_all();
|
||||
node.toggle_selected();
|
||||
}
|
||||
else {
|
||||
widget.point_on_link(&graphene::Point::new(x.floor() as f32,y.floor() as f32));
|
||||
}
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
@ -181,14 +191,15 @@ mod imp {
|
|||
std::mem::swap(&mut node_from, &mut node_to);
|
||||
std::mem::swap(&mut port_from, &mut port_to);
|
||||
}
|
||||
widget.add_link(NodeLink {
|
||||
id: widget.next_link_id(),
|
||||
node_from: node_from.id(),
|
||||
node_to: node_to.id(),
|
||||
port_from: port_from.id(),
|
||||
port_to: port_to.id(),
|
||||
active: true
|
||||
} );
|
||||
widget.add_link(Link::new(
|
||||
widget.next_link_id(),
|
||||
node_from.id(),
|
||||
node_to.id(),
|
||||
port_from.id(),
|
||||
port_to.id(),
|
||||
true,
|
||||
false,
|
||||
));
|
||||
}
|
||||
widget.set_selected_port(None);
|
||||
} else {
|
||||
|
@ -261,18 +272,21 @@ mod imp {
|
|||
))
|
||||
.expect("Failed to get cairo context");
|
||||
|
||||
link_cr.set_line_width(1.5);
|
||||
|
||||
for link in self.links.borrow().values() {
|
||||
if let Some((from_x, from_y, to_x, to_y)) = self.link_coordinates(link) {
|
||||
//println!("from_x: {} from_y: {} to_x: {} to_y: {}", from_x, from_y, to_x, to_y);
|
||||
|
||||
link_cr.set_line_width(link.thickness as f64);
|
||||
// Use dashed line for inactive links, full line otherwise.
|
||||
if link.active {
|
||||
link_cr.set_dash(&[], 0.0);
|
||||
} else {
|
||||
link_cr.set_dash(&[10.0, 5.0], 0.0);
|
||||
}
|
||||
if link.selected() {
|
||||
link_cr.set_source_rgb(1.0, 0.18, 0.18);
|
||||
} else {
|
||||
link_cr.set_source_rgb(0.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
link_cr.move_to(from_x, from_y);
|
||||
link_cr.line_to(to_x, to_y);
|
||||
|
@ -293,13 +307,12 @@ mod imp {
|
|||
///
|
||||
/// # Returns
|
||||
/// `Some((from_x, from_y, to_x, to_y))` if all objects the links refers to exist as widgets.
|
||||
fn link_coordinates(&self, link: &NodeLink) -> Option<(f64, f64, f64, f64)> {
|
||||
pub fn link_coordinates(&self, link: &Link) -> Option<(f64, f64, f64, f64)> {
|
||||
let nodes = self.nodes.borrow();
|
||||
|
||||
// For some reason, gtk4::WidgetExt::translate_coordinates gives me incorrect values,
|
||||
// so we manually calculate the needed offsets here.
|
||||
let from_node = nodes.get(&link.node_from)?;
|
||||
let from_port = from_node.port(&link.port_from)?;
|
||||
|
||||
let from_port = &nodes.get(&link.node_from)?.port(&link.port_from)?;
|
||||
let gtk::Allocation {
|
||||
x: mut fx,
|
||||
y: mut fy,
|
||||
|
@ -307,28 +320,29 @@ mod imp {
|
|||
height: fh,
|
||||
} = from_port.allocation();
|
||||
|
||||
let from_node = from_port
|
||||
.ancestor(Node::static_type())
|
||||
.expect("Port is not a child of a node");
|
||||
let gtk::Allocation { x: fnx, y: fny, .. } = from_node.allocation();
|
||||
fx += fnx + (fw / 2);
|
||||
fy += fny + (fh / 2);
|
||||
|
||||
let to_port = &nodes.get(&link.node_to)?.port(&link.port_to)?;
|
||||
if let Some((port_x, port_y)) = from_port.translate_coordinates(from_node, 0.0, 0.0) {
|
||||
fx += fnx + fw + port_x as i32;
|
||||
fy = fny + (fh / 2) + port_y as i32;
|
||||
}
|
||||
|
||||
let to_node = nodes.get(&link.node_to)?;
|
||||
let to_port = to_node.port(&link.port_to)?;
|
||||
let gtk::Allocation {
|
||||
x: mut tx,
|
||||
y: mut ty,
|
||||
width: tw,
|
||||
width: _tw,
|
||||
height: th,
|
||||
..
|
||||
} = to_port.allocation();
|
||||
let to_node = to_port
|
||||
.ancestor(Node::static_type())
|
||||
.expect("Port is not a child of a node");
|
||||
let gtk::Allocation { x: tnx, y: tny, .. } = to_node.allocation();
|
||||
tx += tnx + (tw / 2);
|
||||
ty += tny + (th / 2);
|
||||
|
||||
let gtk::Allocation { x: tnx, y: tny, .. } = to_node.allocation();
|
||||
if let Some((port_x, port_y)) = to_port.translate_coordinates(to_node, 0.0, 0.0) {
|
||||
tx += tnx + port_x as i32;
|
||||
ty = tny + (th / 2) + port_y as i32;
|
||||
}
|
||||
//println!("{} {} -> {} {}", fx, fy, tx, ty);
|
||||
Some((fx.into(), fy.into(), tx.into(), ty.into()))
|
||||
}
|
||||
}
|
||||
|
@ -460,6 +474,14 @@ impl GraphView {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn unselect_nodes(&self) {
|
||||
let private = imp::GraphView::from_instance(self);
|
||||
for node in private.nodes.borrow_mut().values() {
|
||||
node.set_selected(false);
|
||||
node.unselect_all_ports();
|
||||
}
|
||||
}
|
||||
|
||||
// Port related methods
|
||||
pub fn add_port(
|
||||
&self,
|
||||
|
@ -502,14 +524,14 @@ impl GraphView {
|
|||
}
|
||||
|
||||
// Link related methods
|
||||
pub fn all_links(&self) -> Vec<NodeLink> {
|
||||
pub fn all_links(&self) -> Vec<Link> {
|
||||
let private = imp::GraphView::from_instance(self);
|
||||
let links = private.links.borrow();
|
||||
let links_list: Vec<_> = links.iter().map(|(_, link)| link.clone()).collect();
|
||||
links_list
|
||||
}
|
||||
|
||||
pub fn add_link(&self, link: NodeLink) {
|
||||
pub fn add_link(&self, link: Link) {
|
||||
let private = imp::GraphView::from_instance(self);
|
||||
if !self.link_exists(&link) {
|
||||
private.links.borrow_mut().insert(link.id, link);
|
||||
|
@ -535,6 +557,35 @@ impl GraphView {
|
|||
self.queue_draw();
|
||||
}
|
||||
|
||||
pub fn unselect_links(&self) {
|
||||
let private = imp::GraphView::from_instance(self);
|
||||
for link in private.links.borrow_mut().values() {
|
||||
link.set_selected(false);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn point_on_link(&self, point: &graphene::Point) -> Option<Link> {
|
||||
let private = imp::GraphView::from_instance(self);
|
||||
self.unselect_all();
|
||||
for link in private.links.borrow_mut().values() {
|
||||
if let Some((from_x, from_y, to_x, to_y)) = private.link_coordinates(link) {
|
||||
let quad = graphene::Quad::new(
|
||||
&graphene::Point::new(from_x as f32, from_y as f32 - link.thickness as f32),
|
||||
&graphene::Point::new(to_x as f32, to_y as f32 - link.thickness as f32),
|
||||
&graphene::Point::new(to_x as f32, to_y as f32 + link.thickness as f32),
|
||||
&graphene::Point::new(from_x as f32, from_y as f32 + link.thickness as f32),
|
||||
);
|
||||
if quad.contains(point) {
|
||||
link.toggle_selected();
|
||||
self.queue_draw();
|
||||
return Some(link.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
self.queue_draw();
|
||||
None
|
||||
}
|
||||
|
||||
/// Get the position of the specified node inside the graphview.
|
||||
///
|
||||
/// Returns `None` if the node is not in the graphview.
|
||||
|
@ -579,7 +630,7 @@ impl GraphView {
|
|||
self.queue_draw();
|
||||
}
|
||||
|
||||
pub(super) fn link_exists(&self, new_link: &NodeLink) -> bool {
|
||||
pub(super) fn link_exists(&self, new_link: &Link) -> bool {
|
||||
let private = imp::GraphView::from_instance(self);
|
||||
|
||||
for link in private.links.borrow().values() {
|
||||
|
@ -625,6 +676,13 @@ impl GraphView {
|
|||
private.current_node_id.get()
|
||||
}
|
||||
|
||||
pub fn update_current_node_id(&self, node_id: u32) {
|
||||
let private = imp::GraphView::from_instance(self);
|
||||
if node_id > private.current_node_id.get() {
|
||||
private.current_node_id.set(node_id);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_port_id(&self) -> u32 {
|
||||
let private = imp::GraphView::from_instance(self);
|
||||
private
|
||||
|
@ -633,6 +691,13 @@ impl GraphView {
|
|||
private.current_port_id.get()
|
||||
}
|
||||
|
||||
pub fn update_current_port_id(&self, port_id: u32) {
|
||||
let private = imp::GraphView::from_instance(self);
|
||||
if port_id > private.current_port_id.get() {
|
||||
private.current_port_id.set(port_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn next_link_id(&self) -> u32 {
|
||||
let private = imp::GraphView::from_instance(self);
|
||||
private
|
||||
|
@ -641,7 +706,15 @@ impl GraphView {
|
|||
private.current_link_id.get()
|
||||
}
|
||||
|
||||
pub fn update_current_link_id(&self, link_id: u32) {
|
||||
let private = imp::GraphView::from_instance(self);
|
||||
if link_id > private.current_link_id.get() {
|
||||
private.current_link_id.set(link_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_selected_port(&self, port: Option<&Port>) {
|
||||
self.unselect_all();
|
||||
let private = imp::GraphView::from_instance(self);
|
||||
*private.port_selected.borrow_mut() = port.cloned();
|
||||
}
|
||||
|
@ -689,6 +762,36 @@ impl GraphView {
|
|||
}
|
||||
description
|
||||
}
|
||||
|
||||
pub fn unselect_all(&self) {
|
||||
self.unselect_nodes();
|
||||
self.unselect_links();
|
||||
self.queue_draw();
|
||||
}
|
||||
|
||||
pub fn delete_selected(&self) {
|
||||
let private = imp::GraphView::from_instance(self);
|
||||
let mut link_id = None;
|
||||
let mut node_id = None;
|
||||
for link in private.links.borrow_mut().values() {
|
||||
if link.selected() {
|
||||
link_id = Some(link.id);
|
||||
}
|
||||
}
|
||||
for node in private.nodes.borrow_mut().values() {
|
||||
if node.selected() {
|
||||
node_id = Some(node.id());
|
||||
}
|
||||
}
|
||||
if let Some(id) = link_id {
|
||||
self.remove_link(id);
|
||||
}
|
||||
if let Some(id) = node_id {
|
||||
self.remove_node(id);
|
||||
}
|
||||
self.queue_draw();
|
||||
}
|
||||
|
||||
//TO BE MOVED
|
||||
pub fn render_gst(&self) -> String {
|
||||
let nodes = self.all_nodes(NodeType::Source);
|
||||
|
@ -761,7 +864,7 @@ impl GraphView {
|
|||
|
||||
let mut current_node: Option<Node> = None;
|
||||
let mut current_port: Option<Port> = None;
|
||||
let mut current_link: Option<NodeLink> = None;
|
||||
let mut current_link: Option<Link> = None;
|
||||
for e in parser {
|
||||
match e {
|
||||
Ok(XMLREvent::StartElement {
|
||||
|
@ -841,14 +944,15 @@ impl GraphView {
|
|||
let active: &String = attrs
|
||||
.get::<String>(&String::from("active"))
|
||||
.expect("Unable to find link state");
|
||||
current_link = Some(NodeLink {
|
||||
id: id.parse::<u32>().unwrap(),
|
||||
node_from: node_from.parse::<u32>().unwrap(),
|
||||
node_to: node_to.parse::<u32>().unwrap(),
|
||||
port_from: port_from.parse::<u32>().unwrap(),
|
||||
port_to: port_to.parse::<u32>().unwrap(),
|
||||
active: active.parse::<bool>().unwrap(),
|
||||
});
|
||||
current_link = Some(Link::new(
|
||||
id.parse::<u32>().unwrap(),
|
||||
node_from.parse::<u32>().unwrap(),
|
||||
node_to.parse::<u32>().unwrap(),
|
||||
port_from.parse::<u32>().unwrap(),
|
||||
port_to.parse::<u32>().unwrap(),
|
||||
active.parse::<bool>().unwrap(),
|
||||
false,
|
||||
));
|
||||
}
|
||||
_ => println!("name unknown: {}", name),
|
||||
}
|
||||
|
@ -857,12 +961,13 @@ impl GraphView {
|
|||
println!("closing {}", name);
|
||||
match name.to_string().as_str() {
|
||||
"Graph" => {
|
||||
println!("Graph ended");
|
||||
println!("Graph ended with success");
|
||||
}
|
||||
"Node" => {
|
||||
if let Some(node) = current_node {
|
||||
let id = node.id();
|
||||
self.add_node(id, node);
|
||||
self.update_current_node_id(id);
|
||||
}
|
||||
current_node = None;
|
||||
}
|
||||
|
@ -870,17 +975,21 @@ impl GraphView {
|
|||
"Port" => {
|
||||
if let Some(port) = current_port {
|
||||
let node = current_node.clone();
|
||||
let id = port.id();
|
||||
node.expect("No current node, error...").add_port(
|
||||
port.id(),
|
||||
id,
|
||||
&port.name(),
|
||||
port.direction(),
|
||||
);
|
||||
self.update_current_port_id(id);
|
||||
}
|
||||
current_port = None;
|
||||
}
|
||||
"Link" => {
|
||||
if let Some(link) = current_link {
|
||||
let id = link.id;
|
||||
self.add_link(link);
|
||||
self.update_current_link_id(id);
|
||||
}
|
||||
current_link = None;
|
||||
}
|
||||
|
|
68
src/graphmanager/link.rs
Normal file
68
src/graphmanager/link.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
// link.rs
|
||||
//
|
||||
// Copyright 2021 Tom A. Wagner <tom.a.wagner@protonmail.com>
|
||||
// Copyright 2021 Stéphane Cerveau <scerveau@collabora.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use std::cell::Cell;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Link {
|
||||
pub id: u32,
|
||||
pub node_from: u32,
|
||||
pub node_to: u32,
|
||||
pub port_from: u32,
|
||||
pub port_to: u32,
|
||||
pub active: bool,
|
||||
pub selected: Cell<bool>,
|
||||
pub thickness: u32,
|
||||
}
|
||||
|
||||
impl Link {
|
||||
pub fn new(
|
||||
id: u32,
|
||||
node_from: u32,
|
||||
node_to: u32,
|
||||
port_from: u32,
|
||||
port_to: u32,
|
||||
active: bool,
|
||||
selected: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
id,
|
||||
node_from,
|
||||
node_to,
|
||||
port_from,
|
||||
port_to,
|
||||
active,
|
||||
selected: Cell::new(selected),
|
||||
thickness: 4,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_selected(&self) {
|
||||
self.set_selected(!self.selected.get());
|
||||
}
|
||||
|
||||
pub fn set_selected(&self, selected: bool) {
|
||||
self.selected.set(selected);
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> bool {
|
||||
self.selected.get()
|
||||
}
|
||||
}
|
|
@ -1,8 +1,10 @@
|
|||
mod graphview;
|
||||
mod link;
|
||||
mod node;
|
||||
mod port;
|
||||
|
||||
pub use graphview::GraphView;
|
||||
pub use link::Link;
|
||||
pub use node::Node;
|
||||
pub use node::NodeType;
|
||||
pub use port::Port;
|
||||
|
|
|
@ -58,16 +58,21 @@ impl NodeType {
|
|||
|
||||
mod imp {
|
||||
use super::*;
|
||||
use gtk::Orientation;
|
||||
use once_cell::unsync::OnceCell;
|
||||
pub struct Node {
|
||||
pub(super) grid: gtk::Grid,
|
||||
pub(super) label: gtk::Label,
|
||||
pub(super) layoutbox: gtk::Box,
|
||||
pub(super) inputs: gtk::Box,
|
||||
pub(super) outputs: gtk::Box,
|
||||
pub(super) name: gtk::Label,
|
||||
pub(super) description: gtk::Label,
|
||||
pub(super) id: OnceCell<u32>,
|
||||
pub(super) node_type: OnceCell<NodeType>,
|
||||
pub(super) ports: RefCell<HashMap<u32, Port>>,
|
||||
pub(super) num_ports_in: Cell<i32>,
|
||||
pub(super) num_ports_out: Cell<i32>,
|
||||
pub(super) properties: RefCell<HashMap<String, String>>,
|
||||
pub(super) selected: Cell<bool>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -82,23 +87,62 @@ mod imp {
|
|||
}
|
||||
|
||||
fn new() -> Self {
|
||||
let grid = gtk::Grid::new();
|
||||
let label = gtk::Label::new(None);
|
||||
let layoutbox = gtk::Box::new(Orientation::Vertical, 6);
|
||||
let name_desc = gtk::Box::new(Orientation::Vertical, 6);
|
||||
layoutbox.append(&name_desc);
|
||||
let ports = gtk::Box::builder()
|
||||
.orientation(Orientation::Horizontal)
|
||||
.halign(gtk::Align::Start)
|
||||
.spacing(10)
|
||||
.margin_bottom(10)
|
||||
.margin_top(10)
|
||||
.build();
|
||||
|
||||
grid.attach(&label, 0, 0, 2, 1);
|
||||
layoutbox.append(&ports);
|
||||
let inputs = gtk::Box::builder()
|
||||
.orientation(Orientation::Vertical)
|
||||
.halign(gtk::Align::Start)
|
||||
.spacing(10)
|
||||
.build();
|
||||
|
||||
// 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());
|
||||
ports.append(&inputs);
|
||||
let center = gtk::Box::builder()
|
||||
.orientation(Orientation::Vertical)
|
||||
.halign(gtk::Align::Center)
|
||||
.hexpand(true)
|
||||
.margin_start(20)
|
||||
.margin_end(20)
|
||||
.build();
|
||||
ports.append(¢er);
|
||||
let outputs = gtk::Box::builder()
|
||||
.orientation(Orientation::Vertical)
|
||||
.halign(gtk::Align::End)
|
||||
.spacing(10)
|
||||
.build();
|
||||
ports.append(&outputs);
|
||||
|
||||
let name = gtk::Label::new(None);
|
||||
name_desc.append(&name);
|
||||
|
||||
let description = gtk::Label::new(None);
|
||||
name_desc.append(&description);
|
||||
|
||||
// Display a grab cursor when the mouse is over the name so the user knows the node can be dragged.
|
||||
name.set_cursor(gtk::gdk::Cursor::from_name("grab", None).as_ref());
|
||||
|
||||
Self {
|
||||
grid,
|
||||
label,
|
||||
layoutbox,
|
||||
inputs,
|
||||
outputs,
|
||||
name,
|
||||
description,
|
||||
id: OnceCell::new(),
|
||||
node_type: OnceCell::new(),
|
||||
ports: RefCell::new(HashMap::new()),
|
||||
num_ports_in: Cell::new(0),
|
||||
num_ports_out: Cell::new(0),
|
||||
properties: RefCell::new(HashMap::new()),
|
||||
selected: Cell::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -106,11 +150,11 @@ mod imp {
|
|||
impl ObjectImpl for Node {
|
||||
fn constructed(&self, obj: &Self::Type) {
|
||||
self.parent_constructed(obj);
|
||||
self.grid.set_parent(obj);
|
||||
self.layoutbox.set_parent(obj);
|
||||
}
|
||||
|
||||
fn dispose(&self, _obj: &Self::Type) {
|
||||
self.grid.unparent();
|
||||
self.layoutbox.unparent();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,6 +172,7 @@ impl Node {
|
|||
let private = imp::Node::from_instance(&res);
|
||||
private.id.set(id).expect("Node id already set");
|
||||
res.set_name(name);
|
||||
res.add_css_class("node");
|
||||
private
|
||||
.node_type
|
||||
.set(node_type)
|
||||
|
@ -137,24 +182,36 @@ impl Node {
|
|||
|
||||
fn set_name(&self, name: &str) {
|
||||
let self_ = imp::Node::from_instance(self);
|
||||
self_.label.set_text(name);
|
||||
self_.name.set_text(name);
|
||||
println!("{}", name);
|
||||
}
|
||||
|
||||
fn set_description(&self, description: &str) {
|
||||
let self_ = imp::Node::from_instance(self);
|
||||
self_.description.set_text(description);
|
||||
println!("{}", description);
|
||||
}
|
||||
|
||||
fn update_description(&self) {
|
||||
let self_ = imp::Node::from_instance(self);
|
||||
let mut description = String::from("");
|
||||
for (name, value) in self_.properties.borrow().iter() {
|
||||
description.push_str(&format!("{}:{}", name, value));
|
||||
description.push('\n');
|
||||
}
|
||||
self.set_description(&description);
|
||||
}
|
||||
|
||||
pub fn add_port(&mut self, id: u32, name: &str, direction: PortDirection) {
|
||||
let private = imp::Node::from_instance(self);
|
||||
let port = Port::new(id, name, direction);
|
||||
match port.direction() {
|
||||
PortDirection::Input => {
|
||||
private
|
||||
.grid
|
||||
.attach(&port, 0, private.num_ports_in.get() + 1, 1, 1);
|
||||
private.inputs.append(&port);
|
||||
private.num_ports_in.set(private.num_ports_in.get() + 1);
|
||||
}
|
||||
PortDirection::Output => {
|
||||
private
|
||||
.grid
|
||||
.attach(&port, 1, private.num_ports_out.get() + 1, 1, 1);
|
||||
private.outputs.append(&port);
|
||||
private.num_ports_out.set(private.num_ports_out.get() + 1);
|
||||
}
|
||||
_ => panic!("Port without direction"),
|
||||
|
@ -202,12 +259,12 @@ impl Node {
|
|||
|
||||
pub fn name(&self) -> String {
|
||||
let private = imp::Node::from_instance(self);
|
||||
private.label.text().to_string()
|
||||
private.name.text().to_string()
|
||||
}
|
||||
|
||||
pub fn unique_name(&self) -> String {
|
||||
let private = imp::Node::from_instance(self);
|
||||
let mut unique_name = private.label.text().to_string();
|
||||
let mut unique_name = private.name.text().to_string();
|
||||
unique_name.push_str(&self.id().to_string());
|
||||
unique_name
|
||||
}
|
||||
|
@ -221,6 +278,7 @@ impl Node {
|
|||
let private = imp::Node::from_instance(self);
|
||||
println!("{} {} updated", name, value);
|
||||
private.properties.borrow_mut().insert(name, value);
|
||||
self.update_description();
|
||||
}
|
||||
|
||||
pub fn update_node_properties(&self, new_properties: &HashMap<String, String>) {
|
||||
|
@ -233,4 +291,30 @@ impl Node {
|
|||
let private = imp::Node::from_instance(self);
|
||||
private.properties.borrow()
|
||||
}
|
||||
|
||||
pub fn toggle_selected(&self) {
|
||||
self.set_selected(!self.selected());
|
||||
}
|
||||
|
||||
pub fn set_selected(&self, selected: bool) {
|
||||
let private = imp::Node::from_instance(self);
|
||||
private.selected.set(selected);
|
||||
if selected {
|
||||
self.add_css_class("node-selected");
|
||||
} else {
|
||||
self.remove_css_class("node-selected");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> bool {
|
||||
let private = imp::Node::from_instance(self);
|
||||
private.selected.get()
|
||||
}
|
||||
|
||||
pub fn unselect_all_ports(&self) {
|
||||
let private = imp::Node::from_instance(self);
|
||||
for port in private.ports.borrow_mut().values() {
|
||||
port.set_selected(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ use gtk::{
|
|||
prelude::*,
|
||||
subclass::prelude::*,
|
||||
};
|
||||
use std::cell::Cell;
|
||||
use std::{borrow::Borrow, fmt};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Copy)]
|
||||
|
@ -61,6 +62,7 @@ mod imp {
|
|||
pub(super) label: OnceCell<gtk::Label>,
|
||||
pub(super) id: OnceCell<u32>,
|
||||
pub(super) direction: OnceCell<PortDirection>,
|
||||
pub(super) selected: Cell<bool>,
|
||||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
|
@ -110,26 +112,29 @@ glib::wrapper! {
|
|||
impl Port {
|
||||
pub fn new(id: u32, name: &str, direction: PortDirection) -> Self {
|
||||
// Create the widget and initialize needed fields
|
||||
let res: Self = glib::Object::new(&[]).expect("Failed to create Port");
|
||||
|
||||
let private = imp::Port::from_instance(&res);
|
||||
let port: Self = glib::Object::new(&[]).expect("Failed to create Port");
|
||||
port.add_css_class("port");
|
||||
let private = imp::Port::from_instance(&port);
|
||||
private.id.set(id).expect("Port id already set");
|
||||
private.selected.set(false);
|
||||
private
|
||||
.direction
|
||||
.set(direction)
|
||||
.expect("Port direction already set");
|
||||
if direction == PortDirection::Input {
|
||||
port.add_css_class("port-in");
|
||||
} else {
|
||||
port.add_css_class("port-out");
|
||||
}
|
||||
|
||||
let label = gtk::Label::new(Some(name));
|
||||
label.set_parent(&res);
|
||||
label.set_parent(&port);
|
||||
private
|
||||
.label
|
||||
.set(label)
|
||||
.expect("Port label was already set");
|
||||
|
||||
// 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());
|
||||
|
||||
res
|
||||
port
|
||||
}
|
||||
|
||||
pub fn id(&self) -> u32 {
|
||||
|
@ -147,4 +152,23 @@ impl Port {
|
|||
let label = private.label.borrow().get().unwrap();
|
||||
label.text().to_string()
|
||||
}
|
||||
|
||||
pub fn toggle_selected(&self) {
|
||||
self.set_selected(!self.selected());
|
||||
}
|
||||
|
||||
pub fn set_selected(&self, selected: bool) {
|
||||
let private = imp::Port::from_instance(self);
|
||||
private.selected.set(selected);
|
||||
if selected {
|
||||
self.add_css_class("port-selected");
|
||||
} else {
|
||||
self.remove_css_class("port-selected");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected(&self) -> bool {
|
||||
let private = imp::Port::from_instance(self);
|
||||
private.selected.get()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue