compositeBar.ts 29.6 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import * as nls from 'vs/nls';
7
import { IAction, toAction } from 'vs/base/common/actions';
8 9
import { illegalArgument } from 'vs/base/common/errors';
import * as arrays from 'vs/base/common/arrays';
S
Sandeep Somavarapu 已提交
10
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
I
isidor 已提交
11
import { IBadge } from 'vs/workbench/services/activity/common/activity';
12
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
J
João Moreno 已提交
13
import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
14
import { CompositeActionViewItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionViewItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions';
15
import { Dimension, $, addDisposableListener, EventType, EventHelper, isAncestor } from 'vs/base/browser/dom';
16 17 18
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { Widget } from 'vs/base/browser/ui/widget';
S
Sandeep Somavarapu 已提交
19
import { isUndefinedOrNull } from 'vs/base/common/types';
S
#93038  
SteVen Batten 已提交
20
import { IColorTheme } from 'vs/platform/theme/common/themeService';
S
SteVen Batten 已提交
21
import { Emitter } from 'vs/base/common/event';
22
import { ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
S
SteVen Batten 已提交
23
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
24
import { IComposite } from 'vs/workbench/common/composite';
S
SteVen Batten 已提交
25
import { CompositeDragAndDropData, CompositeDragAndDropObserver, IDraggedCompositeData, ICompositeDragAndDrop, Before2D, toggleDropEffect } from 'vs/workbench/browser/dnd';
26 27 28

export interface ICompositeBarItem {
	id: string;
M
Matt Bierner 已提交
29
	name?: string;
30
	pinned: boolean;
M
Matt Bierner 已提交
31
	order?: number;
32 33
	visible: boolean;
}
34

35 36 37 38
export class CompositeDragAndDrop implements ICompositeDragAndDrop {

	constructor(
		private viewDescriptorService: IViewDescriptorService,
S
SteVen Batten 已提交
39
		private targetContainerLocation: ViewContainerLocation,
40
		private openComposite: (id: string, focus?: boolean) => Promise<IPaneComposite | null>,
S
SteVen Batten 已提交
41
		private moveComposite: (from: string, to: string, before?: Before2D) => void,
42
		private getItems: () => ICompositeBarItem[],
43
	) { }
B
Benjamin Pasero 已提交
44

S
SteVen Batten 已提交
45
	drop(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent, before?: Before2D): void {
46 47 48
		const dragData = data.getData();

		if (dragData.type === 'composite') {
49 50
			const currentContainer = this.viewDescriptorService.getViewContainerById(dragData.id)!;
			const currentLocation = this.viewDescriptorService.getViewContainerLocation(currentContainer);
51

S
SteVen Batten 已提交
52 53
			// ... on the same composite bar
			if (currentLocation === this.targetContainerLocation) {
S
SteVen Batten 已提交
54 55 56
				if (targetCompositeId) {
					this.moveComposite(dragData.id, targetCompositeId, before);
				}
S
SteVen Batten 已提交
57 58 59
			}
			// ... on a different composite bar
			else {
60
				const viewsToMove = this.viewDescriptorService.getViewContainerModel(currentContainer)!.allViewDescriptors;
S
SteVen Batten 已提交
61 62
				if (viewsToMove.some(v => !v.canMoveView)) {
					return;
63 64
				}

65
				this.viewDescriptorService.moveViewContainerToLocation(currentContainer, this.targetContainerLocation, this.getTargetIndex(targetCompositeId, before));
66
			}
S
SteVen Batten 已提交
67 68 69
		}

		if (dragData.type === 'view') {
70
			const viewToMove = this.viewDescriptorService.getViewDescriptorById(dragData.id)!;
71

S
SteVen Batten 已提交
72 73
			if (viewToMove && viewToMove.canMoveView) {
				this.viewDescriptorService.moveViewToLocation(viewToMove, this.targetContainerLocation);
74

75
				const newContainer = this.viewDescriptorService.getViewContainerByViewId(viewToMove.id)!;
76

S
SteVen Batten 已提交
77 78 79
				if (targetCompositeId) {
					this.moveComposite(newContainer.id, targetCompositeId, before);
				}
80

S
SteVen Batten 已提交
81 82 83 84 85
				this.openComposite(newContainer.id, true).then(composite => {
					if (composite) {
						composite.openView(viewToMove.id, true);
					}
				});
86 87 88 89
			}
		}
	}

90 91 92 93
	onDragEnter(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): boolean {
		return this.canDrop(data, targetCompositeId);
	}

S
SteVen Batten 已提交
94
	onDragOver(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): boolean {
95 96 97
		return this.canDrop(data, targetCompositeId);
	}

98 99 100 101 102 103 104
	private getTargetIndex(targetId: string | undefined, before2d: Before2D | undefined): number | undefined {
		if (!targetId) {
			return undefined;
		}

		const items = this.getItems();
		const before = this.targetContainerLocation === ViewContainerLocation.Panel ? before2d?.horizontallyBefore : before2d?.verticallyBefore;
S
SteVen Batten 已提交
105
		return items.filter(o => o.visible).findIndex(o => o.id === targetId) + (before ? 0 : 1);
106 107
	}

108
	private canDrop(data: CompositeDragAndDropData, targetCompositeId: string | undefined): boolean {
S
SteVen Batten 已提交
109 110 111 112
		const dragData = data.getData();

		if (dragData.type === 'composite') {
			// Dragging a composite
113 114
			const currentContainer = this.viewDescriptorService.getViewContainerById(dragData.id)!;
			const currentLocation = this.viewDescriptorService.getViewContainerLocation(currentContainer);
S
SteVen Batten 已提交
115

116
			// ... to the same composite location
S
SteVen Batten 已提交
117 118 119 120
			if (currentLocation === this.targetContainerLocation) {
				return true;
			}

121
			// ... to another composite location
122
			const draggedViews = this.viewDescriptorService.getViewContainerModel(currentContainer)!.allViewDescriptors;
123

S
SteVen Batten 已提交
124
			// ... all views must be movable
S
SteVen Batten 已提交
125
			return !draggedViews.some(v => !v.canMoveView);
S
SteVen Batten 已提交
126 127
		} else {
			// Dragging an individual view
128
			const viewDescriptor = this.viewDescriptorService.getViewDescriptorById(dragData.id);
S
SteVen Batten 已提交
129 130 131 132 133 134 135

			// ... that cannot move
			if (!viewDescriptor || !viewDescriptor.canMoveView) {
				return false;
			}

			// ... to create a view container
S
SteVen Batten 已提交
136
			return true;
S
SteVen Batten 已提交
137 138
		}
	}
139 140
}

141
export interface ICompositeBarOptions {
B
Benjamin Pasero 已提交
142

B
Benjamin Pasero 已提交
143 144
	readonly icon: boolean;
	readonly orientation: ActionsOrientation;
M
Martin Aeschlimann 已提交
145
	readonly colors: (theme: IColorTheme) => ICompositeBarColors;
B
Benjamin Pasero 已提交
146 147
	readonly compositeSize: number;
	readonly overflowActionSize: number;
148
	readonly dndHandler: ICompositeDragAndDrop;
S
SteVen Batten 已提交
149
	readonly preventLoopNavigation?: boolean;
B
Benjamin Pasero 已提交
150

151
	getActivityAction: (compositeId: string) => ActivityAction;
152 153
	getCompositePinnedAction: (compositeId: string) => IAction;
	getOnCompositeClickAction: (compositeId: string) => IAction;
154
	fillExtraContextMenuActions: (actions: IAction[]) => void;
155
	getContextMenuActionsForComposite: (compositeId: string) => IAction[];
156
	openComposite: (compositeId: string) => Promise<IComposite | null>;
157
	getDefaultCompositeId: () => string;
158
	hidePart: () => void;
159 160
}

161
export class CompositeBar extends Widget implements ICompositeBar {
162

B
Benjamin Pasero 已提交
163 164 165
	private readonly _onDidChange = this._register(new Emitter<void>());
	readonly onDidChange = this._onDidChange.event;

I
isidor 已提交
166
	private dimension: Dimension | undefined;
167

B
Benjamin Pasero 已提交
168
	private compositeSwitcherBar: ActionBar | undefined;
I
isidor 已提交
169
	private compositeOverflowAction: CompositeOverflowActivityAction | undefined;
170
	private compositeOverflowActionViewItem: CompositeOverflowActivityActionViewItem | undefined;
171

S
Sandeep Somavarapu 已提交
172 173
	private model: CompositeBarModel;
	private visibleComposites: string[];
174
	private compositeSizeInBar: Map<string, number>;
175 176

	constructor(
177
		items: ICompositeBarItem[],
B
Benjamin Pasero 已提交
178
		private readonly options: ICompositeBarOptions,
179 180
		@IInstantiationService private readonly instantiationService: IInstantiationService,
		@IContextMenuService private readonly contextMenuService: IContextMenuService
181
	) {
182
		super();
B
Benjamin Pasero 已提交
183

184
		this.model = new CompositeBarModel(items, options);
S
Sandeep Somavarapu 已提交
185
		this.visibleComposites = [];
186
		this.compositeSizeInBar = new Map<string, number>();
S
Sandeep Somavarapu 已提交
187
		this.computeSizes(this.model.visibleItems);
S
Sandeep Somavarapu 已提交
188 189
	}

190
	getCompositeBarItems(): ICompositeBarItem[] {
S
Sandeep Somavarapu 已提交
191
		return [...this.model.items];
192 193
	}

194 195 196 197 198 199 200
	setCompositeBarItems(items: ICompositeBarItem[]): void {
		if (this.model.setItems(items)) {
			this.updateCompositeSwitcher();
		}
	}

	getPinnedComposites(): ICompositeBarItem[] {
P
Pine Wu 已提交
201 202 203
		return this.model.pinnedItems;
	}

S
SteVen Batten 已提交
204 205 206 207
	getVisibleComposites(): ICompositeBarItem[] {
		return this.model.visibleItems;
	}

B
Benjamin Pasero 已提交
208
	create(parent: HTMLElement): HTMLElement {
S
Sandeep Somavarapu 已提交
209 210
		const actionBarDiv = parent.appendChild($('.composite-bar'));
		this.compositeSwitcherBar = this._register(new ActionBar(actionBarDiv, {
211
			actionViewItemProvider: action => {
S
Sandeep Somavarapu 已提交
212
				if (action instanceof CompositeOverflowActivityAction) {
213
					return this.compositeOverflowActionViewItem;
S
Sandeep Somavarapu 已提交
214 215
				}
				const item = this.model.findItem(action.id);
S
SteVen Batten 已提交
216 217
				return item && this.instantiationService.createInstance(
					CompositeActionViewItem, action as ActivityAction, item.pinnedAction,
218 219
					compositeId => this.options.getContextMenuActionsForComposite(compositeId),
					() => this.getContextMenuActions(),
S
SteVen Batten 已提交
220 221
					this.options.colors,
					this.options.icon,
222
					this.options.dndHandler,
S
SteVen Batten 已提交
223 224
					this
				);
S
Sandeep Somavarapu 已提交
225 226 227 228
			},
			orientation: this.options.orientation,
			ariaLabel: nls.localize('activityBarAriaLabel', "Active View Switcher"),
			animated: false,
229
			preventLoopNavigation: this.options.preventLoopNavigation,
I
isidor 已提交
230 231
			ignoreOrientationForPreviousAndNextKey: true,
			triggerKeys: { keyDown: true }
S
Sandeep Somavarapu 已提交
232 233 234 235 236
		}));

		// Contextmenu for composites
		this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e)));

237
		let insertDropBefore: Before2D | undefined = undefined;
S
SteVen Batten 已提交
238
		// Register a drop target on the whole bar to prevent forbidden feedback
S
#93038  
SteVen Batten 已提交
239 240
		this._register(CompositeDragAndDropObserver.INSTANCE.registerTarget(parent, {
			onDragOver: (e: IDraggedCompositeData) => {
S
SteVen Batten 已提交
241
				// don't add feedback if this is over the composite bar actions or there are no actions
242 243
				const visibleItems = this.getVisibleComposites();
				if (!visibleItems.length || (e.eventData.target && isAncestor(e.eventData.target as HTMLElement, actionBarDiv))) {
244
					insertDropBefore = this.updateFromDragging(parent, false, false);
S
#93038  
SteVen Batten 已提交
245 246
					return;
				}
S
SteVen Batten 已提交
247

248 249 250
				const insertAtFront = this.insertAtFront(actionBarDiv, e.eventData);
				const target = insertAtFront ? visibleItems[0] : visibleItems[visibleItems.length - 1];
				const validDropTarget = this.options.dndHandler.onDragOver(e.dragAndDropData, target.id, e.eventData);
S
SteVen Batten 已提交
251
				toggleDropEffect(e.eventData.dataTransfer, 'move', validDropTarget);
252
				insertDropBefore = this.updateFromDragging(parent, validDropTarget, insertAtFront);
253
			},
S
SteVen Batten 已提交
254

255
			onDragLeave: (e: IDraggedCompositeData) => {
256
				insertDropBefore = this.updateFromDragging(parent, false, false);
S
SteVen Batten 已提交
257 258
			},
			onDragEnd: (e: IDraggedCompositeData) => {
259
				insertDropBefore = this.updateFromDragging(parent, false, false);
260
			},
261
			onDrop: (e: IDraggedCompositeData) => {
262 263
				const visibleItems = this.getVisibleComposites();
				if (visibleItems.length) {
264 265
					const target = this.insertAtFront(actionBarDiv, e.eventData) ? visibleItems[0] : visibleItems[visibleItems.length - 1];
					this.options.dndHandler.drop(e.dragAndDropData, target.id, e.eventData, insertDropBefore);
S
SteVen Batten 已提交
266
				}
267
				insertDropBefore = this.updateFromDragging(parent, false, false);
268
			}
S
SteVen Batten 已提交
269 270
		}));

S
Sandeep Somavarapu 已提交
271
		return actionBarDiv;
272 273
	}

