diff --git a/Cargo.lock b/Cargo.lock index 4256d24c9d..eb89ff0392 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/crates/contacts_panel/src/contacts_panel.rs b/crates/contacts_panel/src/contacts_panel.rs index 9198efceba..4c9f846576 100644 --- a/crates/contacts_panel/src/contacts_panel.rs +++ b/crates/contacts_panel/src/contacts_panel.rs @@ -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::( diff --git a/crates/drag_and_drop/Cargo.toml b/crates/drag_and_drop/Cargo.toml new file mode 100644 index 0000000000..2fd8ce27b8 --- /dev/null +++ b/crates/drag_and_drop/Cargo.toml @@ -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"] } \ No newline at end of file diff --git a/crates/drag_and_drop/src/drag_and_drop.rs b/crates/drag_and_drop/src/drag_and_drop.rs new file mode 100644 index 0000000000..37c331fbb1 --- /dev/null +++ b/crates/drag_and_drop/src/drag_and_drop.rs @@ -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 { + position: Vector2F, + region_offset: Vector2F, + payload: Arc, + render: Arc, &mut RenderContext) -> ElementBox>, +} + +impl Clone for State { + 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 { + parent: WeakViewHandle, + currently_dragged: Option>, +} + +impl DragAndDrop { + pub fn new(parent: WeakViewHandle, cx: &mut ViewContext) -> Self { + // TODO: Figure out if detaching here would result in a memory leak + cx.observe_global::(|cx| { + if let Some(parent) = cx.global::().parent.upgrade(cx) { + parent.update(cx, |_, cx| cx.notify()) + } + }) + .detach(); + + Self { + parent, + currently_dragged: None, + } + } + + pub fn currently_dragged(&self) -> Option<(Vector2F, &T)> { + self.currently_dragged.as_ref().and_then( + |State { + position, payload, .. + }| { + payload + .downcast_ref::() + .map(|payload| (position.clone(), payload)) + }, + ) + } + + pub fn dragging( + relative_to: Option, + position: Vector2F, + payload: Arc, + cx: &mut EventContext, + render: Arc) -> ElementBox>, + ) { + cx.update_global::(|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::().unwrap(), cx) + }), + }); + + if let Some(parent) = this.parent.upgrade(cx) { + parent.update(cx, |_, cx| cx.notify()) + } + }); + } + + pub fn render(cx: &mut RenderContext) -> Option { + let currently_dragged = cx.global::().currently_dragged.clone(); + + currently_dragged.map( + |State { + region_offset, + position, + payload, + render, + }| { + let position = position + region_offset; + + MouseEventHandler::new::(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::(|this, _| this.currently_dragged.take()); + }); + cx.propogate_event(); + }) + .boxed() + }, + ) + } +} + +pub trait Draggable { + fn as_draggable( + self, + payload: P, + render: impl 'static + Fn(&P, &mut RenderContext) -> ElementBox, + ) -> Self + where + Self: Sized; +} + +impl Draggable for MouseEventHandler { + fn as_draggable( + self, + payload: P, + render: impl 'static + Fn(&P, &mut RenderContext) -> 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::::dragging(Some(e.region), e.position, payload, cx, render) + }) + } +} diff --git a/crates/gpui/src/elements/container.rs b/crates/gpui/src/elements/container.rs index a581e60b74..ad9d7fe0cb 100644 --- a/crates/gpui/src/elements/container.rs +++ b/crates/gpui/src/elements/container.rs @@ -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 diff --git a/crates/gpui/src/elements/mouse_event_handler.rs b/crates/gpui/src/elements/mouse_event_handler.rs index 35e8d6407e..570ec1b1a6 100644 --- a/crates/gpui/src/elements/mouse_event_handler.rs +++ b/crates/gpui/src/elements/mouse_event_handler.rs @@ -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 diff --git a/crates/gpui/src/elements/tooltip.rs b/crates/gpui/src/elements/tooltip.rs index 68605fb6b3..26f8a450db 100644 --- a/crates/gpui/src/elements/tooltip.rs +++ b/crates/gpui/src/elements/tooltip.rs @@ -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::, _, _>(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); diff --git a/crates/gpui/src/presenter.rs b/crates/gpui/src/presenter.rs index 899beacf47..87dc6377c7 100644 --- a/crates/gpui/src/presenter.rs +++ b/crates/gpui/src/presenter.rs @@ -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, 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, + clicked_region: &'a mut Option, + prev_drag_position: &'a mut Option, + handled: bool, invalidated_views: HashSet, } @@ -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(&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> { diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index a1f12b76d3..086af5f64d 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -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, diff --git a/crates/gpui/src/scene/mouse_region.rs b/crates/gpui/src/scene/mouse_region.rs index 6482310259..308722fced 100644 --- a/crates/gpui/src/scene/mouse_region.rs +++ b/crates/gpui/src/scene/mouse_region.rs @@ -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 { - discriminant(&MouseRegionEvent::Move(Default::default())) - } - - pub fn drag_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Drag( - Default::default(), - Default::default(), - )) - } - - pub fn drag_over_disc() -> Discriminant { - discriminant(&MouseRegionEvent::DragOver( - Default::default(), - Default::default(), - )) - } - - pub fn hover_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Hover( - Default::default(), - Default::default(), - )) - } - - pub fn down_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Down(Default::default())) - } - - pub fn up_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Up(Default::default())) - } - - pub fn up_out_disc() -> Discriminant { - discriminant(&MouseRegionEvent::UpOut(Default::default())) - } - - pub fn click_disc() -> Discriminant { - discriminant(&MouseRegionEvent::Click(Default::default())) - } - - pub fn down_out_disc() -> Discriminant { - discriminant(&MouseRegionEvent::DownOut(Default::default())) - } - - pub fn scroll_wheel_disc() -> Discriminant { - 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, Option) { - 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), - } - } -} diff --git a/crates/gpui/src/scene/mouse_region_event.rs b/crates/gpui/src/scene/mouse_region_event.rs new file mode 100644 index 0000000000..6e33240e4b --- /dev/null +++ b/crates/gpui/src/scene/mouse_region_event.rs @@ -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 { + discriminant(&MouseRegionEvent::Move(Default::default())) + } + + pub fn drag_disc() -> Discriminant { + discriminant(&MouseRegionEvent::Drag(Default::default())) + } + + pub fn drag_over_disc() -> Discriminant { + discriminant(&MouseRegionEvent::DragOver(Default::default())) + } + + pub fn hover_disc() -> Discriminant { + discriminant(&MouseRegionEvent::Hover(Default::default())) + } + + pub fn down_disc() -> Discriminant { + discriminant(&MouseRegionEvent::Down(Default::default())) + } + + pub fn up_disc() -> Discriminant { + discriminant(&MouseRegionEvent::Up(Default::default())) + } + + pub fn up_out_disc() -> Discriminant { + discriminant(&MouseRegionEvent::UpOut(Default::default())) + } + + pub fn click_disc() -> Discriminant { + discriminant(&MouseRegionEvent::Click(Default::default())) + } + + pub fn down_out_disc() -> Discriminant { + discriminant(&MouseRegionEvent::DownOut(Default::default())) + } + + pub fn scroll_wheel_disc() -> Discriminant { + 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, Option) { + 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), + } + } +} diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index 702c750ea2..8a071489ad 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -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()) diff --git a/crates/terminal/src/connected_el.rs b/crates/terminal/src/connected_el.rs index ab08efc1b3..f138173a0b 100644 --- a/crates/terminal/src/connected_el.rs +++ b/crates/terminal/src/connected_el.rs @@ -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; diff --git a/crates/terminal/src/terminal_element.rs b/crates/terminal/src/terminal_element.rs new file mode 100644 index 0000000000..183d833382 --- /dev/null +++ b/crates/terminal/src/terminal_element.rs @@ -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, + 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, + highlighted_range: Option>, +} + +///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, + text: Line, //NOTE TO SELF THIS IS BAD PERFORMANCE RN! + background_color: Color, +} + +impl LayoutCell { + fn new(point: Point, 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, + line_height: LineHeight, + em_width: CellWidth, + cursor: Option, + background_color: Color, + cur_size: SizeInfo, + terminal: Arc>>, + selection_color: Color, +} + +impl TerminalEl { + pub fn new( + view_id: usize, + connection: WeakModelHandle, + 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::()); + 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::()).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::>(); + + 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, + text_style: &TextStyle, + terminal_theme: &TerminalStyle, + text_layout_cache: &TextLayoutCache, + modal: bool, + selection_range: Option, +) -> Vec { + 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::>(); + + LayoutLine { + cells, + highlighted_range, + } + }) + .collect::>() +} + +// 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>>, + 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), + ) + ); + } +} diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 1aaeed2d78..08fb65a001 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -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, diff --git a/crates/workspace/Cargo.toml b/crates/workspace/Cargo.toml index 3534e293f8..c40ce56389 100644 --- a/crates/workspace/Cargo.toml +++ b/crates/workspace/Cargo.toml @@ -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" } diff --git a/crates/workspace/src/drag_and_drop.rs b/crates/workspace/src/drag_and_drop.rs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index 2f58f7cba4..68878dc6a4 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -876,7 +876,7 @@ impl Pane { }); } - fn render_tabs(&mut self, cx: &mut RenderContext) -> impl Element { + fn render_tab_bar(&mut self, cx: &mut RenderContext) -> impl Element { let theme = cx.global::().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::(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::(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::(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::( - 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::().dragging(some view handle) - }) - .on_up_out(MouseButton::Left, |_, cx| { - cx.global::().stopped_dragging(some view handle) - }) - .on_drag_over(MouseButton::Left, |started, _, _, cx| { - if started { - if let Some(tab) = cx.global::().current_dragged::() { - 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( + item: &Box, + pane: WeakViewHandle, + detail: Option, + hovered: bool, + pane_active: bool, + tab_active: bool, + cx: &mut RenderContext, + ) -> ElementBox { + let theme = cx.global::().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::( + 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(), ]) } diff --git a/crates/workspace/src/sidebar.rs b/crates/workspace/src/sidebar.rs index 6749de36a9..819959eb75 100644 --- a/crates/workspace/src/sidebar.rs +++ b/crates/workspace/src/sidebar.rs @@ -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() } } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 40934f4ed7..fa81cd9f11 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -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") }