提交 82d00f53 编写于 作者: J Joao Moreno

Merge commit 'refs/pull/62052/head' of github.com:Microsoft/vscode into pr/62052

......@@ -8,6 +8,7 @@ import { GestureEvent } from 'vs/base/browser/touch';
export interface IListVirtualDelegate<T> {
getHeight(element: T): number;
getTemplateId(element: T): string;
hasDynamicHeight?(element: T): boolean;
}
export interface IListRenderer<T, TTemplateData> {
......
......@@ -33,29 +33,34 @@ function canUseTranslate3d(): boolean {
return true;
}
interface IItem<T> {
id: string;
element: T;
size: number;
templateId: string;
readonly id: string;
readonly element: T;
readonly templateId: string;
row: IRow | null;
size: number;
hasDynamicHeight: boolean;
dynamicSizeSnapshotId: number;
}
export interface IListViewOptions {
useShadows?: boolean;
verticalScrollMode?: ScrollbarVisibility;
setRowLineHeight?: boolean;
readonly useShadows?: boolean;
readonly verticalScrollMode?: ScrollbarVisibility;
readonly setRowLineHeight?: boolean;
readonly supportDynamicHeights?: boolean;
}
const DefaultOptions = {
useShadows: true,
verticalScrollMode: ScrollbarVisibility.Auto,
setRowLineHeight: true
setRowLineHeight: true,
supportDynamicHeights: false
};
export class ListView<T> implements ISpliceable<T>, IDisposable {
readonly domNode: HTMLElement;
private items: IItem<T>[];
private itemId: number;
private rangeMap: RangeMap;
......@@ -63,7 +68,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private renderers = new Map<string, IListRenderer<T, any>>();
private lastRenderTop: number;
private lastRenderHeight: number;
private _domNode: HTMLElement;
private dynamicSizeSnapshotId = 0;
private gesture: Gesture;
private rowsContainer: HTMLElement;
private scrollableElement: ScrollableElement;
......@@ -74,6 +79,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private dragAndDropScrollTimeout: number;
private dragAndDropMouseY: number;
private setRowLineHeight: boolean;
private supportDynamicHeights: boolean;
private disposables: IDisposable[];
constructor(
......@@ -95,8 +101,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.lastRenderTop = 0;
this.lastRenderHeight = 0;
this._domNode = document.createElement('div');
this._domNode.className = 'monaco-list';
this.domNode = document.createElement('div');
this.domNode.className = 'monaco-list';
this.rowsContainer = document.createElement('div');
this.rowsContainer.className = 'monaco-list-rows';
......@@ -109,8 +115,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
useShadows: getOrDefault2(options, o => o.useShadows, DefaultOptions.useShadows)
});
this._domNode.appendChild(this.scrollableElement.getDomNode());
container.appendChild(this._domNode);
this.domNode.appendChild(this.scrollableElement.getDomNode());
container.appendChild(this.domNode);
this.disposables = [this.rangeMap, this.gesture, this.scrollableElement, this.cache];
......@@ -126,14 +132,11 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
onDragOver(this.onDragOver, this, this.disposables);
this.setRowLineHeight = getOrDefault2(options, o => o.setRowLineHeight, DefaultOptions.setRowLineHeight);
this.supportDynamicHeights = getOrDefault2(options, o => o.supportDynamicHeights, DefaultOptions.supportDynamicHeights);
this.layout();
}
get domNode(): HTMLElement {
return this._domNode;
}
splice(start: number, deleteCount: number, elements: T[] = []): T[] {
if (this.splicing) {
throw new Error('Can\'t run recursive splices.');
......@@ -164,8 +167,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const inserted = elements.map<IItem<T>>(element => ({
id: String(this.itemId++),
element,
size: this.virtualDelegate.getHeight(element),
templateId: this.virtualDelegate.getTemplateId(element),
size: this.virtualDelegate.getHeight(element),
hasDynamicHeight: !!this.virtualDelegate.hasDynamicHeight && this.virtualDelegate.hasDynamicHeight(element),
dynamicSizeSnapshotId: this.dynamicSizeSnapshotId - 1,
row: null
}));
......@@ -193,10 +198,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const removeRanges = Range.relativeComplement(renderedRestRange, renderRange);
for (let r = 0; r < removeRanges.length; r++) {
const removeRange = removeRanges[r];
for (let i = removeRange.start; i < removeRange.end; i++) {
for (const range of removeRanges) {
for (let i = range.start; i < range.end; i++) {
this.removeItemFromDOM(i);
}
}
......@@ -206,14 +209,22 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
const insertRanges = [elementsRange, ...unrenderedRestRanges].map(r => Range.intersect(renderRange, r));
const beforeElement = this.getNextToLastElement(insertRanges);
for (let r = 0; r < insertRanges.length; r++) {
const insertRange = insertRanges[r];
for (let i = insertRange.start; i < insertRange.end; i++) {
for (const range of insertRanges) {
for (let i = range.start; i < range.end; i++) {
this.insertItemInDOM(i, beforeElement);
}
}
this.updateScrollHeight();
if (this.supportDynamicHeights) {
this.rerender(this.scrollTop, this.renderHeight);
}
return deleted.map(i => i.element);
}
private updateScrollHeight(): void {
this.scrollHeight = this.getContentHeight();
this.rowsContainer.style.height = `${this.scrollHeight}px`;
......@@ -225,8 +236,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.didRequestScrollableElementUpdate = true;
}
return deleted.map(i => i.element);
}
get length(): number {
......@@ -265,7 +274,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
layout(height?: number): void {
this.scrollableElement.setScrollDimensions({
height: height || DOM.getContentHeight(this._domNode)
height: height || DOM.getContentHeight(this.domNode)
});
}
......@@ -320,12 +329,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
}
item.row.domNode!.style.height = `${item.size}px`;
if (this.setRowLineHeight) {
item.row.domNode!.style.lineHeight = `${item.size}px`;
}
this.updateItemInDOM(item, index);
const renderer = this.renderers.get(item.templateId);
......@@ -333,11 +336,17 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
private updateItemInDOM(item: IItem<T>, index: number): void {
item.row!.domNode!.style.top = `${this.elementTop(index)}px`;
item.row!.domNode!.setAttribute('data-index', `${index}`);
item.row!.domNode!.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false');
item.row!.domNode!.setAttribute('aria-setsize', `${this.length}`);
item.row!.domNode!.setAttribute('aria-posinset', `${index + 1}`);
item.row.domNode!.style.top = `${this.elementTop(index)}px`;
item.row.domNode!.style.height = `${item.size}px`;
if (this.setRowLineHeight) {
item.row.domNode!.style.lineHeight = `${item.size}px`;
}
item.row.domNode!.setAttribute('data-index', `${index}`);
item.row.domNode!.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false');
item.row.domNode!.setAttribute('aria-setsize', `${this.length}`);
item.row.domNode!.setAttribute('aria-posinset', `${index + 1}`);
}
private removeItemFromDOM(index: number): void {
......@@ -411,6 +420,10 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
private onScroll(e: ScrollEvent): void {
try {
this.render(e.scrollTop, e.height);
if (this.supportDynamicHeights) {
this.rerender(e.scrollTop, e.height);
}
} catch (err) {
console.log('Got bad scroll event:', e);
throw err;
......@@ -430,7 +443,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
}
private setupDragAndDropScrollInterval(): void {
var viewTop = DOM.getTopLeftOffset(this._domNode).top;
const viewTop = DOM.getTopLeftOffset(this.domNode).top;
if (!this.dragAndDropScrollInterval) {
this.dragAndDropScrollInterval = window.setInterval(() => {
......@@ -505,6 +518,94 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
};
}
/**
* Given a stable rendered state, checks every rendered element whether it needs
* to be probed for dynamic height. Adjusts scroll height and top if necessary.
*/
private rerender(renderTop: number, renderHeight: number): void {
const previousRenderRange = this.getRenderRange(renderTop, renderHeight);
// Let's remember the second element's position, this helps in scrolling up
// and preserving a linear upwards scroll movement
let secondElementIndex: number | undefined;
let secondElementTopDelta: number | undefined;
if (previousRenderRange.end - previousRenderRange.start > 1) {
secondElementIndex = previousRenderRange.start + 1;
secondElementTopDelta = this.elementTop(secondElementIndex) - renderTop;
}
let heightDiff = 0;
while (true) {
const renderRange = this.getRenderRange(renderTop, renderHeight);
let didChange = false;
for (let i = renderRange.start; i < renderRange.end; i++) {
const diff = this.probeDynamicHeight(i);
if (diff !== 0) {
this.rangeMap.splice(i, 1, [this.items[i]]);
}
heightDiff += diff;
didChange = didChange || diff !== 0;
}
if (!didChange) {
if (heightDiff !== 0) {
this.updateScrollHeight();
}
const unrenderRanges = Range.relativeComplement(previousRenderRange, renderRange);
for (const range of unrenderRanges) {
for (let i = range.start; i < range.end; i++) {
if (this.items[i].row) {
this.removeItemFromDOM(i);
}
}
}
for (let i = renderRange.start; i < renderRange.end; i++) {
if (this.items[i].row) {
this.updateItemInDOM(this.items[i], i);
}
}
if (typeof secondElementIndex === 'number') {
this.scrollTop = this.elementTop(secondElementIndex) - secondElementTopDelta;
}
return;
}
}
}
private probeDynamicHeight(index: number): number {
const item = this.items[index];
if (!item.hasDynamicHeight || item.dynamicSizeSnapshotId === this.dynamicSizeSnapshotId) {
return 0;
}
const size = item.size;
const renderer = this.renderers.get(item.templateId);
const row = this.cache.alloc(item.templateId);
row.domNode.style.height = '';
this.rowsContainer.appendChild(row.domNode);
renderer.renderElement(item.element, index, row.templateData);
item.size = row.domNode.offsetHeight;
item.dynamicSizeSnapshotId = this.dynamicSizeSnapshotId;
this.rowsContainer.removeChild(row.domNode);
this.cache.release(row);
return item.size - size;
}
private getNextToLastElement(ranges: IRange[]): HTMLElement | null {
const lastRange = ranges[ranges.length - 1];
......@@ -539,8 +640,8 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
this.items = [];
}
if (this._domNode && this._domNode.parentNode) {
this._domNode.parentNode.removeChild(this._domNode);
if (this.domNode && this.domNode.parentNode) {
this.domNode.parentNode.removeChild(this.domNode);
}
this.disposables = dispose(this.disposables);
......
......@@ -70,6 +70,10 @@ export class ComposedTreeDelegate<T, N extends { element: T }> implements IListV
getTemplateId(element: N): string {
return this.delegate.getTemplateId(element.element);
}
hasDynamicHeight(element: N): boolean {
return !!this.delegate.hasDynamicHeight && this.delegate.hasDynamicHeight(element.element);
}
}
interface ITreeListTemplateData<T> {
......
......@@ -45,13 +45,16 @@
function createIndexTree() {
const delegate = {
getHeight() { return 22; },
getTemplateId() { return 'template'; }
getTemplateId() { return 'template'; },
hasDynamicHeight() { return true; }
};
const renderer = {
templateId: 'template',
renderTemplate(container) { return container; },
renderElement(element, index, container) { container.textContent = element.element; },
renderElement(element, index, container) {
container.innerHTML = `${element.element}<br />${element.element}<br />${element.element}`;
},
disposeElement() { },
disposeTemplate() { }
};
......@@ -79,7 +82,7 @@
}
};
const tree = new IndexTree(container, delegate, [renderer], { filter: treeFilter });
const tree = new IndexTree(container, delegate, [renderer], { filter: treeFilter, setRowLineHeight: false, supportDynamicHeights: true });
return { tree, treeFilter };
}
......
......@@ -17,7 +17,7 @@ async function getTree(fsPath, level) {
const element = path.basename(fsPath);
const stat = await fs.stat(fsPath);
if (!stat.isDirectory() || element === '.git' || element === '.build' || level >= 5) {
if (!stat.isDirectory() || element === '.git' || element === '.build' || level >= 2) {
return { element };
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册