lib/windowing/src/window.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const std = @import("std");
  2 const sys = @import("sys");
  3 const clipboard = @import("clipboard.zig");
  4 const EventIterator = @import("event.zig").EventIterator;
  5 const WindowConfig = @import("config.zig").WindowConfig;
  6 const Backend = @import("backend.zig").Backend;
  7 const input = @import("input.zig");
  8 const Key = @import("key.zig").Key;
  9 const MouseButton = @import("mouse.zig").MouseButton;
 10 const Modifier = @import("modifier.zig").Modifier;
 11 const CursorShape = @import("cursor.zig").CursorShape;
 12 const gamepads = @import("gamepad.zig");
 13 const presentation = @import("presentation.zig");
 14 const surface = @import("surface.zig");
 15 const WaylandStorageStatus = @import("wayland").runtime.StorageStatus;
 16 const WaylandDmabuf = @import("wayland/root.zig").DmabufSurface;
 17 
 18 pub const Size = struct {
 19     width: u32,
 20     height: u32,
 21 };
 22 
 23 pub const Scale = struct {
 24     x: f32,
 25     y: f32,
 26 };
 27 
 28 pub const EventSource = struct {
 29     descriptor: sys.fd.Descriptor,
 30     readable: bool = true,
 31     writable: bool = false,
 32 };
 33 
 34 pub const PresentRegion = struct {
 35     x: u32 = 0,
 36     y: u32 = 0,
 37     width: u32 = 0,
 38     height: u32 = 0,
 39 
 40     pub fn full(width: u32, height: u32) PresentRegion {
 41         return .{ .width = width, .height = height };
 42     }
 43 
 44     pub fn pixelCount(self: PresentRegion) usize {
 45         return @as(usize, self.width) * @as(usize, self.height);
 46     }
 47 
 48     pub fn clamped(self: PresentRegion, width: u32, height: u32) PresentRegion {
 49         if (self.x >= width or self.y >= height) return .{ .x = self.x, .y = self.y };
 50         return .{
 51             .x = self.x,
 52             .y = self.y,
 53             .width = @min(self.width, width - self.x),
 54             .height = @min(self.height, height - self.y),
 55         };
 56     }
 57 };
 58 
 59 pub const CreateError = error{
 60     ConnectionFailed,
 61     InvalidCapacity,
 62     WindowCreationFailed,
 63     UnsupportedPlatform,
 64     OutOfMemory,
 65 };
 66 
 67 pub const PollError = error{
 68     ConnectionLost,
 69     PresentationFailed,
 70 };
 71 
 72 pub const PresentError = error{
 73     UnsupportedPlatform,
 74     UnsupportedFormat,
 75     CapacityExceeded,
 76     Unexpected,
 77     OutOfMemory,
 78     /// The Wayland surface presents dma-bufs with explicit synchronization, and every commit of
 79     /// it must carry sync points that a shared-memory frame lacks.
 80     ExplicitSyncSurface,
 81 };
 82 
 83 pub const Window = struct {
 84     allocator: std.mem.Allocator,
 85     backend: *Backend,
 86 
 87     pub fn create(allocator: std.mem.Allocator, config: WindowConfig) CreateError!Window {
 88         const backend = allocator.create(Backend) catch return error.OutOfMemory;
 89         errdefer allocator.destroy(backend);
 90         try backend.init(allocator, config);
 91         return .{ .allocator = allocator, .backend = backend };
 92     }
 93 
 94     pub fn destroy(self: *Window) void {
 95         self.backend.deinit();
 96         self.allocator.destroy(self.backend);
 97         self.* = undefined;
 98     }
 99 
100     pub fn pollEvents(self: *Window) PollError!EventIterator {
101         return self.backend.pollEvents();
102     }
103 
104     pub fn droppedEventCount(self: *const Window) u64 {
105         return self.backend.droppedEventCount();
106     }
107 
108     pub fn eventSource(self: *const Window) ?EventSource {
109         return self.backend.eventSource();
110     }
111 
112     pub fn shouldClose(self: *const Window) bool {
113         return self.backend.shouldClose();
114     }
115 
116     pub fn getSize(self: *const Window) Size {
117         return self.backend.getSize();
118     }
119 
120     pub fn getFramebufferSize(self: *const Window) Size {
121         return self.backend.getFramebufferSize();
122     }
123 
124     pub fn getDrawArea(self: *const Window) sys.desktop.DrawArea {
125         return sizeToDrawArea(self.getSize());
126     }
127 
128     pub fn getFramebufferArea(self: *const Window) sys.desktop.DrawArea {
129         return sizeToDrawArea(self.getFramebufferSize());
130     }
131 
132     pub fn getNativeSurface(self: *Window) surface.Surface {
133         return self.backend.nativeSurface();
134     }
135 
136     pub fn getScaleEvidence(self: *const Window) sys.desktop.WindowScaleEvidence {
137         return scaleEvidenceFromSizes(self.getSize(), self.getFramebufferSize(), self.getContentScale());
138     }
139 
140     pub fn presentRgba8(
141         self: *Window,
142         rgba8: []const u8,
143         width: u32,
144         height: u32,
145     ) PresentError!void {
146         return self.backend.presentRgba8(rgba8, width, height);
147     }
148 
149     pub fn presentRgba8Region(
150         self: *Window,
151         rgba8: []const u8,
152         width: u32,
153         height: u32,
154         region: PresentRegion,
155     ) PresentError!void {
156         return self.backend.presentRgba8Region(rgba8, width, height, region);
157     }
158 
159     pub fn getContentScale(self: *const Window) Scale {
160         return self.backend.getContentScale();
161     }
162 
163     pub fn isKeyDown(self: *const Window, key: Key) bool {
164         return self.backend.inputState().isKeyDown(key);
165     }
166 
167     pub fn isKeyPressed(self: *const Window, key: Key) bool {
168         return self.backend.inputState().isKeyPressed(key);
169     }
170 
171     pub fn isKeyReleased(self: *const Window, key: Key) bool {
172         return self.backend.inputState().isKeyReleased(key);
173     }
174 
175     pub fn nextPressedKey(self: *Window) ?Key {
176         return self.backend.inputStateMut().nextPressedKey();
177     }
178 
179     pub fn modifiers(self: *const Window) Modifier {
180         return self.backend.modifiers();
181     }
182 
183     pub fn isMouseButtonDown(self: *const Window, button: MouseButton) bool {
184         return self.backend.inputState().isMouseButtonDown(button);
185     }
186 
187     pub fn isMouseButtonPressed(self: *const Window, button: MouseButton) bool {
188         return self.backend.inputState().isMouseButtonPressed(button);
189     }
190 
191     pub fn isMouseButtonReleased(self: *const Window, button: MouseButton) bool {
192         return self.backend.inputState().isMouseButtonReleased(button);
193     }
194 
195     pub fn getMousePosition(self: *const Window) input.Point {
196         return self.backend.inputState().mouse_position;
197     }
198 
199     pub fn getMouseDelta(self: *const Window) input.Point {
200         return self.backend.inputState().mouse_delta;
201     }
202 
203     pub fn getMouseWheel(self: *const Window) input.Wheel {
204         return self.backend.inputState().mouse_wheel;
205     }
206 
207     pub fn nextTextCodepoint(self: *Window) ?u21 {
208         return self.backend.inputStateMut().nextTextCodepoint();
209     }
210 
211     pub fn inputStatus(self: *const Window) input.Status {
212         return self.backend.inputState().status();
213     }
214 
215     pub fn setCursor(self: *Window, shape: CursorShape) void {
216         self.backend.setCursor(shape);
217     }
218 
219     pub fn isCursorOnScreen(self: *const Window) bool {
220         return self.backend.isCursorOnScreen();
221     }
222 
223     pub fn setTitle(self: *Window, title: [:0]const u8) void {
224         self.backend.setTitle(title);
225     }
226 
227     pub fn setMinSize(self: *Window, width: u32, height: u32) void {
228         self.backend.setMinSize(width, height);
229     }
230 
231     pub fn minimize(self: *Window) void {
232         self.backend.minimize();
233     }
234 
235     pub fn maximize(self: *Window) void {
236         self.backend.maximize();
237     }
238 
239     pub fn restore(self: *Window) void {
240         self.backend.restore();
241     }
242 
243     pub fn isMaximized(self: *Window) bool {
244         return self.backend.isMaximized();
245     }
246 
247     pub fn toggleMaximize(self: *Window) void {
248         if (self.isMaximized()) {
249             self.restore();
250         } else {
251             self.maximize();
252         }
253     }
254 
255     pub fn getClipboardTextAlloc(self: *Window, allocator: std.mem.Allocator) !?[]u8 {
256         return self.backend.getClipboardTextAlloc(allocator);
257     }
258 
259     pub fn setClipboardText(self: *Window, text: []const u8) clipboard.PublishError!void {
260         return self.backend.setClipboardText(text);
261     }
262 
263     pub fn clipboardStatus(self: *const Window) clipboard.Status {
264         return self.backend.clipboardStatus();
265     }
266 
267     pub fn x11StorageStatus(self: *const Window) sys.x11.Status {
268         return self.backend.x11StorageStatus();
269     }
270 
271     /// The window's dma-buf presentation with explicit synchronization, when it is a Wayland
272     /// window and the compositor offers `zwp_linux_dmabuf_v1` 4 or later and
273     /// `wp_linux_drm_syncobj_manager_v1`. Its first commit makes shared-memory presents of the
274     /// window fail with `ExplicitSyncSurface`.
275     pub fn waylandDmabuf(self: *Window) ?WaylandDmabuf {
276         return self.backend.waylandDmabuf();
277     }
278 
279     pub fn waylandStorageStatus(self: *const Window) WaylandStorageStatus {
280         return self.backend.waylandStorageStatus();
281     }
282 
283     pub fn nextPresentationOutcome(self: *Window) ?presentation.Outcome {
284         return self.backend.nextPresentationOutcome();
285     }
286 
287     pub fn presentationStatus(self: *const Window) presentation.Status {
288         return self.backend.presentationStatus();
289     }
290 
291     pub fn presentationStorageStatus(self: *const Window) presentation.StorageStatus {
292         return self.backend.presentationStorageStatus();
293     }
294 
295     pub fn gamepadCount(self: *const Window) usize {
296         return self.backend.gamepadState().connectedCount();
297     }
298 
299     pub fn gamepad(self: *const Window, index: usize) *const gamepads.Gamepad {
300         return self.backend.gamepadState().gamepad(index);
301     }
302 
303     pub fn gamepadStatus(self: *const Window) gamepads.Status {
304         return self.backend.gamepadState().status();
305     }
306 };
307 
308 pub fn scaleEvidenceFromSizes(size: Size, framebuffer: Size, content_scale: Scale) sys.desktop.WindowScaleEvidence {
309     return .{
310         .has_window_content_scale = true,
311         .window_display = usableScale(@max(content_scale.x, content_scale.y)),
312         .display_content = usableScale(@max(content_scale.x, content_scale.y)),
313         .pixel_density = sys.desktop.framebufferScaleFromDimensions(
314             @floatFromInt(size.width),
315             @floatFromInt(size.height),
316             @floatFromInt(framebuffer.width),
317             @floatFromInt(framebuffer.height),
318         ) orelse 1.0,
319     };
320 }
321 
322 fn sizeToDrawArea(size: Size) sys.desktop.DrawArea {
323     return .{
324         .width = u32ToI32(size.width),
325         .height = u32ToI32(size.height),
326     };
327 }
328 
329 fn u32ToI32(value: u32) i32 {
330     return if (value > @as(u32, @intCast(std.math.maxInt(i32)))) std.math.maxInt(i32) else @intCast(value);
331 }
332 
333 fn usableScale(scale: f32) f32 {
334     if (!std.math.isFinite(scale) or scale <= 0) return 1.0;
335     return scale;
336 }
337 
338 test "scale evidence derives pixel density from framebuffer dimensions" {
339     const evidence = scaleEvidenceFromSizes(
340         .{ .width = 640, .height = 480 },
341         .{ .width = 1280, .height = 960 },
342         .{ .x = 2, .y = 2 },
343     );
344     try std.testing.expect(evidence.has_window_content_scale);
345     try std.testing.expectEqual(@as(f32, 2), evidence.display_content);
346     try std.testing.expectEqual(@as(f32, 2), evidence.pixel_density);
347 }
348 
349 test "draw areas clamp to signed GUI dimensions" {
350     try std.testing.expectEqual(
351         sys.desktop.DrawArea{ .width = 10, .height = std.math.maxInt(i32) },
352         sizeToDrawArea(.{ .width = 10, .height = std.math.maxInt(u32) }),
353     );
354 }
355 
356 test "present region clamps to framebuffer bounds" {
357     try std.testing.expectEqual(
358         PresentRegion{ .x = 4, .y = 3, .width = 6, .height = 5 },
359         (PresentRegion{ .x = 4, .y = 3, .width = 20, .height = 20 }).clamped(10, 8),
360     );
361     try std.testing.expectEqual(
362         PresentRegion{ .x = 12, .y = 3 },
363         (PresentRegion{ .x = 12, .y = 3, .width = 2, .height = 2 }).clamped(10, 8),
364     );
365     try std.testing.expectEqual(@as(usize, 30), (PresentRegion{ .width = 6, .height = 5 }).pixelCount());
366 }