use crate::mouse::{FullElementCursorState, MouseButtonManager, MouseClickingStrategy};
use cotis::cotis_app::CotisApp;
use cotis::layout::LayoutManagerCompatible;
use cotis::renders::RenderCompatibleWith;
use cotis::utils::ElementId;
use cotis_utils::element_state::{ElementBoundingBox, ElementClipInternalSize};
use cotis_utils::interactivity::mouse::{MouseButton, MouseProvider};
use cotis_utils::math::{BoundingBox, Vector2};
pub enum ScrollAlgorithm {
Momentum(MomentumScroller),
HardScroll(HardScroller),
}
impl ScrollAlgorithm {
pub fn get_position(&self) -> f32 {
match self {
ScrollAlgorithm::Momentum(momentum) => momentum.get_position(),
ScrollAlgorithm::HardScroll(hard) => hard.get_position(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct MomentumScroller {
pub position: f32,
pub scale: f32,
pub velocity: f32,
pub friction: f32,
pub min_velocity: f32,
}
impl MomentumScroller {
pub fn new(scale: f32, friction: f32, min_velocity: f32) -> Self {
assert!(friction > 0.0 && friction < 1.0);
assert!(min_velocity > 0.0);
Self {
position: 0.0,
scale,
velocity: 0.0,
friction: 1.0 - friction,
min_velocity,
}
}
pub fn direct_set_position(&mut self, position: f32) {
self.position = position;
self.velocity = 0.0;
}
pub fn update(&mut self, d_scroll: f32, dt: f32) {
if d_scroll != 0.0 {
self.position += d_scroll * self.scale;
if dt > 0.0 {
self.velocity = d_scroll * self.scale / dt;
}
} else {
self.position += self.velocity * dt;
let friction_per_step = self.friction.powf(dt * 60.0);
self.velocity *= friction_per_step;
if self.velocity.abs() < self.min_velocity {
self.velocity = 0.0;
}
}
}
pub fn get_position(&self) -> f32 {
self.position
}
}
#[derive(Debug, Clone, Copy)]
pub struct HardScroller {
pub position: f32,
pub scale: f32,
}
impl HardScroller {
pub fn new(scale: f32) -> Self {
Self {
position: 0.0,
scale,
}
}
pub fn direct_set_position(&mut self, position: f32) {
self.position = position;
}
pub fn update(&mut self, d_scroll: f32, _dt: f32) {
self.position += d_scroll * self.scale;
}
pub fn get_position(&self) -> f32 {
self.position
}
}
pub struct DoubleAxisScrollManager {
scroll: [ScrollAlgorithm; 2],
}
impl DoubleAxisScrollManager {
pub fn new(scroll: [ScrollAlgorithm; 2]) -> Self {
Self { scroll }
}
pub fn direct_set_position(&mut self, position: Vector2) {
match &mut self.scroll[0] {
ScrollAlgorithm::Momentum(m) => {
m.direct_set_position(position.x);
}
ScrollAlgorithm::HardScroll(h) => {
h.direct_set_position(position.x);
}
}
match &mut self.scroll[1] {
ScrollAlgorithm::Momentum(m) => {
m.direct_set_position(position.y);
}
ScrollAlgorithm::HardScroll(h) => {
h.direct_set_position(position.y);
}
}
}
pub fn update(&mut self, d_scroll_x: f32, d_scroll_y: f32, dt: f32) {
match &mut self.scroll[0] {
ScrollAlgorithm::Momentum(m) => {
m.update(d_scroll_x, dt);
}
ScrollAlgorithm::HardScroll(h) => {
h.update(d_scroll_x, dt);
}
}
match &mut self.scroll[1] {
ScrollAlgorithm::Momentum(m) => {
m.update(d_scroll_y, dt);
}
ScrollAlgorithm::HardScroll(h) => {
h.update(d_scroll_y, dt);
}
}
}
pub fn get_position(&self) -> Vector2 {
Vector2 {
x: self.scroll[0].get_position(),
y: self.scroll[1].get_position(),
}
}
}
pub enum SingleAxisScrollManager {
Horizontal(ScrollAlgorithm),
Vertical(ScrollAlgorithm),
}
impl SingleAxisScrollManager {
pub fn direct_set_position(&mut self, position: f32) {
match self {
SingleAxisScrollManager::Horizontal(ScrollAlgorithm::Momentum(m)) => {
m.direct_set_position(position);
}
SingleAxisScrollManager::Horizontal(ScrollAlgorithm::HardScroll(h)) => {
h.direct_set_position(position);
}
SingleAxisScrollManager::Vertical(ScrollAlgorithm::Momentum(m)) => {
m.direct_set_position(position);
}
SingleAxisScrollManager::Vertical(ScrollAlgorithm::HardScroll(h)) => {
h.direct_set_position(position);
}
}
}
pub fn update(&mut self, d_scroll: f32, dt: f32) {
match self {
SingleAxisScrollManager::Horizontal(ScrollAlgorithm::Momentum(m)) => {
m.update(d_scroll, dt);
}
SingleAxisScrollManager::Horizontal(ScrollAlgorithm::HardScroll(h)) => {
h.update(d_scroll, dt);
}
SingleAxisScrollManager::Vertical(ScrollAlgorithm::Momentum(m)) => {
m.update(d_scroll, dt);
}
SingleAxisScrollManager::Vertical(ScrollAlgorithm::HardScroll(h)) => {
h.update(d_scroll, dt);
}
}
}
pub fn get_position(&self) -> Vector2 {
match self {
SingleAxisScrollManager::Horizontal(s) => Vector2 {
x: s.get_position(),
y: 0.0,
},
SingleAxisScrollManager::Vertical(s) => Vector2 {
x: 0.0,
y: s.get_position(),
},
}
}
}
pub trait FullElementScrollState: FullElementCursorState {
fn get_mouse_pos(&self) -> Vector2;
fn get_mouse_wheel_move_v(&self) -> Vector2;
fn get_bound_box(&self, id: ElementId) -> Option<BoundingBox>;
fn internal_size(&self, id: ElementId) -> Option<Vector2>;
}
impl<
Renderer: RenderCompatibleWith<Manager> + MouseProvider,
Manager: LayoutManagerCompatible<Renderer> + ElementBoundingBox + ElementClipInternalSize,
Pipe,
> FullElementScrollState for CotisApp<Renderer, Manager, Pipe>
{
fn get_mouse_pos(&self) -> Vector2 {
self.render_borrow().get_mouse_position()
}
fn get_mouse_wheel_move_v(&self) -> Vector2 {
self.render_borrow().get_mouse_wheel_move_v()
}
fn get_bound_box(&self, id: ElementId) -> Option<BoundingBox> {
self.layout_manager_borrow().bounding_box(id)
}
fn internal_size(&self, id: ElementId) -> Option<Vector2> {
self.layout_manager_borrow().clip_internal_size(id)
}
}
pub struct DoubleAxisScrollElementManager {
id: ElementId,
exclusive_mouse: MouseButtonManager,
mouse_prev_pos: Option<Vector2>,
manager: DoubleAxisScrollManager,
button: Option<MouseButton>,
child_elements: Vec<ElementId>,
}
impl DoubleAxisScrollElementManager {
pub fn new(
algorithms: [ScrollAlgorithm; 2],
id: ElementId,
child_elements: Vec<ElementId>,
button: Option<MouseButton>,
) -> Self {
Self {
id,
exclusive_mouse: MouseButtonManager::new(MouseClickingStrategy::Pressed, 0.0),
mouse_prev_pos: None,
manager: DoubleAxisScrollManager::new(algorithms),
button,
child_elements,
}
}
fn update_mouse(&mut self, provider: &dyn FullElementScrollState, dt: f32) {
let mouse_button = {
if let Some(button) = self.button {
button
} else {
return;
}
};
let mut hovered = provider.is_hovered(self.id);
if hovered && !self.exclusive_mouse.is_clicked() {
for child in self.child_elements.iter() {
if provider.is_hovered(*child) {
hovered = false;
break;
}
}
}
self.exclusive_mouse
.update(provider.mouse_button_down(mouse_button), dt, hovered);
}
fn get_mouse_drag_delta(
&mut self,
provider: &dyn FullElementScrollState,
dt: f32,
) -> Option<Vector2> {
self.update_mouse(provider, dt);
let clicked = self.exclusive_mouse.is_clicked();
if !clicked {
self.mouse_prev_pos = None;
return None;
}
let mouse_pos = provider.get_mouse_pos();
let res = self.mouse_prev_pos.map(|mouse_prev_pos| {
Vector2::new(
mouse_pos.x - mouse_prev_pos.x,
mouse_pos.y - mouse_prev_pos.y,
)
});
self.mouse_prev_pos = Some(mouse_pos);
res
}
pub fn update(&mut self, provider: &dyn FullElementScrollState, dt: f32) {
let wheel_scroll = provider.get_mouse_wheel_move_v();
let mouse_scroll = self.get_mouse_drag_delta(provider, dt);
let internal = provider.internal_size(self.id);
let bound = provider.get_bound_box(self.id);
if internal.is_none() || bound.is_none() {
return;
}
let internal = internal.unwrap();
let bound = bound.unwrap();
let limits = Vector2 {
x: (internal.x - bound.width).max(0.),
y: (internal.y - bound.height).max(0.),
};
let scroll = if let Some(mouse_scroll) = mouse_scroll {
Vector2 {
x: wheel_scroll.x + mouse_scroll.x,
y: wheel_scroll.y + mouse_scroll.y,
}
} else {
wheel_scroll
};
self.manager.update(scroll.x, scroll.y, dt);
let pos = self.manager.get_position();
if pos.x < 0. || pos.x > -limits.x || pos.y < 0. || pos.y > -limits.y {
self.manager.direct_set_position(Vector2::new(
pos.x.min(0.0).max(-limits.x),
pos.y.min(0.0).max(-limits.y),
));
}
}
pub fn get_position(&self) -> Vector2 {
self.manager.get_position()
}
}
pub struct SingleAxisScrollElementManager {
id: ElementId,
exclusive_mouse: MouseButtonManager,
mouse_prev_pos: Option<Vector2>,
manager: SingleAxisScrollManager,
button: Option<MouseButton>,
child_elements: Vec<ElementId>,
}
impl SingleAxisScrollElementManager {
pub fn new(
manager: SingleAxisScrollManager,
id: ElementId,
child_elements: Vec<ElementId>,
button: Option<MouseButton>,
) -> Self {
Self {
id,
exclusive_mouse: MouseButtonManager::new(MouseClickingStrategy::Pressed, 0.0),
mouse_prev_pos: None,
manager,
button,
child_elements,
}
}
fn update_mouse(&mut self, provider: &dyn FullElementScrollState, dt: f32) {
let mouse_button = {
if let Some(button) = self.button {
button
} else {
return;
}
};
let mut hovered = provider.is_hovered(self.id);
if hovered && !self.exclusive_mouse.is_clicked() {
for child in self.child_elements.iter() {
if provider.is_hovered(*child) {
hovered = false;
break;
}
}
}
self.exclusive_mouse
.update(provider.mouse_button_down(mouse_button), dt, hovered);
}
fn get_mouse_drag_delta(
&mut self,
provider: &dyn FullElementScrollState,
dt: f32,
) -> Option<Vector2> {
self.update_mouse(provider, dt);
let clicked = self.exclusive_mouse.is_clicked();
if !clicked {
self.mouse_prev_pos = None;
return None;
}
let mouse_pos = provider.get_mouse_pos();
let res = self.mouse_prev_pos.map(|mouse_prev_pos| {
Vector2::new(
mouse_pos.x - mouse_prev_pos.x,
mouse_pos.y - mouse_prev_pos.y,
)
});
self.mouse_prev_pos = Some(mouse_pos);
res
}
pub fn update(&mut self, provider: &dyn FullElementScrollState, dt: f32) {
let wheel_scroll = provider.get_mouse_wheel_move_v();
let mouse_scroll = self.get_mouse_drag_delta(provider, dt);
let internal = provider.internal_size(self.id);
let bound = provider.get_bound_box(self.id);
if internal.is_none() || bound.is_none() {
return;
}
let internal = internal.unwrap();
let bound = bound.unwrap();
let limits = Vector2 {
x: (internal.x - bound.width).max(0.),
y: (internal.y - bound.height).max(0.),
};
let scroll = if let Some(mouse_scroll) = mouse_scroll {
Vector2 {
x: wheel_scroll.x + mouse_scroll.x,
y: wheel_scroll.y + mouse_scroll.y,
}
} else {
wheel_scroll
};
self.manager.update(
match &self.manager {
SingleAxisScrollManager::Horizontal(_) => scroll.x,
SingleAxisScrollManager::Vertical(_) => scroll.y,
},
dt,
);
let pos = self.manager.get_position();
if pos.x < 0. || pos.x > -limits.x || pos.y < 0. || pos.y > -limits.y {
self.manager.direct_set_position(match &self.manager {
SingleAxisScrollManager::Horizontal(_) => pos.x.min(0.0).max(-limits.x),
SingleAxisScrollManager::Vertical(_) => pos.y.min(0.0).max(-limits.y),
});
}
}
pub fn get_position(&self) -> Vector2 {
self.manager.get_position()
}
}