Turn struct View into a gtk::Application subclass.

This lets us keep multiple reference-counted copies easier, and lets us emit signals to the controller.
This commit is contained in:
Tom A. Wagner
2021-05-08 13:16:13 +02:00
parent b5071c09a0
commit 2cc684d57c
3 changed files with 83 additions and 53 deletions

View File

@@ -41,7 +41,7 @@ enum Item {
/// It also keeps and manages a state object that contains the current state of objects present on the remote. /// It also keeps and manages a state object that contains the current state of objects present on the remote.
pub struct Controller { pub struct Controller {
state: HashMap<u32, Item>, state: HashMap<u32, Item>,
view: Rc<view::View>, view: view::View,
} }
impl Controller { impl Controller {
@@ -53,7 +53,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: Rc<view::View>, view: view::View,
gtk_receiver: Receiver<PipewireMessage>, gtk_receiver: Receiver<PipewireMessage>,
) -> Rc<RefCell<Controller>> { ) -> Rc<RefCell<Controller>> {
let result = Rc::new(RefCell::new(Controller { let result = Rc::new(RefCell::new(Controller {

View File

@@ -2,10 +2,11 @@ mod controller;
mod pipewire_connection; mod pipewire_connection;
mod view; mod view;
use std::rc::Rc;
use controller::MediaType; use controller::MediaType;
use gtk::glib::{self, PRIORITY_DEFAULT}; use gtk::{
glib::{self, PRIORITY_DEFAULT},
prelude::*,
};
use pipewire::spa::Direction; use pipewire::spa::Direction;
/// Messages used GTK thread to command the pipewire thread. /// Messages used GTK thread to command the pipewire thread.
@@ -55,10 +56,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let pw_thread = let pw_thread =
std::thread::spawn(move || pipewire_connection::thread_main(gtk_sender, pw_receiver)); std::thread::spawn(move || pipewire_connection::thread_main(gtk_sender, pw_receiver));
let view = Rc::new(view::View::new()); let view = view::View::new();
let _controller = controller::Controller::new(view.clone(), gtk_receiver); let _controller = controller::Controller::new(view.clone(), gtk_receiver);
view.run(); view.run(&std::env::args().collect::<Vec<_>>());
pw_sender pw_sender
.send(GtkMessage::Terminate) .send(GtkMessage::Terminate)

View File

@@ -10,8 +10,10 @@ pub use graph_view::GraphView;
pub use node::Node; pub use node::Node;
use gtk::{ use gtk::{
gio,
glib::{self, clone}, glib::{self, clone},
prelude::*, prelude::*,
subclass::prelude::ObjectSubclassExt,
}; };
use pipewire::spa::Direction; use pipewire::spa::Direction;
@@ -35,38 +37,34 @@ static STYLE: &str = "
} }
"; ";
/// Manager struct of the view. mod imp {
/// use super::*;
/// This struct is responsible for setting up the UI, as well as communication with other components outside the view. use gtk::{glib, subclass::prelude::*};
#[derive(Default)]
pub struct View { pub struct View {
app: gtk::Application, pub(super) graphview: GraphView,
graphview: GraphView,
} }
impl View { #[glib::object_subclass]
/// Create the view. impl ObjectSubclass for View {
/// This will set up the entire user interface and prepare it for being run. const NAME: &'static str = "HelvumApplication";
/// type Type = super::View;
/// To show and run the interface, its [`run`](`Self::run`) method will need to be called. type ParentType = gtk::Application;
pub(super) fn new() -> Self {
let graphview = GraphView::new();
let app = gtk::Application::new(Some("org.freedesktop.ryuukyu.helvum"), Default::default()) fn new() -> Self {
.expect("Application creation failed"); View {
graphview: GraphView::new(),
}
}
}
app.connect_startup(|_| { impl ObjectImpl for View {}
// Load CSS from the STYLE variable. impl ApplicationImpl for View {
let provider = gtk::CssProvider::new(); fn activate(&self, app: &Self::Type) {
provider.load_from_data(STYLE.as_bytes()); let scrollwindow = gtk::ScrolledWindowBuilder::new()
gtk::StyleContext::add_provider_for_display( .child(&self.graphview)
&gtk::gdk::Display::get_default().expect("Error initializing gtk css provider."), .build();
&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() let window = gtk::ApplicationWindowBuilder::new()
.application(app) .application(app)
.default_width(1280) .default_width(1280)
@@ -78,7 +76,38 @@ impl View {
.get_settings() .get_settings()
.set_property_gtk_application_prefer_dark_theme(true); .set_property_gtk_application_prefer_dark_theme(true);
window.show(); window.show();
})); }
fn startup(&self, app: &Self::Type) {
self.parent_startup(app);
// 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,
);
}
}
impl GtkApplicationImpl for View {}
}
glib::wrapper! {
pub struct View(ObjectSubclass<imp::View>)
@extends gio::Application, gtk::Application,
@implements gio::ActionGroup, gio::ActionMap;
}
impl View {
/// Create the view.
/// This will set up the entire user interface and prepare it for being run.
pub(super) fn new() -> Self {
let app: View = glib::Object::new(&[("application-id", &"org.freedesktop.ryuukyu.helvum")])
.expect("Failed to create new Application");
// Add <Control-Q> shortcut for quitting the application. // Add <Control-Q> shortcut for quitting the application.
let quit = gtk::gio::SimpleAction::new("quit", None); let quit = gtk::gio::SimpleAction::new("quit", None);
@@ -88,21 +117,13 @@ impl View {
app.set_accels_for_action("app.quit", &["<Control>Q"]); app.set_accels_for_action("app.quit", &["<Control>Q"]);
app.add_action(&quit); app.add_action(&quit);
Self { app, graphview } app
}
/// 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<_>>())
} }
/// Add a new node to the view. /// Add a new node to the view.
pub fn add_node(&self, id: u32, name: &str) { pub fn add_node(&self, id: u32, name: &str) {
let node = crate::view::Node::new(name); let imp = imp::View::from_instance(self);
self.graphview.add_node(id, node); imp.graphview.add_node(id, crate::view::Node::new(name));
} }
/// Add a new port to the view. /// Add a new port to the view.
@@ -114,28 +135,36 @@ impl View {
port_direction: Direction, port_direction: Direction,
port_media_type: Option<MediaType>, port_media_type: Option<MediaType>,
) { ) {
let port = port::Port::new(port_id, port_name, port_direction, port_media_type); let imp = imp::View::from_instance(self);
self.graphview.add_port(node_id, port_id, port) imp.graphview.add_port(
node_id,
port_id,
port::Port::new(port_id, port_name, port_direction, port_media_type),
);
} }
/// Add a new link to the view. /// Add a new link to the view.
pub fn add_link(&self, id: u32, link: crate::PipewireLink) { pub fn add_link(&self, id: u32, link: crate::PipewireLink) {
self.graphview.add_link(id, link); let imp = imp::View::from_instance(self);
imp.graphview.add_link(id, link);
} }
/// Remove the node with the specified id from the view. /// Remove the node with the specified id from the view.
pub fn remove_node(&self, id: u32) { pub fn remove_node(&self, id: u32) {
self.graphview.remove_node(id); let imp = imp::View::from_instance(self);
imp.graphview.remove_node(id);
} }
/// Remove the port with the id `id` from the node with the id `node_id` /// Remove the port with the id `id` from the node with the id `node_id`
/// from the view. /// from the view.
pub fn remove_port(&self, id: u32, node_id: u32) { pub fn remove_port(&self, id: u32, node_id: u32) {
self.graphview.remove_port(id, node_id); let imp = imp::View::from_instance(self);
imp.graphview.remove_port(id, node_id);
} }
/// Remove the link with the specified id from the view. /// Remove the link with the specified id from the view.
pub fn remove_link(&self, id: u32) { pub fn remove_link(&self, id: u32) {
self.graphview.remove_link(id); let imp = imp::View::from_instance(self);
imp.graphview.remove_link(id);
} }
} }