1use crate::mouse::{FullElementCursorState, MouseButtonManager, MouseClickingStrategy};
15use cotis::cotis_app::CotisApp;
16use cotis::layout::LayoutManagerCompatible;
17use cotis::renders::RenderCompatibleWith;
18use cotis::utils::ElementId;
19use cotis_utils::element_state::{ElementBoundingBox, ElementClipInternalSize};
20use cotis_utils::interactivity::mouse::{MouseButton, MouseProvider};
21use cotis_utils::math::{BoundingBox, Vector2};
22
23pub enum ScrollAlgorithm {
25 Momentum(MomentumScroller),
27 HardScroll(HardScroller),
29}
30
31impl ScrollAlgorithm {
32 pub fn get_position(&self) -> f32 {
34 match self {
35 ScrollAlgorithm::Momentum(momentum) => momentum.get_position(),
36 ScrollAlgorithm::HardScroll(hard) => hard.get_position(),
37 }
38 }
39}
40
41#[derive(Debug, Clone, Copy)]
43pub struct MomentumScroller {
44 pub position: f32,
46 pub scale: f32,
48 pub velocity: f32,
50 pub friction: f32,
54 pub min_velocity: f32,
56}
57
58impl MomentumScroller {
59 pub fn new(scale: f32, friction: f32, min_velocity: f32) -> Self {
70 assert!(friction > 0.0 && friction < 1.0);
71 assert!(min_velocity > 0.0);
72 Self {
73 position: 0.0,
74 scale,
75 velocity: 0.0,
76 friction: 1.0 - friction,
77 min_velocity,
78 }
79 }
80
81 pub fn direct_set_position(&mut self, position: f32) {
83 self.position = position;
84 self.velocity = 0.0;
85 }
86
87 pub fn update(&mut self, d_scroll: f32, dt: f32) {
106 if d_scroll != 0.0 {
107 self.position += d_scroll * self.scale;
108 if dt > 0.0 {
109 self.velocity = d_scroll * self.scale / dt;
110 }
111 } else {
112 self.position += self.velocity * dt;
113
114 let friction_per_step = self.friction.powf(dt * 60.0);
116 self.velocity *= friction_per_step;
117
118 if self.velocity.abs() < self.min_velocity {
119 self.velocity = 0.0;
120 }
121 }
122 }
123
124 pub fn get_position(&self) -> f32 {
126 self.position
127 }
128}
129
130#[derive(Debug, Clone, Copy)]
132pub struct HardScroller {
133 pub position: f32,
135 pub scale: f32,
137}
138
139impl HardScroller {
140 pub fn new(scale: f32) -> Self {
142 Self {
143 position: 0.0,
144 scale,
145 }
146 }
147
148 pub fn direct_set_position(&mut self, position: f32) {
150 self.position = position;
151 }
152
153 pub fn update(&mut self, d_scroll: f32, _dt: f32) {
165 self.position += d_scroll * self.scale;
166 }
167
168 pub fn get_position(&self) -> f32 {
170 self.position
171 }
172}
173
174pub struct DoubleAxisScrollManager {
176 scroll: [ScrollAlgorithm; 2],
177}
178
179impl DoubleAxisScrollManager {
180 pub fn new(scroll: [ScrollAlgorithm; 2]) -> Self {
184 Self { scroll }
185 }
186
187 pub fn direct_set_position(&mut self, position: Vector2) {
189 match &mut self.scroll[0] {
190 ScrollAlgorithm::Momentum(m) => {
191 m.direct_set_position(position.x);
192 }
193 ScrollAlgorithm::HardScroll(h) => {
194 h.direct_set_position(position.x);
195 }
196 }
197 match &mut self.scroll[1] {
198 ScrollAlgorithm::Momentum(m) => {
199 m.direct_set_position(position.y);
200 }
201 ScrollAlgorithm::HardScroll(h) => {
202 h.direct_set_position(position.y);
203 }
204 }
205 }
206
207 pub fn update(&mut self, d_scroll_x: f32, d_scroll_y: f32, dt: f32) {
209 match &mut self.scroll[0] {
210 ScrollAlgorithm::Momentum(m) => {
211 m.update(d_scroll_x, dt);
212 }
213 ScrollAlgorithm::HardScroll(h) => {
214 h.update(d_scroll_x, dt);
215 }
216 }
217 match &mut self.scroll[1] {
218 ScrollAlgorithm::Momentum(m) => {
219 m.update(d_scroll_y, dt);
220 }
221 ScrollAlgorithm::HardScroll(h) => {
222 h.update(d_scroll_y, dt);
223 }
224 }
225 }
226
227 pub fn get_position(&self) -> Vector2 {
229 Vector2 {
230 x: self.scroll[0].get_position(),
231 y: self.scroll[1].get_position(),
232 }
233 }
234}
235
236pub enum SingleAxisScrollManager {
238 Horizontal(ScrollAlgorithm),
240 Vertical(ScrollAlgorithm),
242}
243
244impl SingleAxisScrollManager {
245 pub fn direct_set_position(&mut self, position: f32) {
247 match self {
248 SingleAxisScrollManager::Horizontal(ScrollAlgorithm::Momentum(m)) => {
249 m.direct_set_position(position);
250 }
251 SingleAxisScrollManager::Horizontal(ScrollAlgorithm::HardScroll(h)) => {
252 h.direct_set_position(position);
253 }
254 SingleAxisScrollManager::Vertical(ScrollAlgorithm::Momentum(m)) => {
255 m.direct_set_position(position);
256 }
257 SingleAxisScrollManager::Vertical(ScrollAlgorithm::HardScroll(h)) => {
258 h.direct_set_position(position);
259 }
260 }
261 }
262
263 pub fn update(&mut self, d_scroll: f32, dt: f32) {
265 match self {
266 SingleAxisScrollManager::Horizontal(ScrollAlgorithm::Momentum(m)) => {
267 m.update(d_scroll, dt);
268 }
269 SingleAxisScrollManager::Horizontal(ScrollAlgorithm::HardScroll(h)) => {
270 h.update(d_scroll, dt);
271 }
272 SingleAxisScrollManager::Vertical(ScrollAlgorithm::Momentum(m)) => {
273 m.update(d_scroll, dt);
274 }
275 SingleAxisScrollManager::Vertical(ScrollAlgorithm::HardScroll(h)) => {
276 h.update(d_scroll, dt);
277 }
278 }
279 }
280
281 pub fn get_position(&self) -> Vector2 {
283 match self {
284 SingleAxisScrollManager::Horizontal(s) => Vector2 {
285 x: s.get_position(),
286 y: 0.0,
287 },
288 SingleAxisScrollManager::Vertical(s) => Vector2 {
289 x: 0.0,
290 y: s.get_position(),
291 },
292 }
293 }
294}
295
296pub trait FullElementScrollState: FullElementCursorState {
308 fn get_mouse_pos(&self) -> Vector2;
310 fn get_mouse_wheel_move_v(&self) -> Vector2;
312 fn get_bound_box(&self, id: ElementId) -> Option<BoundingBox>;
314 fn internal_size(&self, id: ElementId) -> Option<Vector2>;
316}
317
318impl<
319 Renderer: RenderCompatibleWith<Manager> + MouseProvider,
320 Manager: LayoutManagerCompatible<Renderer> + ElementBoundingBox + ElementClipInternalSize,
321 Pipe,
322> FullElementScrollState for CotisApp<Renderer, Manager, Pipe>
323{
324 fn get_mouse_pos(&self) -> Vector2 {
325 self.render_borrow().get_mouse_position()
326 }
327
328 fn get_mouse_wheel_move_v(&self) -> Vector2 {
329 self.render_borrow().get_mouse_wheel_move_v()
330 }
331
332 fn get_bound_box(&self, id: ElementId) -> Option<BoundingBox> {
333 self.layout_manager_borrow().bounding_box(id)
334 }
335
336 fn internal_size(&self, id: ElementId) -> Option<Vector2> {
337 self.layout_manager_borrow().clip_internal_size(id)
338 }
339}
340
341pub struct DoubleAxisScrollElementManager {
353 id: ElementId,
354 exclusive_mouse: MouseButtonManager,
355 mouse_prev_pos: Option<Vector2>,
356 manager: DoubleAxisScrollManager,
357 button: Option<MouseButton>,
358 child_elements: Vec<ElementId>,
359}
360
361impl DoubleAxisScrollElementManager {
362 pub fn new(
368 algorithms: [ScrollAlgorithm; 2],
369 id: ElementId,
370 child_elements: Vec<ElementId>,
371 button: Option<MouseButton>,
372 ) -> Self {
373 Self {
374 id,
375 exclusive_mouse: MouseButtonManager::new(MouseClickingStrategy::Pressed, 0.0),
376 mouse_prev_pos: None,
377 manager: DoubleAxisScrollManager::new(algorithms),
378 button,
379 child_elements,
380 }
381 }
382
383 fn update_mouse(&mut self, provider: &dyn FullElementScrollState, dt: f32) {
384 let mouse_button = {
385 if let Some(button) = self.button {
386 button
387 } else {
388 return;
389 }
390 };
391 let mut hovered = provider.is_hovered(self.id);
392 if hovered && !self.exclusive_mouse.is_clicked() {
393 for child in self.child_elements.iter() {
394 if provider.is_hovered(*child) {
395 hovered = false;
396 break;
397 }
398 }
399 }
400 self.exclusive_mouse
401 .update(provider.mouse_button_down(mouse_button), dt, hovered);
402 }
403
404 fn get_mouse_drag_delta(
405 &mut self,
406 provider: &dyn FullElementScrollState,
407 dt: f32,
408 ) -> Option<Vector2> {
409 self.update_mouse(provider, dt);
410 let clicked = self.exclusive_mouse.is_clicked();
411 if !clicked {
412 self.mouse_prev_pos = None;
413 return None;
414 }
415 let mouse_pos = provider.get_mouse_pos();
416
417 let res = self.mouse_prev_pos.map(|mouse_prev_pos| {
418 Vector2::new(
419 mouse_pos.x - mouse_prev_pos.x,
420 mouse_pos.y - mouse_prev_pos.y,
421 )
422 });
423 self.mouse_prev_pos = Some(mouse_pos);
424
425 res
426 }
427
428 pub fn update(&mut self, provider: &dyn FullElementScrollState, dt: f32) {
432 let wheel_scroll = provider.get_mouse_wheel_move_v();
433 let mouse_scroll = self.get_mouse_drag_delta(provider, dt);
434 let internal = provider.internal_size(self.id);
435 let bound = provider.get_bound_box(self.id);
436 if internal.is_none() || bound.is_none() {
437 return;
438 }
439 let internal = internal.unwrap();
440 let bound = bound.unwrap();
441 let limits = Vector2 {
442 x: (internal.x - bound.width).max(0.),
443 y: (internal.y - bound.height).max(0.),
444 };
445
446 let scroll = if let Some(mouse_scroll) = mouse_scroll {
447 Vector2 {
448 x: wheel_scroll.x + mouse_scroll.x,
449 y: wheel_scroll.y + mouse_scroll.y,
450 }
451 } else {
452 wheel_scroll
453 };
454 self.manager.update(scroll.x, scroll.y, dt);
455 let pos = self.manager.get_position();
456 if pos.x < 0. || pos.x > -limits.x || pos.y < 0. || pos.y > -limits.y {
457 self.manager.direct_set_position(Vector2::new(
458 pos.x.min(0.0).max(-limits.x),
459 pos.y.min(0.0).max(-limits.y),
460 ));
461 }
462 }
463
464 pub fn get_position(&self) -> Vector2 {
466 self.manager.get_position()
467 }
468}
469
470pub struct SingleAxisScrollElementManager {
475 id: ElementId,
476 exclusive_mouse: MouseButtonManager,
477 mouse_prev_pos: Option<Vector2>,
478 manager: SingleAxisScrollManager,
479 button: Option<MouseButton>,
480 child_elements: Vec<ElementId>,
481}
482
483impl SingleAxisScrollElementManager {
484 pub fn new(
486 manager: SingleAxisScrollManager,
487 id: ElementId,
488 child_elements: Vec<ElementId>,
489 button: Option<MouseButton>,
490 ) -> Self {
491 Self {
492 id,
493 exclusive_mouse: MouseButtonManager::new(MouseClickingStrategy::Pressed, 0.0),
494 mouse_prev_pos: None,
495 manager,
496 button,
497 child_elements,
498 }
499 }
500
501 fn update_mouse(&mut self, provider: &dyn FullElementScrollState, dt: f32) {
502 let mouse_button = {
503 if let Some(button) = self.button {
504 button
505 } else {
506 return;
507 }
508 };
509 let mut hovered = provider.is_hovered(self.id);
510 if hovered && !self.exclusive_mouse.is_clicked() {
511 for child in self.child_elements.iter() {
512 if provider.is_hovered(*child) {
513 hovered = false;
514 break;
515 }
516 }
517 }
518 self.exclusive_mouse
519 .update(provider.mouse_button_down(mouse_button), dt, hovered);
520 }
521
522 fn get_mouse_drag_delta(
523 &mut self,
524 provider: &dyn FullElementScrollState,
525 dt: f32,
526 ) -> Option<Vector2> {
527 self.update_mouse(provider, dt);
528 let clicked = self.exclusive_mouse.is_clicked();
529 if !clicked {
530 self.mouse_prev_pos = None;
531 return None;
532 }
533 let mouse_pos = provider.get_mouse_pos();
534
535 let res = self.mouse_prev_pos.map(|mouse_prev_pos| {
536 Vector2::new(
537 mouse_pos.x - mouse_prev_pos.x,
538 mouse_pos.y - mouse_prev_pos.y,
539 )
540 });
541 self.mouse_prev_pos = Some(mouse_pos);
542
543 res
544 }
545
546 pub fn update(&mut self, provider: &dyn FullElementScrollState, dt: f32) {
550 let wheel_scroll = provider.get_mouse_wheel_move_v();
551 let mouse_scroll = self.get_mouse_drag_delta(provider, dt);
552 let internal = provider.internal_size(self.id);
553 let bound = provider.get_bound_box(self.id);
554 if internal.is_none() || bound.is_none() {
555 return;
556 }
557 let internal = internal.unwrap();
558 let bound = bound.unwrap();
559 let limits = Vector2 {
560 x: (internal.x - bound.width).max(0.),
561 y: (internal.y - bound.height).max(0.),
562 };
563
564 let scroll = if let Some(mouse_scroll) = mouse_scroll {
565 Vector2 {
566 x: wheel_scroll.x + mouse_scroll.x,
567 y: wheel_scroll.y + mouse_scroll.y,
568 }
569 } else {
570 wheel_scroll
571 };
572
573 self.manager.update(
574 match &self.manager {
575 SingleAxisScrollManager::Horizontal(_) => scroll.x,
576 SingleAxisScrollManager::Vertical(_) => scroll.y,
577 },
578 dt,
579 );
580 let pos = self.manager.get_position();
581
582 if pos.x < 0. || pos.x > -limits.x || pos.y < 0. || pos.y > -limits.y {
583 self.manager.direct_set_position(match &self.manager {
584 SingleAxisScrollManager::Horizontal(_) => pos.x.min(0.0).max(-limits.x),
585 SingleAxisScrollManager::Vertical(_) => pos.y.min(0.0).max(-limits.y),
586 });
587 }
588 }
589
590 pub fn get_position(&self) -> Vector2 {
592 self.manager.get_position()
593 }
594}