This commit is contained in:
slowriot 2026-09-25 22:01:47 +09:00 • committed by GitHub
commit fd1776e8f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 1757 additions and 5 deletions

View file

@ -640,6 +640,20 @@ jobs:
popd
make -C examples/example_sdl2_opengl3 -f Makefile.emscripten
- name: Build example_emscripten_opengl3
run: |
pushd emsdk-master
source ./emsdk_env.sh
popd
make -C examples/example_emscripten_opengl3 -f Makefile.emscripten
- name: Build example_emscripten_wgpu
run: |
pushd emsdk-master
source ./emsdk_env.sh
popd
make -C examples/example_emscripten_wgpu -f Makefile.emscripten
# This build compiles example_glfw_wgpu using Makefile.emscripten and Emscripten GLFW built-in implementation (-sUSE_GLFW=3)
# This ensures 2 things: the make build works, and the GLFW built-in implementation is tested
- name: Build example_glfw_wgpu with Emscripten/Makefile

2
.gitignore vendored
View file

@ -47,6 +47,8 @@ examples/*/*.dSYM
examples/*.o.tmp
examples/*.out.js
examples/*.out.wasm
examples/example_emscripten_opengl3/web/*
examples/example_emscripten_wgpu/web/*
examples/example_glfw_opengl3/web/*
examples/example_glfw_wgpu/web/*
examples/example_glfw_wgpu/external/*

View file

@ -0,0 +1,857 @@
// dear imgui: Platform Backend for Emscripten HTML5
//
// See documentation in imgui_impl_emscripten.h.
//
// CHANGELOG
// (minor and older changes stripped away, please see git history for details)
// 2026-09-06: DPI: Added public ImGui_ImplEmscripten_UpdateDisplayProperties() wrapper.
// 2026-09-06: Inputs: Exposed mouse button and key translation functions as public.
// 2026-09-06: DPI: Added ImGui_ImplEmscripten_GetCssToImGuiScale() getter.
// 2026-09-05: DPI: Use the canvas framebuffer dimensions for display sizing and exact per-axis mouse coordinate scaling.
// 2026-09-04: Replaced TargetDevicePixelRatio global with an Init() parameter and runtime setter.
// 2026-09-03: Updated coding style and simplified key translation table setup.
// 2026-04-02: Inputs: Replaced custom KeyboardEvent.code parser with ImHashStr()/ImGuiStorage lookup to match Dear ImGui backend style.
// 2026-03-31: Added configurable TargetDevicePixelRatio to control how browser device pixels map to Dear ImGui pixels.
// 2026-03-31: Inputs: Added BrowserBack/Forward and F13-F24 key mappings.
// 2026-03-31: Moved cursor state into backend userdata and replaced cursor restore storage with owned C strings.
// 2024-12-09: Inputs: Added special handling for modifier keys to also generate modifier key events.
// 2024-12-08: Inputs: Prevent "Delete" key from getting printed in text input.
// 2024-12-06: Inputs: Added special handling for Tab and Enter event capture.
// 2024-12-06: Inputs: Handle blur and focus events correctly, focusin and focusout aren't enough.
// 2024-12-06: Don't rely on devicePixelRatio for WebGPU framebuffer sizing; CSS->ImGui scaling may still use it.
// 2024-11-22: Initial version by Eugene Hopkinson. (#8178)
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_impl_emscripten.h"
#include <emscripten.h>
#include <emscripten/html5.h>
extern ImGuiID ImHashStr(char const* data, size_t data_size = 0, ImGuiID seed = 0); // Declared in imgui_internal.h.
// Browser cursor helpers, adapted from https://github.com/Armchair-Software/emscripten-browser-cursor
enum ImGui_ImplEmscripten_Cursor
{
// General
ImGui_ImplEmscripten_Cursor_Auto, // The UA will determine the cursor to display based on the current context. E.g., equivalent to text when hovering text.
ImGui_ImplEmscripten_Cursor_Default, // The platform-dependent default cursor. Typically an arrow.
ImGui_ImplEmscripten_Cursor_None, // No cursor is rendered.
// Links & status
ImGui_ImplEmscripten_Cursor_ContextMenu, // cursor slightly obscuring a menu icon - A context menu is available.
ImGui_ImplEmscripten_Cursor_Help, // cursor next to a question mark - Help information is available.
ImGui_ImplEmscripten_Cursor_Pointer, // right hand with an index finger pointing up - The cursor is a pointer that indicates a link. Typically an image of a pointing hand.
ImGui_ImplEmscripten_Cursor_Progress, // cursor and hour glass - The program is busy in the background, but the user can still interact with the interface (in contrast to wait).
ImGui_ImplEmscripten_Cursor_Wait, // hour glass - The program is busy, and the user can't interact with the interface (in contrast to progress). Sometimes an image of an hourglass or a watch.
// Selection
ImGui_ImplEmscripten_Cursor_Cell, // plus symbol - The table cell or set of cells can be selected.
ImGui_ImplEmscripten_Cursor_Crosshair, // crosshair - Cross cursor, often used to indicate selection in a bitmap.
ImGui_ImplEmscripten_Cursor_Text, // vertical i-beam - The text can be selected. Typically the shape of an I-beam.
ImGui_ImplEmscripten_Cursor_VerticalText, // horizontal i-beam - The vertical text can be selected. Typically the shape of a sideways I-beam.
// Drag & drop
ImGui_ImplEmscripten_Cursor_Alias, // cursor next to a folder icon with a curved arrow pointing up and to the right - An alias or shortcut is to be created.
ImGui_ImplEmscripten_Cursor_Copy, // cursor next to a smaller folder icon with a plus sign - Something is to be copied.
ImGui_ImplEmscripten_Cursor_Move, // plus sign made of two thin lines, with small arrows facing out - Something is to be moved.
ImGui_ImplEmscripten_Cursor_NoDrop, // cursor next to circle with a line through it - An item may not be dropped at the current location.
ImGui_ImplEmscripten_Cursor_NotAllowed, // circle with a line through it - The requested action will not be carried out.
ImGui_ImplEmscripten_Cursor_Grab, // fully opened hand - Something can be grabbed (dragged to be moved).
ImGui_ImplEmscripten_Cursor_Grabbing, // closed hand - Something is being grabbed (dragged to be moved).
// Resizing & scrolling
ImGui_ImplEmscripten_Cursor_AllScroll, // dot with four triangles around it - Something can be scrolled in any direction (panned).
ImGui_ImplEmscripten_Cursor_ColResize, // The item/column can be resized horizontally. Often rendered as arrows pointing left and right with a vertical bar separating them.
ImGui_ImplEmscripten_Cursor_RowResize, // The item/row can be resized vertically. Often rendered as arrows pointing up and down with a horizontal bar separating them.
ImGui_ImplEmscripten_Cursor_NResize, // arrow pointing up - Some edge is to be moved. For example, the se-resize cursor is used when the movement starts from the south-east corner of the box.
ImGui_ImplEmscripten_Cursor_EResize, // arrow pointing right
ImGui_ImplEmscripten_Cursor_SResize, // arrow pointing down
ImGui_ImplEmscripten_Cursor_WResize, // arrow pointing left
ImGui_ImplEmscripten_Cursor_NEResize, // arrow pointing top-right
ImGui_ImplEmscripten_Cursor_NWResize, // arrow pointing top-left
ImGui_ImplEmscripten_Cursor_SEResize, // arrow pointing bottom-right
ImGui_ImplEmscripten_Cursor_SWResize, // arrow pointing bottom-left
ImGui_ImplEmscripten_Cursor_EWResize, // arrow pointing left and right - Bidirectional resize cursor.
ImGui_ImplEmscripten_Cursor_NSResize, // arrow pointing up and down
ImGui_ImplEmscripten_Cursor_NESWResize, // arrow pointing both to the top-right and bottom-left
ImGui_ImplEmscripten_Cursor_NWSEResize, // arrow pointing both to the top-left and bottom-right
// Zooming
ImGui_ImplEmscripten_Cursor_ZoomIn, // magnifying glass with a plus sign - Something can be zoomed (magnified) in or out.
ImGui_ImplEmscripten_Cursor_ZoomOut,
// Special invalid value
ImGui_ImplEmscripten_Cursor_Invalid
};
static void ImGui_ImplEmscripten_SetBrowserCursor(ImGui_ImplEmscripten_Cursor new_cursor); // set a new cursor from a cursor enum
static char* ImGui_ImplEmscripten_GetBrowserCursor(); // read the current cursor setting as an owned string, caller must free()
static void ImGui_ImplEmscripten_SetBrowserCursor(char const* new_cursor); // set the cursor from an arbitrary string
struct ImGui_ImplEmscripten_Data
{
float TargetDevicePixelRatio = 1.0f;
ImVec2 CssToImGuiScale = ImVec2(1.0f, 1.0f);
ImVec2 CanvasFramebufferSize = ImVec2(0.0f, 0.0f);
ImGui_ImplEmscripten_Cursor CurrentCursor = ImGui_ImplEmscripten_Cursor_Invalid;
char* CursorToRestore = nullptr;
bool LastMouseDrawCursor = false;
bool LastNoMouseCursorChange = false;
};
static ImVec2 ImGui_ImplEmscripten_GetCanvasFramebufferSize()
{
return ImVec2(
(float)EM_ASM_INT({ return Module["canvas"] ? Module["canvas"].width : 0; }),
(float)EM_ASM_INT({ return Module["canvas"] ? Module["canvas"].height : 0; }));
}
static void ImGui_ImplEmscripten_UpdateDisplayProperties(ImGuiIO& io, ImGui_ImplEmscripten_Data* bd)
{
double const css_width = EM_ASM_DOUBLE({ return Module["canvas"] ? Module["canvas"].getBoundingClientRect().width : window.innerWidth; });
double const css_height = EM_ASM_DOUBLE({ return Module["canvas"] ? Module["canvas"].getBoundingClientRect().height : window.innerHeight; });
float const target_device_pixel_ratio = bd->TargetDevicePixelRatio;
ImVec2 framebuffer_size = ImGui_ImplEmscripten_GetCanvasFramebufferSize();
if (framebuffer_size.x <= 0.0f || framebuffer_size.y <= 0.0f)
{
double const device_pixel_ratio = emscripten_get_device_pixel_ratio();
framebuffer_size = ImVec2((float)(int)(css_width * device_pixel_ratio + 0.5), (float)(int)(css_height * device_pixel_ratio + 0.5));
}
bd->CanvasFramebufferSize = framebuffer_size;
io.DisplaySize.x = framebuffer_size.x / target_device_pixel_ratio;
io.DisplaySize.y = framebuffer_size.y / target_device_pixel_ratio;
io.DisplayFramebufferScale = ImVec2(target_device_pixel_ratio, target_device_pixel_ratio);
bd->CssToImGuiScale.x = css_width > 0.0 ? (float)((double)io.DisplaySize.x / css_width) : 1.0f;
bd->CssToImGuiScale.y = css_height > 0.0 ? (float)((double)io.DisplaySize.y / css_height) : 1.0f;
}
// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts
static ImGui_ImplEmscripten_Data* ImGui_ImplEmscripten_GetBackendData()
{
return ImGui::GetCurrentContext() ? (ImGui_ImplEmscripten_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
}
bool ImGui_ImplEmscripten_Init(float target_device_pixel_ratio)
{
// Initialise the Emscripten backend, setting input callbacks
ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
IM_ASSERT(target_device_pixel_ratio > 0.0f && "Target device pixel ratio must be positive.");
if (target_device_pixel_ratio <= 0.0f) return false;
ImGui_ImplEmscripten_Data* bd = IM_NEW(ImGui_ImplEmscripten_Data)();
bd->TargetDevicePixelRatio = target_device_pixel_ratio;
io.BackendPlatformUserData = (void*)bd;
io.BackendPlatformName = "imgui_impl_emscripten";
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;
// Set up initial display size values
ImGui_ImplEmscripten_UpdateDisplayProperties(io, bd);
emscripten_set_mousemove_callback(
EMSCRIPTEN_EVENT_TARGET_WINDOW, // target
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_MOUSEMOVE
{
ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData();
ImVec2 const css_to_imgui_scale = bd ? bd->CssToImGuiScale : ImVec2(1.0f, 1.0f);
ImGui::GetIO().AddMousePosEvent(
(float)mouse_event->clientX * css_to_imgui_scale.x,
(float)mouse_event->clientY * css_to_imgui_scale.y
);
return true; // the event was consumed
}
);
emscripten_set_mousedown_callback(
EMSCRIPTEN_EVENT_TARGET_WINDOW, // target
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_MOUSEDOWN
{
ImGui::GetIO().AddMouseButtonEvent(ImGui_ImplEmscripten_TranslateMouseButton(mouse_event->button), true); // translated button, down
return true; // the event was consumed
}
);
emscripten_set_mouseup_callback(
EMSCRIPTEN_EVENT_TARGET_WINDOW, // target
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_MOUSEUP
{
ImGui::GetIO().AddMouseButtonEvent(ImGui_ImplEmscripten_TranslateMouseButton(mouse_event->button), false); // translated button, up
return true; // the event was consumed
}
);
emscripten_set_mouseenter_callback(
EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenMouseEvent const* mouse_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_MOUSEENTER
{
ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData();
ImVec2 const css_to_imgui_scale = bd ? bd->CssToImGuiScale : ImVec2(1.0f, 1.0f);
ImGui::GetIO().AddMousePosEvent(
(float)mouse_event->clientX * css_to_imgui_scale.x,
(float)mouse_event->clientY * css_to_imgui_scale.y
);
return true; // the event was consumed
}
);
emscripten_set_mouseleave_callback(
EMSCRIPTEN_EVENT_TARGET_DOCUMENT, // target - WINDOW doesn't produce mouseenter events
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenMouseEvent const* /*mouse_event*/, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_MOUSELEAVE
{
ImGuiIO& io = ImGui::GetIO();
io.AddMousePosEvent(-FLT_MAX, -FLT_MAX); // cursor is not in the window
io.ClearInputKeys(); // clear pending input keys on mouse exit
return true; // the event was consumed
}
);
emscripten_set_wheel_callback(
EMSCRIPTEN_EVENT_TARGET_WINDOW, // target
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenWheelEvent const* wheel_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_WHEEL
{
float scale = 1.0f;
switch (wheel_event->deltaMode)
{
case DOM_DELTA_PIXEL: // scrolling in pixels
scale = 1.0f / 100.0f;
break;
case DOM_DELTA_LINE: // scrolling by lines
scale = 1.0f / 3.0f;
break;
case DOM_DELTA_PAGE: // scrolling by pages
scale = 80.0f;
break;
}
// TODO: Make scrolling speeds configurable
ImGuiIO& io = ImGui::GetIO();
io.AddMouseWheelEvent(
-(float)wheel_event->deltaX * scale,
-(float)wheel_event->deltaY * scale
);
return io.WantCaptureMouse; // consume the event when imgui wants to capture mouse input
}
);
emscripten_set_keydown_callback(
EMSCRIPTEN_EVENT_TARGET_WINDOW, // target
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_KEYDOWN
{
const ImGuiKey key = ImGui_ImplEmscripten_TranslateKey(key_event->code);
ImGuiIO& io = ImGui::GetIO();
io.AddKeyEvent(key, true);
switch (key) // special cases for certain key events
{
case ImGuiKey_LeftCtrl: // additional events for modifier keys
case ImGuiKey_RightCtrl:
io.AddKeyEvent(ImGuiMod_Ctrl, true);
break;
case ImGuiKey_LeftShift:
case ImGuiKey_RightShift:
io.AddKeyEvent(ImGuiMod_Shift, true);
break;
case ImGuiKey_LeftAlt:
case ImGuiKey_RightAlt:
io.AddKeyEvent(ImGuiMod_Alt, true);
break;
case ImGuiKey_LeftSuper:
case ImGuiKey_RightSuper:
io.AddKeyEvent(ImGuiMod_Super, true);
break;
// TODO: case ImGuiKey_Menu: Do we want to do anything with this?
case ImGuiKey_Tab: // consuming tab prevents the user tabbing to other parts of the browser interface outside the window content
return io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard
case ImGuiKey_Enter: // consuming enter prevents the word "Enter" appearing in text input via the keypress callback
case ImGuiKey_Delete: // consuming enter prevents the word "Delete" appearing in text input via the keypress callback
return io.WantTextInput; // the event was consumed only if we're currently accepting text input
default:
break;
}
return false; // if no special handling, the event was not consumed
}
);
emscripten_set_keyup_callback(
EMSCRIPTEN_EVENT_TARGET_WINDOW, // target
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_KEYUP
{
const ImGuiKey key = ImGui_ImplEmscripten_TranslateKey(key_event->code);
ImGuiIO& io = ImGui::GetIO();
io.AddKeyEvent(key, false);
switch (key) // special cases for certain key events
{
case ImGuiKey_LeftCtrl: // additional events for modifier keys
case ImGuiKey_RightCtrl:
io.AddKeyEvent(ImGuiMod_Ctrl, false);
break;
case ImGuiKey_LeftShift:
case ImGuiKey_RightShift:
io.AddKeyEvent(ImGuiMod_Shift, false);
break;
case ImGuiKey_LeftAlt:
case ImGuiKey_RightAlt:
io.AddKeyEvent(ImGuiMod_Alt, false);
break;
case ImGuiKey_LeftSuper:
case ImGuiKey_RightSuper:
io.AddKeyEvent(ImGuiMod_Super, false);
break;
default:
break;
}
return false; // the event was not consumed
}
);
emscripten_set_keypress_callback(
EMSCRIPTEN_EVENT_TARGET_WINDOW, // target
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenKeyboardEvent const* key_event, void* /*data*/) // callback, event_type == EMSCRIPTEN_EVENT_KEYPRESS
{
ImGuiIO& io = ImGui::GetIO();
io.AddInputCharactersUTF8(key_event->key);
return io.WantCaptureKeyboard; // the event was consumed only if imgui wants to capture the keyboard
}
);
emscripten_set_resize_callback(
EMSCRIPTEN_EVENT_TARGET_WINDOW, // target
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenUiEvent const* /*event*/, void* /*data*/) // event_type == EMSCRIPTEN_EVENT_RESIZE
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData();
if (bd != nullptr) ImGui_ImplEmscripten_UpdateDisplayProperties(io, bd);
return true; // the event was consumed
}
);
emscripten_set_blur_callback(
EMSCRIPTEN_EVENT_TARGET_WINDOW, // target
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) // event_type == EMSCRIPTEN_EVENT_BLUR
{
ImGuiIO& io = ImGui::GetIO();
io.AddFocusEvent(false);
io.ClearInputKeys(); // clear pending input keys on focus loss
return true; // the event was consumed
}
);
emscripten_set_focus_callback(
EMSCRIPTEN_EVENT_TARGET_WINDOW, // target
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) // event_type == EMSCRIPTEN_EVENT_FOCUS
{
ImGuiIO& io = ImGui::GetIO();
io.AddFocusEvent(true);
io.ClearInputKeys(); // clear pending input keys on focus gain - for example if you press tab to cycle back into the browser window
return true; // the event was consumed
}
);
emscripten_set_focusin_callback(
EMSCRIPTEN_EVENT_TARGET_WINDOW, // target
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) // event_type == EMSCRIPTEN_EVENT_FOCUSIN
{
ImGuiIO& io = ImGui::GetIO();
io.AddFocusEvent(true);
io.ClearInputKeys(); // clear pending input keys on focus gain
return true; // the event was consumed
}
);
emscripten_set_focusout_callback(
EMSCRIPTEN_EVENT_TARGET_WINDOW, // target
nullptr, // userData
false, // useCapture
[](int /*event_type*/, EmscriptenFocusEvent const* /*event*/, void* /*data*/) // event_type == EMSCRIPTEN_EVENT_FOCUSOUT
{
ImGuiIO& io = ImGui::GetIO();
io.AddFocusEvent(false);
io.ClearInputKeys(); // clear pending input keys on focus loss - for example if you press tab to cycle to another part of the UI
return true; // the event was consumed
}
);
// TODO: Touch events
return true;
}
void ImGui_ImplEmscripten_SetTargetDevicePixelRatio(float target_device_pixel_ratio)
{
ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized? Did you call ImGui_ImplEmscripten_Init()?");
IM_ASSERT(target_device_pixel_ratio > 0.0f && "Target device pixel ratio must be positive.");
if (bd == nullptr || target_device_pixel_ratio <= 0.0f || target_device_pixel_ratio == bd->TargetDevicePixelRatio) return;
bd->TargetDevicePixelRatio = target_device_pixel_ratio;
ImGui_ImplEmscripten_UpdateDisplayProperties(ImGui::GetIO(), bd);
}
ImVec2 ImGui_ImplEmscripten_GetCssToImGuiScale()
{
ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized? Did you call ImGui_ImplEmscripten_Init()?");
return bd != nullptr ? bd->CssToImGuiScale : ImVec2(1.0f, 1.0f);
}
void ImGui_ImplEmscripten_UpdateDisplayProperties()
{
ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized? Did you call ImGui_ImplEmscripten_Init()?");
if (bd != nullptr) ImGui_ImplEmscripten_UpdateDisplayProperties(ImGui::GetIO(), bd);
}
void ImGui_ImplEmscripten_Shutdown()
{
ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData();
IM_ASSERT(bd != nullptr && "No platform backend to shutdown, or already shutdown?");
// Unset any callbacks set by Init
emscripten_set_mousemove_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr);
emscripten_set_mousedown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr);
emscripten_set_mouseup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr);
emscripten_set_mouseenter_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, nullptr);
emscripten_set_mouseleave_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, nullptr, false, nullptr);
emscripten_set_wheel_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr);
emscripten_set_keydown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr);
emscripten_set_keyup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr);
emscripten_set_keypress_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr);
emscripten_set_resize_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr);
emscripten_set_blur_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr);
emscripten_set_focus_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr);
emscripten_set_focusin_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr);
emscripten_set_focusout_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, false, nullptr);
// TODO: Touch events
if (bd->CursorToRestore != nullptr)
{
ImGui_ImplEmscripten_SetBrowserCursor(bd->CursorToRestore); // restore the previous cursor state if imgui still owns the cursor on shutdown
free(bd->CursorToRestore);
}
ImGuiIO& io = ImGui::GetIO();
io.BackendPlatformName = nullptr;
io.BackendPlatformUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_HasMouseCursors;
IM_DELETE(bd);
}
static void ImGui_ImplEmscripten_RestoreMouseCursor(ImGui_ImplEmscripten_Data* bd)
{
if (bd->CursorToRestore == nullptr) return;
ImGui_ImplEmscripten_SetBrowserCursor(bd->CursorToRestore); // restore the previous cursor state when leaving imgui cursor ownership
free(bd->CursorToRestore);
bd->CursorToRestore = nullptr;
bd->CurrentCursor = ImGui_ImplEmscripten_Cursor_Invalid; // select an unused value for current cursor to force a set next time
}
static void ImGui_ImplEmscripten_SetMouseCursor(ImGui_ImplEmscripten_Data* bd, ImGui_ImplEmscripten_Cursor new_cursor)
{
if (new_cursor == bd->CurrentCursor) return; // don't do anything if the current cursor is already set
if (bd->CursorToRestore == nullptr) bd->CursorToRestore = ImGui_ImplEmscripten_GetBrowserCursor(); // back up the existing cursor state when first taking cursor ownership
bd->CurrentCursor = new_cursor;
ImGui_ImplEmscripten_SetBrowserCursor(new_cursor);
}
static void ImGui_ImplEmscripten_UpdateMouseCursor(ImGui_ImplEmscripten_Data* bd)
{
// Sync any cursor changes due to ImGui to the browser's cursor
ImGuiIO& io = ImGui::GetIO();
if (io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)
{
if (!bd->LastNoMouseCursorChange)
{
free(bd->CursorToRestore);
bd->CursorToRestore = nullptr;
bd->CurrentCursor = ImGui_ImplEmscripten_Cursor_Invalid;
bd->LastMouseDrawCursor = false;
bd->LastNoMouseCursorChange = true;
}
return;
}
bd->LastNoMouseCursorChange = false;
if (io.MouseDrawCursor)
{
if (bd->LastMouseDrawCursor) return;
ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_None); // hide the cursor for the entire window if imgui is handling cursor drawing - not just when imgui wants to capture the mouse
bd->LastMouseDrawCursor = true;
}
else
{
if (bd->LastMouseDrawCursor)
{
ImGui_ImplEmscripten_RestoreMouseCursor(bd);
bd->LastMouseDrawCursor = false;
}
}
if (io.WantCaptureMouse) // mouse is hovering over the gui
{
switch (ImGui::GetMouseCursor())
{
case ImGuiMouseCursor_None:
ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_None);
break;
case ImGuiMouseCursor_Arrow:
ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_Default);
break;
case ImGuiMouseCursor_TextInput: // When hovering over InputText, etc.
ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_Text);
break;
case ImGuiMouseCursor_ResizeAll: // (Unused by Dear ImGui functions)
ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_Move);
break;
case ImGuiMouseCursor_ResizeNS: // When hovering over a horizontal border
ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_NSResize);
break;
case ImGuiMouseCursor_ResizeEW: // When hovering over a vertical border or a column
ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_EWResize);
break;
case ImGuiMouseCursor_ResizeNESW: // When hovering over the bottom-left corner of a window
ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_NESWResize);
break;
case ImGuiMouseCursor_ResizeNWSE: // When hovering over the bottom-right corner of a window
ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_NWSEResize);
break;
case ImGuiMouseCursor_Hand: // (Unused by Dear ImGui functions. Use for e.g. hyperlinks)
ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_Pointer);
break;
case ImGuiMouseCursor_NotAllowed: // When hovering something with disallowed interaction. Usually a crossed circle.
ImGui_ImplEmscripten_SetMouseCursor(bd, ImGui_ImplEmscripten_Cursor_NotAllowed);
break;
}
}
else // mouse is away from the gui, hovering over some other part of the viewport
{
ImGui_ImplEmscripten_RestoreMouseCursor(bd);
}
}
void ImGui_ImplEmscripten_NewFrame()
{
ImGui_ImplEmscripten_Data* bd = ImGui_ImplEmscripten_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized? Did you call ImGui_ImplEmscripten_Init()?");
// Update any state that needs to be polled
ImVec2 const canvas_framebuffer_size = ImGui_ImplEmscripten_GetCanvasFramebufferSize();
if (canvas_framebuffer_size.x != bd->CanvasFramebufferSize.x || canvas_framebuffer_size.y != bd->CanvasFramebufferSize.y)
ImGui_ImplEmscripten_UpdateDisplayProperties(ImGui::GetIO(), bd);
ImGui_ImplEmscripten_UpdateMouseCursor(bd);
}
static char* ImGui_ImplEmscripten_GetBrowserCursor()
{
// Return the current cursor setting as a newly-allocated string, caller must free it.
return (char*)EM_ASM_PTR(
return stringToNewUTF8(document.body.style.cursor);
);
}
static void ImGui_ImplEmscripten_SetBrowserCursor(ImGui_ImplEmscripten_Cursor new_cursor)
{
// Set the cursor according to the given enum
// Note, implementations omitted for cursors not used by imgui. For full implementation, use https://github.com/Armchair-Software/emscripten-browser-cursor
switch (new_cursor)
{
case ImGui_ImplEmscripten_Cursor_None:
EM_ASM(document.body.style.cursor = 'none';);
break;
case ImGui_ImplEmscripten_Cursor_Default:
default:
EM_ASM(document.body.style.cursor = 'default';);
break;
case ImGui_ImplEmscripten_Cursor_Pointer:
EM_ASM(document.body.style.cursor = 'pointer';);
break;
case ImGui_ImplEmscripten_Cursor_Text:
EM_ASM(document.body.style.cursor = 'text';);
break;
case ImGui_ImplEmscripten_Cursor_Move:
EM_ASM(document.body.style.cursor = 'move';);
break;
case ImGui_ImplEmscripten_Cursor_NotAllowed:
EM_ASM(document.body.style.cursor = 'not-allowed';);
break;
case ImGui_ImplEmscripten_Cursor_EWResize:
EM_ASM(document.body.style.cursor = 'ew-resize';);
break;
case ImGui_ImplEmscripten_Cursor_NSResize:
EM_ASM(document.body.style.cursor = 'ns-resize';);
break;
case ImGui_ImplEmscripten_Cursor_NESWResize:
EM_ASM(document.body.style.cursor = 'nesw-resize';);
break;
case ImGui_ImplEmscripten_Cursor_NWSEResize:
EM_ASM(document.body.style.cursor = 'nwse-resize';);
break;
}
}
static void ImGui_ImplEmscripten_SetBrowserCursor(char const* new_cursor)
{
// Set the cursor from an arbitrary string
EM_ASM({
document.body.style.cursor = UTF8ToString($0);
}, new_cursor);
}
ImGuiMouseButton ImGui_ImplEmscripten_TranslateMouseButton(unsigned short emscripten_button)
{
// Translate an emscripten-provided integer describing a mouse button to an imgui mouse button
if (emscripten_button == 1) return ImGuiMouseButton_Middle; // 1 = middle mouse button
if (emscripten_button == 2) return ImGuiMouseButton_Right; // 2 = right mouse button
if (emscripten_button >= ImGuiMouseButton_COUNT) return ImGuiMouseButton_Middle; // treat any weird clicks on unexpected buttons (button 6 upwards) as middle mouse
return emscripten_button; // any other button translates 1:1
}
static ImGuiStorage const& ImGui_ImplEmscripten_GetKeyTranslationStorage()
{
struct ImGui_ImplEmscripten_KeyTranslation
{
const char* EmscriptenKey;
ImGuiKey Key;
};
static const ImGui_ImplEmscripten_KeyTranslation key_translations[] =
{
// Main character keys
{ "Backquote", ImGuiKey_GraveAccent },
{ "Backslash", ImGuiKey_Backslash },
{ "BracketLeft", ImGuiKey_LeftBracket },
{ "BracketRight", ImGuiKey_RightBracket },
{ "Comma", ImGuiKey_Comma },
{ "Digit0", ImGuiKey_0 },
{ "Digit1", ImGuiKey_1 },
{ "Digit2", ImGuiKey_2 },
{ "Digit3", ImGuiKey_3 },
{ "Digit4", ImGuiKey_4 },
{ "Digit5", ImGuiKey_5 },
{ "Digit6", ImGuiKey_6 },
{ "Digit7", ImGuiKey_7 },
{ "Digit8", ImGuiKey_8 },
{ "Digit9", ImGuiKey_9 },
{ "Equal", ImGuiKey_Equal },
{ "IntlBackslash", ImGuiKey_Backslash }, // Mapping to generic backslash
{ "IntlRo", ImGuiKey_Slash }, // Closest match for non-standard layouts
{ "IntlYen", ImGuiKey_Backslash }, // Closest match for non-standard layouts
{ "KeyA", ImGuiKey_A },
{ "KeyB", ImGuiKey_B },
{ "KeyC", ImGuiKey_C },
{ "KeyD", ImGuiKey_D },
{ "KeyE", ImGuiKey_E },
{ "KeyF", ImGuiKey_F },
{ "KeyG", ImGuiKey_G },
{ "KeyH", ImGuiKey_H },
{ "KeyI", ImGuiKey_I },
{ "KeyJ", ImGuiKey_J },
{ "KeyK", ImGuiKey_K },
{ "KeyL", ImGuiKey_L },
{ "KeyM", ImGuiKey_M },
{ "KeyN", ImGuiKey_N },
{ "KeyO", ImGuiKey_O },
{ "KeyP", ImGuiKey_P },
{ "KeyQ", ImGuiKey_Q },
{ "KeyR", ImGuiKey_R },
{ "KeyS", ImGuiKey_S },
{ "KeyT", ImGuiKey_T },
{ "KeyU", ImGuiKey_U },
{ "KeyV", ImGuiKey_V },
{ "KeyW", ImGuiKey_W },
{ "KeyX", ImGuiKey_X },
{ "KeyY", ImGuiKey_Y },
{ "KeyZ", ImGuiKey_Z },
{ "Minus", ImGuiKey_Minus },
{ "Period", ImGuiKey_Period },
{ "Quote", ImGuiKey_Apostrophe },
{ "Semicolon", ImGuiKey_Semicolon },
{ "Slash", ImGuiKey_Slash },
// Control keys
{ "AltLeft", ImGuiKey_LeftAlt },
{ "AltRight", ImGuiKey_RightAlt },
{ "Backspace", ImGuiKey_Backspace },
{ "CapsLock", ImGuiKey_CapsLock },
{ "ContextMenu", ImGuiKey_Menu },
{ "ControlLeft", ImGuiKey_LeftCtrl },
{ "ControlRight", ImGuiKey_RightCtrl },
{ "Enter", ImGuiKey_Enter },
{ "MetaLeft", ImGuiKey_LeftSuper },
{ "MetaRight", ImGuiKey_RightSuper },
{ "ShiftLeft", ImGuiKey_LeftShift },
{ "ShiftRight", ImGuiKey_RightShift },
{ "Space", ImGuiKey_Space },
{ "Tab", ImGuiKey_Tab },
// Navigation key group
{ "Delete", ImGuiKey_Delete },
{ "End", ImGuiKey_End },
//{ "Help", ImGuiKey_PrintScreen }, // Best approximation
{ "Home", ImGuiKey_Home },
{ "Insert", ImGuiKey_Insert },
{ "PageDown", ImGuiKey_PageDown },
{ "PageUp", ImGuiKey_PageUp },
// Arrow key group
{ "ArrowDown", ImGuiKey_DownArrow },
{ "ArrowLeft", ImGuiKey_LeftArrow },
{ "ArrowRight", ImGuiKey_RightArrow },
{ "ArrowUp", ImGuiKey_UpArrow },
// Browser key group
{ "BrowserBack", ImGuiKey_AppBack }, // Pass through so the embedding app can decide
//{ "BrowserFavorites", ImGuiKey_None }, // No direct mapping
{ "BrowserForward", ImGuiKey_AppForward }, // Pass through so the embedding app can decide
//{ "BrowserHome", ImGuiKey_None }, // No direct mapping
//{ "BrowserRefresh", ImGuiKey_None }, // No direct mapping
//{ "BrowserSearch", ImGuiKey_None }, // No direct mapping
//{ "BrowserStop", ImGuiKey_None }, // No direct mapping
// Number pad group
{ "NumLock", ImGuiKey_NumLock },
{ "Numpad0", ImGuiKey_Keypad0 },
{ "Numpad1", ImGuiKey_Keypad1 },
{ "Numpad2", ImGuiKey_Keypad2 },
{ "Numpad3", ImGuiKey_Keypad3 },
{ "Numpad4", ImGuiKey_Keypad4 },
{ "Numpad5", ImGuiKey_Keypad5 },
{ "Numpad6", ImGuiKey_Keypad6 },
{ "Numpad7", ImGuiKey_Keypad7 },
{ "Numpad8", ImGuiKey_Keypad8 },
{ "Numpad9", ImGuiKey_Keypad9 },
{ "NumpadAdd", ImGuiKey_KeypadAdd },
{ "NumpadBackspace", ImGuiKey_Backspace }, // No direct mapping; backspace functionality
//{ "NumpadClear", ImGuiKey_None }, // No defined Dear ImGui mapping
//{ "NumpadClearEntry", ImGuiKey_None }, // No defined Dear ImGui mapping
{ "NumpadComma", ImGuiKey_KeypadDecimal }, // Closest match
{ "NumpadDecimal", ImGuiKey_KeypadDecimal },
{ "NumpadDivide", ImGuiKey_KeypadDivide },
{ "NumpadEnter", ImGuiKey_KeypadEnter },
{ "NumpadEqual", ImGuiKey_KeypadEqual },
{ "NumpadHash", ImGuiKey_Backslash }, // Mapped to generic backslash for telephone-style '#'
//{ "NumpadMemoryAdd", ImGuiKey_None }, // No defined mapping
//{ "NumpadMemoryClear", ImGuiKey_None }, // No defined mapping
//{ "NumpadMemoryRecall", ImGuiKey_None }, // No defined mapping
//{ "NumpadMemoryStore", ImGuiKey_None }, // No defined mapping
//{ "NumpadMemorySubtract", ImGuiKey_None }, // No defined mapping
{ "NumpadMultiply", ImGuiKey_KeypadMultiply },
{ "NumpadParenLeft", ImGuiKey_LeftBracket }, // Closest available
{ "NumpadParenRight", ImGuiKey_RightBracket }, // Closest available
{ "NumpadStar", ImGuiKey_KeypadMultiply }, // Same as multiply
{ "NumpadSubtract", ImGuiKey_KeypadSubtract },
// Top row key group
{ "Escape", ImGuiKey_Escape },
{ "F1", ImGuiKey_F1 },
{ "F2", ImGuiKey_F2 },
{ "F3", ImGuiKey_F3 },
{ "F4", ImGuiKey_F4 },
{ "F5", ImGuiKey_F5 },
{ "F6", ImGuiKey_F6 },
{ "F7", ImGuiKey_F7 },
{ "F8", ImGuiKey_F8 },
{ "F9", ImGuiKey_F9 },
{ "F10", ImGuiKey_F10 },
{ "F11", ImGuiKey_F11 },
{ "F12", ImGuiKey_F12 },
{ "F13", ImGuiKey_F13 },
{ "F14", ImGuiKey_F14 },
{ "F15", ImGuiKey_F15 },
{ "F16", ImGuiKey_F16 },
{ "F17", ImGuiKey_F17 },
{ "F18", ImGuiKey_F18 },
{ "F19", ImGuiKey_F19 },
{ "F20", ImGuiKey_F20 },
{ "F21", ImGuiKey_F21 },
{ "F22", ImGuiKey_F22 },
{ "F23", ImGuiKey_F23 },
{ "F24", ImGuiKey_F24 },
//{ "Fn", ImGuiKey_None }, // No direct mapping
//{ "FnLock", ImGuiKey_None }, // No direct mapping
{ "PrintScreen", ImGuiKey_PrintScreen },
{ "ScrollLock", ImGuiKey_ScrollLock },
{ "Pause", ImGuiKey_Pause },
// Clipboard/editing keys without direct mapping
//{ "Abort", ImGuiKey_None },
//{ "Again", ImGuiKey_None },
//{ "Convert", ImGuiKey_None },
//{ "Copy", ImGuiKey_None },
//{ "Cut", ImGuiKey_None },
//{ "Find", ImGuiKey_None },
//{ "Open", ImGuiKey_None },
//{ "Paste", ImGuiKey_None },
//{ "Props", ImGuiKey_None },
//{ "Resume", ImGuiKey_None },
//{ "Select", ImGuiKey_None },
//{ "Undo", ImGuiKey_None },
// IME and international keys without direct mapping
//{ "Hiragana", ImGuiKey_None },
//{ "KanaMode", ImGuiKey_None },
//{ "Katakana", ImGuiKey_None },
//{ "Lang1", ImGuiKey_None },
//{ "Lang2", ImGuiKey_None },
//{ "NonConvert", ImGuiKey_None },
// Media and launcher keys without direct mapping
//{ "AudioVolumeDown", ImGuiKey_None },
//{ "AudioVolumeMute", ImGuiKey_None },
//{ "AudioVolumeUp", ImGuiKey_None },
//{ "LaunchApp1", ImGuiKey_None },
//{ "LaunchApp2", ImGuiKey_None },
//{ "LaunchMail", ImGuiKey_None },
//{ "MediaPlayPause", ImGuiKey_None },
//{ "MediaSelect", ImGuiKey_None },
//{ "MediaStop", ImGuiKey_None },
//{ "MediaTrackNext", ImGuiKey_None },
//{ "MediaTrackPrevious", ImGuiKey_None },
// System keys without direct mapping
//{ "Eject", ImGuiKey_None },
//{ "Hyper", ImGuiKey_None },
//{ "Power", ImGuiKey_None },
//{ "Sleep", ImGuiKey_None },
//{ "Super", ImGuiKey_None },
//{ "Suspend", ImGuiKey_None },
//{ "Turbo", ImGuiKey_None },
//{ "Unidentified", ImGuiKey_None },
//{ "WakeUp", ImGuiKey_None },
};
static ImGuiStorage storage;
static bool is_initialized = false;
if (is_initialized) return storage;
is_initialized = true;
storage.Data.reserve(IM_ARRAYSIZE(key_translations));
for (int n = 0; n != IM_ARRAYSIZE(key_translations); ++n)
{
storage.Data.push_back(ImGuiStoragePair(ImHashStr(key_translations[n].EmscriptenKey), key_translations[n].Key));
}
storage.BuildSortByKey();
return storage;
}
ImGuiKey ImGui_ImplEmscripten_TranslateKey(const char* emscripten_key)
{
// Translate a W3C KeyboardEvent.code string into an ImGuiKey.
if (emscripten_key == nullptr || emscripten_key[0] == '\0') return ImGuiKey_None;
ImGuiStorage const& storage = ImGui_ImplEmscripten_GetKeyTranslationStorage();
return (ImGuiKey)storage.GetInt(ImHashStr(emscripten_key), ImGuiKey_None);
}
#endif // IMGUI_DISABLE

