improve hover position logic

This commit is contained in:
Sandeep Somavarapu 2021-04-26 11:03:24 +02:00
parent b6ff982121
commit 7d13ce88e7
2 changed files with 175 additions and 69 deletions

View file

@ -21,7 +21,7 @@ import { CompositeDragAndDropObserver, ICompositeDragAndDrop, Before2D, toggleDr
import { Color } from 'vs/base/common/color';
import { IBaseActionViewItemOptions, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { Codicon } from 'vs/base/common/codicons';
import { IHoverService, IHoverTarget } from 'vs/workbench/services/hover/browser/hover';
import { IHoverService } from 'vs/workbench/services/hover/browser/hover';
import { domEvent } from 'vs/base/browser/event';
import { RunOnceScheduler } from 'vs/base/common/async';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@ -415,15 +415,9 @@ export class ActivityActionViewItem extends BaseActionViewItem {
if (this.hover.value) {
return;
}
const { bottom } = this.container.getBoundingClientRect();
const hoverPosition = this.options.hoverOptions!.position();
const target: IHoverTarget | HTMLElement = hoverPosition === HoverPosition.LEFT || hoverPosition === HoverPosition.RIGHT ? {
targetElements: [this.container],
y: bottom - 10,
dispose: () => { }
} : this.container;
this.hover.value = this.hoverService.showHover({
target,
target: this.container,
hoverPosition,
text: this.computeTitle(),
showPointer: true,

View file

@ -20,6 +20,15 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer';
const $ = dom.$;
type TargetRect = {
left: number,
right: number,
top: number,
bottom: number,
width: number,
height: number,
center: { x: number, y: number },
};
export class HoverWidget extends Widget {
private readonly _messageListeners = new DisposableStore();
@ -170,82 +179,185 @@ export class HoverWidget extends Widget {
this._hover.contentsDomNode.style.maxHeight = '';
const targetBounds = this._target.targetElements.map(e => e.getBoundingClientRect());
const top = Math.min(...targetBounds.map(e => e.top));
const right = Math.max(...targetBounds.map(e => e.right));
const bottom = Math.max(...targetBounds.map(e => e.bottom));
const left = Math.min(...targetBounds.map(e => e.left));
const width = right - left;
const height = bottom - top;
// 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));
} else if (this._hoverPosition === HoverPosition.LEFT) {
this._x = Math.min(...targetBounds.map(e => e.left)) - this._hover.containerDomNode.clientWidth - 1;
} else {
const targetLeft = Math.min(...targetBounds.map(e => e.left));
if (targetLeft + this._hover.containerDomNode.clientWidth >= document.documentElement.clientWidth) {
this._x = document.documentElement.clientWidth - this._workbenchLayoutService.getWindowBorderWidth() - 1;
this._hover.containerDomNode.classList.add('right-aligned');
} else {
this._x = targetLeft;
const targetRect: TargetRect = {
top, right, bottom, left, width, height,
center: {
x: left + (width / 2),
y: top + (height / 2)
}
}
};
// Get vertical alignment and position
if (this._target.y !== undefined) {
this._y = this._target.y;
} 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._hoverPosition = HoverPosition.BELOW;
this._y = targetBottom - 2;
} else {
this._y = targetTop;
}
} else {
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._hoverPosition = HoverPosition.ABOVE;
this._y = targetTop;
} else {
this._y = targetBottom - 2;
}
}
this.adjustHorizontalHoverPosition(targetRect);
this.adjustVerticalHoverPosition(targetRect);
this.computeXCordinate(targetRect);
this.computeYCordinate(targetRect);
if (this._hoverPointer) {
// reset
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.setHoverPointerPosition(targetRect);
}
this._hover.onContentsChanged();
}
private computeXCordinate(target: TargetRect): void {
if (this._target.x !== undefined) {
this._x = this._target.x;
}
else if (this._hoverPosition === HoverPosition.RIGHT) {
this._x = target.right;
}
else if (this._hoverPosition === HoverPosition.LEFT) {
this._x = target.left;
}
else {
if (this._hoverPointer) {
this._x = target.center.x - (this._hover.containerDomNode.clientWidth / 2);
} else {
if (target.left + this._hover.containerDomNode.clientWidth >= document.documentElement.clientWidth) {
this._hover.containerDomNode.classList.add('right-aligned');
this._x = document.documentElement.clientWidth - this._workbenchLayoutService.getWindowBorderWidth() - 1;
} else {
this._x = target.left;
}
}
}
// Hover on left is going beyond window
if (this._x < document.documentElement.clientLeft) {
this._x = target.left;
}
}
private computeYCordinate(target: TargetRect): void {
if (this._target.y !== undefined) {
this._y = this._target.y;
}
else if (this._hoverPosition === HoverPosition.ABOVE) {
this._y = target.top;
}
else if (this._hoverPosition === HoverPosition.BELOW) {
this._y = target.bottom - 2;
}
else {
if (this._hoverPointer) {
this._y = target.center.y + (this._hover.containerDomNode.clientHeight / 2);
} else {
this._y = target.bottom;
}
}
// Hover on bottom is going beyond window
if (this._y > window.innerHeight) {
this._y = target.bottom;
}
}
private adjustHorizontalHoverPosition(target: TargetRect): void {
// Do not adjust horizontal hover position if x cordiante is provided
if (this._target.x !== undefined) {
return;
}
// Position hover on right to target
if (this._hoverPosition === HoverPosition.RIGHT) {
// Hover on the right is going beyond window.
if (target.right + this._hover.containerDomNode.clientWidth >= document.documentElement.clientWidth) {
this._hoverPosition = HoverPosition.LEFT;
}
}
// Position hover on left to target
if (this._hoverPosition === HoverPosition.LEFT) {
// Hover on the left is going beyond window.
if (target.left - this._hover.containerDomNode.clientWidth <= document.documentElement.clientLeft) {
this._hoverPosition = HoverPosition.RIGHT;
}
}
}
private adjustVerticalHoverPosition(target: TargetRect): void {
// Do not adjust vertical hover position if y cordiante is provided
if (this._target.y !== undefined) {
return;
}
// Position hover on top of the target
if (this._hoverPosition === HoverPosition.ABOVE) {
// Hover on top is going beyond window
if (target.top - this._hover.containerDomNode.clientHeight < 0) {
this._hoverPosition = HoverPosition.BELOW;
}
}
// Position hover below the target
else if (this._hoverPosition === HoverPosition.BELOW) {
// Hover on bottom is going beyond window
if (target.bottom + this._hover.containerDomNode.clientHeight > window.innerHeight) {
this._hoverPosition = HoverPosition.ABOVE;
}
}
}
private setHoverPointerPosition(target: TargetRect): void {
if (!this._hoverPointer) {
return;
}
switch (this._hoverPosition) {
case HoverPosition.LEFT:
case HoverPosition.RIGHT:
this._hoverPointer.classList.add(this._hoverPosition === HoverPosition.LEFT ? 'right' : 'left');
const hoverHeight = this._hover.containerDomNode.clientHeight;
// If hover is taller than target and aligned with target's bottom, then show the pointer at the center of target
if (hoverHeight > target.height && this._y === target.bottom) {
this._hoverPointer.style.top = `${target.center.y - target.top - 3}px`;
}
// Otherwise show the pointer at the center of hover
else {
this._hoverPointer.style.top = `${(hoverHeight / 2) - 3}px`;
}
break;
case HoverPosition.ABOVE:
case HoverPosition.BELOW:
this._hoverPointer.classList.add(this._hoverPosition === HoverPosition.ABOVE ? 'bottom' : 'top');
const hoverWidth = this._hover.containerDomNode.clientWidth;
// If hover is wider than target and aligned with target's left, then show the pointer at the center of target
if (hoverWidth > target.width && this._x === target.left) {
this._hoverPointer.style.left = `${target.center.x - target.left - 3}px`;
}
// Otherwise show the pointer at the center of hover
else {
this._hoverPointer.style.left = `${(hoverWidth / 2) - 3}px`;
}
break;
}
}
public focus() {
this._hover.containerDomNode.focus();
}