483 lines
14 KiB
JavaScript
483 lines
14 KiB
JavaScript
|
/* eslint-disable no-unused-vars */
|
||
|
import Events from '../events.js'
|
||
|
import { AbstractScatter } from '../scatter.js'
|
||
|
import { Angle, Points, Polygon } from '../utils.js'
|
||
|
import { InteractionMapper } from '../interaction.js'
|
||
|
|
||
|
/** A container for scatter objects, which uses a single InteractionMapper
|
||
|
* for all children. This reduces the number of registered event handlers
|
||
|
* and covers the common use case that multiple objects are scattered
|
||
|
* on the same level.
|
||
|
*/
|
||
|
export class ScatterContainer extends PIXI.Graphics {
|
||
|
/**
|
||
|
* @constructor
|
||
|
* @param {PIXI.Renderer} renderer - PIXI renderer, needed for hit testing
|
||
|
* @param {Bool} stopEvents - Whether events should be stopped or propagated
|
||
|
* @param {Bool} claimEvents - Whether events should be marked as claimed
|
||
|
* if findTarget return as non-null value.
|
||
|
* @param {PIXI.Container} container - A container for the scatter
|
||
|
* @param {Bool} showBounds - Show bounds for debugging purposes.
|
||
|
* @param {Bool} showTouches - Show touches and pointer for debugging purposes.
|
||
|
* @param {Color} backgroundColor - Set background color if specified.
|
||
|
* @param {PIXIApp} app - Needed if showBounds is true to register
|
||
|
* update handler.
|
||
|
*/
|
||
|
constructor(
|
||
|
renderer,
|
||
|
{
|
||
|
stopEvents = true,
|
||
|
claimEvents = true,
|
||
|
container = null,
|
||
|
showBounds = false,
|
||
|
showPolygon = false,
|
||
|
showTouches = false,
|
||
|
backgroundColor = null,
|
||
|
app = window.app
|
||
|
} = {}
|
||
|
) {
|
||
|
super()
|
||
|
this.container = container
|
||
|
if (this.container)
|
||
|
this.containerDimensions = {
|
||
|
x: this.container.width,
|
||
|
y: this.container.height
|
||
|
}
|
||
|
this.backgroundWidth = null
|
||
|
this.backgroundHeight = null
|
||
|
this.app = app
|
||
|
this.renderer = renderer
|
||
|
this.stopEvents = stopEvents
|
||
|
this.claimEvents = claimEvents
|
||
|
this.delegate = new InteractionMapper(this.eventReceiver, this)
|
||
|
this.showBounds = showBounds
|
||
|
this.showTouches = showTouches
|
||
|
this.showPolygon = showPolygon
|
||
|
this.backgroundColor = backgroundColor
|
||
|
if (showBounds || showTouches || showPolygon) {
|
||
|
//console.log("Show TOUCHES!!!")
|
||
|
this.app.ticker.add(delta => this.update(delta), this)
|
||
|
}
|
||
|
if (backgroundColor) {
|
||
|
this.updateBackground()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
updateBackground() {
|
||
|
this.clear()
|
||
|
let rect = this.bounds
|
||
|
this.beginFill(this.backgroundColor, 1)
|
||
|
this.drawRect(0, 0, rect.width, rect.height)
|
||
|
this.endFill()
|
||
|
}
|
||
|
|
||
|
get eventReceiver() {
|
||
|
return this.renderer.plugins.interaction.interactionDOMElement
|
||
|
}
|
||
|
|
||
|
get bounds() {
|
||
|
let x = 0
|
||
|
let y = 0
|
||
|
|
||
|
// @container: We need to call the constant values, as the container
|
||
|
// gets resized, when a child moves outside the original boundaries.
|
||
|
let w = this.container ? this.containerDimensions.x : this.backgroundWidth || this.app.width
|
||
|
let h = this.container ? this.containerDimensions.y : this.backgroundHeight || this.app.height
|
||
|
|
||
|
if (this.app.fullscreen && this.app.monkeyPatchMapping) {
|
||
|
let fixed = this.mapPositionToPoint({ x: w, y: 0 })
|
||
|
if (fixed.x < w) {
|
||
|
w = fixed.x
|
||
|
}
|
||
|
if (fixed.y > 0) {
|
||
|
y += fixed.y
|
||
|
h -= fixed.y
|
||
|
}
|
||
|
}
|
||
|
return new PIXI.Rectangle(x, y, w, h)
|
||
|
}
|
||
|
|
||
|
get center() {
|
||
|
let r = this.bounds
|
||
|
return { x: r.width / 2, y: r.height / 2 }
|
||
|
}
|
||
|
|
||
|
get polygon() {
|
||
|
let r = this.bounds
|
||
|
let w2 = r.width / 2
|
||
|
let h2 = r.height / 2
|
||
|
let center = { x: w2, y: h2 }
|
||
|
let polygon = new Polygon(center)
|
||
|
polygon.addPoint({ x: -w2, y: -h2 })
|
||
|
polygon.addPoint({ x: w2, y: -h2 })
|
||
|
polygon.addPoint({ x: w2, y: h2 })
|
||
|
polygon.addPoint({ x: -w2, y: h2 })
|
||
|
return polygon
|
||
|
}
|
||
|
|
||
|
update(dt) {
|
||
|
this.clear()
|
||
|
this.lineStyle(1, 0x0000ff)
|
||
|
if (this.showBounds) {
|
||
|
for (let child of this.children) {
|
||
|
if (child.scatter) {
|
||
|
//let {x, y, width, height} = child.scatter.throwBounds()
|
||
|
// new PIXI.Rectangle(x, y, width, height)
|
||
|
this.drawShape(child.scatter.bounds)
|
||
|
let center = child.scatter.center
|
||
|
this.drawCircle(center.x, center.y, 4)
|
||
|
this.drawCircle(child.scatter.x, child.scatter.y, 4)
|
||
|
}
|
||
|
}
|
||
|
this.lineStyle(2, 0x0000ff)
|
||
|
this.drawShape(this.bounds)
|
||
|
}
|
||
|
if (this.showPolygon) {
|
||
|
this.lineStyle(2, 0xff0000)
|
||
|
for (let child of this.children) {
|
||
|
if (child.scatter) {
|
||
|
let polygon = child.scatter.polygon
|
||
|
let shape = new PIXI.Polygon(polygon.flatAbsolutePoints())
|
||
|
//shape.close() not possible in PixiJS v5
|
||
|
this.drawShape(shape)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (this.showTouches) {
|
||
|
let current = this.delegate.interaction.current
|
||
|
for (let [key, point] of current.entries()) {
|
||
|
let local = this.mapPositionToPoint(point)
|
||
|
this.drawCircle(local.x, local.y, 12)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
capture(event) {
|
||
|
if (this.stopEvents) Events.stop(event)
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
fakeInteractionEvent(point, key) {
|
||
|
return { data: { global: point, key: key } }
|
||
|
}
|
||
|
|
||
|
findHitScatter(data, displayObject, hit) {
|
||
|
if (hit && this.hitScatter === null && typeof displayObject != undefined) {
|
||
|
this.hitScatter = displayObject.scatter ? displayObject.scatter : null
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mapPositionToPoint(point, element = null) {
|
||
|
// In case of nested scatters we get an additional parameter that
|
||
|
// contains the found scatter
|
||
|
let local = new PIXI.Point()
|
||
|
let interactionManager = this.renderer.plugins.interaction
|
||
|
interactionManager.mapPositionToPoint(local, point.x, point.y)
|
||
|
if (element instanceof DisplayObjectScatter && element.displayObject.parent != null) {
|
||
|
return element.displayObject.parent.toLocal(local)
|
||
|
}
|
||
|
return local
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* New method hitTest implemented (in InteractionManager, since 4.5.0).
|
||
|
* See https://github.com/pixijs/pixi.js/pull/3878
|
||
|
*/
|
||
|
findTarget(event, local, global) {
|
||
|
if (event.claimedByScatter) {
|
||
|
return null
|
||
|
}
|
||
|
this.hitScatter = null
|
||
|
let interactionManager = this.renderer.plugins.interaction
|
||
|
let fakeEvent = this.fakeInteractionEvent(local)
|
||
|
interactionManager.processInteractive(fakeEvent, this, this.findHitScatter.bind(this), true)
|
||
|
if (this.claimEvents) event.claimedByScatter = this.hitScatter
|
||
|
return this.hitScatter
|
||
|
}
|
||
|
|
||
|
findTargetNew(event, local, global) {
|
||
|
// UO: still problematic. Does not find non interactive elements
|
||
|
// which are needed for some stylus applications
|
||
|
if (event.claimedByScatter) {
|
||
|
return null
|
||
|
}
|
||
|
|
||
|
this.hitScatter = null
|
||
|
let interactionManager = this.renderer.plugins.interaction
|
||
|
let displayObject = interactionManager.hitTest(local, this)
|
||
|
if (displayObject != null && displayObject.scatter != null) this.hitScatter = displayObject.scatter
|
||
|
if (this.claimEvents) event.claimedByScatter = this.hitScatter
|
||
|
|
||
|
return this.hitScatter
|
||
|
}
|
||
|
|
||
|
onStart(event, interaction) {}
|
||
|
|
||
|
onMove(event, interaction) {}
|
||
|
|
||
|
onEnd(event, interaction) {
|
||
|
for (let key of interaction.ended.keys()) {
|
||
|
let point = interaction.ended.get(key)
|
||
|
if (interaction.isLongPress(key)) {
|
||
|
this.onLongPress(key, point)
|
||
|
}
|
||
|
if (interaction.isTap(key)) {
|
||
|
this.onTap(key, point)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
onTap(key, point) {
|
||
|
console.info('ScatterContainer.onTap')
|
||
|
}
|
||
|
|
||
|
onLongPress(key, point) {
|
||
|
console.info('ScatterContainer.onLongPress')
|
||
|
}
|
||
|
|
||
|
bringToFront(displayObject) {
|
||
|
this.addChild(displayObject)
|
||
|
}
|
||
|
|
||
|
layout(width, height) {
|
||
|
this.backgroundWidth = width
|
||
|
this.backgroundHeight = height
|
||
|
if (this.backgroundColor) {
|
||
|
this.updateBackground()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** A wrapper for child elements of a ScatterContainer. Can be used
|
||
|
* to combine scattered objects with non-scattered objects. Any
|
||
|
* PIXI.DisplayObject can be wrapped.
|
||
|
*/
|
||
|
export class DisplayObjectScatter extends AbstractScatter {
|
||
|
constructor(
|
||
|
displayObject,
|
||
|
renderer,
|
||
|
{
|
||
|
x = null,
|
||
|
y = null,
|
||
|
minScale = 0.1,
|
||
|
maxScale = 1.0,
|
||
|
startScale = 1.0,
|
||
|
autoBringToFront = true,
|
||
|
translatable = true,
|
||
|
scalable = true,
|
||
|
rotatable = true,
|
||
|
resizable = false,
|
||
|
movableX = true,
|
||
|
movableY = true,
|
||
|
throwVisibility = 44,
|
||
|
throwDamping = 0.95,
|
||
|
autoThrow = true,
|
||
|
rotationDegrees = null,
|
||
|
rotation = null,
|
||
|
overdoScaling = 1.5,
|
||
|
onTransform = null,
|
||
|
onResize,
|
||
|
onThrowFinished = null
|
||
|
} = {}
|
||
|
) {
|
||
|
// For the simulation of named parameters,
|
||
|
// see: http://exploringjs.com/es6/ch_parameter-handling.html
|
||
|
super({
|
||
|
overdoScaling,
|
||
|
minScale,
|
||
|
maxScale,
|
||
|
startScale,
|
||
|
autoBringToFront,
|
||
|
translatable,
|
||
|
scalable,
|
||
|
rotatable,
|
||
|
resizable,
|
||
|
movableX,
|
||
|
movableY,
|
||
|
throwVisibility,
|
||
|
throwDamping,
|
||
|
autoThrow,
|
||
|
onThrowFinished,
|
||
|
rotationDegrees,
|
||
|
rotation,
|
||
|
onTransform
|
||
|
})
|
||
|
this.onResize = onResize
|
||
|
this.displayObject = displayObject
|
||
|
this.displayObject.scatter = this
|
||
|
this.renderer = renderer
|
||
|
this.scale = startScale
|
||
|
this.rotationDegrees = this.startRotationDegrees
|
||
|
|
||
|
// Only set x and y if they are specified.
|
||
|
// Otherwise the displayobject gets corrupted.
|
||
|
if (x != null) this.x = x
|
||
|
if (y != null) this.y = y
|
||
|
}
|
||
|
|
||
|
|
||
|
getWorldScatter() {
|
||
|
return this
|
||
|
}
|
||
|
|
||
|
/** Returns geometry data as object. **/
|
||
|
getState() {
|
||
|
return {
|
||
|
scale: this.scale,
|
||
|
x: this.x,
|
||
|
y: this.y,
|
||
|
rotation: this.rotation
|
||
|
}
|
||
|
}
|
||
|
|
||
|
setup() {
|
||
|
this.setupMouseWheelInteraction()
|
||
|
}
|
||
|
|
||
|
roundPixel(value) {
|
||
|
// UO: Should be obsolete because Renderer supports roundPixels by default
|
||
|
return value
|
||
|
}
|
||
|
|
||
|
get container() {
|
||
|
// return this.displayObject.parent
|
||
|
let obj = this.displayObject
|
||
|
while (obj.parent != null && !(obj.parent instanceof ScatterContainer)) obj = obj.parent
|
||
|
return obj.parent
|
||
|
}
|
||
|
|
||
|
get x() {
|
||
|
return this.position.x
|
||
|
}
|
||
|
|
||
|
set x(value) {
|
||
|
this.position.x = value
|
||
|
}
|
||
|
|
||
|
get y() {
|
||
|
return this.position.y
|
||
|
}
|
||
|
|
||
|
set y(value) {
|
||
|
this.position.y = value
|
||
|
}
|
||
|
|
||
|
get polygon() {
|
||
|
let polygon = new Polygon(this.center)
|
||
|
let w2 = this.width / 2
|
||
|
let h2 = this.height / 2
|
||
|
polygon.addPoint({ x: -w2, y: -h2 })
|
||
|
polygon.addPoint({ x: w2, y: -h2 })
|
||
|
polygon.addPoint({ x: w2, y: h2 })
|
||
|
polygon.addPoint({ x: -w2, y: h2 })
|
||
|
polygon.rotate(this.rotation)
|
||
|
return polygon
|
||
|
}
|
||
|
|
||
|
get containerBounds() {
|
||
|
return this.container.bounds
|
||
|
}
|
||
|
|
||
|
get containerPolygon() {
|
||
|
let container = this.container
|
||
|
if (container == null) return null
|
||
|
return container.polygon
|
||
|
}
|
||
|
|
||
|
get position() {
|
||
|
return this.displayObject.position
|
||
|
}
|
||
|
|
||
|
set position(value) {
|
||
|
this.displayObject.position = value
|
||
|
}
|
||
|
|
||
|
get scale() {
|
||
|
return this.displayObject.scale.x
|
||
|
}
|
||
|
|
||
|
set scale(value) {
|
||
|
this.displayObject.scale.x = value
|
||
|
this.displayObject.scale.y = value
|
||
|
}
|
||
|
|
||
|
get width() {
|
||
|
return this.displayObject.width
|
||
|
}
|
||
|
|
||
|
get height() {
|
||
|
return this.displayObject.height
|
||
|
}
|
||
|
|
||
|
get bounds() {
|
||
|
return this.displayObject.getBounds()
|
||
|
}
|
||
|
|
||
|
get pivot() {
|
||
|
return this.displayObject.pivot
|
||
|
}
|
||
|
|
||
|
get rotation() {
|
||
|
return this.displayObject.rotation
|
||
|
}
|
||
|
|
||
|
set rotation(value) {
|
||
|
this.displayObject.rotation = value
|
||
|
}
|
||
|
|
||
|
get rotationDegrees() {
|
||
|
return Angle.radian2degree(this.displayObject.rotation)
|
||
|
}
|
||
|
|
||
|
set rotationDegrees(value) {
|
||
|
this.displayObject.rotation = Angle.degree2radian(value)
|
||
|
}
|
||
|
|
||
|
get center() {
|
||
|
let w2 = this.width / 2
|
||
|
let h2 = this.height / 2
|
||
|
let dist = Math.sqrt(w2 * w2 + h2 * h2)
|
||
|
let angle = Points.angle({ x: w2, y: h2 }, { x: 0, y: 0 })
|
||
|
let p = this.displayObject.x
|
||
|
let c = Points.arc(this.position, this.rotation + angle, dist)
|
||
|
return c // Points.subtract(c, this.pivot)
|
||
|
}
|
||
|
|
||
|
get rotationOrigin() {
|
||
|
// In PIXI the default rotation and scale origin is the position
|
||
|
return this.position // Points.add(this.position, this.pivot)
|
||
|
}
|
||
|
|
||
|
mapPositionToContainerPoint(point) {
|
||
|
// UO: We need the coordinates related to this scatter in case
|
||
|
// of nested scatters
|
||
|
if (this.container != null) return this.container.mapPositionToPoint(point, this)
|
||
|
return point
|
||
|
}
|
||
|
|
||
|
capture(event) {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
bringToFront() {
|
||
|
if (this.autoBringToFront) {
|
||
|
if (this.displayObject.parent instanceof ScatterContainer) {
|
||
|
let scatterContainer = this.displayObject.parent
|
||
|
scatterContainer.bringToFront(this.displayObject)
|
||
|
} else if (this.displayObject.parent != null && this.displayObject.parent.scatter) {
|
||
|
this.displayObject.parent.scatter.toFront(this.displayObject)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
toFront(displayObject) {
|
||
|
this.displayObject.addChild(displayObject)
|
||
|
}
|
||
|
|
||
|
validScale(scale) {
|
||
|
scale = Math.max(scale, this.minScale)
|
||
|
scale = Math.min(scale, this.maxScale)
|
||
|
return scale
|
||
|
}
|
||
|
}
|