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

* 💄
This commit is contained in:
Alexander 2018-10-08 11:27:01 +03:00 committed by Benjamin Pasero
parent 005858ed7c
commit e891e2f3d1
16 changed files with 103 additions and 20 deletions

View File

@ -372,6 +372,7 @@
"tab.border": "#2b2b4a",
// "tab.activeBackground": "",
"tab.inactiveBackground": "#10192c",
"tab.modifiedBorder": "#0072bf",
// "tab.activeForeground": "",
// "tab.inactiveForeground": "",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -422,6 +422,7 @@
"tab.inactiveForeground": "#93A1A1",
"tab.inactiveBackground": "#004052",
"tab.border": "#003847",
"tab.modifiedBorder": "#268bd2",
// Workbench: Activity Bar
"activityBar.background": "#003847",

View File

@ -417,6 +417,7 @@
"tab.activeBackground": "#FDF6E3",
"tab.inactiveForeground": "#586E75",
"tab.inactiveBackground": "#D3CBB7",
"tab.modifiedBorder": "#cb4b16",
// "tab.activeBackground": "",
// "tab.activeForeground": "",
// "tab.inactiveForeground": "",

View File

@ -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",

View File

@ -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',

View File

@ -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,

View File

@ -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 */

View File

@ -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');
}
}

View File

@ -937,6 +937,7 @@ export interface IWorkbenchEditorConfiguration {
export interface IWorkbenchEditorPartConfiguration {
showTabs?: boolean;
highlightModifiedTabs?: boolean;
tabCloseButton?: 'left' | 'right' | 'off';
tabSizing?: 'fit' | 'shrink';
showIcons?: boolean;

View File

@ -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,

View File

@ -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'],