mirror of
https://gitlab.freedesktop.org/pipewire/helvum
synced 2026-03-16 03:56:12 +08:00
zooming: Add a control widget that allows for changing the zoom level of the graph
The new widget sits in the headerbar, and allows for changing the zoom level with "+" and "-" buttons, via text entry, and a dropdown where a list of predefined levels can be clicked.
This commit is contained in:
committed by
Tom Wagner
parent
56e73d33c9
commit
727326aca4
@@ -56,6 +56,10 @@ mod imp {
|
|||||||
let scrollwindow = gtk::ScrolledWindow::builder()
|
let scrollwindow = gtk::ScrolledWindow::builder()
|
||||||
.child(&self.graphview)
|
.child(&self.graphview)
|
||||||
.build();
|
.build();
|
||||||
|
let headerbar = gtk::HeaderBar::new();
|
||||||
|
let zoomentry = view::ZoomEntry::new(&self.graphview);
|
||||||
|
headerbar.pack_end(&zoomentry);
|
||||||
|
|
||||||
let window = gtk::ApplicationWindow::builder()
|
let window = gtk::ApplicationWindow::builder()
|
||||||
.application(app)
|
.application(app)
|
||||||
.default_width(1280)
|
.default_width(1280)
|
||||||
@@ -66,6 +70,18 @@ mod imp {
|
|||||||
window
|
window
|
||||||
.settings()
|
.settings()
|
||||||
.set_gtk_application_prefer_dark_theme(true);
|
.set_gtk_application_prefer_dark_theme(true);
|
||||||
|
window.set_titlebar(Some(&headerbar));
|
||||||
|
|
||||||
|
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::<f64>().unwrap();
|
||||||
|
graphview.set_zoom_factor(zoom_factor)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
window.add_action(&zoom_set_action);
|
||||||
|
|
||||||
window.show();
|
window.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,9 @@
|
|||||||
mod graph_view;
|
mod graph_view;
|
||||||
mod node;
|
mod node;
|
||||||
mod port;
|
mod port;
|
||||||
|
mod zoomentry;
|
||||||
|
|
||||||
pub use graph_view::GraphView;
|
pub use graph_view::GraphView;
|
||||||
pub use node::Node;
|
pub use node::Node;
|
||||||
pub use port::Port;
|
pub use port::Port;
|
||||||
|
pub use zoomentry::ZoomEntry;
|
||||||
|
|||||||
179
src/view/zoomentry.rs
Normal file
179
src/view/zoomentry.rs
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
use gtk::{glib, prelude::*, subclass::prelude::*};
|
||||||
|
|
||||||
|
use crate::view;
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use gtk::{gio, glib::clone};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
#[derive(gtk::CompositeTemplate)]
|
||||||
|
#[template(file = "zoomentry.ui")]
|
||||||
|
pub struct ZoomEntry {
|
||||||
|
pub graphview: RefCell<Option<view::GraphView>>,
|
||||||
|
#[template_child]
|
||||||
|
pub zoom_out_button: TemplateChild<gtk::Button>,
|
||||||
|
#[template_child]
|
||||||
|
pub zoom_in_button: TemplateChild<gtk::Button>,
|
||||||
|
#[template_child]
|
||||||
|
pub entry: TemplateChild<gtk::Entry>,
|
||||||
|
pub popover: gtk::PopoverMenu,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ZoomEntry {
|
||||||
|
fn default() -> Self {
|
||||||
|
let menu = gio::Menu::new();
|
||||||
|
menu.append(Some("30%"), Some("win.set-zoom(0.30)"));
|
||||||
|
menu.append(Some("50%"), Some("win.set-zoom(0.50)"));
|
||||||
|
menu.append(Some("75%"), Some("win.set-zoom(0.75)"));
|
||||||
|
menu.append(Some("100%"), Some("win.set-zoom(1.0)"));
|
||||||
|
menu.append(Some("150%"), Some("win.set-zoom(1.5)"));
|
||||||
|
menu.append(Some("200%"), Some("win.set-zoom(2.0)"));
|
||||||
|
menu.append(Some("300%"), Some("win.set-zoom(3.0)"));
|
||||||
|
let popover = gtk::PopoverMenu::from_model(Some(&menu));
|
||||||
|
|
||||||
|
ZoomEntry {
|
||||||
|
graphview: Default::default(),
|
||||||
|
zoom_out_button: Default::default(),
|
||||||
|
zoom_in_button: Default::default(),
|
||||||
|
entry: Default::default(),
|
||||||
|
popover,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for ZoomEntry {
|
||||||
|
const NAME: &'static str = "HelvumZoomEntry";
|
||||||
|
type Type = super::ZoomEntry;
|
||||||
|
type ParentType = gtk::Box;
|
||||||
|
|
||||||
|
fn class_init(klass: &mut Self::Class) {
|
||||||
|
klass.bind_template();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
obj.init_template();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for ZoomEntry {
|
||||||
|
fn constructed(&self, obj: &Self::Type) {
|
||||||
|
self.parent_constructed(obj);
|
||||||
|
|
||||||
|
self.zoom_out_button
|
||||||
|
.connect_clicked(clone!(@weak obj => move |_| {
|
||||||
|
let graphview = obj.imp().graphview.borrow();
|
||||||
|
if let Some(ref graphview) = *graphview {
|
||||||
|
graphview.set_zoom_factor(graphview.zoom_factor() - 0.1);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.zoom_in_button
|
||||||
|
.connect_clicked(clone!(@weak obj => move |_| {
|
||||||
|
let graphview = obj.imp().graphview.borrow();
|
||||||
|
if let Some(ref graphview) = *graphview {
|
||||||
|
graphview.set_zoom_factor(graphview.zoom_factor() + 0.1);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.entry
|
||||||
|
.connect_activate(clone!(@weak obj => move |entry| {
|
||||||
|
if let Ok(zoom_factor) = entry.text().trim_matches('%').parse::<f64>() {
|
||||||
|
let graphview = obj.imp().graphview.borrow();
|
||||||
|
if let Some(ref graphview) = *graphview {
|
||||||
|
graphview.set_zoom_factor(zoom_factor / 100.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
self.entry
|
||||||
|
.connect_icon_press(clone!(@weak obj => move |_, pos| {
|
||||||
|
if pos == gtk::EntryIconPosition::Secondary {
|
||||||
|
obj.imp().popover.show();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.popover.set_parent(&self.entry.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispose(&self, obj: &Self::Type) {
|
||||||
|
self.popover.unparent();
|
||||||
|
|
||||||
|
while let Some(child) = obj.first_child() {
|
||||||
|
child.unparent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
|
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
||||||
|
vec![glib::ParamSpecObject::new(
|
||||||
|
"zoomed-widget",
|
||||||
|
"zoomed widget",
|
||||||
|
"Zoomed Widget",
|
||||||
|
view::GraphView::static_type(),
|
||||||
|
glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT,
|
||||||
|
)]
|
||||||
|
});
|
||||||
|
|
||||||
|
PROPERTIES.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn property(&self, _obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
match pspec.name() {
|
||||||
|
"zoomed-widget" => self.graphview.borrow().to_value(),
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_property(
|
||||||
|
&self,
|
||||||
|
obj: &Self::Type,
|
||||||
|
_id: usize,
|
||||||
|
value: &glib::Value,
|
||||||
|
pspec: &glib::ParamSpec,
|
||||||
|
) {
|
||||||
|
match pspec.name() {
|
||||||
|
"zoomed-widget" => {
|
||||||
|
let widget: view::GraphView = value.get().unwrap();
|
||||||
|
widget.connect_notify_local(
|
||||||
|
Some("zoom-factor"),
|
||||||
|
clone!(@weak obj => move |graphview, _| {
|
||||||
|
let imp = obj.imp();
|
||||||
|
imp.update_zoom_factor_text(graphview.zoom_factor());
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
self.update_zoom_factor_text(widget.zoom_factor());
|
||||||
|
*self.graphview.borrow_mut() = Some(widget);
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl WidgetImpl for ZoomEntry {}
|
||||||
|
impl BoxImpl for ZoomEntry {}
|
||||||
|
|
||||||
|
impl ZoomEntry {
|
||||||
|
/// Update the text contained in the combobox's entry to reflect the provided zoom factor.
|
||||||
|
///
|
||||||
|
/// This does not update the associated [`view::GraphView`]s zoom level.
|
||||||
|
fn update_zoom_factor_text(&self, zoom_factor: f64) {
|
||||||
|
self.entry
|
||||||
|
.buffer()
|
||||||
|
.set_text(&format!("{factor:.0}%", factor = zoom_factor * 100.));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct ZoomEntry(ObjectSubclass<imp::ZoomEntry>)
|
||||||
|
@extends gtk::Box, gtk::Widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZoomEntry {
|
||||||
|
pub fn new(zoomed_widget: &view::GraphView) -> Self {
|
||||||
|
glib::Object::new(&[("zoomed-widget", zoomed_widget)]).expect("Failed to create ZoomEntry")
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/view/zoomentry.ui
Normal file
26
src/view/zoomentry.ui
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<interface>
|
||||||
|
<template class="HelvumZoomEntry" parent="GtkBox">
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="zoom_out_button">
|
||||||
|
<property name="icon-name">zoom-out-symbolic</property>
|
||||||
|
<property name="tooltip-text">Zoom out</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkEntry" id="entry">
|
||||||
|
<property name="secondary-icon-name">go-down-symbolic</property>
|
||||||
|
<property name="input-purpose">digits</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="zoom_in_button">
|
||||||
|
<property name="icon-name">zoom-in-symbolic</property>
|
||||||
|
<property name="tooltip-text">Zoom in</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<style>
|
||||||
|
<class name="linked"/>
|
||||||
|
</style>
|
||||||
|
</template>
|
||||||
|
</interface>
|
||||||
Reference in New Issue
Block a user