From 727326aca4af5aca6d77f3e2210abf07d0280893 Mon Sep 17 00:00:00 2001 From: "Tom A. Wagner" Date: Wed, 9 Nov 2022 16:27:58 +0100 Subject: [PATCH] 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. --- src/application.rs | 16 ++++ src/view/mod.rs | 2 + src/view/zoomentry.rs | 179 ++++++++++++++++++++++++++++++++++++++++++ src/view/zoomentry.ui | 26 ++++++ 4 files changed, 223 insertions(+) create mode 100644 src/view/zoomentry.rs create mode 100644 src/view/zoomentry.ui diff --git a/src/application.rs b/src/application.rs index 4ab58b3..e4e2532 100644 --- a/src/application.rs +++ b/src/application.rs @@ -56,6 +56,10 @@ mod imp { let scrollwindow = gtk::ScrolledWindow::builder() .child(&self.graphview) .build(); + let headerbar = gtk::HeaderBar::new(); + let zoomentry = view::ZoomEntry::new(&self.graphview); + headerbar.pack_end(&zoomentry); + let window = gtk::ApplicationWindow::builder() .application(app) .default_width(1280) @@ -66,6 +70,18 @@ mod imp { window .settings() .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::().unwrap(); + graphview.set_zoom_factor(zoom_factor) + }), + ); + window.add_action(&zoom_set_action); + window.show(); } diff --git a/src/view/mod.rs b/src/view/mod.rs index d38a9a8..c4775e6 100644 --- a/src/view/mod.rs +++ b/src/view/mod.rs @@ -21,7 +21,9 @@ mod graph_view; mod node; mod port; +mod zoomentry; pub use graph_view::GraphView; pub use node::Node; pub use port::Port; +pub use zoomentry::ZoomEntry; diff --git a/src/view/zoomentry.rs b/src/view/zoomentry.rs new file mode 100644 index 0000000..12f8580 --- /dev/null +++ b/src/view/zoomentry.rs @@ -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>, + #[template_child] + pub zoom_out_button: TemplateChild, + #[template_child] + pub zoom_in_button: TemplateChild, + #[template_child] + pub entry: TemplateChild, + 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) { + 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::() { + 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> = 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) + @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") + } +} diff --git a/src/view/zoomentry.ui b/src/view/zoomentry.ui new file mode 100644 index 0000000..975e971 --- /dev/null +++ b/src/view/zoomentry.ui @@ -0,0 +1,26 @@ + + + + \ No newline at end of file