diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 5966bb41b2f..45c8e0562f5 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -669,11 +669,11 @@ export function findParentWithClass(node: HTMLElement, clazz: string, stopAtClaz return null; } -export function createStyleSheet(): HTMLStyleElement { +export function createStyleSheet(container: HTMLElement = document.getElementsByTagName('head')[0]): HTMLStyleElement { let style = document.createElement('style'); style.type = 'text/css'; style.media = 'screen'; - document.getElementsByTagName('head')[0].appendChild(style); + container.appendChild(style); return style; } diff --git a/src/vs/base/browser/ui/list/list.css b/src/vs/base/browser/ui/list/list.css index f6d8a1134db..bf8769cdc60 100644 --- a/src/vs/base/browser/ui/list/list.css +++ b/src/vs/base/browser/ui/list/list.css @@ -40,26 +40,4 @@ /* for OS X ballistic scrolling */ .monaco-list-row.scrolling { display: none !important; -} - -/* Hover */ -.vs .monaco-list-row:hover { background-color: #F0F0F0; } -.vs-dark .monaco-list-row:hover { background-color: rgba(255, 255, 255, 0.08); } -.hc-black .monaco-list-row:hover { outline: 1px dashed #f38518; outline-offset: -1px; background: transparent; } - -/* Focus */ -.monaco-list.element-focused { outline: 0 !important; } -.vs .monaco-list:focus .monaco-list-row.focused { background-color: #DCEBFC; } -.vs-dark .monaco-list:focus .monaco-list-row.focused { background-color: #073655; } -.hc-black .monaco-list:focus .monaco-list-row.focused { outline: 1px solid #f38518; outline-offset: -1px; background: transparent } - -/* Selection */ -.vs .monaco-list .monaco-list-row.selected { background-color: #CCCEDB; } -.vs .monaco-list:focus .monaco-list-row.selected { background-color: #4FA7FF; color: white; } -.vs-dark .monaco-list .monaco-list-row.selected { background-color: #3F3F46; } -.vs-dark .monaco-list:focus .monaco-list-row.selected { background-color: #0E639C; color: white; } -.hc-black .monaco-list .monaco-list-row.selected { outline: 1px dotted #f38518; color: white; } - -/* Selection and focus */ -.vs .monaco-list:focus .monaco-list-row.selected.focused { background-color: #3399FF; color: white; } -.vs-dark .monaco-list:focus .monaco-list-row.selected.focused { background-color: #094771; color: white; } \ No newline at end of file +} \ No newline at end of file diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index f0dd7db987e..f8b20bbce80 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -18,6 +18,8 @@ import Event, { Emitter, EventBufferer, chain, mapEvent, fromCallback, createEmp import { domEvent } from 'vs/base/browser/event'; import { IDelegate, IRenderer, IListEvent, IListMouseEvent, IListContextMenuEvent } from './list'; import { ListView, IListViewOptions } from './listView'; +import { Color } from "vs/base/common/color"; +import { mixin } from "vs/base/common/objects"; export interface IIdentityProvider { (element: T): string; @@ -394,13 +396,36 @@ class MouseController implements IDisposable { } } -export interface IListOptions extends IListViewOptions, IMouseControllerOptions { +export interface IListOptions extends IListViewOptions, IMouseControllerOptions, IListStyles { identityProvider?: IIdentityProvider; ariaLabel?: string; mouseSupport?: boolean; keyboardSupport?: boolean; } +export interface IListStyles { + listFocusBackground?: Color; + listActiveSelectionBackground?: Color; + listActiveSelectionForeground?: Color; + listFocusAndSelectionBackground?: Color; + listFocusAndSelectionForeground?: Color; + listInactiveSelectionBackground?: Color; + listHoverBackground?: Color; + listDropBackground?: Color; + listFocusOutline?: Color; +} + +const defaultStyles: IListStyles = { + listFocusBackground: Color.fromHex('#073655'), + listActiveSelectionBackground: Color.fromHex('#0E639C'), + listActiveSelectionForeground: Color.fromHex('#FFFFFF'), + listFocusAndSelectionBackground: Color.fromHex('#094771'), + listFocusAndSelectionForeground: Color.fromHex('#FFFFFF'), + listInactiveSelectionBackground: Color.fromHex('#3F3F46'), + listHoverBackground: Color.fromHex('#2A2D2E'), + listDropBackground: Color.fromHex('#383B3D') +}; + const DefaultOptions: IListOptions = { keyboardSupport: true, mouseSupport: true @@ -522,6 +547,7 @@ export class List implements ISpliceable, IDisposable { private view: ListView; private spliceable: ISpliceable; private disposables: IDisposable[]; + private styleElement: HTMLStyleElement; @memoize get onFocusChange(): Event> { return mapEvent(this.eventBufferer.wrapEvent(this.focus.onChange), e => this.toListEvent(e)); @@ -559,13 +585,17 @@ export class List implements ISpliceable, IDisposable { this.focus = new FocusTrait(i => this.getElementDomId(i)); this.selection = new Trait('selected'); this.eventBufferer = new EventBufferer(); + mixin(options, defaultStyles, false); renderers = renderers.map(r => new PipelineRenderer(r.templateId, [this.focus.renderer, this.selection.renderer, r])); this.view = new ListView(container, delegate, renderers, options); this.view.domNode.setAttribute('role', 'tree'); + DOM.addClass(this.view.domNode, this.idPrefix); this.view.domNode.tabIndex = 0; + this.styleElement = DOM.createStyleSheet(this.view.domNode); + this.spliceable = new CombinedSpliceable([ new TraitSpliceable(this.focus, this.view, options.identityProvider), new TraitSpliceable(this.selection, this.view, options.identityProvider), @@ -595,6 +625,8 @@ export class List implements ISpliceable, IDisposable { if (options.ariaLabel) { this.view.domNode.setAttribute('aria-label', options.ariaLabel); } + + this.style(options); } splice(start: number, deleteCount: number, elements: T[] = []): void { @@ -774,6 +806,29 @@ export class List implements ISpliceable, IDisposable { this._onOpen.fire(indexes); } + style(styles: IListStyles): void { + + // Indicate selection/focus via background color + if (!styles.listFocusOutline) { + this.styleElement.innerHTML = ` + .monaco-list.${this.idPrefix}:focus .monaco-list-row.focused { background-color: ${styles.listFocusBackground}; } + .monaco-list.${this.idPrefix}:focus .monaco-list-row.selected { background-color: ${styles.listActiveSelectionBackground}; color: ${styles.listActiveSelectionForeground}; } + .monaco-list.${this.idPrefix}:focus .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; color: ${styles.listFocusAndSelectionForeground}; } + .monaco-list.${this.idPrefix} .monaco-list-row.selected { background-color: ${styles.listInactiveSelectionBackground}; } + .monaco-list.${this.idPrefix} .monaco-list-row:hover { background-color: ${styles.listHoverBackground}; } + `; + } + + // Indicate selection/focus via outline + else { + this.styleElement.innerHTML = ` + .monaco-list.${this.idPrefix} .monaco-list-row.selected { outline: 1px dotted ${styles.listFocusOutline}; color: white; } + .monaco-list.${this.idPrefix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; background: transparent } + .monaco-list.${this.idPrefix} .monaco-list-row:hover { outline: 1px dashed ${styles.listFocusOutline}; outline-offset: -1px; background: transparent; } + `; + } + } + private toListEvent({ indexes }: ITraitChangeEvent) { return { indexes, elements: indexes.map(i => this.view.element(i)) }; } diff --git a/src/vs/base/parts/tree/browser/tree.css b/src/vs/base/parts/tree/browser/tree.css index d9aaa763cd5..90df70bf9d1 100644 --- a/src/vs/base/parts/tree/browser/tree.css +++ b/src/vs/base/parts/tree/browser/tree.css @@ -89,26 +89,6 @@ opacity: 0.3; } -/* Default style */ - -.monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { background-color: #DCEBFC; } -.monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: #4FA7FF; color: white; } -.monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { background-color: #3399FF; color: white; } -.monaco-tree .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: #CCCEDB; } -.monaco-tree .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { background-color: #F0F0F0; } -.monaco-tree .monaco-tree-wrapper.drop-target, -.monaco-tree .monaco-tree-rows > .monaco-tree-row.drop-target { background-color: #DDECFF !important; color: inherit !important; } - -/* VS Dark */ - -.vs-dark .monaco-tree.focused .monaco-tree-row.focused:not(.highlighted) { background-color: #073655; } -.vs-dark .monaco-tree.focused .monaco-tree-row.selected:not(.highlighted) { background-color: #0E639C; color: white; } -.vs-dark .monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { background-color: #094771; color: white; } -.vs-dark .monaco-tree .monaco-tree-row.selected:not(.highlighted) { background-color: #3F3F46; } -.vs-dark .monaco-tree .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { background-color: #2A2D2E; } -.vs-dark .monaco-tree-wrapper.drop-target, -.vs-dark .monaco-tree .monaco-tree-row.drop-target { background-color: #383B3D !important; color: inherit !important; } - .vs-dark .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before { background-image: url('collapsed-dark.svg'); } @@ -121,16 +101,6 @@ background-image: url('loading-dark.svg'); } -/* High Contrast Theming */ - -.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row { background: none !important; border: 1px solid transparent; } -.hc-black .monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { border: 1px dotted #f38518; } -.hc-black .monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid #f38518; } -.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid #f38518; } -.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { border: 1px dashed #f38518; } -.hc-black .monaco-tree .monaco-tree-wrapper.drop-target, -.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row.drop-target { background: none !important; border: 1px dashed #f38518; } - .hc-black .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before { background-image: url('collapsed-hc.svg'); } diff --git a/src/vs/base/parts/tree/browser/tree.ts b/src/vs/base/parts/tree/browser/tree.ts index 43997c31538..17f89938944 100644 --- a/src/vs/base/parts/tree/browser/tree.ts +++ b/src/vs/base/parts/tree/browser/tree.ts @@ -13,6 +13,7 @@ import { INavigator } from 'vs/base/common/iterator'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import Event from 'vs/base/common/event'; import { IAction, IActionItem } from "vs/base/common/actions"; +import { Color } from "vs/base/common/color"; export interface ITree extends Events.IEventEmitter { @@ -337,6 +338,11 @@ export interface ITree extends Events.IEventEmitter { */ getNavigator(fromElement?: any, subTreeOnly?: boolean): INavigator; + /** + * Apply styles to the tree. + */ + style(styles: ITreeStyles): void; + /** * Disposes the tree */ @@ -644,7 +650,7 @@ export interface ITreeConfiguration { accessibilityProvider?: IAccessibilityProvider; } -export interface ITreeOptions { +export interface ITreeOptions extends ITreeStyles { twistiePixels?: number; showTwistie?: boolean; indentPixels?: number; @@ -657,6 +663,18 @@ export interface ITreeOptions { keyboardSupport?: boolean; } +export interface ITreeStyles { + listFocusBackground?: Color; + listActiveSelectionBackground?: Color; + listActiveSelectionForeground?: Color; + listFocusAndSelectionBackground?: Color; + listFocusAndSelectionForeground?: Color; + listInactiveSelectionBackground?: Color; + listHoverBackground?: Color; + listDropBackground?: Color; + listFocusOutline?: Color; +} + export interface ITreeContext extends ITreeConfiguration { tree: ITree; options: ITreeOptions; diff --git a/src/vs/base/parts/tree/browser/treeImpl.ts b/src/vs/base/parts/tree/browser/treeImpl.ts index 136dc028723..e28e2f7bacf 100644 --- a/src/vs/base/parts/tree/browser/treeImpl.ts +++ b/src/vs/base/parts/tree/browser/treeImpl.ts @@ -14,6 +14,8 @@ import _ = require('vs/base/parts/tree/browser/tree'); import { INavigator, MappedNavigator } from 'vs/base/common/iterator'; import Event, { Emitter } from 'vs/base/common/event'; import Lifecycle = require('vs/base/common/lifecycle'); +import { Color } from "vs/base/common/color"; +import { mixin } from "vs/base/common/objects"; export class TreeContext implements _.ITreeContext { @@ -48,6 +50,17 @@ export class TreeContext implements _.ITreeContext { } } +const defaultStyles: _.ITreeStyles = { + listFocusBackground: Color.fromHex('#073655'), + listActiveSelectionBackground: Color.fromHex('#0E639C'), + listActiveSelectionForeground: Color.fromHex('#FFFFFF'), + listFocusAndSelectionBackground: Color.fromHex('#094771'), + listFocusAndSelectionForeground: Color.fromHex('#FFFFFF'), + listInactiveSelectionBackground: Color.fromHex('#3F3F46'), + listHoverBackground: Color.fromHex('#2A2D2E'), + listDropBackground: Color.fromHex('#383B3D') +}; + export class Tree extends Events.EventEmitter implements _.ITree { private container: HTMLElement; @@ -76,6 +89,7 @@ export class Tree extends Events.EventEmitter implements _.ITree { this.container = container; this.configuration = configuration; this.options = options; + mixin(this.options, defaultStyles, false); this.options.twistiePixels = typeof this.options.twistiePixels === 'number' ? this.options.twistiePixels : 32; this.options.showTwistie = this.options.showTwistie === false ? false : true; @@ -96,6 +110,10 @@ export class Tree extends Events.EventEmitter implements _.ITree { this.toDispose.push(this.model.addListener2('highlight', () => this._onHighlightChange.fire())); } + public style(styles: _.ITreeStyles): void { + this.view.applyStyles(styles); + } + get onDOMFocus(): Event { return this.view && this.view.onDOMFocus; } diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts index bb4db9ea9de..7e3aa1a6685 100644 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ b/src/vs/base/parts/tree/browser/treeView.ts @@ -362,6 +362,9 @@ export class TreeView extends HeightMap { static BINDING = 'monaco-tree-row'; static LOADING_DECORATION_DELAY = 800; + private static counter: number = 0; + private instance: number; + private static currentExternalDragAndDropData: _.IDragAndDropData = null; private context: IViewContext; @@ -371,6 +374,7 @@ export class TreeView extends HeightMap { private viewListeners: Lifecycle.IDisposable[]; private domNode: HTMLElement; private wrapper: HTMLElement; + private styleElement: HTMLStyleElement; private rowsContainer: HTMLElement; private scrollableElement: ScrollableElement; private wrapperGesture: Touch.Gesture; @@ -413,6 +417,9 @@ export class TreeView extends HeightMap { constructor(context: _.ITreeContext, container: HTMLElement) { super(); + TreeView.counter++; + this.instance = TreeView.counter; + this.context = { dataSource: context.dataSource, renderer: context.renderer, @@ -434,9 +441,11 @@ export class TreeView extends HeightMap { this.items = {}; this.domNode = document.createElement('div'); - this.domNode.className = 'monaco-tree no-focused-item'; + this.domNode.className = `monaco-tree no-focused-item monaco-tree-instance-${this.instance}`; this.domNode.tabIndex = 0; + this.styleElement = DOM.createStyleSheet(this.domNode); + // ARIA this.domNode.setAttribute('role', 'tree'); if (this.context.options.ariaLabel) { @@ -540,6 +549,37 @@ export class TreeView extends HeightMap { this.layout(); this.setupMSGesture(); + + this.applyStyles(context.options); + } + + public applyStyles(styles: _.ITreeStyles): void { + + // Indicate selection/focus via background color + if (!styles.listFocusOutline) { + this.styleElement.innerHTML = ` + .monaco-tree.monaco-tree-instance-${this.instance}.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { background-color: ${styles.listFocusBackground}; } + .monaco-tree.monaco-tree-instance-${this.instance}.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: ${styles.listActiveSelectionBackground}; color: ${styles.listActiveSelectionForeground}; } + .monaco-tree.monaco-tree-instance-${this.instance}.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { background-color: ${styles.listFocusAndSelectionBackground}; color: ${styles.listFocusAndSelectionForeground}; } + .monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: ${styles.listInactiveSelectionBackground}; } + .monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { background-color: ${styles.listHoverBackground}; } + .monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-wrapper.drop-target, + .monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row.drop-target { background-color: ${styles.listDropBackground} !important; } + `; + } + + // Indicate selection/focus via outline + else { + this.styleElement.innerHTML = ` + .monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row { background: none !important; border: 1px solid transparent; } + .monaco-tree.monaco-tree-instance-${this.instance}.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { border: 1px dotted ${styles.listFocusOutline}; } + .monaco-tree.monaco-tree-instance-${this.instance}.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid ${styles.listFocusOutline}; } + .monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid ${styles.listFocusOutline}; } + .monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row:hover:not(.highlighted):not(.selected):not(.focused) { border: 1px dashed ${styles.listFocusOutline}; } + .monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-wrapper.drop-target, + .monaco-tree.monaco-tree-instance-${this.instance} .monaco-tree-rows > .monaco-tree-row.drop-target { background: none !important; border: 1px dashed ${styles.listFocusOutline}; } + `; + } } protected createViewItem(item: Model.Item): IViewItem { diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index c180b82e898..4da89b47013 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -144,6 +144,15 @@ export const selectBackground = registerColor('dropdownBackground', { dark: '#3C export const selectForeground = registerColor('dropdownForeground', { dark: '#F0F0F0', light: null, hc: Color.white }, nls.localize('dropdownForeground', "Dropdown foreground.")); export const selectBorder = registerColor('dropdownBorder', { dark: selectBackground, light: '#CECECE', hc: selectBackground }, nls.localize('dropdownBorder', "Dropdown border.")); +export const listFocusBackground = registerColor('listFocusBackground', { dark: '#073655', light: '#DCEBFC', hc: null }, nls.localize('listFocusBackground', "List/Tree focus background when active")); +export const listActiveSelectionBackground = registerColor('listActiveSelectionBackground', { dark: '#0E639C', light: '#4FA7FF', hc: null }, nls.localize('listActiveSelectionBackground', "List/Tree selection background when active")); +export const listActiveSelectionForeground = registerColor('listActiveSelectionForeground', { dark: Color.white, light: Color.white, hc: null }, nls.localize('listActiveSelectionForeground', "List/Tree selection foreground when active")); +export const listFocusAndSelectionBackground = registerColor('listFocusAndSelectionBackground', { dark: '#094771', light: '#3399FF', hc: null }, nls.localize('listFocusAndSelectionBackground', "List/Tree focus and selection background")); +export const listFocusAndSelectionForeground = registerColor('listFocusAndSelectionForeground', { dark: Color.white, light: Color.white, hc: null }, nls.localize('listFocusAndSelectionForeground', "List/Tree focus and selection foreground")); +export const listInactiveSelectionBackground = registerColor('listInactiveSelectionBackground', { dark: '#3F3F46', light: '#CCCEDB', hc: null }, nls.localize('listInactiveSelectionBackground', "List/Tree selection background when inactive")); +export const listHoverBackground = registerColor('listHoverBackground', { dark: '#2A2D2E', light: '#F0F0F0', hc: null }, nls.localize('listHoverBackground', "List/Tree hover background")); +export const listDropBackground = registerColor('listDropBackground', { dark: '#383B3D', light: '#DDECFF', hc: null }, nls.localize('listDropBackground', "List/Tree drag and drop background")); + /** * Editor background color. * Because of bug https://monacotools.visualstudio.com/DefaultCollection/Monaco/_workitems/edit/13254