Web: Emscripten port updated to current tools. Add saving of options in local storage.

This commit is contained in:
verylowfreq 2022-08-05 11:13:03 +09:00 committed by ruevs
parent 5ca6d04e02
commit f7415048a5
11 changed files with 150 additions and 92 deletions

View File

@ -186,6 +186,10 @@ if(APPLE)
set(CMAKE_FIND_FRAMEWORK LAST) set(CMAKE_FIND_FRAMEWORK LAST)
endif() endif()
if(EMSCRIPTEN)
set(M_LIBRARY "" CACHE STRING "libm (not necessary)" FORCE)
endif()
message(STATUS "Using in-tree libdxfrw") message(STATUS "Using in-tree libdxfrw")
add_subdirectory(extlib/libdxfrw) add_subdirectory(extlib/libdxfrw)

View File

@ -168,37 +168,50 @@ command-line interface is built as `build/bin/solvespace-cli.exe`.
Space Navigator support will not be available. Space Navigator support will not be available.
## Building for web ### Building for web (very experimental)
You will need [Emscripten][]. First, install and prepare `emsdk`: **Please note that this port contains many critical bugs and unimplemented core functions.**
git clone https://github.com/juj/emsdk.git You will need the usual build tools, cmake and [Emscripten][]. On a Debian derivative (e.g. Ubuntu) dependencies other than Emscripten can be installed with:
cd emsdk
./emsdk install latest ```sh
./emsdk update latest apt-get install git build-essential cmake
source ./emsdk_env.sh ```
cd ..
First, install and prepare `emsdk`:
```sh
git clone https://github.com/emscripten-core/emsdk
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
cd ..
```
Before building, check out the project and the necessary submodules: Before building, check out the project and the necessary submodules:
git clone https://github.com/solvespace/solvespace ```sh
cd solvespace git clone https://github.com/solvespace/solvespace
git submodule update cd solvespace
git submodule update --init
```
After that, build SolveSpace as following: After that, build SolveSpace as following:
mkdir build ```sh
cd build mkdir build
cmake .. -DCMAKE_TOOLCHAIN_FILE=../cmake/Toolchain-emscripten.cmake \ cd build
-DCMAKE_BUILD_TYPE=Release emcmake cmake .. -DCMAKE_BUILD_TYPE=Release -DENABLE_LTO="ON" -DENABLE_TESTS="OFF" -DENABLE_CLI="OFF" -DENABLE_COVERAGE="OFF"
make make
```
The graphical interface is built as multiple files in the `build/bin` directory with names The graphical interface is built as multiple files in the `build/bin` directory with names
starting with `solvespace`. It can be run locally with `emrun build/bin/solvespace.html`. starting with `solvespace`. It can be run locally with `emrun build/bin/solvespace.html`.
The command-line interface is not available. The command-line interface is not available.
[emscripten]: https://kripken.github.io/emscripten-site/ [emscripten]: https://emscripten.org/
## Building on macOS ## Building on macOS

View File

@ -7,3 +7,14 @@ set(CMAKE_EXECUTABLE_SUFFIX ".html")
set(CMAKE_SIZEOF_VOID_P 4) set(CMAKE_SIZEOF_VOID_P 4)
set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS FALSE) set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS FALSE)
# FIXME(emscripten): Suppress non-c-typedef-for-linkage warnings in solvespace.h
add_compile_options(-Wno-non-c-typedef-for-linkage)
# Enable optimization. Workaround for "too many locals" error when runs on browser.
if(CMAKE_BUILD_TYPE STREQUAL Release)
add_compile_options(-O2)
else()
add_compile_options(-O1)
endif()

View File

@ -6,3 +6,8 @@ add_executable(CDemo
target_link_libraries(CDemo target_link_libraries(CDemo
slvs) slvs)
if(EMSCRIPTEN)
set_target_properties(CDemo PROPERTIES
LINK_FLAGS "-s TOTAL_MEMORY=134217728")
endif()

View File

@ -304,35 +304,12 @@ if(ENABLE_GUI)
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES" XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME "YES"
XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.solvespace" XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "com.solvespace"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
else()
target_sources(solvespace PRIVATE
platform/guigtk.cpp)
target_include_directories(solvespace PRIVATE SYSTEM
${GTKMM_INCLUDE_DIRS}
${JSONC_INCLUDE_DIRS}
${FONTCONFIG_INCLUDE_DIRS})
target_link_directories(solvespace PRIVATE
${GTKMM_LIBRARY_DIRS}
${JSONC_LIBRARY_DIRS}
${FONTCONFIG_LIBRARY_DIRS})
target_link_libraries(solvespace PRIVATE
${GTKMM_LIBRARIES}
${JSONC_LIBRARIES}
${FONTCONFIG_LIBRARIES})
endif()
if(MSVC)
set_target_properties(solvespace PROPERTIES
LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /INCREMENTAL:NO /OPT:REF")
elseif(APPLE)
set_target_properties(solvespace PROPERTIES
OUTPUT_NAME SolveSpace)
elseif(EMSCRIPTEN) elseif(EMSCRIPTEN)
set(SHELL ${CMAKE_CURRENT_SOURCE_DIR}/platform/html/emshell.html) set(SHELL ${CMAKE_CURRENT_SOURCE_DIR}/platform/html/emshell.html)
set(LINK_FLAGS set(LINK_FLAGS
--bind --shell-file ${SHELL} --bind --shell-file ${SHELL}
--no-heap-copy -s ALLOW_MEMORY_GROWTH=1 --no-heap-copy -s ALLOW_MEMORY_GROWTH=1 -s WASM=1 -s ASYNCIFY=1
-s DYNCALLS=1 -s ASSERTIONS=1
-s TOTAL_STACK=33554432 -s TOTAL_MEMORY=134217728) -s TOTAL_STACK=33554432 -s TOTAL_MEMORY=134217728)
get_target_property(resource_names resources NAMES) get_target_property(resource_names resources NAMES)
@ -344,10 +321,12 @@ if(ENABLE_GUI)
list(APPEND LINK_FLAGS list(APPEND LINK_FLAGS
--emrun --emit-symbol-map --emrun --emit-symbol-map
-s DEMANGLE_SUPPORT=1 -s DEMANGLE_SUPPORT=1
-s SAFE_HEAP=1 -s SAFE_HEAP=1)
-s WASM=1)
endif() endif()
target_sources(solvespace PRIVATE
platform/guihtml.cpp)
string(REPLACE ";" " " LINK_FLAGS "${LINK_FLAGS}") string(REPLACE ";" " " LINK_FLAGS "${LINK_FLAGS}")
set_target_properties(solvespace PROPERTIES set_target_properties(solvespace PROPERTIES
LINK_FLAGS "${LINK_FLAGS}") LINK_FLAGS "${LINK_FLAGS}")
@ -368,6 +347,28 @@ if(ENABLE_GUI)
${EXECUTABLE_OUTPUT_PATH}/solvespaceui.js ${EXECUTABLE_OUTPUT_PATH}/solvespaceui.js
COMMENT "Copying UI script" COMMENT "Copying UI script"
VERBATIM) VERBATIM)
else()
target_sources(solvespace PRIVATE
platform/guigtk.cpp)
target_include_directories(solvespace PRIVATE SYSTEM
${GTKMM_INCLUDE_DIRS}
${JSONC_INCLUDE_DIRS}
${FONTCONFIG_INCLUDE_DIRS})
target_link_directories(solvespace PRIVATE
${GTKMM_LIBRARY_DIRS}
${JSONC_LIBRARY_DIRS}
${FONTCONFIG_LIBRARY_DIRS})
target_link_libraries(solvespace PRIVATE
${GTKMM_LIBRARIES}
${JSONC_LIBRARIES}
${FONTCONFIG_LIBRARIES})
endif()
if(MSVC)
set_target_properties(solvespace PROPERTIES
LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /INCREMENTAL:NO /OPT:REF")
endif() endif()
endif() endif()

View File

@ -45,7 +45,7 @@ static void HandleError(const char *file, int line, const char *function, const
FatalError(message); FatalError(message);
} }
static val Wrap(std::string str) { static val Wrap(const std::string& str) {
// FIXME(emscripten): a nicer way to do this? // FIXME(emscripten): a nicer way to do this?
EM_ASM($Wrap$ret = UTF8ToString($0), str.c_str()); EM_ASM($Wrap$ret = UTF8ToString($0), str.c_str());
return val::global("window")["$Wrap$ret"]; return val::global("window")["$Wrap$ret"];
@ -55,7 +55,7 @@ static std::string Unwrap(val emStr) {
// FIXME(emscripten): a nicer way to do this? // FIXME(emscripten): a nicer way to do this?
val emArray = val::global("window").call<val>("intArrayFromString", emStr, true) ; val emArray = val::global("window").call<val>("intArrayFromString", emStr, true) ;
val::global("window").set("$Wrap$input", emArray); val::global("window").set("$Wrap$input", emArray);
char *strC = (char *)EM_ASM_INT(return allocate($Wrap$input, 'i8', ALLOC_NORMAL)); char *strC = (char *)EM_ASM_INT(return allocate($Wrap$input, ALLOC_NORMAL));
std::string str(strC, emArray["length"].as<int>()); std::string str(strC, emArray["length"].as<int>());
free(strC); free(strC);
return str; return str;
@ -77,7 +77,7 @@ static val Wrap(std::function<void()> *func) {
// Fatal errors // Fatal errors
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
void FatalError(std::string message) { void FatalError(const std::string &message) {
fprintf(stderr, "%s", message.c_str()); fprintf(stderr, "%s", message.c_str());
#ifndef NDEBUG #ifndef NDEBUG
emscripten_debugger(); emscripten_debugger();
@ -92,31 +92,37 @@ void FatalError(std::string message) {
class SettingsImplHtml : public Settings { class SettingsImplHtml : public Settings {
public: public:
void FreezeInt(const std::string &key, uint32_t value) { void FreezeInt(const std::string &key, uint32_t value) {
// FIXME(emscripten): implement val::global("localStorage").call<void>("setItem", Wrap(key), value);
} }
uint32_t ThawInt(const std::string &key, uint32_t defaultValue = 0) { uint32_t ThawInt(const std::string &key, uint32_t defaultValue = 0) {
// FIXME(emscripten): implement val value = val::global("localStorage").call<val>("getItem", Wrap(key));
if(value == val::null())
return defaultValue; return defaultValue;
return val::global("parseInt")(value, 0).as<int>();
} }
void FreezeFloat(const std::string &key, double value) { void FreezeFloat(const std::string &key, double value) {
// FIXME(emscripten): implement val::global("localStorage").call<void>("setItem", Wrap(key), value);
} }
double ThawFloat(const std::string &key, double defaultValue = 0.0) { double ThawFloat(const std::string &key, double defaultValue = 0.0) {
// FIXME(emscripten): implement val value = val::global("localStorage").call<val>("getItem", Wrap(key));
if(value == val::null())
return defaultValue; return defaultValue;
return val::global("parseFloat")(value).as<double>();
} }
void FreezeString(const std::string &key, const std::string &value) { void FreezeString(const std::string &key, const std::string &value) {
// FIXME(emscripten): implement val::global("localStorage").call<void>("setItem", Wrap(key), value);
} }
std::string ThawString(const std::string &key, std::string ThawString(const std::string &key,
const std::string &defaultValue = "") { const std::string &defaultValue = "") {
// FIXME(emscripten): implement val value = val::global("localStorage").call<val>("getItem", Wrap(key));
if(value == val::null())
return defaultValue; return defaultValue;
return Unwrap(value);
} }
}; };
@ -244,7 +250,7 @@ public:
} else { } else {
val htmlLabel = val::global("document").call<val>("createElement", val("span")); val htmlLabel = val::global("document").call<val>("createElement", val("span"));
htmlLabel["classList"].call<void>("add", val("label")); htmlLabel["classList"].call<void>("add", val("label"));
htmlLabel["innerText"] = Wrap(label); htmlLabel.set("innerText", Wrap(label));
menuItem->htmlMenuItem.call<void>("appendChild", htmlLabel); menuItem->htmlMenuItem.call<void>("appendChild", htmlLabel);
} }
menuItem->htmlMenuItem.call<void>("addEventListener", val("trigger"), menuItem->htmlMenuItem.call<void>("addEventListener", val("trigger"),
@ -342,7 +348,7 @@ static KeyboardEvent handledKeyboardEvent;
class WindowImplHtml final : public Window { class WindowImplHtml final : public Window {
public: public:
std::string emCanvasId; std::string emCanvasSel;
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE emContext = 0; EMSCRIPTEN_WEBGL_CONTEXT_HANDLE emContext = 0;
val htmlContainer; val htmlContainer;
@ -351,8 +357,8 @@ public:
std::function<void()> editingDoneFunc; std::function<void()> editingDoneFunc;
std::shared_ptr<MenuBarImplHtml> menuBar; std::shared_ptr<MenuBarImplHtml> menuBar;
WindowImplHtml(val htmlContainer, std::string emCanvasId) : WindowImplHtml(val htmlContainer, std::string emCanvasSel) :
emCanvasId(emCanvasId), emCanvasSel(emCanvasSel),
htmlContainer(htmlContainer), htmlContainer(htmlContainer),
htmlEditor(val::global("document").call<val>("createElement", val("input"))) htmlEditor(val::global("document").call<val>("createElement", val("input")))
{ {
@ -367,43 +373,43 @@ public:
htmlContainer.call<void>("appendChild", htmlEditor); htmlContainer.call<void>("appendChild", htmlEditor);
sscheck(emscripten_set_resize_callback( sscheck(emscripten_set_resize_callback(
"#window", this, /*useCapture=*/false, EMSCRIPTEN_EVENT_TARGET_WINDOW, this, /*useCapture=*/false,
WindowImplHtml::ResizeCallback)); WindowImplHtml::ResizeCallback));
sscheck(emscripten_set_resize_callback( sscheck(emscripten_set_resize_callback(
emCanvasId.c_str(), this, /*useCapture=*/false, emCanvasSel.c_str(), this, /*useCapture=*/false,
WindowImplHtml::ResizeCallback)); WindowImplHtml::ResizeCallback));
sscheck(emscripten_set_mousemove_callback( sscheck(emscripten_set_mousemove_callback(
emCanvasId.c_str(), this, /*useCapture=*/false, emCanvasSel.c_str(), this, /*useCapture=*/false,
WindowImplHtml::MouseCallback)); WindowImplHtml::MouseCallback));
sscheck(emscripten_set_mousedown_callback( sscheck(emscripten_set_mousedown_callback(
emCanvasId.c_str(), this, /*useCapture=*/false, emCanvasSel.c_str(), this, /*useCapture=*/false,
WindowImplHtml::MouseCallback)); WindowImplHtml::MouseCallback));
sscheck(emscripten_set_click_callback( sscheck(emscripten_set_click_callback(
emCanvasId.c_str(), this, /*useCapture=*/false, emCanvasSel.c_str(), this, /*useCapture=*/false,
WindowImplHtml::MouseCallback)); WindowImplHtml::MouseCallback));
sscheck(emscripten_set_dblclick_callback( sscheck(emscripten_set_dblclick_callback(
emCanvasId.c_str(), this, /*useCapture=*/false, emCanvasSel.c_str(), this, /*useCapture=*/false,
WindowImplHtml::MouseCallback)); WindowImplHtml::MouseCallback));
sscheck(emscripten_set_mouseup_callback( sscheck(emscripten_set_mouseup_callback(
emCanvasId.c_str(), this, /*useCapture=*/false, emCanvasSel.c_str(), this, /*useCapture=*/false,
WindowImplHtml::MouseCallback)); WindowImplHtml::MouseCallback));
sscheck(emscripten_set_mouseleave_callback( sscheck(emscripten_set_mouseleave_callback(
emCanvasId.c_str(), this, /*useCapture=*/false, emCanvasSel.c_str(), this, /*useCapture=*/false,
WindowImplHtml::MouseCallback)); WindowImplHtml::MouseCallback));
sscheck(emscripten_set_wheel_callback( sscheck(emscripten_set_wheel_callback(
emCanvasId.c_str(), this, /*useCapture=*/false, emCanvasSel.c_str(), this, /*useCapture=*/false,
WindowImplHtml::WheelCallback)); WindowImplHtml::WheelCallback));
sscheck(emscripten_set_keydown_callback( sscheck(emscripten_set_keydown_callback(
"#window", this, /*useCapture=*/false, EMSCRIPTEN_EVENT_TARGET_WINDOW, this, /*useCapture=*/false,
WindowImplHtml::KeyboardCallback)); WindowImplHtml::KeyboardCallback));
sscheck(emscripten_set_keyup_callback( sscheck(emscripten_set_keyup_callback(
"#window", this, /*useCapture=*/false, EMSCRIPTEN_EVENT_TARGET_WINDOW, this, /*useCapture=*/false,
WindowImplHtml::KeyboardCallback)); WindowImplHtml::KeyboardCallback));
sscheck(emscripten_set_webglcontextlost_callback( sscheck(emscripten_set_webglcontextlost_callback(
emCanvasId.c_str(), this, /*useCapture=*/false, emCanvasSel.c_str(), this, /*useCapture=*/false,
WindowImplHtml::ContextLostCallback)); WindowImplHtml::ContextLostCallback));
sscheck(emscripten_set_webglcontextrestored_callback( sscheck(emscripten_set_webglcontextrestored_callback(
emCanvasId.c_str(), this, /*useCapture=*/false, emCanvasSel.c_str(), this, /*useCapture=*/false,
WindowImplHtml::ContextRestoredCallback)); WindowImplHtml::ContextRestoredCallback));
ResizeCanvasElement(); ResizeCanvasElement();
@ -576,13 +582,13 @@ public:
emAttribs.alpha = false; emAttribs.alpha = false;
emAttribs.failIfMajorPerformanceCaveat = true; emAttribs.failIfMajorPerformanceCaveat = true;
sscheck(emContext = emscripten_webgl_create_context(emCanvasId.c_str(), &emAttribs)); sscheck(emContext = emscripten_webgl_create_context(emCanvasSel.c_str(), &emAttribs));
dbp("Canvas %s: got context %d", emCanvasId.c_str(), emContext); dbp("Canvas %s: got context %d", emCanvasSel.c_str(), emContext);
} }
static int ContextLostCallback(int eventType, const void *reserved, void *data) { static int ContextLostCallback(int eventType, const void *reserved, void *data) {
WindowImplHtml *window = (WindowImplHtml *)data; WindowImplHtml *window = (WindowImplHtml *)data;
dbp("Canvas %s: context lost", window->emCanvasId.c_str()); dbp("Canvas %s: context lost", window->emCanvasSel.c_str());
window->emContext = 0; window->emContext = 0;
if(window->onContextLost) { if(window->onContextLost) {
@ -593,30 +599,30 @@ public:
static int ContextRestoredCallback(int eventType, const void *reserved, void *data) { static int ContextRestoredCallback(int eventType, const void *reserved, void *data) {
WindowImplHtml *window = (WindowImplHtml *)data; WindowImplHtml *window = (WindowImplHtml *)data;
dbp("Canvas %s: context restored", window->emCanvasId.c_str()); dbp("Canvas %s: context restored", window->emCanvasSel.c_str());
window->SetupWebGLContext(); window->SetupWebGLContext();
return EM_TRUE; return EM_TRUE;
} }
void ResizeCanvasElement() { void ResizeCanvasElement() {
double width, height; double width, height;
std::string htmlContainerId = htmlContainer["id"].as<std::string>(); std::string htmlContainerSel = "#" + htmlContainer["id"].as<std::string>();
sscheck(emscripten_get_element_css_size(htmlContainerId.c_str(), &width, &height)); sscheck(emscripten_get_element_css_size(htmlContainerSel.c_str(), &width, &height));
width *= emscripten_get_device_pixel_ratio(); width *= emscripten_get_device_pixel_ratio();
height *= emscripten_get_device_pixel_ratio(); height *= emscripten_get_device_pixel_ratio();
int curWidth, curHeight; int curWidth, curHeight;
sscheck(emscripten_get_canvas_element_size(emCanvasId.c_str(), &curWidth, &curHeight)); sscheck(emscripten_get_canvas_element_size(emCanvasSel.c_str(), &curWidth, &curHeight));
if(curWidth != (int)width || curHeight != (int)curHeight) { if(curWidth != (int)width || curHeight != (int)curHeight) {
dbp("Canvas %s: resizing to (%g,%g)", emCanvasId.c_str(), width, height); dbp("Canvas %s: resizing to (%g,%g)", emCanvasSel.c_str(), width, height);
sscheck(emscripten_set_canvas_element_size( sscheck(emscripten_set_canvas_element_size(
emCanvasId.c_str(), (int)width, (int)height)); emCanvasSel.c_str(), (int)width, (int)height));
} }
} }
static void RenderCallback(void *data) { static void RenderCallback(void *data) {
WindowImplHtml *window = (WindowImplHtml *)data; WindowImplHtml *window = (WindowImplHtml *)data;
if(window->emContext == 0) { if(window->emContext == 0) {
dbp("Canvas %s: cannot render: no context", window->emCanvasId.c_str()); dbp("Canvas %s: cannot render: no context", window->emCanvasSel.c_str());
return; return;
} }
@ -661,7 +667,7 @@ public:
emStrategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF; emStrategy.canvasResolutionScaleMode = EMSCRIPTEN_FULLSCREEN_CANVAS_SCALE_HIDEF;
emStrategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT; emStrategy.filteringMode = EMSCRIPTEN_FULLSCREEN_FILTERING_DEFAULT;
sscheck(emscripten_request_fullscreen_strategy( sscheck(emscripten_request_fullscreen_strategy(
emCanvasId.c_str(), /*deferUntilInEventHandler=*/true, &emStrategy)); emCanvasSel.c_str(), /*deferUntilInEventHandler=*/true, &emStrategy));
} else { } else {
sscheck(emscripten_exit_fullscreen()); sscheck(emscripten_exit_fullscreen());
} }
@ -687,7 +693,7 @@ public:
} }
void GetContentSize(double *width, double *height) override { void GetContentSize(double *width, double *height) override {
sscheck(emscripten_get_element_css_size(emCanvasId.c_str(), width, height)); sscheck(emscripten_get_element_css_size(emCanvasSel.c_str(), width, height));
} }
void SetMinContentSize(double width, double height) override { void SetMinContentSize(double width, double height) override {
@ -711,8 +717,11 @@ public:
htmlContainer["style"].set("cursor", htmlCursor); htmlContainer["style"].set("cursor", htmlCursor);
} }
void SetTooltip(const std::string &text) override { void SetTooltip(const std::string &text, double x, double y,
// FIXME(emscripten): implement double width, double height) override {
val htmlCanvas =
val::global("document").call<val>("querySelector", emCanvasSel);
htmlCanvas.set("title", Wrap(text));
} }
bool IsEditorVisible() override { bool IsEditorVisible() override {
@ -766,10 +775,10 @@ WindowRef CreateWindow(Window::Kind kind, WindowRef parentWindow) {
std::string htmlContainerId = std::string("container") + std::to_string(windowNum); std::string htmlContainerId = std::string("container") + std::to_string(windowNum);
val htmlContainer = val htmlContainer =
val::global("document").call<val>("getElementById", htmlContainerId); val::global("document").call<val>("getElementById", htmlContainerId);
std::string emCanvasId = std::string("canvas") + std::to_string(windowNum); std::string emCanvasSel = std::string("#canvas") + std::to_string(windowNum);
windowNum++; windowNum++;
return std::make_shared<WindowImplHtml>(htmlContainer, emCanvasId); return std::make_shared<WindowImplHtml>(htmlContainer, emCanvasSel);
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -836,7 +845,7 @@ public:
htmlButton["classList"].call<void>("add", val("button")); htmlButton["classList"].call<void>("add", val("button"));
val::global("window").call<void>("setLabelWithMnemonic", htmlButton, Wrap(label)); val::global("window").call<void>("setLabelWithMnemonic", htmlButton, Wrap(label));
if(isDefault) { if(isDefault) {
htmlButton["classList"].call<void>("add", val("selected")); htmlButton["classList"].call<void>("add", val("default"), val("selected"));
} }
std::function<void()> responseFunc = [this, response] { std::function<void()> responseFunc = [this, response] {
@ -900,9 +909,15 @@ void OpenInBrowser(const std::string &url) {
val::global("window").call<void>("open", Wrap(url)); val::global("window").call<void>("open", Wrap(url));
} }
void InitGui(int argc, char **argv) { std::vector<std::string> InitGui(int argc, char **argv) {
static std::function<void()> onBeforeUnload = std::bind(&SolveSpaceUI::Exit, &SS);
val::global("window").call<void>("addEventListener", val("beforeunload"),
Wrap(&onBeforeUnload));
// FIXME(emscripten): get locale from user preferences // FIXME(emscripten): get locale from user preferences
SetLocale("en_US"); SetLocale("en_US");
return {};
} }
static void MainLoopIteration() { static void MainLoopIteration() {
@ -917,5 +932,7 @@ void ExitGui() {
exit(0); exit(0);
} }
void ClearGui() {}
} }
} }

View File

@ -79,4 +79,4 @@
}; };
</script><!-- </script><!--
-->{{{ SCRIPT }}}<!-- -->{{{ SCRIPT }}}<!--
--></body></html><!-- --></body></html>

View File

@ -85,6 +85,11 @@ body {
background: hsl(0, 0%, 20%); background: hsl(0, 0%, 20%);
color: white; color: white;
cursor: default; cursor: default;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
} }
/* Normal menu items */ /* Normal menu items */

View File

@ -95,6 +95,8 @@ window.addEventListener('keydown', function(event) {
} }
} else if(event.key == 'Enter') { } else if(event.key == 'Enter') {
selected.dispatchEvent(new Event('trigger')); selected.dispatchEvent(new Event('trigger'));
} else if(event.key == 'Escape' && hasClass(selected, 'default')) {
selected.dispatchEvent(new Event('trigger'));
} }
if(outSelected) removeClass(outSelected, 'selected'); if(outSelected) removeClass(outSelected, 'selected');
@ -127,7 +129,7 @@ window.addEventListener('keydown', function(event) {
} }
event.stopPropagation(); event.stopPropagation();
} }
}); }, {capture: true});
/* Menu helpers */ /* Menu helpers */
function isMenubar(element) { function isMenubar(element) {

View File

@ -516,7 +516,7 @@ static Platform::Path ResourcePath(const std::string &name) {
return path; return path;
} }
#elif defined(EMSCRIPTEN) #elif defined(__EMSCRIPTEN__)
static Platform::Path ResourcePath(const std::string &name) { static Platform::Path ResourcePath(const std::string &name) {
return Path::From("res/" + name); return Path::From("res/" + name);

View File

@ -6,7 +6,7 @@
#ifndef SOLVESPACE_GL3SHADER_H #ifndef SOLVESPACE_GL3SHADER_H
#define SOLVESPACE_GL3SHADER_H #define SOLVESPACE_GL3SHADER_H
#if defined(WIN32) || defined(EMSCRIPTEN) #if defined(WIN32) || defined(__EMSCRIPTEN__)
# define GL_APICALL /*static linkage*/ # define GL_APICALL /*static linkage*/
# define GL_GLEXT_PROTOTYPES # define GL_GLEXT_PROTOTYPES
# include <GLES2/gl2.h> # include <GLES2/gl2.h>