Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sticky Scroll Tree View #198320

Merged
merged 27 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ac80a13
Tree Sticky Scroll
benibenj Nov 15, 2023
5d3866e
Updated StickyScrollController and TreeRenderer classes for improved
benibenj Nov 15, 2023
ec8f320
Merge branch 'main' into benibenj/treeStickyScroll
benibenj Nov 15, 2023
1d8de70
Updated TreeNodeListMouseController to handle sticky elements and
benibenj Nov 16, 2023
2af5515
:lipstick:
benibenj Nov 16, 2023
c8e5d58
fix max-ratio corner case
benibenj Nov 16, 2023
8d0b4a6
Updated various components for better visibility and scrolling in trees,
benibenj Nov 16, 2023
afb2097
:lipstick:
benibenj Nov 17, 2023
e0b04b5
Refactor StickyScrollController and update CSS selectors in tree.css
benibenj Nov 18, 2023
e35189f
Don't keep empty arrays around
benibenj Nov 19, 2023
4c24d98
SCM InputRenderer
benibenj Nov 19, 2023
630a9f8
SCM Action Button
benibenj Nov 19, 2023
3d0dbcb
Resource Markers
benibenj Nov 19, 2023
eaecf45
compressed explorer view
benibenj Nov 19, 2023
2e3c37d
handle disposable
benibenj Nov 20, 2023
b159c5b
:lipstick:
benibenj Nov 20, 2023
efcce4e
:lipstick:
benibenj Nov 20, 2023
2c13344
Merge branch 'main' into benibenj/treeStickyScroll
benibenj Nov 20, 2023
0566ecd
:lipstick:
benibenj Nov 20, 2023
2e28417
fix colors
benibenj Nov 20, 2023
d7a3050
:lipstick:
benibenj Nov 20, 2023
7f63c62
:lipstick:
benibenj Nov 21, 2023
19e68a0
Updated list and tree UI components to support sticky scrolling and
benibenj Nov 21, 2023
c856c39
Updated getRelativeTop method to consider paddingTop and adjusted
benibenj Nov 21, 2023
32bd56f
Updated CSS and TypeScript for improved scrollbar and sticky scroll
benibenj Nov 21, 2023
252be64
Updated scroll calculations and added node position methods in list and
benibenj Nov 21, 2023
13b6a07
Merge branch 'main' into benibenj/treeStickyScroll
benibenj Nov 21, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/vs/base/browser/ui/list/list.css
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
touch-action: none;
}

/* Make sure the scrollbar renders above overlays (sticky scroll) */
.monaco-list .monaco-scrollable-element > .scrollbar {
z-index: 14;
}

