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>
196 lines
5 KiB
Zig
196 lines
5 KiB
Zig
//! Android Demo - zcatgui running on Android
|
|
//!
|
|
//! Build with:
|
|
//! zig build android-demo -Dtarget=aarch64-linux-android
|
|
//!
|
|
//! This creates a native Android activity that runs the zcatgui demo.
|
|
|
|
const std = @import("std");
|
|
const zcatgui = @import("zcatgui");
|
|
|
|
const AndroidBackend = zcatgui.backend.android.AndroidBackend;
|
|
const log = zcatgui.backend.android.log;
|
|
|
|
// Global state (Android native activities are single-instance)
|
|
var ctx: ?*zcatgui.Context = null;
|
|
var allocator: std.mem.Allocator = undefined;
|
|
|
|
// Demo state
|
|
var counter: i32 = 0;
|
|
var checkbox_checked: bool = false;
|
|
var slider_value: f32 = 0.5;
|
|
|
|
/// Main loop - called from a separate thread
|
|
fn mainLoop() void {
|
|
allocator = std.heap.page_allocator;
|
|
|
|
// Wait for window to be ready
|
|
while (!zcatgui.backend.android.isRunning()) {
|
|
std.Thread.sleep(10 * std.time.ns_per_ms);
|
|
}
|
|
|
|
const size = zcatgui.backend.android.getWindowSize();
|
|
if (size.width == 0 or size.height == 0) {
|
|
log("Invalid window size", .{});
|
|
return;
|
|
}
|
|
|
|
// Initialize context
|
|
const c = allocator.create(zcatgui.Context) catch {
|
|
log("Failed to allocate context", .{});
|
|
return;
|
|
};
|
|
c.* = zcatgui.Context.init(allocator, size.width, size.height) catch {
|
|
log("Failed to init context", .{});
|
|
allocator.destroy(c);
|
|
return;
|
|
};
|
|
ctx = c;
|
|
|
|
log("zcatgui Android initialized: {}x{}", .{ size.width, size.height });
|
|
|
|
// Main loop
|
|
while (zcatgui.backend.android.isRunning()) {
|
|
frame() catch |err| {
|
|
log("Frame error: {}", .{err});
|
|
break;
|
|
};
|
|
|
|
// ~60 FPS
|
|
std.Thread.sleep(16 * std.time.ns_per_ms);
|
|
}
|
|
|
|
// Cleanup
|
|
if (ctx) |context| {
|
|
context.deinit();
|
|
allocator.destroy(context);
|
|
ctx = null;
|
|
}
|
|
|
|
log("zcatgui Android shutdown", .{});
|
|
}
|
|
|
|
fn frame() !void {
|
|
const c = ctx orelse return;
|
|
const be = zcatgui.backend.android.getBackend() orelse return;
|
|
|
|
// Get current size (may have changed due to rotation)
|
|
const size = zcatgui.backend.android.getWindowSize();
|
|
if (size.width == 0 or size.height == 0) return;
|
|
|
|
// Process events
|
|
while (be.backend().pollEvent()) |event| {
|
|
switch (event) {
|
|
.quit => {
|
|
be.running = false;
|
|
return;
|
|
},
|
|
.key => |k| c.input.handleKeyEvent(k),
|
|
.mouse => |m| {
|
|
c.input.setMousePos(m.x, m.y);
|
|
if (m.button) |btn| {
|
|
c.input.setMouseButton(btn, m.pressed);
|
|
}
|
|
if (m.scroll_x != 0 or m.scroll_y != 0) {
|
|
c.input.addScroll(m.scroll_x, m.scroll_y);
|
|
}
|
|
},
|
|
.resize => |r| {
|
|
// Handle resize (screen rotation)
|
|
_ = r;
|
|
},
|
|
.text_input => {},
|
|
}
|
|
}
|
|
|
|
// Begin frame
|
|
c.beginFrame();
|
|
|
|
// Set theme (use high contrast for mobile)
|
|
const theme = zcatgui.Style.Theme.dark;
|
|
|
|
// Large touch-friendly UI
|
|
c.layout.row_height = 60;
|
|
|
|
// Title
|
|
zcatgui.labelEx(c, "zcatgui Android Demo", .{
|
|
.alignment = .center,
|
|
.color = theme.primary,
|
|
});
|
|
|
|
c.layout.row_height = 40;
|
|
zcatgui.label(c, "Touch to interact!");
|
|
|
|
// Spacing
|
|
c.layout.row_height = 20;
|
|
zcatgui.label(c, "");
|
|
|
|
// Counter section
|
|
c.layout.row_height = 50;
|
|
|
|
var buf: [64]u8 = undefined;
|
|
const counter_text = std.fmt.bufPrint(&buf, "Counter: {d}", .{counter}) catch "Counter: ?";
|
|
zcatgui.label(c, counter_text);
|
|
|
|
// Large buttons for touch
|
|
c.layout.row_height = 80;
|
|
|
|
if (zcatgui.button(c, "+")) {
|
|
counter += 1;
|
|
}
|
|
|
|
if (zcatgui.button(c, "-")) {
|
|
counter -= 1;
|
|
}
|
|
|
|
if (zcatgui.button(c, "Reset")) {
|
|
counter = 0;
|
|
}
|
|
|
|
// Checkbox
|
|
c.layout.row_height = 20;
|
|
zcatgui.label(c, "");
|
|
c.layout.row_height = 60;
|
|
|
|
if (zcatgui.checkbox(c, &checkbox_checked, "Enable feature")) {
|
|
// Checkbox changed
|
|
}
|
|
|
|
// Progress bar
|
|
c.layout.row_height = 20;
|
|
zcatgui.label(c, "");
|
|
c.layout.row_height = 40;
|
|
|
|
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);
|
|
|
|
c.layout.row_height = 30;
|
|
_ = zcatgui.widgets.progress.bar(c, slider_value);
|
|
|
|
// End frame
|
|
c.endFrame();
|
|
|
|
// Render
|
|
var fb = try zcatgui.render.Framebuffer.init(allocator, size.width, size.height);
|
|
defer fb.deinit();
|
|
|
|
fb.clear(theme.background);
|
|
|
|
var renderer = zcatgui.render.SoftwareRenderer.init(&fb);
|
|
renderer.executeAll(c.commands.items);
|
|
|
|
// Present to screen
|
|
be.backend().present(&fb);
|
|
}
|
|
|
|
// Thread entry point - Android's main thread handles UI, we run zcatgui in background
|
|
export fn android_main() void {
|
|
mainLoop();
|
|
}
|
|
|
|
// Alternative: Use native activity thread directly (for simpler apps)
|
|
comptime {
|
|
// Ensure ANativeActivity_onCreate is exported from android.zig
|
|
_ = zcatgui.backend.android;
|
|
}
|