diff --git a/TODO.md b/TODO.md index 0d650a6..1f85d84 100644 --- a/TODO.md +++ b/TODO.md @@ -62,6 +62,12 @@ - [x] Icon install - [x] Flatpak infrastructure +## 0.2.1 + +### app + +- [x] Can set pad properties to be used during the pipeline generation. See videomixer_alpha.xml + ## TODO ### Graphview diff --git a/data/screenshots/gps_screenshot.png b/data/screenshots/gps_screenshot.png index 516695d..161e2b2 100644 Binary files a/data/screenshots/gps_screenshot.png and b/data/screenshots/gps_screenshot.png differ diff --git a/graphs/compositor_tee.xml b/graphs/compositor_tee.xml index 10666d0..5629628 100644 --- a/graphs/compositor_tee.xml +++ b/graphs/compositor_tee.xml @@ -2,10 +2,10 @@ - + - + diff --git a/graphs/videomixer_alpha.xml b/graphs/videomixer_alpha.xml new file mode 100644 index 0000000..d11b67a --- /dev/null +++ b/graphs/videomixer_alpha.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/app.rs b/src/app.rs index 9d08fde..608025a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -288,6 +288,7 @@ impl GPSApp { application.add_action(&gio::SimpleAction::new("graph.check", None)); application.add_action(&gio::SimpleAction::new("port.delete", None)); + application.add_action(&gio::SimpleAction::new("port.properties", None)); application.add_action(&gio::SimpleAction::new("node.add-to-favorite", None)); application.add_action(&gio::SimpleAction::new("node.delete", None)); @@ -577,7 +578,7 @@ impl GPSApp { let description = GPS::ElementInfo::element_description(&node.name()).ok(); node.set_tooltip_markup(description.as_deref()); for port in node.all_ports(GM::PortDirection::All) { - let caps = PropertyExt::property(&port, "caps"); + let caps = PropertyExt::property(&port,"_caps"); GPS_TRACE!("caps={} for port id {}", caps.clone().unwrap_or_else(|| "caps unknown".to_string()), port.id()); port.set_tooltip_markup(caps.as_deref()); } @@ -598,7 +599,7 @@ impl GPSApp { GPS_INFO!("Port added port id={} to node id={} in graph id={}", port_id, node_id, graph_id); if let Some(node) = app.graphview.borrow().node(node_id) { if let Some(port) = node.port(port_id) { - let caps = PropertyExt::property(&port, "caps"); + let caps = PropertyExt::property(&port, "_caps"); GPS_TRACE!("caps={} for port id {}", caps.clone().unwrap_or_else(|| "caps unknown".to_string()), port.id()); port.set_tooltip_markup(caps.as_deref()); } @@ -688,13 +689,27 @@ impl GPSApp { let app_weak = app.downgrade(); app.connect_app_menu_action("port.delete", move |_, _| { let app = upgrade_weak!(app_weak); - GPS_DEBUG!("port.delete-link port {} node {}", port_id, node_id); + GPS_TRACE!("port.delete-link port {} node {}", port_id, node_id); app.graphview.borrow().remove_port(node_id, port_id); }); } else { app.disconnect_app_menu_action("port.delete"); } + let app_weak = app.downgrade(); + app.connect_app_menu_action("port.properties", move |_, _| { + let app = upgrade_weak!(app_weak); + GPS_TRACE!("port.properties port {} node {}", port_id, node_id); + let node = app.node(node_id); + let port = app.port(node_id, port_id); + GPSUI::properties::display_pad_properties( + &app, + &node.name(), + &port.name(), + node_id, + port_id, + ); + }); pop_menu.show(); None }, @@ -711,7 +726,6 @@ impl GPSApp { let app = upgrade_weak!(app_weak, None); let node_id = values[1].get::().expect("node id args[1]"); - let node = app.graphview.borrow().node(node_id).expect("Unable to find node with this ID"); let point = values[2].get::().expect("point in args[2]"); let pop_menu = app.app_pop_menu_at_position(&*app.graphview.borrow(), point.to_vec2().x() as f64, point.to_vec2().y() as f64); @@ -740,41 +754,27 @@ impl GPSApp { app.graphview.borrow_mut().remove_node(node_id); } ); - + let node = app.node(node_id); if let Some(input) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Input) { let app_weak = app.downgrade(); app.connect_app_menu_action("node.request-pad-input", move |_,_| { let app = upgrade_weak!(app_weak); - let graphview = app.graphview.borrow(); GPS_DEBUG!("node.request-pad-input {}", node_id); - let properties: HashMap = HashMap::from([ - ("caps".to_string(), input.caps().to_string())]); - let port = graphview.create_port("in", GM::PortDirection::Input, GM::PortPresence::Sometimes); - port.update_properties(&properties); - if let Some(mut node) = graphview.node(node_id) { - graphview.add_port_to_node(&mut node, port); - } + app.create_port_with_caps(node_id, GM::PortDirection::Input, GM::PortPresence::Sometimes, input.caps().to_string()) } ); } else { app.disconnect_app_menu_action("node.request-pad-input"); } + let node = app.node(node_id); if let Some(output) = GPS::ElementInfo::element_supports_new_pad_request(&node.name(), GM::PortDirection::Output) { let app_weak = app.downgrade(); app.connect_app_menu_action("node.request-pad-output", move |_,_| { let app = upgrade_weak!(app_weak); - let graphview = app.graphview.borrow(); GPS_DEBUG!("node.request-pad-output {}", node_id); - let properties: HashMap = HashMap::from([ - ("caps".to_string(), output.caps().to_string())]); - let port = graphview.create_port("out", GM::PortDirection::Output, GM::PortPresence::Sometimes); - port.update_properties(&properties); - if let Some(mut node) = graphview.node(node_id) { - graphview.add_port_to_node(&mut node, port); - } - + app.create_port_with_caps(node_id, GM::PortDirection::Output, GM::PortPresence::Sometimes, output.caps().to_string()); } ); } else { @@ -823,7 +823,7 @@ impl GPSApp { pub fn add_new_element(&self, element_name: &str) { let graphview = self.graphview.borrow(); let (inputs, outputs) = GPS::PadInfo::pads(element_name, false); - let mut node = + let node = graphview.create_node(element_name, GPS::ElementInfo::element_type(element_name)); let node_id = node.id(); if GPS::ElementInfo::element_is_uri_src_handler(element_name) { @@ -837,36 +837,95 @@ impl GPSApp { } }); } - + graphview.add_node(node); for input in inputs { - let properties: HashMap = - HashMap::from([("caps".to_string(), input.caps().to_string())]); - let port = - graphview.create_port("in", GM::PortDirection::Input, GM::PortPresence::Always); - port.update_properties(&properties); - graphview.add_port_to_node(&mut node, port); + self.create_port_with_caps( + node_id, + GM::PortDirection::Input, + GM::PortPresence::Always, + input.caps().to_string(), + ); } for output in outputs { - let properties: HashMap = - HashMap::from([("caps".to_string(), output.caps().to_string())]); - let port = - graphview.create_port("out", GM::PortDirection::Output, GM::PortPresence::Always); - port.update_properties(&properties); - graphview.add_port_to_node(&mut node, port); + self.create_port_with_caps( + node_id, + GM::PortDirection::Output, + GM::PortPresence::Always, + output.caps().to_string(), + ); } - graphview.add_node(node); + } + + fn node(&self, node_id: u32) -> GM::Node { + let node = self + .graphview + .borrow() + .node(node_id) + .unwrap_or_else(|| panic!("Unable to retrieve node with id {}", node_id)); + node + } + + fn port(&self, node_id: u32, port_id: u32) -> GM::Port { + let node = self.node(node_id); + node.port(port_id) + .unwrap_or_else(|| panic!("Unable to retrieve port with id {}", port_id)) } pub fn update_element_properties(&self, node_id: u32, properties: &HashMap) { - let node = self.graphview.borrow().node(node_id).unwrap(); + let node = self.node(node_id); node.update_properties(properties); } + pub fn update_pad_properties( + &self, + node_id: u32, + port_id: u32, + properties: &HashMap, + ) { + let port = self.port(node_id, port_id); + port.update_properties(properties); + } + pub fn element_property(&self, node_id: u32, property_name: &str) -> Option { - let node = self.graphview.borrow().node(node_id).unwrap(); + let node = self.node(node_id); PropertyExt::property(&node, property_name) } + pub fn pad_properties(&self, node_id: u32, port_id: u32) -> HashMap { + let port = self.port(node_id, port_id); + let mut properties: HashMap = HashMap::new(); + for (name, value) in port.properties().iter() { + if !port.hidden_property(name) { + properties.insert(name.to_string(), value.to_string()); + } + } + properties + } + + fn create_port_with_caps( + &self, + node_id: u32, + direction: GM::PortDirection, + presence: GM::PortPresence, + caps: String, + ) { + let node = self.node(node_id); + let ports = node.all_ports(direction); + let port_name = match direction { + GM::PortDirection::Input => String::from("sink_"), + GM::PortDirection::Output => String::from("src_"), + _ => String::from("?"), + }; + let graphview = self.graphview.borrow(); + let port_name = format!("{}{}", port_name, ports.len()); + let port = graphview.create_port(&port_name, direction, presence); + let properties: HashMap = HashMap::from([("_caps".to_string(), caps)]); + port.update_properties(&properties); + if let Some(mut node) = graphview.node(node_id) { + graphview.add_port_to_node(&mut node, port); + } + } + fn clear_graph(&self) { let graph_view = self.graphview.borrow(); graph_view.clear(); diff --git a/src/gps/pipeline.rs b/src/gps/pipeline.rs index ad9cf7c..e076b1b 100644 --- a/src/gps/pipeline.rs +++ b/src/gps/pipeline.rs @@ -284,11 +284,20 @@ impl Pipeline { let unique_name = node.unique_name(); description.push_str(&format!("{} name={} ", node.name(), unique_name)); elements.insert(unique_name.clone(), unique_name.clone()); + // Node properties for (name, value) in node.properties().iter() { //This allow to have an index in front of a property such as an enum. - let value = value.split_once(':').unwrap_or((value, value)); if !node.hidden_property(name) { - description.push_str(&format!("{}={} ", name, value.1)); + description.push_str(&format!("{}={} ", name, value)); + } + } + //Port properties + let ports = node.all_ports(PortDirection::All); + for port in ports { + for (name, value) in port.properties().iter() { + if !port.hidden_property(name) { + description.push_str(&format!("{}::{}={} ", port.name(), name, value)); + } } } diff --git a/src/ui/gps.ui b/src/ui/gps.ui index 72c179d..fe94755 100644 --- a/src/ui/gps.ui +++ b/src/ui/gps.ui @@ -35,6 +35,10 @@
+ + _Properties + app.port.properties + _Delete app.port.delete @@ -77,7 +81,7 @@ - _Properties + _Properties app.node.properties <primary>n diff --git a/src/ui/properties.rs b/src/ui/properties.rs index d23209c..27c59bb 100644 --- a/src/ui/properties.rs +++ b/src/ui/properties.rs @@ -134,7 +134,7 @@ pub fn property_to_widget( let flags = param.flags_class(); for value in flags.values() { combo.append_text(&format!( - "{}:{}: {}", + "{}:{}:{}", value.value(), value.nick(), value.name() @@ -142,9 +142,8 @@ pub fn property_to_widget( } } if let Some(value) = app.element_property(node_id, property_name) { - let value = value.split_once(':').unwrap_or(("0", "")); //Retrieve the first value (index) from the property - combo.set_active(Some(value.0.parse::().unwrap_or(0))); + combo.set_active(Some(value.parse::().unwrap_or(0))); } else if (param.flags() & glib::ParamFlags::READABLE) == glib::ParamFlags::READABLE || (param.flags() & glib::ParamFlags::READWRITE) == glib::ParamFlags::READWRITE { @@ -155,12 +154,7 @@ pub fn property_to_widget( combo.connect_changed(move |c| { if let Some(text) = c.active_text() { - let text = text.to_string(); - let fields: Vec<&str> = text.split(':').collect(); - f( - c.widget_name().to_string(), - format!("{}:{}", fields[0], fields[1]), - ) + f(c.widget_name().to_string(), text.to_string()) } }); Some(combo.upcast::()) @@ -249,3 +243,104 @@ pub fn display_plugin_properties(app: &GPSApp, element_name: &str, node_id: u32) dialog.show(); } + +pub fn display_pad_properties( + app: &GPSApp, + element_name: &str, + port_name: &str, + node_id: u32, + port_id: u32, +) { + let dialog = gtk::Dialog::with_buttons( + Some(&format!("{} properties from {}", port_name, element_name,)), + Some(&app.window), + gtk::DialogFlags::MODAL, + &[("Close", gtk::ResponseType::Close)], + ); + + dialog.set_default_size(640, 480); + dialog.set_modal(true); + + let update_properties: Rc>> = + Rc::new(RefCell::new(HashMap::new())); + + let grid = gtk::Grid::new(); + grid.set_column_spacing(4); + grid.set_row_spacing(4); + grid.set_margin_bottom(12); + + let mut i = 0; + let properties = app.pad_properties(node_id, port_id); + for (name, value) in properties { + let property_name = gtk::Label::new(Some(&name)); + property_name.set_hexpand(true); + property_name.set_halign(gtk::Align::Start); + property_name.set_margin_start(4); + let property_value = gtk::Entry::new(); + property_value.set_width_request(150); + property_value.set_text(&value); + property_value.connect_changed( + glib::clone!(@weak property_name, @weak property_value, @strong update_properties=> move |_| { + update_properties.borrow_mut().insert(property_name.text().to_string(), property_value.text().to_string()); + }), + ); + grid.attach(&property_name, 0, i, 1, 1); + grid.attach(&property_value, 1, i, 1, 1); + i += 1; + } + + // Add a new property allowing to set pads property. + let label = gtk::Label::new(Some("Add a new Property")); + label.set_hexpand(true); + label.set_halign(gtk::Align::Start); + label.set_margin_start(4); + + let property_name = gtk::Entry::new(); + property_name.set_width_request(150); + let property_value = gtk::Entry::new(); + property_value.set_width_request(150); + + property_name.connect_changed( + glib::clone!(@weak property_name, @weak property_value, @strong update_properties=> move |_| { + update_properties.borrow_mut().insert(property_name.text().to_string(), property_value.text().to_string()); + }), + ); + + property_value.connect_changed( + glib::clone!(@weak property_name, @weak property_value, @strong update_properties=> move |_| { + update_properties.borrow_mut().insert(property_name.text().to_string(), property_value.text().to_string()); + }), + ); + grid.attach(&label, 0, i, 1, 1); + grid.attach(&property_name, 1, i, 1, 1); + grid.attach(&property_value, 2, i, 1, 1); + + // Add all specific properties from the given element + + let scrolledwindow = gtk::ScrolledWindow::builder() + .hexpand(true) + .vexpand(true) + .build(); + scrolledwindow.set_child(Some(&grid)); + let content_area = dialog.content_area(); + content_area.append(&scrolledwindow); + content_area.set_vexpand(true); + content_area.set_margin_start(10); + content_area.set_margin_end(10); + content_area.set_margin_top(10); + content_area.set_margin_bottom(10); + + let app_weak = app.downgrade(); + dialog.connect_response( + glib::clone!(@strong update_properties, @weak dialog => move |_,_| { + let app = upgrade_weak!(app_weak); + for p in update_properties.borrow().values() { + GPS_INFO!("updated properties {}", p); + } + app.update_pad_properties(node_id, port_id, &update_properties.borrow()); + dialog.close(); + }), + ); + + dialog.show(); +}