mirror of
https://github.com/zed-industries/zed.git
synced 2025-02-03 00:39:51 +00:00
Merge pull request #1515 from zed-industries/drag-and-drop
Drag and drop
This commit is contained in:
commit
579c84b5e4
27 changed files with 2124 additions and 758 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -1571,6 +1571,14 @@ version = "0.15.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "drag_and_drop"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"collections",
|
||||
"gpui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dwrote"
|
||||
version = "0.11.0"
|
||||
|
@ -6935,6 +6943,7 @@ dependencies = [
|
|||
"clock",
|
||||
"collections",
|
||||
"context_menu",
|
||||
"drag_and_drop",
|
||||
"futures",
|
||||
"gpui",
|
||||
"language",
|
||||
|
|
|
@ -566,7 +566,7 @@ impl ContactsPanel {
|
|||
button
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
let project = project_handle.upgrade(cx.deref_mut());
|
||||
let project = project_handle.upgrade(cx.app);
|
||||
cx.dispatch_action(ToggleProjectOnline { project })
|
||||
})
|
||||
.with_tooltip::<ToggleOnline, _>(
|
||||
|
|
15
crates/drag_and_drop/Cargo.toml
Normal file
15
crates/drag_and_drop/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "drag_and_drop"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
path = "src/drag_and_drop.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
gpui = { path = "../gpui" }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
154
crates/drag_and_drop/src/drag_and_drop.rs
Normal file
154
crates/drag_and_drop/src/drag_and_drop.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
use std::{any::Any, rc::Rc};
|
||||
|
||||
use gpui::{
|
||||
elements::{Container, MouseEventHandler},
|
||||
geometry::vector::Vector2F,
|
||||
scene::DragRegionEvent,
|
||||
Element, ElementBox, EventContext, MouseButton, RenderContext, View, ViewContext,
|
||||
WeakViewHandle,
|
||||
};
|
||||
|
||||
struct State<V: View> {
|
||||
position: Vector2F,
|
||||
region_offset: Vector2F,
|
||||
payload: Rc<dyn Any + 'static>,
|
||||
render: Rc<dyn Fn(Rc<dyn Any>, &mut RenderContext<V>) -> ElementBox>,
|
||||
}
|
||||
|
||||
impl<V: View> Clone for State<V> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
position: self.position.clone(),
|
||||
region_offset: self.region_offset.clone(),
|
||||
payload: self.payload.clone(),
|
||||
render: self.render.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DragAndDrop<V: View> {
|
||||
parent: WeakViewHandle<V>,
|
||||
currently_dragged: Option<State<V>>,
|
||||
}
|
||||
|
||||
impl<V: View> DragAndDrop<V> {
|
||||
pub fn new(parent: WeakViewHandle<V>, cx: &mut ViewContext<V>) -> Self {
|
||||
// TODO: Figure out if detaching here would result in a memory leak
|
||||
cx.observe_global::<Self, _>(|cx| {
|
||||
if let Some(parent) = cx.global::<Self>().parent.upgrade(cx) {
|
||||
parent.update(cx, |_, cx| cx.notify())
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
|
||||
Self {
|
||||
parent,
|
||||
currently_dragged: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn currently_dragged<T: Any>(&self) -> Option<(Vector2F, Rc<T>)> {
|
||||
self.currently_dragged.as_ref().and_then(
|
||||
|State {
|
||||
position, payload, ..
|
||||
}| {
|
||||
payload
|
||||
.clone()
|
||||
.downcast::<T>()
|
||||
.ok()
|
||||
.map(|payload| (position.clone(), payload))
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
pub fn dragging<T: Any>(
|
||||
event: DragRegionEvent,
|
||||
payload: Rc<T>,
|
||||
cx: &mut EventContext,
|
||||
render: Rc<impl 'static + Fn(&T, &mut RenderContext<V>) -> ElementBox>,
|
||||
) {
|
||||
cx.update_global::<Self, _, _>(|this, cx| {
|
||||
let region_offset = if let Some(previous_state) = this.currently_dragged.as_ref() {
|
||||
previous_state.region_offset
|
||||
} else {
|
||||
event.region.origin() - event.prev_mouse_position
|
||||
};
|
||||
|
||||
this.currently_dragged = Some(State {
|
||||
region_offset,
|
||||
position: event.position,
|
||||
payload,
|
||||
render: Rc::new(move |payload, cx| {
|
||||
render(payload.downcast_ref::<T>().unwrap(), cx)
|
||||
}),
|
||||
});
|
||||
|
||||
if let Some(parent) = this.parent.upgrade(cx) {
|
||||
parent.update(cx, |_, cx| cx.notify())
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn render(cx: &mut RenderContext<V>) -> Option<ElementBox> {
|
||||
let currently_dragged = cx.global::<Self>().currently_dragged.clone();
|
||||
|
||||
currently_dragged.map(
|
||||
|State {
|
||||
region_offset,
|
||||
position,
|
||||
payload,
|
||||
render,
|
||||
}| {
|
||||
let position = position + region_offset;
|
||||
|
||||
MouseEventHandler::new::<Self, _, _>(0, cx, |_, cx| {
|
||||
Container::new(render(payload, cx))
|
||||
.with_margin_left(position.x())
|
||||
.with_margin_top(position.y())
|
||||
.aligned()
|
||||
.top()
|
||||
.left()
|
||||
.boxed()
|
||||
})
|
||||
.on_up(MouseButton::Left, |_, cx| {
|
||||
cx.defer(|cx| {
|
||||
cx.update_global::<Self, _, _>(|this, _| this.currently_dragged.take());
|
||||
});
|
||||
cx.propogate_event();
|
||||
})
|
||||
// Don't block hover events or invalidations
|
||||
.with_hoverable(false)
|
||||
.boxed()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Draggable {
|
||||
fn as_draggable<V: View, P: Any>(
|
||||
self,
|
||||
payload: P,
|
||||
render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
impl Draggable for MouseEventHandler {
|
||||
fn as_draggable<V: View, P: Any>(
|
||||
self,
|
||||
payload: P,
|
||||
render: impl 'static + Fn(&P, &mut RenderContext<V>) -> ElementBox,
|
||||
) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let payload = Rc::new(payload);
|
||||
let render = Rc::new(render);
|
||||
self.on_drag(MouseButton::Left, move |e, cx| {
|
||||
let payload = payload.clone();
|
||||
let render = render.clone();
|
||||
DragAndDrop::<V>::dragging(e, payload, cx, render)
|
||||
})
|
||||
}
|
||||
}
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
platform::{self, KeyDownEvent, Platform, PromptLevel, WindowOptions},
|
||||
presenter::Presenter,
|
||||
util::post_inc,
|
||||
AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseRegionId,
|
||||
AssetCache, AssetSource, ClipboardItem, FontCache, InputHandler, MouseButton, MouseRegionId,
|
||||
PathPromptOptions, TextLayoutCache,
|
||||
};
|
||||
pub use action::*;
|
||||
|
@ -490,6 +490,7 @@ impl TestAppContext {
|
|||
keystroke: keystroke.clone(),
|
||||
is_held,
|
||||
}),
|
||||
false,
|
||||
cx,
|
||||
) {
|
||||
return true;
|
||||
|
@ -576,8 +577,7 @@ impl TestAppContext {
|
|||
view_type: PhantomData,
|
||||
titlebar_height: 0.,
|
||||
hovered_region_ids: Default::default(),
|
||||
clicked_region_id: None,
|
||||
right_clicked_region_id: None,
|
||||
clicked_region_ids: None,
|
||||
refreshing: false,
|
||||
};
|
||||
f(view, &mut render_cx)
|
||||
|
@ -1285,8 +1285,7 @@ impl MutableAppContext {
|
|||
view_id,
|
||||
titlebar_height,
|
||||
hovered_region_ids: Default::default(),
|
||||
clicked_region_id: None,
|
||||
right_clicked_region_id: None,
|
||||
clicked_region_ids: None,
|
||||
refreshing: false,
|
||||
})
|
||||
.unwrap(),
|
||||
|
@ -1970,7 +1969,7 @@ impl MutableAppContext {
|
|||
}
|
||||
}
|
||||
|
||||
presenter.borrow_mut().dispatch_event(event, cx)
|
||||
presenter.borrow_mut().dispatch_event(event, false, cx)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
@ -4029,8 +4028,7 @@ pub struct RenderParams {
|
|||
pub view_id: usize,
|
||||
pub titlebar_height: f32,
|
||||
pub hovered_region_ids: HashSet<MouseRegionId>,
|
||||
pub clicked_region_id: Option<MouseRegionId>,
|
||||
pub right_clicked_region_id: Option<MouseRegionId>,
|
||||
pub clicked_region_ids: Option<(Vec<MouseRegionId>, MouseButton)>,
|
||||
pub refreshing: bool,
|
||||
}
|
||||
|
||||
|
@ -4039,8 +4037,7 @@ pub struct RenderContext<'a, T: View> {
|
|||
pub(crate) view_id: usize,
|
||||
pub(crate) view_type: PhantomData<T>,
|
||||
pub(crate) hovered_region_ids: HashSet<MouseRegionId>,
|
||||
pub(crate) clicked_region_id: Option<MouseRegionId>,
|
||||
pub(crate) right_clicked_region_id: Option<MouseRegionId>,
|
||||
pub(crate) clicked_region_ids: Option<(Vec<MouseRegionId>, MouseButton)>,
|
||||
pub app: &'a mut MutableAppContext,
|
||||
pub titlebar_height: f32,
|
||||
pub refreshing: bool,
|
||||
|
@ -4049,8 +4046,7 @@ pub struct RenderContext<'a, T: View> {
|
|||
#[derive(Clone, Copy, Default)]
|
||||
pub struct MouseState {
|
||||
pub hovered: bool,
|
||||
pub clicked: bool,
|
||||
pub right_clicked: bool,
|
||||
pub clicked: Option<MouseButton>,
|
||||
}
|
||||
|
||||
impl<'a, V: View> RenderContext<'a, V> {
|
||||
|
@ -4062,8 +4058,7 @@ impl<'a, V: View> RenderContext<'a, V> {
|
|||
view_type: PhantomData,
|
||||
titlebar_height: params.titlebar_height,
|
||||
hovered_region_ids: params.hovered_region_ids.clone(),
|
||||
clicked_region_id: params.clicked_region_id,
|
||||
right_clicked_region_id: params.right_clicked_region_id,
|
||||
clicked_region_ids: params.clicked_region_ids.clone(),
|
||||
refreshing: params.refreshing,
|
||||
}
|
||||
}
|
||||
|
@ -4087,8 +4082,13 @@ impl<'a, V: View> RenderContext<'a, V> {
|
|||
};
|
||||
MouseState {
|
||||
hovered: self.hovered_region_ids.contains(®ion_id),
|
||||
clicked: self.clicked_region_id == Some(region_id),
|
||||
right_clicked: self.right_clicked_region_id == Some(region_id),
|
||||
clicked: self.clicked_region_ids.as_ref().and_then(|(ids, button)| {
|
||||
if ids.contains(®ion_id) {
|
||||
Some(*button)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6041,6 +6041,7 @@ mod tests {
|
|||
cmd: false,
|
||||
click_count: 1,
|
||||
}),
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
assert_eq!(mouse_down_count.load(SeqCst), 1);
|
||||
|
|
|
@ -24,6 +24,8 @@ pub struct ContainerStyle {
|
|||
pub padding: Padding,
|
||||
#[serde(rename = "background")]
|
||||
pub background_color: Option<Color>,
|
||||
#[serde(rename = "overlay")]
|
||||
pub overlay_color: Option<Color>,
|
||||
#[serde(default)]
|
||||
pub border: Border,
|
||||
#[serde(default)]
|
||||
|
@ -104,6 +106,11 @@ impl Container {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_padding_top(mut self, padding: f32) -> Self {
|
||||
self.style.padding.top = padding;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_padding_bottom(mut self, padding: f32) -> Self {
|
||||
self.style.padding.bottom = padding;
|
||||
self
|
||||
|
@ -114,6 +121,11 @@ impl Container {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn with_overlay_color(mut self, color: Color) -> Self {
|
||||
self.style.overlay_color = Some(color);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_border(mut self, border: Border) -> Self {
|
||||
self.style.border = border;
|
||||
self
|
||||
|
@ -240,7 +252,7 @@ impl Element for Container {
|
|||
cx.scene.push_layer(None);
|
||||
cx.scene.push_quad(Quad {
|
||||
bounds: quad_bounds,
|
||||
background: Default::default(),
|
||||
background: self.style.overlay_color,
|
||||
border: self.style.border,
|
||||
corner_radius: self.style.corner_radius,
|
||||
});
|
||||
|
@ -259,6 +271,17 @@ impl Element for Container {
|
|||
self.style.border.top_width(),
|
||||
);
|
||||
self.child.paint(child_origin, visible_bounds, cx);
|
||||
|
||||
if self.style.overlay_color.is_some() {
|
||||
cx.scene.push_layer(None);
|
||||
cx.scene.push_quad(Quad {
|
||||
bounds: quad_bounds,
|
||||
background: self.style.overlay_color,
|
||||
border: Default::default(),
|
||||
corner_radius: 0.,
|
||||
});
|
||||
cx.scene.pop_layer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -699,7 +699,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,
|
||||
|
@ -808,7 +808,7 @@ mod tests {
|
|||
height,
|
||||
delta,
|
||||
true,
|
||||
&mut presenter.build_event_context(cx),
|
||||
&mut presenter.build_event_context(&mut Default::default(), cx),
|
||||
);
|
||||
}
|
||||
30..=34 => {
|
||||
|
|
|
@ -5,10 +5,12 @@ use crate::{
|
|||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
platform::CursorStyle,
|
||||
scene::{CursorRegion, HandlerSet},
|
||||
scene::{
|
||||
ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragRegionEvent,
|
||||
HandlerSet, HoverRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent,
|
||||
},
|
||||
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MeasurementContext,
|
||||
MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseState, PaintContext,
|
||||
RenderContext, SizeConstraint, View,
|
||||
MouseButton, MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::{any::TypeId, ops::Range};
|
||||
|
@ -18,6 +20,7 @@ pub struct MouseEventHandler {
|
|||
discriminant: (TypeId, usize),
|
||||
cursor_style: Option<CursorStyle>,
|
||||
handlers: HandlerSet,
|
||||
hoverable: bool,
|
||||
padding: Padding,
|
||||
}
|
||||
|
||||
|
@ -33,6 +36,7 @@ impl MouseEventHandler {
|
|||
cursor_style: None,
|
||||
discriminant: (TypeId::of::<Tag>(), id),
|
||||
handlers: Default::default(),
|
||||
hoverable: true,
|
||||
padding: Default::default(),
|
||||
}
|
||||
}
|
||||
|
@ -42,19 +46,41 @@ impl MouseEventHandler {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn capture_all(mut self) -> Self {
|
||||
self.handlers = HandlerSet::capture_all();
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_move(
|
||||
mut self,
|
||||
handler: impl Fn(MoveRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_move(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_down(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_up(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_click(button, handler);
|
||||
self
|
||||
|
@ -63,16 +89,25 @@ impl MouseEventHandler {
|
|||
pub fn on_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_down_out(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_up_out(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_drag(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_drag(button, handler);
|
||||
self
|
||||
|
@ -80,12 +115,17 @@ impl MouseEventHandler {
|
|||
|
||||
pub fn on_hover(
|
||||
mut self,
|
||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_hover(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
|
||||
self.hoverable = is_hoverable;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_padding(mut self, padding: Padding) -> Self {
|
||||
self.padding = padding;
|
||||
self
|
||||
|
@ -127,12 +167,15 @@ impl Element for MouseEventHandler {
|
|||
});
|
||||
}
|
||||
|
||||
cx.scene.push_mouse_region(MouseRegion::from_handlers(
|
||||
cx.current_view_id(),
|
||||
Some(self.discriminant),
|
||||
hit_bounds,
|
||||
self.handlers.clone(),
|
||||
));
|
||||
cx.scene.push_mouse_region(
|
||||
MouseRegion::from_handlers(
|
||||
cx.current_view_id(),
|
||||
Some(self.discriminant),
|
||||
hit_bounds,
|
||||
self.handlers.clone(),
|
||||
)
|
||||
.with_hoverable(self.hoverable),
|
||||
);
|
||||
|
||||
self.child.paint(bounds.origin(), visible_bounds, cx);
|
||||
}
|
||||
|
|
|
@ -7,8 +7,8 @@ use crate::{
|
|||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json::json,
|
||||
presenter::MeasurementContext,
|
||||
Action, Axis, ElementStateHandle, LayoutContext, MouseMovedEvent, PaintContext, RenderContext,
|
||||
SizeConstraint, Task, View,
|
||||
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint,
|
||||
Task, View,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
|
@ -93,10 +93,11 @@ impl Tooltip {
|
|||
};
|
||||
let child =
|
||||
MouseEventHandler::new::<MouseEventHandlerState<Tag>, _, _>(id, cx, |_, _| child)
|
||||
.on_hover(move |hover, MouseMovedEvent { position, .. }, cx| {
|
||||
.on_hover(move |e, cx| {
|
||||
let position = e.position;
|
||||
let window_id = cx.window_id();
|
||||
if let Some(view_id) = cx.view_id() {
|
||||
if hover {
|
||||
if e.started {
|
||||
if !state.visible.get() {
|
||||
state.position.set(position);
|
||||
|
||||
|
|
|
@ -6,12 +6,15 @@ use crate::{
|
|||
json::{self, ToJson},
|
||||
keymap::Keystroke,
|
||||
platform::{CursorStyle, Event},
|
||||
scene::{CursorRegion, MouseRegionEvent},
|
||||
scene::{
|
||||
ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragRegionEvent,
|
||||
HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent,
|
||||
},
|
||||
text_layout::TextLayoutCache,
|
||||
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity,
|
||||
FontSystem, ModelHandle, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseRegionId,
|
||||
ParentId, ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle,
|
||||
UpgradeViewHandle, View, ViewHandle, WeakModelHandle, WeakViewHandle,
|
||||
FontSystem, ModelHandle, MouseButton, MouseMovedEvent, MouseRegion, MouseRegionId, ParentId,
|
||||
ReadModel, ReadView, RenderContext, RenderParams, Scene, UpgradeModelHandle, UpgradeViewHandle,
|
||||
View, ViewHandle, WeakModelHandle, WeakViewHandle,
|
||||
};
|
||||
use collections::{HashMap, HashSet};
|
||||
use pathfinder_geometry::vector::{vec2f, Vector2F};
|
||||
|
@ -31,11 +34,11 @@ pub struct Presenter {
|
|||
font_cache: Arc<FontCache>,
|
||||
text_layout_cache: TextLayoutCache,
|
||||
asset_cache: Arc<AssetCache>,
|
||||
last_mouse_moved_event: Option<MouseMovedEvent>,
|
||||
last_mouse_moved_event: Option<Event>,
|
||||
hovered_region_ids: HashSet<MouseRegionId>,
|
||||
clicked_region: Option<MouseRegion>,
|
||||
right_clicked_region: Option<MouseRegion>,
|
||||
prev_drag_position: Option<Vector2F>,
|
||||
clicked_regions: Vec<MouseRegion>,
|
||||
clicked_button: Option<MouseButton>,
|
||||
mouse_position: Vector2F,
|
||||
titlebar_height: f32,
|
||||
}
|
||||
|
||||
|
@ -58,30 +61,13 @@ impl Presenter {
|
|||
asset_cache,
|
||||
last_mouse_moved_event: None,
|
||||
hovered_region_ids: Default::default(),
|
||||
clicked_region: None,
|
||||
right_clicked_region: None,
|
||||
prev_drag_position: None,
|
||||
clicked_regions: Vec::new(),
|
||||
clicked_button: None,
|
||||
mouse_position: vec2f(0., 0.),
|
||||
titlebar_height,
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn dispatch_path(&self, app: &AppContext) -> Vec<usize> {
|
||||
// let mut path = Vec::new();
|
||||
// if let Some(view_id) = app.focused_view_id(self.window_id) {
|
||||
// self.compute_dispatch_path_from(view_id, &mut path)
|
||||
// }
|
||||
// path
|
||||
// }
|
||||
|
||||
// pub(crate) fn compute_dispatch_path_from(&self, mut view_id: usize, path: &mut Vec<usize>) {
|
||||
// path.push(view_id);
|
||||
// while let Some(parent_id) = self.parents.get(&view_id).copied() {
|
||||
// path.push(parent_id);
|
||||
// view_id = parent_id;
|
||||
// }
|
||||
// path.reverse();
|
||||
// }
|
||||
|
||||
pub fn invalidate(
|
||||
&mut self,
|
||||
invalidation: &mut WindowInvalidation,
|
||||
|
@ -100,11 +86,15 @@ impl Presenter {
|
|||
view_id: *view_id,
|
||||
titlebar_height: self.titlebar_height,
|
||||
hovered_region_ids: self.hovered_region_ids.clone(),
|
||||
clicked_region_id: self.clicked_region.as_ref().and_then(MouseRegion::id),
|
||||
right_clicked_region_id: self
|
||||
.right_clicked_region
|
||||
.as_ref()
|
||||
.and_then(MouseRegion::id),
|
||||
clicked_region_ids: self.clicked_button.map(|button| {
|
||||
(
|
||||
self.clicked_regions
|
||||
.iter()
|
||||
.filter_map(MouseRegion::id)
|
||||
.collect(),
|
||||
button,
|
||||
)
|
||||
}),
|
||||
refreshing: false,
|
||||
})
|
||||
.unwrap(),
|
||||
|
@ -122,11 +112,15 @@ impl Presenter {
|
|||
view_id: *view_id,
|
||||
titlebar_height: self.titlebar_height,
|
||||
hovered_region_ids: self.hovered_region_ids.clone(),
|
||||
clicked_region_id: self.clicked_region.as_ref().and_then(MouseRegion::id),
|
||||
right_clicked_region_id: self
|
||||
.right_clicked_region
|
||||
.as_ref()
|
||||
.and_then(MouseRegion::id),
|
||||
clicked_region_ids: self.clicked_button.map(|button| {
|
||||
(
|
||||
self.clicked_regions
|
||||
.iter()
|
||||
.filter_map(MouseRegion::id)
|
||||
.collect(),
|
||||
button,
|
||||
)
|
||||
}),
|
||||
refreshing: true,
|
||||
})
|
||||
.unwrap();
|
||||
|
@ -157,12 +151,7 @@ impl Presenter {
|
|||
|
||||
if cx.window_is_active(self.window_id) {
|
||||
if let Some(event) = self.last_mouse_moved_event.clone() {
|
||||
let mut invalidated_views = Vec::new();
|
||||
self.handle_hover_events(&event, &mut invalidated_views, cx);
|
||||
|
||||
for view_id in invalidated_views {
|
||||
cx.notify_view(self.window_id, view_id);
|
||||
}
|
||||
self.dispatch_event(event, true, cx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -195,8 +184,15 @@ impl Presenter {
|
|||
view_stack: Vec::new(),
|
||||
refreshing,
|
||||
hovered_region_ids: self.hovered_region_ids.clone(),
|
||||
clicked_region_id: self.clicked_region.as_ref().and_then(MouseRegion::id),
|
||||
right_clicked_region_id: self.right_clicked_region.as_ref().and_then(MouseRegion::id),
|
||||
clicked_region_ids: self.clicked_button.map(|button| {
|
||||
(
|
||||
self.clicked_regions
|
||||
.iter()
|
||||
.filter_map(MouseRegion::id)
|
||||
.collect(),
|
||||
button,
|
||||
)
|
||||
}),
|
||||
titlebar_height: self.titlebar_height,
|
||||
window_size,
|
||||
app: cx,
|
||||
|
@ -231,246 +227,249 @@ impl Presenter {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) -> bool {
|
||||
pub fn dispatch_event(
|
||||
&mut self,
|
||||
event: Event,
|
||||
event_reused: bool,
|
||||
cx: &mut MutableAppContext,
|
||||
) -> bool {
|
||||
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
|
||||
let mut invalidated_views = Vec::new();
|
||||
let mut mouse_down_out_handlers = Vec::new();
|
||||
let mut mouse_moved_region = None;
|
||||
let mut mouse_down_region = None;
|
||||
let mut mouse_up_region = None;
|
||||
let mut clicked_region = None;
|
||||
let mut dragged_region = None;
|
||||
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
|
||||
match &event {
|
||||
Event::MouseDown(
|
||||
e @ MouseButtonEvent {
|
||||
position, button, ..
|
||||
},
|
||||
) => {
|
||||
let mut hit = false;
|
||||
for (region, _) in self.mouse_regions.iter().rev() {
|
||||
if region.bounds.contains_point(*position) {
|
||||
if !hit {
|
||||
hit = true;
|
||||
invalidated_views.push(region.view_id);
|
||||
mouse_down_region =
|
||||
Some((region.clone(), MouseRegionEvent::Down(e.clone())));
|
||||
self.clicked_region = Some(region.clone());
|
||||
self.prev_drag_position = Some(*position);
|
||||
}
|
||||
} else if let Some(handler) = region
|
||||
.handlers
|
||||
.get(&(MouseRegionEvent::down_out_disc(), Some(*button)))
|
||||
{
|
||||
mouse_down_out_handlers.push((
|
||||
handler,
|
||||
region.view_id,
|
||||
MouseRegionEvent::DownOut(e.clone()),
|
||||
));
|
||||
}
|
||||
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.
|
||||
|
||||
// If there is already clicked_button stored, don't replace it.
|
||||
if self.clicked_button.is_none() {
|
||||
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 @ MouseButtonEvent {
|
||||
position, button, ..
|
||||
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,
|
||||
..
|
||||
},
|
||||
) => {
|
||||
self.prev_drag_position.take();
|
||||
if let Some(region) = self.clicked_region.take() {
|
||||
invalidated_views.push(region.view_id);
|
||||
if region.bounds.contains_point(position) {
|
||||
clicked_region = Some((region, MouseRegionEvent::Click(e.clone())));
|
||||
}
|
||||
}
|
||||
|
||||
for (region, _) in self.mouse_regions.iter().rev() {
|
||||
if region.bounds.contains_point(position) {
|
||||
invalidated_views.push(region.view_id);
|
||||
mouse_up_region =
|
||||
Some((region.clone(), MouseRegionEvent::Up(e.clone())));
|
||||
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 let Some(moved) = &mut self.last_mouse_moved_event {
|
||||
if moved.pressed_button == Some(button) {
|
||||
moved.pressed_button = None;
|
||||
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(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Event::MouseMoved(e @ MouseMovedEvent { position, .. }) => {
|
||||
if let Some((clicked_region, prev_drag_position)) = self
|
||||
.clicked_region
|
||||
.as_ref()
|
||||
.zip(self.prev_drag_position.as_mut())
|
||||
{
|
||||
dragged_region = Some((
|
||||
clicked_region.clone(),
|
||||
MouseRegionEvent::Drag(*prev_drag_position, *e),
|
||||
));
|
||||
*prev_drag_position = *position;
|
||||
events_to_send.push(MouseRegionEvent::Move(MoveRegionEvent {
|
||||
region: Default::default(),
|
||||
platform_event: e.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
for (region, _) in self.mouse_regions.iter().rev() {
|
||||
if region.bounds.contains_point(*position) {
|
||||
invalidated_views.push(region.view_id);
|
||||
mouse_moved_region =
|
||||
Some((region.clone(), MouseRegionEvent::Move(e.clone())));
|
||||
break;
|
||||
}
|
||||
}
|
||||
events_to_send.push(MouseRegionEvent::Hover(HoverRegionEvent {
|
||||
region: Default::default(),
|
||||
platform_event: e.clone(),
|
||||
started: false,
|
||||
}));
|
||||
|
||||
self.last_mouse_moved_event = Some(e.clone());
|
||||
self.last_mouse_moved_event = Some(event.clone());
|
||||
}
|
||||
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let (mut handled, mut event_cx) = if let Event::MouseMoved(e) = &event {
|
||||
self.handle_hover_events(e, &mut invalidated_views, cx)
|
||||
} else {
|
||||
(false, self.build_event_context(cx))
|
||||
};
|
||||
|
||||
for (handler, view_id, region_event) in mouse_down_out_handlers {
|
||||
event_cx.with_current_view(view_id, |event_cx| handler(region_event, event_cx))
|
||||
if let Some(position) = event.position() {
|
||||
self.mouse_position = position;
|
||||
}
|
||||
|
||||
if let Some((mouse_down_region, region_event)) = mouse_down_region {
|
||||
handled = true;
|
||||
if let Some(mouse_down_callback) =
|
||||
mouse_down_region.handlers.get(®ion_event.handler_key())
|
||||
{
|
||||
event_cx.with_current_view(mouse_down_region.view_id, |event_cx| {
|
||||
mouse_down_callback(region_event, event_cx);
|
||||
})
|
||||
let mut invalidated_views: HashSet<usize> = Default::default();
|
||||
let mut any_event_handled = false;
|
||||
// 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 topmost element which overlaps with the mouse.
|
||||
|
||||
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() {
|
||||
// Allow mouse regions to appear transparent to hovers
|
||||
if !region.hoverable {
|
||||
continue;
|
||||
}
|
||||
|
||||
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());
|
||||
invalidated_views.insert(region.view_id);
|
||||
}
|
||||
} else {
|
||||
// Ensure that hover exit events aren't sent twice
|
||||
if self.hovered_region_ids.remove(®ion_id) {
|
||||
valid_regions.push(region.clone());
|
||||
invalidated_views.insert(region.view_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MouseRegionEvent::Click(e) => {
|
||||
if e.button == self.clicked_button.unwrap() {
|
||||
// Clear clicked regions and clicked button
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
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();
|
||||
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()) {
|
||||
invalidated_views.insert(valid_region.view_id);
|
||||
|
||||
let mut event_cx = self.build_event_context(&mut invalidated_views, cx);
|
||||
event_cx.handled = true;
|
||||
event_cx.with_current_view(valid_region.view_id, {
|
||||
let region_event = region_event.clone();
|
||||
|cx| {
|
||||
callback(region_event, cx);
|
||||
}
|
||||
});
|
||||
|
||||
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 && region_event.is_capturable() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((move_moved_region, region_event)) = mouse_moved_region {
|
||||
handled = true;
|
||||
if let Some(mouse_moved_callback) =
|
||||
move_moved_region.handlers.get(®ion_event.handler_key())
|
||||
{
|
||||
event_cx.with_current_view(move_moved_region.view_id, |event_cx| {
|
||||
mouse_moved_callback(region_event, event_cx);
|
||||
})
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
if let Some((mouse_up_region, region_event)) = mouse_up_region {
|
||||
handled = true;
|
||||
if let Some(mouse_up_callback) =
|
||||
mouse_up_region.handlers.get(®ion_event.handler_key())
|
||||
{
|
||||
event_cx.with_current_view(mouse_up_region.view_id, |event_cx| {
|
||||
mouse_up_callback(region_event, event_cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((clicked_region, region_event)) = clicked_region {
|
||||
handled = true;
|
||||
if let Some(click_callback) =
|
||||
clicked_region.handlers.get(®ion_event.handler_key())
|
||||
{
|
||||
event_cx.with_current_view(clicked_region.view_id, |event_cx| {
|
||||
click_callback(region_event, event_cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((dragged_region, region_event)) = dragged_region {
|
||||
handled = true;
|
||||
if let Some(drag_callback) =
|
||||
dragged_region.handlers.get(®ion_event.handler_key())
|
||||
{
|
||||
event_cx.with_current_view(dragged_region.view_id, |event_cx| {
|
||||
drag_callback(region_event, event_cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if !handled {
|
||||
handled = event_cx.dispatch_event(root_view_id, &event);
|
||||
}
|
||||
|
||||
invalidated_views.extend(event_cx.invalidated_views);
|
||||
|
||||
for view_id in invalidated_views {
|
||||
cx.notify_view(self.window_id, view_id);
|
||||
}
|
||||
|
||||
handled
|
||||
any_event_handled
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_hover_events<'a>(
|
||||
&'a mut self,
|
||||
e @ MouseMovedEvent {
|
||||
position,
|
||||
pressed_button,
|
||||
..
|
||||
}: &MouseMovedEvent,
|
||||
invalidated_views: &mut Vec<usize>,
|
||||
cx: &'a mut MutableAppContext,
|
||||
) -> (bool, EventContext<'a>) {
|
||||
let mut hover_regions = Vec::new();
|
||||
|
||||
if pressed_button.is_none() {
|
||||
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);
|
||||
|
||||
let mut hover_depth = None;
|
||||
for (region, depth) in self.mouse_regions.iter().rev() {
|
||||
if region.bounds.contains_point(*position)
|
||||
&& hover_depth.map_or(true, |hover_depth| hover_depth == *depth)
|
||||
{
|
||||
hover_depth = Some(*depth);
|
||||
if let Some(region_id) = region.id() {
|
||||
if !self.hovered_region_ids.contains(®ion_id) {
|
||||
invalidated_views.push(region.view_id);
|
||||
hover_regions.push((region.clone(), MouseRegionEvent::Hover(true, *e)));
|
||||
self.hovered_region_ids.insert(region_id);
|
||||
}
|
||||
}
|
||||
} else if let Some(region_id) = region.id() {
|
||||
if self.hovered_region_ids.contains(®ion_id) {
|
||||
invalidated_views.push(region.view_id);
|
||||
hover_regions.push((region.clone(), MouseRegionEvent::Hover(false, *e)));
|
||||
self.hovered_region_ids.remove(®ion_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut event_cx = self.build_event_context(cx);
|
||||
let mut handled = false;
|
||||
|
||||
for (hover_region, region_event) in hover_regions {
|
||||
handled = true;
|
||||
if let Some(hover_callback) = hover_region.handlers.get(®ion_event.handler_key()) {
|
||||
event_cx.with_current_view(hover_region.view_id, |event_cx| {
|
||||
hover_callback(region_event, event_cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
(handled, event_cx)
|
||||
}
|
||||
|
||||
pub fn build_event_context<'a>(
|
||||
&'a mut self,
|
||||
invalidated_views: &'a mut HashSet<usize>,
|
||||
cx: &'a mut MutableAppContext,
|
||||
) -> EventContext<'a> {
|
||||
EventContext {
|
||||
|
@ -478,8 +477,9 @@ impl Presenter {
|
|||
font_cache: &self.font_cache,
|
||||
text_layout_cache: &self.text_layout_cache,
|
||||
view_stack: Default::default(),
|
||||
invalidated_views: Default::default(),
|
||||
invalidated_views,
|
||||
notify_count: 0,
|
||||
handled: false,
|
||||
window_id: self.window_id,
|
||||
app: cx,
|
||||
}
|
||||
|
@ -514,8 +514,7 @@ pub struct LayoutContext<'a> {
|
|||
pub window_size: Vector2F,
|
||||
titlebar_height: f32,
|
||||
hovered_region_ids: HashSet<MouseRegionId>,
|
||||
clicked_region_id: Option<MouseRegionId>,
|
||||
right_clicked_region_id: Option<MouseRegionId>,
|
||||
clicked_region_ids: Option<(Vec<MouseRegionId>, MouseButton)>,
|
||||
}
|
||||
|
||||
impl<'a> LayoutContext<'a> {
|
||||
|
@ -586,8 +585,7 @@ impl<'a> LayoutContext<'a> {
|
|||
view_type: PhantomData,
|
||||
titlebar_height: self.titlebar_height,
|
||||
hovered_region_ids: self.hovered_region_ids.clone(),
|
||||
clicked_region_id: self.clicked_region_id,
|
||||
right_clicked_region_id: self.right_clicked_region_id,
|
||||
clicked_region_ids: self.clicked_region_ids.clone(),
|
||||
refreshing: self.refreshing,
|
||||
};
|
||||
f(view, &mut render_cx)
|
||||
|
@ -699,7 +697,8 @@ pub struct EventContext<'a> {
|
|||
pub window_id: usize,
|
||||
pub notify_count: usize,
|
||||
view_stack: Vec<usize>,
|
||||
invalidated_views: HashSet<usize>,
|
||||
handled: bool,
|
||||
invalidated_views: &'a mut HashSet<usize>,
|
||||
}
|
||||
|
||||
impl<'a> EventContext<'a> {
|
||||
|
@ -765,6 +764,10 @@ impl<'a> EventContext<'a> {
|
|||
pub fn notify_count(&self) -> usize {
|
||||
self.notify_count
|
||||
}
|
||||
|
||||
pub fn propogate_event(&mut self) {
|
||||
self.handled = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for EventContext<'a> {
|
||||
|
|
100
crates/gpui/src/presenter/event_context.rs
Normal file
100
crates/gpui/src/presenter/event_context.rs
Normal file
|
@ -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<usize, ElementBox>,
|
||||
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<usize>,
|
||||
pub(crate) handled: bool,
|
||||
pub(crate) invalidated_views: HashSet<usize>,
|
||||
}
|
||||
|
||||
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<F, T>(&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<usize> {
|
||||
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<dyn Action>) {
|
||||
self.app
|
||||
.dispatch_any_action_at(self.window_id, *self.view_stack.last().unwrap(), action)
|
||||
}
|
||||
|
||||
pub fn dispatch_action<A: 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
|
||||
}
|
||||
}
|
308
crates/gpui/src/presenter/event_dispatcher.rs
Normal file
308
crates/gpui/src/presenter/event_dispatcher.rs
Normal file
|
@ -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<FontCache>,
|
||||
|
||||
last_mouse_moved_event: Option<Event>,
|
||||
cursor_regions: Vec<CursorRegion>,
|
||||
mouse_regions: Vec<(MouseRegion, usize)>,
|
||||
clicked_regions: Vec<MouseRegion>,
|
||||
clicked_button: Option<MouseButton>,
|
||||
mouse_position: Vector2F,
|
||||
hovered_region_ids: HashSet<MouseRegionId>,
|
||||
}
|
||||
|
||||
impl EventDispatcher {
|
||||
pub fn new(window_id: usize, font_cache: Arc<FontCache>) -> 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<MouseRegionId>, MouseButton)> {
|
||||
self.clicked_button.map(|button| {
|
||||
(
|
||||
self.clicked_regions
|
||||
.iter()
|
||||
.filter_map(MouseRegion::id)
|
||||
.collect(),
|
||||
button,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn hovered_region_ids(&self) -> HashSet<MouseRegionId> {
|
||||
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<usize> = 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<MouseRegionEvent> {
|
||||
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<MouseRegion> {
|
||||
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
|
||||
}
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
mod mouse_region;
|
||||
mod mouse_region_event;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
|
@ -13,6 +14,7 @@ use crate::{
|
|||
ImageData,
|
||||
};
|
||||
pub use mouse_region::*;
|
||||
pub use mouse_region_event::*;
|
||||
|
||||
pub struct Scene {
|
||||
scale_factor: f32,
|
||||
|
|
|
@ -2,9 +2,14 @@ use std::{any::TypeId, mem::Discriminant, rc::Rc};
|
|||
|
||||
use collections::HashMap;
|
||||
|
||||
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
|
||||
use crate::{EventContext, MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
|
||||
use crate::{EventContext, MouseButton};
|
||||
|
||||
use super::mouse_region_event::{
|
||||
ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragRegionEvent, HoverRegionEvent,
|
||||
MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent,
|
||||
};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct MouseRegion {
|
||||
|
@ -12,6 +17,7 @@ pub struct MouseRegion {
|
|||
pub discriminant: Option<(TypeId, usize)>,
|
||||
pub bounds: RectF,
|
||||
pub handlers: HandlerSet,
|
||||
pub hoverable: bool,
|
||||
}
|
||||
|
||||
impl MouseRegion {
|
||||
|
@ -30,6 +36,7 @@ impl MouseRegion {
|
|||
discriminant,
|
||||
bounds,
|
||||
handlers,
|
||||
hoverable: true,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,14 +49,15 @@ impl MouseRegion {
|
|||
view_id,
|
||||
discriminant,
|
||||
bounds,
|
||||
handlers: HandlerSet::handle_all(),
|
||||
handlers: HandlerSet::capture_all(),
|
||||
hoverable: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_down(button, handler);
|
||||
self
|
||||
|
@ -58,7 +66,7 @@ impl MouseRegion {
|
|||
pub fn on_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_up(button, handler);
|
||||
self
|
||||
|
@ -67,7 +75,7 @@ impl MouseRegion {
|
|||
pub fn on_click(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_click(button, handler);
|
||||
self
|
||||
|
@ -76,16 +84,25 @@ impl MouseRegion {
|
|||
pub fn on_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_down_out(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_up_out(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_drag(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_drag(button, handler);
|
||||
self
|
||||
|
@ -93,7 +110,7 @@ impl MouseRegion {
|
|||
|
||||
pub fn on_hover(
|
||||
mut self,
|
||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_hover(handler);
|
||||
self
|
||||
|
@ -101,14 +118,19 @@ impl MouseRegion {
|
|||
|
||||
pub fn on_move(
|
||||
mut self,
|
||||
handler: impl Fn(MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(MoveRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_move(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
|
||||
self.hoverable = is_hoverable;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||
pub struct MouseRegionId {
|
||||
pub view_id: usize,
|
||||
pub discriminant: (TypeId, usize),
|
||||
|
@ -124,7 +146,7 @@ pub struct HandlerSet {
|
|||
}
|
||||
|
||||
impl HandlerSet {
|
||||
pub fn handle_all() -> Self {
|
||||
pub fn capture_all() -> Self {
|
||||
#[allow(clippy::type_complexity)]
|
||||
let mut set: HashMap<
|
||||
(Discriminant<MouseRegionEvent>, Option<MouseButton>),
|
||||
|
@ -154,6 +176,10 @@ impl HandlerSet {
|
|||
(MouseRegionEvent::down_out_disc(), Some(button)),
|
||||
Rc::new(|_, _| {}),
|
||||
);
|
||||
set.insert(
|
||||
(MouseRegionEvent::up_out_disc(), Some(button)),
|
||||
Rc::new(|_, _| {}),
|
||||
);
|
||||
}
|
||||
set.insert(
|
||||
(MouseRegionEvent::scroll_wheel_disc(), None),
|
||||
|
@ -170,15 +196,32 @@ impl HandlerSet {
|
|||
self.set.get(key).cloned()
|
||||
}
|
||||
|
||||
pub fn on_move(
|
||||
mut self,
|
||||
handler: impl Fn(MoveRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::move_disc(), None),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Move(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}",
|
||||
region_event);
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::down_disc(), Some(button)),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Down(mouse_button_event) = region_event {
|
||||
handler(mouse_button_event, cx);
|
||||
if let MouseRegionEvent::Down(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}",
|
||||
|
@ -191,12 +234,12 @@ impl HandlerSet {
|
|||
pub fn on_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::up_disc(), Some(button)),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Up(mouse_button_event) = region_event {
|
||||
handler(mouse_button_event, cx);
|
||||
if let MouseRegionEvent::Up(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}",
|
||||
|
@ -209,12 +252,12 @@ impl HandlerSet {
|
|||
pub fn on_click(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::click_disc(), Some(button)),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Click(mouse_button_event) = region_event {
|
||||
handler(mouse_button_event, cx);
|
||||
if let MouseRegionEvent::Click(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}",
|
||||
|
@ -227,12 +270,12 @@ impl HandlerSet {
|
|||
pub fn on_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::down_out_disc(), Some(button)),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::DownOut(mouse_button_event) = region_event {
|
||||
handler(mouse_button_event, cx);
|
||||
if let MouseRegionEvent::DownOut(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}",
|
||||
|
@ -242,15 +285,33 @@ impl HandlerSet {
|
|||
self
|
||||
}
|
||||
|
||||
pub fn on_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::up_out_disc(), Some(button)),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::UpOut(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}",
|
||||
region_event);
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_drag(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::drag_disc(), Some(button)),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Drag(prev_drag_position, mouse_moved_event) = region_event {
|
||||
handler(prev_drag_position, mouse_moved_event, cx);
|
||||
if let MouseRegionEvent::Drag(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}",
|
||||
|
@ -262,12 +323,12 @@ impl HandlerSet {
|
|||
|
||||
pub fn on_hover(
|
||||
mut self,
|
||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::hover_disc(), None),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Hover(hover, mouse_moved_event) = region_event {
|
||||
handler(hover, mouse_moved_event, cx);
|
||||
if let MouseRegionEvent::Hover(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}",
|
||||
|
@ -276,89 +337,4 @@ impl HandlerSet {
|
|||
}));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_move(
|
||||
mut self,
|
||||
handler: impl Fn(MouseMovedEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::move_disc(), None),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Move(move_event)= region_event {
|
||||
handler(move_event, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}",
|
||||
region_event);
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MouseRegionEvent {
|
||||
Move(MouseMovedEvent),
|
||||
Drag(Vector2F, MouseMovedEvent),
|
||||
Hover(bool, MouseMovedEvent),
|
||||
Down(MouseButtonEvent),
|
||||
Up(MouseButtonEvent),
|
||||
Click(MouseButtonEvent),
|
||||
DownOut(MouseButtonEvent),
|
||||
ScrollWheel(ScrollWheelEvent),
|
||||
}
|
||||
|
||||
impl MouseRegionEvent {
|
||||
pub fn move_disc() -> Discriminant<MouseRegionEvent> {
|
||||
std::mem::discriminant(&MouseRegionEvent::Move(Default::default()))
|
||||
}
|
||||
pub fn drag_disc() -> Discriminant<MouseRegionEvent> {
|
||||
std::mem::discriminant(&MouseRegionEvent::Drag(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
))
|
||||
}
|
||||
pub fn hover_disc() -> Discriminant<MouseRegionEvent> {
|
||||
std::mem::discriminant(&MouseRegionEvent::Hover(
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
))
|
||||
}
|
||||
pub fn down_disc() -> Discriminant<MouseRegionEvent> {
|
||||
std::mem::discriminant(&MouseRegionEvent::Down(Default::default()))
|
||||
}
|
||||
pub fn up_disc() -> Discriminant<MouseRegionEvent> {
|
||||
std::mem::discriminant(&MouseRegionEvent::Up(Default::default()))
|
||||
}
|
||||
pub fn click_disc() -> Discriminant<MouseRegionEvent> {
|
||||
std::mem::discriminant(&MouseRegionEvent::Click(Default::default()))
|
||||
}
|
||||
pub fn down_out_disc() -> Discriminant<MouseRegionEvent> {
|
||||
std::mem::discriminant(&MouseRegionEvent::DownOut(Default::default()))
|
||||
}
|
||||
pub fn scroll_wheel_disc() -> Discriminant<MouseRegionEvent> {
|
||||
std::mem::discriminant(&MouseRegionEvent::ScrollWheel(Default::default()))
|
||||
}
|
||||
|
||||
pub fn handler_key(&self) -> (Discriminant<MouseRegionEvent>, Option<MouseButton>) {
|
||||
match self {
|
||||
MouseRegionEvent::Move(_) => (Self::move_disc(), None),
|
||||
MouseRegionEvent::Drag(_, MouseMovedEvent { pressed_button, .. }) => {
|
||||
(Self::drag_disc(), *pressed_button)
|
||||
}
|
||||
MouseRegionEvent::Hover(_, _) => (Self::hover_disc(), None),
|
||||
MouseRegionEvent::Down(MouseButtonEvent { button, .. }) => {
|
||||
(Self::down_disc(), Some(*button))
|
||||
}
|
||||
MouseRegionEvent::Up(MouseButtonEvent { button, .. }) => {
|
||||
(Self::up_disc(), Some(*button))
|
||||
}
|
||||
MouseRegionEvent::Click(MouseButtonEvent { button, .. }) => {
|
||||
(Self::click_disc(), Some(*button))
|
||||
}
|
||||
MouseRegionEvent::DownOut(MouseButtonEvent { button, .. }) => {
|
||||
(Self::down_out_disc(), Some(*button))
|
||||
}
|
||||
MouseRegionEvent::ScrollWheel(_) => (Self::scroll_wheel_disc(), None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
233
crates/gpui/src/scene/mouse_region_event.rs
Normal file
233
crates/gpui/src/scene/mouse_region_event.rs
Normal file
|
@ -0,0 +1,233 @@
|
|||
use std::{
|
||||
mem::{discriminant, Discriminant},
|
||||
ops::Deref,
|
||||
};
|
||||
|
||||
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
|
||||
|
||||
use crate::{MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct MoveRegionEvent {
|
||||
pub region: RectF,
|
||||
pub platform_event: MouseMovedEvent,
|
||||
}
|
||||
|
||||
impl Deref for MoveRegionEvent {
|
||||
type Target = MouseMovedEvent;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.platform_event
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DragRegionEvent {
|
||||
pub region: RectF,
|
||||
pub prev_mouse_position: Vector2F,
|
||||
pub platform_event: MouseMovedEvent,
|
||||
}
|
||||
|
||||
impl Deref for DragRegionEvent {
|
||||
type Target = MouseMovedEvent;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.platform_event
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct HoverRegionEvent {
|
||||
pub region: RectF,
|
||||
pub started: bool,
|
||||
pub platform_event: MouseMovedEvent,
|
||||
}
|
||||
|
||||
impl Deref for HoverRegionEvent {
|
||||
type Target = MouseMovedEvent;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.platform_event
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DownRegionEvent {
|
||||
pub region: RectF,
|
||||
pub platform_event: MouseButtonEvent,
|
||||
}
|
||||
|
||||
impl Deref for DownRegionEvent {
|
||||
type Target = MouseButtonEvent;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.platform_event
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct UpRegionEvent {
|
||||
pub region: RectF,
|
||||
pub platform_event: MouseButtonEvent,
|
||||
}
|
||||
|
||||
impl Deref for UpRegionEvent {
|
||||
type Target = MouseButtonEvent;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.platform_event
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ClickRegionEvent {
|
||||
pub region: RectF,
|
||||
pub platform_event: MouseButtonEvent,
|
||||
}
|
||||
|
||||
impl Deref for ClickRegionEvent {
|
||||
type Target = MouseButtonEvent;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.platform_event
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct DownOutRegionEvent {
|
||||
pub region: RectF,
|
||||
pub platform_event: MouseButtonEvent,
|
||||
}
|
||||
|
||||
impl Deref for DownOutRegionEvent {
|
||||
type Target = MouseButtonEvent;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.platform_event
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct UpOutRegionEvent {
|
||||
pub region: RectF,
|
||||
pub platform_event: MouseButtonEvent,
|
||||
}
|
||||
|
||||
impl Deref for UpOutRegionEvent {
|
||||
type Target = MouseButtonEvent;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.platform_event
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct ScrollWheelRegionEvent {
|
||||
pub region: RectF,
|
||||
pub platform_event: ScrollWheelEvent,
|
||||
}
|
||||
|
||||
impl Deref for ScrollWheelRegionEvent {
|
||||
type Target = ScrollWheelEvent;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.platform_event
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum MouseRegionEvent {
|
||||
Move(MoveRegionEvent),
|
||||
Drag(DragRegionEvent),
|
||||
Hover(HoverRegionEvent),
|
||||
Down(DownRegionEvent),
|
||||
Up(UpRegionEvent),
|
||||
Click(ClickRegionEvent),
|
||||
DownOut(DownOutRegionEvent),
|
||||
UpOut(UpOutRegionEvent),
|
||||
ScrollWheel(ScrollWheelRegionEvent),
|
||||
}
|
||||
|
||||
impl MouseRegionEvent {
|
||||
pub fn set_region(&mut self, region: RectF) {
|
||||
match self {
|
||||
MouseRegionEvent::Move(r) => r.region = region,
|
||||
MouseRegionEvent::Drag(r) => r.region = region,
|
||||
MouseRegionEvent::Hover(r) => r.region = region,
|
||||
MouseRegionEvent::Down(r) => r.region = region,
|
||||
MouseRegionEvent::Up(r) => r.region = region,
|
||||
MouseRegionEvent::Click(r) => r.region = region,
|
||||
MouseRegionEvent::DownOut(r) => r.region = region,
|
||||
MouseRegionEvent::UpOut(r) => r.region = region,
|
||||
MouseRegionEvent::ScrollWheel(r) => r.region = region,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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(_) => false,
|
||||
MouseRegionEvent::Hover(_) => true,
|
||||
MouseRegionEvent::Down(_) => true,
|
||||
MouseRegionEvent::Up(_) => true,
|
||||
MouseRegionEvent::Click(_) => true,
|
||||
MouseRegionEvent::DownOut(_) => false,
|
||||
MouseRegionEvent::UpOut(_) => false,
|
||||
MouseRegionEvent::ScrollWheel(_) => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MouseRegionEvent {
|
||||
pub fn move_disc() -> Discriminant<MouseRegionEvent> {
|
||||
discriminant(&MouseRegionEvent::Move(Default::default()))
|
||||
}
|
||||
|
||||
pub fn drag_disc() -> Discriminant<MouseRegionEvent> {
|
||||
discriminant(&MouseRegionEvent::Drag(Default::default()))
|
||||
}
|
||||
|
||||
pub fn hover_disc() -> Discriminant<MouseRegionEvent> {
|
||||
discriminant(&MouseRegionEvent::Hover(Default::default()))
|
||||
}
|
||||
|
||||
pub fn down_disc() -> Discriminant<MouseRegionEvent> {
|
||||
discriminant(&MouseRegionEvent::Down(Default::default()))
|
||||
}
|
||||
|
||||
pub fn up_disc() -> Discriminant<MouseRegionEvent> {
|
||||
discriminant(&MouseRegionEvent::Up(Default::default()))
|
||||
}
|
||||
|
||||
pub fn up_out_disc() -> Discriminant<MouseRegionEvent> {
|
||||
discriminant(&MouseRegionEvent::UpOut(Default::default()))
|
||||
}
|
||||
|
||||
pub fn click_disc() -> Discriminant<MouseRegionEvent> {
|
||||
discriminant(&MouseRegionEvent::Click(Default::default()))
|
||||
}
|
||||
|
||||
pub fn down_out_disc() -> Discriminant<MouseRegionEvent> {
|
||||
discriminant(&MouseRegionEvent::DownOut(Default::default()))
|
||||
}
|
||||
|
||||
pub fn scroll_wheel_disc() -> Discriminant<MouseRegionEvent> {
|
||||
discriminant(&MouseRegionEvent::ScrollWheel(Default::default()))
|
||||
}
|
||||
|
||||
pub fn handler_key(&self) -> (Discriminant<MouseRegionEvent>, Option<MouseButton>) {
|
||||
match self {
|
||||
MouseRegionEvent::Move(_) => (Self::move_disc(), None),
|
||||
MouseRegionEvent::Drag(e) => (Self::drag_disc(), e.pressed_button),
|
||||
MouseRegionEvent::Hover(_) => (Self::hover_disc(), None),
|
||||
MouseRegionEvent::Down(e) => (Self::down_disc(), Some(e.button)),
|
||||
MouseRegionEvent::Up(e) => (Self::up_disc(), Some(e.button)),
|
||||
MouseRegionEvent::Click(e) => (Self::click_disc(), Some(e.button)),
|
||||
MouseRegionEvent::UpOut(e) => (Self::up_out_disc(), Some(e.button)),
|
||||
MouseRegionEvent::DownOut(e) => (Self::down_out_disc(), Some(e.button)),
|
||||
MouseRegionEvent::ScrollWheel(_) => (Self::scroll_wheel_disc(), None),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,8 +12,7 @@ use gpui::{
|
|||
impl_internal_actions, keymap,
|
||||
platform::CursorStyle,
|
||||
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton,
|
||||
MouseButtonEvent, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext,
|
||||
ViewHandle,
|
||||
MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use menu::{Confirm, SelectNext, SelectPrev};
|
||||
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
|
||||
|
@ -1074,25 +1073,22 @@ impl ProjectPanel {
|
|||
.with_padding_left(padding)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(
|
||||
MouseButton::Left,
|
||||
move |MouseButtonEvent { click_count, .. }, cx| {
|
||||
if kind == EntryKind::Dir {
|
||||
cx.dispatch_action(ToggleExpanded(entry_id))
|
||||
} else {
|
||||
cx.dispatch_action(Open {
|
||||
entry_id,
|
||||
change_focus: click_count > 1,
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
.on_down(
|
||||
MouseButton::Right,
|
||||
move |MouseButtonEvent { position, .. }, cx| {
|
||||
cx.dispatch_action(DeployContextMenu { entry_id, position })
|
||||
},
|
||||
)
|
||||
.on_click(MouseButton::Left, move |e, cx| {
|
||||
if kind == EntryKind::Dir {
|
||||
cx.dispatch_action(ToggleExpanded(entry_id))
|
||||
} else {
|
||||
cx.dispatch_action(Open {
|
||||
entry_id,
|
||||
change_focus: e.click_count > 1,
|
||||
})
|
||||
}
|
||||
})
|
||||
.on_down(MouseButton::Right, move |e, cx| {
|
||||
cx.dispatch_action(DeployContextMenu {
|
||||
entry_id,
|
||||
position: e.position,
|
||||
})
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.boxed()
|
||||
}
|
||||
|
@ -1139,16 +1135,16 @@ impl View for ProjectPanel {
|
|||
.expanded()
|
||||
.boxed()
|
||||
})
|
||||
.on_down(
|
||||
MouseButton::Right,
|
||||
move |MouseButtonEvent { position, .. }, cx| {
|
||||
// When deploying the context menu anywhere below the last project entry,
|
||||
// act as if the user clicked the root of the last worktree.
|
||||
if let Some(entry_id) = last_worktree_root_id {
|
||||
cx.dispatch_action(DeployContextMenu { entry_id, position })
|
||||
}
|
||||
},
|
||||
)
|
||||
.on_down(MouseButton::Right, move |e, cx| {
|
||||
// When deploying the context menu anywhere below the last project entry,
|
||||
// act as if the user clicked the root of the last worktree.
|
||||
if let Some(entry_id) = last_worktree_root_id {
|
||||
cx.dispatch_action(DeployContextMenu {
|
||||
entry_id,
|
||||
position: e.position,
|
||||
})
|
||||
}
|
||||
})
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(ChildView::new(&self.context_menu).boxed())
|
||||
|
|
|
@ -39,13 +39,11 @@ use std::{
|
|||
use thiserror::Error;
|
||||
|
||||
use gpui::{
|
||||
geometry::{
|
||||
rect::RectF,
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
geometry::vector::{vec2f, Vector2F},
|
||||
keymap::Keystroke,
|
||||
ClipboardItem, Entity, ModelContext, MouseButton, MouseButtonEvent, MouseMovedEvent,
|
||||
MutableAppContext, ScrollWheelEvent,
|
||||
scene::{ClickRegionEvent, DownRegionEvent, DragRegionEvent, UpRegionEvent},
|
||||
ClipboardItem, Entity, ModelContext, MouseButton, MouseMovedEvent, MutableAppContext,
|
||||
ScrollWheelEvent,
|
||||
};
|
||||
|
||||
use crate::mappings::{
|
||||
|
@ -676,7 +674,7 @@ impl Terminal {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn mouse_drag(&mut self, e: MouseMovedEvent, origin: Vector2F, bounds: RectF) {
|
||||
pub fn mouse_drag(&mut self, e: DragRegionEvent, origin: Vector2F) {
|
||||
let position = e.position.sub(origin);
|
||||
|
||||
if !self.mouse_mode(e.shift) {
|
||||
|
@ -687,8 +685,8 @@ impl Terminal {
|
|||
// Doesn't make sense to scroll the alt screen
|
||||
if !self.last_mode.contains(TermMode::ALT_SCREEN) {
|
||||
//TODO: Why do these need to be doubled?
|
||||
let top = bounds.origin_y() + (self.cur_size.line_height * 2.);
|
||||
let bottom = bounds.lower_left().y() - (self.cur_size.line_height * 2.);
|
||||
let top = e.region.origin_y() + (self.cur_size.line_height * 2.);
|
||||
let bottom = e.region.lower_left().y() - (self.cur_size.line_height * 2.);
|
||||
|
||||
let scroll_delta = if e.position.y() < top {
|
||||
(top - e.position.y()).powf(1.1)
|
||||
|
@ -705,7 +703,7 @@ impl Terminal {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn mouse_down(&mut self, e: &MouseButtonEvent, origin: Vector2F) {
|
||||
pub fn mouse_down(&mut self, e: &DownRegionEvent, origin: Vector2F) {
|
||||
let position = e.position.sub(origin);
|
||||
let point = mouse_point(position, self.cur_size, self.last_offset);
|
||||
let side = mouse_side(position, self.cur_size);
|
||||
|
@ -719,7 +717,7 @@ impl Terminal {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn left_click(&mut self, e: &MouseButtonEvent, origin: Vector2F) {
|
||||
pub fn left_click(&mut self, e: &ClickRegionEvent, origin: Vector2F) {
|
||||
let position = e.position.sub(origin);
|
||||
|
||||
if !self.mouse_mode(e.shift) {
|
||||
|
@ -743,7 +741,7 @@ impl Terminal {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn mouse_up(&mut self, e: &MouseButtonEvent, origin: Vector2F) {
|
||||
pub fn mouse_up(&mut self, e: &UpRegionEvent, origin: Vector2F) {
|
||||
let position = e.position.sub(origin);
|
||||
if self.mouse_mode(e.shift) {
|
||||
let point = mouse_point(position, self.cur_size, self.last_offset);
|
||||
|
|
|
@ -18,9 +18,8 @@ use gpui::{
|
|||
},
|
||||
serde_json::json,
|
||||
text_layout::{Line, RunStyle},
|
||||
Element, Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton,
|
||||
MouseButtonEvent, MouseRegion, PaintContext, Quad, TextLayoutCache, WeakModelHandle,
|
||||
WeakViewHandle,
|
||||
Element, Event, EventContext, FontCache, KeyDownEvent, ModelContext, MouseButton, MouseRegion,
|
||||
PaintContext, Quad, TextLayoutCache, WeakModelHandle, WeakViewHandle,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use ordered_float::OrderedFloat;
|
||||
|
@ -410,11 +409,11 @@ impl TerminalElement {
|
|||
}
|
||||
}
|
||||
|
||||
fn generic_button_handler(
|
||||
fn generic_button_handler<E>(
|
||||
connection: WeakModelHandle<Terminal>,
|
||||
origin: Vector2F,
|
||||
f: impl Fn(&mut Terminal, Vector2F, MouseButtonEvent, &mut ModelContext<Terminal>),
|
||||
) -> impl Fn(MouseButtonEvent, &mut EventContext) {
|
||||
f: impl Fn(&mut Terminal, Vector2F, E, &mut ModelContext<Terminal>),
|
||||
) -> impl Fn(E, &mut EventContext) {
|
||||
move |event, cx| {
|
||||
cx.focus_parent_view();
|
||||
if let Some(conn_handle) = connection.upgrade(cx.app) {
|
||||
|
@ -453,11 +452,11 @@ impl TerminalElement {
|
|||
),
|
||||
)
|
||||
// Update drag selections
|
||||
.on_drag(MouseButton::Left, move |_prev, event, cx| {
|
||||
.on_drag(MouseButton::Left, move |event, cx| {
|
||||
if cx.is_parent_view_focused() {
|
||||
if let Some(conn_handle) = connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, cx| {
|
||||
terminal.mouse_drag(event, origin, visible_bounds);
|
||||
terminal.mouse_drag(event, origin);
|
||||
cx.notify();
|
||||
})
|
||||
}
|
||||
|
@ -486,20 +485,19 @@ impl TerminalElement {
|
|||
),
|
||||
)
|
||||
// Context menu
|
||||
.on_click(
|
||||
MouseButton::Right,
|
||||
move |e @ MouseButtonEvent { position, .. }, cx| {
|
||||
let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift))
|
||||
} else {
|
||||
// If we can't get the model handle, probably can't deploy the context menu
|
||||
true
|
||||
};
|
||||
if !mouse_mode {
|
||||
cx.dispatch_action(DeployContextMenu { position });
|
||||
}
|
||||
},
|
||||
);
|
||||
.on_click(MouseButton::Right, move |e, cx| {
|
||||
let mouse_mode = if let Some(conn_handle) = connection.upgrade(cx.app) {
|
||||
conn_handle.update(cx.app, |terminal, _cx| terminal.mouse_mode(e.shift))
|
||||
} else {
|
||||
// If we can't get the model handle, probably can't deploy the context menu
|
||||
true
|
||||
};
|
||||
if !mouse_mode {
|
||||
cx.dispatch_action(DeployContextMenu {
|
||||
position: e.position,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Mouse mode handlers:
|
||||
// All mouse modes need the extra click handlers
|
||||
|
|
|
@ -75,7 +75,25 @@ pub struct TabBar {
|
|||
pub pane_button: Interactive<IconButton>,
|
||||
pub active_pane: TabStyles,
|
||||
pub inactive_pane: TabStyles,
|
||||
pub dragged_tab: Tab,
|
||||
pub height: f32,
|
||||
pub drop_target_overlay_color: Color,
|
||||
}
|
||||
|
||||
impl TabBar {
|
||||
pub fn tab_style(&self, pane_active: bool, tab_active: bool) -> &Tab {
|
||||
let tabs = if pane_active {
|
||||
&self.active_pane
|
||||
} else {
|
||||
&self.inactive_pane
|
||||
};
|
||||
|
||||
if tab_active {
|
||||
&tabs.active_tab
|
||||
} else {
|
||||
&tabs.inactive_tab
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize, Default)]
|
||||
|
|
|
@ -15,6 +15,7 @@ client = { path = "../client" }
|
|||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections" }
|
||||
context_menu = { path = "../context_menu" }
|
||||
drag_and_drop = { path = "../drag_and_drop" }
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
menu = { path = "../menu" }
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,7 +1,7 @@
|
|||
use crate::StatusItemView;
|
||||
use gpui::{
|
||||
elements::*, impl_actions, platform::CursorStyle, AnyViewHandle, AppContext, Entity,
|
||||
MouseButton, MouseMovedEvent, RenderContext, Subscription, View, ViewContext, ViewHandle,
|
||||
MouseButton, RenderContext, Subscription, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
|
@ -189,26 +189,18 @@ 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 |old_position,
|
||||
MouseMovedEvent {
|
||||
position: new_position,
|
||||
..
|
||||
},
|
||||
cx| {
|
||||
let delta = new_position.x() - old_position.x();
|
||||
let prev_width = *actual_width.borrow();
|
||||
*custom_width.borrow_mut() = 0f32
|
||||
.max(match side {
|
||||
Side::Left => prev_width + delta,
|
||||
Side::Right => prev_width - delta,
|
||||
})
|
||||
.round();
|
||||
.on_drag(MouseButton::Left, move |e, cx| {
|
||||
let delta = e.position.x() - e.prev_mouse_position.x();
|
||||
let prev_width = *actual_width.borrow();
|
||||
*custom_width.borrow_mut() = 0f32
|
||||
.max(match side {
|
||||
Side::Left => prev_width + delta,
|
||||
Side::Right => prev_width - delta,
|
||||
})
|
||||
.round();
|
||||
|
||||
cx.notify();
|
||||
},
|
||||
)
|
||||
cx.notify();
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ use client::{
|
|||
};
|
||||
use clock::ReplicaId;
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use drag_and_drop::DragAndDrop;
|
||||
use futures::{channel::oneshot, FutureExt};
|
||||
use gpui::{
|
||||
actions,
|
||||
|
@ -901,6 +902,9 @@ impl Workspace {
|
|||
status_bar
|
||||
});
|
||||
|
||||
let drag_and_drop = DragAndDrop::new(cx.weak_handle(), cx);
|
||||
cx.set_global(drag_and_drop);
|
||||
|
||||
let mut this = Workspace {
|
||||
modal: None,
|
||||
weak_self,
|
||||
|
@ -1444,8 +1448,8 @@ impl Workspace {
|
|||
}
|
||||
|
||||
pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
|
||||
let pane = self.active_pane().clone();
|
||||
Pane::add_item(self, pane, item, true, true, cx);
|
||||
let active_pane = self.active_pane().clone();
|
||||
Pane::add_item(self, &active_pane, item, true, true, None, cx);
|
||||
}
|
||||
|
||||
pub fn open_path(
|
||||
|
@ -1531,7 +1535,7 @@ impl Workspace {
|
|||
.map(|ix| (pane.clone(), ix))
|
||||
});
|
||||
if let Some((pane, ix)) = result {
|
||||
pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, false, cx));
|
||||
pane.update(cx, |pane, cx| pane.activate_item(ix, true, true, cx));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
@ -1645,7 +1649,7 @@ impl Workspace {
|
|||
pane.read(cx).active_item().map(|item| {
|
||||
let new_pane = self.add_pane(cx);
|
||||
if let Some(clone) = item.clone_on_split(cx.as_mut()) {
|
||||
Pane::add_item(self, new_pane.clone(), clone, true, true, cx);
|
||||
Pane::add_item(self, &new_pane, clone, true, true, None, cx);
|
||||
}
|
||||
self.center.split(&pane, &new_pane, direction).unwrap();
|
||||
cx.notify();
|
||||
|
@ -2081,11 +2085,11 @@ impl Workspace {
|
|||
}
|
||||
}
|
||||
|
||||
fn render_disconnected_overlay(&self, cx: &AppContext) -> Option<ElementBox> {
|
||||
fn render_disconnected_overlay(&self, cx: &mut RenderContext<Workspace>) -> Option<ElementBox> {
|
||||
if self.project.read(cx).is_read_only() {
|
||||
let theme = &cx.global::<Settings>().theme;
|
||||
Some(
|
||||
EventHandler::new(
|
||||
MouseEventHandler::new::<Workspace, _, _>(0, cx, |_, cx| {
|
||||
let theme = &cx.global::<Settings>().theme;
|
||||
Label::new(
|
||||
"Your connection to the remote project has been lost.".to_string(),
|
||||
theme.workspace.disconnected_overlay.text.clone(),
|
||||
|
@ -2093,9 +2097,9 @@ impl Workspace {
|
|||
.aligned()
|
||||
.contained()
|
||||
.with_style(theme.workspace.disconnected_overlay.container)
|
||||
.boxed(),
|
||||
)
|
||||
.capture_all::<Self>(0)
|
||||
.boxed()
|
||||
})
|
||||
.capture_all()
|
||||
.boxed(),
|
||||
)
|
||||
} else {
|
||||
|
@ -2388,7 +2392,7 @@ impl Workspace {
|
|||
}
|
||||
|
||||
for (pane, item) in items_to_add {
|
||||
Pane::add_item(self, pane.clone(), item.boxed_clone(), false, false, cx);
|
||||
Pane::add_item(self, &pane, item.boxed_clone(), false, false, None, cx);
|
||||
if pane == self.active_pane {
|
||||
pane.update(cx, |pane, cx| pane.focus_active_item(cx));
|
||||
}
|
||||
|
@ -2488,6 +2492,7 @@ impl View for Workspace {
|
|||
.with_background_color(theme.workspace.background)
|
||||
.boxed(),
|
||||
)
|
||||
.with_children(DragAndDrop::render(cx))
|
||||
.with_children(self.render_disconnected_overlay(cx))
|
||||
.named("workspace")
|
||||
}
|
||||
|
@ -2999,7 +3004,7 @@ mod tests {
|
|||
|
||||
let close_items = workspace.update(cx, |workspace, cx| {
|
||||
pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(1, true, true, false, cx);
|
||||
pane.activate_item(1, true, true, cx);
|
||||
assert_eq!(pane.active_item().unwrap().id(), item2.id());
|
||||
});
|
||||
|
||||
|
@ -3101,7 +3106,7 @@ mod tests {
|
|||
workspace.add_item(Box::new(cx.add_view(|_| item.clone())), cx);
|
||||
}
|
||||
left_pane.update(cx, |pane, cx| {
|
||||
pane.activate_item(2, true, true, false, cx);
|
||||
pane.activate_item(2, true, true, cx);
|
||||
});
|
||||
|
||||
workspace
|
||||
|
@ -3325,8 +3330,9 @@ mod tests {
|
|||
});
|
||||
}
|
||||
|
||||
struct TestItem {
|
||||
pub struct TestItem {
|
||||
state: String,
|
||||
pub label: String,
|
||||
save_count: usize,
|
||||
save_as_count: usize,
|
||||
reload_count: usize,
|
||||
|
@ -3340,7 +3346,7 @@ mod tests {
|
|||
tab_detail: Cell<Option<usize>>,
|
||||
}
|
||||
|
||||
enum TestItemEvent {
|
||||
pub enum TestItemEvent {
|
||||
Edit,
|
||||
}
|
||||
|
||||
|
@ -3348,6 +3354,7 @@ mod tests {
|
|||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
state: self.state.clone(),
|
||||
label: self.label.clone(),
|
||||
save_count: self.save_count,
|
||||
save_as_count: self.save_as_count,
|
||||
reload_count: self.reload_count,
|
||||
|
@ -3364,9 +3371,10 @@ mod tests {
|
|||
}
|
||||
|
||||
impl TestItem {
|
||||
fn new() -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: String::new(),
|
||||
label: String::new(),
|
||||
save_count: 0,
|
||||
save_as_count: 0,
|
||||
reload_count: 0,
|
||||
|
@ -3381,6 +3389,11 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn with_label(mut self, state: &str) -> Self {
|
||||
self.label = state.to_string();
|
||||
self
|
||||
}
|
||||
|
||||
fn set_state(&mut self, state: String, cx: &mut ViewContext<Self>) {
|
||||
self.push_to_nav_history(cx);
|
||||
self.state = state;
|
||||
|
|
1
styles/package-lock.json
generated
1
styles/package-lock.json
generated
|
@ -5,6 +5,7 @@
|
|||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "styles",
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
|
|
|
@ -94,3 +94,11 @@ export function popoverShadow(theme: Theme) {
|
|||
offset: [1, 2],
|
||||
};
|
||||
}
|
||||
|
||||
export function draggedShadow(theme: Theme) {
|
||||
return {
|
||||
blur: 6,
|
||||
color: theme.shadow,
|
||||
offset: [1, 2],
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Theme from "../themes/common/theme";
|
||||
import { iconColor, text, border, backgroundColor } from "./components";
|
||||
import { withOpacity } from "../utils/color";
|
||||
import { iconColor, text, border, backgroundColor, draggedShadow } from "./components";
|
||||
|
||||
export default function tabBar(theme: Theme) {
|
||||
const height = 32;
|
||||
|
@ -55,9 +56,23 @@ export default function tabBar(theme: Theme) {
|
|||
},
|
||||
}
|
||||
|
||||
const draggedTab = {
|
||||
...activePaneActiveTab,
|
||||
background: withOpacity(tab.background, 0.8),
|
||||
border: {
|
||||
...tab.border,
|
||||
top: false,
|
||||
left: false,
|
||||
right: false,
|
||||
bottom: false,
|
||||
},
|
||||
shadow: draggedShadow(theme),
|
||||
}
|
||||
|
||||
return {
|
||||
height,
|
||||
background: backgroundColor(theme, 300),
|
||||
dropTargetOverlayColor: withOpacity(theme.textColor.muted, 0.8),
|
||||
border: border(theme, "primary", {
|
||||
left: true,
|
||||
bottom: true,
|
||||
|
@ -71,6 +86,7 @@ export default function tabBar(theme: Theme) {
|
|||
activeTab: inactivePaneActiveTab,
|
||||
inactiveTab: inactivePaneInactiveTab,
|
||||
},
|
||||
draggedTab,
|
||||
paneButton: {
|
||||
color: iconColor(theme, "secondary"),
|
||||
border: {
|
||||
|
|
Loading…
Reference in a new issue