Compare commits

...

6 Commits

Author SHA1 Message Date
LeRoyce Pearson 32f8b66761 refactor: go all in on Element abstraction to prepare for multiplayer support 2024-03-01 16:07:13 -07:00
LeRoyce Pearson 0a0a7d8d49 remove history len debug text 2024-02-29 13:47:04 -07:00
LeRoyce Pearson 527c3aa0ce feat: make text gray if it has no command 2024-02-29 13:45:41 -07:00
LeRoyce Pearson 6d7c4d8b74 feat: marking cards no longer clogs the undo history 2024-02-29 13:40:43 -07:00
LeRoyce Pearson a14e5c3839 feat: refine screen size based sprite selection
Before this change, medium was selected even for devices that could use
the large sprites.

I also changed the metric from `framebuffer_size[1]` to `window_size[1]`.
This is pixel art with pixels that are designed to be seen. We care more
about its size on the screen.
2024-02-29 13:15:21 -07:00
LeRoyce Pearson dbe5a92b54 fix: don't crash if joystick doesn't have expected inputs
My laptop and desktop both have devices that appear as joysticks, but are
very clearly not. Before this change this meant that I couldn't test the
game on desktop after adding joystick support.
2024-02-29 13:13:09 -07:00
11 changed files with 1128 additions and 687 deletions

50
src/Element.zig Normal file
View File

@ -0,0 +1,50 @@
pub const Card = @import("./Element/Card.zig");
pub const HBox = @import("./Element/HBox.zig");
pub const Link = @import("./Element/Link.zig");
pub const Page = @import("./Element/Page.zig");
pub const Pile = @import("./Element/Pile.zig");
pub const Text = @import("./Element/Text.zig");
pub const View = @import("./Element/View.zig");
pointer: ?*anyopaque,
interface: *const Interface,
pub const Element = @This();
pub const Error = error{
OutOfMemory,
};
pub const Interface = struct {
destroy: *const fn (?*anyopaque) void,
minimum_size: *const fn (?*anyopaque, RenderResources) [2]f32,
render: *const fn (?*anyopaque, *seizer.Canvas, RenderResources, [2]f32, [2]f32) void,
get_actions: *const fn (?*anyopaque, *std.ArrayList(Action), RenderResources, [2]f32, [2]f32) Error!void,
};
pub const RenderResources = struct {
hovered: ?Element.Command,
deck: DeckSprites,
font: seizer.Canvas.Font,
};
pub const Action = struct {
center: [2]f32,
command: Command,
};
pub const Command = union(enum) {
goto_reference: protocol.Reference,
callback: struct {
pointer: ?*anyopaque,
function: *const fn (?*anyopaque) void,
},
};
const DeckSprites = assets.DeckSprites;
const protocol = @import("./protocol.zig");
const assets = @import("./assets.zig");
const seizer = @import("seizer");
const gl = seizer.gl;
const std = @import("std");

101
src/Element/Card.zig Normal file
View File

