From 4d1141a675945004a0238a5c3fa27d7a9cd6c31e Mon Sep 17 00:00:00 2001 From: Christopher Davis Date: Mon, 13 Dec 2021 18:19:23 -0800 Subject: [PATCH] widgets: split view and image This will make implementing a more comprehensive photo view simpler. --- data/gtk/image_view.ui | 2 +- src/meson.build | 1 + src/widgets/image.rs | 208 ++++++++++++++++++++++++++++++++++++++ src/widgets/image_view.rs | 19 ++-- src/widgets/mod.rs | 2 + 5 files changed, 221 insertions(+), 11 deletions(-) create mode 100644 src/widgets/image.rs diff --git a/data/gtk/image_view.ui b/data/gtk/image_view.ui index 5e99a23..6ed8cd7 100644 --- a/data/gtk/image_view.ui +++ b/data/gtk/image_view.ui @@ -8,7 +8,7 @@ True - + center center diff --git a/src/meson.build b/src/meson.build index f805446..8045fdf 100644 --- a/src/meson.build +++ b/src/meson.build @@ -14,6 +14,7 @@ run_command( rust_sources = files( 'widgets/image_view.rs', + 'widgets/image.rs', 'widgets/mod.rs', 'application.rs', 'config.rs', diff --git a/src/widgets/image.rs b/src/widgets/image.rs new file mode 100644 index 0000000..80cdd8d --- /dev/null +++ b/src/widgets/image.rs @@ -0,0 +1,208 @@ +// image.rs +// +// Copyright 2021 Christopher Davis +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// 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-or-later + +use gdk::prelude::*; +use gio::subclass::prelude::*; +use gtk::{graphene, prelude::*, subclass::prelude::*}; + +use once_cell::sync::Lazy; + +use std::cell::{Cell, RefCell}; + +mod imp { + use super::*; + + #[derive(Debug, Default)] + pub struct IvImage { + pub file: RefCell>, + pub image_width: Cell, + pub image_height: Cell, + pub texture: RefCell>, + } + + #[glib::object_subclass] + impl ObjectSubclass for IvImage { + const NAME: &'static str = "IvImage"; + type ParentType = gtk::Widget; + type Type = super::IvImage; + + fn new() -> Self { + Self { + file: RefCell::default(), + image_width: Cell::default(), + image_height: Cell::default(), + texture: RefCell::default(), + } + } + } + + impl ObjectImpl for IvImage { + fn properties() -> &'static [glib::ParamSpec] { + static PROPERTIES: Lazy> = Lazy::new(|| { + vec![glib::ParamSpec::new_object( + "file", + "File", + "The current file", + gio::File::static_type(), + glib::ParamFlags::READWRITE, + )] + }); + + PROPERTIES.as_ref() + } + + fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value { + match pspec.name() { + "file" => obj.file().to_value(), + _ => unimplemented!(), + } + } + + fn set_property( + &self, + obj: &Self::Type, + _id: usize, + value: &glib::Value, + pspec: &glib::ParamSpec, + ) { + match pspec.name() { + "file" => obj.set_file(&value.get().unwrap()), + _ => unimplemented!(), + } + } + + fn constructed(&self, obj: &Self::Type) { + obj.set_hexpand(true); + obj.set_vexpand(true); + } + } + + impl WidgetImpl for IvImage { + fn snapshot(&self, widget: &Self::Type, snapshot: >k::Snapshot) { + if let Some(texture) = self.texture.borrow().as_ref() { + let width = widget.width() as f64; + let height = widget.height() as f64; + let ratio = texture.intrinsic_aspect_ratio(); + + if ratio == 0.0 { + texture.snapshot(snapshot.upcast_ref(), width, height); + } else { + let pic_ratio = width / height; + + let (snapshot_width, snapshot_height) = if ratio > pic_ratio { + (width, width / ratio) + } else { + (height * ratio, height) + }; + + let x = (width - snapshot_width.ceil()) / 2.0; + let y = (height - snapshot_height.ceil()).floor() / 2.0; + + snapshot.save(); + snapshot.translate(&graphene::Point::new(x as f32, y as f32)); + texture.snapshot(snapshot.upcast_ref(), snapshot_width, snapshot_height); + snapshot.restore(); + } + } + } + + fn request_mode(&self, _widget: &Self::Type) -> gtk::SizeRequestMode { + gtk::SizeRequestMode::HeightForWidth + } + + fn measure( + &self, + _widget: &Self::Type, + orienation: gtk::Orientation, + for_size: i32, + ) -> (i32, i32, i32, i32) { + if let Some(texture) = self.texture.borrow().as_ref() { + let default_size = 16.0; // Fall back to default icon size; Just a guess since the API GtkPicture uses is private + let measurement = if for_size < 0 { 0 } else { for_size }; + let (min_width, min_height): (f64, f64) = (0.0, 0.0); + + let (min, nat) = if orienation == gtk::Orientation::Horizontal { + let size = texture.compute_concrete_size( + 0.0, + measurement.into(), + default_size, + default_size, + ); + let nat_width = size.0; + + (min_width.ceil(), nat_width.ceil()) + } else { + let size = texture.compute_concrete_size( + measurement.into(), + 0.0, + default_size, + default_size, + ); + let nat_height = size.1; + + (min_height.ceil(), nat_height.ceil()) + }; + + (min as i32, nat as i32, -1, -1) + } else { + (0, 0, -1, -1) + } + } + } +} + +glib::wrapper! { + pub struct IvImage(ObjectSubclass) @extends gtk::Widget; +} + +impl IvImage { + pub fn file(&self) -> Option { + let imp = imp::IvImage::from_instance(self); + + imp.file.borrow().clone() + } + + pub fn set_file(&self, file: &gio::File) { + let imp = imp::IvImage::from_instance(self); + + match gdk::Texture::from_file(file) { + Ok(texture) => { + imp.image_width.set(texture.width()); + imp.image_height.set(texture.height()); + imp.file.replace(Some(file.clone())); + imp.texture.replace(Some(texture)); + } + Err(e) => log::error!("Could not load a valid image from this file: {}", e), + }; + + self.queue_draw(); + self.queue_resize(); + imp.image_height.get(); + } + + pub fn image_width(&self) -> i32 { + let imp = imp::IvImage::from_instance(&self); + imp.image_width.get() + } + + pub fn image_height(&self) -> i32 { + let imp = imp::IvImage::from_instance(&self); + imp.image_height.get() + } +} diff --git a/src/widgets/image_view.rs b/src/widgets/image_view.rs index f3ca5ad..280a614 100644 --- a/src/widgets/image_view.rs +++ b/src/widgets/image_view.rs @@ -32,6 +32,7 @@ use once_cell::sync::Lazy; use std::cell::{Cell, RefCell}; use crate::util; +use crate::widgets::IvImage; mod imp { use super::*; @@ -40,7 +41,7 @@ mod imp { #[template(resource = "/org/gnome/Loupe/gtk/image_view.ui")] pub struct IvImageView { #[template_child] - pub picture: TemplateChild, + pub picture: TemplateChild, #[template_child] pub controls: TemplateChild, #[template_child] @@ -156,18 +157,16 @@ impl IvImageView { } self.set_parent_from_file(file); - let texture = gdk::Texture::from_file(file)?; - imp.picture.set_paintable(Some(&texture)); + imp.picture.set_file(file); *imp.uri.borrow_mut() = Some(file.uri().to_string()); self.notify("filename"); self.update_action_state(); - log::debug!( - "Image dimensions: {} x {}", - texture.width(), - texture.height() - ); - Ok((texture.width(), texture.height())) + let width = imp.picture.image_width(); + let height = imp.picture.image_height(); + + log::debug!("Image dimensions: {} x {}", width, height); + Ok((width, height)) } fn set_parent_from_file(&self, file: &gio::File) { @@ -241,7 +240,7 @@ impl IvImageView { ); let file = gio::File::for_path(path); - imp.picture.set_file(Some(&file)); + imp.picture.set_file(&file); *imp.uri.borrow_mut() = Some(file.uri().to_string()); *imp.filename.borrow_mut() = util::get_file_display_name(&file); self.notify("filename"); diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 69aeba8..0312e33 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -17,6 +17,8 @@ // // SPDX-License-Identifier: GPL-3.0-or-later +mod image; mod image_view; +pub use image::IvImage; pub use image_view::IvImageView;