View file

@ -0,0 +1,70 @@
// dear imgui: Platform Backend for Emscripten HTML5
//
// This is a platform back-end, similar to and offering an alternative to imgui_impl_glfw.
// The intended use-case is for applications built with Emscripten, running in the browser, but *not* using GLFW.
// It uses Emscripten's HTML5 interface to tie callbacks to imgui input, handling window resizing,
// focus, cursor, keyboard input, etc. It does not attempt to handle rendering.
//
// A note about GLFW on Emscripten: Emscripten includes its own GLFW implementation, which wraps browser HTML5 callbacks to provide the standard GLFW input interface. So there are two levels of indirection.
// This backend removes the middleman for input, providing a more efficient direct interface between Emscripten's functionality and imgui input.
//
// This is a useful accompaniment for WebGPU rendering (i.e. with imgui_impl_wgpu), where GLFW is not needed for rendering.
// In that case, this backend replaces all non-rendering-related functionality from GLFW, making it possible to avoid depending on GLFW altogether.
//
// For native cursor rendering, this includes a cut-down implementation of the Emscripten Browser Cursor library: https://github.com/Armchair-Software/emscripten-browser-cursor
// Supported features:
// - Keyboard input
// - Window resizing
// - Cursor position
// - Cursor enters and leaves the window
// - Application focus
// - Browser cursors
// TODO:
// - Touch events
// A note on gamepad input: This back-end does not attempt to handle gamepad events, for the simple
// reason that any time you intend to provide gamepad input to imgui, you will inevitably want to
// also use gamepad input in your own game logic, so duplicating this processing can add a lot of
// inefficiency. For an example of how to handle Emscripten HTML5 gamepad events efficiently, and
// pass the relevant events to imgui, see https://github.com/Armchair-Software/webgpu-demo2
// Don't forget to set io.BackendFlags |= ImGuiBackendFlags_HasGamepad when a gamepad is connected.
#pragma once
#include "imgui.h"
#ifndef IMGUI_DISABLE
#ifndef __EMSCRIPTEN__
#error The imgui_impl_emscripten backend requires Emscripten.
#endif
// Initialise the Emscripten backend, setting input callbacks. This should be called after ImGui::CreateContext();
// The target_device_pixel_ratio parameter controls how many device pixels Dear ImGui should target per Dear ImGui pixel. Default 1.0f gives 1:1 device-pixel rendering.
IMGUI_IMPL_API bool ImGui_ImplEmscripten_Init(float target_device_pixel_ratio = 1.0f);
// Change the target device pixel ratio at runtime and update display properties immediately.
IMGUI_IMPL_API void ImGui_ImplEmscripten_SetTargetDevicePixelRatio(float target_device_pixel_ratio);
// Return the scale used to convert browser CSS pixel coordinates to Dear ImGui coordinates. Valid after Init().
IMGUI_IMPL_API ImVec2 ImGui_ImplEmscripten_GetCssToImGuiScale();
// Refresh display size and scaling properties immediately. Call after changing the canvas framebuffer or CSS size.
IMGUI_IMPL_API void ImGui_ImplEmscripten_UpdateDisplayProperties();
// Input translation helpers.
IMGUI_IMPL_API ImGuiMouseButton ImGui_ImplEmscripten_TranslateMouseButton(unsigned short emscripten_button);
IMGUI_IMPL_API ImGuiKey ImGui_ImplEmscripten_TranslateKey(const char* emscripten_key);
// Shut down the Emscripten backend. This unsets all Emscripten input callbacks set by Init.
// Note it'll also unset any Emscripten input callbacks set elsewhere in the program!
// Note also there is no obligation to ever call this, unless you intend to reset the backend (i.e. with ImGui::DestroyContext()).
// Otherwise, there is not necessarily any such concept as "shutting down" when running in the browser, and we have no resources to release. The user can just close the tab, so you don't need to worry about exiting cleanly.
IMGUI_IMPL_API void ImGui_ImplEmscripten_Shutdown();
// Call every frame to synchronize Dear ImGui's cursor state with the browser's native cursors.
// If you are not using browser native cursor rendering (i.e. if Dear ImGui is rendering cursors internally), you don't need to call this.
IMGUI_IMPL_API void ImGui_ImplEmscripten_NewFrame();
#endif // IMGUI_DISABLE

