event_dispatcher.rs

  1use std::sync::Arc;
  2
  3use collections::{HashMap, HashSet};
  4use pathfinder_geometry::vector::Vector2F;
  5
  6use crate::{
  7    scene::{
  8        ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, HoverRegionEvent,
  9        MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent,
 10    },
 11    CursorRegion, CursorStyle, ElementBox, Event, EventContext, FontCache, MouseButton,
 12    MouseMovedEvent, MouseRegion, MouseRegionId, MutableAppContext, Scene, TextLayoutCache,
 13};
 14
 15pub struct EventDispatcher {
 16    window_id: usize,
 17    font_cache: Arc<FontCache>,
 18
 19    last_mouse_moved_event: Option<Event>,
 20    cursor_regions: Vec<CursorRegion>,
 21    mouse_regions: Vec<(MouseRegion, usize)>,
 22    clicked_regions: Vec<MouseRegion>,
 23    clicked_button: Option<MouseButton>,
 24    mouse_position: Vector2F,
 25    hovered_region_ids: HashSet<MouseRegionId>,
 26}
 27
 28impl EventDispatcher {
 29    pub fn new(window_id: usize, font_cache: Arc<FontCache>) -> Self {
 30        Self {
 31            window_id,
 32            font_cache,
 33
 34            last_mouse_moved_event: Default::default(),
 35            cursor_regions: Default::default(),
 36            mouse_regions: Default::default(),
 37            clicked_regions: Default::default(),
 38            clicked_button: Default::default(),
 39            mouse_position: Default::default(),
 40            hovered_region_ids: Default::default(),
 41        }
 42    }
 43
 44    pub fn clicked_region_ids(&self) -> Option<(Vec<MouseRegionId>, MouseButton)> {
 45        self.clicked_button.map(|button| {
 46            (
 47                self.clicked_regions
 48                    .iter()
 49                    .filter_map(MouseRegion::id)
 50                    .collect(),
 51                button,
 52            )
 53        })
 54    }
 55
 56    pub fn hovered_region_ids(&self) -> HashSet<MouseRegionId> {
 57        self.hovered_region_ids.clone()
 58    }
 59
 60    pub fn update_mouse_regions(&mut self, scene: &Scene) {
 61        self.cursor_regions = scene.cursor_regions();
 62        self.mouse_regions = scene.mouse_regions();
 63    }
 64
 65    pub fn redispatch_mouse_moved_event<'a>(&'a mut self, cx: &mut EventContext<'a>) {
 66        if let Some(event) = self.last_mouse_moved_event.clone() {
 67            self.dispatch_event(event, true, cx);
 68        }
 69    }
 70
 71    pub fn dispatch_event<'a>(
 72        &'a mut self,
 73        event: Event,
 74        event_reused: bool,
 75        cx: &mut EventContext<'a>,
 76    ) -> bool {
 77        let root_view_id = cx.root_view_id(self.window_id);
 78        if root_view_id.is_none() {
 79            return false;
 80        }
 81
 82        let root_view_id = root_view_id.unwrap();
 83        //1. Allocate the correct set of GPUI events generated from the platform events
 84        // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?]
 85        // -> Also moves around mouse related state
 86        let events_to_send = self.select_region_events(&event, cx, event_reused);
 87
 88        // For a given platform event, potentially multiple mouse region events can be created. For a given
 89        // region event, dispatch continues until a mouse region callback fails to propogate (handled is set to true)
 90        // If no region handles any of the produced platform events, we fallback to the old dispatch event style.
 91        let mut invalidated_views: HashSet<usize> = Default::default();
 92        let mut any_event_handled = false;
 93        for mut region_event in events_to_send {
 94            //2. Find mouse regions relevant to each region_event. For example, if the event is click, select
 95            // the clicked_regions that overlap with the mouse position
 96            let valid_regions = self.select_relevant_mouse_regions(&region_event);
 97            let hovered_region_ids = self.hovered_region_ids.clone();
 98
 99            //3. Dispatch region event ot each valid mouse region
100            for valid_region in valid_regions.into_iter() {
101                region_event.set_region(valid_region.bounds);
102                if let MouseRegionEvent::Hover(e) = &mut region_event {
103                    e.started = valid_region
104                        .id()
105                        .map(|region_id| hovered_region_ids.contains(&region_id))
106                        .unwrap_or(false)
107                }
108
109                if let Some(callback) = valid_region.handlers.get(&region_event.handler_key()) {
110                    if !event_reused {
111                        invalidated_views.insert(valid_region.view_id);
112                    }
113
114                    cx.handled = true;
115                    cx.with_current_view(valid_region.view_id, {
116                        let region_event = region_event.clone();
117                        |cx| {
118                            callback(region_event, cx);
119                        }
120                    });
121
122                    // For bubbling events, if the event was handled, don't continue dispatching
123                    // This only makes sense for local events.
124                    if cx.handled && region_event.is_local() {
125                        break;
126                    }
127                }
128            }
129
130            // Keep track if any platform event was handled
131            any_event_handled = any_event_handled && cx.handled;
132        }
133
134        if !any_event_handled {
135            // No platform event was handled, so fall back to old mouse event dispatch style
136            any_event_handled = cx.dispatch_event(root_view_id, &event);
137        }
138
139        // Notify any views which have been validated from event callbacks
140        for view_id in invalidated_views {
141            cx.notify_view(self.window_id, view_id);
142        }
143
144        any_event_handled
145    }
146
147    fn select_region_events(
148        &mut self,
149        event: &Event,
150        cx: &mut MutableAppContext,
151        event_reused: bool,
152    ) -> Vec<MouseRegionEvent> {
153        let mut events_to_send = Vec::new();
154        match event {
155            Event::MouseDown(e) => {
156                //Click events are weird because they can be fired after a drag event.
157                //MDN says that browsers handle this by starting from 'the most
158                //specific ancestor element that contained both [positions]'
159                //So we need to store the overlapping regions on mouse down.
160                self.clicked_regions = self
161                    .mouse_regions
162                    .iter()
163                    .filter_map(|(region, _)| {
164                        region
165                            .bounds
166                            .contains_point(e.position)
167                            .then(|| region.clone())
168                    })
169                    .collect();
170                self.clicked_button = Some(e.button);
171
172                events_to_send.push(MouseRegionEvent::Down(DownRegionEvent {
173                    region: Default::default(),
174                    platform_event: e.clone(),
175                }));
176                events_to_send.push(MouseRegionEvent::DownOut(DownOutRegionEvent {
177                    region: Default::default(),
178                    platform_event: e.clone(),
179                }));
180            }
181            Event::MouseUp(e) => {
182                //NOTE: The order of event pushes is important! MouseUp events MUST be fired
183                //before click events, and so the UpRegionEvent events need to be pushed before
184                //ClickRegionEvents
185                events_to_send.push(MouseRegionEvent::Up(UpRegionEvent {
186                    region: Default::default(),
187                    platform_event: e.clone(),
188                }));
189                events_to_send.push(MouseRegionEvent::UpOut(UpOutRegionEvent {
190                    region: Default::default(),
191                    platform_event: e.clone(),
192                }));
193                events_to_send.push(MouseRegionEvent::Click(ClickRegionEvent {
194                    region: Default::default(),
195                    platform_event: e.clone(),
196                }));
197            }
198            Event::MouseMoved(
199                e @ MouseMovedEvent {
200                    position,
201                    pressed_button,
202                    ..
203                },
204            ) => {
205                let mut style_to_assign = CursorStyle::Arrow;
206                for region in self.cursor_regions.iter().rev() {
207                    if region.bounds.contains_point(*position) {
208                        style_to_assign = region.style;
209                        break;
210                    }
211                }
212
213                cx.platform().set_cursor_style(style_to_assign);
214
215                if !event_reused {
216                    if pressed_button.is_some() {
217                        events_to_send.push(MouseRegionEvent::Drag(DragRegionEvent {
218                            region: Default::default(),
219                            prev_mouse_position: self.mouse_position,
220                            platform_event: e.clone(),
221                        }));
222                    }
223                    events_to_send.push(MouseRegionEvent::Move(MoveRegionEvent {
224                        region: Default::default(),
225                        platform_event: e.clone(),
226                    }));
227                }
228
229                events_to_send.push(MouseRegionEvent::Hover(HoverRegionEvent {
230                    region: Default::default(),
231                    platform_event: e.clone(),
232                    started: false,
233                }));
234
235                self.last_mouse_moved_event = Some(event.clone());
236            }
237            _ => {}
238        }
239        if let Some(position) = event.position() {
240            self.mouse_position = position;
241        }
242        events_to_send
243    }
244
245    fn select_relevant_mouse_regions(
246        &mut self,
247        region_event: &MouseRegionEvent,
248    ) -> Vec<MouseRegion> {
249        let mut valid_regions = Vec::new();
250        //GPUI elements are arranged by depth but sibling elements can register overlapping
251        //mouse regions. As such, hover events are only fired on overlapping elements which
252        //are at the same depth as the deepest element which overlaps with the mouse.
253        if let MouseRegionEvent::Hover(_) = *region_event {
254            let mut top_most_depth = None;
255            let mouse_position = self.mouse_position.clone();
256            for (region, depth) in self.mouse_regions.iter().rev() {
257                let contains_mouse = region.bounds.contains_point(mouse_position);
258
259                if contains_mouse && top_most_depth.is_none() {
260                    top_most_depth = Some(depth);
261                }
262
263                if let Some(region_id) = region.id() {
264                    //This unwrap relies on short circuiting boolean expressions
265                    //The right side of the && is only executed when contains_mouse
266                    //is true, and we know above that when contains_mouse is true
267                    //top_most_depth is set
268                    if contains_mouse && depth == top_most_depth.unwrap() {
269                        //Ensure that hover entrance events aren't sent twice
270                        if self.hovered_region_ids.insert(region_id) {
271                            valid_regions.push(region.clone());
272                        }
273                    } else {
274                        //Ensure that hover exit events aren't sent twice
275                        if self.hovered_region_ids.remove(&region_id) {
276                            valid_regions.push(region.clone());
277                        }
278                    }
279                }
280            }
281        } else if let MouseRegionEvent::Click(e) = region_event {
282            //Clear stored clicked_regions
283            let clicked_regions = std::mem::replace(&mut self.clicked_regions, Vec::new());
284            self.clicked_button = None;
285
286            //Find regions which still overlap with the mouse since the last MouseDown happened
287            for clicked_region in clicked_regions.into_iter().rev() {
288                if clicked_region.bounds.contains_point(e.position) {
289                    valid_regions.push(clicked_region);
290                }
291            }
292        } else if region_event.is_local() {
293            for (mouse_region, _) in self.mouse_regions.iter().rev() {
294                //Contains
295                if mouse_region.bounds.contains_point(self.mouse_position) {
296                    valid_regions.push(mouse_region.clone());
297                }
298            }
299        } else {
300            for (mouse_region, _) in self.mouse_regions.iter().rev() {
301                //NOT contains
302                if !mouse_region.bounds.contains_point(self.mouse_position) {
303                    valid_regions.push(mouse_region.clone());
304                }
305            }
306        }
307        valid_regions
308    }
309}