From 61364b049268df5d4fc01b32602e7b7fd7d67203 Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Thu, 22 Apr 2021 01:03:26 +0200 Subject: [PATCH] introduce hover position and align hover and pointer --- src/vs/base/browser/ui/hover/hoverWidget.ts | 4 ++ .../browser/ui/iconLabel/iconHoverDelegate.ts | 4 +- src/vs/base/browser/ui/iconLabel/iconLabel.ts | 6 +- .../parts/activitybar/activitybarPart.ts | 5 +- .../browser/parts/compositeBarActions.ts | 19 +++--- .../browser/parts/panel/panelPart.ts | 5 +- .../workbench/services/hover/browser/hover.ts | 5 +- .../services/hover/browser/hoverWidget.ts | 61 ++++++++++++++++--- .../services/hover/browser/media/hover.css | 48 ++++++++++++--- 9 files changed, 118 insertions(+), 39 deletions(-) diff --git a/src/vs/base/browser/ui/hover/hoverWidget.ts b/src/vs/base/browser/ui/hover/hoverWidget.ts index 28b240f06c0..34a02c215a3 100644 --- a/src/vs/base/browser/ui/hover/hoverWidget.ts +++ b/src/vs/base/browser/ui/hover/hoverWidget.ts @@ -10,6 +10,10 @@ import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableEle const $ = dom.$; +export const enum HoverPosition { + LEFT, RIGHT, BELOW, ABOVE +} + export class HoverWidget extends Disposable { public readonly containerDomNode: HTMLElement; diff --git a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts index 1f4453fa831..33e7c72fd65 100644 --- a/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts +++ b/src/vs/base/browser/ui/iconLabel/iconHoverDelegate.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -15,7 +15,7 @@ export interface IHoverDelegateTarget extends IDisposable { export interface IHoverDelegateOptions { text: IMarkdownString | string; target: IHoverDelegateTarget | HTMLElement; - anchorPosition?: AnchorPosition; + hoverPosition?: HoverPosition; } export interface IHoverDelegate { diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index ecdd346b6b4..ba6727884bf 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -11,12 +11,12 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { Range } from 'vs/base/common/range'; import { equals } from 'vs/base/common/objects'; import { IHoverDelegate, IHoverDelegateOptions, IHoverDelegateTarget } from 'vs/base/browser/ui/iconLabel/iconHoverDelegate'; -import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { isFunction, isString } from 'vs/base/common/types'; import { domEvent } from 'vs/base/browser/event'; import { localize } from 'vs/nls'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; export interface IIconLabelCreationOptions { supportHighlights?: boolean; @@ -260,7 +260,7 @@ export class IconLabel extends Disposable { hoverOptions = { text: localize('iconLabel.loading', "Loading..."), target, - anchorPosition: AnchorPosition.BELOW + hoverPosition: HoverPosition.BELOW }; hoverDisposable = IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering); @@ -269,7 +269,7 @@ export class IconLabel extends Disposable { hoverOptions = { text: resolvedTooltip, target, - anchorPosition: AnchorPosition.BELOW + hoverPosition: HoverPosition.BELOW }; // awaiting the tooltip could take a while. Make sure we're still hovering. hoverDisposable = IconLabel.adjustXAndShowCustomHover(hoverOptions, mouseX, hoverDelegate, isHovering); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 3aee53a7e63..e20f9c109cb 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -22,7 +22,7 @@ import { Dimension, createCSSRule, asCSSUrl, addDisposableListener, EventType } import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity, ActivityHoverAlignment, IActivityHoverOptions } from 'vs/workbench/browser/parts/compositeBarActions'; +import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity, IActivityHoverOptions } from 'vs/workbench/browser/parts/compositeBarActions'; import { IViewDescriptorService, ViewContainer, IViewContainerModel, ViewContainerLocation, IViewsService, getEnabledViewContainerContextKey } from 'vs/workbench/common/views'; import { IContextKeyService, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { assertIsDefined, isString } from 'vs/base/common/types'; @@ -43,6 +43,7 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { CATEGORIES } from 'vs/workbench/common/actions'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { StringSHA1 } from 'vs/base/common/hash'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; interface IPlaceholderViewContainer { readonly id: string; @@ -221,7 +222,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { private getActivityHoverOptions(): IActivityHoverOptions { return { - alignment: () => this.layoutService.getSideBarPosition() === Position.LEFT ? ActivityHoverAlignment.RIGHT : ActivityHoverAlignment.LEFT, + position: () => this.layoutService.getSideBarPosition() === Position.LEFT ? HoverPosition.RIGHT : HoverPosition.LEFT, delay: () => 0 }; } diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index 59a4e204840..0fd375015eb 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -23,9 +23,9 @@ import { IBaseActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ import { Codicon } from 'vs/base/common/codicons'; import { IHoverService, IHoverTarget } from 'vs/workbench/services/hover/browser/hover'; import { domEvent } from 'vs/base/browser/event'; -import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; export interface ICompositeActivity { badge: IBadge; @@ -127,12 +127,8 @@ export interface ICompositeBarColors { dragAndDropBorder?: Color; } -export const enum ActivityHoverAlignment { - LEFT, RIGHT, BELOW, ABOVE -} - export interface IActivityHoverOptions { - alignment: () => ActivityHoverAlignment; + position: () => HoverPosition; delay: () => number; } @@ -419,20 +415,19 @@ export class ActivityActionViewItem extends BaseActionViewItem { if (this.hover.value) { return; } - const { left, right, bottom } = this.container.getBoundingClientRect(); - const hoverAlignment = this.options.hoverOptions!.alignment(); - const anchorPosition: AnchorPosition | undefined = hoverAlignment === ActivityHoverAlignment.ABOVE ? AnchorPosition.ABOVE : hoverAlignment === ActivityHoverAlignment.BELOW ? AnchorPosition.BELOW : undefined; - const target: IHoverTarget | HTMLElement = anchorPosition === undefined ? { + const { bottom } = this.container.getBoundingClientRect(); + const hoverPosition = this.options.hoverOptions!.position(); + const target: IHoverTarget | HTMLElement = hoverPosition === HoverPosition.LEFT || hoverPosition === HoverPosition.RIGHT ? { targetElements: [this.container], - x: hoverAlignment === ActivityHoverAlignment.RIGHT ? right + 4 : left - 4, y: bottom - 10, dispose: () => { } } : this.container; this.hover.value = this.hoverService.showHover({ target, - anchorPosition, + hoverPosition, text: this.computeTitle(), showPointer: true, + compact: true }); } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index afbec62f5ec..f84fd3382c8 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -24,7 +24,7 @@ import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/com import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_INPUT_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, PANEL_DRAG_AND_DROP_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry'; import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar'; -import { ActivityHoverAlignment, ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; +import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Dimension, trackFocus, EventHelper } from 'vs/base/browser/dom'; @@ -37,6 +37,7 @@ import { ViewContainer, IViewDescriptorService, IViewContainerModel, ViewContain import { IPaneComposite } from 'vs/workbench/common/panecomposite'; import { Before2D, CompositeDragAndDropObserver, ICompositeDragAndDrop, toggleDropEffect } from 'vs/workbench/browser/dnd'; import { IActivity } from 'vs/workbench/common/activity'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; interface ICachedPanel { id: string; @@ -149,7 +150,7 @@ export class PanelPart extends CompositePart implements IPanelService { icon: false, orientation: ActionsOrientation.HORIZONTAL, activityHoverOptions: { - alignment: () => ActivityHoverAlignment.BELOW, + position: () => HoverPosition.BELOW, delay: () => 0 }, openComposite: compositeId => this.openPanel(compositeId, true).then(panel => panel || null), diff --git a/src/vs/workbench/services/hover/browser/hover.ts b/src/vs/workbench/services/hover/browser/hover.ts index fe4477eb0d0..bfc7abd7503 100644 --- a/src/vs/workbench/services/hover/browser/hover.ts +++ b/src/vs/workbench/services/hover/browser/hover.ts @@ -6,7 +6,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IMarkdownString } from 'vs/base/common/htmlContent'; -import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; +import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; export const IHoverService = createDecorator('hoverService'); @@ -85,8 +85,9 @@ export interface IHoverOptions { * Whether to anchor the hover above (default) or below the target. This option will be ignored * if there is not enough room to layout the hover in the specified anchor position. */ - anchorPosition?: AnchorPosition; + hoverPosition?: HoverPosition; + compact?: boolean; showPointer?: boolean; } diff --git a/src/vs/workbench/services/hover/browser/hoverWidget.ts b/src/vs/workbench/services/hover/browser/hoverWidget.ts index c7ebe58df14..c6eb9da6dc4 100644 --- a/src/vs/workbench/services/hover/browser/hoverWidget.ts +++ b/src/vs/workbench/services/hover/browser/hoverWidget.ts @@ -11,7 +11,7 @@ import { IHoverTarget, IHoverOptions } from 'vs/workbench/services/hover/browser import { KeyCode } from 'vs/base/common/keyCodes'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { HoverWidget as BaseHoverWidget, renderHoverAction } from 'vs/base/browser/ui/hover/hoverWidget'; +import { HoverPosition, HoverWidget as BaseHoverWidget, renderHoverAction } from 'vs/base/browser/ui/hover/hoverWidget'; import { Widget } from 'vs/base/browser/ui/widget'; import { AnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -31,7 +31,7 @@ export class HoverWidget extends Widget { private readonly _linkHandler: (url: string) => any; private _isDisposed: boolean = false; - private _anchor: AnchorPosition; + private _hoverPosition: HoverPosition; private _x: number = 0; private _y: number = 0; @@ -43,7 +43,7 @@ export class HoverWidget extends Widget { private readonly _onRequestLayout = this._register(new Emitter()); get onRequestLayout(): Event { return this._onRequestLayout.event; } - get anchor(): AnchorPosition { return this._anchor; } + get anchor(): AnchorPosition { return this._hoverPosition === HoverPosition.BELOW ? AnchorPosition.BELOW : AnchorPosition.ABOVE; } get x(): number { return this._x; } get y(): number { return this._y; } @@ -64,11 +64,14 @@ export class HoverWidget extends Widget { this._hoverPointer = options.showPointer ? $('div.workbench-hover-pointer') : undefined; this._hover = this._register(new BaseHoverWidget()); this._hover.containerDomNode.classList.add('workbench-hover', 'fadeIn'); + if (options.compact) { + this._hover.containerDomNode.classList.add('workbench-hover', 'compact'); + } if (options.additionalClasses) { this._hover.containerDomNode.classList.add(...options.additionalClasses); } - this._anchor = options.anchorPosition ?? AnchorPosition.ABOVE; + this._hoverPosition = options.hoverPosition ?? HoverPosition.ABOVE; // Don't allow mousedown out of the widget, otherwise preventDefault will call and text will // not be selected. @@ -171,6 +174,16 @@ export class HoverWidget extends Widget { // Get horizontal alignment and position if (this._target.x !== undefined) { this._x = this._target.x; + } else if (this._hoverPosition === HoverPosition.RIGHT) { + this._x = Math.max(...targetBounds.map(e => e.right)); + if (this._hoverPointer) { + this._x = this._x + 5; + } + } else if (this._hoverPosition === HoverPosition.LEFT) { + this._x = Math.min(...targetBounds.map(e => e.left)) - this._hover.containerDomNode.clientWidth; + if (this._hoverPointer) { + this._x = this._x - 5; + } } else { const targetLeft = Math.min(...targetBounds.map(e => e.left)); if (targetLeft + this._hover.containerDomNode.clientWidth >= document.documentElement.clientWidth) { @@ -184,11 +197,13 @@ export class HoverWidget extends Widget { // Get vertical alignment and position if (this._target.y !== undefined) { this._y = this._target.y; - } else if (this._anchor === AnchorPosition.ABOVE) { + } else if (this._hoverPosition === HoverPosition.RIGHT || this._hoverPosition === HoverPosition.LEFT) { + this._y = Math.max(...targetBounds.map(e => e.bottom)); + } else if (this._hoverPosition === HoverPosition.ABOVE) { const targetTop = Math.min(...targetBounds.map(e => e.top)); if (targetTop - this._hover.containerDomNode.clientHeight < 0) { const targetBottom = Math.max(...targetBounds.map(e => e.bottom)); - this._anchor = AnchorPosition.BELOW; + this._hoverPosition = HoverPosition.BELOW; this._y = targetBottom - 2; } else { this._y = targetTop; @@ -197,13 +212,43 @@ export class HoverWidget extends Widget { const targetBottom = Math.max(...targetBounds.map(e => e.bottom)); if (targetBottom + this._hover.containerDomNode.clientHeight > window.innerHeight) { const targetTop = Math.min(...targetBounds.map(e => e.top)); - this._anchor = AnchorPosition.ABOVE; + this._hoverPosition = HoverPosition.ABOVE; this._y = targetTop; } else { this._y = targetBottom - 2; } } + if (this._hoverPointer) { + + this._hoverPointer.classList.remove('top'); + this._hoverPointer.classList.remove('left'); + this._hoverPointer.classList.remove('right'); + this._hoverPointer.classList.remove('bottom'); + + if (this._hoverPosition === HoverPosition.RIGHT) { + this._hoverPointer.classList.add('left'); + this._hoverPointer.style.top = `${(this._hover.containerDomNode.clientHeight / 2) - 3}px`; + } + + else if (this._hoverPosition === HoverPosition.LEFT) { + this._hoverPointer.classList.add('right'); + this._hoverPointer.style.top = `${(this._hover.containerDomNode.clientHeight / 2) - 3}px`; + } + + else if (this._hoverPosition === HoverPosition.BELOW) { + this._hoverPointer.classList.add('top'); + this._hoverPointer.style.left = `${(this._hover.containerDomNode.clientWidth / 2) - 3}px`; + this._hoverPointer.style.left = `${targetBounds[0].width / 2}px`; + } + + else if (this._hoverPosition === HoverPosition.ABOVE) { + this._hoverPointer.classList.add('bottom'); + this._hoverPointer.style.left = `${(this._hover.containerDomNode.clientWidth / 2) - 3}px`; + this._hoverPointer.style.left = `${targetBounds[0].width / 2}px`; + } + } + this._hover.onContentsChanged(); } @@ -221,7 +266,7 @@ export class HoverWidget extends Widget { if (this._hoverPointer) { this._hoverPointer.parentElement?.removeChild(this._hoverPointer); } - this._hover.containerDomNode.parentElement?.removeChild(this.domNode); + this._hover.containerDomNode.parentElement?.removeChild(this._hover.containerDomNode); this._messageListeners.dispose(); this._target.dispose(); super.dispose(); diff --git a/src/vs/workbench/services/hover/browser/media/hover.css b/src/vs/workbench/services/hover/browser/media/hover.css index fc384bf629a..e0ab2b10766 100644 --- a/src/vs/workbench/services/hover/browser/media/hover.css +++ b/src/vs/workbench/services/hover/browser/media/hover.css @@ -14,12 +14,12 @@ max-width: 700px; } -.monaco-workbench .workbench-hover { - font-size: 11px; +.monaco-workbench .workbench-hover.compact { + font-size: 12px; } -.monaco-workbench .workbench-hover .hover-contents { - padding: 2px 4px; +.monaco-workbench .workbench-hover.compact .hover-contents { + padding: 2px 8px; } .monaco-workbench .workbench-hover-pointer { @@ -31,12 +31,44 @@ .monaco-workbench .workbench-hover-pointer:after { content: ''; position: absolute; - top: 9px; - right: -3px; width: 5px; height: 5px; - -moz-transform: rotate(1355deg); - -webkit-transform: rotate(135deg) +} + +.monaco-workbench .workbench-hover-pointer.left { + left: -3px; +} + +.monaco-workbench .workbench-hover-pointer.left:after { + -moz-transform: rotate(135deg); + -webkit-transform: rotate(135deg); +} + +.monaco-workbench .workbench-hover-pointer.right { + right: 3px; +} + +.monaco-workbench .workbench-hover-pointer.right:after { + -moz-transform: rotate(315deg); + -webkit-transform: rotate(315deg); +} + +.monaco-workbench .workbench-hover-pointer.top { + top: -3px; +} + +.monaco-workbench .workbench-hover-pointer.top:after { + -moz-transform: rotate(225deg); + -webkit-transform: rotate(225deg); +} + +.monaco-workbench .workbench-hover-pointer.bottom { + bottom: 3px; +} + +.monaco-workbench .workbench-hover-pointer.bottom:after { + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); } .monaco-workbench .workbench-hover a {