1
0
Fork 0
spacetac/src/ui/common/Tooltip.ts
2019-11-23 09:45:50 +01:00

208 lines
5.6 KiB
TypeScript

import { cmp } from "../../common/Tools";
import { MainUI } from "../../MainUI";
import { BaseView } from "../BaseView";
import { UIBackground } from "./UIBackground";
import { UIBuilder, UITextStyle } from "./UIBuilder";
import { UIButton } from "./UIButton";
import { UIContainer } from "./UIContainer";
import { UIImage } from "./UIImage";
import { IBounded, UITools } from "./UITools";
export type TooltipFiller = string | ((filler: TooltipBuilder) => string) | ((filler: TooltipBuilder) => boolean);
export class TooltipContainer extends UIContainer {
view: BaseView
background: UIBackground
content: UIContainer
item?: IBounded
border = 10
margin = 6
viewport: IBounded | null = null
constructor(view: BaseView) {
super(view);
this.view = view;
this.visible = false;
this.background = new UIBackground(view, this);
this.content = new UIContainer(view);
this.add(this.content);
this.view.tooltip_layer.add(this);
}
show(item: IBounded) {
this.item = item;
this.visible = true;
this.update();
}
tryPosition(viewport: IBounded, tooltip: IBounded): [number, number, number] {
let [x, y] = UITools.positionInside(tooltip, viewport);
let distance = Math.max(Math.abs(x - tooltip.x), Math.abs(y - tooltip.y));
if (this.view.isMouseInside({ x: x, y: y, width: tooltip.width, height: tooltip.height })) {
distance += 1000;
}
return [x, y, distance];
}
getBestPosition(item: IBounded, width: number, height: number): [number, number] {
let viewport = this.viewport || {
x: this.view.getX(0),
y: this.view.getY(0),
width: this.view.getX(1) - this.view.getX(0),
height: this.view.getY(1) - this.view.getY(0)
};
let candidates = [
this.tryPosition(viewport, { x: item.x + item.width / 2 - width / 2, y: item.y + item.height + this.margin, width: width, height: height }),
this.tryPosition(viewport, { x: item.x + item.width + this.margin, y: item.y + item.height / 2 - height / 2, width: width, height: height }),
this.tryPosition(viewport, { x: item.x + item.width / 2 - width / 2, y: item.y - height - this.margin, width: width, height: height }),
this.tryPosition(viewport, { x: item.x - width - this.margin, y: item.y + item.height / 2 - height / 2, width: width, height: height }),
]
candidates[0][2] -= 1; // preference to down tooltip on equality
let [x, y, distance] = candidates.sort((a, b) => cmp(a[2], b[2]))[0];
return [x, y];
}
update() {
if (this.visible && this.item) {
let [width, height] = this.background.drawAround(this.content, this.border);
let [x, y] = this.getBestPosition(this.item, width, height);
x += this.border;
y += this.border;
if (x != this.x || y != this.y) {
this.setPosition(x, y);
}
}
}
hide() {
this.content.removeAll();
this.background.clear();
this.visible = false;
}
}
/**
* Functions used to fill a tooltip content
*/
export class TooltipBuilder extends UIBuilder {
private content: TooltipContainer;
constructor(container: TooltipContainer) {
let style = new UITextStyle();
style.center = false;
style.vcenter = false;
style.shadow = true;
super(container.view, container.content, style);
this.content = container;
}
/**
* Configure the positioning and base style of the tooltip
*/
configure(border = 10, margin = 6, viewport: IBounded | null = null): void {
this.content.border = border;
this.content.margin = margin;
if (viewport) {
this.content.viewport = viewport;
}
}
}
/**
* Tooltip system, to display information on hover
*/
export class Tooltip {
readonly view: BaseView;
readonly container: TooltipContainer;
constructor(view: BaseView) {
this.view = view;
this.container = new TooltipContainer(view);
}
get ui(): MainUI {
return this.view.gameui;
}
/**
* Get a tooltip builder
*/
getBuilder(): TooltipBuilder {
return new TooltipBuilder(this.container);
}
/**
* Bind to an UI component
*
* When the component is hovered, the function is called to allow filling the tooltip container
*/
bind(obj: UIButton | UIImage, func: (filler: TooltipBuilder) => boolean): void {
this.view.inputs.setHoverClick(obj,
// enter
() => {
this.hide();
if (func(this.getBuilder())) {
this.container.show(UITools.getBounds(obj));
}
},
// leave
() => this.hide(),
// click
() => this.hide()
);
obj.on("pointerdown", () => this.hide());
}
/**
* Bind to an UI component to display a dynamic text
*/
bindDynamicText(obj: UIButton | UIImage, text_getter: () => string): void {
this.bind(obj, filler => {
let content = text_getter();
if (content) {
filler.text(content, 0, 0, { color: "#cccccc", size: 20 });
return true;
} else {
return false;
}
});
}
/**
* Bind to an UI component to display a simple text
*/
bindStaticText(obj: UIButton | UIImage, text: string): void {
this.bindDynamicText(obj, () => text);
}
/**
* Show a tooltip for a component
*/
show(obj: UIButton, content: TooltipFiller): void {
let builder = this.getBuilder();
let scontent = (typeof content == "string") ? content : content(builder);
if (typeof scontent == "string") {
builder.text(scontent, 0, 0, { color: "#cccccc", size: 20 });
}
if (scontent) {
this.container.show(UITools.getBounds(obj));
} else {
this.hide();
}
}
/**
* Hide the current tooltip
*/
hide(): void {
this.container.hide();
}
}