274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289
	private insertAtFront(element: HTMLElement, event: DragEvent): boolean {
		const rect = element.getBoundingClientRect();
		const posX = event.clientX;
		const posY = event.clientY;

		switch (this.options.orientation) {
			case ActionsOrientation.HORIZONTAL:
			case ActionsOrientation.HORIZONTAL_REVERSE:
				return posX < rect.left;
			case ActionsOrientation.VERTICAL:
			case ActionsOrientation.VERTICAL_REVERSE:
				return posY < rect.top;
		}
	}

	private updateFromDragging(element: HTMLElement, showFeedback: boolean, front: boolean): Before2D | undefined {
290 291
		element.classList.toggle('dragged-over-head', showFeedback && front);
		element.classList.toggle('dragged-over-tail', showFeedback && !front);
292 293 294 295 296 297 298 299

		if (!showFeedback) {
			return undefined;
		}

		return { verticallyBefore: front, horizontallyBefore: front };
	}

S
SteVen Batten 已提交
300
	focus(index?: number): void {
301
		if (this.compositeSwitcherBar) {
S
SteVen Batten 已提交
302
			this.compositeSwitcherBar.focus(index);
303 304 305
		}
	}

B
Benjamin Pasero 已提交
306
	layout(dimension: Dimension): void {
S
Sandeep Somavarapu 已提交
307 308 309
		this.dimension = dimension;
		if (dimension.height === 0 || dimension.width === 0) {
			// Do not layout if not visible. Otherwise the size measurment would be computed wrongly
310 311
			return;
		}
312

S
Sandeep Somavarapu 已提交
313 314 315
		if (this.compositeSizeInBar.size === 0) {
			// Compute size of each composite by getting the size from the css renderer
			// Size is later used for overflow computation
S
Sandeep Somavarapu 已提交
316
			this.computeSizes(this.model.visibleItems);
S
Sandeep Somavarapu 已提交
317
		}
318

S
Sandeep Somavarapu 已提交
319 320 321
		this.updateCompositeSwitcher();
	}

