zcatgui/examples/android_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

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