Separate window show ui and code to enable editing
This commit is contained in:
parent
0012d807e3
commit
9205c93fae
8 changed files with 527 additions and 382 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -6,11 +6,13 @@
|
|||
<lookup name="root">LpWindowContent</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
<!--
|
||||
<binding name="is-showing-image">
|
||||
<lookup name="is-showing-image" type="LpWindow">
|
||||
<lookup name="root">LpWindowContent</lookup>
|
||||
</lookup>
|
||||
</binding>
|
||||
-->
|
||||
<property name="child">
|
||||
<object class="AdwToolbarView" id="toolbar_view">
|
||||
<child type="top">
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ use std::path::{Path, PathBuf};
|
|||
use actions::*;
|
||||
use adw::prelude::*;
|
||||
use adw::subclass::prelude::*;
|
||||
use glib::{clone, Properties};
|
||||
use gtk::{CompositeTemplate, Widget};
|
||||
use adw::{glib, gtk};
|
||||
use gtk::Widget;
|
||||
|
||||
use crate::application::LpApplication;
|
||||
use crate::config;
|
||||
|
|
@ -44,9 +44,7 @@ use crate::deps::*;
|
|||
use crate::util::gettext::*;
|
||||
use crate::util::Direction;
|
||||
use crate::widgets::{LpDragOverlay, LpImage, LpImageView, LpPropertiesView, LpWindowContent};
|
||||
|
||||
/// Show window after X milliseconds even if image dimensions are not known yet
|
||||
const SHOW_WINDOW_AFTER: u64 = 2000;
|
||||
use crate::window::LpWindow;
|
||||
|
||||
/// Animation duration for showing overlay buttons in milliseconds
|
||||
const SHOW_CONTROLS_ANIMATION_DURATION: u32 = 200;
|
||||
|
|
@ -57,43 +55,30 @@ const HIDE_CONTROLS_IDLE_TIMEOUT: u64 = 3000;
|
|||
|
||||
mod imp {
|
||||
|
||||
use gio::glib::VariantTy;
|
||||
|
||||
use super::*;
|
||||
#[derive(Debug, Default, gtk::CompositeTemplate, glib::Properties)]
|
||||
#[properties(wrapper_type = super::LpWindowImage)]
|
||||
#[template(file = "window_image.ui")]
|
||||
pub struct LpWindowImage {
|
||||
#[property(get, construct_only)]
|
||||
window: OnceCell<LpWindow>,
|
||||
|
||||
// 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, Properties, CompositeTemplate)]
|
||||
#[properties(wrapper_type = super::LpWindow)]
|
||||
#[template(file = "window.ui")]
|
||||
pub struct LpWindow {
|
||||
#[template_child]
|
||||
pub(super) window_content: TemplateChild<LpWindowContent>,
|
||||
|
||||
#[template_child]
|
||||
pub(super) headerbar: TemplateChild<adw::HeaderBar>,
|
||||
#[template_child]
|
||||
pub(super) headerbar_events: TemplateChild<gtk::EventControllerMotion>,
|
||||
#[template_child]
|
||||
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>,
|
||||
pub(super) properties_button: TemplateChild<gtk::ToggleButton>,
|
||||
#[template_child]
|
||||
pub(super) primary_menu: TemplateChild<gtk::MenuButton>,
|
||||
|
||||
#[template_child]
|
||||
pub(super) stack: TemplateChild<gtk::Stack>,
|
||||
#[template_child]
|
||||
|
|
@ -129,10 +114,14 @@ mod imp {
|
|||
}
|
||||
|
||||
#[glib::object_subclass]
|
||||
impl ObjectSubclass for LpWindow {
|
||||
const NAME: &'static str = "LpWindow";
|
||||
type Type = super::LpWindow;
|
||||
type ParentType = adw::ApplicationWindow;
|
||||
impl ObjectSubclass for LpWindowImage {
|
||||
const NAME: &'static str = "LpWindowImage";
|
||||
type Type = super::LpWindowImage;
|
||||
type ParentType = adw::Bin;
|
||||
|
||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
|
||||
fn class_init(klass: &mut Self::Class) {
|
||||
// bind_template() is a function generated by the
|
||||
|
|
@ -159,45 +148,31 @@ mod imp {
|
|||
|
||||
ActionPartGlobal::init_actions_and_bindings(klass);
|
||||
Action::init_actions_and_bindings(klass);
|
||||
|
||||
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<Self>) {
|
||||
obj.init_template();
|
||||
}
|
||||
}
|
||||
|
||||
#[glib::derived_properties]
|
||||
impl ObjectImpl for LpWindow {
|
||||
impl ObjectImpl for LpWindowImage {
|
||||
fn constructed(&self) {
|
||||
self.parent_constructed();
|
||||
|
||||
let obj = self.obj();
|
||||
|
||||
self.forward_click_gesture.connect_pressed(clone!(
|
||||
self.forward_click_gesture.connect_pressed(glib::clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_, _, _, _| {
|
||||
obj.image_view().navigate(Direction::Forward, false);
|
||||
}
|
||||
));
|
||||
self.backward_click_gesture.connect_pressed(clone!(
|
||||
self.backward_click_gesture.connect_pressed(glib::clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_, _, _, _| {
|
||||
obj.image_view().navigate(Direction::Back, false);
|
||||
}
|
||||
));
|
||||
self.properties_button.connect_toggled(clone!(
|
||||
self.properties_button.connect_toggled(glib::clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_| {
|
||||
|
|
@ -205,25 +180,6 @@ mod imp {
|
|||
}
|
||||
));
|
||||
|
||||
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.on_current_page_changed();
|
||||
obj.on_fullscreen_changed();
|
||||
|
||||
|
|
@ -233,16 +189,12 @@ mod imp {
|
|||
move |_| obj.on_current_page_changed()
|
||||
));
|
||||
|
||||
obj.connect_fullscreened_notify(glib::clone!(
|
||||
obj.window().connect_fullscreened_notify(glib::clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_| obj.on_fullscreen_changed()
|
||||
));
|
||||
|
||||
obj.connect_map(|win| {
|
||||
win.resize_default();
|
||||
});
|
||||
|
||||
let gesture_click = gtk::GestureClick::new();
|
||||
gesture_click.connect_pressed(glib::clone!(
|
||||
#[weak]
|
||||
|
|
@ -392,15 +344,23 @@ mod imp {
|
|||
));
|
||||
|
||||
// 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());
|
||||
obj.window().connect_visible_dialog_notify(glib::clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_| obj.update_accel_status()
|
||||
));
|
||||
obj.window().connect_is_active_notify(glib::clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
move |_| obj.update_accel_status()
|
||||
));
|
||||
|
||||
self.status_page
|
||||
.set_icon_name(Some(&format!("{}-symbolic", config::APP_ID)));
|
||||
|
||||
self.drop_target.set_types(&[gdk::FileList::static_type()]);
|
||||
|
||||
self.drop_target.connect_accept(clone!(
|
||||
self.drop_target.connect_accept(glib::clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
#[upgrade_or]
|
||||
|
|
@ -420,7 +380,7 @@ mod imp {
|
|||
// the relevant signal to see which parameters you need.
|
||||
// In this case, we need only need the GValue,
|
||||
// so we name it `value` then use `_` for the other spots.
|
||||
self.drop_target.connect_drop(clone!(
|
||||
self.drop_target.connect_drop(glib::clone!(
|
||||
#[weak]
|
||||
obj,
|
||||
#[upgrade_or]
|
||||
|
|
@ -451,7 +411,7 @@ mod imp {
|
|||
}
|
||||
|
||||
// Maybe one day this will actually work
|
||||
obj.present();
|
||||
obj.window().present();
|
||||
|
||||
true
|
||||
}
|
||||
|
|
@ -459,12 +419,10 @@ mod imp {
|
|||
}
|
||||
}
|
||||
|
||||
impl WidgetImpl for LpWindow {}
|
||||
impl WindowImpl for LpWindow {}
|
||||
impl ApplicationWindowImpl for LpWindow {}
|
||||
impl AdwApplicationWindowImpl for LpWindow {}
|
||||
impl WidgetImpl for LpWindowImage {}
|
||||
impl BinImpl for LpWindowImage {}
|
||||
|
||||
impl LpWindow {
|
||||
impl LpWindowImage {
|
||||
fn is_showing_image(&self) -> bool {
|
||||
self.image_view.try_get().map_or(false, |image_view| {
|
||||
image_view
|
||||
|
|
@ -476,20 +434,11 @@ mod imp {
|
|||
}
|
||||
|
||||
glib::wrapper! {
|
||||
pub struct LpWindow(ObjectSubclass<imp::LpWindow>)
|
||||
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow,
|
||||
@implements gio::ActionMap, gio::ActionGroup, gtk::Native;
|
||||
pub struct LpWindowImage(ObjectSubclass<imp::LpWindowImage>)
|
||||
@extends gtk::Widget, adw::Bin;
|
||||
}
|
||||
|
||||
impl LpWindow {
|
||||
pub fn new<A: IsA<gtk::Application>>(app: &A) -> Self {
|
||||
glib::Object::builder().property("application", app).build()
|
||||
}
|
||||
|
||||
fn toggle_fullscreen(&self, fullscreen: bool) {
|
||||
self.set_fullscreened(fullscreen);
|
||||
}
|
||||
|
||||
impl LpWindowImage {
|
||||
fn zoom_out_cursor(&self) {
|
||||
if let Some(image) = self.imp().image_view.current_image() {
|
||||
image.zoom_out_cursor();
|
||||
|
|
@ -563,7 +512,7 @@ impl LpWindow {
|
|||
.as_ref(),
|
||||
);
|
||||
|
||||
if let Ok(selected) = chooser.open_multiple_future(Some(self)).await {
|
||||
if let Ok(selected) = chooser.open_multiple_future(Some(&self.window())).await {
|
||||
let images: Vec<_> = selected
|
||||
.into_iter()
|
||||
.filter_map(|files| {
|
||||
|
|
@ -584,7 +533,7 @@ impl LpWindow {
|
|||
if let Some(ref file) = imp.image_view.current_file() {
|
||||
let launcher = gtk::FileLauncher::new(Some(file));
|
||||
launcher.set_always_ask(true);
|
||||
if let Err(e) = launcher.launch_future(Some(self)).await {
|
||||
if let Err(e) = launcher.launch_future(Some(&self.window())).await {
|
||||
if !e.matches(gtk::DialogError::Dismissed) {
|
||||
log::error!("Could not open image in external program: {}", e);
|
||||
}
|
||||
|
|
@ -616,7 +565,7 @@ impl LpWindow {
|
|||
|
||||
/// Returns true if some text in metadata is currently selected
|
||||
fn has_metadata_selected(&self) -> bool {
|
||||
if let Some(focus_widget) = self.focus() {
|
||||
if let Some(focus_widget) = self.window().focus() {
|
||||
if focus_widget.is_ancestor(&*self.imp().properties_view) {
|
||||
if let Ok(label) = focus_widget.downcast::<gtk::Label>() {
|
||||
return label.selection_bounds().is_some();
|
||||
|
|
@ -634,7 +583,7 @@ impl LpWindow {
|
|||
if let Err(e) = imp.image_view.copy() {
|
||||
log::error!("Failed to copy to clipboard: {}", e);
|
||||
} else {
|
||||
self.show_toast(
|
||||
self.window().show_toast(
|
||||
gettext("Image copied to clipboard"),
|
||||
adw::ToastPriority::High,
|
||||
);
|
||||
|
|
@ -661,17 +610,17 @@ impl LpWindow {
|
|||
.priority(adw::ToastPriority::High)
|
||||
.build();
|
||||
toast.connect_button_clicked(glib::clone!(
|
||||
#[weak(rename_to = win)]
|
||||
#[weak(rename_to = obj)]
|
||||
self,
|
||||
move |_| {
|
||||
let path = path.clone();
|
||||
glib::spawn_future_local(async move {
|
||||
win.image_view()
|
||||
obj.image_view()
|
||||
.set_trash_restore(Some(gio::File::for_path(&path)));
|
||||
let result = crate::util::untrash(&path).await;
|
||||
if let Err(err) = result {
|
||||
log::error!("Failed to untrash {path:?}: {err}");
|
||||
win.show_toast(
|
||||
obj.window().show_toast(
|
||||
gettext("Failed to restore image from trash"),
|
||||
adw::ToastPriority::High,
|
||||
);
|
||||
|
|
@ -679,14 +628,15 @@ impl LpWindow {
|
|||
});
|
||||
}
|
||||
));
|
||||
self.imp().toast_overlay.add_toast(toast);
|
||||
todo!();
|
||||
//self.imp().toast_overlay.add_toast(toast);
|
||||
}
|
||||
Err(err) => {
|
||||
if Some(gio::IOErrorEnum::NotSupported) == err.kind::<gio::IOErrorEnum>() {
|
||||
self.delete_future(&path).await;
|
||||
} else {
|
||||
log::error!("Failed to delete file {path:?}: {err}");
|
||||
self.show_toast(
|
||||
self.window().show_toast(
|
||||
gettext("Failed to move image to trash"),
|
||||
adw::ToastPriority::Normal,
|
||||
);
|
||||
|
|
@ -732,7 +682,7 @@ impl LpWindow {
|
|||
|
||||
if let Err(err) = result {
|
||||
log::error!("Failed to delete file {path:?}: {err}");
|
||||
self.show_toast(
|
||||
self.window().show_toast(
|
||||
gettext("Failed to delete image"),
|
||||
adw::ToastPriority::Normal,
|
||||
);
|
||||
|
|
@ -748,15 +698,6 @@ impl LpWindow {
|
|||
self.imp().headerbar.clone()
|
||||
}
|
||||
|
||||
fn show_toast(&self, text: impl AsRef<str>, 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);
|
||||
}
|
||||
|
||||
/// Set status for actions that are related to the active image
|
||||
fn update_image_actions_status(&self) {
|
||||
// Actions that are enabled if the image is shown
|
||||
|
|
@ -866,7 +807,7 @@ impl LpWindow {
|
|||
imp.stack.set_visible_child(&*imp.status_page);
|
||||
imp.status_page.grab_focus();
|
||||
// Leave fullscreen since status page has no controls to leave it
|
||||
self.set_fullscreened(false);
|
||||
self.window().set_fullscreened(false);
|
||||
}
|
||||
|
||||
imp.properties_button.set_sensitive(has_image);
|
||||
|
|
@ -896,7 +837,7 @@ impl LpWindow {
|
|||
.and_then(|x| x.metadata().file_name())
|
||||
.unwrap_or_else(|| gettext("Image Viewer"));
|
||||
|
||||
self.set_title(Some(&title));
|
||||
self.window().set_title(Some(&title));
|
||||
}
|
||||
|
||||
pub fn image_size_available(&self) {
|
||||
|
|
@ -913,41 +854,7 @@ impl LpWindow {
|
|||
|
||||
if image.is_some_and(|img| img.image_size_available()) {
|
||||
log::debug!("Showing window because image size is ready");
|
||||
self.present();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resize_default(&self) {
|
||||
let imp = self.imp();
|
||||
|
||||
if self
|
||||
.image_view()
|
||||
.current_image()
|
||||
.is_some_and(|img| img.image_size_available())
|
||||
{
|
||||
let shows_properties = imp.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);
|
||||
self.window().present();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -962,19 +869,19 @@ impl LpWindow {
|
|||
|
||||
if current_page.is_some_and(|page| page.image().error().is_some()) {
|
||||
log::debug!("Showing window because loading image failed");
|
||||
self.present();
|
||||
self.window().present();
|
||||
}
|
||||
}
|
||||
|
||||
fn on_fullscreen_changed(&self) {
|
||||
let icon = if self.is_fullscreen() {
|
||||
let icon = if self.window().is_fullscreen() {
|
||||
"view-restore-symbolic"
|
||||
} else {
|
||||
"view-fullscreen-symbolic"
|
||||
};
|
||||
self.imp().fullscreen_button.set_icon_name(icon);
|
||||
|
||||
if !self.is_fullscreen() {
|
||||
if !self.window().is_fullscreen() {
|
||||
self.set_cursor(None);
|
||||
self.show_controls();
|
||||
}
|
||||
|
|
@ -990,14 +897,14 @@ impl LpWindow {
|
|||
}
|
||||
|
||||
fn update_accel_status(&self) {
|
||||
let Some(application) = self.application() else {
|
||||
let Some(application) = self.window().application() else {
|
||||
log::error!("No application for window found");
|
||||
return;
|
||||
};
|
||||
|
||||
// Only change status if active window
|
||||
if self.is_active() {
|
||||
if self.visible_dialog().is_some() {
|
||||
if self.window().is_active() {
|
||||
if self.window().visible_dialog().is_some() {
|
||||
// If AdwDialog is visible, remove global accels that are for the main window
|
||||
ActionPartGlobal::remove_accels(&application);
|
||||
} else {
|
||||
|
|
@ -1007,8 +914,7 @@ impl LpWindow {
|
|||
}
|
||||
}
|
||||
|
||||
async fn show_about(&self) {
|
||||
let about = crate::about::dialog().await;
|
||||
about.present(Some(self));
|
||||
pub fn properties_button(&self) -> gtk::ToggleButton {
|
||||
self.imp().properties_button.clone()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,62 +56,88 @@
|
|||
</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>
|
||||
<template class="LpWindowImage" parent="AdwBin">
|
||||
<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="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="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">headerbar</property>
|
||||
<property name="id">content</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>
|
||||
<property name="sidebar">
|
||||
<object class="AdwLayoutSlot">
|
||||
<property name="id">properties</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="AdwLayout">
|
||||
<property name="name">bottom-properties</property>
|
||||
</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" />
|
||||
|
|
@ -121,187 +147,149 @@
|
|||
</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 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="window" bind-property="fullscreened" bind-flags="sync-create|invert-boolean" />
|
||||
<property name="show-start-title-buttons" bind-source="window" 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="action-name">win.toggle-fullscreen</property>
|
||||
<property name="tooltip-text" translatable="yes">Toggle Fullscreen</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="start">
|
||||
<object class="GtkButton" id="edit_button">
|
||||
<property name="action-name">win.edit</property>
|
||||
<property name="icon-name">document-edit-symbolic</property>
|
||||
<property name="tooltip-text" translatable="yes">Edit Image</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="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>
|
||||
<object class="GtkEventControllerMotion" id="headerbar_events" />
|
||||
</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" />
|
||||
</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="fullscreen-osd" />
|
||||
<class name="drop-widget" />
|
||||
</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>
|
||||
<child>
|
||||
<object class="GtkDropTarget" id="drop_target">
|
||||
<property name="actions">copy</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>
|
||||
<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="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 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>
|
||||
</property>
|
||||
</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>
|
||||
</child>
|
||||
<child type="properties">
|
||||
<object class="LpPropertiesView" id="properties_view" />
|
||||
</child>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</child>
|
||||
<child type="properties">
|
||||
<object class="LpPropertiesView" id="properties_view" />
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -78,7 +78,9 @@ impl ActionPartGlobal {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn init_actions_and_bindings(klass: &mut <super::imp::LpWindow as ObjectSubclass>::Class) {
|
||||
pub fn init_actions_and_bindings(
|
||||
klass: &mut <super::imp::LpWindowImage as ObjectSubclass>::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 <super::imp::LpWindow as ObjectSubclass>::Class) {
|
||||
pub fn init_actions_and_bindings(
|
||||
klass: &mut <super::imp::LpWindowImage as ObjectSubclass>::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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
199
src/window.rs
Normal file
199
src/window.rs
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
// 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<adw::ToastOverlay>,
|
||||
|
||||
#[template_child]
|
||||
pub(super) window_image: TemplateChild<LpWindowImage>,
|
||||
}
|
||||
|
||||
#[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<Self>) {
|
||||
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<imp::LpWindow>)
|
||||
@extends gtk::Widget, gtk::Window, gtk::ApplicationWindow, adw::ApplicationWindow,
|
||||
@implements gio::ActionMap, gio::ActionGroup, gtk::Native;
|
||||
}
|
||||
|
||||
impl LpWindow {
|
||||
pub fn new<A: IsA<gtk::Application>>(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<str>, 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()
|
||||
}
|
||||
}
|
||||
32
src/window.ui
Normal file
32
src/window.ui
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<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="LpWindowImage" id="window_image">
|
||||
<property name="window">LpWindow</property>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</property>
|
||||
</template>
|
||||
</interface>
|
||||
Loading…
Add table
Add a link
Reference in a new issue