From c3a6a44484c4f3db502d397dc3b82b753a9d228e Mon Sep 17 00:00:00 2001 From: Naveen Prashanth Date: Thu, 7 May 2026 08:01:35 +0530 Subject: [PATCH 1/4] blueprints ui --- build.rs | 35 ++++++++++++++++++++++++++++++ src/ui/graph/node.blp | 39 +++++++++++++++++++++++++++++++++ src/ui/graph/node.ui | 28 +++++++++++++----------- src/ui/graph/port.blp | 17 +++++++++++++++ src/ui/graph/port.rs | 8 +++++-- src/ui/graph/port.ui | 15 +++++++++---- src/ui/graph/zoomentry.blp | 24 +++++++++++++++++++++ src/ui/graph/zoomentry.ui | 10 +++++++-- src/ui/window.blp | 39 +++++++++++++++++++++++++++++++++ src/ui/window.rs | 44 ++++++++++++++++++++++++++++++++++---- src/ui/window.ui | 41 +++++++++++++---------------------- 11 files changed, 250 insertions(+), 50 deletions(-) create mode 100644 build.rs create mode 100644 src/ui/graph/node.blp create mode 100644 src/ui/graph/port.blp create mode 100644 src/ui/graph/zoomentry.blp create mode 100644 src/ui/window.blp diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..23f9ca3 --- /dev/null +++ b/build.rs @@ -0,0 +1,35 @@ +fn main() { + let blp_files = [ + "src/ui/window.blp", + "src/ui/graph/node.blp", + "src/ui/graph/port.blp", + "src/ui/graph/zoomentry.blp", + ]; + + for blp in blp_files { + let ui = blp.replace(".blp", ".ui"); + println!("cargo:rerun-if-changed={}", blp); + + let output = std::process::Command::new("blueprint-compiler") + .arg("compile") + .arg("--output") + .arg(&ui) + .arg(blp) + .output(); + + match output { + Ok(output) => { + if !output.status.success() { + let err = String::from_utf8_lossy(&output.stderr); + panic!("Failed to compile blueprint {}: {}", blp, err); + } + } + Err(e) => { + // If compiler is missing, we only fail if the .ui file doesn't exist + if !std::path::Path::new(&ui).exists() { + panic!("blueprint-compiler not found and {} is missing: {}", ui, e); + } + } + } + } +} diff --git a/src/ui/graph/node.blp b/src/ui/graph/node.blp new file mode 100644 index 0000000..6b18af2 --- /dev/null +++ b/src/ui/graph/node.blp @@ -0,0 +1,39 @@ +using Gtk 4.0; + +template HelvumNode : Widget { + styles ["card"] + + Gtk.Box { + orientation: vertical; + + Gtk.Box { + styles ["node-title"] + orientation: vertical; + spacing: 1; + + Gtk.Label node_name { + styles ["heading"] + wrap: true; + ellipsize: end; + lines: 2; + max-width-chars: 20; + } + + Gtk.Label media_name { + styles ["dim-label", "caption"] + visible: false; + wrap: true; + ellipsize: end; + lines: 2; + max-width-chars: 20; + } + } + + Gtk.Separator separator { + /* The node will show the seperator only once ports are added to it */ + visible: false; + } + + Gtk.Grid port_grid {} + } +} diff --git a/src/ui/graph/node.ui b/src/ui/graph/node.ui index 9a80c56..7ccbcf5 100644 --- a/src/ui/graph/node.ui +++ b/src/ui/graph/node.ui @@ -1,27 +1,32 @@ + - + \ No newline at end of file diff --git a/src/ui/graph/port.blp b/src/ui/graph/port.blp new file mode 100644 index 0000000..ec14aff --- /dev/null +++ b/src/ui/graph/port.blp @@ -0,0 +1,17 @@ +using Gtk 4.0; + +template HelvumPort : Widget { + hexpand: true; + + Gtk.Label label { + wrap: true; + ellipsize: end; + lines: 2; + max-width-chars: 20; + } + + Gtk.Box handle_container { + halign: center; + valign: center; + } +} diff --git a/src/ui/graph/port.rs b/src/ui/graph/port.rs index 64afe0a..3a19c7a 100644 --- a/src/ui/graph/port.rs +++ b/src/ui/graph/port.rs @@ -107,7 +107,8 @@ mod imp { #[template_child] pub(super) label: TemplateChild, #[template_child] - pub(super) handle: TemplateChild, + pub(super) handle_container: TemplateChild, + pub(super) handle: PortHandle, } impl Default for Port { @@ -117,7 +118,8 @@ mod imp { media_type: Cell::new(PortMediaType::Unknown), direction: Cell::new(PortDirection::Output), label: TemplateChild::default(), - handle: TemplateChild::default(), + handle_container: TemplateChild::default(), + handle: PortHandle::new(), } } } @@ -143,6 +145,8 @@ mod imp { fn constructed(&self) { self.parent_constructed(); + self.handle_container.append(&self.handle); + // Force left-to-right direction for the ports grid to avoid messed up UI when defaulting to right-to-left gtk::prelude::WidgetExt::set_direction(&*self.obj(), gtk::TextDirection::Ltr); diff --git a/src/ui/graph/port.ui b/src/ui/graph/port.ui index 13722ca..6bb414f 100644 --- a/src/ui/graph/port.ui +++ b/src/ui/graph/port.ui @@ -1,4 +1,9 @@ + - - + \ No newline at end of file diff --git a/src/ui/graph/zoomentry.blp b/src/ui/graph/zoomentry.blp new file mode 100644 index 0000000..4b7ad93 --- /dev/null +++ b/src/ui/graph/zoomentry.blp @@ -0,0 +1,24 @@ +using Gtk 4.0; + +template HelvumZoomEntry : Box { + spacing: 12; + + Gtk.Entry entry { + secondary-icon-name: "go-down-symbolic"; + input-purpose: digits; + max-width-chars: 5; + styles ["osd", "rounded"] + } + + Gtk.Button zoom_out_button { + icon-name: "zoom-out-symbolic"; + tooltip-text: "Zoom out"; + styles ["osd", "rounded"] + } + + Gtk.Button zoom_in_button { + icon-name: "zoom-in-symbolic"; + tooltip-text: "Zoom in"; + styles ["osd", "rounded"] + } +} diff --git a/src/ui/graph/zoomentry.ui b/src/ui/graph/zoomentry.ui index def1a6a..3f3cdae 100644 --- a/src/ui/graph/zoomentry.ui +++ b/src/ui/graph/zoomentry.ui @@ -1,11 +1,17 @@ + + - \ No newline at end of file diff --git a/src/ui/graph/port.blp b/src/ui/graph/port.blp index ec14aff..c57c79f 100644 --- a/src/ui/graph/port.blp +++ b/src/ui/graph/port.blp @@ -10,7 +10,7 @@ template HelvumPort : Widget { max-width-chars: 20; } - Gtk.Box handle_container { + Gtk.Box handle { halign: center; valign: center; } diff --git a/src/ui/graph/port.rs b/src/ui/graph/port.rs index 3a19c7a..dc2eb76 100644 --- a/src/ui/graph/port.rs +++ b/src/ui/graph/port.rs @@ -25,7 +25,6 @@ pub use imp::{PortDirection, PortMediaType}; use crate::PortId; use libspa::{param::format::MediaType, utils::Direction}; -use super::PortHandle; mod imp { use super::*; @@ -107,8 +106,7 @@ mod imp { #[template_child] pub(super) label: TemplateChild, #[template_child] - pub(super) handle_container: TemplateChild, - pub(super) handle: PortHandle, + pub(super) handle: TemplateChild, } impl Default for Port { @@ -118,8 +116,7 @@ mod imp { media_type: Cell::new(PortMediaType::Unknown), direction: Cell::new(PortDirection::Output), label: TemplateChild::default(), - handle_container: TemplateChild::default(), - handle: PortHandle::new(), + handle: TemplateChild::default(), } } } @@ -145,7 +142,8 @@ mod imp { fn constructed(&self) { self.parent_constructed(); - self.handle_container.append(&self.handle); + self.handle.add_css_class("port-handle"); + self.handle.set_size_request(14, 14); // Force left-to-right direction for the ports grid to avoid messed up UI when defaulting to right-to-left gtk::prelude::WidgetExt::set_direction(&*self.obj(), gtk::TextDirection::Ltr); diff --git a/src/ui/graph/port.ui b/src/ui/graph/port.ui deleted file mode 100644 index 6bb414f..0000000 --- a/src/ui/graph/port.ui +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/ui/graph/port_handle.rs b/src/ui/graph/port_handle.rs deleted file mode 100644 index 7eb3a9a..0000000 --- a/src/ui/graph/port_handle.rs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2021 Tom A. Wagner -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License version 3 as published by -// the Free Software Foundation. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// -// SPDX-License-Identifier: GPL-3.0-only - -use adw::{glib, gtk, prelude::*, subclass::prelude::*}; - -mod imp { - use super::*; - - #[derive(Default)] - pub struct PortHandle {} - - #[glib::object_subclass] - impl ObjectSubclass for PortHandle { - const NAME: &'static str = "HelvumPortHandle"; - type Type = super::PortHandle; - type ParentType = gtk::Widget; - - fn class_init(klass: &mut Self::Class) { - klass.set_css_name("port-handle"); - } - } - - impl ObjectImpl for PortHandle { - fn constructed(&self) { - self.parent_constructed(); - - let obj = &*self.obj(); - - obj.set_halign(gtk::Align::Center); - obj.set_valign(gtk::Align::Center); - } - } - - impl WidgetImpl for PortHandle { - fn request_mode(&self) -> gtk::SizeRequestMode { - gtk::SizeRequestMode::ConstantSize - } - - fn measure(&self, _orientation: gtk::Orientation, _for_size: i32) -> (i32, i32, i32, i32) { - (Self::HANDLE_SIZE, Self::HANDLE_SIZE, -1, -1) - } - } - - impl PortHandle { - pub const HANDLE_SIZE: i32 = 14; - } -} - -glib::wrapper! { - pub struct PortHandle(ObjectSubclass) - @extends gtk::Widget, @implements gtk::Accessible, gtk::Buildable, gtk::ConstraintTarget; -} - -impl PortHandle { - pub fn new() -> Self { - glib::Object::new() - } - - pub fn get_link_anchor(&self) -> gtk::graphene::Point { - gtk::graphene::Point::new( - imp::PortHandle::HANDLE_SIZE as f32 / 2.0, - imp::PortHandle::HANDLE_SIZE as f32 / 2.0, - ) - } -} - -impl Default for PortHandle { - fn default() -> Self { - Self::new() - } -} diff --git a/src/ui/graph/zoomentry.ui b/src/ui/graph/zoomentry.ui deleted file mode 100644 index 3f3cdae..0000000 --- a/src/ui/graph/zoomentry.ui +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/ui/window.ui b/src/ui/window.ui deleted file mode 100644 index 744ccd6..0000000 --- a/src/ui/window.ui +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - _About Helvum - app.about - - - - \ No newline at end of file From d166b8a0378755012f42b3927a981ea0c6a7568f Mon Sep 17 00:00:00 2001 From: Naveen Prashanth Date: Thu, 7 May 2026 11:40:06 +0530 Subject: [PATCH 3/4] use blp fully --- build.rs | 47 +++++++++++++++++++++++-------------- src/main.rs | 4 ++++ src/ui/graph/node.rs | 2 +- src/ui/graph/port.rs | 2 +- src/ui/graph/zoomentry.rs | 3 ++- src/ui/helvum.gresource.xml | 9 +++++++ src/ui/window.rs | 2 +- 7 files changed, 47 insertions(+), 22 deletions(-) create mode 100644 src/ui/helvum.gresource.xml diff --git a/build.rs b/build.rs index 1a8a02b..aae8a39 100644 --- a/build.rs +++ b/build.rs @@ -8,30 +8,41 @@ fn main() { println!("cargo:warning=Helvum build script starting..."); + let root = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let out_dir = std::env::var("OUT_DIR").unwrap(); + for blp in blp_files { - let ui = blp.replace(".blp", ".ui"); - println!("cargo:rerun-if-changed={}", blp); + let blp_abs = std::path::Path::new(&root).join(blp); + let ui_abs = blp_abs.with_extension("ui"); + + println!("cargo:rerun-if-changed={}", blp_abs.display()); let output = std::process::Command::new("blueprint-compiler") .arg("compile") .arg("--output") - .arg(&ui) - .arg(blp) - .output(); + .arg(&ui_abs) + .arg(&blp_abs) + .output() + .expect("Failed to run blueprint-compiler"); - match output { - Ok(output) => { - if !output.status.success() { - let err = String::from_utf8_lossy(&output.stderr); - panic!("Failed to compile blueprint {}: {}", blp, err); - } - } - Err(e) => { - // If compiler is missing, we only fail if the .ui file doesn't exist - if !std::path::Path::new(&ui).exists() { - panic!("blueprint-compiler not found and {} is missing: {}", ui, e); - } - } + if !output.status.success() { + panic!("Failed to compile blueprint {}: {}", blp, String::from_utf8_lossy(&output.stderr)); } } + + let gresource_xml = std::path::Path::new(&root).join("src/ui/helvum.gresource.xml"); + println!("cargo:rerun-if-changed={}", gresource_xml.display()); + + let output = std::process::Command::new("glib-compile-resources") + .arg("--target") + .arg(std::path::Path::new(&out_dir).join("helvum.gresource")) + .arg("--sourcedir") + .arg(std::path::Path::new(&root).join("src/ui")) + .arg(&gresource_xml) + .output() + .expect("Failed to run glib-compile-resources"); + + if !output.status.success() { + panic!("Failed to compile gresource: {}", String::from_utf8_lossy(&output.stderr)); + } } diff --git a/src/main.rs b/src/main.rs index 3d02d08..cdb1aa9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -146,6 +146,10 @@ fn main() -> Result<(), Box> { init_glib_logger(); gtk::init()?; + let res_bytes = include_bytes!(concat!(env!("OUT_DIR"), "/helvum.gresource")); + let resource = adw::gio::Resource::from_data(&adw::glib::Bytes::from_static(res_bytes))?; + adw::gio::resources_register(&resource); + // Aquire main context so that we can attach the gtk channel later. let ctx = glib::MainContext::default(); let _guard = ctx.acquire().unwrap(); diff --git a/src/ui/graph/node.rs b/src/ui/graph/node.rs index e27b342..f864be0 100644 --- a/src/ui/graph/node.rs +++ b/src/ui/graph/node.rs @@ -30,7 +30,7 @@ mod imp { #[derive(glib::Properties, gtk::CompositeTemplate, Default)] #[properties(wrapper_type = super::Node)] - #[template(file = "node.ui")] + #[template(resource = "/org/pipewire/Helvum/graph/node.ui")] pub struct Node { #[property(get, set, construct_only)] pub(super) pipewire_id: Cell, diff --git a/src/ui/graph/port.rs b/src/ui/graph/port.rs index dc2eb76..ab7815b 100644 --- a/src/ui/graph/port.rs +++ b/src/ui/graph/port.rs @@ -98,7 +98,7 @@ mod imp { /// Graphical representation of a pipewire port. #[derive(gtk::CompositeTemplate)] - #[template(file = "port.ui")] + #[template(resource = "/org/pipewire/Helvum/graph/port.ui")] pub struct Port { pub(super) pipewire_id: Cell, pub(super) media_type: Cell, diff --git a/src/ui/graph/zoomentry.rs b/src/ui/graph/zoomentry.rs index a53697b..701ae2d 100644 --- a/src/ui/graph/zoomentry.rs +++ b/src/ui/graph/zoomentry.rs @@ -11,7 +11,7 @@ mod imp { use std::sync::LazyLock; #[derive(gtk::CompositeTemplate)] - #[template(file = "zoomentry.ui")] + #[template(resource = "/org/pipewire/Helvum/graph/zoomentry.ui")] pub struct ZoomEntry { pub graphview: RefCell>, #[template_child] @@ -187,3 +187,4 @@ impl ZoomEntry { .build() } } + diff --git a/src/ui/helvum.gresource.xml b/src/ui/helvum.gresource.xml new file mode 100644 index 0000000..3b5c14e --- /dev/null +++ b/src/ui/helvum.gresource.xml @@ -0,0 +1,9 @@ + + + + window.ui + graph/node.ui + graph/port.ui + graph/zoomentry.ui + + diff --git a/src/ui/window.rs b/src/ui/window.rs index c86dfa2..61b8de5 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -7,7 +7,7 @@ mod imp { #[derive(gtk::CompositeTemplate, glib::Properties)] #[properties(wrapper_type = super::Window)] - #[template(file = "window.ui")] + #[template(resource = "/org/pipewire/Helvum/window.ui")] pub struct Window { #[template_child] pub header_bar: TemplateChild, From 12d9600150588184b7300f33dc55e732b6cefe0e Mon Sep 17 00:00:00 2001 From: Naveen Prashanth Date: Thu, 7 May 2026 20:41:04 +0530 Subject: [PATCH 4/4] fix fmt issues --- build.rs | 13 ++++++++++--- src/ui/graph/port.rs | 1 - src/ui/graph/zoomentry.rs | 1 - src/ui/window.rs | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/build.rs b/build.rs index aae8a39..d4466e3 100644 --- a/build.rs +++ b/build.rs @@ -14,7 +14,7 @@ fn main() { for blp in blp_files { let blp_abs = std::path::Path::new(&root).join(blp); let ui_abs = blp_abs.with_extension("ui"); - + println!("cargo:rerun-if-changed={}", blp_abs.display()); let output = std::process::Command::new("blueprint-compiler") @@ -26,7 +26,11 @@ fn main() { .expect("Failed to run blueprint-compiler"); if !output.status.success() { - panic!("Failed to compile blueprint {}: {}", blp, String::from_utf8_lossy(&output.stderr)); + panic!( + "Failed to compile blueprint {}: {}", + blp, + String::from_utf8_lossy(&output.stderr) + ); } } @@ -43,6 +47,9 @@ fn main() { .expect("Failed to run glib-compile-resources"); if !output.status.success() { - panic!("Failed to compile gresource: {}", String::from_utf8_lossy(&output.stderr)); + panic!( + "Failed to compile gresource: {}", + String::from_utf8_lossy(&output.stderr) + ); } } diff --git a/src/ui/graph/port.rs b/src/ui/graph/port.rs index cba9b7c..c4b8155 100644 --- a/src/ui/graph/port.rs +++ b/src/ui/graph/port.rs @@ -25,7 +25,6 @@ use adw::{ pub use imp::{PortDirection, PortMediaType}; use libspa::{param::format::MediaType, utils::Direction}; - mod imp { use super::*; diff --git a/src/ui/graph/zoomentry.rs b/src/ui/graph/zoomentry.rs index 701ae2d..1b37d3a 100644 --- a/src/ui/graph/zoomentry.rs +++ b/src/ui/graph/zoomentry.rs @@ -187,4 +187,3 @@ impl ZoomEntry { .build() } } - diff --git a/src/ui/window.rs b/src/ui/window.rs index 61b8de5..6606815 100644 --- a/src/ui/window.rs +++ b/src/ui/window.rs @@ -66,7 +66,7 @@ mod imp { self.parent_constructed(); self.scrolled_window.set_child(Some(&self.graph)); - + self.zoom_entry.set_halign(gtk::Align::End); self.zoom_entry.set_valign(gtk::Align::End); self.zoom_entry.set_margin_end(24);