diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index af71a291e7b17..3e3fe2f690635 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -166,9 +166,10 @@ export class CodeWindow implements ICodeWindow { } let useCustomTitleStyle = false; - if (isMacintosh && (!windowConfig || !windowConfig.titleBarStyle || windowConfig.titleBarStyle === 'custom')) { + if ((!isMacintosh && windowConfig && windowConfig.titleBarStyle === 'custom') || + (isMacintosh && (!windowConfig || !windowConfig.titleBarStyle || windowConfig.titleBarStyle === 'custom'))) { const isDev = !this.environmentService.isBuilt || !!config.extensionDevelopmentPath; - if (!isDev) { + if (!isMacintosh || !isDev) { useCustomTitleStyle = true; // not enabled when developing due to https://github.com/electron/electron/issues/3647 } } @@ -180,6 +181,9 @@ export class CodeWindow implements ICodeWindow { if (useCustomTitleStyle) { options.titleBarStyle = 'hidden'; this.hiddenTitleBarStyle = true; + if (!isMacintosh) { + options.frame = false; + } } // Create the browser window. @@ -207,6 +211,9 @@ export class CodeWindow implements ICodeWindow { this._win.setSheetOffset(22); // offset dialogs by the height of the custom title bar if we have any } + this._win.on('maximize', (e) => app.emit('browser-window-maximize', e, this._win)); + this._win.on('unmaximize', (e) => app.emit('browser-window-unmaximize', e, this._win)); + if (isFullscreenOrMaximized) { this._win.maximize(); @@ -603,6 +610,7 @@ export class CodeWindow implements ICodeWindow { // Theme windowConfiguration.baseTheme = this.getBaseTheme(); windowConfiguration.backgroundColor = this.getBackgroundColor(); + windowConfiguration.frameless = this.hasHiddenTitleBarStyle() && !isMacintosh; // Perf Counters windowConfiguration.perfEntries = exportEntries(); diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index bb161524a5079..c5aae7485da5c 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -103,6 +103,8 @@ export interface IWindowsService { onWindowOpen: Event; onWindowFocus: Event; onWindowBlur: Event; + onWindowMaximize: Event; + onWindowUnmaximize: Event; // Dialogs pickFileFolderAndOpen(options: INativeOpenDialogOptions): TPromise; @@ -131,6 +133,7 @@ export interface IWindowsService { isMaximized(windowId: number): TPromise; maximizeWindow(windowId: number): TPromise; unmaximizeWindow(windowId: number): TPromise; + minimizeWindow(windowId: number): TPromise; onWindowTitleDoubleClick(windowId: number): TPromise; setDocumentEdited(windowId: number, flag: boolean): TPromise; quit(): TPromise; @@ -181,6 +184,7 @@ export interface IWindowService { _serviceBrand: any; onDidChangeFocus: Event; + onDidChangeMaximize: Event; getConfiguration(): IWindowConfiguration; getCurrentWindowId(): number; @@ -202,6 +206,10 @@ export interface IWindowService { closeWindow(): TPromise; isFocused(): TPromise; setDocumentEdited(flag: boolean): TPromise; + isMaximized(): TPromise; + maximizeWindow(): TPromise; + unmaximizeWindow(): TPromise; + minimizeWindow(): TPromise; onWindowTitleDoubleClick(): TPromise; show(): TPromise; showMessageBox(options: MessageBoxOptions): TPromise; @@ -328,6 +336,7 @@ export interface IWindowConfiguration extends ParsedArgs, IOpenFileRequest { highContrast?: boolean; baseTheme?: string; backgroundColor?: string; + frameless?: boolean; accessibilitySupport?: boolean; perfEntries: PerformanceEntry[]; diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index e6bd74f3dd974..d50a4327ef7e7 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -49,6 +49,7 @@ export interface IWindowsChannel extends IChannel { call(command: 'isMaximized', arg: number): TPromise; call(command: 'maximizeWindow', arg: number): TPromise; call(command: 'unmaximizeWindow', arg: number): TPromise; + call(command: 'minimizeWindow', arg: number): TPromise; call(command: 'onWindowTitleDoubleClick', arg: number): TPromise; call(command: 'setDocumentEdited', arg: [number, boolean]): TPromise; call(command: 'quit'): TPromise; @@ -73,11 +74,15 @@ export class WindowsChannel implements IWindowsChannel { private onWindowOpen: Event; private onWindowFocus: Event; private onWindowBlur: Event; + private onWindowMaximize: Event; + private onWindowUnmaximize: Event; constructor(private service: IWindowsService) { this.onWindowOpen = buffer(service.onWindowOpen, true); this.onWindowFocus = buffer(service.onWindowFocus, true); this.onWindowBlur = buffer(service.onWindowBlur, true); + this.onWindowMaximize = buffer(service.onWindowMaximize, true); + this.onWindowUnmaximize = buffer(service.onWindowUnmaximize, true); } call(command: string, arg?: any): TPromise { @@ -85,6 +90,8 @@ export class WindowsChannel implements IWindowsChannel { case 'event:onWindowOpen': return eventToCall(this.onWindowOpen); case 'event:onWindowFocus': return eventToCall(this.onWindowFocus); case 'event:onWindowBlur': return eventToCall(this.onWindowBlur); + case 'event:onWindowMaximize': return eventToCall(this.onWindowMaximize); + case 'event:onWindowUnmaximize': return eventToCall(this.onWindowUnmaximize); case 'pickFileFolderAndOpen': return this.service.pickFileFolderAndOpen(arg); case 'pickFileAndOpen': return this.service.pickFileAndOpen(arg); case 'pickFolderAndOpen': return this.service.pickFolderAndOpen(arg); @@ -129,6 +136,7 @@ export class WindowsChannel implements IWindowsChannel { case 'isMaximized': return this.service.isMaximized(arg); case 'maximizeWindow': return this.service.maximizeWindow(arg); case 'unmaximizeWindow': return this.service.unmaximizeWindow(arg); + case 'minimizeWindow': return this.service.minimizeWindow(arg); case 'onWindowTitleDoubleClick': return this.service.onWindowTitleDoubleClick(arg); case 'setDocumentEdited': return this.service.setDocumentEdited(arg[0], arg[1]); case 'openWindow': return this.service.openWindow(arg[0], arg[1]); @@ -165,6 +173,13 @@ export class WindowsChannelClient implements IWindowsService { private _onWindowBlur: Event = eventFromCall(this.channel, 'event:onWindowBlur'); get onWindowBlur(): Event { return this._onWindowBlur; } + private _onWindowMaximize: Event = eventFromCall(this.channel, 'event:onWindowMaximize'); + get onWindowMaximize(): Event { return this._onWindowMaximize; } + + private _onWindowUnmaximize: Event = eventFromCall(this.channel, 'event:onWindowUnmaximize'); + get onWindowUnmaximize(): Event { return this._onWindowUnmaximize; } + + pickFileFolderAndOpen(options: INativeOpenDialogOptions): TPromise { return this.channel.call('pickFileFolderAndOpen', options); } @@ -285,6 +300,14 @@ export class WindowsChannelClient implements IWindowsService { return this.channel.call('unmaximizeWindow', windowId); } + minimizeWindow(windowId: number): TPromise { + return this.channel.call('minimizeWindow', windowId); + } + + addMaximizeListener(windowId: number, listener: (maximized: boolean) => void): TPromise { + return TPromise.as(null); + } + onWindowTitleDoubleClick(windowId: number): TPromise { return this.channel.call('onWindowTitleDoubleClick', windowId); } diff --git a/src/vs/platform/windows/electron-browser/windowService.ts b/src/vs/platform/windows/electron-browser/windowService.ts index fbc0aa9b39db7..e39faa22ee02e 100644 --- a/src/vs/platform/windows/electron-browser/windowService.ts +++ b/src/vs/platform/windows/electron-browser/windowService.ts @@ -16,6 +16,7 @@ import { ParsedArgs } from 'vs/platform/environment/common/environment'; export class WindowService implements IWindowService { readonly onDidChangeFocus: Event; + readonly onDidChangeMaximize: Event; _serviceBrand: any; @@ -26,7 +27,10 @@ export class WindowService implements IWindowService { ) { const onThisWindowFocus = mapEvent(filterEvent(windowsService.onWindowFocus, id => id === windowId), _ => true); const onThisWindowBlur = mapEvent(filterEvent(windowsService.onWindowBlur, id => id === windowId), _ => false); + const onThisWindowMaximize = mapEvent(filterEvent(windowsService.onWindowMaximize, id => id === windowId), _ => true); + const onThisWindowUnmaximize = mapEvent(filterEvent(windowsService.onWindowUnmaximize, id => id === windowId), _ => false); this.onDidChangeFocus = anyEvent(onThisWindowFocus, onThisWindowBlur); + this.onDidChangeMaximize = anyEvent(onThisWindowMaximize, onThisWindowUnmaximize); } getCurrentWindowId(): number { @@ -109,6 +113,22 @@ export class WindowService implements IWindowService { return this.windowsService.isFocused(this.windowId); } + isMaximized(): TPromise { + return this.windowsService.isMaximized(this.windowId); + } + + maximizeWindow(): TPromise { + return this.windowsService.maximizeWindow(this.windowId); + } + + unmaximizeWindow(): TPromise { + return this.windowsService.unmaximizeWindow(this.windowId); + } + + minimizeWindow(): TPromise { + return this.windowsService.minimizeWindow(this.windowId); + } + onWindowTitleDoubleClick(): TPromise { return this.windowsService.onWindowTitleDoubleClick(this.windowId); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 24c7924ff9ae9..73a3aff5adb00 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -39,6 +39,8 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable ); readonly onWindowBlur: Event = filterEvent(fromNodeEventEmitter(app, 'browser-window-blur', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); + readonly onWindowMaximize: Event = filterEvent(fromNodeEventEmitter(app, 'browser-window-maximize', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); + readonly onWindowUnmaximize: Event = filterEvent(fromNodeEventEmitter(app, 'browser-window-unmaximize', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); constructor( private sharedProcess: ISharedProcess, @@ -338,6 +340,16 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable return TPromise.as(null); } + minimizeWindow(windowId: number): TPromise { + const codeWindow = this.windowsMainService.getWindowById(windowId); + + if (codeWindow) { + codeWindow.win.minimize(); + } + + return TPromise.as(null); + } + onWindowTitleDoubleClick(windowId: number): TPromise { this.logService.trace('windowsService#onWindowTitleDoubleClick', windowId); const codeWindow = this.windowsMainService.getWindowById(windowId); @@ -510,4 +522,4 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable dispose(): void { this.disposables = dispose(this.disposables); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index e8dcb1d830b3f..62861f9fc0ddf 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -19,6 +19,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { getZoomFactor } from 'vs/base/browser/browser'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { isMacintosh } from 'vs/base/common/platform'; import { memoize } from 'vs/base/common/decorators'; import { NotificationsCenter } from 'vs/workbench/browser/parts/notifications/notificationsCenter'; import { NotificationsToasts } from 'vs/workbench/browser/parts/notifications/notificationsToasts'; @@ -36,7 +37,7 @@ const PANEL_SIZE_BEFORE_MAXIMIZED_BOUNDARY = 0.7; const HIDE_SIDEBAR_WIDTH_THRESHOLD = 50; const HIDE_PANEL_HEIGHT_THRESHOLD = 50; const HIDE_PANEL_WIDTH_THRESHOLD = 100; -const TITLE_BAR_HEIGHT = 22; +const TITLE_BAR_HEIGHT = isMacintosh ? 22 : 29; const STATUS_BAR_HEIGHT = 22; const ACTIVITY_BAR_WIDTH = 50; @@ -78,6 +79,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal private sashY: Sash; private _sidebarWidth: number; private sidebarHeight: number; + private titlebarBaseHeight: number; private titlebarHeight: number; private statusbarHeight: number; private panelSizeBeforeMaximized: number; @@ -149,6 +151,8 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal this.toUnbind.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); this.toUnbind.push(editorGroupService.onGroupOrientationChanged(e => this.onGroupOrientationChanged())); + this.onMaximizeChange(false); + this.registerSashListeners(); } @@ -463,7 +467,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal } this.statusbarHeight = isStatusbarHidden ? 0 : this.partLayoutInfo.statusbar.height; - this.titlebarHeight = isTitlebarHidden ? 0 : this.partLayoutInfo.titlebar.height / getZoomFactor(); // adjust for zoom prevention + this.titlebarHeight = isTitlebarHidden ? 0 : this.titlebarBaseHeight / getZoomFactor(); // adjust for zoom prevention this.sidebarHeight = this.workbenchSize.height - this.statusbarHeight - this.titlebarHeight; let sidebarSize = new Dimension(this.sidebarWidth, this.sidebarHeight); @@ -794,6 +798,14 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal } } + public onMaximizeChange(maximized: boolean): void { + if (!isMacintosh) { + this.titlebarBaseHeight = maximized ? 23 : this.partLayoutInfo.titlebar.height; + } else { + this.titlebarBaseHeight = this.partLayoutInfo.titlebar.height; + } + } + public dispose(): void { if (this.toUnbind) { dispose(this.toUnbind); diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index d4826a2b8f65e..3916b94265b51 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -91,6 +91,7 @@ export class SidebarPart extends CompositePart { const container = $(this.getContainer()); container.style('background-color', this.getColor(SIDE_BAR_BACKGROUND)); + container.style('color', this.getColor(SIDE_BAR_FOREGROUND)); const borderColor = this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder); diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 12c110e78fe61..35a56ff5b6918 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -28,4 +28,63 @@ text-overflow: ellipsis; -webkit-app-region: drag; zoom: 1; /* prevent zooming */ -} \ No newline at end of file +} + +/***********************\ +| Windows and Linux CSS | +\***********************/ + +.monaco-workbench.windows > .part.titlebar, .monaco-workbench.linux > .part.titlebar { + line-height: 25px; + height: 25px; + padding: 0 0 0 0.5em; + -webkit-app-region: no-drag; +} + +.monaco-workbench.windows > .part.titlebar > .window-title, .monaco-workbench.linux > .part.titlebar > .window-title { + line-height: 25px; + flex-grow: 1; + margin-left: 0.35em; + -webkit-app-region: drag; +} + +.monaco-workbench > .part.titlebar > .window-appicon { + width: calc(16em / 12); + height: calc(16em / 12); + -webkit-app-region: no-drag; +} + +.monaco-workbench > .part.titlebar > .window-icon { + width: calc(46em / 12); + height: calc(100% - 1.5em / 12); + margin-bottom: calc(1.5em / 12); + display: flex; + align-items: center; + justify-content: center; + -webkit-app-region: no-drag; + -webkit-transition: background-color .2s; + transition: background-color .2s; +} + +.window-unmaximize { + display: none; +} + +.monaco-workbench > .part.titlebar > .window-icon svg { + width: calc(10em / 12); + height: calc(10em / 12); + shape-rendering: crispEdges; +} + +.monaco-workbench > .part.titlebar > .window-icon:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.monaco-workbench > .part.titlebar.light > .window-icon:hover { + background-color: rgba(0, 0, 0, 0.1); +} + +.monaco-workbench > .part.titlebar.titlebar > .window-close:hover { + background-color: rgba(232, 17, 35, 0.9); + color: rgba(255, 255, 255, 1); +} diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 4fe96407a8572..113da14b33414 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -5,6 +5,7 @@ 'use strict'; +import * as path from 'path'; import 'vs/css!./media/titlebarpart'; import { TPromise } from 'vs/base/common/winjs.base'; import { Builder, $ } from 'vs/base/browser/builder'; @@ -31,6 +32,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER } from 'vs/workbench/common/theme'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import URI from 'vs/base/common/uri'; +import { Color } from 'vs/base/common/color'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { trim } from 'vs/base/common/strings'; import { addDisposableListener, EventType, EventHelper, Dimension } from 'vs/base/browser/dom'; @@ -227,8 +229,19 @@ export class TitlebarPart extends Part implements ITitleService { } public createContentArea(parent: HTMLElement): HTMLElement { + const SVGNS = 'http://www.w3.org/2000/svg'; this.titleContainer = $(parent); + if (!isMacintosh) { + $(this.titleContainer).img({ + class: 'window-appicon', + src: path.join(this.environmentService.appRoot, 'resources/linux/code.png') + }).on(EventType.DBLCLICK, (e) => { + EventHelper.stop(e, true); + this.windowService.closeWindow().then(null, errors.onUnexpectedError); + }); + } + // Title this.title = $(this.titleContainer).div({ class: 'window-title' }); if (this.pendingTitle) { @@ -251,6 +264,59 @@ export class TitlebarPart extends Part implements ITitleService { } }); + const $svg = (name: string, props: { [name: string]: any }) => { + const el = document.createElementNS(SVGNS, name); + Object.keys(props).forEach((prop) => { + el.setAttribute(prop, props[prop]); + }); + return el; + }; + + if (!isMacintosh) { + // The svgs and styles for the titlebar come from the electron-titlebar-windows package + $(this.titleContainer).div({ class: 'window-icon' }, (builder) => { + const svg = $svg('svg', { x: 0, y: 0, viewBox: '0 0 10 1' }); + svg.appendChild($svg('rect', { fill: 'currentColor', width: 10, height: 1 })); + builder.getHTMLElement().appendChild(svg); + }).on(EventType.CLICK, () => { + this.windowService.minimizeWindow().then(null, errors.onUnexpectedError); + }); + + $(this.titleContainer).div({ class: 'window-icon' }, (builder) => { + const svgf = $svg('svg', { class: 'window-maximize', x: 0, y: 0, viewBox: '0 0 10 10' }); + svgf.appendChild($svg('path', { fill: 'currentColor', d: 'M 0 0 L 0 10 L 10 10 L 10 0 L 0 0 z M 1 1 L 9 1 L 9 9 L 1 9 L 1 1 z' })); + builder.getHTMLElement().appendChild(svgf); + + const svgm = $svg('svg', { class: 'window-unmaximize', x: 0, y: 0, viewBox: '0 0 10 10' }); + const mask = $svg('mask', { id: 'Mask' }); + mask.appendChild($svg('rect', { fill: '#fff', width: 10, height: 10 })); + mask.appendChild($svg('path', { fill: '#000', d: 'M 3 1 L 9 1 L 9 7 L 8 7 L 8 2 L 3 2 L 3 1 z' })); + mask.appendChild($svg('path', { fill: '#000', d: 'M 1 3 L 7 3 L 7 9 L 1 9 L 1 3 z' })); + svgm.appendChild(mask); + svgm.appendChild($svg('path', { fill: 'currentColor', d: 'M 2 0 L 10 0 L 10 8 L 8 8 L 8 10 L 0 10 L 0 2 L 2 2 L 2 0 z', mask: 'url(#Mask)' })); + builder.getHTMLElement().appendChild(svgm); + }).on(EventType.CLICK, () => { + this.windowService.isMaximized().then((maximized) => { + if (maximized) { + return this.windowService.unmaximizeWindow(); + } else { + return this.windowService.maximizeWindow(); + } + }).then(null, errors.onUnexpectedError); + }); + + $(this.titleContainer).div({ class: 'window-icon window-close' }, (builder) => { + const svg = $svg('svg', { x: '0', y: '0', viewBox: '0 0 10 10' }); + svg.appendChild($svg('polygon', { fill: 'currentColor', points: '10,1 9,0 5,4 1,0 0,1 4,5 0,9 1,10 5,6 9,10 10,9 6,5' })); + builder.getHTMLElement().appendChild(svg); + }).on(EventType.CLICK, () => { + this.windowService.closeWindow().then(null, errors.onUnexpectedError); + }); + + this.windowService.isMaximized().then((max) => this.onDidChangeMaximized(max), errors.onUnexpectedError); + this.windowService.onDidChangeMaximize(this.onDidChangeMaximized, this); + } + // Since the title area is used to drag the window, we do not want to steal focus from the // currently active element. So we restore focus after a timeout back to where it was. this.titleContainer.on([EventType.MOUSE_DOWN], () => { @@ -262,16 +328,29 @@ export class TitlebarPart extends Part implements ITitleService { }, 0 /* need a timeout because we are in capture phase */); }, void 0, true /* use capture to know the currently active element properly */); + // Now that there exists a titelbar, we don't need the whole page to be a drag region anymore + (document.documentElement.style as any).webkitAppRegion = ''; + document.documentElement.style.height = ''; + return this.titleContainer.getHTMLElement(); } + private onDidChangeMaximized(maximized: boolean) { + ($(this.titleContainer).getHTMLElement().querySelector('.window-maximize') as SVGElement).style.display = maximized ? 'none' : 'inline'; + ($(this.titleContainer).getHTMLElement().querySelector('.window-unmaximize') as SVGElement).style.display = maximized ? 'inline' : 'none'; + $(this.titleContainer).getHTMLElement().style.paddingLeft = maximized ? '0.15em' : '0.5em'; + $(this.titleContainer).getHTMLElement().style.paddingRight = maximized ? 'calc(2em / 12)' : '0'; + } + protected updateStyles(): void { super.updateStyles(); // Part container if (this.titleContainer) { + const bgColor = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND); this.titleContainer.style('color', this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_FOREGROUND : TITLE_BAR_ACTIVE_FOREGROUND)); - this.titleContainer.style('background-color', this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND)); + this.titleContainer.style('background-color', bgColor); + this.titleContainer.getHTMLElement().classList.toggle('light', Color.fromHex(bgColor).isLighter()); const titleBorder = this.getColor(TITLE_BAR_BORDER); this.titleContainer.style('border-bottom', titleBorder ? `1px solid ${titleBorder}` : null); @@ -371,4 +450,4 @@ class ShowItemInFolderAction extends Action { public run(): TPromise { return this.windowsService.showItemInFolder(this.path); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/electron-browser/bootstrap/preload.js b/src/vs/workbench/electron-browser/bootstrap/preload.js index d451c2d5fb720..894c63e22dedb 100644 --- a/src/vs/workbench/electron-browser/bootstrap/preload.js +++ b/src/vs/workbench/electron-browser/bootstrap/preload.js @@ -23,6 +23,12 @@ const baseTheme = config.baseTheme || 'vs'; document.body.className = 'monaco-shell ' + baseTheme; + // makes the window draggable if there is no frame + if (config.frameless) { + document.documentElement.style.webkitAppRegion = 'drag'; + document.documentElement.style.height = '100%'; + } + // adds a stylesheet with the backgrdound color var backgroundColor = config.backgroundColor; if (!backgroundColor) { @@ -36,4 +42,4 @@ } catch (error) { console.error(error); } -})(); \ No newline at end of file +})(); diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index eb578183a03d1..1efe806c4e61d 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -418,10 +418,9 @@ configurationRegistry.registerConfiguration({ 'window.titleBarStyle': { 'type': 'string', 'enum': ['native', 'custom'], - 'default': 'custom', + 'default': isMacintosh ? 'custom' : 'native', 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('titleBarStyle', "Adjust the appearance of the window title bar. Changes require a full restart to apply."), - 'included': isMacintosh + 'description': nls.localize('titleBarStyle', "Adjust the appearance of the window title bar. Changes require a full restart to apply.") }, 'window.nativeTabs': { 'type': 'boolean', diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index 3523c79cce516..082c6129ff7b6 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -791,12 +791,9 @@ export class Workbench implements IPartService { } private getCustomTitleBarStyle(): 'custom' { - if (!isMacintosh) { - return null; // custom title bar is only supported on Mac currently - } const isDev = !this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment; - if (isDev) { + if (isMacintosh && isDev) { return null; // not enabled when developing due to https://github.com/electron/electron/issues/3647 } @@ -1186,6 +1183,14 @@ export class Workbench implements IPartService { this.notificationsCenter, // Notifications Center this.notificationsToasts // Notifications Toasts ); + + if (!isMacintosh && this.getCustomTitleBarStyle()) { + this.windowService.isMaximized().then((max) => { + this.workbenchLayout.onMaximizeChange(max); + this.workbenchLayout.layout(); + }); + this.windowService.onDidChangeMaximize(this.workbenchLayout.onMaximizeChange, this.workbenchLayout); + } } private renderWorkbench(): void { diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 5acfb8aab9e8e..cf6afde7b40b7 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -869,6 +869,7 @@ export class TestWindowService implements IWindowService { public _serviceBrand: any; onDidChangeFocus: Event = new Emitter().event; + onDidChangeMaximize: Event = new Emitter().event; isFocused(): TPromise { return TPromise.as(false); @@ -942,6 +943,10 @@ export class TestWindowService implements IWindowService { return TPromise.as(void 0); } + isMaximized(): TPromise { + return TPromise.as(void 0); + } + setDocumentEdited(flag: boolean): TPromise { return TPromise.as(void 0); } @@ -958,6 +963,18 @@ export class TestWindowService implements IWindowService { return TPromise.wrap({ button: 0 }); } + maximizeWindow(): TPromise { + return TPromise.as(void 0); + } + + unmaximizeWindow(): TPromise { + return TPromise.as(void 0); + } + + minimizeWindow(): TPromise { + return TPromise.as(void 0); + } + showSaveDialog(options: Electron.SaveDialogOptions): TPromise { return TPromise.wrap(void 0); } @@ -1011,6 +1028,8 @@ export class TestWindowsService implements IWindowsService { onWindowOpen: Event; onWindowFocus: Event; onWindowBlur: Event; + onWindowMaximize: Event; + onWindowUnmaximize: Event; isFocused(windowId: number): TPromise { return TPromise.as(false); @@ -1100,6 +1119,10 @@ export class TestWindowsService implements IWindowsService { return TPromise.as(void 0); } + minimizeWindow(windowId: number): TPromise { + return TPromise.as(void 0); + } + onWindowTitleDoubleClick(windowId: number): TPromise { return TPromise.as(void 0); } diff --git a/tslint.json b/tslint.json index 6e26e8166d0ed..d88ceb294b017 100644 --- a/tslint.json +++ b/tslint.json @@ -291,7 +291,7 @@ "**/vs/editor/contrib/**", // editor/contrib is equivalent to /browser/ by convention "**/vs/workbench/{common,browser}/**", "**/vs/workbench/services/*/{common,browser}/**", - "assert" + "*" // node modules ] }, { @@ -462,4 +462,4 @@ "translation-remind": true }, "defaultSeverity": "warning" -} \ No newline at end of file +}