diff --git a/.github/workflows/ci-workflow.yml b/.github/workflows/ci-workflow.yml index 04d3ef1f5..857e695ad 100644 --- a/.github/workflows/ci-workflow.yml +++ b/.github/workflows/ci-workflow.yml @@ -23,6 +23,7 @@ jobs: submodules: true - run: make -j2 MODEL=n0100 epsilon.dfu - run: make -j2 MODEL=n0100 epsilon.onboarding.dfu + - run: make -j2 MODEL=n0100 epsilon.official.onboarding.dfu - run: make -j2 MODEL=n0100 epsilon.onboarding.update.dfu - run: make -j2 MODEL=n0100 epsilon.onboarding.beta.dfu - run: make -j2 MODEL=n0100 flasher.light.dfu @@ -42,6 +43,7 @@ jobs: submodules: true - run: make -j2 epsilon.dfu - run: make -j2 epsilon.onboarding.dfu + - run: make -j2 epsilon.official.onboarding.dfu - run: make -j2 epsilon.onboarding.update.dfu - run: make -j2 epsilon.onboarding.beta.dfu - run: make -j2 flasher.light.dfu @@ -63,10 +65,11 @@ jobs: with: submodules: true - run: make -j2 PLATFORM=simulator TARGET=web + - run: make -j2 PLATFORM=simulator TARGET=web epsilon.official.js - uses: actions/upload-artifact@master with: name: epsilon-simulator-web.zip - path: output/release/simulator/web/simulator.zip + path: output/release/simulator/web/epsilon.zip - run: make -j2 PLATFORM=simulator TARGET=web test.headless.js build-simulator-linux: runs-on: ubuntu-latest diff --git a/build/config.mak b/build/config.mak index 1a0e02b8e..4ba83252f 100644 --- a/build/config.mak +++ b/build/config.mak @@ -21,9 +21,6 @@ OMEGA_THEME ?= omega_light ifndef USE_LIBA $(error platform.mak should define USE_LIBA) endif -ifndef EXE - $(error platform.mak should define EXE, the extension for executables) -endif include build/toolchain.$(TOOLCHAIN).mak SFLAGS += -DDEBUG=$(DEBUG) diff --git a/build/platform.simulator.android.mak b/build/platform.simulator.android.mak index 97cd7faf7..46601bfea 100644 --- a/build/platform.simulator.android.mak +++ b/build/platform.simulator.android.mak @@ -1,8 +1,10 @@ TOOLCHAIN = android -EXE = so EPSILON_TELEMETRY ?= 1 -ifdef NDK_ABI -BUILD_DIR := $(BUILD_DIR)/$(NDK_ABI) +ARCHS = armeabi-v7a arm64-v8a x86 x86_64 + +ifdef ARCH +EXE = so +BUILD_DIR := $(BUILD_DIR)/$(ARCH) endif diff --git a/build/rules.mk b/build/rules.mk index f1296d00a..d08285bc5 100644 --- a/build/rules.mk +++ b/build/rules.mk @@ -1,8 +1,15 @@ # Define standard compilation rules .PHONY: official_authorization +ifeq ($(ACCEPT_OFFICIAL_TOS),1) official_authorization: - @echo "CAUTION: You are about to build an official NumWorks firmware. Distribution of such firmware by a third party is prohibited. Are you sure you want to proceed? Please type "yes" to confirm." && read ans && [ $${ans:-no} = yes ] +else +official_authorization: + @echo "CAUTION: You are trying to build an official NumWorks firmware." + @echo "Distribution of such firmware by a third party is prohibited." + @echo "Please set the ACCEPT_OFFICIAL_TOS environment variable to proceed." + @exit -1 +endif $(eval $(call rule_for, \ AS, %.o, %.s, \ @@ -31,6 +38,12 @@ $(eval $(call rule_for, \ $$(CXX) $$(CXXFLAGS) $$(SFLAGS) -c $$< -o $$@ \ )) +$(eval $(call rule_for, \ + CPP, %, %.inc, \ + $$(CPP) -P $$< $$@ \ +)) + +ifdef EXE ifeq ($(OS),Windows_NT) # Work around command-line length limit # On Msys2 the max command line is 32 000 characters. Our standard LD command @@ -47,6 +60,7 @@ $(eval $(call rule_for, \ $$(LD) $$^ $$(LDFLAGS) -o $$@ \ )) endif +endif $(eval $(call rule_for, \ WINDRES, %.o, %.rc, \ diff --git a/build/targets.all.mak b/build/targets.all.mak index 1a48a316e..cdb5c3633 100644 --- a/build/targets.all.mak +++ b/build/targets.all.mak @@ -40,18 +40,16 @@ all_official: $(Q) $(MAKE) MODEL=n0100 epsilon.official.onboarding.dfu $(Q) cp output/release/device/n0100/epsilon.official.onboarding.dfu output/all_official/epsilon.device.n0100.dfu $(Q) echo "BUILD_FIRMWARE SIMULATOR WEB ZIP" - $(Q) $(MAKE) DEBUG=0 PLATFORM=simulator TARGET=web clean - $(Q) $(call source_emsdk); $(MAKE) DEBUG=0 PLATFORM=simulator TARGET=web output/release/simulator/web/simulator.official.zip - $(Q) cp output/release/simulator/web/simulator.official.zip output/all_official/simulator.web.zip + $(Q) $(MAKE) PLATFORM=simulator TARGET=web clean + $(Q) $(call source_emsdk); $(MAKE) PLATFORM=simulator TARGET=web epsilon.official.zip + $(Q) cp output/release/simulator/web/epsilon.official.zip output/all_official/simulator.web.zip $(Q) echo "BUILD_FIRMWARE SIMULATOR WEB JS" - $(Q) $(call source_emsdk); $(MAKE) DEBUG=0 PLATFORM=simulator TARGET=web epsilon.official.js + $(Q) $(call source_emsdk); $(MAKE) PLATFORM=simulator TARGET=web epsilon.official.js $(Q) cp output/release/simulator/web/epsilon.official.js output/all_official/epsilon.js - $(Q) cp output/release/simulator/web/epsilon.official.js.mem output/all_official/epsilon.js.mem $(Q) echo "BUILD_FIRMWARE SIMULATOR WEB PYTHON JS" - $(Q) $(MAKE) DEBUG=0 PLATFORM=simulator TARGET=web clean - $(Q) $(call source_emsdk); $(MAKE) DEBUG=0 PLATFORM=simulator TARGET=web EPSILON_GETOPT=1 EPSILON_APPS=code epsilon.official.js + $(Q) $(MAKE) PLATFORM=simulator TARGET=web clean + $(Q) $(call source_emsdk); $(MAKE) PLATFORM=simulator TARGET=web EPSILON_GETOPT=1 EPSILON_APPS=code epsilon.official.js $(Q) cp output/release/simulator/web/epsilon.official.js output/all_official/epsilon.python.js - $(Q) cp output/release/simulator/web/epsilon.official.js.mem output/all_official/epsilon.python.js.mem $(Q) echo "BUILD_FIRMWARE SIMULATOR ANDROID" $(Q) $(MAKE) PLATFORM=simulator TARGET=android clean $(Q) $(MAKE) PLATFORM=simulator TARGET=android epsilon.official.signed.apk diff --git a/build/targets.simulator.android.mak b/build/targets.simulator.android.mak new file mode 100644 index 000000000..e6f2d9dbc --- /dev/null +++ b/build/targets.simulator.android.mak @@ -0,0 +1,3 @@ +ifndef ARCH +HANDY_TARGETS_EXTENSIONS += apk +endif diff --git a/build/targets.simulator.web.mak b/build/targets.simulator.web.mak index a1774a34a..e8ef36832 100644 --- a/build/targets.simulator.web.mak +++ b/build/targets.simulator.web.mak @@ -1,6 +1,3 @@ -$(BUILD_DIR)/epsilon%packed.js: EMSCRIPTEN_INIT_FILE = 0 +HANDY_TARGETS_EXTENSIONS += zip $(BUILD_DIR)/test.headless.js: EMSCRIPTEN_MODULARIZE = 0 - -$(BUILD_DIR)/epsilon.packed.js: $(call object_for,$(epsilon_src)) -$(BUILD_DIR)/epsilon.official.packed.js: $(call object_for,$(epsilon_official_src)) diff --git a/build/toolchain.android.mak b/build/toolchain.android.mak index 2249be610..ed6762dbf 100644 --- a/build/toolchain.android.mak +++ b/build/toolchain.android.mak @@ -13,16 +13,16 @@ NDK_TOOLCHAIN_PATH = $(NDK_PATH)/toolchains/llvm/prebuilt/$(NDK_HOST_TAG)/bin # is no toolchain for those archs on those API levels. Let's enforce NDK_VERSION # at 21 for these archs, and 16 for the others. -ifeq ($(NDK_ABI),armeabi-v7a) +ifeq ($(ARCH),armeabi-v7a) NDK_TARGET = armv7a-linux-androideabi NDK_VERSION = 16 -else ifeq ($(NDK_ABI),arm64-v8a) +else ifeq ($(ARCH),arm64-v8a) NDK_TARGET = aarch64-linux-android NDK_VERSION = 21 -else ifeq ($(NDK_ABI),x86) +else ifeq ($(ARCH),x86) NDK_TARGET = i686-linux-android NDK_VERSION = 16 -else ifeq ($(NDK_ABI),x86_64) +else ifeq ($(ARCH),x86_64) NDK_TARGET = x86_64-linux-android NDK_VERSION = 21 endif diff --git a/build/toolchain.emscripten.mak b/build/toolchain.emscripten.mak index fb6aafc39..198d745ca 100644 --- a/build/toolchain.emscripten.mak +++ b/build/toolchain.emscripten.mak @@ -1,6 +1,7 @@ CC = emcc CXX = emcc LD = emcc +CPP = cpp EMSCRIPTEN_ASYNC_SYMBOLS = \ SAFE_HEAP_LOAD \ @@ -120,20 +121,9 @@ endif # Configure EMFLAGS EMFLAGS += -s WASM=0 -# Since emcc 1.39.5, DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR is defautly -# to 1 which discards old looks up to find DOM elements. However SDL relies on -# the presence of a target "#canvas" that used to return the Module['canvas'] -# target but not anymore. It also expects the existence of Module['canvas']. -# Until we fix the DOM of the html calling epsilon module, we use the deprecated -# 'find_event_target'. -# TODO: fix DOM of htmls files that uses epsilon js module -EMFLAGS += -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0 - # Configure LDFLAGS EMSCRIPTEN_MODULARIZE ?= 1 -LDFLAGS += -s MODULARIZE=$(EMSCRIPTEN_MODULARIZE) -s 'EXPORT_NAME="Epsilon"' -EMSCRIPTEN_INIT_FILE ?= 1 -LDFLAGS += --memory-init-file $(EMSCRIPTEN_INIT_FILE) +LDFLAGS += -s MODULARIZE=$(EMSCRIPTEN_MODULARIZE) -s 'EXPORT_NAME="Epsilon"' --memory-init-file 0 SFLAGS += $(EMFLAGS) LDFLAGS += $(EMFLAGS) -Oz -s EXPORTED_FUNCTIONS='["_main", "_IonSimulatorKeyboardKeyDown", "_IonSimulatorKeyboardKeyUp", "_IonSimulatorEventsPushEvent", "_IonSoftwareVersion", "_IonPatchLevel", "_IonDisplayForceRefresh"]' -s EXTRA_EXPORTED_RUNTIME_METHODS='["UTF8ToString"]' diff --git a/escher/src/run_loop.cpp b/escher/src/run_loop.cpp index a12957213..6c9ead568 100644 --- a/escher/src/run_loop.cpp +++ b/escher/src/run_loop.cpp @@ -1,8 +1,5 @@ #include #include -#ifdef __EMSCRIPTEN__ -#include -#endif RunLoop::RunLoop() : m_time(0) { diff --git a/ion/src/simulator/android/Makefile b/ion/src/simulator/android/Makefile index 0a3d25619..016023c76 100644 --- a/ion/src/simulator/android/Makefile +++ b/ion/src/simulator/android/Makefile @@ -11,6 +11,12 @@ $(call object_for,ion/src/simulator/shared/main.cpp) : SFLAGS += -DEPSILON_SDL_F LDFLAGS += -ljnigraphics -llog + +# If ARCH is not defined, we will re-trigger a build for each avaialble ARCH. +# This is used to build APKs, which needs to embbed a binary for each ARCH. + +ifndef ARCH + # Android resources # Some android resources needs to be filtered through ImageMagick. Others are # simply copied over. @@ -27,56 +33,50 @@ $(BUILD_DIR)/app/res/%.xml: ion/src/simulator/android/src/res/%.xml | $$(@D)/. $(call rule_label,COPY) $(Q) cp $< $@ -# Cross-ABI libepsilon.so -# This file is loaded is loaded only once, which prevents us from tracking -# dependencies across ABIs. As a shortcut, we simply force a re-make of -# libepsilon.so for each ABI. +# This rule allow us to build any executable (%) for a specified ARCH ($1) +# We depend on a phony target to make sure this rule is always executed .PHONY: force_remake - -define rule_for_libepsilon -$$(BUILD_DIR)/app/libs/%/lib$(1): force_remake $$$$(@D)/. - $(Q) echo "MAKE NDK_ABI=$$*" - $(Q) $$(MAKE) NDK_ABI=$$* $(1) - $(Q) cp $$(BUILD_DIR)/$$*/$(1) $$@ +define rule_for_arch_executable +.PRECIOUS: $$(BUILD_DIR)/$(1)/%.so +$$(BUILD_DIR)/$(1)/%.so: force_remake + $(Q) echo "MAKE ARCH=$(1) $$*.so" + $(Q) $$(MAKE) ARCH=$(1) --silent $$*.so endef -$(eval $(call rule_for_libepsilon,epsilon.so)) -$(eval $(call rule_for_libepsilon,epsilon.official.so)) +# We need to put the .so files somewhere Gradle can pick them up. +# We decided to use the location "app/libs/$EXECUTABLE/$ARCH/libepsilon.so" +# This way it's easy to import the shared object from Java code (it's always +# named libepsilon.so), and it's easy to make Gradle use a given executable by +# simply using the jniLibs.src directive. +define path_for_arch_jni_lib +$$(BUILD_DIR)/app/libs/%/$(1)/libepsilon.so +endef -# If NDK_ABI is not defined, we will re-trigger a build for each avaialble ABI. -# This is used to build APKs, which needs to embbed a binary for each ABI. +define rule_for_arch_jni_lib +$(call path_for_arch_jni_lib,$(1)): $$(BUILD_DIR)/$(1)/%.so | $$$$(@D)/. + $(Q) cp $$< $$@ +endef -ifndef NDK_ABI +$(foreach ARCH,$(ARCHS),$(eval $(call rule_for_arch_executable,$(ARCH)))) +$(foreach ARCH,$(ARCHS),$(eval $(call rule_for_arch_jni_lib,$(ARCH)))) -NDK_ABIS = armeabi-v7a arm64-v8a x86 x86_64 +apk_deps = $(foreach ARCH,$(ARCHS),$(call path_for_arch_jni_lib,$(ARCH))) +apk_deps += $(subst ion/src/simulator/android/src/res,$(BUILD_DIR)/app/res,$(wildcard ion/src/simulator/android/src/res/*/*)) +apk_deps += $(addprefix $(BUILD_DIR)/app/res/,mipmap/ic_launcher.png mipmap-v26/ic_launcher_foreground.png) -epsilon_apk_deps = $(subst ion/src/simulator/android/src/res,$(BUILD_DIR)/app/res,$(wildcard ion/src/simulator/android/src/res/*/*)) -epsilon_apk_deps += $(addprefix $(BUILD_DIR)/app/res/,mipmap/ic_launcher.png mipmap-v26/ic_launcher_foreground.png) +.PRECIOUS: $(apk_deps) -define rule_for_gradle -.PHONY: gradle_$1_$2 -gradle_$1_$2: $$(epsilon_apk_deps) $$(patsubst %,$$(BUILD_DIR)/app/libs/%/libepsilon$2so,$(NDK_ABIS)) +$(BUILD_DIR)/%.apk: $(apk_deps) @echo "GRADLE ion/src/simulator/android/build.gradle" - $(Q) ANDROID_HOME=$(ANDROID_HOME) EPSILON_VERSION=$(EPSILON_VERSION) BUILD_DIR=$(BUILD_DIR) ion/src/simulator/android/gradlew -b ion/src/simulator/android/build.gradle $1 -endef - -$(eval $(call rule_for_gradle,assembleCodesigned,.)) -$(eval $(call rule_for_gradle,assembleRelease,.)) -$(eval $(call rule_for_gradle,assembleCodesigned,.official.)) -$(eval $(call rule_for_gradle,assembleRelease,.official.)) + $(Q) ANDROID_HOME=$(ANDROID_HOME) EPSILON_VERSION=$(EPSILON_VERSION) BUILD_DIR=$(BUILD_DIR) EPSILON_VARIANT=$* ion/src/simulator/android/gradlew -b ion/src/simulator/android/build.gradle assembleRelease + $(Q) cp $(BUILD_DIR)/app/outputs/apk/release/android-release*.apk $@ DEFAULT = epsilon.apk -.PHONY: epsilon%signed.apk -epsilon%signed.apk: gradle_assembleCodesigned_% - $(warning This is a signed build.) - -.PHONY: epsilon%apk -epsilon%apk: gradle_assembleRelease_% - $(warning Building without code signing. Build epsilon$*signed.apk to generate a signed version.) - -.PHONY: epsilon_run -epsilon_run: gradle_installDebug +.PHONY: %_run +%_run: $(BUILD_DIR)/%.apk + @echo "ADB $*.apk" + $(Q) adb install $< endif diff --git a/ion/src/simulator/android/build.gradle b/ion/src/simulator/android/build.gradle index 531effaf0..33184cff6 100644 --- a/ion/src/simulator/android/build.gradle +++ b/ion/src/simulator/android/build.gradle @@ -53,10 +53,9 @@ android { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt') - } - codesigned { - initWith buildTypes.release - signingConfig signingConfigs.environment + if (projectVariable('SIGNING_STORE_FILE')) { + signingConfig signingConfigs.environment + } } } sourceSets{ @@ -64,12 +63,13 @@ android { manifest.srcFile 'src/AndroidManifest.xml' res.srcDir BUILD_DIR + '/res' java.srcDir 'src' - jniLibs.srcDir BUILD_DIR + '/libs' + jniLibs.srcDir BUILD_DIR + '/libs/' + System.getenv('EPSILON_VARIANT') assets.srcDir '../assets' } } lintOptions { abortOnError false + checkReleaseBuilds false } } diff --git a/ion/src/simulator/web/Makefile b/ion/src/simulator/web/Makefile index b31be6f0b..89f41927a 100644 --- a/ion/src/simulator/web/Makefile +++ b/ion/src/simulator/web/Makefile @@ -10,6 +10,9 @@ SFLAGS += -Iion/src/simulator/web/include # Only render the screen, not the whole calculator which will be drawn in HTML SFLAGS += -DEPSILON_SDL_SCREEN_ONLY=1 +# Enable to set environment variables for a module +LDFLAGS += --pre-js ion/src/simulator/web/preamble_env.js + ion_src += $(addprefix ion/src/simulator/web/, \ callback.cpp \ helpers.cpp \ @@ -24,14 +27,14 @@ ion_src += ion/src/simulator/shared/dummy/telemetry_init.cpp ion_src += ion/src/shared/telemetry_console.cpp endif -DEFAULT = $(BUILD_DIR)/simulator.zip +DEFAULT = epsilon.zip -$(BUILD_DIR)/simulator%zip: $(BUILD_DIR)/epsilon%packed.js +$(BUILD_DIR)/epsilon%zip: $(BUILD_DIR)/epsilon%js $(BUILD_DIR)/ion/src/simulator/web/simulator.html @rm -rf $(basename $@) @mkdir -p $(basename $@) - @cp $^ $(basename $@)/epsilon.js + @cp $< $(basename $@)/epsilon.js @cp ion/src/simulator/assets/background.jpg $(basename $@)/ - @cp ion/src/simulator/web/simulator.html $(basename $@)/ + @cp $(BUILD_DIR)/ion/src/simulator/web/simulator.html $(basename $@)/ $(call rule_label,ZIP) @zip -r -9 -j $@ $(basename $@) > /dev/null @rm -rf $(basename $@) diff --git a/ion/src/simulator/web/preamble_env.js b/ion/src/simulator/web/preamble_env.js new file mode 100644 index 000000000..42fc03f95 --- /dev/null +++ b/ion/src/simulator/web/preamble_env.js @@ -0,0 +1,21 @@ +/* Use: + * + * var options = { + canvas: ..., + env: { + VARIABLE_NAME: VARIABLE_VALUE + } + }; + Epsilon(options); + * + * Cf https://github.com/emscripten-core/emscripten/issues/9827 + * */ + +Module["preRun"] = function () { + if (Module['env'] && typeof Module['env'] === 'object') { + for (var key in Module['env']) { + if (Module['env'].hasOwnProperty(key)) + ENV[key] = Module['env'][key]; + } + } +} diff --git a/ion/src/simulator/web/simulator-keyboard.html b/ion/src/simulator/web/simulator-keyboard.html new file mode 100644 index 000000000..86425fc37 --- /dev/null +++ b/ion/src/simulator/web/simulator-keyboard.html @@ -0,0 +1,54 @@ +
+ +
+ + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + +
+
diff --git a/ion/src/simulator/web/simulator-setup.js b/ion/src/simulator/web/simulator-setup.js new file mode 100644 index 000000000..d4eb18b97 --- /dev/null +++ b/ion/src/simulator/web/simulator-setup.js @@ -0,0 +1,65 @@ +/* To use this file you should adhere to the following conventions: + * - Main canvas has id #canvas + * - Secondary canvas (fullscreen on the side) has id #secondary-canvas + * - Calculator keyboard has id #keyboard */ + +var Module; + +(function () { + var mainCanvas = document.getElementById('canvas'); + var secondaryCanvasContext = document.getElementById('secondary-canvas').getContext('2d'); + var epsilonLanguage = document.documentElement.lang || window.navigator.language.split('-')[0]; + Module = { + canvas: mainCanvas, + arguments: ['--language', epsilonLanguage], + onDisplayRefresh: function() { + secondaryCanvasContext.drawImage(mainCanvas, 0, 0); + } + } + + Epsilon(Module); + + document.querySelectorAll('#keyboard span').forEach(function(span){ + function eventHandler(keyHandler) { + return function(ev) { + var key = this.getAttribute('data-key'); + keyHandler(key); + /* Always prevent default action of event. + * First, this will prevent the browser from delaying that event. Indeed + * the browser would otherwise try to see if that event could have any + * other meaning (e.g. a click) and might delay it as a result. + * Second, this prevents touch events to be handled twice. Indeed, for + * backward compatibility reasons, mobile browsers usually create a fake + * mouse event after each real touch event. This allows desktop websites + * to work unmodified on mobile devices. But here we are explicitly + * handling both touch and mouse events. We therefore need to disable + * the default action of touch events, otherwise the handler would get + * called twice. */ + ev.preventDefault(); + }; + } + /* We decide to hook both to touch and mouse events + * On most mobile browsers, mouse events are generated if addition to touch + * events, so this could seem pointless. But those mouse events are not + * generated in real time: instead, they are buffered and eventually fired + * in a very rapid sequence. This prevents Epsilon from generating an event + * since this quick sequence will trigger the debouncer. */ + ['touchstart', 'mousedown'].forEach(function(type){ + span.addEventListener(type, eventHandler(Module._IonSimulatorKeyboardKeyDown)); + }); + ['touchend', 'mouseup'].forEach(function(type){ + span.addEventListener(type, eventHandler(Module._IonSimulatorKeyboardKeyUp)); + }); + }); +}()); + +function screenshot() { + // toDataURL needs the canvas to be refreshed + Module._IonDisplayForceRefresh(); + + var canvas = document.getElementById('canvas'); + var link = document.createElement('a'); + link.download = 'screenshot.png'; + link.href = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream'); + link.click(); +} diff --git a/ion/src/simulator/web/simulator.html b/ion/src/simulator/web/simulator.html.inc similarity index 56% rename from ion/src/simulator/web/simulator.html rename to ion/src/simulator/web/simulator.html.inc index f5514dacf..b19fadea5 100644 --- a/ion/src/simulator/web/simulator.html +++ b/ion/src/simulator/web/simulator.html.inc @@ -1,3 +1,5 @@ +#define KEYBOARD #keyboard + @@ -65,7 +67,7 @@ a.action svg { max-height: 92.0vh; max-width: 100%; display: block; } - .calculator .screen { + .calculator #canvas { position: absolute; top: 8.5%; left: 16.25%; @@ -84,84 +86,84 @@ a.action svg { display: inline-block; margin-right: 10px; } - .calculator .keyboard { + KEYBOARD { position: absolute; top: 38.5%; left: 10%; width: 81%; bottom: 5%; } - .calculator .keyboard span { + KEYBOARD span { cursor: pointer; border-radius: 40%; display: block; float: left; } - .calculator .keyboard span:hover { + KEYBOARD span:hover { background-color: rgba(0, 0, 0, 0.1); } - .calculator .keyboard span:active { + KEYBOARD span:active { background-color: rgba(0, 0, 0, 0.2); } - .calculator .keyboard .nav { + KEYBOARD .nav { position: absolute; top: 0; height: 27%; width: 100%; } - .calculator .keyboard .nav span { + KEYBOARD .nav span { position: absolute; } - .calculator .keyboard .nav .left { + KEYBOARD .nav .left { top: 36%; left: 2%; width: 15%; height: 28%; } - .calculator .keyboard .nav .right { + KEYBOARD .nav .right { top: 36%; left: 17%; width: 15%; height: 28%; } - .calculator .keyboard .nav .top { + KEYBOARD .nav .top { top: 7%; left: 12%; width: 10%; height: 40%; } - .calculator .keyboard .nav .bottom { + KEYBOARD .nav .bottom { top: 53%; left: 12%; width: 10%; height: 40%; } - .calculator .keyboard .nav .home { + KEYBOARD .nav .home { top: 15%; left: 41%; width: 16%; height: 33%; } - .calculator .keyboard .nav .power { + KEYBOARD .nav .power { top: 54%; left: 42%; width: 16%; height: 33%; } - .calculator .keyboard .nav .ok { + KEYBOARD .nav .ok { top: 31%; left: 67%; width: 13%; height: 38%; } - .calculator .keyboard .nav .back { + KEYBOARD .nav .back { top: 31%; left: 84%; width: 13%; height: 38%; } - .calculator .keyboard .functions { + KEYBOARD .functions { position: absolute; top: 26.75%; left: 0.5%; width: 98%; } - .calculator .keyboard .functions span { + KEYBOARD .functions span { margin: 1.7% 1%; width: 14.65%; height: 0; padding-top: 10%; } - .calculator .keyboard .digits { + KEYBOARD .digits { position: absolute; top: 56.5%; left: 0.5%; width: 98%; } - .calculator .keyboard .digits span { + KEYBOARD .digits span { margin: 1.8% 2%; width: 16%; height: 0; @@ -173,61 +175,8 @@ a.action svg {
NumWorks Calculator - -
- -
- - - - - - - - - - - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - -
-
+ +#include "simulator-keyboard.html"
- +