diff --git a/src/widgets.rs b/src/widgets.rs
index 1212481..2add57a 100644
--- a/src/widgets.rs
+++ b/src/widgets.rs
@@ -28,6 +28,7 @@ mod properties_view;
mod shy_bin;
mod sliding_view;
mod window_content;
+mod window_image;
pub use drag_overlay::LpDragOverlay;
pub use error_details::LpErrorDetails;
@@ -40,3 +41,4 @@ pub use properties_view::LpPropertiesView;
pub use shy_bin::LpShyBin;
pub use sliding_view::LpSlidingView;
pub use window_content::LpWindowContent;
+pub use window_image::LpWindowImage;
diff --git a/src/widgets/window_content.ui b/src/widgets/window_content.ui
index 98870c3..25b14ac 100644
--- a/src/widgets/window_content.ui
+++ b/src/widgets/window_content.ui
@@ -6,11 +6,13 @@
LpWindowContent
+
-
+
+
+
+
diff --git a/src/widgets/window_image/actions.rs b/src/widgets/window_image/actions.rs
index f007aa5..41667fb 100644
--- a/src/widgets/window_image/actions.rs
+++ b/src/widgets/window_image/actions.rs
@@ -78,7 +78,9 @@ impl ActionPartGlobal {
}
}
- pub fn init_actions_and_bindings(klass: &mut ::Class) {
+ pub fn init_actions_and_bindings(
+ klass: &mut ::Class,
+ ) {
for action in Self::iter() {
match action {
ActionPartGlobal::ImageLeftInstant => {
@@ -198,10 +200,14 @@ pub enum Action {
About,
#[strum(to_string = "win.reload")]
Reload,
+ #[strum(to_string = "win.edit")]
+ Edit,
}
impl Action {
- pub fn init_actions_and_bindings(klass: &mut ::Class) {
+ pub fn init_actions_and_bindings(
+ klass: &mut ::Class,
+ ) {
for action in Self::iter() {
match action {
// Navigation
@@ -430,15 +436,16 @@ impl Action {
}
Action::ToggleFullscreen => {
- klass.install_action(&action, None, move |win, _, _| {
+ klass.install_action(&action, None, move |obj, _, _| {
+ let win = obj.window();
win.toggle_fullscreen(!win.is_fullscreen());
});
klass.add_binding_action(gdk::Key::F11, gdk::ModifierType::empty(), &action);
}
Action::LeaveFullscreen => {
- klass.install_action(&action, None, move |win, _, _| {
- win.toggle_fullscreen(false);
+ klass.install_action(&action, None, move |obj, _, _| {
+ obj.window().toggle_fullscreen(false);
});
klass.add_binding_action(gdk::Key::Escape, gdk::ModifierType::empty(), &action);
}
@@ -487,8 +494,8 @@ impl Action {
}
Action::About => {
- klass.install_action_async(&action, None, |win, _, _| async move {
- win.show_about().await
+ klass.install_action_async(&action, None, |obj, _, _| async move {
+ obj.window().show_about().await
});
}
@@ -502,6 +509,13 @@ impl Action {
});
klass.add_binding_action(gdk::Key::F5, gdk::ModifierType::empty(), &action);
}
+
+ Action::Edit => {
+ klass.install_action_async(&action, None, move |obj, _, _| async move {
+ obj.window().show_edit();
+ });
+ klass.add_binding_action(gdk::Key::e, gdk::ModifierType::empty(), &action);
+ }
}
}
}
diff --git a/src/widgets/window_image/controls.rs b/src/widgets/window_image/controls.rs
index 2ab55a3..0f748db 100644
--- a/src/widgets/window_image/controls.rs
+++ b/src/widgets/window_image/controls.rs
@@ -20,22 +20,24 @@
use super::*;
/// Manage visibility of overlay controls
-impl LpWindow {
+impl LpWindowImage {
/// 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) {
+ let window = self.window();
+
self.image_view().controls_box_start().set_opacity(opacity);
self.image_view().controls_box_end().set_opacity(opacity);
- if self.is_headerbar_flat() && self.is_fullscreen() {
+ if self.is_headerbar_flat() && window.is_fullscreen() {
self.headerbar().set_opacity(opacity);
} else {
self.headerbar().set_opacity(1.);
}
- if self.is_fullscreen() && hiding && opacity < 0.9 {
+ if window.is_fullscreen() && hiding && opacity < 0.9 {
self.set_cursor(gdk::Cursor::from_name("none", None).as_ref());
} else {
self.set_cursor(None);
diff --git a/src/window.rs b/src/window.rs
new file mode 100644
index 0000000..696ff2c
--- /dev/null
+++ b/src/window.rs
@@ -0,0 +1,199 @@
+// Copyright (c) 2024 Sophie Herold
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+use adw::prelude::*;
+use adw::subclass::prelude::*;
+use gtk::CompositeTemplate;
+
+use crate::config;
+use crate::deps::*;
+use crate::widgets::LpImageView;
+
+/// Show window after X milliseconds even if image dimensions are not known yet
+const SHOW_WINDOW_AFTER: u64 = 2000;
+
+mod imp {
+
+ use gio::glib::VariantTy;
+
+ use super::*;
+ use crate::widgets::LpWindowImage;
+
+ // To use composite templates, you need
+ // to use derive macro. Derive macros generate
+ // code to e.g. implement a trait on something.
+ // In this case, code is generated for Debug output
+ // and to handle binding the template children.
+ //
+ // For this derive macro, you need to have
+ // `use gtk::CompositeTemplate` in your code.
+ //
+ // Because all of our member fields implement the
+ // `Default` trait, we can use `#[derive(Default)]`.
+ // If some member fields did not implement default,
+ // we'd need to have a `new()` function in the
+ // `impl ObjectSubclass for $TYPE` section.
+ #[derive(Default, Debug, CompositeTemplate)]
+ #[template(file = "window.ui")]
+ pub struct LpWindow {
+ #[template_child]
+ pub(super) toast_overlay: TemplateChild,
+
+ #[template_child]
+ pub(super) window_image: TemplateChild,
+ }
+
+ #[glib::object_subclass]
+ impl ObjectSubclass for LpWindow {
+ const NAME: &'static str = "LpWindow";
+ type Type = super::LpWindow;
+ type ParentType = adw::ApplicationWindow;
+
+ fn class_init(klass: &mut Self::Class) {
+ Self::bind_template(klass);
+
+ klass.add_binding_action(
+ gdk::Key::question,
+ gdk::ModifierType::CONTROL_MASK,
+ "win.show-help-overlay",
+ );
+
+ klass.install_action(
+ "win.show-toast",
+ Some(VariantTy::TUPLE),
+ move |win, _, var| {
+ if let Some((ref toast, i)) = var.and_then(|v| v.get::<(String, i32)>()) {
+ win.show_toast(toast, adw::ToastPriority::__Unknown(i));
+ }
+ },
+ );
+ }
+
+ fn instance_init(obj: &glib::subclass::InitializingObject) {
+ obj.init_template();
+ }
+ }
+
+ impl ObjectImpl for LpWindow {
+ fn constructed(&self) {
+ self.parent_constructed();
+
+ let obj = self.obj();
+
+ if config::APP_ID.ends_with(".Devel") {
+ obj.add_css_class("devel");
+ }
+
+ // Limit effect of modal dialogs to this window
+ // and keeps the others usable
+ gtk::WindowGroup::new().add_window(&*obj);
+
+ glib::timeout_add_local_once(
+ std::time::Duration::from_millis(SHOW_WINDOW_AFTER),
+ glib::clone!(
+ #[weak]
+ obj,
+ move || if !obj.is_visible() {
+ obj.present()
+ }
+ ),
+ );
+
+ obj.connect_map(|win| {
+ win.resize_default();
+ });
+ }
+ }
+
+ impl WidgetImpl for LpWindow {}
+ impl WindowImpl for LpWindow {}
+ impl ApplicationWindowImpl for LpWindow {}
+ impl AdwApplicationWindowImpl for LpWindow {}
+}
+
+glib::wrapper! {
+ pub struct LpWindow(ObjectSubclass)
+ @extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow,
+ @implements gio::ActionMap, gio::ActionGroup, gtk::Native;
+}
+
+impl LpWindow {
+ pub fn new>(app: &A) -> Self {
+ glib::Object::builder().property("application", app).build()
+ }
+
+ pub fn toggle_fullscreen(&self, fullscreen: bool) {
+ self.set_fullscreened(fullscreen);
+ }
+
+ pub fn resize_default(&self) {
+ let imp = self.imp();
+
+ if imp
+ .window_image
+ .image_view()
+ .current_image()
+ .is_some_and(|img| img.image_size_available())
+ {
+ let shows_properties = imp.window_image.properties_button().is_active();
+
+ let (_, window_natural_width, _, _) = self.measure(gtk::Orientation::Horizontal, -1);
+ let (_, window_natural_height, _, _) = self.measure(gtk::Orientation::Vertical, -1);
+
+ // These have to be in sync with the "conditions" for the "overlay-properties"
+ // breakpoint
+ let min_width_for_overlay = adw::LengthUnit::Sp.to_px(590., None).ceil() as i32;
+ let min_height_for_overlay = adw::LengthUnit::Sp.to_px(550., None).ceil() as i32;
+
+ let (width, height) = if shows_properties
+ && window_natural_width < min_width_for_overlay
+ && window_natural_height < min_height_for_overlay
+ {
+ // Avoid overlaying bottom sheet being triggered for the image properties by
+ // using a window wide enough to allow for a sidebar
+ (min_width_for_overlay.saturating_add(1), -1)
+ } else {
+ // this lets the window determine the default size from LpImage's natural size
+ (-1, -1)
+ };
+
+ self.set_default_size(width, height);
+ }
+ }
+
+ pub fn show_toast(&self, text: impl AsRef, priority: adw::ToastPriority) {
+ let imp = self.imp();
+
+ let toast = adw::Toast::new(text.as_ref());
+ toast.set_priority(priority);
+
+ imp.toast_overlay.add_toast(toast);
+ }
+
+ pub async fn show_about(&self) {
+ let about = crate::about::dialog().await;
+ about.present(Some(self));
+ }
+
+ pub fn show_edit(&self) {
+ todo!()
+ }
+
+ pub fn image_view(&self) -> LpImageView {
+ self.imp().window_image.image_view()
+ }
+}
diff --git a/src/window.ui b/src/window.ui
new file mode 100644
index 0000000..a93f412
--- /dev/null
+++ b/src/window.ui
@@ -0,0 +1,32 @@
+
+
+
+ 600
+ 498
+ 360
+ 294
+
+
+
+
+
+ LpWindow
+
+
+
+
+
+