diff --git a/src/gui/windows/_windowlist.zig b/src/gui/windows/_windowlist.zig index 91ff800c..804840dc 100644 --- a/src/gui/windows/_windowlist.zig +++ b/src/gui/windows/_windowlist.zig @@ -13,6 +13,7 @@ pub const inventory_crafting = @import("inventory_crafting.zig"); pub const main = @import("main.zig"); pub const multiplayer = @import("multiplayer.zig"); pub const performance_graph = @import("performance_graph.zig"); +pub const save_selection = @import("save_selection.zig"); pub const settings = @import("settings.zig"); pub const sound = @import("sound.zig"); pub const workbench = @import("workbench.zig"); \ No newline at end of file diff --git a/src/gui/windows/main.zig b/src/gui/windows/main.zig index b60a1176..6e877b06 100644 --- a/src/gui/windows/main.zig +++ b/src/gui/windows/main.zig @@ -19,7 +19,7 @@ const padding: f32 = 8; pub fn onOpen() Allocator.Error!void { var list = try VerticalList.init(.{padding, 16 + padding}, 300, 16); - try list.add(try Button.initText(.{0, 0}, 128, "Singleplayer TODO", .{})); + try list.add(try Button.initText(.{0, 0}, 128, "Singleplayer", gui.openWindowCallback("save_selection"))); try list.add(try Button.initText(.{0, 0}, 128, "Multiplayer", gui.openWindowCallback("multiplayer"))); try list.add(try Button.initText(.{0, 0}, 128, "Settings", gui.openWindowCallback("settings"))); try list.add(try Button.initText(.{0, 0}, 128, "Exit TODO", .{})); diff --git a/src/gui/windows/save_selection.zig b/src/gui/windows/save_selection.zig new file mode 100644 index 00000000..c2211f2b --- /dev/null +++ b/src/gui/windows/save_selection.zig @@ -0,0 +1,130 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + +const main = @import("root"); +const ConnectionManager = main.network.ConnectionManager; +const settings = main.settings; +const Vec2f = main.vec.Vec2f; + +const gui = @import("../gui.zig"); +const GuiComponent = gui.GuiComponent; +const GuiWindow = gui.GuiWindow; +const Button = @import("../components/Button.zig"); +const HorizontalList = @import("../components/HorizontalList.zig"); +const Label = @import("../components/Label.zig"); +const TextInput = @import("../components/TextInput.zig"); +const VerticalList = @import("../components/VerticalList.zig"); + +pub var window = GuiWindow { + .contentSize = Vec2f{128, 256}, + .id = "save_selection", + .title = "Save Selection", +}; + +const padding: f32 = 8; +const width: f32 = 128; +var buttonNameArena: std.heap.ArenaAllocator = undefined; + +fn openWorld(namePtr: usize) void { + const nullTerminatedName = @intToPtr([*:0]const u8, namePtr); + const name = std.mem.span(nullTerminatedName); + std.log.info("TODO: Open world {s}", .{name}); +// new Thread(() -> Server.main(new String[] {name}), "Server Thread").start(); +// Cubyz.gameUI.setMenu(null, false); // hide from UISystem.back() +// while(Server.world == null) { +// try { +// Thread.sleep(10); +// } catch(InterruptedException e) {} +// } +// try { +// GameLauncher.logic.loadWorld(new ClientWorld("127.0.0.1", new UDPConnectionManager(Constants.DEFAULT_PORT+1, false))); // TODO: Don't go over the local network in singleplayer. +// } catch(InterruptedException e) {} +} + +fn flawedDeleteWorld(name: []const u8) !void { + var saveDir = try std.fs.cwd().openDir("saves", .{}); + defer saveDir.close(); + try saveDir.deleteTree(name); + + onClose(); + try onOpen(); +} + +fn deleteWorld(namePtr: usize) void { + const nullTerminatedName = @intToPtr([*:0]const u8, namePtr); + const name = std.mem.span(nullTerminatedName); + flawedDeleteWorld(name) catch |err| { + std.log.err("Encountered error while deleting world \"{s}\": {s}", .{name, @errorName(err)}); + }; +} + +fn parseEscapedFolderName(name: []const u8) ![]const u8 { + var result = std.ArrayList(u8).init(main.threadAllocator); + defer result.deinit(); + var i: u32 = 0; + while(i < name.len) : (i += 1) { + if(name[i] == '_') { + var val: u21 = 0; + for(0..4) |_| { + i += 1; + if(i < name.len) { + val = val*16 + switch(name[i]) { + '0'...'9' => name[i] - '0', + 'a'...'f' => name[i] - 'a' + 10, + 'A'...'F' => name[i] - 'A' + 10, + else => 0, + }; + } + } + var buf: [4]u8 = undefined; + try result.appendSlice(buf[0..std.unicode.utf8Encode(val, &buf) catch 0]); // TODO: Change this to full unicode rather than using this broken utf-16 converter. + } else { + try result.append(name[i]); + } + } + return try result.toOwnedSlice(); +} + +pub fn onOpen() Allocator.Error!void { + buttonNameArena = std.heap.ArenaAllocator.init(main.globalAllocator); + var list = try VerticalList.init(.{padding, 16 + padding}, 300, 8); + // TODO: try list.add(try Button.initText(.{0, 0}, 128, "Create World", gui.openWindowCallback("save_creation"))); + + var dir: std.fs.IterableDir = std.fs.cwd().makeOpenPathIterable("saves", .{}) catch |err| { + std.log.err("Encountered error while trying to open folder \"saves\": {s}", .{@errorName(err)}); + return; + }; + defer dir.close(); + + var iterator = dir.iterate(); + while(iterator.next() catch |err| { + std.log.err("Encountered error while iterating over folder \"saves\": {s}", .{@errorName(err)}); + return; + }) |entry| { + if(entry.kind == .Directory) { + var row = try HorizontalList.init(); + + const decodedName = try parseEscapedFolderName(entry.name); + defer main.threadAllocator.free(decodedName); + const name = try buttonNameArena.allocator().dupeZ(u8, entry.name); // Null terminate, so we can later recover the string from jsut the pointer. + const buttonName = try std.fmt.allocPrint(buttonNameArena.allocator(), "Play {s}", .{decodedName}); + + try row.add(try Button.initText(.{0, 0}, 128, buttonName, .{.callback = &openWorld, .arg = @ptrToInt(name.ptr)})); + try row.add(try Button.initText(.{8, 0}, 64, "delete", .{.callback = &deleteWorld, .arg = @ptrToInt(name.ptr)})); + row.finish(.{0, 0}, .center); + try list.add(row); + } + } + + list.finish(.center); + window.rootComponent = list.toComponent(); + window.contentSize = window.rootComponent.?.pos() + window.rootComponent.?.size() + @splat(2, @as(f32, padding)); + gui.updateWindowPositions(); +} + +pub fn onClose() void { + buttonNameArena.deinit(); + if(window.rootComponent) |*comp| { + comp.deinit(); + } +} \ No newline at end of file