mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 20:13:32 +01:00
Merge branch 'panel-viewlet'
This commit is contained in:
@@ -83,4 +83,27 @@
|
||||
.monaco-panel-view .panel > .panel-body {
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
|
||||
.monaco-panel-view.animated .split-view-view {
|
||||
transition-duration: 0.15s;
|
||||
-webkit-transition-duration: 0.15s;
|
||||
-moz-transition-duration: 0.15s;
|
||||
transition-timing-function: ease-out;
|
||||
-webkit-transition-timing-function: ease-out;
|
||||
-moz-transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
.monaco-panel-view.animated.vertical .split-view-view {
|
||||
transition-property: height;
|
||||
-webkit-transition-property: height;
|
||||
-moz-transition-property: height;
|
||||
}
|
||||
|
||||
.monaco-panel-view.animated.horizontal .split-view-view {
|
||||
transition-property: width;
|
||||
-webkit-transition-property: width;
|
||||
-moz-transition-property: width;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { $, append, addClass, removeClass, toggleClass, trackFocus } from 'vs/base/browser/dom';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { Color, RGBA } from 'vs/base/common/color';
|
||||
import { SplitView, IView } from './splitview2';
|
||||
import { SplitView, IView } from './splitview';
|
||||
|
||||
export interface IPanelOptions {
|
||||
ariaHeaderLabel?: string;
|
||||
@@ -34,20 +34,29 @@ export abstract class Panel implements IView {
|
||||
|
||||
private static HEADER_SIZE = 22;
|
||||
|
||||
private _expanded: boolean;
|
||||
protected _expanded: boolean;
|
||||
private expandedSize: number | undefined = undefined;
|
||||
private _headerVisible = true;
|
||||
private _onDidChange = new Emitter<void>();
|
||||
private _minimumBodySize: number;
|
||||
private _maximumBodySize: number;
|
||||
private ariaHeaderLabel: string;
|
||||
private styles: IPanelStyles | undefined = undefined;
|
||||
|
||||
private el: HTMLElement;
|
||||
private header: HTMLElement;
|
||||
protected disposables: IDisposable[] = [];
|
||||
|
||||
get draggable(): HTMLElement {
|
||||
private _onDidChange = new Emitter<number | undefined>();
|
||||
readonly onDidChange: Event<number | undefined> = this._onDidChange.event;
|
||||
|
||||
get draggableElement(): HTMLElement {
|
||||
return this.header;
|
||||
}
|
||||
|
||||
get dropTargetElement(): HTMLElement {
|
||||
return this.el;
|
||||
}
|
||||
|
||||
private _dropBackground: Color | undefined;
|
||||
get dropBackground(): Color | undefined {
|
||||
return this._dropBackground;
|
||||
@@ -77,7 +86,7 @@ export abstract class Panel implements IView {
|
||||
|
||||
get minimumSize(): number {
|
||||
const headerSize = this.headerSize;
|
||||
const expanded = !this.headerVisible || this.expanded;
|
||||
const expanded = !this.headerVisible || this.isExpanded();
|
||||
const minimumBodySize = expanded ? this._minimumBodySize : 0;
|
||||
|
||||
return headerSize + minimumBodySize;
|
||||
@@ -85,34 +94,31 @@ export abstract class Panel implements IView {
|
||||
|
||||
get maximumSize(): number {
|
||||
const headerSize = this.headerSize;
|
||||
const expanded = !this.headerVisible || this.expanded;
|
||||
const expanded = !this.headerVisible || this.isExpanded();
|
||||
const maximumBodySize = expanded ? this._maximumBodySize : 0;
|
||||
|
||||
return headerSize + maximumBodySize;
|
||||
}
|
||||
|
||||
readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||
|
||||
constructor(options: IPanelOptions = {}) {
|
||||
this._expanded = typeof options.expanded === 'undefined' ? true : !!options.expanded;
|
||||
this.ariaHeaderLabel = options.ariaHeaderLabel || '';
|
||||
this._minimumBodySize = typeof options.minimumBodySize === 'number' ? options.minimumBodySize : 120;
|
||||
this._maximumBodySize = typeof options.maximumBodySize === 'number' ? options.maximumBodySize : Number.POSITIVE_INFINITY;
|
||||
this.header = $('.panel-header');
|
||||
}
|
||||
|
||||
get expanded(): boolean {
|
||||
isExpanded(): boolean {
|
||||
return this._expanded;
|
||||
}
|
||||
|
||||
set expanded(expanded: boolean) {
|
||||
setExpanded(expanded: boolean): void {
|
||||
if (this._expanded === !!expanded) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._expanded = !!expanded;
|
||||
this.updateHeader();
|
||||
this._onDidChange.fire();
|
||||
this._onDidChange.fire(expanded ? this.expandedSize : undefined);
|
||||
}
|
||||
|
||||
get headerVisible(): boolean {
|
||||
@@ -130,9 +136,10 @@ export abstract class Panel implements IView {
|
||||
}
|
||||
|
||||
render(container: HTMLElement): void {
|
||||
const panel = append(container, $('.panel'));
|
||||
this.el = append(container, $('.panel'));
|
||||
|
||||
append(panel, this.header);
|
||||
this.header = $('.panel-header');
|
||||
append(this.el, this.header);
|
||||
this.header.setAttribute('tabindex', '0');
|
||||
this.header.setAttribute('role', 'toolbar');
|
||||
this.header.setAttribute('aria-label', this.ariaHeaderLabel);
|
||||
@@ -148,16 +155,16 @@ export abstract class Panel implements IView {
|
||||
.map(e => new StandardKeyboardEvent(e));
|
||||
|
||||
onHeaderKeyDown.filter(e => e.keyCode === KeyCode.Enter || e.keyCode === KeyCode.Space)
|
||||
.event(() => this.expanded = !this.expanded, null, this.disposables);
|
||||
.event(() => this.setExpanded(!this.isExpanded()), null, this.disposables);
|
||||
|
||||
onHeaderKeyDown.filter(e => e.keyCode === KeyCode.LeftArrow)
|
||||
.event(() => this.expanded = false, null, this.disposables);
|
||||
.event(() => this.setExpanded(false), null, this.disposables);
|
||||
|
||||
onHeaderKeyDown.filter(e => e.keyCode === KeyCode.RightArrow)
|
||||
.event(() => this.expanded = true, null, this.disposables);
|
||||
.event(() => this.setExpanded(true), null, this.disposables);
|
||||
|
||||
domEvent(this.header, 'click')
|
||||
(() => this.expanded = !this.expanded, null, this.disposables);
|
||||
(() => this.setExpanded(!this.isExpanded()), null, this.disposables);
|
||||
|
||||
// TODO@Joao move this down to panelview
|
||||
// onHeaderKeyDown.filter(e => e.keyCode === KeyCode.UpArrow)
|
||||
@@ -166,30 +173,42 @@ export abstract class Panel implements IView {
|
||||
// onHeaderKeyDown.filter(e => e.keyCode === KeyCode.DownArrow)
|
||||
// .event(focusNext, this, this.disposables);
|
||||
|
||||
const body = append(panel, $('.panel-body'));
|
||||
const body = append(this.el, $('.panel-body'));
|
||||
this.renderBody(body);
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
const headerSize = this.headerVisible ? Panel.HEADER_SIZE : 0;
|
||||
this.layoutBody(size - headerSize);
|
||||
|
||||
if (this.isExpanded()) {
|
||||
this.expandedSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
style(styles: IPanelStyles): void {
|
||||
this.header.style.color = styles.headerForeground ? styles.headerForeground.toString() : null;
|
||||
this.header.style.backgroundColor = styles.headerBackground ? styles.headerBackground.toString() : null;
|
||||
this.header.style.borderTop = styles.headerHighContrastBorder ? `1px solid ${styles.headerHighContrastBorder}` : null;
|
||||
this._dropBackground = styles.dropBackground;
|
||||
this.styles = styles;
|
||||
|
||||
if (!this.header) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateHeader();
|
||||
}
|
||||
|
||||
private updateHeader(): void {
|
||||
const expanded = !this.headerVisible || this.expanded;
|
||||
protected updateHeader(): void {
|
||||
const expanded = !this.headerVisible || this.isExpanded();
|
||||
|
||||
this.header.style.height = `${this.headerSize}px`;
|
||||
this.header.style.lineHeight = `${this.headerSize}px`;
|
||||
toggleClass(this.header, 'hidden', !this.headerVisible);
|
||||
toggleClass(this.header, 'expanded', expanded);
|
||||
this.header.setAttribute('aria-expanded', String(expanded));
|
||||
|
||||
this.header.style.color = this.styles.headerForeground ? this.styles.headerForeground.toString() : null;
|
||||
this.header.style.backgroundColor = this.styles.headerBackground ? this.styles.headerBackground.toString() : null;
|
||||
this.header.style.borderTop = this.styles.headerHighContrastBorder ? `1px solid ${this.styles.headerHighContrastBorder}` : null;
|
||||
this._dropBackground = this.styles.dropBackground;
|
||||
}
|
||||
|
||||
protected abstract renderHeader(container: HTMLElement): void;
|
||||
@@ -217,17 +236,18 @@ class PanelDraggable implements IDisposable {
|
||||
readonly onDidDrop = this._onDidDrop.event;
|
||||
|
||||
constructor(private panel: Panel, private context: IDndContext) {
|
||||
domEvent(panel.draggable, 'dragstart')(this.onDragStart, this, this.disposables);
|
||||
domEvent(panel.draggable, 'dragenter')(this.onDragEnter, this, this.disposables);
|
||||
domEvent(panel.draggable, 'dragleave')(this.onDragLeave, this, this.disposables);
|
||||
domEvent(panel.draggable, 'dragend')(this.onDragEnd, this, this.disposables);
|
||||
domEvent(panel.draggable, 'drop')(this.onDrop, this, this.disposables);
|
||||
panel.draggableElement.draggable = true;
|
||||
domEvent(panel.draggableElement, 'dragstart')(this.onDragStart, this, this.disposables);
|
||||
domEvent(panel.dropTargetElement, 'dragenter')(this.onDragEnter, this, this.disposables);
|
||||
domEvent(panel.dropTargetElement, 'dragleave')(this.onDragLeave, this, this.disposables);
|
||||
domEvent(panel.dropTargetElement, 'dragend')(this.onDragEnd, this, this.disposables);
|
||||
domEvent(panel.dropTargetElement, 'drop')(this.onDrop, this, this.disposables);
|
||||
}
|
||||
|
||||
private onDragStart(e: DragEvent): void {
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
|
||||
const dragImage = append(document.body, $('.monaco-panel-drag-image', {}, this.panel.draggable.textContent));
|
||||
const dragImage = append(document.body, $('.monaco-panel-drag-image', {}, this.panel.draggableElement.textContent));
|
||||
e.dataTransfer.setDragImage(dragImage, -10, -10);
|
||||
setTimeout(() => document.body.removeChild(dragImage), 0);
|
||||
|
||||
@@ -240,7 +260,7 @@ class PanelDraggable implements IDisposable {
|
||||
}
|
||||
|
||||
this.dragOverCounter++;
|
||||
this.renderHeader();
|
||||
this.render();
|
||||
}
|
||||
|
||||
private onDragLeave(e: DragEvent): void {
|
||||
@@ -251,7 +271,7 @@ class PanelDraggable implements IDisposable {
|
||||
this.dragOverCounter--;
|
||||
|
||||
if (this.dragOverCounter === 0) {
|
||||
this.renderHeader();
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +281,7 @@ class PanelDraggable implements IDisposable {
|
||||
}
|
||||
|
||||
this.dragOverCounter = 0;
|
||||
this.renderHeader();
|
||||
this.render();
|
||||
this.context.draggable = null;
|
||||
}
|
||||
|
||||
@@ -271,7 +291,7 @@ class PanelDraggable implements IDisposable {
|
||||
}
|
||||
|
||||
this.dragOverCounter = 0;
|
||||
this.renderHeader();
|
||||
this.render();
|
||||
|
||||
if (this.context.draggable !== this) {
|
||||
this._onDidDrop.fire({ from: this.context.draggable.panel, to: this.panel });
|
||||
@@ -280,14 +300,14 @@ class PanelDraggable implements IDisposable {
|
||||
this.context.draggable = null;
|
||||
}
|
||||
|
||||
private renderHeader(): void {
|
||||
private render(): void {
|
||||
let backgroundColor: string = null;
|
||||
|
||||
if (this.dragOverCounter > 0) {
|
||||
backgroundColor = (this.panel.dropBackground || PanelDraggable.DefaultDragOverBackgroundColor).toString();
|
||||
}
|
||||
|
||||
this.panel.draggable.style.backgroundColor = backgroundColor;
|
||||
this.panel.dropTargetElement.style.backgroundColor = backgroundColor;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
@@ -326,16 +346,15 @@ export class PanelView implements IDisposable {
|
||||
const disposables: IDisposable[] = [];
|
||||
panel.onDidChange(this.setupAnimation, this, disposables);
|
||||
|
||||
const panelItem = { panel, disposable: combinedDisposable(disposables) };
|
||||
this.panelItems.splice(index, 0, panelItem);
|
||||
this.splitview.addView(panel, size, index);
|
||||
|
||||
if (this.dnd) {
|
||||
const draggable = new PanelDraggable(panel, this.dndContext);
|
||||
disposables.push(draggable);
|
||||
draggable.onDidDrop(this._onDidDrop.fire, this._onDidDrop, disposables);
|
||||
}
|
||||
|
||||
const panelItem = { panel, disposable: combinedDisposable(disposables) };
|
||||
|
||||
this.panelItems.splice(index, 0, panelItem);
|
||||
this.splitview.addView(panel, size, index);
|
||||
}
|
||||
|
||||
removePanel(panel: Panel): void {
|
||||
@@ -359,7 +378,7 @@ export class PanelView implements IDisposable {
|
||||
}
|
||||
|
||||
const [panelItem] = this.panelItems.splice(fromIndex, 1);
|
||||
this.panelItems.splice(toIndex < fromIndex ? toIndex : toIndex - 1, 0, panelItem);
|
||||
this.panelItems.splice(toIndex, 0, panelItem);
|
||||
|
||||
this.splitview.moveView(fromIndex, toIndex);
|
||||
}
|
||||
@@ -374,6 +393,16 @@ export class PanelView implements IDisposable {
|
||||
this.splitview.resizeView(index, size);
|
||||
}
|
||||
|
||||
getPanelSize(panel: Panel): number {
|
||||
const index = firstIndex(this.panelItems, item => item.panel === panel);
|
||||
|
||||
if (index === -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return this.splitview.getViewSize(index);
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
this.splitview.layout(size);
|
||||
}
|
||||
|
||||
@@ -3,96 +3,21 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-split-view {
|
||||
.monaco-split-view2 {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monaco-split-view > .split-view-view {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-split-view.vertical > .split-view-view {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monaco-split-view.horizontal > .split-view-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-split-view > .split-view-view > .header {
|
||||
position: relative;
|
||||
line-height: 22px;
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
padding-left: 20px;
|
||||
.monaco-split-view2 > .split-view-view {
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.monaco-split-view > .split-view-view > .header.hide {
|
||||
display: none;
|
||||
.monaco-split-view2.vertical > .split-view-view {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Bold font style does not go well with CJK fonts */
|
||||
.monaco-split-view:lang(zh-Hans) > .split-view-view > .header,
|
||||
.monaco-split-view:lang(zh-Hant) > .split-view-view > .header,
|
||||
.monaco-split-view:lang(ja) > .split-view-view > .header,
|
||||
.monaco-split-view:lang(ko) > .split-view-view > .header { font-weight: normal; }
|
||||
|
||||
.monaco-split-view > .split-view-view > .header.collapsible {
|
||||
cursor: pointer;
|
||||
.monaco-split-view2.horizontal > .split-view-view {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-split-view > .split-view-view > .header.collapsible {
|
||||
background-image: url('arrow-collapse.svg');
|
||||
background-position: 2px center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.monaco-split-view > .split-view-view > .header.collapsible:not(.collapsed) {
|
||||
background-image: url('arrow-expand.svg');
|
||||
background-position: 2px center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-split-view > .split-view-view > .header.collapsible {
|
||||
background-image: url('arrow-collapse-dark.svg');
|
||||
}
|
||||
|
||||
.vs-dark .monaco-split-view > .split-view-view > .header.collapsible:not(.collapsed) {
|
||||
background-image: url('arrow-expand-dark.svg');
|
||||
background-position: 2px center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
|
||||
.monaco-split-view.animated > .split-view-view {
|
||||
transition-duration: 0.15s;
|
||||
-webkit-transition-duration: 0.15s;
|
||||
-moz-transition-duration: 0.15s;
|
||||
transition-timing-function: ease-out;
|
||||
-webkit-transition-timing-function: ease-out;
|
||||
-moz-transition-timing-function: ease-out;
|
||||
}
|
||||
|
||||
.monaco-split-view.vertical.animated > .split-view-view {
|
||||
transition-property: height;
|
||||
-webkit-transition-property: height;
|
||||
-moz-transition-property: height;
|
||||
}
|
||||
|
||||
.monaco-split-view.horizontal.animated > .split-view-view {
|
||||
transition-property: width;
|
||||
-webkit-transition-property: width;
|
||||
-moz-transition-property: width;
|
||||
}
|
||||
|
||||
.hc-black .split-view-view .action-label {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.hc-black .split-view-view > .header .action-label:before {
|
||||
top: 4px !important;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,23 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-split-view2 {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.monaco-split-view2 > .split-view-view {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-split-view2.vertical > .split-view-view {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.monaco-split-view2.horizontal > .split-view-view {
|
||||
height: 100%;
|
||||
}
|
||||
@@ -1,342 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./splitview2';
|
||||
import { IDisposable, combinedDisposable, 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 { clamp } from 'vs/base/common/numbers';
|
||||
import { range, firstIndex } from 'vs/base/common/arrays';
|
||||
import { Sash, Orientation, ISashEvent as IBaseSashEvent } from 'vs/base/browser/ui/sash/sash';
|
||||
export { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
|
||||
export interface ISplitViewOptions {
|
||||
orientation?: Orientation; // default Orientation.VERTICAL
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
interface ISashEvent {
|
||||
sash: Sash;
|
||||
start: number;
|
||||
current: number;
|
||||
}
|
||||
|
||||
interface IViewItem {
|
||||
view: IView;
|
||||
size: number;
|
||||
container: HTMLElement;
|
||||
disposable: IDisposable;
|
||||
layout(): void;
|
||||
}
|
||||
|
||||
interface ISashItem {
|
||||
sash: Sash;
|
||||
disposable: IDisposable;
|
||||
}
|
||||
|
||||
interface ISashDragState {
|
||||
index: number;
|
||||
start: number;
|
||||
sizes: number[];
|
||||
minDelta: number;
|
||||
maxDelta: number;
|
||||
}
|
||||
|
||||
export class SplitView implements IDisposable {
|
||||
|
||||
private orientation: Orientation;
|
||||
private el: HTMLElement;
|
||||
private size = 0;
|
||||
private contentSize = 0;
|
||||
private viewItems: IViewItem[] = [];
|
||||
private sashItems: ISashItem[] = [];
|
||||
private sashDragState: ISashDragState;
|
||||
|
||||
get length(): number {
|
||||
return this.viewItems.length;
|
||||
}
|
||||
|
||||
constructor(private container: HTMLElement, options: ISplitViewOptions = {}) {
|
||||
this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation;
|
||||
|
||||
this.el = document.createElement('div');
|
||||
dom.addClass(this.el, 'monaco-split-view2');
|
||||
dom.addClass(this.el, this.orientation === Orientation.VERTICAL ? 'vertical' : 'horizontal');
|
||||
container.appendChild(this.el);
|
||||
}
|
||||
|
||||
addView(view: IView, size: number, index = this.viewItems.length): void {
|
||||
// Add view
|
||||
const container = dom.$('.split-view-view');
|
||||
|
||||
if (this.viewItems.length === 1) {
|
||||
this.el.appendChild(container);
|
||||
} else {
|
||||
this.el.insertBefore(container, this.el.children.item(index));
|
||||
}
|
||||
|
||||
const onChangeDisposable = mapEvent(view.onDidChange, () => item)(this.onViewChange, this);
|
||||
const containerDisposable = toDisposable(() => this.el.removeChild(container));
|
||||
const disposable = combinedDisposable([onChangeDisposable, containerDisposable]);
|
||||
|
||||
const layoutContainer = this.orientation === Orientation.VERTICAL
|
||||
? size => item.container.style.height = `${item.size}px`
|
||||
: size => item.container.style.width = `${item.size}px`;
|
||||
|
||||
const layout = () => {
|
||||
layoutContainer(item.size);
|
||||
item.view.layout(item.size, this.orientation);
|
||||
};
|
||||
|
||||
size = Math.round(size);
|
||||
const item: IViewItem = { view, container, size, layout, disposable };
|
||||
this.viewItems.splice(index, 0, item);
|
||||
|
||||
// Add sash
|
||||
if (this.viewItems.length > 1) {
|
||||
const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
|
||||
const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) };
|
||||
const sash = new Sash(this.el, layoutProvider, { 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);
|
||||
}
|
||||
|
||||
view.render(container, this.orientation);
|
||||
this.relayout();
|
||||
}
|
||||
|
||||
removeView(index: number): void {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove view
|
||||
const viewItem = this.viewItems.splice(index, 1)[0];
|
||||
viewItem.disposable.dispose();
|
||||
|
||||
// Remove sash
|
||||
if (this.viewItems.length >= 1) {
|
||||
const sashIndex = Math.max(index - 1, 0);
|
||||
const sashItem = this.sashItems.splice(sashIndex, 1)[0];
|
||||
sashItem.disposable.dispose();
|
||||
}
|
||||
|
||||
this.relayout();
|
||||
}
|
||||
|
||||
moveView(from: number, to: number): void {
|
||||
if (from < 0 || from >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (to < 0 || to >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (from === to) {
|
||||
return;
|
||||
}
|
||||
|
||||
const viewItem = this.viewItems.splice(from, 1)[0];
|
||||
this.viewItems.splice(to, 0, viewItem);
|
||||
this.layoutViews();
|
||||
}
|
||||
|
||||
private relayout(): void {
|
||||
const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
this.resize(this.viewItems.length - 1, this.contentSize - contentSize);
|
||||
}
|
||||
|
||||
layout(size: number): void {
|
||||
const previousSize = Math.max(this.size, this.contentSize);
|
||||
this.size = size;
|
||||
this.resize(this.viewItems.length - 1, size - previousSize);
|
||||
}
|
||||
|
||||
private onSashStart({ sash, start }: ISashEvent): void {
|
||||
const index = firstIndex(this.sashItems, item => item.sash === sash);
|
||||
const sizes = this.viewItems.map(i => i.size);
|
||||
|
||||
const upIndexes = range(index, -1);
|
||||
const collapseUp = upIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
|
||||
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
|
||||
|
||||
const downIndexes = range(index + 1, this.viewItems.length);
|
||||
const collapseDown = downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0);
|
||||
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0);
|
||||
|
||||
const minDelta = -Math.min(collapseUp, expandDown);
|
||||
const maxDelta = Math.min(collapseDown, expandUp);
|
||||
|
||||
this.sashDragState = { start, index, sizes, minDelta, maxDelta };
|
||||
}
|
||||
|
||||
private onSashChange({ sash, current }: ISashEvent): void {
|
||||
const { index, start, sizes, minDelta, maxDelta } = this.sashDragState;
|
||||
const delta = clamp(current - start, minDelta, maxDelta);
|
||||
|
||||
this.resize(index, delta, sizes);
|
||||
}
|
||||
|
||||
private onViewChange(item: IViewItem): void {
|
||||
const index = this.viewItems.indexOf(item);
|
||||
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const size = clamp(item.size, item.view.minimumSize, item.view.maximumSize);
|
||||
item.size = size;
|
||||
this.relayout();
|
||||
}
|
||||
|
||||
resizeView(index: number, size: number): void {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const item = this.viewItems[index];
|
||||
size = Math.round(size);
|
||||
size = clamp(size, item.view.minimumSize, item.view.maximumSize);
|
||||
let delta = size - item.size;
|
||||
|
||||
if (delta !== 0 && index < this.viewItems.length - 1) {
|
||||
const downIndexes = range(index + 1, this.viewItems.length);
|
||||
const collapseDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
||||
const expandDown = downIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
||||
const deltaDown = clamp(delta, -expandDown, collapseDown);
|
||||
|
||||
this.resize(index, deltaDown);
|
||||
delta -= deltaDown;
|
||||
}
|
||||
|
||||
if (delta !== 0 && index > 0) {
|
||||
const upIndexes = range(index - 1, -1);
|
||||
const collapseUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].size - this.viewItems[i].view.minimumSize), 0);
|
||||
const expandUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - this.viewItems[i].size), 0);
|
||||
const deltaUp = clamp(-delta, -collapseUp, expandUp);
|
||||
|
||||
this.resize(index - 1, deltaUp);
|
||||
}
|
||||
}
|
||||
|
||||
private resize(index: number, delta: number, sizes = this.viewItems.map(i => i.size)): void {
|
||||
if (index < 0 || index >= this.viewItems.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (delta !== 0) {
|
||||
const upIndexes = range(index, -1);
|
||||
const up = upIndexes.map(i => this.viewItems[i]);
|
||||
const upSizes = upIndexes.map(i => sizes[i]);
|
||||
const downIndexes = range(index + 1, this.viewItems.length);
|
||||
const down = downIndexes.map(i => this.viewItems[i]);
|
||||
const downSizes = downIndexes.map(i => sizes[i]);
|
||||
|
||||
for (let i = 0, deltaUp = delta; deltaUp !== 0 && i < up.length; i++) {
|
||||
const item = up[i];
|
||||
const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize);
|
||||
const viewDelta = size - upSizes[i];
|
||||
|
||||
deltaUp -= viewDelta;
|
||||
item.size = size;
|
||||
}
|
||||
|
||||
for (let i = 0, deltaDown = delta; deltaDown !== 0 && i < down.length; i++) {
|
||||
const item = down[i];
|
||||
const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize);
|
||||
const viewDelta = size - downSizes[i];
|
||||
|
||||
deltaDown += viewDelta;
|
||||
item.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
let emptyDelta = this.size - contentSize;
|
||||
|
||||
for (let i = this.viewItems.length - 1; emptyDelta > 0 && i >= 0; i--) {
|
||||
const item = this.viewItems[i];
|
||||
const size = clamp(item.size + emptyDelta, item.view.minimumSize, item.view.maximumSize);
|
||||
const viewDelta = size - item.size;
|
||||
|
||||
emptyDelta -= viewDelta;
|
||||
item.size = size;
|
||||
}
|
||||
|
||||
this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
|
||||
|
||||
this.layoutViews();
|
||||
}
|
||||
|
||||
private layoutViews(): void {
|
||||
this.viewItems.forEach(item => item.layout());
|
||||
this.sashItems.forEach(item => item.sash.layout());
|
||||
|
||||
// Update sashes enablement
|
||||
let previous = false;
|
||||
const collapsesDown = this.viewItems.map(i => previous = (i.size - i.view.minimumSize > 0) || previous);
|
||||
|
||||
previous = false;
|
||||
const expandsDown = this.viewItems.map(i => previous = (i.view.maximumSize - i.size > 0) || previous);
|
||||
|
||||
const reverseViews = [...this.viewItems].reverse();
|
||||
previous = false;
|
||||
const collapsesUp = reverseViews.map(i => previous = (i.size - i.view.minimumSize > 0) || previous).reverse();
|
||||
|
||||
previous = false;
|
||||
const expandsUp = reverseViews.map(i => previous = (i.view.maximumSize - i.size > 0) || previous).reverse();
|
||||
|
||||
this.sashItems.forEach((s, i) => {
|
||||
if ((collapsesDown[i] && expandsUp[i + 1]) || (expandsDown[i] && collapsesUp[i + 1])) {
|
||||
s.sash.enable();
|
||||
} else {
|
||||
s.sash.disable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.viewItems.forEach(i => i.disposable.dispose());
|
||||
this.viewItems = [];
|
||||
|
||||
this.sashItems.forEach(i => i.disposable.dispose());
|
||||
this.sashItems = [];
|
||||
}
|
||||
}
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { SplitView, IView, Orientation } from 'vs/base/browser/ui/splitview/splitview2';
|
||||
import { SplitView, IView, Orientation } from 'vs/base/browser/ui/splitview/splitview';
|
||||
import { Sash } from 'vs/base/browser/ui/sash/sash';
|
||||
|
||||
class TestView implements IView {
|
||||
|
||||
private _onDidChange = new Emitter<void>();
|
||||
private _onDidChange = new Emitter<number | undefined>();
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
get minimumSize(): number { return this._minimumSize; }
|
||||
|
||||
Reference in New Issue
Block a user