druid_shell/
common_util.rs

1// Copyright 2019 The Druid Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Common functions used by the backends
16
17use std::cell::Cell;
18use std::num::NonZeroU64;
19use std::sync::atomic::{AtomicU64, Ordering};
20use std::time::Duration;
21
22use instant::Instant;
23
24use crate::kurbo::Point;
25use crate::WinHandler;
26
27// This is the default timing on windows.
28const MULTI_CLICK_INTERVAL: Duration = Duration::from_millis(500);
29// the max distance between two clicks for them to count as a multi-click
30const MULTI_CLICK_MAX_DISTANCE: f64 = 5.0;
31
32/// Strip the access keys from the menu string.
33///
34/// Changes "E&xit" to "Exit". Actual ampersands are escaped as "&&".
35#[allow(dead_code)]
36pub fn strip_access_key(raw_menu_text: &str) -> String {
37    let mut saw_ampersand = false;
38    let mut result = String::new();
39    for c in raw_menu_text.chars() {
40        if c == '&' {
41            if saw_ampersand {
42                result.push(c);
43            }
44            saw_ampersand = !saw_ampersand;
45        } else {
46            result.push(c);
47            saw_ampersand = false;
48        }
49    }
50    result
51}
52
53/// A trait for implementing the boxed callback hack.
54pub(crate) trait IdleCallback: Send {
55    fn call(self: Box<Self>, a: &mut dyn WinHandler);
56}
57
58impl<F: FnOnce(&mut dyn WinHandler) + Send> IdleCallback for F {
59    fn call(self: Box<F>, a: &mut dyn WinHandler) {
60        (*self)(a)
61    }
62}
63
64/// An incrementing counter for generating unique ids.
65///
66/// This can be used safely from multiple threads.
67///
68/// The counter will overflow if `next()` is called 2^64 - 2 times.
69/// If this is possible for your application, and reuse would be undesirable,
70/// use something else.
71pub struct Counter(AtomicU64);
72
73impl Counter {
74    /// Create a new counter.
75    pub const fn new() -> Counter {
76        Counter(AtomicU64::new(1))
77    }
78
79    /// Creates a new counter with a given starting value.
80    ///
81    /// # Safety
82    ///
83    /// The value must not be zero.
84    pub const unsafe fn new_unchecked(init: u64) -> Counter {
85        Counter(AtomicU64::new(init))
86    }
87
88    /// Return the next value.
89    pub fn next(&self) -> u64 {
90        self.0.fetch_add(1, Ordering::Relaxed)
91    }
92
93    /// Return the next value, as a `NonZeroU64`.
94    pub fn next_nonzero(&self) -> NonZeroU64 {
95        // safe because our initial value is 1 and can only be incremented.
96        unsafe { NonZeroU64::new_unchecked(self.0.fetch_add(1, Ordering::Relaxed)) }
97    }
98}
99
100/// A small helper for determining the click-count of a mouse-down event.
101///
102/// Click-count is incremented if both the duration and distance between a pair
103/// of clicks are below some threshold.
104#[derive(Debug, Clone)]
105pub struct ClickCounter {
106    max_interval: Cell<Duration>,
107    max_distance: Cell<f64>,
108    last_click: Cell<Instant>,
109    last_pos: Cell<Point>,
110    click_count: Cell<u8>,
111}
112
113#[allow(dead_code)]
114impl ClickCounter {
115    /// Create a new ClickCounter with the given interval and distance.
116    pub fn new(max_interval: Duration, max_distance: f64) -> ClickCounter {
117        ClickCounter {
118            max_interval: Cell::new(max_interval),
119            max_distance: Cell::new(max_distance),
120            last_click: Cell::new(Instant::now()),
121            click_count: Cell::new(0),
122            last_pos: Cell::new(Point::new(f64::MAX, 0.0)),
123        }
124    }
125
126    pub fn set_interval_ms(&self, millis: u64) {
127        self.max_interval.set(Duration::from_millis(millis))
128    }
129
130    pub fn set_distance(&self, distance: f64) {
131        self.max_distance.set(distance)
132    }
133
134    /// Return the click count for a click occurring now, at the provided position.
135    pub fn count_for_click(&self, click_pos: Point) -> u8 {
136        let click_time = Instant::now();
137        let last_time = self.last_click.replace(click_time);
138        let last_pos = self.last_pos.replace(click_pos);
139        let elapsed = click_time - last_time;
140        let distance = last_pos.distance(click_pos);
141        if elapsed > self.max_interval.get() || distance > self.max_distance.get() {
142            self.click_count.set(0);
143        }
144        let click_count = self.click_count.get().saturating_add(1);
145        self.click_count.set(click_count);
146        click_count
147    }
148}
149
150impl Default for ClickCounter {
151    fn default() -> Self {
152        ClickCounter::new(MULTI_CLICK_INTERVAL, MULTI_CLICK_MAX_DISTANCE)
153    }
154}