Drag and drop tabs working. all known bugs fixed

This commit is contained in:
K Simmons 2022-08-25 11:14:24 -07:00
parent 0a97a9c0fd
commit 042ece00b1
8 changed files with 142 additions and 46 deletions

View file

@ -116,6 +116,8 @@ impl<V: View> DragAndDrop<V> {
}); });
cx.propogate_event(); cx.propogate_event();
}) })
// Don't block hover events or invalidations
.with_hoverable(false)
.boxed() .boxed()
}, },
) )

View file

@ -24,6 +24,8 @@ pub struct ContainerStyle {
pub padding: Padding, pub padding: Padding,
#[serde(rename = "background")] #[serde(rename = "background")]
pub background_color: Option<Color>, pub background_color: Option<Color>,
#[serde(rename = "overlay")]
pub overlay_color: Option<Color>,
#[serde(default)] #[serde(default)]
pub border: Border, pub border: Border,
#[serde(default)] #[serde(default)]
@ -119,6 +121,11 @@ impl Container {
self self
} }
pub fn with_overlay_color(mut self, color: Color) -> Self {
self.style.overlay_color = Some(color);
self
}
pub fn with_border(mut self, border: Border) -> Self { pub fn with_border(mut self, border: Border) -> Self {
self.style.border = border; self.style.border = border;
self self
@ -245,7 +252,7 @@ impl Element for Container {
cx.scene.push_layer(None); cx.scene.push_layer(None);
cx.scene.push_quad(Quad { cx.scene.push_quad(Quad {
bounds: quad_bounds, bounds: quad_bounds,
background: Default::default(), background: self.style.overlay_color,
border: self.style.border, border: self.style.border,
corner_radius: self.style.corner_radius, corner_radius: self.style.corner_radius,
}); });
@ -264,6 +271,17 @@ impl Element for Container {
self.style.border.top_width(), self.style.border.top_width(),
); );
self.child.paint(child_origin, visible_bounds, cx); self.child.paint(child_origin, visible_bounds, cx);
if self.style.overlay_color.is_some() {
cx.scene.push_layer(None);
cx.scene.push_quad(Quad {
bounds: quad_bounds,
background: self.style.overlay_color,
border: Default::default(),
corner_radius: 0.,
});
cx.scene.pop_layer();
}
} }
} }

View file

@ -20,6 +20,7 @@ pub struct MouseEventHandler {
discriminant: (TypeId, usize), discriminant: (TypeId, usize),
cursor_style: Option<CursorStyle>, cursor_style: Option<CursorStyle>,
handlers: HandlerSet, handlers: HandlerSet,
hoverable: bool,
padding: Padding, padding: Padding,
} }
@ -35,6 +36,7 @@ impl MouseEventHandler {
cursor_style: None, cursor_style: None,
discriminant: (TypeId::of::<Tag>(), id), discriminant: (TypeId::of::<Tag>(), id),
handlers: Default::default(), handlers: Default::default(),
hoverable: true,
padding: Default::default(), padding: Default::default(),
} }
} }
@ -119,6 +121,11 @@ impl MouseEventHandler {
self self
} }
pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
self.hoverable = is_hoverable;
self
}
pub fn with_padding(mut self, padding: Padding) -> Self { pub fn with_padding(mut self, padding: Padding) -> Self {
self.padding = padding; self.padding = padding;
self self
@ -160,12 +167,15 @@ impl Element for MouseEventHandler {
}); });
} }
cx.scene.push_mouse_region(MouseRegion::from_handlers( cx.scene.push_mouse_region(
cx.current_view_id(), MouseRegion::from_handlers(
Some(self.discriminant), cx.current_view_id(),
hit_bounds, Some(self.discriminant),
self.handlers.clone(), hit_bounds,
)); self.handlers.clone(),
)
.with_hoverable(self.hoverable),
);
self.child.paint(bounds.origin(), visible_bounds, cx); self.child.paint(bounds.origin(), visible_bounds, cx);
} }

View file

