Skip to main content

open_gpui_canvas/gpui/
input.rs

1use crate::{CanvasEvent, CanvasKey, CanvasKeyModifiers, PointerButton};
2use open_gpui::{
3    Bounds, Context, DispatchPhase, Entity, FocusHandle, KeyDownEvent, Keystroke, Modifiers,
4    MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, ScrollWheelEvent,
5    Window, px,
6};
7use std::rc::Rc;
8
9#[derive(Clone, Copy, Debug, PartialEq)]
10pub struct CanvasInputMapper {
11    bounds: Bounds<Pixels>,
12    line_height: Pixels,
13}
14
15impl CanvasInputMapper {
16    pub fn new(bounds: Bounds<Pixels>) -> Self {
17        Self {
18            bounds,
19            line_height: px(16.0),
20        }
21    }
22
23    pub fn with_line_height(mut self, line_height: Pixels) -> Self {
24        self.line_height = line_height;
25        self
26    }
27
28    pub fn mouse_down(&self, event: &MouseDownEvent) -> Option<CanvasEvent> {
29        Some(CanvasEvent::PointerDown {
30            position: self.local_position(event.position)?,
31            button: pointer_button(event.button)?,
32            modifiers: Self::modifiers(event.modifiers),
33        })
34    }
35
36    pub fn mouse_move(&self, event: &MouseMoveEvent) -> Option<CanvasEvent> {
37        Some(CanvasEvent::PointerMove {
38            position: self.local_position(event.position)?,
39            modifiers: Self::modifiers(event.modifiers),
40        })
41    }
42
43    pub fn mouse_up(&self, event: &MouseUpEvent) -> Option<CanvasEvent> {
44        Some(CanvasEvent::PointerUp {
45            position: self.local_position(event.position)?,
46            button: pointer_button(event.button)?,
47            modifiers: Self::modifiers(event.modifiers),
48        })
49    }
50
51    pub fn scroll_wheel(&self, event: &ScrollWheelEvent) -> Option<CanvasEvent> {
52        if self.local_position(event.position).is_none() {
53            return None;
54        }
55
56        Some(CanvasEvent::Wheel {
57            delta: event.delta.pixel_delta(self.line_height),
58        })
59    }
60
61    pub fn key_down_event(event: &KeyDownEvent) -> CanvasEvent {
62        let key = canvas_key(&event.keystroke);
63        if key == CanvasKey::Escape {
64            return CanvasEvent::Cancel;
65        }
66
67        CanvasEvent::KeyDown {
68            key,
69            modifiers: Self::modifiers(event.keystroke.modifiers),
70            repeat: event.is_held,
71        }
72    }
73
74    pub fn modifiers(modifiers: Modifiers) -> CanvasKeyModifiers {
75        canvas_key_modifiers(modifiers)
76    }
77
78    pub fn local_position(&self, position: Point<Pixels>) -> Option<Point<Pixels>> {
79        self.bounds
80            .contains(&position)
81            .then(|| position - self.bounds.origin)
82    }
83}
84
85#[derive(Clone, Copy, Debug, PartialEq)]
86pub struct CanvasEditorInputMapper {
87    mapper: CanvasInputMapper,
88    pointer_interacting: bool,
89}
90
91impl CanvasEditorInputMapper {
92    pub fn new(bounds: Bounds<Pixels>) -> Self {
93        Self {
94            mapper: CanvasInputMapper::new(bounds),
95            pointer_interacting: false,
96        }
97    }
98
99    pub fn with_line_height(mut self, line_height: Pixels) -> Self {
100        self.mapper = self.mapper.with_line_height(line_height);
101        self
102    }
103
104    pub fn with_pointer_interacting(mut self, pointer_interacting: bool) -> Self {
105        self.pointer_interacting = pointer_interacting;
106        self
107    }
108
109    pub fn mouse_down(&self, event: &MouseDownEvent) -> Option<CanvasEvent> {
110        self.mapper.mouse_down(event)
111    }
112
113    pub fn mouse_move(&self, event: &MouseMoveEvent) -> Option<CanvasEvent> {
114        if self.pointer_interacting {
115            return Some(CanvasEvent::PointerMove {
116                position: event.position - self.mapper.bounds.origin,
117                modifiers: CanvasInputMapper::modifiers(event.modifiers),
118            });
119        }
120
121        self.mapper.mouse_move(event)
122    }
123
124    pub fn mouse_up(&self, event: &MouseUpEvent) -> Option<CanvasEvent> {
125        if self.pointer_interacting {
126            return pointer_button(event.button).map(|button| CanvasEvent::PointerUp {
127                position: event.position - self.mapper.bounds.origin,
128                button,
129                modifiers: CanvasInputMapper::modifiers(event.modifiers),
130            });
131        }
132
133        self.mapper.mouse_up(event)
134    }
135
136    pub fn scroll_wheel(&self, event: &ScrollWheelEvent) -> Option<CanvasEvent> {
137        self.mapper.scroll_wheel(event)
138    }
139}
140
141pub struct CanvasEditorInputHandler<T> {
142    pointer_interacting: Rc<dyn Fn(&T) -> bool>,
143    dispatch: Rc<dyn Fn(&mut T, CanvasEvent, &mut Context<T>)>,
144}
145
146impl<T> Clone for CanvasEditorInputHandler<T> {
147    fn clone(&self) -> Self {
148        Self {
149            pointer_interacting: self.pointer_interacting.clone(),
150            dispatch: self.dispatch.clone(),
151        }
152    }
153}
154
155impl<T> CanvasEditorInputHandler<T> {
156    pub fn new(
157        pointer_interacting: impl Fn(&T) -> bool + 'static,
158        dispatch: impl Fn(&mut T, CanvasEvent, &mut Context<T>) + 'static,
159    ) -> Self {
160        Self {
161            pointer_interacting: Rc::new(pointer_interacting),
162            dispatch: Rc::new(dispatch),
163        }
164    }
165
166    pub fn pointer_interacting(&self, target: &T) -> bool {
167        (self.pointer_interacting)(target)
168    }
169
170    pub fn dispatch_event(&self, target: &mut T, event: CanvasEvent, cx: &mut Context<T>) {
171        (self.dispatch)(target, event, cx)
172    }
173
174    pub fn dispatch_key_down(&self, target: &mut T, event: &KeyDownEvent, cx: &mut Context<T>) {
175        self.dispatch_event(target, CanvasInputMapper::key_down_event(event), cx);
176    }
177}
178
179pub fn register_canvas_editor_input<T>(
180    entity: Entity<T>,
181    focus_handle: FocusHandle,
182    bounds: Bounds<Pixels>,
183    handler: CanvasEditorInputHandler<T>,
184    window: &mut Window,
185) where
186    T: 'static,
187{
188    let mapper = CanvasEditorInputMapper::new(bounds);
189
190    window.on_mouse_event({
191        let entity = entity.clone();
192        let handler = handler.clone();
193        move |event: &MouseDownEvent, phase, window, cx| {
194            if phase != DispatchPhase::Bubble {
195                return;
196            }
197
198            let Some(event) = mapper.mouse_down(event) else {
199                return;
200            };
201
202            window.focus(&focus_handle, cx);
203            entity.update(cx, |target, cx| handler.dispatch_event(target, event, cx));
204        }
205    });
206
207    window.on_mouse_event({
208        let entity = entity.clone();
209        let handler = handler.clone();
210        move |event: &MouseMoveEvent, phase, _, cx| {
211            entity.update(cx, |target, cx| {
212                let pointer_interacting = handler.pointer_interacting(target);
213                if pointer_interacting {
214                    if phase != DispatchPhase::Capture {
215                        return;
216                    }
217                    let mapper = mapper.with_pointer_interacting(true);
218                    if let Some(event) = mapper.mouse_move(event) {
219                        handler.dispatch_event(target, event, cx);
220                        cx.stop_propagation();
221                    }
222                    return;
223                }
224
225                if phase == DispatchPhase::Bubble
226                    && let Some(event) = mapper.mouse_move(event)
227                {
228                    handler.dispatch_event(target, event, cx);
229                }
230            });
231        }
232    });
233
234    window.on_mouse_event({
235        let entity = entity.clone();
236        let handler = handler.clone();
237        move |event: &MouseUpEvent, phase, _, cx| {
238            entity.update(cx, |target, cx| {
239                let pointer_interacting = handler.pointer_interacting(target);
240                if pointer_interacting {
241                    if phase != DispatchPhase::Capture {
242                        return;
243                    }
244                    let mapper = mapper.with_pointer_interacting(true);
245                    if let Some(event) = mapper.mouse_up(event) {
246                        handler.dispatch_event(target, event, cx);
247                        cx.stop_propagation();
248                    }
249                    return;
250                }
251
252                if phase == DispatchPhase::Bubble
253                    && let Some(event) = mapper.mouse_up(event)
254                {
255                    handler.dispatch_event(target, event, cx);
256                }
257            });
258        }
259    });
260
261    window.on_mouse_event({
262        let entity = entity.clone();
263        let handler = handler.clone();
264        move |event: &ScrollWheelEvent, phase, _, cx| {
265            if phase != DispatchPhase::Bubble {
266                return;
267            }
268
269            if let Some(event) = mapper.scroll_wheel(event) {
270                entity.update(cx, |target, cx| handler.dispatch_event(target, event, cx));
271            }
272        }
273    });
274}
275
276fn pointer_button(button: MouseButton) -> Option<PointerButton> {
277    match button {
278        MouseButton::Left => Some(PointerButton::Primary),
279        MouseButton::Right => Some(PointerButton::Secondary),
280        MouseButton::Middle => Some(PointerButton::Middle),
281        MouseButton::Navigate(_) => None,
282    }
283}
284
285fn canvas_key(keystroke: &Keystroke) -> CanvasKey {
286    match keystroke.key.as_str() {
287        "delete" | "del" => CanvasKey::Delete,
288        "backspace" => CanvasKey::Backspace,
289        "escape" | "esc" => CanvasKey::Escape,
290        "enter" | "return" => CanvasKey::Enter,
291        key if key.chars().count() == 1 => CanvasKey::Character(
292            keystroke
293                .key_char
294                .clone()
295                .unwrap_or_else(|| key.to_string()),
296        ),
297        key => CanvasKey::Named(key.to_string()),
298    }
299}
300
301fn canvas_key_modifiers(modifiers: Modifiers) -> CanvasKeyModifiers {
302    CanvasKeyModifiers {
303        shift: modifiers.shift,
304        alt: modifiers.alt,
305        control: modifiers.control,
306        platform: modifiers.platform,
307        function: modifiers.function,
308    }
309}