diff --git a/assets/kenney-ui/Font/Kenney Future Narrow.ttf b/assets/kenney-ui/Font/Kenney Future Narrow.ttf new file mode 100644 index 0000000..c4cc8a0 Binary files /dev/null and b/assets/kenney-ui/Font/Kenney Future Narrow.ttf differ diff --git a/assets/kenney-ui/License.txt b/assets/kenney-ui/License.txt new file mode 100644 index 0000000..a8334da --- /dev/null +++ b/assets/kenney-ui/License.txt @@ -0,0 +1,22 @@ + + + UI Pack (2.0) + + Created/distributed by Kenney (www.kenney.nl) + Creation date: 12-06-2024 + + ------------------------------ + + License: (Creative Commons Zero, CC0) + http://creativecommons.org/publicdomain/zero/1.0/ + + This content is free to use in personal, educational and commercial projects. + Support us by crediting Kenney or www.kenney.nl (this is not mandatory) + + ------------------------------ + + Donate: http://support.kenney.nl + Patreon: http://patreon.com/kenney/ + + Follow on Twitter for updates: + http://twitter.com/KenneyNL \ No newline at end of file diff --git a/assets/roboto-font/LICENSE.txt b/assets/roboto-font/LICENSE.txt deleted file mode 100644 index 9c48e05..0000000 --- a/assets/roboto-font/LICENSE.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2011 The Roboto Project Authors (https://github.com/googlefonts/roboto-classic) - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -https://openfontlicense.org - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/assets/roboto-font/Roboto-Bold.ttf b/assets/roboto-font/Roboto-Bold.ttf deleted file mode 100644 index 9d7cf22..0000000 Binary files a/assets/roboto-font/Roboto-Bold.ttf and /dev/null differ diff --git a/assets/roboto-font/Roboto-Italic.ttf b/assets/roboto-font/Roboto-Italic.ttf deleted file mode 100644 index c3abaef..0000000 Binary files a/assets/roboto-font/Roboto-Italic.ttf and /dev/null differ diff --git a/assets/roboto-font/Roboto-Regular.ttf b/assets/roboto-font/Roboto-Regular.ttf deleted file mode 100644 index 7e3bb2f..0000000 Binary files a/assets/roboto-font/Roboto-Regular.ttf and /dev/null differ diff --git a/build.zig b/build.zig index 0fb5fc2..e1cd3d8 100644 --- a/build.zig +++ b/build.zig @@ -145,7 +145,7 @@ fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builti const asset_files = [_][]const u8{ "sokoban/sokoban_tilesheet.png", "sokoban/sokoban_tilesheet@2.png", - "roboto-font/Roboto-Regular.ttf", + "kenney-ui/Font/Kenney Future Narrow.ttf", "tiled/map.tmx", "tiled/tileset.tsx", "impact-sounds/footstep_concrete_000.ogg" diff --git a/src/app.zig b/src/app.zig index 2ae0669..26b78b1 100644 --- a/src/app.zig +++ b/src/app.zig @@ -334,14 +334,6 @@ const BoxSprites = struct { }; }; -const GameScene = struct { - world: World, - undo_stack: std.ArrayList(UndoFrame) = .empty, - current_level: u32 = 0, - animation_timer: Platform.Nanoseconds = 0, - paused: bool = false, -}; - levels: std.ArrayList(World), roboto_font: Font, @@ -355,15 +347,20 @@ footstep_sound: Audio.SoundId = .nil, sfx_bus: Audio.BusId, -game_scene: GameScene, -in_main_menu: bool = true, -main_menu_state: UI.State = .empty, +ui_state: UI.State = .empty, + +world: World, +undo_stack: std.ArrayList(UndoFrame) = .empty, +current_level: u32 = 0, +animation_timer: Platform.Nanoseconds = 0, +menu_shown: bool = false, pub fn init(self: *App, plt: Platform.Init) !?u8 { - const roboto_font = try Font.init(plt.gpa, .{ + var roboto_font = try Font.init(plt.gpa, .{ .spritesheet = Gfx.initSpritesheet(.{ .format = .r8 }), - .ttf_data = plt.assets.readFile("roboto-font/Roboto-Regular.ttf") + .ttf_data = plt.assets.readFile("kenney-ui/Font/Kenney Future Narrow.ttf") }); + roboto_font.prepareASCII(80); const tilesheet1_png = try STBImage.load(plt.assets.readFile("sokoban/sokoban_tilesheet.png")); const tilesheet2_png = try STBImage.load(plt.assets.readFile("sokoban/sokoban_tilesheet@2.png")); @@ -452,13 +449,11 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 { .sfx_bus = Audio.addBus(.{ .label = "sfx" }), .levels = .empty, - .game_scene = .{ - .world = undefined, - } + .world = undefined, }; self.levels = try self.loadLevels(plt.gpa, plt.assets); - self.game_scene.world.copy(self.levels.items[0]); + self.world.copy(self.levels.items[0]); return null; } @@ -703,29 +698,29 @@ pub fn drawWorld(self: *App, world: *World) !void { } } -pub fn frameGameScene(self: *App, plt: *Platform.Frame) !void { +pub fn frame(self: *App, plt: *Platform.Frame) !void { Gfx.setClearColor(clear_color); Gfx.setDefaultSampler(.linear); const dt = plt.deltaTime(); if (plt.input.isKeyPressed(.ESCAPE)) { - self.game_scene.paused = !self.game_scene.paused; + self.menu_shown = !self.menu_shown; } - var world = &self.game_scene.world; + var world = &self.world; - if (!self.game_scene.paused) { + if (!self.menu_shown) { const input = getPlayerInput(plt.input); if (input.undo) { - if (self.game_scene.undo_stack.pop()) |undo_frame| { + if (self.undo_stack.pop()) |undo_frame| { undo_frame.apply(world); } } if (input.restart) { - self.setWorldByLevel(self.game_scene.current_level); + self.setWorldByLevel(self.current_level); } var tick_context: TickContext = .{ .world = world }; @@ -764,14 +759,14 @@ pub fn frameGameScene(self: *App, plt: *Platform.Frame) !void { const frames = self.player_sprites.getDirection(entity.player_direction); if (target_visual_pos.sub(entity.visual_pos).length() > 0.1) { - self.game_scene.animation_timer += plt.input.delta_time; + self.animation_timer += plt.input.delta_time; const animatin_time = std.time.ns_per_s / 5; - while (self.game_scene.animation_timer >= animatin_time) { + while (self.animation_timer >= animatin_time) { entity.player_frame_index = (entity.player_frame_index + 1) % @as(u32, @intCast(frames.len)); - self.game_scene.animation_timer -= animatin_time; + self.animation_timer -= animatin_time; } } else { - self.game_scene.animation_timer = 0; + self.animation_timer = 0; entity.player_frame_index = 0; } } @@ -779,7 +774,7 @@ pub fn frameGameScene(self: *App, plt: *Platform.Frame) !void { } if (!tick_context.undo_frame.isEmpty()) { - try self.game_scene.undo_stack.append(plt.gpa, tick_context.undo_frame); + try self.undo_stack.append(plt.gpa, tick_context.undo_frame); } { @@ -804,9 +799,9 @@ pub fn frameGameScene(self: *App, plt: *Platform.Frame) !void { } } - if (goal_count > 0 and goal_count == box_at_goal_count and self.game_scene.current_level+1 < self.levels.items.len) { - self.game_scene.current_level += 1; - self.setWorldByLevel(self.game_scene.current_level); + if (goal_count > 0 and goal_count == box_at_goal_count and self.current_level+1 < self.levels.items.len) { + self.current_level += 1; + self.setWorldByLevel(self.current_level); } } } @@ -818,68 +813,61 @@ pub fn frameGameScene(self: *App, plt: *Platform.Frame) !void { canvas.push(); defer canvas.pop(clear_color); - try self.drawWorld(&self.game_scene.world); + try self.drawWorld(world); - if (self.game_scene.paused) { + if (self.menu_shown) { var color = clear_color; color.a = 0.8; Gfx.drawRectangle(.init(0, 0), canvas.size, color); } } -} -pub fn frameMainMenu(self: *App, plt: *Platform.Frame) !void { - Gfx.setClearColor(clear_color); - Gfx.setDefaultSampler(.linear); + if (self.menu_shown) { + var canvas = CanvasScaler{ + .size = Vec2.initFromInt(usize, 1920, 1080) + }; + canvas.push(); + defer canvas.pop(null); - var canvas = CanvasScaler{ - .size = Vec2.initFromInt(usize, 1920, 1080) - }; - canvas.push(); - defer canvas.pop(clear_color); + var ui = UI.init(&self.ui_state, plt.input); + ui.beginFrame(); + defer ui.endFrame(); - var ui = UI.init(&self.main_menu_state, plt.input); - ui.beginFrame(); - defer ui.endFrame(); + const ui_frame = ui.top(); + const font = &self.roboto_font; + ui_frame.font = font; + ui_frame.text_color = .black; + ui_frame.text_height = 80; + ui_frame.container = Rect{ .pos = .init(0, 0), .size = canvas.size }; + ui_frame.container = ui_frame.container.shrinkPercent(0.1); - const ui_frame = ui.top(); - const font = &self.roboto_font; - ui_frame.font = font; - ui_frame.text_color = .black; - ui_frame.text_height = 80; - ui_frame.container = Rect{ .pos = .init(0, 0), .size = canvas.size }; - ui_frame.container = ui_frame.container.shrinkPercent(0.1); + ui_frame.padding.addHorizontal(150); + ui_frame.padding.addVertical(80); - ui_frame.padding.addHorizontal(100); - ui_frame.padding.addVertical(50); + var layout = UI.VerticalLayout{ .ui = &ui, .gap = 30 }; - var layout = UI.VerticalLayout{ .ui = &ui, .gap = 30 }; + if (layout.textButton("Continue")) { + self.menu_shown = false; + } - if (layout.textButton("Play")) { - log.debug("press play", .{}); - } + if (layout.textButton("Level select")) { + self.menu_shown = false; + } - if (layout.textButton("Settings")) { - } + if (layout.textButton("Settings")) { + } - if (!builtin.target.cpu.arch.isWasm()) { - if (layout.textButton("Exit")) { - plt.request_quit = true; + if (!builtin.target.cpu.arch.isWasm()) { + if (layout.textButton("Exit")) { + plt.request_quit = true; + } } } } -pub fn frame(self: *App, plt: *Platform.Frame) !void { - if (self.in_main_menu) { - try self.frameMainMenu(plt); - } else { - try self.frameGameScene(plt); - } -} - pub fn setWorldByLevel(self: *App, index: u32) void { - self.game_scene.world.copy(self.levels.items[index]); - self.game_scene.undo_stack.clearRetainingCapacity(); + self.world.copy(self.levels.items[index]); + self.undo_stack.clearRetainingCapacity(); } pub fn deinit(self: *App, plt: Platform.Deinit) void { @@ -887,5 +875,5 @@ pub fn deinit(self: *App, plt: Platform.Deinit) void { Gfx.deinitSpritesheet(self.roboto_font.spritesheet); self.levels.deinit(plt.gpa); - self.game_scene.undo_stack.deinit(plt.gpa); + self.undo_stack.deinit(plt.gpa); } diff --git a/src/canvas_scaler.zig b/src/canvas_scaler.zig index caa53c6..2936325 100644 --- a/src/canvas_scaler.zig +++ b/src/canvas_scaler.zig @@ -36,35 +36,37 @@ pub fn push(self: Canvas) void { Gfx.transformScale(.init(scale, scale)); } -pub fn pop(self: Canvas, color: Color) void { +pub fn pop(self: Canvas, maybe_color: ?Color) void { Gfx.transformPop(); - const scale = self.getScale(); - const window_size = Gfx.getWindowSize(); - const scaled_canvas = self.size.multiplyScalar(scale); - if (window_size.x > scaled_canvas.x) { - const margin_x = (window_size.x - scaled_canvas.x) / 2; - Gfx.drawRectangle( - .init(0, 0), - .init(margin_x, window_size.y), - color - ); - Gfx.drawRectangle( - .init(window_size.x-margin_x, 0), - .init(margin_x, window_size.y), - color - ); - } else { - const margin_y = (window_size.y - scaled_canvas.y) / 2; - Gfx.drawRectangle( - .init(0, 0), - .init(window_size.x, margin_y), - color - ); - Gfx.drawRectangle( - .init(0, window_size.y-margin_y), - .init(window_size.x, margin_y), - color - ); + if (maybe_color) |color| { + const scale = self.getScale(); + const window_size = Gfx.getWindowSize(); + const scaled_canvas = self.size.multiplyScalar(scale); + if (window_size.x > scaled_canvas.x) { + const margin_x = (window_size.x - scaled_canvas.x) / 2; + Gfx.drawRectangle( + .init(0, 0), + .init(margin_x, window_size.y), + color + ); + Gfx.drawRectangle( + .init(window_size.x-margin_x, 0), + .init(margin_x, window_size.y), + color + ); + } else { + const margin_y = (window_size.y - scaled_canvas.y) / 2; + Gfx.drawRectangle( + .init(0, 0), + .init(window_size.x, margin_y), + color + ); + Gfx.drawRectangle( + .init(0, window_size.y-margin_y), + .init(window_size.x, margin_y), + color + ); + } } } diff --git a/src/font.zig b/src/font.zig index 5fb712a..d2c3d20 100644 --- a/src/font.zig +++ b/src/font.zig @@ -46,6 +46,22 @@ pub fn deinit(self: *Font, gpa: Allocator) void { self.cache.deinit(gpa); } +pub fn prepareASCII(font: *Font, height: f32) void { + font.prepareGlyphs(height, 32, 127); +} + +pub fn prepareGlyphs(font: *Font, height: f32, first_codepoint: u21, last_codepoint: u21) void { + const scale = font.stb.scaleForPixelHeight(height); + const scale_x = scale; + const scale_y = scale; + + for (first_codepoint..last_codepoint) |i| { + const codepoint: u21 = @intCast(i); + const glyph_index = font.getGlyphIndex(codepoint) orelse continue; + _ = font.getGlyph(glyph_index, scale_x, scale_y); + } +} + pub fn getGlyphIndex(font: *Font, codepoint: u21) ?Glyph.Index { if (font.stb.findGlyphIndex(codepoint)) |index| { return index; @@ -126,6 +142,12 @@ pub fn getAscent(font: *Font, height: f32) f32 { return @as(f32, @floatFromInt(vmetrics.ascent)) * scale; } +pub fn getDescent(font: *Font, height: f32) f32 { + const vmetrics = font.stb.getFontVMetrics(); + const scale = font.stb.scaleForPixelHeight(height); + return @as(f32, @floatFromInt(vmetrics.descent)) * scale; +} + pub fn getAscentDescent(font: *Font, height: f32) f32 { const vmetrics = font.stb.getFontVMetrics(); const scale = font.stb.scaleForPixelHeight(height); diff --git a/src/platform/graphics.zig b/src/platform/graphics.zig index cceb2cc..e9878dd 100644 --- a/src/platform/graphics.zig +++ b/src/platform/graphics.zig @@ -753,9 +753,7 @@ pub fn setTexture(id: Texture.Id, opts: SetTextureOptions) void { texture.image = new_image; texture.updated_at = null; - log.debug("is dynamic {f}, {}", .{id, texture.dynamic}); if (texture.dynamic) { - log.debug("update texture {f}", .{id}); sg.updateImage(new_image, image_data); texture.updated_at = self.frame.index; } diff --git a/src/ui.zig b/src/ui.zig index 631b077..caa999e 100644 --- a/src/ui.zig +++ b/src/ui.zig @@ -78,7 +78,10 @@ pub const VerticalLayout = struct { const frame = self.ui.top(); const font = frame.font orelse return false; - var button_size = font.measureText(text, frame.text_height).size; + var button_size = Vec2{ + .x = font.measureText(text, frame.text_height).size.x, + .y = font.getAscent(frame.text_height) + }; button_size = button_size.add(frame.padding.toVec2()); const row_rect = self.next(button_size.y); @@ -319,9 +322,7 @@ pub fn textButton(self: *UI, rect: Rect, text: []const u8) bool { Gfx.drawRectangle(rect.pos, rect.size, .white); } - var content_size = rect.size.sub(frame.padding.toVec2()); - content_size.x = @max(content_size.x, 0); - content_size.y = @max(content_size.y, 0); + const content_size = rect.size.sub(frame.padding.toVec2()); self.drawText( rect.pos