From 91f896c5ef3fe7a49fbe5ebabd224202291ae471 Mon Sep 17 00:00:00 2001 From: geemili Date: Mon, 26 Feb 2024 19:50:26 -0700 Subject: [PATCH] implement hovering over elements --- src/main.zig | 205 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 195 insertions(+), 10 deletions(-) diff --git a/src/main.zig b/src/main.zig index 07cd67b..7cd9fdb 100644 --- a/src/main.zig +++ b/src/main.zig @@ -132,7 +132,10 @@ pub fn main() !void { const handler = &drawCardHandler; - var selection: usize = 0; + var actions = std.ArrayList(Action).init(gpa.allocator()); + defer actions.deinit(); + + var hovered_action: ?Action = null; while (seizer.backend.glfw.c.glfwWindowShouldClose(window) != seizer.backend.glfw.c.GLFW_TRUE) { input_state = .{ @@ -143,11 +146,40 @@ pub fn main() !void { }; seizer.backend.glfw.c.glfwPollEvents(); - if (input_state.left) { - if (selection == 0) selection = game_state.hands[0].items.len; - selection = selection - 1; - } else if (input_state.right) { - selection = (selection + 1) % game_state.hands[0].items.len; + if (hovered_action == null and actions.items.len > 0) { + hovered_action = actions.items[0]; + } + + if (hovered_action) |hovered| { + if (input_state.left) { + for (actions.items) |action| { + if (action.element.pointer == hovered.element.pointer) { + continue; + } + if (action.center[0] < hovered.center[0]) { + hovered_action = Action{ + .center = .{ action.center[0], hovered.center[1] }, + .command = action.command, + .element = action.element, + }; + break; + } + } + } else if (input_state.right) { + for (actions.items) |action| { + if (action.element.pointer == hovered.element.pointer) { + continue; + } + if (action.center[0] > hovered.center[0]) { + hovered_action = Action{ + .center = .{ action.center[0], hovered.center[1] }, + .command = action.command, + .element = action.element, + }; + break; + } + } + } } if (root_element == null) { @@ -206,6 +238,7 @@ pub fn main() !void { root.pointer, &canvas, .{ + .hovered = if (hovered_action) |ha| ha.element else null, .deck = deck_sprites, }, .{ 0, 0 }, @@ -219,10 +252,35 @@ pub fn main() !void { canvas.end(); seizer.backend.glfw.c.glfwSwapBuffers(window); + + if (root_element) |root| { + actions.clearRetainingCapacity(); + try root.interface.get_actions( + root.pointer, + &actions, + .{ + .hovered = if (hovered_action) |ha| ha.element else null, + .deck = deck_sprites, + }, + .{ 0, 0 }, + .{ + @floatFromInt(window_size[0]), + @floatFromInt(window_size[1]), + }, + ); + } } } +const Action = struct { + center: [2]f32, + /// A string representing what should occur if this action is taken + command: []const u8, + element: Element, +}; + const RenderResources = struct { + hovered: ?Element, deck: DeckSprites, }; @@ -240,9 +298,14 @@ pub const Element = struct { pointer: ?*anyopaque, interface: *const Interface, + pub const Error = error{ + OutOfMemory, + }; + pub const Interface = struct { 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, }; }; @@ -292,6 +355,7 @@ pub const Page = struct { .interface = &Element.Interface{ .minimum_size = &element_minimum_size, .render = &element_render, + .get_actions = &element_get_actions, }, }; } @@ -345,6 +409,40 @@ pub const Page = struct { child.element.interface.render(child.element.pointer, canvas, render_resources, child_min, child_max); } } + + pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Action), render_resources: 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); + } + } }; pub const HBox = struct { @@ -371,6 +469,7 @@ pub const HBox = struct { .interface = &Element.Interface{ .minimum_size = &element_minimum_size, .render = &element_render, + .get_actions = &element_get_actions, }, }; } @@ -424,6 +523,40 @@ pub const HBox = struct { x += child_size[0] + space_around; } } + + pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Action), render_resources: 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; + } + } }; pub const Pile = struct { @@ -451,6 +584,7 @@ pub const Pile = struct { .interface = &Element.Interface{ .minimum_size = &element_minimum_size, .render = &element_render, + .get_actions = &element_get_actions, }, }; } @@ -483,6 +617,30 @@ pub const Pile = struct { }, .{}); } } + + if (render_resources.hovered != null and render_resources.hovered.?.pointer == @as(?*anyopaque, this)) { + 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(Action), render_resources: RenderResources, min: [2]f32, max: [2]f32) Element.Error!void { + const this: *@This() = @ptrCast(@alignCast(pointer)); + + 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 = "take", + .element = this.element(), + }); } }; @@ -511,6 +669,7 @@ pub const Spread = struct { .interface = &Element.Interface{ .minimum_size = &element_minimum_size, .render = &element_render, + .get_actions = &element_get_actions, }, }; } @@ -527,22 +686,48 @@ pub const Spread = struct { pub fn element_render(pointer: ?*anyopaque, canvas: *seizer.Canvas, render_resources: RenderResources, min: [2]f32, max: [2]f32) void { const this: *@This() = @ptrCast(@alignCast(pointer)); - const start_y = max[1] - @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[1])); - for (this.cards, 0..) |card, i| { const ox = @as(f32, @floatFromInt(i)) * @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[0])); if (this.hidden) { render_resources.deck.tilesheet.renderTile(canvas, render_resources.deck.back, .{ min[0] + ox, - start_y, + min[1], }, .{}); } else { render_resources.deck.tilesheet.renderTile(canvas, render_resources.deck.getTileForCard(card), .{ min[0] + ox, - start_y, + min[1], }, .{}); } } + + if (render_resources.hovered != null and render_resources.hovered.?.pointer == @as(?*anyopaque, this)) { + canvas.rect( + min, + .{ max[0] - min[0], max[1] - min[1] }, + .{ .color = .{ 0xAA, 0xFF, 0xAA, 0x60 } }, + ); + } + } + + pub fn element_get_actions(pointer: ?*anyopaque, actions: *std.ArrayList(Action), render_resources: RenderResources, min: [2]f32, max: [2]f32) Element.Error!void { + const this: *@This() = @ptrCast(@alignCast(pointer)); + + _ = max; + + for (this.cards, 0..) |card, i| { + const ox = @as(f32, @floatFromInt(i)) * @as(f32, @floatFromInt(render_resources.deck.tilesheet.tile_size[0])); + + _ = card; + try actions.append(.{ + .center = [2]f32{ + min[0] + ox, + min[1], + }, + .command = "mark", + .element = this.element(), + }); + } } };