diff --git a/src/main.zig b/src/main.zig index 35cea0f..e32b3e9 100644 --- a/src/main.zig +++ b/src/main.zig @@ -17,45 +17,30 @@ pub fn main() !void { raylib.SetTextureFilter(global_font.texture, raylib.TEXTURE_FILTER_BILINEAR); var background = Background.init(); - var column = Column.init( - allocator, - raylib.LoadTexture("icon-mask.png"), - raylib.LoadTexture("icon-normal-2.png"), - "Game", - ); - defer column.deinit(); + var crossmenu = CrossMenu.init(allocator); + defer crossmenu.deinit(); - var item1 = Item.init( - raylib.LoadTexture("menu/game/CometCrash/ICON0.PNG"), - "Comet Crash", - "3/1/2025 23:11", - ); - var item2 = Item.init( - raylib.LoadTexture("menu/game/LBP1/ICON0.PNG"), - "LittleBigPlanet", - "3/1/2025 23:15", - ); - var item3 = Item.init( - raylib.LoadTexture("menu/game/LBP2/ICON0.PNG"), - "LittleBigPlanet 2", - "3/1/2025 23:26", - ); - var item4 = Item.init( - raylib.LoadTexture("menu/game/LBP3/ICON0.PNG"), - "LittleBigPlanet 3", - "3/1/2025 23:48", - ); - try column.appendItem(&item1); - try column.appendItem(&item2); - try column.appendItem(&item3); - try column.appendItem(&item4); - column.refresh(false); + const game_column = try crossmenu.appendColumn(.{ + .icon = raylib.LoadTexture("icon-mask.png"), + .normal = raylib.LoadTexture("icon-normal-2.png"), + .title = "Game", + }); + + try game_column.appendItem(.{ + .icon = raylib.LoadTexture("menu/game/CometCrash/ICON0.PNG"), + .title = "Comet Crash", + .subtitle = "3/1/2025 23:11", + }); + + try game_column.appendItem(.{ + .icon = raylib.LoadTexture("menu/game/LBP1/ICON0.PNG"), + .title = "LittleBigPlanet", + .subtitle = "3/1/2025 23:15", + }); icon_shader = raylib.LoadShaderFromMemory(0, @embedFile("shaders/icon.fs")); defer raylib.UnloadShader(icon_shader); - // const mask_loc = raylib.GetShaderLocation(shader, "textureMask"); - // const normal_loc = raylib.GetShaderLocation(icon_shader, "textureNormal"); const light_dir_loc = raylib.GetShaderLocation(icon_shader, "lightDir"); const light_color_loc = raylib.GetShaderLocation(icon_shader, "lightColor"); const ambient_color_loc = raylib.GetShaderLocation(icon_shader, "ambientColor"); @@ -68,7 +53,7 @@ pub fn main() !void { raylib.SetShaderValue(icon_shader, light_dir_loc, &raylib.Vector3{ .x = 0.5, .y = 0, .z = 0 }, raylib.SHADER_UNIFORM_VEC3); raylib.SetShaderValue(icon_shader, light_color_loc, &raylib.Vector4{ .x = 1, .y = 1, .z = 1, .w = 1.5 }, raylib.SHADER_UNIFORM_VEC4); - raylib.SetShaderValue(icon_shader, ambient_color_loc, &raylib.Vector4{ .x = 0.90, .y = 0.82, .z = 0.98, .w = 0.8 }, raylib.SHADER_UNIFORM_VEC4); + raylib.SetShaderValue(icon_shader, ambient_color_loc, &raylib.Vector4{ .x = 0.94, .y = 0.97, .z = 0.98, .w = 0.8 }, raylib.SHADER_UNIFORM_VEC4); raylib.SetTargetFPS(120); while (!raylib.WindowShouldClose()) { @@ -76,31 +61,29 @@ pub fn main() !void { screen_width = @floatFromInt(raylib.GetScreenWidth()); screen_height = @floatFromInt(raylib.GetScreenHeight()); scales.recalculate(); - column.refresh(false); + crossmenu.refresh(false); } if (raylib.IsKeyPressed('D')) debug_draw = !debug_draw; if (raylib.IsKeyPressed('S')) raylib.TakeScreenshot("screenshot.png"); datetime.update(); background.update(); - column.update(); + crossmenu.update(); raylib.BeginDrawing(); defer raylib.EndDrawing(); background.draw(); - column.draw(); + crossmenu.draw(); if (debug_draw) { drawDebugGrid(); const debug_text = try std.fmt.allocPrint(allocator, \\screen size = {d}x{d} - \\selected = {d} , .{ screen_width, screen_height, - column.selected, }); defer allocator.free(debug_text); raylib.DrawText(@ptrCast(debug_text), 2, 2, 8, raylib.GREEN); @@ -245,24 +228,68 @@ fn drawDebugGrid() void { } } +pub const CrossMenu = struct { + allocator: Allocator, + columns: std.ArrayListUnmanaged(Column) = .empty, + selected: usize = 0, + + pub fn init(allocator: Allocator) CrossMenu { + return .{ .allocator = allocator }; + } + + pub fn deinit(self: *CrossMenu) void { + for (self.columns.items) |*column| column.deinit(); + self.columns.deinit(self.allocator); + } + + pub fn refresh(self: *CrossMenu, comptime animate: bool) void { + for (self.columns.items) |*column| column.refresh(animate); + } + + pub fn update(self: *CrossMenu) void { + for (self.columns.items) |*column| column.update(); + } + + pub fn draw(self: *CrossMenu) void { + for (self.columns.items) |*column| column.draw(); + } + + pub fn appendColumn(self: *CrossMenu, options: Column.Options) !*Column { + const column = try self.columns.addOne(self.allocator); + column.* = Column.init(self.allocator, options); + return column; + } +}; + // TODO item actions // TODO item groups // TODO item group sort pub const Column = struct { - icon: raylib.Texture2D, - normal: ?raylib.Texture2D = null, - title: []const u8, - - items: std.ArrayList(*Item), + items: std.ArrayList(Item), selected: usize = 0, - pub fn init(allocator: Allocator, icon: raylib.Texture2D, normal: ?raylib.Texture2D, title: []const u8) Column { - raylib.SetTextureFilter(icon, raylib.TEXTURE_FILTER_BILINEAR); + icon: Image, + title: Text, + + pub const Options = struct { + icon: raylib.Texture2D, + normal: ?raylib.Texture2D = null, + title: []const u8, + }; + + pub fn init(allocator: Allocator, options: Options) Column { + raylib.SetTextureFilter(options.icon, raylib.TEXTURE_FILTER_BILINEAR); + return .{ - .icon = icon, - .normal = normal, - .title = title, .items = .init(allocator), + .icon = .{ + .texture = options.icon, + .normal = options.normal, + }, + .title = .{ + .string = options.title, + .align_h = .center, + }, }; } @@ -271,43 +298,36 @@ pub const Column = struct { } pub fn draw(self: *Column) void { - var icon = Image{ - .texture = self.icon, - .normal = self.normal, - .box = .{ - .x = scales.column_position_center.x, - .y = scales.column_position_center.y, - .w = scales.icon_width, - .h = scales.icon_height, - }, + self.icon.box = .{ + .x = scales.column_position_center.x, + .y = scales.column_position_center.y, + .w = scales.icon_width, + .h = scales.icon_height, }; - var title = Text{ - .string = self.title, - .font_size = scales.column_title_font_size, - .font_spacing = 1, - .box = .{ - .x = icon.box.x - 8, - .y = icon.box.y + icon.box.h + 6, - .w = icon.box.w + 16, - .h = scales.column_title_font_size, - }, - .align_h = .center, + self.title.font_size = scales.column_title_font_size; + self.title.box = .{ + .x = self.icon.box.x - 8, + .y = self.icon.box.y + self.icon.box.h + 6, + .w = self.icon.box.w + 16, + .h = scales.column_title_font_size, }; - for (self.items.items) |item| item.draw(); + for (self.items.items) |*item| item.draw(); - icon.draw(); - title.draw(); + self.icon.draw(); + self.title.draw(); } - pub fn appendItem(self: *Column, item: *Item) !void { - try self.items.append(item); + pub fn appendItem(self: *Column, options: Item.Options) !void { + const item = try self.items.addOne(); + item.* = Item.init(self.items.allocator, options); self.refresh(false); } - pub fn insertItem(self: *Column, idx: usize, item: *Item) !void { - try self.items.insert(idx, item); + pub fn insertItem(self: *Column, idx: usize, options: Item.Options) !void { + const items = try self.items.addManyAt(idx, 1); + items[0] = Item.init(self.items.allocator, options); self.refresh(false); } @@ -327,13 +347,13 @@ pub const Column = struct { fn refresh(self: *Column, comptime animate: bool) void { var y = scales.column_item_before_start - (scales.icon_height + scales.column_item_spacing) * @as(f32, @floatFromInt(self.selected)); - for (self.items.items[0..self.selected]) |item| { + for (self.items.items[0..self.selected]) |*item| { item.position.set(animate, .{ .x = scales.column_position_center.x, .y = y }); item.icon_scale.set(animate, 1.0); y += scales.icon_height + scales.column_item_spacing; } y = scales.column_item_after_start; - for (self.items.items[self.selected..], self.selected..) |item, i| { + for (self.items.items[self.selected..], self.selected..) |*item, i| { const icon_scale = if (i == self.selected) scales.column_selected_item_icon_scale else 1.0; item.position.set(animate, .{ .x = scales.column_position_center.x, .y = y }); item.icon_scale.set(animate, icon_scale); @@ -347,17 +367,30 @@ pub const Item = struct { position: Tweener(raylib.Vector2, .{}) = .{}, icon_scale: Tweener(f32, 1.0) = .{}, - icon: raylib.Texture2D, - title: []const u8, - subtitle: []const u8, + icon: Image, + title: Text, + subtitle: Text, + + pub const Options = struct { + icon: raylib.Texture2D, + title: []const u8, + subtitle: []const u8, + }; + + pub fn init(_: Allocator, options: Options) Item { + raylib.SetTextureFilter(options.icon, raylib.TEXTURE_FILTER_BILINEAR); + raylib.SetTextureWrap(options.icon, raylib.TEXTURE_WRAP_CLAMP); - pub fn init(texture: raylib.Texture2D, title: []const u8, subtitle: []const u8) Item { - raylib.SetTextureFilter(texture, raylib.TEXTURE_FILTER_BILINEAR); - raylib.SetTextureWrap(texture, raylib.TEXTURE_WRAP_CLAMP); return .{ - .icon = texture, - .title = title, - .subtitle = subtitle, + .icon = .{ .texture = options.icon }, + .title = .{ + .string = options.title, + .align_v = .right, + }, + .subtitle = .{ + .string = options.subtitle, + .align_v = .left, + }, }; } @@ -365,45 +398,32 @@ pub const Item = struct { const position = self.position.update(); const icon_scale = self.icon_scale.update(); - var icon = Image{ - .texture = self.icon, - .box = .{ - .x = position.x - scales.icon_width / 2.0 * (icon_scale - 1), - .y = position.y, - .w = scales.icon_width * icon_scale, - .h = scales.icon_height * icon_scale, - }, + self.icon.box = .{ + .x = position.x - scales.icon_width / 2.0 * (icon_scale - 1), + .y = position.y, + .w = scales.icon_width * icon_scale, + .h = scales.icon_height * icon_scale, }; - var title = Text{ - .string = self.title, - .box = .{ - .x = icon.box.x + 8 + icon.box.w, - .y = icon.box.y, - .w = 300, - .h = icon.box.h / 2.0 - 2, - }, - .font_size = scales.item_title_font_size, - .font_spacing = 1, - .align_v = .right, + self.title.font_size = scales.item_title_font_size; + self.title.box = .{ + .x = self.icon.box.x + 8 + self.icon.box.w, + .y = self.icon.box.y, + .w = 300, + .h = self.icon.box.h / 2.0 - 2, }; - var subtitle = Text{ - .string = self.subtitle, - .box = .{ - .x = icon.box.x + 8 + icon.box.w, - .y = icon.box.y + icon.box.h / 2.0 + 1, - .w = 200, - .h = icon.box.h / 2.0 - 2, - }, - .font_size = scales.item_subtitle_font_size, - .font_spacing = 1, - .align_v = .left, + self.subtitle.font_size = scales.item_subtitle_font_size; + self.subtitle.box = .{ + .x = self.icon.box.x + 8 + self.icon.box.w, + .y = self.icon.box.y + self.icon.box.h / 2.0 + 1, + .w = 200, + .h = self.icon.box.h / 2.0 - 2, }; - icon.draw(); - title.draw(); - subtitle.draw(); + self.icon.draw(); + self.title.draw(); + self.subtitle.draw(); } }; @@ -603,15 +623,15 @@ pub const BoundingBox = struct { }; /// A texture within a bounding box. Positions and scales based on configurable parameters. -/// Will not crop texture if using `Mode.original`. +/// Despite the BoundingBox name, will never crop the texture, even when using `Mode.original`. pub const Image = struct { - box: BoundingBox, + box: BoundingBox = undefined, texture: raylib.Texture2D, normal: ?raylib.Texture2D = null, mode: Mode = .fit, - align_w: Align = .center, align_h: Align = .center, + align_v: Align = .center, pub fn draw(self: *Image) void { const width: f32 = @floatFromInt(self.texture.width); @@ -624,12 +644,12 @@ pub const Image = struct { .fill => @max(width_scale, height_scale), }; const position = raylib.Vector2{ - .x = switch (self.align_w) { + .x = switch (self.align_h) { .left => 0, .center => self.box.x + self.box.w / 2.0 - (width * scale) / 2.0, .right => self.box.x + self.box.w - width * scale, }, - .y = switch (self.align_h) { + .y = switch (self.align_v) { .left => 0, .center => self.box.y + self.box.h / 2.0 - (height * scale) / 2.0, .right => self.box.y + self.box.h - height * scale, @@ -661,10 +681,10 @@ pub const Image = struct { // TODO ellipsize text // TODO clip text and scroll pub const Text = struct { - box: BoundingBox, + box: BoundingBox = undefined, string: []const u8, - font_size: f32, - font_spacing: f32, + font_size: f32 = undefined, + font_spacing: f32 = 1, align_h: Align = .left, align_v: Align = .left,