From 0b3256236ff2b6dd3c57fcecc4cf79c544c2b51f Mon Sep 17 00:00:00 2001 From: zenith391 <39484230+zenith391@users.noreply.github.com> Date: Sun, 10 Nov 2024 19:14:31 +0100 Subject: [PATCH] feat!: use f32 for Position and Size --- src/backends/gtk/common.zig | 4 +- src/components/Alignment.zig | 16 ++++---- src/components/Image.zig | 4 +- src/components/Label.zig | 2 +- src/components/Navigation.zig | 8 +--- src/containers.zig | 77 ++++++++++++++++++++++------------- src/data.zig | 72 ++++++++++++++++++++++++-------- src/internal.zig | 6 ++- src/window.zig | 8 ++-- 9 files changed, 128 insertions(+), 69 deletions(-) diff --git a/src/backends/gtk/common.zig b/src/backends/gtk/common.zig index 6d9a2c38..56a7b781 100644 --- a/src/backends/gtk/common.zig +++ b/src/backends/gtk/common.zig @@ -267,8 +267,8 @@ pub fn Events(comptime T: type) type { var requisition: c.GtkRequisition = undefined; c.gtk_widget_get_preferred_size(self.peer, null, &requisition); return lib.Size.init( - @as(u32, @intCast(requisition.width)), - @as(u32, @intCast(requisition.height)), + @floatFromInt(requisition.width), + @floatFromInt(requisition.height), ); } }; diff --git a/src/components/Alignment.zig b/src/components/Alignment.zig index b4b6d114..ff938087 100644 --- a/src/components/Alignment.zig +++ b/src/components/Alignment.zig @@ -85,20 +85,20 @@ pub const Alignment = struct { self.relayouting.store(true, .seq_cst); defer self.relayouting.store(false, .seq_cst); - const available = Size{ .width = @as(u32, @intCast(peer.getWidth())), .height = @as(u32, @intCast(peer.getHeight())) }; + const available = self.getSize(); const alignX = self.x.get(); const alignY = self.y.get(); - if (self.child.get().peer) |widgetPeer| { - const preferredSize = self.child.get().getPreferredSize(available); - const finalSize = Size.intersect(preferredSize, available); + if (self.child.get().peer) |widget_peer| { + const preferred_size = self.child.get().getPreferredSize(available); + const final_size = Size.intersect(preferred_size, available); - const x = @as(u32, @intFromFloat(alignX * @as(f32, @floatFromInt(available.width -| finalSize.width)))); - const y = @as(u32, @intFromFloat(alignY * @as(f32, @floatFromInt(available.height -| finalSize.height)))); + const x: u32 = @intFromFloat(alignX * available.width - final_size.width); + const y: u32 = @intFromFloat(alignY * available.height - final_size.height); - peer.move(widgetPeer, x, y); - peer.resize(widgetPeer, finalSize.width, finalSize.height); + peer.move(widget_peer, x, y); + peer.resize(widget_peer, @intFromFloat(final_size.width), @intFromFloat(final_size.height)); } _ = self.widget_data.atoms.animation_controller.addChangeListener(.{ diff --git a/src/components/Image.zig b/src/components/Image.zig index ab78eb02..e1bacaa0 100644 --- a/src/components/Image.zig +++ b/src/components/Image.zig @@ -48,9 +48,9 @@ pub const Image = struct { pub fn getPreferredSize(self: *Image, available: Size) Size { if (self.data.get()) |data| { - return Size.init(data.width, data.height); + return Size.init(@floatFromInt(data.width), @floatFromInt(data.height)); } else { - return Size.init(100, 30).intersect(available); + return Size.init(100, 100).intersect(available); } } diff --git a/src/components/Label.zig b/src/components/Label.zig index 501b39c1..81f7b263 100644 --- a/src/components/Label.zig +++ b/src/components/Label.zig @@ -57,7 +57,7 @@ pub const Label = struct { } else { // Crude approximation const len = self.text.get().len; - return Size{ .width = @as(u32, @intCast(10 * len)), .height = 40.0 }; + return Size{ .width = @floatFromInt(10 * len), .height = 40.0 }; } } diff --git a/src/components/Navigation.zig b/src/components/Navigation.zig index e0c187bd..c63049ac 100644 --- a/src/components/Navigation.zig +++ b/src/components/Navigation.zig @@ -65,14 +65,10 @@ pub const Navigation = struct { self.relayouting.store(true, .seq_cst); defer self.relayouting.store(false, .seq_cst); - const available = Size{ - .width = @as(u32, @intCast(peer.getWidth())), - .height = @as(u32, @intCast(peer.getHeight())), - }; - + const available = self.getSize(); if (self.activeChild.peer) |widgetPeer| { peer.move(widgetPeer, 0, 0); - peer.resize(widgetPeer, available.width, available.height); + peer.resize(widgetPeer, @intFromFloat(available.width), @intFromFloat(available.height)); } } } diff --git a/src/containers.zig b/src/containers.zig index 7bca81d7..c2dd8362 100644 --- a/src/containers.zig +++ b/src/containers.zig @@ -46,14 +46,15 @@ const ColumnRowConfig = struct { pub fn ColumnLayout(peer: Callbacks, widgets: []*Widget) void { const expandedCount = getExpandedCount(widgets); const config = peer.getLayoutConfig(ColumnRowConfig); + const spacing: f32 = @floatFromInt(config.spacing); - const totalAvailableHeight = @as(u32, @intCast(peer.getSize(peer.userdata).height -| (widgets.len -| 1) * config.spacing)); + const totalAvailableHeight = peer.getSize(peer.userdata).height - @as(f32, @floatFromInt((widgets.len -| 1) * config.spacing)); - var childHeight = if (expandedCount == 0) 0 else @as(u32, @intCast(totalAvailableHeight)) / expandedCount; + var childHeight = if (expandedCount == 0) 0 else totalAvailableHeight / @as(f32, @floatFromInt(expandedCount)); for (widgets) |widget| { if (!widget.container_expanded) { const available = if (expandedCount > 0) Size.init(0, 0) else Size.init(peer.getSize(peer.userdata).width, totalAvailableHeight); - const divider = if (expandedCount == 0) 1 else expandedCount; + const divider: f32 = if (expandedCount == 0) 1 else @floatFromInt(expandedCount); const takenHeight = widget.getPreferredSize(available).height / divider; if (childHeight >= takenHeight) { childHeight -= takenHeight; @@ -71,16 +72,16 @@ pub fn ColumnLayout(peer: Callbacks, widgets: []*Widget) void { if (widget.peer) |widgetPeer| { const minimumSize = widget.getPreferredSize(Size.init(1, 1)); if (config.wrapping) { - if (childY >= @as(f32, @floatFromInt(peer.getSize(peer.userdata).height -| minimumSize.height))) { + if (childY >= @max(0, peer.getSize(peer.userdata).height - minimumSize.height)) { childY = 0; // TODO: largest width of all the column - childX += @as(f32, @floatFromInt(minimumSize.width)); + childX += minimumSize.width; } } const available = Size{ - .width = @as(u32, @intCast(peer.getSize(peer.userdata).width)), - .height = if (widget.container_expanded) childHeight else (@as(u32, @intCast(peer.getSize(peer.userdata).height)) -| @as(u32, @intFromFloat(childY))), + .width = peer.getSize(peer.userdata).width, + .height = if (widget.container_expanded) childHeight else peer.getSize(peer.userdata).height - childY, }; const preferred = widget.getPreferredSize(available); const size = blk: { @@ -99,9 +100,8 @@ pub fn ColumnLayout(peer: Callbacks, widgets: []*Widget) void { } }; - const x: u32 = @as(u32, @intFromFloat(childX)); - peer.moveResize(peer.userdata, widgetPeer, x, @as(u32, @intFromFloat(childY)), size.width, size.height); - childY += @as(f32, @floatFromInt(size.height)) + if (isLastWidget) 0 else @as(f32, @floatFromInt(config.spacing)); + peer.moveResize(peer.userdata, widgetPeer, @intFromFloat(childX), @intFromFloat(childY), @intFromFloat(size.width), @intFromFloat(size.height)); + childY += size.height + if (isLastWidget) 0 else spacing; } } @@ -122,14 +122,15 @@ pub fn ColumnLayout(peer: Callbacks, widgets: []*Widget) void { pub fn RowLayout(peer: Callbacks, widgets: []*Widget) void { const expandedCount = getExpandedCount(widgets); const config = peer.getLayoutConfig(ColumnRowConfig); + const spacing: f32 = @floatFromInt(config.spacing); - const totalAvailableWidth = @as(u32, @intCast(peer.getSize(peer.userdata).width -| (widgets.len -| 1) * config.spacing)); + const totalAvailableWidth = peer.getSize(peer.userdata).width - @as(f32, @floatFromInt((widgets.len -| 1) * config.spacing)); - var childWidth = if (expandedCount == 0) 0 else @as(u32, @intCast(totalAvailableWidth)) / expandedCount; + var childWidth = if (expandedCount == 0) 0 else totalAvailableWidth / @as(f32, @floatFromInt(expandedCount)); for (widgets) |widget| { if (!widget.container_expanded) { const available = if (expandedCount > 0) Size.init(0, 0) else Size.init(totalAvailableWidth, peer.getSize(peer.userdata).height); - const divider = if (expandedCount == 0) 1 else expandedCount; + const divider: f32 = if (expandedCount == 0) 1.0 else @floatFromInt(expandedCount); const takenWidth = widget.getPreferredSize(available).width / divider; if (childWidth >= takenWidth) { childWidth -= takenWidth; @@ -147,16 +148,16 @@ pub fn RowLayout(peer: Callbacks, widgets: []*Widget) void { if (widget.peer) |widgetPeer| { const minimumSize = widget.getPreferredSize(Size.init(1, 1)); if (config.wrapping) { - if (childX >= @as(f32, @floatFromInt(peer.getSize(peer.userdata).width -| minimumSize.width))) { + if (childX >= peer.getSize(peer.userdata).width - minimumSize.width) { childX = 0; // TODO: largest height of all the row - childY += @as(f32, @floatFromInt(minimumSize.height)); + childY += minimumSize.height; } } const available = Size{ - .width = if (widget.container_expanded) childWidth else (@as(u32, @intCast(peer.getSize(peer.userdata).width)) -| @as(u32, @intFromFloat(childX))), - .height = @as(u32, @intCast(peer.getSize(peer.userdata).height)), + .width = if (widget.container_expanded) childWidth else peer.getSize(peer.userdata).width - childX, + .height = peer.getSize(peer.userdata).height, }; const preferred = widget.getPreferredSize(available); const size = blk: { @@ -175,9 +176,15 @@ pub fn RowLayout(peer: Callbacks, widgets: []*Widget) void { } }; - const y: u32 = @as(u32, @intFromFloat(childY)); - peer.moveResize(peer.userdata, widgetPeer, @as(u32, @intFromFloat(childX)), y, size.width, size.height); - childX += @as(f32, @floatFromInt(size.width)) + if (isLastWidget) 0 else @as(f32, @floatFromInt(config.spacing)); + peer.moveResize( + peer.userdata, + widgetPeer, + @intFromFloat(childX), + @intFromFloat(childY), + @intFromFloat(size.width), + @intFromFloat(size.height), + ); + childX += size.width + if (isLastWidget) 0.0 else spacing; } } @@ -204,15 +211,22 @@ pub fn MarginLayout(peer: Callbacks, widgets: []*Widget) void { if (widgets[0].peer) |widgetPeer| { const available = peer.getSize(peer.userdata); - const left = std.math.lossyCast(u32, margin_rect.x()); - const top = std.math.lossyCast(u32, margin_rect.y()); + const left = margin_rect.x(); + const top = margin_rect.y(); const right = margin_rect.width(); const bottom = margin_rect.height(); if (peer.computingPreferredSize) { // What to return for computing preferred size const preferredSize = widgets[0].getPreferredSize(.{ .width = 0, .height = 0 }); - peer.moveResize(peer.userdata, widgetPeer, left, top, preferredSize.width + right, preferredSize.height + bottom); + peer.moveResize( + peer.userdata, + widgetPeer, + @intFromFloat(left), + @intFromFloat(top), + @intFromFloat(preferredSize.width + right), + @intFromFloat(preferredSize.height + bottom), + ); } else { // What to return for actual layouting const preferredSize = widgets[0].getPreferredSize(available); @@ -222,7 +236,14 @@ pub fn MarginLayout(peer: Callbacks, widgets: []*Widget) void { const finalSize = available; //peer.moveResize(peer.userdata, widgetPeer, 0, 0, finalSize.width, finalSize.height); - peer.moveResize(peer.userdata, widgetPeer, left, top, finalSize.width -| left -| right, finalSize.height -| top -| bottom); + peer.moveResize( + peer.userdata, + widgetPeer, + @intFromFloat(left), + @intFromFloat(top), + @intFromFloat(@max(0, finalSize.width - left - right)), + @intFromFloat(@max(0, finalSize.height - top - bottom)), + ); } } } @@ -233,7 +254,7 @@ pub fn StackLayout(peer: Callbacks, widgets: []*Widget) void { for (widgets) |widget| { if (widget.peer) |widgetPeer| { const widgetSize = if (peer.computingPreferredSize) widget.getPreferredSize(peer.availableSize.?) else size; - peer.moveResize(peer.userdata, widgetPeer, 0, 0, widgetSize.width, widgetSize.height); + peer.moveResize(peer.userdata, widgetPeer, 0, 0, @intFromFloat(widgetSize.width), @intFromFloat(widgetSize.height)); } } } @@ -420,8 +441,8 @@ pub const Container = struct { fn fakeResMove(data: usize, widget: backend.PeerType, x: u32, y: u32, w: u32, h: u32) void { const size = @as(*Size, @ptrFromInt(data)); _ = widget; - size.width = @max(size.width, x + w); - size.height = @max(size.height, y + h); + size.width = @max(size.width, @as(f32, @floatFromInt(x + w))); + size.height = @max(size.height, @as(f32, @floatFromInt(y + h))); } fn fakeSetTabOrder(data: usize, widgets: []const backend.PeerType) void { @@ -431,7 +452,7 @@ pub const Container = struct { fn getSize(data: usize) Size { const peer = @as(*backend.Container, @ptrFromInt(data)); - return Size{ .width = @as(u32, @intCast(peer.getWidth())), .height = @as(u32, @intCast(peer.getHeight())) }; + return Size{ .width = @floatFromInt(peer.getWidth()), .height = @floatFromInt(peer.getHeight()) }; } fn moveResize(data: usize, widget: backend.PeerType, x: u32, y: u32, w: u32, h: u32) void { diff --git a/src/data.zig b/src/data.zig index 5d8ed725..4d868ce5 100644 --- a/src/data.zig +++ b/src/data.zig @@ -50,7 +50,6 @@ pub fn lerp(a: anytype, b: @TypeOf(a), t: f64) @TypeOf(a) { @compileError("type " ++ @typeName(T) ++ " does not support linear interpolation"); } } -const lerpInt = lerp; pub const Easing = *const fn (t: f64) f64; pub const Easings = struct { @@ -1033,34 +1032,38 @@ pub fn FormattedAtom(allocator: std.mem.Allocator, comptime fmt: []const u8, chi /// A position expressed in display pixels. pub const Position = struct { - x: i32, - y: i32, + x: f32, + y: f32, /// Shorthand for struct initialization - pub fn init(x: i32, y: i32) Position { + pub fn init(x: f32, y: f32) Position { return Position{ .x = x, .y = y }; } pub fn lerp(a: Position, b: Position, t: f64) Position { return Position{ - .x = lerpInt(a.x, b.x, t), - .y = lerpInt(a.y, b.y, t), + .x = lerpFloat(a.x, b.x, t), + .y = lerpFloat(a.y, b.y, t), }; } }; /// A size expressed in display pixels. pub const Size = struct { - width: u32, - height: u32, + width: f32, + height: f32, /// Shorthand for struct initialization - pub fn init(width: u32, height: u32) Size { + pub fn init(width: f32, height: f32) Size { + std.debug.assert(width >= 0); + std.debug.assert(height >= 0); return Size{ .width = width, .height = height }; } /// Returns the size with the least area pub fn min(a: Size, b: Size) Size { + std.debug.assert(a.width >= 0 and a.height >= 0); + std.debug.assert(b.width >= 0 and b.height >= 0); if (a.width * a.height < b.width * b.height) { return a; } else { @@ -1070,6 +1073,8 @@ pub const Size = struct { /// Returns the size with the most area pub fn max(a: Size, b: Size) Size { + std.debug.assert(a.width >= 0 and a.height >= 0); + std.debug.assert(b.width >= 0 and b.height >= 0); if (a.width * a.height > b.width * b.height) { return a; } else { @@ -1079,6 +1084,8 @@ pub const Size = struct { /// Combine two sizes by taking the largest width and the largest height pub fn combine(a: Size, b: Size) Size { + std.debug.assert(a.width >= 0 and a.height >= 0); + std.debug.assert(b.width >= 0 and b.height >= 0); return Size{ .width = @max(a.width, b.width), .height = @max(a.height, b.height), @@ -1087,6 +1094,8 @@ pub const Size = struct { /// Intersect two sizes by taking the lowest width and the lowest height pub fn intersect(a: Size, b: Size) Size { + std.debug.assert(a.width >= 0 and a.height >= 0); + std.debug.assert(b.width >= 0 and b.height >= 0); return Size{ .width = @min(a.width, b.width), .height = @min(a.height, b.height), @@ -1094,9 +1103,11 @@ pub const Size = struct { } pub fn lerp(a: Size, b: Size, t: f64) Size { + std.debug.assert(a.width >= 0 and a.height >= 0); + std.debug.assert(b.width >= 0 and b.height >= 0); return Size{ - .width = lerpInt(a.width, b.width, t), - .height = lerpInt(a.height, b.height, t), + .width = lerpFloat(a.width, b.width, t), + .height = lerpFloat(a.height, b.height, t), }; } @@ -1147,10 +1158,10 @@ pub const Rectangle = struct { origin: Position, size: Size, - pub fn init(ox: i32, oy: i32, owidth: u32, oheight: u32) Rectangle { + pub fn init(ox: f32, oy: f32, owidth: f32, oheight: f32) Rectangle { return Rectangle{ - .origin = .{ .x = ox, .y = oy }, - .size = .{ .width = owidth, .height = oheight }, + .origin = Position.init(ox, oy), + .size = Size.init(owidth, oheight), }; } @@ -1161,21 +1172,46 @@ pub const Rectangle = struct { }; } - pub fn x(self: Rectangle) i32 { + pub fn x(self: Rectangle) f32 { return self.origin.x; } - pub fn y(self: Rectangle) i32 { + pub fn y(self: Rectangle) f32 { return self.origin.y; } - pub fn width(self: Rectangle) u32 { + pub fn width(self: Rectangle) f32 { return self.size.width; } - pub fn height(self: Rectangle) u32 { + pub fn height(self: Rectangle) f32 { return self.size.height; } + + pub fn combine(a: Rectangle, b: Rectangle) Rectangle { + // The X coordinate of the right-most point of the first rectangle. + const right = a.origin.x + a.size.width; + // The Y coordinate of the bottom point of the first rectangle. + const bottom = a.origin.y + a.size.height; + + const new_origin = Position{ + .x = @min(a.origin.x, b.origin.x), + .y = @min(a.origin.y, b.origin.y), + }; + return .{ + .origin = new_origin, + .size = .{ + .width = @max(right, b.origin.x + b.size.width) - new_origin.x, + .height = @max(bottom, b.origin.y + b.size.height) - new_origin.y, + }, + }; + } + + pub fn intersection(a: Rectangle, b: Rectangle) Rectangle { + _ = a; + _ = b; + return undefined; + } }; const expectEqual = std.testing.expectEqual; diff --git a/src/internal.zig b/src/internal.zig index f33d7bd6..fc295b79 100644 --- a/src/internal.zig +++ b/src/internal.zig @@ -196,6 +196,10 @@ pub fn Widgeting(comptime T: type) type { } } + pub fn getSize(self: *T) Size { + return Size.init(@floatFromInt(self.getWidth()), @floatFromInt(self.getHeight())); + } + pub fn getWidth(self: *T) u32 { if (self.peer) |peer| { return @intCast(peer.getWidth()); @@ -681,7 +685,7 @@ pub fn Events(comptime T: type) type { fn resizeHandler(width: u32, height: u32, data: usize) void { const self = @as(*T, @ptrFromInt(data)); - const size = Size{ .width = width, .height = height }; + const size = Size{ .width = @floatFromInt(width), .height = @floatFromInt(height) }; for (self.widget_data.handlers.resizeHandlers.items) |func| { func(self, size) catch |err| errorHandler(err); } diff --git a/src/window.zig b/src/window.zig index f66c1c4c..c69e3b61 100644 --- a/src/window.zig +++ b/src/window.zig @@ -104,7 +104,7 @@ pub const Window = struct { const id = std.process.getEnvVarOwned(internal.scratch_allocator, EMULATOR_KEY) catch unreachable; defer internal.scratch_allocator.free(id); if (devices.get(id)) |device| { - self.peer.resize(@as(c_int, @intCast(device.resolution.width)), @as(c_int, @intCast(device.resolution.height))); + self.peer.resize(@as(c_int, @intFromFloat(device.resolution.width)), @as(c_int, @intFromFloat(device.resolution.height))); self.setSourceDpi(device.dpi); return; } else if (!did_invalid_warning) { @@ -116,14 +116,14 @@ pub const Window = struct { did_invalid_warning = true; } } - self.size.set(.{ .width = width, .height = height }); + self.size.set(.{ .width = @floatFromInt(width), .height = @floatFromInt(height) }); self.peer.setUserData(self); self.peer.resize(@as(c_int, @intCast(width)), @as(c_int, @intCast(height))); } fn sizeChanged(width: u32, height: u32, data: usize) void { const self = @as(*Window, @ptrFromInt(data)); - self.size.set(.{ .width = width, .height = height }); + self.size.set(.{ .width = @floatFromInt(width), .height = @floatFromInt(height) }); } fn propertyChanged(name: []const u8, value: *const anyopaque, data: usize) void { @@ -186,7 +186,9 @@ pub const Window = struct { if (self._child) |child| { child.unref(); } + self.animation_controller.deinit(); self.on_frame.deinitAllListeners(); + internal.lasting_allocator.destroy(self.on_frame); self.peer.deinit(); } };