mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 09:08:48 +01:00
Merge pull request #178627 from microsoft/rebornix/awake-lion
List View top padding
This commit is contained in:
@@ -108,7 +108,7 @@ export interface IPagedListOptions<T> {
|
||||
readonly mouseSupport?: boolean;
|
||||
readonly horizontalScrolling?: boolean;
|
||||
readonly scrollByPage?: boolean;
|
||||
readonly additionalScrollHeight?: number;
|
||||
readonly paddingBottom?: number;
|
||||
}
|
||||
|
||||
function fromPagedListOptions<T>(modelProvider: () => IPagedModel<T>, options: IPagedListOptions<T>): IListOptions<number> {
|
||||
|
||||
@@ -56,12 +56,13 @@ export interface IListViewAccessibilityProvider<T> {
|
||||
}
|
||||
|
||||
export interface IListViewOptionsUpdate {
|
||||
readonly additionalScrollHeight?: number;
|
||||
readonly smoothScrolling?: boolean;
|
||||
readonly horizontalScrolling?: boolean;
|
||||
readonly scrollByPage?: boolean;
|
||||
readonly mouseWheelScrollSensitivity?: number;
|
||||
readonly fastScrollSensitivity?: number;
|
||||
readonly paddingTop?: number;
|
||||
readonly paddingBottom?: number;
|
||||
}
|
||||
|
||||
export interface IListViewOptions<T> extends IListViewOptionsUpdate {
|
||||
@@ -298,7 +299,7 @@ export class ListView<T> implements IListView<T> {
|
||||
private setRowLineHeight: boolean;
|
||||
private setRowHeight: boolean;
|
||||
private supportDynamicHeights: boolean;
|
||||
private additionalScrollHeight: number;
|
||||
private paddingBottom: number;
|
||||
private accessibilityProvider: ListViewAccessibilityProvider<T>;
|
||||
private scrollWidth: number | undefined;
|
||||
|
||||
@@ -364,7 +365,7 @@ export class ListView<T> implements IListView<T> {
|
||||
|
||||
this.items = [];
|
||||
this.itemId = 0;
|
||||
this.rangeMap = new RangeMap();
|
||||
this.rangeMap = new RangeMap(options.paddingTop ?? 0);
|
||||
|
||||
for (const renderer of renderers) {
|
||||
this.renderers.set(renderer.templateId, renderer);
|
||||
@@ -386,7 +387,7 @@ export class ListView<T> implements IListView<T> {
|
||||
this._horizontalScrolling = options.horizontalScrolling ?? DefaultOptions.horizontalScrolling;
|
||||
this.domNode.classList.toggle('horizontal-scrolling', this._horizontalScrolling);
|
||||
|
||||
this.additionalScrollHeight = typeof options.additionalScrollHeight === 'undefined' ? 0 : options.additionalScrollHeight;
|
||||
this.paddingBottom = typeof options.paddingBottom === 'undefined' ? 0 : options.paddingBottom;
|
||||
|
||||
this.accessibilityProvider = new ListViewAccessibilityProvider(options.accessibilityProvider);
|
||||
|
||||
@@ -441,8 +442,8 @@ export class ListView<T> implements IListView<T> {
|
||||
}
|
||||
|
||||
updateOptions(options: IListViewOptionsUpdate) {
|
||||
if (options.additionalScrollHeight !== undefined) {
|
||||
this.additionalScrollHeight = options.additionalScrollHeight;
|
||||
if (options.paddingBottom !== undefined) {
|
||||
this.paddingBottom = options.paddingBottom;
|
||||
this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight });
|
||||
}
|
||||
|
||||
@@ -471,6 +472,22 @@ export class ListView<T> implements IListView<T> {
|
||||
if (scrollableOptions) {
|
||||
this.scrollableElement.updateOptions(scrollableOptions);
|
||||
}
|
||||
|
||||
if (options.paddingTop !== undefined && options.paddingTop !== this.rangeMap.paddingTop) {
|
||||
// trigger a rerender
|
||||
const lastRenderRange = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
|
||||
const offset = options.paddingTop - this.rangeMap.paddingTop;
|
||||
this.rangeMap.paddingTop = options.paddingTop;
|
||||
|
||||
this.render(lastRenderRange, Math.max(0, this.lastRenderTop + offset), this.lastRenderHeight, undefined, undefined, true);
|
||||
this.setScrollTop(this.lastRenderTop);
|
||||
|
||||
this.eventuallyUpdateScrollDimensions();
|
||||
|
||||
if (this.supportDynamicHeights) {
|
||||
this._rerender(this.lastRenderTop, this.lastRenderHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegateScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
|
||||
@@ -602,7 +619,7 @@ export class ListView<T> implements IListView<T> {
|
||||
|
||||
// TODO@joao: improve this optimization to catch even more cases
|
||||
if (start === 0 && deleteCount >= this.items.length) {
|
||||
this.rangeMap = new RangeMap();
|
||||
this.rangeMap = new RangeMap(this.rangeMap.paddingTop);
|
||||
this.rangeMap.splice(0, 0, inserted);
|
||||
deleted = this.items;
|
||||
this.items = inserted;
|
||||
@@ -1017,7 +1034,7 @@ export class ListView<T> implements IListView<T> {
|
||||
}
|
||||
|
||||
get scrollHeight(): number {
|
||||
return this._scrollHeight + (this.horizontalScrolling ? 10 : 0) + this.additionalScrollHeight;
|
||||
return this._scrollHeight + (this.horizontalScrolling ? 10 : 0) + this.paddingBottom;
|
||||
}
|
||||
|
||||
// Events
|
||||
|
||||
@@ -991,12 +991,13 @@ export interface IListOptions<T> extends IListOptionsUpdate {
|
||||
readonly mouseSupport?: boolean;
|
||||
readonly horizontalScrolling?: boolean;
|
||||
readonly scrollByPage?: boolean;
|
||||
readonly additionalScrollHeight?: number;
|
||||
readonly transformOptimization?: boolean;
|
||||
readonly smoothScrolling?: boolean;
|
||||
readonly scrollableElementChangeOptions?: ScrollableElementChangeOptions;
|
||||
readonly alwaysConsumeMouseWheel?: boolean;
|
||||
readonly initialSize?: Dimension;
|
||||
readonly paddingTop?: number;
|
||||
readonly paddingBottom?: number;
|
||||
}
|
||||
|
||||
export interface IListStyles {
|
||||
|
||||
@@ -91,6 +91,21 @@ export class RangeMap {
|
||||
|
||||
private groups: IRangedGroup[] = [];
|
||||
private _size = 0;
|
||||
private _paddingTop = 0;
|
||||
|
||||
get paddingTop() {
|
||||
return this._paddingTop;
|
||||
}
|
||||
|
||||
set paddingTop(paddingTop: number) {
|
||||
this._size = this._size + paddingTop - this._paddingTop;
|
||||
this._paddingTop = paddingTop;
|
||||
}
|
||||
|
||||
constructor(topPadding?: number) {
|
||||
this._paddingTop = topPadding ?? 0;
|
||||
this._size = this._paddingTop;
|
||||
}
|
||||
|
||||
splice(index: number, deleteCount: number, items: IItem[] = []): void {
|
||||
const diff = items.length - deleteCount;
|
||||
@@ -104,7 +119,7 @@ export class RangeMap {
|
||||
}));
|
||||
|
||||
this.groups = concat(before, middle, after);
|
||||
this._size = this.groups.reduce((t, g) => t + (g.size * (g.range.end - g.range.start)), 0);
|
||||
this._size = this._paddingTop + this.groups.reduce((t, g) => t + (g.size * (g.range.end - g.range.start)), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,8 +150,12 @@ export class RangeMap {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (position < this._paddingTop) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
let size = 0;
|
||||
let size = this._paddingTop;
|
||||
|
||||
for (const group of this.groups) {
|
||||
const count = group.range.end - group.range.start;
|
||||
@@ -177,7 +196,7 @@ export class RangeMap {
|
||||
const newCount = count + groupCount;
|
||||
|
||||
if (index < newCount) {
|
||||
return position + ((index - count) * group.size);
|
||||
return this._paddingTop + position + ((index - count) * group.size);
|
||||
}
|
||||
|
||||
position += groupCount * group.size;
|
||||
|
||||
@@ -343,3 +343,83 @@ suite('RangeMap', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('RangeMap with top padding', () => {
|
||||
let rangeMap: RangeMap;
|
||||
|
||||
setup(() => {
|
||||
rangeMap = new RangeMap(10);
|
||||
});
|
||||
|
||||
test('empty', () => {
|
||||
assert.strictEqual(rangeMap.size, 10);
|
||||
assert.strictEqual(rangeMap.count, 0);
|
||||
});
|
||||
|
||||
const one = { size: 1 };
|
||||
const five = { size: 5 };
|
||||
const ten = { size: 10 };
|
||||
|
||||
test('length & count', () => {
|
||||
rangeMap.splice(0, 0, [one]);
|
||||
assert.strictEqual(rangeMap.size, 11);
|
||||
assert.strictEqual(rangeMap.count, 1);
|
||||
});
|
||||
|
||||
test('length & count #2', () => {
|
||||
rangeMap.splice(0, 0, [one, one, one, one, one]);
|
||||
assert.strictEqual(rangeMap.size, 15);
|
||||
assert.strictEqual(rangeMap.count, 5);
|
||||
});
|
||||
|
||||
test('length & count #3', () => {
|
||||
rangeMap.splice(0, 0, [five]);
|
||||
assert.strictEqual(rangeMap.size, 15);
|
||||
assert.strictEqual(rangeMap.count, 1);
|
||||
});
|
||||
|
||||
test('length & count #4', () => {
|
||||
rangeMap.splice(0, 0, [five, five, five, five, five]);
|
||||
assert.strictEqual(rangeMap.size, 35);
|
||||
assert.strictEqual(rangeMap.count, 5);
|
||||
});
|
||||
|
||||
test('insert', () => {
|
||||
rangeMap.splice(0, 0, [five, five, five, five, five]);
|
||||
assert.strictEqual(rangeMap.size, 35);
|
||||
assert.strictEqual(rangeMap.count, 5);
|
||||
|
||||
rangeMap.splice(0, 0, [five, five, five, five, five]);
|
||||
assert.strictEqual(rangeMap.size, 60);
|
||||
assert.strictEqual(rangeMap.count, 10);
|
||||
|
||||
rangeMap.splice(5, 0, [ten, ten]);
|
||||
assert.strictEqual(rangeMap.size, 80);
|
||||
assert.strictEqual(rangeMap.count, 12);
|
||||
|
||||
rangeMap.splice(12, 0, [{ size: 200 }]);
|
||||
assert.strictEqual(rangeMap.size, 280);
|
||||
assert.strictEqual(rangeMap.count, 13);
|
||||
});
|
||||
|
||||
suite('indexAt, positionAt', () => {
|
||||
test('empty', () => {
|
||||
assert.strictEqual(rangeMap.indexAt(0), 0);
|
||||
assert.strictEqual(rangeMap.indexAt(10), 0);
|
||||
assert.strictEqual(rangeMap.indexAt(-1), -1);
|
||||
assert.strictEqual(rangeMap.positionAt(0), -1);
|
||||
assert.strictEqual(rangeMap.positionAt(10), -1);
|
||||
assert.strictEqual(rangeMap.positionAt(-1), -1);
|
||||
});
|
||||
|
||||
test('simple', () => {
|
||||
rangeMap.splice(0, 0, [one]);
|
||||
assert.strictEqual(rangeMap.indexAt(0), 0);
|
||||
assert.strictEqual(rangeMap.indexAt(1), 0);
|
||||
assert.strictEqual(rangeMap.indexAt(10), 0);
|
||||
assert.strictEqual(rangeMap.indexAt(11), 1);
|
||||
assert.strictEqual(rangeMap.positionAt(0), 10);
|
||||
assert.strictEqual(rangeMap.positionAt(1), -1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -268,7 +268,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD
|
||||
mouseSupport: true,
|
||||
multipleSelectionSupport: false,
|
||||
typeNavigationEnabled: true,
|
||||
additionalScrollHeight: 0,
|
||||
paddingBottom: 0,
|
||||
// transformOptimization: (isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native',
|
||||
styleController: (_suffix: string) => { return this._list!; },
|
||||
overrideStyles: {
|
||||
|
||||
@@ -754,15 +754,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
|
||||
}`);
|
||||
}
|
||||
|
||||
// top insert toolbar
|
||||
const topInsertToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType);
|
||||
styleSheets.push(`.notebookOverlay .cell-list-top-cell-toolbar-container { top: -${topInsertToolbarHeight - 3}px }`);
|
||||
styleSheets.push(`.notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element,
|
||||
.notebookOverlay > .cell-list-container > .notebook-gutter > .monaco-list > .monaco-scrollable-element {
|
||||
padding-top: ${topInsertToolbarHeight}px !important;
|
||||
box-sizing: border-box;
|
||||
}`);
|
||||
|
||||
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .code-cell-row div.cell.code { margin-left: ${codeCellLeftMargin + cellRunGutter}px; }`);
|
||||
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell { margin-right: ${cellRightMargin}px; }`);
|
||||
styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .cell-inner-container { padding-top: ${cellTopMargin}px; }`);
|
||||
@@ -878,7 +869,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
|
||||
multipleSelectionSupport: true,
|
||||
selectionNavigation: true,
|
||||
typeNavigationEnabled: true,
|
||||
additionalScrollHeight: 0,
|
||||
paddingTop: this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType),
|
||||
paddingBottom: 0,
|
||||
transformOptimization: false, //(isMacintosh && isNative) || getTitleBarStyle(this.configurationService, this.environmentService) === 'native',
|
||||
initialSize: this._dimension,
|
||||
styleController: (_suffix: string) => { return this._list; },
|
||||
@@ -1477,8 +1469,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
|
||||
}));
|
||||
|
||||
if (this._dimension) {
|
||||
const topInserToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType);
|
||||
this._list.layout(this._dimension.height - topInserToolbarHeight, this._dimension.width);
|
||||
this._list.layout(this._dimension.height, this._dimension.width);
|
||||
} else {
|
||||
this._list.layout();
|
||||
}
|
||||
@@ -1772,15 +1763,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
|
||||
DOM.size(this._body, dimension.width, newBodyHeight);
|
||||
|
||||
const topInserToolbarHeight = this._notebookOptions.computeTopInsertToolbarHeight(this.viewModel?.viewType);
|
||||
const newCellListHeight = Math.max(newBodyHeight - topInserToolbarHeight, 0);
|
||||
const newCellListHeight = newBodyHeight;
|
||||
if (this._list.getRenderHeight() < newCellListHeight) {
|
||||
// the new dimension is larger than the list viewport, update its additional height first, otherwise the list view will move down a bit (as the `scrollBottom` will move down)
|
||||
this._list.updateOptions({ additionalScrollHeight: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : topInserToolbarHeight });
|
||||
this._list.updateOptions({ paddingBottom: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : 0, paddingTop: topInserToolbarHeight });
|
||||
this._list.layout(newCellListHeight, dimension.width);
|
||||
} else {
|
||||
// the new dimension is smaller than the list viewport, if we update the additional height, the `scrollBottom` will move up, which moves the whole list view upwards a bit. So we run a layout first.
|
||||
this._list.layout(newCellListHeight, dimension.width);
|
||||
this._list.updateOptions({ additionalScrollHeight: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : topInserToolbarHeight });
|
||||
this._list.updateOptions({ paddingBottom: this._allowScrollBeyondLastLine() ? Math.max(0, (newCellListHeight - 50)) : 0, paddingTop: topInserToolbarHeight });
|
||||
}
|
||||
|
||||
this._overlayContainer.style.visibility = 'visible';
|
||||
|
||||
@@ -139,8 +139,8 @@ suite('NotebookCellList', () => {
|
||||
});
|
||||
|
||||
const cellList = createNotebookCellList(instantiationService);
|
||||
// without additionalscrollheight, the last 20 px will always be hidden due to `topInsertToolbarHeight`
|
||||
cellList.updateOptions({ additionalScrollHeight: 100 });
|
||||
// without paddingBottom, the last 20 px will always be hidden due to `topInsertToolbarHeight`
|
||||
cellList.updateOptions({ paddingBottom: 100 });
|
||||
cellList.attachViewModel(viewModel);
|
||||
|
||||
// render height 210, it can render 3 full cells and 1 partial cell
|
||||
|
||||
@@ -97,7 +97,7 @@ export class TerminalTabList extends WorkbenchList<ITerminalInstance> {
|
||||
accessibilityProvider: instantiationService.createInstance(TerminalTabsAccessibilityProvider),
|
||||
smoothScrolling: _configurationService.getValue<boolean>('workbench.list.smoothScrolling'),
|
||||
multipleSelectionSupport: true,
|
||||
additionalScrollHeight: TerminalTabsListSizes.TabHeight,
|
||||
paddingBottom: TerminalTabsListSizes.TabHeight,
|
||||
dnd: instantiationService.createInstance(TerminalTabsDragAndDrop),
|
||||
openOnSingleClick: true
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user