use nami::{Binding, Computed};
use waterui_core::{AnyView, View, raw_view};
use crate::{Point, StretchAxis};
#[derive(Clone, Debug)]
pub struct ScrollController<T: Clone + 'static> {
target: Binding<T>,
generation: Binding<i32>,
}
impl<T: Clone + 'static> ScrollController<T> {
#[must_use]
pub fn new(initial_target: T) -> Self {
Self {
target: Binding::container(initial_target),
generation: Binding::container(0),
}
}
pub fn scroll_to(&self, target: T) {
self.target.set(target);
self.generation.set(
self.generation
.get()
.checked_add(1)
.expect("scroll request generation overflow"),
);
}
#[must_use]
pub fn target(&self) -> Computed<T> {
self.target.clone().into()
}
#[must_use]
pub fn generation(&self) -> Computed<i32> {
self.generation.clone().into()
}
}
impl<T> Default for ScrollController<T>
where
T: Clone + Default + 'static,
{
fn default() -> Self {
Self::new(T::default())
}
}
#[derive(Debug)]
pub struct ScrollView {
axis: Axis,
content: AnyView,
controller: Option<ScrollController<Point>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
#[non_exhaustive]
pub enum Axis {
Horizontal,
#[default]
Vertical,
All,
}
impl ScrollView {
#[must_use]
pub const fn new(axis: Axis, content: AnyView) -> Self {
Self {
axis,
content,
controller: None,
}
}
pub fn into_inner(self) -> (Axis, AnyView, Option<ScrollController<Point>>) {
(self.axis, self.content, self.controller)
}
#[must_use = "this borrows the scroll view's parts without consuming it"]
pub const fn as_parts(&self) -> (Axis, &AnyView, Option<&ScrollController<Point>>) {
(self.axis, &self.content, self.controller.as_ref())
}
#[must_use]
pub fn scroll_controller(mut self, controller: &ScrollController<Point>) -> Self {
self.controller = Some(controller.clone());
self
}
pub fn horizontal(content: impl View) -> Self {
Self::new(Axis::Horizontal, AnyView::new(content))
}
pub fn vertical(content: impl View) -> Self {
Self::new(Axis::Vertical, AnyView::new(content))
}
pub fn both(content: impl View) -> Self {
Self::new(Axis::All, AnyView::new(content))
}
}
raw_view!(ScrollView, StretchAxis::Both);
pub fn scroll(content: impl View) -> ScrollView {
ScrollView::vertical(content)
}
pub fn scroll_horizontal(content: impl View) -> ScrollView {
ScrollView::horizontal(content)
}
pub fn scroll_both(content: impl View) -> ScrollView {
ScrollView::both(content)
}
#[cfg(test)]
mod tests {
use super::ScrollController;
use crate::Point;
use nami::Signal;
#[test]
fn repeated_target_requests_advance_generation() {
let controller = ScrollController::new(Point::zero());
controller.scroll_to(Point::new(0.0, 240.0));
assert_eq!(controller.target().get(), Point::new(0.0, 240.0));
assert_eq!(controller.generation().get(), 1);
controller.scroll_to(Point::new(0.0, 240.0));
assert_eq!(controller.target().get(), Point::new(0.0, 240.0));
assert_eq!(controller.generation().get(), 2);
}
}