imxc 0.5.4 → 0.6.0

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.
@@ -0,0 +1,165 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { registerTemplate, APP_TSX, buildImxDts, TSCONFIG, GITIGNORE, cmakeTemplate } from './index.js';
4
+ const APPSTATE_INTERFACE = `interface AppState {
5
+ count: number;
6
+ speed: number;
7
+ onIncrement: () => void;
8
+ }`;
9
+ const APPSTATE_H = `#pragma once
10
+ #include <functional>
11
+
12
+ struct AppState {
13
+ int count = 0;
14
+ float speed = 5.0f;
15
+ std::function<void()> onIncrement;
16
+ };
17
+ `;
18
+ const MAIN_CPP = `#include <imx/runtime.h>
19
+ #include <imx/renderer.h>
20
+
21
+ #include <imgui.h>
22
+ #include <imgui_impl_glfw.h>
23
+ #include <imgui_impl_opengl3.h>
24
+ #include <GLFW/glfw3.h>
25
+
26
+ #include "AppState.h"
27
+
28
+ struct App {
29
+ GLFWwindow* window = nullptr;
30
+ ImGuiIO* io = nullptr;
31
+ imx::Runtime runtime;
32
+ AppState state;
33
+ };
34
+
35
+ static void render_frame(App& app) {
36
+ glfwMakeContextCurrent(app.window);
37
+ if (glfwGetWindowAttrib(app.window, GLFW_ICONIFIED) != 0) return;
38
+
39
+ int fb_w = 0, fb_h = 0;
40
+ glfwGetFramebufferSize(app.window, &fb_w, &fb_h);
41
+ if (fb_w <= 0 || fb_h <= 0) return;
42
+
43
+ ImGui_ImplOpenGL3_NewFrame();
44
+ ImGui_ImplGlfw_NewFrame();
45
+ ImGui::NewFrame();
46
+
47
+ imx::render_root(app.runtime, app.state);
48
+
49
+ ImGui::Render();
50
+ glViewport(0, 0, fb_w, fb_h);
51
+ glClearColor(0.12F, 0.12F, 0.15F, 1.0F);
52
+ glClear(GL_COLOR_BUFFER_BIT);
53
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
54
+
55
+ if ((app.io->ConfigFlags & ImGuiConfigFlags_ViewportsEnable) != 0) {
56
+ ImGui::UpdatePlatformWindows();
57
+ ImGui::RenderPlatformWindowsDefault();
58
+ }
59
+
60
+ glfwMakeContextCurrent(app.window);
61
+ glfwSwapBuffers(app.window);
62
+ }
63
+
64
+ static void window_size_callback(GLFWwindow* window, int, int) {
65
+ auto* app = static_cast<App*>(glfwGetWindowUserPointer(window));
66
+ if (app) render_frame(*app);
67
+ }
68
+
69
+ int main() {
70
+ if (glfwInit() == 0) return 1;
71
+
72
+ const char* glsl_version = "#version 150";
73
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
74
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
75
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
76
+ #ifdef __APPLE__
77
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
78
+ #endif
79
+
80
+ GLFWwindow* window = glfwCreateWindow(800, 600, "APP_NAME", nullptr, nullptr);
81
+ if (!window) { glfwTerminate(); return 1; }
82
+ glfwMakeContextCurrent(window);
83
+ glfwSwapInterval(1);
84
+
85
+ IMGUI_CHECKVERSION();
86
+ ImGui::CreateContext();
87
+ ImGuiIO& io = ImGui::GetIO();
88
+ io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
89
+ io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
90
+
91
+ ImGui::StyleColorsDark();
92
+ ImGuiStyle& style = ImGui::GetStyle();
93
+ if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
94
+ style.WindowRounding = 0.0F;
95
+ style.Colors[ImGuiCol_WindowBg].w = 1.0F;
96
+ }
97
+
98
+ ImGui_ImplGlfw_InitForOpenGL(window, true);
99
+ ImGui_ImplOpenGL3_Init(glsl_version);
100
+
101
+ App app;
102
+ app.window = window;
103
+ app.io = &io;
104
+ glfwSetWindowUserPointer(window, &app);
105
+ glfwSetWindowSizeCallback(window, window_size_callback);
106
+ app.state.onIncrement = [&]() { app.state.count++; };
107
+
108
+ while (glfwWindowShouldClose(window) == 0) {
109
+ if (app.runtime.needs_frame()) {
110
+ glfwPollEvents();
111
+ } else {
112
+ glfwWaitEventsTimeout(0.1);
113
+ }
114
+ render_frame(app);
115
+ app.runtime.frame_rendered(ImGui::IsAnyItemActive());
116
+ }
117
+
118
+ ImGui_ImplOpenGL3_Shutdown();
119
+ ImGui_ImplGlfw_Shutdown();
120
+ ImGui::DestroyContext();
121
+ glfwDestroyWindow(window);
122
+ glfwTerminate();
123
+ return 0;
124
+ }
125
+
126
+ #ifdef _WIN32
127
+ #include <windows.h>
128
+ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { return main(); }
129
+ #endif
130
+ `;
131
+ function generate(projectDir, projectName) {
132
+ const srcDir = path.join(projectDir, 'src');
133
+ if (fs.existsSync(path.join(srcDir, 'App.tsx'))) {
134
+ console.error(`Error: ${srcDir}/App.tsx already exists. Aborting.`);
135
+ process.exit(1);
136
+ }
137
+ fs.mkdirSync(srcDir, { recursive: true });
138
+ const publicDir = path.join(projectDir, 'public');
139
+ fs.mkdirSync(publicDir, { recursive: true });
140
+ // Write files
141
+ fs.writeFileSync(path.join(srcDir, 'main.cpp'), MAIN_CPP.replace('APP_NAME', projectName));
142
+ fs.writeFileSync(path.join(srcDir, 'AppState.h'), APPSTATE_H);
143
+ fs.writeFileSync(path.join(srcDir, 'App.tsx'), APP_TSX);
144
+ fs.writeFileSync(path.join(srcDir, 'imx.d.ts'), buildImxDts(APPSTATE_INTERFACE));
145
+ fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), TSCONFIG);
146
+ fs.writeFileSync(path.join(projectDir, 'CMakeLists.txt'), cmakeTemplate(projectName, 'https://github.com/bgocumlu/imx.git'));
147
+ fs.writeFileSync(path.join(projectDir, '.gitignore'), GITIGNORE);
148
+ console.log(`imxc: initialized project "${projectName}" with template "minimal"`);
149
+ console.log('');
150
+ console.log(' Created:');
151
+ console.log(` src/main.cpp — app shell (GLFW + OpenGL + ImGui)`);
152
+ console.log(` src/AppState.h — C++ state struct (bound to TSX props)`);
153
+ console.log(` src/App.tsx — your root component`);
154
+ console.log(` src/imx.d.ts — type definitions for IDE support`);
155
+ console.log(` tsconfig.json — TypeScript config`);
156
+ console.log(` CMakeLists.txt — build config with FetchContent`);
157
+ console.log(` .gitignore — ignores build/, node_modules/, *.ini`);
158
+ console.log(` public/ — static assets (copied to exe directory)`);
159
+ console.log('');
160
+ console.log(' Next steps:');
161
+ console.log(` cd ${projectName}`);
162
+ console.log(` cmake -B build`);
163
+ console.log(` cmake --build build`);
164
+ }
165
+ registerTemplate({ name: 'minimal', description: 'Bare ImGui app with struct binding', generate });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,244 @@
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
+ url: string;
6
+ response: string;
7
+ loading: boolean;
8
+ onFetch: () => void;
9
+ }`;
10
+ const APPSTATE_H = `#pragma once
11
+ #include <string>
12
+ #include <functional>
13
+
14
+ struct AppState {
15
+ std::string url = "http://jsonplaceholder.typicode.com/todos/1";
16
+ std::string response = "";
17
+ bool loading = false;
18
+ std::function<void()> onFetch;
19
+ };
20
+ `;
21
+ const ASYNC_H = `#pragma once
22
+ #include <thread>
23
+ #include <functional>
24
+ #include <imx/runtime.h>
25
+
26
+ namespace imx {
27
+
28
+ // Runs \`work\` on a background thread, then calls \`on_done\` with the result.
29
+ // Calls request_frame() so the UI wakes up to display the result.
30
+ // Replace with a thread pool if you need to limit concurrency.
31
+ template<typename T>
32
+ void run_async(Runtime& runtime, std::function<T()> work, std::function<void(T)> on_done) {
33
+ std::thread([&runtime, work = std::move(work), on_done = std::move(on_done)]() {
34
+ T result = work();
35
+ on_done(std::move(result));
36
+ runtime.request_frame();
37
+ }).detach();
38
+ }
39
+
40
+ } // namespace imx
41
+ `;
42
+ const MAIN_CPP = `#include <imx/runtime.h>
43
+ #include <imx/renderer.h>
44
+
45
+ #include <imgui.h>
46
+ #include <imgui_impl_glfw.h>
47
+ #include <imgui_impl_opengl3.h>
48
+ #include <GLFW/glfw3.h>
49
+
50
+ #include <thread>
51
+ #include <imx/httplib.h>
52
+ #include "async.h"
53
+ #include "AppState.h"
54
+
55
+ struct App {
56
+ GLFWwindow* window = nullptr;
57
+ ImGuiIO* io = nullptr;
58
+ imx::Runtime runtime;
59
+ AppState state;
60
+ };
61
+
62
+ static void render_frame(App& app) {
63
+ glfwMakeContextCurrent(app.window);
64
+ if (glfwGetWindowAttrib(app.window, GLFW_ICONIFIED) != 0) return;
65
+
66
+ int fb_w = 0, fb_h = 0;
67
+ glfwGetFramebufferSize(app.window, &fb_w, &fb_h);
68
+ if (fb_w <= 0 || fb_h <= 0) return;
69
+
70
+ ImGui_ImplOpenGL3_NewFrame();
71
+ ImGui_ImplGlfw_NewFrame();
72
+ ImGui::NewFrame();
73
+
74
+ imx::render_root(app.runtime, app.state);
75
+
76
+ ImGui::Render();
77
+ glViewport(0, 0, fb_w, fb_h);
78
+ glClearColor(0.12F, 0.12F, 0.15F, 1.0F);
79
+ glClear(GL_COLOR_BUFFER_BIT);
80
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
81
+
82
+ if ((app.io->ConfigFlags & ImGuiConfigFlags_ViewportsEnable) != 0) {
83
+ ImGui::UpdatePlatformWindows();
84
+ ImGui::RenderPlatformWindowsDefault();
85
+ }
86
+
87
+ glfwMakeContextCurrent(app.window);
88
+ glfwSwapBuffers(app.window);
89
+ }
90
+
91
+ static void window_size_callback(GLFWwindow* window, int, int) {
92
+ auto* app = static_cast<App*>(glfwGetWindowUserPointer(window));
93
+ if (app) render_frame(*app);
94
+ }
95
+
96
+ int main() {
97
+ if (glfwInit() == 0) return 1;
98
+
99
+ const char* glsl_version = "#version 150";
100
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
101
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
102
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
103
+ #ifdef __APPLE__
104
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
105
+ #endif
106
+
107
+ GLFWwindow* window = glfwCreateWindow(800, 600, "APP_NAME", nullptr, nullptr);
108
+ if (!window) { glfwTerminate(); return 1; }
109
+ glfwMakeContextCurrent(window);
110
+ glfwSwapInterval(1);
111
+
112
+ IMGUI_CHECKVERSION();
113
+ ImGui::CreateContext();
114
+ ImGuiIO& io = ImGui::GetIO();
115
+ io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
116
+ io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
117
+
118
+ ImGui::StyleColorsDark();
119
+ ImGuiStyle& style = ImGui::GetStyle();
120
+ if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
121
+ style.WindowRounding = 0.0F;
122
+ style.Colors[ImGuiCol_WindowBg].w = 1.0F;
123
+ }
124
+
125
+ ImGui_ImplGlfw_InitForOpenGL(window, true);
126
+ ImGui_ImplOpenGL3_Init(glsl_version);
127
+
128
+ App app;
129
+ app.window = window;
130
+ app.io = &io;
131
+ glfwSetWindowUserPointer(window, &app);
132
+ glfwSetWindowSizeCallback(window, window_size_callback);
133
+
134
+ // Wire up the HTTP fetch callback
135
+ app.state.onFetch = [&]() {
136
+ app.state.loading = true;
137
+ app.state.response = "";
138
+ std::string url = app.state.url;
139
+ imx::run_async<std::string>(
140
+ app.runtime,
141
+ [url]() -> std::string {
142
+ // Parse http://host/path
143
+ auto scheme_end = url.find("://");
144
+ if (scheme_end == std::string::npos)
145
+ return "Error: invalid URL (must start with http://)";
146
+ auto host_start = scheme_end + 3;
147
+ auto path_start = url.find('/', host_start);
148
+ std::string host = (path_start != std::string::npos)
149
+ ? url.substr(0, path_start) : url;
150
+ std::string path = (path_start != std::string::npos)
151
+ ? url.substr(path_start) : "/";
152
+
153
+ // For HTTPS: #define CPPHTTPLIB_OPENSSL_SUPPORT and link OpenSSL
154
+ httplib::Client cli(host);
155
+ cli.set_connection_timeout(5);
156
+ cli.set_read_timeout(5);
157
+ auto res = cli.Get(path);
158
+ if (res) return res->body;
159
+ return "Error: request failed";
160
+ },
161
+ [&](std::string body) {
162
+ app.state.response = std::move(body);
163
+ app.state.loading = false;
164
+ }
165
+ );
166
+ };
167
+
168
+ while (glfwWindowShouldClose(window) == 0) {
169
+ if (app.runtime.needs_frame()) {
170
+ glfwPollEvents();
171
+ } else {
172
+ glfwWaitEventsTimeout(0.1);
173
+ }
174
+ render_frame(app);
175
+ app.runtime.frame_rendered(ImGui::IsAnyItemActive());
176
+ }
177
+
178
+ ImGui_ImplOpenGL3_Shutdown();
179
+ ImGui_ImplGlfw_Shutdown();
180
+ ImGui::DestroyContext();
181
+ glfwDestroyWindow(window);
182
+ glfwTerminate();
183
+ return 0;
184
+ }
185
+
186
+ #ifdef _WIN32
187
+ #include <windows.h>
188
+ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { return main(); }
189
+ #endif
190
+ `;
191
+ const APP_TSX = `export default function App(props: AppState) {
192
+ return (
193
+ <DockSpace>
194
+ <Window title="Networking Demo">
195
+ <Column gap={8}>
196
+ <Text>HTTP Client Example</Text>
197
+ <Separator />
198
+ <TextInput label="URL" value={props.url} />
199
+ <Button title="Fetch" onPress={props.onFetch} disabled={props.loading} />
200
+ {props.loading && <Text color={[1, 0.8, 0, 1]}>Loading...</Text>}
201
+ {props.response !== "" && <Text wrapped={true}>{props.response}</Text>}
202
+ </Column>
203
+ </Window>
204
+ </DockSpace>
205
+ );
206
+ }
207
+ `;
208
+ function generate(projectDir, projectName) {
209
+ const srcDir = path.join(projectDir, 'src');
210
+ if (fs.existsSync(path.join(srcDir, 'App.tsx'))) {
211
+ console.error(`Error: ${srcDir}/App.tsx already exists. Aborting.`);
212
+ process.exit(1);
213
+ }
214
+ fs.mkdirSync(srcDir, { recursive: true });
215
+ const publicDir = path.join(projectDir, 'public');
216
+ fs.mkdirSync(publicDir, { recursive: true });
217
+ // Write files
218
+ fs.writeFileSync(path.join(srcDir, 'main.cpp'), MAIN_CPP.replace('APP_NAME', projectName));
219
+ fs.writeFileSync(path.join(srcDir, 'AppState.h'), APPSTATE_H);
220
+ fs.writeFileSync(path.join(srcDir, 'async.h'), ASYNC_H);
221
+ fs.writeFileSync(path.join(srcDir, 'App.tsx'), APP_TSX);
222
+ fs.writeFileSync(path.join(srcDir, 'imx.d.ts'), buildImxDts(APPSTATE_INTERFACE));
223
+ fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), TSCONFIG);
224
+ fs.writeFileSync(path.join(projectDir, 'CMakeLists.txt'), cmakeTemplate(projectName, 'https://github.com/bgocumlu/imx.git'));
225
+ fs.writeFileSync(path.join(projectDir, '.gitignore'), GITIGNORE);
226
+ console.log(`imxc: initialized project "${projectName}" with template "networking"`);
227
+ console.log('');
228
+ console.log(' Created:');
229
+ console.log(` src/main.cpp — app shell with HTTP fetch callback`);
230
+ console.log(` src/AppState.h — C++ state struct with URL/response fields`);
231
+ console.log(` src/async.h — run_async() helper (std::thread)`);
232
+ console.log(` src/App.tsx — networking demo UI`);
233
+ console.log(` src/imx.d.ts — type definitions for IDE support`);
234
+ console.log(` tsconfig.json — TypeScript config`);
235
+ console.log(` CMakeLists.txt — build config with FetchContent (imx + cpp-httplib)`);
236
+ console.log(` .gitignore — ignores build/, node_modules/, *.ini`);
237
+ console.log(` public/ — static assets (copied to exe directory)`);
238
+ console.log('');
239
+ console.log(' Next steps:');
240
+ console.log(` cd ${projectName}`);
241
+ console.log(` cmake -B build`);
242
+ console.log(` cmake --build build`);
243
+ }
244
+ registerTemplate({ name: 'networking', description: 'HTTP client with cpp-httplib', generate });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,238 @@
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
+ name: string;
6
+ volume: number;
7
+ darkMode: boolean;
8
+ onSave: () => void;
9
+ onLoad: () => void;
10
+ }`;
11
+ const APPSTATE_H = `#pragma once
12
+ #include <string>
13
+ #include <functional>
14
+ #include <imx/json.hpp>
15
+
16
+ struct AppState {
17
+ std::string name = "World";
18
+ float volume = 50.0F;
19
+ bool darkMode = true;
20
+ std::function<void()> onSave;
21
+ std::function<void()> onLoad;
22
+ };
23
+
24
+ // Only serialize data fields — callbacks are not persisted
25
+ NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(AppState, name, volume, darkMode)
26
+ `;
27
+ const PERSISTENCE_H = `#pragma once
28
+ #include <fstream>
29
+ #include <string>
30
+ #include <imx/json.hpp>
31
+
32
+ namespace imx {
33
+
34
+ // Save state as formatted JSON. Returns true on success.
35
+ // Saves next to the executable by default — change path for platform app data dirs.
36
+ template<typename T>
37
+ bool save_json(const std::string& path, const T& state) {
38
+ std::ofstream f(path);
39
+ if (!f) return false;
40
+ f << nlohmann::json(state).dump(2);
41
+ return true;
42
+ }
43
+
44
+ // Load state from JSON file. Returns true on success, leaves state unchanged on failure.
45
+ template<typename T>
46
+ bool load_json(const std::string& path, T& state) {
47
+ std::ifstream f(path);
48
+ if (!f) return false;
49
+ nlohmann::json j = nlohmann::json::parse(f, nullptr, false);
50
+ if (j.is_discarded()) return false;
51
+ nlohmann::from_json(j, state);
52
+ return true;
53
+ }
54
+
55
+ } // namespace imx
56
+ `;
57
+ const MAIN_CPP = `#include <imx/runtime.h>
58
+ #include <imx/renderer.h>
59
+
60
+ #include <imgui.h>
61
+ #include <imgui_impl_glfw.h>
62
+ #include <imgui_impl_opengl3.h>
63
+ #include <GLFW/glfw3.h>
64
+
65
+ #include "persistence.h"
66
+ #include "AppState.h"
67
+
68
+ struct App {
69
+ GLFWwindow* window = nullptr;
70
+ ImGuiIO* io = nullptr;
71
+ imx::Runtime runtime;
72
+ AppState state;
73
+ };
74
+
75
+ static void render_frame(App& app) {
76
+ glfwMakeContextCurrent(app.window);
77
+ if (glfwGetWindowAttrib(app.window, GLFW_ICONIFIED) != 0) return;
78
+
79
+ int fb_w = 0, fb_h = 0;
80
+ glfwGetFramebufferSize(app.window, &fb_w, &fb_h);
81
+ if (fb_w <= 0 || fb_h <= 0) return;
82
+
83
+ ImGui_ImplOpenGL3_NewFrame();
84
+ ImGui_ImplGlfw_NewFrame();
85
+ ImGui::NewFrame();
86
+
87
+ imx::render_root(app.runtime, app.state);
88
+
89
+ ImGui::Render();
90
+ glViewport(0, 0, fb_w, fb_h);
91
+ glClearColor(0.12F, 0.12F, 0.15F, 1.0F);
92
+ glClear(GL_COLOR_BUFFER_BIT);
93
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
94
+
95
+ if ((app.io->ConfigFlags & ImGuiConfigFlags_ViewportsEnable) != 0) {
96
+ ImGui::UpdatePlatformWindows();
97
+ ImGui::RenderPlatformWindowsDefault();
98
+ }
99
+
100
+ glfwMakeContextCurrent(app.window);
101
+ glfwSwapBuffers(app.window);
102
+ }
103
+
104
+ static void window_size_callback(GLFWwindow* window, int, int) {
105
+ auto* app = static_cast<App*>(glfwGetWindowUserPointer(window));
106
+ if (app) render_frame(*app);
107
+ }
108
+
109
+ int main() {
110
+ if (glfwInit() == 0) return 1;
111
+
112
+ const char* glsl_version = "#version 150";
113
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
114
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
115
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
116
+ #ifdef __APPLE__
117
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
118
+ #endif
119
+
120
+ GLFWwindow* window = glfwCreateWindow(800, 600, "APP_NAME", nullptr, nullptr);
121
+ if (!window) { glfwTerminate(); return 1; }
122
+ glfwMakeContextCurrent(window);
123
+ glfwSwapInterval(1);
124
+
125
+ IMGUI_CHECKVERSION();
126
+ ImGui::CreateContext();
127
+ ImGuiIO& io = ImGui::GetIO();
128
+ io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
129
+ io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;
130
+
131
+ ImGui::StyleColorsDark();
132
+ ImGuiStyle& style = ImGui::GetStyle();
133
+ if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) {
134
+ style.WindowRounding = 0.0F;
135
+ style.Colors[ImGuiCol_WindowBg].w = 1.0F;
136
+ }
137
+
138
+ ImGui_ImplGlfw_InitForOpenGL(window, true);
139
+ ImGui_ImplOpenGL3_Init(glsl_version);
140
+
141
+ App app;
142
+ app.window = window;
143
+ app.io = &io;
144
+ glfwSetWindowUserPointer(window, &app);
145
+ glfwSetWindowSizeCallback(window, window_size_callback);
146
+
147
+ app.state.onSave = [&]() {
148
+ imx::save_json("state.json", app.state);
149
+ };
150
+ app.state.onLoad = [&]() {
151
+ imx::load_json("state.json", app.state);
152
+ app.runtime.request_frame();
153
+ };
154
+
155
+ // Auto-load saved state (silently fails if no file exists)
156
+ imx::load_json("state.json", app.state);
157
+
158
+ while (glfwWindowShouldClose(window) == 0) {
159
+ if (app.runtime.needs_frame()) {
160
+ glfwPollEvents();
161
+ } else {
162
+ glfwWaitEventsTimeout(0.1);
163
+ }
164
+ render_frame(app);
165
+ app.runtime.frame_rendered(ImGui::IsAnyItemActive());
166
+ }
167
+
168
+ ImGui_ImplOpenGL3_Shutdown();
169
+ ImGui_ImplGlfw_Shutdown();
170
+ ImGui::DestroyContext();
171
+ glfwDestroyWindow(window);
172
+ glfwTerminate();
173
+ return 0;
174
+ }
175
+
176
+ #ifdef _WIN32
177
+ #include <windows.h>
178
+ int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { return main(); }
179
+ #endif
180
+ `;
181
+ const APP_TSX = `export default function App(props: AppState) {
182
+ return (
183
+ <DockSpace>
184
+ <Window title="Persistence Demo">
185
+ <Column gap={8}>
186
+ <Text>JSON Save/Load Example</Text>
187
+ <Separator />
188
+ <TextInput label="Name" value={props.name} />
189
+ <SliderFloat label="Volume" value={props.volume} min={0} max={100} />
190
+ <Checkbox label="Dark Mode" value={props.darkMode} />
191
+ <Separator />
192
+ <Row gap={8}>
193
+ <Button title="Save" onPress={props.onSave} />
194
+ <Button title="Load" onPress={props.onLoad} />
195
+ </Row>
196
+ </Column>
197
+ </Window>
198
+ </DockSpace>
199
+ );
200
+ }
201
+ `;
202
+ function generate(projectDir, projectName) {
203
+ const srcDir = path.join(projectDir, 'src');
204
+ if (fs.existsSync(path.join(srcDir, 'App.tsx'))) {
205
+ console.error(`Error: ${srcDir}/App.tsx already exists. Aborting.`);
206
+ process.exit(1);
207
+ }
208
+ fs.mkdirSync(srcDir, { recursive: true });
209
+ const publicDir = path.join(projectDir, 'public');
210
+ fs.mkdirSync(publicDir, { recursive: true });
211
+ // Write files
212
+ fs.writeFileSync(path.join(srcDir, 'main.cpp'), MAIN_CPP.replace('APP_NAME', projectName));
213
+ fs.writeFileSync(path.join(srcDir, 'AppState.h'), APPSTATE_H);
214
+ fs.writeFileSync(path.join(srcDir, 'persistence.h'), PERSISTENCE_H);
215
+ fs.writeFileSync(path.join(srcDir, 'App.tsx'), APP_TSX);
216
+ fs.writeFileSync(path.join(srcDir, 'imx.d.ts'), buildImxDts(APPSTATE_INTERFACE));
217
+ fs.writeFileSync(path.join(projectDir, 'tsconfig.json'), TSCONFIG);
218
+ fs.writeFileSync(path.join(projectDir, 'CMakeLists.txt'), cmakeTemplate(projectName, 'https://github.com/bgocumlu/imx.git'));
219
+ fs.writeFileSync(path.join(projectDir, '.gitignore'), GITIGNORE);
220
+ console.log(`imxc: initialized project "${projectName}" with template "persistence"`);
221
+ console.log('');
222
+ console.log(' Created:');
223
+ console.log(` src/main.cpp — app shell with save/load callbacks`);
224
+ console.log(` src/AppState.h — C++ state struct with nlohmann/json serialization`);
225
+ console.log(` src/persistence.h — save_json/load_json helpers (nlohmann/json)`);
226
+ console.log(` src/App.tsx — your root component`);
227
+ console.log(` src/imx.d.ts — type definitions for IDE support`);
228
+ console.log(` tsconfig.json — TypeScript config`);
229
+ console.log(` CMakeLists.txt — build config with FetchContent (imx + nlohmann/json)`);
230
+ console.log(` .gitignore — ignores build/, node_modules/, *.ini`);
231
+ console.log(` public/ — static assets (copied to exe directory)`);
232
+ console.log('');
233
+ console.log(' Next steps:');
234
+ console.log(` cd ${projectName}`);
235
+ console.log(` cmake -B build`);
236
+ console.log(` cmake --build build`);
237
+ }
238
+ registerTemplate({ name: 'persistence', description: 'JSON save/load with nlohmann/json', generate });
@@ -2,6 +2,7 @@ import ts from 'typescript';
2
2
  import type { ParsedFile, ParseError } from './parser.js';
3
3
  export interface ValidationResult {
4
4
  errors: ParseError[];
5
+ warnings: ParseError[];
5
6
  customComponents: Map<string, string>;
6
7
  useStateCalls: UseStateInfo[];
7
8
  }