Compare commits

...

5 Commits

Author SHA1 Message Date
bb5e0fb766 update build 2026-03-13 11:03:21 -04:00
68fc916bc1 factor out scroll into ui 2026-03-13 10:17:13 -04:00
4e0d9fdcca reorganize project files 2026-03-13 09:34:23 -04:00
bf69bafd42 replace nob with noblike 2026-03-12 16:48:00 -04:00
71c221aa04 add clang format 2026-03-12 16:35:54 -04:00
9 changed files with 962 additions and 3460 deletions

31
.clang-format Normal file
View File

@@ -0,0 +1,31 @@
BasedOnStyle: LLVM
IndentWidth: 4
TabWidth: 4
UseTab: Never
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
AlignConsecutiveDeclarations: true
AlignConsecutiveAssignments: true
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: AllIfsAndElse
AlignConsecutiveMacros: true
AllowShortBlocksOnASingleLine: Always
AllowShortCaseLabelsOnASingleLine: true
Cpp11BracedListStyle: false
ColumnLimit: 0

12
.vscode/tasks.json vendored
View File

@@ -47,7 +47,7 @@
}, },
"group": "build", "group": "build",
"problemMatcher": { "problemMatcher": {
"owner": "cpp", "owner": "c",
"fileLocation": ["relative", "${workspaceFolder}"], "fileLocation": ["relative", "${workspaceFolder}"],
"pattern": { "pattern": {
"regexp": "^(.+?):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$", "regexp": "^(.+?):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
@@ -63,15 +63,15 @@
{ {
"label": "clean", "label": "clean",
"type": "shell", "type": "shell",
"command": "rm", "command": "${workspaceFolder}/build",
"args": ["-rf", "build_debug", "build_release"], "args": ["clean"],
"windows": { "windows": {
"command": "PowerShell", "command": "${workspaceFolder}/build.exe",
"args": ["-Command", "Remove-Item -Recurse -Force -ErrorAction SilentlyContinue build_debug, build_release"] "args": ["clean"]
}, },
"group": "build", "group": "build",
"problemMatcher": [], "problemMatcher": [],
"detail": "Remove build directories" "detail": "Clean build artifacts via build.c"
} }
] ]
} }

344
build.c
View File

