Added Scrollview.
This commit is contained in:
@@ -10,6 +10,7 @@ import Timeline from './timeline.js'
|
||||
import Theme from './theme.js'
|
||||
import Button from './button.js'
|
||||
import ButtonGroup from './buttongroup.js'
|
||||
import Scrollview from './scrollview.js'
|
||||
import Slider from './slider.js'
|
||||
import Switch from './switch.js'
|
||||
import Popup from './popup.js'
|
||||
@@ -41,6 +42,7 @@ window.AppTest = AppTest
|
||||
window.Theme = Theme
|
||||
window.Button = Button
|
||||
window.ButtonGroup = ButtonGroup
|
||||
window.Scrollview = Scrollview
|
||||
window.Slider = Slider
|
||||
window.Switch = Switch
|
||||
window.Popup = Popup
|
||||
|
||||
@@ -0,0 +1,585 @@
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
constructor(options)
|
||||
{
|
||||
super()
|
||||
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)
|
||||
this.ease = new PIXI.extras.Ease.list()
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
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())
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
get scrollbarOffsetHorizontal()
|
||||
{
|
||||
return this.options.scrollbarOffsetHorizontal
|
||||
}
|
||||
set scrollbarOffsetHorizontal(value)
|
||||
{
|
||||
this.options.scrollbarOffsetHorizontal = value
|
||||
}
|
||||
|
||||
/**
|
||||
* offset of vertical scrollbar (in pixels)
|
||||
* @type {number}
|
||||
*/
|
||||
get scrollbarOffsetVertical()
|
||||
{
|
||||
return this.options.scrollbarOffsetVertical
|
||||
}
|
||||
set scrollbarOffsetVertical(value)
|
||||
{
|
||||
this.options.scrollbarOffsetVertical = value
|
||||
}
|
||||
|
||||
/**
|
||||
* disable the scrollbox (if set to true this will also remove the mask)
|
||||
* @type {boolean}
|
||||
*/
|
||||
get disable()
|
||||
{
|
||||
return this._disabled
|
||||
}
|
||||
set disable(value)
|
||||
{
|
||||
if (this._disabled !== value)
|
||||
{
|
||||
this._disabled = value
|
||||
this.update()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* call stopPropagation on any events that impact scrollbox
|
||||
* @type {boolean}
|
||||
*/
|
||||
get stopPropagation()
|
||||
{
|
||||
return this.options.stopPropagation
|
||||
}
|
||||
set stopPropagation(value)
|
||||
{
|
||||
this.options.stopPropagation = value
|
||||
}
|
||||
|
||||
/**
|
||||
* user may drag the content area to scroll content
|
||||
* @type {boolean}
|
||||
*/
|
||||
get dragScroll()
|
||||
{
|
||||
return this.options.dragScroll
|
||||
}
|
||||
set dragScroll(value)
|
||||
{
|
||||
this.options.dragScroll = value
|
||||
if (value)
|
||||
{
|
||||
this.content.drag()
|
||||
}
|
||||
else
|
||||
{
|
||||
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}
|
||||
*/
|
||||
get boxWidth()
|
||||
{
|
||||
return this.options.boxWidth
|
||||
}
|
||||
set boxWidth(value)
|
||||
{
|
||||
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}
|
||||
*/
|
||||
get overflow()
|
||||
{
|
||||
return this.options.overflow
|
||||
}
|
||||
set overflow(value)
|
||||
{
|
||||
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}
|
||||
*/
|
||||
get overflowX()
|
||||
{
|
||||
return this.options.overflowX
|
||||
}
|
||||
set overflowX(value)
|
||||
{
|
||||
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}
|
||||
*/
|
||||
get overflowY()
|
||||
{
|
||||
return this.options.overflowY
|
||||
}
|
||||
set overflowY(value)
|
||||
{
|
||||
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}
|
||||
*/
|
||||
get boxHeight()
|
||||
{
|
||||
return this.options.boxHeight
|
||||
}
|
||||
set boxHeight(value)
|
||||
{
|
||||
this.options.boxHeight = value
|
||||
this.content.screenHeight = value
|
||||
this.update()
|
||||
}
|
||||
|
||||
/**
|
||||
* scrollbar size in pixels
|
||||
* @type {number}
|
||||
*/
|
||||
get scrollbarSize()
|
||||
{
|
||||
return this.options.scrollbarSize
|
||||
}
|
||||
set scrollbarSize(value)
|
||||
{
|
||||
this.options.scrollbarSize = value
|
||||
}
|
||||
|
||||
/**
|
||||
* width of scrollbox less the scrollbar (if visible)
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get contentWidth()
|
||||
{
|
||||
return this.options.boxWidth - (this.isScrollbarVertical ? this.options.scrollbarSize : 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* height of scrollbox less the scrollbar (if visible)
|
||||
* @type {number}
|
||||
* @readonly
|
||||
*/
|
||||
get contentHeight()
|
||||
{
|
||||
return this.options.boxHeight - (this.isScrollbarHorizontal ? this.options.scrollbarSize : 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* is the vertical scrollbar visible
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get isScrollbarVertical()
|
||||
{
|
||||
return this._isScrollbarVertical
|
||||
}
|
||||
|
||||
/**
|
||||
* is the horizontal scrollbar visible
|
||||
* @type {boolean}
|
||||
* @readonly
|
||||
*/
|
||||
get isScrollbarHorizontal()
|
||||
{
|
||||
return this._isScrollbarHorizontal
|
||||
}
|
||||
|
||||
/**
|
||||
* top coordinate of scrollbar
|
||||
*/
|
||||
get scrollTop()
|
||||
{
|
||||
return this.content.top
|
||||
}
|
||||
|
||||
/**
|
||||
* left coordinate of scrollbar
|
||||
*/
|
||||
get scrollLeft()
|
||||
{
|
||||
return this.content.left
|
||||
}
|
||||
|
||||
/**
|
||||
* width of content area
|
||||
* if not set then it uses content.width to calculate width
|
||||
*/
|
||||
get scrollWidth()
|
||||
{
|
||||
return this._scrollWidth || this.content.width
|
||||
}
|
||||
set scrollWidth(value)
|
||||
{
|
||||
this._scrollWidth = value
|
||||
}
|
||||
|
||||
/**
|
||||
* height of content area
|
||||
* if not set then it uses content.height to calculate height
|
||||
*/
|
||||
get scrollHeight()
|
||||
{
|
||||
return this._scrollHeight || this.content.height
|
||||
}
|
||||
set scrollHeight(value)
|
||||
{
|
||||
this._scrollHeight = value
|
||||
}
|
||||
|
||||
/**
|
||||
* draws scrollbars
|
||||
* @private
|
||||
*/
|
||||
_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
|
||||
this.scrollbar.clear()
|
||||
let options = {}
|
||||
options.left = 0
|
||||
options.right = this.scrollWidth + (this._isScrollbarVertical ? this.options.scrollbarSize : 0)
|
||||
options.top = 0
|
||||
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)
|
||||
this.scrollbarTop = (this.content.top / height) * this.boxHeight
|
||||
this.scrollbarTop = this.scrollbarTop < 0 ? 0 : this.scrollbarTop
|
||||
this.scrollbarHeight = (this.boxHeight / height) * this.boxHeight
|
||||
this.scrollbarHeight = this.scrollbarTop + this.scrollbarHeight > this.boxHeight ? this.boxHeight - this.scrollbarTop : this.scrollbarHeight
|
||||
this.scrollbarLeft = (this.content.left / width) * this.boxWidth
|
||||
this.scrollbarLeft = this.scrollbarLeft < 0 ? 0 : this.scrollbarLeft
|
||||
this.scrollbarWidth = (this.boxWidth / width) * this.boxWidth
|
||||
this.scrollbarWidth = this.scrollbarWidth + this.scrollbarLeft > this.boxWidth ? this.boxWidth - this.scrollbarLeft : this.scrollbarWidth
|
||||
if (this.isScrollbarVertical)
|
||||
{
|
||||
this.scrollbar
|
||||
.beginFill(this.options.scrollbarBackground, this.options.scrollbarBackgroundAlpha)
|
||||
.drawRect(this.boxWidth - this.scrollbarSize + this.options.scrollbarOffsetVertical, 0, this.scrollbarSize, this.boxHeight)
|
||||
.endFill()
|
||||
}
|
||||
if (this.isScrollbarHorizontal)
|
||||
{
|
||||
this.scrollbar
|
||||
.beginFill(this.options.scrollbarBackground, this.options.scrollbarBackgroundAlpha)
|
||||
.drawRect(0, this.boxHeight - this.scrollbarSize + this.options.scrollbarOffsetHorizontal, this.boxWidth, this.scrollbarSize)
|
||||
.endFill()
|
||||
}
|
||||
if (this.isScrollbarVertical)
|
||||
{
|
||||
this.scrollbar
|
||||
.beginFill(this.options.scrollbarForeground, this.options.scrollbarForegroundAlpha)
|
||||
.drawRect(this.boxWidth - this.scrollbarSize + this.options.scrollbarOffsetVertical, this.scrollbarTop, this.scrollbarSize, this.scrollbarHeight)
|
||||
.endFill()
|
||||
}
|
||||
if (this.isScrollbarHorizontal)
|
||||
{
|
||||
this.scrollbar
|
||||
.beginFill(this.options.scrollbarForeground, this.options.scrollbarForegroundAlpha)
|
||||
.drawRect(this.scrollbarLeft, this.boxHeight - this.scrollbarSize + this.options.scrollbarOffsetHorizontal, this.scrollbarWidth, this.scrollbarSize)
|
||||
.endFill()
|
||||
}
|
||||
// this.content.forceHitArea = new PIXI.Rectangle(0, 0 , this.boxWidth, this.boxHeight)
|
||||
this.activateFade()
|
||||
}
|
||||
|
||||
/**
|
||||
* draws mask layer
|
||||
* @private
|
||||
*/
|
||||
_drawMask()
|
||||
{
|
||||
this._maskContent
|
||||
.beginFill(0)
|
||||
.drawRect(0, 0, this.boxWidth, this.boxHeight)
|
||||
.endFill()
|
||||
this.content.mask = this._maskContent
|
||||
}
|
||||
|
||||
/**
|
||||
* call when scrollbox content changes
|
||||
*/
|
||||
update()
|
||||
{
|
||||
this.content.mask = null
|
||||
this._maskContent.clear()
|
||||
if (!this._disabled)
|
||||
{
|
||||
this._drawScrollbars()
|
||||
this._drawMask()
|
||||
if (this.options.dragScroll)
|
||||
{
|
||||
const direction = this.isScrollbarHorizontal && this.isScrollbarVertical ? 'all' : this.isScrollbarHorizontal ? 'x' : 'y'
|
||||
if (direction !== null)
|
||||
{
|
||||
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
|
||||
*/
|
||||
activateFade()
|
||||
{
|
||||
if (this.options.fade)
|
||||
{
|
||||
if (this.fade)
|
||||
{
|
||||
this.ease.remove(this.fade)
|
||||
}
|
||||
this.scrollbar.alpha = 1
|
||||
const time = this.options.fade === true ? 1000 : this.options.fade
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handle pointer down on scrollbar
|
||||
* @param {PIXI.interaction.InteractionEvent} e
|
||||
* @private
|
||||
*/
|
||||
scrollbarDown(e)
|
||||
{
|
||||
const local = this.toLocal(e.data.global)
|
||||
if (this.isScrollbarHorizontal)
|
||||
{
|
||||
if (local.y > this.boxHeight - this.scrollbarSize)
|
||||
{
|
||||
if (local.x >= this.scrollbarLeft && local.x <= this.scrollbarLeft + this.scrollbarWidth)
|
||||
{
|
||||
this.pointerDown = { type: 'horizontal', last: local }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (local.x > this.scrollbarLeft)
|
||||
{
|
||||
this.content.left += this.content.worldScreenWidth
|
||||
this.update()
|
||||
}
|
||||
else
|
||||
{
|
||||
this.content.left -= this.content.worldScreenWidth
|
||||
this.update()
|
||||
}
|
||||
}
|
||||
if (this.options.stopPropagation)
|
||||
{
|
||||
e.stopPropagation()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
if (this.isScrollbarVertical)
|
||||
{
|
||||
if (local.x > this.boxWidth - this.scrollbarSize)
|
||||
{
|
||||
if (local.y >= this.scrollbarTop && local.y <= this.scrollbarTop + this.scrollbarWidth)
|
||||
{
|
||||
this.pointerDown = { type: 'vertical', last: local }
|
||||
}
|
||||
else
|
||||
{
|
||||
if (local.y > this.scrollbarTop)
|
||||
{
|
||||
this.content.top += this.content.worldScreenHeight
|
||||
this.update()
|
||||
}
|
||||
else
|
||||
{
|
||||
this.content.top -= this.content.worldScreenHeight
|
||||
this.update()
|
||||
}
|
||||
}
|
||||
if (this.options.stopPropagation)
|
||||
{
|
||||
e.stopPropagation()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handle pointer move on scrollbar
|
||||
* @param {PIXI.interaction.InteractionEvent} e
|
||||
* @private
|
||||
*/
|
||||
scrollbarMove(e)
|
||||
{
|
||||
if (this.pointerDown)
|
||||
{
|
||||
if (this.pointerDown.type === 'horizontal')
|
||||
{
|
||||
const local = this.toLocal(e.data.global)
|
||||
this.content.left += local.x - this.pointerDown.last.x
|
||||
this.pointerDown.last = local
|
||||
this.update()
|
||||
}
|
||||
else if (this.pointerDown.type === 'vertical')
|
||||
{
|
||||
const local = this.toLocal(e.data.global)
|
||||
this.content.top += local.y - this.pointerDown.last.y
|
||||
this.pointerDown.last = local
|
||||
this.update()
|
||||
}
|
||||
if (this.options.stopPropagation)
|
||||
{
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* handle pointer down on scrollbar
|
||||
* @private
|
||||
*/
|
||||
scrollbarUp()
|
||||
{
|
||||
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)
|
||||
*/
|
||||
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)
|
||||
{
|
||||
this.scrollWidth = options.scrollWidth
|
||||
}
|
||||
if (options.scrollHeight)
|
||||
{
|
||||
this.scrollHeight = options.scrollHeight
|
||||
}
|
||||
this.content.resize(this.options.boxWidth, this.options.boxHeight, this.scrollWidth, this.scrollHeight)
|
||||
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
|
||||
*/
|
||||
ensureVisible(x, y, width, height)
|
||||
{
|
||||
this.content.ensureVisible(x, y, width, height)
|
||||
this._drawScrollbars()
|
||||
}
|
||||
}
|
||||
Vendored
-1
File diff suppressed because one or more lines are too long
+25
-29
@@ -13,7 +13,6 @@
|
||||
<script src="../../dist/iwmlib.3rdparty.js"></script>
|
||||
<script src="../../dist/iwmlib.js"></script>
|
||||
<script src="../../dist/iwmlib.pixi.js"></script>
|
||||
<!-- <script src="./scrollbox.min.js"></script> -->
|
||||
</head>
|
||||
<body onload="Doctest.run()">
|
||||
<h1>Scrollview</h1>
|
||||
@@ -37,38 +36,35 @@
|
||||
const app = new PIXIApp({
|
||||
view: canvas,
|
||||
width: 900,
|
||||
height: 250,
|
||||
height: 400,
|
||||
transparent: false
|
||||
}).setup().run()
|
||||
|
||||
// let scrollview1 = new Scrollview({
|
||||
// x: 10,
|
||||
// y: 20
|
||||
// })
|
||||
app.loader
|
||||
.add('elephant1', './assets/elephant-1.jpg')
|
||||
.add('elephant2', './assets/elephant-2.jpg')
|
||||
.add('elephant3', './assets/elephant-3.jpg')
|
||||
.load((loader, resources) => {
|
||||
const sprite1 = new PIXI.Sprite(resources.elephant1.texture)
|
||||
const sprite2 = new PIXI.Sprite(resources.elephant2.texture)
|
||||
const sprite3 = new PIXI.Sprite(resources.elephant3.texture)
|
||||
|
||||
// let scrollview2 = new Scrollview({
|
||||
// x: 90,
|
||||
// y: 20,
|
||||
// fill: 0xfd355a,
|
||||
// fillActive: 0x5954d3,
|
||||
// controlFill: 0xfecd2d,
|
||||
// controlFillActive: 0xfd413b,
|
||||
// strokeActiveWidth: 4,
|
||||
// controlStrokeActive: 0x50d968,
|
||||
// controlStrokeActiveWidth: 12,
|
||||
// controlStrokeActiveAlpha: .8,
|
||||
// tooltip: 'Dies ist ein Switch'
|
||||
// })
|
||||
const scrollview1 = new Scrollview({boxWidth: 300, boxHeight: 180})
|
||||
scrollview1.content.addChild(sprite1)
|
||||
app.scene.addChild(scrollview1)
|
||||
|
||||
// const scrollbox = new PIXI.extras.Scrollbox({boxWidth: 500, boxHeight: 200})
|
||||
// scrollbox.x = 70
|
||||
// scrollbox.y = 30
|
||||
|
||||
// const sprite = new PIXI.Sprite(resources.fulda.texture)
|
||||
// sprite.scale.set(.5, .5)
|
||||
// scrollbox.content.addChild(sprite)
|
||||
// app.stage.addChild(scrollbox)
|
||||
|
||||
// app.scene.addChild(switch1, switch2)
|
||||
const scrollview2 = new Scrollview({boxWidth: 300, boxHeight: 300})
|
||||
scrollview2.x = 500
|
||||
scrollview2.y = 30
|
||||
sprite2.x = 40
|
||||
sprite2.y = 40
|
||||
sprite2.scale.set(.3, .3)
|
||||
sprite3.x = 60
|
||||
sprite3.y = 100
|
||||
sprite3.alpha = .6
|
||||
sprite3.scale.set(.5, .5)
|
||||
scrollview2.content.addChild(sprite2, sprite3)
|
||||
app.scene.addChild(scrollview2)
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
|
||||
+10
-416
@@ -1,5 +1,4 @@
|
||||
import Theme from './theme.js'
|
||||
import Tooltip from './tooltip.js'
|
||||
import Scrollbox from './scrollbox.js'
|
||||
|
||||
/**
|
||||
* Callback for the switch action.
|
||||
@@ -54,140 +53,22 @@ import Tooltip from './tooltip.js'
|
||||
* app.scene.addChild(switch1)
|
||||
*
|
||||
* @class
|
||||
* @extends PIXI.Container
|
||||
* @see {@link http://pixijs.download/dev/docs/PIXI.Container.html|PIXI.Container}
|
||||
* @see {@link https://www.iwm-tuebingen.de/iwmbrowser/lib/pixi/switch.html|DocTest}
|
||||
* @extends PIXI.extras.Scrollbox
|
||||
* @see {@link https://davidfig.github.io/pixi-scrollbox/jsdoc/Scrollbox.html|Scrollbox}
|
||||
* @see {@link https://davidfig.github.io/pixi-viewport/jsdoc/Viewport.html|Viewport}
|
||||
*/
|
||||
export default class Switch extends PIXI.Container {
|
||||
export default class Scrollview extends Scrollbox {
|
||||
|
||||
/**
|
||||
* Creates an instance of a Switch.
|
||||
*
|
||||
* @constructor
|
||||
* @param {object} [opts] - An options object to specify to style and behaviour of the switch.
|
||||
* @param {number} [opts.id=auto generated] - The id of the switch.
|
||||
* @param {number} [opts.x=0] - The x position of the switch. Can be also set after creation with switch.x = 0.
|
||||
* @param {number} [opts.y=0] - The y position of the switch. Can be also set after creation with switch.y = 0.
|
||||
* @param {string|Theme} [opts.theme=dark] - The theme to use for this switch. Possible values are dark, light, red
|
||||
* or a Theme object.
|
||||
* @param {number} [opts.width=44] - The width of the switch.
|
||||
* @param {number} [opts.height=28] - The height of the switch.
|
||||
* @param {number} [opts.fill=Theme.fill] - The color of the switch background as a hex value.
|
||||
* @param {number} [opts.fillAlpha=Theme.fillAlpha] - The alpha value of the background.
|
||||
* @param {number} [opts.fillActive=Theme.fillActive] - The color of the switch background when activated.
|
||||
* @param {number} [opts.fillActiveAlpha=Theme.fillActiveAlpha] - The alpha value of the background when activated.
|
||||
* @param {number} [opts.stroke=Theme.stroke] - The color of the border as a hex value.
|
||||
* @param {number} [opts.strokeWidth=Theme.strokeWidth] - The width of the border in pixel.
|
||||
* @param {number} [opts.strokeAlpha=Theme.strokeAlpha] - The alpha value of the border.
|
||||
* @param {number} [opts.strokeActive=Theme.strokeActive] - The color of the border when activated.
|
||||
* @param {number} [opts.strokeActiveWidth=Theme.strokeActiveWidth] - The width of the border in pixel when activated.
|
||||
* @param {number} [opts.strokeActiveAlpha=Theme.strokeActiveAlpha] - The alpha value of the border when activated.
|
||||
* @param {number} [opts.controlFill=Theme.stroke] - The color of the switch control background as a hex value.
|
||||
* @param {number} [opts.controlFillAlpha=Theme.strokeAlpha] - The alpha value of the background.
|
||||
* @param {number} [opts.controlFillActive=Theme.stroke] - The color of the switch control background when activated.
|
||||
* @param {number} [opts.controlFillActiveAlpha=Theme.strokeAlpha] - The alpha value of the background when activated.
|
||||
* @param {number} [opts.controlStroke=Theme.stroke] - The color of the border as a hex value.
|
||||
* @param {number} [opts.controlStrokeWidth=Theme.strokeWidth * 0.8] - The width of the border in pixel.
|
||||
* @param {number} [opts.controlStrokeAlpha=Theme.strokeAlpha] - The alpha value of the border.
|
||||
* @param {number} [opts.controlStrokeActive=Theme.stroke] - The color of the border when activated.
|
||||
* @param {number} [opts.controlStrokeActiveWidth=Theme.strokeActiveWidth * 0.8] - The width of the border in pixel when activated.
|
||||
* @param {number} [opts.controlStrokeActiveAlpha=Theme.strokeActiveAlpha] - The alpha value of the border when activated.
|
||||
* @param {number} [opts.duration=Theme.fast] - The duration of the animation when the switch gets activated in seconds.
|
||||
* @param {number} [opts.durationActive=Theme.fast] - The duration of the animation when the switch gets deactivated in seconds.
|
||||
* @param {boolean} [opts.disabled=false] - Is the switch disabled? When disabled, the switch has a lower alpha value
|
||||
* and cannot be clicked (interactive is set to false).
|
||||
* @param {boolean} [opts.active=false] - Is the button initially active?
|
||||
* @param {actionCallback} [opts.action] - Executed when the switch was triggered in inactive state (by pointerup).
|
||||
* @param {actionActiveCallback} [opts.actionActive] - Executed when the button was triggered in active state (by pointerup).
|
||||
* @param {beforeActionCallback} [opts.beforeAction] - Executed before an action is triggered.
|
||||
* @param {afterActionCallback} [opts.afterAction] - Executed after an action was triggered.
|
||||
* @param {string|object} [opts.tooltip] - A string for the label of the tooltip or an object to configure the tooltip
|
||||
* to display.
|
||||
* @param {boolean} [opts.visible=true] - Is the switch initially visible (property visible)?
|
||||
*/
|
||||
constructor(opts = {}) {
|
||||
|
||||
super()
|
||||
|
||||
const theme = Theme.fromString(opts.theme)
|
||||
this.theme = theme
|
||||
super(opts)
|
||||
|
||||
this.opts = Object.assign({}, {
|
||||
id: PIXI.utils.uid(),
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 44,
|
||||
height: 28,
|
||||
fill: theme.fill,
|
||||
fillAlpha: theme.fillAlpha,
|
||||
fillActive: theme.primaryColor,
|
||||
fillActiveAlpha: theme.fillActiveAlpha,
|
||||
stroke: theme.stroke,
|
||||
strokeWidth: theme.strokeWidth,
|
||||
strokeAlpha: theme.strokeAlpha,
|
||||
strokeActive: theme.primaryColor,
|
||||
strokeActiveWidth: theme.strokeActiveWidth,
|
||||
strokeActiveAlpha: theme.strokeActiveAlpha,
|
||||
controlFill: theme.stroke,
|
||||
controlFillAlpha: theme.strokeAlpha,
|
||||
controlFillActive: theme.stroke,
|
||||
controlFillActiveAlpha: theme.strokeAlpha,
|
||||
controlStroke: theme.stroke,
|
||||
controlStrokeWidth: theme.strokeWidth * .8,
|
||||
controlStrokeAlpha: theme.strokeAlpha,
|
||||
controlStrokeActive: theme.stroke,
|
||||
controlStrokeActiveWidth: theme.strokeActiveWidth * .8,
|
||||
controlStrokeActiveAlpha: theme.strokeActiveAlpha,
|
||||
duration: theme.fast,
|
||||
durationActive: theme.fast,
|
||||
disabled: false,
|
||||
active: false,
|
||||
action: null,
|
||||
actionActive: null,
|
||||
beforeAction: null,
|
||||
afterAction: null,
|
||||
tooltip: null,
|
||||
visible: true
|
||||
}, opts)
|
||||
|
||||
this.opts.controlRadius = this.opts.controlRadius || (this.opts.height / 2)
|
||||
this.opts.controlRadiusActive = this.opts.controlRadiusActive || this.opts.controlRadius
|
||||
|
||||
// Validation
|
||||
//-----------------
|
||||
if (this.opts.height > this.opts.width) {
|
||||
this.opts.height = this.opts.width
|
||||
}
|
||||
|
||||
// Properties
|
||||
//-----------------
|
||||
this.id = this.opts.id
|
||||
this.radius = this.opts.height / 2
|
||||
|
||||
this._active = null
|
||||
this._disabled = null
|
||||
|
||||
this.switchObj = null
|
||||
this.control = null
|
||||
this.tooltip = null
|
||||
|
||||
this.visible = this.opts.visible
|
||||
|
||||
// animated
|
||||
//-----------------
|
||||
this.tempAnimated = {
|
||||
fill: this.opts.fill,
|
||||
fillAlpha: this.opts.fillAlpha,
|
||||
stroke: this.opts.stroke,
|
||||
strokeWidth: this.opts.strokeWidth,
|
||||
strokeAlpha: this.opts.strokeAlpha,
|
||||
controlFill: this.opts.controlFill,
|
||||
controlFillAlpha: this.opts.controlFillAlpha,
|
||||
controlStroke: this.opts.controlStroke,
|
||||
controlStrokeWidth: this.opts.controlStrokeWidth,
|
||||
controlStrokeAlpha: this.opts.controlStrokeAlpha,
|
||||
controlRadius: this.opts.controlRadius
|
||||
}
|
||||
this.opts = opts
|
||||
|
||||
// setup
|
||||
//-----------------
|
||||
@@ -202,306 +83,19 @@ export default class Switch extends PIXI.Container {
|
||||
* Creates children and instantiates everything.
|
||||
*
|
||||
* @private
|
||||
* @return {Switch} A reference to the switch for chaining.
|
||||
* @return {Scrollview} A reference to the Scrollview for chaining.
|
||||
*/
|
||||
setup() {
|
||||
|
||||
// Switch
|
||||
//-----------------
|
||||
let switchObj = new PIXI.Graphics()
|
||||
this.switchObj = switchObj
|
||||
this.addChild(switchObj)
|
||||
|
||||
// Control
|
||||
//-----------------
|
||||
this.xInactive = this.opts.controlRadius
|
||||
this.xActive = this.opts.width - this.opts.controlRadiusActive
|
||||
|
||||
let control = new PIXI.Graphics()
|
||||
control.x = this.opts.active ? this.xActive : this.xInactive
|
||||
control.y = this.opts.height / 2
|
||||
|
||||
this.control = control
|
||||
|
||||
this.addChild(this.control)
|
||||
|
||||
// interaction
|
||||
//-----------------
|
||||
this.switchObj.on('pointerover', e => {
|
||||
TweenLite.to(this.control, this.theme.fast, {alpha: .83})
|
||||
})
|
||||
|
||||
this.switchObj.on('pointerout', e => {
|
||||
TweenLite.to(this.control, this.theme.fast, {alpha: 1})
|
||||
})
|
||||
|
||||
this.switchObj.on('pointerdown', e => {
|
||||
TweenLite.to(this.control, this.theme.fast, {alpha: .7})
|
||||
})
|
||||
|
||||
this.switchObj.on('pointerup', e => {
|
||||
|
||||
if (this.opts.beforeAction) {
|
||||
this.opts.beforeAction.call(this, e, this)
|
||||
}
|
||||
|
||||
this.active = !this.active
|
||||
|
||||
if (this.active) {
|
||||
if (this.opts.action) {
|
||||
this.opts.action.call(this, e, this)
|
||||
}
|
||||
} else {
|
||||
if (this.opts.actionActive) {
|
||||
this.opts.actionActive.call(this, e, this)
|
||||
}
|
||||
}
|
||||
|
||||
TweenLite.to(this.control, this.theme.fast, {alpha: .83})
|
||||
|
||||
if (this.opts.afterAction) {
|
||||
this.opts.afterAction.call(this, e, this)
|
||||
}
|
||||
})
|
||||
|
||||
// disabled
|
||||
//-----------------
|
||||
this.disabled = this.opts.disabled
|
||||
|
||||
// active
|
||||
//-----------------
|
||||
this.active = this.opts.active
|
||||
|
||||
// tooltip
|
||||
//-----------------
|
||||
if (this.opts.tooltip) {
|
||||
if (typeof this.opts.tooltip === 'string') {
|
||||
this.tooltip = new Tooltip({
|
||||
object: this,
|
||||
content: this.opts.tooltip
|
||||
})
|
||||
} else {
|
||||
this.opts.tooltip.object = this
|
||||
this.tooltip = new Tooltip(this.opts.tooltip)
|
||||
}
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called to refresh the layout of the switch. Can be used after resizing.
|
||||
* Should be called to refresh the layout of the Scrollview. Can be used after resizing.
|
||||
*
|
||||
* @return {Switch} A reference to the switch for chaining.
|
||||
* @return {Scrollview} A reference to the Scrollview for chaining.
|
||||
*/
|
||||
layout() {
|
||||
|
||||
// set position
|
||||
//-----------------
|
||||
this.position.set(this.opts.x, this.opts.y)
|
||||
|
||||
// draw
|
||||
//-----------------
|
||||
this.draw()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the switch to the canvas.
|
||||
*
|
||||
* @private
|
||||
* @return {Switch} A reference to the switch for chaining.
|
||||
*/
|
||||
draw() {
|
||||
|
||||
this.switchObj.clear()
|
||||
if (this.active) {
|
||||
this.switchObj.lineStyle(this.opts.strokeActiveWidth, this.opts.strokeActive, this.opts.strokeActiveAlpha)
|
||||
this.switchObj.beginFill(this.opts.fillActive, this.opts.fillActiveAlpha)
|
||||
} else {
|
||||
this.switchObj.lineStyle(this.opts.strokeWidth, this.opts.stroke, this.opts.strokeAlpha)
|
||||
this.switchObj.beginFill(this.opts.fill, this.opts.fillAlpha)
|
||||
}
|
||||
this.switchObj.moveTo(this.radius, 0)
|
||||
this.switchObj.lineTo(this.opts.width - this.radius, 0)
|
||||
this.switchObj.arcTo(this.opts.width, 0, this.opts.width, this.radius, this.radius)
|
||||
this.switchObj.lineTo(this.opts.width, this.radius + 1) // BUGFIX: If not specified, there is a small area without a stroke.
|
||||
this.switchObj.arcTo(this.opts.width, this.opts.height, this.opts.width - this.radius, this.opts.height, this.radius)
|
||||
this.switchObj.lineTo(this.radius, this.opts.height)
|
||||
this.switchObj.arcTo(0, this.opts.height, 0, this.radius, this.radius)
|
||||
this.switchObj.arcTo(0, 0, this.radius, 0, this.radius)
|
||||
this.switchObj.endFill()
|
||||
|
||||
// Draw control
|
||||
this.control.clear()
|
||||
if (this.active) {
|
||||
this.control.lineStyle(this.opts.controlStrokeActiveWidth, this.opts.controlStrokeActive, this.opts.controlStrokeActiveAlpha)
|
||||
this.control.beginFill(this.opts.controlFillActive, this.opts.controlFillActiveAlpha)
|
||||
this.control.drawCircle(0, 0, this.opts.controlRadiusActive - 1)
|
||||
} else {
|
||||
this.control.lineStyle(this.opts.controlStrokeWidth, this.opts.controlStroke, this.opts.controlStrokeAlpha)
|
||||
this.control.beginFill(this.opts.controlFill, this.opts.controlFillAlpha)
|
||||
this.control.drawCircle(0, 0, this.opts.controlRadius - 1)
|
||||
}
|
||||
this.control.endFill()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the animation.
|
||||
*
|
||||
* @private
|
||||
* @return {Switch} A reference to the switch for chaining.
|
||||
*/
|
||||
drawAnimated() {
|
||||
|
||||
this.switchObj.clear()
|
||||
this.switchObj.lineStyle(this.tempAnimated.strokeWidth, this.tempAnimated.stroke, this.tempAnimated.strokeAlpha)
|
||||
this.switchObj.beginFill(this.tempAnimated.fill, this.tempAnimated.fillAlpha)
|
||||
this.switchObj.moveTo(this.radius, 0)
|
||||
this.switchObj.lineTo(this.opts.width - this.radius, 0)
|
||||
this.switchObj.arcTo(this.opts.width, 0, this.opts.width, this.radius, this.radius)
|
||||
this.switchObj.lineTo(this.opts.width, this.radius + 1) // BUGFIX: If not specified, there is a small area without a stroke.
|
||||
this.switchObj.arcTo(this.opts.width, this.opts.height, this.opts.width - this.radius, this.opts.height, this.radius)
|
||||
this.switchObj.lineTo(this.radius, this.opts.height)
|
||||
this.switchObj.arcTo(0, this.opts.height, 0, this.radius, this.radius)
|
||||
this.switchObj.arcTo(0, 0, this.radius, 0, this.radius)
|
||||
this.switchObj.endFill()
|
||||
|
||||
this.control.clear()
|
||||
this.control.lineStyle(this.tempAnimated.controlStrokeWidth, this.tempAnimated.controlStroke, this.tempAnimated.controlStrokeAlpha)
|
||||
this.control.beginFill(this.tempAnimated.controlFill, this.tempAnimated.controlFillAlpha)
|
||||
this.control.drawCircle(0, 0, this.tempAnimated.controlRadius - 1)
|
||||
this.control.endFill()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the active state.
|
||||
*
|
||||
* @member {boolean}
|
||||
*/
|
||||
get active() {
|
||||
return this._active
|
||||
}
|
||||
|
||||
set active(value) {
|
||||
|
||||
this._active = value
|
||||
|
||||
if (this._active) {
|
||||
|
||||
TweenLite.to(this.control, this.opts.duration, {x: this.xActive})
|
||||
TweenLite.to(this.tempAnimated, this.opts.duration, {
|
||||
colorProps: {
|
||||
fill: this.opts.fillActive,
|
||||
stroke: this.opts.strokeActive,
|
||||
controlFill: this.opts.controlFillActive,
|
||||
controlStroke: this.opts.controlStrokeActive,
|
||||
format: 'number'
|
||||
},
|
||||
fillAlpha: this.opts.fillActiveAlpha,
|
||||
strokeWidth: this.opts.strokeActiveWidth,
|
||||
strokeAlpha: this.opts.strokeActiveAlpha,
|
||||
controlFillAlpha: this.opts.controlFillActiveAlpha,
|
||||
controlStrokeWidth: this.opts.controlStrokeActiveWidth,
|
||||
controlStrokeAlpha: this.opts.controlStrokeActiveAlpha,
|
||||
controlRadius: this.opts.controlRadiusActive,
|
||||
onUpdate: () => this.drawAnimated(),
|
||||
onComplete: () => this.draw()
|
||||
})
|
||||
|
||||
|
||||
} else {
|
||||
TweenLite.to(this.control, this.opts.durationActive, {x: this.xInactive})
|
||||
TweenLite.to(this.tempAnimated, this.opts.durationActive, {
|
||||
colorProps: {
|
||||
fill: this.opts.fill,
|
||||
stroke: this.opts.stroke,
|
||||
controlFill: this.opts.controlFill,
|
||||
controlStroke: this.opts.controlStroke,
|
||||
format: 'number'
|
||||
},
|
||||
fillAlpha: this.opts.fillAlpha,
|
||||
strokeWidth: this.opts.strokeWidth,
|
||||
strokeAlpha: this.opts.strokeAlpha,
|
||||
controlFillAlpha: this.opts.controlFillAlpha,
|
||||
controlStrokeWidth: this.opts.controlStrokeWidth,
|
||||
controlStrokeAlpha: this.opts.controlStrokeAlpha,
|
||||
controlRadius: this.opts.controlRadius,
|
||||
onUpdate: () => this.drawAnimated(),
|
||||
onComplete: () => this.draw()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or sets the disabled state. When disabled, the switch cannot be clicked.
|
||||
*
|
||||
* @member {boolean}
|
||||
*/
|
||||
get disabled() {
|
||||
return this._disabled
|
||||
}
|
||||
|
||||
set disabled(value) {
|
||||
|
||||
this._disabled = value
|
||||
|
||||
if (this._disabled) {
|
||||
this.switchObj.interactive = false
|
||||
this.switchObj.buttonMode = false
|
||||
this.switchObj.alpha = .5
|
||||
this.control.alpha = .5
|
||||
} else {
|
||||
this.switchObj.interactive = true
|
||||
this.switchObj.buttonMode = true
|
||||
this.switchObj.alpha = 1
|
||||
this.control.alpha = 1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the switch (sets his alpha values to 1).
|
||||
*
|
||||
* @return {Switch} A reference to the switch for chaining.
|
||||
*/
|
||||
show() {
|
||||
|
||||
this.opts.strokeAlpha = 1
|
||||
this.opts.strokeActiveAlpha = 1
|
||||
this.opts.fillAlpha = 1
|
||||
this.opts.fillActiveAlpha = 1
|
||||
this.opts.controlStrokeAlpha = 1
|
||||
this.opts.controlStrokeActiveAlpha = 1
|
||||
this.opts.controlFillAlpha = 1
|
||||
this.opts.controlFillActiveAlpha = 1
|
||||
|
||||
this.layout()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Hides the switch (sets his alpha values to 1).
|
||||
*
|
||||
* @return {Switch} A reference to the switch for chaining.
|
||||
*/
|
||||
hide() {
|
||||
|
||||
this.opts.strokeAlpha = 0
|
||||
this.opts.strokeActiveAlpha = 0
|
||||
this.opts.fillAlpha = 0
|
||||
this.opts.fillActiveAlpha = 0
|
||||
this.opts.controlStrokeAlpha = 0
|
||||
this.opts.controlStrokeActiveAlpha = 0
|
||||
this.opts.controlFillAlpha = 0
|
||||
this.opts.controlFillActiveAlpha = 0
|
||||
|
||||
this.layout()
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user