imxc 0.5.4 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/compile.d.ts +8 -0
- package/dist/compile.js +285 -25
- package/dist/components.js +597 -45
- package/dist/diagnostics.js +3 -2
- package/dist/emitter.d.ts +6 -3
- package/dist/emitter.js +1454 -322
- package/dist/index.js +34 -7
- package/dist/init.d.ts +7 -1
- package/dist/init.js +43 -455
- package/dist/ir.d.ts +437 -5
- package/dist/lowering.d.ts +4 -3
- package/dist/lowering.js +770 -57
- package/dist/parser.d.ts +1 -0
- package/dist/templates/async.d.ts +1 -0
- package/dist/templates/async.js +228 -0
- package/dist/templates/custom.d.ts +15 -0
- package/dist/templates/custom.js +945 -0
- package/dist/templates/filedialog.d.ts +1 -0
- package/dist/templates/filedialog.js +216 -0
- package/dist/templates/hotreload.d.ts +1 -0
- package/dist/templates/hotreload.js +400 -0
- package/dist/templates/index.d.ts +16 -0
- package/dist/templates/index.js +553 -0
- package/dist/templates/minimal.d.ts +1 -0
- package/dist/templates/minimal.js +165 -0
- package/dist/templates/networking.d.ts +1 -0
- package/dist/templates/networking.js +244 -0
- package/dist/templates/persistence.d.ts +1 -0
- package/dist/templates/persistence.js +238 -0
- package/dist/validator.d.ts +1 -0
- package/dist/validator.js +51 -22
- package/dist/watch.d.ts +2 -1
- package/dist/watch.js +21 -4
- package/package.json +2 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { registerTemplate, buildImxDts, TSCONFIG, GITIGNORE, cmakeTemplate } from './index.js';
|
|
4
|
+
const APPSTATE_INTERFACE = `interface AppState {
|
|
5
|
+
filePath: string;
|
|
6
|
+
message: string;
|
|
7
|
+
onOpen: () => void;
|
|
8
|
+
onSave: () => void;
|
|
9
|
+
}`;
|
|
10
|
+
const APPSTATE_H = `#pragma once
|
|
11
|
+
#include <string>
|
|
12
|
+
#include <functional>
|
|
13
|
+
|
|
14
|
+
struct AppState {
|
|
15
|
+
std::string filePath = "";
|
|
16
|
+
std::string message = "";
|
|
17
|
+
std::function<void()> onOpen;
|
|
18
|
+
std::function<void()> onSave;
|
|
19
|
+
};
|
|
20
|
+
`;
|
|
21
|
+
const MAIN_CPP = `#include <imx/runtime.h>
|
|
22
|
+
#include <imx/renderer.h>
|
|
23
|
+
#include <imx/pfd.h>
|
|
24
|
+
|
|
25
|
+
#include <imgui.h>
|
|
26
|
+
#include <imgui_impl_glfw.h>
|
|
27
|
+
#include <imgui_impl_opengl3.h>
|
|
28
|
+
#include <GLFW/glfw3.h>
|
|
29
|
+
|
|
30
|
+
#include "AppState.h"
|
|
31
|
+
|
|
32
|
+
struct App {
|
|
33
|
+
GLFWwindow* window = nullptr;
|
|
34
|
+
ImGuiIO* io = nullptr;
|
|
35
|
+
imx::Runtime runtime;
|
|
36
|
+
AppState state;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
static void render_frame(App& app) {
|
|
40
|
+
glfwMakeContextCurrent(app.window);
|
|
41
|
+
if (glfwGetWindowAttrib(app.window, GLFW_ICONIFIED) != 0) return;
|
|
42
|
+
|
|
43
|
+
int fb_w = 0, fb_h = 0;
|
|
44
|
+
glfwGetFramebufferSize(app.window, &fb_w, &fb_h);
|
|
45
|
+
if (fb_w <= 0 || fb_h <= 0) return;
|
|
46
|
+
|
|
47
|
+
ImGui_ImplOpenGL3_NewFrame();
|
|
48
|
+
ImGui_ImplGlfw_NewFrame();
|
|
49
|
+
ImGui::NewFrame();
|
|
50
|
+
|
|
51
|
+
imx::render_root(app.runtime, app.state);
|
|
52
|
+
|
|
53
|
+
ImGui::Render();
|
|
54
|
+
glViewport(0, 0, fb_w, fb_h);
|
|
55
|
+
glClearColor(0.12F, 0.12F, 0.15F, 1.0F);
|
|
56
|
+
glClear(GL_COLOR_BUFFER_BIT);
|
|
57
|
+
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
|
58
|
+
|
|
59
|
+
if ((app.io->ConfigFlags & ImGuiConfigFlags_ViewportsEnable) != 0) {
|
|
60
|
+
ImGui::UpdatePlatformWindows();
|
|
61
|
+
ImGui::RenderPlatformWindowsDefault();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
glfwMakeContextCurrent(app.window);
|
|
65
|
+
glfwSwapBuffers(app.window);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
static void window_size_callback(GLFWwindow* window, int, int) {
|
|
69
|
+
auto* app = static_cast<App*>(glfwGetWindowUserPointer(window));
|
|
70
|
+
if (app) render_frame(*app);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
int main() {
|
|
74
|
+
if (glfwInit() == 0) return 1;
|
|
75
|
+
|
|
76
|
+
const char* glsl_version = "#version 150";
|
|
77
|
+
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
|
78
|
+
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
|
79
|
+
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
|
80
|
+
#ifdef __APPLE__
|
|
81
|
+
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
|
82
|
+
#endif
|
|
83
|
+
|
|
84
|
+
GLFWwindow* window = glfwCreateWindow(800, 600, "APP_NAME", nullptr, nullptr);
|
|
85
|
+
if (!window) { glfwTerminate(); return 1; }
|
|
86
|
+
glfwMakeContextCurrent(window);
|
|
87
|
+
glfwSwapInterval(1);
|
|
88
|
+
|
|
89
|
+
IMGUI_CHECKVERSION();
|
|
90
|
+
ImGui::CreateContext();
|
|
91
|
+
ImGuiIO& io = ImGui::GetIO();
|
|
92
|
+
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
|
93
|
+
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
|
|
94
|
+
|
|
95
|
+
ImGui::StyleColorsDark();
|
|
96
|
+
ImGuiStyle& style = ImGui::GetStyle();
|
|
97
|
+
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
|
|
98
|
+
style.WindowRounding = 0.0F;
|
|
99
|
+
style.Colors[ImGuiCol_WindowBg].w = 1.0F;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
|
103
|
+
ImGui_ImplOpenGL3_Init(glsl_version);
|
|
104
|
+
|
|
105
|
+
App app;
|
|
106
|
+
app.window = window;
|
|
107
|
+
app.io = &io;
|
|
108
|
+
glfwSetWindowUserPointer(window, &app);
|
|
109
|
+
glfwSetWindowSizeCallback(window, window_size_callback);
|
|
110
|
+
|
|
111
|
+
app.state.onOpen = [&]() {
|
|
112
|
+
auto result = pfd::open_file("Open File").result();
|
|
113
|
+
if (!result.empty()) {
|
|
114
|
+
app.state.filePath = result[0];
|
|
115
|
+
app.state.message = "Opened: " + result[0];
|
|
116
|
+
app.runtime.request_frame();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
app.state.onSave = [&]() {
|
|
121
|
+
auto result = pfd::save_file("Save File").result();
|
|
122
|
+
if (!result.empty()) {
|
|
123
|
+
app.state.filePath = result;
|
|
124
|
+
app.state.message = "Save to: " + result;
|
|
125
|
+
app.runtime.request_frame();
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// GLFW file drop callback
|
|
130
|
+
glfwSetDropCallback(window, [](GLFWwindow* w, int count, const char** paths) {
|
|
131
|
+
auto* a = static_cast<App*>(glfwGetWindowUserPointer(w));
|
|
132
|
+
if (a && count > 0) {
|
|
133
|
+
a->state.filePath = paths[0];
|
|
134
|
+
a->state.message = "Dropped: " + std::string(paths[0]);
|
|
135
|
+
a->runtime.request_frame();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
while (glfwWindowShouldClose(window) == 0) {
|
|
140
|
+
if (app.runtime.needs_frame()) {
|
|
141
|
+
glfwPollEvents();
|
|
142
|
+
} else {
|
|
143
|
+
glfwWaitEventsTimeout(0.1);
|
|
144
|
+
}
|
|
145
|
+
render_frame(app);
|
|
146
|
+
app.runtime.frame_rendered(ImGui::IsAnyItemActive());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
ImGui_ImplOpenGL3_Shutdown();
|
|
150
|
+
ImGui_ImplGlfw_Shutdown();
|
|
151
|
+
ImGui::DestroyContext();
|
|
152
|
+
glfwDestroyWindow(window);
|
|
153
|
+
glfwTerminate();
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
#ifdef _WIN32
|
|
158
|
+
#include <windows.h>
|
|
159
|
+
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { return main(); }
|
|
160
|
+
#endif
|
|
161
|
+
`;
|
|
162
|
+
const APP_TSX = `export default function App(props: AppState) {
|
|
163
|
+
return (
|
|
164
|
+
<DockSpace>
|
|
165
|
+
<Window title="File Dialog Demo">
|
|
166
|
+
<Column gap={8}>
|
|
167
|
+
<Text>Native File Dialogs + Drag & Drop</Text>
|
|
168
|
+
<Separator />
|
|
169
|
+
<Row gap={8}>
|
|
170
|
+
<Button title="Open File" onPress={props.onOpen} />
|
|
171
|
+
<Button title="Save File" onPress={props.onSave} />
|
|
172
|
+
</Row>
|
|
173
|
+
<Text>Drag & drop a file onto this window</Text>
|
|
174
|
+
{props.message !== "" && <Text color={[0, 1, 0, 1]}>{props.message}</Text>}
|
|
175
|
+
{props.filePath !== "" && <Text>Path: {props.filePath}</Text>}
|
|
176
|
+
</Column>
|
|
177
|
+
</Window>
|
|
178
|
+
</DockSpace>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
`;
|
|
182
|
+
function generate(projectDir, projectName) {
|
|
183
|
+
const srcDir = path.join(projectDir, 'src');
|
|
184
|
+
if (fs.existsSync(path.join(srcDir, 'App.tsx'))) {
|
|
185
|
+
console.error(`Error: ${srcDir}/App.tsx already exists. Aborting.`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
189
|
+
const publicDir = path.join(projectDir, 'public');
|
|
190
|
+
fs.mkdirSync(publicDir, { recursive: true });
|
|
191
|
+
// Write files
|
|
192
|
+
fs.writeFileSync(path.join(srcDir, 'main.cpp'), MAIN_CPP.replace('APP_NAME', projectName));
|
|
193
|
+
fs.writeFileSync(path.join(srcDir, 'AppState.h'), APPSTATE_H);
|
|
194
|
+
fs.writeFileSync(path.join(srcDir, 'App.tsx'), APP_TSX);
|
|
195
|
+
fs.writeFileSync(path.join(srcDir, 'imx.d.ts'), buildImxDts(APPSTATE_INTERFACE));
|
|
196
|
+
fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), TSCONFIG);
|
|
197
|
+
fs.writeFileSync(path.join(projectDir, 'CMakeLists.txt'), cmakeTemplate(projectName, 'https://github.com/bgocumlu/imx.git'));
|
|
198
|
+
fs.writeFileSync(path.join(projectDir, '.gitignore'), GITIGNORE);
|
|
199
|
+
console.log(`imxc: initialized project "${projectName}" with template "filedialog"`);
|
|
200
|
+
console.log('');
|
|
201
|
+
console.log(' Created:');
|
|
202
|
+
console.log(` src/main.cpp — app shell with pfd + GLFW drop callback`);
|
|
203
|
+
console.log(` src/AppState.h — C++ state struct with filePath/message fields`);
|
|
204
|
+
console.log(` src/App.tsx — your root component`);
|
|
205
|
+
console.log(` src/imx.d.ts — type definitions for IDE support`);
|
|
206
|
+
console.log(` tsconfig.json — TypeScript config`);
|
|
207
|
+
console.log(` CMakeLists.txt — build config with FetchContent`);
|
|
208
|
+
console.log(` .gitignore — ignores build/, node_modules/, *.ini`);
|
|
209
|
+
console.log(` public/ — static assets (copied to exe directory)`);
|
|
210
|
+
console.log('');
|
|
211
|
+
console.log(' Next steps:');
|
|
212
|
+
console.log(` cd ${projectName}`);
|
|
213
|
+
console.log(` cmake -B build`);
|
|
214
|
+
console.log(` cmake --build build`);
|
|
215
|
+
}
|
|
216
|
+
registerTemplate({ name: 'filedialog', description: 'Native file dialogs + drag & drop', generate });
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { registerTemplate, buildImxDts, TSCONFIG, GITIGNORE } from './index.js';
|
|
4
|
+
const APPSTATE_INTERFACE = `interface AppState {
|
|
5
|
+
count: number;
|
|
6
|
+
speed: number;
|
|
7
|
+
watchCmd: string;
|
|
8
|
+
onIncrement: () => void;
|
|
9
|
+
onCopyCmd: () => void;
|
|
10
|
+
}`;
|
|
11
|
+
const APPSTATE_H = `#pragma once
|
|
12
|
+
#include <string>
|
|
13
|
+
#include <functional>
|
|
14
|
+
|
|
15
|
+
struct AppState {
|
|
16
|
+
int count = 0;
|
|
17
|
+
float speed = 5.0F;
|
|
18
|
+
std::string watchCmd;
|
|
19
|
+
std::function<void()> onIncrement;
|
|
20
|
+
std::function<void()> onCopyCmd;
|
|
21
|
+
};
|
|
22
|
+
`;
|
|
23
|
+
const HOTRELOAD_H = `#pragma once
|
|
24
|
+
#include <string>
|
|
25
|
+
#include <filesystem>
|
|
26
|
+
#include <iostream>
|
|
27
|
+
#include <imx/runtime.h>
|
|
28
|
+
#include <imgui.h>
|
|
29
|
+
#include "AppState.h"
|
|
30
|
+
|
|
31
|
+
#ifdef _WIN32
|
|
32
|
+
#ifndef WIN32_LEAN_AND_MEAN
|
|
33
|
+
#define WIN32_LEAN_AND_MEAN
|
|
34
|
+
#endif
|
|
35
|
+
#include <windows.h>
|
|
36
|
+
#else
|
|
37
|
+
#include <dlfcn.h>
|
|
38
|
+
#include <unistd.h>
|
|
39
|
+
#endif
|
|
40
|
+
|
|
41
|
+
struct HotModule {
|
|
42
|
+
using RenderFn = void(*)(imx::Runtime&, AppState&, ImGuiContext*);
|
|
43
|
+
|
|
44
|
+
#ifdef _WIN32
|
|
45
|
+
HMODULE handle = nullptr;
|
|
46
|
+
#else
|
|
47
|
+
void* handle = nullptr;
|
|
48
|
+
#endif
|
|
49
|
+
RenderFn render = nullptr;
|
|
50
|
+
std::filesystem::file_time_type last_write{};
|
|
51
|
+
std::string path;
|
|
52
|
+
|
|
53
|
+
bool load(const std::string& lib_path) {
|
|
54
|
+
path = lib_path;
|
|
55
|
+
if (!std::filesystem::exists(path)) {
|
|
56
|
+
std::cerr << "hotreload: " << path << " not found\\n";
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
last_write = std::filesystem::last_write_time(path);
|
|
60
|
+
|
|
61
|
+
#ifdef _WIN32
|
|
62
|
+
// Copy DLL to avoid locking the original (allows rebuild while loaded)
|
|
63
|
+
std::string copy_path = path + ".live";
|
|
64
|
+
std::filesystem::copy_file(path, copy_path, std::filesystem::copy_options::overwrite_existing);
|
|
65
|
+
handle = LoadLibraryA(copy_path.c_str());
|
|
66
|
+
if (!handle) {
|
|
67
|
+
std::cerr << "hotreload: LoadLibrary failed\\n";
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
render = reinterpret_cast<RenderFn>(GetProcAddress(handle, "imx_render"));
|
|
71
|
+
#else
|
|
72
|
+
handle = dlopen(path.c_str(), RTLD_NOW);
|
|
73
|
+
if (!handle) {
|
|
74
|
+
std::cerr << "hotreload: dlopen failed: " << dlerror() << "\\n";
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
render = reinterpret_cast<RenderFn>(dlsym(handle, "imx_render"));
|
|
78
|
+
#endif
|
|
79
|
+
if (!render) {
|
|
80
|
+
std::cerr << "hotreload: imx_render symbol not found\\n";
|
|
81
|
+
unload();
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
std::cout << "hotreload: loaded " << path << "\\n";
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
void unload() {
|
|
89
|
+
if (!handle) return;
|
|
90
|
+
#ifdef _WIN32
|
|
91
|
+
FreeLibrary(handle);
|
|
92
|
+
#else
|
|
93
|
+
dlclose(handle);
|
|
94
|
+
#endif
|
|
95
|
+
handle = nullptr;
|
|
96
|
+
render = nullptr;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
bool check_reload() {
|
|
100
|
+
if (path.empty() || !std::filesystem::exists(path)) return false;
|
|
101
|
+
auto current = std::filesystem::last_write_time(path);
|
|
102
|
+
if (current == last_write) return false;
|
|
103
|
+
std::cout << "hotreload: change detected, reloading...\\n";
|
|
104
|
+
unload();
|
|
105
|
+
// Small delay to ensure file write is complete
|
|
106
|
+
#ifdef _WIN32
|
|
107
|
+
Sleep(100);
|
|
108
|
+
#else
|
|
109
|
+
usleep(100000);
|
|
110
|
+
#endif
|
|
111
|
+
if (load(path)) {
|
|
112
|
+
std::cout << "hotreload: reload successful\\n";
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
std::cerr << "hotreload: reload failed\\n";
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
~HotModule() { unload(); }
|
|
120
|
+
};
|
|
121
|
+
`;
|
|
122
|
+
const UI_ENTRY_CPP = `#include <imx/runtime.h>
|
|
123
|
+
#include <imgui.h>
|
|
124
|
+
#include "AppState.h"
|
|
125
|
+
|
|
126
|
+
// Declared in app_root.gen.cpp (generated by imxc)
|
|
127
|
+
namespace imx {
|
|
128
|
+
template <> void render_root<AppState>(imx::Runtime& runtime, AppState& state);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#ifdef _WIN32
|
|
132
|
+
#define EXPORT __declspec(dllexport)
|
|
133
|
+
#else
|
|
134
|
+
#define EXPORT __attribute__((visibility("default")))
|
|
135
|
+
#endif
|
|
136
|
+
|
|
137
|
+
// ImGuiContext* must be passed from host — DLL has its own static ImGui globals
|
|
138
|
+
extern "C" EXPORT void imx_render(imx::Runtime& runtime, AppState& state, ImGuiContext* ctx) {
|
|
139
|
+
ImGui::SetCurrentContext(ctx);
|
|
140
|
+
imx::render_root(runtime, state);
|
|
141
|
+
}
|
|
142
|
+
`;
|
|
143
|
+
const MAIN_CPP = `#include <imx/runtime.h>
|
|
144
|
+
#include <imx/renderer.h>
|
|
145
|
+
|
|
146
|
+
#include <imgui.h>
|
|
147
|
+
#include <imgui_impl_glfw.h>
|
|
148
|
+
#include <imgui_impl_opengl3.h>
|
|
149
|
+
#include <GLFW/glfw3.h>
|
|
150
|
+
|
|
151
|
+
#include "hotreload.h"
|
|
152
|
+
#include "AppState.h"
|
|
153
|
+
|
|
154
|
+
struct App {
|
|
155
|
+
GLFWwindow* window = nullptr;
|
|
156
|
+
ImGuiIO* io = nullptr;
|
|
157
|
+
imx::Runtime runtime;
|
|
158
|
+
AppState state;
|
|
159
|
+
HotModule module;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
static void render_frame(App& app) {
|
|
163
|
+
glfwMakeContextCurrent(app.window);
|
|
164
|
+
if (glfwGetWindowAttrib(app.window, GLFW_ICONIFIED) != 0) return;
|
|
165
|
+
|
|
166
|
+
int fb_w = 0, fb_h = 0;
|
|
167
|
+
glfwGetFramebufferSize(app.window, &fb_w, &fb_h);
|
|
168
|
+
if (fb_w <= 0 || fb_h <= 0) return;
|
|
169
|
+
|
|
170
|
+
ImGui_ImplOpenGL3_NewFrame();
|
|
171
|
+
ImGui_ImplGlfw_NewFrame();
|
|
172
|
+
ImGui::NewFrame();
|
|
173
|
+
|
|
174
|
+
app.module.check_reload();
|
|
175
|
+
if (app.module.render) {
|
|
176
|
+
app.module.render(app.runtime, app.state, ImGui::GetCurrentContext());
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
ImGui::Render();
|
|
180
|
+
glViewport(0, 0, fb_w, fb_h);
|
|
181
|
+
glClearColor(0.12F, 0.12F, 0.15F, 1.0F);
|
|
182
|
+
glClear(GL_COLOR_BUFFER_BIT);
|
|
183
|
+
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
|
|
184
|
+
|
|
185
|
+
if ((app.io->ConfigFlags & ImGuiConfigFlags_ViewportsEnable) != 0) {
|
|
186
|
+
ImGui::UpdatePlatformWindows();
|
|
187
|
+
ImGui::RenderPlatformWindowsDefault();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
glfwMakeContextCurrent(app.window);
|
|
191
|
+
glfwSwapBuffers(app.window);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
static void window_size_callback(GLFWwindow* window, int, int) {
|
|
195
|
+
auto* app = static_cast<App*>(glfwGetWindowUserPointer(window));
|
|
196
|
+
if (app) render_frame(*app);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
int main() {
|
|
200
|
+
if (glfwInit() == 0) return 1;
|
|
201
|
+
|
|
202
|
+
const char* glsl_version = "#version 150";
|
|
203
|
+
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
|
204
|
+
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
|
205
|
+
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
|
206
|
+
#ifdef __APPLE__
|
|
207
|
+
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
|
|
208
|
+
#endif
|
|
209
|
+
|
|
210
|
+
GLFWwindow* window = glfwCreateWindow(1000, 700, "APP_NAME", nullptr, nullptr);
|
|
211
|
+
if (!window) { glfwTerminate(); return 1; }
|
|
212
|
+
glfwMakeContextCurrent(window);
|
|
213
|
+
glfwSwapInterval(1);
|
|
214
|
+
|
|
215
|
+
IMGUI_CHECKVERSION();
|
|
216
|
+
ImGui::CreateContext();
|
|
217
|
+
ImGuiIO& io = ImGui::GetIO();
|
|
218
|
+
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
|
219
|
+
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
|
|
220
|
+
|
|
221
|
+
ImGui::StyleColorsDark();
|
|
222
|
+
ImGuiStyle& style = ImGui::GetStyle();
|
|
223
|
+
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
|
|
224
|
+
style.WindowRounding = 0.0F;
|
|
225
|
+
style.Colors[ImGuiCol_WindowBg].w = 1.0F;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
ImGui_ImplGlfw_InitForOpenGL(window, true);
|
|
229
|
+
ImGui_ImplOpenGL3_Init(glsl_version);
|
|
230
|
+
|
|
231
|
+
App app;
|
|
232
|
+
app.window = window;
|
|
233
|
+
app.io = &io;
|
|
234
|
+
glfwSetWindowUserPointer(window, &app);
|
|
235
|
+
glfwSetWindowSizeCallback(window, window_size_callback);
|
|
236
|
+
|
|
237
|
+
app.state.onIncrement = [&]() { app.state.count++; };
|
|
238
|
+
app.state.watchCmd = "npx imxc watch src -o build/generated --build \\"cmake --build build --target imx_ui\\"";
|
|
239
|
+
app.state.onCopyCmd = [&]() { imx::clipboard_set(app.state.watchCmd.c_str()); };
|
|
240
|
+
|
|
241
|
+
#ifdef _WIN32
|
|
242
|
+
app.module.load("imx_ui.dll");
|
|
243
|
+
#elif defined(__APPLE__)
|
|
244
|
+
app.module.load("libimx_ui.dylib");
|
|
245
|
+
#else
|
|
246
|
+
app.module.load("libimx_ui.so");
|
|
247
|
+
#endif
|
|
248
|
+
|
|
249
|
+
while (glfwWindowShouldClose(window) == 0) {
|
|
250
|
+
if (app.runtime.needs_frame()) {
|
|
251
|
+
glfwPollEvents();
|
|
252
|
+
} else {
|
|
253
|
+
glfwWaitEventsTimeout(0.1);
|
|
254
|
+
}
|
|
255
|
+
render_frame(app);
|
|
256
|
+
app.runtime.frame_rendered(ImGui::IsAnyItemActive());
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
app.module.unload();
|
|
260
|
+
|
|
261
|
+
ImGui_ImplOpenGL3_Shutdown();
|
|
262
|
+
ImGui_ImplGlfw_Shutdown();
|
|
263
|
+
ImGui::DestroyContext();
|
|
264
|
+
glfwDestroyWindow(window);
|
|
265
|
+
glfwTerminate();
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
#ifdef _WIN32
|
|
270
|
+
#include <windows.h>
|
|
271
|
+
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { return main(); }
|
|
272
|
+
#endif
|
|
273
|
+
`;
|
|
274
|
+
const APP_TSX = `export default function App(props: AppState) {
|
|
275
|
+
const [showAbout, setShowAbout] = useState(false);
|
|
276
|
+
|
|
277
|
+
return (
|
|
278
|
+
<DockSpace>
|
|
279
|
+
<Window title="Controls">
|
|
280
|
+
<Column gap={8}>
|
|
281
|
+
<Text>Count: {props.count}</Text>
|
|
282
|
+
<Button title="Increment" onPress={props.onIncrement} />
|
|
283
|
+
<SliderFloat label="Speed" value={props.speed} min={0} max={100} />
|
|
284
|
+
<Separator />
|
|
285
|
+
<Button title="About" onPress={() => setShowAbout(!showAbout)} />
|
|
286
|
+
</Column>
|
|
287
|
+
</Window>
|
|
288
|
+
<Window title="Hot Reload">
|
|
289
|
+
<Column gap={8}>
|
|
290
|
+
<Text>Run this in a second terminal to enable live reload:</Text>
|
|
291
|
+
<Text wrapped={true}>{props.watchCmd}</Text>
|
|
292
|
+
<Button title="Copy" onPress={props.onCopyCmd} />
|
|
293
|
+
</Column>
|
|
294
|
+
</Window>
|
|
295
|
+
{showAbout && <Window title="About">
|
|
296
|
+
<Text>Built with IMX (hot-reloadable!)</Text>
|
|
297
|
+
<Button title="Close" onPress={() => setShowAbout(false)} />
|
|
298
|
+
</Window>}
|
|
299
|
+
</DockSpace>
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
`;
|
|
303
|
+
function cmakeHotreload(projectName) {
|
|
304
|
+
return `cmake_minimum_required(VERSION 3.25)
|
|
305
|
+
project(${projectName} LANGUAGES CXX)
|
|
306
|
+
|
|
307
|
+
set(CMAKE_CXX_STANDARD 20)
|
|
308
|
+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|
309
|
+
|
|
310
|
+
include(FetchContent)
|
|
311
|
+
set(FETCHCONTENT_QUIET OFF)
|
|
312
|
+
|
|
313
|
+
FetchContent_Declare(
|
|
314
|
+
imx
|
|
315
|
+
GIT_REPOSITORY https://github.com/bgocumlu/imx.git
|
|
316
|
+
GIT_TAG v0.6.1
|
|
317
|
+
GIT_SHALLOW TRUE
|
|
318
|
+
GIT_PROGRESS TRUE
|
|
319
|
+
)
|
|
320
|
+
message(STATUS "Fetching IMX (includes ImGui + GLFW)...")
|
|
321
|
+
FetchContent_MakeAvailable(imx)
|
|
322
|
+
|
|
323
|
+
include(ImxCompile)
|
|
324
|
+
|
|
325
|
+
imx_compile_tsx(GENERATED
|
|
326
|
+
SOURCES src/App.tsx
|
|
327
|
+
OUTPUT_DIR \${CMAKE_BINARY_DIR}/generated
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# UI shared library (hot-reloadable)
|
|
331
|
+
add_library(imx_ui SHARED
|
|
332
|
+
src/ui_entry.cpp
|
|
333
|
+
\${GENERATED}
|
|
334
|
+
)
|
|
335
|
+
target_link_libraries(imx_ui PRIVATE imx::renderer)
|
|
336
|
+
target_include_directories(imx_ui PRIVATE \${CMAKE_BINARY_DIR}/generated \${CMAKE_CURRENT_SOURCE_DIR}/src)
|
|
337
|
+
|
|
338
|
+
# Host executable (loads UI module dynamically)
|
|
339
|
+
add_executable(${projectName}
|
|
340
|
+
src/main.cpp
|
|
341
|
+
)
|
|
342
|
+
set_target_properties(${projectName} PROPERTIES WIN32_EXECUTABLE $<CONFIG:Release>)
|
|
343
|
+
target_link_libraries(${projectName} PRIVATE imx::renderer \${CMAKE_DL_LIBS})
|
|
344
|
+
target_include_directories(${projectName} PRIVATE \${CMAKE_CURRENT_SOURCE_DIR}/src)
|
|
345
|
+
|
|
346
|
+
# Copy DLL/SO next to host exe after build
|
|
347
|
+
add_custom_command(TARGET imx_ui POST_BUILD
|
|
348
|
+
COMMAND \${CMAKE_COMMAND} -E copy $<TARGET_FILE:imx_ui> $<TARGET_FILE_DIR:${projectName}>
|
|
349
|
+
COMMENT "Copying UI module to host directory"
|
|
350
|
+
)
|
|
351
|
+
add_dependencies(${projectName} imx_ui)
|
|
352
|
+
|
|
353
|
+
# Copy public/ assets to output directory
|
|
354
|
+
add_custom_command(TARGET ${projectName} POST_BUILD
|
|
355
|
+
COMMAND \${CMAKE_COMMAND} -E copy_directory
|
|
356
|
+
\${CMAKE_CURRENT_SOURCE_DIR}/public
|
|
357
|
+
$<TARGET_FILE_DIR:${projectName}>
|
|
358
|
+
COMMENT "Copying public/ assets"
|
|
359
|
+
)
|
|
360
|
+
`;
|
|
361
|
+
}
|
|
362
|
+
function generate(projectDir, projectName) {
|
|
363
|
+
const srcDir = path.join(projectDir, 'src');
|
|
364
|
+
if (fs.existsSync(path.join(srcDir, 'App.tsx'))) {
|
|
365
|
+
console.error(`Error: ${srcDir}/App.tsx already exists. Aborting.`);
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
fs.mkdirSync(srcDir, { recursive: true });
|
|
369
|
+
const publicDir = path.join(projectDir, 'public');
|
|
370
|
+
fs.mkdirSync(publicDir, { recursive: true });
|
|
371
|
+
// Write files
|
|
372
|
+
fs.writeFileSync(path.join(srcDir, 'main.cpp'), MAIN_CPP.replaceAll('APP_NAME', projectName));
|
|
373
|
+
fs.writeFileSync(path.join(srcDir, 'AppState.h'), APPSTATE_H);
|
|
374
|
+
fs.writeFileSync(path.join(srcDir, 'hotreload.h'), HOTRELOAD_H);
|
|
375
|
+
fs.writeFileSync(path.join(srcDir, 'ui_entry.cpp'), UI_ENTRY_CPP);
|
|
376
|
+
fs.writeFileSync(path.join(srcDir, 'App.tsx'), APP_TSX);
|
|
377
|
+
fs.writeFileSync(path.join(srcDir, 'imx.d.ts'), buildImxDts(APPSTATE_INTERFACE));
|
|
378
|
+
fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), TSCONFIG);
|
|
379
|
+
fs.writeFileSync(path.join(projectDir, 'CMakeLists.txt'), cmakeHotreload(projectName));
|
|
380
|
+
fs.writeFileSync(path.join(projectDir, '.gitignore'), GITIGNORE);
|
|
381
|
+
console.log(`imxc: initialized project "${projectName}" with template "hotreload"`);
|
|
382
|
+
console.log('');
|
|
383
|
+
console.log(' Created:');
|
|
384
|
+
console.log(` src/main.cpp — host exe with DLL hot reload loop`);
|
|
385
|
+
console.log(` src/AppState.h — C++ state struct (lives in host, survives reloads)`);
|
|
386
|
+
console.log(` src/hotreload.h — cross-platform DLL/SO loader`);
|
|
387
|
+
console.log(` src/ui_entry.cpp — DLL export wrapper for imx_render()`);
|
|
388
|
+
console.log(` src/App.tsx — your root component (compiled into DLL)`);
|
|
389
|
+
console.log(` src/imx.d.ts — type definitions for IDE support`);
|
|
390
|
+
console.log(` tsconfig.json — TypeScript config`);
|
|
391
|
+
console.log(` CMakeLists.txt — build config with host exe + UI shared lib`);
|
|
392
|
+
console.log(` .gitignore — ignores build/, node_modules/, *.ini`);
|
|
393
|
+
console.log(` public/ — static assets (copied to exe directory)`);
|
|
394
|
+
console.log('');
|
|
395
|
+
console.log(' Next steps:');
|
|
396
|
+
console.log(` cd ${projectName}`);
|
|
397
|
+
console.log(` cmake -B build`);
|
|
398
|
+
console.log(` cmake --build build`);
|
|
399
|
+
}
|
|
400
|
+
registerTemplate({ name: 'hotreload', description: 'DLL hot reload for live UI iteration', generate });
|