use std::collections::HashMap;
use std::sync::Arc;
use crate::callback::Callback;
use crate::core::element::Key;
use crate::core::node::{ScrollbarZone, ScrollbarZonesParams, WidgetNode, compute_scrollbar_zones};
use crate::style::{Rect, ScrollbarVariant, Style};
use crate::widgets::document_view::FormatCache;
use crate::widgets::document_view::node::{DocumentTableRectSelection, VisualCache};
use crate::widgets::internal::StackProps;
use crate::widgets::scroll::{KineticScrollState, ScrollAxis, SmoothScrollState};
use super::{
ScrollEvent, ScrollKeymap, ScrollRequest, ScrollTarget, ScrollViewportEvent,
ScrollVisibleChild, ScrollWheelBehavior,
};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub(crate) struct ScrollViewportSnapshot {
pub offset: usize,
pub metrics: super::ScrollMetrics,
pub viewport_width: u16,
pub children_len: usize,
pub first_visible_index: Option<usize>,
pub last_visible_index: Option<usize>,
pub visible: Vec<ScrollVisibleChild>,
pub top_indicator: bool,
pub bottom_indicator: bool,
pub bottom_count: usize,
}
#[derive(Clone, Debug)]
pub struct RememberedScrollAnchor {
pub top_child_key: Option<Key>,
pub top_child_index: usize,
pub top_delta: i32,
pub old_offset: usize,
pub center_child_key: Option<Key>,
pub center_child_index: usize,
pub anchor_numerator: u32,
pub anchor_denominator: u32,
pub pinned_top: bool,
pub pinned_bottom: bool,
}
#[derive(Clone)]
pub struct ScrollViewNode {
pub props: StackProps,
pub element_offset: Option<usize>,
pub element_scroll_request: Option<ScrollRequest>,
pub scroll_target: Option<ScrollTarget>,
pub cancelled_scroll_target: Option<ScrollTarget>,
pub offset: usize,
pub smooth_scroll: SmoothScrollState,
pub(crate) wheel_scroll: KineticScrollState,
pub max_offset: usize,
pub scroll_offset: u16,
pub content_height: u16,
pub content_width: u16,
pub viewport_height: u16,
pub viewport_width: u16,
pub axis: ScrollAxis,
pub h_offset: usize,
pub h_max_offset: usize,
pub h_scroll_offset: u16,
pub h_scroll_override: Option<usize>,
pub h_scroll_handler_dirty: bool,
pub content_viewport_w: u16,
pub scroll_keys: ScrollKeymap,
pub scroll_wheel: bool,
pub scroll_wheel_multiplier: Option<u16>,
pub h_scroll_wheel_multiplier: Option<u16>,
pub scroll_wheel_behavior: ScrollWheelBehavior,
pub ambient_page_scroll: bool,
pub focusable: bool,
pub scrollbar: bool,
pub scrollbar_variant: ScrollbarVariant,
pub scrollbar_gap: u16,
pub scrollbar_thumb: Option<char>,
pub scrollbar_thumb_style: Option<Style>,
pub scrollbar_thumb_focus_style: Option<Style>,
pub scrollbar_track_style: Option<Style>,
pub h_scrollbar: bool,
pub h_scrollbar_variant: ScrollbarVariant,
pub h_scrollbar_gap: u16,
pub h_scrollbar_thumb: Option<char>,
pub h_scrollbar_thumb_style: Option<Style>,
pub h_scrollbar_thumb_focus_style: Option<Style>,
pub h_scrollbar_track_style: Option<Style>,
pub show_scroll_indicators: bool,
pub scroll_indicator_style: Style,
pub top_indicator: bool,
pub bottom_indicator: bool,
pub bottom_count: usize,
pub scroll_override: Option<usize>,
pub scroll_handler_dirty: bool,
pub on_scroll: Option<Callback<ScrollEvent>>,
pub on_scroll_to: Option<Callback<usize>>,
pub on_viewport_change: Option<Callback<ScrollViewportEvent>>,
pub viewport_snapshot: Option<ScrollViewportSnapshot>,
pub layout_cache: ScrollViewLayoutCache,
pub virtual_cache: VirtualHeightCache,
pub offscreen_doc_selections: HashMap<Key, OffscreenDocSelection>,
}
#[derive(Clone)]
pub struct OffscreenDocSelection {
pub docs: Vec<SingleDocSelection>,
}
#[derive(Clone)]
pub struct SingleDocSelection {
pub selection_cursor: usize,
pub selection_anchor: Option<usize>,
pub table_rect_selection: Option<DocumentTableRectSelection>,
pub format_cache: FormatCache,
pub visual_cache: VisualCache,
pub flat_text: Arc<str>,
pub shared_selection_id: Option<Arc<str>>,
}
#[derive(Clone, Debug)]
pub struct HeightCacheItem {
pub natural_w: u16,
pub h: u16,
pub flex_w: bool,
pub percent_w: Option<u16>,
pub gap_after: bool,
pub is_portal: bool,
}
#[derive(Clone, Debug)]
pub struct ScrollViewHeightCache {
pub content_hash: u64,
pub width_sensitive: bool,
pub items: Vec<HeightCacheItem>,
}
#[derive(Clone, Debug, Default)]
pub struct ScrollViewLayoutCache {
entries: Vec<ScrollViewLayoutCacheEntry>,
pub height_cache: Option<ScrollViewHeightCache>,
pub active_content_hash: Option<u64>,
}
#[derive(Clone, Debug)]
pub struct ScrollViewLayoutCacheEntry {
pub viewport_w: u16,
pub layout_hash: u64,
pub rects: Vec<Rect>,
pub content_height: u16,
}
impl ScrollViewLayoutCache {
pub fn get(
&self,
viewport_w: u16,
layout_hash: u64,
children_len: usize,
) -> Option<(Vec<Rect>, u16)> {
self.entries
.iter()
.find(|entry| {
entry.viewport_w == viewport_w
&& entry.layout_hash == layout_hash
&& entry.rects.len() == children_len
})
.map(|entry| (entry.rects.clone(), entry.content_height))
}
pub fn insert(
&mut self,
viewport_w: u16,
layout_hash: u64,
rects: Vec<Rect>,
content_height: u16,
) {
if let Some(entry) = self
.entries
.iter_mut()
.find(|entry| entry.viewport_w == viewport_w && entry.layout_hash == layout_hash)
{
entry.rects = rects;
entry.content_height = content_height;
return;
}
self.entries.push(ScrollViewLayoutCacheEntry {
viewport_w,
layout_hash,
rects,
content_height,
});
if self.entries.len() > 2 {
self.entries.remove(0);
}
}
pub fn store_heights(
&mut self,
content_hash: u64,
width_sensitive: bool,
items: Vec<HeightCacheItem>,
) {
self.height_cache = Some(ScrollViewHeightCache {
content_hash,
width_sensitive,
items,
});
}
pub fn invalidate(&mut self) {
self.entries.clear();
self.height_cache = None;
self.active_content_hash = None;
}
}
#[derive(Clone, Debug)]
pub struct VirtualChildEntry {
pub layout_hash: u64,
pub key: Option<Key>,
pub h: u16,
pub w: u16,
pub x: i16,
pub flex_w: bool,
pub percent_w: Option<u16>,
pub gap_after: bool,
pub is_portal: bool,
pub stale: bool,
}
#[derive(Clone, Debug, Default)]
pub struct VirtualHeightCache {
pub entries: Vec<Option<VirtualChildEntry>>,
pub viewport_w: u16,
pub width_sensitive: bool,
pub total_measured_height: u64,
pub measured_count: u32,
}
impl VirtualHeightCache {
pub fn estimated_height(&self, fallback: u16) -> u16 {
if self.measured_count == 0 {
fallback
} else {
(self.total_measured_height / self.measured_count as u64) as u16
}
}
pub fn has_unresolved_in_zone(
&self,
offset: usize,
viewport_h: u16,
gap: u16,
estimate: u16,
children_len: usize,
) -> bool {
if self.entries.len() != children_len {
return true;
}
let zone_start = offset.saturating_sub(viewport_h as usize);
let zone_end = offset.saturating_add((viewport_h as usize).saturating_mul(2));
let mut y = 0usize;
for entry in &self.entries {
let (h, has_gap, unresolved) = match entry {
Some(entry) => (entry.h, entry.gap_after, entry.stale),
None => (estimate, true, true),
};
let row_start = y;
let row_end = y.saturating_add(h as usize);
if unresolved && row_end >= zone_start && row_start <= zone_end {
return true;
}
y = row_end;
if has_gap {
y = y.saturating_add(gap as usize);
}
}
false
}
pub fn estimated_width(&self) -> u16 {
let (sum, count) = self
.entries
.iter()
.flatten()
.filter(|e| !e.is_portal && e.w > 0)
.fold((0u64, 0u32), |(s, c), e| (s + e.w as u64, c + 1));
if count == 0 {
0
} else {
(sum / count as u64) as u16
}
}
pub fn record_measurement(&mut self, h: u16) {
self.total_measured_height += h as u64;
self.measured_count += 1;
}
pub fn unrecord_measurement(&mut self, h: u16) {
self.total_measured_height = self.total_measured_height.saturating_sub(h as u64);
self.measured_count = self.measured_count.saturating_sub(1);
}
pub fn reset(&mut self) {
self.entries.clear();
self.total_measured_height = 0;
self.measured_count = 0;
}
}
impl WidgetNode for ScrollViewNode {
fn is_focusable(&self) -> bool {
self.focusable
}
fn has_on_click(&self) -> bool {
self.on_scroll.is_some()
|| self.on_scroll_to.is_some()
|| self.scrollbar
|| self.scroll_wheel
}
fn scrollbar_zones(
&self,
id: crate::core::node::NodeId,
rect: Rect,
parent_border_x: Option<i16>,
parent_border_y: Option<i16>,
) -> Vec<ScrollbarZone> {
if !self.scrollbar && !self.h_scrollbar {
return Vec::new();
}
let inner = rect.inner(self.props.border, self.props.padding);
if inner.w == 0 || inner.h == 0 {
return Vec::new();
}
let use_standalone_v =
self.scrollbar && matches!(self.scrollbar_variant, ScrollbarVariant::Standalone);
let content_w = if use_standalone_v {
inner
.w
.saturating_sub(1u16.saturating_add(self.scrollbar_gap))
} else {
inner.w
};
compute_scrollbar_zones(ScrollbarZonesParams {
id,
rect,
inner,
border: self.props.border,
scrollbar: self.scrollbar,
scrollbar_variant: self.scrollbar_variant,
scrollbar_gap: self.scrollbar_gap,
h_scrollbar: self.h_scrollbar && self.axis.horizontal_enabled(),
h_scrollbar_variant: self.h_scrollbar_variant,
content_x: inner.x,
content_width: content_w,
max_content_width: self.content_width as usize,
wrap: false,
parent_border_x,
parent_border_y,
})
}
}