@ -0,0 +1,101 @@
allocator: std.mem.Allocator,
visual: Visual,
command: ?[]const u8,
marked: bool,
const Visual = union(enum) {
back,
card: assets.Card,
};
pub fn create(allocator: std.mem.Allocator, options: struct { visual: Visual, command: ?[]const u8, marked: ?bool = null }) !*@This() {
const this = try allocator.create(@This());
errdefer allocator.destroy(this);
this.* = .{
.allocator = allocator,
.visual = options.visual,
.command = options.command,
.marked = options.marked orelse false,
};
return this;
}
pub fn element(this: *@This()) Element {
return Element{
.pointer = this,
.interface = &Element.Interface{
.minimum_size = &element_minimum_size,
.render = &element_render,
.get_actions = &element_get_actions,
},
};
}
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
const this: *@This() = @ptrCast(@alignCast(pointer));
_ = this;
return .{
@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[0])),
@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])),
};
}
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
const this: *@This() = @ptrCast(@alignCast(pointer));
const mark_offset = if (this.marked)
[2]f32{
0,
@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) * -0.4,
}
else
[2]f32{ 0, 0 };
switch (this.visual) {
.back => render_resources.deck.tilesheet.renderTile(
canvas,
render_resources.deck.back,
.{ min[0] + mark_offset[0], min[1] + mark_offset[1] },
.{},
),
.card => |card| render_resources.deck.tilesheet.renderTile(
canvas,
render_resources.deck.getTileForCard(card),
.{ @floor(min[0] + mark_offset[0]), @floor(min[1] + mark_offset[1]) },
.{},
),
}
if (render_resources.hovered != null and this.command != null and std.mem.eql(u8, render_resources.hovered.?, this.command.?)) {
canvas.rect(
.{ min[0] + mark_offset[0], min[1] + mark_offset[1] },
.{ max[0] - min[0], max[1] - min[1] },
.{ .color = .{ 0xAA, 0xFF, 0xAA, 0x60 } },
);
}
}
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
const this: *@This() = @ptrCast(@alignCast(pointer));
_ = render_resources;
if (this.command) |command| {
try actions.append(.{
.center = [2]f32{
(min[0] + max[0]) / 2,
(min[1] + max[1]) / 2,
},
.command = command,
});
}
}
const Element = @import("../Element.zig");
const assets = @import("../assets.zig");
const seizer = @import("seizer");
const gl = seizer.gl;
const std = @import("std");

130
src/Element/HBox.zig Normal file
View File

@ -0,0 +1,130 @@
allocator: std.mem.Allocator,
children: std.ArrayListUnmanaged(Element),
pub fn create(allocator: std.mem.Allocator) !*@This() {
const this = try allocator.create(@This());
errdefer allocator.destroy(this);
this.* = .{
.allocator = allocator,
.children = .{},
};
return this;
}
pub fn addElement(this: *@This(), child_element: Element) !void {
try this.children.append(this.allocator, child_element);
}
pub fn element(this: *@This()) Element {
return Element{
.pointer = this,
.interface = &Element.Interface{
.destroy = &element_destroy,
.minimum_size = &element_minimum_size,
.render = &element_render,
.get_actions = &element_get_actions,
},
};
}
pub fn element_destroy(pointer: ?*anyopaque) void {
const this: *@This() = @ptrCast(@alignCast(pointer));
for (this.children.items) |child| {
child.interface.destroy(child.pointer);
}
this.children.deinit(this.allocator);
this.allocator.destroy(this);
}
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
const this: *@This() = @ptrCast(@alignCast(pointer));
var minimum_size = [2]f32{ 0, 0 };
for (this.children.items) |child| {
const child_size = child.interface.minimum_size(child.pointer, render_resources);
minimum_size = .{
minimum_size[0] + child_size[0],
@max(minimum_size[1], child_size[1]),
};
}
return minimum_size;
}
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
const this: *@This() = @ptrCast(@alignCast(pointer));
if (this.children.items.len == 0) return;
const parent_size = [2]f32{
max[0] - min[0],
max[1] - min[1],
};
var filled_space: f32 = 0;
for (this.children.items) |child| {
const child_size = child.interface.minimum_size(child.pointer, render_resources);
filled_space += child_size[0];
}
const empty_space = parent_size[0] - filled_space;
const num_spaces = if (empty_space > 0) this.children.items.len + 1 else this.children.items.len - 1;
const space_around = empty_space / @as(f32, @floatFromInt(num_spaces));
var x: f32 = min[0] + @max(space_around, 0);
for (this.children.items) |child| {
const child_size = child.interface.minimum_size(child.pointer, render_resources);
const child_min = [2]f32{ x, min[1] };
const child_max = [2]f32{ x + child_size[0], max[1] };
child.interface.render(child.pointer, canvas, render_resources, child_min, child_max);
x += child_size[0] + space_around;
}
}
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
const this: *@This() = @ptrCast(@alignCast(pointer));
if (this.children.items.len == 0) return;
const parent_size = [2]f32{
max[0] - min[0],
max[1] - min[1],
};
var filled_space: f32 = 0;
for (this.children.items) |child| {
const child_size = child.interface.minimum_size(child.pointer, render_resources);
filled_space += child_size[0];
}
const empty_space = parent_size[0] - filled_space;
const space_around = empty_space / @as(f32, @floatFromInt((this.children.items.len + 1)));
var x: f32 = min[0] + space_around;
for (this.children.items) |child| {
const child_size = child.interface.minimum_size(child.pointer, render_resources);
const child_min = [2]f32{ x, min[1] };
const child_max = [2]f32{ x + child_size[0], max[1] };
try child.interface.get_actions(child.pointer, actions, render_resources, child_min, child_max);
x += child_size[0] + space_around;
}
}
const Element = @import("../Element.zig");
const assets = @import("../assets.zig");
const seizer = @import("seizer");
const gl = seizer.gl;
const std = @import("std");

