From 0f43ef933148c4388806539b55bec18c51c09172 Mon Sep 17 00:00:00 2001 From: K Simmons Date: Mon, 22 Aug 2022 16:16:27 -0700 Subject: [PATCH] Dispatch Event refactoring. Appears to be workingCo-Authored-By: nathan@zed.dev --- crates/gpui/src/elements/list.rs | 4 +- crates/gpui/src/presenter.rs | 143 ++++---- crates/gpui/src/presenter/event_context.rs | 100 ++++++ crates/gpui/src/presenter/event_dispatcher.rs | 308 ++++++++++++++++++ crates/gpui/src/scene/mouse_region.rs | 2 +- crates/gpui/src/scene/mouse_region_event.rs | 6 +- crates/workspace/src/sidebar.rs | 2 +- 7 files changed, 494 insertions(+), 71 deletions(-) create mode 100644 crates/gpui/src/presenter/event_context.rs create mode 100644 crates/gpui/src/presenter/event_dispatcher.rs diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index e0ca47b598e2cd20c82917bc3d56f28dcd54703d..e53672501c3f21ab94904662e0450e45c14fca4f 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -698,7 +698,7 @@ mod tests { 40., vec2f(0., -54.), true, - &mut presenter.build_event_context(cx), + &mut presenter.build_event_context(&mut Default::default(), cx), ); let (_, logical_scroll_top) = list.layout( constraint, @@ -807,7 +807,7 @@ mod tests { height, delta, true, - &mut presenter.build_event_context(cx), + &mut presenter.build_event_context(&mut Default::default(), cx), ); } 30..=34 => { diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 918bf31322bbb4583bc4581209339a72b2f90a47..42dac6d2072d2fc7a803aed151c8cae0763983d1 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -79,6 +79,7 @@ impl Presenter { self.rendered_views.remove(view_id); } for view_id in &invalidation.updated { + dbg!(view_id); self.rendered_views.insert( *view_id, cx.render_view(RenderParams { @@ -151,6 +152,7 @@ impl Presenter { if cx.window_is_active(self.window_id) { if let Some(event) = self.last_mouse_moved_event.clone() { + println!("Redispatching mouse moved"); self.dispatch_event(event, true, cx); } } @@ -236,15 +238,15 @@ impl Presenter { if let Some(root_view_id) = cx.root_view_id(self.window_id) { let mut events_to_send = Vec::new(); - //1. Allocate the correct set of GPUI events generated from the platform events - // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?] - // -> Also moves around mouse related state + // 1. Allocate the correct set of GPUI events generated from the platform events + // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?] + // -> Also moves around mouse related state match &event { Event::MouseDown(e) => { - //Click events are weird because they can be fired after a drag event. - //MDN says that browsers handle this by starting from 'the most - //specific ancestor element that contained both [positions]' - //So we need to store the overlapping regions on mouse down. + // Click events are weird because they can be fired after a drag event. + // MDN says that browsers handle this by starting from 'the most + // specific ancestor element that contained both [positions]' + // So we need to store the overlapping regions on mouse down. self.clicked_regions = self .mouse_regions .iter() @@ -267,9 +269,9 @@ impl Presenter { })); } Event::MouseUp(e) => { - //NOTE: The order of event pushes is important! MouseUp events MUST be fired - //before click events, and so the UpRegionEvent events need to be pushed before - //ClickRegionEvents + // NOTE: The order of event pushes is important! MouseUp events MUST be fired + // before click events, and so the UpRegionEvent events need to be pushed before + // ClickRegionEvents events_to_send.push(MouseRegionEvent::Up(UpRegionEvent { region: Default::default(), platform_event: e.clone(), @@ -330,74 +332,87 @@ impl Presenter { let mut invalidated_views: HashSet = Default::default(); let mut any_event_handled = false; - //2. Process the raw mouse events into region events + // 2. Process the raw mouse events into region events for mut region_event in events_to_send { let mut valid_regions = Vec::new(); - //GPUI elements are arranged by depth but sibling elements can register overlapping - //mouse regions. As such, hover events are only fired on overlapping elements which - //are at the same depth as the deepest element which overlaps with the mouse. - if let MouseRegionEvent::Hover(_) = region_event { - let mut top_most_depth = None; - let mouse_position = self.mouse_position.clone(); - for (region, depth) in self.mouse_regions.iter().rev() { - let contains_mouse = region.bounds.contains_point(mouse_position); - - if contains_mouse && top_most_depth.is_none() { - top_most_depth = Some(depth); - } + // GPUI elements are arranged by depth but sibling elements can register overlapping + // mouse regions. As such, hover events are only fired on overlapping elements which + // are at the same depth as the deepest element which overlaps with the mouse. - if let Some(region_id) = region.id() { - //This unwrap relies on short circuiting boolean expressions - //The right side of the && is only executed when contains_mouse - //is true, and we know above that when contains_mouse is true - //top_most_depth is set - if contains_mouse && depth == top_most_depth.unwrap() { - //Ensure that hover entrance events aren't sent twice - if self.hovered_region_ids.insert(region_id) { - valid_regions.push(region.clone()); - } - } else { - //Ensure that hover exit events aren't sent twice - if self.hovered_region_ids.remove(®ion_id) { - valid_regions.push(region.clone()); + match ®ion_event { + MouseRegionEvent::Hover(_) => { + let mut top_most_depth = None; + let mouse_position = self.mouse_position.clone(); + for (region, depth) in self.mouse_regions.iter().rev() { + let contains_mouse = region.bounds.contains_point(mouse_position); + + if contains_mouse && top_most_depth.is_none() { + top_most_depth = Some(depth); + } + + if let Some(region_id) = region.id() { + // This unwrap relies on short circuiting boolean expressions + // The right side of the && is only executed when contains_mouse + // is true, and we know above that when contains_mouse is true + // top_most_depth is set + if contains_mouse && depth == top_most_depth.unwrap() { + //Ensure that hover entrance events aren't sent twice + if self.hovered_region_ids.insert(region_id) { + valid_regions.push(region.clone()); + } + } else { + // Ensure that hover exit events aren't sent twice + if self.hovered_region_ids.remove(®ion_id) { + valid_regions.push(region.clone()); + } } } } } - } else if let MouseRegionEvent::Click(e) = ®ion_event { - //Clear presenter state - let clicked_regions = std::mem::replace(&mut self.clicked_regions, Vec::new()); - self.clicked_button = None; - - //Find regions which still overlap with the mouse since the last MouseDown happened - for clicked_region in clicked_regions.into_iter().rev() { - if clicked_region.bounds.contains_point(e.position) { - valid_regions.push(clicked_region); + MouseRegionEvent::Click(e) => { + // Clear presenter state + let clicked_regions = + std::mem::replace(&mut self.clicked_regions, Vec::new()); + self.clicked_button = None; + + // Find regions which still overlap with the mouse since the last MouseDown happened + for clicked_region in clicked_regions.into_iter().rev() { + if clicked_region.bounds.contains_point(e.position) { + valid_regions.push(clicked_region); + } + } + } + MouseRegionEvent::Drag(_) => { + for clicked_region in self.clicked_regions.iter().rev() { + valid_regions.push(clicked_region.clone()); } } - } else if region_event.is_local() { - for (mouse_region, _) in self.mouse_regions.iter().rev() { - //Contains - if mouse_region.bounds.contains_point(self.mouse_position) { - valid_regions.push(mouse_region.clone()); + + MouseRegionEvent::UpOut(_) | MouseRegionEvent::DownOut(_) => { + for (mouse_region, _) in self.mouse_regions.iter().rev() { + // NOT contains + if !mouse_region.bounds.contains_point(self.mouse_position) { + valid_regions.push(mouse_region.clone()); + } } } - } else { - for (mouse_region, _) in self.mouse_regions.iter().rev() { - //NOT contains - if !mouse_region.bounds.contains_point(self.mouse_position) { - valid_regions.push(mouse_region.clone()); + _ => { + for (mouse_region, _) in self.mouse_regions.iter().rev() { + // Contains + if mouse_region.bounds.contains_point(self.mouse_position) { + valid_regions.push(mouse_region.clone()); + } } } } //3. Fire region events let hovered_region_ids = self.hovered_region_ids.clone(); - let mut event_cx = self.build_event_context(&mut invalidated_views, cx); for valid_region in valid_regions.into_iter() { region_event.set_region(valid_region.bounds); if let MouseRegionEvent::Hover(e) = &mut region_event { + println!("Hover event selected"); e.started = valid_region .id() .map(|region_id| hovered_region_ids.contains(®ion_id)) @@ -405,12 +420,11 @@ impl Presenter { } if let Some(callback) = valid_region.handlers.get(®ion_event.handler_key()) { - if !event_reused { - invalidated_views.insert(valid_region.view_id); - } + dbg!(valid_region.view_id); + invalidated_views.insert(valid_region.view_id); + let mut event_cx = self.build_event_context(&mut invalidated_views, cx); event_cx.handled = true; - let local = region_event.is_local(); event_cx.with_current_view(valid_region.view_id, { let region_event = region_event.clone(); |cx| { @@ -418,18 +432,17 @@ impl Presenter { } }); + any_event_handled = any_event_handled || event_cx.handled; // For bubbling events, if the event was handled, don't continue dispatching // This only makes sense for local events. - if event_cx.handled && local { + if event_cx.handled && region_event.is_capturable() { break; } } } - - any_event_handled = any_event_handled && event_cx.handled; } - if !any_event_handled { + if !any_event_handled && !event_reused { let mut event_cx = self.build_event_context(&mut invalidated_views, cx); any_event_handled = event_cx.dispatch_event(root_view_id, &event); } diff --git a/crates/gpui/src/presenter/event_context.rs b/crates/gpui/src/presenter/event_context.rs new file mode 100644 index 0000000000000000000000000000000000000000..d4258a6d1e724b8a9b30e742b6aef31648565c91 --- /dev/null +++ b/crates/gpui/src/presenter/event_context.rs @@ -0,0 +1,100 @@ +use std::ops::{Deref, DerefMut}; + +use collections::{HashMap, HashSet}; + +use crate::{Action, ElementBox, Event, FontCache, MutableAppContext, TextLayoutCache}; + +pub struct EventContext<'a> { + rendered_views: &'a mut HashMap, + pub font_cache: &'a FontCache, + pub text_layout_cache: &'a TextLayoutCache, + pub app: &'a mut MutableAppContext, + pub window_id: usize, + pub notify_count: usize, + view_stack: Vec, + pub(crate) handled: bool, + pub(crate) invalidated_views: HashSet, +} + +impl<'a> EventContext<'a> { + pub(crate) fn dispatch_event(&mut self, view_id: usize, event: &Event) -> bool { + if let Some(mut element) = self.rendered_views.remove(&view_id) { + let result = + self.with_current_view(view_id, |this| element.dispatch_event(event, this)); + self.rendered_views.insert(view_id, element); + result + } else { + false + } + } + + pub(crate) fn with_current_view(&mut self, view_id: usize, f: F) -> T + where + F: FnOnce(&mut Self) -> T, + { + self.view_stack.push(view_id); + let result = f(self); + self.view_stack.pop(); + result + } + + pub fn window_id(&self) -> usize { + self.window_id + } + + pub fn view_id(&self) -> Option { + self.view_stack.last().copied() + } + + pub fn is_parent_view_focused(&self) -> bool { + if let Some(parent_view_id) = self.view_stack.last() { + self.app.focused_view_id(self.window_id) == Some(*parent_view_id) + } else { + false + } + } + + pub fn focus_parent_view(&mut self) { + if let Some(parent_view_id) = self.view_stack.last() { + self.app.focus(self.window_id, Some(*parent_view_id)) + } + } + + pub fn dispatch_any_action(&mut self, action: Box) { + self.app + .dispatch_any_action_at(self.window_id, *self.view_stack.last().unwrap(), action) + } + + pub fn dispatch_action(&mut self, action: A) { + self.dispatch_any_action(Box::new(action)); + } + + pub fn notify(&mut self) { + self.notify_count += 1; + if let Some(view_id) = self.view_stack.last() { + self.invalidated_views.insert(*view_id); + } + } + + pub fn notify_count(&self) -> usize { + self.notify_count + } + + pub fn propogate_event(&mut self) { + self.handled = false; + } +} + +impl<'a> Deref for EventContext<'a> { + type Target = MutableAppContext; + + fn deref(&self) -> &Self::Target { + self.app + } +} + +impl<'a> DerefMut for EventContext<'a> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.app + } +} diff --git a/crates/gpui/src/presenter/event_dispatcher.rs b/crates/gpui/src/presenter/event_dispatcher.rs new file mode 100644 index 0000000000000000000000000000000000000000..4c72334910b514a257be8213410d1aa9dbb05b79 --- /dev/null +++ b/crates/gpui/src/presenter/event_dispatcher.rs @@ -0,0 +1,308 @@ +use std::sync::Arc; + +use collections::{HashMap, HashSet}; +use pathfinder_geometry::vector::Vector2F; + +use crate::{ + scene::{ + ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, HoverRegionEvent, + MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent, + }, + CursorRegion, CursorStyle, ElementBox, Event, EventContext, FontCache, MouseButton, + MouseMovedEvent, MouseRegion, MouseRegionId, MutableAppContext, Scene, TextLayoutCache, +}; + +pub struct EventDispatcher { + window_id: usize, + font_cache: Arc, + + last_mouse_moved_event: Option, + cursor_regions: Vec, + mouse_regions: Vec<(MouseRegion, usize)>, + clicked_regions: Vec, + clicked_button: Option, + mouse_position: Vector2F, + hovered_region_ids: HashSet, +} + +impl EventDispatcher { + pub fn new(window_id: usize, font_cache: Arc) -> Self { + Self { + window_id, + font_cache, + + last_mouse_moved_event: Default::default(), + cursor_regions: Default::default(), + mouse_regions: Default::default(), + clicked_regions: Default::default(), + clicked_button: Default::default(), + mouse_position: Default::default(), + hovered_region_ids: Default::default(), + } + } + + pub fn clicked_region_ids(&self) -> Option<(Vec, MouseButton)> { + self.clicked_button.map(|button| { + ( + self.clicked_regions + .iter() + .filter_map(MouseRegion::id) + .collect(), + button, + ) + }) + } + + pub fn hovered_region_ids(&self) -> HashSet { + self.hovered_region_ids.clone() + } + + pub fn update_mouse_regions(&mut self, scene: &Scene) { + self.cursor_regions = scene.cursor_regions(); + self.mouse_regions = scene.mouse_regions(); + } + + pub fn redispatch_mouse_moved_event<'a>(&'a mut self, cx: &mut EventContext<'a>) { + if let Some(event) = self.last_mouse_moved_event.clone() { + self.dispatch_event(event, true, cx); + } + } + + pub fn dispatch_event<'a>( + &'a mut self, + event: Event, + event_reused: bool, + cx: &mut EventContext<'a>, + ) -> bool { + let root_view_id = cx.root_view_id(self.window_id); + if root_view_id.is_none() { + return false; + } + + let root_view_id = root_view_id.unwrap(); + //1. Allocate the correct set of GPUI events generated from the platform events + // -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?] + // -> Also moves around mouse related state + let events_to_send = self.select_region_events(&event, cx, event_reused); + + // For a given platform event, potentially multiple mouse region events can be created. For a given + // region event, dispatch continues until a mouse region callback fails to propogate (handled is set to true) + // If no region handles any of the produced platform events, we fallback to the old dispatch event style. + let mut invalidated_views: HashSet = Default::default(); + let mut any_event_handled = false; + for mut region_event in events_to_send { + //2. Find mouse regions relevant to each region_event. For example, if the event is click, select + // the clicked_regions that overlap with the mouse position + let valid_regions = self.select_relevant_mouse_regions(®ion_event); + let hovered_region_ids = self.hovered_region_ids.clone(); + + //3. Dispatch region event ot each valid mouse region + for valid_region in valid_regions.into_iter() { + region_event.set_region(valid_region.bounds); + if let MouseRegionEvent::Hover(e) = &mut region_event { + e.started = valid_region + .id() + .map(|region_id| hovered_region_ids.contains(®ion_id)) + .unwrap_or(false) + } + + if let Some(callback) = valid_region.handlers.get(®ion_event.handler_key()) { + if !event_reused { + invalidated_views.insert(valid_region.view_id); + } + + cx.handled = true; + cx.with_current_view(valid_region.view_id, { + let region_event = region_event.clone(); + |cx| { + callback(region_event, cx); + } + }); + + // For bubbling events, if the event was handled, don't continue dispatching + // This only makes sense for local events. + if cx.handled && region_event.is_local() { + break; + } + } + } + + // Keep track if any platform event was handled + any_event_handled = any_event_handled && cx.handled; + } + + if !any_event_handled { + // No platform event was handled, so fall back to old mouse event dispatch style + any_event_handled = cx.dispatch_event(root_view_id, &event); + } + + // Notify any views which have been validated from event callbacks + for view_id in invalidated_views { + cx.notify_view(self.window_id, view_id); + } + + any_event_handled + } + + fn select_region_events( + &mut self, + event: &Event, + cx: &mut MutableAppContext, + event_reused: bool, + ) -> Vec { + let mut events_to_send = Vec::new(); + match event { + Event::MouseDown(e) => { + //Click events are weird because they can be fired after a drag event. + //MDN says that browsers handle this by starting from 'the most + //specific ancestor element that contained both [positions]' + //So we need to store the overlapping regions on mouse down. + self.clicked_regions = self + .mouse_regions + .iter() + .filter_map(|(region, _)| { + region + .bounds + .contains_point(e.position) + .then(|| region.clone()) + }) + .collect(); + self.clicked_button = Some(e.button); + + events_to_send.push(MouseRegionEvent::Down(DownRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + events_to_send.push(MouseRegionEvent::DownOut(DownOutRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + } + Event::MouseUp(e) => { + //NOTE: The order of event pushes is important! MouseUp events MUST be fired + //before click events, and so the UpRegionEvent events need to be pushed before + //ClickRegionEvents + events_to_send.push(MouseRegionEvent::Up(UpRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + events_to_send.push(MouseRegionEvent::UpOut(UpOutRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + events_to_send.push(MouseRegionEvent::Click(ClickRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + } + Event::MouseMoved( + e @ MouseMovedEvent { + position, + pressed_button, + .. + }, + ) => { + let mut style_to_assign = CursorStyle::Arrow; + for region in self.cursor_regions.iter().rev() { + if region.bounds.contains_point(*position) { + style_to_assign = region.style; + break; + } + } + cx.platform().set_cursor_style(style_to_assign); + + if !event_reused { + if pressed_button.is_some() { + events_to_send.push(MouseRegionEvent::Drag(DragRegionEvent { + region: Default::default(), + prev_mouse_position: self.mouse_position, + platform_event: e.clone(), + })); + } + events_to_send.push(MouseRegionEvent::Move(MoveRegionEvent { + region: Default::default(), + platform_event: e.clone(), + })); + } + + events_to_send.push(MouseRegionEvent::Hover(HoverRegionEvent { + region: Default::default(), + platform_event: e.clone(), + started: false, + })); + + self.last_mouse_moved_event = Some(event.clone()); + } + _ => {} + } + if let Some(position) = event.position() { + self.mouse_position = position; + } + events_to_send + } + + fn select_relevant_mouse_regions( + &mut self, + region_event: &MouseRegionEvent, + ) -> Vec { + let mut valid_regions = Vec::new(); + //GPUI elements are arranged by depth but sibling elements can register overlapping + //mouse regions. As such, hover events are only fired on overlapping elements which + //are at the same depth as the deepest element which overlaps with the mouse. + if let MouseRegionEvent::Hover(_) = *region_event { + let mut top_most_depth = None; + let mouse_position = self.mouse_position.clone(); + for (region, depth) in self.mouse_regions.iter().rev() { + let contains_mouse = region.bounds.contains_point(mouse_position); + + if contains_mouse && top_most_depth.is_none() { + top_most_depth = Some(depth); + } + + if let Some(region_id) = region.id() { + //This unwrap relies on short circuiting boolean expressions + //The right side of the && is only executed when contains_mouse + //is true, and we know above that when contains_mouse is true + //top_most_depth is set + if contains_mouse && depth == top_most_depth.unwrap() { + //Ensure that hover entrance events aren't sent twice + if self.hovered_region_ids.insert(region_id) { + valid_regions.push(region.clone()); + } + } else { + //Ensure that hover exit events aren't sent twice + if self.hovered_region_ids.remove(®ion_id) { + valid_regions.push(region.clone()); + } + } + } + } + } else if let MouseRegionEvent::Click(e) = region_event { + //Clear stored clicked_regions + let clicked_regions = std::mem::replace(&mut self.clicked_regions, Vec::new()); + self.clicked_button = None; + + //Find regions which still overlap with the mouse since the last MouseDown happened + for clicked_region in clicked_regions.into_iter().rev() { + if clicked_region.bounds.contains_point(e.position) { + valid_regions.push(clicked_region); + } + } + } else if region_event.is_local() { + for (mouse_region, _) in self.mouse_regions.iter().rev() { + //Contains + if mouse_region.bounds.contains_point(self.mouse_position) { + valid_regions.push(mouse_region.clone()); + } + } + } else { + for (mouse_region, _) in self.mouse_regions.iter().rev() { + //NOT contains + if !mouse_region.bounds.contains_point(self.mouse_position) { + valid_regions.push(mouse_region.clone()); + } + } + } + valid_regions + } +} diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 639f52208b0856963fd397f1319a025273b21c1a..5919d09180bb484234364b8dc296150a6a55d390 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -113,7 +113,7 @@ impl MouseRegion { } } -#[derive(Copy, Clone, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct MouseRegionId { pub view_id: usize, pub discriminant: (TypeId, usize), diff --git a/crates/gpui/src/scene/mouse_region_event.rs b/crates/gpui/src/scene/mouse_region_event.rs index 6556f57fea2b462ae209f2595d3efa6337e66c77..a375651c7aca9e282caec411c47b708e19837f1a 100644 --- a/crates/gpui/src/scene/mouse_region_event.rs +++ b/crates/gpui/src/scene/mouse_region_event.rs @@ -163,10 +163,12 @@ impl MouseRegionEvent { } } - pub fn is_local(&self) -> bool { + /// When true, mouse event handlers must call cx.propagate_event() to bubble + /// the event to handlers they are painted on top of. + pub fn is_capturable(&self) -> bool { match self { MouseRegionEvent::Move(_) => true, - MouseRegionEvent::Drag(_) => true, + MouseRegionEvent::Drag(_) => false, MouseRegionEvent::Hover(_) => true, MouseRegionEvent::Down(_) => true, MouseRegionEvent::Up(_) => true, diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index c89057cb11721f3f60c7471811e326e726a0bd5a..2071d3802bc3c3d526d03c7f0031995462c81de2 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -190,7 +190,7 @@ impl Sidebar { .with_cursor_style(CursorStyle::ResizeLeftRight) .on_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere .on_drag(MouseButton::Left, move |e, cx| { - let delta = e.prev_mouse_position.x() - e.position.x(); + let delta = e.position.x() - e.prev_mouse_position.x(); let prev_width = *actual_width.borrow(); *custom_width.borrow_mut() = 0f32 .max(match side {