wip tab drag and drop
This commit is contained in:
parent
86fdd55fd4
commit
133c194f4a
20 changed files with 1642 additions and 413 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -1577,6 +1577,14 @@ version = "0.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "drag_and_drop"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"collections",
|
||||||
|
"gpui",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dwrote"
|
name = "dwrote"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
@ -6941,6 +6949,7 @@ dependencies = [
|
||||||
"clock",
|
"clock",
|
||||||
"collections",
|
"collections",
|
||||||
"context_menu",
|
"context_menu",
|
||||||
|
"drag_and_drop",
|
||||||
"futures",
|
"futures",
|
||||||
"gpui",
|
"gpui",
|
||||||
"language",
|
"language",
|
||||||
|
|
|
@ -566,7 +566,7 @@ impl ContactsPanel {
|
||||||
button
|
button
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.on_click(MouseButton::Left, move |_, cx| {
|
.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 })
|
cx.dispatch_action(ToggleProjectOnline { project })
|
||||||
})
|
})
|
||||||
.with_tooltip::<ToggleOnline, _>(
|
.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"] }
|
151
crates/drag_and_drop/src/drag_and_drop.rs
Normal file
151
crates/drag_and_drop/src/drag_and_drop.rs
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
use std::{any::Any, sync::Arc};
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
elements::{Container, MouseEventHandler},
|
||||||
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
|
Element, ElementBox, EventContext, MouseButton, RenderContext, View, ViewContext,
|
||||||
|
WeakViewHandle,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct State<V: View> {
|
||||||
|
position: Vector2F,
|
||||||
|
region_offset: Vector2F,
|
||||||
|
payload: Arc<dyn Any>,
|
||||||
|
render: Arc<dyn Fn(Arc<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, &T)> {
|
||||||
|
self.currently_dragged.as_ref().and_then(
|
||||||
|
|State {
|
||||||
|
position, payload, ..
|
||||||
|
}| {
|
||||||
|
payload
|
||||||
|
.downcast_ref::<T>()
|
||||||
|
.map(|payload| (position.clone(), payload))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dragging<T: Any>(
|
||||||
|
relative_to: Option<RectF>,
|
||||||
|
position: Vector2F,
|
||||||
|
payload: Arc<T>,
|
||||||
|
cx: &mut EventContext,
|
||||||
|
render: Arc<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 {
|
||||||
|
if let Some(relative_to) = relative_to {
|
||||||
|
relative_to.origin() - position
|
||||||
|
} else {
|
||||||
|
Vector2F::zero()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.currently_dragged = Some(State {
|
||||||
|
region_offset,
|
||||||
|
position,
|
||||||
|
payload,
|
||||||
|
render: Arc::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())
|
||||||
|
.boxed()
|
||||||
|
})
|
||||||
|
.on_up(MouseButton::Left, |_, cx| {
|
||||||
|
cx.defer(|cx| {
|
||||||
|
cx.update_global::<Self, _, _>(|this, _| this.currently_dragged.take());
|
||||||
|
});
|
||||||
|
cx.propogate_event();
|
||||||
|
})
|
||||||
|
.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 = Arc::new(payload);
|
||||||
|
let render = Arc::new(render);
|
||||||
|
self.on_drag(MouseButton::Left, move |e, cx| {
|
||||||
|
let payload = payload.clone();
|
||||||
|
let render = render.clone();
|
||||||
|
DragAndDrop::<V>::dragging(Some(e.region), e.position, payload, cx, render)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -104,6 +104,11 @@ impl Container {
|
||||||
self
|
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 {
|
pub fn with_padding_bottom(mut self, padding: f32) -> Self {
|
||||||
self.style.padding.bottom = padding;
|
self.style.padding.bottom = padding;
|
||||||
self
|
self
|
||||||
|
|
|
@ -5,10 +5,13 @@ use crate::{
|
||||||
vector::{vec2f, Vector2F},
|
vector::{vec2f, Vector2F},
|
||||||
},
|
},
|
||||||
platform::CursorStyle,
|
platform::CursorStyle,
|
||||||
scene::{CursorRegion, HandlerSet},
|
scene::{
|
||||||
|
ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragOverRegionEvent,
|
||||||
|
DragRegionEvent, HandlerSet, HoverRegionEvent, MoveRegionEvent, UpOutRegionEvent,
|
||||||
|
UpRegionEvent,
|
||||||
|
},
|
||||||
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MeasurementContext,
|
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MeasurementContext,
|
||||||
MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseState, PaintContext,
|
MouseButton, MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View,
|
||||||
RenderContext, SizeConstraint, View,
|
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{any::TypeId, ops::Range};
|
use std::{any::TypeId, ops::Range};
|
||||||
|
@ -42,10 +45,18 @@ impl MouseEventHandler {
|
||||||
self
|
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(
|
pub fn on_down(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_down(button, handler);
|
self.handlers = self.handlers.on_down(button, handler);
|
||||||
self
|
self
|
||||||
|
@ -54,7 +65,7 @@ impl MouseEventHandler {
|
||||||
pub fn on_up(
|
pub fn on_up(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_up(button, handler);
|
self.handlers = self.handlers.on_up(button, handler);
|
||||||
self
|
self
|
||||||
|
@ -63,7 +74,7 @@ impl MouseEventHandler {
|
||||||
pub fn on_click(
|
pub fn on_click(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_click(button, handler);
|
self.handlers = self.handlers.on_click(button, handler);
|
||||||
self
|
self
|
||||||
|
@ -72,7 +83,7 @@ impl MouseEventHandler {
|
||||||
pub fn on_down_out(
|
pub fn on_down_out(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_down_out(button, handler);
|
self.handlers = self.handlers.on_down_out(button, handler);
|
||||||
self
|
self
|
||||||
|
@ -81,16 +92,16 @@ impl MouseEventHandler {
|
||||||
pub fn on_up_out(
|
pub fn on_up_out(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_up(button, handler);
|
self.handlers = self.handlers.on_up_out(button, handler);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_drag(
|
pub fn on_drag(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static,
|
handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_drag(button, handler);
|
self.handlers = self.handlers.on_drag(button, handler);
|
||||||
self
|
self
|
||||||
|
@ -99,7 +110,7 @@ impl MouseEventHandler {
|
||||||
pub fn on_drag_over(
|
pub fn on_drag_over(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
handler: impl Fn(DragOverRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_drag_over(button, handler);
|
self.handlers = self.handlers.on_drag_over(button, handler);
|
||||||
self
|
self
|
||||||
|
@ -107,7 +118,7 @@ impl MouseEventHandler {
|
||||||
|
|
||||||
pub fn on_hover(
|
pub fn on_hover(
|
||||||
mut self,
|
mut self,
|
||||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_hover(handler);
|
self.handlers = self.handlers.on_hover(handler);
|
||||||
self
|
self
|
||||||
|
|
|
@ -7,8 +7,8 @@ use crate::{
|
||||||
geometry::{rect::RectF, vector::Vector2F},
|
geometry::{rect::RectF, vector::Vector2F},
|
||||||
json::json,
|
json::json,
|
||||||
presenter::MeasurementContext,
|
presenter::MeasurementContext,
|
||||||
Action, Axis, ElementStateHandle, LayoutContext, MouseMovedEvent, PaintContext, RenderContext,
|
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint,
|
||||||
SizeConstraint, Task, View,
|
Task, View,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -93,10 +93,11 @@ impl Tooltip {
|
||||||
};
|
};
|
||||||
let child =
|
let child =
|
||||||
MouseEventHandler::new::<MouseEventHandlerState<Tag>, _, _>(id, cx, |_, _| 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();
|
let window_id = cx.window_id();
|
||||||
if let Some(view_id) = cx.view_id() {
|
if let Some(view_id) = cx.view_id() {
|
||||||
if hover {
|
if e.started {
|
||||||
if !state.visible.get() {
|
if !state.visible.get() {
|
||||||
state.position.set(position);
|
state.position.set(position);
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,11 @@ use crate::{
|
||||||
json::{self, ToJson},
|
json::{self, ToJson},
|
||||||
keymap::Keystroke,
|
keymap::Keystroke,
|
||||||
platform::{CursorStyle, Event},
|
platform::{CursorStyle, Event},
|
||||||
scene::{CursorRegion, MouseRegionEvent},
|
scene::{
|
||||||
|
ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragOverRegionEvent,
|
||||||
|
DragRegionEvent, HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent,
|
||||||
|
UpRegionEvent,
|
||||||
|
},
|
||||||
text_layout::TextLayoutCache,
|
text_layout::TextLayoutCache,
|
||||||
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity,
|
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity,
|
||||||
FontSystem, ModelHandle, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseRegionId,
|
FontSystem, ModelHandle, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseRegionId,
|
||||||
|
@ -140,8 +144,7 @@ impl Presenter {
|
||||||
|
|
||||||
if cx.window_is_active(self.window_id) {
|
if cx.window_is_active(self.window_id) {
|
||||||
if let Some(event) = self.last_mouse_moved_event.clone() {
|
if let Some(event) = self.last_mouse_moved_event.clone() {
|
||||||
let mut invalidated_views = Vec::new();
|
let invalidated_views = self.handle_hover_events(&event, cx).invalidated_views;
|
||||||
self.handle_hover_events(&event, &mut invalidated_views, cx);
|
|
||||||
|
|
||||||
for view_id in invalidated_views {
|
for view_id in invalidated_views {
|
||||||
cx.notify_view(self.window_id, view_id);
|
cx.notify_view(self.window_id, view_id);
|
||||||
|
@ -216,48 +219,60 @@ impl Presenter {
|
||||||
|
|
||||||
pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) -> bool {
|
pub fn dispatch_event(&mut self, event: Event, cx: &mut MutableAppContext) -> bool {
|
||||||
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
|
if let Some(root_view_id) = cx.root_view_id(self.window_id) {
|
||||||
let mut invalidated_views = Vec::new();
|
|
||||||
let mut events_to_send = Vec::new();
|
let mut events_to_send = Vec::new();
|
||||||
|
|
||||||
match &event {
|
match &event {
|
||||||
Event::MouseDown(e @ MouseButtonEvent { position, .. }) => {
|
Event::MouseDown(e @ MouseButtonEvent { position, .. }) => {
|
||||||
let mut hit = false;
|
|
||||||
for (region, _) in self.mouse_regions.iter().rev() {
|
for (region, _) in self.mouse_regions.iter().rev() {
|
||||||
if region.bounds.contains_point(*position) {
|
if region.bounds.contains_point(*position) {
|
||||||
if !hit {
|
events_to_send.push((
|
||||||
hit = true;
|
region.clone(),
|
||||||
invalidated_views.push(region.view_id);
|
MouseRegionEvent::Down(DownRegionEvent {
|
||||||
events_to_send
|
region: region.bounds,
|
||||||
.push((region.clone(), MouseRegionEvent::Down(e.clone())));
|
platform_event: e.clone(),
|
||||||
self.clicked_region = Some(region.clone());
|
}),
|
||||||
self.prev_drag_position = Some(*position);
|
));
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
events_to_send
|
events_to_send.push((
|
||||||
.push((region.clone(), MouseRegionEvent::DownOut(e.clone())));
|
region.clone(),
|
||||||
|
MouseRegionEvent::DownOut(DownOutRegionEvent {
|
||||||
|
region: region.bounds,
|
||||||
|
platform_event: e.clone(),
|
||||||
|
}),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::MouseUp(e @ MouseButtonEvent { position, .. }) => {
|
Event::MouseUp(e @ MouseButtonEvent { position, .. }) => {
|
||||||
let mut hit = false;
|
|
||||||
for (region, _) in self.mouse_regions.iter().rev() {
|
for (region, _) in self.mouse_regions.iter().rev() {
|
||||||
if region.bounds.contains_point(*position) {
|
if region.bounds.contains_point(*position) {
|
||||||
if !hit {
|
events_to_send.push((
|
||||||
hit = true;
|
region.clone(),
|
||||||
invalidated_views.push(region.view_id);
|
MouseRegionEvent::Up(UpRegionEvent {
|
||||||
events_to_send
|
region: region.bounds,
|
||||||
.push((region.clone(), MouseRegionEvent::Up(e.clone())));
|
platform_event: e.clone(),
|
||||||
}
|
}),
|
||||||
|
));
|
||||||
} else {
|
} else {
|
||||||
events_to_send
|
events_to_send.push((
|
||||||
.push((region.clone(), MouseRegionEvent::UpOut(e.clone())));
|
region.clone(),
|
||||||
|
MouseRegionEvent::UpOut(UpOutRegionEvent {
|
||||||
|
region: region.bounds,
|
||||||
|
platform_event: e.clone(),
|
||||||
|
}),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.prev_drag_position.take();
|
self.prev_drag_position.take();
|
||||||
if let Some(region) = self.clicked_region.take() {
|
if let Some(region) = self.clicked_region.take() {
|
||||||
invalidated_views.push(region.view_id);
|
|
||||||
if region.bounds.contains_point(*position) {
|
if region.bounds.contains_point(*position) {
|
||||||
events_to_send.push((region, MouseRegionEvent::Click(e.clone())));
|
let bounds = region.bounds.clone();
|
||||||
|
events_to_send.push((
|
||||||
|
region,
|
||||||
|
MouseRegionEvent::Click(ClickRegionEvent {
|
||||||
|
region: bounds,
|
||||||
|
platform_event: e.clone(),
|
||||||
|
}),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -269,9 +284,28 @@ impl Presenter {
|
||||||
{
|
{
|
||||||
events_to_send.push((
|
events_to_send.push((
|
||||||
clicked_region.clone(),
|
clicked_region.clone(),
|
||||||
|
<<<<<<< HEAD
|
||||||
MouseRegionEvent::Drag(*prev_drag_position, *e),
|
MouseRegionEvent::Drag(*prev_drag_position, *e),
|
||||||
|
=======
|
||||||
|
MouseRegionEvent::Drag(DragRegionEvent {
|
||||||
|
region: clicked_region.bounds,
|
||||||
|
prev_drag_position: *prev_drag_position,
|
||||||
|
platform_event: e.clone(),
|
||||||
|
}),
|
||||||
|
>>>>>>> 4bd8a4b0 (wip tab drag and drop)
|
||||||
));
|
));
|
||||||
*prev_drag_position = *position;
|
}
|
||||||
|
|
||||||
|
for (region, _) in self.mouse_regions.iter().rev() {
|
||||||
|
if region.bounds.contains_point(*position) {
|
||||||
|
events_to_send.push((
|
||||||
|
region.clone(),
|
||||||
|
MouseRegionEvent::Move(MoveRegionEvent {
|
||||||
|
region: region.bounds,
|
||||||
|
platform_event: e.clone(),
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.last_mouse_moved_event = Some(event.clone());
|
self.last_mouse_moved_event = Some(event.clone());
|
||||||
|
@ -279,12 +313,12 @@ impl Presenter {
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut handled, mut event_cx) =
|
let (invalidated_views, dispatch_directives, handled) = {
|
||||||
self.handle_hover_events(&event, &mut invalidated_views, cx);
|
let mut event_cx = self.handle_hover_events(&event, cx);
|
||||||
|
event_cx.process_region_events(events_to_send);
|
||||||
|
|
||||||
for (region, event) in events_to_send {
|
if !event_cx.handled {
|
||||||
if event.is_local() {
|
event_cx.handled = event_cx.dispatch_event(root_view_id, &event);
|
||||||
handled = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(callback) = region.handlers.get(&event.handler_key()) {
|
if let Some(callback) = region.handlers.get(&event.handler_key()) {
|
||||||
|
@ -292,7 +326,13 @@ impl Presenter {
|
||||||
callback(event, event_cx);
|
callback(event, event_cx);
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
(
|
||||||
|
event_cx.invalidated_views,
|
||||||
|
event_cx.dispatched_actions,
|
||||||
|
event_cx.handled,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
if !handled {
|
if !handled {
|
||||||
handled = event_cx.dispatch_event(root_view_id, &event);
|
handled = event_cx.dispatch_event(root_view_id, &event);
|
||||||
|
@ -313,9 +353,8 @@ impl Presenter {
|
||||||
fn handle_hover_events<'a>(
|
fn handle_hover_events<'a>(
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
event: &Event,
|
event: &Event,
|
||||||
invalidated_views: &mut Vec<usize>,
|
|
||||||
cx: &'a mut MutableAppContext,
|
cx: &'a mut MutableAppContext,
|
||||||
) -> (bool, EventContext<'a>) {
|
) -> EventContext<'a> {
|
||||||
let mut events_to_send = Vec::new();
|
let mut events_to_send = Vec::new();
|
||||||
|
|
||||||
if let Event::MouseMoved(
|
if let Event::MouseMoved(
|
||||||
|
@ -343,46 +382,48 @@ impl Presenter {
|
||||||
hover_depth = Some(*depth);
|
hover_depth = Some(*depth);
|
||||||
if let Some(region_id) = region.id() {
|
if let Some(region_id) = region.id() {
|
||||||
if !self.hovered_region_ids.contains(®ion_id) {
|
if !self.hovered_region_ids.contains(®ion_id) {
|
||||||
invalidated_views.push(region.view_id);
|
|
||||||
let region_event = if pressed_button.is_some() {
|
let region_event = if pressed_button.is_some() {
|
||||||
MouseRegionEvent::DragOver(true, e.clone())
|
MouseRegionEvent::DragOver(DragOverRegionEvent {
|
||||||
|
region: region.bounds,
|
||||||
|
started: true,
|
||||||
|
platform_event: e.clone(),
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
MouseRegionEvent::Hover(true, e.clone())
|
MouseRegionEvent::Hover(HoverRegionEvent {
|
||||||
|
region: region.bounds,
|
||||||
|
started: true,
|
||||||
|
platform_event: e.clone(),
|
||||||
|
})
|
||||||
};
|
};
|
||||||
events_to_send.push((region.clone(), region_event));
|
events_to_send.push((region.clone(), region_event));
|
||||||
self.hovered_region_ids.insert(region_id);
|
self.hovered_region_ids.insert(region_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if let Some(region_id) = region.id() {
|
} else if let Some(region_id) = region.id() {
|
||||||
if self.hovered_region_ids.contains(®ion_id) {
|
if self.hovered_region_ids.contains(®ion_id) {
|
||||||
invalidated_views.push(region.view_id);
|
let region_event = if pressed_button.is_some() {
|
||||||
let region_event = if pressed_button.is_some() {
|
MouseRegionEvent::DragOver(DragOverRegionEvent {
|
||||||
MouseRegionEvent::DragOver(false, e.clone())
|
region: region.bounds,
|
||||||
} else {
|
started: false,
|
||||||
MouseRegionEvent::Hover(false, e.clone())
|
platform_event: e.clone(),
|
||||||
};
|
})
|
||||||
events_to_send.push((region.clone(), region_event));
|
} else {
|
||||||
self.hovered_region_ids.remove(®ion_id);
|
MouseRegionEvent::Hover(HoverRegionEvent {
|
||||||
}
|
region: region.bounds,
|
||||||
|
started: false,
|
||||||
|
platform_event: e.clone(),
|
||||||
|
})
|
||||||
|
};
|
||||||
|
events_to_send.push((region.clone(), region_event));
|
||||||
|
self.hovered_region_ids.remove(®ion_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut event_cx = self.build_event_context(cx);
|
let mut event_cx = self.build_event_context(cx);
|
||||||
let mut handled = false;
|
event_cx.process_region_events(events_to_send);
|
||||||
|
event_cx
|
||||||
for (region, event) in events_to_send {
|
|
||||||
if event.is_local() {
|
|
||||||
handled = true;
|
|
||||||
}
|
|
||||||
if let Some(callback) = region.handlers.get(&event.handler_key()) {
|
|
||||||
event_cx.with_current_view(region.view_id, |event_cx| {
|
|
||||||
callback(event, event_cx);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(handled, event_cx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_event_context<'a>(
|
pub fn build_event_context<'a>(
|
||||||
|
@ -396,6 +437,9 @@ impl Presenter {
|
||||||
view_stack: Default::default(),
|
view_stack: Default::default(),
|
||||||
invalidated_views: Default::default(),
|
invalidated_views: Default::default(),
|
||||||
notify_count: 0,
|
notify_count: 0,
|
||||||
|
clicked_region: &mut self.clicked_region,
|
||||||
|
prev_drag_position: &mut self.prev_drag_position,
|
||||||
|
handled: false,
|
||||||
window_id: self.window_id,
|
window_id: self.window_id,
|
||||||
app: cx,
|
app: cx,
|
||||||
}
|
}
|
||||||
|
@ -615,6 +659,9 @@ pub struct EventContext<'a> {
|
||||||
pub window_id: usize,
|
pub window_id: usize,
|
||||||
pub notify_count: usize,
|
pub notify_count: usize,
|
||||||
view_stack: Vec<usize>,
|
view_stack: Vec<usize>,
|
||||||
|
clicked_region: &'a mut Option<MouseRegion>,
|
||||||
|
prev_drag_position: &'a mut Option<Vector2F>,
|
||||||
|
handled: bool,
|
||||||
invalidated_views: HashSet<usize>,
|
invalidated_views: HashSet<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -630,6 +677,36 @@ impl<'a> EventContext<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn process_region_events(&mut self, events: Vec<(MouseRegion, MouseRegionEvent)>) {
|
||||||
|
for (region, event) in events {
|
||||||
|
if event.is_local() {
|
||||||
|
if self.handled {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match &event {
|
||||||
|
MouseRegionEvent::Down(e) => {
|
||||||
|
*self.clicked_region = Some(region.clone());
|
||||||
|
*self.prev_drag_position = Some(e.position);
|
||||||
|
}
|
||||||
|
MouseRegionEvent::Drag(e) => {
|
||||||
|
*self.prev_drag_position = Some(e.position);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.invalidated_views.insert(region.view_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(callback) = region.handlers.get(&event.handler_key()) {
|
||||||
|
self.handled = true;
|
||||||
|
self.with_current_view(region.view_id, |event_cx| {
|
||||||
|
callback(event, event_cx);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn with_current_view<F, T>(&mut self, view_id: usize, f: F) -> T
|
fn with_current_view<F, T>(&mut self, view_id: usize, f: F) -> T
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Self) -> T,
|
F: FnOnce(&mut Self) -> T,
|
||||||
|
@ -681,6 +758,10 @@ impl<'a> EventContext<'a> {
|
||||||
pub fn notify_count(&self) -> usize {
|
pub fn notify_count(&self) -> usize {
|
||||||
self.notify_count
|
self.notify_count
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn propogate_event(&mut self) {
|
||||||
|
self.handled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Deref for EventContext<'a> {
|
impl<'a> Deref for EventContext<'a> {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
mod mouse_region;
|
mod mouse_region;
|
||||||
|
mod mouse_region_event;
|
||||||
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
@ -13,6 +14,7 @@ use crate::{
|
||||||
ImageData,
|
ImageData,
|
||||||
};
|
};
|
||||||
pub use mouse_region::*;
|
pub use mouse_region::*;
|
||||||
|
pub use mouse_region_event::*;
|
||||||
|
|
||||||
pub struct Scene {
|
pub struct Scene {
|
||||||
scale_factor: f32,
|
scale_factor: f32,
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
use std::{
|
use std::{any::TypeId, mem::Discriminant, rc::Rc};
|
||||||
any::TypeId,
|
|
||||||
mem::{discriminant, Discriminant},
|
|
||||||
rc::Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use collections::HashMap;
|
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, DragOverRegionEvent, DragRegionEvent,
|
||||||
|
HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
pub struct MouseRegion {
|
pub struct MouseRegion {
|
||||||
|
@ -52,7 +53,7 @@ impl MouseRegion {
|
||||||
pub fn on_down(
|
pub fn on_down(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_down(button, handler);
|
self.handlers = self.handlers.on_down(button, handler);
|
||||||
self
|
self
|
||||||
|
@ -61,7 +62,7 @@ impl MouseRegion {
|
||||||
pub fn on_up(
|
pub fn on_up(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_up(button, handler);
|
self.handlers = self.handlers.on_up(button, handler);
|
||||||
self
|
self
|
||||||
|
@ -70,7 +71,7 @@ impl MouseRegion {
|
||||||
pub fn on_click(
|
pub fn on_click(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_click(button, handler);
|
self.handlers = self.handlers.on_click(button, handler);
|
||||||
self
|
self
|
||||||
|
@ -79,7 +80,7 @@ impl MouseRegion {
|
||||||
pub fn on_down_out(
|
pub fn on_down_out(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_down_out(button, handler);
|
self.handlers = self.handlers.on_down_out(button, handler);
|
||||||
self
|
self
|
||||||
|
@ -88,7 +89,7 @@ impl MouseRegion {
|
||||||
pub fn on_up_out(
|
pub fn on_up_out(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_up_out(button, handler);
|
self.handlers = self.handlers.on_up_out(button, handler);
|
||||||
self
|
self
|
||||||
|
@ -97,7 +98,7 @@ impl MouseRegion {
|
||||||
pub fn on_drag(
|
pub fn on_drag(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static,
|
handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_drag(button, handler);
|
self.handlers = self.handlers.on_drag(button, handler);
|
||||||
self
|
self
|
||||||
|
@ -106,7 +107,7 @@ impl MouseRegion {
|
||||||
pub fn on_drag_over(
|
pub fn on_drag_over(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
handler: impl Fn(DragOverRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_drag_over(button, handler);
|
self.handlers = self.handlers.on_drag_over(button, handler);
|
||||||
self
|
self
|
||||||
|
@ -114,7 +115,7 @@ impl MouseRegion {
|
||||||
|
|
||||||
pub fn on_hover(
|
pub fn on_hover(
|
||||||
mut self,
|
mut self,
|
||||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.handlers = self.handlers.on_hover(handler);
|
self.handlers = self.handlers.on_hover(handler);
|
||||||
self
|
self
|
||||||
|
@ -191,15 +192,32 @@ impl HandlerSet {
|
||||||
self.set.get(key).cloned()
|
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(
|
pub fn on_down(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.set.insert((MouseRegionEvent::down_disc(), Some(button)),
|
self.set.insert((MouseRegionEvent::down_disc(), Some(button)),
|
||||||
Rc::new(move |region_event, cx| {
|
Rc::new(move |region_event, cx| {
|
||||||
if let MouseRegionEvent::Down(mouse_button_event) = region_event {
|
if let MouseRegionEvent::Down(e) = region_event {
|
||||||
handler(mouse_button_event, cx);
|
handler(e, cx);
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
panic!(
|
||||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}",
|
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}",
|
||||||
|
@ -212,12 +230,12 @@ impl HandlerSet {
|
||||||
pub fn on_up(
|
pub fn on_up(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.set.insert((MouseRegionEvent::up_disc(), Some(button)),
|
self.set.insert((MouseRegionEvent::up_disc(), Some(button)),
|
||||||
Rc::new(move |region_event, cx| {
|
Rc::new(move |region_event, cx| {
|
||||||
if let MouseRegionEvent::Up(mouse_button_event) = region_event {
|
if let MouseRegionEvent::Up(e) = region_event {
|
||||||
handler(mouse_button_event, cx);
|
handler(e, cx);
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
panic!(
|
||||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}",
|
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}",
|
||||||
|
@ -230,12 +248,12 @@ impl HandlerSet {
|
||||||
pub fn on_click(
|
pub fn on_click(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.set.insert((MouseRegionEvent::click_disc(), Some(button)),
|
self.set.insert((MouseRegionEvent::click_disc(), Some(button)),
|
||||||
Rc::new(move |region_event, cx| {
|
Rc::new(move |region_event, cx| {
|
||||||
if let MouseRegionEvent::Click(mouse_button_event) = region_event {
|
if let MouseRegionEvent::Click(e) = region_event {
|
||||||
handler(mouse_button_event, cx);
|
handler(e, cx);
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
panic!(
|
||||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}",
|
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}",
|
||||||
|
@ -248,12 +266,12 @@ impl HandlerSet {
|
||||||
pub fn on_down_out(
|
pub fn on_down_out(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.set.insert((MouseRegionEvent::down_out_disc(), Some(button)),
|
self.set.insert((MouseRegionEvent::down_out_disc(), Some(button)),
|
||||||
Rc::new(move |region_event, cx| {
|
Rc::new(move |region_event, cx| {
|
||||||
if let MouseRegionEvent::DownOut(mouse_button_event) = region_event {
|
if let MouseRegionEvent::DownOut(e) = region_event {
|
||||||
handler(mouse_button_event, cx);
|
handler(e, cx);
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
panic!(
|
||||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}",
|
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}",
|
||||||
|
@ -266,12 +284,12 @@ impl HandlerSet {
|
||||||
pub fn on_up_out(
|
pub fn on_up_out(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.set.insert((MouseRegionEvent::up_out_disc(), Some(button)),
|
self.set.insert((MouseRegionEvent::up_out_disc(), Some(button)),
|
||||||
Rc::new(move |region_event, cx| {
|
Rc::new(move |region_event, cx| {
|
||||||
if let MouseRegionEvent::UpOut(mouse_button_event) = region_event {
|
if let MouseRegionEvent::UpOut(e) = region_event {
|
||||||
handler(mouse_button_event, cx);
|
handler(e, cx);
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
panic!(
|
||||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}",
|
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}",
|
||||||
|
@ -284,12 +302,12 @@ impl HandlerSet {
|
||||||
pub fn on_drag(
|
pub fn on_drag(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static,
|
handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.set.insert((MouseRegionEvent::drag_disc(), Some(button)),
|
self.set.insert((MouseRegionEvent::drag_disc(), Some(button)),
|
||||||
Rc::new(move |region_event, cx| {
|
Rc::new(move |region_event, cx| {
|
||||||
if let MouseRegionEvent::Drag(prev_drag_position, mouse_moved_event) = region_event {
|
if let MouseRegionEvent::Drag(e) = region_event {
|
||||||
handler(prev_drag_position, mouse_moved_event, cx);
|
handler(e, cx);
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
panic!(
|
||||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}",
|
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}",
|
||||||
|
@ -302,12 +320,12 @@ impl HandlerSet {
|
||||||
pub fn on_drag_over(
|
pub fn on_drag_over(
|
||||||
mut self,
|
mut self,
|
||||||
button: MouseButton,
|
button: MouseButton,
|
||||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
handler: impl Fn(DragOverRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.set.insert((MouseRegionEvent::drag_over_disc(), Some(button)),
|
self.set.insert((MouseRegionEvent::drag_over_disc(), Some(button)),
|
||||||
Rc::new(move |region_event, cx| {
|
Rc::new(move |region_event, cx| {
|
||||||
if let MouseRegionEvent::DragOver(started, mouse_moved_event) = region_event {
|
if let MouseRegionEvent::DragOver(e) = region_event {
|
||||||
handler(started, mouse_moved_event, cx);
|
handler(e, cx);
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
panic!(
|
||||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DragOver, found {:?}",
|
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DragOver, found {:?}",
|
||||||
|
@ -319,12 +337,12 @@ impl HandlerSet {
|
||||||
|
|
||||||
pub fn on_hover(
|
pub fn on_hover(
|
||||||
mut self,
|
mut self,
|
||||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.set.insert((MouseRegionEvent::hover_disc(), None),
|
self.set.insert((MouseRegionEvent::hover_disc(), None),
|
||||||
Rc::new(move |region_event, cx| {
|
Rc::new(move |region_event, cx| {
|
||||||
if let MouseRegionEvent::Hover(started, mouse_moved_event) = region_event {
|
if let MouseRegionEvent::Hover(e) = region_event {
|
||||||
handler(started, mouse_moved_event, cx);
|
handler(e, cx);
|
||||||
} else {
|
} else {
|
||||||
panic!(
|
panic!(
|
||||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}",
|
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}",
|
||||||
|
@ -334,106 +352,3 @@ impl HandlerSet {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum MouseRegionEvent {
|
|
||||||
Move(MouseMovedEvent),
|
|
||||||
Drag(Vector2F, MouseMovedEvent),
|
|
||||||
DragOver(bool, MouseMovedEvent),
|
|
||||||
Hover(bool, MouseMovedEvent),
|
|
||||||
Down(MouseButtonEvent),
|
|
||||||
Up(MouseButtonEvent),
|
|
||||||
Click(MouseButtonEvent),
|
|
||||||
UpOut(MouseButtonEvent),
|
|
||||||
DownOut(MouseButtonEvent),
|
|
||||||
ScrollWheel(ScrollWheelEvent),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MouseRegionEvent {
|
|
||||||
pub fn move_disc() -> Discriminant<MouseRegionEvent> {
|
|
||||||
discriminant(&MouseRegionEvent::Move(Default::default()))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drag_disc() -> Discriminant<MouseRegionEvent> {
|
|
||||||
discriminant(&MouseRegionEvent::Drag(
|
|
||||||
Default::default(),
|
|
||||||
Default::default(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drag_over_disc() -> Discriminant<MouseRegionEvent> {
|
|
||||||
discriminant(&MouseRegionEvent::DragOver(
|
|
||||||
Default::default(),
|
|
||||||
Default::default(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hover_disc() -> Discriminant<MouseRegionEvent> {
|
|
||||||
discriminant(&MouseRegionEvent::Hover(
|
|
||||||
Default::default(),
|
|
||||||
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 is_local(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
MouseRegionEvent::DownOut(_)
|
|
||||||
| MouseRegionEvent::UpOut(_)
|
|
||||||
| MouseRegionEvent::DragOver(_, _) => false,
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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::DragOver(_, MouseMovedEvent { pressed_button, .. }) => {
|
|
||||||
(Self::drag_over_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::UpOut(MouseButtonEvent { button, .. }) => {
|
|
||||||
(Self::up_out_disc(), Some(*button))
|
|
||||||
}
|
|
||||||
MouseRegionEvent::DownOut(MouseButtonEvent { button, .. }) => {
|
|
||||||
(Self::down_out_disc(), Some(*button))
|
|
||||||
}
|
|
||||||
MouseRegionEvent::ScrollWheel(_) => (Self::scroll_wheel_disc(), None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
231
crates/gpui/src/scene/mouse_region_event.rs
Normal file
231
crates/gpui/src/scene/mouse_region_event.rs
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
use std::{
|
||||||
|
mem::{discriminant, Discriminant},
|
||||||
|
ops::Deref,
|
||||||
|
};
|
||||||
|
|
||||||
|
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
|
||||||
|
|
||||||
|
use crate::{MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
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)]
|
||||||
|
pub struct DragRegionEvent {
|
||||||
|
pub region: RectF,
|
||||||
|
pub prev_drag_position: Vector2F,
|
||||||
|
pub platform_event: MouseMovedEvent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for DragRegionEvent {
|
||||||
|
type Target = MouseMovedEvent;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.platform_event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct DragOverRegionEvent {
|
||||||
|
pub region: RectF,
|
||||||
|
pub started: bool,
|
||||||
|
pub platform_event: MouseMovedEvent,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for DragOverRegionEvent {
|
||||||
|
type Target = MouseMovedEvent;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.platform_event
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
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)]
|
||||||
|
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)]
|
||||||
|
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)]
|
||||||
|
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)]
|
||||||
|
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)]
|
||||||
|
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)]
|
||||||
|
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)]
|
||||||
|
pub enum MouseRegionEvent {
|
||||||
|
Move(MoveRegionEvent),
|
||||||
|
Drag(DragRegionEvent),
|
||||||
|
DragOver(DragOverRegionEvent),
|
||||||
|
Hover(HoverRegionEvent),
|
||||||
|
Down(DownRegionEvent),
|
||||||
|
Up(UpRegionEvent),
|
||||||
|
Click(ClickRegionEvent),
|
||||||
|
DownOut(DownOutRegionEvent),
|
||||||
|
UpOut(UpOutRegionEvent),
|
||||||
|
ScrollWheel(ScrollWheelRegionEvent),
|
||||||
|
}
|
||||||
|
|
||||||
|
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 drag_over_disc() -> Discriminant<MouseRegionEvent> {
|
||||||
|
discriminant(&MouseRegionEvent::DragOver(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 is_local(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
MouseRegionEvent::DownOut(_)
|
||||||
|
| MouseRegionEvent::UpOut(_)
|
||||||
|
| MouseRegionEvent::DragOver(_) => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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::DragOver(e) => (Self::drag_over_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,
|
impl_internal_actions, keymap,
|
||||||
platform::CursorStyle,
|
platform::CursorStyle,
|
||||||
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton,
|
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton,
|
||||||
MouseButtonEvent, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext,
|
MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle,
|
||||||
ViewHandle,
|
|
||||||
};
|
};
|
||||||
use menu::{Confirm, SelectNext, SelectPrev};
|
use menu::{Confirm, SelectNext, SelectPrev};
|
||||||
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
|
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
|
||||||
|
@ -1064,25 +1063,22 @@ impl ProjectPanel {
|
||||||
.with_padding_left(padding)
|
.with_padding_left(padding)
|
||||||
.boxed()
|
.boxed()
|
||||||
})
|
})
|
||||||
.on_click(
|
.on_click(MouseButton::Left, move |e, cx| {
|
||||||
MouseButton::Left,
|
if kind == EntryKind::Dir {
|
||||||
move |MouseButtonEvent { click_count, .. }, cx| {
|
cx.dispatch_action(ToggleExpanded(entry_id))
|
||||||
if kind == EntryKind::Dir {
|
} else {
|
||||||
cx.dispatch_action(ToggleExpanded(entry_id))
|
cx.dispatch_action(Open {
|
||||||
} else {
|
entry_id,
|
||||||
cx.dispatch_action(Open {
|
change_focus: e.click_count > 1,
|
||||||
entry_id,
|
})
|
||||||
change_focus: click_count > 1,
|
}
|
||||||
})
|
})
|
||||||
}
|
.on_down(MouseButton::Right, move |e, cx| {
|
||||||
},
|
cx.dispatch_action(DeployContextMenu {
|
||||||
)
|
entry_id,
|
||||||
.on_down(
|
position: e.position,
|
||||||
MouseButton::Right,
|
})
|
||||||
move |MouseButtonEvent { position, .. }, cx| {
|
})
|
||||||
cx.dispatch_action(DeployContextMenu { entry_id, position })
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
@ -1129,16 +1125,16 @@ impl View for ProjectPanel {
|
||||||
.expanded()
|
.expanded()
|
||||||
.boxed()
|
.boxed()
|
||||||
})
|
})
|
||||||
.on_down(
|
.on_down(MouseButton::Right, move |e, cx| {
|
||||||
MouseButton::Right,
|
// When deploying the context menu anywhere below the last project entry,
|
||||||
move |MouseButtonEvent { position, .. }, cx| {
|
// act as if the user clicked the root of the last worktree.
|
||||||
// When deploying the context menu anywhere below the last project entry,
|
if let Some(entry_id) = last_worktree_root_id {
|
||||||
// act as if the user clicked the root of the last worktree.
|
cx.dispatch_action(DeployContextMenu {
|
||||||
if let Some(entry_id) = last_worktree_root_id {
|
entry_id,
|
||||||
cx.dispatch_action(DeployContextMenu { entry_id, position })
|
position: e.position,
|
||||||
}
|
})
|
||||||
},
|
}
|
||||||
)
|
})
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
.with_child(ChildView::new(&self.context_menu).boxed())
|
.with_child(ChildView::new(&self.context_menu).boxed())
|
||||||
|
|
|
@ -16,8 +16,8 @@ use gpui::{
|
||||||
},
|
},
|
||||||
json::json,
|
json::json,
|
||||||
text_layout::{Line, RunStyle},
|
text_layout::{Line, RunStyle},
|
||||||
Event, FontCache, KeyDownEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion,
|
Event, FontCache, KeyDownEvent, MouseButton, MouseRegion, PaintContext, Quad, ScrollWheelEvent,
|
||||||
PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, WeakViewHandle,
|
TextLayoutCache, WeakModelHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ordered_float::OrderedFloat;
|
use ordered_float::OrderedFloat;
|
||||||
|
|
793
crates/terminal/src/terminal_element.rs
Normal file
793
crates/terminal/src/terminal_element.rs
Normal file
|
@ -0,0 +1,793 @@
|
||||||
|
use alacritty_terminal::{
|
||||||
|
grid::{Dimensions, GridIterator, Indexed, Scroll},
|
||||||
|
index::{Column as GridCol, Line as GridLine, Point, Side},
|
||||||
|
selection::{Selection, SelectionRange, SelectionType},
|
||||||
|
sync::FairMutex,
|
||||||
|
term::{
|
||||||
|
cell::{Cell, Flags},
|
||||||
|
SizeInfo,
|
||||||
|
},
|
||||||
|
Term,
|
||||||
|
};
|
||||||
|
use editor::{Cursor, CursorShape, HighlightedRange, HighlightedRangeLine};
|
||||||
|
use gpui::{
|
||||||
|
color::Color,
|
||||||
|
elements::*,
|
||||||
|
fonts::{TextStyle, Underline},
|
||||||
|
geometry::{
|
||||||
|
rect::RectF,
|
||||||
|
vector::{vec2f, Vector2F},
|
||||||
|
},
|
||||||
|
json::json,
|
||||||
|
text_layout::{Line, RunStyle},
|
||||||
|
Event, FontCache, KeyDownEvent, MouseButton, MouseRegion, PaintContext, Quad, ScrollWheelEvent,
|
||||||
|
SizeConstraint, TextLayoutCache, WeakModelHandle,
|
||||||
|
};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use ordered_float::OrderedFloat;
|
||||||
|
use settings::Settings;
|
||||||
|
use theme::TerminalStyle;
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
|
use std::{cmp::min, ops::Range, sync::Arc};
|
||||||
|
use std::{fmt::Debug, ops::Sub};
|
||||||
|
|
||||||
|
use crate::{color_translation::convert_color, connection::TerminalConnection, ZedListener};
|
||||||
|
|
||||||
|
///Scrolling is unbearably sluggish by default. Alacritty supports a configurable
|
||||||
|
///Scroll multiplier that is set to 3 by default. This will be removed when I
|
||||||
|
///Implement scroll bars.
|
||||||
|
const ALACRITTY_SCROLL_MULTIPLIER: f32 = 3.;
|
||||||
|
|
||||||
|
///Used to display the grid as passed to Alacritty and the TTY.
|
||||||
|
///Useful for debugging inconsistencies between behavior and display
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
const DEBUG_GRID: bool = false;
|
||||||
|
|
||||||
|
///The GPUI element that paints the terminal.
|
||||||
|
///We need to keep a reference to the view for mouse events, do we need it for any other terminal stuff, or can we move that to connection?
|
||||||
|
pub struct TerminalEl {
|
||||||
|
connection: WeakModelHandle<TerminalConnection>,
|
||||||
|
view_id: usize,
|
||||||
|
modal: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
///New type pattern so I don't mix these two up
|
||||||
|
struct CellWidth(f32);
|
||||||
|
struct LineHeight(f32);
|
||||||
|
|
||||||
|
struct LayoutLine {
|
||||||
|
cells: Vec<LayoutCell>,
|
||||||
|
highlighted_range: Option<Range<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
///New type pattern to ensure that we use adjusted mouse positions throughout the code base, rather than
|
||||||
|
struct PaneRelativePos(Vector2F);
|
||||||
|
|
||||||
|
///Functionally the constructor for the PaneRelativePos type, mutates the mouse_position
|
||||||
|
fn relative_pos(mouse_position: Vector2F, origin: Vector2F) -> PaneRelativePos {
|
||||||
|
PaneRelativePos(mouse_position.sub(origin)) //Avoid the extra allocation by mutating
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
struct LayoutCell {
|
||||||
|
point: Point<i32, i32>,
|
||||||
|
text: Line, //NOTE TO SELF THIS IS BAD PERFORMANCE RN!
|
||||||
|
background_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LayoutCell {
|
||||||
|
fn new(point: Point<i32, i32>, text: Line, background_color: Color) -> LayoutCell {
|
||||||
|
LayoutCell {
|
||||||
|
point,
|
||||||
|
text,
|
||||||
|
background_color,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///The information generated during layout that is nescessary for painting
|
||||||
|
pub struct LayoutState {
|
||||||
|
layout_lines: Vec<LayoutLine>,
|
||||||
|
line_height: LineHeight,
|
||||||
|
em_width: CellWidth,
|
||||||
|
cursor: Option<Cursor>,
|
||||||
|
background_color: Color,
|
||||||
|
cur_size: SizeInfo,
|
||||||
|
terminal: Arc<FairMutex<Term<ZedListener>>>,
|
||||||
|
selection_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TerminalEl {
|
||||||
|
pub fn new(
|
||||||
|
view_id: usize,
|
||||||
|
connection: WeakModelHandle<TerminalConnection>,
|
||||||
|
modal: bool,
|
||||||
|
) -> TerminalEl {
|
||||||
|
TerminalEl {
|
||||||
|
view_id,
|
||||||
|
connection,
|
||||||
|
modal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Element for TerminalEl {
|
||||||
|
type LayoutState = LayoutState;
|
||||||
|
type PaintState = ();
|
||||||
|
|
||||||
|
fn layout(
|
||||||
|
&mut self,
|
||||||
|
constraint: gpui::SizeConstraint,
|
||||||
|
cx: &mut gpui::LayoutContext,
|
||||||
|
) -> (gpui::geometry::vector::Vector2F, Self::LayoutState) {
|
||||||
|
//Settings immutably borrows cx here for the settings and font cache
|
||||||
|
//and we need to modify the cx to resize the terminal. So instead of
|
||||||
|
//storing Settings or the font_cache(), we toss them ASAP and then reborrow later
|
||||||
|
let text_style = make_text_style(cx.font_cache(), cx.global::<Settings>());
|
||||||
|
let line_height = LineHeight(cx.font_cache().line_height(text_style.font_size));
|
||||||
|
let cell_width = CellWidth(
|
||||||
|
cx.font_cache()
|
||||||
|
.em_advance(text_style.font_id, text_style.font_size),
|
||||||
|
);
|
||||||
|
let connection_handle = self.connection.upgrade(cx).unwrap();
|
||||||
|
|
||||||
|
//Tell the view our new size. Requires a mutable borrow of cx and the view
|
||||||
|
let cur_size = make_new_size(constraint, &cell_width, &line_height);
|
||||||
|
//Note that set_size locks and mutates the terminal.
|
||||||
|
connection_handle.update(cx.app, |connection, _| connection.set_size(cur_size));
|
||||||
|
|
||||||
|
let (selection_color, terminal_theme) = {
|
||||||
|
let theme = &(cx.global::<Settings>()).theme;
|
||||||
|
(theme.editor.selection.selection, &theme.terminal)
|
||||||
|
};
|
||||||
|
|
||||||
|
let terminal_mutex = connection_handle.read(cx).term.clone();
|
||||||
|
let term = terminal_mutex.lock();
|
||||||
|
let grid = term.grid();
|
||||||
|
let cursor_point = grid.cursor.point;
|
||||||
|
let cursor_text = grid[cursor_point.line][cursor_point.column].c.to_string();
|
||||||
|
|
||||||
|
let content = term.renderable_content();
|
||||||
|
|
||||||
|
let layout_lines = layout_lines(
|
||||||
|
content.display_iter,
|
||||||
|
&text_style,
|
||||||
|
terminal_theme,
|
||||||
|
cx.text_layout_cache,
|
||||||
|
self.modal,
|
||||||
|
content.selection,
|
||||||
|
);
|
||||||
|
|
||||||
|
let block_text = cx.text_layout_cache.layout_str(
|
||||||
|
&cursor_text,
|
||||||
|
text_style.font_size,
|
||||||
|
&[(
|
||||||
|
cursor_text.len(),
|
||||||
|
RunStyle {
|
||||||
|
font_id: text_style.font_id,
|
||||||
|
color: terminal_theme.colors.background,
|
||||||
|
underline: Default::default(),
|
||||||
|
},
|
||||||
|
)],
|
||||||
|
);
|
||||||
|
|
||||||
|
let cursor = get_cursor_shape(
|
||||||
|
content.cursor.point.line.0 as usize,
|
||||||
|
content.cursor.point.column.0 as usize,
|
||||||
|
content.display_offset,
|
||||||
|
&line_height,
|
||||||
|
&cell_width,
|
||||||
|
cur_size.total_lines(),
|
||||||
|
&block_text,
|
||||||
|
)
|
||||||
|
.map(move |(cursor_position, block_width)| {
|
||||||
|
let block_width = if block_width != 0.0 {
|
||||||
|
block_width
|
||||||
|
} else {
|
||||||
|
cell_width.0
|
||||||
|
};
|
||||||
|
|
||||||
|
Cursor::new(
|
||||||
|
cursor_position,
|
||||||
|
block_width,
|
||||||
|
line_height.0,
|
||||||
|
terminal_theme.colors.cursor,
|
||||||
|
CursorShape::Block,
|
||||||
|
Some(block_text.clone()),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
drop(term);
|
||||||
|
|
||||||
|
let background_color = if self.modal {
|
||||||
|
terminal_theme.colors.modal_background
|
||||||
|
} else {
|
||||||
|
terminal_theme.colors.background
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
constraint.max,
|
||||||
|
LayoutState {
|
||||||
|
layout_lines,
|
||||||
|
line_height,
|
||||||
|
em_width: cell_width,
|
||||||
|
cursor,
|
||||||
|
cur_size,
|
||||||
|
background_color,
|
||||||
|
terminal: terminal_mutex,
|
||||||
|
selection_color,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint(
|
||||||
|
&mut self,
|
||||||
|
bounds: gpui::geometry::rect::RectF,
|
||||||
|
visible_bounds: gpui::geometry::rect::RectF,
|
||||||
|
layout: &mut Self::LayoutState,
|
||||||
|
cx: &mut gpui::PaintContext,
|
||||||
|
) -> Self::PaintState {
|
||||||
|
//Setup element stuff
|
||||||
|
let clip_bounds = Some(visible_bounds);
|
||||||
|
|
||||||
|
cx.paint_layer(clip_bounds, |cx| {
|
||||||
|
let cur_size = layout.cur_size.clone();
|
||||||
|
let origin = bounds.origin() + vec2f(layout.em_width.0, 0.);
|
||||||
|
|
||||||
|
//Elements are ephemeral, only at paint time do we know what could be clicked by a mouse
|
||||||
|
attach_mouse_handlers(
|
||||||
|
origin,
|
||||||
|
cur_size,
|
||||||
|
self.view_id,
|
||||||
|
&layout.terminal,
|
||||||
|
visible_bounds,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
|
||||||
|
cx.paint_layer(clip_bounds, |cx| {
|
||||||
|
//Start with a background color
|
||||||
|
cx.scene.push_quad(Quad {
|
||||||
|
bounds: RectF::new(bounds.origin(), bounds.size()),
|
||||||
|
background: Some(layout.background_color),
|
||||||
|
border: Default::default(),
|
||||||
|
corner_radius: 0.,
|
||||||
|
});
|
||||||
|
|
||||||
|
//Draw cell backgrounds
|
||||||
|
for layout_line in &layout.layout_lines {
|
||||||
|
for layout_cell in &layout_line.cells {
|
||||||
|
let position = vec2f(
|
||||||
|
(origin.x() + layout_cell.point.column as f32 * layout.em_width.0)
|
||||||
|
.floor(),
|
||||||
|
origin.y() + layout_cell.point.line as f32 * layout.line_height.0,
|
||||||
|
);
|
||||||
|
let size = vec2f(layout.em_width.0.ceil(), layout.line_height.0);
|
||||||
|
|
||||||
|
cx.scene.push_quad(Quad {
|
||||||
|
bounds: RectF::new(position, size),
|
||||||
|
background: Some(layout_cell.background_color),
|
||||||
|
border: Default::default(),
|
||||||
|
corner_radius: 0.,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Draw Selection
|
||||||
|
cx.paint_layer(clip_bounds, |cx| {
|
||||||
|
let mut highlight_y = None;
|
||||||
|
let highlight_lines = layout
|
||||||
|
.layout_lines
|
||||||
|
.iter()
|
||||||
|
.filter_map(|line| {
|
||||||
|
if let Some(range) = &line.highlighted_range {
|
||||||
|
if let None = highlight_y {
|
||||||
|
highlight_y = Some(
|
||||||
|
origin.y()
|
||||||
|
+ line.cells[0].point.line as f32 * layout.line_height.0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let start_x = origin.x()
|
||||||
|
+ line.cells[range.start].point.column as f32 * layout.em_width.0;
|
||||||
|
let end_x = origin.x()
|
||||||
|
+ line.cells[range.end].point.column as f32 * layout.em_width.0
|
||||||
|
+ layout.em_width.0;
|
||||||
|
|
||||||
|
return Some(HighlightedRangeLine { start_x, end_x });
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<HighlightedRangeLine>>();
|
||||||
|
|
||||||
|
if let Some(y) = highlight_y {
|
||||||
|
let hr = HighlightedRange {
|
||||||
|
start_y: y, //Need to change this
|
||||||
|
line_height: layout.line_height.0,
|
||||||
|
lines: highlight_lines,
|
||||||
|
color: layout.selection_color,
|
||||||
|
//Copied from editor. TODO: move to theme or something
|
||||||
|
corner_radius: 0.15 * layout.line_height.0,
|
||||||
|
};
|
||||||
|
hr.paint(bounds, cx.scene);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.paint_layer(clip_bounds, |cx| {
|
||||||
|
for layout_line in &layout.layout_lines {
|
||||||
|
for layout_cell in &layout_line.cells {
|
||||||
|
let point = layout_cell.point;
|
||||||
|
|
||||||
|
//Don't actually know the start_x for a line, until here:
|
||||||
|
let cell_origin = vec2f(
|
||||||
|
(origin.x() + point.column as f32 * layout.em_width.0).floor(),
|
||||||
|
origin.y() + point.line as f32 * layout.line_height.0,
|
||||||
|
);
|
||||||
|
|
||||||
|
layout_cell.text.paint(
|
||||||
|
cell_origin,
|
||||||
|
visible_bounds,
|
||||||
|
layout.line_height.0,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//Draw cursor
|
||||||
|
if let Some(cursor) = &layout.cursor {
|
||||||
|
cx.paint_layer(clip_bounds, |cx| {
|
||||||
|
cursor.paint(origin, cx);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
if DEBUG_GRID {
|
||||||
|
cx.paint_layer(clip_bounds, |cx| {
|
||||||
|
draw_debug_grid(bounds, layout, cx);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dispatch_event(
|
||||||
|
&mut self,
|
||||||
|
event: &gpui::Event,
|
||||||
|
_bounds: gpui::geometry::rect::RectF,
|
||||||
|
visible_bounds: gpui::geometry::rect::RectF,
|
||||||
|
layout: &mut Self::LayoutState,
|
||||||
|
_paint: &mut Self::PaintState,
|
||||||
|
cx: &mut gpui::EventContext,
|
||||||
|
) -> bool {
|
||||||
|
match event {
|
||||||
|
Event::ScrollWheel(ScrollWheelEvent {
|
||||||
|
delta, position, ..
|
||||||
|
}) => visible_bounds
|
||||||
|
.contains_point(*position)
|
||||||
|
.then(|| {
|
||||||
|
let vertical_scroll =
|
||||||
|
(delta.y() / layout.line_height.0) * ALACRITTY_SCROLL_MULTIPLIER;
|
||||||
|
|
||||||
|
if let Some(connection) = self.connection.upgrade(cx.app) {
|
||||||
|
connection.update(cx.app, |connection, _| {
|
||||||
|
connection
|
||||||
|
.term
|
||||||
|
.lock()
|
||||||
|
.scroll_display(Scroll::Delta(vertical_scroll.round() as i32));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.is_some(),
|
||||||
|
Event::KeyDown(KeyDownEvent { keystroke, .. }) => {
|
||||||
|
if !cx.is_parent_view_focused() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.connection
|
||||||
|
.upgrade(cx.app)
|
||||||
|
.map(|connection| {
|
||||||
|
connection
|
||||||
|
.update(cx.app, |connection, _| connection.try_keystroke(keystroke))
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug(
|
||||||
|
&self,
|
||||||
|
_bounds: gpui::geometry::rect::RectF,
|
||||||
|
_layout: &Self::LayoutState,
|
||||||
|
_paint: &Self::PaintState,
|
||||||
|
_cx: &gpui::DebugContext,
|
||||||
|
) -> gpui::serde_json::Value {
|
||||||
|
json!({
|
||||||
|
"type": "TerminalElement",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mouse_to_cell_data(
|
||||||
|
pos: Vector2F,
|
||||||
|
origin: Vector2F,
|
||||||
|
cur_size: SizeInfo,
|
||||||
|
display_offset: usize,
|
||||||
|
) -> (Point, alacritty_terminal::index::Direction) {
|
||||||
|
let relative_pos = relative_pos(pos, origin);
|
||||||
|
let point = grid_cell(&relative_pos, cur_size, display_offset);
|
||||||
|
let side = cell_side(&relative_pos, cur_size);
|
||||||
|
(point, side)
|
||||||
|
}
|
||||||
|
|
||||||
|
///Configures a text style from the current settings.
|
||||||
|
fn make_text_style(font_cache: &FontCache, settings: &Settings) -> TextStyle {
|
||||||
|
// Pull the font family from settings properly overriding
|
||||||
|
let family_id = settings
|
||||||
|
.terminal_overrides
|
||||||
|
.font_family
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|family_name| font_cache.load_family(&[family_name]).log_err())
|
||||||
|
.or_else(|| {
|
||||||
|
settings
|
||||||
|
.terminal_defaults
|
||||||
|
.font_family
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|family_name| font_cache.load_family(&[family_name]).log_err())
|
||||||
|
})
|
||||||
|
.unwrap_or(settings.buffer_font_family);
|
||||||
|
|
||||||
|
TextStyle {
|
||||||
|
color: settings.theme.editor.text_color,
|
||||||
|
font_family_id: family_id,
|
||||||
|
font_family_name: font_cache.family_name(family_id).unwrap(),
|
||||||
|
font_id: font_cache
|
||||||
|
.select_font(family_id, &Default::default())
|
||||||
|
.unwrap(),
|
||||||
|
font_size: settings
|
||||||
|
.terminal_overrides
|
||||||
|
.font_size
|
||||||
|
.or(settings.terminal_defaults.font_size)
|
||||||
|
.unwrap_or(settings.buffer_font_size),
|
||||||
|
font_properties: Default::default(),
|
||||||
|
underline: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///Configures a size info object from the given information.
|
||||||
|
fn make_new_size(
|
||||||
|
constraint: SizeConstraint,
|
||||||
|
cell_width: &CellWidth,
|
||||||
|
line_height: &LineHeight,
|
||||||
|
) -> SizeInfo {
|
||||||
|
SizeInfo::new(
|
||||||
|
constraint.max.x() - cell_width.0,
|
||||||
|
constraint.max.y(),
|
||||||
|
cell_width.0,
|
||||||
|
line_height.0,
|
||||||
|
0.,
|
||||||
|
0.,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn layout_lines(
|
||||||
|
grid: GridIterator<Cell>,
|
||||||
|
text_style: &TextStyle,
|
||||||
|
terminal_theme: &TerminalStyle,
|
||||||
|
text_layout_cache: &TextLayoutCache,
|
||||||
|
modal: bool,
|
||||||
|
selection_range: Option<SelectionRange>,
|
||||||
|
) -> Vec<LayoutLine> {
|
||||||
|
let lines = grid.group_by(|i| i.point.line);
|
||||||
|
lines
|
||||||
|
.into_iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(line_index, (_, line))| {
|
||||||
|
let mut highlighted_range = None;
|
||||||
|
let cells = line
|
||||||
|
.enumerate()
|
||||||
|
.map(|(x_index, indexed_cell)| {
|
||||||
|
if selection_range
|
||||||
|
.map(|range| range.contains(indexed_cell.point))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
let mut range = highlighted_range.take().unwrap_or(x_index..x_index);
|
||||||
|
range.end = range.end.max(x_index);
|
||||||
|
highlighted_range = Some(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cell_text = &indexed_cell.c.to_string();
|
||||||
|
|
||||||
|
let cell_style = cell_style(&indexed_cell, terminal_theme, text_style, modal);
|
||||||
|
|
||||||
|
//This is where we might be able to get better performance
|
||||||
|
let layout_cell = text_layout_cache.layout_str(
|
||||||
|
cell_text,
|
||||||
|
text_style.font_size,
|
||||||
|
&[(cell_text.len(), cell_style)],
|
||||||
|
);
|
||||||
|
|
||||||
|
LayoutCell::new(
|
||||||
|
Point::new(line_index as i32, indexed_cell.point.column.0 as i32),
|
||||||
|
layout_cell,
|
||||||
|
convert_color(&indexed_cell.bg, &terminal_theme.colors, modal),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<Vec<LayoutCell>>();
|
||||||
|
|
||||||
|
LayoutLine {
|
||||||
|
cells,
|
||||||
|
highlighted_range,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<LayoutLine>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the cursor position and expected block width, may return a zero width if x_for_index returns
|
||||||
|
// the same position for sequential indexes. Use em_width instead
|
||||||
|
//TODO: This function is messy, too many arguments and too many ifs. Simplify.
|
||||||
|
fn get_cursor_shape(
|
||||||
|
line: usize,
|
||||||
|
line_index: usize,
|
||||||
|
display_offset: usize,
|
||||||
|
line_height: &LineHeight,
|
||||||
|
cell_width: &CellWidth,
|
||||||
|
total_lines: usize,
|
||||||
|
text_fragment: &Line,
|
||||||
|
) -> Option<(Vector2F, f32)> {
|
||||||
|
let cursor_line = line + display_offset;
|
||||||
|
if cursor_line <= total_lines {
|
||||||
|
let cursor_width = if text_fragment.width() == 0. {
|
||||||
|
cell_width.0
|
||||||
|
} else {
|
||||||
|
text_fragment.width()
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((
|
||||||
|
vec2f(
|
||||||
|
line_index as f32 * cell_width.0,
|
||||||
|
cursor_line as f32 * line_height.0,
|
||||||
|
),
|
||||||
|
cursor_width,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///Convert the Alacritty cell styles to GPUI text styles and background color
|
||||||
|
fn cell_style(
|
||||||
|
indexed: &Indexed<&Cell>,
|
||||||
|
style: &TerminalStyle,
|
||||||
|
text_style: &TextStyle,
|
||||||
|
modal: bool,
|
||||||
|
) -> RunStyle {
|
||||||
|
let flags = indexed.cell.flags;
|
||||||
|
let fg = convert_color(&indexed.cell.fg, &style.colors, modal);
|
||||||
|
|
||||||
|
let underline = flags
|
||||||
|
.contains(Flags::UNDERLINE)
|
||||||
|
.then(|| Underline {
|
||||||
|
color: Some(fg),
|
||||||
|
squiggly: false,
|
||||||
|
thickness: OrderedFloat(1.),
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
RunStyle {
|
||||||
|
color: fg,
|
||||||
|
font_id: text_style.font_id,
|
||||||
|
underline,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attach_mouse_handlers(
|
||||||
|
origin: Vector2F,
|
||||||
|
cur_size: SizeInfo,
|
||||||
|
view_id: usize,
|
||||||
|
terminal_mutex: &Arc<FairMutex<Term<ZedListener>>>,
|
||||||
|
visible_bounds: RectF,
|
||||||
|
cx: &mut PaintContext,
|
||||||
|
) {
|
||||||
|
let click_mutex = terminal_mutex.clone();
|
||||||
|
let drag_mutex = terminal_mutex.clone();
|
||||||
|
let mouse_down_mutex = terminal_mutex.clone();
|
||||||
|
|
||||||
|
cx.scene.push_mouse_region(
|
||||||
|
MouseRegion::new(view_id, None, visible_bounds)
|
||||||
|
.on_down(MouseButton::Left, move |e, _| {
|
||||||
|
let mut term = mouse_down_mutex.lock();
|
||||||
|
|
||||||
|
let (point, side) = mouse_to_cell_data(
|
||||||
|
e.position,
|
||||||
|
origin,
|
||||||
|
cur_size,
|
||||||
|
term.renderable_content().display_offset,
|
||||||
|
);
|
||||||
|
term.selection = Some(Selection::new(SelectionType::Simple, point, side))
|
||||||
|
})
|
||||||
|
.on_click(MouseButton::Left, move |e, cx| {
|
||||||
|
let mut term = click_mutex.lock();
|
||||||
|
|
||||||
|
let (point, side) = mouse_to_cell_data(
|
||||||
|
e.position,
|
||||||
|
origin,
|
||||||
|
cur_size,
|
||||||
|
term.renderable_content().display_offset,
|
||||||
|
);
|
||||||
|
|
||||||
|
let selection_type = match e.click_count {
|
||||||
|
0 => return, //This is a release
|
||||||
|
1 => Some(SelectionType::Simple),
|
||||||
|
2 => Some(SelectionType::Semantic),
|
||||||
|
3 => Some(SelectionType::Lines),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let selection = selection_type
|
||||||
|
.map(|selection_type| Selection::new(selection_type, point, side));
|
||||||
|
|
||||||
|
term.selection = selection;
|
||||||
|
cx.focus_parent_view();
|
||||||
|
cx.notify();
|
||||||
|
})
|
||||||
|
.on_drag(MouseButton::Left, move |e, cx| {
|
||||||
|
let mut term = drag_mutex.lock();
|
||||||
|
|
||||||
|
let (point, side) = mouse_to_cell_data(
|
||||||
|
e.position,
|
||||||
|
origin,
|
||||||
|
cur_size,
|
||||||
|
term.renderable_content().display_offset,
|
||||||
|
);
|
||||||
|
|
||||||
|
if let Some(mut selection) = term.selection.take() {
|
||||||
|
selection.update(point, side);
|
||||||
|
term.selection = Some(selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
cx.notify();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
///Copied (with modifications) from alacritty/src/input.rs > Processor::cell_side()
|
||||||
|
fn cell_side(pos: &PaneRelativePos, cur_size: SizeInfo) -> Side {
|
||||||
|
let x = pos.0.x() as usize;
|
||||||
|
let cell_x = x.saturating_sub(cur_size.cell_width() as usize) % cur_size.cell_width() as usize;
|
||||||
|
let half_cell_width = (cur_size.cell_width() / 2.0) as usize;
|
||||||
|
|
||||||
|
let additional_padding =
|
||||||
|
(cur_size.width() - cur_size.cell_width() * 2.) % cur_size.cell_width();
|
||||||
|
let end_of_grid = cur_size.width() - cur_size.cell_width() - additional_padding;
|
||||||
|
|
||||||
|
if cell_x > half_cell_width
|
||||||
|
// Edge case when mouse leaves the window.
|
||||||
|
|| x as f32 >= end_of_grid
|
||||||
|
{
|
||||||
|
Side::Right
|
||||||
|
} else {
|
||||||
|
Side::Left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///Copied (with modifications) from alacritty/src/event.rs > Mouse::point()
|
||||||
|
///Position is a pane-relative position. That means the top left corner of the mouse
|
||||||
|
///Region should be (0,0)
|
||||||
|
fn grid_cell(pos: &PaneRelativePos, cur_size: SizeInfo, display_offset: usize) -> Point {
|
||||||
|
let pos = pos.0;
|
||||||
|
let col = pos.x() / cur_size.cell_width(); //TODO: underflow...
|
||||||
|
let col = min(GridCol(col as usize), cur_size.last_column());
|
||||||
|
|
||||||
|
let line = pos.y() / cur_size.cell_height();
|
||||||
|
let line = min(line as i32, cur_size.bottommost_line().0);
|
||||||
|
|
||||||
|
//when clicking, need to ADD to get to the top left cell
|
||||||
|
//e.g. total_lines - viewport_height, THEN subtract display offset
|
||||||
|
//0 -> total_lines - viewport_height - display_offset + mouse_line
|
||||||
|
|
||||||
|
Point::new(GridLine(line - display_offset as i32), col)
|
||||||
|
}
|
||||||
|
|
||||||
|
///Draws the grid as Alacritty sees it. Useful for checking if there is an inconsistency between
|
||||||
|
///Display and conceptual grid.
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
fn draw_debug_grid(bounds: RectF, layout: &mut LayoutState, cx: &mut PaintContext) {
|
||||||
|
let width = layout.cur_size.width();
|
||||||
|
let height = layout.cur_size.height();
|
||||||
|
//Alacritty uses 'as usize', so shall we.
|
||||||
|
for col in 0..(width / layout.em_width.0).round() as usize {
|
||||||
|
cx.scene.push_quad(Quad {
|
||||||
|
bounds: RectF::new(
|
||||||
|
bounds.origin() + vec2f((col + 1) as f32 * layout.em_width.0, 0.),
|
||||||
|
vec2f(1., height),
|
||||||
|
),
|
||||||
|
background: Some(Color::green()),
|
||||||
|
border: Default::default(),
|
||||||
|
corner_radius: 0.,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
for row in 0..((height / layout.line_height.0) + 1.0).round() as usize {
|
||||||
|
cx.scene.push_quad(Quad {
|
||||||
|
bounds: RectF::new(
|
||||||
|
bounds.origin() + vec2f(layout.em_width.0, row as f32 * layout.line_height.0),
|
||||||
|
vec2f(width, 1.),
|
||||||
|
),
|
||||||
|
background: Some(Color::green()),
|
||||||
|
border: Default::default(),
|
||||||
|
corner_radius: 0.,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mouse_to_selection() {
|
||||||
|
let term_width = 100.;
|
||||||
|
let term_height = 200.;
|
||||||
|
let cell_width = 10.;
|
||||||
|
let line_height = 20.;
|
||||||
|
let mouse_pos_x = 100.; //Window relative
|
||||||
|
let mouse_pos_y = 100.; //Window relative
|
||||||
|
let origin_x = 10.;
|
||||||
|
let origin_y = 20.;
|
||||||
|
|
||||||
|
let cur_size = alacritty_terminal::term::SizeInfo::new(
|
||||||
|
term_width,
|
||||||
|
term_height,
|
||||||
|
cell_width,
|
||||||
|
line_height,
|
||||||
|
0.,
|
||||||
|
0.,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
|
||||||
|
let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
|
||||||
|
let (point, _) =
|
||||||
|
crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
|
||||||
|
assert_eq!(
|
||||||
|
point,
|
||||||
|
alacritty_terminal::index::Point::new(
|
||||||
|
alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
|
||||||
|
alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mouse_to_selection_off_edge() {
|
||||||
|
let term_width = 100.;
|
||||||
|
let term_height = 200.;
|
||||||
|
let cell_width = 10.;
|
||||||
|
let line_height = 20.;
|
||||||
|
let mouse_pos_x = 100.; //Window relative
|
||||||
|
let mouse_pos_y = 100.; //Window relative
|
||||||
|
let origin_x = 10.;
|
||||||
|
let origin_y = 20.;
|
||||||
|
|
||||||
|
let cur_size = alacritty_terminal::term::SizeInfo::new(
|
||||||
|
term_width,
|
||||||
|
term_height,
|
||||||
|
cell_width,
|
||||||
|
line_height,
|
||||||
|
0.,
|
||||||
|
0.,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
let mouse_pos = gpui::geometry::vector::vec2f(mouse_pos_x, mouse_pos_y);
|
||||||
|
let origin = gpui::geometry::vector::vec2f(origin_x, origin_y); //Position of terminal window, 1 'cell' in
|
||||||
|
let (point, _) =
|
||||||
|
crate::terminal_element::mouse_to_cell_data(mouse_pos, origin, cur_size, 0);
|
||||||
|
assert_eq!(
|
||||||
|
point,
|
||||||
|
alacritty_terminal::index::Point::new(
|
||||||
|
alacritty_terminal::index::Line(((mouse_pos_y - origin_y) / line_height) as i32),
|
||||||
|
alacritty_terminal::index::Column(((mouse_pos_x - origin_x) / cell_width) as usize),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -78,6 +78,22 @@ pub struct TabBar {
|
||||||
pub height: f32,
|
pub height: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)]
|
#[derive(Clone, Deserialize, Default)]
|
||||||
pub struct TabStyles {
|
pub struct TabStyles {
|
||||||
pub active_tab: Tab,
|
pub active_tab: Tab,
|
||||||
|
|
|
@ -15,6 +15,7 @@ client = { path = "../client" }
|
||||||
clock = { path = "../clock" }
|
clock = { path = "../clock" }
|
||||||
collections = { path = "../collections" }
|
collections = { path = "../collections" }
|
||||||
context_menu = { path = "../context_menu" }
|
context_menu = { path = "../context_menu" }
|
||||||
|
drag_and_drop = { path = "../drag_and_drop" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
language = { path = "../language" }
|
language = { path = "../language" }
|
||||||
menu = { path = "../menu" }
|
menu = { path = "../menu" }
|
||||||
|
|
|
@ -876,7 +876,7 @@ impl Pane {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_tabs(&mut self, cx: &mut RenderContext<Self>) -> impl Element {
|
fn render_tab_bar(&mut self, cx: &mut RenderContext<Self>) -> impl Element {
|
||||||
let theme = cx.global::<Settings>().theme.clone();
|
let theme = cx.global::<Settings>().theme.clone();
|
||||||
|
|
||||||
enum Tabs {}
|
enum Tabs {}
|
||||||
|
@ -889,131 +889,38 @@ impl Pane {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let is_pane_active = self.is_active;
|
let pane_active = self.is_active;
|
||||||
|
|
||||||
let tab_styles = match is_pane_active {
|
|
||||||
true => theme.workspace.tab_bar.active_pane.clone(),
|
|
||||||
false => theme.workspace.tab_bar.inactive_pane.clone(),
|
|
||||||
};
|
|
||||||
let filler_style = tab_styles.inactive_tab.clone();
|
|
||||||
|
|
||||||
let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
|
let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
|
||||||
for (ix, (item, detail)) in self.items.iter().zip(self.tab_details(cx)).enumerate() {
|
for (ix, (item, detail)) in self
|
||||||
let item_id = item.id();
|
.items
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.zip(self.tab_details(cx))
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
let detail = if detail == 0 { None } else { Some(detail) };
|
let detail = if detail == 0 { None } else { Some(detail) };
|
||||||
let is_tab_active = ix == self.active_item_index;
|
let tab_active = ix == self.active_item_index;
|
||||||
|
|
||||||
let close_tab_callback = {
|
|
||||||
let pane = pane.clone();
|
|
||||||
move |_, cx: &mut EventContext| {
|
|
||||||
cx.dispatch_action(CloseItem {
|
|
||||||
item_id,
|
|
||||||
pane: pane.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
row.add_child({
|
row.add_child({
|
||||||
let mut tab_style = match is_tab_active {
|
MouseEventHandler::new::<Tab, _, _>(ix, cx, {
|
||||||
true => tab_styles.active_tab.clone(),
|
let item = item.clone();
|
||||||
false => tab_styles.inactive_tab.clone(),
|
let pane = pane.clone();
|
||||||
};
|
let hovered = mouse_state.hovered;
|
||||||
|
|
||||||
let title = item.tab_content(detail, &tab_style, cx);
|
move |_, cx| {
|
||||||
|
Self::render_tab(
|
||||||
if ix == 0 {
|
&item,
|
||||||
tab_style.container.border.left = false;
|
pane,
|
||||||
}
|
detail,
|
||||||
|
hovered,
|
||||||
MouseEventHandler::new::<Tab, _, _>(ix, cx, |_, cx| {
|
pane_active,
|
||||||
Container::new(
|
tab_active,
|
||||||
Flex::row()
|
cx,
|
||||||
.with_child(
|
)
|
||||||
Align::new({
|
}
|
||||||
let diameter = 7.0;
|
|
||||||
let icon_color = if item.has_conflict(cx) {
|
|
||||||
Some(tab_style.icon_conflict)
|
|
||||||
} else if item.is_dirty(cx) {
|
|
||||||
Some(tab_style.icon_dirty)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
ConstrainedBox::new(
|
|
||||||
Canvas::new(move |bounds, _, cx| {
|
|
||||||
if let Some(color) = icon_color {
|
|
||||||
let square = RectF::new(
|
|
||||||
bounds.origin(),
|
|
||||||
vec2f(diameter, diameter),
|
|
||||||
);
|
|
||||||
cx.scene.push_quad(Quad {
|
|
||||||
bounds: square,
|
|
||||||
background: Some(color),
|
|
||||||
border: Default::default(),
|
|
||||||
corner_radius: diameter / 2.,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_width(diameter)
|
|
||||||
.with_height(diameter)
|
|
||||||
.boxed()
|
|
||||||
})
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_child(
|
|
||||||
Container::new(Align::new(title).boxed())
|
|
||||||
.with_style(ContainerStyle {
|
|
||||||
margin: Margin {
|
|
||||||
left: tab_style.spacing,
|
|
||||||
right: tab_style.spacing,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_child(
|
|
||||||
Align::new(
|
|
||||||
ConstrainedBox::new(if mouse_state.hovered {
|
|
||||||
enum TabCloseButton {}
|
|
||||||
let icon = Svg::new("icons/x_mark_thin_8.svg");
|
|
||||||
MouseEventHandler::new::<TabCloseButton, _, _>(
|
|
||||||
item_id,
|
|
||||||
cx,
|
|
||||||
|mouse_state, _| {
|
|
||||||
if mouse_state.hovered {
|
|
||||||
icon.with_color(tab_style.icon_close_active)
|
|
||||||
.boxed()
|
|
||||||
} else {
|
|
||||||
icon.with_color(tab_style.icon_close)
|
|
||||||
.boxed()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.with_padding(Padding::uniform(4.))
|
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
|
||||||
.on_click(MouseButton::Left, close_tab_callback.clone())
|
|
||||||
.on_click(
|
|
||||||
MouseButton::Middle,
|
|
||||||
close_tab_callback.clone(),
|
|
||||||
)
|
|
||||||
.named("close-tab-icon")
|
|
||||||
} else {
|
|
||||||
Empty::new().boxed()
|
|
||||||
})
|
|
||||||
.with_width(tab_style.icon_width)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.boxed(),
|
|
||||||
)
|
|
||||||
.with_style(tab_style.container)
|
|
||||||
.boxed()
|
|
||||||
})
|
})
|
||||||
.with_cursor_style(if is_tab_active && is_pane_active {
|
.with_cursor_style(if pane_active && tab_active {
|
||||||
CursorStyle::Arrow
|
CursorStyle::Arrow
|
||||||
} else {
|
} else {
|
||||||
CursorStyle::PointingHand
|
CursorStyle::PointingHand
|
||||||
|
@ -1021,24 +928,20 @@ impl Pane {
|
||||||
.on_down(MouseButton::Left, move |_, cx| {
|
.on_down(MouseButton::Left, move |_, cx| {
|
||||||
cx.dispatch_action(ActivateItem(ix));
|
cx.dispatch_action(ActivateItem(ix));
|
||||||
})
|
})
|
||||||
.on_click(MouseButton::Middle, close_tab_callback)
|
.on_click(MouseButton::Middle, {
|
||||||
.on_drag(MouseButton::Left, |_, cx| {
|
let pane = pane.clone();
|
||||||
cx.global::<DragAndDrop>().dragging(some view handle)
|
move |_, cx: &mut EventContext| {
|
||||||
})
|
cx.dispatch_action(CloseItem {
|
||||||
.on_up_out(MouseButton::Left, |_, cx| {
|
item_id: item.id(),
|
||||||
cx.global::<DragAndDrop>().stopped_dragging(some view handle)
|
pane: pane.clone(),
|
||||||
})
|
})
|
||||||
.on_drag_over(MouseButton::Left, |started, _, _, cx| {
|
|
||||||
if started {
|
|
||||||
if let Some(tab) = cx.global::<DragAndDrop>().current_dragged::<Tab>() {
|
|
||||||
cx.dispatch_action(ReceivingTab);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.boxed()
|
.boxed()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
|
||||||
row.add_child(
|
row.add_child(
|
||||||
Empty::new()
|
Empty::new()
|
||||||
.contained()
|
.contained()
|
||||||
|
@ -1088,6 +991,109 @@ impl Pane {
|
||||||
|
|
||||||
tab_details
|
tab_details
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_tab<V: View>(
|
||||||
|
item: &Box<dyn ItemHandle>,
|
||||||
|
pane: WeakViewHandle<Pane>,
|
||||||
|
detail: Option<usize>,
|
||||||
|
hovered: bool,
|
||||||
|
pane_active: bool,
|
||||||
|
tab_active: bool,
|
||||||
|
cx: &mut RenderContext<V>,
|
||||||
|
) -> ElementBox {
|
||||||
|
let theme = cx.global::<Settings>().theme.clone();
|
||||||
|
let tab_style = theme.workspace.tab_bar.tab_style(pane_active, tab_active);
|
||||||
|
let title = item.tab_content(detail, tab_style, cx);
|
||||||
|
|
||||||
|
Container::new(
|
||||||
|
Flex::row()
|
||||||
|
.with_child(
|
||||||
|
Align::new({
|
||||||
|
let diameter = 7.0;
|
||||||
|
let icon_color = if item.has_conflict(cx) {
|
||||||
|
Some(tab_style.icon_conflict)
|
||||||
|
} else if item.is_dirty(cx) {
|
||||||
|
Some(tab_style.icon_dirty)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
ConstrainedBox::new(
|
||||||
|
Canvas::new(move |bounds, _, cx| {
|
||||||
|
if let Some(color) = icon_color {
|
||||||
|
let square =
|
||||||
|
RectF::new(bounds.origin(), vec2f(diameter, diameter));
|
||||||
|
cx.scene.push_quad(Quad {
|
||||||
|
bounds: square,
|
||||||
|
background: Some(color),
|
||||||
|
border: Default::default(),
|
||||||
|
corner_radius: diameter / 2.,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_width(diameter)
|
||||||
|
.with_height(diameter)
|
||||||
|
.boxed()
|
||||||
|
})
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Container::new(Align::new(title).boxed())
|
||||||
|
.with_style(ContainerStyle {
|
||||||
|
margin: Margin {
|
||||||
|
left: tab_style.spacing,
|
||||||
|
right: tab_style.spacing,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_child(
|
||||||
|
Align::new(
|
||||||
|
ConstrainedBox::new(if hovered {
|
||||||
|
let item_id = item.id();
|
||||||
|
enum TabCloseButton {}
|
||||||
|
let icon = Svg::new("icons/x_mark_thin_8.svg");
|
||||||
|
MouseEventHandler::new::<TabCloseButton, _, _>(
|
||||||
|
item_id,
|
||||||
|
cx,
|
||||||
|
|mouse_state, _| {
|
||||||
|
if mouse_state.hovered {
|
||||||
|
icon.with_color(tab_style.icon_close_active).boxed()
|
||||||
|
} else {
|
||||||
|
icon.with_color(tab_style.icon_close).boxed()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with_padding(Padding::uniform(4.))
|
||||||
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
|
.on_click(MouseButton::Left, {
|
||||||
|
let pane = pane.clone();
|
||||||
|
move |_, cx| {
|
||||||
|
cx.dispatch_action(CloseItem {
|
||||||
|
item_id,
|
||||||
|
pane: pane.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_click(MouseButton::Middle, |_, cx| cx.propogate_event())
|
||||||
|
.named("close-tab-icon")
|
||||||
|
} else {
|
||||||
|
Empty::new().boxed()
|
||||||
|
})
|
||||||
|
.with_width(tab_style.icon_width)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.boxed(),
|
||||||
|
)
|
||||||
|
.with_style(tab_style.container)
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Entity for Pane {
|
impl Entity for Pane {
|
||||||
|
@ -1110,7 +1116,7 @@ impl View for Pane {
|
||||||
Flex::column()
|
Flex::column()
|
||||||
.with_child({
|
.with_child({
|
||||||
let mut tab_row = Flex::row()
|
let mut tab_row = Flex::row()
|
||||||
.with_child(self.render_tabs(cx).flex(1., true).named("tabs"));
|
.with_child(self.render_tab_bar(cx).flex(1., true).named("tabs"));
|
||||||
|
|
||||||
if self.is_active {
|
if self.is_active {
|
||||||
tab_row.add_children([
|
tab_row.add_children([
|
||||||
|
@ -1167,12 +1173,11 @@ impl View for Pane {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.with_cursor_style(CursorStyle::PointingHand)
|
.with_cursor_style(CursorStyle::PointingHand)
|
||||||
.on_down(
|
.on_down(MouseButton::Left, |e, cx| {
|
||||||
MouseButton::Left,
|
cx.dispatch_action(DeploySplitMenu {
|
||||||
|MouseButtonEvent { position, .. }, cx| {
|
position: e.position,
|
||||||
cx.dispatch_action(DeploySplitMenu { position });
|
});
|
||||||
},
|
})
|
||||||
)
|
|
||||||
.boxed(),
|
.boxed(),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::StatusItemView;
|
use crate::StatusItemView;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
elements::*, impl_actions, platform::CursorStyle, AnyViewHandle, AppContext, Entity,
|
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 serde::Deserialize;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
|
@ -189,26 +189,18 @@ impl Sidebar {
|
||||||
})
|
})
|
||||||
.with_cursor_style(CursorStyle::ResizeLeftRight)
|
.with_cursor_style(CursorStyle::ResizeLeftRight)
|
||||||
.on_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere
|
.on_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere
|
||||||
.on_drag(
|
.on_drag(MouseButton::Left, move |e, cx| {
|
||||||
MouseButton::Left,
|
let delta = e.prev_drag_position.x() - e.position.x();
|
||||||
move |old_position,
|
let prev_width = *actual_width.borrow();
|
||||||
MouseMovedEvent {
|
*custom_width.borrow_mut() = 0f32
|
||||||
position: new_position,
|
.max(match side {
|
||||||
..
|
Side::Left => prev_width + delta,
|
||||||
},
|
Side::Right => prev_width - delta,
|
||||||
cx| {
|
})
|
||||||
let delta = new_position.x() - old_position.x();
|
.round();
|
||||||
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()
|
.boxed()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ use client::{
|
||||||
};
|
};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map, HashMap, HashSet};
|
||||||
|
use drag_and_drop::DragAndDrop;
|
||||||
use futures::{channel::oneshot, FutureExt};
|
use futures::{channel::oneshot, FutureExt};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions,
|
actions,
|
||||||
|
@ -895,6 +896,9 @@ impl Workspace {
|
||||||
status_bar
|
status_bar
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let drag_and_drop = DragAndDrop::new(cx.weak_handle(), cx);
|
||||||
|
cx.set_global(drag_and_drop);
|
||||||
|
|
||||||
let mut this = Workspace {
|
let mut this = Workspace {
|
||||||
modal: None,
|
modal: None,
|
||||||
weak_self,
|
weak_self,
|
||||||
|
@ -2471,6 +2475,7 @@ impl View for Workspace {
|
||||||
.with_background_color(theme.workspace.background)
|
.with_background_color(theme.workspace.background)
|
||||||
.boxed(),
|
.boxed(),
|
||||||
)
|
)
|
||||||
|
.with_children(DragAndDrop::render(cx))
|
||||||
.with_children(self.render_disconnected_overlay(cx))
|
.with_children(self.render_disconnected_overlay(cx))
|
||||||
.named("workspace")
|
.named("workspace")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue