Compare commits
No commits in common. "fbf1d4c040ca1540e4aff85a6bc8d9b593d323d5" and "7b99f3344cf9c65278fbaa126f1087f084fa227d" have entirely different histories.
fbf1d4c040
...
7b99f3344c
Binary file not shown.
@ -1,22 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
Binary file not shown.
93
assets/roboto-font/LICENSE.txt
Normal file
93
assets/roboto-font/LICENSE.txt
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
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.
|
||||||
BIN
assets/roboto-font/Roboto-Bold.ttf
Normal file
BIN
assets/roboto-font/Roboto-Bold.ttf
Normal file
Binary file not shown.
BIN
assets/roboto-font/Roboto-Italic.ttf
Normal file
BIN
assets/roboto-font/Roboto-Italic.ttf
Normal file
Binary file not shown.
BIN
assets/roboto-font/Roboto-Regular.ttf
Normal file
BIN
assets/roboto-font/Roboto-Regular.ttf
Normal file
Binary file not shown.
32
build.zig
32
build.zig
@ -128,11 +128,7 @@ fn getPackageSuffix(target: Build.ResolvedTarget) []const u8 {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const bytes_per_kib = 1024;
|
|
||||||
const bytes_per_mib = bytes_per_kib * 1024;
|
|
||||||
|
|
||||||
const ProjectOptions = struct {
|
const ProjectOptions = struct {
|
||||||
stack_size: usize = 4 * bytes_per_mib,
|
|
||||||
imgui: ?bool = null,
|
imgui: ?bool = null,
|
||||||
assets_from_filesystem: ?bool = null,
|
assets_from_filesystem: ?bool = null,
|
||||||
tracy: ?bool = null,
|
tracy: ?bool = null,
|
||||||
@ -145,15 +141,10 @@ fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builti
|
|||||||
const asset_files = [_][]const u8{
|
const asset_files = [_][]const u8{
|
||||||
"sokoban/sokoban_tilesheet.png",
|
"sokoban/sokoban_tilesheet.png",
|
||||||
"sokoban/sokoban_tilesheet@2.png",
|
"sokoban/sokoban_tilesheet@2.png",
|
||||||
"kenney-ui/Font/Kenney Future Narrow.ttf",
|
"roboto-font/Roboto-Regular.ttf",
|
||||||
"tiled/map.tmx",
|
"tiled/map.tmx",
|
||||||
"tiled/tileset.tsx",
|
"tiled/tileset.tsx",
|
||||||
"impact-sounds/footstep_concrete_000.ogg",
|
"impact-sounds/footstep_concrete_000.ogg"
|
||||||
"kenney-ui/PNG/Red/Double/button_square_depth_flat.png",
|
|
||||||
"kenney-ui/PNG/Red/Default/button_square_depth_flat.png",
|
|
||||||
"kenney-ui/PNG/Red/Double/button_square_flat.png",
|
|
||||||
"kenney-ui/PNG/Red/Default/button_square_flat.png",
|
|
||||||
"kenney-ui/Sounds/tap-b.ogg",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const is_wasm = target.result.cpu.arch.isWasm();
|
const is_wasm = target.result.cpu.arch.isWasm();
|
||||||
@ -197,9 +188,6 @@ fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builti
|
|||||||
if (assets_from_filesystem) {
|
if (assets_from_filesystem) {
|
||||||
build_options.addOptionPathDirectory("assets_dir", assets_dir);
|
build_options.addOptionPathDirectory("assets_dir", assets_dir);
|
||||||
}
|
}
|
||||||
if (is_debug and !is_wasm) {
|
|
||||||
build_options.addOption(usize, "stack_size_limit", opts.stack_size);
|
|
||||||
}
|
|
||||||
platform_mod.addOptions("build_options", build_options);
|
platform_mod.addOptions("build_options", build_options);
|
||||||
|
|
||||||
platform_mod.addImport("embedded_assets", try createAssetsModule(b, assets_dir, &asset_files));
|
platform_mod.addImport("embedded_assets", try createAssetsModule(b, assets_dir, &asset_files));
|
||||||
@ -207,8 +195,6 @@ fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builti
|
|||||||
const math_mod = b.createModule(.{
|
const math_mod = b.createModule(.{
|
||||||
.root_source_file = b.path("src/platform/math.zig")
|
.root_source_file = b.path("src/platform/math.zig")
|
||||||
});
|
});
|
||||||
const dep_ok_color = b.dependency("ok_color", .{});
|
|
||||||
math_mod.addImport("ok_color", dep_ok_color.module("ok_color"));
|
|
||||||
platform_mod.addImport("math", math_mod);
|
platform_mod.addImport("math", math_mod);
|
||||||
|
|
||||||
dep_sokol = b.dependency("sokol", .{
|
dep_sokol = b.dependency("sokol", .{
|
||||||
@ -289,18 +275,6 @@ fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builti
|
|||||||
&(dep_sokol.artifact("sokol_clib").step)
|
&(dep_sokol.artifact("sokol_clib").step)
|
||||||
);
|
);
|
||||||
|
|
||||||
var extra_args: std.ArrayList([]const u8) = .empty;
|
|
||||||
try extra_args.append(b.allocator, "-sALLOW_MEMORY_GROWTH");
|
|
||||||
try extra_args.append(b.allocator, "-sENVIRONMENT=web");
|
|
||||||
try extra_args.append(b.allocator, b.fmt("-sSTACK_SIZE={}", .{opts.stack_size}));
|
|
||||||
|
|
||||||
if (is_debug) {
|
|
||||||
try extra_args.append(b.allocator, "-sASSERTIONS=2");
|
|
||||||
try extra_args.append(b.allocator, "-sSTACK_OVERFLOW_CHECK=2");
|
|
||||||
try extra_args.append(b.allocator, "-gsource-map");
|
|
||||||
try extra_args.append(b.allocator, "-g2");
|
|
||||||
}
|
|
||||||
|
|
||||||
const link_step = try sokol.emLinkStep(b, .{
|
const link_step = try sokol.emLinkStep(b, .{
|
||||||
.lib_main = main_lib,
|
.lib_main = main_lib,
|
||||||
.target = target,
|
.target = target,
|
||||||
@ -310,7 +284,7 @@ fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builti
|
|||||||
.use_emmalloc = true,
|
.use_emmalloc = true,
|
||||||
.use_filesystem = true,
|
.use_filesystem = true,
|
||||||
.shell_file_path = b.path("src/platform/shell.html"),
|
.shell_file_path = b.path("src/platform/shell.html"),
|
||||||
.extra_args = extra_args.items
|
.extra_args = &.{ "-sALLOW_MEMORY_GROWTH", "-sENVIRONMENT=web" }
|
||||||
});
|
});
|
||||||
|
|
||||||
return ProjectResult{
|
return ProjectResult{
|
||||||
|
|||||||
@ -15,9 +15,6 @@
|
|||||||
.stb = .{
|
.stb = .{
|
||||||
.path = "./libs/stb",
|
.path = "./libs/stb",
|
||||||
},
|
},
|
||||||
.ok_color = .{
|
|
||||||
.path = "./libs/ok_color",
|
|
||||||
},
|
|
||||||
.tiled = .{
|
.tiled = .{
|
||||||
.path = "./libs/tiled",
|
.path = "./libs/tiled",
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,37 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn build(b: *std.Build) void {
|
|
||||||
const target = b.standardTargetOptions(.{});
|
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
|
||||||
|
|
||||||
const src_dep = b.dependency("src", .{});
|
|
||||||
|
|
||||||
|
|
||||||
const mod = b.addModule("ok_color", .{
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
.root_source_file = b.path("src/root.zig"),
|
|
||||||
.link_libc = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const write_file_step = b.addWriteFiles();
|
|
||||||
|
|
||||||
const impl_c = write_file_step.add("impl.c", b.fmt("#include \"ok_color.h\"", .{}));
|
|
||||||
|
|
||||||
{
|
|
||||||
mod.addCSourceFile(.{
|
|
||||||
.file = impl_c,
|
|
||||||
.flags = &.{}
|
|
||||||
});
|
|
||||||
mod.addCMacro("OK_COLOR_IMPLEMENTATION", "");
|
|
||||||
mod.addIncludePath(src_dep.path("."));
|
|
||||||
}
|
|
||||||
|
|
||||||
const translate_c = b.addTranslateC(.{
|
|
||||||
.root_source_file = impl_c,
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
});
|
|
||||||
translate_c.addIncludePath(src_dep.path("."));
|
|
||||||
mod.addImport("c", translate_c.createModule());
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
.{
|
|
||||||
.name = .ok_color,
|
|
||||||
.version = "0.0.1",
|
|
||||||
.paths = .{"src", "build.zig", "build.zig.zon"},
|
|
||||||
.dependencies = .{
|
|
||||||
.src = .{
|
|
||||||
.url = "git+https://github.com/LievenPetersen/ok_color.git#292a6cd7ac87de1fd7d990acccc95cd740acd927",
|
|
||||||
.hash = "N-V-__8AABlQAABAjUmBALBI_rkToYvZ7xoI6nvZ-t99DeXm",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
.fingerprint = 0xb8f470b7d88e58ea,
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
|
|
||||||
const c = @import("c");
|
|
||||||
|
|
||||||
pub const RGB = c.RGB;
|
|
||||||
pub const HSV = c.HSV;
|
|
||||||
pub const Lab = c.Lab;
|
|
||||||
pub const Linear_RGB = c.Linear_RGB;
|
|
||||||
|
|
||||||
pub extern fn okhsv_to_srgb(hsv: HSV) RGB;
|
|
||||||
pub extern fn srgb_to_okhsv(rgb: RGB) HSV;
|
|
||||||
pub extern fn linear_srgb_to_oklab(linear_rgb: Linear_RGB) Lab;
|
|
||||||
pub extern fn oklab_to_linear_srgb(lab: Lab) Linear_RGB;
|
|
||||||
pub extern fn srgb_transfer_function(a: f32) f32;
|
|
||||||
pub extern fn srgb_transfer_function_inv(a: f32) f32;
|
|
||||||
465
src/app.zig
465
src/app.zig
@ -2,7 +2,6 @@ const std = @import("std");
|
|||||||
const log = std.log.scoped(.app);
|
const log = std.log.scoped(.app);
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const STBImage = @import("stb_image");
|
const STBImage = @import("stb_image");
|
||||||
const STBTrueType = @import("stb_truetype");
|
const STBTrueType = @import("stb_truetype");
|
||||||
@ -19,7 +18,6 @@ const Color = Platform.Color;
|
|||||||
const CanvasScaler = @import("./canvas_scaler.zig");
|
const CanvasScaler = @import("./canvas_scaler.zig");
|
||||||
const Font = @import("./font.zig");
|
const Font = @import("./font.zig");
|
||||||
const Tilesheet = @import("./tilesheet.zig");
|
const Tilesheet = @import("./tilesheet.zig");
|
||||||
const UI = @import("./ui.zig");
|
|
||||||
|
|
||||||
const App = @This();
|
const App = @This();
|
||||||
|
|
||||||
@ -98,6 +96,7 @@ const Entity = struct {
|
|||||||
player,
|
player,
|
||||||
box,
|
box,
|
||||||
wall,
|
wall,
|
||||||
|
coin,
|
||||||
box_hole,
|
box_hole,
|
||||||
box_goal,
|
box_goal,
|
||||||
};
|
};
|
||||||
@ -334,52 +333,37 @@ const BoxSprites = struct {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const MenuState = enum {
|
world: World,
|
||||||
main,
|
undo_stack: std.ArrayList(UndoFrame) = .empty,
|
||||||
level_select,
|
current_level: u32 = 0,
|
||||||
};
|
|
||||||
|
|
||||||
levels: std.ArrayList(World),
|
levels: std.ArrayList(World),
|
||||||
|
|
||||||
roboto_font: Font,
|
roboto_font: Font,
|
||||||
sokoban_tilesheet: Tilesheet,
|
tilesheet: Tilesheet,
|
||||||
|
|
||||||
player_sprites: PlayerSprites,
|
player_sprites: PlayerSprites,
|
||||||
button_up_sprite: Gfx.Sprite.Id,
|
|
||||||
button_down_sprite: Gfx.Sprite.Id,
|
|
||||||
box_sprites_by_color: std.EnumArray(BoxColor, BoxSprites),
|
box_sprites_by_color: std.EnumArray(BoxColor, BoxSprites),
|
||||||
|
animation_timer: Platform.Nanoseconds,
|
||||||
|
|
||||||
footstep_buffer: Audio.BufferId,
|
footstep_buffer: Audio.BufferId,
|
||||||
footstep_sound: Audio.SoundId = .nil,
|
footstep_sound: Audio.SoundId = .nil,
|
||||||
|
|
||||||
sfx_bus: Audio.BusId,
|
sfx_bus: Audio.BusId,
|
||||||
ui_bus: Audio.BusId,
|
|
||||||
click_buffers: []const Audio.BufferId,
|
|
||||||
|
|
||||||
ui_state: UI.State,
|
|
||||||
|
|
||||||
world: World,
|
|
||||||
undo_stack: std.ArrayList(UndoFrame) = .empty,
|
|
||||||
current_level: u32 = 0,
|
|
||||||
animation_timer: Platform.Nanoseconds = 0,
|
|
||||||
menu_state: ?MenuState = null,
|
|
||||||
|
|
||||||
pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
||||||
var roboto_font = try Font.init(plt.gpa, .{
|
const roboto_font = try Font.init(plt.gpa, .{
|
||||||
.spritesheet = Gfx.initSpritesheet(.{ .format = .r8 }),
|
.spritesheet = Gfx.initSpritesheet(.{ .format = .r8 }),
|
||||||
.ttf_data = plt.assets.readFile("kenney-ui/Font/Kenney Future Narrow.ttf")
|
.ttf_data = plt.assets.readFile("roboto-font/Roboto-Regular.ttf")
|
||||||
});
|
});
|
||||||
roboto_font.prepareASCII(80);
|
|
||||||
|
|
||||||
const tilesheet1_png = try STBImage.load(plt.assets.readFile("sokoban/sokoban_tilesheet.png"));
|
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"));
|
const tilesheet2_png = try STBImage.load(plt.assets.readFile("sokoban/sokoban_tilesheet@2.png"));
|
||||||
assert(tilesheet2_png.width == tilesheet1_png.width*2);
|
assert(tilesheet2_png.width == tilesheet1_png.width*2);
|
||||||
assert(tilesheet2_png.height == tilesheet1_png.height*2);
|
assert(tilesheet2_png.height == tilesheet1_png.height*2);
|
||||||
|
|
||||||
const spritesheet = Gfx.initSpritesheet(.{ .mipmaps = 2 });
|
|
||||||
|
|
||||||
var levels = [_][*]u8{ tilesheet2_png.rgba8_pixels, tilesheet1_png.rgba8_pixels };
|
var levels = [_][*]u8{ tilesheet2_png.rgba8_pixels, tilesheet1_png.rgba8_pixels };
|
||||||
const sokoban_tilesheet = try Tilesheet.init(
|
|
||||||
|
const tilesheet = try Tilesheet.init(
|
||||||
plt.arena,
|
plt.arena,
|
||||||
.init(.{
|
.init(.{
|
||||||
.width = tilesheet2_png.width,
|
.width = tilesheet2_png.width,
|
||||||
@ -390,7 +374,7 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
|||||||
.init(128, 128),
|
.init(128, 128),
|
||||||
.{
|
.{
|
||||||
.padding = 1,
|
.padding = 1,
|
||||||
.spritesheet = spritesheet
|
.spritesheet = Gfx.initSpritesheet(.{ .mipmaps = levels.len })
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -415,12 +399,10 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
|||||||
}) |entry| {
|
}) |entry| {
|
||||||
var sprite_ids = player_sprites.getDirection(entry.dir);
|
var sprite_ids = player_sprites.getDirection(entry.dir);
|
||||||
inline for (0.., entry.positions) |i, pos| {
|
inline for (0.., entry.positions) |i, pos| {
|
||||||
sprite_ids[i] = sokoban_tilesheet.get(@intFromFloat(pos.x), @intFromFloat(pos.y));
|
sprite_ids[i] = tilesheet.get(@intFromFloat(pos.x), @intFromFloat(pos.y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// button_square_depth_flat.png
|
|
||||||
|
|
||||||
const footstep_sound = Audio.addBuffer(.{
|
const footstep_sound = Audio.addBuffer(.{
|
||||||
.vorbis = plt.assets.readFile("impact-sounds/footstep_concrete_000.ogg")
|
.vorbis = plt.assets.readFile("impact-sounds/footstep_concrete_000.ogg")
|
||||||
});
|
});
|
||||||
@ -428,100 +410,48 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
|||||||
self.* = App{
|
self.* = App{
|
||||||
.player_sprites = player_sprites,
|
.player_sprites = player_sprites,
|
||||||
.roboto_font = roboto_font,
|
.roboto_font = roboto_font,
|
||||||
.sokoban_tilesheet = sokoban_tilesheet,
|
.animation_timer = 0,
|
||||||
|
.tilesheet = tilesheet,
|
||||||
.footstep_buffer = footstep_sound,
|
.footstep_buffer = footstep_sound,
|
||||||
.box_sprites_by_color = .init(.{
|
.box_sprites_by_color = .init(.{
|
||||||
.none = .nil,
|
.none = .nil,
|
||||||
.brown = .{
|
.brown = .{
|
||||||
.regular = sokoban_tilesheet.get(6, 0),
|
.regular = tilesheet.get(6, 0),
|
||||||
.darker = sokoban_tilesheet.get(6, 1),
|
.darker = tilesheet.get(6, 1),
|
||||||
.in_hole = sokoban_tilesheet.get(6, 4),
|
.in_hole = tilesheet.get(6, 4),
|
||||||
},
|
},
|
||||||
.red = .{
|
.red = .{
|
||||||
.regular = sokoban_tilesheet.get(7, 0),
|
.regular = tilesheet.get(7, 0),
|
||||||
.darker = sokoban_tilesheet.get(7, 1),
|
.darker = tilesheet.get(7, 1),
|
||||||
.in_hole = sokoban_tilesheet.get(7, 4),
|
.in_hole = tilesheet.get(7, 4),
|
||||||
},
|
},
|
||||||
.blue = .{
|
.blue = .{
|
||||||
.regular = sokoban_tilesheet.get(8, 0),
|
.regular = tilesheet.get(8, 0),
|
||||||
.darker = sokoban_tilesheet.get(8, 1),
|
.darker = tilesheet.get(8, 1),
|
||||||
.in_hole = sokoban_tilesheet.get(8, 4),
|
.in_hole = tilesheet.get(8, 4),
|
||||||
},
|
},
|
||||||
.green = .{
|
.green = .{
|
||||||
.regular = sokoban_tilesheet.get(9, 0),
|
.regular = tilesheet.get(9, 0),
|
||||||
.darker = sokoban_tilesheet.get(9, 1),
|
.darker = tilesheet.get(9, 1),
|
||||||
.in_hole = sokoban_tilesheet.get(9, 4),
|
.in_hole = tilesheet.get(9, 4),
|
||||||
},
|
},
|
||||||
.gray = .{
|
.gray = .{
|
||||||
.regular = sokoban_tilesheet.get(10, 0),
|
.regular = tilesheet.get(10, 0),
|
||||||
.darker = sokoban_tilesheet.get(10, 1),
|
.darker = tilesheet.get(10, 1),
|
||||||
.in_hole = sokoban_tilesheet.get(10, 4),
|
.in_hole = tilesheet.get(10, 4),
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
.button_up_sprite = try initSpriteFromAssets(
|
|
||||||
plt.assets,
|
|
||||||
"kenney-ui/PNG/Red/Double/button_square_depth_flat.png",
|
|
||||||
"kenney-ui/PNG/Red/Default/button_square_depth_flat.png",
|
|
||||||
.{ .padding = 1, .spritesheet = spritesheet }
|
|
||||||
),
|
|
||||||
.button_down_sprite = try initSpriteFromAssets(
|
|
||||||
plt.assets,
|
|
||||||
"kenney-ui/PNG/Red/Double/button_square_flat.png",
|
|
||||||
"kenney-ui/PNG/Red/Default/button_square_flat.png",
|
|
||||||
.{ .padding = 1, .spritesheet = spritesheet }
|
|
||||||
),
|
|
||||||
.sfx_bus = Audio.addBus(.{ .label = "sfx" }),
|
.sfx_bus = Audio.addBus(.{ .label = "sfx" }),
|
||||||
.ui_bus = Audio.addBus(.{ .label = "ui" }),
|
|
||||||
.levels = .empty,
|
|
||||||
.ui_state = try .init(plt.gpa, 0, 64), // TODO: seed
|
|
||||||
.click_buffers = try plt.arena.dupe(Audio.BufferId, &.{
|
|
||||||
Audio.addBuffer(.{
|
|
||||||
.vorbis = plt.assets.readFile("kenney-ui/Sounds/tap-b.ogg")
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
|
|
||||||
.world = undefined,
|
.world = undefined,
|
||||||
|
.levels = .empty,
|
||||||
};
|
};
|
||||||
|
|
||||||
Audio.getBus(self.ui_bus).?.volume = 0.3;
|
|
||||||
|
|
||||||
self.levels = try self.loadLevels(plt.gpa, plt.assets);
|
self.levels = try self.loadLevels(plt.gpa, plt.assets);
|
||||||
self.world.copy(self.levels.items[0]);
|
self.world.copy(self.levels.items[0]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initSpriteFromAssets(
|
|
||||||
assets: *Platform.Assets,
|
|
||||||
image0_path: []const u8,
|
|
||||||
image1_path: []const u8,
|
|
||||||
opts: Gfx.SpriteOptions
|
|
||||||
) !Gfx.Sprite.Id {
|
|
||||||
const image0_png = try STBImage.load(assets.readFile(image0_path));
|
|
||||||
defer image0_png.deinit();
|
|
||||||
|
|
||||||
const image1_png = try STBImage.load(assets.readFile(image1_path));
|
|
||||||
defer image1_png.deinit();
|
|
||||||
|
|
||||||
assert(image0_png.width == image1_png.width*2);
|
|
||||||
assert(image0_png.height == image1_png.height*2);
|
|
||||||
|
|
||||||
var levels = [_][*]u8{ image0_png.rgba8_pixels, image1_png.rgba8_pixels };
|
|
||||||
|
|
||||||
const sprite = Gfx.initSprite(opts);
|
|
||||||
|
|
||||||
Gfx.setSprite(sprite, .{
|
|
||||||
.mipchain = .init(.{
|
|
||||||
.width = image0_png.width,
|
|
||||||
.height = image0_png.height,
|
|
||||||
.format = .rgba8,
|
|
||||||
.levels = &levels,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
return sprite;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn loadLevels(self: *App, gpa: Allocator, assets: *Platform.Assets) !std.ArrayList(World) {
|
fn loadLevels(self: *App, gpa: Allocator, assets: *Platform.Assets) !std.ArrayList(World) {
|
||||||
var xml_parser = try Tiled.xml.Parser.initCapacity(gpa, .{});
|
var xml_parser = try Tiled.xml.Parser.initCapacity(gpa, .{});
|
||||||
defer xml_parser.deinit(gpa);
|
defer xml_parser.deinit(gpa);
|
||||||
@ -582,7 +512,7 @@ fn loadLevels(self: *App, gpa: Allocator, assets: *Platform.Assets) !std.ArrayLi
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
std.sort.insertion(World, levels.items, Context{}, Context.lessThan);
|
std.mem.sort(World, levels.items, Context{}, Context.lessThan);
|
||||||
|
|
||||||
return levels;
|
return levels;
|
||||||
}
|
}
|
||||||
@ -615,8 +545,7 @@ fn loadLevel(
|
|||||||
.x = @intCast(ox),
|
.x = @intCast(ox),
|
||||||
.y = @intCast(oy),
|
.y = @intCast(oy),
|
||||||
},
|
},
|
||||||
.visual_pos = .initFromInt(usize, ox, oy),
|
.sprite = self.tilesheet.get(pos.x, pos.y)
|
||||||
.sprite = self.sokoban_tilesheet.get(pos.x, pos.y)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -636,6 +565,8 @@ fn loadLevel(
|
|||||||
color = BoxColor.string_mapping.get(props_color) orelse return error.UnknownBoxColor;
|
color = BoxColor.string_mapping.get(props_color) orelse return error.UnknownBoxColor;
|
||||||
} else if (std.mem.eql(u8, props_type, "player")) {
|
} else if (std.mem.eql(u8, props_type, "player")) {
|
||||||
entity_type = .player;
|
entity_type = .player;
|
||||||
|
} else if (std.mem.eql(u8, props_type, "coin")) {
|
||||||
|
entity_type = .coin;
|
||||||
} else if (std.mem.eql(u8, props_type, "box_hole")) {
|
} else if (std.mem.eql(u8, props_type, "box_hole")) {
|
||||||
entity_type = .box_hole;
|
entity_type = .box_hole;
|
||||||
color = BoxColor.string_mapping.get(props_color) orelse .none;
|
color = BoxColor.string_mapping.get(props_color) orelse .none;
|
||||||
@ -657,7 +588,7 @@ fn loadLevel(
|
|||||||
.y = @intCast(oy),
|
.y = @intCast(oy),
|
||||||
},
|
},
|
||||||
.visual_pos = .initFromInt(usize, ox, oy),
|
.visual_pos = .initFromInt(usize, ox, oy),
|
||||||
.sprite = self.sokoban_tilesheet.get(pos.x, pos.y),
|
.sprite = self.tilesheet.get(pos.x, pos.y),
|
||||||
.color = color
|
.color = color
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -701,14 +632,116 @@ fn getPlayerInput(input: *Platform.Input) PlayerInput {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const clear_color = Color.rgb(40, 40, 40);
|
pub fn frame(self: *App, plt: Platform.Frame) !void {
|
||||||
const tile_size = Vec2.init(48, 48);
|
const input = getPlayerInput(plt.input);
|
||||||
|
|
||||||
pub fn drawWorld(self: *App, world: *World) !void {
|
const clear_color = Color.rgb(40, 40, 40);
|
||||||
inline for (.{ Entity.Layer.ground, Entity.Layer.on_ground, Entity.Layer.surface }) |layer| {
|
Gfx.setClearColor(clear_color);
|
||||||
var entity_iter = world.entities.iterator();
|
Gfx.setDefaultSampler(.linear);
|
||||||
|
|
||||||
|
const tile_size = Vec2.init(48, 48);
|
||||||
|
var canvas = CanvasScaler{
|
||||||
|
.size = Vec2.initFromInt(usize, self.world.width, self.world.height).multiply(tile_size)
|
||||||
|
};
|
||||||
|
canvas.push();
|
||||||
|
defer canvas.pop(clear_color);
|
||||||
|
|
||||||
|
const dt = plt.deltaTime();
|
||||||
|
|
||||||
|
if (input.undo) {
|
||||||
|
if (self.undo_stack.pop()) |undo_frame| {
|
||||||
|
undo_frame.apply(&self.world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.restart) {
|
||||||
|
try self.setWorldByLevel(self.current_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
var tick_context: TickContext = .{
|
||||||
|
.world = &self.world
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
var entity_iter = self.world.entities.iterator();
|
||||||
while (entity_iter.next()) |entity_id| {
|
while (entity_iter.next()) |entity_id| {
|
||||||
const entity = world.entities.getAssumeExists(entity_id);
|
var interpolate_pos = false;
|
||||||
|
|
||||||
|
const entity = self.world.entities.getAssumeExists(entity_id);
|
||||||
|
if (entity.type == .player) {
|
||||||
|
interpolate_pos = true;
|
||||||
|
|
||||||
|
const prev_player_direction = entity.player_direction;
|
||||||
|
if (tick_context.moveEntity(entity_id, input.move_dir)) {
|
||||||
|
if (entity.player_direction != prev_player_direction) {
|
||||||
|
entity.player_frame_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.footstep_sound = Audio.play(.{ .buffer = self.footstep_buffer, .bus = self.sfx_bus });
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (entity.type == .box) {
|
||||||
|
interpolate_pos = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const target_visual_pos = Vec2.initFromInt(i32, entity.pos.x, entity.pos.y);
|
||||||
|
var visual_step = target_visual_pos.sub(entity.visual_pos);
|
||||||
|
if (interpolate_pos) {
|
||||||
|
const speed = 10;
|
||||||
|
visual_step = visual_step.multiplyScalar(dt * speed);
|
||||||
|
}
|
||||||
|
entity.visual_pos = entity.visual_pos.add(visual_step);
|
||||||
|
|
||||||
|
if (entity.type == .player) {
|
||||||
|
const frames = self.player_sprites.getDirection(entity.player_direction);
|
||||||
|
|
||||||
|
if (target_visual_pos.sub(entity.visual_pos).length() > 0.1) {
|
||||||
|
self.animation_timer += plt.input.delta_time;
|
||||||
|
const animatin_time = std.time.ns_per_s / 5;
|
||||||
|
while (self.animation_timer >= animatin_time) {
|
||||||
|
entity.player_frame_index = (entity.player_frame_index + 1) % @as(u32, @intCast(frames.len));
|
||||||
|
self.animation_timer -= animatin_time;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.animation_timer = 0;
|
||||||
|
entity.player_frame_index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.sprite = frames[entity.player_frame_index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tick_context.undo_frame.isEmpty()) {
|
||||||
|
try self.undo_stack.append(plt.gpa, tick_context.undo_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
var goal_count: u32 = 0;
|
||||||
|
var box_at_goal_count: u32 = 0;
|
||||||
|
{
|
||||||
|
var goal_iter = self.world.iterEntities(.{ .type = .box_goal });
|
||||||
|
while (goal_iter.next()) |goal_id| {
|
||||||
|
const goal = self.world.entities.getAssumeExists(goal_id);
|
||||||
|
goal_count += 1;
|
||||||
|
|
||||||
|
if (self.world.findEntity(.{ .pos = goal.pos, .type = .box })) |box| {
|
||||||
|
var has_correct_color = true;
|
||||||
|
if (goal.color != .none) {
|
||||||
|
has_correct_color = goal.color == box.color;
|
||||||
|
}
|
||||||
|
|
||||||
|
const is_on_top = box.visual_pos.distance(goal.visual_pos) < 0.05;
|
||||||
|
if (is_on_top and has_correct_color) {
|
||||||
|
box_at_goal_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline for (.{ Entity.Layer.ground, Entity.Layer.on_ground, Entity.Layer.surface }) |layer| {
|
||||||
|
var entity_iter = self.world.entities.iterator();
|
||||||
|
while (entity_iter.next()) |entity_id| {
|
||||||
|
const entity = self.world.entities.getAssumeExists(entity_id);
|
||||||
if (entity.visual_layer != layer) {
|
if (entity.visual_layer != layer) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -717,14 +750,14 @@ pub fn drawWorld(self: *App, world: *World) !void {
|
|||||||
|
|
||||||
const box_hole_margin = 0.1;
|
const box_hole_margin = 0.1;
|
||||||
if (entity.type == .box) {
|
if (entity.type == .box) {
|
||||||
const box_hole_id = world.findEntityId(.{ .type = .box_hole, .pos = entity.pos });
|
const box_hole_id = self.world.findEntityId(.{ .type = .box_hole, .pos = entity.pos });
|
||||||
if (world.entities.get(box_hole_id)) |box_hole| {
|
if (self.world.entities.get(box_hole_id)) |box_hole| {
|
||||||
if (entity.box_hole_id == box_hole_id and box_hole.visual_pos.distance(entity.visual_pos) < box_hole_margin) {
|
if (entity.box_hole_id == box_hole_id and box_hole.visual_pos.distance(entity.visual_pos) < box_hole_margin) {
|
||||||
sprite = null;
|
sprite = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (world.findEntity(.{ .type = .box_goal, .pos = entity.pos })) |box_goal| {
|
if (self.world.findEntity(.{ .type = .box_goal, .pos = entity.pos })) |box_goal| {
|
||||||
if (box_goal.visual_pos.distance(entity.visual_pos) < 0.2) {
|
if (box_goal.visual_pos.distance(entity.visual_pos) < 0.2) {
|
||||||
var is_correct = true;
|
var is_correct = true;
|
||||||
if (box_goal.color != .none) {
|
if (box_goal.color != .none) {
|
||||||
@ -737,17 +770,13 @@ pub fn drawWorld(self: *App, world: *World) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (entity.type == .box_hole) {
|
} else if (entity.type == .box_hole) {
|
||||||
var box_iter = world.iterEntities(.{ .type = .box, .pos = entity.pos });
|
var box_iter = self.world.iterEntities(.{ .type = .box, .pos = entity.pos });
|
||||||
while (box_iter.next()) |box_id| {
|
while (box_iter.next()) |box_id| {
|
||||||
const box = world.entities.getAssumeExists(box_id);
|
const box = self.world.entities.getAssumeExists(box_id);
|
||||||
if (box.visual_pos.distance(entity.visual_pos) < box_hole_margin) {
|
if (box.visual_pos.distance(entity.visual_pos) < box_hole_margin) {
|
||||||
sprite = self.box_sprites_by_color.get(box.color).in_hole;
|
sprite = self.box_sprites_by_color.get(box.color).in_hole;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (entity.type == .player) {
|
|
||||||
const frames = self.player_sprites.getDirection(entity.player_direction);
|
|
||||||
|
|
||||||
sprite = frames[entity.player_frame_index];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sprite != null) {
|
if (sprite != null) {
|
||||||
@ -760,203 +789,14 @@ pub fn drawWorld(self: *App, world: *World) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn frame(self: *App, plt: *Platform.Frame) !void {
|
if (goal_count > 0 and goal_count == box_at_goal_count and self.current_level+1 < self.levels.items.len) {
|
||||||
Gfx.setClearColor(clear_color);
|
self.current_level += 1;
|
||||||
Gfx.setDefaultSampler(.linear);
|
try self.setWorldByLevel(self.current_level);
|
||||||
|
|
||||||
const dt = plt.deltaTime();
|
|
||||||
|
|
||||||
var world = &self.world;
|
|
||||||
|
|
||||||
const menu_shown = self.menu_state != null;
|
|
||||||
|
|
||||||
if (!menu_shown) {
|
|
||||||
const input = getPlayerInput(plt.input);
|
|
||||||
|
|
||||||
if (input.undo) {
|
|
||||||
if (self.undo_stack.pop()) |undo_frame| {
|
|
||||||
undo_frame.apply(world);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.restart) {
|
|
||||||
self.setWorldByLevel(self.current_level);
|
|
||||||
}
|
|
||||||
|
|
||||||
var tick_context: TickContext = .{ .world = world };
|
|
||||||
|
|
||||||
{
|
|
||||||
var entity_iter = world.entities.iterator();
|
|
||||||
while (entity_iter.next()) |entity_id| {
|
|
||||||
var interpolate_pos = false;
|
|
||||||
|
|
||||||
const entity = world.entities.getAssumeExists(entity_id);
|
|
||||||
if (entity.type == .player) {
|
|
||||||
interpolate_pos = true;
|
|
||||||
|
|
||||||
const prev_player_direction = entity.player_direction;
|
|
||||||
if (tick_context.moveEntity(entity_id, input.move_dir)) {
|
|
||||||
if (entity.player_direction != prev_player_direction) {
|
|
||||||
entity.player_frame_index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.footstep_sound = Audio.play(self.footstep_buffer, .{ .bus = self.sfx_bus });
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (entity.type == .box) {
|
|
||||||
interpolate_pos = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const target_visual_pos = Vec2.initFromInt(i32, entity.pos.x, entity.pos.y);
|
|
||||||
var visual_step = target_visual_pos.sub(entity.visual_pos);
|
|
||||||
if (interpolate_pos) {
|
|
||||||
const speed = 10;
|
|
||||||
visual_step = visual_step.multiplyScalar(dt * speed);
|
|
||||||
}
|
|
||||||
entity.visual_pos = entity.visual_pos.add(visual_step);
|
|
||||||
|
|
||||||
if (entity.type == .player) {
|
|
||||||
const frames = self.player_sprites.getDirection(entity.player_direction);
|
|
||||||
|
|
||||||
if (target_visual_pos.sub(entity.visual_pos).length() > 0.1) {
|
|
||||||
self.animation_timer += plt.input.delta_time;
|
|
||||||
const animatin_time = std.time.ns_per_s / 5;
|
|
||||||
while (self.animation_timer >= animatin_time) {
|
|
||||||
entity.player_frame_index = (entity.player_frame_index + 1) % @as(u32, @intCast(frames.len));
|
|
||||||
self.animation_timer -= animatin_time;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.animation_timer = 0;
|
|
||||||
entity.player_frame_index = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tick_context.undo_frame.isEmpty()) {
|
|
||||||
try self.undo_stack.append(plt.gpa, tick_context.undo_frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var goal_count: u32 = 0;
|
|
||||||
var box_at_goal_count: u32 = 0;
|
|
||||||
|
|
||||||
var goal_iter = world.iterEntities(.{ .type = .box_goal });
|
|
||||||
while (goal_iter.next()) |goal_id| {
|
|
||||||
const goal = world.entities.getAssumeExists(goal_id);
|
|
||||||
goal_count += 1;
|
|
||||||
|
|
||||||
if (world.findEntity(.{ .pos = goal.pos, .type = .box })) |box| {
|
|
||||||
var has_correct_color = true;
|
|
||||||
if (goal.color != .none) {
|
|
||||||
has_correct_color = goal.color == box.color;
|
|
||||||
}
|
|
||||||
|
|
||||||
const is_on_top = box.visual_pos.distance(goal.visual_pos) < 0.05;
|
|
||||||
if (is_on_top and has_correct_color) {
|
|
||||||
box_at_goal_count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var canvas = CanvasScaler{
|
|
||||||
.size = Vec2.initFromInt(usize, world.width, world.height).multiply(tile_size)
|
|
||||||
};
|
|
||||||
canvas.push();
|
|
||||||
defer canvas.pop(clear_color);
|
|
||||||
|
|
||||||
try self.drawWorld(world);
|
|
||||||
|
|
||||||
if (menu_shown) {
|
|
||||||
var color = clear_color;
|
|
||||||
color.a = 0.8;
|
|
||||||
Gfx.drawRectangle(.init(0, 0), canvas.size, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.menu_state) |menu_state| {
|
|
||||||
var canvas = CanvasScaler{
|
|
||||||
.size = Vec2.initFromInt(usize, 1920, 1080)
|
|
||||||
};
|
|
||||||
canvas.push();
|
|
||||||
defer canvas.pop(null);
|
|
||||||
|
|
||||||
var ui = UI.init(&self.ui_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 = Color.rgb(0, 0, 0);
|
|
||||||
ui_frame.button_normal_sprite = self.button_up_sprite;
|
|
||||||
ui_frame.button_down_sprite = self.button_down_sprite;
|
|
||||||
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.audio_bus = self.ui_bus;
|
|
||||||
ui_frame.button_sound = self.click_buffers;
|
|
||||||
|
|
||||||
if (menu_state == .main) {
|
|
||||||
ui_frame.padding.addHorizontal(150);
|
|
||||||
ui_frame.padding.addVertical(80);
|
|
||||||
|
|
||||||
var layout = UI.VerticalLayout{ .ui = &ui, .gap = 30 };
|
|
||||||
|
|
||||||
if (layout.textButton("Continue")) {
|
|
||||||
self.menu_state = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (layout.textButton("Level select")) {
|
|
||||||
self.menu_state = .level_select;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!builtin.target.cpu.arch.isWasm()) {
|
|
||||||
if (layout.textButton("Exit")) {
|
|
||||||
plt.request_quit = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plt.input.isKeyPressed(.ESCAPE)) {
|
|
||||||
self.menu_state = null;
|
|
||||||
}
|
|
||||||
} else if (menu_state == .level_select) {
|
|
||||||
ui_frame.padding.addHorizontal(30);
|
|
||||||
ui_frame.padding.addVertical(30);
|
|
||||||
|
|
||||||
var layout = UI.VerticalLayout{ .ui = &ui, .gap = 30 };
|
|
||||||
|
|
||||||
for (0.., self.levels.items) |i, level| {
|
|
||||||
const label = try std.fmt.allocPrint(plt.frame, "{}", .{level.level_number});
|
|
||||||
|
|
||||||
if (layout.textButton(label)) {
|
|
||||||
self.menu_state = null;
|
|
||||||
self.setWorldByLevel(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plt.input.isKeyPressed(.ESCAPE)) {
|
|
||||||
self.menu_state = .main;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (plt.input.isKeyPressed(.ESCAPE)) {
|
|
||||||
self.menu_state = .main;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setWorldByLevel(self: *App, index: usize) void {
|
pub fn setWorldByLevel(self: *App, index: u32) !void {
|
||||||
self.world.copy(self.levels.items[index]);
|
self.world.copy(self.levels.items[index]);
|
||||||
self.undo_stack.clearRetainingCapacity();
|
self.undo_stack.clearRetainingCapacity();
|
||||||
}
|
}
|
||||||
@ -967,5 +807,4 @@ pub fn deinit(self: *App, plt: Platform.Deinit) void {
|
|||||||
|
|
||||||
self.levels.deinit(plt.gpa);
|
self.levels.deinit(plt.gpa);
|
||||||
self.undo_stack.deinit(plt.gpa);
|
self.undo_stack.deinit(plt.gpa);
|
||||||
self.ui_state.deinit(plt.gpa);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,37 +36,35 @@ pub fn push(self: Canvas) void {
|
|||||||
Gfx.transformScale(.init(scale, scale));
|
Gfx.transformScale(.init(scale, scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop(self: Canvas, maybe_color: ?Color) void {
|
pub fn pop(self: Canvas, color: Color) void {
|
||||||
Gfx.transformPop();
|
Gfx.transformPop();
|
||||||
|
|
||||||
if (maybe_color) |color| {
|
const scale = self.getScale();
|
||||||
const scale = self.getScale();
|
const window_size = Gfx.getWindowSize();
|
||||||
const window_size = Gfx.getWindowSize();
|
const scaled_canvas = self.size.multiplyScalar(scale);
|
||||||
const scaled_canvas = self.size.multiplyScalar(scale);
|
if (window_size.x > scaled_canvas.x) {
|
||||||
if (window_size.x > scaled_canvas.x) {
|
const margin_x = (window_size.x - scaled_canvas.x) / 2;
|
||||||
const margin_x = (window_size.x - scaled_canvas.x) / 2;
|
Gfx.drawRectangle(
|
||||||
Gfx.drawRectangle(
|
.init(0, 0),
|
||||||
.init(0, 0),
|
.init(margin_x, window_size.y),
|
||||||
.init(margin_x, window_size.y),
|
color
|
||||||
color
|
);
|
||||||
);
|
Gfx.drawRectangle(
|
||||||
Gfx.drawRectangle(
|
.init(window_size.x-margin_x, 0),
|
||||||
.init(window_size.x-margin_x, 0),
|
.init(margin_x, window_size.y),
|
||||||
.init(margin_x, window_size.y),
|
color
|
||||||
color
|
);
|
||||||
);
|
} else {
|
||||||
} else {
|
const margin_y = (window_size.y - scaled_canvas.y) / 2;
|
||||||
const margin_y = (window_size.y - scaled_canvas.y) / 2;
|
Gfx.drawRectangle(
|
||||||
Gfx.drawRectangle(
|
.init(0, 0),
|
||||||
.init(0, 0),
|
.init(window_size.x, margin_y),
|
||||||
.init(window_size.x, margin_y),
|
color
|
||||||
color
|
);
|
||||||
);
|
Gfx.drawRectangle(
|
||||||
Gfx.drawRectangle(
|
.init(0, window_size.y-margin_y),
|
||||||
.init(0, window_size.y-margin_y),
|
.init(window_size.x, margin_y),
|
||||||
.init(window_size.x, margin_y),
|
color
|
||||||
color
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
72
src/font.zig
72
src/font.zig
@ -46,22 +46,6 @@ pub fn deinit(self: *Font, gpa: Allocator) void {
|
|||||||
self.cache.deinit(gpa);
|
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 {
|
pub fn getGlyphIndex(font: *Font, codepoint: u21) ?Glyph.Index {
|
||||||
if (font.stb.findGlyphIndex(codepoint)) |index| {
|
if (font.stb.findGlyphIndex(codepoint)) |index| {
|
||||||
return index;
|
return index;
|
||||||
@ -109,20 +93,19 @@ pub fn getGlyph(font: *Font, index: Glyph.Index, scale_x: f32, scale_y: f32) ?*F
|
|||||||
|
|
||||||
var bitmap_buffer: [Font.Glyph.max_width * Font.Glyph.max_height]u8 = undefined;
|
var bitmap_buffer: [Font.Glyph.max_width * Font.Glyph.max_height]u8 = undefined;
|
||||||
const bitmap = Gfx.Image{
|
const bitmap = Gfx.Image{
|
||||||
.pixels = (&bitmap_buffer).ptr,
|
.pixels = .{ .r8 = (&bitmap_buffer).ptr },
|
||||||
.format = .r8,
|
|
||||||
.width = box_width,
|
.width = box_width,
|
||||||
.height = box_height
|
.height = box_height
|
||||||
};
|
};
|
||||||
font.stb.makeGlyphBitmap(
|
font.stb.makeGlyphBitmap(
|
||||||
bitmap.pixels,
|
bitmap.pixels.?.r8,
|
||||||
box_width, box_height,
|
box_width, box_height,
|
||||||
box_width,
|
box_width,
|
||||||
glyph_key.scale_x.toF32(),
|
glyph_key.scale_x.toF32(),
|
||||||
glyph_key.scale_y.toF32(),
|
glyph_key.scale_y.toF32(),
|
||||||
glyph_key.index
|
glyph_key.index
|
||||||
);
|
);
|
||||||
Gfx.setSprite(sprite, .{ .mipchain = .initSingle(bitmap) });
|
Gfx.setSprite(sprite, .{ .image = bitmap });
|
||||||
|
|
||||||
glyph.sprite = sprite;
|
glyph.sprite = sprite;
|
||||||
}
|
}
|
||||||
@ -142,23 +125,9 @@ pub fn getAscent(font: *Font, height: f32) f32 {
|
|||||||
return @as(f32, @floatFromInt(vmetrics.ascent)) * scale;
|
return @as(f32, @floatFromInt(vmetrics.ascent)) * scale;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getDescent(font: *Font, height: f32) f32 {
|
pub fn measureText(font: *Font, height: f32, text: []const u8) Rect {
|
||||||
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);
|
|
||||||
return @as(f32, @floatFromInt(vmetrics.ascent - vmetrics.descent)) * scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn measureText(font: *Font, text: []const u8, height: f32) Rect {
|
|
||||||
var bounds: Rect = .init(0, 0, 0, 0);
|
var bounds: Rect = .init(0, 0, 0, 0);
|
||||||
var first_glyph = true;
|
var first_glyph = false;
|
||||||
|
|
||||||
const ascent = font.getAscent(height);
|
|
||||||
|
|
||||||
var layout = TextRunLayout.init(font, height);
|
var layout = TextRunLayout.init(font, height);
|
||||||
var iter = std.unicode.Utf8Iterator{
|
var iter = std.unicode.Utf8Iterator{
|
||||||
@ -169,14 +138,11 @@ pub fn measureText(font: *Font, text: []const u8, height: f32) Rect {
|
|||||||
const glyph_index = font.getGlyphIndex(codepoint) orelse continue;
|
const glyph_index = font.getGlyphIndex(codepoint) orelse continue;
|
||||||
const glyph_layout = layout.next(glyph_index) orelse continue;
|
const glyph_layout = layout.next(glyph_index) orelse continue;
|
||||||
|
|
||||||
var glyph_rect = glyph_layout.rect;
|
|
||||||
glyph_rect.pos.y += ascent;
|
|
||||||
|
|
||||||
if (first_glyph) {
|
if (first_glyph) {
|
||||||
bounds = glyph_rect;
|
bounds = glyph_layout.rect;
|
||||||
first_glyph = false;
|
first_glyph = false;
|
||||||
} else {
|
} else {
|
||||||
bounds = bounds.expand(glyph_rect);
|
bounds = bounds.expand(glyph_layout.rect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,13 +153,20 @@ pub const DrawTextOptions = struct {
|
|||||||
text: []const u8,
|
text: []const u8,
|
||||||
pos: Vec2,
|
pos: Vec2,
|
||||||
height: f32,
|
height: f32,
|
||||||
color: Gfx.Color,
|
color: Gfx.Color = .white,
|
||||||
|
alignment: Vec2 = .init(0, 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn drawText(font: *Font, opts: DrawTextOptions) void {
|
pub fn drawText(font: *Font, opts: DrawTextOptions) void {
|
||||||
var layout = Font.TextRunLayout.init(font, opts.height);
|
var layout = Font.TextRunLayout.init(font, opts.height);
|
||||||
|
|
||||||
const ascent = font.getAscent(opts.height);
|
var pos = opts.pos;
|
||||||
|
pos.y += font.getAscent(opts.height);
|
||||||
|
|
||||||
|
if (!opts.alignment.eql(.zero)) {
|
||||||
|
const bounds = font.measureText(opts.height, opts.text);
|
||||||
|
pos = pos.sub(bounds.size.multiply(opts.alignment));
|
||||||
|
}
|
||||||
|
|
||||||
var iter = std.unicode.Utf8Iterator{
|
var iter = std.unicode.Utf8Iterator{
|
||||||
.bytes = opts.text,
|
.bytes = opts.text,
|
||||||
@ -206,8 +179,7 @@ pub fn drawText(font: *Font, opts: DrawTextOptions) void {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var glyph_pos = glyph_layout.rect.pos.add(opts.pos);
|
var glyph_pos = glyph_layout.rect.pos.add(pos);
|
||||||
glyph_pos.y += ascent;
|
|
||||||
|
|
||||||
// Round to nearest pixel to avoid artifacts
|
// Round to nearest pixel to avoid artifacts
|
||||||
glyph_pos.x = @round(glyph_pos.x);
|
glyph_pos.x = @round(glyph_pos.x);
|
||||||
@ -344,13 +316,9 @@ const GlyphCache = struct {
|
|||||||
found_existing: bool,
|
found_existing: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn keyToHashMapIndex(self: *GlyphCache, key: Glyph.Key, offset: usize) usize {
|
|
||||||
return @intCast((key.toInt() + offset) % self.glyphs_hashmap.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insertOrGet(self: *GlyphCache, key: Glyph.Key) ?InsertOrGetResult {
|
fn insertOrGet(self: *GlyphCache, key: Glyph.Key) ?InsertOrGetResult {
|
||||||
for (0..self.glyphs_hashmap.len) |offset| {
|
for (0..self.glyphs_hashmap.len) |offset| {
|
||||||
const i = self.keyToHashMapIndex(key, offset);
|
const i = (key.toInt() + offset) % self.glyphs_hashmap.len;
|
||||||
const hashmap_id = self.glyphs_hashmap[i];
|
const hashmap_id = self.glyphs_hashmap[i];
|
||||||
if (hashmap_id == GlyphSlot.nil_id) {
|
if (hashmap_id == GlyphSlot.nil_id) {
|
||||||
const id = self.popFreeId() orelse return null;
|
const id = self.popFreeId() orelse return null;
|
||||||
@ -382,7 +350,7 @@ const GlyphCache = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn removeFromHashmap(self: *GlyphCache, id: GlyphSlot.Id) void {
|
fn removeFromHashmap(self: *GlyphCache, id: GlyphSlot.Id) void {
|
||||||
var bucket = self.keyToHashMapIndex(self.glyphs[id].glyph.key, 0);
|
var bucket = self.glyphs[id].glyph.key.toInt() % self.glyphs_hashmap.len;
|
||||||
for (0..self.glyphs_hashmap.len) |_| {
|
for (0..self.glyphs_hashmap.len) |_| {
|
||||||
assert(self.glyphs_hashmap[bucket] != GlyphSlot.nil_id);
|
assert(self.glyphs_hashmap[bucket] != GlyphSlot.nil_id);
|
||||||
if (self.glyphs_hashmap[bucket] == id) {
|
if (self.glyphs_hashmap[bucket] == id) {
|
||||||
@ -399,7 +367,7 @@ const GlyphCache = struct {
|
|||||||
|
|
||||||
while (self.glyphs_hashmap[current] != GlyphSlot.nil_id) {
|
while (self.glyphs_hashmap[current] != GlyphSlot.nil_id) {
|
||||||
const current_glyph_id = self.glyphs_hashmap[current];
|
const current_glyph_id = self.glyphs_hashmap[current];
|
||||||
const home = self.keyToHashMapIndex(self.glyphs[current_glyph_id].glyph.key, 0);
|
const home = self.glyphs[current_glyph_id].glyph.key.toInt() % self.glyphs_hashmap.len;
|
||||||
|
|
||||||
if (self.hashmapDistance(home, current) > self.hashmapDistance(home, empty)) {
|
if (self.hashmapDistance(home, current) > self.hashmapDistance(home, empty)) {
|
||||||
self.glyphs_hashmap[empty] = self.glyphs_hashmap[current];
|
self.glyphs_hashmap[empty] = self.glyphs_hashmap[current];
|
||||||
|
|||||||
@ -1,94 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const log = std.log;
|
|
||||||
|
|
||||||
const Platform = @import("platform");
|
|
||||||
const Gfx = Platform.Gfx;
|
|
||||||
const Vec2 = Platform.Vec2;
|
|
||||||
const Color = Platform.Color;
|
|
||||||
const Rect = Platform.Rect;
|
|
||||||
|
|
||||||
const NineRect = @This();
|
|
||||||
|
|
||||||
fn splitRect(rect: Rect, corner_size: Vec2) [9]Rect {
|
|
||||||
var result: [9]Rect = @splat(.zero);
|
|
||||||
|
|
||||||
// Corners
|
|
||||||
inline for (0.., .{ Vec2.init(0, 0), Vec2.init(1, 0), Vec2.init(0, 1), Vec2.init(1, 1) }) |i, corner_coeff| {
|
|
||||||
result[i] = Rect{
|
|
||||||
.pos = rect.pos
|
|
||||||
.add(rect.size.multiply(corner_coeff))
|
|
||||||
.sub(corner_size.multiply(corner_coeff)),
|
|
||||||
.size = corner_size
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Horizontal edges
|
|
||||||
result[4] = Rect{
|
|
||||||
.pos = rect.pos.add(.init(corner_size.x, 0)),
|
|
||||||
.size = .{
|
|
||||||
.x = rect.size.x - corner_size.x*2,
|
|
||||||
.y = corner_size.y
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
result[5] = Rect{
|
|
||||||
.pos = rect.pos.add(.init(corner_size.x, rect.size.y-corner_size.y)),
|
|
||||||
.size = .{
|
|
||||||
.x = rect.size.x - corner_size.x*2,
|
|
||||||
.y = corner_size.y
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Vertical edges
|
|
||||||
result[6] = Rect{
|
|
||||||
.pos = rect.pos.add(.init(0, corner_size.y)),
|
|
||||||
.size = .{
|
|
||||||
.x = corner_size.x,
|
|
||||||
.y = rect.size.y - corner_size.y*2,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
result[7] = Rect{
|
|
||||||
.pos = rect.pos.add(.init(rect.size.x-corner_size.x, corner_size.y)),
|
|
||||||
.size = .{
|
|
||||||
.x = corner_size.x,
|
|
||||||
.y = rect.size.y - corner_size.y*2,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Middle
|
|
||||||
result[8] = rect.shrink(corner_size);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(id: Gfx.Sprite.Id, pos: Vec2, size: Vec2, color: Color) void {
|
|
||||||
|
|
||||||
const spritesheet = Gfx.getSpriteOwner(id);
|
|
||||||
const sprite_rect = Rect{
|
|
||||||
.pos = Gfx.getSpritePosition(id) orelse return,
|
|
||||||
.size = Gfx.getSpriteSize(id)
|
|
||||||
};
|
|
||||||
const spritesheet_size = Gfx.getSpritesheetSize(spritesheet);
|
|
||||||
|
|
||||||
const visual_rect = Rect{ .pos = pos, .size = size };
|
|
||||||
const corner_size = Vec2{
|
|
||||||
.x = @min(@divFloor(sprite_rect.size.x, 2), size.x/2),
|
|
||||||
.y = @min(@divFloor(sprite_rect.size.y, 2), size.y/2)
|
|
||||||
};
|
|
||||||
|
|
||||||
const uv_rects = splitRect(sprite_rect, corner_size);
|
|
||||||
const visual_rects = splitRect(visual_rect, corner_size);
|
|
||||||
|
|
||||||
for (0..9) |i| {
|
|
||||||
var quad: Gfx.VertexQuad = undefined;
|
|
||||||
quad.setColor(color);
|
|
||||||
quad.setRect(visual_rects[i]);
|
|
||||||
quad.setUVRect(uv_rects[i].divide(spritesheet_size));
|
|
||||||
|
|
||||||
Gfx.draw(.{
|
|
||||||
.texture = Gfx.getSpritesheetTexture(spritesheet),
|
|
||||||
.quad = quad
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -370,13 +370,14 @@ fn busMultipliedVolume(id: BusId) f32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PlayOptions = struct {
|
const PlayOptions = struct {
|
||||||
|
buffer: BufferId,
|
||||||
// TODO: adsr: ?Sound.ADSR = null,
|
// TODO: adsr: ?Sound.ADSR = null,
|
||||||
volume: f32 = 1,
|
volume: f32 = 1,
|
||||||
loop: bool = false,
|
loop: bool = false,
|
||||||
bus: ?BusId = null
|
bus: ?BusId = null
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn play(buffer: BufferId, opts: PlayOptions) SoundId {
|
pub fn play(opts: PlayOptions) SoundId {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
|
|
||||||
var mutex = &self.thread_state.mutex;
|
var mutex = &self.thread_state.mutex;
|
||||||
@ -393,7 +394,7 @@ pub fn play(buffer: BufferId, opts: PlayOptions) SoundId {
|
|||||||
return .nil;
|
return .nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buffer == BufferId.nil) {
|
if (opts.buffer == BufferId.nil) {
|
||||||
return .nil;
|
return .nil;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -408,7 +409,7 @@ pub fn play(buffer: BufferId, opts: PlayOptions) SoundId {
|
|||||||
.cursor = 0,
|
.cursor = 0,
|
||||||
.volume = opts.volume,
|
.volume = opts.volume,
|
||||||
.loop = opts.loop,
|
.loop = opts.loop,
|
||||||
.buffer = buffer,
|
.buffer = opts.buffer,
|
||||||
.bus = bus,
|
.bus = bus,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -433,7 +434,7 @@ pub fn stop(id: SoundId) void {
|
|||||||
pub fn getBus(id: BusId) ?*Bus {
|
pub fn getBus(id: BusId) ?*Bus {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
|
|
||||||
if (id == BusId.nil) {
|
if (id == .nil) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return &self.buses.items[id.index];
|
return &self.buses.items[id.index];
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
const Math = @import("math");
|
|
||||||
const Vec4 = Math.Vec4;
|
|
||||||
|
|
||||||
const Color = @This();
|
const Color = @This();
|
||||||
|
|
||||||
r: f32,
|
r: f32,
|
||||||
@ -53,38 +50,3 @@ pub fn lerp(self: Color, other: Color, t: f32) Color {
|
|||||||
.a = std.math.lerp(self.a, other.a, t),
|
.a = std.math.lerp(self.a, other.a, t),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn invert(self: Color) Color {
|
|
||||||
return Color{
|
|
||||||
.r = 1 - self.r,
|
|
||||||
.g = 1 - self.g,
|
|
||||||
.b = 1 - self.b,
|
|
||||||
.a = self.a,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lerp_invert(self: Color, t: f32) Color {
|
|
||||||
return self.lerp(self.invert(), t);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lerp_in_oklab(self: Color, other: Color, t: f32) Color {
|
|
||||||
return Color.fromVec4(Math.rgba_lerp_in_oklab(self.toVec4(), other.toVec4(), t));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toVec4(self: Color) Vec4 {
|
|
||||||
return Vec4{
|
|
||||||
.x = self.r,
|
|
||||||
.y = self.g,
|
|
||||||
.z = self.b,
|
|
||||||
.w = self.a
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fromVec4(v4: Vec4) Color {
|
|
||||||
return Color{
|
|
||||||
.r = v4.x,
|
|
||||||
.g = v4.y,
|
|
||||||
.b = v4.z,
|
|
||||||
.a = v4.w
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@ -22,7 +22,6 @@ const SlotMapType = @import("slot_map.zig").SlotMapType;
|
|||||||
|
|
||||||
const Frame = struct {
|
const Frame = struct {
|
||||||
index: u32,
|
index: u32,
|
||||||
draw_calls: u32 = 0,
|
|
||||||
quads: std.ArrayList(VertexQuad),
|
quads: std.ArrayList(VertexQuad),
|
||||||
transforms: std.ArrayList(TransformFrame),
|
transforms: std.ArrayList(TransformFrame),
|
||||||
|
|
||||||
@ -495,8 +494,6 @@ pub fn flush() void {
|
|||||||
sg.applyUniforms(shd.UB_fs_params, sg.asRange(&fs_params));
|
sg.applyUniforms(shd.UB_fs_params, sg.asRange(&fs_params));
|
||||||
assert(self.frame.quads.items.len > 0);
|
assert(self.frame.quads.items.len > 0);
|
||||||
sg.draw(0, @intCast(self.frame.quads.items.len * 6), 1);
|
sg.draw(0, @intCast(self.frame.quads.items.len * 6), 1);
|
||||||
|
|
||||||
self.frame.draw_calls += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn endFrame() void {
|
pub fn endFrame() void {
|
||||||
@ -504,13 +501,6 @@ pub fn endFrame() void {
|
|||||||
|
|
||||||
flush();
|
flush();
|
||||||
|
|
||||||
// TODO: Workaround for when nothing is drawn.
|
|
||||||
// Just push anything so that screen is cleared
|
|
||||||
if (self.frame.draw_calls == 0) {
|
|
||||||
drawRectangle(.init(0, 0), .init(0, 0), .black);
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.frame.pass_started) {
|
if (self.frame.pass_started) {
|
||||||
sg.endPass();
|
sg.endPass();
|
||||||
sg.commit();
|
sg.commit();
|
||||||
@ -753,7 +743,9 @@ pub fn setTexture(id: Texture.Id, opts: SetTextureOptions) void {
|
|||||||
texture.image = new_image;
|
texture.image = new_image;
|
||||||
texture.updated_at = null;
|
texture.updated_at = null;
|
||||||
|
|
||||||
|
log.debug("is dynamic {f}, {}", .{id, texture.dynamic});
|
||||||
if (texture.dynamic) {
|
if (texture.dynamic) {
|
||||||
|
log.debug("update texture {f}", .{id});
|
||||||
sg.updateImage(new_image, image_data);
|
sg.updateImage(new_image, image_data);
|
||||||
texture.updated_at = self.frame.index;
|
texture.updated_at = self.frame.index;
|
||||||
}
|
}
|
||||||
@ -1178,46 +1170,19 @@ pub fn getSpriteUVRect(id: Sprite.Id) ?Rect {
|
|||||||
const sprite = self.sprites.get(id) orelse return null;
|
const sprite = self.sprites.get(id) orelse return null;
|
||||||
const sprite_mipchain = sprite.mipchain orelse return null;
|
const sprite_mipchain = sprite.mipchain orelse return null;
|
||||||
|
|
||||||
const position = getSpritePosition(id) orelse return null;
|
const spritesheet = self.spritesheets.get(sprite.spritesheet) orelse return null;
|
||||||
|
|
||||||
const spritesheet_size = getSpritesheetSize(sprite.spritesheet);
|
|
||||||
const sprite_size = Vec2.initFromInt(u32, sprite_mipchain.width, sprite_mipchain.height);
|
|
||||||
return Rect{
|
|
||||||
.pos = position.divide(spritesheet_size),
|
|
||||||
.size = sprite_size.divide(spritesheet_size),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getSpritePosition(id: Sprite.Id) ?Vec2 {
|
|
||||||
const self = &g_state;
|
|
||||||
const sprite = self.sprites.get(id) orelse return null;
|
|
||||||
|
|
||||||
if (!ensureSpritePacked(id)) {
|
if (!ensureSpritePacked(id)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(sprite.position != null);
|
assert(sprite.position != null);
|
||||||
return sprite.position;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getSpriteSize(id: Sprite.Id) Vec2 {
|
const sprite_size = Vec2.initFromInt(u32, sprite_mipchain.width, sprite_mipchain.height);
|
||||||
const self = &g_state;
|
return Rect{
|
||||||
const sprite = self.sprites.get(id) orelse return .zero;
|
.pos = sprite.position.?.divide(spritesheet.size),
|
||||||
const sprite_mipchain = sprite.mipchain orelse return .zero;
|
.size = sprite_size.divide(spritesheet.size),
|
||||||
|
};
|
||||||
return Vec2.initFromInt(u32, sprite_mipchain.width, sprite_mipchain.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getSpriteOwner(id: Sprite.Id) Spritesheet.Id {
|
|
||||||
const self = &g_state;
|
|
||||||
const sprite = self.sprites.get(id) orelse return .nil;
|
|
||||||
return sprite.spritesheet;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getSpritesheetSize(id: Spritesheet.Id) Vec2 {
|
|
||||||
const self = &g_state;
|
|
||||||
const spritesheet = self.spritesheets.get(id) orelse return .zero;
|
|
||||||
return spritesheet.size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drawSpriteQuad(id: Sprite.Id, points: Quad, color: Color) void {
|
pub fn drawSpriteQuad(id: Sprite.Id, points: Quad, color: Color) void {
|
||||||
@ -1288,12 +1253,6 @@ pub fn deinitSpritesheet(id: Spritesheet.Id) void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getSpritesheetTexture(id: Spritesheet.Id) Texture.Id {
|
|
||||||
const self = &g_state;
|
|
||||||
const spritesheet = self.spritesheets.get(id) orelse return .nil;
|
|
||||||
return spritesheet.texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Image = struct {
|
pub const Image = struct {
|
||||||
format: Image.Format,
|
format: Image.Format,
|
||||||
pixels: [*]u8,
|
pixels: [*]u8,
|
||||||
@ -1384,13 +1343,9 @@ pub const Image = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn getPixel(self: Image, x: usize, y: usize) []u8 {
|
pub fn getPixel(self: Image, x: usize, y: usize) []u8 {
|
||||||
return self.getPixelLine(x, y, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getPixelLine(self: Image, x: usize, y: usize, width: usize) []u8 {
|
|
||||||
const pixel_index = y * self.width + x;
|
const pixel_index = y * self.width + x;
|
||||||
const pixel_size = self.format.pixelSize();
|
const pixel_size = self.format.pixelSize();
|
||||||
return self.pixels[(pixel_size*pixel_index)..][0..(width*pixel_size)];
|
return self.pixels[(pixel_size*pixel_index)..][0..pixel_size];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setRect(self: Image, x: u32, y: u32, width: u32, height: u32, pixel: []const u8) void {
|
pub fn setRect(self: Image, x: u32, y: u32, width: u32, height: u32, pixel: []const u8) void {
|
||||||
@ -1480,7 +1435,6 @@ pub const Image = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn extrudeEdge(self: Image, rect: Image.Rect, size: u32) void {
|
pub fn extrudeEdge(self: Image, rect: Image.Rect, size: u32) void {
|
||||||
// Left edge
|
|
||||||
for (0..rect.width) |ox| {
|
for (0..rect.width) |ox| {
|
||||||
const pixel = self.getPixel(rect.x+ox, rect.y);
|
const pixel = self.getPixel(rect.x+ox, rect.y);
|
||||||
for (0..size) |i| {
|
for (0..size) |i| {
|
||||||
@ -1488,7 +1442,6 @@ pub const Image = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Right edge
|
|
||||||
for (0..rect.width) |ox| {
|
for (0..rect.width) |ox| {
|
||||||
const pixel = self.getPixel(rect.x+ox, rect.y+rect.height-1);
|
const pixel = self.getPixel(rect.x+ox, rect.y+rect.height-1);
|
||||||
for (0..size) |i| {
|
for (0..size) |i| {
|
||||||
@ -1496,7 +1449,6 @@ pub const Image = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Top edge
|
|
||||||
for (0..rect.height) |oy| {
|
for (0..rect.height) |oy| {
|
||||||
const pixel = self.getPixel(rect.x, rect.y+oy);
|
const pixel = self.getPixel(rect.x, rect.y+oy);
|
||||||
for (0..size) |i| {
|
for (0..size) |i| {
|
||||||
@ -1504,7 +1456,6 @@ pub const Image = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom edge
|
|
||||||
for (0..rect.height) |oy| {
|
for (0..rect.height) |oy| {
|
||||||
const pixel = self.getPixel(rect.x+rect.width-1, rect.y+oy);
|
const pixel = self.getPixel(rect.x+rect.width-1, rect.y+oy);
|
||||||
for (0..size) |i| {
|
for (0..size) |i| {
|
||||||
@ -1512,29 +1463,7 @@ pub const Image = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Top left
|
// TODO: Add padding to corners.
|
||||||
{
|
|
||||||
const pixel = self.getPixel(rect.x, rect.y);
|
|
||||||
self.setRect(rect.x-size, rect.y-size, size, size, pixel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top right
|
|
||||||
{
|
|
||||||
const pixel = self.getPixel(rect.x+rect.width-1, rect.y);
|
|
||||||
self.setRect(rect.x+rect.width, rect.y-size, size, size, pixel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bottom left
|
|
||||||
{
|
|
||||||
const pixel = self.getPixel(rect.x, rect.y+rect.height-1);
|
|
||||||
self.setRect(rect.x-size, rect.y+rect.height, size, size, pixel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bottom right
|
|
||||||
{
|
|
||||||
const pixel = self.getPixel(rect.x+rect.width-1, rect.y+rect.height-1);
|
|
||||||
self.setRect(rect.x+rect.width, rect.y+rect.height, size, size, pixel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1823,8 +1752,4 @@ pub const TransformFrame = struct {
|
|||||||
pub fn apply(self: TransformFrame, position: Vec2) Vec2 {
|
pub fn apply(self: TransformFrame, position: Vec2) Vec2 {
|
||||||
return position.multiply(self.scale).add(self.offset);
|
return position.multiply(self.scale).add(self.offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn applyInverse(self: TransformFrame, position: Vec2) Vec2 {
|
|
||||||
return position.sub(self.offset).divide(self.scale);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -98,18 +98,6 @@ pub fn getMouseDown(self: Input, button: MouseButton) ?Nanoseconds {
|
|||||||
return self.mouse_buttons.getDown(button, self.now);
|
return self.mouse_buttons.getDown(button, self.now);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn nanosecondsToSeconds(nanoseconds: Nanoseconds) f32 {
|
|
||||||
return @floatCast(@as(f64, @floatFromInt(nanoseconds)) / std.time.ns_per_s);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deltaTime(self: Input) f32 {
|
|
||||||
return nanosecondsToSeconds(self.delta_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn time(self: Input) f32 {
|
|
||||||
return nanosecondsToSeconds(self.now);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const KeyCode = enum(std.math.IntFittingRange(0, sokol.app.max_keycodes-1)) {
|
pub const KeyCode = enum(std.math.IntFittingRange(0, sokol.app.max_keycodes-1)) {
|
||||||
SPACE = 32,
|
SPACE = 32,
|
||||||
APOSTROPHE = 39,
|
APOSTROPHE = 39,
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
|
|
||||||
const ok_color = @import("ok_color");
|
|
||||||
|
|
||||||
pub const bytes_per_kib = 1024;
|
pub const bytes_per_kib = 1024;
|
||||||
pub const bytes_per_mib = bytes_per_kib * 1024;
|
pub const bytes_per_mib = bytes_per_kib * 1024;
|
||||||
pub const bytes_per_gib = bytes_per_mib * 1024;
|
pub const bytes_per_gib = bytes_per_mib * 1024;
|
||||||
@ -466,10 +464,6 @@ pub const Rect = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shrinkPercent(self: Rect, percent: f32) Rect {
|
|
||||||
return self.shrink(self.size.multiplyScalar(percent / 2));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn grow(self: Rect, margin: Vec2) Rect {
|
pub fn grow(self: Rect, margin: Vec2) Rect {
|
||||||
return self.shrink(margin.multiplyScalar(-1));
|
return self.shrink(margin.multiplyScalar(-1));
|
||||||
}
|
}
|
||||||
@ -487,103 +481,3 @@ pub fn isInsideRect(rect_pos: Vec2, rect_size: Vec2, pos: Vec2) bool {
|
|||||||
};
|
};
|
||||||
return rect.isInside(pos);
|
return rect.isInside(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rgb_lerp_in_oklab(from: Vec3, to: Vec3, t: f32) Vec3 {
|
|
||||||
const from_oklab = ok_color.linear_srgb_to_oklab(.{
|
|
||||||
.r = from.x,
|
|
||||||
.g = from.y,
|
|
||||||
.b = from.z,
|
|
||||||
});
|
|
||||||
|
|
||||||
const to_oklab = ok_color.linear_srgb_to_oklab(.{
|
|
||||||
.r = to.x,
|
|
||||||
.g = to.y,
|
|
||||||
.b = to.z,
|
|
||||||
});
|
|
||||||
|
|
||||||
const color = ok_color.oklab_to_linear_srgb(.{
|
|
||||||
.L = std.math.lerp(from_oklab.L, to_oklab.L, t),
|
|
||||||
.a = std.math.lerp(from_oklab.a, to_oklab.a, t),
|
|
||||||
.b = std.math.lerp(from_oklab.b, to_oklab.b, t)
|
|
||||||
});
|
|
||||||
|
|
||||||
return Vec3.init(color.r, color.g, color.b);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rgba_lerp_in_oklab(from: Vec4, to: Vec4, t: f32) Vec4 {
|
|
||||||
const lerped = rgb_lerp_in_oklab(from.toVec3XYZ(), to.toVec3XYZ(), t);
|
|
||||||
const alpha = std.math.lerp(from.w, to.w, t);
|
|
||||||
return Vec4.initVec3XYZ(lerped, alpha);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rgb_shift_in_okhsv(rgb_color: Vec3, hue_shift: f32, saturation_shift: f32, value_shift: f32) Vec3 {
|
|
||||||
var hsv_color = rgb_to_okhv(rgb_color);
|
|
||||||
hsv_color.x = @mod(hsv_color.x + hue_shift, 1);
|
|
||||||
hsv_color.y = std.math.clamp(hsv_color.y + saturation_shift, 0, 1);
|
|
||||||
hsv_color.z = std.math.clamp(hsv_color.z + value_shift, 0, 1);
|
|
||||||
|
|
||||||
var shifted_rgb_color = okhsv_to_rgb(hsv_color);
|
|
||||||
shifted_rgb_color = shifted_rgb_color.clamp(0, 1);
|
|
||||||
return shifted_rgb_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rgb_value_shift_in_okhsv(rgb_color: Vec3, value_shift: f32) Vec3 {
|
|
||||||
return rgb_shift_in_okhsv(rgb_color, 0, 0, value_shift);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rgba_value_shift_in_okhsv(rgba_color: Vec4, value_shift: f32) Vec4 {
|
|
||||||
const shifted = rgb_value_shift_in_okhsv(rgba_color.toVec3XYZ(), value_shift);
|
|
||||||
return Vec4.initVec3XYZ(shifted, rgba_color.w);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rgb_to_okhv(rgb_color: Vec3) Vec3 {
|
|
||||||
const srgb = linear_rgb_to_srgb(.{
|
|
||||||
.x = rgb_color.x,
|
|
||||||
.y = rgb_color.y,
|
|
||||||
.z = rgb_color.z,
|
|
||||||
});
|
|
||||||
const hsv_color = ok_color.srgb_to_okhsv(.{
|
|
||||||
.r = srgb.x,
|
|
||||||
.g = srgb.y,
|
|
||||||
.b = srgb.z,
|
|
||||||
});
|
|
||||||
return Vec3.init(hsv_color.h, hsv_color.s, hsv_color.v);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn okhsv_to_rgb(hsv_color: Vec3) Vec3 {
|
|
||||||
const srgb_color = ok_color.okhsv_to_srgb(.{
|
|
||||||
.h = hsv_color.x,
|
|
||||||
.s = hsv_color.y,
|
|
||||||
.v = hsv_color.z,
|
|
||||||
});
|
|
||||||
const rgb_color = srgb_to_linear_rgb(.{
|
|
||||||
.x = srgb_color.r,
|
|
||||||
.y = srgb_color.g,
|
|
||||||
.z = srgb_color.b,
|
|
||||||
});
|
|
||||||
return Vec3.init(rgb_color.x, rgb_color.y, rgb_color.z);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn srgb_to_linear_rgb(a: Vec3) Vec3 {
|
|
||||||
return Vec3.init(
|
|
||||||
ok_color.srgb_transfer_function_inv(a.x),
|
|
||||||
ok_color.srgb_transfer_function_inv(a.y),
|
|
||||||
ok_color.srgb_transfer_function_inv(a.z)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn linear_rgb_to_srgb(a: Vec3) Vec3 {
|
|
||||||
return Vec3.init(
|
|
||||||
ok_color.srgb_transfer_function(a.x),
|
|
||||||
ok_color.srgb_transfer_function(a.y),
|
|
||||||
ok_color.srgb_transfer_function(a.z),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ease_out_back(t: f32) f32 {
|
|
||||||
const c1 = 1.70158;
|
|
||||||
const c3 = c1 + 1.0;
|
|
||||||
|
|
||||||
const pow = std.math.pow;
|
|
||||||
return 1.0 + c3 * pow(f32, t - 1.0, 3) + c1 * pow(f32, t - 1.0, 2);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -105,12 +105,10 @@ fn PlatformType(App: type) type {
|
|||||||
|
|
||||||
_ = self.frame_arena.reset(.free_all); // TODO: make arena reset mode configurable
|
_ = self.frame_arena.reset(.free_all); // TODO: make arena reset mode configurable
|
||||||
|
|
||||||
{
|
const now = Io.Timestamp.now(self.io, .awake);
|
||||||
const now = Io.Timestamp.now(self.io, .awake);
|
self.input.now = @intCast(self.started_at.durationTo(now).nanoseconds);
|
||||||
self.input.now = @intCast(self.started_at.durationTo(now).nanoseconds);
|
self.input.delta_time = @intCast(self.last_frame_at.durationTo(now).nanoseconds);
|
||||||
self.input.delta_time = @intCast(self.last_frame_at.durationTo(now).nanoseconds);
|
self.last_frame_at = now;
|
||||||
self.last_frame_at = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.input.keyboard.pressed.contains(.F4)) {
|
if (self.input.keyboard.pressed.contains(.F4)) {
|
||||||
self.show_imgui = !self.show_imgui;
|
self.show_imgui = !self.show_imgui;
|
||||||
@ -128,21 +126,16 @@ fn PlatformType(App: type) type {
|
|||||||
ImGUI.beginFrame(self.show_imgui, self.frame_arena.allocator());
|
ImGUI.beginFrame(self.show_imgui, self.frame_arena.allocator());
|
||||||
|
|
||||||
if (@hasDecl(App, "frame")) {
|
if (@hasDecl(App, "frame")) {
|
||||||
var plt = Frame{
|
const plt = Frame{
|
||||||
.gpa = self.gpa,
|
.gpa = self.gpa,
|
||||||
.arena = self.arena.allocator(),
|
.arena = self.arena.allocator(),
|
||||||
.io = self.io,
|
.io = self.io,
|
||||||
.input = &self.input,
|
.input = &self.input,
|
||||||
.input_events = self.events.items,
|
.input_events = self.events.items,
|
||||||
.frame = self.frame_arena.allocator(),
|
.frame = self.frame_arena.allocator(),
|
||||||
.assets = &self.assets,
|
.assets = &self.assets
|
||||||
.request_quit = false
|
|
||||||
};
|
};
|
||||||
try self.app.frame(&plt);
|
try self.app.frame(plt);
|
||||||
|
|
||||||
if (plt.request_quit) {
|
|
||||||
sapp.requestQuit();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Gfx.flush();
|
Gfx.flush();
|
||||||
@ -183,14 +176,6 @@ fn PlatformType(App: type) type {
|
|||||||
self.input.mouse_buttons.pressed = .empty;
|
self.input.mouse_buttons.pressed = .empty;
|
||||||
self.input.mouse_buttons.released = .empty;
|
self.input.mouse_buttons.released = .empty;
|
||||||
self.input.mouse_scroll = .init(0, 0);
|
self.input.mouse_scroll = .init(0, 0);
|
||||||
|
|
||||||
if (Gfx.getFrameIndex() == 1) {
|
|
||||||
const now = Io.Timestamp.now(self.io, .awake);
|
|
||||||
const time_to_first_frame = self.started_at.durationTo(now);
|
|
||||||
if (time_to_first_frame.nanoseconds > 100 * std.time.ns_per_ms) {
|
|
||||||
log.warn("Time to first frame is long, {f}", .{time_to_first_frame});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sokolFrame(user_data: ?*anyopaque) callconv(.c) void {
|
fn sokolFrame(user_data: ?*anyopaque) callconv(.c) void {
|
||||||
@ -618,18 +603,21 @@ pub const Frame = struct {
|
|||||||
assets: *Assets,
|
assets: *Assets,
|
||||||
input: *Input,
|
input: *Input,
|
||||||
input_events: []Input.Event,
|
input_events: []Input.Event,
|
||||||
request_quit: bool,
|
|
||||||
|
fn nanosecondsToSeconds(nanoseconds: Nanoseconds) f32 {
|
||||||
|
return @floatCast(@as(f64, @floatFromInt(nanoseconds)) / std.time.ns_per_s);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deltaTime(self: Frame) f32 {
|
pub fn deltaTime(self: Frame) f32 {
|
||||||
return self.input.deltaTime();
|
return nanosecondsToSeconds(self.input.delta_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn time(self: Frame) f32 {
|
pub fn time(self: Frame) f32 {
|
||||||
return self.input.time();
|
return nanosecondsToSeconds(self.input.now);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fn runThread(App: type, opts: RunOptions) void {
|
pub fn run(App: type, opts: RunOptions) void {
|
||||||
const gpa = opts.init.gpa;
|
const gpa = opts.init.gpa;
|
||||||
const io = opts.init.io;
|
const io = opts.init.io;
|
||||||
|
|
||||||
@ -712,19 +700,6 @@ fn runThread(App: type, opts: RunOptions) void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(App: type, opts: RunOptions) void {
|
|
||||||
if (@hasDecl(build_options, "stack_size_limit")) {
|
|
||||||
var t = std.Thread.spawn(
|
|
||||||
.{ .stack_size = build_options.stack_size_limit },
|
|
||||||
runThread,
|
|
||||||
.{App, opts}
|
|
||||||
) catch @panic("Failed to launch thread for limiting stack size");
|
|
||||||
t.join();
|
|
||||||
} else {
|
|
||||||
runThread(App, opts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn posixSignalHandler(sig: std.posix.SIG) callconv(.c) void {
|
fn posixSignalHandler(sig: std.posix.SIG) callconv(.c) void {
|
||||||
_ = sig;
|
_ = sig;
|
||||||
sapp.requestQuit();
|
sapp.requestQuit();
|
||||||
|
|||||||
@ -40,8 +40,7 @@ void main() {
|
|||||||
if (texture_mode == 0) {
|
if (texture_mode == 0) {
|
||||||
frag_color = tex_color * color;
|
frag_color = tex_color * color;
|
||||||
} else if (texture_mode == 1) {
|
} else if (texture_mode == 1) {
|
||||||
float opacity = tex_color.r;
|
frag_color = vec4(1, 1, 1, tex_color.r) * color;
|
||||||
frag_color = vec4(opacity, opacity, opacity, opacity) * color;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|||||||
443
src/ui.zig
443
src/ui.zig
@ -1,443 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const log = std.log;
|
|
||||||
const assert = std.debug.assert;
|
|
||||||
const Allocator = std.mem.Allocator;
|
|
||||||
const Font = @import("./font.zig");
|
|
||||||
const NineRect = @import("./nine_rect.zig");
|
|
||||||
|
|
||||||
const Platform = @import("platform");
|
|
||||||
const Gfx = Platform.Gfx;
|
|
||||||
const Audio = Platform.Audio;
|
|
||||||
const Math = Platform.Math;
|
|
||||||
const Color = Platform.Color;
|
|
||||||
const Vec2 = Platform.Vec2;
|
|
||||||
const Rect = Math.Rect;
|
|
||||||
|
|
||||||
const UI = @This();
|
|
||||||
|
|
||||||
pub const Key = enum(u64) {
|
|
||||||
_,
|
|
||||||
|
|
||||||
const StringHasher = std.hash.Wyhash;
|
|
||||||
pub const CombineHasher = std.hash.Fnv1a_64;
|
|
||||||
|
|
||||||
pub const nil = Key.init(0);
|
|
||||||
|
|
||||||
pub fn init(hash: u64) Key {
|
|
||||||
return @enumFromInt(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn initPtr(ptr: anytype) Key {
|
|
||||||
return Key.initUsize(@intFromPtr(ptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn initUsize(num: usize) Key {
|
|
||||||
return Key.init(@truncate(num));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn initString(seed: u64, text: []const u8) Key {
|
|
||||||
return Key.init(StringHasher.hash(seed, text));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn combine(self: Key, other: Key) Key {
|
|
||||||
var hasher = CombineHasher.init();
|
|
||||||
hasher.update(std.mem.asBytes(&@intFromEnum(self)));
|
|
||||||
hasher.update(std.mem.asBytes(&@intFromEnum(other)));
|
|
||||||
|
|
||||||
return Key.init(hasher.final());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn isNil(self: Key) bool {
|
|
||||||
return self == nil;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format(self: Key, writer: *std.io.Writer) std.io.Writer.Error!void {
|
|
||||||
try writer.print("{s}{{ 0x{x:016} }}", .{ @typeName(Key), @intFromEnum(self) });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const VerticalLayout = struct {
|
|
||||||
ui: *UI,
|
|
||||||
used: f32 = 0,
|
|
||||||
gap: f32 = 0,
|
|
||||||
|
|
||||||
pub fn advance(self: *VerticalLayout, height: f32) void {
|
|
||||||
self.used += height;
|
|
||||||
self.used += self.gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(self: *VerticalLayout, height: f32) Rect {
|
|
||||||
const container = self.ui.top().container;
|
|
||||||
const result = Rect{
|
|
||||||
.pos = container.pos.add(.init(0, self.used)),
|
|
||||||
.size = .init(container.size.x, height)
|
|
||||||
};
|
|
||||||
|
|
||||||
self.advance(height);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn textButton(self: *VerticalLayout, text: []const u8) bool {
|
|
||||||
const frame = self.ui.top();
|
|
||||||
const font = frame.font orelse return false;
|
|
||||||
|
|
||||||
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);
|
|
||||||
const button_rect = Rect{
|
|
||||||
.pos = row_rect.pos,
|
|
||||||
.size = button_size
|
|
||||||
};
|
|
||||||
|
|
||||||
return self.ui.textButton(button_rect, text);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Sides = struct {
|
|
||||||
left: f32,
|
|
||||||
right: f32,
|
|
||||||
top: f32,
|
|
||||||
bottom: f32,
|
|
||||||
|
|
||||||
pub const empty = Sides{
|
|
||||||
.left = 0,
|
|
||||||
.right = 0,
|
|
||||||
.top = 0,
|
|
||||||
.bottom = 0
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn grow(self: Sides, rect: Rect) Rect {
|
|
||||||
return Rect{
|
|
||||||
.pos = rect.pos.sub(.init(self.left, self.top)),
|
|
||||||
.size = rect.size.add(.init(self.left+self.right, self.top+self.bottom))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn shrink(self: Sides, rect: Rect) Rect {
|
|
||||||
return Rect{
|
|
||||||
.pos = rect.pos.add(.init(self.left, self.top)),
|
|
||||||
.size = rect.size.sub(.init(self.left+self.right, self.top+self.bottom))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(lhs: Sides, rhs: Sides) Sides {
|
|
||||||
return Sides{
|
|
||||||
.left = lhs.left + rhs.left,
|
|
||||||
.right = lhs.right + rhs.right,
|
|
||||||
.top = lhs.top + rhs.top,
|
|
||||||
.bottom = lhs.bottom + rhs.bottom,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn initVertical(amount: f32) Sides {
|
|
||||||
return Sides{
|
|
||||||
.top = amount/2,
|
|
||||||
.bottom = amount/2,
|
|
||||||
.left = 0,
|
|
||||||
.right = 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn initHorizontal(amount: f32) Sides {
|
|
||||||
return Sides{
|
|
||||||
.left = amount/2,
|
|
||||||
.right = amount/2,
|
|
||||||
.top = 0,
|
|
||||||
.bottom = 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn vertical(self: Sides) f32 {
|
|
||||||
return self.top + self.bottom;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn horizontal(self: Sides) f32 {
|
|
||||||
return self.left + self.right;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addVertical(self: *Sides, amount: f32) void {
|
|
||||||
self.* = self.add(.initVertical(amount));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn addHorizontal(self: *Sides, amount: f32) void {
|
|
||||||
self.* = self.add(.initHorizontal(amount));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toVec2(self: Sides) Vec2 {
|
|
||||||
return Vec2.init(self.horizontal(), self.vertical());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Frame = struct {
|
|
||||||
container: Rect = .zero,
|
|
||||||
button_normal_sprite: Gfx.Sprite.Id = .nil,
|
|
||||||
button_down_sprite: Gfx.Sprite.Id = .nil,
|
|
||||||
button_sound: []const Audio.BufferId = &.{},
|
|
||||||
key: Key = .nil,
|
|
||||||
font: ?*Font = null,
|
|
||||||
text_color: Color = .white,
|
|
||||||
text_height: f32 = 64,
|
|
||||||
margin: Sides = .empty,
|
|
||||||
padding: Sides = .empty,
|
|
||||||
audio_bus: Audio.BusId = .nil
|
|
||||||
};
|
|
||||||
|
|
||||||
const Box = struct {
|
|
||||||
used: bool = false,
|
|
||||||
hot_t: f32 = 0,
|
|
||||||
hot_rising_edge_t: f32 = 0,
|
|
||||||
active_t: f32 = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const State = struct {
|
|
||||||
hot: Key,
|
|
||||||
active: Key,
|
|
||||||
|
|
||||||
boxes: std.array_hash_map.Auto(Key, Box),
|
|
||||||
rng_state: std.Random.DefaultPrng,
|
|
||||||
|
|
||||||
pub fn init(gpa: Allocator, seed: u64, max_boxes: usize) !State {
|
|
||||||
assert(max_boxes > 0);
|
|
||||||
|
|
||||||
var self = State{
|
|
||||||
.hot = .nil,
|
|
||||||
.active = .nil,
|
|
||||||
.boxes = .empty,
|
|
||||||
.rng_state = .init(seed)
|
|
||||||
};
|
|
||||||
try self.boxes.ensureTotalCapacity(gpa, max_boxes);
|
|
||||||
self.boxes.putAssumeCapacity(.nil, .{});
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *State, gpa: Allocator) void {
|
|
||||||
self.boxes.deinit(gpa);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
state: *State,
|
|
||||||
input: *Platform.Input,
|
|
||||||
stack: [32]Frame,
|
|
||||||
stack_len: u32,
|
|
||||||
|
|
||||||
pub fn init(state: *State, input: *Platform.Input) UI {
|
|
||||||
var stack: [32]Frame = undefined;
|
|
||||||
stack[0] = Frame{};
|
|
||||||
|
|
||||||
return UI{
|
|
||||||
.state = state,
|
|
||||||
.input = input,
|
|
||||||
.stack = stack,
|
|
||||||
.stack_len = 1
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn beginFrame(self: *UI) void {
|
|
||||||
{
|
|
||||||
var i: usize = 0;
|
|
||||||
while (i < self.state.boxes.count()) {
|
|
||||||
const entry = self.state.boxes.entries.get(i);
|
|
||||||
|
|
||||||
if (!entry.value.used and entry.key != Key.nil) {
|
|
||||||
self.state.boxes.swapRemoveAt(i);
|
|
||||||
} else {
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const dt = self.input.deltaTime();
|
|
||||||
const rate = 1 - std.math.pow(f32, 2, -45 * dt);
|
|
||||||
|
|
||||||
for (self.state.boxes.keys()) |key| {
|
|
||||||
if (key == Key.nil) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const box = self.state.boxes.getEntry(key).?.value_ptr;
|
|
||||||
box.used = false;
|
|
||||||
|
|
||||||
const is_hot_f32: f32 = @floatFromInt(@intFromBool(self.state.hot == key));
|
|
||||||
const is_active_f32: f32 = @floatFromInt(@intFromBool(self.state.active == key));
|
|
||||||
|
|
||||||
box.hot_t += rate * (is_hot_f32 - box.hot_t );
|
|
||||||
box.active_t += rate * (is_active_f32 - box.active_t);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.state.active == Key.nil) {
|
|
||||||
self.state.hot = .nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn endFrame(self: *UI) void {
|
|
||||||
if (self.input.isMouseReleased(.left)) {
|
|
||||||
self.state.active = .nil;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(self: *UI) void {
|
|
||||||
if (self.stack_len == self.stack.len) {
|
|
||||||
log.warn("UI stack capacity limit reached", .{});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
assert(self.stack_len >= 1);
|
|
||||||
self.stack[self.stack_len] = self.stack[self.stack_len-1];
|
|
||||||
self.stack_len += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop(self: *UI) void {
|
|
||||||
if (self.stack_len <= 1) {
|
|
||||||
log.warn("Attempt to pop from empty UI", .{});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.stack_len -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn top(self: *UI) *Frame {
|
|
||||||
assert(self.stack_len > 0);
|
|
||||||
return &self.stack[self.stack_len-1];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn measureText(self: *UI, text: []const u8) Rect {
|
|
||||||
const frame = self.top();
|
|
||||||
const font = frame.font orelse return .zero;
|
|
||||||
|
|
||||||
return font.measureText(
|
|
||||||
text,
|
|
||||||
frame.text_height,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn drawText(self: *UI, pos: Vec2, text: []const u8) void {
|
|
||||||
const frame = self.top();
|
|
||||||
const font = frame.font orelse return;
|
|
||||||
|
|
||||||
font.drawText(.{
|
|
||||||
.pos = pos,
|
|
||||||
.text = text,
|
|
||||||
.height = frame.text_height,
|
|
||||||
.color = frame.text_color
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getMousePosition(self: *UI) ?Vec2 {
|
|
||||||
if (self.input.mouse_position) |screen_mouse_position| {
|
|
||||||
return Gfx.getTransform().applyInverse(screen_mouse_position);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getBox(self: *UI, key: Key) *Box {
|
|
||||||
if (self.state.boxes.count() >= self.state.boxes.capacity()) {
|
|
||||||
log.warn("UI box limit reached!", .{});
|
|
||||||
return self.state.boxes.getEntry(.nil).?.value_ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
const gop = self.state.boxes.getOrPutAssumeCapacity(key);
|
|
||||||
const box = gop.value_ptr;
|
|
||||||
if (!gop.found_existing) {
|
|
||||||
box.* = .{};
|
|
||||||
}
|
|
||||||
box.used = true;
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ButtonState = enum {
|
|
||||||
normal,
|
|
||||||
hot,
|
|
||||||
active,
|
|
||||||
released,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn buttonLogic(self: *UI, rect: Rect, key: Key) ButtonState {
|
|
||||||
if (self.getMousePosition()) |mouse_position| {
|
|
||||||
const is_inside = rect.isInside(mouse_position);
|
|
||||||
if (self.state.hot == Key.nil and is_inside) {
|
|
||||||
self.state.hot = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.state.hot == key and self.input.isMousePressed(.left)) {
|
|
||||||
self.state.active = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.state.active == key and self.input.isMouseReleased(.left) and is_inside) {
|
|
||||||
return .released;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (self.state.active == key) {
|
|
||||||
return .active;
|
|
||||||
} else if (self.state.hot == key) {
|
|
||||||
return .hot;
|
|
||||||
} else {
|
|
||||||
return .normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn squishRect(rect: Rect, sx: f32, sy: f32) Rect {
|
|
||||||
const amount = rect.size.multiply(.init(sx, sy));
|
|
||||||
return Rect{
|
|
||||||
.pos = rect.pos.sub(amount.divideScalar(2)),
|
|
||||||
.size = rect.size.add(amount)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn textButton(self: *UI, rect: Rect, text: []const u8) bool {
|
|
||||||
const frame = self.top();
|
|
||||||
const font = frame.font orelse return false;
|
|
||||||
const text_bounds = self.measureText(text);
|
|
||||||
|
|
||||||
const key = Key.initString(@intFromEnum(frame.key), text);
|
|
||||||
const button_state = self.buttonLogic(rect, key);
|
|
||||||
const box = self.getBox(key);
|
|
||||||
|
|
||||||
var sprite: Gfx.Sprite.Id = .nil;
|
|
||||||
if (button_state == .active) {
|
|
||||||
sprite = frame.button_down_sprite;
|
|
||||||
} else {
|
|
||||||
sprite = frame.button_normal_sprite;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hot_t = Math.ease_out_back(box.hot_t);
|
|
||||||
const active_t = Math.ease_out_back(box.active_t);
|
|
||||||
|
|
||||||
const animated_rect = squishRect(
|
|
||||||
rect,
|
|
||||||
0.06 * hot_t - 0.1 * active_t,
|
|
||||||
-0.09 * hot_t + 0.16 * active_t,
|
|
||||||
);
|
|
||||||
NineRect.draw(
|
|
||||||
sprite,
|
|
||||||
animated_rect.pos,
|
|
||||||
animated_rect.size,
|
|
||||||
Color.white.lerp_invert(hot_t * 0.2 - active_t * 0.2),
|
|
||||||
);
|
|
||||||
|
|
||||||
const content_size = rect.size.sub(frame.padding.toVec2());
|
|
||||||
|
|
||||||
font.drawText(.{
|
|
||||||
.pos = rect.pos
|
|
||||||
.sub(text_bounds.pos)
|
|
||||||
.add(.init(frame.padding.left, frame.padding.top))
|
|
||||||
.add(content_size.sub(text_bounds.size).multiplyScalar(0.5))
|
|
||||||
.add(.init(0, -5 * hot_t + 15 * active_t)),
|
|
||||||
.text = text,
|
|
||||||
.height = frame.text_height,
|
|
||||||
.color = frame.text_color.lerp_invert(hot_t * 0.9 + active_t * 0.1),
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = button_state == .released;
|
|
||||||
if (result) {
|
|
||||||
var rng = self.state.rng_state.random();
|
|
||||||
if (frame.button_sound.len > 0) {
|
|
||||||
_ = Audio.play(frame.button_sound[rng.uintLessThan(usize, frame.button_sound.len)], .{ .bus = frame.audio_bus });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user