panelPart.ts 13.4 KB
Newer Older
I
isidor 已提交
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.
 *--------------------------------------------------------------------------------------------*/

B
Benjamin Pasero 已提交
6
import 'vs/css!./media/panelpart';
J
Johannes Rieken 已提交
7
import { TPromise } from 'vs/base/common/winjs.base';
I
isidor 已提交
8
import { IAction, Action } from 'vs/base/common/actions';
9
import Event from 'vs/base/common/event';
I
isidor 已提交
10
import { Builder, Dimension } from 'vs/base/browser/builder';
11
import { Registry } from 'vs/platform/registry/common/platform';
I
isidor 已提交
12
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
J
Johannes Rieken 已提交
13
import { IPanel } from 'vs/workbench/common/panel';
14
import { CompositePart, ICompositeTitleLabel } from 'vs/workbench/browser/parts/compositePart';
15
import { Panel, PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel';
16
import { IPanelService, IPanelIdentifier } from 'vs/workbench/services/panel/common/panelService';
17
import { IPartService, Parts, Position } from 'vs/workbench/services/part/common/partService';
J
Johannes Rieken 已提交
18 19 20 21 22
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
23
import { ClosePanelAction, TogglePanelPositionAction, PanelActivityAction, ToggleMaximizedPanelAction } from 'vs/workbench/browser/parts/panel/panelActions';
24
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
I
isidor 已提交
25 26
import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry';
I
isidor 已提交
27 28 29
import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar';
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
I
isidor 已提交
30 31
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IBadge } from 'vs/workbench/services/activity/common/activity';
32
import { INotificationService } from 'vs/platform/notification/common/notification';
I
isidor 已提交
33 34 35

export class PanelPart extends CompositePart<Panel> implements IPanelService {

M
Matt Bierner 已提交
36
	public static readonly activePanelSettingsKey = 'workbench.panelpart.activepanelid';
I
isidor 已提交
37
	private static readonly PINNED_PANELS = 'workbench.panel.pinnedPanels';
I
isidor 已提交
38
	private static readonly MIN_COMPOSITE_BAR_WIDTH = 50;
I
isidor 已提交
39

40
	public _serviceBrand: any;
41

I
isidor 已提交
42
	private blockOpeningPanel: boolean;
I
isidor 已提交
43
	private compositeBar: CompositeBar;
44
	private dimension: Dimension;
I
isidor 已提交
45
	private toolbarWidth = new Map<string, number>();
I
isidor 已提交
46 47

	constructor(
48
		id: string,
49
		@INotificationService notificationService: INotificationService,
50 51 52 53
		@IStorageService storageService: IStorageService,
		@ITelemetryService telemetryService: ITelemetryService,
		@IContextMenuService contextMenuService: IContextMenuService,
		@IPartService partService: IPartService,
54
		@IKeybindingService keybindingService: IKeybindingService,
55
		@IInstantiationService instantiationService: IInstantiationService,
56
		@IThemeService themeService: IThemeService
I
isidor 已提交
57
	) {
58
		super(
59
			notificationService,
60 61 62 63 64
			storageService,
			telemetryService,
			contextMenuService,
			partService,
			keybindingService,
65
			instantiationService,
66
			themeService,
67
			Registry.as<PanelRegistry>(PanelExtensions.Panels),
68
			PanelPart.activePanelSettingsKey,
69
			Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId(),
70 71
			'panel',
			'panel',
72
			null,
73 74
			id,
			{ hasTitle: true }
75
		);
76

I
isidor 已提交
77 78 79 80 81 82 83 84
		this.compositeBar = this.instantiationService.createInstance(CompositeBar, {
			icon: false,
			storageId: PanelPart.PINNED_PANELS,
			orientation: ActionsOrientation.HORIZONTAL,
			composites: this.getPanels(),
			openComposite: (compositeId: string) => this.openPanel(compositeId, true),
			getActivityAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)),
			getCompositePinnedAction: (compositeId: string) => new ToggleCompositePinnedAction(this.getPanel(compositeId), this.compositeBar),
85
			getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(PanelActivityAction, this.getPanel(compositeId)),
I
isidor 已提交
86
			getDefaultCompositeId: () => Registry.as<PanelRegistry>(PanelExtensions.Panels).getDefaultPanelId(),
I
isidor 已提交
87
			hidePart: () => this.partService.setPanelHidden(true),
I
isidor 已提交
88
			overflowActionSize: 44,
I
isidor 已提交
89 90
			colors: {
				backgroundColor: PANEL_BACKGROUND,
I
isidor 已提交
91 92
				badgeBackground,
				badgeForeground,
I
isidor 已提交
93 94
				dragAndDropBackground: PANEL_DRAG_AND_DROP_BACKGROUND
			}
I
isidor 已提交
95 96
		});
		this.toUnbind.push(this.compositeBar);