/* for OS X ballistic scrolling */
.monaco-list-row.scrolling {
display: none !important;
Expand Down
14 changes: 10 additions & 4 deletions src/vs/base/browser/ui/list/listView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ export interface IListView<T> extends ISpliceable<T>, IDisposable {
readonly renderHeight: number;
readonly scrollHeight: number;
readonly firstVisibleIndex: number;
readonly firstMostlyVisibleIndex: number;
readonly lastVisibleIndex: number;
onDidScroll: Event<ScrollEvent>;
onWillScroll: Event<ScrollEvent>;
Expand Down Expand Up @@ -753,16 +754,21 @@ export class ListView<T> implements IListView<T> {

get firstVisibleIndex(): number {
const range = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight);
const firstElTop = this.rangeMap.positionAt(range.start);
const nextElTop = this.rangeMap.positionAt(range.start + 1);
return range.start;
}

get firstMostlyVisibleIndex(): number {
const firstVisibleIndex = this.firstVisibleIndex;
const firstElTop = this.rangeMap.positionAt(firstVisibleIndex);
const nextElTop = this.rangeMap.positionAt(firstVisibleIndex + 1);
if (nextElTop !== -1) {
const firstElMidpoint = (nextElTop - firstElTop) / 2 + firstElTop;
if (firstElMidpoint < this.scrollTop) {
return range.start + 1;
return firstVisibleIndex + 1;
}
}

return range.start;
return firstVisibleIndex;
}

get lastVisibleIndex(): number {
Expand Down
58 changes: 45 additions & 13 deletions src/vs/base/browser/ui/list/listWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,8 @@ export function isInputElement(e: HTMLElement): boolean {
return e.tagName === 'INPUT' || e.tagName === 'TEXTAREA';
}

export function isMonacoEditor(e: HTMLElement): boolean {
if (e.classList.contains('monaco-editor')) {
function isListElementDescendantOfClass(e: HTMLElement, className: string): boolean {
if (e.classList.contains(className)) {
return true;
}

Expand All @@ -269,7 +269,27 @@ export function isMonacoEditor(e: HTMLElement): boolean {
return false;
}

return isMonacoEditor(e.parentElement);
return isListElementDescendantOfClass(e.parentElement, className);
}

export function isMonacoEditor(e: HTMLElement): boolean {
return isListElementDescendantOfClass(e, 'monaco-editor');
}

export function isMonacoCustomToggle(e: HTMLElement): boolean {
return isListElementDescendantOfClass(e, 'monaco-custom-toggle');
}

export function isActionItem(e: HTMLElement): boolean {
return isListElementDescendantOfClass(e, 'action-item');
}

export function isMonacoTwistie(e: HTMLElement): boolean {
return isListElementDescendantOfClass(e, 'monaco-tl-twistie');
}

export function isStickyScrollElement(e: HTMLElement): boolean {
return isListElementDescendantOfClass(e, 'monaco-tree-sticky-row');
}

export function isButton(e: HTMLElement): boolean {
Expand Down Expand Up @@ -1598,6 +1618,10 @@ export class List<T> implements ISpliceable<T>, IDisposable {
return this.view.firstVisibleIndex;
}

get firstMostlyVisibleIndex(): number {
return this.view.firstMostlyVisibleIndex;
}

get lastVisibleIndex(): number {
return this.view.lastVisibleIndex;
}
Expand Down Expand Up @@ -1830,7 +1854,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
return this.getFocus().map(i => this.view.element(i));
}

reveal(index: number, relativeTop?: number): void {
reveal(index: number, relativeTop?: number, paddingTop: number = 0): void {
if (index < 0 || index >= this.length) {
throw new ListError(this.user, `Invalid index ${index}`);
}
Expand All @@ -1841,16 +1865,16 @@ export class List<T> implements ISpliceable<T>, IDisposable {

if (isNumber(relativeTop)) {
// y = mx + b
const m = elementHeight - this.view.renderHeight;
this.view.setScrollTop(m * clamp(relativeTop, 0, 1) + elementTop);
const m = elementHeight - this.view.renderHeight + paddingTop;
this.view.setScrollTop(m * clamp(relativeTop, 0, 1) + elementTop - paddingTop);
} else {
const viewItemBottom = elementTop + elementHeight;
const scrollBottom = scrollTop + this.view.renderHeight;

if (elementTop < scrollTop && viewItemBottom >= scrollBottom) {
if (elementTop < scrollTop + paddingTop && viewItemBottom >= scrollBottom) {
// The element is already overflowing the viewport, no-op
} else if (elementTop < scrollTop || (viewItemBottom >= scrollBottom && elementHeight >= this.view.renderHeight)) {
this.view.setScrollTop(elementTop);
} else if (elementTop < scrollTop + paddingTop || (viewItemBottom >= scrollBottom && elementHeight >= this.view.renderHeight)) {
this.view.setScrollTop(elementTop - paddingTop);
} else if (viewItemBottom >= scrollBottom) {
this.view.setScrollTop(viewItemBottom - this.view.renderHeight);
}
Expand All @@ -1861,7 +1885,7 @@ export class List<T> implements ISpliceable<T>, IDisposable {
* Returns the relative position of an element rendered in the list.
* Returns `null` if the element isn't *entirely* in the visible viewport.
*/
getRelativeTop(index: number): number | null {
getRelativeTop(index: number, paddingTop: number = 0): number | null {
if (index < 0 || index >= this.length) {
throw new ListError(this.user, `Invalid index ${index}`);
}
Expand All @@ -1870,13 +1894,13 @@ export class List<T> implements ISpliceable<T>, IDisposable {
const elementTop = this.view.elementTop(index);
const elementHeight = this.view.elementHeight(index);

if (elementTop < scrollTop || elementTop + elementHeight > scrollTop + this.view.renderHeight) {
if (elementTop < scrollTop + paddingTop || elementTop + elementHeight > scrollTop + this.view.renderHeight) {
return null;
}

// y = mx + b
const m = elementHeight - this.view.renderHeight;
return Math.abs((scrollTop - elementTop) / m);
const m = elementHeight - this.view.renderHeight + paddingTop;
return Math.abs((scrollTop + paddingTop - elementTop) / m);
}

isDOMFocused(): boolean {
Expand All @@ -1887,10 +1911,18 @@ export class List<T> implements ISpliceable<T>, IDisposable {
return this.view.domNode;
}

getScrollableElement(): HTMLElement {
return this.view.scrollableElementDomNode;
}

getElementID(index: number): string {
return this.view.getElementDomId(index);
}

getElementTop(index: number): number {
return this.view.elementTop(index);
}

style(styles: IListStyles): void {
this.styleController.style(styles);
}
Expand Down
Loading
Loading