Merge from vscode 61d5f2b82f17bf9f99f56405204caab88a7e8747

This commit is contained in:
ADS Merger
2020-03-19 06:57:07 +00:00
parent 03ce5d1ba7
commit 84f67f61c4
137 changed files with 13234 additions and 796 deletions

View File

@@ -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);
}
}

View File

@@ -8,6 +8,19 @@
overflow: hidden;
}
.monaco-workbench .part > .drop-block-overlay {
visibility: hidden; /* use visibility to ensure transitions */
transition-property: opacity;
transition-timing-function: linear;
transition-duration: 250ms;
width: 100%;
height: 100%;
position: absolute;
top: 0;
opacity: 0;
pointer-events: none;
}
.monaco-workbench .part > .title {
display: none; /* Parts have to opt in to show title area */
}

View File

@@ -130,8 +130,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),
() => this.getPinnedViewletIds()
(from: string, to: string, before?: boolean) => this.compositeBar.move(from, to, before)
),
compositeSize: 50,
colors: (theme: IColorTheme) => this.getActivitybarItemColors(theme),
@@ -337,6 +336,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 {

View File

@@ -9,6 +9,42 @@
margin-bottom: 4px;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item::before,
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item::after {
position: absolute;
content: '';
width: 48px;
height: 2px;
display: block;
background-color: var(--insert-border-color);
opacity: 0;
transition-property: opacity;
transition-duration: 0ms;
transition-delay: 100ms;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item::before {
margin-top: -3px;
margin-bottom: 1px;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item::after {
margin-top: 1px;
margin-bottom: -3px;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.top::before,
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.top::after,
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.bottom::before,
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.bottom::after {
transition-delay: 0s;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.top::before,
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-item.bottom::after {
opacity: 1;
}
.monaco-workbench .activitybar > .content :not(.monaco-menu) > .monaco-action-bar .action-label {
position: relative;
z-index: 1;

View File

@@ -7,6 +7,12 @@
width: 48px;
}
.monaco-workbench .part > .drop-block-overlay.visible {
visibility: visible;
backdrop-filter: brightness(97%) blur(2px);
opacity: 1;
}
.monaco-workbench .activitybar > .content {
height: 100%;
display: flex;

View File

@@ -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,38 @@ 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 getVisibleCompositeIds: () => string[]
private moveComposite: (from: string, to: string, before?: boolean) => void,
) { }
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 +82,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)!;
this.openComposite(newCompositeId, true).then(composite => {
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(newContainer.id, true).then(composite => {
if (composite) {
composite.openView(viewDescriptor.id, true);
composite.openView(viewToMove.id, true);
}
});
}
} else {
}
}
}
@@ -129,41 +121,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 +146,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 +181,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 +196,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);
}
@@ -278,100 +241,25 @@ export class CompositeBar extends Widget implements ICompositeBar {
// Contextmenu for composites
this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e)));
// Register a drop target on the whole bar to prevent forbidden feedback
this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(parent, {}));
// 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 +407,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);
}
}
}

View File

@@ -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;
element.style.backgroundColor = isDragging && dragBackground ? dragBackground.toString() : '';
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;
}
return top || left;
}
private showContextMenu(container: HTMLElement): void {

View File

@@ -32,6 +32,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { MementoObject } from 'vs/workbench/common/memento';
import { assertIsDefined } from 'vs/base/common/types';
import { IBoundarySashes } from 'vs/base/browser/ui/grid/gridview';
import { CompositeDragAndDropObserver } from 'vs/workbench/browser/dnd';
interface IEditorPartUIState {
serializedGrid: ISerializedGrid;
@@ -826,6 +827,20 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro
// Drop support
this._register(this.createEditorDropTarget(this.container, {}));
// No drop in the editor
const overlay = document.createElement('div');
addClass(overlay, 'drop-block-overlay');
parent.appendChild(overlay);
CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, {
onDragStart: e => {
toggleClass(overlay, 'visible', true);
},
onDragEnd: e => {
toggleClass(overlay, 'visible', false);
}
});
return this.container;
}

View File