322
	addComposite({ id, name, order, requestedIndex }: { id: string; name: string, order?: number, requestedIndex?: number; }): void {
S
Sandeep Somavarapu 已提交
323
		// Add to the model
S
SteVen Batten 已提交
324
		if (this.model.add(id, name, order, requestedIndex)) {
S
Sandeep Somavarapu 已提交
325
			this.computeSizes([this.model.findItem(id)]);
326
			this.updateCompositeSwitcher();
327
		}
328 329
	}

B
Benjamin Pasero 已提交
330
	removeComposite(id: string): void {
S
Sandeep Somavarapu 已提交
331 332 333 334

		// If it pinned, unpin it first
		if (this.isPinned(id)) {
			this.unpin(id);
335 336
		}

S
Sandeep Somavarapu 已提交
337 338 339 340
		// Remove from the model
		if (this.model.remove(id)) {
			this.updateCompositeSwitcher();
		}
341 342
	}

S
Sandeep Somavarapu 已提交
343 344
	hideComposite(id: string): void {
		if (this.model.hide(id)) {
S
Sandeep Somavarapu 已提交
345
			this.resetActiveComposite(id);
346
			this.updateCompositeSwitcher();
S
Sandeep Somavarapu 已提交
347 348 349
		}
	}

B
Benjamin Pasero 已提交
350
	activateComposite(id: string): void {
S
Sandeep Somavarapu 已提交
351 352
		const previousActiveItem = this.model.activeItem;
		if (this.model.activate(id)) {
353 354
			// Update if current composite is neither visible nor pinned
			// or previous active composite is not pinned
M
Matt Bierner 已提交
355
			if (this.visibleComposites.indexOf(id) === - 1 || (!!this.model.activeItem && !this.model.activeItem.pinned) || (previousActiveItem && !previousActiveItem.pinned)) {
S
Sandeep Somavarapu 已提交
356
				this.updateCompositeSwitcher();
357
			}
358 359 360
		}
	}

