iwmlib/lib/pixi/scatter.js

480 lines
14 KiB
JavaScript
Executable File

/* 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()
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) {
// console.log("findHitScatter", displayObject)
// }
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,
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.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
}
}