use repose_core::*;
use std::cell::RefCell;
use std::rc::Rc;
use crate::anim_ext::{AnimatedContent, AnimatedContentConfig, EnterTransition, ExitTransition};
#[derive(Clone)]
pub struct PagerConfig {
pub modifier: Modifier,
pub page_spacing: Dp,
pub user_scroll_enabled: bool,
pub content_padding: PaddingValues,
}
impl Default for PagerConfig {
fn default() -> Self {
Self {
modifier: Modifier::new(),
page_spacing: Dp::ZERO,
user_scroll_enabled: true,
content_padding: PaddingValues::default(),
}
}
}
struct PagerDrag {
start: Option<(f32, f32)>,
}
fn pager_threshold_px() -> f32 {
Dp(24.0).to_px().0.max(8.0)
}
fn horizontal_handlers(
key: &str,
state: &Rc<PagerState>,
) -> (
impl Fn(PointerEvent) + Clone + 'static,
impl Fn(PointerEvent) + Clone + 'static,
impl Fn(PointerEvent) + Clone + 'static,
) {
let drag = Rc::new(remember_with_key(format!("pager_drag:{key}"), || {
RefCell::new(PagerDrag { start: None })
}));
let st = state.clone();
let on_down = {
let drag = drag.clone();
move |e: PointerEvent| {
drag.borrow_mut().start = Some((e.position.x, e.position.y));
}
};
let on_up = {
let drag = drag.clone();
move |e: PointerEvent| {
if let Some((sx, sy)) = drag.borrow().start {
let dx = e.position.x - sx;
let dy = e.position.y - sy;
let threshold = pager_threshold_px();
if dx.abs() > threshold && dx.abs() > dy.abs() * 1.5 {
if dx < 0.0 {
st.set_page(st.current_page.get().saturating_add(1));
} else {
st.set_page(st.current_page.get().saturating_sub(1));
}
}
}
drag.borrow_mut().start = None;
}
};
let on_cancel = {
let drag = drag.clone();
move |_: PointerEvent| {
drag.borrow_mut().start = None;
}
};
(on_down, on_up, on_cancel)
}
fn vertical_handlers(
key: &str,
state: &Rc<PagerState>,
) -> (
impl Fn(PointerEvent) + Clone + 'static,
impl Fn(PointerEvent) + Clone + 'static,
impl Fn(PointerEvent) + Clone + 'static,
) {
let drag = Rc::new(remember_with_key(format!("vpager_drag:{key}"), || {
RefCell::new(PagerDrag { start: None })
}));
let st = state.clone();
let on_down = {
let drag = drag.clone();
move |e: PointerEvent| {
drag.borrow_mut().start = Some((e.position.x, e.position.y));
}
};
let on_up = {
let drag = drag.clone();
move |e: PointerEvent| {
if let Some((sx, sy)) = drag.borrow().start {
let dx = e.position.x - sx;
let dy = e.position.y - sy;
let threshold = pager_threshold_px();
if dy.abs() > threshold && dy.abs() > dx.abs() * 1.5 {
if dy < 0.0 {
st.set_page(st.current_page.get().saturating_add(1));
} else {
st.set_page(st.current_page.get().saturating_sub(1));
}
}
}
drag.borrow_mut().start = None;
}
};
let on_cancel = {
let drag = drag.clone();
move |_: PointerEvent| {
drag.borrow_mut().start = None;
}
};
(on_down, on_up, on_cancel)
}
pub struct PagerState {
current_page: Signal<usize>,
previous_page: Signal<usize>,
page_count: Signal<usize>,
}
impl PagerState {
pub fn new(page_count: usize) -> Self {
Self {
current_page: signal(0),
previous_page: signal(0),
page_count: signal(page_count.max(1)),
}
}
pub fn current_page(&self) -> usize {
self.current_page.get()
}
pub fn set_page(&self, page: usize) {
let max_page = self.page_count.get().saturating_sub(1);
let prev = self.current_page.get();
let next = page.min(max_page);
if next == prev {
return;
}
batch(|| {
self.previous_page.set(prev);
self.current_page.set(next);
});
}
fn direction(&self) -> i32 {
let cur = self.current_page.get() as i64;
let prev = self.previous_page.get() as i64;
(cur - prev).signum() as i32
}
pub fn page_count(&self) -> usize {
self.page_count.get()
}
pub fn set_page_count(&self, count: usize) {
let count = count.max(1);
let cur = self.current_page.get();
let max_page = count - 1;
batch(|| {
self.page_count.set(count);
if cur > max_page {
self.previous_page.set(cur);
self.current_page.set(max_page);
}
});
}
}
#[allow(non_snake_case)]
pub fn HorizontalPager(
key: impl Into<String>,
state: Rc<PagerState>,
page_builder: impl Fn(usize) -> View + 'static,
config: PagerConfig,
) -> View {
let key = key.into();
let page = state.current_page.get();
let page_spacing = config.page_spacing;
let dir = if state.direction() < 0 { -1.0 } else { 1.0 };
let slide_offset = (Dp(800.0) + page_spacing) * dir;
let (on_down, on_up, on_cancel) = horizontal_handlers(&key, &state);
let content = AnimatedContent(
page,
page_builder,
AnimatedContentConfig {
key: format!("page_content:{key}"),
spec: AnimationSpec::spring_gentle(),
enter: EnterTransition::Composite(vec![
EnterTransition::FadeIn,
EnterTransition::SlideIn {
offset_x: slide_offset,
offset_y: Dp::ZERO,
},
]),
exit: ExitTransition::Composite(vec![
ExitTransition::FadeOut,
ExitTransition::SlideOut {
offset_x: -(slide_offset),
offset_y: Dp::ZERO,
},
]),
},
);
let pager_mod = Modifier::new().fill_max_size().then(config.modifier);
if config.user_scroll_enabled {
crate::Box(
pager_mod
.on_pointer_down(on_down)
.on_pointer_up(on_up)
.on_pointer_cancel(on_cancel),
)
.with_children(vec![
crate::Box(Modifier::new().fill_max_size()).with_children(vec![content]),
])
} else {
crate::Box(pager_mod).with_children(vec![
crate::Box(Modifier::new().fill_max_size()).with_children(vec![content]),
])
}
}
#[allow(non_snake_case)]
pub fn VerticalPager(
key: impl Into<String>,
state: Rc<PagerState>,
page_builder: impl Fn(usize) -> View + 'static,
config: PagerConfig,
) -> View {
let key = key.into();
let page = state.current_page.get();
let page_spacing = config.page_spacing;
let dir = if state.direction() < 0 { -1.0 } else { 1.0 };
let slide_offset = (Dp(600.0) + page_spacing) * dir;
let (on_down, on_up, on_cancel) = vertical_handlers(&key, &state);
let content = AnimatedContent(
page,
page_builder,
AnimatedContentConfig {
key: format!("vpage_content:{key}"),
spec: AnimationSpec::spring_gentle(),
enter: EnterTransition::Composite(vec![
EnterTransition::FadeIn,
EnterTransition::SlideIn {
offset_x: Dp::ZERO,
offset_y: slide_offset,
},
]),
exit: ExitTransition::Composite(vec![
ExitTransition::FadeOut,
ExitTransition::SlideOut {
offset_x: Dp::ZERO,
offset_y: -(slide_offset),
},
]),
},
);
let pager_mod = Modifier::new().fill_max_size().then(config.modifier);
if config.user_scroll_enabled {
crate::Box(
pager_mod
.on_pointer_down(on_down)
.on_pointer_up(on_up)
.on_pointer_cancel(on_cancel),
)
.with_children(vec![
crate::Box(Modifier::new().fill_max_size()).with_children(vec![content]),
])
} else {
crate::Box(pager_mod).with_children(vec![
crate::Box(Modifier::new().fill_max_size()).with_children(vec![content]),
])
}
}