image: Use macro for object properties
This commit is contained in:
parent
6594b2caf0
commit
55811a2bb7
6 changed files with 166 additions and 278 deletions
|
|
@ -20,7 +20,7 @@
|
||||||
//!
|
//!
|
||||||
//! It is not always feasible or desiareble to store the complete decoded
|
//! It is not always feasible or desiareble to store the complete decoded
|
||||||
//! image in the VRAM. [`TiledImage`] allows to compose the parts of
|
//! image in the VRAM. [`TiledImage`] allows to compose the parts of
|
||||||
//! the image currently viewed, out of smaller [`Tiles`].
|
//! the image currently viewed, out of smaller [`Tile`]s.
|
||||||
//!
|
//!
|
||||||
//! This is especially important for SVGs where every the image has to
|
//! This is especially important for SVGs where every the image has to
|
||||||
//! be re-genearted for each zoom level. It can also be used to allow
|
//! be re-genearted for each zoom level. It can also be used to allow
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ use futures::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use std::cell::{Cell, OnceCell, RefCell};
|
use std::cell::{Cell, OnceCell, RefCell};
|
||||||
|
use std::marker::PhantomData;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Default background color around images and behind transparent images
|
/// Default background color around images and behind transparent images
|
||||||
|
|
@ -102,34 +103,46 @@ const THUMBNAIL_SIZE: f32 = 128.;
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use glib::Properties;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default, Properties)]
|
||||||
|
#[properties(wrapper_type = super::LpImage)]
|
||||||
pub struct LpImage {
|
pub struct LpImage {
|
||||||
|
#[property(get, set = LpImage::set_file, explicit_notify)]
|
||||||
pub(super) file: RefCell<Option<gio::File>>,
|
pub(super) file: RefCell<Option<gio::File>>,
|
||||||
|
#[property(get)]
|
||||||
pub(super) is_deleted: Cell<bool>,
|
pub(super) is_deleted: Cell<bool>,
|
||||||
|
#[property(get)]
|
||||||
pub(super) is_unsupported: Cell<bool>,
|
pub(super) is_unsupported: Cell<bool>,
|
||||||
|
/// Set to true when image is ready for displaying
|
||||||
|
#[property(get)]
|
||||||
|
pub(super) is_loaded: Cell<bool>,
|
||||||
|
/// Set if an error has occurred, shown on error_page
|
||||||
|
#[property(get)]
|
||||||
|
pub(super) error: RefCell<Option<String>>,
|
||||||
|
#[property(name = "format-name", type = Option<String>, get = Self::format_name)]
|
||||||
|
pub(super) format: RefCell<Option<decoder::ImageFormat>>,
|
||||||
|
pub(super) background_color: RefCell<Option<gdk::RGBA>>,
|
||||||
|
|
||||||
/// Track changes to this image
|
/// Track changes to this image
|
||||||
pub(super) file_monitor: RefCell<Option<gio::FileMonitor>>,
|
pub(super) file_monitor: RefCell<Option<gio::FileMonitor>>,
|
||||||
pub(super) frame_buffer: Arc<ArcSwap<tiling::FrameBuffer>>,
|
pub(super) frame_buffer: Arc<ArcSwap<tiling::FrameBuffer>>,
|
||||||
pub(super) decoder: RefCell<Option<Arc<Decoder>>>,
|
pub(super) decoder: RefCell<Option<Arc<Decoder>>>,
|
||||||
pub(super) format: RefCell<Option<decoder::ImageFormat>>,
|
|
||||||
pub(super) background_color: RefCell<Option<gdk::RGBA>>,
|
|
||||||
|
|
||||||
/// Set to true when image is ready for displaying
|
|
||||||
pub(super) is_loaded: Cell<bool>,
|
|
||||||
/// Set if an error has occurred, shown on error_page
|
|
||||||
pub(super) error: RefCell<Option<String>>,
|
|
||||||
|
|
||||||
/// Rotation final value (can differ from `rotation` during animation)
|
/// Rotation final value (can differ from `rotation` during animation)
|
||||||
pub(super) rotation_target: Cell<f64>,
|
pub(super) rotation_target: Cell<f64>,
|
||||||
/// Rotated presentation of original image in degrees clockwise
|
/// Rotated presentation of original image in degrees clockwise
|
||||||
|
#[property(get, set = Self::set_rotation, explicit_notify)]
|
||||||
pub(super) rotation: Cell<f64>,
|
pub(super) rotation: Cell<f64>,
|
||||||
// Animates the `rotation` property
|
// Animates the `rotation` property
|
||||||
pub(super) rotation_animation: OnceCell<adw::TimedAnimation>,
|
pub(super) rotation_animation: OnceCell<adw::TimedAnimation>,
|
||||||
|
|
||||||
/// Mirrored presentation of original image
|
/// Mirrored presentation of original image
|
||||||
|
#[property(get, set = Self::set_mirrored, explicit_notify)]
|
||||||
pub(super) mirrored: Cell<bool>,
|
pub(super) mirrored: Cell<bool>,
|
||||||
|
|
||||||
/// Displayed zoom level
|
/// Displayed zoom level
|
||||||
|
#[property(get, set = Self::set_zoom, explicit_notify)]
|
||||||
pub(super) zoom: Cell<f64>,
|
pub(super) zoom: Cell<f64>,
|
||||||
pub(super) zoom_animation: OnceCell<adw::TimedAnimation>,
|
pub(super) zoom_animation: OnceCell<adw::TimedAnimation>,
|
||||||
/// Targeted zoom level, might differ from `zoom` when animation is running
|
/// Targeted zoom level, might differ from `zoom` when animation is running
|
||||||
|
|
@ -141,20 +154,33 @@ mod imp {
|
||||||
pub(super) zoom_vscrollbar_transition: Cell<bool>,
|
pub(super) zoom_vscrollbar_transition: Cell<bool>,
|
||||||
|
|
||||||
/// Always fit image into window, causes `zoom` to change automatically
|
/// Always fit image into window, causes `zoom` to change automatically
|
||||||
|
#[property(get, set)]
|
||||||
pub(super) best_fit: Cell<bool>,
|
pub(super) best_fit: Cell<bool>,
|
||||||
/// Max zoom level is reached, stored to only send signals on change
|
/// Max zoom level is reached, stored to only send signals on change
|
||||||
pub(super) max_zoom: Cell<bool>,
|
#[property(get, set)]
|
||||||
|
pub(super) is_max_zoom: Cell<bool>,
|
||||||
|
|
||||||
/// Horizontal scrolling
|
/// Horizontal scrolling
|
||||||
pub(super) hadjustment: RefCell<Option<gtk::Adjustment>>,
|
#[property(override_interface = gtk::Scrollable, get , set = Self::set_hadjustment)]
|
||||||
|
pub(super) hadjustment: RefCell<gtk::Adjustment>,
|
||||||
/// Vertical scrolling
|
/// Vertical scrolling
|
||||||
pub(super) vadjustment: RefCell<Option<gtk::Adjustment>>,
|
#[property(override_interface = gtk::Scrollable, get , set = Self::set_vadjustment)]
|
||||||
|
pub(super) vadjustment: RefCell<gtk::Adjustment>,
|
||||||
|
|
||||||
|
#[property(override_interface = gtk::Scrollable, get = Self::scroll_policy, set = Self::set_ignore_scroll_policy)]
|
||||||
|
pub(super) _hscroll_policy: PhantomData<gtk::ScrollablePolicy>,
|
||||||
|
#[property(override_interface = gtk::Scrollable, get = Self::scroll_policy, set = Self::set_ignore_scroll_policy)]
|
||||||
|
pub(super) _vscroll_policy: PhantomData<gtk::ScrollablePolicy>,
|
||||||
|
|
||||||
/// Currently EXIF data
|
/// Currently EXIF data
|
||||||
|
#[property(name = "metadata", get)]
|
||||||
pub(super) image_metadata: RefCell<LpImageMetadata>,
|
pub(super) image_metadata: RefCell<LpImageMetadata>,
|
||||||
/// Image dimension details for SVGs
|
/// Image dimension details for SVGs
|
||||||
pub(super) dimension_details: RefCell<decoder::ImageDimensionDetails>,
|
pub(super) dimension_details: RefCell<decoder::ImageDimensionDetails>,
|
||||||
|
|
||||||
|
#[property(get)]
|
||||||
|
_image_size_available: bool,
|
||||||
|
|
||||||
/// Current pointer position
|
/// Current pointer position
|
||||||
pub(super) pointer_position: Cell<Option<(f64, f64)>>,
|
pub(super) pointer_position: Cell<Option<(f64, f64)>>,
|
||||||
|
|
||||||
|
|
@ -188,91 +214,15 @@ mod imp {
|
||||||
|
|
||||||
impl ObjectImpl for LpImage {
|
impl ObjectImpl for LpImage {
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
Self::derived_properties()
|
||||||
vec![
|
|
||||||
glib::ParamSpecObject::builder::<gio::File>("file")
|
|
||||||
.read_only()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecBoolean::builder("is-deleted")
|
|
||||||
.read_only()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecBoolean::builder("is-loaded")
|
|
||||||
.read_only()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecBoolean::builder("is-unsupported")
|
|
||||||
.read_only()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecString::builder("error").read_only().build(),
|
|
||||||
glib::ParamSpecObject::builder::<LpImageMetadata>("metadata")
|
|
||||||
.read_only()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecString::builder("format-name")
|
|
||||||
.read_only()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecDouble::builder("rotation")
|
|
||||||
.explicit_notify()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecBoolean::builder("mirrored")
|
|
||||||
.explicit_notify()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecDouble::builder("zoom")
|
|
||||||
.explicit_notify()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecBoolean::builder("best-fit")
|
|
||||||
.explicit_notify()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecBoolean::builder("is-max-zoom")
|
|
||||||
.read_only()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecVariant::builder("image-size", glib::VariantTy::TUPLE)
|
|
||||||
.read_only()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecOverride::for_interface::<gtk::Scrollable>("hadjustment"),
|
|
||||||
glib::ParamSpecOverride::for_interface::<gtk::Scrollable>("vadjustment"),
|
|
||||||
glib::ParamSpecOverride::for_interface::<gtk::Scrollable>("hscroll-policy"),
|
|
||||||
glib::ParamSpecOverride::for_interface::<gtk::Scrollable>("vscroll-policy"),
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
PROPERTIES.as_ref()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||||
let obj = self.obj();
|
self.derived_set_property(id, value, pspec)
|
||||||
match pspec.name() {
|
|
||||||
"file" => obj.file().to_value(),
|
|
||||||
"is-deleted" => obj.is_deleted().to_value(),
|
|
||||||
"is-loaded" => obj.is_loaded().to_value(),
|
|
||||||
"is-unsupported" => obj.is_unsupported().to_value(),
|
|
||||||
"error" => obj.error().to_value(),
|
|
||||||
"metadata" => obj.metadata().to_value(),
|
|
||||||
"format-name" => obj.format_name().to_value(),
|
|
||||||
"rotation" => obj.rotation().to_value(),
|
|
||||||
"mirrored" => obj.mirrored().to_value(),
|
|
||||||
"zoom" => obj.zoom().to_value(),
|
|
||||||
"best-fit" => obj.is_best_fit().to_value(),
|
|
||||||
"is-max-zoom" => obj.is_max_zoom().to_value(),
|
|
||||||
"image-size" => obj.image_size().to_variant().to_value(),
|
|
||||||
// don't use getter functions here since they can return a fake adjustment
|
|
||||||
"hadjustment" => self.hadjustment.borrow().to_value(),
|
|
||||||
"vadjustment" => self.vadjustment.borrow().to_value(),
|
|
||||||
"hscroll-policy" | "vscroll-policy" => gtk::ScrollablePolicy::Minimum.to_value(),
|
|
||||||
name => unimplemented!("property {}", name),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
let obj = self.obj();
|
self.derived_property(id, pspec)
|
||||||
match pspec.name() {
|
|
||||||
"rotation" => obj.set_rotation(value.get().unwrap()),
|
|
||||||
"mirrored" => obj.set_mirrored(value.get().unwrap()),
|
|
||||||
"zoom" => obj.set_zoom(value.get().unwrap()),
|
|
||||||
"best-fit" => obj.set_best_fit(value.get().unwrap()),
|
|
||||||
"hadjustment" => obj.set_hadjustment(value.get().unwrap()),
|
|
||||||
"vadjustment" => obj.set_vadjustment(value.get().unwrap()),
|
|
||||||
"hscroll-policy" | "vscroll-policy" => (),
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn constructed(&self) {
|
fn constructed(&self) {
|
||||||
|
|
@ -559,6 +509,102 @@ mod imp {
|
||||||
|
|
||||||
zoom_gesture.group_with(&rotation_gesture);
|
zoom_gesture.group_with(&rotation_gesture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_file(&self, file: &gio::File) {
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
if obj.file().map_or(false, |x| file.equal(&x)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.file.replace(Some(file.clone()));
|
||||||
|
|
||||||
|
let monitor =
|
||||||
|
file.monitor_file(gio::FileMonitorFlags::WATCH_MOVES, gio::Cancellable::NONE);
|
||||||
|
if let Ok(m) = &monitor {
|
||||||
|
m.connect_changed(glib::clone!(@weak obj => move |_, file_a, file_b, event| {
|
||||||
|
obj.file_changed(event, file_a, file_b);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.file_monitor.replace(monitor.ok());
|
||||||
|
|
||||||
|
obj.notify_file();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_mirrored(&self, mirrored: bool) {
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
if mirrored == obj.mirrored() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.mirrored.set(mirrored);
|
||||||
|
obj.notify_mirrored();
|
||||||
|
obj.queue_draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set zoom level aiming for cursor position or center if not available
|
||||||
|
///
|
||||||
|
/// Aiming means that the scrollbars are adjust such that the same point
|
||||||
|
/// of the image remains under the cursor after changing the zoom level.
|
||||||
|
fn set_zoom(&self, zoom: f64) {
|
||||||
|
self.obj()
|
||||||
|
.set_zoom_aiming(zoom, self.pointer_position.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_hadjustment(&self, hadjustment: Option<gtk::Adjustment>) {
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
let adjustment = hadjustment.unwrap_or_default();
|
||||||
|
|
||||||
|
adjustment.connect_value_changed(glib::clone!(@weak obj => move |_| {
|
||||||
|
obj.request_tiles();
|
||||||
|
obj.queue_draw();
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.hadjustment.replace(adjustment);
|
||||||
|
obj.configure_adjustments();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_vadjustment(&self, vadjustment: Option<gtk::Adjustment>) {
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
let adjustment = vadjustment.unwrap_or_default();
|
||||||
|
|
||||||
|
adjustment.connect_value_changed(glib::clone!(@weak obj => move |_| {
|
||||||
|
obj.request_tiles();
|
||||||
|
obj.queue_draw();
|
||||||
|
}));
|
||||||
|
|
||||||
|
self.vadjustment.replace(adjustment);
|
||||||
|
obj.configure_adjustments();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_rotation(&self, rotation: f64) {
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
if rotation == obj.rotation() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.rotation.set(rotation);
|
||||||
|
obj.notify_rotation();
|
||||||
|
obj.queue_draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_ignore_scroll_policy(&self, scroll_policy: gtk::ScrollablePolicy) {
|
||||||
|
log::error!("Ignored setting new scroll policy {scroll_policy:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_policy(&self) -> gtk::ScrollablePolicy {
|
||||||
|
gtk::ScrollablePolicy::Minimum
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Image format displayable name
|
||||||
|
pub fn format_name(&self) -> Option<String> {
|
||||||
|
self.format.borrow().as_ref().map(|x| x.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WidgetImpl for LpImage {
|
impl WidgetImpl for LpImage {
|
||||||
|
|
@ -640,14 +686,13 @@ mod imp {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Apply the scrolling position to the image
|
// Apply the scrolling position to the image
|
||||||
if let Some(adj) = self.hadjustment.borrow().as_ref() {
|
let hadj: gtk::Adjustment = widget.hadjustment();
|
||||||
let x = -(adj.value() - (adj.upper() - display_width) / 2.);
|
let x = -(hadj.value() - (hadj.upper() - display_width) / 2.);
|
||||||
snapshot.translate(&graphene::Point::new(widget.round_f64(x) as f32, 0.));
|
snapshot.translate(&graphene::Point::new(widget.round_f64(x) as f32, 0.));
|
||||||
}
|
|
||||||
if let Some(adj) = self.vadjustment.borrow().as_ref() {
|
let vadj = widget.vadjustment();
|
||||||
let y = -(adj.value() - (adj.upper() - display_height) / 2.);
|
let y = -(vadj.value() - (vadj.upper() - display_height) / 2.);
|
||||||
snapshot.translate(&graphene::Point::new(0., widget.round_f64(y) as f32));
|
snapshot.translate(&graphene::Point::new(0., widget.round_f64(y) as f32));
|
||||||
}
|
|
||||||
|
|
||||||
// Centering in widget when no scrolling (black bars around image)
|
// Centering in widget when no scrolling (black bars around image)
|
||||||
let x = widget.round_f64(f64::max((widget_width - display_width) / 2.0, 0.));
|
let x = widget.round_f64(f64::max((widget_width - display_width) / 2.0, 0.));
|
||||||
|
|
@ -797,14 +842,14 @@ impl LpImage {
|
||||||
DecoderUpdate::Metadata(metadata) => {
|
DecoderUpdate::Metadata(metadata) => {
|
||||||
log::debug!("Received metadata");
|
log::debug!("Received metadata");
|
||||||
imp.image_metadata.replace(LpImageMetadata::from(metadata));
|
imp.image_metadata.replace(LpImageMetadata::from(metadata));
|
||||||
self.notify("metadata");
|
self.notify_metadata();
|
||||||
|
|
||||||
self.reset_rotation();
|
self.reset_rotation();
|
||||||
}
|
}
|
||||||
DecoderUpdate::Dimensions(dimension_details) => {
|
DecoderUpdate::Dimensions(dimension_details) => {
|
||||||
log::debug!("Received dimensions: {:?}", self.original_dimensions());
|
log::debug!("Received dimensions: {:?}", self.original_dimensions());
|
||||||
self.imp().dimension_details.replace(dimension_details);
|
self.imp().dimension_details.replace(dimension_details);
|
||||||
self.notify("image-size");
|
self.notify_image_size_available();
|
||||||
self.configure_best_fit();
|
self.configure_best_fit();
|
||||||
self.request_tiles();
|
self.request_tiles();
|
||||||
}
|
}
|
||||||
|
|
@ -832,7 +877,7 @@ impl LpImage {
|
||||||
}
|
}
|
||||||
DecoderUpdate::Format(format) => {
|
DecoderUpdate::Format(format) => {
|
||||||
imp.format.replace(Some(format));
|
imp.format.replace(Some(format));
|
||||||
self.notify("format-name");
|
self.notify_format_name();
|
||||||
}
|
}
|
||||||
DecoderUpdate::Animated => {
|
DecoderUpdate::Animated => {
|
||||||
let callback_id = self
|
let callback_id = self
|
||||||
|
|
@ -871,18 +916,6 @@ impl LpImage {
|
||||||
glib::ControlFlow::Continue
|
glib::ControlFlow::Continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_loaded(&self) -> bool {
|
|
||||||
self.imp().is_loaded.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_deleted(&self) -> bool {
|
|
||||||
self.imp().is_deleted.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_unsupported(&self) -> bool {
|
|
||||||
self.imp().is_unsupported.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Zoom level that makes the image fit in widget
|
/// Zoom level that makes the image fit in widget
|
||||||
///
|
///
|
||||||
/// During image rotation the image does not actually fit into widget.
|
/// During image rotation the image does not actually fit into widget.
|
||||||
|
|
@ -929,29 +962,6 @@ impl LpImage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn file(&self) -> Option<gio::File> {
|
|
||||||
self.imp().file.borrow().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn set_file(&self, file: &gio::File) {
|
|
||||||
let imp = self.imp();
|
|
||||||
|
|
||||||
imp.file.replace(Some(file.clone()));
|
|
||||||
|
|
||||||
let monitor = file.monitor_file(gio::FileMonitorFlags::WATCH_MOVES, gio::Cancellable::NONE);
|
|
||||||
if let Ok(m) = &monitor {
|
|
||||||
m.connect_changed(
|
|
||||||
glib::clone!(@weak self as obj => move |_, file_a, file_b, event| {
|
|
||||||
obj.file_changed(event, file_a, file_b);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
imp.file_monitor.replace(monitor.ok());
|
|
||||||
|
|
||||||
self.notify("file");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// File changed on drive
|
/// File changed on drive
|
||||||
fn file_changed(
|
fn file_changed(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -990,7 +1000,7 @@ impl LpImage {
|
||||||
| gio::FileMonitorEvent::Unmounted => {
|
| gio::FileMonitorEvent::Unmounted => {
|
||||||
log::debug!("File no longer available: {event:?} {}", file_a.uri());
|
log::debug!("File no longer available: {event:?} {}", file_a.uri());
|
||||||
self.imp().is_deleted.set(true);
|
self.imp().is_deleted.set(true);
|
||||||
self.notify("is-deleted");
|
self.notify_is_deleted();
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
@ -1055,34 +1065,6 @@ impl LpImage {
|
||||||
Some(renderer.render_texture(&node, None))
|
Some(renderer.render_texture(&node, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mirrored(&self) -> bool {
|
|
||||||
self.imp().mirrored.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_mirrored(&self, mirrored: bool) {
|
|
||||||
if mirrored == self.mirrored() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.imp().mirrored.set(mirrored);
|
|
||||||
self.notify("mirrored");
|
|
||||||
self.queue_draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rotation(&self) -> f64 {
|
|
||||||
self.imp().rotation.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_rotation(&self, rotation: f64) {
|
|
||||||
if rotation == self.rotation() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.imp().rotation.set(rotation);
|
|
||||||
self.notify("rotation");
|
|
||||||
self.queue_draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set rotation and mirroring to the state would have after loading
|
/// Set rotation and mirroring to the state would have after loading
|
||||||
pub fn reset_rotation(&self) {
|
pub fn reset_rotation(&self) {
|
||||||
let orientation = self.metadata().orientation();
|
let orientation = self.metadata().orientation();
|
||||||
|
|
@ -1125,41 +1107,6 @@ impl LpImage {
|
||||||
self.imp().best_fit.get()
|
self.imp().best_fit.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_best_fit(&self, best_fit: bool) {
|
|
||||||
if best_fit == self.is_best_fit() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.imp().best_fit.set(best_fit);
|
|
||||||
self.notify("best-fit");
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Current zoom level
|
|
||||||
pub fn zoom(&self) -> f64 {
|
|
||||||
self.imp().zoom.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set zoom level aiming for cursor position or center if not available
|
|
||||||
///
|
|
||||||
/// Aiming means that the scrollbars are adjust such that the same point
|
|
||||||
/// of the image remains under the cursor after changing the zoom level.
|
|
||||||
fn set_zoom(&self, zoom: f64) {
|
|
||||||
self.set_zoom_aiming(zoom, self.imp().pointer_position.get())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_max_zoom(&self) -> bool {
|
|
||||||
self.imp().max_zoom.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_max_zoom(&self, value: bool) {
|
|
||||||
if self.is_max_zoom() == value {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.imp().max_zoom.set(value);
|
|
||||||
self.notify("is-max-zoom");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn applicable_zoom(&self) -> f64 {
|
fn applicable_zoom(&self) -> f64 {
|
||||||
decoder::tiling::zoom_normalize(self.zoom()) / self.scale_factor() as f64
|
decoder::tiling::zoom_normalize(self.zoom()) / self.scale_factor() as f64
|
||||||
}
|
}
|
||||||
|
|
@ -1244,7 +1191,7 @@ impl LpImage {
|
||||||
.set_value(self.vadjustment_corrected_for_zoom(zoom_ratio, y));
|
.set_value(self.vadjustment_corrected_for_zoom(zoom_ratio, y));
|
||||||
}
|
}
|
||||||
|
|
||||||
self.notify("zoom");
|
self.notify_zoom();
|
||||||
self.queue_draw();
|
self.queue_draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1381,9 +1328,9 @@ impl LpImage {
|
||||||
let max_zoom = self.max_zoom();
|
let max_zoom = self.max_zoom();
|
||||||
if zoom >= max_zoom {
|
if zoom >= max_zoom {
|
||||||
zoom = max_zoom;
|
zoom = max_zoom;
|
||||||
self.set_max_zoom(true);
|
self.set_is_max_zoom(true);
|
||||||
} else {
|
} else {
|
||||||
self.set_max_zoom(false);
|
self.set_is_max_zoom(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let extended_best_fit_threshold = if snap_best_fit {
|
let extended_best_fit_threshold = if snap_best_fit {
|
||||||
|
|
@ -1516,48 +1463,6 @@ impl LpImage {
|
||||||
adjustment.set_value(value);
|
adjustment.set_value(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hadjustment(&self) -> gtk::Adjustment {
|
|
||||||
if let Some(adj) = self.imp().hadjustment.borrow().as_ref() {
|
|
||||||
adj.clone()
|
|
||||||
} else {
|
|
||||||
log::trace!("Hadjustment not set yet: Using fake object");
|
|
||||||
gtk::Adjustment::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_hadjustment(&self, adjustment: Option<gtk::Adjustment>) {
|
|
||||||
if let Some(adj) = &adjustment {
|
|
||||||
adj.connect_value_changed(glib::clone!(@weak self as obj => move |_| {
|
|
||||||
obj.request_tiles();
|
|
||||||
obj.queue_draw();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.imp().hadjustment.replace(adjustment);
|
|
||||||
self.configure_adjustments();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn vadjustment(&self) -> gtk::Adjustment {
|
|
||||||
if let Some(adj) = self.imp().vadjustment.borrow().as_ref() {
|
|
||||||
adj.clone()
|
|
||||||
} else {
|
|
||||||
log::trace!("Vadjustment not set yet: Using fake object");
|
|
||||||
gtk::Adjustment::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_vadjustment(&self, adjustment: Option<gtk::Adjustment>) {
|
|
||||||
if let Some(adj) = &adjustment {
|
|
||||||
adj.connect_value_changed(glib::clone!(@weak self as obj => move |_| {
|
|
||||||
obj.request_tiles();
|
|
||||||
obj.queue_draw();
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
self.imp().vadjustment.replace(adjustment);
|
|
||||||
self.configure_adjustments();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure scrollbars for current situation
|
/// Configure scrollbars for current situation
|
||||||
fn configure_adjustments(&self) {
|
fn configure_adjustments(&self) {
|
||||||
let hadjustment = self.hadjustment();
|
let hadjustment = self.hadjustment();
|
||||||
|
|
@ -1679,10 +1584,6 @@ impl LpImage {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn metadata(&self) -> LpImageMetadata {
|
|
||||||
self.imp().image_metadata.borrow().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dimension_details(&self) -> decoder::ImageDimensionDetails {
|
pub fn dimension_details(&self) -> decoder::ImageDimensionDetails {
|
||||||
self.imp().dimension_details.borrow().clone()
|
self.imp().dimension_details.borrow().clone()
|
||||||
}
|
}
|
||||||
|
|
@ -1699,22 +1600,12 @@ impl LpImage {
|
||||||
self.imp().format.borrow().clone()
|
self.imp().format.borrow().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Image format displayable name
|
|
||||||
pub fn format_name(&self) -> Option<String> {
|
|
||||||
self.imp().format.borrow().as_ref().map(|x| x.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns decoding error if one occured
|
|
||||||
pub fn error(&self) -> Option<String> {
|
|
||||||
self.imp().error.borrow().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_error(&self, err: Option<anyhow::Error>) {
|
fn set_error(&self, err: Option<anyhow::Error>) {
|
||||||
log::debug!("Decoding error: {err:?}");
|
log::debug!("Decoding error: {err:?}");
|
||||||
self.imp()
|
self.imp()
|
||||||
.error
|
.error
|
||||||
.replace(err.as_ref().map(|x| x.to_string()));
|
.replace(err.as_ref().map(|x| x.to_string()));
|
||||||
self.notify("error");
|
self.notify_error();
|
||||||
|
|
||||||
if err.is_some() {
|
if err.is_some() {
|
||||||
self.set_loaded(false);
|
self.set_loaded(false);
|
||||||
|
|
@ -1724,7 +1615,7 @@ impl LpImage {
|
||||||
fn set_unsupported(&self, is_unsupported: bool) {
|
fn set_unsupported(&self, is_unsupported: bool) {
|
||||||
if self.is_unsupported() != is_unsupported {
|
if self.is_unsupported() != is_unsupported {
|
||||||
self.imp().is_unsupported.set(true);
|
self.imp().is_unsupported.set(true);
|
||||||
self.notify("is-unsupported");
|
self.notify_is_unsupported();
|
||||||
|
|
||||||
if is_unsupported {
|
if is_unsupported {
|
||||||
self.set_loaded(false);
|
self.set_loaded(false);
|
||||||
|
|
@ -1735,7 +1626,7 @@ impl LpImage {
|
||||||
fn set_loaded(&self, is_loaded: bool) {
|
fn set_loaded(&self, is_loaded: bool) {
|
||||||
if self.is_loaded() != is_loaded {
|
if self.is_loaded() != is_loaded {
|
||||||
self.imp().is_loaded.set(is_loaded);
|
self.imp().is_loaded.set(is_loaded);
|
||||||
self.notify("is-loaded");
|
self.notify_is_loaded();
|
||||||
|
|
||||||
if is_loaded {
|
if is_loaded {
|
||||||
self.set_error(None);
|
self.set_error(None);
|
||||||
|
|
|
||||||
|
|
@ -118,26 +118,23 @@ mod imp {
|
||||||
imp.spinner.set_visible(spinner_visible);
|
imp.spinner.set_visible(spinner_visible);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
obj.image().connect_notify_local(
|
obj.image()
|
||||||
Some("is-loaded"),
|
.connect_is_loaded_notify(clone!(@weak obj => move |_| {
|
||||||
clone!(@weak obj => move |_,_| {
|
|
||||||
if obj.image().is_loaded() {
|
if obj.image().is_loaded() {
|
||||||
log::debug!("Showing image");
|
log::debug!("Showing image");
|
||||||
obj.imp()
|
obj.imp()
|
||||||
.stack
|
.stack
|
||||||
.set_visible_child(&*obj.imp().image_stack_page);
|
.set_visible_child(&*obj.imp().image_stack_page);
|
||||||
}
|
}
|
||||||
}),
|
}));
|
||||||
);
|
|
||||||
|
|
||||||
obj.image().connect_notify_local(
|
obj.image()
|
||||||
Some("error"),
|
.connect_error_notify(clone!(@weak obj => move |_| {
|
||||||
clone!(@weak obj => move |_,_| {
|
|
||||||
if obj.image().error().is_some() {
|
if obj.image().error().is_some() {
|
||||||
obj.imp().stack.set_visible_child(&*obj.imp().error_page);
|
obj.imp().stack.set_visible_child(&*obj.imp().error_page);
|
||||||
}
|
}
|
||||||
}),
|
}));
|
||||||
);
|
|
||||||
// Do not waste CPU on spinner if it is not visible
|
// Do not waste CPU on spinner if it is not visible
|
||||||
self.spinner.connect_map(|s| s.start());
|
self.spinner.connect_map(|s| s.start());
|
||||||
self.spinner.connect_unmap(|s| s.stop());
|
self.spinner.connect_unmap(|s| s.stop());
|
||||||
|
|
|
||||||
|
|
@ -301,7 +301,7 @@ impl LpPropertiesView {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn image_size(&self, _image_size: Option<glib::Variant>) -> String {
|
fn image_size(&self, _image_size: bool) -> String {
|
||||||
if let Some(image) = self.image() {
|
if let Some(image) = self.image() {
|
||||||
match image.dimension_details() {
|
match image.dimension_details() {
|
||||||
ImageDimensionDetails::Svg(string) => string,
|
ImageDimensionDetails::Svg(string) => string,
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@
|
||||||
<property name="subtitle-selectable">True</property>
|
<property name="subtitle-selectable">True</property>
|
||||||
<binding name="subtitle">
|
<binding name="subtitle">
|
||||||
<closure type="gchararray" function="image_size">
|
<closure type="gchararray" function="image_size">
|
||||||
<lookup type="LpImage" name="image-size">
|
<lookup type="LpImage" name="image-size-available">
|
||||||
<lookup name="image">LpPropertiesView</lookup>
|
<lookup name="image">LpPropertiesView</lookup>
|
||||||
</lookup>
|
</lookup>
|
||||||
</closure>
|
</closure>
|
||||||
|
|
|
||||||
|
|
@ -319,7 +319,7 @@ mod imp {
|
||||||
);
|
);
|
||||||
|
|
||||||
current_image_signals.connect_closure(
|
current_image_signals.connect_closure(
|
||||||
"notify::image-size",
|
"notify::image-size-available",
|
||||||
false,
|
false,
|
||||||
glib::closure_local!(@watch win => move |_: &LpImage, _: &glib::ParamSpec| {
|
glib::closure_local!(@watch win => move |_: &LpImage, _: &glib::ParamSpec| {
|
||||||
win.image_size_ready();
|
win.image_size_ready();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue