window: Move to window_image for editing separation
This commit is contained in:
parent
7253be8905
commit
0012d807e3
4 changed files with 0 additions and 0 deletions
1014
src/widgets/window_image.rs
Normal file
1014
src/widgets/window_image.rs
Normal file
File diff suppressed because it is too large
Load diff
308
src/widgets/window_image.ui
Normal file
308
src/widgets/window_image.ui
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<menu id="primary_menu">
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_New Window</attribute>
|
||||
<attribute name="action">app.new-window</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Open…</attribute>
|
||||
<attribute name="action">win.open</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Open With…</attribute>
|
||||
<attribute name="action">win.open-with</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Print…</attribute>
|
||||
<attribute name="action">win.print</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<attribute name="label" translatable="yes">Rotate</attribute>
|
||||
<attribute name="display-hint">inline-buttons</attribute>
|
||||
<item>
|
||||
<attribute name="custom">rotate-left</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="custom">rotate-right</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Set as Background…</attribute>
|
||||
<attribute name="action">win.set-background</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Delete</attribute>
|
||||
<attribute name="action">win.trash</attribute>
|
||||
</item>
|
||||
</section>
|
||||
<section>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Help</attribute>
|
||||
<attribute name="action">app.help</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_Keyboard Shortcuts</attribute>
|
||||
<attribute name="action">win.show-help-overlay</attribute>
|
||||
</item>
|
||||
<item>
|
||||
<attribute name="label" translatable="yes">_About Image Viewer</attribute>
|
||||
<attribute name="action">win.about</attribute>
|
||||
</item>
|
||||
</section>
|
||||
</menu>
|
||||
<template class="LpWindow" parent="AdwApplicationWindow">
|
||||
<property name="default-width">600</property>
|
||||
<property name="default-height">498</property>
|
||||
<property name="width-request">360</property>
|
||||
<property name="height-request">294</property>
|
||||
<child>
|
||||
<object class="AdwBreakpoint">
|
||||
<condition>max-width: 590sp</condition>
|
||||
<setter object="multi_layout_view" property="layout-name">bottom-properties</setter>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwBreakpoint">
|
||||
<condition>max-width: 590sp and max-height: 550sp</condition>
|
||||
<setter object="multi_layout_view" property="layout-name">overlay-properties</setter>
|
||||
</object>
|
||||
</child>
|
||||
<property name="content">
|
||||
<object class="AdwToastOverlay" id="toast_overlay">
|
||||
<property name="child">
|
||||
<object class="AdwMultiLayoutView" id="multi_layout_view">
|
||||
<child>
|
||||
<object class="AdwLayout">
|
||||
<property name="name">sidebar-properies</property>
|
||||
<property name="content">
|
||||
<object class="LpWindowContent" id="window_content">
|
||||
<property name="show-properties" bind-source="properties_button" bind-property="active" bind-flags="sync-create|invert-boolean" />
|
||||
<property name="headerbar">
|
||||
<object class="AdwLayoutSlot">
|
||||
<property name="id">headerbar</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="content">
|
||||
<object class="AdwOverlaySplitView" id="properties_split_view">
|
||||
<property name="sidebar-position">end</property>
|
||||
<property name="min-sidebar-width">255</property>
|
||||
<property name="show-sidebar" bind-source="properties_button" bind-property="active" bind-flags="bidirectional|sync-create" />
|
||||
<property name="content">
|
||||
<object class="AdwLayoutSlot">
|
||||
<property name="id">content</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="sidebar">
|
||||
<object class="AdwLayoutSlot">
|
||||
<property name="id">properties</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwLayout">
|
||||
<property name="name">bottom-properties</property>
|
||||
<property name="content">
|
||||
<object class="LpWindowContent">
|
||||
<property name="show-properties" bind-source="properties_button" bind-property="active" bind-flags="sync-create|invert-boolean" />
|
||||
<property name="headerbar">
|
||||
<object class="AdwLayoutSlot">
|
||||
<property name="id">headerbar</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="content">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="AdwLayoutSlot">
|
||||
<property name="id">content</property>
|
||||
<property name="vexpand">True</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkRevealer">
|
||||
<property name="transition-type">slide-up</property>
|
||||
<property name="reveal-child" bind-source="properties_button" bind-property="active" bind-flags="bidirectional|sync-create" />
|
||||
<style>
|
||||
<class name="bottom-properties" />
|
||||
</style>
|
||||
<property name="child">
|
||||
<object class="AdwLayoutSlot">
|
||||
<property name="id">properties</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwLayout">
|
||||
<property name="name">overlay-properties</property>
|
||||
<property name="content">
|
||||
<object class="AdwBottomSheet">
|
||||
<property name="modal">True</property>
|
||||
<property name="open" bind-source="properties_button" bind-property="active" bind-flags="bidirectional|sync-create" />
|
||||
<property name="content">
|
||||
<object class="LpWindowContent">
|
||||
<property name="show-properties" bind-source="properties_button" bind-property="active" bind-flags="sync-create|invert-boolean" />
|
||||
<property name="headerbar">
|
||||
<object class="AdwLayoutSlot">
|
||||
<property name="id">headerbar</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="content">
|
||||
<object class="AdwLayoutSlot">
|
||||
<property name="id">content</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
<property name="sheet">
|
||||
<object class="AdwToolbarView">
|
||||
<child type="top">
|
||||
<object class="AdwHeaderBar">
|
||||
<property name="show-title">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<property name="content">
|
||||
<object class="AdwLayoutSlot">
|
||||
<property name="id">properties</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<!-- Layout slots -->
|
||||
<child type="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-start-title-buttons" bind-source="LpWindow" bind-property="fullscreened" bind-flags="sync-create|invert-boolean" />
|
||||
<style>
|
||||
<class name="fullscreen-osd" />
|
||||
</style>
|
||||
<child type="start">
|
||||
<object class="GtkButton" id="fullscreen_button">
|
||||
<property name="halign">end</property>
|
||||
<property name="action-name">win.toggle-fullscreen</property>
|
||||
<property name="tooltip-text" translatable="yes">Toggle Fullscreen</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkMenuButton" id="primary_menu">
|
||||
<property name="icon-name">open-menu-symbolic</property>
|
||||
<property name="tooltip-text" translatable="yes">Main Menu</property>
|
||||
<property name="primary">True</property>
|
||||
<property name="popover">
|
||||
<object class="GtkPopoverMenu">
|
||||
<property name="menu-model">primary_menu</property>
|
||||
<child type="rotate-left">
|
||||
<object class="GtkButton">
|
||||
<property name="icon-name">object-rotate-left-symbolic</property>
|
||||
<property name="tooltip-text" translatable="yes">Rotate Left</property>
|
||||
<property name="action-name">win.rotate-ccw</property>
|
||||
<style>
|
||||
<class name="flat" />
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
<child type="rotate-right">
|
||||
<object class="GtkButton">
|
||||
<property name="icon-name">object-rotate-right-symbolic</property>
|
||||
<property name="tooltip-text" translatable="yes">Rotate Right</property>
|
||||
<property name="action-name">win.rotate-cw</property>
|
||||
<style>
|
||||
<class name="flat" />
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="end">
|
||||
<object class="GtkToggleButton" id="properties_button">
|
||||
<property name="icon-name">info-outline-symbolic</property>
|
||||
<property name="tooltip-text" translatable="yes">Image Properties</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEventControllerMotion" id="headerbar_events" />
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child type="content">
|
||||
<object class="LpDragOverlay" id="drag_overlay">
|
||||
<property name="drop_target">drop_target</property>
|
||||
<property name="content">
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible-child">status_page</property>
|
||||
<style>
|
||||
<class name="drop-widget" />
|
||||
</style>
|
||||
<child>
|
||||
<object class="GtkDropTarget" id="drop_target">
|
||||
<property name="actions">copy</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwStatusPage" id="status_page">
|
||||
<property name="title" translatable="yes">View Images</property>
|
||||
<property name="description" translatable="yes">Drag and drop images here</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="halign">center</property>
|
||||
<property name="label" translatable="yes">_Open Files…</property>
|
||||
<property name="use-underline">True</property>
|
||||
<property name="action-name">win.open</property>
|
||||
<style>
|
||||
<class name="pill" />
|
||||
<class name="suggested-action" />
|
||||
</style>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="LpImageView" id="image_view">
|
||||
<child>
|
||||
<object class="GtkGestureClick" id="forward_click_gesture">
|
||||
<property name="button">9</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkGestureClick" id="backward_click_gesture">
|
||||
<property name="button">8</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="properties">
|
||||
<object class="LpPropertiesView" id="properties_view" />
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</template>
|
||||
</interface>
|
||||
522
src/widgets/window_image/actions.rs
Normal file
522
src/widgets/window_image/actions.rs
Normal file
|
|
@ -0,0 +1,522 @@
|
|||
// 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: >k::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: >k::Application) {
|
||||
for action in Self::iter() {
|
||||
application.set_accels_for_action(&action, &[]);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_actions_and_bindings(klass: &mut <super::imp::LpWindow 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(>k::PanDirection::Up);
|
||||
});
|
||||
}
|
||||
ActionPartGlobal::PanRight => {
|
||||
klass.install_action(&action, None, move |win, _, _| {
|
||||
win.pan(>k::PanDirection::Right);
|
||||
});
|
||||
}
|
||||
ActionPartGlobal::PanDown => {
|
||||
klass.install_action(&action, None, move |win, _, _| {
|
||||
win.pan(>k::PanDirection::Down);
|
||||
});
|
||||
}
|
||||
ActionPartGlobal::PanLeft => {
|
||||
klass.install_action(&action, None, move |win, _, _| {
|
||||
win.pan(>k::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,
|
||||
}
|
||||
|
||||
impl Action {
|
||||
pub fn init_actions_and_bindings(klass: &mut <super::imp::LpWindow 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 |win, _, _| {
|
||||
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.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, |win, _, _| async move {
|
||||
win.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
198
src/widgets/window_image/controls.rs
Normal file
198
src/widgets/window_image/controls.rs
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
// 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 LpWindow {
|
||||
/// 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) {
|
||||
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() {
|
||||
self.headerbar().set_opacity(opacity);
|
||||
} else {
|
||||
self.headerbar().set_opacity(1.);
|
||||
}
|
||||
|
||||
if self.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
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue