print: Use LpImage for preview

This commit is contained in:
Sophie Herold 2023-12-25 20:18:26 +01:00
parent e387d8dc66
commit 6827a9c176
9 changed files with 143 additions and 44 deletions

View file

@ -45,6 +45,11 @@ lpprintpreviewpage {
box-shadow: 0 0 14px 3px #666; box-shadow: 0 0 14px 3px #666;
} }
lpprintpreviewpage lpimage {
outline: 1px dotted rgba(0, 0, 0, 0.3);
}
dropdown.flat button:not(:hover) { dropdown.flat button:not(:hover) {
background: none; background: none;
} }

View file

@ -583,6 +583,7 @@ pub trait FrameBufferExt {
/// Return true if the next frame should be shown and removes the outdated /// Return true if the next frame should be shown and removes the outdated
/// frame /// frame
fn frame_timeout(&self, elapsed: Duration) -> bool; fn frame_timeout(&self, elapsed: Duration) -> bool;
fn next_frame(&self);
/// Returns the number of currently buffered frames /// Returns the number of currently buffered frames
fn n_frames(&self) -> usize; fn n_frames(&self) -> usize;
fn set_original_dimensions(&self, size: Coordinates); fn set_original_dimensions(&self, size: Coordinates);
@ -661,17 +662,21 @@ impl FrameBufferExt for ArcSwap<FrameBuffer> {
.front() .front()
.is_some_and(|next_frame| elapsed >= next_frame.delay) .is_some_and(|next_frame| elapsed >= next_frame.delay)
{ {
self.rcu(|tiling_store| { self.next_frame();
let mut new_store = (**tiling_store).clone();
new_store.images.pop_front();
Arc::new(new_store)
});
return true; return true;
} }
false false
} }
fn next_frame(&self) {
self.rcu(|tiling_store| {
let mut new_store = (**tiling_store).clone();
new_store.images.pop_front();
Arc::new(new_store)
});
}
fn n_frames(&self) -> usize { fn n_frames(&self) -> usize {
self.load().images.len() self.load().images.len()
} }

View file

