slidingView: Implement swipe gesture (AdwSwipeable)

This commit is contained in:
Sophie Herold 2023-01-23 18:18:26 +01:00 • committed by Christopher Davis
parent 74d148572f
commit d533124daa
3 changed files with 112 additions and 18 deletions

6
Cargo.lock generated
View file

@ -1125,8 +1125,7 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]] [[package]]
name = "libadwaita" name = "libadwaita"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://gitlab.gnome.org/sophie-h/libadwaita-rs.git?branch=wip/sophie-h/swipeable#312a55327aeb23e160714249fe646ed36b6878e1"
checksum = "9dfa0722d4f1724f661cbf668c273c5926296ca411ed3814e206f8fd082b6c48"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"futures-channel", "futures-channel",
@ -1144,8 +1143,7 @@ dependencies = [
[[package]] [[package]]
name = "libadwaita-sys" name = "libadwaita-sys"
version = "0.2.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "git+https://gitlab.gnome.org/sophie-h/libadwaita-rs.git?branch=wip/sophie-h/swipeable#312a55327aeb23e160714249fe646ed36b6878e1"
checksum = "de902982372b454a0081d7fd9dd567b37b73ae29c8f6da1820374d345fd95d5b"
dependencies = [ dependencies = [
"gdk4-sys", "gdk4-sys",
"gio-sys 0.16.3", "gio-sys 0.16.3",

View file

@ -38,6 +38,8 @@ features = ["v2_74"]
package = "libadwaita" package = "libadwaita"
version = "0.2.0" version = "0.2.0"
features = ["v1_2"] features = ["v1_2"]
git = "https://gitlab.gnome.org/sophie-h/libadwaita-rs.git"
branch = "wip/sophie-h/swipeable"
[dependencies.gettext-rs] [dependencies.gettext-rs]
version = "0.7.0" version = "0.7.0"

View file

@ -15,7 +15,9 @@ use once_cell::sync::{Lazy, OnceCell};
use std::cell::{Cell, RefCell}; use std::cell::{Cell, RefCell};
use std::path::{Path, PathBuf}; 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 { mod imp {
use super::*; use super::*;
@ -32,7 +34,8 @@ mod imp {
/// Move position to not break animations when pages are removed/added /// Move position to not break animations when pages are removed/added
pub(super) position_shift: Cell<f64>, pub(super) position_shift: Cell<f64>,
/// The animation used to animate image changes /// The animation used to animate image changes
pub(super) scroll_animation: OnceCell<adw::TimedAnimation>, pub(super) scroll_animation: OnceCell<adw::SpringAnimation>,
pub(super) swipe_tracker: OnceCell<adw::SwipeTracker>,
} }
#[glib::object_subclass] #[glib::object_subclass]
@ -40,6 +43,7 @@ mod imp {
const NAME: &'static str = "LpSlidingView"; const NAME: &'static str = "LpSlidingView";
type Type = super::LpSlidingView; type Type = super::LpSlidingView;
type ParentType = gtk::Widget; type ParentType = gtk::Widget;
type Interfaces = (adw::Swipeable,);
} }
impl ObjectImpl for LpSlidingView { impl ObjectImpl for LpSlidingView {
@ -70,7 +74,54 @@ mod imp {
} }
fn constructed(&self) { fn constructed(&self) {
let obj = self.instance();
self.parent_constructed();
self.instance().set_overflow(gtk::Overflow::Hidden); 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<f64> {
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! { glib::wrapper! {
pub struct LpSlidingView(ObjectSubclass<imp::LpSlidingView>) pub struct LpSlidingView(ObjectSubclass<imp::LpSlidingView>)
@extends gtk::Widget, @extends gtk::Widget,
@implements gtk::Buildable; @implements gtk::Buildable, adw::Swipeable;
} }
impl LpSlidingView { impl LpSlidingView {
@ -219,16 +301,19 @@ impl LpSlidingView {
/// Move to specified page with animation /// Move to specified page with animation
pub fn scroll_to(&self, page: &LpImagePage) { pub fn scroll_to(&self, page: &LpImagePage) {
if self.current_page().as_ref() != Some(page) { self.scroll_to_velocity(page, 0.);
let index = self.index_of(page).unwrap(); }
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()); let animation = self.scroll_animation();
animation.set_value_to(index as f64 - self.position_shift());
animation.play(); animation.set_value_from(self.position());
self.set_current_page(Some(page)); 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 /// Animates removal of current page
@ -257,6 +342,11 @@ impl LpSlidingView {
self.imp().pages.borrow().get(prev_index).cloned() self.imp().pages.borrow().get(prev_index).cloned()
} }
fn page_at(&self, position: f64) -> Option<LpImagePage> {
let index = (position + self.position_shift()) as usize;
self.imp().pages.borrow().get(index).cloned()
}
fn n_pages(&self) -> usize { fn n_pages(&self) -> usize {
self.imp().pages.borrow().len() self.imp().pages.borrow().len()
} }
@ -310,14 +400,18 @@ impl LpSlidingView {
.and_then(|x| self.index_of(x)) .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(|| { self.imp().scroll_animation.get_or_init(|| {
let target = adw::CallbackAnimationTarget::new( let target = adw::CallbackAnimationTarget::new(
glib::clone!(@weak self as obj => move |position| obj.set_position(position)), glib::clone!(@weak self as obj => move |position| obj.set_position(position)),
); );
let animation = adw::TimedAnimation::builder() let animation = adw::SpringAnimation::builder()
.duration(SCROLL_DURATION) .spring_params(&adw::SpringParams::new(
SCROLL_DAMPING_RATIO,
SCROLL_MASS,
SCROLL_STIFFNESS,
))
.widget(self) .widget(self)
.target(&target) .target(&target)
.build(); .build();