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: f32,
pub user_scroll_enabled: bool,
pub content_padding: PaddingValues,
}
impl Default for PagerConfig {
fn default() -> Self {
Self {
modifier: Modifier::new(),
page_spacing: 0.0,
user_scroll_enabled: true,
content_padding: PaddingValues::default(),
}
}
}
pub struct PagerState {
current_page: Signal<usize>,
page_count: Signal<usize>,
}
impl PagerState {
pub fn new(page_count: usize) -> Self {
Self {
current_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);
self.current_page.set(page.min(max_page));
}
pub fn page_count(&self) -> usize {
self.page_count.get()
}
}
#[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 slide_offset = 800.0 + page_spacing;
let drag_start_x = Rc::new(remember_with_key(format!("pager_drag:{key}"), || {
RefCell::new(None::<f32>)
}));
let on_down = {
let d = drag_start_x.clone();
move |e: PointerEvent| {
*d.borrow_mut() = Some(e.position.x);
}
};
let st = state.clone();
let on_up = {
let d = drag_start_x.clone();
move |e: PointerEvent| {
if let Some(start_x) = *d.borrow() {
let delta = e.position.x - start_x;
let threshold = 50.0;
if delta.abs() > threshold {
if delta < 0.0 {
let next =
(st.current_page.get() + 1).min(st.page_count.get().saturating_sub(1));
st.current_page.set(next);
} else {
let prev = st.current_page.get().saturating_sub(1);
st.current_page.set(prev);
}
}
}
*d.borrow_mut() = None;
}
};
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: 0.0,
},
]),
exit: ExitTransition::Composite(vec![
ExitTransition::FadeOut,
ExitTransition::SlideOut {
offset_x: -(slide_offset),
offset_y: 0.0,
},
]),
},
);
let gesture = if config.user_scroll_enabled {
crate::Box(
Modifier::new()
.fill_max_size()
.hit_passthrough()
.on_pointer_down(on_down)
.on_pointer_up(on_up),
)
} else {
crate::Box(Modifier::new().fill_max_size().hit_passthrough())
};
crate::ZStack(Modifier::new().fill_max_size().then(config.modifier)).with_children(vec![
crate::Box(Modifier::new().fill_max_size()).with_children(vec![content]),
gesture,
])
}
#[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 slide_offset = 600.0 + page_spacing;
let drag_start_y = Rc::new(remember_with_key(format!("vpager_drag:{key}"), || {
RefCell::new(None::<f32>)
}));
let on_down = {
let d = drag_start_y.clone();
move |e: PointerEvent| {
*d.borrow_mut() = Some(e.position.y);
}
};
let st = state.clone();
let on_up = {
let d = drag_start_y.clone();
move |e: PointerEvent| {
if let Some(start_y) = *d.borrow() {
let delta = e.position.y - start_y;
let threshold = 50.0;
if delta.abs() > threshold {
if delta < 0.0 {
let next =
(st.current_page.get() + 1).min(st.page_count.get().saturating_sub(1));
st.current_page.set(next);
} else {
let prev = st.current_page.get().saturating_sub(1);
st.current_page.set(prev);
}
}
}
*d.borrow_mut() = None;
}
};
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: 0.0,
offset_y: slide_offset,
},
]),
exit: ExitTransition::Composite(vec![
ExitTransition::FadeOut,
ExitTransition::SlideOut {
offset_x: 0.0,
offset_y: -(slide_offset),
},
]),
},
);
let gesture = if config.user_scroll_enabled {
crate::Box(
Modifier::new()
.fill_max_size()
.hit_passthrough()
.on_pointer_down(on_down)
.on_pointer_up(on_up),
)
} else {
crate::Box(Modifier::new().fill_max_size().hit_passthrough())
};
crate::ZStack(Modifier::new().fill_max_size().then(config.modifier)).with_children(vec![
crate::Box(Modifier::new().fill_max_size()).with_children(vec![content]),
gesture,
])
}