2024-01-01 22:41:56 +01:00
|
|
|
// Copyright (c) 2023-2024 Sophie Herold
|
2024-07-17 19:06:34 +02:00
|
|
|
// Copyright (c) 2024 Fina Wilke
|
2023-12-05 15:57:18 +01:00
|
|
|
//
|
|
|
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
|
//
|
|
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
|
2024-07-19 23:19:34 +02:00
|
|
|
use decoder::DecoderError;
|
|
|
|
|
|
2023-12-05 15:57:18 +01:00
|
|
|
use super::*;
|
|
|
|
|
|
2023-12-26 23:24:19 +01:00
|
|
|
impl imp::LpImage {
|
2023-12-05 15:57:18 +01:00
|
|
|
// Set filename when (re)loading image
|
|
|
|
|
async fn set_file_loaded(&self, file: &gio::File) {
|
|
|
|
|
// Do not check if same file since it might be set via initialized with
|
|
|
|
|
// `Self::init` before
|
2023-12-26 23:24:19 +01:00
|
|
|
self.file.replace(Some(file.clone()));
|
2023-12-05 15:57:18 +01:00
|
|
|
self.reload_file_info().await;
|
|
|
|
|
self.setup_file_monitor().await;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Set filename after filename changed
|
|
|
|
|
async fn set_file_changed(&self, file: &gio::File) {
|
2023-12-26 23:24:19 +01:00
|
|
|
let obj = self.obj();
|
|
|
|
|
let prev_mime_type = obj.metadata().mime_type();
|
2023-12-05 15:57:18 +01:00
|
|
|
|
2023-12-26 23:24:19 +01:00
|
|
|
self.file.replace(Some(file.clone()));
|
2023-12-05 15:57:18 +01:00
|
|
|
self.reload_file_info().await;
|
|
|
|
|
|
|
|
|
|
// Reload if mime type changed since other decoding path might be responsible
|
|
|
|
|
// now
|
2023-12-26 23:24:19 +01:00
|
|
|
if obj.metadata().mime_type() != prev_mime_type {
|
|
|
|
|
obj.load(file).await;
|
2023-12-05 15:57:18 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.setup_file_monitor().await;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn setup_file_monitor(&self) {
|
2023-12-26 23:24:19 +01:00
|
|
|
let obj = self.obj();
|
2023-12-05 15:57:18 +01:00
|
|
|
|
2023-12-26 23:24:19 +01:00
|
|
|
if let Some(file) = obj.file() {
|
2023-12-05 15:57:18 +01:00
|
|
|
let monitor =
|
|
|
|
|
file.monitor_file(gio::FileMonitorFlags::WATCH_MOVES, gio::Cancellable::NONE);
|
|
|
|
|
if let Ok(m) = &monitor {
|
2024-06-18 00:37:38 +02:00
|
|
|
m.connect_changed(glib::clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |_, file_a, file_b, event| {
|
|
|
|
|
obj.imp().file_changed(event, file_a, file_b);
|
|
|
|
|
}
|
|
|
|
|
));
|
2023-12-05 15:57:18 +01:00
|
|
|
}
|
|
|
|
|
|
2023-12-26 23:24:19 +01:00
|
|
|
self.file_monitor.replace(monitor.ok());
|
2023-12-05 15:57:18 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Called when decoder sends update
|
|
|
|
|
pub fn update(&self, update: DecoderUpdate) {
|
2023-12-26 23:24:19 +01:00
|
|
|
let obj = self.obj();
|
2023-12-05 15:57:18 +01:00
|
|
|
|
|
|
|
|
match update {
|
|
|
|
|
DecoderUpdate::Metadata(metadata) => {
|
|
|
|
|
log::debug!("Received metadata");
|
2024-01-01 22:41:56 +01:00
|
|
|
self.metadata.borrow_mut().merge(*metadata);
|
2023-12-05 15:57:18 +01:00
|
|
|
self.emmit_metadata_changed();
|
|
|
|
|
|
2023-12-26 23:24:19 +01:00
|
|
|
obj.reset_rotation();
|
2023-12-05 15:57:18 +01:00
|
|
|
}
|
2024-01-01 22:41:56 +01:00
|
|
|
DecoderUpdate::Dimensions => {
|
2023-12-05 15:57:18 +01:00
|
|
|
log::debug!("Received dimensions: {:?}", self.original_dimensions());
|
2023-12-26 23:24:19 +01:00
|
|
|
obj.notify_image_size_available();
|
2023-12-05 15:57:18 +01:00
|
|
|
self.configure_best_fit();
|
|
|
|
|
self.request_tiles();
|
2023-12-05 21:33:19 +01:00
|
|
|
self.configure_adjustments();
|
2023-12-05 15:57:18 +01:00
|
|
|
}
|
|
|
|
|
DecoderUpdate::Redraw => {
|
|
|
|
|
self.set_loaded(true);
|
|
|
|
|
|
2023-12-26 23:24:19 +01:00
|
|
|
obj.queue_draw();
|
|
|
|
|
self.frame_buffer.rcu(|tiles| {
|
2023-12-05 15:57:18 +01:00
|
|
|
let mut new_tiles = (**tiles).clone();
|
2023-12-26 23:24:19 +01:00
|
|
|
new_tiles.cleanup(self.zoom_target.get(), self.preload_area());
|
2023-12-05 15:57:18 +01:00
|
|
|
new_tiles
|
|
|
|
|
});
|
2023-12-26 23:24:19 +01:00
|
|
|
if self.background_color.borrow().is_none() {
|
2024-06-18 00:37:38 +02:00
|
|
|
glib::spawn_future_local(glib::clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
async move {
|
|
|
|
|
let imp = obj.imp();
|
2023-12-26 23:24:19 +01:00
|
|
|
|
2024-06-18 00:37:38 +02:00
|
|
|
let color = imp.background_color_guess().await;
|
|
|
|
|
imp.set_background_color(color);
|
|
|
|
|
if obj.is_mapped() {
|
|
|
|
|
obj.queue_draw();
|
|
|
|
|
}
|
2023-12-05 15:57:18 +01:00
|
|
|
}
|
2024-06-18 00:37:38 +02:00
|
|
|
));
|
2023-12-05 15:57:18 +01:00
|
|
|
}
|
|
|
|
|
}
|
2024-07-19 23:19:34 +02:00
|
|
|
DecoderUpdate::GenericError(err) => {
|
2023-12-05 15:57:18 +01:00
|
|
|
self.set_error(Some(err));
|
|
|
|
|
}
|
|
|
|
|
DecoderUpdate::Animated => {
|
2023-12-26 23:24:19 +01:00
|
|
|
if self.still.get() {
|
2023-12-25 20:18:26 +01:00
|
|
|
// Just load the first frame
|
2023-12-26 23:24:19 +01:00
|
|
|
self.frame_buffer.next_frame();
|
2023-12-25 20:18:26 +01:00
|
|
|
} else {
|
2024-06-18 00:37:38 +02:00
|
|
|
let callback_id = obj.add_tick_callback(glib::clone!(
|
|
|
|
|
#[weak(rename_to = obj)]
|
|
|
|
|
self,
|
2024-06-23 22:28:16 +02:00
|
|
|
#[upgrade_or]
|
|
|
|
|
glib::ControlFlow::Break,
|
2024-06-18 00:37:38 +02:00
|
|
|
move |_, clock| obj.tick_callback(clock)
|
|
|
|
|
));
|
2023-12-26 23:24:19 +01:00
|
|
|
self.tick_callback.replace(Some(callback_id));
|
2023-12-25 20:18:26 +01:00
|
|
|
}
|
2023-12-05 15:57:18 +01:00
|
|
|
}
|
2024-07-19 23:19:34 +02:00
|
|
|
DecoderUpdate::SpecificError(err) => {
|
|
|
|
|
self.set_specific_error(err);
|
2023-12-05 15:57:18 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Roughly called for every frame if image is visible
|
|
|
|
|
///
|
|
|
|
|
/// We handle advancing to the next frame for animated GIFs etc here.
|
|
|
|
|
fn tick_callback(&self, clock: &gdk::FrameClock) -> glib::ControlFlow {
|
2023-12-26 23:24:19 +01:00
|
|
|
let obj = self.obj();
|
|
|
|
|
|
2023-12-05 15:57:18 +01:00
|
|
|
// Do not animate if not visible
|
2023-12-26 23:24:19 +01:00
|
|
|
if !obj.is_mapped() {
|
2023-12-05 15:57:18 +01:00
|
|
|
return glib::ControlFlow::Continue;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-26 23:24:19 +01:00
|
|
|
let elapsed = clock.frame_time() - self.last_animated_frame.get();
|
2023-12-05 15:57:18 +01:00
|
|
|
let duration = std::time::Duration::from_micros(elapsed as u64);
|
|
|
|
|
|
|
|
|
|
// Check if it's time to show the next frame
|
2023-12-26 23:24:19 +01:00
|
|
|
if self.frame_buffer.frame_timeout(duration) {
|
2023-12-05 15:57:18 +01:00
|
|
|
// Just draw since frame_timeout updated to new frame
|
2023-12-26 23:24:19 +01:00
|
|
|
obj.queue_draw();
|
|
|
|
|
self.last_animated_frame.set(clock.frame_time());
|
|
|
|
|
if let Some(decoder) = self.decoder.borrow().as_ref() {
|
2023-12-05 15:57:18 +01:00
|
|
|
// Decode new frame and load it into buffer
|
|
|
|
|
decoder.fill_frame_buffer();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glib::ControlFlow::Continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn preload_area(&self) -> graphene::Rect {
|
|
|
|
|
let viewport = self.viewport();
|
|
|
|
|
viewport.inset_r(-viewport.width() / 3., -viewport.height() / 3.)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub(super) fn request_tiles(&self) {
|
2023-12-26 23:24:19 +01:00
|
|
|
if let Some(decoder) = self.decoder.borrow().as_ref() {
|
2023-12-05 15:57:18 +01:00
|
|
|
if self.zoom_animation().state() != adw::AnimationState::Playing {
|
|
|
|
|
// Force minimum tile size of 1000x1000 since with smaller
|
|
|
|
|
// tiles the tiled rendering advantage disappears
|
|
|
|
|
let x_inset = f32::min(-3., (self.viewport().width() - 1000.) / 2.);
|
|
|
|
|
let y_inset = f32::min(-3., (self.viewport().height() - 1000.) / 2.);
|
|
|
|
|
|
|
|
|
|
decoder.request(crate::decoder::TileRequest {
|
|
|
|
|
viewport: self.viewport().inset_r(x_inset, y_inset),
|
|
|
|
|
area: self.preload_area(),
|
2023-12-26 23:24:19 +01:00
|
|
|
zoom: self.zoom_target.get(),
|
2023-12-05 15:57:18 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// File changed on drive
|
|
|
|
|
fn file_changed(
|
|
|
|
|
&self,
|
|
|
|
|
event: gio::FileMonitorEvent,
|
|
|
|
|
file_a: &gio::File,
|
|
|
|
|
file_b: Option<&gio::File>,
|
|
|
|
|
) {
|
|
|
|
|
match event {
|
|
|
|
|
gio::FileMonitorEvent::Renamed => {
|
|
|
|
|
if let Some(file) = file_b.cloned() {
|
|
|
|
|
log::debug!("Moved to {}", file.uri());
|
2023-12-26 23:24:19 +01:00
|
|
|
let obj = self.obj().to_owned();
|
|
|
|
|
|
2023-12-05 15:57:18 +01:00
|
|
|
// current file got replaced with a new one
|
2023-12-26 23:24:19 +01:00
|
|
|
let file_replace = obj.file().map_or(false, |x| x.equal(&file));
|
2023-12-05 15:57:18 +01:00
|
|
|
if file_replace {
|
|
|
|
|
log::debug!("Image got replaced {}", file.uri());
|
|
|
|
|
// TODO: error handling is missing
|
|
|
|
|
glib::spawn_future_local(async move {
|
|
|
|
|
obj.load(&file).await;
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
glib::spawn_future_local(async move {
|
2023-12-26 23:24:19 +01:00
|
|
|
obj.imp().set_file_changed(&file).await;
|
2023-12-05 15:57:18 +01:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
gio::FileMonitorEvent::ChangesDoneHint => {
|
2023-12-26 23:24:19 +01:00
|
|
|
let obj = self.obj().to_owned();
|
2023-12-05 15:57:18 +01:00
|
|
|
let file = file_a.clone();
|
|
|
|
|
log::debug!("Image was edited {}", file.uri());
|
|
|
|
|
// TODO: error handling is missing
|
|
|
|
|
glib::spawn_future_local(async move {
|
|
|
|
|
obj.load(&file).await;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
gio::FileMonitorEvent::Deleted
|
|
|
|
|
| gio::FileMonitorEvent::MovedOut
|
|
|
|
|
| gio::FileMonitorEvent::Unmounted => {
|
|
|
|
|
log::debug!("File no longer available: {event:?} {}", file_a.uri());
|
2023-12-26 23:24:19 +01:00
|
|
|
self.is_deleted.set(true);
|
|
|
|
|
self.obj().notify_is_deleted();
|
2023-12-05 15:57:18 +01:00
|
|
|
}
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_error(&self, err: Option<anyhow::Error>) {
|
|
|
|
|
log::debug!("Decoding error: {err:?}");
|
2024-02-29 14:21:26 +01:00
|
|
|
|
|
|
|
|
// Keeping first error instead of replacing with new error
|
|
|
|
|
if err.is_some() && self.error.borrow().is_some() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-26 23:24:19 +01:00
|
|
|
self.error.replace(err.as_ref().map(|x| x.to_string()));
|
|
|
|
|
self.obj().notify_error();
|
2023-12-05 15:57:18 +01:00
|
|
|
|
|
|
|
|
if err.is_some() {
|
|
|
|
|
self.set_loaded(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-19 23:19:34 +02:00
|
|
|
fn set_specific_error(&self, error: DecoderError) {
|
2023-12-26 23:24:19 +01:00
|
|
|
let obj = self.obj();
|
2024-07-19 23:19:34 +02:00
|
|
|
if obj.specific_error() != error {
|
|
|
|
|
self.specific_error.set(error);
|
|
|
|
|
obj.notify_specific_error();
|
2023-12-05 15:57:18 +01:00
|
|
|
|
2024-07-19 23:19:34 +02:00
|
|
|
if error.is_err() {
|
2023-12-05 15:57:18 +01:00
|
|
|
self.set_loaded(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_loaded(&self, is_loaded: bool) {
|
2023-12-26 23:24:19 +01:00
|
|
|
let obj = self.obj();
|
|
|
|
|
|
|
|
|
|
if obj.is_loaded() != is_loaded {
|
|
|
|
|
self.is_loaded.set(is_loaded);
|
|
|
|
|
obj.notify_is_loaded();
|
2023-12-05 15:57:18 +01:00
|
|
|
|
|
|
|
|
if is_loaded {
|
|
|
|
|
self.set_error(None);
|
2024-07-19 23:19:34 +02:00
|
|
|
self.set_specific_error(DecoderError::None);
|
2023-12-05 15:57:18 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-12-26 23:24:19 +01:00
|
|
|
|
|
|
|
|
impl LpImage {
|
|
|
|
|
pub fn init(&self, file: &gio::File) {
|
|
|
|
|
self.imp().file.replace(Some(file.clone()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn load(&self, file: &gio::File) {
|
|
|
|
|
let imp = self.imp();
|
|
|
|
|
log::debug!("Loading file {}", file.uri());
|
|
|
|
|
|
2024-06-19 00:36:54 +02:00
|
|
|
if imp.rotation_animation().state() == adw::AnimationState::Playing {
|
|
|
|
|
log::debug!("Queueing image reload due to playing rotate animation.");
|
|
|
|
|
self.imp().queued_reload.replace(Some(file.clone()));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-26 23:24:19 +01:00
|
|
|
imp.metadata.replace(Metadata::default());
|
|
|
|
|
self.imp().emmit_metadata_changed();
|
|
|
|
|
self.imp().set_file_loaded(file).await;
|
|
|
|
|
|
|
|
|
|
let tiles = &self.imp().frame_buffer;
|
|
|
|
|
// Delete all stored textures for reloads
|
|
|
|
|
tiles.reset();
|
|
|
|
|
// Reset background color for reloads
|
|
|
|
|
imp.set_background_color(None);
|
|
|
|
|
|
|
|
|
|
let (decoder, mut decoder_update) =
|
|
|
|
|
Decoder::new(file.clone(), self.metadata().mime_type(), tiles.clone()).await;
|
|
|
|
|
|
|
|
|
|
let weak_obj = self.downgrade();
|
|
|
|
|
glib::spawn_future_local(async move {
|
|
|
|
|
while let Some(update) = decoder_update.next().await {
|
|
|
|
|
if let Some(obj) = weak_obj.upgrade() {
|
|
|
|
|
obj.imp().update(update);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
log::debug!("Stopped listening to decoder since sender is gone");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
imp.decoder.replace(Some(Arc::new(decoder)));
|
|
|
|
|
}
|
|
|
|
|
}
|