76
src/Element/Link.zig Normal file
View File

@ -0,0 +1,76 @@
allocator: std.mem.Allocator,
child: Element,
reference: protocol.Reference,
pub fn create(allocator: std.mem.Allocator, child: Element, reference: protocol.Reference) !*@This() {
const this = try allocator.create(@This());
errdefer allocator.destroy(this);
this.* = .{
.allocator = allocator,
.child = child,
.reference = reference,
};
return this;
}
pub fn element(this: *@This()) Element {
return Element{
.pointer = this,
.interface = &Element.Interface{
.destroy = &element_destroy,
.minimum_size = &element_minimum_size,
.render = &element_render,
.get_actions = &element_get_actions,
},
};
}
pub fn element_destroy(pointer: ?*anyopaque) void {
const this: *@This() = @ptrCast(@alignCast(pointer));
this.child.interface.destroy(this.child.pointer);
this.allocator.destroy(this);
}
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
const this: *@This() = @ptrCast(@alignCast(pointer));
return this.child.interface.minimum_size(this.child.pointer, render_resources);
}
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
const this: *@This() = @ptrCast(@alignCast(pointer));
// render child element
this.child.interface.render(this.child.pointer, canvas, render_resources, min, max);
// render selection indicator on top if this link is selected
if (render_resources.hovered != null and std.meta.eql(render_resources.hovered.?, Element.Command{ .goto_reference = this.reference })) {
canvas.rect(
.{ min[0], min[1] },
.{ max[0] - min[0], max[1] - min[1] },
.{ .color = .{ 0xAA, 0xFF, 0xAA, 0x60 } },
);
}
}
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
const this: *@This() = @ptrCast(@alignCast(pointer));
_ = render_resources;
try actions.append(.{
.center = [2]f32{
(min[0] + max[0]) / 2,
(min[1] + max[1]) / 2,
},
.command = .{ .goto_reference = this.reference },
});
}
const Element = @import("../Element.zig");
const protocol = @import("../protocol.zig");
const assets = @import("../assets.zig");
const seizer = @import("seizer");
const gl = seizer.gl;
const std = @import("std");

150
src/Element/Page.zig Normal file
View File