97 98 99 100 101 102 103

		this.registerListeners();
	}

	private registerListeners(): void {

		// Activate panel action on opening of a panel
104 105 106 107 108
		this.toUnbind.push(this.onDidPanelOpen(panel => {
			this.compositeBar.activateComposite(panel.getId());
			// Need to relayout composite bar since different panels have different action bar width
			this.layoutCompositeBar();
		}));
109
		this.toUnbind.push(this.compositeBar.onDidContextMenu(e => this.showContextMenu(e)));
110 111

		// Deactivate panel action on close
I
isidor 已提交
112
		this.toUnbind.push(this.onDidPanelClose(panel => this.compositeBar.deactivateComposite(panel.getId())));
I
isidor 已提交
113 114
	}

115
	public get onDidPanelOpen(): Event<IPanel> {
116
		return this._onDidCompositeOpen.event;
117 118 119
	}

	public get onDidPanelClose(): Event<IPanel> {
120
		return this._onDidCompositeClose.event;
121 122
	}

I
isidor 已提交
123
	public updateStyles(): void {
124 125
		super.updateStyles();

B
Benjamin Pasero 已提交
126
		const container = this.getContainer();
127
		container.style('background-color', this.getColor(PANEL_BACKGROUND));
I
isidor 已提交
128
		container.style('border-left-color', this.getColor(PANEL_BORDER) || this.getColor(contrastBorder));
B
Benjamin Pasero 已提交
129 130

		const title = this.getTitleArea();
131
		title.style('border-top-color', this.getColor(PANEL_BORDER) || this.getColor(contrastBorder));
132 133
	}

I
isidor 已提交
134 135 136 137 138
	public openPanel(id: string, focus?: boolean): TPromise<Panel> {
		if (this.blockOpeningPanel) {
			return TPromise.as(null); // Workaround against a potential race condition
		}

139
		// First check if panel is hidden and show if so
140
		let promise = TPromise.wrap(null);
141
		if (!this.partService.isVisible(Parts.PANEL_PART)) {
I
isidor 已提交
142 143
			try {
				this.blockOpeningPanel = true;
144
				promise = this.partService.setPanelHidden(false);
I
isidor 已提交
145 146 147 148 149
			} finally {
				this.blockOpeningPanel = false;
			}
		}

150
		return promise.then(() => this.openComposite(id, focus));
I
isidor 已提交
151 152
	}

I
isidor 已提交
153 154 155 156
	public showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable {
		return this.compositeBar.showActivity(panelId, badge, clazz);
	}

I
isidor 已提交
157
	private getPanel(panelId: string): IPanelIdentifier {
158
		return this.getPanels().filter(p => p.id === panelId).pop();
I
isidor 已提交
159 160 161 162 163 164 165 166 167 168 169 170 171
	}

	private showContextMenu(e: MouseEvent): void {
		const event = new StandardMouseEvent(e);
		const actions: Action[] = this.getPanels().map(panel => this.instantiationService.createInstance(ToggleCompositePinnedAction, panel, this.compositeBar));

		this.contextMenuService.showContextMenu({
			getAnchor: () => { return { x: event.posx, y: event.posy }; },
			getActions: () => TPromise.as(actions),
			onHide: () => dispose(actions)
		});
	}

172
	public getPanels(): PanelDescriptor[] {
173
		return Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanels()
174
			.filter(p => p.enabled)
B
Benjamin Pasero 已提交
175
			.sort((v1, v2) => v1.order - v2.order);
176 177
	}

I
isidor 已提交
178
	public setPanelEnablement(id: string, enabled: boolean): void {
179 180 181 182 183 184 185 186 187 188 189
		const descriptor = Registry.as<PanelRegistry>(PanelExtensions.Panels).getPanels().filter(p => p.id === id).pop();
		if (descriptor && descriptor.enabled !== enabled) {
			descriptor.enabled = enabled;
			if (enabled) {
				this.compositeBar.addComposite(descriptor);
			} else {
				this.compositeBar.removeComposite(id);
			}
		}
	}

I
isidor 已提交
190
	protected getActions(): IAction[] {
I
isidor 已提交
191
		return [
192
			this.instantiationService.createInstance(ToggleMaximizedPanelAction, ToggleMaximizedPanelAction.ID, ToggleMaximizedPanelAction.LABEL),
I
isidor 已提交
193
			this.instantiationService.createInstance(TogglePanelPositionAction, TogglePanelPositionAction.ID, TogglePanelPositionAction.LABEL),
I
isidor 已提交
194 195
			this.instantiationService.createInstance(ClosePanelAction, ClosePanelAction.ID, ClosePanelAction.LABEL)
		];
I
isidor 已提交
196 197
	}

I
isidor 已提交
198 199 200 201 202 203 204 205 206
	public getActivePanel(): IPanel {
		return this.getActiveComposite();
	}

	public getLastActivePanelId(): string {
		return this.getLastActiveCompositetId();
	}

	public hideActivePanel(): TPromise<void> {
207
		return this.hideActiveComposite().then(composite => void 0);
I
isidor 已提交
208
	}
I
isidor 已提交
209

