提交 09050b6a 编写于 作者: J Joao Moreno

splitview: initial tests

上级 7ecb3f9f
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./splitview';
import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import Event, { fromEventEmitter, mapEvent } from 'vs/base/common/event';
import types = require('vs/base/common/types');
import dom = require('vs/base/browser/dom');
import { Sash, IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider, Orientation, ISashEvent as IBaseSashEvent } from 'vs/base/browser/ui/sash/sash';
export { Orientation } from 'vs/base/browser/ui/sash/sash';
interface ISashEvent {
sash: Sash;
start: number;
current: number;
}
export interface IOptions {
orientation?: Orientation; // default Orientation.VERTICAL
canChangeOrderByDragAndDrop?: boolean;
}
export interface IView {
readonly minimumSize: number;
readonly maximumSize: number;
readonly onDidChange: Event<void>;
render(container: HTMLElement, orientation: Orientation): void;
layout(size: number, orientation: Orientation): void;
focus(): void;
}
class ViewItem {
public explicitSize: number;
constructor(
readonly view: IView,
readonly container: HTMLElement,
public size: number,
private orientation: Orientation,
private disposables: IDisposable[]
) {
}
layout(): void {
if (this.orientation === Orientation.VERTICAL) {
this.container.style.height = `${this.size}px`;
} else {
this.container.style.width = `${this.size}px`;
}
this.view.layout(this.size, this.orientation);
}
dispose(): void {
this.disposables = dispose(this.disposables);
}
}
interface ISashItem {
sash: Sash;
disposable: IDisposable;
}
export class SplitView implements IDisposable, IHorizontalSashLayoutProvider, IVerticalSashLayoutProvider {
private orientation: Orientation;
private el: HTMLElement;
// private size: number;
// private viewElements: HTMLElement[];
private viewItems: ViewItem[] = [];
private sashItems: ISashItem[] = [];
// private viewChangeListeners: IDisposable[];
// private viewFocusPreviousListeners: IDisposable[];
// private viewFocusNextListeners: IDisposable[];
// private viewFocusListeners: IDisposable[];
// private viewDnDListeners: IDisposable[][];
// private sashOrientation: Orientation;
// private sashes: Sash[];
// private sashesListeners: IDisposable[];
// private eventWrapper: (event: ISashEvent) => ISashEvent;
// private animationTimeout: number;
// private state: IState;
// 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;
get length(): number {
return this.viewItems.length;
}
constructor(private container: HTMLElement, options?: IOptions) {
options = options || {};
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
this.el = document.createElement('div');
dom.addClass(this.el, 'monaco-split-view');
dom.addClass(this.el, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal');
container.appendChild(this.el);
// this.size = null;
// this.viewElements = [];
// this.views = [];
// this.viewChangeListeners = [];
// this.viewFocusPreviousListeners = [];
// this.viewFocusNextListeners = [];
// this.viewFocusListeners = [];
// this.viewDnDListeners = [];
// this.sashes = [];
// this.sashesListeners = [];
// this.animationTimeout = null;
// this.sashOrientation = this.orientation === Orientation.VERTICAL
// ? Orientation.HORIZONTAL
// : Orientation.VERTICAL;
// if (this.orientation === Orientation.VERTICAL) {
// this.eventWrapper = e => { return { start: e.startY, current: e.currentY }; };
// } else {
// this.eventWrapper = e => { return { start: e.startX, current: e.currentX }; };
// }
// The void space exists to handle the case where all other views are fixed size
// this.addView(new VoidView(), 0);
}
private getContainerSize(): number {
return this.orientation === Orientation.VERTICAL
? dom.getContentHeight(this.container)
: dom.getContentWidth(this.container);
}
addView(view: IView, size: number, index = this.viewItems.length - 1): void {
// Create view container
const container = document.createElement('div');
dom.addClass(container, 'split-view-view');
const containerDisposable = toDisposable(() => this.el.removeChild(container));
// List to change events
const onChangeDisposable = mapEvent(view.onDidChange, () => item)(this.onViewChange, this);
// Create item
const item = new ViewItem(view, container, size, this.orientation, [onChangeDisposable, containerDisposable]);
this.viewItems.splice(index, 0, item);
// Render view
view.render(container, this.orientation);
// Attach view
if (this.viewItems.length === 1) {
this.el.appendChild(container);
} else {
this.el.insertBefore(container, this.el.children.item(index));
}
// Add sash
if (this.viewItems.length <= 1) {
return;
}
const orientation = this.orientation === Orientation.VERTICAL
? Orientation.HORIZONTAL
: Orientation.VERTICAL;
const sash = new Sash(this.el, this, { orientation });
const sashEventMapper = this.orientation === Orientation.VERTICAL
? (e: IBaseSashEvent) => ({ sash, start: e.startY, current: e.currentY })
: (e: IBaseSashEvent) => ({ sash, start: e.startX, current: e.currentX });
const onStart = mapEvent(fromEventEmitter<IBaseSashEvent>(sash, 'start'), sashEventMapper);
const onStartDisposable = onStart(this.onSashStart, this);
const onChange = mapEvent(fromEventEmitter<IBaseSashEvent>(sash, 'change'), sashEventMapper);
const onSashChangeDisposable = onChange(this.onSashChange, this);
const disposable = combinedDisposable([onStartDisposable, onSashChangeDisposable, sash]);
const sashItem: ISashItem = {
sash,
disposable
};
this.sashItems.splice(index - 1, 0, sashItem);
}
removeView(index: number): void {
if (index < 0 || index >= this.viewItems.length) {
return;
}
// Remove view
const viewItem = this.viewItems.splice(index, 1)[0];
viewItem.dispose();
if (this.viewItems.length < 1) {
return;
}
// Remove sash
const sashIndex = Math.max(index - 1, 0);
const sashItem = this.sashItems.splice(sashIndex, 1)[0];
sashItem.disposable.dispose();
}
layout(size?: number): void {
size = size || this.getContainerSize();
}
private onSashStart({ sash, start, current }: ISashEvent): void {
}
private onSashChange({ sash, start, current }: ISashEvent): void {
}
// Main algorithm
// private expandCollapse(collapse: number, collapses: number[], expands: number[], collapseIndexes: number[], expandIndexes: number[]): void {
// let totalCollapse = collapse;
// let totalExpand = totalCollapse;
// collapseIndexes.forEach(i => {
// let collapse = Math.min(collapses[i], totalCollapse);
// totalCollapse -= collapse;
// this.views[i].size -= collapse;
// });
// expandIndexes.forEach(i => {
// let expand = Math.min(expands[i], totalExpand);
// totalExpand -= expand;
// this.views[i].size += expand;
// });
// }
private getLastFlexibleViewIndex(exceptIndex: number = null): number {
// for (let i = this.views.length - 1; i >= 0; i--) {
// if (exceptIndex === i) {
// continue;
// }
// if (this.views[i].sizing === ViewSizing.Flexible) {
// return i;
// }
// }
return -1;
}
private layoutViews(): void {
this.viewItems.forEach(item => item.layout());
this.sashItems.forEach(item => item.sash.layout());
// Update sashes enablement
// let previous = false;
// let collapsesDown = this.views.map(v => previous = (v.size - v.minimumSize > 0) || previous);
// previous = false;
// let expandsDown = this.views.map(v => previous = (v.maximumSize - v.size > 0) || previous);
// let reverseViews = this.views.slice().reverse();
// previous = false;
// let collapsesUp = reverseViews.map(v => previous = (v.size - v.minimumSize > 0) || previous).reverse();
// previous = false;
// let expandsUp = reverseViews.map(v => previous = (v.maximumSize - v.size > 0) || previous).reverse();
// this.sashes.forEach((s, i) => {
// if ((collapsesDown[i] && expandsUp[i + 1]) || (expandsDown[i] && collapsesUp[i + 1])) {
// s.enable();
// } else {
// s.disable();
// }
// });
}
private onViewChange(view: ViewItem): void {
}
// private setupAnimation(): void {
// if (types.isNumber(this.animationTimeout)) {
// window.clearTimeout(this.animationTimeout);
// }
// dom.addClass(this.el, 'animated');
// this.animationTimeout = window.setTimeout(() => this.clearAnimation(), 200);
// }
// private clearAnimation(): void {
// this.animationTimeout = null;
// dom.removeClass(this.el, 'animated');
// }
getVerticalSashLeft(sash: Sash): number {
return this.getSashPosition(sash);
}
getHorizontalSashTop(sash: Sash): number {
return this.getSashPosition(sash);
}
private getSashPosition(sash: Sash): number {
let position = 0;
for (let i = 0; i < this.sashItems.length; i++) {
position += this.viewItems[i].size;
if (this.sashItems[i].sash === sash) {
return position;
}
}
throw new Error('Sash not found');
}
dispose(): void {
}
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Emitter } from 'vs/base/common/event';
import { SplitView, IView, Orientation } from 'vs/base/browser/ui/splitview/splitview2';
class TestView implements IView {
private _onDidChange = new Emitter<void>();
readonly onDidChange = this._onDidChange.event;
get minimumSize(): number { return this._minimumSize; }
set minimumSize(size: number) { this._minimumSize = size; this._onDidChange.fire(); }
get maximumSize(): number { return this._maximumSize; }
set maximumSize(size: number) { this._maximumSize = size; this._onDidChange.fire(); }
private _onDidRender = new Emitter<{ container: HTMLElement; orientation: Orientation }>();
readonly onDidRender = this._onDidRender.event;
private _onDidLayout = new Emitter<{ size: number; orientation: Orientation }>();
readonly onDidLayout = this._onDidLayout.event;
private _onDidFocus = new Emitter<void>();
readonly onDidFocus = this._onDidFocus.event;
constructor(
private _minimumSize: number,
private _maximumSize: number
) {
assert(_minimumSize <= _maximumSize, 'splitview view minimum size must be <= maximum size');
}
render(container: HTMLElement, orientation: Orientation): void {
this._onDidRender.fire({ container, orientation });
}
layout(size: number, orientation: Orientation): void {
this._onDidLayout.fire({ size, orientation });
}
focus(): void {
this._onDidFocus.fire();
}
dispose(): void {
this._onDidChange.dispose();
this._onDidRender.dispose();
this._onDidLayout.dispose();
this._onDidFocus.dispose();
}
}
suite('Splitview', () => {
let container: HTMLElement;
setup(() => {
container = document.createElement('div');
container.style.position = 'absolute';
container.style.width = '200px';
container.style.height = '200px';
});
teardown(() => {
container = null;
});
test('empty splitview has empty DOM', () => {
const splitview = new SplitView(container);
assert.equal(container.firstElementChild.childElementCount, 0, 'split view should be empty');
splitview.dispose();
});
test('splitview has views as sashes as children', () => {
const view = new TestView(20, 20);
const splitview = new SplitView(container);
splitview.addView(view, 20);
splitview.addView(view, 20);
splitview.addView(view, 20);
let viewQuery = container.querySelectorAll('.monaco-split-view > .split-view-view');
assert.equal(viewQuery.length, 3, 'split view should have 3 views');
let sashQuery = container.querySelectorAll('.monaco-split-view > .monaco-sash');
assert.equal(sashQuery.length, 2, 'split view should have 2 sashes');
splitview.removeView(2);
viewQuery = container.querySelectorAll('.monaco-split-view > .split-view-view');
assert.equal(viewQuery.length, 2, 'split view should have 2 views');
sashQuery = container.querySelectorAll('.monaco-split-view > .monaco-sash');
assert.equal(sashQuery.length, 1, 'split view should have 1 sash');
splitview.removeView(0);
viewQuery = container.querySelectorAll('.monaco-split-view > .split-view-view');
assert.equal(viewQuery.length, 1, 'split view should have 1 view');
sashQuery = container.querySelectorAll('.monaco-split-view > .monaco-sash');
assert.equal(sashQuery.length, 0, 'split view should have no sashes');
splitview.removeView(0);
viewQuery = container.querySelectorAll('.monaco-split-view > .split-view-view');
assert.equal(viewQuery.length, 0, 'split view should have no views');
sashQuery = container.querySelectorAll('.monaco-split-view > .monaco-sash');
assert.equal(sashQuery.length, 0, 'split view should have no sashes');
splitview.dispose();
view.dispose();
});
});
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册