#![allow(non_snake_case)]
use std::rc::Rc;
use repose_core::animation::AnimationSpec;
use repose_core::*;
use repose_ui::{
Box, Column,
ViewExt,
};
use super::*;
#[derive(Clone, Debug)]
pub struct SwipeToDismissConfig {
pub modifier: Modifier,
pub dismiss_threshold: f32,
pub dismissed_offset: f32,
pub animation_spec: AnimationSpec,
pub gestures_enabled: bool,
pub enable_dismiss_from_start_to_end: bool,
pub enable_dismiss_from_end_to_start: bool,
}
impl Default for SwipeToDismissConfig {
fn default() -> Self {
Self {
modifier: Modifier::new(),
dismiss_threshold: SwipeToDismissDefaults::DISMISS_THRESHOLD,
dismissed_offset: SwipeToDismissDefaults::DISMISSED_OFFSET,
animation_spec: AnimationSpec::spring_gentle(),
gestures_enabled: true,
enable_dismiss_from_start_to_end: true,
enable_dismiss_from_end_to_start: true,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DismissDirection {
StartToEnd,
EndToStart,
Both,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DismissValue {
Default,
DismissedToStart,
DismissedToEnd,
}
pub struct SwipeToDismissState {
swipeable: repose_core::SwipeableState<DismissValue>,
dismissed_offset: f32,
}
impl Default for SwipeToDismissState {
fn default() -> Self {
Self::new()
}
}
impl SwipeToDismissState {
pub fn new() -> Self {
Self::with_config(SwipeToDismissConfig::default())
}
pub fn with_config(config: SwipeToDismissConfig) -> Self {
let one_third = 1.0 / 3.0;
let positional_threshold = (config.dismiss_threshold * one_third) / config.dismissed_offset;
let mut anchors = vec![(0.0, DismissValue::Default)];
if config.enable_dismiss_from_end_to_start {
anchors.push((-config.dismissed_offset, DismissValue::DismissedToStart));
}
if config.enable_dismiss_from_start_to_end {
anchors.push((config.dismissed_offset, DismissValue::DismissedToEnd));
}
anchors.sort_by(|(a, _), (b, _)| a.partial_cmp(b).unwrap());
let swipeable = repose_core::SwipeableState::new(
anchors,
repose_core::SwipeableConfig {
animation_spec: config.animation_spec.clone(),
positional_threshold,
..Default::default()
},
);
swipeable.snap_to(0.0);
Self {
swipeable,
dismissed_offset: config.dismissed_offset,
}
}
pub fn offset(&self) -> f32 {
self.swipeable.offset()
}
pub fn set_offset_instant(&self, off: f32) {
self.swipeable.snap_to(off);
}
pub fn is_dismissed(&self) -> bool {
self.swipeable.current_value() != DismissValue::Default
}
pub fn dismiss(&self) {
self.swipeable.animate_to(&DismissValue::DismissedToStart);
}
pub fn dismiss_to(&self, offset: f32) {
let value = if offset < 0.0 {
DismissValue::DismissedToStart
} else {
DismissValue::DismissedToEnd
};
self.swipeable.animate_to(&value);
}
pub fn reset(&self) {
self.swipeable.animate_to(&DismissValue::Default);
}
fn try_handle_dismiss_with_threshold(&self, on_dismiss: &Option<Rc<dyn Fn()>>, _threshold: f32) {
if !self.swipeable.is_animating() {
let val = self.swipeable.current_value();
if val != DismissValue::Default {
if let Some(cb) = on_dismiss {
cb();
}
}
}
}
}
pub fn SwipeToDismiss(
state: Rc<SwipeToDismissState>,
on_dismiss: Option<Rc<dyn Fn()>>,
background: View,
content: View,
modifier: Modifier,
config: SwipeToDismissConfig,
) -> View {
let offset = state.offset();
state.try_handle_dismiss_with_threshold(&on_dismiss, config.dismiss_threshold);
let s1 = state.swipeable.clone();
let s2 = state.swipeable.clone();
let s3 = state.swipeable.clone();
let on_down = { move |e: PointerEvent| s1.on_pointer_down(e.position.x) };
let on_move = { move |e: PointerEvent| s2.on_pointer_move(e.position.x) };
let on_up = { move |_e: PointerEvent| s3.on_pointer_up() };
let display_offset = offset
.max(-config.dismissed_offset)
.min(config.dismissed_offset);
let content_modifier = {
let mut m = Modifier::new()
.fill_max_width()
.translate(display_offset, 0.0);
if config.gestures_enabled {
m = m
.on_pointer_down(on_down)
.on_pointer_move(on_move)
.on_pointer_up(on_up);
}
m
};
Column(modifier.fill_max_width()).child((
Box(Modifier::new().fill_max_size().absolute()).child(background),
Box(content_modifier).child(content),
))
}