add imgui and tracy
This commit is contained in:
parent
11fc686caa
commit
0174a3f3f0
70
build.zig
70
build.zig
@ -1,9 +1,13 @@
|
||||
const std = @import("std");
|
||||
const sokol = @import("sokol");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
pub fn build(b: *std.Build) !void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const has_imgui = b.option(bool, "imgui", "ImGui integration") orelse (optimize == .Debug);
|
||||
const has_tracy = b.option(bool, "tracy", "Tracy integration") orelse (optimize == .Debug);
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "game_2025_12_13",
|
||||
.root_module = b.createModule(.{
|
||||
@ -12,6 +16,70 @@ pub fn build(b: *std.Build) void {
|
||||
.optimize = optimize,
|
||||
}),
|
||||
});
|
||||
const exe_mod = exe.root_module;
|
||||
|
||||
const tracy_dependency = b.dependency("tracy", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.tracy_enable = has_tracy,
|
||||
.tracy_only_localhost = true
|
||||
});
|
||||
exe_mod.linkLibrary(tracy_dependency.artifact("tracy"));
|
||||
exe_mod.addImport("tracy", tracy_dependency.module("tracy"));
|
||||
|
||||
const stb_dependency = b.dependency("stb", .{});
|
||||
exe_mod.addIncludePath(stb_dependency.path("."));
|
||||
|
||||
const sokol_c_dependency = b.dependency("sokol_c", .{});
|
||||
exe_mod.addIncludePath(sokol_c_dependency.path("util"));
|
||||
|
||||
const fontstash_dependency = b.dependency("fontstash", .{});
|
||||
exe_mod.addIncludePath(fontstash_dependency.path("src"));
|
||||
|
||||
const sokol_dependency = b.dependency("sokol", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.with_sokol_imgui = has_imgui
|
||||
});
|
||||
|
||||
exe_mod.addImport("sokol", sokol_dependency.module("sokol"));
|
||||
exe_mod.linkLibrary(sokol_dependency.artifact("sokol_clib"));
|
||||
|
||||
var cflags_buffer: [64][]const u8 = undefined;
|
||||
var cflags = std.ArrayListUnmanaged([]const u8).initBuffer(&cflags_buffer);
|
||||
switch (sokol.resolveSokolBackend(.auto, target.result)) {
|
||||
.d3d11 => try cflags.appendBounded("-DSOKOL_D3D11"),
|
||||
.metal => try cflags.appendBounded("-DSOKOL_METAL"),
|
||||
.gl => try cflags.appendBounded("-DSOKOL_GLCORE"),
|
||||
.gles3 => try cflags.appendBounded("-DSOKOL_GLES3"),
|
||||
.wgpu => try cflags.appendBounded("-DSOKOL_WGPU"),
|
||||
else => @panic("unknown sokol backend"),
|
||||
}
|
||||
exe_mod.addIncludePath(b.path("src/libs"));
|
||||
exe_mod.addCSourceFile(.{
|
||||
.file = b.path("src/libs/sokol_fontstash_impl.c"),
|
||||
.flags = cflags.items
|
||||
});
|
||||
|
||||
exe_mod.addCSourceFile(.{
|
||||
.file = b.path("src/libs/stb_image.c"),
|
||||
.flags = &.{}
|
||||
});
|
||||
|
||||
if (has_imgui) {
|
||||
if (b.lazyDependency("cimgui", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
})) |cimgui_dependency| {
|
||||
sokol_dependency.artifact("sokol_clib").addIncludePath(cimgui_dependency.path("src"));
|
||||
exe_mod.addImport("cimgui", cimgui_dependency.module("cimgui"));
|
||||
}
|
||||
}
|
||||
|
||||
var options = b.addOptions();
|
||||
options.addOption(bool, "has_imgui", has_imgui);
|
||||
options.addOption(bool, "has_tracy", has_tracy);
|
||||
exe_mod.addOptions("options", options);
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
|
||||
@ -1,81 +1,38 @@
|
||||
.{
|
||||
// This is the default name used by packages depending on this one. For
|
||||
// example, when a user runs `zig fetch --save <url>`, this field is used
|
||||
// as the key in the `dependencies` table. Although the user can choose a
|
||||
// different name, most users will stick with this provided value.
|
||||
//
|
||||
// It is redundant to include "zig" in this name because it is already
|
||||
// within the Zig package namespace.
|
||||
.name = .game_2025_12_13,
|
||||
// This is a [Semantic Version](https://semver.org/).
|
||||
// In a future version of Zig it will be used for package deduplication.
|
||||
.version = "0.0.0",
|
||||
// Together with name, this represents a globally unique package
|
||||
// identifier. This field is generated by the Zig toolchain when the
|
||||
// package is first created, and then *never changes*. This allows
|
||||
// unambiguous detection of one package being an updated version of
|
||||
// another.
|
||||
//
|
||||
// When forking a Zig project, this id should be regenerated (delete the
|
||||
// field and run `zig build`) if the upstream project is still maintained.
|
||||
// Otherwise, the fork is *hostile*, attempting to take control over the
|
||||
// original project's identity. Thus it is recommended to leave the comment
|
||||
// on the following line intact, so that it shows up in code reviews that
|
||||
// modify the field.
|
||||
.fingerprint = 0x5704f7ae3ffdd7f8, // Changing this has security and trust implications.
|
||||
// Tracks the earliest Zig version that the package considers to be a
|
||||
// supported use case.
|
||||
.fingerprint = 0x5704f7ae3ffdd7f8,
|
||||
.minimum_zig_version = "0.15.2",
|
||||
// This field is optional.
|
||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||
// Once all dependencies are fetched, `zig build` no longer requires
|
||||
// internet connectivity.
|
||||
.dependencies = .{
|
||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||
//.example = .{
|
||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
||||
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||
// // the new URL. If the contents of a URL change this will result in a hash mismatch
|
||||
// // which will prevent zig from using it.
|
||||
// .url = "https://example.com/foo.tar.gz",
|
||||
//
|
||||
// // This is computed from the file contents of the directory of files that is
|
||||
// // obtained after fetching `url` and applying the inclusion rules given by
|
||||
// // `paths`.
|
||||
// //
|
||||
// // This field is the source of truth; packages do not come from a `url`; they
|
||||
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
||||
// // obtain a package matching this `hash`.
|
||||
// //
|
||||
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
||||
// .hash = "...",
|
||||
//
|
||||
// // When this is provided, the package is found in a directory relative to the
|
||||
// // build root. In this case the package's hash is irrelevant and therefore not
|
||||
// // computed. This field and `url` are mutually exclusive.
|
||||
// .path = "foo",
|
||||
//
|
||||
// // When this is set to `true`, a package is declared to be lazily
|
||||
// // fetched. This makes the dependency only get fetched if it is
|
||||
// // actually used.
|
||||
// .lazy = false,
|
||||
//},
|
||||
.sokol = .{
|
||||
.url = "git+https://github.com/floooh/sokol-zig.git#1e233203b41893a8bf9c1c91933eba98204b6ed8",
|
||||
.hash = "sokol-0.1.0-pb1HK42FNgDb5sqnsadiO2qabkfUX8jXP_DheOZGcD1W",
|
||||
},
|
||||
.cimgui = .{
|
||||
.url = "git+https://github.com/floooh/dcimgui.git#33c99ef426b68030412b5a4b11487a23da9d4f13",
|
||||
.hash = "cimgui-0.1.0-44ClkQRJlABdFMKRqIG8KDD6jy1eQbgPO335NziPYjmL",
|
||||
.lazy = true,
|
||||
},
|
||||
.tracy = .{
|
||||
.url = "git+https://github.com/sagehane/zig-tracy.git#80933723efe9bf840fe749b0bfc0d610f1db1669",
|
||||
.hash = "zig_tracy-0.0.5-aOIqsX1tAACKaRRB-sraMLuNiMASXi_y-4FtRuw4cTpx",
|
||||
},
|
||||
.stb = .{
|
||||
.url = "git+https://github.com/nothings/stb.git#f1c79c02822848a9bed4315b12c8c8f3761e1296",
|
||||
.hash = "N-V-__8AABQ7TgCnPlp8MP4YA8znrjd6E-ZjpF1rvrS8J_2I",
|
||||
},
|
||||
.sokol_c = .{
|
||||
.url = "git+https://github.com/floooh/sokol.git#c66a1f04e6495d635c5e913335ab2308281e0492",
|
||||
.hash = "N-V-__8AAC3eYABB1DVLb4dkcEzq_xVeEZZugVfQ6DoNQBDN",
|
||||
},
|
||||
.fontstash = .{
|
||||
.url = "git+https://github.com/memononen/fontstash.git#b5ddc9741061343740d85d636d782ed3e07cf7be",
|
||||
.hash = "N-V-__8AAA9xHgAxdLYPmlNTy6qzv9IYqiIePEHQUOPWYQ_6",
|
||||
},
|
||||
},
|
||||
// Specifies the set of files and directories that are included in this package.
|
||||
// Only files and directories listed here are included in the `hash` that
|
||||
// is computed for this package. Only files listed here will remain on disk
|
||||
// when using the zig package manager. As a rule of thumb, one should list
|
||||
// files required for compilation plus any license(s).
|
||||
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||
// the build root itself.
|
||||
// A directory listed here means that all files within, recursively, are included.
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
// For example...
|
||||
//"LICENSE",
|
||||
//"README.md",
|
||||
},
|
||||
}
|
||||
|
||||
93
src/assets/roboto-font/LICENSE.txt
Normal file
93
src/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
src/assets/roboto-font/Roboto-Bold.ttf
Normal file
BIN
src/assets/roboto-font/Roboto-Bold.ttf
Normal file
Binary file not shown.
BIN
src/assets/roboto-font/Roboto-Italic.ttf
Normal file
BIN
src/assets/roboto-font/Roboto-Italic.ttf
Normal file
Binary file not shown.
BIN
src/assets/roboto-font/Roboto-Regular.ttf
Normal file
BIN
src/assets/roboto-font/Roboto-Regular.ttf
Normal file
Binary file not shown.
1375
src/graphics.zig
Normal file
1375
src/graphics.zig
Normal file
File diff suppressed because it is too large
Load Diff
502
src/imgui.zig
Normal file
502
src/imgui.zig
Normal file
@ -0,0 +1,502 @@
|
||||
const std = @import("std");
|
||||
const Math = @import("./math.zig");
|
||||
const options = @import("options");
|
||||
pub const ig = @import("cimgui");
|
||||
const Vec2 = Math.Vec2;
|
||||
const Vec3 = Math.Vec3;
|
||||
const Vec4 = Math.Vec4;
|
||||
|
||||
const sokol = @import("sokol");
|
||||
const sapp = sokol.app;
|
||||
const simgui = sokol.imgui;
|
||||
|
||||
const enabled = options.has_imgui;
|
||||
|
||||
var global_allocator: ?std.mem.Allocator = null;
|
||||
|
||||
pub const WindowOptions = struct {
|
||||
name: [*c]const u8,
|
||||
pos: ?Vec2 = null,
|
||||
size: ?Vec2 = null,
|
||||
collapsed: ?bool = null,
|
||||
open: ?*bool = null
|
||||
};
|
||||
|
||||
pub const SliderOptions = struct {
|
||||
label: [*c]const u8,
|
||||
value: *f32,
|
||||
min: f32,
|
||||
max: f32,
|
||||
};
|
||||
|
||||
fn toImVec2(vec2: Vec2) ig.ImVec2 {
|
||||
return ig.ImVec2{
|
||||
.x = vec2.x,
|
||||
.y = vec2.y,
|
||||
};
|
||||
}
|
||||
|
||||
inline fn structCast(T: type, value: anytype) T {
|
||||
return @as(*T, @ptrFromInt(@intFromPtr(&value))).*;
|
||||
}
|
||||
|
||||
pub fn setup(gpa: std.mem.Allocator, desc: simgui.Desc) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
global_allocator = gpa;
|
||||
simgui.setup(desc);
|
||||
}
|
||||
|
||||
pub fn addFont(ttf_data: []const u8, font_size: f32) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
var font_config: ig.ImFontConfig = .{};
|
||||
font_config.FontDataOwnedByAtlas = false;
|
||||
font_config.OversampleH = 2;
|
||||
font_config.OversampleV = 2;
|
||||
font_config.GlyphMaxAdvanceX = std.math.floatMax(f32);
|
||||
font_config.RasterizerMultiply = 1.0;
|
||||
font_config.RasterizerDensity = 1.0;
|
||||
font_config.EllipsisChar = 0;
|
||||
|
||||
const io = ig.igGetIO();
|
||||
_ = ig.ImFontAtlas_AddFontFromMemoryTTF(
|
||||
io.*.Fonts,
|
||||
@constCast(@ptrCast(ttf_data.ptr)),
|
||||
@intCast(ttf_data.len),
|
||||
font_size,
|
||||
&font_config,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
pub fn shutdown() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
simgui.shutdown();
|
||||
}
|
||||
|
||||
pub fn handleEvent(ev: sapp.Event) bool {
|
||||
if (enabled) {
|
||||
return simgui.handleEvent(ev);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn newFrame(desc: simgui.FrameDesc) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
simgui.newFrame(desc);
|
||||
}
|
||||
|
||||
pub fn render() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
simgui.render();
|
||||
}
|
||||
|
||||
pub fn beginWindow(opts: WindowOptions) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opts.pos) |pos| {
|
||||
ig.igSetNextWindowPos(toImVec2(pos), ig.ImGuiCond_Once);
|
||||
}
|
||||
if (opts.size) |size| {
|
||||
ig.igSetNextWindowSize(toImVec2(size), ig.ImGuiCond_Once);
|
||||
}
|
||||
if (opts.collapsed) |collapsed| {
|
||||
ig.igSetNextWindowCollapsed(collapsed, ig.ImGuiCond_Once);
|
||||
}
|
||||
|
||||
ig.igSetNextWindowBgAlpha(1);
|
||||
|
||||
var open = ig.igBegin(opts.name, opts.open, ig.ImGuiWindowFlags_None);
|
||||
if (opts.open) |opts_open| {
|
||||
if (opts_open.* == false) {
|
||||
open = false;
|
||||
}
|
||||
}
|
||||
if (!open) {
|
||||
endWindow();
|
||||
}
|
||||
|
||||
return open;
|
||||
}
|
||||
|
||||
pub fn endWindow() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igEnd();
|
||||
}
|
||||
|
||||
pub fn textFmt(comptime fmt: []const u8, args: anytype) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formatted = std.fmt.allocPrintSentinel(global_allocator, fmt, args, 0) catch return;
|
||||
defer global_allocator.free(formatted);
|
||||
|
||||
text(formatted);
|
||||
}
|
||||
|
||||
pub fn text(text_z: [*c]const u8) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igText("%s", text_z);
|
||||
}
|
||||
|
||||
pub fn beginDisabled(disabled: bool) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igBeginDisabled(disabled);
|
||||
}
|
||||
|
||||
pub fn endDisabled() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igEndDisabled();
|
||||
}
|
||||
|
||||
pub fn button(label: [*c]const u8) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igButton(label);
|
||||
}
|
||||
|
||||
pub fn slider(opts: SliderOptions) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igSliderFloat(opts.label, opts.value, opts.min, opts.max);
|
||||
}
|
||||
|
||||
pub fn checkbox(label: [*c]const u8, value: *bool) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igCheckbox(label, value);
|
||||
}
|
||||
|
||||
pub fn beginTabBar(id: [*c]const u8) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igBeginTabBar(id, ig.ImGuiTabBarFlags_None);
|
||||
}
|
||||
|
||||
pub fn endTabBar() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igEndTabBar();
|
||||
}
|
||||
|
||||
pub fn beginTabItem(label: [*c]const u8) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igBeginTabItem(label, null, ig.ImGuiTabItemFlags_None);
|
||||
}
|
||||
|
||||
pub fn endTabItem() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
return ig.igEndTabItem();
|
||||
}
|
||||
|
||||
pub fn beginGroup() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igBeginGroup();
|
||||
}
|
||||
|
||||
pub fn endGroup() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igEndGroup();
|
||||
}
|
||||
|
||||
pub fn sameLine() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igSameLine();
|
||||
}
|
||||
|
||||
pub fn beginTable(id: [*c]const u8, columns: u32, flags: ig.ImGuiTableFlags) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igBeginTable(id, @intCast(columns), flags);
|
||||
}
|
||||
|
||||
pub fn endTable() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igEndTable();
|
||||
}
|
||||
|
||||
pub fn tableNextColumn() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ = ig.igTableNextColumn();
|
||||
}
|
||||
|
||||
pub fn tableNextRow() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ = ig.igTableNextRow();
|
||||
}
|
||||
|
||||
pub fn tableSetColumnIndex(index: usize) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ = ig.igTableSetColumnIndex(@intCast(index));
|
||||
}
|
||||
|
||||
pub fn tableSetupColumn(label: [*c]const u8, flags: ig.ImGuiTableColumnFlags) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igTableSetupColumn(label, flags);
|
||||
}
|
||||
|
||||
pub fn tableHeadersRow() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igTableHeadersRow();
|
||||
}
|
||||
|
||||
pub const ID = union(enum) {
|
||||
string: []const u8,
|
||||
int: i32
|
||||
};
|
||||
|
||||
pub fn pushID(id: ID) void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (id) {
|
||||
.string => |str| ig.igPushIDStr(str.ptr, str.ptr + str.len),
|
||||
.int => |int| ig.igPushIDInt(int)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn popID() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igPopID();
|
||||
}
|
||||
|
||||
pub const TreeNodeFlags = packed struct {
|
||||
selected: bool = false,
|
||||
framed: bool = false,
|
||||
allow_overlap: bool = false,
|
||||
no_tree_pushOnOpen: bool = false,
|
||||
no_auto_open_on_log: bool = false,
|
||||
default_open: bool = false,
|
||||
open_on_double_click: bool = false,
|
||||
open_on_arrow: bool = false,
|
||||
leaf: bool = false,
|
||||
bullet: bool = false,
|
||||
frame_padding: bool = false,
|
||||
span_avail_width: bool = false,
|
||||
span_full_width: bool = false,
|
||||
span_label_width: bool = false,
|
||||
span_all_columns: bool = false,
|
||||
label_span_all_columns: bool = false,
|
||||
nav_left_jumps_back_here: bool = false,
|
||||
collapsing_header: bool = false,
|
||||
|
||||
fn toInt(self: TreeNodeFlags) u32 {
|
||||
// TODO: Try using comptime to reduce this duplication.
|
||||
// Would be great if `toInt()` could be replaced with just a @bitCast
|
||||
//
|
||||
// If the underlying C enum is exhaustive, maybe a bitcast could be performed?
|
||||
// If the order of enums is correct
|
||||
const flags = .{
|
||||
.{ self.selected, ig.ImGuiTreeNodeFlags_Selected },
|
||||
.{ self.framed, ig.ImGuiTreeNodeFlags_Framed },
|
||||
.{ self.allow_overlap, ig.ImGuiTreeNodeFlags_AllowOverlap },
|
||||
.{ self.no_tree_pushOnOpen, ig.ImGuiTreeNodeFlags_NoTreePushOnOpen },
|
||||
.{ self.no_auto_open_on_log, ig.ImGuiTreeNodeFlags_NoAutoOpenOnLog },
|
||||
.{ self.default_open, ig.ImGuiTreeNodeFlags_DefaultOpen },
|
||||
.{ self.open_on_double_click, ig.ImGuiTreeNodeFlags_OpenOnDoubleClick },
|
||||
.{ self.open_on_arrow, ig.ImGuiTreeNodeFlags_OpenOnArrow },
|
||||
.{ self.leaf, ig.ImGuiTreeNodeFlags_Leaf },
|
||||
.{ self.bullet, ig.ImGuiTreeNodeFlags_Bullet },
|
||||
.{ self.frame_padding, ig.ImGuiTreeNodeFlags_FramePadding },
|
||||
.{ self.span_avail_width, ig.ImGuiTreeNodeFlags_SpanAvailWidth },
|
||||
.{ self.span_full_width, ig.ImGuiTreeNodeFlags_SpanFullWidth },
|
||||
.{ self.span_label_width, ig.ImGuiTreeNodeFlags_SpanLabelWidth },
|
||||
.{ self.span_all_columns, ig.ImGuiTreeNodeFlags_SpanAllColumns },
|
||||
.{ self.label_span_all_columns, ig.ImGuiTreeNodeFlags_LabelSpanAllColumns },
|
||||
.{ self.nav_left_jumps_back_here, ig.ImGuiTreeNodeFlags_NavLeftJumpsBackHere },
|
||||
.{ self.collapsing_header, ig.ImGuiTreeNodeFlags_CollapsingHeader },
|
||||
};
|
||||
|
||||
var sum: u32 = 0;
|
||||
inline for (flags) |flag_pair| {
|
||||
if (flag_pair[0]) {
|
||||
sum += flag_pair[1];
|
||||
}
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
};
|
||||
|
||||
pub fn treeNode(label: [*c]const u8, flags: TreeNodeFlags) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igTreeNodeEx(label, @intCast(flags.toInt()));
|
||||
}
|
||||
|
||||
pub fn treePop() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igTreePop();
|
||||
}
|
||||
|
||||
pub fn isItemClicked() bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igIsItemClicked();
|
||||
}
|
||||
|
||||
pub fn isItemToggledOpen() bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igIsItemToggledOpen();
|
||||
}
|
||||
|
||||
pub fn colorPicker4(label: [*c]const u8, color: *Vec4) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igColorPicker4(label, color.asArray().ptr, 0, null);
|
||||
}
|
||||
|
||||
pub fn colorEdit4(label: [*c]const u8, color: *Vec4) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igColorEdit4(label, color.asArray().ptr, 0);
|
||||
}
|
||||
|
||||
pub fn beginCombo(label: [*c]const u8, preview_value: [*c]const u8) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igBeginCombo(label, preview_value, 0);
|
||||
}
|
||||
|
||||
pub fn endCombo() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igEndCombo();
|
||||
}
|
||||
|
||||
pub fn selectable(label: [*c]const u8, selected: bool) bool {
|
||||
if (!enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ig.igSelectableEx(label, selected, 0, .{ });
|
||||
}
|
||||
|
||||
pub fn setItemDefaultFocus() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igSetItemDefaultFocus();
|
||||
}
|
||||
|
||||
pub fn combo(label: [*c]const u8, items: []const [*c]const u8, selected: *usize) void {
|
||||
if (beginCombo(label, items[selected.*])) {
|
||||
defer endCombo();
|
||||
|
||||
for (0.., items) |i, item| {
|
||||
const is_selected = selected.* == i;
|
||||
if (selectable(item, is_selected)) {
|
||||
selected.* = i;
|
||||
}
|
||||
|
||||
if (is_selected) {
|
||||
setItemDefaultFocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn separator() void {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ig.igSeparator();
|
||||
}
|
||||
85
src/libs/sokol_fontstash_impl.c
Normal file
85
src/libs/sokol_fontstash_impl.c
Normal file
@ -0,0 +1,85 @@
|
||||
#include "stdlib.h"
|
||||
#include "stdio.h"
|
||||
#include "stdbool.h"
|
||||
#include <stdint.h>
|
||||
#define FONTSTASH_IMPLEMENTATION
|
||||
#include "fontstash.h"
|
||||
|
||||
#include "sokol/sokol_gfx.h"
|
||||
#include "sokol/sokol_gl.h"
|
||||
#define SOKOL_FONTSTASH_IMPL
|
||||
#include "sokol_fontstash.h"
|
||||
|
||||
typedef struct FONSstate FONSstate;
|
||||
|
||||
// Expose private functions so that `getTextBoundsUtf8` could be implemented in zig
|
||||
FONSstate* zig_fons__getState(FONScontext* stash)
|
||||
{
|
||||
return fons__getState(stash);
|
||||
}
|
||||
|
||||
FONSfont* zig_getFont(FONScontext* stash, int index)
|
||||
{
|
||||
if (index < 0 || index >= stash->nfonts) {
|
||||
return NULL;
|
||||
}
|
||||
FONSfont *font = stash->fonts[index];
|
||||
if (font->data == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return font;
|
||||
}
|
||||
|
||||
bool zig_isTopLeft(FONScontext* stash)
|
||||
{
|
||||
return stash->params.flags & FONS_ZERO_TOPLEFT;
|
||||
}
|
||||
|
||||
int zig_getGlyphIndex(FONSglyph* glyph)
|
||||
{
|
||||
return glyph->index;
|
||||
}
|
||||
|
||||
float zig_fons__tt_getPixelHeightScale(FONSfont *font, float size)
|
||||
{
|
||||
return fons__tt_getPixelHeightScale(&font->font, size);
|
||||
}
|
||||
|
||||
float zig_fons__getVertAlign(FONScontext* stash, FONSfont* font, int align, short isize)
|
||||
{
|
||||
return fons__getVertAlign(stash, font, align, isize);
|
||||
}
|
||||
|
||||
FONSglyph* zig_fons__getGlyph(
|
||||
FONScontext* stash,
|
||||
FONSfont* font,
|
||||
unsigned int codepoint,
|
||||
short isize,
|
||||
short iblur
|
||||
)
|
||||
{
|
||||
return fons__getGlyph(stash, font, codepoint, isize, iblur);
|
||||
}
|
||||
|
||||
void zig_fons__getQuad(
|
||||
FONScontext* stash,
|
||||
FONSfont* font,
|
||||
int prevGlyphIndex, FONSglyph* glyph,
|
||||
float scale,
|
||||
float spacing,
|
||||
float* x, float* y,
|
||||
FONSquad* q
|
||||
)
|
||||
{
|
||||
fons__getQuad(
|
||||
stash,
|
||||
font,
|
||||
prevGlyphIndex,
|
||||
glyph,
|
||||
scale,
|
||||
spacing,
|
||||
x, y,
|
||||
q
|
||||
);
|
||||
}
|
||||
2
src/libs/stb_image.c
Normal file
2
src/libs/stb_image.c
Normal file
@ -0,0 +1,2 @@
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
326
src/main.zig
326
src/main.zig
@ -1,6 +1,328 @@
|
||||
const std = @import("std");
|
||||
const tracy = @import("tracy");
|
||||
const Gfx = @import("./graphics.zig");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const Window = @import("./window.zig");
|
||||
const Event = Window.Event;
|
||||
const MouseButton = Window.MouseButton;
|
||||
|
||||
const Math = @import("./math.zig");
|
||||
const Vec2 = Math.Vec2;
|
||||
|
||||
const sokol = @import("sokol");
|
||||
const slog = sokol.log;
|
||||
const sg = sokol.gfx;
|
||||
const sapp = sokol.app;
|
||||
const sglue = sokol.glue;
|
||||
|
||||
const log = std.log.scoped(.ui);
|
||||
|
||||
var window: Window = undefined;
|
||||
var event_queue: std.ArrayListUnmanaged(Event) = .empty;
|
||||
var mouse_inside_window: bool = false;
|
||||
var event_queue_full_shown = false;
|
||||
var sokol_logger: sapp.Logger = .{ .func = sokolLogCallback };
|
||||
|
||||
fn signalHandler(sig: i32) callconv(.c) void {
|
||||
_ = sig;
|
||||
sapp.requestQuit();
|
||||
}
|
||||
|
||||
fn initErrorable() !void {
|
||||
// const allocator = window.app.allocator;
|
||||
|
||||
try Gfx.init(.{
|
||||
.allocator = window.gpa,
|
||||
.logger = @as(*sokol.gfx.Logger, @ptrFromInt(@intFromPtr(&sokol_logger))).*,
|
||||
});
|
||||
}
|
||||
|
||||
export fn init() void {
|
||||
var zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
initErrorable() catch |e| {
|
||||
log.err("init() failed: {}", .{e});
|
||||
sapp.requestQuit();
|
||||
};
|
||||
}
|
||||
|
||||
fn cStrToZig(c_str: [*c]const u8) [:0]const u8 {
|
||||
return @import("std").mem.span(c_str);
|
||||
}
|
||||
|
||||
fn sokolLogFmt(log_level: u32, comptime format: []const u8, args: anytype) void {
|
||||
const log_sokol = std.log.scoped(.sokol);
|
||||
|
||||
if (log_level == 0) {
|
||||
log_sokol.err(format, args);
|
||||
} else if (log_level == 1) {
|
||||
log_sokol.err(format, args);
|
||||
} else if (log_level == 2) {
|
||||
log_sokol.warn(format, args);
|
||||
} else {
|
||||
log_sokol.info(format, args);
|
||||
}
|
||||
}
|
||||
|
||||
fn sokolLogCallback(tag: [*c]const u8, log_level: u32, log_item: u32, message: [*c]const u8, line_nr: u32, filename: [*c]const u8, user_data: ?*anyopaque) callconv(.c) void {
|
||||
_ = user_data;
|
||||
|
||||
if (filename != null) {
|
||||
const format = "[{s}][id:{}] {s}:{}: {s}";
|
||||
const args = .{
|
||||
cStrToZig(tag orelse "-"),
|
||||
log_item,
|
||||
std.fs.path.basename(cStrToZig(filename orelse "-")),
|
||||
line_nr,
|
||||
cStrToZig(message orelse "")
|
||||
};
|
||||
|
||||
sokolLogFmt(log_level, format, args);
|
||||
} else {
|
||||
const format = "[{s}][id:{}] {s}";
|
||||
const args = .{
|
||||
cStrToZig(tag orelse "-"),
|
||||
log_item,
|
||||
cStrToZig(message orelse "")
|
||||
};
|
||||
|
||||
sokolLogFmt(log_level, format, args);
|
||||
}
|
||||
}
|
||||
|
||||
fn frameErrorable() !void {
|
||||
Gfx.beginFrame();
|
||||
defer Gfx.endFrame();
|
||||
|
||||
// try window.frame(event_queue.items);
|
||||
event_queue.clearRetainingCapacity();
|
||||
}
|
||||
|
||||
export fn frame() void {
|
||||
tracy.frameMark();
|
||||
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
frameErrorable() catch |e| {
|
||||
log.err("frame() failed: {}", .{e});
|
||||
if (@errorReturnTrace()) |trace| {
|
||||
std.debug.dumpStackTrace(trace.*);
|
||||
}
|
||||
sapp.requestQuit();
|
||||
};
|
||||
}
|
||||
|
||||
export fn cleanup() void {
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
// window.beforeExit() catch |e| {
|
||||
// log.err("window.beforeExit() failed: {}", .{e});
|
||||
// if (@errorReturnTrace()) |trace| {
|
||||
// std.debug.dumpStackTrace(trace.*);
|
||||
// }
|
||||
// };
|
||||
|
||||
// const allocator = window.app.allocator;
|
||||
|
||||
window.deinit();
|
||||
Gfx.deinit();
|
||||
// event_queue.deinit(allocator);
|
||||
}
|
||||
|
||||
fn appendToEventQueue(e_ptr: [*c]const sapp.Event) !bool {
|
||||
const e = e_ptr.*;
|
||||
blk: switch (e.type) {
|
||||
.MOUSE_DOWN => {
|
||||
const mouse_button = Window.MouseButton.fromSokol(e.mouse_button) orelse break :blk;
|
||||
|
||||
try appendEvent(Event{
|
||||
.mouse_pressed = .{
|
||||
.button = mouse_button,
|
||||
.position = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.MOUSE_UP => {
|
||||
const mouse_button = MouseButton.fromSokol(e.mouse_button) orelse break :blk;
|
||||
|
||||
try appendEvent(Event{
|
||||
.mouse_released = .{
|
||||
.button = mouse_button,
|
||||
.position = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.MOUSE_MOVE => {
|
||||
if (!mouse_inside_window) {
|
||||
try appendEvent(Event{
|
||||
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
});
|
||||
}
|
||||
|
||||
try appendEvent(Event{
|
||||
.mouse_move = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
});
|
||||
|
||||
mouse_inside_window = true;
|
||||
return true;
|
||||
},
|
||||
.MOUSE_ENTER => {
|
||||
if (!mouse_inside_window) {
|
||||
try appendEvent(Event{
|
||||
.mouse_enter = Vec2.init(e.mouse_x, e.mouse_y)
|
||||
});
|
||||
}
|
||||
|
||||
mouse_inside_window = true;
|
||||
return true;
|
||||
},
|
||||
.RESIZED => {
|
||||
if (mouse_inside_window) {
|
||||
try appendEvent(Event{
|
||||
.mouse_leave = {}
|
||||
});
|
||||
}
|
||||
|
||||
try appendEvent(Event{
|
||||
.window_resize = {}
|
||||
});
|
||||
|
||||
mouse_inside_window = false;
|
||||
return true;
|
||||
},
|
||||
.MOUSE_LEAVE => {
|
||||
if (mouse_inside_window) {
|
||||
try appendEvent(Event{
|
||||
.mouse_leave = {}
|
||||
});
|
||||
}
|
||||
|
||||
mouse_inside_window = false;
|
||||
return true;
|
||||
},
|
||||
.MOUSE_SCROLL => {
|
||||
try appendEvent(Event{
|
||||
.mouse_scroll = Vec2.init(e.scroll_x, e.scroll_y)
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.KEY_DOWN => {
|
||||
try appendEvent(Event{
|
||||
.key_pressed = .{
|
||||
.code = @enumFromInt(@intFromEnum(e.key_code)),
|
||||
.repeat = e.key_repeat
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.KEY_UP => {
|
||||
try appendEvent(Event{
|
||||
.key_released = @enumFromInt(@intFromEnum(e.key_code))
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.CHAR => {
|
||||
try appendEvent(Event{
|
||||
.char = @intCast(e.char_code)
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
.QUIT_REQUESTED => {
|
||||
// TODO: handle quit request. Maybe show confirmation window in certain cases.
|
||||
},
|
||||
else => {}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn appendEvent(e: Event) !void {
|
||||
event_queue.appendBounded(e) catch return error.EventQueueFull;
|
||||
}
|
||||
|
||||
export fn event(e_ptr: [*c]const sapp.Event) void {
|
||||
const zone = tracy.initZone(@src(), .{ });
|
||||
defer zone.deinit();
|
||||
|
||||
if (Gfx.event(e_ptr)) {
|
||||
appendEvent(Event{
|
||||
.mouse_leave = {}
|
||||
}) catch {};
|
||||
mouse_inside_window = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const consumed_event = appendToEventQueue(e_ptr) catch |e| switch (e) {
|
||||
error.EventQueueFull => blk: {
|
||||
if (!event_queue_full_shown) {
|
||||
log.warn("Event queue is full! Frame is taking too long to process", .{});
|
||||
event_queue_full_shown = true;
|
||||
}
|
||||
break :blk false;
|
||||
},
|
||||
// else => blk: {
|
||||
// log.err("Failed to append event to queue: {}", .{e});
|
||||
// if (@errorReturnTrace()) |trace| {
|
||||
// std.debug.dumpStackTrace(trace.*);
|
||||
// }
|
||||
// break :blk false;
|
||||
// }
|
||||
};
|
||||
|
||||
if (consumed_event) {
|
||||
event_queue_full_shown = false;
|
||||
sapp.consumeEvent();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
// Prints to stderr, ignoring potential errors.
|
||||
std.debug.print("All your {s} are belong to us.\n", .{"codebase"});
|
||||
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
|
||||
defer _ = debug_allocator.deinit();
|
||||
|
||||
var gpa: std.mem.Allocator = undefined;
|
||||
if (builtin.mode == .ReleaseFast) {
|
||||
gpa = std.heap.smp_allocator;
|
||||
} else {
|
||||
gpa = debug_allocator.allocator();
|
||||
}
|
||||
|
||||
// TODO: Use tracy TracingAllocator
|
||||
|
||||
tracy.setThreadName("Main");
|
||||
|
||||
try Window.init(&window, gpa);
|
||||
|
||||
var sa: std.posix.Sigaction = .{
|
||||
.handler = .{ .handler = signalHandler },
|
||||
.mask = std.posix.sigemptyset(),
|
||||
.flags = std.posix.SA.RESTART,
|
||||
};
|
||||
std.posix.sigaction(std.posix.SIG.INT, &sa, null);
|
||||
|
||||
sapp.run(.{
|
||||
.init_cb = init,
|
||||
.frame_cb = frame,
|
||||
.cleanup_cb = cleanup,
|
||||
.event_cb = event,
|
||||
.width = 640,
|
||||
.height = 480,
|
||||
.icon = .{ .sokol_default = true },
|
||||
.high_dpi = true,
|
||||
.sample_count = 4,
|
||||
.window_title = "Game",
|
||||
.logger = sokol_logger,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
399
src/math.zig
Normal file
399
src/math.zig
Normal file
@ -0,0 +1,399 @@
|
||||
const std = @import("std");
|
||||
const assert = std.debug.assert;
|
||||
|
||||
pub const 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_kb = 1000;
|
||||
pub const bytes_per_mb = bytes_per_kb * 1000;
|
||||
pub const bytes_per_gb = bytes_per_mb * 1000;
|
||||
|
||||
pub const Vec2 = extern struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
|
||||
pub const zero = init(0, 0);
|
||||
|
||||
pub fn init(x: f32, y: f32) Vec2 {
|
||||
return Vec2{
|
||||
.x = x,
|
||||
.y = y,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initAngle(angle: f32) Vec2 {
|
||||
return Vec2{
|
||||
.x = @cos(angle),
|
||||
.y = @sin(angle),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn rotateLeft90(self: Vec2) Vec2 {
|
||||
return Vec2.init(self.y, -self.x);
|
||||
}
|
||||
|
||||
pub fn rotateRight90(self: Vec2) Vec2 {
|
||||
return Vec2.init(-self.y, self.x);
|
||||
}
|
||||
|
||||
pub fn flip(self: Vec2) Vec2 {
|
||||
return Vec2.init(-self.x, -self.y);
|
||||
}
|
||||
|
||||
pub fn add(self: Vec2, other: Vec2) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x + other.x,
|
||||
self.y + other.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn sub(self: Vec2, other: Vec2) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x - other.x,
|
||||
self.y - other.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn multiplyScalar(self: Vec2, value: f32) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x * value,
|
||||
self.y * value,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn multiply(self: Vec2, other: Vec2) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x * other.x,
|
||||
self.y * other.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn divide(self: Vec2, other: Vec2) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x / other.x,
|
||||
self.y / other.y,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn divideScalar(self: Vec2, value: f32) Vec2 {
|
||||
return Vec2.init(
|
||||
self.x / value,
|
||||
self.y / value,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn length(self: Vec2) f32 {
|
||||
return @sqrt(self.x*self.x + self.y*self.y);
|
||||
}
|
||||
|
||||
pub fn distance(self: Vec2, other: Vec2) f32 {
|
||||
return self.sub(other).length();
|
||||
}
|
||||
|
||||
pub fn limitLength(self: Vec2, max_length: f32) Vec2 {
|
||||
const self_length = self.length();
|
||||
if (self_length > max_length) {
|
||||
return Vec2.init(self.x / self_length * max_length, self.y / self_length * max_length);
|
||||
} else {
|
||||
return self;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalized(self: Vec2) Vec2 {
|
||||
const self_length = self.length();
|
||||
if (self_length == 0) {
|
||||
return Vec2.init(0, 0);
|
||||
}
|
||||
return Vec2.init(self.x / self_length, self.y / self_length);
|
||||
}
|
||||
|
||||
pub fn initScalar(value: f32) Vec2 {
|
||||
return Vec2.init(value, value);
|
||||
}
|
||||
|
||||
pub fn eql(self: Vec2, other: Vec2) bool {
|
||||
return self.x == other.x and self.y == other.y;
|
||||
}
|
||||
|
||||
pub fn format(self: Vec2, writer: *std.io.Writer) std.io.Writer.Error!void {
|
||||
try writer.print("Vec2{{ {d}, {d} }}", .{ self.x, self.y });
|
||||
}
|
||||
};
|
||||
|
||||
pub const Vec3 = extern struct {
|
||||
x: f32, y: f32, z: f32,
|
||||
|
||||
pub const zero = init(0, 0, 0);
|
||||
|
||||
pub fn init(x: f32, y: f32, z: f32) Vec3 {
|
||||
return Vec3{
|
||||
.x = x,
|
||||
.y = y,
|
||||
.z = z,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initScalar(value: f32) Vec3 {
|
||||
return Vec3.init(value, value, value);
|
||||
}
|
||||
|
||||
pub fn asArray(self: *Vec3) []f32 {
|
||||
const ptr: [*]f32 = @alignCast(@ptrCast(@as(*anyopaque, @ptrCast(self))));
|
||||
return ptr[0..3];
|
||||
}
|
||||
|
||||
pub fn lerp(a: Vec3, b: Vec3, t: f32) Vec3 {
|
||||
return Vec3.init(
|
||||
std.math.lerp(a.x, b.x, t),
|
||||
std.math.lerp(a.y, b.y, t),
|
||||
std.math.lerp(a.z, b.z, t),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn clamp(self: Vec3, min_value: f32, max_value: f32) Vec3 {
|
||||
return Vec3.init(
|
||||
std.math.clamp(self.x, min_value, max_value),
|
||||
std.math.clamp(self.y, min_value, max_value),
|
||||
std.math.clamp(self.z, min_value, max_value),
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Vec4 = extern struct {
|
||||
x: f32, y: f32, z: f32, w: f32,
|
||||
|
||||
pub const zero = init(0, 0, 0, 0);
|
||||
|
||||
pub fn init(x: f32, y: f32, z: f32, w: f32) Vec4 {
|
||||
return Vec4{
|
||||
.x = x,
|
||||
.y = y,
|
||||
.z = z,
|
||||
.w = w
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initVec3XYZ(vec3: Vec3, w: f32) Vec4 {
|
||||
return init(vec3.x, vec3.y, vec3.z, w);
|
||||
}
|
||||
|
||||
pub fn initScalar(value: f32) Vec4 {
|
||||
return Vec4.init(value, value, value, value);
|
||||
}
|
||||
|
||||
pub fn multiplyMat4(left: Vec4, right: Mat4) Vec4 {
|
||||
var result: Vec4 = undefined;
|
||||
|
||||
// TODO: SIMD
|
||||
|
||||
result.x = left.x * right.columns[0][0];
|
||||
result.y = left.x * right.columns[0][1];
|
||||
result.z = left.x * right.columns[0][2];
|
||||
result.w = left.x * right.columns[0][3];
|
||||
|
||||
result.x += left.y * right.columns[1][0];
|
||||
result.y += left.y * right.columns[1][1];
|
||||
result.z += left.y * right.columns[1][2];
|
||||
result.w += left.y * right.columns[1][3];
|
||||
|
||||
result.x += left.z * right.columns[2][0];
|
||||
result.y += left.z * right.columns[2][1];
|
||||
result.z += left.z * right.columns[2][2];
|
||||
result.w += left.z * right.columns[2][3];
|
||||
|
||||
result.x += left.w * right.columns[3][0];
|
||||
result.y += left.w * right.columns[3][1];
|
||||
result.z += left.w * right.columns[3][2];
|
||||
result.w += left.w * right.columns[3][3];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
pub fn multiply(left: Vec4, right: Vec4) Vec4 {
|
||||
return init(
|
||||
left.x * right.x,
|
||||
left.y * right.y,
|
||||
left.z * right.z,
|
||||
left.w * right.w
|
||||
);
|
||||
}
|
||||
|
||||
pub fn asArray(self: *Vec4) []f32 {
|
||||
const ptr: [*]f32 = @alignCast(@ptrCast(@as(*anyopaque, @ptrCast(self))));
|
||||
return ptr[0..4];
|
||||
}
|
||||
|
||||
pub fn initArray(array: []const f32) Vec4 {
|
||||
return Vec4.init(array[0], array[1], array[2], array[3]);
|
||||
}
|
||||
|
||||
pub fn lerp(a: Vec4, b: Vec4, t: f32) Vec4 {
|
||||
return Vec4.init(
|
||||
std.math.lerp(a.x, b.x, t),
|
||||
std.math.lerp(a.y, b.y, t),
|
||||
std.math.lerp(a.z, b.z, t),
|
||||
std.math.lerp(a.w, b.w, t),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn clamp(self: Vec4, min_value: f32, max_value: f32) Vec4 {
|
||||
return Vec4.init(
|
||||
std.math.clamp(self.x, min_value, max_value),
|
||||
std.math.clamp(self.y, min_value, max_value),
|
||||
std.math.clamp(self.z, min_value, max_value),
|
||||
std.math.clamp(self.w, min_value, max_value),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn toVec3XYZ(self: Vec4) Vec3 {
|
||||
return Vec3.init(self.x, self.y, self.z);
|
||||
}
|
||||
};
|
||||
|
||||
pub const Mat4 = extern struct {
|
||||
columns: [4][4]f32,
|
||||
|
||||
pub fn initZero() Mat4 {
|
||||
var self: Mat4 = undefined;
|
||||
@memset(self.asArray(), 0);
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn initIdentity() Mat4 {
|
||||
return Mat4.initDiagonal(1);
|
||||
}
|
||||
|
||||
pub fn initDiagonal(value: f32) Mat4 {
|
||||
var self = Mat4.initZero();
|
||||
self.columns[0][0] = value;
|
||||
self.columns[1][1] = value;
|
||||
self.columns[2][2] = value;
|
||||
self.columns[3][3] = value;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn multiply(left: Mat4, right: Mat4) Mat4 {
|
||||
var self: Mat4 = undefined;
|
||||
|
||||
inline for (.{ 0, 1, 2, 3 }) |i| {
|
||||
var column = Vec4.initArray(&right.columns[i]).multiplyMat4(left);
|
||||
@memcpy(&self.columns[i], column.asArray());
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn initScale(scale: Vec3) Mat4 {
|
||||
var self = Mat4.initIdentity();
|
||||
self.columns[0][0] = scale.x;
|
||||
self.columns[1][1] = scale.y;
|
||||
self.columns[2][2] = scale.z;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn initTranslate(offset: Vec3) Mat4 {
|
||||
var self = Mat4.initIdentity();
|
||||
self.columns[3][0] = offset.x;
|
||||
self.columns[3][1] = offset.y;
|
||||
self.columns[3][2] = offset.z;
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn asArray(self: *Mat4) []f32 {
|
||||
const ptr: [*]f32 = @alignCast(@ptrCast(@as(*anyopaque, @ptrCast(&self.columns))));
|
||||
return ptr[0..16];
|
||||
}
|
||||
};
|
||||
|
||||
pub const Rect = struct {
|
||||
pos: Vec2,
|
||||
size: Vec2,
|
||||
|
||||
pub const zero = Rect{
|
||||
.pos = Vec2.zero,
|
||||
.size = Vec2.zero
|
||||
};
|
||||
|
||||
pub fn init(x: f32, y: f32, width: f32, height: f32) Rect {
|
||||
return Rect{
|
||||
.pos = Vec2.init(x, y),
|
||||
.size = Vec2.init(width, height)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn clip(self: Rect, other: Rect) Rect {
|
||||
const left_edge = @max(self.left(), other.left());
|
||||
const right_edge = @min(self.right(), other.right());
|
||||
const top_edge = @max(self.top(), other.top());
|
||||
const bottom_edge = @min(self.bottom(), other.bottom());
|
||||
return Rect.init(
|
||||
left_edge,
|
||||
top_edge,
|
||||
right_edge - left_edge,
|
||||
bottom_edge - top_edge
|
||||
);
|
||||
}
|
||||
|
||||
pub fn left(self: Rect) f32 {
|
||||
return self.pos.x;
|
||||
}
|
||||
|
||||
pub fn right(self: Rect) f32 {
|
||||
return self.pos.x + self.size.x;
|
||||
}
|
||||
|
||||
pub fn top(self: Rect) f32 {
|
||||
return self.pos.y;
|
||||
}
|
||||
|
||||
pub fn bottom(self: Rect) f32 {
|
||||
return self.pos.y + self.size.y;
|
||||
}
|
||||
|
||||
pub fn isInside(self: Rect, pos: Vec2) bool {
|
||||
const x_overlap = self.pos.x <= pos.x and pos.x < self.pos.x + self.size.x;
|
||||
const y_overlap = self.pos.y <= pos.y and pos.y < self.pos.y + self.size.y;
|
||||
return x_overlap and y_overlap;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Line = struct {
|
||||
p0: Vec2,
|
||||
p1: Vec2
|
||||
};
|
||||
|
||||
pub fn isInsideRect(rect_pos: Vec2, rect_size: Vec2, pos: Vec2) bool {
|
||||
const rect = Rect{
|
||||
.pos = rect_pos,
|
||||
.size = rect_size
|
||||
};
|
||||
return rect.isInside(pos);
|
||||
}
|
||||
|
||||
pub fn rgba(r: u8, g: u8, b: u8, a: f32) Vec4 {
|
||||
assert(0 <= a and a <= 1);
|
||||
return Vec4.init(
|
||||
@as(f32, @floatFromInt(r)) / 255,
|
||||
@as(f32, @floatFromInt(g)) / 255,
|
||||
@as(f32, @floatFromInt(b)) / 255,
|
||||
a,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn rgb(r: u8, g: u8, b: u8) Vec4 {
|
||||
return rgba(r, g, b, 1);
|
||||
}
|
||||
|
||||
pub fn rgb_hex(text: []const u8) ?Vec4 {
|
||||
if (text.len != 7) {
|
||||
return null;
|
||||
}
|
||||
if (text[0] != '#') {
|
||||
return null;
|
||||
}
|
||||
const r = std.fmt.parseInt(u8, text[1..3], 16) catch return null;
|
||||
const g = std.fmt.parseInt(u8, text[3..5], 16) catch return null;
|
||||
const b = std.fmt.parseInt(u8, text[5..7], 16) catch return null;
|
||||
return rgb(r, g, b);
|
||||
}
|
||||
187
src/window.zig
Normal file
187
src/window.zig
Normal file
@ -0,0 +1,187 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
|
||||
const sokol = @import("sokol");
|
||||
|
||||
const Math = @import("./math.zig");
|
||||
const Vec2 = Math.Vec2;
|
||||
|
||||
const Window = @This();
|
||||
|
||||
pub const MouseButton = enum {
|
||||
left,
|
||||
right,
|
||||
middle,
|
||||
|
||||
pub fn fromSokol(mouse_button: sokol.app.Mousebutton) ?MouseButton {
|
||||
return switch(mouse_button) {
|
||||
.LEFT => MouseButton.left,
|
||||
.RIGHT => MouseButton.right,
|
||||
.MIDDLE => MouseButton.middle,
|
||||
else => null
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const KeyCode = enum(std.math.IntFittingRange(0, sokol.app.max_keycodes-1)) {
|
||||
SPACE = 32,
|
||||
APOSTROPHE = 39,
|
||||
COMMA = 44,
|
||||
MINUS = 45,
|
||||
PERIOD = 46,
|
||||
SLASH = 47,
|
||||
_0 = 48,
|
||||
_1 = 49,
|
||||
_2 = 50,
|
||||
_3 = 51,
|
||||
_4 = 52,
|
||||
_5 = 53,
|
||||
_6 = 54,
|
||||
_7 = 55,
|
||||
_8 = 56,
|
||||
_9 = 57,
|
||||
SEMICOLON = 59,
|
||||
EQUAL = 61,
|
||||
A = 65,
|
||||
B = 66,
|
||||
C = 67,
|
||||
D = 68,
|
||||
E = 69,
|
||||
F = 70,
|
||||
G = 71,
|
||||
H = 72,
|
||||
I = 73,
|
||||
J = 74,
|
||||
K = 75,
|
||||
L = 76,
|
||||
M = 77,
|
||||
N = 78,
|
||||
O = 79,
|
||||
P = 80,
|
||||
Q = 81,
|
||||
R = 82,
|
||||
S = 83,
|
||||
T = 84,
|
||||
U = 85,
|
||||
V = 86,
|
||||
W = 87,
|
||||
X = 88,
|
||||
Y = 89,
|
||||
Z = 90,
|
||||
LEFT_BRACKET = 91,
|
||||
BACKSLASH = 92,
|
||||
RIGHT_BRACKET = 93,
|
||||
GRAVE_ACCENT = 96,
|
||||
WORLD_1 = 161,
|
||||
WORLD_2 = 162,
|
||||
ESCAPE = 256,
|
||||
ENTER = 257,
|
||||
TAB = 258,
|
||||
BACKSPACE = 259,
|
||||
INSERT = 260,
|
||||
DELETE = 261,
|
||||
RIGHT = 262,
|
||||
LEFT = 263,
|
||||
DOWN = 264,
|
||||
UP = 265,
|
||||
PAGE_UP = 266,
|
||||
PAGE_DOWN = 267,
|
||||
HOME = 268,
|
||||
END = 269,
|
||||
CAPS_LOCK = 280,
|
||||
SCROLL_LOCK = 281,
|
||||
NUM_LOCK = 282,
|
||||
PRINT_SCREEN = 283,
|
||||
PAUSE = 284,
|
||||
F1 = 290,
|
||||
F2 = 291,
|
||||
F3 = 292,
|
||||
F4 = 293,
|
||||
F5 = 294,
|
||||
F6 = 295,
|
||||
F7 = 296,
|
||||
F8 = 297,
|
||||
F9 = 298,
|
||||
F10 = 299,
|
||||
F11 = 300,
|
||||
F12 = 301,
|
||||
F13 = 302,
|
||||
F14 = 303,
|
||||
F15 = 304,
|
||||
F16 = 305,
|
||||
F17 = 306,
|
||||
F18 = 307,
|
||||
F19 = 308,
|
||||
F20 = 309,
|
||||
F21 = 310,
|
||||
F22 = 311,
|
||||
F23 = 312,
|
||||
F24 = 313,
|
||||
F25 = 314,
|
||||
KP_0 = 320,
|
||||
KP_1 = 321,
|
||||
KP_2 = 322,
|
||||
KP_3 = 323,
|
||||
KP_4 = 324,
|
||||
KP_5 = 325,
|
||||
KP_6 = 326,
|
||||
KP_7 = 327,
|
||||
KP_8 = 328,
|
||||
KP_9 = 329,
|
||||
KP_DECIMAL = 330,
|
||||
KP_DIVIDE = 331,
|
||||
KP_MULTIPLY = 332,
|
||||
KP_SUBTRACT = 333,
|
||||
KP_ADD = 334,
|
||||
KP_ENTER = 335,
|
||||
KP_EQUAL = 336,
|
||||
LEFT_SHIFT = 340,
|
||||
LEFT_CONTROL = 341,
|
||||
LEFT_ALT = 342,
|
||||
LEFT_SUPER = 343,
|
||||
RIGHT_SHIFT = 344,
|
||||
RIGHT_CONTROL = 345,
|
||||
RIGHT_ALT = 346,
|
||||
RIGHT_SUPER = 347,
|
||||
MENU = 348,
|
||||
};
|
||||
|
||||
pub const Event = union(enum) {
|
||||
mouse_pressed: struct {
|
||||
button: MouseButton,
|
||||
position: Vec2,
|
||||
},
|
||||
mouse_released: struct {
|
||||
button: MouseButton,
|
||||
position: Vec2,
|
||||
},
|
||||
mouse_move: Vec2,
|
||||
mouse_enter: Vec2,
|
||||
mouse_leave,
|
||||
mouse_scroll: Vec2,
|
||||
key_pressed: struct {
|
||||
code: KeyCode,
|
||||
repeat: bool
|
||||
},
|
||||
key_released: KeyCode,
|
||||
window_resize,
|
||||
char: u21,
|
||||
};
|
||||
|
||||
gpa: Allocator,
|
||||
events: std.ArrayList(Event),
|
||||
|
||||
pub fn init(self: *Window, gpa: Allocator) !void {
|
||||
self.* = Window{
|
||||
.gpa = gpa,
|
||||
.events = .empty
|
||||
// .last_frame_at_ns = std.time.nanoTimestamp(),
|
||||
// .frame_arena = ArenaAllocator.init(gpa),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Window) void {
|
||||
const gpa = self.gpa;
|
||||
_ = gpa; // autofix
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user