Cubyz/src/gui/gui.zig
IntegratedQuantum c2ef9aba62 Remove the root . . . . . . . . . . import, replacing it with @import("main")
To fully solve the ZLS issues I had to also add the import to the other executables.
2025-03-27 21:16:35 +01:00

750 lines
21 KiB
Zig

const std = @import("std");
const main = @import("main");
const graphics = main.graphics;
const draw = graphics.draw;
const ZonElement = main.ZonElement;
const settings = main.settings;
const vec = main.vec;
const Vec2f = vec.Vec2f;
const List = main.List;
const NeverFailingAllocator = main.heap.NeverFailingAllocator;
const Button = @import("components/Button.zig");
const CheckBox = @import("components/CheckBox.zig");
const ItemSlot = @import("components/ItemSlot.zig");
const ScrollBar = @import("components/ScrollBar.zig");
const ContinuousSlider = @import("components/ContinuousSlider.zig");
const DiscreteSlider = @import("components/DiscreteSlider.zig");
const TextInput = @import("components/TextInput.zig");
pub const GuiComponent = @import("gui_component.zig").GuiComponent;
pub const GuiWindow = @import("GuiWindow.zig");
pub const windowlist = @import("windows/_windowlist.zig");
const GamepadCursor = @import("gamepad_cursor.zig");
var windowList: List(*GuiWindow) = undefined;
var hudWindows: List(*GuiWindow) = undefined;
pub var openWindows: List(*GuiWindow) = undefined;
var selectedWindow: ?*GuiWindow = null;
pub var selectedTextInput: ?*TextInput = null;
var hoveredAWindow: bool = false;
pub var reorderWindows: bool = false;
pub var hideGui: bool = false;
pub var scale: f32 = undefined;
pub var hoveredItemSlot: ?*ItemSlot = null;
const GuiCommandQueue = struct { // MARK: GuiCommandQueue
const Action = enum {
open,
close,
};
const Command = struct {
window: *GuiWindow,
action: Action,
};
var commands: main.utils.ConcurrentQueue(Command) = undefined;
fn init() void {
commands = .init(main.globalAllocator, 16);
}
fn deinit() void {
commands.deinit();
}
fn scheduleCommand(command: Command) void {
commands.enqueue(command);
}
fn executeCommands() void {
while(commands.dequeue()) |command| {
switch(command.action) {
.open => {
executeOpenWindowCommand(command.window);
},
.close => {
executeCloseWindowCommand(command.window);
},
}
}
}
fn executeOpenWindowCommand(window: *GuiWindow) void {
defer updateWindowPositions();
for(openWindows.items, 0..) |_openWindow, i| {
if(_openWindow == window) {
_ = openWindows.swapRemove(i);
openWindows.appendAssumeCapacity(window);
selectedWindow = null;
return;
}
}
openWindows.append(window);
window.onOpenFn();
selectedWindow = null;
}
fn executeCloseWindowCommand(window: *GuiWindow) void {
defer updateWindowPositions();
if(selectedWindow == window) {
selectedWindow = null;
}
for(openWindows.items, 0..) |_openWindow, i| {
if(_openWindow == window) {
_ = openWindows.swapRemove(i);
window.onCloseFn();
break;
}
}
}
};
pub const Callback = struct {
callback: ?*const fn(usize) void = null,
arg: usize = 0,
pub fn run(self: Callback) void {
if(self.callback) |callback| {
callback(self.arg);
}
}
};
pub fn initWindowList() void {
GuiCommandQueue.init();
windowList = .init(main.globalAllocator);
hudWindows = .init(main.globalAllocator);
openWindows = .init(main.globalAllocator);
inline for(@typeInfo(windowlist).@"struct".decls) |decl| {
const windowStruct = @field(windowlist, decl.name);
windowStruct.window.id = decl.name;
addWindow(&windowStruct.window);
const functionNames = [_][]const u8{"render", "update", "updateSelected", "updateHovered", "onOpen", "onClose"};
inline for(functionNames) |function| {
if(@hasDecl(windowStruct, function)) {
@field(windowStruct.window, function ++ "Fn") = &@field(windowStruct, function);
}
}
}
}
pub fn deinitWindowList() void {
windowList.deinit();
hudWindows.deinit();
openWindows.deinit();
GuiCommandQueue.deinit();
}
pub fn init() void { // MARK: init()
inline for(@typeInfo(windowlist).@"struct".decls) |decl| {
const windowStruct = @field(windowlist, decl.name);
if(@hasDecl(windowStruct, "init")) {
windowStruct.init();
}
}
GuiWindow.__init();
Button.__init();
CheckBox.__init();
ItemSlot.__init();
ScrollBar.__init();
ContinuousSlider.__init();
DiscreteSlider.__init();
TextInput.__init();
load();
GamepadCursor.init();
}
pub fn deinit() void {
save();
GamepadCursor.deinit();
for(openWindows.items) |window| {
window.onCloseFn();
}
openWindows.clearRetainingCapacity();
GuiWindow.__deinit();
Button.__deinit();
CheckBox.__deinit();
ItemSlot.__deinit();
ScrollBar.__deinit();
ContinuousSlider.__deinit();
DiscreteSlider.__deinit();
TextInput.__deinit();
inline for(@typeInfo(windowlist).@"struct".decls) |decl| {
const WindowStruct = @field(windowlist, decl.name);
if(@hasDecl(WindowStruct, "deinit")) {
WindowStruct.deinit();
}
}
}
pub fn save() void { // MARK: save()
const guiZon = ZonElement.initObject(main.stackAllocator);
defer guiZon.deinit(main.stackAllocator);
for(windowList.items) |window| {
const windowZon = ZonElement.initObject(main.stackAllocator);
for(window.relativePosition, 0..) |relPos, i| {
const relPosZon = ZonElement.initObject(main.stackAllocator);
switch(relPos) {
.ratio => |ratio| {
relPosZon.put("type", "ratio");
relPosZon.put("ratio", ratio);
},
.attachedToFrame => |attachedToFrame| {
relPosZon.put("type", "attachedToFrame");
relPosZon.put("selfAttachmentPoint", @intFromEnum(attachedToFrame.selfAttachmentPoint));
relPosZon.put("otherAttachmentPoint", @intFromEnum(attachedToFrame.otherAttachmentPoint));
},
.relativeToWindow => |relativeToWindow| {
relPosZon.put("type", "relativeToWindow");
relPosZon.put("reference", relativeToWindow.reference.id);
relPosZon.put("ratio", relativeToWindow.ratio);
},
.attachedToWindow => |attachedToWindow| {
relPosZon.put("type", "attachedToWindow");
relPosZon.put("reference", attachedToWindow.reference.id);
relPosZon.put("selfAttachmentPoint", @intFromEnum(attachedToWindow.selfAttachmentPoint));
relPosZon.put("otherAttachmentPoint", @intFromEnum(attachedToWindow.otherAttachmentPoint));
},
}
windowZon.put(([_][]const u8{"relPos0", "relPos1"})[i], relPosZon);
}
windowZon.put("scale", window.scale);
guiZon.put(window.id, windowZon);
}
main.files.cubyzDir().writeZon("gui_layout.zig.zon", guiZon) catch |err| {
std.log.err("Could not write gui_layout.zig.zon: {s}", .{@errorName(err)});
};
}
fn load() void {
const zon: ZonElement = main.files.cubyzDir().readToZon(main.stackAllocator, "gui_layout.zig.zon") catch |err| blk: {
if(err != error.FileNotFound) {
std.log.err("Could not read gui_layout.zig.zon: {s}", .{@errorName(err)});
}
break :blk .null;
};
defer zon.deinit(main.stackAllocator);
for(windowList.items) |window| {
const windowZon = zon.getChild(window.id);
if(windowZon == .null) continue;
for(&window.relativePosition, 0..) |*relPos, i| {
const relPosZon = windowZon.getChild(([_][]const u8{"relPos0", "relPos1"})[i]);
const typ = relPosZon.get([]const u8, "type", "ratio");
if(std.mem.eql(u8, typ, "ratio")) {
relPos.* = .{.ratio = relPosZon.get(f32, "ratio", 0.5)};
} else if(std.mem.eql(u8, typ, "attachedToFrame")) {
relPos.* = .{.attachedToFrame = .{
.selfAttachmentPoint = @enumFromInt(relPosZon.get(u8, "selfAttachmentPoint", 0)),
.otherAttachmentPoint = @enumFromInt(relPosZon.get(u8, "otherAttachmentPoint", 0)),
}};
} else if(std.mem.eql(u8, typ, "relativeToWindow")) {
const reference = getWindowById(relPosZon.get([]const u8, "reference", "")) orelse continue;
relPos.* = .{.relativeToWindow = .{
.reference = reference,
.ratio = relPosZon.get(f32, "ratio", 0.5),
}};
} else if(std.mem.eql(u8, typ, "attachedToWindow")) {
const reference = getWindowById(relPosZon.get([]const u8, "reference", "")) orelse continue;
relPos.* = .{.attachedToWindow = .{
.reference = reference,
.selfAttachmentPoint = @enumFromInt(relPosZon.get(u8, "selfAttachmentPoint", 0)),
.otherAttachmentPoint = @enumFromInt(relPosZon.get(u8, "otherAttachmentPoint", 0)),
}};
} else {
std.log.err("Unknown window attachment type: {s}", .{typ});
}
}
window.scale = windowZon.get(f32, "scale", 1);
}
}
fn getWindowById(id: []const u8) ?*GuiWindow {
for(windowList.items) |window| {
if(std.mem.eql(u8, id, window.id)) {
return window;
}
}
std.log.err("Could not find window with id: {s}", .{id});
return null;
}
pub fn updateGuiScale() void {
if(settings.guiScale) |guiScale| {
scale = guiScale;
} else {
const windowSize = main.Window.getWindowSize();
const screenWidth = @min(windowSize[0], windowSize[1]*16/9);
scale = @floor(screenWidth/640.0 + 0.2);
if(scale < 1) {
scale = 0.5;
}
}
}
fn addWindow(window: *GuiWindow) void {
for(windowList.items) |other| {
if(std.mem.eql(u8, window.id, other.id)) {
std.log.err("Duplicate window id: {s}", .{window.id});
return;
}
}
if(window.isHud) {
hudWindows.append(window);
}
windowList.append(window);
}
pub fn openWindow(id: []const u8) void {
defer updateWindowPositions();
for(windowList.items) |window| {
if(std.mem.eql(u8, window.id, id)) {
openWindowFromRef(window);
return;
}
}
std.log.err("Could not find window with id {s}.", .{id});
}
pub fn openWindowFromRef(window: *GuiWindow) void {
GuiCommandQueue.scheduleCommand(.{.action = .open, .window = window});
}
pub fn toggleWindow(id: []const u8) void {
defer updateWindowPositions();
for(windowList.items) |window| {
if(std.mem.eql(u8, window.id, id)) {
for(openWindows.items, 0..) |_openWindow, i| {
if(_openWindow == window) {
_ = openWindows.swapRemove(i);
window.onCloseFn();
selectedWindow = null;
return;
}
}
openWindows.append(window);
window.onOpenFn();
selectedWindow = null;
return;
}
}
std.log.err("Could not find window with id {s}.", .{id});
}
pub fn openHud() void {
inventory.init();
for(windowList.items) |window| {
if(window.isHud) {
openWindowFromRef(window);
}
}
reorderWindows = false;
}
fn openWindowCallbackFunction(windowPtr: usize) void {
openWindowFromRef(@ptrFromInt(windowPtr));
}
pub fn openWindowCallback(comptime id: []const u8) Callback {
return .{
.callback = &openWindowCallbackFunction,
.arg = @intFromPtr(&@field(windowlist, id).window),
};
}
pub fn closeWindowFromRef(window: *GuiWindow) void {
GuiCommandQueue.scheduleCommand(.{.action = .close, .window = window});
}
pub fn closeWindow(id: []const u8) void {
for(windowList.items) |window| {
if(std.mem.eql(u8, window.id, id)) {
closeWindowFromRef(window);
return;
}
}
std.log.err("Could not find window with id {s}.", .{id});
}
pub fn setSelectedTextInput(newSelectedTextInput: ?*TextInput) void {
if(selectedTextInput) |current| {
if(current != newSelectedTextInput) {
current.deselect();
}
}
selectedTextInput = newSelectedTextInput;
}
pub const textCallbacks = struct {
pub fn char(codepoint: u21) void {
if(selectedTextInput) |current| {
current.inputCharacter(codepoint);
}
}
pub fn left(mods: main.Window.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.left(mods);
}
}
pub fn right(mods: main.Window.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.right(mods);
}
}
pub fn down(mods: main.Window.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.down(mods);
}
}
pub fn up(mods: main.Window.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.up(mods);
}
}
pub fn gotoStart(mods: main.Window.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.gotoStart(mods);
}
}
pub fn gotoEnd(mods: main.Window.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.gotoEnd(mods);
}
}
pub fn deleteLeft(mods: main.Window.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.deleteLeft(mods);
}
}
pub fn deleteRight(mods: main.Window.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.deleteRight(mods);
}
}
pub fn selectAll(mods: main.Window.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.selectAll(mods);
}
}
pub fn copy(mods: main.Window.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.copy(mods);
}
}
pub fn paste(mods: main.Window.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.paste(mods);
}
}
pub fn cut(mods: main.Window.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.cut(mods);
}
}
pub fn newline(mods: main.Window.Key.Modifiers) void {
if(selectedTextInput) |current| {
current.newline(mods);
}
}
};
pub fn mainButtonPressed() void {
inventory.update();
selectedWindow = null;
selectedTextInput = null;
var selectedI: usize = 0;
for(openWindows.items, 0..) |window, i| {
var mousePosition = main.Window.getMousePosition()/@as(Vec2f, @splat(scale));
mousePosition -= window.pos;
if(@reduce(.And, mousePosition >= Vec2f{0, 0}) and @reduce(.And, mousePosition < window.size)) {
selectedWindow = window;
selectedI = i;
}
}
if(selectedWindow) |_selectedWindow| {
const mousePosition = main.Window.getMousePosition()/@as(Vec2f, @splat(scale));
_selectedWindow.mainButtonPressed(mousePosition);
_ = openWindows.orderedRemove(selectedI);
openWindows.appendAssumeCapacity(_selectedWindow);
} else if(main.game.world != null and inventory.carried.getItem(0) == null) {
toggleGameMenu();
}
}
pub fn mainButtonReleased() void {
inventory.applyChanges(true);
const oldWindow = selectedWindow;
selectedWindow = null;
for(openWindows.items) |window| {
var mousePosition = main.Window.getMousePosition()/@as(Vec2f, @splat(scale));
mousePosition -= window.pos;
if(@reduce(.And, mousePosition >= Vec2f{0, 0}) and @reduce(.And, mousePosition < window.size)) {
selectedWindow = window;
}
}
if(selectedWindow != oldWindow) { // Unselect the window if the mouse left it.
selectedWindow = null;
}
if(oldWindow) |_oldWindow| {
const mousePosition = main.Window.getMousePosition()/@as(Vec2f, @splat(scale));
_oldWindow.mainButtonReleased(mousePosition);
}
}
pub fn secondaryButtonPressed() void {
inventory.update();
}
pub fn secondaryButtonReleased() void {
inventory.applyChanges(false);
}
pub fn updateWindowPositions() void {
var wasChanged: bool = true;
while(wasChanged) {
wasChanged = false;
for(windowList.items) |window| {
const oldPos = window.pos;
window.updateWindowPosition();
const newPos = window.pos;
if(vec.lengthSquare(oldPos - newPos) >= 1e-3) {
wasChanged = true;
}
}
}
}
pub fn updateAndRenderGui() void {
const mousePos = main.Window.getMousePosition()/@as(Vec2f, @splat(scale));
hoveredAWindow = false;
GuiCommandQueue.executeCommands();
if(!main.Window.grabbed) {
if(selectedWindow) |selected| {
selected.updateSelected(mousePos);
}
hoveredItemSlot = null;
var i: usize = openWindows.items.len;
while(i != 0) {
i -= 1;
const window: *GuiWindow = openWindows.items[i];
if(GuiComponent.contains(window.pos, window.size, mousePos)) {
window.updateHovered(mousePos);
hoveredAWindow = true;
break;
}
}
inventory.update();
}
for(openWindows.items) |window| {
window.update();
}
if(!hideGui) {
if(!main.Window.grabbed) {
draw.setColor(0x80000000);
GuiWindow.borderShader.bind();
graphics.c.glUniform2f(GuiWindow.borderUniforms.effectLength, main.Window.getWindowSize()[0]/6, main.Window.getWindowSize()[1]/6);
draw.customShadedRect(GuiWindow.borderUniforms, .{0, 0}, main.Window.getWindowSize());
}
const oldScale = draw.setScale(scale);
defer draw.restoreScale(oldScale);
for(openWindows.items) |window| {
window.render(mousePos);
}
inventory.render(mousePos);
}
const oldScale = draw.setScale(scale);
defer draw.restoreScale(oldScale);
GamepadCursor.render();
}
pub fn toggleGameMenu() void {
main.Window.setMouseGrabbed(!main.Window.grabbed);
if(main.Window.grabbed) { // Take of the currently held item stack and close some windows
main.game.Player.inventory.depositOrDrop(inventory.carried);
hoveredItemSlot = null;
var i: usize = 0;
while(i < openWindows.items.len) {
const window = openWindows.items[i];
if(window.closeIfMouseIsGrabbed) {
_ = openWindows.swapRemove(i);
window.onCloseFn();
} else {
i += 1;
}
}
reorderWindows = false;
}
}
pub const inventory = struct { // MARK: inventory
const ItemStack = main.items.ItemStack;
const Inventory = main.items.Inventory;
pub var carried: Inventory = undefined;
var carriedItemSlot: *ItemSlot = undefined;
var leftClickSlots: List(*ItemSlot) = undefined;
var rightClickSlots: List(*ItemSlot) = undefined;
var initialized: bool = false;
const minCraftingCooldown = 20;
const maxCraftingCooldown = 400;
var nextCraftingAction: i64 = undefined;
var craftingCooldown: u63 = undefined;
var startedCrafting: bool = false;
pub fn init() void {
carried = Inventory.init(main.globalAllocator, 1, .normal, .{.hand = main.game.Player.id});
leftClickSlots = .init(main.globalAllocator);
rightClickSlots = .init(main.globalAllocator);
carriedItemSlot = ItemSlot.init(.{0, 0}, carried, 0, .default, .normal);
carriedItemSlot.renderFrame = false;
initialized = true;
startedCrafting = false;
}
pub fn deinit() void {
initialized = false;
carried.deinit(main.globalAllocator);
carriedItemSlot.deinit();
leftClickSlots.deinit();
rightClickSlots.deinit();
}
pub fn deleteItemSlotReferences(slot: *const ItemSlot) void {
if(slot == hoveredItemSlot) {
hoveredItemSlot = null;
}
var i: usize = 0;
while(i < leftClickSlots.items.len) {
if(leftClickSlots.items[i] == slot) {
_ = leftClickSlots.swapRemove(i);
continue;
}
i += 1;
}
i = 0;
while(i < rightClickSlots.items.len) {
if(rightClickSlots.items[i] == slot) {
_ = rightClickSlots.swapRemove(i);
continue;
}
i += 1;
}
}
fn update() void {
if(!initialized) return;
if(hoveredItemSlot) |itemSlot| {
if(itemSlot.inventory.type == .crafting and itemSlot.mode == .takeOnly) {
if(main.KeyBoard.key("mainGuiButton").pressed) {
const time = std.time.milliTimestamp();
if(!startedCrafting) {
startedCrafting = true;
craftingCooldown = maxCraftingCooldown;
nextCraftingAction = time;
}
while(time -% nextCraftingAction >= 0) {
nextCraftingAction +%= craftingCooldown;
craftingCooldown -= (craftingCooldown - minCraftingCooldown)*craftingCooldown/1000;
itemSlot.inventory.depositOrSwap(itemSlot.itemSlot, carried);
}
return;
}
}
if(itemSlot.mode != .normal) return;
if(carried.getAmount(0) == 0) return;
if(main.KeyBoard.key("mainGuiButton").pressed) {
for(leftClickSlots.items) |deliveredSlot| {
if(itemSlot == deliveredSlot) {
return;
}
}
if(itemSlot.inventory.getItem(itemSlot.itemSlot) == null) {
leftClickSlots.append(itemSlot);
}
} else if(main.KeyBoard.key("secondaryGuiButton").pressed) {
for(rightClickSlots.items) |deliveredSlot| {
if(itemSlot == deliveredSlot) {
return;
}
}
itemSlot.inventory.deposit(itemSlot.itemSlot, carried, 1);
rightClickSlots.append(itemSlot);
}
}
}
fn applyChanges(leftClick: bool) void {
if(!initialized) return;
if(main.game.world == null) return;
if(leftClick) {
if(startedCrafting) {
startedCrafting = false;
return;
}
if(leftClickSlots.items.len != 0) {
const targetInventories = main.stackAllocator.alloc(Inventory, leftClickSlots.items.len);
defer main.stackAllocator.free(targetInventories);
const targetSlots = main.stackAllocator.alloc(u32, leftClickSlots.items.len);
defer main.stackAllocator.free(targetSlots);
for(0..leftClickSlots.items.len) |i| {
targetInventories[i] = leftClickSlots.items[i].inventory;
targetSlots[i] = leftClickSlots.items[i].itemSlot;
}
carried.distribute(targetInventories, targetSlots);
leftClickSlots.clearRetainingCapacity();
} else if(hoveredItemSlot) |hovered| {
hovered.inventory.depositOrSwap(hovered.itemSlot, carried);
} else if(!hoveredAWindow) {
carried.dropStack(0);
}
} else {
if(rightClickSlots.items.len != 0) {
rightClickSlots.clearRetainingCapacity();
} else if(hoveredItemSlot) |hovered| {
hovered.inventory.takeHalf(hovered.itemSlot, carried);
} else if(!hoveredAWindow) {
carried.dropOne(0);
}
}
}
fn render(mousePos: Vec2f) void {
if(!initialized) return;
carriedItemSlot.pos = mousePos - Vec2f{12, 12};
carriedItemSlot.render(.{0, 0});
// Draw tooltip:
if(carried.getAmount(0) == 0) if(hoveredItemSlot) |hovered| {
if(hovered.inventory.getItem(hovered.itemSlot)) |item| {
const tooltip = item.getTooltip();
var textBuffer = graphics.TextBuffer.init(main.stackAllocator, tooltip, .{}, false, .left);
defer textBuffer.deinit();
var size = textBuffer.calculateLineBreaks(16, 300);
size[0] = 0;
for(textBuffer.lineBreaks.items) |lineBreak| {
size[0] = @max(size[0], lineBreak.width);
}
var pos = mousePos;
if(pos[0] + size[0] >= main.Window.getWindowSize()[0]/scale) {
pos[0] -= size[0];
}
if(pos[1] + size[1] >= main.Window.getWindowSize()[1]/scale) {
pos[1] -= size[1];
}
pos = @max(pos, Vec2f{0, 0});
const border1: f32 = 2;
const border2: f32 = 1;
draw.setColor(0xffffff00);
draw.rect(pos - @as(Vec2f, @splat(border1)), size + @as(Vec2f, @splat(2*border1)));
draw.setColor(0xff000000);
draw.rect(pos - @as(Vec2f, @splat(border2)), size + @as(Vec2f, @splat(2*border2)));
textBuffer.render(pos[0], pos[1], 16);
}
};
}
};