add shop panels
This commit is contained in:
parent
29b6af5592
commit
ca29401911
636
src/app.zig
636
src/app.zig
@ -37,6 +37,7 @@ entities: EntitySlotMap,
|
|||||||
eternal_flame_id: EntityId,
|
eternal_flame_id: EntityId,
|
||||||
|
|
||||||
upgrades: Upgrades,
|
upgrades: Upgrades,
|
||||||
|
shop: Shop,
|
||||||
|
|
||||||
font: Gfx.Font.Id,
|
font: Gfx.Font.Id,
|
||||||
idle_animations: AnimationDirections,
|
idle_animations: AnimationDirections,
|
||||||
@ -227,19 +228,29 @@ const Upgrades = struct {
|
|||||||
minion_walk: MinionWalkStats = .{},
|
minion_walk: MinionWalkStats = .{},
|
||||||
|
|
||||||
tree_station_count: u32 = 0,
|
tree_station_count: u32 = 0,
|
||||||
tree_health: u32 = 10,
|
tree_level: u32 = 1,
|
||||||
tree_respawn_duration: Nanoseconds = std.time.ns_per_s,
|
tree_health: u32 = tree_levels[0].health,
|
||||||
tree_wood_amount: u32 = 1,
|
tree_respawn_duration: Nanoseconds = tree_levels[0].respawn_duration,
|
||||||
|
tree_wood_amount: u32 = tree_levels[0].wood_amount,
|
||||||
|
tree_wood_health: f32 = tree_levels[0].wood_health,
|
||||||
|
|
||||||
burn_max_stack_size: u32 = 10,
|
|
||||||
burn_speed: f32 = 10,
|
burn_speed: f32 = 10,
|
||||||
burn_wood_health: f32 = 20,
|
|
||||||
burn_heat_per_wood: u32 = 1,
|
burn_heat_per_wood: u32 = 1,
|
||||||
|
burn_max_stack_size: u32 = 10,
|
||||||
burn_levels: [2]BurnLevel = .{
|
burn_levels: [2]BurnLevel = .{
|
||||||
.{ .threshold = 10, .multiplier = 2 },
|
.{ .threshold = 10, .multiplier = 2 },
|
||||||
.{ .threshold = 20, .multiplier = 3 },
|
.{ .threshold = 20, .multiplier = 3 },
|
||||||
},
|
},
|
||||||
|
|
||||||
|
const tree_levels = [_]TreeLevel{
|
||||||
|
TreeLevel{
|
||||||
|
.health = 10,
|
||||||
|
.respawn_duration = std.time.ns_per_s,
|
||||||
|
.wood_amount = 1,
|
||||||
|
.wood_health = 20,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const MinionWalkStats = struct {
|
const MinionWalkStats = struct {
|
||||||
friction: f32 = 0.998,
|
friction: f32 = 0.998,
|
||||||
max_velocity: f32 = 50,
|
max_velocity: f32 = 50,
|
||||||
@ -250,6 +261,133 @@ const Upgrades = struct {
|
|||||||
threshold: u32,
|
threshold: u32,
|
||||||
multiplier: u32,
|
multiplier: u32,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TreeLevel = struct {
|
||||||
|
health: u32,
|
||||||
|
respawn_duration: Nanoseconds,
|
||||||
|
wood_amount: u32,
|
||||||
|
wood_health: f32,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const Shop = struct {
|
||||||
|
minion_upgrades: []Item,
|
||||||
|
tree_upgrades: []Item,
|
||||||
|
fire_upgrades: []Item,
|
||||||
|
|
||||||
|
const Item = struct {
|
||||||
|
label: []const u8,
|
||||||
|
|
||||||
|
value_ref: ValueRef,
|
||||||
|
value_scaling: Scaling,
|
||||||
|
|
||||||
|
cost: u32,
|
||||||
|
cost_scaling: Scaling,
|
||||||
|
|
||||||
|
unlocked: bool = false,
|
||||||
|
limit: ?f32 = null,
|
||||||
|
|
||||||
|
const ValueType = enum {
|
||||||
|
u32,
|
||||||
|
f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ValueRef = union(ValueType) {
|
||||||
|
u32: *u32,
|
||||||
|
f32: *f32,
|
||||||
|
|
||||||
|
pub fn initU32(value: *u32) ValueRef {
|
||||||
|
return ValueRef{
|
||||||
|
.u32 = value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initF32(value: *f32) ValueRef {
|
||||||
|
return ValueRef{
|
||||||
|
.f32 = value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deref(self: ValueRef) Value {
|
||||||
|
return switch(self) {
|
||||||
|
.u32 => |value| Value{ .u32 = value.* },
|
||||||
|
.f32 => |value| Value{ .f32 = value.* },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(self: *ValueRef, new_value: Value) void {
|
||||||
|
assert(@as(ValueType, self.*) == @as(ValueType, new_value));
|
||||||
|
|
||||||
|
switch (new_value) {
|
||||||
|
.u32 => |value| {
|
||||||
|
self.u32.* = value;
|
||||||
|
},
|
||||||
|
.f32 => |value| {
|
||||||
|
self.f32.* = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Value = union(ValueType) {
|
||||||
|
u32: u32,
|
||||||
|
f32: f32,
|
||||||
|
|
||||||
|
pub fn format(self: Value, writer: *std.Io.Writer) std.Io.Writer.Error!void {
|
||||||
|
switch (self) {
|
||||||
|
.u32 => |v| try writer.print("{}", .{ v }),
|
||||||
|
.f32 => |v| try writer.print("{:.2}", .{ v }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Scaling = struct {
|
||||||
|
fixed: f32,
|
||||||
|
linear: f32,
|
||||||
|
quadratic: f32,
|
||||||
|
|
||||||
|
pub fn initFixed(fixed: f32) Scaling {
|
||||||
|
return Scaling{
|
||||||
|
.fixed = fixed,
|
||||||
|
.linear = 1,
|
||||||
|
.quadratic = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initLinear(fixed: f32, linear: f32) Scaling {
|
||||||
|
return Scaling{
|
||||||
|
.fixed = fixed,
|
||||||
|
.linear = linear,
|
||||||
|
.quadratic = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initQuadratic(fixed: f32, linear: f32, quadratic: f32) Scaling {
|
||||||
|
return Scaling{
|
||||||
|
.fixed = fixed,
|
||||||
|
.linear = linear,
|
||||||
|
.quadratic = quadratic,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply(self: Scaling, value: Value) Value {
|
||||||
|
const value_f32: f32 = switch(value) {
|
||||||
|
.u32 => |v| @floatFromInt(v),
|
||||||
|
.f32 => |v| v
|
||||||
|
};
|
||||||
|
|
||||||
|
var new_value_f32: f32 = 0;
|
||||||
|
new_value_f32 += value_f32 * value_f32 * self.quadratic;
|
||||||
|
new_value_f32 += value_f32 * self.linear;
|
||||||
|
new_value_f32 += self.fixed;
|
||||||
|
|
||||||
|
return switch(value) {
|
||||||
|
.u32 => Value{ .u32 = @ceil(new_value_f32) },
|
||||||
|
.f32 => Value{ .f32 = new_value_f32 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
fn initImageDataFromSTB(img: STBImage) Gfx.ImageData {
|
fn initImageDataFromSTB(img: STBImage) Gfx.ImageData {
|
||||||
@ -309,9 +447,12 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
|||||||
.camera_pos = .init(0, 0),
|
.camera_pos = .init(0, 0),
|
||||||
.font = font,
|
.font = font,
|
||||||
.eternal_flame_id = undefined,
|
.eternal_flame_id = undefined,
|
||||||
.upgrades = .{}
|
.upgrades = .{},
|
||||||
|
.shop = undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
try self.initShop(plt.arena);
|
||||||
|
|
||||||
self.spawnBurnStation(100);
|
self.spawnBurnStation(100);
|
||||||
self.spawnBurnStation(150);
|
self.spawnBurnStation(150);
|
||||||
|
|
||||||
@ -323,6 +464,95 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn initShop(self: *App, arena: Allocator) !void {
|
||||||
|
const minion_upgrades = try arena.dupe(Shop.Item, &.{
|
||||||
|
Shop.Item{
|
||||||
|
.label = "Minion count",
|
||||||
|
.limit = 50,
|
||||||
|
.unlocked = true,
|
||||||
|
|
||||||
|
.value_ref = .initU32(&self.upgrades.minion_count),
|
||||||
|
.value_scaling = .initFixed(1),
|
||||||
|
|
||||||
|
.cost = 1,
|
||||||
|
.cost_scaling = .initFixed(1),
|
||||||
|
},
|
||||||
|
Shop.Item{
|
||||||
|
.label = "Carry capacity",
|
||||||
|
.unlocked = true,
|
||||||
|
|
||||||
|
.value_ref = .initU32(&self.upgrades.minion_carry_capacity),
|
||||||
|
.value_scaling = .initFixed(1),
|
||||||
|
|
||||||
|
.cost = 1,
|
||||||
|
.cost_scaling = .initFixed(1),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const tree_upgrades = try arena.dupe(Shop.Item, &.{
|
||||||
|
Shop.Item{
|
||||||
|
.label = "Tree count",
|
||||||
|
.unlocked = true,
|
||||||
|
|
||||||
|
.value_ref = .initU32(&self.upgrades.tree_station_count),
|
||||||
|
.value_scaling = .initFixed(1),
|
||||||
|
|
||||||
|
.cost = 1,
|
||||||
|
.cost_scaling = .initFixed(1),
|
||||||
|
},
|
||||||
|
Shop.Item{
|
||||||
|
.label = "Tree level",
|
||||||
|
.limit = Upgrades.tree_levels.len,
|
||||||
|
.unlocked = true,
|
||||||
|
|
||||||
|
.value_ref = .initU32(&self.upgrades.tree_level),
|
||||||
|
.value_scaling = .initFixed(1),
|
||||||
|
|
||||||
|
.cost = 1,
|
||||||
|
.cost_scaling = .initFixed(1),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const fire_upgrades = try arena.dupe(Shop.Item, &.{
|
||||||
|
Shop.Item{
|
||||||
|
.label = "Burn speed",
|
||||||
|
.unlocked = true,
|
||||||
|
|
||||||
|
.value_ref = .initF32(&self.upgrades.burn_speed),
|
||||||
|
.value_scaling = .initFixed(1),
|
||||||
|
|
||||||
|
.cost = 1,
|
||||||
|
.cost_scaling = .initFixed(1),
|
||||||
|
},
|
||||||
|
Shop.Item{
|
||||||
|
.label = "Heat per wood",
|
||||||
|
.unlocked = true,
|
||||||
|
|
||||||
|
.value_ref = .initU32(&self.upgrades.burn_heat_per_wood),
|
||||||
|
.value_scaling = .initFixed(1),
|
||||||
|
|
||||||
|
.cost = 1,
|
||||||
|
.cost_scaling = .initFixed(1),
|
||||||
|
},
|
||||||
|
Shop.Item{
|
||||||
|
.label = "Max stack size",
|
||||||
|
.unlocked = true,
|
||||||
|
|
||||||
|
.value_ref = .initU32(&self.upgrades.burn_max_stack_size),
|
||||||
|
.value_scaling = .initFixed(1),
|
||||||
|
|
||||||
|
.cost = 1,
|
||||||
|
.cost_scaling = .initFixed(1),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
self.shop = .{
|
||||||
|
.minion_upgrades = minion_upgrades,
|
||||||
|
.tree_upgrades = tree_upgrades,
|
||||||
|
.fire_upgrades = fire_upgrades,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
fn spawnEntity(self: *App, e: Entity) ?EntityId {
|
fn spawnEntity(self: *App, e: Entity) ?EntityId {
|
||||||
return self.entities.insert(e) catch {
|
return self.entities.insert(e) catch {
|
||||||
log.warn("Failed to spawn entity {}, entity limit reached", .{e.type});
|
log.warn("Failed to spawn entity {}, entity limit reached", .{e.type});
|
||||||
@ -391,7 +621,7 @@ fn destroyTree(self: *App, tree_id: EntityId) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn spawnWood(self: *App, pos: Vec2) void {
|
fn spawnWood(self: *App, pos: Vec2) void {
|
||||||
const health = self.upgrades.burn_wood_health;
|
const health = self.upgrades.tree_wood_health;
|
||||||
|
|
||||||
_ = self.spawnEntity(Entity{
|
_ = self.spawnEntity(Entity{
|
||||||
.type = .wood,
|
.type = .wood,
|
||||||
@ -773,6 +1003,318 @@ fn getBurnMultiplier(self: *App) u32 {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn buyUpgrade(self: *App, shop_item: *Shop.Item) void {
|
||||||
|
if (self.heat < shop_item.cost) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.heat -= shop_item.cost;
|
||||||
|
|
||||||
|
shop_item.cost = shop_item.cost_scaling.apply(.{ .u32 = shop_item.cost }).u32;
|
||||||
|
|
||||||
|
const value = shop_item.value_ref.deref();
|
||||||
|
const new_value = shop_item.value_scaling.apply(value);
|
||||||
|
shop_item.value_ref.set(new_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn showShopPanel(
|
||||||
|
self: *App,
|
||||||
|
plt: Platform.Frame,
|
||||||
|
panel_label: []const u8,
|
||||||
|
panel_pos: Vec2,
|
||||||
|
shop_items: []Shop.Item
|
||||||
|
) !void {
|
||||||
|
var unlocked_count: f32 = 0;
|
||||||
|
for (shop_items) |*shop_item| {
|
||||||
|
if (shop_item.unlocked) {
|
||||||
|
unlocked_count += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unlocked_count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const panel_size = Vec2.init(400, unlocked_count * 32 + 10);
|
||||||
|
const border_width = 5;
|
||||||
|
const border_color = Color.rgb(180, 180, 180);
|
||||||
|
|
||||||
|
Gfx.transformPush();
|
||||||
|
defer Gfx.transformPop();
|
||||||
|
|
||||||
|
Gfx.transformTranslate(panel_pos);
|
||||||
|
Gfx.transformTranslate(.init(-panel_size.x/2, -panel_size.y));
|
||||||
|
Gfx.drawRectangleOutline(.init(0, 0), panel_size, border_color, border_width, 0);
|
||||||
|
|
||||||
|
Gfx.transformTranslate(.init(0, panel_size.y));
|
||||||
|
{
|
||||||
|
const panel_label_size = 40;
|
||||||
|
const panel_label_margin = Vec2.init(15, 10);
|
||||||
|
const panel_label_width = Gfx.measureText(self.font, panel_label_size, panel_label).size.x;
|
||||||
|
|
||||||
|
Gfx.transformPush();
|
||||||
|
defer Gfx.transformPop();
|
||||||
|
|
||||||
|
Gfx.drawText(self.font, .{
|
||||||
|
.text = panel_label,
|
||||||
|
.pos = panel_label_margin.add(.init(0, 5)),
|
||||||
|
.height = panel_label_size
|
||||||
|
});
|
||||||
|
Gfx.drawRectangleOutline(
|
||||||
|
.init(0, border_width),
|
||||||
|
Vec2.init(panel_label_width, panel_label_size).add(panel_label_margin.multiplyScalar(2)),
|
||||||
|
border_color,
|
||||||
|
border_width,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Gfx.transformTranslate(.init(10, -5));
|
||||||
|
|
||||||
|
var index: usize = 0;
|
||||||
|
for (shop_items) |*shop_item| {
|
||||||
|
if (!shop_item.unlocked) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
defer index += 1;
|
||||||
|
|
||||||
|
Gfx.transformPush();
|
||||||
|
defer Gfx.transformPop();
|
||||||
|
Gfx.transformTranslate(.init(0, -32 * @as(f32, @floatFromInt(index + 1))));
|
||||||
|
|
||||||
|
const hitbox = Rect.init(-10, 0, panel_size.x, 32);
|
||||||
|
const cost = shop_item.cost;
|
||||||
|
|
||||||
|
var limit_reached = false;
|
||||||
|
if (shop_item.limit) |limit| {
|
||||||
|
limit_reached = switch (shop_item.value_ref) {
|
||||||
|
.u32 => |value| @as(f32, @floatFromInt(value.*)) >= limit,
|
||||||
|
.f32 => |value| value.* >= limit
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var text_color = Color.white;
|
||||||
|
var is_mouse_hovering = false;
|
||||||
|
if (limit_reached) {
|
||||||
|
text_color = .rgb(120, 120, 120);
|
||||||
|
} else if (plt.input.mouse_position) |screen_mouse| {
|
||||||
|
const transform = Gfx.getTransform();
|
||||||
|
const world_mouse = screen_mouse.sub(transform.offset);
|
||||||
|
if (hitbox.isInside(world_mouse)) {
|
||||||
|
is_mouse_hovering = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_mouse_hovering) {
|
||||||
|
if (self.heat >= cost) {
|
||||||
|
if (plt.input.isMousePressed(.left)) {
|
||||||
|
self.buyUpgrade(shop_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plt.input.isMouseDown(.left)) {
|
||||||
|
text_color = .rgb(100, 200, 100);
|
||||||
|
} else {
|
||||||
|
text_color = .rgb(180, 255, 180);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text_color = .rgb(255, 180, 180);
|
||||||
|
}
|
||||||
|
Gfx.drawLine(
|
||||||
|
.init(hitbox.left() + 5, hitbox.bottom() - 5),
|
||||||
|
.init(hitbox.right() - 5, hitbox.bottom() - 5),
|
||||||
|
text_color,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Gfx.drawText(self.font, .{
|
||||||
|
.text = shop_item.label,
|
||||||
|
.pos = .init(0, 0),
|
||||||
|
.height = 24,
|
||||||
|
.color = text_color
|
||||||
|
});
|
||||||
|
|
||||||
|
const allocPrint = std.fmt.allocPrint;
|
||||||
|
Gfx.drawText(self.font, .{
|
||||||
|
.text = try allocPrint(plt.arena, "{f}", .{shop_item.value_ref.deref()}),
|
||||||
|
.pos = .init(180, 0),
|
||||||
|
.height = 24,
|
||||||
|
.color = text_color
|
||||||
|
});
|
||||||
|
|
||||||
|
if (is_mouse_hovering) {
|
||||||
|
const new_value = shop_item.value_scaling.apply(shop_item.value_ref.deref());
|
||||||
|
Gfx.drawText(self.font, .{
|
||||||
|
.text = try allocPrint(plt.arena, "> {f}", .{new_value}),
|
||||||
|
.pos = .init(240, 0),
|
||||||
|
.height = 24,
|
||||||
|
.color = text_color
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Gfx.drawText(self.font, .{
|
||||||
|
.text = try std.fmt.allocPrint(plt.arena, "{}H", .{cost}),
|
||||||
|
.pos = .init(panel_size.x - 20, 0),
|
||||||
|
.height = 24,
|
||||||
|
.color = text_color,
|
||||||
|
.alignment = .init(1, 0)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn showUI(self: *App, plt: Platform.Frame) !void {
|
||||||
|
var heat_pos = Vec2.init(0, -200);
|
||||||
|
|
||||||
|
{ // Draw heat counter
|
||||||
|
var builder = Gfx.TextBuilder.init(plt.arena);
|
||||||
|
|
||||||
|
builder.font(self.font, 64);
|
||||||
|
builder.color = .rgb(200, 20, 20);
|
||||||
|
try builder.put(try std.fmt.allocPrint(plt.arena, "{}", .{self.heat}));
|
||||||
|
|
||||||
|
builder.font(self.font, 32);
|
||||||
|
builder.color = .rgb(240, 100, 100);
|
||||||
|
builder.offset(.init(10, 0));
|
||||||
|
try builder.put("Heat");
|
||||||
|
|
||||||
|
builder.draw(heat_pos.sub(builder.bounds.size.divideScalar(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
try self.showShopPanel(plt, "Minion", Vec2.init(0, -600), self.shop.minion_upgrades);
|
||||||
|
try self.showShopPanel(plt, "Flame", Vec2.init(600, -600), self.shop.fire_upgrades);
|
||||||
|
try self.showShopPanel(plt, "Trees", Vec2.init(-600, -600), self.shop.tree_upgrades);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn showDebugScaling(label: []const u8, scaling: *Shop.Item.Scaling) void {
|
||||||
|
ImGUI.pushID(.{ .string = label });
|
||||||
|
defer ImGUI.popID();
|
||||||
|
|
||||||
|
const field_width = 80;
|
||||||
|
|
||||||
|
ImGUI.text("{s}", .{label});
|
||||||
|
ImGUI.sameLine();
|
||||||
|
ImGUI.setNextItemWidth(field_width);
|
||||||
|
_ = ImGUI.inputF32Drag(.{ .label = "fixed", .value = &scaling.fixed });
|
||||||
|
ImGUI.sameLine();
|
||||||
|
ImGUI.setNextItemWidth(field_width);
|
||||||
|
_ = ImGUI.inputF32Drag(.{ .label = "linear", .value = &scaling.linear });
|
||||||
|
ImGUI.sameLine();
|
||||||
|
ImGUI.setNextItemWidth(field_width);
|
||||||
|
_ = ImGUI.inputF32Drag(.{ .label = "quadratic", .value = &scaling.quadratic });
|
||||||
|
}
|
||||||
|
|
||||||
|
fn showDebugShopItems(self: *App, label: []const u8, shop_items: []Shop.Item) void {
|
||||||
|
_ = self; // autofix
|
||||||
|
ImGUI.pushID(.{ .string = label });
|
||||||
|
defer ImGUI.popID();
|
||||||
|
|
||||||
|
_ = ImGUI.text("{s}", .{label});
|
||||||
|
for (0.., shop_items) |i, *shop_item| {
|
||||||
|
ImGUI.pushID(.{ .int = @intCast(i) });
|
||||||
|
defer ImGUI.popID();
|
||||||
|
|
||||||
|
if (ImGUI.collapsingHeader(shop_item.label)) {
|
||||||
|
_ = ImGUI.inputU32("cost", &shop_item.cost);
|
||||||
|
_ = ImGUI.checkbox("unlocked", &shop_item.unlocked);
|
||||||
|
|
||||||
|
showDebugScaling("Cost scaling", &shop_item.cost_scaling);
|
||||||
|
showDebugScaling("Value scaling", &shop_item.value_scaling);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn showDebug(self: *App, plt: Platform.Frame) !void {
|
||||||
|
_ = plt; // autofix
|
||||||
|
if (!ImGUI.beginWindow(.{ .name = "Debug", .alpha = 0.8, .size = .init(500, 200), .collapsed = true })) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
defer ImGUI.endWindow();
|
||||||
|
|
||||||
|
if (ImGUI.beginTabBar("tab bar")) {
|
||||||
|
defer ImGUI.endTabBar();
|
||||||
|
|
||||||
|
if (ImGUI.beginTabItem("Upgrades")) {
|
||||||
|
defer ImGUI.endTabItem();
|
||||||
|
|
||||||
|
var heat: u32 = @intCast(self.heat);
|
||||||
|
if (ImGUI.inputU32("heat", &heat)) {
|
||||||
|
self.heat = @intCast(heat);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGUI.separator();
|
||||||
|
_ = ImGUI.text("Minion upgrades", .{});
|
||||||
|
_ = ImGUI.inputU32("count", &self.upgrades.minion_count);
|
||||||
|
_ = ImGUI.inputU32("carry_capacity", &self.upgrades.minion_carry_capacity);
|
||||||
|
|
||||||
|
_ = ImGUI.inputU32("throw_strength", &self.upgrades.minion_throw_strength);
|
||||||
|
_ = ImGUI.inputDuration("throw_cooldown", &self.upgrades.minion_throw_cooldown);
|
||||||
|
|
||||||
|
_ = ImGUI.inputU32("chop_damage", &self.upgrades.minion_chop_damage);
|
||||||
|
_ = ImGUI.inputF32Drag(.{
|
||||||
|
.label = "chop_rate",
|
||||||
|
.value = &self.upgrades.minion_chop_rate
|
||||||
|
});
|
||||||
|
|
||||||
|
_ = ImGUI.inputF32Drag(.{
|
||||||
|
.label = "walk_max_speed",
|
||||||
|
.value = &self.upgrades.minion_walk.max_speed
|
||||||
|
});
|
||||||
|
_ = ImGUI.inputF32Drag(.{
|
||||||
|
.label = "walk_max_vel",
|
||||||
|
.value = &self.upgrades.minion_walk.max_velocity
|
||||||
|
});
|
||||||
|
_ = ImGUI.inputF32Drag(.{
|
||||||
|
.label = "walk_friction",
|
||||||
|
.value = &self.upgrades.minion_walk.friction,
|
||||||
|
.ex = .{ .min = 0, .max = 1, .speed = 0.0001 }
|
||||||
|
});
|
||||||
|
|
||||||
|
ImGUI.separator();
|
||||||
|
_ = ImGUI.text("Tree upgrades", .{});
|
||||||
|
_ = ImGUI.inputU32("level", &self.upgrades.tree_level);
|
||||||
|
_ = ImGUI.inputU32("station_count", &self.upgrades.tree_station_count);
|
||||||
|
_ = ImGUI.inputU32("health", &self.upgrades.tree_health);
|
||||||
|
_ = ImGUI.inputU32("wood_amount", &self.upgrades.tree_wood_amount);
|
||||||
|
_ = ImGUI.inputF32(.{
|
||||||
|
.label = "wood_health",
|
||||||
|
.value = &self.upgrades.tree_wood_health
|
||||||
|
});
|
||||||
|
_ = ImGUI.inputDuration("respawn_duration", &self.upgrades.tree_respawn_duration);
|
||||||
|
|
||||||
|
ImGUI.separator();
|
||||||
|
_ = ImGUI.text("Burn upgrades", .{});
|
||||||
|
_ = ImGUI.inputF32Drag(.{
|
||||||
|
.label = "speed",
|
||||||
|
.value = &self.upgrades.burn_speed
|
||||||
|
});
|
||||||
|
_ = ImGUI.inputU32("heat_per_wood", &self.upgrades.burn_heat_per_wood);
|
||||||
|
_ = ImGUI.inputU32("max_stack_size", &self.upgrades.burn_max_stack_size);
|
||||||
|
for (1.., &self.upgrades.burn_levels) |i, *level| {
|
||||||
|
ImGUI.pushID(.{ .int = @intCast(i) });
|
||||||
|
defer ImGUI.popID();
|
||||||
|
|
||||||
|
_ = ImGUI.text("Level {}", .{i});
|
||||||
|
ImGUI.sameLine();
|
||||||
|
ImGUI.setNextItemWidth(150);
|
||||||
|
_ = ImGUI.inputU32("threshold", &level.threshold);
|
||||||
|
ImGUI.sameLine();
|
||||||
|
ImGUI.setNextItemWidth(100);
|
||||||
|
_ = ImGUI.inputU32("multiplier", &level.multiplier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGUI.beginTabItem("Shop")) {
|
||||||
|
defer ImGUI.endTabItem();
|
||||||
|
|
||||||
|
self.showDebugShopItems("Minion", self.shop.minion_upgrades);
|
||||||
|
ImGUI.separator();
|
||||||
|
self.showDebugShopItems("Tree", self.shop.tree_upgrades);
|
||||||
|
ImGUI.separator();
|
||||||
|
self.showDebugShopItems("Fire", self.shop.fire_upgrades);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn frame(self: *App, plt: Platform.Frame) !void {
|
pub fn frame(self: *App, plt: Platform.Frame) !void {
|
||||||
const input = plt.input;
|
const input = plt.input;
|
||||||
_ = input; // autofix
|
_ = input; // autofix
|
||||||
@ -790,8 +1332,8 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
|||||||
self.camera_pos = self.camera_pos.sub(plt.input.mouse_delta);
|
self.camera_pos = self.camera_pos.sub(plt.input.mouse_delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
Gfx.viewTranslate(plt.input.window_size.x/2, plt.input.window_size.y/2);
|
Gfx.transformTranslate(plt.input.window_size.divideScalar(2));
|
||||||
Gfx.viewTranslate(-self.camera_pos.x, -self.camera_pos.y);
|
Gfx.transformTranslate(self.camera_pos.multiplyScalar(-1));
|
||||||
|
|
||||||
Gfx.setClearColor(.rgb(40, 40, 40));
|
Gfx.setClearColor(.rgb(40, 40, 40));
|
||||||
Gfx.drawLine(.init(-800, 0), .init(800, 0), .white, 5);
|
Gfx.drawLine(.init(-800, 0), .init(800, 0), .white, 5);
|
||||||
@ -1186,80 +1728,8 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Gfx.drawText(self.font, .{
|
try self.showUI(plt);
|
||||||
.height = 32,
|
try self.showDebug(plt);
|
||||||
.pos = .init(-50, -200),
|
|
||||||
.text = try std.fmt.allocPrint(plt.arena, "Heat: {}", .{self.heat}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (ImGUI.beginWindow(.{ .name = "Debug", .alpha = 0.8, .size = .init(500, 200) })) {
|
|
||||||
defer ImGUI.endWindow();
|
|
||||||
|
|
||||||
var heat: u32 = @intCast(self.heat);
|
|
||||||
if (ImGUI.inputU32("heat", &heat)) {
|
|
||||||
self.heat = @intCast(heat);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGUI.separator();
|
|
||||||
_ = ImGUI.text("Minion upgrades", .{});
|
|
||||||
_ = ImGUI.inputU32("count", &self.upgrades.minion_count);
|
|
||||||
_ = ImGUI.inputU32("carry_capacity", &self.upgrades.minion_carry_capacity);
|
|
||||||
|
|
||||||
_ = ImGUI.inputU32("throw_strength", &self.upgrades.minion_throw_strength);
|
|
||||||
_ = ImGUI.inputDuration("throw_cooldown", &self.upgrades.minion_throw_cooldown);
|
|
||||||
|
|
||||||
_ = ImGUI.inputU32("chop_damage", &self.upgrades.minion_chop_damage);
|
|
||||||
_ = ImGUI.inputF32Drag(.{
|
|
||||||
.label = "chop_rate",
|
|
||||||
.value = &self.upgrades.minion_chop_rate
|
|
||||||
});
|
|
||||||
|
|
||||||
_ = ImGUI.inputF32Drag(.{
|
|
||||||
.label = "walk_max_speed",
|
|
||||||
.value = &self.upgrades.minion_walk.max_speed
|
|
||||||
});
|
|
||||||
_ = ImGUI.inputF32Drag(.{
|
|
||||||
.label = "walk_max_vel",
|
|
||||||
.value = &self.upgrades.minion_walk.max_velocity
|
|
||||||
});
|
|
||||||
_ = ImGUI.inputF32Drag(.{
|
|
||||||
.label = "walk_friction",
|
|
||||||
.value = &self.upgrades.minion_walk.friction,
|
|
||||||
.ex = .{ .min = 0, .max = 1, .speed = 0.0001 }
|
|
||||||
});
|
|
||||||
|
|
||||||
ImGUI.separator();
|
|
||||||
_ = ImGUI.text("Tree upgrades", .{});
|
|
||||||
_ = ImGUI.inputU32("station_count", &self.upgrades.tree_station_count);
|
|
||||||
_ = ImGUI.inputU32("health", &self.upgrades.tree_health);
|
|
||||||
_ = ImGUI.inputU32("wood_amount", &self.upgrades.tree_wood_amount);
|
|
||||||
_ = ImGUI.inputDuration("respawn_duration", &self.upgrades.tree_respawn_duration);
|
|
||||||
|
|
||||||
ImGUI.separator();
|
|
||||||
_ = ImGUI.text("Burn upgrades", .{});
|
|
||||||
_ = ImGUI.inputF32Drag(.{
|
|
||||||
.label = "speed",
|
|
||||||
.value = &self.upgrades.burn_speed
|
|
||||||
});
|
|
||||||
_ = ImGUI.inputF32(.{
|
|
||||||
.label = "wood_health",
|
|
||||||
.value = &self.upgrades.burn_wood_health
|
|
||||||
});
|
|
||||||
_ = ImGUI.inputU32("heat_per_wood", &self.upgrades.burn_heat_per_wood);
|
|
||||||
_ = ImGUI.inputU32("max_stack_size", &self.upgrades.burn_max_stack_size);
|
|
||||||
for (1.., &self.upgrades.burn_levels) |i, *level| {
|
|
||||||
ImGUI.pushID(.{ .int = @intCast(i) });
|
|
||||||
defer ImGUI.popID();
|
|
||||||
|
|
||||||
_ = ImGUI.text("Level {}", .{i});
|
|
||||||
ImGUI.sameLine();
|
|
||||||
ImGUI.setNextItemWidth(150);
|
|
||||||
_ = ImGUI.inputU32("threshold", &level.threshold);
|
|
||||||
ImGUI.sameLine();
|
|
||||||
ImGUI.setNextItemWidth(100);
|
|
||||||
_ = ImGUI.inputU32("multiplier", &level.multiplier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *App, plt: Platform.Deinit) void {
|
pub fn deinit(self: *App, plt: Platform.Deinit) void {
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
492
src/graphics.zig
492
src/graphics.zig
@ -26,7 +26,6 @@ const State = struct {
|
|||||||
|
|
||||||
frame_index: u64,
|
frame_index: u64,
|
||||||
|
|
||||||
view: Mat4,
|
|
||||||
shader: sg.Shader,
|
shader: sg.Shader,
|
||||||
pipeline: sg.Pipeline,
|
pipeline: sg.Pipeline,
|
||||||
bindings: sg.Bindings,
|
bindings: sg.Bindings,
|
||||||
@ -45,6 +44,9 @@ const State = struct {
|
|||||||
|
|
||||||
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,
|
||||||
@ -151,7 +153,6 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
|||||||
self.* = State{
|
self.* = State{
|
||||||
.gpa = gpa,
|
.gpa = gpa,
|
||||||
.frame_index = 0,
|
.frame_index = 0,
|
||||||
.view = .initIdentity(),
|
|
||||||
.shader = shader,
|
.shader = shader,
|
||||||
.pipeline = pipeline,
|
.pipeline = pipeline,
|
||||||
.bindings = bindings,
|
.bindings = bindings,
|
||||||
@ -167,6 +168,9 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
|
|||||||
.nearest_sampler = nearest_sampler,
|
.nearest_sampler = nearest_sampler,
|
||||||
.default_sampler = .nearest,
|
.default_sampler = .nearest,
|
||||||
|
|
||||||
|
.transforms_buffer = undefined,
|
||||||
|
.transforms = .initBuffer(&self.transforms_buffer),
|
||||||
|
|
||||||
.textures_buffer = undefined,
|
.textures_buffer = undefined,
|
||||||
.textures = .init(&self.textures_buffer),
|
.textures = .init(&self.textures_buffer),
|
||||||
.nil_texture = undefined,
|
.nil_texture = undefined,
|
||||||
@ -240,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,
|
||||||
@ -330,23 +334,62 @@ fn toSokolColor(color: Color) sokol.gfx.Color {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn viewTranslate(x: f32, y: f32) void {
|
fn getTransformPtr() ?*TransformFrame {
|
||||||
flush(.{});
|
|
||||||
|
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
self.view.translate(x, y, 0);
|
if (self.transforms.items.len == 0) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn viewScale(x: f32, y: f32) void {
|
return &self.transforms.items[self.transforms.items.len-1];
|
||||||
flush(.{});
|
}
|
||||||
|
|
||||||
|
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 self = &g_state;
|
||||||
self.view.scale(x, y, 1);
|
|
||||||
|
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,
|
||||||
@ -358,8 +401,6 @@ pub fn beginFrame() void {
|
|||||||
.swapchain = sglue.swapchain()
|
.swapchain = sglue.swapchain()
|
||||||
});
|
});
|
||||||
|
|
||||||
self.view = .initIdentity();
|
|
||||||
|
|
||||||
const default_spritesheet = self.spritesheets.getAssumeExists(self.default_spritesheet);
|
const default_spritesheet = self.spritesheets.getAssumeExists(self.default_spritesheet);
|
||||||
const spritesheet_texture = self.textures.getAssumeExists(default_spritesheet.texture);
|
const spritesheet_texture = self.textures.getAssumeExists(default_spritesheet.texture);
|
||||||
self.bindings.views[shd.VIEW_tex] = spritesheet_texture.view;
|
self.bindings.views[shd.VIEW_tex] = spritesheet_texture.view;
|
||||||
@ -448,7 +489,7 @@ pub fn flush(opts: FlushOptions) void {
|
|||||||
var vs_params: shd.VsParams = .{
|
var vs_params: shd.VsParams = .{
|
||||||
// TODO: Maybe 'view' matrix is not needed.
|
// TODO: Maybe 'view' matrix is not needed.
|
||||||
// We probably should only have a combined 'mvp' matrix
|
// We probably should only have a combined 'mvp' matrix
|
||||||
.view = self.view,
|
.view = .initIdentity(),
|
||||||
.projection = createProjectionMatrix(window_size)
|
.projection = createProjectionMatrix(window_size)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -480,6 +521,12 @@ 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 {
|
||||||
@ -488,6 +535,54 @@ pub fn drawRectangle(pos: Vec2, size: Vec2, color: Color) void {
|
|||||||
drawSprite(self.default_sprite, pos, size, color);
|
drawSprite(self.default_sprite, pos, size, 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(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(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(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(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 {
|
pub fn drawLine(from: Vec2, to: Vec2, color: Color, width: f32) void {
|
||||||
const self = &g_state;
|
const self = &g_state;
|
||||||
|
|
||||||
@ -749,6 +844,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1066,11 +1167,17 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn drawSpriteQuad(id: Sprite.Id, points: [4]Vec2, color: Color) void {
|
fn drawSpriteQuad(id: Sprite.Id, points: [4]Vec2, color: Color) void {
|
||||||
@ -1086,19 +1193,10 @@ fn drawSpriteQuad(id: Sprite.Id, points: [4]Vec2, color: Color) void {
|
|||||||
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);
|
||||||
|
|
||||||
const sprite = self.sprites.getAssumeExists(id);
|
const sprite = self.sprites.getAssumeExists(id);
|
||||||
const spritesheet = self.spritesheets.getAssumeExists(sprite.spritesheet);
|
const spritesheet = self.spritesheets.getAssumeExists(sprite.spritesheet);
|
||||||
const texel_size = Vec2.init(1, 1).divide(spritesheet.size);
|
|
||||||
_ = texel_size; // autofix
|
|
||||||
|
|
||||||
// TODO: I hope this doesn't bite me in the ass.
|
|
||||||
// The shrink of the UV by half a texel
|
|
||||||
// This is to avoid linear sampling artifacts at edges
|
|
||||||
//
|
|
||||||
// Yes I did regret this. Fuck.
|
|
||||||
//
|
|
||||||
// quad.setUVRect(sprite_uv.shrink(texel_size.divideScalar(2)));
|
|
||||||
quad.setUVRect(sprite_uv);
|
|
||||||
texture = spritesheet.texture;
|
texture = spritesheet.texture;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1215,63 +1313,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");
|
||||||
@ -1279,25 +1372,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
|
||||||
);
|
);
|
||||||
@ -1308,26 +1401,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;
|
|
||||||
|
pub const TextRunLayout = struct {
|
||||||
|
font: Font.Id,
|
||||||
|
scale_x: f32,
|
||||||
|
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);
|
||||||
const glyph_size = Vec2.initFromInt(i32, glyph.box.x1 - glyph.box.x0, glyph.box.y1 - glyph.box.y0);
|
const glyph_size = Vec2.initFromInt(i32, glyph.box.x1 - glyph.box.x0, glyph.box.y1 - glyph.box.y0);
|
||||||
if (glyph.sprite != self.nil_sprite) {
|
|
||||||
var draw_pos = current_point;
|
var draw_pos = self.pen_pos;
|
||||||
draw_pos.x -= @floatFromInt(glyph.box.x0);
|
draw_pos.x -= @floatFromInt(glyph.box.x0);
|
||||||
draw_pos.x += @as(f32, @floatFromInt(hmetrics.left_side_bearing)) * scale;
|
draw_pos.x += @as(f32, @floatFromInt(hmetrics.left_side_bearing)) * self.scale_x;
|
||||||
draw_pos.y += @floatFromInt(glyph.box.y0);
|
draw_pos.y += @floatFromInt(glyph.box.y0);
|
||||||
drawSprite(glyph.sprite, draw_pos, glyph_size, opts.color);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
current_point.x += @as(f32, @floatFromInt(hmetrics.advance_width)) * scale;
|
return bounds;
|
||||||
prev_glyph = glyph_index;
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
@ -1947,7 +2253,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,
|
||||||
@ -1979,6 +2288,8 @@ 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,
|
||||||
@ -2023,3 +2334,12 @@ pub const Quad = extern struct {
|
|||||||
self.vertices[3].position = pos.add(size);
|
self.vertices[3].position = pos.add(size);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const TransformFrame = struct {
|
||||||
|
offset: Vec2,
|
||||||
|
scale: Vec2,
|
||||||
|
|
||||||
|
pub fn apply(self: TransformFrame, position: Vec2) Vec2 {
|
||||||
|
return position.multiply(self.scale).add(self.offset);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
@ -460,3 +460,46 @@ pub fn setNextItemWidth(width: f32) void {
|
|||||||
|
|
||||||
ig.igSetNextItemWidth(width);
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
19
src/math.zig
19
src/math.zig
@ -404,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;
|
||||||
}
|
}
|
||||||
@ -447,9 +460,13 @@ pub const Rect = struct {
|
|||||||
pub fn shrink(self: Rect, margin: Vec2) Rect {
|
pub fn shrink(self: Rect, margin: Vec2) Rect {
|
||||||
return Rect{
|
return Rect{
|
||||||
.pos = self.pos.add(margin),
|
.pos = self.pos.add(margin),
|
||||||
.size = self.pos.add(margin.multiplyScalar(2))
|
.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 {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user