提交 8957a49e 编写于 作者: J João Moreno 提交者: Benjamin Pasero

Support grid descriptors (#51337)

* grid: createSerializedGrid

* fix gridview styles order, remove from parent on dispose

* adopt in editor part

* emit events for added groups properly

* fix array use
上级 1e934abd
......@@ -362,12 +362,20 @@ interface InitialLayoutContext<T extends ISerializableView> {
root: GridBranchNode<T>;
}
export interface ISerializedNode {
type: 'branch' | 'leaf';
data: ISerializedNode[] | object;
export interface ISerializedLeafNode {
type: 'leaf';
data: object;
size: number;
}
export interface ISerializedBranchNode {
type: 'branch';
data: ISerializedNode[];
size: number;
}
export type ISerializedNode = ISerializedLeafNode | ISerializedBranchNode;
export interface ISerializedGrid {
root: ISerializedNode;
orientation: Orientation;
......@@ -535,4 +543,81 @@ export class SerializableGrid<T extends ISerializableView> extends Grid<T> {
this.restoreViewsSize(childLocation, child, orthogonal(orientation), widthScale, heightScale);
}
}
}
export type GridNodeDescriptor = { size?: number, groups?: GridNodeDescriptor[] };
export type GridDescriptor = { orientation: Orientation, groups?: GridNodeDescriptor[] };
export function sanitizeGridNodeDescriptor(nodeDescriptor: GridNodeDescriptor): void {
if (nodeDescriptor.groups && nodeDescriptor.groups.length === 0) {
nodeDescriptor.groups = undefined;
}
if (!nodeDescriptor.groups) {
return;
}
let totalDefinedSize = 0;
let totalDefinedSizeCount = 0;
for (const child of nodeDescriptor.groups) {
sanitizeGridNodeDescriptor(child);
if (child.size) {
totalDefinedSize += child.size;
totalDefinedSizeCount++;
}
}
const totalUndefinedSize = totalDefinedSizeCount > 0 ? totalDefinedSize : 1;
const totalUndefinedSizeCount = nodeDescriptor.groups.length - totalDefinedSizeCount;
const eachUndefinedSize = totalUndefinedSize / totalUndefinedSizeCount;
for (const child of nodeDescriptor.groups) {
if (!child.size) {
child.size = eachUndefinedSize;
}
}
}
function createSerializedNode(nodeDescriptor: GridNodeDescriptor): ISerializedNode {
if (nodeDescriptor.groups) {
return { type: 'branch', data: nodeDescriptor.groups.map(c => createSerializedNode(c)), size: nodeDescriptor.size! };
} else {
return { type: 'leaf', data: null, size: nodeDescriptor.size! };
}
}
function getDimensions(node: ISerializedNode, orientation: Orientation): { width?: number, height?: number } {
if (node.type === 'branch') {
const childrenDimensions = node.data.map(c => getDimensions(c, orthogonal(orientation)));
if (orientation === Orientation.VERTICAL) {
const width = node.size || (childrenDimensions.length === 0 ? undefined : Math.max(...childrenDimensions.map(d => d.width || 0)));
const height = childrenDimensions.length === 0 ? undefined : childrenDimensions.reduce((r, d) => r + d.height, 0);
return { width, height };
} else {
const width = childrenDimensions.length === 0 ? undefined : childrenDimensions.reduce((r, d) => r + d.width, 0);
const height = node.size || (childrenDimensions.length === 0 ? undefined : Math.max(...childrenDimensions.map(d => d.height || 0)));
return { width, height };
}
} else {
const width = orientation === Orientation.VERTICAL ? node.size : undefined;
const height = orientation === Orientation.VERTICAL ? undefined : node.size;
return { width, height };
}
}
export function createSerializedGrid(gridDescriptor: GridDescriptor): ISerializedGrid {
sanitizeGridNodeDescriptor(gridDescriptor);
const root = createSerializedNode(gridDescriptor);
const { width, height } = getDimensions(root, gridDescriptor.orientation);
return {
root,
orientation: gridDescriptor.orientation,
width: width || 1,
height: height || 1
};
}
\ No newline at end of file
......@@ -459,8 +459,8 @@ export class GridView implements IDisposable {
constructor(container: HTMLElement, options: IGridViewOptions = {}) {
this.element = append(container, $('.monaco-grid-view'));
this.root = new BranchNode(Orientation.VERTICAL, this.styles);
this.styles = options.styles || defaultStyles;
this.root = new BranchNode(Orientation.VERTICAL, this.styles);
}
style(styles: IGridViewStyles): void {
......@@ -687,5 +687,11 @@ export class GridView implements IDisposable {
dispose(): void {
this.onDidSashResetRelay.dispose();
this.root.dispose();
if (this.element && this.element.parentElement) {
this.element.parentElement.removeChild(this.element);
}
this.element = null;
}
}
\ No newline at end of file
......@@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Direction, Grid, getRelativeLocation, Orientation, SerializableGrid, ISerializableView, IViewDeserializer, GridNode, Sizing, isGridBranchNode } from 'vs/base/browser/ui/grid/grid';
import { Direction, Grid, getRelativeLocation, Orientation, SerializableGrid, ISerializableView, IViewDeserializer, GridNode, Sizing, isGridBranchNode, sanitizeGridNodeDescriptor, GridNodeDescriptor, createSerializedGrid } from 'vs/base/browser/ui/grid/grid';
import { TestView, nodesToArrays } from './util';
import { deepClone } from 'vs/base/common/objects';
suite('Grid', function () {
let container: HTMLElement;
......@@ -737,4 +738,35 @@ suite('SerializableGrid', function () {
assert.deepEqual(view2Copy.size, [800, 300]);
assert.deepEqual(view3Copy.size, [800, 300]);
});
test('sanitizeGridNodeDescriptor', function () {
const nodeDescriptor = { groups: [{ size: 0.2 }, { size: 0.2 }, { size: 0.6, groups: [{}, {}] }] };
const nodeDescriptorCopy = deepClone<GridNodeDescriptor>(nodeDescriptor);
sanitizeGridNodeDescriptor(nodeDescriptorCopy);
assert.deepEqual(nodeDescriptorCopy, { groups: [{ size: 0.2 }, { size: 0.2 }, { size: 0.6, groups: [{ size: 0.5 }, { size: 0.5 }] }] });
});
test('createSerializedGrid', function () {
const gridDescriptor = { orientation: Orientation.VERTICAL, groups: [{ size: 0.2 }, { size: 0.2 }, { size: 0.6, groups: [{}, {}] }] };
const serializedGrid = createSerializedGrid(gridDescriptor);
assert.deepEqual(serializedGrid, {
root: {
type: 'branch',
size: undefined,
data: [
{ type: 'leaf', size: 0.2, data: null },
{ type: 'leaf', size: 0.2, data: null },
{
type: 'branch', size: 0.6, data: [
{ type: 'leaf', size: 0.5, data: null },
{ type: 'leaf', size: 0.5, data: null }
]
}
]
},
orientation: Orientation.VERTICAL,
width: 1,
height: 1
});
});
});
\ No newline at end of file
......@@ -13,7 +13,7 @@ import { Event, Emitter, once } from 'vs/base/common/event';
import { contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry';
import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, ICopyEditorOptions, GroupsOrder, GroupChangeKind, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument } from 'vs/workbench/services/group/common/editorGroupsService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, ISerializedNode, GridBranchNode, isGridBranchNode, GridNode } from 'vs/base/browser/ui/grid/grid';
import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, ISerializedNode, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid } from 'vs/base/browser/ui/grid/grid';
import { GroupIdentifier, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
import { values } from 'vs/base/common/map';
import { EDITOR_GROUP_BORDER } from 'vs/workbench/common/theme';
......@@ -332,80 +332,61 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
applyLayout(layout: EditorGroupLayout): void {
const gridHasFocus = isAncestor(document.activeElement, this.container);
const groupsInGridOrder = this.getGroups(GroupsOrder.GRID_APPEARANCE);
// Determine how many groups we need overall
let groupsInLayout = 0;
let layoutGroupsCount = 0;
function countGroups(groups: GroupLayoutArgument[]): void {
groups.forEach(group => {
if (Array.isArray(group.groups)) {
countGroups(group.groups);
} else {
groupsInLayout++;
layoutGroupsCount++;
}
});
}
countGroups(layout.groups);
// If we currently have too many groups, merge them into the last one
if (groupsInLayout < groupsInGridOrder.length) {
const lastGroupInLayout = groupsInGridOrder[groupsInLayout - 1];
groupsInGridOrder.forEach((group, index) => {
if (index >= groupsInLayout) {
let currentGroupViews = this.getGroups(GroupsOrder.GRID_APPEARANCE);
if (layoutGroupsCount < currentGroupViews.length) {
const lastGroupInLayout = currentGroupViews[layoutGroupsCount - 1];
currentGroupViews.forEach((group, index) => {
if (index >= layoutGroupsCount) {
this.mergeGroup(group, lastGroupInLayout);
}
});
}
// Apply orientation
if (typeof layout.orientation === 'number') {
this.setGroupOrientation(layout.orientation);
currentGroupViews = this.getGroups(GroupsOrder.GRID_APPEARANCE);
}
// Build layout
let currentGroupIndex = 0;
const buildLayout = (groups: IEditorGroupView[], descriptions: GroupLayoutArgument[], direction: GroupDirection) => {
if (descriptions.length === 0) {
return; // we need at least one group to layout
}
const activeGroup = this.activeGroup;
// Either move existing or add a new group for each item in the description
let totalProportions = 0;
descriptions.forEach((description, index) => {
if (index > 0) {
currentGroupIndex++;
const existingGroup = groupsInGridOrder[currentGroupIndex];
if (existingGroup) {
groups.push(this.moveGroup(existingGroup, groups[index - 1], direction));
} else {
groups.push(this.addGroup(groups[index - 1], direction));
}
}
// Prepare grid descriptor to create new grid from
const gridDescriptor = createSerializedGrid({
orientation: this.toGridViewOrientation(layout.orientation, this.gridWidget.orientation),
groups: layout.groups
});
if (typeof description.size === 'number') {
totalProportions += description.size;
}
});
// Recreate gridwidget with descriptor
this.doCreateGridControlWithState(this.container, gridDescriptor, activeGroup.id, currentGroupViews);
// Apply proportions if they are valid (sum() === 1)
if (totalProportions === 1) {
const totalSize = groups.map(group => this.getSize(group)).reduce(((prev, cur) => prev + cur));
descriptions.forEach((description, index) => {
this.setSize(groups[index], totalSize * description.size);
});
}
// Layout
this.doLayout(this.dimension);
// Continue building layout if description.groups is array-type
descriptions.forEach((description, index) => {
if (Array.isArray(description.groups)) {
buildLayout([groups[index]], description.groups, direction === GroupDirection.RIGHT ? GroupDirection.DOWN : GroupDirection.RIGHT);
}
});
};
// Update container
this.updateContainer();
// Mark preferred size as changed
this.resetPreferredSize();
buildLayout([groupsInGridOrder[0]], layout.groups, this.orientation === GroupOrientation.HORIZONTAL ? GroupDirection.RIGHT : GroupDirection.DOWN);
// Events for groupd that got added
this.getGroups(GroupsOrder.GRID_APPEARANCE).forEach(groupView => {
if (currentGroupViews.indexOf(groupView) === -1) {
this._onDidAddGroup.fire(groupView);
}
});
// Restore Focus
// Restore focus as needed
if (gridHasFocus) {
this._activeGroup.focus();
}
......@@ -533,6 +514,14 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
}
}
private toGridViewOrientation(orientation: GroupOrientation, fallback?: Orientation): Orientation {
if (typeof orientation === 'number') {
return orientation === GroupOrientation.HORIZONTAL ? Orientation.HORIZONTAL : Orientation.VERTICAL;
}
return fallback;
}
removeGroup(group: IEditorGroupView | GroupIdentifier): void {
const groupView = this.assertGroupView(group);
if (this.groupViews.size === 1) {
......@@ -707,14 +696,14 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
this._onDidPreferredSizeChange.fire();
}
private getGridSeparatorBorder(): Color {
private get gridSeparatorBorder(): Color {
return this.theme.getColor(EDITOR_GROUP_BORDER) || this.theme.getColor(contrastBorder) || Color.transparent;
}
protected updateStyles(): void {
this.container.style.backgroundColor = this.getColor(editorBackground);
this.gridWidget.style({ separatorBorder: this.getGridSeparatorBorder() });
this.gridWidget.style({ separatorBorder: this.gridSeparatorBorder });
}
createContentArea(parent: HTMLElement): HTMLElement {
......@@ -743,7 +732,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
// Grid Widget (no previous UI state or failed to restore)
if (!this.gridWidget) {
const initialGroup = this.doCreateGroupView();
this.gridWidget = this._register(new SerializableGrid(container, initialGroup));
this.gridWidget = new SerializableGrid(container, initialGroup);
// Ensure a group is active
this.doSetGroupActive(initialGroup);
......@@ -766,16 +755,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
this.mostRecentActiveGroups = uiState.mostRecentActiveGroups;
// Grid Widget
this.gridWidget = this._register(SerializableGrid.deserialize(container, uiState.serializedGrid, {
fromJSON: (serializedEditorGroup: ISerializedEditorGroup) => {
const groupView = this.doCreateGroupView(serializedEditorGroup);
if (groupView.id === uiState.activeGroup) {
this.doSetGroupActive(groupView);
}
return groupView;
}
}, { styles: { separatorBorder: this.getGridSeparatorBorder() } }));
this.doCreateGridControlWithState(container, uiState.serializedGrid, uiState.activeGroup);
// Ensure last active group has focus
this._activeGroup.focus();
......@@ -796,6 +776,40 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
}
}
private doCreateGridControlWithState(container: HTMLElement, serializedGrid: ISerializedGrid, activeGroupId: GroupIdentifier, editorGroupViewsToReuse?: IEditorGroupView[]): void {
// Dispose old
if (this.gridWidget) {
this.gridWidget.dispose();
}
// Determine group views to reuse if any
let reuseGroupViews: IEditorGroupView[];
if (editorGroupViewsToReuse) {
reuseGroupViews = editorGroupViewsToReuse.slice(0); // do not modify original array
} else {
reuseGroupViews = [];
}
// Create new
this.gridWidget = SerializableGrid.deserialize(container, serializedGrid, {
fromJSON: (serializedEditorGroup: ISerializedEditorGroup) => {
let groupView: IEditorGroupView;
if (reuseGroupViews.length > 0) {
groupView = reuseGroupViews.shift();
} else {
groupView = this.doCreateGroupView(serializedEditorGroup);
}
if (groupView.id === activeGroupId) {
this.doSetGroupActive(groupView);
}
return groupView;
}
}, { styles: { separatorBorder: this.gridSeparatorBorder } });
}
private doGetPreviousState(): IEditorPartUIState {
const legacyState = this.doGetPreviousLegacyState();
if (legacyState) {
......@@ -937,7 +951,13 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
layout(dimension: Dimension): Dimension[] {
const sizes = super.layout(dimension);
this.dimension = sizes[1];
this.doLayout(sizes[1]);
return sizes;
}
private doLayout(dimension: Dimension): void {
this.dimension = dimension;
// Layout Grid
try {
......@@ -948,8 +968,6 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
// Event
this._onDidLayout.fire(dimension);
return sizes;
}
shutdown(): void {
......@@ -981,8 +999,13 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor
this.groupViews.forEach(group => group.dispose());
this.groupViews.clear();
// Grid widget
if (this.gridWidget) {
this.gridWidget = dispose(this.gridWidget);
}
super.dispose();
}
//#endregion
}
\ No newline at end of file
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册