├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── bench ├── color.zig ├── geometry.zig └── image.zig ├── build.zig ├── build.zig.zon ├── default.nix ├── dep ├── AngelCodeFont.zig └── wayland-protocols │ ├── fractional-scale-v1.xml │ ├── wayland.xml │ ├── xdg-decoration-unstable-v1.xml │ └── xdg-shell.xml ├── examples ├── assets │ ├── PressStart2P_8.fnt │ ├── PressStart2P_8.png │ ├── colormap.frag │ ├── colormap.frag.spv │ ├── cursor_none.tvg │ ├── monochrome.png │ ├── textures.frag │ ├── textures.frag.spv │ ├── textures.vert │ ├── textures.vert.spv │ ├── ui.png │ └── wedge.png ├── bicubic_filter.zig ├── bitmap_font.zig ├── blit.zig ├── canvas.zig ├── clear.zig ├── colormapped_image.zig ├── cutscene.zig ├── file_browser.zig ├── fill_rect.zig ├── multi_window.zig ├── scene.zig ├── sprite_batch.zig ├── texture_rect.zig ├── tinyvg.zig ├── ui_plot_sine.zig ├── ui_stage.zig └── ui_view_image.zig └── src ├── Canvas.zig ├── Canvas ├── Font.zig └── Transformed.zig ├── Display.zig ├── Display ├── Buffer.zig ├── Surface.zig ├── Swapchain.zig ├── ToplevelSurface.zig ├── Wayland.zig ├── cursor_none.tvg ├── evdev.zig ├── evdev_to_seizer.zig └── xkb_to_seizer.zig ├── color.zig ├── colormaps.zig ├── colormaps └── turbo_srgb.zig ├── geometry.zig ├── geometry ├── mat.zig ├── mat3.zig ├── mat4.zig └── vec.zig ├── image.zig ├── input.zig ├── input ├── gamecontrollerdb.txt ├── gamepad.zig ├── keyboard.zig └── mouse.zig ├── seizer.zig ├── ui.zig └── ui ├── Element.zig └── Element ├── Button.zig ├── FlexBox.zig ├── Frame.zig ├── Image.zig ├── Label.zig ├── PanZoom.zig ├── Plot.zig └── TextField.zig /.gitattributes: -------------------------------------------------------------------------------- 1 | *.wav filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .zig-cache/ 2 | zig-out/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 geemili 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # seizer 2 | 3 | `seizer` is a Zig library for making games and applications that target the desktop and browser. 4 | It exposes an OpenGL ES 3.0 rendering context. It is currently in an alpha state, and the APIs 5 | constantly break. 6 | 7 | ## Planned Features 8 | 9 | - [ ] Cross-platform Windowing 10 | - [ ] Linux (Wayland only, X11 is not planned at the moment, sorry) 11 | - [ ] Windows 12 | - [ ] Web (Firefox, Chrome) 13 | - [ ] MacOS 14 | - [ ] Input handling 15 | - [ ] Gamepad 16 | - [ ] Mouse 17 | - [ ] Keyboard 18 | - [ ] Touch 19 | - [ ] Hardware accelerated rendering 20 | - [ ] 2d sprite based rendering 21 | - [ ] shader effects 22 | - [ ] Multiple backends 23 | - [ ] WebGL 2.0/OpenGL ES 3.0 24 | - [ ] DirectX 25 | - [ ] Vulkan 26 | - [ ] a built-in by optional GUI library 27 | - [ ] specific device support 28 | - [ ] Steam Deck 29 | - [ ] Anbernic RG35XX H 30 | - [ ] Anbernic RG351M 31 | - [ ] Powkiddy RGB30 32 | 33 | ## FAQ 34 | 35 | > How do I run the examples on NixOS? 36 | 37 | Mostly included because NixOS is my daily driver OS. 38 | 39 | First, [make you have OpenGL enabled](https://nixos.wiki/wiki/OpenGL). 40 | 41 | Second, make sure that `libEGL` and `vulkan-loader` are available in `LD_LIBRARY_PATH`, [or `NIX_LD_LIBRARY_PATH` if you are using nix-ld](https://github.com/nix-community/nix-ld). 42 | 43 | And third, run the examples with an explicit target. For example: 44 | 45 | ``` 46 | zig build example-clear-run -Dtarget=x86_64-linux-gnu 47 | ``` 48 | 49 | Without the above step, Zig will use the native target. When targeting native Zig will use dynamic loader specific to NixOS, 50 | at some path like `/nix/store/r8qsxm85rlxzdac7988psm7gimg4dl3q-glibc-2.39-52/lib/ld-linux-x86-64.so.2`. Giving Zig the generic 51 | target we tell it to use the standard location of the dynamic loader, `/usr/lib64/ld-linux-x86-64.so.2`. 52 | 53 | You can also explicitly set the dynamic loader path: 54 | 55 | ``` 56 | zig build example-clear-run -Ddynamic-linker=/lib64/ld-linux-x86-64.so.2 57 | ``` 58 | 59 | > Why should I use `seizer` over SDL or GLFW? 60 | 61 | You probably shouldn't, at the moment. I'm using it for the following reasons: 62 | 63 | - I want to learn about low level details of the systems I'm deploying to! 64 | - I prefer using Zig. While I can use SDL and GLFW from Zig, it means diving into C code if I'm trying to figure out why something isn't working. 65 | - I use Wayland on Linux, and last I checked SDL2 and GLFW need to be configured to target Wayland instead of X11. 66 | - I want my games to run on gaming handhelds like the Anbernic RG35XX H. This requires learning the low level details, and if I'm relying on GLFW or SDL2, I would be forced to use C and its build systems. Not to mention that GLFW doesn't support systems without a window manager, which is not uncommon on retro gaming handhelds. 67 | 68 | But regardless of the personal insanity that drives me to make `seizer`, I would highly recommend [SDL2][] or [GLFW][] 69 | if you need something that is `stable` and `works`. 70 | 71 | > Why is it called "seizer"? 72 | 73 | It is a reference to the "Seizer Beam" from the game [Zero Wing][]. Move Zig! 74 | 75 | [zero wing]: https://en.wikipedia.org/wiki/Zero_Wing 76 | 77 | ## Inspiration 78 | 79 | `seizer` doesn't exist in a void and there are many projects that have come before it. In fact, `seizer` 80 | was originally a wrapper over [SDL2][], then over [GLFW][], and now is its own thing. 81 | 82 | - [SDL2][]: A C library for windowing and obtaining a OpenGL context. 83 | - [GLFW][]: A C library for windowing and obtaining a OpenGL context. 84 | - [dvui][]: A zig immediate mode UI. 85 | - [bgfx][]: It is not yet implemented, but I want to have a graphics abstraction similar to `bgfx`. OpenGL ES 3.0 is nice, but unless I want to spend time bundling [ANGLE][], it is not supported well on MacOS or Windows. 86 | 87 | [dvui]: https://github.com/david-vanderson/dvui 88 | [SDL2]: https://www.libsdl.org/ 89 | [GLFW]: https://www.glfw.org/ 90 | [bgfx]: https://github.com/bkaradzic/bgfx 91 | [ANGLE]: https://github.com/google/angle 92 | -------------------------------------------------------------------------------- /bench/color.zig: -------------------------------------------------------------------------------- 1 | var prng = std.Random.DefaultPrng.init(419621509410522679); 2 | 3 | const NUM_SAMPLES = 4096; 4 | var argb_dst_samples: [NUM_SAMPLES]argbf32_premultiplied = undefined; 5 | var argb_src_samples: [NUM_SAMPLES]argbf32_premultiplied = undefined; 6 | var argb8888_dst_samples: [NUM_SAMPLES]argb8 = undefined; 7 | var argb8888_src_samples: [NUM_SAMPLES]argb8 = undefined; 8 | 9 | var argb_dst_b: [NUM_SAMPLES]f32 = undefined; 10 | var argb_dst_g: [NUM_SAMPLES]f32 = undefined; 11 | var argb_dst_r: [NUM_SAMPLES]f32 = undefined; 12 | var argb_dst_a: [NUM_SAMPLES]f32 = undefined; 13 | 14 | var argb_src_b: [NUM_SAMPLES]f32 = undefined; 15 | var argb_src_g: [NUM_SAMPLES]f32 = undefined; 16 | var argb_src_r: [NUM_SAMPLES]f32 = undefined; 17 | var argb_src_a: [NUM_SAMPLES]f32 = undefined; 18 | 19 | pub fn main() !void { 20 | for (argb_dst_samples[0..], &argb_dst_b, &argb_dst_g, &argb_dst_r, &argb_dst_a) |*dst, *b, *g, *r, *a| { 21 | dst.* = seizer.color.argb(f32, .straight, f32).init( 22 | prng.random().float(f32), 23 | prng.random().float(f32), 24 | prng.random().float(f32), 25 | prng.random().float(f32), 26 | ).convertAlphaModelTo(.premultiplied); 27 | b.* = dst.b; 28 | g.* = dst.g; 29 | r.* = dst.r; 30 | a.* = dst.a; 31 | } 32 | for (argb_src_samples[0..], &argb_src_b, &argb_src_g, &argb_src_r, &argb_src_a) |*src, *b, *g, *r, *a| { 33 | src.* = seizer.color.argb(f32, .straight, f32).init( 34 | prng.random().float(f32), 35 | prng.random().float(f32), 36 | prng.random().float(f32), 37 | prng.random().float(f32), 38 | ).convertAlphaModelTo(.premultiplied); 39 | b.* = src.b; 40 | g.* = src.g; 41 | r.* = src.r; 42 | a.* = src.a; 43 | } 44 | for (argb8888_dst_samples[0..], argb_dst_samples[0..]) |*encoded, linear| { 45 | encoded.* = linear.convertColorTo(seizer.color.sRGB8).convertAlphaTo(u8); 46 | } 47 | for (argb8888_src_samples[0..], argb_src_samples[0..]) |*encoded, linear| { 48 | encoded.* = linear.convertColorTo(seizer.color.sRGB8).convertAlphaTo(u8); 49 | } 50 | 51 | const stdout = std.io.getStdOut().writer(); 52 | var bench = zbench.Benchmark.init(std.heap.page_allocator, .{}); 53 | defer bench.deinit(); 54 | 55 | try bench.add("sRGB8 roundtrip f32 naive", sRGB_roundtrip_naive_f32, .{}); 56 | try bench.add("sRGB8 roundtrip f64 naive", sRGB_roundtrip_naive_f64, .{}); 57 | try bench.add("argb compositeSrcOver", argb_compositeSrcOver, .{}); 58 | try bench.add("argb compositeXor", argb_compositeXor, .{}); 59 | try bench.add("argb8888 to argb", argb8888_to_argb, .{}); 60 | try bench.add("argb to argb8888", argb_to_argb8888, .{}); 61 | try bench.add("sRGB encode naive", @"sRGB Encode Naive", .{}); 62 | try bench.add("x^2.2 encode approximation", @"x^2.2 encode approximation", .{}); 63 | 64 | try stdout.writeAll("\n"); 65 | try bench.run(stdout); 66 | } 67 | 68 | fn sRGB_roundtrip_naive_f32(_: std.mem.Allocator) void { 69 | var i: u8 = 0; 70 | while (true) : (i += 1) { 71 | std.mem.doNotOptimizeAway(seizer.color.sRGB8.encodeNaive(f32, seizer.color.sRGB8.decodeNaive(@enumFromInt(i), f32))); 72 | if (i == std.math.maxInt(u8)) break; 73 | } 74 | } 75 | 76 | fn sRGB_roundtrip_naive_f64(_: std.mem.Allocator) void { 77 | var i: u8 = 0; 78 | while (true) : (i += 1) { 79 | std.mem.doNotOptimizeAway(seizer.color.sRGB8.encodeNaive(f64, seizer.color.sRGB8.decodeNaive(@enumFromInt(i), f64))); 80 | if (i == std.math.maxInt(u8)) break; 81 | } 82 | } 83 | 84 | fn argb_compositeSrcOver(_: std.mem.Allocator) void { 85 | for (argb_dst_samples, argb_src_samples) |dst, src| { 86 | std.mem.doNotOptimizeAway(argbf32_premultiplied.compositeSrcOver(dst, src)); 87 | } 88 | } 89 | 90 | fn argb_compositeXor(_: std.mem.Allocator) void { 91 | for (argb_dst_samples, argb_src_samples) |dst, src| { 92 | std.mem.doNotOptimizeAway(argbf32_premultiplied.compositeXor(dst, src)); 93 | } 94 | } 95 | 96 | fn argb8888_to_argb(_: std.mem.Allocator) void { 97 | for (argb8888_src_samples) |argb8888_sample| { 98 | std.mem.doNotOptimizeAway(argb8888_sample.convertColorTo(f32).convertAlphaTo(f32)); 99 | } 100 | } 101 | 102 | fn argb_to_argb8888(_: std.mem.Allocator) void { 103 | for (argb_src_samples) |argb_sample| { 104 | std.mem.doNotOptimizeAway(argb_sample.convertColorTo(seizer.color.sRGB8).convertAlphaTo(u8)); 105 | } 106 | } 107 | 108 | fn @"sRGB Encode Naive"(_: std.mem.Allocator) void { 109 | for (argb_src_samples) |argb_sample| { 110 | const encoded = [3]seizer.color.sRGB8{ 111 | seizer.color.sRGB8.encodeNaive(f32, argb_sample.b), 112 | seizer.color.sRGB8.encodeNaive(f32, argb_sample.g), 113 | seizer.color.sRGB8.encodeNaive(f32, argb_sample.r), 114 | }; 115 | std.mem.doNotOptimizeAway(encoded); 116 | const alpha_u8: u8 = @intFromFloat(argb_sample.a * std.math.maxInt(u8)); 117 | std.mem.doNotOptimizeAway(alpha_u8); 118 | } 119 | } 120 | 121 | fn @"x^2.2 encode approximation"(_: std.mem.Allocator) void { 122 | for (argb_src_samples) |argb_sample| { 123 | const encoded = [3]seizer.color.sRGB8{ 124 | seizer.color.sRGB8.encodeFast22Approx(f32, argb_sample.b), 125 | seizer.color.sRGB8.encodeFast22Approx(f32, argb_sample.g), 126 | seizer.color.sRGB8.encodeFast22Approx(f32, argb_sample.r), 127 | }; 128 | std.mem.doNotOptimizeAway(encoded); 129 | const alpha_u8: u8 = @intFromFloat(argb_sample.a * std.math.maxInt(u8)); 130 | std.mem.doNotOptimizeAway(alpha_u8); 131 | } 132 | } 133 | 134 | const argb8 = seizer.color.argb(seizer.color.sRGB8, .premultiplied, u8); 135 | const argbf32_premultiplied = seizer.color.argbf32_premultiplied; 136 | 137 | const seizer = @import("seizer"); 138 | const std = @import("std"); 139 | const zbench = @import("zbench"); 140 | -------------------------------------------------------------------------------- /bench/geometry.zig: -------------------------------------------------------------------------------- 1 | var prng = std.Random.DefaultPrng.init(419621509410522679); 2 | 3 | const BOX_COUNT = 1024 * 64; 4 | 5 | var overlap_f32_aabbs: [BOX_COUNT / 2]bool = undefined; 6 | var overlap_f64_aabbs: [BOX_COUNT / 2]bool = undefined; 7 | var overlap_i32_aabbs: [BOX_COUNT / 2]bool = undefined; 8 | var overlap_i64_aabbs: [BOX_COUNT / 2]bool = undefined; 9 | 10 | var f32_aabbs: [BOX_COUNT]geometry.AABB(f32) = undefined; 11 | var f64_aabbs: [BOX_COUNT]geometry.AABB(f64) = undefined; 12 | var i32_aabbs: [BOX_COUNT]geometry.AABB(i32) = undefined; 13 | var i64_aabbs: [BOX_COUNT]geometry.AABB(i64) = undefined; 14 | 15 | pub fn main() !void { 16 | const stdout = std.io.getStdOut().writer(); 17 | var bench = zbench.Benchmark.init(std.heap.page_allocator, .{}); 18 | defer bench.deinit(); 19 | 20 | var boxes_f32: [BOX_COUNT][4]f32 = undefined; 21 | var boxes_f64: [BOX_COUNT][4]f64 = undefined; 22 | var boxes_i32: [BOX_COUNT][4]i32 = undefined; 23 | var boxes_i64: [BOX_COUNT][4]i64 = undefined; 24 | 25 | for (&boxes_f32, &boxes_f64, &boxes_i32, &boxes_i64) |*bf32, *bf64, *bi32, *bi64| { 26 | bf64.*[0] = prng.random().float(f32); 27 | bf64.*[1] = prng.random().float(f32); 28 | bf64.*[2] = prng.random().float(f32); 29 | bf64.*[3] = prng.random().float(f32); 30 | 31 | bf32.*[0] = @floatCast(bf64.*[0]); 32 | bf32.*[1] = @floatCast(bf64.*[1]); 33 | bf32.*[2] = @floatCast(bf64.*[2]); 34 | bf32.*[3] = @floatCast(bf64.*[3]); 35 | 36 | bi32.*[0] = @intFromFloat(bf64.*[0] * (std.math.maxInt(i32) >> 1)); 37 | bi32.*[1] = @intFromFloat(bf64.*[1] * (std.math.maxInt(i32) >> 1)); 38 | bi32.*[2] = @intFromFloat(bf64.*[2] * (std.math.maxInt(i32) >> 1)); 39 | bi32.*[3] = @intFromFloat(bf64.*[3] * (std.math.maxInt(i32) >> 1)); 40 | 41 | bi64.*[0] = @intFromFloat(bf64.*[0] * (std.math.maxInt(i64) >> 1)); 42 | bi64.*[1] = @intFromFloat(bf64.*[1] * (std.math.maxInt(i64) >> 1)); 43 | bi64.*[2] = @intFromFloat(bf64.*[2] * (std.math.maxInt(i64) >> 1)); 44 | bi64.*[3] = @intFromFloat(bf64.*[3] * (std.math.maxInt(i64) >> 1)); 45 | } 46 | 47 | for (&f32_aabbs, &f64_aabbs, &boxes_f32, &boxes_f64) |*aabbf32, *aabbf64, bf32, bf64| { 48 | aabbf32.* = geometry.AABB(f32).init(bf32[0..2].*, bf32[2..4].*); 49 | aabbf64.* = geometry.AABB(f64).init(bf64[0..2].*, bf64[2..4].*); 50 | } 51 | 52 | for (&i32_aabbs, &i64_aabbs, &boxes_i32, &boxes_i64) |*aabbi32, *aabbi64, bi32, bi64| { 53 | aabbi32.* = geometry.AABB(i32).init(bi32[0..2].*, bi32[2..4].*); 54 | aabbi64.* = geometry.AABB(i64).init(bi64[0..2].*, bi64[2..4].*); 55 | } 56 | 57 | @memset(&overlap_f32_aabbs, false); 58 | @memset(&overlap_f64_aabbs, false); 59 | @memset(&overlap_i32_aabbs, false); 60 | @memset(&overlap_i64_aabbs, false); 61 | 62 | try bench.add("AABB(f32) overlap", @"AABB(f32) overlap", .{}); 63 | try bench.add("AABB(f64) overlap", @"AABB(f64) overlap", .{}); 64 | try bench.add("AABB(i32) overlap", @"AABB(i32) overlap", .{}); 65 | try bench.add("AABB(i64) overlap", @"AABB(i64) overlap", .{}); 66 | 67 | try bench.add("AABB(f32) intersection", @"AABB(f32) intersection", .{}); 68 | try bench.add("AABB(f64) intersection", @"AABB(f64) intersection", .{}); 69 | try bench.add("AABB(f32) intersection", @"AABB(f32) intersection", .{}); 70 | try bench.add("AABB(f64) intersection", @"AABB(f64) intersection", .{}); 71 | 72 | try stdout.writeAll("\n"); 73 | try bench.run(stdout); 74 | 75 | var sum_f32_aabb_overlaps: usize = 0; 76 | var sum_f64_aabb_overlaps: usize = 0; 77 | var sum_i32_aabb_overlaps: usize = 0; 78 | var sum_i64_aabb_overlaps: usize = 0; 79 | 80 | for ( 81 | overlap_f32_aabbs, 82 | overlap_f64_aabbs, 83 | overlap_i32_aabbs, 84 | overlap_i64_aabbs, 85 | ) |of32, of64, oi32, oi64| { 86 | sum_f32_aabb_overlaps += if (of32) 1 else 0; 87 | sum_f64_aabb_overlaps += if (of64) 1 else 0; 88 | sum_i32_aabb_overlaps += if (oi32) 1 else 0; 89 | sum_i64_aabb_overlaps += if (oi64) 1 else 0; 90 | } 91 | 92 | try stdout.print( 93 | \\Overlaps detected: 94 | \\|{0s:16}|{2s:16}|{4s:16}|{6s:16}| 95 | \\|{1:16}|{3:16}|{5:16}|{7:16}| 96 | , .{ 97 | "AABB(f32)", 98 | sum_f32_aabb_overlaps, 99 | "AABB(f64)", 100 | sum_f64_aabb_overlaps, 101 | "AABB(i32)", 102 | sum_i32_aabb_overlaps, 103 | "AABB(i64)", 104 | sum_i64_aabb_overlaps, 105 | }); 106 | try stdout.writeAll("\n"); 107 | } 108 | 109 | fn @"AABB(f32) overlap"(_: std.mem.Allocator) void { 110 | for (&overlap_f32_aabbs, f32_aabbs[0 .. BOX_COUNT / 2], f32_aabbs[BOX_COUNT / 2 ..]) |*overlaps, b1, b2| { 111 | overlaps.* = b1.overlaps(b2); 112 | } 113 | } 114 | 115 | fn @"AABB(f64) overlap"(_: std.mem.Allocator) void { 116 | for (&overlap_f64_aabbs, f64_aabbs[0 .. BOX_COUNT / 2], f64_aabbs[BOX_COUNT / 2 ..]) |*overlaps, b1, b2| { 117 | overlaps.* = b1.overlaps(b2); 118 | } 119 | } 120 | 121 | fn @"AABB(i32) overlap"(_: std.mem.Allocator) void { 122 | for (&overlap_i32_aabbs, i32_aabbs[0 .. BOX_COUNT / 2], i32_aabbs[BOX_COUNT / 2 ..]) |*overlaps, b1, b2| { 123 | overlaps.* = b1.overlaps(b2); 124 | } 125 | } 126 | 127 | fn @"AABB(i64) overlap"(_: std.mem.Allocator) void { 128 | for (&overlap_i64_aabbs, i64_aabbs[0 .. BOX_COUNT / 2], i64_aabbs[BOX_COUNT / 2 ..]) |*overlaps, b1, b2| { 129 | overlaps.* = b1.overlaps(b2); 130 | } 131 | } 132 | 133 | fn @"AABB(f32) intersection"(_: std.mem.Allocator) void { 134 | for (f32_aabbs[0 .. BOX_COUNT / 2], f32_aabbs[BOX_COUNT / 2 ..]) |b1, b2| { 135 | std.mem.doNotOptimizeAway(b1.intersection(b2)); 136 | } 137 | } 138 | 139 | fn @"AABB(f64) intersection"(_: std.mem.Allocator) void { 140 | for (f64_aabbs[0 .. BOX_COUNT / 2], f64_aabbs[BOX_COUNT / 2 ..]) |b1, b2| { 141 | std.mem.doNotOptimizeAway(b1.intersection(b2)); 142 | } 143 | } 144 | 145 | const seizer = @import("seizer"); 146 | const geometry = seizer.geometry; 147 | const std = @import("std"); 148 | const zbench = @import("zbench"); 149 | -------------------------------------------------------------------------------- /bench/image.zig: -------------------------------------------------------------------------------- 1 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 2 | var prng: std.Random.DefaultPrng = undefined; 3 | 4 | var src_linear_argbf32: seizer.image.Linear(argbf32_premultiplied) = undefined; 5 | var src_tiled_argbf32: seizer.image.Tiled(.{ 16, 16 }, argbf32_premultiplied) = undefined; 6 | 7 | var dst_linear_argbf32: seizer.image.Linear(argbf32_premultiplied) = undefined; 8 | var dst_tiled_argbf32: seizer.image.Tiled(.{ 16, 16 }, argbf32_premultiplied) = undefined; 9 | 10 | var ops_pos: []const [2]u32 = &.{}; 11 | 12 | pub fn main() !void { 13 | defer _ = gpa.deinit(); 14 | const stdout = std.io.getStdOut().writer(); 15 | 16 | var src_size = [2]u32{ 64, 64 }; 17 | var dst_size = [2]u32{ 1280, 800 }; 18 | var seed: u64 = 2958574218529385335; 19 | var num_operations: u32 = 4096; 20 | 21 | var arg_iter = try std.process.argsWithAllocator(gpa.allocator()); 22 | defer arg_iter.deinit(); 23 | _ = arg_iter.skip(); 24 | while (arg_iter.next()) |flag| { 25 | if (std.mem.eql(u8, flag, "--src-width")) { 26 | const src_width_text = arg_iter.next() orelse { 27 | std.debug.print("flag `--src-width` missing argument\n", .{}); 28 | std.process.exit(1); 29 | }; 30 | src_size[0] = try std.fmt.parseInt(u32, src_width_text, 0); 31 | } else if (std.mem.eql(u8, flag, "--src-height")) { 32 | const src_height_text = arg_iter.next() orelse { 33 | std.debug.print("flag `--src-height` missing argument\n", .{}); 34 | std.process.exit(1); 35 | }; 36 | src_size[1] = try std.fmt.parseInt(u32, src_height_text, 0); 37 | } else if (std.mem.eql(u8, flag, "--dst-width")) { 38 | const dst_width_text = arg_iter.next() orelse { 39 | std.debug.print("flag `--dst-width` missing argument\n", .{}); 40 | std.process.exit(1); 41 | }; 42 | dst_size[0] = try std.fmt.parseInt(u32, dst_width_text, 0); 43 | } else if (std.mem.eql(u8, flag, "--dst-height")) { 44 | const dst_height_text = arg_iter.next() orelse { 45 | std.debug.print("flag `--dst-height` missing argument\n", .{}); 46 | std.process.exit(1); 47 | }; 48 | dst_size[1] = try std.fmt.parseInt(u32, dst_height_text, 0); 49 | } else if (std.mem.eql(u8, flag, "--seed")) { 50 | const seed_text = arg_iter.next() orelse { 51 | std.debug.print("flag `--seed` missing argument\n", .{}); 52 | std.process.exit(1); 53 | }; 54 | seed = try std.fmt.parseInt(u64, seed_text, 0); 55 | } else if (std.mem.eql(u8, flag, "--num-ops")) { 56 | const num_ops_text = arg_iter.next() orelse { 57 | std.debug.print("flag `--num-ops` missing argument\n", .{}); 58 | std.process.exit(1); 59 | }; 60 | num_operations = try std.fmt.parseInt(u32, num_ops_text, 0); 61 | } else { 62 | std.debug.print("unknown flag \"{}\"\n", .{std.zig.fmtEscapes(flag)}); 63 | std.process.exit(1); 64 | } 65 | } 66 | 67 | try stdout.print( 68 | \\seed = {} 69 | \\dst_size = <{}, {}> 70 | \\src_size = <{}, {}> 71 | \\num_operations = {} 72 | \\ 73 | , 74 | .{ seed, dst_size[0], dst_size[1], src_size[0], src_size[1], num_operations }, 75 | ); 76 | 77 | prng = std.Random.DefaultPrng.init(seed); 78 | 79 | // init operation positions 80 | const ops_pos_buffer = try gpa.allocator().alloc([2]u32, num_operations); 81 | defer gpa.allocator().free(ops_pos_buffer); 82 | for (ops_pos_buffer) |*pos| { 83 | pos.* = [2]u32{ 84 | prng.random().uintLessThan(u32, dst_size[0] - src_size[0]), 85 | prng.random().uintLessThan(u32, dst_size[1] - src_size[1]), 86 | }; 87 | } 88 | ops_pos = ops_pos_buffer; 89 | 90 | // init src images 91 | src_linear_argbf32 = try seizer.image.Linear(argbf32_premultiplied).alloc(gpa.allocator(), src_size); 92 | defer src_linear_argbf32.free(gpa.allocator()); 93 | 94 | src_tiled_argbf32 = try seizer.image.Tiled(.{ 16, 16 }, argbf32_premultiplied).alloc(gpa.allocator(), src_size); 95 | defer src_tiled_argbf32.free(gpa.allocator()); 96 | 97 | for (0..src_size[1]) |y| { 98 | for (0..src_size[0]) |x| { 99 | const pos = [2]u32{ @intCast(x), @intCast(y) }; 100 | const pixel = seizer.color.argb(f32, .straight, f32).init( 101 | prng.random().float(f32), 102 | prng.random().float(f32), 103 | prng.random().float(f32), 104 | prng.random().float(f32), 105 | ).convertAlphaModelTo(.premultiplied); 106 | 107 | src_linear_argbf32.setPixel(pos, pixel); 108 | src_tiled_argbf32.setPixel(pos, pixel); 109 | } 110 | } 111 | 112 | // init dst images 113 | dst_linear_argbf32 = try seizer.image.Linear(argbf32_premultiplied).alloc(gpa.allocator(), dst_size); 114 | defer dst_linear_argbf32.free(gpa.allocator()); 115 | 116 | dst_tiled_argbf32 = try seizer.image.Tiled(.{ 16, 16 }, argbf32_premultiplied).alloc(gpa.allocator(), dst_size); 117 | defer dst_tiled_argbf32.free(gpa.allocator()); 118 | 119 | // create benchmarks 120 | var bench = zbench.Benchmark.init(gpa.allocator(), .{}); 121 | defer bench.deinit(); 122 | 123 | try bench.add("composite Linear(argb(f32))", linearArgbF32Composite, .{}); 124 | try bench.add("compositeLinear Tiled(.{16,16},argb(f32))", tiled16x16ArgbF32CompositeLinear, .{}); 125 | 126 | try stdout.writeAll("\n"); 127 | try bench.run(stdout); 128 | 129 | // write final image hashes to stdout just to make sure they aren't optimized away 130 | var linear_hash = std.hash.Fnv1a_128.init(); 131 | for (0..dst_linear_argbf32.size[1]) |y| { 132 | for (0..dst_linear_argbf32.size[1]) |x| { 133 | linear_hash.update(std.mem.asBytes(&dst_linear_argbf32.getPixel(.{ @intCast(x), @intCast(y) }))); 134 | } 135 | } 136 | try stdout.print("\n\nlinear hash = {x}\n", .{linear_hash.final()}); 137 | 138 | var tiled_hash = std.hash.Fnv1a_128.init(); 139 | for (0..dst_tiled_argbf32.size_px[1]) |y| { 140 | for (0..dst_tiled_argbf32.size_px[1]) |x| { 141 | tiled_hash.update(std.mem.asBytes(&dst_tiled_argbf32.getPixel(.{ @intCast(x), @intCast(y) }))); 142 | } 143 | } 144 | try stdout.print("tiled hash = {x}\n", .{tiled_hash.final()}); 145 | } 146 | 147 | fn linearArgbF32Composite(_: std.mem.Allocator) void { 148 | dst_linear_argbf32.clear(argbf32_premultiplied.BLACK); 149 | for (ops_pos) |pos| { 150 | dst_linear_argbf32.slice(pos, src_linear_argbf32.size).composite(src_linear_argbf32.asSlice()); 151 | } 152 | std.mem.doNotOptimizeAway(dst_linear_argbf32.pixels); 153 | } 154 | 155 | fn tiled16x16ArgbF32CompositeLinear(_: std.mem.Allocator) void { 156 | dst_tiled_argbf32.set(seizer.geometry.UAABB(u32).init(.{ 0, 0 }, .{ dst_tiled_argbf32.size_px[0] - 1, dst_tiled_argbf32.size_px[1] - 1 }), argbf32_premultiplied.BLACK); 157 | for (ops_pos) |pos| { 158 | const dst_area = seizer.geometry.UAABB(u32).init( 159 | pos, 160 | .{ 161 | pos[0] + src_linear_argbf32.size[0] - 1, 162 | pos[1] + src_linear_argbf32.size[1] - 1, 163 | }, 164 | ); 165 | dst_tiled_argbf32.compositeLinear(dst_area, src_linear_argbf32.asSlice()); 166 | } 167 | } 168 | 169 | const argb8_premul = seizer.color.argb(seizer.color.sRGB8, .premultiplied, u8); 170 | const argbf32_premultiplied = seizer.color.argbf32_premultiplied; 171 | 172 | const seizer = @import("seizer"); 173 | const std = @import("std"); 174 | const zbench = @import("zbench"); 175 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Builder = std.Build; 3 | 4 | const Example = enum { 5 | clear, 6 | blit, 7 | fill_rect, 8 | texture_rect, 9 | bitmap_font, 10 | sprite_batch, 11 | bicubic_filter, 12 | tinyvg, 13 | ui_stage, 14 | multi_window, 15 | file_browser, 16 | ui_view_image, 17 | ui_plot_sine, 18 | colormapped_image, 19 | canvas, 20 | cutscene, 21 | }; 22 | 23 | pub fn build(b: *Builder) !void { 24 | const target = b.standardTargetOptions(.{}); 25 | const optimize = b.standardOptimizeOption(.{}); 26 | 27 | const import_wayland = b.option(bool, "wayland", "enable wayland display backend (defaults to true on linux)") orelse switch (target.result.os.tag) { 28 | .linux => true, 29 | .windows => false, 30 | else => false, 31 | }; 32 | 33 | // Dependencies 34 | const zigimg_dep = b.dependency("zigimg", .{ 35 | .target = target, 36 | .optimize = optimize, 37 | }); 38 | 39 | const tinyvg = b.dependency("tinyvg", .{ 40 | .target = target, 41 | .optimize = optimize, 42 | }); 43 | 44 | const libxev = b.dependency("libxev", .{ 45 | .target = target, 46 | .optimize = optimize, 47 | }); 48 | 49 | const xkb_dep = b.dependency("xkb", .{ 50 | .target = target, 51 | .optimize = optimize, 52 | }); 53 | 54 | const angelcode_font_module = b.addModule("AngelCodeFont", .{ 55 | .root_source_file = b.path("dep/AngelCodeFont.zig"), 56 | }); 57 | 58 | const shimizu_dep = b.dependency("shimizu", .{ 59 | .target = target, 60 | .optimize = optimize, 61 | }); 62 | 63 | const zigcoro_dep = b.dependency("zigcoro", .{ 64 | .target = target, 65 | .optimize = optimize, 66 | }); 67 | 68 | // generate additional wayland protocol definitions with shimizu-scanner 69 | const generate_wayland_unstable_zig_cmd = b.addRunArtifact(shimizu_dep.artifact("shimizu-scanner")); 70 | generate_wayland_unstable_zig_cmd.addFileArg(b.path("dep/wayland-protocols/xdg-decoration-unstable-v1.xml")); 71 | generate_wayland_unstable_zig_cmd.addFileArg(b.path("dep/wayland-protocols/fractional-scale-v1.xml")); 72 | generate_wayland_unstable_zig_cmd.addArgs(&.{ "--interface-version", "zxdg_decoration_manager_v1", "1" }); 73 | generate_wayland_unstable_zig_cmd.addArgs(&.{ "--interface-version", "wp_fractional_scale_manager_v1", "1" }); 74 | 75 | generate_wayland_unstable_zig_cmd.addArg("--import"); 76 | generate_wayland_unstable_zig_cmd.addFileArg(b.path("dep/wayland-protocols/wayland.xml")); 77 | generate_wayland_unstable_zig_cmd.addArg("@import(\"core\")"); 78 | 79 | generate_wayland_unstable_zig_cmd.addArg("--import"); 80 | generate_wayland_unstable_zig_cmd.addFileArg(b.path("dep/wayland-protocols/xdg-shell.xml")); 81 | generate_wayland_unstable_zig_cmd.addArg("@import(\"wayland-protocols\").xdg_shell"); 82 | 83 | generate_wayland_unstable_zig_cmd.addArg("--output"); 84 | const wayland_unstable_dir = generate_wayland_unstable_zig_cmd.addOutputDirectoryArg("wayland-unstable"); 85 | 86 | const wayland_unstable_module = b.addModule("wayland-unstable", .{ 87 | .root_source_file = wayland_unstable_dir.path(b, "root.zig"), 88 | .target = target, 89 | .optimize = optimize, 90 | .imports = &.{ 91 | .{ .name = "wire", .module = shimizu_dep.module("wire") }, 92 | .{ .name = "core", .module = shimizu_dep.module("core") }, 93 | .{ .name = "wayland-protocols", .module = shimizu_dep.module("wayland-protocols") }, 94 | }, 95 | }); 96 | 97 | // seizer 98 | const module = b.addModule("seizer", .{ 99 | .root_source_file = b.path("src/seizer.zig"), 100 | .imports = &.{ 101 | .{ .name = "zigimg", .module = zigimg_dep.module("zigimg") }, 102 | .{ .name = "tvg", .module = tinyvg.module("tvg") }, 103 | .{ .name = "xev", .module = libxev.module("xev") }, 104 | .{ .name = "AngelCodeFont", .module = angelcode_font_module }, 105 | .{ .name = "libcoro", .module = zigcoro_dep.module("libcoro") }, 106 | }, 107 | }); 108 | 109 | if (import_wayland) { 110 | module.addImport("shimizu", shimizu_dep.module("shimizu")); 111 | module.addImport("wayland-protocols", shimizu_dep.module("wayland-protocols")); 112 | module.addImport("wayland-unstable", wayland_unstable_module); 113 | module.addImport("xkb", xkb_dep.module("xkb")); 114 | } 115 | 116 | const example_fields = @typeInfo(Example).Enum.fields; 117 | inline for (example_fields) |tag| { 118 | const tag_name = tag.name; 119 | const exe = b.addExecutable(.{ 120 | .name = tag_name, 121 | .root_source_file = b.path("examples/" ++ tag_name ++ ".zig"), 122 | .target = target, 123 | .optimize = optimize, 124 | }); 125 | exe.root_module.addImport("seizer", module); 126 | 127 | // build 128 | const build_step = b.step("example-" ++ tag_name, "Build the " ++ tag_name ++ " example"); 129 | 130 | const install_exe = b.addInstallArtifact(exe, .{}); 131 | b.getInstallStep().dependOn(&install_exe.step); 132 | build_step.dependOn(&install_exe.step); 133 | 134 | // run 135 | const run_cmd = b.addRunArtifact(exe); 136 | if (b.args) |args| { 137 | run_cmd.addArgs(args); 138 | } 139 | const run_step = b.step("example-" ++ tag_name ++ "-run", "Run the " ++ tag_name ++ " example"); 140 | run_step.dependOn(&run_cmd.step); 141 | } 142 | 143 | // test seizer module 144 | const module_test_exe = b.addTest(.{ 145 | .root_source_file = b.path("./src/seizer.zig"), 146 | .target = target, 147 | .optimize = optimize, 148 | }); 149 | module_test_exe.root_module.addImport("zigimg", zigimg_dep.module("zigimg")); 150 | const run_module_test_exe = b.addRunArtifact(module_test_exe); 151 | 152 | const test_step = b.step("test", "Run seizer module tests"); 153 | test_step.dependOn(&run_module_test_exe.step); 154 | 155 | // benchmarks 156 | const benchmark_optimize = b.option(std.builtin.OptimizeMode, "bench-optimize", "The optimization mode to use for the benchmarks (default ReleaseFast)") orelse .ReleaseFast; 157 | const zbench_dep = b.dependency("zbench", .{ 158 | .target = target, 159 | .optimize = benchmark_optimize, 160 | }); 161 | 162 | // color benchmarks 163 | const bench_color_exe = b.addExecutable(.{ 164 | .name = "bench_color", 165 | .root_source_file = b.path("./bench/color.zig"), 166 | .target = target, 167 | .optimize = benchmark_optimize, 168 | }); 169 | bench_color_exe.root_module.addImport("seizer", module); 170 | bench_color_exe.root_module.addImport("zbench", zbench_dep.module("zbench")); 171 | b.installArtifact(bench_color_exe); 172 | 173 | const run_bench_color_exe = b.addRunArtifact(bench_color_exe); 174 | 175 | const bench_color_step = b.step("bench-color", "Run color benchmarks"); 176 | bench_color_step.dependOn(&run_bench_color_exe.step); 177 | 178 | // image benchmarks 179 | const bench_image_exe = b.addExecutable(.{ 180 | .name = "bench_image", 181 | .root_source_file = b.path("./bench/image.zig"), 182 | .target = target, 183 | .optimize = benchmark_optimize, 184 | }); 185 | bench_image_exe.root_module.addImport("seizer", module); 186 | bench_image_exe.root_module.addImport("zbench", zbench_dep.module("zbench")); 187 | b.installArtifact(bench_image_exe); 188 | 189 | const run_bench_image_exe = b.addRunArtifact(bench_image_exe); 190 | if (b.args) |args| { 191 | run_bench_image_exe.addArgs(args); 192 | } 193 | 194 | const bench_image_step = b.step("bench-image", "Run image benchmarks"); 195 | bench_image_step.dependOn(&run_bench_image_exe.step); 196 | 197 | // image benchmarks 198 | const bench_geometry_exe = b.addExecutable(.{ 199 | .name = "bench_geometry", 200 | .root_source_file = b.path("./bench/geometry.zig"), 201 | .target = target, 202 | .optimize = benchmark_optimize, 203 | }); 204 | bench_geometry_exe.root_module.addImport("seizer", module); 205 | bench_geometry_exe.root_module.addImport("zbench", zbench_dep.module("zbench")); 206 | b.installArtifact(bench_geometry_exe); 207 | 208 | const run_bench_geometry_exe = b.addRunArtifact(bench_geometry_exe); 209 | if (b.args) |args| { 210 | run_bench_geometry_exe.addArgs(args); 211 | } 212 | 213 | const bench_geometry_step = b.step("bench-geometry", "Run geometry benchmarks"); 214 | bench_geometry_step.dependOn(&run_bench_geometry_exe.step); 215 | 216 | // Compile everything without installing it, so we can skip the LLVM output 217 | const check_step = b.step("check", "check that everything compiles"); 218 | 219 | const check_module_test_exe = b.addTest(.{ 220 | .root_source_file = b.path("./src/seizer.zig"), 221 | .target = target, 222 | .optimize = optimize, 223 | }); 224 | check_module_test_exe.root_module.addImport("zigimg", zigimg_dep.module("zigimg")); 225 | check_step.dependOn(&check_module_test_exe.step); 226 | 227 | const check_bench_color_exe = b.addExecutable(.{ 228 | .name = "bench_color", 229 | .root_source_file = b.path("./bench/color.zig"), 230 | .target = target, 231 | .optimize = benchmark_optimize, 232 | }); 233 | check_bench_color_exe.root_module.addImport("seizer", module); 234 | check_bench_color_exe.root_module.addImport("zbench", zbench_dep.module("zbench")); 235 | check_step.dependOn(&check_bench_color_exe.step); 236 | 237 | const check_bench_image_exe = b.addExecutable(.{ 238 | .name = "bench_image", 239 | .root_source_file = b.path("./bench/image.zig"), 240 | .target = target, 241 | .optimize = benchmark_optimize, 242 | }); 243 | check_bench_image_exe.root_module.addImport("seizer", module); 244 | check_bench_image_exe.root_module.addImport("zbench", zbench_dep.module("zbench")); 245 | check_step.dependOn(&check_bench_image_exe.step); 246 | 247 | const check_bench_geometry_exe = b.addExecutable(.{ 248 | .name = "bench_geometry", 249 | .root_source_file = b.path("./bench/geometry.zig"), 250 | .target = target, 251 | .optimize = benchmark_optimize, 252 | }); 253 | check_bench_geometry_exe.root_module.addImport("seizer", module); 254 | check_bench_geometry_exe.root_module.addImport("zbench", zbench_dep.module("zbench")); 255 | check_step.dependOn(&check_bench_geometry_exe.step); 256 | 257 | inline for (example_fields) |tag| { 258 | // check that this example compiles, but skip llvm output that takes a while to run 259 | const exe_check = b.addExecutable(.{ 260 | .name = tag.name, 261 | .root_source_file = b.path("examples/" ++ tag.name ++ ".zig"), 262 | .target = target, 263 | .optimize = optimize, 264 | }); 265 | exe_check.root_module.addImport("seizer", module); 266 | check_step.dependOn(&exe_check.step); 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = "seizer", 3 | .version = "0.1.0", 4 | .paths = .{""}, 5 | .dependencies = .{ 6 | .zigimg = .{ 7 | .url = "https://github.com/zigimg/zigimg/archive/d9dbbe22b5f7b5f1f4772169ed93ffeed8e8124d.tar.gz", 8 | .hash = "122013646f7038ecc71ddf8a0d7de346d29a6ec40140af57f838b0a975c69af512b0", 9 | }, 10 | .tinyvg = .{ 11 | .url = "https://github.com/TinyVG/sdk/archive/d103ec3ec461a2f96b8a7e7c1dda8ff54d4c8825.tar.gz", 12 | .hash = "12202e4803c43c3513b27cd8d993da79fc459ab21ab4d0aa4aa2b6bf32b08d29bd44", 13 | }, 14 | .@"zig-xml" = .{ 15 | .url = "https://github.com/leroycep/zig-xml/archive/e89acff41d17f1a9b31bd64a8ca54682c81b832c.tar.gz", 16 | .hash = "1220b7adb7430c32325b18edbb004f7d523dd0e66306e4985e7667ef7eef36c479a4", 17 | }, 18 | .libxev = .{ 19 | .url = "https://github.com/mitchellh/libxev/archive/1dd3c9015a542757b049f6d33beb8941f57bce1f.tar.gz", 20 | .hash = "1220b644b45718a869b37bc37bbc476e69922b21090003b38c1b68a7218fc365771a", 21 | }, 22 | .vulkan_zig = .{ 23 | .url = "https://github.com/Snektron/vulkan-zig/archive/66b7b773bb61e2102025f2d5ff0ae8c5f53e19cc.tar.gz", 24 | .hash = "12208958f173b8b81bfac797955f0416ab38b21d1f69d4ebf6c7ca460a828a41cd45", 25 | }, 26 | .xkb = .{ 27 | .url = "https://git.sr.ht/~geemili/xkb/archive/581636d48383940d5ab7500a7a15b242ed8ae007.tar.gz", 28 | .hash = "12208582ac218c676b37763dd67d25c79ea3808d0c73369663e0e4f9a2b0040ed73e", 29 | }, 30 | .shimizu = .{ 31 | .url = "https://git.sr.ht/~geemili/shimizu/archive/a39eb4935c36b1ede38b34491f8eda93af01d4fe.tar.gz", 32 | .hash = "12202790861db271425630f57bef87fde01e3a0d043e6dbaadbe28d22986bbf3968c", 33 | }, 34 | .zbench = .{ 35 | .url = "https://github.com/hendriknielaender/zBench/archive/0d5417d53e38026503cf993ef9aa24a5fa9554dc.tar.gz", 36 | .hash = "12202e943486d4351fcc633aed880df43e9025f8866c746619da284a3048ef529233", 37 | }, 38 | .zigcoro = .{ 39 | .url = "git+https://github.com/rsepassi/zigcoro#ca58a912c3c0957d6aab0405f4c78f68e1dababb", 40 | .hash = "12204959321c5e16a70944b8cdf423f0fa2bdf7db1f72bacc89b8af85acc4c054d9c", 41 | }, 42 | }, 43 | } 44 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | with import {}; 2 | 3 | mkShell { 4 | name = "dotnet"; 5 | packages = [ 6 | vulkan-headers 7 | vulkan-loader 8 | vulkan-validation-layers 9 | vulkan-tools # vulkaninfo 10 | shaderc # GLSL to SPIRV compiler - glslc 11 | renderdoc # Graphics debugger 12 | tracy # Graphics profiler 13 | vulkan-tools-lunarg # vkconfig 14 | pkg-config 15 | ]; 16 | 17 | LD_LIBRARY_PATH="${glfw}/lib:${freetype}/lib:${vulkan-loader}/lib:${vulkan-validation-layers}/lib:${renderdoc}/lib"; 18 | # LD_LIBRARY_PATH="${glfw}/lib:${freetype}/lib:${vulkan-loader}/lib:${vulkan-validation-layers}/lib"; 19 | VULKAN_SDK = "${vulkan-headers}"; 20 | VK_LAYER_PATH = "${vulkan-validation-layers}/share/vulkan/explicit_layer.d"; 21 | } 22 | -------------------------------------------------------------------------------- /dep/wayland-protocols/fractional-scale-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2022 Kenny Levinsen 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a 7 | copy of this software and associated documentation files (the "Software"), 8 | to deal in the Software without restriction, including without limitation 9 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | and/or sell copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice (including the next 14 | paragraph) shall be included in all copies or substantial portions of the 15 | Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | 25 | 26 | 27 | This protocol allows a compositor to suggest for surfaces to render at 28 | fractional scales. 29 | 30 | A client can submit scaled content by utilizing wp_viewport. This is done by 31 | creating a wp_viewport object for the surface and setting the destination 32 | rectangle to the surface size before the scale factor is applied. 33 | 34 | The buffer size is calculated by multiplying the surface size by the 35 | intended scale. 36 | 37 | The wl_surface buffer scale should remain set to 1. 38 | 39 | If a surface has a surface-local size of 100 px by 50 px and wishes to 40 | submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should 41 | be used and the wp_viewport destination rectangle should be 100 px by 50 px. 42 | 43 | For toplevel surfaces, the size is rounded halfway away from zero. The 44 | rounding algorithm for subsurface position and size is not defined. 45 | 46 | 47 | 48 | 49 | A global interface for requesting surfaces to use fractional scales. 50 | 51 | 52 | 53 | 54 | Informs the server that the client will not be using this protocol 55 | object anymore. This does not affect any other objects, 56 | wp_fractional_scale_v1 objects included. 57 | 58 | 59 | 60 | 61 | 63 | 64 | 65 | 66 | 67 | Create an add-on object for the the wl_surface to let the compositor 68 | request fractional scales. If the given wl_surface already has a 69 | wp_fractional_scale_v1 object associated, the fractional_scale_exists 70 | protocol error is raised. 71 | 72 | 74 | 76 | 77 | 78 | 79 | 80 | 81 | An additional interface to a wl_surface object which allows the compositor 82 | to inform the client of the preferred scale. 83 | 84 | 85 | 86 | 87 | Destroy the fractional scale object. When this object is destroyed, 88 | preferred_scale events will no longer be sent. 89 | 90 | 91 | 92 | 93 | 94 | Notification of a new preferred scale for this surface that the 95 | compositor suggests that the client should use. 96 | 97 | The sent scale is the numerator of a fraction with a denominator of 120. 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /dep/wayland-protocols/xdg-decoration-unstable-v1.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright © 2018 Simon Ser 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a 7 | copy of this software and associated documentation files (the "Software"), 8 | to deal in the Software without restriction, including without limitation 9 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 10 | and/or sell copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice (including the next 14 | paragraph) shall be included in all copies or substantial portions of the 15 | Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 20 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 22 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | 25 | 26 | 27 | 28 | This interface allows a compositor to announce support for server-side 29 | decorations. 30 | 31 | A window decoration is a set of window controls as deemed appropriate by 32 | the party managing them, such as user interface components used to move, 33 | resize and change a window's state. 34 | 35 | A client can use this protocol to request being decorated by a supporting 36 | compositor. 37 | 38 | If compositor and client do not negotiate the use of a server-side 39 | decoration using this protocol, clients continue to self-decorate as they 40 | see fit. 41 | 42 | Warning! The protocol described in this file is experimental and 43 | backward incompatible changes may be made. Backward compatible changes 44 | may be added together with the corresponding interface version bump. 45 | Backward incompatible changes are done by bumping the version number in 46 | the protocol and interface names and resetting the interface version. 47 | Once the protocol is to be declared stable, the 'z' prefix and the 48 | version number in the protocol and interface names are removed and the 49 | interface version number is reset. 50 | 51 | 52 | 53 | 54 | Destroy the decoration manager. This doesn't destroy objects created 55 | with the manager. 56 | 57 | 58 | 59 | 60 | 61 | Create a new decoration object associated with the given toplevel. 62 | 63 | Creating an xdg_toplevel_decoration from an xdg_toplevel which has a 64 | buffer attached or committed is a client error, and any attempts by a 65 | client to attach or manipulate a buffer prior to the first 66 | xdg_toplevel_decoration.configure event must also be treated as 67 | errors. 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | The decoration object allows the compositor to toggle server-side window 77 | decorations for a toplevel surface. The client can request to switch to 78 | another mode. 79 | 80 | The xdg_toplevel_decoration object must be destroyed before its 81 | xdg_toplevel. 82 | 83 | 84 | 85 | 87 | 89 | 91 | 92 | 93 | 94 | 95 | 96 | Switch back to a mode without any server-side decorations at the next 97 | commit. 98 | 99 | 100 | 101 | 102 | 103 | These values describe window decoration modes. 104 | 105 | 107 | 109 | 110 | 111 | 112 | 113 | Set the toplevel surface decoration mode. This informs the compositor 114 | that the client prefers the provided decoration mode. 115 | 116 | After requesting a decoration mode, the compositor will respond by 117 | emitting an xdg_surface.configure event. The client should then update 118 | its content, drawing it without decorations if the received mode is 119 | server-side decorations. The client must also acknowledge the configure 120 | when committing the new content (see xdg_surface.ack_configure). 121 | 122 | The compositor can decide not to use the client's mode and enforce a 123 | different mode instead. 124 | 125 | Clients whose decoration mode depend on the xdg_toplevel state may send 126 | a set_mode request in response to an xdg_surface.configure event and wait 127 | for the next xdg_surface.configure event to prevent unwanted state. 128 | Such clients are responsible for preventing configure loops and must 129 | make sure not to send multiple successive set_mode requests with the 130 | same decoration mode. 131 | 132 | If an invalid mode is supplied by the client, the invalid_mode protocol 133 | error is raised by the compositor. 134 | 135 | 136 | 137 | 138 | 139 | 140 | Unset the toplevel surface decoration mode. This informs the compositor 141 | that the client doesn't prefer a particular decoration mode. 142 | 143 | This request has the same semantics as set_mode. 144 | 145 | 146 | 147 | 148 | 149 | The configure event configures the effective decoration mode. The 150 | configured state should not be applied immediately. Clients must send an 151 | ack_configure in response to this event. See xdg_surface.configure and 152 | xdg_surface.ack_configure for details. 153 | 154 | A configure event can be sent at any time. The specified mode must be 155 | obeyed by the client. 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /examples/assets/PressStart2P_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaji-dev/seizer/76c1f9a88b5ad54f7371c2a462ef97ac6ba4fb8e/examples/assets/PressStart2P_8.png -------------------------------------------------------------------------------- /examples/assets/colormap.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_EXT_nonuniform_qualifier : enable 3 | 4 | layout(location=0) in vec2 uv; 5 | layout(location=1) in vec4 tint; 6 | 7 | layout(push_constant, std430) uniform PushConstants { 8 | mat4 transform; 9 | int texture_id; 10 | }; 11 | 12 | layout(binding=1) uniform sampler2D textures[]; 13 | 14 | layout(location=0) out vec4 color; 15 | 16 | layout(binding=2) uniform ColormappingValues { 17 | int colormap_texture_id; 18 | float min_value; 19 | float max_value; 20 | }; 21 | 22 | void main() { 23 | float value = texture(textures[nonuniformEXT(texture_id)], uv).r; 24 | if (value < min_value) { 25 | discard; 26 | } else if (value > max_value) { 27 | color = tint * vec4(1,0,0,1); 28 | } else { 29 | // float colormap_index = (log(value) - log(min_value)) / (log(max_value) - log(min_value)); 30 | float colormap_index = (value - min_value) / (max_value - min_value); 31 | color = tint * texture(textures[nonuniformEXT(colormap_texture_id)], vec2(colormap_index, 0.5)); 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /examples/assets/colormap.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaji-dev/seizer/76c1f9a88b5ad54f7371c2a462ef97ac6ba4fb8e/examples/assets/colormap.frag.spv -------------------------------------------------------------------------------- /examples/assets/cursor_none.tvg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaji-dev/seizer/76c1f9a88b5ad54f7371c2a462ef97ac6ba4fb8e/examples/assets/cursor_none.tvg -------------------------------------------------------------------------------- /examples/assets/monochrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaji-dev/seizer/76c1f9a88b5ad54f7371c2a462ef97ac6ba4fb8e/examples/assets/monochrome.png -------------------------------------------------------------------------------- /examples/assets/textures.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | layout(location = 0) in vec2 uv; 3 | 4 | layout(location = 0) out vec4 color; 5 | 6 | layout(binding = 0) uniform sampler2D texID; 7 | 8 | void main() { 9 | color = texture(texID, uv); 10 | } 11 | 12 | -------------------------------------------------------------------------------- /examples/assets/textures.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaji-dev/seizer/76c1f9a88b5ad54f7371c2a462ef97ac6ba4fb8e/examples/assets/textures.frag.spv -------------------------------------------------------------------------------- /examples/assets/textures.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(location = 0) in vec2 vertexPosition; 4 | layout(location = 1) in vec2 texturePosition; 5 | 6 | layout(location = 0) out vec2 uv; 7 | 8 | void main() { 9 | gl_Position = vec4(vertexPosition.xy, 0.0, 1.0); 10 | uv = texturePosition; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /examples/assets/textures.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaji-dev/seizer/76c1f9a88b5ad54f7371c2a462ef97ac6ba4fb8e/examples/assets/textures.vert.spv -------------------------------------------------------------------------------- /examples/assets/ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaji-dev/seizer/76c1f9a88b5ad54f7371c2a462ef97ac6ba4fb8e/examples/assets/ui.png -------------------------------------------------------------------------------- /examples/assets/wedge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaji-dev/seizer/76c1f9a88b5ad54f7371c2a462ef97ac6ba4fb8e/examples/assets/wedge.png -------------------------------------------------------------------------------- /examples/bicubic_filter.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | 8 | var image: seizer.image.Linear(seizer.color.argbf32_premultiplied) = undefined; 9 | var bicubic_upscale: seizer.image.Linear(seizer.color.argbf32_premultiplied) = undefined; 10 | var bicubic_downscale: seizer.image.Linear(seizer.color.argbf32_premultiplied) = undefined; 11 | 12 | pub fn init() !void { 13 | try display.init(gpa.allocator(), seizer.getLoop()); 14 | 15 | try display.initToplevelSurface(&toplevel_surface, .{}); 16 | toplevel_surface.setOnRender(&render_listener, onRender, null); 17 | 18 | image = try seizer.image.Linear(seizer.color.argbf32_premultiplied).fromMemory(gpa.allocator(), @embedFile("./assets/wedge.png")); 19 | errdefer image.free(gpa.allocator()); 20 | 21 | bicubic_upscale = try seizer.image.Linear(seizer.color.argbf32_premultiplied).alloc(gpa.allocator(), .{ 5 * image.size[0], 5 * image.size[1] }); 22 | errdefer bicubic_upscale.free(gpa.allocator()); 23 | bicubic_upscale.asSlice().resize(image.asSlice()); 24 | 25 | bicubic_downscale = try seizer.image.Linear(seizer.color.argbf32_premultiplied).alloc(gpa.allocator(), .{ image.size[0] / 2, image.size[1] / 2 }); 26 | errdefer bicubic_downscale.free(gpa.allocator()); 27 | bicubic_downscale.asSlice().resize(image.asSlice()); 28 | 29 | seizer.setDeinit(deinit); 30 | } 31 | 32 | fn deinit() void { 33 | bicubic_downscale.free(gpa.allocator()); 34 | bicubic_upscale.free(gpa.allocator()); 35 | image.free(gpa.allocator()); 36 | toplevel_surface.deinit(); 37 | display.deinit(); 38 | _ = gpa.deinit(); 39 | } 40 | 41 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 42 | _ = listener; 43 | 44 | const canvas = try surface.canvas(); 45 | canvas.clear(.{ .r = 0.5, .g = 0.5, .b = 0.7, .a = 1.0 }); 46 | 47 | const sizef = canvas.size(); 48 | const y = [4]f64{ 49 | 0, 50 | sizef[1] * 1.0 / 3.0, 51 | sizef[1] * 2.0 / 3.0, 52 | sizef[1], 53 | }; 54 | canvas.textureRect(seizer.geometry.AABB(f64).init(.{ 0, y[0] }, .{ sizef[0], y[1] }), bicubic_downscale.asSlice(), .{}); 55 | canvas.textureRect(seizer.geometry.AABB(f64).init(.{ 0, y[1] }, .{ sizef[0], y[2] }), image.asSlice(), .{}); 56 | canvas.textureRect(seizer.geometry.AABB(f64).init(.{ 0, y[2] }, .{ sizef[0], y[3] }), bicubic_upscale.asSlice(), .{}); 57 | 58 | try surface.present(); 59 | } 60 | 61 | const seizer = @import("seizer"); 62 | const std = @import("std"); 63 | -------------------------------------------------------------------------------- /examples/bitmap_font.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | 8 | var font: seizer.Canvas.Font = undefined; 9 | 10 | pub fn init() !void { 11 | try display.init(gpa.allocator(), seizer.getLoop()); 12 | 13 | try display.initToplevelSurface(&toplevel_surface, .{}); 14 | toplevel_surface.setOnRender(&render_listener, onRender, null); 15 | 16 | font = try seizer.Canvas.Font.fromFileContents( 17 | gpa.allocator(), 18 | @embedFile("./assets/PressStart2P_8.fnt"), 19 | &.{ 20 | .{ .name = "PressStart2P_8.png", .contents = @embedFile("./assets/PressStart2P_8.png") }, 21 | }, 22 | ); 23 | errdefer font.deinit(); 24 | 25 | seizer.setDeinit(deinit); 26 | } 27 | 28 | /// This is a global deinit, not window specific. This is important because windows can hold onto Graphics resources. 29 | fn deinit() void { 30 | font.deinit(); 31 | toplevel_surface.deinit(); 32 | display.deinit(); 33 | _ = gpa.deinit(); 34 | } 35 | 36 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 37 | _ = listener; 38 | 39 | const canvas = try surface.canvas(); 40 | canvas.clear(.{ .r = 0.5, .g = 0.5, .b = 0.7, .a = 1.0 }); 41 | 42 | var pos = [2]f64{ 50, 50 }; 43 | pos[1] += canvas.writeText(&font, pos, "Hello, world!", .{})[1]; 44 | pos[1] += canvas.writeText(&font, pos, "Hello, world!", .{ .color = .{ .r = 0, .g = 0, .b = 0, .a = 1 } })[1]; 45 | pos[1] += canvas.printText(&font, pos, "pos = <{}, {}>", .{ pos[0], pos[1] }, .{})[1]; 46 | 47 | try surface.present(); 48 | } 49 | 50 | const seizer = @import("seizer"); 51 | const std = @import("std"); 52 | -------------------------------------------------------------------------------- /examples/blit.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | 8 | var image: seizer.image.Linear(seizer.color.argbf32_premultiplied) = undefined; 9 | 10 | pub fn init() !void { 11 | try display.init(gpa.allocator(), seizer.getLoop()); 12 | 13 | try display.initToplevelSurface(&toplevel_surface, .{}); 14 | toplevel_surface.setOnRender(&render_listener, onRender, null); 15 | 16 | image = try seizer.image.Linear(seizer.color.argbf32_premultiplied).fromMemory(gpa.allocator(), @embedFile("./assets/wedge.png")); 17 | errdefer image.free(gpa.allocator()); 18 | 19 | seizer.setDeinit(deinit); 20 | } 21 | 22 | /// This is a global deinit, not window specific. This is important because windows can hold onto Graphics resources. 23 | fn deinit() void { 24 | image.free(gpa.allocator()); 25 | toplevel_surface.deinit(); 26 | display.deinit(); 27 | _ = gpa.deinit(); 28 | } 29 | 30 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 31 | _ = listener; 32 | 33 | const canvas = try surface.canvas(); 34 | canvas.clear(.{ .r = 0.5, .g = 0.5, .b = 0.7, .a = 1.0 }); 35 | 36 | canvas.blit(.{ 50, 50 }, image.asSlice()); 37 | 38 | try surface.present(); 39 | } 40 | 41 | const seizer = @import("seizer"); 42 | const std = @import("std"); 43 | -------------------------------------------------------------------------------- /examples/canvas.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | 8 | pub fn init() !void { 9 | try display.init(gpa.allocator(), seizer.getLoop()); 10 | 11 | try display.initToplevelSurface(&toplevel_surface, .{}); 12 | toplevel_surface.setOnRender(&render_listener, onRender, null); 13 | 14 | seizer.setDeinit(deinit); 15 | } 16 | 17 | fn deinit() void { 18 | toplevel_surface.deinit(); 19 | display.deinit(); 20 | _ = gpa.deinit(); 21 | } 22 | 23 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 24 | _ = listener; 25 | 26 | const canvas = try surface.canvas(); 27 | canvas.clear(.{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 }); 28 | const BLUE = seizer.color.fromSRGB(91, 206, 250, 255); 29 | const PINK = seizer.color.fromSRGB(245, 169, 184, 255); 30 | const WHITE = seizer.color.fromSRGB(255, 255, 255, 255); 31 | 32 | canvas.line(.{ 5.5, 5.5 }, .{ 200.5, 200.5 }, .{ 33 | .color = BLUE, 34 | .end_color = PINK, 35 | .end_width = 2, 36 | }); 37 | 38 | canvas.line(.{ 200.5, 200.5 }, .{ 200.5, 400.5 }, .{ 39 | .color = PINK, 40 | .end_color = WHITE, 41 | .width = 2, 42 | .end_width = 4, 43 | }); 44 | 45 | canvas.line(.{ 200.5, 400.5 }, .{ 400.5, 200.5 }, .{ 46 | .color = WHITE, 47 | .end_color = PINK, 48 | .width = 4, 49 | .end_width = 8, 50 | }); 51 | 52 | canvas.line(.{ 400.5, 200.5 }, .{ 400.5, 5.5 }, .{ 53 | .color = PINK, 54 | .end_color = BLUE, 55 | .width = 8, 56 | .end_width = 16, 57 | }); 58 | 59 | try surface.present(); 60 | } 61 | 62 | const seizer = @import("seizer"); 63 | const std = @import("std"); 64 | -------------------------------------------------------------------------------- /examples/clear.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | 8 | pub fn init() !void { 9 | try display.init(gpa.allocator(), seizer.getLoop()); 10 | 11 | try display.initToplevelSurface(&toplevel_surface, .{}); 12 | toplevel_surface.setOnRender(&render_listener, onRender, null); 13 | 14 | seizer.setDeinit(deinit); 15 | } 16 | 17 | fn deinit() void { 18 | toplevel_surface.deinit(); 19 | display.deinit(); 20 | _ = gpa.deinit(); 21 | } 22 | 23 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 24 | _ = listener; 25 | 26 | const canvas = try surface.canvas(); 27 | canvas.clear(.{ .r = 0.5, .g = 0.5, .b = 0.7, .a = 1.0 }); 28 | try surface.present(); 29 | } 30 | 31 | const seizer = @import("seizer"); 32 | const std = @import("std"); 33 | -------------------------------------------------------------------------------- /examples/colormapped_image.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | 8 | var image: seizer.image.Linear(seizer.color.argbf32_premultiplied) = undefined; 9 | 10 | pub const COLORMAP_SHADER_VULKAN = align_source_words: { 11 | const words_align1 = std.mem.bytesAsSlice(u32, @embedFile("./assets/colormap.frag.spv")); 12 | const aligned_words: [words_align1.len]u32 = words_align1[0..words_align1.len].*; 13 | break :align_source_words aligned_words; 14 | }; 15 | 16 | pub const ColormapUniformData = extern struct { 17 | colormap_texture_id: u32, 18 | min_value: f32, 19 | max_value: f32, 20 | }; 21 | 22 | pub fn init() !void { 23 | try display.init(gpa.allocator(), seizer.getLoop()); 24 | 25 | try display.initToplevelSurface(&toplevel_surface, .{}); 26 | toplevel_surface.setOnRender(&render_listener, onRender, null); 27 | 28 | // const zigimg_image = try seizer.zigimg.Image.fromMemory(seizer.platform.allocator(), @embedFile("assets/monochrome.png")); 29 | // defer zigimg_image.deinit(); 30 | image = try seizer.image.Linear(seizer.color.argbf32_premultiplied).fromMemory(gpa.allocator(), @embedFile("./assets/monochrome.png")); 31 | errdefer image.free(gpa.allocator()); 32 | 33 | // setup global deinit callback 34 | seizer.setDeinit(deinit); 35 | } 36 | 37 | pub fn deinit() void { 38 | image.free(gpa.allocator()); 39 | 40 | toplevel_surface.deinit(); 41 | display.deinit(); 42 | _ = gpa.deinit(); 43 | } 44 | 45 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 46 | _ = listener; 47 | 48 | const canvas = try surface.canvas(); 49 | canvas.clear(.{ .r = 0.7, .g = 0.2, .b = 0.2, .a = 1.0 }); 50 | 51 | // // split our canvas into two different modes of rendering 52 | // const regular_canvas_rendering = canvas.begin(render_buffer, .{ 53 | // .window_size = window_size, 54 | // .window_scale = window_scale, 55 | // .clear_color = .{ 0.7, 0.5, 0.5, 1.0 }, 56 | // }); 57 | 58 | // const colormap_texture_id = canvas.addTexture(colormap_texture); 59 | // const colormap_canvas_rendering = regular_canvas_rendering.withPipeline(colormap_pipeline, &.{ 60 | // .{ 61 | // .binding = 2, 62 | // .data = std.mem.asBytes(&ColormapUniformData{ 63 | // .min_value = 1.0 / @as(f32, @floatFromInt(std.math.maxInt(u16))), 64 | // .max_value = 1, 65 | // .colormap_texture_id = colormap_texture_id, 66 | // }), 67 | // }, 68 | // }); 69 | 70 | // // render the image twice, once with the regular shader and one with our colormapping shader 71 | // regular_canvas_rendering.rect(.{ 0, 0 }, .{ 480, 480 }, .{ .texture = texture }); 72 | canvas.textureRect(seizer.geometry.AABB(f64).init(.{ 0, 0 }, .{ 480, 480 }), image.asSlice(), .{}); 73 | // colormap_canvas_rendering.rect(.{ 480, 0 }, .{ 480, 480 }, .{ .texture = texture }); 74 | 75 | try surface.present(); 76 | } 77 | 78 | const seizer = @import("seizer"); 79 | const std = @import("std"); 80 | -------------------------------------------------------------------------------- /examples/cutscene.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | var input_listener: seizer.Display.ToplevelSurface.OnInputListener = undefined; 8 | 9 | var font: seizer.Canvas.Font = undefined; 10 | var stack_cutscene: []align(16) u8 = undefined; 11 | var frame_cutscene: seizer.libcoro.Frame = undefined; 12 | var message: []const u8 = ""; 13 | var clear_color: seizer.color.argbf32_premultiplied = seizer.color.argbf32_premultiplied.BLACK; 14 | 15 | pub fn init() !void { 16 | try display.init(gpa.allocator(), seizer.getLoop()); 17 | 18 | try display.initToplevelSurface(&toplevel_surface, .{}); 19 | toplevel_surface.setOnRender(&render_listener, onRender, null); 20 | toplevel_surface.setOnInput(&input_listener, onInput, null); 21 | 22 | font = try seizer.Canvas.Font.fromFileContents( 23 | gpa.allocator(), 24 | @embedFile("./assets/PressStart2P_8.fnt"), 25 | &.{ 26 | .{ .name = "PressStart2P_8.png", .contents = @embedFile("./assets/PressStart2P_8.png") }, 27 | }, 28 | ); 29 | errdefer font.deinit(); 30 | 31 | stack_cutscene = try seizer.libcoro.stackAlloc(gpa.allocator(), null); 32 | errdefer gpa.allocator().free(stack_cutscene); 33 | const coro = try seizer.libcoro.xasync(cutsceneCoro, .{}, stack_cutscene); 34 | frame_cutscene = coro.frame(); 35 | errdefer frame_cutscene.deinit(); 36 | 37 | seizer.setDeinit(deinit); 38 | } 39 | 40 | fn deinit() void { 41 | frame_cutscene.deinit(); 42 | gpa.allocator().free(stack_cutscene); 43 | font.deinit(); 44 | toplevel_surface.deinit(); 45 | display.deinit(); 46 | _ = gpa.deinit(); 47 | } 48 | 49 | fn onInput(listener: *seizer.Display.ToplevelSurface.OnInputListener, surface: *seizer.Display.ToplevelSurface, event: seizer.input.Event) !void { 50 | _ = listener; 51 | if (event == .click) { 52 | if (event.click.pressed) { 53 | if (frame_cutscene.status != .Done) { 54 | seizer.libcoro.xresume(frame_cutscene); 55 | try surface.requestAnimationFrame(); 56 | } 57 | if (frame_cutscene.status == .Done) { 58 | surface.hide(); 59 | } 60 | } 61 | } 62 | } 63 | 64 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 65 | _ = listener; 66 | 67 | const canvas = try surface.canvas(); 68 | canvas.clear(clear_color); 69 | _ = canvas.writeText(&font, .{ 32, 32 }, message, .{}); 70 | try surface.present(); 71 | } 72 | 73 | fn cutsceneCoro() void { 74 | message = "Hello World! Click to continue."; 75 | seizer.libcoro.xsuspend(); 76 | message = "This is a scene scripted using libcoro."; 77 | clear_color = .{ .r = 0.5, .g = 0.5, .b = 0.7, .a = 1.0 }; 78 | seizer.libcoro.xsuspend(); 79 | message = "It means we can write linear code that pauses execution\nuntil we tell it to resume."; 80 | clear_color = .{ .r = 0.1, .g = 0.1, .b = 0.6, .a = 1.0 }; 81 | seizer.libcoro.xsuspend(); 82 | message = "It's very useful for cutscenes!"; 83 | seizer.libcoro.xsuspend(); 84 | message = "Bye!"; 85 | seizer.libcoro.xsuspend(); 86 | message = ""; 87 | } 88 | 89 | const seizer = @import("seizer"); 90 | const std = @import("std"); 91 | -------------------------------------------------------------------------------- /examples/fill_rect.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | 8 | pub fn init() !void { 9 | try display.init(gpa.allocator(), seizer.getLoop()); 10 | 11 | try display.initToplevelSurface(&toplevel_surface, .{}); 12 | toplevel_surface.setOnRender(&render_listener, onRender, null); 13 | 14 | seizer.setDeinit(deinit); 15 | } 16 | 17 | /// This is a global deinit, not window specific. This is important because windows can hold onto Graphics resources. 18 | fn deinit() void { 19 | toplevel_surface.deinit(); 20 | display.deinit(); 21 | _ = gpa.deinit(); 22 | } 23 | 24 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 25 | _ = listener; 26 | 27 | const canvas = try surface.canvas(); 28 | canvas.clear(.{ .r = 0.5, .g = 0.5, .b = 0.7, .a = 1.0 }); 29 | 30 | const color_square = seizer.geometry.AABB(f64).init(.{ .{ 0, 0 }, .{ 25, 25 } }); 31 | 32 | canvas.fillRect(color_square.translate(.{ 25, 25 }), .{ .r = 1, .g = 0, .b = 0, .a = 1 }, .{}); 33 | canvas.fillRect(color_square.translate(.{ 75, 25 }), .{ .r = 0, .g = 1, .b = 0, .a = 1 }, .{}); 34 | canvas.fillRect(color_square.translate(.{ 125, 25 }), .{ .r = 0, .g = 0, .b = 1, .a = 1 }, .{}); 35 | canvas.fillRect(color_square.translate(.{ 175, 25 }), .{ .r = 0, .g = 0, .b = 0, .a = 1 }, .{}); 36 | canvas.fillRect(color_square.translate(.{ 225, 25 }), .{ .r = 0, .g = 0, .b = 0, .a = 0 }, .{}); 37 | 38 | try surface.present(); 39 | } 40 | 41 | const seizer = @import("seizer"); 42 | const std = @import("std"); 43 | -------------------------------------------------------------------------------- /examples/multi_window.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | 6 | var font: seizer.Canvas.Font = undefined; 7 | 8 | var next_window_id: usize = 0; 9 | var open_window_count: usize = 0; 10 | 11 | const WindowData = struct { 12 | title: ?[:0]const u8 = null, 13 | toplevel_surface: seizer.Display.ToplevelSurface, 14 | close_listener: seizer.Display.ToplevelSurface.CloseListener, 15 | event_listener: seizer.Display.ToplevelSurface.OnInputListener, 16 | render_listener: seizer.Display.ToplevelSurface.OnRenderListener, 17 | }; 18 | 19 | pub fn init() !void { 20 | try display.init(gpa.allocator(), seizer.getLoop()); 21 | 22 | font = try seizer.Canvas.Font.fromFileContents( 23 | gpa.allocator(), 24 | @embedFile("./assets/PressStart2P_8.fnt"), 25 | &.{ 26 | .{ .name = "PressStart2P_8.png", .contents = @embedFile("./assets/PressStart2P_8.png") }, 27 | }, 28 | ); 29 | errdefer font.deinit(); 30 | 31 | try createNewWindow(null); 32 | 33 | seizer.setDeinit(deinit); 34 | } 35 | 36 | pub fn deinit() void { 37 | font.deinit(); 38 | display.deinit(); 39 | _ = gpa.deinit(); 40 | } 41 | 42 | fn createNewWindow(title: ?[:0]const u8) !void { 43 | const window_data = try gpa.allocator().create(WindowData); 44 | errdefer gpa.allocator().destroy(window_data); 45 | 46 | window_data.* = .{ 47 | .title = title, 48 | .toplevel_surface = undefined, 49 | .close_listener = undefined, 50 | .event_listener = undefined, 51 | .render_listener = undefined, 52 | }; 53 | 54 | try display.initToplevelSurface(&window_data.toplevel_surface, .{}); 55 | window_data.toplevel_surface.setOnClose(&window_data.close_listener, onToplevelClose); 56 | window_data.toplevel_surface.setOnInput(&window_data.event_listener, onToplevelInputEvent, null); 57 | window_data.toplevel_surface.setOnRender(&window_data.render_listener, onRender, null); 58 | 59 | next_window_id += 1; 60 | open_window_count += 1; 61 | } 62 | 63 | fn onToplevelClose(close_listener: *seizer.Display.ToplevelSurface.CloseListener, surface: *seizer.Display.ToplevelSurface) !void { 64 | const window_data: *WindowData = @fieldParentPtr("close_listener", close_listener); 65 | _ = surface; 66 | 67 | window_data.toplevel_surface.deinit(); 68 | if (window_data.title) |title| gpa.allocator().free(title); 69 | gpa.allocator().destroy(window_data); 70 | open_window_count -= 1; 71 | } 72 | 73 | fn onToplevelInputEvent(listener: *seizer.Display.ToplevelSurface.OnInputListener, surface: *seizer.Display.ToplevelSurface, event: seizer.input.Event) !void { 74 | _ = listener; 75 | _ = surface; 76 | switch (event) { 77 | .key => |key| switch (key.key) { 78 | .unicode => |unicode| switch (unicode) { 79 | 'n' => if (key.action == .press) { 80 | const title = try std.fmt.allocPrintZ(gpa.allocator(), "Window {}", .{next_window_id}); 81 | errdefer gpa.allocator().free(title); 82 | 83 | try createNewWindow(title); 84 | }, 85 | else => {}, 86 | }, 87 | else => {}, 88 | }, 89 | 90 | else => {}, 91 | } 92 | } 93 | 94 | fn onRender(render_listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 95 | const window_data: *WindowData = @fieldParentPtr("render_listener", render_listener); 96 | 97 | const canvas = try surface.canvas(); 98 | const window_size = canvas.size(); 99 | canvas.clear(.{ .r = 0.5, .g = 0.5, .b = 0.7, .a = 1.0 }); 100 | 101 | if (window_data.title) |title| { 102 | _ = canvas.writeText(&font, .{ window_size[0] / 2, window_size[1] / 2 }, title, .{ 103 | .scale = 3, 104 | .@"align" = .center, 105 | .baseline = .middle, 106 | }); 107 | } else { 108 | _ = canvas.writeText(&font, .{ window_size[0] / 2, window_size[1] / 2 }, "Press N to spawn new window", .{ 109 | .scale = 3, 110 | .@"align" = .center, 111 | .baseline = .middle, 112 | }); 113 | } 114 | 115 | try surface.present(); 116 | } 117 | 118 | const seizer = @import("seizer"); 119 | const std = @import("std"); 120 | -------------------------------------------------------------------------------- /examples/scene.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const seizer = @import("seizer"); 3 | const gl = seizer.gl; 4 | const builtin = @import("builtin"); 5 | 6 | const SceneManager = seizer.scene.Manager(Context, &[_]type{ 7 | Scene1, 8 | Scene2, 9 | Scene3, 10 | }); 11 | 12 | const Context = struct { 13 | scene: SceneManager, 14 | alloc: std.mem.Allocator, 15 | }; 16 | 17 | // Call the comptime function `seizer.run`, which will ensure that everything is 18 | // set up for the platform we are targeting. 19 | pub usingnamespace seizer.run(.{ 20 | .init = init, 21 | .event = event, 22 | .render = render, 23 | .update = update, 24 | }); 25 | 26 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 27 | var context: Context = undefined; 28 | 29 | fn init() !void { 30 | context = .{ 31 | .scene = try SceneManager.init(gpa.allocator(), &context, .{}), 32 | .alloc = gpa.allocator(), 33 | }; 34 | try context.scene.push(.@"scene.Scene1"); 35 | } 36 | 37 | fn update(a: f64, delta: f64) !void { 38 | try context.scene.update(a, delta); 39 | } 40 | 41 | fn event(e: seizer.event.Event) !void { 42 | try context.scene.event(e); 43 | switch (e) { 44 | .Quit => seizer.quit(), 45 | else => {}, 46 | } 47 | } 48 | 49 | fn render(alpha: f64) !void { 50 | try context.scene.render(alpha); 51 | } 52 | 53 | pub const Scene1 = struct { 54 | ctx: *Context, 55 | pub fn init(ctx: *Context) !@This() { 56 | std.log.info("Enter scene 1", .{}); 57 | return @This(){.ctx = ctx}; 58 | } 59 | pub fn deinit(_: *@This()) void { 60 | std.log.info("Exit scene 1", .{}); 61 | } 62 | pub fn event(this: *@This(), e: seizer.event.Event) !void { 63 | switch (e) { 64 | .MouseButtonDown => |mouse| { 65 | switch (mouse.button) { 66 | // .Right => this.ctx.scene.pop(), 67 | .Left => try this.ctx.scene.push(.@"scene.Scene2"), 68 | else => {}, 69 | } 70 | }, 71 | else => {}, 72 | } 73 | } 74 | pub fn render(this: *@This(), alpha: f64) !void { 75 | _ = this; 76 | _ = alpha; 77 | 78 | gl.clearColor(0.7, 0.5, 0.5, 1.0); 79 | gl.clear(gl.COLOR_BUFFER_BIT); 80 | } 81 | }; 82 | 83 | 84 | pub const Scene2 = struct { 85 | ctx: *Context, 86 | string: []const u8, 87 | pub fn init(ctx: *Context) !@This() { 88 | std.log.info("Enter scene 2", .{}); 89 | const string = try std.fmt.allocPrint(ctx.alloc, "Help", .{}); 90 | return @This(){.ctx = ctx, .string = string}; 91 | } 92 | pub fn deinit(this: *@This()) void { 93 | std.log.info("Exit scene 2", .{}); 94 | this.ctx.alloc.free(this.string); 95 | } 96 | pub fn event(this: *@This(), e: seizer.event.Event) !void { 97 | switch (e) { 98 | .MouseButtonDown => |mouse| { 99 | switch (mouse.button) { 100 | .Right => this.ctx.scene.pop(), 101 | .Left => try this.ctx.scene.replace(.@"scene.Scene3"), 102 | else => {}, 103 | } 104 | }, 105 | else => {}, 106 | } 107 | } 108 | pub fn render(this: *@This(), alpha: f64) !void { 109 | _ = this; 110 | _ = alpha; 111 | 112 | gl.clearColor(0.5, 0.7, 0.5, 1.0); 113 | gl.clear(gl.COLOR_BUFFER_BIT); 114 | } 115 | }; 116 | 117 | pub const Scene3 = struct { 118 | ctx: *Context, 119 | string: []const u8, 120 | pub fn init(ctx: *Context) !@This() { 121 | std.log.info("Enter scene 3", .{}); 122 | const string = try std.fmt.allocPrint(ctx.alloc, "Help", .{}); 123 | return @This(){.ctx = ctx, .string = string}; 124 | } 125 | pub fn deinit(this: *@This()) void { 126 | std.log.info("Exit scene 3", .{}); 127 | this.ctx.alloc.free(this.string); 128 | } 129 | pub fn event(this: *@This(), e: seizer.event.Event) !void { 130 | switch (e) { 131 | .MouseButtonDown => |mouse| { 132 | switch (mouse.button) { 133 | .Right => this.ctx.scene.pop(), 134 | // .Left => try this.ctx.scene.push(.Scene2), 135 | else => {}, 136 | } 137 | }, 138 | else => {}, 139 | } 140 | } 141 | pub fn render(this: *@This(), alpha: f64) !void { 142 | _ = this; 143 | _ = alpha; 144 | 145 | gl.clearColor(0.5, 0.5, 0.7, 1.0); 146 | gl.clear(gl.COLOR_BUFFER_BIT); 147 | } 148 | }; 149 | -------------------------------------------------------------------------------- /examples/sprite_batch.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | 8 | var font: seizer.Canvas.Font = undefined; 9 | var player_image: seizer.image.Linear(seizer.color.argbf32_premultiplied) = undefined; 10 | var sprites: std.MultiArrayList(Sprite) = .{}; 11 | 12 | var spawn_timer_duration: u32 = 10; 13 | var spawn_timer: u32 = 0; 14 | var prng: std.rand.DefaultPrng = undefined; 15 | 16 | var frametimes: [256]u64 = [_]u64{0} ** 256; 17 | var frametime_index: usize = 0; 18 | var between_frame_timer: std.time.Timer = undefined; 19 | var time_between_frames: [256]u64 = [_]u64{0} ** 256; 20 | 21 | const Sprite = struct { 22 | pos: [2]f64, 23 | vel: [2]f64, 24 | size: [2]f64, 25 | }; 26 | const WorldBounds = struct { min: [2]f64, max: [2]f64 }; 27 | 28 | pub fn move(positions: [][2]f64, velocities: []const [2]f64) void { 29 | for (positions, velocities) |*pos, vel| { 30 | pos[0] += vel[0]; 31 | pos[1] += vel[1]; 32 | } 33 | } 34 | 35 | pub fn keepInBounds(positions: []const [2]f64, velocities: [][2]f64, sizes: []const [2]f64, world_bounds: WorldBounds) void { 36 | for (positions, velocities, sizes) |pos, *vel, size| { 37 | if (pos[0] < world_bounds.min[0] and vel[0] < 0) vel[0] = -vel[0]; 38 | if (pos[1] < world_bounds.min[1] and vel[1] < 0) vel[1] = -vel[1]; 39 | if (pos[0] + size[0] > world_bounds.max[0] and vel[0] > 0) vel[0] = -vel[0]; 40 | if (pos[1] + size[1] > world_bounds.max[1] and vel[1] > 0) vel[1] = -vel[1]; 41 | } 42 | } 43 | 44 | pub fn init() !void { 45 | prng = std.Random.DefaultPrng.init(1337); 46 | 47 | try display.init(gpa.allocator(), seizer.getLoop()); 48 | 49 | try display.initToplevelSurface(&toplevel_surface, .{}); 50 | toplevel_surface.setOnRender(&render_listener, onRender, null); 51 | 52 | font = try seizer.Canvas.Font.fromFileContents( 53 | gpa.allocator(), 54 | @embedFile("./assets/PressStart2P_8.fnt"), 55 | &.{ 56 | .{ .name = "PressStart2P_8.png", .contents = @embedFile("./assets/PressStart2P_8.png") }, 57 | }, 58 | ); 59 | errdefer font.deinit(); 60 | 61 | player_image = try seizer.image.Linear(seizer.color.argbf32_premultiplied).fromMemory(gpa.allocator(), @embedFile("assets/wedge.png")); 62 | errdefer player_image.free(gpa.allocator()); 63 | 64 | between_frame_timer = try std.time.Timer.start(); 65 | 66 | seizer.setDeinit(deinit); 67 | } 68 | 69 | pub fn deinit() void { 70 | font.deinit(); 71 | player_image.free(gpa.allocator()); 72 | sprites.deinit(gpa.allocator()); 73 | toplevel_surface.deinit(); 74 | display.deinit(); 75 | _ = gpa.deinit(); 76 | } 77 | 78 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 79 | _ = listener; 80 | const window_size = surface.current_configuration.window_size; 81 | 82 | time_between_frames[frametime_index] = between_frame_timer.lap(); 83 | 84 | const frame_start = std.time.nanoTimestamp(); 85 | defer { 86 | const frame_end = std.time.nanoTimestamp(); 87 | const duration: u64 = @intCast(frame_end - frame_start); 88 | frametimes[frametime_index] = duration; 89 | frametime_index += 1; 90 | frametime_index %= frametimes.len; 91 | } 92 | const world_bounds = WorldBounds{ 93 | .min = .{ 0, 0 }, 94 | .max = .{ @floatFromInt(window_size[0]), @floatFromInt(window_size[1]) }, 95 | }; 96 | 97 | // update sprites 98 | { 99 | const sprites_slice = sprites.slice(); 100 | keepInBounds(sprites_slice.items(.pos), sprites_slice.items(.vel), sprites_slice.items(.size), world_bounds); 101 | move(sprites_slice.items(.pos), sprites_slice.items(.vel)); 102 | } 103 | 104 | spawn_timer -|= 1; 105 | if (spawn_timer <= 1 and sprites.len < 1) { 106 | spawn_timer = spawn_timer_duration; 107 | 108 | const world_size = [2]f64{ 109 | world_bounds.max[0] - world_bounds.min[0], 110 | world_bounds.max[1] - world_bounds.min[1], 111 | }; 112 | 113 | const scale = prng.random().float(f64) * 3; 114 | const size = [2]f64{ 115 | @as(f64, @floatFromInt(player_image.size[0])) * scale, 116 | @as(f64, @floatFromInt(player_image.size[1])) * scale, 117 | }; 118 | try sprites.append(gpa.allocator(), .{ 119 | .pos = .{ 120 | prng.random().float(f64) * (world_size[0] - size[0]) + world_bounds.min[0], 121 | prng.random().float(f64) * (world_size[1] - size[1]) + world_bounds.min[1], 122 | }, 123 | .vel = .{ 124 | prng.random().float(f64) * 10 - 5, 125 | prng.random().float(f64) * 10 - 5, 126 | }, 127 | .size = size, 128 | }); 129 | } 130 | 131 | // begin rendering 132 | 133 | const canvas = try surface.canvas(); 134 | canvas.clear(.{ .r = 0.5, .g = 0.5, .b = 0.7, .a = 1.0 }); 135 | 136 | for (sprites.items(.pos), sprites.items(.size)) |pos, size| { 137 | canvas.fillRect( 138 | seizer.geometry.AABB(f64).init(pos, .{ pos[0] + size[0], pos[1] + size[1] }), 139 | seizer.color.argbf32_premultiplied.TRANSPARENT, 140 | .{}, 141 | ); 142 | canvas.textureRect( 143 | seizer.geometry.AABB(f64).init(pos, .{ pos[0] + size[0], pos[1] + size[1] }), 144 | player_image.asSlice(), 145 | .{}, 146 | ); 147 | } 148 | 149 | var text_pos = [2]f64{ 50, 50 }; 150 | text_pos[1] += canvas.printText(&font, text_pos, "sprite count = {}", .{sprites.len}, .{})[1]; 151 | 152 | var frametime_total: f64 = 0; 153 | for (frametimes) |f| { 154 | frametime_total += @floatFromInt(f); 155 | } 156 | text_pos[1] += canvas.printText(&font, text_pos, "avg. frametime = {d:0.2} ms", .{frametime_total / @as(f64, @floatFromInt(frametimes.len)) / std.time.ns_per_ms}, .{})[1]; 157 | 158 | var between_frame_total: f64 = 0; 159 | for (time_between_frames) |f| { 160 | between_frame_total += @floatFromInt(f); 161 | } 162 | text_pos[1] += canvas.printText(&font, text_pos, "avg. time between frames = {d:0.2} ms", .{between_frame_total / @as(f64, @floatFromInt(frametimes.len)) / std.time.ns_per_ms}, .{})[1]; 163 | 164 | try surface.requestAnimationFrame(); 165 | try surface.present(); 166 | } 167 | 168 | const seizer = @import("seizer"); 169 | const std = @import("std"); 170 | -------------------------------------------------------------------------------- /examples/texture_rect.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | 8 | var image: seizer.image.Linear(seizer.color.argbf32_premultiplied) = undefined; 9 | 10 | pub fn init() !void { 11 | try display.init(gpa.allocator(), seizer.getLoop()); 12 | 13 | try display.initToplevelSurface(&toplevel_surface, .{}); 14 | toplevel_surface.setOnRender(&render_listener, onRender, null); 15 | 16 | image = try seizer.image.Linear(seizer.color.argbf32_premultiplied).fromMemory(gpa.allocator(), @embedFile("./assets/wedge.png")); 17 | errdefer image.free(gpa.allocator()); 18 | 19 | seizer.setDeinit(deinit); 20 | } 21 | 22 | /// This is a global deinit, not window specific. This is important because windows can hold onto Graphics resources. 23 | fn deinit() void { 24 | image.free(gpa.allocator()); 25 | toplevel_surface.deinit(); 26 | display.deinit(); 27 | _ = gpa.deinit(); 28 | } 29 | 30 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 31 | _ = listener; 32 | 33 | const canvas = try surface.canvas(); 34 | canvas.clear(.{ .r = 0.5, .g = 0.5, .b = 0.7, .a = 1.0 }); 35 | 36 | canvas.textureRect( 37 | seizer.geometry.AABB(f64).init( 38 | .{ 50, 50 }, 39 | .{ @max(canvas.size()[0] - 50, 0), @max(canvas.size()[1] - 50, 0) }, 40 | ), 41 | image.asSlice(), 42 | .{}, 43 | ); 44 | 45 | try surface.present(); 46 | } 47 | 48 | const seizer = @import("seizer"); 49 | const std = @import("std"); 50 | -------------------------------------------------------------------------------- /examples/tinyvg.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | var render_listener_cursor: seizer.Display.Surface.OnRenderListener = undefined; 8 | 9 | var cursor_surface: seizer.Display.Surface = undefined; 10 | var shield_tvg: seizer.image.TVG = undefined; 11 | var cursor_tvg: seizer.image.TVG = undefined; 12 | var shield_image: seizer.image.Linear(seizer.color.argbf32_premultiplied) = undefined; 13 | var cursor_image: seizer.image.Linear(seizer.color.argbf32_premultiplied) = undefined; 14 | 15 | pub fn init() !void { 16 | try display.init(gpa.allocator(), seizer.getLoop()); 17 | 18 | try display.initToplevelSurface(&toplevel_surface, .{}); 19 | toplevel_surface.setOnRender(&render_listener, onRender, null); 20 | 21 | shield_tvg = try seizer.image.TVG.fromMemory(gpa.allocator(), &shield_icon_tvg); 22 | errdefer shield_tvg.deinit(gpa.allocator()); 23 | cursor_tvg = try seizer.image.TVG.fromMemory(gpa.allocator(), cursor_none_tvg); 24 | errdefer cursor_tvg.deinit(gpa.allocator()); 25 | 26 | shield_image = try ImageLinear.alloc(gpa.allocator(), shield_tvg.size()); 27 | cursor_image = try ImageLinear.alloc(gpa.allocator(), cursor_tvg.size()); 28 | 29 | try shield_tvg.rasterize(&shield_image, .{}); 30 | try cursor_tvg.rasterize(&cursor_image, .{}); 31 | 32 | try display.initSurface(&cursor_surface, .{ .size = cursor_image.size }); 33 | cursor_surface.setOnRender(&render_listener_cursor, onCursorRender, null); 34 | display.seat.?.cursor_wl_surface = &cursor_surface; 35 | display.seat.?.cursor_hotspot = .{ 9, 5 }; 36 | 37 | seizer.setDeinit(deinit); 38 | } 39 | 40 | pub fn deinit() void { 41 | cursor_tvg.deinit(gpa.allocator()); 42 | cursor_image.free(gpa.allocator()); 43 | cursor_surface.deinit(); 44 | 45 | shield_tvg.deinit(gpa.allocator()); 46 | shield_image.free(gpa.allocator()); 47 | 48 | toplevel_surface.deinit(); 49 | 50 | display.deinit(); 51 | 52 | _ = gpa.deinit(); 53 | } 54 | 55 | fn onCursorRender(listener: *seizer.Display.Surface.OnRenderListener, surface: *seizer.Display.Surface) anyerror!void { 56 | _ = listener; 57 | 58 | const canvas = try surface.canvas(); 59 | canvas.clear(.{ .r = 0, .g = 0, .b = 0, .a = 0 }); 60 | canvas.blit(.{ 0, 0 }, cursor_image.asSlice()); 61 | 62 | try surface.present(); 63 | } 64 | 65 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 66 | _ = listener; 67 | 68 | const canvas = try surface.canvas(); 69 | canvas.clear(.{ .r = 0.5, .g = 0.5, .b = 0.7, .a = 1.0 }); 70 | 71 | canvas.blit(.{ 50, 50 }, shield_image.asSlice()); 72 | canvas.blit(.{ 100, 50 }, cursor_image.asSlice()); 73 | 74 | try surface.present(); 75 | } 76 | 77 | const shield_icon_tvg = [_]u8{ 78 | 0x72, 0x56, 0x01, 0x42, 0x18, 0x18, 0x02, 0x29, 0xad, 0xff, 0xff, 0xff, 79 | 0xf1, 0xe8, 0xff, 0x03, 0x02, 0x00, 0x04, 0x05, 0x03, 0x30, 0x04, 0x00, 80 | 0x0c, 0x14, 0x02, 0x2c, 0x03, 0x0c, 0x42, 0x1b, 0x57, 0x30, 0x5c, 0x03, 81 | 0x45, 0x57, 0x54, 0x42, 0x54, 0x2c, 0x02, 0x14, 0x45, 0x44, 0x03, 0x40, 82 | 0x4b, 0x38, 0x51, 0x30, 0x54, 0x03, 0x28, 0x51, 0x20, 0x4b, 0x1b, 0x44, 83 | 0x03, 0x1a, 0x42, 0x19, 0x40, 0x18, 0x3e, 0x03, 0x18, 0x37, 0x23, 0x32, 84 | 0x30, 0x32, 0x03, 0x3d, 0x32, 0x48, 0x37, 0x48, 0x3e, 0x03, 0x47, 0x40, 85 | 0x46, 0x42, 0x45, 0x44, 0x30, 0x14, 0x03, 0x36, 0x14, 0x3c, 0x19, 0x3c, 86 | 0x20, 0x03, 0x3c, 0x26, 0x37, 0x2c, 0x30, 0x2c, 0x03, 0x2a, 0x2c, 0x24, 87 | 0x27, 0x24, 0x20, 0x03, 0x24, 0x1a, 0x29, 0x14, 0x30, 0x14, 0x00, 88 | }; 89 | const cursor_none_tvg = @embedFile("assets/cursor_none.tvg"); 90 | 91 | const seizer = @import("seizer"); 92 | const std = @import("std"); 93 | const ImageLinear = seizer.image.Linear(seizer.color.argbf32_premultiplied); 94 | -------------------------------------------------------------------------------- /examples/ui_plot_sine.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | var input_listener: seizer.Display.ToplevelSurface.OnInputListener = undefined; 8 | 9 | var font: seizer.Canvas.Font = undefined; 10 | var ui_image: seizer.image.Linear(seizer.color.argbf32_premultiplied) = undefined; 11 | var _stage: *seizer.ui.Stage = undefined; 12 | 13 | pub fn init() !void { 14 | try display.init(gpa.allocator(), seizer.getLoop()); 15 | 16 | try display.initToplevelSurface(&toplevel_surface, .{}); 17 | toplevel_surface.setOnInput(&input_listener, onToplevelInputEvent, null); 18 | toplevel_surface.setOnRender(&render_listener, onRender, null); 19 | 20 | font = try seizer.Canvas.Font.fromFileContents( 21 | gpa.allocator(), 22 | @embedFile("./assets/PressStart2P_8.fnt"), 23 | &.{ 24 | .{ .name = "PressStart2P_8.png", .contents = @embedFile("./assets/PressStart2P_8.png") }, 25 | }, 26 | ); 27 | errdefer font.deinit(); 28 | 29 | ui_image = try seizer.image.Linear(seizer.color.argbf32_premultiplied).fromMemory(gpa.allocator(), @embedFile("./assets/ui.png")); 30 | errdefer ui_image.free(gpa.allocator()); 31 | 32 | _stage = try seizer.ui.Stage.create(gpa.allocator(), .{ 33 | .padding = .{ 34 | .min = .{ 16, 16 }, 35 | .max = .{ 16, 16 }, 36 | }, 37 | .text_font = &font, 38 | .text_scale = 1, 39 | .text_color = seizer.color.argbf32_premultiplied.WHITE, 40 | .background_image = seizer.Canvas.NinePatch.init(ui_image.slice(.{ 0, 0 }, .{ 48, 48 }), seizer.geometry.Inset(f64).initXY(16, 16)), 41 | .background_color = seizer.color.argbf32_premultiplied.WHITE, 42 | }); 43 | errdefer _stage.destroy(); 44 | 45 | var flexbox = try seizer.ui.Element.FlexBox.create(_stage); 46 | defer flexbox.element().release(); 47 | flexbox.justification = .center; 48 | flexbox.cross_align = .center; 49 | _stage.setRoot(flexbox.element()); 50 | 51 | const frame = try seizer.ui.Element.Frame.create(_stage); 52 | defer frame.element().release(); 53 | try flexbox.appendChild(frame.element()); 54 | 55 | var frame_flexbox = try seizer.ui.Element.FlexBox.create(_stage); 56 | defer frame_flexbox.element().release(); 57 | frame_flexbox.justification = .center; 58 | frame_flexbox.cross_align = .center; 59 | frame.setChild(frame_flexbox.element()); 60 | 61 | const hello_world_label = try seizer.ui.Element.Label.create(_stage, "y = sin(x)"); 62 | defer hello_world_label.element().release(); 63 | hello_world_label.style = _stage.default_style.with(.{ 64 | .text_color = seizer.color.argbf32_premultiplied.BLACK, 65 | .background_image = seizer.Canvas.NinePatch.init(ui_image.slice(.{ 48, 0 }, .{ 48, 48 }), seizer.geometry.Inset(f64).initXY(16, 16)), 66 | }); 67 | try frame_flexbox.appendChild(hello_world_label.element()); 68 | 69 | const sine_plot = try seizer.ui.Element.Plot.create(_stage); 70 | defer sine_plot.element().release(); 71 | try sine_plot.lines.put(_stage.gpa, try _stage.gpa.dupe(u8, "y = sin(x)"), .{}); 72 | sine_plot.x_range = .{ 0, std.math.tau }; 73 | sine_plot.y_range = .{ -1, 1 }; 74 | try frame_flexbox.appendChild(sine_plot.element()); 75 | 76 | try sine_plot.lines.getPtr("y = sin(x)").?.x.ensureTotalCapacity(_stage.gpa, 360); 77 | try sine_plot.lines.getPtr("y = sin(x)").?.y.ensureTotalCapacity(_stage.gpa, 360); 78 | 79 | sine_plot.lines.getPtr("y = sin(x)").?.x.items.len = 360; 80 | sine_plot.lines.getPtr("y = sin(x)").?.y.items.len = 360; 81 | 82 | const x_array = sine_plot.lines.getPtr("y = sin(x)").?.x.items; 83 | const y_array = sine_plot.lines.getPtr("y = sin(x)").?.y.items; 84 | for (x_array, y_array, 0..) |*x, *y, i| { 85 | x.* = std.math.tau * @as(f32, @floatFromInt(i)) / 360; 86 | y.* = @sin(x.*); 87 | } 88 | 89 | seizer.setDeinit(deinit); 90 | } 91 | 92 | pub fn deinit() void { 93 | _stage.destroy(); 94 | 95 | font.deinit(); 96 | ui_image.free(gpa.allocator()); 97 | 98 | toplevel_surface.deinit(); 99 | display.deinit(); 100 | _ = gpa.deinit(); 101 | } 102 | 103 | fn onToplevelInputEvent(listener: *seizer.Display.ToplevelSurface.OnInputListener, surface: *seizer.Display.ToplevelSurface, event: seizer.input.Event) !void { 104 | _ = listener; 105 | if (_stage.processEvent(event)) |_| { 106 | try surface.requestAnimationFrame(); 107 | try display.connection.sendRequest(@TypeOf(surface.wl_surface)._SPECIFIED_INTERFACE, surface.wl_surface, .commit, .{}); 108 | } 109 | } 110 | 111 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 112 | _ = listener; 113 | 114 | const canvas = try surface.canvas(); 115 | canvas.clear(.{ .r = 0, .g = 0, .b = 0, .a = 1.0 }); 116 | 117 | _stage.needs_layout = true; 118 | _stage.render(canvas, .{ .min = .{ 0, 0 }, .max = canvas.size() }); 119 | 120 | try surface.present(); 121 | } 122 | 123 | const seizer = @import("seizer"); 124 | const std = @import("std"); 125 | -------------------------------------------------------------------------------- /examples/ui_stage.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | var input_listener: seizer.Display.ToplevelSurface.OnInputListener = undefined; 8 | 9 | var font: seizer.Canvas.Font = undefined; 10 | var ui_image: seizer.image.Linear(seizer.color.argbf32_premultiplied) = undefined; 11 | var stage: *seizer.ui.Stage = undefined; 12 | 13 | pub fn init() !void { 14 | try display.init(gpa.allocator(), seizer.getLoop()); 15 | 16 | try display.initToplevelSurface(&toplevel_surface, .{}); 17 | toplevel_surface.setOnInput(&input_listener, onToplevelInputEvent, null); 18 | toplevel_surface.setOnRender(&render_listener, onRender, null); 19 | 20 | font = try seizer.Canvas.Font.fromFileContents( 21 | gpa.allocator(), 22 | @embedFile("./assets/PressStart2P_8.fnt"), 23 | &.{ 24 | .{ .name = "PressStart2P_8.png", .contents = @embedFile("./assets/PressStart2P_8.png") }, 25 | }, 26 | ); 27 | errdefer font.deinit(); 28 | 29 | ui_image = try seizer.image.Linear(seizer.color.argbf32_premultiplied).fromMemory(gpa.allocator(), @embedFile("./assets/ui.png")); 30 | errdefer ui_image.free(gpa.allocator()); 31 | 32 | stage = try seizer.ui.Stage.create(gpa.allocator(), .{ 33 | .padding = .{ 34 | .min = .{ 16, 16 }, 35 | .max = .{ 16, 16 }, 36 | }, 37 | .text_font = &font, 38 | .text_scale = 1, 39 | .text_color = seizer.color.argbf32_premultiplied.BLACK, 40 | .background_image = seizer.Canvas.NinePatch.init(ui_image.slice(.{ 0, 0 }, .{ 48, 48 }), seizer.geometry.Inset(f64).initXY(16, 16)), 41 | .background_color = seizer.color.argbf32_premultiplied.WHITE, 42 | }); 43 | errdefer stage.destroy(); 44 | 45 | var flexbox = try seizer.ui.Element.FlexBox.create(stage); 46 | defer flexbox.element().release(); 47 | flexbox.justification = .center; 48 | flexbox.cross_align = .center; 49 | stage.setRoot(flexbox.element()); 50 | 51 | const frame = try seizer.ui.Element.Frame.create(stage); 52 | defer frame.element().release(); 53 | try flexbox.appendChild(frame.element()); 54 | 55 | var frame_flexbox = try seizer.ui.Element.FlexBox.create(stage); 56 | defer frame_flexbox.element().release(); 57 | frame_flexbox.justification = .center; 58 | frame_flexbox.cross_align = .center; 59 | frame.setChild(frame_flexbox.element()); 60 | 61 | const hello_world_label = try seizer.ui.Element.Label.create(stage, "Hello, world!"); 62 | defer hello_world_label.element().release(); 63 | hello_world_label.style = stage.default_style.with(.{ 64 | .background_image = seizer.Canvas.NinePatch.init(ui_image.slice(.{ 48, 0 }, .{ 48, 48 }), seizer.geometry.Inset(f64).initXY(16, 16)), 65 | }); 66 | try frame_flexbox.appendChild(hello_world_label.element()); 67 | 68 | const hello_button = try seizer.ui.Element.Button.create(stage, "Hello"); 69 | defer hello_button.element().release(); 70 | 71 | hello_button.default_style.padding = .{ 72 | .min = .{ 8, 7 }, 73 | .max = .{ 8, 9 }, 74 | }; 75 | hello_button.default_style.text_color = seizer.color.argbf32_premultiplied.BLACK; 76 | hello_button.default_style.background_color = seizer.color.argbf32_premultiplied.WHITE; 77 | hello_button.default_style.background_ninepatch = seizer.Canvas.NinePatch.init(ui_image.slice(.{ 120, 24 }, .{ 24, 24 }), seizer.geometry.Inset(f64).initXY(8, 8)); 78 | 79 | hello_button.hovered_style.padding = .{ 80 | .min = .{ 8, 8 }, 81 | .max = .{ 8, 8 }, 82 | }; 83 | hello_button.hovered_style.text_color = seizer.color.argbf32_premultiplied.BLACK; 84 | hello_button.hovered_style.background_color = seizer.color.argbf32_premultiplied.WHITE; 85 | hello_button.hovered_style.background_ninepatch = seizer.Canvas.NinePatch.init(ui_image.slice(.{ 96, 0 }, .{ 24, 24 }), seizer.geometry.Inset(f64).initXY(8, 8)); 86 | 87 | hello_button.clicked_style.padding = .{ 88 | .min = .{ 8, 9 }, 89 | .max = .{ 8, 7 }, 90 | }; 91 | hello_button.clicked_style.text_color = seizer.color.argbf32_premultiplied.BLACK; 92 | hello_button.clicked_style.background_color = seizer.color.argbf32_premultiplied.WHITE; 93 | hello_button.clicked_style.background_ninepatch = seizer.Canvas.NinePatch.init(ui_image.slice(.{ 120, 0 }, .{ 24, 24 }), seizer.geometry.Inset(f64).initXY(8, 8)); 94 | 95 | try frame_flexbox.appendChild(hello_button.element()); 96 | 97 | seizer.setDeinit(deinit); 98 | } 99 | 100 | pub fn deinit() void { 101 | stage.destroy(); 102 | 103 | font.deinit(); 104 | ui_image.free(gpa.allocator()); 105 | 106 | toplevel_surface.deinit(); 107 | display.deinit(); 108 | _ = gpa.deinit(); 109 | } 110 | 111 | fn onToplevelInputEvent(listener: *seizer.Display.ToplevelSurface.OnInputListener, surface: *seizer.Display.ToplevelSurface, event: seizer.input.Event) !void { 112 | _ = listener; 113 | if (stage.processEvent(event)) |_| { 114 | try surface.requestAnimationFrame(); 115 | try display.connection.sendRequest(@TypeOf(surface.wl_surface)._SPECIFIED_INTERFACE, surface.wl_surface, .commit, .{}); 116 | } 117 | } 118 | 119 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 120 | _ = listener; 121 | 122 | const canvas = try surface.canvas(); 123 | canvas.clear(.{ .r = 0.5, .g = 0.5, .b = 0.7, .a = 1.0 }); 124 | 125 | stage.needs_layout = true; 126 | stage.render(canvas, .{ .min = .{ 0, 0 }, .max = canvas.size() }); 127 | 128 | try surface.present(); 129 | } 130 | 131 | const seizer = @import("seizer"); 132 | const std = @import("std"); 133 | -------------------------------------------------------------------------------- /examples/ui_view_image.zig: -------------------------------------------------------------------------------- 1 | pub const main = seizer.main; 2 | 3 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .{}; 4 | var display: seizer.Display = undefined; 5 | var toplevel_surface: seizer.Display.ToplevelSurface = undefined; 6 | var render_listener: seizer.Display.ToplevelSurface.OnRenderListener = undefined; 7 | var input_listener: seizer.Display.ToplevelSurface.OnInputListener = undefined; 8 | 9 | var font: seizer.Canvas.Font = undefined; 10 | var ui_image: seizer.image.Linear(seizer.color.argbf32_premultiplied) = undefined; 11 | var character_image: seizer.image.Linear(seizer.color.argbf32_premultiplied) = undefined; 12 | var _stage: *seizer.ui.Stage = undefined; 13 | 14 | pub fn init() !void { 15 | try display.init(gpa.allocator(), seizer.getLoop()); 16 | 17 | try display.initToplevelSurface(&toplevel_surface, .{}); 18 | toplevel_surface.setOnInput(&input_listener, onToplevelInputEvent, null); 19 | toplevel_surface.setOnRender(&render_listener, onRender, null); 20 | 21 | font = try seizer.Canvas.Font.fromFileContents( 22 | gpa.allocator(), 23 | @embedFile("./assets/PressStart2P_8.fnt"), 24 | &.{ 25 | .{ .name = "PressStart2P_8.png", .contents = @embedFile("./assets/PressStart2P_8.png") }, 26 | }, 27 | ); 28 | errdefer font.deinit(); 29 | 30 | ui_image = try seizer.image.Linear(seizer.color.argbf32_premultiplied).fromMemory(gpa.allocator(), @embedFile("./assets/ui.png")); 31 | errdefer ui_image.free(gpa.allocator()); 32 | 33 | character_image = try seizer.image.Linear(seizer.color.argbf32_premultiplied).fromMemory(gpa.allocator(), @embedFile("./assets/wedge.png")); 34 | errdefer character_image.free(gpa.allocator()); 35 | 36 | // initialize ui stage and elements 37 | _stage = try seizer.ui.Stage.create(gpa.allocator(), .{ 38 | .padding = .{ 39 | .min = .{ 16, 16 }, 40 | .max = .{ 16, 16 }, 41 | }, 42 | .text_font = &font, 43 | .text_scale = 1, 44 | .text_color = seizer.color.argbf32_premultiplied.WHITE, 45 | .background_image = seizer.Canvas.NinePatch.init(ui_image.slice(.{ 0, 0 }, .{ 48, 48 }), seizer.geometry.Inset(f64).initXY(16, 16)), 46 | .background_color = seizer.color.argbf32_premultiplied.WHITE, 47 | }); 48 | errdefer _stage.destroy(); 49 | 50 | var flexbox = try seizer.ui.Element.FlexBox.create(_stage); 51 | defer flexbox.element().release(); 52 | flexbox.justification = .center; 53 | flexbox.cross_align = .center; 54 | 55 | const frame = try seizer.ui.Element.Frame.create(_stage); 56 | defer frame.element().release(); 57 | 58 | const frame_flexbox = try seizer.ui.Element.FlexBox.create(_stage); 59 | defer frame_flexbox.element().release(); 60 | frame_flexbox.cross_align = .center; 61 | 62 | const title_label = try seizer.ui.Element.Label.create(_stage, "Images in PanZoom"); 63 | defer title_label.element().release(); 64 | title_label.style = _stage.default_style.with(.{ 65 | .text_color = seizer.color.argbf32_premultiplied.BLACK, 66 | .background_image = seizer.Canvas.NinePatch.init(ui_image.slice(.{ 48, 0 }, .{ 48, 48 }), seizer.geometry.Inset(f64).initXY(16, 16)), 67 | }); 68 | 69 | const pan_zoom = try seizer.ui.Element.PanZoom.create(_stage); 70 | defer pan_zoom.element().release(); 71 | 72 | const pan_zoom_flexbox = try seizer.ui.Element.FlexBox.create(_stage); 73 | defer pan_zoom_flexbox.element().release(); 74 | 75 | const character_image_element = try seizer.ui.Element.Image.create(_stage, character_image.asSlice()); 76 | defer character_image_element.element().release(); 77 | 78 | const image_element = try seizer.ui.Element.Image.create(_stage, ui_image.asSlice()); 79 | defer image_element.element().release(); 80 | 81 | const hello_button = try seizer.ui.Element.Button.create(_stage, "Hello"); 82 | defer hello_button.element().release(); 83 | 84 | const text_field = try seizer.ui.Element.TextField.create(_stage); 85 | defer text_field.element().release(); 86 | 87 | // put elements into containers 88 | _stage.setRoot(flexbox.element()); 89 | 90 | try flexbox.appendChild(frame.element()); 91 | 92 | frame.setChild(frame_flexbox.element()); 93 | 94 | try frame_flexbox.appendChild(title_label.element()); 95 | try frame_flexbox.appendChild(pan_zoom.element()); 96 | 97 | try pan_zoom.appendChild(pan_zoom_flexbox.element()); 98 | 99 | try pan_zoom_flexbox.appendChild(character_image_element.element()); 100 | try pan_zoom_flexbox.appendChild(image_element.element()); 101 | try pan_zoom_flexbox.appendChild(hello_button.element()); 102 | try pan_zoom_flexbox.appendChild(text_field.element()); 103 | 104 | // setup global deinit callback 105 | seizer.setDeinit(deinit); 106 | } 107 | 108 | pub fn deinit() void { 109 | _stage.destroy(); 110 | 111 | font.deinit(); 112 | character_image.free(gpa.allocator()); 113 | ui_image.free(gpa.allocator()); 114 | 115 | toplevel_surface.deinit(); 116 | display.deinit(); 117 | _ = gpa.deinit(); 118 | } 119 | 120 | fn onToplevelInputEvent(listener: *seizer.Display.ToplevelSurface.OnInputListener, surface: *seizer.Display.ToplevelSurface, event: seizer.input.Event) !void { 121 | _ = listener; 122 | if (_stage.processEvent(event)) |_| { 123 | try surface.requestAnimationFrame(); 124 | } 125 | try display.connection.sendRequest(@TypeOf(surface.wl_surface)._SPECIFIED_INTERFACE, surface.wl_surface, .commit, .{}); 126 | } 127 | 128 | fn onRender(listener: *seizer.Display.ToplevelSurface.OnRenderListener, surface: *seizer.Display.ToplevelSurface) anyerror!void { 129 | _ = listener; 130 | 131 | const canvas = try surface.canvas(); 132 | canvas.clear(.{ .r = 0, .g = 0, .b = 0, .a = 1.0 }); 133 | 134 | _stage.needs_layout = true; 135 | _stage.render(canvas, .{ .min = .{ 0, 0 }, .max = canvas.size() }); 136 | 137 | try surface.present(); 138 | } 139 | 140 | const seizer = @import("seizer"); 141 | const std = @import("std"); 142 | -------------------------------------------------------------------------------- /src/Canvas/Font.zig: -------------------------------------------------------------------------------- 1 | allocator: std.mem.Allocator, 2 | pages: std.AutoHashMapUnmanaged(u32, seizer.image.Linear(seizer.color.argbf32_premultiplied)), 3 | glyphs: GlyphMap, 4 | line_height: f64, 5 | base: f64, 6 | scale: [2]f64, 7 | 8 | const AngelCodeFont = @import("AngelCodeFont"); 9 | const Font = @This(); 10 | 11 | const GlyphMap = std.AutoHashMapUnmanaged(AngelCodeFont.Glyph.Id, AngelCodeFont.Glyph); 12 | 13 | pub const ImageFile = struct { 14 | name: []const u8, 15 | contents: []const u8, 16 | }; 17 | 18 | pub fn fromFileContents(allocator: std.mem.Allocator, font_contents: []const u8, image_list: []const ImageFile) !@This() { 19 | var font_data = try AngelCodeFont.parse(allocator, font_contents); 20 | defer font_data.deinit(); 21 | 22 | var image_contents = std.StringHashMapUnmanaged([]const u8){}; 23 | defer image_contents.deinit(allocator); 24 | for (image_list) |image| { 25 | try image_contents.putNoClobber(allocator, image.name, image.contents); 26 | } 27 | 28 | var missing_image = false; 29 | 30 | var pages = std.AutoHashMapUnmanaged(u32, seizer.image.Linear(seizer.color.argbf32_premultiplied)){}; 31 | defer pages.deinit(allocator); 32 | var page_name_iterator = font_data.pages.iterator(); 33 | while (page_name_iterator.next()) |entry| { 34 | const image_content = image_contents.get(entry.value_ptr.*) orelse { 35 | log.warn("no matching image found for \"{}\"", .{std.zig.fmtEscapes(entry.value_ptr.*)}); 36 | missing_image = true; 37 | continue; 38 | }; 39 | try pages.ensureUnusedCapacity(allocator, 1); 40 | var image = try seizer.image.Linear(seizer.color.argbf32_premultiplied).fromMemory(allocator, image_content); 41 | errdefer image.free(allocator); 42 | 43 | pages.putAssumeCapacity(entry.key_ptr.*, image); 44 | } 45 | 46 | if (missing_image) return error.MissingImage; 47 | 48 | return @This(){ 49 | .allocator = allocator, 50 | .pages = pages.move(), 51 | .glyphs = font_data.glyphs.move(), 52 | .line_height = font_data.lineHeight, 53 | .base = font_data.base, 54 | .scale = .{ font_data.scale[0], font_data.scale[1] }, 55 | }; 56 | } 57 | 58 | pub fn deinit(this: *@This()) void { 59 | var page_iter = this.pages.valueIterator(); 60 | while (page_iter.next()) |page| { 61 | page.free(this.allocator); 62 | } 63 | this.pages.deinit(this.allocator); 64 | this.glyphs.deinit(this.allocator); 65 | } 66 | 67 | pub fn textSize(this: *const @This(), text: []const u8, scale: f64) [2]f64 { 68 | var layout = this.textLayout(text, .{ .pos = .{ 0, 0 }, .scale = scale }); 69 | while (layout.next()) |_| {} 70 | return layout.size; 71 | } 72 | 73 | pub fn fmtTextSize(this: *const @This(), comptime format: []const u8, args: anytype, scale: f64) [2]f64 { 74 | return AngelCodeFont.fmtTextSize( 75 | &this.glyphs, 76 | this.line_height, 77 | format, 78 | args, 79 | scale, 80 | ); 81 | } 82 | 83 | pub const TextLayout = AngelCodeFont.TextLayout; 84 | pub fn textLayout(this: *const @This(), text: []const u8, options: TextLayout.Options) TextLayout { 85 | return AngelCodeFont.textLayout( 86 | &this.glyphs, 87 | this.line_height, 88 | text, 89 | options, 90 | ); 91 | } 92 | 93 | pub const TextLayoutWriter = AngelCodeFont.TextLayoutWriter; 94 | 95 | const log = std.log.scoped(.seizer); 96 | 97 | const seizer = @import("../seizer.zig"); 98 | const std = @import("std"); 99 | -------------------------------------------------------------------------------- /src/Canvas/Transformed.zig: -------------------------------------------------------------------------------- 1 | parent: seizer.Canvas, 2 | transform: [4][4]f64, 3 | clip_area: seizer.geometry.AABB(f64), 4 | 5 | pub const InitOptions = struct { 6 | clip: seizer.geometry.AABB(f64), 7 | transform: [4][4]f64 = seizer.geometry.mat4.identity(f64), 8 | }; 9 | 10 | pub fn init(parent: seizer.Canvas, options: InitOptions) @This() { 11 | return .{ 12 | .parent = parent, 13 | .transform = options.transform, 14 | .clip_area = options.clip, 15 | }; 16 | } 17 | 18 | pub fn canvas(this: *@This()) seizer.Canvas { 19 | return .{ 20 | .ptr = this, 21 | .interface = CANVAS_INTERFACE, 22 | }; 23 | } 24 | 25 | const CANVAS_INTERFACE: *const seizer.Canvas.Interface = &.{ 26 | .size = canvas_size, 27 | .clear = canvas_clear, 28 | .blit = canvas_blit, 29 | .texture_rect = canvas_textureRect, 30 | .fill_rect = canvas_fillRect, 31 | .line = canvas_line, 32 | }; 33 | 34 | pub fn canvas_size(this_opaque: ?*anyopaque) [2]f64 { 35 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 36 | return this.clip_area.size(); 37 | } 38 | 39 | pub fn canvas_clear(this_opaque: ?*anyopaque, color: seizer.color.argbf32_premultiplied) void { 40 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 41 | this.parent.clear(color); 42 | } 43 | 44 | pub fn canvas_blit(this_opaque: ?*anyopaque, pos: [2]f64, src_image: seizer.image.Slice(seizer.color.argbf32_premultiplied)) void { 45 | _ = this_opaque; 46 | _ = pos; 47 | _ = src_image; 48 | std.debug.panic("Canvas.Transformed does not support blitting at this time", .{}); 49 | } 50 | 51 | pub fn canvas_fillRect(this_opaque: ?*anyopaque, area: seizer.geometry.AABB(f64), color: seizer.color.argbf32_premultiplied, options: seizer.Canvas.FillRectOptions) void { 52 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 53 | 54 | const transformed_area = seizer.geometry.AABB(f64).init(.{ 55 | seizer.geometry.mat4.mulVec(f64, this.transform, area.min ++ .{ 0, 1 })[0..2].*, 56 | seizer.geometry.mat4.mulVec(f64, this.transform, area.max ++ .{ 0, 1 })[0..2].*, 57 | }); 58 | 59 | const clipped_area = transformed_area.clamp(this.clip_area); 60 | 61 | return this.parent.fillRect(clipped_area, color, options); 62 | } 63 | 64 | pub fn canvas_textureRect(this_opaque: ?*anyopaque, dst_area: seizer.geometry.AABB(f64), src_image: seizer.image.Slice(seizer.color.argbf32_premultiplied), options: seizer.Canvas.TextureRectOptions) void { 65 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 66 | 67 | const dst_area_t = seizer.geometry.AABB(f64){ 68 | .min = seizer.geometry.mat4.mulVec(f64, this.transform, dst_area.min ++ .{ 0, 1 })[0..2].*, 69 | .max = seizer.geometry.mat4.mulVec(f64, this.transform, dst_area.max ++ .{ 0, 1 })[0..2].*, 70 | }; 71 | 72 | if (!dst_area_t.overlaps(this.clip_area)) { 73 | return; 74 | } 75 | 76 | const dst_area_clipped = dst_area_t.clamp(this.clip_area); 77 | 78 | const src_area = options.src_area orelse seizer.geometry.AABB(f64){ 79 | .min = .{ 0, 0 }, 80 | .max = .{ @floatFromInt(src_image.size[0]), @floatFromInt(src_image.size[1]) }, 81 | }; 82 | const src_area_clipped = src_area.inset(.{ 83 | .min = .{ 84 | ((dst_area_clipped.min[0] - dst_area_t.min[0]) / dst_area_t.size()[0]) * src_area.size()[0], 85 | ((dst_area_clipped.min[1] - dst_area_t.min[1]) / dst_area_t.size()[1]) * src_area.size()[1], 86 | }, 87 | .max = .{ 88 | ((dst_area_clipped.max[0] - dst_area_t.max[0]) / dst_area_t.size()[0]) * src_area.size()[0], 89 | ((dst_area_clipped.max[1] - dst_area_t.max[1]) / dst_area_t.size()[1]) * src_area.size()[1], 90 | }, 91 | }); 92 | 93 | this.parent.textureRect(dst_area_clipped, src_image, .{ 94 | .src_area = src_area_clipped, 95 | .color = options.color, 96 | .depth = options.depth, 97 | }); 98 | } 99 | 100 | pub fn canvas_line(this_opaque: ?*anyopaque, start: [2]f64, end: [2]f64, options: seizer.Canvas.LineOptions) void { 101 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 102 | 103 | const start_t = seizer.geometry.mat4.mulVec(f64, this.transform, start ++ .{ 0, 1 })[0..2].*; 104 | const end_t = seizer.geometry.mat4.mulVec(f64, this.transform, end ++ .{ 0, 1 })[0..2].*; 105 | 106 | this.parent.line(start_t, end_t, options); 107 | } 108 | 109 | const log = std.log.scoped(.seizer); 110 | 111 | const seizer = @import("../seizer.zig"); 112 | const std = @import("std"); 113 | -------------------------------------------------------------------------------- /src/Display/Buffer.zig: -------------------------------------------------------------------------------- 1 | wl_buffer: shimizu.Proxy(shimizu.core.wl_buffer), 2 | size: [2]u32, 3 | pixels: [*]seizer.color.argb(seizer.color.sRGB8, .premultiplied, u8), 4 | 5 | pub fn clear(this: @This(), color: seizer.color.argb(f64)) void { 6 | @memset(this.pixels[0 .. this.size[0] * this.size[1]], color.toArgb8888()); 7 | } 8 | 9 | pub fn image(this: @This()) seizer.image.Linear(seizer.color.argb8888) { 10 | return .{ 11 | .size = this.size, 12 | .stride = this.size[0], 13 | .pixels = this.pixels, 14 | }; 15 | } 16 | 17 | pub fn canvas(this: *@This()) seizer.Canvas { 18 | return .{ 19 | .ptr = this, 20 | .interface = CANVAS_INTERFACE, 21 | }; 22 | } 23 | 24 | const CANVAS_INTERFACE: *const seizer.Canvas.Interface = &.{ 25 | .size = canvas_size, 26 | .blit = canvas_blit, 27 | .texture_rect = canvas_textureRect, 28 | .fill_rect = canvas_fillRect, 29 | .line = canvas_line, 30 | }; 31 | 32 | pub fn canvas_size(this_opaque: ?*anyopaque) [2]f64 { 33 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 34 | return .{ @floatFromInt(this.size[0]), @floatFromInt(this.size[1]) }; 35 | } 36 | 37 | pub fn canvas_blit(this_opaque: ?*anyopaque, pos: [2]f64, src_image: seizer.image.Linear(seizer.color.argb8888)) void { 38 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 39 | const pos_i = [2]i32{ 40 | @intFromFloat(@floor(pos[0])), 41 | @intFromFloat(@floor(pos[1])), 42 | }; 43 | const size_i = [2]i32{ 44 | @intCast(this.size[0]), 45 | @intCast(this.size[1]), 46 | }; 47 | 48 | if (pos_i[0] + size_i[0] <= 0 or pos_i[1] + size_i[1] <= 0) return; 49 | if (pos_i[0] >= size_i[0] or pos_i[1] >= size_i[1]) return; 50 | 51 | const src_size = [2]u32{ 52 | @min(src_image.size[0], @as(u32, @intCast(size_i[0] - pos_i[0]))), 53 | @min(src_image.size[1], @as(u32, @intCast(size_i[1] - pos_i[1]))), 54 | }; 55 | 56 | const src_offset = [2]u32{ 57 | if (pos_i[0] < 0) @intCast(-pos_i[0]) else 0, 58 | if (pos_i[1] < 0) @intCast(-pos_i[1]) else 0, 59 | }; 60 | const dest_offset = [2]u32{ 61 | @intCast(@max(pos_i[0], 0)), 62 | @intCast(@max(pos_i[1], 0)), 63 | }; 64 | 65 | const src = src_image.slice(src_offset, src_size); 66 | const dest = this.image().slice(dest_offset, src_size); 67 | 68 | dest.composite(src); 69 | } 70 | 71 | pub fn canvas_fillRect(this_opaque: ?*anyopaque, pos: [2]f64, size: [2]f64, options: seizer.Canvas.RectOptions) void { 72 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 73 | const a = [2]i32{ @intFromFloat(pos[0]), @intFromFloat(pos[1]) }; 74 | const b = [2]i32{ @intFromFloat(pos[0] + size[0]), @intFromFloat(pos[1] + size[1]) }; 75 | 76 | this.image().drawFillRect(a, b, options.color.toArgb8888()); 77 | } 78 | 79 | pub fn canvas_textureRect(this_opaque: ?*anyopaque, dst_pos: [2]f64, dst_size: [2]f64, src_image: seizer.image.Linear(seizer.color.argb8888), options: seizer.Canvas.RectOptions) void { 80 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 81 | 82 | const start_pos = [2]u32{ 83 | @min(@as(u32, @intFromFloat(@floor(@max(@min(dst_pos[0], dst_pos[0] + dst_size[0]), 0)))), this.size[0]), 84 | @min(@as(u32, @intFromFloat(@floor(@max(@min(dst_pos[1], dst_pos[1] + dst_size[1]), 0)))), this.size[1]), 85 | }; 86 | const end_pos = [2]u32{ 87 | @min(@as(u32, @intFromFloat(@floor(@max(dst_pos[0], dst_pos[0] + dst_size[0], 0)))), this.size[0]), 88 | @min(@as(u32, @intFromFloat(@floor(@max(dst_pos[1], dst_pos[1] + dst_size[1], 0)))), this.size[1]), 89 | }; 90 | 91 | const src_size = [2]f64{ 92 | @floatFromInt(src_image.size[0]), 93 | @floatFromInt(src_image.size[1]), 94 | }; 95 | 96 | const color_mask = options.color.toArgb8888(); 97 | 98 | for (start_pos[1]..end_pos[1]) |y| { 99 | for (start_pos[0]..end_pos[0]) |x| { 100 | const pos = [2]f64{ @floatFromInt(x), @floatFromInt(y) }; 101 | const texture_coord = [2]f64{ 102 | std.math.clamp(((pos[0] - dst_pos[0]) / dst_size[0]) * src_size[0], 0, src_size[0]), 103 | std.math.clamp(((pos[1] - dst_pos[1]) / dst_size[1]) * src_size[1], 0, src_size[1]), 104 | }; 105 | const dst_pixel = this.image().getPixel(.{ @intCast(x), @intCast(y) }); 106 | const src_pixel = src_image.getPixel(.{ 107 | @intFromFloat(texture_coord[0]), 108 | @intFromFloat(texture_coord[1]), 109 | }); 110 | const src_pixel_tint = src_pixel.tint(color_mask); 111 | this.image().setPixel(.{ @intCast(x), @intCast(y) }, dst_pixel.compositeSrcOver(src_pixel_tint)); 112 | } 113 | } 114 | } 115 | 116 | pub fn canvas_line(this_opaque: ?*anyopaque, start: [2]f64, end: [2]f64, options: seizer.Canvas.LineOptions) void { 117 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 118 | const start_i = [2]i32{ 119 | @intFromFloat(@floor(start[0])), 120 | @intFromFloat(@floor(start[1])), 121 | }; 122 | const end_i = [2]i32{ 123 | @intFromFloat(@floor(end[0])), 124 | @intFromFloat(@floor(end[1])), 125 | }; 126 | 127 | this.image().drawLine(start_i, end_i, options.color.toArgb8888()); 128 | } 129 | 130 | const seizer = @import("../seizer.zig"); 131 | const shimizu = @import("shimizu"); 132 | const std = @import("std"); 133 | -------------------------------------------------------------------------------- /src/Display/Surface.zig: -------------------------------------------------------------------------------- 1 | const Surface = @This(); 2 | 3 | display: *seizer.Display, 4 | wl_surface: shimizu.Object.WithInterface(shimizu.core.wl_surface), 5 | swapchain: Swapchain, 6 | framebuffer: seizer.image.Linear(seizer.color.argbf32_premultiplied), 7 | on_render_listener: ?*OnRenderListener, 8 | size: [2]u32, 9 | 10 | pub const InitOptions = struct { 11 | size: [2]u32 = .{ 32, 32 }, 12 | }; 13 | 14 | pub const OnRenderListener = struct { 15 | callback: CallbackFn, 16 | userdata: ?*anyopaque, 17 | 18 | pub const CallbackFn = *const fn (*OnRenderListener, *Surface) anyerror!void; 19 | }; 20 | 21 | pub fn deinit(this: *@This()) void { 22 | this.swapchain.deinit(); 23 | this.framebuffer.free(this.display.allocator); 24 | } 25 | 26 | pub fn setOnRender(this: *@This(), on_render_listener: *OnRenderListener, callback: OnRenderListener.CallbackFn, userdata: ?*anyopaque) void { 27 | on_render_listener.* = .{ 28 | .callback = callback, 29 | .userdata = userdata, 30 | }; 31 | this.on_render_listener = on_render_listener; 32 | } 33 | 34 | pub fn canvas(this: *@This()) !seizer.Canvas { 35 | // try this.framebuffer.resize(this.display.allocator, this.size); 36 | return .{ 37 | .ptr = this, 38 | .interface = CANVAS_INTERFACE, 39 | }; 40 | } 41 | 42 | pub fn present(this: *@This()) !void { 43 | if (this.swapchain.size[0] != this.framebuffer.size[0] or 44 | this.swapchain.size[1] != this.framebuffer.size[1]) 45 | { 46 | this.swapchain.deinit(); 47 | try this.swapchain.allocate(.{ .connection = &this.display.connection, .id = this.display.globals.wl_shm.? }, this.framebuffer.size, 3); 48 | } 49 | 50 | const buffer = try this.swapchain.getBuffer(); 51 | for (0..buffer.size[1]) |y| { 52 | const row = buffer.pixels[y * buffer.size[0] ..][0..buffer.size[0]]; 53 | for (row, 0..) |*px, x| { 54 | px.* = this.framebuffer.getPixel(.{ @intCast(x), @intCast(y) }).convertColorTo(seizer.color.sRGB8).convertAlphaTo(u8); 55 | } 56 | } 57 | 58 | try this.display.connection.sendRequest(wayland.wl_surface, this.wl_surface, .attach, .{ 59 | .x = 0, 60 | .y = 0, 61 | .buffer = buffer.wl_buffer.id, 62 | }); 63 | try this.display.connection.sendRequest(wayland.wl_surface, this.wl_surface, .damage_buffer, .{ 64 | .x = 0, 65 | .y = 0, 66 | .width = @intCast(buffer.size[0]), 67 | .height = @intCast(buffer.size[1]), 68 | }); 69 | try this.display.connection.sendRequest(wayland.wl_surface, this.wl_surface, .commit, .{}); 70 | } 71 | 72 | pub fn hide(this: *@This()) void { 73 | this.display.connection.sendRequest(wayland.wl_surface, this.wl_surface, .attach, .{ 74 | .x = 0, 75 | .y = 0, 76 | // shimizu: TODO: make properly nullable? 77 | .buffer = @enumFromInt(0), 78 | }) catch {}; 79 | this.display.connection.sendRequest(wayland.wl_surface, this.wl_surface, .damage_buffer, .{ 80 | .x = 0, 81 | .y = 0, 82 | .width = std.math.maxInt(i32), 83 | .height = std.math.maxInt(i32), 84 | }) catch {}; 85 | this.display.connection.sendRequest(wayland.wl_surface, this.wl_surface, .commit, .{}) catch {}; 86 | } 87 | 88 | // Canvas implementation 89 | 90 | const CANVAS_INTERFACE: *const seizer.Canvas.Interface = &.{ 91 | .size = canvas_size, 92 | .clear = canvas_clear, 93 | .blit = canvas_blit, 94 | .texture_rect = canvas_textureRect, 95 | .fill_rect = canvas_fillRect, 96 | .line = canvas_line, 97 | }; 98 | 99 | pub fn canvas_size(this_opaque: ?*anyopaque) [2]f64 { 100 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 101 | return seizer.geometry.vec.into(f64, this.framebuffer.size); 102 | } 103 | 104 | pub fn canvas_clear(this_opaque: ?*anyopaque, color: seizer.color.argbf32_premultiplied) void { 105 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 106 | this.framebuffer.clear(color); 107 | } 108 | 109 | pub fn canvas_blit(this_opaque: ?*anyopaque, pos: [2]f64, src_image: seizer.image.Slice(seizer.color.argbf32_premultiplied)) void { 110 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 111 | 112 | const pos_i = [2]i32{ 113 | @intFromFloat(@floor(pos[0])), 114 | @intFromFloat(@floor(pos[1])), 115 | }; 116 | const size_i = [2]i32{ 117 | @intCast(this.framebuffer.size[0]), 118 | @intCast(this.framebuffer.size[1]), 119 | }; 120 | 121 | if (pos_i[0] + size_i[0] <= 0 or pos_i[1] + size_i[1] <= 0) return; 122 | if (pos_i[0] >= size_i[0] or pos_i[1] >= size_i[1]) return; 123 | 124 | const src_size = [2]u32{ 125 | @min(src_image.size[0], @as(u32, @intCast(size_i[0] - pos_i[0]))), 126 | @min(src_image.size[1], @as(u32, @intCast(size_i[1] - pos_i[1]))), 127 | }; 128 | 129 | const src_offset = [2]u32{ 130 | if (pos_i[0] < 0) @intCast(-pos_i[0]) else 0, 131 | if (pos_i[1] < 0) @intCast(-pos_i[1]) else 0, 132 | }; 133 | const dest_offset = [2]u32{ 134 | @intCast(@max(pos_i[0], 0)), 135 | @intCast(@max(pos_i[1], 0)), 136 | }; 137 | 138 | const src = src_image.slice(src_offset, src_size); 139 | const dest = this.framebuffer.slice(dest_offset, src_size); 140 | 141 | dest.composite(src); 142 | } 143 | 144 | pub fn canvas_fillRect(this_opaque: ?*anyopaque, area: seizer.geometry.AABB(f64), color: seizer.color.argbf32_premultiplied, options: seizer.Canvas.FillRectOptions) void { 145 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 146 | _ = options; 147 | 148 | const area_u = area.into(i32); 149 | 150 | this.framebuffer.asSlice().drawFillRect(area_u.min, area_u.max, color); 151 | } 152 | 153 | pub fn canvas_textureRect(this_opaque: ?*anyopaque, dst_area: seizer.geometry.AABB(f64), src_image: seizer.image.Slice(seizer.color.argbf32_premultiplied), options: seizer.Canvas.TextureRectOptions) void { 154 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 155 | 156 | _ = this; 157 | _ = dst_area; 158 | _ = src_image; 159 | _ = options; 160 | } 161 | 162 | pub fn canvas_line(this_opaque: ?*anyopaque, start: [2]f64, end: [2]f64, options: seizer.Canvas.LineOptions) void { 163 | const this: *@This() = @ptrCast(@alignCast(this_opaque)); 164 | 165 | const start_f = seizer.geometry.vec.into(f32, start); 166 | const end_f = seizer.geometry.vec.into(f32, end); 167 | const end_color = options.end_color orelse options.color; 168 | const width: f32 = @floatCast(options.width); 169 | const end_width: f32 = @floatCast(options.end_width orelse width); 170 | 171 | this.framebuffer.asSlice().drawLine(.{ .min = .{ 0, 0 }, .max = this.framebuffer.size }, start_f, end_f, .{ width, end_width }, .{ options.color, end_color }); 172 | } 173 | 174 | const Swapchain = @import("./Swapchain.zig"); 175 | 176 | const wayland = shimizu.core; 177 | 178 | // stable protocols 179 | const viewporter = @import("wayland-protocols").viewporter; 180 | const linux_dmabuf_v1 = @import("wayland-protocols").linux_dmabuf_v1; 181 | const xdg_shell = @import("wayland-protocols").xdg_shell; 182 | 183 | // unstable protocols 184 | const xdg_decoration = @import("wayland-unstable").xdg_decoration_unstable_v1; 185 | const fractional_scale_v1 = @import("wayland-unstable").fractional_scale_v1; 186 | 187 | const log = std.log.scoped(.seizer); 188 | 189 | const seizer = @import("../seizer.zig"); 190 | const shimizu = @import("shimizu"); 191 | const std = @import("std"); 192 | const xev = @import("xev"); 193 | -------------------------------------------------------------------------------- /src/Display/Swapchain.zig: -------------------------------------------------------------------------------- 1 | //! An ARGB framebuffer 2 | size: [2]u32 = .{ 0, 0 }, 3 | memory: []align(std.mem.page_size) u8 = &.{}, 4 | 5 | wl_shm_pool: shimizu.Proxy(shimizu.core.wl_shm_pool) = .{ .connection = undefined, .id = @enumFromInt(0) }, 6 | buffers: std.BoundedArray(Buffer, 6) = .{}, 7 | free: std.BoundedArray(u32, 6) = .{}, 8 | 9 | wl_buffer_event_listener: shimizu.Listener = undefined, 10 | 11 | pub fn allocate(this: *@This(), wl_shm: shimizu.Proxy(shimizu.core.wl_shm), size: [2]u32, count: u32) !void { 12 | std.debug.assert(count <= 6); 13 | const fd = try std.posix.memfd_create("swapchain", 0); 14 | defer std.posix.close(fd); 15 | 16 | const frame_size = size[0] * size[1] * @sizeOf(argb8); 17 | const total_size = frame_size * count; 18 | try std.posix.ftruncate(fd, total_size); 19 | 20 | const memory = try std.posix.mmap(null, total_size, std.posix.PROT.WRITE, .{ .TYPE = .SHARED }, fd, 0); 21 | 22 | const wl_shm_pool = try wl_shm.sendRequest(.create_pool, .{ 23 | .fd = @enumFromInt(fd), 24 | .size = @intCast(total_size), 25 | }); 26 | 27 | var buffers = std.BoundedArray(Buffer, 6){}; 28 | var free = std.BoundedArray(u32, 6){}; 29 | var offset: u32 = 0; 30 | for (0..count) |index| { 31 | const wl_buffer = try wl_shm_pool.sendRequest(.create_buffer, .{ 32 | .offset = @intCast(offset), 33 | .width = @intCast(size[0]), 34 | .height = @intCast(size[1]), 35 | .stride = @intCast(size[0] * @sizeOf(argb8)), 36 | .format = .argb8888, 37 | }); 38 | buffers.appendAssumeCapacity(.{ 39 | .wl_buffer = wl_buffer, 40 | .size = size, 41 | .pixels = @ptrCast(@alignCast(memory[offset..][0..frame_size].ptr)), 42 | }); 43 | free.appendAssumeCapacity(@intCast(index)); 44 | offset += frame_size; 45 | } 46 | 47 | this.* = .{ 48 | .size = size, 49 | .memory = memory, 50 | .wl_shm_pool = wl_shm_pool, 51 | .buffers = buffers, 52 | .free = free, 53 | .wl_buffer_event_listener = undefined, 54 | }; 55 | for (this.buffers.slice()) |buffer| { 56 | buffer.wl_buffer.setEventListener(&this.wl_buffer_event_listener, onWlBufferEvent, this); 57 | } 58 | } 59 | 60 | pub fn deinit(this: *@This()) void { 61 | if (@intFromEnum(this.wl_shm_pool.id) != 0) this.wl_shm_pool.sendRequest(.destroy, .{}) catch {}; 62 | if (this.memory.len > 0) std.posix.munmap(this.memory); 63 | this.* = undefined; 64 | } 65 | 66 | pub fn getBuffer(this: *@This()) !Buffer { 67 | const buffer_index = this.free.popOrNull() orelse return error.OutOfFramebuffers; 68 | return this.buffers.slice()[buffer_index]; 69 | } 70 | 71 | fn onWlBufferEvent(listener: *shimizu.Listener, wl_buffer: shimizu.Proxy(shimizu.core.wl_buffer), event: shimizu.core.wl_buffer.Event) !void { 72 | const this: *@This() = @fieldParentPtr("wl_buffer_event_listener", listener); 73 | switch (event) { 74 | .release => { 75 | const index = for (this.buffers.slice(), 0..) |buffer, i| { 76 | if (buffer.wl_buffer.id == wl_buffer.id) break i; 77 | } else return; 78 | this.free.appendAssumeCapacity(@intCast(index)); 79 | }, 80 | } 81 | } 82 | 83 | pub const argb8 = seizer.color.argb(seizer.color.sRGB8, .premultiplied, u8); 84 | 85 | const Image = seizer.image.Linear(seizer.color.argb8); 86 | const Buffer = @import("./Buffer.zig"); 87 | 88 | const seizer = @import("../seizer.zig"); 89 | const shimizu = @import("shimizu"); 90 | const std = @import("std"); 91 | -------------------------------------------------------------------------------- /src/Display/cursor_none.tvg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klaji-dev/seizer/76c1f9a88b5ad54f7371c2a462ef97ac6ba4fb8e/src/Display/cursor_none.tvg -------------------------------------------------------------------------------- /src/Display/evdev_to_seizer.zig: -------------------------------------------------------------------------------- 1 | pub fn evdevToSeizer(scancode: u32) seizer.input.keyboard.Scancode { 2 | std.debug.assert(scancode < std.math.maxInt(u16)); 3 | const evdev_key: evdev.KEY = @enumFromInt(@as(u16, @intCast(scancode))); 4 | return switch (evdev_key) { 5 | .esc => .escape, 6 | .@"1" => .@"1", 7 | .@"2" => .@"2", 8 | .@"3" => .@"3", 9 | .@"4" => .@"4", 10 | .@"5" => .@"5", 11 | .@"6" => .@"6", 12 | .@"7" => .@"7", 13 | .@"8" => .@"8", 14 | .@"9" => .@"9", 15 | .@"0" => .@"0", 16 | 17 | .minus => .minus, 18 | .equal => .equal, 19 | .backspace => .backspace, 20 | .tab => .tab, 21 | 22 | .q => .q, 23 | .w => .w, 24 | .e => .e, 25 | .r => .r, 26 | .t => .t, 27 | .y => .y, 28 | .u => .u, 29 | .i => .i, 30 | .o => .o, 31 | .p => .p, 32 | .a => .a, 33 | .s => .s, 34 | .d => .d, 35 | .f => .f, 36 | .g => .g, 37 | .h => .h, 38 | .j => .j, 39 | .k => .k, 40 | .l => .l, 41 | .z => .z, 42 | .x => .x, 43 | .c => .c, 44 | .v => .v, 45 | .b => .b, 46 | .n => .n, 47 | .m => .m, 48 | 49 | .leftbrace => .bracket_left, 50 | .rightbrace => .bracket_right, 51 | .enter => .enter, 52 | .leftctrl => .control_left, 53 | .rightctrl => .control_right, 54 | .semicolon => .semicolon, 55 | .apostrophe => .quote, 56 | .grave => .backquote, 57 | .leftshift => .shift_left, 58 | .backslash => .backslash, 59 | .comma => .comma, 60 | .dot => .period, 61 | .slash => .slash, 62 | .rightshift => .shift_right, 63 | .kpasterisk => .numpad_multiply, 64 | .leftalt => .alt_left, 65 | .rightalt => .alt_right, 66 | .space => .space, 67 | .capslock => .caps_lock, 68 | .f1 => .f1, 69 | .f2 => .f2, 70 | .f3 => .f3, 71 | .f4 => .f4, 72 | .f5 => .f5, 73 | .f6 => .f6, 74 | .f7 => .f7, 75 | .f8 => .f8, 76 | .f9 => .f9, 77 | .f10 => .f10, 78 | .f11 => .f11, 79 | .f12 => .f12, 80 | .numlock => .num_lock, 81 | .scrolllock => .scroll_lock, 82 | .kp7 => .numpad7, 83 | .kp8 => .numpad8, 84 | .kp9 => .numpad9, 85 | .kpminus => .numpad_subtract, 86 | .kp4 => .numpad4, 87 | .kp5 => .numpad5, 88 | .kp6 => .numpad6, 89 | .kpplus => .numpad_add, 90 | .kp1 => .numpad1, 91 | .kp2 => .numpad2, 92 | .kp3 => .numpad3, 93 | .kp0 => .numpad0, 94 | .kpdot => .numpad_decimal, 95 | 96 | .home => .home, 97 | .up => .arrow_up, 98 | .pageup => .page_up, 99 | .left => .arrow_left, 100 | .right => .arrow_right, 101 | .end => .end, 102 | .down => .arrow_down, 103 | .pagedown => .page_down, 104 | .insert => .insert, 105 | .delete => .delete, 106 | // .mute => .mute, 107 | // .volumedown => .volumedown, 108 | // .volumeup => .volumeup, 109 | 110 | .kpcomma => .numpad_comma, 111 | .yen => .intl_yen, 112 | .ro => .intl_ro, 113 | .leftmeta => .meta_left, 114 | .rightmeta => .meta_right, 115 | 116 | else => |k| std.debug.panic("Unknown Linux kernel EvDev KEY: {}", .{k}), 117 | }; 118 | } 119 | 120 | const evdev = @import("./evdev.zig"); 121 | const seizer = @import("../seizer.zig"); 122 | const std = @import("std"); 123 | -------------------------------------------------------------------------------- /src/Display/xkb_to_seizer.zig: -------------------------------------------------------------------------------- 1 | pub fn xkbSymbolToSeizerKey(symbol: xkb.Symbol) Key { 2 | if (symbol.character) |unicode_character| { 3 | return .{ .unicode = unicode_character }; 4 | } 5 | return switch (symbol.code) { 6 | xkb.Symbol.NoSymbol.code, 7 | xkb.Symbol.VoidSymbol.code, 8 | => .unidentified, 9 | 10 | xkb.Symbol.Escape.code => .escape, 11 | xkb.Symbol.BackSpace.code => .backspace, 12 | 13 | xkb.Symbol.Control_L.code, 14 | xkb.Symbol.Control_R.code, 15 | => .control, 16 | 17 | xkb.Symbol.Shift_L.code, 18 | xkb.Symbol.Shift_R.code, 19 | => .shift, 20 | 21 | xkb.Symbol.Alt_L.code, 22 | xkb.Symbol.Alt_R.code, 23 | => .alt, 24 | 25 | xkb.Symbol.Meta_L.code, 26 | xkb.Symbol.Meta_R.code, 27 | xkb.Symbol.Super_L.code, 28 | xkb.Symbol.Super_R.code, 29 | => .meta, 30 | 31 | xkb.Symbol.Caps_Lock.code => .caps_lock, 32 | 33 | xkb.Symbol.F1.code => .f1, 34 | xkb.Symbol.F2.code => .f2, 35 | xkb.Symbol.F3.code => .f3, 36 | xkb.Symbol.F4.code => .f4, 37 | xkb.Symbol.F5.code => .f5, 38 | xkb.Symbol.F6.code => .f6, 39 | xkb.Symbol.F7.code => .f7, 40 | xkb.Symbol.F8.code => .f8, 41 | xkb.Symbol.F9.code => .f9, 42 | xkb.Symbol.F10.code => .f10, 43 | xkb.Symbol.F11.code => .f11, 44 | xkb.Symbol.F12.code => .f12, 45 | xkb.Symbol.F13.code => .f13, 46 | xkb.Symbol.F14.code => .f14, 47 | xkb.Symbol.F15.code => .f15, 48 | xkb.Symbol.F16.code => .f16, 49 | xkb.Symbol.F17.code => .f17, 50 | xkb.Symbol.F18.code => .f18, 51 | xkb.Symbol.F19.code => .f19, 52 | xkb.Symbol.F20.code => .f20, 53 | 54 | xkb.Symbol.Num_Lock.code => .num_lock, 55 | xkb.Symbol.Scroll_Lock.code => .scroll_lock, 56 | 57 | xkb.Symbol.Up.code => .arrow_up, 58 | xkb.Symbol.Left.code => .arrow_left, 59 | xkb.Symbol.Right.code => .arrow_right, 60 | xkb.Symbol.Down.code => .arrow_down, 61 | xkb.Symbol.Home.code => .home, 62 | xkb.Symbol.End.code => .end, 63 | xkb.Symbol.Prior.code => .page_up, 64 | xkb.Symbol.Next.code => .page_down, 65 | 66 | xkb.Symbol.Insert.code => .insert, 67 | 68 | else => |k| std.debug.panic("Unknown xkb Symbol code 0x{x}", .{k}), 69 | }; 70 | } 71 | 72 | const Key = @import("../seizer.zig").input.keyboard.Key; 73 | const xkb = @import("xkb"); 74 | const std = @import("std"); 75 | -------------------------------------------------------------------------------- /src/colormaps.zig: -------------------------------------------------------------------------------- 1 | pub const turbo_srgb = @import("./colormaps/turbo_srgb.zig").turbo_srgb; 2 | -------------------------------------------------------------------------------- /src/colormaps/turbo_srgb.zig: -------------------------------------------------------------------------------- 1 | pub const turbo_srgb = [256][4]f32{ 2 | .{ 0.18995, 0.07176, 0.23217, 1.0 }, 3 | .{ 0.19483, 0.08339, 0.26149, 1.0 }, 4 | .{ 0.19956, 0.09498, 0.29024, 1.0 }, 5 | .{ 0.20415, 0.10652, 0.31844, 1.0 }, 6 | .{ 0.20860, 0.11802, 0.34607, 1.0 }, 7 | .{ 0.21291, 0.12947, 0.37314, 1.0 }, 8 | .{ 0.21708, 0.14087, 0.39964, 1.0 }, 9 | .{ 0.22111, 0.15223, 0.42558, 1.0 }, 10 | .{ 0.22500, 0.16354, 0.45096, 1.0 }, 11 | .{ 0.22875, 0.17481, 0.47578, 1.0 }, 12 | .{ 0.23236, 0.18603, 0.50004, 1.0 }, 13 | .{ 0.23582, 0.19720, 0.52373, 1.0 }, 14 | .{ 0.23915, 0.20833, 0.54686, 1.0 }, 15 | .{ 0.24234, 0.21941, 0.56942, 1.0 }, 16 | .{ 0.24539, 0.23044, 0.59142, 1.0 }, 17 | .{ 0.24830, 0.24143, 0.61286, 1.0 }, 18 | .{ 0.25107, 0.25237, 0.63374, 1.0 }, 19 | .{ 0.25369, 0.26327, 0.65406, 1.0 }, 20 | .{ 0.25618, 0.27412, 0.67381, 1.0 }, 21 | .{ 0.25853, 0.28492, 0.69300, 1.0 }, 22 | .{ 0.26074, 0.29568, 0.71162, 1.0 }, 23 | .{ 0.26280, 0.30639, 0.72968, 1.0 }, 24 | .{ 0.26473, 0.31706, 0.74718, 1.0 }, 25 | .{ 0.26652, 0.32768, 0.76412, 1.0 }, 26 | .{ 0.26816, 0.33825, 0.78050, 1.0 }, 27 | .{ 0.26967, 0.34878, 0.79631, 1.0 }, 28 | .{ 0.27103, 0.35926, 0.81156, 1.0 }, 29 | .{ 0.27226, 0.36970, 0.82624, 1.0 }, 30 | .{ 0.27334, 0.38008, 0.84037, 1.0 }, 31 | .{ 0.27429, 0.39043, 0.85393, 1.0 }, 32 | .{ 0.27509, 0.40072, 0.86692, 1.0 }, 33 | .{ 0.27576, 0.41097, 0.87936, 1.0 }, 34 | .{ 0.27628, 0.42118, 0.89123, 1.0 }, 35 | .{ 0.27667, 0.43134, 0.90254, 1.0 }, 36 | .{ 0.27691, 0.44145, 0.91328, 1.0 }, 37 | .{ 0.27701, 0.45152, 0.92347, 1.0 }, 38 | .{ 0.27698, 0.46153, 0.93309, 1.0 }, 39 | .{ 0.27680, 0.47151, 0.94214, 1.0 }, 40 | .{ 0.27648, 0.48144, 0.95064, 1.0 }, 41 | .{ 0.27603, 0.49132, 0.95857, 1.0 }, 42 | .{ 0.27543, 0.50115, 0.96594, 1.0 }, 43 | .{ 0.27469, 0.51094, 0.97275, 1.0 }, 44 | .{ 0.27381, 0.52069, 0.97899, 1.0 }, 45 | .{ 0.27273, 0.53040, 0.98461, 1.0 }, 46 | .{ 0.27106, 0.54015, 0.98930, 1.0 }, 47 | .{ 0.26878, 0.54995, 0.99303, 1.0 }, 48 | .{ 0.26592, 0.55979, 0.99583, 1.0 }, 49 | .{ 0.26252, 0.56967, 0.99773, 1.0 }, 50 | .{ 0.25862, 0.57958, 0.99876, 1.0 }, 51 | .{ 0.25425, 0.58950, 0.99896, 1.0 }, 52 | .{ 0.24946, 0.59943, 0.99835, 1.0 }, 53 | .{ 0.24427, 0.60937, 0.99697, 1.0 }, 54 | .{ 0.23874, 0.61931, 0.99485, 1.0 }, 55 | .{ 0.23288, 0.62923, 0.99202, 1.0 }, 56 | .{ 0.22676, 0.63913, 0.98851, 1.0 }, 57 | .{ 0.22039, 0.64901, 0.98436, 1.0 }, 58 | .{ 0.21382, 0.65886, 0.97959, 1.0 }, 59 | .{ 0.20708, 0.66866, 0.97423, 1.0 }, 60 | .{ 0.20021, 0.67842, 0.96833, 1.0 }, 61 | .{ 0.19326, 0.68812, 0.96190, 1.0 }, 62 | .{ 0.18625, 0.69775, 0.95498, 1.0 }, 63 | .{ 0.17923, 0.70732, 0.94761, 1.0 }, 64 | .{ 0.17223, 0.71680, 0.93981, 1.0 }, 65 | .{ 0.16529, 0.72620, 0.93161, 1.0 }, 66 | .{ 0.15844, 0.73551, 0.92305, 1.0 }, 67 | .{ 0.15173, 0.74472, 0.91416, 1.0 }, 68 | .{ 0.14519, 0.75381, 0.90496, 1.0 }, 69 | .{ 0.13886, 0.76279, 0.89550, 1.0 }, 70 | .{ 0.13278, 0.77165, 0.88580, 1.0 }, 71 | .{ 0.12698, 0.78037, 0.87590, 1.0 }, 72 | .{ 0.12151, 0.78896, 0.86581, 1.0 }, 73 | .{ 0.11639, 0.79740, 0.85559, 1.0 }, 74 | .{ 0.11167, 0.80569, 0.84525, 1.0 }, 75 | .{ 0.10738, 0.81381, 0.83484, 1.0 }, 76 | .{ 0.10357, 0.82177, 0.82437, 1.0 }, 77 | .{ 0.10026, 0.82955, 0.81389, 1.0 }, 78 | .{ 0.09750, 0.83714, 0.80342, 1.0 }, 79 | .{ 0.09532, 0.84455, 0.79299, 1.0 }, 80 | .{ 0.09377, 0.85175, 0.78264, 1.0 }, 81 | .{ 0.09287, 0.85875, 0.77240, 1.0 }, 82 | .{ 0.09267, 0.86554, 0.76230, 1.0 }, 83 | .{ 0.09320, 0.87211, 0.75237, 1.0 }, 84 | .{ 0.09451, 0.87844, 0.74265, 1.0 }, 85 | .{ 0.09662, 0.88454, 0.73316, 1.0 }, 86 | .{ 0.09958, 0.89040, 0.72393, 1.0 }, 87 | .{ 0.10342, 0.89600, 0.71500, 1.0 }, 88 | .{ 0.10815, 0.90142, 0.70599, 1.0 }, 89 | .{ 0.11374, 0.90673, 0.69651, 1.0 }, 90 | .{ 0.12014, 0.91193, 0.68660, 1.0 }, 91 | .{ 0.12733, 0.91701, 0.67627, 1.0 }, 92 | .{ 0.13526, 0.92197, 0.66556, 1.0 }, 93 | .{ 0.14391, 0.92680, 0.65448, 1.0 }, 94 | .{ 0.15323, 0.93151, 0.64308, 1.0 }, 95 | .{ 0.16319, 0.93609, 0.63137, 1.0 }, 96 | .{ 0.17377, 0.94053, 0.61938, 1.0 }, 97 | .{ 0.18491, 0.94484, 0.60713, 1.0 }, 98 | .{ 0.19659, 0.94901, 0.59466, 1.0 }, 99 | .{ 0.20877, 0.95304, 0.58199, 1.0 }, 100 | .{ 0.22142, 0.95692, 0.56914, 1.0 }, 101 | .{ 0.23449, 0.96065, 0.55614, 1.0 }, 102 | .{ 0.24797, 0.96423, 0.54303, 1.0 }, 103 | .{ 0.26180, 0.96765, 0.52981, 1.0 }, 104 | .{ 0.27597, 0.97092, 0.51653, 1.0 }, 105 | .{ 0.29042, 0.97403, 0.50321, 1.0 }, 106 | .{ 0.30513, 0.97697, 0.48987, 1.0 }, 107 | .{ 0.32006, 0.97974, 0.47654, 1.0 }, 108 | .{ 0.33517, 0.98234, 0.46325, 1.0 }, 109 | .{ 0.35043, 0.98477, 0.45002, 1.0 }, 110 | .{ 0.36581, 0.98702, 0.43688, 1.0 }, 111 | .{ 0.38127, 0.98909, 0.42386, 1.0 }, 112 | .{ 0.39678, 0.99098, 0.41098, 1.0 }, 113 | .{ 0.41229, 0.99268, 0.39826, 1.0 }, 114 | .{ 0.42778, 0.99419, 0.38575, 1.0 }, 115 | .{ 0.44321, 0.99551, 0.37345, 1.0 }, 116 | .{ 0.45854, 0.99663, 0.36140, 1.0 }, 117 | .{ 0.47375, 0.99755, 0.34963, 1.0 }, 118 | .{ 0.48879, 0.99828, 0.33816, 1.0 }, 119 | .{ 0.50362, 0.99879, 0.32701, 1.0 }, 120 | .{ 0.51822, 0.99910, 0.31622, 1.0 }, 121 | .{ 0.53255, 0.99919, 0.30581, 1.0 }, 122 | .{ 0.54658, 0.99907, 0.29581, 1.0 }, 123 | .{ 0.56026, 0.99873, 0.28623, 1.0 }, 124 | .{ 0.57357, 0.99817, 0.27712, 1.0 }, 125 | .{ 0.58646, 0.99739, 0.26849, 1.0 }, 126 | .{ 0.59891, 0.99638, 0.26038, 1.0 }, 127 | .{ 0.61088, 0.99514, 0.25280, 1.0 }, 128 | .{ 0.62233, 0.99366, 0.24579, 1.0 }, 129 | .{ 0.63323, 0.99195, 0.23937, 1.0 }, 130 | .{ 0.64362, 0.98999, 0.23356, 1.0 }, 131 | .{ 0.65394, 0.98775, 0.22835, 1.0 }, 132 | .{ 0.66428, 0.98524, 0.22370, 1.0 }, 133 | .{ 0.67462, 0.98246, 0.21960, 1.0 }, 134 | .{ 0.68494, 0.97941, 0.21602, 1.0 }, 135 | .{ 0.69525, 0.97610, 0.21294, 1.0 }, 136 | .{ 0.70553, 0.97255, 0.21032, 1.0 }, 137 | .{ 0.71577, 0.96875, 0.20815, 1.0 }, 138 | .{ 0.72596, 0.96470, 0.20640, 1.0 }, 139 | .{ 0.73610, 0.96043, 0.20504, 1.0 }, 140 | .{ 0.74617, 0.95593, 0.20406, 1.0 }, 141 | .{ 0.75617, 0.95121, 0.20343, 1.0 }, 142 | .{ 0.76608, 0.94627, 0.20311, 1.0 }, 143 | .{ 0.77591, 0.94113, 0.20310, 1.0 }, 144 | .{ 0.78563, 0.93579, 0.20336, 1.0 }, 145 | .{ 0.79524, 0.93025, 0.20386, 1.0 }, 146 | .{ 0.80473, 0.92452, 0.20459, 1.0 }, 147 | .{ 0.81410, 0.91861, 0.20552, 1.0 }, 148 | .{ 0.82333, 0.91253, 0.20663, 1.0 }, 149 | .{ 0.83241, 0.90627, 0.20788, 1.0 }, 150 | .{ 0.84133, 0.89986, 0.20926, 1.0 }, 151 | .{ 0.85010, 0.89328, 0.21074, 1.0 }, 152 | .{ 0.85868, 0.88655, 0.21230, 1.0 }, 153 | .{ 0.86709, 0.87968, 0.21391, 1.0 }, 154 | .{ 0.87530, 0.87267, 0.21555, 1.0 }, 155 | .{ 0.88331, 0.86553, 0.21719, 1.0 }, 156 | .{ 0.89112, 0.85826, 0.21880, 1.0 }, 157 | .{ 0.89870, 0.85087, 0.22038, 1.0 }, 158 | .{ 0.90605, 0.84337, 0.22188, 1.0 }, 159 | .{ 0.91317, 0.83576, 0.22328, 1.0 }, 160 | .{ 0.92004, 0.82806, 0.22456, 1.0 }, 161 | .{ 0.92666, 0.82025, 0.22570, 1.0 }, 162 | .{ 0.93301, 0.81236, 0.22667, 1.0 }, 163 | .{ 0.93909, 0.80439, 0.22744, 1.0 }, 164 | .{ 0.94489, 0.79634, 0.22800, 1.0 }, 165 | .{ 0.95039, 0.78823, 0.22831, 1.0 }, 166 | .{ 0.95560, 0.78005, 0.22836, 1.0 }, 167 | .{ 0.96049, 0.77181, 0.22811, 1.0 }, 168 | .{ 0.96507, 0.76352, 0.22754, 1.0 }, 169 | .{ 0.96931, 0.75519, 0.22663, 1.0 }, 170 | .{ 0.97323, 0.74682, 0.22536, 1.0 }, 171 | .{ 0.97679, 0.73842, 0.22369, 1.0 }, 172 | .{ 0.98000, 0.73000, 0.22161, 1.0 }, 173 | .{ 0.98289, 0.72140, 0.21918, 1.0 }, 174 | .{ 0.98549, 0.71250, 0.21650, 1.0 }, 175 | .{ 0.98781, 0.70330, 0.21358, 1.0 }, 176 | .{ 0.98986, 0.69382, 0.21043, 1.0 }, 177 | .{ 0.99163, 0.68408, 0.20706, 1.0 }, 178 | .{ 0.99314, 0.67408, 0.20348, 1.0 }, 179 | .{ 0.99438, 0.66386, 0.19971, 1.0 }, 180 | .{ 0.99535, 0.65341, 0.19577, 1.0 }, 181 | .{ 0.99607, 0.64277, 0.19165, 1.0 }, 182 | .{ 0.99654, 0.63193, 0.18738, 1.0 }, 183 | .{ 0.99675, 0.62093, 0.18297, 1.0 }, 184 | .{ 0.99672, 0.60977, 0.17842, 1.0 }, 185 | .{ 0.99644, 0.59846, 0.17376, 1.0 }, 186 | .{ 0.99593, 0.58703, 0.16899, 1.0 }, 187 | .{ 0.99517, 0.57549, 0.16412, 1.0 }, 188 | .{ 0.99419, 0.56386, 0.15918, 1.0 }, 189 | .{ 0.99297, 0.55214, 0.15417, 1.0 }, 190 | .{ 0.99153, 0.54036, 0.14910, 1.0 }, 191 | .{ 0.98987, 0.52854, 0.14398, 1.0 }, 192 | .{ 0.98799, 0.51667, 0.13883, 1.0 }, 193 | .{ 0.98590, 0.50479, 0.13367, 1.0 }, 194 | .{ 0.98360, 0.49291, 0.12849, 1.0 }, 195 | .{ 0.98108, 0.48104, 0.12332, 1.0 }, 196 | .{ 0.97837, 0.46920, 0.11817, 1.0 }, 197 | .{ 0.97545, 0.45740, 0.11305, 1.0 }, 198 | .{ 0.97234, 0.44565, 0.10797, 1.0 }, 199 | .{ 0.96904, 0.43399, 0.10294, 1.0 }, 200 | .{ 0.96555, 0.42241, 0.09798, 1.0 }, 201 | .{ 0.96187, 0.41093, 0.09310, 1.0 }, 202 | .{ 0.95801, 0.39958, 0.08831, 1.0 }, 203 | .{ 0.95398, 0.38836, 0.08362, 1.0 }, 204 | .{ 0.94977, 0.37729, 0.07905, 1.0 }, 205 | .{ 0.94538, 0.36638, 0.07461, 1.0 }, 206 | .{ 0.94084, 0.35566, 0.07031, 1.0 }, 207 | .{ 0.93612, 0.34513, 0.06616, 1.0 }, 208 | .{ 0.93125, 0.33482, 0.06218, 1.0 }, 209 | .{ 0.92623, 0.32473, 0.05837, 1.0 }, 210 | .{ 0.92105, 0.31489, 0.05475, 1.0 }, 211 | .{ 0.91572, 0.30530, 0.05134, 1.0 }, 212 | .{ 0.91024, 0.29599, 0.04814, 1.0 }, 213 | .{ 0.90463, 0.28696, 0.04516, 1.0 }, 214 | .{ 0.89888, 0.27824, 0.04243, 1.0 }, 215 | .{ 0.89298, 0.26981, 0.03993, 1.0 }, 216 | .{ 0.88691, 0.26152, 0.03753, 1.0 }, 217 | .{ 0.88066, 0.25334, 0.03521, 1.0 }, 218 | .{ 0.87422, 0.24526, 0.03297, 1.0 }, 219 | .{ 0.86760, 0.23730, 0.03082, 1.0 }, 220 | .{ 0.86079, 0.22945, 0.02875, 1.0 }, 221 | .{ 0.85380, 0.22170, 0.02677, 1.0 }, 222 | .{ 0.84662, 0.21407, 0.02487, 1.0 }, 223 | .{ 0.83926, 0.20654, 0.02305, 1.0 }, 224 | .{ 0.83172, 0.19912, 0.02131, 1.0 }, 225 | .{ 0.82399, 0.19182, 0.01966, 1.0 }, 226 | .{ 0.81608, 0.18462, 0.01809, 1.0 }, 227 | .{ 0.80799, 0.17753, 0.01660, 1.0 }, 228 | .{ 0.79971, 0.17055, 0.01520, 1.0 }, 229 | .{ 0.79125, 0.16368, 0.01387, 1.0 }, 230 | .{ 0.78260, 0.15693, 0.01264, 1.0 }, 231 | .{ 0.77377, 0.15028, 0.01148, 1.0 }, 232 | .{ 0.76476, 0.14374, 0.01041, 1.0 }, 233 | .{ 0.75556, 0.13731, 0.00942, 1.0 }, 234 | .{ 0.74617, 0.13098, 0.00851, 1.0 }, 235 | .{ 0.73661, 0.12477, 0.00769, 1.0 }, 236 | .{ 0.72686, 0.11867, 0.00695, 1.0 }, 237 | .{ 0.71692, 0.11268, 0.00629, 1.0 }, 238 | .{ 0.70680, 0.10680, 0.00571, 1.0 }, 239 | .{ 0.69650, 0.10102, 0.00522, 1.0 }, 240 | .{ 0.68602, 0.09536, 0.00481, 1.0 }, 241 | .{ 0.67535, 0.08980, 0.00449, 1.0 }, 242 | .{ 0.66449, 0.08436, 0.00424, 1.0 }, 243 | .{ 0.65345, 0.07902, 0.00408, 1.0 }, 244 | .{ 0.64223, 0.07380, 0.00401, 1.0 }, 245 | .{ 0.63082, 0.06868, 0.00401, 1.0 }, 246 | .{ 0.61923, 0.06367, 0.00410, 1.0 }, 247 | .{ 0.60746, 0.05878, 0.00427, 1.0 }, 248 | .{ 0.59550, 0.05399, 0.00453, 1.0 }, 249 | .{ 0.58336, 0.04931, 0.00486, 1.0 }, 250 | .{ 0.57103, 0.04474, 0.00529, 1.0 }, 251 | .{ 0.55852, 0.04028, 0.00579, 1.0 }, 252 | .{ 0.54583, 0.03593, 0.00638, 1.0 }, 253 | .{ 0.53295, 0.03169, 0.00705, 1.0 }, 254 | .{ 0.51989, 0.02756, 0.00780, 1.0 }, 255 | .{ 0.50664, 0.02354, 0.00863, 1.0 }, 256 | .{ 0.49321, 0.01963, 0.00955, 1.0 }, 257 | .{ 0.47960, 0.01583, 0.01055, 1.0 }, 258 | }; 259 | -------------------------------------------------------------------------------- /src/geometry/mat.zig: -------------------------------------------------------------------------------- 1 | //! Matrix math operations 2 | 3 | pub fn mul(comptime M: usize, comptime N: usize, comptime P: usize, comptime T: type, a: [N][M]T, b: [P][N]T) [P][M]T { 4 | const a_t = transpose(N, M, T, a); 5 | 6 | var res: [P][M]T = undefined; 7 | 8 | for (&res, 0..) |*column, i| { 9 | const vb: @Vector(N, T) = b[i]; 10 | for (column, 0..) |*c, j| { 11 | const va: @Vector(N, T) = a_t[j]; 12 | 13 | c.* = @reduce(.Add, va * vb); 14 | } 15 | } 16 | 17 | return res; 18 | } 19 | 20 | pub fn transpose(comptime N: usize, comptime M: usize, comptime T: type, matrix: [N][M]T) [M][N]T { 21 | var result: [M][N]T = undefined; 22 | 23 | for (0..M) |i| { 24 | for (0..N) |j| { 25 | result[i][j] = matrix[j][i]; 26 | } 27 | } 28 | 29 | return result; 30 | } 31 | 32 | test { 33 | try std.testing.expectEqualDeep([2][3]f32{ 34 | .{ 7, 9, 11 }, 35 | .{ 8, 10, 12 }, 36 | }, transpose(3, 2, f32, .{ 37 | .{ 7, 8 }, 38 | .{ 9, 10 }, 39 | .{ 11, 12 }, 40 | })); 41 | } 42 | 43 | const std = @import("std"); 44 | 45 | test mul { 46 | try std.testing.expectEqualDeep([3][4]f32{ 47 | .{ 5, 8, 6, 11 }, 48 | .{ 4, 9, 5, 9 }, 49 | .{ 3, 5, 3, 6 }, 50 | }, mul( 51 | 4, 52 | 3, 53 | 3, 54 | f32, 55 | .{ 56 | .{ 1, 2, 0, 1 }, 57 | .{ 0, 1, 1, 1 }, 58 | .{ 1, 1, 1, 2 }, 59 | }, 60 | .{ 61 | .{ 1, 2, 4 }, 62 | .{ 2, 3, 2 }, 63 | .{ 1, 1, 2 }, 64 | }, 65 | )); 66 | 67 | try std.testing.expectEqualDeep([1][4]f32{ 68 | .{ 1, 2, 3, 4 }, 69 | }, mul( 70 | 4, 71 | 4, 72 | 1, 73 | f32, 74 | .{ 75 | .{ 1, 0, 0, 0 }, 76 | .{ 0, 1, 0, 0 }, 77 | .{ 0, 0, 1, 0 }, 78 | .{ 0, 0, 0, 1 }, 79 | }, 80 | .{ 81 | .{ 1, 2, 3, 4 }, 82 | }, 83 | )); 84 | 85 | try std.testing.expectEqualDeep([4][4]f32{ 86 | .{ 1, 0, 0, 0 }, 87 | .{ 0, 1, 0, 0 }, 88 | .{ 0, 0, 1, 0 }, 89 | .{ 7, 9, 11, 1 }, 90 | }, mul( 91 | 4, 92 | 4, 93 | 4, 94 | f32, 95 | .{ 96 | .{ 1, 0, 0, 0 }, 97 | .{ 0, 1, 0, 0 }, 98 | .{ 0, 0, 1, 0 }, 99 | .{ 2, 3, 4, 1 }, 100 | }, 101 | .{ 102 | .{ 1, 0, 0, 0 }, 103 | .{ 0, 1, 0, 0 }, 104 | .{ 0, 0, 1, 0 }, 105 | .{ 5, 6, 7, 1 }, 106 | }, 107 | )); 108 | 109 | try std.testing.expectEqualDeep([4][4]f32{ 110 | .{ 1, 0, 0, 0 }, 111 | .{ 0, 1, 0, 0 }, 112 | .{ 0, 0, 1, 0 }, 113 | .{ 5, 6, 7, 0 }, 114 | }, mul( 115 | 4, 116 | 4, 117 | 4, 118 | f32, 119 | .{ 120 | .{ 1, 0, 0, 0 }, 121 | .{ 0, 1, 0, 0 }, 122 | .{ 0, 0, 1, 0 }, 123 | .{ 2, 3, 4, 0 }, 124 | }, 125 | .{ 126 | .{ 1, 0, 0, 0 }, 127 | .{ 0, 1, 0, 0 }, 128 | .{ 0, 0, 1, 0 }, 129 | .{ 5, 6, 7, 0 }, 130 | }, 131 | )); 132 | } 133 | -------------------------------------------------------------------------------- /src/geometry/mat3.zig: -------------------------------------------------------------------------------- 1 | pub fn identity(comptime T: type) [3][3]T { 2 | return .{ 3 | .{ 1, 0, 0 }, 4 | .{ 0, 1, 0 }, 5 | .{ 0, 0, 1 }, 6 | }; 7 | } 8 | 9 | pub fn mulVec(comptime T: type, a: [3][3]T, b: [3]T) [3]T { 10 | return mat.mul(3, 3, 1, T, a, .{b})[0]; 11 | } 12 | 13 | pub fn mul(comptime T: type, a: [3][3]T, b: [3][3]T) [3][3]T { 14 | return mat.mul(3, 3, 3, T, a, b); 15 | } 16 | 17 | /// Multiply all matrices in a list together 18 | pub fn mulAll(comptime T: type, matrices: []const [3][3]T) [3][3]T { 19 | var res: [3][3]T = matrices[matrices.len - 1]; 20 | 21 | for (0..matrices.len - 1) |i| { 22 | const inverse_i = (matrices.len - 2) - i; 23 | const left_matrix = matrices[inverse_i]; 24 | res = mul(T, left_matrix, res); 25 | } 26 | 27 | return res; 28 | } 29 | 30 | pub fn translate(comptime T: type, translation: [2]T) [3][3]T { 31 | return .{ 32 | .{ 1, 0, 0 }, 33 | .{ 0, 1, 0 }, 34 | translation ++ .{1}, 35 | }; 36 | } 37 | 38 | pub fn scale(comptime T: type, scaling: [2]T) [3][3]T { 39 | return .{ 40 | .{ scaling[0], 0, 0 }, 41 | .{ 0, scaling[1], 0 }, 42 | .{ 0, 0, 1 }, 43 | }; 44 | } 45 | 46 | pub fn rotate(comptime T: type, turns: T) [3][3]T { 47 | return .{ 48 | .{ @cos(turns * std.math.tau), @sin(turns * std.math.tau), 0 }, 49 | .{ -@sin(turns * std.math.tau), @cos(turns * std.math.tau), 0 }, 50 | .{ 0, 0, 1 }, 51 | }; 52 | } 53 | 54 | pub fn shear(comptime T: type, turns: [2]T) [3][3]T { 55 | return .{ 56 | .{ 1, @tan(turns[1] * std.math.tau), 0 }, 57 | .{ @tan(turns[0] * std.math.tau), 1, 0 }, 58 | .{ 0, 0, 1 }, 59 | }; 60 | } 61 | 62 | pub fn orthographic(comptime T: type, left: T, right: T, bottom: T, top: T) [3][3]T { 63 | const widthRatio = 1 / (right - left); 64 | const heightRatio = 1 / (top - bottom); 65 | const tx = -(right + left) * widthRatio; 66 | const ty = -(top + bottom) * heightRatio; 67 | return .{ 68 | .{ 2 * widthRatio, 0, 0 }, 69 | .{ 0, 2 * heightRatio, 0 }, 70 | .{ tx, ty, 1 }, 71 | }; 72 | } 73 | 74 | const std = @import("std"); 75 | const vec = @import("./vec.zig"); 76 | const mat = @import("./mat.zig"); 77 | -------------------------------------------------------------------------------- /src/geometry/mat4.zig: -------------------------------------------------------------------------------- 1 | pub fn identity(comptime T: type) [4][4]T { 2 | return .{ 3 | .{ 1, 0, 0, 0 }, 4 | .{ 0, 1, 0, 0 }, 5 | .{ 0, 0, 1, 0 }, 6 | .{ 0, 0, 0, 1 }, 7 | }; 8 | } 9 | 10 | pub fn mulVec(comptime T: type, a: [4][4]T, b: [4]T) [4]T { 11 | return mat.mul(4, 4, 1, T, a, .{b})[0]; 12 | } 13 | 14 | pub fn mul(comptime T: type, a: [4][4]T, b: [4][4]T) [4][4]T { 15 | return mat.mul(4, 4, 4, T, a, b); 16 | } 17 | 18 | /// Multiply all matrices in a list together 19 | pub fn mulAll(comptime T: type, matrices: []const [4][4]T) [4][4]T { 20 | var res: [4][4]T = matrices[matrices.len - 1]; 21 | 22 | for (0..matrices.len - 1) |i| { 23 | const inverse_i = (matrices.len - 2) - i; 24 | const left_matrix = matrices[inverse_i]; 25 | res = mul(T, left_matrix, res); 26 | } 27 | 28 | return res; 29 | } 30 | 31 | pub fn translate(comptime T: type, translation: [3]T) [4][4]T { 32 | return .{ 33 | .{ 1, 0, 0, 0 }, 34 | .{ 0, 1, 0, 0 }, 35 | .{ 0, 0, 1, 0 }, 36 | translation ++ .{1}, 37 | }; 38 | } 39 | 40 | pub fn scale(comptime T: type, scaling: [3]T) [4][4]T { 41 | return .{ 42 | .{ scaling[0], 0, 0, 0 }, 43 | .{ 0, scaling[1], 0, 0 }, 44 | .{ 0, 0, scaling[2], 0 }, 45 | .{ 0, 0, 0, 1 }, 46 | }; 47 | } 48 | 49 | pub fn rotateZ(comptime T: type, turns: T) [4][4]T { 50 | return .{ 51 | .{ @cos(turns * std.math.tau), @sin(turns * std.math.tau), 0, 0 }, 52 | .{ -@sin(turns * std.math.tau), @cos(turns * std.math.tau), 0, 0 }, 53 | .{ 0, 0, 1, 0 }, 54 | .{ 0, 0, 0, 1 }, 55 | }; 56 | } 57 | 58 | const std = @import("std"); 59 | const vec = @import("./vec.zig"); 60 | const mat = @import("./mat.zig"); 61 | 62 | test translate { 63 | try std.testing.expectEqualDeep( 64 | [4]f32{ 7, 9, 11, 1 }, 65 | mulVec( 66 | f32, 67 | translate(f32, .{ 2, 3, 4 }), 68 | .{ 5, 6, 7, 1 }, 69 | ), 70 | ); 71 | } 72 | 73 | test mulAll { 74 | try std.testing.expectEqualDeep( 75 | mul( 76 | f32, 77 | mul( 78 | f32, 79 | translate(f32, .{ 80 | 20.0 / 2.0, 81 | 20.0 / 2.0, 82 | 0, 83 | }), 84 | scale(f32, .{ 85 | 640.0 / 4096.0, 86 | 500.0 / 2080.0, 87 | 1, 88 | }), 89 | ), 90 | mul( 91 | f32, 92 | scale(f32, .{ 93 | 2.0, 94 | 2.0, 95 | 1.0, 96 | }), 97 | translate(f32, .{ 98 | 4096.0 / 2.0, 99 | 2080.0 / 2.0, 100 | 0, 101 | }), 102 | ), 103 | ), 104 | mulAll(f32, &.{ 105 | translate(f32, .{ 106 | 20.0 / 2.0, 107 | 20.0 / 2.0, 108 | 0, 109 | }), 110 | scale(f32, .{ 111 | 640.0 / 4096.0, 112 | 500.0 / 2080.0, 113 | 1, 114 | }), 115 | scale(f32, .{ 116 | 2.0, 117 | 2.0, 118 | 1.0, 119 | }), 120 | translate(f32, .{ 121 | 4096.0 / 2.0, 122 | 2080.0 / 2.0, 123 | 0, 124 | }), 125 | }), 126 | ); 127 | } 128 | -------------------------------------------------------------------------------- /src/geometry/vec.zig: -------------------------------------------------------------------------------- 1 | pub fn into(comptime T: type, vec: anytype) [2]T { 2 | const t = @typeInfo(T); 3 | const vt = @typeInfo(@TypeOf(vec)); 4 | const vt_child = @typeInfo(vt.Array.child); 5 | if (vt != .Array) @compileLog("vec.to only operates on arrays"); 6 | if (t == .Float or t == .ComptimeFloat) { 7 | return if (vt_child == .Float) 8 | .{ @floatCast(vec[0]), @floatCast(vec[1]) } 9 | else 10 | .{ @floatFromInt(vec[0]), @floatFromInt(vec[1]) }; 11 | } else if (t == .Int or t == .ComptimeInt) { 12 | return if (vt_child == .Int) 13 | .{ @intCast(vec[0]), @intCast(vec[1]) } 14 | else 15 | .{ @intFromFloat(vec[0]), @intFromFloat(vec[1]) }; 16 | } else { 17 | @panic("Attempting to convert vector to non-numeric type"); 18 | } 19 | } 20 | 21 | pub fn cross(comptime T: type, a: [3]T, b: [3]T) [3]T { 22 | return .{ 23 | a[1] * b[2] - a[2] * b[1], 24 | a[2] * b[0] - a[0] * b[2], 25 | a[0] * b[1] - a[1] * b[0], 26 | }; 27 | } 28 | 29 | test cross { 30 | try std.testing.expectEqualSlices(f32, &.{ 0, 1, 0 }, &cross(f32, .{ 0, 0, 1 }, .{ 1, 0, 0 })); 31 | } 32 | 33 | pub fn magnitude(comptime D: usize, comptime T: type, vector: @Vector(D, T)) T { 34 | return @sqrt(@reduce(.Add, vector * vector)); 35 | } 36 | 37 | test magnitude { 38 | try std.testing.expectEqual(@as(f32, 1.0), magnitude(2, f32, .{ 0, 1 })); 39 | try std.testing.expectEqual(@as(f32, @sqrt(2.0)), magnitude(2, f32, .{ 1, 1 })); 40 | try std.testing.expectEqual(@as(f32, @sqrt(2.0)), magnitude(2, f32, .{ 1, -1 })); 41 | } 42 | 43 | pub fn normalize(comptime D: usize, comptime T: type, vector: @Vector(D, T)) @Vector(D, T) { 44 | return vector / @as(@Vector(D, T), @splat(magnitude(D, T, vector))); 45 | } 46 | 47 | test normalize { 48 | try std.testing.expectEqual(@Vector(2, f32){ 0, 1 }, normalize(2, f32, .{ 0, 10 })); 49 | try std.testing.expectEqual(@Vector(2, f32){ 1.0 / @sqrt(2.0), 1.0 / @sqrt(2.0) }, normalize(2, f32, .{ 5, 5 })); 50 | } 51 | 52 | const std = @import("std"); 53 | -------------------------------------------------------------------------------- /src/input.zig: -------------------------------------------------------------------------------- 1 | pub const gamepad = @import("./input/gamepad.zig"); 2 | pub const keyboard = @import("./input/keyboard.zig"); 3 | pub const mouse = @import("./input/mouse.zig"); 4 | 5 | pub const Event = union(enum) { 6 | hover: Hover, 7 | click: Click, 8 | scroll: Scroll, 9 | text: Text, 10 | key: Key, 11 | 12 | pub const Hover = struct { 13 | pos: [2]f64, 14 | modifiers: mouse.Modifiers, 15 | 16 | pub fn transform(hover: Hover, transform_matrix: [4][4]f64) Hover { 17 | const transformed_pos = seizer.geometry.mat4.mulVec(f64, transform_matrix, .{ 18 | hover.pos[0], 19 | hover.pos[1], 20 | 0, 21 | 1, 22 | })[0..2].*; 23 | return Hover{ .pos = transformed_pos, .modifiers = hover.modifiers }; 24 | } 25 | }; 26 | 27 | pub const Click = struct { 28 | pos: [2]f64, 29 | button: mouse.Button, 30 | pressed: bool, 31 | 32 | pub fn transform(click: Click, transform_matrix: [4][4]f64) Click { 33 | const transformed_pos = seizer.geometry.mat4.mulVec(f64, transform_matrix, .{ 34 | click.pos[0], 35 | click.pos[1], 36 | 0, 37 | 1, 38 | })[0..2].*; 39 | return Click{ 40 | .pos = transformed_pos, 41 | .button = click.button, 42 | .pressed = click.pressed, 43 | }; 44 | } 45 | }; 46 | 47 | pub const Scroll = struct { 48 | offset: [2]f64, 49 | }; 50 | 51 | pub const Text = struct { 52 | text: std.BoundedArray(u8, 16), 53 | }; 54 | 55 | pub const Key = struct { 56 | key: keyboard.Key, 57 | scancode: keyboard.Scancode, 58 | action: keyboard.Action, 59 | mods: keyboard.Modifiers, 60 | }; 61 | 62 | pub fn transform(this: Event, matrix: [4][4]f64) Event { 63 | return switch (this) { 64 | .hover => |hover| .{ .hover = hover.transform(matrix) }, 65 | .click => |click| .{ .click = click.transform(matrix) }, 66 | else => this, 67 | }; 68 | } 69 | }; 70 | 71 | const seizer = @import("./seizer.zig"); 72 | const std = @import("std"); 73 | -------------------------------------------------------------------------------- /src/input/keyboard.zig: -------------------------------------------------------------------------------- 1 | /// The value of a key pressed by the user, taking modifier keys (e.g. `Shift`), 2 | /// keyboard locale, and keyboard layout into account. 3 | /// 4 | /// Similar to `xkb.Symbol`. 5 | /// 6 | /// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values 7 | pub const Key = union(enum) { 8 | unidentified, 9 | /// The key has a printable unicode value. 10 | unicode: u21, 11 | 12 | // https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#modifier_keys 13 | 14 | alt, 15 | alt_graph, 16 | caps_lock, 17 | control, 18 | @"fn", 19 | fn_lock, 20 | hyper, 21 | meta, 22 | num_lock, 23 | scroll_lock, 24 | shift, 25 | super, 26 | symbol, 27 | symbol_lock, 28 | 29 | // https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#whitespace_keys 30 | 31 | enter, 32 | tab, 33 | 34 | // https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#navigation_keys 35 | 36 | arrow_down, 37 | arrow_left, 38 | arrow_right, 39 | arrow_up, 40 | end, 41 | home, 42 | page_down, 43 | page_up, 44 | 45 | // https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#editing_keys 46 | 47 | backspace, 48 | clear, 49 | copy, 50 | /// The Cursor Select key 51 | cr_sel, 52 | cut, 53 | delete, 54 | erase_eof, 55 | /// The Extend Selection key 56 | ex_sel, 57 | insert, 58 | paste, 59 | redo, 60 | undo, 61 | 62 | // https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#function_keys 63 | 64 | f1, 65 | f2, 66 | f3, 67 | f4, 68 | f5, 69 | f6, 70 | f7, 71 | f8, 72 | f9, 73 | f10, 74 | f11, 75 | f12, 76 | f13, 77 | f14, 78 | f15, 79 | f16, 80 | f17, 81 | f18, 82 | f19, 83 | f20, 84 | 85 | /// The first general-purpose virtual function key. 86 | soft1, 87 | /// The second general-purpose virtual function key. 88 | soft2, 89 | /// The third general-purpose virtual function key. 90 | soft3, 91 | /// The fourth general-purpose virtual function key. 92 | soft4, 93 | 94 | // https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values#ui_keys 95 | 96 | accept, 97 | again, 98 | attn, 99 | cancel, 100 | context_menu, 101 | escape, 102 | execute, 103 | find, 104 | finish, 105 | help, 106 | /// Pauses the application or state, if applicable. 107 | /// 108 | /// Note: Not to be confused with `media_pause`. 109 | pause, 110 | /// Resumes a previously paused application, if applicable. 111 | /// 112 | /// Note: Not to be confused with `media_play`. 113 | play, 114 | props, 115 | select, 116 | zoom_in, 117 | zoom_out, 118 | }; 119 | 120 | /// Similar to the Linux kernel's EvDev `KEY` values. 121 | /// 122 | /// https://www.w3.org/TR/uievents-code/ 123 | pub const Scancode = enum { 124 | 125 | // https://www.w3.org/TR/uievents-code/#key-alphanumeric-writing-system 126 | 127 | /// `\`~` on a US keyboard 128 | backquote, 129 | /// `\|` on a US keyboard 130 | backslash, 131 | /// `[{` on a US keyboard 132 | bracket_left, 133 | /// `]}` on a US keyboard 134 | bracket_right, 135 | /// `,<` on a US keyboard 136 | comma, 137 | /// `0)` on a US keyboard 138 | @"0", 139 | /// `1!` on a US keyboard 140 | @"1", 141 | /// `2@` on a US keyboard 142 | @"2", 143 | /// `3#` on a US keyboard 144 | @"3", 145 | /// `4$` on a US keyboard 146 | @"4", 147 | /// `5%` on a US keyboard 148 | @"5", 149 | /// `6^` on a US keyboard 150 | @"6", 151 | /// `7&` on a US keyboard 152 | @"7", 153 | /// `8*` on a US keyboard 154 | @"8", 155 | /// `9(` on a US keyboard 156 | @"9", 157 | /// `=+` on a US keyboard 158 | equal, 159 | 160 | /// Located between the left `Shift` and `Z` keys. Labelled `\|` on a UK keyboard. 161 | intl_backslash, 162 | /// Located between the `/` and right `Shift` keys. Labelled `\ろ` (ro) on a Japanese keyboard. 163 | intl_ro, 164 | /// Located between the `=` and `Backspace` keys. Labelled `¥` (yen) on a Japanese keyboard. `\/` on a Russian keyboard. 165 | intl_yen, 166 | 167 | q, 168 | w, 169 | e, 170 | r, 171 | t, 172 | y, 173 | u, 174 | i, 175 | o, 176 | p, 177 | 178 | a, 179 | s, 180 | d, 181 | f, 182 | g, 183 | h, 184 | j, 185 | k, 186 | l, 187 | 188 | z, 189 | x, 190 | c, 191 | v, 192 | b, 193 | n, 194 | m, 195 | 196 | /// `-_` on a US keyboard 197 | minus, 198 | period, 199 | quote, 200 | semicolon, 201 | /// `/?` on a US keyboard 202 | slash, 203 | 204 | // https://www.w3.org/TR/uievents-code/#key-alphanumeric-functional 205 | 206 | alt_left, 207 | alt_right, 208 | backspace, 209 | caps_lock, 210 | context_menu, 211 | control_left, 212 | control_right, 213 | enter, 214 | meta_left, 215 | meta_right, 216 | shift_left, 217 | shift_right, 218 | space, 219 | tab, 220 | 221 | convert, 222 | kana_mode, 223 | lang1, 224 | lang2, 225 | lang3, 226 | lang4, 227 | lang5, 228 | non_convert, 229 | 230 | // https://www.w3.org/TR/uievents-code/#key-controlpad-section 231 | 232 | delete, 233 | end, 234 | help, 235 | home, 236 | insert, 237 | page_down, 238 | page_up, 239 | 240 | // https://www.w3.org/TR/uievents-code/#key-arrowpad-section 241 | 242 | arrow_down, 243 | arrow_left, 244 | arrow_right, 245 | arrow_up, 246 | 247 | // https://www.w3.org/TR/uievents-code/#key-numpad-section 248 | 249 | num_lock, 250 | numpad0, 251 | numpad1, 252 | numpad2, 253 | numpad3, 254 | numpad4, 255 | numpad5, 256 | numpad6, 257 | numpad7, 258 | numpad8, 259 | numpad9, 260 | numpad_add, 261 | numpad_backspace, 262 | numpad_clear, 263 | numpad_clear_entry, 264 | numpad_comma, 265 | numpad_decimal, 266 | numpad_divide, 267 | numpad_enter, 268 | numpad_equal, 269 | numpad_hash, 270 | numpad_memory_add, 271 | numpad_memory_clear, 272 | numpad_memory_recall, 273 | numpad_memory_store, 274 | numpad_memory_subtract, 275 | numpad_multiply, 276 | numpad_paren_left, 277 | numpad_paren_right, 278 | numpad_star, 279 | numpad_subtract, 280 | 281 | // https://www.w3.org/TR/uievents-code/#key-function-section 282 | 283 | escape, 284 | f1, 285 | f2, 286 | f3, 287 | f4, 288 | f5, 289 | f6, 290 | f7, 291 | f8, 292 | f9, 293 | f10, 294 | f11, 295 | f12, 296 | @"fn", 297 | fn_lock, 298 | print_screen, 299 | scroll_lock, 300 | pause, 301 | 302 | // https://www.w3.org/TR/uievents-code/#key-media 303 | 304 | browser_back, 305 | browser_favorites, 306 | browser_forward, 307 | browser_home, 308 | browser_refresh, 309 | browser_search, 310 | browser_stop, 311 | eject, 312 | launch_app1, 313 | launch_app2, 314 | launch_mail, 315 | media_play_pause, 316 | media_select, 317 | media_stop, 318 | media_track_next, 319 | media_track_previous, 320 | power, 321 | sleep, 322 | audio_volume_down, 323 | audio_volume_mute, 324 | audio_volume_up, 325 | wake_up, 326 | }; 327 | 328 | pub const Action = enum { 329 | press, 330 | repeat, 331 | release, 332 | }; 333 | 334 | pub const Modifiers = packed struct { 335 | shift: bool = false, 336 | control: bool = false, 337 | alt: bool = false, 338 | super: bool = false, 339 | caps_lock: bool = false, 340 | num_lock: bool = false, 341 | }; 342 | 343 | const std = @import("std"); 344 | -------------------------------------------------------------------------------- /src/input/mouse.zig: -------------------------------------------------------------------------------- 1 | /// Values taken from the linux/input-event-codes.h 2 | pub const Button = enum(u16) { 3 | left = 0x110, 4 | right = 0x111, 5 | middle = 0x112, 6 | side = 0x113, 7 | extra = 0x114, 8 | forward = 0x115, 9 | back = 0x116, 10 | task = 0x117, 11 | }; 12 | 13 | pub const Modifiers = packed struct { 14 | left: bool, 15 | right: bool, 16 | middle: bool, 17 | }; 18 | -------------------------------------------------------------------------------- /src/seizer.zig: -------------------------------------------------------------------------------- 1 | // seizer sub libraries 2 | pub const color = @import("./color.zig"); 3 | pub const colormaps = @import("./colormaps.zig"); 4 | pub const geometry = @import("./geometry.zig"); 5 | pub const image = @import("./image.zig"); 6 | pub const input = @import("./input.zig"); 7 | pub const ui = @import("./ui.zig"); 8 | 9 | pub const Canvas = @import("./Canvas.zig"); 10 | pub const Display = @import("./Display.zig"); 11 | 12 | // re-exported libraries 13 | pub const tvg = @import("tvg"); 14 | pub const xev = @import("xev"); 15 | pub const libcoro = @import("libcoro"); 16 | 17 | /// Seizer version 18 | pub const version = std.SemanticVersion{ 19 | .major = 0, 20 | .minor = 1, 21 | .patch = 0, 22 | }; 23 | 24 | // 25 | const seizer = @This(); 26 | 27 | var loop: xev.Loop = undefined; 28 | var deinit_fn: ?DeinitFn = null; 29 | 30 | pub fn main() anyerror!void { 31 | const root = @import("root"); 32 | 33 | if (!@hasDecl(root, "init")) { 34 | @compileError("root module must contain init function"); 35 | } 36 | 37 | loop = try xev.Loop.init(.{}); 38 | defer loop.deinit(); 39 | 40 | // Call root module's `init()` function 41 | root.init() catch |err| { 42 | std.debug.print("{s}\n", .{@errorName(err)}); 43 | if (@errorReturnTrace()) |trace| { 44 | std.debug.dumpStackTrace(trace.*); 45 | } 46 | return; 47 | }; 48 | defer { 49 | if (deinit_fn) |deinit| { 50 | deinit(); 51 | } 52 | } 53 | 54 | try loop.run(.until_done); 55 | } 56 | 57 | pub fn getLoop() *xev.Loop { 58 | return &loop; 59 | } 60 | 61 | pub const DeinitFn = *const fn () void; 62 | pub fn setDeinit(new_deinit_fn: ?seizer.DeinitFn) void { 63 | deinit_fn = new_deinit_fn; 64 | } 65 | 66 | comptime { 67 | if (builtin.is_test) { 68 | _ = color; 69 | _ = image; 70 | } 71 | } 72 | 73 | const builtin = @import("builtin"); 74 | const std = @import("std"); 75 | -------------------------------------------------------------------------------- /src/ui/Element.zig: -------------------------------------------------------------------------------- 1 | pub const Button = @import("./Element/Button.zig"); 2 | pub const FlexBox = @import("./Element/FlexBox.zig"); 3 | pub const Frame = @import("./Element/Frame.zig"); 4 | pub const Image = @import("./Element/Image.zig"); 5 | pub const Label = @import("./Element/Label.zig"); 6 | pub const PanZoom = @import("./Element/PanZoom.zig"); 7 | pub const Plot = @import("./Element/Plot.zig"); 8 | pub const TextField = @import("./Element/TextField.zig"); 9 | 10 | ptr: ?*anyopaque, 11 | interface: *const Interface, 12 | 13 | const Element = @This(); 14 | 15 | pub const TransformedRect = struct { 16 | rect: AABB, 17 | transform: [4][4]f64, 18 | 19 | pub fn transformWithTranslation(this: @This()) [4][4]f64 { 20 | return seizer.geometry.mat4.mul( 21 | f64, 22 | seizer.geometry.mat4.translate(f64, .{ -this.rect.min()[0], -this.rect.min()[1], 0 }), 23 | this.transform, 24 | ); 25 | } 26 | }; 27 | 28 | pub const Interface = struct { 29 | acquire_fn: *const fn (Element) void, 30 | release_fn: *const fn (Element) void, 31 | set_parent_fn: *const fn (Element, ?Element) void, 32 | get_parent_fn: *const fn (Element) ?Element, 33 | get_child_rect_fn: *const fn (Element, child: Element) ?TransformedRect = getChildRectDefault, 34 | 35 | process_event_fn: *const fn (Element, event: seizer.input.Event) ?Element, 36 | get_min_size_fn: *const fn (Element) [2]f64, 37 | layout_fn: *const fn (Element, min_size: [2]f64, max_size: [2]f64) [2]f64 = layoutDefault, 38 | render_fn: *const fn (Element, Canvas, AABB) void, 39 | 40 | pub fn getTypeErasedFunctions(comptime T: type, typed_fns: struct { 41 | acquire_fn: *const fn (*T) void, 42 | release_fn: *const fn (*T) void, 43 | set_parent_fn: *const fn (*T, ?Element) void, 44 | get_parent_fn: *const fn (*T) ?Element, 45 | get_child_rect_fn: ?*const fn (*T, child: Element) ?TransformedRect = null, 46 | 47 | process_event_fn: *const fn (*T, event: seizer.input.Event) ?Element, 48 | get_min_size_fn: *const fn (*T) [2]f64, 49 | layout_fn: ?*const fn (*T, min_size: [2]f64, max_size: [2]f64) [2]f64 = null, 50 | render_fn: *const fn (*T, Canvas, AABB) void, 51 | }) Interface { 52 | const type_erased_fns = struct { 53 | fn acquire(element: Element) void { 54 | const t: *T = @ptrCast(@alignCast(element.ptr)); 55 | return typed_fns.acquire_fn(t); 56 | } 57 | fn release(element: Element) void { 58 | const t: *T = @ptrCast(@alignCast(element.ptr)); 59 | return typed_fns.release_fn(t); 60 | } 61 | fn setParent(element: Element, parent: ?Element) void { 62 | const t: *T = @ptrCast(@alignCast(element.ptr)); 63 | return typed_fns.set_parent_fn(t, parent); 64 | } 65 | fn getParent(element: Element) ?Element { 66 | const t: *T = @ptrCast(@alignCast(element.ptr)); 67 | return typed_fns.get_parent_fn(t); 68 | } 69 | fn getChildRect(element: Element, child: Element) ?TransformedRect { 70 | const t: *T = @ptrCast(@alignCast(element.ptr)); 71 | if (typed_fns.get_child_rect_fn) |get_child_rect_fn| { 72 | return get_child_rect_fn(t, child); 73 | } else { 74 | return element.getChildRectDefault(child); 75 | } 76 | } 77 | fn processEvent(element: Element, event: seizer.input.Event) ?Element { 78 | const t: *T = @ptrCast(@alignCast(element.ptr)); 79 | return typed_fns.process_event_fn(t, event); 80 | } 81 | fn getMinSize(element: Element) [2]f64 { 82 | const t: *T = @ptrCast(@alignCast(element.ptr)); 83 | return typed_fns.get_min_size_fn(t); 84 | } 85 | fn layout(element: Element, min_size: [2]f64, max_size: [2]f64) [2]f64 { 86 | const t: *T = @ptrCast(@alignCast(element.ptr)); 87 | if (typed_fns.layout_fn) |layout_fn| { 88 | return layout_fn(t, min_size, max_size); 89 | } else { 90 | return element.layoutDefault(min_size, max_size); 91 | } 92 | } 93 | fn render(element: Element, canvas: Canvas, rect: AABB) void { 94 | const t: *T = @ptrCast(@alignCast(element.ptr)); 95 | return typed_fns.render_fn(t, canvas, rect); 96 | } 97 | }; 98 | return Interface{ 99 | .acquire_fn = type_erased_fns.acquire, 100 | .release_fn = type_erased_fns.release, 101 | .set_parent_fn = type_erased_fns.setParent, 102 | .get_parent_fn = type_erased_fns.getParent, 103 | .get_child_rect_fn = type_erased_fns.getChildRect, 104 | 105 | .process_event_fn = type_erased_fns.processEvent, 106 | .get_min_size_fn = type_erased_fns.getMinSize, 107 | .layout_fn = type_erased_fns.layout, 108 | .render_fn = type_erased_fns.render, 109 | }; 110 | } 111 | }; 112 | 113 | // Reference counting functions 114 | 115 | pub fn acquire(element: Element) void { 116 | return element.interface.acquire_fn(element); 117 | } 118 | 119 | pub fn release(element: Element) void { 120 | return element.interface.release_fn(element); 121 | } 122 | 123 | // Parent setter/getter 124 | pub fn setParent(element: Element, parent: ?Element) void { 125 | return element.interface.set_parent_fn(element, parent); 126 | } 127 | 128 | pub fn getParent(element: Element) ?Element { 129 | return element.interface.get_parent_fn(element); 130 | } 131 | 132 | // child rect query 133 | pub fn getChildRect(element: Element, child: Element) ?TransformedRect { 134 | return element.interface.get_child_rect_fn(element, child); 135 | } 136 | 137 | // input handling 138 | pub fn processEvent(element: Element, event: seizer.input.Event) ?Element { 139 | return element.interface.process_event_fn(element, event); 140 | } 141 | 142 | // layouting functions 143 | pub fn getMinSize(element: Element) [2]f64 { 144 | return element.interface.get_min_size_fn(element); 145 | } 146 | 147 | pub fn layout(element: Element, min_size: [2]f64, max_size: [2]f64) [2]f64 { 148 | return element.interface.layout_fn(element, min_size, max_size); 149 | } 150 | 151 | // rendering 152 | pub fn render(element: Element, canvas: Canvas, rect: AABB) void { 153 | return element.interface.render_fn(element, canvas, rect); 154 | } 155 | 156 | // Default functions 157 | 158 | pub fn layoutDefault(element: Element, min_size: [2]f64, max_size: [2]f64) [2]f64 { 159 | _ = min_size; 160 | _ = max_size; 161 | return element.getMinSize(); 162 | } 163 | 164 | pub fn getChildRectDefault(element: Element, child: Element) ?TransformedRect { 165 | _ = element; 166 | _ = child; 167 | return null; 168 | } 169 | 170 | const AABB = seizer.ui.AABB; 171 | const seizer = @import("../seizer.zig"); 172 | const std = @import("std"); 173 | const Canvas = @import("../Canvas.zig"); 174 | -------------------------------------------------------------------------------- /src/ui/Element/Button.zig: -------------------------------------------------------------------------------- 1 | stage: *ui.Stage, 2 | reference_count: usize = 1, 3 | parent: ?Element = null, 4 | 5 | text: std.ArrayListUnmanaged(u8), 6 | 7 | default_style: Style, 8 | hovered_style: Style, 9 | clicked_style: Style, 10 | 11 | on_click: ?ui.Callable(fn (*@This()) void) = null, 12 | 13 | const RECT_COLOR_DEFAULT = seizer.color.fromSRGB(0x30, 0x30, 0x30, 0xff); 14 | const RECT_COLOR_HOVERED = seizer.color.fromSRGB(0x50, 0x50, 0x50, 0xff); 15 | const RECT_COLOR_CLICKED = seizer.color.fromSRGB(0x70, 0x70, 0x70, 0xff); 16 | 17 | const TEXT_COLOR_DEFAULT = seizer.color.argbf32_premultiplied.fromArray(.{ 1, 1, 1, 1 }); 18 | const TEXT_COLOR_HOVERED = seizer.color.argbf32_premultiplied.fromArray(.{ 1, 1, 0.214, 1 }); 19 | const TEXT_COLOR_CLICKED = seizer.color.argbf32_premultiplied.fromArray(.{ 1, 1, 0, 1 }); 20 | 21 | pub const Style = struct { 22 | padding: seizer.geometry.Inset(f64), 23 | text_font: *const seizer.Canvas.Font, 24 | text_scale: f64, 25 | text_color: seizer.color.argbf32_premultiplied, 26 | background_ninepatch: ?seizer.Canvas.NinePatch = null, 27 | background_color: seizer.color.argbf32_premultiplied, 28 | }; 29 | 30 | pub fn create(stage: *ui.Stage, text: []const u8) !*@This() { 31 | const this = try stage.gpa.create(@This()); 32 | 33 | const pad = stage.default_style.text_font.line_height / 2; 34 | const default_style = Style{ 35 | .padding = .{ 36 | .min = .{ pad, pad }, 37 | .max = .{ pad, pad }, 38 | }, 39 | .text_font = stage.default_style.text_font, 40 | .text_scale = stage.default_style.text_scale, 41 | .text_color = TEXT_COLOR_DEFAULT, 42 | .background_ninepatch = null, 43 | .background_color = RECT_COLOR_DEFAULT, 44 | }; 45 | 46 | const hovered_style = Style{ 47 | .padding = .{ 48 | .min = .{ pad, pad }, 49 | .max = .{ pad, pad }, 50 | }, 51 | .text_font = stage.default_style.text_font, 52 | .text_scale = stage.default_style.text_scale, 53 | .text_color = TEXT_COLOR_HOVERED, 54 | .background_ninepatch = null, 55 | .background_color = RECT_COLOR_HOVERED, 56 | }; 57 | 58 | const clicked_style = Style{ 59 | .padding = .{ 60 | .min = .{ pad, pad }, 61 | .max = .{ pad, pad }, 62 | }, 63 | .text_font = stage.default_style.text_font, 64 | .text_scale = stage.default_style.text_scale, 65 | .text_color = TEXT_COLOR_CLICKED, 66 | .background_ninepatch = null, 67 | .background_color = RECT_COLOR_CLICKED, 68 | }; 69 | 70 | var text_owned = std.ArrayListUnmanaged(u8){}; 71 | errdefer text_owned.deinit(stage.gpa); 72 | try text_owned.appendSlice(stage.gpa, text); 73 | 74 | this.* = .{ 75 | .stage = stage, 76 | .text = text_owned, 77 | .default_style = default_style, 78 | .hovered_style = hovered_style, 79 | .clicked_style = clicked_style, 80 | }; 81 | return this; 82 | } 83 | 84 | pub fn element(this: *@This()) Element { 85 | return .{ 86 | .ptr = this, 87 | .interface = &INTERFACE, 88 | }; 89 | } 90 | 91 | const INTERFACE = Element.Interface.getTypeErasedFunctions(@This(), .{ 92 | .acquire_fn = acquire, 93 | .release_fn = release, 94 | .set_parent_fn = setParent, 95 | .get_parent_fn = getParent, 96 | 97 | .process_event_fn = processEvent, 98 | .get_min_size_fn = getMinSize, 99 | .render_fn = render, 100 | }); 101 | 102 | fn acquire(this: *@This()) void { 103 | this.reference_count += 1; 104 | } 105 | 106 | fn release(this: *@This()) void { 107 | this.reference_count -= 1; 108 | if (this.reference_count == 0) { 109 | this.text.deinit(this.stage.gpa); 110 | this.stage.gpa.destroy(this); 111 | } 112 | } 113 | 114 | fn setParent(this: *@This(), new_parent: ?Element) void { 115 | this.parent = new_parent; 116 | } 117 | 118 | fn getParent(this: *@This()) ?Element { 119 | return this.parent; 120 | } 121 | 122 | fn processEvent(this: *@This(), event: seizer.input.Event) ?Element { 123 | switch (event) { 124 | .hover => return this.element(), 125 | .click => |click| { 126 | if (click.button == .left) { 127 | if (click.pressed) { 128 | this.stage.capturePointer(this.element()); 129 | 130 | if (this.on_click) |on_click| { 131 | on_click.call(.{this}); 132 | } 133 | } else { 134 | this.stage.releasePointer(this.element()); 135 | } 136 | return this.element(); 137 | } 138 | }, 139 | .key => |key| { 140 | switch (key.key) { 141 | .unicode => |c| if ((c == ' ' or c == '\n') and key.action == .press) { 142 | this.stage.capturePointer(this.element()); 143 | if (this.on_click) |on_click| { 144 | on_click.call(.{this}); 145 | } 146 | return this.element(); 147 | } else { 148 | this.stage.releasePointer(this.element()); 149 | }, 150 | else => {}, 151 | } 152 | }, 153 | else => {}, 154 | } 155 | 156 | return null; 157 | } 158 | 159 | pub fn getMinSize(this: *@This()) [2]f64 { 160 | const is_pressed = if (this.stage.pointer_capture_element) |pce| pce.ptr == this.element().ptr else false; 161 | const is_hovered = if (this.stage.hovered_element) |hovered| hovered.ptr == this.element().ptr else false; 162 | const style = if (is_pressed) this.clicked_style else if (is_hovered) this.hovered_style else this.default_style; 163 | 164 | const text_size = style.text_font.textSize(this.text.items, style.text_scale); 165 | return .{ 166 | text_size[0] + style.padding.size()[0], 167 | text_size[1] + style.padding.size()[1], 168 | }; 169 | } 170 | 171 | fn render(this: *@This(), canvas: Canvas, rect: AABB) void { 172 | const is_pressed = if (this.stage.pointer_capture_element) |pce| pce.ptr == this.element().ptr else false; 173 | const is_hovered = if (this.stage.hovered_element) |hovered| hovered.ptr == this.element().ptr else false; 174 | const style = if (is_pressed) this.clicked_style else if (is_hovered) this.hovered_style else this.default_style; 175 | 176 | if (style.background_ninepatch) |ninepatch| { 177 | canvas.ninePatch(rect, ninepatch.image, ninepatch.inset, .{ 178 | .color = style.background_color, 179 | }); 180 | } else { 181 | canvas.fillRect(rect, style.background_color, .{}); 182 | } 183 | 184 | _ = canvas.writeText(style.text_font, .{ 185 | rect.min()[0] + style.padding.min[0], 186 | rect.min()[1] + style.padding.min[1], 187 | }, this.text.items, .{ 188 | .scale = style.text_scale, 189 | .color = style.text_color, 190 | }); 191 | } 192 | 193 | const seizer = @import("../../seizer.zig"); 194 | const ui = seizer.ui; 195 | const Element = ui.Element; 196 | const AABB = seizer.geometry.AABB(f64); 197 | const Canvas = seizer.Canvas; 198 | const std = @import("std"); 199 | -------------------------------------------------------------------------------- /src/ui/Element/FlexBox.zig: -------------------------------------------------------------------------------- 1 | stage: *ui.Stage, 2 | reference_count: usize = 1, 3 | parent: ?Element = null, 4 | 5 | children: std.AutoArrayHashMapUnmanaged(Element, AABB) = .{}, 6 | direction: Direction = .column, 7 | justification: Justification = .start, 8 | cross_align: CrossAlign = .start, 9 | 10 | pub const Direction = enum { 11 | row, 12 | column, 13 | }; 14 | 15 | pub const Justification = enum { 16 | start, 17 | center, 18 | space_between, 19 | end, 20 | }; 21 | 22 | pub const CrossAlign = enum { 23 | start, 24 | center, 25 | end, 26 | }; 27 | 28 | pub fn create(stage: *ui.Stage) !*@This() { 29 | const this = try stage.gpa.create(@This()); 30 | this.* = .{ 31 | .stage = stage, 32 | }; 33 | return this; 34 | } 35 | 36 | pub fn appendChild(this: *@This(), child: Element) !void { 37 | try this.children.putNoClobber(this.stage.gpa, child, .{ .min = .{ 0, 0 }, .max = .{ 0, 0 } }); 38 | child.acquire(); 39 | child.setParent(this.element()); 40 | this.stage.needs_layout = true; 41 | } 42 | 43 | pub fn element(this: *@This()) Element { 44 | return .{ 45 | .ptr = this, 46 | .interface = &INTERFACE, 47 | }; 48 | } 49 | 50 | const INTERFACE = Element.Interface.getTypeErasedFunctions(@This(), .{ 51 | .acquire_fn = acquire, 52 | .release_fn = release, 53 | .set_parent_fn = setParent, 54 | .get_parent_fn = getParent, 55 | .get_child_rect_fn = element_getChildRect, 56 | 57 | .process_event_fn = processEvent, 58 | .get_min_size_fn = getMinSize, 59 | .layout_fn = layout, 60 | .render_fn = render, 61 | }); 62 | 63 | fn acquire(this: *@This()) void { 64 | this.reference_count += 1; 65 | } 66 | 67 | fn release(this: *@This()) void { 68 | this.reference_count -= 1; 69 | if (this.reference_count == 0) { 70 | for (this.children.keys()) |child| { 71 | child.release(); 72 | } 73 | this.children.deinit(this.stage.gpa); 74 | this.stage.gpa.destroy(this); 75 | } 76 | } 77 | 78 | fn setParent(this: *@This(), new_parent: ?Element) void { 79 | this.parent = new_parent; 80 | } 81 | 82 | fn getParent(this: *@This()) ?Element { 83 | return this.parent; 84 | } 85 | 86 | fn element_getChildRect(this: *@This(), child: Element) ?Element.TransformedRect { 87 | const child_rect = this.children.get(child) orelse return null; 88 | 89 | if (this.parent) |parent| { 90 | if (parent.getChildRect(this.element())) |rect_transform| { 91 | return .{ 92 | .rect = child_rect.translate(rect_transform.rect.min), 93 | .transform = rect_transform.transform, 94 | }; 95 | } 96 | } 97 | return .{ 98 | .rect = child_rect, 99 | .transform = seizer.geometry.mat4.identity(f64), 100 | }; 101 | } 102 | 103 | fn processEvent(this: *@This(), event: seizer.input.Event) ?Element { 104 | switch (event) { 105 | .hover => |hover| { 106 | for (this.children.keys(), this.children.values()) |child_element, child_rect| { 107 | if (child_rect.contains(hover.pos)) { 108 | const child_event = event.transform(seizer.geometry.mat4.translate(f64, .{ 109 | -child_rect.min[0], 110 | -child_rect.min[1], 111 | 0, 112 | })); 113 | 114 | if (child_element.processEvent(child_event)) |hovered| { 115 | return hovered; 116 | } 117 | } 118 | } 119 | }, 120 | .click => |click| { 121 | for (this.children.keys(), this.children.values()) |child_element, child_rect| { 122 | if (child_rect.contains(click.pos)) { 123 | const child_event = event.transform(seizer.geometry.mat4.translate(f64, .{ 124 | -child_rect.min[0], 125 | -child_rect.min[1], 126 | 0, 127 | })); 128 | 129 | if (child_element.processEvent(child_event)) |clicked| { 130 | return clicked; 131 | } 132 | } 133 | } 134 | }, 135 | else => {}, 136 | } 137 | return null; 138 | } 139 | 140 | pub fn getMinSize(this: *@This()) [2]f64 { 141 | const main_axis: usize = switch (this.direction) { 142 | .row => 0, 143 | .column => 1, 144 | }; 145 | const cross_axis: usize = switch (this.direction) { 146 | .row => 1, 147 | .column => 0, 148 | }; 149 | 150 | var min_size = [2]f64{ 0, 0 }; 151 | for (this.children.keys()) |child_element| { 152 | const child_min = child_element.getMinSize(); 153 | 154 | min_size[main_axis] += child_min[main_axis]; 155 | min_size[cross_axis] = @max(min_size[cross_axis], child_min[cross_axis]); 156 | } 157 | return min_size; 158 | } 159 | 160 | pub fn layout(this: *@This(), min_size: [2]f64, max_size: [2]f64) [2]f64 { 161 | const content_min_size = this.getMinSize(); 162 | 163 | const main_axis: usize = switch (this.direction) { 164 | .row => 0, 165 | .column => 1, 166 | }; 167 | const cross_axis: usize = switch (this.direction) { 168 | .row => 1, 169 | .column => 0, 170 | }; 171 | 172 | // Do a first pass where we divide the space equally between the children 173 | const main_equal_space_per_child = max_size[main_axis] / @as(f64, @floatFromInt(this.children.count())); 174 | 175 | var main_space_used: f64 = 0; 176 | var cross_min_width: f64 = min_size[cross_axis]; 177 | var children_requesting_space: f64 = 0; 178 | for (this.children.keys(), this.children.values()) |child_element, *child_rect| { 179 | var constraint_min: [2]f64 = undefined; 180 | var constraint_max: [2]f64 = undefined; 181 | 182 | constraint_min[main_axis] = 0; 183 | constraint_min[cross_axis] = cross_min_width; 184 | 185 | constraint_max[main_axis] = main_equal_space_per_child; 186 | constraint_max[cross_axis] = max_size[cross_axis]; 187 | 188 | const child_size = child_element.layout(constraint_min, constraint_max); 189 | child_rect.* = .{ .min = .{ 0, 0 }, .max = child_size }; 190 | if (child_size[main_axis] >= main_equal_space_per_child) { 191 | children_requesting_space += 1; 192 | } 193 | 194 | main_space_used += child_size[main_axis]; 195 | cross_min_width = @max(cross_min_width, child_size[cross_axis]); 196 | } 197 | 198 | // Do a second pass where we allocate more space to any children that used their full amount of space 199 | const MAX_ITERATIONS = 10; 200 | var iterations: usize = 0; 201 | while (main_space_used < max_size[main_axis] and children_requesting_space >= 0 and iterations < MAX_ITERATIONS) : (iterations += 1) { 202 | const main_space_per_grow = (max_size[main_axis] - main_space_used) / children_requesting_space; 203 | 204 | main_space_used = 0; 205 | cross_min_width = min_size[cross_axis]; 206 | children_requesting_space = 0; 207 | for (this.children.keys(), this.children.values()) |child_element, *child_rect| { 208 | var constraint_min: [2]f64 = undefined; 209 | var constraint_max: [2]f64 = undefined; 210 | 211 | constraint_min[main_axis] = child_rect.max[main_axis]; 212 | constraint_min[cross_axis] = child_rect.max[cross_axis]; 213 | 214 | if (child_rect.max[main_axis] >= main_equal_space_per_child) { 215 | constraint_max[main_axis] = child_rect.max[main_axis] + main_space_per_grow; 216 | } else { 217 | constraint_max[main_axis] = child_rect.max[main_axis]; 218 | } 219 | constraint_max[cross_axis] = max_size[cross_axis]; 220 | 221 | child_rect.max = child_element.layout(constraint_min, constraint_max); 222 | if (child_rect.max[main_axis] >= main_equal_space_per_child) { 223 | children_requesting_space += 1; 224 | } 225 | 226 | main_space_used += child_rect.max[main_axis]; 227 | cross_min_width = @max(cross_min_width, child_rect.max[cross_axis]); 228 | } 229 | } 230 | 231 | main_space_used = @max(content_min_size[main_axis], main_space_used); 232 | 233 | const num_items: f64 = @floatFromInt(this.children.count()); 234 | 235 | const space_before: f64 = switch (this.justification) { 236 | .start, .space_between => 0, 237 | .center => (max_size[main_axis] - main_space_used) / 2, 238 | .end => max_size[main_axis] - main_space_used, 239 | }; 240 | const space_between: f64 = switch (this.justification) { 241 | .start, .center, .end => 0, 242 | .space_between => (max_size[main_axis] - main_space_used) / @max(num_items - 1, 1), 243 | }; 244 | const space_after: f64 = switch (this.justification) { 245 | .start => max_size[main_axis] - main_space_used, 246 | .center => (max_size[main_axis] - main_space_used) / 2, 247 | .space_between, .end => 0, 248 | }; 249 | _ = space_after; 250 | 251 | const cross_axis_size = @min(max_size[cross_axis], cross_min_width); 252 | 253 | var main_pos: f64 = space_before; 254 | 255 | for (this.children.values()) |*child_rect| { 256 | var child_translate: [2]f64 = undefined; 257 | child_translate[main_axis] = main_pos; 258 | child_translate[cross_axis] = switch (this.cross_align) { 259 | .start => 0, 260 | .center => (cross_axis_size - child_rect.size()[cross_axis]) / 2, 261 | .end => cross_axis_size - child_rect.size()[cross_axis], 262 | }; 263 | 264 | child_rect.* = child_rect.translate(child_translate); 265 | 266 | main_pos = child_rect.max[main_axis] + space_between; 267 | } 268 | 269 | var bounds = [2]f64{ 0, 0 }; 270 | bounds[main_axis] = max_size[main_axis]; 271 | bounds[cross_axis] = cross_axis_size; 272 | return bounds; 273 | } 274 | 275 | fn render(this: *@This(), canvas: Canvas, rect: AABB) void { 276 | for (this.children.keys(), this.children.values()) |child_element, child_rect| { 277 | child_element.render(canvas, child_rect.translate(rect.min)); 278 | } 279 | } 280 | 281 | const seizer = @import("../../seizer.zig"); 282 | const ui = seizer.ui; 283 | const Element = ui.Element; 284 | const AABB = seizer.geometry.AABB(f64); 285 | const Canvas = seizer.Canvas; 286 | const std = @import("std"); 287 | -------------------------------------------------------------------------------- /src/ui/Element/Frame.zig: -------------------------------------------------------------------------------- 1 | stage: *ui.Stage, 2 | reference_count: usize = 1, 3 | parent: ?Element = null, 4 | style: ui.Style, 5 | 6 | child: ?Element = null, 7 | child_rect: AABB = AABB.init(.{ 0, 0 }, .{ 0, 0 }), 8 | 9 | pub fn create(stage: *ui.Stage) !*@This() { 10 | const this = try stage.gpa.create(@This()); 11 | this.* = .{ 12 | .stage = stage, 13 | .style = stage.default_style, 14 | }; 15 | return this; 16 | } 17 | 18 | pub fn setChild(this: *@This(), new_child_opt: ?Element) void { 19 | if (new_child_opt) |new_child| { 20 | new_child.acquire(); 21 | } 22 | if (this.child) |r| { 23 | r.release(); 24 | } 25 | if (new_child_opt) |new_child| { 26 | new_child.setParent(this.element()); 27 | } 28 | this.child = new_child_opt; 29 | } 30 | 31 | pub fn element(this: *@This()) Element { 32 | return .{ 33 | .ptr = this, 34 | .interface = &INTERFACE, 35 | }; 36 | } 37 | 38 | const INTERFACE = Element.Interface.getTypeErasedFunctions(@This(), .{ 39 | .acquire_fn = acquire, 40 | .release_fn = release, 41 | .set_parent_fn = setParent, 42 | .get_parent_fn = getParent, 43 | .get_child_rect_fn = element_getChildRect, 44 | 45 | .process_event_fn = processEvent, 46 | .get_min_size_fn = getMinSize, 47 | .layout_fn = layout, 48 | .render_fn = render, 49 | }); 50 | 51 | fn acquire(this: *@This()) void { 52 | this.reference_count += 1; 53 | } 54 | 55 | fn release(this: *@This()) void { 56 | this.reference_count -= 1; 57 | if (this.reference_count == 0) { 58 | if (this.child) |child| { 59 | child.release(); 60 | } 61 | this.stage.gpa.destroy(this); 62 | } 63 | } 64 | 65 | fn setParent(this: *@This(), new_parent: ?Element) void { 66 | this.parent = new_parent; 67 | } 68 | 69 | fn getParent(this: *@This()) ?Element { 70 | return this.parent; 71 | } 72 | 73 | fn element_getChildRect(this: *@This(), child: Element) ?Element.TransformedRect { 74 | if (!std.meta.eql(this.child, child)) return null; 75 | 76 | if (this.parent) |parent| { 77 | if (parent.getChildRect(this.element())) |parent_rect_transform| { 78 | return .{ 79 | .rect = AABB.init( 80 | .{ 81 | parent_rect_transform.rect.min()[0] + this.child_rect.min()[0], 82 | parent_rect_transform.rect.min()[1] + this.child_rect.min()[1], 83 | }, 84 | .{ 85 | parent_rect_transform.rect.min()[0] + this.child_rect.max()[0], 86 | parent_rect_transform.rect.min()[1] + this.child_rect.max()[1], 87 | }, 88 | ), 89 | .transform = parent_rect_transform.transform, 90 | }; 91 | } 92 | } 93 | return .{ 94 | .rect = this.child_rect, 95 | .transform = seizer.geometry.mat4.identity(f64), 96 | }; 97 | } 98 | 99 | fn processEvent(this: *@This(), event: seizer.input.Event) ?Element { 100 | if (this.child == null) return null; 101 | 102 | const child_event = event.transform(seizer.geometry.mat4.translate(f64, .{ 103 | -this.child_rect.min()[0], 104 | -this.child_rect.min()[1], 105 | 0, 106 | })); 107 | 108 | switch (event) { 109 | .hover => |hover| { 110 | if (this.child_rect.contains(hover.pos)) { 111 | return this.child.?.processEvent(child_event); 112 | } 113 | }, 114 | .click => |click| { 115 | if (this.child_rect.contains(click.pos)) { 116 | return this.child.?.processEvent(child_event); 117 | } 118 | }, 119 | else => {}, 120 | } 121 | return null; 122 | } 123 | 124 | fn getMinSize(this: *@This()) [2]f64 { 125 | const padding_size = this.style.padding.size(); 126 | 127 | if (this.child) |child| { 128 | const child_size = child.getMinSize(); 129 | return .{ 130 | child_size[0] + padding_size[0], 131 | child_size[1] + padding_size[1], 132 | }; 133 | } 134 | 135 | return padding_size; 136 | } 137 | 138 | pub fn layout(this: *@This(), min_size: [2]f64, max_size: [2]f64) [2]f64 { 139 | const padding_size = this.style.padding.size(); 140 | 141 | if (this.child) |child| { 142 | const child_size = child.layout(min_size, .{ 143 | max_size[0] - padding_size[0], 144 | max_size[1] - padding_size[1], 145 | }); 146 | this.child_rect = AABB.init( 147 | this.style.padding.min, 148 | .{ 149 | this.style.padding.min[0] + child_size[0], 150 | this.style.padding.min[1] + child_size[1], 151 | }, 152 | ); 153 | return .{ 154 | child_size[0] + padding_size[0], 155 | child_size[1] + padding_size[1], 156 | }; 157 | } 158 | 159 | return padding_size; 160 | } 161 | 162 | fn render(this: *@This(), canvas: Canvas, rect: AABB) void { 163 | canvas.ninePatch(rect, this.style.background_image.image, this.style.background_image.inset, .{ 164 | .color = this.style.background_color, 165 | }); 166 | 167 | if (this.child) |child| { 168 | child.render(canvas, this.child_rect.translate(rect.min)); 169 | } 170 | } 171 | 172 | const seizer = @import("../../seizer.zig"); 173 | const ui = seizer.ui; 174 | const Element = ui.Element; 175 | const AABB = seizer.geometry.AABB(f64); 176 | const Canvas = seizer.Canvas; 177 | const std = @import("std"); 178 | -------------------------------------------------------------------------------- /src/ui/Element/Image.zig: -------------------------------------------------------------------------------- 1 | stage: *ui.Stage, 2 | reference_count: usize = 1, 3 | parent: ?Element = null, 4 | 5 | image: seizer.image.Slice(seizer.color.argbf32_premultiplied), 6 | 7 | pub fn create(stage: *ui.Stage, image: seizer.image.Slice(seizer.color.argbf32_premultiplied)) !*@This() { 8 | const this = try stage.gpa.create(@This()); 9 | errdefer stage.gpa.destroy(this); 10 | 11 | this.* = .{ 12 | .stage = stage, 13 | .image = image, 14 | }; 15 | return this; 16 | } 17 | 18 | pub fn element(this: *@This()) Element { 19 | return .{ 20 | .ptr = this, 21 | .interface = &INTERFACE, 22 | }; 23 | } 24 | 25 | const INTERFACE = Element.Interface.getTypeErasedFunctions(@This(), .{ 26 | .acquire_fn = acquire, 27 | .release_fn = release, 28 | .set_parent_fn = setParent, 29 | .get_parent_fn = getParent, 30 | 31 | .process_event_fn = processEvent, 32 | .get_min_size_fn = getMinSize, 33 | .render_fn = render, 34 | }); 35 | 36 | fn acquire(this: *@This()) void { 37 | this.reference_count += 1; 38 | } 39 | 40 | fn release(this: *@This()) void { 41 | this.reference_count -= 1; 42 | if (this.reference_count == 0) { 43 | this.stage.gpa.destroy(this); 44 | } 45 | } 46 | 47 | fn setParent(this: *@This(), new_parent: ?Element) void { 48 | this.parent = new_parent; 49 | } 50 | 51 | fn getParent(this: *@This()) ?Element { 52 | return this.parent; 53 | } 54 | 55 | fn processEvent(this: *@This(), event: seizer.input.Event) ?Element { 56 | _ = this; 57 | _ = event; 58 | return null; 59 | } 60 | 61 | fn getMinSize(this: *@This()) [2]f64 { 62 | return .{ 63 | @floatFromInt(this.image.size[0]), 64 | @floatFromInt(this.image.size[1]), 65 | }; 66 | } 67 | 68 | fn render(this: *@This(), canvas: Canvas, rect: AABB) void { 69 | canvas.textureRect(rect, this.image, .{}); 70 | } 71 | 72 | const seizer = @import("../../seizer.zig"); 73 | const ui = seizer.ui; 74 | const Element = ui.Element; 75 | const AABB = seizer.geometry.AABB(f64); 76 | const Canvas = seizer.Canvas; 77 | const std = @import("std"); 78 | -------------------------------------------------------------------------------- /src/ui/Element/Label.zig: -------------------------------------------------------------------------------- 1 | stage: *ui.Stage, 2 | reference_count: usize = 1, 3 | parent: ?Element = null, 4 | 5 | text: std.ArrayListUnmanaged(u8), 6 | 7 | style: ui.Style, 8 | 9 | pub fn create(stage: *ui.Stage, text: []const u8) !*@This() { 10 | const this = try stage.gpa.create(@This()); 11 | errdefer stage.gpa.destroy(this); 12 | 13 | var text_owned = std.ArrayListUnmanaged(u8){}; 14 | errdefer text_owned.deinit(stage.gpa); 15 | try text_owned.appendSlice(stage.gpa, text); 16 | 17 | this.* = .{ 18 | .stage = stage, 19 | 20 | .text = text_owned, 21 | .style = stage.default_style, 22 | }; 23 | return this; 24 | } 25 | 26 | pub fn element(this: *@This()) Element { 27 | return .{ 28 | .ptr = this, 29 | .interface = &INTERFACE, 30 | }; 31 | } 32 | 33 | const INTERFACE = Element.Interface.getTypeErasedFunctions(@This(), .{ 34 | .acquire_fn = acquire, 35 | .release_fn = release, 36 | .set_parent_fn = setParent, 37 | .get_parent_fn = getParent, 38 | 39 | .process_event_fn = processEvent, 40 | .get_min_size_fn = getMinSize, 41 | .render_fn = render, 42 | }); 43 | 44 | fn acquire(this: *@This()) void { 45 | this.reference_count += 1; 46 | } 47 | 48 | fn release(this: *@This()) void { 49 | this.reference_count -= 1; 50 | if (this.reference_count == 0) { 51 | this.text.deinit(this.stage.gpa); 52 | this.stage.gpa.destroy(this); 53 | } 54 | } 55 | 56 | fn setParent(this: *@This(), new_parent: ?Element) void { 57 | this.parent = new_parent; 58 | } 59 | 60 | fn getParent(this: *@This()) ?Element { 61 | return this.parent; 62 | } 63 | 64 | fn processEvent(this: *@This(), event: seizer.input.Event) ?Element { 65 | _ = this; 66 | _ = event; 67 | return null; 68 | } 69 | 70 | fn getMinSize(this: *@This()) [2]f64 { 71 | const text_size = this.style.text_font.textSize(this.text.items, this.style.text_scale); 72 | return .{ 73 | text_size[0] + this.style.padding.size()[0], 74 | text_size[1] + this.style.padding.size()[1], 75 | }; 76 | } 77 | 78 | fn render(this: *@This(), canvas: Canvas, rect: AABB) void { 79 | canvas.ninePatch(rect, this.style.background_image.image, this.style.background_image.inset, .{ 80 | .color = this.style.background_color, 81 | }); 82 | 83 | _ = canvas.writeText(this.style.text_font, .{ 84 | rect.min[0] + this.style.padding.min[0], 85 | rect.min[1] + this.style.padding.min[1], 86 | }, this.text.items, .{ 87 | .scale = this.style.text_scale, 88 | .color = this.style.text_color, 89 | }); 90 | } 91 | 92 | const seizer = @import("../../seizer.zig"); 93 | const ui = seizer.ui; 94 | const Element = ui.Element; 95 | const AABB = seizer.geometry.AABB(f64); 96 | const Canvas = seizer.Canvas; 97 | const std = @import("std"); 98 | -------------------------------------------------------------------------------- /src/ui/Element/PanZoom.zig: -------------------------------------------------------------------------------- 1 | stage: *ui.Stage, 2 | reference_count: usize = 1, 3 | parent: ?Element = null, 4 | 5 | /// The size of the Canvas before zooming and panning 6 | size: [2]f64 = .{ 1, 1 }, 7 | /// The size of the Canvas after zooming and panning 8 | output_size: [2]f64 = .{ 1, 1 }, 9 | 10 | /// Value is log-scale, meaning that to get the actual scaling you need to do @exp(zoom) 11 | zoom: f64 = 0, 12 | pan: [2]f64 = .{ 0, 0 }, 13 | 14 | pan_start: ?[2]f64 = null, 15 | cursor_pos: [2]f64 = .{ 0, 0 }, 16 | 17 | bg_color: seizer.color.argbf32_premultiplied = seizer.color.fromSRGB(0x40, 0x40, 0x40, 0xFF), 18 | 19 | children: std.AutoArrayHashMapUnmanaged(Element, void) = .{}, 20 | systems: std.AutoArrayHashMapUnmanaged(?*anyopaque, System) = .{}, 21 | 22 | const PanZoom = @This(); 23 | 24 | const System = struct { 25 | userdata: ?*anyopaque, 26 | render_fn: RenderFn, 27 | 28 | const RenderFn = *const fn (userdata: ?*anyopaque, pan_zoom: *PanZoom, canvas: Canvas) void; 29 | }; 30 | 31 | pub fn create(stage: *ui.Stage) !*@This() { 32 | const this = try stage.gpa.create(@This()); 33 | this.* = .{ 34 | .stage = stage, 35 | }; 36 | return this; 37 | } 38 | 39 | pub fn appendChild(this: *@This(), child: Element) !void { 40 | try this.children.put(this.stage.gpa, child, {}); 41 | child.acquire(); 42 | child.setParent(this.element()); 43 | } 44 | 45 | pub fn element(this: *@This()) Element { 46 | return .{ 47 | .ptr = this, 48 | .interface = &INTERFACE, 49 | }; 50 | } 51 | 52 | const INTERFACE = Element.Interface.getTypeErasedFunctions(@This(), .{ 53 | .acquire_fn = acquire, 54 | .release_fn = release, 55 | .set_parent_fn = setParent, 56 | .get_parent_fn = getParent, 57 | .get_child_rect_fn = element_getChildRect, 58 | 59 | .process_event_fn = processEvent, 60 | .get_min_size_fn = getMinSize, 61 | .layout_fn = layout, 62 | .render_fn = render, 63 | }); 64 | 65 | fn acquire(this: *@This()) void { 66 | this.reference_count += 1; 67 | } 68 | 69 | fn release(this: *@This()) void { 70 | this.reference_count -= 1; 71 | if (this.reference_count == 0) { 72 | for (this.children.keys()) |child| { 73 | child.release(); 74 | } 75 | this.children.deinit(this.stage.gpa); 76 | this.systems.deinit(this.stage.gpa); 77 | this.stage.gpa.destroy(this); 78 | } 79 | } 80 | 81 | fn setParent(this: *@This(), new_parent: ?Element) void { 82 | this.parent = new_parent; 83 | } 84 | 85 | fn getParent(this: *@This()) ?Element { 86 | return this.parent; 87 | } 88 | 89 | fn processEvent(this: *@This(), event: seizer.input.Event) ?Element { 90 | const inverse = panZoomInverse( 91 | this.output_size, 92 | this.size, 93 | this.zoom, 94 | this.pan, 95 | ); 96 | const transformed_event = event.transform(inverse); 97 | 98 | if (this.stage.pointer_capture_element == null or this.stage.pointer_capture_element.?.ptr != this.element().ptr) { 99 | for (this.children.keys()) |child| { 100 | if (child.processEvent(transformed_event)) |element_that_handled_event| { 101 | return element_that_handled_event; 102 | } 103 | } 104 | } 105 | 106 | switch (event) { 107 | .hover => |hover| { 108 | this.cursor_pos = hover.pos; 109 | if (this.pan_start) |pan_start| { 110 | this.stage.cursor_shape = .move; 111 | const pan_start_inverse = panZoomInverse( 112 | this.output_size, 113 | this.size, 114 | this.zoom, 115 | pan_start, 116 | ); 117 | 118 | this.pan = seizer.geometry.mat4.mulVec(f64, pan_start_inverse, .{ 119 | hover.pos[0], 120 | hover.pos[1], 121 | 0, 122 | 1, 123 | })[0..2].*; 124 | return this.element(); 125 | } 126 | }, 127 | .click => |click| { 128 | if (click.button == .middle) { 129 | if (!click.pressed) { 130 | this.stage.releasePointer(this.element()); 131 | this.pan_start = null; 132 | return this.element(); 133 | } 134 | 135 | this.stage.capturePointer(this.element()); 136 | this.pan_start = transformed_event.click.pos; 137 | return this.element(); 138 | } 139 | }, 140 | .scroll => |scroll| { 141 | const new_zoom = this.zoom - scroll.offset[1] / 128; 142 | 143 | const new_inverse = panZoomInverse( 144 | this.output_size, 145 | this.size, 146 | new_zoom, 147 | this.pan, 148 | ); 149 | 150 | const cursor_before = seizer.geometry.mat4.mulVec(f64, inverse, this.cursor_pos ++ [2]f64{ 0, 1 }); 151 | const cursor_after = seizer.geometry.mat4.mulVec(f64, new_inverse, this.cursor_pos ++ [2]f64{ 0, 1 }); 152 | 153 | this.zoom = new_zoom; 154 | this.pan = .{ 155 | this.pan[0] + (cursor_after[0] - cursor_before[0]), 156 | this.pan[1] + (cursor_after[1] - cursor_before[1]), 157 | }; 158 | }, 159 | else => {}, 160 | } 161 | 162 | return this.element(); 163 | } 164 | 165 | fn getMinSize(this: *@This()) [2]f64 { 166 | _ = this; 167 | return .{ 1, 1 }; 168 | } 169 | 170 | pub fn layout(this: *@This(), min_size: [2]f64, max_size: [2]f64) [2]f64 { 171 | _ = min_size; 172 | 173 | this.size = .{ 0, 0 }; 174 | for (this.children.keys()) |child| { 175 | const child_size = child.getMinSize(); 176 | this.size = .{ 177 | @max(this.size[0], child_size[0]), 178 | @max(this.size[1], child_size[1]), 179 | }; 180 | } 181 | 182 | for (this.children.keys()) |child| { 183 | _ = child.layout( 184 | .{ 0, 0 }, 185 | this.size, 186 | ); 187 | } 188 | 189 | this.output_size = max_size; 190 | return this.output_size; 191 | } 192 | 193 | fn render(this: *@This(), parent_canvas: Canvas, rect: AABB) void { 194 | parent_canvas.fillRect(rect, this.bg_color, .{}); 195 | 196 | var transformed_canvas = parent_canvas.transformed(.{ 197 | .clip = rect, 198 | .transform = seizer.geometry.mat4.mul( 199 | f64, 200 | panZoomTransform( 201 | rect.size(), 202 | this.size, 203 | this.zoom, 204 | this.pan, 205 | ), 206 | seizer.geometry.mat4.translate(f64, .{ rect.min[0], rect.min[1], 0 }), 207 | ), 208 | }); 209 | const canvas = transformed_canvas.canvas(); 210 | 211 | for (this.children.keys()) |child| { 212 | child.render(canvas, .{ .min = .{ 0, 0 }, .max = this.size }); 213 | } 214 | for (this.systems.values()) |system| { 215 | system.render_fn(system.userdata, this, canvas); 216 | } 217 | } 218 | 219 | fn element_getChildRect(this: *@This(), child: Element) ?Element.TransformedRect { 220 | _ = this.children.get(child) orelse return null; 221 | 222 | const transform = panZoomInverse( 223 | this.output_size, 224 | this.size, 225 | this.zoom, 226 | this.pan, 227 | ); 228 | if (this.parent) |parent| { 229 | if (parent.getChildRect(this.element())) |rect_transform| { 230 | return .{ 231 | .rect = .{ .min = .{ 0, 0 }, .max = this.size }, 232 | .transform = seizer.geometry.mat4.mul(f64, transform, rect_transform.transformWithTranslation()), 233 | }; 234 | } 235 | } 236 | return .{ 237 | .rect = .{ .min = .{ 0, 0 }, .max = this.size }, 238 | .transform = transform, 239 | }; 240 | } 241 | 242 | pub fn panZoomTransform(out_size: [2]f64, child_size: [2]f64, zoom_ln: f64, pan: [2]f64) [4][4]f64 { 243 | const zoom = @exp(zoom_ln); 244 | 245 | const child_aspect = child_size[0] / child_size[1]; 246 | 247 | const out_aspect = out_size[0] / out_size[1]; 248 | 249 | const aspect = child_aspect / out_aspect; 250 | 251 | const size = if (aspect >= 1) 252 | [2]f64{ 253 | out_size[0], 254 | out_size[1] / aspect, 255 | } 256 | else 257 | [2]f64{ 258 | out_size[0] * aspect, 259 | out_size[1], 260 | }; 261 | 262 | return seizer.geometry.mat4.mulAll( 263 | f64, 264 | &.{ 265 | seizer.geometry.mat4.translate(f64, .{ 266 | (out_size[0] - size[0]) / 2.0, 267 | (out_size[1] - size[1]) / 2.0, 268 | 0, 269 | }), 270 | seizer.geometry.mat4.scale(f64, .{ 271 | size[0] / child_size[0], 272 | size[1] / child_size[1], 273 | 1, 274 | }), 275 | seizer.geometry.mat4.translate(f64, .{ 276 | child_size[0] / 2.0, 277 | child_size[1] / 2.0, 278 | 0, 279 | }), 280 | seizer.geometry.mat4.scale(f64, .{ 281 | zoom, 282 | zoom, 283 | 1, 284 | }), 285 | seizer.geometry.mat4.translate(f64, .{ 286 | pan[0], 287 | pan[1], 288 | 0, 289 | }), 290 | seizer.geometry.mat4.translate(f64, .{ 291 | -child_size[0] / 2.0, 292 | -child_size[1] / 2.0, 293 | 0, 294 | }), 295 | }, 296 | ); 297 | } 298 | 299 | pub fn panZoomInverse(out_size: [2]f64, child_size: [2]f64, zoom_ln: f64, pan: [2]f64) [4][4]f64 { 300 | const zoom = @exp(zoom_ln); 301 | 302 | const child_aspect = child_size[0] / child_size[1]; 303 | 304 | const out_aspect = out_size[0] / out_size[1]; 305 | 306 | const aspect = child_aspect / out_aspect; 307 | 308 | const size = if (aspect >= 1) 309 | [2]f64{ 310 | out_size[0], 311 | out_size[1] / aspect, 312 | } 313 | else 314 | [2]f64{ 315 | out_size[0] * aspect, 316 | out_size[1], 317 | }; 318 | 319 | return seizer.geometry.mat4.mulAll(f64, &.{ 320 | seizer.geometry.mat4.translate(f64, .{ 321 | child_size[0] / 2.0, 322 | child_size[1] / 2.0, 323 | 0, 324 | }), 325 | seizer.geometry.mat4.translate(f64, .{ 326 | -pan[0], 327 | -pan[1], 328 | 0, 329 | }), 330 | seizer.geometry.mat4.scale(f64, .{ 331 | 1.0 / zoom, 332 | 1.0 / zoom, 333 | 1, 334 | }), 335 | seizer.geometry.mat4.translate(f64, .{ 336 | -child_size[0] / 2.0, 337 | -child_size[1] / 2.0, 338 | 0, 339 | }), 340 | seizer.geometry.mat4.scale(f64, .{ 341 | 1.0 / (size[0] / child_size[0]), 342 | 1.0 / (size[1] / child_size[1]), 343 | 1, 344 | }), 345 | seizer.geometry.mat4.translate(f64, .{ 346 | -(out_size[0] - size[0]) / 2.0, 347 | -(out_size[1] - size[1]) / 2.0, 348 | 0, 349 | }), 350 | }); 351 | } 352 | 353 | const seizer = @import("../../seizer.zig"); 354 | const ui = seizer.ui; 355 | const Element = ui.Element; 356 | const AABB = seizer.ui.AABB; 357 | const Canvas = seizer.Canvas; 358 | const std = @import("std"); 359 | --------------------------------------------------------------------------------