@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
229 lines (174 loc) • 5.86 kB
JavaScript
import dat from 'dat.gui'
import View from "../../view/View.js";
import { OptionGroup } from "./OptionGroup.js";
function isOption(c) {
return typeof c.read === "function";
}
/**
* DAT.GUI sets styles directly on the element internally, to prevent this - we use this hack that clears styles every frame while view is visible
* @param {View} view
*/
function clearGUIStyles(view) {
let clearStyleFlag = false;
function clearStyle() {
view.el.removeAttribute('style');
}
function runClearStyle() {
clearStyle();
if (clearStyleFlag) {
requestAnimationFrame(runClearStyle);
}
}
view.on.linked.add(function () {
clearStyleFlag = true;
runClearStyle();
});
view.on.unlinked.add(function () {
clearStyleFlag = false;
});
}
class OptionsView extends View {
/**
*
* @constructor
* @param {OptionGroup} options
* @param {Localization} localization
* @param {string[][]} [inclusions]
*/
constructor({ options, localization, inclusions }) {
super();
if (inclusions === undefined) {
//no inclusions specified, include everything
inclusions = [];
options.traverseOptions(op => {
inclusions.push(op.computePath());
});
}
const gui = new dat.GUI({
autoPlace: false,
resizable: false,
closed: false
});
/**
*
* @param {Option|OptionGroup} option
* @returns {String[]}
*/
function getPathFor(option) {
return option.computePath();
}
/**
*
* @param {Option|OptionGroup} option
*/
function getNameFor(option) {
const pathString = getPathFor(option).join('.');
const localizationKey = 'system_option.' + pathString;
return localization.getString(localizationKey);
}
const controls = new Map();
function updateLocalization() {
controls.forEach((control, option) => {
const optionName = getNameFor(option);
if (option.isOptionGroup) {
control.name = optionName;
} else {
control.name(optionName);
}
});
}
this.on.linked.add(updateLocalization);
this.bindSignal(localization.locale.onChanged, updateLocalization);
const self = this;
/**
*
* @param {Option} option
* @param rootFolder
*/
function makeOption(option, rootFolder) {
const op = {
v: null
};
let control;
Object.defineProperty(op, "v", {
get: function () {
return option.read();
},
set: function (v) {
option.write(v);
}
});
try {
control = rootFolder.add(op, "v", option.settings.values);
} catch (e) {
console.error("Failed to add option controller", option, e);
return;
}
if (typeof option.settings.min === "number") {
control = control.min(option.settings.min);
}
if (typeof option.settings.max === "number") {
control = control.max(option.settings.max);
}
control.name(getNameFor(option));
controls.set(option, control);
self.bindSignal(option.on.written, (v) => {
control.updateDisplay();
});
}
/**
*
* @param {Option|OptionGroup} op
*/
function isOptionIncluded(op) {
const n = inclusions.length;
const path = op.computePath();
const pathLength = path.length;
main: for (let i = 0; i < n; i++) {
const inclusion = inclusions[i];
const inclusionLength = inclusion.length;
if (pathLength > inclusionLength) {
continue;
}
for (let j = 0; j < pathLength; j++) {
const pathPart = path[j];
const inclusionPart = inclusion[j];
if (pathPart !== inclusionPart) {
continue main;
}
}
//path is inside the inclusion
return true;
}
return false;
}
/**
*
* @param {OptionGroup} group
* @param {GUI} rootFolder
*/
function makeGroup(group, rootFolder) {
group.children.forEach((c) => {
if (!isOptionIncluded(c)) {
return;
}
if (isOption(c)) {
makeOption(c, rootFolder);
} else {
const folder = rootFolder.addFolder(c.id);
folder.name = getNameFor(c);
controls.set(c, folder);
makeGroup(c, folder);
}
});
}
makeGroup(options, gui);
this.el = gui.domElement;
clearGUIStyles(this);
//update all controls on linkage
this.on.linked.add(() => {
controls.forEach(control => control.updateDisplay());
}, this);
}
}
export default OptionsView;