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.
- 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,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 });
|
package/dist/validator.d.ts
CHANGED