Merge pull request #1515 from zed-industries/drag-and-drop

Drag and drop
This commit is contained in:
K Simmons 2022-08-25 16:52:15 -07:00 committed by GitHub
commit 579c84b5e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 2124 additions and 758 deletions

9
Cargo.lock generated
View file

@ -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",

View file

@ -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, _>(

View 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"] }

View 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)
})
}
}

View file

@ -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(&region_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(&region_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);

View file

@ -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();
}
}
}

View file

@ -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 => {

View file

@ -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);
}

View file

@ -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);

View file

@ -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(&region_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 &region_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(&region_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(&region_id))
.unwrap_or(false)
}
if let Some(callback) = valid_region.handlers.get(&region_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(&region_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(&region_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(&region_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(&region_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(&region_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(&region_id) {
invalidated_views.push(region.view_id);
hover_regions.push((region.clone(), MouseRegionEvent::Hover(false, *e)));
self.hovered_region_ids.remove(&region_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(&region_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> {

View 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
}
}

View 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(&region_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(&region_id))
.unwrap_or(false)
}
if let Some(callback) = valid_region.handlers.get(&region_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(&region_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
}
}

View file

@ -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,

View file

@ -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),
}
}
}

View 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),
}
}
}

View file

@ -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())

View file

@ -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);

View file

@ -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

View file

@ -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)]

View file

@ -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

View file

@ -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()
}
}

View file

@ -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;

View file

@ -5,6 +5,7 @@
"requires": true,
"packages": {
"": {
"name": "styles",
"version": "1.0.0",
"license": "ISC",
"dependencies": {

View file

@ -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],
};
}

View file

@ -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: {