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>
183 lines
4.7 KiB
Zig
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);
|
|
}
|