From e75fc6dbe9a35f4223e8edda7bbd22cc4f14447a Mon Sep 17 00:00:00 2001 From: Marto Date: Wed, 29 Apr 2026 21:45:56 +0200 Subject: [PATCH] Implemented Entity tagged union for highlighted entity (similar to abstract class in some ways) --- src/{ => common}/constants.zig | 3 ++ src/common/structures.zig | 7 +++++ src/infrastructure/node.zig | 11 +++++-- src/infrastructure/node_manager.zig | 12 ++++++-- src/infrastructure/road.zig | 2 +- src/infrastructure/road_manager.zig | 17 ++++------ src/main.zig | 2 +- src/simulator.zig | 48 ++++++++++++++++++++++++----- 8 files changed, 77 insertions(+), 25 deletions(-) rename src/{ => common}/constants.zig (93%) create mode 100644 src/common/structures.zig diff --git a/src/constants.zig b/src/common/constants.zig similarity index 93% rename from src/constants.zig rename to src/common/constants.zig index 8ad7483..1a08460 100644 --- a/src/constants.zig +++ b/src/common/constants.zig @@ -23,3 +23,6 @@ pub const ROAD_SIZE = 20; pub const ROAD_COLOUR = clr.black; /// Colour of the road that is highlighted pub const ROAD_HIGHLIGHTED_COLOUR = clr.green; + +pub const TEXT_SIZE = 50; +pub const TEXT_COLOUR = clr.black; \ No newline at end of file diff --git a/src/common/structures.zig b/src/common/structures.zig new file mode 100644 index 0000000..39f9f0a --- /dev/null +++ b/src/common/structures.zig @@ -0,0 +1,7 @@ +const Road = @import("../infrastructure/road.zig").Road; +const Node = @import("../infrastructure/node.zig").Node; + +pub const Entity = union(enum) { + node: *Node, + road: *Road, +}; \ No newline at end of file diff --git a/src/infrastructure/node.zig b/src/infrastructure/node.zig index d46b015..7c01393 100644 --- a/src/infrastructure/node.zig +++ b/src/infrastructure/node.zig @@ -1,7 +1,7 @@ const std = @import("std"); const rl = @import("raylib"); -const c = @import("../constants.zig"); +const c = @import("../common/constants.zig"); const e = @import("../errors.zig"); const Road = @import("road.zig").Road; @@ -39,11 +39,16 @@ pub const Node = struct { rl.drawCircleV(self.pos, c.NODE_RADIUS, colour); } - /// Identifies whether the pos (location) is within the snapping radius of the node - pub fn posWithinRadius(self: *const Node, pos: rl.Vector2) bool { + /// Determines whether the pos (location) is within the snapping radius of the node + pub fn withinSnapRadius(self: *const Node, pos: rl.Vector2) bool { return rl.checkCollisionPointCircle(pos, self.pos, c.NODE_SNAP_RADIUS); } + /// Determines whether the pos (location) is within the strict (visual representation) radius of the node + pub fn withinRadius(self: *const Node, pos: rl.Vector2) bool { + return rl.checkCollisionPointCircle(pos, self.pos, c.NODE_RADIUS); + } + /// Tries to reference the passed road to self /// /// Returns an error if the passed road cannot be appended to the list or if said road is already in the list diff --git a/src/infrastructure/node_manager.zig b/src/infrastructure/node_manager.zig index 32920c7..0c8e294 100644 --- a/src/infrastructure/node_manager.zig +++ b/src/infrastructure/node_manager.zig @@ -1,7 +1,7 @@ const std = @import("std"); const Vector2 = @import("raylib").Vector2; -const c = @import("../constants.zig"); +const c = @import("../common/constants.zig"); const e = @import("../errors.zig"); const Node = @import("node.zig").Node; const Road = @import("road.zig").Road; @@ -53,7 +53,7 @@ pub const NodeManager = struct { /// otherwise creates a new node and returns its pointer pub fn getSelectedNode(self: *NodeManager, allocator: std.mem.Allocator, pos: Vector2) !*Node { for (self.nodes.items) |node| { - if (node.posWithinRadius(pos)) return node; + if (node.withinSnapRadius(pos)) return node; } // No node is within that position, so we must create a new one @@ -98,6 +98,14 @@ pub const NodeManager = struct { return e.Entity.NotFound; } + + pub fn getHighlightedNode(self: *const NodeManager, pos: Vector2) ?*Node { + for (self.nodes.items) |node| { + if (node.withinRadius(pos)) return node; + } + + return null; + } }; const expect = std.testing.expect; diff --git a/src/infrastructure/road.zig b/src/infrastructure/road.zig index 9a8ddd1..c31d632 100644 --- a/src/infrastructure/road.zig +++ b/src/infrastructure/road.zig @@ -1,6 +1,6 @@ const rl = @import("raylib"); -const c = @import("../constants.zig"); +const c = @import("../common/constants.zig"); const Node = @import("node.zig").Node; pub const Road = struct { diff --git a/src/infrastructure/road_manager.zig b/src/infrastructure/road_manager.zig index 26ab41b..874d1e1 100644 --- a/src/infrastructure/road_manager.zig +++ b/src/infrastructure/road_manager.zig @@ -7,13 +7,11 @@ const Node = @import("node.zig").Node; pub const RoadManager = struct { next_id: usize, roads: std.ArrayList(*Road), - highlighted_road: ?*Road, pub fn init() RoadManager { return .{ .next_id = 0, .roads = .empty, - .highlighted_road = null, }; } @@ -26,9 +24,9 @@ pub const RoadManager = struct { } /// Draws all the roads in the list, sends the information ahead whether the road drawn should be highlighted - pub fn draw(self: *const RoadManager, delete_mode: bool) void { + pub fn draw(self: *const RoadManager, highlighted_road: ?*Road) void { for (self.roads.items) |road| { - const is_highlighted = delete_mode and self.highlighted_road != null and self.highlighted_road.? == road; + const is_highlighted = if (highlighted_road) |h_road| road == h_road else false; road.draw(is_highlighted); } } @@ -85,16 +83,13 @@ pub const RoadManager = struct { return e.Entity.NotFound; } - /// Sets the pointer to the road that is intersection with pos coordinate - pub fn update_highlighted_road(self: *RoadManager, pos: Vector2) void { + /// Returns if pos is pointing at a road, or null if it isn't at any + pub fn getHighlightedRoad(self: *const RoadManager, pos: Vector2) ?*Road { for (self.roads.items) |road| { - if (!road.isHighlighted(pos)) continue; - - self.highlighted_road = road; - return; + if (road.isHighlighted(pos)) return road; } - self.highlighted_road = null; + return null; } }; diff --git a/src/main.zig b/src/main.zig index e199448..c76f37d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,7 +1,7 @@ const std = @import("std"); const rl = @import("raylib"); -const c = @import("constants.zig"); +const c = @import("common/constants.zig"); const Simulator = @import("simulator.zig").Simulator; pub fn main(init: std.process.Init) !void { diff --git a/src/simulator.zig b/src/simulator.zig index ce2f464..f00e852 100644 --- a/src/simulator.zig +++ b/src/simulator.zig @@ -1,7 +1,9 @@ const std = @import("std"); const rl = @import("raylib"); -const c = @import("constants.zig"); +const c = @import("common/constants.zig"); +const st = @import("common/structures.zig"); +const Road = @import("infrastructure/road.zig").Road; const NodeManager = @import("infrastructure/node_manager.zig").NodeManager; const RoadManager = @import("infrastructure/road_manager.zig").RoadManager; @@ -18,6 +20,12 @@ pub const Simulator = struct { /// Tracks whether the system will delete the road cursor is pointed at /// (in such case, the road-to-be-deleted will also be highlighted) delete_mode: bool, + /// Tracks whether highlighting all entities that are connected to hovered entity is enabled + /// + /// For example, if I hover over a node it will highlight all roads that are connected to it; + /// Same goes for hovering over a road or in the future, a car (might show destination and path to it) + show_connections: bool, + highlighted_entity: ?st.Entity, /// Constructor for convenience pub fn init(new_allocator: std.mem.Allocator) Simulator { @@ -27,6 +35,8 @@ pub const Simulator = struct { .road_man = .init(), .auto_continue = false, .delete_mode = false, + .show_connections = false, + .highlighted_entity = null, }; } @@ -40,13 +50,21 @@ pub const Simulator = struct { pub fn draw(self: *const Simulator, pos: rl.Vector2) void { rl.clearBackground(c.BACKGROUND_COLOR); - self.road_man.draw(self.delete_mode); + var highlighted_road: ?*Road = null; + + if (self.delete_mode) { + if (self.highlighted_entity) |entity| { + if (entity == .road) highlighted_road = entity.road; + } + } + + self.road_man.draw(highlighted_road); self.node_man.draw(pos); } /// Update tick pub fn update(self: *Simulator, pos: rl.Vector2) void { - self.road_man.update_highlighted_road(pos); + self.updateHighlightedEntity(pos); } /// Exposed input handling function exposed to raylib @@ -59,6 +77,7 @@ pub const Simulator = struct { fn handleKeyboardInput(self: *Simulator) void { self.auto_continue = rl.isKeyDown(.left_control); self.delete_mode = rl.isKeyDown(.left_shift); + self.show_connections = rl.isKeyDown(.left_alt); if (rl.isKeyReleased(.c)) self.clear() catch |err| { std.debug.panic("Failed to clear the entities: {}\n", .{err}); @@ -72,7 +91,7 @@ pub const Simulator = struct { /// Function that handles functionality that executes upon left click fn leftClickEvent(self: *Simulator, pos: rl.Vector2) void { - if (self.delete_mode) { + if (self.delete_mode and self.highlighted_entity != null and self.highlighted_entity.? == .road) { self.delete_road() catch |err| { std.debug.panic("Failed to delete the road: {}\n", .{err}); }; @@ -104,8 +123,9 @@ pub const Simulator = struct { /// User initiated road destroying functionality fn delete_road(self: *Simulator) !void { - if (self.road_man.highlighted_road == null) return; - const h_road = self.road_man.highlighted_road.?; + // We can trust this because this only gets called if valid and if type is road + std.debug.assert(self.highlighted_entity != null and self.highlighted_entity.? == .road); + const h_road = self.highlighted_entity.?.road; const start_node = h_road.nodes[0]; const end_node = h_road.nodes[1]; @@ -113,7 +133,6 @@ pub const Simulator = struct { self.road_man.deleteRoad(self.allocator, h_road) catch |err| { std.debug.panic("Road deletion failed: {}\n", .{err}); }; - self.road_man.highlighted_road = null; if (start_node.roads.items.len == 0) try self.node_man.deleteNode(self.allocator, start_node); @@ -127,4 +146,19 @@ pub const Simulator = struct { self.road_man.clear(self.allocator); try self.node_man.clear(self.allocator); } + + /// Updates the variable that tracks the highlighted entity + fn updateHighlightedEntity(self: *Simulator, pos: rl.Vector2) void { + if (self.node_man.getHighlightedNode(pos)) |node| { + self.highlighted_entity = .{ .node = node }; + return; + } + + if (self.road_man.getHighlightedRoad(pos)) |road| { + self.highlighted_entity = .{ .road = road }; + return; + } + + self.highlighted_entity = null; + } }; \ No newline at end of file