Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add navigator.virtualKeyboard #55

Merged
merged 4 commits into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/bright-bottles-rest.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'nxjs-runtime': patch
---

Add `navigator.virtualKeyboard`
5 changes: 5 additions & 0 deletions .changeset/cuddly-papayas-join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'nxjs-constants': patch
---

Add `Swkbd.Type` enum
4 changes: 4 additions & 0 deletions apps/virtual-keyboard/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/*.nro
/node_modules
/romfs/main.js
/romfs/main.js.map
17 changes: 17 additions & 0 deletions apps/virtual-keyboard/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "virtual-keyboard",
"version": "0.0.0",
"private": true,
"description": "nx.js app that invokes the virtual keyboard",
"scripts": {
"build": "esbuild --bundle --sourcemap --sources-content=false --target=es2020 --format=esm src/main.ts --outfile=romfs/main.js",
"nro": "nxjs-pack"
},
"license": "MIT",
"devDependencies": {
"esbuild": "^0.17.19",
"nxjs-constants": "workspace:^",
"nxjs-pack": "workspace:^",
"nxjs-runtime": "workspace:^"
}
}
52 changes: 52 additions & 0 deletions apps/virtual-keyboard/src/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { Hid, Swkbd } from 'nxjs-constants';
const { Button } = Hid;

const canvas = Switch.screen;
const ctx = Switch.screen.getContext('2d');
const vk = navigator.virtualKeyboard;

function render() {
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);

ctx.font = '24px system-ui';
ctx.fillStyle = '#444';
ctx.fillText('Press "ZL" to show num keyboard', 10, 32);
ctx.fillText('Press "ZR" to show text keyboard', 10, 68);

ctx.font = '32px system-ui';
ctx.fillStyle = 'black';
ctx.fillText(`${vk.value}`, 10, 120);

const { width } = ctx.measureText(vk.value.slice(0, vk.cursorIndex));
ctx.fillStyle = 'green';
ctx.fillRect(13 + width, 90, 3, 32);
}

Switch.addEventListener('buttondown', (e) => {
const isOpen = vk.boundingRect.height > 0;
if (isOpen) {
if (e.detail & Button.Plus) {
e.preventDefault();
} else if (e.detail & Button.ZL) {
vk.hide();
}
} else {
if (e.detail & Button.ZR) {
vk.type = Swkbd.Type.Normal;
vk.okButtonText = 'Done';
vk.show();
} else if (e.detail & Button.ZL) {
vk.type = Swkbd.Type.NumPad;
vk.okButtonText = 'Submit';
vk.leftButtonText = ':';
vk.rightButtonText = '.';
vk.show();
}
}
});

vk.addEventListener('change', render);
vk.addEventListener('cursormove', render);
vk.addEventListener('geometrychange', render);
render();
16 changes: 16 additions & 0 deletions apps/virtual-keyboard/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "es2020",
"moduleResolution": "node",
"noEmit": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true,
"types": [
"nxjs-runtime"
]
},
"include": [
"src/**/*.ts"
]
}
3 changes: 2 additions & 1 deletion packages/constants/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as Applet from './applet';
import * as Hid from './hid';
import * as Swkbd from './swkbd';

export { Applet, Hid };
export { Applet, Hid, Swkbd };
13 changes: 13 additions & 0 deletions packages/constants/src/swkbd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// Type of keyboard.
export enum Type {
Normal = 0, ///< Normal keyboard.
NumPad = 1, ///< Number pad. The buttons at the bottom left/right are only available when they're set by \ref swkbdConfigSetLeftOptionalSymbolKey / \ref swkbdConfigSetRightOptionalSymbolKey.
QWERTY = 2, ///< QWERTY (and variants) keyboard only.
Unknown3 = 3, ///< The same as SwkbdType_Normal keyboard.
Latin = 4, ///< All Latin like languages keyboard only (without CJK keyboard).
ZhHans = 5, ///< Chinese Simplified keyboard only.
ZhHant = 6, ///< Chinese Traditional keyboard only.
Korean = 7, ///< Korean keyboard only.
All = 8, ///< All language keyboards.
Unknown9 = 9, ///< Unknown
}
18 changes: 18 additions & 0 deletions packages/runtime/src/$.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Callback, NetworkInfo } from './types';
import type { Server } from './tcp';
import type { MemoryDescriptor, Memory } from './wasm';
import type { VirtualKeyboard } from './navigator/virtual-keyboard';

