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

Polish explorer tooltip rendering #174085

Merged
merged 9 commits into from
Feb 14, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
height: 100%;
}

.explorer-item-hover {
/* -- Must set important as hover overrides the cursor -- */
cursor: pointer !important;
padding-left: 6px;
height: 22px;
font-size: 13px;
}

.explorer-folders-view .monaco-list-row {
padding-left: 4px; /* align top level twistie with `Explorer` title label */
}
Expand Down
70 changes: 66 additions & 4 deletions src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'
import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { ILabelService } from 'vs/platform/label/common/label';
import { isNumber } from 'vs/base/common/types';
import { isNumber, isStringArray } from 'vs/base/common/types';
import { IEditableData } from 'vs/workbench/common/views';
import { EditorInput } from 'vs/workbench/common/editor/editorInput';
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
Expand All @@ -62,6 +62,9 @@ import { ResourceSet } from 'vs/base/common/map';
import { TernarySearchTree } from 'vs/base/common/ternarySearchTree';
import { defaultInputBoxStyles } from 'vs/platform/theme/browser/defaultStyles';
import { timeout } from 'vs/base/common/async';
import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';

export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> {

Expand Down Expand Up @@ -275,6 +278,64 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
private _onDidChangeActiveDescendant = new EventMultiplexer<void>();
readonly onDidChangeActiveDescendant = this._onDidChangeActiveDescendant.event;

private readonly hoverDelegate = new class implements IHoverDelegate {

readonly placement = 'element';

get delay() {
return this.configurationService.getValue<number>('workbench.hover.delay');
}

constructor(
private readonly configurationService: IConfigurationService,
private readonly hoverService: IHoverService
) { }

showHover(options: IHoverDelegateOptions, focus?: boolean): IHoverWidget | undefined {
let element: HTMLElement;
if (options.target instanceof HTMLElement) {
element = options.target;
} else {
element = options.target.targetElements[0];
}

const row = element.closest('.monaco-tl-row') as HTMLElement | undefined;

const child = element.querySelector('div.monaco-icon-label-container') as Element | undefined;
const childOfChild = child?.querySelector('span.monaco-icon-name-container') as HTMLElement | undefined;
let overflowed = false;
if (childOfChild && child) {
const width = child.clientWidth;
const childWidth = childOfChild.offsetWidth;
// Check if element is overflowing its parent container
overflowed = width <= childWidth;
}

const hasDecoration = element.classList.toString().includes('monaco-decoration-iconBadge');
// If it's overflowing or has a decoration show the tooltip
overflowed = overflowed || hasDecoration;

const indentGuideElement = row?.querySelector('.monaco-tl-indent') as HTMLElement | undefined;
if (!indentGuideElement) {
return;
}

return overflowed ? this.hoverService.showHover({
...options,
target: indentGuideElement,
compact: true,
additionalClasses: ['explorer-item-hover'],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The icon would be a nice addition and I see you said we can defer that, but I think aligning the position and font exactly with underneath would be really good to go out with this release. It's one of the things that makes the Windows Explorer feature so nice imo.

image

Copy link
Member Author

@lramos15 lramos15 Feb 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Hopefully this is close enough. Spent quite some time trying to get the icon to work, but even cloning the row doesn't make it work because it's a psuedo element with a psuedo style. I would have to figure out how to get that pseudo element created in the hover as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks better 👍

You could maybe align it easy via getComputedStyle and then applying all the relevant font values to the hover element?

skipFadeInAnimation: true,
showPointer: false,
onClick: (e) => {
this.hoverService.hideHover();
element.dispatchEvent(new MouseEvent(e.type, { ...e, bubbles: true }));
},
hoverPosition: HoverPosition.RIGHT,
}, focus) : undefined;
}
}(this.configurationService, this.hoverService);

constructor(
container: HTMLElement,
private labels: ResourceLabels,
Expand All @@ -285,7 +346,8 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
@IExplorerService private readonly explorerService: IExplorerService,
@ILabelService private readonly labelService: ILabelService,
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
@IContextMenuService private readonly contextMenuService: IContextMenuService
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IHoverService private readonly hoverService: IHoverService
) {
this.config = this.configurationService.getValue<IFilesConfiguration>();

Expand Down Expand Up @@ -317,8 +379,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu

renderTemplate(container: HTMLElement): IFileTemplateData {
const templateDisposables = new DisposableStore();

const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true }));
const label = templateDisposables.add(this.labels.create(container, { supportHighlights: true, hoverDelegate: this.hoverDelegate }));
templateDisposables.add(label.onDidRender(() => {
try {
if (templateData.currentContext) {
Expand Down Expand Up @@ -417,6 +478,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
const realignNestedChildren = stat.nestedParent && themeIsUnhappyWithNesting;

templateData.label.setResource({ resource: stat.resource, name: label }, {
title: isStringArray(label) ? label[0] : label,
fileKind: stat.isRoot ? FileKind.ROOT_FOLDER : stat.isDirectory ? FileKind.FOLDER : FileKind.FILE,
extraClasses: realignNestedChildren ? [...extraClasses, 'align-nest-icon-with-parent-icon'] : extraClasses,
fileDecorations: this.config.explorer.decorations,
Expand Down
6 changes: 5 additions & 1 deletion src/vs/workbench/services/hover/browser/hover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import { IDisposable } from 'vs/base/common/lifecycle';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';

export const IHoverService = createDecorator<IHoverService>('hoverService');

Expand Down Expand Up @@ -129,6 +128,11 @@ export interface IHoverOptions {
trapFocus?: boolean;

/**
* A callback which will be executed when the hover is clicked
*/
onClick?(e: MouseEvent): void;

/*
* The container to pass to {@link IContextViewProvider.showContextView} which renders the hover
* in. This is particularly useful for more natural tab focusing behavior, where the hover is
* created as the next tab index after the element being hovered and/or to workaround the
Expand Down
5 changes: 5 additions & 0 deletions src/vs/workbench/services/hover/browser/hoverService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ export class HoverService implements IHoverService {
options.container
);
hover.onRequestLayout(() => provider.layout());
if (options.onClick) {
hoverDisposables.add(addDisposableListener(hover.domNode, EventType.CLICK, e => {
options.onClick!(e);
}));
}
if ('targetElements' in options.target) {
for (const element of options.target.targetElements) {
hoverDisposables.add(addDisposableListener(element, EventType.CLICK, () => this.hideHover()));
Expand Down