add bare bones level select
This commit is contained in:
parent
05698200d2
commit
fbf1d4c040
BIN
assets/kenney-ui/Sounds/click-b.ogg
Normal file
BIN
assets/kenney-ui/Sounds/click-b.ogg
Normal file
Binary file not shown.
@ -148,7 +148,12 @@ fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builti
|
||||
"kenney-ui/Font/Kenney Future Narrow.ttf",
|
||||
"tiled/map.tmx",
|
||||
"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();
|
||||
@ -202,6 +207,8 @@ fn buildProject(ctx: Context, target: Build.ResolvedTarget, optimize: std.builti
|
||||
const math_mod = b.createModule(.{
|
||||
.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);
|
||||
|
||||
dep_sokol = b.dependency("sokol", .{
|
||||
|
||||
@ -15,6 +15,9 @@
|
||||
.stb = .{
|
||||
.path = "./libs/stb",
|
||||
},
|
||||
.ok_color = .{
|
||||
.path = "./libs/ok_color",
|
||||
},
|
||||
.tiled = .{
|
||||
.path = "./libs/tiled",
|
||||
},
|
||||
|
||||
37
libs/ok_color/build.zig
Normal file
37
libs/ok_color/build.zig
Normal file
@ -0,0 +1,37 @@
|
||||
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());
|
||||
}
|
||||
12
libs/ok_color/build.zig.zon
Normal file
12
libs/ok_color/build.zig.zon
Normal file
@ -0,0 +1,12 @@
|
||||
.{
|
||||
.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,
|
||||
}
|
||||
14
libs/ok_color/src/root.zig
Normal file
14
libs/ok_color/src/root.zig
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
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;
|
||||
196
src/app.zig
196
src/app.zig
@ -334,26 +334,35 @@ const BoxSprites = struct {
|
||||
};
|
||||
};
|
||||
|
||||
const MenuState = enum {
|
||||
main,
|
||||
level_select,
|
||||
};
|
||||
|
||||
levels: std.ArrayList(World),
|
||||
|
||||
roboto_font: Font,
|
||||
tilesheet: Tilesheet,
|
||||
sokoban_tilesheet: Tilesheet,
|
||||
|
||||
player_sprites: PlayerSprites,
|
||||
button_up_sprite: Gfx.Sprite.Id,
|
||||
button_down_sprite: Gfx.Sprite.Id,
|
||||
box_sprites_by_color: std.EnumArray(BoxColor, BoxSprites),
|
||||
|
||||
footstep_buffer: Audio.BufferId,
|
||||
footstep_sound: Audio.SoundId = .nil,
|
||||
|
||||
sfx_bus: Audio.BusId,
|
||||
ui_bus: Audio.BusId,
|
||||
click_buffers: []const Audio.BufferId,
|
||||
|
||||
ui_state: UI.State = .empty,
|
||||
ui_state: UI.State,
|
||||
|
||||
world: World,
|
||||
undo_stack: std.ArrayList(UndoFrame) = .empty,
|
||||
current_level: u32 = 0,
|
||||
animation_timer: Platform.Nanoseconds = 0,
|
||||
menu_shown: bool = false,
|
||||
menu_state: ?MenuState = null,
|
||||
|
||||
pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
||||
var roboto_font = try Font.init(plt.gpa, .{
|
||||
@ -367,9 +376,10 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
||||
assert(tilesheet2_png.width == tilesheet1_png.width*2);
|
||||
assert(tilesheet2_png.height == tilesheet1_png.height*2);
|
||||
|
||||
var levels = [_][*]u8{ tilesheet2_png.rgba8_pixels, tilesheet1_png.rgba8_pixels };
|
||||
const spritesheet = Gfx.initSpritesheet(.{ .mipmaps = 2 });
|
||||
|
||||
const tilesheet = try Tilesheet.init(
|
||||
var levels = [_][*]u8{ tilesheet2_png.rgba8_pixels, tilesheet1_png.rgba8_pixels };
|
||||
const sokoban_tilesheet = try Tilesheet.init(
|
||||
plt.arena,
|
||||
.init(.{
|
||||
.width = tilesheet2_png.width,
|
||||
@ -380,7 +390,7 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
||||
.init(128, 128),
|
||||
.{
|
||||
.padding = 1,
|
||||
.spritesheet = Gfx.initSpritesheet(.{ .mipmaps = levels.len })
|
||||
.spritesheet = spritesheet
|
||||
}
|
||||
);
|
||||
|
||||
@ -405,10 +415,12 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
||||
}) |entry| {
|
||||
var sprite_ids = player_sprites.getDirection(entry.dir);
|
||||
inline for (0.., entry.positions) |i, pos| {
|
||||
sprite_ids[i] = tilesheet.get(@intFromFloat(pos.x), @intFromFloat(pos.y));
|
||||
sprite_ids[i] = sokoban_tilesheet.get(@intFromFloat(pos.x), @intFromFloat(pos.y));
|
||||
}
|
||||
}
|
||||
|
||||
// button_square_depth_flat.png
|
||||
|
||||
const footstep_sound = Audio.addBuffer(.{
|
||||
.vorbis = plt.assets.readFile("impact-sounds/footstep_concrete_000.ogg")
|
||||
});
|
||||
@ -416,48 +428,100 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
||||
self.* = App{
|
||||
.player_sprites = player_sprites,
|
||||
.roboto_font = roboto_font,
|
||||
.tilesheet = tilesheet,
|
||||
.sokoban_tilesheet = sokoban_tilesheet,
|
||||
.footstep_buffer = footstep_sound,
|
||||
.box_sprites_by_color = .init(.{
|
||||
.none = .nil,
|
||||
.brown = .{
|
||||
.regular = tilesheet.get(6, 0),
|
||||
.darker = tilesheet.get(6, 1),
|
||||
.in_hole = tilesheet.get(6, 4),
|
||||
.regular = sokoban_tilesheet.get(6, 0),
|
||||
.darker = sokoban_tilesheet.get(6, 1),
|
||||
.in_hole = sokoban_tilesheet.get(6, 4),
|
||||
},
|
||||
.red = .{
|
||||
.regular = tilesheet.get(7, 0),
|
||||
.darker = tilesheet.get(7, 1),
|
||||
.in_hole = tilesheet.get(7, 4),
|
||||
.regular = sokoban_tilesheet.get(7, 0),
|
||||
.darker = sokoban_tilesheet.get(7, 1),
|
||||
.in_hole = sokoban_tilesheet.get(7, 4),
|
||||
},
|
||||
.blue = .{
|
||||
.regular = tilesheet.get(8, 0),
|
||||
.darker = tilesheet.get(8, 1),
|
||||
.in_hole = tilesheet.get(8, 4),
|
||||
.regular = sokoban_tilesheet.get(8, 0),
|
||||
.darker = sokoban_tilesheet.get(8, 1),
|
||||
.in_hole = sokoban_tilesheet.get(8, 4),
|
||||
},
|
||||
.green = .{
|
||||
.regular = tilesheet.get(9, 0),
|
||||
.darker = tilesheet.get(9, 1),
|
||||
.in_hole = tilesheet.get(9, 4),
|
||||
.regular = sokoban_tilesheet.get(9, 0),
|
||||
.darker = sokoban_tilesheet.get(9, 1),
|
||||
.in_hole = sokoban_tilesheet.get(9, 4),
|
||||
},
|
||||
.gray = .{
|
||||
.regular = tilesheet.get(10, 0),
|
||||
.darker = tilesheet.get(10, 1),
|
||||
.in_hole = tilesheet.get(10, 4),
|
||||
.regular = sokoban_tilesheet.get(10, 0),
|
||||
.darker = sokoban_tilesheet.get(10, 1),
|
||||
.in_hole = sokoban_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" }),
|
||||
.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,
|
||||
};
|
||||
|
||||
Audio.getBus(self.ui_bus).?.volume = 0.3;
|
||||
|
||||
self.levels = try self.loadLevels(plt.gpa, plt.assets);
|
||||
self.world.copy(self.levels.items[0]);
|
||||
|
||||
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) {
|
||||
var xml_parser = try Tiled.xml.Parser.initCapacity(gpa, .{});
|
||||
defer xml_parser.deinit(gpa);
|
||||
@ -552,7 +616,7 @@ fn loadLevel(
|
||||
.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)
|
||||
});
|
||||
}
|
||||
|
||||
@ -593,7 +657,7 @@ fn loadLevel(
|
||||
.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),
|
||||
.color = color
|
||||
});
|
||||
}
|
||||
@ -704,13 +768,11 @@ pub fn frame(self: *App, plt: *Platform.Frame) !void {
|
||||
|
||||
const dt = plt.deltaTime();
|
||||
|
||||
if (plt.input.isKeyPressed(.ESCAPE)) {
|
||||
self.menu_shown = !self.menu_shown;
|
||||
}
|
||||
|
||||
var world = &self.world;
|
||||
|
||||
if (!self.menu_shown) {
|
||||
const menu_shown = self.menu_state != null;
|
||||
|
||||
if (!menu_shown) {
|
||||
const input = getPlayerInput(plt.input);
|
||||
|
||||
if (input.undo) {
|
||||
@ -740,7 +802,7 @@ pub fn frame(self: *App, plt: *Platform.Frame) !void {
|
||||
entity.player_frame_index = 0;
|
||||
}
|
||||
|
||||
self.footstep_sound = Audio.play(.{ .buffer = self.footstep_buffer, .bus = self.sfx_bus });
|
||||
self.footstep_sound = Audio.play(self.footstep_buffer, .{ .bus = self.sfx_bus });
|
||||
}
|
||||
|
||||
} else if (entity.type == .box) {
|
||||
@ -815,14 +877,14 @@ pub fn frame(self: *App, plt: *Platform.Frame) !void {
|
||||
|
||||
try self.drawWorld(world);
|
||||
|
||||
if (self.menu_shown) {
|
||||
if (menu_shown) {
|
||||
var color = clear_color;
|
||||
color.a = 0.8;
|
||||
Gfx.drawRectangle(.init(0, 0), canvas.size, color);
|
||||
}
|
||||
}
|
||||
|
||||
if (self.menu_shown) {
|
||||
if (self.menu_state) |menu_state| {
|
||||
var canvas = CanvasScaler{
|
||||
.size = Vec2.initFromInt(usize, 1920, 1080)
|
||||
};
|
||||
@ -836,36 +898,65 @@ pub fn frame(self: *App, plt: *Platform.Frame) !void {
|
||||
const ui_frame = ui.top();
|
||||
const font = &self.roboto_font;
|
||||
ui_frame.font = font;
|
||||
ui_frame.text_color = .black;
|
||||
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;
|
||||
|
||||
ui_frame.padding.addHorizontal(150);
|
||||
ui_frame.padding.addVertical(80);
|
||||
if (menu_state == .main) {
|
||||
ui_frame.padding.addHorizontal(150);
|
||||
ui_frame.padding.addVertical(80);
|
||||
|
||||
var layout = UI.VerticalLayout{ .ui = &ui, .gap = 30 };
|
||||
var layout = UI.VerticalLayout{ .ui = &ui, .gap = 30 };
|
||||
|
||||
if (layout.textButton("Continue")) {
|
||||
self.menu_shown = false;
|
||||
}
|
||||
|
||||
if (layout.textButton("Level select")) {
|
||||
self.menu_shown = false;
|
||||
}
|
||||
|
||||
if (layout.textButton("Settings")) {
|
||||
}
|
||||
|
||||
if (!builtin.target.cpu.arch.isWasm()) {
|
||||
if (layout.textButton("Exit")) {
|
||||
plt.request_quit = true;
|
||||
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: u32) void {
|
||||
pub fn setWorldByLevel(self: *App, index: usize) void {
|
||||
self.world.copy(self.levels.items[index]);
|
||||
self.undo_stack.clearRetainingCapacity();
|
||||
}
|
||||
@ -876,4 +967,5 @@ pub fn deinit(self: *App, plt: Platform.Deinit) void {
|
||||
|
||||
self.levels.deinit(plt.gpa);
|
||||
self.undo_stack.deinit(plt.gpa);
|
||||
self.ui_state.deinit(plt.gpa);
|
||||
}
|
||||
|
||||
94
src/nine_rect.zig
Normal file
94
src/nine_rect.zig
Normal file
@ -0,0 +1,94 @@
|
||||
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,14 +370,13 @@ fn busMultipliedVolume(id: BusId) f32 {
|
||||
}
|
||||
|
||||
const PlayOptions = struct {
|
||||
buffer: BufferId,
|
||||
// TODO: adsr: ?Sound.ADSR = null,
|
||||
volume: f32 = 1,
|
||||
loop: bool = false,
|
||||
bus: ?BusId = null
|
||||
};
|
||||
|
||||
pub fn play(opts: PlayOptions) SoundId {
|
||||
pub fn play(buffer: BufferId, opts: PlayOptions) SoundId {
|
||||
const self = &g_state;
|
||||
|
||||
var mutex = &self.thread_state.mutex;
|
||||
@ -394,7 +393,7 @@ pub fn play(opts: PlayOptions) SoundId {
|
||||
return .nil;
|
||||
}
|
||||
|
||||
if (opts.buffer == BufferId.nil) {
|
||||
if (buffer == BufferId.nil) {
|
||||
return .nil;
|
||||
}
|
||||
|
||||
@ -409,7 +408,7 @@ pub fn play(opts: PlayOptions) SoundId {
|
||||
.cursor = 0,
|
||||
.volume = opts.volume,
|
||||
.loop = opts.loop,
|
||||
.buffer = opts.buffer,
|
||||
.buffer = buffer,
|
||||
.bus = bus,
|
||||
};
|
||||
|
||||
@ -434,7 +433,7 @@ pub fn stop(id: SoundId) void {
|
||||
pub fn getBus(id: BusId) ?*Bus {
|
||||
const self = &g_state;
|
||||
|
||||
if (id == .nil) {
|
||||
if (id == BusId.nil) {
|
||||
return null;
|
||||
}
|
||||
return &self.buses.items[id.index];
|
||||
|
||||
@ -1,6 +1,9 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const Math = @import("math");
|
||||
const Vec4 = Math.Vec4;
|
||||
|
||||
const Color = @This();
|
||||
|
||||
r: f32,
|
||||
@ -50,3 +53,38 @@ pub fn lerp(self: Color, other: Color, t: f32) Color {
|
||||
.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
|
||||
};
|
||||
}
|
||||
|
||||
@ -1178,19 +1178,46 @@ pub fn getSpriteUVRect(id: Sprite.Id) ?Rect {
|
||||
const sprite = self.sprites.get(id) orelse return null;
|
||||
const sprite_mipchain = sprite.mipchain orelse return null;
|
||||
|
||||
const spritesheet = self.spritesheets.get(sprite.spritesheet) orelse return null;
|
||||
const position = getSpritePosition(id) 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)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
assert(sprite.position != null);
|
||||
return sprite.position;
|
||||
}
|
||||
|
||||
const sprite_size = Vec2.initFromInt(u32, sprite_mipchain.width, sprite_mipchain.height);
|
||||
return Rect{
|
||||
.pos = sprite.position.?.divide(spritesheet.size),
|
||||
.size = sprite_size.divide(spritesheet.size),
|
||||
};
|
||||
pub fn getSpriteSize(id: Sprite.Id) Vec2 {
|
||||
const self = &g_state;
|
||||
const sprite = self.sprites.get(id) orelse return .zero;
|
||||
const sprite_mipchain = sprite.mipchain orelse return .zero;
|
||||
|
||||
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 {
|
||||
@ -1261,6 +1288,12 @@ 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 {
|
||||
format: Image.Format,
|
||||
pixels: [*]u8,
|
||||
@ -1351,9 +1384,13 @@ pub const Image = struct {
|
||||
}
|
||||
|
||||
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_size = self.format.pixelSize();
|
||||
return self.pixels[(pixel_size*pixel_index)..][0..pixel_size];
|
||||
return self.pixels[(pixel_size*pixel_index)..][0..(width*pixel_size)];
|
||||
}
|
||||
|
||||
pub fn setRect(self: Image, x: u32, y: u32, width: u32, height: u32, pixel: []const u8) void {
|
||||
|
||||
@ -98,6 +98,18 @@ pub fn getMouseDown(self: Input, button: MouseButton) ?Nanoseconds {
|
||||
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)) {
|
||||
SPACE = 32,
|
||||
APOSTROPHE = 39,
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
const ok_color = @import("ok_color");
|
||||
|
||||
pub const bytes_per_kib = 1024;
|
||||
pub const bytes_per_mib = bytes_per_kib * 1024;
|
||||
pub const bytes_per_gib = bytes_per_mib * 1024;
|
||||
@ -485,3 +487,103 @@ pub fn isInsideRect(rect_pos: Vec2, rect_size: Vec2, pos: Vec2) bool {
|
||||
};
|
||||
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,10 +105,12 @@ fn PlatformType(App: type) type {
|
||||
|
||||
_ = self.frame_arena.reset(.free_all); // TODO: make arena reset mode configurable
|
||||
|
||||
const now = Io.Timestamp.now(self.io, .awake);
|
||||
self.input.now = @intCast(self.started_at.durationTo(now).nanoseconds);
|
||||
self.input.delta_time = @intCast(self.last_frame_at.durationTo(now).nanoseconds);
|
||||
self.last_frame_at = now;
|
||||
{
|
||||
const now = Io.Timestamp.now(self.io, .awake);
|
||||
self.input.now = @intCast(self.started_at.durationTo(now).nanoseconds);
|
||||
self.input.delta_time = @intCast(self.last_frame_at.durationTo(now).nanoseconds);
|
||||
self.last_frame_at = now;
|
||||
}
|
||||
|
||||
if (self.input.keyboard.pressed.contains(.F4)) {
|
||||
self.show_imgui = !self.show_imgui;
|
||||
@ -181,6 +183,14 @@ fn PlatformType(App: type) type {
|
||||
self.input.mouse_buttons.pressed = .empty;
|
||||
self.input.mouse_buttons.released = .empty;
|
||||
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 {
|
||||
@ -610,16 +620,12 @@ pub const Frame = struct {
|
||||
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 {
|
||||
return nanosecondsToSeconds(self.input.delta_time);
|
||||
return self.input.deltaTime();
|
||||
}
|
||||
|
||||
pub fn time(self: Frame) f32 {
|
||||
return nanosecondsToSeconds(self.input.now);
|
||||
return self.input.time();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -40,7 +40,8 @@ void main() {
|
||||
if (texture_mode == 0) {
|
||||
frag_color = tex_color * color;
|
||||
} else if (texture_mode == 1) {
|
||||
frag_color = vec4(1, 1, 1, tex_color.r) * color;
|
||||
float opacity = tex_color.r;
|
||||
frag_color = vec4(opacity, opacity, opacity, opacity) * color;
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
151
src/ui.zig
151
src/ui.zig
@ -1,10 +1,13 @@
|
||||
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;
|
||||
@ -169,29 +172,51 @@ pub const Sides = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const Box = struct {
|
||||
margin: Sides,
|
||||
padding: Sides,
|
||||
};
|
||||
|
||||
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,
|
||||
|
||||
pub const empty = State{
|
||||
.hot = .nil,
|
||||
.active = .nil
|
||||
};
|
||||
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,
|
||||
@ -212,6 +237,37 @@ pub fn init(state: *State, input: *Platform.Input) UI {
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@ -275,6 +331,21 @@ pub fn getMousePosition(self: *UI) ?Vec2 {
|
||||
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,
|
||||
@ -308,29 +379,65 @@ fn buttonLogic(self: *UI, rect: Rect, key: Key) ButtonState {
|
||||
}
|
||||
}
|
||||
|
||||
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 button_state = self.buttonLogic(rect, Key.initString(@intFromEnum(frame.key), 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) {
|
||||
Gfx.drawRectangle(rect.pos, rect.size, .blue);
|
||||
} else if (button_state == .hot) {
|
||||
Gfx.drawRectangle(rect.pos, rect.size, .red);
|
||||
sprite = frame.button_down_sprite;
|
||||
} else {
|
||||
Gfx.drawRectangle(rect.pos, rect.size, .white);
|
||||
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());
|
||||
|
||||
self.drawText(
|
||||
rect.pos
|
||||
.sub(text_bounds.pos)
|
||||
.add(.init(frame.padding.left, frame.padding.top))
|
||||
.add(content_size.sub(text_bounds.size).multiplyScalar(0.5)),
|
||||
text
|
||||
);
|
||||
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),
|
||||
});
|
||||
|
||||
return button_state == .released;
|
||||
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