View file

@ -1,6 +1,6 @@
// dear imgui: Renderer for WebGPU
// This needs to be used along with a Platform Binding (e.g. GLFW, SDL2, SDL3)
// (Please note that WebGPU is a recent API, may not be supported by all browser, and its ecosystem is generally a mess)
// This needs to be used along with a Platform Binding (e.g. GLFW, SDL2, SDL3, Emscripten)
// (Please note that WebGPU is a recent API, may not be supported by all browsers, and its ecosystem is generally a mess)
// Implemented features:
// [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID/ImTextureRef!

View file

@ -1,6 +1,6 @@
// dear imgui: Renderer for WebGPU
// This needs to be used along with a Platform Binding (e.g. GLFW, SDL2, SDL3)
// (Please note that WebGPU is a recent API, may not be supported by all browser, and its ecosystem is generally a mess)
// This needs to be used along with a Platform Binding (e.g. GLFW, SDL2, SDL3, Emscripten)
// (Please note that WebGPU is a recent API, may not be supported by all browsers, and its ecosystem is generally a mess)
// When targeting native platforms:
// - One of IMGUI_IMPL_WEBGPU_BACKEND_DAWN, IMGUI_IMPL_WEBGPU_BACKEND_WGPU or IMGUI_IMPL_WEBGPU_BACKEND_WGVK *must* be provided.