B
Benjamin Pasero 已提交
361
	deactivateComposite(id: string): void {
S
Sandeep Somavarapu 已提交
362 363 364 365 366
		const previousActiveItem = this.model.activeItem;
		if (this.model.deactivate()) {
			if (previousActiveItem && !previousActiveItem.pinned) {
				this.updateCompositeSwitcher();
			}
367
		}
368 369
	}

B
Benjamin Pasero 已提交
370
	showActivity(compositeId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable {
371 372 373 374
		if (!badge) {
			throw illegalArgument('badge');
		}

J
Joao Moreno 已提交
375 376 377 378 379
		if (typeof priority !== 'number') {
			priority = 0;
		}

		const activity: ICompositeActivity = { badge, clazz, priority };
S
Sandeep Somavarapu 已提交
380
		this.model.addActivity(compositeId, activity);
381

S
Sandeep Somavarapu 已提交
382 383
		return toDisposable(() => this.model.removeActivity(compositeId, activity));
	}
384

385
	async pin(compositeId: string, open?: boolean): Promise<void> {
S
Sandeep Somavarapu 已提交
386 387
		if (this.model.setPinned(compositeId, true)) {
			this.updateCompositeSwitcher();
388

S
Sandeep Somavarapu 已提交
389
			if (open) {
390 391
				await this.options.openComposite(compositeId);
				this.activateComposite(compositeId); // Activate after opening
392
			}
S
Sandeep Somavarapu 已提交
393
		}
394 395
	}

B
Benjamin Pasero 已提交
396
	unpin(compositeId: string): void {
S
Sandeep Somavarapu 已提交
397
		if (this.model.setPinned(compositeId, false)) {
398

S
Sandeep Somavarapu 已提交
399
			this.updateCompositeSwitcher();
400

S
Sandeep Somavarapu 已提交
401 402 403
			this.resetActiveComposite(compositeId);
		}
	}
404

S
Sandeep Somavarapu 已提交
405 406
	private resetActiveComposite(compositeId: string) {
		const defaultCompositeId = this.options.getDefaultCompositeId();
407

S
Sandeep Somavarapu 已提交
408 409 410 411 412
		// Case: composite is not the active one or the active one is a different one
		// Solv: we do nothing
		if (!this.model.activeItem || this.model.activeItem.id !== compositeId) {
			return;
		}
413

S
Sandeep Somavarapu 已提交
414 415
		// Deactivate itself
		this.deactivateComposite(compositeId);
416

S
Sandeep Somavarapu 已提交
417 418 419 420 421
		// Case: composite is not the default composite and default composite is still showing
		// Solv: we open the default composite
		if (defaultCompositeId !== compositeId && this.isPinned(defaultCompositeId)) {
			this.options.openComposite(defaultCompositeId);
		}
422

S
Sandeep Somavarapu 已提交
423 424
		// Case: we closed the last visible composite
		// Solv: we hide the part
S
SteVen Batten 已提交
425
		else if (this.visibleComposites.length === 0) {
S
Sandeep Somavarapu 已提交
426 427 428 429 430 431 432
			this.options.hidePart();
		}

		// Case: we closed the default composite
		// Solv: we open the next visible composite from top
		else {
			this.options.openComposite(this.visibleComposites.filter(cid => cid !== compositeId)[0]);
S
Sandeep Somavarapu 已提交
433
		}
I
isidor 已提交
434 435
	}

B
Benjamin Pasero 已提交
436
	isPinned(compositeId: string): boolean {
S
Sandeep Somavarapu 已提交
437
		const item = this.model.findItem(compositeId);
B
Benjamin Pasero 已提交
438
		return item?.pinned;
439 440
	}

441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468
	move(compositeId: string, toCompositeId: string, before?: boolean): void {

		if (before !== undefined) {
			const fromIndex = this.model.items.findIndex(c => c.id === compositeId);
			let toIndex = this.model.items.findIndex(c => c.id === toCompositeId);

			if (fromIndex >= 0 && toIndex >= 0) {
				if (!before && fromIndex > toIndex) {
					toIndex++;
				}

				if (before && fromIndex < toIndex) {
					toIndex--;
				}

				if (toIndex < this.model.items.length && toIndex >= 0 && toIndex !== fromIndex) {
					if (this.model.move(this.model.items[fromIndex].id, this.model.items[toIndex].id)) {
						// timeout helps to prevent artifacts from showing up
						setTimeout(() => this.updateCompositeSwitcher(), 0);
					}
				}
			}

		} else {
			if (this.model.move(compositeId, toCompositeId)) {
				// timeout helps to prevent artifacts from showing up
				setTimeout(() => this.updateCompositeSwitcher(), 0);
			}
S
Sandeep Somavarapu 已提交
469
		}
470 471
	}

472
	getAction(compositeId: string): ActivityAction {
S
Sandeep Somavarapu 已提交
473
		const item = this.model.findItem(compositeId);
B
Benjamin Pasero 已提交
474
		return item?.activityAction;
475 476
	}

477
	private computeSizes(items: ICompositeBarModelItem[]): void {
478 479 480 481
		const size = this.options.compositeSize;
		if (size) {
			items.forEach(composite => this.compositeSizeInBar.set(composite.id, size));
		} else {
B
Benjamin Pasero 已提交
482 483
			const compositeSwitcherBar = this.compositeSwitcherBar;
			if (compositeSwitcherBar && this.dimension && this.dimension.height !== 0 && this.dimension.width !== 0) {
484
				// Compute sizes only if visible. Otherwise the size measurment would be computed wrongly.
B
Benjamin Pasero 已提交
485 486
				const currentItemsLength = compositeSwitcherBar.viewItems.length;
				compositeSwitcherBar.push(items.map(composite => composite.activityAction));
487
				items.map((composite, index) => this.compositeSizeInBar.set(composite.id, this.options.orientation === ActionsOrientation.VERTICAL
B
Benjamin Pasero 已提交
488 489
					? compositeSwitcherBar.getHeight(currentItemsLength + index)
					: compositeSwitcherBar.getWidth(currentItemsLength + index)
490
				));
B
Benjamin Pasero 已提交
491
				items.forEach(() => compositeSwitcherBar.pull(compositeSwitcherBar.viewItems.length - 1));
492 493 494 495
			}
		}
	}

496
	private updateCompositeSwitcher(): void {
B
Benjamin Pasero 已提交
497 498
		const compositeSwitcherBar = this.compositeSwitcherBar;
		if (!compositeSwitcherBar || !this.dimension) {
499 500 501
			return; // We have not been rendered yet so there is nothing to update.
		}

S
Sandeep Somavarapu 已提交
502
		let compositesToShow = this.model.visibleItems.filter(item =>
S
Sandeep Somavarapu 已提交
503 504 505
			item.pinned
			|| (this.model.activeItem && this.model.activeItem.id === item.id) /* Show the active composite even if it is not pinned */
		).map(item => item.id);
506 507 508

		// Ensure we are not showing more composites than we have height for
		let overflows = false;
I
isidor 已提交
509 510 511 512
		let maxVisible = compositesToShow.length;
		let size = 0;
		const limit = this.options.orientation === ActionsOrientation.VERTICAL ? this.dimension.height : this.dimension.width;
		for (let i = 0; i < compositesToShow.length && size <= limit; i++) {
M
Matt Bierner 已提交
513
			size += this.compositeSizeInBar.get(compositesToShow[i])!;
I
isidor 已提交
514 515
			if (size > limit) {
				maxVisible = i;
I
isidor 已提交
516
			}
I
isidor 已提交
517 518
		}
		overflows = compositesToShow.length > maxVisible;
519

I
isidor 已提交
520
		if (overflows) {
M
Matt Bierner 已提交
521
			size -= this.compositeSizeInBar.get(compositesToShow[maxVisible])!;
I
isidor 已提交
522
			compositesToShow = compositesToShow.slice(0, maxVisible);
I
isidor 已提交
523
			size += this.options.overflowActionSize;
I
isidor 已提交
524 525
		}
		// Check if we need to make extra room for the overflow action
I
isidor 已提交
526
		if (size > limit) {
M
Matt Bierner 已提交
527
			size -= this.compositeSizeInBar.get(compositesToShow.pop()!)!;
I
isidor 已提交
528
		}
S
Sandeep Somavarapu 已提交
529

I
isidor 已提交
530
		// We always try show the active composite
M
Matt Bierner 已提交
531 532 533
		if (this.model.activeItem && compositesToShow.every(compositeId => !!this.model.activeItem && compositeId !== this.model.activeItem.id)) {
			const removedComposite = compositesToShow.pop()!;
			size = size - this.compositeSizeInBar.get(removedComposite)! + this.compositeSizeInBar.get(this.model.activeItem.id)!;
S
Sandeep Somavarapu 已提交
534
			compositesToShow.push(this.model.activeItem.id);
535
		}
S
Sandeep Somavarapu 已提交
536

I
isidor 已提交
537 538 539 540
		// The active composite might have bigger size than the removed composite, check for overflow again
		if (size > limit) {
			compositesToShow.length ? compositesToShow.splice(compositesToShow.length - 2, 1) : compositesToShow.pop();
		}
541

S
Sandeep Somavarapu 已提交
542
		const visibleCompositesChange = !arrays.equals(compositesToShow, this.visibleComposites);
543 544 545

		// Pull out overflow action if there is a composite change so that we can add it to the end later
		if (this.compositeOverflowAction && visibleCompositesChange) {
B
Benjamin Pasero 已提交
546
			compositeSwitcherBar.pull(compositeSwitcherBar.length() - 1);
547 548

			this.compositeOverflowAction.dispose();
I
isidor 已提交
549
			this.compositeOverflowAction = undefined;
550

551 552
			if (this.compositeOverflowActionViewItem) {
				this.compositeOverflowActionViewItem.dispose();
M
Matt Bierner 已提交
553
			}
554
			this.compositeOverflowActionViewItem = undefined;
555 556
		}

S
Sandeep Somavarapu 已提交
557 558 559
		// Pull out composites that overflow or got hidden
		const compositesToRemove: number[] = [];
		this.visibleComposites.forEach((compositeId, index) => {
560
			if (!compositesToShow.includes(compositeId)) {
S
Sandeep Somavarapu 已提交
561
				compositesToRemove.push(index);
562 563
			}
		});
S
Sandeep Somavarapu 已提交
564
		compositesToRemove.reverse().forEach(index => {
B
Benjamin Pasero 已提交
565 566
			const actionViewItem = compositeSwitcherBar.viewItems[index];
			compositeSwitcherBar.pull(index);
567
			actionViewItem.dispose();
S
Sandeep Somavarapu 已提交
568 569
			this.visibleComposites.splice(index, 1);
		});
570

S
Sandeep Somavarapu 已提交
571 572 573 574 575
		// Update the positions of the composites
		compositesToShow.forEach((compositeId, newIndex) => {
			const currentIndex = this.visibleComposites.indexOf(compositeId);
			if (newIndex !== currentIndex) {
				if (currentIndex !== -1) {
B
Benjamin Pasero 已提交
576 577
					const actionViewItem = compositeSwitcherBar.viewItems[currentIndex];
					compositeSwitcherBar.pull(currentIndex);
578
					actionViewItem.dispose();
S
Sandeep Somavarapu 已提交
579
					this.visibleComposites.splice(currentIndex, 1);
580 581
				}

B
Benjamin Pasero 已提交
582
				compositeSwitcherBar.push(this.model.findItem(compositeId).activityAction, { label: true, icon: this.options.icon, index: newIndex });
S
Sandeep Somavarapu 已提交
583 584 585
				this.visibleComposites.splice(newIndex, 0, compositeId);
			}
		});
586 587

		// Add overflow action as needed
S
SteVen Batten 已提交
588
		if ((visibleCompositesChange && overflows)) {
M
Matt Bierner 已提交
589
			this.compositeOverflowAction = this.instantiationService.createInstance(CompositeOverflowActivityAction, () => {
590 591
				if (this.compositeOverflowActionViewItem) {
					this.compositeOverflowActionViewItem.showMenu();
M
Matt Bierner 已提交
592 593
				}
			});
594 595
			this.compositeOverflowActionViewItem = this.instantiationService.createInstance(
				CompositeOverflowActivityActionViewItem,
596 597
				this.compositeOverflowAction,
				() => this.getOverflowingComposites(),
R
Rob Lourens 已提交
598
				() => this.model.activeItem ? this.model.activeItem.id : undefined,
599
				compositeId => {
S
Sandeep Somavarapu 已提交
600
					const item = this.model.findItem(compositeId);
S
SteVen Batten 已提交
601
					return item?.activity[0]?.badge;
S
Sandeep Somavarapu 已提交
602
				},
I
isidor 已提交
603
				this.options.getOnCompositeClickAction,
I
isidor 已提交
604
				this.options.colors
605 606
			);

B
Benjamin Pasero 已提交
607
			compositeSwitcherBar.push(this.compositeOverflowAction, { label: false, icon: true });
608
		}
609

610
		this._onDidChange.fire();
611 612
	}

613
	private getOverflowingComposites(): { id: string, name?: string; }[] {
S
Sandeep Somavarapu 已提交
614
		let overflowingIds = this.model.visibleItems.filter(item => item.pinned).map(item => item.id);
S
Sandeep Somavarapu 已提交
615 616 617 618

		// Show the active composite even if it is not pinned
		if (this.model.activeItem && !this.model.activeItem.pinned) {
			overflowingIds.push(this.model.activeItem.id);
619 620
		}

621 622
		overflowingIds = overflowingIds.filter(compositeId => !this.visibleComposites.includes(compositeId));
		return this.model.visibleItems.filter(c => overflowingIds.includes(c.id)).map(item => { return { id: item.id, name: this.getAction(item.id)?.label || item.name }; });
623 624
	}

S
Sandeep Somavarapu 已提交
625 626 627
	private showContextMenu(e: MouseEvent): void {
		EventHelper.stop(e, true);
		const event = new StandardMouseEvent(e);
S
Sandeep Somavarapu 已提交
628 629
		this.contextMenuService.showContextMenu({
			getAnchor: () => { return { x: event.posx, y: event.posy }; },
630
			getActions: () => this.getContextMenuActions()
S
Sandeep Somavarapu 已提交
631 632 633
		});
	}

634
	getContextMenuActions(): IAction[] {
S
Sandeep Somavarapu 已提交
635
		const actions: IAction[] = this.model.visibleItems
636
			.map(({ id, name, activityAction }) => (toAction({
S
Sandeep Somavarapu 已提交
637
				id,
S
SteVen Batten 已提交
638
				label: this.getAction(id).label || name || id,
S
Sandeep Somavarapu 已提交
639
				checked: this.isPinned(id),
640
				enabled: activityAction.enabled,
S
Sandeep Somavarapu 已提交
641 642 643 644 645 646 647
				run: () => {
					if (this.isPinned(id)) {
						this.unpin(id);
					} else {
						this.pin(id, true);
					}
				}
648
			})));
649 650 651

		this.options.fillExtraContextMenuActions(actions);

S
Sandeep Somavarapu 已提交
652
		return actions;
653
	}
S
Sandeep Somavarapu 已提交
654
}
655

