2019-07-30 16:56:29 +02:00
|
|
|
/* globals ThrowPropsPlugin, Strong */
|
2019-03-21 09:57:27 +01:00
|
|
|
|
|
|
|
/* Imports */
|
|
|
|
import Events from '../events.js'
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Class that represents a PixiJS List.
|
|
|
|
*
|
|
|
|
* @example
|
2019-09-10 16:34:59 +02:00
|
|
|
* const elephant1 = PIXI.Sprite.from('./assets/elephant-1.jpg')
|
|
|
|
* const elephant2 = PIXI.Sprite.from('./assets/elephant-2.jpg')
|
2019-07-18 12:26:39 +02:00
|
|
|
*
|
2019-03-21 09:57:27 +01:00
|
|
|
* // Create the list
|
|
|
|
* const list = new List([elephant1, elephant2])
|
2019-07-18 12:26:39 +02:00
|
|
|
*
|
2019-03-21 09:57:27 +01:00
|
|
|
* app.scene.addChild(list)
|
|
|
|
*
|
|
|
|
* @class
|
|
|
|
* @extends PIXI.Container
|
|
|
|
* @see {@link http://pixijs.download/dev/docs/PIXI.Container.html|PixiJS Container}
|
|
|
|
* @see {@link https://www.iwm-tuebingen.de/iwmbrowser/lib/pixi/list.html|DocTest}
|
|
|
|
*/
|
|
|
|
export default class List extends PIXI.Container {
|
|
|
|
/**
|
|
|
|
* Creates an instance of a Flippable.
|
|
|
|
*
|
|
|
|
* @constructor
|
|
|
|
* @param {PIXI.DisplayObject[]} items - An array of PIXI.DisplayObjects.
|
|
|
|
* @param {object} [opts] - An options object which can contain the following properties.
|
|
|
|
* @param {number} [opts.width] - The width of the list. If the items are larger than this width, the overflow
|
|
|
|
* will be hidden.
|
|
|
|
* @param {number} [opts.height] - The height of the list. If the items are larger than this height, the overflow
|
|
|
|
* will be hidden.
|
|
|
|
* @param {number} [opts.padding=10] - The inner spacing (distance from one item to the previous/next item).
|
|
|
|
* @param {number} [opts.margin=10] - The outer spacing (distance from one item to the border).
|
|
|
|
* @param {string} [opts.orientation=vertical] - The orientation of the button group. Can be horizontal or vertical.
|
|
|
|
* @param {string} [opts.align=left] - The horizontal position of the items. Possible values are
|
|
|
|
* left, center and right.
|
|
|
|
* @param {string} [opts.verticalAlign=middle] - The vertical position of the items. Possible values are
|
|
|
|
* top, middle and bottom.
|
2019-03-29 11:40:01 +01:00
|
|
|
* @param {PIXI.Application} [opts.app] - The PixiJS Application. Must be set if you want to use the mousewheel to
|
|
|
|
* scroll your list.
|
2019-03-21 09:57:27 +01:00
|
|
|
*/
|
|
|
|
constructor(items = [], opts = {}) {
|
|
|
|
super()
|
|
|
|
|
2019-07-18 12:26:39 +02:00
|
|
|
this.opts = Object.assign(
|
|
|
|
{},
|
|
|
|
{
|
|
|
|
padding: 10,
|
|
|
|
margin: 10,
|
|
|
|
orientation: 'vertical',
|
|
|
|
align: 'left',
|
|
|
|
verticalAlign: 'middle',
|
|
|
|
width: null,
|
|
|
|
height: null,
|
|
|
|
app: null
|
|
|
|
},
|
|
|
|
opts
|
|
|
|
)
|
2019-03-21 09:57:27 +01:00
|
|
|
|
|
|
|
this.__items = items
|
|
|
|
this.__dragging = false
|
|
|
|
|
|
|
|
// setup
|
|
|
|
//--------------------
|
|
|
|
this.setup()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates children and instantiates everything.
|
|
|
|
*
|
|
|
|
* @private
|
|
|
|
* @return {List} A reference to the list for chaining.
|
|
|
|
*/
|
|
|
|
setup() {
|
|
|
|
// inner container
|
|
|
|
//--------------------
|
|
|
|
const container = new PIXI.Container()
|
|
|
|
this.addChild(container)
|
|
|
|
this.container = container
|
|
|
|
|
|
|
|
// mask
|
|
|
|
//--------------------
|
|
|
|
const mask = new PIXI.Graphics()
|
|
|
|
this.addChild(mask)
|
|
|
|
this.__mask = mask
|
|
|
|
|
|
|
|
// add items
|
|
|
|
//--------------------
|
2019-07-18 12:26:39 +02:00
|
|
|
for (let item of this.__items) {
|
2019-03-21 09:57:27 +01:00
|
|
|
container.addChild(item)
|
|
|
|
}
|
|
|
|
|
|
|
|
// interaction
|
|
|
|
//--------------------
|
|
|
|
this.interactive = this.opts.width || this.opts.height
|
|
|
|
this.on('pointerdown', this.onStart.bind(this))
|
|
|
|
this.on('pointermove', this.onMove.bind(this))
|
|
|
|
this.on('pointerup', this.onEnd.bind(this))
|
|
|
|
this.on('pointercancel', this.onEnd.bind(this))
|
|
|
|
this.on('pointerout', this.onEnd.bind(this))
|
|
|
|
this.on('pointerupoutside', this.onEnd.bind(this))
|
2019-03-29 11:40:01 +01:00
|
|
|
this.on('scroll', this.onScroll.bind(this))
|
|
|
|
|
|
|
|
// mousewheel
|
|
|
|
//--------------------
|
|
|
|
if (this.opts.app) {
|
|
|
|
const app = this.opts.app
|
|
|
|
app.view.addEventListener('mousewheel', event => {
|
2019-07-30 16:56:29 +02:00
|
|
|
const bounds = this.mask ? this.mask.getBounds() : this.getBounds()
|
2019-03-29 11:40:01 +01:00
|
|
|
const x = event.clientX - app.view.getBoundingClientRect().left
|
|
|
|
const y = event.clientY - app.view.getBoundingClientRect().top
|
|
|
|
if (bounds.contains(x, y)) {
|
2019-03-29 15:01:21 +01:00
|
|
|
event.preventDefault()
|
2019-03-29 11:40:01 +01:00
|
|
|
this.emit('scroll', event)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2019-03-21 09:57:27 +01:00
|
|
|
|
|
|
|
this.layout()
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Replaces the existing items and relayouts the list.
|
|
|
|
*
|
|
|
|
* @param {PIXI.DisplayObject[]} items - An array of PIXI.DisplayObjects.
|
|
|
|
* @return {List} A reference to the list for chaining.
|
|
|
|
*/
|
|
|
|
setItems(items) {
|
|
|
|
this.container.removeChildren()
|
|
|
|
this.__items = items
|
2019-07-18 12:26:39 +02:00
|
|
|
for (let item of this.__items) {
|
2019-03-21 09:57:27 +01:00
|
|
|
this.container.addChild(item)
|
|
|
|
}
|
|
|
|
this.layout()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Should be called to refresh the layout of the list (the width or the height).
|
|
|
|
*
|
|
|
|
* @return {List} A reference to the list for chaining.
|
|
|
|
*/
|
|
|
|
layout() {
|
|
|
|
const margin = this.opts.margin
|
|
|
|
|
|
|
|
let x = margin
|
|
|
|
let y = margin
|
|
|
|
|
|
|
|
for (let item of this.__items) {
|
|
|
|
item.x = x
|
|
|
|
item.y = y
|
|
|
|
|
|
|
|
if (this.opts.orientation === 'vertical') {
|
|
|
|
y += item.height + this.opts.padding
|
|
|
|
} else {
|
|
|
|
x += item.width + this.opts.padding
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// vertical
|
|
|
|
//--------------------
|
|
|
|
if (this.opts.orientation === 'vertical') {
|
|
|
|
switch (this.opts.align) {
|
|
|
|
case 'center':
|
2019-07-30 16:56:29 +02:00
|
|
|
this.__items.forEach(it => (it.x = margin + this.width / 2 - it.width / 2))
|
2019-03-21 09:57:27 +01:00
|
|
|
break
|
|
|
|
case 'right':
|
2019-07-30 16:56:29 +02:00
|
|
|
this.__items.forEach(it => (it.x = margin + this.width - it.width))
|
2019-03-21 09:57:27 +01:00
|
|
|
break
|
|
|
|
default:
|
2019-07-18 12:26:39 +02:00
|
|
|
this.__items.forEach(it => (it.x = margin))
|
2019-03-21 09:57:27 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.opts.height) {
|
|
|
|
const mask = this.__mask
|
|
|
|
mask.clear()
|
|
|
|
mask.beginFill(0x000)
|
|
|
|
mask.drawRect(0, 0, this.width + 2 * margin, this.opts.height)
|
|
|
|
this.mask = mask
|
|
|
|
|
|
|
|
this.interactive = this.innerHeight > this.opts.height
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// horizontal
|
|
|
|
//--------------------
|
|
|
|
if (this.opts.orientation === 'horizontal') {
|
|
|
|
switch (this.opts.verticalAlign) {
|
|
|
|
case 'top':
|
2019-07-18 12:26:39 +02:00
|
|
|
this.__items.forEach(it => (it.y = margin))
|
2019-03-21 09:57:27 +01:00
|
|
|
break
|
|
|
|
case 'bottom':
|
2019-07-30 16:56:29 +02:00
|
|
|
this.__items.forEach(it => (it.y = margin + this.height - it.height))
|
2019-03-21 09:57:27 +01:00
|
|
|
break
|
|
|
|
default:
|
2019-07-30 16:56:29 +02:00
|
|
|
this.__items.forEach(it => (it.y = margin + this.height / 2 - it.height / 2))
|
2019-03-21 09:57:27 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.opts.width) {
|
|
|
|
const mask = this.__mask
|
|
|
|
mask.clear()
|
|
|
|
mask.beginFill(0x000)
|
|
|
|
mask.drawRect(0, 0, this.opts.width, this.height + 2 * margin)
|
|
|
|
this.mask = mask
|
|
|
|
|
|
|
|
this.interactive = this.innerWidth > this.opts.width
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return this
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-07-18 12:26:39 +02:00
|
|
|
*
|
2019-03-21 09:57:27 +01:00
|
|
|
*/
|
|
|
|
get innerWidth() {
|
|
|
|
let size = 0
|
|
|
|
|
2019-07-18 12:26:39 +02:00
|
|
|
this.__items.forEach(it => (size += it.width))
|
2019-03-21 09:57:27 +01:00
|
|
|
size += this.opts.padding * (this.__items.length - 1)
|
|
|
|
size += 2 * this.opts.margin
|
|
|
|
|
|
|
|
return size
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-07-18 12:26:39 +02:00
|
|
|
*
|
2019-03-21 09:57:27 +01:00
|
|
|
*/
|
|
|
|
get innerHeight() {
|
|
|
|
let size = 0
|
|
|
|
|
2019-07-18 12:26:39 +02:00
|
|
|
this.__items.forEach(it => (size += it.height))
|
2019-03-21 09:57:27 +01:00
|
|
|
size += this.opts.padding * (this.__items.length - 1)
|
|
|
|
size += 2 * this.opts.margin
|
|
|
|
|
|
|
|
return size
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Resizes the list.
|
2019-07-18 12:26:39 +02:00
|
|
|
*
|
2019-03-21 09:57:27 +01:00
|
|
|
* @param {number} widthOrHeight - The new width (if orientation is horizontal) or height (if orientation is vertical) of the list.
|
|
|
|
*/
|
|
|
|
resize(widthOrHeight) {
|
|
|
|
if (this.opts.orientation === 'horizontal') {
|
|
|
|
this.opts.width = widthOrHeight
|
|
|
|
} else {
|
|
|
|
this.opts.height = widthOrHeight
|
|
|
|
}
|
|
|
|
|
|
|
|
this.layout()
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-07-18 12:26:39 +02:00
|
|
|
*
|
2019-03-21 09:57:27 +01:00
|
|
|
* @private
|
2019-07-18 12:26:39 +02:00
|
|
|
* @param {*} event
|
2019-03-21 09:57:27 +01:00
|
|
|
*/
|
|
|
|
onStart(event) {
|
|
|
|
this.__dragging = true
|
|
|
|
|
|
|
|
this.capture(event)
|
|
|
|
|
|
|
|
this.__delta = {
|
|
|
|
x: this.container.position.x - event.data.global.x,
|
|
|
|
y: this.container.position.y - event.data.global.y
|
|
|
|
}
|
|
|
|
|
2019-07-18 12:26:39 +02:00
|
|
|
TweenLite.killTweensOf(this.container.position, { x: true, y: true })
|
|
|
|
if (typeof ThrowPropsPlugin != 'undefined') {
|
2019-03-29 09:11:21 +01:00
|
|
|
ThrowPropsPlugin.track(this.container.position, 'x,y')
|
|
|
|
}
|
2019-03-21 09:57:27 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-07-18 12:26:39 +02:00
|
|
|
*
|
2019-03-21 09:57:27 +01:00
|
|
|
* @private
|
2019-07-18 12:26:39 +02:00
|
|
|
* @param {*} event
|
2019-03-21 09:57:27 +01:00
|
|
|
*/
|
|
|
|
onMove(event) {
|
|
|
|
if (this.__dragging) {
|
|
|
|
this.capture(event)
|
|
|
|
|
|
|
|
if (this.opts.orientation === 'horizontal') {
|
|
|
|
this.container.position.x = event.data.global.x + this.__delta.x
|
|
|
|
} else {
|
|
|
|
this.container.position.y = event.data.global.y + this.__delta.y
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-07-18 12:26:39 +02:00
|
|
|
*
|
2019-03-21 09:57:27 +01:00
|
|
|
* @private
|
2019-07-18 12:26:39 +02:00
|
|
|
* @param {*} event
|
2019-03-21 09:57:27 +01:00
|
|
|
*/
|
|
|
|
onEnd(event) {
|
|
|
|
if (this.__dragging) {
|
|
|
|
this.__dragging = false
|
|
|
|
|
|
|
|
this.capture(event)
|
|
|
|
|
|
|
|
const throwProps = {}
|
2019-07-18 12:26:39 +02:00
|
|
|
|
2019-03-21 09:57:27 +01:00
|
|
|
if (this.opts.orientation === 'horizontal') {
|
|
|
|
let min = this.opts.width - this.innerWidth
|
|
|
|
min = min > 0 ? 0 : min
|
|
|
|
throwProps.x = {
|
|
|
|
velocity: 'auto',
|
|
|
|
min,
|
|
|
|
max: 0
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
let min = this.opts.height - this.innerHeight
|
|
|
|
min = min > 0 ? 0 : min
|
|
|
|
throwProps.y = {
|
|
|
|
velocity: 'auto',
|
|
|
|
min,
|
|
|
|
max: 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-18 12:26:39 +02:00
|
|
|
if (typeof ThrowPropsPlugin != 'undefined') {
|
|
|
|
ThrowPropsPlugin.to(
|
|
|
|
this.container.position,
|
|
|
|
{
|
|
|
|
throwProps,
|
|
|
|
ease: Strong.easeOut,
|
2019-07-30 16:56:29 +02:00
|
|
|
onComplete: () => ThrowPropsPlugin.untrack(this.container.position)
|
2019-07-18 12:26:39 +02:00
|
|
|
},
|
|
|
|
0.8,
|
|
|
|
0.4
|
|
|
|
)
|
2019-03-29 09:11:21 +01:00
|
|
|
}
|
2019-03-21 09:57:27 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-29 11:40:01 +01:00
|
|
|
/**
|
2019-07-18 12:26:39 +02:00
|
|
|
*
|
2019-03-29 11:40:01 +01:00
|
|
|
* @private
|
2019-07-18 12:26:39 +02:00
|
|
|
* @param {*} event
|
2019-03-29 11:40:01 +01:00
|
|
|
*/
|
|
|
|
onScroll(event) {
|
|
|
|
this.capture(event)
|
|
|
|
|
|
|
|
if (this.opts.orientation === 'horizontal') {
|
|
|
|
this.container.position.x -= event.deltaX
|
|
|
|
if (this.container.position.x > 0) {
|
|
|
|
this.container.position.x = 0
|
2019-07-30 16:56:29 +02:00
|
|
|
} else if (this.container.position.x + this.innerWidth < this.opts.width) {
|
2019-03-29 11:40:01 +01:00
|
|
|
this.container.position.x = this.opts.width - this.innerWidth
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.container.position.y -= event.deltaY
|
|
|
|
if (this.container.position.y > 0) {
|
|
|
|
this.container.position.y = 0
|
2019-07-30 16:56:29 +02:00
|
|
|
} else if (this.container.position.y + this.innerHeight < this.opts.height) {
|
2019-03-29 11:40:01 +01:00
|
|
|
this.container.position.y = this.opts.height - this.innerHeight
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-21 09:57:27 +01:00
|
|
|
/**
|
|
|
|
* Captures an event to inform InteractionMapper about processed events.
|
|
|
|
*
|
|
|
|
* @param {event|PIXI.InteractionEvent} event - The PIXI event to capture.
|
|
|
|
*/
|
|
|
|
capture(event) {
|
2019-07-30 16:56:29 +02:00
|
|
|
const originalEvent = event.data && event.data.originalEvent ? event.data.originalEvent : event
|
2019-03-29 11:40:01 +01:00
|
|
|
Events.capturedBy(originalEvent, this)
|
2019-03-21 09:57:27 +01:00
|
|
|
}
|
|
|
|
}
|