|
| 1 | +import './input-field-cursor' |
| 2 | +import './input-field-type' |
| 3 | + |
| 4 | +declare global { |
| 5 | + namespace modmanager.gui { |
| 6 | + interface InputField extends ig.FocusGui { |
| 7 | + gfx: ig.Image |
| 8 | + value: string[] |
| 9 | + bg: sc.ButtonBgGui |
| 10 | + focusTimer: number |
| 11 | + alphaTimer: number |
| 12 | + animateOnPress: boolean |
| 13 | + noFocusOnPressed: boolean |
| 14 | + submitSound: ig.Sound |
| 15 | + blockedSound: ig.Sound |
| 16 | + type: modmanager.gui.InputFieldType |
| 17 | + boundProcessInput: (this: Window, ev: KeyboardEvent) => any |
| 18 | + validChars: RegExp |
| 19 | + onCharacterInput: (value: string, key: string) => any |
| 20 | + dummyForClipping: sc.DummyContainer |
| 21 | + highlight: sc.ButtonHighlightGui |
| 22 | + textChild: sc.TextGui |
| 23 | + cursorTick: number |
| 24 | + cursorPos: number |
| 25 | + cursor: InputFieldCursor |
| 26 | + obscure: boolean |
| 27 | + obscureChar: string |
| 28 | + |
| 29 | + calculateCursorPos(this: this): number |
| 30 | + getValueAsString(this: this): string |
| 31 | + processInput(this: this, event: KeyboardEvent): void |
| 32 | + setTextChildText(this: this, text: string): void |
| 33 | + setText(this: this, text: string): void |
| 34 | + unsetFocus(this: this): void |
| 35 | + updateCursorPos(this: this, delta: number): void |
| 36 | + setObscure(this: this, obscure: boolean): void |
| 37 | + } |
| 38 | + |
| 39 | + interface InputFieldCon extends ImpactClass<InputField> { |
| 40 | + new ( |
| 41 | + width: number, |
| 42 | + height: number, |
| 43 | + type?: modmanager.gui.InputFieldType, |
| 44 | + obscure?: boolean, |
| 45 | + obscureChar?: string |
| 46 | + ): InputField |
| 47 | + } |
| 48 | + |
| 49 | + let InputField: InputFieldCon |
| 50 | + } |
| 51 | +} |
| 52 | + |
| 53 | +modmanager.gui.InputField = ig.FocusGui.extend({ |
| 54 | + gfx: new ig.Image('media/gui/buttons.png'), |
| 55 | + value: [], |
| 56 | + bg: null, |
| 57 | + focusTimer: 0, |
| 58 | + alphaTimer: 0, |
| 59 | + animateOnPress: false, |
| 60 | + noFocusOnPressed: false, |
| 61 | + submitSound: sc.BUTTON_SOUND.submit, |
| 62 | + blockedSound: sc.BUTTON_SOUND.denied, |
| 63 | + type: null, |
| 64 | + boundProcessInput: null, |
| 65 | + validChars: /[a-zA-Z0-9,! ]*/, |
| 66 | + cursorPos: 0, |
| 67 | + onCharacterInput: undefined, |
| 68 | + dummyForClipping: null, |
| 69 | + cursorTick: 0, |
| 70 | + cursor: undefined, |
| 71 | + obscure: false, |
| 72 | + obscureChar: '*', |
| 73 | + init(width: number, height: number, type?: modmanager.gui.InputFieldType, obscure?: boolean, obscureChar?: string) { |
| 74 | + this.parent(true) |
| 75 | + this.setSize(width, height) |
| 76 | + |
| 77 | + this.obscure = obscure || false |
| 78 | + this.obscureChar = obscureChar || '*' |
| 79 | + |
| 80 | + this.hook.clip = true |
| 81 | + |
| 82 | + this.type = type || modmanager.gui.INPUT_FIELD_TYPE.DEFAULT |
| 83 | + |
| 84 | + this.bg = new sc.ButtonBgGui(this.hook.size.x, this.type) |
| 85 | + this.bg.setAlign(ig.GUI_ALIGN.X_LEFT, ig.GUI_ALIGN.Y_TOP) |
| 86 | + this.bg.hook.size = this.hook.size |
| 87 | + this.addChildGui(this.bg) |
| 88 | + |
| 89 | + this.highlight = new sc.ButtonHighlightGui(this.hook.size.x, this.type) |
| 90 | + this.addChildGui(this.highlight) |
| 91 | + |
| 92 | + this.textChild = new sc.TextGui(this.value, { |
| 93 | + speed: ig.TextBlock.SPEED.IMMEDIATE, |
| 94 | + }) |
| 95 | + |
| 96 | + this.textChild.setAlign(ig.GUI_ALIGN.X_LEFT, ig.GUI_ALIGN.Y_TOP) |
| 97 | + |
| 98 | + // #region dummy |
| 99 | + this.dummyForClipping = new sc.DummyContainer(this.textChild) |
| 100 | + this.dummyForClipping.setAlign(ig.GUI_ALIGN.X_LEFT, ig.GUI_ALIGN.Y_TOP) |
| 101 | + this.dummyForClipping.setPos(4, 1) |
| 102 | + this.dummyForClipping.setSize(width - 8, height) |
| 103 | + this.addChildGui(this.dummyForClipping) |
| 104 | + // #endregion |
| 105 | + |
| 106 | + // #region cursor |
| 107 | + this.cursor = new modmanager.gui.InputFieldCursor('#FF6D00') |
| 108 | + this.cursor.hook.pos.y = 2 |
| 109 | + // Set initial cursor position. |
| 110 | + this.cursor.hook.pos.x = this.calculateCursorPos() |
| 111 | + this.addChildGui(this.cursor) |
| 112 | + // #endregion |
| 113 | + |
| 114 | + this.boundProcessInput = this.processInput.bind(this) |
| 115 | + this.validChars = /[a-zA-Z0-9,! ]*/ |
| 116 | + }, |
| 117 | + |
| 118 | + focusGained() { |
| 119 | + this.parent() |
| 120 | + ig.input.ignoreKeyboard = true |
| 121 | + for (const action of Object.keys(ig.input.actions) as ig.Input.KnownAction[]) ig.input.actions[action] = false |
| 122 | + this.cursor.active = true |
| 123 | + window.addEventListener('keydown', this.boundProcessInput, false) |
| 124 | + }, |
| 125 | + |
| 126 | + focusLost() { |
| 127 | + this.parent() |
| 128 | + ig.input.ignoreKeyboard = false |
| 129 | + this.cursor.active = false |
| 130 | + window.removeEventListener('keydown', this.boundProcessInput) |
| 131 | + }, |
| 132 | + |
| 133 | + processInput(event: KeyboardEvent) { |
| 134 | + event.preventDefault() |
| 135 | + switch (event.code) { |
| 136 | + case 'ArrowLeft': |
| 137 | + this.updateCursorPos(-1) |
| 138 | + break |
| 139 | + case 'ArrowRight': |
| 140 | + this.updateCursorPos(1) |
| 141 | + break |
| 142 | + case 'Home': |
| 143 | + this.cursorPos = 0 |
| 144 | + break |
| 145 | + case 'End': |
| 146 | + this.cursorPos = this.value.length |
| 147 | + break |
| 148 | + default: { |
| 149 | + let old = this.getValueAsString() |
| 150 | + |
| 151 | + if (event.key.length === 1 && this.validChars.test(event.key)) { |
| 152 | + this.value.splice(this.cursorPos, 0, event.key) |
| 153 | + this.updateCursorPos(1) |
| 154 | + } else if (event.code === 'Backspace' && this.value.length > 0 && this.cursorPos !== 0) { |
| 155 | + // Backspace |
| 156 | + this.value.splice(this.cursorPos - 1, 1) |
| 157 | + this.updateCursorPos(-1) |
| 158 | + } else if (event.code === 'Delete' && this.value.length > 0 && this.cursorPos !== this.value.length) { |
| 159 | + this.value.splice(this.cursorPos, 1) |
| 160 | + } |
| 161 | + |
| 162 | + let text = this.getValueAsString() |
| 163 | + if (text !== old) { |
| 164 | + this.setTextChildText(text) |
| 165 | + |
| 166 | + if (this.onCharacterInput) { |
| 167 | + this.onCharacterInput(text, event.key) |
| 168 | + } |
| 169 | + } |
| 170 | + |
| 171 | + this.cursor.movingTimer = 1 |
| 172 | + |
| 173 | + break |
| 174 | + } |
| 175 | + } |
| 176 | + |
| 177 | + this.cursor.hook.pos.x = this.calculateCursorPos() |
| 178 | + }, |
| 179 | + |
| 180 | + setTextChildText(text: string) { |
| 181 | + if (this.obscure) { |
| 182 | + this.textChild.setText(this.obscureChar.repeat(this.value.length)) |
| 183 | + } else { |
| 184 | + this.textChild.setText(text) |
| 185 | + } |
| 186 | + }, |
| 187 | + |
| 188 | + setText(text: string) { |
| 189 | + this.setTextChildText(text) |
| 190 | + this.value = text.split('') |
| 191 | + this.cursorPos = text.length |
| 192 | + this.cursor.hook.pos.x = this.calculateCursorPos() |
| 193 | + }, |
| 194 | + |
| 195 | + getValueAsString() { |
| 196 | + return this.value.join('') |
| 197 | + }, |
| 198 | + |
| 199 | + updateCursorPos(delta) { |
| 200 | + this.cursorPos += delta |
| 201 | + this.cursorPos = Math.min(Math.max(this.cursorPos, 0), this.value.length) |
| 202 | + }, |
| 203 | + |
| 204 | + calculateCursorPos() { |
| 205 | + let value = this.obscure |
| 206 | + ? this.obscureChar.repeat(this.cursorPos) |
| 207 | + : this.value.slice(0, this.cursorPos).join('') |
| 208 | + return this.textChild.textBlock.font.getTextDimensions(value, this.textChild.textBlock.linePadding).x / 2 + 1.5 |
| 209 | + }, |
| 210 | + |
| 211 | + setObscure(obscure) { |
| 212 | + this.obscure = obscure |
| 213 | + this.setTextChildText(this.getValueAsString()) |
| 214 | + }, |
| 215 | + |
| 216 | + // Liberated from ButtonGui |
| 217 | + update() { |
| 218 | + this.parent() |
| 219 | + |
| 220 | + if (this.keepPressed && this.pressed && this.animateOnPress) { |
| 221 | + // If this element is currently focussed |
| 222 | + if (this.focus) { |
| 223 | + this.alphaTimer = (this.alphaTimer + ig.system.actualTick) % 1 |
| 224 | + } else { |
| 225 | + this.alphaTimer = 0 |
| 226 | + this.focusTimer = 0.1 |
| 227 | + } |
| 228 | + } else if (this.keepPressed && this.pressed && !this.noFocusOnPressed) { |
| 229 | + this.focusTimer = this.focusTimer + ig.system.actualTick |
| 230 | + if (this.focusTimer > 0.1) this.focusTimer = 0.1 // This line is made redundant by this.focusTimer.limit(0, 0.1); |
| 231 | + this.alphaTimer = 0 |
| 232 | + } else if (this.focus && this.focusTimer < 0.1) { |
| 233 | + // If we are focussing and the focus timer is less than max, increase the focus timer |
| 234 | + this.focusTimer = this.focusTimer + ig.system.actualTick |
| 235 | + this.alphaTimer = 0 |
| 236 | + } else if (!this.focus && this.focusTimer > 0) { |
| 237 | + // If we are no longer focussing, reduce the focus timer |
| 238 | + this.focusTimer = this.focusTimer - ig.system.actualTick |
| 239 | + this.alphaTimer = 0 |
| 240 | + } else { |
| 241 | + this.alphaTimer = (this.alphaTimer + ig.system.actualTick) % 1 |
| 242 | + } |
| 243 | + this.focusTimer.limit(0, 0.1) |
| 244 | + this.bg.currentTileOffset = this.keepPressed && this.pressed ? 'pressed' : this.focus ? 'focus' : 'default' |
| 245 | + if (this.highlight) { |
| 246 | + this.highlight.focusWeight = this.focusTimer / 0.1 |
| 247 | + var a = this.alphaTimer / 1, |
| 248 | + a = KEY_SPLINES.EASE_IN_OUT.get(1 - (a > 0.5 ? 1 - (a - 0.5) * 2 : a * 2)), |
| 249 | + a = 0.8 * a + 0.2 |
| 250 | + this.active || (a = a * 0.5) |
| 251 | + this.highlight.hook.localAlpha = a |
| 252 | + } |
| 253 | + }, |
| 254 | + |
| 255 | + unsetFocus() { |
| 256 | + this.focus = false |
| 257 | + this.setPressed(false) |
| 258 | + if (this.highlight) { |
| 259 | + this.highlight.hook.localAlpha = 0 |
| 260 | + this.highlight.focusWeight = 0 |
| 261 | + } |
| 262 | + this.focusTimer = 0 |
| 263 | + this.alphaTimer = 0 |
| 264 | + }, |
| 265 | +}) |
0 commit comments