Skip to content

Commit a1b977a

Browse files
committed
fix: support double tap on HarmonyOS
1 parent 2e7d8a2 commit a1b977a

2 files changed

Lines changed: 139 additions & 27 deletions

File tree

‎main/src/home_tab.rs‎

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -612,11 +612,18 @@ mod external_driver_form_tests {
612612
}
613613

614614
#[test]
615-
fn connection_primary_click_open_policy_is_ohos_only() {
616-
assert_eq!(
617-
cfg!(target_env = "ohos"),
618-
HomePage::opens_connection_on_primary_click()
619-
);
615+
fn connection_primary_click_only_selects_on_all_platforms() {
616+
let source = include_str!("home_tab.rs");
617+
let handler = source
618+
.rsplit("fn handle_connection_primary_click(")
619+
.next()
620+
.expect("primary click handler exists")
621+
.split("\n fn drag_connection(")
622+
.next()
623+
.expect("primary click handler has an end marker");
624+
625+
assert!(handler.contains("self.selected_connection_id = conn_id;"));
626+
assert!(!handler.contains("open_connection_from_quick"));
620627
}
621628

622629
#[test]
@@ -641,7 +648,7 @@ mod external_driver_form_tests {
641648
assert!(section.contains(".on_click(cx.listener(move |this, _, window, cx| {"));
642649
assert!(section.contains("this.handle_connection_primary_click"));
643650
assert!(section.contains(".on_double_click(cx.listener(move |this, _, w, cx| {"));
644-
assert!(section.contains("if !Self::opens_connection_on_primary_click()"));
651+
assert!(!section.contains("opens_connection_on_primary_click"));
645652
}
646653
}
647654

@@ -3700,7 +3707,6 @@ impl HomePage {
37003707
) -> AnyElement {
37013708
let conn_id = conn.id;
37023709
let clone_conn = conn.clone();
3703-
let click_conn = conn.clone();
37043710
let sftp_hover_conn = conn.clone();
37053711
let edit_conn = conn.clone();
37063712
let edit_conn_type = conn.connection_type;
@@ -3737,13 +3743,11 @@ impl HomePage {
37373743
.cursor_pointer()
37383744
.hover(|style| style.bg(cx.theme().muted))
37393745
.on_double_click(cx.listener(move |this, _, w, cx| {
3740-
if !Self::opens_connection_on_primary_click() {
3741-
this.open_connection_from_quick(&clone_conn, w, cx);
3742-
}
3746+
this.open_connection_from_quick(&clone_conn, w, cx);
37433747
cx.notify()
37443748
}))
37453749
.on_click(cx.listener(move |this, _, window, cx| {
3746-
this.handle_connection_primary_click(conn_id, &click_conn, window, cx);
3750+
this.handle_connection_primary_click(conn_id, window, cx);
37473751
}))
37483752
// 激活指示灯
37493753
.when(is_active, |this| {
@@ -4086,21 +4090,13 @@ impl HomePage {
40864090
}
40874091
}
40884092

4089-
fn opens_connection_on_primary_click() -> bool {
4090-
cfg!(target_env = "ohos")
4091-
}
4092-
40934093
fn handle_connection_primary_click(
40944094
&mut self,
40954095
conn_id: Option<i64>,
4096-
conn: &StoredConnection,
4097-
window: &mut Window,
4096+
_window: &mut Window,
40984097
cx: &mut Context<Self>,
40994098
) {
41004099
self.selected_connection_id = conn_id;
4101-
if Self::opens_connection_on_primary_click() {
4102-
self.open_connection_from_quick(conn, window, cx);
4103-
}
41044100
cx.notify();
41054101
}
41064102

@@ -4124,7 +4120,6 @@ impl HomePage {
41244120
) -> AnyElement {
41254121
let conn_id = conn.id;
41264122
let clone_conn = conn.clone();
4127-
let click_conn = conn.clone();
41284123
let sftp_hover_conn = conn.clone();
41294124
let edit_conn = conn.clone();
41304125
let edit_conn_type = conn.connection_type;
@@ -4170,13 +4165,11 @@ impl HomePage {
41704165
.border_color(cx.theme().list_active_border)
41714166
})
41724167
.on_double_click(cx.listener(move |this, _, w, cx| {
4173-
if !Self::opens_connection_on_primary_click() {
4174-
this.open_connection_from_quick(&clone_conn, w, cx);
4175-
}
4168+
this.open_connection_from_quick(&clone_conn, w, cx);
41764169
cx.notify()
41774170
}))
41784171
.on_click(cx.listener(move |this, _, window, cx| {
4179-
this.handle_connection_primary_click(conn_id, &click_conn, window, cx);
4172+
this.handle_connection_primary_click(conn_id, window, cx);
41804173
}))
41814174
.when(is_active, |this| {
41824175
// card version

‎ohos/native/patches/gpui_ohos/src/ohos/window.rs‎

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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

5961
pub(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)]
151205
struct TouchVelocityTracker {
152206
samples: VecDeque<TouchSample>,
@@ -354,6 +408,9 @@ impl TouchVelocityTracker {
354408
impl 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

Comments
 (0)