Zig bindings for orca (still WIP) #140

Open
rdunnington wants to merge 24 commits from zig_ffi into main
23 changed files with 5149 additions and 93 deletions

View File

@ -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

View File

@ -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,
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 defaultMask = OC_UI_STYLE_BORDER_COLOR
oc_ui_style_mask unselectedMask = OC_UI_STYLE_BORDER_COLOR
| OC_UI_STYLE_BORDER_SIZE;
oc_ui_style_match_after(defaultPattern, &defaultStyle, defaultMask);
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 };

6
samples/zig-sample/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
Sample
zig-out
zig-cache
liborca.a
profile.dtrace
profile.spall

View File

@ -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.

View File

@ -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);
}

View File

@ -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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

BIN
samples/zig-sample/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -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);
}

6
samples/zig-ui/.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
UI
zig-out
zig-cache
liborca.a
profile.dtrace
profile.spall

13
samples/zig-ui/README.md Normal file
View File

@ -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.

107
samples/zig-ui/build.zig Normal file
View File

@ -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);
}

Binary file not shown.

Binary file not shown.

871
samples/zig-ui/src/main.zig Normal file
View File

@ -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,
});
}

View File

@ -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)

3574
src/orca.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -8,11 +8,10 @@
#include "platform.h"
#ifdef __cplusplus
extern "C"
{
extern "C" {
#endif
oc_host_platform oc_get_host_platform()
oc_host_platform oc_get_host_platform(void)
{
return OC_HOST_PLATFORM_MACOS;
}

View File

@ -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"

View File

@ -8,11 +8,10 @@
#include "platform.h"
#ifdef __cplusplus
extern "C"
{
extern "C" {
#endif
oc_host_platform oc_get_host_platform()
oc_host_platform oc_get_host_platform(void)
{
return OC_HOST_PLATFORM_WINDOWS;
}

View File

@ -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

View File

@ -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
{