parent
50450ad359
commit
c36e7f4eb6
6 changed files with 279 additions and 458 deletions
|
|
@ -46,6 +46,10 @@ impl LpImageMetadata {
|
||||||
pub fn gps_location(&self) -> Option<GPSLocation> {
|
pub fn gps_location(&self) -> Option<GPSLocation> {
|
||||||
self.imp().metadata.borrow().gps_location()
|
self.imp().metadata.borrow().gps_location()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn src(&self) -> std::cell::Ref<super::ImageMetadata> {
|
||||||
|
self.imp().metadata.borrow()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<ImageMetadata> for LpImageMetadata {
|
impl From<ImageMetadata> for LpImageMetadata {
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,7 @@ mod imp {
|
||||||
pub(super) _vscroll_policy: PhantomData<gtk::ScrollablePolicy>,
|
pub(super) _vscroll_policy: PhantomData<gtk::ScrollablePolicy>,
|
||||||
|
|
||||||
/// Currently EXIF data
|
/// Currently EXIF data
|
||||||
#[property(name = "metadata", get)]
|
#[property(name = "metadata", type=LpImageMetadata, 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>,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
// Copyright (c) 2022-2023 Sophie Herold
|
// Copyright (c) 2022-2023 Sophie Herold
|
||||||
// Copyright (c) 2022-2023 Christopher Davis
|
// Copyright (c) 2022 Christopher Davis
|
||||||
// Copyright (c) 2023 Julian Hofer
|
|
||||||
//
|
//
|
||||||
// This program is free software: you can redistribute it and/or modify
|
// 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
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -23,14 +22,11 @@ use crate::util;
|
||||||
|
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use adw::subclass::prelude::*;
|
use adw::subclass::prelude::*;
|
||||||
use glib::clone;
|
use glib::Properties;
|
||||||
use gtk::CompositeTemplate;
|
use gtk::CompositeTemplate;
|
||||||
|
|
||||||
use futures::future::{AbortHandle, Abortable};
|
use std::cell::{OnceCell, RefCell};
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use std::cell::RefCell;
|
|
||||||
|
|
||||||
use crate::image_metadata::LpImageMetadata;
|
|
||||||
use crate::widgets::image::LpImage;
|
use crate::widgets::image::LpImage;
|
||||||
|
|
||||||
const FALLBACK: &str = "–";
|
const FALLBACK: &str = "–";
|
||||||
|
|
@ -38,16 +34,45 @@ const FALLBACK: &str = "–";
|
||||||
mod imp {
|
mod imp {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Debug, Default, CompositeTemplate)]
|
#[derive(Debug, Default, CompositeTemplate, Properties)]
|
||||||
|
#[properties(wrapper_type = super::LpPropertiesView)]
|
||||||
#[template(file = "properties_view.ui")]
|
#[template(file = "properties_view.ui")]
|
||||||
pub struct LpPropertiesView {
|
pub struct LpPropertiesView {
|
||||||
pub(super) file: RefCell<Option<glib::WeakRef<gio::File>>>,
|
#[property(nullable, set = Self::set_image, get)]
|
||||||
pub(super) image: RefCell<Option<glib::WeakRef<LpImage>>>,
|
image: RefCell<Option<LpImage>>,
|
||||||
|
image_signals: OnceCell<glib::SignalGroup>,
|
||||||
|
|
||||||
pub(super) metadata: RefCell<LpImageMetadata>,
|
#[template_child]
|
||||||
|
folder: TemplateChild<adw::ActionRow>,
|
||||||
|
|
||||||
pub(super) file_info: RefCell<Option<gio::FileInfo>>,
|
#[template_child]
|
||||||
pub(super) info_handle: RefCell<Option<AbortHandle>>,
|
image_size: TemplateChild<adw::ActionRow>,
|
||||||
|
#[template_child]
|
||||||
|
image_format: TemplateChild<adw::ActionRow>,
|
||||||
|
#[template_child]
|
||||||
|
file_size: TemplateChild<adw::ActionRow>,
|
||||||
|
|
||||||
|
#[template_child]
|
||||||
|
file_created: TemplateChild<adw::ActionRow>,
|
||||||
|
#[template_child]
|
||||||
|
file_modified: TemplateChild<adw::ActionRow>,
|
||||||
|
|
||||||
|
#[template_child]
|
||||||
|
details: TemplateChild<adw::PreferencesGroup>,
|
||||||
|
#[template_child]
|
||||||
|
location: TemplateChild<adw::ActionRow>,
|
||||||
|
#[template_child]
|
||||||
|
originally_created: TemplateChild<adw::ActionRow>,
|
||||||
|
#[template_child]
|
||||||
|
aperture: TemplateChild<adw::ActionRow>,
|
||||||
|
#[template_child]
|
||||||
|
exposure: TemplateChild<adw::ActionRow>,
|
||||||
|
#[template_child]
|
||||||
|
iso: TemplateChild<adw::ActionRow>,
|
||||||
|
#[template_child]
|
||||||
|
focal_length: TemplateChild<adw::ActionRow>,
|
||||||
|
#[template_child]
|
||||||
|
maker_model: TemplateChild<adw::ActionRow>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
|
|
@ -58,18 +83,13 @@ mod imp {
|
||||||
|
|
||||||
fn class_init(klass: &mut Self::Class) {
|
fn class_init(klass: &mut Self::Class) {
|
||||||
Self::bind_template(klass);
|
Self::bind_template(klass);
|
||||||
Self::Type::bind_template_callbacks(klass);
|
|
||||||
|
|
||||||
klass.install_action_async(
|
klass.install_action_async("properties.open-folder", None, |obj, _, _| async move {
|
||||||
"properties.open-folder",
|
obj.imp().open_directory().await;
|
||||||
None,
|
});
|
||||||
|properties, _, _| async move {
|
|
||||||
let _ = properties.open_directory().await;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
klass.install_action("properties.open-location", None, move |properties, _, _| {
|
klass.install_action("properties.open-location", None, move |obj, _, _| {
|
||||||
properties.open_location();
|
obj.imp().open_location();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,63 +99,165 @@ mod imp {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectImpl for LpPropertiesView {
|
impl ObjectImpl for LpPropertiesView {
|
||||||
fn properties() -> &'static [glib::ParamSpec] {
|
|
||||||
static PROPERTIES: Lazy<Vec<glib::ParamSpec>> = Lazy::new(|| {
|
|
||||||
vec![
|
|
||||||
glib::ParamSpecObject::builder::<gio::File>("file")
|
|
||||||
.explicit_notify()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecObject::builder::<gio::FileInfo>("file-info")
|
|
||||||
.read_only()
|
|
||||||
.explicit_notify()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecObject::builder::<LpImage>("image")
|
|
||||||
.explicit_notify()
|
|
||||||
.build(),
|
|
||||||
glib::ParamSpecObject::builder::<LpImageMetadata>("metadata")
|
|
||||||
.explicit_notify()
|
|
||||||
.build(),
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
PROPERTIES.as_ref()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn property(&self, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
|
||||||
let obj = self.obj();
|
|
||||||
let prop_name = pspec.name();
|
|
||||||
|
|
||||||
match prop_name {
|
|
||||||
"file" => obj.file().to_value(),
|
|
||||||
"image" => obj.image().to_value(),
|
|
||||||
"metadata" => self.metadata.borrow().to_value(),
|
|
||||||
"file-info" => obj.file_info().to_value(),
|
|
||||||
_ => unimplemented!("Failed to get property \"{prop_name}\""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_property(&self, _id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
|
||||||
let obj = self.obj();
|
|
||||||
let prop_name = pspec.name();
|
|
||||||
|
|
||||||
match prop_name {
|
|
||||||
"image" => obj.set_image(value.get().ok().as_ref()),
|
|
||||||
"file" => obj.set_file(value.get().ok().as_ref()),
|
|
||||||
"metadata" => obj.set_metadata(value.get().unwrap()),
|
|
||||||
_ => unimplemented!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn constructed(&self) {
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
|
||||||
let obj = self.obj();
|
let obj = self.obj();
|
||||||
|
|
||||||
self.parent_constructed();
|
let image_signals = self.image_signals();
|
||||||
obj.action_set_enabled("properties.open-folder", false);
|
|
||||||
|
image_signals.connect_notify_local(
|
||||||
|
Some("image-size-available"),
|
||||||
|
glib::clone!(@weak obj => move |_,_| obj.imp().update()),
|
||||||
|
);
|
||||||
|
image_signals.connect_notify_local(
|
||||||
|
Some("metadata"),
|
||||||
|
glib::clone!(@weak obj => move |_,_| obj.imp().update()),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn properties() -> &'static [glib::ParamSpec] {
|
||||||
|
Self::derived_properties()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_property(&self, id: usize, value: &glib::Value, pspec: &glib::ParamSpec) {
|
||||||
|
self.derived_set_property(id, value, pspec)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
||||||
|
self.derived_property(id, pspec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WidgetImpl for LpPropertiesView {}
|
impl WidgetImpl for LpPropertiesView {}
|
||||||
impl BinImpl for LpPropertiesView {}
|
impl BinImpl for LpPropertiesView {}
|
||||||
|
|
||||||
|
impl LpPropertiesView {
|
||||||
|
fn set_image(&self, image: Option<LpImage>) {
|
||||||
|
self.image.replace(image.clone());
|
||||||
|
self.image_signals().set_target(image.as_ref());
|
||||||
|
self.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn image_signals(&self) -> &glib::SignalGroup {
|
||||||
|
self.image_signals
|
||||||
|
.get_or_init(glib::SignalGroup::new::<LpImage>)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&self) {
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
let Some(image) = obj.image() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let Some(file) = image.file() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Folder
|
||||||
|
Self::update_row(&self.folder, self.folder_name());
|
||||||
|
obj.action_set_enabled("properties.open-folder", file.path().is_some());
|
||||||
|
|
||||||
|
// Image info
|
||||||
|
Self::update_row(&self.image_size, self.image_size());
|
||||||
|
Self::update_row(&self.image_format, image.format().map(|x| x.to_string()));
|
||||||
|
//Self::update_row(&self.file_size, self.image_size());
|
||||||
|
|
||||||
|
// Sizes
|
||||||
|
|
||||||
|
// Details
|
||||||
|
let metadata = image.metadata();
|
||||||
|
let meta = metadata.src();
|
||||||
|
let has_details = [
|
||||||
|
Self::update_row(&self.location, meta.gps_location().map(|x| x.display())),
|
||||||
|
Self::update_row(&self.originally_created, meta.originally_created()),
|
||||||
|
Self::update_row(&self.aperture, meta.f_number()),
|
||||||
|
Self::update_row(&self.exposure, meta.exposure_time()),
|
||||||
|
Self::update_row(&self.iso, meta.iso()),
|
||||||
|
Self::update_row(&self.focal_length, meta.focal_length()),
|
||||||
|
Self::update_row(&self.maker_model, meta.maker_model()),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.any(|x| *x);
|
||||||
|
self.details.set_visible(has_details);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_row(row: &adw::ActionRow, value: Option<impl AsRef<str>>) -> bool {
|
||||||
|
if let Some(value) = value {
|
||||||
|
row.set_subtitle(value.as_ref());
|
||||||
|
row.set_visible(true);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
row.set_subtitle(FALLBACK);
|
||||||
|
row.set_visible(false);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn folder_name(&self) -> Option<String> {
|
||||||
|
self.file()
|
||||||
|
.and_then(|f| f.parent())
|
||||||
|
.and_then(|p| util::get_file_display_name(&p))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn image_size(&self) -> Option<String> {
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
if let Some(image) = obj.image() {
|
||||||
|
match image.dimension_details() {
|
||||||
|
ImageDimensionDetails::Svg(string) => Some(string),
|
||||||
|
_ => {
|
||||||
|
let (width, height) = image.image_size();
|
||||||
|
if width > 0 && height > 0 {
|
||||||
|
return Some(format!("{width}\u{202F}\u{D7}\u{202F}{height}"));
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn file(&self) -> Option<gio::File> {
|
||||||
|
self.image.borrow().as_ref().and_then(|x| x.file())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn open_directory(&self) {
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
let launcher = gtk::FileLauncher::new(self.file().as_ref());
|
||||||
|
let win = obj.native().and_downcast::<gtk::Window>();
|
||||||
|
if let Err(e) = launcher.open_containing_folder_future(win.as_ref()).await {
|
||||||
|
log::error!("Could not open parent directory: {e}");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open GPS location in apps like Maps via `geo:` URI
|
||||||
|
fn open_location(&self) {
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
|
if let Some(uri) = obj
|
||||||
|
.image()
|
||||||
|
.and_then(|x| x.metadata().gps_location())
|
||||||
|
.map(|x| x.geo_uri())
|
||||||
|
{
|
||||||
|
gio::AppInfo::launch_default_for_uri_async(
|
||||||
|
&uri,
|
||||||
|
gio::AppLaunchContext::NONE,
|
||||||
|
gio::Cancellable::NONE,
|
||||||
|
|result| {
|
||||||
|
if let Err(err) = result {
|
||||||
|
log::error!("Failed to show geo URI: {err}")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
|
|
@ -144,9 +266,8 @@ glib::wrapper! {
|
||||||
@implements gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
@implements gtk::Buildable, gtk::ConstraintTarget, gtk::Orientable;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gtk::template_callbacks]
|
|
||||||
impl LpPropertiesView {
|
impl LpPropertiesView {
|
||||||
fn set_file(&self, file: Option<&gio::File>) {
|
/* fn set_file(&self, file: Option<&gio::File>) {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
|
|
||||||
self.action_set_enabled(
|
self.action_set_enabled(
|
||||||
|
|
@ -173,179 +294,5 @@ impl LpPropertiesView {
|
||||||
imp.file.replace(weak);
|
imp.file.replace(weak);
|
||||||
self.notify("file");
|
self.notify("file");
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
fn file(&self) -> Option<gio::File> {
|
|
||||||
let imp = self.imp();
|
|
||||||
imp.file.borrow().as_ref().and_then(|w| w.upgrade())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_image(&self, image: Option<&LpImage>) {
|
|
||||||
let imp = self.imp();
|
|
||||||
|
|
||||||
imp.image.replace(image.as_ref().map(|x| x.downgrade()));
|
|
||||||
self.notify("image");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn image(&self) -> Option<LpImage> {
|
|
||||||
let imp = self.imp();
|
|
||||||
imp.image.borrow().as_ref().and_then(|w| w.upgrade())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_info(&self) -> Option<gio::FileInfo> {
|
|
||||||
let imp = self.imp();
|
|
||||||
imp.file_info.borrow().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_file_info(&self, file: &gio::File) {
|
|
||||||
let imp = self.imp();
|
|
||||||
|
|
||||||
// We need to be able to cancel this future so that
|
|
||||||
// changing files before the metadata loads does not cause
|
|
||||||
// old metadata to be loaded. In order to make a cancellable
|
|
||||||
// future, we use `AbortHandle` and `Abortable` from the `futures`
|
|
||||||
// crate.
|
|
||||||
//
|
|
||||||
// `handle` is the `AbortHandle`, which we need to store in order
|
|
||||||
// to abort the future. `reg` is the `AbortRegistration` that we
|
|
||||||
// give to `Abortable::new()` to tie our future to the `AbortHandle`.
|
|
||||||
//
|
|
||||||
// Here we build the future:
|
|
||||||
let (handle, reg) = AbortHandle::new_pair();
|
|
||||||
let fut = Abortable::new(
|
|
||||||
clone!(@weak self as view, @strong file => async move {
|
|
||||||
let info_result = util::query_attributes_future(
|
|
||||||
&file,
|
|
||||||
vec![
|
|
||||||
gio::FILE_ATTRIBUTE_STANDARD_SIZE,
|
|
||||||
gio::FILE_ATTRIBUTE_TIME_CREATED,
|
|
||||||
gio::FILE_ATTRIBUTE_TIME_MODIFIED,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match info_result {
|
|
||||||
Ok(info) => view.on_info_loaded(info),
|
|
||||||
Err(err) => log::warn!("Failed to load file info: {err}"),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
reg,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ...store the handle:
|
|
||||||
imp.info_handle.replace(Some(handle));
|
|
||||||
|
|
||||||
// ...then spawn the future.
|
|
||||||
glib::spawn_future_local(async {
|
|
||||||
let _ = fut.await;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_metadata(&self, metadata: LpImageMetadata) {
|
|
||||||
let imp = self.imp();
|
|
||||||
imp.metadata.replace(metadata);
|
|
||||||
self.notify("metadata");
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is where we handle the results of the future.
|
|
||||||
// This could technically be part of the future, but we keep
|
|
||||||
// them separate for a bit of cleanliness.
|
|
||||||
fn on_info_loaded(&self, info: gio::FileInfo) {
|
|
||||||
let imp = self.imp();
|
|
||||||
imp.file_info.replace(Some(info));
|
|
||||||
self.notify("file-info");
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn open_directory(&self) -> anyhow::Result<()> {
|
|
||||||
let launcher = gtk::FileLauncher::new(self.file().as_ref());
|
|
||||||
let win = self.native().and_downcast::<gtk::Window>();
|
|
||||||
if let Err(e) = launcher.open_containing_folder_future(win.as_ref()).await {
|
|
||||||
log::error!("Could not open parent directory: {e}");
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Open GPS location in apps like Maps via `geo:` URI
|
|
||||||
fn open_location(&self) {
|
|
||||||
if let Some(uri) = self
|
|
||||||
.image()
|
|
||||||
.and_then(|x| x.metadata().gps_location())
|
|
||||||
.map(|x| x.geo_uri())
|
|
||||||
{
|
|
||||||
gio::AppInfo::launch_default_for_uri_async(
|
|
||||||
&uri,
|
|
||||||
gio::AppLaunchContext::NONE,
|
|
||||||
gio::Cancellable::NONE,
|
|
||||||
|result| {
|
|
||||||
if let Err(err) = result {
|
|
||||||
log::error!("Failed to show geo URI: {err}")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// In the LpPropertiesView UI file we define a few `gtk::Expression`s
|
|
||||||
// that are closures. These closures take either the current `gio::File`
|
|
||||||
// or the current file's associated `gio::FileInfo` and process them
|
|
||||||
// accordingly.
|
|
||||||
//
|
|
||||||
// In this function we chain `Option`s with `and_then()` in order
|
|
||||||
// to handle optional results with a fallback, without needing to
|
|
||||||
// have multiple `match` or `if let` branches, and without needing
|
|
||||||
// to unwrap.
|
|
||||||
#[template_callback]
|
|
||||||
fn folder_name(&self, file: Option<gio::File>) -> String {
|
|
||||||
file.and_then(|f| f.parent()) // If the file exists, get the parent
|
|
||||||
.and_then(|p| util::get_file_display_name(&p)) // if that exists, the display name
|
|
||||||
.unwrap_or_else(|| FALLBACK.to_owned()) // and if we get nothing,
|
|
||||||
// use `FALLBACK`
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn image_size(&self, _image_size: bool) -> String {
|
|
||||||
if let Some(image) = self.image() {
|
|
||||||
match image.dimension_details() {
|
|
||||||
ImageDimensionDetails::Svg(string) => string,
|
|
||||||
_ => {
|
|
||||||
let (width, height) = image.image_size();
|
|
||||||
if width > 0 && height > 0 {
|
|
||||||
format!("{width}\u{202F}\u{D7}\u{202F}{height}")
|
|
||||||
} else {
|
|
||||||
FALLBACK.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
FALLBACK.to_owned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn file_size(&self, info: Option<gio::FileInfo>) -> String {
|
|
||||||
info.map(|info| {
|
|
||||||
let size = info.size() as u64;
|
|
||||||
glib::format_size(size).to_string()
|
|
||||||
})
|
|
||||||
.unwrap_or_else(|| FALLBACK.to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn created_date(&self, info: Option<gio::FileInfo>) -> String {
|
|
||||||
info.and_then(|i| i.creation_date_time())
|
|
||||||
.and_then(|t| util::datetime_fmt(&t))
|
|
||||||
.unwrap_or_else(|| FALLBACK.to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn modified_date(&self, info: Option<gio::FileInfo>) -> String {
|
|
||||||
info.and_then(|i| i.modification_date_time())
|
|
||||||
.and_then(|t| util::datetime_fmt(&t))
|
|
||||||
.unwrap_or_else(|| FALLBACK.to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[template_callback]
|
|
||||||
fn has_content(&self, content: Option<String>) -> bool {
|
|
||||||
content.map_or(false, |x| !x.is_empty())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,6 @@
|
||||||
<interface>
|
<interface>
|
||||||
<template class="LpPropertiesView" parent="AdwBin">
|
<template class="LpPropertiesView" parent="AdwBin">
|
||||||
<property name="width-request">255</property>
|
<property name="width-request">255</property>
|
||||||
<binding name="metadata">
|
|
||||||
<lookup type="LpImage" name="metadata">
|
|
||||||
<lookup type="LpPropertiesView" name="image" />
|
|
||||||
</lookup>
|
|
||||||
</binding>
|
|
||||||
<binding name="file">
|
|
||||||
<lookup type="LpImage" name="file">
|
|
||||||
<lookup type="LpPropertiesView" name="image" />
|
|
||||||
</lookup>
|
|
||||||
</binding>
|
|
||||||
<property name="child">
|
<property name="child">
|
||||||
<object class="GtkScrolledWindow">
|
<object class="GtkScrolledWindow">
|
||||||
<property name="child">
|
<property name="child">
|
||||||
|
|
@ -25,14 +15,9 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwPreferencesGroup">
|
<object class="AdwPreferencesGroup">
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow" id="folder">
|
||||||
<property name="title" translatable="yes">Folder</property>
|
<property name="title" translatable="yes">Folder</property>
|
||||||
<property name="activatable-widget">folder_button</property>
|
<property name="activatable-widget">folder_button</property>
|
||||||
<binding name="subtitle">
|
|
||||||
<closure type="gchararray" function="folder_name">
|
|
||||||
<lookup name="file">LpPropertiesView</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>
|
|
||||||
<child type="suffix">
|
<child type="suffix">
|
||||||
<object class="GtkButton" id="folder_button">
|
<object class="GtkButton" id="folder_button">
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
|
|
@ -54,44 +39,27 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwPreferencesGroup">
|
<object class="AdwPreferencesGroup">
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow" id="image_size">
|
||||||
<property name="title" translatable="yes">Image Size</property>
|
<property name="title" translatable="yes">Image Size</property>
|
||||||
<property name="subtitle-selectable">True</property>
|
<property name="subtitle-selectable">True</property>
|
||||||
<binding name="subtitle">
|
|
||||||
<closure type="gchararray" function="image_size">
|
|
||||||
<lookup type="LpImage" name="image-size-available">
|
|
||||||
<lookup name="image">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>
|
|
||||||
<style>
|
<style>
|
||||||
<class name="property" />
|
<class name="property" />
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow" id="image_format">
|
||||||
<property name="title" translatable="yes">Image Format</property>
|
<property name="title" translatable="yes">Image Format</property>
|
||||||
<property name="subtitle-selectable">True</property>
|
<property name="subtitle-selectable">True</property>
|
||||||
<binding name="subtitle">
|
|
||||||
<lookup type="LpImage" name="format-name">
|
|
||||||
<lookup type="LpPropertiesView" name="image" />
|
|
||||||
</lookup>
|
|
||||||
</binding>
|
|
||||||
<style>
|
<style>
|
||||||
<class name="property" />
|
<class name="property" />
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow" id="file_size">
|
||||||
<property name="title" translatable="yes">File Size</property>
|
<property name="title" translatable="yes">File Size</property>
|
||||||
<property name="subtitle-selectable">True</property>
|
<property name="subtitle-selectable">True</property>
|
||||||
<binding name="subtitle">
|
|
||||||
<closure type="gchararray" function="file_size">
|
|
||||||
<lookup name="file-info">LpPropertiesView</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>
|
|
||||||
<style>
|
<style>
|
||||||
<class name="property" />
|
<class name="property" />
|
||||||
</style>
|
</style>
|
||||||
|
|
@ -102,28 +70,19 @@
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwPreferencesGroup">
|
<object class="AdwPreferencesGroup">
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow" id="file_created">
|
||||||
<property name="title" translatable="yes">File Created</property>
|
<property name="title" translatable="yes">File Created</property>
|
||||||
<property name="subtitle-selectable">True</property>
|
<property name="subtitle-selectable">True</property>
|
||||||
<binding name="subtitle">
|
|
||||||
<closure type="gchararray" function="created_date">
|
|
||||||
<lookup name="file-info">LpPropertiesView</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>
|
|
||||||
<style>
|
<style>
|
||||||
<class name="property" />
|
<class name="property" />
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow" id="file_modified">
|
||||||
<property name="title" translatable="yes">File Modified</property>
|
<property name="title" translatable="yes">File Modified</property>
|
||||||
<property name="subtitle-selectable">True</property>
|
<property name="subtitle-selectable">True</property>
|
||||||
<binding name="subtitle">
|
<style>
|
||||||
<closure type="gchararray" function="modified_date">
|
|
||||||
<lookup name="file-info">LpPropertiesView</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>" <style>
|
|
||||||
<class name="property" />
|
<class name="property" />
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
|
|
@ -131,28 +90,11 @@
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwPreferencesGroup">
|
<object class="AdwPreferencesGroup" id="details">
|
||||||
<binding name="visible">
|
|
||||||
<lookup name="has-information">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</binding>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow" id="location">
|
||||||
<property name="title" translatable="yes">Location</property>
|
<property name="title" translatable="yes">Location</property>
|
||||||
<property name="activatable-widget">location_button</property>
|
<property name="activatable-widget">location_button</property>
|
||||||
<binding name="subtitle">
|
|
||||||
<lookup name="location">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</binding>
|
|
||||||
<binding name="visible">
|
|
||||||
<closure type="gboolean" function="has_content">
|
|
||||||
<lookup name="location">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>
|
|
||||||
<child type="suffix">
|
<child type="suffix">
|
||||||
<object class="GtkButton" id="location_button">
|
<object class="GtkButton" id="location_button">
|
||||||
<property name="valign">center</property>
|
<property name="valign">center</property>
|
||||||
|
|
@ -170,126 +112,54 @@
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow" id="originally_created">
|
||||||
<property name="title" translatable="yes">Originally Created</property>
|
<property name="title" translatable="yes">Originally Created</property>
|
||||||
<property name="subtitle-selectable">True</property>
|
<property name="subtitle-selectable">True</property>
|
||||||
<binding name="subtitle">
|
|
||||||
<lookup name="originally-created">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</binding>
|
|
||||||
<binding name="visible">
|
|
||||||
<closure type="gboolean" function="has_content">
|
|
||||||
<lookup name="originally-created">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>
|
|
||||||
<style>
|
<style>
|
||||||
<class name="property" />
|
<class name="property" />
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow" id="aperture">
|
||||||
<property name="title" translatable="yes">Aperture</property>
|
<property name="title" translatable="yes">Aperture</property>
|
||||||
<property name="subtitle-selectable">True</property>
|
<property name="subtitle-selectable">True</property>
|
||||||
<binding name="subtitle">
|
|
||||||
<lookup name="f-number">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</binding>
|
|
||||||
<binding name="visible">
|
|
||||||
<closure type="gboolean" function="has_content">
|
|
||||||
<lookup name="f-number">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>
|
|
||||||
<style>
|
<style>
|
||||||
<class name="property" />
|
<class name="property" />
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow" id="exposure">
|
||||||
<property name="title" translatable="yes">Exposure</property>
|
<property name="title" translatable="yes">Exposure</property>
|
||||||
<property name="subtitle-selectable">True</property>
|
<property name="subtitle-selectable">True</property>
|
||||||
<binding name="subtitle">
|
|
||||||
<lookup name="exposure-time">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</binding>
|
|
||||||
<binding name="visible">
|
|
||||||
<closure type="gboolean" function="has_content">
|
|
||||||
<lookup name="exposure-time">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>
|
|
||||||
<style>
|
<style>
|
||||||
<class name="property" />
|
<class name="property" />
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow" id="iso">
|
||||||
<property name="title" translatable="yes">ISO</property>
|
<property name="title" translatable="yes">ISO</property>
|
||||||
<property name="subtitle-selectable">True</property>
|
<property name="subtitle-selectable">True</property>
|
||||||
<binding name="subtitle">
|
|
||||||
<lookup name="iso">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</binding>
|
|
||||||
<binding name="visible">
|
|
||||||
<closure type="gboolean" function="has_content">
|
|
||||||
<lookup name="iso">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>
|
|
||||||
<style>
|
<style>
|
||||||
<class name="property" />
|
<class name="property" />
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow" id="focal_length">
|
||||||
<property name="title" translatable="yes">Focal Length</property>
|
<property name="title" translatable="yes">Focal Length</property>
|
||||||
<property name="subtitle-selectable">True</property>
|
<property name="subtitle-selectable">True</property>
|
||||||
<binding name="subtitle">
|
|
||||||
<lookup name="focal-length">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</binding>
|
|
||||||
<binding name="visible">
|
|
||||||
<closure type="gboolean" function="has_content">
|
|
||||||
<lookup name="focal-length">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>
|
|
||||||
<style>
|
<style>
|
||||||
<class name="property" />
|
<class name="property" />
|
||||||
</style>
|
</style>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwActionRow">
|
<object class="AdwActionRow" id="maker_model">
|
||||||
<property name="title" translatable="yes">Maker, Model</property>
|
<property name="title" translatable="yes">Maker, Model</property>
|
||||||
<property name="subtitle-selectable">True</property>
|
<property name="subtitle-selectable">True</property>
|
||||||
<binding name="subtitle">
|
|
||||||
<lookup name="maker-model">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</binding>
|
|
||||||
<binding name="visible">
|
|
||||||
<closure type="gboolean" function="has_content">
|
|
||||||
<lookup name="maker-model">
|
|
||||||
<lookup name="metadata">LpPropertiesView</lookup>
|
|
||||||
</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>
|
|
||||||
<style>
|
<style>
|
||||||
<class name="property" />
|
<class name="property" />
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::util::{self, Direction, Position};
|
use crate::util::{self, Direction, Position};
|
||||||
use crate::widgets::{LpDragOverlay, LpImage, LpImagePage, LpImageView, LpPropertiesView};
|
use crate::widgets::{LpDragOverlay, LpImage, LpImageView, LpPropertiesView};
|
||||||
|
|
||||||
/// Show window after X milliseconds even if image dimensions are not known yet
|
/// Show window after X milliseconds even if image dimensions are not known yet
|
||||||
const SHOW_WINDOW_AFTER: u64 = 2000;
|
const SHOW_WINDOW_AFTER: u64 = 2000;
|
||||||
|
|
@ -81,6 +81,8 @@ mod imp {
|
||||||
#[template_child]
|
#[template_child]
|
||||||
pub(super) properties_button: TemplateChild<gtk::ToggleButton>,
|
pub(super) properties_button: TemplateChild<gtk::ToggleButton>,
|
||||||
#[template_child]
|
#[template_child]
|
||||||
|
pub(super) primary_menu: TemplateChild<gtk::MenuButton>,
|
||||||
|
#[template_child]
|
||||||
pub(super) toast_overlay: TemplateChild<adw::ToastOverlay>,
|
pub(super) toast_overlay: TemplateChild<adw::ToastOverlay>,
|
||||||
#[template_child]
|
#[template_child]
|
||||||
pub(super) stack: TemplateChild<gtk::Stack>,
|
pub(super) stack: TemplateChild<gtk::Stack>,
|
||||||
|
|
@ -120,7 +122,6 @@ mod imp {
|
||||||
// bind_template() is a function generated by the
|
// bind_template() is a function generated by the
|
||||||
// CompositeTemplate macro to bind all children at once.
|
// CompositeTemplate macro to bind all children at once.
|
||||||
Self::bind_template(klass);
|
Self::bind_template(klass);
|
||||||
Self::Type::bind_template_callbacks(klass);
|
|
||||||
|
|
||||||
klass.add_binding(
|
klass.add_binding(
|
||||||
gdk::Key::c,
|
gdk::Key::c,
|
||||||
|
|
@ -262,10 +263,10 @@ mod imp {
|
||||||
|
|
||||||
impl ObjectImpl for LpWindow {
|
impl ObjectImpl for LpWindow {
|
||||||
fn constructed(&self) {
|
fn constructed(&self) {
|
||||||
let obj = self.obj();
|
|
||||||
|
|
||||||
self.parent_constructed();
|
self.parent_constructed();
|
||||||
|
|
||||||
|
let obj = self.obj();
|
||||||
|
|
||||||
self.forward_click_gesture
|
self.forward_click_gesture
|
||||||
.connect_pressed(clone!(@weak obj => move |_,_,_,_| {
|
.connect_pressed(clone!(@weak obj => move |_,_,_,_| {
|
||||||
obj.image_view().navigate(Direction::Forward);
|
obj.image_view().navigate(Direction::Forward);
|
||||||
|
|
@ -294,6 +295,14 @@ mod imp {
|
||||||
glib::clone!(@weak obj => move |_| obj.on_current_page_changed()),
|
glib::clone!(@weak obj => move |_| obj.on_current_page_changed()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
obj.connect_fullscreened_notify(
|
||||||
|
glib::clone!(@weak obj => move |_| obj.on_fullscreen_changed()),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.properties_button.connect_active_notify(
|
||||||
|
glib::clone!(@weak obj => move |_| obj.update_headerbar_style()),
|
||||||
|
);
|
||||||
|
|
||||||
let gesture_click = gtk::GestureClick::new();
|
let gesture_click = gtk::GestureClick::new();
|
||||||
gesture_click
|
gesture_click
|
||||||
.connect_pressed(glib::clone!(@weak obj => move |_, _, _, _| obj.on_click()));
|
.connect_pressed(glib::clone!(@weak obj => move |_, _, _, _| obj.on_click()));
|
||||||
|
|
@ -449,7 +458,6 @@ glib::wrapper! {
|
||||||
@implements gio::ActionMap, gio::ActionGroup, gtk::Native;
|
@implements gio::ActionMap, gio::ActionGroup, gtk::Native;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[gtk::template_callbacks]
|
|
||||||
impl LpWindow {
|
impl LpWindow {
|
||||||
pub fn new<A: IsA<gtk::Application>>(app: &A) -> Self {
|
pub fn new<A: IsA<gtk::Application>>(app: &A) -> Self {
|
||||||
glib::Object::builder().property("application", app).build()
|
glib::Object::builder().property("application", app).build()
|
||||||
|
|
@ -723,6 +731,10 @@ impl LpWindow {
|
||||||
/// Handles change in availability of images
|
/// Handles change in availability of images
|
||||||
fn on_current_page_changed(&self) {
|
fn on_current_page_changed(&self) {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
|
let current_page = imp.image_view.current_page();
|
||||||
|
|
||||||
|
// HeaderBar style
|
||||||
|
self.update_headerbar_style();
|
||||||
|
|
||||||
// Window title
|
// Window title
|
||||||
let title = self
|
let title = self
|
||||||
|
|
@ -734,7 +746,11 @@ impl LpWindow {
|
||||||
|
|
||||||
self.set_title(Some(&title));
|
self.set_title(Some(&title));
|
||||||
|
|
||||||
let has_image = imp.image_view.current_page().is_some();
|
// Properties view
|
||||||
|
let current_image = current_page.as_ref().map(|x| x.image());
|
||||||
|
imp.properties_view.set_image(current_image.as_ref());
|
||||||
|
|
||||||
|
let has_image = current_page.is_some();
|
||||||
|
|
||||||
imp.properties_button.set_sensitive(has_image);
|
imp.properties_button.set_sensitive(has_image);
|
||||||
self.set_actions_enabled(has_image);
|
self.set_actions_enabled(has_image);
|
||||||
|
|
@ -806,6 +822,10 @@ impl LpWindow {
|
||||||
self.action_set_enabled("win.zoom-in", can_zoom_in);
|
self.action_set_enabled("win.zoom-in", can_zoom_in);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set control opacity
|
||||||
|
///
|
||||||
|
/// Also changes headerbar transparency if it flat and hides/shows cursor on
|
||||||
|
/// fullscreen
|
||||||
fn set_control_opacity(&self, opacity: f64, hiding: bool) {
|
fn set_control_opacity(&self, opacity: f64, hiding: bool) {
|
||||||
self.image_view().controls_box_start().set_opacity(opacity);
|
self.image_view().controls_box_start().set_opacity(opacity);
|
||||||
self.image_view().controls_box_end().set_opacity(opacity);
|
self.image_view().controls_box_end().set_opacity(opacity);
|
||||||
|
|
@ -881,7 +901,7 @@ impl LpWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_controls_visible(&self) -> bool {
|
fn are_controls_visible(&self) -> bool {
|
||||||
if self.hide_controls_animation().state() == adw::AnimationState::Playing {
|
if self.hide_controls_animation().state() == adw::AnimationState::Playing {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -892,7 +912,7 @@ impl LpWindow {
|
||||||
|
|
||||||
/// Start animation to show controls
|
/// Start animation to show controls
|
||||||
fn show_controls(&self) {
|
fn show_controls(&self) {
|
||||||
if !self.is_controls_visible() {
|
if !self.are_controls_visible() {
|
||||||
self.hide_controls_animation().pause();
|
self.hide_controls_animation().pause();
|
||||||
self.show_controls_animation()
|
self.show_controls_animation()
|
||||||
.set_value_from(self.controls_opacity());
|
.set_value_from(self.controls_opacity());
|
||||||
|
|
@ -902,7 +922,7 @@ impl LpWindow {
|
||||||
|
|
||||||
/// Start animation to hide controls
|
/// Start animation to hide controls
|
||||||
fn hide_controls(&self) {
|
fn hide_controls(&self) {
|
||||||
if self.is_controls_visible() {
|
if self.are_controls_visible() {
|
||||||
self.show_controls_animation().pause();
|
self.show_controls_animation().pause();
|
||||||
self.hide_controls_animation()
|
self.hide_controls_animation()
|
||||||
.set_value_from(self.controls_opacity());
|
.set_value_from(self.controls_opacity());
|
||||||
|
|
@ -910,12 +930,6 @@ impl LpWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether or not the window is showing images
|
|
||||||
fn is_showing_image(&self) -> bool {
|
|
||||||
let imp = self.imp();
|
|
||||||
imp.stack.visible_child().as_ref() == Some(imp.image_view.upcast_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_click(&self) {
|
fn on_click(&self) {
|
||||||
self.show_controls();
|
self.show_controls();
|
||||||
|
|
||||||
|
|
@ -946,23 +960,38 @@ impl LpWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if controls can be hidden
|
||||||
|
///
|
||||||
/// Only hide controls if cursor not over controls and there is an image
|
/// Only hide controls if cursor not over controls and there is an image
|
||||||
/// shown
|
/// shown
|
||||||
fn can_hide_controls(&self) -> bool {
|
fn can_hide_controls(&self) -> bool {
|
||||||
!self
|
let imp = self.imp();
|
||||||
|
|
||||||
|
let controls_hovered = self
|
||||||
.image_view()
|
.image_view()
|
||||||
.controls_box_start_events()
|
.controls_box_start_events()
|
||||||
.contains_pointer()
|
.contains_pointer()
|
||||||
&& !self
|
|| self
|
||||||
.image_view()
|
.image_view()
|
||||||
.controls_box_end_events()
|
.controls_box_end_events()
|
||||||
.contains_pointer()
|
.contains_pointer();
|
||||||
&& self.is_showing_image()
|
|
||||||
&& (!self.is_headerbar_flat() || !self.imp().headerbar_events.contains_pointer())
|
let headerbar_hideable = self.is_content_extended_to_top();
|
||||||
|
let headerbar_hovered = imp.headerbar_events.contains_pointer();
|
||||||
|
|
||||||
|
let main_menu_open = imp.primary_menu.is_active();
|
||||||
|
|
||||||
|
// Buttom controls are not hovered
|
||||||
|
!controls_hovered
|
||||||
|
// Either headerbar must not be hidden with other controls or not be hovered
|
||||||
|
&& (!headerbar_hideable || !headerbar_hovered)
|
||||||
|
// Main menu is not open
|
||||||
|
&& !main_menu_open
|
||||||
}
|
}
|
||||||
|
|
||||||
#[template_callback]
|
fn on_fullscreen_changed(&self) {
|
||||||
fn on_fullscreened(&self) {
|
self.update_headerbar_style();
|
||||||
|
|
||||||
if !self.is_fullscreened() {
|
if !self.is_fullscreened() {
|
||||||
self.set_cursor(None);
|
self.set_cursor(None);
|
||||||
self.show_controls();
|
self.show_controls();
|
||||||
|
|
@ -986,25 +1015,23 @@ impl LpWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[template_callback]
|
fn is_content_extended_to_top(&self) -> bool {
|
||||||
fn extend_to_top(&self, fullscreened: bool, show_properties: bool, show_menu: bool) -> bool {
|
self.is_fullscreened() && !self.imp().properties_button.is_active()
|
||||||
fullscreened && !show_properties && !show_menu
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[template_callback]
|
fn update_headerbar_style(&self) {
|
||||||
fn top_bar_style(
|
let imp = self.imp();
|
||||||
&self,
|
|
||||||
fullscreened: bool,
|
let extend = self.is_content_extended_to_top();
|
||||||
show_properties: bool,
|
imp.toolbar_view.set_extend_content_to_top_edge(extend);
|
||||||
show_menu: bool,
|
|
||||||
current_image: Option<LpImage>,
|
|
||||||
) -> adw::ToolbarStyle {
|
|
||||||
// Flat headerbar for empty state and fullscreen
|
// Flat headerbar for empty state and fullscreen
|
||||||
if current_image.is_none() || self.extend_to_top(fullscreened, show_properties, show_menu) {
|
let toolbar_style = if imp.image_view.current_image().is_none() || extend {
|
||||||
adw::ToolbarStyle::Flat
|
adw::ToolbarStyle::Flat
|
||||||
} else {
|
} else {
|
||||||
// Use the border variant of raised to avoid shadows over images
|
// Use the border variant of raised to avoid shadows over images
|
||||||
adw::ToolbarStyle::__Unknown(2)
|
adw::ToolbarStyle::RaisedBorder
|
||||||
}
|
};
|
||||||
|
imp.toolbar_view.set_top_bar_style(toolbar_style);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,6 @@
|
||||||
<property name="width-request">360</property>
|
<property name="width-request">360</property>
|
||||||
<property name="height-request">360</property>
|
<property name="height-request">360</property>
|
||||||
<property name="title" translatable="yes">Image Viewer</property>
|
<property name="title" translatable="yes">Image Viewer</property>
|
||||||
<signal name="notify::fullscreened" handler="on_fullscreened" swapped="yes" />
|
|
||||||
<child>
|
<child>
|
||||||
<object class="AdwBreakpoint">
|
<object class="AdwBreakpoint">
|
||||||
<condition>max-width: 590sp</condition>
|
<condition>max-width: 590sp</condition>
|
||||||
|
|
@ -71,25 +70,6 @@
|
||||||
</child>
|
</child>
|
||||||
<property name="content">
|
<property name="content">
|
||||||
<object class="AdwToolbarView" id="toolbar_view">
|
<object class="AdwToolbarView" id="toolbar_view">
|
||||||
<binding name="top-bar-style">
|
|
||||||
<closure type="AdwToolbarStyle" function="top_bar_style">
|
|
||||||
<lookup name="fullscreened">LpWindow</lookup>
|
|
||||||
<lookup name="active">properties_button</lookup>
|
|
||||||
<lookup name="active">primary_menu</lookup>
|
|
||||||
<lookup name="image">
|
|
||||||
<lookup name="current-page">
|
|
||||||
image_view
|
|
||||||
</lookup>
|
|
||||||
</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>
|
|
||||||
<binding name="extend-content-to-top-edge">
|
|
||||||
<closure type="gboolean" function="extend_to_top">
|
|
||||||
<lookup name="fullscreened">LpWindow</lookup>
|
|
||||||
<lookup name="active">properties_button</lookup>
|
|
||||||
<lookup name="active">primary_menu</lookup>
|
|
||||||
</closure>
|
|
||||||
</binding>
|
|
||||||
<child type="top">
|
<child type="top">
|
||||||
<object class="AdwHeaderBar" id="headerbar">
|
<object class="AdwHeaderBar" id="headerbar">
|
||||||
<property name="show-end-title-buttons" bind-source="LpWindow" bind-property="fullscreened" bind-flags="sync-create|invert-boolean" />
|
<property name="show-end-title-buttons" bind-source="LpWindow" bind-property="fullscreened" bind-flags="sync-create|invert-boolean" />
|
||||||
|
|
@ -228,13 +208,6 @@
|
||||||
<property name="flap">
|
<property name="flap">
|
||||||
<object class="LpPropertiesView" id="properties_view">
|
<object class="LpPropertiesView" id="properties_view">
|
||||||
<property name="vexpand">true</property>
|
<property name="vexpand">true</property>
|
||||||
<binding name="image">
|
|
||||||
<lookup name="image">
|
|
||||||
<lookup name="current-page">
|
|
||||||
image_view
|
|
||||||
</lookup>
|
|
||||||
</lookup>
|
|
||||||
</binding>
|
|
||||||
</object>
|
</object>
|
||||||
</property>
|
</property>
|
||||||
</object>
|
</object>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue