未验证 提交 a6b1c77e 编写于 作者: S SteVen Batten 提交者: GitHub

Drag and Drop for Global Awareness (#92299)

* wip

* more feedback

* some cleanup

* move things out of paneview.ts

* dragging within a view pane container

* moving composites within and views across

* drag full composites to panel

* move composites between both

* moving views to composites

* dragging single view composite into view pane

* fix orienation for overlay

* quick cleanup before check in
上级 c3e6110d
......@@ -99,3 +99,26 @@
.monaco-pane-view.animated.horizontal .split-view-view {
transition-property: width;
}
#monaco-workbench-pane-drop-overlay {
position: absolute;
z-index: 10000;
width: 100%;
height: 100%;
left: 0;
box-sizing: border-box;
}
#monaco-workbench-pane-drop-overlay > .pane-overlay-indicator {
position: absolute;
width: 100%;
height: 100%;
min-height: 22px;
pointer-events: none; /* very important to not take events away from the parent */
transition: opacity 150ms ease-out;
}
#monaco-workbench-pane-drop-overlay > .pane-overlay-indicator.overlay-move-transition {
transition: top 70ms ease-out, left 70ms ease-out, width 70ms ease-out, height 70ms ease-out, opacity 150ms ease-out;
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDragAndDropData } from 'vs/base/browser/dnd';
export class CompositeDragAndDropData implements IDragAndDropData {
constructor(private type: 'view' | 'composite', private id: string) { }
update(dataTransfer: DataTransfer): void {
// no-op
}
getData(): {
type: 'view' | 'composite';
id: string;
} {
return { type: this.type, id: this.id };
}
}
export interface ICompositeDragAndDrop {
drop(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): void;
onDragOver(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean;
onDragEnter(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean;
}
......@@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri';
import { ITextFileService, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles';
import { Schemas } from 'vs/base/common/network';
import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { DataTransfers } from 'vs/base/browser/dnd';
import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd';
import { DragMouseEvent } from 'vs/base/browser/mouseEvent';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { MIME_BINARY } from 'vs/base/common/mime';
......@@ -21,7 +21,7 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor';
import { IEditorService, IResourceEditorInputType } from 'vs/workbench/services/editor/common/editorService';
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { addDisposableListener, EventType, asDomUri } from 'vs/base/browser/dom';
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing';
......@@ -29,6 +29,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
import { IHostService } from 'vs/workbench/services/host/browser/host';
import { isStandalone } from 'vs/base/browser/browser';
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
import { Emitter } from 'vs/base/common/event';
export interface IDraggedResource {
resource: URI;
......@@ -507,3 +508,219 @@ export function containsDragType(event: DragEvent, ...dragTypesToFind: string[])
return false;
}
export interface ICompositeDragAndDrop {
drop(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent, before?: boolean): void;
onDragOver(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean;
onDragEnter(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean;
}
export interface ICompositeDragAndDropObserverCallbacks {
onDragEnter?: (e: IDraggedCompositeData) => void;
onDragLeave?: (e: IDraggedCompositeData) => void;
onDrop?: (e: IDraggedCompositeData) => void;
onDragOver?: (e: IDraggedCompositeData) => void;
onDragStart?: (e: IDraggedCompositeData) => void;
onDragEnd?: (e: IDraggedCompositeData) => void;
}
export class CompositeDragAndDropData implements IDragAndDropData {
constructor(private type: 'view' | 'composite', private id: string) { }
update(dataTransfer: DataTransfer): void {
// no-op
}
getData(): {
type: 'view' | 'composite';
id: string;
} {
return { type: this.type, id: this.id };
}
}
export interface IDraggedCompositeData {
eventData: DragEvent;
dragAndDropData: CompositeDragAndDropData;
}
export class DraggedCompositeIdentifier {
constructor(private _compositeId: string) { }
get id(): string {
return this._compositeId;
}
}
export class DraggedViewIdentifier {
constructor(private _viewId: string) { }
get id(): string {
return this._viewId;
}
}
export type ViewType = 'composite' | 'view';
export class CompositeDragAndDropObserver extends Disposable {
private transferData: LocalSelectionTransfer<DraggedCompositeIdentifier | DraggedViewIdentifier>;
private _onDragStart = this._register(new Emitter<IDraggedCompositeData>());
private _onDragEnd = this._register(new Emitter<IDraggedCompositeData>());
private static _instance: CompositeDragAndDropObserver | undefined;
static get INSTANCE(): CompositeDragAndDropObserver {
if (!CompositeDragAndDropObserver._instance) {
CompositeDragAndDropObserver._instance = new CompositeDragAndDropObserver();
}
return CompositeDragAndDropObserver._instance;
}
private constructor() {
super();
this.transferData = LocalSelectionTransfer.getInstance<DraggedCompositeIdentifier | DraggedViewIdentifier>();
}
private readDragData(type: ViewType): CompositeDragAndDropData | undefined {
if (this.transferData.hasData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype)) {
const data = this.transferData.getData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype);
if (data && data[0]) {
return new CompositeDragAndDropData(type, data[0].id);
}
}
return undefined;
}
private writeDragData(id: string, type: ViewType): void {
this.transferData.setData([type === 'view' ? new DraggedViewIdentifier(id) : new DraggedCompositeIdentifier(id)], type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype);
}
registerTarget(element: HTMLElement, callbacks: ICompositeDragAndDropObserverCallbacks): IDisposable {
const disposableStore = new DisposableStore();
disposableStore.add(new DragAndDropObserver(element, {
onDragEnd: e => {
// no-op
},
onDragEnter: e => {
e.preventDefault();
if (callbacks.onDragEnter) {
const data = this.readDragData('composite') || this.readDragData('view');
if (data) {
callbacks.onDragEnter({ eventData: e, dragAndDropData: data! });
}
}
},
onDragLeave: e => {
const data = this.readDragData('composite') || this.readDragData('view');
if (callbacks.onDragLeave && data) {
callbacks.onDragLeave({ eventData: e, dragAndDropData: data! });
}
},
onDrop: e => {
if (callbacks.onDrop) {
const data = this.readDragData('composite') || this.readDragData('view');
if (!data) {
return;
}
callbacks.onDrop({ eventData: e, dragAndDropData: data! });
// Fire drag event in case drop handler destroys the dragged element
this._onDragEnd.fire({ eventData: e, dragAndDropData: data! });
}
},
onDragOver: e => {
e.preventDefault();
if (callbacks.onDragOver) {
const data = this.readDragData('composite') || this.readDragData('view');
if (!data) {
return;
}
callbacks.onDragOver({ eventData: e, dragAndDropData: data! });
}
}
}));
if (callbacks.onDragStart) {
this._onDragStart.event(e => {
callbacks.onDragStart!(e);
}, this, disposableStore);
}
if (callbacks.onDragEnd) {
this._onDragEnd.event(e => {
callbacks.onDragEnd!(e);
});
}
return this._register(disposableStore);
}
registerDraggable(element: HTMLElement, type: ViewType, id: string, callbacks: ICompositeDragAndDropObserverCallbacks): IDisposable {
element.draggable = true;
const disposableStore = new DisposableStore();
disposableStore.add(addDisposableListener(element, EventType.DRAG_START, e => {
this.writeDragData(id, type);
this._onDragStart.fire({ eventData: e, dragAndDropData: this.readDragData(type)! });
}));
disposableStore.add(new DragAndDropObserver(element, {
onDragEnd: e => {
const data = this.readDragData(type);
if (data && data.getData().id === id) {
this.transferData.clearData(type === 'view' ? DraggedViewIdentifier.prototype : DraggedCompositeIdentifier.prototype);
}
if (!data) {
return;
}
this._onDragEnd.fire({ eventData: e, dragAndDropData: data! });
},
onDragEnter: e => {
if (callbacks.onDragEnter) {
const data = this.readDragData('composite') || this.readDragData('view');
if (!data) {
return;
}
if (data) {
callbacks.onDragEnter({ eventData: e, dragAndDropData: data! });
}
}
},
onDragLeave: e => {
const data = this.readDragData('composite') || this.readDragData('view');
if (!data) {
return;
}
if (callbacks.onDragLeave) {
callbacks.onDragLeave({ eventData: e, dragAndDropData: data! });
}
},
onDrop: e => {
if (callbacks.onDrop) {
const data = this.readDragData('composite') || this.readDragData('view');
if (!data) {
return;
}
callbacks.onDrop({ eventData: e, dragAndDropData: data! });
// Fire drag event in case drop handler destroys the dragged element
this._onDragEnd.fire({ eventData: e, dragAndDropData: data! });
}
},
onDragOver: e => {
if (callbacks.onDragOver) {
const data = this.readDragData('composite') || this.readDragData('view');
if (!data) {
return;
}
callbacks.onDragOver({ eventData: e, dragAndDropData: data! });
}
}
}));
if (callbacks.onDragStart) {
this._onDragStart.event(e => {
callbacks.onDragStart!(e);
}, this, disposableStore);
}
if (callbacks.onDragEnd) {
this._onDragEnd.event(e => {
callbacks.onDragEnd!(e);
});
}
return this._register(disposableStore);
}
}
......@@ -17,7 +17,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IDisposable, toDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction } from 'vs/workbench/browser/actions/layoutActions';
import { IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService';
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND, EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar';
import { Dimension, addClass, removeNode } from 'vs/base/browser/dom';
......@@ -39,6 +39,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { getMenuBarVisibility } from 'vs/platform/windows/common/windows';
import { isWeb } from 'vs/base/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { CompositeDragAndDropObserver } from 'vs/workbench/browser/dnd';
interface ICachedViewlet {
id: string;
......@@ -130,7 +131,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
hidePart: () => this.layoutService.setSideBarHidden(true),
dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Sidebar,
(id: string, focus?: boolean) => this.viewletService.openViewlet(id, focus),
(from: string, to: string) => this.compositeBar.move(from, to),
(from: string, to: string, before?: boolean) => this.compositeBar.move(from, to, before),
() => this.getPinnedViewletIds()
),
compositeSize: 50,
......@@ -298,6 +299,10 @@ export class ActivitybarPart extends Part implements IActivityBarService {
createContentArea(parent: HTMLElement): HTMLElement {
this.element = parent;
const overlay = document.createElement('div');
addClass(overlay, 'drag-overlay');
parent.appendChild(overlay);
this.content = document.createElement('div');
addClass(this.content, 'content');
parent.appendChild(this.content);
......@@ -317,6 +322,23 @@ export class ActivitybarPart extends Part implements IActivityBarService {
this.createGlobalActivityActionBar(globalActivities);
CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, {
onDragStart: e => {
overlay.style.backgroundColor = this.theme.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND, true)?.toString() || '';
overlay.style.opacity = '.8';
},
onDragEnd: e => {
overlay.style.opacity = '';
},
onDragEnter: e => {
overlay.style.opacity = '';
},
onDragLeave: e => {
overlay.style.backgroundColor = this.theme.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND, true)?.toString() || '';
overlay.style.opacity = '.8';
}
});
return this.content;
}
......@@ -337,6 +359,7 @@ export class ActivitybarPart extends Part implements IActivityBarService {
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : '';
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : '';
container.style.borderLeftColor = !isPositionLeft ? borderColor : '';
// container.style.outlineColor = this.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';
}
private getActivitybarItemColors(theme: IColorTheme): ICompositeBarColors {
......
......@@ -9,6 +9,26 @@
margin-bottom: 4px;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.top::before {
content: '';
width: 48px;
height: 2px;
display: block;
background-color: var(--insert-border-color);
margin-top: -3px;
margin-bottom: 1px;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.bottom::after {
content: '';
width: 48px;
height: 2px;
display: block;
background-color: var(--insert-border-color);
margin-top: 1px;
margin-bottom: -3px;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label {
position: relative;
z-index: 1;
......
......@@ -7,6 +7,17 @@
width: 48px;
}
.monaco-workbench .part > .drag-overlay {
transition-property: opacity;
transition-duration: .2s;
width: 100%;
height: 100%;
position: absolute;
opacity: 0;
top: 0;
pointer-events: none;
}
.monaco-workbench .activitybar > .content {
height: 100%;
display: flex;
......
......@@ -11,21 +11,19 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { IBadge } from 'vs/workbench/services/activity/common/activity';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors, DraggedCompositeIdentifier } from 'vs/workbench/browser/parts/compositeBarActions';
import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions';
import { Dimension, $, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Widget } from 'vs/base/browser/ui/widget';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { LocalSelectionTransfer, DragAndDropObserver } from 'vs/workbench/browser/dnd';
import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService';
import { Emitter } from 'vs/base/common/event';
import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { Registry } from 'vs/platform/registry/common/platform';
import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/base/parts/composite/browser/compositeDnd';
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
import { IComposite } from 'vs/workbench/common/composite';
import { CompositeDragAndDropData, CompositeDragAndDropObserver, IDraggedCompositeData, ICompositeDragAndDrop } from 'vs/workbench/browser/dnd';
export interface ICompositeBarItem {
id: string;
......@@ -41,30 +39,39 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
private viewDescriptorService: IViewDescriptorService,
private targetContainerLocation: ViewContainerLocation,
private openComposite: (id: string, focus?: boolean) => Promise<IPaneComposite | undefined>,
private moveComposite: (from: string, to: string) => void,
private moveComposite: (from: string, to: string, before?: boolean) => void,
private getVisibleCompositeIds: () => string[]
) { }
drop(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): void {
drop(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent, before?: boolean): void {
const dragData = data.getData();
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
if (dragData.type === 'composite') {
const currentContainer = viewContainerRegistry.get(dragData.id)!;
const currentLocation = viewContainerRegistry.getViewContainerLocation(currentContainer);
// Inserting a composite between composites
if (targetCompositeId) {
if (currentLocation !== this.targetContainerLocation && this.targetContainerLocation !== ViewContainerLocation.Panel) {
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
if (destinationContainer && !destinationContainer.rejectAddedViews) {
const viewsToMove = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.filter(vd => vd.canMoveView);
this.viewDescriptorService.moveViewsToContainer(viewsToMove, destinationContainer);
this.openComposite(targetCompositeId, true).then(composite => {
// ... on the same composite bar
if (currentLocation === this.targetContainerLocation) {
this.moveComposite(dragData.id, targetCompositeId, before);
}
// ... on a different composite bar
else {
const viewsToMove = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.filter(vd => vd.canMoveView);
if (viewsToMove.length === 1) {
this.viewDescriptorService.moveViewToLocation(viewsToMove[0], this.targetContainerLocation);
const newContainer = this.viewDescriptorService.getViewContainer(viewsToMove[0].id)!;
this.moveComposite(newContainer.id, targetCompositeId, before);
this.openComposite(newContainer.id, true).then(composite => {
if (composite && viewsToMove.length === 1) {
composite.openView(viewsToMove[0].id, true);
}
});
}
} else {
this.moveComposite(dragData.id, targetCompositeId);
}
} else {
const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer).allViewDescriptors;
......@@ -76,38 +83,24 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
}
if (dragData.type === 'view') {
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dragData.id);
if (viewDescriptor && viewDescriptor.canMoveView) {
if (targetCompositeId) {
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
if (destinationContainer && !destinationContainer.rejectAddedViews) {
if (this.targetContainerLocation === ViewContainerLocation.Sidebar || this.targetContainerLocation === ViewContainerLocation.Panel) {
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], destinationContainer);
this.openComposite(targetCompositeId, true).then(composite => {
if (composite) {
composite.openView(viewDescriptor.id, true);
}
});
} else {
this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.targetContainerLocation);
this.moveComposite(this.viewDescriptorService.getViewContainer(viewDescriptor.id)!.id, targetCompositeId);
}
}
} else {
this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.targetContainerLocation);
const newCompositeId = this.viewDescriptorService.getViewContainer(dragData.id)!.id;
const visibleItems = this.getVisibleCompositeIds();
const targetId = visibleItems.length ? visibleItems[visibleItems.length - 1] : undefined;
if (targetId && targetId !== newCompositeId) {
this.moveComposite(newCompositeId, targetId);
}
if (targetCompositeId) {
const viewToMove = this.viewDescriptorService.getViewDescriptor(dragData.id)!;
if (viewToMove && viewToMove.canMoveView) {
this.viewDescriptorService.moveViewToLocation(viewToMove, this.targetContainerLocation);
const newContainer = this.viewDescriptorService.getViewContainer(viewToMove.id)!;
this.moveComposite(newContainer.id, targetCompositeId, before);
this.openComposite(newCompositeId, true).then(composite => {
this.openComposite(newContainer.id, true).then(composite => {
if (composite) {
composite.openView(viewDescriptor.id, true);
composite.openView(viewToMove.id, true);
}
});
}
} else {
}
}
}
......@@ -129,41 +122,21 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
const currentContainer = viewContainerRegistry.get(dragData.id)!;
const currentLocation = viewContainerRegistry.getViewContainerLocation(currentContainer);
// ... to the same location
// ... to the same composite location
if (currentLocation === this.targetContainerLocation) {
return true;
}
// ... across view containers but without a destination composite
if (!targetCompositeId) {
const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors;
if (draggedViews.some(vd => !vd.canMoveView)) {
return false;
}
if (draggedViews.length !== 1) {
return false;
}
const defaultLocation = viewContainerRegistry.getViewContainerLocation(this.viewDescriptorService.getDefaultContainer(draggedViews[0].id)!);
if (this.targetContainerLocation === ViewContainerLocation.Sidebar && this.targetContainerLocation !== defaultLocation) {
return false;
}
return true;
}
// ... from panel to the sidebar
if (this.targetContainerLocation === ViewContainerLocation.Sidebar) {
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
return !!destinationContainer &&
!destinationContainer.rejectAddedViews &&
this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.some(vd => vd.canMoveView);
}
// ... from sidebar to the panel
else {
// ... to another composite location
const draggedViews = this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors;
if (draggedViews.length !== 1) {
return false;
}
// ... single view
const defaultContainer = this.viewDescriptorService.getDefaultContainer(draggedViews[0].id);
const canMoveToDefault = !!defaultContainer && this.viewDescriptorService.getViewContainerLocation(defaultContainer) === this.targetContainerLocation;
return !!draggedViews[0].canMoveView && (!!draggedViews[0].containerIcon || canMoveToDefault || this.targetContainerLocation === ViewContainerLocation.Panel);
} else {
// Dragging an individual view
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dragData.id);
......@@ -174,13 +147,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
}
// ... to create a view container
if (!targetCompositeId) {
return this.targetContainerLocation === ViewContainerLocation.Panel;
}
// ... into a destination
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
return !!destinationContainer && !destinationContainer.rejectAddedViews;
return this.targetContainerLocation === ViewContainerLocation.Panel || !!viewDescriptor.containerIcon;
}
}
}
......@@ -215,8 +182,6 @@ export class CompositeBar extends Widget implements ICompositeBar {
private visibleComposites: string[];
private compositeSizeInBar: Map<string, number>;
private compositeTransfer: LocalSelectionTransfer<DraggedCompositeIdentifier | DraggedViewIdentifier>;
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange = this._onDidChange.event;
......@@ -232,7 +197,6 @@ export class CompositeBar extends Widget implements ICompositeBar {
this.model = new CompositeBarModel(items, options);
this.visibleComposites = [];
this.compositeSizeInBar = new Map<string, number>();
this.compositeTransfer = LocalSelectionTransfer.getInstance<DraggedCompositeIdentifier>();
this.computeSizes(this.model.visibleItems);
}
......@@ -279,99 +243,21 @@ export class CompositeBar extends Widget implements ICompositeBar {
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e)));
// Allow to drop at the end to move composites to the end
this._register(new DragAndDropObserver(excessDiv, {
onDragOver: (e: DragEvent) => {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
EventHelper.stop(e, true);
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
if (Array.isArray(data)) {
const draggedCompositeId = data[0].id;
// Check if drop is allowed
if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e)) {
e.dataTransfer.dropEffect = 'none';
}
}
}
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
EventHelper.stop(e, true);
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
if (Array.isArray(data)) {
const draggedViewId = data[0].id;
// Check if drop is allowed
if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), undefined, e)) {
e.dataTransfer.dropEffect = 'none';
}
}
}
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(excessDiv, {
onDragEnter: (e: IDraggedCompositeData) => {
const pinnedItems = this.getPinnedComposites();
const validDropTarget = this.options.dndHandler.onDragEnter(e.dragAndDropData, pinnedItems[pinnedItems.length - 1].id, e.eventData);
this.updateFromDragging(excessDiv, validDropTarget);
},
onDragEnter: (e: DragEvent) => {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
EventHelper.stop(e, true);
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
if (Array.isArray(data)) {
const draggedCompositeId = data[0].id;
// Check if drop is allowed
const validDropTarget = this.options.dndHandler.onDragEnter(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e);
this.updateFromDragging(excessDiv, validDropTarget);
}
}
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
EventHelper.stop(e, true);
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
if (Array.isArray(data)) {
const draggedViewId = data[0].id;
// Check if drop is allowed
const validDropTarget = this.options.dndHandler.onDragEnter(new CompositeDragAndDropData('view', draggedViewId), undefined, e);
this.updateFromDragging(excessDiv, validDropTarget);
}
}
},
onDragLeave: (e: DragEvent) => {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) ||
this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
this.updateFromDragging(excessDiv, false);
}
},
onDragEnd: (e: DragEvent) => {
// no-op, will not be called
},
onDrop: (e: DragEvent) => {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
EventHelper.stop(e, true);
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
if (Array.isArray(data)) {
const draggedCompositeId = data[0].id;
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
this.options.dndHandler.drop(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e);
this.updateFromDragging(excessDiv, false);
}
}
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
if (Array.isArray(data)) {
const draggedViewId = data[0].id;
this.compositeTransfer.clearData(DraggedViewIdentifier.prototype);
this.options.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), undefined, e);
this.updateFromDragging(excessDiv, false);
}
}
onDragLeave: (e: IDraggedCompositeData) => {
this.updateFromDragging(excessDiv, false);
},
onDrop: (e: IDraggedCompositeData) => {
const pinnedItems = this.getPinnedComposites();
this.options.dndHandler.drop(e.dragAndDropData, pinnedItems[pinnedItems.length - 1].id, e.eventData, false);
this.updateFromDragging(excessDiv, false);
}
}));
return actionBarDiv;
......@@ -519,10 +405,34 @@ export class CompositeBar extends Widget implements ICompositeBar {
return item?.pinned;
}
move(compositeId: string, toCompositeId: string): void {
if (this.model.move(compositeId, toCompositeId)) {
// timeout helps to prevent artifacts from showing up
setTimeout(() => this.updateCompositeSwitcher(), 0);
move(compositeId: string, toCompositeId: string, before?: boolean): void {
if (before !== undefined) {
const fromIndex = this.model.items.findIndex(c => c.id === compositeId);
let toIndex = this.model.items.findIndex(c => c.id === toCompositeId);
if (fromIndex >= 0 && toIndex >= 0) {
if (!before && fromIndex > toIndex) {
toIndex++;
}
if (before && fromIndex < toIndex) {
toIndex--;
}
if (toIndex < this.model.items.length && toIndex >= 0 && toIndex !== fromIndex) {
if (this.model.move(this.model.items[fromIndex].id, this.model.items[toIndex].id)) {
// timeout helps to prevent artifacts from showing up
setTimeout(() => this.updateCompositeSwitcher(), 0);
}
}
}
} else {
if (this.model.move(compositeId, toCompositeId)) {
// timeout helps to prevent artifacts from showing up
setTimeout(() => this.updateCompositeSwitcher(), 0);
}
}
}
......
......@@ -18,10 +18,8 @@ import { DelayedDragHandler } from 'vs/base/browser/dnd';
import { IActivity } from 'vs/workbench/common/activity';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Emitter } from 'vs/base/common/event';
import { DragAndDropObserver, LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
import { LocalSelectionTransfer, DraggedCompositeIdentifier, DraggedViewIdentifier, CompositeDragAndDropObserver, ICompositeDragAndDrop } from 'vs/workbench/browser/dnd';
import { Color } from 'vs/base/common/color';
import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/base/parts/composite/browser/compositeDnd';
export interface ICompositeActivity {
badge: IBadge;
......@@ -167,11 +165,15 @@ export class ActivityActionViewItem extends BaseActionViewItem {
// Apply foreground color to activity bar items provided with codicons
this.label.style.color = foreground ? foreground.toString() : '';
}
const dragColor = colors.activeBackgroundColor || colors.activeForegroundColor;
this.container.style.setProperty('--insert-border-color', dragColor ? dragColor.toString() : '');
} else {
const foreground = this._action.checked ? colors.activeForegroundColor : colors.inactiveForegroundColor;
const borderBottomColor = this._action.checked ? colors.activeBorderBottomColor : null;
this.label.style.color = foreground ? foreground.toString() : '';
this.label.style.borderBottomColor = borderBottomColor ? borderBottomColor.toString() : '';
this.container.style.setProperty('--insert-border-color', colors.activeForegroundColor ? colors.activeForegroundColor.toString() : '');
}
}
......@@ -445,14 +447,6 @@ class ManageExtensionAction extends Action {
}
}
export class DraggedCompositeIdentifier {
constructor(private _compositeId: string) { }
get id(): string {
return this._compositeId;
}
}
export class CompositeActionViewItem extends ActivityActionViewItem {
private static manageExtensionAction: ManageExtensionAction;
......@@ -522,105 +516,38 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
this.showContextMenu(container);
}));
let insertDropBefore: boolean | undefined = undefined;
// Allow to drag
this._register(dom.addDisposableListener(this.container, dom.EventType.DRAG_START, (e: DragEvent) => {
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'move';
}
// Registe as dragged to local transfer
this.compositeTransfer.setData([new DraggedCompositeIdentifier(this.activity.id)], DraggedCompositeIdentifier.prototype);
// Trigger the action even on drag start to prevent clicks from failing that started a drag
if (!this.getAction().checked) {
this.getAction().run();
}
}));
this._register(new DragAndDropObserver(this.container, {
onDragEnter: e => {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
if (Array.isArray(data) && data[0].id !== this.activity.id) {
const validDropTarget = this.dndHandler.onDragEnter(new CompositeDragAndDropData('composite', data[0].id), this.activity.id, e);
this.updateFromDragging(container, validDropTarget);
}
}
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
if (Array.isArray(data) && data[0].id !== this.activity.id) {
const validDropTarget = this.dndHandler.onDragEnter(new CompositeDragAndDropData('view', data[0].id), this.activity.id, e);
this.updateFromDragging(container, validDropTarget);
}
}
},
this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(this.container, 'composite', this.activity.id, {
onDragOver: e => {
dom.EventHelper.stop(e, true);
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
if (Array.isArray(data)) {
const draggedCompositeId = data[0].id;
if (draggedCompositeId !== this.activity.id) {
if (e.dataTransfer && !this.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), this.activity.id, e)) {
e.dataTransfer.dropEffect = 'none';
}
}
}
}
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
if (Array.isArray(data)) {
const draggedViewId = data[0].id;
if (e.dataTransfer && !this.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), this.activity.id, e)) {
e.dataTransfer.dropEffect = 'none';
}
}
}
const isValidMove = e.dragAndDropData.getData().id !== this.activity.id && this.dndHandler.onDragOver(e.dragAndDropData, this.activity.id, e.eventData);
insertDropBefore = this.updateFromDragging(container, isValidMove, e.eventData);
},
onDragLeave: e => {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) ||
this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
this.updateFromDragging(container, false);
}
insertDropBefore = this.updateFromDragging(container, false, e.eventData);
},
onDragEnd: e => {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
this.updateFromDragging(container, false);
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
}
insertDropBefore = this.updateFromDragging(container, false, e.eventData);
},
onDrop: e => {
dom.EventHelper.stop(e, true);
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
if (Array.isArray(data)) {
const draggedCompositeId = data[0].id;
if (draggedCompositeId !== this.activity.id) {
this.updateFromDragging(container, false);
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
this.dndHandler.drop(new CompositeDragAndDropData('composite', draggedCompositeId), this.activity.id, e);
}
}
this.dndHandler.drop(e.dragAndDropData, this.activity.id, e.eventData, !!insertDropBefore);
insertDropBefore = this.updateFromDragging(container, false, e.eventData);
},
onDragStart: e => {
if (e.dragAndDropData.getData().id !== this.activity.id) {
return;
}
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
if (Array.isArray(data)) {
const draggedViewId = data[0].id;
this.updateFromDragging(container, false);
this.compositeTransfer.clearData(DraggedViewIdentifier.prototype);
if (e.eventData.dataTransfer) {
e.eventData.dataTransfer.effectAllowed = 'move';
}
this.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), this.activity.id, e);
}
// Trigger the action even on drag start to prevent clicks from failing that started a drag
if (!this.getAction().checked) {
this.getAction().run();
}
}
}));
......@@ -637,11 +564,42 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
this.updateStyles();
}
private updateFromDragging(element: HTMLElement, isDragging: boolean): void {
const theme = this.themeService.getColorTheme();
const dragBackground = this.options.colors(theme).dragAndDropBackground;
private updateFromDragging(element: HTMLElement, showFeedback: boolean, event: DragEvent): boolean | undefined {
const rect = element.getBoundingClientRect();
const posX = event.clientX;
const posY = event.clientY;
const height = rect.bottom - rect.top;
const width = rect.right - rect.left;
const forceTop = posY <= rect.top + height * 0.4;
const forceBottom = posY > rect.bottom - height * 0.4;
const preferTop = posY <= rect.top + height * 0.5;
const forceLeft = posX <= rect.left + width * 0.4;
const forceRight = posX > rect.right - width * 0.4;
const preferLeft = posX <= rect.left + width * 0.5;
const classes = element.classList;
const lastClasses = {
vertical: classes.contains('top') ? 'top' : (classes.contains('bottom') ? 'bottom' : undefined),
horizontal: classes.contains('left') ? 'left' : (classes.contains('right') ? 'right' : undefined)
};
const top = forceTop || (preferTop && !lastClasses.vertical) || (!forceBottom && lastClasses.vertical === 'top');
const bottom = forceBottom || (!preferTop && !lastClasses.vertical) || (!forceTop && lastClasses.vertical === 'bottom');
const left = forceLeft || (preferLeft && !lastClasses.horizontal) || (!forceRight && lastClasses.horizontal === 'left');
const right = forceRight || (!preferLeft && !lastClasses.horizontal) || (!forceLeft && lastClasses.horizontal === 'right');
dom.toggleClass(element, 'top', showFeedback && top);
dom.toggleClass(element, 'bottom', showFeedback && bottom);
dom.toggleClass(element, 'left', showFeedback && left);
dom.toggleClass(element, 'right', showFeedback && right);
if (!showFeedback) {
return undefined;
}
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : '';
return top || left;
}
private showContextMenu(container: HTMLElement): void {
......
......@@ -22,8 +22,9 @@
opacity: 0; /* hidden initially */
transition: opacity 150ms ease-out;
/* color: red; */
}
#monaco-workbench-editor-drop-overlay > .editor-group-overlay-indicator.overlay-move-transition {
transition: top 70ms ease-out, left 70ms ease-out, width 70ms ease-out, height 70ms ease-out, opacity 150ms ease-out;
}
\ No newline at end of file
}
......@@ -85,6 +85,26 @@
display: flex;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.left::before {
content: '';
width: 2px;
height: 35px;
display: block;
background-color: var(--insert-border-color);
margin-left: -11px;
margin-right: 9px;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.right::after {
content: '';
width: 2px;
height: 35px;
display: block;
background-color: var(--insert-border-color);
margin-right: -11px;
margin-left: 9px;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item .action-label{
margin-right: 0;
}
......
......@@ -20,13 +20,13 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, PositionPanelActionConfigs, SetPanelPositionAction } from 'vs/workbench/browser/parts/panel/panelActions';
import { IThemeService, registerThemingParticipant, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND, PANEL_INPUT_BORDER } from 'vs/workbench/common/theme';
import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND, PANEL_INPUT_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry';
import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar';
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
import { IBadge } from 'vs/workbench/services/activity/common/activity';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Dimension, trackFocus } from 'vs/base/browser/dom';
import { Dimension, trackFocus, addClass } from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
......@@ -37,6 +37,7 @@ import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExte
import { MenuId } from 'vs/platform/actions/common/actions';
import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
import { CompositeDragAndDropObserver } from 'vs/workbench/browser/dnd';
interface ICachedPanel {
id: string;
......@@ -145,7 +146,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
hidePart: () => this.layoutService.setPanelHidden(true),
dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Panel,
(id: string, focus?: boolean) => this.openPanel(id, focus) as Promise<IPaneComposite | undefined>,
(from: string, to: string) => this.compositeBar.move(from, to),
(from: string, to: string, before?: boolean) => this.compositeBar.move(from, to, before),
() => this.getPinnedPanels().map(p => p.id)
),
compositeSize: 0,
......@@ -341,6 +342,31 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
super.create(parent);
const overlay = document.createElement('div');
addClass(overlay, 'drag-overlay');
parent.appendChild(overlay);
CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, {
onDragStart: e => {
// this.element.style.outline = `1px solid`;
// this.element.style.outlineOffset = '-1px';
overlay.style.backgroundColor = this.theme.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND, true)?.toString() || '';
overlay.style.opacity = '.8';
},
onDragEnd: e => {
// this.element.style.outline = '';
overlay.style.opacity = '';
},
onDragEnter: e => {
overlay.style.opacity = '';
},
onDragLeave: e => {
overlay.style.backgroundColor = this.theme.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND, true)?.toString() || '';
overlay.style.opacity = '.8';
}
});
const focusTracker = this._register(trackFocus(parent));
this._register(focusTracker.onDidFocus(() => this.panelFocusContextKey.set(true)));
this._register(focusTracker.onDidBlur(() => this.panelFocusContextKey.set(false)));
......
......@@ -23,9 +23,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { Event, Emitter } from 'vs/base/common/event';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER } from 'vs/workbench/common/theme';
import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER, SIDE_BAR_DRAG_AND_DROP_BACKGROUND, EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { EventType, addDisposableListener, trackFocus } from 'vs/base/browser/dom';
import { EventType, addDisposableListener, trackFocus, addClass } from 'vs/base/browser/dom';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
......@@ -33,9 +33,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { LayoutPriority } from 'vs/base/browser/ui/grid/grid';
import { assertIsDefined } from 'vs/base/common/types';
import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { DraggedCompositeIdentifier } from 'vs/workbench/browser/parts/compositeBarActions';
import { LocalSelectionTransfer, CompositeDragAndDropObserver, DraggedViewIdentifier, DraggedCompositeIdentifier } from 'vs/workbench/browser/dnd';
export class SidebarPart extends CompositePart<Viewlet> implements IViewletService {
......@@ -154,6 +152,28 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
super.create(parent);
const overlay = document.createElement('div');
addClass(overlay, 'drag-overlay');
parent.appendChild(overlay);
CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, {
onDragStart: e => {
overlay.style.backgroundColor = this.theme.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND, true)?.toString() || '';
overlay.style.opacity = '.8';
},
onDragEnd: e => {
// this.element.style.outline = '';
overlay.style.opacity = '';
},
onDragEnter: e => {
overlay.style.opacity = '';
},
onDragLeave: e => {
overlay.style.backgroundColor = this.theme.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND, true)?.toString() || '';
overlay.style.opacity = '.8';
}
});
const focusTracker = this._register(trackFocus(parent));
this._register(focusTracker.onDidFocus(() => this.sideBarFocusContextKey.set(true)));
this._register(focusTracker.onDidBlur(() => this.sideBarFocusContextKey.set(false)));
......@@ -209,6 +229,7 @@ export class SidebarPart extends CompositePart<Viewlet> implements IViewletServi
container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : '';
container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : '';
container.style.borderLeftColor = !isPositionLeft ? borderColor || '' : '';
container.style.outlineColor = this.getColor(SIDE_BAR_DRAG_AND_DROP_BACKGROUND) ?? '';
}
layout(width: number, height: number): void {
......
......@@ -6,9 +6,9 @@
import 'vs/css!./media/paneviewlet';
import * as nls from 'vs/nls';
import { Event, Emitter } from 'vs/base/common/event';
import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry';
import { ColorIdentifier, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { attachStyler, IColorMapping, attachButtonStyler, attachLinkStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { SIDE_BAR_DRAG_AND_DROP_BACKGROUND, SIDE_BAR_SECTION_HEADER_FOREGROUND, SIDE_BAR_SECTION_HEADER_BACKGROUND, SIDE_BAR_SECTION_HEADER_BORDER, PANEL_BACKGROUND, SIDE_BAR_BACKGROUND, EDITOR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
import { append, $, trackFocus, toggleClass, EventType, isAncestor, Dimension, addDisposableListener, removeClass, addClass } from 'vs/base/browser/dom';
import { IDisposable, combinedDisposable, dispose, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { firstIndex } from 'vs/base/common/arrays';
......@@ -20,8 +20,8 @@ import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { PaneView, IPaneViewOptions, IPaneOptions, Pane, DefaultPaneDndController } from 'vs/base/browser/ui/splitview/paneview';
import { IThemeService, Themable } from 'vs/platform/theme/common/themeService';
import { PaneView, IPaneViewOptions, IPaneOptions, Pane } from 'vs/base/browser/ui/splitview/paneview';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
......@@ -42,11 +42,12 @@ import { parseLinkedText } from 'vs/base/common/linkedText';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { Button } from 'vs/base/browser/ui/button/button';
import { Link } from 'vs/platform/opener/browser/link';
import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
import { CompositeDragAndDropObserver, DragAndDropObserver } from 'vs/workbench/browser/dnd';
import { Orientation } from 'vs/base/browser/ui/sash/sash';
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
import { CompositeProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator';
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
import { RunOnceScheduler } from 'vs/base/common/async';
export interface IPaneColors extends IColorMapping {
dropBackground?: ColorIdentifier;
......@@ -61,14 +62,6 @@ export interface IViewPaneOptions extends IPaneOptions {
titleMenuId?: MenuId;
}
export class DraggedViewIdentifier {
constructor(private _viewId: string) { }
get id(): string {
return this._viewId;
}
}
type WelcomeActionClassification = {
viewId: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
uri: { classification: 'SystemMetaData', purpose: 'FeatureInsight' };
......@@ -508,6 +501,210 @@ interface IViewPaneItem {
disposable: IDisposable;
}
const enum DropDirection {
UP,
DOWN,
LEFT,
RIGHT
}
class ViewPaneDropOverlay extends Themable {
private static readonly OVERLAY_ID = 'monaco-workbench-pane-drop-overlay';
private container!: HTMLElement;
private overlay!: HTMLElement;
private _currentDropOperation: DropDirection | undefined;
// private currentDropOperation: IDropOperation | undefined;
private _disposed: boolean | undefined;
private cleanupOverlayScheduler: RunOnceScheduler;
get currentDropOperation(): DropDirection | undefined {
return this._currentDropOperation;
}
constructor(
private paneElement: HTMLElement,
private orientation: Orientation,
protected themeService: IThemeService
) {
super(themeService);
this.cleanupOverlayScheduler = this._register(new RunOnceScheduler(() => this.dispose(), 300));
this.create();
}
get disposed(): boolean {
return !!this._disposed;
}
private create(): void {
// Container
this.container = document.createElement('div');
this.container.id = ViewPaneDropOverlay.OVERLAY_ID;
// Parent
this.paneElement.appendChild(this.container);
addClass(this.paneElement, 'dragged-over');
this._register(toDisposable(() => {
this.paneElement.removeChild(this.container);
removeClass(this.paneElement, 'dragged-over');
}));
// Overlay
this.overlay = document.createElement('div');
addClass(this.overlay, 'pane-overlay-indicator');
this.container.appendChild(this.overlay);
// Overlay Event Handling
this.registerListeners();
// Styles
this.updateStyles();
}
protected updateStyles(): void {
// Overlay drop background
this.overlay.style.backgroundColor = this.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND) || '';
// Overlay contrast border (if any)
const activeContrastBorderColor = this.getColor(activeContrastBorder);
this.overlay.style.outlineColor = activeContrastBorderColor || '';
this.overlay.style.outlineOffset = activeContrastBorderColor ? '-2px' : '';
this.overlay.style.outlineStyle = activeContrastBorderColor ? 'dashed' : '';
this.overlay.style.outlineWidth = activeContrastBorderColor ? '2px' : '';
this.overlay.style.borderColor = activeContrastBorderColor || '';
this.overlay.style.borderStyle = 'solid' || '';
}
private registerListeners(): void {
this._register(new DragAndDropObserver(this.container, {
onDragEnter: e => undefined,
onDragOver: e => {
// Position overlay
this.positionOverlay(e.offsetX, e.offsetY);
// Make sure to stop any running cleanup scheduler to remove the overlay
if (this.cleanupOverlayScheduler.isScheduled()) {
this.cleanupOverlayScheduler.cancel();
}
},
onDragLeave: e => this.dispose(),
onDragEnd: e => this.dispose(),
onDrop: e => {
// Dispose overlay
this.dispose();
}
}));
this._register(addDisposableListener(this.container, EventType.MOUSE_OVER, () => {
// Under some circumstances we have seen reports where the drop overlay is not being
// cleaned up and as such the editor area remains under the overlay so that you cannot
// type into the editor anymore. This seems related to using VMs and DND via host and
// guest OS, though some users also saw it without VMs.
// To protect against this issue we always destroy the overlay as soon as we detect a
// mouse event over it. The delay is used to guarantee we are not interfering with the
// actual DROP event that can also trigger a mouse over event.
if (!this.cleanupOverlayScheduler.isScheduled()) {
this.cleanupOverlayScheduler.schedule();
}
}));
}
private positionOverlay(mousePosX: number, mousePosY: number): void {
const paneWidth = this.paneElement.clientWidth;
const paneHeight = this.paneElement.clientHeight;
const splitWidthThreshold = paneWidth / 2;
const splitHeightThreshold = paneHeight / 2;
let dropDirection: DropDirection | undefined;
if (this.orientation === Orientation.VERTICAL) {
if (mousePosY < splitHeightThreshold) {
dropDirection = DropDirection.UP;
} else if (mousePosY >= splitHeightThreshold) {
dropDirection = DropDirection.DOWN;
}
} else {
if (mousePosX < splitWidthThreshold) {
dropDirection = DropDirection.LEFT;
} else if (mousePosX >= splitWidthThreshold) {
dropDirection = DropDirection.RIGHT;
}
}
// Draw overlay based on split direction
switch (dropDirection) {
case DropDirection.UP:
this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '50%' });
break;
case DropDirection.DOWN:
this.doPositionOverlay({ bottom: '0', left: '0', width: '100%', height: '50%' });
break;
case DropDirection.LEFT:
this.doPositionOverlay({ top: '0', left: '0', width: '50%', height: '100%' });
break;
case DropDirection.RIGHT:
this.doPositionOverlay({ top: '0', right: '0', width: '50%', height: '100%' });
break;
default:
this.doPositionOverlay({ top: '0', left: '0', width: '100%', height: '100%' });
}
this.doUpdateOverlayBorder(dropDirection);
// Make sure the overlay is visible now
this.overlay.style.opacity = '1';
// Enable transition after a timeout to prevent initial animation
setTimeout(() => addClass(this.overlay, 'overlay-move-transition'), 0);
// Remember as current split direction
this._currentDropOperation = dropDirection;
}
private doUpdateOverlayBorder(direction: DropDirection | undefined): void {
this.overlay.style.borderTopWidth = direction === DropDirection.UP ? '2px' : '0px';
this.overlay.style.borderLeftWidth = direction === DropDirection.LEFT ? '2px' : '0px';
this.overlay.style.borderBottomWidth = direction === DropDirection.DOWN ? '2px' : '0px';
this.overlay.style.borderRightWidth = direction === DropDirection.RIGHT ? '2px' : '0px';
}
private doPositionOverlay(options: { top?: string, bottom?: string, left?: string, right?: string, width: string, height: string }): void {
// Container
this.container.style.height = '100%';
// Overlay
this.overlay.style.top = options.top || '';
this.overlay.style.left = options.left || '';
this.overlay.style.bottom = options.bottom || '';
this.overlay.style.right = options.right || '';
this.overlay.style.width = options.width;
this.overlay.style.height = options.height;
}
contains(element: HTMLElement): boolean {
return element === this.container || element === this.overlay;
}
dispose(): void {
super.dispose();
this._disposed = true;
}
}
export class ViewPaneContainer extends Component implements IViewPaneContainer {
readonly viewContainer: ViewContainer;
......@@ -515,8 +712,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
private paneItems: IViewPaneItem[] = [];
private paneview?: PaneView;
private static viewTransfer = LocalSelectionTransfer.getInstance<DraggedViewIdentifier>();
private visible: boolean = false;
private areExtensionsReady: boolean = false;
......@@ -583,10 +778,6 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
throw new Error('Could not find container');
}
// Use default pane dnd controller if not specified
if (!this.options.dnd) {
this.options.dnd = new DefaultPaneDndController();
}
this.viewContainer = container;
this.visibleViewsStorageId = `${id}.numberOfVisibleViews`;
......@@ -949,19 +1140,104 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
this.paneItems.splice(index, 0, paneItem);
assertIsDefined(this.paneview).addPane(pane, size, index);
this._register(addDisposableListener(pane.draggableElement, EventType.DRAG_START, (e: DragEvent) => {
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'move';
}
let overlay: ViewPaneDropOverlay | undefined;
// Register as dragged to local transfer
ViewPaneContainer.viewTransfer.setData([new DraggedViewIdentifier(pane.id)], DraggedViewIdentifier.prototype);
}));
this._register(CompositeDragAndDropObserver.INSTANCE.registerDraggable(pane.draggableElement, 'view', pane.id, {}));
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(pane.dropTargetElement, {
onDragEnter: (e) => {
if (!overlay) {
const dropData = e.dragAndDropData.getData();
if (dropData.type === 'view' && dropData.id !== pane.id) {
const oldViewContainer = this.viewDescriptorService.getViewContainer(dropData.id);
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dropData.id);
if (oldViewContainer !== this.viewContainer && (!viewDescriptor || !viewDescriptor.canMoveView)) {
return;
}
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.options.orientation ?? Orientation.VERTICAL, this.themeService);
}
if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) {
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
const container = viewContainerRegistry.get(dropData.id)!;
const viewsToMove = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors;
if (viewsToMove.length === 1 && viewsToMove[0].canMoveView) {
overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.options.orientation ?? Orientation.VERTICAL, this.themeService);
}
}
}
},
onDragLeave: (e) => {
overlay?.dispose();
overlay = undefined;
},
onDrop: (e) => {
if (overlay) {
const dropData = e.dragAndDropData.getData();
if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) {
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
const container = viewContainerRegistry.get(dropData.id)!;
const viewsToMove = this.viewDescriptorService.getViewDescriptors(container).allViewDescriptors;
if (viewsToMove.length === 1 && viewsToMove[0].canMoveView) {
dropData.type = 'view';
dropData.id = viewsToMove[0].id;
}
}
if (dropData.type === 'view') {
const oldViewContainer = this.viewDescriptorService.getViewContainer(dropData.id);
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dropData.id);
if (oldViewContainer !== this.viewContainer && viewDescriptor && viewDescriptor.canMoveView) {
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], this.viewContainer);
}
if (overlay.currentDropOperation === DropDirection.DOWN ||
overlay.currentDropOperation === DropDirection.RIGHT) {
const fromIndex = this.panes.findIndex(p => p.id === dropData.id);
let toIndex = this.panes.findIndex(p => p.id === pane.id);
if (fromIndex >= 0 && toIndex >= 0) {
if (fromIndex > toIndex) {
toIndex++;
}
if (toIndex < this.panes.length && toIndex !== fromIndex) {
this.movePane(this.panes[fromIndex], this.panes[toIndex]);
}
}
}
if (overlay.currentDropOperation === DropDirection.UP ||
overlay.currentDropOperation === DropDirection.LEFT) {
const fromIndex = this.panes.findIndex(p => p.id === dropData.id);
let toIndex = this.panes.findIndex(p => p.id === pane.id);
if (fromIndex >= 0 && toIndex >= 0) {
if (fromIndex < toIndex) {
toIndex--;
}
if (toIndex >= 0 && toIndex !== fromIndex) {
this.movePane(this.panes[fromIndex], this.panes[toIndex]);
}
}
}
}
}
this._register(addDisposableListener(pane.draggableElement, EventType.DRAG_END, (e: DragEvent) => {
if (ViewPaneContainer.viewTransfer.hasData(DraggedViewIdentifier.prototype)) {
ViewPaneContainer.viewTransfer.clearData(DraggedViewIdentifier.prototype);
overlay?.dispose();
overlay = undefined;
}
}));
}
......
......@@ -445,11 +445,6 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
}
moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void {
const previousContainer = this.getViewContainer(view.id);
if (previousContainer && this.getViewContainerLocation(previousContainer) === location) {
return;
}
let container = this.getDefaultContainer(view.id)!;
if (this.getViewContainerLocation(container) !== location) {
container = this.registerViewContainerForSingleView(view, location);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册