View file

@ -75,6 +75,14 @@ OSX + OpenGL2 example. <BR>
OSX + OpenGL3 example. <BR>
= main.mm + imgui_impl_osx.mm + imgui_impl_opengl3.cpp <BR>
[example_emscripten_opengl3/](https://github.com/ocornut/imgui/blob/master/examples/example_emscripten_opengl3/) <BR>
Emscripten (browser) + OpenGL3/ES2/ES3 example. Uses imgui_impl_emscripten for platform input and imgui_impl_opengl3 for rendering. <BR>
= main.cpp + imgui_impl_emscripten.cpp + imgui_impl_opengl3.cpp
[example_emscripten_wgpu/](https://github.com/ocornut/imgui/blob/master/examples/example_emscripten_wgpu/) <BR>
Emscripten (browser) + WebGPU example. Uses imgui_impl_emscripten for platform input and imgui_impl_wgpu for rendering. <BR>
= main.cpp + imgui_impl_emscripten.cpp + imgui_impl_wgpu.cpp
[example_glfw_wgpu/](https://github.com/ocornut/imgui/blob/master/examples/example_glfw_wgpu/) <BR>
GLFW + WebGPU example. Supports Emscripten (web), Dawn (native), WGPU (native). <BR>
= main.cpp + imgui_impl_glfw.cpp + imgui_impl_wgpu.cpp
@ -271,4 +279,3 @@ when an interactive drag is in progress.
Note that some setup configurations or GPU drivers may introduce additional display lag depending on their settings.
If you notice that dragging windows is laggy and you are not sure what the cause is: try drawing a simple
2D shape directly under the mouse cursor to help identify the issue!

View file

@ -0,0 +1,94 @@
#
# Makefile to use with emscripten
# See https://emscripten.org/docs/getting_started/downloads.html
# for installation instructions.
#
# This Makefile assumes you have loaded emscripten's environment.
# (On Windows, you may need to execute emsdk_env.bat or encmdprompt.bat ahead)
#
# Running `make -f Makefile.emscripten` will produce three files:
# - web/index.html
# - web/index.js
# - web/index.wasm
#
# All three are needed to run the demo.
CC = emcc
CXX = em++
WEB_DIR = web
EXE = $(WEB_DIR)/index.html
IMGUI_DIR = ../..
SOURCES = main.cpp
SOURCES += $(IMGUI_DIR)/backends/imgui_impl_emscripten.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp
SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp
OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES))))
UNAME_S := $(shell uname -s)
CPPFLAGS =
LDFLAGS =
EMS =
##---------------------------------------------------------------------
## EMSCRIPTEN OPTIONS
##---------------------------------------------------------------------
# ("EMS" options gets added to both CPPFLAGS and LDFLAGS, whereas some options are for linker only)
EMS += -s DISABLE_EXCEPTION_CATCHING=1
LDFLAGS += -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s NO_EXIT_RUNTIME=0 -s ASSERTIONS=1
# Use WebGL 1 by default. Set USE_WEBGL2=1 to use OpenGL ES 3 and WebGL 2 instead.
USE_WEBGL2 ?= 0
ifeq ($(USE_WEBGL2), 1)
CPPFLAGS += -DIMGUI_IMPL_OPENGL_ES3
LDFLAGS += -s MIN_WEBGL_VERSION=2 -s MAX_WEBGL_VERSION=2
endif
# Build as single file (binary text encoded in .html file)
#LDFLAGS += -sSINGLE_FILE
# Emscripten allows preloading a file or folder to be accessible at runtime.
# The Makefile for this example project suggests embedding the misc/fonts/ folder into our application, it will then be accessible as "/fonts"
# See documentation for more details: https://emscripten.org/docs/porting/files/packaging_files.html
# (Default value is 0. Set to 1 to enable file-system and include the misc/fonts/ folder as part of the build.)
USE_FILE_SYSTEM ?= 0
ifeq ($(USE_FILE_SYSTEM), 0)
LDFLAGS += -s NO_FILESYSTEM=1
CPPFLAGS += -DIMGUI_DISABLE_FILE_FUNCTIONS
endif
ifeq ($(USE_FILE_SYSTEM), 1)
LDFLAGS += --no-heap-copy --preload-file ../../misc/fonts@/fonts
endif
##---------------------------------------------------------------------
## FINAL BUILD FLAGS
##---------------------------------------------------------------------
CPPFLAGS += -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends
#CPPFLAGS += -g
CPPFLAGS += -Wall -Wformat -Os $(EMS)
LDFLAGS += --shell-file ../libs/emscripten/shell_minimal.html
LDFLAGS += $(EMS)
##---------------------------------------------------------------------
## BUILD RULES
##---------------------------------------------------------------------
%.o:%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
%.o:$(IMGUI_DIR)/%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
%.o:$(IMGUI_DIR)/backends/%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
all: $(EXE)
@echo Build complete for $(EXE)
$(WEB_DIR):
mkdir $@
$(EXE): $(OBJS) $(WEB_DIR)
$(CXX) -o $@ $(OBJS) $(LDFLAGS)
clean:
rm -f $(EXE) $(OBJS) $(WEB_DIR)/*.js $(WEB_DIR)/*.wasm $(WEB_DIR)/*.wasm.pre

View file

@ -0,0 +1,23 @@
## How to Build
- You need to install Emscripten from https://emscripten.org/docs/getting_started/downloads.html, and have the environment variables set, as described in https://emscripten.org/docs/getting_started/downloads.html#installation-instructions
- Depending on your configuration, in Windows you may need to run `emsdk/emsdk_env.bat` in your console to access the Emscripten command-line tools.
- You may also refer to our [Continuous Integration setup](https://github.com/ocornut/imgui/tree/master/.github/workflows) for Emscripten setup.
- Then build using `make -f Makefile.emscripten` while in the `example_emscripten_opengl3/` directory.
- This example is browser-only. It uses `imgui_impl_emscripten` for platform input and `imgui_impl_opengl3` for rendering.
- The default build targets OpenGL ES 2 and WebGL 1. Build with `make -f Makefile.emscripten USE_WEBGL2=1` to target OpenGL ES 3 and WebGL 2 instead.
## How to Run
To run on a local machine:
- `emrun web/index.html` will spawn a temporary local webserver and open the example in your browser. See https://emscripten.org/docs/compiling/Running-html-files-with-emrun.html for details.
- Otherwise, generally you will need a local webserver:
- Quoting [https://emscripten.org/docs/getting_started](https://emscripten.org/docs/getting_started/Tutorial.html#generating-html):<br>
_"Unfortunately several browsers (including Chrome, Safari, and Internet Explorer) do not support file:// [XHR](https://emscripten.org/docs/site/glossary.html#term-xhr) requests, and can't load extra files needed by the HTML (like a .wasm file, or packaged file data as mentioned lower down). For these browsers you'll need to serve the files using a [local webserver](https://emscripten.org/docs/getting_started/FAQ.html#faq-local-webserver) and then open http://localhost:8000/hello.html."_
- You may use Python 3 builtin webserver: `python -m http.server -d web`.
- You may use Python 2 builtin webserver: `cd web && python -m SimpleHTTPServer`.

View file

@ -0,0 +1,210 @@
// Dear ImGui: standalone example application for using Emscripten + OpenGL 3
// - This uses imgui_impl_emscripten for platform inputs and imgui_impl_opengl3 for rendering.
// - Emscripten is required to build this example. See https://emscripten.org.
// - WebGL 1 is used by default. Define IMGUI_IMPL_OPENGL_ES3 and enable WebGL 2 to use WebGL 2 instead.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
#include "imgui.h"
#include "imgui_impl_emscripten.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#if defined(IMGUI_IMPL_OPENGL_ES2)
#include <GLES2/gl2.h>
#elif defined(IMGUI_IMPL_OPENGL_ES3)
#include <GLES3/gl3.h>
#endif
#include <emscripten.h>
#include <emscripten/html5.h>
#include "../libs/emscripten/emscripten_mainloop_stub.h"
// Data
static int gl_framebuffer_width = 0;
static int gl_framebuffer_height = 0;
// Forward declarations
static void GetFramebufferSizeFromDisplaySize(int width, int height, int* framebuffer_width, int* framebuffer_height);
static void ResizeCanvas(int width, int height);
static void GetFramebufferSizeFromDisplaySize(int width, int height, int* framebuffer_width, int* framebuffer_height)
{
ImGuiIO& io = ImGui::GetIO();
*framebuffer_width = (int)(width * io.DisplayFramebufferScale.x + 0.5f);
*framebuffer_height = (int)(height * io.DisplayFramebufferScale.y + 0.5f);
}
static void ResizeCanvas(int width, int height)
{
if (width <= 0 || height <= 0)
return;
int framebuffer_width = 0;
int framebuffer_height = 0;
GetFramebufferSizeFromDisplaySize(width, height, &framebuffer_width, &framebuffer_height);
if (framebuffer_width <= 0 || framebuffer_height <= 0)
return;
emscripten_set_canvas_element_size("#canvas", framebuffer_width, framebuffer_height);
gl_framebuffer_width = framebuffer_width;
gl_framebuffer_height = framebuffer_height;
}
// Main code
int main(int, char**)
{
// Decide GL+GLSL versions
EmscriptenWebGLContextAttributes context_attributes;
emscripten_webgl_init_context_attributes(&context_attributes);
#if defined(IMGUI_IMPL_OPENGL_ES2)
// GL ES 2.0 + GLSL 100 (WebGL 1.0)
const char* glsl_version = "#version 100";
context_attributes.majorVersion = 1;
context_attributes.minorVersion = 0;
#elif defined(IMGUI_IMPL_OPENGL_ES3)
// GL ES 3.0 + GLSL 300 es (WebGL 2.0)
const char* glsl_version = "#version 300 es";
context_attributes.majorVersion = 2;
context_attributes.minorVersion = 0;
#endif
// Create window with graphics context
EMSCRIPTEN_WEBGL_CONTEXT_HANDLE gl_context = emscripten_webgl_create_context("#canvas", &context_attributes);
if (gl_context == 0)
{
fprintf(stderr, "Failed to create WebGL context.\n");
return 1;
}
if (emscripten_webgl_make_context_current(gl_context) != EMSCRIPTEN_RESULT_SUCCESS)
{
fprintf(stderr, "Failed to make WebGL context current.\n");
emscripten_webgl_destroy_context(gl_context);
return 1;
}
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
// Setup Dear ImGui style
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
ImGui_ImplEmscripten_Init();
if (io.DisplaySize.x > 0.0f && io.DisplaySize.y > 0.0f)
ResizeCanvas((int)io.DisplaySize.x, (int)io.DisplaySize.y);
ImGui_ImplOpenGL3_Init(glsl_version);
// Load Fonts
// - If fonts are not explicitly loaded, Dear ImGui will select an embedded font: either AddFontDefaultVector() or AddFontDefaultBitmap().
// This selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold.
// - You can load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
// - If a file cannot be loaded, AddFont functions will return a nullptr. Please handle those errors in your code (e.g. use an assertion, display an error and quit).
// - Read 'docs/FONTS.md' for more instructions and details.
// - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use FreeType for higher quality font rendering.
// - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
// - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details.
//style.FontSizeBase = 20.0f;
//io.Fonts->AddFontDefaultVector();
//io.Fonts->AddFontDefaultBitmap();
#ifndef IMGUI_DISABLE_FILE_FUNCTIONS
//io.Fonts->AddFontFromFileTTF("fonts/segoeui.ttf");
//io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf");
//io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf");
//io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf");
//ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf");
//IM_ASSERT(font != nullptr);
#endif
// Our state
bool show_demo_window = true;
bool show_another_window = false;
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
// Main loop
// For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file.
// You may manually call LoadIniSettingsFromMemory() to load settings from your own storage.
io.IniFilename = nullptr;
EMSCRIPTEN_MAINLOOP_BEGIN
{
// Input handling is callback-driven via imgui_impl_emscripten, so there is no event pump here.
// React to changes in browser window size.
int width = (int)io.DisplaySize.x;
int height = (int)io.DisplaySize.y;
if (width <= 0 || height <= 0)
continue;
int framebuffer_width = 0;
int framebuffer_height = 0;
GetFramebufferSizeFromDisplaySize(width, height, &framebuffer_width, &framebuffer_height);
if (framebuffer_width != gl_framebuffer_width || framebuffer_height != gl_framebuffer_height)
ResizeCanvas(width, height);
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplEmscripten_NewFrame();
ImGui::NewFrame();
// 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).
if (show_demo_window)
ImGui::ShowDemoWindow(&show_demo_window);
// 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
{
static float f = 0.0f;
static int counter = 0;
ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it.
ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too)
ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state
ImGui::Checkbox("Another Window", &show_another_window);
ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f
ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color
if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
counter++;
ImGui::SameLine();
ImGui::Text("counter = %d", counter);
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
ImGui::End();
}
// 3. Show another simple window.
if (show_another_window)
{
ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
ImGui::Text("Hello from another window!");
if (ImGui::Button("Close Me"))
show_another_window = false;
ImGui::End();
}
// Rendering
ImGui::Render();
glViewport(0, 0, framebuffer_width, framebuffer_height);
glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
EMSCRIPTEN_MAINLOOP_END;
// Cleanup
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplEmscripten_Shutdown();
ImGui::DestroyContext();
emscripten_webgl_destroy_context(gl_context);
return 0;
}

View file

@ -0,0 +1,99 @@
#
# Makefile to use with emscripten
# See https://emscripten.org/docs/getting_started/downloads.html
# for installation instructions.
#
# This Makefile assumes you have loaded emscripten's environment.
# (On Windows, you may need to execute emsdk_env.bat or encmdprompt.bat ahead)
#
# Running `make -f Makefile.emscripten` will produce three files:
# - web/index.html
# - web/index.js
# - web/index.wasm
#
# All three are needed to run the demo.
CC = emcc
CXX = em++
WEB_DIR = web
EXE = $(WEB_DIR)/index.html
IMGUI_DIR = ../..
SOURCES = main.cpp
SOURCES += $(IMGUI_DIR)/backends/imgui_impl_emscripten.cpp $(IMGUI_DIR)/backends/imgui_impl_wgpu.cpp
SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp
OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES))))
UNAME_S := $(shell uname -s)
CPPFLAGS =
LDFLAGS =
EMS =
##---------------------------------------------------------------------
## EMSCRIPTEN OPTIONS
##---------------------------------------------------------------------
# ("EMS" options gets added to both CPPFLAGS and LDFLAGS, whereas some options are for linker only)
EMS += -s DISABLE_EXCEPTION_CATCHING=1
LDFLAGS += -s WASM=1
LDFLAGS += -s ALLOW_MEMORY_GROWTH=1
LDFLAGS += -s ASYNCIFY=1
LDFLAGS += -s NO_EXIT_RUNTIME=0
LDFLAGS += -s ASSERTIONS=1
# This example uses imgui_impl_emscripten directly, so no GLFW port is required.
# Use Emscripten's Dawn-based WebGPU port.
EMS += --use-port=emdawnwebgpu
LDFLAGS += --use-port=emdawnwebgpu
# Build as single file (binary text encoded in .html file)
#LDFLAGS += -sSINGLE_FILE
# Emscripten allows preloading a file or folder to be accessible at runtime.
# The Makefile for this example project suggests embedding the misc/fonts/ folder into our application, it will then be accessible as "/fonts"
# See documentation for more details: https://emscripten.org/docs/porting/files/packaging_files.html
# (Default value is 0. Set to 1 to enable file-system and include the misc/fonts/ folder as part of the build.)
USE_FILE_SYSTEM ?= 0
ifeq ($(USE_FILE_SYSTEM), 0)
LDFLAGS += -s NO_FILESYSTEM=1
CPPFLAGS += -DIMGUI_DISABLE_FILE_FUNCTIONS
endif
ifeq ($(USE_FILE_SYSTEM), 1)
LDFLAGS += --no-heap-copy --preload-file ../../misc/fonts@/fonts
endif
##---------------------------------------------------------------------
## FINAL BUILD FLAGS
##---------------------------------------------------------------------
CPPFLAGS += -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends
#CPPFLAGS += -g
CPPFLAGS += -Wall -Wformat -Os $(EMS)
LDFLAGS += --shell-file ../libs/emscripten/shell_minimal.html
LDFLAGS += $(EMS)
##---------------------------------------------------------------------
## BUILD RULES
##---------------------------------------------------------------------
%.o:%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
%.o:$(IMGUI_DIR)/%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
%.o:$(IMGUI_DIR)/backends/%.cpp
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $<
all: $(EXE)
@echo Build complete for $(EXE)
$(WEB_DIR):
mkdir $@
serve: all
python3 -m http.server -d $(WEB_DIR)
$(EXE): $(OBJS) $(WEB_DIR)
$(CXX) -o $@ $(OBJS) $(LDFLAGS)
clean:
rm -f $(EXE) $(OBJS) $(WEB_DIR)/*.js $(WEB_DIR)/*.wasm $(WEB_DIR)/*.wasm.pre

View file

@ -0,0 +1,24 @@
## How to Build
- You need to install Emscripten from https://emscripten.org/docs/getting_started/downloads.html, and have the environment variables set, as described in https://emscripten.org/docs/getting_started/downloads.html#installation-instructions
- Depending on your configuration, in Windows you may need to run `emsdk/emsdk_env.bat` in your console to access the Emscripten command-line tools.
- You may also refer to our [Continuous Integration setup](https://github.com/ocornut/imgui/tree/master/.github/workflows) for Emscripten setup.
- Then build using `make -f Makefile.emscripten` while in the `example_emscripten_wgpu/` directory.
- This example is browser-only. It uses `imgui_impl_emscripten` for platform input and `imgui_impl_wgpu` for rendering.
- Requires recent Emscripten with the `emdawnwebgpu` port available.
## How to Run
To run on a local machine:
- Make sure your browser supports WebGPU and it is enabled.
- `emrun web/index.html` will spawn a temporary local webserver and open the example in your browser. See https://emscripten.org/docs/compiling/Running-html-files-with-emrun.html for details.
- Otherwise, generally you will need a local webserver:
- Quoting [https://emscripten.org/docs/getting_started](https://emscripten.org/docs/getting_started/Tutorial.html#generating-html):<br>
_"Unfortunately several browsers (including Chrome, Safari, and Internet Explorer) do not support file:// [XHR](https://emscripten.org/docs/site/glossary.html#term-xhr) requests, and can’t load extra files needed by the HTML (like a .wasm file, or packaged file data as mentioned lower down). For these browsers you’ll need to serve the files using a [local webserver](https://emscripten.org/docs/getting_started/FAQ.html#faq-local-webserver) and then open http://localhost:8000/hello.html."_
- You may use Python 3 builtin webserver: `python -m http.server -d web` (this is what `make serve` uses).
- You may use Python 2 builtin webserver: `cd web && python -m SimpleHTTPServer`.

View file

@ -0,0 +1,352 @@
// Dear ImGui: standalone example application for using Emscripten + WebGPU
// - This uses imgui_impl_emscripten for platform inputs and imgui_impl_wgpu for rendering.
// - Emscripten is required to build this example. See https://emscripten.org.
// - Dawn is used as the WebGPU implementation via Emscripten's emdawnwebgpu port.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
#include "imgui.h"
#include "imgui_impl_emscripten.h"
#include "imgui_impl_wgpu.h"
#include <stdio.h>
#include <stdlib.h>
#include <utility>
#include <emscripten.h>
#include <emscripten/html5.h>
#include "../libs/emscripten/emscripten_mainloop_stub.h"
#include <webgpu/webgpu.h>
#include <webgpu/webgpu_cpp.h>
#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN)
#error This example requires IMGUI_IMPL_WEBGPU_BACKEND_DAWN.
#endif
// Data
static WGPUInstance wgpu_instance = nullptr;
static WGPUDevice wgpu_device = nullptr;
static WGPUSurface wgpu_surface = nullptr;
static WGPUQueue wgpu_queue = nullptr;
static WGPUSurfaceConfiguration wgpu_surface_configuration = {};
static int wgpu_surface_width = 1280;
static int wgpu_surface_height = 800;
// Forward declarations
static bool InitWGPU();
static void GetFramebufferSizeFromDisplaySize(int width, int height, int* framebuffer_width, int* framebuffer_height);
static void ResizeSurface(int width, int height);
static void GetFramebufferSizeFromDisplaySize(int width, int height, int* framebuffer_width, int* framebuffer_height)
{
ImGuiIO& io = ImGui::GetIO();
*framebuffer_width = (int)(width * io.DisplayFramebufferScale.x + 0.5f);
*framebuffer_height = (int)(height * io.DisplayFramebufferScale.y + 0.5f);
}
static void ResizeSurface(int width, int height)
{
if (width <= 0 || height <= 0)
return;
int framebuffer_width = 0;
int framebuffer_height = 0;
GetFramebufferSizeFromDisplaySize(width, height, &framebuffer_width, &framebuffer_height);
if (framebuffer_width <= 0 || framebuffer_height <= 0)
return;
emscripten_set_canvas_element_size("#canvas", framebuffer_width, framebuffer_height);
wgpu_surface_configuration.width = wgpu_surface_width = framebuffer_width;
wgpu_surface_configuration.height = wgpu_surface_height = framebuffer_height;
wgpuSurfaceConfigure(wgpu_surface, &wgpu_surface_configuration);
}
// Main code
int main(int, char**)
{
if (!InitWGPU())
return 1;
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
// Setup Dear ImGui style
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
// Setup Platform/Renderer backends
ImGui_ImplEmscripten_Init();
if (io.DisplaySize.x > 0.0f && io.DisplaySize.y > 0.0f)
ResizeSurface((int)io.DisplaySize.x, (int)io.DisplaySize.y);
ImGui_ImplWGPU_InitInfo init_info;
init_info.Device = wgpu_device;
init_info.NumFramesInFlight = 3;
init_info.RenderTargetFormat = wgpu_surface_configuration.format;
init_info.DepthStencilFormat = WGPUTextureFormat_Undefined;
ImGui_ImplWGPU_Init(&init_info);
// Load Fonts
// - If fonts are not explicitly loaded, Dear ImGui will select an embedded font: either AddFontDefaultVector() or AddFontDefaultBitmap().
// This selection is based on (style.FontSizeBase * style.FontScaleMain * style.FontScaleDpi) reaching a small threshold.
// - You can load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
// - If a file cannot be loaded, AddFont functions will return a nullptr. Please handle those errors in your code (e.g. use an assertion, display an error and quit).
// - Read 'docs/FONTS.md' for more instructions and details.
// - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use FreeType for higher quality font rendering.
// - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
// - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details.
//style.FontSizeBase = 20.0f;
//io.Fonts->AddFontDefaultVector();
//io.Fonts->AddFontDefaultBitmap();
#ifndef IMGUI_DISABLE_FILE_FUNCTIONS
//io.Fonts->AddFontFromFileTTF("fonts/segoeui.ttf");
//io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf");
//io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf");
//io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf");
//ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf");
//IM_ASSERT(font != nullptr);
#endif
// Our state
bool show_demo_window = true;
bool show_another_window = false;
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
// Main loop
// For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file.
// You may manually call LoadIniSettingsFromMemory() to load settings from your own storage.
io.IniFilename = nullptr;
EMSCRIPTEN_MAINLOOP_BEGIN
{
// Input handling is callback-driven via imgui_impl_emscripten, so there is no event pump here.
// React to changes in browser window size.
int width = (int)io.DisplaySize.x;
int height = (int)io.DisplaySize.y;
if (width <= 0 || height <= 0)
continue;
int framebuffer_width = 0;
int framebuffer_height = 0;
GetFramebufferSizeFromDisplaySize(width, height, &framebuffer_width, &framebuffer_height);
if (framebuffer_width != wgpu_surface_width || framebuffer_height != wgpu_surface_height)
ResizeSurface(width, height);
// Check surface status for error. If texture is not optimal, try to reconfigure the surface.
WGPUSurfaceTexture surface_texture;
wgpuSurfaceGetCurrentTexture(wgpu_surface, &surface_texture);
if (ImGui_ImplWGPU_IsSurfaceStatusError(surface_texture.status))
{
fprintf(stderr, "Unrecoverable Surface Texture status=%#.8x\n", surface_texture.status);
abort();
}
if (ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(surface_texture.status))
{
if (surface_texture.texture)
wgpuTextureRelease(surface_texture.texture);
ResizeSurface(width, height);
continue;
}
// Start the Dear ImGui frame
ImGui_ImplWGPU_NewFrame();
ImGui_ImplEmscripten_NewFrame();
ImGui::NewFrame();
// 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).
if (show_demo_window)
ImGui::ShowDemoWindow(&show_demo_window);
// 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
{
static float f = 0.0f;
static int counter = 0;
ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it.
ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too)
ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state
ImGui::Checkbox("Another Window", &show_another_window);
ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f
ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color
if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
counter++;
ImGui::SameLine();
ImGui::Text("counter = %d", counter);
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate);
ImGui::End();
}
// 3. Show another simple window.
if (show_another_window)
{
ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
ImGui::Text("Hello from another window!");
if (ImGui::Button("Close Me"))
show_another_window = false;
ImGui::End();
}
// Rendering
ImGui::Render();
WGPUTextureViewDescriptor view_desc = {};
view_desc.format = wgpu_surface_configuration.format;
view_desc.dimension = WGPUTextureViewDimension_2D;
view_desc.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED;
view_desc.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED;
view_desc.aspect = WGPUTextureAspect_All;
WGPUTextureView texture_view = wgpuTextureCreateView(surface_texture.texture, &view_desc);
WGPURenderPassColorAttachment color_attachments = {};
color_attachments.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
color_attachments.loadOp = WGPULoadOp_Clear;
color_attachments.storeOp = WGPUStoreOp_Store;
color_attachments.clearValue = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w };
color_attachments.view = texture_view;
WGPURenderPassDescriptor render_pass_desc = {};
render_pass_desc.colorAttachmentCount = 1;
render_pass_desc.colorAttachments = &color_attachments;
render_pass_desc.depthStencilAttachment = nullptr;
WGPUCommandEncoderDescriptor enc_desc = {};
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device, &enc_desc);
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc);
ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass);
wgpuRenderPassEncoderEnd(pass);
WGPUCommandBufferDescriptor cmd_buffer_desc = {};
WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc);
wgpuQueueSubmit(wgpu_queue, 1, &cmd_buffer);
wgpuTextureViewRelease(texture_view);
wgpuRenderPassEncoderRelease(pass);
wgpuCommandEncoderRelease(encoder);
wgpuCommandBufferRelease(cmd_buffer);
}
EMSCRIPTEN_MAINLOOP_END;
// Cleanup
ImGui_ImplWGPU_Shutdown();
ImGui_ImplEmscripten_Shutdown();
ImGui::DestroyContext();
wgpuSurfaceUnconfigure(wgpu_surface);
wgpuSurfaceRelease(wgpu_surface);
wgpuQueueRelease(wgpu_queue);
wgpuDeviceRelease(wgpu_device);
wgpuInstanceRelease(wgpu_instance);
return 0;
}
static WGPUAdapter RequestAdapter(wgpu::Instance& instance)
{
wgpu::Adapter acquired_adapter;
wgpu::RequestAdapterOptions adapter_options;
auto onRequestAdapter = [&](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, wgpu::StringView message)
{
if (status != wgpu::RequestAdapterStatus::Success)
{
printf("Failed to get an adapter: %s\n", message.data);
return;
}
acquired_adapter = std::move(adapter);
};
// Synchronously (wait until) acquire Adapter
wgpu::Future waitAdapterFunc { instance.RequestAdapter(&adapter_options, wgpu::CallbackMode::WaitAnyOnly, onRequestAdapter) };
wgpu::WaitStatus waitStatusAdapter = instance.WaitAny(waitAdapterFunc, UINT64_MAX);
IM_ASSERT(acquired_adapter != nullptr && waitStatusAdapter == wgpu::WaitStatus::Success && "Error on Adapter request");
return acquired_adapter.MoveToCHandle();
}
static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter)
{
// Set device callback functions
wgpu::DeviceDescriptor device_desc;
device_desc.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous,
[](const wgpu::Device&, wgpu::DeviceLostReason type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetDeviceLostReasonName((WGPUDeviceLostReason)type), msg.data); }
);
device_desc.SetUncapturedErrorCallback(
[](const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetErrorTypeName((WGPUErrorType)type), msg.data); }
);
wgpu::Device acquired_device;
auto onRequestDevice = [&](wgpu::RequestDeviceStatus status, wgpu::Device local_device, wgpu::StringView message)
{
if (status != wgpu::RequestDeviceStatus::Success)
{
printf("Failed to get a device: %s\n", message.data);
return;
}
acquired_device = std::move(local_device);
};
// Synchronously (wait until) get Device
wgpu::Future waitDeviceFunc { adapter.RequestDevice(&device_desc, wgpu::CallbackMode::WaitAnyOnly, onRequestDevice) };
wgpu::WaitStatus waitStatusDevice = instance.WaitAny(waitDeviceFunc, UINT64_MAX);
IM_ASSERT(acquired_device != nullptr && waitStatusDevice == wgpu::WaitStatus::Success && "Error on Device request");
return acquired_device.MoveToCHandle();
}
static bool InitWGPU()
{
WGPUTextureFormat preferred_fmt = WGPUTextureFormat_Undefined; // acquired from SurfaceCapabilities
// Google DAWN backend: Adapter and Device acquisition, Surface creation
wgpu::InstanceDescriptor instance_desc = {};
static constexpr wgpu::InstanceFeatureName timedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny;
instance_desc.requiredFeatureCount = 1;
instance_desc.requiredFeatures = &timedWaitAny;
wgpu::Instance instance = wgpu::CreateInstance(&instance_desc);
wgpu::Adapter adapter = RequestAdapter(instance);
ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter.Get());
wgpu_device = RequestDevice(instance, adapter);
// Create the surface.
wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvas_desc = {};
canvas_desc.selector = "#canvas";
wgpu::SurfaceDescriptor surface_desc = {};
surface_desc.nextInChain = &canvas_desc;
wgpu::Surface surface = instance.CreateSurface(&surface_desc);
if (!surface)
return false;
// Moving Dawn objects into WGPU handles
wgpu_instance = instance.MoveToCHandle();
wgpu_surface = surface.MoveToCHandle();
WGPUSurfaceCapabilities surface_capabilities = {};
wgpuSurfaceGetCapabilities(wgpu_surface, adapter.Get(), &surface_capabilities);
preferred_fmt = surface_capabilities.formats[0];
wgpu_surface_configuration.presentMode = WGPUPresentMode_Fifo;
wgpu_surface_configuration.alphaMode = WGPUCompositeAlphaMode_Auto;
wgpu_surface_configuration.usage = WGPUTextureUsage_RenderAttachment;
wgpu_surface_configuration.width = wgpu_surface_width;
wgpu_surface_configuration.height = wgpu_surface_height;
wgpu_surface_configuration.device = wgpu_device;
wgpu_surface_configuration.format = preferred_fmt;
wgpuSurfaceConfigure(wgpu_surface, &wgpu_surface_configuration);
wgpu_queue = wgpuDeviceGetQueue(wgpu_device);
return true;
}