wip tab drag and drop

This commit is contained in:
K Simmons 2022-07-26 10:04:16 -07:00
parent 86fdd55fd4
commit 133c194f4a
20 changed files with 1642 additions and 413 deletions

9
Cargo.lock generated
View File

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

View File

@ -566,7 +566,7 @@ impl ContactsPanel {
button
.with_cursor_style(CursorStyle::PointingHand)
.on_click(MouseButton::Left, move |_, cx| {
let project = project_handle.upgrade(cx.deref_mut());
let project = project_handle.upgrade(cx.app);
cx.dispatch_action(ToggleProjectOnline { project })
})
.with_tooltip::<ToggleOnline, _>(

View File

@ -0,0 +1,15 @@
[package]
name = "drag_and_drop"
version = "0.1.0"
edition = "2021"
[lib]
path = "src/drag_and_drop.rs"
doctest = false
[dependencies]
collections = { path = "../collections" }
gpui = { path = "../gpui" }
[dev-dependencies]
gpui = { path = "../gpui", features = ["test-support"] }

View File

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

View File

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

View File

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

View File

@ -7,8 +7,8 @@ use crate::{
geometry::{rect::RectF, vector::Vector2F},
json::json,
presenter::MeasurementContext,
Action, Axis, ElementStateHandle, LayoutContext, MouseMovedEvent, PaintContext, RenderContext,
SizeConstraint, Task, View,
Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint,
Task, View,
};
use serde::Deserialize;
use std::{
@ -93,10 +93,11 @@ impl Tooltip {
};
let child =
MouseEventHandler::new::<MouseEventHandlerState<Tag>, _, _>(id, cx, |_, _| child)
.on_hover(move |hover, MouseMovedEvent { position, .. }, cx| {
.on_hover(move |e, cx| {
let position = e.position;
let window_id = cx.window_id();
if let Some(view_id) = cx.view_id() {
if hover {
if e.started {
if !state.visible.get() {
state.position.set(position);

View File

@ -6,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(&region_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(&region_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(&region_id);
}
if self.hovered_region_ids.contains(&region_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(&region_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> {

View File

@ -1,4 +1,5 @@
mod mouse_region;
mod mouse_region_event;
use serde::Deserialize;
use serde_json::json;
@ -13,6 +14,7 @@ use crate::{
ImageData,
};
pub use mouse_region::*;
pub use mouse_region_event::*;
pub struct Scene {
scale_factor: f32,

View File

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

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

View File

@ -12,8 +12,7 @@ use gpui::{
impl_internal_actions, keymap,
platform::CursorStyle,
AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, MouseButton,
MouseButtonEvent, MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext,
ViewHandle,
MutableAppContext, PromptLevel, RenderContext, Task, View, ViewContext, ViewHandle,
};
use menu::{Confirm, SelectNext, SelectPrev};
use project::{Entry, EntryKind, Project, ProjectEntryId, ProjectPath, Worktree, WorktreeId};
@ -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())

View File

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

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

View File

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

View File

@ -15,6 +15,7 @@ client = { path = "../client" }
clock = { path = "../clock" }
collections = { path = "../collections" }
context_menu = { path = "../context_menu" }
drag_and_drop = { path = "../drag_and_drop" }
gpui = { path = "../gpui" }
language = { path = "../language" }
menu = { path = "../menu" }

View File

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

View File

@ -1,7 +1,7 @@
use crate::StatusItemView;
use gpui::{
elements::*, impl_actions, platform::CursorStyle, AnyViewHandle, AppContext, Entity,
MouseButton, MouseMovedEvent, RenderContext, Subscription, View, ViewContext, ViewHandle,
MouseButton, RenderContext, Subscription, View, ViewContext, ViewHandle,
};
use serde::Deserialize;
use settings::Settings;
@ -189,26 +189,18 @@ impl Sidebar {
})
.with_cursor_style(CursorStyle::ResizeLeftRight)
.on_down(MouseButton::Left, |_, _| {}) // This prevents the mouse down event from being propagated elsewhere
.on_drag(
MouseButton::Left,
move |old_position,
MouseMovedEvent {
position: new_position,
..
},
cx| {
let delta = new_position.x() - old_position.x();
let prev_width = *actual_width.borrow();
*custom_width.borrow_mut() = 0f32
.max(match side {
Side::Left => prev_width + delta,
Side::Right => prev_width - delta,
})
.round();
.on_drag(MouseButton::Left, move |e, cx| {
let delta = e.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()
}
}

View File

@ -16,6 +16,7 @@ use client::{
};
use clock::ReplicaId;
use collections::{hash_map, HashMap, HashSet};
use drag_and_drop::DragAndDrop;
use futures::{channel::oneshot, FutureExt};
use gpui::{
actions,
@ -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")
}