const std = @import("std"); const builtin = @import("builtin"); const dvui = @import("dvui"); const SDLBackend = @import("sdl-backend"); const sdl_c = SDLBackend.c; const WindowData = @import("WindowData.zig"); const dbprint = std.debug.print; const WindowContext = struct { backend: SDLBackend, backend_id: u32, window: dvui.Window, title: [:0]u8, id: usize, data: WindowData, fn init(allocator: std.mem.Allocator, id: usize) !*WindowContext { var ctx = try allocator.create(WindowContext); errdefer allocator.destroy(ctx); ctx.id = id; const title_bytes = try std.fmt.allocPrint(allocator, "My DVUI App #{d}", .{id}); defer allocator.free(title_bytes); ctx.title = try allocator.allocSentinel(u8, title_bytes.len, 0); @memcpy(ctx.title[0..title_bytes.len], title_bytes); ctx.backend = try SDLBackend.initWindow(.{ .allocator = allocator, .size = .{ .w = 800.0, .h = 600.0 }, .title = ctx.title, .vsync = true, }); errdefer destroyBackendKeepingSDL(&ctx.backend); ctx.backend_id = @intCast(sdl_c.SDL_GetWindowID(ctx.backend.window)); const theme = switch (ctx.backend.preferredColorScheme() orelse .light) { .light => dvui.Theme.builtin.adwaita_light, .dark => dvui.Theme.builtin.adwaita_dark, }; ctx.window = try dvui.Window.init(@src(), allocator, ctx.backend.backend(), .{ .theme = theme }); errdefer ctx.window.deinit(); ctx.data = WindowData.init(); return ctx; } fn deinit(self: *WindowContext, allocator: std.mem.Allocator, last_backend: bool) void { self.window.deinit(); if (last_backend) { self.backend.deinit(); } else { destroyBackendKeepingSDL(&self.backend); } allocator.free(self.title); self.* = undefined; } fn destroyBackendKeepingSDL(backend: *SDLBackend) void { sdl_c.SDL_DestroyRenderer(backend.renderer); sdl_c.SDL_DestroyWindow(backend.window); backend.we_own_window = false; backend.deinit(); } }; pub fn main() !void { if (@import("builtin").os.tag == .windows) { // on windows graphical apps have no console, so output goes to nowhere - attach it manually. related: https://github.com/ziglang/zig/issues/4196 dvui.Backend.Common.windowsAttachConsole() catch {}; } SDLBackend.enableSDLLogging(); std.log.info("SDL version: {f}", .{SDLBackend.getSDLVersion()}); var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); const allocator = gpa.allocator(); var windows = try std.ArrayList(*WindowContext).initCapacity(allocator, 0); defer { for (windows.items, 0..) |ctx, i| { const last = i + 1 == windows.items.len; ctx.deinit(allocator, last); allocator.destroy(ctx); } windows.deinit(allocator); } var next_window_id: usize = 1; const first_ctx = try WindowContext.init(allocator, next_window_id); next_window_id += 1; try windows.append(allocator, first_ctx); main_loop: while (true) { if (windows.items.len == 0) break :main_loop; const saw_events = try pumpEvents(&windows); var min_wait_event_micros: u32 = std.math.maxInt(u32); var idx: usize = 0; while (saw_events and idx < windows.items.len) { const ctx = windows.items[idx]; const flags = sdl_c.SDL_GetWindowFlags(ctx.backend.window); const occluded = (flags & (sdl_c.SDL_WINDOW_HIDDEN | sdl_c.SDL_WINDOW_MINIMIZED | sdl_c.SDL_WINDOW_OCCLUDED)) != 0; if (occluded) { idx += 1; continue; } const nstime = @max(ctx.window.frame_time_ns, ctx.backend.nanoTime()); try ctx.window.begin(nstime); _ = sdl_c.SDL_SetRenderDrawColor(ctx.backend.renderer, 0, 0, 0, 255); _ = sdl_c.SDL_RenderClear(ctx.backend.renderer); const keep_open = try gui_frame(ctx, allocator, &windows, &next_window_id); if (!keep_open) { closeWindow(&windows, allocator, idx); continue; } const end = try ctx.window.end(.{}); // cursor management try ctx.backend.setCursor(ctx.window.cursorRequested()); try ctx.backend.textInputRect(ctx.window.textInputRequested()); // render frame to OS try ctx.backend.renderPresent(); const sleep_time = ctx.window.waitTime(end); min_wait_event_micros = @min(min_wait_event_micros, sleep_time); idx += 1; } if (min_wait_event_micros != std.math.maxInt(u32)) { min_wait_event_micros = @max(min_wait_event_micros, 1 / 120 * std.time.us_per_s); std.Thread.sleep(min_wait_event_micros * std.time.ns_per_us); } else { std.Thread.sleep(1 / 60 * std.time.ns_per_s); } if (windows.items.len == 0) break :main_loop; } } fn closeWindow(windows: *std.ArrayList(*WindowContext), allocator: std.mem.Allocator, idx: usize) void { const last = windows.items.len == 1; const ctx = windows.swapRemove(idx); ctx.deinit(allocator, last); allocator.destroy(ctx); } fn eventWindowId(event: sdl_c.SDL_Event) ?u32 { return switch (event.type) { sdl_c.SDL_EVENT_KEY_DOWN => @intCast(event.key.windowID), sdl_c.SDL_EVENT_KEY_UP => @intCast(event.key.windowID), sdl_c.SDL_EVENT_TEXT_INPUT => @intCast(event.text.windowID), sdl_c.SDL_EVENT_TEXT_EDITING => @intCast(event.edit.windowID), sdl_c.SDL_EVENT_MOUSE_MOTION => @intCast(event.motion.windowID), sdl_c.SDL_EVENT_MOUSE_BUTTON_DOWN => @intCast(event.button.windowID), sdl_c.SDL_EVENT_MOUSE_BUTTON_UP => @intCast(event.button.windowID), sdl_c.SDL_EVENT_MOUSE_WHEEL => @intCast(event.wheel.windowID), sdl_c.SDL_EVENT_WINDOW_FIRST...sdl_c.SDL_EVENT_WINDOW_LAST => @intCast(event.window.windowID), else => null, }; } fn pumpEvents(windows: *std.ArrayList(*WindowContext)) !bool { var event: sdl_c.SDL_Event = undefined; const poll_got_event = if (SDLBackend.sdl3) true else 1; var saw_event = false; while (sdl_c.SDL_PollEvent(&event) == poll_got_event) { saw_event = true; if (eventWindowId(event)) |wid| { for (windows.items) |ctx| { if (ctx.backend_id == wid) { _ = try ctx.backend.addEvent(&ctx.window, event); break; } } } else { //std.debug.print("null with: {any}\n", .{event.type}); // broadcast events without a window target (like SDL_QUIT) for (windows.items) |ctx| { _ = try ctx.backend.addEvent(&ctx.window, event); } } } return saw_event; } fn gui_frame(ctx: *WindowContext, allocator: std.mem.Allocator, windows: *std.ArrayList(*WindowContext), next_window_id: *usize) !bool { for (ctx.window.events.items) |*e| { if (e.evt == .window and e.evt.window.action == .close) return false; } var root = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .both, .padding = dvui.Rect.all(12), .background = true, .style = .window }); defer root.deinit(); dvui.label(@src(), "Window #{d}", .{ctx.id}, .{ .font_style = .title_2 }); dvui.label(@src(), "Open windows: {d}", .{windows.items.len}, .{}); dvui.label(@src(), "Frame {d}, fps: {d}", .{ ctx.data.frame, dvui.FPS() }, .{}); ctx.data.frame += 1; if (dvui.button(@src(), "New window", .{}, .{})) { const id = next_window_id.*; next_window_id.* += 1; const new_ctx = try WindowContext.init(allocator, id); try windows.append(allocator, new_ctx); } return true; }