iwmlib/lib/pixi/scrollbox.js

612 lines
20 KiB
JavaScript
Raw Normal View History

2019-05-14 13:56:05 +02:00
/**
* pixi.js scrollbox: a masked content box that can scroll vertically or horizontally with scrollbars
*/
export default class Scrollbox extends PIXI.Container {
/**
* create a scrollbox
* @param {object} options
* @param {boolean} [options.dragScroll=true] user may drag the content area to scroll content
* @param {string} [options.overflowX=auto] (none, scroll, hidden, auto) this changes whether the scrollbar is shown
* @param {string} [options.overflowY=auto] (none, scroll, hidden, auto) this changes whether the scrollbar is shown
* @param {string} [options.overflow] (none, scroll, hidden, auto) sets overflowX and overflowY to this value
* @param {number} [options.boxWidth=100] width of scrollbox including scrollbar (in pixels)
* @param {number} [options.boxHeight=100] height of scrollbox including scrollbar (in pixels)
* @param {number} [options.scrollbarSize=10] size of scrollbar (in pixels)
* @param {number} [options.scrollbarOffsetHorizontal=0] offset of horizontal scrollbar (in pixels)
* @param {number} [options.scrollbarOffsetVertical=0] offset of vertical scrollbar (in pixels)
* @param {boolean} [options.stopPropagation=true] call stopPropagation on any events that impact scrollbox
* @param {number} [options.scrollbarBackground=0xdddddd] background color of scrollbar
* @param {number} [options.scrollbarBackgroundAlpha=1] alpha of background of scrollbar
* @param {number} [options.scrollbarForeground=0x888888] foreground color of scrollbar
* @param {number} [options.scrollbarForegroundAlpha=1] alpha of foreground of scrollbar
* @param {string} [options.underflow=top-left] what to do when content underflows the scrollbox size: none: do nothing; (left/right/center AND top/bottom/center); OR center (e.g., 'top-left', 'center', 'none', 'bottomright')
* @param {(boolean|number)} [options.fade] fade the scrollbar when not in use (true = 1000ms)
* @param {number} [options.fadeWait=3000] time to wait before fading the scrollbar if options.fade is set
* @param {(string|function)} [options.fadeEase=easeInOutSine] easing function to use for fading
*/
2019-07-18 12:26:39 +02:00
constructor(options) {
2019-05-14 13:56:05 +02:00
super()
2019-07-18 12:26:39 +02:00
this.options = Object.assign(
{},
{
boxWidth: 100,
boxHeight: 100,
scrollbarSize: 10,
scrollbarBackground: 14540253,
scrollbarBackgroundAlpha: 1,
scrollbarForeground: 8947848,
scrollbarForegroundAlpha: 1,
dragScroll: true,
stopPropagation: true,
scrollbarOffsetHorizontal: 0,
scrollbarOffsetVertical: 0,
underflow: 'top-left',
fadeScrollbar: false,
fadeWait: 3000,
fadeEase: 'easeInOutSine'
},
options
)
2019-05-14 13:56:05 +02:00
this.ease = new PIXI.extras.Ease.list()
2019-05-14 14:13:37 +02:00
this.on('added', event => {
this.update()
})
2019-05-14 13:56:05 +02:00
/**
* content in placed in here
* you can use any function from pixi-viewport on content to manually move the content (see https://davidfig.github.io/pixi-viewport/jsdoc/)
* @type {PIXI.extras.Viewport}
*/
2019-07-18 12:26:39 +02:00
this.content = this.addChild(
new PIXI.extras.Viewport({
passiveWheel: this.options.stopPropagation,
stopPropagation: this.options.stopPropagation,
screenWidth: this.options.boxWidth,
screenHeight: this.options.boxHeight
})
)
this.content.decelerate().on('moved', () => this._drawScrollbars())
2019-05-14 13:56:05 +02:00
/**
* graphics element for drawing the scrollbars
* @type {PIXI.Graphics}
*/
this.scrollbar = this.addChild(new PIXI.Graphics())
this.scrollbar.interactive = true
this.scrollbar.on('pointerdown', this.scrollbarDown, this)
this.interactive = true
this.on('pointermove', this.scrollbarMove, this)
this.on('pointerup', this.scrollbarUp, this)
this.on('pointercancel', this.scrollbarUp, this)
this.on('pointerupoutside', this.scrollbarUp, this)
this._maskContent = this.addChild(new PIXI.Graphics())
this.update()
}
/**
* offset of horizontal scrollbar (in pixels)
* @type {number}
*/
2019-07-18 12:26:39 +02:00
get scrollbarOffsetHorizontal() {
2019-05-14 13:56:05 +02:00
return this.options.scrollbarOffsetHorizontal
}
2019-07-18 12:26:39 +02:00
set scrollbarOffsetHorizontal(value) {
2019-05-14 13:56:05 +02:00
this.options.scrollbarOffsetHorizontal = value
}
/**
* offset of vertical scrollbar (in pixels)
* @type {number}
*/
2019-07-18 12:26:39 +02:00
get scrollbarOffsetVertical() {
2019-05-14 13:56:05 +02:00
return this.options.scrollbarOffsetVertical
}
2019-07-18 12:26:39 +02:00
set scrollbarOffsetVertical(value) {
2019-05-14 13:56:05 +02:00
this.options.scrollbarOffsetVertical = value
}
/**
* disable the scrollbox (if set to true this will also remove the mask)
* @type {boolean}
*/
2019-07-18 12:26:39 +02:00
get disable() {
2019-05-14 13:56:05 +02:00
return this._disabled
}
2019-07-18 12:26:39 +02:00
set disable(value) {
if (this._disabled !== value) {
2019-05-14 13:56:05 +02:00
this._disabled = value
this.update()
}
}
/**
* call stopPropagation on any events that impact scrollbox
* @type {boolean}
*/
2019-07-18 12:26:39 +02:00
get stopPropagation() {
2019-05-14 13:56:05 +02:00
return this.options.stopPropagation
}
2019-07-18 12:26:39 +02:00
set stopPropagation(value) {
2019-05-14 13:56:05 +02:00
this.options.stopPropagation = value
}
/**
* user may drag the content area to scroll content
* @type {boolean}
*/
2019-07-18 12:26:39 +02:00
get dragScroll() {
2019-05-14 13:56:05 +02:00
return this.options.dragScroll
}
2019-07-18 12:26:39 +02:00
set dragScroll(value) {
2019-05-14 13:56:05 +02:00
this.options.dragScroll = value
2019-07-18 12:26:39 +02:00
if (value) {
2019-05-14 13:56:05 +02:00
this.content.drag()
2019-07-18 12:26:39 +02:00
} else {
2019-05-14 13:56:05 +02:00
this.content.removePlugin('drag')
}
this.update()
}
/**
* width of scrollbox including the scrollbar (if visible)- this changes the size and not the scale of the box
* @type {number}
*/
2019-07-18 12:26:39 +02:00
get boxWidth() {
2019-05-14 13:56:05 +02:00
return this.options.boxWidth
}
2019-07-18 12:26:39 +02:00
set boxWidth(value) {
2019-05-14 13:56:05 +02:00
this.options.boxWidth = value
this.content.screenWidth = value
this.update()
}
/**
* sets overflowX and overflowY to (scroll, hidden, auto) changing whether the scrollbar is shown
* scroll = always show scrollbar
* hidden = hide overflow and do not show scrollbar
* auto = if content is larger than box size, then show scrollbar
* @type {string}
*/
2019-07-18 12:26:39 +02:00
get overflow() {
2019-05-14 13:56:05 +02:00
return this.options.overflow
}
2019-07-18 12:26:39 +02:00
set overflow(value) {
2019-05-14 13:56:05 +02:00
this.options.overflow = value
this.options.overflowX = value
this.options.overflowY = value
this.update()
}
/**
* sets overflowX to (scroll, hidden, auto) changing whether the scrollbar is shown
* scroll = always show scrollbar
* hidden = hide overflow and do not show scrollbar
* auto = if content is larger than box size, then show scrollbar
* @type {string}
*/
2019-07-18 12:26:39 +02:00
get overflowX() {
2019-05-14 13:56:05 +02:00
return this.options.overflowX
}
2019-07-18 12:26:39 +02:00
set overflowX(value) {
2019-05-14 13:56:05 +02:00
this.options.overflowX = value
this.update()
}
/**
* sets overflowY to (scroll, hidden, auto) changing whether the scrollbar is shown
* scroll = always show scrollbar
* hidden = hide overflow and do not show scrollbar
* auto = if content is larger than box size, then show scrollbar
* @type {string}
*/
2019-07-18 12:26:39 +02:00
get overflowY() {
2019-05-14 13:56:05 +02:00
return this.options.overflowY
}
2019-07-18 12:26:39 +02:00
set overflowY(value) {
2019-05-14 13:56:05 +02:00
this.options.overflowY = value
this.update()
}
/**
* height of scrollbox including the scrollbar (if visible) - this changes the size and not the scale of the box
* @type {number}
*/
2019-07-18 12:26:39 +02:00
get boxHeight() {
2019-05-14 13:56:05 +02:00
return this.options.boxHeight
}
2019-07-18 12:26:39 +02:00
set boxHeight(value) {
2019-05-14 13:56:05 +02:00
this.options.boxHeight = value
this.content.screenHeight = value
this.update()
}
/**
* scrollbar size in pixels
* @type {number}
*/
2019-07-18 12:26:39 +02:00
get scrollbarSize() {
2019-05-14 13:56:05 +02:00
return this.options.scrollbarSize
}
2019-07-18 12:26:39 +02:00
set scrollbarSize(value) {
2019-05-14 13:56:05 +02:00
this.options.scrollbarSize = value
}
/**
* width of scrollbox less the scrollbar (if visible)
* @type {number}
* @readonly
*/
2019-07-18 12:26:39 +02:00
get contentWidth() {
return (
this.options.boxWidth -
(this.isScrollbarVertical ? this.options.scrollbarSize : 0)
)
2019-05-14 13:56:05 +02:00
}
/**
* height of scrollbox less the scrollbar (if visible)
* @type {number}
* @readonly
*/
2019-07-18 12:26:39 +02:00
get contentHeight() {
return (
this.options.boxHeight -
(this.isScrollbarHorizontal ? this.options.scrollbarSize : 0)
)
2019-05-14 13:56:05 +02:00
}
/**
* is the vertical scrollbar visible
* @type {boolean}
* @readonly
*/
2019-07-18 12:26:39 +02:00
get isScrollbarVertical() {
2019-05-14 13:56:05 +02:00
return this._isScrollbarVertical
}
/**
* is the horizontal scrollbar visible
* @type {boolean}
* @readonly
*/
2019-07-18 12:26:39 +02:00
get isScrollbarHorizontal() {
2019-05-14 13:56:05 +02:00
return this._isScrollbarHorizontal
}
/**
* top coordinate of scrollbar
*/
2019-07-18 12:26:39 +02:00
get scrollTop() {
2019-05-14 13:56:05 +02:00
return this.content.top
}
/**
* left coordinate of scrollbar
*/
2019-07-18 12:26:39 +02:00
get scrollLeft() {
2019-05-14 13:56:05 +02:00
return this.content.left
}
/**
* width of content area
* if not set then it uses content.width to calculate width
*/
2019-07-18 12:26:39 +02:00
get scrollWidth() {
2019-05-14 13:56:05 +02:00
return this._scrollWidth || this.content.width
}
2019-07-18 12:26:39 +02:00
set scrollWidth(value) {
2019-05-14 13:56:05 +02:00
this._scrollWidth = value
}
/**
* height of content area
* if not set then it uses content.height to calculate height
*/
2019-07-18 12:26:39 +02:00
get scrollHeight() {
2019-05-14 13:56:05 +02:00
return this._scrollHeight || this.content.height
}
2019-07-18 12:26:39 +02:00
set scrollHeight(value) {
2019-05-14 13:56:05 +02:00
this._scrollHeight = value
}
/**
* draws scrollbars
* @private
*/
2019-07-18 12:26:39 +02:00
_drawScrollbars() {
this._isScrollbarHorizontal =
this.overflowX === 'scroll'
? true
: ['hidden', 'none'].indexOf(this.overflowX) !== -1
? false
: this.scrollWidth > this.options.boxWidth
this._isScrollbarVertical =
this.overflowY === 'scroll'
? true
: ['hidden', 'none'].indexOf(this.overflowY) !== -1
? false
: this.scrollHeight > this.options.boxHeight
2019-05-14 13:56:05 +02:00
this.scrollbar.clear()
let options = {}
options.left = 0
2019-07-18 12:26:39 +02:00
options.right =
this.scrollWidth +
(this._isScrollbarVertical ? this.options.scrollbarSize : 0)
2019-05-14 13:56:05 +02:00
options.top = 0
2019-07-18 12:26:39 +02:00
options.bottom =
this.scrollHeight +
(this.isScrollbarHorizontal ? this.options.scrollbarSize : 0)
const width =
this.scrollWidth +
(this.isScrollbarVertical ? this.options.scrollbarSize : 0)
const height =
this.scrollHeight +
(this.isScrollbarHorizontal ? this.options.scrollbarSize : 0)
2019-05-14 13:56:05 +02:00
this.scrollbarTop = (this.content.top / height) * this.boxHeight
this.scrollbarTop = this.scrollbarTop < 0 ? 0 : this.scrollbarTop
this.scrollbarHeight = (this.boxHeight / height) * this.boxHeight
2019-07-18 12:26:39 +02:00
this.scrollbarHeight =
this.scrollbarTop + this.scrollbarHeight > this.boxHeight
? this.boxHeight - this.scrollbarTop
: this.scrollbarHeight
2019-05-14 13:56:05 +02:00
this.scrollbarLeft = (this.content.left / width) * this.boxWidth
this.scrollbarLeft = this.scrollbarLeft < 0 ? 0 : this.scrollbarLeft
this.scrollbarWidth = (this.boxWidth / width) * this.boxWidth
2019-07-18 12:26:39 +02:00
this.scrollbarWidth =
this.scrollbarWidth + this.scrollbarLeft > this.boxWidth
? this.boxWidth - this.scrollbarLeft
: this.scrollbarWidth
if (this.isScrollbarVertical) {
2019-05-14 13:56:05 +02:00
this.scrollbar
2019-07-18 12:26:39 +02:00
.beginFill(
this.options.scrollbarBackground,
this.options.scrollbarBackgroundAlpha
)
.drawRect(
this.boxWidth -
this.scrollbarSize +
this.options.scrollbarOffsetVertical,
0,
this.scrollbarSize,
this.boxHeight
)
2019-05-14 13:56:05 +02:00
.endFill()
}
2019-07-18 12:26:39 +02:00
if (this.isScrollbarHorizontal) {
2019-05-14 13:56:05 +02:00
this.scrollbar
2019-07-18 12:26:39 +02:00
.beginFill(
this.options.scrollbarBackground,
this.options.scrollbarBackgroundAlpha
)
.drawRect(
0,
this.boxHeight -
this.scrollbarSize +
this.options.scrollbarOffsetHorizontal,
this.boxWidth,
this.scrollbarSize
)
2019-05-14 13:56:05 +02:00
.endFill()
}
2019-07-18 12:26:39 +02:00
if (this.isScrollbarVertical) {
2019-05-14 13:56:05 +02:00
this.scrollbar
2019-07-18 12:26:39 +02:00
.beginFill(
this.options.scrollbarForeground,
this.options.scrollbarForegroundAlpha
)
.drawRect(
this.boxWidth -
this.scrollbarSize +
this.options.scrollbarOffsetVertical,
this.scrollbarTop,
this.scrollbarSize,
this.scrollbarHeight
)
2019-05-14 13:56:05 +02:00
.endFill()
}
2019-07-18 12:26:39 +02:00
if (this.isScrollbarHorizontal) {
2019-05-14 13:56:05 +02:00
this.scrollbar
2019-07-18 12:26:39 +02:00
.beginFill(
this.options.scrollbarForeground,
this.options.scrollbarForegroundAlpha
)
.drawRect(
this.scrollbarLeft,
this.boxHeight -
this.scrollbarSize +
this.options.scrollbarOffsetHorizontal,
this.scrollbarWidth,
this.scrollbarSize
)
2019-05-14 13:56:05 +02:00
.endFill()
}
// this.content.forceHitArea = new PIXI.Rectangle(0, 0 , this.boxWidth, this.boxHeight)
this.activateFade()
}
/**
* draws mask layer
* @private
*/
2019-07-18 12:26:39 +02:00
_drawMask() {
2019-05-14 13:56:05 +02:00
this._maskContent
.beginFill(0)
.drawRect(0, 0, this.boxWidth, this.boxHeight)
.endFill()
this.content.mask = this._maskContent
}
/**
* call when scrollbox content changes
*/
2019-07-18 12:26:39 +02:00
update() {
2019-05-14 13:56:05 +02:00
this.content.mask = null
this._maskContent.clear()
2019-07-18 12:26:39 +02:00
if (!this._disabled) {
2019-05-14 13:56:05 +02:00
this._drawScrollbars()
this._drawMask()
2019-07-18 12:26:39 +02:00
if (this.options.dragScroll) {
const direction =
this.isScrollbarHorizontal && this.isScrollbarVertical
? 'all'
: this.isScrollbarHorizontal
? 'x'
: 'y'
if (direction !== null) {
2019-05-14 13:56:05 +02:00
this.content
.drag({ clampWheel: true, direction })
.clamp({ direction, underflow: this.options.underflow })
}
}
}
}
/**
* show the scrollbar and restart the timer for fade if options.fade is set
*/
2019-07-18 12:26:39 +02:00
activateFade() {
if (this.options.fade) {
if (this.fade) {
2019-05-14 13:56:05 +02:00
this.ease.remove(this.fade)
}
this.scrollbar.alpha = 1
const time = this.options.fade === true ? 1000 : this.options.fade
2019-07-18 12:26:39 +02:00
this.fade = this.ease.to(this.scrollbar, { alpha: 0 }, time, {
wait: this.options.fadeWait,
ease: this.options.fadeEase
})
this.fade.on('each', () => (this.content.dirty = true))
2019-05-14 13:56:05 +02:00
}
}
/**
* handle pointer down on scrollbar
* @param {PIXI.interaction.InteractionEvent} e
* @private
*/
2019-07-18 12:26:39 +02:00
scrollbarDown(e) {
2019-05-14 13:56:05 +02:00
const local = this.toLocal(e.data.global)
2019-07-18 12:26:39 +02:00
if (this.isScrollbarHorizontal) {
if (local.y > this.boxHeight - this.scrollbarSize) {
if (
local.x >= this.scrollbarLeft &&
local.x <= this.scrollbarLeft + this.scrollbarWidth
) {
2019-05-14 13:56:05 +02:00
this.pointerDown = { type: 'horizontal', last: local }
2019-07-18 12:26:39 +02:00
} else {
if (local.x > this.scrollbarLeft) {
2019-05-14 13:56:05 +02:00
this.content.left += this.content.worldScreenWidth
this.update()
2019-07-18 12:26:39 +02:00
} else {
2019-05-14 13:56:05 +02:00
this.content.left -= this.content.worldScreenWidth
this.update()
}
}
2019-07-18 12:26:39 +02:00
if (this.options.stopPropagation) {
2019-05-14 13:56:05 +02:00
e.stopPropagation()
}
return
}
}
2019-07-18 12:26:39 +02:00
if (this.isScrollbarVertical) {
if (local.x > this.boxWidth - this.scrollbarSize) {
if (
local.y >= this.scrollbarTop &&
local.y <= this.scrollbarTop + this.scrollbarWidth
) {
2019-05-14 13:56:05 +02:00
this.pointerDown = { type: 'vertical', last: local }
2019-07-18 12:26:39 +02:00
} else {
if (local.y > this.scrollbarTop) {
2019-05-14 13:56:05 +02:00
this.content.top += this.content.worldScreenHeight
this.update()
2019-07-18 12:26:39 +02:00
} else {
2019-05-14 13:56:05 +02:00
this.content.top -= this.content.worldScreenHeight
this.update()
}
}
2019-07-18 12:26:39 +02:00
if (this.options.stopPropagation) {
2019-05-14 13:56:05 +02:00
e.stopPropagation()
}
return
}
}
}
/**
* handle pointer move on scrollbar
* @param {PIXI.interaction.InteractionEvent} e
* @private
*/
2019-07-18 12:26:39 +02:00
scrollbarMove(e) {
if (this.pointerDown) {
if (this.pointerDown.type === 'horizontal') {
2019-05-14 13:56:05 +02:00
const local = this.toLocal(e.data.global)
this.content.left += local.x - this.pointerDown.last.x
this.pointerDown.last = local
this.update()
2019-07-18 12:26:39 +02:00
} else if (this.pointerDown.type === 'vertical') {
2019-05-14 13:56:05 +02:00
const local = this.toLocal(e.data.global)
this.content.top += local.y - this.pointerDown.last.y
this.pointerDown.last = local
this.update()
}
2019-07-18 12:26:39 +02:00
if (this.options.stopPropagation) {
2019-05-14 13:56:05 +02:00
e.stopPropagation()
}
}
}
/**
* handle pointer down on scrollbar
* @private
*/
2019-07-18 12:26:39 +02:00
scrollbarUp() {
2019-05-14 13:56:05 +02:00
this.pointerDown = null
}
/**
* resize the mask for the container
* @param {object} options
* @param {number} [options.boxWidth] width of scrollbox including scrollbar (in pixels)
* @param {number} [options.boxHeight] height of scrollbox including scrollbar (in pixels)
* @param {number} [options.scrollWidth] set the width of the inside of the scrollbox (leave null to use content.width)
* @param {number} [options.scrollHeight] set the height of the inside of the scrollbox (leave null to use content.height)
*/
2019-07-18 12:26:39 +02:00
resize(options) {
this.options.boxWidth =
typeof options.boxWidth !== 'undefined'
? options.boxWidth
: this.options.boxWidth
this.options.boxHeight =
typeof options.boxHeight !== 'undefined'
? options.boxHeight
: this.options.boxHeight
if (options.scrollWidth) {
2019-05-14 13:56:05 +02:00
this.scrollWidth = options.scrollWidth
}
2019-07-18 12:26:39 +02:00
if (options.scrollHeight) {
2019-05-14 13:56:05 +02:00
this.scrollHeight = options.scrollHeight
}
2019-07-18 12:26:39 +02:00
this.content.resize(
this.options.boxWidth,
this.options.boxHeight,
this.scrollWidth,
this.scrollHeight
)
2019-05-14 13:56:05 +02:00
this.update()
}
/**
* ensure that the bounding box is visible
* @param {number} x - relative to content's coordinate system
* @param {number} y
* @param {number} width
* @param {number} height
*/
2019-07-18 12:26:39 +02:00
ensureVisible(x, y, width, height) {
2019-05-14 13:56:05 +02:00
this.content.ensureVisible(x, y, width, height)
this._drawScrollbars()
}
}