- 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
This commit is contained in:
Sandeep Somavarapu
2026-02-12 18:54:32 +01:00
committed by GitHub
parent 14b8c701f1
commit 27abe96390
13 changed files with 343 additions and 275 deletions

View File

@@ -50,7 +50,7 @@ export abstract class Part<MementoType extends object = object> extends Componen
constructor(
id: string,
private options: IPartOptions,
protected options: IPartOptions,
themeService: IThemeService,
storageService: IStorageService,
protected readonly layoutService: IWorkbenchLayoutService

View File

@@ -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));

View File

@@ -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,

View File

@@ -257,8 +257,9 @@ export abstract class CompositePart<T extends Composite, MementoType extends obj
show(compositeContainer);
// Setup action runner
const toolBar = assertReturnsDefined(this.toolBar);
toolBar.actionRunner = composite.getActionRunner();
if (this.toolBar) {
this.toolBar.actionRunner = composite.getActionRunner();
}
// Update title with composite title if it differs from descriptor
const descriptor = this.registry.getComposite(composite.getId());
@@ -275,13 +276,15 @@ export abstract class CompositePart<T extends Composite, MementoType extends obj
actionsBinding();
// Action Run Handling
this.actionsListener.value = toolBar.actionRunner.onDidRun(e => {
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<T extends Composite, MementoType extends obj
this.titleLabel.updateTitle(compositeId, compositeTitle, keybinding?.getLabel() ?? undefined);
const toolBar = assertReturnsDefined(this.toolBar);
toolBar.setAriaLabel(localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle));
this.toolBar?.setAriaLabel(localize('ariaCompositeToolbarLabel', "{0} actions", compositeTitle));
}
private collectCompositeActions(composite?: Composite): () => void {
@@ -350,12 +352,13 @@ export abstract class CompositePart<T extends Composite, MementoType extends obj
const secondaryActions: IAction[] = composite?.getSecondaryActions().slice(0) || [];
// Update context
const toolBar = assertReturnsDefined(this.toolBar);
toolBar.context = this.actionsContextProvider();
if (this.toolBar) {
this.toolBar.context = this.actionsContextProvider();
}
// Return fn to set into toolbar
return () => {
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<T extends Composite, MementoType extends obj
return composite;
}
protected override createTitleArea(parent: HTMLElement): HTMLElement {
protected override createTitleArea(parent: HTMLElement): HTMLElement | undefined {
if (!this.options.hasTitle) {
return undefined;
}
// Title Area Container
const titleArea = append(parent, $('.composite'));
@@ -463,9 +469,8 @@ export abstract class CompositePart<T extends Composite, MementoType extends obj
override updateStyles(): void {
super.updateStyles();
// Forward to title label
const titleLabel = assertReturnsDefined(this.titleLabel);
titleLabel.updateStyles();
// Forward to title label if present
this.titleLabel?.updateStyles();
}
protected actionViewItemProvider(action: IAction, options: IBaseActionViewItemOptions): IActionViewItem | undefined {

View File

@@ -8,7 +8,8 @@
}
.monaco-workbench .pane-composite-part > .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;
}

View File

@@ -88,7 +88,6 @@ export interface IPaneCompositeBarOptions {
export class PaneCompositeBar extends Disposable {
private readonly viewContainerDisposables = this._register(new DisposableMap<string, IDisposable>());
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),

View File

@@ -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<PaneCompos
get onDidPaneCompositeOpen(): Event<IPaneComposite> { return Event.map(this.onDidCompositeOpen.event, compositeEvent => <IPaneComposite>compositeEvent.composite); }
readonly onDidPaneCompositeClose = this.onDidCompositeClose.event as Event<IPaneComposite>;
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<PaneCompos
private compositeBarPosition: CompositeBarPosition | undefined = undefined;
private emptyPaneMessageElement: HTMLElement | undefined;
private readonly globalActionsMenuId: MenuId;
private globalToolBar: MenuWorkbenchToolBar | undefined;
private globalLeftToolBar: MenuWorkbenchToolBar | undefined;
private blockOpening: DeferredPromise<PaneComposite | undefined> | undefined = undefined;
protected contentDimension: Dimension | undefined;
@@ -142,6 +141,9 @@ export abstract class AbstractPaneCompositePart extends CompositePart<PaneCompos
compositeCSSClass: string,
titleForegroundColor: string | undefined,
titleBorderColor: string | undefined,
protected readonly location: ViewContainerLocation,
registryId: string,
private readonly globalActionsMenuId: MenuId,
@INotificationService notificationService: INotificationService,
@IStorageService storageService: IStorageService,
@IContextMenuService contextMenuService: IContextMenuService,
@@ -155,18 +157,6 @@ export abstract class AbstractPaneCompositePart extends CompositePart<PaneCompos
@IExtensionService private readonly extensionService: IExtensionService,
@IMenuService protected readonly menuService: IMenuService,
) {
let location = ViewContainerLocation.Sidebar;
let registryId = Extensions.Viewlets;
let globalActionsMenuId = MenuId.SidebarTitle;
if (partId === Parts.PANEL_PART) {
location = ViewContainerLocation.Panel;
registryId = Extensions.Panels;
globalActionsMenuId = MenuId.PanelTitle;
} else if (partId === Parts.AUXILIARYBAR_PART) {
location = ViewContainerLocation.AuxiliaryBar;
registryId = Extensions.Auxiliary;
globalActionsMenuId = MenuId.AuxiliaryBarTitle;
}
super(
notificationService,
storageService,
@@ -186,9 +176,6 @@ export abstract class AbstractPaneCompositePart extends CompositePart<PaneCompos
partId,
partOptions
);
this.location = location;
this.globalActionsMenuId = globalActionsMenuId;
this.registerListeners();
}
@@ -283,63 +270,68 @@ export abstract class AbstractPaneCompositePart extends CompositePart<PaneCompos
this.emptyPaneMessageElement!.style.backgroundColor = backgroundColor;
};
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();
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<PaneCompos
this.onTitleAreaContextMenu(new StandardMouseEvent(getWindow(titleArea), e));
}));
// Global Left Actions Toolbar (optional, subclasses provide a menu ID)
const globalLeftActionsMenuId = this.getGlobalLeftActionsMenuId();
if (globalLeftActionsMenuId) {
const globalLeftTitleActionsContainer = titleArea.appendChild($('.global-actions-left'));
this.globalLeftToolBar = this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar,
globalLeftTitleActionsContainer,
globalLeftActionsMenuId,
{
actionViewItemProvider: (action, options) => 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<PaneCompos
this.titleContainer = parent;
const titleLabel = super.createTitleLabel(parent);
this.titleLabelElement!.draggable = true;
this.titleLabelElement!.draggable = this.viewDescriptorService.canMoveViews();
const draggedItemProvider = (): { type: 'view' | 'composite'; id: string } => {
const activeViewlet = this.getActivePaneComposite()!;
return { type: 'composite', id: activeViewlet.getId() };
@@ -489,7 +501,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart<PaneCompos
}
protected createCompositeBar(): PaneCompositeBar {
return this.instantiationService.createInstance(PaneCompositeBar, this.getCompositeBarOptions(), this.partId, this);
return this.instantiationService.createInstance(PaneCompositeBar, this.location, this.getCompositeBarOptions(), this.partId, this);
}
protected override onTitleAreaUpdate(compositeId: string): void {
@@ -642,7 +654,8 @@ export abstract class AbstractPaneCompositePart extends CompositePart<PaneCompos
// Each toolbar item has 4px margin
const toolBarWidth = this.toolBar.getItemsWidth() + this.toolBar.getItemsLength() * 4;
const globalToolBarWidth = this.globalToolBar ? this.globalToolBar.getItemsWidth() + this.globalToolBar.getItemsLength() * 4 : 0;
return toolBarWidth + globalToolBarWidth + 8; // 8px padding left
const globalLeftToolBarWidth = this.globalLeftToolBar ? this.globalLeftToolBar.getItemsWidth() + this.globalLeftToolBar.getItemsLength() * 4 : 0;
return toolBarWidth + globalToolBarWidth + globalLeftToolBarWidth + 8; // 8px padding left
}
private onTitleAreaContextMenu(event: StandardMouseEvent): void {
@@ -694,6 +707,14 @@ export abstract class AbstractPaneCompositePart extends CompositePart<PaneCompos
return undefined;
}
/**
* Override in subclasses to provide a menu ID for a global toolbar on the left side
* of the composite bar / title area. Returns `undefined` by default (no left toolbar).
*/
protected getGlobalLeftActionsMenuId(): MenuId | undefined {
return undefined;
}
protected abstract shouldShowCompositeBar(): boolean;
protected abstract getCompositeBarOptions(): IPaneCompositeBarOptions;
protected abstract getCompositeBarPosition(): CompositeBarPosition;

View File

@@ -22,7 +22,7 @@ import { Dimension } from '../../../../base/browser/dom.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { assertReturnsDefined } from '../../../../base/common/types.js';
import { IExtensionService } from '../../../services/extensions/common/extensions.js';
import { IViewDescriptorService } from '../../../common/views.js';
import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js';
import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';
import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js';
import { AbstractPaneCompositePart, CompositeBarPosition } from '../paneCompositePart.js';
@@ -31,6 +31,7 @@ import { getContextMenuActions } from '../../../../platform/actions/browser/menu
import { IPaneCompositeBarOptions } from '../paneCompositeBar.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { Extensions } from '../../panecomposite.js';
export class PanelPart extends AbstractPaneCompositePart {
@@ -92,6 +93,9 @@ export class PanelPart extends AbstractPaneCompositePart {
'panel',
undefined,
PANEL_TITLE_BORDER,
ViewContainerLocation.Panel,
Extensions.Panels,
MenuId.PanelTitle,
notificationService,
storageService,
contextMenuService,

View File

@@ -27,12 +27,13 @@ import { ActionsOrientation } from '../../../../base/browser/ui/actionbar/action
import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';
import { IPaneCompositeBarOptions } from '../paneCompositeBar.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { Action2, IMenuService, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { Action2, IMenuService, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
import { Separator } from '../../../../base/common/actions.js';
import { ToggleActivityBarVisibilityActionId } from '../../actions/layoutActions.js';
import { localize2 } from '../../../../nls.js';
import { IHoverService } from '../../../../platform/hover/browser/hover.js';
import { VisibleViewContainersTracker } from '../visibleViewContainersTracker.js';
import { Extensions } from '../../panecomposite.js';
export class SidebarPart extends AbstractPaneCompositePart {
@@ -63,7 +64,7 @@ export class SidebarPart extends AbstractPaneCompositePart {
return Math.max(width, 300);
}
private readonly activityBarPart = this._register(this.instantiationService.createInstance(ActivitybarPart, this));
private readonly activityBarPart = this._register(this.instantiationService.createInstance(ActivitybarPart, this.location, this));
private readonly visibleViewContainersTracker: VisibleViewContainersTracker;
//#endregion
@@ -93,6 +94,9 @@ export class SidebarPart extends AbstractPaneCompositePart {
'viewlet',
SIDE_BAR_TITLE_FOREGROUND,
SIDE_BAR_TITLE_BORDER,
ViewContainerLocation.Sidebar,
Extensions.Viewlets,
MenuId.SidebarTitle,
notificationService,
storageService,
contextMenuService,
@@ -186,7 +190,7 @@ export class SidebarPart extends AbstractPaneCompositePart {
}
protected override createCompositeBar(): ActivityBarCompositeBar {
return this.instantiationService.createInstance(ActivityBarCompositeBar, this.getCompositeBarOptions(), this.partId, this, false);
return this.instantiationService.createInstance(ActivityBarCompositeBar, ViewContainerLocation.Sidebar, this.getCompositeBarOptions(), this.partId, this, false);
}
protected getCompositeBarOptions(): IPaneCompositeBarOptions {

View File

@@ -421,94 +421,96 @@ export class ViewPaneContainer<MementoType extends object = object> 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<MementoType extends object = object> 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<MementoType extends object = object> 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 {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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,