src/components/input/text_field.js
import Phaser from "phaser"
import _ from "lodash"
import TextDisplay from "../display/text_display"
import KeyboardDrawer from "./keyboard_drawer"
/**
* Draws an interactive button in the display
*/
class TextField extends TextDisplay {
/**
* @param {object} scene - The container Phaser.Scene
* @param {number} x - The x position of the TextDisplay in the game world
* @param {number} y - The y position of the TextDisplay in the game world
* @param {number} [width=200] - Display width in pixels
* @param {number} [height=400] - Display height in pixels
* @param {string} [content=""] - The text displayed
*/
constructor(
scene,
{
x, y,
width=400, height=200,
initialText="",
styles={
fontSize: 24,
fontFamily: "Helvetica",
align: "left",
padding: {top: 10, left: 10, right: 10, bottom: 10},
wordWrap: {width: width - 10}
},
hasOutline=true,
helpTextValue="This is the help text",
editMode="drawer",
submitOnEnter=false,
}
) {
// group attributes
super(
scene,
{
x: x, y: y,
width: width, height: height,
initialText: initialText,
styles: styles,
hasOutline: hasOutline,
}
)
this.helpTextValue = helpTextValue
// set up listener for button press event
this.scene.events.on("domlessKeyboardButtonPress", function(buttonChar, keyCode) {
if (this.active) {
this.addText(buttonChar, keyCode)
}
}, this)
// activate on click inside / deactivate on click outside
this.scene.input.on("pointerup", this.pointerListener, this)
this.editMode = editMode
this.submitOnEnter = submitOnEnter
if (this.editMode === "drawer" && !this.scene.keyboardDrawer) {
this.scene.keyboardDrawer = new KeyboardDrawer(
this.scene,
{
enterLabel: this.submitOnEnter ? "OK" : "\u23CE"
}
)
}
//submit on TAB
if (!this.scene.tabKey) {
this.scene.tabKey = this.scene.input.keyboard.addKey("TAB")
}
if (!this.scene.shiftKey) {
this.scene.shiftKey = this.scene.input.keyboard.addKey("SHIFT")
}
// calc pixel width of space char, for cursor
let testNoSpace = this.scene.add.text(0, 0, "II", this.styles)
let testSpace = this.scene.add.text(0, 0, "I I", this.styles)
this.spacePixelWidth = testSpace.width - testNoSpace.width
testNoSpace.destroy()
testSpace.destroy()
this.scene.input.keyboard.on("keydown-TAB", this.handleTab, this)
this.setInteractive()
this.updateOn = _.union(this.updateOn, ["x", "y", "width", "height", "styles"])
this.updateCallback = this.initTextFieldComponents
this.initTextFieldComponents()
}
initTextFieldComponents(diffObject, diffOld) {
// don't do anything if I'm not yet initialized
if (!this.initialized) {
return
}
super.initTextDisplayComponents(diffObject, diffOld)
// add or reinit cursor Text object
if (this.cursor) {
this.cursor.setStyle(this.styles)
this.placeCursor()
} else {
this.cursor = this.scene.add.text(-this.width / 2, -this.height / 2, "_", this.styles)
this.cursor.setOrigin(0, 0)
this.add(this.cursor)
this.cursor.setAlpha(0)
}
this.cursor.setMask(this.contentMask)
// set custom word wrapping to account for cursor
this.content.setWordWrapCallback(
function(text) {
let wrappedText = this.content.basicWordWrap(
(this.cursor ? text + "_" : text),
this.content.context,
this.width - this.content.padding.right * 2
).trimRight().slice(0, -1)
return wrappedText
}, this
)
// set up help text
if (this.helpText) {
this.helpText.setStyle(this.styles)
this.helpText.setPosition(-this.width / 2, -this.height / 2)
} else {
this.helpText = this.scene.add.text(-this.width / 2, -this.height / 2, this.helpTextValue, this.styles)
this.helpText.setColor("gray")
this.add(this.helpText)
}
}
handleTab(event) {
event.preventDefault()
if (this.active) {
this.submit()
}
}
pointerListener(pointer, currentlyOver) {
// don't do anything if we were just dragging
if (pointer.isDragging) {
return
}
// don't do anything if we are tweening the keyboard drawer
if (
this.scene.keyboardDrawer &&
this.scene.keyboardDrawer.slideTween &&
this.scene.keyboardDrawer.slideTween.progress > 0 &&
this.scene.keyboardDrawer.slideTween.progress < 1
) {
return
}
// activate on click inside this, deactivate on click outside
if (_.includes(currentlyOver, this)) {
this.activate()
} else {
// is the cursor over any other TextFields besides this one?
let deactivateTo = _.find(currentlyOver, o => o.constructor.name === "TextField" && o.id !== this.id)
this.deactivate(false, deactivateTo)
}
}
activate(force=false) {
if (!this.active || force) {
console.log("activating textField " + this.id)
super.activate()
this.cursor.setAlpha(1)
this.cursorTween = this.scene.add.tween(
{
targets: [this.cursor],
alpha: 0,
duration: 250,
yoyo: true,
repeat: -1
}
)
this.helpText.setAlpha(0)
if (this.editMode === "drawer") {
// if this field is part of a form
// the drawer should push the whole form up
let drawerPushElement = this
if (this.form && !this.form.drawer) {
drawerPushElement = this.form.submitButton
}
// but don't push me above the top of the viewport
let
myTop = this.y - this.height,
maxPush = myTop - this.scene.cameras.main.scrollY
this.scene.keyboardDrawer.activate(drawerPushElement, maxPush)
}
}
}
deactivate(force=false, to=null) {
if (this.active || force) {
console.log("deactivating textField " + this.id)
// deactivate but don't disable interactive
super.deactivate(false, false)
this.cursor.setAlpha(0)
if (this.cursorTween) {
this.cursorTween.stop()
}
if (!this.content.text) {
this.helpText.setAlpha(1)
}
// Deactivate the keyboard drawer
// Or move it to the next textField
if (this.editMode === "drawer") {
if (to) {
this.scene.keyboardDrawer.reFocus(to)
} else {
this.scene.keyboardDrawer.deactivate(this)
}
}
}
}
submit() {
let nextField = null
if (this.scene.shiftKey.isDown && this.scene.tabKey.isDown && this.form.previousField(this)) {
nextField = this.form.previousField(this)
} else if (!this.scene.shiftKey.isDown && this.form && this.form.nextField(this)) {
nextField = this.form.nextField(this)
}
this.deactivate(false, nextField)
}
placeCursor() {
let wrappedText = this.content.basicWordWrap(
this.content.text + "_",
this.content.context,
this.width - this.content.padding.right * 2).split("\n")
let lastLineContent = _.last(wrappedText).trimRight().slice(0, -1)
let lastLine = this.scene.add.text(0, 0, lastLineContent, this.styles)
this.cursor.x = this.content.x + lastLine.width - this.content.padding.right * 2
lastLine.destroy()
// calc cursor y pos
let contentHeight = this.content.height
if (this.content.height > this.height) {
contentHeight = this.height
}
this.cursor.y = -this.height / 2 + contentHeight - this.cursor.height
// modify cursor position if we overflowed a line
if (this.cursor.x + this.cursor.width > this.width / 2) {
this.cursor.x = this.content.x - this.content.width / 2
this.cursor.y = -this.height / 2 + contentHeight
}
}
addText(extraText, keyCode) {
this.pageDownButton.deactivate(true)
if (keyCode === Phaser.Input.Keyboard.KeyCodes.BACKSPACE) {
this.content.setText(this.content.text.slice(0, -1))
} else if (keyCode === Phaser.Input.Keyboard.KeyCodes.ENTER && this.submitOnEnter) {
this.submit()
} else if (extraText) {
this.content.setText(this.content.text + extraText)
}
this.content.updateText()
if (this.content.height > this.height) {
this.content.y = -(this.height / 2 + (this.content.height - this.height))
} else {
this.content.y = -this.height / 2
}
this.placeCursor()
// enable the pageUp button if we have overflow content
// and it is not already enabled. Otherwise disable.
if (this.content.y < -this.height / 2) {
if (!this.pageUpButton.alpha) {
this.pageUpButton.activate(true)
}
} else if (this.pageUpButton.alpha) {
this.pageUpButton.deactivate(true)
}
}
clearText() {
this.pageDownButton.deactivate(true)
this.pageUpButton.deactivate(true)
this.content.setText("")
this.content.updateText()
this.content.y = -this.height / 2
this.placeCursor()
}
pageUp() {
// assume we are scrolling up a page
let scrollY = this.height
// modify behavior if we are near the top of the content
let disablePageUp = false
if (this.content.y >= -this.height - this.height / 2) {
scrollY = -this.content.y - this.height / 2
disablePageUp = true
}
// tween it by calling the parent method
super.pageUp(scrollY, null, disablePageUp)
}
pageDown() {
// assume we a scrolling down a page
let contentBottom = this.content.y + this.content.height
let scrollY = this.height
// modify behavior if we are near the bottom of the content
let disablePageDown = false
if (contentBottom <= this.height + this.height / 2) {
scrollY = contentBottom - this.height / 2
disablePageDown = true
}
// tween it by calling the parent method
super.pageDown(scrollY, null, disablePageDown)
}
}
export default TextField