1use super::*;
9use crate::cast::traits::*;
10use crate::geom::{Coord, Offset, Rect, Size, Vec2};
11use crate::{ActionMoved, Id};
12use kas_macros::{autoimpl, impl_default};
13use std::time::Instant;
14
15const TIMER_SELECT: TimerHandle = TimerHandle::new(1 << 60, true);
16const TIMER_KINETIC: TimerHandle = TimerHandle::new((1 << 60) + 1, true);
17const KINETIC_RESIDUAL_VEL_REDUCTION_FACTOR: f32 = 0.5;
18
19#[derive(Clone, Debug, Default, PartialEq)]
21pub struct KineticStart {
22 vel: Vec2,
23 rest: Vec2,
24}
25
26#[derive(Clone, Debug)]
28pub struct Kinetic {
29 press: Option<PressSource>,
30 t_step: Instant,
31 vel: Vec2,
32 rest: Vec2,
33}
34
35impl Default for Kinetic {
36 #[inline]
37 fn default() -> Self {
38 let now = Instant::now();
39
40 Kinetic {
41 press: None,
42 t_step: now,
43 vel: Vec2::ZERO,
44 rest: Vec2::ZERO,
45 }
46 }
47}
48
49impl Kinetic {
50 pub fn press_start(&mut self, press: PressSource) {
52 self.press = Some(press);
53 }
54
55 pub fn press_move(&mut self, press: PressSource) -> bool {
59 self.press == Some(press) && self.vel == Vec2::ZERO
60 }
61
62 pub fn press_end(&mut self, press: PressSource, vel: Vec2) -> bool {
67 if self.press != Some(press) {
68 return false;
69 }
70 self.press = None;
71
72 self.vel += vel;
73 if self.vel.distance_l_inf() < 1.0 {
74 self.stop();
75 false
76 } else {
77 self.t_step = Instant::now();
78 true
79 }
80 }
81
82 pub fn start(&mut self, start: KineticStart) -> Offset {
86 self.vel += start.vel;
87 let d = self.rest + start.rest;
88 let delta = Offset::conv_trunc(d);
89 self.rest = d - Vec2::conv(delta);
90 self.t_step = Instant::now();
91 delta
92 }
93
94 pub fn step(&mut self, cx: &EventState) -> Option<Offset> {
100 let evc = cx.config().event();
101 let now = Instant::now();
102 let dur = (now - self.t_step).as_secs_f32();
103 self.t_step = now;
104
105 if let Some(source) = self.press {
106 let decay_sub = evc.kinetic_grab_sub();
107 let v = cx.press_velocity(source).unwrap_or_default();
108 self.vel -= v.abs().min(Vec2::splat(decay_sub * dur)) * -v.sign();
109 }
110
111 let (decay_mul, decay_sub) = evc.kinetic_decay();
112 let v = self.vel * decay_mul.powf(dur);
113 self.vel = v - v.abs().min(Vec2::splat(decay_sub * dur)) * v.sign();
114
115 if self.press.is_none() && self.vel.distance_l_inf() < 1.0 {
116 self.stop();
117 return None;
118 }
119
120 let d = self.vel * dur + self.rest;
121 let delta = Offset::conv_trunc(d);
122 self.rest = d - Vec2::conv(delta);
123
124 Some(delta)
125 }
126
127 #[inline]
129 pub fn stop(&mut self) {
130 self.vel = Vec2::ZERO;
131 }
132
133 pub fn stop_with_residual(&mut self, delta: Offset) -> KineticStart {
137 let mut start = KineticStart::default();
138 if delta.0 != 0 {
139 start.vel.0 = self.vel.0 * KINETIC_RESIDUAL_VEL_REDUCTION_FACTOR;
140 start.rest.0 = self.rest.0 + f32::conv(delta.0);
141 self.vel.0 = 0.0;
142 self.rest.0 = 0.0;
143 }
144 if delta.1 != 0 {
145 start.vel.1 = self.vel.1 * KINETIC_RESIDUAL_VEL_REDUCTION_FACTOR;
146 start.rest.1 = self.rest.1 + f32::conv(delta.1);
147 self.vel.1 = 0.0;
148 self.rest.1 = 0.0;
149 }
150 start
151 }
152
153 #[inline]
155 pub fn is_scrolling(&self) -> bool {
156 self.vel != Vec2::ZERO
157 }
158}
159
160#[derive(Clone, Debug, Default)]
164pub struct ScrollComponent {
165 max_offset: Offset,
166 offset: Offset,
167 kinetic: Kinetic,
168}
169
170impl ScrollComponent {
171 #[inline]
173 pub fn is_kinetic_scrolling(&self) -> bool {
174 self.kinetic.is_scrolling()
175 }
176
177 #[inline]
181 pub fn max_offset(&self) -> Offset {
182 self.max_offset
183 }
184
185 #[inline]
190 pub fn offset(&self) -> Offset {
191 self.offset
192 }
193
194 #[must_use]
201 pub fn set_sizes(&mut self, window_size: Size, content_size: Size) -> Option<ActionMoved> {
202 let max_offset = (Offset::conv(content_size) - Offset::conv(window_size)).max(Offset::ZERO);
203 if max_offset == self.max_offset {
204 return None;
205 }
206 self.max_offset = max_offset;
207 self.set_offset(self.offset)
208 }
209
210 #[must_use]
217 pub fn set_offset(&mut self, offset: Offset) -> Option<ActionMoved> {
218 let offset = offset.clamp(Offset::ZERO, self.max_offset);
219 if offset == self.offset {
220 None
221 } else {
222 self.kinetic.stop();
223 self.offset = offset;
224 Some(ActionMoved)
225 }
226 }
227
228 #[must_use]
237 pub fn focus_rect(
238 &mut self,
239 cx: &mut EventCx,
240 rect: Rect,
241 window_rect: Rect,
242 ) -> Option<ActionMoved> {
243 let action = self.self_focus_rect(rect, window_rect);
244 cx.set_scroll(Scroll::Rect(rect - self.offset));
245 action
246 }
247
248 #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
253 #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
254 #[must_use]
255 pub fn self_focus_rect(&mut self, rect: Rect, window_rect: Rect) -> Option<ActionMoved> {
256 self.kinetic.stop();
257 let max_vis = rect.pos - window_rect.pos;
258 let extra_size = Offset::conv(rect.size) - Offset::conv(window_rect.size);
259 let min_vis = max_vis + extra_size;
260 let center = max_vis + extra_size / 2;
261 let lb = (min_vis + center) / 2;
262 let ub = (max_vis + center) / 2;
263 let offset = self.offset.max(lb).min(ub);
264 self.set_offset(offset)
265 }
266
267 pub fn scroll(&mut self, cx: &mut EventCx, id: Id, window_rect: Rect, scroll: Scroll) {
269 match scroll {
270 Scroll::None | Scroll::Scrolled => (),
271 Scroll::Offset(delta) => {
272 self.scroll_by_delta(cx, delta);
273 }
274 Scroll::Kinetic(start) => {
275 let delta = self.kinetic.start(start);
276 let delta = self.scroll_self_by_delta(cx, delta);
277 if delta == Offset::ZERO {
278 cx.set_scroll(Scroll::Scrolled);
279 } else {
280 cx.set_scroll(Scroll::Kinetic(self.kinetic.stop_with_residual(delta)));
281 }
282 if self.kinetic.is_scrolling() {
283 cx.request_frame_timer(id, TIMER_KINETIC);
284 }
285 }
286 Scroll::Rect(rect) => {
287 let action = self.focus_rect(cx, rect, window_rect);
288 cx.action_moved(action);
289 }
290 }
291 }
292
293 fn scroll_self_by_delta(&mut self, cx: &mut EventState, d: Offset) -> Offset {
297 let mut delta = d;
298 let offset = (self.offset - d).clamp(Offset::ZERO, self.max_offset);
299 if offset != self.offset {
300 delta = d - (self.offset - offset);
301 self.offset = offset;
302 cx.region_moved();
303 }
304 delta
305 }
306
307 fn scroll_by_delta(&mut self, cx: &mut EventCx, d: Vec2) {
308 let delta = d + self.kinetic.rest;
309 let offset = delta.cast_nearest();
310 self.kinetic.rest = delta - Vec2::conv(offset);
311 let delta = self.scroll_self_by_delta(cx, offset);
312 cx.set_scroll(if delta != Offset::ZERO {
313 Scroll::Offset(delta.cast())
314 } else {
315 Scroll::Scrolled
316 });
317 }
318
319 pub fn scroll_by_event(
334 &mut self,
335 cx: &mut EventCx,
336 event: Event,
337 id: Id,
338 window_rect: Rect,
339 ) -> IsUsed {
340 match event {
341 Event::Command(cmd, _) => {
342 let offset = match cmd {
343 Command::Home => Offset::ZERO,
344 Command::End => self.max_offset,
345 cmd => {
346 let delta = match cmd {
347 Command::Left => ScrollDelta::Lines(1.0, 0.0),
348 Command::Right => ScrollDelta::Lines(-1.0, 0.0),
349 Command::Up => ScrollDelta::Lines(0.0, 1.0),
350 Command::Down => ScrollDelta::Lines(0.0, -1.0),
351 Command::PageUp | Command::PageDown => {
352 let mut v = 0.5 * f32::conv(window_rect.size.1);
353 if cmd == Command::PageDown {
354 v = -v;
355 }
356 ScrollDelta::PixelDelta(Vec2(0.0, v))
357 }
358 _ => return Unused,
359 };
360 self.scroll_by_delta(cx, delta.as_offset(cx));
361 return Used;
362 }
363 };
364 cx.action_moved(self.set_offset(offset));
365 cx.set_scroll(Scroll::Rect(window_rect));
366 }
367 Event::Scroll(delta) => {
368 self.kinetic.stop();
369 self.scroll_by_delta(cx, delta.as_offset(cx));
370 }
371 Event::PressStart(press)
372 if self.max_offset != Offset::ZERO && cx.config_enable_pan(*press) =>
373 {
374 let _ = press
375 .grab(id, GrabMode::Grab)
376 .with_icon(CursorIcon::Grabbing)
377 .complete(cx);
378 self.kinetic.press_start(press.source);
379 }
380 Event::PressMove { press, delta }
381 if self.max_offset != Offset::ZERO && cx.config_enable_pan(*press) =>
382 {
383 if self.kinetic.press_move(press.source) {
384 self.scroll_by_delta(cx, delta);
385 }
386 }
387 Event::PressEnd { press, .. }
388 if self.max_offset != Offset::ZERO && cx.config_enable_pan(*press) =>
389 {
390 if let Some(velocity) = cx.press_velocity(press.source)
391 && self.kinetic.press_end(press.source, velocity)
392 {
393 cx.request_frame_timer(id, TIMER_KINETIC);
394 }
395 }
396 Event::Timer(TIMER_KINETIC) => {
397 if let Some(delta) = self.kinetic.step(cx) {
398 let delta = self.scroll_self_by_delta(cx, delta);
399 let scroll = if delta == Offset::ZERO {
400 Scroll::Scrolled
401 } else {
402 Scroll::Kinetic(self.kinetic.stop_with_residual(delta))
403 };
404 cx.set_scroll(scroll);
405 }
406
407 if self.kinetic.is_scrolling() {
408 cx.request_frame_timer(id, TIMER_KINETIC);
409 }
410 }
411 _ => return Unused,
412 }
413 Used
414 }
415}
416
417#[impl_default(TextPhase::None)]
418#[autoimpl(Clone, Debug, PartialEq)]
419enum TextPhase {
420 None,
421 PressStart(PressSource, Coord), Pan(PressSource), Cursor(PressSource, Coord), }
425
426#[derive(Clone, Debug, Default)]
428pub struct TextInput {
429 phase: TextPhase,
430}
431
432#[autoimpl(Clone, Debug)]
434pub enum TextInputAction {
435 Used,
437 Unused,
439 PressStart {
448 coord: Coord,
450 clear: bool,
452 repeats: u32,
454 },
455 PressMove {
465 coord: Coord,
466 repeats: u32,
468 },
469 PressEnd { coord: Coord },
486}
487
488impl TextInput {
489 pub fn handle(&mut self, cx: &mut EventCx, w_id: Id, event: Event) -> TextInputAction {
497 use TextInputAction as Action;
498 match event {
499 Event::PressStart(press) if press.is_primary() => {
500 let mut action = Action::Used;
501 let icon = if press.is_touch() {
502 self.phase = TextPhase::PressStart(*press, press.coord());
503 let delay = cx.config().event().touch_select_delay();
504 cx.request_timer(w_id.clone(), TIMER_SELECT, delay);
505 None
506 } else if press.is_mouse() {
507 if cx.config_enable_mouse_text_pan() {
508 self.phase = TextPhase::Pan(*press);
509 Some(CursorIcon::Grabbing)
510 } else {
511 self.phase = TextPhase::Cursor(*press, press.coord());
512 action = Action::PressStart {
513 coord: press.coord(),
514 clear: !cx.modifiers().shift_key(),
515 repeats: press.repetitions(),
516 };
517 None
518 }
519 } else {
520 unreachable!()
521 };
522 press
523 .grab(w_id, GrabMode::Grab)
524 .with_opt_icon(icon)
525 .complete(cx);
526 action
527 }
528 Event::PressMove { press, delta } => match self.phase {
529 TextPhase::PressStart(source, start_coord) if *press == source => {
530 let delta = press.coord - start_coord;
531 if cx.config_test_pan_thresh(delta) {
532 self.phase = TextPhase::Pan(source);
533 cx.set_scroll(Scroll::Offset(delta.cast()));
534 }
535 Action::Used
536 }
537 TextPhase::Pan(source) if *press == source => {
538 cx.set_scroll(Scroll::Offset(delta));
539 Action::Used
540 }
541 TextPhase::Cursor(source, _) if *press == source => {
542 self.phase = TextPhase::Cursor(source, press.coord);
543 Action::PressMove {
544 coord: press.coord,
545 repeats: press.repetitions(),
546 }
547 }
548 _ => Action::Used,
549 },
550 Event::PressEnd { press, .. } => match std::mem::take(&mut self.phase) {
551 TextPhase::None => Action::Used,
552 TextPhase::PressStart(_, coord) => Action::PressEnd { coord },
553 TextPhase::Pan(source) => {
554 if *press == source
555 && let Some(vel) = cx.press_velocity(source)
556 {
557 let rest = Vec2::ZERO;
558 cx.set_scroll(Scroll::Kinetic(KineticStart { vel, rest }));
559 }
560
561 Action::Used
562 }
563 TextPhase::Cursor(_, coord) => Action::PressEnd { coord },
564 },
565 Event::Timer(TIMER_SELECT) => match self.phase {
566 TextPhase::PressStart(source, coord) => {
567 self.phase = TextPhase::Cursor(source, coord);
568 Action::PressStart {
569 coord,
570 clear: !cx.modifiers().shift_key(),
571 repeats: 1,
572 }
573 }
574 _ => Action::Unused,
575 },
576 _ => Action::Unused,
577 }
578 }
579
580 #[inline]
585 pub fn is_selecting(&self) -> bool {
586 matches!(&self.phase, TextPhase::Cursor(_, _))
587 }
588
589 pub fn stop_selecting(&mut self) {
591 if self.is_selecting() {
592 self.phase = TextPhase::None;
593 }
594 }
595}
596
597#[impl_default(ClickPhase::None)]
598#[autoimpl(Clone, Debug, PartialEq)]
599enum ClickPhase {
600 None,
601 PressStart(PressSource, Coord), Pan(PressSource), }
604
605#[derive(Clone, Debug, Default)]
607pub struct ClickInput {
608 phase: ClickPhase,
609}
610
611#[autoimpl(Clone, Debug)]
613pub enum ClickInputAction {
614 Used,
616 Unused,
618 ClickStart {
624 coord: Coord,
626 repeats: u32,
628 },
629 ClickEnd { coord: Coord, success: bool },
638}
639
640impl ClickInput {
641 pub fn handle(&mut self, cx: &mut EventCx, w_id: Id, event: Event) -> ClickInputAction {
648 use ClickInputAction as Action;
649 match event {
650 Event::PressStart(press) if press.is_primary() => {
651 let mut action = Action::Used;
652 let icon = if cx.config_enable_mouse_text_pan() {
653 self.phase = ClickPhase::Pan(*press);
654 Some(CursorIcon::Grabbing)
655 } else {
656 self.phase = ClickPhase::PressStart(*press, press.coord());
657 action = Action::ClickStart {
658 coord: press.coord(),
659 repeats: press.repetitions(),
660 };
661 None
662 };
663 press
664 .grab(w_id, GrabMode::Grab)
665 .with_opt_icon(icon)
666 .complete(cx);
667 action
668 }
669 Event::PressMove { press, delta } => match self.phase {
670 ClickPhase::PressStart(source, start_coord) if *press == source => {
671 let delta = press.coord - start_coord;
672 if cx.config_test_pan_thresh(delta) {
673 self.phase = ClickPhase::Pan(source);
674 cx.set_scroll(Scroll::Offset(delta.cast()));
675 }
676 Action::Used
677 }
678 ClickPhase::Pan(source) if *press == source => {
679 cx.set_scroll(Scroll::Offset(delta));
680 Action::Used
681 }
682 _ => Action::Used,
683 },
684 Event::PressEnd { press, success } => match std::mem::take(&mut self.phase) {
685 ClickPhase::PressStart(source, _) if *press == source => Action::ClickEnd {
686 coord: press.coord,
687 success,
688 },
689 ClickPhase::Pan(source) => {
690 if *press == source
691 && let Some(vel) = cx.press_velocity(source)
692 {
693 let rest = Vec2::ZERO;
694 cx.set_scroll(Scroll::Kinetic(KineticStart { vel, rest }));
695 }
696
697 Action::Used
698 }
699 _ => Action::Used,
700 },
701 _ => Action::Unused,
702 }
703 }
704}