├── .gitignore ├── .gitmodules ├── .vscode ├── launch.json └── settings.json ├── CMakeLists.txt ├── LICENSE.md ├── Makefile ├── README.md ├── docker └── Dockerfile ├── eslintrc.js ├── include ├── roku.h └── setjmp.h ├── project ├── components │ ├── scene.brs │ └── scene.xml ├── images │ ├── mm_icon_focus_hd.png │ ├── mm_icon_focus_sd.png │ ├── splash_screen_hd.png │ └── splash_screen_sd.png └── source │ ├── Main.brs │ ├── helpers.brs │ ├── roku.brs │ ├── runtime.brs │ ├── spectest.brs │ └── wasi.brs ├── run.sh ├── samples ├── cmake │ ├── CMakeLists.txt │ ├── cmake.brs │ ├── main.cc │ └── manifest ├── doom │ ├── CMakeLists.txt │ ├── DOOMLIC.TXT │ ├── doom.brs │ ├── doom1.wad │ ├── manifest │ └── src │ │ ├── am_map.c │ │ ├── am_map.h │ │ ├── d_englsh.h │ │ ├── d_event.h │ │ ├── d_french.h │ │ ├── d_items.c │ │ ├── d_items.h │ │ ├── d_main.c │ │ ├── d_main.h │ │ ├── d_net.c │ │ ├── d_net.h │ │ ├── d_player.h │ │ ├── d_textur.h │ │ ├── d_think.h │ │ ├── d_ticcmd.h │ │ ├── doomdata.h │ │ ├── doomdef.c │ │ ├── doomdef.h │ │ ├── doomstat.c │ │ ├── doomstat.h │ │ ├── doomtype.h │ │ ├── dstrings.c │ │ ├── dstrings.h │ │ ├── f_finale.c │ │ ├── f_finale.h │ │ ├── f_wipe.c │ │ ├── f_wipe.h │ │ ├── g_game.c │ │ ├── g_game.h │ │ ├── hu_lib.c │ │ ├── hu_lib.h │ │ ├── hu_stuff.c │ │ ├── hu_stuff.h │ │ ├── i_main.c │ │ ├── i_net.c │ │ ├── i_net.h │ │ ├── i_sound.c │ │ ├── i_sound.h │ │ ├── i_system.c │ │ ├── i_system.h │ │ ├── i_video.c │ │ ├── i_video.h │ │ ├── info.c │ │ ├── info.h │ │ ├── m_argv.c │ │ ├── m_argv.h │ │ ├── m_bbox.c │ │ ├── m_bbox.h │ │ ├── m_cheat.c │ │ ├── m_cheat.h │ │ ├── m_fixed.c │ │ ├── m_fixed.h │ │ ├── m_menu.c │ │ ├── m_menu.h │ │ ├── m_misc.c │ │ ├── m_misc.h │ │ ├── m_random.c │ │ ├── m_random.h │ │ ├── m_swap.c │ │ ├── m_swap.h │ │ ├── p_ceilng.c │ │ ├── p_doors.c │ │ ├── p_enemy.c │ │ ├── p_floor.c │ │ ├── p_inter.c │ │ ├── p_inter.h │ │ ├── p_lights.c │ │ ├── p_local.h │ │ ├── p_map.c │ │ ├── p_maputl.c │ │ ├── p_mobj.c │ │ ├── p_mobj.h │ │ ├── p_plats.c │ │ ├── p_pspr.c │ │ ├── p_pspr.h │ │ ├── p_saveg.c │ │ ├── p_saveg.h │ │ ├── p_setup.c │ │ ├── p_setup.h │ │ ├── p_sight.c │ │ ├── p_spec.c │ │ ├── p_spec.h │ │ ├── p_switch.c │ │ ├── p_telept.c │ │ ├── p_tick.c │ │ ├── p_tick.h │ │ ├── p_user.c │ │ ├── r_bsp.c │ │ ├── r_bsp.h │ │ ├── r_data.c │ │ ├── r_data.h │ │ ├── r_defs.h │ │ ├── r_draw.c │ │ ├── r_draw.h │ │ ├── r_local.h │ │ ├── r_main.c │ │ ├── r_main.h │ │ ├── r_plane.c │ │ ├── r_plane.h │ │ ├── r_segs.c │ │ ├── r_segs.h │ │ ├── r_sky.c │ │ ├── r_sky.h │ │ ├── r_state.h │ │ ├── r_things.c │ │ ├── r_things.h │ │ ├── s_sound.c │ │ ├── s_sound.h │ │ ├── sounds.c │ │ ├── sounds.h │ │ ├── st_lib.c │ │ ├── st_lib.h │ │ ├── st_stuff.c │ │ ├── st_stuff.h │ │ ├── tables.c │ │ ├── tables.h │ │ ├── v_video.c │ │ ├── v_video.h │ │ ├── w_wad.c │ │ ├── w_wad.h │ │ ├── wi_stuff.c │ │ ├── wi_stuff.h │ │ ├── z_zone.c │ │ └── z_zone.h ├── files │ ├── files.brs │ ├── files.cc │ └── manifest ├── javascript │ ├── CMakeLists.txt │ ├── duktape.sh │ ├── duktape.yaml │ ├── duktape │ │ ├── duk_config.h │ │ ├── duk_source_meta.json │ │ ├── duktape.c │ │ └── duktape.h │ ├── javascript.brs │ ├── javascript.cc │ └── manifest ├── mandelbrot │ ├── mandelbrot.brs │ ├── mandelbrot.c │ └── manifest └── rust │ ├── manifest │ ├── rust.brs │ └── rust.rs ├── src ├── brs-writer.cc ├── brs-writer.h └── wasm2brs.cc └── test ├── .eslintignore ├── .eslintrc.js ├── index.ts ├── package-lock.json ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | 4 | *.stamp 5 | 6 | project/source/*.out* 7 | project/source/*.wad 8 | project/manifest -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/wabt"] 2 | path = third_party/wabt 3 | url = https://github.com/WebAssembly/wabt.git 4 | [submodule "third_party/testsuite"] 5 | path = third_party/testsuite 6 | url = https://github.com/MotleyCoderDev/testsuite.git 7 | [submodule "third_party/binaryen"] 8 | path = third_party/binaryen 9 | url = https://github.com/MotleyCoderDev/binaryen.git 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "brightscript", 9 | "request": "launch", 10 | "name": "BrightScript Debug: Launch Project", 11 | "stopOnEntry": false, 12 | "host": "${promptForHost}", 13 | "password": "rokudev", 14 | "rootDir": "${workspaceFolder}/project", 15 | "outDir": "${workspaceFolder}/build/vscode_roku_deploy", 16 | "enableDebuggerAutoRecovery": false, 17 | "stopDebuggerOnAppExit": false 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": true 4 | }, 5 | "files.associations": { 6 | "*.def": "cpp" 7 | } 8 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2020, Trevor Sundberg. See LICENSE.md 2 | cmake_minimum_required(VERSION 3.13.4) 3 | project(WASM2BRS VERSION 1.0.0) 4 | 5 | add_subdirectory(third_party/wabt) 6 | add_subdirectory(third_party/binaryen) 7 | 8 | include_directories(${WABT_SOURCE_DIR} ${WABT_BINARY_DIR}) 9 | 10 | add_executable(wasm2brs src/wasm2brs.cc src/brs-writer.cc) 11 | 12 | add_dependencies(wasm2brs wabt) 13 | target_link_libraries(wasm2brs wabt) 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2020, Trevor Sundberg. See LICENSE.md 2 | 3 | # These rules aren't backed by files and will always run 4 | .PHONY: wasm2brs doom files mandelbrot javascript rust cmake test clean run_test all 5 | 6 | # This rule must be first so it runs when you don't specify a target 7 | all: wasm2brs doom files mandelbrot javascript rust cmake test 8 | 9 | # Because we call into cmake, we don't know whether some rules need to be updated 10 | # For example in rule build/wasm2brs/wasm2brs we don't know if brs-writer.cc changed 11 | # unless we run the cmake every time. The solution is to FORCE cmake to run every time 12 | # by using an empty rule. This also has the advantage over a PHONY rule that anyone 13 | # who depends on a rule that uses FORCE (e.g. build/wasm2brs/wasm2brs) is not themselves 14 | # forced to rebuild each time, but they would if we used a PHONY. 15 | FORCE: ; 16 | 17 | # --- clean 18 | clean: 19 | $(call clean-project) 20 | rm -rf build 21 | rm -rf test/node_modules 22 | 23 | define clean-project 24 | rm -rf project/source/*.out* 25 | rm -rf project/source/*.wad 26 | rm -rf project/manifest 27 | endef 28 | 29 | # --- wasm2brs 30 | wasm2brs: build/wasm2brs/wasm2brs 31 | 32 | build/wasm2brs/wasm2brs: build/wasm2brs/Makefile FORCE 33 | GNUMAKEFLAGS=--no-print-directory cmake --build ./build/wasm2brs --parallel 34 | 35 | build/wasm2brs/Makefile: 36 | mkdir -p build/wasm2brs 37 | cd build/wasm2brs && cmake ../.. 38 | 39 | # --- test 40 | test: build/test/index.js 41 | 42 | run_test: build/test/index.js build/wasm2brs/wasm2brs 43 | $(call clean-project) 44 | NODE_PATH=test/node_modules node build/test/index.js $(ARGS) 45 | 46 | build/test/index.js: test/index.ts test/tsconfig.json test/node_modules 47 | rm -rf build/test/ && cd test && npm run build 48 | 49 | test/node_modules: test/package.json 50 | cd test && npm install && touch node_modules 51 | 52 | # --- doom 53 | doom: build/doom/doom-wasm.out.brs 54 | $(call clean-project) 55 | cp build/doom/doom-wasm.out*.brs project/source/ 56 | cp samples/doom/doom.brs project/source/doom.out.brs 57 | cp samples/doom/doom1.wad project/source/doom1.wad 58 | cp samples/doom/manifest project/manifest 59 | 60 | build/doom/doom-wasm.out.brs: build/doom/doom.wasm build/wasm2brs/wasm2brs 61 | ./build/wasm2brs/third_party/binaryen/bin/wasm-opt -g -O4 ./build/doom/doom.wasm -o ./build/doom/doom-opt.wasm 62 | ./build/wasm2brs/wasm2brs -o build/doom/doom-wasm.out.brs ./build/doom/doom-opt.wasm 63 | 64 | build/doom/doom.wasm: build/doom/Makefile FORCE 65 | GNUMAKEFLAGS=--no-print-directory cmake --build ./build/doom --parallel 66 | 67 | build/doom/Makefile: 68 | mkdir -p build/doom 69 | cd build/doom && wasimake cmake ../../samples/doom 70 | 71 | # --- files 72 | files: build/files/files-wasm.out.brs 73 | $(call clean-project) 74 | cp build/files/files-wasm.out*.brs project/source/ 75 | cp samples/files/files.brs project/source/files.out.brs 76 | cp samples/files/manifest project/manifest 77 | 78 | build/files/files-wasm.out.brs: build/files/files.wasm build/wasm2brs/wasm2brs 79 | ./build/wasm2brs/third_party/binaryen/bin/wasm-opt -g -Oz ./build/files/files.wasm -o ./build/files/files-opt.wasm 80 | ./build/wasm2brs/wasm2brs -o build/files/files-wasm.out.brs ./build/files/files-opt.wasm 81 | 82 | build/files/files.wasm: samples/files/files.cc 83 | mkdir -p build/files 84 | wasic++ -g -Oz samples/files/files.cc -o ./build/files/files.wasm 85 | 86 | # --- cmake 87 | cmake: build/cmake/cmake-wasm.out.brs 88 | $(call clean-project) 89 | cp build/cmake/cmake-wasm.out*.brs project/source/ 90 | cp samples/cmake/cmake.brs project/source/cmake.out.brs 91 | cp samples/cmake/manifest project/manifest 92 | 93 | build/cmake/cmake-wasm.out.brs: build/cmake/Makefile build/wasm2brs/wasm2brs FORCE 94 | GNUMAKEFLAGS=--no-print-directory cmake --build ./build/cmake --parallel 95 | 96 | build/cmake/Makefile: 97 | mkdir -p build/cmake 98 | cd build/cmake && wasimake cmake ../../samples/cmake 99 | 100 | # --- mandelbrot 101 | mandelbrot: build/mandelbrot/mandelbrot-wasm.out.brs 102 | $(call clean-project) 103 | cp build/mandelbrot/mandelbrot-wasm.out*.brs project/source/ 104 | cp samples/mandelbrot/mandelbrot.brs project/source/mandelbrot.out.brs 105 | cp samples/mandelbrot/manifest project/manifest 106 | 107 | build/mandelbrot/mandelbrot-wasm.out.brs: build/mandelbrot/mandelbrot.wasm build/wasm2brs/wasm2brs 108 | ./build/wasm2brs/third_party/binaryen/bin/wasm-opt -O4 ./build/mandelbrot/mandelbrot.wasm -o ./build/mandelbrot/mandelbrot-opt.wasm 109 | ./build/wasm2brs/wasm2brs -o build/mandelbrot/mandelbrot-wasm.out.brs ./build/mandelbrot/mandelbrot-opt.wasm 110 | 111 | build/mandelbrot/mandelbrot.wasm: samples/mandelbrot/mandelbrot.c 112 | mkdir -p build/mandelbrot 113 | clang -Ofast --target=wasm32 -nostdlib -Wl,--no-entry samples/mandelbrot/mandelbrot.c -o ./build/mandelbrot/mandelbrot.wasm 114 | 115 | # --- javascript 116 | javascript: build/javascript/javascript-wasm.out.brs 117 | $(call clean-project) 118 | cp build/javascript/javascript-wasm.out*.brs project/source/ 119 | cp samples/javascript/javascript.brs project/source/javascript.out.brs 120 | cp samples/javascript/manifest project/manifest 121 | 122 | build/javascript/javascript-wasm.out.brs: build/javascript/javascript.wasm build/wasm2brs/wasm2brs 123 | ./build/wasm2brs/third_party/binaryen/bin/wasm-opt -g -O4 ./build/javascript/javascript.wasm -o ./build/javascript/javascript-opt.wasm 124 | ./build/wasm2brs/wasm2brs -o build/javascript/javascript-wasm.out.brs ./build/javascript/javascript-opt.wasm 125 | 126 | build/javascript/javascript.wasm: build/javascript/Makefile FORCE 127 | GNUMAKEFLAGS=--no-print-directory cmake --build ./build/javascript --parallel 128 | 129 | build/javascript/Makefile: 130 | mkdir -p build/javascript 131 | cd build/javascript && wasimake cmake ../../samples/javascript 132 | 133 | # --- rust 134 | rust: build/rust/rust-wasm.out.brs 135 | $(call clean-project) 136 | cp build/rust/rust-wasm.out*.brs project/source/ 137 | cp samples/rust/rust.brs project/source/rust.out.brs 138 | cp samples/rust/manifest project/manifest 139 | 140 | build/rust/rust-wasm.out.brs: build/rust/rust.wasm build/wasm2brs/wasm2brs 141 | ./build/wasm2brs/third_party/binaryen/bin/wasm-opt -g -O4 ./build/rust/rust.wasm -o ./build/rust/rust-opt.wasm 142 | ./build/wasm2brs/wasm2brs -o build/rust/rust-wasm.out.brs ./build/rust/rust-opt.wasm 143 | 144 | build/rust/rust.wasm: samples/rust/rust.rs 145 | mkdir -p build/rust 146 | cd samples/rust && rustc --target wasm32-wasi rust.rs -o ../../build/rust/rust.wasm -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2020, Trevor Sundberg. See LICENSE.md 2 | FROM ubuntu:20.04 3 | 4 | ARG DEBIAN_FRONTEND=noninteractive 5 | 6 | RUN apt-get update && \ 7 | apt-get install -y \ 8 | cmake \ 9 | clang \ 10 | lld \ 11 | git \ 12 | curl \ 13 | nodejs \ 14 | npm \ 15 | python3 \ 16 | python3-pip \ 17 | dumb-init 18 | 19 | RUN ln /usr/bin/lld /usr/bin/wasm-ld 20 | 21 | RUN groupadd -g 1000 group && useradd -g 1000 -u 1000 -ms /bin/bash user 22 | USER user 23 | WORKDIR /home/user 24 | 25 | RUN curl https://raw.githubusercontent.com/wasienv/wasienv/master/install.sh | sh 26 | 27 | ENV WASMER_DIR="/home/user/.wasmer" 28 | ENV WASMER_CACHE_DIR="/home/user/.wasmer/cache" 29 | ENV PATH="/home/user/.wasmer/bin:/home/user/.wasienv/bin/:${PATH}:/home/user/.wasmer/globals/wapm_packages/.bin" 30 | 31 | RUN curl -sSf https://sh.rustup.rs | bash -s -- -y 32 | ENV PATH="/home/user/.cargo/bin:${PATH}" 33 | RUN rustup target add wasm32-wasi 34 | RUN cargo install cargo-wasi 35 | 36 | ENTRYPOINT ["/usr/bin/dumb-init", "--"] 37 | CMD echo "No command specified" 38 | -------------------------------------------------------------------------------- /eslintrc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020, Trevor Sundberg. See LICENSE.md 2 | module.exports = { 3 | parser: "@typescript-eslint/parser", 4 | extends: [ 5 | "eslint:all", 6 | "plugin:@typescript-eslint/recommended" 7 | ], 8 | parserOptions: { 9 | ecmaVersion: 2018, 10 | sourceType: "module" 11 | }, 12 | env: { 13 | node: true, 14 | es6: true 15 | }, 16 | rules: { 17 | "indent": "off", 18 | "@typescript-eslint/indent": ["error", 2], 19 | "@typescript-eslint/explicit-function-return-type": "off", 20 | "@typescript-eslint/no-explicit-any": "off", 21 | "no-extra-parens": "off", 22 | "@typescript-eslint/no-extra-parens": ["error"], 23 | "@typescript-eslint/explicit-member-accessibility": ["error"], 24 | 25 | "max-statements": ["error", 100], 26 | "max-len": ["error", 120], 27 | "max-lines": "off", 28 | "padded-blocks": ["error", "never"], 29 | "object-property-newline": "off", 30 | "object-curly-newline": ["error", {multiline: true, consistent: true}], 31 | "multiline-ternary": "off", 32 | "function-call-argument-newline": ["error", "consistent"], 33 | 34 | "no-console": "off", 35 | "no-process-env": "off", 36 | 37 | "quote-props": ["error", "consistent-as-needed"], 38 | "one-var": "off", 39 | "no-ternary": "off", 40 | "no-confusing-arrow": "off", 41 | "no-await-in-loop": "off", 42 | "no-magic-numbers": "off", 43 | "no-new": "off", 44 | "require-await": "off", 45 | "class-methods-use-this": "off", 46 | "@typescript-eslint/camelcase": "off", 47 | "global-require": "off", 48 | "callback-return": "off", 49 | "no-plusplus": "off", 50 | "max-params": ["error", 6], 51 | "no-sync": "off", 52 | 53 | "default-case": "off", 54 | "no-undef": "off", 55 | "max-classes-per-file": "off", 56 | "prefer-named-capture-group": "off", 57 | "require-atomic-updates": "off", 58 | "no-bitwise": "off", 59 | "no-mixed-operators": "off", 60 | "id-length": "off", 61 | "no-continue": "off", 62 | "no-warning-comments": "off", 63 | "complexity": "off", 64 | "max-lines-per-function": "off", 65 | "implicit-arrow-linebreak": "off", 66 | "sort-keys": "off", 67 | "no-undefined": "off", 68 | "@typescript-eslint/no-non-null-assertion": "off", 69 | "array-element-newline": "off", 70 | "default-param-last": "off", 71 | "newline-per-chained-call": "off", 72 | "function-paren-newline": "off", 73 | "sort-imports": "off", 74 | "max-depth": "off" 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /include/roku.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2020, Trevor Sundberg. See LICENSE.md */ 2 | /* All the imports here correspond with functions in `roku.brs` */ 3 | 4 | #pragma once 5 | #ifndef ROKU_H 6 | #define ROKU_H 7 | 8 | #ifdef __wasilibc_unmodified_upstream 9 | #define ROKU_BUILTIN(name) __attribute__((__import_name__(#name))) 10 | #else 11 | #define ROKU_BUILTIN(name) 12 | #endif 13 | 14 | /* 15 | This function causes the script to pause for the specified time, without wasting CPU cycles. 16 | There are 1000 milliseconds in one second. 17 | */ 18 | ROKU_BUILTIN(sleep) void roku_sleep(int milliseconds); 19 | 20 | /* 21 | Create a singleton global surface. 22 | If bitsPerPixel = 8, then you must call roku_set_surface_colors before roku_draw_surface. 23 | */ 24 | void roku_create_surface(int bitsPerPixel, int width, int height); 25 | 26 | /* 27 | Set the color palette for a created surface. Only valid when bitsPerPixel = 8. 28 | */ 29 | void roku_set_surface_colors(void* colorTable); 30 | 31 | /* 32 | Render the surface to the screen with the given pixel data. 33 | The pixelData must be of size width * height * (bitsPerPixel / 8). 34 | */ 35 | void roku_draw_surface(void* pixelData); 36 | 37 | /* 38 | Poll to see if any buttons are pressed, released, or held. 39 | When pressed, the value will match roku_button (e.g. 6 for ROKU_OK). 40 | When released, the value will be roku_button + 100 (e.g. 106 for ROKU_OK). 41 | When held / repeated, the value will be roku_button + 1000 (e.g. 1006 for ROKU_OK). 42 | Returns ROKU_INVALID if there are no button events. 43 | */ 44 | unsigned int roku_poll_button(void); 45 | 46 | enum roku_button { 47 | ROKU_BACK = 0, 48 | ROKU_UP = 2, 49 | ROKU_DOWN = 3, 50 | ROKU_LEFT = 4, 51 | ROKU_RIGHT = 5, 52 | ROKU_OK = 6, 53 | ROKU_INSTANTREPLAY = 7, 54 | ROKU_REWIND = 8, 55 | ROKU_FASTFORWARD = 9, 56 | ROKU_OPTIONS = 10, 57 | ROKU_PLAY = 13, 58 | ROKU_INVALID = 0xFFFFFFFF 59 | }; 60 | 61 | #endif 62 | -------------------------------------------------------------------------------- /include/setjmp.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2020, Trevor Sundberg. See LICENSE.md */ 2 | #pragma once 3 | #ifndef SETJMP_H 4 | 5 | #include 6 | #include 7 | 8 | typedef int jmp_buf; 9 | 10 | static int setjmp(jmp_buf env) { 11 | return 0; 12 | } 13 | 14 | static void longjmp(jmp_buf env, int val) { 15 | fprintf(stderr, "Function 'longjmp' called with value %d and is not yet supported (aborting...)\n", val); 16 | abort(); 17 | } 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /project/components/scene.brs: -------------------------------------------------------------------------------- 1 | ' Copyright 2020, Trevor Sundberg. See LICENSE.md 2 | Sub Init() 3 | m.keyboard = m.top.findNode("keyboard") 4 | m.enter = m.top.findNode("enter") 5 | End Sub 6 | 7 | Function onKeyEvent(key as String, press as Boolean) as Boolean 8 | If press Then 9 | If key = "down" And m.keyboard.isInFocusChain() Then 10 | m.enter.setFocus(True) 11 | Return True 12 | End If 13 | If key = "up" And m.enter.isInFocusChain() Then 14 | m.keyboard.setFocus(True) 15 | Return True 16 | End If 17 | End If 18 | Return False 19 | End Function 20 | -------------------------------------------------------------------------------- /project/components/scene.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |