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/README.md b/samples/zig-sample/README.md index 99b973c..5320304 100644 --- a/samples/zig-sample/README.md +++ b/samples/zig-sample/README.md @@ -9,6 +9,5 @@ These two commands build the runtime - the native host executable - and the samp ### 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: -* `oc_ui` * `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/src/main.zig b/samples/zig-sample/src/main.zig index 27ba3db..a0ede41 100644 --- a/samples/zig-sample/src/main.zig +++ b/samples/zig-sample/src/main.zig @@ -205,7 +205,7 @@ export fn oc_on_frame_refresh() void { const text_rect = text_metrics.ink; const center_x = frame_size.x / 2; - const text_begin_x = center_x - text_rect.Flat.w / 2; + const text_begin_x = center_x - text_rect.w / 2; Mat2x3.push(Mat2x3.translate(text_begin_x, 100)); defer Mat2x3.pop(); 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..4d8112c --- /dev/null +++ b/samples/zig-ui/src/main.zig @@ -0,0 +1,870 @@ +const std = @import("std"); +const oc = @import("orca"); + +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: oc.UiContext = 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(); + + 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 { + oc.uiProcessCEvent(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 => oc.uiSetTheme(oc.ui_dark_theme), + .SetLightTheme => oc.uiSetTheme(oc.ui_light_theme), + .None => {}, + } + cmd = .None; + + var default_style = oc.UiStyle{ .font = font_regular }; + { + oc.uiBeginFrame(frame_size, &default_style); + defer oc.uiEndFrame(); + + //-------------------------------------------------------------------------------------------- + // Menu bar + //-------------------------------------------------------------------------------------------- + { + oc.uiMenuBarBegin("menu_bar"); + defer oc.uiMenuBarEnd(); + + { + oc.uiMenuBegin("File"); + defer oc.uiMenuEnd(); + + if (oc.uiMenuButton("Quit").pressed) { + oc.requestQuit(); + } + } + + { + oc.uiMenuBegin("Theme"); + defer oc.uiMenuEnd(); + + if (oc.uiMenuButton("Dark theme").pressed) { + cmd = .SetDarkTheme; + } + if (oc.uiMenuButton("Light theme").pressed) { + cmd = .SetLightTheme; + } + } + } + + { + oc.uiPanelBegin("main panel", .{}); + defer oc.uiPanelEnd(); + + { + oc.uiStyleNext(.{ + .size = .{ + .width = .fill_parent, + .height = .{ .custom = .{ .kind = .Parent, .value = 1, .relax = 1 } }, + }, + .layout = .{ + .axis = .X, + .margin = .{ .x = 16, .y = 16 }, + .spacing = 16, + }, + }); + _ = oc.uiBoxBegin("Background", .{ .draw_background = true }); + defer _ = oc.uiBoxEnd(); + + widgets(scratch.arena); + + styling(scratch.arena); + } + } + } + + _ = canvas.select(); + surface.select(); + + oc.Canvas.setColor(ui.theme.bg0); + oc.Canvas.clear(); + + oc.uiDraw(); + 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(); + + { + oc.uiStyleNext(.{ + .size = .{ + .width = .fill_parent, + }, + .layout = .{ + .axis = .X, + .spacing = 32, + }, + }); + _ = oc.uiBoxBegin("top", .{}); + defer _ = oc.uiBoxEnd(); + + { + oc.uiStyleNext(.{ + .layout = .{ + .axis = .Y, + .spacing = 24, + }, + }); + _ = oc.uiBoxBegin("top_left", .{}); + defer _ = oc.uiBoxEnd(); + + //----------------------------------------------------------------------------- + // Label + //----------------------------------------------------------------------------- + _ = oc.uiLabel("Label"); + + //----------------------------------------------------------------------------- + // Button + //----------------------------------------------------------------------------- + if (oc.uiButton("Button").clicked) { + logPush("Button clicked"); + } + + { + oc.uiStyleNext(.{ + .layout = .{ + .axis = .X, + .alignment = .{ .y = .Center }, + .spacing = 8, + }, + }); + _ = oc.uiBoxBegin("checkbox", .{}); + defer _ = oc.uiBoxEnd(); + + //------------------------------------------------------------------------- + // Checkbox + //------------------------------------------------------------------------- + if (oc.uiCheckbox("checkbox", &checkbox_checked).clicked) { + if (checkbox_checked) { + logPush("Checkbox checked"); + } else { + logPush("Checkbox unhecked"); + } + } + + _ = oc.uiLabel("Checkbox"); + } + } + + //--------------------------------------------------------------------------------- + // Vertical slider + //--------------------------------------------------------------------------------- + oc.uiStyleNext(.{ .size = .{ .height = .{ .pixels = 130 } } }); + _ = oc.uiSlider("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; + } + + { + oc.uiStyleNext(.{ + .layout = .{ + .axis = .Y, + .spacing = 24, + }, + }); + _ = oc.uiBoxBegin("top right", .{}); + defer _ = oc.uiBoxEnd(); + + //----------------------------------------------------------------------------- + // Tooltip + //----------------------------------------------------------------------------- + if (oc.uiLabel("Tooltip").hovering) { + oc.uiTooltip("Hi"); + } + + //----------------------------------------------------------------------------- + // Radio group + //----------------------------------------------------------------------------- + var options = [_][]const u8{ + "Radio 1", + "Radio 2", + }; + var radio_group_info = oc.UiRadioGroupInfo{ + .selected_index = radio_selected, + .options = &options, + }; + var result = oc.uiRadioGroup("radio_group", &radio_group_info); + radio_selected = result.selected_index.?; + if (result.changed) { + logPushf("Selected {s}", .{options[radio_selected]}); + } + + //----------------------------------------------------------------------------- + // Horizontal slider + //----------------------------------------------------------------------------- + oc.uiStyleNext(.{ .size = .{ .width = .{ .pixels = 130 } } }); + _ = oc.uiSlider("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 + //------------------------------------------------------------------------------------- + oc.uiStyleNext(.{ + .size = .{ + .width = .{ .pixels = 305 }, + .height = .text, + }, + }); + var textResult = oc.uiTextBox("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 = oc.UiSelectPopupInfo{ + .selected_index = selected, + .options = &options, + .placeholder = "Select", + }; + var selectResult = oc.uiSelectPopup("select", &select_popup_info); + if (selectResult.selected_index != selected) { + logPushf("Selected {s}", .{options[selectResult.selected_index.?]}); + } + selected = selectResult.selected_index; + + //------------------------------------------------------------------------------------- + // Scrollable panel + //------------------------------------------------------------------------------------- + { + oc.uiStyleNext(.{ + .size = .{ + .width = .fill_parent, + .height = .{ + .custom = .{ .kind = .Parent, .value = 1, .relax = 1, .min_size = 200 }, + }, + }, + .bg_color = ui.theme.bg2, + .border_color = ui.theme.border, + .border_size = 1, + .roundness = ui.theme.roundness_small, + }); + _ = oc.uiPanelBegin("log", .{ .draw_background = true, .draw_border = true }); + defer oc.uiPanelEnd(); + + { + oc.uiStyleNext(.{ + .layout = .{ + .margin = .{ .x = 16, .y = 16 }, + }, + }); + _ = oc.uiBoxBegin("contents", .{}); + defer _ = oc.uiBoxEnd(); + + if (log_lines.list.empty()) { + oc.uiStyleNext(.{ .color = ui.theme.text2 }); + _ = oc.uiLabel("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; + _ = oc.uiBoxBegin(id, .{}); + defer _ = oc.uiBoxEnd(); + + _ = oc.uiLabel(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: oc.UiStatus = .{}; +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: oc.UiStatus = .{}; +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.theme or ui.theme.palette. + // + // Rule-based styling is described at + // https://www.forkingpaths.dev/posts/23-03-10/rule_based_styling_imgui.html + columnBegin("Styling", 2.0 / 3.0); + defer columnEnd(); + + { + oc.uiStyleNext(.{ + .size = .{ + .width = .fill_parent, + .height = .{ .pixels = 152 }, + }, + .layout = .{ + .margin = .{ .x = 310, .y = 16 }, + }, + .bg_color = oc.ui_dark_theme.bg0, + .roundness = oc.ui_dark_theme.roundness_small, + }); + _ = oc.uiBoxBegin("styled_radios", .{ .draw_background = true, .draw_border = true }); + defer _ = oc.uiBoxEnd(); + + resetNextRadioGroupToDarkTheme(arena); + + var unselected_tag = oc.uiTagMake("radio"); + var unselected_pattern = oc.UiPattern.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 } }); + } + oc.uiStyleMatchAfter(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 = oc.uiTagMake("radio_selected"); + var selected_pattern = oc.UiPattern.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 } }); + } + oc.uiStyleMatchAfter(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 = oc.uiTagMake("label"); + var label_pattern = oc.UiPattern.init(); + label_pattern.push(arena, .{ .sel = .{ .tag = label_tag } }); + oc.uiStyleMatchAfter(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 = oc.UiRadioGroupInfo{ + .selected_index = styling_selected_radio, + .options = &options, + }; + var result = oc.uiRadioGroup("radio_group", &radio_group_info); + styling_selected_radio = result.selected_index; + } + + { + oc.uiStyleNext(.{ .layout = .{ .axis = .X, .spacing = 32 } }); + _ = oc.uiBoxBegin("controls", .{}); + defer _ = oc.uiBoxEnd(); + + { + oc.uiStyleNext(.{ .layout = .{ .axis = .Y, .spacing = 16 } }); + _ = oc.uiBoxBegin("unselected", .{}); + defer _ = oc.uiBoxEnd(); + + oc.uiStyleNext(.{ .font_size = 16 }); + _ = oc.uiLabel("Radio style"); + + { + oc.uiStyleNext(.{ .layout = .{ .spacing = 4 } }); + _ = oc.uiBoxBegin("size", .{}); + defer _ = oc.uiBoxEnd(); + + 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; + } + + { + oc.uiStyleNext(.{ .layout = .{ .spacing = 4 } }); + _ = oc.uiBoxBegin("background", .{}); + defer _ = oc.uiBoxEnd(); + + 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); + } + + { + oc.uiStyleNext(.{ .layout = .{ .spacing = 4 } }); + _ = oc.uiBoxBegin("border", .{}); + defer _ = oc.uiBoxEnd(); + + 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; + + { + oc.uiStyleNext(.{ .layout = .{ .spacing = 10 } }); + _ = oc.uiBoxBegin("status_override", .{}); + defer _ = oc.uiBoxEnd(); + + _ = oc.uiLabel("Override"); + + var status_options = [_][]const u8{ + "Always", + "When hovering", + "When active", + }; + var status_info = oc.UiRadioGroupInfo{ + .selected_index = unselected_status_index, + .options = &status_options, + }; + var status_result = oc.uiRadioGroup("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, + }; + } + } + + { + oc.uiStyleNext(.{ .layout = .{ .axis = .Y, .spacing = 16 } }); + _ = oc.uiBoxBegin("selected", .{}); + defer _ = oc.uiBoxEnd(); + + oc.uiStyleNext(.{ .font_size = 16 }); + _ = oc.uiLabel("Radio selected style"); + + { + oc.uiStyleNext(.{ .layout = .{ .spacing = 4 } }); + _ = oc.uiBoxBegin("size", .{}); + defer _ = oc.uiBoxEnd(); + + 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; + } + + { + oc.uiStyleNext(.{ .layout = .{ .spacing = 4 } }); + _ = oc.uiBoxBegin("color", .{}); + defer _ = oc.uiBoxEnd(); + + 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); + } + + { + oc.uiStyleNext(.{ .layout = .{ .spacing = 4 } }); + _ = oc.uiBoxBegin("background", .{}); + defer _ = oc.uiBoxEnd(); + + 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); + } + + { + oc.uiStyleNext(.{ .layout = .{ .spacing = 10 } }); + _ = oc.uiBoxBegin("status_override", .{}); + defer _ = oc.uiBoxEnd(); + + oc.uiStyleNext(.{ .size = .{ .height = .{ .pixels = 30 } } }); + _ = oc.uiBoxMake("spacer", .{}); + + _ = oc.uiLabel("Override"); + + var status_options = [_][]const u8{ + "Always", + "When hovering", + "When active", + }; + var status_info = oc.UiRadioGroupInfo{ + .selected_index = selected_status_index, + .options = &status_options, + }; + var status_result = oc.uiRadioGroup("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, + }; + } + } + + { + oc.uiStyleNext(.{ .layout = .{ .axis = .Y, .spacing = 10 } }); + _ = oc.uiBoxBegin("label", .{}); + defer _ = oc.uiBoxEnd(); + + oc.uiStyleNext(.{ .font_size = 16 }); + _ = oc.uiLabel("Label style"); + + { + oc.uiStyleNext(.{ .layout = .{ .axis = .X, .spacing = 8 } }); + _ = oc.uiBoxBegin("font_color", .{}); + defer _ = oc.uiBoxEnd(); + + oc.uiStyleMatchAfter(oc.UiPattern.owner(), .{ + .size = .{ .width = .{ .pixels = 100 } }, + }); + _ = oc.uiLabel("Font color"); + + var color_names = [_][]const u8{ + "Default", + "Red", + "Orange", + "Amber", + "Yellow", + "Lime", + "Light green", + "Green", + }; + var colors = [_]oc.Color{ + oc.ui_dark_theme.text0, + oc.ui_dark_theme.palette.red5, + oc.ui_dark_theme.palette.orange5, + oc.ui_dark_theme.palette.amber5, + oc.ui_dark_theme.palette.yellow5, + oc.ui_dark_theme.palette.lime5, + oc.ui_dark_theme.palette.light_green5, + oc.ui_dark_theme.palette.green5, + }; + var color_info = oc.UiSelectPopupInfo{ + .selected_index = label_font_color_selected, + .options = &color_names, + }; + var color_result = oc.uiSelectPopup("color", &color_info); + label_font_color_selected = color_result.selected_index; + label_font_color = colors[label_font_color_selected.?]; + } + + { + oc.uiStyleNext(.{ .layout = .{ .axis = .X, .spacing = 8 } }); + _ = oc.uiBoxBegin("font", .{}); + defer _ = oc.uiBoxEnd(); + + oc.uiStyleMatchAfter(oc.UiPattern.owner(), .{ + .size = .{ .width = .{ .pixels = 100 } }, + }); + _ = oc.uiLabel("Font"); + + var font_names = [_][]const u8{ + "Regular", + "Bold", + }; + var fonts = [_]*oc.Font{ + &font_regular, + &font_bold, + }; + var font_info = oc.UiSelectPopupInfo{ + .selected_index = label_font_selected, + .options = &font_names, + }; + var font_result = oc.uiSelectPopup("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 { + oc.uiStyleNext(.{ + .size = .{ + .width = .{ + .custom = .{ .kind = .Parent, .value = widthFraction, .relax = 1 }, + }, + .height = .fill_parent, + }, + .layout = .{ + .axis = .Y, + .margin = .{ .y = 8 }, + .spacing = 24, + }, + .bg_color = ui.theme.bg1, + .border_color = ui.theme.border, + .border_size = 1, + .roundness = ui.theme.roundness_small, + }); + _ = oc.uiBoxBegin(header, .{ .draw_background = true, .draw_border = true }); + + { + oc.uiStyleNext(.{ + .size = .{ .width = .fill_parent }, + .layout = .{ .alignment = .{ .x = .Center } }, + }); + _ = oc.uiBoxBegin("header", .{}); + defer _ = oc.uiBoxEnd(); + + oc.uiStyleNext(.{ .font_size = 18 }); + _ = oc.uiLabel(header); + } + + oc.uiStyleNext(.{ + .size = .{ + .width = .fill_parent, + .height = .{ + .custom = .{ .kind = .Parent, .value = 1, .relax = 1 }, + }, + }, + .layout = .{ + .alignment = .{ .x = .Start }, + .margin = .{ .x = 16 }, + .spacing = 24, + }, + }); + _ = oc.uiBoxBegin("contents", .{}); +} + +fn columnEnd() void { + _ = oc.uiBoxEnd(); // contents + _ = oc.uiBoxEnd(); // column +} + +fn labeledSlider(label: []const u8, value: *f32) void { + oc.uiStyleNext(.{ .layout = .{ .axis = .X, .spacing = 8 } }); + _ = oc.uiBoxBegin(label, .{}); + defer _ = oc.uiBoxEnd(); + + oc.uiStyleMatchAfter(oc.UiPattern.owner(), .{ + .size = .{ .width = .{ .pixels = 100 } }, + }); + _ = oc.uiLabel(label); + + oc.uiStyleNext(.{ + .size = .{ .width = .{ .pixels = 100 } }, + }); + _ = oc.uiSlider("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.theme or ui.theme.palette +fn resetNextRadioGroupToDarkTheme(arena: *oc.Arena) void { + var unselected_tag = oc.uiTagMake("radio"); + var unselected_pattern = oc.UiPattern.init(); + unselected_pattern.push(arena, .{ .sel = .{ .tag = unselected_tag } }); + oc.uiStyleMatchAfter(unselected_pattern, .{ + .border_color = oc.ui_dark_theme.text3, + .border_size = 1, + }); + + var unselected_hover_pattern = oc.UiPattern.init(); + unselected_hover_pattern.push(arena, .{ .sel = .{ .tag = unselected_tag } }); + unselected_hover_pattern.push(arena, .{ .op = .And, .sel = .{ .status = .{ .hover = true } } }); + oc.uiStyleMatchAfter(unselected_hover_pattern, .{ + .bg_color = oc.ui_dark_theme.fill0, + .border_color = oc.ui_dark_theme.primary, + }); + + var unselected_active_pattern = oc.UiPattern.init(); + unselected_active_pattern.push(arena, .{ .sel = .{ .tag = unselected_tag } }); + unselected_active_pattern.push(arena, .{ .op = .And, .sel = .{ .status = .{ .active = true } } }); + oc.uiStyleMatchAfter(unselected_active_pattern, .{ + .bg_color = oc.ui_dark_theme.fill1, + .border_color = oc.ui_dark_theme.primary, + }); + + var selected_tag = oc.uiTagMake("radio_selected"); + var selected_pattern = oc.UiPattern.init(); + selected_pattern.push(arena, .{ .sel = .{ .tag = selected_tag } }); + oc.uiStyleMatchAfter(selected_pattern, .{ + .color = oc.ui_dark_theme.palette.white, + .bg_color = oc.ui_dark_theme.primary, + }); + + var selected_hover_pattern = oc.UiPattern.init(); + selected_hover_pattern.push(arena, .{ .sel = .{ .tag = selected_tag } }); + selected_hover_pattern.push(arena, .{ .op = .And, .sel = .{ .status = .{ .hover = true } } }); + oc.uiStyleMatchAfter(selected_hover_pattern, .{ + .bg_color = oc.ui_dark_theme.primary_hover, + }); + + var selected_active_pattern = oc.UiPattern.init(); + selected_active_pattern.push(arena, .{ .sel = .{ .tag = selected_tag } }); + selected_active_pattern.push(arena, .{ .op = .And, .sel = .{ .status = .{ .active = true } } }); + oc.uiStyleMatchAfter(selected_active_pattern, .{ + .bg_color = oc.ui_dark_theme.primary_active, + }); +} diff --git a/src/orca.zig b/src/orca.zig index 2d94654..5c1263f 100644 --- a/src/orca.zig +++ b/src/orca.zig @@ -363,10 +363,23 @@ pub const Arena = extern struct { 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] //------------------------------------------------------------------------------------------ @@ -1194,6 +1207,36 @@ const MouseCursor = enum(c_uint) { 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, @@ -1440,7 +1483,18 @@ pub const KeyCode = enum(c_uint) { RightAlt = 346, RightSuper = 347, Menu = 348, - Count = 349, +}; + +pub const key_code_count: usize = 349; + +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) { @@ -1451,10 +1505,111 @@ pub const MouseButton = enum(c_uint) { Ext2 = 0x04, }; +pub const mouse_button_count: usize = 5; + +/// 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; @@ -1677,21 +1832,26 @@ pub const Image = extern struct { pub const drawRegion = oc_image_draw_region; }; -pub const Rect = extern union { - Flat: extern struct { - x: f32, - y: f32, - w: f32, - h: f32, - }, - Pairs: extern struct { - xy: Vec2, - wh: Vec2, - }, - Array: [4]f32, +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 .{ .Flat = .{ .x = x, .y = y, .w = w, .h = h } }; + 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); } }; @@ -1701,6 +1861,10 @@ pub const Color = extern struct { 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); } @@ -1845,6 +2009,1314 @@ pub const Canvas = extern struct { 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 UiKey = extern struct { + hash: u64, +}; + +pub const UiAxis = enum(c_uint) { X, Y }; + +pub const UiLayoutMargin = struct { + x: ?f32 = null, + y: ?f32 = null, + + pub fn array(self: *UiLayoutMargin) *[2]?f32 { + return @ptrCast(self); + } +}; + +pub const UiAlignment = enum(c_uint) { Start, End, Center }; + +pub const UiLayoutAlignment = struct { + x: ?UiAlignment = null, + y: ?UiAlignment = null, + + pub fn array(self: *UiLayoutAlignment) *[2]?UiAlignment { + return @ptrCast(self); + } +}; + +pub const UiLayout = struct { + axis: ?UiAxis = null, + spacing: ?f32 = null, + margin: UiLayoutMargin = .{}, + alignment: UiLayoutAlignment = .{}, +}; + +pub const UiSizeKind = enum(c_uint) { + Text, + Pixels, + Children, + Parent, + ParentMinusPixels, +}; + +pub const UiSizeCustom = struct { + kind: UiSizeKind = .Text, + value: f32 = 0, + relax: f32 = 0, + min_size: f32 = 0, +}; + +pub const UiSize = union(enum) { + text, + pixels: f32, + children, + fill_parent, + parent: f32, + parent_minus_pixels: f32, + custom: UiSizeCustom, +}; + +pub const UiBoxSize = struct { + width: ?UiSize = null, + height: ?UiSize = null, + + pub fn array(self: *UiBoxSize) *[2]?UiSize { + return @ptrCast(self); + } +}; + +pub const UiBoxFloating = struct { + x: ?bool = null, + y: ?bool = null, + + pub fn array(self: *UiBoxFloating) *[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 UiAnimationMask = 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 UiStyle = struct { + size: UiBoxSize = .{}, + layout: UiLayout = .{}, + floating: UiBoxFloating = .{}, + 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: UiAnimationMask = .None, +}; + +pub const UiPalette = 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 ui_dark_palette = @extern(*UiPalette, .{ .name = "OC_UI_DARK_PALETTE" }); + +/// Visualized in doc/UIColors.md +pub const ui_light_palette = @extern(*UiPalette, .{ .name = "OC_UI_LIGHT_PALETTE" }); + +pub const UiTheme = 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: *UiPalette, +}; + +pub const ui_dark_theme = @extern(*UiTheme, .{ .name = "OC_UI_DARK_THEME" }); +pub const ui_light_theme = @extern(*UiTheme, .{ .name = "OC_UI_LIGHT_THEME" }); + +pub const UiTag = extern struct { + hash: u64, +}; + +pub const UiSelectorKind = enum(c_uint) { + any, + owner, + text, + tag, + status, + key, +}; + +pub const UiStatus = packed struct(u8) { + _: u1 = 0, + + hover: bool = false, + hot: bool = false, + active: bool = false, + dragging: bool = false, + + __: u3 = 0, + + pub fn empty(self: UiStatus) bool { + return !self.hover and !self.hot and !self.active and !self.dragging; + } +}; + +pub const UiSelectorOp = enum(c_uint) { + Descendant, + And, +}; + +pub const UiSelector = struct { + op: UiSelectorOp = .Descendant, + sel: union(UiSelectorKind) { + any, + owner, + text: []u8, + key: UiKey, + tag: UiTag, + status: UiStatus, + }, +}; + +pub const UiPattern = extern struct { + l: List, + + extern fn oc_ui_pattern_push(arena: *Arena, pattern: *UiPattern, selector: UiSelectorInternal) void; + extern fn oc_ui_pattern_all() UiPattern; + extern fn oc_ui_pattern_owner() UiPattern; + + pub fn init() UiPattern { + return .{ .l = List.init() }; + } + + /// Push the selector onto frame arena and insert it into the pattern's linked list. + /// Underlying UiSelector 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: *UiPattern, arena: *Arena, selector: UiSelector) void { + oc_ui_pattern_push(arena, self, uiConvertSelector(selector)); + } + + pub const all = oc_ui_pattern_all; + pub const owner = oc_ui_pattern_owner; +}; + +pub const UiStyleRule = struct { + box_elt: ListElt, + build_elt: ListElt, + tmp_elt: ListElt, + + owner: *UiBox, + pattern: UiPattern, + style: *UiStyle, +}; + +pub const UiSig = extern struct { + box: *UiBox, + + 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 UiBoxDrawProc = *fn (box: *UiBox, data: ?*anyopaque) callconv(.C) void; + +pub const UiOverflowAllow = packed struct { + x: bool = false, + y: bool = false, + + pub fn array(self: *UiOverflowAllow) *[2]bool { + return @ptrCast(self); + } +}; + +pub const UiFlags = 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: UiOverflowAllow = .{}, // 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 UiBox = extern struct { + // hierarchy + list_elt: ListElt, + children: List, + parent: ?*UiBox, + + overlay_elt: ListElt, + + // keying and caching + bucket_elt: ListElt, + key: UiKey, + frame_counter: u64, + + // builder-provided info + flags: UiFlags, + string: Str8, + tags: List, + + draw_proc: UiBoxDrawProc, + draw_data: *anyopaque, + + // styling + before_rules: List, + after_rules: List, + + target_style: ?*UiStyle, + style: UiStyleInternal, + z: u32, + + float_pos: Vec2, + children_sum: [2]f32, + spacing: [2]f32, + min_size: [2]f32, + rect: Rect, + + // signals + sig: ?*UiSig, + + // 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, +}; + +pub const UiInputText = extern struct { + count: u8, + codepoints: [64]Utf32, +}; + +const UiCSize = extern struct { + kind: UiSizeKind, + value: f32 = 0, + relax: f32 = 0, + min_size: f32 = 0, + + fn fromUiSize(ui_size: UiSize) UiCSize { + return switch (ui_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 UiStackElt = extern struct { + parent: ?*UiStackElt, + elt: extern union { + box: *UiBox, + size: UiCSize, + clip: Rect, + }, +}; + +pub const UiTagElt = extern struct { + list_elt: ListElt, + tag: UiTag, +}; + +pub const UiEditMove = enum(c_uint) { + none, + char, + word, + line, +}; + +pub const UiContext = 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: *UiBox, + overlay: *UiBox, + overlay_list: List, + box_stack: *UiStackElt, + clip_stack: *UiStackElt, + + next_box_before_rules: List, + next_box_after_rules: List, + next_box_tags: List, + + z: u32, + hovered: ?*UiBox, + + focus: ?*UiBox, + edit_cursor: i32, + edit_mark: i32, + edit_first_displayed_char: i32, + edit_cursor_blink_start: f64, + edit_selection_mode: UiEditMove, + edit_word_selection_initial_cursor: i32, + edit_word_selection_initial_mark: i32, + + theme: *UiTheme, + + extern fn oc_ui_init(context: *UiContext) void; + pub const init = oc_ui_init; +}; + +const UiLayoutAlignmentInternal = extern struct { + x: UiAlignment, + y: UiAlignment, +}; + +const UiLayoutMarginInternal = extern struct { + x: f32, + y: f32, +}; + +const UiLayoutInternal = extern struct { + axis: UiAxis, + spacing: f32, + margin: UiLayoutMarginInternal, + alignment: UiLayoutAlignmentInternal, +}; + +const UiBoxSizeInternal = extern struct { + width: UiCSize, + height: UiCSize, +}; + +const UiBoxFloatingInternal = extern struct { + x: bool, + y: bool, +}; + +const UiFloatTargetInternal = extern struct { + x: f32, + y: f32, +}; + +const UiStyleInternal = extern struct { + size: UiBoxSizeInternal, + layout: UiLayoutInternal, + floating: UiBoxFloatingInternal, + float_target: UiFloatTargetInternal, + color: Color, + bg_color: Color, + border_color: Color, + font: Font, + font_size: f32, + border_size: f32, + roundness: f32, + animation_time: f32, + animation_mask: UiAnimationMask, +}; + +const UiStyleMaskInternal = 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_get_context() *UiContext; +extern fn oc_ui_set_context(context: *UiContext) void; + +extern fn oc_ui_process_event(event: *CEvent) void; +extern fn oc_ui_begin_frame(size: Vec2, default_style: *UiStyleInternal, mask: UiStyleMaskInternal) void; +extern fn oc_ui_end_frame() void; +extern fn oc_ui_draw() void; +extern fn oc_ui_set_theme(theme: *UiTheme) void; + +pub const uiGetContext = oc_ui_get_context; +pub const uiSetContext = oc_ui_set_context; +pub const uiProcessCEvent = oc_ui_process_event; + +pub const uiEndFrame = oc_ui_end_frame; +pub const uiDraw = oc_ui_draw; +pub const uiSetTheme = oc_ui_set_theme; + +pub fn uiBeginFrame(size: Vec2, default_style: *UiStyle) void { + var default_style_and_mask = uiConvertStyle(default_style); + oc_ui_begin_frame(size, &default_style_and_mask.style, default_style_and_mask.mask); +} + +const UiStyleAndMaskInternal = struct { + style: UiStyleInternal, + mask: UiStyleMaskInternal, +}; + +fn uiConvertStyle(style: *const UiStyle) UiStyleAndMaskInternal { + var style_internal: UiStyleInternal = std.mem.zeroes(UiStyleInternal); + var mask: UiStyleMaskInternal = .{}; + if (style.size.width) |width| { + style_internal.size.width = UiCSize.fromUiSize(width); + mask.size_width = true; + } + if (style.size.height) |height| { + style_internal.size.height = UiCSize.fromUiSize(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 keys +//------------------------------------------------------------------------------------------ + +extern fn oc_ui_key_make_str8(string: Str8) UiKey; +extern fn oc_ui_key_make_path(path: Str8List) UiKey; + +pub fn uiKeyMake(string: []const u8) UiKey { + return oc_ui_key_make_str8(Str8.fromSlice(string)); +} + +pub const uiKeyMakePath = oc_ui_key_make_path; + +//------------------------------------------------------------------------------------------ +// [UI]: box hierarchy building +//------------------------------------------------------------------------------------------ + +extern fn oc_ui_box_make_str8(string: Str8, flags: UiFlags) *UiBox; +extern fn oc_ui_box_begin_str8(string: Str8, flags: UiFlags) *UiBox; +extern fn oc_ui_box_end() *UiBox; + +extern fn oc_ui_box_push(box: *UiBox) void; +extern fn oc_ui_box_pop() void; +extern fn oc_ui_box_top() ?*UiBox; + +extern fn oc_ui_box_lookup_key(key: UiKey) ?*UiBox; +extern fn oc_ui_box_lookup_str8(string: Str8) ?*UiBox; + +extern fn oc_ui_box_set_draw_proc(box: UiBox, proc: UiBoxDrawProc, data: ?*anyopaque) void; + +pub fn uiBoxMake(string: []const u8, flags: UiFlags) *UiBox { + return oc_ui_box_make_str8(Str8.fromSlice(string), flags); +} + +pub fn uiBoxBegin(string: []const u8, flags: UiFlags) *UiBox { + return oc_ui_box_begin_str8(Str8.fromSlice(string), flags); +} + +pub const uiBoxEnd = oc_ui_box_end; + +pub const uiBoxPush = oc_ui_box_push; +pub const uiBoxPop = oc_ui_box_pop; +pub const uiBoxTop = oc_ui_box_top; + +pub const uiBoxLookupKey = oc_ui_box_lookup_key; + +pub fn uiBoxLookupStr(string: []const u8) ?*UiBox { + return oc_ui_box_lookup_str8(Str8.fromSlice(string)); +} + +pub const uiBoxSetDrawProc = oc_ui_box_set_draw_proc; + +//------------------------------------------------------------------------------------------ +// [UI]: box status and signals +//------------------------------------------------------------------------------------------ + +extern fn oc_ui_box_closed(box: *UiBox) bool; +extern fn oc_ui_box_set_closed(box: *UiBox, closed: bool) void; + +extern fn oc_ui_box_active(box: *UiBox) bool; +extern fn oc_ui_box_activate(box: *UiBox) void; +extern fn oc_ui_box_deactivate(box: *UiBox) void; + +extern fn oc_ui_box_hot(box: *UiBox) bool; +extern fn oc_ui_box_set_hot(box: *UiBox, hot: bool) void; + +extern fn oc_ui_box_sig(box: *UiBox) UiSig; + +pub const uiBoxClosed = oc_ui_box_closed; +pub const uiBoxSetClosed = oc_ui_box_set_closed; + +pub const uiBoxActive = oc_ui_box_active; +pub const uiBoxActivate = oc_ui_box_activate; +pub const uiBoxDeactivate = oc_ui_box_deactivate; + +pub const uiBoxHot = oc_ui_box_hot; +pub const uiBoxSetHot = oc_ui_box_set_hot; + +pub const uiBoxSig = oc_ui_box_sig; + +//------------------------------------------------------------------------------------------ +// [UI]: tagging +//------------------------------------------------------------------------------------------ + +extern fn oc_ui_tag_make_str8(string: Str8) UiTag; +extern fn oc_ui_tag_box_str8(box: *UiBox, string: Str8) void; +extern fn oc_ui_tag_next_str8(string: Str8) void; + +pub fn uiTagMake(string: []const u8) UiTag { + return oc_ui_tag_make_str8(Str8.fromSlice(string)); +} + +pub fn uiTagBox(box: *UiBox, string: []const u8) void { + oc_ui_tag_box_str8(box, Str8.fromSlice(string)); +} + +pub fn uiTagNext(string: []const u8) void { + oc_ui_tag_next_str8(Str8.fromSlice(string)); +} + +//------------------------------------------------------------------------------------------ +// [UI]: styling +//------------------------------------------------------------------------------------------ + +const UiSelectorDataInternal = extern union { + text: Str8, + key: UiKey, + tag: UiTag, + status: UiStatus, +}; + +const UiSelectorInternal = extern struct { + list_elt: ListElt, + kind: UiSelectorKind, + op: UiSelectorOp, + data: UiSelectorDataInternal, +}; + +extern fn oc_ui_style_next(style: *UiStyleInternal, mask: UiStyleMaskInternal) void; +extern fn oc_ui_style_match_before(pattern: UiPattern, style: *UiStyleInternal, mask: UiStyleMaskInternal) void; +extern fn oc_ui_style_match_after(patterh: UiPattern, style: *UiStyleInternal, mask: UiStyleMaskInternal) void; + +pub fn uiStyleNext(style: UiStyle) void { + var style_and_mask = uiConvertStyle(&style); + oc_ui_style_next(&style_and_mask.style, style_and_mask.mask); +} + +pub fn uiStyleMatchBefore(pattern: UiPattern, style: UiStyle) void { + var style_and_mask = uiConvertStyle(&style); + oc_ui_style_match_before(pattern, &style_and_mask.style, style_and_mask.mask); +} + +pub fn uiStyleMatchAfter(pattern: UiPattern, style: UiStyle) void { + var style_and_mask = uiConvertStyle(&style); + oc_ui_style_match_after(pattern, &style_and_mask.style, style_and_mask.mask); +} + +pub fn uiApplyStyle(dst: *UiStyle, src: *UiStyle) 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 uiConvertSelector(selector: UiSelector) UiSelectorInternal { + var data: UiSelectorDataInternal = switch (selector.sel) { + .any, .owner => std.mem.zeroes(UiSelectorDataInternal), + .text => |text| .{ .text = Str8.fromSlice(text) }, + .key => |key| .{ .key = key }, + .tag => |tag| .{ .tag = tag }, + .status => |status| .{ .status = status }, + }; + + return UiSelectorInternal{ + .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) UiSig; +extern fn oc_ui_button_str8(label: Str8) UiSig; +extern fn oc_ui_checkbox_str8(label: Str8, checked: *bool) UiSig; +extern fn oc_ui_slider_str8(label: Str8, value: *f32) *UiBox; +extern fn oc_ui_scrollbar_str8(label: Str8, thumbRatio: f32, scrollValue: *f32) *UiBox; +extern fn oc_ui_tooltip_str8(label: Str8) void; + +extern fn oc_ui_panel_begin_str8(name: Str8, flags: UiFlags) 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) UiSig; + +const UiTextBoxResultInternal = extern struct { + changed: bool, + accepted: bool, + text: Str8, +}; +extern fn oc_ui_text_box_str8(name: Str8, arena: Arena, text: Str8) UiTextBoxResultInternal; + +const UiSelectPopupInfoInternal = 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: *UiSelectPopupInfoInternal) UiSelectPopupInfoInternal; + +const UiRadioGroupInfoInternal = extern struct { + changed: bool, + selected_index: c_int, + option_count: c_int, + options: [*]Str8, +}; +extern fn oc_ui_radio_group_str8(name: Str8, info: UiRadioGroupInfoInternal) UiRadioGroupInfoInternal; + +pub fn uiLabel(label: []const u8) UiSig { + return oc_ui_label_str8(Str8.fromSlice(label)); +} + +pub fn uiButton(label: []const u8) UiSig { + return oc_ui_button_str8(Str8.fromSlice(label)); +} + +pub fn uiCheckbox(label: []const u8, checked: *bool) UiSig { + return oc_ui_checkbox_str8(Str8.fromSlice(label), checked); +} + +pub fn uiSlider(label: []const u8, value: *f32) *UiBox { + return oc_ui_slider_str8(Str8.fromSlice(label), value); +} + +pub fn uiScrollbar(label: []const u8, thumbRatio: f32, scrollValue: *f32) *UiBox { + return oc_ui_scrollbar_str8(Str8.fromSlice(label), thumbRatio, scrollValue); +} + +pub fn uiTooltip(label: []const u8) void { + oc_ui_tooltip_str8(Str8.fromSlice(label)); +} + +pub fn uiPanelBegin(name: []const u8, flags: UiFlags) void { + oc_ui_panel_begin_str8(Str8.fromSlice(name), flags); +} + +pub fn uiPanelEnd() void { + oc_ui_panel_end(); +} + +pub fn uiMenuBarBegin(label: []const u8) void { + oc_ui_menu_bar_begin_str8(Str8.fromSlice(label)); +} + +pub fn uiMenuBarEnd() void { + oc_ui_menu_bar_end(); +} + +pub fn uiMenuBegin(label: []const u8) void { + oc_ui_menu_begin_str8(Str8.fromSlice(label)); +} + +pub fn uiMenuEnd() void { + oc_ui_menu_end(); +} + +pub fn uiMenuButton(name: []const u8) UiSig { + return oc_ui_menu_button_str8(Str8.fromSlice(name)); +} + +pub const UiTextBoxResult = struct { + changed: bool, + accepted: bool, + text: []u8, +}; + +pub fn uiTextBox(name: []const u8, arena: Arena, text: []const u8) UiTextBoxResult { + 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 UiSelectPopupInfo = struct { + changed: bool = false, + selected_index: ?usize, + options: [][]const u8, + placeholder: []const u8 = "", +}; + +pub fn uiSelectPopup(name: []const u8, info: *UiSelectPopupInfo) UiSelectPopupInfo { + var info_internal = UiSelectPopupInfoInternal{ + .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 UiRadioGroupInfo = struct { + changed: bool = false, + selected_index: ?usize, + options: [][]const u8, +}; + +pub fn uiRadioGroup(name: []const u8, info: *UiRadioGroupInfo) UiRadioGroupInfo { + var info_internal = UiRadioGroupInfoInternal{ + .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 //------------------------------------------------------------------------------------------ @@ -1861,22 +3333,28 @@ pub const Canvas = extern struct { // [FILE IO] basic API //------------------------------------------------------------------------------------------ -const File = extern struct { +pub const File = extern struct { const OpenFlags = packed struct(u16) { - none: bool, - append: bool, - truncate: bool, - create: bool, + _: u1 = 0, - symlink: bool, - no_follow: bool, - restrict: bool, + 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) { - none: bool, - read: bool, - write: bool, + _: u1 = 0, + + read: bool = false, + write: bool = false, + + __: u13 = 0, }; const Whence = enum(c_uint) { @@ -1975,8 +3453,8 @@ const File = extern struct { 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_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; @@ -1992,11 +3470,22 @@ const File = extern struct { pub const lastError = oc_file_last_error; pub const pos = oc_file_pos; pub const seek = oc_file_seek; - pub const write = oc_file_write; - pub const read = oc_file_read; + + 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 const size = oc_file_size; + + 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; @@ -2006,11 +3495,11 @@ const File = extern struct { // [FILE IO] low-level io queue api //------------------------------------------------------------------------------------------ -const io = struct { - const ReqId = u16; - const Op = u32; +pub const io = struct { + pub const ReqId = u16; + pub const Op = u32; - const OpEnum = enum(c_uint) { + pub const OpEnum = enum(c_uint) { OpenAt = 0, Close, FStat, @@ -2020,7 +3509,7 @@ const io = struct { Error, }; - const Req = extern struct { + pub const Req = extern struct { id: ReqId, op: Op, handle: File, @@ -2042,7 +3531,7 @@ const io = struct { }, }; - const Error = enum(i32) { + pub const Error = enum(i32) { Ok = 0, Unknown, Op, // unsupported operation @@ -2068,7 +3557,7 @@ const io = struct { Walkout, // attempted to walk out of root directory }; - const Cmp = extern struct { + pub const Cmp = extern struct { id: ReqId, err: Error, data: extern union { diff --git a/src/ui/ui.c b/src/ui/ui.c index c526f40..68bcfc6 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 label, 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(label, 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* label, f32* value) +{ + return oc_ui_slider_str8(OC_STR8(label), value); +} + +oc_ui_box* oc_ui_scrollbar_str8(oc_str8 label, 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(label, 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* label, f32 thumbRatio, f32* scrollValue) +{ + return oc_ui_scrollbar_str8(OC_STR8(label), 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 name) { 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(name, flags); oc_ui_sig sig = oc_ui_box_sig(box); return (sig); } +oc_ui_sig oc_ui_menu_button(const char* name) +{ + return oc_ui_menu_button_str8(OC_STR8(name)); +} + //------------------------------------------------------------------------------ // 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..93661a5 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,17 +728,6 @@ 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);