use crate::{
messenger::MessageData,
widget::{
component::interactive::navigation::{use_nav_scroll_view, NavJump, NavScroll, NavSignal},
context::WidgetMountOrChangeContext,
utils::Vec2,
WidgetId, WidgetIdOrRef,
},
widget_hook,
};
use serde::{Deserialize, Serialize};
fn is_zero(v: &Vec2) -> bool {
v.x.abs() < 1.0e-6 && v.y.abs() < 1.0e-6
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ScrollViewState {
#[serde(default)]
pub value: Vec2,
#[serde(default)]
pub size_factor: Vec2,
}
implement_props_data!(ScrollViewState);
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScrollViewRange {
#[serde(default)]
#[serde(skip_serializing_if = "is_zero")]
pub from: Vec2,
#[serde(default)]
#[serde(skip_serializing_if = "is_zero")]
pub to: Vec2,
}
implement_props_data!(ScrollViewRange);
impl Default for ScrollViewRange {
fn default() -> Self {
Self {
from: Vec2 { x: 0.0, y: 0.0 },
to: Vec2 { x: 1.0, y: 1.0 },
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ScrollViewNotifyProps(
#[serde(default)]
#[serde(skip_serializing_if = "WidgetIdOrRef::is_none")]
pub WidgetIdOrRef,
);
implement_props_data!(ScrollViewNotifyProps);
#[derive(Debug, Clone)]
pub struct ScrollViewNotifyMessage {
pub sender: WidgetId,
pub state: ScrollViewState,
}
implement_message_data!(ScrollViewNotifyMessage);
widget_hook! {
pub use_scroll_view_notified_state(life_cycle) {
life_cycle.change(|context| {
for msg in context.messenger.messages {
if let Some(msg) = msg.as_any().downcast_ref::<ScrollViewNotifyMessage>() {
drop(context.state.write_with(msg.state.clone()));
}
}
});
}
}
widget_hook! {
pub use_scroll_view(life_cycle) [use_nav_scroll_view] {
fn notify<T>(context: &WidgetMountOrChangeContext, data: T)
where
T: 'static + MessageData,
{
if let Ok(notify) = context.props.read::<ScrollViewNotifyProps>() {
if let Some(to) = notify.0.read() {
context.messenger.write(to, data);
}
}
}
life_cycle.mount(|context| {
notify(&context, ScrollViewNotifyMessage {
sender: context.id.to_owned(),
state: ScrollViewState::default(),
});
drop(context.state.write_with(ScrollViewState::default()));
});
life_cycle.change(|context| {
let mut data = context.state.read_cloned_or_default::<ScrollViewState>();
let range = context.props.read::<ScrollViewRange>();
let mut dirty = false;
for msg in context.messenger.messages {
if let Some(
NavSignal::Jump(NavJump::Scroll(NavScroll::Change(value, factor, relative)))
) = msg.as_any().downcast_ref() {
if *relative {
data.value.x += value.x;
data.value.y += value.y;
} else {
data.value = *value;
}
if let Ok(range) = &range {
data.value.x = data.value.x.max(range.from.x).min(range.to.x);
data.value.y = data.value.y.max(range.from.y).min(range.to.y);
}
data.size_factor = *factor;
dirty = true;
}
}
if dirty {
notify(&context, ScrollViewNotifyMessage {
sender: context.id.to_owned(),
state: data.clone(),
});
drop(context.state.write_with(data));
}
});
}
}