use std::cell::RefCell;
use std::rc::Rc;
use web_sys::HtmlDivElement;
use yew::prelude::*;
use yew_hooks::*;
use crate::hooks::*;
use crate::utils::*;
use crate::ScrollOption;
#[derive(Clone)]
pub struct UseHorizontalStateHandle {
scroll_position_state: UseSmoothDampHandle,
thumb_position_state: UseSmoothDampHandle,
thumb_size_state: UseHorizontalThumbSizeHandle,
is_thumb_dragging: Rc<RefCell<bool>>,
drag_start_mouse_position: Rc<RefCell<f64>>,
drag_start_thumb_position: Rc<RefCell<f64>>,
is_touch_scrolling: Rc<RefCell<bool>>,
touch_start_touch_position: Rc<RefCell<f64>>,
touch_start_scroll_position: Rc<RefCell<f64>>,
is_inner_scrolling: Rc<RefCell<bool>>,
scroll_option: ScrollOption,
outer_ref: NodeRef,
inner_ref: NodeRef,
thumb_ref: NodeRef,
}
impl UseHorizontalStateHandle {
fn new(
scroll_position_state: UseSmoothDampHandle,
thumb_position_state: UseSmoothDampHandle,
thumb_size_state: UseHorizontalThumbSizeHandle,
is_thumb_dragging: Rc<RefCell<bool>>,
drag_start_mouse_position: Rc<RefCell<f64>>,
drag_start_thumb_position: Rc<RefCell<f64>>,
is_touch_scrolling: Rc<RefCell<bool>>,
touch_start_touch_position: Rc<RefCell<f64>>,
touch_start_scroll_position: Rc<RefCell<f64>>,
is_inner_scrolling: Rc<RefCell<bool>>,
smooth_option: ScrollOption,
outer_ref: NodeRef,
inner_ref: NodeRef,
thumb_ref: NodeRef,
) -> Self {
Self {
scroll_position_state,
thumb_position_state,
thumb_size_state,
is_thumb_dragging,
drag_start_mouse_position,
drag_start_thumb_position,
is_touch_scrolling,
touch_start_touch_position,
touch_start_scroll_position,
is_inner_scrolling,
scroll_option: smooth_option,
outer_ref,
inner_ref,
thumb_ref,
}
}
fn use_on_wheel(&self) {
use_event(self.outer_ref.clone(), "wheel", {
let this = self.clone();
move |evt: WheelEvent| {
evt.prevent_default();
if let (Some(outer), Some(inner)) = (
this.outer_ref.cast::<HtmlDivElement>(),
this.inner_ref.cast::<HtmlDivElement>(),
) {
let already_processed = get_event_custom_flag(
&evt,
"yew-scroll-area-horizontal-horizontal-processed",
);
if let Some(flag) = already_processed {
if flag {
return;
}
}
let delta = evt.delta_x();
let delta = delta * this.scroll_option.mouse_wheel_speed_scale;
let outer_width = outer.offset_width() as f64;
let inner_width = inner.offset_width() as f64;
let scrollable_width = (inner_width - outer_width).max(0.0);
let scroll_translation =
(this.scroll_position_state.target() - delta).clamp(-scrollable_width, 0.0);
let thumb_translation = if scrollable_width == 0.0 {
0.0
} else {
-scroll_translation / scrollable_width
* (outer_width - *this.thumb_size_state)
};
this.scroll_position_state
.set_smooth_time(this.scroll_option.mouse_wheel_smooth_time);
this.thumb_position_state
.set_smooth_time(this.scroll_option.mouse_wheel_smooth_time);
this.scroll_position_state.set_target(scroll_translation);
this.thumb_position_state.set_target(thumb_translation);
let processed = !((delta < 0.0 && scroll_translation == 0.0)
|| (delta > 0.0 && scroll_translation == -scrollable_width));
set_event_custom_flag(
&evt,
"yew-scroll-area-horizontal-wheel-processed",
processed,
);
}
}
});
}
fn use_on_start_drag_thumb(&self) {
use_event(self.thumb_ref.clone(), "mousedown", {
let this = self.clone();
move |evt: MouseEvent| {
if this.thumb_ref.get().is_some() {
let mouse_position = evt.client_x() as f64;
*this.is_thumb_dragging.borrow_mut() = true;
*this.drag_start_mouse_position.borrow_mut() = mouse_position;
*this.drag_start_thumb_position.borrow_mut() = *this.thumb_position_state;
}
}
});
use_event(self.thumb_ref.clone(), "touchstart", {
let this = self.clone();
move |evt: TouchEvent| {
if this.thumb_ref.get().is_some() {
if let Some(touch) = evt.touches().item(0) {
let touch_position = touch.client_x() as f64;
*this.is_thumb_dragging.borrow_mut() = true;
*this.drag_start_mouse_position.borrow_mut() = touch_position;
*this.drag_start_thumb_position.borrow_mut() = *this.thumb_position_state;
}
}
}
});
}
fn use_on_drag_thumb(&self) {
use_event_with_window("mousemove", {
let this = self.clone();
move |evt: MouseEvent| {
if *this.is_thumb_dragging.borrow() {
evt.prevent_default();
if let (Some(outer), Some(inner)) = (
this.outer_ref.cast::<HtmlDivElement>(),
this.inner_ref.cast::<HtmlDivElement>(),
) {
let mouse_position = evt.client_x() as f64;
let x = mouse_position - *this.drag_start_mouse_position.borrow();
let outer_width = outer.offset_width() as f64;
let inner_width = inner.offset_width() as f64;
let scrollable_width = (inner_width - outer_width).max(0.0);
let thumb_translation = (*this.drag_start_thumb_position.borrow() + x)
.clamp(0.0, (outer_width - *this.thumb_size_state).max(0.0));
let scroll_translation = -thumb_translation
/ (outer_width - *this.thumb_size_state)
* scrollable_width;
this.scroll_position_state
.set_smooth_time(this.scroll_option.mouse_drag_thumb_smooth_time);
this.thumb_position_state
.set_smooth_time(this.scroll_option.mouse_drag_thumb_smooth_time);
this.scroll_position_state.set_target(scroll_translation);
this.thumb_position_state.set_target(thumb_translation);
}
}
}
});
use_event_with_window("touchmove", {
let this = self.clone();
move |evt: TouchEvent| {
if *this.is_thumb_dragging.borrow() {
if let (Some(outer), Some(inner)) = (
this.outer_ref.cast::<HtmlDivElement>(),
this.inner_ref.cast::<HtmlDivElement>(),
) {
if let Some(touch) = evt.touches().item(0) {
let touch_position = touch.client_x() as f64;
let x = touch_position - *this.drag_start_mouse_position.borrow();
let outer_width = outer.offset_width() as f64;
let inner_width = inner.offset_width() as f64;
let scrollable_width = (inner_width - outer_width).max(0.0);
let thumb_translation = (*this.drag_start_thumb_position.borrow() + x)
.clamp(0.0, (outer_width - *this.thumb_size_state).max(0.0));
let scroll_translation = -thumb_translation
/ (outer_width - *this.thumb_size_state)
* scrollable_width;
this.scroll_position_state
.set_smooth_time(this.scroll_option.touch_drag_thumb_smooth_time);
this.thumb_position_state
.set_smooth_time(this.scroll_option.touch_drag_thumb_smooth_time);
this.scroll_position_state.set_target(scroll_translation);
this.thumb_position_state.set_target(thumb_translation);
set_event_custom_flag(
&evt,
"yew-scroll-area-horizontal-touchmove-processed",
true,
);
}
}
}
}
});
}
fn use_on_end_drag_thumb(&self) {
use_event_with_window("mouseup", {
let this = self.clone();
move |_: Event| {
*this.is_thumb_dragging.borrow_mut() = false;
}
});
use_event_with_window("touchend", {
let this = self.clone();
move |_: Event| {
*this.is_thumb_dragging.borrow_mut() = false;
}
});
}
fn use_on_touch_start(&self) {
use_event(self.outer_ref.clone(), "touchstart", {
let this = self.clone();
move |evt: TouchEvent| {
if let Some(touch) = evt.touches().item(0) {
let touch_position = touch.client_x() as f64;
*this.is_touch_scrolling.borrow_mut() = true;
*this.touch_start_touch_position.borrow_mut() = touch_position;
*this.touch_start_scroll_position.borrow_mut() = *this.scroll_position_state;
}
}
});
}
fn use_on_touch_move(&self) {
use_event(self.outer_ref.clone(), "touchmove", {
let this = self.clone();
move |evt: TouchEvent| {
if let Some(touch) = evt.touches().item(0) {
let touch_position = touch.client_x() as f64;
if *this.is_touch_scrolling.borrow() {
if let (Some(outer), Some(inner)) = (
this.outer_ref.cast::<HtmlDivElement>(),
this.inner_ref.cast::<HtmlDivElement>(),
) {
if *this.is_thumb_dragging.borrow() {
set_event_custom_flag(
&evt,
"yew-scroll-area-horizontal-touchmove-processed",
true,
);
return;
}
let already_processed = get_event_custom_flag(
&evt,
"yew-scroll-area-horizontal-touchmove-processed",
);
if let Some(flag) = already_processed {
if flag {
*this.is_inner_scrolling.borrow_mut() = true;
return;
} else {
if *this.is_inner_scrolling.borrow() {
*this.touch_start_touch_position.borrow_mut() =
touch_position;
*this.touch_start_scroll_position.borrow_mut() =
*this.scroll_position_state;
*this.is_inner_scrolling.borrow_mut() = false;
}
}
}
let x =
touch_position as f64 - *this.touch_start_touch_position.borrow();
let x = x * this.scroll_option.touch_swipe_speed_scale;
let outer_width = outer.offset_width() as f64;
let inner_width = inner.offset_width() as f64;
let scrollable_width = (inner_width - outer_width).max(0.0);
let scroll_translation = (*this.touch_start_scroll_position.borrow()
+ x)
.clamp(-scrollable_width, 0.0);
let thumb_translation = if scrollable_width != 0.0 {
-scroll_translation / scrollable_width
* (outer_width - *this.thumb_size_state)
} else {
0.0
};
this.scroll_position_state
.set_smooth_time(this.scroll_option.touch_swipe_smooth_time);
this.thumb_position_state
.set_smooth_time(this.scroll_option.touch_swipe_smooth_time);
this.scroll_position_state.set_target(scroll_translation);
this.thumb_position_state.set_target(thumb_translation);
let processed = !((x > 0.0 && scroll_translation == 0.0)
|| (x < 0.0 && scroll_translation == -scrollable_width));
set_event_custom_flag(
&evt,
"yew-scroll-area-horizontal-touchmove-processed",
processed,
);
}
}
}
}
});
}
fn use_on_touch_end(&self) {
use_event_with_window("touchend", {
let this = self.clone();
move |_: Event| {
*this.is_touch_scrolling.borrow_mut() = false;
}
});
}
pub fn update(&self, timestamp: f64) {
self.scroll_position_state.update(timestamp);
self.thumb_position_state.update(timestamp);
self.thumb_size_state.update();
}
pub fn scroll_position(&self) -> f64 {
*self.scroll_position_state
}
pub fn thumb_position(&self) -> f64 {
*self.thumb_position_state
}
pub fn thumb_size(&self) -> f64 {
*self.thumb_size_state
}
}
pub fn use_horizontal_state(
outer_ref: NodeRef,
inner_ref: NodeRef,
thumb_ref: NodeRef,
scroll_option: ScrollOption,
) -> UseHorizontalStateHandle {
let scroll_position_state = use_smooth_damp();
let thumb_position_state = use_smooth_damp();
let thumb_size_state = use_horizontal_thumb_size(outer_ref.clone(), inner_ref.clone());
let is_thumb_dragging = use_mut_ref(|| false);
let drag_start_mouse_position = use_mut_ref(|| 0.0);
let drag_start_thumb_position = use_mut_ref(|| 0.0);
let is_touch_scrolling = use_mut_ref(|| false);
let touch_start_touch_position = use_mut_ref(|| 0.0);
let touch_start_scroll_position = use_mut_ref(|| 0.0);
let is_inner_scrolling = use_mut_ref(|| false);
let handle = UseHorizontalStateHandle::new(
scroll_position_state,
thumb_position_state,
thumb_size_state,
is_thumb_dragging,
drag_start_mouse_position,
drag_start_thumb_position,
is_touch_scrolling,
touch_start_touch_position,
touch_start_scroll_position,
is_inner_scrolling,
scroll_option,
outer_ref,
inner_ref,
thumb_ref,
);
handle.use_on_wheel();
handle.use_on_start_drag_thumb();
handle.use_on_drag_thumb();
handle.use_on_end_drag_thumb();
handle.use_on_touch_start();
handle.use_on_touch_move();
handle.use_on_touch_end();
handle
}