// 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 "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" //////////////////////////////// // Win32 input gathering #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include struct InputState { Vec2F32 mouse; Vec2F32 scroll_delta; B32 mouse_down; B32 was_mouse_down; }; static InputState g_input; 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; } //////////////////////////////// // 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; // Demo widget state B32 demo_checkbox_a; B32 demo_checkbox_b; int32_t demo_radio_sel; int32_t demo_dropdown_sel; char demo_text_a[128]; char demo_text_b[128]; int32_t demo_button_count; }; //////////////////////////////// // 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(AppState *app) { 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 = { 16, 16, 12, 12 }, .childGap = 12, .layoutDirection = CLAY_TOP_TO_BOTTOM, } ) { // Section: Buttons ui_label("LblButtons", "Buttons"); CLAY(CLAY_ID("ButtonRow"), .layout = { .sizing = { .width = CLAY_SIZING_FIT(), .height = CLAY_SIZING_FIT() }, .childGap = 8, .layoutDirection = CLAY_LEFT_TO_RIGHT, } ) { if (ui_button("BtnHello", "Click Me")) { app->demo_button_count++; } if (ui_button("BtnReset", "Reset")) { app->demo_button_count = 0; } } // Show click count static char btn_count_buf[64]; snprintf(btn_count_buf, sizeof(btn_count_buf), "Button clicked %d times", app->demo_button_count); ui_label("LblBtnCount", btn_count_buf); // Separator CLAY(CLAY_ID("Sep1"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } }, .backgroundColor = g_theme.border ) {} // Section: Checkboxes ui_label("LblCheckboxes", "Checkboxes"); ui_checkbox("ChkA", "Enable feature A", &app->demo_checkbox_a); ui_checkbox("ChkB", "Enable feature B", &app->demo_checkbox_b); CLAY(CLAY_ID("Sep2"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } }, .backgroundColor = g_theme.border ) {} // Section: Radio buttons ui_label("LblRadio", "Output Format"); static const char *radio_options[] = { "WAV", "AIFF", "FLAC" }; ui_radio_group("RadioFmt", radio_options, 3, &app->demo_radio_sel); CLAY(CLAY_ID("Sep3"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } }, .backgroundColor = g_theme.border ) {} // Section: Text inputs ui_label("LblText", "Text Inputs"); CLAY(CLAY_ID("TextRow"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT() }, .childGap = 8, .layoutDirection = CLAY_LEFT_TO_RIGHT, } ) { CLAY(CLAY_ID("TextCol1"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT() }, .childGap = 4, .layoutDirection = CLAY_TOP_TO_BOTTOM, } ) { ui_label("LblName", "Name:"); ui_text_input("TxtName", app->demo_text_a, sizeof(app->demo_text_a)); } CLAY(CLAY_ID("TextCol2"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIT() }, .childGap = 4, .layoutDirection = CLAY_TOP_TO_BOTTOM, } ) { ui_label("LblPath", "Output Path:"); ui_text_input("TxtPath", app->demo_text_b, sizeof(app->demo_text_b)); } } CLAY(CLAY_ID("Sep4"), .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } }, .backgroundColor = g_theme.border ) {} // Section: Dropdown ui_label("LblDropdown", "Sample Rate"); CLAY(CLAY_ID("DropdownWrapper"), .layout = { .sizing = { .width = CLAY_SIZING_FIXED(200), .height = CLAY_SIZING_FIT() }, } ) { static const char *rate_options[] = { "44100 Hz", "48000 Hz", "88200 Hz", "96000 Hz", "192000 Hz" }; ui_dropdown("DropRate", rate_options, 5, &app->demo_dropdown_sel); } } } } 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); } } } //////////////////////////////// // 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(app); 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)); PlatformInputEvents input_events = platform_get_input_events(app->window); ui_widgets_begin_frame(input_events, g_input.mouse_down, g_input.was_mouse_down); // 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) { (void)argc; (void)argv; PlatformWindowDesc window_desc = {}; PlatformWindow *window = platform_create_window(&window_desc); if (!window) return 1; S32 w, h; platform_get_size(window, &w, &h); RendererDesc renderer_desc = {}; renderer_desc.window_handle = platform_get_native_handle(window); renderer_desc.width = w; renderer_desc.height = h; Renderer *renderer = renderer_create(&renderer_desc); if (!renderer) { platform_destroy_window(window); return 1; } MidiEngine *midi = midi_create(); // 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); ui_widgets_init(); 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; app.demo_dropdown_sel = 1; // default to 48000 Hz snprintf(app.demo_text_a, sizeof(app.demo_text_a), "My Instrument"); snprintf(app.demo_text_b, sizeof(app.demo_text_b), "C:\\Samples\\output"); 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: 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; } do_frame(&app); } exit_app: midi_destroy(midi); ui_destroy(ui); renderer_destroy(renderer); platform_destroy_window(window); return 0; }