Skip to content

Commit

Permalink
Merge pull request #40757 from SrTobi-Forks/master
Browse files Browse the repository at this point in the history
added Center Mode (#15684)
  • Loading branch information
bpasero authored Feb 21, 2018
2 parents 659220a + a0da58b commit 094f5ee
Show file tree
Hide file tree
Showing 7 changed files with 280 additions and 31 deletions.
2 changes: 2 additions & 0 deletions src/vs/code/electron-main/menus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -688,6 +688,7 @@ export class CodeMenu {

const fullscreen = new MenuItem(this.withKeybinding('workbench.action.toggleFullScreen', { label: this.mnemonicLabel(nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen")), click: () => this.windowsMainService.getLastActiveWindow().toggleFullScreen(), enabled: this.windowsMainService.getWindowCount() > 0 }));
const toggleZenMode = this.createMenuItem(nls.localize('miToggleZenMode', "Toggle Zen Mode"), 'workbench.action.toggleZenMode');
const toggleCenteredLayout = this.createMenuItem(nls.localize('miToggleCenteredLayout', "Toggle Centered Layout"), 'workbench.action.toggleCenteredLayout');
const toggleMenuBar = this.createMenuItem(nls.localize({ key: 'miToggleMenuBar', comment: ['&& denotes a mnemonic'] }, "Toggle Menu &&Bar"), 'workbench.action.toggleMenuBar');
const splitEditor = this.createMenuItem(nls.localize({ key: 'miSplitEditor', comment: ['&& denotes a mnemonic'] }, "Split &&Editor"), 'workbench.action.splitEditor');
const toggleEditorLayout = this.createMenuItem(nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Toggle Editor Group &&Layout"), 'workbench.action.toggleEditorGroupLayout');
Expand Down Expand Up @@ -747,6 +748,7 @@ export class CodeMenu {
__separator__(),
fullscreen,
toggleZenMode,
toggleCenteredLayout,
isWindows || isLinux ? toggleMenuBar : void 0,
__separator__(),
splitEditor,
Expand Down
36 changes: 36 additions & 0 deletions src/vs/workbench/browser/actions/toggleCenteredLayout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { TPromise } from 'vs/base/common/winjs.base';
import nls = require('vs/nls');
import { Action } from 'vs/base/common/actions';
import { Registry } from 'vs/platform/registry/common/platform';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
import { IPartService } from 'vs/workbench/services/part/common/partService';

class ToggleCenteredLayout extends Action {

public static readonly ID = 'workbench.action.toggleCenteredLayout';
public static readonly LABEL = nls.localize('toggleCenteredLayout', "Toggle Centered Layout");

constructor(
id: string,
label: string,
@IPartService private partService: IPartService
) {
super(id, label);
this.enabled = !!this.partService;
}

public run(): TPromise<any> {
this.partService.toggleCenteredEditorLayout();

return TPromise.as(null);
}
}

const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleCenteredLayout, ToggleCenteredLayout.ID, ToggleCenteredLayout.LABEL), 'View: Toggle Centered Layout', nls.localize('view', "View"));
237 changes: 206 additions & 31 deletions src/vs/workbench/browser/parts/editor/editorGroupsControl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl';
import { ITitleAreaControl } from 'vs/workbench/browser/parts/editor/titleControl';
import { NoTabsTitleControl } from 'vs/workbench/browser/parts/editor/noTabsTitleControl';
Expand All @@ -37,6 +38,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler';
import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { ResourcesDropHandler, LocalSelectionTransfer, DraggedEditorIdentifier } from 'vs/workbench/browser/dnd';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IPartService } from 'vs/workbench/services/part/common/partService';

export enum Rochade {
NONE,
Expand Down Expand Up @@ -87,11 +89,18 @@ export interface IEditorGroupsControl {
dispose(): void;
}

interface CenteredEditorLayoutData {
leftMarginRatio: number;
size: number;
}

/**
* Helper class to manage multiple side by side editors for the editor part.
*/
export class EditorGroupsControl extends Themable implements IEditorGroupsControl, IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider {

private static readonly CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY = 'workbench.centerededitorlayout.layoutData';

private static readonly TITLE_AREA_CONTROL_KEY = '__titleAreaControl';
private static readonly PROGRESS_BAR_CONTROL_KEY = '__progressBar';
private static readonly INSTANTIATION_SERVICE_KEY = '__instantiationService';
Expand All @@ -101,6 +110,8 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro

private static readonly EDITOR_TITLE_HEIGHT = 35;

private static readonly CENTERED_EDITOR_MIN_MARGIN = 10;

private static readonly SNAP_TO_MINIMIZED_THRESHOLD_WIDTH = 50;
private static readonly SNAP_TO_MINIMIZED_THRESHOLD_HEIGHT = 20;

Expand All @@ -125,6 +136,22 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
private sashTwo: Sash;
private startSiloThreeSize: number;

// if the centered editor layout is activated, the editor inside of silo ONE is centered
// the silo will then contain:
// [left margin]|[editor]|[right margin]
// - The size of the editor is defined by centeredEditorSize
// - The position is defined by the ratio centeredEditorLeftMarginRatio = left-margin/(left-margin + editor + right-margin).
// - The two sashes can be used to control the size and position of the editor inside of the silo.
// - In order to seperate the two sashes from the sashes that control the size of bordering widgets
// CENTERED_EDITOR_MIN_MARGIN is forced as a minimum size for the two margins.
private centeredEditorActive: boolean;
private centeredEditorSashLeft: Sash;
private centeredEditorSashRight: Sash;
private centeredEditorPreferedSize: number;
private centeredEditorLeftMarginRatio: number;
private centeredEditorDragStartPosition: number;
private centeredEditorDragStartSize: number;

private visibleEditors: BaseEditor[];

private lastActiveEditor: BaseEditor;
Expand All @@ -144,6 +171,8 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
groupOrientation: GroupOrientation,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorGroupService private editorGroupService: IEditorGroupService,
@IPartService private partService: IPartService,
@IStorageService private storageServise: IStorageService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IExtensionService private extensionService: IExtensionService,
@IInstantiationService private instantiationService: IInstantiationService,
Expand Down Expand Up @@ -971,39 +1000,72 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro

// For each position
POSITIONS.forEach(position => {
const silo = this.silos[position];

// Containers (they contain everything and can move between silos)
const container = $(silo).div({ 'class': 'container' });
this.createSilo(position);
});

// InstantiationServices
const instantiationService = this.instantiationService.createChild(new ServiceCollection(
[IContextKeyService, this.contextKeyService.createScoped(container.getHTMLElement())]
));
container.setProperty(EditorGroupsControl.INSTANTIATION_SERVICE_KEY, instantiationService); // associate with container
// Update Styles
this.updateStyles();
}

// Title containers
const titleContainer = $(container).div({ 'class': 'title' });
if (this.tabOptions.showTabs) {
titleContainer.addClass('tabs');
}
if (this.tabOptions.showIcons) {
titleContainer.addClass('show-file-icons');
}
this.hookTitleDragListener(titleContainer);
private createSilo(position: Position): void {
const silo = this.silos[position];

// Title Control
this.createTitleControl(this.stacks.groupAt(position), silo, titleContainer, instantiationService);
// Containers (they contain everything and can move between silos)
const container = $(silo).div({ 'class': 'container' });

// Progress Bar
const progressBar = new ProgressBar($(container));
this.toUnbind.push(attachProgressBarStyler(progressBar, this.themeService));
progressBar.getContainer().hide();
container.setProperty(EditorGroupsControl.PROGRESS_BAR_CONTROL_KEY, progressBar); // associate with container
});
// InstantiationServices
const instantiationService = this.instantiationService.createChild(new ServiceCollection(
[IContextKeyService, this.contextKeyService.createScoped(container.getHTMLElement())]
));
container.setProperty(EditorGroupsControl.INSTANTIATION_SERVICE_KEY, instantiationService); // associate with container

// Update Styles
this.updateStyles();
// Title containers
const titleContainer = $(container).div({ 'class': 'title' });
if (this.tabOptions.showTabs) {
titleContainer.addClass('tabs');
}
if (this.tabOptions.showIcons) {
titleContainer.addClass('show-file-icons');
}
this.hookTitleDragListener(titleContainer);

// Title Control
this.createTitleControl(this.stacks.groupAt(position), silo, titleContainer, instantiationService);

// Progress Bar
const progressBar = new ProgressBar($(container));
this.toUnbind.push(attachProgressBarStyler(progressBar, this.themeService));
progressBar.getContainer().hide();
container.setProperty(EditorGroupsControl.PROGRESS_BAR_CONTROL_KEY, progressBar); // associate with container

// Sash for first position to support centered editor layout
if (position === Position.ONE) {

// Center Layout stuff
this.centeredEditorSashLeft = new Sash(container.getHTMLElement(), this, { baseSize: 5, orientation: Orientation.VERTICAL });
this.toUnbind.push(this.centeredEditorSashLeft.onDidStart(() => this.onCenterSashLeftDragStart()));
this.toUnbind.push(this.centeredEditorSashLeft.onDidChange((e: ISashEvent) => this.onCenterSashLeftDrag(e)));
this.toUnbind.push(this.centeredEditorSashLeft.onDidEnd(() => this.storeCenteredLayoutData()));
this.toUnbind.push(this.centeredEditorSashLeft.onDidReset(() => this.resetCenteredEditor()));

this.centeredEditorSashRight = new Sash(container.getHTMLElement(), this, { baseSize: 5, orientation: Orientation.VERTICAL });
this.toUnbind.push(this.centeredEditorSashRight.onDidStart(() => this.onCenterSashRightDragStart()));
this.toUnbind.push(this.centeredEditorSashRight.onDidChange((e: ISashEvent) => this.onCenterSashRightDrag(e)));
this.toUnbind.push(this.centeredEditorSashRight.onDidEnd(() => this.storeCenteredLayoutData()));
this.toUnbind.push(this.centeredEditorSashRight.onDidReset(() => this.resetCenteredEditor()));

this.centeredEditorActive = false;
this.centeredEditorLeftMarginRatio = 0.5;
this.centeredEditorPreferedSize = -1; // set this later if we know the container size

// Restore centered layout position and size
const centeredLayoutDataString = this.storageServise.get(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.GLOBAL);
if (centeredLayoutDataString) {
const centeredLayout = <CenteredEditorLayoutData>JSON.parse(centeredLayoutDataString);
this.centeredEditorLeftMarginRatio = centeredLayout.leftMarginRatio;
this.centeredEditorPreferedSize = centeredLayout.size;
}
}
}

protected updateStyles(): void {
Expand Down Expand Up @@ -1858,12 +1920,87 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
this.sashTwo.layout();
}

private get centeredEditorAvailableSize(): number {
return this.silosSize[Position.ONE] - EditorGroupsControl.CENTERED_EDITOR_MIN_MARGIN * 2;
}

private get centeredEditorSize(): number {
return Math.min(this.centeredEditorAvailableSize, this.centeredEditorPreferedSize);
}

private get centeredEditorPosition(): number {
return EditorGroupsControl.CENTERED_EDITOR_MIN_MARGIN + this.centeredEditorLeftMarginRatio * (this.centeredEditorAvailableSize - this.centeredEditorSize);
}

private get centeredEditorEndPosition(): number {
return this.centeredEditorPosition + this.centeredEditorSize;
}

private setCenteredEditorPositionAndSize(pos: number, size: number): void {
this.centeredEditorPreferedSize = Math.max(this.minSize, size);
pos -= EditorGroupsControl.CENTERED_EDITOR_MIN_MARGIN;
pos = Math.min(pos, this.centeredEditorAvailableSize - this.centeredEditorSize);
pos = Math.max(0, pos);
this.centeredEditorLeftMarginRatio = pos / (this.centeredEditorAvailableSize - this.centeredEditorSize);

this.layoutContainers();
}

private onCenterSashLeftDragStart(): void {
this.centeredEditorDragStartPosition = this.centeredEditorPosition;
this.centeredEditorDragStartSize = this.centeredEditorSize;
}

private onCenterSashLeftDrag(e: ISashEvent): void {
const minMargin = EditorGroupsControl.CENTERED_EDITOR_MIN_MARGIN;
const diffPos = e.currentX - e.startX;
const diffSize = -diffPos;

const pos = this.centeredEditorDragStartPosition + diffPos;
const size = this.centeredEditorDragStartSize + diffSize;
this.setCenteredEditorPositionAndSize(pos, pos <= minMargin ? size + pos - minMargin : size);
}

private onCenterSashRightDragStart(): void {
this.centeredEditorDragStartPosition = this.centeredEditorPosition;
this.centeredEditorDragStartSize = this.centeredEditorSize;
}

private onCenterSashRightDrag(e: ISashEvent): void {
const diffPos = e.currentX - e.startX;
const diffSize = diffPos;

const pos = this.centeredEditorDragStartPosition;
const maxSize = this.centeredEditorAvailableSize - this.centeredEditorDragStartPosition;
const size = Math.min(maxSize, this.centeredEditorDragStartSize + diffSize);
this.setCenteredEditorPositionAndSize(size < this.minSize ? pos + (size - this.minSize) : pos, size);
}

private storeCenteredLayoutData(): void {
const data: CenteredEditorLayoutData = {
leftMarginRatio: this.centeredEditorLeftMarginRatio,
size: this.centeredEditorSize
};
this.storageServise.store(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, JSON.stringify(data), StorageScope.GLOBAL);
}

public getVerticalSashTop(sash: Sash): number {
return 0;
}

public getVerticalSashLeft(sash: Sash): number {
return sash === this.sashOne ? this.silosSize[Position.ONE] : this.silosSize[Position.TWO] + this.silosSize[Position.ONE];
switch (sash) {
case this.sashOne:
return this.silosSize[Position.ONE];
case this.sashTwo:
return this.silosSize[Position.TWO] + this.silosSize[Position.ONE];
case this.centeredEditorSashLeft:
return this.centeredEditorPosition;
case this.centeredEditorSashRight:
return this.centeredEditorEndPosition;
default:
return 0;
}
}

public getVerticalSashHeight(sash: Sash): number {
Expand Down Expand Up @@ -2013,6 +2150,27 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
}
});

// Layout centered Editor (only in vertical layout when one group is opened)
const doCentering = this.layoutVertically && this.stacks.groups.length === 1 && this.partService.isEditorLayoutCentered();
if (doCentering && !this.centeredEditorActive) {
this.centeredEditorSashLeft.show();
this.centeredEditorSashRight.show();

// no size set yet. Calculate a default value
if (this.centeredEditorPreferedSize === -1) {
this.resetCenteredEditor(false);
}
} else if (!doCentering && this.centeredEditorActive) {
this.centeredEditorSashLeft.hide();
this.centeredEditorSashRight.hide();
}
this.centeredEditorActive = doCentering;

if (this.centeredEditorActive) {
this.centeredEditorSashLeft.layout();
this.centeredEditorSashRight.layout();
}

// Layout visible editors
POSITIONS.forEach(position => {
this.layoutEditor(position);
Expand All @@ -2031,10 +2189,17 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro

private layoutEditor(position: Position): void {
const editorSize = this.silosSize[position];
if (editorSize && this.visibleEditors[position]) {
const editor = this.visibleEditors[position];
if (editorSize && editor) {
let editorWidth = this.layoutVertically ? editorSize : this.dimension.width;
let editorHeight = (this.layoutVertically ? this.dimension.height : this.silosSize[position]) - EditorGroupsControl.EDITOR_TITLE_HEIGHT;

let editorPosition = 0;
if (this.centeredEditorActive) {
editorWidth = this.centeredEditorSize;
editorPosition = this.centeredEditorPosition;
}

if (position !== Position.ONE) {
if (this.layoutVertically) {
editorWidth--; // accomodate for 1px left-border in containers TWO, THREE when laying out vertically
Expand All @@ -2043,8 +2208,18 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro
}
}

this.visibleEditors[position].layout(new Dimension(editorWidth, editorHeight));
editor.getContainer().style({ 'margin-left': `${editorPosition}px`, 'width': `${editorWidth}px` });
editor.layout(new Dimension(editorWidth, editorHeight));
}
}

private resetCenteredEditor(layout: boolean = true) {
this.centeredEditorLeftMarginRatio = 0.5;
this.centeredEditorPreferedSize = Math.floor(this.dimension.width * 0.61);
if (layout) {
this.layoutContainers();
}
this.storageServise.remove(EditorGroupsControl.CENTERED_EDITOR_LAYOUT_DATA_STORAGE_KEY, StorageScope.GLOBAL);
}

public getInstantiationService(position: Position): IInstantiationService {
Expand Down
Loading

0 comments on commit 094f5ee

Please sign in to comment.