ui: Non-circular overlays and less buttons

Part-of: <https://gitlab.gnome.org/GNOME/loupe/-/merge_requests/450>
This commit is contained in:
Sophie Herold 2024-09-09 22:55:39 +02:00
parent e5930b134c
commit 0b4c69459f
18 changed files with 470 additions and 93 deletions

5
Cargo.lock generated
View file

@ -1287,9 +1287,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.155"
version = "0.2.158"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
[[package]]
name = "libgweather"
@ -1388,6 +1388,7 @@ dependencies = [
"indexmap",
"kamadak-exif",
"libadwaita",
"libc",
"libgweather",
"log",
"once_cell",

View file

@ -35,6 +35,7 @@ gufo-common = { version = "0.1.0", features = ["serde"] }
gvdb-macros = "0.1.6"
indexmap = "2.0.0"
kamadak-exif = "0.5.5"
libc = "0.2.158"
libgweather = "4.4.0"
log = "0.4.17"
once_cell = "1.16.0"

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 13.398438 2.011719 c -0.394532 0.027343 -0.765626 0.210937 -1.027344 0.507812 l -5.78125 6.609375 l -1.890625 -2.519531 c -0.496094 -0.664063 -1.4375 -0.796875 -2.101563 -0.300781 c -0.316406 0.238281 -0.527344 0.59375 -0.582031 0.988281 c -0.058594 0.394531 0.046875 0.796875 0.285156 1.113281 l 3 4 c 0.273438 0.359375 0.691407 0.582032 1.144531 0.597656 c 0.449219 0.015626 0.886719 -0.171874 1.183594 -0.511718 l 7 -8 c 0.542969 -0.625 0.480469 -1.570313 -0.140625 -2.117188 c -0.300781 -0.261718 -0.691406 -0.394531 -1.089843 -0.367187 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 710 B

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 3 7 h 10 v 2 h -10 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 188 B

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" height="16px" viewBox="0 0 16 16" width="16px"><path d="m 7 3 v 4 h -4 v 2 h 4 v 4 h 2 v -4 h 4 v -2 h -4 v -4 z m 0 0" fill="#222222"/></svg>

After

Width:  |  Height:  |  Size: 222 B

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="16px" height="16px" version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<path d="m10.875 10.062c-0.8125 0.14844-1.1055 1.1602-0.5 1.7188l3 3c0.95703 0.9375 2.3633-0.5 1.4062-1.4375l-3-3c-0.23438-0.23828-0.57422-0.34766-0.90625-0.28125z" fill="#222"/>
<path d="m6.5703 0.0625c-3.5781 0-6.5 2.9219-6.5 6.5s2.9219 6.5 6.5 6.5c3.5781 0 6.5-2.9219 6.5-6.5s-2.9219-6.5-6.5-6.5zm0 2c2.5 0 4.5 2.0039 4.5 4.5 0 2.5-2 4.5-4.5 4.5-2.4961 0-4.5-2-4.5-4.5 0-2.4961 2.0039-4.5 4.5-4.5z" fill="#222"/>
<g transform="translate(-14.443 -19.459)" fill="#222">
<path d="m21.023 23c0.53906 0 0.97656 0.4375 0.97656 0.97656v4.0898c0 0.53906-0.4375 0.97656-0.97656 0.97656-0.53906 0-0.97656-0.4375-0.97656-0.97656v-4.0898c0-0.53906 0.4375-0.97656 0.97656-0.97656z"/>
<path d="m19.027 25h3.9727c0.55078 0 1 0.44922 1 1s-0.44922 1-1 1h-3.9727c-0.55078 0-1-0.44922-1-1s0.44922-1 1-1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 952 B

View file

@ -1,7 +1,13 @@
/* Overlay controls for next, zoom, fullscreen */
button.osd.circular {
min-width: 42px;
min-height: 42px;
/* Overlay controls for prev/next and zoom */
.osd-box {
border-radius: 12px;
background: rgba(0, 0, 0, 0.65);
padding: 6px;
}
.osd-box menubutton button {
padding-left: 0;
padding-right: 0;
}
.fullscreen revealer:not(.raised) .fullscreen-osd {
@ -65,4 +71,4 @@ dialog.error-details textview {
.error-message {
padding: 12px;
font-size: 0.95em;
}
}

View file

@ -0,0 +1 @@
The design of the overlay buttons, especially for zoom controls has been reworked. A new menu supports selecting from pre-defined zoom levels as well as entering a specific zoom level.

View file

@ -18,6 +18,7 @@
pub mod gettext;
use std::ffi::CStr;
use std::fmt::{Debug, Write};
use std::path::{Path, PathBuf};
@ -229,3 +230,34 @@ pub fn contrast_ratio(color1: &gdk::RGBA, color2: &gdk::RGBA) -> f32 {
(l1 + 0.05) / (l2 + 0.05)
}
#[derive(Default)]
pub struct LocaleSettings {
pub decimal_point: Option<String>,
pub thousands_sep: Option<String>,
}
pub fn locale_settings() -> LocaleSettings {
unsafe {
let lconv = libc::localeconv();
let mut locale_settings = LocaleSettings::default();
if lconv.is_null() {
return locale_settings;
}
let lconv = *lconv;
if !(lconv).decimal_point.is_null() {
let s = CStr::from_ptr(lconv.decimal_point);
locale_settings.decimal_point = s.to_str().map(|x| x.to_string()).ok();
}
if !lconv.thousands_sep.is_null() {
let s = CStr::from_ptr(lconv.thousands_sep);
locale_settings.thousands_sep = s.to_str().map(|x| x.to_string()).ok();
}
locale_settings
}
}

View file

@ -184,6 +184,7 @@ mod imp {
pub(super) zoom_animation: OnceCell<adw::TimedAnimation>,
/// Targeted zoom level, might differ from `zoom` when animation is
/// running
#[property(get, set = Self::set_zoom_target)]
pub(super) zoom_target: Cell<f64>,
/// Point in image that should stay under the cursor during animation.
/// The value is in image coordinates.

View file

@ -212,6 +212,7 @@ impl imp::LpImage {
log::trace!("Setting zoom target {zoom_target}");
self.zoom_target.set(zoom_target);
self.obj().notify_zoom_target();
if self.obj().zoom() == self.zoom_target.get() {
self.request_tiles();
@ -365,11 +366,16 @@ impl LpImage {
/// Zoom to specific level with animation not snapping to best-fit
///
/// Used for zooming to 100% or 200%
/// Used for zooming to 100% or 200% etc
pub fn zoom_to_exact(&self, zoom: f64) {
self.imp().zoom_to_full(zoom, true, false, false);
}
/// Same as `zoom_to_exact` but zoom to center
pub fn zoom_to_exact_center(&self, zoom: f64) {
self.imp().zoom_to_full(zoom, true, false, true);
}
pub fn is_best_fit(&self) -> bool {
self.imp().best_fit.get()
}

View file

@ -6,18 +6,10 @@
<attribute name="label" translatable="yes">_Open With…</attribute>
<attribute name="action">win.open-with</attribute>
</item>
<!--<item>
<attribute name="label" translatable="yes">_Display On…</attribute>
<attribute name="action">win.display-on</attribute>
</item>-->
<item>
<attribute name="label" translatable="yes">_Print…</attribute>
<attribute name="action">win.print</attribute>
</item>
<item>
<attribute name="label" translatable="yes">_Copy</attribute>
<attribute name="action">win.copy-image</attribute>
</item>
</section>
<section>
<attribute name="label" translatable="yes">Rotate</attribute>
@ -34,6 +26,14 @@
<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>
<item>
<attribute name="label" translatable="yes">_Copy</attribute>
<attribute name="action">win.copy-image</attribute>
</item>
</section>
</menu>
<template class="LpImagePage" parent="AdwBin">

View file

@ -45,7 +45,7 @@ use crate::decoder::DecoderError;
use crate::deps::*;
use crate::file_model::LpFileModel;
use crate::util::gettext::*;
use crate::util::{Direction, Position};
use crate::util::{self, Direction, Position};
use crate::widgets::{LpImage, LpImagePage, LpPrint, LpSlidingView};
// The number of pages we want to buffer
@ -63,18 +63,38 @@ mod imp {
/// Direct child of this Adw::Bin
#[template_child]
pub(super) bin_child: TemplateChild<gtk::Widget>,
/// overlayed controls
#[template_child]
pub(super) controls_box_start: TemplateChild<gtk::Widget>,
#[template_child]
pub(super) zoom_toggle: TemplateChild<gtk::ToggleButton>,
#[template_child]
pub(super) controls_box_start_events: TemplateChild<gtk::EventControllerMotion>,
/// overlayed controls
#[template_child]
pub(super) controls_box_end: TemplateChild<gtk::Widget>,
#[template_child]
pub(super) controls_box_end_events: TemplateChild<gtk::EventControllerMotion>,
#[template_child]
pub(super) fullscreen_button: TemplateChild<gtk::Button>,
pub(super) zoom_menu_button: TemplateChild<gtk::MenuButton>,
#[template_child]
pub(super) zoom_to_list: TemplateChild<gtk::ListBox>,
#[template_child]
pub(super) zoom_to_300: TemplateChild<gtk::ListBoxRow>,
#[template_child]
pub(super) zoom_to_200: TemplateChild<gtk::ListBoxRow>,
#[template_child]
pub(super) zoom_to_100: TemplateChild<gtk::ListBoxRow>,
#[template_child]
pub(super) zoom_to_66: TemplateChild<gtk::ListBoxRow>,
#[template_child]
pub(super) zoom_to_50: TemplateChild<gtk::ListBoxRow>,
#[template_child]
pub(super) zoom_value: TemplateChild<gtk::Entry>,
#[template_child]
pub(super) submit_zoom: TemplateChild<gtk::Button>,
#[template_child]
pub sliding_view: TemplateChild<LpSlidingView>,
@ -98,6 +118,9 @@ mod imp {
#[property(get = Self::is_next_available)]
_is_next_available: PhantomData<bool>,
#[property(get, set)]
zoom_toggle_state: Cell<bool>,
}
#[glib::object_subclass]
@ -148,10 +171,10 @@ mod imp {
move || obj.target_page_reached()
));
let signal_group = obj.current_image_signals();
let current_image_signal_group = obj.current_image_signals();
let view = &*obj;
signal_group.connect_closure(
current_image_signal_group.connect_closure(
"notify::file",
true,
glib::closure_local!(
@ -163,7 +186,7 @@ mod imp {
),
);
signal_group.connect_closure(
current_image_signal_group.connect_closure(
"notify::is-deleted",
true,
glib::closure_local!(
@ -175,6 +198,97 @@ mod imp {
),
);
current_image_signal_group.connect_closure(
"notify::zoom-target",
true,
glib::closure_local!(
#[watch]
view,
move |img: LpImage, _: glib::ParamSpec| {
let mut percent =
((img.zoom_target() * 10_000.).round() / 100.).to_string();
if let Some(decimal) = util::locale_settings().decimal_point {
percent = percent.replace('.', &decimal);
}
view.imp()
.zoom_value
.set_text(&gettext_f("{} %", [percent]));
view.imp().zoom_value.select_region(0, -1);
}
),
);
self.zoom_toggle.connect_clicked(clone!(
#[weak]
obj,
move |button| {
if let Some(page) = obj.current_page() {
if button.is_active() {
page.image().zoom_in_center()
} else {
page.image().zoom_best_fit()
};
}
}
));
self.zoom_to_list.connect_row_activated(clone!(
#[weak]
obj,
move |_, row| {
let imp = obj.imp();
let value = if row == &*imp.zoom_to_300 {
3.
} else if row == &*imp.zoom_to_200 {
2.
} else if row == &*imp.zoom_to_100 {
1.
} else if row == &*imp.zoom_to_66 {
0.66
} else if row == &*imp.zoom_to_50 {
0.5
} else {
log::error!("Activated unknown entry in zoom to popover.");
return;
};
if let Some(image) = obj.current_image() {
image.zoom_to_exact_center(value);
}
// Close popover after click
imp.zoom_menu_button.set_active(false);
}
));
self.submit_zoom.connect_clicked(clone!(
#[weak]
obj,
move |_| {
if let Some(page) = obj.current_page() {
let mut value = obj.imp().zoom_value.text().to_string();
let locale_settings = util::locale_settings();
if let Some(thousands) = locale_settings.thousands_sep {
value = value.replace(&thousands, "");
}
if let Some(decimal) = locale_settings.decimal_point {
value = value.replace(&decimal, ".");
}
value = value.replace('%', "");
value = value.trim().to_string();
if let Ok(value) = value.parse::<f64>() {
page.image().zoom_to_exact(value / 100.);
}
}
}
));
self.drag_source.set_exclusive(true);
self.drag_source.connect_prepare(glib::clone!(
@ -678,16 +792,6 @@ impl LpImageView {
}
}
pub fn on_fullscreen_changed(&self, is_fullscreened: bool) {
let icon = if is_fullscreened {
"view-restore-symbolic"
} else {
"view-fullscreen-symbolic"
};
self.imp().fullscreen_button.set_icon_name(icon);
}
pub fn rotate_image(&self, angle: f64) {
if let Some(current_page) = self.current_page() {
current_page.image().rotate_by(angle);
@ -770,4 +874,8 @@ impl LpImageView {
Ok(())
}
pub fn zoom_menu_button(&self) -> gtk::MenuButton {
self.imp().zoom_menu_button.clone()
}
}

View file

@ -18,7 +18,10 @@
<property name="margin-end">18</property>
<property name="margin-top">18</property>
<property name="margin-bottom">18</property>
<property name="spacing">12</property>
<property name="spacing">6</property>
<style>
<class name="osd-box" />
</style>
<child>
<object class="GtkButton" id="prev_button">
<property name="halign">start</property>
@ -26,8 +29,7 @@
<property name="action-name">win.previous</property>
<property name="tooltip-text" translatable="yes">Previous Image</property>
<style>
<class name="osd" />
<class name="circular" />
<class name="flat" />
</style>
</object>
</child>
@ -37,8 +39,7 @@
<property name="action-name">win.next</property>
<property name="tooltip-text" translatable="yes">Next Image</property>
<style>
<class name="osd" />
<class name="circular" />
<class name="flat" />
</style>
</object>
</child>
@ -55,38 +56,203 @@
<property name="margin-end">18</property>
<property name="margin-top">18</property>
<property name="margin-bottom">18</property>
<property name="spacing">12</property>
<style>
<class name="osd-box" />
</style>
<child>
<object class="GtkButton" id="zoom_out">
<property name="icon-name">zoom-out-symbolic</property>
<property name="action-name">win.zoom-out-center</property>
<property name="tooltip-text" translatable="yes">Zoom Out</property>
<object class="GtkRevealer">
<property name="reveal-child" bind-source="LpImageView" bind-property="zoom-toggle-state" bind-flags="sync-create" />
<property name="transition-type">slide-left</property>
<child>
<object class="GtkBox">
<property name="spacing">6</property>
<child>
<object class="GtkButton" id="zoom_out">
<property name="icon-name">minus-symbolic</property>
<property name="action-name">win.zoom-out-center</property>
<property name="tooltip-text" translatable="yes">Zoom Out</property>
<style>
<class name="flat" />
</style>
</object>
</child>
<child>
<object class="GtkButton" id="zoom_in">
<property name="margin-end">6</property>
<property name="icon-name">plus-symbolic</property>
<property name="action-name">win.zoom-in-center</property>
<property name="tooltip-text" translatable="yes">Zoom In</property>
<style>
<class name="flat" />
</style>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkToggleButton" id="zoom_toggle">
<property name="margin-end">6</property>
<property name="icon-name">zoom-toggle-symbolic</property>
<property name="active" bind-source="LpImageView" bind-property="zoom-toggle-state" bind-flags="sync-create" />
<property name="tooltip-text" translatable="yes">Toggle Zoom</property>
<style>
<class name="osd" />
<class name="circular" />
<class name="flat" />
</style>
</object>
</child>
<child>
<object class="GtkButton" id="zoom_in">
<property name="icon-name">zoom-in-symbolic</property>
<property name="action-name">win.zoom-in-center</property>
<property name="tooltip-text" translatable="yes">Zoom In</property>
<object class="GtkMenuButton" id="zoom_menu_button">
<property name="tooltip-text" translatable="yes">Zoom to Specific Level</property>
<property name="direction">up</property>
<property name="icon-name">pan-down-symbolic</property>
<style>
<class name="osd" />
<class name="circular" />
</style>
</object>
</child>
<child>
<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>
<style>
<class name="osd" />
<class name="circular" />
<class name="flat" />
</style>
<property name="popover">
<!--
Use manual implementation instead of GtkPopoverMenu because:
1. We need to specify the shortcuts manually since called from the menu the zoom does not aim for the cursor.
2. Hovering other menu entries would steal the focus from the GtkEntry.
-->
<object class="GtkPopover">
<property name="default-widget">submit_zoom</property>
<style>
<class name="menu" />
</style>
<property name="child">
<object class="GtkBox">
<property name="orientation">vertical</property>
<property name="margin-top">6</property>
<property name="margin-end">6</property>
<property name="margin-bottom">6</property>
<property name="margin-start">6</property>
<property name="spacing">6</property>
<child>
<object class="GtkListBox" id="zoom_to_list">
<child>
<object class="GtkListBoxRow" id="zoom_to_300">
<property name="child">
<object class="GtkBox">
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label">300 %</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">end</property>
<property name="hexpand">true</property>
<property name="label">Ctrl+3</property>
<style>
<class name="dim-label" />
</style>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkListBoxRow" id="zoom_to_200">
<property name="child">
<object class="GtkBox">
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label">200 %</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">end</property>
<property name="hexpand">true</property>
<property name="label">Ctrl+2</property>
<style>
<class name="dim-label" />
</style>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkListBoxRow" id="zoom_to_100">
<property name="child">
<object class="GtkBox">
<child>
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label">100 %</property>
</object>
</child>
<child>
<object class="GtkLabel">
<property name="halign">end</property>
<property name="hexpand">true</property>
<property name="label">Ctrl+1</property>
<style>
<class name="dim-label" />
</style>
</object>
</child>
</object>
</property>
</object>
</child>
<child>
<object class="GtkListBoxRow" id="zoom_to_66">
<property name="child">
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label">66 %</property>
</object>
</property>
</object>
</child>
<child>
<object class="GtkListBoxRow" id="zoom_to_50">
<property name="child">
<object class="GtkLabel">
<property name="halign">start</property>
<property name="label">50 %</property>
</object>
</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkBox">
<property name="spacing">12</property>
<child>
<object class="GtkEntry" id="zoom_value">
<property name="activates-default">True</property>
<property name="input-purpose">number</property>
<property name="max-width-chars">8</property>
<property name="tooltip-text">Custom Zoom Level</property>
</object>
</child>
<child>
<object class="GtkButton" id="submit_zoom">
<property name="icon-name">checkmark-symbolic</property>
<property name="tooltip-text">Zoom to Specified Level</property>
<style>
<class name="suggested-action" />
<class name="circular" />
</style>
</object>
</child>
</object>
</child>
</object>
</property>
</object>
</property>
</object>
</child>
<child>
@ -100,4 +266,4 @@
<class name="image-view" />
</style>
</template>
</interface>
</interface>

View file

@ -79,9 +79,6 @@ mod imp {
#[properties(wrapper_type = super::LpWindow)]
#[template(file = "window.ui")]
pub struct LpWindow {
// Template children are used with the
// TemplateChild<T> wrapper, where T is the
// object type of the template child.
#[template_child]
pub(super) window_content: TemplateChild<LpWindowContent>,
#[template_child]
@ -92,6 +89,9 @@ mod imp {
pub(super) properties_button: TemplateChild<gtk::ToggleButton>,
#[template_child]
pub(super) primary_menu: TemplateChild<gtk::MenuButton>,
#[template_child]
pub(super) fullscreen_button: TemplateChild<gtk::Button>,
#[template_child]
pub(super) toast_overlay: TemplateChild<adw::ToastOverlay>,
#[template_child]
@ -375,6 +375,22 @@ mod imp {
obj.schedule_hide_controls();
});
obj.image_view()
.zoom_menu_button()
.connect_active_notify(glib::clone!(
#[weak]
obj,
move |button| {
if button.is_active() {
// Show overlay controls when zoom popover in them gets opened
obj.show_controls();
} else {
// Schedule hide overlay controls when zoom popover gets closed
obj.schedule_hide_controls();
}
}
));
// Activate global shortcuts only if no dialog is open
obj.connect_visible_dialog_notify(|obj| obj.update_accel_status());
obj.connect_is_active_notify(|obj| obj.update_accel_status());
@ -499,7 +515,7 @@ impl LpWindow {
}
fn zoom_to_exact(&self, level: f64) {
if let Some(image) = self.imp().image_view.current_image() {
if let Some(image) = self.image_view().current_image() {
image.zoom_to_exact(level);
}
}
@ -750,8 +766,9 @@ impl LpWindow {
Action::RotateCw,
Action::RotateCcw,
Action::ZoomBestFit,
Action::ZoomToExact1,
Action::ZoomToExact2,
Action::ZoomToExact100,
Action::ZoomToExact200,
Action::ZoomToExact300,
];
let enabled_shown = self
@ -780,6 +797,12 @@ impl LpWindow {
self.action_set_enabled(&Action::ZoomInCursor, can_zoom_in && enabled_shown);
self.action_set_enabled(&Action::ZoomInCenter, can_zoom_in && enabled_shown);
self.image_view().set_zoom_toggle_state(
self.image_view()
.current_image()
.is_some_and(|image| !image.is_best_fit()),
);
// Actions that are available if there is an current image, even if it's not
// shown
const ACTIONS_CURRENT: &[Action] = &[
@ -944,9 +967,12 @@ impl LpWindow {
}
fn on_fullscreen_changed(&self) {
self.imp()
.image_view
.on_fullscreen_changed(self.is_fullscreen());
let icon = if self.is_fullscreen() {
"view-restore-symbolic"
} else {
"view-fullscreen-symbolic"
};
self.imp().fullscreen_button.set_icon_name(icon);
if !self.is_fullscreen() {
self.set_cursor(None);

View file

@ -16,10 +16,6 @@
<attribute name="label" translatable="yes">_Open With…</attribute>
<attribute name="action">win.open-with</attribute>
</item>
<!--<item>
<attribute name="label" translatable="yes">_Display On…</attribute>
<attribute name="action">win.display-on</attribute>
</item>-->
<item>
<attribute name="label" translatable="yes">_Print…</attribute>
<attribute name="action">win.print</attribute>
@ -40,6 +36,10 @@
<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>
@ -198,17 +198,10 @@
<class name="fullscreen-osd" />
</style>
<child type="start">
<object class="GtkButton">
<property name="icon-name">edit-copy-symbolic</property>
<property name="action-name">win.copy-image</property>
<property name="tooltip-text" translatable="yes">Copy to Clipboard</property>
</object>
</child>
<child type="start">
<object class="GtkButton">
<property name="icon-name">user-trash-symbolic</property>
<property name="action-name">win.trash</property>
<property name="tooltip-text" translatable="yes">Move to Trash</property>
<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">

View file

@ -167,10 +167,12 @@ pub enum Action {
ZoomInCursor,
#[strum(to_string = "win.zoom-best-fit")]
ZoomBestFit,
#[strum(to_string = "win.zoom-to-exact-1")]
ZoomToExact1,
#[strum(to_string = "win.zoom-to-exact-2")]
ZoomToExact2,
#[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,
@ -332,7 +334,7 @@ impl Action {
);
}
Action::ZoomToExact1 => {
Action::ZoomToExact100 => {
klass.install_action(&action, None, move |win, _, _| {
win.zoom_to_exact(1.);
});
@ -350,7 +352,7 @@ impl Action {
);
}
Action::ZoomToExact2 => {
Action::ZoomToExact200 => {
klass.install_action(&action, None, move |win, _, _| {
win.zoom_to_exact(2.);
});
@ -368,6 +370,24 @@ impl 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 {
@ -427,12 +447,13 @@ impl Action {
klass.install_action_async(&action, None, |win, _, _| async move {
win.trash().await;
});
klass.add_binding_action(gdk::Key::Delete, gdk::ModifierType::empty(), &action);
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 => {

View file

@ -129,7 +129,7 @@ impl LpWindow {
/// Start animation to hide controls
pub fn hide_controls(&self) {
if self.are_controls_visible() {
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());