UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

246 lines (181 loc) • 5.55 kB
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); } }