diff --git a/po/POTFILES.in b/po/POTFILES.in index 9127299..d2b2899 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -22,6 +22,8 @@ src/widgets.rs src/widgets/drag_overlay.rs src/widgets/error_details.rs src/widgets/error_details.ui +src/widgets/fullscreen_widget.rs +src/widgets/fullscreen_widget.ui src/widgets/image.rs src/widgets/image/background_color.rs src/widgets/image/drag.rs diff --git a/src/widgets.rs b/src/widgets.rs index affcffd..132d856 100644 --- a/src/widgets.rs +++ b/src/widgets.rs @@ -19,6 +19,7 @@ mod drag_overlay; mod error_details; +mod fullscreen_widget; mod image; mod image_page; mod image_view; @@ -30,6 +31,7 @@ mod sliding_view; pub use drag_overlay::LpDragOverlay; pub use error_details::LpErrorDetails; +pub use fullscreen_widget::LpFullscreenWidget; pub use image::LpImage; pub use image_page::LpImagePage; pub use image_view::LpImageView; diff --git a/src/widgets/fullscreen_widget.rs b/src/widgets/fullscreen_widget.rs new file mode 100644 index 0000000..6c699ef --- /dev/null +++ b/src/widgets/fullscreen_widget.rs @@ -0,0 +1,147 @@ +// Copyright (c) 2024 Christopher Davis +// +// 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 std::cell::{Cell, RefCell}; + +use adw::prelude::*; +use adw::subclass::prelude::*; +use glib::Properties; +use gtk::CompositeTemplate; + +use super::*; +use crate::deps::*; + +mod imp { + use super::*; + + #[derive(Default, Debug, Properties, CompositeTemplate)] + #[properties(wrapper_type = super::LpFullscreenWidget)] + #[template(file = "fullscreen_widget.ui")] + pub struct LpFullscreenWidget { + #[template_child] + pub(super) toolbar_view: TemplateChild, + #[template_child] + pub(super) header_container: TemplateChild, + + #[property(get, set, nullable)] + headerbar: RefCell>, + #[property(get, set, nullable)] + content: RefCell>, + + #[property(get, set)] + fullscreened: Cell, + #[property(get, set)] + extend_content: Cell, + #[property(get = LpFullscreenWidget::is_content_extended_to_top, type = bool)] + _is_content_extended_to_top: (), + #[property(get, set)] + is_empty: Cell, + #[property(get, set)] + headerbar_opacity: Cell, + } + + impl LpFullscreenWidget { + fn is_content_extended_to_top(&self) -> bool { + let obj = self.obj(); + obj.fullscreened() && obj.extend_content() + } + } + + #[glib::object_subclass] + impl ObjectSubclass for LpFullscreenWidget { + const NAME: &'static str = "LpFullscreenWidget"; + type Type = super::LpFullscreenWidget; + type ParentType = adw::Bin; + + fn class_init(klass: &mut Self::Class) { + klass.bind_template(); + } + + fn instance_init(obj: &gio::glib::subclass::InitializingObject) { + obj.init_template(); + } + } + + #[glib::derived_properties] + impl ObjectImpl for LpFullscreenWidget { + fn constructed(&self) { + self.parent_constructed(); + + let obj = self.obj(); + obj.connect_is_empty_notify(|obj| { + obj.update_headerbar_style(); + }); + + obj.connect_fullscreened_notify(|obj| { + obj.update_headerbar_style(); + }); + + obj.connect_extend_content_notify(|obj| { + obj.update_headerbar_style(); + }); + } + } + + impl WidgetImpl for LpFullscreenWidget {} + impl BinImpl for LpFullscreenWidget {} +} + +glib::wrapper! { + pub struct LpFullscreenWidget(ObjectSubclass) + @extends gtk::Widget, adw::Bin, + @implements gtk::Buildable, gtk::ConstraintTarget; +} + +impl LpFullscreenWidget { + pub fn is_headerbar_flat(&self) -> bool { + matches!( + self.imp().toolbar_view.top_bar_style(), + adw::ToolbarStyle::Flat + ) + } + + fn update_header_opacity(&self) { + if let Some(headerbar) = self.headerbar() { + if self.is_headerbar_flat() && !self.is_empty() { + // Bring headerbar opacity in sync with controls + headerbar.set_opacity(self.headerbar_opacity()); + } else { + headerbar.set_opacity(1.); + }; + } + } + + fn update_headerbar_style(&self) { + self.imp() + .toolbar_view + .set_extend_content_to_top_edge(self.is_content_extended_to_top()); + + // Flat headerbar for empty state and fullscreen + let style = if self.is_empty() || self.is_content_extended_to_top() { + adw::ToolbarStyle::Flat + } else { + // Use the border variant of raised to avoid shadows over images + adw::ToolbarStyle::RaisedBorder + }; + + let toolbar_view = self.imp().toolbar_view.get(); + if style != toolbar_view.top_bar_style() { + toolbar_view.set_top_bar_style(style); + self.update_header_opacity(); + } + } +} diff --git a/src/widgets/fullscreen_widget.ui b/src/widgets/fullscreen_widget.ui new file mode 100644 index 0000000..7ace3a7 --- /dev/null +++ b/src/widgets/fullscreen_widget.ui @@ -0,0 +1,30 @@ + + + + diff --git a/src/window.rs b/src/window.rs index 37994c0..47dfe05 100644 --- a/src/window.rs +++ b/src/window.rs @@ -34,7 +34,7 @@ use std::path::{Path, PathBuf}; use actions::*; use adw::prelude::*; use adw::subclass::prelude::*; -use glib::clone; +use glib::{clone, Properties}; use gtk::{CompositeTemplate, Widget}; use crate::application::LpApplication; @@ -42,7 +42,7 @@ use crate::config; use crate::deps::*; use crate::util::gettext::*; use crate::util::Direction; -use crate::widgets::{LpDragOverlay, LpImage, LpImageView, LpPropertiesView}; +use crate::widgets::{LpDragOverlay, LpFullscreenWidget, LpImage, LpImageView, LpPropertiesView}; /// Show window after X milliseconds even if image dimensions are not known yet const SHOW_WINDOW_AFTER: u64 = 2000; @@ -73,14 +73,15 @@ mod imp { // 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)] + #[derive(Default, Debug, Properties, CompositeTemplate)] + #[properties(wrapper_type = super::LpWindow)] #[template(file = "window.ui")] pub struct LpWindow { // Template children are used with the // TemplateChild wrapper, where T is the // object type of the template child. #[template_child] - pub(super) toolbar_view: TemplateChild, + pub(super) fullscreen_widget: TemplateChild, #[template_child] pub(super) headerbar: TemplateChild, #[template_child] @@ -110,6 +111,11 @@ mod imp { #[template_child] pub(super) backward_click_gesture: TemplateChild, + #[property(get, set)] + is_empty: Cell, + #[property(get, set)] + headerbar_opacity: Cell, + /// Motion controller for complete window pub(super) motion_controller: gtk::EventControllerMotion, pub(super) pointer_position: Cell<(f64, f64)>, @@ -167,6 +173,7 @@ mod imp { } } + #[glib::derived_properties] impl ObjectImpl for LpWindow { fn constructed(&self) { self.parent_constructed(); @@ -233,12 +240,6 @@ mod imp { win.resize_default(); }); - self.properties_button.connect_active_notify(glib::clone!( - #[weak] - obj, - move |_| obj.update_headerbar_style() - )); - let gesture_click = gtk::GestureClick::new(); gesture_click.connect_pressed(glib::clone!( #[weak] @@ -339,11 +340,11 @@ mod imp { ), ); - self.toolbar_view.connect_top_bar_style_notify(glib::clone!( - #[weak] - obj, - move |_| obj.on_top_bar_style_notify() - )); + self.image_view + .controls_box_start() + .bind_property("opacity", &*self.obj(), "headerbar-opacity") + .sync_create() + .build(); // action win.previous status self.image_view @@ -760,7 +761,7 @@ impl LpWindow { let current_page = imp.image_view.current_page(); // HeaderBar style - self.update_headerbar_style(); + self.set_is_empty(current_page.is_none()); // Window title self.update_title(); @@ -896,8 +897,6 @@ impl LpWindow { .image_view .on_fullscreen_changed(self.is_fullscreen()); - self.update_headerbar_style(); - if !self.is_fullscreen() { self.set_cursor(None); self.show_controls(); @@ -906,39 +905,11 @@ impl LpWindow { } fn is_headerbar_flat(&self) -> bool { - matches!( - self.imp().toolbar_view.top_bar_style(), - adw::ToolbarStyle::Flat - ) - } - - fn on_top_bar_style_notify(&self) { - if self.is_headerbar_flat() { - // Bring headerbar opacity in sync with controls - self.headerbar().set_opacity(self.controls_opacity()); - } else { - self.headerbar().set_opacity(1.); - } + self.imp().fullscreen_widget.is_headerbar_flat() } fn is_content_extended_to_top(&self) -> bool { - self.is_fullscreen() && !self.imp().properties_button.is_active() - } - - fn update_headerbar_style(&self) { - let imp = self.imp(); - - let extend = self.is_content_extended_to_top(); - imp.toolbar_view.set_extend_content_to_top_edge(extend); - - // Flat headerbar for empty state and fullscreen - let toolbar_style = if imp.image_view.current_image().is_none() || extend { - adw::ToolbarStyle::Flat - } else { - // Use the border variant of raised to avoid shadows over images - adw::ToolbarStyle::RaisedBorder - }; - imp.toolbar_view.set_top_bar_style(toolbar_style); + self.imp().fullscreen_widget.is_content_extended_to_top() } fn update_accel_status(&self) { diff --git a/src/window.ui b/src/window.ui index 4231d67..e241578 100644 --- a/src/window.ui +++ b/src/window.ui @@ -68,75 +68,72 @@ - - - - - - - - - - - edit-copy-symbolic - win.copy-image - Copy to Clipboard - - - - - user-trash-symbolic - win.trash - Move to Trash - - - - - open-menu-symbolic - Main Menu - True - - - primary_menu - - - object-rotate-left-symbolic - Rotate Left - win.rotate-ccw - - - - - - object-rotate-right-symbolic - Rotate Right - win.rotate-cw - - - - - - - - - - info-outline-symbolic - Image Properties - - - - - + + + + + + + + + + edit-copy-symbolic + win.copy-image + Copy to Clipboard - + + + + user-trash-symbolic + win.trash + Move to Trash + + + + + open-menu-symbolic + Main Menu + True + + + primary_menu + + + object-rotate-left-symbolic + Rotate Left + win.rotate-ccw + + + + + + object-rotate-right-symbolic + Rotate Right + win.rotate-cw + + + + + + + + + + info-outline-symbolic + Image Properties + + + + + - +