@@ -3,8 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./media/editorquickaccess';
import { localize } from 'vs/nls';
import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource, IQuickPick } from 'vs/platform/quickinput/common/quickInput';
import { IQuickPickSeparator, quickPickItemScorerAccessor, IQuickPickItemWithResource } from 'vs/platform/quickinput/common/quickInput';
import { PickerQuickAccessProvider, IPickerQuickAccessItem, TriggerAction } from 'vs/platform/quickinput/browser/pickerQuickAccess';
import { IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { EditorsOrder, IEditorIdentifier, toResource, SideBySideEditor } from 'vs/workbench/common/editor';
@@ -25,18 +26,14 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro
@IModelService private readonly modelService: IModelService,
@IModeService private readonly modeService: IModeService
) {
super(prefix);
}
protected configure(picker: IQuickPick<IEditorQuickPickItem>): void {
// Allow to open editors in background without closing picker
picker.canAcceptInBackground = true;
super(prefix, { canAcceptInBackground: true });
}
protected getPicks(filter: string): Array<IEditorQuickPickItem | IQuickPickSeparator> {
const query = prepareQuery(filter);
const scorerCache = Object.create(null);
// Filtering
const filteredEditorEntries = this.doGetEditorPickItems().filter(entry => {
if (!query.value) {
return true;
@@ -102,11 +99,11 @@ export abstract class BaseEditorQuickAccessProvider extends PickerQuickAccessPro
description: editor.getDescription(),
iconClasses: getIconClasses(this.modelService, this.modeService, resource),
italic: !this.editorGroupService.getGroup(groupId)?.isPinned(editor),
buttonsAlwaysVisible: isDirty,
buttons: [
{
iconClass: isDirty ? 'codicon-circle-filled' : 'codicon-close',
tooltip: localize('closeEditor', "Close Editor")
iconClass: isDirty ? 'dirty-editor codicon-circle-filled' : 'codicon-close',
tooltip: localize('closeEditor', "Close Editor"),
alwaysVisible: isDirty
}
],
trigger: async () => {

View File

@@ -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;
}
}

View File

@@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.quick-input-list .quick-input-list-entry.has-actions:hover .quick-input-list-entry-action-bar .action-label.dirty-editor::before {
content: "\ea76"; /* Close icon flips between black dot and "X" for dirty open editors */
}

View File

@@ -85,6 +85,40 @@
display: flex;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::before,
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::after {
content: '';
width: 2px;
display: block;
background-color: var(--insert-border-color);
opacity: 0;
transition-property: opacity;
transition-duration: 0ms;
transition-delay: 100ms;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::before {
margin-left: -11px;
margin-right: 9px;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item::after {
margin-right: -11px;
margin-left: 9px;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.right::before,
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.left::after,
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.left::before,
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.right::after {
transition-delay: 0s;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.left::before,
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item.right::after {
opacity: 1;
}
.monaco-workbench .part.panel > .composite.title> .panel-switcher-container > .monaco-action-bar .action-item .action-label{
margin-right: 0;
}

View File

@@ -144,9 +144,8 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(),
hidePart: () => this.layoutService.setPanelHidden(true),
dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Panel,
(id: string, focus?: boolean) => (<unknown>this.openPanel(id, focus)) as Promise<IPaneComposite | undefined>, // {{SQL CARBON EDIT}} strict-null-check
(from: string, to: string) => this.compositeBar.move(from, to),
() => this.getPinnedPanels().map(p => p.id)
(id: string, focus?: boolean) => <unknown>this.openPanel(id, focus) as Promise<IPaneComposite | undefined>, // {{SQL CARBON EDIT}} strict-null-checks
(from: string, to: string, before?: boolean) => this.compositeBar.move(from, to, before)
),
compositeSize: 0,
overflowActionSize: 44,

View File

@@ -23,7 +23,7 @@ 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 } from 'vs/workbench/common/theme';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { EventType, addDisposableListener, trackFocus } from 'vs/base/browser/dom';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
@@ -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, DraggedViewIdentifier, DraggedCompositeIdentifier } from 'vs/workbench/browser/dnd';
export class SidebarPart extends CompositePart<Viewlet> implements IViewletService {
@@ -209,6 +207,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 {

View File

@@ -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) {
this._register(addDisposableListener(pane.draggableElement, EventType.DRAG_END, (e: DragEvent) => {
if (ViewPaneContainer.viewTransfer.hasData(DraggedViewIdentifier.prototype)) {
ViewPaneContainer.viewTransfer.clearData(DraggedViewIdentifier.prototype);
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]);
}
}
}
}
}
overlay?.dispose();
overlay = undefined;
}
}));
}

View File

@@ -130,7 +130,7 @@ class BrowserMain extends Disposable {
}
private restoreBaseTheme(): void {
addClass(this.domElement, window.localStorage.getItem('vscode.baseTheme') || getThemeTypeSelector(DARK));
addClass(this.domElement, window.localStorage.getItem('vscode.baseTheme') || getThemeTypeSelector(LIGHT) /* Fallback to a light theme by default on web */);
}
private saveBaseTheme(): void {