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:
Tom A. Wagner
2021-03-30 19:57:05 +02:00
parent 48821be18d
commit 2cb155c5ee
4 changed files with 172 additions and 108 deletions

View File

@@ -19,7 +19,7 @@ pub enum MediaType {
enum Item {
Node {
// 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.
media_type: Option<MediaType>,
},
@@ -43,7 +43,7 @@ enum Item {
pub struct Controller {
con: Rc<RefCell<PipewireConnection>>,
state: HashMap<u32, Item>,
view: view::GraphView,
view: Rc<view::View>,
}
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`
/// will also drop the controller, unless the `Rc` is cloned outside of this function.
pub(super) fn new(
view: view::GraphView,
view: Rc<view::View>,
con: Rc<RefCell<PipewireConnection>>,
) -> Rc<RefCell<Controller>> {
let result = Rc::new(RefCell::new(Controller {
@@ -107,21 +107,19 @@ impl Controller {
fn add_node(&mut self, node: &GlobalObject<ForeignDict>) {
info!("Adding node to graph: id {}", node.id);
// Update graph to contain the new node.
let node_widget = crate::view::Node::new(
&node
.props
.as_ref()
.map(|dict| {
String::from(
dict.get("node.nick")
.or_else(|| dict.get("node.description"))
.or_else(|| dict.get("node.name"))
.unwrap_or_default(),
)
})
.unwrap_or_default(),
);
// Get the nicest possible name for the node, using a fallback chain of possible name attributes.
let node_name = &node
.props
.as_ref()
.map(|dict| {
String::from(
dict.get("node.nick")
.or_else(|| dict.get("node.description"))
.or_else(|| dict.get("node.name"))
.unwrap_or_default(),
)
})
.unwrap_or_default();
// FIXME: This relies on the node being passed to us by the pipwire server before its port.
let media_type = node
@@ -143,12 +141,12 @@ impl Controller {
.flatten()
.flatten();
self.view.add_node(node.id, node_widget.clone());
self.view.add_node(node.id, node_name);
self.state.insert(
node.id,
Item::Node {
widget: node_widget,
// widget: node_widget,
media_type,
},
);
@@ -178,7 +176,8 @@ impl Controller {
None
};
let new_port = crate::view::port::Port::new(
self.view.add_port(
node_id,
port.id,
&port_label,
if matches!(props.get("port.direction"), Some("in")) {
@@ -189,8 +188,6 @@ impl Controller {
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.
self.state.insert(port.id, Item::Port { node_id });
}
@@ -246,9 +243,18 @@ impl Controller {
fn global_remove(&mut self, id: u32) {
if let Some(item) = self.state.remove(&id) {
match item {
Item::Node { .. } => self.remove_node(id),
Item::Port { node_id } => self.remove_port(id, node_id),
Item::Link => self.remove_link(id),
Item::Node { .. } => {
info!("Removing node from graph: id {}", 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 {
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);
}
}

View File

@@ -2,26 +2,9 @@ mod controller;
mod pipewire_connection;
mod view;
use gtk::glib::{self, clone};
use gtk::prelude::*;
use std::rc::Rc;
// 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;
}
";
use gtk::{glib, prelude::*};
#[derive(Debug)]
pub struct PipewireLink {
@@ -35,10 +18,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::init();
gtk::init()?;
let graphview = view::GraphView::new();
let view = Rc::new(view::View::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,
// 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)
});
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(
&gtk::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<_>>());
view.run();
Ok(())
}

View File

@@ -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);
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_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();
}
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;
}
pub fn move_node(&self, node: &gtk::Widget, x: f32, y: f32) {
pub(super) fn move_node(&self, node: &gtk::Widget, x: f32, y: f32) {
let layout_manager = self
.get_layout_manager()
.expect("Failed to get layout manager")

View File

@@ -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 node;
pub mod port;
pub use graph_view::GraphView;
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(
&gtk::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);
}
}