From 03ae8eccb61da627b058865b519a48da5a153eed Mon Sep 17 00:00:00 2001 From: Roman Pytkov Date: Wed, 10 Dec 2025 02:26:06 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BA=D0=B0=D0=BA=D0=B8=D0=B5=20=D1=82=D0=BE?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B4=D0=B2=D0=B8=D0=B6=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/WindowData.zig | 11 +++ src/main.zig | 164 ++++++++++++++++++++++++--------------------- 2 files changed, 99 insertions(+), 76 deletions(-) create mode 100644 src/WindowData.zig diff --git a/src/WindowData.zig b/src/WindowData.zig new file mode 100644 index 0000000..67006c3 --- /dev/null +++ b/src/WindowData.zig @@ -0,0 +1,11 @@ +const std = @import("std"); + +const WindowData = @This(); + +frame: u64, + +pub fn init() WindowData { + return WindowData{ + .frame = 0, + }; +} diff --git a/src/main.zig b/src/main.zig index fbe26b7..aff09bd 100644 --- a/src/main.zig +++ b/src/main.zig @@ -2,12 +2,52 @@ 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(); @@ -21,6 +61,13 @@ const WindowContext = struct { 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 { @@ -46,12 +93,10 @@ pub fn main() !void { } var next_window_id: usize = 1; - const first_ctx = try createWindow(allocator, next_window_id); + const first_ctx = try WindowContext.init(allocator, next_window_id); next_window_id += 1; try windows.append(allocator, first_ctx); - var interrupted = false; - main_loop: while (true) { if (windows.items.len == 0) break :main_loop; @@ -59,18 +104,22 @@ pub fn main() !void { var min_wait_event_micros: u32 = std.math.maxInt(u32); var idx: usize = 0; - while (idx < windows.items.len) { + while (saw_events and idx < windows.items.len) { const ctx = windows.items[idx]; - // beginWait coordinates with waitTime below to run frames only when needed - const nstime = ctx.window.beginWait(interrupted); - // marks the beginning of a frame for dvui, can call dvui functions after this + 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); - // if dvui widgets might not cover the whole window, then need to clear - // the previous frame's render - _ = SDLBackend.c.SDL_SetRenderDrawColor(ctx.backend.renderer, 0, 0, 0, 255); - _ = SDLBackend.c.SDL_RenderClear(ctx.backend.renderer); + _ = 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) { @@ -78,9 +127,7 @@ pub fn main() !void { continue; } - // marks end of dvui frame, don't call dvui functions after this - // - sends all dvui stuff to backend for rendering, must be called before renderPresent() - const end_micros = try ctx.window.end(.{}); + const end = try ctx.window.end(.{}); // cursor management try ctx.backend.setCursor(ctx.window.cursorRequested()); @@ -89,50 +136,23 @@ pub fn main() !void { // render frame to OS try ctx.backend.renderPresent(); - // waitTime and beginWait combine to achieve variable framerates - const wait_event_micros = ctx.window.waitTime(end_micros); - min_wait_event_micros = @min(min_wait_event_micros, wait_event_micros); + 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; - - interrupted = saw_events or try windows.items[0].backend.waitEventTimeout(min_wait_event_micros); } } -fn createWindow(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); - - 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(); - - return ctx; -} - fn closeWindow(windows: *std.ArrayList(*WindowContext), allocator: std.mem.Allocator, idx: usize) void { const last = windows.items.len == 1; const ctx = windows.swapRemove(idx); @@ -140,48 +160,38 @@ fn closeWindow(windows: *std.ArrayList(*WindowContext), allocator: std.mem.Alloc allocator.destroy(ctx); } -fn destroyBackendKeepingSDL(backend: *SDLBackend) void { - SDLBackend.c.SDL_DestroyRenderer(backend.renderer); - SDLBackend.c.SDL_DestroyWindow(backend.window); - backend.we_own_window = false; - backend.deinit(); -} - -fn windowId(ctx: *WindowContext) u32 { - return @intCast(SDLBackend.c.SDL_GetWindowID(ctx.backend.window)); -} - -fn eventWindowId(event: SDLBackend.c.SDL_Event) ?u32 { +fn eventWindowId(event: sdl_c.SDL_Event) ?u32 { return switch (event.type) { - if (SDLBackend.sdl3) SDLBackend.c.SDL_EVENT_KEY_DOWN else SDLBackend.c.SDL_KEYDOWN => @intCast(event.key.windowID), - if (SDLBackend.sdl3) SDLBackend.c.SDL_EVENT_KEY_UP else SDLBackend.c.SDL_KEYUP => @intCast(event.key.windowID), - if (SDLBackend.sdl3) SDLBackend.c.SDL_EVENT_TEXT_INPUT else SDLBackend.c.SDL_TEXTINPUT => @intCast(event.text.windowID), - if (SDLBackend.sdl3) SDLBackend.c.SDL_EVENT_TEXT_EDITING else SDLBackend.c.SDL_TEXTEDITING => @intCast(event.edit.windowID), - if (SDLBackend.sdl3) SDLBackend.c.SDL_EVENT_MOUSE_MOTION else SDLBackend.c.SDL_MOUSEMOTION => @intCast(event.motion.windowID), - if (SDLBackend.sdl3) SDLBackend.c.SDL_EVENT_MOUSE_BUTTON_DOWN else SDLBackend.c.SDL_MOUSEBUTTONDOWN => @intCast(event.button.windowID), - if (SDLBackend.sdl3) SDLBackend.c.SDL_EVENT_MOUSE_BUTTON_UP else SDLBackend.c.SDL_MOUSEBUTTONUP => @intCast(event.button.windowID), - if (SDLBackend.sdl3) SDLBackend.c.SDL_EVENT_MOUSE_WHEEL else SDLBackend.c.SDL_MOUSEWHEEL => @intCast(event.wheel.windowID), - if (SDLBackend.sdl3) SDLBackend.c.SDL_EVENT_WINDOW_CLOSE_REQUESTED else SDLBackend.c.SDL_WINDOWEVENT => @intCast(event.window.windowID), + 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: SDLBackend.c.SDL_Event = undefined; + var event: sdl_c.SDL_Event = undefined; const poll_got_event = if (SDLBackend.sdl3) true else 1; var saw_event = false; - while (SDLBackend.c.SDL_PollEvent(&event) == poll_got_event) { + while (sdl_c.SDL_PollEvent(&event) == poll_got_event) { saw_event = true; if (eventWindowId(event)) |wid| { for (windows.items) |ctx| { - if (windowId(ctx) == wid) { + 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); @@ -195,7 +205,6 @@ fn pumpEvents(windows: *std.ArrayList(*WindowContext)) !bool { 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; - // Treat SDL_QUIT as a global request; ignore here so other windows keep running. } var root = dvui.box(@src(), .{ .dir = .vertical }, .{ .expand = .both, .padding = dvui.Rect.all(12), .background = true, .style = .window }); @@ -203,11 +212,14 @@ fn gui_frame(ctx: *WindowContext, allocator: std.mem.Allocator, windows: *std.Ar 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 createWindow(allocator, id); + const new_ctx = try WindowContext.init(allocator, id); try windows.append(allocator, new_ctx); }