Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
name: Build

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:

env:
HERMES_VERSION: 80359d48dbf0a108031d69c8a22bad180cfb4df3

jobs:
build:
name: ${{ matrix.os }} (${{ matrix.build_type }})
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
build_type: [Debug, Release]

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Ubuntu dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y \
libx11-dev \
libxi-dev \
libxcursor-dev \
libgl1-mesa-dev \
libicu-dev \
clang \
cmake \
ninja-build \
nodejs \
npm

- name: Install macOS dependencies
if: runner.os == 'macOS'
run: |
brew install cmake ninja node

- name: Cache Hermes build
id: hermes-cache
uses: actions/cache@v4
with:
path: |
hermes-src
hermes-build
key: ${{ runner.os }}-hermes-${{ env.HERMES_VERSION }}

- name: Build Hermes (if not cached)
if: steps.hermes-cache.outputs.cache-hit != 'true'
run: |
git clone https://github.com/facebook/hermes.git hermes-src
cd hermes-src
git checkout ${{ env.HERMES_VERSION }}
# Remove .git directory immediately to save ~100MB in cache
rm -rf .git
cd ..
cmake -S hermes-src -B hermes-build -DCMAKE_BUILD_TYPE=Release -G Ninja
cmake --build hermes-build --config Release

- name: Install npm dependencies
run: npm install

- name: Configure project (Ubuntu)
if: runner.os == 'Linux'
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++ \
-DHERMES_BUILD_DIR=${{ github.workspace }}/hermes-build \
-G Ninja

- name: Configure project (macOS)
if: runner.os == 'macOS'
run: |
cmake -B build \
-DCMAKE_BUILD_TYPE=${{ matrix.build_type }} \
-DHERMES_BUILD_DIR=${{ github.workspace }}/hermes-build \
-G Ninja

- name: Build project
run: cmake --build build

- name: Upload showcase executable
uses: actions/upload-artifact@v4
with:
name: showcase-${{ matrix.os }}-${{ matrix.build_type }}
path: build/examples/showcase/showcase${{ runner.os == 'Windows' && '.exe' || '' }}
if-no-files-found: error

- name: Upload hello executable
uses: actions/upload-artifact@v4
with:
name: hello-${{ matrix.os }}-${{ matrix.build_type }}
path: build/examples/hello/hello${{ runner.os == 'Windows' && '.exe' || '' }}
if-no-files-found: error

