malwoden
Version:
   
372 lines • 13.3 kB
JavaScript
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Widget = void 0;
var Calc = require("../calc");
/**
* A Widget represents a reusable component able to draw to a terminal. These don't have to be used
* if you prefer to directly write to the terminal itself, but can simplify the process. The abstract class
* here contains basic shared methods related to Widgets, like adding/removing children, calculating
* position, etc.
*
* While Malwoden provides several widgets out of the box, this class can be easily extended to make
* custom widgets of all shapes and sizes.
*/
var Widget = /** @class */ (function () {
/**
* Creates a new Widget.
* @param config - WidgetConfig
*/
function Widget(config) {
var _a;
this.origin = { x: 0, y: 0 };
this.children = [];
this.disabled = false;
this.origin = (_a = config.origin) !== null && _a !== void 0 ? _a : { x: 0, y: 0 };
this.state = config.initialState;
this.absoluteOrigin = this.origin;
this.updateAbsoluteOrigin();
}
// ---------------------------------------------------------------------------
// Primary Getters/Setters
// ---------------------------------------------------------------------------
/**
* Sets the terminal for this widget and all children widgets. Will be passed
* to any children added in the future as well.
* @param terminal - Leave empty to clear
* @returns this
*/
Widget.prototype.setTerminal = function (terminal) {
this.terminal = terminal;
for (var _i = 0, _a = this.children; _i < _a.length; _i++) {
var c = _a[_i];
c.setTerminal(terminal);
}
return this;
};
Widget.prototype.registerMouseContext = function (mouseContext) {
this.clearMouseContext();
this.mouseRegistration = {
mouseContext: mouseContext,
mouseOnUp: this.cascadeMouseClick.bind(this),
mouseOnDown: this.cascadeMouseClick.bind(this),
};
mouseContext.onMouseUp(this.mouseRegistration.mouseOnUp);
mouseContext.onMouseDown(this.mouseRegistration.mouseOnDown);
return this;
};
Widget.prototype.clearMouseContext = function () {
if (this.mouseRegistration) {
this.mouseRegistration.mouseContext.clearMouseUp(this.mouseRegistration.mouseOnUp);
this.mouseRegistration.mouseContext.clearMouseDown(this.mouseRegistration.mouseOnDown);
}
this.mouseRegistration = undefined;
return this;
};
/**
* Sets the mouseHandler for this widget and all children widgets. Will be passed
* to any children added in the future as well.
* @param mouseHandler - Leave empty to clear
* @returns this
*/
Widget.prototype.setMouseHandler = function (mouseHandler) {
this.mouseHandler = mouseHandler;
for (var _i = 0, _a = this.children; _i < _a.length; _i++) {
var c = _a[_i];
c.setMouseHandler(mouseHandler);
}
return this;
};
/**
* Gets the current state of the widget. While not a copy, it's recommended to
* use setState rather than mutate this object.
* @returns
*/
Widget.prototype.getState = function () {
return this.state;
};
/**
* Sets the state of the widget. Partial values allowed.
* @param state Partial<S>
* @returns - The widget
*/
Widget.prototype.setState = function (state) {
Object.assign(this.state, state);
return this;
};
/**
* Sets the disabled state.
* @param disabled - Default true
* @returns - The Widget
*/
Widget.prototype.setDisabled = function (disabled) {
if (disabled === void 0) { disabled = true; }
this.disabled = disabled;
return this;
};
// ---------------------------------------------------------------------------
// Position Related
// ---------------------------------------------------------------------------
/**
* Sets the local origin of the widget relative to it's parent,
* then updates the absoluteOrigin of the widget
* @param origin Vector2 - The position relative to its parent
* @returns - The widget
*/
Widget.prototype.setOrigin = function (origin) {
this.origin = origin;
this.updateAbsoluteOrigin();
return this;
};
/**
* Gets the local origin. This will be relative to the parent's origin.
* @returns - Vector2
*/
Widget.prototype.getOrigin = function () {
return this.origin;
};
/**
* Whether or not the widget will update/draw
* @returns boolean
*/
Widget.prototype.isDisabled = function () {
return this.disabled;
};
/**
* Returns a Vector2 relative to true 0,0, which is usually the top left of the terminal.
* @returns - Vector2
*/
Widget.prototype.getAbsoluteOrigin = function () {
return this.absoluteOrigin;
};
/**
* Transforms the given local position to an absolute position.
* @param localPosition Vector2
* @returns Vector2
*/
Widget.prototype.localToAbsolute = function (localPosition) {
return Calc.Vector.add(this.absoluteOrigin, localPosition);
};
/**
* Transforms a given absolute position to one relative to the
* widget's local origin.
* @param absolutePosition Vector2
* @returns Vector2
*/
Widget.prototype.absoluteToLocal = function (absolutePosition) {
return Calc.Vector.subtract(absolutePosition, this.absoluteOrigin);
};
// ---------------------------------------------------------------------------
// Parent/Child Functions
// ---------------------------------------------------------------------------
/**
* Updates the absoluteOrigin position, called
* whenever a widget's local origin has moved.
*/
Widget.prototype.updateAbsoluteOrigin = function () {
if (this.parent === undefined) {
this.absoluteOrigin = __assign({}, this.origin);
}
else {
var parentAbs = this.parent.absoluteOrigin;
this.absoluteOrigin = Calc.Vector.add(parentAbs, this.origin);
}
for (var _i = 0, _a = this.children; _i < _a.length; _i++) {
var c = _a[_i];
c.updateAbsoluteOrigin();
}
};
/**
* Adds the widget to a parent.
* @param parent
* @returns this - The child widget
*/
Widget.prototype.setParent = function (parent) {
parent.addChild(this);
return this;
};
/**
* Adds a child widget to this widget. Removes any existing parent from the child first.
* @param child - The child widget
* @returns The child widget
*/
Widget.prototype.addChild = function (child) {
if (child.parent) {
child.parent.removeChild(child);
}
child.parent = this;
child.updateAbsoluteOrigin();
child.setTerminal(this.terminal);
child.setMouseHandler(this.mouseHandler);
this.children.push(child);
return child;
};
/**
* Removes a child widget from the parent widget. Clears inherited Terminal/MouseHandler/KeyboardHandler
* @param child The child widget
* @returns The child widget if found, undefined otherwise.
*/
Widget.prototype.removeChild = function (child) {
var newChildren = [];
var foundChild;
for (var _i = 0, _a = this.children; _i < _a.length; _i++) {
var c = _a[_i];
if (c !== child) {
newChildren.push(c);
}
else {
foundChild = c;
}
}
this.children = newChildren;
if (foundChild) {
foundChild.setTerminal(undefined);
foundChild.setMouseHandler(undefined);
}
return foundChild;
};
// ---------------------------------------------------------------------------
// Updating
// ---------------------------------------------------------------------------
/**
* Set a function to run whenever update or cascadeUpdate is called. Generally this is used
* with closures/currying to transform game state to function state.
* @param updateFunc - The function called on update
* @returns The widget
*/
Widget.prototype.setUpdateFunc = function (updateFunc) {
this.updateFunc = updateFunc;
return this;
};
/**
* Clears the function run on update.
* @returns The Widget
*/
Widget.prototype.clearUpdateFunc = function () {
this.updateFunc = undefined;
return this;
};
/**
* Calls update() for this widget and all children widgets recursively.
* Children added last will be called last. If a widget is disabled, the update
* will stop there.
*/
Widget.prototype.cascadeUpdate = function () {
if (this.isDisabled()) {
return;
}
if (this.updateFunc) {
var s = this.updateFunc();
Object.assign(this.state, s);
}
for (var _i = 0, _a = this.children; _i < _a.length; _i++) {
var c = _a[_i];
c.cascadeUpdate();
}
};
/**
* Calls an updateFunc if previously given, and merges it into the
* widgets state.
*/
Widget.prototype.update = function () {
if (this.isDisabled()) {
return;
}
if (!this.updateFunc)
return;
var s = this.updateFunc();
Object.assign(this.state, s);
};
// ---------------------------------------------------------------------------
// Drawing
// ---------------------------------------------------------------------------
/**
* Calls the draw() method of this widget and all children widgets recursively.
* If a widget is disabled it will stop the cascade.
*/
Widget.prototype.cascadeDraw = function () {
if (this.isDisabled())
return;
this.draw();
for (var _i = 0, _a = this.children; _i < _a.length; _i++) {
var c = _a[_i];
c.cascadeDraw();
}
};
/**
* Draws on to the given terminal using the widget's current state. Will not draw if disabled.
* @returns
*/
Widget.prototype.draw = function () {
if (this.isDisabled())
return;
this.onDraw();
};
// ---------------------------------------------------------------------------
// Clicking
// ---------------------------------------------------------------------------
/**
* Sends a mouse event through a tree of Widgets. If any widget's onMouseClick
* returns true, the event will be captured and stop cascading.
*
* Widgets are called in reverse order, so the last one to render will be
* the first one called for onMouseClick.
* @param mouse
*/
Widget.prototype.cascadeMouseClick = function (event) {
if (this.isDisabled())
return false;
for (var i = this.children.length - 1; i >= 0; i--) {
var c = this.children[i];
var captured = c.cascadeMouseClick(event);
if (captured)
return true;
}
return this.onMouseClick(event);
};
/**
* Calls onMouseClick if the widget is not disabled
* @param event - MouseHanderEvent
*/
Widget.prototype.mouseClick = function (event) {
if (this.isDisabled())
return false;
return this.onMouseClick(event);
};
/**
* Will be fired on click() or cascadeClick(). Return 'true' to capture the event, which
* will make it no longer cascade to other widgets. Return 'false' to pass the event to other widgets.
* @param absolutePosition Vector2 - The absolute position of the rendering terminal
* @returns boolean - Capture the event
*/
Widget.prototype.onMouseClick = function (mouse) {
return false;
};
// --------------------------------------------------------------------------
// Drawing
// --------------------------------------------------------------------------
/**
* Draws a glyph on the terminal using a position local to the widget.
* @param pos - A local position
* @param glyph - The glyph to draw
* @returns
*/
Widget.prototype.drawGlyph = function (pos, glyph) {
if (!this.terminal)
return;
this.terminal.drawGlyph({ x: this.absoluteOrigin.x + pos.x, y: this.absoluteOrigin.y + pos.y }, glyph);
};
return Widget;
}());
exports.Widget = Widget;
//# sourceMappingURL=widget.js.map