@ -0,0 +1,150 @@
allocator: std.mem.Allocator,
children: std.ArrayListUnmanaged(Child),
pub const Child = struct {
/// Where is the child attached on the parent? Imagine it as a pin going through
/// both the parent and child element. This defines where on the parent that pin
/// passes through.
///
/// In a virtual coordinate where <0,0> = top-left, <1,1> = bottom-right, unless
/// the numbers are negative.
anchor_in_parent: [2]f32,
/// Where is the child attached on the parent? Imagine it as a pin going through
/// both the parent and child element. This defines where on the child that pin
/// passes through.
///
/// In a virtual coordinate where <0,0> = top-left, <1,1> = bottom-right, unless
/// the numbers are negative.
anchor_in_child: [2]f32,
element: Element,
};
pub fn create(allocator: std.mem.Allocator) !*@This() {
const this = try allocator.create(@This());
errdefer allocator.destroy(this);
this.* = .{
.allocator = allocator,
.children = .{},
};
return this;
}
pub fn addElement(this: *@This(), anchor_in_parent: [2]f32, anchor_in_child: [2]f32, child_element: Element) !void {
try this.children.append(this.allocator, Child{
.anchor_in_parent = anchor_in_parent,
.anchor_in_child = anchor_in_child,
.element = child_element,
});
}
pub fn element(this: *@This()) Element {
return Element{
.pointer = this,
.interface = &Element.Interface{
.destroy = &element_destroy,
.minimum_size = &element_minimum_size,
.render = &element_render,
.get_actions = &element_get_actions,
},
};
}
pub fn element_destroy(pointer: ?*anyopaque) void {
const this: *@This() = @ptrCast(@alignCast(pointer));
for (this.children.items) |child| {
child.element.interface.destroy(child.element.pointer);
}
this.children.deinit(this.allocator);
this.allocator.destroy(this);
}
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
const this: *@This() = @ptrCast(@alignCast(pointer));
var minimum_size = [2]f32{ 0, 0 };
for (this.children.items) |child| {
const child_size = child.element.interface.minimum_size(child.element.pointer, render_resources);
minimum_size = .{
@max(minimum_size[0], child_size[0]),
@max(minimum_size[1], child_size[1]),
};
}
return minimum_size;
}
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
const this: *@This() = @ptrCast(@alignCast(pointer));
const parent_size = [2]f32{
max[0] - min[0],
max[1] - min[1],
};
for (this.children.items) |child| {
const pos_in_parent = [2]f32{
child.anchor_in_parent[0] * parent_size[0],
child.anchor_in_parent[1] * parent_size[1],
};
const child_size = child.element.interface.minimum_size(child.element.pointer, render_resources);
const pos_in_child = [2]f32{
child.anchor_in_child[0] * child_size[0],
child.anchor_in_child[1] * child_size[1],
};
const child_min = [2]f32{
@max(min[0], pos_in_parent[0] - pos_in_child[0]),
@max(min[1], pos_in_parent[1] - pos_in_child[1]),
};
const child_max = [2]f32{
@min(max[0], pos_in_parent[0] + (child_size[0] - pos_in_child[0])),
@min(max[1], pos_in_parent[1] + (child_size[1] - pos_in_child[1])),
};
child.element.interface.render(child.element.pointer, canvas, render_resources, child_min, child_max);
}
}
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
const this: *@This() = @ptrCast(@alignCast(pointer));
const parent_size = [2]f32{
max[0] - min[0],
max[1] - min[1],
};
for (this.children.items) |child| {
const pos_in_parent = [2]f32{
child.anchor_in_parent[0] * parent_size[0],
child.anchor_in_parent[1] * parent_size[1],
};
const child_size = child.element.interface.minimum_size(child.element.pointer, render_resources);
const pos_in_child = [2]f32{
child.anchor_in_child[0] * child_size[0],
child.anchor_in_child[1] * child_size[1],
};
const child_min = [2]f32{
pos_in_parent[0] - pos_in_child[0],
pos_in_parent[1] - pos_in_child[1],
};
const child_max = [2]f32{
pos_in_parent[0] + (child_size[0] - pos_in_child[0]),
pos_in_parent[1] + (child_size[1] - pos_in_child[1]),
};
try child.element.interface.get_actions(child.element.pointer, actions, render_resources, child_min, child_max);
}
}
const Element = @import("../Element.zig");
const assets = @import("../assets.zig");
const seizer = @import("seizer");
const gl = seizer.gl;
const std = @import("std");

93
src/Element/Pile.zig Normal file
View File