210
	protected createTitleLabel(parent: Builder): ICompositeTitleLabel {
I
isidor 已提交
211 212
		const titleArea = this.compositeBar.create(parent.getHTMLElement());
		titleArea.classList.add('panel-switcher-container');
I
isidor 已提交
213

214 215
		return {
			updateTitle: (id, title, keybinding) => {
I
isidor 已提交
216
				const action = this.compositeBar.getAction(id);
217 218 219
				if (action) {
					action.label = title;
				}
220 221
			},
			updateStyles: () => {
222
				// Handled via theming participant
223 224
			}
		};
I
isidor 已提交
225 226
	}

I
isidor 已提交
227
	public layout(dimension: Dimension): Dimension[] {
I
isidor 已提交
228 229 230
		if (!this.partService.isVisible(Parts.PANEL_PART)) {
			return [dimension];
		}
I
isidor 已提交
231

232 233 234 235 236 237 238
		if (this.partService.getPanelPosition() === Position.RIGHT) {
			// Take into account the 1px border when layouting
			this.dimension = new Dimension(dimension.width - 1, dimension.height);
		} else {
			this.dimension = dimension;
		}
		const sizes = super.layout(this.dimension);
239 240 241 242 243 244
		this.layoutCompositeBar();

		return sizes;
	}

	private layoutCompositeBar(): void {
I
isidor 已提交
245
		if (this.dimension) {
I
isidor 已提交
246
			let availableWidth = this.dimension.width - 40; // take padding into account
I
isidor 已提交
247 248
			if (this.toolBar) {
				// adjust height for global actions showing
I
isidor 已提交
249
				availableWidth = Math.max(PanelPart.MIN_COMPOSITE_BAR_WIDTH, availableWidth - this.getToolbarWidth());
I
isidor 已提交
250 251
			}
			this.compositeBar.layout(new Dimension(availableWidth, this.dimension.height));
I
isidor 已提交
252 253
		}
	}
I
isidor 已提交
254

I
isidor 已提交
255 256 257 258 259 260 261 262 263 264
	private getToolbarWidth(): number {
		const activePanel = this.getActivePanel();
		if (!activePanel) {
			return 0;
		}
		if (!this.toolbarWidth.has(activePanel.getId())) {
			this.toolbarWidth.set(activePanel.getId(), this.toolBar.getContainer().getHTMLElement().offsetWidth);
		}

		return this.toolbarWidth.get(activePanel.getId());
I
isidor 已提交
265
	}
266 267 268 269
}

registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {

270 271 272 273 274 275 276 277 278 279 280 281 282 283
	// Panel Background: since panels can host editors, we apply a background rule if the panel background
	// color is different from the editor background color. This is a bit of a hack though. The better way
	// would be to have a way to push the background color onto each editor widget itself somehow.
	const panelBackground = theme.getColor(PANEL_BACKGROUND);
	if (panelBackground && panelBackground !== theme.getColor(editorBackground)) {
		collector.addRule(`
			.monaco-workbench > .part.panel > .content .monaco-editor,
			.monaco-workbench > .part.panel > .content .monaco-editor .margin,
			.monaco-workbench > .part.panel > .content .monaco-editor .monaco-editor-background {
				background-color: ${panelBackground};
			}
		`);
	}

284
	// Title Active
285
	const titleActive = theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND);
286
	const titleActiveBorder = theme.getColor(PANEL_ACTIVE_TITLE_BORDER);
287 288 289
	if (titleActive || titleActiveBorder) {
		collector.addRule(`
			.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:hover .action-label,
I
isidor 已提交
290
			.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label {
291 292 293 294 295
				color: ${titleActive};
				border-bottom-color: ${titleActiveBorder};
			}
		`);
	}
296 297

	// Title Inactive
298
	const titleInactive = theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND);
299 300 301 302 303 304 305
	if (titleInactive) {
		collector.addRule(`
			.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label {
				color: ${titleInactive};
			}
		`);
	}
306 307

	// Title focus
308
	const focusBorderColor = theme.getColor(focusBorder);
309 310
	if (focusBorderColor) {
		collector.addRule(`
I
isidor 已提交
311
			.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:focus .action-label {
312 313 314
				color: ${titleActive};
				border-bottom-color: ${focusBorderColor} !important;
				border-bottom: 1px solid;
I
isidor 已提交
315 316 317 318
			}
			`);
		collector.addRule(`
			.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:focus {
319 320
				outline: none;
			}
I
isidor 已提交
321
			`);
322
	}
323

B
Benjamin Pasero 已提交
324
	// Styling with Outline color (e.g. high contrast theme)
325
	const outline = theme.getColor(activeContrastBorder);
B
Benjamin Pasero 已提交
326
	if (outline) {
327
		const outline = theme.getColor(activeContrastBorder);
328 329

		collector.addRule(`
I
isidor 已提交
330
			.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item.checked .action-label,
331 332 333 334 335 336
			.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item .action-label:hover {
				outline-color: ${outline};
				outline-width: 1px;
				outline-style: solid;
				border-bottom: none;
				padding-bottom: 0;
I
isidor 已提交
337
				outline-offset: 1px;
338 339
			}

I
isidor 已提交
340
			.monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:not(.checked) .action-label:hover {
341 342 343 344
				outline-style: dashed;
			}
		`);
	}
345
});