slidingView: Implement swipe gesture (AdwSwipeable)
This commit is contained in:
parent
74d148572f
commit
d533124daa
3 changed files with 112 additions and 18 deletions
6
Cargo.lock
generated
6
Cargo.lock
generated
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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,17 +301,20 @@ 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.);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scroll_to_velocity(&self, page: &LpImagePage, initial_velocity: f64) {
|
||||||
let index = self.index_of(page).unwrap();
|
let index = self.index_of(page).unwrap();
|
||||||
|
|
||||||
let animation = self.scroll_animation();
|
let animation = self.scroll_animation();
|
||||||
|
|
||||||
animation.set_value_from(self.position());
|
animation.set_value_from(self.position());
|
||||||
animation.set_value_to(index as f64 - self.position_shift());
|
animation.set_value_to(index as f64 - self.position_shift());
|
||||||
|
animation.set_initial_velocity(initial_velocity);
|
||||||
animation.play();
|
animation.play();
|
||||||
self.set_current_page(Some(page));
|
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();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue