zcatgui/examples/wasm_demo.zig
reugenio 6889474327 feat: zcatgui v0.15.0 - Mobile & Web Backends
WASM Backend:
- src/backend/wasm.zig: Browser backend using extern JS functions
- web/zcatgui.js: Canvas 2D rendering bridge (~200 LOC)
- web/index.html: Demo page with event handling
- examples/wasm_demo.zig: Widget showcase for browser
- Output: 18KB WASM binary

Android Backend:
- src/backend/android.zig: ANativeActivity + ANativeWindow
- examples/android_demo.zig: Touch-enabled demo
- Touch-to-mouse event mapping
- Logging via __android_log_print
- Targets: ARM64 (device), x86_64 (emulator)

iOS Backend:
- src/backend/ios.zig: UIKit bridge via extern C functions
- ios/ZcatguiBridge.h: Objective-C header
- ios/ZcatguiBridge.m: UIKit implementation (~320 LOC)
- CADisplayLink render loop
- Touch event queue with @synchronized
- Targets: ARM64 (device), ARM64 simulator

Build System:
- WASM: zig build wasm
- Android: zig build android / android-x86
- iOS: zig build ios / ios-sim
- Conditional compilation for platform detection

Documentation:
- docs/MOBILE_WEB_BACKENDS.md: Comprehensive guide (~400 lines)
- Updated DEVELOPMENT_PLAN.md with FASE 10
- Updated CLAUDE.md with new commands

Stats: 3 backends, ~1500 new LOC, cross-platform ready

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 18:20:13 +01:00

183 lines
4.7 KiB
Zig

//! WASM Demo - zcatgui running in a web browser
//!
//! Build with:
//! zig build wasm-demo
//!
//! Then serve the `web/` directory and open index.html
const std = @import("std");
const zcatgui = @import("zcatgui");
// Use WASM allocator
const allocator = std.heap.wasm_allocator;
// Global state (since WASM is single-threaded)
var ctx: ?*zcatgui.Context = null;
var backend: ?*WasmBackend = null;
var running: bool = true;
// Demo state
var counter: i32 = 0;
var checkbox_checked: bool = false;
var slider_value: f32 = 0.5;
var text_buffer: [256]u8 = [_]u8{0} ** 256;
var text_len: usize = 0;
const WasmBackend = zcatgui.backend.wasm.WasmBackend;
/// Called once at startup
export fn wasm_main() void {
init() catch |err| {
zcatgui.backend.wasm.log("Init error: {}", .{err});
};
}
/// Called every frame
export fn wasm_frame() void {
if (ctx) |c| {
frame(c) catch |err| {
zcatgui.backend.wasm.log("Frame error: {}", .{err});
};
}
}
fn init() !void {
// Initialize backend
const be = try allocator.create(WasmBackend);
be.* = try WasmBackend.init(800, 600);
backend = be;
// Initialize context
const c = try allocator.create(zcatgui.Context);
c.* = try zcatgui.Context.init(allocator, 800, 600);
ctx = c;
zcatgui.backend.wasm.log("zcatgui WASM initialized!", .{});
}
fn frame(c: *zcatgui.Context) !void {
const be = backend.?;
// Process events
while (be.backend().pollEvent()) |event| {
switch (event) {
.quit => running = false,
.key => |k| c.input.handleKeyEvent(k),
.mouse => |m| {
// Update mouse position
c.input.setMousePos(m.x, m.y);
// Update mouse buttons
if (m.button) |btn| {
c.input.setMouseButton(btn, m.pressed);
}
// Update scroll
if (m.scroll_x != 0 or m.scroll_y != 0) {
c.input.addScroll(m.scroll_x, m.scroll_y);
}
},
.resize => |r| {
// Handle resize
_ = r;
},
.text_input => |t| {
// Handle text input
if (text_len < text_buffer.len - 1) {
const slice = t.text[0..t.len];
for (slice) |char| {
if (text_len < text_buffer.len - 1) {
text_buffer[text_len] = char;
text_len += 1;
}
}
}
},
}
}
// Begin frame
c.beginFrame();
// Set theme
const theme = zcatgui.Style.Theme.dark;
// Title
c.layout.row_height = 40;
zcatgui.labelEx(c, "zcatgui WASM Demo", .{
.alignment = .center,
.color = theme.primary,
});
c.layout.row_height = 20;
zcatgui.label(c, "Running in WebAssembly!");
// Spacing
c.layout.row_height = 10;
zcatgui.label(c, "");
// Counter section
c.layout.row_height = 32;
var buf: [64]u8 = undefined;
const counter_text = std.fmt.bufPrint(&buf, "Counter: {d}", .{counter}) catch "Counter: ?";
zcatgui.label(c, counter_text);
// Buttons
if (zcatgui.button(c, "Increment")) {
counter += 1;
}
if (zcatgui.button(c, "Decrement")) {
counter -= 1;
}
if (zcatgui.button(c, "Reset")) {
counter = 0;
}
// Checkbox
c.layout.row_height = 10;
zcatgui.label(c, "");
c.layout.row_height = 32;
if (zcatgui.checkbox(c, &checkbox_checked, "Enable feature")) {
// Checkbox changed
}
// Progress bar
c.layout.row_height = 10;
zcatgui.label(c, "");
c.layout.row_height = 32;
var slider_buf: [32]u8 = undefined;
const slider_label = std.fmt.bufPrint(&slider_buf, "Progress: {d:.0}%", .{slider_value * 100}) catch "Progress: ?";
zcatgui.label(c, slider_label);
// Progress bar showing value
_ = zcatgui.widgets.progress.bar(c, slider_value);
// Info
c.layout.row_height = 10;
zcatgui.label(c, "");
c.layout.row_height = 24;
zcatgui.labelEx(c, "Press keys to type, Tab to navigate", .{
.alignment = .center,
.color = zcatgui.Color.rgb(128, 128, 128),
});
// End frame
c.endFrame();
// Render
var fb = zcatgui.render.Framebuffer.init(allocator, 800, 600) catch return;
defer fb.deinit();
// Clear with background color
fb.clear(theme.background);
// Execute draw commands
var renderer = zcatgui.render.SoftwareRenderer.init(&fb);
renderer.executeAll(c.commands.items);
// Present to canvas
be.backend().present(&fb);
}