Drag and drop tabs working. all known bugs fixed
This commit is contained in:
parent
0a97a9c0fd
commit
042ece00b1
8 changed files with 142 additions and 46 deletions
|
@ -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()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 ®ion_event {
|
match ®ion_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(®ion_id) {
|
if self.hovered_region_ids.remove(®ion_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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue