2024-01-08 21:24:43 +01:00
|
|
|
// Copyright (c) 2023-2024 Sophie Herold
|
2023-11-21 20:55:03 +01:00
|
|
|
// Copyright (c) 2023 Alice Mikhaylenko
|
|
|
|
|
// Copyright (c) 2023 Christopher Davis
|
2023-04-19 20:02:01 +02:00
|
|
|
// Copyright (c) 2023 Lubosz Sarnecki
|
2024-07-17 19:06:34 +02:00
|
|
|
// Copyright (c) 2024 Fina Wilke
|
2023-04-19 20:02:01 +02:00
|
|
|
//
|
|
|
|
|
// 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
|
|
|
|
|
|
2023-03-06 22:59:40 +01:00
|
|
|
//! A sliding view widget for images
|
2023-01-23 01:10:15 +01:00
|
|
|
//!
|
|
|
|
|
//! This widget it similar to `AdwCarousel`.
|
|
|
|
|
|
2023-12-06 23:45:02 +01:00
|
|
|
use std::cell::{Cell, OnceCell, RefCell};
|
2023-01-23 01:10:15 +01:00
|
|
|
|
|
|
|
|
use adw::prelude::*;
|
|
|
|
|
use adw::subclass::prelude::*;
|
2023-11-29 19:28:27 +01:00
|
|
|
use glib::Properties;
|
2023-01-23 01:10:15 +01:00
|
|
|
use indexmap::IndexMap;
|
2024-01-08 21:24:43 +01:00
|
|
|
use log::error;
|
2023-06-23 11:07:44 -04:00
|
|
|
use once_cell::sync::Lazy;
|
2023-01-23 01:10:15 +01:00
|
|
|
|
2023-12-06 23:45:02 +01:00
|
|
|
use crate::deps::*;
|
|
|
|
|
use crate::widgets::LpImagePage;
|
2023-01-23 01:10:15 +01:00
|
|
|
|
2024-01-08 21:24:43 +01:00
|
|
|
const SCROLL_DAMPING_RATIO: f64 = 1.;
|
|
|
|
|
const SCROLL_MASS: f64 = 0.5;
|
|
|
|
|
const SCROLL_STIFFNESS: f64 = 500.;
|
|
|
|
|
|
|
|
|
|
/// Duration for cards animation
|
|
|
|
|
const STEP_DURATION: u32 = 250;
|
|
|
|
|
/// Offset of card at the beginning in app pixels
|
|
|
|
|
const STEP_PIXEL_SHIFT: f32 = 100.;
|
|
|
|
|
/// Progress point at which the move-out animation starts.
|
|
|
|
|
/// The progress is a value between 0.0 and 1.0
|
|
|
|
|
const STEP_MOVE_OUT_DELAY: f32 = 0.5;
|
2023-01-23 01:10:15 +01:00
|
|
|
|
2023-02-05 23:56:42 +01:00
|
|
|
/// Space between images in application pixels
|
|
|
|
|
///
|
|
|
|
|
/// This is combined with the percent component
|
2024-01-08 21:24:43 +01:00
|
|
|
const PAGE_SPACING_FIXED: f32 = 25.;
|
2023-02-05 23:56:42 +01:00
|
|
|
|
|
|
|
|
/// Space between images as factor of width
|
|
|
|
|
const PAGE_SPACING_PERCENT: f32 = 0.02;
|
|
|
|
|
|
2024-01-08 21:24:43 +01:00
|
|
|
#[derive(Debug)]
|
|
|
|
|
enum PositionTracking {
|
2024-01-28 15:41:00 +01:00
|
|
|
/// Animatable position, 0.0 first image, 1.0 second image etc
|
2024-01-08 21:24:43 +01:00
|
|
|
Position(f64),
|
|
|
|
|
StackedCards(StackedCards),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for PositionTracking {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
PositionTracking::Position(0.)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PositionTracking {
|
|
|
|
|
fn set_position(&mut self, position: f64) {
|
|
|
|
|
*self = Self::Position(position);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_progress(&mut self, progress: f64) {
|
|
|
|
|
match self {
|
|
|
|
|
Self::Position(_) => {
|
|
|
|
|
log::error!("Trying to set StackedCards progress while not in animation state.")
|
|
|
|
|
}
|
|
|
|
|
Self::StackedCards(cards) => cards.progress = progress,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
struct StackedCards {
|
|
|
|
|
prev_image: LpImagePage,
|
|
|
|
|
progress: f64,
|
2024-01-28 15:41:00 +01:00
|
|
|
backward: bool,
|
2024-01-08 21:24:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl StackedCards {
|
2024-01-28 15:41:00 +01:00
|
|
|
fn new(prev_image: LpImagePage, backward: bool) -> Self {
|
2024-01-08 21:24:43 +01:00
|
|
|
Self {
|
|
|
|
|
prev_image,
|
|
|
|
|
progress: 0.,
|
2024-01-28 15:41:00 +01:00
|
|
|
backward,
|
2024-01-08 21:24:43 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
mod imp {
|
|
|
|
|
use glib::subclass::Signal;
|
|
|
|
|
|
2023-12-06 23:45:02 +01:00
|
|
|
use super::*;
|
|
|
|
|
|
2023-11-29 19:28:27 +01:00
|
|
|
#[derive(Default, Properties)]
|
|
|
|
|
#[properties(wrapper_type = super::LpSlidingView)]
|
2023-01-23 01:10:15 +01:00
|
|
|
pub struct LpSlidingView {
|
|
|
|
|
/// Pages that can be slided through
|
|
|
|
|
pub(super) pages: RefCell<Vec<LpImagePage>>,
|
|
|
|
|
/// Page that is currently shown or the animation is animating towards
|
2023-11-29 19:28:27 +01:00
|
|
|
#[property(get)]
|
2023-01-23 01:10:15 +01:00
|
|
|
pub(super) current_page: RefCell<Option<LpImagePage>>,
|
|
|
|
|
/// Animatable position, 0.0 first image, 1.0 second image etc
|
2024-01-08 21:24:43 +01:00
|
|
|
pub(super) position_tracking: RefCell<PositionTracking>,
|
2023-01-23 01:10:15 +01:00
|
|
|
/// Move position to not break animations when pages are removed/added
|
|
|
|
|
pub(super) position_shift: Cell<f64>,
|
|
|
|
|
/// The animation used to animate image changes
|
2023-01-23 18:18:26 +01:00
|
|
|
pub(super) scroll_animation: OnceCell<adw::SpringAnimation>,
|
2024-01-08 21:24:43 +01:00
|
|
|
pub(super) step_animation: OnceCell<adw::TimedAnimation>,
|
2023-01-24 20:49:15 +01:00
|
|
|
/// Implements swiping
|
2023-01-23 18:18:26 +01:00
|
|
|
pub(super) swipe_tracker: OnceCell<adw::SwipeTracker>,
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[glib::object_subclass]
|
|
|
|
|
impl ObjectSubclass for LpSlidingView {
|
|
|
|
|
const NAME: &'static str = "LpSlidingView";
|
|
|
|
|
type Type = super::LpSlidingView;
|
|
|
|
|
type ParentType = gtk::Widget;
|
2023-01-23 18:18:26 +01:00
|
|
|
type Interfaces = (adw::Swipeable,);
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ObjectImpl for LpSlidingView {
|
|
|
|
|
fn properties() -> &'static [glib::ParamSpec] {
|
2023-11-29 19:28:27 +01:00
|
|
|
Self::derived_properties()
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
2023-11-29 19:28:27 +01:00
|
|
|
fn property(&self, id: usize, pspec: &glib::ParamSpec) -> glib::Value {
|
|
|
|
|
self.derived_property(id, pspec)
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn signals() -> &'static [Signal] {
|
|
|
|
|
static SIGNALS: Lazy<Vec<Signal>> =
|
|
|
|
|
Lazy::new(|| vec![Signal::builder("target-page-reached").build()]);
|
|
|
|
|
SIGNALS.as_ref()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn constructed(&self) {
|
2023-02-12 02:07:42 +01:00
|
|
|
let obj = self.obj();
|
2023-01-23 18:18:26 +01:00
|
|
|
self.parent_constructed();
|
|
|
|
|
|
2023-02-12 02:07:42 +01:00
|
|
|
self.obj().set_overflow(gtk::Overflow::Hidden);
|
2023-01-23 18:18:26 +01:00
|
|
|
|
|
|
|
|
let swipe_tracker = adw::SwipeTracker::builder()
|
2023-02-12 02:07:42 +01:00
|
|
|
.swipeable(&*self.obj())
|
2023-01-24 20:49:15 +01:00
|
|
|
.reversed(self.is_rtl())
|
2023-07-26 17:03:30 +04:00
|
|
|
.lower_overshoot(true)
|
|
|
|
|
.upper_overshoot(true)
|
2023-01-23 18:18:26 +01:00
|
|
|
.build();
|
|
|
|
|
|
2024-06-18 00:37:38 +02:00
|
|
|
swipe_tracker.connect_begin_swipe(glib::clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |_| {
|
|
|
|
|
obj.scroll_animation().pause();
|
|
|
|
|
obj.step_animation().pause();
|
2023-01-23 18:18:26 +01:00
|
|
|
}
|
2024-06-18 00:37:38 +02:00
|
|
|
));
|
|
|
|
|
|
|
|
|
|
swipe_tracker.connect_update_swipe(glib::clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |_, position| {
|
|
|
|
|
obj.set_position(position);
|
|
|
|
|
}
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
swipe_tracker.connect_end_swipe(glib::clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
|
|
|
|
move |_, velocity, to| {
|
|
|
|
|
if let Some(page) = obj.page_at(to) {
|
|
|
|
|
obj.scroll_to(&page, velocity);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
));
|
2023-01-23 18:18:26 +01:00
|
|
|
|
|
|
|
|
self.swipe_tracker.set(swipe_tracker).unwrap();
|
|
|
|
|
|
2023-02-20 23:44:17 +01:00
|
|
|
// Avoid propagating scroll events to AdwFlap if at beginning or end
|
2023-01-23 18:18:26 +01:00
|
|
|
let scroll_controller =
|
|
|
|
|
gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::HORIZONTAL);
|
|
|
|
|
|
2024-06-18 00:37:38 +02:00
|
|
|
scroll_controller.connect_scroll(glib::clone!(
|
|
|
|
|
#[weak]
|
|
|
|
|
obj,
|
2024-06-23 22:28:16 +02:00
|
|
|
#[upgrade_or]
|
|
|
|
|
glib::Propagation::Proceed,
|
2024-06-18 00:37:38 +02:00
|
|
|
move |_, x, _| {
|
2023-01-24 20:49:15 +01:00
|
|
|
let direction_sign = if obj.imp().is_rtl() { -1. } else { 1. };
|
|
|
|
|
|
|
|
|
|
if x * direction_sign > 0. {
|
|
|
|
|
// check end
|
2023-01-23 18:18:26 +01:00
|
|
|
if let Some(max) = obj.imp().snap_points().last() {
|
2023-08-02 14:27:23 +02:00
|
|
|
if obj.position() >= *max {
|
|
|
|
|
glib::Propagation::Stop
|
|
|
|
|
} else {
|
|
|
|
|
glib::Propagation::Proceed
|
|
|
|
|
}
|
2023-01-23 18:18:26 +01:00
|
|
|
} else {
|
2023-08-02 14:27:23 +02:00
|
|
|
glib::Propagation::Stop
|
2023-01-23 18:18:26 +01:00
|
|
|
}
|
|
|
|
|
} else {
|
2023-01-24 20:49:15 +01:00
|
|
|
//check beginning
|
2023-01-23 18:18:26 +01:00
|
|
|
if let Some(min) = obj.imp().snap_points().first() {
|
2023-08-02 14:27:23 +02:00
|
|
|
if obj.position() <= *min {
|
|
|
|
|
glib::Propagation::Stop
|
|
|
|
|
} else {
|
|
|
|
|
glib::Propagation::Proceed
|
|
|
|
|
}
|
2023-01-23 18:18:26 +01:00
|
|
|
} else {
|
2023-08-02 14:27:23 +02:00
|
|
|
glib::Propagation::Stop
|
2023-01-23 18:18:26 +01:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-18 00:37:38 +02:00
|
|
|
}
|
|
|
|
|
));
|
2023-01-23 18:18:26 +01:00
|
|
|
|
2023-02-12 02:07:42 +01:00
|
|
|
obj.add_controller(scroll_controller);
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
2023-03-02 18:06:58 -05:00
|
|
|
|
|
|
|
|
fn dispose(&self) {
|
|
|
|
|
let obj = self.obj();
|
|
|
|
|
while let Some(child) = obj.first_child() {
|
|
|
|
|
child.unparent();
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl WidgetImpl for LpSlidingView {
|
|
|
|
|
fn size_allocate(&self, width: i32, height: i32, _baseline: i32) {
|
2024-01-28 15:41:00 +01:00
|
|
|
let obj = self.obj();
|
|
|
|
|
|
|
|
|
|
// Unparent deleted children if not in animation that might contain them
|
|
|
|
|
if matches!(
|
|
|
|
|
*self.position_tracking.borrow(),
|
|
|
|
|
PositionTracking::Position(_)
|
|
|
|
|
) {
|
|
|
|
|
if let Some(mut child) = obj.first_child() {
|
|
|
|
|
let mut children = vec![child.clone()];
|
|
|
|
|
while let Some(new_child) = child.next_sibling() {
|
|
|
|
|
child = new_child;
|
|
|
|
|
children.push(child.clone());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for child in children {
|
|
|
|
|
if let Ok(page) = child.downcast::<LpImagePage>() {
|
|
|
|
|
if obj.index_of(&page).is_none() {
|
|
|
|
|
page.unparent();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
for (page_index, page) in self.pages.borrow().iter().enumerate() {
|
2024-01-08 21:24:43 +01:00
|
|
|
match *self.position_tracking.borrow() {
|
|
|
|
|
PositionTracking::Position(_) => {
|
2024-01-28 21:54:51 +01:00
|
|
|
self.size_allocate_position(page_index, page, width, height);
|
2024-01-08 21:24:43 +01:00
|
|
|
}
|
2024-01-28 15:41:00 +01:00
|
|
|
PositionTracking::StackedCards(_) => {
|
|
|
|
|
// Hide all and show to relevant widgets later
|
|
|
|
|
page.set_child_visible(false);
|
2024-01-08 21:24:43 +01:00
|
|
|
}
|
2023-03-15 22:35:59 +01:00
|
|
|
}
|
|
|
|
|
|
2023-03-21 21:50:54 +01:00
|
|
|
if !page.scrolled_window().is_mapped() {
|
|
|
|
|
// SVG needs to know a widget size before rendering something in LpImage
|
|
|
|
|
page.scrolled_window().allocate(width, height, 0, None);
|
|
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
2024-01-28 15:41:00 +01:00
|
|
|
|
|
|
|
|
if let PositionTracking::StackedCards(stacked_cards) = &*self.position_tracking.borrow()
|
|
|
|
|
{
|
|
|
|
|
self.size_allocate_stacked_cards(stacked_cards, width, height);
|
|
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn measure(&self, orientation: gtk::Orientation, for_size: i32) -> (i32, i32, i32, i32) {
|
2023-02-12 02:07:42 +01:00
|
|
|
let obj = self.obj();
|
2023-01-23 01:10:15 +01:00
|
|
|
|
|
|
|
|
for page in self.pages.borrow().iter() {
|
|
|
|
|
if Some(page) != obj.current_page().as_ref() {
|
|
|
|
|
page.measure(orientation, for_size);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if let Some(current_page) = obj.current_page() {
|
|
|
|
|
current_page.measure(orientation, for_size)
|
|
|
|
|
} else {
|
|
|
|
|
(0, 0, -1, -1)
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-24 20:49:15 +01:00
|
|
|
|
|
|
|
|
fn direction_changed(&self, _previous_direction: gtk::TextDirection) {
|
|
|
|
|
self.swipe_tracker
|
|
|
|
|
.get()
|
|
|
|
|
.unwrap()
|
|
|
|
|
.set_reversed(self.is_rtl());
|
|
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
2023-01-23 18:18:26 +01:00
|
|
|
|
|
|
|
|
impl SwipeableImpl for LpSlidingView {
|
|
|
|
|
fn progress(&self) -> f64 {
|
2023-02-12 02:07:42 +01:00
|
|
|
self.obj().position()
|
2023-01-23 18:18:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn distance(&self) -> f64 {
|
2023-02-12 02:07:42 +01:00
|
|
|
let obj = self.obj();
|
2024-01-28 14:13:52 +01:00
|
|
|
let width = obj.width();
|
2023-01-23 18:18:26 +01:00
|
|
|
|
2023-02-08 22:43:43 +01:00
|
|
|
width as f64 + self.page_spacing(width) as f64
|
2023-01-23 18:18:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn snap_points(&self) -> Vec<f64> {
|
2023-02-12 02:07:42 +01:00
|
|
|
let obj = self.obj();
|
2023-01-23 18:18:26 +01:00
|
|
|
|
|
|
|
|
(0..obj.n_pages())
|
|
|
|
|
.map(|i| i as f64 - obj.position_shift())
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn cancel_progress(&self) -> f64 {
|
2023-02-12 02:07:42 +01:00
|
|
|
let obj = self.obj();
|
2023-01-23 18:18:26 +01:00
|
|
|
let snap_points = self.snap_points();
|
|
|
|
|
|
|
|
|
|
if let (Some(min), Some(max)) = (snap_points.first(), snap_points.last()) {
|
2023-01-24 20:49:15 +01:00
|
|
|
obj.position().round().clamp(*min, *max)
|
2023-01-23 18:18:26 +01:00
|
|
|
} else {
|
|
|
|
|
0.
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-24 20:49:15 +01:00
|
|
|
|
|
|
|
|
impl LpSlidingView {
|
2024-01-08 21:24:43 +01:00
|
|
|
/// Usualy allocate or during scroll animation
|
|
|
|
|
fn size_allocate_position(
|
|
|
|
|
&self,
|
|
|
|
|
page_index: usize,
|
|
|
|
|
page: &LpImagePage,
|
|
|
|
|
width: i32,
|
|
|
|
|
height: i32,
|
|
|
|
|
) {
|
|
|
|
|
let scroll_position = self.obj().position() as f32;
|
|
|
|
|
let position_shift = self.position_shift.get() as f32;
|
|
|
|
|
let page_position = page_index as f32;
|
|
|
|
|
|
2024-09-04 20:33:06 +02:00
|
|
|
// Reset from potential stacked cards animation
|
|
|
|
|
page.set_opacity(1.);
|
|
|
|
|
|
2024-01-08 21:24:43 +01:00
|
|
|
// reverse page order for RTL languages
|
|
|
|
|
let direction_sign = if self.is_rtl() { -1. } else { 1. };
|
|
|
|
|
|
|
|
|
|
// This positions the pages within the carousel and shifts them
|
|
|
|
|
// according to the position that should currently be shown.
|
|
|
|
|
let x = direction_sign
|
|
|
|
|
* (page_position - scroll_position - position_shift)
|
|
|
|
|
* (width as f32 + self.page_spacing(width));
|
|
|
|
|
|
|
|
|
|
// Only show visible images
|
|
|
|
|
if page_position == (scroll_position + position_shift).floor()
|
|
|
|
|
|| page_position == (scroll_position + position_shift).ceil()
|
|
|
|
|
{
|
|
|
|
|
page.set_child_visible(true);
|
|
|
|
|
let transform = gsk::Transform::new().translate(&graphene::Point::new(x, 0.));
|
|
|
|
|
page.allocate(width, height, 0, Some(transform));
|
|
|
|
|
} else {
|
|
|
|
|
page.set_child_visible(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Allocate during stacked card animation
|
|
|
|
|
fn size_allocate_stacked_cards(
|
|
|
|
|
&self,
|
|
|
|
|
stacked_cards: &StackedCards,
|
|
|
|
|
width: i32,
|
|
|
|
|
height: i32,
|
|
|
|
|
) {
|
|
|
|
|
let obj = self.obj().to_owned();
|
|
|
|
|
if let Some(new_page) = obj.current_page() {
|
2024-01-28 15:41:00 +01:00
|
|
|
let prev_page = &stacked_cards.prev_image;
|
2024-01-08 21:24:43 +01:00
|
|
|
|
2024-01-28 15:41:00 +01:00
|
|
|
let mut direction_sign = if stacked_cards.backward { -1. } else { 1. };
|
2024-01-08 21:24:43 +01:00
|
|
|
if self.is_rtl() {
|
|
|
|
|
direction_sign *= -1.;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let progress = stacked_cards.progress;
|
|
|
|
|
|
2024-01-28 15:41:00 +01:00
|
|
|
//New Page
|
|
|
|
|
|
|
|
|
|
// Ensure that new image is on top
|
|
|
|
|
new_page.unparent();
|
|
|
|
|
new_page.insert_after(&obj, Some(prev_page));
|
|
|
|
|
|
2024-01-28 21:54:51 +01:00
|
|
|
// Don't show spinner or error during animation
|
|
|
|
|
if new_page.image().is_loaded() {
|
|
|
|
|
new_page.set_child_visible(true);
|
|
|
|
|
} else {
|
|
|
|
|
// Set explicity since re-parenting resets the state
|
|
|
|
|
new_page.set_child_visible(false);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-28 15:41:00 +01:00
|
|
|
let x = direction_sign * STEP_PIXEL_SHIFT * (1. - progress as f32);
|
|
|
|
|
let transform = gsk::Transform::new().translate(&graphene::Point::new(x, 0.));
|
|
|
|
|
new_page.allocate(width, height, 0, Some(transform));
|
|
|
|
|
new_page.set_opacity(progress);
|
|
|
|
|
|
|
|
|
|
// Prev Page
|
2024-01-28 21:54:51 +01:00
|
|
|
if prev_page.image().is_loaded() {
|
|
|
|
|
prev_page.set_child_visible(true);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-28 15:41:00 +01:00
|
|
|
let x = -direction_sign
|
|
|
|
|
* f32::max(
|
|
|
|
|
0.,
|
|
|
|
|
(progress as f32 - STEP_MOVE_OUT_DELAY) * STEP_PIXEL_SHIFT,
|
|
|
|
|
);
|
|
|
|
|
let transform = gsk::Transform::new().translate(&graphene::Point::new(x, 0.));
|
|
|
|
|
prev_page.allocate(width, height, 0, Some(transform));
|
2024-01-08 21:24:43 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-02-08 22:43:43 +01:00
|
|
|
fn page_spacing(&self, width: i32) -> f32 {
|
|
|
|
|
width as f32 * PAGE_SPACING_PERCENT + PAGE_SPACING_FIXED
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-24 20:49:15 +01:00
|
|
|
fn is_rtl(&self) -> bool {
|
2023-02-12 02:07:42 +01:00
|
|
|
let obj = self.obj();
|
2023-01-24 20:49:15 +01:00
|
|
|
obj.direction() == gtk::TextDirection::Rtl
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
glib::wrapper! {
|
|
|
|
|
pub struct LpSlidingView(ObjectSubclass<imp::LpSlidingView>)
|
|
|
|
|
@extends gtk::Widget,
|
2023-01-23 18:18:26 +01:00
|
|
|
@implements gtk::Buildable, adw::Swipeable;
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl LpSlidingView {
|
2023-12-21 21:28:37 +01:00
|
|
|
/// Returns a struct that allows lazy updates
|
|
|
|
|
///
|
|
|
|
|
/// Lazy updates don't apply their notifications directly but only when the
|
|
|
|
|
/// editor struct is dropped.
|
|
|
|
|
pub fn editor(&self) -> SlidingViewEditor {
|
|
|
|
|
SlidingViewEditor::new(self.clone())
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
/// Add page to the end
|
|
|
|
|
pub fn append(&self, page: &LpImagePage) {
|
|
|
|
|
self.insert(page, self.n_pages());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Add page to the front
|
|
|
|
|
pub fn prepend(&self, page: &LpImagePage) {
|
|
|
|
|
self.insert(page, 0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Add page to the specified position
|
|
|
|
|
pub fn insert(&self, page: &LpImagePage, pos: usize) {
|
2023-04-12 01:11:59 +02:00
|
|
|
if pos > self.n_pages() {
|
|
|
|
|
log::error!("Invalid insert position {pos} for {}", page.file().uri());
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
let current_index = self.current_index();
|
|
|
|
|
|
|
|
|
|
let previous_sibling = self.imp().pages.borrow().get(pos).cloned();
|
|
|
|
|
self.imp().pages.borrow_mut().insert(pos, page.clone());
|
|
|
|
|
page.insert_after(self, previous_sibling.as_ref());
|
|
|
|
|
|
|
|
|
|
self.shift_position(current_index);
|
|
|
|
|
|
|
|
|
|
if self.n_pages() == 1 {
|
|
|
|
|
self.set_current_page(Some(page));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Reorders the pages by moving the specified page to the new index
|
|
|
|
|
pub fn move_to(&self, page: &LpImagePage, new_index: usize) {
|
|
|
|
|
if let Some(index) = self.index_of(page) {
|
|
|
|
|
let current_index = self.current_index();
|
|
|
|
|
{
|
|
|
|
|
let mut vec = self.imp().pages.borrow_mut();
|
|
|
|
|
let page = vec.remove(index);
|
|
|
|
|
vec.insert(new_index, page);
|
|
|
|
|
}
|
|
|
|
|
self.shift_position(current_index);
|
|
|
|
|
} else {
|
2023-04-12 01:11:59 +02:00
|
|
|
log::error!("Page to move not found: {}", page.file().uri());
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Removes the page
|
2023-12-21 21:28:37 +01:00
|
|
|
fn remove(&self, page: &LpImagePage, lazy: bool) {
|
2023-01-23 01:10:15 +01:00
|
|
|
let current_index = self.current_index();
|
|
|
|
|
|
|
|
|
|
if let Some(index) = self.index_of(page) {
|
2024-01-28 21:54:51 +01:00
|
|
|
// Only unparent if far enough away to not be involved in delete animation
|
|
|
|
|
if current_index.map_or(false, |x| x.abs_diff(index) as isize > 1) {
|
|
|
|
|
page.unparent();
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
self.imp().pages.borrow_mut().remove(index);
|
|
|
|
|
|
|
|
|
|
self.queue_allocate();
|
|
|
|
|
|
|
|
|
|
self.shift_position(current_index);
|
|
|
|
|
|
2023-12-21 21:28:37 +01:00
|
|
|
if self.is_empty() && !lazy {
|
|
|
|
|
self.clear(false);
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
log::error!("Trying to remove non-existent page.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Signal that fires when the animation for scrolling to new page ends
|
|
|
|
|
pub fn connect_target_page_reached(&self, f: impl Fn() + 'static) {
|
|
|
|
|
self.connect_local("target-page-reached", false, move |_| {
|
|
|
|
|
f();
|
|
|
|
|
None
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-05 17:25:44 +01:00
|
|
|
fn emit_target_page_reached(&self) {
|
|
|
|
|
self.emit_by_name::<()>("target-page-reached", &[]);
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
/// Removes all pages
|
2023-12-21 21:28:37 +01:00
|
|
|
fn clear(&self, lazy: bool) {
|
2023-01-23 01:10:15 +01:00
|
|
|
self.scroll_animation().pause();
|
2024-01-08 21:24:43 +01:00
|
|
|
self.step_animation().pause();
|
2023-01-23 01:10:15 +01:00
|
|
|
|
|
|
|
|
for page in self.imp().pages.borrow().iter() {
|
|
|
|
|
page.unparent();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.imp().pages.borrow_mut().clear();
|
|
|
|
|
self.set_position(0.);
|
|
|
|
|
self.imp().position_shift.set(0.);
|
|
|
|
|
|
2023-12-21 21:28:37 +01:00
|
|
|
if !lazy {
|
|
|
|
|
self.set_current_page(None);
|
|
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Gives all pages with hash access via their path
|
2023-04-12 01:11:59 +02:00
|
|
|
pub fn pages(&self) -> IndexMap<glib::GString, LpImagePage> {
|
2023-01-23 01:10:15 +01:00
|
|
|
self.imp()
|
|
|
|
|
.pages
|
|
|
|
|
.borrow()
|
|
|
|
|
.iter()
|
|
|
|
|
.cloned()
|
2023-04-12 01:11:59 +02:00
|
|
|
.map(|x| (x.file().uri(), x))
|
2023-01-23 01:10:15 +01:00
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-12 01:11:59 +02:00
|
|
|
pub fn get(&self, file: &gio::File) -> Option<LpImagePage> {
|
|
|
|
|
let uri = file.uri();
|
2023-01-23 01:10:15 +01:00
|
|
|
self.imp()
|
|
|
|
|
.pages
|
|
|
|
|
.borrow()
|
|
|
|
|
.iter()
|
2023-04-12 01:11:59 +02:00
|
|
|
.find(|x| x.file().uri() == uri)
|
2023-01-23 01:10:15 +01:00
|
|
|
.cloned()
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-31 23:34:25 +01:00
|
|
|
/// Move to specified page without animation
|
|
|
|
|
pub fn instant_to(&self, page: &LpImagePage) {
|
|
|
|
|
if let Some(index) = self.index_of(page) {
|
2024-02-05 17:25:44 +01:00
|
|
|
self.step_animation().pause();
|
|
|
|
|
self.scroll_animation().pause();
|
|
|
|
|
|
2024-01-31 23:34:25 +01:00
|
|
|
self.set_position(index as f64 - self.position_shift());
|
2024-02-05 17:25:44 +01:00
|
|
|
|
|
|
|
|
self.set_current_page(Some(page));
|
|
|
|
|
self.emit_target_page_reached();
|
2024-01-31 23:34:25 +01:00
|
|
|
} else {
|
|
|
|
|
log::error!("Page not in LpSlidingView {}", page.file().uri());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
/// Move to specified page with animation
|
2024-01-08 21:24:43 +01:00
|
|
|
pub fn animate_to(&self, page: &LpImagePage) {
|
|
|
|
|
if let Some(index) = self.index_of(page) {
|
|
|
|
|
if self
|
|
|
|
|
.current_index()
|
|
|
|
|
.map_or(false, |x| (x as i64 - index as i64).abs() == 1)
|
|
|
|
|
{
|
|
|
|
|
self.step_to(page);
|
|
|
|
|
} else {
|
|
|
|
|
self.scroll_to(page, 0.);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
log::error!("Page not in LpSlidingView {}", page.file().uri());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Move to image with cards animation
|
|
|
|
|
fn step_to(&self, page: &LpImagePage) {
|
|
|
|
|
let animation = self.step_animation();
|
2024-01-28 15:41:00 +01:00
|
|
|
if let Some(prev_page) = self.current_page() {
|
|
|
|
|
if let (Some(prev_index), Some(new_index)) =
|
|
|
|
|
(self.index_of(&prev_page), self.index_of(page))
|
|
|
|
|
{
|
|
|
|
|
let backward = new_index < prev_index;
|
|
|
|
|
|
|
|
|
|
*self.imp().position_tracking.borrow_mut() =
|
|
|
|
|
PositionTracking::StackedCards(StackedCards::new(prev_page, backward));
|
2024-09-04 20:33:06 +02:00
|
|
|
// Set current page here as well, to make sure it's set when animation completes for disabled animations
|
|
|
|
|
self.set_current_page(Some(page));
|
2024-01-28 15:41:00 +01:00
|
|
|
animation.play();
|
|
|
|
|
}
|
2024-01-08 21:24:43 +01:00
|
|
|
}
|
|
|
|
|
self.set_current_page(Some(page));
|
2023-01-23 18:18:26 +01:00
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
|
2024-01-08 21:24:43 +01:00
|
|
|
/// Move to image with scroll animation
|
|
|
|
|
fn scroll_to(&self, page: &LpImagePage, initial_velocity: f64) {
|
2023-04-12 01:11:59 +02:00
|
|
|
if let Some(index) = self.index_of(page) {
|
|
|
|
|
let animation = self.scroll_animation();
|
2023-01-23 18:18:26 +01:00
|
|
|
|
2023-04-12 01:11:59 +02:00
|
|
|
animation.set_value_from(self.position());
|
|
|
|
|
animation.set_value_to(index as f64 - self.position_shift());
|
|
|
|
|
animation.set_initial_velocity(initial_velocity);
|
|
|
|
|
self.set_current_page(Some(page));
|
2024-09-04 20:33:06 +02:00
|
|
|
animation.play();
|
2023-04-12 01:11:59 +02:00
|
|
|
} else {
|
|
|
|
|
log::error!("Page not in LpSlidingView {}", page.file().uri());
|
|
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Animates removal of current page
|
|
|
|
|
///
|
2023-02-20 23:44:17 +01:00
|
|
|
/// Moves to next page if possible, or to previous, or immediately removes
|
2023-01-23 01:10:15 +01:00
|
|
|
/// the current page if last remaining.
|
|
|
|
|
pub fn scroll_to_neighbor(&self) {
|
|
|
|
|
if let Some(next_page) = self.next_page() {
|
2024-01-28 15:41:00 +01:00
|
|
|
self.step_to(&next_page);
|
2023-01-23 01:10:15 +01:00
|
|
|
} else if let Some(prev_page) = self.prev_page() {
|
2024-01-28 15:41:00 +01:00
|
|
|
self.step_to(&prev_page);
|
2023-01-23 01:10:15 +01:00
|
|
|
} else if let Some(current_page) = self.current_page() {
|
2023-12-21 21:28:37 +01:00
|
|
|
self.remove(¤t_page, false);
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn next_page(&self) -> Option<LpImagePage> {
|
|
|
|
|
let current_index = self.current_index()?;
|
|
|
|
|
let next_index = current_index.checked_add(1)?;
|
|
|
|
|
self.imp().pages.borrow().get(next_index).cloned()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn prev_page(&self) -> Option<LpImagePage> {
|
|
|
|
|
let current_index = self.current_index()?;
|
|
|
|
|
let prev_index = current_index.checked_sub(1)?;
|
|
|
|
|
self.imp().pages.borrow().get(prev_index).cloned()
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 18:18:26 +01:00
|
|
|
fn page_at(&self, position: f64) -> Option<LpImagePage> {
|
|
|
|
|
let index = (position + self.position_shift()) as usize;
|
|
|
|
|
self.imp().pages.borrow().get(index).cloned()
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 01:10:15 +01:00
|
|
|
fn n_pages(&self) -> usize {
|
|
|
|
|
self.imp().pages.borrow().len()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn is_empty(&self) -> bool {
|
|
|
|
|
self.imp().pages.borrow().is_empty()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn index_of(&self, page: &LpImagePage) -> Option<usize> {
|
|
|
|
|
self.imp().pages.borrow().iter().position(|x| x == page)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn position(&self) -> f64 {
|
2024-01-28 15:41:00 +01:00
|
|
|
match &*self.imp().position_tracking.borrow() {
|
|
|
|
|
PositionTracking::Position(position) => *position,
|
|
|
|
|
PositionTracking::StackedCards(_) => {
|
|
|
|
|
log::error!("Using SlidindView position while in StackedCards animation");
|
|
|
|
|
0.
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn position_shift(&self) -> f64 {
|
|
|
|
|
self.imp().position_shift.get()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Sets position shift to correct page insert or removal
|
|
|
|
|
///
|
2023-11-29 21:15:36 +01:00
|
|
|
/// Takes the old position of the current page and calculates and sets the
|
|
|
|
|
/// shift to keep the current page optically at the same position.
|
2023-01-23 01:10:15 +01:00
|
|
|
fn shift_position(&self, old_index: Option<usize>) {
|
|
|
|
|
if let (Some(old_index), Some(new_index)) = (old_index, self.current_index()) {
|
|
|
|
|
let shift = new_index as f64 - old_index as f64;
|
|
|
|
|
self.imp().position_shift.set(self.position_shift() + shift);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_position(&self, position: f64) {
|
2024-01-08 21:24:43 +01:00
|
|
|
self.imp()
|
|
|
|
|
.position_tracking
|
|
|
|
|
.borrow_mut()
|
|
|
|
|
.set_position(position);
|
2023-01-23 01:10:15 +01:00
|
|
|
self.queue_allocate();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn set_current_page(&self, page: Option<&LpImagePage>) {
|
|
|
|
|
self.imp().current_page.replace(page.cloned());
|
|
|
|
|
self.notify("current-page");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn current_index(&self) -> Option<usize> {
|
|
|
|
|
self.imp()
|
|
|
|
|
.current_page
|
|
|
|
|
.borrow()
|
|
|
|
|
.as_ref()
|
|
|
|
|
.and_then(|x| self.index_of(x))
|
|
|
|
|
}
|
|
|
|
|
|
2023-01-23 18:18:26 +01:00
|
|
|
fn scroll_animation(&self) -> &adw::SpringAnimation {
|
2023-01-23 01:10:15 +01:00
|
|
|
self.imp().scroll_animation.get_or_init(|| {
|
2024-06-18 00:37:38 +02:00
|
|
|
let target = adw::CallbackAnimationTarget::new(glib::clone!(
|
|
|
|
|
#[weak(rename_to = obj)]
|
|
|
|
|
self,
|
|
|
|
|
move |position| obj.set_position(position)
|
|
|
|
|
));
|
2023-01-23 01:10:15 +01:00
|
|
|
|
2023-01-23 18:18:26 +01:00
|
|
|
let animation = adw::SpringAnimation::builder()
|
|
|
|
|
.spring_params(&adw::SpringParams::new(
|
|
|
|
|
SCROLL_DAMPING_RATIO,
|
|
|
|
|
SCROLL_MASS,
|
|
|
|
|
SCROLL_STIFFNESS,
|
|
|
|
|
))
|
2023-01-23 01:10:15 +01:00
|
|
|
.widget(self)
|
|
|
|
|
.target(&target)
|
|
|
|
|
.build();
|
|
|
|
|
|
2024-06-18 00:37:38 +02:00
|
|
|
animation.connect_done(glib::clone!(
|
|
|
|
|
#[weak(rename_to = obj)]
|
|
|
|
|
self,
|
|
|
|
|
move |_| obj.emit_target_page_reached()
|
|
|
|
|
));
|
2023-01-23 01:10:15 +01:00
|
|
|
|
|
|
|
|
animation
|
|
|
|
|
})
|
|
|
|
|
}
|
2024-01-08 21:24:43 +01:00
|
|
|
|
|
|
|
|
fn step_animation(&self) -> &adw::TimedAnimation {
|
|
|
|
|
self.imp().step_animation.get_or_init(|| {
|
2024-06-18 00:37:38 +02:00
|
|
|
let target: adw::CallbackAnimationTarget =
|
|
|
|
|
adw::CallbackAnimationTarget::new(glib::clone!(
|
|
|
|
|
#[weak(rename_to = obj)]
|
|
|
|
|
self,
|
|
|
|
|
move |progress| {
|
|
|
|
|
obj.imp()
|
|
|
|
|
.position_tracking
|
|
|
|
|
.borrow_mut()
|
|
|
|
|
.set_progress(progress);
|
|
|
|
|
obj.queue_allocate();
|
|
|
|
|
}
|
|
|
|
|
));
|
2024-01-08 21:24:43 +01:00
|
|
|
|
|
|
|
|
let animation = adw::TimedAnimation::builder()
|
|
|
|
|
.duration(STEP_DURATION)
|
|
|
|
|
.value_from(0.)
|
|
|
|
|
.value_to(1.)
|
|
|
|
|
.widget(self)
|
|
|
|
|
.target(&target)
|
|
|
|
|
.build();
|
|
|
|
|
|
2024-06-18 00:37:38 +02:00
|
|
|
animation.connect_done(glib::clone!(
|
|
|
|
|
#[weak(rename_to = obj)]
|
|
|
|
|
self,
|
|
|
|
|
move |_| {
|
|
|
|
|
if let Some(current_index) = obj.current_index() {
|
|
|
|
|
let imp = obj.imp();
|
|
|
|
|
imp.position_tracking.borrow_mut().set_progress(1.);
|
|
|
|
|
imp.position_tracking
|
|
|
|
|
.borrow_mut()
|
|
|
|
|
.set_position(current_index as f64 - obj.position_shift());
|
|
|
|
|
obj.queue_allocate();
|
|
|
|
|
} else {
|
|
|
|
|
error!("No current page at end of animation");
|
|
|
|
|
}
|
|
|
|
|
obj.emit_target_page_reached();
|
2024-01-08 21:24:43 +01:00
|
|
|
}
|
2024-06-18 00:37:38 +02:00
|
|
|
));
|
2024-01-08 21:24:43 +01:00
|
|
|
|
|
|
|
|
animation
|
|
|
|
|
})
|
|
|
|
|
}
|
2023-01-23 01:10:15 +01:00
|
|
|
}
|
2023-12-21 21:28:37 +01:00
|
|
|
|
|
|
|
|
/// See [`LpSlidingView::editor`](LpSlidingView::editor)
|
|
|
|
|
pub struct SlidingViewEditor {
|
|
|
|
|
sliding_view: LpSlidingView,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SlidingViewEditor {
|
|
|
|
|
fn new(sliding_view: LpSlidingView) -> Self {
|
|
|
|
|
Self { sliding_view }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Removes the page giving notifications later
|
|
|
|
|
pub fn remove_lazy(&self, page: &LpImagePage) {
|
|
|
|
|
self.remove(page, true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Removes all pages giving notifications later
|
|
|
|
|
pub fn clear_lazy(&self) {
|
|
|
|
|
self.clear(true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::ops::Deref for SlidingViewEditor {
|
|
|
|
|
type Target = LpSlidingView;
|
|
|
|
|
|
|
|
|
|
fn deref(&self) -> &LpSlidingView {
|
|
|
|
|
&self.sliding_view
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Drop for SlidingViewEditor {
|
|
|
|
|
fn drop(&mut self) {
|
|
|
|
|
// Trigger everything that hasn't been done by lazy updates
|
|
|
|
|
if self.is_empty() {
|
|
|
|
|
self.clear(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|