use std::cell::RefCell;
use cranpose_core::{MutableState, RuntimeCell};
use crate::lazy::nearest_range::NearestRangeState;
#[derive(Clone, Copy)]
pub struct LazyListScrollPosition {
pub(super) index: MutableState<usize>,
pub(super) scroll_offset: MutableState<f32>,
inner: RuntimeCell<RefCell<ScrollPositionInner>>,
}
struct ScrollPositionInner {
current_index: usize,
current_scroll_offset: f32,
last_known_first_item_key: Option<u64>,
nearest_range_state: NearestRangeState,
}
impl LazyListScrollPosition {
pub(super) fn new(index: usize, scroll_offset: f32) -> Self {
Self {
index: cranpose_core::mutableStateOf(index),
scroll_offset: cranpose_core::mutableStateOf(scroll_offset),
inner: RuntimeCell::new(RefCell::new(ScrollPositionInner {
current_index: index,
current_scroll_offset: scroll_offset,
last_known_first_item_key: None,
nearest_range_state: NearestRangeState::new(index),
})),
}
}
pub(super) fn current(&self) -> Option<(usize, f32)> {
self.inner.try_with(|inner| {
let inner = inner.borrow();
(inner.current_index, inner.current_scroll_offset)
})
}
pub(super) fn current_index(&self) -> usize {
self.inner
.try_with(|cell| cell.borrow().current_index)
.unwrap_or(0)
}
pub(super) fn current_scroll_offset(&self) -> f32 {
self.inner
.try_with(|cell| cell.borrow().current_scroll_offset)
.unwrap_or(0.0)
}
pub fn index(&self) -> usize {
if !self.index.is_alive() {
return 0;
}
let Some(index) = self.inner.try_with(|cell| cell.borrow().current_index) else {
return 0;
};
self.index.subscribe_current_scope_only();
index
}
pub fn scroll_offset(&self) -> f32 {
if !self.scroll_offset.is_alive() {
return 0.0;
}
let Some(offset) = self
.inner
.try_with(|cell| cell.borrow().current_scroll_offset)
else {
return 0.0;
};
self.scroll_offset.subscribe_current_scope_only();
offset
}
pub(crate) fn update_from_measure_result(
&self,
first_visible_index: usize,
first_visible_scroll_offset: f32,
first_visible_item_key: Option<u64>,
) {
if !self.index.is_alive() || !self.scroll_offset.is_alive() {
return;
}
let updated = self.inner.try_with(|cell| {
let mut inner = cell.borrow_mut();
inner.current_index = first_visible_index;
inner.current_scroll_offset = first_visible_scroll_offset;
inner.last_known_first_item_key = first_visible_item_key;
inner.nearest_range_state.update(first_visible_index);
});
if updated.is_none() {
return;
}
if self.index.get_non_reactive() != first_visible_index {
self.index.set(first_visible_index);
}
if (self.scroll_offset.get_non_reactive() - first_visible_scroll_offset).abs() > 0.001 {
self.scroll_offset.set(first_visible_scroll_offset);
}
}
pub(crate) fn request_position_and_forget_last_known_key(
&self,
index: usize,
scroll_offset: f32,
) {
if !self.index.is_alive() || !self.scroll_offset.is_alive() {
return;
}
let updated = self.inner.try_with(|cell| {
let mut inner = cell.borrow_mut();
inner.current_index = index;
inner.current_scroll_offset = scroll_offset;
inner.last_known_first_item_key = None;
inner.nearest_range_state.update(index);
});
if updated.is_none() {
return;
}
if self.index.get_non_reactive() != index {
self.index.set(index);
}
if (self.scroll_offset.get_non_reactive() - scroll_offset).abs() > 0.001 {
self.scroll_offset.set(scroll_offset);
}
}
pub(crate) fn update_if_first_item_moved<F>(
&self,
new_item_count: usize,
find_by_key: F,
) -> usize
where
F: Fn(u64) -> Option<usize>,
{
let Some((current_index, new_index)) = self.first_item_index(new_item_count, find_by_key)
else {
return 0;
};
if current_index != new_index
&& let Some(cell) = self.inner.try_get()
{
{
let mut inner = cell.borrow_mut();
inner.current_index = new_index;
inner.nearest_range_state.update(new_index);
}
self.index.set(new_index);
}
new_index
}
pub(crate) fn first_item_moved<F>(&self, new_item_count: usize, find_by_key: F) -> bool
where
F: Fn(u64) -> Option<usize>,
{
self.first_item_index(new_item_count, find_by_key)
.is_some_and(|(current_index, new_index)| current_index != new_index)
}
fn first_item_index<F>(&self, new_item_count: usize, find_by_key: F) -> Option<(usize, usize)>
where
F: Fn(u64) -> Option<usize>,
{
if !self.index.is_alive() {
return None;
}
let (current_index, last_key) = self.inner.try_with(|cell| {
let inner = cell.borrow();
(inner.current_index, inner.last_known_first_item_key)
})?;
let new_index = match last_key {
None => current_index.min(new_item_count.saturating_sub(1)),
Some(key) => find_by_key(key)
.unwrap_or_else(|| current_index.min(new_item_count.saturating_sub(1))),
};
Some((current_index, new_index))
}
pub fn nearest_range(&self) -> std::ops::Range<usize> {
self.inner
.try_with(|cell| cell.borrow().nearest_range_state.range())
.unwrap_or(0..0)
}
}