@ -16,6 +16,8 @@
// //
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: GPL-3.0-or-later
#![allow(clippy::new_without_default)]
/*! /*!
# Loupe Image Viewer # Loupe Image Viewer

View file

@ -117,6 +117,13 @@ const MAX_ZOOM_LEVEL: f64 = 20.0;
/// The thumbnail is currently used for drag and drop. /// The thumbnail is currently used for drag and drop.
const THUMBNAIL_SIZE: f32 = 128.; const THUMBNAIL_SIZE: f32 = 128.;
#[derive(Default, Debug, Clone, Copy)]
pub enum FitMode {
#[default]
BestFit,
LargeFit,
}
mod imp { mod imp {
use super::*; use super::*;
@ -134,8 +141,10 @@ mod imp {
/// Set if an error has occurred, shown on error_page /// Set if an error has occurred, shown on error_page
#[property(get)] #[property(get)]
pub(super) error: RefCell<Option<String>>, pub(super) error: RefCell<Option<String>>,
//pub(super) format: RefCell<Option<decoder::ImageFormat>>,
pub(super) background_color: RefCell<Option<gdk::RGBA>>, pub(super) background_color: RefCell<Option<gdk::RGBA>>,
pub(super) fixed_background_color: RefCell<Option<gdk::RGBA>>,
/// Animations disabled
pub(super) still: Cell<bool>,
/// 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>>,
@ -170,6 +179,8 @@ mod imp {
/// Always fit image into window, causes `zoom` to change automatically /// Always fit image into window, causes `zoom` to change automatically
#[property(get, set)] #[property(get, set)]
pub(super) best_fit: Cell<bool>, pub(super) best_fit: Cell<bool>,
/// Determines what `best-fit` does
pub(super) fit_mode: Cell<FitMode>,
/// Max zoom level is reached, stored to only send signals on change /// Max zoom level is reached, stored to only send signals on change
#[property(get, set)] #[property(get, set)]
pub(super) is_max_zoom: Cell<bool>, pub(super) is_max_zoom: Cell<bool>,
@ -224,6 +235,10 @@ mod imp {
type ParentType = gtk::Widget; type ParentType = gtk::Widget;
type Type = super::LpImage; type Type = super::LpImage;
type Interfaces = (gtk::Scrollable,); type Interfaces = (gtk::Scrollable,);
fn class_init(klass: &mut Self::Class) {
klass.set_css_name("lpimage")
}
} }
impl ObjectImpl for LpImage { impl ObjectImpl for LpImage {
@ -310,3 +325,16 @@ glib::wrapper! {
@extends gtk::Widget, @extends gtk::Widget,
@implements gtk::Scrollable; @implements gtk::Scrollable;
} }
impl LpImage {
pub fn new() -> Self {
glib::Object::new()
}
/// Rerturns new not animated image
pub fn new_still() -> Self {
let image = Self::new();
image.imp().still.replace(true);
image
}
}

View file

@ -22,10 +22,20 @@ impl LpImage {
/// ///
/// Returns the default color if no one has been guessed yet /// Returns the default color if no one has been guessed yet
pub fn background_color(&self) -> gdk::RGBA { pub fn background_color(&self) -> gdk::RGBA {
(*self.imp().background_color.borrow()).unwrap_or_else(Self::default_background_color) let imp = self.imp();
if let Some(fixed) = *imp.fixed_background_color.borrow() {
return fixed;
}
(*imp.background_color.borrow()).unwrap_or_else(Self::default_background_color)
} }
pub fn set_background_color(&self, color: Option<gdk::RGBA>) { pub fn set_fixed_background_color(&self, color: Option<gdk::RGBA>) {
self.imp().fixed_background_color.replace(color);
}
pub(super) fn set_background_color(&self, color: Option<gdk::RGBA>) {
self.imp().background_color.replace(color); self.imp().background_color.replace(color);
} }
@ -51,6 +61,12 @@ impl LpImage {
/// For non-transparent images this always returns /// For non-transparent images this always returns
/// `BACKGROUND_COLOR_DEFAULT` /// `BACKGROUND_COLOR_DEFAULT`
pub async fn background_color_guess(&self) -> Option<gdk::RGBA> { pub async fn background_color_guess(&self) -> Option<gdk::RGBA> {
let imp = self.imp();
if imp.fixed_background_color.borrow().is_some() {
return None;
}
// Shortcut for formats that don't support transparency // Shortcut for formats that don't support transparency
if !self if !self
.metadata() .metadata()

View file

@ -146,9 +146,14 @@ impl LpImage {
self.emmit_metadata_changed(); self.emmit_metadata_changed();
} }
DecoderUpdate::Animated => { DecoderUpdate::Animated => {
let callback_id = self if imp.still.get() {
// Just load the first frame
self.imp().frame_buffer.next_frame();
} else {
let callback_id = self
.add_tick_callback(glib::clone!(@weak self as obj => @default-return glib::ControlFlow::Break, move |_, clock| obj.tick_callback(clock))); .add_tick_callback(glib::clone!(@weak self as obj => @default-return glib::ControlFlow::Break, move |_, clock| obj.tick_callback(clock)));
imp.tick_callback.replace(Some(callback_id)); imp.tick_callback.replace(Some(callback_id));
}
} }
DecoderUpdate::UnsupportedFormat => { DecoderUpdate::UnsupportedFormat => {
self.set_unsupported(true); self.set_unsupported(true);

View file

@ -49,16 +49,23 @@ impl LpImage {
let texture_aspect_ratio = image_width / image_height; let texture_aspect_ratio = image_width / image_height;
let widget_aspect_ratio = self.width() as f64 / self.height() as f64; let widget_aspect_ratio = self.width() as f64 / self.height() as f64;
let max_zoom_factor = match self.imp().fit_mode.get() {
// Do not allow to zoom larger than original size
FitMode::BestFit => 1.,
// Allow arbitrary zoom
FitMode::LargeFit => f64::MAX,
};
let default_zoom = if texture_aspect_ratio > widget_aspect_ratio { let default_zoom = if texture_aspect_ratio > widget_aspect_ratio {
(self.width() as f64 / image_width).min(1.) (self.width() as f64 / image_width).min(max_zoom_factor)
} else { } else {
(self.height() as f64 / image_height).min(1.) (self.height() as f64 / image_height).min(max_zoom_factor)
}; };
let rotated_zoom = if 1. / texture_aspect_ratio > widget_aspect_ratio { let rotated_zoom = if 1. / texture_aspect_ratio > widget_aspect_ratio {
(self.width() as f64 / image_height).min(1.) (self.width() as f64 / image_height).min(max_zoom_factor)
} else { } else {
(self.height() as f64 / image_width).min(1.) (self.height() as f64 / image_width).min(max_zoom_factor)
}; };
rotated * rotated_zoom + (1. - rotated) * default_zoom rotated * rotated_zoom + (1. - rotated) * default_zoom
@ -80,6 +87,11 @@ impl LpImage {
self.imp().best_fit.get() self.imp().best_fit.get()
} }
pub fn set_fit_mode(&self, fit_mode: FitMode) {
self.imp().fit_mode.replace(fit_mode);
self.configure_best_fit();
}
pub(super) fn applicable_zoom(&self) -> f64 { pub(super) fn applicable_zoom(&self) -> f64 {
decoder::tiling::zoom_normalize(self.zoom()) / self.scaling() decoder::tiling::zoom_normalize(self.zoom()) / self.scaling()
} }

View file

@ -479,11 +479,15 @@ glib::wrapper! {
impl LpPrint { impl LpPrint {
pub fn new( pub fn new(
image: LpImage, image1: LpImage,
parent_window: gtk::Window, parent_window: gtk::Window,
print_settings: Option<gtk::PrintSettings>, print_settings: Option<gtk::PrintSettings>,
page_setup: Option<gtk::PageSetup>, page_setup: Option<gtk::PageSetup>,
) -> Self { ) -> Self {
let image = LpImage::new_still();
image.set_fixed_background_color(Some(gdk::RGBA::new(0.94, 0.94, 0.94, 1.)));
let image2 = image.clone();
glib::spawn_future_local(async move { image2.load(&image1.file().unwrap()).await });
let print_settings = print_settings.unwrap_or_default(); let print_settings = print_settings.unwrap_or_default();
let obj: Self = glib::Object::builder() let obj: Self = glib::Object::builder()

View file

@ -17,14 +17,17 @@
//! Print preview widget that shows a fake page with the image on it //! Print preview widget that shows a fake page with the image on it
use std::cell::RefCell;
use adw::subclass::prelude::*; use adw::subclass::prelude::*;
use glib::Properties; use glib::Properties;
use gtk::prelude::*; use gtk::prelude::*;
use once_cell::sync::OnceCell;
use crate::deps::*; use crate::deps::*;
use crate::widgets::LpPrint; use crate::widgets::LpPrint;
use super::image;
mod imp_page { mod imp_page {
use super::*; use super::*;
@ -47,42 +50,35 @@ mod imp_page {
self.parent_constructed(); self.parent_constructed();
self.obj().set_overflow(gtk::Overflow::Hidden); self.obj().set_overflow(gtk::Overflow::Hidden);
} }
fn dispose(&self) {
self.obj().first_child().unwrap().unparent();
}
} }
impl WidgetImpl for LpPrintPreviewPage { impl WidgetImpl for LpPrintPreviewPage {
fn snapshot(&self, snapshot: &gtk::Snapshot) { fn size_allocate(&self, _width: i32, _height: i32, _baseline: i32) {
snapshot.save();
let scale = self.print().user_scale_original() * self.display_scale();
let margin_left = self.print().effective_margin_left() * self.display_scale(); let margin_left = self.print().effective_margin_left() * self.display_scale();
let margin_top = self.print().effective_margin_top() * self.display_scale(); let margin_top = self.print().effective_margin_top() * self.display_scale();
let width = self.print().user_width() * self.display_scale();
let height = self.print().user_height() * self.display_scale();
if let Some(texture) = self.print().image().print_data(scale) { let image = self.print().image();
let area = graphene::Rect::new(
margin_left as f32,
margin_top as f32,
texture.width() as f32,
texture.height() as f32,
);
snapshot.append_texture(&texture, &area); image.allocate(
width as i32,
// Border around image height as i32,
snapshot.append_inset_shadow( -1,
&gsk::RoundedRect::from_rect(area, 0.), Some(
&gdk::RGBA::new(0., 0., 0., 0.3), gsk::Transform::new()
0., .translate(&graphene::Point::new(margin_left as f32, margin_top as f32)),
0., ),
1., );
0.,
);
}
snapshot.restore();
} }
} }
impl LpPrintPreviewPage { impl LpPrintPreviewPage {
fn preview(&self) -> super::LpPrintPreview { pub(super) fn preview(&self) -> super::LpPrintPreview {
self.obj().parent().unwrap().downcast().unwrap() self.obj().parent().unwrap().downcast().unwrap()
} }
@ -99,13 +95,14 @@ mod imp_page {
} }
mod imp { mod imp {
use super::*; use super::*;
#[derive(Debug, Default, Properties)] #[derive(Debug, Default, Properties)]
#[properties(wrapper_type = super::LpPrintPreview)] #[properties(wrapper_type = super::LpPrintPreview)]
pub struct LpPrintPreview { pub struct LpPrintPreview {
#[property(get, set)] #[property(type = LpPrint, get, set = Self::set_print)]
print: OnceCell<LpPrint>, print: RefCell<Option<LpPrint>>,
page: LpPrintPreviewPage, page: LpPrintPreviewPage,
} }
@ -127,6 +124,10 @@ mod imp {
self.page.insert_after(&*self.obj(), gtk::Widget::NONE); self.page.insert_after(&*self.obj(), gtk::Widget::NONE);
} }
fn dispose(&self) {
self.page.unparent();
}
fn properties() -> &'static [glib::ParamSpec] { fn properties() -> &'static [glib::ParamSpec] {
Self::derived_properties() Self::derived_properties()
} }
@ -152,11 +153,21 @@ mod imp {
baseline, baseline,
); );
self.page.queue_draw(); self.page.queue_resize();
} }
} }
impl LpPrintPreview { impl LpPrintPreview {
fn set_print(&self, print: LpPrint) {
let page = self.page.clone();
print.connect_image_notify(move |_| {
page.init();
});
self.print.replace(Some(print));
self.obj().notify_print();
}
fn aspect_preserving_size(&self, for_width: i32, for_height: i32) -> (i32, i32) { fn aspect_preserving_size(&self, for_width: i32, for_height: i32) -> (i32, i32) {
let print = self.obj().print(); let print = self.obj().print();
@ -182,6 +193,17 @@ impl Default for LpPrintPreviewPage {
} }
} }
impl LpPrintPreviewPage {
fn init(&self) {
let imp = self.imp();
let image = imp.preview().print().image();
image.set_sensitive(false);
image.set_parent(self);
image.set_fit_mode(image::FitMode::LargeFit);
}
}
glib::wrapper! { glib::wrapper! {
pub struct LpPrintPreview(ObjectSubclass<imp::LpPrintPreview>) pub struct LpPrintPreview(ObjectSubclass<imp::LpPrintPreview>)
@extends gtk::Widget; @extends gtk::Widget;