From d533124daabe543a27507e7538b11df1307949a2 Mon Sep 17 00:00:00 2001 From: Sophie Herold Date: Mon, 23 Jan 2023 18:18:26 +0100 Subject: [PATCH] slidingView: Implement swipe gesture (AdwSwipeable) --- Cargo.lock | 6 +- Cargo.toml | 2 + src/widgets/sliding_view.rs | 122 +++++++++++++++++++++++++++++++----- 3 files changed, 112 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c441d7..b3dfecd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1125,8 +1125,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libadwaita" version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dfa0722d4f1724f661cbf668c273c5926296ca411ed3814e206f8fd082b6c48" +source = "git+https://gitlab.gnome.org/sophie-h/libadwaita-rs.git?branch=wip/sophie-h/swipeable#312a55327aeb23e160714249fe646ed36b6878e1" dependencies = [ "bitflags", "futures-channel", @@ -1144,8 +1143,7 @@ dependencies = [ [[package]] name = "libadwaita-sys" version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de902982372b454a0081d7fd9dd567b37b73ae29c8f6da1820374d345fd95d5b" +source = "git+https://gitlab.gnome.org/sophie-h/libadwaita-rs.git?branch=wip/sophie-h/swipeable#312a55327aeb23e160714249fe646ed36b6878e1" dependencies = [ "gdk4-sys", "gio-sys 0.16.3", diff --git a/Cargo.toml b/Cargo.toml index 3e7f932..618fe90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,8 @@ features = ["v2_74"] package = "libadwaita" version = "0.2.0" features = ["v1_2"] +git = "https://gitlab.gnome.org/sophie-h/libadwaita-rs.git" +branch = "wip/sophie-h/swipeable" [dependencies.gettext-rs] version = "0.7.0" diff --git a/src/widgets/sliding_view.rs b/src/widgets/sliding_view.rs index 1f80f94..5cb1754 100644 --- a/src/widgets/sliding_view.rs +++ b/src/widgets/sliding_view.rs @@ -15,7 +15,9 @@ use once_cell::sync::{Lazy, OnceCell}; use std::cell::{Cell, RefCell}; use std::path::{Path, PathBuf}; -static SCROLL_DURATION: u32 = 200; +static SCROLL_DAMPING_RATIO: f64 = 1.; +static SCROLL_MASS: f64 = 0.5; +static SCROLL_STIFFNESS: f64 = 500.; mod imp { use super::*; @@ -32,7 +34,8 @@ mod imp { /// Move position to not break animations when pages are removed/added pub(super) position_shift: Cell, /// The animation used to animate image changes - pub(super) scroll_animation: OnceCell, + pub(super) scroll_animation: OnceCell, + pub(super) swipe_tracker: OnceCell, } #[glib::object_subclass] @@ -40,6 +43,7 @@ mod imp { const NAME: &'static str = "LpSlidingView"; type Type = super::LpSlidingView; type ParentType = gtk::Widget; + type Interfaces = (adw::Swipeable,); } impl ObjectImpl for LpSlidingView { @@ -70,7 +74,54 @@ mod imp { } fn constructed(&self) { + let obj = self.instance(); + self.parent_constructed(); + self.instance().set_overflow(gtk::Overflow::Hidden); + + let swipe_tracker = adw::SwipeTracker::builder() + .swipeable(&*self.instance()) + .build(); + + swipe_tracker.connect_begin_swipe( + glib::clone!(@weak obj => move |_| obj.scroll_animation().pause()), + ); + + 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_velocity(&page, velocity); + } + })); + + self.swipe_tracker.set(swipe_tracker).unwrap(); + + // Avoid propagating scoll events to AdwFlap if at the end + let scroll_controller = + gtk::EventControllerScroll::new(gtk::EventControllerScrollFlags::HORIZONTAL); + + scroll_controller.connect_scroll( + glib::clone!(@weak obj => @default-return gtk::Inhibit(false), move |_, x, _| { + if x > 0. { + if let Some(max) = obj.imp().snap_points().last() { + gtk::Inhibit(obj.position() >= *max) + } else { + gtk::Inhibit(true) + } + } else { + if let Some(min) = obj.imp().snap_points().first() { + gtk::Inhibit(obj.position() <= *min) + } else { + gtk::Inhibit(true) + } + } + }), + ); + + obj.add_controller(&scroll_controller); } } @@ -105,12 +156,43 @@ mod imp { } } } + + impl SwipeableImpl for LpSlidingView { + fn progress(&self) -> f64 { + self.instance().position() + } + + fn distance(&self) -> f64 { + let obj = self.instance(); + + (obj.allocation().width() as usize * obj.n_pages()) as f64 + } + + fn snap_points(&self) -> Vec { + let obj = self.instance(); + + (0..obj.n_pages()) + .map(|i| i as f64 - obj.position_shift()) + .collect() + } + + fn cancel_progress(&self) -> f64 { + let obj = self.instance(); + let snap_points = self.snap_points(); + + if let (Some(min), Some(max)) = (snap_points.first(), snap_points.last()) { + obj.position().round().max(*min).min(*max) + } else { + 0. + } + } + } } glib::wrapper! { pub struct LpSlidingView(ObjectSubclass) @extends gtk::Widget, - @implements gtk::Buildable; + @implements gtk::Buildable, adw::Swipeable; } impl LpSlidingView { @@ -219,16 +301,19 @@ impl LpSlidingView { /// Move to specified page with animation pub fn scroll_to(&self, page: &LpImagePage) { - if self.current_page().as_ref() != Some(page) { - let index = self.index_of(page).unwrap(); + self.scroll_to_velocity(page, 0.); + } - let animation = self.scroll_animation(); + pub fn scroll_to_velocity(&self, page: &LpImagePage, initial_velocity: f64) { + let index = self.index_of(page).unwrap(); - animation.set_value_from(self.position()); - animation.set_value_to(index as f64 - self.position_shift()); - animation.play(); - self.set_current_page(Some(page)); - } + let animation = self.scroll_animation(); + + animation.set_value_from(self.position()); + animation.set_value_to(index as f64 - self.position_shift()); + animation.set_initial_velocity(initial_velocity); + animation.play(); + self.set_current_page(Some(page)); } /// Animates removal of current page @@ -257,6 +342,11 @@ impl LpSlidingView { self.imp().pages.borrow().get(prev_index).cloned() } + fn page_at(&self, position: f64) -> Option { + let index = (position + self.position_shift()) as usize; + self.imp().pages.borrow().get(index).cloned() + } + fn n_pages(&self) -> usize { self.imp().pages.borrow().len() } @@ -310,14 +400,18 @@ impl LpSlidingView { .and_then(|x| self.index_of(x)) } - fn scroll_animation(&self) -> &adw::TimedAnimation { + fn scroll_animation(&self) -> &adw::SpringAnimation { self.imp().scroll_animation.get_or_init(|| { let target = adw::CallbackAnimationTarget::new( glib::clone!(@weak self as obj => move |position| obj.set_position(position)), ); - let animation = adw::TimedAnimation::builder() - .duration(SCROLL_DURATION) + let animation = adw::SpringAnimation::builder() + .spring_params(&adw::SpringParams::new( + SCROLL_DAMPING_RATIO, + SCROLL_MASS, + SCROLL_STIFFNESS, + )) .widget(self) .target(&target) .build();