mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-18 18:08:07 +03:00
wip tab drag and drop
This commit is contained in:
parent
86fdd55fd4
commit
133c194f4a
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"
|
||||
checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
|
||||
|
||||
[[package]]
|
||||
name = "drag_and_drop"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"collections",
|
||||
"gpui",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dwrote"
|
||||
version = "0.11.0"
|
||||
@ -6941,6 +6949,7 @@ dependencies = [
|
||||
"clock",
|
||||
"collections",
|
||||
"context_menu",
|
||||
"drag_and_drop",
|
||||
"futures",
|
||||
"gpui",
|
||||
"language",
|
||||
|
@ -566,7 +566,7 @@ impl ContactsPanel {
|
||||
button
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_click(MouseButton::Left, move |_, cx| {
|
||||
let project = project_handle.upgrade(cx.deref_mut());
|
||||
let project = project_handle.upgrade(cx.app);
|
||||
cx.dispatch_action(ToggleProjectOnline { project })
|
||||
})
|
||||
.with_tooltip::<ToggleOnline, _>(
|
||||
|
15
crates/drag_and_drop/Cargo.toml
Normal file
15
crates/drag_and_drop/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "drag_and_drop"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
path = "src/drag_and_drop.rs"
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
collections = { path = "../collections" }
|
||||
gpui = { path = "../gpui" }
|
||||
|
||||
[dev-dependencies]
|
||||
gpui = { path = "../gpui", features = ["test-support"] }
|
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
|
||||
}
|
||||
|
||||
pub fn with_padding_top(mut self, padding: f32) -> Self {
|
||||
self.style.padding.top = padding;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_padding_bottom(mut self, padding: f32) -> Self {
|
||||
self.style.padding.bottom = padding;
|
||||
self
|
||||
|
@ -5,10 +5,13 @@ use crate::{
|
||||
vector::{vec2f, Vector2F},
|
||||
},
|
||||
platform::CursorStyle,
|
||||
scene::{CursorRegion, HandlerSet},
|
||||
scene::{
|
||||
ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragOverRegionEvent,
|
||||
DragRegionEvent, HandlerSet, HoverRegionEvent, MoveRegionEvent, UpOutRegionEvent,
|
||||
UpRegionEvent,
|
||||
},
|
||||
DebugContext, Element, ElementBox, Event, EventContext, LayoutContext, MeasurementContext,
|
||||
MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseState, PaintContext,
|
||||
RenderContext, SizeConstraint, View,
|
||||
MouseButton, MouseRegion, MouseState, PaintContext, RenderContext, SizeConstraint, View,
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::{any::TypeId, ops::Range};
|
||||
@ -42,10 +45,18 @@ impl MouseEventHandler {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_move(
|
||||
mut self,
|
||||
handler: impl Fn(MoveRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_move(handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_down(button, handler);
|
||||
self
|
||||
@ -54,7 +65,7 @@ impl MouseEventHandler {
|
||||
pub fn on_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_up(button, handler);
|
||||
self
|
||||
@ -63,7 +74,7 @@ impl MouseEventHandler {
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_click(button, handler);
|
||||
self
|
||||
@ -72,7 +83,7 @@ impl MouseEventHandler {
|
||||
pub fn on_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_down_out(button, handler);
|
||||
self
|
||||
@ -81,16 +92,16 @@ impl MouseEventHandler {
|
||||
pub fn on_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_up(button, handler);
|
||||
self.handlers = self.handlers.on_up_out(button, handler);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_drag(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_drag(button, handler);
|
||||
self
|
||||
@ -99,7 +110,7 @@ impl MouseEventHandler {
|
||||
pub fn on_drag_over(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DragOverRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_drag_over(button, handler);
|
||||
self
|
||||
@ -107,7 +118,7 @@ impl MouseEventHandler {
|
||||
|
||||
pub fn on_hover(
|
||||
mut self,
|
||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_hover(handler);
|
||||
self
|
||||
|
@ -7,8 +7,8 @@ use crate::{
|
||||
geometry::{rect::RectF, vector::Vector2F},
|
||||
json::json,
|
||||
presenter::MeasurementContext,
|
||||
Action, Axis, ElementStateHandle, LayoutContext, MouseMovedEvent, PaintContext, RenderContext,
|
||||
SizeConstraint, Task, View,
|
||||
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint,
|
||||
Task, View,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use std::{
|
||||
@ -93,10 +93,11 @@ impl Tooltip {
|
||||
};
|
||||
let child =
|
||||
MouseEventHandler::new::<MouseEventHandlerState<Tag>, _, _>(id, cx, |_, _| child)
|
||||
.on_hover(move |hover, MouseMovedEvent { position, .. }, cx| {
|
||||
.on_hover(move |e, cx| {
|
||||
let position = e.position;
|
||||
let window_id = cx.window_id();
|
||||
if let Some(view_id) = cx.view_id() {
|
||||
if hover {
|
||||
if e.started {
|
||||
if !state.visible.get() {
|
||||
state.position.set(position);
|
||||
|
||||
|
@ -6,7 +6,11 @@ use crate::{
|
||||
json::{self, ToJson},
|
||||
keymap::Keystroke,
|
||||
platform::{CursorStyle, Event},
|
||||
scene::{CursorRegion, MouseRegionEvent},
|
||||
scene::{
|
||||
ClickRegionEvent, CursorRegion, DownOutRegionEvent, DownRegionEvent, DragOverRegionEvent,
|
||||
DragRegionEvent, HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent,
|
||||
UpRegionEvent,
|
||||
},
|
||||
text_layout::TextLayoutCache,
|
||||
Action, AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AssetCache, ElementBox, Entity,
|
||||
FontSystem, ModelHandle, MouseButtonEvent, MouseMovedEvent, MouseRegion, MouseRegionId,
|
||||
@ -140,8 +144,7 @@ impl Presenter {
|
||||
|
||||
if cx.window_is_active(self.window_id) {
|
||||
if let Some(event) = self.last_mouse_moved_event.clone() {
|
||||
let mut invalidated_views = Vec::new();
|
||||
self.handle_hover_events(&event, &mut invalidated_views, cx);
|
||||
let invalidated_views = self.handle_hover_events(&event, cx).invalidated_views;
|
||||
|
||||
for view_id in invalidated_views {
|
||||
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 {
|
||||
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();
|
||||
|
||||
match &event {
|
||||
Event::MouseDown(e @ MouseButtonEvent { position, .. }) => {
|
||||
let mut hit = false;
|
||||
for (region, _) in self.mouse_regions.iter().rev() {
|
||||
if region.bounds.contains_point(*position) {
|
||||
if !hit {
|
||||
hit = true;
|
||||
invalidated_views.push(region.view_id);
|
||||
events_to_send
|
||||
.push((region.clone(), MouseRegionEvent::Down(e.clone())));
|
||||
self.clicked_region = Some(region.clone());
|
||||
self.prev_drag_position = Some(*position);
|
||||
}
|
||||
events_to_send.push((
|
||||
region.clone(),
|
||||
MouseRegionEvent::Down(DownRegionEvent {
|
||||
region: region.bounds,
|
||||
platform_event: e.clone(),
|
||||
}),
|
||||
));
|
||||
} else {
|
||||
events_to_send
|
||||
.push((region.clone(), MouseRegionEvent::DownOut(e.clone())));
|
||||
events_to_send.push((
|
||||
region.clone(),
|
||||
MouseRegionEvent::DownOut(DownOutRegionEvent {
|
||||
region: region.bounds,
|
||||
platform_event: e.clone(),
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::MouseUp(e @ MouseButtonEvent { position, .. }) => {
|
||||
let mut hit = false;
|
||||
for (region, _) in self.mouse_regions.iter().rev() {
|
||||
if region.bounds.contains_point(*position) {
|
||||
if !hit {
|
||||
hit = true;
|
||||
invalidated_views.push(region.view_id);
|
||||
events_to_send
|
||||
.push((region.clone(), MouseRegionEvent::Up(e.clone())));
|
||||
}
|
||||
events_to_send.push((
|
||||
region.clone(),
|
||||
MouseRegionEvent::Up(UpRegionEvent {
|
||||
region: region.bounds,
|
||||
platform_event: e.clone(),
|
||||
}),
|
||||
));
|
||||
} else {
|
||||
events_to_send
|
||||
.push((region.clone(), MouseRegionEvent::UpOut(e.clone())));
|
||||
events_to_send.push((
|
||||
region.clone(),
|
||||
MouseRegionEvent::UpOut(UpOutRegionEvent {
|
||||
region: region.bounds,
|
||||
platform_event: e.clone(),
|
||||
}),
|
||||
));
|
||||
}
|
||||
}
|
||||
self.prev_drag_position.take();
|
||||
if let Some(region) = self.clicked_region.take() {
|
||||
invalidated_views.push(region.view_id);
|
||||
if region.bounds.contains_point(*position) {
|
||||
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((
|
||||
clicked_region.clone(),
|
||||
<<<<<<< HEAD
|
||||
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());
|
||||
@ -279,12 +313,12 @@ impl Presenter {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let (mut handled, mut event_cx) =
|
||||
self.handle_hover_events(&event, &mut invalidated_views, cx);
|
||||
let (invalidated_views, dispatch_directives, handled) = {
|
||||
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.is_local() {
|
||||
handled = true;
|
||||
if !event_cx.handled {
|
||||
event_cx.handled = event_cx.dispatch_event(root_view_id, &event);
|
||||
}
|
||||
|
||||
if let Some(callback) = region.handlers.get(&event.handler_key()) {
|
||||
@ -292,7 +326,13 @@ impl Presenter {
|
||||
callback(event, event_cx);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
event_cx.invalidated_views,
|
||||
event_cx.dispatched_actions,
|
||||
event_cx.handled,
|
||||
)
|
||||
};
|
||||
|
||||
if !handled {
|
||||
handled = event_cx.dispatch_event(root_view_id, &event);
|
||||
@ -313,9 +353,8 @@ impl Presenter {
|
||||
fn handle_hover_events<'a>(
|
||||
&'a mut self,
|
||||
event: &Event,
|
||||
invalidated_views: &mut Vec<usize>,
|
||||
cx: &'a mut MutableAppContext,
|
||||
) -> (bool, EventContext<'a>) {
|
||||
) -> EventContext<'a> {
|
||||
let mut events_to_send = Vec::new();
|
||||
|
||||
if let Event::MouseMoved(
|
||||
@ -343,46 +382,48 @@ impl Presenter {
|
||||
hover_depth = Some(*depth);
|
||||
if let Some(region_id) = region.id() {
|
||||
if !self.hovered_region_ids.contains(®ion_id) {
|
||||
invalidated_views.push(region.view_id);
|
||||
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 {
|
||||
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));
|
||||
self.hovered_region_ids.insert(region_id);
|
||||
}
|
||||
}
|
||||
} else if let Some(region_id) = region.id() {
|
||||
if self.hovered_region_ids.contains(®ion_id) {
|
||||
invalidated_views.push(region.view_id);
|
||||
let region_event = if pressed_button.is_some() {
|
||||
MouseRegionEvent::DragOver(false, e.clone())
|
||||
} else {
|
||||
MouseRegionEvent::Hover(false, e.clone())
|
||||
};
|
||||
events_to_send.push((region.clone(), region_event));
|
||||
self.hovered_region_ids.remove(®ion_id);
|
||||
}
|
||||
if self.hovered_region_ids.contains(®ion_id) {
|
||||
let region_event = if pressed_button.is_some() {
|
||||
MouseRegionEvent::DragOver(DragOverRegionEvent {
|
||||
region: region.bounds,
|
||||
started: false,
|
||||
platform_event: e.clone(),
|
||||
})
|
||||
} else {
|
||||
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 handled = false;
|
||||
|
||||
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)
|
||||
event_cx.process_region_events(events_to_send);
|
||||
event_cx
|
||||
}
|
||||
|
||||
pub fn build_event_context<'a>(
|
||||
@ -396,6 +437,9 @@ impl Presenter {
|
||||
view_stack: Default::default(),
|
||||
invalidated_views: Default::default(),
|
||||
notify_count: 0,
|
||||
clicked_region: &mut self.clicked_region,
|
||||
prev_drag_position: &mut self.prev_drag_position,
|
||||
handled: false,
|
||||
window_id: self.window_id,
|
||||
app: cx,
|
||||
}
|
||||
@ -615,6 +659,9 @@ pub struct EventContext<'a> {
|
||||
pub window_id: usize,
|
||||
pub notify_count: usize,
|
||||
view_stack: Vec<usize>,
|
||||
clicked_region: &'a mut Option<MouseRegion>,
|
||||
prev_drag_position: &'a mut Option<Vector2F>,
|
||||
handled: bool,
|
||||
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
|
||||
where
|
||||
F: FnOnce(&mut Self) -> T,
|
||||
@ -681,6 +758,10 @@ impl<'a> EventContext<'a> {
|
||||
pub fn notify_count(&self) -> usize {
|
||||
self.notify_count
|
||||
}
|
||||
|
||||
pub fn propogate_event(&mut self) {
|
||||
self.handled = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Deref for EventContext<'a> {
|
||||
|
@ -1,4 +1,5 @@
|
||||
mod mouse_region;
|
||||
mod mouse_region_event;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde_json::json;
|
||||
@ -13,6 +14,7 @@ use crate::{
|
||||
ImageData,
|
||||
};
|
||||
pub use mouse_region::*;
|
||||
pub use mouse_region_event::*;
|
||||
|
||||
pub struct Scene {
|
||||
scale_factor: f32,
|
||||
|
@ -1,13 +1,14 @@
|
||||
use std::{
|
||||
any::TypeId,
|
||||
mem::{discriminant, Discriminant},
|
||||
rc::Rc,
|
||||
};
|
||||
use std::{any::TypeId, mem::Discriminant, rc::Rc};
|
||||
|
||||
use collections::HashMap;
|
||||
use pathfinder_geometry::{rect::RectF, vector::Vector2F};
|
||||
use pathfinder_geometry::rect::RectF;
|
||||
|
||||
use crate::{EventContext, MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
|
||||
use crate::{EventContext, MouseButton};
|
||||
|
||||
use super::mouse_region_event::{
|
||||
ClickRegionEvent, DownOutRegionEvent, DownRegionEvent, DragOverRegionEvent, DragRegionEvent,
|
||||
HoverRegionEvent, MouseRegionEvent, MoveRegionEvent, UpOutRegionEvent, UpRegionEvent,
|
||||
};
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
pub struct MouseRegion {
|
||||
@ -52,7 +53,7 @@ impl MouseRegion {
|
||||
pub fn on_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_down(button, handler);
|
||||
self
|
||||
@ -61,7 +62,7 @@ impl MouseRegion {
|
||||
pub fn on_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_up(button, handler);
|
||||
self
|
||||
@ -70,7 +71,7 @@ impl MouseRegion {
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_click(button, handler);
|
||||
self
|
||||
@ -79,7 +80,7 @@ impl MouseRegion {
|
||||
pub fn on_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_down_out(button, handler);
|
||||
self
|
||||
@ -88,7 +89,7 @@ impl MouseRegion {
|
||||
pub fn on_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_up_out(button, handler);
|
||||
self
|
||||
@ -97,7 +98,7 @@ impl MouseRegion {
|
||||
pub fn on_drag(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_drag(button, handler);
|
||||
self
|
||||
@ -106,7 +107,7 @@ impl MouseRegion {
|
||||
pub fn on_drag_over(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DragOverRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_drag_over(button, handler);
|
||||
self
|
||||
@ -114,7 +115,7 @@ impl MouseRegion {
|
||||
|
||||
pub fn on_hover(
|
||||
mut self,
|
||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.handlers = self.handlers.on_hover(handler);
|
||||
self
|
||||
@ -191,15 +192,32 @@ impl HandlerSet {
|
||||
self.set.get(key).cloned()
|
||||
}
|
||||
|
||||
pub fn on_move(
|
||||
mut self,
|
||||
handler: impl Fn(MoveRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::move_disc(), None),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Move(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Move, found {:?}",
|
||||
region_event);
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn on_down(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DownRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::down_disc(), Some(button)),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Down(mouse_button_event) = region_event {
|
||||
handler(mouse_button_event, cx);
|
||||
if let MouseRegionEvent::Down(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Down, found {:?}",
|
||||
@ -212,12 +230,12 @@ impl HandlerSet {
|
||||
pub fn on_up(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(UpRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::up_disc(), Some(button)),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Up(mouse_button_event) = region_event {
|
||||
handler(mouse_button_event, cx);
|
||||
if let MouseRegionEvent::Up(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Up, found {:?}",
|
||||
@ -230,12 +248,12 @@ impl HandlerSet {
|
||||
pub fn on_click(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(ClickRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::click_disc(), Some(button)),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Click(mouse_button_event) = region_event {
|
||||
handler(mouse_button_event, cx);
|
||||
if let MouseRegionEvent::Click(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Click, found {:?}",
|
||||
@ -248,12 +266,12 @@ impl HandlerSet {
|
||||
pub fn on_down_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DownOutRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::down_out_disc(), Some(button)),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::DownOut(mouse_button_event) = region_event {
|
||||
handler(mouse_button_event, cx);
|
||||
if let MouseRegionEvent::DownOut(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DownOut, found {:?}",
|
||||
@ -266,12 +284,12 @@ impl HandlerSet {
|
||||
pub fn on_up_out(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(MouseButtonEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(UpOutRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::up_out_disc(), Some(button)),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::UpOut(mouse_button_event) = region_event {
|
||||
handler(mouse_button_event, cx);
|
||||
if let MouseRegionEvent::UpOut(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::UpOut, found {:?}",
|
||||
@ -284,12 +302,12 @@ impl HandlerSet {
|
||||
pub fn on_drag(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(Vector2F, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DragRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::drag_disc(), Some(button)),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Drag(prev_drag_position, mouse_moved_event) = region_event {
|
||||
handler(prev_drag_position, mouse_moved_event, cx);
|
||||
if let MouseRegionEvent::Drag(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Drag, found {:?}",
|
||||
@ -302,12 +320,12 @@ impl HandlerSet {
|
||||
pub fn on_drag_over(
|
||||
mut self,
|
||||
button: MouseButton,
|
||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(DragOverRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::drag_over_disc(), Some(button)),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::DragOver(started, mouse_moved_event) = region_event {
|
||||
handler(started, mouse_moved_event, cx);
|
||||
if let MouseRegionEvent::DragOver(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::DragOver, found {:?}",
|
||||
@ -319,12 +337,12 @@ impl HandlerSet {
|
||||
|
||||
pub fn on_hover(
|
||||
mut self,
|
||||
handler: impl Fn(bool, MouseMovedEvent, &mut EventContext) + 'static,
|
||||
handler: impl Fn(HoverRegionEvent, &mut EventContext) + 'static,
|
||||
) -> Self {
|
||||
self.set.insert((MouseRegionEvent::hover_disc(), None),
|
||||
Rc::new(move |region_event, cx| {
|
||||
if let MouseRegionEvent::Hover(started, mouse_moved_event) = region_event {
|
||||
handler(started, mouse_moved_event, cx);
|
||||
if let MouseRegionEvent::Hover(e) = region_event {
|
||||
handler(e, cx);
|
||||
} else {
|
||||
panic!(
|
||||
"Mouse Region Event incorrectly called with mismatched event type. Expected MouseRegionEvent::Hover, found {:?}",
|
||||
@ -334,106 +352,3 @@ impl HandlerSet {
|
||||
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,
|
||||
platform::CursorStyle,
|
||||
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton,
|
||||
MouseButtonEvent, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext,
|
||||
ViewHandle,
|
||||
MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use menu::{Confirm, SelectNext, SelectPrev};
|
||||
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
|
||||
@ -1064,25 +1063,22 @@ impl ProjectPanel {
|
||||
.with_padding_left(padding)
|
||||
.boxed()
|
||||
})
|
||||
.on_click(
|
||||
MouseButton::Left,
|
||||
move |MouseButtonEvent { click_count, .. }, cx| {
|
||||
if kind == EntryKind::Dir {
|
||||
cx.dispatch_action(ToggleExpanded(entry_id))
|
||||
} else {
|
||||
cx.dispatch_action(Open {
|
||||
entry_id,
|
||||
change_focus: click_count > 1,
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
.on_down(
|
||||
MouseButton::Right,
|
||||
move |MouseButtonEvent { position, .. }, cx| {
|
||||
cx.dispatch_action(DeployContextMenu { entry_id, position })
|
||||
},
|
||||
)
|
||||
.on_click(MouseButton::Left, move |e, cx| {
|
||||
if kind == EntryKind::Dir {
|
||||
cx.dispatch_action(ToggleExpanded(entry_id))
|
||||
} else {
|
||||
cx.dispatch_action(Open {
|
||||
entry_id,
|
||||
change_focus: e.click_count > 1,
|
||||
})
|
||||
}
|
||||
})
|
||||
.on_down(MouseButton::Right, move |e, cx| {
|
||||
cx.dispatch_action(DeployContextMenu {
|
||||
entry_id,
|
||||
position: e.position,
|
||||
})
|
||||
})
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.boxed()
|
||||
}
|
||||
@ -1129,16 +1125,16 @@ impl View for ProjectPanel {
|
||||
.expanded()
|
||||
.boxed()
|
||||
})
|
||||
.on_down(
|
||||
MouseButton::Right,
|
||||
move |MouseButtonEvent { position, .. }, cx| {
|
||||
// When deploying the context menu anywhere below the last project entry,
|
||||
// act as if the user clicked the root of the last worktree.
|
||||
if let Some(entry_id) = last_worktree_root_id {
|
||||
cx.dispatch_action(DeployContextMenu { entry_id, position })
|
||||
}
|
||||
},
|
||||
)
|
||||
.on_down(MouseButton::Right, move |e, cx| {
|
||||
// When deploying the context menu anywhere below the last project entry,
|
||||
// act as if the user clicked the root of the last worktree.
|
||||
if let Some(entry_id) = last_worktree_root_id {
|
||||
cx.dispatch_action(DeployContextMenu {
|
||||
entry_id,
|
||||
position: e.position,
|
||||
})
|
||||
}
|
||||
})
|
||||
.boxed(),
|
||||
)
|
||||
.with_child(ChildView::new(&self.context_menu).boxed())
|
||||
|
@ -16,8 +16,8 @@ use gpui::{
|
||||
},
|
||||
json::json,
|
||||
text_layout::{Line, RunStyle},
|
||||
Event, FontCache, KeyDownEvent, MouseButton, MouseButtonEvent, MouseMovedEvent, MouseRegion,
|
||||
PaintContext, Quad, ScrollWheelEvent, TextLayoutCache, WeakModelHandle, WeakViewHandle,
|
||||
Event, FontCache, KeyDownEvent, MouseButton, MouseRegion, PaintContext, Quad, ScrollWheelEvent,
|
||||
TextLayoutCache, WeakModelHandle, WeakViewHandle,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
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,
|
||||
}
|
||||
|
||||
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)]
|
||||
pub struct TabStyles {
|
||||
pub active_tab: Tab,
|
||||
|
@ -15,6 +15,7 @@ client = { path = "../client" }
|
||||
clock = { path = "../clock" }
|
||||
collections = { path = "../collections" }
|
||||
context_menu = { path = "../context_menu" }
|
||||
drag_and_drop = { path = "../drag_and_drop" }
|
||||
gpui = { path = "../gpui" }
|
||||
language = { path = "../language" }
|
||||
menu = { path = "../menu" }
|
||||
|
@ -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();
|
||||
|
||||
enum Tabs {}
|
||||
@ -889,131 +889,38 @@ impl Pane {
|
||||
None
|
||||
};
|
||||
|
||||
let is_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 pane_active = self.is_active;
|
||||
|
||||
let mut row = Flex::row().scrollable::<Tabs, _>(1, autoscroll, cx);
|
||||
for (ix, (item, detail)) in self.items.iter().zip(self.tab_details(cx)).enumerate() {
|
||||
let item_id = item.id();
|
||||
for (ix, (item, detail)) in self
|
||||
.items
|
||||
.iter()
|
||||
.cloned()
|
||||
.zip(self.tab_details(cx))
|
||||
.enumerate()
|
||||
{
|
||||
let detail = if detail == 0 { None } else { Some(detail) };
|
||||
let is_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(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let tab_active = ix == self.active_item_index;
|
||||
|
||||
row.add_child({
|
||||
let mut tab_style = match is_tab_active {
|
||||
true => tab_styles.active_tab.clone(),
|
||||
false => tab_styles.inactive_tab.clone(),
|
||||
};
|
||||
MouseEventHandler::new::<Tab, _, _>(ix, cx, {
|
||||
let item = item.clone();
|
||||
let pane = pane.clone();
|
||||
let hovered = mouse_state.hovered;
|
||||
|
||||
let title = item.tab_content(detail, &tab_style, cx);
|
||||
|
||||
if ix == 0 {
|
||||
tab_style.container.border.left = false;
|
||||
}
|
||||
|
||||
MouseEventHandler::new::<Tab, _, _>(ix, cx, |_, 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 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()
|
||||
move |_, cx| {
|
||||
Self::render_tab(
|
||||
&item,
|
||||
pane,
|
||||
detail,
|
||||
hovered,
|
||||
pane_active,
|
||||
tab_active,
|
||||
cx,
|
||||
)
|
||||
}
|
||||
})
|
||||
.with_cursor_style(if is_tab_active && is_pane_active {
|
||||
.with_cursor_style(if pane_active && tab_active {
|
||||
CursorStyle::Arrow
|
||||
} else {
|
||||
CursorStyle::PointingHand
|
||||
@ -1021,24 +928,20 @@ impl Pane {
|
||||
.on_down(MouseButton::Left, move |_, cx| {
|
||||
cx.dispatch_action(ActivateItem(ix));
|
||||
})
|
||||
.on_click(MouseButton::Middle, close_tab_callback)
|
||||
.on_drag(MouseButton::Left, |_, cx| {
|
||||
cx.global::<DragAndDrop>().dragging(some view handle)
|
||||
})
|
||||
.on_up_out(MouseButton::Left, |_, cx| {
|
||||
cx.global::<DragAndDrop>().stopped_dragging(some view handle)
|
||||
})
|
||||
.on_drag_over(MouseButton::Left, |started, _, _, cx| {
|
||||
if started {
|
||||
if let Some(tab) = cx.global::<DragAndDrop>().current_dragged::<Tab>() {
|
||||
cx.dispatch_action(ReceivingTab);
|
||||
}
|
||||
.on_click(MouseButton::Middle, {
|
||||
let pane = pane.clone();
|
||||
move |_, cx: &mut EventContext| {
|
||||
cx.dispatch_action(CloseItem {
|
||||
item_id: item.id(),
|
||||
pane: pane.clone(),
|
||||
})
|
||||
}
|
||||
})
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
|
||||
let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
|
||||
row.add_child(
|
||||
Empty::new()
|
||||
.contained()
|
||||
@ -1088,6 +991,109 @@ impl Pane {
|
||||
|
||||
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 {
|
||||
@ -1110,7 +1116,7 @@ impl View for Pane {
|
||||
Flex::column()
|
||||
.with_child({
|
||||
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 {
|
||||
tab_row.add_children([
|
||||
@ -1167,12 +1173,11 @@ impl View for Pane {
|
||||
},
|
||||
)
|
||||
.with_cursor_style(CursorStyle::PointingHand)
|
||||
.on_down(
|
||||
MouseButton::Left,
|
||||
|MouseButtonEvent { position, .. }, cx| {
|
||||
cx.dispatch_action(DeploySplitMenu { position });
|
||||
},
|
||||
)
|
||||
.on_down(MouseButton::Left, |e, cx| {
|
||||
cx.dispatch_action(DeploySplitMenu {
|
||||
position: e.position,
|
||||
});
|
||||
})
|
||||
.boxed(),
|
||||
])
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::StatusItemView;
|
||||
use gpui::{
|
||||
elements::*, impl_actions, platform::CursorStyle, AnyViewHandle, AppContext, Entity,
|
||||
MouseButton, MouseMovedEvent, RenderContext, Subscription, View, ViewContext, ViewHandle,
|
||||
MouseButton, RenderContext, Subscription, View, ViewContext, ViewHandle,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use settings::Settings;
|
||||
@ -189,26 +189,18 @@ impl Sidebar {
|
||||
})
|
||||
.with_cursor_style(CursorStyle::ResizeLeftRight)
|
||||
.on_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere
|
||||
.on_drag(
|
||||
MouseButton::Left,
|
||||
move |old_position,
|
||||
MouseMovedEvent {
|
||||
position: new_position,
|
||||
..
|
||||
},
|
||||
cx| {
|
||||
let delta = new_position.x() - old_position.x();
|
||||
let prev_width = *actual_width.borrow();
|
||||
*custom_width.borrow_mut() = 0f32
|
||||
.max(match side {
|
||||
Side::Left => prev_width + delta,
|
||||
Side::Right => prev_width - delta,
|
||||
})
|
||||
.round();
|
||||
.on_drag(MouseButton::Left, move |e, cx| {
|
||||
let delta = e.prev_drag_position.x() - e.position.x();
|
||||
let prev_width = *actual_width.borrow();
|
||||
*custom_width.borrow_mut() = 0f32
|
||||
.max(match side {
|
||||
Side::Left => prev_width + delta,
|
||||
Side::Right => prev_width - delta,
|
||||
})
|
||||
.round();
|
||||
|
||||
cx.notify();
|
||||
},
|
||||
)
|
||||
cx.notify();
|
||||
})
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ use client::{
|
||||
};
|
||||
use clock::ReplicaId;
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use drag_and_drop::DragAndDrop;
|
||||
use futures::{channel::oneshot, FutureExt};
|
||||
use gpui::{
|
||||
actions,
|
||||
@ -895,6 +896,9 @@ impl Workspace {
|
||||
status_bar
|
||||
});
|
||||
|
||||
let drag_and_drop = DragAndDrop::new(cx.weak_handle(), cx);
|
||||
cx.set_global(drag_and_drop);
|
||||
|
||||
let mut this = Workspace {
|
||||
modal: None,
|
||||
weak_self,
|
||||
@ -2471,6 +2475,7 @@ impl View for Workspace {
|
||||
.with_background_color(theme.workspace.background)
|
||||
.boxed(),
|
||||
)
|
||||
.with_children(DragAndDrop::render(cx))
|
||||
.with_children(self.render_disconnected_overlay(cx))
|
||||
.named("workspace")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user