@ -245,17 +245,21 @@ impl Presenter {
// MDN says that browsers handle this by starting from 'the most // MDN says that browsers handle this by starting from 'the most
// specific ancestor element that contained both [positions]' // specific ancestor element that contained both [positions]'
// So we need to store the overlapping regions on mouse down. // So we need to store the overlapping regions on mouse down.
self.clicked_regions = self
.mouse_regions // If there is already clicked_button stored, don't replace it.
.iter() if self.clicked_button.is_none() {
.filter_map(|(region, _)| { self.clicked_regions = self
region .mouse_regions
.bounds .iter()
.contains_point(e.position) .filter_map(|(region, _)| {
.then(|| region.clone()) region
}) .bounds
.collect(); .contains_point(e.position)
self.clicked_button = Some(e.button); .then(|| region.clone())
})
.collect();
self.clicked_button = Some(e.button);
}
events_to_send.push(MouseRegionEvent::Down(DownRegionEvent { events_to_send.push(MouseRegionEvent::Down(DownRegionEvent {
region: Default::default(), region: Default::default(),
@ -337,13 +341,18 @@ impl Presenter {
// GPUI elements are arranged by depth but sibling elements can register overlapping // GPUI elements are arranged by depth but sibling elements can register overlapping
// mouse regions. As such, hover events are only fired on overlapping elements which // mouse regions. As such, hover events are only fired on overlapping elements which
// are at the same depth as the deepest element which overlaps with the mouse. // are at the same depth as the topmost element which overlaps with the mouse.
match &region_event { match &region_event {
MouseRegionEvent::Hover(_) => { MouseRegionEvent::Hover(_) => {
let mut top_most_depth = None; let mut top_most_depth = None;
let mouse_position = self.mouse_position.clone(); let mouse_position = self.mouse_position.clone();
for (region, depth) in self.mouse_regions.iter().rev() { for (region, depth) in self.mouse_regions.iter().rev() {
// Allow mouse regions to appear transparent to hovers
if !region.hoverable {
continue;
}
let contains_mouse = region.bounds.contains_point(mouse_position); let contains_mouse = region.bounds.contains_point(mouse_position);
if contains_mouse && top_most_depth.is_none() { if contains_mouse && top_most_depth.is_none() {
@ -359,26 +368,30 @@ impl Presenter {
//Ensure that hover entrance events aren't sent twice //Ensure that hover entrance events aren't sent twice
if self.hovered_region_ids.insert(region_id) { if self.hovered_region_ids.insert(region_id) {
valid_regions.push(region.clone()); valid_regions.push(region.clone());
invalidated_views.insert(region.view_id);
} }
} else { } else {
// Ensure that hover exit events aren't sent twice // Ensure that hover exit events aren't sent twice
if self.hovered_region_ids.remove(&region_id) { if self.hovered_region_ids.remove(&region_id) {
valid_regions.push(region.clone()); valid_regions.push(region.clone());
invalidated_views.insert(region.view_id);
} }
} }
} }
} }
} }
MouseRegionEvent::Click(e) => { MouseRegionEvent::Click(e) => {
// Clear presenter state if e.button == self.clicked_button.unwrap() {
let clicked_regions = // Clear clicked regions and clicked button
std::mem::replace(&mut self.clicked_regions, Vec::new()); let clicked_regions =
self.clicked_button = None; std::mem::replace(&mut self.clicked_regions, Vec::new());
self.clicked_button = None;
// Find regions which still overlap with the mouse since the last MouseDown happened // Find regions which still overlap with the mouse since the last MouseDown happened
for clicked_region in clicked_regions.into_iter().rev() { for clicked_region in clicked_regions.into_iter().rev() {
if clicked_region.bounds.contains_point(e.position) { if clicked_region.bounds.contains_point(e.position) {
valid_regions.push(clicked_region); valid_regions.push(clicked_region);
}
} }
} }
} }

View file

@ -17,6 +17,7 @@ pub struct MouseRegion {
pub discriminant: Option<(TypeId, usize)>, pub discriminant: Option<(TypeId, usize)>,
pub bounds: RectF, pub bounds: RectF,
pub handlers: HandlerSet, pub handlers: HandlerSet,
pub hoverable: bool,
} }
impl MouseRegion { impl MouseRegion {
@ -35,6 +36,7 @@ impl MouseRegion {
discriminant, discriminant,
bounds, bounds,
handlers, handlers,
hoverable: true,
} }
} }
@ -48,6 +50,7 @@ impl MouseRegion {
discriminant, discriminant,
bounds, bounds,
handlers: HandlerSet::capture_all(), handlers: HandlerSet::capture_all(),
hoverable: true,
} }
} }
@ -120,6 +123,11 @@ impl MouseRegion {
self.handlers = self.handlers.on_move(handler); self.handlers = self.handlers.on_move(handler);
self self
} }
pub fn with_hoverable(mut self, is_hoverable: bool) -> Self {
self.hoverable = is_hoverable;
self
}
} }
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]

