Detailed changes
@@ -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, _>(
@@ -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"] }
@@ -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};
-use crate::{EventContext, MouseButton, MouseButtonEvent, MouseMovedEvent, ScrollWheelEvent};
+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),
- }
- }
-}
@@ -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;
@@ -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(),
- };
-
- 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()
+ MouseEventHandler::new::<Tab, _, _>(ix, cx, {
+ let item = item.clone();
+ let pane = pane.clone();
+ let hovered = mouse_state.hovered;
+
+ 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")
}