@ -0,0 +1,93 @@
allocator: std.mem.Allocator,
cards: []Card,
hidden: bool = false,
command: ?[]const u8 = null,
pub fn create(allocator: std.mem.Allocator, cards: []const Card) !*@This() {
const this = try allocator.create(@This());
errdefer allocator.destroy(this);
const cards_owned = try allocator.dupe(Card, cards);
errdefer allocator.free(cards_owned);
this.* = .{
.allocator = allocator,
.cards = cards_owned,
};
return this;
}
pub fn element(this: *@This()) Element {
return Element{
.pointer = this,
.interface = &Element.Interface{
.minimum_size = &element_minimum_size,
.render = &element_render,
.get_actions = &element_get_actions,
},
};
}
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
const this: *@This() = @ptrCast(@alignCast(pointer));
return .{
@floatFromInt(render_resources.deck.tilesheet.tile_size[0]),
@as(f32, (@floatFromInt(render_resources.deck.tilesheet.tile_size[1]))) + @as(f32, (@floatFromInt(render_resources.deck.tilesheet.tile_size[1]))) * @as(f32, @floatFromInt(this.cards.len)) / 52.0 * 0.25,
};
}
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
const this: *@This() = @ptrCast(@alignCast(pointer));
const start_y: f32 = max[1] - @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1]));
for (this.cards, 0..) |card, i| {
const oy = -@as(f32, @floatFromInt(i)) * (@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) / (52.0 * 4));
if (this.hidden) {
render_resources.deck.tilesheet.renderTile(canvas, render_resources.deck.back, .{
min[0],
start_y + oy,
}, .{});
} else {
render_resources.deck.tilesheet.renderTile(canvas, render_resources.deck.getTileForCard(card), .{
min[0],
start_y + oy,
}, .{});
}
}
if (render_resources.hovered != null and this.command != null and std.mem.eql(u8, render_resources.hovered.?, this.command.?)) {
const oy = -@as(f32, @floatFromInt(this.cards.len)) * (@as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) / (52.0 * 4));
canvas.rect(
.{ min[0], start_y + oy },
.{ @floatFromInt(render_resources.deck.tilesheet.tile_size[0]), @floatFromInt(render_resources.deck.tilesheet.tile_size[1]) },
.{ .color = .{ 0xAA, 0xFF, 0xAA, 0x60 } },
);
}
}
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
const this: *@This() = @ptrCast(@alignCast(pointer));
if (this.command) |command| {
const center = [2]f32{
min[0] + @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[0])) / 2,
max[1] - @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])) * (2.0 - @as(f32, @floatFromInt(this.cards.len)) / (52.0 * 4)),
};
try actions.append(.{
.center = center,
.command = command,
});
}
}
const Card = assets.Card;
const Element = @import("../Element.zig");
const assets = @import("../assets.zig");
const seizer = @import("seizer");
const gl = seizer.gl;
const std = @import("std");

62
src/Element/Text.zig Normal file
View File

@ -0,0 +1,62 @@
allocator: std.mem.Allocator,
text: []const u8,
pub fn create(allocator: std.mem.Allocator, text: []const u8) !*@This() {
const this = try allocator.create(@This());
errdefer allocator.destroy(this);
const text_owned = try allocator.dupe(u8, text);
errdefer allocator.free(text_owned);
this.* = .{
.allocator = allocator,
.text = text_owned,
};
return this;
}
pub fn element(this: *@This()) Element {
return Element{
.pointer = this,
.interface = &Element.Interface{
.destroy = &element_destroy,
.minimum_size = &element_minimum_size,
.render = &element_render,
.get_actions = &element_get_actions,
},
};
}
pub fn element_destroy(pointer: ?*anyopaque) void {
const this: *@This() = @ptrCast(@alignCast(pointer));
this.allocator.free(this.text);
this.allocator.destroy(this);
}
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
const this: *@This() = @ptrCast(@alignCast(pointer));
return render_resources.font.textSize(this.text, 1);
}
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
const this: *@This() = @ptrCast(@alignCast(pointer));
_ = max;
_ = render_resources;
_ = canvas.writeText(min, this.text, .{});
}
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
_ = pointer;
_ = actions;
_ = render_resources;
_ = min;
_ = max;
}
const Element = @import("../Element.zig");
const assets = @import("../assets.zig");
const seizer = @import("seizer");
const gl = seizer.gl;
const std = @import("std");

179
src/Element/View.zig Normal file
View File