- name: Upload dynamic-windows executable
uses: actions/upload-artifact@v4
with:
name: dynamic-windows-${{ matrix.os }}-${{ matrix.build_type }}
path: build/examples/dynamic-windows/dynamic-windows${{ runner.os == 'Windows' && '.exe' || '' }}
if-no-files-found: error
66 changes: 65 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ _**Note**: This project is an independent experiment and is not affiliated with,
- [Table Components](#table-components)
- [Drawing Primitives](#drawing-primitives)
- [Adding New Components](#adding-new-components)
- [Creating Custom Widgets](#creating-custom-widgets)
- [Architecture](#architecture)
- [Three-Unit Architecture](#three-unit-architecture)
- [Component Overview](#component-overview)
Expand Down Expand Up @@ -763,6 +764,62 @@ const [enabled, setEnabled] = useState(false);

The FFI layer provides access to the entire Dear ImGui API, so you can expose any widget you need!

### Creating Custom Widgets

While you can compose existing React components together, Dear ImGui's real power lies in creating fully custom widgets using its immediate-mode drawing API. The [`examples/custom-widget/`](examples/custom-widget/) directory demonstrates this with an interactive radial menu.

![Custom Widget Screenshot](media/custom-widget.jpg)

**How custom widgets work:**

1. **Add a render function** in [`lib/imgui-unit/renderer.js`](lib/imgui-unit/renderer.js) that uses ImGui's draw list API
2. **Add a case** to the switch statement in [`renderNode()`](a86e558b6ea712179d955f04a6ec17493b18953a/lib/imgui-unit/renderer.js#L785)
3. **Use `_igDummy()`** to reserve layout space for your custom drawing
4. **Handle mouse interaction** using `_igGetMousePos()` and `_igIsMouseClicked_Bool()`

**Example: RadialMenu** ([`lib/imgui-unit/renderer.js:607-752`](a86e558b6ea712179d955f04a6ec17493b18953a/lib/imgui-unit/renderer.js#L607-L752)

```javascript
function renderRadialMenu(node: any, vec2: c_ptr): void {
const drawList = _igGetWindowDrawList();

// Get cursor position and calculate drawing coordinates
_igGetCursorScreenPos(vec2);
const centerX = +get_ImVec2_x(vec2) + radius;
const centerY = +get_ImVec2_y(vec2) + radius;

...

// Draw filled sectors using path API
_ImDrawList_PathClear(drawList);
_ImDrawList_PathLineTo(drawList, centerPtr);
_ImDrawList_PathArcTo(drawList, centerPtr, radius, angleStart, angleEnd, 32);
_ImDrawList_PathFillConvex(drawList, color);

...

// Reserve space in layout
set_ImVec2_x(vec2, radius * 2);
set_ImVec2_y(vec2, radius * 2);
_igDummy(vec2);
}
```

**Key ImGui drawing functions:**

- `_ImDrawList_AddLine()`, `_ImDrawList_AddCircle()`, `_ImDrawList_AddRect()` - Basic shapes
- `_ImDrawList_PathArcTo()`, `_ImDrawList_PathFillConvex()` - Complex paths
- `_ImDrawList_AddText_Vec2()` - Text at specific positions
- `_igCalcTextSize()` - Measure text for centering
- `_igGetMousePos()`, `_igIsMouseClicked_Bool()` - Mouse interaction

**See the full implementation:**

- Code: [`lib/imgui-unit/renderer.js`](lib/imgui-unit/renderer.js) (`renderRadialMenu()`)
- Usage: [`examples/custom-widget/app.jsx`](examples/custom-widget/app.jsx)

This pattern works for any custom widget - graphs, charts, dials, custom controls, visualizations, etc. The draw list API gives you complete control over rendering while ImGui handles the window system and input.

## Architecture

### Three-Unit Architecture
Expand Down Expand Up @@ -1028,7 +1085,14 @@ Hermes is **automatically** cloned and built as part of the CMake configuration
```bash
cmake -B build -DHERMES_GIT_TAG=abc123def
```
- **Always Release mode**: Hermes always builds in Release for optimal performance
- **Build type**: Set via `HERMES_BUILD_TYPE` (default: Release)
```bash
# Build Hermes in Debug mode (useful for debugging shermes crashes)
cmake -B build -DHERMES_BUILD_TYPE=Debug

# Or RelWithDebInfo (optimized but with debug symbols)
cmake -B build -DHERMES_BUILD_TYPE=RelWithDebInfo
```
- **Per-config isolation**: Debug and Release builds get separate Hermes clones

### Building the Project
Expand Down
8 changes: 7 additions & 1 deletion cmake/HermesExternal.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,20 @@ else()
CACHE STRING "Hermes git repository URL")
set(HERMES_GIT_TAG "80359d48dbf0a108031d69c8a22bad180cfb4df3"
CACHE STRING "Hermes git tag/commit/branch to build")
set(HERMES_BUILD_TYPE "Release"
CACHE STRING "Hermes build type (Release, Debug, RelWithDebInfo, MinSizeRel)")

# Set Hermes paths - everything in build directory
set(HERMES_SRC "${CMAKE_BINARY_DIR}/hermes-src")
set(HERMES_BUILD "${CMAKE_BINARY_DIR}/hermes")

message(STATUS "Hermes git URL: ${HERMES_GIT_URL}")
message(STATUS "Hermes git tag: ${HERMES_GIT_TAG}")
message(STATUS "Hermes build type: ${HERMES_BUILD_TYPE}")

# Configure Hermes build options
set(HERMES_CMAKE_ARGS
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_BUILD_TYPE=${HERMES_BUILD_TYPE}
-DHERMES_ENABLE_TEST_SUITE=OFF
-DHERMES_BUILD_APPLE_FRAMEWORK=OFF
-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}
Expand Down
1 change: 1 addition & 0 deletions cmake/hermes.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function(hermes_compile_native)
$<$<CONFIG:Debug>:-g3>
--exported-unit=${ARG_UNIT_NAME}
-Xes6-block-scoping
-Xline-directives
)
if(ARG_FLAGS)
list(APPEND COMPILER_FLAGS ${ARG_FLAGS})
Expand Down
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
add_subdirectory(hello)
add_subdirectory(dynamic-windows)
add_subdirectory(showcase)
add_subdirectory(custom-widget)
7 changes: 7 additions & 0 deletions examples/custom-widget/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Custom Widget Example

add_react_imgui_app(
TARGET custom-widget
ENTRY_POINT index.jsx
SOURCES custom-widget.cpp
)
49 changes: 49 additions & 0 deletions examples/custom-widget/app.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React, { useState } from "react";

const ACTIONS = ["Cut", "Copy", "Paste", "Delete", "Save"];

export function App() {
const [menuOpen, setMenuOpen] = useState(false);
const [selectedAction, setSelectedAction] = useState(null);

const handleItemClick = (index) => {
setSelectedAction(ACTIONS[index]);
setMenuOpen(false);
};

return (
<window title="Custom Widget Example" defaultWidth={600} defaultHeight={400}>
<text>This example demonstrates creating custom ImGui widgets.</text>
<text>The RadialMenu is a custom widget built using ImGui's draw list API.</text>
<separator />

<text>Click the button to open the radial menu:</text>
<button onClick={() => setMenuOpen(!menuOpen)}>
{menuOpen ? "Close Menu" : "Open Radial Menu"}
</button>

{menuOpen && (
<>
<separator />
<radialmenu
radius={80}
items={ACTIONS}
onItemClick={handleItemClick}
centerText="Actions"
/>
<separator />
</>
)}

{selectedAction && (
<>
<text color="#00FF88">Selected action: {selectedAction}</text>
<separator />
</>
)}

<text color="#888888">Hover over menu sectors to highlight them.</text>
<text color="#888888">Click a sector to select an action.</text>
</window>
);
}
5 changes: 5 additions & 0 deletions examples/custom-widget/custom-widget.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Custom Widget Example - Demonstrates custom ImGui widget creation
// This example shows how to create custom widgets using ImGui's draw list API

#define PROVIDE_IMGUI_MAIN
#include "imgui-runtime.h"
19 changes: 19 additions & 0 deletions examples/custom-widget/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";
import { createRoot, render } from "react-imgui-reconciler/reconciler.js";
import { App } from "./app.jsx";

// Create React root with fiber root and container
const root = createRoot();

// Expose to typed unit via global
globalThis.reactApp = {
rootChildren: [],

// Render the app
render() {
render(React.createElement(App), root);
}
};

// Initial render
globalThis.reactApp.render();
Loading
Loading