Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/electron-browser/parts/titlebar/titlebarPart.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Event } from '../../../../base/common/event.js';
7
import { getZoomFactor } from '../../../../base/browser/browser.js';
8
import { $, addDisposableListener, append, EventType, getWindow, getWindowId, hide, show } from '../../../../base/browser/dom.js';
9
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
10
import { IConfigurationService, IConfigurationChangeEvent } from '../../../../platform/configuration/common/configuration.js';
11
import { IStorageService } from '../../../../platform/storage/common/storage.js';
12
import { INativeWorkbenchEnvironmentService } from '../../../services/environment/electron-browser/environmentService.js';
13
import { IHostService } from '../../../services/host/browser/host.js';
14
import { isMacintosh, isWindows, isLinux, isBigSurOrNewer } from '../../../../base/common/platform.js';
15
import { IMenuService, MenuId } from '../../../../platform/actions/common/actions.js';
16
import { BrowserTitlebarPart as BrowserTitlebarPart, BrowserTitleService, IAuxiliaryTitlebarPart } from '../../../browser/parts/titlebar/titlebarPart.js';
17
import { IContextMenuService } from '../../../../platform/contextview/browser/contextView.js';
18
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
19
import { IWorkbenchLayoutService, Parts } from '../../../services/layout/browser/layoutService.js';
20
import { INativeHostService } from '../../../../platform/native/common/native.js';
21
import { hasNativeTitlebar, useWindowControlsOverlay, DEFAULT_CUSTOM_TITLEBAR_HEIGHT, hasNativeMenu } from '../../../../platform/window/common/window.js';
22
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
23
import { Codicon } from '../../../../base/common/codicons.js';
24
import { ThemeIcon } from '../../../../base/common/themables.js';
25
import { NativeMenubarControl } from './menubarControl.js';
26
import { IEditorGroupsContainer, IEditorGroupsService } from '../../../services/editor/common/editorGroupsService.js';
27
import { IEditorService } from '../../../services/editor/common/editorService.js';
28
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
29
import { CodeWindow, mainWindow } from '../../../../base/browser/window.js';
30
import { IsWindowAlwaysOnTopContext } from '../../../common/contextkeys.js';
31
32
export class NativeTitlebarPart extends BrowserTitlebarPart {
33
34
//#region IView
35
36
override get minimumHeight(): number {
37
if (!isMacintosh) {
38
return super.minimumHeight;
39
}
40
41
return (this.isCommandCenterVisible ? DEFAULT_CUSTOM_TITLEBAR_HEIGHT : this.macTitlebarSize) / (this.preventZoom ? getZoomFactor(getWindow(this.element)) : 1);
42
}
43
override get maximumHeight(): number { return this.minimumHeight; }
44
45
private bigSurOrNewer: boolean;
46
private get macTitlebarSize() {
47
if (this.bigSurOrNewer) {
48
return 28; // macOS Big Sur increases title bar height
49
}
50
51
return 22;
52
}
53
54
//#endregion
55
56
private maxRestoreControl: HTMLElement | undefined;
57
private resizer: HTMLElement | undefined;
58
59
private cachedWindowControlStyles: { bgColor: string; fgColor: string } | undefined;
60
private cachedWindowControlHeight: number | undefined;
61
62
constructor(
63
id: string,
64
targetWindow: CodeWindow,
65
editorGroupsContainer: IEditorGroupsContainer,
66
@IContextMenuService contextMenuService: IContextMenuService,
67
@IConfigurationService configurationService: IConfigurationService,
68
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,
69
@IInstantiationService instantiationService: IInstantiationService,
70
@IThemeService themeService: IThemeService,
71
@IStorageService storageService: IStorageService,
72
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
73
@IContextKeyService contextKeyService: IContextKeyService,
74
@IHostService hostService: IHostService,
75
@INativeHostService private readonly nativeHostService: INativeHostService,
76
@IEditorGroupsService editorGroupService: IEditorGroupsService,
77
@IEditorService editorService: IEditorService,
78
@IMenuService menuService: IMenuService,
79
@IKeybindingService keybindingService: IKeybindingService
80
) {
81
super(id, targetWindow, editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, editorService, menuService, keybindingService);
82
83
this.bigSurOrNewer = isBigSurOrNewer(environmentService.os.release);
84
85
this.handleWindowsAlwaysOnTop(targetWindow.vscodeWindowId);
86
}
87
88
private async handleWindowsAlwaysOnTop(targetWindowId: number): Promise<void> {
89
const isWindowAlwaysOnTopContext = IsWindowAlwaysOnTopContext.bindTo(this.contextKeyService);
90
91
this._register(this.nativeHostService.onDidChangeWindowAlwaysOnTop(({ windowId, alwaysOnTop }) => {
92
if (windowId === targetWindowId) {
93
isWindowAlwaysOnTopContext.set(alwaysOnTop);
94
}
95
}));
96
97
isWindowAlwaysOnTopContext.set(await this.nativeHostService.isWindowAlwaysOnTop({ targetWindowId }));
98
}
99
100
protected override onMenubarVisibilityChanged(visible: boolean): void {
101
102
// Hide title when toggling menu bar
103
if ((isWindows || isLinux) && this.currentMenubarVisibility === 'toggle' && visible) {
104
105
// Hack to fix issue #52522 with layered webkit-app-region elements appearing under cursor
106
if (this.dragRegion) {
107
hide(this.dragRegion);
108
setTimeout(() => show(this.dragRegion!), 50);
109
}
110
}
111
112
super.onMenubarVisibilityChanged(visible);
113
}
114
115
protected override onConfigurationChanged(event: IConfigurationChangeEvent): void {
116
super.onConfigurationChanged(event);
117
118
if (event.affectsConfiguration('window.doubleClickIconToClose')) {
119
if (this.appIcon) {
120
this.onUpdateAppIconDragBehavior();
121
}
122
}
123
}
124
125
private onUpdateAppIconDragBehavior(): void {
126
const setting = this.configurationService.getValue('window.doubleClickIconToClose');
127
if (setting && this.appIcon) {
128
(this.appIcon.style as any)['-webkit-app-region'] = 'no-drag';
129
} else if (this.appIcon) {
130
(this.appIcon.style as any)['-webkit-app-region'] = 'drag';
131
}
132
}
133
134
protected override installMenubar(): void {
135
super.installMenubar();
136
137
if (this.menubar) {
138
return;
139
}
140
141
if (this.customMenubar.value) {
142
this._register(this.customMenubar.value.onFocusStateChange(e => this.onMenubarFocusChanged(e)));
143
}
144
}
145
146
private onMenubarFocusChanged(focused: boolean): void {
147
if ((isWindows || isLinux) && this.currentMenubarVisibility !== 'compact' && this.dragRegion) {
148
if (focused) {
149
hide(this.dragRegion);
150
} else {
151
show(this.dragRegion);
152
}
153
}
154
}
155
156
protected override createContentArea(parent: HTMLElement): HTMLElement {
157
const result = super.createContentArea(parent);
158
const targetWindow = getWindow(parent);
159
const targetWindowId = getWindowId(targetWindow);
160
161
// Native menu controller
162
if (isMacintosh || hasNativeMenu(this.configurationService)) {
163
this._register(this.instantiationService.createInstance(NativeMenubarControl));
164
}
165
166
// App Icon (Native Windows/Linux)
167
if (this.appIcon) {
168
this.onUpdateAppIconDragBehavior();
169
170
this._register(addDisposableListener(this.appIcon, EventType.DBLCLICK, (() => {
171
this.nativeHostService.closeWindow({ targetWindowId });
172
})));
173
}
174
175
// Custom Window Controls (Native Windows/Linux)
176
if (
177
!hasNativeTitlebar(this.configurationService) && // not for native title bars
178
!useWindowControlsOverlay(this.configurationService) && // not when controls are natively drawn
179
this.windowControlsContainer
180
) {
181
182
// Minimize
183
const minimizeIcon = append(this.windowControlsContainer, $('div.window-icon.window-minimize' + ThemeIcon.asCSSSelector(Codicon.chromeMinimize)));
184
this._register(addDisposableListener(minimizeIcon, EventType.CLICK, () => {
185
this.nativeHostService.minimizeWindow({ targetWindowId });
186
}));
187
188
// Restore
189
this.maxRestoreControl = append(this.windowControlsContainer, $('div.window-icon.window-max-restore'));
190
this._register(addDisposableListener(this.maxRestoreControl, EventType.CLICK, async () => {
191
const maximized = await this.nativeHostService.isMaximized({ targetWindowId });
192
if (maximized) {
193
return this.nativeHostService.unmaximizeWindow({ targetWindowId });
194
}
195
196
return this.nativeHostService.maximizeWindow({ targetWindowId });
197
}));
198
199
// Close
200
const closeIcon = append(this.windowControlsContainer, $('div.window-icon.window-close' + ThemeIcon.asCSSSelector(Codicon.chromeClose)));
201
this._register(addDisposableListener(closeIcon, EventType.CLICK, () => {
202
this.nativeHostService.closeWindow({ targetWindowId });
203
}));
204
205
// Resizer
206
this.resizer = append(this.rootContainer, $('div.resizer'));
207
this._register(Event.runAndSubscribe(this.layoutService.onDidChangeWindowMaximized, ({ windowId, maximized }) => {
208
if (windowId === targetWindowId) {
209
this.onDidChangeWindowMaximized(maximized);
210
}
211
}, { windowId: targetWindowId, maximized: this.layoutService.isWindowMaximized(targetWindow) }));
212
}
213
214
// Window System Context Menu
215
// See https://github.com/electron/electron/issues/24893
216
if (isWindows && !hasNativeTitlebar(this.configurationService)) {
217
this._register(this.nativeHostService.onDidTriggerWindowSystemContextMenu(({ windowId, x, y }) => {
218
if (targetWindowId !== windowId) {
219
return;
220
}
221
222
const zoomFactor = getZoomFactor(getWindow(this.element));
223
this.onContextMenu(new MouseEvent(EventType.MOUSE_UP, { clientX: x / zoomFactor, clientY: y / zoomFactor }), MenuId.TitleBarContext);
224
}));
225
}
226
227
return result;
228
}
229
230
private onDidChangeWindowMaximized(maximized: boolean): void {
231
if (this.maxRestoreControl) {
232
if (maximized) {
233
this.maxRestoreControl.classList.remove(...ThemeIcon.asClassNameArray(Codicon.chromeMaximize));
234
this.maxRestoreControl.classList.add(...ThemeIcon.asClassNameArray(Codicon.chromeRestore));
235
} else {
236
this.maxRestoreControl.classList.remove(...ThemeIcon.asClassNameArray(Codicon.chromeRestore));
237
this.maxRestoreControl.classList.add(...ThemeIcon.asClassNameArray(Codicon.chromeMaximize));
238
}
239
}
240
241
if (this.resizer) {
242
if (maximized) {
243
hide(this.resizer);
244
} else {
245
show(this.resizer);
246
}
247
}
248
}
249
250
override updateStyles(): void {
251
super.updateStyles();
252
253
// Part container
254
if (this.element) {
255
if (useWindowControlsOverlay(this.configurationService)) {
256
if (
257
!this.cachedWindowControlStyles ||
258
this.cachedWindowControlStyles.bgColor !== this.element.style.backgroundColor ||
259
this.cachedWindowControlStyles.fgColor !== this.element.style.color
260
) {
261
this.nativeHostService.updateWindowControls({
262
targetWindowId: getWindowId(getWindow(this.element)),
263
backgroundColor: this.element.style.backgroundColor,
264
foregroundColor: this.element.style.color
265
});
266
}
267
}
268
}
269
}
270
271
override layout(width: number, height: number): void {
272
super.layout(width, height);
273
274
if (useWindowControlsOverlay(this.configurationService)) {
275
276
// When the user goes into full screen mode, the height of the title bar becomes 0.
277
// Instead, set it back to the default titlebar height for Catalina users
278
// so that they can have the traffic lights rendered at the proper offset.
279
// Ref https://github.com/microsoft/vscode/issues/159862
280
281
const newHeight = (height > 0 || this.bigSurOrNewer) ? Math.round(height * getZoomFactor(getWindow(this.element))) : this.macTitlebarSize;
282
if (newHeight !== this.cachedWindowControlHeight) {
283
this.cachedWindowControlHeight = newHeight;
284
this.nativeHostService.updateWindowControls({
285
targetWindowId: getWindowId(getWindow(this.element)),
286
height: newHeight
287
});
288
}
289
}
290
}
291
}
292
293
export class MainNativeTitlebarPart extends NativeTitlebarPart {
294
295
constructor(
296
@IContextMenuService contextMenuService: IContextMenuService,
297
@IConfigurationService configurationService: IConfigurationService,
298
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,
299
@IInstantiationService instantiationService: IInstantiationService,
300
@IThemeService themeService: IThemeService,
301
@IStorageService storageService: IStorageService,
302
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
303
@IContextKeyService contextKeyService: IContextKeyService,
304
@IHostService hostService: IHostService,
305
@INativeHostService nativeHostService: INativeHostService,
306
@IEditorGroupsService editorGroupService: IEditorGroupsService,
307
@IEditorService editorService: IEditorService,
308
@IMenuService menuService: IMenuService,
309
@IKeybindingService keybindingService: IKeybindingService
310
) {
311
super(Parts.TITLEBAR_PART, mainWindow, editorGroupService.mainPart, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, editorGroupService, editorService, menuService, keybindingService);
312
}
313
}
314
315
export class AuxiliaryNativeTitlebarPart extends NativeTitlebarPart implements IAuxiliaryTitlebarPart {
316
317
private static COUNTER = 1;
318
319
get height() { return this.minimumHeight; }
320
321
constructor(
322
readonly container: HTMLElement,
323
editorGroupsContainer: IEditorGroupsContainer,
324
private readonly mainTitlebar: BrowserTitlebarPart,
325
@IContextMenuService contextMenuService: IContextMenuService,
326
@IConfigurationService configurationService: IConfigurationService,
327
@INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService,
328
@IInstantiationService instantiationService: IInstantiationService,
329
@IThemeService themeService: IThemeService,
330
@IStorageService storageService: IStorageService,
331
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
332
@IContextKeyService contextKeyService: IContextKeyService,
333
@IHostService hostService: IHostService,
334
@INativeHostService nativeHostService: INativeHostService,
335
@IEditorGroupsService editorGroupService: IEditorGroupsService,
336
@IEditorService editorService: IEditorService,
337
@IMenuService menuService: IMenuService,
338
@IKeybindingService keybindingService: IKeybindingService
339
) {
340
const id = AuxiliaryNativeTitlebarPart.COUNTER++;
341
super(`workbench.parts.auxiliaryTitle.${id}`, getWindow(container), editorGroupsContainer, contextMenuService, configurationService, environmentService, instantiationService, themeService, storageService, layoutService, contextKeyService, hostService, nativeHostService, editorGroupService, editorService, menuService, keybindingService);
342
}
343
344
override get preventZoom(): boolean {
345
346
// Prevent zooming behavior if any of the following conditions are met:
347
// 1. Shrinking below the window control size (zoom < 1)
348
// 2. No custom items are present in the main title bar
349
// The auxiliary title bar never contains any zoomable items itself,
350
// but we want to match the behavior of the main title bar.
351
352
return getZoomFactor(getWindow(this.element)) < 1 || !this.mainTitlebar.hasZoomableElements;
353
}
354
}
355
356
export class NativeTitleService extends BrowserTitleService {
357
358
protected override createMainTitlebarPart(): MainNativeTitlebarPart {
359
return this.instantiationService.createInstance(MainNativeTitlebarPart);
360
}
361
362
protected override doCreateAuxiliaryTitlebarPart(container: HTMLElement, editorGroupsContainer: IEditorGroupsContainer, instantiationService: IInstantiationService): AuxiliaryNativeTitlebarPart {
363
return instantiationService.createInstance(AuxiliaryNativeTitlebarPart, container, editorGroupsContainer, this.mainPart);
364
}
365
}
366
367