@ -0,0 +1,179 @@
allocator: std.mem.Allocator,
reference: protocol.Reference,
response: ?protocol.Response,
link_actions_in_view: std.AutoHashMapUnmanaged(*LinkProxy, void) = .{},
const View = @This();
const LinkProxy = struct {
view: *View,
reference: protocol.Reference,
fn callback(pointer: ?*anyopaque) void {
const this: *@This() = @ptrCast(@alignCast(pointer));
this.view.setReference(this.reference);
}
};
pub fn create(allocator: std.mem.Allocator, reference: protocol.Reference) !*@This() {
const this = try allocator.create(@This());
errdefer allocator.destroy(this);
this.* = .{
.allocator = allocator,
.reference = reference,
.response = null,
};
return this;
}
pub fn setReference(this: *@This(), reference: protocol.Reference) void {
if (this.response) |response| {
response.arena.deinit();
this.response = null;
}
this.reference = reference;
var action_proxies_iter = this.link_actions_in_view.keyIterator();
while (action_proxies_iter.next()) |link_proxy| {
this.allocator.destroy(link_proxy.*);
}
this.link_actions_in_view.clearRetainingCapacity();
}
pub fn element(this: *@This()) Element {
return Element{
.pointer = this,
.interface = &Element.Interface{
.destroy = &element_destroy,
.minimum_size = &element_minimum_size,
.render = &element_render,
.get_actions = &element_get_actions,
},
};
}
const LOADING_TEXT = "Loading...";
pub fn element_destroy(pointer: ?*anyopaque) void {
const this: *@This() = @ptrCast(@alignCast(pointer));
if (this.response) |response| {
switch (response.body) {
.element => |root| root.interface.destroy(root.pointer),
}
response.arena.deinit();
}
var action_proxies_iter = this.link_actions_in_view.keyIterator();
while (action_proxies_iter.next()) |link_proxy| {
this.allocator.destroy(link_proxy.*);
}
this.link_actions_in_view.deinit(this.allocator);
switch (this.reference) {
.handler => |handler| if (handler.interface.deinit) |deinit| deinit(handler.pointer),
}
this.allocator.destroy(this);
}
pub fn element_minimum_size(pointer: ?*anyopaque, render_resources: Element.RenderResources) [2]f32 {
const this: *@This() = @ptrCast(@alignCast(pointer));
if (this.response) |response| {
switch (response.body) {
.element => |root| return root.interface.minimum_size(root.pointer, render_resources),
}
} else {
return render_resources.font.textSize(LOADING_TEXT, 1);
}
}
pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) void {
const this: *@This() = @ptrCast(@alignCast(pointer));
if (this.response == null) {
this.response = this.reference.handler.interface.handle(this.reference.handler.pointer, .{
.allocator = this.allocator,
}) catch response_error: {
std.log.warn("Could not get response", .{});
break :response_error null;
};
}
if (this.response) |response| {
var child_render_resources = render_resources;
if (render_resources.hovered) |hovered| {
switch (hovered) {
.callback => |cb| {
if (this.link_actions_in_view.getKey(@ptrCast(@alignCast(cb.pointer)))) |link_proxy| {
child_render_resources.hovered = .{ .goto_reference = link_proxy.reference };
}
},
else => {},
}
}
switch (response.body) {
.element => |root| root.interface.render(root.pointer, canvas, child_render_resources, min, max),
}
} else {
_ = canvas.writeText(min, LOADING_TEXT, .{});
}
}
pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Element.Action), render_resources: Element.RenderResources, min: [2]f32, max: [2]f32) Element.Error!void {
const this: *@This() = @ptrCast(@alignCast(pointer));
// clean up previous link actions
var action_proxies_iter = this.link_actions_in_view.keyIterator();
while (action_proxies_iter.next()) |link_proxy| {
this.allocator.destroy(link_proxy.*);
}
this.link_actions_in_view.clearRetainingCapacity();
var children_actions = std.ArrayList(Element.Action).init(this.allocator);
defer children_actions.deinit();
if (this.response) |response| {
switch (response.body) {
.element => |root| {
try root.interface.get_actions(root.pointer, &children_actions, render_resources, min, max);
},
}
}
for (children_actions.items) |child_action| {
switch (child_action.command) {
.goto_reference => |ref| {
const link_proxy_action = try this.allocator.create(LinkProxy);
try this.link_actions_in_view.put(this.allocator, link_proxy_action, {});
link_proxy_action.* = .{
.view = this,
.reference = ref,
};
try actions.append(.{
.center = child_action.center,
.command = .{
.callback = .{
.pointer = link_proxy_action,
.function = &LinkProxy.callback,
},
},
});
},
.callback => try actions.append(child_action),
}
}
}
const Element = @import("../Element.zig");
const protocol = @import("../protocol.zig");
const assets = @import("../assets.zig");
const seizer = @import("seizer");
const gl = seizer.gl;
const std = @import("std");

