UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

270 lines (269 loc) 8.36 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); import { math } from "../../../core/math/math.js"; import { ORIENTATION_HORIZONTAL } from "../../../scene/constants.js"; import { GraphNode } from "../../../scene/graph-node.js"; import { Component } from "../component.js"; import { ElementDragHelper } from "../element/element-drag-helper.js"; class ScrollbarComponent extends Component { constructor() { super(...arguments); /** @private */ __publicField(this, "_orientation", ORIENTATION_HORIZONTAL); /** @private */ __publicField(this, "_value", 0); /** @private */ __publicField(this, "_handleSize", 0); /** * @type {Entity|null} * @private */ __publicField(this, "_handleEntity", null); /** * @type {EventHandle|null} * @private */ __publicField(this, "_evtHandleEntityElementAdd", null); /** * @type {EventHandle[]} * @private */ __publicField(this, "_evtHandleEntityChanges", []); /** * @type {ElementDragHelper|null} * @private */ __publicField(this, "_handleDragHelper", null); } /** * Sets whether the scrollbar moves horizontally or vertically. Can be: * * - {@link ORIENTATION_HORIZONTAL}: The scrollbar animates in the horizontal axis. * - {@link ORIENTATION_VERTICAL}: The scrollbar animates in the vertical axis. * * Defaults to {@link ORIENTATION_HORIZONTAL}. * * @type {number} */ set orientation(arg) { if (this._orientation === arg) { return; } this._orientation = arg; if (this._handleEntity?.element) { this._handleEntity.element[this._getOppositeDimension()] = 0; this._rebuildDragHelper(); this._updateHandlePositionAndSize(); } } /** * Gets whether the scrollbar moves horizontally or vertically. * * @type {number} */ get orientation() { return this._orientation; } /** * Sets the current position value of the scrollbar, in the range 0 to 1. Defaults to 0. * * @type {number} */ set value(arg) { if (Math.abs(arg - this._value) > 1e-5) { this._value = math.clamp(arg, 0, 1); this._updateHandlePositionAndSize(); this.fire("set:value", this._value); } } /** * Gets the current position value of the scrollbar. * * @type {number} */ get value() { return this._value; } /** * Sets the size of the handle relative to the size of the track, in the range 0 to 1. For a * vertical scrollbar, a value of 1 means that the handle will take up the full height of the * track. * * @type {number} */ set handleSize(arg) { if (Math.abs(arg - this._handleSize) > 1e-5) { this._handleSize = math.clamp(arg, 0, 1); this._updateHandlePositionAndSize(); } } /** * Gets the size of the handle relative to the size of the track. * * @type {number} */ get handleSize() { return this._handleSize; } /** * Sets the entity to be used as the scrollbar handle. This entity must have an * {@link ElementComponent} (with `useInput: true` for the handle to be draggable). * * @type {Entity|string|null} */ set handleEntity(arg) { let newEntity; if (arg instanceof GraphNode) { newEntity = arg; } else if (typeof arg === "string") { newEntity = this.system.app.getEntityFromIndex(arg) ?? null; } else { newEntity = null; } if (this._handleEntity === newEntity) { return; } if (this._handleEntity) { this._handleEntityUnsubscribe(); } this._handleEntity = newEntity; if (newEntity) { this._handleEntitySubscribe(); } } /** * Gets the entity to be used as the scrollbar handle. * * @type {Entity|null} */ get handleEntity() { return this._handleEntity; } _handleEntitySubscribe() { this._evtHandleEntityElementAdd = this._handleEntity.on("element:add", this._onHandleElementGain, this); if (this._handleEntity.element) { this._onHandleElementGain(); } } _handleEntityUnsubscribe() { this._evtHandleEntityElementAdd?.off(); this._evtHandleEntityElementAdd = null; if (this._handleEntity?.element) { this._onHandleElementLose(); } } _handleEntityElementSubscribe() { const element = this._handleEntity.element; const handles = this._evtHandleEntityChanges; handles.push(element.once("beforeremove", this._onHandleElementLose, this)); handles.push(element.on("set:anchor", this._updateHandlePositionAndSize, this)); handles.push(element.on("set:margin", this._updateHandlePositionAndSize, this)); handles.push(element.on("set:pivot", this._updateHandlePositionAndSize, this)); } _handleEntityElementUnsubscribe() { for (let i = 0; i < this._evtHandleEntityChanges.length; i++) { this._evtHandleEntityChanges[i].off(); } this._evtHandleEntityChanges.length = 0; } _onHandleElementGain() { this._handleEntityElementSubscribe(); this._rebuildDragHelper(); this._updateHandlePositionAndSize(); } _rebuildDragHelper() { this._destroyDragHelper(); this._handleDragHelper = new ElementDragHelper(this._handleEntity.element, this._getAxis()); this._handleDragHelper.enabled = this.enabled && this.entity.enabled; this._handleDragHelper.on("drag:move", this._onHandleDrag, this); } _onHandleElementLose() { this._handleEntityElementUnsubscribe(); this._destroyDragHelper(); } _onHandleDrag(position) { if (this._handleEntity && this.enabled && this.entity.enabled) { this.value = this._handlePositionToScrollValue(position[this._getAxis()]); } } _updateHandlePositionAndSize() { const handleEntity = this._handleEntity; if (!handleEntity) return; const position = handleEntity.getLocalPosition(); position[this._getAxis()] = this._getHandlePosition(); handleEntity.setLocalPosition(position); if (handleEntity.element) { handleEntity.element[this._getDimension()] = this._getHandleLength(); } } _handlePositionToScrollValue(handlePosition) { return handlePosition * this._getSign() / this._getUsableTrackLength(); } _scrollValueToHandlePosition(value) { return value * this._getSign() * this._getUsableTrackLength(); } _getUsableTrackLength() { return Math.max(this._getTrackLength() - this._getHandleLength(), 1e-3); } _getTrackLength() { if (this.entity.element) { return this._orientation === ORIENTATION_HORIZONTAL ? this.entity.element.calculatedWidth : this.entity.element.calculatedHeight; } return 0; } _getHandleLength() { return this._getTrackLength() * this._handleSize; } _getHandlePosition() { return this._scrollValueToHandlePosition(this._value); } _getSign() { return this._orientation === ORIENTATION_HORIZONTAL ? 1 : -1; } _getAxis() { return this._orientation === ORIENTATION_HORIZONTAL ? "x" : "y"; } _getDimension() { return this._orientation === ORIENTATION_HORIZONTAL ? "width" : "height"; } _getOppositeDimension() { return this._orientation === ORIENTATION_HORIZONTAL ? "height" : "width"; } _destroyDragHelper() { this._handleDragHelper?.destroy(); this._handleDragHelper = null; } onEnable() { if (this._handleDragHelper) { this._handleDragHelper.enabled = true; } } onDisable() { if (this._handleDragHelper) { this._handleDragHelper.enabled = false; } } onRemove() { this._destroyDragHelper(); } resolveDuplicatedEntityReferenceProperties(oldScrollbar, duplicatedIdsMap) { if (oldScrollbar.handleEntity) { this.handleEntity = duplicatedIdsMap[oldScrollbar.handleEntity.guid]; } } } /** * Fired whenever the scroll value changes. The handler is passed a number representing the * current scroll value. * * @event * @example * entity.scrollbar.on('set:value', (value) => { * console.log(`Scroll value is now ${value}`); * }); */ __publicField(ScrollbarComponent, "EVENT_SETVALUE", "set:value"); export { ScrollbarComponent };