cherry pick changes from gamejam
This commit is contained in:
parent
62cbb9e380
commit
ef5275b17f
@ -11,6 +11,9 @@ a: f32,
|
|||||||
pub const black = rgb(0, 0, 0);
|
pub const black = rgb(0, 0, 0);
|
||||||
pub const white = rgb(255, 255, 255);
|
pub const white = rgb(255, 255, 255);
|
||||||
pub const purple = rgb(255, 0, 255);
|
pub const purple = rgb(255, 0, 255);
|
||||||
|
pub const red = rgb(255, 0, 0);
|
||||||
|
pub const blue = rgb(0, 0, 255);
|
||||||
|
pub const green = rgb(0, 255, 0);
|
||||||
|
|
||||||
pub fn rgba(r: u8, g: u8, b: u8, a: f32) Color {
|
pub fn rgba(r: u8, g: u8, b: u8, a: f32) Color {
|
||||||
assert(0 <= a and a <= 1);
|
assert(0 <= a and a <= 1);
|
||||||
@ -38,3 +41,12 @@ pub fn rgb_hex(text: []const u8) ?Color {
|
|||||||
const b = std.fmt.parseInt(u8, text[5..7], 16) catch return null;
|
const b = std.fmt.parseInt(u8, text[5..7], 16) catch return null;
|
||||||
return rgb(r, g, b);
|
return rgb(r, g, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lerp(self: Color, other: Color, t: f32) Color {
|
||||||
|
return Color{
|
||||||
|
.r = std.math.lerp(self.r, other.r, t),
|
||||||
|
.g = std.math.lerp(self.g, other.g, t),
|
||||||
|
.b = std.math.lerp(self.b, other.b, t),
|
||||||
|
.a = std.math.lerp(self.a, other.a, t),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
603
src/graphics.zig
603
src/graphics.zig
@ -39,11 +39,14 @@ const State = struct {
|
|||||||
nearest_sampler: sg.Sampler,
|
nearest_sampler: sg.Sampler,
|
||||||
default_sampler: Sampler,
|
default_sampler: Sampler,
|
||||||
|
|
||||||
quads_buffer: [2048]Quad,
|
quads_buffer: [8192]VertexQuad,
|
||||||
quads: std.ArrayList(Quad),
|
quads: std.ArrayList(VertexQuad),
|
||||||
|
|
||||||
clear_color: Color,
|
clear_color: Color,
|
||||||
|
|
||||||
|
transforms_buffer: [32]TransformFrame,
|
||||||
|
transforms: std.ArrayList(TransformFrame) = .empty,
|
||||||
|
|
||||||
textures_buffer: [Texture.max_textures]Texture.SlotMap.Slot,
|
textures_buffer: [Texture.max_textures]Texture.SlotMap.Slot,
|
||||||
textures: Texture.SlotMap,
|
textures: Texture.SlotMap,
|
||||||
nil_texture: Texture.Id,
|
nil_texture: Texture.Id,
|
||||||
@ -123,7 +126,7 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
|||||||
const max_quads = self.quads_buffer.len;
|
const max_quads = self.quads_buffer.len;
|
||||||
|
|
||||||
bindings.vertex_buffers[0] = sg.makeBuffer(.{
|
bindings.vertex_buffers[0] = sg.makeBuffer(.{
|
||||||
.size = @sizeOf(Quad) * max_quads,
|
.size = @sizeOf(VertexQuad) * max_quads,
|
||||||
.usage = .{ .vertex_buffer = true, .stream_update = true },
|
.usage = .{ .vertex_buffer = true, .stream_update = true },
|
||||||
.label = "quad-vertices"
|
.label = "quad-vertices"
|
||||||
});
|
});
|
||||||
@ -163,22 +166,25 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
|||||||
|
|
||||||
.linear_sampler = linear_sampler,
|
.linear_sampler = linear_sampler,
|
||||||
.nearest_sampler = nearest_sampler,
|
.nearest_sampler = nearest_sampler,
|
||||||
.default_sampler = .linear,
|
.default_sampler = .nearest,
|
||||||
|
|
||||||
|
.transforms_buffer = undefined,
|
||||||
|
.transforms = .initBuffer(&self.transforms_buffer),
|
||||||
|
|
||||||
.textures_buffer = undefined,
|
.textures_buffer = undefined,
|
||||||
.textures = .init(.initBuffer(&self.textures_buffer)),
|
.textures = .init(&self.textures_buffer),
|
||||||
.nil_texture = undefined,
|
.nil_texture = undefined,
|
||||||
|
|
||||||
.sprites_buffer = undefined,
|
.sprites_buffer = undefined,
|
||||||
.sprites = .init(.initBuffer(&self.sprites_buffer)),
|
.sprites = .init(&self.sprites_buffer),
|
||||||
.nil_sprite = undefined,
|
.nil_sprite = undefined,
|
||||||
|
|
||||||
.spritesheets_buffer = undefined,
|
.spritesheets_buffer = undefined,
|
||||||
.spritesheets = .init(.initBuffer(&self.spritesheets_buffer)),
|
.spritesheets = .init(&self.spritesheets_buffer),
|
||||||
.nil_spritesheet = undefined,
|
.nil_spritesheet = undefined,
|
||||||
|
|
||||||
.fonts_buffer = undefined,
|
.fonts_buffer = undefined,
|
||||||
.fonts = .init(.initBuffer(&self.fonts_buffer)),
|
.fonts = .init(&self.fonts_buffer),
|
||||||
.nil_font = undefined,
|
.nil_font = undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -238,7 +244,7 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
|||||||
var pixels: [width * height * 4]u8 = undefined;
|
var pixels: [width * height * 4]u8 = undefined;
|
||||||
@memset(&pixels, 0xFF);
|
@memset(&pixels, 0xFF);
|
||||||
|
|
||||||
self.default_sprite = initSprite(.{});
|
self.default_sprite = initSprite(.{ .padding = 0 });
|
||||||
setSprite(self.default_sprite, .{
|
setSprite(self.default_sprite, .{
|
||||||
.image = ImageData{
|
.image = ImageData{
|
||||||
.width = width,
|
.width = width,
|
||||||
@ -328,9 +334,62 @@ fn toSokolColor(color: Color) sokol.gfx.Color {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn getTransformPtr() ?*TransformFrame {
|
||||||
|
const self = &g_state;
|
||||||
|
if (self.transforms.items.len == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &self.transforms.items[self.transforms.items.len-1];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transformTranslate(offset: Vec2) void {
|
||||||
|
const top_frame = getTransformPtr() orelse return;
|
||||||
|
|
||||||
|
top_frame.offset = top_frame.offset.add(offset.multiply(top_frame.scale));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transformScale(scale: Vec2) void {
|
||||||
|
const top_frame = getTransformPtr() orelse return;
|
||||||
|
|
||||||
|
top_frame.scale = top_frame.scale.multiply(scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transformPush() void {
|
||||||
|
const self = &g_state;
|
||||||
|
|
||||||
|
const top_frame = getTransformPtr() orelse return;
|
||||||
|
|
||||||
|
self.transforms.appendBounded(top_frame.*) catch {
|
||||||
|
log.warn("Transform stack limit reached!", .{});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transformPop() void {
|
||||||
|
const self = &g_state;
|
||||||
|
_ = self.transforms.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getTransform() TransformFrame {
|
||||||
|
if (getTransformPtr()) |top_frame| {
|
||||||
|
return top_frame.*;
|
||||||
|
} else {
|
||||||
|
return TransformFrame{
|
||||||
|
.offset = .init(0, 0),
|
||||||
|
.scale = .init(1, 1)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn beginFrame() void {
|
pub fn beginFrame() void {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
|
|
||||||
|
self.transforms.clearRetainingCapacity();
|
||||||
|
self.transforms.appendAssumeCapacity(TransformFrame{
|
||||||
|
.offset = .init(0, 0),
|
||||||
|
.scale = .init(1, 1),
|
||||||
|
});
|
||||||
|
|
||||||
var pass_action: sg.PassAction = .{};
|
var pass_action: sg.PassAction = .{};
|
||||||
pass_action.colors[0] = .{
|
pass_action.colors[0] = .{
|
||||||
.load_action = .CLEAR,
|
.load_action = .CLEAR,
|
||||||
@ -462,15 +521,73 @@ pub fn endFrame() void {
|
|||||||
while (spritesheet_iter.next()) |spritesheet_id| {
|
while (spritesheet_iter.next()) |spritesheet_id| {
|
||||||
repackSpritesheetIfNeeded(spritesheet_id);
|
repackSpritesheetIfNeeded(spritesheet_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.transforms.items.len > 1) {
|
||||||
|
log.warn("Too many calls to transformPush()", .{});
|
||||||
|
} else if (self.transforms.items.len == 0) {
|
||||||
|
log.warn("Too many calls to transformPop()", .{});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drawRectangle(pos: Vec2, size: Vec2, color: Color) void {
|
pub fn drawRectangle(pos: Vec2, size: Vec2, color: Color) void {
|
||||||
const self = &g_state;
|
drawQuad(Quad.initRect(pos, size), color);
|
||||||
|
|
||||||
drawSprite(self.default_sprite, pos, size, color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
pub fn drawQuad(quad: Quad, color: Color) void {
|
||||||
|
const self = &g_state;
|
||||||
|
|
||||||
|
drawSpriteQuad(self.default_sprite, quad, color);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drawRectangleOutline(pos: Vec2, size: Vec2, color: Color, width: f32, alignment: f32) void {
|
||||||
|
const self = &g_state;
|
||||||
|
|
||||||
|
const outer_rect = (Rect{ .pos = pos, .size = size }).grow(Vec2.init(width, width).multiplyScalar(1-alignment));
|
||||||
|
const inner_rect = (Rect{ .pos = pos, .size = size }).shrink(Vec2.init(width, width).multiplyScalar(alignment));
|
||||||
|
|
||||||
|
drawSpriteQuad(
|
||||||
|
self.default_sprite,
|
||||||
|
.init(.{
|
||||||
|
.init(outer_rect.right(), outer_rect.top()),
|
||||||
|
.init(outer_rect.left(), outer_rect.top()),
|
||||||
|
.init(inner_rect.right(), inner_rect.top()),
|
||||||
|
.init(inner_rect.left(), inner_rect.top()),
|
||||||
|
}),
|
||||||
|
color
|
||||||
|
);
|
||||||
|
drawSpriteQuad(
|
||||||
|
self.default_sprite,
|
||||||
|
.init(.{
|
||||||
|
.init(outer_rect.right(), outer_rect.top()),
|
||||||
|
.init(inner_rect.right(), inner_rect.top()),
|
||||||
|
.init(outer_rect.right(), outer_rect.bottom()),
|
||||||
|
.init(inner_rect.right(), inner_rect.bottom()),
|
||||||
|
}),
|
||||||
|
color
|
||||||
|
);
|
||||||
|
drawSpriteQuad(
|
||||||
|
self.default_sprite,
|
||||||
|
.init(.{
|
||||||
|
.init(outer_rect.left(), outer_rect.top()),
|
||||||
|
.init(inner_rect.left(), inner_rect.top()),
|
||||||
|
.init(outer_rect.left(), outer_rect.bottom()),
|
||||||
|
.init(inner_rect.left(), inner_rect.bottom()),
|
||||||
|
}),
|
||||||
|
color
|
||||||
|
);
|
||||||
|
drawSpriteQuad(
|
||||||
|
self.default_sprite,
|
||||||
|
.init(.{
|
||||||
|
.init(outer_rect.right(), outer_rect.bottom()),
|
||||||
|
.init(outer_rect.left(), outer_rect.bottom()),
|
||||||
|
.init(inner_rect.right(), inner_rect.bottom()),
|
||||||
|
.init(inner_rect.left(), inner_rect.bottom()),
|
||||||
|
}),
|
||||||
|
color
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drawLine(from: Vec2, to: Vec2, color: Color, width: f32) void {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
|
|
||||||
const step = to.sub(from).normalized().multiplyScalar(width/2);
|
const step = to.sub(from).normalized().multiplyScalar(width/2);
|
||||||
@ -480,18 +597,11 @@ pub fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
|
|||||||
const top_right = to.add(step.rotateLeft90());
|
const top_right = to.add(step.rotateLeft90());
|
||||||
const bottom_right = to.add(step.rotateRight90());
|
const bottom_right = to.add(step.rotateRight90());
|
||||||
|
|
||||||
var quad: Quad = undefined;
|
drawSpriteQuad(
|
||||||
quad.setColor(color);
|
self.default_sprite,
|
||||||
quad.setUVRect(.init(0, 0, 1, 1));
|
.init(.{ top_right, top_left, bottom_right, bottom_left }),
|
||||||
quad.vertices[0] = top_right;
|
color
|
||||||
quad.vertices[1] = top_left;
|
);
|
||||||
quad.vertices[2] = bottom_right;
|
|
||||||
quad.vertices[3] = bottom_left;
|
|
||||||
|
|
||||||
draw(.{
|
|
||||||
.texture = self.default_sprite,
|
|
||||||
.quad = quad
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TextureOptions = struct {
|
const TextureOptions = struct {
|
||||||
@ -500,7 +610,7 @@ const TextureOptions = struct {
|
|||||||
|
|
||||||
pub fn initTexture(opts: TextureOptions) Texture.Id {
|
pub fn initTexture(opts: TextureOptions) Texture.Id {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
const id = self.textures.insertBounded() catch {
|
const id = self.textures.insertUndefined() catch {
|
||||||
log.warn("Failed to create texture, limit reached! limit: {}", .{self.textures.slots.capacity});
|
log.warn("Failed to create texture, limit reached! limit: {}", .{self.textures.slots.capacity});
|
||||||
return self.nil_texture;
|
return self.nil_texture;
|
||||||
};
|
};
|
||||||
@ -649,7 +759,7 @@ pub fn setTexture(id: Texture.Id, texture_data: ImageData) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn drawTexture(id: Texture.Id, pos: Vec2, size: Vec2, color: Color) void {
|
pub fn drawTexture(id: Texture.Id, pos: Vec2, size: Vec2, color: Color) void {
|
||||||
var quad: Quad = undefined;
|
var quad: VertexQuad = undefined;
|
||||||
quad.setColor(color);
|
quad.setColor(color);
|
||||||
quad.setRect(.{ .pos = pos, .size = size });
|
quad.setRect(.{ .pos = pos, .size = size });
|
||||||
quad.setUVRect(.init(0, 0, 1, 1));
|
quad.setUVRect(.init(0, 0, 1, 1));
|
||||||
@ -689,7 +799,7 @@ pub const Sampler = enum {
|
|||||||
const DrawOptions = struct {
|
const DrawOptions = struct {
|
||||||
sampler: ?Sampler = null,
|
sampler: ?Sampler = null,
|
||||||
texture: Texture.Id,
|
texture: Texture.Id,
|
||||||
quad: Quad
|
quad: VertexQuad
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn draw(opts: DrawOptions) void {
|
pub fn draw(opts: DrawOptions) void {
|
||||||
@ -738,6 +848,12 @@ pub fn draw(opts: DrawOptions) void {
|
|||||||
flush(.{});
|
flush(.{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getTransformPtr()) |transform| {
|
||||||
|
for (&quad.vertices) |*vertex| {
|
||||||
|
vertex.position = transform.apply(vertex.position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.quads.appendAssumeCapacity(quad);
|
self.quads.appendAssumeCapacity(quad);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -754,7 +870,7 @@ pub fn getNilSprite() Sprite.Id {
|
|||||||
|
|
||||||
pub fn initSprite(opts: SpriteOptions) Sprite.Id {
|
pub fn initSprite(opts: SpriteOptions) Sprite.Id {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
const id = self.sprites.insertBounded() catch {
|
const id = self.sprites.insertUndefined() catch {
|
||||||
log.warn("Failed to create sprite, limit reached! limit: {}", .{self.sprites.slots.capacity});
|
log.warn("Failed to create sprite, limit reached! limit: {}", .{self.sprites.slots.capacity});
|
||||||
return self.nil_sprite;
|
return self.nil_sprite;
|
||||||
};
|
};
|
||||||
@ -1055,31 +1171,39 @@ pub fn getSpriteUVRect(id: Sprite.Id) ?Rect {
|
|||||||
|
|
||||||
assert(sprite.position != null);
|
assert(sprite.position != null);
|
||||||
|
|
||||||
|
|
||||||
const sprite_size = Vec2.initFromInt(u32, sprite_data.width, sprite_data.height);
|
const sprite_size = Vec2.initFromInt(u32, sprite_data.width, sprite_data.height);
|
||||||
return Rect{
|
var uv_rect = Rect{
|
||||||
.pos = sprite.position.?.divide(spritesheet.size),
|
.pos = sprite.position.?.divide(spritesheet.size),
|
||||||
.size = sprite_size.divide(spritesheet.size),
|
.size = sprite_size.divide(spritesheet.size),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const texel_size = Vec2.init(1, 1).divide(spritesheet.size);
|
||||||
|
uv_rect = uv_rect.shrink(texel_size.divideScalar(8));
|
||||||
|
|
||||||
|
return uv_rect;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drawSprite(id: Sprite.Id, pos: Vec2, size: Vec2, color: Color) void {
|
pub fn drawSpriteQuad(id: Sprite.Id, points: Quad, color: Color) void {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
|
|
||||||
var quad: Quad = undefined;
|
var quad: VertexQuad = undefined;
|
||||||
quad.setColor(color);
|
quad.setColor(color);
|
||||||
quad.setRect(.{ .pos = pos, .size = size });
|
quad.vertices[0].position = points.positions[0];
|
||||||
|
quad.vertices[1].position = points.positions[1];
|
||||||
|
quad.vertices[2].position = points.positions[2];
|
||||||
|
quad.vertices[3].position = points.positions[3];
|
||||||
|
|
||||||
var texture: ?Texture.Id = null;
|
var texture: ?Texture.Id = null;
|
||||||
if (id != self.nil_sprite) {
|
if (id != self.nil_sprite) {
|
||||||
if (getSpriteUVRect(id)) |sprite_uv| {
|
if (getSpriteUVRect(id)) |sprite_uv| {
|
||||||
quad.setUVRect(sprite_uv);
|
quad.setUVRect(sprite_uv);
|
||||||
if (self.sprites.get(id)) |sprite| {
|
|
||||||
if (self.spritesheets.get(sprite.spritesheet)) |spritesheet| {
|
const sprite = self.sprites.getAssumeExists(id);
|
||||||
|
const spritesheet = self.spritesheets.getAssumeExists(sprite.spritesheet);
|
||||||
texture = spritesheet.texture;
|
texture = spritesheet.texture;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
draw(.{
|
draw(.{
|
||||||
.texture = texture orelse self.nil_texture,
|
.texture = texture orelse self.nil_texture,
|
||||||
@ -1087,13 +1211,18 @@ pub fn drawSprite(id: Sprite.Id, pos: Vec2, size: Vec2, color: Color) void {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn drawSprite(id: Sprite.Id, pos: Vec2, size: Vec2, color: Color) void {
|
||||||
|
const rect = Quad.initRect(pos, size);
|
||||||
|
drawSpriteQuad(id, rect, color);
|
||||||
|
}
|
||||||
|
|
||||||
pub const SpriteSheetOptions = struct {
|
pub const SpriteSheetOptions = struct {
|
||||||
format: ImageData.Format = .rgba8
|
format: ImageData.Format = .rgba8
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn initSpritesheet(opts: SpriteSheetOptions) Spritesheet.Id {
|
pub fn initSpritesheet(opts: SpriteSheetOptions) Spritesheet.Id {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
const id = self.spritesheets.insertBounded() catch {
|
const id = self.spritesheets.insertUndefined() catch {
|
||||||
log.warn("Failed to create spritesheet, limit reached! limit: {}", .{self.spritesheets.slots.capacity});
|
log.warn("Failed to create spritesheet, limit reached! limit: {}", .{self.spritesheets.slots.capacity});
|
||||||
return self.nil_spritesheet;
|
return self.nil_spritesheet;
|
||||||
};
|
};
|
||||||
@ -1127,7 +1256,7 @@ pub fn deinitSpritesheet(id: Spritesheet.Id) void {
|
|||||||
|
|
||||||
pub fn initFont() Font.Id {
|
pub fn initFont() Font.Id {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
const id = self.fonts.insertBounded() catch {
|
const id = self.fonts.insertUndefined() catch {
|
||||||
log.warn("Failed to create font, limit reached! limit: {}", .{self.fonts.slots.capacity});
|
log.warn("Failed to create font, limit reached! limit: {}", .{self.fonts.slots.capacity});
|
||||||
return self.nil_font;
|
return self.nil_font;
|
||||||
};
|
};
|
||||||
@ -1183,63 +1312,58 @@ pub fn setFont(id: Font.Id, ttf_data: [:0]const u8) void {
|
|||||||
font.cache = .init();
|
font.cache = .init();
|
||||||
}
|
}
|
||||||
|
|
||||||
const DrawTextOptions = struct {
|
fn getFont(id: Font.Id) ?*Font {
|
||||||
text: []const u8,
|
|
||||||
pos: Vec2,
|
|
||||||
height: f32,
|
|
||||||
color: Color = .white
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn drawText(id: Font.Id, opts: DrawTextOptions) void {
|
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
if (id == self.nil_font) {
|
if (id == self.nil_font) {
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const font = self.fonts.get(id) orelse {
|
return self.fonts.get(id);
|
||||||
log.warn("Attempt to draw text with font that doesn't exist: {}", .{id});
|
}
|
||||||
return;
|
|
||||||
};
|
|
||||||
const stb_font = font.stb orelse {
|
|
||||||
log.warn("Attempt to draw text with font that isn't set: {}", .{id});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
const fallback_glyph = stb_font.findGlyphIndex('?') orelse {
|
pub fn getGlyphIndex(id: Font.Id, codepoint: u21) ?GlyphIndex {
|
||||||
log.warn("Failed to find fallback glyph '?'", .{});
|
const font = getFont(id) orelse return null;
|
||||||
return;
|
const stb_font = font.stb orelse return null;
|
||||||
};
|
|
||||||
|
|
||||||
const scale = stb_font.scaleForPixelHeight(opts.height);
|
if (stb_font.findGlyphIndex(codepoint)) |index| {
|
||||||
const scale_x = scale;
|
return index;
|
||||||
const scale_y = scale;
|
} else {
|
||||||
|
// Fallback
|
||||||
|
return getGlyphIndex(id, '?');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getFontPixelScale(id: Font.Id, height: f32) f32 {
|
||||||
|
const font = getFont(id) orelse return 0;
|
||||||
|
const stb_font = font.stb orelse return 0;
|
||||||
|
|
||||||
|
return stb_font.scaleForPixelHeight(height);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getFontAscent(id: Font.Id, height: f32) f32 {
|
||||||
|
const font = getFont(id) orelse return 0;
|
||||||
|
const stb_font = font.stb orelse return 0;
|
||||||
|
|
||||||
const vmetrics = stb_font.getFontVMetrics();
|
const vmetrics = stb_font.getFontVMetrics();
|
||||||
const baseline = @as(f32, @floatFromInt(vmetrics.ascent)) * scale;
|
const scale = stb_font.scaleForPixelHeight(height);
|
||||||
|
return @as(f32, @floatFromInt(vmetrics.ascent)) * scale;
|
||||||
|
}
|
||||||
|
|
||||||
var current_point = opts.pos;
|
pub fn getGlyph(id: Font.Id, index: GlyphIndex, scale_x: f32, scale_y: f32) ?*Font.Glyph {
|
||||||
current_point.y += baseline;
|
const self = &g_state;
|
||||||
|
const font = getFont(id) orelse return null;
|
||||||
|
const stb_font = font.stb orelse return null;
|
||||||
|
|
||||||
var prev_glyph: ?u32 = null;
|
// TODO: Allow specifying a different scale for x and y
|
||||||
var cursor: usize = 0;
|
const glyph_key = Font.Glyph.Key{
|
||||||
while (cursor < opts.text.len) {
|
.index = index,
|
||||||
const codepoint_len = std.unicode.utf8ByteSequenceLength(opts.text[cursor]) catch |e| {
|
.scale_x = scale_x,
|
||||||
log.err("Failed to draw text, invalid codepoint at {} in '{s}': {}", .{cursor, opts.text, e});
|
.scale_y = scale_y
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
const codepoint_bytes = opts.text[cursor..][0..codepoint_len];
|
|
||||||
const codepoint = std.unicode.utf8Decode(codepoint_bytes) catch |e| {
|
|
||||||
log.err("Failed to draw text, invalid codepoint at {} in '{s}': {}", .{cursor, opts.text, e});
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
cursor += codepoint_len;
|
|
||||||
|
|
||||||
const glyph_index = stb_font.findGlyphIndex(codepoint) orelse fallback_glyph;
|
|
||||||
const glyph_key = Font.Glyph.Key{ .index = glyph_index, .scale_x = scale_x, .scale_y = scale_y };
|
|
||||||
|
|
||||||
var glyph: *Font.Glyph = undefined;
|
var glyph: *Font.Glyph = undefined;
|
||||||
if (font.cache.lookup(glyph_key)) |glyph_id| {
|
if (font.cache.lookup(glyph_key)) |glyph_id| {
|
||||||
glyph = font.cache.get(glyph_id);
|
return font.cache.get(glyph_id);
|
||||||
} else {
|
} else {
|
||||||
const glyph_id = font.cache.insert(glyph_key) orelse {
|
const glyph_id = font.cache.insert(glyph_key) orelse {
|
||||||
@panic("TODO: render glyph lru_tail");
|
@panic("TODO: render glyph lru_tail");
|
||||||
@ -1247,25 +1371,25 @@ pub fn drawText(id: Font.Id, opts: DrawTextOptions) void {
|
|||||||
glyph = font.cache.get(glyph_id);
|
glyph = font.cache.get(glyph_id);
|
||||||
glyph.box = stb_font.getGlyphBitmapBox(glyph_key.index, glyph_key.scale_x, glyph_key.scale_y);
|
glyph.box = stb_font.getGlyphBitmapBox(glyph_key.index, glyph_key.scale_x, glyph_key.scale_y);
|
||||||
|
|
||||||
const width: u32 = @intCast(glyph.box.x1 - glyph.box.x0);
|
const box_width: u32 = @intCast(glyph.box.x1 - glyph.box.x0);
|
||||||
const height: u32 = @intCast(glyph.box.y1 - glyph.box.y0);
|
const box_height: u32 = @intCast(glyph.box.y1 - glyph.box.y0);
|
||||||
if (width > 0 and height > 0) {
|
if (box_width > 0 and box_height > 0) {
|
||||||
assert(glyph.sprite == self.nil_sprite);
|
assert(glyph.sprite == self.nil_sprite);
|
||||||
glyph.sprite = initSprite(.{ .spritesheet = font.spritesheet, .padding = 1 });
|
glyph.sprite = initSprite(.{ .spritesheet = font.spritesheet, .padding = 1 });
|
||||||
|
|
||||||
assert(width < Font.Glyph.max_width);
|
assert(box_width < Font.Glyph.max_width);
|
||||||
assert(height < Font.Glyph.max_height);
|
assert(box_height < Font.Glyph.max_height);
|
||||||
|
|
||||||
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 = ImageData{
|
const bitmap = ImageData{
|
||||||
.pixels = .{ .r8 = (&bitmap_buffer).ptr },
|
.pixels = .{ .r8 = (&bitmap_buffer).ptr },
|
||||||
.width = width,
|
.width = box_width,
|
||||||
.height = height
|
.height = box_height
|
||||||
};
|
};
|
||||||
stb_font.makeGlyphBitmap(
|
stb_font.makeGlyphBitmap(
|
||||||
bitmap.pixels.?.r8,
|
bitmap.pixels.?.r8,
|
||||||
width, height,
|
box_width, box_height,
|
||||||
width,
|
box_width,
|
||||||
glyph_key.scale_x, glyph_key.scale_y,
|
glyph_key.scale_x, glyph_key.scale_y,
|
||||||
glyph_key.index
|
glyph_key.index
|
||||||
);
|
);
|
||||||
@ -1276,23 +1400,239 @@ pub fn drawText(id: Font.Id, opts: DrawTextOptions) void {
|
|||||||
}
|
}
|
||||||
glyph.used_this_frame = true;
|
glyph.used_this_frame = true;
|
||||||
|
|
||||||
if (prev_glyph != null) {
|
return glyph;
|
||||||
const kerning = stb_font.getGlyphKernAdvance(prev_glyph.?, glyph_index);
|
|
||||||
current_point.x += @as(f32, @floatFromInt(kerning)) * scale;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (glyph.sprite != self.nil_sprite) {
|
pub const TextRunLayout = struct {
|
||||||
const glyph_size = Vec2.initFromInt(i32, glyph.box.x1 - glyph.box.x0, glyph.box.y1 - glyph.box.y0);
|
font: Font.Id,
|
||||||
const glyph_pos = current_point.sub(.initFromInt(i32, glyph.box.x0, -glyph.box.y0));
|
scale_x: f32,
|
||||||
drawSprite(glyph.sprite, glyph_pos, glyph_size, opts.color);
|
scale_y: f32,
|
||||||
|
|
||||||
|
prev_glyph: ?GlyphIndex,
|
||||||
|
pen_pos: Vec2,
|
||||||
|
|
||||||
|
const GlyphRect = struct {
|
||||||
|
rect: Rect,
|
||||||
|
sprite: Sprite.Id,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(font: Font.Id, height: f32) TextRunLayout {
|
||||||
|
const scale = getFontPixelScale(font, height);
|
||||||
|
|
||||||
|
return TextRunLayout{
|
||||||
|
.font = font,
|
||||||
|
.scale_x = scale,
|
||||||
|
.scale_y = scale,
|
||||||
|
.prev_glyph = null,
|
||||||
|
.pen_pos = .init(0, 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(self: *TextRunLayout, glyph_index: GlyphIndex) ?GlyphRect {
|
||||||
|
const glyph = getGlyph(self.font, glyph_index, self.scale_x, self.scale_y) orelse return null;
|
||||||
|
|
||||||
|
const font = getFont(self.font) orelse return null;
|
||||||
|
const stb_font = font.stb orelse return null;
|
||||||
|
|
||||||
|
if (self.prev_glyph) |prev_glyph| {
|
||||||
|
const kerning = stb_font.getGlyphKernAdvance(prev_glyph, glyph_index);
|
||||||
|
self.pen_pos.x += @as(f32, @floatFromInt(kerning)) * self.scale_x;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hmetrics = stb_font.getGlyphHMetrics(glyph_index);
|
const hmetrics = stb_font.getGlyphHMetrics(glyph_index);
|
||||||
current_point.x += @as(f32, @floatFromInt(hmetrics.advance_width)) * scale;
|
const glyph_size = Vec2.initFromInt(i32, glyph.box.x1 - glyph.box.x0, glyph.box.y1 - glyph.box.y0);
|
||||||
prev_glyph = glyph_index;
|
|
||||||
|
var draw_pos = self.pen_pos;
|
||||||
|
draw_pos.x -= @floatFromInt(glyph.box.x0);
|
||||||
|
draw_pos.x += @as(f32, @floatFromInt(hmetrics.left_side_bearing)) * self.scale_x;
|
||||||
|
draw_pos.y += @floatFromInt(glyph.box.y0);
|
||||||
|
const draw_rect = Rect{
|
||||||
|
.pos = draw_pos,
|
||||||
|
.size = glyph_size
|
||||||
|
};
|
||||||
|
|
||||||
|
self.pen_pos.x += @as(f32, @floatFromInt(hmetrics.advance_width)) * self.scale_x;
|
||||||
|
self.prev_glyph = glyph_index;
|
||||||
|
|
||||||
|
return GlyphRect{
|
||||||
|
.rect = draw_rect,
|
||||||
|
.sprite = glyph.sprite
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn measureText(id: Font.Id, height: f32, text: []const u8) Rect {
|
||||||
|
var bounds: Rect = .init(0, 0, 0, 0);
|
||||||
|
var first_glyph = false;
|
||||||
|
|
||||||
|
var layout = TextRunLayout.init(id, height);
|
||||||
|
var iter = std.unicode.Utf8Iterator{
|
||||||
|
.bytes = text,
|
||||||
|
.i = 0
|
||||||
|
};
|
||||||
|
while (iter.nextCodepoint()) |codepoint| {
|
||||||
|
const glyph_index = getGlyphIndex(id, codepoint) orelse continue;
|
||||||
|
const glyph_layout = layout.next(glyph_index) orelse continue;
|
||||||
|
|
||||||
|
if (first_glyph) {
|
||||||
|
bounds = glyph_layout.rect;
|
||||||
|
first_glyph = false;
|
||||||
|
} else {
|
||||||
|
bounds = bounds.expand(glyph_layout.rect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DrawTextOptions = struct {
|
||||||
|
text: []const u8,
|
||||||
|
pos: Vec2,
|
||||||
|
height: f32,
|
||||||
|
color: Color = .white,
|
||||||
|
alignment: Vec2 = .init(0, 0)
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn drawText(id: Font.Id, opts: DrawTextOptions) void {
|
||||||
|
const self = &g_state;
|
||||||
|
|
||||||
|
var layout = TextRunLayout.init(id, opts.height);
|
||||||
|
|
||||||
|
var pos = opts.pos;
|
||||||
|
pos.y += getFontAscent(id, opts.height);
|
||||||
|
|
||||||
|
if (!opts.alignment.eql(.zero)) {
|
||||||
|
const bounds = measureText(id, opts.height, opts.text);
|
||||||
|
pos = pos.sub(bounds.size.multiply(opts.alignment));
|
||||||
|
}
|
||||||
|
|
||||||
|
var iter = std.unicode.Utf8Iterator{
|
||||||
|
.bytes = opts.text,
|
||||||
|
.i = 0
|
||||||
|
};
|
||||||
|
while (iter.nextCodepoint()) |codepoint| {
|
||||||
|
const glyph_index = getGlyphIndex(id, codepoint) orelse continue;
|
||||||
|
const glyph_layout = layout.next(glyph_index) orelse continue;
|
||||||
|
if (glyph_layout.sprite == self.nil_sprite) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawSprite(
|
||||||
|
glyph_layout.sprite,
|
||||||
|
glyph_layout.rect.pos.add(pos),
|
||||||
|
glyph_layout.rect.size,
|
||||||
|
opts.color
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const TextBuilder = struct {
|
||||||
|
gpa: std.mem.Allocator,
|
||||||
|
glyphs: std.ArrayList(Glyph),
|
||||||
|
|
||||||
|
color: Color,
|
||||||
|
baseline: f32,
|
||||||
|
layout: ?TextRunLayout,
|
||||||
|
|
||||||
|
bounds: Rect,
|
||||||
|
|
||||||
|
const Glyph = struct {
|
||||||
|
sprite: Sprite.Id,
|
||||||
|
rect: Rect,
|
||||||
|
color: Color
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn init(gpa: std.mem.Allocator) TextBuilder {
|
||||||
|
return TextBuilder{
|
||||||
|
.gpa = gpa,
|
||||||
|
.glyphs = .empty,
|
||||||
|
.color = .white,
|
||||||
|
.baseline = 0,
|
||||||
|
.layout = null,
|
||||||
|
.bounds = .init(0, 0, 0, 0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn font(self: *TextBuilder, id: Font.Id, height: f32) void {
|
||||||
|
var new_layout = TextRunLayout.init(id, height);
|
||||||
|
if (self.layout) |layout| {
|
||||||
|
if (new_layout.font == layout.font and
|
||||||
|
new_layout.scale_x == layout.scale_x and
|
||||||
|
new_layout.scale_y == layout.scale_y
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const font_baseline = getFontAscent(id, height);
|
||||||
|
if (font_baseline > self.baseline) {
|
||||||
|
// Fixup previously drawn glyph to match the new larger baseline
|
||||||
|
const offset_y = font_baseline - self.baseline;
|
||||||
|
for (self.glyphs.items) |*glyph| {
|
||||||
|
glyph.rect.pos.y += offset_y;
|
||||||
|
}
|
||||||
|
self.bounds.pos.y += offset_y;
|
||||||
|
self.baseline = font_baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.layout) |layout| {
|
||||||
|
new_layout.pen_pos = layout.pen_pos;
|
||||||
|
if (layout.font == new_layout.font) {
|
||||||
|
new_layout.prev_glyph = layout.prev_glyph;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.layout = new_layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn putCodepoint(self: *TextBuilder, codepoint: u21) !void {
|
||||||
|
if (self.layout == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const layout = &self.layout.?;
|
||||||
|
|
||||||
|
const glyph_index = getGlyphIndex(layout.font, codepoint) orelse return;
|
||||||
|
const glyph_layout = layout.next(glyph_index) orelse return;
|
||||||
|
|
||||||
|
var rect = glyph_layout.rect;
|
||||||
|
rect.pos.y += self.baseline;
|
||||||
|
|
||||||
|
try self.glyphs.append(self.gpa, Glyph{
|
||||||
|
.sprite = glyph_layout.sprite,
|
||||||
|
.rect = rect,
|
||||||
|
.color = self.color
|
||||||
|
});
|
||||||
|
|
||||||
|
if (self.bounds.size.x > 0 and self.bounds.size.y > 0) {
|
||||||
|
self.bounds = self.bounds.expand(rect);
|
||||||
|
} else {
|
||||||
|
self.bounds = rect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn put(self: *TextBuilder, text: []const u8) !void {
|
||||||
|
var iter = std.unicode.Utf8Iterator{ .bytes = text, .i = 0 };
|
||||||
|
while (iter.nextCodepoint()) |codepoint| {
|
||||||
|
try self.putCodepoint(codepoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(self: *TextBuilder, pos: Vec2) void {
|
||||||
|
for (self.glyphs.items) |glyph| {
|
||||||
|
if (glyph.sprite == g_state.nil_sprite) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
drawSprite(glyph.sprite, glyph.rect.pos.add(pos), glyph.rect.size, glyph.color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn offset(self: *TextBuilder, vec2: Vec2) void {
|
||||||
|
if (self.layout) |*layout| {
|
||||||
|
layout.pen_pos = layout.pen_pos.add(vec2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const ImageData = struct {
|
pub const ImageData = struct {
|
||||||
const Format = enum {
|
const Format = enum {
|
||||||
rgba8,
|
rgba8,
|
||||||
@ -1375,7 +1715,7 @@ pub const ImageData = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn getPixel(self: ImageData, x: u32, y: u32) []u8 {
|
pub fn getPixel(self: ImageData, x: u32, y: u32) []u8 {
|
||||||
const pixel_index = y * self.width + x;
|
const pixel_index = y * self.width + x;
|
||||||
if (self.pixels) |pixels| {
|
if (self.pixels) |pixels| {
|
||||||
return switch (pixels) {
|
return switch (pixels) {
|
||||||
@ -1482,7 +1822,7 @@ pub const Sprite = struct {
|
|||||||
position: ?Vec2,
|
position: ?Vec2,
|
||||||
padding: u32,
|
padding: u32,
|
||||||
|
|
||||||
const max_sprites = 128;
|
const max_sprites = 256;
|
||||||
const SlotMap = SlotMapType(std.math.IntFittingRange(0, max_sprites-1), u8, Sprite);
|
const SlotMap = SlotMapType(std.math.IntFittingRange(0, max_sprites-1), u8, Sprite);
|
||||||
pub const Id = SlotMap.Id;
|
pub const Id = SlotMap.Id;
|
||||||
};
|
};
|
||||||
@ -1912,7 +2252,10 @@ pub const Font = struct {
|
|||||||
const max_width: u32 = 512;
|
const max_width: u32 = 512;
|
||||||
const max_height: u32 = 512;
|
const max_height: u32 = 512;
|
||||||
|
|
||||||
|
const Index = u32;
|
||||||
|
|
||||||
const Key = packed struct {
|
const Key = packed struct {
|
||||||
|
// TODO: Quantize `scale_x` and `scale_y`.
|
||||||
index: u32,
|
index: u32,
|
||||||
scale_x: f32,
|
scale_x: f32,
|
||||||
scale_y: f32,
|
scale_y: f32,
|
||||||
@ -1944,22 +2287,24 @@ pub const Font = struct {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const GlyphIndex = Font.Glyph.Index;
|
||||||
|
|
||||||
pub const Vertex = extern struct {
|
pub const Vertex = extern struct {
|
||||||
position: Vec2,
|
position: Vec2,
|
||||||
texcoord: Vec2,
|
texcoord: Vec2,
|
||||||
color: Vec4,
|
color: Vec4,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Quad = extern struct {
|
pub const VertexQuad = extern struct {
|
||||||
vertices: [4]Vertex,
|
vertices: [4]Vertex,
|
||||||
|
|
||||||
pub fn init(vertices: [4]Vertex) Quad {
|
pub fn init(vertices: [4]Vertex) VertexQuad {
|
||||||
return Quad{
|
return VertexQuad{
|
||||||
.vertices = vertices
|
.vertices = vertices
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setColor(self: *Quad, color: Color) void {
|
pub fn setColor(self: *VertexQuad, color: Color) void {
|
||||||
for (&self.vertices) |*vertex| {
|
for (&self.vertices) |*vertex| {
|
||||||
vertex.color = Vec4{
|
vertex.color = Vec4{
|
||||||
.x = color.r,
|
.x = color.r,
|
||||||
@ -1970,7 +2315,7 @@ pub const Quad = extern struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setUVRect(self: *Quad, rect: Rect) void {
|
pub fn setUVRect(self: *VertexQuad, rect: Rect) void {
|
||||||
const pos = rect.pos;
|
const pos = rect.pos;
|
||||||
const size = rect.size;
|
const size = rect.size;
|
||||||
self.vertices[0].texcoord = pos;
|
self.vertices[0].texcoord = pos;
|
||||||
@ -1979,7 +2324,7 @@ pub const Quad = extern struct {
|
|||||||
self.vertices[3].texcoord = pos.add(size);
|
self.vertices[3].texcoord = pos.add(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setRect(self: *Quad, rect: Rect) void {
|
pub fn setRect(self: *VertexQuad, rect: Rect) void {
|
||||||
const pos = rect.pos;
|
const pos = rect.pos;
|
||||||
const size = rect.size;
|
const size = rect.size;
|
||||||
self.vertices[0].position = pos;
|
self.vertices[0].position = pos;
|
||||||
@ -1988,3 +2333,45 @@ pub const Quad = extern struct {
|
|||||||
self.vertices[3].position = pos.add(size);
|
self.vertices[3].position = pos.add(size);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Quad = struct {
|
||||||
|
positions: [4]Vec2,
|
||||||
|
|
||||||
|
pub fn init(positions: [4]Vec2) Quad {
|
||||||
|
return Quad{
|
||||||
|
.positions = positions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initRect(pos: Vec2, size: Vec2) Quad {
|
||||||
|
return Quad{
|
||||||
|
.positions = .{
|
||||||
|
pos,
|
||||||
|
pos.add(.init(size.x, 0)),
|
||||||
|
pos.add(.init(0, size.y)),
|
||||||
|
pos.add(size),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn applyRotate(self: *Quad, rad: f32, origin: Vec2) void {
|
||||||
|
for (&self.positions) |*pos| {
|
||||||
|
pos.* = pos.sub(origin).rotate(rad).add(origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn applyOffset(self: *Quad, vec2: Vec2) void {
|
||||||
|
for (&self.positions) |*pos| {
|
||||||
|
pos.* = pos.add(vec2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TransformFrame = struct {
|
||||||
|
offset: Vec2,
|
||||||
|
scale: Vec2,
|
||||||
|
|
||||||
|
pub fn apply(self: TransformFrame, position: Vec2) Vec2 {
|
||||||
|
return position.multiply(self.scale).add(self.offset);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
266
src/imgui.zig
266
src/imgui.zig
@ -15,6 +15,8 @@ const simgui = sokol.imgui;
|
|||||||
|
|
||||||
const ig = @import("cimgui");
|
const ig = @import("cimgui");
|
||||||
|
|
||||||
|
const Nanoseconds = @import("./platform.zig").Nanoseconds;
|
||||||
|
|
||||||
const ImGUI = @This();
|
const ImGUI = @This();
|
||||||
|
|
||||||
const State = struct {
|
const State = struct {
|
||||||
@ -130,16 +132,16 @@ pub fn endFrame() void {
|
|||||||
|
|
||||||
ig.igEnd();
|
ig.igEnd();
|
||||||
|
|
||||||
if (ig.igBeginMainMenuBar()) {
|
// if (ig.igBeginMainMenuBar()) {
|
||||||
defer ig.igEndMainMenuBar();
|
// defer ig.igEndMainMenuBar();
|
||||||
|
//
|
||||||
if (ig.igBeginMenu("foo")) {
|
// if (ig.igBeginMenu("foo")) {
|
||||||
defer ig.igEndMenu();
|
// defer ig.igEndMenu();
|
||||||
|
//
|
||||||
if (ig.igMenuItem("bar")) {
|
// if (ig.igMenuItem("bar")) {
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
simgui.render();
|
simgui.render();
|
||||||
}
|
}
|
||||||
@ -150,14 +152,16 @@ pub const WindowOptions = struct {
|
|||||||
size: ?Vec2 = null,
|
size: ?Vec2 = null,
|
||||||
collapsed: ?bool = null,
|
collapsed: ?bool = null,
|
||||||
open: ?*bool = null,
|
open: ?*bool = null,
|
||||||
|
alpha: f32 = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn beginWindow(opts: WindowOptions) bool {
|
inline fn isDisabled() bool {
|
||||||
if (!build_options.has_imgui) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
if (!self.enabled) {
|
return !(build_options.has_imgui and self.enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn beginWindow(opts: WindowOptions) bool {
|
||||||
|
if (isDisabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,7 +179,7 @@ pub fn beginWindow(opts: WindowOptions) bool {
|
|||||||
ig.igSetNextWindowCollapsed(collapsed, ig.ImGuiCond_Once);
|
ig.igSetNextWindowCollapsed(collapsed, ig.ImGuiCond_Once);
|
||||||
}
|
}
|
||||||
|
|
||||||
ig.igSetNextWindowBgAlpha(1);
|
ig.igSetNextWindowBgAlpha(opts.alpha);
|
||||||
|
|
||||||
const frame = g_state.frame.?;
|
const frame = g_state.frame.?;
|
||||||
const namez = frame.dupeSentinel(u8, opts.name, 0) catch blk: {
|
const namez = frame.dupeSentinel(u8, opts.name, 0) catch blk: {
|
||||||
@ -197,11 +201,7 @@ pub fn beginWindow(opts: WindowOptions) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn endWindow() void {
|
pub fn endWindow() void {
|
||||||
if (!build_options.has_imgui) {
|
if (isDisabled()) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
const self = &g_state;
|
|
||||||
if (!self.enabled) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,11 +217,7 @@ fn formatString(comptime fmt: []const u8, args: anytype) [:0]const u8 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn text(comptime fmt: []const u8, args: anytype) void {
|
pub fn text(comptime fmt: []const u8, args: anytype) void {
|
||||||
if (!build_options.has_imgui) {
|
if (isDisabled()) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
const self = &g_state;
|
|
||||||
if (!self.enabled) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,11 +225,7 @@ pub fn text(comptime fmt: []const u8, args: anytype) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn button(label: []const u8) bool {
|
pub fn button(label: []const u8) bool {
|
||||||
if (!build_options.has_imgui) {
|
if (isDisabled()) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const self = &g_state;
|
|
||||||
if (!self.enabled) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,11 +240,7 @@ pub const SliderOptions = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn slider(opts: SliderOptions) bool {
|
pub fn slider(opts: SliderOptions) bool {
|
||||||
if (!build_options.has_imgui) {
|
if (isDisabled()) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const self = &g_state;
|
|
||||||
if (!self.enabled) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,12 +252,115 @@ pub fn slider(opts: SliderOptions) bool {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn checkbox(label: []const u8, value: *bool) bool {
|
const InputF32Options = struct {
|
||||||
if (!build_options.has_imgui) {
|
label: []const u8,
|
||||||
|
value: *f32,
|
||||||
|
ex: ?struct {
|
||||||
|
step: f32,
|
||||||
|
step_fast: f32,
|
||||||
|
format: [*c]const u8 = null,
|
||||||
|
} = null
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn inputF32(opts: InputF32Options) bool {
|
||||||
|
if (isDisabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const self = &g_state;
|
|
||||||
if (!self.enabled) {
|
if (opts.ex) |ex| {
|
||||||
|
return ig.igInputFloatEx(
|
||||||
|
formatString("{s}", .{opts.label}),
|
||||||
|
opts.value,
|
||||||
|
ex.step,
|
||||||
|
ex.step_fast,
|
||||||
|
ex.format,
|
||||||
|
ig.ImGuiInputTextFlags_None
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return ig.igInputFloat(
|
||||||
|
formatString("{s}", .{opts.label}),
|
||||||
|
opts.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const InputF32DragOptions = struct {
|
||||||
|
label: []const u8,
|
||||||
|
value: *f32,
|
||||||
|
ex: ?struct {
|
||||||
|
speed: f32 = 1,
|
||||||
|
min: f32 = 0,
|
||||||
|
max: f32 = 10
|
||||||
|
} = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn inputF32Drag(opts: InputF32DragOptions) bool {
|
||||||
|
if (isDisabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.ex) |ex| {
|
||||||
|
return ig.igDragFloatEx(
|
||||||
|
formatString("{s}", .{opts.label}),
|
||||||
|
opts.value,
|
||||||
|
ex.speed,
|
||||||
|
ex.min,
|
||||||
|
ex.max,
|
||||||
|
null,
|
||||||
|
ig.ImGuiSliderFlags_None
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return ig.igDragFloat(
|
||||||
|
formatString("{s}", .{opts.label}),
|
||||||
|
opts.value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inputDuration(label: []const u8, duration_ns: *Nanoseconds) bool {
|
||||||
|
const duration_ms: f32 = @floatFromInt(@divTrunc(duration_ns.*, std.time.ns_per_ms));
|
||||||
|
var duration_s = duration_ms / std.time.ms_per_s;
|
||||||
|
if (ImGUI.inputF32(.{
|
||||||
|
.label = label,
|
||||||
|
.value = &duration_s,
|
||||||
|
.ex = .{
|
||||||
|
.step = 0.01,
|
||||||
|
.step_fast = 0.1,
|
||||||
|
.format = "%0.3f s"
|
||||||
|
}
|
||||||
|
})) {
|
||||||
|
duration_ns.* = @trunc(duration_s * std.time.ns_per_s);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inputI32(label: []const u8, value: *i32) bool {
|
||||||
|
if (isDisabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ig.igInputInt(
|
||||||
|
formatString("{s}", .{label}),
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inputU32(label: []const u8, value: *u32) bool {
|
||||||
|
var value_i32: i32 = @intCast(value.*);
|
||||||
|
if (inputI32(label, &value_i32)) {
|
||||||
|
if (value_i32 >= 0) {
|
||||||
|
value.* = @intCast(value_i32);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn checkbox(label: []const u8, value: *bool) bool {
|
||||||
|
if (isDisabled()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,11 +371,7 @@ pub fn checkbox(label: []const u8, value: *bool) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn separator() void {
|
pub fn separator() void {
|
||||||
if (!build_options.has_imgui) {
|
if (isDisabled()) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
const self = &g_state;
|
|
||||||
if (!self.enabled) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,11 +385,7 @@ const ColorEditOptions = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn colorEdit(opts: ColorEditOptions) bool {
|
pub fn colorEdit(opts: ColorEditOptions) bool {
|
||||||
if (!build_options.has_imgui) {
|
if (isDisabled()) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const self = &g_state;
|
|
||||||
if (!self.enabled) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,3 +420,86 @@ fn toImVec2(vec2: Vec2) ig.ImVec2 {
|
|||||||
.y = vec2.y,
|
.y = vec2.y,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn sameLine() void {
|
||||||
|
if (isDisabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ig.igSameLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const ID = union(enum) {
|
||||||
|
string: []const u8,
|
||||||
|
int: i32
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn pushID(id: ID) void {
|
||||||
|
if (isDisabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (id) {
|
||||||
|
.string => |str| ig.igPushIDStr(str.ptr, str.ptr + str.len),
|
||||||
|
.int => |int| ig.igPushIDInt(int)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn popID() void {
|
||||||
|
if (isDisabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ig.igPopID();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setNextItemWidth(width: f32) void {
|
||||||
|
if (isDisabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ig.igSetNextItemWidth(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn beginTabBar(id: [*c]const u8) bool {
|
||||||
|
if (isDisabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ig.igBeginTabBar(id, ig.ImGuiTabBarFlags_None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn endTabBar() void {
|
||||||
|
if (isDisabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ig.igEndTabBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn beginTabItem(label: [*c]const u8) bool {
|
||||||
|
if (isDisabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ig.igBeginTabItem(label, null, ig.ImGuiTabItemFlags_None);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn endTabItem() void {
|
||||||
|
if (isDisabled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ig.igEndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collapsingHeader(label: []const u8) bool {
|
||||||
|
if (isDisabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ig.igCollapsingHeader(
|
||||||
|
formatString("{s}", .{label}),
|
||||||
|
ig.ImGuiTreeNodeFlags_None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -15,6 +15,7 @@ focused: bool,
|
|||||||
keyboard: KeyStateType(KeyCode),
|
keyboard: KeyStateType(KeyCode),
|
||||||
mouse_buttons: KeyStateType(MouseButton),
|
mouse_buttons: KeyStateType(MouseButton),
|
||||||
mouse_position: ?Vec2,
|
mouse_position: ?Vec2,
|
||||||
|
mouse_delta: Vec2,
|
||||||
mouse_scroll: Vec2,
|
mouse_scroll: Vec2,
|
||||||
|
|
||||||
pub fn init(window_size: Vec2) Input {
|
pub fn init(window_size: Vec2) Input {
|
||||||
@ -24,6 +25,7 @@ pub fn init(window_size: Vec2) Input {
|
|||||||
.keyboard = .empty,
|
.keyboard = .empty,
|
||||||
.mouse_buttons = .empty,
|
.mouse_buttons = .empty,
|
||||||
.mouse_position = null,
|
.mouse_position = null,
|
||||||
|
.mouse_delta = .init(0, 0),
|
||||||
.mouse_scroll = .init(0, 0),
|
.mouse_scroll = .init(0, 0),
|
||||||
.window_size = window_size
|
.window_size = window_size
|
||||||
};
|
};
|
||||||
@ -45,7 +47,7 @@ pub fn isMousePressed(self: Input, button: MouseButton) bool {
|
|||||||
return self.mouse_buttons.pressed.contains(button);
|
return self.mouse_buttons.pressed.contains(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn iMouseReleased(self: Input, button: MouseButton) bool {
|
pub fn isMouseReleased(self: Input, button: MouseButton) bool {
|
||||||
return self.mouse_buttons.released.contains(button);
|
return self.mouse_buttons.released.contains(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
44
src/math.zig
44
src/math.zig
@ -336,11 +336,11 @@ pub const Mat4 = extern struct {
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initScale(scale: Vec3) Mat4 {
|
pub fn initScale(vec: Vec3) Mat4 {
|
||||||
var self = Mat4.initIdentity();
|
var self = Mat4.initIdentity();
|
||||||
self.columns[0][0] = scale.x;
|
self.columns[0][0] = vec.x;
|
||||||
self.columns[1][1] = scale.y;
|
self.columns[1][1] = vec.y;
|
||||||
self.columns[2][2] = scale.z;
|
self.columns[2][2] = vec.z;
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,6 +352,18 @@ pub const Mat4 = extern struct {
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn translate(self: *Mat4, x: f32, y: f32, z: f32) void {
|
||||||
|
self.columns[3][0] += x;
|
||||||
|
self.columns[3][1] += y;
|
||||||
|
self.columns[3][2] += z;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scale(self: *Mat4, x: f32, y: f32, z: f32) void {
|
||||||
|
self.columns[0][0] *= x;
|
||||||
|
self.columns[1][1] *= y;
|
||||||
|
self.columns[2][2] *= z;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn asArray(self: *Mat4) []f32 {
|
pub fn asArray(self: *Mat4) []f32 {
|
||||||
const ptr: [*]f32 = @alignCast(@ptrCast(@as(*anyopaque, @ptrCast(&self.columns))));
|
const ptr: [*]f32 = @alignCast(@ptrCast(@as(*anyopaque, @ptrCast(&self.columns))));
|
||||||
return ptr[0..16];
|
return ptr[0..16];
|
||||||
@ -392,6 +404,19 @@ pub const Rect = struct {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn expand(self: Rect, other: Rect) Rect {
|
||||||
|
const left_edge = @min(self.left(), other.left());
|
||||||
|
const right_edge = @max(self.right(), other.right());
|
||||||
|
const top_edge = @min(self.top(), other.top());
|
||||||
|
const bottom_edge = @max(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 {
|
pub fn left(self: Rect) f32 {
|
||||||
return self.pos.x;
|
return self.pos.x;
|
||||||
}
|
}
|
||||||
@ -431,6 +456,17 @@ pub const Rect = struct {
|
|||||||
const y_overlap = self.pos.y <= pos.y and pos.y < self.pos.y + self.size.y;
|
const y_overlap = self.pos.y <= pos.y and pos.y < self.pos.y + self.size.y;
|
||||||
return x_overlap and y_overlap;
|
return x_overlap and y_overlap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn shrink(self: Rect, margin: Vec2) Rect {
|
||||||
|
return Rect{
|
||||||
|
.pos = self.pos.add(margin),
|
||||||
|
.size = self.size.sub(margin.multiplyScalar(2))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn grow(self: Rect, margin: Vec2) Rect {
|
||||||
|
return self.shrink(margin.multiplyScalar(-1));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Line = struct {
|
pub const Line = struct {
|
||||||
|
|||||||
@ -44,6 +44,7 @@ fn PlatformType(App: type) type {
|
|||||||
last_frame_at: Io.Timestamp,
|
last_frame_at: Io.Timestamp,
|
||||||
|
|
||||||
assets: Assets,
|
assets: Assets,
|
||||||
|
last_mouse_position: ?Vec2,
|
||||||
input: Input,
|
input: Input,
|
||||||
events_buffer: [256]Input.Event,
|
events_buffer: [256]Input.Event,
|
||||||
events_overflow: bool,
|
events_overflow: bool,
|
||||||
@ -108,6 +109,13 @@ fn PlatformType(App: type) type {
|
|||||||
self.show_imgui = !self.show_imgui;
|
self.show_imgui = !self.show_imgui;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.last_mouse_position != null and self.input.mouse_position != null) {
|
||||||
|
self.input.mouse_delta = self.input.mouse_position.?.sub(self.last_mouse_position.?);
|
||||||
|
} else {
|
||||||
|
self.input.mouse_delta = .init(0, 0);
|
||||||
|
}
|
||||||
|
self.last_mouse_position = self.input.mouse_position;
|
||||||
|
|
||||||
{
|
{
|
||||||
Gfx.beginFrame();
|
Gfx.beginFrame();
|
||||||
ImGUI.beginFrame(self.show_imgui, self.frame_arena.allocator());
|
ImGUI.beginFrame(self.show_imgui, self.frame_arena.allocator());
|
||||||
@ -140,6 +148,7 @@ fn PlatformType(App: type) type {
|
|||||||
self.input.keyboard.released = .empty;
|
self.input.keyboard.released = .empty;
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sokolFrame(user_data: ?*anyopaque) callconv(.c) void {
|
fn sokolFrame(user_data: ?*anyopaque) callconv(.c) void {
|
||||||
@ -155,68 +164,101 @@ fn PlatformType(App: type) type {
|
|||||||
|
|
||||||
// TODO: I don't like the names of these 'event' related functions.
|
// TODO: I don't like the names of these 'event' related functions.
|
||||||
// All of them "append an event", but they do different things.
|
// All of them "append an event", but they do different things.
|
||||||
fn applyEventToState(self: *Self, e: Input.Event) void {
|
fn applyEventToState(self: *Self, e: Input.Event) bool {
|
||||||
const input = &self.input;
|
const input = &self.input;
|
||||||
|
|
||||||
switch (e) {
|
switch (e) {
|
||||||
.key_pressed => |key| {
|
.key_pressed => |key| {
|
||||||
assert(!input.isKeyDown(key));
|
if (input.isKeyDown(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
input.keyboard.press(key);
|
input.keyboard.press(key);
|
||||||
self.last_key_pressed = key;
|
self.last_key_pressed = key;
|
||||||
},
|
},
|
||||||
.key_released => |key| {
|
.key_released => |key| {
|
||||||
assert(input.isKeyDown(key));
|
if (!input.isKeyDown(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
input.keyboard.release(key);
|
input.keyboard.release(key);
|
||||||
},
|
},
|
||||||
.mouse_pressed => |button| {
|
.mouse_pressed => |button| {
|
||||||
assert(input.mouse_position != null);
|
if (input.mouse_position == null or input.isMouseDown(button)) {
|
||||||
assert(!input.isMouseDown(button));
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
input.mouse_buttons.press(button);
|
input.mouse_buttons.press(button);
|
||||||
},
|
},
|
||||||
.mouse_released => |button| {
|
.mouse_released => |button| {
|
||||||
assert(input.mouse_position != null);
|
if (input.mouse_position == null or !input.isMouseDown(button)) {
|
||||||
assert(input.isMouseDown(button));
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
input.mouse_buttons.release(button);
|
input.mouse_buttons.release(button);
|
||||||
},
|
},
|
||||||
.mouse_enter => |pos| {
|
.mouse_enter => |pos| {
|
||||||
assert(input.mouse_position == null);
|
if (input.mouse_position != null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
input.mouse_position = pos;
|
input.mouse_position = pos;
|
||||||
},
|
},
|
||||||
.mouse_move => |pos| {
|
.mouse_move => |pos| {
|
||||||
assert(input.mouse_position != null);
|
if (input.mouse_position == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
input.mouse_position = pos;
|
input.mouse_position = pos;
|
||||||
},
|
},
|
||||||
.mouse_leave => {
|
.mouse_leave => {
|
||||||
assert(input.mouse_position != null);
|
if (input.mouse_position == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
input.mouse_position = null;
|
input.mouse_position = null;
|
||||||
},
|
},
|
||||||
.char => |char| {
|
.char => |char| {
|
||||||
// A 'key_pressed' event always occurs before a 'char' event.
|
// A 'key_pressed' event always occurs before a 'char' event.
|
||||||
// This allows us to differentiate between keycodes and scan codes.
|
// This allows us to differentiate between keycodes and scan codes.
|
||||||
// And how to map key codes to character codes.
|
// And how to map key codes to character codes.
|
||||||
assert(self.last_key_pressed != null);
|
if (self.last_key_pressed == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
input.key_code_mapping.put(self.last_key_pressed.?, char);
|
input.key_code_mapping.put(self.last_key_pressed.?, char);
|
||||||
self.last_key_pressed = null;
|
self.last_key_pressed = null;
|
||||||
},
|
},
|
||||||
.mouse_scroll => |offset| {
|
.mouse_scroll => |offset| {
|
||||||
assert(input.mouse_position != null);
|
if (input.mouse_position == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
input.mouse_scroll = input.mouse_scroll.add(offset);
|
input.mouse_scroll = input.mouse_scroll.add(offset);
|
||||||
},
|
},
|
||||||
.window_resize => |window_size| {
|
.window_resize => |window_size| {
|
||||||
self.input.window_size = window_size;
|
self.input.window_size = window_size;
|
||||||
},
|
},
|
||||||
.focused => {
|
.focused => {
|
||||||
assert(!input.focused);
|
if (input.focused) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
input.focused = true;
|
input.focused = true;
|
||||||
},
|
},
|
||||||
.unfocused => {
|
.unfocused => {
|
||||||
assert(input.focused);
|
if (!input.focused) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
assert(input.mouse_position == null);
|
assert(input.mouse_position == null);
|
||||||
assert(input.keyboard.down.eql(.empty));
|
assert(input.keyboard.down.eql(.empty));
|
||||||
assert(input.mouse_buttons.down.eql(.empty));
|
assert(input.mouse_buttons.down.eql(.empty));
|
||||||
input.focused = false;
|
input.focused = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn appendEvent(self: *Self, e: Input.Event) void {
|
fn appendEvent(self: *Self, e: Input.Event) void {
|
||||||
@ -228,8 +270,9 @@ fn PlatformType(App: type) type {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self.applyEventToState(e)) {
|
||||||
self.events.appendAssumeCapacity(e);
|
self.events.appendAssumeCapacity(e);
|
||||||
self.applyEventToState(e);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pushEvent(self: *Self, e: Input.Event) void {
|
fn pushEvent(self: *Self, e: Input.Event) void {
|
||||||
@ -240,16 +283,6 @@ fn PlatformType(App: type) type {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e == .key_pressed and input.isKeyDown(e.key_pressed)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (e == .key_released and !input.isKeyDown(e.key_released)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (e == .mouse_move and input.mouse_position.?.eql(e.mouse_move)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e == .unfocused) {
|
if (e == .unfocused) {
|
||||||
var key_iter = input.keyboard.down.iterator();
|
var key_iter = input.keyboard.down.iterator();
|
||||||
while (key_iter.next()) |key| {
|
while (key_iter.next()) |key| {
|
||||||
@ -259,10 +292,8 @@ fn PlatformType(App: type) type {
|
|||||||
while (mouse_buttons_iter.next()) |button| {
|
while (mouse_buttons_iter.next()) |button| {
|
||||||
self.appendEvent(.{ .mouse_released = button });
|
self.appendEvent(.{ .mouse_released = button });
|
||||||
}
|
}
|
||||||
if (input.mouse_position != null) {
|
|
||||||
self.appendEvent(.{ .mouse_leave = {} });
|
self.appendEvent(.{ .mouse_leave = {} });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (e == .unfocused) {
|
if (e == .unfocused) {
|
||||||
@ -372,6 +403,11 @@ fn PlatformType(App: type) type {
|
|||||||
.unfocused = {}
|
.unfocused = {}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
.RESIZED => {
|
||||||
|
self.pushEvent(.{
|
||||||
|
.window_resize = .initFromInt(i32, e.window_width, e.window_height)
|
||||||
|
});
|
||||||
|
},
|
||||||
else => {}
|
else => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -566,7 +602,8 @@ pub fn run(App: type, opts: RunOptions) void {
|
|||||||
.last_key_pressed = null,
|
.last_key_pressed = null,
|
||||||
.frame_arena = .init(gpa),
|
.frame_arena = .init(gpa),
|
||||||
.show_imgui = builtin.mode == .Debug,
|
.show_imgui = builtin.mode == .Debug,
|
||||||
.assets = .init(gpa, io, assets_dir)
|
.assets = .init(gpa, io, assets_dir),
|
||||||
|
.last_mouse_position = null
|
||||||
};
|
};
|
||||||
|
|
||||||
inline for (EmbeddedAssets.files) |file| {
|
inline for (EmbeddedAssets.files) |file| {
|
||||||
|
|||||||
@ -78,10 +78,7 @@ pub fn SlotMapType(Index: type, Generation: type, Value: type) type {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn clearRetainingCapacity(self: *Self) void {
|
pub fn clearRetainingCapacity(self: *Self) void {
|
||||||
var slots = self.slots;
|
self.* = .init(self.slots.items);
|
||||||
slots.clearRetainingCapacity();
|
|
||||||
|
|
||||||
self.* = .init(slots);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insertHole(self: *Self, index: Index) void {
|
fn insertHole(self: *Self, index: Index) void {
|
||||||
@ -121,19 +118,6 @@ pub fn SlotMapType(Index: type, Generation: type, Value: type) type {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ensureUnusedCapacity(self: *Self, gpa: Allocator, additional_count: usize) Allocator.Error!void {
|
|
||||||
if (additional_count > self.hole_count) {
|
|
||||||
const new_capacity, const overflow = @addWithOverflow(
|
|
||||||
self.slots.capacity,
|
|
||||||
additional_count - self.hole_count
|
|
||||||
);
|
|
||||||
if (overflow != 0) return error.OutOfMemory;
|
|
||||||
if (new_capacity >= std.math.maxInt(Index)) return error.OutOfMemory;
|
|
||||||
|
|
||||||
try self.slots.ensureTotalCapacity(gpa, new_capacity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unusedCapacity(self: *Self) usize {
|
pub fn unusedCapacity(self: *Self) usize {
|
||||||
const capacity = @min(self.slots.capacity, std.math.maxInt(Index));
|
const capacity = @min(self.slots.capacity, std.math.maxInt(Index));
|
||||||
return capacity - self.slots.items.len + self.hole_count;
|
return capacity - self.slots.items.len + self.hole_count;
|
||||||
@ -151,18 +135,19 @@ pub fn SlotMapType(Index: type, Generation: type, Value: type) type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(self: *Self, gpa: Allocator) Allocator.Error!Id {
|
pub fn insertUndefined(self: *Self) Allocator.Error!Id {
|
||||||
try self.ensureUnusedCapacity(gpa, 1);
|
|
||||||
return self.insertAssumeCapacity();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insertBounded(self: *Self) Allocator.Error!Id {
|
|
||||||
if (self.unusedCapacity() == 0) {
|
if (self.unusedCapacity() == 0) {
|
||||||
return error.OutOfMemory;
|
return error.OutOfMemory;
|
||||||
}
|
}
|
||||||
return self.insertAssumeCapacity();
|
return self.insertAssumeCapacity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert(self: *Self, value: Value) Allocator.Error!Id {
|
||||||
|
const id = try self.insertUndefined();
|
||||||
|
self.getAssumeExists(id).* = value;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn exists(self: *Self, id: Id) bool {
|
pub fn exists(self: *Self, id: Id) bool {
|
||||||
if (id.index >= self.slots.items.len) {
|
if (id.index >= self.slots.items.len) {
|
||||||
return false;
|
return false;
|
||||||
@ -211,15 +196,11 @@ pub fn SlotMapType(Index: type, Generation: type, Value: type) type {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(slots: std.ArrayList(Slot)) Self {
|
pub fn init(slots: []Slot) Self {
|
||||||
var self: Self = .empty;
|
var self: Self = .empty;
|
||||||
self.slots = slots;
|
self.slots = .initBuffer(slots);
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self, gpa: Allocator) void {
|
|
||||||
self.slots.deinit(gpa);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,13 +215,13 @@ test "insert & remove" {
|
|||||||
var map: TestMap = .empty;
|
var map: TestMap = .empty;
|
||||||
defer map.deinit(gpa);
|
defer map.deinit(gpa);
|
||||||
|
|
||||||
const id1 = try map.insert(gpa);
|
const id1 = try map.insertUndefined(gpa);
|
||||||
try expect(map.exists(id1));
|
try expect(map.exists(id1));
|
||||||
try expect(map.remove(id1));
|
try expect(map.remove(id1));
|
||||||
try expect(!map.exists(id1));
|
try expect(!map.exists(id1));
|
||||||
try expect(!map.remove(id1));
|
try expect(!map.remove(id1));
|
||||||
|
|
||||||
const id2 = try map.insert(gpa);
|
const id2 = try map.insertUndefined(gpa);
|
||||||
try expect(map.exists(id2));
|
try expect(map.exists(id2));
|
||||||
try expect(!map.exists(id1));
|
try expect(!map.exists(id1));
|
||||||
}
|
}
|
||||||
@ -253,14 +234,14 @@ test "generation wrap around" {
|
|||||||
defer map.deinit(gpa);
|
defer map.deinit(gpa);
|
||||||
|
|
||||||
// Grow array list so that at least 1 slot exists
|
// Grow array list so that at least 1 slot exists
|
||||||
const id1 = try map.insert(gpa);
|
const id1 = try map.insertUndefined(gpa);
|
||||||
map.removeAssumeExists(id1);
|
map.removeAssumeExists(id1);
|
||||||
|
|
||||||
// Artificially increase generation count
|
// Artificially increase generation count
|
||||||
map.slots.items[id1.index].generation = std.math.maxInt(@FieldType(TestMap.Id, "generation"));
|
map.slots.items[id1.index].generation = std.math.maxInt(@FieldType(TestMap.Id, "generation"));
|
||||||
|
|
||||||
// Check if generation wraps around
|
// Check if generation wraps around
|
||||||
const id2 = try map.insert(gpa);
|
const id2 = try map.insertUndefined(gpa);
|
||||||
map.removeAssumeExists(id2);
|
map.removeAssumeExists(id2);
|
||||||
try expectEqual(id1.index, id2.index);
|
try expectEqual(id1.index, id2.index);
|
||||||
try expectEqual(0, map.slots.items[id1.index].generation);
|
try expectEqual(0, map.slots.items[id1.index].generation);
|
||||||
@ -274,9 +255,9 @@ test "iterator" {
|
|||||||
defer map.deinit(gpa);
|
defer map.deinit(gpa);
|
||||||
|
|
||||||
// Create array which has a hole
|
// Create array which has a hole
|
||||||
const id1 = try map.insert(gpa);
|
const id1 = try map.insertUndefined(gpa);
|
||||||
const id2 = try map.insert(gpa);
|
const id2 = try map.insertUndefined(gpa);
|
||||||
const id3 = try map.insert(gpa);
|
const id3 = try map.insertUndefined(gpa);
|
||||||
|
|
||||||
map.removeAssumeExists(id2);
|
map.removeAssumeExists(id2);
|
||||||
|
|
||||||
@ -294,11 +275,11 @@ test "clear retaining capacity" {
|
|||||||
var map: TestMap = .empty;
|
var map: TestMap = .empty;
|
||||||
defer map.deinit(gpa);
|
defer map.deinit(gpa);
|
||||||
|
|
||||||
const id1 = try map.insert(gpa);
|
const id1 = try map.insertUndefined(gpa);
|
||||||
try expect(map.exists(id1));
|
try expect(map.exists(id1));
|
||||||
map.clearRetainingCapacity();
|
map.clearRetainingCapacity();
|
||||||
|
|
||||||
const id2 = try map.insert(gpa);
|
const id2 = try map.insertUndefined(gpa);
|
||||||
try expect(map.exists(id2));
|
try expect(map.exists(id2));
|
||||||
|
|
||||||
try expectEqual(id1, id2);
|
try expectEqual(id1, id2);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user