lazy-widgets
Version:
Typescript retained mode GUI for the HTML canvas API
163 lines • 6.67 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { watchArrayField } from '../decorators/FlagFields.js';
import { PointerEvent } from '../events/PointerEvent.js';
/**
* The base implementation of the {@link Viewport} interface. See
* {@link CanvasViewport} and {@link ClippedViewport}.
*
* @category Core
*/
export class BaseViewport {
constructor(child, relativeCoordinates) {
this.parent = null;
/**
* Should the layout be resolved, even if the child widget doesn't have a
* dirty layout?
*/
this.forceRelayout = true;
this.child = child;
this.relativeCoordinates = relativeCoordinates;
this.constraints = [0, Infinity, 0, Infinity];
this.rect = [0, 0, 0, 0];
this.offset = [0, 0];
}
/**
* Forces re-layout and calls {@link BaseViewport#updateChildPos}. Used as a
* callback for the {@link BaseViewport#rect} field watcher.
*/
relayoutAndReposition() {
this.forceRelayout = true;
this.updateChildPos();
}
/**
* Resolves the position of the child and finalizes its bounds. This
* effectively updates the position of the child in an out-of-order fashion
* (doesn't wait for the proper stage of the layout resolution). Used as a
* callback for the {@link BaseViewport#offset} field watcher.
*/
updateChildPos() {
if (!this.relativeCoordinates && this.child.attached) {
const [l, t, _w, _h] = this.rect;
const [ox, oy] = this.offset;
const newX = l + ox;
const newY = t + oy;
const [oldX, oldY] = this.child.position;
if (newX !== oldX || newY !== oldY) {
this.child.resolvePosition(newX, newY);
this.child.finalizeBounds();
}
}
}
/**
* Resolves the given child's layout by calling
* {@link Widget#resolveDimensions} with the current
* {@link Viewport#constraints}, {@link Widget#resolvePosition} and
* {@link Widget#finalizeBounds}.
*
* Handles both relative and absolute coordinates. The previous position is
* used.
*
* @returns Returns true if the child was resized, else, false.
*/
resolveLayout() {
if (!(this.child.layoutDirty || this.forceRelayout)) {
return false;
}
this.forceRelayout = false;
// Resolve child's layout
const [oldWidth, oldHeight] = this.child.dimensions;
this.child.resolveDimensions(...this.constraints);
if (this.relativeCoordinates) {
this.child.resolvePosition(0, 0);
}
else {
this.child.resolvePosition(...this.child.idealPosition);
}
this.child.finalizeBounds();
const [newWidth, newHeight] = this.child.dimensions;
return newWidth !== oldWidth || newHeight !== oldHeight;
}
dispatchTricklingEvent(event) {
// Drop event if it is a positional event with no target outside the
// child's viewport
if (event instanceof PointerEvent) {
const [cl, ct, cw, ch] = this.rect;
const cr = cl + cw;
const cb = ct + ch;
if (event.target === null) {
if (event.x < cl) {
return null;
}
if (event.x >= cr) {
return null;
}
if (event.y < ct) {
return null;
}
if (event.y >= cb) {
return null;
}
}
// Correct position of pointer event if this viewport has relative
// positions.
if (this.relativeCoordinates) {
const [ox, oy] = this.offset;
const x = cl + ox;
const y = ct + oy;
if (x !== 0 || y !== 0) {
event = event.correctOffset(x, y);
}
}
}
return this.child.dispatchEvent(event);
}
/**
* Get the rect of the child alongside more extra information,
* clipped/clamped to the bounds of the viewport. Usually only for internal,
* but can be used externally if you know what you're doing.
*/
getClippedViewportRect() {
// Calculate child's source and destination
const [vpX, vpY, vpW, vpH] = this.rect;
const [innerWidth, innerHeight] = this.child.dimensions;
const [xOffset, yOffset] = this.offset;
// viewport right and bottom
const vpR = vpX + vpW;
const vpB = vpY + vpH;
// original child destination left and top
const origXDst = vpX + xOffset;
const origYDst = vpY + yOffset;
// clipped child destination left, top, width and height
const xDst = Math.min(Math.max(origXDst, vpX), vpR);
const yDst = Math.min(Math.max(origYDst, vpY), vpB);
const wClipped = Math.min(Math.max(origXDst + innerWidth, vpX), vpR) - xDst;
const hClipped = Math.min(Math.max(origYDst + innerHeight, vpY), vpB) - yDst;
return [vpX, vpY, vpW, vpH, origXDst, origYDst, xDst, yDst, wClipped, hClipped];
}
}
/** Has the warning for dimensionless canvases been issued? */
BaseViewport.dimensionlessWarned = false;
/** Has the warning for non-power of 2 dimensions been issued? */
BaseViewport.powerOf2Warned = false;
/**
* The maximum retries allowed for
* {@link Viewport#resolveLayout | resolving the layout}. The first attempt
* is not counted. Only retries that exceed this limit are discarded; if
* maxRelayout is 4, then the 5th retry will be discarded.
*/
BaseViewport.maxRelayout = 4;
__decorate([
watchArrayField(BaseViewport.prototype.relayoutAndReposition)
], BaseViewport.prototype, "constraints", void 0);
__decorate([
watchArrayField(BaseViewport.prototype.relayoutAndReposition)
], BaseViewport.prototype, "rect", void 0);
__decorate([
watchArrayField(BaseViewport.prototype.updateChildPos)
], BaseViewport.prototype, "offset", void 0);
//# sourceMappingURL=BaseViewport.js.map