提交 119513fb 编写于 作者: S Sandeep Somavarapu 提交者: GitHub

Merge pull request #30197 from Microsoft/sandy081/viewsDragAndDrop

Support reordering of views in split view by drag and drop
......@@ -29,6 +29,7 @@ export enum ViewSizing {
export interface IOptions {
orientation?: Orientation; // default Orientation.VERTICAL
canChangeOrderByDragAndDrop?: boolean;
}
export interface ISashEvent {
......@@ -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;
......@@ -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();
......@@ -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;
......@@ -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';
......@@ -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[];
......@@ -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[];
......@@ -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');
......@@ -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
......@@ -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);
}
......@@ -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 });
......@@ -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]);
......@@ -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);
......@@ -913,5 +1037,7 @@ export class SplitView implements
this.layoutViewElement = null;
this.eventWrapper = null;
this.state = null;
super.dispose();
}
}
......@@ -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(() => {
......@@ -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);
......@@ -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);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册