|
| 1 | +--- |
| 2 | +title: "Capy -- 2024 Recap" |
| 3 | +authors: ["zenith391"] |
| 4 | +date: 2025-01-01T10:35:14+01:00 |
| 5 | +tags: ["capy", "recap"] |
| 6 | +--- |
| 7 | + |
| 8 | +**Reminder: Capy is NOT ready for use in production as I'm still making breaking changes** |
| 9 | + |
| 10 | +[Capy](https://github.com/capy-ui/capy) is a **GUI framework** for Zig made with performance, |
| 11 | +cross-compilation and native widgets in mind. The goal is to be able to seamlessly cross-compile |
| 12 | +from any platform to any other one. This includes Windows, Linux, WebAssembly, and soon will include |
| 13 | +macOS, Android and iOS. |
| 14 | + |
| 15 | +## [`AnimationController`](https://capy-ui.org/api-reference/#capy.AnimationController) |
| 16 | + |
| 17 | +The animation system for [Atom](https://capy-ui.org/api-reference/#capy.data.Atom) has been remade. |
| 18 | +It now requires an [`AnimationController`](https://capy-ui.org/api-reference/#capy.AnimationController) |
| 19 | +which is used to periodically update the Atom. |
| 20 | + |
| 21 | +Previously, this was implemented using global variables, but using global variables had two |
| 22 | +problems: it couldn't synchronise the animation with the window's refresh rate, and due to that, |
| 23 | +animations worked at the maximum speed the CPU could allow, which meant Capy maximized one CPU core |
| 24 | +for animations ! |
| 25 | + |
| 26 | +Now, animations don't hog CPU cores anymore while also looking more smooth. The tradeoff means |
| 27 | +that the new syntax for animating atoms is now: |
| 28 | +```zig |
| 29 | +atom.animate( |
| 30 | + animation_controller, |
| 31 | + capy.Easings.InOut, // easing |
| 32 | + capy.Colors.red, // target value |
| 33 | + 1000 // duration in milliseconds |
| 34 | +); |
| 35 | +``` |
| 36 | + |
| 37 | +## API Reference (!) |
| 38 | + |
| 39 | +Capy now has an [API reference](/api-reference) which is auto-generated by Zig itself. This piece |
| 40 | +of documentation has been long overdue, but I couldn't add the API reference as, at first, I |
| 41 | +encountered compiler errors, which have now been fixed thanks to the latest compiler updates. And |
| 42 | +then, the API reference simply wouldn't use the `capy` module as its main page, which meant that |
| 43 | +all the functions and structs couldn't be discovered on the API reference, which kind of defeated |
| 44 | +its purpose. |
| 45 | + |
| 46 | +The fix to that ended up being renaming `src/main.zig` to `src/capy.zig`, as the new documentation |
| 47 | +generator in Zig silently expects this naming convention. |
| 48 | + |
| 49 | +<!-- insert image --> |
| 50 | + |
| 51 | +## Improvements to [`Label`](https://capy-ui.org/api-reference/#capy.components.Label.Label) |
| 52 | + |
| 53 | +The component can now use different fonts in different sizes using the new [`TextLayout`](/api-reference/#capy.misc.TextLayout) |
| 54 | +struct. This allows for more complex typography for Capy applications. |
| 55 | + |
| 56 | +<!-- insert image --> |
| 57 | + |
| 58 | +## Userdata |
| 59 | +See the `many-counters.zig` example. |
| 60 | +This system allows for each component to hold their state or their data. |
| 61 | +In the following example, it's used to hold the count for each counter widget: |
| 62 | +```zig |
| 63 | +//! This is a test for Capy's ease of use when using lists of many items |
| 64 | +const capy = @import("capy"); |
| 65 | +const std = @import("std"); |
| 66 | +pub usingnamespace capy.cross_platform; |
| 67 | +
|
| 68 | +const CounterState = struct { |
| 69 | + count: capy.Atom(i64) = capy.Atom(i64).of(0), |
| 70 | +}; |
| 71 | +
|
| 72 | +fn counter() anyerror!*capy.Alignment { |
| 73 | + var state1 = try capy.internal.lasting_allocator.create(CounterState); |
| 74 | + state1.* = .{}; |
| 75 | + const format = try capy.FormattedAtom(capy.internal.lasting_allocator, "{d}", .{&state1.count}); |
| 76 | +
|
| 77 | + return capy.alignment( |
| 78 | + .{}, |
| 79 | + (try capy.row(.{}, .{ |
| 80 | + capy.button(.{ |
| 81 | + .label = "-", |
| 82 | + .onclick = (struct { |
| 83 | + fn sub(pointer: *anyopaque) !void { |
| 84 | + const button: *capy.Button = @ptrCast(@alignCast(pointer)); |
| 85 | + const state: *CounterState = button.getUserdata(CounterState).?; |
| 86 | + state.count.set(state.count.get() - 1); |
| 87 | + } |
| 88 | + }).sub, |
| 89 | + }), |
| 90 | + capy.textField(.{ .text = "0", .readOnly = true }) |
| 91 | + .bind("text", format), |
| 92 | + capy.button(.{ .label = "+", .onclick = struct { |
| 93 | + fn add(pointer: *anyopaque) anyerror!void { |
| 94 | + const button: *capy.Button = @ptrCast(@alignCast(pointer)); |
| 95 | + const state: *CounterState = button.getUserdata(CounterState).?; |
| 96 | + state.count.set(state.count.get() + 1); |
| 97 | + } |
| 98 | + }.add }), |
| 99 | + })) |
| 100 | + .addUserdata(CounterState, state1), |
| 101 | + ); |
| 102 | +} |
| 103 | +
|
| 104 | +pub fn main() !void { |
| 105 | + try capy.init(); |
| 106 | + defer capy.deinit(); |
| 107 | +
|
| 108 | + var window = try capy.Window.init(); |
| 109 | + try window.set(capy.column(.{}, .{ |
| 110 | + capy.column(.{ .name = "counters-column" }, .{ |
| 111 | + counter(), |
| 112 | + }), |
| 113 | + })); |
| 114 | +
|
| 115 | + window.show(); |
| 116 | + capy.runEventLoop(); |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +## [`Monitor`](/api-reference/#capy.monitor.Monitor) and Fullscreen API |
| 121 | + |
| 122 | +Capy now has an API for handling monitors. It currently handles monitor names, sizes, and most |
| 123 | +importantly, the list of video modes a monitor supports. This can be used to set windows fullscreen, |
| 124 | +be it in borderless maximized mode or in exclusive fullscreen mode (the one that allows you to |
| 125 | +change the monitor's resolution). |
| 126 | + |
| 127 | +## Progress on the macOS backend |
| 128 | + |
| 129 | +Thanks to effort by [geon](https://github.com/geon), the macOS backend has tremendously progressed |
| 130 | +in the past month. Up until the start of October, the macOS backend could only show an empty window. |
| 131 | +Now it can already run small examples (like the `border-layout` example). |
| 132 | + |
| 133 | +## Grid Layout |
| 134 | + |
| 135 | +Before now, Capy used to only support few layout algorithms: `Row`, `Column` and `Stack`. The |
| 136 | +problem is this set only allows for simple layouts, given that `Row` and `Column` are essentially |
| 137 | +akin to flex boxes (with a few features missing) and `Stack` is just Z-ordering. |
| 138 | + |
| 139 | +Hence, a new layout was introduced: the grid layout. Its implementation in Capy is based on |
| 140 | +[CSS Grid Layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout), which is a |
| 141 | +flexible and expressive API that has the advantage of being familiar to most developers. |
| 142 | + |
| 143 | +Capy's implementation isn't an exact copy of this (as the way styling and layouting happens is |
| 144 | +different from CSS), but shares a lot of similarities with it. Nonetheless, a picture's worth a |
| 145 | +thousand words: |
| 146 | + |
| 147 | + |
| 148 | + |
| 149 | +```zig |
| 150 | +const grid = try capy.grid(.{ |
| 151 | + .template_columns = &([_]capy.GridLayoutConfig.LengthUnit{.{ .fraction = 1 }} ** 5), |
| 152 | + .template_rows = &.{ .{ .pixels = 150 }, .{ .pixels = 300 } }, |
| 153 | + .column_spacing = 5, |
| 154 | + .row_spacing = 10, |
| 155 | +}, .{}); |
| 156 | +// add buttons to grid |
| 157 | +// ... |
| 158 | +// display the grid on the window |
| 159 | +try window.set( |
| 160 | + // by default, capy.alignment centers the given component. |
| 161 | + capy.alignment(.{}, |
| 162 | + grid |
| 163 | + ) |
| 164 | +) |
| 165 | +``` |
| 166 | + |
| 167 | +As the features are similar to CSS Grid, I recommend [Josh W. Comeau's tutorial](https://www.joshwcomeau.com/css/interactive-guide-to-grid/) |
| 168 | +for an introduction to its capabilities. The only feature that's been intentionally ommited are |
| 169 | +grid template areas. |
| 170 | + |
| 171 | +## Added [`Dropdown`](/api-reference/#capy.components.Dropdown.Dropdown) component |
| 172 | + |
| 173 | + |
| 174 | + |
| 175 | +The above example was made with |
| 176 | +```zig |
| 177 | +const selectedValue = capy.Atom([]const u8).alloc(""); |
| 178 | +defer selectedValue.deinit(); |
| 179 | +
|
| 180 | +try window.set(capy.column(.{}, .{ |
| 181 | + capy.dropdown(.{ |
| 182 | + .values = &.{ "hello", "world", "test" }, |
| 183 | + }) |
| 184 | + .bind("selected_value", selectedValue), |
| 185 | +
|
| 186 | + capy.label(.{}) |
| 187 | + .bind("text", selectedValue), |
| 188 | +})); |
| 189 | +window.show(); |
| 190 | +``` |
| 191 | + |
| 192 | +## Progress on the WebAssembly backend |
| 193 | + |
| 194 | +The WebAssembly backend has also been enhanced. Most efforts have been in bug fixing. |
| 195 | + |
| 196 | +- Fixed [`Canvas`](https://capy-ui.org/api-reference/#capy.components.Canvas.Canvas) on high-DPI |
| 197 | +screens. This means the [osm-viewer](/zig-osm/) example now properly works on screens with high-DPI. |
| 198 | +- Add support for `TextField.read_only` and `Button.enabled` |
| 199 | +- Add support for `Window.on_frame` |
0 commit comments