diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..49e88a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.DS_Store + +/.zig-cache/ +/zig-out/ diff --git a/build.zig b/build.zig new file mode 100644 index 0000000..8ec2a07 --- /dev/null +++ b/build.zig @@ -0,0 +1,70 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // ---- deps + const dvui_dep = b.dependency("dvui", .{ + .target = target, + .optimize = optimize, + .backend = .sdl, + .sdl3 = true, + }); + + // ---- logic library + const lib_mod = b.createModule(.{ + .root_source_file = b.path("src/hncore/root.zig"), + .target = target, + .optimize = optimize, + }); + + const lib = b.addLibrary(.{ + .linkage = .static, + .name = "hncore", + .root_module = lib_mod, + }); + b.installArtifact(lib); + + // ---- executable + const exe_mod = b.createModule(.{ + .root_source_file = b.path("src/heavens-net/main.zig"), + .target = target, + .optimize = optimize, + }); + exe_mod.addImport("hncore", lib_mod); + exe_mod.addImport("dvui", dvui_dep.module("dvui_sdl")); + + const exe = b.addExecutable(.{ + .name = "heavens-net", + .root_module = exe_mod, + }); + b.installArtifact(exe); + + // ---- running + const run_cmd = b.addRunArtifact(exe); + run_cmd.step.dependOn(b.getInstallStep()); + if (b.args) |args| { + run_cmd.addArgs(args); + } + const run_step = b.step("run", "Run the app"); + run_step.dependOn(&run_cmd.step); + + // ---- testing + const lib_unit_tests = b.addTest(.{ + .root_module = lib_mod, + }); + const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); + + const test_step = b.step("test", "Run unit tests"); + test_step.dependOn(&run_lib_unit_tests.step); + + // ---- document generation + const install_docs = b.addInstallDirectory(.{ + .source_dir = lib.getEmittedDocs(), + .install_dir = .prefix, + .install_subdir = "docs", + }); + const docs_step = b.step("docs", "Install docs into zig-out/docs"); + docs_step.dependOn(&install_docs.step); +} diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 0000000..811bb1d --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,19 @@ +.{ + .name = .heavens_net, + .version = "0.0.1", + .fingerprint = 0x5cbe403baf740fcb, + .minimum_zig_version = "0.15.0-dev.149+2b57f6b71", + + .dependencies = .{ + .dvui = .{ + .url = "https://github.com/david-vanderson/dvui/archive/9f446c8600b3385418c8926481076c335261f222.zip", + .hash = "dvui-0.2.0-AQFJmesqywBKCM6r9orR324EGhZvGsjhJw0ZHWcvwzyh", + }, + }, + + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/src/heavens-net/App.zig b/src/heavens-net/App.zig new file mode 100644 index 0000000..e256cf8 --- /dev/null +++ b/src/heavens-net/App.zig @@ -0,0 +1,50 @@ +const std = @import("std"); +const dvui = @import("dvui"); +const hncore = @import("hncore"); + +const win = @import("./win/root.zig"); + +const Self = @This(); + +alloc: std.mem.Allocator, +today: win.today.Mock, + +pub fn init(alloc: std.mem.Allocator) !Self { + return Self { + .alloc = alloc, + .today = try win.today.Mock.init(alloc), + }; +} +pub fn deinit(self: *Self) void { + self.today.deinit(); +} + +pub fn gui(self: *Self) !void { + // ---- menu + { + var root = try dvui.menu(@src(), .horizontal, .{ .background = true, .expand = .horizontal }); + defer root.deinit(); + + if (try dvui.menuItemLabel(@src(), "File", .{ .submenu = true }, .{})) |r| { + var float = try dvui.floatingMenu( + @src(), .{.from = dvui.Rect.fromPoint(dvui.Point{ .x = r.x, .y = r.y + r.h })}, .{}); + defer float.deinit(); + + if (try dvui.menuItemLabel(@src(), "Open", .{}, .{})) |_| { + } + } + } + + // ---- project tabs + { + var tbox = try dvui.box(@src(), .vertical, .{ + .expand = .both, + .background = true, + .color_fill = .{ .name = .fill_window }, + }); + defer tbox.deinit(); + } + + // ---- windows + try win.today.gui(&self.today); +} diff --git a/src/heavens-net/main.zig b/src/heavens-net/main.zig new file mode 100644 index 0000000..9bce9d1 --- /dev/null +++ b/src/heavens-net/main.zig @@ -0,0 +1,49 @@ +const std = @import("std"); +const dvui = @import("dvui"); +const hncore = @import("hncore"); + +const App = @import("./App.zig"); + +pub fn main() !void { + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer if (gpa.deinit() == .leak) { + @panic("memory leak detected"); + }; + + var backend = try dvui.backend.initWindow(.{ + .allocator = gpa.allocator(), + .size = .{ .w = 800, .h = 600, }, + .min_size = .{ .w = 250, .h = 350, }, + .vsync = true, + .title = "Heaven's Net", + }); + defer backend.deinit(); + + var win = try dvui.Window.init(@src(), gpa.allocator(), backend.backend(), .{}); + defer win.deinit(); + + var app = try App.init(gpa.allocator()); + defer app.deinit(); + while (true) { + try win.begin( + win.beginWait(backend.hasEvent()), + ); + + const quit = try backend.addAllEvents(&win); + if (quit) { + break; + } + + try app.gui(); + + const end_micros = try win.end(.{}); + + backend.setCursor(win.cursorRequested()); + backend.textInputRect(win.textInputRequested()); + backend.renderPresent(); + + backend.waitEventTimeout( + win.waitTime(end_micros, null), + ); + } +} diff --git a/src/heavens-net/win/root.zig b/src/heavens-net/win/root.zig new file mode 100644 index 0000000..e53fb68 --- /dev/null +++ b/src/heavens-net/win/root.zig @@ -0,0 +1 @@ +pub const today = @import("./today.zig"); diff --git a/src/heavens-net/win/today.zig b/src/heavens-net/win/today.zig new file mode 100644 index 0000000..5e87406 --- /dev/null +++ b/src/heavens-net/win/today.zig @@ -0,0 +1,88 @@ +const std = @import("std"); +const dvui = @import("dvui"); + +pub const Task = struct { + name: [:0]const u8, + mark: bool, +}; + +pub fn gui(ctx: anytype) !void { + var win = try dvui.floatingWindow(@src(), .{}, .{}); + defer win.deinit(); + + try dvui.windowHeader("Today", "", null); + + var scrollArea = try dvui.scrollArea(@src(), .{}, .{ + .expand = .both, + }); + defer scrollArea.deinit(); + + var reorder = try dvui.reorder(@src(), .{ + .background = true, + .expand = .both, + }); + defer reorder.deinit(); + + var list = try dvui.box(@src(), .vertical, .{ .expand = .both, }); + defer list.deinit(); + + const tasks = ctx.get_tasks(); + for (0.., tasks) |idx, task| { + var reorderable = try reorder.reorderable(@src(), .{}, .{ + .id_extra = idx, + .expand = .horizontal, + }); + defer reorderable.deinit(); + + var hbox = try dvui.box(@src(), .horizontal, .{ + .expand = .both, + .background = true, + .border = dvui.Rect.all(1), + }); + defer hbox.deinit(); + + var mark: bool = task.mark; + if (try dvui.checkbox(@src(), &mark, null, .{})) { + ctx.mark(idx, mark); + } + + try dvui.label(@src(), "{s}", .{ task.name, }, .{}); + + _ = try dvui.ReorderWidget.draggable(@src(), .{ .reorderable = reorderable, }, .{ + .expand = .vertical, + .gravity_x = 1.0, + .gravity_y = 0.5, + }); + } + + try dvui.labelNoFmt(@src(), "no tasks anymore :)", .{ .gravity_x = 0.5 }); +} + +pub const Mock = struct { + const Self = @This(); + const TaskList = std.ArrayList(Task); + + tasks: TaskList, + + pub fn init(alloc: std.mem.Allocator) !Self { + var tasks = TaskList.init(alloc); + errdefer tasks.deinit(); + + try tasks.append(Task { .name = "helloworld", .mark = true, }); + try tasks.append(Task { .name = "goodbye", .mark = false, }); + + return Mock { + .tasks = tasks, + }; + } + pub fn deinit(self: *Self) void { + self.tasks.deinit(); + } + + fn mark(self: *Self, idx: usize, check: bool) void { + self.tasks.items[idx].mark = check; + } + fn get_tasks(self: *const Self) []const Task { + return self.tasks.items; + } +}; diff --git a/src/hncore/root.zig b/src/hncore/root.zig new file mode 100644 index 0000000..8eab4c3 --- /dev/null +++ b/src/hncore/root.zig @@ -0,0 +1,4 @@ +test { + @import("std").testing.refAllDecls(@This()); +} +