@@ -3,8 +3,8 @@
// macOS: cc build.c -o build // macOS: cc build.c -o build
// After that, just run: ./build (or build.exe on Windows) // After that, just run: ./build (or build.exe on Windows)
#define NOB_IMPLEMENTATION #define BUILD_IMPLEMENTATION
#include "vendor/nob/nob.h" #include "build.h"
//////////////////////////////// ////////////////////////////////
// FreeType source files // FreeType source files
@@ -31,18 +31,18 @@ static const char *freetype_sources[] = {
// Font embedding — reads a .ttf and writes a C header with the data as a byte array // Font embedding — reads a .ttf and writes a C header with the data as a byte array
static bool embed_font_file(const char *ttf_path, const char *header_path) { static bool embed_font_file(const char *ttf_path, const char *header_path) {
if (!nob_needs_rebuild1(header_path, ttf_path)) { if (!needs_rebuild1(header_path, ttf_path)) {
nob_log(NOB_INFO, "Font header %s is up to date", header_path); build_log(LOG_INFO, "Font header %s is up to date", header_path);
return true; return true;
} }
Nob_String_Builder sb = {0}; String_Builder sb = {0};
if (!nob_read_entire_file(ttf_path, &sb)) return false; if (!sb_read_file(&sb, ttf_path)) return false;
FILE *out = fopen(header_path, "wb"); FILE *out = fopen(header_path, "wb");
if (!out) { if (!out) {
nob_log(NOB_ERROR, "Could not open %s for writing", header_path); build_log(LOG_ERROR, "Could not open %s for writing", header_path);
nob_sb_free(sb); sb_free(&sb);
return false; return false;
} }
@@ -58,8 +58,8 @@ static bool embed_font_file(const char *ttf_path, const char *header_path) {
fprintf(out, "static const unsigned int font_inter_size = %zu;\n", sb.count); fprintf(out, "static const unsigned int font_inter_size = %zu;\n", sb.count);
fclose(out); fclose(out);
nob_sb_free(sb); sb_free(&sb);
nob_log(NOB_INFO, "Generated %s (%zu bytes)", header_path, sb.count); build_log(LOG_INFO, "Generated %s (%zu bytes)", header_path, sb.count);
return true; return true;
} }
@@ -78,17 +78,17 @@ static const char *frameworks[] = {
}; };
static bool build_freetype_lib(const char *build_dir, bool debug) { static bool build_freetype_lib(const char *build_dir, bool debug) {
const char *obj_dir = nob_temp_sprintf("%s/freetype_obj", build_dir); const char *obj_dir = temp_sprintf("%s/freetype_obj", build_dir);
const char *lib_path = "vendor/freetype/libfreetype.a"; const char *lib_path = "vendor/freetype/libfreetype.a";
if (!nob_needs_rebuild(lib_path, freetype_sources, NOB_ARRAY_LEN(freetype_sources))) { if (!needs_rebuild(lib_path, freetype_sources, ARRAY_LEN(freetype_sources))) {
nob_log(NOB_INFO, "freetype is up to date"); build_log(LOG_INFO, "freetype is up to date");
return true; return true;
} }
if (!nob_mkdir_if_not_exists(obj_dir)) return false; if (!mkdir_if_not_exists(obj_dir)) return false;
for (size_t i = 0; i < NOB_ARRAY_LEN(freetype_sources); i++) { for (size_t i = 0; i < ARRAY_LEN(freetype_sources); i++) {
const char *src = freetype_sources[i]; const char *src = freetype_sources[i];
const char *base = strrchr(src, '/'); const char *base = strrchr(src, '/');
base = base ? base + 1 : src; base = base ? base + 1 : src;
@@ -96,30 +96,29 @@ static bool build_freetype_lib(const char *build_dir, bool debug) {
snprintf(obj_name, sizeof(obj_name), "%s", base); snprintf(obj_name, sizeof(obj_name), "%s", base);
char *dot = strrchr(obj_name, '.'); char *dot = strrchr(obj_name, '.');
if (dot) { dot[1] = 'o'; dot[2] = '\0'; } if (dot) { dot[1] = 'o'; dot[2] = '\0'; }
const char *obj_path = nob_temp_sprintf("%s/%s", obj_dir, obj_name); const char *obj_path = temp_sprintf("%s/%s", obj_dir, obj_name);
Nob_Cmd cmd = {0}; Cmd cmd = {0};
nob_cmd_append(&cmd, "clang"); cmd_append(&cmd, "clang");
nob_cmd_append(&cmd, "-std=c11", "-c"); cmd_append(&cmd, "-std=c11", "-c");
nob_cmd_append(&cmd, "-DFT2_BUILD_LIBRARY"); cmd_append(&cmd, "-DFT2_BUILD_LIBRARY");
nob_cmd_append(&cmd, "-Ivendor/freetype/include"); cmd_append(&cmd, "-Ivendor/freetype/include");
nob_cmd_append(&cmd, "-Wall", "-Wno-unused-function", "-Wno-unused-parameter"); cmd_append(&cmd, "-w");
if (debug) { if (debug) {
nob_cmd_append(&cmd, "-g", "-O0"); cmd_append(&cmd, "-g", "-O0");
} else { } else {
nob_cmd_append(&cmd, "-O2"); cmd_append(&cmd, "-O2");
} }
nob_cmd_append(&cmd, "-o", obj_path); cmd_append(&cmd, "-o", obj_path);
nob_cmd_append(&cmd, src); cmd_append(&cmd, src);
Nob_Cmd_Opt copt = {0}; if (!cmd_run(&cmd)) return false;
if (!nob_cmd_run_opt(&cmd, copt)) return false;
} }
// Archive // Archive
{ {
Nob_Cmd cmd = {0}; Cmd cmd = {0};
nob_cmd_append(&cmd, "ar", "rcs", lib_path); cmd_append(&cmd, "ar", "rcs", lib_path);
for (size_t i = 0; i < NOB_ARRAY_LEN(freetype_sources); i++) { for (size_t i = 0; i < ARRAY_LEN(freetype_sources); i++) {
const char *src = freetype_sources[i]; const char *src = freetype_sources[i];
const char *base = strrchr(src, '/'); const char *base = strrchr(src, '/');
base = base ? base + 1 : src; base = base ? base + 1 : src;
@@ -127,26 +126,24 @@ static bool build_freetype_lib(const char *build_dir, bool debug) {
snprintf(obj_name, sizeof(obj_name), "%s", base); snprintf(obj_name, sizeof(obj_name), "%s", base);
char *dot = strrchr(obj_name, '.'); char *dot = strrchr(obj_name, '.');
if (dot) { dot[1] = 'o'; dot[2] = '\0'; } if (dot) { dot[1] = 'o'; dot[2] = '\0'; }
nob_cmd_append(&cmd, nob_temp_sprintf("%s/%s", obj_dir, obj_name)); cmd__append(&cmd, 1, temp_sprintf("%s/%s", obj_dir, obj_name));
} }
Nob_Cmd_Opt copt = {0}; if (!cmd_run(&cmd)) return false;
if (!nob_cmd_run_opt(&cmd, copt)) return false;
} }
// Clean up obj dir // Clean up obj dir
{ {
Nob_Cmd cmd = {0}; Cmd cmd = {0};
nob_cmd_append(&cmd, "rm", "-rf", obj_dir); cmd_append(&cmd, "rm", "-rf", obj_dir);
Nob_Cmd_Opt copt = {0}; cmd_run(&cmd);
nob_cmd_run_opt(&cmd, copt);
} }
nob_log(NOB_INFO, "Built %s", lib_path); build_log(LOG_INFO, "Built %s", lib_path);
return true; return true;
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
NOB_GO_REBUILD_URSELF(argc, argv); GO_REBUILD_URSELF(argc, argv);
bool debug = false; bool debug = false;
bool clean = false; bool clean = false;
@@ -154,8 +151,8 @@ int main(int argc, char **argv) {
if (strcmp(argv[i], "debug") == 0) debug = true; if (strcmp(argv[i], "debug") == 0) debug = true;
else if (strcmp(argv[i], "clean") == 0) clean = true; else if (strcmp(argv[i], "clean") == 0) clean = true;
else { else {
nob_log(NOB_ERROR, "unknown argument: %s", argv[i]); build_log(LOG_ERROR, "unknown argument: %s", argv[i]);
nob_log(NOB_ERROR, "usage: %s [debug] [clean]", argv[0]); build_log(LOG_ERROR, "usage: %s [debug] [clean]", argv[0]);
return 1; return 1;
} }
} }
@@ -163,28 +160,26 @@ int main(int argc, char **argv) {
const char *build_dir = debug ? "build_debug" : "build_release"; const char *build_dir = debug ? "build_debug" : "build_release";
if (clean) { if (clean) {
nob_log(NOB_INFO, "Cleaning build artifacts"); build_log(LOG_INFO, "Cleaning build artifacts");
{ Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "rm", "-rf", "build_debug"); { Cmd cmd = {0}; cmd_append(&cmd, "rm", "-rf", "build_debug"); cmd_run(&cmd); }
Nob_Cmd_Opt opt = {0}; nob_cmd_run_opt(&cmd, opt); } { Cmd cmd = {0}; cmd_append(&cmd, "rm", "-rf", "build_release"); cmd_run(&cmd); }
{ Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "rm", "-rf", "build_release");
Nob_Cmd_Opt opt = {0}; nob_cmd_run_opt(&cmd, opt); }
remove("vendor/freetype/libfreetype.a"); remove("vendor/freetype/libfreetype.a");
remove("src/renderer/font_inter.gen.h"); remove("src/renderer/font_inter.gen.h");
return 0; return 0;
} }
// App bundle paths // App bundle paths
const char *app_dir = nob_temp_sprintf("%s/autosample.app", build_dir); const char *app_dir = temp_sprintf("%s/autosample.app", build_dir);
const char *contents = nob_temp_sprintf("%s/Contents", app_dir); const char *contents = temp_sprintf("%s/Contents", app_dir);
const char *macos_dir = nob_temp_sprintf("%s/Contents/MacOS", app_dir); const char *macos_dir = temp_sprintf("%s/Contents/MacOS", app_dir);
const char *res_dir = nob_temp_sprintf("%s/Contents/Resources", app_dir); const char *res_dir = temp_sprintf("%s/Contents/Resources", app_dir);
const char *binary_path = nob_temp_sprintf("%s/Contents/MacOS/autosample", app_dir); const char *binary_path = temp_sprintf("%s/Contents/MacOS/autosample", app_dir);
if (!nob_mkdir_if_not_exists(build_dir)) return 1; if (!mkdir_if_not_exists(build_dir)) return 1;
if (!nob_mkdir_if_not_exists(app_dir)) return 1; if (!mkdir_if_not_exists(app_dir)) return 1;
if (!nob_mkdir_if_not_exists(contents)) return 1; if (!mkdir_if_not_exists(contents)) return 1;
if (!nob_mkdir_if_not_exists(macos_dir)) return 1; if (!mkdir_if_not_exists(macos_dir)) return 1;
if (!nob_mkdir_if_not_exists(res_dir)) return 1; if (!mkdir_if_not_exists(res_dir)) return 1;
// Build static libraries // Build static libraries
if (!build_freetype_lib(build_dir, debug)) return 1; if (!build_freetype_lib(build_dir, debug)) return 1;
@@ -195,41 +190,42 @@ int main(int argc, char **argv) {
// Unity build: single clang invocation compiles main.c (which #includes everything) // Unity build: single clang invocation compiles main.c (which #includes everything)
{ {
Nob_Cmd cmd = {0}; Cmd cmd = {0};
nob_cmd_append(&cmd, "clang"); cmd_append(&cmd, "clang");
nob_cmd_append(&cmd, "-std=c11", "-x", "objective-c"); cmd_append(&cmd, "-std=c11", "-x", "objective-c");
nob_cmd_append(&cmd, "-Wall", "-Wextra", "-Wno-missing-field-initializers"); cmd_append(&cmd, "-Wall", "-Wextra", "-Wno-missing-field-initializers");
nob_cmd_append(&cmd, "-Wno-deprecated-declarations"); cmd_append(&cmd, "-Wno-deprecated-declarations");
nob_cmd_append(&cmd, "-Isrc", "-Ivendor/clay"); cmd_append(&cmd, "-Isrc");
nob_cmd_append(&cmd, "-Ivendor/nanosvg"); cmd_append(&cmd, "-isystem", "vendor/clay");
nob_cmd_append(&cmd, "-Ivendor/freetype/include"); cmd_append(&cmd, "-isystem", "vendor/nanosvg");
cmd_append(&cmd, "-isystem", "vendor/freetype/include");
if (debug) { if (debug) {
nob_cmd_append(&cmd, "-g", "-O0", "-D_DEBUG"); cmd_append(&cmd, "-g", "-O0", "-D_DEBUG");
} else { } else {
nob_cmd_append(&cmd, "-O2", "-DNDEBUG"); cmd_append(&cmd, "-O2", "-DNDEBUG");
} }
nob_cmd_append(&cmd, "-o", binary_path); cmd_append(&cmd, "-o", binary_path);
nob_cmd_append(&cmd, "src/main.c"); cmd_append(&cmd, "src/daw/daw_main.c");
// Reset language mode so .a is treated as a library, not source // Reset language mode so .a is treated as a library, not source
nob_cmd_append(&cmd, "-x", "none"); cmd_append(&cmd, "-x", "none");
nob_cmd_append(&cmd, "vendor/freetype/libfreetype.a"); cmd_append(&cmd, "vendor/freetype/libfreetype.a");
{ {
size_t i; size_t i;
for (i = 0; i < NOB_ARRAY_LEN(frameworks); i++) for (i = 0; i < ARRAY_LEN(frameworks); i++)
nob_cmd_append(&cmd, frameworks[i]); cmd__append(&cmd, 1, frameworks[i]);
} }
nob_cmd_append(&cmd, "-lm"); cmd_append(&cmd, "-lm");
{ Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; } if (!cmd_run(&cmd)) return 1;
} }
// Write Info.plist // Write Info.plist
{ {
const char *plist_path = nob_temp_sprintf("%s/Contents/Info.plist", app_dir); const char *plist_path = temp_sprintf("%s/Contents/Info.plist", app_dir);
nob_write_entire_file(plist_path, const char *plist =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n" "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n"
" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" " \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
@@ -252,35 +248,11 @@ int main(int argc, char **argv) {
" <key>NSSupportsAutomaticGraphicsSwitching</key>\n" " <key>NSSupportsAutomaticGraphicsSwitching</key>\n"
" <true/>\n" " <true/>\n"
"</dict>\n" "</dict>\n"
"</plist>\n", "</plist>\n";
strlen( write_entire_file(plist_path, plist, strlen(plist));
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\"\n"
" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">\n"
"<dict>\n"
" <key>CFBundleExecutable</key>\n"
" <string>autosample</string>\n"
" <key>CFBundleIdentifier</key>\n"
" <string>com.autosample.app</string>\n"
" <key>CFBundleName</key>\n"
" <string>autosample</string>\n"
" <key>CFBundleVersion</key>\n"
" <string>0.1</string>\n"
" <key>CFBundleShortVersionString</key>\n"
" <string>0.1</string>\n"
" <key>CFBundlePackageType</key>\n"
" <string>APPL</string>\n"
" <key>NSHighResolutionCapable</key>\n"
" <true/>\n"
" <key>NSSupportsAutomaticGraphicsSwitching</key>\n"
" <true/>\n"
"</dict>\n"
"</plist>\n"
));
} }
nob_log(NOB_INFO, "Build complete: %s", app_dir); build_log(LOG_INFO, "Build complete: %s", app_dir);
return 0; return 0;
} }
@@ -309,20 +281,19 @@ static const char *link_libs[] = {
// SPIR-V shader compilation — compiles .glsl to .spv, then embeds as C header // SPIR-V shader compilation — compiles .glsl to .spv, then embeds as C header
static bool compile_shader(const char *glslc_path, const char *src, const char *spv_path, const char *stage) { static bool compile_shader(const char *glslc_path, const char *src, const char *spv_path, const char *stage) {
Nob_Cmd cmd = {0}; Cmd cmd = {0};
nob_cmd_append(&cmd, glslc_path, nob_temp_sprintf("-fshader-stage=%s", stage), "-o", spv_path, src); cmd_append(&cmd, glslc_path, temp_sprintf("-fshader-stage=%s", stage), "-o", spv_path, src);
Nob_Cmd_Opt opt = {0}; return cmd_run(&cmd);
return nob_cmd_run_opt(&cmd, opt);
} }
static bool embed_spirv(const char *spv_path, const char *header_path, const char *array_name) { static bool embed_spirv(const char *spv_path, const char *header_path, const char *array_name) {
Nob_String_Builder sb = {0}; String_Builder sb = {0};
if (!nob_read_entire_file(spv_path, &sb)) return false; if (!sb_read_file(&sb, spv_path)) return false;
FILE *out = fopen(header_path, "wb"); FILE *out = fopen(header_path, "wb");
if (!out) { if (!out) {
nob_log(NOB_ERROR, "Could not open %s for writing", header_path); build_log(LOG_ERROR, "Could not open %s for writing", header_path);
nob_sb_free(sb); sb_free(&sb);
return false; return false;
} }
@@ -341,96 +312,93 @@ static bool embed_spirv(const char *spv_path, const char *header_path, const cha
fprintf(out, "};\n"); fprintf(out, "};\n");
fclose(out); fclose(out);
nob_sb_free(sb); sb_free(&sb);
nob_log(NOB_INFO, "Generated %s (%zu bytes)", header_path, sb.count); build_log(LOG_INFO, "Generated %s (%zu bytes)", header_path, sb.count);
return true; return true;
} }
static bool compile_and_embed_shaders(const char *build_dir) { static bool compile_and_embed_shaders(const char *build_dir) {
const char *vk_sdk = get_vulkan_sdk_path(); const char *vk_sdk = get_vulkan_sdk_path();
const char *glslc = nob_temp_sprintf("%s\\Bin\\glslc.exe", vk_sdk); const char *glslc = temp_sprintf("%s\\Bin\\glslc.exe", vk_sdk);
const char *vert_src = "src/renderer/ui.v.glsl"; const char *vert_src = "src/renderer/ui.v.glsl";
const char *frag_src = "src/renderer/ui.f.glsl"; const char *frag_src = "src/renderer/ui.f.glsl";
const char *vert_spv = nob_temp_sprintf("%s/ui_vert.spv", build_dir); const char *vert_spv = temp_sprintf("%s/ui_vert.spv", build_dir);
const char *frag_spv = nob_temp_sprintf("%s/ui_frag.spv", build_dir); const char *frag_spv = temp_sprintf("%s/ui_frag.spv", build_dir);
const char *vert_hdr = "src/renderer/ui_vert.spv.h"; const char *vert_hdr = "src/renderer/ui_vert.spv.h";
const char *frag_hdr = "src/renderer/ui_frag.spv.h"; const char *frag_hdr = "src/renderer/ui_frag.spv.h";
// Check if rebuild needed // Check if rebuild needed
if (nob_needs_rebuild1(vert_hdr, vert_src)) { if (needs_rebuild1(vert_hdr, vert_src)) {
nob_log(NOB_INFO, "Compiling vertex shader"); build_log(LOG_INFO, "Compiling vertex shader");
if (!compile_shader(glslc, vert_src, vert_spv, "vertex")) return false; if (!compile_shader(glslc, vert_src, vert_spv, "vertex")) return false;
if (!embed_spirv(vert_spv, vert_hdr, "ui_vert_spv")) return false; if (!embed_spirv(vert_spv, vert_hdr, "ui_vert_spv")) return false;
} else { } else {
nob_log(NOB_INFO, "Vertex shader is up to date"); build_log(LOG_INFO, "Vertex shader is up to date");
} }
if (nob_needs_rebuild1(frag_hdr, frag_src)) { if (needs_rebuild1(frag_hdr, frag_src)) {
nob_log(NOB_INFO, "Compiling fragment shader"); build_log(LOG_INFO, "Compiling fragment shader");
if (!compile_shader(glslc, frag_src, frag_spv, "fragment")) return false; if (!compile_shader(glslc, frag_src, frag_spv, "fragment")) return false;
if (!embed_spirv(frag_spv, frag_hdr, "ui_frag_spv")) return false; if (!embed_spirv(frag_spv, frag_hdr, "ui_frag_spv")) return false;
} else { } else {
nob_log(NOB_INFO, "Fragment shader is up to date"); build_log(LOG_INFO, "Fragment shader is up to date");
} }
return true; return true;
} }
static bool build_freetype_lib(const char *build_dir, bool debug) { static bool build_freetype_lib(const char *build_dir, bool debug) {
const char *obj_dir = nob_temp_sprintf("%s\\freetype_obj", build_dir); const char *obj_dir = temp_sprintf("%s\\freetype_obj", build_dir);
const char *lib_path = debug ? "vendor\\freetype\\freetype_d.lib" : "vendor\\freetype\\freetype.lib"; const char *lib_path = debug ? "vendor\\freetype\\freetype_d.lib" : "vendor\\freetype\\freetype.lib";
if (!nob_needs_rebuild(lib_path, freetype_sources, NOB_ARRAY_LEN(freetype_sources))) { if (!needs_rebuild(lib_path, freetype_sources, ARRAY_LEN(freetype_sources))) {
nob_log(NOB_INFO, "freetype is up to date"); build_log(LOG_INFO, "freetype is up to date");
return true; return true;
} }
if (!nob_mkdir_if_not_exists(obj_dir)) return false; if (!mkdir_if_not_exists(obj_dir)) return false;
for (size_t i = 0; i < NOB_ARRAY_LEN(freetype_sources); i++) { for (size_t i = 0; i < ARRAY_LEN(freetype_sources); i++) {
const char *src = freetype_sources[i]; const char *src = freetype_sources[i];
Nob_Cmd cmd = {0}; Cmd cmd = {0};
nob_cmd_append(&cmd, "cl.exe", "/nologo", "/c"); cmd_append(&cmd, "cl.exe", "/nologo", "/c");
nob_cmd_append(&cmd, "/std:c11"); cmd_append(&cmd, "/std:c11");
nob_cmd_append(&cmd, "/DFT2_BUILD_LIBRARY"); cmd_append(&cmd, "/DFT2_BUILD_LIBRARY");
nob_cmd_append(&cmd, "/Ivendor/freetype/include"); cmd_append(&cmd, "/Ivendor/freetype/include");
nob_cmd_append(&cmd, "/W3"); cmd_append(&cmd, "/W0");
if (debug) { if (debug) {
nob_cmd_append(&cmd, "/MTd", "/Zi"); cmd_append(&cmd, "/MTd", "/Zi");
} else { } else {
nob_cmd_append(&cmd, "/MT", "/O2"); cmd_append(&cmd, "/MT", "/O2");
} }
nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", obj_dir)); cmd_append(&cmd, temp_sprintf("/Fo:%s/", obj_dir));
nob_cmd_append(&cmd, src); cmd_append(&cmd, src);
Nob_Cmd_Opt copt = {0}; if (!cmd_run(&cmd)) return false;
if (!nob_cmd_run_opt(&cmd, copt)) return false;
} }
// Archive // Archive
{ {
Nob_Cmd cmd = {0}; Cmd cmd = {0};
nob_cmd_append(&cmd, "lib.exe", "/nologo", nob_temp_sprintf("/OUT:%s", lib_path)); cmd_append(&cmd, "lib.exe", "/nologo", temp_sprintf("/OUT:%s", lib_path));
nob_cmd_append(&cmd, nob_temp_sprintf("%s/*.obj", obj_dir)); cmd_append(&cmd, temp_sprintf("%s/*.obj", obj_dir));
Nob_Cmd_Opt copt = {0}; if (!cmd_run(&cmd)) return false;
if (!nob_cmd_run_opt(&cmd, copt)) return false;
} }
// Clean up obj dir // Clean up obj dir
{ {
Nob_Cmd cmd = {0}; Cmd cmd = {0};
nob_cmd_append(&cmd, "cmd.exe", "/c", cmd_append(&cmd, "cmd.exe", "/c",
nob_temp_sprintf("if exist %s rmdir /s /q %s", obj_dir, obj_dir)); temp_sprintf("if exist %s rmdir /s /q %s", obj_dir, obj_dir));
Nob_Cmd_Opt copt = {0}; cmd_run(&cmd);
nob_cmd_run_opt(&cmd, copt);
} }
nob_log(NOB_INFO, "Built %s", lib_path); build_log(LOG_INFO, "Built %s", lib_path);
return true; return true;
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
NOB_GO_REBUILD_URSELF(argc, argv); GO_REBUILD_URSELF(argc, argv);
bool debug = false; bool debug = false;
bool clean = false; bool clean = false;
@@ -438,8 +406,8 @@ int main(int argc, char **argv) {
if (strcmp(argv[i], "debug") == 0) debug = true; if (strcmp(argv[i], "debug") == 0) debug = true;
else if (strcmp(argv[i], "clean") == 0) clean = true; else if (strcmp(argv[i], "clean") == 0) clean = true;
else { else {
nob_log(NOB_ERROR, "unknown argument: %s", argv[i]); build_log(LOG_ERROR, "unknown argument: %s", argv[i]);
nob_log(NOB_ERROR, "usage: %s [debug] [clean]", argv[0]); build_log(LOG_ERROR, "usage: %s [debug] [clean]", argv[0]);
return 1; return 1;
} }
} }
@@ -447,13 +415,13 @@ int main(int argc, char **argv) {
const char *build_dir = debug ? "build_debug" : "build_release"; const char *build_dir = debug ? "build_debug" : "build_release";
if (clean) { if (clean) {
nob_log(NOB_INFO, "Cleaning build artifacts"); build_log(LOG_INFO, "Cleaning build artifacts");
{ Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cmd.exe", "/c", { Cmd cmd = {0}; cmd_append(&cmd, "cmd.exe", "/c",
"if exist build_debug rmdir /s /q build_debug"); "if exist build_debug rmdir /s /q build_debug");
Nob_Cmd_Opt opt = {0}; nob_cmd_run_opt(&cmd, opt); } cmd_run(&cmd); }
{ Nob_Cmd cmd = {0}; nob_cmd_append(&cmd, "cmd.exe", "/c", { Cmd cmd = {0}; cmd_append(&cmd, "cmd.exe", "/c",
"if exist build_release rmdir /s /q build_release"); "if exist build_release rmdir /s /q build_release");
Nob_Cmd_Opt opt = {0}; nob_cmd_run_opt(&cmd, opt); } cmd_run(&cmd); }
remove("vendor\\freetype\\freetype.lib"); remove("vendor\\freetype\\freetype.lib");
remove("vendor\\freetype\\freetype_d.lib"); remove("vendor\\freetype\\freetype_d.lib");
remove("src\\renderer\\font_inter.gen.h"); remove("src\\renderer\\font_inter.gen.h");
@@ -462,7 +430,7 @@ int main(int argc, char **argv) {
return 0; return 0;
} }
if (!nob_mkdir_if_not_exists(build_dir)) return 1; if (!mkdir_if_not_exists(build_dir)) return 1;
// Build static libraries // Build static libraries
if (!build_freetype_lib(build_dir, debug)) return 1; if (!build_freetype_lib(build_dir, debug)) return 1;
@@ -475,50 +443,52 @@ int main(int argc, char **argv) {
if (!compile_and_embed_shaders(build_dir)) return 1; if (!compile_and_embed_shaders(build_dir)) return 1;
const char *vk_sdk = get_vulkan_sdk_path(); const char *vk_sdk = get_vulkan_sdk_path();
const char *vk_include = nob_temp_sprintf("/I%s/Include", vk_sdk); const char *vk_lib = temp_sprintf("%s/Lib/vulkan-1.lib", vk_sdk);
const char *vk_lib = nob_temp_sprintf("%s/Lib/vulkan-1.lib", vk_sdk);
// Unity build: single cl.exe invocation compiles main.c (which #includes everything) // Unity build: single cl.exe invocation compiles main.c (which #includes everything)
{ {
Nob_Cmd cmd = {0}; Cmd cmd = {0};
nob_cmd_append(&cmd, "cl.exe"); cmd_append(&cmd, "cl.exe");
nob_cmd_append(&cmd, "/nologo", "/std:c11", "/TC", "/W3"); cmd_append(&cmd, "/nologo", "/std:c11", "/TC", "/W3");
nob_cmd_append(&cmd, "/Isrc", "/Ivendor/clay"); cmd_append(&cmd, "/Isrc");
nob_cmd_append(&cmd, "/Ivendor/nanosvg"); cmd_append(&cmd, "/external:W0");
nob_cmd_append(&cmd, "/Ivendor/freetype/include"); cmd_append(&cmd, "/external:Ivendor/clay");
nob_cmd_append(&cmd, vk_include); cmd_append(&cmd, "/external:Ivendor/nanosvg");
cmd_append(&cmd, "/external:Ivendor/freetype/include");
cmd_append(&cmd, temp_sprintf("/external:I%s/Include", vk_sdk));
if (debug) { if (debug) {
nob_cmd_append(&cmd, "/MTd", "/Zi", "/D_DEBUG"); cmd_append(&cmd, "/MTd", "/Zi", "/D_DEBUG");
} else { } else {
nob_cmd_append(&cmd, "/MT", "/Zi", "/O2", "/DNDEBUG"); cmd_append(&cmd, "/MT", "/Zi", "/O2", "/DNDEBUG");
} }
nob_cmd_append(&cmd, nob_temp_sprintf("/Fe:%s/autosample.exe", build_dir)); cmd_append(&cmd, temp_sprintf("/Fe:%s/autosample.exe", build_dir));
nob_cmd_append(&cmd, nob_temp_sprintf("/Fo:%s/", build_dir)); cmd_append(&cmd, temp_sprintf("/Fo:%s/", build_dir));
nob_cmd_append(&cmd, nob_temp_sprintf("/Fd:%s/autosample.pdb", build_dir)); cmd_append(&cmd, temp_sprintf("/Fd:%s/autosample.pdb", build_dir));
nob_cmd_append(&cmd, "src/main.c"); cmd_append(&cmd, "src/daw/daw_main.c");
nob_cmd_append(&cmd, "/link"); cmd_append(&cmd, "/link");
nob_cmd_append(&cmd, "/MACHINE:X64"); cmd_append(&cmd, "/MACHINE:X64");
nob_cmd_append(&cmd, "/SUBSYSTEM:CONSOLE"); cmd_append(&cmd, debug ? "/SUBSYSTEM:CONSOLE" : "/SUBSYSTEM:WINDOWS");
nob_cmd_append(&cmd, nob_temp_sprintf("/PDB:%s/autosample.pdb", build_dir)); if (!debug) cmd_append(&cmd, "/ENTRY:mainCRTStartup");
nob_cmd_append(&cmd, "/DEBUG"); cmd_append(&cmd, temp_sprintf("/PDB:%s/autosample.pdb", build_dir));
nob_cmd_append(&cmd, debug ? "vendor/freetype/freetype_d.lib" : "vendor/freetype/freetype.lib"); cmd_append(&cmd, "/DEBUG");
nob_cmd_append(&cmd, vk_lib); cmd_append(&cmd, debug ? "vendor/freetype/freetype_d.lib" : "vendor/freetype/freetype.lib");
cmd_append(&cmd, vk_lib);
{ {
size_t i; size_t i;
for (i = 0; i < NOB_ARRAY_LEN(link_libs); i++) for (i = 0; i < ARRAY_LEN(link_libs); i++)
nob_cmd_append(&cmd, link_libs[i]); cmd__append(&cmd, 1, link_libs[i]);
} }
{ Nob_Cmd_Opt opt = {0}; if (!nob_cmd_run_opt(&cmd, opt)) return 1; } if (!cmd_run(&cmd)) return 1;
} }
// Clean up obj files // Clean up obj files
nob_delete_file(nob_temp_sprintf("%s/main.obj", build_dir)); delete_file(temp_sprintf("%s/daw_main.obj", build_dir));
nob_log(NOB_INFO, "Build complete: %s/autosample.exe", build_dir); build_log(LOG_INFO, "Build complete: %s/autosample.exe", build_dir);
return 0; return 0;
} }

600
build.h Normal file
View File

@@ -0,0 +1,600 @@
// build.h — Minimal C build system (stb-style single header)
//
// Define BUILD_IMPLEMENTATION in exactly one file before including this header.
//
// Bootstrap (one-time):
// Windows: cl /nologo build.c
// macOS: cc build.c -o build
// After that, just run ./build (or build.exe) — it rebuilds itself.
#ifndef BUILD_H
#define BUILD_H
#ifdef _MSC_VER
# define _CRT_SECURE_NO_WARNINGS
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <errno.h>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
# include <direct.h>
# include <io.h>
#else
# include <sys/stat.h>
# include <sys/wait.h>
# include <unistd.h>
#endif
////////////////////////////////
// Macros
#define ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
// Self-rebuild: call at the top of main(). Recompiles the build script
// if the source file is newer than the running binary, then re-executes.
#define GO_REBUILD_URSELF(argc, argv) \
go_rebuild_urself((argc), (argv), __FILE__)
////////////////////////////////
// Logging
typedef enum {
LOG_INFO,
LOG_WARNING,
LOG_ERROR,
} Log_Level;
void build_log(Log_Level level, const char *fmt, ...);
////////////////////////////////
// Temp allocator — ring buffer for short-lived sprintf results
#ifndef TEMP_CAPACITY
#define TEMP_CAPACITY (8 * 1024 * 1024)
#endif
char *temp_sprintf(const char *fmt, ...);
void temp_reset(void);
////////////////////////////////
// String builder — growable byte buffer
typedef struct {
char *items;
size_t count;
size_t capacity;
} String_Builder;
bool sb_read_file(String_Builder *sb, const char *path);
void sb_free(String_Builder *sb);
////////////////////////////////
// File I/O
bool write_entire_file(const char *path, const void *data, size_t size);
bool delete_file(const char *path);
bool rename_file(const char *old_path, const char *new_path);
bool mkdir_if_not_exists(const char *path);
////////////////////////////////
// Rebuild checking — returns 1 if rebuild needed, 0 if up to date, -1 on error
int needs_rebuild(const char *output, const char **inputs, size_t count);
int needs_rebuild1(const char *output, const char *input);
////////////////////////////////
// Command builder and runner
typedef struct {
const char **items;
size_t count;
size_t capacity;
} Cmd;
// Appends arguments to a command. Use via the macro which auto-counts args.
void cmd__append(Cmd *cmd, size_t n, ...);
void cmd__append_arr(Cmd *cmd, const char **args, size_t n);
#define cmd_append(cmd, ...) do { \
const char *_cmd_args[] = {__VA_ARGS__}; \
cmd__append_arr((cmd), _cmd_args, sizeof(_cmd_args) / sizeof(_cmd_args[0])); \
} while(0)
// Runs the command synchronously, resets cmd->count to 0, returns success.
bool cmd_run(Cmd *cmd);
// Frees the command's allocated memory.
void cmd_free(Cmd *cmd);
////////////////////////////////
// Self-rebuild
void go_rebuild_urself(int argc, char **argv, const char *source);
#endif // BUILD_H
////////////////////////////////
// Implementation
////////////////////////////////
#ifdef BUILD_IMPLEMENTATION
////////////////////////////////
// Temp allocator
static size_t g_temp_size = 0;
static char g_temp[TEMP_CAPACITY];
void temp_reset(void) {
g_temp_size = 0;
}
static char *temp_alloc(size_t size) {
if (g_temp_size + size > TEMP_CAPACITY) {
g_temp_size = 0; // wrap around
}
char *result = g_temp + g_temp_size;
g_temp_size += size;
return result;
}
char *temp_sprintf(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
va_list args2;
va_copy(args2, args);
int n = vsnprintf(NULL, 0, fmt, args);
va_end(args);
char *result = temp_alloc(n + 1);
vsnprintf(result, n + 1, fmt, args2);
va_end(args2);
return result;
}
////////////////////////////////
// Logging
void build_log(Log_Level level, const char *fmt, ...) {
switch (level) {
case LOG_INFO: fprintf(stderr, "[INFO] "); break;
case LOG_WARNING: fprintf(stderr, "[WARNING] "); break;
case LOG_ERROR: fprintf(stderr, "[ERROR] "); break;
}
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
fprintf(stderr, "\n");
}
////////////////////////////////
// String builder
static void sb_ensure(String_Builder *sb, size_t needed) {
if (sb->count + needed <= sb->capacity) return;
size_t new_cap = sb->capacity ? sb->capacity * 2 : 256;
while (new_cap < sb->count + needed) new_cap *= 2;
sb->items = (char *)realloc(sb->items, new_cap);
sb->capacity = new_cap;
}
bool sb_read_file(String_Builder *sb, const char *path) {
FILE *f = fopen(path, "rb");
if (!f) {
build_log(LOG_ERROR, "Could not open %s: %s", path, strerror(errno));
return false;
}
fseek(f, 0, SEEK_END);
#ifdef _WIN32
long long m = _telli64(_fileno(f));
#else
long long m = ftell(f);
#endif
if (m < 0) {
build_log(LOG_ERROR, "Could not get size of %s: %s", path, strerror(errno));
fclose(f);
return false;
}
fseek(f, 0, SEEK_SET);
sb_ensure(sb, (size_t)m);
fread(sb->items + sb->count, (size_t)m, 1, f);
if (ferror(f)) {
build_log(LOG_ERROR, "Could not read %s: %s", path, strerror(errno));
fclose(f);
return false;
}
sb->count += (size_t)m;
fclose(f);
return true;
}
void sb_free(String_Builder *sb) {
free(sb->items);
sb->items = NULL;
sb->count = 0;
sb->capacity = 0;
}
////////////////////////////////
// File I/O
bool write_entire_file(const char *path, const void *data, size_t size) {
FILE *f = fopen(path, "wb");
if (!f) {
build_log(LOG_ERROR, "Could not open %s for writing: %s", path, strerror(errno));
return false;
}
const char *buf = (const char *)data;
while (size > 0) {
size_t n = fwrite(buf, 1, size, f);
if (ferror(f)) {
build_log(LOG_ERROR, "Could not write to %s: %s", path, strerror(errno));
fclose(f);
return false;
}
size -= n;
buf += n;
}
fclose(f);
return true;
}
bool delete_file(const char *path) {
build_log(LOG_INFO, "Deleting %s", path);
#ifdef _WIN32
DWORD attr = GetFileAttributesA(path);
if (attr == INVALID_FILE_ATTRIBUTES) return true; // doesn't exist
if (attr & FILE_ATTRIBUTE_DIRECTORY) {
if (!RemoveDirectoryA(path)) {
build_log(LOG_ERROR, "Could not delete directory %s", path);
return false;
}
} else {
if (!DeleteFileA(path)) {
build_log(LOG_ERROR, "Could not delete file %s", path);
return false;
}
}
#else
if (remove(path) < 0 && errno != ENOENT) {
build_log(LOG_ERROR, "Could not delete %s: %s", path, strerror(errno));
return false;
}
#endif
return true;
}
bool rename_file(const char *old_path, const char *new_path) {
build_log(LOG_INFO, "Renaming %s -> %s", old_path, new_path);
#ifdef _WIN32
if (!MoveFileEx(old_path, new_path, MOVEFILE_REPLACE_EXISTING)) {
build_log(LOG_ERROR, "Could not rename %s to %s", old_path, new_path);
return false;
}
#else
if (rename(old_path, new_path) < 0) {
build_log(LOG_ERROR, "Could not rename %s to %s: %s", old_path, new_path, strerror(errno));
return false;
}
#endif
return true;
}
bool mkdir_if_not_exists(const char *path) {
#ifdef _WIN32
int result = _mkdir(path);
#else
int result = mkdir(path, 0755);
#endif
if (result < 0) {
if (errno == EEXIST) return true;
build_log(LOG_ERROR, "Could not create directory %s: %s", path, strerror(errno));
return false;
}
build_log(LOG_INFO, "Created directory %s", path);
return true;
}
////////////////////////////////
// Rebuild checking
int needs_rebuild(const char *output, const char **inputs, size_t count) {
#ifdef _WIN32
HANDLE out_h = CreateFile(output, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
if (out_h == INVALID_HANDLE_VALUE) {
if (GetLastError() == ERROR_FILE_NOT_FOUND) return 1;
build_log(LOG_ERROR, "Could not open %s", output);
return -1;
}
FILETIME out_time;
if (!GetFileTime(out_h, NULL, NULL, &out_time)) {
CloseHandle(out_h);
build_log(LOG_ERROR, "Could not get time of %s", output);
return -1;
}
CloseHandle(out_h);
for (size_t i = 0; i < count; i++) {
HANDLE in_h = CreateFile(inputs[i], GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
if (in_h == INVALID_HANDLE_VALUE) {
build_log(LOG_ERROR, "Could not open %s", inputs[i]);
return -1;
}
FILETIME in_time;
if (!GetFileTime(in_h, NULL, NULL, &in_time)) {
CloseHandle(in_h);
build_log(LOG_ERROR, "Could not get time of %s", inputs[i]);
return -1;
}
CloseHandle(in_h);
if (CompareFileTime(&in_time, &out_time) == 1) return 1;
}
return 0;
#else
struct stat sb;
memset(&sb, 0, sizeof(sb));
if (stat(output, &sb) < 0) {
if (errno == ENOENT) return 1;
build_log(LOG_ERROR, "Could not stat %s: %s", output, strerror(errno));
return -1;
}
time_t out_time = sb.st_mtime;
for (size_t i = 0; i < count; i++) {
if (stat(inputs[i], &sb) < 0) {
build_log(LOG_ERROR, "Could not stat %s: %s", inputs[i], strerror(errno));
return -1;
}
if (sb.st_mtime > out_time) return 1;
}
return 0;
#endif
}
int needs_rebuild1(const char *output, const char *input) {
return needs_rebuild(output, &input, 1);
}
////////////////////////////////
// Command builder and runner
static void cmd__grow(Cmd *cmd) {
size_t new_cap = cmd->capacity ? cmd->capacity * 2 : 32;
cmd->items = (const char **)realloc(cmd->items, new_cap * sizeof(const char *));
cmd->capacity = new_cap;
}
void cmd__append(Cmd *cmd, size_t n, ...) {
va_list args;
va_start(args, n);
for (size_t i = 0; i < n; i++) {
const char *arg = va_arg(args, const char *);
if (cmd->count >= cmd->capacity) cmd__grow(cmd);
cmd->items[cmd->count++] = arg;
}
va_end(args);
}
void cmd__append_arr(Cmd *cmd, const char **args, size_t n) {
for (size_t i = 0; i < n; i++) {
if (cmd->count >= cmd->capacity) cmd__grow(cmd);
cmd->items[cmd->count++] = args[i];
}
}
static void cmd_render(Cmd *cmd, char *buf, size_t buf_size) {
size_t pos = 0;
for (size_t i = 0; i < cmd->count && pos < buf_size - 1; i++) {
if (i > 0 && pos < buf_size - 1) buf[pos++] = ' ';
const char *arg = cmd->items[i];
size_t len = strlen(arg);
if (pos + len < buf_size - 1) {
memcpy(buf + pos, arg, len);
pos += len;
}
}
buf[pos] = '\0';
}
#ifdef _WIN32
// Properly quote a command line for CreateProcess on Windows.
static void cmd_quote_win32(Cmd *cmd, String_Builder *quoted) {
for (size_t i = 0; i < cmd->count; i++) {
const char *arg = cmd->items[i];
size_t len = strlen(arg);
if (i > 0) {
sb_ensure(quoted, 1);
quoted->items[quoted->count++] = ' ';
}
if (len != 0 && strpbrk(arg, " \t\n\v\"") == NULL) {
sb_ensure(quoted, len);
memcpy(quoted->items + quoted->count, arg, len);
quoted->count += len;
} else {
sb_ensure(quoted, len * 2 + 3);
quoted->items[quoted->count++] = '"';
size_t backslashes = 0;
for (size_t j = 0; j < len; j++) {
char c = arg[j];
if (c == '\\') {
backslashes++;
} else {
if (c == '"') {
for (size_t k = 0; k < backslashes + 1; k++)
quoted->items[quoted->count++] = '\\';
}
backslashes = 0;
}
quoted->items[quoted->count++] = c;
}
for (size_t k = 0; k < backslashes; k++)
quoted->items[quoted->count++] = '\\';
quoted->items[quoted->count++] = '"';
}
}
sb_ensure(quoted, 1);
quoted->items[quoted->count] = '\0';
}
#endif
bool cmd_run(Cmd *cmd) {
if (cmd->count == 0) {
build_log(LOG_ERROR, "Cannot run empty command");
return false;
}
// Log the command
{
char render_buf[4096];
cmd_render(cmd, render_buf, sizeof(render_buf));
build_log(LOG_INFO, "CMD: %s", render_buf);
}
#ifdef _WIN32
STARTUPINFO si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
si.dwFlags |= STARTF_USESTDHANDLES;
PROCESS_INFORMATION pi;
memset(&pi, 0, sizeof(pi));
String_Builder quoted = {0};
cmd_quote_win32(cmd, &quoted);
BOOL ok = CreateProcessA(NULL, quoted.items, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi);
sb_free(&quoted);
cmd->count = 0;
if (!ok) {
build_log(LOG_ERROR, "Could not create process for %s", cmd->items[0]);
return false;
}
CloseHandle(pi.hThread);
WaitForSingleObject(pi.hProcess, INFINITE);
DWORD exit_code;
if (!GetExitCodeProcess(pi.hProcess, &exit_code)) {
build_log(LOG_ERROR, "Could not get exit code");
CloseHandle(pi.hProcess);
return false;
}
CloseHandle(pi.hProcess);
if (exit_code != 0) {
build_log(LOG_ERROR, "Command exited with code %lu", exit_code);
return false;
}
return true;
#else
pid_t pid = fork();
if (pid < 0) {
build_log(LOG_ERROR, "Could not fork: %s", strerror(errno));
cmd->count = 0;
return false;
}
if (pid == 0) {
// Child: build null-terminated argv
const char **argv_null = (const char **)malloc((cmd->count + 1) * sizeof(const char *));
memcpy(argv_null, cmd->items, cmd->count * sizeof(const char *));
argv_null[cmd->count] = NULL;
execvp(argv_null[0], (char *const *)argv_null);
build_log(LOG_ERROR, "Could not exec %s: %s", argv_null[0], strerror(errno));
exit(1);
}
cmd->count = 0;
int wstatus;
for (;;) {
if (waitpid(pid, &wstatus, 0) < 0) {
build_log(LOG_ERROR, "Could not wait on pid %d: %s", pid, strerror(errno));
return false;
}
if (WIFEXITED(wstatus)) {
int code = WEXITSTATUS(wstatus);
if (code != 0) {
build_log(LOG_ERROR, "Command exited with code %d", code);
return false;
}
return true;
}
if (WIFSIGNALED(wstatus)) {
build_log(LOG_ERROR, "Command killed by signal %d", WTERMSIG(wstatus));
return false;
}
}
#endif
}
void cmd_free(Cmd *cmd) {
free(cmd->items);
cmd->items = NULL;
cmd->count = 0;
cmd->capacity = 0;
}
////////////////////////////////
// Self-rebuild
#ifdef _WIN32
# define REBUILD_CMD(binary, source) "cl.exe", "/nologo", temp_sprintf("/Fe:%s", binary), source
#else
# define REBUILD_CMD(binary, source) "cc", "-o", binary, source
#endif
void go_rebuild_urself(int argc, char **argv, const char *source) {
const char *binary = argv[0];
#ifdef _WIN32
// Ensure .exe extension for Windows
size_t len = strlen(binary);
if (len < 4 || strcmp(binary + len - 4, ".exe") != 0) {
binary = temp_sprintf("%s.exe", binary);
}
#endif
int rebuild = needs_rebuild1(binary, source);
if (rebuild < 0) exit(1);
if (rebuild == 0) return;
const char *old_binary = temp_sprintf("%s.old", binary);
if (!rename_file(binary, old_binary)) exit(1);
Cmd cmd = {0};
cmd_append(&cmd, REBUILD_CMD(binary, source));
if (!cmd_run(&cmd)) {
rename_file(old_binary, binary);
exit(1);
}
// Re-execute with the new binary
cmd_append(&cmd, binary);
{
int i;
for (i = 1; i < argc; i++) {
cmd__append(&cmd, 1, argv[i]);
}
}
if (!cmd_run(&cmd)) exit(1);
exit(0);
}
#endif // BUILD_IMPLEMENTATION

View File

@@ -33,7 +33,7 @@
#include "midi/midi_win32.c" #include "midi/midi_win32.c"
#include "audio/audio_asio.c" #include "audio/audio_asio.c"
#endif #endif
#include "menus.c" #include "daw_menus.c"
//////////////////////////////// ////////////////////////////////
// Clay text config helpers // Clay text config helpers
@@ -130,10 +130,8 @@ typedef struct AppState {
F32 demo_slider_v; F32 demo_slider_v;
F32 demo_fader; F32 demo_fader;
// Scrollbar drag state // Main content scroll state
B32 scrollbar_dragging; UI_ScrollState main_scroll;
F32 scrollbar_drag_start_y;
F32 scrollbar_drag_start_scroll;
// Audio device selection // Audio device selection
S32 audio_device_sel; // dropdown index: 0 = None, 1+ = device S32 audio_device_sel; // dropdown index: 0 = None, 1+ = device
@@ -228,21 +226,8 @@ static void build_main_panel(AppState *app) {
ui_tab_bar("MainTabRow", main_tabs, 1, &sel); ui_tab_bar("MainTabRow", main_tabs, 1, &sel);
} }
CLAY(CLAY_ID("MainScrollArea"), ui_scroll_begin("MainScroll", &app->main_scroll);
.layout = { {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
}
) {
CLAY(CLAY_ID("MainContent"),
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { uip(16), uip(16), uip(12), uip(12) },
.childGap = uip(12),
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() },
) {
// Top highlight (beveled edge) // Top highlight (beveled edge)
CLAY(CLAY_ID("MainHighlight"), CLAY(CLAY_ID("MainHighlight"),
.layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } }, .layout = { .sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(1) } },
@@ -410,97 +395,7 @@ static void build_main_panel(AppState *app) {
} }
} }
} }
ui_scroll_end("MainScroll", &app->main_scroll);
// Scrollbar
{
Clay_ScrollContainerData scroll_data = Clay_GetScrollContainerData(CLAY_ID("MainContent"));
if (scroll_data.found && scroll_data.contentDimensions.height > scroll_data.scrollContainerDimensions.height) {
F32 track_h = scroll_data.scrollContainerDimensions.height;
F32 content_h = scroll_data.contentDimensions.height;
F32 visible_ratio = track_h / content_h;
F32 thumb_h = Max(visible_ratio * track_h, uis(24));
F32 scroll_range = content_h - track_h;
F32 scroll_pct = scroll_range > 0 ? -scroll_data.scrollPosition->y / scroll_range : 0;
F32 thumb_y = scroll_pct * (track_h - thumb_h);
F32 bar_w = uis(8);
// Handle scrollbar drag
Clay_ElementId thumb_id = CLAY_ID("MainScrollThumb");
Clay_ElementId track_id = CLAY_ID("MainScrollTrack");
B32 thumb_hovered = Clay_PointerOver(thumb_id);
B32 track_hovered = Clay_PointerOver(track_id);
PlatformInput input = g_wstate.input;
B32 mouse_clicked = input.mouse_down && !input.was_mouse_down;
if (mouse_clicked && thumb_hovered) {
app->scrollbar_dragging = true;
app->scrollbar_drag_start_y = input.mouse_pos.y;
app->scrollbar_drag_start_scroll = scroll_data.scrollPosition->y;
} else if (mouse_clicked && track_hovered && !thumb_hovered) {
// Click on track: jump scroll position so thumb centers on click
Clay_BoundingBox track_bb = Clay_GetElementData(track_id).boundingBox;
F32 click_rel = input.mouse_pos.y - track_bb.y;
F32 target_pct = (click_rel - thumb_h / 2) / (track_h - thumb_h);
if (target_pct < 0) target_pct = 0;
if (target_pct > 1) target_pct = 1;
scroll_data.scrollPosition->y = -target_pct * scroll_range;
// Start dragging from new position
app->scrollbar_dragging = true;
app->scrollbar_drag_start_y = input.mouse_pos.y;
app->scrollbar_drag_start_scroll = scroll_data.scrollPosition->y;
}
if (!input.mouse_down) {
app->scrollbar_dragging = false;
}
if (app->scrollbar_dragging) {
F32 dy = input.mouse_pos.y - app->scrollbar_drag_start_y;
F32 scroll_per_px = scroll_range / (track_h - thumb_h);
F32 new_scroll = app->scrollbar_drag_start_scroll - dy * scroll_per_px;
if (new_scroll > 0) new_scroll = 0;
if (new_scroll < -scroll_range) new_scroll = -scroll_range;
scroll_data.scrollPosition->y = new_scroll;
}
// Thumb color: highlight on hover or drag
Clay_Color thumb_color = g_theme.scrollbar_grab;
if (app->scrollbar_dragging || thumb_hovered) {
thumb_color = (Clay_Color){
(F32)Min((S32)thumb_color.r + 30, 255),
(F32)Min((S32)thumb_color.g + 30, 255),
(F32)Min((S32)thumb_color.b + 30, 255),
thumb_color.a
};
}
CLAY(track_id,
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(bar_w), .height = CLAY_SIZING_GROW() },
},
.backgroundColor = g_theme.scrollbar_bg
) {
CLAY(thumb_id,
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(thumb_h) },
},
.backgroundColor = thumb_color,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
.floating = {
.offset = { 0, thumb_y },
.attachPoints = {
.element = CLAY_ATTACH_POINT_LEFT_TOP,
.parent = CLAY_ATTACH_POINT_LEFT_TOP,
},
.attachTo = CLAY_ATTACH_TO_PARENT,
},
) {}
}
} else {
app->scrollbar_dragging = false;
}
}
} // MainScrollArea
} }
} }

