From db433586aacd65780826325125aea895b1ed34ee Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 16 Jan 2024 16:52:55 -0800 Subject: [PATCH 1/5] Add some small test code for tracking down this list bug --- crates/gpui/src/app/test_context.rs | 12 +- crates/gpui/src/element.rs | 6 + crates/gpui/src/elements/list.rs | 227 ++++++++++++++++++---------- crates/gpui/src/interactive.rs | 7 +- crates/gpui/src/window.rs | 1 + 5 files changed, 163 insertions(+), 90 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 17c2a573a89e84573fc4667ae40964000c2ac3b7..3b01096bb5ad76ab250dc8df9e8fcb42dc6ff3e2 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -3,9 +3,9 @@ use crate::{ div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor, - IntoElement, Keystroke, Model, ModelContext, Pixels, Platform, Render, Result, Size, Task, - TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext, VisualContext, - WindowContext, WindowHandle, WindowOptions, + InputEvent, IntoElement, Keystroke, Model, ModelContext, Pixels, Platform, Render, Result, + Size, Task, TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext, + VisualContext, WindowContext, WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; @@ -609,6 +609,12 @@ impl<'a> VisualTestContext { self.cx.simulate_input(self.window, input) } + /// Simulate an event from the platform, e.g. a SrollWheelEvent + pub fn simulate_event(&mut self, event: InputEvent) { + self.update(|cx| cx.dispatch_event(event)); + self.background_executor.run_until_parked(); + } + /// Simulates the user blurring the window. pub fn deactivate_window(&mut self) { if Some(self.window) == self.test_platform.active_window() { diff --git a/crates/gpui/src/element.rs b/crates/gpui/src/element.rs index 179c2cb1e25db449556e92cfbf9710278dbd2b73..3022f9f30a5fa48d3a3d9b14b06011bdde2cc610 100644 --- a/crates/gpui/src/element.rs +++ b/crates/gpui/src/element.rs @@ -115,6 +115,12 @@ pub trait Render: 'static + Sized { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement; } +impl Render for () { + fn render(&mut self, _cx: &mut ViewContext) -> impl IntoElement { + () + } +} + /// You can derive [`IntoElement`] on any type that implements this trait. /// It is used to allow views to be expressed in terms of abstract data. pub trait RenderOnce: 'static { diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 2c076c8bdcdcb77fcc477f82dfba4f04a29bc2f2..7713bd27e1af3f903a512fd9995263b8dda308f3 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -1,6 +1,6 @@ use crate::{ point, px, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask, - DispatchPhase, Element, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, + DispatchPhase, Element, IntoElement, IsZero, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, WindowContext, }; use collections::VecDeque; @@ -28,6 +28,7 @@ struct StateInner { render_item: Box AnyElement>, items: SumTree, logical_scroll_top: Option, + pending_scroll_delta: Pixels, alignment: ListAlignment, overdraw: Pixels, #[allow(clippy::type_complexity)] @@ -92,6 +93,7 @@ impl ListState { alignment: orientation, overdraw, scroll_handler: None, + pending_scroll_delta: px(0.), }))) } @@ -230,6 +232,8 @@ impl StateInner { delta: Point, cx: &mut WindowContext, ) { + // self.pending_scroll_delta += delta.y; + let scroll_max = (self.items.summary().height - height).max(px(0.)); let new_scroll_top = (self.scroll_top(scroll_top) - delta.y) .max(px(0.)) @@ -346,105 +350,119 @@ impl Element for List { height: AvailableSpace::MinContent, }; - // Render items after the scroll top, including those in the trailing overdraw let mut cursor = old_items.cursor::(); - cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); - for (ix, item) in cursor.by_ref().enumerate() { - let visible_height = rendered_height - scroll_top.offset_in_item; - if visible_height >= bounds.size.height + state.overdraw { - break; - } - // Use the previously cached height if available - let mut height = if let ListItem::Rendered { height } = item { - Some(*height) - } else { - None - }; - - // If we're within the visible area or the height wasn't cached, render and measure the item's element - if visible_height < bounds.size.height || height.is_none() { - let mut element = (state.render_item)(scroll_top.item_ix + ix, cx); - let element_size = element.measure(available_item_space, cx); - height = Some(element_size.height); - if visible_height < bounds.size.height { - item_elements.push_back(element); + loop { + // Render items after the scroll top, including those in the trailing overdraw + cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); + for (ix, item) in cursor.by_ref().enumerate() { + let visible_height = rendered_height - scroll_top.offset_in_item; + if visible_height >= bounds.size.height + state.overdraw { + break; + } + + // Use the previously cached height if available + let mut height = if let ListItem::Rendered { height } = item { + Some(*height) + } else { + None + }; + + // If we're within the visible area or the height wasn't cached, render and measure the item's element + if visible_height < bounds.size.height || height.is_none() { + let mut element = (state.render_item)(scroll_top.item_ix + ix, cx); + let element_size = element.measure(available_item_space, cx); + height = Some(element_size.height); + if visible_height < bounds.size.height { + item_elements.push_back(element); + } } + + let height = height.unwrap(); + rendered_height += height; + measured_items.push_back(ListItem::Rendered { height }); } - let height = height.unwrap(); - rendered_height += height; - measured_items.push_back(ListItem::Rendered { height }); - } + // Prepare to start walking upward from the item at the scroll top. + cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); + + // If the rendered items do not fill the visible region, then adjust + // the scroll top upward. + if rendered_height - scroll_top.offset_in_item < bounds.size.height { + while rendered_height < bounds.size.height { + cursor.prev(&()); + if cursor.item().is_some() { + let mut element = (state.render_item)(cursor.start().0, cx); + let element_size = element.measure(available_item_space, cx); + + rendered_height += element_size.height; + measured_items.push_front(ListItem::Rendered { + height: element_size.height, + }); + item_elements.push_front(element) + } else { + break; + } + } - // Prepare to start walking upward from the item at the scroll top. - cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); + scroll_top = ListOffset { + item_ix: cursor.start().0, + offset_in_item: rendered_height - bounds.size.height, + }; + + match state.alignment { + ListAlignment::Top => { + scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.)); + state.logical_scroll_top = Some(scroll_top); + } + ListAlignment::Bottom => { + scroll_top = ListOffset { + item_ix: cursor.start().0, + offset_in_item: rendered_height - bounds.size.height, + }; + state.logical_scroll_top = None; + } + }; + } - // If the rendered items do not fill the visible region, then adjust - // the scroll top upward. - if rendered_height - scroll_top.offset_in_item < bounds.size.height { - while rendered_height < bounds.size.height { + // Measure items in the leading overdraw + let mut leading_overdraw = scroll_top.offset_in_item; + while leading_overdraw < state.overdraw { cursor.prev(&()); - if cursor.item().is_some() { - let mut element = (state.render_item)(cursor.start().0, cx); - let element_size = element.measure(available_item_space, cx); + if let Some(item) = cursor.item() { + let height = if let ListItem::Rendered { height } = item { + *height + } else { + let mut element = (state.render_item)(cursor.start().0, cx); + element.measure(available_item_space, cx).height + }; - rendered_height += element_size.height; - measured_items.push_front(ListItem::Rendered { - height: element_size.height, - }); - item_elements.push_front(element) + leading_overdraw += height; + measured_items.push_front(ListItem::Rendered { height }); } else { break; } } - scroll_top = ListOffset { - item_ix: cursor.start().0, - offset_in_item: rendered_height - bounds.size.height, - }; + let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len()); + let mut cursor = old_items.cursor::(); + let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &()); + new_items.extend(measured_items, &()); + cursor.seek(&Count(measured_range.end), Bias::Right, &()); + new_items.append(cursor.suffix(&()), &()); - match state.alignment { - ListAlignment::Top => { - scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.)); - state.logical_scroll_top = Some(scroll_top); - } - ListAlignment::Bottom => { - scroll_top = ListOffset { - item_ix: cursor.start().0, - offset_in_item: rendered_height - bounds.size.height, - }; - state.logical_scroll_top = None; - } - }; - } + state.items = new_items; + state.last_layout_bounds = Some(bounds); - // Measure items in the leading overdraw - let mut leading_overdraw = scroll_top.offset_in_item; - while leading_overdraw < state.overdraw { - cursor.prev(&()); - if let Some(item) = cursor.item() { - let height = if let ListItem::Rendered { height } = item { - *height - } else { - let mut element = (state.render_item)(cursor.start().0, cx); - element.measure(available_item_space, cx).height - }; + // if !state.pending_scroll_delta.is_zero() { + // // Do scroll manipulation - leading_overdraw += height; - measured_items.push_front(ListItem::Rendered { height }); - } else { - break; - } + // state.pending_scroll_delta = px(0.); + // } else { + break; + // } } - let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len()); - let mut cursor = old_items.cursor::(); - let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &()); - new_items.extend(measured_items, &()); - cursor.seek(&Count(measured_range.end), Bias::Right, &()); - new_items.append(cursor.suffix(&()), &()); - // Paint the visible items cx.with_content_mask(Some(ContentMask { bounds }), |cx| { let mut item_origin = bounds.origin; @@ -456,12 +474,12 @@ impl Element for List { } }); - state.items = new_items; - state.last_layout_bounds = Some(bounds); - let list_state = self.state.clone(); let height = bounds.size.height; + dbg!("scroll is being bound"); + cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { + dbg!("scroll dispatched!"); if phase == DispatchPhase::Bubble && bounds.contains(&event.position) && cx.was_top_layer(&event.position, cx.stacking_order()) @@ -562,3 +580,44 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height { self.0.partial_cmp(&other.height).unwrap() } } + +#[cfg(test)] +mod test { + + use crate::{self as gpui, Entity, TestAppContext}; + + #[gpui::test] + fn test_reset_after_paint_before_scroll(cx: &mut TestAppContext) { + use crate::{div, list, point, px, size, Element, ListState, Styled}; + + let (v, cx) = cx.add_window_view(|_| ()); + + let state = ListState::new(5, crate::ListAlignment::Top, px(10.), |_, _| { + div().h(px(10.)).w_full().into_any() + }); + + cx.update(|cx| { + cx.with_view_id(v.entity_id(), |cx| { + list(state.clone()) + .w_full() + .h_full() + .z_index(10) + .into_any() + .draw(point(px(0.0), px(0.0)), size(px(100.), px(20.)).into(), cx) + }); + }); + + state.reset(5); + + cx.simulate_event(gpui::InputEvent::ScrollWheel(gpui::ScrollWheelEvent { + position: point(px(1.), px(1.)), + delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-500.))), + ..Default::default() + })); + + assert_eq!(state.logical_scroll_top().item_ix, 0); + assert_eq!(state.logical_scroll_top().offset_in_item, px(0.)); + + panic!("We should not get here yet!") + } +} diff --git a/crates/gpui/src/interactive.rs b/crates/gpui/src/interactive.rs index dfccfc35307f1eb2e75e2d7e8fe8eb73b2c4b7ef..419be9ac3860e3c33c0c27d58754054000d24a15 100644 --- a/crates/gpui/src/interactive.rs +++ b/crates/gpui/src/interactive.rs @@ -2,7 +2,7 @@ use crate::{ div, point, Element, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext, }; use smallvec::SmallVec; -use std::{any::Any, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf}; +use std::{any::Any, default, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf}; #[derive(Clone, Debug, Eq, PartialEq)] pub struct KeyDownEvent { @@ -30,9 +30,10 @@ impl Deref for ModifiersChangedEvent { /// The phase of a touch motion event. /// Based on the winit enum of the same name. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Default)] pub enum TouchPhase { Started, + #[default] Moved, Ended, } @@ -136,7 +137,7 @@ impl MouseMoveEvent { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct ScrollWheelEvent { pub position: Point, pub delta: ScrollDelta, diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 869d6b18268cc64bc18f9187dd34e8032237d28b..df787603471ca1920623dbee8e3d8e0e6d3a3b86 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1716,6 +1716,7 @@ impl<'a> WindowContext<'a> { .mouse_listeners .remove(&event.type_id()) { + dbg!(handlers.len()); // Because handlers may add other handlers, we sort every time. handlers.sort_by(|(a, _, _), (b, _, _)| a.cmp(b)); From cae35d3334adea86d560953cd02388a10bb9b313 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 16 Jan 2024 22:19:55 -0800 Subject: [PATCH 2/5] Fix draw helper, add helper traits for selecting groupings of input events --- crates/gpui/src/app/test_context.rs | 56 ++++--- crates/gpui/src/elements/list.rs | 29 ++-- crates/gpui/src/interactive.rs | 170 ++++++++++++++-------- crates/gpui/src/platform.rs | 6 +- crates/gpui/src/platform/mac/events.rs | 8 +- crates/gpui/src/platform/mac/platform.rs | 10 +- crates/gpui/src/platform/mac/window.rs | 56 +++---- crates/gpui/src/platform/test/platform.rs | 2 +- crates/gpui/src/platform/test/window.rs | 14 +- crates/gpui/src/window.rs | 61 ++++---- 10 files changed, 243 insertions(+), 169 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 3b01096bb5ad76ab250dc8df9e8fcb42dc6ff3e2..556c104f69b9da1a20b32cb0fa325f5aeb975ce9 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -1,11 +1,11 @@ #![deny(missing_docs)] use crate::{ - div, Action, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, - BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, ForegroundExecutor, - InputEvent, IntoElement, Keystroke, Model, ModelContext, Pixels, Platform, Render, Result, - Size, Task, TestDispatcher, TestPlatform, TestWindow, TextSystem, View, ViewContext, - VisualContext, WindowContext, WindowHandle, WindowOptions, + Action, AnyElement, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, + AvailableSpace, BackgroundExecutor, ClipboardItem, Context, Entity, EventEmitter, + ForegroundExecutor, InputEvent, Keystroke, Model, ModelContext, Pixels, Platform, Point, + Render, Result, Size, Task, TestDispatcher, TestPlatform, TestWindow, TextSystem, View, + ViewContext, VisualContext, WindowContext, WindowHandle, WindowOptions, }; use anyhow::{anyhow, bail}; use futures::{Stream, StreamExt}; @@ -167,10 +167,14 @@ impl TestAppContext { } /// Adds a new window with no content. - pub fn add_empty_window(&mut self) -> AnyWindowHandle { + pub fn add_empty_window(&mut self) -> &mut VisualTestContext { let mut cx = self.app.borrow_mut(); - cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| EmptyView {})) - .any_handle + let window = cx.open_window(WindowOptions::default(), |cx| cx.new_view(|_| ())); + drop(cx); + let cx = Box::new(VisualTestContext::from_window(*window.deref(), self)); + cx.run_until_parked(); + // it might be nice to try and cleanup these at the end of each test. + Box::leak(cx) } /// Adds a new window, and returns its root view and a `VisualTestContext` which can be used @@ -609,9 +613,32 @@ impl<'a> VisualTestContext { self.cx.simulate_input(self.window, input) } + /// Draw an element to the window. Useful for simulating events or actions + pub fn draw( + &mut self, + origin: Point, + space: Size, + f: impl FnOnce(&mut WindowContext) -> AnyElement, + ) { + self.update(|cx| { + let entity_id = cx + .window + .root_view + .as_ref() + .expect("Can't draw to this window without a root view") + .entity_id(); + cx.with_view_id(entity_id, |cx| { + f(cx).draw(origin, space, cx); + }); + + cx.refresh(); + }) + } + /// Simulate an event from the platform, e.g. a SrollWheelEvent - pub fn simulate_event(&mut self, event: InputEvent) { - self.update(|cx| cx.dispatch_event(event)); + /// Make sure you've called [VisualTestContext::draw] first! + pub fn simulate_event(&mut self, event: E) { + self.update(|cx| cx.dispatch_event(event.to_platform_input())); self.background_executor.run_until_parked(); } @@ -769,12 +796,3 @@ impl AnyWindowHandle { self.update(cx, |_, cx| cx.new_view(build_view)).unwrap() } } - -/// An EmptyView for testing. -pub struct EmptyView {} - -impl Render for EmptyView { - fn render(&mut self, _cx: &mut crate::ViewContext) -> impl IntoElement { - div() - } -} diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 7713bd27e1af3f903a512fd9995263b8dda308f3..96067d24000033e5c3b292eea3d70871887be7cc 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -1,6 +1,6 @@ use crate::{ point, px, AnyElement, AvailableSpace, BorrowAppContext, BorrowWindow, Bounds, ContentMask, - DispatchPhase, Element, IntoElement, IsZero, Pixels, Point, ScrollWheelEvent, Size, Style, + DispatchPhase, Element, IntoElement, Pixels, Point, ScrollWheelEvent, Size, Style, StyleRefinement, Styled, WindowContext, }; use collections::VecDeque; @@ -584,36 +584,33 @@ impl<'a> sum_tree::SeekTarget<'a, ListItemSummary, ListItemSummary> for Height { #[cfg(test)] mod test { - use crate::{self as gpui, Entity, TestAppContext}; + use gpui::{ScrollDelta, ScrollWheelEvent}; + + use crate::{self as gpui, TestAppContext}; #[gpui::test] fn test_reset_after_paint_before_scroll(cx: &mut TestAppContext) { use crate::{div, list, point, px, size, Element, ListState, Styled}; - let (v, cx) = cx.add_window_view(|_| ()); + let cx = cx.add_empty_window(); let state = ListState::new(5, crate::ListAlignment::Top, px(10.), |_, _| { div().h(px(10.)).w_full().into_any() }); - cx.update(|cx| { - cx.with_view_id(v.entity_id(), |cx| { - list(state.clone()) - .w_full() - .h_full() - .z_index(10) - .into_any() - .draw(point(px(0.0), px(0.0)), size(px(100.), px(20.)).into(), cx) - }); - }); + cx.draw( + point(px(0.), px(0.)), + size(px(100.), px(20.)).into(), + |_| list(state.clone()).w_full().h_full().z_index(10).into_any(), + ); state.reset(5); - cx.simulate_event(gpui::InputEvent::ScrollWheel(gpui::ScrollWheelEvent { + cx.simulate_event(ScrollWheelEvent { position: point(px(1.), px(1.)), - delta: gpui::ScrollDelta::Pixels(point(px(0.), px(-500.))), + delta: ScrollDelta::Pixels(point(px(0.), px(-500.))), ..Default::default() - })); + }); assert_eq!(state.logical_scroll_top().item_ix, 0); assert_eq!(state.logical_scroll_top().offset_in_item, px(0.)); diff --git a/crates/gpui/src/interactive.rs b/crates/gpui/src/interactive.rs index 419be9ac3860e3c33c0c27d58754054000d24a15..86e0a6378e29c290aa7e83ab712f2455997d1d4f 100644 --- a/crates/gpui/src/interactive.rs +++ b/crates/gpui/src/interactive.rs @@ -1,8 +1,14 @@ use crate::{ - div, point, Element, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext, + point, seal::Sealed, IntoElement, Keystroke, Modifiers, Pixels, Point, Render, ViewContext, }; use smallvec::SmallVec; -use std::{any::Any, default, fmt::Debug, marker::PhantomData, ops::Deref, path::PathBuf}; +use std::{any::Any, fmt::Debug, ops::Deref, path::PathBuf}; + +pub trait InputEvent: Sealed + 'static { + fn to_platform_input(self) -> PlatformInput; +} +pub trait KeyEvent: InputEvent {} +pub trait MouseEvent: InputEvent {} #[derive(Clone, Debug, Eq, PartialEq)] pub struct KeyDownEvent { @@ -10,16 +16,40 @@ pub struct KeyDownEvent { pub is_held: bool, } +impl Sealed for KeyDownEvent {} +impl InputEvent for KeyDownEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::KeyDown(self) + } +} +impl KeyEvent for KeyDownEvent {} + #[derive(Clone, Debug)] pub struct KeyUpEvent { pub keystroke: Keystroke, } +impl Sealed for KeyUpEvent {} +impl InputEvent for KeyUpEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::KeyUp(self) + } +} +impl KeyEvent for KeyUpEvent {} + #[derive(Clone, Debug, Default)] pub struct ModifiersChangedEvent { pub modifiers: Modifiers, } +impl Sealed for ModifiersChangedEvent {} +impl InputEvent for ModifiersChangedEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::ModifiersChanged(self) + } +} +impl KeyEvent for ModifiersChangedEvent {} + impl Deref for ModifiersChangedEvent { type Target = Modifiers; @@ -46,6 +76,14 @@ pub struct MouseDownEvent { pub click_count: usize, } +impl Sealed for MouseDownEvent {} +impl InputEvent for MouseDownEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::MouseDown(self) + } +} +impl MouseEvent for MouseDownEvent {} + #[derive(Clone, Debug, Default)] pub struct MouseUpEvent { pub button: MouseButton, @@ -54,38 +92,20 @@ pub struct MouseUpEvent { pub click_count: usize, } +impl Sealed for MouseUpEvent {} +impl InputEvent for MouseUpEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::MouseUp(self) + } +} +impl MouseEvent for MouseUpEvent {} + #[derive(Clone, Debug, Default)] pub struct ClickEvent { pub down: MouseDownEvent, pub up: MouseUpEvent, } -pub struct Drag -where - R: Fn(&mut V, &mut ViewContext) -> E, - V: 'static, - E: IntoElement, -{ - pub state: S, - pub render_drag_handle: R, - view_element_types: PhantomData<(V, E)>, -} - -impl Drag -where - R: Fn(&mut V, &mut ViewContext) -> E, - V: 'static, - E: Element, -{ - pub fn new(state: S, render_drag_handle: R) -> Self { - Drag { - state, - render_drag_handle, - view_element_types: Default::default(), - } - } -} - #[derive(Hash, PartialEq, Eq, Copy, Clone, Debug)] pub enum MouseButton { Left, @@ -131,6 +151,14 @@ pub struct MouseMoveEvent { pub modifiers: Modifiers, } +impl Sealed for MouseMoveEvent {} +impl InputEvent for MouseMoveEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::MouseMove(self) + } +} +impl MouseEvent for MouseMoveEvent {} + impl MouseMoveEvent { pub fn dragging(&self) -> bool { self.pressed_button == Some(MouseButton::Left) @@ -145,6 +173,14 @@ pub struct ScrollWheelEvent { pub touch_phase: TouchPhase, } +impl Sealed for ScrollWheelEvent {} +impl InputEvent for ScrollWheelEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::ScrollWheel(self) + } +} +impl MouseEvent for ScrollWheelEvent {} + impl Deref for ScrollWheelEvent { type Target = Modifiers; @@ -202,6 +238,14 @@ pub struct MouseExitEvent { pub modifiers: Modifiers, } +impl Sealed for MouseExitEvent {} +impl InputEvent for MouseExitEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::MouseExited(self) + } +} +impl MouseEvent for MouseExitEvent {} + impl Deref for MouseExitEvent { type Target = Modifiers; @@ -221,7 +265,7 @@ impl ExternalPaths { impl Render for ExternalPaths { fn render(&mut self, _: &mut ViewContext) -> impl IntoElement { - div() // Intentionally left empty because the platform will render icons for the dragged files + () // Intentionally left empty because the platform will render icons for the dragged files } } @@ -240,8 +284,16 @@ pub enum FileDropEvent { Exited, } +impl Sealed for FileDropEvent {} +impl InputEvent for FileDropEvent { + fn to_platform_input(self) -> PlatformInput { + PlatformInput::FileDrop(self) + } +} +impl MouseEvent for FileDropEvent {} + #[derive(Clone, Debug)] -pub enum InputEvent { +pub enum PlatformInput { KeyDown(KeyDownEvent), KeyUp(KeyUpEvent), ModifiersChanged(ModifiersChangedEvent), @@ -253,19 +305,19 @@ pub enum InputEvent { FileDrop(FileDropEvent), } -impl InputEvent { +impl PlatformInput { pub fn position(&self) -> Option> { match self { - InputEvent::KeyDown { .. } => None, - InputEvent::KeyUp { .. } => None, - InputEvent::ModifiersChanged { .. } => None, - InputEvent::MouseDown(event) => Some(event.position), - InputEvent::MouseUp(event) => Some(event.position), - InputEvent::MouseMove(event) => Some(event.position), - InputEvent::MouseExited(event) => Some(event.position), - InputEvent::ScrollWheel(event) => Some(event.position), - InputEvent::FileDrop(FileDropEvent::Exited) => None, - InputEvent::FileDrop( + PlatformInput::KeyDown { .. } => None, + PlatformInput::KeyUp { .. } => None, + PlatformInput::ModifiersChanged { .. } => None, + PlatformInput::MouseDown(event) => Some(event.position), + PlatformInput::MouseUp(event) => Some(event.position), + PlatformInput::MouseMove(event) => Some(event.position), + PlatformInput::MouseExited(event) => Some(event.position), + PlatformInput::ScrollWheel(event) => Some(event.position), + PlatformInput::FileDrop(FileDropEvent::Exited) => None, + PlatformInput::FileDrop( FileDropEvent::Entered { position, .. } | FileDropEvent::Pending { position, .. } | FileDropEvent::Submit { position, .. }, @@ -275,29 +327,29 @@ impl InputEvent { pub fn mouse_event(&self) -> Option<&dyn Any> { match self { - InputEvent::KeyDown { .. } => None, - InputEvent::KeyUp { .. } => None, - InputEvent::ModifiersChanged { .. } => None, - InputEvent::MouseDown(event) => Some(event), - InputEvent::MouseUp(event) => Some(event), - InputEvent::MouseMove(event) => Some(event), - InputEvent::MouseExited(event) => Some(event), - InputEvent::ScrollWheel(event) => Some(event), - InputEvent::FileDrop(event) => Some(event), + PlatformInput::KeyDown { .. } => None, + PlatformInput::KeyUp { .. } => None, + PlatformInput::ModifiersChanged { .. } => None, + PlatformInput::MouseDown(event) => Some(event), + PlatformInput::MouseUp(event) => Some(event), + PlatformInput::MouseMove(event) => Some(event), + PlatformInput::MouseExited(event) => Some(event), + PlatformInput::ScrollWheel(event) => Some(event), + PlatformInput::FileDrop(event) => Some(event), } } pub fn keyboard_event(&self) -> Option<&dyn Any> { match self { - InputEvent::KeyDown(event) => Some(event), - InputEvent::KeyUp(event) => Some(event), - InputEvent::ModifiersChanged(event) => Some(event), - InputEvent::MouseDown(_) => None, - InputEvent::MouseUp(_) => None, - InputEvent::MouseMove(_) => None, - InputEvent::MouseExited(_) => None, - InputEvent::ScrollWheel(_) => None, - InputEvent::FileDrop(_) => None, + PlatformInput::KeyDown(event) => Some(event), + PlatformInput::KeyUp(event) => Some(event), + PlatformInput::ModifiersChanged(event) => Some(event), + PlatformInput::MouseDown(_) => None, + PlatformInput::MouseUp(_) => None, + PlatformInput::MouseMove(_) => None, + PlatformInput::MouseExited(_) => None, + PlatformInput::ScrollWheel(_) => None, + PlatformInput::FileDrop(_) => None, } } } diff --git a/crates/gpui/src/platform.rs b/crates/gpui/src/platform.rs index 5a2335919ebe6ff9b2277e02d4f6f55b4dc9a80c..8a2e3ea272d392f4db1ba777523ff70b225c1539 100644 --- a/crates/gpui/src/platform.rs +++ b/crates/gpui/src/platform.rs @@ -7,7 +7,7 @@ mod test; use crate::{ Action, AnyWindowHandle, BackgroundExecutor, Bounds, DevicePixels, Font, FontId, FontMetrics, - FontRun, ForegroundExecutor, GlobalPixels, GlyphId, InputEvent, Keymap, LineLayout, Pixels, + FontRun, ForegroundExecutor, GlobalPixels, GlyphId, Keymap, LineLayout, Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Result, Scene, SharedString, Size, TaskLabel, }; @@ -88,7 +88,7 @@ pub(crate) trait Platform: 'static { fn on_resign_active(&self, callback: Box); fn on_quit(&self, callback: Box); fn on_reopen(&self, callback: Box); - fn on_event(&self, callback: Box bool>); + fn on_event(&self, callback: Box bool>); fn set_menus(&self, menus: Vec, keymap: &Keymap); fn on_app_menu_action(&self, callback: Box); @@ -155,7 +155,7 @@ pub trait PlatformWindow { fn zoom(&self); fn toggle_full_screen(&self); fn on_request_frame(&self, callback: Box); - fn on_input(&self, callback: Box bool>); + fn on_input(&self, callback: Box bool>); fn on_active_status_change(&self, callback: Box); fn on_resize(&self, callback: Box, f32)>); fn on_fullscreen(&self, callback: Box); diff --git a/crates/gpui/src/platform/mac/events.rs b/crates/gpui/src/platform/mac/events.rs index c67018ad5d4666ec8ae15511e4739f811ebcf309..f84833d3cbb1678c1ee1ee9b0c1793bb9df8bae0 100644 --- a/crates/gpui/src/platform/mac/events.rs +++ b/crates/gpui/src/platform/mac/events.rs @@ -1,7 +1,7 @@ use crate::{ - point, px, InputEvent, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, - MouseButton, MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, - Pixels, ScrollDelta, ScrollWheelEvent, TouchPhase, + point, px, KeyDownEvent, KeyUpEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, + MouseDownEvent, MouseExitEvent, MouseMoveEvent, MouseUpEvent, NavigationDirection, Pixels, + PlatformInput, ScrollDelta, ScrollWheelEvent, TouchPhase, }; use cocoa::{ appkit::{NSEvent, NSEventModifierFlags, NSEventPhase, NSEventType}, @@ -82,7 +82,7 @@ unsafe fn read_modifiers(native_event: id) -> Modifiers { } } -impl InputEvent { +impl PlatformInput { pub unsafe fn from_native(native_event: id, window_height: Option) -> Option { let event_type = native_event.eventType(); diff --git a/crates/gpui/src/platform/mac/platform.rs b/crates/gpui/src/platform/mac/platform.rs index 8061cc136064c8624d4e8ad217d0a1703274001f..499ac0b59104d9ab0b8204a95c01371a228a6b47 100644 --- a/crates/gpui/src/platform/mac/platform.rs +++ b/crates/gpui/src/platform/mac/platform.rs @@ -1,8 +1,8 @@ use super::{events::key_to_native, BoolExt}; use crate::{ Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, - ForegroundExecutor, InputEvent, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, - MacTextSystem, MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, + ForegroundExecutor, Keymap, MacDispatcher, MacDisplay, MacDisplayLinker, MacTextSystem, + MacWindow, Menu, MenuItem, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, VideoTimestamp, WindowOptions, }; use anyhow::anyhow; @@ -153,7 +153,7 @@ pub struct MacPlatformState { resign_active: Option>, reopen: Option>, quit: Option>, - event: Option bool>>, + event: Option bool>>, menu_command: Option>, validate_menu_command: Option bool>>, will_open_menu: Option>, @@ -637,7 +637,7 @@ impl Platform for MacPlatform { self.0.lock().reopen = Some(callback); } - fn on_event(&self, callback: Box bool>) { + fn on_event(&self, callback: Box bool>) { self.0.lock().event = Some(callback); } @@ -976,7 +976,7 @@ unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform { extern "C" fn send_event(this: &mut Object, _sel: Sel, native_event: id) { unsafe { - if let Some(event) = InputEvent::from_native(native_event, None) { + if let Some(event) = PlatformInput::from_native(native_event, None) { let platform = get_mac_platform(this); let mut lock = platform.0.lock(); if let Some(mut callback) = lock.event.take() { diff --git a/crates/gpui/src/platform/mac/window.rs b/crates/gpui/src/platform/mac/window.rs index c364021281a3690635713bd756c520b1d5f3558b..134390bb79900b0cc09efba720b373303d8cd26b 100644 --- a/crates/gpui/src/platform/mac/window.rs +++ b/crates/gpui/src/platform/mac/window.rs @@ -1,9 +1,9 @@ use super::{display_bounds_from_native, ns_string, MacDisplay, MetalRenderer, NSRange}; use crate::{ display_bounds_to_native, point, px, size, AnyWindowHandle, Bounds, ExternalPaths, - FileDropEvent, ForegroundExecutor, GlobalPixels, InputEvent, KeyDownEvent, Keystroke, - Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, - Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, + FileDropEvent, ForegroundExecutor, GlobalPixels, KeyDownEvent, Keystroke, Modifiers, + ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, + PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point, PromptLevel, Size, Timer, WindowAppearance, WindowBounds, WindowKind, WindowOptions, }; use block::ConcreteBlock; @@ -319,7 +319,7 @@ struct MacWindowState { renderer: MetalRenderer, kind: WindowKind, request_frame_callback: Option>, - event_callback: Option bool>>, + event_callback: Option bool>>, activate_callback: Option>, resize_callback: Option, f32)>>, fullscreen_callback: Option>, @@ -333,7 +333,7 @@ struct MacWindowState { synthetic_drag_counter: usize, last_fresh_keydown: Option, traffic_light_position: Option>, - previous_modifiers_changed_event: Option, + previous_modifiers_changed_event: Option, // State tracking what the IME did after the last request ime_state: ImeState, // Retains the last IME Text @@ -928,7 +928,7 @@ impl PlatformWindow for MacWindow { self.0.as_ref().lock().request_frame_callback = Some(callback); } - fn on_input(&self, callback: Box bool>) { + fn on_input(&self, callback: Box bool>) { self.0.as_ref().lock().event_callback = Some(callback); } @@ -1053,9 +1053,9 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: let mut lock = window_state.as_ref().lock(); let window_height = lock.content_size().height; - let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) }; + let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) }; - if let Some(InputEvent::KeyDown(event)) = event { + if let Some(PlatformInput::KeyDown(event)) = event { // For certain keystrokes, macOS will first dispatch a "key equivalent" event. // If that event isn't handled, it will then dispatch a "key down" event. GPUI // makes no distinction between these two types of events, so we need to ignore @@ -1102,7 +1102,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent: .flatten() .is_some(); if !is_composing { - handled = callback(InputEvent::KeyDown(event)); + handled = callback(PlatformInput::KeyDown(event)); } if !handled { @@ -1146,11 +1146,11 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { let is_active = unsafe { lock.native_window.isKeyWindow() == YES }; let window_height = lock.content_size().height; - let event = unsafe { InputEvent::from_native(native_event, Some(window_height)) }; + let event = unsafe { PlatformInput::from_native(native_event, Some(window_height)) }; if let Some(mut event) = event { match &mut event { - InputEvent::MouseDown( + PlatformInput::MouseDown( event @ MouseDownEvent { button: MouseButton::Left, modifiers: Modifiers { control: true, .. }, @@ -1172,7 +1172,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { // Because we map a ctrl-left_down to a right_down -> right_up let's ignore // the ctrl-left_up to avoid having a mismatch in button down/up events if the // user is still holding ctrl when releasing the left mouse button - InputEvent::MouseUp( + PlatformInput::MouseUp( event @ MouseUpEvent { button: MouseButton::Left, modifiers: Modifiers { control: true, .. }, @@ -1194,7 +1194,7 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { }; match &event { - InputEvent::MouseMove( + PlatformInput::MouseMove( event @ MouseMoveEvent { pressed_button: Some(_), .. @@ -1216,15 +1216,15 @@ extern "C" fn handle_view_event(this: &Object, _: Sel, native_event: id) { } } - InputEvent::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return, + PlatformInput::MouseMove(_) if !(is_active || lock.kind == WindowKind::PopUp) => return, - InputEvent::MouseUp(MouseUpEvent { .. }) => { + PlatformInput::MouseUp(MouseUpEvent { .. }) => { lock.synthetic_drag_counter += 1; } - InputEvent::ModifiersChanged(ModifiersChangedEvent { modifiers }) => { + PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers }) => { // Only raise modifiers changed event when they have actually changed - if let Some(InputEvent::ModifiersChanged(ModifiersChangedEvent { + if let Some(PlatformInput::ModifiersChanged(ModifiersChangedEvent { modifiers: prev_modifiers, })) = &lock.previous_modifiers_changed_event { @@ -1258,7 +1258,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) { key: ".".into(), ime_key: None, }; - let event = InputEvent::KeyDown(KeyDownEvent { + let event = PlatformInput::KeyDown(KeyDownEvent { keystroke: keystroke.clone(), is_held: false, }); @@ -1655,7 +1655,7 @@ extern "C" fn dragging_entered(this: &Object, _: Sel, dragging_info: id) -> NSDr if send_new_event(&window_state, { let position = drag_event_position(&window_state, dragging_info); let paths = external_paths_from_event(dragging_info); - InputEvent::FileDrop(FileDropEvent::Entered { position, paths }) + PlatformInput::FileDrop(FileDropEvent::Entered { position, paths }) }) { window_state.lock().external_files_dragged = true; NSDragOperationCopy @@ -1669,7 +1669,7 @@ extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDr let position = drag_event_position(&window_state, dragging_info); if send_new_event( &window_state, - InputEvent::FileDrop(FileDropEvent::Pending { position }), + PlatformInput::FileDrop(FileDropEvent::Pending { position }), ) { NSDragOperationCopy } else { @@ -1679,7 +1679,10 @@ extern "C" fn dragging_updated(this: &Object, _: Sel, dragging_info: id) -> NSDr extern "C" fn dragging_exited(this: &Object, _: Sel, _: id) { let window_state = unsafe { get_window_state(this) }; - send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited)); + send_new_event( + &window_state, + PlatformInput::FileDrop(FileDropEvent::Exited), + ); window_state.lock().external_files_dragged = false; } @@ -1688,7 +1691,7 @@ extern "C" fn perform_drag_operation(this: &Object, _: Sel, dragging_info: id) - let position = drag_event_position(&window_state, dragging_info); if send_new_event( &window_state, - InputEvent::FileDrop(FileDropEvent::Submit { position }), + PlatformInput::FileDrop(FileDropEvent::Submit { position }), ) { YES } else { @@ -1712,7 +1715,10 @@ fn external_paths_from_event(dragging_info: *mut Object) -> ExternalPaths { extern "C" fn conclude_drag_operation(this: &Object, _: Sel, _: id) { let window_state = unsafe { get_window_state(this) }; - send_new_event(&window_state, InputEvent::FileDrop(FileDropEvent::Exited)); + send_new_event( + &window_state, + PlatformInput::FileDrop(FileDropEvent::Exited), + ); } async fn synthetic_drag( @@ -1727,7 +1733,7 @@ async fn synthetic_drag( if lock.synthetic_drag_counter == drag_id { if let Some(mut callback) = lock.event_callback.take() { drop(lock); - callback(InputEvent::MouseMove(event.clone())); + callback(PlatformInput::MouseMove(event.clone())); window_state.lock().event_callback = Some(callback); } } else { @@ -1737,7 +1743,7 @@ async fn synthetic_drag( } } -fn send_new_event(window_state_lock: &Mutex, e: InputEvent) -> bool { +fn send_new_event(window_state_lock: &Mutex, e: PlatformInput) -> bool { let window_state = window_state_lock.lock().event_callback.take(); if let Some(mut callback) = window_state { callback(e); diff --git a/crates/gpui/src/platform/test/platform.rs b/crates/gpui/src/platform/test/platform.rs index 3a4f5bb36a1360d7d994e82e9347563f985caa79..f5e2170b28acdeae304495172c7b6bbac568bcf4 100644 --- a/crates/gpui/src/platform/test/platform.rs +++ b/crates/gpui/src/platform/test/platform.rs @@ -239,7 +239,7 @@ impl Platform for TestPlatform { unimplemented!() } - fn on_event(&self, _callback: Box bool>) { + fn on_event(&self, _callback: Box bool>) { unimplemented!() } diff --git a/crates/gpui/src/platform/test/window.rs b/crates/gpui/src/platform/test/window.rs index f05e13e3a027e2be9d4f17690fe7162a73396d03..5c8a3e5a59cf91b86d50c311c1beeccfd5ac2840 100644 --- a/crates/gpui/src/platform/test/window.rs +++ b/crates/gpui/src/platform/test/window.rs @@ -1,7 +1,7 @@ use crate::{ - px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, InputEvent, KeyDownEvent, - Keystroke, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, - Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions, + px, AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, KeyDownEvent, Keystroke, + Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, + Point, Size, TestPlatform, TileId, WindowAppearance, WindowBounds, WindowOptions, }; use collections::HashMap; use parking_lot::Mutex; @@ -19,7 +19,7 @@ pub struct TestWindowState { platform: Weak, sprite_atlas: Arc, pub(crate) should_close_handler: Option bool>>, - input_callback: Option bool>>, + input_callback: Option bool>>, active_status_change_callback: Option>, resize_callback: Option, f32)>>, moved_callback: Option>, @@ -85,7 +85,7 @@ impl TestWindow { self.0.lock().active_status_change_callback = Some(callback); } - pub fn simulate_input(&mut self, event: InputEvent) -> bool { + pub fn simulate_input(&mut self, event: PlatformInput) -> bool { let mut lock = self.0.lock(); let Some(mut callback) = lock.input_callback.take() else { return false; @@ -97,7 +97,7 @@ impl TestWindow { } pub fn simulate_keystroke(&mut self, keystroke: Keystroke, is_held: bool) { - if self.simulate_input(InputEvent::KeyDown(KeyDownEvent { + if self.simulate_input(PlatformInput::KeyDown(KeyDownEvent { keystroke: keystroke.clone(), is_held, })) { @@ -220,7 +220,7 @@ impl PlatformWindow for TestWindow { fn on_request_frame(&self, _callback: Box) {} - fn on_input(&self, callback: Box bool>) { + fn on_input(&self, callback: Box bool>) { self.0.lock().input_callback = Some(callback) } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index df787603471ca1920623dbee8e3d8e0e6d3a3b86..00b17ba3c07ba6db532035c37d5bf7cdd74e9f59 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -5,13 +5,14 @@ use crate::{ AsyncWindowContext, AvailableSpace, Bounds, BoxShadow, Context, Corners, CursorStyle, DevicePixels, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, FontId, GlobalElementId, GlyphId, Hsla, - ImageData, InputEvent, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeystrokeEvent, LayoutId, - Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseMoveEvent, MouseUpEvent, - Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, - PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams, - RenderSvgParams, ScaledPixels, Scene, Shadow, SharedString, Size, Style, SubscriberSet, - Subscription, Surface, TaffyLayoutEngine, Task, Underline, UnderlineStyle, View, VisualContext, - WeakView, WindowBounds, WindowOptions, SUBPIXEL_VARIANTS, + ImageData, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, KeystrokeEvent, LayoutId, + Model, ModelContext, Modifiers, MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent, + MouseUpEvent, Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, + PlatformInputHandler, PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, + RenderGlyphParams, RenderImageParams, RenderSvgParams, ScaledPixels, Scene, Shadow, + SharedString, Size, Style, SubscriberSet, Subscription, Surface, TaffyLayoutEngine, Task, + Underline, UnderlineStyle, View, VisualContext, WeakView, WindowBounds, WindowOptions, + SUBPIXEL_VARIANTS, }; use anyhow::{anyhow, Context as _, Result}; use collections::{FxHashMap, FxHashSet}; @@ -968,7 +969,7 @@ impl<'a> WindowContext<'a> { /// Register a mouse event listener on the window for the next frame. The type of event /// is determined by the first parameter of the given listener. When the next frame is rendered /// the listener will be cleared. - pub fn on_mouse_event( + pub fn on_mouse_event( &mut self, mut handler: impl FnMut(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { @@ -996,7 +997,7 @@ impl<'a> WindowContext<'a> { /// /// This is a fairly low-level method, so prefer using event handlers on elements unless you have /// a specific need to register a global listener. - pub fn on_key_event( + pub fn on_key_event( &mut self, listener: impl Fn(&Event, DispatchPhase, &mut WindowContext) + 'static, ) { @@ -1617,7 +1618,7 @@ impl<'a> WindowContext<'a> { } /// Dispatch a mouse or keyboard event on the window. - pub fn dispatch_event(&mut self, event: InputEvent) -> bool { + pub fn dispatch_event(&mut self, event: PlatformInput) -> bool { // Handlers may set this to false by calling `stop_propagation`. self.app.propagate_event = true; // Handlers may set this to true by calling `prevent_default`. @@ -1626,37 +1627,37 @@ impl<'a> WindowContext<'a> { let event = match event { // Track the mouse position with our own state, since accessing the platform // API for the mouse position can only occur on the main thread. - InputEvent::MouseMove(mouse_move) => { + PlatformInput::MouseMove(mouse_move) => { self.window.mouse_position = mouse_move.position; self.window.modifiers = mouse_move.modifiers; - InputEvent::MouseMove(mouse_move) + PlatformInput::MouseMove(mouse_move) } - InputEvent::MouseDown(mouse_down) => { + PlatformInput::MouseDown(mouse_down) => { self.window.mouse_position = mouse_down.position; self.window.modifiers = mouse_down.modifiers; - InputEvent::MouseDown(mouse_down) + PlatformInput::MouseDown(mouse_down) } - InputEvent::MouseUp(mouse_up) => { + PlatformInput::MouseUp(mouse_up) => { self.window.mouse_position = mouse_up.position; self.window.modifiers = mouse_up.modifiers; - InputEvent::MouseUp(mouse_up) + PlatformInput::MouseUp(mouse_up) } - InputEvent::MouseExited(mouse_exited) => { + PlatformInput::MouseExited(mouse_exited) => { self.window.modifiers = mouse_exited.modifiers; - InputEvent::MouseExited(mouse_exited) + PlatformInput::MouseExited(mouse_exited) } - InputEvent::ModifiersChanged(modifiers_changed) => { + PlatformInput::ModifiersChanged(modifiers_changed) => { self.window.modifiers = modifiers_changed.modifiers; - InputEvent::ModifiersChanged(modifiers_changed) + PlatformInput::ModifiersChanged(modifiers_changed) } - InputEvent::ScrollWheel(scroll_wheel) => { + PlatformInput::ScrollWheel(scroll_wheel) => { self.window.mouse_position = scroll_wheel.position; self.window.modifiers = scroll_wheel.modifiers; - InputEvent::ScrollWheel(scroll_wheel) + PlatformInput::ScrollWheel(scroll_wheel) } // Translate dragging and dropping of external files from the operating system // to internal drag and drop events. - InputEvent::FileDrop(file_drop) => match file_drop { + PlatformInput::FileDrop(file_drop) => match file_drop { FileDropEvent::Entered { position, paths } => { self.window.mouse_position = position; if self.active_drag.is_none() { @@ -1666,7 +1667,7 @@ impl<'a> WindowContext<'a> { cursor_offset: position, }); } - InputEvent::MouseMove(MouseMoveEvent { + PlatformInput::MouseMove(MouseMoveEvent { position, pressed_button: Some(MouseButton::Left), modifiers: Modifiers::default(), @@ -1674,7 +1675,7 @@ impl<'a> WindowContext<'a> { } FileDropEvent::Pending { position } => { self.window.mouse_position = position; - InputEvent::MouseMove(MouseMoveEvent { + PlatformInput::MouseMove(MouseMoveEvent { position, pressed_button: Some(MouseButton::Left), modifiers: Modifiers::default(), @@ -1683,21 +1684,21 @@ impl<'a> WindowContext<'a> { FileDropEvent::Submit { position } => { self.activate(true); self.window.mouse_position = position; - InputEvent::MouseUp(MouseUpEvent { + PlatformInput::MouseUp(MouseUpEvent { button: MouseButton::Left, position, modifiers: Modifiers::default(), click_count: 1, }) } - FileDropEvent::Exited => InputEvent::MouseUp(MouseUpEvent { + FileDropEvent::Exited => PlatformInput::MouseUp(MouseUpEvent { button: MouseButton::Left, position: Point::default(), modifiers: Modifiers::default(), click_count: 1, }), }, - InputEvent::KeyDown(_) | InputEvent::KeyUp(_) => event, + PlatformInput::KeyDown(_) | PlatformInput::KeyUp(_) => event, }; if let Some(any_mouse_event) = event.mouse_event() { @@ -2976,7 +2977,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { /// Add a listener for any mouse event that occurs in the window. /// This is a fairly low level method. /// Typically, you'll want to use methods on UI elements, which perform bounds checking etc. - pub fn on_mouse_event( + pub fn on_mouse_event( &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, ) { @@ -2989,7 +2990,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { } /// Register a callback to be invoked when the given Key Event is dispatched to the window. - pub fn on_key_event( + pub fn on_key_event( &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, ) { From a99ee5e599dbb491982fc052a3f093e536444db0 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 16 Jan 2024 22:30:44 -0800 Subject: [PATCH 3/5] Fix test failures --- crates/collab/src/tests/editor_tests.rs | 78 ++++++++++--------------- crates/gpui/src/app/test_context.rs | 5 ++ crates/search/src/buffer_search.rs | 8 +-- 3 files changed, 40 insertions(+), 51 deletions(-) diff --git a/crates/collab/src/tests/editor_tests.rs b/crates/collab/src/tests/editor_tests.rs index 0c3601b07531bf5c77459fd5530a31ba8ef68717..a5fa187d24acc93af3b2ff64dbf2ef96fffa7ea3 100644 --- a/crates/collab/src/tests/editor_tests.rs +++ b/crates/collab/src/tests/editor_tests.rs @@ -185,31 +185,27 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor( .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) .await .unwrap(); - let window_a = cx_a.add_empty_window(); - let editor_a = - window_a.build_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx)); + let cx_a = cx_a.add_empty_window(); + let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a), cx)); let mut editor_cx_a = EditorTestContext { - cx: VisualTestContext::from_window(window_a, cx_a), - window: window_a.into(), + cx: cx_a.clone(), + window: cx_a.handle(), editor: editor_a, assertion_cx: AssertionContextManager::new(), }; - let window_b = cx_b.add_empty_window(); - let mut cx_b = VisualTestContext::from_window(window_b, cx_b); - + let cx_b = cx_b.add_empty_window(); // Open a buffer as client B let buffer_b = project_b - .update(&mut cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) + .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx)) .await .unwrap(); - let editor_b = window_b.build_view(&mut cx_b, |cx| { - Editor::for_buffer(buffer_b, Some(project_b), cx) - }); + let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b), cx)); + let mut editor_cx_b = EditorTestContext { - cx: cx_b, - window: window_b.into(), + cx: cx_b.clone(), + window: cx_b.handle(), editor: editor_b, assertion_cx: AssertionContextManager::new(), }; @@ -311,10 +307,9 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) .await .unwrap(); - let window_b = cx_b.add_empty_window(); - let editor_b = window_b.build_view(cx_b, |cx| { - Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx) - }); + let cx_b = cx_b.add_empty_window(); + let editor_b = + cx_b.new_view(|cx| Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)); let fake_language_server = fake_language_servers.next().await.unwrap(); cx_a.background_executor.run_until_parked(); @@ -323,10 +318,8 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu assert!(!buffer.completion_triggers().is_empty()) }); - let mut cx_b = VisualTestContext::from_window(window_b, cx_b); - // Type a completion trigger character as the guest. - editor_b.update(&mut cx_b, |editor, cx| { + editor_b.update(cx_b, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([13..13])); editor.handle_input(".", cx); }); @@ -392,8 +385,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu }); // Confirm a completion on the guest. - - editor_b.update(&mut cx_b, |editor, cx| { + editor_b.update(cx_b, |editor, cx| { assert!(editor.context_menu_visible()); editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx); assert_eq!(editor.text(cx), "fn main() { a.first_method() }"); @@ -431,7 +423,7 @@ async fn test_collaborating_with_completion(cx_a: &mut TestAppContext, cx_b: &mu ); }); - buffer_b.read_with(&mut cx_b, |buffer, _| { + buffer_b.read_with(cx_b, |buffer, _| { assert_eq!( buffer.text(), "use d::SomeTrait;\nfn main() { a.first_method() }" @@ -960,7 +952,7 @@ async fn test_share_project( cx_c: &mut TestAppContext, ) { let executor = cx_a.executor(); - let window_b = cx_b.add_empty_window(); + let cx_b = cx_b.add_empty_window(); let mut server = TestServer::start(executor.clone()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -1075,7 +1067,7 @@ async fn test_share_project( .await .unwrap(); - let editor_b = window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx)); + let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, None, cx)); // Client A sees client B's selection executor.run_until_parked(); @@ -1089,8 +1081,7 @@ async fn test_share_project( }); // Edit the buffer as client B and see that edit as client A. - let mut cx_b = VisualTestContext::from_window(window_b, cx_b); - editor_b.update(&mut cx_b, |editor, cx| editor.handle_input("ok, ", cx)); + editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx)); executor.run_until_parked(); buffer_a.read_with(cx_a, |buffer, _| { @@ -1099,7 +1090,7 @@ async fn test_share_project( // Client B can invite client C on a project shared by client A. active_call_b - .update(&mut cx_b, |call, cx| { + .update(cx_b, |call, cx| { call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx) }) .await @@ -1190,12 +1181,8 @@ async fn test_on_input_format_from_host_to_guest( .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) .await .unwrap(); - let window_a = cx_a.add_empty_window(); - let editor_a = window_a - .update(cx_a, |_, cx| { - cx.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)) - }) - .unwrap(); + let cx_a = cx_a.add_empty_window(); + let editor_a = cx_a.new_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)); let fake_language_server = fake_language_servers.next().await.unwrap(); executor.run_until_parked(); @@ -1226,10 +1213,9 @@ async fn test_on_input_format_from_host_to_guest( .await .unwrap(); - let mut cx_a = VisualTestContext::from_window(window_a, cx_a); // Type a on type formatting trigger character as the guest. cx_a.focus_view(&editor_a); - editor_a.update(&mut cx_a, |editor, cx| { + editor_a.update(cx_a, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([13..13])); editor.handle_input(">", cx); }); @@ -1241,7 +1227,7 @@ async fn test_on_input_format_from_host_to_guest( }); // Undo should remove LSP edits first - editor_a.update(&mut cx_a, |editor, cx| { + editor_a.update(cx_a, |editor, cx| { assert_eq!(editor.text(cx), "fn main() { a>~< }"); editor.undo(&Undo, cx); assert_eq!(editor.text(cx), "fn main() { a> }"); @@ -1252,7 +1238,7 @@ async fn test_on_input_format_from_host_to_guest( assert_eq!(buffer.text(), "fn main() { a> }") }); - editor_a.update(&mut cx_a, |editor, cx| { + editor_a.update(cx_a, |editor, cx| { assert_eq!(editor.text(cx), "fn main() { a> }"); editor.undo(&Undo, cx); assert_eq!(editor.text(cx), "fn main() { a }"); @@ -1323,17 +1309,15 @@ async fn test_on_input_format_from_guest_to_host( .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx)) .await .unwrap(); - let window_b = cx_b.add_empty_window(); - let editor_b = window_b.build_view(cx_b, |cx| { - Editor::for_buffer(buffer_b, Some(project_b.clone()), cx) - }); + let cx_b = cx_b.add_empty_window(); + let editor_b = cx_b.new_view(|cx| Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)); let fake_language_server = fake_language_servers.next().await.unwrap(); executor.run_until_parked(); - let mut cx_b = VisualTestContext::from_window(window_b, cx_b); + // Type a on type formatting trigger character as the guest. cx_b.focus_view(&editor_b); - editor_b.update(&mut cx_b, |editor, cx| { + editor_b.update(cx_b, |editor, cx| { editor.change_selections(None, cx, |s| s.select_ranges([13..13])); editor.handle_input(":", cx); }); @@ -1374,7 +1358,7 @@ async fn test_on_input_format_from_guest_to_host( }); // Undo should remove LSP edits first - editor_b.update(&mut cx_b, |editor, cx| { + editor_b.update(cx_b, |editor, cx| { assert_eq!(editor.text(cx), "fn main() { a:~: }"); editor.undo(&Undo, cx); assert_eq!(editor.text(cx), "fn main() { a: }"); @@ -1385,7 +1369,7 @@ async fn test_on_input_format_from_guest_to_host( assert_eq!(buffer.text(), "fn main() { a: }") }); - editor_b.update(&mut cx_b, |editor, cx| { + editor_b.update(cx_b, |editor, cx| { assert_eq!(editor.text(cx), "fn main() { a: }"); editor.undo(&Undo, cx); assert_eq!(editor.text(cx), "fn main() { a }"); diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 556c104f69b9da1a20b32cb0fa325f5aeb975ce9..41cb722081b60b9b404244ed0e12e126dd63b279 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -568,6 +568,11 @@ pub struct VisualTestContext { } impl<'a> VisualTestContext { + /// Get the underlying window handle underlying this context. + pub fn handle(&self) -> AnyWindowHandle { + self.window + } + /// Provides the `WindowContext` for the duration of the closure. pub fn update(&mut self, f: impl FnOnce(&mut WindowContext) -> R) -> R { self.cx.update_window(self.window, |_, cx| f(cx)).unwrap() diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index e217a7ab73cd2fd1aaa540f1b56cf13b7ec1c84b..ed2654de36508de7b20f2f522bc51b245354bd73 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1081,7 +1081,7 @@ mod tests { use super::*; use editor::{DisplayPoint, Editor}; - use gpui::{Context, EmptyView, Hsla, TestAppContext, VisualTestContext}; + use gpui::{Context, Hsla, TestAppContext, VisualTestContext}; use language::Buffer; use smol::stream::StreamExt as _; use unindent::Unindent as _; @@ -1114,7 +1114,7 @@ mod tests { .unindent(), ) }); - let (_, cx) = cx.add_window_view(|_| EmptyView {}); + let cx = cx.add_empty_window(); let editor = cx.new_view(|cx| Editor::for_buffer(buffer.clone(), None, cx)); let search_bar = cx.new_view(|cx| { @@ -1461,7 +1461,7 @@ mod tests { "Should pick a query with multiple results" ); let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), buffer_text)); - let window = cx.add_window(|_| EmptyView {}); + let window = cx.add_window(|_| ()); let editor = window.build_view(cx, |cx| Editor::for_buffer(buffer.clone(), None, cx)); @@ -1657,7 +1657,7 @@ mod tests { "# .unindent(); let buffer = cx.new_model(|cx| Buffer::new(0, cx.entity_id().as_u64(), buffer_text)); - let (_, cx) = cx.add_window_view(|_| EmptyView {}); + let cx = cx.add_empty_window(); let editor = cx.new_view(|cx| Editor::for_buffer(buffer.clone(), None, cx)); From 80852c3e182ccdd4e1237b5e7770efa33ea9ec28 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Tue, 16 Jan 2024 22:34:15 -0800 Subject: [PATCH 4/5] Add documentation to the new test --- crates/gpui/src/elements/list.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 96067d24000033e5c3b292eea3d70871887be7cc..9d49149e7abe7333f42abe5eb07b32e697098774 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -598,20 +598,30 @@ mod test { div().h(px(10.)).w_full().into_any() }); + // Ensure that the list is scrolled to the top + state.scroll_to(gpui::ListOffset { + item_ix: 0, + offset_in_item: px(0.0), + }); + + // Paint cx.draw( point(px(0.), px(0.)), size(px(100.), px(20.)).into(), |_| list(state.clone()).w_full().h_full().z_index(10).into_any(), ); + // Reset state.reset(5); + // And then recieve a scroll event _before_ the next paint cx.simulate_event(ScrollWheelEvent { position: point(px(1.), px(1.)), delta: ScrollDelta::Pixels(point(px(0.), px(-500.))), ..Default::default() }); + // Scroll position should stay at the top of the list assert_eq!(state.logical_scroll_top().item_ix, 0); assert_eq!(state.logical_scroll_top().offset_in_item, px(0.)); From 6db18e89722522aece9cba01098d2d41c678c3c4 Mon Sep 17 00:00:00 2001 From: Mikayla Date: Wed, 17 Jan 2024 13:00:29 -0800 Subject: [PATCH 5/5] Drop scroll events if there's been a reset co-authored-by: Nathan co-authored-by: Conrad --- crates/gpui/src/app/test_context.rs | 3 +- crates/gpui/src/elements/list.rs | 201 ++++++++++++++-------------- crates/gpui/src/keymap/matcher.rs | 1 - crates/gpui/src/window.rs | 1 - 4 files changed, 102 insertions(+), 104 deletions(-) diff --git a/crates/gpui/src/app/test_context.rs b/crates/gpui/src/app/test_context.rs index 41cb722081b60b9b404244ed0e12e126dd63b279..d95558f058a91bb4a7cd9a3ab347ee10584bffba 100644 --- a/crates/gpui/src/app/test_context.rs +++ b/crates/gpui/src/app/test_context.rs @@ -643,7 +643,8 @@ impl<'a> VisualTestContext { /// Simulate an event from the platform, e.g. a SrollWheelEvent /// Make sure you've called [VisualTestContext::draw] first! pub fn simulate_event(&mut self, event: E) { - self.update(|cx| cx.dispatch_event(event.to_platform_input())); + self.test_window(self.window) + .simulate_input(event.to_platform_input()); self.background_executor.run_until_parked(); } diff --git a/crates/gpui/src/elements/list.rs b/crates/gpui/src/elements/list.rs index 9d49149e7abe7333f42abe5eb07b32e697098774..c0874a8dd4116275846edc24c1ebcfcfd9d752b6 100644 --- a/crates/gpui/src/elements/list.rs +++ b/crates/gpui/src/elements/list.rs @@ -28,9 +28,9 @@ struct StateInner { render_item: Box AnyElement>, items: SumTree, logical_scroll_top: Option, - pending_scroll_delta: Pixels, alignment: ListAlignment, overdraw: Pixels, + reset: bool, #[allow(clippy::type_complexity)] scroll_handler: Option>, } @@ -93,12 +93,17 @@ impl ListState { alignment: orientation, overdraw, scroll_handler: None, - pending_scroll_delta: px(0.), + reset: false, }))) } + /// Reset this instantiation of the list state. + /// + /// Note that this will cause scroll events to be dropped until the next paint. pub fn reset(&self, element_count: usize) { let state = &mut *self.0.borrow_mut(); + state.reset = true; + state.logical_scroll_top = None; state.items = SumTree::new(); state @@ -154,11 +159,13 @@ impl ListState { scroll_top.item_ix = item_count; scroll_top.offset_in_item = px(0.); } + state.logical_scroll_top = Some(scroll_top); } pub fn scroll_to_reveal_item(&self, ix: usize) { let state = &mut *self.0.borrow_mut(); + let mut scroll_top = state.logical_scroll_top(); let height = state .last_layout_bounds @@ -189,9 +196,9 @@ impl ListState { /// Get the bounds for the given item in window coordinates. pub fn bounds_for_item(&self, ix: usize) -> Option> { let state = &*self.0.borrow(); + let bounds = state.last_layout_bounds.unwrap_or_default(); let scroll_top = state.logical_scroll_top(); - if ix < scroll_top.item_ix { return None; } @@ -232,7 +239,11 @@ impl StateInner { delta: Point, cx: &mut WindowContext, ) { - // self.pending_scroll_delta += delta.y; + // Drop scroll events after a reset, since we can't calculate + // the new logical scroll top without the item heights + if self.reset { + return; + } let scroll_max = (self.items.summary().height - height).max(px(0.)); let new_scroll_top = (self.scroll_top(scroll_top) - delta.y) @@ -329,6 +340,8 @@ impl Element for List { ) { let state = &mut *self.state.0.borrow_mut(); + state.reset = false; + // If the width of the list has changed, invalidate all cached item heights if state.last_layout_bounds.map_or(true, |last_bounds| { last_bounds.size.width != bounds.size.width @@ -352,117 +365,104 @@ impl Element for List { let mut cursor = old_items.cursor::(); - loop { - // Render items after the scroll top, including those in the trailing overdraw - cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); - for (ix, item) in cursor.by_ref().enumerate() { - let visible_height = rendered_height - scroll_top.offset_in_item; - if visible_height >= bounds.size.height + state.overdraw { - break; - } - - // Use the previously cached height if available - let mut height = if let ListItem::Rendered { height } = item { - Some(*height) - } else { - None - }; - - // If we're within the visible area or the height wasn't cached, render and measure the item's element - if visible_height < bounds.size.height || height.is_none() { - let mut element = (state.render_item)(scroll_top.item_ix + ix, cx); - let element_size = element.measure(available_item_space, cx); - height = Some(element_size.height); - if visible_height < bounds.size.height { - item_elements.push_back(element); - } - } - - let height = height.unwrap(); - rendered_height += height; - measured_items.push_back(ListItem::Rendered { height }); + // Render items after the scroll top, including those in the trailing overdraw + cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); + for (ix, item) in cursor.by_ref().enumerate() { + let visible_height = rendered_height - scroll_top.offset_in_item; + if visible_height >= bounds.size.height + state.overdraw { + break; } - // Prepare to start walking upward from the item at the scroll top. - cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); - - // If the rendered items do not fill the visible region, then adjust - // the scroll top upward. - if rendered_height - scroll_top.offset_in_item < bounds.size.height { - while rendered_height < bounds.size.height { - cursor.prev(&()); - if cursor.item().is_some() { - let mut element = (state.render_item)(cursor.start().0, cx); - let element_size = element.measure(available_item_space, cx); - - rendered_height += element_size.height; - measured_items.push_front(ListItem::Rendered { - height: element_size.height, - }); - item_elements.push_front(element) - } else { - break; - } + // Use the previously cached height if available + let mut height = if let ListItem::Rendered { height } = item { + Some(*height) + } else { + None + }; + + // If we're within the visible area or the height wasn't cached, render and measure the item's element + if visible_height < bounds.size.height || height.is_none() { + let mut element = (state.render_item)(scroll_top.item_ix + ix, cx); + let element_size = element.measure(available_item_space, cx); + height = Some(element_size.height); + if visible_height < bounds.size.height { + item_elements.push_back(element); } + } - scroll_top = ListOffset { - item_ix: cursor.start().0, - offset_in_item: rendered_height - bounds.size.height, - }; + let height = height.unwrap(); + rendered_height += height; + measured_items.push_back(ListItem::Rendered { height }); + } - match state.alignment { - ListAlignment::Top => { - scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.)); - state.logical_scroll_top = Some(scroll_top); - } - ListAlignment::Bottom => { - scroll_top = ListOffset { - item_ix: cursor.start().0, - offset_in_item: rendered_height - bounds.size.height, - }; - state.logical_scroll_top = None; - } - }; - } + // Prepare to start walking upward from the item at the scroll top. + cursor.seek(&Count(scroll_top.item_ix), Bias::Right, &()); - // Measure items in the leading overdraw - let mut leading_overdraw = scroll_top.offset_in_item; - while leading_overdraw < state.overdraw { + // If the rendered items do not fill the visible region, then adjust + // the scroll top upward. + if rendered_height - scroll_top.offset_in_item < bounds.size.height { + while rendered_height < bounds.size.height { cursor.prev(&()); - if let Some(item) = cursor.item() { - let height = if let ListItem::Rendered { height } = item { - *height - } else { - let mut element = (state.render_item)(cursor.start().0, cx); - element.measure(available_item_space, cx).height - }; + if cursor.item().is_some() { + let mut element = (state.render_item)(cursor.start().0, cx); + let element_size = element.measure(available_item_space, cx); - leading_overdraw += height; - measured_items.push_front(ListItem::Rendered { height }); + rendered_height += element_size.height; + measured_items.push_front(ListItem::Rendered { + height: element_size.height, + }); + item_elements.push_front(element) } else { break; } } - let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len()); - let mut cursor = old_items.cursor::(); - let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &()); - new_items.extend(measured_items, &()); - cursor.seek(&Count(measured_range.end), Bias::Right, &()); - new_items.append(cursor.suffix(&()), &()); + scroll_top = ListOffset { + item_ix: cursor.start().0, + offset_in_item: rendered_height - bounds.size.height, + }; - state.items = new_items; - state.last_layout_bounds = Some(bounds); + match state.alignment { + ListAlignment::Top => { + scroll_top.offset_in_item = scroll_top.offset_in_item.max(px(0.)); + state.logical_scroll_top = Some(scroll_top); + } + ListAlignment::Bottom => { + scroll_top = ListOffset { + item_ix: cursor.start().0, + offset_in_item: rendered_height - bounds.size.height, + }; + state.logical_scroll_top = None; + } + }; + } - // if !state.pending_scroll_delta.is_zero() { - // // Do scroll manipulation + // Measure items in the leading overdraw + let mut leading_overdraw = scroll_top.offset_in_item; + while leading_overdraw < state.overdraw { + cursor.prev(&()); + if let Some(item) = cursor.item() { + let height = if let ListItem::Rendered { height } = item { + *height + } else { + let mut element = (state.render_item)(cursor.start().0, cx); + element.measure(available_item_space, cx).height + }; - // state.pending_scroll_delta = px(0.); - // } else { - break; - // } + leading_overdraw += height; + measured_items.push_front(ListItem::Rendered { height }); + } else { + break; + } } + let measured_range = cursor.start().0..(cursor.start().0 + measured_items.len()); + let mut cursor = old_items.cursor::(); + let mut new_items = cursor.slice(&Count(measured_range.start), Bias::Right, &()); + new_items.extend(measured_items, &()); + cursor.seek(&Count(measured_range.end), Bias::Right, &()); + new_items.append(cursor.suffix(&()), &()); + // Paint the visible items cx.with_content_mask(Some(ContentMask { bounds }), |cx| { let mut item_origin = bounds.origin; @@ -474,12 +474,13 @@ impl Element for List { } }); + state.items = new_items; + state.last_layout_bounds = Some(bounds); + let list_state = self.state.clone(); let height = bounds.size.height; - dbg!("scroll is being bound"); cx.on_mouse_event(move |event: &ScrollWheelEvent, phase, cx| { - dbg!("scroll dispatched!"); if phase == DispatchPhase::Bubble && bounds.contains(&event.position) && cx.was_top_layer(&event.position, cx.stacking_order()) @@ -624,7 +625,5 @@ mod test { // Scroll position should stay at the top of the list assert_eq!(state.logical_scroll_top().item_ix, 0); assert_eq!(state.logical_scroll_top().offset_in_item, px(0.)); - - panic!("We should not get here yet!") } } diff --git a/crates/gpui/src/keymap/matcher.rs b/crates/gpui/src/keymap/matcher.rs index 5410ddce06e9ca999aaee0911fd7a69d6a0e61c8..491dee6895fe391a4b93233cf6b861875deac803 100644 --- a/crates/gpui/src/keymap/matcher.rs +++ b/crates/gpui/src/keymap/matcher.rs @@ -209,7 +209,6 @@ mod tests { ); assert!(!matcher.has_pending_keystrokes()); - eprintln!("PROBLEM AREA"); // If a is prefixed, C will not be dispatched because there // was a pending binding for it assert_eq!( diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 00b17ba3c07ba6db532035c37d5bf7cdd74e9f59..11a720bf1f17aab827a581fff5f3a4b1b63b8379 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -1717,7 +1717,6 @@ impl<'a> WindowContext<'a> { .mouse_listeners .remove(&event.type_id()) { - dbg!(handlers.len()); // Because handlers may add other handlers, we sort every time. handlers.sort_by(|(a, _, _), (b, _, _)| a.cmp(b));