mirror of
https://gitlab.freedesktop.org/pipewire/helvum
synced 2026-03-15 03:26:10 +08:00
view: Refactor view to have a manager View struct.
The view struct creates, manages and runs the view, and handles all communication with components outside of the view.
This commit is contained in:
@@ -19,7 +19,7 @@ pub enum MediaType {
|
|||||||
enum Item {
|
enum Item {
|
||||||
Node {
|
Node {
|
||||||
// Keep track of the widget to easily remove ports on it later.
|
// Keep track of the widget to easily remove ports on it later.
|
||||||
widget: view::Node,
|
// widget: view::Node,
|
||||||
// Keep track of the nodes media type to color ports on it.
|
// Keep track of the nodes media type to color ports on it.
|
||||||
media_type: Option<MediaType>,
|
media_type: Option<MediaType>,
|
||||||
},
|
},
|
||||||
@@ -43,7 +43,7 @@ enum Item {
|
|||||||
pub struct Controller {
|
pub struct Controller {
|
||||||
con: Rc<RefCell<PipewireConnection>>,
|
con: Rc<RefCell<PipewireConnection>>,
|
||||||
state: HashMap<u32, Item>,
|
state: HashMap<u32, Item>,
|
||||||
view: view::GraphView,
|
view: Rc<view::View>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Controller {
|
impl Controller {
|
||||||
@@ -55,7 +55,7 @@ impl Controller {
|
|||||||
/// The returned `Rc` will be the only strong reference kept to the controller, so dropping the `Rc`
|
/// The returned `Rc` will be the only strong reference kept to the controller, so dropping the `Rc`
|
||||||
/// will also drop the controller, unless the `Rc` is cloned outside of this function.
|
/// will also drop the controller, unless the `Rc` is cloned outside of this function.
|
||||||
pub(super) fn new(
|
pub(super) fn new(
|
||||||
view: view::GraphView,
|
view: Rc<view::View>,
|
||||||
con: Rc<RefCell<PipewireConnection>>,
|
con: Rc<RefCell<PipewireConnection>>,
|
||||||
) -> Rc<RefCell<Controller>> {
|
) -> Rc<RefCell<Controller>> {
|
||||||
let result = Rc::new(RefCell::new(Controller {
|
let result = Rc::new(RefCell::new(Controller {
|
||||||
@@ -107,21 +107,19 @@ impl Controller {
|
|||||||
fn add_node(&mut self, node: &GlobalObject<ForeignDict>) {
|
fn add_node(&mut self, node: &GlobalObject<ForeignDict>) {
|
||||||
info!("Adding node to graph: id {}", node.id);
|
info!("Adding node to graph: id {}", node.id);
|
||||||
|
|
||||||
// Update graph to contain the new node.
|
// Get the nicest possible name for the node, using a fallback chain of possible name attributes.
|
||||||
let node_widget = crate::view::Node::new(
|
let node_name = &node
|
||||||
&node
|
.props
|
||||||
.props
|
.as_ref()
|
||||||
.as_ref()
|
.map(|dict| {
|
||||||
.map(|dict| {
|
String::from(
|
||||||
String::from(
|
dict.get("node.nick")
|
||||||
dict.get("node.nick")
|
.or_else(|| dict.get("node.description"))
|
||||||
.or_else(|| dict.get("node.description"))
|
.or_else(|| dict.get("node.name"))
|
||||||
.or_else(|| dict.get("node.name"))
|
.unwrap_or_default(),
|
||||||
.unwrap_or_default(),
|
)
|
||||||
)
|
})
|
||||||
})
|
.unwrap_or_default();
|
||||||
.unwrap_or_default(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// FIXME: This relies on the node being passed to us by the pipwire server before its port.
|
// FIXME: This relies on the node being passed to us by the pipwire server before its port.
|
||||||
let media_type = node
|
let media_type = node
|
||||||
@@ -143,12 +141,12 @@ impl Controller {
|
|||||||
.flatten()
|
.flatten()
|
||||||
.flatten();
|
.flatten();
|
||||||
|
|
||||||
self.view.add_node(node.id, node_widget.clone());
|
self.view.add_node(node.id, node_name);
|
||||||
|
|
||||||
self.state.insert(
|
self.state.insert(
|
||||||
node.id,
|
node.id,
|
||||||
Item::Node {
|
Item::Node {
|
||||||
widget: node_widget,
|
// widget: node_widget,
|
||||||
media_type,
|
media_type,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -178,7 +176,8 @@ impl Controller {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_port = crate::view::port::Port::new(
|
self.view.add_port(
|
||||||
|
node_id,
|
||||||
port.id,
|
port.id,
|
||||||
&port_label,
|
&port_label,
|
||||||
if matches!(props.get("port.direction"), Some("in")) {
|
if matches!(props.get("port.direction"), Some("in")) {
|
||||||
@@ -189,8 +188,6 @@ impl Controller {
|
|||||||
media_type,
|
media_type,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.view.add_port_to_node(node_id, new_port.id, new_port);
|
|
||||||
|
|
||||||
// Save node_id so we can delete this port easily.
|
// Save node_id so we can delete this port easily.
|
||||||
self.state.insert(port.id, Item::Port { node_id });
|
self.state.insert(port.id, Item::Port { node_id });
|
||||||
}
|
}
|
||||||
@@ -246,9 +243,18 @@ impl Controller {
|
|||||||
fn global_remove(&mut self, id: u32) {
|
fn global_remove(&mut self, id: u32) {
|
||||||
if let Some(item) = self.state.remove(&id) {
|
if let Some(item) = self.state.remove(&id) {
|
||||||
match item {
|
match item {
|
||||||
Item::Node { .. } => self.remove_node(id),
|
Item::Node { .. } => {
|
||||||
Item::Port { node_id } => self.remove_port(id, node_id),
|
info!("Removing node from graph: id {}", id);
|
||||||
Item::Link => self.remove_link(id),
|
self.view.remove_node(id);
|
||||||
|
}
|
||||||
|
Item::Port { node_id } => {
|
||||||
|
info!("Removing port from graph: id {}, node_id: {}", id, node_id);
|
||||||
|
self.view.remove_port(id, node_id);
|
||||||
|
}
|
||||||
|
Item::Link => {
|
||||||
|
info!("Removing link from graph: id {}", id);
|
||||||
|
self.view.remove_link(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
warn!(
|
warn!(
|
||||||
@@ -257,24 +263,4 @@ impl Controller {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_node(&self, id: u32) {
|
|
||||||
info!("Removing node from graph: id {}", id);
|
|
||||||
|
|
||||||
self.view.remove_node(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_port(&self, id: u32, node_id: u32) {
|
|
||||||
info!("Removing port from graph: id {}, node_id: {}", id, node_id);
|
|
||||||
|
|
||||||
if let Some(Item::Node { widget, .. }) = self.state.get(&node_id) {
|
|
||||||
widget.remove_port(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_link(&self, id: u32) {
|
|
||||||
info!("Removing link from graph: id {}", id);
|
|
||||||
|
|
||||||
self.view.remove_link(id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
64
src/main.rs
64
src/main.rs
@@ -2,26 +2,9 @@ mod controller;
|
|||||||
mod pipewire_connection;
|
mod pipewire_connection;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
use gtk::glib::{self, clone};
|
use std::rc::Rc;
|
||||||
use gtk::prelude::*;
|
|
||||||
|
|
||||||
// FIXME: This should be in its own .css file.
|
use gtk::{glib, prelude::*};
|
||||||
static STYLE: &str = "
|
|
||||||
.audio {
|
|
||||||
background: rgb(50,100,240);
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.video {
|
|
||||||
background: rgb(200,200,0);
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
|
|
||||||
.midi {
|
|
||||||
background: rgb(200,0,50);
|
|
||||||
color: black;
|
|
||||||
}
|
|
||||||
";
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PipewireLink {
|
pub struct PipewireLink {
|
||||||
@@ -35,10 +18,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
env_logger::init();
|
env_logger::init();
|
||||||
gtk::init()?;
|
gtk::init()?;
|
||||||
|
|
||||||
let graphview = view::GraphView::new();
|
let view = Rc::new(view::View::new());
|
||||||
|
|
||||||
let pw_con = pipewire_connection::PipewireConnection::new()?;
|
let pw_con = pipewire_connection::PipewireConnection::new()?;
|
||||||
let _controller = controller::Controller::new(graphview.clone(), pw_con.clone());
|
let _controller = controller::Controller::new(view.clone(), pw_con.clone());
|
||||||
|
|
||||||
// Do an initial roundtrip before showing the view,
|
// Do an initial roundtrip before showing the view,
|
||||||
// so that the graph is already populated when the window opens.
|
// so that the graph is already populated when the window opens.
|
||||||
@@ -49,43 +31,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
Continue(true)
|
Continue(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
let app = gtk::Application::new(Some("org.freedesktop.ryuukyu.helvum"), Default::default())
|
view.run();
|
||||||
.expect("Application creation failed");
|
|
||||||
|
|
||||||
app.connect_startup(|_| {
|
|
||||||
// Load CSS from the STYLE variable.
|
|
||||||
let provider = gtk::CssProvider::new();
|
|
||||||
provider.load_from_data(STYLE.as_bytes());
|
|
||||||
gtk::StyleContext::add_provider_for_display(
|
|
||||||
>k::gdk::Display::get_default().expect("Error initializing gtk css provider."),
|
|
||||||
&provider,
|
|
||||||
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
app.connect_activate(move |app| {
|
|
||||||
let scrollwindow = gtk::ScrolledWindowBuilder::new().child(&graphview).build();
|
|
||||||
let window = gtk::ApplicationWindowBuilder::new()
|
|
||||||
.application(app)
|
|
||||||
.default_width(1280)
|
|
||||||
.default_height(720)
|
|
||||||
.title("Helvum - Pipewire Patchbay")
|
|
||||||
.child(&scrollwindow)
|
|
||||||
.build();
|
|
||||||
window
|
|
||||||
.get_settings()
|
|
||||||
.set_property_gtk_application_prefer_dark_theme(true);
|
|
||||||
window.show();
|
|
||||||
});
|
|
||||||
|
|
||||||
let quit = gtk::gio::SimpleAction::new("quit", None);
|
|
||||||
quit.connect_activate(clone!(@weak app => move |_, _| {
|
|
||||||
app.quit();
|
|
||||||
}));
|
|
||||||
app.set_accels_for_action("app.quit", &["<Control>Q"]);
|
|
||||||
app.add_action(&quit);
|
|
||||||
|
|
||||||
app.run(&std::env::args().collect::<Vec<_>>());
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ impl GraphView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_port_to_node(&self, node_id: u32, port_id: u32, port: crate::view::port::Port) {
|
pub fn add_port(&self, node_id: u32, port_id: u32, port: crate::view::port::Port) {
|
||||||
let private = imp::GraphView::from_instance(self);
|
let private = imp::GraphView::from_instance(self);
|
||||||
|
|
||||||
if let Some(node) = private.nodes.borrow_mut().get_mut(&node_id) {
|
if let Some(node) = private.nodes.borrow_mut().get_mut(&node_id) {
|
||||||
@@ -217,6 +217,14 @@ impl GraphView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn remove_port(&self, id: u32, node_id: u32) {
|
||||||
|
let private = imp::GraphView::from_instance(self);
|
||||||
|
let nodes = private.nodes.borrow();
|
||||||
|
if let Some(node) = nodes.get(&node_id) {
|
||||||
|
node.remove_port(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add a link to the graph.
|
/// Add a link to the graph.
|
||||||
///
|
///
|
||||||
/// `add_link` takes three arguments: `link_id` is the id of the link as assigned by the pipewire server,
|
/// `add_link` takes three arguments: `link_id` is the id of the link as assigned by the pipewire server,
|
||||||
@@ -235,11 +243,11 @@ impl GraphView {
|
|||||||
self.queue_draw();
|
self.queue_draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_dragged(&self, widget: Option<gtk::Widget>) {
|
pub(super) fn set_dragged(&self, widget: Option<gtk::Widget>) {
|
||||||
*imp::GraphView::from_instance(self).dragged.borrow_mut() = widget;
|
*imp::GraphView::from_instance(self).dragged.borrow_mut() = widget;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn move_node(&self, node: >k::Widget, x: f32, y: f32) {
|
pub(super) fn move_node(&self, node: >k::Widget, x: f32, y: f32) {
|
||||||
let layout_manager = self
|
let layout_manager = self
|
||||||
.get_layout_manager()
|
.get_layout_manager()
|
||||||
.expect("Failed to get layout manager")
|
.expect("Failed to get layout manager")
|
||||||
|
|||||||
124
src/view/mod.rs
124
src/view/mod.rs
@@ -1,6 +1,130 @@
|
|||||||
|
//! The view presented to the user.
|
||||||
|
//!
|
||||||
|
//! This module contains gtk widgets and helper struct needed to present the graphical user interface.
|
||||||
|
|
||||||
mod graph_view;
|
mod graph_view;
|
||||||
mod node;
|
mod node;
|
||||||
pub mod port;
|
pub mod port;
|
||||||
|
|
||||||
pub use graph_view::GraphView;
|
pub use graph_view::GraphView;
|
||||||
pub use node::Node;
|
pub use node::Node;
|
||||||
|
|
||||||
|
use gtk::{
|
||||||
|
glib::{self, clone},
|
||||||
|
prelude::*,
|
||||||
|
};
|
||||||
|
use pipewire::port::Direction;
|
||||||
|
|
||||||
|
use crate::controller::MediaType;
|
||||||
|
|
||||||
|
// FIXME: This should be in its own .css file.
|
||||||
|
static STYLE: &str = "
|
||||||
|
.audio {
|
||||||
|
background: rgb(50,100,240);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video {
|
||||||
|
background: rgb(200,200,0);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.midi {
|
||||||
|
background: rgb(200,0,50);
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
";
|
||||||
|
|
||||||
|
/// Manager struct of the view.
|
||||||
|
///
|
||||||
|
/// This struct is responsible for setting up the UI, as well as communication with other components outside the view.
|
||||||
|
pub struct View {
|
||||||
|
app: gtk::Application,
|
||||||
|
graphview: GraphView,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl View {
|
||||||
|
/// Create the view.
|
||||||
|
/// This will set up the entire user interface and prepare it for being run.
|
||||||
|
///
|
||||||
|
/// To show and run the interface, its [`run`](`Self::run`) method will need to be called.
|
||||||
|
pub(super) fn new() -> Self {
|
||||||
|
let graphview = GraphView::new();
|
||||||
|
|
||||||
|
let app = gtk::Application::new(Some("org.freedesktop.ryuukyu.helvum"), Default::default())
|
||||||
|
.expect("Application creation failed");
|
||||||
|
|
||||||
|
app.connect_startup(|_| {
|
||||||
|
// Load CSS from the STYLE variable.
|
||||||
|
let provider = gtk::CssProvider::new();
|
||||||
|
provider.load_from_data(STYLE.as_bytes());
|
||||||
|
gtk::StyleContext::add_provider_for_display(
|
||||||
|
>k::gdk::Display::get_default().expect("Error initializing gtk css provider."),
|
||||||
|
&provider,
|
||||||
|
gtk::STYLE_PROVIDER_PRIORITY_APPLICATION,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.connect_activate(clone!(@strong graphview => move |app| {
|
||||||
|
let scrollwindow = gtk::ScrolledWindowBuilder::new().child(&graphview).build();
|
||||||
|
let window = gtk::ApplicationWindowBuilder::new()
|
||||||
|
.application(app)
|
||||||
|
.default_width(1280)
|
||||||
|
.default_height(720)
|
||||||
|
.title("Helvum - Pipewire Patchbay")
|
||||||
|
.child(&scrollwindow)
|
||||||
|
.build();
|
||||||
|
window
|
||||||
|
.get_settings()
|
||||||
|
.set_property_gtk_application_prefer_dark_theme(true);
|
||||||
|
window.show();
|
||||||
|
}));
|
||||||
|
|
||||||
|
let quit = gtk::gio::SimpleAction::new("quit", None);
|
||||||
|
quit.connect_activate(clone!(@weak app => move |_, _| {
|
||||||
|
app.quit();
|
||||||
|
}));
|
||||||
|
app.set_accels_for_action("app.quit", &["<Control>Q"]);
|
||||||
|
app.add_action(&quit);
|
||||||
|
|
||||||
|
Self { app, graphview }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the view. This will enter a gtk event loop and remain in that until the application is quit by the user.
|
||||||
|
pub(super) fn run(&self) -> i32 {
|
||||||
|
self.app.run(&std::env::args().collect::<Vec<_>>())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_node(&self, id: u32, name: &str) {
|
||||||
|
let node = crate::view::Node::new(name);
|
||||||
|
self.graphview.add_node(id, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_port(
|
||||||
|
&self,
|
||||||
|
node_id: u32,
|
||||||
|
port_id: u32,
|
||||||
|
port_name: &str,
|
||||||
|
port_direction: Direction,
|
||||||
|
port_media_type: Option<MediaType>,
|
||||||
|
) {
|
||||||
|
let port = port::Port::new(port_id, port_name, port_direction, port_media_type);
|
||||||
|
self.graphview.add_port(node_id, port_id, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_link(&self, id: u32, link: crate::PipewireLink) {
|
||||||
|
self.graphview.add_link(id, link);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_node(&self, id: u32) {
|
||||||
|
self.graphview.remove_node(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_port(&self, id: u32, node_id: u32) {
|
||||||
|
self.graphview.remove_port(id, node_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_link(&self, id: u32) {
|
||||||
|
self.graphview.remove_link(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user