From 9b12ed9a3b7763e2a03615bf494f727fb73ead28 Mon Sep 17 00:00:00 2001 From: Richard Knoll Date: Mon, 8 Jan 2024 10:43:08 -0800 Subject: [PATCH] Add support for system keyboard in string/number prompts --- libs/game/numberprompt.ts | 96 ++++++++++++++++++++++++++++++---- libs/game/prompt.ts | 98 +++++++++++++++++++++++++++++++---- libs/game/pxt.json | 2 + libs/game/systemKeyboard.cpp | 27 ++++++++++ libs/game/systemKeyboard.d.ts | 20 +++++++ 5 files changed, 223 insertions(+), 20 deletions(-) create mode 100644 libs/game/systemKeyboard.cpp create mode 100644 libs/game/systemKeyboard.d.ts diff --git a/libs/game/numberprompt.ts b/libs/game/numberprompt.ts index b825bbfd3..e96b67ee0 100644 --- a/libs/game/numberprompt.ts +++ b/libs/game/numberprompt.ts @@ -4,19 +4,21 @@ namespace game { * Ask the player for a number value. * @param message The message to display on the text-entry screen * @param answerLength The maximum number of digits the user can enter (1 - 10) + * @param useSystemKeyboard Use the computer keyboard for typing if the game is being played in the simulator */ //% weight=10 help=game/ask-for-number - //% blockId=gameaskfornumber block="ask for number %message || and max length %answerLength" + //% blockId=gameaskfornumber + //% block="ask for number $message || and max length $answerLength use system keyboard $useSystemKeyboard" //% message.shadow=text //% message.defl="" //% answerLength.defl="6" //% answerLength.min=1 //% answerLength.max=10 //% group="Prompt" - export function askForNumber(message: any, answerLength = 6) { + export function askForNumber(message: any, answerLength = 6, useSystemKeyboard = false) { answerLength = Math.max(0, Math.min(10, answerLength)); let p = new game.NumberPrompt(); - const result = p.show(console.inspect(message), answerLength); + const result = p.show(console.inspect(message), answerLength, useSystemKeyboard); return result; } @@ -123,6 +125,14 @@ namespace game { private blink: boolean; private frameCount: number; + private useSystemKeyboard: boolean; + + private renderable: scene.Renderable; + private selectionStart: number; + private selectionEnd: number; + + private changeTime = 0; + constructor(theme?: PromptTheme) { if (theme) { this.theme = theme; @@ -146,7 +156,7 @@ namespace game { this.inputIndex = 0; } - show(message: string, answerLength: number) : number { + show(message: string, answerLength: number, useSystemKeyboard = false) : number { this.message = message; this.answerLength = answerLength; this.inputIndex = 0; @@ -154,11 +164,35 @@ namespace game { controller._setUserEventsEnabled(false); game.pushScene() - this.draw(); - this.registerHandlers(); - this.confirmPressed = false; + if (useSystemKeyboard && control.deviceDalVersion() === "sim") { + this.useSystemKeyboard = true; + this.draw(); + helpers._promptForText(this.answerLength, true); + this.selectionEnd = 0; + this.selectionStart = 0; + control.onEvent(_KEYBOARD_CHANGE_EVENT, 0, () => { + this.result = helpers._getTextPromptString().substr(0, this.answerLength); + + for (let i = 0; i < this.inputs.length; i++) { + this.drawInput(this.inputs[i].image, this.result.charAt(i) || "", this.theme.colorInput) + } + + this.changeTime = game.runtime(); + + this.selectionStart = helpers._getTextPromptSelectionStart(); + this.selectionEnd = helpers._getTextPromptSelectionEnd(); + }) + + control.waitForEvent(_KEYBOARD_ENTER_EVENT, 0); + } + else { + this.useSystemKeyboard = false; + this.draw(); + this.registerHandlers(); + this.confirmPressed = false; + pauseUntil(() => this.confirmPressed); + } - pauseUntil(() => this.confirmPressed); game.popScene(); controller._setUserEventsEnabled(true); @@ -168,9 +202,51 @@ namespace game { private draw() { this.drawPromptText(); - this.drawNumpad(); this.drawInputarea(); - this.drawBottomBar(); + + if (!this.useSystemKeyboard) { + this.drawNumpad(); + this.drawBottomBar(); + } + + this.renderable = scene.createRenderable(this.inputs[0].z - 1, () => { + if (!this.useSystemKeyboard) return; + + if (this.selectionStart === this.selectionEnd) { + const input = this.inputs[this.selectionStart]; + if (input && !(Math.idiv(game.runtime() - this.changeTime, 500) & 1)) { + screen.fillRect(input.left, input.top, 1, input.height, this.theme.colorInput); + } + } + else { + let currentY = undefined; + let startX = undefined; + let endX = undefined; + for (let i = this.selectionStart; i < this.selectionEnd; i++) { + const current = this.inputs[i]; + + if (!current) break; + + if (!currentY) { + currentY = current.top; + startX = current.left + endX = current.right; + } + else if (current.top !== currentY) { + screen.fillRect(startX, currentY, endX - startX, this.inputs[0].height, this.theme.colorCursor); + + currentY = current.top; + startX = current.left + endX = current.right; + } + else { + endX = current.right; + } + } + + screen.fillRect(startX, currentY, endX - startX, this.inputs[0].height, this.theme.colorCursor); + } + }); } private drawPromptText() { diff --git a/libs/game/prompt.ts b/libs/game/prompt.ts index 396e046df..8abc85bcd 100644 --- a/libs/game/prompt.ts +++ b/libs/game/prompt.ts @@ -1,4 +1,7 @@ namespace game { + export const _KEYBOARD_CHANGE_EVENT = 7339; + export const _KEYBOARD_ENTER_EVENT = 7340; + export interface PromptTheme { colorPrompt: number; colorInput: number; @@ -15,18 +18,20 @@ namespace game { * Ask the player for a string value. * @param message The message to display on the text-entry screen * @param answerLength The maximum number of characters the user can enter (1 - 24) + * @param useSystemKeyboard Use the computer keyboard for typing if the game is being played in the simulator */ //% weight=10 help=game/ask-for-string - //% blockId=gameaskforstring block="ask for string %message || and max length %answerLength" + //% blockId=gameaskforstring + //% block="ask for string $message || and max length $answerLength use system keyboard $useSystemKeyboard" //% message.shadow=text //% message.defl="" //% answerLength.defl="12" //% answerLength.min=1 //% answerLength.max=24 //% group="Prompt" - export function askForString(message: any, answerLength = 12) { + export function askForString(message: any, answerLength = 12, useSystemKeyboard = false) { let p = new game.Prompt(); - const result = p.show(console.inspect(message), answerLength); + const result = p.show(console.inspect(message), answerLength, useSystemKeyboard); return result; } @@ -135,6 +140,13 @@ namespace game { private inputIndex: number; private blink: boolean; private frameCount: number; + private useSystemKeyboard: boolean; + + private renderable: scene.Renderable; + private selectionStart: number; + private selectionEnd: number; + + private changeTime = 0; constructor(theme?: PromptTheme) { if (theme) { @@ -159,7 +171,7 @@ namespace game { this.inputIndex = 0; } - show(message: string, answerLength: number) { + show(message: string, answerLength: number, useSystemKeyboard = false) { this.message = message; this.answerLength = answerLength; this.inputIndex = 0; @@ -167,11 +179,35 @@ namespace game { controller._setUserEventsEnabled(false); game.pushScene() - this.draw(); - this.registerHandlers(); - this.confirmPressed = false; + if (useSystemKeyboard && control.deviceDalVersion() === "sim") { + this.useSystemKeyboard = true; + this.draw(); + helpers._promptForText(this.answerLength, false); + this.selectionEnd = 0; + this.selectionStart = 0; + control.onEvent(_KEYBOARD_CHANGE_EVENT, 0, () => { + this.result = helpers._getTextPromptString().substr(0, this.answerLength); + + for (let i = 0; i < this.inputs.length; i++) { + this.drawInput(this.inputs[i].image, this.result.charAt(i) || "", this.theme.colorInput) + } + + this.changeTime = game.runtime(); + + this.selectionStart = helpers._getTextPromptSelectionStart(); + this.selectionEnd = helpers._getTextPromptSelectionEnd(); + }) + + control.waitForEvent(_KEYBOARD_ENTER_EVENT, 0); + } + else { + this.useSystemKeyboard = false; + this.draw(); + this.registerHandlers(); + this.confirmPressed = false; + pauseUntil(() => this.confirmPressed); + } - pauseUntil(() => this.confirmPressed); game.popScene(); controller._setUserEventsEnabled(true); @@ -181,9 +217,51 @@ namespace game { private draw() { this.drawPromptText(); - this.drawKeyboard(); this.drawInputarea(); - this.drawBottomBar(); + + if (!this.useSystemKeyboard) { + this.drawKeyboard(); + this.drawBottomBar(); + } + + this.renderable = scene.createRenderable(this.inputs[0].z - 1, () => { + if (!this.useSystemKeyboard) return; + + if (this.selectionStart === this.selectionEnd) { + const input = this.inputs[this.selectionStart]; + if (input && !(Math.idiv(game.runtime() - this.changeTime, 500) & 1)) { + screen.fillRect(input.left, input.top, 1, input.height, this.theme.colorInput); + } + } + else { + let currentY = undefined; + let startX = undefined; + let endX = undefined; + for (let i = this.selectionStart; i < this.selectionEnd; i++) { + const current = this.inputs[i]; + + if (!current) break; + + if (!currentY) { + currentY = current.top; + startX = current.left + endX = current.right; + } + else if (current.top !== currentY) { + screen.fillRect(startX, currentY, endX - startX, this.inputs[0].height, this.theme.colorCursor); + + currentY = current.top; + startX = current.left + endX = current.right; + } + else { + endX = current.right; + } + } + + screen.fillRect(startX, currentY, endX - startX, this.inputs[0].height, this.theme.colorCursor); + } + }); } private drawPromptText() { diff --git a/libs/game/pxt.json b/libs/game/pxt.json index ebdd945dc..acb27cef2 100644 --- a/libs/game/pxt.json +++ b/libs/game/pxt.json @@ -3,6 +3,8 @@ "description": "The game and sprite library - beta", "files": [ "ns.ts", + "systemKeyboard.d.ts", + "systemKeyboard.cpp", "gameoverrides.ts", "basesprite.ts", "constants.ts", diff --git a/libs/game/systemKeyboard.cpp b/libs/game/systemKeyboard.cpp new file mode 100644 index 000000000..b22661ab1 --- /dev/null +++ b/libs/game/systemKeyboard.cpp @@ -0,0 +1,27 @@ +#include "pxt.h" + +namespace Keyboard { + +//% +void promptForText(int maxLength, bool numberOnly) { +} + +//% +void cancelTextPrompt() { +} + +//% +ManagedString getTextPromptString() { + return NULL; +} + +//% +int getTextPromptSelectionStart() { + return 0; +} + +//% +int getTextPromptSelectionEnd() { + return 0; +} +} \ No newline at end of file diff --git a/libs/game/systemKeyboard.d.ts b/libs/game/systemKeyboard.d.ts new file mode 100644 index 000000000..ba5435b8b --- /dev/null +++ b/libs/game/systemKeyboard.d.ts @@ -0,0 +1,20 @@ + +/** + * These shims are for enabling system keyboard support in text/number prompts. + */ +declare namespace helpers { + //% shim=Keyboard::promptForText + function _promptForText(maxLength: number, numbersOnly: boolean): void; + + //% shim=Keyboard::cancelTextPrompt + function _cancelTextPrompt(): void; + + //% shim=Keyboard::getTextPromptString + function _getTextPromptString(): string; + + //% shim=Keyboard::getTextPromptSelectionStart + function _getTextPromptSelectionStart(): number; + + //% shim=Keyboard::getTextPromptSelectionEnd + function _getTextPromptSelectionEnd(): number; +} \ No newline at end of file