View file

@ -77,6 +77,7 @@ pub struct TabBar {
pub inactive_pane: TabStyles, pub inactive_pane: TabStyles,
pub dragged_tab: Tab, pub dragged_tab: Tab,
pub height: f32, pub height: f32,
pub drop_target_overlay_color: Color,
} }
impl TabBar { impl TabBar {

View file

@ -7,6 +7,7 @@ use drag_and_drop::{DragAndDrop, Draggable};
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
actions, actions,
color::Color,
elements::*, elements::*,
geometry::{ geometry::{
rect::RectF, rect::RectF,
@ -22,6 +23,7 @@ use project::{Project, ProjectEntryId, ProjectPath};
use serde::Deserialize; use serde::Deserialize;
use settings::{Autosave, Settings}; use settings::{Autosave, Settings};
use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc}; use std::{any::Any, cell::RefCell, cmp, mem, path::Path, rc::Rc};
use theme::Theme;
use util::ResultExt; use util::ResultExt;
#[derive(Clone, Deserialize, PartialEq)] #[derive(Clone, Deserialize, PartialEq)]
@ -475,15 +477,14 @@ impl Pane {
// If the item already exists, move it to the desired destination and activate it // If the item already exists, move it to the desired destination and activate it
pane.update(cx, |pane, cx| { pane.update(cx, |pane, cx| {
if existing_item_index != destination_index { if existing_item_index != destination_index {
cx.reparent(&item);
let existing_item_is_active = existing_item_index == pane.active_item_index; let existing_item_is_active = existing_item_index == pane.active_item_index;
pane.items.remove(existing_item_index); pane.items.remove(existing_item_index);
if existing_item_index < destination_index {
destination_index -= 1;
}
if existing_item_index < pane.active_item_index { if existing_item_index < pane.active_item_index {
pane.active_item_index -= 1; pane.active_item_index -= 1;
} }
destination_index = destination_index.min(pane.items.len());
pane.items.insert(destination_index, item.clone()); pane.items.insert(destination_index, item.clone());
@ -985,8 +986,9 @@ impl Pane {
enum Tabs {} enum Tabs {}
enum Tab {} enum Tab {}
enum Filler {}
let pane = cx.handle(); let pane = cx.handle();
MouseEventHandler::new::<Tabs, _, _>(0, cx, |mouse_state, cx| { MouseEventHandler::new::<Tabs, _, _>(0, cx, |_, cx| {
let autoscroll = if mem::take(&mut self.autoscroll) { let autoscroll = if mem::take(&mut self.autoscroll) {
Some(self.active_item_index) Some(self.active_item_index)
} else { } else {
@ -1011,14 +1013,22 @@ impl Pane {
let item = item.clone(); let item = item.clone();
let pane = pane.clone(); let pane = pane.clone();
let detail = detail.clone(); let detail = detail.clone();
let hovered = mouse_state.hovered;
let theme = cx.global::<Settings>().theme.clone(); let theme = cx.global::<Settings>().theme.clone();
move |_, cx| { move |mouse_state, cx| {
let tab_style = let tab_style =
theme.workspace.tab_bar.tab_style(pane_active, tab_active); theme.workspace.tab_bar.tab_style(pane_active, tab_active);
Self::render_tab(&item, pane, detail, hovered, tab_style, cx) let hovered = mouse_state.hovered;
Self::render_tab(
&item,
pane,
detail,
hovered,
Self::tab_overlay_color(hovered, theme.as_ref(), cx),
tab_style,
cx,
)
} }
}) })
.with_cursor_style(if pane_active && tab_active { .with_cursor_style(if pane_active && tab_active {
@ -1059,6 +1069,7 @@ impl Pane {
dragged_item.pane.clone(), dragged_item.pane.clone(),
detail, detail,
false, false,
None,
&tab_style, &tab_style,
cx, cx,
) )
@ -1069,14 +1080,25 @@ impl Pane {
}) })
} }
// Use the inactive tab style along with the current pane's active status to decide how to render
// the filler
let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false); let filler_style = theme.workspace.tab_bar.tab_style(pane_active, false);
row.add_child( row.add_child(
Empty::new() MouseEventHandler::new::<Filler, _, _>(0, cx, |mouse_state, cx| {
.contained() let mut filler = Empty::new()
.with_style(filler_style.container) .contained()
.with_border(filler_style.container.border) .with_style(filler_style.container)
.flex(1., true) .with_border(filler_style.container.border);
.named("filler"),
if let Some(overlay) = Self::tab_overlay_color(mouse_state.hovered, &theme, cx)
{
filler = filler.with_overlay_color(overlay);
}
filler.boxed()
})
.flex(1., true)
.named("filler"),
); );
row.boxed() row.boxed()
@ -1128,12 +1150,13 @@ impl Pane {
pane: WeakViewHandle<Pane>, pane: WeakViewHandle<Pane>,
detail: Option<usize>, detail: Option<usize>,
hovered: bool, hovered: bool,
overlay: Option<Color>,
tab_style: &theme::Tab, tab_style: &theme::Tab,
cx: &mut RenderContext<V>, cx: &mut RenderContext<V>,
) -> ElementBox { ) -> ElementBox {
let title = item.tab_content(detail, &tab_style, cx); let title = item.tab_content(detail, &tab_style, cx);
Flex::row() let mut tab = Flex::row()
.with_child( .with_child(
Align::new({ Align::new({
let diameter = 7.0; let diameter = 7.0;
@ -1216,10 +1239,13 @@ impl Pane {
.boxed(), .boxed(),
) )
.contained() .contained()
.with_style(tab_style.container) .with_style(tab_style.container);
.constrained()
.with_height(tab_style.height) if let Some(overlay) = overlay {
.boxed() tab = tab.with_overlay_color(overlay);
}
tab.constrained().with_height(tab_style.height).boxed()
} }
fn handle_dropped_item(pane: &WeakViewHandle<Pane>, index: usize, cx: &mut EventContext) { fn handle_dropped_item(pane: &WeakViewHandle<Pane>, index: usize, cx: &mut EventContext) {
@ -1237,6 +1263,23 @@ impl Pane {
cx.propogate_event(); cx.propogate_event();
} }
} }
fn tab_overlay_color(
hovered: bool,
theme: &Theme,
cx: &mut RenderContext<Self>,
) -> Option<Color> {
if hovered
&& cx
.global::<DragAndDrop<Workspace>>()
.currently_dragged::<DraggedItem>()
.is_some()
{
Some(theme.workspace.tab_bar.drop_target_overlay_color)
} else {
None
}
}
} }
impl Entity for Pane { impl Entity for Pane {
@ -1327,7 +1370,7 @@ impl View for Pane {
tab_row tab_row
.constrained() .constrained()
.with_height(cx.global::<Settings>().theme.workspace.tab_bar.height) .with_height(cx.global::<Settings>().theme.workspace.tab_bar.height)
.boxed() .named("tab bar")
}) })
.with_child(ChildView::new(&self.toolbar).boxed()) .with_child(ChildView::new(&self.toolbar).boxed())
.with_child(ChildView::new(active_item).flex(1., true).boxed()) .with_child(ChildView::new(active_item).flex(1., true).boxed())

View file

@ -72,6 +72,7 @@ export default function tabBar(theme: Theme) {
return { return {
height, height,
background: backgroundColor(theme, 300), background: backgroundColor(theme, 300),
dropTargetOverlayColor: withOpacity(theme.textColor.muted, 0.8),
border: border(theme, "primary", { border: border(theme, "primary", {
left: true, left: true,
bottom: true, bottom: true,