widgets: Rename Window* to *Window

This commit is contained in:
Sophie Herold 2024-09-14 21:39:53 +02:00
parent 4b0d5f7b6d
commit f5660ba83e
9 changed files with 41 additions and 41 deletions

View file

@ -0,0 +1,536 @@
// 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 <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-or-later
use std::ops::Deref;
use adw::subclass::prelude::*;
use gtk::prelude::*;
use strum::IntoEnumIterator;
use crate::deps::*;
use crate::util::{Direction, Position};
/// Actions that need some global accels
///
/// The app level accels are needed for arrow key bindings. Because the arrow
/// keys are usually used for widget navigation, it's not possible to overwrite
/// them on key-binding level.
#[derive(strum::Display, strum::AsRefStr, strum::EnumIter)]
pub enum ActionPartGlobal {
#[strum(to_string = "win.image-left-instant")]
ImageLeftInstant,
#[strum(to_string = "win.image-right-instant")]
ImageRightInstant,
// Pan
#[strum(to_string = "win.pan-up")]
PanUp,
#[strum(to_string = "win.pan-right")]
PanRight,
#[strum(to_string = "win.pan-down")]
PanDown,
#[strum(to_string = "win.pan-left")]
PanLeft,
}
impl ActionPartGlobal {
pub fn add_accels(application: &gtk::Application) {
for action in Self::iter() {
match action {
ActionPartGlobal::ImageRightInstant => {
application.set_accels_for_action(&action, &["Right"])
}
ActionPartGlobal::ImageLeftInstant => {
application.set_accels_for_action(&action, &["Left"])
}
ActionPartGlobal::PanUp => {
application.set_accels_for_action(&action, &["<Ctrl>Up"])
}
ActionPartGlobal::PanRight => {
application.set_accels_for_action(&action, &["<Ctrl>Right"])
}
ActionPartGlobal::PanDown => {
application.set_accels_for_action(&action, &["<Ctrl>Down"])
}
ActionPartGlobal::PanLeft => {
application.set_accels_for_action(&action, &["<Ctrl>Left"])
}
}
}
}
pub fn remove_accels(application: &gtk::Application) {
for action in Self::iter() {
application.set_accels_for_action(&action, &[]);
}
}
pub fn init_actions_and_bindings(
klass: &mut <super::imp::LpImageWindow as ObjectSubclass>::Class,
) {
for action in Self::iter() {
match action {
ActionPartGlobal::ImageLeftInstant => {
klass.install_action(&action, None, move |win, _, _| {
if win.direction() == gtk::TextDirection::Rtl {
win.imp().image_view.navigate(Direction::Forward, false);
} else {
win.imp().image_view.navigate(Direction::Back, false);
}
});
klass.add_binding_action(
gdk::Key::Page_Down,
gdk::ModifierType::empty(),
&action,
);
}
ActionPartGlobal::ImageRightInstant => {
klass.install_action(&action, None, move |win, _, _| {
if win.direction() == gtk::TextDirection::Rtl {
win.imp().image_view.navigate(Direction::Back, false);
} else {
win.imp().image_view.navigate(Direction::Forward, false);
}
});
klass.add_binding_action(
gdk::Key::Page_Up,
gdk::ModifierType::empty(),
&action,
);
}
// Pan
ActionPartGlobal::PanUp => {
klass.install_action(&action, None, move |win, _, _| {
win.pan(&gtk::PanDirection::Up);
});
}
ActionPartGlobal::PanRight => {
klass.install_action(&action, None, move |win, _, _| {
win.pan(&gtk::PanDirection::Right);
});
}
ActionPartGlobal::PanDown => {
klass.install_action(&action, None, move |win, _, _| {
win.pan(&gtk::PanDirection::Down);
});
}
ActionPartGlobal::PanLeft => {
klass.install_action(&action, None, move |win, _, _| {
win.pan(&gtk::PanDirection::Left);
});
}
}
}
}
}
#[derive(strum::Display, strum::AsRefStr, strum::EnumIter)]
pub enum Action {
// Navigation
#[strum(to_string = "win.image-left")]
ImageLeft,
#[strum(to_string = "win.image-right")]
ImageRight,
#[strum(to_string = "win.previous")]
Previous,
#[strum(to_string = "win.next")]
Next,
#[strum(to_string = "win.first")]
First,
#[strum(to_string = "win.last")]
Last,
// Zoom/Rotate
#[strum(to_string = "win.rotate-cw")]
RotateCw,
#[strum(to_string = "win.rotate-ccw")]
RotateCcw,
#[strum(to_string = "win.zoom-out-center")]
ZoomOutCenter,
#[strum(to_string = "win.zoom-in-center")]
ZoomInCenter,
#[strum(to_string = "win.zoom-out-cursor")]
ZoomOutCursor,
#[strum(to_string = "win.zoom-in-cursor")]
ZoomInCursor,
#[strum(to_string = "win.zoom-best-fit")]
ZoomBestFit,
#[strum(to_string = "win.zoom-to-exact-100")]
ZoomToExact100,
#[strum(to_string = "win.zoom-to-exact-200")]
ZoomToExact200,
#[strum(to_string = "win.zoom-to-exact-300")]
ZoomToExact300,
// Misc
#[strum(to_string = "win.open")]
Open,
#[strum(to_string = "win.open-with")]
OpenWith,
#[strum(to_string = "win.print")]
Print,
#[strum(to_string = "win.copy-image")]
CopyImage,
#[strum(to_string = "win.set-background")]
SetBackground,
#[strum(to_string = "win.toggle-fullscreen")]
ToggleFullscreen,
#[strum(to_string = "win.leave-fullscreen")]
LeaveFullscreen,
#[strum(to_string = "win.trash")]
Trash,
#[strum(to_string = "win.delete")]
Delete,
#[strum(to_string = "win.toggle-properties")]
ToggleProperties,
#[strum(to_string = "win.about")]
About,
#[strum(to_string = "win.reload")]
Reload,
#[strum(to_string = "win.edit")]
Edit,
}
impl Action {
pub fn init_actions_and_bindings(
klass: &mut <super::imp::LpImageWindow as ObjectSubclass>::Class,
) {
for action in Self::iter() {
match action {
// Navigation
Action::ImageLeft => klass.install_action(&action, None, move |win, _, _| {
if win.direction() == gtk::TextDirection::Rtl {
win.imp().image_view.navigate(Direction::Forward, true);
} else {
win.imp().image_view.navigate(Direction::Back, true);
}
}),
Action::ImageRight => klass.install_action(&action, None, move |win, _, _| {
if win.direction() == gtk::TextDirection::Rtl {
win.imp().image_view.navigate(Direction::Back, true);
} else {
win.imp().image_view.navigate(Direction::Forward, true);
}
}),
Action::Previous => klass.install_action(&action, None, move |win, _, _| {
win.imp().image_view.navigate(Direction::Back, true);
}),
Action::Next => klass.install_action(&action, None, move |win, _, _| {
win.imp().image_view.navigate(Direction::Forward, true);
}),
Action::First => {
klass.install_action(&action, None, move |win, _, _| {
win.imp().image_view.jump(Position::First);
});
klass.add_binding_action(gdk::Key::Home, gdk::ModifierType::empty(), &action);
}
Action::Last => {
klass.install_action(&action, None, move |win, _, _| {
win.imp().image_view.jump(Position::Last);
});
klass.add_binding_action(gdk::Key::End, gdk::ModifierType::empty(), &action);
}
// Rotation/Zoom
Action::RotateCw => {
klass.install_action(&action, None, |win, _, _| {
win.rotate_image(90.0);
});
klass.add_binding_action(gdk::Key::r, gdk::ModifierType::CONTROL_MASK, &action);
}
Action::RotateCcw => {
klass.install_action(&action, None, |win, _, _| {
win.rotate_image(-90.0);
});
klass.add_binding_action(
gdk::Key::r,
gdk::ModifierType::CONTROL_MASK.union(gdk::ModifierType::SHIFT_MASK),
&action,
);
}
Action::ZoomOutCenter => klass.install_action(&action, None, move |win, _, _| {
win.zoom_out_center();
}),
Action::ZoomInCenter => klass.install_action(&action, None, move |win, _, _| {
win.zoom_in_center();
}),
Action::ZoomOutCursor => {
klass.install_action(&action, None, move |win, _, _| {
win.zoom_out_cursor();
});
klass.add_binding_action(gdk::Key::minus, gdk::ModifierType::empty(), &action);
klass.add_binding_action(
gdk::Key::minus,
gdk::ModifierType::CONTROL_MASK,
&action,
);
klass.add_binding_action(
gdk::Key::KP_Subtract,
gdk::ModifierType::empty(),
&action,
);
klass.add_binding_action(
gdk::Key::KP_Subtract,
gdk::ModifierType::CONTROL_MASK,
&action,
);
}
Action::ZoomInCursor => {
klass.install_action(&action, None, move |win, _, _| {
win.zoom_in_cursor();
});
klass.add_binding_action(gdk::Key::equal, gdk::ModifierType::empty(), &action);
klass.add_binding_action(
gdk::Key::equal,
gdk::ModifierType::CONTROL_MASK,
&action,
);
klass.add_binding_action(gdk::Key::plus, gdk::ModifierType::empty(), &action);
klass.add_binding_action(
gdk::Key::plus,
gdk::ModifierType::CONTROL_MASK,
&action,
);
klass.add_binding_action(gdk::Key::KP_Add, gdk::ModifierType::empty(), &action);
klass.add_binding_action(
gdk::Key::KP_Add,
gdk::ModifierType::CONTROL_MASK,
&action,
);
}
Action::ZoomBestFit => {
klass.install_action(&action, None, move |win, _, _| {
win.zoom_best_fit();
});
klass.add_binding_action(gdk::Key::_0, gdk::ModifierType::empty(), &action);
klass.add_binding_action(
gdk::Key::_0,
gdk::ModifierType::CONTROL_MASK,
&action,
);
klass.add_binding_action(gdk::Key::KP_0, gdk::ModifierType::empty(), &action);
klass.add_binding_action(
gdk::Key::KP_0,
gdk::ModifierType::CONTROL_MASK,
&action,
);
}
Action::ZoomToExact100 => {
klass.install_action(&action, None, move |win, _, _| {
win.zoom_to_exact(1.);
});
klass.add_binding_action(gdk::Key::_1, gdk::ModifierType::empty(), &action);
klass.add_binding_action(gdk::Key::KP_1, gdk::ModifierType::empty(), &action);
klass.add_binding_action(
gdk::Key::_1,
gdk::ModifierType::CONTROL_MASK,
&action,
);
klass.add_binding_action(
gdk::Key::KP_1,
gdk::ModifierType::CONTROL_MASK,
&action,
);
}
Action::ZoomToExact200 => {
klass.install_action(&action, None, move |win, _, _| {
win.zoom_to_exact(2.);
});
klass.add_binding_action(gdk::Key::_2, gdk::ModifierType::empty(), &action);
klass.add_binding_action(gdk::Key::KP_2, gdk::ModifierType::empty(), &action);
klass.add_binding_action(
gdk::Key::_2,
gdk::ModifierType::CONTROL_MASK,
&action,
);
klass.add_binding_action(
gdk::Key::KP_2,
gdk::ModifierType::CONTROL_MASK,
&action,
);
}
Action::ZoomToExact300 => {
klass.install_action(&action, None, move |win, _, _| {
win.zoom_to_exact(2.);
});
klass.add_binding_action(gdk::Key::_3, gdk::ModifierType::empty(), &action);
klass.add_binding_action(gdk::Key::KP_3, gdk::ModifierType::empty(), &action);
klass.add_binding_action(
gdk::Key::_3,
gdk::ModifierType::CONTROL_MASK,
&action,
);
klass.add_binding_action(
gdk::Key::KP_3,
gdk::ModifierType::CONTROL_MASK,
&action,
);
}
// Misc
Action::Open => {
klass.install_action_async(&action, None, |win, _, _| async move {
win.pick_file().await;
});
klass.add_binding_action(gdk::Key::O, gdk::ModifierType::CONTROL_MASK, &action);
}
Action::OpenWith => {
klass.install_action_async(&action, None, |win, _, _| async move {
win.open_with().await;
});
klass.add_binding_action(
gdk::Key::O,
gdk::ModifierType::CONTROL_MASK.union(gdk::ModifierType::SHIFT_MASK),
&action,
);
}
Action::Print => {
klass.install_action(&action, None, move |win, _, _| {
win.print();
});
klass.add_binding_action(gdk::Key::P, gdk::ModifierType::CONTROL_MASK, &action);
}
Action::CopyImage => klass.install_action(&action, None, move |win, _, _| {
win.copy_image();
}),
Action::SetBackground => {
klass.install_action_async(&action, None, |win, _, _| async move {
win.set_background().await;
});
klass.add_binding_action(
gdk::Key::F8,
gdk::ModifierType::CONTROL_MASK,
&action,
);
}
Action::ToggleFullscreen => {
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 |obj, _, _| {
obj.window().toggle_fullscreen(false);
});
klass.add_binding_action(gdk::Key::Escape, gdk::ModifierType::empty(), &action);
}
Action::Trash => {
klass.install_action_async(&action, None, |win, _, _| async move {
win.trash().await;
});
klass.add_binding_action(
gdk::Key::KP_Delete,
gdk::ModifierType::empty(),
&action,
);
// The binding added last is shown in menus
klass.add_binding_action(gdk::Key::Delete, gdk::ModifierType::empty(), &action);
}
Action::Delete => {
klass.install_action_async(&action, None, |win, _, _| async move {
win.delete().await;
});
klass.add_binding_action(
gdk::Key::Delete,
gdk::ModifierType::SHIFT_MASK,
&action,
);
klass.add_binding_action(
gdk::Key::KP_Delete,
gdk::ModifierType::SHIFT_MASK,
&action,
);
}
Action::ToggleProperties => {
klass.install_action(&action, None, move |win, _, _| {
win.imp()
.properties_button
.set_active(!win.imp().properties_button.is_active());
});
klass.add_binding_action(gdk::Key::F9, gdk::ModifierType::empty(), &action);
klass.add_binding_action(
gdk::Key::Return,
gdk::ModifierType::ALT_MASK,
&action,
);
}
Action::About => {
klass.install_action_async(&action, None, |obj, _, _| async move {
obj.window().show_about().await
});
}
Action::Reload => {
klass.install_action_async(&action, None, move |win, _, _| async move {
if let Some(current_page) = win.imp().image_view.current_page() {
current_page.image().reload().await;
} else {
log::error!("No current image to reload");
}
});
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);
}
}
}
}
}
impl Deref for ActionPartGlobal {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
impl Deref for Action {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}

View file

@ -0,0 +1,200 @@
// Copyright (c) 2023-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 <http://www.gnu.org/licenses/>.
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
// Copyright (c) 2023 Lubosz Sarnecki
use super::*;
/// Manage visibility of overlay controls
impl LpImageWindow {
/// 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() && window.is_fullscreen() {
self.headerbar().set_opacity(opacity);
} else {
self.headerbar().set_opacity(1.);
}
if window.is_fullscreen() && hiding && opacity < 0.9 {
self.set_cursor(gdk::Cursor::from_name("none", None).as_ref());
} else {
self.set_cursor(None);
}
}
pub fn controls_opacity(&self) -> f64 {
self.image_view().controls_box_start().opacity()
}
/// Animation to show controls
fn show_controls_animation(&self) -> &adw::TimedAnimation {
self.imp().show_controls_animation.get_or_init(|| {
let target = adw::CallbackAnimationTarget::new(glib::clone!(
#[weak(rename_to = obj)]
self,
move |opacity| obj.set_control_opacity(opacity, false)
));
adw::TimedAnimation::builder()
.duration(SHOW_CONTROLS_ANIMATION_DURATION)
.widget(&self.image_view())
.target(&target)
.value_to(1.)
.build()
})
}
/// Animation to hide controls
fn hide_controls_animation(&self) -> &adw::TimedAnimation {
self.imp().hide_controls_animation.get_or_init(|| {
let target = adw::CallbackAnimationTarget::new(glib::clone!(
#[weak(rename_to = obj)]
self,
move |opacity| obj.set_control_opacity(opacity, true)
));
adw::TimedAnimation::builder()
.duration(HIDE_CONTROLS_ANIMATION_DURATION)
.widget(&self.image_view())
.target(&target)
.value_to(0.)
.build()
})
}
/// Schedule a fade animation to play after `HIDE_CONTROLS_IDLE_TIMEOUT` of
/// inactivity
pub fn schedule_hide_controls(&self) {
self.unschedule_hide_controls();
let new_timeout = glib::timeout_add_local_once(
std::time::Duration::from_millis(HIDE_CONTROLS_IDLE_TIMEOUT),
glib::clone!(
#[weak(rename_to = win)]
self,
move || {
win.imp().hide_controls_timeout.take();
win.hide_controls();
}
),
);
self.imp().hide_controls_timeout.replace(Some(new_timeout));
}
fn unschedule_hide_controls(&self) {
if let Some(current_timeout) = self.imp().hide_controls_timeout.take() {
current_timeout.remove();
}
}
pub fn are_controls_visible(&self) -> bool {
if self.hide_controls_animation().state() == adw::AnimationState::Playing {
return false;
}
self.image_view().controls_box_start().opacity() == 1.
|| self.show_controls_animation().state() == adw::AnimationState::Playing
}
/// Start animation to show controls
pub fn show_controls(&self) {
if !self.are_controls_visible() {
self.hide_controls_animation().pause();
self.show_controls_animation()
.set_value_from(self.controls_opacity());
self.show_controls_animation().play();
}
}
/// Start animation to hide controls
pub fn hide_controls(&self) {
if self.are_controls_visible() && !self.image_view().zoom_menu_button().is_active() {
self.show_controls_animation().pause();
self.hide_controls_animation()
.set_value_from(self.controls_opacity());
self.hide_controls_animation().play();
}
}
pub fn on_click(&self) {
self.show_controls();
if self.can_hide_controls() {
self.schedule_hide_controls();
} else {
self.unschedule_hide_controls();
}
}
pub fn on_motion(&self, pointer_position: (f64, f64)) {
let imp = self.imp();
// Check if position really changed since swipe gesture sends change event with
// same position. Also, don't connect this to "leave" because swipe also
// sends fake "leave" events when the widget under the cursor changes.
if imp.pointer_position.get() == pointer_position {
return;
}
imp.pointer_position.set(pointer_position);
self.show_controls();
if self.can_hide_controls() {
self.schedule_hide_controls();
} else {
self.unschedule_hide_controls();
}
}
/// Returns `true` if controls can be hidden
///
/// Only hide controls if cursor not over controls and there is an image
/// shown
fn can_hide_controls(&self) -> bool {
let imp = self.imp();
let controls_hovered = self
.image_view()
.controls_box_start_events()
.contains_pointer()
|| self
.image_view()
.controls_box_end_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
}
}