76
src/LocalUI.zig Normal file
View File

@ -0,0 +1,76 @@
pub const main_menu = Handler{
.pointer = null,
.interface = &.{
.handle = &main_menu_handler,
.deinit = null,
},
};
fn main_menu_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
var arena = std.heap.ArenaAllocator.init(request.allocator);
errdefer arena.deinit();
var join_multiplayer_text = try Element.Text.create(arena.allocator(), "Join Multiplayer Game");
var join_multiplayer_link = try Element.Link.create(arena.allocator(), join_multiplayer_text.element(), .{ .handler = join_multiplayer_game });
var host_multiplayer_text = try Element.Text.create(arena.allocator(), "Host Multiplayer Game");
var host_multiplayer_link = try Element.Link.create(arena.allocator(), host_multiplayer_text.element(), .{ .handler = host_multiplayer_game });
var play_game_hbox = try Element.HBox.create(arena.allocator());
try play_game_hbox.addElement(join_multiplayer_link.element());
try play_game_hbox.addElement(host_multiplayer_link.element());
var page = try Element.Page.create(arena.allocator());
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, play_game_hbox.element());
return Response{ .arena = arena, .body = .{ .element = page.element() } };
}
pub const join_multiplayer_game = Handler{
.pointer = null,
.interface = &.{
.handle = &join_multiplayer_game_handler,
.deinit = null,
},
};
fn join_multiplayer_game_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
var arena = std.heap.ArenaAllocator.init(request.allocator);
errdefer arena.deinit();
var text = try Element.Text.create(arena.allocator(), "Joining Multiplayer Game");
var page = try Element.Page.create(arena.allocator());
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element());
return Response{ .arena = arena, .body = .{ .element = page.element() } };
}
pub const host_multiplayer_game = Handler{
.pointer = null,
.interface = &.{
.handle = &host_multiplayer_game_handler,
.deinit = null,
},
};
fn host_multiplayer_game_handler(_: ?*anyopaque, request: Request) Handler.Error!Response {
var arena = std.heap.ArenaAllocator.init(request.allocator);
errdefer arena.deinit();
var text = try Element.Text.create(arena.allocator(), "Hosting Multiplayer Game");
var page = try Element.Page.create(arena.allocator());
try page.addElement(.{ 0.5, 0.5 }, .{ 0.5, 0.5 }, text.element());
return Response{ .arena = arena, .body = .{ .element = page.element() } };
}
const Handler = protocol.Handler;
const Request = protocol.Request;
const Response = protocol.Response;
const protocol = @import("./protocol.zig");
const Element = @import("./Element.zig");
const std = @import("std");

File diff suppressed because it is too large Load Diff

31
src/protocol.zig Normal file
View File

@ -0,0 +1,31 @@
pub const Handler = struct {
pointer: ?*anyopaque,
interface: *const Interface,
pub const Error = error{OutOfMemory};
pub const Fn = *const fn (Request) Error!Response;
pub const Interface = struct {
handle: *const fn (?*anyopaque, Request) Error!Response,
deinit: ?*const fn (?*anyopaque) void,
};
};
pub const Reference = union(enum) {
handler: Handler,
};
pub const Request = struct {
allocator: std.mem.Allocator,
body: ?[]const u8 = null,
};
pub const Response = struct {
arena: std.heap.ArenaAllocator,
body: Body,
const Body = union(enum) {
element: Element,
};
};
const Element = @import("./Element.zig");
const std = @import("std");