Skip to content

Commit

Permalink
Merge pull request #30197 from Microsoft/sandy081/viewsDragAndDrop
Browse files Browse the repository at this point in the history
Support reordering of views in split view by drag and drop
  • Loading branch information
sandy081 committed Jul 7, 2017
2 parents 90a99c7 + 788f260 commit 119513f
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 11 deletions.
140 changes: 133 additions & 7 deletions src/vs/base/browser/ui/splitview/splitview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export enum ViewSizing {

export interface IOptions {
orientation?: Orientation; // default Orientation.VERTICAL
canChangeOrderByDragAndDrop?: boolean;
}

export interface ISashEvent {
Expand All @@ -48,6 +49,7 @@ export interface IView extends ee.IEventEmitter {
fixedSize: number;
minimumSize: number;
maximumSize: number;
draggableElement: HTMLElement;
render(container: HTMLElement, orientation: Orientation): void;
layout(size: number, orientation: Orientation): void;
focus(): void;
Expand All @@ -70,6 +72,7 @@ export abstract class View extends ee.EventEmitter implements IView {
protected _sizing: ViewSizing;
protected _fixedSize: number;
protected _minimumSize: number;
protected _draggableElement: HTMLElement = null;

constructor(opts: IViewOptions) {
super();
Expand All @@ -84,6 +87,7 @@ export abstract class View extends ee.EventEmitter implements IView {
get fixedSize(): number { return this._fixedSize; }
get minimumSize(): number { return this.sizing === ViewSizing.Fixed ? this.fixedSize : this._minimumSize; }
get maximumSize(): number { return this.sizing === ViewSizing.Fixed ? this.fixedSize : Number.POSITIVE_INFINITY; }
get draggableElement(): HTMLElement { return this._draggableElement; }

protected setFlexible(size?: number): void {
this._sizing = ViewSizing.Flexible;
Expand Down Expand Up @@ -165,6 +169,7 @@ export abstract class HeaderView extends View {
render(container: HTMLElement, orientation: Orientation): void {
this.header = document.createElement('div');
this.header.className = 'header';
this._draggableElement = this.header;

let headerSize = this.headerSize + 'px';

Expand Down Expand Up @@ -476,10 +481,15 @@ function sum(arr: number[]): number {
return arr.reduce((a, b) => a + b);
}

export class SplitView implements
export interface SplitViewStyles {
dropBackground?: Color;
}

export class SplitView extends lifecycle.Disposable implements
sash.IHorizontalSashLayoutProvider,
sash.IVerticalSashLayoutProvider {
private orientation: Orientation;
private canDragAndDrop: boolean;
private el: HTMLElement;
private size: number;
private viewElements: HTMLElement[];
Expand All @@ -488,6 +498,7 @@ export class SplitView implements
private viewFocusPreviousListeners: lifecycle.IDisposable[];
private viewFocusNextListeners: lifecycle.IDisposable[];
private viewFocusListeners: lifecycle.IDisposable[];
private viewDnDListeners: lifecycle.IDisposable[][];
private initialWeights: number[];
private sashOrientation: sash.Orientation;
private sashes: sash.Sash[];
Expand All @@ -496,13 +507,22 @@ export class SplitView implements
private layoutViewElement: (viewElement: HTMLElement, size: number) => void;
private eventWrapper: (event: sash.ISashEvent) => ISashEvent;
private animationTimeout: number;
private _onFocus: Emitter<IView>;
private state: IState;
private draggedView: IView;
private dropBackground: Color;

private _onFocus: Emitter<IView> = this._register(new Emitter<IView>());
readonly onFocus: Event<IView> = this._onFocus.event;

private _onDidOrderChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidOrderChange: Event<void> = this._onDidOrderChange.event;

constructor(container: HTMLElement, options?: IOptions) {
super();
options = options || {};

this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
this.canDragAndDrop = !!options.canChangeOrderByDragAndDrop;

this.el = document.createElement('div');
dom.addClass(this.el, 'monaco-split-view');
Expand All @@ -516,11 +536,11 @@ export class SplitView implements
this.viewFocusPreviousListeners = [];
this.viewFocusNextListeners = [];
this.viewFocusListeners = [];
this.viewDnDListeners = [];
this.initialWeights = [];
this.sashes = [];
this.sashesListeners = [];
this.animationTimeout = null;
this._onFocus = new Emitter<IView>();

this.sashOrientation = this.orientation === Orientation.VERTICAL
? sash.Orientation.HORIZONTAL
Expand All @@ -540,10 +560,6 @@ export class SplitView implements
this.addView(new VoidView(), 1, 0);
}

get onFocus(): Event<IView> {
return this._onFocus.event;
}

getViews<T extends IView>(): T[] {
return <T[]>this.views.slice(0, this.views.length - 1);
}
Expand Down Expand Up @@ -579,6 +595,9 @@ export class SplitView implements
this.el.insertBefore(viewElement, this.el.children.item(index));
}

// Listen to Drag and Drop
this.viewDnDListeners[index] = this.listenToDragAndDrop(view, viewElement);

// Add sash
if (this.views.length > 2) {
let s = new sash.Sash(this.el, this, { orientation: this.sashOrientation });
Expand Down Expand Up @@ -636,6 +655,9 @@ export class SplitView implements
this.viewFocusNextListeners[index].dispose();
this.viewFocusNextListeners.splice(index, 1);

lifecycle.dispose(this.viewDnDListeners[index]);
this.viewDnDListeners.splice(index, 1);

this.views.splice(index, 1);
this.initialWeights.splice(index, 1);
this.el.removeChild(this.viewElements[index]);
Expand Down Expand Up @@ -672,6 +694,108 @@ export class SplitView implements
this.layoutViews();
}

style(styles: SplitViewStyles): void {
this.dropBackground = styles.dropBackground;
}

private listenToDragAndDrop(view: IView, viewElement: HTMLElement): lifecycle.IDisposable[] {
if (!this.canDragAndDrop || view instanceof VoidView) {
return [];
}

const disposables: lifecycle.IDisposable[] = [];

// Allow to drag
if (view.draggableElement) {
view.draggableElement.draggable = true;
disposables.push(dom.addDisposableListener(view.draggableElement, dom.EventType.DRAG_START, (e: DragEvent) => {
e.dataTransfer.effectAllowed = 'move';
this.draggedView = view;
}));
}

// Drag enter
let counter = 0; // see https://github.com/Microsoft/vscode/issues/14470
disposables.push(dom.addDisposableListener(viewElement, dom.EventType.DRAG_ENTER, (e: DragEvent) => {
if (this.draggedView && this.draggedView !== view) {
counter++;
this.updateFromDragging(view, viewElement, true);
}
}));

// Drag leave
disposables.push(dom.addDisposableListener(viewElement, dom.EventType.DRAG_LEAVE, (e: DragEvent) => {
if (this.draggedView && this.draggedView !== view) {
counter--;
if (counter === 0) {
this.updateFromDragging(view, viewElement, false);
}
}
}));

// Drag end
disposables.push(dom.addDisposableListener(viewElement, dom.EventType.DRAG_END, (e: DragEvent) => {
if (this.draggedView) {
counter = 0;
this.updateFromDragging(view, viewElement, false);
this.draggedView = null;
}
}));

// Drop
disposables.push(dom.addDisposableListener(viewElement, dom.EventType.DROP, (e: DragEvent) => {
dom.EventHelper.stop(e, true);
counter = 0;
this.updateFromDragging(view, viewElement, false);
if (this.draggedView && this.draggedView !== view) {
this.move(this.views.indexOf(this.draggedView), this.views.indexOf(view));
}
this.draggedView = null;
}));

return disposables;
}

private updateFromDragging(view: IView, viewElement: HTMLElement, isDragging: boolean): void {
viewElement.style.backgroundColor = isDragging && this.dropBackground ? this.dropBackground.toString() : null;
}

private move(fromIndex: number, toIndex: number): void {
if (fromIndex < 0 || toIndex > this.views.length - 2) {
return;
}

const [viewChangeListener] = this.viewChangeListeners.splice(fromIndex, 1);
this.viewChangeListeners.splice(toIndex, 0, viewChangeListener);

const [viewFocusPreviousListener] = this.viewFocusPreviousListeners.splice(fromIndex, 1);
this.viewFocusPreviousListeners.splice(toIndex, 0, viewFocusPreviousListener);

const [viewFocusListener] = this.viewFocusListeners.splice(fromIndex, 1);
this.viewFocusListeners.splice(toIndex, 0, viewFocusListener);

const [viewFocusNextListener] = this.viewFocusNextListeners.splice(fromIndex, 1);
this.viewFocusNextListeners.splice(toIndex, 0, viewFocusNextListener);

const [viewDnDListeners] = this.viewDnDListeners.splice(fromIndex, 1);
this.viewDnDListeners.splice(toIndex, 0, viewDnDListeners);

const [view] = this.views.splice(fromIndex, 1);
this.views.splice(toIndex, 0, view);

const [weight] = this.initialWeights.splice(fromIndex, 1);
this.initialWeights.splice(toIndex, 0, weight);

this.el.removeChild(this.viewElements[fromIndex]);
this.el.insertBefore(this.viewElements[fromIndex], this.viewElements[toIndex < fromIndex ? toIndex : toIndex + 1]);
const [viewElement] = this.viewElements.splice(fromIndex, 1);
this.viewElements.splice(toIndex, 0, viewElement);

this.layout();

this._onDidOrderChange.fire();
}

private onSashStart(sash: sash.Sash, event: ISashEvent): void {
let i = this.sashes.indexOf(sash);
let collapses = this.views.map(v => v.size - v.minimumSize);
Expand Down Expand Up @@ -913,5 +1037,7 @@ export class SplitView implements
this.layoutViewElement = null;
this.eventWrapper = null;
this.state = null;

super.dispose();
}
}
14 changes: 10 additions & 4 deletions src/vs/workbench/parts/views/browser/views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -354,9 +354,15 @@ export class ComposedViewsViewlet extends Viewlet {
super.create(parent);

this.viewletContainer = DOM.append(parent.getHTMLElement(), DOM.$(''));
this.splitView = this._register(new SplitView(this.viewletContainer/* , { canChangeOrderByDragAndDrop: true } */));
// this.attachSplitViewStyler(this.splitView);
this.splitView = this._register(new SplitView(this.viewletContainer, { canChangeOrderByDragAndDrop: true }));
this.attachSplitViewStyler(this.splitView);
this._register(this.splitView.onFocus((view: IView) => this.lastFocusedView = view));
this._register(this.splitView.onDidOrderChange(() => {
const views = this.splitView.getViews<IView>();
for (let order = 0; order < views.length; order++) {
this.viewsStates.get(views[order].id).order = order;
}
}));

return this.onViewDescriptorsChanged()
.then(() => {
Expand Down Expand Up @@ -561,7 +567,7 @@ export class ComposedViewsViewlet extends Viewlet {
}, widget);
}

protected attachSplitViewStyler(widget: IThemable): IDisposable {
private attachSplitViewStyler(widget: IThemable): IDisposable {
return attachStyler(this.themeService, {
dropBackground: SIDE_BAR_DRAG_AND_DROP_BACKGROUND
}, widget);
Expand Down Expand Up @@ -651,7 +657,7 @@ export class ComposedViewsViewlet extends Viewlet {
return true;
}

private getViewDescriptorsFromRegistry(defaultOrder: boolean = true): IViewDescriptor[] {
private getViewDescriptorsFromRegistry(defaultOrder: boolean = false): IViewDescriptor[] {
return ViewsRegistry.getViews(this.location)
.sort((a, b) => {
const viewStateA = this.viewsStates.get(a.id);
Expand Down

0 comments on commit 119513f

Please sign in to comment.