@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
246 lines (181 loc) • 5.55 kB
JavaScript
import { assert } from "../../core/assert.js";
import List from "../../core/collection/list/List.js";
import AABB2 from "../../core/geom/2d/aabb/AABB2.js";
import ConcurrentExecutor from "../../core/process/executor/ConcurrentExecutor.js";
import Task from "../../core/process/task/Task.js";
import { TaskSignal } from "../../core/process/task/TaskSignal.js";
import { frameThrottle } from "../../engine/graphics/FrameThrottle.js";
import { PointerDevice } from "../../engine/input/devices/PointerDevice.js";
import EmptyView from "../elements/EmptyView.js";
import { GMLEngine } from "./gml/GMLEngine.js";
import TooltipView from "./TooltipView.js";
export class TooltipManager {
constructor() {
/**
* @private
* @type {GMLEngine}
*/
this.gml = null;
/**
*
* @type {View|null}
*/
this.contextView = new EmptyView();
this.contextView.addClass('ui-tooltip-manager-context');
/**
*
* @type {PointerDevice}
*/
this.pointer = null;
this.updateTask = new Task({
name: "Update Tooltip Cursor",
cycleFunction: () => {
const position = this.pointer.position;
this.update(position.x, position.y);
return TaskSignal.Yield;
}
});
this.executor = new ConcurrentExecutor(500, 0.00001);
/**
*
* @type {List<VisualTip>}
*/
this.tips = new List();
}
/**
*
* @returns {GMLEngine}
*/
getGML() {
return this.gml;
}
/**
*
* @param {GMLEngine} gml
* @param {PointerDevice} pointer
*/
initialize(gml, pointer) {
this.gml = gml;
/**
*
* @type {PointerDevice}
*/
this.pointer = pointer;
this.signalBindings = [];
}
startup() {
return new Promise((resolve, reject) => {
if (this.contextView === null) {
throw new Error('ContextView not set; probably not initialized');
}
this.signalBindings.forEach(b => b.link());
this.executor.run(this.updateTask);
this.gml.startup().then(resolve, reject);
});
}
shutdown() {
const self = this;
return new Promise(function (resolve, reject) {
self.signalBindings.forEach(b => b.unlink());
self.executor.removeTask(self.updateTask);
resolve();
});
}
update() {
//TODO do update stuff
/*
this.tips.forEach(t => {
this.positionTip(t.view);
});
*/
}
/**
* @private
* @returns {TooltipView}
* @param {VisualTip} tip
*/
buildTipView(tip) {
const gml = this.getGML();
gml.pushState();
gml.setTooltipsEnabled(false);
//build tip content
const factoryProduct = tip.factory.call(tip.factoryContext);
gml.popState();
assert.defined(factoryProduct, "factory product");
assert.notNull(factoryProduct, "factory product");
let contentView;
if (typeof factoryProduct === "object") {
//expecting factory product to be a view, passing it directly
contentView = factoryProduct;
} else {
//treat as a normal string, compile
const code = factoryProduct.toString();
//build content view
contentView = this.gml.compile(code);
}
const view = new TooltipView(tip, contentView);
tip.view = view;
const layout = frameThrottle(() => {
this.positionTip(view);
});
view.bindSignal(tip.target.position.onChanged, layout);
view.bindSignal(tip.target.size.onChanged, layout);
return view;
}
/**
*
* @param {VisualTip} tip
*/
add(tip) {
assert.defined(tip, 'tip');
assert.notNull(tip, 'tip');
const contextView = this.contextView;
//build tip view
const tooltipView = this.buildTipView(tip);
//make transparent
tooltipView.css({
opacity: 0
});
requestAnimationFrame(() => {
this.positionTip(tooltipView);
tooltipView.css({
opacity: 1
});
});
contextView.addChild(tooltipView);
//remember tip
this.tips.add(tip);
}
/**
*
* @param {VisualTip} tip
* @returns {boolean}
*/
contains(tip) {
return this.tips.contains(tip);
}
/**
*
* @param {VisualTip} tip
*/
remove(tip) {
const removed = this.tips.removeOneOf(tip);
if (removed) {
this.contextView.removeChild(tip.view);
//clear out the view
tip.view = null;
}
}
/**
*
* @param {TooltipView} tipView
*/
positionTip(tipView) {
if (tipView === null) {
return;
}
const contextView = this.contextView;
const bounds = new AABB2(0, 0, contextView.size.x, contextView.size.y);
tipView.layout(bounds);
}
}