graphview: save position as a hidden property

- Save/load node position in XML file
- Introduce a graph ID
This commit is contained in:
Stéphane Cerveau 2022-01-12 18:42:29 +01:00
parent 1ac03c16ae
commit e9b905230a
2 changed files with 85 additions and 6 deletions

View file

@ -56,6 +56,7 @@ mod imp {
#[derive(Default)] #[derive(Default)]
pub struct GraphView { pub struct GraphView {
pub(super) id: Cell<u32>,
pub(super) nodes: RefCell<HashMap<u32, Node>>, pub(super) nodes: RefCell<HashMap<u32, Node>>,
pub(super) links: RefCell<HashMap<u32, Link>>, pub(super) links: RefCell<HashMap<u32, Link>>,
pub(super) current_node_id: Cell<u32>, pub(super) current_node_id: Cell<u32>,
@ -126,6 +127,18 @@ mod imp {
} }
), ),
); );
drag_controller.connect_drag_end(
clone!(@strong drag_state => move |drag_controller, _x, _y| {
let widget = drag_controller
.widget()
.expect("drag-end event has no widget")
.dynamic_cast::<Self::Type>()
.expect("drag-end event is not on the GraphView");
widget.graph_updated();
}
),
);
obj.add_controller(&drag_controller); obj.add_controller(&drag_controller);
let gesture = gtk::GestureClick::new(); let gesture = gtk::GestureClick::new();
@ -252,6 +265,13 @@ mod imp {
<()>::static_type().into(), <()>::static_type().into(),
) )
.build(), .build(),
Signal::builder(
"graph-updated",
// returns graph ID
&[u32::static_type().into()],
<()>::static_type().into(),
)
.build(),
] ]
}); });
SIGNALS.as_ref() SIGNALS.as_ref()
@ -374,6 +394,16 @@ impl GraphView {
); );
glib::Object::new(&[]).expect("Failed to create GraphView") glib::Object::new(&[]).expect("Failed to create GraphView")
} }
pub fn set_id(&self, id: u32) {
let private = imp::GraphView::from_instance(self);
private.id.set(id)
}
fn graph_updated(&self) {
let private = imp::GraphView::from_instance(self);
self.emit_by_name("graph-updated", &[&private.id.get()])
.expect("unable to send signal");
}
pub fn add_node_with_port(&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);
@ -423,6 +453,7 @@ impl GraphView {
let port_id = self.next_port_id(); let port_id = self.next_port_id();
self.add_port(id, port_id, "out", PortDirection::Output); self.add_port(id, port_id, "out", PortDirection::Output);
} }
self.graph_updated();
} }
pub fn add_node(&self, id: u32, node: Node) { pub fn add_node(&self, id: u32, node: Node) {
@ -544,6 +575,7 @@ impl GraphView {
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); private.links.borrow_mut().insert(link.id, link);
self.graph_updated();
self.queue_draw(); self.queue_draw();
} }
} }
@ -772,12 +804,14 @@ impl GraphView {
} }
pub fn render_xml(&self, filename: &str) -> anyhow::Result<()> { pub fn render_xml(&self, filename: &str) -> anyhow::Result<()> {
let private = imp::GraphView::from_instance(self);
let mut file = File::create(filename).unwrap(); let mut file = File::create(filename).unwrap();
let mut writer = EmitterConfig::new() let mut writer = EmitterConfig::new()
.perform_indent(true) .perform_indent(true)
.create_writer(&mut file); .create_writer(&mut file);
writer.write(XMLWEvent::start_element("Graph"))?; writer
.write(XMLWEvent::start_element("Graph").attr("id", &private.id.get().to_string()))?;
//Get the nodes //Get the nodes
let nodes = self.all_nodes(NodeType::All); let nodes = self.all_nodes(NodeType::All);
@ -806,6 +840,20 @@ impl GraphView {
)?; )?;
writer.write(XMLWEvent::end_element())?; writer.write(XMLWEvent::end_element())?;
} }
if let Some(position) = self.node_position(&node.upcast()) {
writer.write(
XMLWEvent::start_element("Property")
.attr("name", "_pos_x")
.attr("value", &position.0.to_string()),
)?;
writer.write(XMLWEvent::end_element())?;
writer.write(
XMLWEvent::start_element("Property")
.attr("name", "_pos_y")
.attr("value", &position.1.to_string()),
)?;
writer.write(XMLWEvent::end_element())?;
}
writer.write(XMLWEvent::end_element())?; writer.write(XMLWEvent::end_element())?;
} }
//Get the link and write it. //Get the link and write it.
@ -826,7 +874,7 @@ impl GraphView {
} }
pub fn load_xml(&self, filename: &str) -> anyhow::Result<()> { pub fn load_xml(&self, filename: &str) -> anyhow::Result<()> {
let file = File::open(filename).unwrap(); let file = File::open(filename)?;
let file = BufReader::new(file); let file = BufReader::new(file);
let parser = EventReader::new(file); let parser = EventReader::new(file);
@ -849,6 +897,9 @@ impl GraphView {
match name.to_string().as_str() { match name.to_string().as_str() {
"Graph" => { "Graph" => {
println!("New graph detected"); println!("New graph detected");
if let Some(id) = attrs.get::<String>(&String::from("id")) {
self.set_id(id.parse::<u32>().expect("id should be an u32"));
}
} }
"Node" => { "Node" => {
let id = attrs let id = attrs
@ -935,7 +986,21 @@ impl GraphView {
"Node" => { "Node" => {
if let Some(node) = current_node { if let Some(node) = current_node {
let id = node.id(); let id = node.id();
let mut pos_x = 0 as f32;
let mut pos_y = 0 as f32;
if let Some(value) = node.property("_pos_x") {
pos_x = value.parse::<f32>().unwrap();
}
if let Some(value) = node.property("_pos_y") {
pos_y = value.parse::<f32>().unwrap();
}
self.add_node(id, node); self.add_node(id, node);
if let Some(node) = self.node(&id) {
if pos_x != 0.0 || pos_y != 0.0 {
self.move_node(&node.upcast(), pos_x, pos_y);
}
}
self.update_current_node_id(id); self.update_current_node_id(id);
} }
current_node = None; current_node = None;

View file

@ -71,6 +71,7 @@ mod imp {
pub(super) ports: RefCell<HashMap<u32, Port>>, pub(super) ports: RefCell<HashMap<u32, Port>>,
pub(super) num_ports_in: Cell<i32>, pub(super) num_ports_in: Cell<i32>,
pub(super) num_ports_out: Cell<i32>, pub(super) num_ports_out: Cell<i32>,
// Properties are differnet from GObject properties
pub(super) properties: RefCell<HashMap<String, String>>, pub(super) properties: RefCell<HashMap<String, String>>,
pub(super) selected: Cell<bool>, pub(super) selected: Cell<bool>,
} }
@ -190,13 +191,18 @@ impl Node {
self_.description.set_text(description); self_.description.set_text(description);
println!("{}", description); println!("{}", description);
} }
pub fn hidden_property(&self, name: &str) -> bool {
name.starts_with('_')
}
fn update_description(&self) { fn update_description(&self) {
let self_ = imp::Node::from_instance(self); let self_ = imp::Node::from_instance(self);
let mut description = String::from(""); let mut description = String::from("");
for (name, value) in self_.properties.borrow().iter() { for (name, value) in self_.properties.borrow().iter() {
description.push_str(&format!("{}:{}", name, value)); if !self.hidden_property(name) {
description.push('\n'); description.push_str(&format!("{}:{}", name, value));
description.push('\n');
}
} }
self.set_description(&description); self.set_description(&description);
} }
@ -280,8 +286,8 @@ impl Node {
self.update_description(); self.update_description();
} }
pub fn update_node_properties(&self, new_properties: &HashMap<String, String>) { pub fn update_properties(&self, new_node_properties: &HashMap<String, String>) {
for (key, value) in new_properties { for (key, value) in new_node_properties {
self.add_property(key.clone(), value.clone()); self.add_property(key.clone(), value.clone());
} }
} }
@ -291,6 +297,14 @@ impl Node {
private.properties.borrow() private.properties.borrow()
} }
pub fn property(&self, name: &str) -> Option<String> {
let private = imp::Node::from_instance(self);
if let Some(property) = private.properties.borrow().get(name) {
return Some(property.clone());
}
None
}
pub fn toggle_selected(&self) { pub fn toggle_selected(&self) {
self.set_selected(!self.selected()); self.set_selected(!self.selected());
} }