move from imgui to CLAY

This commit is contained in:
2026-02-25 15:20:47 -05:00
parent 6656b6d0b2
commit 12dae774e4
41 changed files with 6835 additions and 77320 deletions

View File

@@ -1,44 +1,357 @@
// Unity build - include all src files here
// -mta
// [h]
#include "base/base_inc.h"
#include "platform/platform.h"
#include "renderer/renderer.h"
#include "midi/midi.h"
#include "imgui.h"
#include "imgui_internal.h"
#include <iostream>
#include "ui/ui_core.h"
#include "ui/ui_widgets.h"
// [cpp]
#include "base/base_inc.cpp"
#include "ui/ui_core.cpp"
#include "ui/ui_widgets.cpp"
#include "platform/platform_win32.cpp"
#include "renderer/renderer_dx12.cpp"
#include "midi/midi_win32.cpp"
#include "menus.cpp"
#include "theme.cpp"
////////////////////////////////
// Win32 input gathering
static void build_default_layout(ImGuiID dockspace_id) {
ImGui::DockBuilderRemoveNode(dockspace_id);
ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace);
ImGui::DockBuilderSetNodeSize(dockspace_id, ImGui::GetMainViewport()->Size);
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
ImGuiID center = dockspace_id;
ImGuiID left = ImGui::DockBuilderSplitNode(center, ImGuiDir_Left, 0.15f, nullptr, &center);
ImGuiID right = ImGui::DockBuilderSplitNode(center, ImGuiDir_Right, 0.20f, nullptr, &center);
ImGuiID bottom = ImGui::DockBuilderSplitNode(center, ImGuiDir_Down, 0.25f, nullptr, &center);
struct InputState {
Vec2F32 mouse;
Vec2F32 scroll_delta;
B32 mouse_down;
B32 was_mouse_down;
};
ImGui::DockBuilderDockWindow("Browser", left);
ImGui::DockBuilderDockWindow("Main", center);
ImGui::DockBuilderDockWindow("Properties", right);
ImGui::DockBuilderDockWindow("MIDI Devices", right);
ImGui::DockBuilderDockWindow("Log", bottom);
static InputState g_input;
ImGui::DockBuilderFinish(dockspace_id);
static void input_gather(void *window_handle) {
HWND hwnd = (HWND)window_handle;
// Mouse position
POINT cursor;
GetCursorPos(&cursor);
ScreenToClient(hwnd, &cursor);
g_input.mouse = v2f32((F32)cursor.x, (F32)cursor.y);
// Mouse button
g_input.was_mouse_down = g_input.mouse_down;
g_input.mouse_down = (GetAsyncKeyState(VK_LBUTTON) & 0x8000) != 0;
// Scroll (TODO: hook WM_MOUSEWHEEL for real scroll deltas)
g_input.scroll_delta = v2f32(0, 0);
}
////////////////////////////////
// Clay text config helpers
static Clay_TextElementConfig g_text_config_normal;
static Clay_TextElementConfig g_text_config_title;
static Clay_TextElementConfig g_text_config_dim;
static void init_text_configs() {
g_text_config_normal = {};
g_text_config_normal.textColor = g_theme.text;
g_text_config_normal.fontSize = 15;
g_text_config_normal.wrapMode = CLAY_TEXT_WRAP_NONE;
g_text_config_title = {};
g_text_config_title.textColor = g_theme.text;
g_text_config_title.fontSize = 15;
g_text_config_title.wrapMode = CLAY_TEXT_WRAP_NONE;
g_text_config_dim = {};
g_text_config_dim.textColor = g_theme.text_dim;
g_text_config_dim.fontSize = 15;
g_text_config_dim.wrapMode = CLAY_TEXT_WRAP_NONE;
}
////////////////////////////////
// Panel builders
static void build_panel_title_bar(Clay_ElementId id, Clay_String title) {
CLAY(id,
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(24) },
.padding = { 8, 8, 0, 0 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = g_theme.title_bar,
.border = { .color = g_theme.border, .width = { .bottom = 1 } }
) {
CLAY_TEXT(title, &g_text_config_title);
}
}
static void build_browser_panel(B32 show) {
if (!show) return;
CLAY(CLAY_ID("BrowserPanel"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(200), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = g_theme.bg_medium,
.border = { .color = g_theme.border, .width = { .right = 1 } }
) {
build_panel_title_bar(CLAY_ID("BrowserTitleBar"), CLAY_STRING("Browser"));
CLAY(CLAY_ID("BrowserContent"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { 8, 8, 6, 6 },
.childGap = 4,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
CLAY_TEXT(CLAY_STRING("Instruments"), &g_text_config_normal);
}
}
}
static void build_main_panel() {
CLAY(CLAY_ID("MainPanel"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = g_theme.bg_light
) {
build_panel_title_bar(CLAY_ID("MainTitleBar"), CLAY_STRING("Main"));
CLAY(CLAY_ID("MainContent"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { 8, 8, 6, 6 },
.childGap = 4,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
CLAY_TEXT(CLAY_STRING("Main content area"), &g_text_config_normal);
}
}
}
static void build_properties_panel(B32 show) {
if (!show) return;
CLAY(CLAY_ID("PropertiesPanel"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = g_theme.bg_medium,
.border = { .color = g_theme.border, .width = { .bottom = 1 } }
) {
build_panel_title_bar(CLAY_ID("PropertiesTitleBar"), CLAY_STRING("Properties"));
CLAY(CLAY_ID("PropertiesContent"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { 8, 8, 6, 6 },
.childGap = 4,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
CLAY_TEXT(CLAY_STRING("Details"), &g_text_config_normal);
}
}
}
static void build_midi_panel(B32 show, MidiEngine *midi) {
if (!show) return;
CLAY(CLAY_ID("MidiPanel"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = g_theme.bg_medium
) {
build_panel_title_bar(CLAY_ID("MidiTitleBar"), CLAY_STRING("MIDI Devices"));
CLAY(CLAY_ID("MidiContent"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { 8, 8, 6, 6 },
.childGap = 4,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Refresh button
CLAY(CLAY_ID("MidiRefreshBtn"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIXED(28) },
.padding = { 12, 12, 0, 0 },
.childAlignment = { .y = CLAY_ALIGN_Y_CENTER },
},
.backgroundColor = Clay_Hovered() ? g_theme.accent_hover : g_theme.bg_lighter,
.cornerRadius = CLAY_CORNER_RADIUS(3)
) {
CLAY_TEXT(CLAY_STRING("Refresh"), &g_text_config_normal);
}
// Device list - use static buffers so strings persist for Clay rendering
static char device_bufs[64][128];
int32_t device_count = midi_get_device_count(midi);
for (int32_t i = 0; i < device_count && i < 64; i++) {
MidiDeviceInfo *dev = midi_get_device(midi, i);
int len = snprintf(device_bufs[i], sizeof(device_bufs[i]), "[%s] %s", dev->is_input ? "IN" : "OUT", dev->name);
Clay_String device_str = { .isStaticallyAllocated = false, .length = len, .chars = device_bufs[i] };
CLAY(CLAY_IDI("MidiDevice", i),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT() },
.padding = { 4, 4, 2, 2 },
}
) {
CLAY_TEXT(device_str, &g_text_config_normal);
}
}
if (device_count == 0) {
CLAY_TEXT(CLAY_STRING("No MIDI devices found"), &g_text_config_dim);
}
}
}
}
static void build_log_panel(B32 show) {
if (!show) return;
CLAY(CLAY_ID("LogPanel"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(180) },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.backgroundColor = g_theme.bg_medium,
.border = { .color = g_theme.border, .width = { .top = 1 } }
) {
build_panel_title_bar(CLAY_ID("LogTitleBar"), CLAY_STRING("Log"));
CLAY(CLAY_ID("LogContent"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { 8, 8, 6, 6 },
.childGap = 4,
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
CLAY_TEXT(CLAY_STRING("Output / Log"), &g_text_config_normal);
}
}
}
////////////////////////////////
// App state — all mutable state the frame function needs
struct AppState {
PlatformWindow *window;
Renderer *renderer;
MidiEngine *midi;
UI_Context *ui;
S32 last_w, last_h;
B32 show_browser;
B32 show_props;
B32 show_log;
B32 show_midi_devices;
LARGE_INTEGER freq;
LARGE_INTEGER last_time;
};
////////////////////////////////
// Build the full UI layout for one frame
static void build_ui(AppState *app) {
CLAY(CLAY_ID("Root"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
}
) {
// Top row: browser | main | right column
CLAY(CLAY_ID("TopRow"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
}
) {
build_browser_panel(app->show_browser);
build_main_panel();
if (app->show_props || app->show_midi_devices) {
CLAY(CLAY_ID("RightColumn"),
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(250), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.border = { .color = g_theme.border, .width = { .left = 1 } }
) {
build_properties_panel(app->show_props);
build_midi_panel(app->show_midi_devices, app->midi);
}
}
}
build_log_panel(app->show_log);
}
}
////////////////////////////////
// Render one frame: resize if needed, build UI, submit to GPU.
// Called from the main loop and from WM_SIZE during live resize.
static void do_frame(AppState *app) {
// Timing
LARGE_INTEGER now;
QueryPerformanceCounter(&now);
F32 dt = (F32)(now.QuadPart - app->last_time.QuadPart) / (F32)app->freq.QuadPart;
app->last_time = now;
if (dt > 0.1f) dt = 0.1f;
// Resize
S32 w, h;
platform_get_size(app->window, &w, &h);
if (w != app->last_w || h != app->last_h) {
renderer_resize(app->renderer, w, h);
app->last_w = w;
app->last_h = h;
}
if (!renderer_begin_frame(app->renderer))
return;
// Gather input
input_gather(platform_get_native_handle(app->window));
// Build UI with Clay
ui_begin_frame(app->ui, (F32)w, (F32)h, g_input.mouse, g_input.mouse_down,
g_input.scroll_delta, dt);
build_ui(app);
Clay_RenderCommandArray render_commands = ui_end_frame(app->ui);
// Render
renderer_end_frame(app->renderer, render_commands);
}
////////////////////////////////
// Platform frame callback for live resize
static void frame_callback(void *user_data) {
do_frame((AppState *)user_data);
}
////////////////////////////////
// Entry point
int main(int argc, char **argv) {
std::cout << "Hello" << std::endl;
(void)argc;
(void)argv;
@@ -47,7 +360,7 @@ int main(int argc, char **argv) {
if (!window)
return 1;
int32_t w, h;
S32 w, h;
platform_get_size(window, &w, &h);
RendererDesc renderer_desc = {};
@@ -62,99 +375,48 @@ int main(int argc, char **argv) {
MidiEngine *midi = midi_create();
setup_theme();
// Initialize UI (Clay)
ui_init_theme();
UI_Context *ui = ui_create((F32)w, (F32)h);
ui_set_measure_text_fn(ui, renderer_measure_text, renderer);
init_text_configs();
setup_menus(window);
int32_t last_w = w, last_h = h;
bool show_demo = true;
bool show_browser = true;
bool show_props = true;
bool show_log = true;
bool show_midi_devices = true;
bool first_frame = true;
AppState app = {};
app.window = window;
app.renderer = renderer;
app.midi = midi;
app.ui = ui;
app.last_w = w;
app.last_h = h;
app.show_browser = 1;
app.show_props = 1;
app.show_log = 1;
app.show_midi_devices = 1;
QueryPerformanceFrequency(&app.freq);
QueryPerformanceCounter(&app.last_time);
platform_set_frame_callback(window, frame_callback, &app);
while (platform_poll_events(window)) {
// Menu commands
int32_t menu_cmd = platform_poll_menu_command(window);
switch (menu_cmd) {
case MENU_FILE_EXIT: platform_destroy_window(window); return 0;
case MENU_VIEW_BROWSER: show_browser = !show_browser; break;
case MENU_VIEW_PROPERTIES:show_props = !show_props; break;
case MENU_VIEW_LOG: show_log = !show_log; break;
case MENU_VIEW_DEMO: show_demo = !show_demo; break;
case MENU_VIEW_MIDI_DEVICES: show_midi_devices = !show_midi_devices; break;
case MENU_FILE_EXIT: goto exit_app;
case MENU_VIEW_BROWSER: app.show_browser = !app.show_browser; break;
case MENU_VIEW_PROPERTIES:app.show_props = !app.show_props; break;
case MENU_VIEW_LOG: app.show_log = !app.show_log; break;
case MENU_VIEW_MIDI_DEVICES: app.show_midi_devices = !app.show_midi_devices; break;
default: break;
}
platform_get_size(window, &w, &h);
if (w != last_w || h != last_h) {
renderer_resize(renderer, w, h);
last_w = w;
last_h = h;
}
if (!renderer_begin_frame(renderer))
continue;
// Full-window dockspace
ImGuiID dockspace_id = ImGui::GetID("MainDockSpace");
ImGui::DockSpaceOverViewport(dockspace_id, ImGui::GetMainViewport());
if (first_frame) {
build_default_layout(dockspace_id);
first_frame = false;
}
// Left panel
if (show_browser) {
ImGui::Begin("Browser", &show_browser);
ImGui::Text("Instruments");
ImGui::Separator();
ImGui::End();
}
// Main content
ImGui::Begin("Main");
ImGui::Text("Main content area");
ImGui::End();
// Right panel
if (show_props) {
ImGui::Begin("Properties", &show_props);
ImGui::Text("Details");
ImGui::Separator();
ImGui::End();
}
// MIDI Devices panel
if (show_midi_devices) {
ImGui::Begin("MIDI Devices", &show_midi_devices);
if (ImGui::Button("Refresh"))
midi_refresh_devices(midi);
ImGui::Separator();
for (int32_t i = 0; i < midi_get_device_count(midi); i++) {
MidiDeviceInfo *dev = midi_get_device(midi, i);
ImGui::Text("[%s] %s", dev->is_input ? "IN" : "OUT", dev->name);
}
if (midi_get_device_count(midi) == 0)
ImGui::TextDisabled("No MIDI devices found");
ImGui::End();
}
// Bottom panel
if (show_log) {
ImGui::Begin("Log", &show_log);
ImGui::Text("Output / Log");
ImGui::Separator();
ImGui::End();
}
// Demo window
if (show_demo)
ImGui::ShowDemoWindow(&show_demo);
renderer_end_frame(renderer);
do_frame(&app);
}
exit_app:
midi_destroy(midi);
ui_destroy(ui);
renderer_destroy(renderer);
platform_destroy_window(window);
return 0;