From 2cc684d57c8ef5aaf5808a583400241a6760f56b Mon Sep 17 00:00:00 2001 From: "Tom A. Wagner" Date: Sat, 8 May 2021 13:16:13 +0200 Subject: [PATCH] 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. --- src/controller.rs | 4 +- src/main.rs | 11 +++-- src/view/mod.rs | 121 ++++++++++++++++++++++++++++------------------ 3 files changed, 83 insertions(+), 53 deletions(-) diff --git a/src/controller.rs b/src/controller.rs index f76d859..b510585 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -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. pub struct Controller { state: HashMap, - view: Rc, + view: view::View, } 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` /// will also drop the controller, unless the `Rc` is cloned outside of this function. pub(super) fn new( - view: Rc, + view: view::View, gtk_receiver: Receiver, ) -> Rc> { let result = Rc::new(RefCell::new(Controller { diff --git a/src/main.rs b/src/main.rs index 5c9c951..542edbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,10 +2,11 @@ mod controller; mod pipewire_connection; mod view; -use std::rc::Rc; - use controller::MediaType; -use gtk::glib::{self, PRIORITY_DEFAULT}; +use gtk::{ + glib::{self, PRIORITY_DEFAULT}, + prelude::*, +}; use pipewire::spa::Direction; /// Messages used GTK thread to command the pipewire thread. @@ -55,10 +56,10 @@ fn main() -> Result<(), Box> { let pw_thread = 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); - view.run(); + view.run(&std::env::args().collect::>()); pw_sender .send(GtkMessage::Terminate) diff --git a/src/view/mod.rs b/src/view/mod.rs index 17a70d2..a0252e4 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -10,8 +10,10 @@ pub use graph_view::GraphView; pub use node::Node; use gtk::{ + gio, glib::{self, clone}, prelude::*, + subclass::prelude::ObjectSubclassExt, }; use pipewire::spa::Direction; @@ -35,38 +37,34 @@ static STYLE: &str = " } "; -/// 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, -} +mod imp { + use super::*; + use gtk::{glib, subclass::prelude::*}; -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(); + #[derive(Default)] + pub struct View { + pub(super) graphview: GraphView, + } - let app = gtk::Application::new(Some("org.freedesktop.ryuukyu.helvum"), Default::default()) - .expect("Application creation failed"); + #[glib::object_subclass] + impl ObjectSubclass for View { + const NAME: &'static str = "HelvumApplication"; + type Type = super::View; + type ParentType = gtk::Application; - 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, - ); - }); + fn new() -> Self { + View { + graphview: GraphView::new(), + } + } + } - app.connect_activate(clone!(@strong graphview => move |app| { - let scrollwindow = gtk::ScrolledWindowBuilder::new().child(&graphview).build(); + impl ObjectImpl for View {} + impl ApplicationImpl for View { + fn activate(&self, app: &Self::Type) { + let scrollwindow = gtk::ScrolledWindowBuilder::new() + .child(&self.graphview) + .build(); let window = gtk::ApplicationWindowBuilder::new() .application(app) .default_width(1280) @@ -78,7 +76,38 @@ impl View { .get_settings() .set_property_gtk_application_prefer_dark_theme(true); 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( + >k::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) + @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 shortcut for quitting the application. let quit = gtk::gio::SimpleAction::new("quit", None); @@ -88,21 +117,13 @@ impl View { app.set_accels_for_action("app.quit", &["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::>()) + app } /// Add a new node to the view. pub fn add_node(&self, id: u32, name: &str) { - let node = crate::view::Node::new(name); - self.graphview.add_node(id, node); + let imp = imp::View::from_instance(self); + imp.graphview.add_node(id, crate::view::Node::new(name)); } /// Add a new port to the view. @@ -114,28 +135,36 @@ impl View { port_direction: Direction, port_media_type: Option, ) { - let port = port::Port::new(port_id, port_name, port_direction, port_media_type); - self.graphview.add_port(node_id, port_id, port) + let imp = imp::View::from_instance(self); + 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. 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. 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` /// from the view. 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. pub fn remove_link(&self, id: u32) { - self.graphview.remove_link(id); + let imp = imp::View::from_instance(self); + imp.graphview.remove_link(id); } }