mirror of
https://gitlab.freedesktop.org/dabrain34/GstPipelineStudio.git
synced 2025-02-16 10:35:16 +00:00
GPS: Add xml save/load in graphview
Can now save and load in XML graphs/nodes/ports/links
This commit is contained in:
parent
5f91fbaef7
commit
52286ada4a
8 changed files with 465 additions and 131 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -415,6 +415,7 @@ dependencies = [
|
||||||
"gtk4",
|
"gtk4",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"xml-rs",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -965,3 +966,9 @@ name = "winapi-x86_64-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xml-rs"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3"
|
||||||
|
|
|
@ -10,4 +10,5 @@ gtk = { version = "0.3", package = "gtk4" }
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
gstreamer = "0.16"
|
gstreamer = "0.16"
|
||||||
log = "0.4.11"
|
log = "0.4.11"
|
||||||
once_cell = "1.7.2"
|
once_cell = "1.7.2"
|
||||||
|
xml-rs = "0.8.4"
|
3
TODO.md
3
TODO.md
|
@ -14,13 +14,14 @@ TODO:
|
||||||
- [] create contextual menu on pad or element
|
- [] create contextual menu on pad or element
|
||||||
- [] upclass the element
|
- [] upclass the element
|
||||||
- [] create a crate for graphview/node/port
|
- [] create a crate for graphview/node/port
|
||||||
- [] save/load pipeline
|
- [x] save/load pipeline
|
||||||
- [] Run a pipeline with GStreamer
|
- [] Run a pipeline with GStreamer
|
||||||
- [] Run the pipeline with GStreamer
|
- [] Run the pipeline with GStreamer
|
||||||
- [] Control the pipeline with GStreamer
|
- [] Control the pipeline with GStreamer
|
||||||
- [x] Define the license
|
- [x] Define the license
|
||||||
- [] Connect the logs to the window
|
- [] Connect the logs to the window
|
||||||
- [] Create a window for the video output
|
- [] Create a window for the video output
|
||||||
|
- [] Add multiple graphviews with tabs.
|
||||||
|
|
||||||
## Code cleanup
|
## Code cleanup
|
||||||
|
|
||||||
|
|
123
src/app.rs
123
src/app.rs
|
@ -19,8 +19,8 @@
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use gtk::{gio, glib};
|
use gtk::{gio, glib};
|
||||||
use gtk::{
|
use gtk::{
|
||||||
AboutDialog, Application, ApplicationWindow, Builder, Button, FileChooserDialog, ResponseType,
|
AboutDialog, Application, ApplicationWindow, Builder, Button, FileChooserAction,
|
||||||
Statusbar, Viewport,
|
FileChooserDialog, ResponseType, Statusbar, Viewport,
|
||||||
};
|
};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::{Rc, Weak};
|
||||||
|
@ -115,8 +115,6 @@ impl GPSApp {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_ui(&self, application: &Application) {
|
pub fn build_ui(&self, application: &Application) {
|
||||||
//let app_weak = self.downgrade();
|
|
||||||
|
|
||||||
let drawing_area_window: Viewport = self
|
let drawing_area_window: Viewport = self
|
||||||
.builder
|
.builder
|
||||||
.object("drawing_area")
|
.object("drawing_area")
|
||||||
|
@ -133,6 +131,63 @@ impl GPSApp {
|
||||||
.expect("Couldn't get window");
|
.expect("Couldn't get window");
|
||||||
status_bar.push(status_bar.context_id("Description"), "GPS is ready");
|
status_bar.push(status_bar.context_id("Description"), "GPS is ready");
|
||||||
|
|
||||||
|
let action = gio::SimpleAction::new("open", None);
|
||||||
|
let app_weak = self.downgrade();
|
||||||
|
// Add a dialog to open the graph
|
||||||
|
action.connect_activate(glib::clone!(@weak window => move |_, _| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
let file_chooser = FileChooserDialog::new(
|
||||||
|
Some("Open File"),
|
||||||
|
Some(&window),
|
||||||
|
FileChooserAction::Open,
|
||||||
|
&[("Open", ResponseType::Ok), ("Cancel", ResponseType::Cancel)],
|
||||||
|
);
|
||||||
|
file_chooser.connect_response(move |d: &FileChooserDialog, response: ResponseType| {
|
||||||
|
if response == ResponseType::Ok {
|
||||||
|
let file = d.file().expect("Couldn't get file");
|
||||||
|
let filename = String::from(file.path().expect("Couldn't get file path").to_str().expect("unable to convert to string"));
|
||||||
|
println!("Open file {}", filename);
|
||||||
|
app.load_graph(&filename).expect("Unable to open file");
|
||||||
|
}
|
||||||
|
|
||||||
|
d.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
file_chooser.show();
|
||||||
|
|
||||||
|
}));
|
||||||
|
application.add_action(&action);
|
||||||
|
application.set_accels_for_action("app.open", &["<primary>o"]);
|
||||||
|
|
||||||
|
// Add a dialog to save the graph
|
||||||
|
let action = gio::SimpleAction::new("save_as", None);
|
||||||
|
let app_weak = self.downgrade();
|
||||||
|
action.connect_activate(glib::clone!(@weak window => move |_, _| {
|
||||||
|
let app = upgrade_weak!(app_weak);
|
||||||
|
let file_chooser = FileChooserDialog::new(
|
||||||
|
Some("Save File"),
|
||||||
|
Some(&window),
|
||||||
|
FileChooserAction::Open,
|
||||||
|
&[("Save", ResponseType::Ok), ("Cancel", ResponseType::Cancel)],
|
||||||
|
);
|
||||||
|
file_chooser.connect_response(move |d: &FileChooserDialog, response: ResponseType| {
|
||||||
|
if response == ResponseType::Ok {
|
||||||
|
let file = d.file().expect("Couldn't get file");
|
||||||
|
let filename = String::from(file.path().expect("Couldn't get file path").to_str().expect("unable to convert to string"));
|
||||||
|
println!("Save file {}", filename);
|
||||||
|
app.save_graph(&filename).expect("Unable to save file");
|
||||||
|
}
|
||||||
|
|
||||||
|
d.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
file_chooser.show();
|
||||||
|
|
||||||
|
}));
|
||||||
|
|
||||||
|
application.add_action(&action);
|
||||||
|
application.set_accels_for_action("app.save", &["<primary>s"]);
|
||||||
|
|
||||||
let action = gio::SimpleAction::new("quit", None);
|
let action = gio::SimpleAction::new("quit", None);
|
||||||
action.connect_activate({
|
action.connect_activate({
|
||||||
let app = application.downgrade();
|
let app = application.downgrade();
|
||||||
|
@ -173,42 +228,17 @@ impl GPSApp {
|
||||||
let elements = Pipeline::elements_list().expect("Unable to obtain element's list");
|
let elements = Pipeline::elements_list().expect("Unable to obtain element's list");
|
||||||
pluginlist::display_plugin_list(&app, &elements);
|
pluginlist::display_plugin_list(&app, &elements);
|
||||||
}));
|
}));
|
||||||
// Create a dialog to open a file
|
|
||||||
let open_button: Button = self
|
let add_button: Button = self
|
||||||
.builder
|
.builder
|
||||||
.object("button-open-file")
|
.object("button-play")
|
||||||
.expect("Couldn't get app_button");
|
.expect("Couldn't get app_button");
|
||||||
let open_dialog: FileChooserDialog = self
|
let app_weak = self.downgrade();
|
||||||
.builder
|
add_button.connect_clicked(glib::clone!(@weak window => move |_| {
|
||||||
.object("dialog-open-file")
|
// entry.set_text("Clicked!");
|
||||||
.expect("Couldn't get window");
|
let _app = upgrade_weak!(app_weak);
|
||||||
open_button.connect_clicked(glib::clone!(@weak window => move |_| {
|
|
||||||
open_dialog.connect_response(|dialog, _| dialog.close());
|
|
||||||
open_dialog.add_buttons(&[
|
|
||||||
("Open", ResponseType::Ok),
|
|
||||||
("Cancel", ResponseType::Cancel)
|
|
||||||
]);
|
|
||||||
|
|
||||||
open_dialog.connect_response(|open_dialog, response| {
|
|
||||||
if response == ResponseType::Ok {
|
|
||||||
let file = open_dialog.file().expect("Couldn't get file");
|
|
||||||
println!("Files: {:?}", file);
|
|
||||||
}
|
|
||||||
open_dialog.close();
|
|
||||||
});
|
|
||||||
open_dialog.show();
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// let add_button: Button = self
|
|
||||||
// .builder
|
|
||||||
// .object("button-play")
|
|
||||||
// .expect("Couldn't get app_button");
|
|
||||||
// let app_weak = self.downgrade();
|
|
||||||
// add_button.connect_clicked(glib::clone!(@weak window => move |_| {
|
|
||||||
// // entry.set_text("Clicked!");
|
|
||||||
// let app = upgrade_weak!(app_weak);
|
|
||||||
|
|
||||||
// }));
|
|
||||||
let add_button: Button = self
|
let add_button: Button = self
|
||||||
.builder
|
.builder
|
||||||
.object("button-stop")
|
.object("button-stop")
|
||||||
|
@ -221,15 +251,15 @@ impl GPSApp {
|
||||||
let node_id = graph_view.get_next_node_id();
|
let node_id = graph_view.get_next_node_id();
|
||||||
let element_name = String::from("appsink");
|
let element_name = String::from("appsink");
|
||||||
let pads = Pipeline::get_pads(&element_name, false);
|
let pads = Pipeline::get_pads(&element_name, false);
|
||||||
graph_view.add_node(node_id, Node::new(node_id, &element_name, Pipeline::get_element_type(&element_name)), pads.0, pads.1);
|
graph_view.add_node_with_port(node_id, Node::new(node_id, &element_name, Pipeline::get_element_type(&element_name)), pads.0, pads.1);
|
||||||
let node_id = graph_view.get_next_node_id();
|
let node_id = graph_view.get_next_node_id();
|
||||||
let element_name = String::from("videotestsrc");
|
let element_name = String::from("videotestsrc");
|
||||||
let pads = Pipeline::get_pads(&element_name, false);
|
let pads = Pipeline::get_pads(&element_name, false);
|
||||||
graph_view.add_node(node_id, Node::new(node_id, &element_name, Pipeline::get_element_type(&element_name)), pads.0, pads.1);
|
graph_view.add_node_with_port(node_id, Node::new(node_id, &element_name, Pipeline::get_element_type(&element_name)), pads.0, pads.1);
|
||||||
let node_id = graph_view.get_next_node_id();
|
let node_id = graph_view.get_next_node_id();
|
||||||
let element_name = String::from("videoconvert");
|
let element_name = String::from("videoconvert");
|
||||||
let pads = Pipeline::get_pads(&element_name, false);
|
let pads = Pipeline::get_pads(&element_name, false);
|
||||||
graph_view.add_node(node_id, Node::new(node_id, &element_name, Pipeline::get_element_type(&element_name)), pads.0, pads.1);
|
graph_view.add_node_with_port(node_id, Node::new(node_id, &element_name, Pipeline::get_element_type(&element_name)), pads.0, pads.1);
|
||||||
|
|
||||||
}));
|
}));
|
||||||
let add_button: Button = self
|
let add_button: Button = self
|
||||||
|
@ -256,7 +286,7 @@ impl GPSApp {
|
||||||
let graph_view = self.graphview.borrow_mut();
|
let graph_view = self.graphview.borrow_mut();
|
||||||
let node_id = graph_view.next_node_id();
|
let node_id = graph_view.next_node_id();
|
||||||
let pads = Pipeline::get_pads(&element_name, false);
|
let pads = Pipeline::get_pads(&element_name, false);
|
||||||
graph_view.add_node(
|
graph_view.add_node_with_port(
|
||||||
node_id,
|
node_id,
|
||||||
Node::new(
|
Node::new(
|
||||||
node_id,
|
node_id,
|
||||||
|
@ -267,4 +297,17 @@ impl GPSApp {
|
||||||
pads.1,
|
pads.1,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn save_graph(&self, filename: &str) -> anyhow::Result<(), Box<dyn error::Error>> {
|
||||||
|
let graph_view = self.graphview.borrow_mut();
|
||||||
|
graph_view.render_xml(filename)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_graph(&self, filename: &str) -> anyhow::Result<(), Box<dyn error::Error>> {
|
||||||
|
let graph_view = self.graphview.borrow_mut();
|
||||||
|
graph_view.remove_all_nodes();
|
||||||
|
graph_view.load_xml(filename)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
34
src/gps.ui
34
src/gps.ui
|
@ -8,6 +8,16 @@
|
||||||
<attribute name="action">app.new-window</attribute>
|
<attribute name="action">app.new-window</attribute>
|
||||||
<attribute name="accel"><primary>n</attribute>
|
<attribute name="accel"><primary>n</attribute>
|
||||||
</item>
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes" comments="Primary menu entry that opens the graph">_Open</attribute>
|
||||||
|
<attribute name="action">app.open</attribute>
|
||||||
|
<attribute name="accel"><primary>n</attribute>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<attribute name="label" translatable="yes" comments="Primary menu entry that saves the graph">_Save As</attribute>
|
||||||
|
<attribute name="action">app.save_as</attribute>
|
||||||
|
<attribute name="accel"><primary>n</attribute>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<attribute name="label" translatable="yes" comments="Primary menu entry that opens the About dialog.">_About GstPipelineStudio</attribute>
|
<attribute name="label" translatable="yes" comments="Primary menu entry that opens the About dialog.">_About GstPipelineStudio</attribute>
|
||||||
<attribute name="action">app.about</attribute>
|
<attribute name="action">app.about</attribute>
|
||||||
|
@ -59,13 +69,17 @@
|
||||||
<property name="default-height">600</property>
|
<property name="default-height">600</property>
|
||||||
<child type="titlebar">
|
<child type="titlebar">
|
||||||
<object class="GtkHeaderBar" id="header-bar">
|
<object class="GtkHeaderBar" id="header-bar">
|
||||||
<property name="hexpand">0</property>
|
|
||||||
<property name="show-title-buttons">True</property>
|
|
||||||
<child type="start">
|
<child type="start">
|
||||||
<object class="GtkButton">
|
<object class="GtkMenuButton" id="gear_menu_button">
|
||||||
<property name="action-name">win.open</property>
|
<property name="valign">3</property>
|
||||||
<property name="label">Open</property>
|
<property name="focus-on-click">0</property>
|
||||||
</object>
|
<property name="popover">
|
||||||
|
<object class="GtkPopoverMenu" id="gear_menu">
|
||||||
|
<property name="menu-model">primary_menu</property>
|
||||||
|
</object>
|
||||||
|
</property>
|
||||||
|
<property name="icon-name">open-menu-symbolic</property>
|
||||||
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
@ -86,14 +100,6 @@
|
||||||
<property name="icon-name">list-add</property>
|
<property name="icon-name">list-add</property>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
|
||||||
<object class="GtkButton" id="button-open-file">
|
|
||||||
<property name="hexpand">1</property>
|
|
||||||
<property name="label">gtk-open</property>
|
|
||||||
<property name="receives-default">1</property>
|
|
||||||
<property name="icon-name">document-open-symbolic</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton" id="button-play">
|
<object class="GtkButton" id="button-play">
|
||||||
<property name="hexpand">1</property>
|
<property name="hexpand">1</property>
|
||||||
|
|
|
@ -18,7 +18,15 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
use super::{node::Node, port::Port, port::PortDirection};
|
extern crate xml;
|
||||||
|
use xml::reader::EventReader;
|
||||||
|
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 std::fs::File;
|
||||||
|
use std::io::BufReader;
|
||||||
|
|
||||||
use gtk::{
|
use gtk::{
|
||||||
glib::{self, clone},
|
glib::{self, clone},
|
||||||
|
@ -29,14 +37,15 @@ use gtk::{
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
|
|
||||||
use std::cell::RefMut;
|
use std::cell::RefMut;
|
||||||
use std::{cmp::Ordering, collections::HashMap};
|
use std::{cmp::Ordering, collections::HashMap, error};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NodeLink {
|
pub struct NodeLink {
|
||||||
|
pub id: u32,
|
||||||
pub node_from: u32,
|
pub node_from: u32,
|
||||||
pub node_to: u32,
|
pub node_to: u32,
|
||||||
pub port_from: u32,
|
pub port_from: u32,
|
||||||
pub port_to: u32,
|
pub port_to: u32,
|
||||||
|
pub active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
static GRAPHVIEW_STYLE: &str = include_str!("graphview.css");
|
static GRAPHVIEW_STYLE: &str = include_str!("graphview.css");
|
||||||
|
@ -54,7 +63,7 @@ mod imp {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct GraphView {
|
pub struct GraphView {
|
||||||
pub(super) nodes: RefCell<HashMap<u32, Node>>,
|
pub(super) nodes: RefCell<HashMap<u32, Node>>,
|
||||||
pub(super) links: RefCell<HashMap<u32, (NodeLink, bool)>>,
|
pub(super) links: RefCell<HashMap<u32, NodeLink>>,
|
||||||
pub(super) current_node_id: Cell<u32>,
|
pub(super) current_node_id: Cell<u32>,
|
||||||
pub(super) current_port_id: Cell<u32>,
|
pub(super) current_port_id: Cell<u32>,
|
||||||
pub(super) current_link_id: Cell<u32>,
|
pub(super) current_link_id: Cell<u32>,
|
||||||
|
@ -132,24 +141,27 @@ mod imp {
|
||||||
.expect("click event has no widget")
|
.expect("click event has no widget")
|
||||||
.dynamic_cast::<Self::Type>()
|
.dynamic_cast::<Self::Type>()
|
||||||
.expect("click event is not on the GraphView");
|
.expect("click event is not on the GraphView");
|
||||||
if let Some(target) = widget.pick(x, y, gtk::PickFlags::DEFAULT) {
|
|
||||||
if let Some(target) = target.ancestor(Port::static_type()) {
|
if let Some(target) = widget.pick(x, y, gtk::PickFlags::DEFAULT) {
|
||||||
let to_port = target.dynamic_cast::<Port>().expect("click event is not on the Port");
|
if let Some(target) = target.ancestor(Port::static_type()) {
|
||||||
if !widget.port_is_linked(&to_port) {
|
let to_port = target.dynamic_cast::<Port>().expect("click event is not on the Port");
|
||||||
let selected_port = widget.selected_port().to_owned();
|
if let None = widget.port_is_linked(to_port.id()) {
|
||||||
if let Some(from_port) = selected_port {
|
let selected_port = widget.selected_port().to_owned();
|
||||||
println!("Port {} is clicked at {}:{}", to_port.id(), x, y);
|
if let Some(from_port) = selected_port {
|
||||||
if widget.ports_compatible(&to_port) {
|
println!("Port {} is clicked at {}:{}", to_port.id(), x, y);
|
||||||
let from_node = from_port.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::<Node>().expect("Unable to cast to Node");
|
if widget.ports_compatible(&to_port) {
|
||||||
let to_node = to_port.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::<Node>().expect("Unable to cast to Node");
|
let from_node = from_port.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::<Node>().expect("Unable to cast to Node");
|
||||||
println!("add link");
|
let to_node = to_port.ancestor(Node::static_type()).expect("Unable to reach parent").dynamic_cast::<Node>().expect("Unable to cast to Node");
|
||||||
widget.add_link(widget.get_next_link_id(), NodeLink {
|
println!("add link");
|
||||||
node_from: from_node.id(),
|
widget.add_link(NodeLink {
|
||||||
node_to: to_node.id(),
|
id: widget.next_link_id(),
|
||||||
port_from: from_port.id(),
|
node_from: from_node.id(),
|
||||||
port_to: to_port.id()
|
node_to: to_node.id(),
|
||||||
}, true);
|
port_from: from_port.id(),
|
||||||
}
|
port_to: to_port.id(),
|
||||||
|
active: true
|
||||||
|
} );
|
||||||
|
}
|
||||||
widget.set_selected_port(None);
|
widget.set_selected_port(None);
|
||||||
} else {
|
} else {
|
||||||
println!("add selected port id");
|
println!("add selected port id");
|
||||||
|
@ -195,12 +207,12 @@ mod imp {
|
||||||
|
|
||||||
link_cr.set_line_width(1.5);
|
link_cr.set_line_width(1.5);
|
||||||
|
|
||||||
for (link, active) in self.links.borrow().values() {
|
for link in self.links.borrow().values() {
|
||||||
if let Some((from_x, from_y, to_x, to_y)) = self.get_link_coordinates(link) {
|
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);
|
//println!("from_x: {} from_y: {} to_x: {} to_y: {}", from_x, from_y, to_x, to_y);
|
||||||
|
|
||||||
// Use dashed line for inactive links, full line otherwise.
|
// Use dashed line for inactive links, full line otherwise.
|
||||||
if *active {
|
if link.active {
|
||||||
link_cr.set_dash(&[], 0.0);
|
link_cr.set_dash(&[], 0.0);
|
||||||
} else {
|
} else {
|
||||||
link_cr.set_dash(&[10.0, 5.0], 0.0);
|
link_cr.set_dash(&[10.0, 5.0], 0.0);
|
||||||
|
@ -225,13 +237,13 @@ mod imp {
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
/// `Some((from_x, from_y, to_x, to_y))` if all objects the links refers to exist as widgets.
|
/// `Some((from_x, from_y, to_x, to_y))` if all objects the links refers to exist as widgets.
|
||||||
fn get_link_coordinates(&self, link: &NodeLink) -> Option<(f64, f64, f64, f64)> {
|
fn link_coordinates(&self, link: &NodeLink) -> Option<(f64, f64, f64, f64)> {
|
||||||
let nodes = self.nodes.borrow();
|
let nodes = self.nodes.borrow();
|
||||||
|
|
||||||
// For some reason, gtk4::WidgetExt::translate_coordinates gives me incorrect values,
|
// For some reason, gtk4::WidgetExt::translate_coordinates gives me incorrect values,
|
||||||
// so we manually calculate the needed offsets here.
|
// so we manually calculate the needed offsets here.
|
||||||
|
|
||||||
let from_port = &nodes.get(&link.node_from)?.get_port(link.port_from)?;
|
let from_port = &nodes.get(&link.node_from)?.port(&link.port_from)?;
|
||||||
let gtk::Allocation {
|
let gtk::Allocation {
|
||||||
x: mut fx,
|
x: mut fx,
|
||||||
y: mut fy,
|
y: mut fy,
|
||||||
|
@ -246,7 +258,7 @@ mod imp {
|
||||||
fx += fnx + (fw / 2);
|
fx += fnx + (fw / 2);
|
||||||
fy += fny + (fh / 2);
|
fy += fny + (fh / 2);
|
||||||
|
|
||||||
let to_port = &nodes.get(&link.node_to)?.get_port(link.port_to)?;
|
let to_port = &nodes.get(&link.node_to)?.port(&link.port_to)?;
|
||||||
let gtk::Allocation {
|
let gtk::Allocation {
|
||||||
x: mut tx,
|
x: mut tx,
|
||||||
y: mut ty,
|
y: mut ty,
|
||||||
|
@ -284,21 +296,22 @@ impl GraphView {
|
||||||
glib::Object::new(&[]).expect("Failed to create GraphView")
|
glib::Object::new(&[]).expect("Failed to create GraphView")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_node(&self, id: u32, node: Node, input: u32, output: u32) {
|
pub fn add_node_with_port(&self, id: u32, node: Node, input: u32, output: u32) {
|
||||||
let private = imp::GraphView::from_instance(self);
|
let private = imp::GraphView::from_instance(self);
|
||||||
node.set_parent(self);
|
node.set_parent(self);
|
||||||
|
|
||||||
// Place widgets in colums of 3, growing down
|
// Place widgets in colums of 3, growing down
|
||||||
// let x = if let Some(node_type) = node_type {
|
let x = if let Some(node_type) = node.node_type() {
|
||||||
// match node_type {
|
match node_type {
|
||||||
// NodeType::Src => 20.0,
|
NodeType::Source => 20.0,
|
||||||
// NodeType::Transform => 420.0,
|
NodeType::Transform => 320.0,
|
||||||
// NodeType::Sink => 820.0,
|
NodeType::Sink => 620.0,
|
||||||
// }
|
_ => 20.0,
|
||||||
// } else {
|
}
|
||||||
// 420.0
|
} else {
|
||||||
// };
|
420.0
|
||||||
let x = 20.0;
|
};
|
||||||
|
|
||||||
let y = private
|
let y = private
|
||||||
.nodes
|
.nodes
|
||||||
.borrow()
|
.borrow()
|
||||||
|
@ -335,6 +348,10 @@ impl GraphView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_node(&self, id: u32, node: Node) {
|
||||||
|
self.add_node_with_port(id, node, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn remove_node(&self, id: u32) {
|
pub fn remove_node(&self, id: u32) {
|
||||||
let private = imp::GraphView::from_instance(self);
|
let private = imp::GraphView::from_instance(self);
|
||||||
let mut nodes = private.nodes.borrow_mut();
|
let mut nodes = private.nodes.borrow_mut();
|
||||||
|
@ -347,7 +364,7 @@ impl GraphView {
|
||||||
}
|
}
|
||||||
pub fn all_nodes(&self) -> Vec<Node> {
|
pub fn all_nodes(&self) -> Vec<Node> {
|
||||||
let private = imp::GraphView::from_instance(self);
|
let private = imp::GraphView::from_instance(self);
|
||||||
let nodes = private.nodes.borrow_mut();
|
let nodes = private.nodes.borrow();
|
||||||
let nodes_list: Vec<_> = nodes.iter().map(|(_, node)| node.clone()).collect();
|
let nodes_list: Vec<_> = nodes.iter().map(|(_, node)| node.clone()).collect();
|
||||||
nodes_list
|
nodes_list
|
||||||
}
|
}
|
||||||
|
@ -368,6 +385,17 @@ impl GraphView {
|
||||||
self.queue_draw();
|
self.queue_draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn node_is_linked(&self, node_id: u32) -> Option<u32> {
|
||||||
|
let private = imp::GraphView::from_instance(self);
|
||||||
|
for (key, link) in private.links.borrow().iter() {
|
||||||
|
if link.node_from == node_id || link.node_to == node_id {
|
||||||
|
return Some(*key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Port related methods
|
||||||
pub fn add_port(&self, node_id: u32, port_id: u32, port: Port) {
|
pub fn add_port(&self, node_id: u32, port_id: u32, port: Port) {
|
||||||
let private = imp::GraphView::from_instance(self);
|
let private = imp::GraphView::from_instance(self);
|
||||||
println!(
|
println!(
|
||||||
|
@ -392,18 +420,37 @@ impl GraphView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_link(&self, link_id: u32, link: NodeLink, active: bool) {
|
pub fn port_is_linked(&self, port_id: u32) -> Option<(u32, u32, u32)> {
|
||||||
|
let private = imp::GraphView::from_instance(self);
|
||||||
|
for (key, link) in private.links.borrow().iter() {
|
||||||
|
if link.port_from == port_id || link.port_to == port_id {
|
||||||
|
return Some((*key, link.node_from, link.port_from));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link related methods
|
||||||
|
|
||||||
|
pub fn all_links(&self) -> Vec<NodeLink> {
|
||||||
|
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) {
|
||||||
let private = imp::GraphView::from_instance(self);
|
let private = imp::GraphView::from_instance(self);
|
||||||
if !self.link_exists(&link) {
|
if !self.link_exists(&link) {
|
||||||
private.links.borrow_mut().insert(link_id, (link, active));
|
private.links.borrow_mut().insert(link.id, link);
|
||||||
self.queue_draw();
|
self.queue_draw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_link_state(&self, link_id: u32, active: bool) {
|
pub fn set_link_state(&self, link_id: u32, active: bool) {
|
||||||
let private = imp::GraphView::from_instance(self);
|
let private = imp::GraphView::from_instance(self);
|
||||||
if let Some((_, state)) = private.links.borrow_mut().get_mut(&link_id) {
|
if let Some(link) = private.links.borrow_mut().get_mut(&link_id) {
|
||||||
*state = active;
|
link.active = active;
|
||||||
self.queue_draw();
|
self.queue_draw();
|
||||||
} else {
|
} else {
|
||||||
warn!("Link state changed on unknown link (id={})", link_id);
|
warn!("Link state changed on unknown link (id={})", link_id);
|
||||||
|
@ -418,17 +465,6 @@ impl GraphView {
|
||||||
self.queue_draw();
|
self.queue_draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn node_is_linked(&self, node_id: u32) -> Option<u32> {
|
|
||||||
let private = imp::GraphView::from_instance(self);
|
|
||||||
let links = private.links.borrow_mut();
|
|
||||||
for (key, value) in &*links {
|
|
||||||
if value.0.node_from == node_id || value.0.node_to == node_id {
|
|
||||||
return Some(*key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the position of the specified node inside the graphview.
|
/// Get the position of the specified node inside the graphview.
|
||||||
///
|
///
|
||||||
/// Returns `None` if the node is not in the graphview.
|
/// Returns `None` if the node is not in the graphview.
|
||||||
|
@ -476,7 +512,7 @@ impl GraphView {
|
||||||
pub(super) fn link_exists(&self, new_link: &NodeLink) -> bool {
|
pub(super) fn link_exists(&self, new_link: &NodeLink) -> bool {
|
||||||
let private = imp::GraphView::from_instance(self);
|
let private = imp::GraphView::from_instance(self);
|
||||||
|
|
||||||
for (link, _active) in private.links.borrow().values() {
|
for link in private.links.borrow().values() {
|
||||||
if (new_link.port_from == link.port_from && new_link.port_to == link.port_to)
|
if (new_link.port_from == link.port_from && new_link.port_to == link.port_to)
|
||||||
|| (new_link.port_to == link.port_from && new_link.port_from == link.port_to)
|
|| (new_link.port_to == link.port_from && new_link.port_from == link.port_to)
|
||||||
{
|
{
|
||||||
|
@ -487,18 +523,6 @@ impl GraphView {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn port_is_linked(&self, port: &Port) -> bool {
|
|
||||||
let private = imp::GraphView::from_instance(self);
|
|
||||||
|
|
||||||
for (id, (link, _active)) in private.links.borrow().iter() {
|
|
||||||
if port.id() == link.port_from || port.id() == link.port_to {
|
|
||||||
println!("port {} is already linked {}", port.id(), id);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn ports_compatible(&self, to_port: &Port) -> bool {
|
pub(super) fn ports_compatible(&self, to_port: &Port) -> bool {
|
||||||
let current_port = self.selected_port().to_owned();
|
let current_port = self.selected_port().to_owned();
|
||||||
if let Some(from_port) = current_port {
|
if let Some(from_port) = current_port {
|
||||||
|
@ -539,7 +563,7 @@ impl GraphView {
|
||||||
private.current_port_id.get()
|
private.current_port_id.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_next_link_id(&self) -> u32 {
|
fn next_link_id(&self) -> u32 {
|
||||||
let private = imp::GraphView::from_instance(self);
|
let private = imp::GraphView::from_instance(self);
|
||||||
private
|
private
|
||||||
.current_link_id
|
.current_link_id
|
||||||
|
@ -556,6 +580,181 @@ impl GraphView {
|
||||||
let private = imp::GraphView::from_instance(self);
|
let private = imp::GraphView::from_instance(self);
|
||||||
private.port_selected.borrow_mut()
|
private.port_selected.borrow_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Render graph methods
|
||||||
|
pub fn render_xml(&self, filename: &str) -> anyhow::Result<(), Box<dyn error::Error>> {
|
||||||
|
let mut file = File::create(filename).unwrap();
|
||||||
|
let mut writer = EmitterConfig::new()
|
||||||
|
.perform_indent(true)
|
||||||
|
.create_writer(&mut file);
|
||||||
|
|
||||||
|
writer.write(XMLWEvent::start_element("Graph"))?;
|
||||||
|
|
||||||
|
//Get the nodes
|
||||||
|
let nodes = self.all_nodes();
|
||||||
|
for node in nodes {
|
||||||
|
writer.write(
|
||||||
|
XMLWEvent::start_element("Node")
|
||||||
|
.attr("name", &node.name())
|
||||||
|
.attr("id", &node.id().to_string())
|
||||||
|
.attr("type", &node.node_type().unwrap().to_string()),
|
||||||
|
)?;
|
||||||
|
for port in node.ports().values() {
|
||||||
|
writer.write(
|
||||||
|
XMLWEvent::start_element("Port")
|
||||||
|
.attr("name", &port.name())
|
||||||
|
.attr("id", &port.id().to_string())
|
||||||
|
.attr("direction", &port.direction().to_string()),
|
||||||
|
)?;
|
||||||
|
writer.write(XMLWEvent::end_element())?;
|
||||||
|
}
|
||||||
|
writer.write(XMLWEvent::end_element())?;
|
||||||
|
}
|
||||||
|
//Get the link and write it.
|
||||||
|
for link in self.all_links() {
|
||||||
|
writer.write(
|
||||||
|
XMLWEvent::start_element("Link")
|
||||||
|
.attr("id", &link.id.to_string())
|
||||||
|
.attr("node_from", &link.node_from.to_string())
|
||||||
|
.attr("node_to", &link.node_to.to_string())
|
||||||
|
.attr("port_from", &link.port_from.to_string())
|
||||||
|
.attr("port_to", &link.port_to.to_string())
|
||||||
|
.attr("active", &link.active.to_string()),
|
||||||
|
)?;
|
||||||
|
writer.write(XMLWEvent::end_element())?;
|
||||||
|
}
|
||||||
|
writer.write(XMLWEvent::end_element())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_xml(&self, filename: &str) -> anyhow::Result<(), Box<dyn error::Error>> {
|
||||||
|
let file = File::open(filename).unwrap();
|
||||||
|
let file = BufReader::new(file);
|
||||||
|
|
||||||
|
let parser = EventReader::new(file);
|
||||||
|
|
||||||
|
let mut current_node: Option<Node> = None;
|
||||||
|
let mut current_port: Option<Port> = None;
|
||||||
|
let mut current_link: Option<NodeLink> = None;
|
||||||
|
for e in parser {
|
||||||
|
match e {
|
||||||
|
Ok(XMLREvent::StartElement {
|
||||||
|
ref name,
|
||||||
|
ref attributes,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
println!("{}", name);
|
||||||
|
let mut attrs = HashMap::new();
|
||||||
|
attributes.iter().for_each(|a| {
|
||||||
|
attrs.insert(a.name.to_string(), a.value.to_string());
|
||||||
|
});
|
||||||
|
match name.to_string().as_str() {
|
||||||
|
"Graph" => {
|
||||||
|
println!("New graph detected");
|
||||||
|
}
|
||||||
|
"Node" => {
|
||||||
|
let id = attrs
|
||||||
|
.get::<String>(&String::from("id"))
|
||||||
|
.expect("Unable to find node id");
|
||||||
|
let name = attrs
|
||||||
|
.get::<String>(&String::from("name"))
|
||||||
|
.expect("Unable to find node name");
|
||||||
|
let node_type: &String = attrs
|
||||||
|
.get::<String>(&String::from("type"))
|
||||||
|
.expect("Unable to find node type");
|
||||||
|
|
||||||
|
current_node = Some(Node::new(
|
||||||
|
id.parse::<u32>().unwrap(),
|
||||||
|
name,
|
||||||
|
NodeType::from_str(node_type.as_str()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
"Port" => {
|
||||||
|
let id = attrs
|
||||||
|
.get::<String>(&String::from("id"))
|
||||||
|
.expect("Unable to find port id");
|
||||||
|
let name = attrs
|
||||||
|
.get::<String>(&String::from("name"))
|
||||||
|
.expect("Unable to find port name");
|
||||||
|
let direction: &String = attrs
|
||||||
|
.get::<String>(&String::from("direction"))
|
||||||
|
.expect("Unable to find port direction");
|
||||||
|
current_port = Some(Port::new(
|
||||||
|
id.parse::<u32>().unwrap(),
|
||||||
|
name,
|
||||||
|
PortDirection::from_str(direction),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
"Link" => {
|
||||||
|
let id = attrs
|
||||||
|
.get::<String>(&String::from("id"))
|
||||||
|
.expect("Unable to find link id");
|
||||||
|
let node_from = attrs
|
||||||
|
.get::<String>(&String::from("node_from"))
|
||||||
|
.expect("Unable to find link node_from");
|
||||||
|
let node_to = attrs
|
||||||
|
.get::<String>(&String::from("node_to"))
|
||||||
|
.expect("Unable to find link node_to");
|
||||||
|
let port_from = attrs
|
||||||
|
.get::<String>(&String::from("port_from"))
|
||||||
|
.expect("Unable to find link port_from");
|
||||||
|
let port_to = attrs
|
||||||
|
.get::<String>(&String::from("port_to"))
|
||||||
|
.expect("Unable to find link port_to");
|
||||||
|
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(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ => println!("name unknown: {}", name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(XMLREvent::EndElement { name }) => {
|
||||||
|
println!("closing {}", name);
|
||||||
|
match name.to_string().as_str() {
|
||||||
|
"Graph" => {
|
||||||
|
println!("Graph ended");
|
||||||
|
}
|
||||||
|
"Node" => {
|
||||||
|
if let Some(node) = current_node {
|
||||||
|
let id = node.id();
|
||||||
|
self.add_node(id, node);
|
||||||
|
}
|
||||||
|
current_node = None;
|
||||||
|
}
|
||||||
|
"Port" => {
|
||||||
|
if let Some(port) = current_port {
|
||||||
|
let node = current_node.clone();
|
||||||
|
node.expect("No current node, error...")
|
||||||
|
.add_port(port.id(), port);
|
||||||
|
}
|
||||||
|
current_port = None;
|
||||||
|
}
|
||||||
|
"Link" => {
|
||||||
|
if let Some(link) = current_link {
|
||||||
|
self.add_link(link);
|
||||||
|
}
|
||||||
|
current_link = None;
|
||||||
|
}
|
||||||
|
_ => println!("name unknown: {}", name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error: {}", e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for GraphView {
|
impl Default for GraphView {
|
||||||
|
|
|
@ -24,16 +24,38 @@ use gtk::subclass::prelude::*;
|
||||||
use super::Port;
|
use super::Port;
|
||||||
use super::PortDirection;
|
use super::PortDirection;
|
||||||
|
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, Ref, RefCell};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum NodeType {
|
pub enum NodeType {
|
||||||
Source,
|
Source,
|
||||||
Transform,
|
Transform,
|
||||||
Sink,
|
Sink,
|
||||||
|
All,
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for NodeType {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeType {
|
||||||
|
pub fn from_str(node_type_name: &str) -> NodeType {
|
||||||
|
match node_type_name {
|
||||||
|
"Source" => NodeType::Source,
|
||||||
|
"Transform" => NodeType::Transform,
|
||||||
|
"Sink" => NodeType::Sink,
|
||||||
|
"All" => NodeType::All,
|
||||||
|
_ => NodeType::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
use super::*;
|
use super::*;
|
||||||
use once_cell::unsync::OnceCell;
|
use once_cell::unsync::OnceCell;
|
||||||
|
@ -104,11 +126,14 @@ impl Node {
|
||||||
let private = imp::Node::from_instance(&res);
|
let private = imp::Node::from_instance(&res);
|
||||||
private.id.set(id).expect("Node id already set");
|
private.id.set(id).expect("Node id already set");
|
||||||
res.set_name(name);
|
res.set_name(name);
|
||||||
private.node_type.set(node_type);
|
private
|
||||||
|
.node_type
|
||||||
|
.set(node_type)
|
||||||
|
.expect("Node type is already set");
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_name(&self, name: &str) {
|
fn set_name(&self, name: &str) {
|
||||||
let self_ = imp::Node::from_instance(self);
|
let self_ = imp::Node::from_instance(self);
|
||||||
self_.label.set_text(name);
|
self_.label.set_text(name);
|
||||||
println!("{}", name);
|
println!("{}", name);
|
||||||
|
@ -130,14 +155,19 @@ impl Node {
|
||||||
.attach(&port, 1, private.num_ports_out.get() + 1, 1, 1);
|
.attach(&port, 1, private.num_ports_out.get() + 1, 1, 1);
|
||||||
private.num_ports_out.set(private.num_ports_out.get() + 1);
|
private.num_ports_out.set(private.num_ports_out.get() + 1);
|
||||||
}
|
}
|
||||||
|
_ => panic!("Port without direction"),
|
||||||
}
|
}
|
||||||
|
|
||||||
private.ports.borrow_mut().insert(id, port);
|
private.ports.borrow_mut().insert(id, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_port(&self, id: u32) -> Option<super::port::Port> {
|
pub fn ports(&self) -> Ref<HashMap<u32, Port>> {
|
||||||
let private = imp::Node::from_instance(self);
|
let private = imp::Node::from_instance(self);
|
||||||
private.ports.borrow_mut().get(&id).cloned()
|
private.ports.borrow()
|
||||||
|
}
|
||||||
|
pub fn port(&self, id: &u32) -> Option<super::port::Port> {
|
||||||
|
let private = imp::Node::from_instance(self);
|
||||||
|
private.ports.borrow().get(id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn remove_port(&self, id: u32) {
|
pub fn remove_port(&self, id: u32) {
|
||||||
|
@ -146,13 +176,31 @@ impl Node {
|
||||||
match port.direction() {
|
match port.direction() {
|
||||||
PortDirection::Input => private.num_ports_in.set(private.num_ports_in.get() - 1),
|
PortDirection::Input => private.num_ports_in.set(private.num_ports_in.get() - 1),
|
||||||
PortDirection::Output => private.num_ports_in.set(private.num_ports_out.get() - 1),
|
PortDirection::Output => private.num_ports_in.set(private.num_ports_out.get() - 1),
|
||||||
|
_ => panic!("Port without direction"),
|
||||||
}
|
}
|
||||||
|
|
||||||
port.unparent();
|
port.unparent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> u32 {
|
pub fn id(&self) -> u32 {
|
||||||
let private = imp::Node::from_instance(self);
|
let private = imp::Node::from_instance(self);
|
||||||
private.id.get().copied().expect("Node id is not set")
|
private.id.get().copied().expect("Node id is not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
let private = imp::Node::from_instance(self);
|
||||||
|
private.label.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();
|
||||||
|
unique_name.push_str(&self.id().to_string());
|
||||||
|
unique_name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn node_type(&self) -> Option<&NodeType> {
|
||||||
|
let private = imp::Node::from_instance(self);
|
||||||
|
private.node_type.get()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,31 @@ use gtk::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
subclass::prelude::*,
|
subclass::prelude::*,
|
||||||
};
|
};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum PortDirection {
|
pub enum PortDirection {
|
||||||
Input,
|
Input,
|
||||||
Output,
|
Output,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PortDirection {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
// or, alternatively:
|
||||||
|
// fmt::Debug::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PortDirection {
|
||||||
|
pub fn from_str(port_direction_name: &str) -> PortDirection {
|
||||||
|
match port_direction_name {
|
||||||
|
"Input" => PortDirection::Input,
|
||||||
|
"Output" => PortDirection::Output,
|
||||||
|
_ => PortDirection::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
|
@ -119,4 +139,13 @@ impl Port {
|
||||||
let private = imp::Port::from_instance(self);
|
let private = imp::Port::from_instance(self);
|
||||||
private.direction.get().expect("Port direction is not set")
|
private.direction.get().expect("Port direction is not set")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> String {
|
||||||
|
let private = imp::Port::from_instance(self);
|
||||||
|
private
|
||||||
|
.direction
|
||||||
|
.get()
|
||||||
|
.expect("direction is not set")
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue