提交 8548bcbe 编写于 作者: J Joao Moreno

Merge branch 'tree-horizontal-scroll'

fixes #15539
......@@ -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;
......
......@@ -78,6 +78,11 @@ export interface ITree {
*/
refresh(element?: any, recursive?: boolean): WinJS.Promise;
/**
* Updates an element's width.
*/
updateWidth(element: any): void;
/**
* Expands an element.
* The returned promise returns a boolean for whether the element was expanded or not.
......@@ -674,6 +679,7 @@ export interface ITreeOptions extends ITreeStyles {
showTwistie?: boolean;
indentPixels?: number;
verticalScrollMode?: ScrollbarVisibility;
horizontalScrollMode?: ScrollbarVisibility;
alwaysFocused?: boolean;
autoExpandSingleChildren?: boolean;
useShadows?: boolean;
......
......@@ -160,6 +160,11 @@ export class Tree implements _.ITree {
return this.model.refresh(element, recursive);
}
public updateWidth(element: any): void {
let item = this.model.getItem(element);
return this.view.updateWidth(item);
}
public expand(element: any): WinJS.Promise {
return this.model.expand(element);
}
......@@ -214,7 +219,7 @@ export class Tree implements _.ITree {
}
getContentHeight(): number {
return this.view.getTotalHeight();
return this.view.getContentHeight();
}
public setHighlight(element?: any, eventPayload?: any): void {
......
......@@ -26,6 +26,7 @@ import Event, { Emitter } from 'vs/base/common/event';
import { IDomNodePagePosition } from 'vs/base/browser/dom';
import { DataTransfers } from 'vs/base/browser/dnd';
import { DefaultTreestyler } from './treeDefaults';
import { Delayer } from 'vs/base/common/async';
export interface IRow {
element: HTMLElement;
......@@ -101,6 +102,7 @@ export class RowCache implements Lifecycle.IDisposable {
export interface IViewContext extends _.ITreeContext {
cache: RowCache;
horizontalScrolling: boolean;
}
export class ViewItem implements IViewItem {
......@@ -113,6 +115,7 @@ export class ViewItem implements IViewItem {
public top: number;
public height: number;
public width: number = 0;
public onDragStart: (e: DragEvent) => void;
public needsRender: boolean;
......@@ -251,8 +254,32 @@ export class ViewItem implements IViewItem {
}
if (!skipUserRender) {
const style = window.getComputedStyle(this.element);
const paddingLeft = parseFloat(style.paddingLeft);
if (this.context.horizontalScrolling) {
this.element.style.width = 'fit-content';
}
this.context.renderer.renderElement(this.context.tree, this.model.getElement(), this.templateId, this.row.templateData);
if (this.context.horizontalScrolling) {
this.width = DOM.getContentWidth(this.element) + paddingLeft;
this.element.style.width = '';
}
}
}
updateWidth(): any {
if (!this.context.horizontalScrolling) {
return;
}
const style = window.getComputedStyle(this.element);
const paddingLeft = parseFloat(style.paddingLeft);
this.element.style.width = 'fit-content';
this.width = DOM.getContentWidth(this.element) + paddingLeft;
this.element.style.width = '';
}
public insertInDOM(container: HTMLElement, afterElement: HTMLElement): void {
......@@ -386,6 +413,9 @@ export class TreeView extends HeightMap {
private lastPointerType: string;
private lastClickTimeStamp: number = 0;
private horizontalScrolling: boolean = true;
private contentWidthUpdateDelayer = new Delayer<void>(50);
private lastRenderTop: number;
private lastRenderHeight: number;
......@@ -422,6 +452,9 @@ export class TreeView extends HeightMap {
TreeView.counter++;
this.instance = TreeView.counter;
const horizontalScrollMode = typeof context.options.horizontalScrollMode === 'undefined' ? ScrollbarVisibility.Hidden : context.options.horizontalScrollMode;
const horizontalScrolling = horizontalScrollMode !== ScrollbarVisibility.Hidden;
this.context = {
dataSource: context.dataSource,
renderer: context.renderer,
......@@ -432,7 +465,8 @@ export class TreeView extends HeightMap {
tree: context.tree,
accessibilityProvider: context.accessibilityProvider,
options: context.options,
cache: new RowCache(context)
cache: new RowCache(context),
horizontalScrolling
};
this.modelListeners = [];
......@@ -471,12 +505,12 @@ export class TreeView extends HeightMap {
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) {
......@@ -609,9 +643,14 @@ export class TreeView extends HeightMap {
}
this.viewHeight = height || DOM.getContentHeight(this.wrapper); // render
this.scrollHeight = this.getContentHeight();
if (this.horizontalScrolling) {
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;
......@@ -645,6 +684,11 @@ export class TreeView extends HeightMap {
this.rowsContainer.style.top = (topItem.top - renderTop) + 'px';
}
if (this.horizontalScrolling) {
this.rowsContainer.style.left = -scrollLeft + 'px';
this.rowsContainer.style.width = `${Math.max(scrollWidth, viewWidth)}px`;
}
this.lastRenderTop = renderTop;
this.lastRenderHeight = renderBottom - renderTop;
}
......@@ -685,6 +729,24 @@ export class TreeView extends HeightMap {
}
this.scrollTop = scrollTop;
this.updateScrollWidth();
}
private updateScrollWidth(): void {
if (!this.horizontalScrolling) {
return;
}
this.contentWidthUpdateDelayer.trigger(() => {
const keys = Object.keys(this.items);
let scrollWidth = 0;
for (const key of keys) {
scrollWidth = Math.max(scrollWidth, this.items[key].width);
}
this.scrollWidth = scrollWidth + 10 /* scrollbar */;
});
}
public focusNextPage(eventPayload?: any): void {
......@@ -742,11 +804,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 {
......@@ -756,7 +832,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
......@@ -764,12 +840,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;
}
......@@ -938,6 +1014,21 @@ export class TreeView extends HeightMap {
}
}
public updateWidth(item: Model.Item): void {
if (!item || !item.isVisible()) {
return;
}
const viewItem = this.items[item.id];
if (!viewItem) {
return;
}
viewItem.updateWidth();
this.updateScrollWidth();
}
public getRelativeTop(item: Model.Item): number {
if (item && item.isVisible()) {
var viewItem = this.items[item.id];
......
......@@ -10,6 +10,7 @@ export interface IViewItem {
model: Item;
top: number;
height: number;
width: number;
}
export class HeightMap {
......@@ -22,7 +23,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;
}
......
......@@ -45,7 +45,8 @@ class TestHeightMap extends HeightMap {
return {
model: item,
top: 0,
height: item.getHeight()
height: item.getHeight(),
width: 0
};
}
}
......
......@@ -25,6 +25,7 @@ import { isUndefinedOrNull } from 'vs/base/common/types';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import Event, { Emitter } from 'vs/base/common/event';
import { createStyleSheet } from 'vs/base/browser/dom';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
export type ListWidget = List<any> | PagedList<any> | ITree;
......@@ -106,6 +107,7 @@ function createScopedContextKeyService(contextKeyService: IContextKeyService, wi
export const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier';
export const openModeSettingKey = 'workbench.list.openMode';
export const horizontalScrollingKey = 'workbench.tree.horizontalScrolling';
function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean {
return configurationService.getValue(multiSelectModifierSettingKey) === 'alt';
......@@ -330,7 +332,7 @@ export class WorkbenchTree extends Tree {
readonly contextKeyService: IContextKeyService;
protected disposables: IDisposable[] = [];
protected disposables: IDisposable[];
private listDoubleSelection: IContextKey<boolean>;
private listMultiSelection: IContextKey<boolean>;
......@@ -346,19 +348,20 @@ export class WorkbenchTree extends Tree {
@IListService listService: IListService,
@IThemeService themeService: IThemeService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService private configurationService: IConfigurationService
@IConfigurationService configurationService: IConfigurationService
) {
super(
container,
handleTreeController(configuration, instantiationService),
// mixin magic:
// - define some custom tree options common for all workbench trees
// - mixin theme colors from default tree styles right on creation
mixin(options, mixin({
keyboardSupport: false
} as ITreeOptions, computeStyles(themeService.getTheme(), defaultListStyles), false), false)
);
const config = handleTreeController(configuration, instantiationService);
const horizontalScrollMode = configurationService.getValue(horizontalScrollingKey) ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden;
const opts = {
horizontalScrollMode,
keyboardSupport: false,
...computeStyles(themeService.getTheme(), defaultListStyles),
...options
};
super(container, config, opts);
this.disposables = [];
this.contextKeyService = createScopedContextKeyService(contextKeyService, this);
this.listDoubleSelection = WorkbenchListDoubleSelection.bindTo(this.contextKeyService);
this.listMultiSelection = WorkbenchListMultiSelection.bindTo(this.contextKeyService);
......@@ -372,23 +375,19 @@ export class WorkbenchTree extends Tree {
attachListStyler(this, themeService)
);
this.registerListeners();
}
private registerListeners(): void {
this.disposables.push(this.onDidChangeSelection(() => {
const selection = this.getSelection();
this.listDoubleSelection.set(selection && selection.length === 2);
this.listMultiSelection.set(selection && selection.length > 1);
}));
this.disposables.push(this.configurationService.onDidChangeConfiguration(e => {
this.disposables.push(configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(openModeSettingKey)) {
this._openOnSingleClick = useSingleClickToOpen(this.configurationService);
this._openOnSingleClick = useSingleClickToOpen(configurationService);
}
if (e.affectsConfiguration(multiSelectModifierSettingKey)) {
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService);
this._useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService);
}
}));
}
......@@ -573,6 +572,11 @@ configurationRegistry.registerConfiguration({
key: 'openModeModifier',
comment: ['`singleClick` and `doubleClick` refers to a value the setting can take and should not be localized.']
}, "Controls how to open items in trees and lists using the mouse (if supported). Set to `singleClick` to open items with a single mouse click and `doubleClick` to only open via mouse double click. For parents with children in trees, this setting will control if a single click expands the parent or a double click. Note that some trees and lists might choose to ignore this setting if it is not applicable. ")
},
[horizontalScrollingKey]: {
'type': 'boolean',
'default': false,
'description': localize('horizontalScrolling setting', "Controls whether trees support horizontal scrolling in the workbench.")
}
}
});
......@@ -25,6 +25,7 @@ import { Schemas } from 'vs/base/common/network';
import { FileKind, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files';
import { ITextModel } from 'vs/editor/common/model';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import Event, { Emitter } from 'vs/base/common/event';
export interface IResourceLabel {
name: string;
......@@ -38,12 +39,16 @@ export interface IResourceLabelOptions extends IIconLabelValueOptions {
}
export class ResourceLabel extends IconLabel {
private toDispose: IDisposable[];
private label: IResourceLabel;
private options: IResourceLabelOptions;
private computedIconClasses: string[];
private lastKnownConfiguredLangId: string;
private _onDidRender = new Emitter<void>();
readonly onDidRender: Event<void> = this._onDidRender.event;
constructor(
container: HTMLElement,
options: IIconLabelCreationOptions,
......@@ -217,6 +222,7 @@ export class ResourceLabel extends IconLabel {
}
this.setValue(label, this.label.description, iconLabelOptions);
this._onDidRender.fire();
}
public dispose(): void {
......
......@@ -6,7 +6,6 @@
import { TPromise } from 'vs/base/common/winjs.base';
import nls = require('vs/nls');
import lifecycle = require('vs/base/common/lifecycle');
import objects = require('vs/base/common/objects');
import DOM = require('vs/base/browser/dom');
import URI from 'vs/base/common/uri';
......@@ -20,7 +19,7 @@ import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { isMacintosh, isLinux } from 'vs/base/common/platform';
import glob = require('vs/base/common/glob');
import { FileLabel, IFileLabelOptions } from 'vs/workbench/browser/labels';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDisposable, dispose, empty as EmptyDisposable } from 'vs/base/common/lifecycle';
import { IFilesConfiguration, SortOrder } from 'vs/workbench/parts/files/common/files';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { FileOperationError, FileOperationResult, IFileService, FileKind } from 'vs/platform/files/common/files';
......@@ -179,6 +178,7 @@ export class ActionRunner extends BaseActionRunner implements IActionRunner {
}
export interface IFileTemplateData {
elementDisposable: IDisposable;
label: FileLabel;
container: HTMLElement;
}
......@@ -228,12 +228,15 @@ export class FileRenderer implements IRenderer {
}
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): IFileTemplateData {
const elementDisposable = EmptyDisposable;
const label = this.instantiationService.createInstance(FileLabel, container, void 0);
return { label, container };
return { elementDisposable, label, container };
}
public renderElement(tree: ITree, stat: FileStat, templateId: string, templateData: IFileTemplateData): void {
templateData.elementDisposable.dispose();
const editableData: IEditableData = this.state.getEditableData(stat);
// File Label
......@@ -246,12 +249,17 @@ export class FileRenderer implements IRenderer {
extraClasses,
fileDecorations: this.config.explorer.decorations
});
templateData.elementDisposable = templateData.label.onDidRender(() => {
tree.updateWidth(stat);
});
}
// Input Box
else {
templateData.label.element.style.display = 'none';
this.renderInputBox(templateData.container, tree, stat, editableData);
templateData.elementDisposable = EmptyDisposable;
}
}
......@@ -296,7 +304,7 @@ export class FileRenderer implements IRenderer {
if (!blur) { // https://github.com/Microsoft/vscode/issues/20269
tree.domFocus();
}
lifecycle.dispose(toDispose);
dispose(toDispose);
container.removeChild(label.element);
}, 0);
});
......
......@@ -25,6 +25,7 @@ interface IConfiguration extends IWindowsConfiguration {
update: { channel: string; };
telemetry: { enableCrashReporter: boolean };
keyboard: { touchbar: { enabled: boolean } };
workbench: { tree: { horizontalScrolling: boolean } };
}
export class SettingsChangeRelauncher implements IWorkbenchContribution {
......@@ -36,6 +37,7 @@ export class SettingsChangeRelauncher implements IWorkbenchContribution {
private updateChannel: string;
private enableCrashReporter: boolean;
private touchbarEnabled: boolean;
private treeHorizontalScrolling: boolean;
private firstFolderResource: URI;
private extensionHostRestarter: RunOnceScheduler;
......@@ -99,6 +101,12 @@ export class SettingsChangeRelauncher implements IWorkbenchContribution {
changed = true;
}
// Tree horizontal scrolling support
if (config.workbench && config.workbench.tree && typeof config.workbench.tree.horizontalScrolling === 'boolean' && config.workbench.tree.horizontalScrolling !== this.treeHorizontalScrolling) {
this.treeHorizontalScrolling = config.workbench.tree.horizontalScrolling;
changed = true;
}
// Notify only when changed and we are the focused window (avoids notification spam across windows)
if (notify && changed) {
this.doConfirm(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册