diff --git a/doc/cheatsheets/cheatsheet_ui.h b/doc/cheatsheets/cheatsheet_ui.h index 3487195..733fbe1 100644 --- a/doc/cheatsheets/cheatsheet_ui.h +++ b/doc/cheatsheets/cheatsheet_ui.h @@ -29,15 +29,17 @@ oc_ui_sig oc_ui_label(const char* label); oc_ui_sig oc_ui_label_str8(oc_str8 label); oc_ui_sig oc_ui_button(const char* label); oc_ui_sig oc_ui_checkbox(const char* name, bool* checked); -oc_ui_box* oc_ui_slider(const char* label, f32 thumbRatio, f32* scrollValue); +oc_ui_box* oc_ui_slider(const char* name, f32* value); +oc_ui_box* oc_ui_scrollbar(const char* name, f32 thumbRatio, f32* scrollValue); oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8 text); oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_info* info); +oc_ui_radio_group_info oc_ui_radio_group(const char* name, oc_ui_radio_group_info* info); void oc_ui_panel_begin(const char* name, oc_ui_flags flags); void oc_ui_panel_end(void); #define oc_ui_panel(s, f) -void oc_ui_menu_bar_begin(const char* label); +void oc_ui_menu_bar_begin(const char* name); void oc_ui_menu_bar_end(void); #define oc_ui_menu_bar(name) @@ -45,11 +47,11 @@ void oc_ui_menu_begin(const char* label); void oc_ui_menu_end(void); #define oc_ui_menu(name) -oc_ui_sig oc_ui_menu_button(const char* name); +oc_ui_sig oc_ui_menu_button(const char* label); -oc_ui_sig oc_ui_tooltip_begin(const char* name); +oc_ui_sig oc_ui_tooltip_begin(const char* label); void oc_ui_tooltip_end(void); -#define oc_ui_tooltip(name) +#define oc_ui_tooltip(label) //------------------------------------------------------------------------------------- // Styling diff --git a/samples/ui/src/main.c b/samples/ui/src/main.c index 49c2fe8..7bcecdb 100644 --- a/samples/ui/src/main.c +++ b/samples/ui/src/main.c @@ -73,6 +73,12 @@ ORCA_EXPORT void oc_on_raw_event(oc_event* event) oc_ui_process_event(event); } +ORCA_EXPORT void oc_on_resize(u32 width, u32 height) +{ + frameSize.x = width; + frameSize.y = height; +} + void log_push(const char* line) { oc_str8_list_push(&logArena, &logLines, (oc_str8)OC_STR8(line)); @@ -158,12 +164,6 @@ void labeled_slider(const char* label, f32* value) } } -ORCA_EXPORT void oc_on_resize(u32 width, u32 height) -{ - frameSize.x = width; - frameSize.y = height; -} - void reset_next_radio_group_to_dark_theme(oc_arena* arena); ORCA_EXPORT void oc_on_frame_refresh(void) @@ -330,7 +330,7 @@ ORCA_EXPORT void oc_on_frame_refresh(void) radioSelected = result.selectedIndex; if(result.changed) { - log_pushf("Selected Radio %i", result.selectedIndex + 1); + log_pushf("Selected %s", options[result.selectedIndex].ptr); } //----------------------------------------------------------------------------- @@ -359,13 +359,13 @@ ORCA_EXPORT void oc_on_frame_refresh(void) .size.height = { OC_UI_SIZE_TEXT } }, OC_UI_STYLE_SIZE); static oc_str8 text = OC_STR8_LIT("Text box"); - oc_ui_text_box_result res = oc_ui_text_box("text", scratch.arena, text); - if(res.changed) + oc_ui_text_box_result result = oc_ui_text_box("text", scratch.arena, text); + if(result.changed) { oc_arena_clear(&textArena); - text = oc_str8_push_copy(&textArena, res.text); + text = oc_str8_push_copy(&textArena, result.text); } - if(res.accepted) + if(result.accepted) { log_pushf("Entered text \"%s\"", text.ptr); } @@ -465,7 +465,7 @@ ORCA_EXPORT void oc_on_frame_refresh(void) oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, .size.height = { OC_UI_SIZE_PIXELS, 152 }, - .layout.margin.x = 320, + .layout.margin.x = 310, .layout.margin.y = 16, .bgColor = OC_UI_DARK_THEME.bg0, .roundness = OC_UI_DARK_THEME.roundnessSmall }, @@ -527,8 +527,8 @@ ORCA_EXPORT void oc_on_frame_refresh(void) | OC_UI_STYLE_BG_COLOR | OC_UI_STYLE_ROUNDNESS); - oc_ui_pattern labelPattern = { 0 }; oc_ui_tag labelTag = oc_ui_tag_make("label"); + oc_ui_pattern labelPattern = { 0 }; oc_ui_pattern_push(scratch.arena, &labelPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = labelTag }); oc_ui_style_match_after(labelPattern, &(oc_ui_style){ .color = labelFontColor, @@ -818,32 +818,32 @@ ORCA_EXPORT void oc_on_frame_refresh(void) // You won't need it in a real program as long as your colors come from ui.theme or ui.theme->palette void reset_next_radio_group_to_dark_theme(oc_arena* arena) { - oc_ui_tag defaultTag = oc_ui_tag_make("radio"); - oc_ui_pattern defaultPattern = { 0 }; - oc_ui_pattern_push(arena, &defaultPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = defaultTag }); - oc_ui_style defaultStyle = { .borderColor = OC_UI_DARK_THEME.text3, - .borderSize = 1 }; - oc_ui_style_mask defaultMask = OC_UI_STYLE_BORDER_COLOR - | OC_UI_STYLE_BORDER_SIZE; - oc_ui_style_match_after(defaultPattern, &defaultStyle, defaultMask); + oc_ui_tag unselectedTag = oc_ui_tag_make("radio"); + oc_ui_pattern unselectedPattern = { 0 }; + oc_ui_pattern_push(arena, &unselectedPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = unselectedTag }); + oc_ui_style unselectedStyle = { .borderColor = OC_UI_DARK_THEME.text3, + .borderSize = 1 }; + oc_ui_style_mask unselectedMask = OC_UI_STYLE_BORDER_COLOR + | OC_UI_STYLE_BORDER_SIZE; + oc_ui_style_match_after(unselectedPattern, &unselectedStyle, unselectedMask); - oc_ui_pattern hoverPattern = { 0 }; - oc_ui_pattern_push(arena, &hoverPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = defaultTag }); - oc_ui_pattern_push(arena, &hoverPattern, (oc_ui_selector){ .op = OC_UI_SEL_AND, .kind = OC_UI_SEL_STATUS, .status = OC_UI_HOVER }); + oc_ui_pattern unselectedHoverPattern = { 0 }; + oc_ui_pattern_push(arena, &unselectedHoverPattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = unselectedTag }); + oc_ui_pattern_push(arena, &unselectedHoverPattern, (oc_ui_selector){ .op = OC_UI_SEL_AND, .kind = OC_UI_SEL_STATUS, .status = OC_UI_HOVER }); oc_ui_style hoverStyle = { .bgColor = OC_UI_DARK_THEME.fill0, .borderColor = OC_UI_DARK_THEME.primary }; oc_ui_style_mask hoverMask = OC_UI_STYLE_BG_COLOR | OC_UI_STYLE_BORDER_COLOR; - oc_ui_style_match_after(hoverPattern, &hoverStyle, hoverMask); + oc_ui_style_match_after(unselectedHoverPattern, &hoverStyle, hoverMask); - oc_ui_pattern activePattern = { 0 }; - oc_ui_pattern_push(arena, &activePattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = defaultTag }); - oc_ui_pattern_push(arena, &activePattern, (oc_ui_selector){ .op = OC_UI_SEL_AND, .kind = OC_UI_SEL_STATUS, .status = OC_UI_ACTIVE }); + oc_ui_pattern unselectedActivePattern = { 0 }; + oc_ui_pattern_push(arena, &unselectedActivePattern, (oc_ui_selector){ .kind = OC_UI_SEL_TAG, .tag = unselectedTag }); + oc_ui_pattern_push(arena, &unselectedActivePattern, (oc_ui_selector){ .op = OC_UI_SEL_AND, .kind = OC_UI_SEL_STATUS, .status = OC_UI_ACTIVE }); oc_ui_style activeStyle = { .bgColor = OC_UI_DARK_THEME.fill1, .borderColor = OC_UI_DARK_THEME.primary }; oc_ui_style_mask activeMask = OC_UI_STYLE_BG_COLOR | OC_UI_STYLE_BORDER_COLOR; - oc_ui_style_match_after(activePattern, &activeStyle, activeMask); + oc_ui_style_match_after(unselectedActivePattern, &activeStyle, activeMask); oc_ui_tag selectedTag = oc_ui_tag_make("radio_selected"); oc_ui_pattern selectedPattern = { 0 }; diff --git a/samples/zig-sample/.gitignore b/samples/zig-sample/.gitignore new file mode 100644 index 0000000..592c104 --- /dev/null +++ b/samples/zig-sample/.gitignore @@ -0,0 +1,6 @@ +Sample +zig-out +zig-cache +liborca.a +profile.dtrace +profile.spall diff --git a/samples/zig-sample/README.md b/samples/zig-sample/README.md new file mode 100644 index 0000000..5320304 --- /dev/null +++ b/samples/zig-sample/README.md @@ -0,0 +1,13 @@ +### Build and run +Zig version `0.11.0` or greater is required for this sample. To build and run: +``` +orca dev build-runtime +zig build run +``` + +These two commands build the runtime - the native host executable - and the sample as a loadable wasm library, then runs it. To only build the sample without running it, use `zig build bundle`. + +### Warning +Zig bindings for Orca are in-progress and experimental. You may encounter bugs since not all the bound APIs have been tested extensively - this sample is currently the only code doing so! Additionally, not all APIs have zig coverage yet, notably: +* `gles` +As more APIs get tested, there is a possibility of breaking changes. Please report any bugs you find on the Handmade discord in the #orca channel. diff --git a/samples/zig-sample/build.zig b/samples/zig-sample/build.zig new file mode 100644 index 0000000..b346daa --- /dev/null +++ b/samples/zig-sample/build.zig @@ -0,0 +1,107 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +fn addSourceString(str: []const u8, strings: *std.ArrayList(u8), sources: *std.ArrayList([]const u8)) !void { + var begin = strings.items.len; + try strings.appendSlice(str); + var realstring = strings.items[begin..]; + try sources.append(realstring); +} + +pub fn build(b: *std.Build) !void { + const optimize = b.standardOptimizeOption(.{}); + var wasm_target = std.zig.CrossTarget{ + .cpu_arch = .wasm32, + .os_tag = .freestanding, + }; + wasm_target.cpu_features_add.addFeature(@intFromEnum(std.Target.wasm.Feature.bulk_memory)); + + var orca_source_strings = try std.ArrayList(u8).initCapacity(b.allocator, 1024 * 4); + var orca_sources = try std.ArrayList([]const u8).initCapacity(b.allocator, 128); + defer orca_source_strings.deinit(); + defer orca_sources.deinit(); + + { + try addSourceString("../../src/orca.c", &orca_source_strings, &orca_sources); + + var libc_shim_dir = try std.fs.cwd().openIterableDir("../../src/libc-shim/src", .{}); + var walker = try libc_shim_dir.walk(b.allocator); + defer walker.deinit(); + + while (try walker.next()) |entry| { + const extension = std.fs.path.extension(entry.path); + if (std.mem.eql(u8, extension, ".c")) { + var path_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; + var abs_path = try libc_shim_dir.dir.realpath(entry.path, &path_buffer); + try addSourceString(abs_path, &orca_source_strings, &orca_sources); + } + } + } + + const orca_compile_opts = [_][]const u8{ + "-D__ORCA__", + "--no-standard-libraries", + "-fno-builtin", + "-g", + "-O2", + "-mexec-model=reactor", + "-fno-sanitize=undefined", + "-isystem ../../src/libc-shim/include", + "-I../../src", + "-I../../src/ext", + "-Wl,--export-dynamic", + }; + + var orca_lib = b.addStaticLibrary(.{ + .name = "orca", + .target = wasm_target, + .optimize = optimize, + }); + orca_lib.rdynamic = true; + orca_lib.addIncludePath(.{ .path = "../../src" }); + orca_lib.addIncludePath(.{ .path = "../../src/libc-shim/include" }); + orca_lib.addIncludePath(.{ .path = "../../src/ext" }); + orca_lib.addCSourceFiles(orca_sources.items, &orca_compile_opts); + + // builds the wasm module out of the orca C sources and main.zig + const orca_module: *std.Build.Module = b.createModule(.{ + .source_file = .{ .path = "../../src/orca.zig" }, + }); + const wasm_lib = b.addSharedLibrary(.{ + .name = "module", + .root_source_file = .{ .path = "src/main.zig" }, + .target = wasm_target, + .optimize = optimize, + }); + wasm_lib.rdynamic = true; + wasm_lib.addIncludePath(.{ .path = "../../src" }); + wasm_lib.addIncludePath(.{ .path = "../../src/libc-shim/include" }); + wasm_lib.addIncludePath(.{ .path = "../../ext" }); + wasm_lib.addModule("orca", orca_module); + wasm_lib.linkLibrary(orca_lib); + + // copies the wasm module into zig-out/wasm_lib + b.installArtifact(wasm_lib); + + // Runs the orca build command + const bundle_cmd_str = [_][]const u8{ "orca", "bundle", "--orca-dir", "../..", "--name", "Sample", "--icon", "icon.png", "--resource-dir", "data", "zig-out/lib/module.wasm" }; + var bundle_cmd = b.addSystemCommand(&bundle_cmd_str); + bundle_cmd.step.dependOn(b.getInstallStep()); + + const bundle_step = b.step("bundle", "Runs the orca toolchain to bundle the wasm module into an orca app."); + bundle_step.dependOn(&bundle_cmd.step); + + // Runs the app + const run_cmd_windows = [_][]const u8{"Sample/bin/Sample.exe"}; + const run_cmd_macos = [_][]const u8{ "open", "Sample.app" }; + const run_cmd_str: []const []const u8 = switch (builtin.os.tag) { + .windows => &run_cmd_windows, + .macos => &run_cmd_macos, + else => @compileError("unsupported platform"), + }; + var run_cmd = b.addSystemCommand(run_cmd_str); + run_cmd.step.dependOn(&bundle_cmd.step); + + const run_step = b.step("run", "Runs the bundled app using the Orca runtime."); + run_step.dependOn(&run_cmd.step); +} diff --git a/samples/zig-sample/data/ZigFontReadMe.txt b/samples/zig-sample/data/ZigFontReadMe.txt new file mode 100644 index 0000000..20a5f5b --- /dev/null +++ b/samples/zig-sample/data/ZigFontReadMe.txt @@ -0,0 +1,29 @@ +T E P I D M O N K E Y F O N T S +freeware fonts for a freeware world + +Site: http://www.tepidmonkey.com/ +E-mail: brandon@tepidmonkey.com + +Thanks for your interest in my fonts! + +For help on how to unzip, unstuff or install one of my +fonts, please visit my site at +www.tepidmonkey.com and go to the Help section. +If you have any comments or questions, you can e-mail +me at brandon@tepidmonkey.com and I'll try to reply as +soon as possible. + +Every week, I present a brand new original font for +your downloading pleasure, so be sure to visit my web +site every Sunday. + +You may use this font(s) for non-commercial and +commercial purposes. You are not allowed to sell this +font for any fee at all. You are allowed to +redistribute it as long as you don't charge ANYTHING +for it (at all) and if you include this unaltered +Read Me file. You may not change any aspect of the font +file or this file. +For the full set of terms of use (which override what +is listed here), go to www.tepidmonkey.com +and visit the Terms section. \ No newline at end of file diff --git a/samples/zig-sample/data/orca_jumping.jpg b/samples/zig-sample/data/orca_jumping.jpg new file mode 100644 index 0000000..50e1c35 Binary files /dev/null and b/samples/zig-sample/data/orca_jumping.jpg differ diff --git a/samples/zig-sample/data/zig.ttf b/samples/zig-sample/data/zig.ttf new file mode 100644 index 0000000..3d33887 Binary files /dev/null and b/samples/zig-sample/data/zig.ttf differ diff --git a/samples/zig-sample/icon.png b/samples/zig-sample/icon.png new file mode 100644 index 0000000..f921edb Binary files /dev/null and b/samples/zig-sample/icon.png differ diff --git a/samples/zig-sample/src/main.zig b/samples/zig-sample/src/main.zig new file mode 100644 index 0000000..a0ede41 --- /dev/null +++ b/samples/zig-sample/src/main.zig @@ -0,0 +1,289 @@ +const std = @import("std"); +const oc = @import("orca"); + +const lerp = std.math.lerp; + +const Vec2 = oc.Vec2; +const Mat2x3 = oc.Mat2x3; +const Str8 = oc.Str8; + +var surface: oc.Surface = undefined; +var canvas: oc.Canvas = undefined; +var font: oc.Font = undefined; +var orca_image: oc.Image = undefined; +var gradient_image: oc.Image = undefined; + +var counter: u32 = 0; +var last_seconds: f64 = 0; +var frame_size: Vec2 = .{ .x = 0, .y = 0 }; + +var rotation_demo: f32 = 0; + +export fn oc_on_init() void { + oc.windowSetTitle("zig sample"); + oc.windowSetSize(Vec2{ .x = 480, .y = 640 }); + + oc.log.info("current platform: {}", .{oc.getHostPlatform()}, @src()); + + surface = oc.Surface.canvas(); + canvas = oc.Canvas.create(); + + oc.assert(oc.Canvas.nil().isNil() == true, "nil canvas should be nil", .{}, @src()); + oc.assert(canvas.isNil() == false, "created canvas should not be nil", .{}, @src()); + + const ranges = oc.UnicodeRange.range(&[_]oc.UnicodeRange.Enum{ + .BasicLatin, + .C1ControlsAndLatin1Supplement, + .LatinExtendedA, + .LatinExtendedB, + .Specials, + }); + font = oc.Font.createFromPath("/zig.ttf", &ranges); + oc.assert(oc.Font.nil().isNil() == true, "nil font should be nil", .{}, @src()); + oc.assert(font.isNil() == false, "created font should not be nil", .{}, @src()); + + orca_image = oc.Image.createFromPath(surface, Str8.fromSlice("/orca_jumping.jpg"), false); + oc.assert(oc.Image.nil().isNil() == true, "nil image should be nil", .{}, @src()); + oc.assert(orca_image.isNil() == false, "created image should not be nil", .{}, @src()); + + // generate a gradient and upload it to an image + { + const width = 256; + const height = 128; + + const tl = oc.Color{ .r = 70.0 / 255.0, .g = 13.0 / 255.0, .b = 108.0 / 255.0 }; + const bl = oc.Color{ .r = 251.0 / 255.0, .g = 167.0 / 255.0, .b = 87.0 / 255.0 }; + const tr = oc.Color{ .r = 48.0 / 255.0, .g = 164.0 / 255.0, .b = 219.0 / 255.0 }; + const br = oc.Color{ .r = 151.0 / 255.0, .g = 222.0 / 255.0, .b = 150.0 / 255.0 }; + + var pixels: [width * height]u32 = undefined; + for (0..height) |y| { + for (0..width) |x| { + const h: f32 = @floatFromInt(height - 1); + const w: f32 = @floatFromInt(width - 1); + const y_norm: f32 = @as(f32, @floatFromInt(y)) / h; + const x_norm: f32 = @as(f32, @floatFromInt(x)) / w; + + const tl_weight = (1 - x_norm) * (1 - y_norm); + const bl_weight = (1 - x_norm) * y_norm; + const tr_weight = x_norm * (1 - y_norm); + const br_weight = x_norm * y_norm; + + const r: f32 = tl_weight * tl.r + bl_weight * bl.r + tr_weight * tr.r + br_weight * br.r; + const g: f32 = tl_weight * tl.g + bl_weight * bl.g + tr_weight * tr.g + br_weight * br.g; + const b: f32 = tl_weight * tl.b + bl_weight * bl.b + tr_weight * tr.b + br_weight * br.b; + const color = oc.Color{ .r = r, .g = g, .b = b, .a = 1.0 }; + pixels[y * width + x] = color.toRgba8(); + } + } + + gradient_image = oc.Image.create(surface, width, height); + gradient_image.uploadRegionRgba8(oc.Rect.xywh(0, 0, width, height), @ptrCast((&pixels).ptr)); + } +} + +export fn oc_on_resize(width: u32, height: u32) void { + frame_size = Vec2{ .x = @floatFromInt(width), .y = @floatFromInt(height) }; + oc.log.info("frame resize: {d:.2}, {d:.2}", .{ frame_size.x, frame_size.y }, @src()); +} + +export fn oc_on_mouse_down(button: oc.MouseButton) void { + oc.log.info("mouse down! {}", .{button}, @src()); +} + +export fn oc_on_mouse_up(button: oc.MouseButton) void { + oc.log.info("mouse up! {}", .{button}, @src()); +} + +export fn oc_on_mouse_wheel(dx: f32, dy: f32) void { + oc.log.info("mouse wheel! dx: {d:.2}, dy: {d:.2}", .{ dx, dy }, @src()); +} + +export fn oc_on_key_down(scan: oc.ScanCode, key: oc.KeyCode) void { + oc.log.info("key down: {} {}", .{ scan, key }, @src()); +} + +export fn oc_on_key_up(scan: oc.ScanCode, key: oc.KeyCode) void { + oc.log.info("key up: {} {}", .{ scan, key }, @src()); + + switch (key) { + oc.KeyCode.Escape => oc.requestQuit(), + oc.KeyCode.B => oc.abort("aborting", .{}, @src()), + oc.KeyCode.A => oc.assert(false, "test assert failed", .{}, @src()), + oc.KeyCode.W => oc.log.warn("logging a test warning", .{}, @src()), + oc.KeyCode.E => oc.log.err("logging a test error", .{}, @src()), + else => {}, + } +} + +export fn oc_on_frame_refresh() void { + counter += 1; + + const secs: f64 = oc.clock.time(.Date); + + if (last_seconds != @floor(secs)) { + last_seconds = @floor(secs); + oc.log.info("seconds since Jan 1, 1970: {d:.0}", .{secs}, @src()); + } + + _ = canvas.select(); + oc.Canvas.setColorRgba(0.05, 0.05, 0.05, 1.0); + oc.Canvas.clear(); + + oc.Canvas.setColorRgba(1.0, 0.05, 0.05, 1.0); + + { + const translation: Mat2x3 = .{ .m = [_]f32{ 1, 0, 50, 0, 1, 50 } }; + Mat2x3.push(translation); + defer Mat2x3.pop(); + + oc.assert(std.meta.eql(Mat2x3.top(), translation), "top of matrix stack should be what we pushed", .{}, @src()); + oc.Canvas.setWidth(1); + oc.Canvas.rectangleFill(50, 0, 10, 10); + oc.Canvas.rectangleStroke(70, 0, 10, 10); + oc.Canvas.roundedRectangleFill(90, 0, 10, 10, 3); + oc.Canvas.roundedRectangleStroke(110, 0, 10, 10, 3); + + const green = oc.Color{ .r = 0.05, .g = 1, .b = 0.05, .a = 1 }; + oc.Canvas.setColor(green); + oc.assert(std.meta.eql(oc.Canvas.getColor(), green), "color should be green", .{}, @src()); + + oc.Canvas.setTolerance(1); + oc.Canvas.setJoint(.Bevel); + oc.Canvas.ellipseFill(140, 5, 10, 5); + oc.Canvas.ellipseStroke(170, 5, 10, 5); + oc.Canvas.circleFill(195, 5, 5); + oc.Canvas.circleStroke(215, 5, 5); + + oc.Canvas.arc(230, 5, 5, 0, std.math.pi); + } + + { + rotation_demo += 0.03; + + const rot = Mat2x3.rotate(rotation_demo); + const trans = Mat2x3.translate(285, 55); + Mat2x3.push(Mat2x3.mul_m(trans, rot)); + defer Mat2x3.pop(); + + oc.Canvas.rectangleFill(-5, -5, 10, 10); + } + + { + var scratch_scope = oc.Arena.scratchBegin(); + defer scratch_scope.end(); + + var scratch: *oc.Arena = scratch_scope.arena; + + var str1: Str8 = Str8.collate(scratch, &[_][]const u8{ "Hello", "from", "Zig!" }, ">> ", " ", " <<") catch |e| fatal(e, @src()); + + var str2_list = oc.Str8List.init(); + str2_list.push(scratch, Str8.fromSlice("All")) catch |e| fatal(e, @src()); + str2_list.pushf(scratch, "your", .{}) catch |e| fatal(e, @src()); + str2_list.pushSlice(scratch, "base!!") catch |e| fatal(e, @src()); + + oc.assert(str2_list.containsSlice("All"), "str2_list should have the string we just pushed", .{}, @src()); + + { + var elt_first = str2_list.list.first; + var elt_last = str2_list.list.last; + oc.assert(elt_first != null, "list checks", .{}, @src()); + oc.assert(elt_last != null, "list checks", .{}, @src()); + oc.assert(elt_first != elt_last, "list checks", .{}, @src()); + oc.assert(elt_first.?.next != null, "list checks", .{}, @src()); + oc.assert(elt_first.?.prev == null, "list checks", .{}, @src()); + oc.assert(elt_last.?.next == null, "list checks", .{}, @src()); + oc.assert(elt_last.?.prev != null, "list checks", .{}, @src()); + oc.assert(elt_first.?.next != elt_last, "list checks", .{}, @src()); + oc.assert(elt_last.?.prev != elt_first, "list checks", .{}, @src()); + } + + var str2: Str8 = str2_list.collate(scratch, Str8.fromSlice("<< "), Str8.fromSlice("-"), Str8.fromSlice(" >>")) catch |e| fatal(e, @src()); + + const font_size = 18; + const text_metrics = font.textMetrics(font_size, str1); + const text_rect = text_metrics.ink; + + const center_x = frame_size.x / 2; + const text_begin_x = center_x - text_rect.w / 2; + + Mat2x3.push(Mat2x3.translate(text_begin_x, 100)); + defer Mat2x3.pop(); + + oc.Canvas.setColorRgba(1.0, 0.05, 0.05, 1.0); + oc.Canvas.setFont(font); + oc.Canvas.setFontSize(font_size); + oc.Canvas.moveTo(0, 0); + oc.Canvas.textOutlines(str1); + oc.Canvas.moveTo(0, 35); + oc.Canvas.textOutlines(str2); + oc.Canvas.fill(); + } + + { + var scratch_scope = oc.Arena.scratchBegin(); + defer scratch_scope.end(); + + var scratch: *oc.Arena = scratch_scope.arena; + + var separators = oc.Str8List.init(); + separators.pushSlice(scratch, " ") catch |e| fatal(e, @src()); + separators.pushSlice(scratch, "|") catch |e| fatal(e, @src()); + separators.pushSlice(scratch, "-") catch |e| fatal(e, @src()); + + const big_string = Str8.fromSlice("This is |a one-word string that | has no spaces in it"); + var strings: oc.Str8List = big_string.split(scratch, separators) catch |e| fatal(e, @src()); + var collated = strings.join(scratch) catch |e| fatal(e, @src()); + + oc.Canvas.setFontSize(12); + oc.Canvas.moveTo(0, 170); + oc.Canvas.textOutlines(collated); + oc.Canvas.fill(); + } + + { + const orca_size = orca_image.size(); + + { + const trans = Mat2x3.translate(0, 200); + const scale = Mat2x3.scaleUniform(0.25); + Mat2x3.push(Mat2x3.mul_m(trans, scale)); + defer Mat2x3.pop(); + + orca_image.draw(oc.Rect.xywh(0, 0, orca_size.x, orca_size.y)); + + var half_size = orca_size; + half_size.x /= 2; + orca_image.drawRegion(oc.Rect.xywh(0, 0, half_size.x, half_size.y), oc.Rect.xywh(orca_size.x + 10, 0, half_size.x, half_size.y)); + } + + { + const x_offset = orca_size.x * 0.25 + orca_size.x * 0.25 * 0.5 + 5; + const gradient_size = gradient_image.size(); + + const trans = Mat2x3.translate(x_offset, 200); + const scale = Mat2x3.scaleUniform((orca_size.y * 0.25) / gradient_size.y); + Mat2x3.push(Mat2x3.mul_m(trans, scale)); + defer Mat2x3.pop(); + + gradient_image.draw(oc.Rect.xywh(0, 0, gradient_size.x, gradient_size.y)); + } + } + + surface.select(); + canvas.render(); + surface.present(); +} + +export fn oc_on_terminate() void { + oc.log.info("byebye {}", .{counter}, @src()); +} + +fn fatal(err: anyerror, source: std.builtin.SourceLocation) noreturn { + oc.abort("Caught fatal {}", .{err}, source); + unreachable; +} + +fn oneMinusLerp(a: anytype, b: anytype, t: anytype) @TypeOf(a, b, t) { + return 1.0 - lerp(a, b, t); +} diff --git a/samples/zig-ui/.gitignore b/samples/zig-ui/.gitignore new file mode 100644 index 0000000..ab6e6a5 --- /dev/null +++ b/samples/zig-ui/.gitignore @@ -0,0 +1,6 @@ +UI +zig-out +zig-cache +liborca.a +profile.dtrace +profile.spall diff --git a/samples/zig-ui/README.md b/samples/zig-ui/README.md new file mode 100644 index 0000000..5320304 --- /dev/null +++ b/samples/zig-ui/README.md @@ -0,0 +1,13 @@ +### Build and run +Zig version `0.11.0` or greater is required for this sample. To build and run: +``` +orca dev build-runtime +zig build run +``` + +These two commands build the runtime - the native host executable - and the sample as a loadable wasm library, then runs it. To only build the sample without running it, use `zig build bundle`. + +### Warning +Zig bindings for Orca are in-progress and experimental. You may encounter bugs since not all the bound APIs have been tested extensively - this sample is currently the only code doing so! Additionally, not all APIs have zig coverage yet, notably: +* `gles` +As more APIs get tested, there is a possibility of breaking changes. Please report any bugs you find on the Handmade discord in the #orca channel. diff --git a/samples/zig-ui/build.zig b/samples/zig-ui/build.zig new file mode 100644 index 0000000..5060e0b --- /dev/null +++ b/samples/zig-ui/build.zig @@ -0,0 +1,107 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +fn addSourceString(str: []const u8, strings: *std.ArrayList(u8), sources: *std.ArrayList([]const u8)) !void { + var begin = strings.items.len; + try strings.appendSlice(str); + var realstring = strings.items[begin..]; + try sources.append(realstring); +} + +pub fn build(b: *std.Build) !void { + const optimize = b.standardOptimizeOption(.{}); + var wasm_target = std.zig.CrossTarget{ + .cpu_arch = .wasm32, + .os_tag = .freestanding, + }; + wasm_target.cpu_features_add.addFeature(@intFromEnum(std.Target.wasm.Feature.bulk_memory)); + + var orca_source_strings = try std.ArrayList(u8).initCapacity(b.allocator, 1024 * 4); + var orca_sources = try std.ArrayList([]const u8).initCapacity(b.allocator, 128); + defer orca_source_strings.deinit(); + defer orca_sources.deinit(); + + { + try addSourceString("../../src/orca.c", &orca_source_strings, &orca_sources); + + var libc_shim_dir = try std.fs.cwd().openIterableDir("../../src/libc-shim/src", .{}); + var walker = try libc_shim_dir.walk(b.allocator); + defer walker.deinit(); + + while (try walker.next()) |entry| { + const extension = std.fs.path.extension(entry.path); + if (std.mem.eql(u8, extension, ".c")) { + var path_buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined; + var abs_path = try libc_shim_dir.dir.realpath(entry.path, &path_buffer); + try addSourceString(abs_path, &orca_source_strings, &orca_sources); + } + } + } + + const orca_compile_opts = [_][]const u8{ + "-D__ORCA__", + "--no-standard-libraries", + "-fno-builtin", + "-g", + "-O2", + "-mexec-model=reactor", + "-fno-sanitize=undefined", + "-isystem ../../src/libc-shim/include", + "-I../../src", + "-I../../src/ext", + "-Wl,--export-dynamic", + }; + + var orca_lib = b.addStaticLibrary(.{ + .name = "orca", + .target = wasm_target, + .optimize = optimize, + }); + orca_lib.rdynamic = true; + orca_lib.addIncludePath(.{ .path = "../../src" }); + orca_lib.addIncludePath(.{ .path = "../../src/libc-shim/include" }); + orca_lib.addIncludePath(.{ .path = "../../src/ext" }); + orca_lib.addCSourceFiles(orca_sources.items, &orca_compile_opts); + + // builds the wasm module out of the orca C sources and main.zig + const orca_module: *std.Build.Module = b.createModule(.{ + .source_file = .{ .path = "../../src/orca.zig" }, + }); + const wasm_lib = b.addSharedLibrary(.{ + .name = "module", + .root_source_file = .{ .path = "src/main.zig" }, + .target = wasm_target, + .optimize = optimize, + }); + wasm_lib.rdynamic = true; + wasm_lib.addIncludePath(.{ .path = "../../src" }); + wasm_lib.addIncludePath(.{ .path = "../../src/libc-shim/include" }); + wasm_lib.addIncludePath(.{ .path = "../../ext" }); + wasm_lib.addModule("orca", orca_module); + wasm_lib.linkLibrary(orca_lib); + + // copies the wasm module into zig-out/wasm_lib + b.installArtifact(wasm_lib); + + // Runs the orca build command + const bundle_cmd_str = [_][]const u8{ "orca", "bundle", "--orca-dir", "../..", "--name", "UI", "--resource-dir", "data", "zig-out/lib/module.wasm" }; + var bundle_cmd = b.addSystemCommand(&bundle_cmd_str); + bundle_cmd.step.dependOn(b.getInstallStep()); + + const bundle_step = b.step("bundle", "Runs the orca toolchain to bundle the wasm module into an orca app."); + bundle_step.dependOn(&bundle_cmd.step); + + // Runs the app + const run_cmd_windows = [_][]const u8{"UI/bin/UI.exe"}; + const run_cmd_macos = [_][]const u8{ "open", "UI.app" }; + const run_cmd_str: []const []const u8 = switch (builtin.os.tag) { + .windows => &run_cmd_windows, + .macos => &run_cmd_macos, + else => @compileError("unsupported platform"), + }; + var run_cmd = b.addSystemCommand(run_cmd_str); + run_cmd.step.dependOn(&bundle_cmd.step); + + const run_step = b.step("run", "Runs the bundled app using the Orca runtime."); + run_step.dependOn(&run_cmd.step); +} diff --git a/samples/zig-ui/data/OpenSans-Bold.ttf b/samples/zig-ui/data/OpenSans-Bold.ttf new file mode 100644 index 0000000..4a5bc39 Binary files /dev/null and b/samples/zig-ui/data/OpenSans-Bold.ttf differ diff --git a/samples/zig-ui/data/OpenSans-Regular.ttf b/samples/zig-ui/data/OpenSans-Regular.ttf new file mode 100644 index 0000000..29e9e60 Binary files /dev/null and b/samples/zig-ui/data/OpenSans-Regular.ttf differ diff --git a/samples/zig-ui/src/main.zig b/samples/zig-ui/src/main.zig new file mode 100644 index 0000000..b81f558 --- /dev/null +++ b/samples/zig-ui/src/main.zig @@ -0,0 +1,871 @@ +const std = @import("std"); +const oc = @import("orca"); +const ui = oc.ui; + +var frame_size: oc.Vec2 = .{ .x = 1200, .y = 838 }; + +var surface: oc.Surface = undefined; +var canvas: oc.Canvas = undefined; +var font_regular: oc.Font = undefined; +var font_bold: oc.Font = undefined; +var ui_ctx: ui.Context = undefined; +var text_arena: oc.Arena = undefined; +var log_arena: oc.Arena = undefined; +var log_lines: oc.Str8List = undefined; + +const Cmd = enum { + None, + SetDarkTheme, + SetLightTheme, +}; +var cmd: Cmd = .None; + +export fn oc_on_init() void { + oc.windowSetTitle("Orca Zig UI Demo"); + oc.windowSetSize(frame_size); + + surface = oc.Surface.canvas(); + canvas = oc.Canvas.create(); + ui.init(&ui_ctx); + + var fonts = [_]*oc.Font{ &font_regular, &font_bold }; + var font_names = [_][]const u8{ "/OpenSans-Regular.ttf", "/OpenSans-Bold.ttf" }; + for (fonts, font_names) |font, name| { + var scratch = oc.Arena.scratchBegin(); + defer scratch.end(); + + var file = oc.File.open(oc.Str8.fromSlice(name), .{ .read = true }, .{}); + if (file.lastError() != oc.io.Error.Ok) { + oc.log.err("Couldn't open file {s}", .{name}, @src()); + } + + var size = file.getSize(); + var buffer = scratch.arena.push(size) catch { + oc.log.err("Out of memory", .{}, @src()); + return; + }; + _ = file.read(size, buffer); + file.close(); + + var ranges = oc.UnicodeRange.range(&[_]oc.UnicodeRange.Enum{ + .BasicLatin, + .C1ControlsAndLatin1Supplement, + .LatinExtendedA, + .LatinExtendedB, + .Specials, + }); + + font.* = oc.Font.createFromMemory(buffer[0..size], &ranges); + } + + text_arena = oc.Arena.init(); + log_arena = oc.Arena.init(); + log_lines = oc.Str8List.init(); +} + +export fn oc_on_raw_event(event: *oc.CEvent) void { + ui.processCEvent(event); +} + +export fn oc_on_resize(width: u32, height: u32) void { + frame_size.x = @floatFromInt(width); + frame_size.y = @floatFromInt(height); +} + +export fn oc_on_frame_refresh() void { + var scratch = oc.Arena.scratchBegin(); + defer scratch.end(); + + switch (cmd) { + .SetDarkTheme => ui.setTheme(ui.dark_theme), + .SetLightTheme => ui.setTheme(ui.light_theme), + .None => {}, + } + cmd = .None; + + var default_style = ui.Style{ .font = font_regular }; + { + ui.beginFrame(frame_size, &default_style); + defer ui.endFrame(); + + //-------------------------------------------------------------------------------------------- + // Menu bar + //-------------------------------------------------------------------------------------------- + { + ui.menuBarBegin("menu_bar"); + defer ui.menuBarEnd(); + + { + ui.menuBegin("File"); + defer ui.menuEnd(); + + if (ui.menuButton("Quit").pressed) { + oc.requestQuit(); + } + } + + { + ui.menuBegin("Theme"); + defer ui.menuEnd(); + + if (ui.menuButton("Dark theme").pressed) { + cmd = .SetDarkTheme; + } + if (ui.menuButton("Light theme").pressed) { + cmd = .SetLightTheme; + } + } + } + + { + ui.panelBegin("main panel", .{}); + defer ui.panelEnd(); + + { + ui.styleNext(.{ + .size = .{ + .width = .fill_parent, + .height = .{ .custom = .{ .kind = .Parent, .value = 1, .relax = 1 } }, + }, + .layout = .{ + .axis = .X, + .margin = .{ .x = 16, .y = 16 }, + .spacing = 16, + }, + }); + _ = ui.boxBegin("Background", .{ .draw_background = true }); + defer _ = ui.boxEnd(); + + widgets(scratch.arena); + + styling(scratch.arena); + } + } + } + + _ = canvas.select(); + surface.select(); + + oc.Canvas.setColor(ui_ctx.theme.bg0); + oc.Canvas.clear(); + + ui.draw(); + canvas.render(); + surface.present(); +} + +var checkbox_checked: bool = false; +var v_slider_value: f32 = 0; +var v_slider_logged_value: f32 = 0; +var v_slider_log_time: f64 = 0; +var radio_selected: usize = 0; +var h_slider_value: f32 = 0; +var h_slider_logged_value: f32 = 0; +var h_slider_log_time: f64 = 0; +var text: []const u8 = "Text box"; +var selected: ?usize = null; + +fn widgets(arena: *oc.Arena) void { + columnBegin("Widgets", 1.0 / 3.0); + defer columnEnd(); + + { + ui.styleNext(.{ + .size = .{ + .width = .fill_parent, + }, + .layout = .{ + .axis = .X, + .spacing = 32, + }, + }); + _ = ui.boxBegin("top", .{}); + defer _ = ui.boxEnd(); + + { + ui.styleNext(.{ + .layout = .{ + .axis = .Y, + .spacing = 24, + }, + }); + _ = ui.boxBegin("top_left", .{}); + defer _ = ui.boxEnd(); + + //----------------------------------------------------------------------------- + // Label + //----------------------------------------------------------------------------- + _ = ui.makeLabel("Label"); + + //----------------------------------------------------------------------------- + // Button + //----------------------------------------------------------------------------- + if (ui.button("Button").clicked) { + logPush("Button clicked"); + } + + { + ui.styleNext(.{ + .layout = .{ + .axis = .X, + .alignment = .{ .y = .Center }, + .spacing = 8, + }, + }); + _ = ui.boxBegin("checkbox", .{}); + defer _ = ui.boxEnd(); + + //------------------------------------------------------------------------- + // Checkbox + //------------------------------------------------------------------------- + if (ui.checkbox("checkbox", &checkbox_checked).clicked) { + if (checkbox_checked) { + logPush("Checkbox checked"); + } else { + logPush("Checkbox unhecked"); + } + } + + _ = ui.makeLabel("Checkbox"); + } + } + + //--------------------------------------------------------------------------------- + // Vertical slider + //--------------------------------------------------------------------------------- + ui.styleNext(.{ .size = .{ .height = .{ .pixels = 130 } } }); + _ = ui.slider("v_slider", &v_slider_value); + + var now = oc.clock.time(.Monotonic); + if ((now - v_slider_log_time) >= 0.2 and v_slider_value != v_slider_logged_value) { + logPushf("Vertical slider moved to {d:.3}", .{v_slider_value}); + v_slider_logged_value = v_slider_value; + v_slider_log_time = now; + } + + { + ui.styleNext(.{ + .layout = .{ + .axis = .Y, + .spacing = 24, + }, + }); + _ = ui.boxBegin("top right", .{}); + defer _ = ui.boxEnd(); + + //----------------------------------------------------------------------------- + // Tooltip + //----------------------------------------------------------------------------- + if (ui.makeLabel("Tooltip").hovering) { + ui.tooltip("Hi"); + } + + //----------------------------------------------------------------------------- + // Radio group + //----------------------------------------------------------------------------- + var options = [_][]const u8{ + "Radio 1", + "Radio 2", + }; + var radio_group_info = ui.RadioGroupInfo{ + .selected_index = radio_selected, + .options = &options, + }; + var result = ui.radioGroup("radio_group", &radio_group_info); + radio_selected = result.selected_index.?; + if (result.changed) { + logPushf("Selected {s}", .{options[radio_selected]}); + } + + //----------------------------------------------------------------------------- + // Horizontal slider + //----------------------------------------------------------------------------- + ui.styleNext(.{ .size = .{ .width = .{ .pixels = 130 } } }); + _ = ui.slider("h_slider", &h_slider_value); + + if ((now - h_slider_log_time) >= 0.2 and h_slider_value != h_slider_logged_value) { + logPushf("Slider moved to {d:.3}", .{h_slider_value}); + h_slider_logged_value = h_slider_value; + h_slider_log_time = now; + } + } + } + + //------------------------------------------------------------------------------------- + // Text box + //------------------------------------------------------------------------------------- + ui.styleNext(.{ + .size = .{ + .width = .{ .pixels = 305 }, + .height = .text, + }, + }); + var textResult = ui.textBox("text", arena, text); + if (textResult.changed) { + text_arena.clear(); + text = text_arena.pushStr(textResult.text) catch { + oc.log.err("Out of memory", .{}, @src()); + oc.requestQuit(); + return; + }; + } + if (textResult.accepted) { + logPushf("Entered text {s}", .{text}); + } + + //------------------------------------------------------------------------------------- + // Select + //------------------------------------------------------------------------------------- + var options = [_][]const u8{ + "Option 1", + "Option 2", + }; + var select_popup_info = ui.SelectPopupInfo{ + .selected_index = selected, + .options = &options, + .placeholder = "Select", + }; + var selectResult = ui.selectPopup("select", &select_popup_info); + if (selectResult.selected_index != selected) { + logPushf("Selected {s}", .{options[selectResult.selected_index.?]}); + } + selected = selectResult.selected_index; + + //------------------------------------------------------------------------------------- + // Scrollable panel + //------------------------------------------------------------------------------------- + { + ui.styleNext(.{ + .size = .{ + .width = .fill_parent, + .height = .{ + .custom = .{ .kind = .Parent, .value = 1, .relax = 1, .min_size = 200 }, + }, + }, + .bg_color = ui_ctx.theme.bg2, + .border_color = ui_ctx.theme.border, + .border_size = 1, + .roundness = ui_ctx.theme.roundness_small, + }); + _ = ui.panelBegin("log", .{ .draw_background = true, .draw_border = true }); + defer ui.panelEnd(); + + { + ui.styleNext(.{ + .layout = .{ + .margin = .{ .x = 16, .y = 16 }, + }, + }); + _ = ui.boxBegin("contents", .{}); + defer _ = ui.boxEnd(); + + if (log_lines.list.empty()) { + ui.styleNext(.{ .color = ui_ctx.theme.text2 }); + _ = ui.makeLabel("Log"); + } + + var i: i32 = 0; + var log_lines_iter = log_lines.iter(); + while (log_lines_iter.next()) |log_line| { + var buf: [15]u8 = undefined; + var id = std.fmt.bufPrint(&buf, "{d}", .{i}) catch unreachable; + _ = ui.boxBegin(id, .{}); + defer _ = ui.boxEnd(); + + _ = ui.makeLabel(log_line.string.slice()); + + i += 1; + } + } + } +} + +var styling_selected_radio: ?usize = 0; +var unselected_width: f32 = 16; +var unselected_height: f32 = 16; +var unselected_roundness: f32 = 8; +var unselected_bg_color: oc.Color = oc.Color.rgba(0.086, 0.086, 0.102, 1); +var unselected_border_color: oc.Color = oc.Color.rgba(0.976, 0.976, 0.976, 0.35); +var unselected_border_size: f32 = 1; +var unselected_when_status: ui.Status = .{}; +var unselected_status_index: ?usize = 0; +var selected_width: f32 = 16; +var selected_height: f32 = 16; +var selected_roundness: f32 = 8; +var selected_center_color: oc.Color = oc.Color.rgba(1, 1, 1, 1); +var selected_bg_color: oc.Color = oc.Color.rgba(0.33, 0.66, 1, 1); +var selected_when_status: ui.Status = .{}; +var selected_status_index: ?usize = 0; +var label_font_color: oc.Color = oc.Color.rgba(0.976, 0.976, 0.976, 1); +var label_font_color_selected: ?usize = 0; +var label_font: *oc.Font = &font_regular; +var label_font_selected: ?usize = 0; +var label_font_size: f32 = 14; + +fn styling(arena: *oc.Arena) void { + //----------------------------------------------------------------------------------------- + // Styling + //----------------------------------------------------------------------------------------- + // Initial values here are hardcoded from the dark theme and everything is overridden all + // the time. In a real program you'd only override what you need and supply the values from + // ui_ctx.theme or ui_ctx.theme.palette. + // + // Rule-based styling is described at + // https://www.forkingpaths.dev/posts/23-03-10/rule_based_styling_imgui_ctx.html + columnBegin("Styling", 2.0 / 3.0); + defer columnEnd(); + + { + ui.styleNext(.{ + .size = .{ + .width = .fill_parent, + .height = .{ .pixels = 152 }, + }, + .layout = .{ + .margin = .{ .x = 310, .y = 16 }, + }, + .bg_color = ui.dark_theme.bg0, + .roundness = ui.dark_theme.roundness_small, + }); + _ = ui.boxBegin("styled_radios", .{ .draw_background = true, .draw_border = true }); + defer _ = ui.boxEnd(); + + resetNextRadioGroupToDarkTheme(arena); + + var unselected_tag = ui.Tag.make("radio"); + var unselected_pattern = ui.Pattern.init(); + unselected_pattern.push(arena, .{ .sel = .{ .tag = unselected_tag } }); + if (!unselected_when_status.empty()) { + unselected_pattern.push(arena, .{ .op = .And, .sel = .{ .status = unselected_when_status } }); + } + ui.styleMatchAfter(unselected_pattern, .{ + .size = .{ + .width = .{ .pixels = unselected_width }, + .height = .{ .pixels = unselected_height }, + }, + .bg_color = unselected_bg_color, + .border_color = unselected_border_color, + .border_size = unselected_border_size, + .roundness = unselected_roundness, + }); + + var selected_tag = ui.Tag.make("radio_selected"); + var selected_pattern = ui.Pattern.init(); + selected_pattern.push(arena, .{ .sel = .{ .tag = selected_tag } }); + if (!selected_when_status.empty()) { + selected_pattern.push(arena, .{ .op = .And, .sel = .{ .status = selected_when_status } }); + } + ui.styleMatchAfter(selected_pattern, .{ + .size = .{ + .width = .{ .pixels = selected_width }, + .height = .{ .pixels = selected_height }, + }, + .color = selected_center_color, + .bg_color = selected_bg_color, + .roundness = selected_roundness, + }); + + var label_tag = ui.Tag.make("label"); + var label_pattern = ui.Pattern.init(); + label_pattern.push(arena, .{ .sel = .{ .tag = label_tag } }); + ui.styleMatchAfter(label_pattern, .{ + .color = label_font_color, + .font = label_font.*, + .font_size = label_font_size, + }); + + var options = [_][]const u8{ + "I", + "Am", + "Stylish", + }; + var radio_group_info = ui.RadioGroupInfo{ + .selected_index = styling_selected_radio, + .options = &options, + }; + var result = ui.radioGroup("radio_group", &radio_group_info); + styling_selected_radio = result.selected_index; + } + + { + ui.styleNext(.{ .layout = .{ .axis = .X, .spacing = 32 } }); + _ = ui.boxBegin("controls", .{}); + defer _ = ui.boxEnd(); + + { + ui.styleNext(.{ .layout = .{ .axis = .Y, .spacing = 16 } }); + _ = ui.boxBegin("unselected", .{}); + defer _ = ui.boxEnd(); + + ui.styleNext(.{ .font_size = 16 }); + _ = ui.makeLabel("Radio style"); + + { + ui.styleNext(.{ .layout = .{ .spacing = 4 } }); + _ = ui.boxBegin("size", .{}); + defer _ = ui.boxEnd(); + + var width_slider = (unselected_width - 8) / 16; + labeledSlider("Width", &width_slider); + unselected_width = 8 + width_slider * 16; + + var height_slider = (unselected_height - 8) / 16; + labeledSlider("Height", &height_slider); + unselected_height = 8 + height_slider * 16; + + var roundness_slider = (unselected_roundness - 4) / 8; + labeledSlider("Roundness", &roundness_slider); + unselected_roundness = 4 + roundness_slider * 8; + } + + { + ui.styleNext(.{ .layout = .{ .spacing = 4 } }); + _ = ui.boxBegin("background", .{}); + defer _ = ui.boxEnd(); + + labeledSlider("Background R", &unselected_bg_color.r); + labeledSlider("Background G", &unselected_bg_color.g); + labeledSlider("Background B", &unselected_bg_color.b); + labeledSlider("Background A", &unselected_bg_color.a); + } + + { + ui.styleNext(.{ .layout = .{ .spacing = 4 } }); + _ = ui.boxBegin("border", .{}); + defer _ = ui.boxEnd(); + + labeledSlider("Border R", &unselected_border_color.r); + labeledSlider("Border G", &unselected_border_color.g); + labeledSlider("Border B", &unselected_border_color.b); + labeledSlider("Border A", &unselected_border_color.a); + } + + var border_size_slider = unselected_border_size / 5; + labeledSlider("Border size", &border_size_slider); + unselected_border_size = border_size_slider * 5; + + { + ui.styleNext(.{ .layout = .{ .spacing = 10 } }); + _ = ui.boxBegin("status_override", .{}); + defer _ = ui.boxEnd(); + + _ = ui.makeLabel("Override"); + + var status_options = [_][]const u8{ + "Always", + "When hovering", + "When active", + }; + var status_info = ui.RadioGroupInfo{ + .selected_index = unselected_status_index, + .options = &status_options, + }; + var status_result = ui.radioGroup("status", &status_info); + unselected_status_index = status_result.selected_index; + unselected_when_status = switch (unselected_status_index.?) { + 0 => .{}, + 1 => .{ .hover = true }, + 2 => .{ .active = true }, + else => unreachable, + }; + } + } + + { + ui.styleNext(.{ .layout = .{ .axis = .Y, .spacing = 16 } }); + _ = ui.boxBegin("selected", .{}); + defer _ = ui.boxEnd(); + + ui.styleNext(.{ .font_size = 16 }); + _ = ui.makeLabel("Radio selected style"); + + { + ui.styleNext(.{ .layout = .{ .spacing = 4 } }); + _ = ui.boxBegin("size", .{}); + defer _ = ui.boxEnd(); + + var width_slider = (selected_width - 8) / 16; + labeledSlider("Width", &width_slider); + selected_width = 8 + width_slider * 16; + + var height_slider = (selected_height - 8) / 16; + labeledSlider("Height", &height_slider); + selected_height = 8 + height_slider * 16; + + var roundness_slider = (selected_roundness - 4) / 8; + labeledSlider("Roundness", &roundness_slider); + selected_roundness = 4 + roundness_slider * 8; + } + + { + ui.styleNext(.{ .layout = .{ .spacing = 4 } }); + _ = ui.boxBegin("color", .{}); + defer _ = ui.boxEnd(); + + labeledSlider("Center R", &selected_center_color.r); + labeledSlider("Center G", &selected_center_color.g); + labeledSlider("Center B", &selected_center_color.b); + labeledSlider("Center A", &selected_center_color.a); + } + + { + ui.styleNext(.{ .layout = .{ .spacing = 4 } }); + _ = ui.boxBegin("background", .{}); + defer _ = ui.boxEnd(); + + labeledSlider("Background R", &selected_bg_color.r); + labeledSlider("Background G", &selected_bg_color.g); + labeledSlider("Background B", &selected_bg_color.b); + labeledSlider("Background A", &selected_bg_color.a); + } + + { + ui.styleNext(.{ .layout = .{ .spacing = 10 } }); + _ = ui.boxBegin("status_override", .{}); + defer _ = ui.boxEnd(); + + ui.styleNext(.{ .size = .{ .height = .{ .pixels = 30 } } }); + _ = ui.boxMake("spacer", .{}); + + _ = ui.makeLabel("Override"); + + var status_options = [_][]const u8{ + "Always", + "When hovering", + "When active", + }; + var status_info = ui.RadioGroupInfo{ + .selected_index = selected_status_index, + .options = &status_options, + }; + var status_result = ui.radioGroup("status", &status_info); + selected_status_index = status_result.selected_index; + selected_when_status = switch (selected_status_index.?) { + 0 => .{}, + 1 => .{ .hover = true }, + 2 => .{ .active = true }, + else => unreachable, + }; + } + } + + { + ui.styleNext(.{ .layout = .{ .axis = .Y, .spacing = 10 } }); + _ = ui.boxBegin("label", .{}); + defer _ = ui.boxEnd(); + + ui.styleNext(.{ .font_size = 16 }); + _ = ui.makeLabel("Label style"); + + { + ui.styleNext(.{ .layout = .{ .axis = .X, .spacing = 8 } }); + _ = ui.boxBegin("font_color", .{}); + defer _ = ui.boxEnd(); + + ui.styleMatchAfter(ui.Pattern.owner(), .{ + .size = .{ .width = .{ .pixels = 100 } }, + }); + _ = ui.makeLabel("Font color"); + + var color_names = [_][]const u8{ + "Default", + "Red", + "Orange", + "Amber", + "Yellow", + "Lime", + "Light green", + "Green", + }; + var colors = [_]oc.Color{ + ui.dark_theme.text0, + ui.dark_theme.palette.red5, + ui.dark_theme.palette.orange5, + ui.dark_theme.palette.amber5, + ui.dark_theme.palette.yellow5, + ui.dark_theme.palette.lime5, + ui.dark_theme.palette.light_green5, + ui.dark_theme.palette.green5, + }; + var color_info = ui.SelectPopupInfo{ + .selected_index = label_font_color_selected, + .options = &color_names, + }; + var color_result = ui.selectPopup("color", &color_info); + label_font_color_selected = color_result.selected_index; + label_font_color = colors[label_font_color_selected.?]; + } + + { + ui.styleNext(.{ .layout = .{ .axis = .X, .spacing = 8 } }); + _ = ui.boxBegin("font", .{}); + defer _ = ui.boxEnd(); + + ui.styleMatchAfter(ui.Pattern.owner(), .{ + .size = .{ .width = .{ .pixels = 100 } }, + }); + _ = ui.makeLabel("Font"); + + var font_names = [_][]const u8{ + "Regular", + "Bold", + }; + var fonts = [_]*oc.Font{ + &font_regular, + &font_bold, + }; + var font_info = ui.SelectPopupInfo{ + .selected_index = label_font_selected, + .options = &font_names, + }; + var font_result = ui.selectPopup("font_style", &font_info); + label_font_selected = font_result.selected_index; + label_font = fonts[label_font_selected.?]; + } + + var font_size_slider = (label_font_size - 8) / 16; + labeledSlider("Font size", &font_size_slider); + label_font_size = 8 + font_size_slider * 16; + } + } +} + +fn columnBegin(header: []const u8, widthFraction: f32) void { + ui.styleNext(.{ + .size = .{ + .width = .{ + .custom = .{ .kind = .Parent, .value = widthFraction, .relax = 1 }, + }, + .height = .fill_parent, + }, + .layout = .{ + .axis = .Y, + .margin = .{ .y = 8 }, + .spacing = 24, + }, + .bg_color = ui_ctx.theme.bg1, + .border_color = ui_ctx.theme.border, + .border_size = 1, + .roundness = ui_ctx.theme.roundness_small, + }); + _ = ui.boxBegin(header, .{ .draw_background = true, .draw_border = true }); + + { + ui.styleNext(.{ + .size = .{ .width = .fill_parent }, + .layout = .{ .alignment = .{ .x = .Center } }, + }); + _ = ui.boxBegin("header", .{}); + defer _ = ui.boxEnd(); + + ui.styleNext(.{ .font_size = 18 }); + _ = ui.makeLabel(header); + } + + ui.styleNext(.{ + .size = .{ + .width = .fill_parent, + .height = .{ + .custom = .{ .kind = .Parent, .value = 1, .relax = 1 }, + }, + }, + .layout = .{ + .alignment = .{ .x = .Start }, + .margin = .{ .x = 16 }, + .spacing = 24, + }, + }); + _ = ui.boxBegin("contents", .{}); +} + +fn columnEnd() void { + _ = ui.boxEnd(); // contents + _ = ui.boxEnd(); // column +} + +fn labeledSlider(label: []const u8, value: *f32) void { + ui.styleNext(.{ .layout = .{ .axis = .X, .spacing = 8 } }); + _ = ui.boxBegin(label, .{}); + defer _ = ui.boxEnd(); + + ui.styleMatchAfter(ui.Pattern.owner(), .{ + .size = .{ .width = .{ .pixels = 100 } }, + }); + _ = ui.makeLabel(label); + + ui.styleNext(.{ + .size = .{ .width = .{ .pixels = 100 } }, + }); + _ = ui.slider("slider", value); +} + +fn logPush(line: []const u8) void { + log_lines.push(&log_arena, oc.Str8.fromSlice(line)) catch { + oc.log.err("Out of memory", .{}, @src()); + oc.requestQuit(); + return; + }; +} + +fn logPushf(comptime fmt: []const u8, args: anytype) void { + var str = oc.Str8.pushf(&log_arena, fmt, args) catch { + oc.log.err("Out of memory", .{}, @src()); + oc.requestQuit(); + return; + }; + log_lines.push(&log_arena, str) catch { + oc.log.err("Out of memory", .{}, @src()); + oc.requestQuit(); + return; + }; +} + +/// This makes sure the light theme doesn't break the styling overrides +/// You won't need it in a real program as long as your colors come from ui_ctx.theme or ui_ctx.theme.palette +fn resetNextRadioGroupToDarkTheme(arena: *oc.Arena) void { + var unselected_tag = ui.Tag.make("radio"); + var unselected_pattern = ui.Pattern.init(); + unselected_pattern.push(arena, .{ .sel = .{ .tag = unselected_tag } }); + ui.styleMatchAfter(unselected_pattern, .{ + .border_color = ui.dark_theme.text3, + .border_size = 1, + }); + + var unselected_hover_pattern = ui.Pattern.init(); + unselected_hover_pattern.push(arena, .{ .sel = .{ .tag = unselected_tag } }); + unselected_hover_pattern.push(arena, .{ .op = .And, .sel = .{ .status = .{ .hover = true } } }); + ui.styleMatchAfter(unselected_hover_pattern, .{ + .bg_color = ui.dark_theme.fill0, + .border_color = ui.dark_theme.primary, + }); + + var unselected_active_pattern = ui.Pattern.init(); + unselected_active_pattern.push(arena, .{ .sel = .{ .tag = unselected_tag } }); + unselected_active_pattern.push(arena, .{ .op = .And, .sel = .{ .status = .{ .active = true } } }); + ui.styleMatchAfter(unselected_active_pattern, .{ + .bg_color = ui.dark_theme.fill1, + .border_color = ui.dark_theme.primary, + }); + + var selected_tag = ui.Tag.make("radio_selected"); + var selected_pattern = ui.Pattern.init(); + selected_pattern.push(arena, .{ .sel = .{ .tag = selected_tag } }); + ui.styleMatchAfter(selected_pattern, .{ + .color = ui.dark_theme.palette.white, + .bg_color = ui.dark_theme.primary, + }); + + var selected_hover_pattern = ui.Pattern.init(); + selected_hover_pattern.push(arena, .{ .sel = .{ .tag = selected_tag } }); + selected_hover_pattern.push(arena, .{ .op = .And, .sel = .{ .status = .{ .hover = true } } }); + ui.styleMatchAfter(selected_hover_pattern, .{ + .bg_color = ui.dark_theme.primary_hover, + }); + + var selected_active_pattern = ui.Pattern.init(); + selected_active_pattern.push(arena, .{ .sel = .{ .tag = selected_tag } }); + selected_active_pattern.push(arena, .{ .op = .And, .sel = .{ .status = .{ .active = true } } }); + ui.styleMatchAfter(selected_active_pattern, .{ + .bg_color = ui.dark_theme.primary_active, + }); +} diff --git a/src/app/app.c b/src/app/app.c index a704722..030f02d 100644 --- a/src/app/app.c +++ b/src/app/app.c @@ -188,7 +188,7 @@ oc_key_code oc_scancode_to_keycode(oc_scan_code scanCode) return (oc_appData.keyMap[scanCode]); } -#define OC_DEFAULT_KEYMAP_ENTRY(sc, sv, ...) [(int) sc] = (oc_key_code)sc, +#define OC_DEFAULT_KEYMAP_ENTRY(sc, sv, kc, ...) [(int) sc] = OC_VA_NOPT(sv, ##__VA_ARGS__) __VA_ARGS__, oc_key_code oc_defaultKeyMap[OC_SCANCODE_COUNT] = { OC_KEY_TABLE(OC_DEFAULT_KEYMAP_ENTRY) diff --git a/src/orca.zig b/src/orca.zig new file mode 100644 index 0000000..2f2d994 --- /dev/null +++ b/src/orca.zig @@ -0,0 +1,3574 @@ +const std = @import("std"); +const AllocError = std.mem.Allocator.Error; + +//------------------------------------------------------------------------------------------ +// [PLATFORM] +//------------------------------------------------------------------------------------------ + +pub const Platform = enum(c_uint) { + MacOS, + Windows, +}; + +extern fn oc_get_host_platform() Platform; +pub const getHostPlatform = oc_get_host_platform; + +//------------------------------------------------------------------------------------------ +// [DEBUG] Logging +//------------------------------------------------------------------------------------------ + +pub const log = struct { + pub const Level = enum(c_uint) { + Error, + Warning, + Info, + }; + + pub const Output = opaque { + extern var OC_LOG_DEFAULT_OUTPUT: ?*Output; + extern fn oc_log_set_output(output: *Output) void; + + pub inline fn default() ?*Output { + return OC_LOG_DEFAULT_OUTPUT; + } + + const set = oc_log_set_output; + }; + + extern fn oc_log_set_level(level: Level) void; + extern fn oc_log_ext(level: Level, function: [*]const u8, file: [*]const u8, line: c_int, fmt: [*]const u8, ...) void; + + const setLevel = oc_log_set_level; + + pub fn info(comptime fmt: []const u8, args: anytype, source: std.builtin.SourceLocation) void { + ext(Level.Info, fmt, args, source); + } + + pub fn warn(comptime fmt: []const u8, args: anytype, source: std.builtin.SourceLocation) void { + ext(Level.Warning, fmt, args, source); + } + + pub fn err(comptime fmt: []const u8, args: anytype, source: std.builtin.SourceLocation) void { + ext(Level.Error, fmt, args, source); + } + + pub fn ext(comptime level: Level, comptime fmt: []const u8, args: anytype, source: std.builtin.SourceLocation) void { + var format_buf: [512:0]u8 = undefined; + _ = std.fmt.bufPrintZ(&format_buf, fmt, args) catch 0; // just discard NoSpaceLeft error for now + var line: c_int = @intCast(source.line); + + oc_log_ext(level, source.fn_name.ptr, source.file.ptr, line, format_buf[0..].ptr); + } +}; + +//------------------------------------------------------------------------------------------ +// [DEBUG] Assert/Abort +//------------------------------------------------------------------------------------------ + +extern fn oc_abort_ext(file: [*]const u8, function: [*]const u8, line: c_int, fmt: [*]const u8, ...) void; +extern fn oc_assert_fail(file: [*]const u8, function: [*]const u8, line: c_int, src: [*]const u8, fmt: [*]const u8, ...) void; + +pub fn assert(condition: bool, comptime fmt: []const u8, args: anytype, source: std.builtin.SourceLocation) void { + if (condition == false) { + var format_buf: [512:0]u8 = undefined; + _ = std.fmt.bufPrintZ(&format_buf, fmt, args) catch 0; + var line: c_int = @intCast(source.line); + + oc_assert_fail(source.file.ptr, source.fn_name.ptr, line, "assertion failed", format_buf[0..].ptr); + } +} + +pub fn abort(comptime fmt: []const u8, args: anytype, source: std.builtin.SourceLocation) void { + var format_buf: [512:0]u8 = undefined; + _ = std.fmt.bufPrintZ(&format_buf, fmt, args) catch 0; + var line: c_int = @intCast(source.line); + + oc_abort_ext(source.file.ptr, source.fn_name.ptr, line, format_buf[0..].ptr); +} + +//------------------------------------------------------------------------------------------ +// [INTRUSIVE LIST] +//------------------------------------------------------------------------------------------ + +pub const ListElt = extern struct { + prev: ?*ListElt, + next: ?*ListElt, + + pub fn entry(self: *ListElt, comptime ParentType: type, comptime field_name_in_parent: []const u8) *ParentType { + return @fieldParentPtr(ParentType, field_name_in_parent, self); + } + + pub fn nextEntry(comptime ParentType: type, comptime field_name_in_parent: []const u8, elt_parent: *ParentType) ?*ParentType { + const elt: ?*ListElt = @field(elt_parent, field_name_in_parent); + if (elt.next) |next| { + return next.entry(ParentType, field_name_in_parent); + } + + return null; + } + + pub fn prevEntry(comptime ParentType: type, comptime field_name_in_parent: []const u8, elt_parent: *ParentType) ?*ParentType { + const elt: ?*ListElt = @field(elt_parent, field_name_in_parent); + if (elt.prev) |prev| { + return prev.entry(ParentType, field_name_in_parent); + } + + return null; + } +}; + +pub const List = extern struct { + first: ?*ListElt, + last: ?*ListElt, + + pub fn init() List { + return .{ + .first = null, + .last = null, + }; + } + + pub fn firstEntry(self: *List, comptime EltParentType: type, comptime field_name_in_parent: []const u8) ?*EltParentType { + if (self.first) |elt| { + return elt.entry(EltParentType, field_name_in_parent); + } + return null; + } + + pub fn lastEntry(self: *List, comptime EltParentType: type, comptime field_name_in_parent: []const u8) ?*EltParentType { + if (self.last) |elt| { + return elt.entry(EltParentType, field_name_in_parent); + } + return null; + } + + const IterDirection = enum { + Forward, + Backward, + }; + + pub fn makeIter(comptime direction: IterDirection, comptime EltParentType: type, comptime field_name_in_parent: []const u8) type { + return struct { + const Self = @This(); + + item: ?*ListElt, + + pub fn next(self: *Self) ?*EltParentType { + if (self.item) |elt| { + var entry: *EltParentType = elt.entry(EltParentType, field_name_in_parent); + self.item = if (direction == .Forward) elt.next else elt.prev; + return entry; + } + return null; + } + }; + } + + pub fn iter(self: *const List, comptime EltParentType: type, comptime field_name_in_parent: []const u8) makeIter(.Forward, EltParentType, field_name_in_parent) { + const Iter = makeIter(.Forward, EltParentType, field_name_in_parent); + return Iter{ + .item = self.first, + }; + } + + pub fn iterReverse(self: *const List, comptime EltParentType: type, comptime field_name_in_parent: []const u8) makeIter(.Backward, EltParentType, field_name_in_parent) { + const Iter = makeIter(.Backward, EltParentType, field_name_in_parent); + return Iter{ + .item = self.last, + }; + } + + pub fn insert(self: *List, after_elt: ?*ListElt, elt: *ListElt) void { + elt.prev = after_elt; + elt.next = after_elt.next; + if (after_elt.next) |elt_next| { + after_elt.next.prev = elt_next; + } else { + self.last = elt; + } + after_elt.next = elt; + } + + pub fn insertBefore(self: *List, before_elt: ?*ListElt, elt: *ListElt) void { + elt.next = before_elt; + elt.prev = before_elt.prev; + if (before_elt.prev) |elt_prev| { + before_elt.prev.next = elt_prev; + } else { + self.first = elt; + } + before_elt.prev = elt; + } + + pub fn remove(self: *List, elt: *ListElt) void { + if (elt.prev != null) { + elt.prev.next = elt.next; + } else { + self.first = elt.next; + } + if (elt.next != null) { + elt.next.prev = elt.prev; + } else { + self.last = elt.prev; + } + elt.prev = blk: { + const tmp = null; + elt.next = tmp; + break :blk tmp; + }; + } + + pub fn push(self: *List, elt: *ListElt) void { + elt.next = self.first; + elt.prev = null; + if (self.first) |elt_first| { + elt_first.prev = elt; + } else { + self.last = elt; + } + self.first = elt; + } + + pub fn pop(self: *List) ?*ListElt { + if (self.first) |elt_begin| { + remove(self, elt_begin); + return elt_begin; + } + return null; + } + + pub fn popEntry(self: *List, comptime EltParentType: type, comptime field_name_in_parent: []const u8) ?*EltParentType { + if (self.pop()) |elt| { + return elt.entry(EltParentType, field_name_in_parent); + } + return null; + } + + pub fn pushBack(self: *List, elt: *ListElt) void { + elt.prev = self.last; + elt.next = null; + if (self.last) |last_elt| { + last_elt.next = elt; + } else { + self.first = elt; + } + self.last = elt; + } + + pub fn popBack(self: *List) ?*ListElt { + if (self.last) |last_elt| { + remove(self, last_elt); + return last_elt; + } + return null; + } + + pub fn empty(self: *const List) bool { + return (self.first == null) or (self.last == null); + } +}; + +//------------------------------------------------------------------------------------------ +// [MEMORY] +//------------------------------------------------------------------------------------------ + +pub const BaseAllocator = extern struct { + const MemReserveFunction = *const fn (context: *BaseAllocator, size: u64) ?[*]u8; + const MemModifyFunction = *const fn (context: *BaseAllocator, ptr: ?[*]u8, size: u64) ?[*]u8; + + func_reserve: MemReserveFunction, + func_commit: MemModifyFunction, + func_decommit: MemModifyFunction, + func_release: MemModifyFunction, + + extern fn oc_base_allocator_default() *BaseAllocator; + pub const default = oc_base_allocator_default; +}; + +pub const ArenaChunk = extern struct { + list_elt: ListElt, + ptr: ?[*]u8, + offset: u64, + committed: u64, + cap: u64, +}; + +pub const ArenaScope = extern struct { + arena: *Arena, + chunk: *ArenaChunk, + offset: u64, + + extern fn oc_arena_scope_end(scope: ArenaScope) void; + pub const end = oc_arena_scope_end; +}; + +pub const ArenaOptions = extern struct { + base: ?*BaseAllocator, + reserve: u64, +}; + +pub const Arena = extern struct { + base: ?*BaseAllocator, + chunks: List, + current_chunk: ?*ArenaChunk, + + extern fn oc_arena_init(arena: *Arena) void; + extern fn oc_arena_init_with_options(arena: *Arena, options: *ArenaOptions) void; + extern fn oc_arena_cleanup(arena: *Arena) void; + extern fn oc_arena_push(arena: *Arena, size: u64) ?[*]u8; + extern fn oc_arena_push_aligned(arena: *Arena, size: u64, alignment: u32) ?[*]u8; + extern fn oc_arena_clear(arena: *Arena) void; + extern fn oc_arena_scope_begin(arena: *Arena) ArenaScope; + + extern fn oc_scratch_begin() ArenaScope; + extern fn oc_scratch_begin_next(used: *Arena) ArenaScope; + + pub fn init() Arena { + var arena: Arena = undefined; + oc_arena_init(&arena); + return arena; + } + pub fn initWithOptions(opts: ArenaOptions) Arena { + var arena: Arena = undefined; + oc_arena_init(&arena, &opts); + return arena; + } + pub const deinit = oc_arena_cleanup; + + pub const clear = oc_arena_clear; + pub const scopeBegin = oc_arena_scope_begin; + + pub fn push(arena: *Arena, size: usize) AllocError![]u8 { + return arena.pushAligned(size, 1); + } + + pub fn pushAligned(arena: *Arena, size: usize, alignment: u32) AllocError![]u8 { + if (oc_arena_push_aligned(arena, size, alignment)) |mem| { + return mem[0..size]; + } + return AllocError.OutOfMemory; + } + + pub fn pushType(arena: *Arena, comptime T: type) AllocError!*T { + var mem: []u8 = try arena.pushAligned(@sizeOf(T), @alignOf(T)); + assert(mem.len >= @sizeOf(T), "need at least {} bytes, but got {}", .{ mem.len, @sizeOf(T) }, @src()); + var p: *T = @alignCast(@ptrCast(mem.ptr)); + return p; + } + + pub fn pushArray(arena: *Arena, comptime T: type, count: usize) AllocError![]T { + var mem: []u8 = try arena.pushAligned(@sizeOf(T) * count, @alignOf(T)); + std.debug.assert(mem.len >= @sizeOf(T) * count); + var items: [*]T = @alignCast(@ptrCast(mem.ptr)); + return items[0..count]; + } + + pub fn pushStr(arena: *Arena, str: []u8) AllocError![]u8 { + var result = try arena.pushArray(u8, str.len + 1); + @memcpy(result[0..str.len], str); + result[str.len] = 0; + return result; + } + + pub const scratchBegin = oc_scratch_begin; + pub const scratchBeginNext = oc_scratch_begin_next; +}; + +pub const Pool = extern struct { + arena: Arena, + free_list: List, + block_size: u64, +}; + +//------------------------------------------------------------------------------------------ +// [STRINGS] +//------------------------------------------------------------------------------------------ + +fn stringType(comptime CharType: type) type { + return extern struct { + pub const StrListElt = extern struct { + list_elt: ListElt, + string: Str, + }; + + pub const StrList = extern struct { + list: List, + elt_count: u64, + len: u64, + + pub fn init() StrList { + return .{ + .list = List.init(), + .elt_count = 0, + .len = 0, + }; + } + + pub fn push(list: *StrList, arena: *Arena, str: Str) AllocError!void { + var elt: *StrListElt = try arena.pushType(StrListElt); + elt.string = str; + list.list.pushBack(&elt.list_elt); + list.elt_count += 1; + list.len += str.len; + } + + pub fn pushSlice(list: *StrList, arena: *Arena, str: []const CharType) AllocError!void { + try list.push(arena, Str.fromSlice(str)); + } + + pub fn pushf(list: *StrList, arena: *Arena, comptime format: []const u8, args: anytype) AllocError!void { + var str = try Str.pushf(arena, format, args); + try list.push(arena, str); + } + + pub fn iter(list: *const StrList) List.makeIter(.Forward, StrListElt, "list_elt") { + return list.list.iter(StrListElt, "list_elt"); + } + + pub fn iterReverse(list: *const StrList) List.makeIter(.Backward, StrListElt, "list_elt") { + return list.list.iterReverse(StrListElt, "list_elt"); + } + + pub fn find(list: *const StrList, needle: *const Str) ?*StrListElt { + return list.findSlice(needle.slice()); + } + + pub fn findSlice(list: *const StrList, needle: []const CharType) ?*StrListElt { + var iterator = list.iter(); + while (iterator.next()) |elt_string| { + if (std.mem.eql(CharType, elt_string.string.slice(), needle)) { + return elt_string; + } + } + return null; + } + + pub fn contains(list: *const StrList, needle: *const Str) bool { + return list.findSlice(needle.slice()) != null; + } + + pub fn containsSlice(list: *const StrList, needle: []const CharType) bool { + return list.findSlice(needle) != null; + } + + pub fn join(list: *const StrList, arena: *Arena) AllocError!Str { + const empty = Str{ .ptr = null, .len = 0 }; + return try list.collate(arena, empty, empty, empty); + } + + pub fn collate(list: *const StrList, arena: *Arena, prefix: Str, separator: Str, postfix: Str) AllocError!Str { + var str: Str = undefined; + str.len = @intCast(prefix.len + list.len + (list.elt_count - 1) * separator.len + postfix.len); + str.ptr = (try arena.pushArray(CharType, str.len + 1)).ptr; + @memcpy(str.ptr.?[0..prefix.len], prefix.slice()); + + var offset = prefix.len; + + var iterator = list.iter(); + var index: usize = 0; + while (iterator.next()) |list_str| { + if (index > 0) { + @memcpy(str.ptr.?[offset .. offset + separator.len], separator.slice()); + offset += separator.len; + } + @memcpy(str.ptr.?[offset .. offset + list_str.string.len], list_str.string.slice()); + offset += list_str.string.len; + index += 1; + } + + @memcpy(str.ptr.?[offset .. offset + postfix.len], postfix.slice()); + str.ptr.?[str.len] = 0; + return str; + } + }; + + const Str = @This(); + + ptr: ?[*]CharType, + len: usize, + + extern fn strncmp(a: ?[*]u8, b: ?[*]u8, len: usize) c_int; + + pub fn fromSlice(str: []const CharType) Str { + return .{ + .ptr = @constCast(str.ptr), + .len = str.len, + }; + } + + pub fn slice(str: *const Str) []CharType { + if (str.ptr) |p| { + return p[0..str.len]; + } + + return &[_]CharType{}; + } + + pub fn sliceInner(str: *const Str, start: u64, end: u64) []CharType { + if (str.ptr) |p| { + assert(start <= end, "{}.sliceLen: start <= end", .{typename()}, @src()); + assert(end <= str.len, "{}.sliceLen: end <= str.len", .{typename()}, @src()); + return p[start..end]; + } + + return &[_]CharType{}; + } + + pub fn fromBuffer(len: u64, buffer: ?[]CharType) Str { + return .{ + .ptr = buffer, + .len = @intCast(len), + }; + } + + pub fn pushBuffer(arena: *Arena, buffer: []u8) Str { + var str: Str = undefined; + str.len = buffer.len; + str.ptr = arena.pushArray(CharType, buffer.len + 1); + @memcpy(str.ptr[0..buffer.len], buffer); + str.ptr[buffer.len] = 0; + return str; + } + + pub fn pushCopy(str: *const Str, arena: *Arena) Str { + return pushBuffer(arena, str.ptr[0..str.len]); + } + + pub fn pushSlice(str: *const Str, arena: *Arena, start: usize, end: usize) Str { + return pushBuffer(arena, str.ptr[start..end]); + } + + pub fn pushf(arena: *Arena, comptime format: []const u8, args: anytype) AllocError!Str { + if (CharType != u8) { + @compileError("pushf() is only supported for Str8"); + } + + var str: Str = undefined; + str.len = @intCast(std.fmt.count(format, args)); + str.ptr = (try arena.pushArray(CharType, str.len + 1)).ptr; + _ = std.fmt.bufPrintZ(str.ptr.?[0 .. str.len + 1], format, args) catch unreachable; + return str; + } + + pub fn join(arena: *Arena, strings: []const []const CharType) AllocError!Str { + const empty = &[_]CharType{}; + return collate(arena, strings, empty, empty, empty); + } + + pub fn collate( + arena: *Arena, + strings: []const []const CharType, + prefix: []const CharType, + separator: []const CharType, + postfix: []const CharType, + ) AllocError!Str { + var strings_total_len: usize = 0; + for (strings) |s| { + strings_total_len += s.len; + } + + var str: Str = undefined; + str.len = prefix.len + strings_total_len + (strings.len - 1) * separator.len + postfix.len; + str.ptr = (try arena.pushArray(CharType, str.len + 1)).ptr; + @memcpy(str.ptr.?[0..prefix.len], prefix); + + var offset = prefix.len; + + for (strings, 0..) |list_str, index| { + if (index > 0) { + @memcpy(str.ptr.?[offset .. offset + separator.len], separator); + offset += separator.len; + } + @memcpy(str.ptr.?[offset .. offset + list_str.len], list_str); + offset += list_str.len; + } + + @memcpy(str.ptr.?[offset .. offset + postfix.len], postfix); + offset += postfix.len; + str.ptr.?[str.len] = 0; + return str; + } + + pub fn split(str: *const Str, arena: *Arena, separators: StrList) AllocError!StrList { + var list = StrList.init(); + if (str.ptr == null) { + return list; + } + + const ptr = str.ptr.?; + + var offset: usize = 0; + var offset_substring: usize = 0; + + while (offset < str.len) { + const haystack = ptr[offset..str.len]; + var separator_iter = separators.iter(); + while (separator_iter.next()) |list_sep| { + if (std.mem.startsWith(CharType, haystack, list_sep.string.slice()) and list_sep.string.len > 0) { + var substr = ptr[offset_substring..offset]; + if (separators.containsSlice(substr)) { + substr = ptr[offset..offset]; + } + try list.pushSlice(arena, substr); + + // -1 / +1 to account for offset += 1 at the end of the loop + offset += list_sep.string.len - 1; + offset_substring = offset + 1; + break; + } + } + + offset += 1; + } + + if (offset_substring != offset) { + var substr = ptr[offset_substring..offset]; + try list.pushSlice(arena, substr); + } + + return list; + } + + pub fn cmp(a: *const Str, b: *const Str) std.math.Order { + if (CharType != u8) { + @compileError("cmp() is only supported for Str8"); + } + const value = strncmp(a.ptr, b.ptr, std.math.min(a.len, b.len)); + if (value < 0) { + return .lt; + } else if (value == 0) { + return .eq; + } else { + return .gt; + } + } + + fn typename() []const u8 { + return switch (CharType) { + u8 => "Str8", + u16 => "Str16", + u32 => "Str32", + else => unreachable, + }; + } + }; +} + +pub const Str8 = stringType(u8); +pub const Str16 = stringType(u16); +pub const Str32 = stringType(u32); + +pub const Str8List = Str8.StrList; +pub const Str16List = Str16.StrList; +pub const Str32List = Str32.StrList; + +pub const Str8ListElt = Str8.StrListElt; +pub const Str16ListElt = Str16.StrListElt; +pub const Str32ListElt = Str32.StrListElt; + +//------------------------------------------------------------------------------------------ +// [UTF8] +//------------------------------------------------------------------------------------------ + +pub const Utf32 = u32; + +pub const Utf8Dec = extern struct { + code_point: Utf32, // decoded codepoint + size: u32, // size of corresponding oc_utf8 sequence +}; + +pub const Utf8 = struct { + + // getting sizes / offsets / indices + extern fn oc_utf8_size_from_leading_char(leadingChar: c_char) u32; + extern fn oc_utf8_codepoint_size(code_point: Utf32) u32; + extern fn oc_utf8_codepoint_count_for_string(string: Str8) u64; + extern fn oc_utf8_byte_count_for_codepoints(code_points: Str32) u64; + extern fn oc_utf8_next_offset(string: Str8, byteOffset: u64) u64; + extern fn oc_utf8_prev_offset(string: Str8, byteOffset: u64) u64; + + pub const sizeFromLeadingChar = oc_utf8_size_from_leading_char; + pub const codepointSize = oc_utf8_codepoint_size; + pub const codepointCountForString = oc_utf8_codepoint_count_for_string; + pub const byteCountForCodepoints = oc_utf8_byte_count_for_codepoints; + pub const nextOffset = oc_utf8_next_offset; + pub const prevOffset = oc_utf8_prev_offset; + + // encoding / decoding + extern fn oc_utf8_decode(string: Str8) Utf8Dec; //NOTE: decode a single oc_utf8 sequence at start of string + extern fn oc_utf8_decode_at(string: Str8, offset: u64) Utf8Dec; //NOTE: decode a single oc_utf8 sequence starting at byte offset + extern fn oc_utf8_encode(dst: [*]u8, code_point: Utf32) Str8; //NOTE: encode codepoint into backing buffer dst + extern fn oc_utf8_to_codepoints(maxCount: u64, backing: [*]Utf32, string: Str8) Str32; + extern fn oc_utf8_from_codepoints(maxBytes: u64, backing: [*]c_char, code_points: Str32) Str8; + extern fn oc_utf8_push_to_codepoints(arena: *Arena, string: Str8) Str32; + extern fn oc_utf8_push_from_codepoints(arena: *Arena, code_points: Str32) Str8; + + pub const decode = oc_utf8_decode; + pub const decodeAt = oc_utf8_decode_at; + pub const encode = oc_utf8_encode; + pub const toCodepoints = oc_utf8_to_codepoints; + pub const fromCodepoints = oc_utf8_from_codepoints; + pub const pushToCodepoints = oc_utf8_push_to_codepoints; + pub const pushFromCodepoints = oc_utf8_push_from_codepoints; +}; + +//------------------------------------------------------------------------------------------ +// [UTF8] Unicode +//------------------------------------------------------------------------------------------ + +pub const UnicodeRange = extern struct { + first_code_point: Utf32, + count: u32, + + extern const OC_UNICODE_BASIC_LATIN: UnicodeRange; + extern const OC_UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT: UnicodeRange; + extern const OC_UNICODE_LATIN_EXTENDED_A: UnicodeRange; + extern const OC_UNICODE_LATIN_EXTENDED_B: UnicodeRange; + extern const OC_UNICODE_IPA_EXTENSIONS: UnicodeRange; + extern const OC_UNICODE_SPACING_MODIFIER_LETTERS: UnicodeRange; + extern const OC_UNICODE_COMBINING_DIACRITICAL_MARKS: UnicodeRange; + extern const OC_UNICODE_GREEK_COPTIC: UnicodeRange; + extern const OC_UNICODE_CYRILLIC: UnicodeRange; + extern const OC_UNICODE_CYRILLIC_SUPPLEMENT: UnicodeRange; + extern const OC_UNICODE_ARMENIAN: UnicodeRange; + extern const OC_UNICODE_HEBREW: UnicodeRange; + extern const OC_UNICODE_ARABIC: UnicodeRange; + extern const OC_UNICODE_SYRIAC: UnicodeRange; + extern const OC_UNICODE_THAANA: UnicodeRange; + extern const OC_UNICODE_DEVANAGARI: UnicodeRange; + extern const OC_UNICODE_BENGALI_ASSAMESE: UnicodeRange; + extern const OC_UNICODE_GURMUKHI: UnicodeRange; + extern const OC_UNICODE_GUJARATI: UnicodeRange; + extern const OC_UNICODE_ORIYA: UnicodeRange; + extern const OC_UNICODE_TAMIL: UnicodeRange; + extern const OC_UNICODE_TELUGU: UnicodeRange; + extern const OC_UNICODE_KANNADA: UnicodeRange; + extern const OC_UNICODE_MALAYALAM: UnicodeRange; + extern const OC_UNICODE_SINHALA: UnicodeRange; + extern const OC_UNICODE_THAI: UnicodeRange; + extern const OC_UNICODE_LAO: UnicodeRange; + extern const OC_UNICODE_TIBETAN: UnicodeRange; + extern const OC_UNICODE_MYANMAR: UnicodeRange; + extern const OC_UNICODE_GEORGIAN: UnicodeRange; + extern const OC_UNICODE_HANGUL_JAMO: UnicodeRange; + extern const OC_UNICODE_ETHIOPIC: UnicodeRange; + extern const OC_UNICODE_CHEROKEE: UnicodeRange; + extern const OC_UNICODE_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS: UnicodeRange; + extern const OC_UNICODE_OGHAM: UnicodeRange; + extern const OC_UNICODE_RUNIC: UnicodeRange; + extern const OC_UNICODE_TAGALOG: UnicodeRange; + extern const OC_UNICODE_HANUNOO: UnicodeRange; + extern const OC_UNICODE_BUHID: UnicodeRange; + extern const OC_UNICODE_TAGBANWA: UnicodeRange; + extern const OC_UNICODE_KHMER: UnicodeRange; + extern const OC_UNICODE_MONGOLIAN: UnicodeRange; + extern const OC_UNICODE_LIMBU: UnicodeRange; + extern const OC_UNICODE_TAI_LE: UnicodeRange; + extern const OC_UNICODE_KHMER_SYMBOLS: UnicodeRange; + extern const OC_UNICODE_PHONETIC_EXTENSIONS: UnicodeRange; + extern const OC_UNICODE_LATIN_EXTENDED_ADDITIONAL: UnicodeRange; + extern const OC_UNICODE_GREEK_EXTENDED: UnicodeRange; + extern const OC_UNICODE_GENERAL_PUNCTUATION: UnicodeRange; + extern const OC_UNICODE_SUPERSCRIPTS_AND_SUBSCRIPTS: UnicodeRange; + extern const OC_UNICODE_CURRENCY_SYMBOLS: UnicodeRange; + extern const OC_UNICODE_COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS: UnicodeRange; + extern const OC_UNICODE_LETTERLIKE_SYMBOLS: UnicodeRange; + extern const OC_UNICODE_NUMBER_FORMS: UnicodeRange; + extern const OC_UNICODE_ARROWS: UnicodeRange; + extern const OC_UNICODE_MATHEMATICAL_OPERATORS: UnicodeRange; + extern const OC_UNICODE_MISCELLANEOUS_TECHNICAL: UnicodeRange; + extern const OC_UNICODE_CONTROL_PICTURES: UnicodeRange; + extern const OC_UNICODE_OPTICAL_CHARACTER_RECOGNITION: UnicodeRange; + extern const OC_UNICODE_ENCLOSED_ALPHANUMERICS: UnicodeRange; + extern const OC_UNICODE_BOX_DRAWING: UnicodeRange; + extern const OC_UNICODE_BLOCK_ELEMENTS: UnicodeRange; + extern const OC_UNICODE_GEOMETRIC_SHAPES: UnicodeRange; + extern const OC_UNICODE_MISCELLANEOUS_SYMBOLS: UnicodeRange; + extern const OC_UNICODE_DINGBATS: UnicodeRange; + extern const OC_UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A: UnicodeRange; + extern const OC_UNICODE_SUPPLEMENTAL_ARROWS_A: UnicodeRange; + extern const OC_UNICODE_BRAILLE_PATTERNS: UnicodeRange; + extern const OC_UNICODE_SUPPLEMENTAL_ARROWS_B: UnicodeRange; + extern const OC_UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B: UnicodeRange; + extern const OC_UNICODE_SUPPLEMENTAL_MATHEMATICAL_OPERATORS: UnicodeRange; + extern const OC_UNICODE_MISCELLANEOUS_SYMBOLS_AND_ARROWS: UnicodeRange; + extern const OC_UNICODE_CJK_RADICALS_SUPPLEMENT: UnicodeRange; + extern const OC_UNICODE_KANGXI_RADICALS: UnicodeRange; + extern const OC_UNICODE_IDEOGRAPHIC_DESCRIPTION_CHARACTERS: UnicodeRange; + extern const OC_UNICODE_CJK_SYMBOLS_AND_PUNCTUATION: UnicodeRange; + extern const OC_UNICODE_HIRAGANA: UnicodeRange; + extern const OC_UNICODE_KATAKANA: UnicodeRange; + extern const OC_UNICODE_BOPOMOFO: UnicodeRange; + extern const OC_UNICODE_HANGUL_COMPATIBILITY_JAMO: UnicodeRange; + extern const OC_UNICODE_KANBUN_KUNTEN: UnicodeRange; + extern const OC_UNICODE_BOPOMOFO_EXTENDED: UnicodeRange; + extern const OC_UNICODE_KATAKANA_PHONETIC_EXTENSIONS: UnicodeRange; + extern const OC_UNICODE_ENCLOSED_CJK_LETTERS_AND_MONTHS: UnicodeRange; + extern const OC_UNICODE_CJK_COMPATIBILITY: UnicodeRange; + extern const OC_UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A: UnicodeRange; + extern const OC_UNICODE_YIJING_HEXAGRAM_SYMBOLS: UnicodeRange; + extern const OC_UNICODE_CJK_UNIFIED_IDEOGRAPHS: UnicodeRange; + extern const OC_UNICODE_YI_SYLLABLES: UnicodeRange; + extern const OC_UNICODE_YI_RADICALS: UnicodeRange; + extern const OC_UNICODE_HANGUL_SYLLABLES: UnicodeRange; + extern const OC_UNICODE_HIGH_SURROGATE_AREA: UnicodeRange; + extern const OC_UNICODE_LOW_SURROGATE_AREA: UnicodeRange; + extern const OC_UNICODE_PRIVATE_USE_AREA: UnicodeRange; + extern const OC_UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS: UnicodeRange; + extern const OC_UNICODE_ALPHABETIC_PRESENTATION_FORMS: UnicodeRange; + extern const OC_UNICODE_ARABIC_PRESENTATION_FORMS_A: UnicodeRange; + extern const OC_UNICODE_VARIATION_SELECTORS: UnicodeRange; + extern const OC_UNICODE_COMBINING_HALF_MARKS: UnicodeRange; + extern const OC_UNICODE_CJK_COMPATIBILITY_FORMS: UnicodeRange; + extern const OC_UNICODE_SMALL_FORM_VARIANTS: UnicodeRange; + extern const OC_UNICODE_ARABIC_PRESENTATION_FORMS_B: UnicodeRange; + extern const OC_UNICODE_HALFWIDTH_AND_FULLWIDTH_FORMS: UnicodeRange; + extern const OC_UNICODE_SPECIALS: UnicodeRange; + extern const OC_UNICODE_LINEAR_B_SYLLABARY: UnicodeRange; + extern const OC_UNICODE_LINEAR_B_IDEOGRAMS: UnicodeRange; + extern const OC_UNICODE_AEGEAN_NUMBERS: UnicodeRange; + extern const OC_UNICODE_OLD_ITALIC: UnicodeRange; + extern const OC_UNICODE_GOTHIC: UnicodeRange; + extern const OC_UNICODE_UGARITIC: UnicodeRange; + extern const OC_UNICODE_DESERET: UnicodeRange; + extern const OC_UNICODE_SHAVIAN: UnicodeRange; + extern const OC_UNICODE_OSMANYA: UnicodeRange; + extern const OC_UNICODE_CYPRIOT_SYLLABARY: UnicodeRange; + extern const OC_UNICODE_BYZANTINE_MUSICAL_SYMBOLS: UnicodeRange; + extern const OC_UNICODE_MUSICAL_SYMBOLS: UnicodeRange; + extern const OC_UNICODE_TAI_XUAN_JING_SYMBOLS: UnicodeRange; + extern const OC_UNICODE_MATHEMATICAL_ALPHANUMERIC_SYMBOLS: UnicodeRange; + extern const OC_UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B: UnicodeRange; + extern const OC_UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT: UnicodeRange; + extern const OC_UNICODE_TAGS: UnicodeRange; + extern const OC_UNICODE_VARIATION_SELECTORS_SUPPLEMENT: UnicodeRange; + extern const OC_UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_A: UnicodeRange; + extern const OC_UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_B: UnicodeRange; + + pub const Enum = enum { + BasicLatin, + C1ControlsAndLatin1Supplement, + LatinExtendedA, + LatinExtendedB, + IpaExtensions, + SpacingModifierLetters, + CombiningDiacriticalMarks, + GreekCoptic, + Cyrillic, + CyrillicSupplement, + Armenian, + Hebrew, + Arabic, + Syriac, + Thaana, + Devanagari, + BengaliAssamese, + Gurmukhi, + Gujarati, + Oriya, + Tamil, + Telugu, + Kannada, + Malayalam, + Sinhala, + Thai, + Lao, + Tibetan, + Myanmar, + Georgian, + HangulJamo, + Ethiopic, + Cherokee, + UnifiedCanadianAboriginalSyllabics, + Ogham, + Runic, + Tagalog, + Hanunoo, + Buhid, + Tagbanwa, + Khmer, + Mongolian, + Limbu, + TaiLe, + KhmerSymbols, + PhoneticExtensions, + LatinExtendedAdditional, + GreekExtended, + GeneralPunctuation, + SuperscriptsAndSubscripts, + CurrencySymbols, + CombiningDiacriticalMarksForSymbols, + LetterlikeSymbols, + NumberForms, + Arrows, + MathematicalOperators, + MiscellaneousTechnical, + ControlPictures, + OpticalCharacterRecognition, + EnclosedAlphanumerics, + BoxDrawing, + BlockElements, + GeometricShapes, + MiscellaneousSymbols, + Dingbats, + MiscellaneousMathematicalSymbolsA, + SupplementalArrowsA, + BraillePatterns, + SupplementalArrowsB, + MiscellaneousMathematicalSymbolsB, + SupplementalMathematicalOperators, + MiscellaneousSymbolsAndArrows, + CjkRadicalsSupplement, + KangxiRadicals, + IdeographicDescriptionCharacters, + CjkSymbolsAndPunctuation, + Hiragana, + Katakana, + Bopomofo, + HangulCompatibilityJamo, + KanbunKunten, + BopomofoExtended, + KatakanaPhoneticExtensions, + EnclosedCjkLettersAndMonths, + CjkCompatibility, + CjkUnifiedIdeographsExtensionA, + YijingHexagramSymbols, + CjkUnifiedIdeographs, + YiSyllables, + YiRadicals, + HangulSyllables, + HighSurrogateArea, + LowSurrogateArea, + PrivateUseArea, + CjkCompatibilityIdeographs, + AlphabeticPresentationForms, + ArabicPresentationFormsA, + VariationSelectors, + CombiningHalfMarks, + CjkCompatibilityForms, + SmallFormVariants, + ArabicPresentationFormsB, + HalfwidthAndFullwidthForms, + Specials, + LinearBSyllabary, + LinearBIdeograms, + AegeanNumbers, + OldItalic, + Gothic, + Ugaritic, + Deseret, + Shavian, + Osmanya, + CypriotSyllabary, + ByzantineMusicalSymbols, + MusicalSymbols, + TaiXuanJingSymbols, + MathematicalAlphanumericSymbols, + CjkUnifiedIdeographsExtensionB, + CjkCompatibilityIdeographsSupplement, + Tags, + VariationSelectorsSupplement, + SupplementaryPrivateUseAreaA, + SupplementaryPrivateUseAreaB, + }; + + pub fn range(comptime enums: []const Enum) [enums.len]UnicodeRange { + var r: [enums.len]UnicodeRange = undefined; + for (enums, 0..r.len) |e, i| { + r[i] = switch (e) { + .BasicLatin => OC_UNICODE_BASIC_LATIN, + .C1ControlsAndLatin1Supplement => OC_UNICODE_C1_CONTROLS_AND_LATIN_1_SUPPLEMENT, + .LatinExtendedA => OC_UNICODE_LATIN_EXTENDED_A, + .LatinExtendedB => OC_UNICODE_LATIN_EXTENDED_B, + .IpaExtensions => OC_UNICODE_IPA_EXTENSIONS, + .SpacingModifierLetters => OC_UNICODE_SPACING_MODIFIER_LETTERS, + .CombiningDiacriticalMarks => OC_UNICODE_COMBINING_DIACRITICAL_MARKS, + .GreekCoptic => OC_UNICODE_GREEK_COPTIC, + .Cyrillic => OC_UNICODE_CYRILLIC, + .CyrillicSupplement => OC_UNICODE_CYRILLIC_SUPPLEMENT, + .Armenian => OC_UNICODE_ARMENIAN, + .Hebrew => OC_UNICODE_HEBREW, + .Arabic => OC_UNICODE_ARABIC, + .Syriac => OC_UNICODE_SYRIAC, + .Thaana => OC_UNICODE_THAANA, + .Devanagari => OC_UNICODE_DEVANAGARI, + .BengaliAssamese => OC_UNICODE_BENGALI_ASSAMESE, + .Gurmukhi => OC_UNICODE_GURMUKHI, + .Gujarati => OC_UNICODE_GUJARATI, + .Oriya => OC_UNICODE_ORIYA, + .Tamil => OC_UNICODE_TAMIL, + .Telugu => OC_UNICODE_TELUGU, + .Kannada => OC_UNICODE_KANNADA, + .Malayalam => OC_UNICODE_MALAYALAM, + .Sinhala => OC_UNICODE_SINHALA, + .Thai => OC_UNICODE_THAI, + .Lao => OC_UNICODE_LAO, + .Tibetan => OC_UNICODE_TIBETAN, + .Myanmar => OC_UNICODE_MYANMAR, + .Georgian => OC_UNICODE_GEORGIAN, + .HangulJamo => OC_UNICODE_HANGUL_JAMO, + .Ethiopic => OC_UNICODE_ETHIOPIC, + .Cherokee => OC_UNICODE_CHEROKEE, + .UnifiedCanadianAboriginalSyllabics => OC_UNICODE_UNIFIED_CANADIAN_ABORIGINAL_SYLLABICS, + .Ogham => OC_UNICODE_OGHAM, + .Runic => OC_UNICODE_RUNIC, + .Tagalog => OC_UNICODE_TAGALOG, + .Hanunoo => OC_UNICODE_HANUNOO, + .Buhid => OC_UNICODE_BUHID, + .Tagbanwa => OC_UNICODE_TAGBANWA, + .Khmer => OC_UNICODE_KHMER, + .Mongolian => OC_UNICODE_MONGOLIAN, + .Limbu => OC_UNICODE_LIMBU, + .TaiLe => OC_UNICODE_TAI_LE, + .KhmerSymbols => OC_UNICODE_KHMER_SYMBOLS, + .PhoneticExtensions => OC_UNICODE_PHONETIC_EXTENSIONS, + .LatinExtendedAdditional => OC_UNICODE_LATIN_EXTENDED_ADDITIONAL, + .GreekExtended => OC_UNICODE_GREEK_EXTENDED, + .GeneralPunctuation => OC_UNICODE_GENERAL_PUNCTUATION, + .SuperscriptsAndSubscripts => OC_UNICODE_SUPERSCRIPTS_AND_SUBSCRIPTS, + .CurrencySymbols => OC_UNICODE_CURRENCY_SYMBOLS, + .CombiningDiacriticalMarksForSymbols => OC_UNICODE_COMBINING_DIACRITICAL_MARKS_FOR_SYMBOLS, + .LetterlikeSymbols => OC_UNICODE_LETTERLIKE_SYMBOLS, + .NumberForms => OC_UNICODE_NUMBER_FORMS, + .Arrows => OC_UNICODE_ARROWS, + .MathematicalOperators => OC_UNICODE_MATHEMATICAL_OPERATORS, + .MiscellaneousTechnical => OC_UNICODE_MISCELLANEOUS_TECHNICAL, + .ControlPictures => OC_UNICODE_CONTROL_PICTURES, + .OpticalCharacterRecognition => OC_UNICODE_OPTICAL_CHARACTER_RECOGNITION, + .EnclosedAlphanumerics => OC_UNICODE_ENCLOSED_ALPHANUMERICS, + .BoxDrawing => OC_UNICODE_BOX_DRAWING, + .BlockElements => OC_UNICODE_BLOCK_ELEMENTS, + .GeometricShapes => OC_UNICODE_GEOMETRIC_SHAPES, + .MiscellaneousSymbols => OC_UNICODE_MISCELLANEOUS_SYMBOLS, + .Dingbats => OC_UNICODE_DINGBATS, + .MiscellaneousMathematicalSymbolsA => OC_UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_A, + .SupplementalArrowsA => OC_UNICODE_SUPPLEMENTAL_ARROWS_A, + .BraillePatterns => OC_UNICODE_BRAILLE_PATTERNS, + .SupplementalArrowsB => OC_UNICODE_SUPPLEMENTAL_ARROWS_B, + .MiscellaneousMathematicalSymbolsB => OC_UNICODE_MISCELLANEOUS_MATHEMATICAL_SYMBOLS_B, + .SupplementalMathematicalOperators => OC_UNICODE_SUPPLEMENTAL_MATHEMATICAL_OPERATORS, + .MiscellaneousSymbolsAndArrows => OC_UNICODE_MISCELLANEOUS_SYMBOLS_AND_ARROWS, + .CjkRadicalsSupplement => OC_UNICODE_CJK_RADICALS_SUPPLEMENT, + .KangxiRadicals => OC_UNICODE_KANGXI_RADICALS, + .IdeographicDescriptionCharacters => OC_UNICODE_IDEOGRAPHIC_DESCRIPTION_CHARACTERS, + .CjkSymbolsAndPunctuation => OC_UNICODE_CJK_SYMBOLS_AND_PUNCTUATION, + .Hiragana => OC_UNICODE_HIRAGANA, + .Katakana => OC_UNICODE_KATAKANA, + .Bopomofo => OC_UNICODE_BOPOMOFO, + .HangulCompatibilityJamo => OC_UNICODE_HANGUL_COMPATIBILITY_JAMO, + .KanbunKunten => OC_UNICODE_KANBUN_KUNTEN, + .BopomofoExtended => OC_UNICODE_BOPOMOFO_EXTENDED, + .KatakanaPhoneticExtensions => OC_UNICODE_KATAKANA_PHONETIC_EXTENSIONS, + .EnclosedCjkLettersAndMonths => OC_UNICODE_ENCLOSED_CJK_LETTERS_AND_MONTHS, + .CjkCompatibility => OC_UNICODE_CJK_COMPATIBILITY, + .CjkUnifiedIdeographsExtensionA => OC_UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A, + .YijingHexagramSymbols => OC_UNICODE_YIJING_HEXAGRAM_SYMBOLS, + .CjkUnifiedIdeographs => OC_UNICODE_CJK_UNIFIED_IDEOGRAPHS, + .YiSyllables => OC_UNICODE_YI_SYLLABLES, + .YiRadicals => OC_UNICODE_YI_RADICALS, + .HangulSyllables => OC_UNICODE_HANGUL_SYLLABLES, + .HighSurrogateArea => OC_UNICODE_HIGH_SURROGATE_AREA, + .LowSurrogateArea => OC_UNICODE_LOW_SURROGATE_AREA, + .PrivateUseArea => OC_UNICODE_PRIVATE_USE_AREA, + .CjkCompatibilityIdeographs => OC_UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS, + .AlphabeticPresentationForms => OC_UNICODE_ALPHABETIC_PRESENTATION_FORMS, + .ArabicPresentationFormsA => OC_UNICODE_ARABIC_PRESENTATION_FORMS_A, + .VariationSelectors => OC_UNICODE_VARIATION_SELECTORS, + .CombiningHalfMarks => OC_UNICODE_COMBINING_HALF_MARKS, + .CjkCompatibilityForms => OC_UNICODE_CJK_COMPATIBILITY_FORMS, + .SmallFormVariants => OC_UNICODE_SMALL_FORM_VARIANTS, + .ArabicPresentationFormsB => OC_UNICODE_ARABIC_PRESENTATION_FORMS_B, + .HalfwidthAndFullwidthForms => OC_UNICODE_HALFWIDTH_AND_FULLWIDTH_FORMS, + .Specials => OC_UNICODE_SPECIALS, + .LinearBSyllabary => OC_UNICODE_LINEAR_B_SYLLABARY, + .LinearBIdeograms => OC_UNICODE_LINEAR_B_IDEOGRAMS, + .AegeanNumbers => OC_UNICODE_AEGEAN_NUMBERS, + .OldItalic => OC_UNICODE_OLD_ITALIC, + .Gothic => OC_UNICODE_GOTHIC, + .Ugaritic => OC_UNICODE_UGARITIC, + .Deseret => OC_UNICODE_DESERET, + .Shavian => OC_UNICODE_SHAVIAN, + .Osmanya => OC_UNICODE_OSMANYA, + .CypriotSyllabary => OC_UNICODE_CYPRIOT_SYLLABARY, + .ByzantineMusicalSymbols => OC_UNICODE_BYZANTINE_MUSICAL_SYMBOLS, + .MusicalSymbols => OC_UNICODE_MUSICAL_SYMBOLS, + .TaiXuanJingSymbols => OC_UNICODE_TAI_XUAN_JING_SYMBOLS, + .MathematicalAlphanumericSymbols => OC_UNICODE_MATHEMATICAL_ALPHANUMERIC_SYMBOLS, + .CjkUnifiedIdeographsExtensionB => OC_UNICODE_CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B, + .CjkCompatibilityIdeographsSupplement => OC_UNICODE_CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT, + .Tags => OC_UNICODE_TAGS, + .VariationSelectorsSupplement => OC_UNICODE_VARIATION_SELECTORS_SUPPLEMENT, + .SupplementaryPrivateUseAreaA => OC_UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_A, + .SupplementaryPrivateUseAreaB => OC_UNICODE_SUPPLEMENTARY_PRIVATE_USE_AREA_B, + }; + } + + return r; + } +}; + +//------------------------------------------------------------------------------------------ +// [FONT]: fonts +//------------------------------------------------------------------------------------------ + +pub const Font = extern struct { + h: u64, + + extern fn oc_font_nil() Font; + extern fn oc_font_is_nil(font: Font) bool; + + extern fn oc_font_create_from_memory(mem: Str8, range_count: u32, ranges: [*]const UnicodeRange) Font; + extern fn oc_font_create_from_file(file: File, range_count: u32, ranges: [*]const UnicodeRange) Font; + extern fn oc_font_create_from_path(path: Str8, range_count: u32, ranges: [*]const UnicodeRange) Font; + + extern fn oc_font_get_glyph_indices(font: Font, code_points: Str32, backing: Str32) Str32; + extern fn oc_font_push_glyph_indices(font: Font, arena: *Arena, code_points: Str32) Str32; + extern fn oc_font_get_glyph_index(font: Font, code_point: Utf32) u32; + extern fn oc_font_destroy(font: Font) void; + + extern fn oc_font_get_metrics(font: Font) FontMetrics; + extern fn oc_font_get_metrics_unscaled(font: Font, em_size: f32) FontMetrics; + extern fn oc_font_get_scale_for_em_pixels(font: Font, em_size: f32) f32; + extern fn oc_font_text_metrics_utf32(font: Font, font_size: f32, code_points: Str32) TextMetrics; + extern fn oc_font_text_metrics(font: Font, font_size: f32, text: Str8) TextMetrics; + + pub const nil = oc_font_nil; + pub const isNil = oc_font_is_nil; + pub fn createFromMemory(mem: []const u8, ranges: []const UnicodeRange) Font { + return oc_font_create_from_memory(Str8.fromSlice(mem), @intCast(ranges.len), ranges.ptr); + } + pub fn createFromFile(file: File, ranges: []const UnicodeRange) Font { + return oc_font_create_from_file(file, @intCast(ranges.len), ranges.ptr); + } + pub fn createFromPath(path: []const u8, ranges: []const UnicodeRange) Font { + return oc_font_create_from_path(Str8.fromSlice(path), @intCast(ranges.len), ranges.ptr); + } + pub const getGlyphIndices = oc_font_get_glyph_indices; + pub const pushGlyphIndices = oc_font_push_glyph_indices; + pub const getGlyphIndex = oc_font_get_glyph_index; + pub const destroy = oc_font_destroy; + pub const getMetrics = oc_font_get_metrics; + pub const getMetricsUnscaled = oc_font_get_metrics_unscaled; + pub const getScaleForEmPixels = oc_font_get_scale_for_em_pixels; + pub const textMetricsUtf32 = oc_font_text_metrics_utf32; + pub const textMetrics = oc_font_text_metrics; +}; + +pub const JointType = enum(c_uint) { + Miter, + Bevel, + None, +}; + +pub const CapType = enum(c_uint) { + None, + Square, +}; + +pub const FontMetrics = extern struct { + ascent: f32, // the extent above the baseline (by convention a positive value extends above the baseline) + descent: f32, // the extent below the baseline (by convention, positive value extends below the baseline) + line_gap: f32, // spacing between one row's descent and the next row's ascent + x_height: f32, // height of the lower case letter 'x' + cap_height: f32, // height of the upper case letter 'M' + width: f32, // maximum width of the font +}; + +pub const GlyphMetrics = extern struct { + ink: Rect, + advance: Vec2, +}; + +pub const TextMetrics = extern struct { + ink: Rect, + logical: Rect, + advance: Vec2, +}; + +//------------------------------------------------------------------------------------------ +// [APP] +//------------------------------------------------------------------------------------------ + +extern fn oc_request_quit() void; +pub const requestQuit = oc_request_quit; + +//------------------------------------------------------------------------------------------ +// [APP] input +//------------------------------------------------------------------------------------------ + +const MouseCursor = enum(c_uint) { + Arrow, + Resize0, + Resize90, + Resize45, + Resize135, + Text, +}; +extern fn oc_set_cursor(cursor: MouseCursor) void; +pub const setCursor = oc_set_cursor; + +pub const EventType = enum(c_uint) { + none, + keyboard_mods, + keyboard_key, + keyboard_char, + mouse_button, + mouse_move, + mouse_wheel, + mouse_enter, + mouse_leave, + clipboard_paste, + window_resize, + window_move, + window_focus, + window_unfocus, + window_hide, + window_show, + window_close, + pathdrop, + frame, + quit, +}; + +pub const KeyAction = enum(c_uint) { + NoAction, + Press, + Release, + Repeat, +}; + +pub const ScanCode = enum(c_uint) { + Unknown = 0, + Space = 32, + Apostrophe = 39, + Comma = 44, + Minus = 45, + Period = 46, + Slash = 47, + Num0 = 48, + Num1 = 49, + Num2 = 50, + Num3 = 51, + Num4 = 52, + Num5 = 53, + Num6 = 54, + Num7 = 55, + Num8 = 56, + Num9 = 57, + Semicolon = 59, + Equal = 61, + LeftBracket = 91, + Backslash = 92, + RightBracket = 93, + GraveAccent = 96, + A = 97, + B = 98, + C = 99, + D = 100, + E = 101, + F = 102, + G = 103, + H = 104, + I = 105, + J = 106, + K = 107, + L = 108, + M = 109, + N = 110, + O = 111, + P = 112, + Q = 113, + R = 114, + S = 115, + T = 116, + U = 117, + V = 118, + W = 119, + X = 120, + Y = 121, + Z = 122, + World1 = 161, + World2 = 162, + Escape = 256, + Enter = 257, + Tab = 258, + Backspace = 259, + Insert = 260, + Delete = 261, + Right = 262, + Left = 263, + Down = 264, + Up = 265, + PageUp = 266, + PageDown = 267, + Home = 268, + End = 269, + CapsLock = 280, + ScrollLock = 281, + NumLock = 282, + PrintScreen = 283, + Pause = 284, + F1 = 290, + F2 = 291, + F3 = 292, + F4 = 293, + F5 = 294, + F6 = 295, + F7 = 296, + F8 = 297, + F9 = 298, + F10 = 299, + F11 = 300, + F12 = 301, + F13 = 302, + F14 = 303, + F15 = 304, + F16 = 305, + F17 = 306, + F18 = 307, + F19 = 308, + F20 = 309, + F21 = 310, + F22 = 311, + F23 = 312, + F24 = 313, + F25 = 314, + Kp0 = 320, + Kp1 = 321, + Kp2 = 322, + Kp3 = 323, + Kp4 = 324, + Kp5 = 325, + Kp6 = 326, + Kp7 = 327, + Kp8 = 328, + Kp9 = 329, + KpDecimal = 330, + KpDivide = 331, + KpMultiply = 332, + KpSubtract = 333, + KpAdd = 334, + KpEnter = 335, + KpEqual = 336, + LeftShift = 340, + LeftControl = 341, + LeftAlt = 342, + LeftSuper = 343, + RightShift = 344, + RightControl = 345, + RightAlt = 346, + RightSuper = 347, + Menu = 348, +}; + +pub const KeyCode = enum(c_uint) { + Unknown = 0, + Space = ' ', + Apostrophe = '\'', + Comma = ',', + Minus = '-', + Period = '.', + Slash = '/', + Num0 = '0', + Num1 = '1', + Num2 = '2', + Num3 = '3', + Num4 = '4', + Num5 = '5', + Num6 = '6', + Num7 = '7', + Num8 = '8', + Num9 = '9', + Semicolon = ';', + Equal = '=', + LeftBracket = '[', + Backslash = '\\', + RightBracket = ']', + GraveAccent = '`', + A = 'a', + B = 'b', + C = 'c', + D = 'd', + E = 'e', + F = 'f', + G = 'g', + H = 'h', + I = 'i', + J = 'j', + K = 'k', + L = 'l', + M = 'm', + N = 'n', + O = 'o', + P = 'p', + Q = 'q', + R = 'r', + S = 's', + T = 't', + U = 'u', + V = 'v', + W = 'w', + X = 'x', + Y = 'y', + Z = 'z', + World1 = 161, + World2 = 162, + Escape = 256, + Enter = 257, + Tab = 258, + Backspace = 259, + Insert = 260, + Delete = 261, + Right = 262, + Left = 263, + Down = 264, + Up = 265, + PageUp = 266, + PageDown = 267, + Home = 268, + End = 269, + CapsLock = 280, + ScrollLock = 281, + NumLock = 282, + PrintScreen = 283, + Pause = 284, + F1 = 290, + F2 = 291, + F3 = 292, + F4 = 293, + F5 = 294, + F6 = 295, + F7 = 296, + F8 = 297, + F9 = 298, + F10 = 299, + F11 = 300, + F12 = 301, + F13 = 302, + F14 = 303, + F15 = 304, + F16 = 305, + F17 = 306, + F18 = 307, + F19 = 308, + F20 = 309, + F21 = 310, + F22 = 311, + F23 = 312, + F24 = 313, + F25 = 314, + Kp0 = 320, + Kp1 = 321, + Kp2 = 322, + Kp3 = 323, + Kp4 = 324, + Kp5 = 325, + Kp6 = 326, + Kp7 = 327, + Kp8 = 328, + Kp9 = 329, + KpDecimal = 330, + KpDivide = 331, + KpMultiply = 332, + KpSubtract = 333, + KpAdd = 334, + KpEnter = 335, + KpEqual = 336, + LeftShift = 340, + LeftControl = 341, + LeftAlt = 342, + LeftSuper = 343, + RightShift = 344, + RightControl = 345, + RightAlt = 346, + RightSuper = 347, + Menu = 348, +}; + +pub const key_code_count: usize = blk: { + const enum_fields = @typeInfo(KeyCode).Enum.fields; + break :blk enum_fields[enum_fields.len - 1].value + 1; +}; + +pub const KeymodFlags = packed struct(c_uint) { + none: bool, + alt: bool, + shift: bool, + ctrl: bool, + cmd: bool, + main_modified: bool, // cmd on Mac, ctrl on Win32 + _: u26, +}; + +pub const MouseButton = enum(c_uint) { + Left = 0x00, + Right = 0x01, + Middle = 0x02, + Ext1 = 0x03, + Ext2 = 0x04, +}; + +pub const mouse_button_count: usize = blk: { + const enum_fields = @typeInfo(MouseButton).Enum.fields; + break :blk enum_fields[enum_fields.len - 1].value + 1; +}; + +/// Keyboard and mouse buttons input +pub const KeyEvent = extern struct { + action: KeyAction, + scan_code: ScanCode, + key_code: KeyCode, + button: MouseButton, + mods: KeymodFlags, + click_count: u8, +}; + +/// Character input +pub const CharEvent = extern struct { + codepoint: Utf32, + sequence: [8]u8, + sequence_len: u8, +}; + +/// Mouse move/scroll +pub const MouseEvent = extern struct { + x: f32, + y: f32, + delta_x: f32, + delta_y: f32, + mods: KeymodFlags, +}; + +/// Window resize / move +pub const MoveEvent = extern struct { + frame: Rect, + content: Rect, +}; + +pub const CEvent = extern struct { + window: Window, + type: EventType, + data: extern union { + key: KeyEvent, + character: CharEvent, + mouse: MouseEvent, + move: MoveEvent, + paths: Str8List, + }, + + pub fn event(self: *CEvent) Event { + return .{ .window = self.window, .event = switch (self.type) { + .none => .none, + .keyboard_mods => .{ .keyboard_mods = self.data.key }, + .keyboard_key => .{ .keyboard_key = self.data.key }, + .keyboard_char => .{ .keyboard_char = self.data.character }, + .mouse_button => .{ .mouse_button = self.data.key }, + .mouse_move => .{ .mouse_move = self.data.mouse }, + .mouse_wheel => .{ .mouse_wheel = self.data.mouse }, + .mouse_enter => .{ .mouse_enter = self.data.mouse }, + .mouse_leave => .{ .mouse_leave = self.data.mouse }, + .clipboard_paste => .clipboard_paste, + .window_resize => .{ .window_resize = self.data.move }, + .window_move => .{ .window_move = self.data.move }, + .window_focus => .window_focus, + .window_unfocus => .window_unfocus, + .window_hide => .window_hide, + .window_show => .window_show, + .window_close => .window_close, + .pathdrop => .{ .pathdrop = self.data.paths }, + .frame => .frame, + .quit => .quit, + } }; + } +}; + +pub const Event = struct { + window: Window, + event: union(EventType) { + none, + keyboard_mods: KeyEvent, + keyboard_key: KeyEvent, + keyboard_char: CharEvent, + mouse_button: KeyEvent, + mouse_move: MouseEvent, + mouse_wheel: MouseEvent, + mouse_enter: MouseEvent, + mouse_leave: MouseEvent, + clipboard_paste, + window_resize: MoveEvent, + window_move: MoveEvent, + window_focus, + window_unfocus, + window_hide, + window_show, + window_close, + pathdrop: Str8List, + frame, + quit, + }, +}; + +//------------------------------------------------------------------------------------------ +// [APP] windows +//------------------------------------------------------------------------------------------ + +pub const Window = extern struct { + h: u64, +}; + +extern fn oc_window_set_title(title: Str8) void; +extern fn oc_window_set_size(size: Vec2) void; + +pub fn windowSetTitle(title: []const u8) void { + oc_window_set_title(Str8.fromSlice(title)); +} + +pub const windowSetSize = oc_window_set_size; + +//------------------------------------------------------------------------------------------ +// [CLOCK] +//------------------------------------------------------------------------------------------ + +pub const clock = struct { + pub const Kind = enum(c_uint) { + Monotonic, + Uptime, + Date, + }; + + extern fn oc_clock_time(clock: Kind) f64; + pub const time = oc_clock_time; +}; + +//------------------------------------------------------------------------------------------ +// [MATH] Vec2 +//------------------------------------------------------------------------------------------ + +pub const Vec2 = extern struct { + x: f32, + y: f32, + + pub fn mul(self: Vec2, scalar: f32) Vec2 { + return .{ + .x = self.x * scalar, + .y = self.y * scalar, + }; + } + + pub fn add(a: Vec2, b: Vec2) Vec2 { + return .{ + .x = a.x + b.x, + .y = a.y + b.y, + }; + } +}; + +//------------------------------------------------------------------------------------------ +// [MATH] Matrix stack +//------------------------------------------------------------------------------------------ + +pub const Mat2x3 = extern struct { + m: [6]f32, + + extern fn oc_mat2x3_mul(m: Mat2x3, p: Vec2) Vec2; + extern fn oc_mat2x3_mul_m(lhs: Mat2x3, rhs: Mat2x3) Mat2x3; + extern fn oc_mat2x3_inv(x: Mat2x3) Mat2x3; + extern fn oc_mat2x3_rotate(radians: f32) Mat2x3; + extern fn oc_mat2x3_translate(x: f32, y: f32) Mat2x3; + + extern fn oc_matrix_push(matrix: Mat2x3) void; + extern fn oc_matrix_pop() void; + extern fn oc_matrix_top() Mat2x3; + + pub const mul = oc_mat2x3_mul; + pub const mul_m = oc_mat2x3_mul_m; + pub const inv = oc_mat2x3_inv; + pub const rotate = oc_mat2x3_rotate; + pub const translate = oc_mat2x3_translate; + pub fn scale(x: f32, y: f32) Mat2x3 { + return .{ .m = .{ x, 0, 0, 0, y, 0 } }; + } + pub fn scaleUniform(s: f32) Mat2x3 { + return .{ .m = .{ s, 0, 0, 0, s, 0 } }; + } + + pub const push = oc_matrix_push; + pub const pop = oc_matrix_pop; + pub const top = oc_matrix_top; +}; + +//------------------------------------------------------------------------------------------ +// [GRAPHICS] Clip stack +//------------------------------------------------------------------------------------------ + +pub const clip = struct { + extern fn oc_clip_push(x: f32, y: f32, w: f32, h: f32) void; + extern fn oc_clip_pop() void; + extern fn oc_clip_top() Rect; + + pub const push = oc_clip_push; + pub const pop = oc_clip_pop; + pub const top = oc_clip_top; +}; + +//------------------------------------------------------------------------------------------ +// [GRAPHICS]: path primitives +//------------------------------------------------------------------------------------------ + +const PathEltType = enum(c_uint) { + Move, + Line, + Quadratic, + Cubic, +}; + +const PathElt = extern struct { + type: PathEltType, + p: [3]Vec2, +}; + +const PathDescriptor = extern struct { + start_index: u32, + count: u32, + start_point: Vec2, +}; + +const Attributes = extern struct { + width: f32, + tolerance: f32, + color: Color, + joint: JointType, + max_joint_excursion: f32, + cap: CapType, + + font: Font, + font_size: f32, + + image: Image, + src_region: Rect, + + transform: Mat2x3, + clip: Rect, +}; + +const PrimitiveCmd = enum(c_uint) { + Fill, + Stroke, + Jump, +}; + +const Primitive = extern struct { + cmd: PrimitiveCmd, + attributes: Attributes, + data: extern union { + path: PathDescriptor, + rect: Rect, + jump: u32, + }, +}; + +//------------------------------------------------------------------------------------------ +// [GRAPHICS]: resources +//------------------------------------------------------------------------------------------ + +pub const Surface = extern struct { + h: u64, + + extern fn oc_surface_nil(void) Surface; + extern fn oc_surface_is_nil(surface: Surface) bool; + extern fn oc_surface_canvas() Surface; + extern fn oc_surface_gles() Surface; + extern fn oc_surface_select(surface: Surface) void; + extern fn oc_surface_present(surface: Surface) void; + extern fn oc_surface_get_size(surface: Surface) Vec2; + extern fn oc_surface_contents_scaling(surface: Surface) Vec2; + extern fn oc_surface_bring_to_front(surface: Surface) void; + extern fn oc_surface_send_to_back(surface: Surface) void; + extern fn oc_surface_render_commands( + surface: Surface, + color: Color, + primitive_count: u32, + primitives: [*]Primitive, + elt_count: u32, + elements: [*]PathElt, + ) void; + + pub const nil = oc_surface_nil; + pub const isNil = oc_surface_is_nil; + pub const canvas = oc_surface_canvas; + pub const gles = oc_surface_gles; + pub const select = oc_surface_select; + pub const present = oc_surface_present; + pub const getSize = oc_surface_get_size; + pub const contentsScaling = oc_surface_contents_scaling; + pub const bringToFront = oc_surface_bring_to_front; + pub const sendToBack = oc_surface_send_to_back; + pub fn renderCommands(surface: Surface, color: Color, primitives: []Primitive, elements: []PathElt) void { + oc_surface_render_commands(surface, color, @intCast(primitives.len), primitives.ptr, @intCast(elements.len), elements.ptr); + } +}; + +pub const Image = extern struct { + h: u64, + + extern fn oc_image_nil() Image; + extern fn oc_image_is_nil(image: Image) bool; + extern fn oc_image_create(surface: Surface, width: u32, height: u32) Image; + extern fn oc_image_create_from_rgba8(surface: Surface, width: u32, height: u32, pixels: [*]const u8) Image; + extern fn oc_image_create_from_memory(surface: Surface, mem: Str8, flip: bool) Image; + extern fn oc_image_create_from_file(surface: Surface, file: File, flip: bool) Image; + extern fn oc_image_create_from_path(surface: Surface, path: Str8, flip: bool) Image; + extern fn oc_image_destroy(image: Image) void; + extern fn oc_image_upload_region_rgba8(image: Image, region: Rect, pixels: [*]const u8) void; + extern fn oc_image_size(image: Image) Vec2; + extern fn oc_image_draw(image: Image, rect: Rect) void; + extern fn oc_image_draw_region(image: Image, srcRegion: Rect, dstRegion: Rect) void; + + pub const nil = oc_image_nil; + pub const isNil = oc_image_is_nil; + pub const create = oc_image_create; + pub const createFromRgba8 = oc_image_create_from_rgba8; + pub const createFromMemory = oc_image_create_from_memory; + pub const createFromFile = oc_image_create_from_file; + pub const createFromPath = oc_image_create_from_path; + pub const destroy = oc_image_destroy; + pub const uploadRegionRgba8 = oc_image_upload_region_rgba8; + pub const size = oc_image_size; + pub const draw = oc_image_draw; + pub const drawRegion = oc_image_draw_region; +}; + +pub const Rect = extern struct { + x: f32, + y: f32, + w: f32, + h: f32, + + pub fn xywh(x: f32, y: f32, w: f32, h: f32) Rect { + return .{ .x = x, .y = y, .w = w, .h = h }; + } + + pub fn xyArray(rect: *Rect) *[2]f32 { + return @ptrCast(rect); + } + + pub fn whArray(rect: *Rect) *[2]f32 { + return @ptrCast(&rect.w); + } + + pub fn array(rect: *Rect) *[4]f32 { + return @ptrCast(rect); + } +}; + +pub const Color = extern struct { + r: f32 = 1.0, + g: f32 = 1.0, + b: f32 = 1.0, + a: f32 = 1.0, + + pub fn rgba(r: f32, g: f32, b: f32, a: f32) Color { + return .{ .r = r, .g = g, .b = b, .a = a }; + } + + pub fn toArray(color: *Color) *[4]f32 { + return @ptrCast(color); + } + + pub fn toRgba8(color: *const Color) u32 { + var c: u32 = 0; + c |= @as(u32, @intFromFloat(color.r * 255.0)); + c |= @as(u32, @intFromFloat(color.g * 255.0)) << 8; + c |= @as(u32, @intFromFloat(color.b * 255.0)) << 16; + c |= @as(u32, @intFromFloat(color.a * 255.0)) << 24; + return c; + } +}; + +//------------------------------------------------------------------------------------------ +// [GRAPHICS]: Canvas +//------------------------------------------------------------------------------------------ + +pub const Canvas = extern struct { + h: u64, + + extern fn oc_canvas_nil() Canvas; + extern fn oc_canvas_is_nil(canvas: Canvas) bool; + extern fn oc_canvas_create() Canvas; + extern fn oc_canvas_destroy(canvas: Canvas) void; + extern fn oc_canvas_select(canvas: Canvas) Canvas; + extern fn oc_render(canvas: Canvas) void; + + pub const nil = oc_canvas_nil; + pub const isNil = oc_canvas_is_nil; + pub const create = oc_canvas_create; + pub const destroy = oc_canvas_destroy; + pub const select = oc_canvas_select; + pub const render = oc_render; + + // vector graphics + + extern fn oc_clear() void; + extern fn oc_fill() void; + extern fn oc_stroke() void; + + pub const clear = oc_clear; + pub const fill = oc_fill; + pub const stroke = oc_stroke; + + // attributes setting/getting + + extern fn oc_set_color(color: Color) void; + extern fn oc_set_color_rgba(r: f32, g: f32, b: f32, a: f32) void; + extern fn oc_set_width(width: f32) void; + extern fn oc_set_tolerance(tolerance: f32) void; + extern fn oc_set_joint(joint: JointType) void; + extern fn oc_set_max_joint_excursion(max_joint_excursion: f32) void; + extern fn oc_set_cap(cap: CapType) void; + extern fn oc_set_font(font: Font) void; + extern fn oc_set_font_size(size: f32) void; + extern fn oc_set_text_flip(flip: bool) void; + extern fn oc_set_image(image: Image) void; + extern fn oc_set_image_source_region(region: Rect) void; + + extern fn oc_get_color() Color; + extern fn oc_get_width() f32; + extern fn oc_get_tolerance() f32; + extern fn oc_get_joint() JointType; + extern fn oc_get_max_joint_excursion() f32; + extern fn oc_get_cap() CapType; + extern fn oc_get_font() Font; + extern fn oc_get_font_size() f32; + extern fn oc_get_text_flip() bool; + extern fn oc_get_image() Image; + extern fn oc_get_image_source_region() Rect; + + pub const setColor = oc_set_color; + pub const setColorRgba = oc_set_color_rgba; + pub const setWidth = oc_set_width; + pub const setTolerance = oc_set_tolerance; + pub const setJoint = oc_set_joint; + pub const setMaxJointExcursion = oc_set_max_joint_excursion; + pub const setCap = oc_set_cap; + pub const setFont = oc_set_font; + pub const setFontSize = oc_set_font_size; + pub const setTextFlip = oc_set_text_flip; + pub const setImage = oc_set_image; + pub const setImageSourceRegion = oc_set_image_source_region; + + pub const getColor = oc_get_color; + pub const getWidth = oc_get_width; + pub const getTolerance = oc_get_tolerance; + pub const getJoint = oc_get_joint; + pub const getMaxJointExcursion = oc_get_max_joint_excursion; + pub const getCap = oc_get_cap; + pub const getFont = oc_get_font; + pub const getFontSize = oc_get_font_size; + pub const getTextFlip = oc_get_text_flip; + pub const getImage = oc_get_image; + pub const getImageSourceRegion = oc_get_image_source_region; + + // path construction + + extern fn oc_get_position() Vec2; + extern fn oc_move_to(x: f32, y: f32) void; + extern fn oc_line_to(x: f32, y: f32) void; + extern fn oc_quadratic_to(x1: f32, y1: f32, x2: f32, y2: f32) void; + extern fn oc_cubic_to(x1: f32, y1: f32, x2: f32, y2: f32, x3: f32, y3: f32) void; + extern fn oc_close_path() void; + + extern fn oc_glyph_outlines(glyph_indices: Str32) Rect; + extern fn oc_codepoints_outlines(string: Str32) void; + extern fn oc_text_outlines(string: Str8) void; + + pub const getPosition = oc_get_position; + pub const moveTo = oc_move_to; + pub const lineTo = oc_line_to; + pub const quadraticTo = oc_quadratic_to; + pub const cubicTo = oc_cubic_to; + pub const closePath = oc_close_path; + + pub const glyphOutlines = oc_glyph_outlines; + pub const codepointsOutlines = oc_codepoints_outlines; + pub const textOutlines = oc_text_outlines; + + // shape helpers + + extern fn oc_rectangle_fill(x: f32, y: f32, w: f32, h: f32) void; + extern fn oc_rectangle_stroke(x: f32, y: f32, w: f32, h: f32) void; + extern fn oc_rounded_rectangle_fill(x: f32, y: f32, w: f32, h: f32, r: f32) void; + extern fn oc_rounded_rectangle_stroke(x: f32, y: f32, w: f32, h: f32, r: f32) void; + extern fn oc_ellipse_fill(x: f32, y: f32, rx: f32, ry: f32) void; + extern fn oc_ellipse_stroke(x: f32, y: f32, rx: f32, ry: f32) void; + extern fn oc_circle_fill(x: f32, y: f32, r: f32) void; + extern fn oc_circle_stroke(x: f32, y: f32, r: f32) void; + extern fn oc_arc(x: f32, y: f32, r: f32, arc_angle: f32, start_angle: f32) void; + + pub const rectangleFill = oc_rectangle_fill; + pub const rectangleStroke = oc_rectangle_stroke; + pub const roundedRectangleFill = oc_rounded_rectangle_fill; + pub const roundedRectangleStroke = oc_rounded_rectangle_stroke; + pub const ellipseFill = oc_ellipse_fill; + pub const ellipseStroke = oc_ellipse_stroke; + pub const circleFill = oc_circle_fill; + pub const circleStroke = oc_circle_stroke; + pub const arc = oc_arc; +}; + +//------------------------------------------------------------------------------------------ +// [UI]: input state +//------------------------------------------------------------------------------------------ + +pub const KeyState = extern struct { + last_update: u64, + transition_count: u32, + repeat_count: u32, + down: bool, + sys_clicked: bool, + sys_double_clicked: bool, + sys_triple_clicked: bool, +}; + +pub const KeyboardState = extern struct { + keys: [key_code_count]KeyState, + mods: KeymodFlags, +}; + +pub const MouseButtonsState = extern struct { + left: KeyState, + right: KeyState, + middle: KeyState, + ext1: KeyState, + ext2: KeyState, + + pub fn array(self: *MouseButtonsState) [mouse_button_count]KeyState { + return .{ + self.left, + self.right, + self.middle, + self.ext1, + self.ext2, + }; + } +}; + +pub const MouseState = extern struct { + last_update: u64, + pos_valid: bool, + pos: Vec2, + delta: Vec2, + wheel: Vec2, + buttons: MouseButtonsState, +}; + +pub const TextState = extern struct { + last_update: u64, + backing: [64]Utf32, + codepoints: Str32, +}; + +pub const ClipboardState = extern struct { + last_update: u64, + pasted_text: Str8, +}; + +pub const InputState = extern struct { + frame_counter: u64, + keyboard: KeyboardState, + mouse: MouseState, + text: TextState, + clipboard: ClipboardState, + + extern fn oc_input_process_event(arena: *Arena, state: *InputState, event: *CEvent) void; + extern fn oc_input_next_frame(state: *InputState) void; + + extern fn oc_key_down(state: *InputState, key: KeyCode) bool; + extern fn oc_key_press_count(state: *InputState, key: KeyCode) u8; + extern fn oc_key_release_count(state: *InputState, key: KeyCode) u8; + extern fn oc_key_repeat_count(state: *InputState, key: KeyCode) u8; + + extern fn oc_key_down_scancode(state: *InputState, key: ScanCode) bool; + extern fn oc_key_press_count_scancode(state: *InputState, key: ScanCode) u8; + extern fn oc_key_release_count_scancode(state: *InputState, key: ScanCode) u8; + extern fn oc_key_repeat_count_scancode(state: *InputState, key: ScanCode) u8; + + extern fn oc_mouse_down(state: *InputState, button: MouseButton) bool; + extern fn oc_mouse_pressed(state: *InputState, button: MouseButton) u8; + extern fn oc_mouse_released(state: *InputState, button: MouseButton) u8; + extern fn oc_mouse_clicked(state: *InputState, button: MouseButton) bool; + extern fn oc_mouse_double_clicked(state: *InputState, button: MouseButton) bool; + extern fn oc_mouse_position(state: *InputState) Vec2; + extern fn oc_mouse_delta(state: *InputState) Vec2; + extern fn oc_mouse_wheel(state: *InputState) Vec2; + + extern fn oc_input_text_utf32(arena: *Arena, state: *InputState) Str32; + extern fn oc_input_text_utf8(arena: *Arena, state: *InputState) Str8; + + extern fn oc_clipboard_pasted(state: *InputState) bool; + extern fn oc_clipboard_pasted_text(state: *InputState) Str8; + + extern fn oc_key_mods(state: *InputState) KeymodFlags; + + pub fn processEvent(self: *InputState, arena: *Arena, event: *CEvent) void { + oc_input_process_event(arena, self, event); + } + + pub const nextFrame = oc_input_next_frame; + + pub const keyDown = oc_key_down; + pub const keyPressCount = oc_key_press_count; + pub const keyReleaseCount = oc_key_release_count; + pub const keyRepeatCount = oc_key_repeat_count; + + pub const keyDownScancode = oc_key_down_scancode; + pub const keyPressCountScancode = oc_key_press_count_scancode; + pub const keyReleaseCountScancode = oc_key_release_count_scancode; + pub const keyRepeatCountScancode = oc_key_repeat_count_scancode; + + pub const mouseDown = oc_mouse_down; + pub const mousePressed = oc_mouse_pressed; + pub const mouseReleased = oc_mouse_released; + pub const mouseClicked = oc_mouse_clicked; + pub const mouseDoubleClicked = oc_mouse_double_clicked; + pub const mousePosition = oc_mouse_position; + pub const mouseDelta = oc_mouse_delta; + pub const mouseWheel = oc_mouse_wheel; + + pub const inputTextUtf32 = oc_input_text_utf32; + pub const inputTextUtf8 = oc_input_text_utf8; + + pub const clipboardPasted = oc_clipboard_pasted; + + pub fn clipboardPastedText(self: *InputState) []u8 { + return oc_clipboard_pasted_text(self).slice(); + } + + pub const keyMods = oc_key_mods; +}; + +//------------------------------------------------------------------------------------------ +// [UI]: structs +//------------------------------------------------------------------------------------------ +pub const ui = struct { + pub const Key = extern struct { + hash: u64, + + extern fn oc_ui_key_make_str8(string: Str8) Key; + extern fn oc_ui_key_make_path(path: Str8List) Key; + + pub fn make(string: []const u8) Key { + return oc_ui_key_make_str8(Str8.fromSlice(string)); + } + + pub const makePath = oc_ui_key_make_path; + }; + + pub const Axis = enum(c_uint) { + X, + Y, + }; + + pub const LayoutMargin = struct { + x: ?f32 = null, + y: ?f32 = null, + + pub fn array(self: *LayoutMargin) *[2]?f32 { + return @ptrCast(self); + } + }; + + pub const Alignment = enum(c_uint) { + Start, + End, + Center, + }; + + pub const LayoutAlignment = struct { + x: ?Alignment = null, + y: ?Alignment = null, + + pub fn array(self: *LayoutAlignment) *[2]?Alignment { + return @ptrCast(self); + } + }; + + pub const Layout = struct { + axis: ?Axis = null, + spacing: ?f32 = null, + margin: LayoutMargin = .{}, + alignment: LayoutAlignment = .{}, + }; + + pub const SizeKind = enum(c_uint) { + Text, + Pixels, + Children, + Parent, + ParentMinusPixels, + }; + + pub const SizeCustom = struct { + kind: SizeKind = .Text, + value: f32 = 0, + relax: f32 = 0, + min_size: f32 = 0, + }; + + pub const Size = union(enum) { + text, + pixels: f32, + children, + fill_parent, + parent: f32, + parent_minus_pixels: f32, + custom: SizeCustom, + }; + + pub const BoxSize = struct { + width: ?Size = null, + height: ?Size = null, + + pub fn array(self: *BoxSize) *[2]?Size { + return @ptrCast(self); + } + }; + + pub const BoxFloating = struct { + x: ?bool = null, + y: ?bool = null, + + pub fn array(self: *BoxFloating) *[2]?bool { + return @ptrCast(self); + } + }; + + pub const FloatTarget = struct { + x: ?f32 = null, + y: ?f32 = null, + + pub fn array(self: *FloatTarget) *[2]f32 { + return @ptrCast(self); + } + }; + + //NOTE: flags for axis-dependent properties (e.g. FloatX/Y) need to be consecutive bits + // in order to play well with axis agnostic functions. Using explicit bit shifts here + // so that arithmetic on flags is possible + pub const AnimationMask = enum(c_uint) { + None = 0, + SizeWidth = 1 << 1, + SizeHeight = 1 << 2, + LayoutAxis = 1 << 3, + LayoutAlignX = 1 << 4, + LayoutAlignY = 1 << 5, + LayoutSpacing = 1 << 6, + LayoutMarginX = 1 << 7, + LayoutMarginY = 1 << 8, + FloatX = 1 << 9, + FloatY = 1 << 10, + Color = 1 << 11, + BgColor = 1 << 12, + BorderColor = 1 << 13, + BorderSize = 1 << 14, + Roundness = 1 << 15, + + FontSize = 1 << 17, + }; + + pub const Style = struct { + size: BoxSize = .{}, + layout: Layout = .{}, + floating: BoxFloating = .{}, + float_target: FloatTarget = .{}, + color: ?Color = null, + bg_color: ?Color = null, + border_color: ?Color = null, + font: ?Font = null, + font_size: ?f32 = null, + border_size: ?f32 = null, + roundness: ?f32 = null, + animation_time: ?f32 = null, + animation_mask: AnimationMask = .None, + }; + + pub const Palette = extern struct { + red0: Color, + red1: Color, + red2: Color, + red3: Color, + red4: Color, + red5: Color, + red6: Color, + red7: Color, + red8: Color, + red9: Color, + orange0: Color, + orange1: Color, + orange2: Color, + orange3: Color, + orange4: Color, + orange5: Color, + orange6: Color, + orange7: Color, + orange8: Color, + orange9: Color, + amber0: Color, + amber1: Color, + amber2: Color, + amber3: Color, + amber4: Color, + amber5: Color, + amber6: Color, + amber7: Color, + amber8: Color, + amber9: Color, + yellow0: Color, + yellow1: Color, + yellow2: Color, + yellow3: Color, + yellow4: Color, + yellow5: Color, + yellow6: Color, + yellow7: Color, + yellow8: Color, + yellow9: Color, + lime0: Color, + lime1: Color, + lime2: Color, + lime3: Color, + lime4: Color, + lime5: Color, + lime6: Color, + lime7: Color, + lime8: Color, + lime9: Color, + light_green0: Color, + light_green1: Color, + light_green2: Color, + light_green3: Color, + light_green4: Color, + light_green5: Color, + light_green6: Color, + light_green7: Color, + light_green8: Color, + light_green9: Color, + green0: Color, + green1: Color, + green2: Color, + green3: Color, + green4: Color, + green5: Color, + green6: Color, + green7: Color, + green8: Color, + green9: Color, + teal0: Color, + teal1: Color, + teal2: Color, + teal3: Color, + teal4: Color, + teal5: Color, + teal6: Color, + teal7: Color, + teal8: Color, + teal9: Color, + cyan0: Color, + cyan1: Color, + cyan2: Color, + cyan3: Color, + cyan4: Color, + cyan5: Color, + cyan6: Color, + cyan7: Color, + cyan8: Color, + cyan9: Color, + light_blue0: Color, + light_blue1: Color, + light_blue2: Color, + light_blue3: Color, + light_blue4: Color, + light_blue5: Color, + light_blue6: Color, + light_blue7: Color, + light_blue8: Color, + light_blue9: Color, + blue0: Color, + blue1: Color, + blue2: Color, + blue3: Color, + blue4: Color, + blue5: Color, + blue6: Color, + blue7: Color, + blue8: Color, + blue9: Color, + indigo0: Color, + indigo1: Color, + indigo2: Color, + indigo3: Color, + indigo4: Color, + indigo5: Color, + indigo6: Color, + indigo7: Color, + indigo8: Color, + indigo9: Color, + violet0: Color, + violet1: Color, + violet2: Color, + violet3: Color, + violet4: Color, + violet5: Color, + violet6: Color, + violet7: Color, + violet8: Color, + violet9: Color, + purple0: Color, + purple1: Color, + purple2: Color, + purple3: Color, + purple4: Color, + purple5: Color, + purple6: Color, + purple7: Color, + purple8: Color, + purple9: Color, + pink0: Color, + pink1: Color, + pink2: Color, + pink3: Color, + pink4: Color, + pink5: Color, + pink6: Color, + pink7: Color, + pink8: Color, + pink9: Color, + grey0: Color, + grey1: Color, + grey2: Color, + grey3: Color, + grey4: Color, + grey5: Color, + grey6: Color, + grey7: Color, + grey8: Color, + grey9: Color, + black: Color, + white: Color, + }; + + /// Visualized in doc/UIColors.md + pub const dark_palette = @extern(*Palette, .{ .name = "OC_UI_DARK_PALETTE" }); + + /// Visualized in doc/UIColors.md + pub const light_palette = @extern(*Palette, .{ .name = "OC_UI_LIGHT_PALETTE" }); + + pub const Theme = extern struct { + white: Color, + primary: Color, + primary_hover: Color, + primary_active: Color, + border: Color, + fill0: Color, + fill1: Color, + fill2: Color, + bg0: Color, + bg1: Color, + bg2: Color, + bg3: Color, + bg4: Color, + text0: Color, + text1: Color, + text2: Color, + text3: Color, + slider_thumb_border: Color, + elevated_border: Color, + + roundness_small: f32, + roundness_medium: f32, + roundness_large: f32, + + palette: *Palette, + }; + + pub const dark_theme = @extern(*Theme, .{ .name = "OC_UI_DARK_THEME" }); + pub const light_theme = @extern(*Theme, .{ .name = "OC_UI_LIGHT_THEME" }); + + pub const Tag = extern struct { + hash: u64, + + extern fn oc_ui_tag_make_str8(string: Str8) Tag; + + pub fn make(string: []const u8) Tag { + return oc_ui_tag_make_str8(Str8.fromSlice(string)); + } + }; + + pub const SelectorKind = enum(c_uint) { + any, + owner, + text, + tag, + status, + key, + }; + + pub const Status = packed struct(u8) { + _: u1 = 0, + + hover: bool = false, + hot: bool = false, + active: bool = false, + dragging: bool = false, + + __: u3 = 0, + + pub fn empty(self: Status) bool { + return !self.hover and !self.hot and !self.active and !self.dragging; + } + }; + + pub const SelectorOp = enum(c_uint) { + Descendant, + And, + }; + + pub const Selector = struct { + op: SelectorOp = .Descendant, + sel: union(SelectorKind) { + any, + owner, + text: []u8, + key: Key, + tag: Tag, + status: Status, + }, + }; + + pub const Pattern = extern struct { + l: List, + + extern fn oc_ui_pattern_push(arena: *Arena, pattern: *Pattern, selector: SelectorInternal) void; + extern fn oc_ui_pattern_all() Pattern; + extern fn oc_ui_pattern_owner() Pattern; + + pub fn init() Pattern { + return .{ .l = List.init() }; + } + + /// Push the selector onto frame arena and insert it into the pattern's linked list. + /// Underlying Selector implementation has a ListElt within it that is not exposed to the Zig interface + /// in order to simplify the conversion. + /// + /// WARN: You can use a pattern in multiple rules, but be aware that a pattern is referencing + /// an underlying list of selectors, hence pushing to a pattern also modifies rules in + /// which the pattern was previously used! + pub fn push(self: *Pattern, arena: *Arena, selector: Selector) void { + oc_ui_pattern_push(arena, self, convertSelector(selector)); + } + + pub const all = oc_ui_pattern_all; + pub const owner = oc_ui_pattern_owner; + }; + + pub const StyleRule = struct { + box_elt: ListElt, + build_elt: ListElt, + tmp_elt: ListElt, + + owner: *Box, + pattern: Pattern, + style: *Style, + }; + + pub const Sig = extern struct { + box: *Box, + + mouse: Vec2, + delta: Vec2, + wheel: Vec2, + + pressed: bool, + released: bool, + clicked: bool, + doubleClicked: bool, + tripleClicked: bool, + rightPressed: bool, + + dragging: bool, + hovering: bool, + + pasted: bool, + }; + + pub const BoxDrawProc = *fn (box: *Box, data: ?*anyopaque) callconv(.C) void; + + pub const OverflowAllow = packed struct { + x: bool = false, + y: bool = false, + + pub fn array(self: *OverflowAllow) *[2]bool { + return @ptrCast(self); + } + }; + + pub const Flags = packed struct(c_uint) { + clickable: bool = false, // 0 + scroll_wheel_x: bool = false, // 1 + scroll_wheel_y: bool = false, // 2 + block_mouse: bool = false, // 3 + hot_animation: bool = false, // 4 + active_animation: bool = false, // 5 + overflow_allow: OverflowAllow = .{}, // 6-7 + clip: bool = false, // 8 + draw_background: bool = false, // 9 + draw_foreground: bool = false, // 10 + draw_border: bool = false, // 11 + draw_text: bool = false, // 12 + draw_proc: bool = false, // 13 + _: u2 = 0, // 14-15 + + overlay: bool = false, // 16 + __: u15 = 0, + }; + + pub const Box = extern struct { + // hierarchy + list_elt: ListElt, + children: List, + parent: ?*Box, + + overlay_elt: ListElt, + + // keying and caching + bucket_elt: ListElt, + key: Key, + frame_counter: u64, + + // builder-provided info + flags: Flags, + string: Str8, + tags: List, + + draw_proc: BoxDrawProc, + draw_data: *anyopaque, + + // styling + before_rules: List, + after_rules: List, + + target_style: ?*Style, + style: StyleInternal, + z: u32, + + float_pos: Vec2, + children_sum: [2]f32, + spacing: [2]f32, + min_size: [2]f32, + rect: Rect, + + // signals + sig: ?*Sig, + + // stateful behavior + fresh: bool, + closed: bool, + parent_closed: bool, + dragging: bool, + hot: bool, + active: bool, + scroll: Vec2, + pressed_mouse: Vec2, + + // animation data + hot_transition: f32, + active_transition: f32, + + // status and signals + extern fn oc_ui_box_closed(box: *Box) bool; + extern fn oc_ui_box_set_closed(box: *Box, closed: bool) void; + + extern fn oc_ui_box_active(box: *Box) bool; + extern fn oc_ui_box_activate(box: *Box) void; + extern fn oc_ui_box_deactivate(box: *Box) void; + + extern fn oc_ui_box_hot(box: *Box) bool; + extern fn oc_ui_box_set_hot(box: *Box, hot: bool) void; + + extern fn oc_ui_box_sig(box: *Box) Sig; + + pub const closed = oc_ui_box_closed; + pub const setClosed = oc_ui_box_set_closed; + + pub const active = oc_ui_box_active; + pub const activate = oc_ui_box_activate; + pub const deactivate = oc_ui_box_deactivate; + + pub const hot = oc_ui_box_hot; + pub const setHot = oc_ui_box_set_hot; + + pub const sig = oc_ui_box_sig; + }; + + pub const InputText = extern struct { + count: u8, + codepoints: [64]Utf32, + }; + + const CSize = extern struct { + kind: SizeKind, + value: f32 = 0, + relax: f32 = 0, + min_size: f32 = 0, + + fn fromSize(size: Size) CSize { + return switch (size) { + .text => .{ .kind = .Text }, + .pixels => |pixels| .{ .kind = .Pixels, .value = pixels }, + .children => .{ .kind = .Children }, + .fill_parent => .{ .kind = .Parent, .value = 1 }, + .parent => |fraction| .{ .kind = .Parent, .value = fraction }, + .parent_minus_pixels => |pixels| .{ .kind = .ParentMinusPixels, .value = pixels }, + .custom => |custom| .{ + .kind = custom.kind, + .value = custom.value, + .relax = custom.relax, + .min_size = custom.min_size, + }, + }; + } + }; + + pub const StackElt = extern struct { + parent: ?*StackElt, + elt: extern union { + box: *Box, + size: CSize, + clip: Rect, + }, + }; + + pub const TagElt = extern struct { + list_elt: ListElt, + tag: Tag, + }; + + pub const EditMove = enum(c_uint) { + none, + char, + word, + line, + }; + + pub const Context = extern struct { + is_init: bool, + + input: InputState, + + frame_counter: u64, + frame_time: f64, + last_frame_duration: f64, + + frame_arena: Arena, + box_pool: Pool, + box_map: [1024]List, + + root: *Box, + overlay: *Box, + overlay_list: List, + box_stack: *StackElt, + clip_stack: *StackElt, + + next_box_before_rules: List, + next_box_after_rules: List, + next_box_tags: List, + + z: u32, + hovered: ?*Box, + + focus: ?*Box, + edit_cursor: i32, + edit_mark: i32, + edit_first_displayed_char: i32, + edit_cursor_blink_start: f64, + edit_selection_mode: EditMove, + edit_word_selection_initial_cursor: i32, + edit_word_selection_initial_mark: i32, + + theme: *Theme, + }; + + const LayoutAlignmentInternal = extern struct { + x: Alignment, + y: Alignment, + }; + + const LayoutMarginInternal = extern struct { + x: f32, + y: f32, + }; + + const LayoutInternal = extern struct { + axis: Axis, + spacing: f32, + margin: LayoutMarginInternal, + alignment: LayoutAlignmentInternal, + }; + + const BoxSizeInternal = extern struct { + width: CSize, + height: CSize, + }; + + const BoxFloatingInternal = extern struct { + x: bool, + y: bool, + }; + + const FloatTargetInternal = extern struct { + x: f32, + y: f32, + }; + + const StyleInternal = extern struct { + size: BoxSizeInternal, + layout: LayoutInternal, + floating: BoxFloatingInternal, + float_target: FloatTargetInternal, + color: Color, + bg_color: Color, + border_color: Color, + font: Font, + font_size: f32, + border_size: f32, + roundness: f32, + animation_time: f32, + animation_mask: AnimationMask, + }; + + const StyleMaskInternal = packed struct(u64) { + _: u1 = 0, + size_width: bool = false, + size_height: bool = false, + layout_axis: bool = false, + layout_align_x: bool = false, + layout_align_y: bool = false, + layout_spacing: bool = false, + layout_margin_x: bool = false, + layout_margin_y: bool = false, + float_x: bool = false, + float_y: bool = false, + color: bool = false, + bg_color: bool = false, + border_color: bool = false, + border_size: bool = false, + roundness: bool = false, + font: bool = false, + font_size: bool = false, + animation_time: bool = false, + animation_mask: bool = false, + __: u44 = 0, + }; + + //------------------------------------------------------------------------------------------ + // [UI]: context initialization and frame cycle + //------------------------------------------------------------------------------------------ + extern fn oc_ui_init(context: *Context) void; + extern fn oc_ui_get_context() *Context; + extern fn oc_ui_set_context(context: *Context) void; + + extern fn oc_ui_process_event(event: *CEvent) void; + extern fn oc_ui_begin_frame(size: Vec2, default_style: *StyleInternal, mask: StyleMaskInternal) void; + extern fn oc_ui_end_frame() void; + extern fn oc_ui_draw() void; + extern fn oc_ui_set_theme(theme: *Theme) void; + + pub const init = oc_ui_init; + pub const getContext = oc_ui_get_context; + pub const setContext = oc_ui_set_context; + + pub const processCEvent = oc_ui_process_event; + + pub fn beginFrame(size: Vec2, default_style: *Style) void { + var default_style_and_mask = convertStyle(default_style); + oc_ui_begin_frame(size, &default_style_and_mask.style, default_style_and_mask.mask); + } + + pub const endFrame = oc_ui_end_frame; + pub const draw = oc_ui_draw; + pub const setTheme = oc_ui_set_theme; + + const StyleAndMaskInternal = struct { + style: StyleInternal, + mask: StyleMaskInternal, + }; + + fn convertStyle(style: *const Style) StyleAndMaskInternal { + var style_internal: StyleInternal = std.mem.zeroes(StyleInternal); + var mask: StyleMaskInternal = .{}; + if (style.size.width) |width| { + style_internal.size.width = CSize.fromSize(width); + mask.size_width = true; + } + if (style.size.height) |height| { + style_internal.size.height = CSize.fromSize(height); + mask.size_height = true; + } + if (style.layout.axis) |axis| { + style_internal.layout.axis = axis; + mask.layout_axis = true; + } + if (style.layout.alignment.x) |x| { + style_internal.layout.alignment.x = x; + mask.layout_align_x = true; + } + if (style.layout.alignment.y) |y| { + style_internal.layout.alignment.y = y; + mask.layout_align_y = true; + } + if (style.layout.spacing) |spacing| { + style_internal.layout.spacing = spacing; + mask.layout_spacing = true; + } + if (style.layout.margin.x) |x| { + style_internal.layout.margin.x = x; + mask.layout_margin_x = true; + } + if (style.layout.margin.y) |y| { + style_internal.layout.margin.y = y; + mask.layout_margin_y = true; + } + if (style.floating.x) |x| { + style_internal.floating.x = x; + if (style.float_target.x) |target_x| { + style_internal.float_target.x = target_x; + } + mask.float_x = true; + } + if (style.floating.y) |y| { + style_internal.floating.y = y; + if (style.float_target.y) |target_y| { + style_internal.float_target.y = target_y; + } + mask.float_y = true; + } + if (style.color) |color| { + style_internal.color = color; + mask.color = true; + } + if (style.bg_color) |bg_color| { + style_internal.bg_color = bg_color; + mask.bg_color = true; + } + if (style.border_color) |border_color| { + style_internal.border_color = border_color; + mask.border_color = true; + } + if (style.border_size) |border_size| { + style_internal.border_size = border_size; + mask.border_size = true; + } + if (style.roundness) |roundness| { + style_internal.roundness = roundness; + mask.roundness = true; + } + if (style.font) |font| { + style_internal.font = font; + mask.font = true; + } + if (style.font_size) |font_size| { + style_internal.font_size = font_size; + mask.font_size = true; + } + if (style.animation_time) |animation_time| { + style_internal.animation_time = animation_time; + mask.animation_time = true; + } + if (style.animation_mask != .None) { + style_internal.animation_mask = @enumFromInt( + @intFromEnum(style_internal.animation_mask) | @intFromEnum(style.animation_mask), + ); + mask.animation_mask = true; + } + + return .{ .style = style_internal, .mask = mask }; + } + + //------------------------------------------------------------------------------------------ + // [UI]: box hierarchy building + //------------------------------------------------------------------------------------------ + extern fn oc_ui_box_make_str8(string: Str8, flags: Flags) *Box; + extern fn oc_ui_box_begin_str8(string: Str8, flags: Flags) *Box; + extern fn oc_ui_box_end() *Box; + + extern fn oc_ui_box_push(box: *Box) void; + extern fn oc_ui_box_pop() void; + extern fn oc_ui_box_top() ?*Box; + + extern fn oc_ui_box_lookup_key(key: Key) ?*Box; + extern fn oc_ui_box_lookup_str8(string: Str8) ?*Box; + + extern fn oc_ui_box_set_draw_proc(box: Box, proc: BoxDrawProc, data: ?*anyopaque) void; + + pub fn boxMake(string: []const u8, flags: Flags) *Box { + return oc_ui_box_make_str8(Str8.fromSlice(string), flags); + } + + pub fn boxBegin(string: []const u8, flags: Flags) *Box { + return oc_ui_box_begin_str8(Str8.fromSlice(string), flags); + } + + pub const boxEnd = oc_ui_box_end; + + pub const boxPush = oc_ui_box_push; + pub const boxPop = oc_ui_box_pop; + pub const boxTop = oc_ui_box_top; + + pub const boxLookupKey = oc_ui_box_lookup_key; + + pub fn boxLookupStr(string: []const u8) ?*Box { + return oc_ui_box_lookup_str8(Str8.fromSlice(string)); + } + + pub const boxSetDrawProc = oc_ui_box_set_draw_proc; + + //------------------------------------------------------------------------------------------ + // [UI]: tagging + //------------------------------------------------------------------------------------------ + + extern fn oc_ui_tag_box_str8(box: *Box, string: Str8) void; + extern fn oc_ui_tag_next_str8(string: Str8) void; + + pub fn tagBox(box: *Box, string: []const u8) void { + oc_ui_tag_box_str8(box, Str8.fromSlice(string)); + } + + pub fn tagNext(string: []const u8) void { + oc_ui_tag_next_str8(Str8.fromSlice(string)); + } + + //------------------------------------------------------------------------------------------ + // [UI]: styling + //------------------------------------------------------------------------------------------ + + const SelectorDataInternal = extern union { + text: Str8, + key: Key, + tag: Tag, + status: Status, + }; + + const SelectorInternal = extern struct { + list_elt: ListElt, + kind: SelectorKind, + op: SelectorOp, + data: SelectorDataInternal, + }; + + extern fn oc_ui_style_next(style: *StyleInternal, mask: StyleMaskInternal) void; + extern fn oc_ui_style_match_before(pattern: Pattern, style: *StyleInternal, mask: StyleMaskInternal) void; + extern fn oc_ui_style_match_after(patterh: Pattern, style: *StyleInternal, mask: StyleMaskInternal) void; + + pub fn styleNext(style: Style) void { + var style_and_mask = convertStyle(&style); + oc_ui_style_next(&style_and_mask.style, style_and_mask.mask); + } + + pub fn styleMatchBefore(pattern: Pattern, style: Style) void { + var style_and_mask = convertStyle(&style); + oc_ui_style_match_before(pattern, &style_and_mask.style, style_and_mask.mask); + } + + pub fn styleMatchAfter(pattern: Pattern, style: Style) void { + var style_and_mask = convertStyle(&style); + oc_ui_style_match_after(pattern, &style_and_mask.style, style_and_mask.mask); + } + + pub fn applyStyle(dst: *Style, src: *Style) void { + if (src.size.width) |width| { + dst.size.width = width; + } + if (src.size.height) |height| { + dst.size.height = height; + } + if (src.layout.axis) |axis| { + dst.layout.axis = axis; + } + if (src.layout.alignment.x) |x| { + dst.layout.alignment.x = x; + } + if (src.layout.alignment.y) |y| { + dst.layout.alignment.y = y; + } + if (src.layout.spacing) |spacing| { + dst.layout.spacing = spacing; + } + if (src.layout.margin.x) |x| { + dst.layout.margin.x = x; + } + if (src.layout.margin.y) |y| { + dst.layout.margin.y = y; + } + if (src.floating.x) |x| { + dst.floating.x = x; + } + if (src.float_target.x) |x| { + dst.float_target.x = x; + } + if (src.floating.y) |y| { + dst.floating.y = y; + } + if (src.float_target.y) |y| { + dst.float_target.y = y; + } + if (src.color) |color| { + dst.color = color; + } + if (src.bg_color) |bg_color| { + dst.bg_color = bg_color; + } + if (src.border_color) |border_color| { + dst.border_color = border_color; + } + if (src.border_size) |border_size| { + dst.border_size = border_size; + } + if (src.roudness) |roudness| { + dst.roudness = roudness; + } + if (src.font) |font| { + dst.font = font; + } + if (src.font_size) |font_size| { + dst.font_size = font_size; + } + if (src.animation_time) |animation_time| { + dst.animation_time = animation_time; + } + if (src.animation_mask) |animation_mask| { + dst.animation_mask = animation_mask; + } + } + + fn convertSelector(selector: Selector) SelectorInternal { + var data: SelectorDataInternal = switch (selector.sel) { + .any, .owner => std.mem.zeroes(SelectorDataInternal), + .text => |text| .{ .text = Str8.fromSlice(text) }, + .key => |key| .{ .key = key }, + .tag => |tag| .{ .tag = tag }, + .status => |status| .{ .status = status }, + }; + + return SelectorInternal{ + .list_elt = .{ .prev = null, .next = null }, + .kind = selector.sel, + .op = selector.op, + .data = data, + }; + } + + //------------------------------------------------------------------------------------------ + // [UI]: basic widget helpers + //------------------------------------------------------------------------------------------ + + extern fn oc_ui_label_str8(label: Str8) Sig; + extern fn oc_ui_button_str8(label: Str8) Sig; + extern fn oc_ui_checkbox_str8(label: Str8, checked: *bool) Sig; + extern fn oc_ui_slider_str8(label: Str8, value: *f32) *Box; + extern fn oc_ui_scrollbar_str8(label: Str8, thumbRatio: f32, scrollValue: *f32) *Box; + extern fn oc_ui_tooltip_str8(label: Str8) void; + + extern fn oc_ui_panel_begin_str8(name: Str8, flags: Flags) void; + extern fn oc_ui_panel_end() void; + + extern fn oc_ui_menu_bar_begin_str8(label: Str8) void; + extern fn oc_ui_menu_bar_end() void; + extern fn oc_ui_menu_begin_str8(label: Str8) void; + extern fn oc_ui_menu_end() void; + extern fn oc_ui_menu_button_str8(name: Str8) Sig; + + const TextBoxResultInternal = extern struct { + changed: bool, + accepted: bool, + text: Str8, + }; + extern fn oc_ui_text_box_str8(name: Str8, arena: *Arena, text: Str8) TextBoxResultInternal; + + const SelectPopupInfoInternal = extern struct { + changed: bool, + selected_index: c_int, + option_count: c_int, + options: [*]Str8, + placeholder: Str8, + }; + extern fn oc_ui_select_popup_str8(name: Str8, info: *SelectPopupInfoInternal) SelectPopupInfoInternal; + + const RadioGroupInfoInternal = extern struct { + changed: bool, + selected_index: c_int, + option_count: c_int, + options: [*]Str8, + }; + extern fn oc_ui_radio_group_str8(name: Str8, info: RadioGroupInfoInternal) RadioGroupInfoInternal; + + pub fn makeLabel(label: []const u8) Sig { + return oc_ui_label_str8(Str8.fromSlice(label)); + } + + pub fn button(label: []const u8) Sig { + return oc_ui_button_str8(Str8.fromSlice(label)); + } + + pub fn checkbox(name: []const u8, checked: *bool) Sig { + return oc_ui_checkbox_str8(Str8.fromSlice(name), checked); + } + + pub fn slider(name: []const u8, value: *f32) *Box { + return oc_ui_slider_str8(Str8.fromSlice(name), value); + } + + pub fn scrollbar(name: []const u8, thumbRatio: f32, scrollValue: *f32) *Box { + return oc_ui_scrollbar_str8(Str8.fromSlice(name), thumbRatio, scrollValue); + } + + pub fn tooltip(label: []const u8) void { + oc_ui_tooltip_str8(Str8.fromSlice(label)); + } + + pub fn panelBegin(name: []const u8, flags: Flags) void { + oc_ui_panel_begin_str8(Str8.fromSlice(name), flags); + } + + pub fn panelEnd() void { + oc_ui_panel_end(); + } + + pub fn menuBarBegin(label: []const u8) void { + oc_ui_menu_bar_begin_str8(Str8.fromSlice(label)); + } + + pub fn menuBarEnd() void { + oc_ui_menu_bar_end(); + } + + pub fn menuBegin(label: []const u8) void { + oc_ui_menu_begin_str8(Str8.fromSlice(label)); + } + + pub fn menuEnd() void { + oc_ui_menu_end(); + } + + pub fn menuButton(label: []const u8) Sig { + return oc_ui_menu_button_str8(Str8.fromSlice(label)); + } + + pub const TextBoxResult = struct { + changed: bool, + accepted: bool, + text: []u8, + }; + + pub fn textBox(name: []const u8, arena: *Arena, text: []const u8) TextBoxResult { + var result_internal = oc_ui_text_box_str8(Str8.fromSlice(name), arena, Str8.fromSlice(text)); + return .{ + .changed = result_internal.changed, + .accepted = result_internal.accepted, + .text = result_internal.text.slice(), + }; + } + + pub const SelectPopupInfo = struct { + changed: bool = false, + selected_index: ?usize, + options: [][]const u8, + placeholder: []const u8 = "", + }; + + pub fn selectPopup(name: []const u8, info: *SelectPopupInfo) SelectPopupInfo { + var info_internal = SelectPopupInfoInternal{ + .changed = info.changed, + .selected_index = if (info.selected_index) |selected_index| @intCast(selected_index) else -1, + .option_count = @intCast(info.options.len), + .options = @ptrCast(info.options.ptr), + .placeholder = Str8.fromSlice(info.placeholder), + }; + var result_internal = oc_ui_select_popup_str8(Str8.fromSlice(name), &info_internal); + return .{ + .changed = result_internal.changed, + .selected_index = if (result_internal.selected_index >= 0) @intCast(result_internal.selected_index) else null, + .options = @ptrCast(result_internal.options[0..@intCast(result_internal.option_count)]), + .placeholder = result_internal.placeholder.slice(), + }; + } + + pub const RadioGroupInfo = struct { + changed: bool = false, + selected_index: ?usize, + options: [][]const u8, + }; + + pub fn radioGroup(name: []const u8, info: *RadioGroupInfo) RadioGroupInfo { + var info_internal = RadioGroupInfoInternal{ + .changed = info.changed, + .selected_index = if (info.selected_index) |selected_index| @intCast(selected_index) else -1, + .option_count = @intCast(info.options.len), + .options = @ptrCast(info.options.ptr), + }; + var result_internal = oc_ui_radio_group_str8(Str8.fromSlice(name), info_internal); + return .{ + .changed = result_internal.changed, + .selected_index = if (result_internal.selected_index >= 0) @intCast(result_internal.selected_index) else null, + .options = @ptrCast(result_internal.options[0..@intCast(result_internal.option_count)]), + }; + } +}; + +//------------------------------------------------------------------------------------------ +// [GRAPHICS]: GLES +//------------------------------------------------------------------------------------------ + +// TODO + +//------------------------------------------------------------------------------------------ +// [FILE IO] basic API +//------------------------------------------------------------------------------------------ + +pub const File = extern struct { + const OpenFlags = packed struct(u16) { + _: u1 = 0, + + append: bool = false, + truncate: bool = false, + create: bool = false, + + symlink: bool = false, + no_follow: bool = false, + restrict: bool = false, + + __: u9 = 0, + }; + + const AccessFlags = packed struct(u16) { + _: u1 = 0, + + read: bool = false, + write: bool = false, + + __: u13 = 0, + }; + + const Whence = enum(c_uint) { + Set, + End, + Current, + }; + + const Type = enum(c_uint) { + Unknown, + Regular, + Directory, + Symlink, + Block, + Character, + Fifo, + Socket, + }; + + const Perm = packed struct(u16) { + other_exec: bool, + other_write: bool, + other_read: bool, + group_exec: bool, + group_write: bool, + group_read: bool, + owner_exec: bool, + owner_write: bool, + owner_read: bool, + sticky_bit: bool, + set_gid: bool, + set_uid: bool, + }; + + const DateStamp = extern struct { + seconds: i64, // seconds relative to NTP epoch. + fraction: u64, // fraction of seconds elapsed since the time specified by seconds. + }; + + const Status = extern struct { + uid: u64, + type: Type, + perm: Perm, + size: u64, + + creation_date: DateStamp, + access_date: DateStamp, + modification_date: DateStamp, + }; + + const DialogKind = enum(c_uint) { + Save, + Open, + }; + + const DialogFlags = packed struct(u32) { + files: bool, + directories: bool, + multiple: bool, + create_directories: bool, + }; + + const DialogDesc = extern struct { + kind: DialogKind, + flags: DialogFlags, + title: Str8, + ok_label: Str8, + start_at: File, + start_path: Str8, + filters: Str8List, + }; + + const DialogButton = enum(c_uint) { + Cancel, + Ok, + }; + + const OpenWithDialogElt = extern struct { + list_elt: ListElt, + file: File, + }; + + const OpenWithDialogResult = extern struct { + button: DialogButton, + file: File, + selection: List, + }; + + h: u64, + + extern fn oc_file_nil() File; + extern fn oc_file_is_nil(file: File) bool; + extern fn oc_file_open(path: Str8, rights: AccessFlags, flags: OpenFlags) File; + extern fn oc_file_open_at(dir: File, path: Str8, rights: AccessFlags, flags: OpenFlags) File; + extern fn oc_file_close(file: File) void; + extern fn oc_file_last_error(file: File) io.Error; + extern fn oc_file_pos(file: File) i64; + extern fn oc_file_seek(file: File, offset: i64, whence: Whence) i64; + extern fn oc_file_write(file: File, size: u64, buffer: [*]u8) u64; + extern fn oc_file_read(file: File, size: u64, buffer: [*]u8) u64; + + extern fn oc_file_get_status(file: File) Status; + extern fn oc_file_size(file: File) u64; + + extern fn oc_file_open_with_request(path: Str8, rights: AccessFlags, flags: OpenFlags) File; + extern fn oc_file_open_with_dialog(arena: *Arena, rights: AccessFlags, flags: OpenFlags, desc: *DialogDesc) OpenWithDialogResult; + + pub const nil = oc_file_nil; + pub const isNil = oc_file_is_nil; + pub const open = oc_file_open; + pub const openAt = oc_file_open_at; + pub const close = oc_file_close; + pub const lastError = oc_file_last_error; + pub const pos = oc_file_pos; + pub const seek = oc_file_seek; + + pub fn write(self: File, size: u64, buffer: []u8) u64 { + assert(size <= buffer.len, "Trying to write more than the buffer length: {d}/{d}", .{ size, buffer.len }, @src()); + return @intCast(oc_file_write(self, size, buffer.ptr)); + } + + pub fn read(self: File, size: u64, buffer: []u8) u64 { + assert(size <= buffer.len, "Trying to read more than the buffer length: {d}/{d}", .{ size, buffer.len }, @src()); + return @intCast(oc_file_read(self, size, buffer.ptr)); + } + + pub const getStatus = oc_file_get_status; + + pub fn getSize(self: File) usize { + return @intCast(oc_file_size(self)); + } + + pub const openWithRequest = oc_file_open_with_request; + pub const openWithDialog = oc_file_open_with_dialog; +}; + +//------------------------------------------------------------------------------------------ +// [FILE IO] low-level io queue api +//------------------------------------------------------------------------------------------ + +pub const io = struct { + pub const ReqId = u16; + pub const Op = u32; + + pub const OpEnum = enum(c_uint) { + OpenAt = 0, + Close, + FStat, + Seek, + Read, + Write, + Error, + }; + + pub const Req = extern struct { + id: ReqId, + op: Op, + handle: File, + + offset: i64, + size: u64, + + buffer: extern union { + data: ?[*]u8, + unused: u64, // This is a horrible hack to get the same layout on wasm and on host + }, + + type: extern union { + open: extern struct { + rights: File.AccessFlags, + flags: File.OpenFlags, + }, + whence: File.Whence, + }, + }; + + pub const Error = enum(i32) { + Ok = 0, + Unknown, + Op, // unsupported operation + Handle, // invalid handle + Prev, // previously had a fatal error (last error stored on handle) + Arg, // invalid argument or argument combination + Perm, // access denied + Space, // no space left + NoEntry, // file or directory does not exist + Exists, // file already exists + NotDir, // path element is not a directory + Dir, // attempted to write directory + MaxFiles, // max open files reached + MaxLinks, // too many symbolic links in path + PathLength, // path too long + FileSize, // file too big + Overflow, // offset too big + NotReady, // no data ready to be read/written + Mem, // failed to allocate memory + Interrupt, // operation interrupted by a signal + Physical, // physical IO error + NoDevice, // device not found + Walkout, // attempted to walk out of root directory + }; + + pub const Cmp = extern struct { + id: ReqId, + err: Error, + data: extern union { + result: i64, + size: u64, + offset: i64, + handle: File, + }, + }; + + extern fn oc_io_wait_single_req(req: *Req) Cmp; + + pub const waitSingleReq = oc_io_wait_single_req; +}; diff --git a/src/platform/osx_platform.c b/src/platform/osx_platform.c index c9ab756..56ee617 100644 --- a/src/platform/osx_platform.c +++ b/src/platform/osx_platform.c @@ -8,14 +8,13 @@ #include "platform.h" #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif - oc_host_platform oc_get_host_platform() - { - return OC_HOST_PLATFORM_MACOS; - } +oc_host_platform oc_get_host_platform(void) +{ + return OC_HOST_PLATFORM_MACOS; +} #ifdef __cplusplus } // extern "C" diff --git a/src/platform/platform.h b/src/platform/platform.h index 3ec268c..b4e70e7 100644 --- a/src/platform/platform.h +++ b/src/platform/platform.h @@ -121,7 +121,7 @@ typedef enum OC_HOST_PLATFORM_WINDOWS, } oc_host_platform; -ORCA_API oc_host_platform oc_get_host_platform(); +ORCA_API oc_host_platform oc_get_host_platform(void); #ifdef __cplusplus } // extern "C" diff --git a/src/platform/win32_platform.c b/src/platform/win32_platform.c index 1d82f90..78c976a 100644 --- a/src/platform/win32_platform.c +++ b/src/platform/win32_platform.c @@ -8,14 +8,13 @@ #include "platform.h" #ifdef __cplusplus -extern "C" -{ +extern "C" { #endif - oc_host_platform oc_get_host_platform() - { - return OC_HOST_PLATFORM_WINDOWS; - } +oc_host_platform oc_get_host_platform(void) +{ + return OC_HOST_PLATFORM_WINDOWS; +} #ifdef __cplusplus } // extern "C" diff --git a/src/ui/ui.c b/src/ui/ui.c index c526f40..d577820 100644 --- a/src/ui/ui.c +++ b/src/ui/ui.c @@ -1778,7 +1778,7 @@ oc_ui_sig oc_ui_button_behavior(oc_ui_box* box) { oc_ui_box_set_hot(box, false); } - if(!sig.hovering && !sig.dragging) + if(!sig.hovering || !sig.dragging) { oc_ui_box_deactivate(box); } @@ -1873,7 +1873,7 @@ void oc_ui_checkbox_draw(oc_ui_box* box, void* data) oc_matrix_pop(); } -oc_ui_sig oc_ui_checkbox(const char* name, bool* checked) +oc_ui_sig oc_ui_checkbox_str8(oc_str8 name, bool* checked) { oc_ui_context* ui = oc_ui_get_context(); oc_ui_theme* theme = ui->theme; @@ -1918,7 +1918,7 @@ oc_ui_sig oc_ui_checkbox(const char* name, bool* checked) | OC_UI_FLAG_HOT_ANIMATION | OC_UI_FLAG_ACTIVE_ANIMATION; - box = oc_ui_box_make(name, flags); + box = oc_ui_box_make_str8(name, flags); oc_ui_tag_box(box, "checkbox"); oc_ui_box_set_draw_proc(box, oc_ui_checkbox_draw, 0); @@ -1968,7 +1968,7 @@ oc_ui_sig oc_ui_checkbox(const char* name, bool* checked) | OC_UI_FLAG_HOT_ANIMATION | OC_UI_FLAG_ACTIVE_ANIMATION; - box = oc_ui_box_make(name, flags); + box = oc_ui_box_make_str8(name, flags); oc_ui_tag_box(box, "checkbox"); } @@ -1981,16 +1981,21 @@ oc_ui_sig oc_ui_checkbox(const char* name, bool* checked) return (sig); } +oc_ui_sig oc_ui_checkbox(const char* name, bool* checked) +{ + return oc_ui_checkbox_str8(OC_STR8(name), checked); +} + //------------------------------------------------------------------------------ // slider / scrollbar //------------------------------------------------------------------------------ -oc_ui_box* oc_ui_slider(const char* label, f32* value) +oc_ui_box* oc_ui_slider_str8(oc_str8 name, f32* value) { oc_ui_context* ui = oc_ui_get_context(); oc_ui_theme* theme = ui->theme; oc_ui_style_match_before(oc_ui_pattern_all(), &(oc_ui_style){ 0 }, OC_UI_STYLE_LAYOUT); - oc_ui_box* frame = oc_ui_box_begin(label, 0); + oc_ui_box* frame = oc_ui_box_begin_str8(name, 0); { oc_ui_axis trackAxis = (frame->rect.w > frame->rect.h) ? OC_UI_AXIS_X : OC_UI_AXIS_Y; oc_ui_axis secondAxis = (trackAxis == OC_UI_AXIS_Y) ? OC_UI_AXIS_X : OC_UI_AXIS_Y; @@ -2161,12 +2166,17 @@ oc_ui_box* oc_ui_slider(const char* label, f32* value) return (frame); } -oc_ui_box* oc_ui_scrollbar(const char* label, f32 thumbRatio, f32* scrollValue) +oc_ui_box* oc_ui_slider(const char* name, f32* value) +{ + return oc_ui_slider_str8(OC_STR8(name), value); +} + +oc_ui_box* oc_ui_scrollbar_str8(oc_str8 name, f32 thumbRatio, f32* scrollValue) { oc_ui_context* ui = oc_ui_get_context(); oc_ui_theme* theme = ui->theme; oc_ui_style_match_before(oc_ui_pattern_all(), &(oc_ui_style){ 0 }, OC_UI_STYLE_LAYOUT); - oc_ui_box* frame = oc_ui_box_begin(label, 0); + oc_ui_box* frame = oc_ui_box_begin_str8(name, 0); { f32 minThumbRatio = 17. / oc_max(frame->rect.w, frame->rect.h); thumbRatio = oc_min(oc_max(thumbRatio, minThumbRatio), 1.); @@ -2292,10 +2302,15 @@ oc_ui_box* oc_ui_scrollbar(const char* label, f32 thumbRatio, f32* scrollValue) return (frame); } +oc_ui_box* oc_ui_scrollbar(const char* name, f32 thumbRatio, f32* scrollValue) +{ + return oc_ui_scrollbar_str8(OC_STR8(name), thumbRatio, scrollValue); +} + //------------------------------------------------------------------------------ // panels //------------------------------------------------------------------------------ -void oc_ui_panel_begin(const char* str, oc_ui_flags flags) +void oc_ui_panel_begin_str8(oc_str8 str, oc_ui_flags flags) { flags = flags | OC_UI_FLAG_CLIP @@ -2311,7 +2326,7 @@ void oc_ui_panel_begin(const char* str, oc_ui_flags flags) .layout.margin.y = 0 }, OC_UI_STYLE_SIZE | OC_UI_STYLE_LAYOUT_MARGINS); - oc_ui_box_begin(str, flags); + oc_ui_box_begin_str8(str, flags); oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_PARENT, 1 }, .size.height = { OC_UI_SIZE_PARENT, 1 } }, @@ -2320,6 +2335,11 @@ void oc_ui_panel_begin(const char* str, oc_ui_flags flags) oc_ui_box_begin("contents", 0); } +void oc_ui_panel_begin(const char* str, oc_ui_flags flags) +{ + oc_ui_panel_begin_str8(OC_STR8(str), flags); +} + void oc_ui_panel_end(void) { oc_ui_box_end(); // contents @@ -2413,7 +2433,7 @@ void oc_ui_tooltip_arrow_draw(oc_ui_box* box, void* data) oc_matrix_pop(); } -void oc_ui_tooltip(const char* label) +void oc_ui_tooltip_str8(oc_str8 label) { oc_ui_context* ui = oc_ui_get_context(); oc_ui_theme* theme = ui->theme; @@ -2424,7 +2444,7 @@ void oc_ui_tooltip(const char* label) .floatTarget.x = p.x, .floatTarget.y = p.y }; oc_ui_style_next(&containerStyle, OC_UI_STYLE_FLOAT); - oc_ui_container(label, OC_UI_FLAG_OVERLAY) + oc_ui_container_str8(label, OC_UI_FLAG_OVERLAY) { oc_ui_style arrowStyle = { .size.width = { OC_UI_SIZE_PIXELS, 24 }, .size.height = { OC_UI_SIZE_PIXELS, 24 }, @@ -2460,17 +2480,22 @@ void oc_ui_tooltip(const char* label) oc_ui_box* contents = oc_ui_box_begin("contents", OC_UI_FLAG_DRAW_BACKGROUND); - oc_ui_label(label); + oc_ui_label_str8(label); oc_ui_box_end(); } } +void oc_ui_tooltip(const char* label) +{ + oc_ui_tooltip_str8(OC_STR8(label)); +} + //------------------------------------------------------------------------------ // Menus //------------------------------------------------------------------------------ -void oc_ui_menu_bar_begin(const char* name) +void oc_ui_menu_bar_begin_str8(oc_str8 name) { oc_ui_style style = { .size.width = { OC_UI_SIZE_PARENT, 1, 0 }, @@ -2481,7 +2506,7 @@ void oc_ui_menu_bar_begin(const char* name) | OC_UI_STYLE_LAYOUT_AXIS; oc_ui_style_next(&style, mask); - oc_ui_box* bar = oc_ui_box_begin(name, OC_UI_FLAG_DRAW_BACKGROUND); + oc_ui_box* bar = oc_ui_box_begin_str8(name, OC_UI_FLAG_DRAW_BACKGROUND); oc_ui_sig sig = oc_ui_box_sig(bar); oc_ui_context* ui = oc_ui_get_context(); @@ -2491,16 +2516,21 @@ void oc_ui_menu_bar_begin(const char* name) } } +void oc_ui_menu_bar_begin(const char* name) +{ + oc_ui_menu_bar_begin_str8(OC_STR8(name)); +} + void oc_ui_menu_bar_end(void) { oc_ui_box_end(); // menu bar } -void oc_ui_menu_begin(const char* label) +void oc_ui_menu_begin_str8(oc_str8 label) { oc_ui_context* ui = oc_ui_get_context(); oc_ui_theme* theme = ui->theme; - oc_ui_box* container = oc_ui_box_make(label, 0); + oc_ui_box* container = oc_ui_box_make_str8(label, 0); oc_ui_box_push(container); oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_CHILDREN }, @@ -2525,7 +2555,7 @@ void oc_ui_menu_begin(const char* label) oc_ui_style_next(&(oc_ui_style){ .size.width = { OC_UI_SIZE_TEXT }, .size.height = { OC_UI_SIZE_TEXT } }, OC_UI_STYLE_SIZE); - oc_ui_box* buttonLabel = oc_ui_box_make(label, OC_UI_FLAG_DRAW_TEXT); + oc_ui_box* buttonLabel = oc_ui_box_make_str8(label, OC_UI_FLAG_DRAW_TEXT); oc_ui_box_end(); // button @@ -2592,13 +2622,18 @@ void oc_ui_menu_begin(const char* label) oc_ui_box_push(menu); } +void oc_ui_menu_begin(const char* label) +{ + oc_ui_menu_begin_str8(OC_STR8(label)); +} + void oc_ui_menu_end(void) { oc_ui_box_pop(); // menu oc_ui_box_pop(); // container } -oc_ui_sig oc_ui_menu_button(const char* name) +oc_ui_sig oc_ui_menu_button_str8(oc_str8 label) { oc_ui_context* ui = oc_ui_get_context(); oc_ui_theme* theme = ui->theme; @@ -2627,11 +2662,16 @@ oc_ui_sig oc_ui_menu_button(const char* name) | OC_UI_FLAG_DRAW_TEXT | OC_UI_FLAG_DRAW_BACKGROUND; - oc_ui_box* box = oc_ui_box_make(name, flags); + oc_ui_box* box = oc_ui_box_make_str8(label, flags); oc_ui_sig sig = oc_ui_box_sig(box); return (sig); } +oc_ui_sig oc_ui_menu_button(const char* label) +{ + return oc_ui_menu_button_str8(OC_STR8(label)); +} + //------------------------------------------------------------------------------ // Select //------------------------------------------------------------------------------ @@ -2689,14 +2729,14 @@ void oc_ui_select_popup_draw_checkmark(oc_ui_box* box, void* data) oc_matrix_pop(); } -oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_info* info) +oc_ui_select_popup_info oc_ui_select_popup_str8(oc_str8 name, oc_ui_select_popup_info* info) { oc_ui_select_popup_info result = *info; oc_ui_context* ui = oc_ui_get_context(); oc_ui_theme* theme = ui->theme; - oc_ui_container(name, 0) + oc_ui_container_str8(name, 0) { oc_ui_pattern hoverPattern = { 0 }; oc_ui_pattern_push(&ui->frameArena, &hoverPattern, (oc_ui_selector){ .kind = OC_UI_SEL_STATUS, .status = OC_UI_HOVER }); @@ -2908,6 +2948,11 @@ oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_ return (result); } +oc_ui_select_popup_info oc_ui_select_popup(const char* name, oc_ui_select_popup_info* info) +{ + return oc_ui_select_popup_str8(OC_STR8(name), info); +} + //------------------------------------------------------------------------------ // Radio group //------------------------------------------------------------------------------ @@ -2926,7 +2971,7 @@ void oc_ui_radio_indicator_draw(oc_ui_box* box, void* data) oc_matrix_pop(); } -oc_ui_radio_group_info oc_ui_radio_group(const char* name, oc_ui_radio_group_info* info) +oc_ui_radio_group_info oc_ui_radio_group_str8(oc_str8 name, oc_ui_radio_group_info* info) { oc_ui_radio_group_info result = *info; @@ -2937,7 +2982,7 @@ oc_ui_radio_group_info oc_ui_radio_group(const char* name, oc_ui_radio_group_inf .layout.spacing = 12 }, OC_UI_STYLE_LAYOUT_AXIS | OC_UI_STYLE_LAYOUT_SPACING); - oc_ui_container(name, 0) + oc_ui_container_str8(name, 0) { for(int i = 0; i < info->optionCount; i++) { @@ -3036,6 +3081,11 @@ oc_ui_radio_group_info oc_ui_radio_group(const char* name, oc_ui_radio_group_inf return (result); } +oc_ui_radio_group_info oc_ui_radio_group(const char* name, oc_ui_radio_group_info* info) +{ + return oc_ui_radio_group_str8(OC_STR8(name), info); +} + //------------------------------------------------------------------------------ // text box //------------------------------------------------------------------------------ @@ -3817,7 +3867,7 @@ void oc_ui_text_box_render(oc_ui_box* box, void* data) } } -oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8 text) +oc_ui_text_box_result oc_ui_text_box_str8(oc_str8 name, oc_arena* arena, oc_str8 text) { oc_ui_context* ui = oc_ui_get_context(); oc_ui_theme* theme = ui->theme; @@ -3853,7 +3903,7 @@ oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8 oc_ui_flags frameFlags = OC_UI_FLAG_CLICKABLE | OC_UI_FLAG_DRAW_BACKGROUND | OC_UI_FLAG_DRAW_BORDER; - oc_ui_box* frame = oc_ui_box_begin(name, frameFlags); + oc_ui_box* frame = oc_ui_box_begin_str8(name, frameFlags); oc_ui_tag_box(frame, "frame"); oc_font font = frame->style.font; f32 fontSize = frame->style.fontSize; @@ -4136,6 +4186,11 @@ oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8 return (result); } +oc_ui_text_box_result oc_ui_text_box(const char* name, oc_arena* arena, oc_str8 text) +{ + return oc_ui_text_box_str8(OC_STR8(name), arena, text); +} + //------------------------------------------------------------------------------ // Themes // doc/UIColors.md has them visualized diff --git a/src/ui/ui.h b/src/ui/ui.h index e411d4b..cde5ddf 100644 --- a/src/ui/ui.h +++ b/src/ui/ui.h @@ -518,7 +518,6 @@ struct oc_ui_box oc_list beforeRules; oc_list afterRules; - //oc_ui_style_tag tag; oc_ui_style* targetStyle; oc_ui_style style; u32 z; @@ -632,8 +631,6 @@ typedef struct oc_ui_context i32 editWordSelectionInitialCursor; i32 editWordSelectionInitialMark; - bool clipboardRegistered; - oc_ui_theme* theme; } oc_ui_context; @@ -731,39 +728,28 @@ ORCA_API void oc_ui_style_match_after(oc_ui_pattern pattern, oc_ui_style* style, //------------------------------------------------------------------------- // Basic widget helpers //------------------------------------------------------------------------- -enum -{ - OC_UI_STYLE_TAG_USER_MAX = 1 << 16, - OC_UI_STYLE_TAG_LABEL, - OC_UI_STYLE_TAG_BUTTON, - OC_UI_STYLE_TAG_SCROLLBAR, - OC_UI_STYLE_TAG_PANEL, - OC_UI_STYLE_TAG_TOOLTIP, - OC_UI_STYLE_TAG_MENU -}; - ORCA_API oc_ui_sig oc_ui_label(const char* label); ORCA_API oc_ui_sig oc_ui_label_str8(oc_str8 label); ORCA_API oc_ui_sig oc_ui_button(const char* label); ORCA_API oc_ui_sig oc_ui_checkbox(const char* name, bool* checked); -ORCA_API oc_ui_box* oc_ui_slider(const char* label, f32* value); -ORCA_API oc_ui_box* oc_ui_scrollbar(const char* label, f32 thumbRatio, f32* scrollValue); +ORCA_API oc_ui_box* oc_ui_slider(const char* name, f32* value); +ORCA_API oc_ui_box* oc_ui_scrollbar(const char* name, f32 thumbRatio, f32* scrollValue); ORCA_API void oc_ui_tooltip(const char* label); ORCA_API void oc_ui_panel_begin(const char* name, oc_ui_flags flags); ORCA_API void oc_ui_panel_end(void); #define oc_ui_panel(s, f) oc_defer_loop(oc_ui_panel_begin(s, f), oc_ui_panel_end()) -ORCA_API void oc_ui_menu_bar_begin(const char* label); +ORCA_API void oc_ui_menu_bar_begin(const char* name); ORCA_API void oc_ui_menu_bar_end(void); #define oc_ui_menu_bar(name) oc_defer_loop(oc_ui_menu_bar_begin(name), oc_ui_menu_bar_end()) ORCA_API void oc_ui_menu_begin(const char* label); ORCA_API void oc_ui_menu_end(void); -#define oc_ui_menu(name) oc_defer_loop(oc_ui_menu_begin(name), oc_ui_menu_end()) +#define oc_ui_menu(label) oc_defer_loop(oc_ui_menu_begin(label), oc_ui_menu_end()) -ORCA_API oc_ui_sig oc_ui_menu_button(const char* name); +ORCA_API oc_ui_sig oc_ui_menu_button(const char* label); typedef struct oc_ui_text_box_result {