Source: framework/components/scrollbar/component.js

Object.assign(pc, function () {
    /**
     * @private
     * @component
     * @name pc.ScrollbarComponent
     * @description Create a new ScrollbarComponent
     * @classdesc A ScrollbarComponent enables a group of entities to behave like a draggable scrollbar.
     * @param {pc.ScrollbarComponentSystem} system The ComponentSystem that created this Component
     * @param {pc.Entity} entity The Entity that this Component is attached to.
     * @extends pc.Component
     * @property {pc.ORIENTATION} orientation Whether the scrollbar moves horizontally or vertically.
     * @property {Number} value The current position value of the scrollbar, in the range 0...1.
     * @property {Number} handleSize The size of the handle relative to the size of the track, in the range 0...1. For a vertical scrollbar, a value of 1 means that the handle will take up the full height of the track.
     * @property {pc.Entity} handleEntity The entity to be used as the scrollbar handle. This entity must have a Scrollbar component.
     */
    var ScrollbarComponent = function ScrollbarComponent(system, entity) {
        pc.Component.call(this, system, entity);

        this._app = system.app;

        this._handleReference = new pc.EntityReference(this, 'handleEntity', {
            'element#gain': this._onHandleElementGain,
            'element#lose': this._onHandleElementLose,
            'element#set:anchor': this._onSetHandleAlignment,
            'element#set:margin': this._onSetHandleAlignment,
            'element#set:pivot': this._onSetHandleAlignment
        });

        this._toggleLifecycleListeners('on');
    };
    ScrollbarComponent.prototype = Object.create(pc.Component.prototype);
    ScrollbarComponent.prototype.constructor = ScrollbarComponent;

    Object.assign(ScrollbarComponent.prototype, {
        _toggleLifecycleListeners: function (onOrOff) {
            this[onOrOff]('set_value', this._onSetValue, this);
            this[onOrOff]('set_handleSize', this._onSetHandleSize, this);
            this[onOrOff]('set_orientation', this._onSetOrientation, this);

            // TODO Handle scrollwheel events
        },

        _onHandleElementGain: function () {
            this._destroyDragHelper();
            this._handleDragHelper = new pc.ElementDragHelper(this._handleReference.entity.element, this._getAxis());
            this._handleDragHelper.on('drag:move', this._onHandleDrag, this);

            this._updateHandlePositionAndSize();
        },

        _onHandleElementLose: function () {
            this._destroyDragHelper();
        },

        _onHandleDrag: function (position) {
            if (this._handleReference.entity && this.enabled && this.entity.enabled) {
                this.value = this._handlePositionToScrollValue(position[this._getAxis()]);
            }
        },

        _onSetValue: function (name, oldValue, newValue) {
            if (Math.abs(newValue - oldValue) > 1e-5) {
                this.data.value = pc.math.clamp(newValue, 0, 1);
                this._updateHandlePositionAndSize();
                this.fire('set:value', this.data.value);
            }
        },

        _onSetHandleSize: function (name, oldValue, newValue) {
            if (Math.abs(newValue - oldValue) > 1e-5) {
                this.data.handleSize = pc.math.clamp(newValue, 0, 1);
                this._updateHandlePositionAndSize();
            }
        },

        _onSetHandleAlignment: function () {
            this._updateHandlePositionAndSize();
        },

        _onSetOrientation: function (name, oldValue, newValue) {
            if (newValue !== oldValue && this._handleReference.hasComponent('element')) {
                this._handleReference.entity.element[this._getOppositeDimension()] = 0;
            }
        },

        _updateHandlePositionAndSize: function () {
            var handleEntity = this._handleReference.entity;
            var handleElement = handleEntity && handleEntity.element;

            if (handleEntity) {
                var position = handleEntity.getLocalPosition();
                position[this._getAxis()] = this._getHandlePosition();
                this._handleReference.entity.setLocalPosition(position);
            }

            if (handleElement) {
                handleElement[this._getDimension()] = this._getHandleLength();
            }
        },

        _handlePositionToScrollValue: function (handlePosition) {
            return handlePosition * this._getSign() / this._getUsableTrackLength();
        },

        _scrollValueToHandlePosition: function (value) {
            return value * this._getSign() * this._getUsableTrackLength();
        },

        _getUsableTrackLength: function () {
            return Math.max(this._getTrackLength() - this._getHandleLength(), 0.001);
        },

        _getTrackLength: function () {
            if (this.entity.element) {
                return this.orientation === pc.ORIENTATION_HORIZONTAL ? this.entity.element.calculatedWidth : this.entity.element.calculatedHeight;
            }

            return 0;
        },

        _getHandleLength: function () {
            return this._getTrackLength() * this.handleSize;
        },

        _getHandlePosition: function () {
            return this._scrollValueToHandlePosition(this.value);
        },

        _getSign: function () {
            return this.orientation === pc.ORIENTATION_HORIZONTAL ? 1 : -1;
        },

        _getAxis: function () {
            return this.orientation === pc.ORIENTATION_HORIZONTAL ? 'x' : 'y';
        },

        _getDimension: function () {
            return this.orientation === pc.ORIENTATION_HORIZONTAL ? 'width' : 'height';
        },

        _getOppositeDimension: function () {
            return this.orientation === pc.ORIENTATION_HORIZONTAL ? 'height' : 'width';
        },

        _destroyDragHelper: function () {
            if (this._handleDragHelper) {
                this._handleDragHelper.destroy();
            }
        },

        _setHandleDraggingEnabled: function (enabled) {
            if (this._handleDragHelper) {
                this._handleDragHelper.enabled = enabled;
            }
        },

        onEnable: function () {
            this._handleReference.onParentComponentEnable();
            this._setHandleDraggingEnabled(true);
        },

        onDisable: function () {
            this._setHandleDraggingEnabled(false);
        },

        onRemove: function () {
            this._destroyDragHelper();
            this._toggleLifecycleListeners('off');
        }
    });

    return {
        ScrollbarComponent: ScrollbarComponent
    };
}());

/**
 * @event
 * @private
 * @name pc.ScrollbarComponent#set:value
 * @description Fired whenever the scroll value changes.
 * @param {Number} value The current scroll value.
 */