From af4051c3c2777156c6625f32ae311b296b88ade9 Mon Sep 17 00:00:00 2001 From: "Tom A. Wagner" Date: Thu, 20 Jul 2023 10:33:41 +0200 Subject: [PATCH] ui: Port to libadwaita This ports the application to libadwaita, enabling us to use the libadwaita stylesheet and widgets to better implement the Gnome Human Interface Guidelines. --- Cargo.lock | 34 +++++++++++++- Cargo.toml | 3 +- meson.build | 1 + src/application.rs | 50 +++++++++------------ src/graph_manager.rs | 2 +- src/main.rs | 2 +- src/pipewire_connection/mod.rs | 2 +- src/style.css | 2 +- src/ui/graph/graph_view.rs | 82 +++++----------------------------- src/ui/graph/link.rs | 2 +- src/ui/graph/node.rs | 2 +- src/ui/graph/port.rs | 4 +- src/ui/graph/zoomentry.rs | 22 ++++----- src/ui/mod.rs | 3 ++ src/ui/window.rs | 62 +++++++++++++++++++++++++ src/ui/window.ui | 34 ++++++++++++++ 16 files changed, 185 insertions(+), 122 deletions(-) create mode 100644 src/ui/window.rs create mode 100644 src/ui/window.ui diff --git a/Cargo.lock b/Cargo.lock index 9240fcc..382376a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -499,7 +499,7 @@ name = "helvum" version = "0.4.1" dependencies = [ "glib", - "gtk4", + "libadwaita", "log", "once_cell", "pipewire", @@ -527,6 +527,38 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "libadwaita" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06444f4ca05a60693da6e9e2b591bd40a298e65a118a8d5e830771718b3e0253" +dependencies = [ + "gdk-pixbuf", + "gdk4", + "gio", + "glib", + "gtk4", + "libadwaita-sys", + "libc", + "pango", +] + +[[package]] +name = "libadwaita-sys" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "021cfe3d1fcfa82411765a791f7e9b32f35dd98ce88d2e3fa10e7320f5cc8ce7" +dependencies = [ + "gdk4-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk4-sys", + "libc", + "pango-sys", + "system-deps", +] + [[package]] name = "libc" version = "0.2.147" diff --git a/Cargo.toml b/Cargo.toml index b655236..bc28296 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,10 @@ categories = ["gui", "multimedia"] [dependencies] pipewire = "0.7.1" -gtk = { version = "0.7", package = "gtk4" } +adw = { version = "0.5", package = "libadwaita" } glib = { version = "0.18", features = ["log"] } log = "0.4.11" once_cell = "1.7.2" + diff --git a/meson.build b/meson.build index 0ceaaa8..9136b00 100644 --- a/meson.build +++ b/meson.build @@ -12,6 +12,7 @@ base_id = 'org.pipewire.Helvum' dependency('glib-2.0', version: '>= 2.66') dependency('gtk4', version: '>= 4.4.0') +dependency('libadwaita-1') dependency('libpipewire-0.3') desktop_file_validate = find_program('desktop-file-validate', required: false) diff --git a/src/application.rs b/src/application.rs index a74191f..a4307b1 100644 --- a/src/application.rs +++ b/src/application.rs @@ -14,9 +14,10 @@ // // SPDX-License-Identifier: GPL-3.0-only -use gtk::{ +use adw::{ gio, glib::{self, clone, Receiver}, + gtk, prelude::*, subclass::prelude::*, }; @@ -29,11 +30,12 @@ static STYLE: &str = include_str!("style.css"); mod imp { use super::*; + use adw::subclass::prelude::AdwApplicationImpl; use once_cell::unsync::OnceCell; #[derive(Default)] pub struct Application { - pub(super) graphview: ui::graph::GraphView, + pub(super) window: ui::Window, pub(super) graph_manager: OnceCell, } @@ -41,7 +43,7 @@ mod imp { impl ObjectSubclass for Application { const NAME: &'static str = "HelvumApplication"; type Type = super::Application; - type ParentType = gtk::Application; + type ParentType = adw::Application; } impl ObjectImpl for Application {} @@ -49,36 +51,19 @@ mod imp { fn activate(&self) { let app = &*self.obj(); - let scrollwindow = gtk::ScrolledWindow::builder() - .child(&self.graphview) - .build(); - let headerbar = gtk::HeaderBar::new(); - let zoomentry = ui::graph::ZoomEntry::new(&self.graphview); - headerbar.pack_end(&zoomentry); + let graphview = self.window.graph(); - let window = gtk::ApplicationWindow::builder() - .application(app) - .default_width(1280) - .default_height(720) - .title("Helvum - Pipewire Patchbay") - .child(&scrollwindow) - .build(); - window - .settings() - .set_gtk_application_prefer_dark_theme(true); - window.set_titlebar(Some(&headerbar)); + self.window.set_application(Some(app)); let zoom_set_action = gio::SimpleAction::new("set-zoom", Some(&f64::static_variant_type())); - zoom_set_action.connect_activate( - clone!(@weak self.graphview as graphview => move|_, param| { - let zoom_factor = param.unwrap().get::().unwrap(); - graphview.set_zoom_factor(zoom_factor, None) - }), - ); - window.add_action(&zoom_set_action); + zoom_set_action.connect_activate(clone!(@weak graphview => move|_, param| { + let zoom_factor = param.unwrap().get::().unwrap(); + graphview.set_zoom_factor(zoom_factor, None) + })); + self.window.add_action(&zoom_set_action); - window.show(); + self.window.show(); } fn startup(&self) { @@ -95,11 +80,12 @@ mod imp { } } impl GtkApplicationImpl for Application {} + impl AdwApplicationImpl for Application {} } glib::wrapper! { pub struct Application(ObjectSubclass) - @extends gio::Application, gtk::Application, + @extends gio::Application, gtk::Application, adw::Application, @implements gio::ActionGroup, gio::ActionMap; } @@ -117,7 +103,11 @@ impl Application { let imp = app.imp(); imp.graph_manager - .set(GraphManager::new(&imp.graphview, pw_sender, gtk_receiver)) + .set(GraphManager::new( + &imp.window.graph(), + pw_sender, + gtk_receiver, + )) .expect("Should be able to set graph manager"); // Add shortcut for quitting the application. diff --git a/src/graph_manager.rs b/src/graph_manager.rs index da64b49..874d378 100644 --- a/src/graph_manager.rs +++ b/src/graph_manager.rs @@ -14,7 +14,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use gtk::{glib, prelude::*, subclass::prelude::*}; +use adw::{glib, prelude::*, subclass::prelude::*}; use pipewire::channel::Sender as PwSender; diff --git a/src/main.rs b/src/main.rs index fefd353..9e95930 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,7 @@ mod graph_manager; mod pipewire_connection; mod ui; -use gtk::prelude::*; +use adw::{gtk, prelude::*}; use pipewire::spa::{format::MediaType, Direction}; /// Messages sent by the GTK thread to notify the pipewire thread. diff --git a/src/pipewire_connection/mod.rs b/src/pipewire_connection/mod.rs index 2138582..cf7f294 100644 --- a/src/pipewire_connection/mod.rs +++ b/src/pipewire_connection/mod.rs @@ -18,7 +18,7 @@ mod state; use std::{cell::RefCell, collections::HashMap, rc::Rc}; -use gtk::glib::{self, clone}; +use adw::glib::{self, clone}; use log::{debug, info, warn}; use pipewire::{ link::{Link, LinkChangeMask, LinkInfo, LinkListener, LinkState}, diff --git a/src/style.css b/src/style.css index d7a7c7f..d36b1cd 100644 --- a/src/style.css +++ b/src/style.css @@ -36,5 +36,5 @@ } graphview { - background-color: @text_view_bg; + background-color: @view_bg_color; } \ No newline at end of file diff --git a/src/ui/graph/graph_view.rs b/src/ui/graph/graph_view.rs index 8197be5..a4efbf7 100644 --- a/src/ui/graph/graph_view.rs +++ b/src/ui/graph/graph_view.rs @@ -14,11 +14,14 @@ // // SPDX-License-Identifier: GPL-3.0-only -use gtk::{ - cairo, gio, +use adw::{ + gio, glib::{self, clone}, - graphene::{self, Point}, - gsk, + gtk::{ + self, cairo, + graphene::{self, Point}, + gsk, + }, prelude::*, subclass::prelude::*, }; @@ -36,11 +39,7 @@ mod imp { use std::cell::{Cell, RefCell}; use std::collections::{HashMap, HashSet}; - use gtk::{ - gdk::{self, RGBA}, - graphene::Rect, - gsk::ColorStop, - }; + use adw::gtk::gdk::{self}; use log::warn; use once_cell::sync::Lazy; use pipewire::spa::format::MediaType; @@ -115,7 +114,7 @@ mod imp { #[glib::object_subclass] impl ObjectSubclass for GraphView { - const NAME: &'static str = "GraphView"; + const NAME: &'static str = "HelvumGraphView"; type Type = super::GraphView; type ParentType = gtk::Widget; type Interfaces = (gtk::Scrollable,); @@ -224,7 +223,7 @@ mod imp { let widget = &*self.obj(); let alloc = widget.allocation(); - self.snapshot_background(widget, snapshot); + // self.snapshot_background(widget, snapshot); // Draw all visible children self.nodes @@ -462,67 +461,6 @@ mod imp { self.obj().add_controller(zoom_gesture); } - fn snapshot_background(&self, widget: &super::GraphView, snapshot: >k::Snapshot) { - // Grid size and line width during neutral zoom (factor 1.0). - const NORMAL_GRID_SIZE: f32 = 20.0; - const NORMAL_GRID_LINE_WIDTH: f32 = 1.0; - - let zoom_factor = self.zoom_factor.get(); - let grid_size = NORMAL_GRID_SIZE * zoom_factor as f32; - let grid_line_width = NORMAL_GRID_LINE_WIDTH * zoom_factor as f32; - - let alloc = widget.allocation(); - - // We need to offset the lines between 0 and (excluding) `grid_size` so the grid moves with - // the rest of the view when scrolling. - // The offset is rounded so the grid is always aligned to a row of pixels. - let hadj = self - .hadjustment - .borrow() - .as_ref() - .map(|hadjustment| hadjustment.value()) - .unwrap_or(0.0); - let hoffset = (grid_size - (hadj as f32 % grid_size)) % grid_size; - let vadj = self - .vadjustment - .borrow() - .as_ref() - .map(|vadjustment| vadjustment.value()) - .unwrap_or(0.0); - let voffset = (grid_size - (vadj as f32 % grid_size)) % grid_size; - - snapshot.push_repeat( - &Rect::new(0.0, 0.0, alloc.width() as f32, alloc.height() as f32), - Some(&Rect::new(0.0, voffset, alloc.width() as f32, grid_size)), - ); - let grid_color = RGBA::new(0.137, 0.137, 0.137, 1.0); - snapshot.append_linear_gradient( - &Rect::new(0.0, voffset, alloc.width() as f32, grid_line_width), - &Point::new(0.0, 0.0), - &Point::new(alloc.width() as f32, 0.0), - &[ - ColorStop::new(0.0, grid_color), - ColorStop::new(1.0, grid_color), - ], - ); - snapshot.pop(); - - snapshot.push_repeat( - &Rect::new(0.0, 0.0, alloc.width() as f32, alloc.height() as f32), - Some(&Rect::new(hoffset, 0.0, grid_size, alloc.height() as f32)), - ); - snapshot.append_linear_gradient( - &Rect::new(hoffset, 0.0, grid_line_width, alloc.height() as f32), - &Point::new(0.0, 0.0), - &Point::new(0.0, alloc.height() as f32), - &[ - ColorStop::new(0.0, grid_color), - ColorStop::new(1.0, grid_color), - ], - ); - snapshot.pop(); - } - fn draw_link( &self, link_cr: &cairo::Context, diff --git a/src/ui/graph/link.rs b/src/ui/graph/link.rs index 2f9f7df..140c74c 100644 --- a/src/ui/graph/link.rs +++ b/src/ui/graph/link.rs @@ -14,7 +14,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use gtk::{glib, prelude::*, subclass::prelude::*}; +use adw::{glib, prelude::*, subclass::prelude::*}; use pipewire::spa::format::MediaType; use super::Port; diff --git a/src/ui/graph/node.rs b/src/ui/graph/node.rs index eef768c..46b3722 100644 --- a/src/ui/graph/node.rs +++ b/src/ui/graph/node.rs @@ -14,7 +14,7 @@ // // SPDX-License-Identifier: GPL-3.0-only -use gtk::{glib, prelude::*, subclass::prelude::*}; +use adw::{glib, gtk, prelude::*, subclass::prelude::*}; use pipewire::spa::Direction; use super::Port; diff --git a/src/ui/graph/port.rs b/src/ui/graph/port.rs index 47c15bc..17ac7c8 100644 --- a/src/ui/graph/port.rs +++ b/src/ui/graph/port.rs @@ -14,10 +14,10 @@ // // SPDX-License-Identifier: GPL-3.0-only -use gtk::{ +use adw::{ gdk, glib::{self, subclass::Signal}, - graphene, + gtk::{self, graphene}, prelude::*, subclass::prelude::*, }; diff --git a/src/ui/graph/zoomentry.rs b/src/ui/graph/zoomentry.rs index 719cdee..6c6a951 100644 --- a/src/ui/graph/zoomentry.rs +++ b/src/ui/graph/zoomentry.rs @@ -1,4 +1,4 @@ -use gtk::{glib, prelude::*, subclass::prelude::*}; +use adw::{glib, gtk, prelude::*, subclass::prelude::*}; use super::GraphView; @@ -127,15 +127,17 @@ mod imp { fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) { match pspec.name() { "zoomed-widget" => { - let widget: GraphView = value.get().unwrap(); - widget.connect_notify_local( - Some("zoom-factor"), - clone!(@weak self as imp => move |graphview, _| { - imp.update_zoom_factor_text(graphview.zoom_factor()); - }), - ); - self.update_zoom_factor_text(widget.zoom_factor()); - *self.graphview.borrow_mut() = Some(widget); + let widget: Option = value.get().unwrap(); + if let Some(ref widget) = widget { + widget.connect_notify_local( + Some("zoom-factor"), + clone!(@weak self as imp => move |graphview, _| { + imp.update_zoom_factor_text(graphview.zoom_factor()); + }), + ); + self.update_zoom_factor_text(widget.zoom_factor()); + } + *self.graphview.borrow_mut() = widget; } _ => unimplemented!(), } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 37f2896..b690f21 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -19,3 +19,6 @@ //! This module contains gtk widgets needed to present the graphical user interface. pub mod graph; + +mod window; +pub use window::*; diff --git a/src/ui/window.rs b/src/ui/window.rs new file mode 100644 index 0000000..5756ba1 --- /dev/null +++ b/src/ui/window.rs @@ -0,0 +1,62 @@ +use adw::{gio, gtk, prelude::*, subclass::prelude::*}; + +use super::graph; + +mod imp { + use super::*; + + #[derive(Default, gtk::CompositeTemplate, glib::Properties)] + #[properties(wrapper_type = super::Window)] + #[template(file = "window.ui")] + pub struct Window { + #[template_child] + pub header_bar: TemplateChild, + #[template_child] + #[property(type = graph::GraphView, get = |_| self.graph.clone())] + pub graph: TemplateChild, + } + + #[glib::object_subclass] + impl ObjectSubclass for Window { + const NAME: &'static str = "HelvumWindow"; + type Type = super::Window; + type ParentType = adw::ApplicationWindow; + + fn class_init(klass: &mut Self::Class) { + // Ensure custom types are registered + graph::GraphView::ensure_type(); + graph::ZoomEntry::ensure_type(); + + klass.bind_template(); + } + + fn instance_init(obj: &glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + #[glib::derived_properties] + impl ObjectImpl for Window {} + impl WidgetImpl for Window {} + impl WindowImpl for Window {} + impl ApplicationWindowImpl for Window {} + impl AdwApplicationWindowImpl for Window {} +} + +glib::wrapper! { + pub struct Window(ObjectSubclass) + @extends adw::ApplicationWindow, gtk::ApplicationWindow, gtk::Window, gtk::Widget, + @implements gio::ActionGroup, gio::ActionMap; +} + +impl Window { + pub fn new() -> Self { + glib::Object::new() + } +} + +impl Default for Window { + fn default() -> Self { + Self::new() + } +} diff --git a/src/ui/window.ui b/src/ui/window.ui new file mode 100644 index 0000000..f29fbe6 --- /dev/null +++ b/src/ui/window.ui @@ -0,0 +1,34 @@ + + + + + +