From e891e2f3d19a4c10784e62dafdafba5c47580968 Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 8 Oct 2018 11:27:01 +0300 Subject: [PATCH] Add border for dirty tabs (#59759) * Theming for dirty tabs * Improve border for dirty tabs 1. Make it thinner 2. Add a setting to disable it 3. Use 4 colors (activeFocused, activeUnfocused, inactiveFocused, inactiveUnfocused) 4. Move part of logic * Hot apply of setting `dirtyTabBorder` * Rename the setting * Add default dirty border for all themes * 3 of the 4 colors should be derived from the one main color * Rename `dirty` to `modified` * Rename `modifiedActiveFocusedBorder` to `modifiedBorder` * Add modified border color for built-in themes * More contrast * Applying style directly on element * Changing only color * Using full border * Using existing div for border styling * Add setting to telemetry * Uncomment cleanup code * tweak colors and descriptions * :lipstick: --- .../theme-abyss/themes/abyss-color-theme.json | 1 + .../themes/kimbie-dark-color-theme.json | 1 + .../themes/dimmed-monokai-color-theme.json | 1 + .../themes/monokai-color-theme.json | 1 + .../themes/quietlight-color-theme.json | 1 + .../theme-red/themes/Red-color-theme.json | 1 + .../themes/solarized-dark-color-theme.json | 1 + .../themes/solarized-light-color-theme.json | 1 + .../themes/tomorrow-night-blue-theme.json | 1 + .../telemetry/common/telemetryUtils.ts | 1 + .../workbench/browser/parts/editor/editor.ts | 1 + .../parts/editor/media/tabstitlecontrol.css | 25 ++++++----- .../browser/parts/editor/tabsTitleControl.ts | 45 +++++++++++++++++-- src/vs/workbench/common/editor.ts | 1 + src/vs/workbench/common/theme.ts | 36 ++++++++++++--- .../electron-browser/main.contribution.ts | 5 +++ 16 files changed, 103 insertions(+), 20 deletions(-) diff --git a/extensions/theme-abyss/themes/abyss-color-theme.json b/extensions/theme-abyss/themes/abyss-color-theme.json index 53454a8ed40..d74d3d57ed5 100644 --- a/extensions/theme-abyss/themes/abyss-color-theme.json +++ b/extensions/theme-abyss/themes/abyss-color-theme.json @@ -372,6 +372,7 @@ "tab.border": "#2b2b4a", // "tab.activeBackground": "", "tab.inactiveBackground": "#10192c", + "tab.modifiedBorder": "#0072bf", // "tab.activeForeground": "", // "tab.inactiveForeground": "", diff --git a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json index 36f18683ad4..d4fbd2f7e2e 100644 --- a/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json +++ b/extensions/theme-kimbie-dark/themes/kimbie-dark-color-theme.json @@ -22,6 +22,7 @@ "editorGroupHeader.tabsBackground": "#131510", "editorLineNumber.activeForeground": "#adadad", "tab.inactiveBackground": "#131510", + "tab.modifiedBorder": "#cd9731", "titleBar.activeBackground": "#423523", "statusBar.background": "#423523", "statusBar.debuggingBackground": "#423523", diff --git a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json index 846b4feae52..b50041d6cdd 100644 --- a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json +++ b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json @@ -23,6 +23,7 @@ "editorGroupHeader.tabsBackground": "#282828", "tab.inactiveBackground": "#404040", "tab.border": "#303030", + "tab.modifiedBorder": "#6796e6", "tab.inactiveForeground": "#d8d8d8", "peekView.border": "#3655b5", "panelTitle.activeForeground": "#ffffff", diff --git a/extensions/theme-monokai/themes/monokai-color-theme.json b/extensions/theme-monokai/themes/monokai-color-theme.json index 8a21859255b..3806508775a 100644 --- a/extensions/theme-monokai/themes/monokai-color-theme.json +++ b/extensions/theme-monokai/themes/monokai-color-theme.json @@ -35,6 +35,7 @@ "editorGroup.dropBackground": "#41433980", "tab.inactiveBackground": "#34352f", "tab.border": "#1e1f1c", + "tab.modifiedBorder": "#007acc", "tab.inactiveForeground": "#ccccc7", // needs to be bright so it's readable when another editor group is focused "widget.shadow": "#000000", "progressBar.background": "#75715E", diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index 5d575484e9c..b1755e55baa 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -497,6 +497,7 @@ "editor.lineHighlightBackground": "#E4F6D4", "editorLineNumber.activeForeground": "#9769dc", "editor.selectionBackground": "#C9D0D9", + "tab.modifiedBorder": "#f1897f", "panel.background": "#F5F5F5", "sideBar.background": "#F2F2F2", "sideBarSectionHeader.background": "#ede8ef", diff --git a/extensions/theme-red/themes/Red-color-theme.json b/extensions/theme-red/themes/Red-color-theme.json index 2231f5e7e4e..3de9575b0db 100644 --- a/extensions/theme-red/themes/Red-color-theme.json +++ b/extensions/theme-red/themes/Red-color-theme.json @@ -5,6 +5,7 @@ "activityBar.background": "#580000", "tab.inactiveBackground": "#300a0a", "tab.activeBackground": "#490000", + "tab.modifiedBorder": "#db7e58", "sideBar.background": "#330000", "statusBar.background": "#700000", "statusBar.noFolderBackground": "#700000", diff --git a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json index e11ed770918..30cbe306f4d 100644 --- a/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json +++ b/extensions/theme-solarized-dark/themes/solarized-dark-color-theme.json @@ -422,6 +422,7 @@ "tab.inactiveForeground": "#93A1A1", "tab.inactiveBackground": "#004052", "tab.border": "#003847", + "tab.modifiedBorder": "#268bd2", // Workbench: Activity Bar "activityBar.background": "#003847", diff --git a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json index 1f0cd3202e7..79caab20000 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -417,6 +417,7 @@ "tab.activeBackground": "#FDF6E3", "tab.inactiveForeground": "#586E75", "tab.inactiveBackground": "#D3CBB7", + "tab.modifiedBorder": "#cb4b16", // "tab.activeBackground": "", // "tab.activeForeground": "", // "tab.inactiveForeground": "", diff --git a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json index 7ffe04a8d10..8d01980349f 100644 --- a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json +++ b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-theme.json @@ -26,6 +26,7 @@ "editorGroup.dropBackground": "#25375daa", "peekViewResult.background": "#001c40", "tab.inactiveBackground": "#001c40", + "tab.modifiedBorder": "#FFEEAD", "debugToolBar.background": "#001c40", "titleBar.activeBackground": "#001126", "statusBar.background": "#001126", diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index f16b7fd8627..8a636e69d9a 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -182,6 +182,7 @@ const configurationValueWhitelist = [ 'workbench.editor.enablePreview', 'workbench.editor.enablePreviewFromQuickOpen', 'workbench.editor.showTabs', + 'workbench.editor.highlightModifiedTabs', 'workbench.editor.swipeToNavigate', 'workbench.sideBar.location', 'workbench.startupEditor', diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index b86023265b4..0cb979f115a 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -28,6 +28,7 @@ export interface IEditorPartOptions extends IWorkbenchEditorPartConfiguration { export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { showTabs: true, + highlightModifiedTabs: false, tabCloseButton: 'right', tabSizing: 'fit', showIcons: true, diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css index 020706de84f..53dc25ba88e 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitlecontrol.css @@ -92,28 +92,33 @@ display: none; /* hidden by default until a color is provided (see below) */ } -.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container { +.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container, +.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container, +.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container { display: block; position: absolute; - top: 0; left: 0; z-index: 6; /* over possible title border */ pointer-events: none; - background-color: var(--tab-border-top-color); width: 100%; +} + +.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-top > .tab-border-top-container { + top: 0; height: 1px; + background-color: var(--tab-border-top-color); } .monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.active.tab-border-bottom > .tab-border-bottom-container { - display: block; - position: absolute; bottom: 0; - left: 0; - z-index: 6; /* over possible title border */ - pointer-events: none; - background-color: var(--tab-border-bottom-color); - width: 100%; height: 1px; + background-color: var(--tab-border-bottom-color); +} + +.monaco-workbench > .part.editor > .content .editor-group-container > .title .tabs-container > .tab.dirty-border-top > .tab-border-top-container { + top: 0; + height: 2px; + background-color: var(--tab-dirty-border-top-color); } /* Tab Label */ diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 0b796ad956b..b6626ac1bbf 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -25,7 +25,7 @@ import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElemen import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { getOrSet } from 'vs/base/common/map'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP } from 'vs/workbench/common/theme'; +import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, editorBackground, breadcrumbsBackground } from 'vs/platform/theme/common/colorRegistry'; import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, DragAndDropObserver } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; @@ -340,6 +340,9 @@ export class TabsTitleControl extends TitleControl { // Activity has an impact on each tab this.forEachTab((editor, index, tabContainer, tabLabelWidget, tabLabel) => { this.redrawEditorActive(isGroupActive, editor, tabContainer, tabLabelWidget); + if (this.accessor.partOptions.highlightModifiedTabs) { + this.redrawEditorDirty(editor, tabContainer); + } }); // Activity has an impact on the toolbar, so we need to update and layout @@ -378,7 +381,8 @@ export class TabsTitleControl extends TitleControl { oldOptions.tabCloseButton !== newOptions.tabCloseButton || oldOptions.tabSizing !== newOptions.tabSizing || oldOptions.showIcons !== newOptions.showIcons || - oldOptions.iconTheme !== newOptions.iconTheme + oldOptions.iconTheme !== newOptions.iconTheme || + oldOptions.highlightModifiedTabs !== newOptions.highlightModifiedTabs ) { this.redraw(); } @@ -892,7 +896,7 @@ export class TabsTitleControl extends TitleControl { // Tab is inactive else { - // Containr + // Container removeClass(tabContainer, 'active'); tabContainer.setAttribute('aria-selected', 'false'); tabContainer.style.backgroundColor = this.getColor(TAB_INACTIVE_BACKGROUND); @@ -904,10 +908,43 @@ export class TabsTitleControl extends TitleControl { } private redrawEditorDirty(editor: IEditorInput, tabContainer: HTMLElement): void { + + // Tab: dirty if (editor.isDirty()) { addClass(tabContainer, 'dirty'); - } else { + + // Highlight modified tabs with a border if configured + if (this.accessor.partOptions.highlightModifiedTabs) { + const isGroupActive = this.accessor.activeGroup === this.group; + const isTabActive = this.group.isActive(editor); + + let modifiedBorderColor: string; + if (isGroupActive && isTabActive) { + modifiedBorderColor = this.getColor(TAB_ACTIVE_MODIFIED_BORDER); + } else if (isGroupActive && !isTabActive) { + modifiedBorderColor = this.getColor(TAB_INACTIVE_MODIFIED_BORDER); + } else if (!isGroupActive && isTabActive) { + modifiedBorderColor = this.getColor(TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER); + } else { + modifiedBorderColor = this.getColor(TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER); + } + + if (modifiedBorderColor) { + addClass(tabContainer, 'dirty-border-top'); + tabContainer.style.setProperty('--tab-dirty-border-top-color', modifiedBorderColor); + } + } else { + removeClass(tabContainer, 'dirty-border-top'); + tabContainer.style.removeProperty('--tab-dirty-border-top-color'); + } + } + + // Tab: not dirty + else { removeClass(tabContainer, 'dirty'); + + removeClass(tabContainer, 'dirty-border-top'); + tabContainer.style.removeProperty('--tab-dirty-border-top-color'); } } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index c179bc95c23..9e02f137ee7 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -937,6 +937,7 @@ export interface IWorkbenchEditorConfiguration { export interface IWorkbenchEditorPartConfiguration { showTabs?: boolean; + highlightModifiedTabs?: boolean; tabCloseButton?: 'left' | 'right' | 'off'; tabSizing?: 'fit' | 'shrink'; showIcons?: boolean; diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 1f6ab7f0dde..d07022f7003 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -60,24 +60,48 @@ export const TAB_ACTIVE_BORDER = registerColor('tab.activeBorder', { hc: null }, nls.localize('tabActiveBorder', "Border on the bottom of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); -export const TAB_ACTIVE_BORDER_TOP = registerColor('tab.activeBorderTop', { - dark: null, - light: null, - hc: null -}, nls.localize('tabActiveBorderTop', "Border to the top of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); - export const TAB_UNFOCUSED_ACTIVE_BORDER = registerColor('tab.unfocusedActiveBorder', { dark: transparent(TAB_ACTIVE_BORDER, 0.5), light: transparent(TAB_ACTIVE_BORDER, 0.7), hc: null }, nls.localize('tabActiveUnfocusedBorder', "Border on the bottom of an active tab in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_ACTIVE_BORDER_TOP = registerColor('tab.activeBorderTop', { + dark: null, + light: null, + hc: null +}, nls.localize('tabActiveBorderTop', "Border to the top of an active tab. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + export const TAB_UNFOCUSED_ACTIVE_BORDER_TOP = registerColor('tab.unfocusedActiveBorderTop', { dark: transparent(TAB_ACTIVE_BORDER_TOP, 0.5), light: transparent(TAB_ACTIVE_BORDER_TOP, 0.7), hc: null }, nls.localize('tabActiveUnfocusedBorderTop', "Border to the top of an active tab in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_ACTIVE_MODIFIED_BORDER = registerColor('tab.activeModifiedBorder', { + dark: '#3399CC', + light: '#33AAEE', + hc: null +}, nls.localize('tabActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + +export const TAB_INACTIVE_MODIFIED_BORDER = registerColor('tab.inactiveModifiedBorder', { + dark: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5), + light: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5), + hc: Color.white +}, nls.localize('tabInactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an active group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + +export const TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER = registerColor('tab.unfocusedActiveModifiedBorder', { + dark: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.5), + light: transparent(TAB_ACTIVE_MODIFIED_BORDER, 0.7), + hc: Color.white +}, nls.localize('unfocusedActiveModifiedBorder', "Border on the top of modified (dirty) active tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + +export const TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER = registerColor('tab.unfocusedInactiveModifiedBorder', { + dark: transparent(TAB_INACTIVE_MODIFIED_BORDER, 0.5), + light: transparent(TAB_INACTIVE_MODIFIED_BORDER, 0.5), + hc: Color.white +}, nls.localize('unfocusedINactiveModifiedBorder', "Border on the top of modified (dirty) inactive tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + export const TAB_HOVER_BORDER = registerColor('tab.hoverBorder', { dark: null, light: null, diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index f775a01626f..5aeae1bba94 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -478,6 +478,11 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), 'default': true }, + 'workbench.editor.highlightModifiedTabs': { + 'type': 'boolean', + 'description': nls.localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not."), + 'default': false + }, 'workbench.editor.labelFormat': { 'type': 'string', 'enum': ['default', 'short', 'medium', 'long'],