View File

@@ -218,6 +218,14 @@ static Clay_String clay_str(const char *s) {
#define WID(s) CLAY_SID(clay_str(s)) #define WID(s) CLAY_SID(clay_str(s))
#define WIDI(s, i) CLAY_SIDI(clay_str(s), i) #define WIDI(s, i) CLAY_SIDI(clay_str(s), i)
// Sub-element index constants for ui_scroll
enum {
SCROLL_IDX_AREA = 0,
SCROLL_IDX_CONTENT = 1,
SCROLL_IDX_TRACK = 2,
SCROLL_IDX_THUMB = 3,
};
//////////////////////////////// ////////////////////////////////
// Icon // Icon
@@ -2052,3 +2060,135 @@ B32 ui_fader(const char *id, const char *label, F32 *value, F32 max_val, B32 is_
return changed; return changed;
} }
////////////////////////////////
// Scrollable area with custom scrollbar
//
// CLAY() is a for-loop macro, so we can't split begin/end across two function
// calls while keeping the content in the caller. Instead, ui_scroll_begin opens
// the outer wrapper + scrollable content element using Clay__OpenElement /
// Clay__ConfigureOpenElement, and ui_scroll_end closes them and renders the
// scrollbar.
void ui_scroll_begin(const char *id, UI_ScrollState *state) {
(void)state;
Clay_String cs = clay_str(id);
// Open outer row (content + scrollbar side by side)
Clay__OpenElementWithId(CLAY_SIDI(cs, SCROLL_IDX_AREA));
Clay__ConfigureOpenElement((Clay_ElementDeclaration) {
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.layoutDirection = CLAY_LEFT_TO_RIGHT,
},
});
// Open scrollable content area
Clay__OpenElementWithId(CLAY_SIDI(cs, SCROLL_IDX_CONTENT));
Clay__ConfigureOpenElement((Clay_ElementDeclaration) {
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_GROW() },
.padding = { uip(16), uip(16), uip(12), uip(12) },
.childGap = uip(12),
.layoutDirection = CLAY_TOP_TO_BOTTOM,
},
.clip = { .vertical = true, .childOffset = Clay_GetScrollOffset() },
});
// Caller inserts content here, then calls ui_scroll_end()
}
void ui_scroll_end(const char *id, UI_ScrollState *state) {
Clay_String cs = clay_str(id);
// Close scrollable content area
Clay__CloseElement();
// Draw scrollbar
Clay_ElementId content_eid = CLAY_SIDI(cs, SCROLL_IDX_CONTENT);
Clay_ScrollContainerData scroll_data = Clay_GetScrollContainerData(content_eid);
if (scroll_data.found && scroll_data.contentDimensions.height > scroll_data.scrollContainerDimensions.height) {
F32 track_h = scroll_data.scrollContainerDimensions.height;
F32 content_h = scroll_data.contentDimensions.height;
F32 visible_ratio = track_h / content_h;
F32 thumb_h = Max(visible_ratio * track_h, uis(24));
F32 scroll_range = content_h - track_h;
F32 scroll_pct = scroll_range > 0 ? -scroll_data.scrollPosition->y / scroll_range : 0;
F32 thumb_y = scroll_pct * (track_h - thumb_h);
F32 bar_w = uis(8);
Clay_ElementId thumb_eid = CLAY_SIDI(cs, SCROLL_IDX_THUMB);
Clay_ElementId track_eid = CLAY_SIDI(cs, SCROLL_IDX_TRACK);
B32 thumb_hovered = Clay_PointerOver(thumb_eid);
B32 track_hovered = Clay_PointerOver(track_eid);
PlatformInput input = g_wstate.input;
B32 mouse_clicked = input.mouse_down && !input.was_mouse_down;
if (mouse_clicked && thumb_hovered) {
state->dragging = true;
state->drag_start_y = input.mouse_pos.y;
state->drag_start_scroll = scroll_data.scrollPosition->y;
} else if (mouse_clicked && track_hovered && !thumb_hovered) {
Clay_BoundingBox track_bb = Clay_GetElementData(track_eid).boundingBox;
F32 click_rel = input.mouse_pos.y - track_bb.y;
F32 target_pct = (click_rel - thumb_h / 2) / (track_h - thumb_h);
if (target_pct < 0) target_pct = 0;
if (target_pct > 1) target_pct = 1;
scroll_data.scrollPosition->y = -target_pct * scroll_range;
state->dragging = true;
state->drag_start_y = input.mouse_pos.y;
state->drag_start_scroll = scroll_data.scrollPosition->y;
}
if (!input.mouse_down) {
state->dragging = false;
}
if (state->dragging) {
F32 dy = input.mouse_pos.y - state->drag_start_y;
F32 scroll_per_px = scroll_range / (track_h - thumb_h);
F32 new_scroll = state->drag_start_scroll - dy * scroll_per_px;
if (new_scroll > 0) new_scroll = 0;
if (new_scroll < -scroll_range) new_scroll = -scroll_range;
scroll_data.scrollPosition->y = new_scroll;
}
Clay_Color thumb_color = g_theme.scrollbar_grab;
if (state->dragging || thumb_hovered) {
thumb_color = (Clay_Color){
(F32)Min((S32)thumb_color.r + 30, 255),
(F32)Min((S32)thumb_color.g + 30, 255),
(F32)Min((S32)thumb_color.b + 30, 255),
thumb_color.a
};
}
CLAY(track_eid,
.layout = {
.sizing = { .width = CLAY_SIZING_FIXED(bar_w), .height = CLAY_SIZING_GROW() },
},
.backgroundColor = g_theme.scrollbar_bg
) {
CLAY(thumb_eid,
.layout = {
.sizing = { .width = CLAY_SIZING_GROW(), .height = CLAY_SIZING_FIXED(thumb_h) },
},
.backgroundColor = thumb_color,
.cornerRadius = CLAY_CORNER_RADIUS(CORNER_RADIUS),
.floating = {
.offset = { 0, thumb_y },
.attachPoints = {
.element = CLAY_ATTACH_POINT_LEFT_TOP,
.parent = CLAY_ATTACH_POINT_LEFT_TOP,
},
.attachTo = CLAY_ATTACH_TO_PARENT,
},
) {}
}
} else {
state->dragging = false;
}
// Close outer row
Clay__CloseElement();
}

View File

@@ -128,3 +128,25 @@ B32 ui_slider_v(const char *id, const char *label, F32 *value, F32 max_val, B32
// DAW-style fader (vertical slider with fader cap icon). // DAW-style fader (vertical slider with fader cap icon).
B32 ui_fader(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable); B32 ui_fader(const char *id, const char *label, F32 *value, F32 max_val, B32 is_signed, F32 default_val, B32 editable);
////////////////////////////////
// Scrollable area with custom scrollbar
//
// Usage:
// UI_ScrollState scroll = {0}; // persist across frames
// ui_scroll_begin("MyScroll", &scroll);
// /* ... scrollable content ... */
// ui_scroll_end("MyScroll", &scroll);
typedef struct UI_ScrollState {
B32 dragging;
F32 drag_start_y;
F32 drag_start_scroll;
} UI_ScrollState;
// Opens a horizontal row containing a vertical-scroll content area.
// The content area clips vertically and uses Clay_GetScrollOffset().
void ui_scroll_begin(const char *id, UI_ScrollState *state);
// Closes the content area and draws the scrollbar thumb/track.
void ui_scroll_end(const char *id, UI_ScrollState *state);

3156
vendor/nob/nob.h vendored

File diff suppressed because it is too large Load Diff