diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index 13951c0ec242060b2ba7b63aad77a77f0b9ae22f..636af54a1f02bacd2b49b70491f772ec62954bc4 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -459,6 +459,9 @@ const sizeUtils = { getBorderLeftWidth: function (element: HTMLElement): number { return getDimension(element, 'border-left-width', 'borderLeftWidth'); }, + getBorderRightWidth: function (element: HTMLElement): number { + return getDimension(element, 'border-right-width', 'borderRightWidth'); + }, getBorderTopWidth: function (element: HTMLElement): number { return getDimension(element, 'border-top-width', 'borderTopWidth'); }, @@ -466,6 +469,12 @@ const sizeUtils = { return getDimension(element, 'border-bottom-width', 'borderBottomWidth'); }, + getPaddingLeft: function (element: HTMLElement): number { + return getDimension(element, 'padding-left', 'paddingLeft'); + }, + getPaddingRight: function (element: HTMLElement): number { + return getDimension(element, 'padding-right', 'paddingRight'); + }, getPaddingTop: function (element: HTMLElement): number { return getDimension(element, 'padding-top', 'paddingTop'); }, @@ -571,6 +580,12 @@ export function getTotalWidth(element: HTMLElement): number { return element.offsetWidth + margin; } +export function getContentWidth(element: HTMLElement): number { + let border = sizeUtils.getBorderLeftWidth(element) + sizeUtils.getBorderRightWidth(element); + let padding = sizeUtils.getPaddingLeft(element) + sizeUtils.getPaddingRight(element); + return element.offsetWidth - border - padding; +} + export function getTotalScrollWidth(element: HTMLElement): number { let margin = sizeUtils.getMarginLeft(element) + sizeUtils.getMarginRight(element); return element.scrollWidth + margin; diff --git a/src/vs/base/parts/tree/browser/tree.ts b/src/vs/base/parts/tree/browser/tree.ts index 0d66edf166760b282a3bffa306ee0417740653f4..e9b6eb2fb50e22e7a47375d5fdf34d179b5c4eaf 100644 --- a/src/vs/base/parts/tree/browser/tree.ts +++ b/src/vs/base/parts/tree/browser/tree.ts @@ -668,11 +668,16 @@ export interface ITreeConfiguration { accessibilityProvider?: IAccessibilityProvider; } +export interface IContentWidthProvider { + getContentWidth(): number; +} + export interface ITreeOptions extends ITreeStyles { twistiePixels?: number; showTwistie?: boolean; indentPixels?: number; verticalScrollMode?: ScrollbarVisibility; + contentWidthProvider?: IContentWidthProvider; alwaysFocused?: boolean; autoExpandSingleChildren?: boolean; useShadows?: boolean; diff --git a/src/vs/base/parts/tree/browser/treeImpl.ts b/src/vs/base/parts/tree/browser/treeImpl.ts index f7c9173df5a2b5686378a052bbf591ccb55321fb..527ff39518cc5464ccd89590bdba75dd5872fb88 100644 --- a/src/vs/base/parts/tree/browser/treeImpl.ts +++ b/src/vs/base/parts/tree/browser/treeImpl.ts @@ -212,7 +212,7 @@ export class Tree implements _.ITree { } getContentHeight(): number { - return this.view.getTotalHeight(); + return this.view.getContentHeight(); } public setHighlight(element?: any, eventPayload?: any): void { diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts index e717487827d868b762b385d2fd38be36b83497da..bd750717af2ef810c444c4c5575c9abc345897e2 100644 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ b/src/vs/base/parts/tree/browser/treeView.ts @@ -25,6 +25,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import Event, { Emitter } from 'vs/base/common/event'; import { IDomNodePagePosition } from 'vs/base/browser/dom'; import { DataTransfers } from 'vs/base/browser/dnd'; +import { Delayer } from 'vs/base/common/async'; export interface IRow { element: HTMLElement; @@ -383,6 +384,8 @@ export class TreeView extends HeightMap { private msGesture: MSGesture; private lastPointerType: string; private lastClickTimeStamp: number = 0; + private contentWidthProvider?: _.IContentWidthProvider; + private contentWidthUpdateDelayer = new Delayer(50); private lastRenderTop: number; private lastRenderHeight: number; @@ -460,16 +463,19 @@ export class TreeView extends HeightMap { DOM.addClass(this.domNode, 'no-row-padding'); } + const horizontalScrollMode = typeof context.options.contentWidthProvider === 'undefined' ? ScrollbarVisibility.Hidden : ScrollbarVisibility.Auto; + this.contentWidthProvider = context.options.contentWidthProvider; + this.wrapper = document.createElement('div'); this.wrapper.className = 'monaco-tree-wrapper'; this.scrollableElement = new ScrollableElement(this.wrapper, { alwaysConsumeMouseWheel: true, - horizontal: ScrollbarVisibility.Hidden, + horizontal: horizontalScrollMode, vertical: (typeof context.options.verticalScrollMode !== 'undefined' ? context.options.verticalScrollMode : ScrollbarVisibility.Auto), useShadows: context.options.useShadows }); this.scrollableElement.onScroll((e) => { - this.render(e.scrollTop, e.height); + this.render(e.scrollTop, e.height, e.scrollLeft, e.width, e.scrollWidth); }); if (Browser.isIE) { @@ -670,9 +676,14 @@ export class TreeView extends HeightMap { } this.viewHeight = height || DOM.getContentHeight(this.wrapper); // render + this.scrollHeight = this.getContentHeight(); + + if (this.contentWidthProvider) { + this.viewWidth = DOM.getContentWidth(this.wrapper); + } } - private render(scrollTop: number, viewHeight: number): void { + private render(scrollTop: number, viewHeight: number, scrollLeft: number, viewWidth: number, scrollWidth: number): void { var i: number; var stop: number; @@ -706,6 +717,11 @@ export class TreeView extends HeightMap { this.rowsContainer.style.top = (topItem.top - renderTop) + 'px'; } + if (this.contentWidthProvider) { + this.rowsContainer.style.left = -scrollLeft + 'px'; + this.rowsContainer.style.width = `${Math.max(scrollWidth, viewWidth)}px`; + } + this.lastRenderTop = renderTop; this.lastRenderHeight = renderBottom - renderTop; } @@ -746,6 +762,12 @@ export class TreeView extends HeightMap { } this.scrollTop = scrollTop; + + if (this.contentWidthProvider) { + this.contentWidthUpdateDelayer.trigger(() => { + this.scrollWidth = this.contentWidthProvider.getContentWidth(); + }); + } } public focusNextPage(eventPayload?: any): void { @@ -803,11 +825,25 @@ export class TreeView extends HeightMap { return scrollDimensions.height; } - public set viewHeight(viewHeight: number) { - this.scrollableElement.setScrollDimensions({ - height: viewHeight, - scrollHeight: this.getTotalHeight() - }); + public set viewHeight(height: number) { + this.scrollableElement.setScrollDimensions({ height }); + } + + private set scrollHeight(scrollHeight: number) { + this.scrollableElement.setScrollDimensions({ scrollHeight }); + } + + public get viewWidth(): number { + const scrollDimensions = this.scrollableElement.getScrollDimensions(); + return scrollDimensions.width; + } + + public set viewWidth(viewWidth: number) { + this.scrollableElement.setScrollDimensions({ width: viewWidth }); + } + + private set scrollWidth(scrollWidth: number) { + this.scrollableElement.setScrollDimensions({ scrollWidth }); } public get scrollTop(): number { @@ -817,7 +853,7 @@ export class TreeView extends HeightMap { public set scrollTop(scrollTop: number) { this.scrollableElement.setScrollDimensions({ - scrollHeight: this.getTotalHeight() + scrollHeight: this.getContentHeight() }); this.scrollableElement.setScrollPosition({ scrollTop: scrollTop @@ -825,12 +861,12 @@ export class TreeView extends HeightMap { } public getScrollPosition(): number { - const height = this.getTotalHeight() - this.viewHeight; + const height = this.getContentHeight() - this.viewHeight; return height <= 0 ? 1 : this.scrollTop / height; } public setScrollPosition(pos: number): void { - const height = this.getTotalHeight() - this.viewHeight; + const height = this.getContentHeight() - this.viewHeight; this.scrollTop = height * pos; } diff --git a/src/vs/base/parts/tree/browser/treeViewModel.ts b/src/vs/base/parts/tree/browser/treeViewModel.ts index 0dcb27d000bdfb5305767c5af919d39b5a4cf950..016d3ce209318b2ebd13f01da961efe32ca29d63 100644 --- a/src/vs/base/parts/tree/browser/treeViewModel.ts +++ b/src/vs/base/parts/tree/browser/treeViewModel.ts @@ -22,7 +22,7 @@ export class HeightMap { this.indexes = {}; } - public getTotalHeight(): number { + public getContentHeight(): number { var last = this.heightMap[this.heightMap.length - 1]; return !last ? 0 : last.top + last.height; } diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts index acac6760d6a7fe49f23e4f324973453e6d755fb1..bd2ceea814c3403efe9f0e85b09821549ac9d39c 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts @@ -416,7 +416,8 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView accessibilityProvider }, { autoExpandSingleChildren: true, - ariaLabel: nls.localize('treeAriaLabel', "Files Explorer") + ariaLabel: nls.localize('treeAriaLabel', "Files Explorer"), + contentWidthProvider: { getContentWidth: () => this.getOptimalWidth() } }); // Bind context keys @@ -459,10 +460,32 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView } public getOptimalWidth(): number { + if (!this.explorerViewer) { + return 0; + } + + let result = 0; const parentNode = this.explorerViewer.getHTMLElement(); - const childNodes = [].slice.call(parentNode.querySelectorAll('.explorer-item .label-name')); // select all file labels + const rows = [].slice.call(parentNode.querySelectorAll('.monaco-tree-row')) as HTMLElement[]; + + for (const row of rows) { + const rowStyle = window.getComputedStyle(row); + const content = row.querySelector('.content') as HTMLElement; + const twistieStyle = window.getComputedStyle(content, ':before'); + const iconLabel = content.querySelector('.monaco-icon-label') as HTMLElement; + const iconStyle = window.getComputedStyle(iconLabel, ':before'); // width + padding + const decorationsStyle = window.getComputedStyle(iconLabel, ':after'); // width + padding + const label = iconLabel.querySelector('.monaco-icon-label-description-container > .label-name') as HTMLElement; + + const twistieWidth = (twistieStyle.display !== 'none' && twistieStyle.content) ? parseFloat(twistieStyle.width) + parseFloat(twistieStyle.paddingLeft) + parseFloat(twistieStyle.paddingRight) : 0; + const iconWidth = iconStyle.content ? parseFloat(iconStyle.width) + parseFloat(iconStyle.paddingLeft) + parseFloat(iconStyle.paddingRight) : 0; + const decorationsWidth = decorationsStyle.content ? parseFloat(decorationsStyle.width) + parseFloat(decorationsStyle.paddingLeft) + parseFloat(decorationsStyle.paddingRight) : 0; + const width = parseFloat(rowStyle.paddingLeft) + twistieWidth + iconWidth + decorationsWidth + label.offsetWidth; + + result = Math.max(result, width); + } - return DOM.getLargestChildWidth(parentNode, childNodes); + return result + 12; // 12 for right padding } private onFileOperation(e: FileOperationEvent): void {