diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index b4d4b0a510053..25968310933e8 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -113,6 +113,7 @@ export class MenuId { static readonly MenubarLayoutMenu = new MenuId('MenubarLayoutMenu'); static readonly MenubarNewBreakpointMenu = new MenuId('MenubarNewBreakpointMenu'); static readonly MenubarPanelAlignmentMenu = new MenuId('MenubarPanelAlignmentMenu'); + static readonly MenubarPanelPositionMenu = new MenuId('MenubarPanelPositionMenu'); static readonly MenubarPreferencesMenu = new MenuId('MenubarPreferencesMenu'); static readonly MenubarRecentMenu = new MenuId('MenubarRecentMenu'); static readonly MenubarSelectionMenu = new MenuId('MenubarSelectionMenu'); diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index e056b85d06b51..0eeba97761d78 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext, IsWebContext, IsMacNativeContext, IsDevelopmentContext, IsIOSContext } from 'vs/platform/contextkey/common/contextkeys'; -import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext } from 'vs/workbench/common/contextkeys'; +import { SplitEditorsVertically, InEditorZenModeContext, ActiveEditorCanRevertContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, AuxiliaryBarVisibleContext, SideBarVisibleContext, PanelAlignmentContext, PanelMaximizedContext, PanelVisibleContext, ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, EditorTabsVisibleContext, IsCenteredLayoutContext, ActiveEditorGroupIndexContext, ActiveEditorGroupLastContext, ActiveEditorReadonlyContext, EditorAreaVisibleContext, ActiveEditorAvailableEditorIdsContext, DirtyWorkingCopiesContext, EmptyWorkspaceSupportContext, EnterMultiRootWorkspaceSupportContext, HasWebFileSystemAccess, IsFullscreenContext, OpenFolderWorkspaceSupportContext, RemoteNameContext, VirtualWorkspaceContext, WorkbenchStateContext, WorkspaceFolderCountContext, PanelPositionContext } from 'vs/workbench/common/contextkeys'; import { TEXT_DIFF_EDITOR_ID, EditorInputCapabilities, SIDE_BY_SIDE_EDITOR_ID, DEFAULT_EDITOR_ASSOCIATION } from 'vs/workbench/common/editor'; import { trackFocus, addDisposableListener, EventType, WebFileSystemAccess } from 'vs/base/browser/dom'; import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -15,7 +15,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Parts, positionToString } from 'vs/workbench/services/layout/browser/layoutService'; import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; import { getVirtualWorkspaceScheme } from 'vs/platform/workspace/common/virtualWorkspace'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; @@ -63,6 +63,7 @@ export class WorkbenchContextKeysHandler extends Disposable { private isCenteredLayoutContext: IContextKey; private sideBarVisibleContext: IContextKey; private editorAreaVisibleContext: IContextKey; + private panelPositionContext: IContextKey; private panelVisibleContext: IContextKey; private panelAlignmentContext: IContextKey; private panelMaximizedContext: IContextKey; @@ -179,6 +180,8 @@ export class WorkbenchContextKeysHandler extends Disposable { this.sideBarVisibleContext = SideBarVisibleContext.bindTo(this.contextKeyService); // Panel + this.panelPositionContext = PanelPositionContext.bindTo(this.contextKeyService); + this.panelPositionContext.set(positionToString(this.layoutService.getPanelPosition())); this.panelVisibleContext = PanelVisibleContext.bindTo(this.contextKeyService); this.panelVisibleContext.set(this.layoutService.isVisible(Parts.PANEL_PART)); this.panelMaximizedContext = PanelMaximizedContext.bindTo(this.contextKeyService); @@ -228,6 +231,7 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.layoutService.onDidChangeZenMode(enabled => this.inZenModeContext.set(enabled))); this._register(this.layoutService.onDidChangeFullscreen(fullscreen => this.isFullscreenContext.set(fullscreen))); this._register(this.layoutService.onDidChangeCenteredLayout(centered => this.isCenteredLayoutContext.set(centered))); + this._register(this.layoutService.onDidChangePanelPosition(position => this.panelPositionContext.set(position))); this._register(this.layoutService.onDidChangePanelAlignment(alignment => this.panelAlignmentContext.set(alignment))); diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 35158adb130e4..701e29cb3bdc9 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -13,7 +13,7 @@ import { IUntypedEditorInput, pathsToEditors } from 'vs/workbench/common/editor' import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; -import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionToString, panelOpensMaximizedFromString, PanelAlignment } from 'vs/workbench/services/layout/browser/layoutService'; +import { Position, Parts, PanelOpensMaximizedOptions, IWorkbenchLayoutService, positionFromString, positionToString, panelOpensMaximizedFromString, PanelAlignment } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IStorageService, StorageScope, WillSaveStateReason } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -120,6 +120,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private readonly _onDidChangeWindowMaximized = this._register(new Emitter()); readonly onDidChangeWindowMaximized = this._onDidChangeWindowMaximized.event; + private readonly _onDidChangePanelPosition = this._register(new Emitter()); + readonly onDidChangePanelPosition = this._onDidChangePanelPosition.event; + private readonly _onDidChangePartVisibility = this._register(new Emitter()); readonly onDidChangePartVisibility = this._onDidChangePartVisibility.event; @@ -346,6 +349,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const newPositionValue = (position === Position.LEFT) ? 'left' : 'right'; const oldPositionValue = (position === Position.RIGHT) ? 'left' : 'right'; const panelAlignment = this.getPanelAlignment(); + const panelPosition = this.getPanelPosition(); this.stateModel.setRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON, position); @@ -368,7 +372,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi auxiliaryBar.updateStyles(); // Move activity bar, side bar, and side panel - this.adjustPartPositions(position, panelAlignment); + this.adjustPartPositions(position, panelAlignment, panelPosition); } private updateWindowBorder(skipLayout: boolean = false) { @@ -429,6 +433,10 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.setSideBarPosition(change.value as Position); } + if (change.key === LayoutStateKeys.PANEL_POSITION) { + this.setPanelPosition(change.value as Position); + } + if (change.key === LayoutStateKeys.PANEL_ALIGNMENT) { this.setPanelAlignment(change.value as PanelAlignment); } @@ -987,15 +995,18 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } getMaximumEditorDimensions(): Dimension { + const panelPosition = this.getPanelPosition(); + const isColumn = panelPosition === Position.RIGHT || panelPosition === Position.LEFT; const takenWidth = (this.isVisible(Parts.ACTIVITYBAR_PART) ? this.activityBarPartView.minimumWidth : 0) + (this.isVisible(Parts.SIDEBAR_PART) ? this.sideBarPartView.minimumWidth : 0) + + (this.isVisible(Parts.PANEL_PART) && isColumn ? this.panelPartView.minimumWidth : 0) + (this.isVisible(Parts.AUXILIARYBAR_PART) ? this.auxiliaryBarPartView.minimumWidth : 0); const takenHeight = (this.isVisible(Parts.TITLEBAR_PART) ? this.titleBarPartView.minimumHeight : 0) + (this.isVisible(Parts.STATUSBAR_PART) ? this.statusBarPartView.minimumHeight : 0) + - (this.isVisible(Parts.PANEL_PART) ? this.panelPartView.minimumHeight : 0); + (this.isVisible(Parts.PANEL_PART) && !isColumn ? this.panelPartView.minimumHeight : 0); const availableWidth = this.dimension.width - takenWidth; const availableHeight = this.dimension.height - takenHeight; @@ -1218,7 +1229,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Panel Size const panelSize = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN) ? this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView) - : this.workbenchGrid.getViewSize(this.panelPartView).height; + : (this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION) === Position.BOTTOM ? this.workbenchGrid.getViewSize(this.panelPartView).height : this.workbenchGrid.getViewSize(this.panelPartView).width); this.stateModel.setInitializationValue(LayoutStateKeys.PANEL_SIZE, panelSize as number); // Auxiliary Bar Size @@ -1311,8 +1322,8 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.workbenchGrid.resizeView(this.panelPartView, { - width: viewSize.width, - height: viewSize.height + sizeChangePxHeight + width: viewSize.width + (this.getPanelPosition() !== Position.BOTTOM ? sizeChangePxWidth : 0), + height: viewSize.height + (this.getPanelPosition() !== Position.BOTTOM ? 0 : sizeChangePxHeight) }); break; @@ -1451,31 +1462,43 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return viewContainerModel.activeViewDescriptors.length >= 1; } - private adjustPartPositions(sideBarPosition: Position, panelAlignment: PanelAlignment): void { + private adjustPartPositions(sideBarPosition: Position, panelAlignment: PanelAlignment, panelPosition: Position): void { // Move activity bar, side bar, and side panel - const sideBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); - const auxiliaryBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); + const sideBarSiblingToEditor = panelPosition !== Position.BOTTOM || !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); + const auxiliaryBarSiblingToEditor = panelPosition !== Position.BOTTOM || !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); + const preMovePanelWidth = !this.isVisible(Parts.PANEL_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView) ?? this.panelPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.panelPartView).width; + const preMovePanelHeight = !this.isVisible(Parts.PANEL_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.panelPartView) ?? this.panelPartView.minimumHeight) : this.workbenchGrid.getViewSize(this.panelPartView).height; const preMoveSideBarSize = !this.isVisible(Parts.SIDEBAR_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.sideBarPartView) ?? this.sideBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.sideBarPartView).width; const preMoveAuxiliaryBarSize = !this.isVisible(Parts.AUXILIARYBAR_PART) ? Sizing.Invisible(this.workbenchGrid.getViewCachedVisibleSize(this.auxiliaryBarPartView) ?? this.auxiliaryBarPartView.minimumWidth) : this.workbenchGrid.getViewSize(this.auxiliaryBarPartView).width; if (sideBarPosition === Position.LEFT) { this.workbenchGrid.moveViewTo(this.activityBarPartView, [2, 0]); - this.workbenchGrid.moveView(this.sideBarPartView, preMoveSideBarSize, sideBarNextToEditor ? this.editorPartView : this.activityBarPartView, sideBarNextToEditor ? Direction.Left : Direction.Right); - if (auxiliaryBarNextToEditor) { + this.workbenchGrid.moveView(this.sideBarPartView, preMoveSideBarSize, sideBarSiblingToEditor ? this.editorPartView : this.activityBarPartView, sideBarSiblingToEditor ? Direction.Left : Direction.Right); + if (auxiliaryBarSiblingToEditor) { this.workbenchGrid.moveView(this.auxiliaryBarPartView, preMoveAuxiliaryBarSize, this.editorPartView, Direction.Right); } else { this.workbenchGrid.moveViewTo(this.auxiliaryBarPartView, [2, -1]); } } else { this.workbenchGrid.moveViewTo(this.activityBarPartView, [2, -1]); - this.workbenchGrid.moveView(this.sideBarPartView, preMoveSideBarSize, sideBarNextToEditor ? this.editorPartView : this.activityBarPartView, sideBarNextToEditor ? Direction.Right : Direction.Left); - if (auxiliaryBarNextToEditor) { + this.workbenchGrid.moveView(this.sideBarPartView, preMoveSideBarSize, sideBarSiblingToEditor ? this.editorPartView : this.activityBarPartView, sideBarSiblingToEditor ? Direction.Right : Direction.Left); + if (auxiliaryBarSiblingToEditor) { this.workbenchGrid.moveView(this.auxiliaryBarPartView, preMoveAuxiliaryBarSize, this.editorPartView, Direction.Left); } else { this.workbenchGrid.moveViewTo(this.auxiliaryBarPartView, [2, 0]); } } + // We moved all the side parts based on the editor and ignored the panel + // Now, we need to put the panel back in the right position when it is next to the editor + if (panelPosition !== Position.BOTTOM) { + this.workbenchGrid.moveView(this.panelPartView, preMovePanelWidth, this.editorPartView, panelPosition === Position.LEFT ? Direction.Left : Direction.Right); + this.workbenchGrid.resizeView(this.panelPartView, { + height: preMovePanelHeight as number, + width: preMovePanelWidth as number + }); + } + // Moving views in the grid can cause them to re-distribute sizing unnecessarily // Resize visible parts to the width they were before the operation if (this.isVisible(Parts.SIDEBAR_PART)) { @@ -1494,6 +1517,11 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi } setPanelAlignment(alignment: PanelAlignment, skipLayout?: boolean): void { + // Panel alignment only applies to a panel in the bottom position + if (this.getPanelPosition() !== Position.BOTTOM) { + this.setPanelPosition(Position.BOTTOM); + } + // the workbench grid currently prevents us from supporting panel maximization with non-center panel alignment if (alignment !== 'center' && this.isPanelMaximized()) { this.toggleMaximizedPanel(); @@ -1501,7 +1529,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_ALIGNMENT, alignment); - this.adjustPartPositions(this.getSideBarPosition(), alignment); + this.adjustPartPositions(this.getSideBarPosition(), alignment, this.getPanelPosition()); this._onDidChangePanelAlignment.fire(alignment); } @@ -1582,18 +1610,23 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi toggleMaximizedPanel(): void { const size = this.workbenchGrid.getViewSize(this.panelPartView); + const panelPosition = this.getPanelPosition(); const isMaximized = this.isPanelMaximized(); if (!isMaximized) { if (this.isVisible(Parts.PANEL_PART)) { - this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT, size.height); + if (panelPosition === Position.BOTTOM) { + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT, size.height); + } else { + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH, size.width); + } } this.setEditorHidden(true); } else { this.setEditorHidden(false); this.workbenchGrid.resizeView(this.panelPartView, { - width: size.width, - height: this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT) + width: panelPosition === Position.BOTTOM ? size.width : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH), + height: panelPosition === Position.BOTTOM ? this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT) : size.height }); } this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_WAS_LAST_MAXIMIZED, !isMaximized); @@ -1604,7 +1637,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi */ private panelOpensMaximized(): boolean { // the workbench grid currently prevents us from supporting panel maximization with non-center panel alignment - if (this.getPanelAlignment() !== 'center') { + if (this.getPanelAlignment() !== 'center' && this.getPanelPosition() === Position.BOTTOM) { return false; } @@ -1690,7 +1723,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi isPanelMaximized(): boolean { // the workbench grid currently prevents us from supporting panel maximization with non-center panel alignment - return this.getPanelAlignment() === 'center' && !this.isVisible(Parts.EDITOR_PART); + return (this.getPanelAlignment() === 'center' || this.getPanelPosition() !== Position.BOTTOM) && !this.isVisible(Parts.EDITOR_PART); } getSideBarPosition(): Position { @@ -1724,6 +1757,83 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.configurationService.updateValue('window.menuBarVisibility', newVisibilityValue); } + getPanelPosition(): Position { + return this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION); + } + + setPanelPosition(position: Position): void { + if (!this.isVisible(Parts.PANEL_PART)) { + this.setPanelHidden(false); + } + + const panelPart = this.getPart(Parts.PANEL_PART); + const oldPositionValue = positionToString(this.getPanelPosition()); + const newPositionValue = positionToString(position); + + // Adjust CSS + const panelContainer = assertIsDefined(panelPart.getContainer()); + panelContainer.classList.remove(oldPositionValue); + panelContainer.classList.add(newPositionValue); + + // Update Styles + panelPart.updateStyles(); + + // Layout + const size = this.workbenchGrid.getViewSize(this.panelPartView); + const sideBarSize = this.workbenchGrid.getViewSize(this.sideBarPartView); + const auxiliaryBarSize = this.workbenchGrid.getViewSize(this.auxiliaryBarPartView); + + let editorHidden = !this.isVisible(Parts.EDITOR_PART); + + // Save last non-maximized size for panel before move + if (newPositionValue !== oldPositionValue && !editorHidden) { + + // Save the current size of the panel for the new orthogonal direction + // If moving down, save the width of the panel + // Otherwise, save the height of the panel + if (position === Position.BOTTOM) { + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH, size.width); + } else if (positionFromString(oldPositionValue) === Position.BOTTOM) { + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT, size.height); + } + } + + if (position === Position.BOTTOM && this.getPanelAlignment() !== 'center' && editorHidden) { + this.toggleMaximizedPanel(); + editorHidden = false; + } + + this.stateModel.setRuntimeValue(LayoutStateKeys.PANEL_POSITION, position); + + const sideBarVisible = this.isVisible(Parts.SIDEBAR_PART); + const auxiliaryBarVisible = this.isVisible(Parts.AUXILIARYBAR_PART); + + if (position === Position.BOTTOM) { + this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.height : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_HEIGHT), this.editorPartView, Direction.Down); + } else if (position === Position.RIGHT) { + this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.width : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH), this.editorPartView, Direction.Right); + } else { + this.workbenchGrid.moveView(this.panelPartView, editorHidden ? size.width : this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_LAST_NON_MAXIMIZED_WIDTH), this.editorPartView, Direction.Left); + } + + // Reset sidebar to original size before shifting the panel + this.workbenchGrid.resizeView(this.sideBarPartView, sideBarSize); + if (!sideBarVisible) { + this.setSideBarHidden(true); + } + + this.workbenchGrid.resizeView(this.auxiliaryBarPartView, auxiliaryBarSize); + if (!auxiliaryBarVisible) { + this.setAuxiliaryBarHidden(true); + } + + if (position === Position.BOTTOM) { + this.adjustPartPositions(this.getSideBarPosition(), this.getPanelAlignment(), position); + } + + this._onDidChangePanelPosition.fire(newPositionValue); + } + isWindowMaximized() { return this.windowState.runtime.maximized; } @@ -1811,42 +1921,62 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const panelSize = this.stateModel.getInitializationValue(LayoutStateKeys.PANEL_SIZE) ? 0 : nodes.panel.size; const result = [] as ISerializedNode[]; - const panelAlignment = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_ALIGNMENT); - const sideBarPosition = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON); - const sideBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); - const auxiliaryBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); - - const editorSectionWidth = availableWidth - activityBarSize - (sideBarNextToEditor ? 0 : sideBarSize) - (auxiliaryBarNextToEditor ? 0 : auxiliaryBarSize); - result.push({ - type: 'branch', - data: [this.arrangeEditorNodes({ - editor: nodes.editor, - sideBar: sideBarNextToEditor ? nodes.sideBar : undefined, - auxiliaryBar: auxiliaryBarNextToEditor ? nodes.auxiliaryBar : undefined - }, availableHeight - panelSize, editorSectionWidth), nodes.panel], - size: editorSectionWidth - }); + if (this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION) !== Position.BOTTOM) { + result.push(nodes.editor); + nodes.editor.size = availableWidth - activityBarSize - sideBarSize - panelSize - auxiliaryBarSize; + if (this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION) === Position.RIGHT) { + result.push(nodes.panel); + } else { + result.splice(0, 0, nodes.panel); + } - if (!sideBarNextToEditor) { - if (sideBarPosition === Position.LEFT) { + if (this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON) === Position.LEFT) { + result.push(nodes.auxiliaryBar); result.splice(0, 0, nodes.sideBar); + result.splice(0, 0, nodes.activityBar); } else { + result.splice(0, 0, nodes.auxiliaryBar); result.push(nodes.sideBar); + result.push(nodes.activityBar); } - } + } else { + const panelAlignment = this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_ALIGNMENT); + const sideBarPosition = this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON); + const sideBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.LEFT && panelAlignment === 'right') || (sideBarPosition === Position.RIGHT && panelAlignment === 'left')); + const auxiliaryBarNextToEditor = !(panelAlignment === 'center' || (sideBarPosition === Position.RIGHT && panelAlignment === 'right') || (sideBarPosition === Position.LEFT && panelAlignment === 'left')); - if (!auxiliaryBarNextToEditor) { - if (sideBarPosition === Position.RIGHT) { - result.splice(0, 0, nodes.auxiliaryBar); - } else { - result.push(nodes.auxiliaryBar); + const editorSectionWidth = availableWidth - activityBarSize - (sideBarNextToEditor ? 0 : sideBarSize) - (auxiliaryBarNextToEditor ? 0 : auxiliaryBarSize); + result.push({ + type: 'branch', + data: [this.arrangeEditorNodes({ + editor: nodes.editor, + sideBar: sideBarNextToEditor ? nodes.sideBar : undefined, + auxiliaryBar: auxiliaryBarNextToEditor ? nodes.auxiliaryBar : undefined + }, availableHeight - panelSize, editorSectionWidth), nodes.panel], + size: editorSectionWidth + }); + + if (!sideBarNextToEditor) { + if (sideBarPosition === Position.LEFT) { + result.splice(0, 0, nodes.sideBar); + } else { + result.push(nodes.sideBar); + } } - } - if (sideBarPosition === Position.LEFT) { - result.splice(0, 0, nodes.activityBar); - } else { - result.push(nodes.activityBar); + if (!auxiliaryBarNextToEditor) { + if (sideBarPosition === Position.RIGHT) { + result.splice(0, 0, nodes.auxiliaryBar); + } else { + result.push(nodes.auxiliaryBar); + } + } + + if (sideBarPosition === Position.LEFT) { + result.splice(0, 0, nodes.activityBar); + } else { + result.push(nodes.activityBar); + } } return result; @@ -1950,6 +2080,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi panelVisible: boolean; statusbarVisible: boolean; sideBarPosition: string; + panelPosition: string; }; type StartupLayoutEventClassification = { @@ -1959,6 +2090,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi panelVisible: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; statusbarVisible: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; sideBarPosition: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; + panelPosition: { classification: 'SystemMetaData', purpose: 'FeatureInsight'; }; }; const layoutDescriptor: StartupLayoutEvent = { @@ -1968,6 +2100,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi panelVisible: !this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_HIDDEN), statusbarVisible: !this.stateModel.getRuntimeValue(LayoutStateKeys.STATUSBAR_HIDDEN), sideBarPosition: positionToString(this.stateModel.getRuntimeValue(LayoutStateKeys.SIDEBAR_POSITON)), + panelPosition: positionToString(this.stateModel.getRuntimeValue(LayoutStateKeys.PANEL_POSITION)), }; this.telemetryService.publicLog2('startupLayout', layoutDescriptor); diff --git a/src/vs/workbench/browser/layoutState.ts b/src/vs/workbench/browser/layoutState.ts index edbf01b9ef4c1..983b9cbec15db 100644 --- a/src/vs/workbench/browser/layoutState.ts +++ b/src/vs/workbench/browser/layoutState.ts @@ -65,6 +65,7 @@ export const LayoutStateKeys = { // Part Positions SIDEBAR_POSITON: new RuntimeStateKey('sideBar.position', StorageScope.GLOBAL, StorageTarget.USER, Position.LEFT), + PANEL_POSITION: new RuntimeStateKey('panel.position', StorageScope.WORKSPACE, StorageTarget.USER, Position.BOTTOM), PANEL_ALIGNMENT: new RuntimeStateKey('panel.alignment', StorageScope.GLOBAL, StorageTarget.USER, 'center'), // Part Visibility @@ -148,10 +149,11 @@ export class LayoutStateModel extends Disposable { // Set dynamic defaults: part sizing and side bar visibility const workbenchDimensions = getClientArea(this.container); + LayoutStateKeys.PANEL_POSITION.defaultValue = positionFromString(this.configurationService.getValue(WorkbenchLayoutSettings.PANEL_POSITION) ?? 'bottom'); LayoutStateKeys.GRID_SIZE.defaultValue = { height: workbenchDimensions.height, width: workbenchDimensions.width }; LayoutStateKeys.SIDEBAR_SIZE.defaultValue = Math.min(300, workbenchDimensions.width / 4); LayoutStateKeys.AUXILIARYBAR_SIZE.defaultValue = Math.min(300, workbenchDimensions.width / 4); - LayoutStateKeys.PANEL_SIZE.defaultValue = workbenchDimensions.height / 3; + LayoutStateKeys.PANEL_SIZE.defaultValue = (this.stateCache.get(LayoutStateKeys.PANEL_POSITION.name) ?? LayoutStateKeys.PANEL_POSITION.defaultValue) === 'bottom' ? workbenchDimensions.height / 3 : workbenchDimensions.width / 4; LayoutStateKeys.SIDEBAR_HIDDEN.defaultValue = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; @@ -260,6 +262,7 @@ export class LayoutStateModel extends Disposable { } export enum WorkbenchLayoutSettings { + PANEL_POSITION = 'workbench.panel.defaultLocation', PANEL_OPENS_MAXIMIZED = 'workbench.panel.opensMaximized', ZEN_MODE_CONFIG = 'zenMode', ZEN_MODE_SILENT_NOTIFICATIONS = 'zenMode.silentNotifications', diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index 41f6a9c5846b1..e90911a75dcd1 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -27,6 +27,7 @@ import { IAction, Separator, toAction } from 'vs/base/common/actions'; import { ToggleAuxiliaryBarAction } from 'vs/workbench/browser/parts/auxiliarybar/auxiliaryBarActions'; import { assertIsDefined } from 'vs/base/common/types'; import { MoveSidePanelToPanelAction } from 'vs/workbench/browser/parts/panel/panelActions'; +import { LayoutPriority } from 'vs/base/browser/ui/splitview/splitview'; export class AuxiliaryBarPart extends BasePanelPart { static readonly activePanelSettingsKey = 'workbench.auxiliarybar.activepanelid'; @@ -39,6 +40,8 @@ export class AuxiliaryBarPart extends BasePanelPart { override readonly minimumHeight: number = 0; override readonly maximumHeight: number = Number.POSITIVE_INFINITY; + readonly priority: LayoutPriority = LayoutPriority.Low; + constructor( @INotificationService notificationService: INotificationService, @IStorageService storageService: IStorageService, diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 5e5b04df889bb..1387e4d81b78c 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -903,7 +903,7 @@ export class EditorPart extends Part implements IEditorGroupsService, IEditorGro let lastOpenHorizontalPosition: Position | undefined; let lastOpenVerticalPosition: Position | undefined; const openPartAtPosition = (position: Position) => { - if (!this.layoutService.isVisible(Parts.PANEL_PART) && position === Position.BOTTOM) { + if (!this.layoutService.isVisible(Parts.PANEL_PART) && position === this.layoutService.getPanelPosition()) { this.layoutService.setPartHidden(false, Parts.PANEL_PART); } else if (!this.layoutService.isVisible(Parts.AUXILIARYBAR_PART) && position === (this.layoutService.getSideBarPosition() === Position.RIGHT ? Position.LEFT : Position.RIGHT)) { this.layoutService.setPartHidden(false, Parts.AUXILIARYBAR_PART); diff --git a/src/vs/workbench/browser/parts/panel/media/basepanelpart.css b/src/vs/workbench/browser/parts/panel/media/basepanelpart.css index 250764d90d831..6cb382b8a5d68 100644 --- a/src/vs/workbench/browser/parts/panel/media/basepanelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/basepanelpart.css @@ -205,16 +205,16 @@ /* Rotate icons when panel is on right */ .monaco-workbench .part.basepanel.right .title-actions .codicon-split-horizontal::before, -.monaco-workbench .part.basepanel.right .title-actions .codicon-panel-maximize::before, -.monaco-workbench .part.basepanel.right .title-actions .codicon-panel-restore::before { +.monaco-workbench .part.basepanel.right .global-actions .codicon-panel-maximize::before, +.monaco-workbench .part.basepanel.right .global-actions .codicon-panel-restore::before { display: inline-block; transform: rotate(-90deg); } /* Rotate icons when panel is on left */ .monaco-workbench .part.basepanel.left .title-actions .codicon-split-horizontal::before, -.monaco-workbench .part.basepanel.left .title-actions .codicon-panel-maximize::before, -.monaco-workbench .part.basepanel.left .title-actions .codicon-panel-restore::before { +.monaco-workbench .part.basepanel.left .global-actions .codicon-panel-maximize::before, +.monaco-workbench .part.basepanel.left .global-actions .codicon-panel-restore::before { display: inline-block; transform: rotate(90deg); } diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 9035c0b16b1c4..dbc5ee6b78934 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -20,7 +20,6 @@ import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { ViewContainerLocationToString, ViewContainerLocation, IViewDescriptorService, IViewsService } from 'vs/workbench/common/views'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { INotificationService } from 'vs/platform/notification/common/notification'; const maximizeIcon = registerIcon('panel-maximize', Codicon.chevronUp, localize('maximizeIcon', 'Icon to maximize a panel.')); @@ -130,10 +129,31 @@ export const AlignPanelActionConfigs: PanelActionConfig[] = [ createAlignmentPanelActionConfig(AlignPanelActionId.JUSTIFY, 'View: Set Panel Alignment to Justify', localize('alignPanelJustify', 'Set Panel Alignment to Justify'), localize('alignPanelJustifyShort', "Justify"), 'justify'), ]; -const alignmentByActionId = new Map(AlignPanelActionConfigs.map(config => [config.id, config.value])); +const positionByActionId = new Map(PositionPanelActionConfigs.map(config => [config.id, config.value])); +export class SetPanelPositionAction extends Action { + constructor( + id: string, + label: string, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + ) { + super(id, label); + } + + override async run(): Promise { + const position = positionByActionId.get(this.id); + this.layoutService.setPanelPosition(position === undefined ? Position.BOTTOM : position); + } +} + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + submenu: MenuId.MenubarPanelPositionMenu, + title: localize('positionPanel', "Panel Position"), + group: '3_workbench_layout_move', + order: 4 +}); PositionPanelActionConfigs.forEach(positionPanelAction => { - const { id, label } = positionPanelAction; + const { id, label, shortLabel, value, when } = positionPanelAction; registerAction2(class extends Action2 { constructor() { @@ -145,17 +165,19 @@ PositionPanelActionConfigs.forEach(positionPanelAction => { }); } run(accessor: ServicesAccessor): void { - const notificationService = accessor.get(INotificationService); - const commandService = accessor.get(ICommandService); - - notificationService.warn(localize('deprecatedPanelMoveMessage', "Moving the panel with this command has been deprecated in favor of the \"Move Views From Panel To Side Panel\" and \"Move Views From Side Panel To Panel\" commands.")); - if (positionPanelAction.value === Position.BOTTOM) { - commandService.executeCommand('workbench.action.moveSidePanelToPanel'); - } else { - commandService.executeCommand('workbench.action.movePanelToSidePanel'); - } + const layoutService = accessor.get(IWorkbenchLayoutService); + layoutService.setPanelPosition(value === undefined ? Position.BOTTOM : value); } }); + + MenuRegistry.appendMenuItem(MenuId.MenubarPanelPositionMenu, { + command: { + id, + title: shortLabel, + toggled: when.negate() + }, + order: 5 + }); }); MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { @@ -193,21 +215,6 @@ AlignPanelActionConfigs.forEach(alignPanelAction => { }); }); -export class SetPanelAlignmentAction extends Action { - constructor( - id: string, - label: string, - @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService - ) { - super(id, label); - } - - override async run(): Promise { - const alignment = alignmentByActionId.get(this.id); - this.layoutService.setPanelAlignment(alignment === undefined ? 'center' : alignment); - } -} - export class PanelActivityAction extends ActivityAction { constructor( @@ -331,21 +338,21 @@ registerAction2(class extends Action2 { f1: true, icon: maximizeIcon, // the workbench grid currently prevents us from supporting panel maximization with non-center panel alignment - precondition: PanelAlignmentContext.isEqualTo('center'), + precondition: ContextKeyExpr.or(PanelAlignmentContext.isEqualTo('center'), PanelPositionContext.notEqualsTo('bottom')), toggled: { condition: PanelMaximizedContext, icon: restoreIcon, tooltip: localize('minimizePanel', "Restore Panel Size") }, menu: [{ id: MenuId.PanelTitle, group: 'navigation', order: 1, // the workbench grid currently prevents us from supporting panel maximization with non-center panel alignment - when: PanelAlignmentContext.isEqualTo('center') + when: ContextKeyExpr.or(PanelAlignmentContext.isEqualTo('center'), PanelPositionContext.notEqualsTo('bottom')) }] }); } run(accessor: ServicesAccessor) { const layoutService = accessor.get(IWorkbenchLayoutService); const notificationService = accessor.get(INotificationService); - if (layoutService.getPanelAlignment() !== 'center') { + if (layoutService.getPanelAlignment() !== 'center' && layoutService.getPanelPosition() === Position.BOTTOM) { notificationService.warn(localize('panelMaxNotSupported', "Maximizing the panel is only supported when it is center aligned.")); return; } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 69da4553060bf..c72e81f98317a 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -12,13 +12,13 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ActionsOrientation, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { ActivePanelContext, PanelFocusContext, getEnabledViewContainerContextKey } from 'vs/workbench/common/contextkeys'; import { CompositePart, ICompositeTitleLabel } from 'vs/workbench/browser/parts/compositePart'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { PanelActivityAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, MovePanelToSidePanelAction } from 'vs/workbench/browser/parts/panel/panelActions'; +import { PanelActivityAction, TogglePanelAction, PlaceHolderPanelActivityAction, PlaceHolderToggleCompositePinnedAction, PositionPanelActionConfigs, SetPanelPositionAction, MovePanelToSidePanelAction } from 'vs/workbench/browser/parts/panel/panelActions'; import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_INPUT_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, PANEL_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -930,7 +930,7 @@ export class PanelPart extends BasePanelPart { protected getActivityHoverOptions(): IActivityHoverOptions { return { - position: () => !this.layoutService.isPanelMaximized() ? HoverPosition.ABOVE : HoverPosition.BELOW, + position: () => this.layoutService.getPanelPosition() === Position.BOTTOM && !this.layoutService.isPanelMaximized() ? HoverPosition.ABOVE : HoverPosition.BELOW, }; } @@ -939,6 +939,10 @@ export class PanelPart extends BasePanelPart { actions.push(...[ new Separator(), toAction({ id: MovePanelToSidePanelAction.ID, label: localize('moveToSidePanel', "Move Views to Side Panel"), run: () => this.instantiationService.invokeFunction(accessor => new MovePanelToSidePanelAction().run(accessor)) }), + ...PositionPanelActionConfigs + // show the contextual menu item if it is not in that position + .filter(({ when }) => this.contextKeyService.contextMatchesRules(when)) + .map(({ id, label }) => this.instantiationService.createInstance(SetPanelPositionAction, id, label)), this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel")) ]); } @@ -966,7 +970,12 @@ export class PanelPart extends BasePanelPart { } override layout(width: number, height: number, top: number, left: number): void { - const dimensions = new Dimension(width, height); + let dimensions: Dimension; + if (this.layoutService.getPanelPosition() === Position.RIGHT) { + dimensions = new Dimension(width - 1, height); // Take into account the 1px border when layouting + } else { + dimensions = new Dimension(width, height); + } // Layout contents super.layout(dimensions.width, dimensions.height, top, left); diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 17e0b2b1d6e44..0c01b54a8f918 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -39,7 +39,7 @@ import { PANEL_SECTION_BORDER, PANEL_SECTION_DRAG_AND_DROP_BACKGROUND, PANEL_SEC import { IAddedViewDescriptorRef, ICustomViewDescriptor, IView, IViewContainerModel, IViewDescriptor, IViewDescriptorRef, IViewDescriptorService, IViewPaneContainer, IViewsService, ViewContainer, ViewContainerLocation, ViewContainerLocationToString, ViewVisibilityState } from 'vs/workbench/common/views'; import { FocusedViewContext } from 'vs/workbench/common/contextkeys'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; export const ViewsSubMenu = new MenuId('Views'); MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { @@ -617,7 +617,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { case ViewContainerLocation.AuxiliaryBar: return Orientation.VERTICAL; case ViewContainerLocation.Panel: - return Orientation.HORIZONTAL; + return this.layoutService.getPanelPosition() === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; } return Orientation.VERTICAL; diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index aa8b60656e713..bf561fbf839ba 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -285,8 +285,7 @@ const registry = Registry.as(ConfigurationExtensions.Con 'type': 'string', 'enum': ['left', 'bottom', 'right'], 'default': 'bottom', - 'description': localize('panelDefaultLocation', "Controls the default location of the panel (terminal, debug console, output, problems). It can either show at the bottom, right, or left of the workbench."), - 'deprecationMessage': localize('panelDefaultLocationDeprecated', "This setting has been deprecated with the addition of the new side panel. Instead of setting the location for the panel, you now use the \"Move Views\" commands to set the location of the individual view containers within the panels.") + 'description': localize('panelDefaultLocation', "Controls the default location of the panel (terminal, debug console, output, problems) in a new workspace. It can either show at the bottom, right, or left of the editor area."), }, 'workbench.panel.opensMaximized': { 'type': 'string', diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index c6755a2a284f3..0ab5bac6b82be 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -342,7 +342,7 @@ export class Workbench extends Layout { { id: Parts.ACTIVITYBAR_PART, role: 'none', classes: ['activitybar', this.getSideBarPosition() === Position.LEFT ? 'left' : 'right'] }, // Use role 'none' for some parts to make screen readers less chatty #114892 { id: Parts.SIDEBAR_PART, role: 'none', classes: ['sidebar', this.getSideBarPosition() === Position.LEFT ? 'left' : 'right'] }, { id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.willRestoreEditors() } }, - { id: Parts.PANEL_PART, role: 'none', classes: ['panel', 'basepanel', positionToString(Position.BOTTOM)] }, + { id: Parts.PANEL_PART, role: 'none', classes: ['panel', 'basepanel', positionToString(this.getPanelPosition())] }, { id: Parts.AUXILIARYBAR_PART, role: 'none', classes: ['auxiliarybar', 'basepanel', this.getSideBarPosition() === Position.LEFT ? 'right' : 'left'] }, { id: Parts.STATUSBAR_PART, role: 'status', classes: ['statusbar'] } ].forEach(({ id, role, classes, options }) => { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts index 4a6d2c182e14b..357c121317f3c 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalGroup.ts @@ -7,7 +7,7 @@ import { TERMINAL_VIEW_ID } from 'vs/workbench/contrib/terminal/common/terminal' import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITerminalInstance, Direction, ITerminalGroup, ITerminalService, ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views'; @@ -246,6 +246,7 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { private _terminalInstances: ITerminalInstance[] = []; private _splitPaneContainer: SplitPaneContainer | undefined; private _groupElement: HTMLElement | undefined; + private _panelPosition: Position = Position.BOTTOM; private _terminalLocation: ViewContainerLocation = ViewContainerLocation.Panel; private _instanceDisposables: Map = new Map(); @@ -276,6 +277,7 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance | undefined, @ITerminalService private readonly _terminalService: ITerminalService, @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, @IInstantiationService private readonly _instantiationService: IInstantiationService ) { @@ -286,7 +288,7 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { if (this._container) { this.attachToElement(this._container); } - this._onPanelOrientationChanged.fire(this._terminalLocation === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL); + this._onPanelOrientationChanged.fire(this._terminalLocation === ViewContainerLocation.Panel && this._panelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL); } addInstance(shellLaunchConfigOrInstance: IShellLaunchConfig | ITerminalInstance, parentTerminalId?: number): void { @@ -466,8 +468,9 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { this._container.appendChild(this._groupElement); if (!this._splitPaneContainer) { + this._panelPosition = this._layoutService.getPanelPosition(); this._terminalLocation = this._viewDescriptorService.getViewLocationById(TERMINAL_VIEW_ID)!; - const orientation = this._terminalLocation === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL; + const orientation = this._terminalLocation === ViewContainerLocation.Panel && this._panelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; this._splitPaneContainer = this._instantiationService.createInstance(SplitPaneContainer, this._groupElement, orientation); this.terminalInstances.forEach(instance => this._splitPaneContainer!.split(instance, this._activeInstanceIndex + 1)); if (this._initialRelativeSizes) { @@ -529,11 +532,13 @@ export class TerminalGroup extends Disposable implements ITerminalGroup { layout(width: number, height: number): void { if (this._splitPaneContainer) { // Check if the panel position changed and rotate panes if so + const newPanelPosition = this._layoutService.getPanelPosition(); const newTerminalLocation = this._viewDescriptorService.getViewLocationById(TERMINAL_VIEW_ID)!; - const terminalPositionChanged = newTerminalLocation !== this._terminalLocation; + const terminalPositionChanged = newPanelPosition !== this._panelPosition || newTerminalLocation !== this._terminalLocation; if (terminalPositionChanged) { - const newOrientation = newTerminalLocation === ViewContainerLocation.Panel ? Orientation.HORIZONTAL : Orientation.VERTICAL; + const newOrientation = newTerminalLocation === ViewContainerLocation.Panel && newPanelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; this._splitPaneContainer.setOrientation(newOrientation); + this._panelPosition = newPanelPosition; this._terminalLocation = newTerminalLocation; this._onPanelOrientationChanged.fire(this._splitPaneContainer.orientation); } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index d8b6b5fd7301c..00064038b7d56 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -51,6 +51,7 @@ import { URI } from 'vs/base/common/uri'; import { DataTransfers } from 'vs/base/browser/dnd'; import { CodeDataTransfers, containsDragType, DragAndDropObserver, IDragAndDropObserverCallbacks } from 'vs/workbench/browser/dnd'; import { getColorClass, getColorStyleElement, getStandardColors } from 'vs/workbench/contrib/terminal/browser/terminalIcon'; +import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -2064,6 +2065,7 @@ class TerminalInstanceDragAndDropController extends Disposable implements IDragA constructor( private readonly _container: HTMLElement, + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, ) { super(); @@ -2174,8 +2176,9 @@ class TerminalInstanceDragAndDropController extends Disposable implements IDragA } private _getViewOrientation(): Orientation { + const panelPosition = this._layoutService.getPanelPosition(); const terminalLocation = this._viewDescriptorService.getViewLocationById(TERMINAL_VIEW_ID); - return terminalLocation === ViewContainerLocation.Panel + return terminalLocation === ViewContainerLocation.Panel && panelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; } diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index 763c8d0238483..f831821d163e3 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -13,7 +13,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { EditorResourceAccessor, IUntitledTextResourceEditorInput, SideBySideEditor, pathsToEditors, IResourceDiffEditorInput, IUntypedEditorInput } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest } from 'vs/platform/windows/common/windows'; +import { WindowMinimumSize, IOpenFileRequest, IWindowsConfiguration, getTitleBarStyle, IAddFoldersRequest, INativeRunActionInWindowRequest, INativeRunKeybindingInWindowRequest, INativeOpenFileRequest } from 'vs/platform/windows/common/windows'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { applyZoom } from 'vs/platform/windows/electron-sandbox/window'; @@ -47,7 +47,7 @@ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { posix, dirname } from 'vs/base/common/path'; import { getBaseLabel } from 'vs/base/common/labels'; import { ITunnelService, extractLocalHostUriMetaDataForPortMapping } from 'vs/platform/tunnel/common/tunnel'; -import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkbenchLayoutService, Parts, positionFromString, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService'; import { WorkingCopyCapabilities } from 'vs/workbench/services/workingCopy/common/workingCopy'; import { AutoSaveMode, IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; @@ -312,6 +312,10 @@ export class NativeWindow extends Disposable { this.onDidChangeWindowMaximized(this.environmentService.configuration.maximized ?? false); + // Detect panel position to determine minimum width + this._register(this.layoutService.onDidChangePanelPosition(pos => this.onDidChangePanelPosition(positionFromString(pos)))); + this.onDidChangePanelPosition(this.layoutService.getPanelPosition()); + // Lifecycle this._register(this.lifecycleService.onBeforeShutdownError(e => this.onBeforeShutdownError(e))); this._register(this.lifecycleService.onWillShutdown((e) => this.onWillShutdown(e))); @@ -397,6 +401,23 @@ export class NativeWindow extends Disposable { this.layoutService.updateWindowMaximizedState(maximized); } + private getWindowMinimumWidth(panelPosition: Position = this.layoutService.getPanelPosition()): number { + + // if panel is on the side, then return the larger minwidth + const panelOnSide = panelPosition === Position.LEFT || panelPosition === Position.RIGHT; + if (panelOnSide) { + return WindowMinimumSize.WIDTH_WITH_VERTICAL_PANEL; + } + + return WindowMinimumSize.WIDTH; + } + + private onDidChangePanelPosition(pos: Position): void { + const minWidth = this.getWindowMinimumWidth(pos); + + this.nativeHostService.setMinimumSize(minWidth, undefined); + } + private onDidChangeVisibleEditors(): void { // Close when empty: check if we should close the window based on the setting diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 4120a59a8a228..c7b7ecba631ec 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -99,6 +99,11 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ readonly onDidChangeCenteredLayout: Event; + /* + * Emit when panel position changes. + */ + readonly onDidChangePanelPosition: Event; + /** * Emit when panel alignment changes. */ @@ -201,8 +206,17 @@ export interface IWorkbenchLayoutService extends ILayoutService { */ toggleMenuBar(): void; + /* + * Gets the current panel position. Note that the panel can be hidden too. + */ + getPanelPosition(): Position; + + /** + * Sets the panel position. + */ + setPanelPosition(position: Position): void; + /** - * * Gets the panel alignement. */ getPanelAlignment(): PanelAlignment; diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index 733bf64f86170..87b2cf90c31c6 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -544,6 +544,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { onDidChangeCenteredLayout: Event = Event.None; onDidChangeFullscreen: Event = Event.None; onDidChangeWindowMaximized: Event = Event.None; + onDidChangePanelPosition: Event = Event.None; onDidChangePanelAlignment: Event = Event.None; onDidChangePartVisibility: Event = Event.None; onDidLayout = Event.None; @@ -578,6 +579,7 @@ export class TestLayoutService implements IWorkbenchLayoutService { getMenubarVisibility(): MenuBarVisibility { throw new Error('not implemented'); } toggleMenuBar(): void { } getSideBarPosition() { return 0; } + getPanelPosition() { return 0; } getPanelAlignment(): PanelAlignment { return 'center'; } async setPanelPosition(_position: PartPosition): Promise { } async setPanelAlignment(_alignment: PanelAlignment): Promise { }