656
interface ICompositeBarModelItem extends ICompositeBarItem {
S
Sandeep Somavarapu 已提交
657
	activityAction: ActivityAction;
658
	pinnedAction: IAction;
S
Sandeep Somavarapu 已提交
659 660
	activity: ICompositeActivity[];
}
661

S
Sandeep Somavarapu 已提交
662
class CompositeBarModel {
663

I
isidor 已提交
664
	private _items: ICompositeBarModelItem[] = [];
S
Sandeep Somavarapu 已提交
665
	private readonly options: ICompositeBarOptions;
M
Matt Bierner 已提交
666
	activeItem?: ICompositeBarModelItem;
667

S
Sandeep Somavarapu 已提交
668
	constructor(
669 670
		items: ICompositeBarItem[],
		options: ICompositeBarOptions
S
Sandeep Somavarapu 已提交
671 672
	) {
		this.options = options;
673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708
		this.setItems(items);
	}

	get items(): ICompositeBarModelItem[] {
		return this._items;
	}

	setItems(items: ICompositeBarItem[]): boolean {
		const result: ICompositeBarModelItem[] = [];
		let hasChanges: boolean = false;
		if (!this.items || this.items.length === 0) {
			this._items = items.map(i => this.createCompositeBarItem(i.id, i.name, i.order, i.pinned, i.visible));
			hasChanges = true;
		} else {
			const existingItems = this.items;
			for (let index = 0; index < items.length; index++) {
				const newItem = items[index];
				const existingItem = existingItems.filter(({ id }) => id === newItem.id)[0];
				if (existingItem) {
					if (
						existingItem.pinned !== newItem.pinned ||
						index !== existingItems.indexOf(existingItem)
					) {
						existingItem.pinned = newItem.pinned;
						result.push(existingItem);
						hasChanges = true;
					} else {
						result.push(existingItem);
					}
				} else {
					result.push(this.createCompositeBarItem(newItem.id, newItem.name, newItem.order, newItem.pinned, newItem.visible));
					hasChanges = true;
				}
			}
			this._items = result;
		}
709

710
		return hasChanges;
S
Sandeep Somavarapu 已提交
711 712
	}

713
	get visibleItems(): ICompositeBarModelItem[] {
S
Sandeep Somavarapu 已提交
714 715
		return this.items.filter(item => item.visible);
	}
716

717
	get pinnedItems(): ICompositeBarModelItem[] {
P
Pine Wu 已提交
718 719 720
		return this.items.filter(item => item.visible && item.pinned);
	}

M
Matt Bierner 已提交
721
	private createCompositeBarItem(id: string, name: string | undefined, order: number | undefined, pinned: boolean, visible: boolean): ICompositeBarModelItem {
S
Sandeep Somavarapu 已提交
722 723
		const options = this.options;
		return {
S
Sandeep Somavarapu 已提交
724 725
			id, name, pinned, order, visible,
			activity: [],
S
Sandeep Somavarapu 已提交
726
			get activityAction() {
727
				return options.getActivityAction(id);
S
Sandeep Somavarapu 已提交
728 729
			},
			get pinnedAction() {
730
				return options.getCompositePinnedAction(id);
S
Sandeep Somavarapu 已提交
731
			}
S
Sandeep Somavarapu 已提交
732 733
		};
	}
734

S
SteVen Batten 已提交
735
	add(id: string, name: string, order: number | undefined, requestedIndex: number | undefined): boolean {
S
Sandeep Somavarapu 已提交
736 737
		const item = this.findItem(id);
		if (item) {
S
Sandeep Somavarapu 已提交
738
			let changed = false;
S
Sandeep Somavarapu 已提交
739
			item.name = name;
S
Sandeep Somavarapu 已提交
740 741 742 743 744 745 746 747
			if (!isUndefinedOrNull(order)) {
				changed = item.order !== order;
				item.order = order;
			}
			if (!item.visible) {
				item.visible = true;
				changed = true;
			}
748

S
Sandeep Somavarapu 已提交
749
			return changed;
S
Sandeep Somavarapu 已提交
750
		} else {
S
Sandeep Somavarapu 已提交
751
			const item = this.createCompositeBarItem(id, name, order, true, true);
S
SteVen Batten 已提交
752 753 754 755 756 757 758 759 760 761 762
			if (!isUndefinedOrNull(requestedIndex)) {
				let index = 0;
				let rIndex = requestedIndex;
				while (rIndex > 0 && index < this.items.length) {
					if (this.items[index++].visible) {
						rIndex--;
					}
				}

				this.items.splice(index, 0, item);
			} else if (isUndefinedOrNull(order)) {
S
Sandeep Somavarapu 已提交
763 764 765
				this.items.push(item);
			} else {
				let index = 0;
M
Matt Bierner 已提交
766
				while (index < this.items.length && typeof this.items[index].order === 'number' && this.items[index].order! < order) {
S
Sandeep Somavarapu 已提交
767 768
					index++;
				}
S
Sandeep Somavarapu 已提交
769
				this.items.splice(index, 0, item);
S
Sandeep Somavarapu 已提交
770
			}
771

S
Sandeep Somavarapu 已提交
772
			return true;
773
		}
S
Sandeep Somavarapu 已提交
774
	}
775

S
Sandeep Somavarapu 已提交
776 777 778
	remove(id: string): boolean {
		for (let index = 0; index < this.items.length; index++) {
			if (this.items[index].id === id) {
779
				this.items.splice(index, 1);
S
Sandeep Somavarapu 已提交
780 781
				return true;
			}
782
		}
S
Sandeep Somavarapu 已提交
783
		return false;
784 785
	}

S
Sandeep Somavarapu 已提交
786 787 788 789 790 791 792 793 794 795 796 797 798
	hide(id: string): boolean {
		for (const item of this.items) {
			if (item.id === id) {
				if (item.visible) {
					item.visible = false;
					return true;
				}
				return false;
			}
		}
		return false;
	}

S
Sandeep Somavarapu 已提交
799
	move(compositeId: string, toCompositeId: string): boolean {
800

S
Sandeep Somavarapu 已提交
801 802 803 804 805 806
		const fromIndex = this.findIndex(compositeId);
		const toIndex = this.findIndex(toCompositeId);

		// Make sure both items are known to the model
		if (fromIndex === -1 || toIndex === -1) {
			return false;
807
		}
808

S
Sandeep Somavarapu 已提交
809 810
		const sourceItem = this.items.splice(fromIndex, 1)[0];
		this.items.splice(toIndex, 0, sourceItem);
811

S
Sandeep Somavarapu 已提交
812 813
		// Make sure a moved composite gets pinned
		sourceItem.pinned = true;
814

S
Sandeep Somavarapu 已提交
815
		return true;
816 817
	}

S
Sandeep Somavarapu 已提交
818
	setPinned(id: string, pinned: boolean): boolean {
819
		for (const item of this.items) {
S
Sandeep Somavarapu 已提交
820 821 822 823 824 825 826
			if (item.id === id) {
				if (item.pinned !== pinned) {
					item.pinned = pinned;
					return true;
				}
				return false;
			}
827
		}
S
Sandeep Somavarapu 已提交
828 829
		return false;
	}
830

S
Sandeep Somavarapu 已提交
831 832 833 834 835 836 837 838 839 840 841 842 843 844 845
	addActivity(id: string, activity: ICompositeActivity): boolean {
		const item = this.findItem(id);
		if (item) {
			const stack = item.activity;
			for (let i = 0; i <= stack.length; i++) {
				if (i === stack.length) {
					stack.push(activity);
					break;
				} else if (stack[i].priority <= activity.priority) {
					stack.splice(i, 0, activity);
					break;
				}
			}
			this.updateActivity(id);
			return true;
846
		}
S
Sandeep Somavarapu 已提交
847
		return false;
848 849
	}

S
Sandeep Somavarapu 已提交
850 851 852 853 854 855 856 857 858
	removeActivity(id: string, activity: ICompositeActivity): boolean {
		const item = this.findItem(id);
		if (item) {
			const index = item.activity.indexOf(activity);
			if (index !== -1) {
				item.activity.splice(index, 1);
				this.updateActivity(id);
				return true;
			}
859
		}
S
Sandeep Somavarapu 已提交
860 861
		return false;
	}
862

S
Sandeep Somavarapu 已提交
863 864 865 866 867 868 869 870 871 872
	updateActivity(id: string): void {
		const item = this.findItem(id);
		if (item) {
			if (item.activity.length) {
				const [{ badge, clazz }] = item.activity;
				item.activityAction.setBadge(badge, clazz);
			}
			else {
				item.activityAction.setBadge(undefined);
			}
873
		}
874 875
	}

S
Sandeep Somavarapu 已提交
876 877 878 879 880
	activate(id: string): boolean {
		if (!this.activeItem || this.activeItem.id !== id) {
			if (this.activeItem) {
				this.deactivate();
			}
881
			for (const item of this.items) {
S
Sandeep Somavarapu 已提交
882 883 884 885 886 887 888 889 890
				if (item.id === id) {
					this.activeItem = item;
					this.activeItem.activityAction.activate();
					return true;
				}
			}
		}
		return false;
	}
891

S
Sandeep Somavarapu 已提交
892 893 894
	deactivate(): boolean {
		if (this.activeItem) {
			this.activeItem.activityAction.deactivate();
R
Rob Lourens 已提交
895
			this.activeItem = undefined;
S
Sandeep Somavarapu 已提交
896
			return true;
897
		}
S
Sandeep Somavarapu 已提交
898 899
		return false;
	}
900

901
	findItem(id: string): ICompositeBarModelItem {
S
Sandeep Somavarapu 已提交
902
		return this.items.filter(item => item.id === id)[0];
903 904
	}

S
Sandeep Somavarapu 已提交
905
	private findIndex(id: string): number {
S
Sandeep Somavarapu 已提交
906 907 908
		for (let index = 0; index < this.items.length; index++) {
			if (this.items[index].id === id) {
				return index;
909 910
			}
		}
S
Sandeep Somavarapu 已提交
911
		return -1;
912
	}
913
}