@@ -54,6 +54,8 @@ pub(crate) struct OhosWindow {
5454 scroll_animation : RefCell < Option < ScrollAnimation > > ,
5555 scroll_frame_rate_boosted : Cell < bool > ,
5656 pending_touch_click_feedback_cancel : Cell < Option < Modifiers > > ,
57+ tap_click_tracker : Cell < TapClickTracker > ,
58+ active_touch_click_count : Cell < u32 > ,
5759}
5860
5961pub ( crate ) struct OhosWindowHandle {
@@ -147,6 +149,58 @@ struct TouchSample {
147149 timestamp : Duration ,
148150}
149151
152+ #[ derive( Clone , Copy , Default ) ]
153+ struct TapClickTracker {
154+ last_tap_position : Option < Point < Pixels > > ,
155+ last_tap_timestamp : Option < Duration > ,
156+ click_count : u32 ,
157+ }
158+
159+ impl TapClickTracker {
160+ fn record_tap ( & mut self , position : Point < Pixels > , timestamp : Option < Duration > ) -> u32 {
161+ let Some ( timestamp) = timestamp else {
162+ self . reset ( ) ;
163+ return 1 ;
164+ } ;
165+
166+ let click_count = if self . is_consecutive_tap ( position, timestamp) {
167+ ( self . click_count + 1 ) . min ( OhosWindow :: MAX_TOUCH_CLICK_COUNT )
168+ } else {
169+ 1
170+ } ;
171+
172+ self . last_tap_position = Some ( position) ;
173+ self . last_tap_timestamp = Some ( timestamp) ;
174+ self . click_count = click_count;
175+ click_count
176+ }
177+
178+ fn reset ( & mut self ) {
179+ * self = Self :: default ( ) ;
180+ }
181+
182+ fn is_consecutive_tap ( & self , position : Point < Pixels > , timestamp : Duration ) -> bool {
183+ let ( Some ( last_position) , Some ( last_timestamp) ) =
184+ ( self . last_tap_position , self . last_tap_timestamp )
185+ else {
186+ return false ;
187+ } ;
188+ let Some ( interval) = timestamp. checked_sub ( last_timestamp) else {
189+ return false ;
190+ } ;
191+
192+ interval <= OhosWindow :: DOUBLE_TAP_MAX_INTERVAL
193+ && Self :: distance_within_threshold ( last_position, position)
194+ }
195+
196+ fn distance_within_threshold ( a : Point < Pixels > , b : Point < Pixels > ) -> bool {
197+ let dx = a. x . as_f32 ( ) - b. x . as_f32 ( ) ;
198+ let dy = a. y . as_f32 ( ) - b. y . as_f32 ( ) ;
199+ let max = OhosWindow :: DOUBLE_TAP_MAX_DISTANCE ;
200+ dx * dx + dy * dy <= max * max
201+ }
202+ }
203+
150204#[ derive( Default ) ]
151205struct TouchVelocityTracker {
152206 samples : VecDeque < TouchSample > ,
@@ -354,6 +408,9 @@ impl TouchVelocityTracker {
354408impl OhosWindow {
355409 const TOUCH_SLOP : f32 = 5.0 ;
356410 const TAP_MAX_DURATION : Duration = Duration :: from_millis ( 220 ) ;
411+ const DOUBLE_TAP_MAX_INTERVAL : Duration = Duration :: from_millis ( 500 ) ;
412+ const DOUBLE_TAP_MAX_DISTANCE : f32 = 24.0 ;
413+ const MAX_TOUCH_CLICK_COUNT : u32 = 3 ;
357414 const MIN_MOMENTUM_VELOCITY : f32 = 240.0 ;
358415 const MAX_MOMENTUM_VELOCITY : f32 = 9_000.0 ;
359416 const FLING_VELOCITY_SCALE : f32 = 1.5 ;
@@ -423,6 +480,8 @@ impl OhosWindow {
423480 scroll_animation : RefCell :: new ( None ) ,
424481 scroll_frame_rate_boosted : Cell :: new ( false ) ,
425482 pending_touch_click_feedback_cancel : Cell :: new ( None ) ,
483+ tap_click_tracker : Cell :: new ( TapClickTracker :: default ( ) ) ,
484+ active_touch_click_count : Cell :: new ( 1 ) ,
426485 } )
427486 }
428487
@@ -551,6 +610,23 @@ impl OhosWindow {
551610 ) ;
552611 }
553612
613+ fn record_touch_tap_click_count (
614+ & self ,
615+ position : Point < Pixels > ,
616+ timestamp : Option < Duration > ,
617+ ) -> u32 {
618+ let mut tracker = self . tap_click_tracker . get ( ) ;
619+ let click_count = tracker. record_tap ( position, timestamp) ;
620+ self . tap_click_tracker . set ( tracker) ;
621+ self . active_touch_click_count . set ( click_count) ;
622+ click_count
623+ }
624+
625+ fn reset_touch_tap_click_count ( & self ) {
626+ self . tap_click_tracker . set ( TapClickTracker :: default ( ) ) ;
627+ self . active_touch_click_count . set ( 1 ) ;
628+ }
629+
554630 fn tracked_touch_velocity ( & self ) -> Point < f32 > {
555631 self . touch_velocity_tracker
556632 . borrow ( )
@@ -1449,11 +1525,13 @@ impl OhosWindow {
14491525 match touch_event. event_type {
14501526 TouchEvent :: Down => {
14511527 if self . begin_touch_tracking ( position, event_timestamp, true ) {
1528+ let click_count =
1529+ self . record_touch_tap_click_count ( position, event_timestamp) ;
14521530 self . dispatch_input ( PlatformInput :: MouseDown ( MouseDownEvent {
14531531 button : MouseButton :: Left ,
14541532 position,
14551533 modifiers,
1456- click_count : 1 ,
1534+ click_count,
14571535 first_mouse : false ,
14581536 } ) ) ;
14591537 }
@@ -1498,12 +1576,13 @@ impl OhosWindow {
14981576 button : MouseButton :: Left ,
14991577 position,
15001578 modifiers,
1501- click_count : 1 ,
1579+ click_count : self . active_touch_click_count . get ( ) ,
15021580 } ) ) ;
15031581 }
15041582 }
15051583 TouchState :: Pending ( pending_state) if pending_state. mouse_down_sent => {
15061584 self . cancel_touch_click_feedback ( modifiers) ;
1585+ self . reset_touch_tap_click_count ( ) ;
15071586 }
15081587 TouchState :: Idle | TouchState :: Pending ( ..) => { }
15091588 }
@@ -1529,6 +1608,7 @@ impl OhosWindow {
15291608 self . cancel_touch_click_feedback ( modifiers) ;
15301609 pending_state. mouse_down_sent = false ;
15311610 }
1611+ self . reset_touch_tap_click_count ( ) ;
15321612 pending_state. cancel_click = true ;
15331613 }
15341614
@@ -1553,6 +1633,7 @@ impl OhosWindow {
15531633 if pending_state. mouse_down_sent {
15541634 self . cancel_touch_click_feedback ( modifiers) ;
15551635 }
1636+ self . reset_touch_tap_click_count ( ) ;
15561637 self . set_scroll_frame_rate_boost ( true ) ;
15571638 * self . last_dispatched_touch_position . borrow_mut ( ) =
15581639 Some ( pending_state. last_position ) ;
@@ -1600,6 +1681,7 @@ impl OhosWindow {
16001681 if self . touch_is_active ( ) {
16011682 self . cancel_touch_click_feedback ( modifiers) ;
16021683 }
1684+ self . reset_touch_tap_click_count ( ) ;
16031685 if self . touch_is_scrolling ( ) {
16041686 self . dispatch_scroll_end ( position, modifiers) ;
16051687 }
@@ -1676,6 +1758,43 @@ mod tests {
16761758 assert ! ( OhosWindow :: movement_exceeds_touch_slop( slop_squared + 0.01 ) ) ;
16771759 }
16781760
1761+ #[ test]
1762+ fn tap_click_tracker_counts_nearby_taps_within_interval ( ) {
1763+ let mut tracker = TapClickTracker :: default ( ) ;
1764+ let first = point ( px ( 40.0 ) , px ( 80.0 ) ) ;
1765+ let second = point ( px ( 52.0 ) , px ( 90.0 ) ) ;
1766+ let third = point ( px ( 56.0 ) , px ( 92.0 ) ) ;
1767+
1768+ let first_count = tracker. record_tap ( first, Some ( Duration :: from_millis ( 1_000 ) ) ) ;
1769+ let second_count = tracker. record_tap ( second, Some ( Duration :: from_millis ( 1_320 ) ) ) ;
1770+ let third_count = tracker. record_tap ( third, Some ( Duration :: from_millis ( 1_520 ) ) ) ;
1771+
1772+ assert_eq ! ( 1 , first_count) ;
1773+ assert_eq ! ( 2 , second_count) ;
1774+ assert_eq ! ( 3 , third_count) ;
1775+ }
1776+
1777+ #[ test]
1778+ fn tap_click_tracker_resets_for_slow_or_distant_taps ( ) {
1779+ let mut tracker = TapClickTracker :: default ( ) ;
1780+ let first = point ( px ( 40.0 ) , px ( 80.0 ) ) ;
1781+ let slow = point ( px ( 42.0 ) , px ( 82.0 ) ) ;
1782+ let distant = point ( px ( 120.0 ) , px ( 160.0 ) ) ;
1783+
1784+ assert_eq ! (
1785+ 1 ,
1786+ tracker. record_tap( first, Some ( Duration :: from_millis( 1_000 ) ) )
1787+ ) ;
1788+ assert_eq ! (
1789+ 1 ,
1790+ tracker. record_tap( slow, Some ( Duration :: from_millis( 1_600 ) ) )
1791+ ) ;
1792+ assert_eq ! (
1793+ 1 ,
1794+ tracker. record_tap( distant, Some ( Duration :: from_millis( 1_800 ) ) )
1795+ ) ;
1796+ }
1797+
16791798 #[ test]
16801799 fn touch_axis_prefers_dominant_direction ( ) {
16811800 assert ! ( matches!(
0 commit comments