export interface Init {
// battery.c
Expand All @@ -18,6 +19,23 @@ export interface Init {
nifmInitialize(): () => void;
networkInfo(): NetworkInfo;

// swkbd.c
swkbdCreate(fns: {
onCancel: () => void;
onChange: (
str: string,
cursorPos: number,
dicStartCursorPos: number,
dicEndCursorPos: number
) => void;
onSubmit: (str: string) => void;
onCursorMove: (str: string, cursorPos: number) => void;
}): VirtualKeyboard;
swkbdShow(s: VirtualKeyboard): [number, number, number, number];
swkbdHide(s: VirtualKeyboard): void;
swkbdExit(this: VirtualKeyboard): void;
swkbdUpdate(this: VirtualKeyboard): void;

// tcp.c
connect(cb: Callback<number>, ip: string, port: number): void;
write(cb: Callback<number>, fd: number, data: ArrayBuffer): void;
Expand Down
1 change: 1 addition & 0 deletions packages/runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type * from './types';
export type * from './console';
export type * from './navigator';
export type * from './navigator/battery';
export type { VirtualKeyboard } from './navigator/virtual-keyboard';
export type {
CanvasImageSource,
Canvas,
Expand Down
23 changes: 20 additions & 3 deletions packages/runtime/src/navigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,16 @@ import { IllegalConstructor, def } from './utils';
import { BatteryManager } from './navigator/battery';
import { INTERNAL_SYMBOL } from './types';
import type { SwitchClass } from './switch';
import {
type VirtualKeyboard,
create as newVirtualKeyboard,
} from './navigator/virtual-keyboard';

declare const Switch: SwitchClass;

interface NavigatorState {
battery?: BatteryManager;
batt?: BatteryManager;
vk?: VirtualKeyboard;
}

const state: NavigatorState = {};
Expand Down Expand Up @@ -55,13 +60,25 @@ export class Navigator {
* @see https://developer.mozilla.org/docs/Web/API/Navigator/getBattery
*/
async getBattery() {
let b = state.battery;
let b = state.batt;
if (!b) {
// @ts-expect-error
b = state.battery = new BatteryManager(INTERNAL_SYMBOL);
b = state.batt = new BatteryManager(INTERNAL_SYMBOL);
}
return b;
}

/**
* A {@link VirtualKeyboard} instance to show or hide the virtual keyboard
* programmatically, and get the current position and size of the virtual keyboard.
*
* @see https://developer.mozilla.org/docs/Web/API/Navigator/virtualKeyboard
*/
get virtualKeyboard() {
let vk = state.vk;
if (!vk) vk = state.vk = newVirtualKeyboard();
return vk;
}
}
def('Navigator', Navigator);

Expand Down
144 changes: 144 additions & 0 deletions packages/runtime/src/navigator/virtual-keyboard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { $ } from '../$';
import { DOMRect } from '../domrect';
import { IllegalConstructor, def } from '../utils';
import type { SwitchClass } from '../switch';
import { Event } from '../polyfills/event';

declare const Switch: SwitchClass;

let update: () => void;
let value = '';
let cursorPos = 0;

/**
* @see https://developer.mozilla.org/docs/Web/API/VirtualKeyboard
*/
export class VirtualKeyboard extends EventTarget {
/**
* Indicates the position and size of the on-screen virtual keyboard that overlays the screen.
*
* @see https://developer.mozilla.org/docs/Web/API/VirtualKeyboard/boundingRect
*/
readonly boundingRect: DOMRect;

/**
* Set the type of virtual keyboard.
*/
type?: number;

/**
* Text to display for the "OK" button. Max of 8 characters.
*
* @example "Submit"
*/
okButtonText?: string;

/**
* Single character to use for the left-side button.
*
* @example "-"
* @note Only for "NumPad" keyboard type.
*/
leftButtonText?: string;

/**
* Single character to use for the right-side button.
*
* @example "+"
* @note Only for "NumPad" keyboard type.
*/
rightButtonText?: string;

/**
* If set to `true`, then the dictionary will be enabled
* for faster user input based on predictive text.
*/
enableDictionary?: boolean;

/**
* If set to `true`, then the "Return" key will be enabled,
* allowing for newlines to be included in the input.
*/
enableReturn?: boolean;

/**
* Specifies the min string length. When the input
* is too short, the "OK" button will be disabled.
*/
minLength?: number;

/**
* Specifies the max string length. When the input
* is too long, input will stop being accepted.
*/
maxLength?: number;

/**
* @ignore
*/
constructor() {
super();
throw new IllegalConstructor();
}

get overlaysContent() {
return true;
}

get value() {
return value;
}

get cursorIndex() {
return cursorPos;
}

show() {
Switch.addEventListener('frame', update);
Object.assign(this.boundingRect, $.swkbdShow(this));
this.dispatchEvent(new Event('geometrychange'));
}

hide() {
$.swkbdHide(this);
onHide(this);
}
}
def('VirtualKeyboard', VirtualKeyboard);

function onHide(k: VirtualKeyboard) {
Switch.removeEventListener('frame', update);
const b = k.boundingRect;
b.x = b.y = b.width = b.height = 0;
k.dispatchEvent(new Event('geometrychange'));
}

export function create() {
const k = $.swkbdCreate({
onCancel() {
onHide(k);
},
onChange(str, ci) {
value = str;
cursorPos = ci;
k.dispatchEvent(new Event('change'));
},
onCursorMove(str, ci) {
value = str;
cursorPos = ci;
k.dispatchEvent(new Event('cursormove'));
},
onSubmit(str) {
value = str;
onHide(k);
k.dispatchEvent(new Event('submit'));
},
});
Object.setPrototypeOf(k, VirtualKeyboard.prototype);
EventTarget.call(k);
// @ts-expect-error
k.boundingRect = new DOMRect();
update = $.swkbdUpdate.bind(k);
Switch.addEventListener('exit', $.swkbdExit.bind(k));
return k;
}
15 changes: 15 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions source/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "font.h"
#include "fs.h"
#include "nifm.h"
#include "software-keyboard.h"
#include "wasm.h"
#include "image.h"
#include "tcp.h"
Expand Down Expand Up @@ -476,6 +477,7 @@ int main(int argc, char *argv[])
nx_init_battery(ctx, init_obj);
nx_init_nifm(ctx, init_obj);
nx_init_tcp(ctx, init_obj);
nx_init_swkbd(ctx, init_obj);
nx_init_wasm(ctx, init_obj);
JS_SetPropertyStr(ctx, global_obj, "$", init_obj);

Expand Down
Loading