From 27abe963904b80371b7c2d56e283b9ed8e5f109d Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 12 Feb 2026 18:54:32 +0100 Subject: [PATCH] - support hiding title in a part (#294970) * - support hiding title in a part - support can move views in workbench - support globalLeftToolbar in pane composite part * clean up * chat feedback * fix compilation --- src/vs/workbench/browser/part.ts | 2 +- .../parts/activitybar/activitybarPart.ts | 19 +- .../parts/auxiliarybar/auxiliaryBarPart.ts | 4 + .../workbench/browser/parts/compositePart.ts | 39 +- .../browser/parts/media/paneCompositePart.css | 10 +- .../browser/parts/paneCompositeBar.ts | 6 +- .../browser/parts/paneCompositePart.ts | 159 ++++---- .../browser/parts/panel/panelPart.ts | 6 +- .../browser/parts/sidebar/sidebarPart.ts | 10 +- .../browser/parts/views/viewPaneContainer.ts | 345 +++++++++--------- src/vs/workbench/common/views.ts | 2 + .../views/browser/viewDescriptorService.ts | 14 + .../parts/activitybar/activitybarPart.test.ts | 2 + 13 files changed, 343 insertions(+), 275 deletions(-) diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index d66a8f5f0ee..50776ac10da 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -50,7 +50,7 @@ export abstract class Part extends Componen constructor( id: string, - private options: IPartOptions, + protected options: IPartOptions, themeService: IThemeService, storageService: IStorageService, protected readonly layoutService: IWorkbenchLayoutService diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index ab1fb9c9d09..6e847c65e2f 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -68,6 +68,7 @@ export class ActivitybarPart extends Part { private _isCompact: boolean; constructor( + private readonly location: ViewContainerLocation, private readonly paneCompositePart: IPaneCompositePart, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @@ -117,7 +118,7 @@ export class ActivitybarPart extends Part { const actionHeight = this._isCompact ? ActivitybarPart.COMPACT_ACTION_HEIGHT : ActivitybarPart.ACTION_HEIGHT; const iconSize = this._isCompact ? ActivitybarPart.COMPACT_ICON_SIZE : ActivitybarPart.ICON_SIZE; - return this.instantiationService.createInstance(ActivityBarCompositeBar, { + return this.instantiationService.createInstance(ActivityBarCompositeBar, this.location, { partContainerClass: 'activitybar', pinnedViewContainersKey: ActivitybarPart.pinnedViewContainersKey, placeholderViewContainersKey: ActivitybarPart.placeholderViewContainersKey, @@ -251,6 +252,7 @@ export class ActivityBarCompositeBar extends PaneCompositeBar { private readonly keyboardNavigationDisposables = this._register(new DisposableStore()); constructor( + location: ViewContainerLocation, options: IPaneCompositeBarOptions, part: Parts, paneCompositePart: IPaneCompositePart, @@ -266,13 +268,14 @@ export class ActivityBarCompositeBar extends PaneCompositeBar { @IMenuService private readonly menuService: IMenuService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, ) { - super({ - ...options, - fillExtraContextMenuActions: (actions, e) => { - options.fillExtraContextMenuActions(actions, e); - this.fillContextMenuActions(actions, e); - } - }, part, paneCompositePart, instantiationService, storageService, extensionService, viewDescriptorService, viewService, contextKeyService, environmentService, layoutService); + super(location, + { + ...options, + fillExtraContextMenuActions: (actions, e) => { + options.fillExtraContextMenuActions(actions, e); + this.fillContextMenuActions(actions, e); + } + }, part, paneCompositePart, instantiationService, storageService, extensionService, viewDescriptorService, viewService, contextKeyService, environmentService, layoutService); if (showGlobalActivities) { this.globalCompositeBar = this._register(instantiationService.createInstance(GlobalCompositeBar, () => this.getContextMenuActions(), (theme: IColorTheme) => this.options.colors(theme), this.options.activityHoverOptions)); diff --git a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts index 7ea1536fb12..bb02c39dc96 100644 --- a/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts +++ b/src/vs/workbench/browser/parts/auxiliarybar/auxiliaryBarPart.ts @@ -33,6 +33,7 @@ import { IConfigurationService } from '../../../../platform/configuration/common import { getContextMenuActions } from '../../../../platform/actions/browser/menuEntryActionViewItem.js'; import { IHoverService } from '../../../../platform/hover/browser/hover.js'; import { VisibleViewContainersTracker } from '../visibleViewContainersTracker.js'; +import { Extensions } from '../../panecomposite.js'; interface IAuxiliaryBarPartConfiguration { position: ActivityBarPosition; @@ -110,6 +111,9 @@ export class AuxiliaryBarPart extends AbstractPaneCompositePart { 'auxiliarybar', undefined, SIDE_BAR_TITLE_BORDER, + ViewContainerLocation.AuxiliaryBar, + Extensions.Auxiliary, + MenuId.AuxiliaryBarTitle, notificationService, storageService, contextMenuService, diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index e879ad7e21e..8b631280705 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -257,8 +257,9 @@ export abstract class CompositePart { + if (this.toolBar) { + this.actionsListener.value = this.toolBar.actionRunner.onDidRun(e => { - // Check for Error - if (e.error && !isCancellationError(e.error)) { - this.notificationService.error(e.error); - } - }); + // Check for Error + if (e.error && !isCancellationError(e.error)) { + this.notificationService.error(e.error); + } + }); + } // Indicate to composite that it is now visible composite.setVisible(true); @@ -338,8 +341,7 @@ export abstract class CompositePart void { @@ -350,12 +352,13 @@ export abstract class CompositePart { - toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions), menuIds); + this.toolBar?.setActions(prepareActions(primaryActions), prepareActions(secondaryActions), menuIds); this.titleArea?.classList.toggle('has-actions', primaryActions.length > 0 || secondaryActions.length > 0); }; } @@ -399,7 +402,10 @@ export abstract class CompositePart .title.has-composite-bar > .title-actions .monaco-action-bar .action-item, -.monaco-workbench .pane-composite-part > .title.has-composite-bar > .global-actions .monaco-action-bar .action-item { +.monaco-workbench .pane-composite-part > .title.has-composite-bar > .global-actions .monaco-action-bar .action-item, +.monaco-workbench .pane-composite-part > .title.has-composite-bar > .global-actions-left .monaco-action-bar .action-item { margin-right: 4px; } @@ -16,6 +17,13 @@ outline-offset: -2px; } +/* Global left actions toolbar: rendered first visually via CSS order */ +.monaco-workbench .pane-composite-part > .title > .global-actions-left { + order: -1; + display: flex; + align-items: center; +} + .monaco-workbench .pane-composite-part > .title.has-composite-bar > .title-label { display: none; } diff --git a/src/vs/workbench/browser/parts/paneCompositeBar.ts b/src/vs/workbench/browser/parts/paneCompositeBar.ts index 585b6aa92f6..bc6003e2f44 100644 --- a/src/vs/workbench/browser/parts/paneCompositeBar.ts +++ b/src/vs/workbench/browser/parts/paneCompositeBar.ts @@ -88,7 +88,6 @@ export interface IPaneCompositeBarOptions { export class PaneCompositeBar extends Disposable { private readonly viewContainerDisposables = this._register(new DisposableMap()); - private readonly location: ViewContainerLocation; private readonly compositeBar: CompositeBar; readonly dndHandler: ICompositeDragAndDrop; @@ -97,6 +96,7 @@ export class PaneCompositeBar extends Disposable { private hasExtensionsRegistered: boolean = false; constructor( + private readonly location: ViewContainerLocation, protected readonly options: IPaneCompositeBarOptions, protected readonly part: Parts, private readonly paneCompositePart: IPaneCompositePart, @@ -111,10 +111,6 @@ export class PaneCompositeBar extends Disposable { ) { super(); - this.location = paneCompositePart.partId === Parts.PANEL_PART - ? ViewContainerLocation.Panel : paneCompositePart.partId === Parts.AUXILIARYBAR_PART - ? ViewContainerLocation.AuxiliaryBar : ViewContainerLocation.Sidebar; - this.dndHandler = new CompositeDragAndDrop(this.viewDescriptorService, this.location, this.options.orientation, async (id: string, focus?: boolean) => { return await this.paneCompositePart.openPaneComposite(id, focus) ?? null; }, (from: string, to: string, before?: Before2D) => this.compositeBar.move(from, to, this.options.orientation === ActionsOrientation.VERTICAL ? before?.verticallyBefore : before?.horizontallyBefore), diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index a452575cf9e..a431fe643f7 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -7,7 +7,7 @@ import './media/paneCompositePart.css'; import { Event } from '../../../base/common/event.js'; import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js'; import { IProgressIndicator } from '../../../platform/progress/common/progress.js'; -import { Extensions, PaneComposite, PaneCompositeDescriptor, PaneCompositeRegistry } from '../panecomposite.js'; +import { PaneComposite, PaneCompositeDescriptor, PaneCompositeRegistry } from '../panecomposite.js'; import { IPaneComposite } from '../../common/panecomposite.js'; import { IViewDescriptorService, ViewContainerLocation } from '../../common/views.js'; import { DisposableStore, MutableDisposable } from '../../../base/common/lifecycle.js'; @@ -117,7 +117,6 @@ export abstract class AbstractPaneCompositePart extends CompositePart { return Event.map(this.onDidCompositeOpen.event, compositeEvent => compositeEvent.composite); } readonly onDidPaneCompositeClose = this.onDidCompositeClose.event as Event; - private readonly location: ViewContainerLocation; private titleContainer: HTMLElement | undefined; private headerFooterCompositeBarContainer: HTMLElement | undefined; protected readonly headerFooterCompositeBarDispoables = this._register(new DisposableStore()); @@ -126,8 +125,8 @@ export abstract class AbstractPaneCompositePart extends CompositePart | undefined = undefined; protected contentDimension: Dimension | undefined; @@ -142,6 +141,9 @@ export abstract class AbstractPaneCompositePart extends CompositePart { - EventHelper.stop(e.eventData, true); - if (this.paneCompositeBar.value) { - const validDropTarget = this.paneCompositeBar.value.dndHandler.onDragEnter(e.dragAndDropData, undefined, e.eventData); - toggleDropEffect(e.eventData.dataTransfer, 'move', validDropTarget); - } - }, - onDragEnter: (e) => { - EventHelper.stop(e.eventData, true); - if (this.paneCompositeBar.value) { - const validDropTarget = this.paneCompositeBar.value.dndHandler.onDragEnter(e.dragAndDropData, undefined, e.eventData); - setDropBackgroundFeedback(validDropTarget); - } - }, - onDragLeave: (e) => { - EventHelper.stop(e.eventData, true); - setDropBackgroundFeedback(false); - }, - onDragEnd: (e) => { - EventHelper.stop(e.eventData, true); - setDropBackgroundFeedback(false); - }, - onDrop: (e) => { - EventHelper.stop(e.eventData, true); - setDropBackgroundFeedback(false); - if (this.paneCompositeBar.value) { - this.paneCompositeBar.value.dndHandler.drop(e.dragAndDropData, undefined, e.eventData); - } else { - // Allow opening views/composites if the composite bar is hidden - const dragData = e.dragAndDropData.getData(); - - if (dragData.type === 'composite') { - const currentContainer = this.viewDescriptorService.getViewContainerById(dragData.id)!; - this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.location, undefined, 'dnd'); - this.openPaneComposite(currentContainer.id, true); + if (this.viewDescriptorService.canMoveViews()) { + this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(this.element, { + onDragOver: (e) => { + EventHelper.stop(e.eventData, true); + if (this.paneCompositeBar.value) { + const validDropTarget = this.paneCompositeBar.value.dndHandler.onDragEnter(e.dragAndDropData, undefined, e.eventData); + toggleDropEffect(e.eventData.dataTransfer, 'move', validDropTarget); } + }, + onDragEnter: (e) => { + EventHelper.stop(e.eventData, true); + if (this.paneCompositeBar.value) { + const validDropTarget = this.paneCompositeBar.value.dndHandler.onDragEnter(e.dragAndDropData, undefined, e.eventData); + setDropBackgroundFeedback(validDropTarget); + } + }, + onDragLeave: (e) => { + EventHelper.stop(e.eventData, true); + setDropBackgroundFeedback(false); + }, + onDragEnd: (e) => { + EventHelper.stop(e.eventData, true); + setDropBackgroundFeedback(false); + }, + onDrop: (e) => { + EventHelper.stop(e.eventData, true); + setDropBackgroundFeedback(false); + if (this.paneCompositeBar.value) { + this.paneCompositeBar.value.dndHandler.drop(e.dragAndDropData, undefined, e.eventData); + } else { + // Allow opening views/composites if the composite bar is hidden + const dragData = e.dragAndDropData.getData(); - else if (dragData.type === 'view') { - const viewToMove = this.viewDescriptorService.getViewDescriptorById(dragData.id)!; - if (viewToMove.canMoveView) { - this.viewDescriptorService.moveViewToLocation(viewToMove, this.location, 'dnd'); + if (dragData.type === 'composite') { + const currentContainer = this.viewDescriptorService.getViewContainerById(dragData.id)!; + this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.location, undefined, 'dnd'); + this.openPaneComposite(currentContainer.id, true); + } - const newContainer = this.viewDescriptorService.getViewContainerByViewId(viewToMove.id)!; + else if (dragData.type === 'view') { + const viewToMove = this.viewDescriptorService.getViewDescriptorById(dragData.id)!; + if (viewToMove.canMoveView) { + this.viewDescriptorService.moveViewToLocation(viewToMove, this.location, 'dnd'); - this.openPaneComposite(newContainer.id, true).then(composite => { - composite?.openView(viewToMove.id, true); - }); + const newContainer = this.viewDescriptorService.getViewContainerByViewId(viewToMove.id)!; + + this.openPaneComposite(newContainer.id, true).then(composite => { + composite?.openView(viewToMove.id, true); + }); + } } } - } - }, - })); + }, + })); + } } - protected override createTitleArea(parent: HTMLElement): HTMLElement { + protected override createTitleArea(parent: HTMLElement): HTMLElement | undefined { const titleArea = super.createTitleArea(parent); + if (!titleArea) { + return undefined; + } this._register(addDisposableListener(titleArea, EventType.CONTEXT_MENU, e => { this.onTitleAreaContextMenu(new StandardMouseEvent(getWindow(titleArea), e)); @@ -349,6 +341,26 @@ export abstract class AbstractPaneCompositePart extends CompositePart this.actionViewItemProvider(action, options), + orientation: ActionsOrientation.HORIZONTAL, + getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id), + anchorAlignmentProvider: () => this.getTitleAreaDropDownAnchorAlignment(), + hoverDelegate: this.toolbarHoverDelegate, + hiddenItemStrategy: HiddenItemStrategy.NoHide, + highlightToggledItems: false, + telemetrySource: this.nameForTelemetry + } + )); + } + const globalTitleActionsContainer = titleArea.appendChild($('.global-actions')); // Global Actions Toolbar @@ -375,7 +387,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart { const activeViewlet = this.getActivePaneComposite()!; return { type: 'composite', id: activeViewlet.getId() }; @@ -489,7 +501,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart extends Comp let bounds: BoundingRect; - this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(parent, { - onDragEnter: (e) => { - bounds = getOverlayBounds(); - if (overlay?.disposed) { - overlay = undefined; - } - - if (!overlay && inBounds(bounds, e.eventData)) { - const dropData = e.dragAndDropData.getData(); - if (dropData.type === 'view') { - - const oldViewContainer = this.viewDescriptorService.getViewContainerByViewId(dropData.id); - const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dropData.id); - - if (oldViewContainer !== this.viewContainer && (!viewDescriptor || !viewDescriptor.canMoveView || this.viewContainer.rejectAddedViews)) { - return; - } - - overlay = new ViewPaneDropOverlay(parent, undefined, bounds, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService); + if (this.viewDescriptorService.canMoveViews()) { + this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(parent, { + onDragEnter: (e) => { + bounds = getOverlayBounds(); + if (overlay?.disposed) { + overlay = undefined; } - if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) { - const container = this.viewDescriptorService.getViewContainerById(dropData.id)!; - const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; + if (!overlay && inBounds(bounds, e.eventData)) { + const dropData = e.dragAndDropData.getData(); + if (dropData.type === 'view') { + + const oldViewContainer = this.viewDescriptorService.getViewContainerByViewId(dropData.id); + const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dropData.id); + + if (oldViewContainer !== this.viewContainer && (!viewDescriptor || !viewDescriptor.canMoveView || this.viewContainer.rejectAddedViews)) { + return; + } - if (!viewsToMove.some(v => !v.canMoveView) && viewsToMove.length > 0) { overlay = new ViewPaneDropOverlay(parent, undefined, bounds, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService); } - } - } - }, - onDragOver: (e) => { - if (overlay?.disposed) { - overlay = undefined; - } - if (overlay && !inBounds(bounds, e.eventData)) { - overlay.dispose(); - overlay = undefined; - } + if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) { + const container = this.viewDescriptorService.getViewContainerById(dropData.id)!; + const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; - if (inBounds(bounds, e.eventData)) { - toggleDropEffect(e.eventData.dataTransfer, 'move', overlay !== undefined); - } - }, - onDragLeave: (e) => { - overlay?.dispose(); - overlay = undefined; - }, - onDrop: (e) => { - if (overlay) { - const dropData = e.dragAndDropData.getData(); - const viewsToMove: IViewDescriptor[] = []; - - if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) { - const container = this.viewDescriptorService.getViewContainerById(dropData.id)!; - const allViews = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; - if (!allViews.some(v => !v.canMoveView)) { - viewsToMove.push(...allViews); - } - } else if (dropData.type === 'view') { - const oldViewContainer = this.viewDescriptorService.getViewContainerByViewId(dropData.id); - const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dropData.id); - if (oldViewContainer !== this.viewContainer && viewDescriptor?.canMoveView) { - this.viewDescriptorService.moveViewsToContainer([viewDescriptor], this.viewContainer, undefined, 'dnd'); - } - } - - const paneCount = this.panes.length; - - if (viewsToMove.length > 0) { - this.viewDescriptorService.moveViewsToContainer(viewsToMove, this.viewContainer, undefined, 'dnd'); - } - - if (paneCount > 0) { - for (const view of viewsToMove) { - const paneToMove = this.panes.find(p => p.id === view.id); - if (paneToMove) { - this.movePane(paneToMove, this.panes[this.panes.length - 1]); + if (!viewsToMove.some(v => !v.canMoveView) && viewsToMove.length > 0) { + overlay = new ViewPaneDropOverlay(parent, undefined, bounds, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService); } } } - } + }, + onDragOver: (e) => { + if (overlay?.disposed) { + overlay = undefined; + } - overlay?.dispose(); - overlay = undefined; - } - })); + if (overlay && !inBounds(bounds, e.eventData)) { + overlay.dispose(); + overlay = undefined; + } + + if (inBounds(bounds, e.eventData)) { + toggleDropEffect(e.eventData.dataTransfer, 'move', overlay !== undefined); + } + }, + onDragLeave: (e) => { + overlay?.dispose(); + overlay = undefined; + }, + onDrop: (e) => { + if (overlay) { + const dropData = e.dragAndDropData.getData(); + const viewsToMove: IViewDescriptor[] = []; + + if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id) { + const container = this.viewDescriptorService.getViewContainerById(dropData.id)!; + const allViews = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; + if (!allViews.some(v => !v.canMoveView)) { + viewsToMove.push(...allViews); + } + } else if (dropData.type === 'view') { + const oldViewContainer = this.viewDescriptorService.getViewContainerByViewId(dropData.id); + const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dropData.id); + if (oldViewContainer !== this.viewContainer && viewDescriptor?.canMoveView) { + this.viewDescriptorService.moveViewsToContainer([viewDescriptor], this.viewContainer, undefined, 'dnd'); + } + } + + const paneCount = this.panes.length; + + if (viewsToMove.length > 0) { + this.viewDescriptorService.moveViewsToContainer(viewsToMove, this.viewContainer, undefined, 'dnd'); + } + + if (paneCount > 0) { + for (const view of viewsToMove) { + const paneToMove = this.panes.find(p => p.id === view.id); + if (paneToMove) { + this.movePane(paneToMove, this.panes[this.panes.length - 1]); + } + } + } + } + + overlay?.dispose(); + overlay = undefined; + } + })); + } this._register(this.onDidSashChange(() => this.saveViewSizes())); this._register(this.viewContainerModel.onDidAddVisibleViewDescriptors(added => this.onDidAddViewDescriptors(added))); @@ -874,111 +876,82 @@ export class ViewPaneContainer extends Comp let overlay: ViewPaneDropOverlay | undefined; - if (pane.draggableElement) { - store.add(CompositeDragAndDropObserver.INSTANCE.registerDraggable(pane.draggableElement, () => { return { type: 'view', id: pane.id }; }, {})); - } + if (this.viewDescriptorService.canMoveViews()) { - store.add(CompositeDragAndDropObserver.INSTANCE.registerTarget(pane.dropTargetElement, { - onDragEnter: (e) => { - if (!overlay) { - const dropData = e.dragAndDropData.getData(); - if (dropData.type === 'view' && dropData.id !== pane.id) { + if (pane.draggableElement) { + store.add(CompositeDragAndDropObserver.INSTANCE.registerDraggable(pane.draggableElement, () => { return { type: 'view', id: pane.id }; }, {})); + } - const oldViewContainer = this.viewDescriptorService.getViewContainerByViewId(dropData.id); - const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dropData.id); + store.add(CompositeDragAndDropObserver.INSTANCE.registerTarget(pane.dropTargetElement, { + onDragEnter: (e) => { + if (!overlay) { + const dropData = e.dragAndDropData.getData(); + if (dropData.type === 'view' && dropData.id !== pane.id) { - if (oldViewContainer !== this.viewContainer && (!viewDescriptor || !viewDescriptor.canMoveView || this.viewContainer.rejectAddedViews)) { - return; - } + const oldViewContainer = this.viewDescriptorService.getViewContainerByViewId(dropData.id); + const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dropData.id); - overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, undefined, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService); - } + if (oldViewContainer !== this.viewContainer && (!viewDescriptor || !viewDescriptor.canMoveView || this.viewContainer.rejectAddedViews)) { + return; + } - if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id && !this.viewContainer.rejectAddedViews) { - const container = this.viewDescriptorService.getViewContainerById(dropData.id)!; - const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; - - if (!viewsToMove.some(v => !v.canMoveView) && viewsToMove.length > 0) { overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, undefined, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService); } - } - } - }, - onDragOver: (e) => { - toggleDropEffect(e.eventData.dataTransfer, 'move', overlay !== undefined); - }, - onDragLeave: (e) => { - overlay?.dispose(); - overlay = undefined; - }, - onDrop: (e) => { - if (overlay) { - const dropData = e.dragAndDropData.getData(); - const viewsToMove: IViewDescriptor[] = []; - let anchorView: IViewDescriptor | undefined; - if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id && !this.viewContainer.rejectAddedViews) { - const container = this.viewDescriptorService.getViewContainerById(dropData.id)!; - const allViews = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; + if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id && !this.viewContainer.rejectAddedViews) { + const container = this.viewDescriptorService.getViewContainerById(dropData.id)!; + const viewsToMove = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; - if (allViews.length > 0 && !allViews.some(v => !v.canMoveView)) { - viewsToMove.push(...allViews); - anchorView = allViews[0]; - } - } else if (dropData.type === 'view') { - const oldViewContainer = this.viewDescriptorService.getViewContainerByViewId(dropData.id); - const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dropData.id); - if (oldViewContainer !== this.viewContainer && viewDescriptor && viewDescriptor.canMoveView && !this.viewContainer.rejectAddedViews) { - viewsToMove.push(viewDescriptor); - } - - if (viewDescriptor) { - anchorView = viewDescriptor; + if (!viewsToMove.some(v => !v.canMoveView) && viewsToMove.length > 0) { + overlay = new ViewPaneDropOverlay(pane.dropTargetElement, this.orientation ?? Orientation.VERTICAL, undefined, this.viewDescriptorService.getViewContainerLocation(this.viewContainer)!, this.themeService); + } } } + }, + onDragOver: (e) => { + toggleDropEffect(e.eventData.dataTransfer, 'move', overlay !== undefined); + }, + onDragLeave: (e) => { + overlay?.dispose(); + overlay = undefined; + }, + onDrop: (e) => { + if (overlay) { + const dropData = e.dragAndDropData.getData(); + const viewsToMove: IViewDescriptor[] = []; + let anchorView: IViewDescriptor | undefined; - if (viewsToMove) { - this.viewDescriptorService.moveViewsToContainer(viewsToMove, this.viewContainer, undefined, 'dnd'); - } + if (dropData.type === 'composite' && dropData.id !== this.viewContainer.id && !this.viewContainer.rejectAddedViews) { + const container = this.viewDescriptorService.getViewContainerById(dropData.id)!; + const allViews = this.viewDescriptorService.getViewContainerModel(container).allViewDescriptors; - if (anchorView) { - if (overlay.currentDropOperation === DropDirection.DOWN || - overlay.currentDropOperation === DropDirection.RIGHT) { + if (allViews.length > 0 && !allViews.some(v => !v.canMoveView)) { + viewsToMove.push(...allViews); + anchorView = allViews[0]; + } + } else if (dropData.type === 'view') { + const oldViewContainer = this.viewDescriptorService.getViewContainerByViewId(dropData.id); + const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dropData.id); + if (oldViewContainer !== this.viewContainer && viewDescriptor && viewDescriptor.canMoveView && !this.viewContainer.rejectAddedViews) { + viewsToMove.push(viewDescriptor); + } - const fromIndex = this.panes.findIndex(p => p.id === anchorView!.id); - let toIndex = this.panes.findIndex(p => p.id === pane.id); - - if (fromIndex >= 0 && toIndex >= 0) { - if (fromIndex > toIndex) { - toIndex++; - } - - if (toIndex < this.panes.length && toIndex !== fromIndex) { - this.movePane(this.panes[fromIndex], this.panes[toIndex]); - } + if (viewDescriptor) { + anchorView = viewDescriptor; } } - if (overlay.currentDropOperation === DropDirection.UP || - overlay.currentDropOperation === DropDirection.LEFT) { - const fromIndex = this.panes.findIndex(p => p.id === anchorView!.id); - let toIndex = this.panes.findIndex(p => p.id === pane.id); - - if (fromIndex >= 0 && toIndex >= 0) { - if (fromIndex < toIndex) { - toIndex--; - } - - if (toIndex >= 0 && toIndex !== fromIndex) { - this.movePane(this.panes[fromIndex], this.panes[toIndex]); - } - } + if (viewsToMove) { + this.viewDescriptorService.moveViewsToContainer(viewsToMove, this.viewContainer, undefined, 'dnd'); } - if (viewsToMove.length > 1) { - viewsToMove.slice(1).forEach(view => { - let toIndex = this.panes.findIndex(p => p.id === anchorView!.id); - const fromIndex = this.panes.findIndex(p => p.id === view.id); + if (anchorView) { + if (overlay.currentDropOperation === DropDirection.DOWN || + overlay.currentDropOperation === DropDirection.RIGHT) { + + const fromIndex = this.panes.findIndex(p => p.id === anchorView!.id); + let toIndex = this.panes.findIndex(p => p.id === pane.id); + if (fromIndex >= 0 && toIndex >= 0) { if (fromIndex > toIndex) { toIndex++; @@ -986,18 +959,50 @@ export class ViewPaneContainer extends Comp if (toIndex < this.panes.length && toIndex !== fromIndex) { this.movePane(this.panes[fromIndex], this.panes[toIndex]); - anchorView = view; } } - }); + } + + if (overlay.currentDropOperation === DropDirection.UP || + overlay.currentDropOperation === DropDirection.LEFT) { + const fromIndex = this.panes.findIndex(p => p.id === anchorView!.id); + let toIndex = this.panes.findIndex(p => p.id === pane.id); + + if (fromIndex >= 0 && toIndex >= 0) { + if (fromIndex < toIndex) { + toIndex--; + } + + if (toIndex >= 0 && toIndex !== fromIndex) { + this.movePane(this.panes[fromIndex], this.panes[toIndex]); + } + } + } + + if (viewsToMove.length > 1) { + viewsToMove.slice(1).forEach(view => { + let toIndex = this.panes.findIndex(p => p.id === anchorView!.id); + const fromIndex = this.panes.findIndex(p => p.id === view.id); + if (fromIndex >= 0 && toIndex >= 0) { + if (fromIndex > toIndex) { + toIndex++; + } + + if (toIndex < this.panes.length && toIndex !== fromIndex) { + this.movePane(this.panes[fromIndex], this.panes[toIndex]); + anchorView = view; + } + } + }); + } } } - } - overlay?.dispose(); - overlay = undefined; - } - })); + overlay?.dispose(); + overlay = undefined; + } + })); + } } removePanes(panes: ViewPane[]): void { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 9c60b15706c..0a903822543 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -609,6 +609,8 @@ export interface IViewDescriptorService { getDefaultContainerById(id: string): ViewContainer | null; getViewLocationById(id: string): ViewContainerLocation | null; + canMoveViews(): boolean; + readonly onDidChangeContainer: Event<{ views: IViewDescriptor[]; from: ViewContainer; to: ViewContainer }>; moveViewsToContainer(views: IViewDescriptor[], viewContainer: ViewContainer, visibilityState?: ViewVisibilityState, reason?: string): void; diff --git a/src/vs/workbench/services/views/browser/viewDescriptorService.ts b/src/vs/workbench/services/views/browser/viewDescriptorService.ts index 2345f6dda14..a25cf00bfc9 100644 --- a/src/vs/workbench/services/views/browser/viewDescriptorService.ts +++ b/src/vs/workbench/services/views/browser/viewDescriptorService.ts @@ -311,7 +311,14 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return this.viewContainersRegistry.getDefaultViewContainer(location); } + canMoveViews(): boolean { + return true; + } + moveViewContainerToLocation(viewContainer: ViewContainer, location: ViewContainerLocation, requestedIndex?: number, reason?: string): void { + if (!this.canMoveViews()) { + return; + } this.logger.value.trace(`moveViewContainerToLocation: viewContainer:${viewContainer.id} location:${location} reason:${reason}`); this.moveViewContainerToLocationWithoutSaving(viewContainer, location, requestedIndex); this.saveViewCustomizations(); @@ -327,6 +334,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor } moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation, reason?: string): void { + if (!this.canMoveViews()) { + return; + } this.logger.value.trace(`moveViewToLocation: view:${view.id} location:${location} reason:${reason}`); const container = this.registerGeneratedViewContainer(location); this.moveViewsToContainer([view], container); @@ -337,6 +347,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor return; } + if (!this.canMoveViews()) { + return; + } + this.logger.value.trace(`moveViewsToContainer: views:${views.map(view => view.id).join(',')} viewContainer:${viewContainer.id} reason:${reason}`); const from = this.getViewContainerByViewId(views[0].id); diff --git a/src/vs/workbench/test/browser/parts/activitybar/activitybarPart.test.ts b/src/vs/workbench/test/browser/parts/activitybar/activitybarPart.test.ts index 3f62cde9f8a..e28ed97e587 100644 --- a/src/vs/workbench/test/browser/parts/activitybar/activitybarPart.test.ts +++ b/src/vs/workbench/test/browser/parts/activitybar/activitybarPart.test.ts @@ -20,6 +20,7 @@ import { Event, Emitter } from '../../../../../base/common/event.js'; import { IPaneComposite } from '../../../../common/panecomposite.js'; import { PaneCompositeDescriptor } from '../../../../browser/panecomposite.js'; import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js'; +import { ViewContainerLocation } from '../../../../common/views.js'; class StubPaneCompositePart implements IPaneCompositePart { declare readonly _serviceBrand: undefined; @@ -81,6 +82,7 @@ suite('ActivitybarPart', () => { const stubInstantiationService = { createInstance: () => { throw new Error('not expected'); } } as unknown as IInstantiationService; const part = disposables.add(new ActivitybarPart( + ViewContainerLocation.Sidebar, new StubPaneCompositePart(), stubInstantiationService, layoutService,