Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
89f41c9d78 | |||
8d53a185a4 | |||
ca3d26a18d | |||
a339d2a392 | |||
fc303ba553 | |||
ee8d0cde34 | |||
845582a14d | |||
907d71a19a | |||
b9153e2708 | |||
a1ede8d1a0 | |||
0bfa0f1098 |
@ -12,7 +12,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
.sdl3 = true,
|
.sdl3 = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---- logic library
|
// ---- library
|
||||||
const lib_mod = b.createModule(.{
|
const lib_mod = b.createModule(.{
|
||||||
.root_source_file = b.path("src/hncore/root.zig"),
|
.root_source_file = b.path("src/hncore/root.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
@ -26,9 +26,10 @@ pub fn build(b: *std.Build) void {
|
|||||||
});
|
});
|
||||||
b.installArtifact(lib);
|
b.installArtifact(lib);
|
||||||
|
|
||||||
|
|
||||||
// ---- executable
|
// ---- executable
|
||||||
const exe_mod = b.createModule(.{
|
const exe_mod = b.createModule(.{
|
||||||
.root_source_file = b.path("src/heavens-net/main.zig"),
|
.root_source_file = b.path("src/hnet/main.zig"),
|
||||||
.target = target,
|
.target = target,
|
||||||
.optimize = optimize,
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
@ -36,7 +37,7 @@ pub fn build(b: *std.Build) void {
|
|||||||
exe_mod.addImport("dvui", dvui_dep.module("dvui_sdl"));
|
exe_mod.addImport("dvui", dvui_dep.module("dvui_sdl"));
|
||||||
|
|
||||||
const exe = b.addExecutable(.{
|
const exe = b.addExecutable(.{
|
||||||
.name = "heavens-net",
|
.name = "hnet",
|
||||||
.root_module = exe_mod,
|
.root_module = exe_mod,
|
||||||
});
|
});
|
||||||
b.installArtifact(exe);
|
b.installArtifact(exe);
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const dvui = @import("dvui");
|
|
||||||
const hncore = @import("hncore");
|
|
||||||
|
|
||||||
const compo = @import("./compo/root.zig");
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
stage: compo.stage.Mock,
|
|
||||||
search: compo.search.Mock,
|
|
||||||
taskedit: compo.taskedit.Mock,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator) !Self {
|
|
||||||
return Self {
|
|
||||||
.search = try compo.search.Mock.init(alloc),
|
|
||||||
.stage = try compo.stage.Mock.init(alloc),
|
|
||||||
.taskedit = try compo.taskedit.Mock.init(alloc),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
self.taskedit.deinit();
|
|
||||||
self.stage.deinit();
|
|
||||||
self.search.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn gui(self: *Self) !void {
|
|
||||||
try compo.menu.gui(.{});
|
|
||||||
|
|
||||||
// background
|
|
||||||
{
|
|
||||||
var box = try dvui.box(@src(), .vertical, .{
|
|
||||||
.expand = .both,
|
|
||||||
.background = true,
|
|
||||||
.color_fill = .{ .color = dvui.Color.white, },
|
|
||||||
});
|
|
||||||
defer box.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---- windows
|
|
||||||
try compo.search.gui(&self.search);
|
|
||||||
try compo.stage.gui(&self.stage);
|
|
||||||
try compo.taskedit.gui(&self.taskedit);
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const dvui = @import("dvui");
|
|
||||||
|
|
||||||
pub fn gui(ctx: anytype) !void {
|
|
||||||
_ = ctx;
|
|
||||||
|
|
||||||
var root = try dvui.menu(@src(), .horizontal, .{ .background = true, .expand = .horizontal });
|
|
||||||
defer root.deinit();
|
|
||||||
|
|
||||||
if (try dvui.menuItemLabel(@src(), "Workspace", .{ .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(), "New", .{}, .{})) |_| {
|
|
||||||
}
|
|
||||||
if (try dvui.menuItemLabel(@src(), "Open", .{}, .{})) |_| {
|
|
||||||
}
|
|
||||||
|
|
||||||
try dvui.separator(@src(), .{});
|
|
||||||
|
|
||||||
if (try dvui.menuItemLabel(@src(), "Save", .{}, .{})) |_| {
|
|
||||||
}
|
|
||||||
if (try dvui.menuItemLabel(@src(), "Save as", .{}, .{})) |_| {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (try dvui.menuItemLabel(@src(), "Task", .{ .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(), "New", .{}, .{})) |_| {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
pub const menu = @import("./menu.zig");
|
|
||||||
pub const search = @import("./search.zig");
|
|
||||||
pub const stage = @import("./stage.zig");
|
|
||||||
pub const taskcard = @import("./taskcard.zig");
|
|
||||||
pub const taskedit = @import("./taskedit.zig");
|
|
@ -1,119 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const dvui = @import("dvui");
|
|
||||||
|
|
||||||
const ui = @import("../ui/root.zig");
|
|
||||||
const compo = @import("./root.zig");
|
|
||||||
|
|
||||||
pub const Task = struct {
|
|
||||||
id: usize,
|
|
||||||
summary: [:0]const u8,
|
|
||||||
|
|
||||||
done : bool = false,
|
|
||||||
staged : bool = false,
|
|
||||||
archived: bool = false,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn gui(ctx: anytype) !void {
|
|
||||||
var win = try dvui.floatingWindow(@src(), .{}, .{});
|
|
||||||
defer win.deinit();
|
|
||||||
|
|
||||||
try dvui.windowHeader("Search", "", null);
|
|
||||||
|
|
||||||
// task adder
|
|
||||||
{
|
|
||||||
var adder = try dvui.textEntry(@src(), .{}, .{
|
|
||||||
.expand = .horizontal,
|
|
||||||
});
|
|
||||||
defer adder.deinit();
|
|
||||||
|
|
||||||
if (dvui.focusedWidgetId() == adder.data().id) {
|
|
||||||
if (ui.event.keyPress("activate")) {
|
|
||||||
std.debug.print("hello\n", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// task list
|
|
||||||
{
|
|
||||||
var scrollArea = try dvui.scrollArea(@src(), .{}, .{
|
|
||||||
.expand = .both,
|
|
||||||
});
|
|
||||||
defer scrollArea.deinit();
|
|
||||||
|
|
||||||
var list = try dvui.box(@src(), .vertical, .{
|
|
||||||
.expand = .both,
|
|
||||||
.padding = dvui.Rect.all(4),
|
|
||||||
});
|
|
||||||
defer list.deinit();
|
|
||||||
|
|
||||||
for (0.., ctx.tasks()) |idx, task| {
|
|
||||||
if (idx > 0) {
|
|
||||||
_ = try dvui.spacer(@src(), .{ .w = 0, .h = 4}, .{
|
|
||||||
.id_extra = idx,
|
|
||||||
.expand = .horizontal,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var subctx = TaskCardCtx(@TypeOf(ctx)) { .ctx = ctx, .task = task };
|
|
||||||
try compo.taskcard.gui(@src(), &subctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
try dvui.labelNoFmt(@src(), "no tasks anymore :)", .{ .gravity_x = 0.5 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn TaskCardCtx(T: type) type {
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
ctx : T,
|
|
||||||
task: Task,
|
|
||||||
|
|
||||||
pub fn open(self: *Self) void {
|
|
||||||
_ = self;
|
|
||||||
std.debug.print("OPEN\n", .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(self: *const Self) usize {
|
|
||||||
return self.task.id;
|
|
||||||
}
|
|
||||||
pub fn summary(self: *const Self) []const u8 {
|
|
||||||
return self.task.summary;
|
|
||||||
}
|
|
||||||
pub fn done(self: *const Self) bool {
|
|
||||||
return self.task.done;
|
|
||||||
}
|
|
||||||
pub fn archived(self: *const Self) bool {
|
|
||||||
return self.task.archived;
|
|
||||||
}
|
|
||||||
pub fn staged(self: *const Self) bool {
|
|
||||||
return self.task.staged;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Mock = struct {
|
|
||||||
const Self = @This();
|
|
||||||
const TaskList = std.ArrayList(Task);
|
|
||||||
|
|
||||||
_tasks: TaskList,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator) !Self {
|
|
||||||
var ts = TaskList.init(alloc);
|
|
||||||
errdefer ts.deinit();
|
|
||||||
|
|
||||||
try ts.append(Task { .id = 0, .summary = "helloworld", });
|
|
||||||
try ts.append(Task { .id = 1, .summary = "goodbye", });
|
|
||||||
|
|
||||||
return Mock {
|
|
||||||
._tasks = ts,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
self._tasks.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tasks(self: *const Self) []const Task {
|
|
||||||
return self._tasks.items;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,124 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const dvui = @import("dvui");
|
|
||||||
|
|
||||||
const compo = @import("./root.zig");
|
|
||||||
|
|
||||||
pub const Task = struct {
|
|
||||||
id: usize,
|
|
||||||
summary: [:0]const u8,
|
|
||||||
done: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn gui(ctx: anytype) !void {
|
|
||||||
var win = try dvui.floatingWindow(@src(), .{}, .{});
|
|
||||||
defer win.deinit();
|
|
||||||
|
|
||||||
try dvui.windowHeader("Stage", "", 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,
|
|
||||||
.padding = dvui.Rect.all(4),
|
|
||||||
});
|
|
||||||
defer list.deinit();
|
|
||||||
|
|
||||||
const tasks = ctx.get_tasks();
|
|
||||||
for (0.., tasks) |idx, task| {
|
|
||||||
if (idx > 0) {
|
|
||||||
_ = try dvui.spacer(@src(), .{ .w = 0, .h = 4}, .{
|
|
||||||
.id_extra = idx,
|
|
||||||
.expand = .horizontal,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var reorderable = try reorder.reorderable(@src(), .{
|
|
||||||
.draw_target = false,
|
|
||||||
}, .{
|
|
||||||
.id_extra = idx,
|
|
||||||
.expand = .horizontal,
|
|
||||||
});
|
|
||||||
defer reorderable.deinit();
|
|
||||||
|
|
||||||
var subctx = TaskCardCtx(@TypeOf(ctx)) {
|
|
||||||
.ctx = ctx,
|
|
||||||
.task = task,
|
|
||||||
.reorderable = reorderable,
|
|
||||||
};
|
|
||||||
try compo.taskcard.gui(@src(), &subctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn TaskCardCtx(T: type) type {
|
|
||||||
return struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
ctx : T,
|
|
||||||
task: Task,
|
|
||||||
reorderable: *dvui.Reorderable,
|
|
||||||
|
|
||||||
pub fn open(self: *Self) void {
|
|
||||||
_ = self;
|
|
||||||
std.debug.print("OPEN\n", .{});
|
|
||||||
}
|
|
||||||
pub fn startDragging(self: *Self, p: dvui.Point) void {
|
|
||||||
self.reorderable.reorder.dragStart(self.reorderable.data().id, p);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn id(self: *const Self) usize {
|
|
||||||
return self.task.id;
|
|
||||||
}
|
|
||||||
pub fn summary(self: *const Self) []const u8 {
|
|
||||||
return self.task.summary;
|
|
||||||
}
|
|
||||||
pub fn done(self: *const Self) bool {
|
|
||||||
return self.task.done;
|
|
||||||
}
|
|
||||||
pub fn staged(_: *const Self) bool {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
pub fn archived(_: *const Self) bool {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
for (0..100) |id| {
|
|
||||||
try tasks.append(Task { .id = id*2, .summary = "helloworld", .done = true, });
|
|
||||||
try tasks.append(Task { .id = id*2+1, .summary = "goodbye", .done = false, });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Mock {
|
|
||||||
.tasks = tasks,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
self.tasks.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mark(self: *Self, idx: usize, check: bool) void {
|
|
||||||
self.tasks.items[idx].done = check;
|
|
||||||
}
|
|
||||||
pub fn get_tasks(self: *const Self) []const Task {
|
|
||||||
return self.tasks.items;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,185 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const dvui = @import("dvui");
|
|
||||||
|
|
||||||
const ui = @import("../ui/root.zig");
|
|
||||||
|
|
||||||
pub fn gui(src: std.builtin.SourceLocation, ctx: anytype) !void {
|
|
||||||
var card = try dvui.box(src, .horizontal, .{
|
|
||||||
.id_extra = ctx.id(),
|
|
||||||
.expand = .horizontal,
|
|
||||||
.background = true,
|
|
||||||
.border = dvui.Rect.all(1),
|
|
||||||
});
|
|
||||||
defer card.deinit();
|
|
||||||
|
|
||||||
{
|
|
||||||
var check = ctx.done();
|
|
||||||
const changed = try dvui.checkbox(@src(), &check, null, .{
|
|
||||||
.gravity_y = 0.5,
|
|
||||||
.gravity_x = 0,
|
|
||||||
});
|
|
||||||
if (changed) {
|
|
||||||
std.debug.print("CHANGED\n", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const hover = true;
|
|
||||||
if (hover) {
|
|
||||||
var icons = try dvui.box(@src(), .horizontal, .{
|
|
||||||
.expand = .vertical,
|
|
||||||
.gravity_x = 1,
|
|
||||||
});
|
|
||||||
defer icons.deinit();
|
|
||||||
|
|
||||||
if (ctx.staged()) {
|
|
||||||
if (try buttonIcon(@src(), "unstage", dvui.entypo.light_down)) {
|
|
||||||
std.debug.print("UNSTAGE\n", .{});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (try buttonIcon(@src(), "stage", dvui.entypo.light_up)) {
|
|
||||||
std.debug.print("STAGE\n", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ctx.archived()) {
|
|
||||||
if (try buttonIcon(@src(), "unarchive", dvui.entypo.back_in_time)) {
|
|
||||||
std.debug.print("UNARCHIVE\n", .{});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (try buttonIcon(@src(), "archive", dvui.entypo.archive)) {
|
|
||||||
std.debug.print("ARCHIVE\n", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rc = try title(ctx, card.data().rectScale().r.topLeft());
|
|
||||||
|
|
||||||
// dragging link
|
|
||||||
if (!@hasDecl(@TypeOf(ctx.*), "startDragging")) {
|
|
||||||
// WIP
|
|
||||||
// dvui.pathStroke([_]dvui.Point{rc.topLeft(), cw.});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn buttonIcon(src: std.builtin.SourceLocation, name: []const u8, icon: []const u8) !bool {
|
|
||||||
return try dvui.buttonIcon(src, name, icon, .{}, .{
|
|
||||||
.margin = dvui.Rect.all(0),
|
|
||||||
.padding = dvui.Rect.all(4),
|
|
||||||
.gravity_y = 0.5,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn title(ctx: anytype, topLeft: dvui.Point) !dvui.Rect {
|
|
||||||
// this codes are based on dvui.labelClick
|
|
||||||
|
|
||||||
var lw = dvui.LabelWidget.init(@src(), "#{d} {s}", .{ctx.id(), ctx.summary()}, .{
|
|
||||||
.expand = .horizontal,
|
|
||||||
.gravity_x = 0,
|
|
||||||
.name = "LabelClick"
|
|
||||||
});
|
|
||||||
// now lw has a Rect from its parent but hasn't processed events or drawn
|
|
||||||
|
|
||||||
const lwid = lw.data().id;
|
|
||||||
|
|
||||||
// if lw is visible, we want to be able to keyboard navigate to it
|
|
||||||
if (lw.data().visible()) {
|
|
||||||
try dvui.tabIndexSet(lwid, lw.data().options.tab_index);
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw border and background
|
|
||||||
try lw.install();
|
|
||||||
|
|
||||||
// loop over all events this frame in order of arrival
|
|
||||||
for (dvui.events()) |*e| {
|
|
||||||
|
|
||||||
// skip if lw would not normally process this event
|
|
||||||
if (!lw.matchEvent(e))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
switch (e.evt) {
|
|
||||||
.mouse => |me| {
|
|
||||||
if (me.action == .focus) {
|
|
||||||
e.handled = true;
|
|
||||||
|
|
||||||
// focus this widget for events after this one (starting with e.num)
|
|
||||||
dvui.focusWidget(lwid, null, e.num);
|
|
||||||
} else if (me.action == .press and me.button.pointer()) {
|
|
||||||
e.handled = true;
|
|
||||||
dvui.captureMouse(lwid);
|
|
||||||
|
|
||||||
// for touch events, we want to cancel our click if a drag is started
|
|
||||||
dvui.dragPreStart(me.p, .{ .name = "TASK", .offset = topLeft.diff(me.p), });
|
|
||||||
} else if (me.action == .release and me.button.pointer()) {
|
|
||||||
// mouse button was released, do we still have mouse capture?
|
|
||||||
if (dvui.captured(lwid)) {
|
|
||||||
e.handled = true;
|
|
||||||
|
|
||||||
// cancel our capture
|
|
||||||
dvui.captureMouse(null);
|
|
||||||
dvui.dragEnd();
|
|
||||||
|
|
||||||
// if the release was within our border, the click is successful
|
|
||||||
if (lw.data().borderRectScale().r.contains(me.p)) {
|
|
||||||
ctx.open();
|
|
||||||
|
|
||||||
// if the user interacts successfully with a
|
|
||||||
// widget, it usually means part of the GUI is
|
|
||||||
// changing, so the convention is to call refresh
|
|
||||||
// so the user doesn't have to remember
|
|
||||||
dvui.refresh(null, @src(), lwid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (me.action == .motion) {
|
|
||||||
if (me.button.touch()) {
|
|
||||||
if (dvui.captured(lwid)) {
|
|
||||||
if (dvui.dragging(me.p)) |_| {
|
|
||||||
// touch: if we overcame the drag threshold, then
|
|
||||||
// that means the person probably didn't want to
|
|
||||||
// touch this button, they were trying to scroll
|
|
||||||
dvui.captureMouse(null);
|
|
||||||
dvui.dragEnd();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (@hasDecl(@TypeOf(ctx.*), "startDragging")) {
|
|
||||||
// call ctx.startDragging if the method exists
|
|
||||||
if (dvui.captured(lwid)) {
|
|
||||||
e.handled = true;
|
|
||||||
if (dvui.dragging(me.p)) |_| {
|
|
||||||
ctx.startDragging(me.p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (me.action == .position) {
|
|
||||||
// a single .position mouse event is at the end of each
|
|
||||||
// frame, so this means the mouse ended above us
|
|
||||||
dvui.cursorSet(.hand);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.key => |ke| {
|
|
||||||
if (ke.action == .down and ke.matchBind("activate")) {
|
|
||||||
e.handled = true;
|
|
||||||
ctx.open();
|
|
||||||
dvui.refresh(null, @src(), lwid);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we didn't handle this event, send it to lw - this means we don't
|
|
||||||
// need to call lw.processEvents()
|
|
||||||
if (!e.handled) {
|
|
||||||
lw.processEvent(e, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw text
|
|
||||||
try lw.draw();
|
|
||||||
|
|
||||||
// draw an accent border if we are focused
|
|
||||||
if (lwid == dvui.focusedWidgetId()) {
|
|
||||||
try lw.data().focusBorder();
|
|
||||||
}
|
|
||||||
|
|
||||||
// done with lw, have it report min size to parent
|
|
||||||
defer lw.deinit();
|
|
||||||
return lw.data().scaleRect().r;
|
|
||||||
}
|
|
@ -1,158 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const dvui = @import("dvui");
|
|
||||||
|
|
||||||
const ui = @import("../ui/root.zig");
|
|
||||||
|
|
||||||
pub const Tab = enum {
|
|
||||||
info,
|
|
||||||
detail,
|
|
||||||
network,
|
|
||||||
gantt,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn gui(ctx: anytype) !void {
|
|
||||||
var win = try dvui.floatingWindow(@src(), .{}, .{});
|
|
||||||
defer win.deinit();
|
|
||||||
|
|
||||||
// window header
|
|
||||||
{
|
|
||||||
var buf: [128]u8 = undefined;
|
|
||||||
const subtitle = std.fmt.bufPrint(&buf, "#{d}", .{ctx.taskId()}) catch unreachable;
|
|
||||||
try dvui.windowHeader("Task", subtitle, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
// task summary
|
|
||||||
{
|
|
||||||
var hbox = try dvui.box(@src(), .horizontal, .{ .expand = .horizontal, });
|
|
||||||
defer hbox.deinit();
|
|
||||||
|
|
||||||
var mark: bool = false;
|
|
||||||
if (try dvui.checkbox(@src(), &mark, null, .{ .gravity_y = 0.5, })) {
|
|
||||||
}
|
|
||||||
|
|
||||||
var summary = try dvui.textEntry(@src(), .{}, .{
|
|
||||||
.expand = .horizontal,
|
|
||||||
});
|
|
||||||
defer summary.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// tabs
|
|
||||||
{
|
|
||||||
const shown = ctx.shownTab();
|
|
||||||
|
|
||||||
var tbox = try dvui.box(@src(), .vertical, .{ .expand = .both, });
|
|
||||||
defer tbox.deinit();
|
|
||||||
|
|
||||||
{
|
|
||||||
var tabs = dvui.TabsWidget.init(@src(), .{ .dir = .horizontal }, .{
|
|
||||||
.expand = .horizontal,
|
|
||||||
});
|
|
||||||
try tabs.install();
|
|
||||||
defer tabs.deinit();
|
|
||||||
|
|
||||||
inline for (std.meta.fields(Tab)) |tabMeta| {
|
|
||||||
const tab = @field(Tab, tabMeta.name);
|
|
||||||
if (try tabs.addTabLabel(shown == tab, tabMeta.name)) {
|
|
||||||
ctx.switchTab(tab);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var vbox = try dvui.box(@src(), .vertical, .{ .expand = .both, });
|
|
||||||
defer vbox.deinit();
|
|
||||||
|
|
||||||
switch (shown) {
|
|
||||||
.info => try tabInfo(ctx),
|
|
||||||
.detail => try tabDetail(ctx),
|
|
||||||
else => try dvui.labelNoFmt(@src(), "NOT IMPLEMENTED YET :(", .{
|
|
||||||
.gravity_x = 0.5,
|
|
||||||
.gravity_y = 0.5,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tabInfo(ctx: anytype) !void {
|
|
||||||
_ = ctx;
|
|
||||||
|
|
||||||
var vbox = try dvui.box(@src(), .vertical, .{ .expand = .both, });
|
|
||||||
defer vbox.deinit();
|
|
||||||
|
|
||||||
const fields = [_][:0]const u8 {"foo", "baz", "bar"};
|
|
||||||
for (0.., fields) |idx, name| {
|
|
||||||
var hbox = try dvui.box(@src(), .horizontal, .{
|
|
||||||
.id_extra = idx,
|
|
||||||
.expand = .horizontal,
|
|
||||||
});
|
|
||||||
defer hbox.deinit();
|
|
||||||
|
|
||||||
{
|
|
||||||
var box = try dvui.box(@src(), .horizontal, .{
|
|
||||||
.min_size_content = .{ .w = 64, .h = 0, },
|
|
||||||
});
|
|
||||||
defer box.deinit();
|
|
||||||
try dvui.label(@src(), "{s}", .{name}, .{
|
|
||||||
.gravity_x = 1,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{
|
|
||||||
var box = try dvui.box(@src(), .horizontal, .{
|
|
||||||
.expand = .horizontal,
|
|
||||||
});
|
|
||||||
defer box.deinit();
|
|
||||||
try dvui.labelNoFmt(@src(), "this is a field value", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn tabDetail(ctx: anytype) !void {
|
|
||||||
_ = ctx;
|
|
||||||
// const task = ctx.task();
|
|
||||||
|
|
||||||
var vbox = try dvui.box(@src(), .vertical, .{ .expand = .both, });
|
|
||||||
defer vbox.deinit();
|
|
||||||
|
|
||||||
// detail
|
|
||||||
{
|
|
||||||
var detail = try dvui.textEntry(@src(), .{
|
|
||||||
.break_lines = true,
|
|
||||||
.scroll_vertical = true,
|
|
||||||
.scroll_horizontal = true,
|
|
||||||
.multiline = true,
|
|
||||||
}, .{
|
|
||||||
.expand = .both,
|
|
||||||
});
|
|
||||||
defer detail.deinit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Mock = struct {
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
_taskId: usize,
|
|
||||||
_summary: []const u8,
|
|
||||||
|
|
||||||
_shownTab: Tab,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator) !Self {
|
|
||||||
_ = alloc;
|
|
||||||
return Mock {
|
|
||||||
._taskId = 0,
|
|
||||||
._summary = "hello",
|
|
||||||
._shownTab = .info,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
_ = self;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn switchTab(self: *Self, tab: Tab) void {
|
|
||||||
self._shownTab = tab;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn taskId(self: *const Self) usize { return self._taskId; }
|
|
||||||
fn summary(self: *const Self) []const u8 { return self._summary; }
|
|
||||||
|
|
||||||
fn shownTab(self: *const Self) Tab { return self._shownTab; }
|
|
||||||
};
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
|||||||
const dvui = @import("dvui");
|
|
||||||
|
|
||||||
pub fn key(bind: []const u8) ?*dvui.Event {
|
|
||||||
for (dvui.events()) |*e| {
|
|
||||||
if (e.evt == .key) {
|
|
||||||
if (e.evt.key.matchBind(bind)) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
pub fn keyDown(bind: []const u8) bool {
|
|
||||||
return if (key(bind)) |e| e.evt.key.action == .down else false;
|
|
||||||
}
|
|
||||||
pub fn keyPress(bind: []const u8) bool {
|
|
||||||
return if (key(bind)) |e| (e.evt.key.action == .down or e.evt.key.action == .repeat) else false;
|
|
||||||
}
|
|
||||||
pub fn keyUp(bind: []const u8) bool {
|
|
||||||
return if (key(bind)) |e| e.evt.key.action == .up else false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mouse(action: dvui.Event.Mouse.Action, button: dvui.enums.Button) ?*dvui.Event {
|
|
||||||
for (dvui.events()) |*e| {
|
|
||||||
if (e.evt == .mouse and e.evt.mouse.action == action and e.evt.mouse.button == button) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
pub const event = @import("./event.zig");
|
|
@ -1,134 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const CommandIF = @import("./command/root.zig").Interface;
|
|
||||||
|
|
||||||
///
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
///
|
|
||||||
const CommandList = std.ArrayList(CommandIF);
|
|
||||||
|
|
||||||
///
|
|
||||||
commands: CommandList,
|
|
||||||
|
|
||||||
/// next command to be applied by redoing
|
|
||||||
head: usize,
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn init(alloc: std.mem.Allocator) Self {
|
|
||||||
return Self {
|
|
||||||
.commands = CommandList.init(alloc),
|
|
||||||
.head = 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
for (self.commands.items) |*command| {
|
|
||||||
command.deinit();
|
|
||||||
}
|
|
||||||
self.commands.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn exec(self: *Self, command: CommandIF) !void {
|
|
||||||
if (self.head >= self.commands.items.len) {
|
|
||||||
try self.commands.append(command);
|
|
||||||
} else {
|
|
||||||
try self.commands.resize(self.head + 1);
|
|
||||||
self.commands.items[self.head] = command;
|
|
||||||
}
|
|
||||||
errdefer _ = self.commands.pop();
|
|
||||||
|
|
||||||
self.head = self.commands.items.len;
|
|
||||||
try command.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn undo(self: *Self) !void {
|
|
||||||
if (self.head == 0) {
|
|
||||||
return error.NoCommand;
|
|
||||||
}
|
|
||||||
self.head -= 1;
|
|
||||||
errdefer self.head += 1;
|
|
||||||
|
|
||||||
try self.commands.items[self.head].revert();
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn redo(self: *Self) !void {
|
|
||||||
if (self.head >= self.commands.items.len) {
|
|
||||||
return error.NoCommand;
|
|
||||||
}
|
|
||||||
try self.commands.items[self.head].apply();
|
|
||||||
self.head += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn isUndoAvailable(self: *const Self) bool {
|
|
||||||
return self.head > 0;
|
|
||||||
}
|
|
||||||
///
|
|
||||||
pub fn isRedoAvailable(self: *const Self) bool {
|
|
||||||
return self.head < self.commands.items.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
var value1: i32 = 0;
|
|
||||||
var value2: i32 = 0;
|
|
||||||
|
|
||||||
var history = init(std.testing.allocator);
|
|
||||||
defer history.deinit();
|
|
||||||
|
|
||||||
try std.testing.expectEqual(false, history.isUndoAvailable());
|
|
||||||
try std.testing.expectEqual(false, history.isRedoAvailable());
|
|
||||||
try std.testing.expectError(error.NoCommand, history.undo());
|
|
||||||
try std.testing.expectError(error.NoCommand, history.redo());
|
|
||||||
|
|
||||||
try history.exec(
|
|
||||||
try CommandIF.make(std.testing.allocator, CommandIF.Mock { .target = &value1, }));
|
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
|
||||||
try std.testing.expectEqual(value1, 1);
|
|
||||||
|
|
||||||
try history.undo();
|
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), false);
|
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
|
||||||
try std.testing.expectEqual(value1, 0);
|
|
||||||
|
|
||||||
try history.redo();
|
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
|
||||||
try std.testing.expectEqual(value1, 1);
|
|
||||||
|
|
||||||
try history.exec(
|
|
||||||
try CommandIF.make(std.testing.allocator, CommandIF.Mock { .target = &value2, }));
|
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
|
||||||
try std.testing.expectEqual(value1, 1);
|
|
||||||
try std.testing.expectEqual(value2, 1);
|
|
||||||
|
|
||||||
try history.undo();
|
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
|
||||||
try std.testing.expectEqual(value1, 1);
|
|
||||||
try std.testing.expectEqual(value2, 0);
|
|
||||||
|
|
||||||
try history.redo();
|
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
|
||||||
try std.testing.expectEqual(value1, 1);
|
|
||||||
try std.testing.expectEqual(value2, 1);
|
|
||||||
|
|
||||||
try history.undo();
|
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
|
||||||
try std.testing.expectEqual(value1, 1);
|
|
||||||
try std.testing.expectEqual(value2, 0);
|
|
||||||
|
|
||||||
try history.undo();
|
|
||||||
try std.testing.expectEqual(history.isUndoAvailable(), false);
|
|
||||||
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
|
||||||
try std.testing.expectEqual(value1, 0);
|
|
||||||
try std.testing.expectEqual(value2, 0);
|
|
||||||
}
|
|
295
src/hncore/Digraph.zig
Normal file
295
src/hncore/Digraph.zig
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// A data type to store connections of nodes in directional-graph.
|
||||||
|
pub fn Container(comptime T: type, comptime lessThanFn: LessThanFunc(T)) type {
|
||||||
|
return struct {
|
||||||
|
///
|
||||||
|
pub const Node = T;
|
||||||
|
|
||||||
|
/// A connection between 2 nodes.
|
||||||
|
/// e.g.) `Conn { .from = X, .to = Y }` => "X is connected to Y"
|
||||||
|
pub const Conn = struct { from: T, to: T, };
|
||||||
|
|
||||||
|
///
|
||||||
|
pub const ConnList = std.ArrayList(Conn);
|
||||||
|
|
||||||
|
///
|
||||||
|
pub const Error = error {
|
||||||
|
AlreadyConnected,
|
||||||
|
NotConnected,
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
map: ConnList,
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn init(alloc: std.mem.Allocator, mapUnsorted: []const Conn) !@This() {
|
||||||
|
var mapSorted = ConnList.init(alloc);
|
||||||
|
try mapSorted.ensureTotalCapacity(mapUnsorted.len);
|
||||||
|
for (mapUnsorted) |conn| {
|
||||||
|
try mapSorted.append(conn);
|
||||||
|
}
|
||||||
|
std.mem.sort(Conn, mapSorted.items, {}, compareConn);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.map = mapSorted,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
///
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.map.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Makes new connection between `from` to `to`.
|
||||||
|
/// Returns false if they are already connected otherwise true.
|
||||||
|
pub fn connectIf(self: *@This(), from: T, to: T) !bool {
|
||||||
|
const begin, const end = self.findSegment(from);
|
||||||
|
if (self.findConnectionInSegment(from, to, begin, end)) |_| {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
try self.map.insert(end, Conn { .from = from, .to = to, });
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Same to `connectIf`, but returns an error if it's already connected.
|
||||||
|
pub fn connect(self: *@This(), from: T, to: T) !void {
|
||||||
|
if (!try self.connectIf(from, to)) {
|
||||||
|
return Error.AlreadyConnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes an existing connection between `from` to `to`.
|
||||||
|
/// Returns false if they are not connected otherwise true.
|
||||||
|
pub fn disconnectIf(self: *@This(), from: T, to: T) bool {
|
||||||
|
const begin, const end = self.findSegment(from);
|
||||||
|
if (self.findConnectionInSegment(from, to, begin, end)) |idx| {
|
||||||
|
_ = self.map.orderedRemove(idx);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Same to `disconnectIf`, but returns an error if it's not connected.
|
||||||
|
pub fn disconnect(self: *@This(), from: T, to: T) !void {
|
||||||
|
if (!self.disconnectIf(from, to)) {
|
||||||
|
return Error.NotConnected;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn isConnected(self: *const @This(), from: T, to: T) bool {
|
||||||
|
const begin, const end = self.findSegment(from);
|
||||||
|
return self.findConnectionInSegment(from, to, begin, end) != null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn getChildrenOf(self: *const @This(), from: T) []const Conn {
|
||||||
|
const begin, const end = self.findSegment(from);
|
||||||
|
return self.map.items[begin..end];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn findConnectionInSegment(self: *const @This(), from: T, to: T, begin: usize, end: usize) ?usize {
|
||||||
|
for (self.map.items[begin..end], begin..) |v, idx| {
|
||||||
|
if ((v.from == from) and (v.to == to)) {
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
fn findSegment(self: *const @This(), from: T) struct { usize, usize } {
|
||||||
|
const n = self.map.items.len;
|
||||||
|
if (n == 0) {
|
||||||
|
return .{ 0, 0, };
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseIdx = self.binsearch(from).?;
|
||||||
|
const baseFrom = self.map.items[baseIdx].from;
|
||||||
|
|
||||||
|
var begin: usize = undefined;
|
||||||
|
var end : usize = undefined;
|
||||||
|
if (lessThanFn(baseFrom, from)) {
|
||||||
|
begin = baseIdx;
|
||||||
|
while ((begin < n) and lessThanFn(self.map.items[begin].from, from)) { begin += 1; }
|
||||||
|
|
||||||
|
end = begin;
|
||||||
|
while ((end < n) and (self.map.items[end].from == from)) { end += 1; }
|
||||||
|
|
||||||
|
} else if (lessThanFn(from, baseFrom)) {
|
||||||
|
end = baseIdx;
|
||||||
|
while ((end > 0) and lessThanFn(from, self.map.items[end-1].from)) { end -= 1; }
|
||||||
|
|
||||||
|
begin = end;
|
||||||
|
while ((begin > 0) and (self.map.items[begin-1].from == from)) { begin -= 1; }
|
||||||
|
|
||||||
|
} else {
|
||||||
|
begin = baseIdx;
|
||||||
|
while ((begin > 0) and (self.map.items[begin-1].from == from)) { begin -= 1; }
|
||||||
|
|
||||||
|
end = baseIdx;
|
||||||
|
while ((end < n) and (self.map.items[end].from == from)) { end += 1; }
|
||||||
|
}
|
||||||
|
return .{ begin, end, };
|
||||||
|
}
|
||||||
|
fn binsearch(self: *const @This(), from: T) ?usize {
|
||||||
|
if (self.map.items.len == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var left : usize = 0;
|
||||||
|
var right: usize = self.map.items.len;
|
||||||
|
|
||||||
|
var idx: usize = undefined;
|
||||||
|
while (left < right) {
|
||||||
|
idx = (left + right) / 2;
|
||||||
|
|
||||||
|
const target = self.map.items[idx].from;
|
||||||
|
if (lessThanFn(target, from)) {
|
||||||
|
left = idx + 1;
|
||||||
|
} else if (lessThanFn(from, target)) {
|
||||||
|
right = idx -| 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
|
fn compareConn(_: void, a: Conn, b: Conn) bool {
|
||||||
|
return lessThanFn(a.from, b.from);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type of comparator function for the type T, which is to be passed as an argument of `Container()`.
|
||||||
|
pub fn LessThanFunc(comptime T: type) type {
|
||||||
|
return fn (lhs: T, rhs: T) bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a lessThanFunc for the comparable type T.
|
||||||
|
pub fn lessThanFuncFor(comptime T: type) LessThanFunc(T) {
|
||||||
|
return struct {
|
||||||
|
fn inner(lhs: T, rhs: T) bool {
|
||||||
|
if (@typeInfo(T) == .pointer) {
|
||||||
|
return @intFromPtr(lhs) < @intFromPtr(rhs);
|
||||||
|
} else {
|
||||||
|
return lhs < rhs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "compile check for various types" {
|
||||||
|
_ = Container(u8, lessThanFuncFor(u8));
|
||||||
|
_ = Container(u16, lessThanFuncFor(u16));
|
||||||
|
_ = Container(i8, lessThanFuncFor(i8));
|
||||||
|
_ = Container(i16, lessThanFuncFor(i16));
|
||||||
|
_ = Container(*i8, lessThanFuncFor(*i8));
|
||||||
|
_ = Container(*anyopaque, lessThanFuncFor(*anyopaque));
|
||||||
|
}
|
||||||
|
test "check if connected" {
|
||||||
|
const Sut = Container(u8, lessThanFuncFor(u8));
|
||||||
|
|
||||||
|
const map = [_]Sut.Conn {
|
||||||
|
.{ .from = 3, .to = 0, },
|
||||||
|
.{ .from = 0, .to = 1, },
|
||||||
|
.{ .from = 1, .to = 3, },
|
||||||
|
};
|
||||||
|
|
||||||
|
var sut = try Sut.init(std.testing.allocator, map[0..]);
|
||||||
|
defer sut.deinit();
|
||||||
|
|
||||||
|
try std.testing.expect(sut.isConnected(0, 1));
|
||||||
|
try std.testing.expect(!sut.isConnected(1, 0));
|
||||||
|
|
||||||
|
try std.testing.expect(sut.isConnected(1, 3));
|
||||||
|
try std.testing.expect(!sut.isConnected(3, 1));
|
||||||
|
|
||||||
|
try std.testing.expect(sut.isConnected(3, 0));
|
||||||
|
try std.testing.expect(!sut.isConnected(0, 3));
|
||||||
|
|
||||||
|
try std.testing.expect(!sut.isConnected(0, 2));
|
||||||
|
try std.testing.expect(!sut.isConnected(2, 0));
|
||||||
|
|
||||||
|
try std.testing.expect(!sut.isConnected(1, 2));
|
||||||
|
try std.testing.expect(!sut.isConnected(2, 1));
|
||||||
|
}
|
||||||
|
test "make new connection" {
|
||||||
|
const Sut = Container(u8, lessThanFuncFor(u8));
|
||||||
|
|
||||||
|
var sut = try Sut.init(std.testing.allocator, &.{});
|
||||||
|
defer sut.deinit();
|
||||||
|
|
||||||
|
try std.testing.expect(try sut.connectIf(2, 1));
|
||||||
|
|
||||||
|
try std.testing.expect(sut.isConnected(2, 1));
|
||||||
|
try std.testing.expect(!sut.isConnected(1, 2));
|
||||||
|
|
||||||
|
try sut.connect(3, 1);
|
||||||
|
|
||||||
|
try std.testing.expect(sut.isConnected(3, 1));
|
||||||
|
try std.testing.expect(!sut.isConnected(1, 3));
|
||||||
|
}
|
||||||
|
test "making an existing connection fails" {
|
||||||
|
const Sut = Container(u8, lessThanFuncFor(u8));
|
||||||
|
|
||||||
|
const map = [_]Sut.Conn {
|
||||||
|
.{ .from = 0, .to = 1, },
|
||||||
|
};
|
||||||
|
var sut = try Sut.init(std.testing.allocator, map[0..]);
|
||||||
|
defer sut.deinit();
|
||||||
|
|
||||||
|
try std.testing.expect(!try sut.connectIf(0, 1));
|
||||||
|
try std.testing.expectError(Sut.Error.AlreadyConnected, sut.connect(0, 1));
|
||||||
|
}
|
||||||
|
test "disconnect an existing connection" {
|
||||||
|
const Sut = Container(u8, lessThanFuncFor(u8));
|
||||||
|
|
||||||
|
const map = [_]Sut.Conn {
|
||||||
|
.{ .from = 0, .to = 1, },
|
||||||
|
.{ .from = 2, .to = 3, },
|
||||||
|
};
|
||||||
|
var sut = try Sut.init(std.testing.allocator, map[0..]);
|
||||||
|
defer sut.deinit();
|
||||||
|
|
||||||
|
try std.testing.expect(sut.disconnectIf(0, 1));
|
||||||
|
try std.testing.expect(!sut.isConnected(0, 1));
|
||||||
|
|
||||||
|
try sut.disconnect(2, 3);
|
||||||
|
try std.testing.expect(!sut.isConnected(2, 3));
|
||||||
|
}
|
||||||
|
test "disconnecting a missing connection fails" {
|
||||||
|
const Sut = Container(u8, lessThanFuncFor(u8));
|
||||||
|
|
||||||
|
var sut = try Sut.init(std.testing.allocator, &.{});
|
||||||
|
defer sut.deinit();
|
||||||
|
|
||||||
|
try std.testing.expect(!sut.disconnectIf(0, 1));
|
||||||
|
try std.testing.expectError(Sut.Error.NotConnected, sut.disconnect(1, 0));
|
||||||
|
}
|
||||||
|
test "chaotic operation" {
|
||||||
|
const Sut = Container(u16, lessThanFuncFor(u16));
|
||||||
|
|
||||||
|
var sut = try Sut.init(std.testing.allocator, &.{});
|
||||||
|
defer sut.deinit();
|
||||||
|
|
||||||
|
const N = 100;
|
||||||
|
const P = 109;
|
||||||
|
const Q = 113;
|
||||||
|
const R = 127;
|
||||||
|
for (0..N) |v| {
|
||||||
|
const x: Sut.Node = @intCast(v);
|
||||||
|
try sut.connect((x*Q)%P, (x*R)%P);
|
||||||
|
}
|
||||||
|
for (N/2..N) |v| {
|
||||||
|
const x: Sut.Node = @intCast(v);
|
||||||
|
try sut.disconnect((x*Q)%P, (x*R)%P);
|
||||||
|
}
|
||||||
|
for (0..N/2) |v| {
|
||||||
|
const x: Sut.Node = @intCast(v);
|
||||||
|
try std.testing.expect(sut.isConnected((x*Q)%P, (x*R)%P));
|
||||||
|
}
|
||||||
|
for (N/2..N) |v| {
|
||||||
|
const x: Sut.Node = @intCast(v);
|
||||||
|
try std.testing.expect(!sut.isConnected((x*Q)%P, (x*R)%P));
|
||||||
|
}
|
||||||
|
}
|
71
src/hncore/Merged.zig
Normal file
71
src/hncore/Merged.zig
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// Returns a type having fields that type A and type B have.
|
||||||
|
/// Fields from B are more preferred than from A in case that field names are duplicated.
|
||||||
|
pub fn Merged(comptime A: type, comptime B: type) type {
|
||||||
|
const at = @typeInfo(A).@"struct";
|
||||||
|
const bt = @typeInfo(B).@"struct";
|
||||||
|
|
||||||
|
return @Type(.{
|
||||||
|
.@"struct" = .{
|
||||||
|
.layout = .auto,
|
||||||
|
.fields = mergeFields(at.fields, bt.fields),
|
||||||
|
.is_tuple = false,
|
||||||
|
.decls = &.{},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fn mergeFields(
|
||||||
|
comptime a: []const std.builtin.Type.StructField,
|
||||||
|
comptime b: []const std.builtin.Type.StructField) []std.builtin.Type.StructField {
|
||||||
|
var ret: [a.len+b.len]std.builtin.Type.StructField = undefined;
|
||||||
|
var len: usize = 0;
|
||||||
|
|
||||||
|
inline for (b) |f| {
|
||||||
|
ret[len] = f;
|
||||||
|
ret[len].is_comptime = false;
|
||||||
|
len += 1;
|
||||||
|
}
|
||||||
|
inline for (a) |f| {
|
||||||
|
inline for (b) |fb| {
|
||||||
|
if (std.mem.eql(u8, f.name, fb.name)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret[len] = f;
|
||||||
|
ret[len].is_comptime = false;
|
||||||
|
len += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret[0..len];
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same to `Merged(@TypeOf(a), @TypeOf(b))` but returns a merged value, not type.
|
||||||
|
pub fn merge(a: anytype, b: anytype) Merged(@TypeOf(a), @TypeOf(b)) {
|
||||||
|
var ret: Merged(@TypeOf(a), @TypeOf(b)) = undefined;
|
||||||
|
|
||||||
|
const af = @typeInfo(@TypeOf(a)).@"struct".fields;
|
||||||
|
const bf = @typeInfo(@TypeOf(b)).@"struct".fields;
|
||||||
|
|
||||||
|
inline for (bf) |f| {
|
||||||
|
@field(ret, f.name) = @field(b, f.name);
|
||||||
|
}
|
||||||
|
inline for (af) |f| {
|
||||||
|
inline for (bf) |fb| {
|
||||||
|
if (std.mem.eql(u8, f.name, fb.name)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
@field(ret, f.name) = @field(a, f.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
test "merging 2 structs" {
|
||||||
|
const merged = merge(.{.a = 0, .b = 1, .c = 2,}, .{.b = 44, .d = 53,});
|
||||||
|
try std.testing.expect(merged.a == 0);
|
||||||
|
try std.testing.expect(merged.b == 44);
|
||||||
|
try std.testing.expect(merged.c == 2);
|
||||||
|
try std.testing.expect(merged.d == 53);
|
||||||
|
}
|
76
src/hncore/Mindmap.zig
Normal file
76
src/hncore/Mindmap.zig
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Digraph = @import("./Digraph.zig");
|
||||||
|
|
||||||
|
pub const Node = @import("./Node.zig");
|
||||||
|
pub const NodeList = std.ArrayList(*Node);
|
||||||
|
pub const NodeDigraph = Digraph.Container(*const Node, Digraph.lessThanFuncFor(*const Node));
|
||||||
|
|
||||||
|
///
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
///
|
||||||
|
nodes: NodeList,
|
||||||
|
|
||||||
|
///
|
||||||
|
digraph: NodeDigraph,
|
||||||
|
|
||||||
|
///
|
||||||
|
root: *Node,
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn init(alloc: std.mem.Allocator) !@This() {
|
||||||
|
var nodes = NodeList.init(alloc);
|
||||||
|
errdefer nodes.deinit();
|
||||||
|
|
||||||
|
var digraph = try NodeDigraph.init(alloc, &.{});
|
||||||
|
errdefer digraph.deinit();
|
||||||
|
|
||||||
|
var root = try alloc.create(Node);
|
||||||
|
errdefer alloc.destroy(root);
|
||||||
|
|
||||||
|
root.* = try Node.init(alloc, 0, "helloworld");
|
||||||
|
errdefer root.deinit(alloc);
|
||||||
|
|
||||||
|
try nodes.append(root);
|
||||||
|
|
||||||
|
var node1 = try alloc.create(Node);
|
||||||
|
errdefer alloc.destroy(node1);
|
||||||
|
node1.* = try Node.init(alloc, 1, "node1");
|
||||||
|
errdefer node1.deinit(alloc);
|
||||||
|
try nodes.append(node1);
|
||||||
|
try digraph.connect(root, node1);
|
||||||
|
|
||||||
|
var node2 = try alloc.create(Node);
|
||||||
|
errdefer alloc.destroy(node2);
|
||||||
|
node2.* = try Node.init(alloc, 2, "node1");
|
||||||
|
errdefer node2.deinit(alloc);
|
||||||
|
try nodes.append(node2);
|
||||||
|
try digraph.connect(root, node2);
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.nodes = nodes,
|
||||||
|
.digraph = digraph,
|
||||||
|
.root = root,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
///
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
for (self.nodes.items) |node| {
|
||||||
|
node.deinit(self.alloc);
|
||||||
|
self.alloc.destroy(node);
|
||||||
|
}
|
||||||
|
self.digraph.deinit();
|
||||||
|
self.nodes.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn serialize(self: *const @This(), writer: anytype) !void {
|
||||||
|
_ = self;
|
||||||
|
_ = writer;
|
||||||
|
}
|
||||||
|
///
|
||||||
|
pub fn deserialize(reader: anytype) !@This() {
|
||||||
|
_ = reader;
|
||||||
|
}
|
35
src/hncore/Node.zig
Normal file
35
src/hncore/Node.zig
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
///
|
||||||
|
/// unique and immutable integer
|
||||||
|
id: usize,
|
||||||
|
|
||||||
|
/// summary text of this node
|
||||||
|
summary: []const u8,
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn init(alloc: std.mem.Allocator, id: usize, summary: []const u8) !@This() {
|
||||||
|
return .{
|
||||||
|
.id = id,
|
||||||
|
.summary = try alloc.dupe(u8, summary),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/// pass the same allocator as init() call
|
||||||
|
pub fn deinit(self: *@This(), alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.summary);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "serialize" {
|
||||||
|
const alloc = std.testing.allocator;
|
||||||
|
var node = try init(alloc, 0, "helloworld");
|
||||||
|
defer node.deinit(alloc);
|
||||||
|
|
||||||
|
var json = std.ArrayList(u8).init(alloc);
|
||||||
|
defer json.deinit();
|
||||||
|
try std.json.stringify(node, .{}, json.writer());
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings(
|
||||||
|
json.items,
|
||||||
|
\\{"id":0,"summary":"helloworld"}
|
||||||
|
);
|
||||||
|
}
|
16
src/hncore/Project.zig
Normal file
16
src/hncore/Project.zig
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Mindmap = @import("./Mindmap.zig");
|
||||||
|
|
||||||
|
///
|
||||||
|
mindmap: Mindmap,
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn init(alloc: std.mem.Allocator) !@This() {
|
||||||
|
return .{
|
||||||
|
.mindmap = try .init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *@This()) void {
|
||||||
|
self.mindmap.deinit();
|
||||||
|
}
|
@ -1,48 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Task = @import("./Task.zig");
|
|
||||||
|
|
||||||
///
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
///
|
|
||||||
const TaskList = std.ArrayList(*Task);
|
|
||||||
|
|
||||||
///
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
|
|
||||||
///
|
|
||||||
name: [:0]const u8,
|
|
||||||
|
|
||||||
///
|
|
||||||
tasks: TaskList,
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn init(alloc: std.mem.Allocator, name: []const u8) !Self {
|
|
||||||
return Self {
|
|
||||||
.alloc = alloc,
|
|
||||||
.name = try alloc.dupeZ(u8, name),
|
|
||||||
.tasks = TaskList.init(alloc),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
self.tasks.deinit();
|
|
||||||
self.alloc.free(self.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn rename(self: *Self, name: []const u8) !void {
|
|
||||||
self.alloc.free(self.name);
|
|
||||||
self.name = try self.alloc.dupeZ(u8, name);
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
var stage = try init(std.testing.allocator, "helloworld");
|
|
||||||
defer stage.deinit();
|
|
||||||
try std.testing.expectEqualStrings("helloworld", stage.name);
|
|
||||||
|
|
||||||
try stage.rename("goodbye");
|
|
||||||
try std.testing.expectEqualStrings("goodbye", stage.name);
|
|
||||||
}
|
|
210
src/hncore/Store.zig
Normal file
210
src/hncore/Store.zig
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// A container for all existing instances of T.
|
||||||
|
/// This is like a dedicated allocator for the type, T, with a refcount system.
|
||||||
|
pub fn Store(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
///
|
||||||
|
pub const Item = T;
|
||||||
|
|
||||||
|
///
|
||||||
|
pub const VTable = struct {
|
||||||
|
///
|
||||||
|
udata: ?*anyopaque = null,
|
||||||
|
|
||||||
|
///
|
||||||
|
deinitItem: ?*fn (?*anyopaque, *T) void = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// A struct which holds an item and its reference count.
|
||||||
|
pub const Slot = struct {
|
||||||
|
item : Item,
|
||||||
|
refcnt: usize,
|
||||||
|
|
||||||
|
pub fn ref(self: *@This()) void {
|
||||||
|
self.refcnt += 1;
|
||||||
|
}
|
||||||
|
pub fn unref(self: *@This()) void {
|
||||||
|
self.refcnt -= 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
pub const Error = error {
|
||||||
|
DetectedItemLeak,
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
alloc : std.mem.Allocator,
|
||||||
|
|
||||||
|
///
|
||||||
|
vtable: VTable,
|
||||||
|
|
||||||
|
///
|
||||||
|
slots : std.ArrayList(*Slot),
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn init(alloc: std.mem.Allocator, vtable: VTable) @This() {
|
||||||
|
return .{
|
||||||
|
.alloc = alloc,
|
||||||
|
.vtable = vtable,
|
||||||
|
.slots = std.ArrayList(*Slot).init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
///
|
||||||
|
pub fn deinit(self: *@This()) !void {
|
||||||
|
defer self.slots.deinit();
|
||||||
|
|
||||||
|
try self.collectGarbage();
|
||||||
|
if (self.slots.items.len > 0) {
|
||||||
|
return Error.DetectedItemLeak;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a slot which contains newly-allocated item.
|
||||||
|
/// - A reference count of the returned slot is 1
|
||||||
|
/// - The returned slot and its contents are alive till the reference count becomes 0
|
||||||
|
pub fn add(self: *@This(), item: Item) !*Slot {
|
||||||
|
for (self.slots.items) |slot| {
|
||||||
|
if (slot.refcnt == 0) {
|
||||||
|
self.deinitItem(&slot.item);
|
||||||
|
slot.* = .{ .item = item, .refcnt = 1, };
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newSlot = try self.alloc.create(Slot);
|
||||||
|
errdefer self.alloc.destroy(newSlot);
|
||||||
|
|
||||||
|
newSlot.* = .{ .item = item, .refcnt = 1, };
|
||||||
|
try self.slots.append(newSlot);
|
||||||
|
return newSlot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Releases memory of slots whose reference count is 0.
|
||||||
|
/// Usually no need to call this function because such slots will be reused.
|
||||||
|
pub fn collectGarbage(self: *@This()) !void {
|
||||||
|
var slots = self.slots.items;
|
||||||
|
|
||||||
|
var set: usize = 0;
|
||||||
|
var get: usize = 0;
|
||||||
|
while (set < slots.len) {
|
||||||
|
if (slots[set].refcnt == 0) {
|
||||||
|
self.deinitItem(&slots[set].item);
|
||||||
|
self.alloc.destroy(slots[set]);
|
||||||
|
|
||||||
|
get += 1;
|
||||||
|
if (get >= slots.len) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
slots[set] = slots[get];
|
||||||
|
} else {
|
||||||
|
set += 1;
|
||||||
|
get += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try self.slots.resize(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinitItem(self: *@This(), item: *Item) void {
|
||||||
|
if (self.vtable.deinitItem != null) {
|
||||||
|
self.vtable.deinitItem.?(self.vtable.udata, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "add new item" {
|
||||||
|
const Item = u16;
|
||||||
|
|
||||||
|
var sut = Store(Item).init(std.testing.allocator, .{});
|
||||||
|
defer sut.deinit() catch unreachable;
|
||||||
|
|
||||||
|
{
|
||||||
|
var slot = try sut.add(123);
|
||||||
|
defer slot.unref();
|
||||||
|
|
||||||
|
// check initial value
|
||||||
|
try std.testing.expect(slot.refcnt == 1);
|
||||||
|
try std.testing.expect(slot.item == 123);
|
||||||
|
|
||||||
|
// check refcnt handling
|
||||||
|
slot.ref();
|
||||||
|
try std.testing.expect(slot.refcnt == 2);
|
||||||
|
slot.unref();
|
||||||
|
try std.testing.expect(slot.refcnt == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test "reuse an expired slot by adding right after removal" {
|
||||||
|
const Item = u16;
|
||||||
|
|
||||||
|
var sut = Store(Item).init(std.testing.allocator, .{});
|
||||||
|
defer sut.deinit() catch unreachable;
|
||||||
|
|
||||||
|
var address: usize = undefined;
|
||||||
|
{
|
||||||
|
var slot = try sut.add(123);
|
||||||
|
defer slot.unref();
|
||||||
|
address = @intFromPtr(slot);
|
||||||
|
try std.testing.expect(slot.item == 123);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var slot = try sut.add(456);
|
||||||
|
defer slot.unref();
|
||||||
|
try std.testing.expect(address == @intFromPtr(slot));
|
||||||
|
try std.testing.expect(slot.item == 456);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test "reusing doesn't happen by adding twice" {
|
||||||
|
const Item = u16;
|
||||||
|
|
||||||
|
var sut = Store(Item).init(std.testing.allocator, .{});
|
||||||
|
defer sut.deinit() catch unreachable;
|
||||||
|
|
||||||
|
var slot1 = try sut.add(123);
|
||||||
|
defer slot1.unref();
|
||||||
|
|
||||||
|
var slot2 = try sut.add(456);
|
||||||
|
defer slot2.unref();
|
||||||
|
|
||||||
|
try std.testing.expect(slot1 != slot2);
|
||||||
|
try std.testing.expect(slot1.item == 123);
|
||||||
|
try std.testing.expect(slot2.item == 456);
|
||||||
|
}
|
||||||
|
test "chaotic addition and removals" {
|
||||||
|
const Item = u16;
|
||||||
|
|
||||||
|
var sut = Store(Item).init(std.testing.allocator, .{});
|
||||||
|
defer sut.deinit() catch unreachable;
|
||||||
|
|
||||||
|
var slots = std.ArrayList(*Store(Item).Slot).init(std.testing.allocator);
|
||||||
|
defer {
|
||||||
|
for (slots.items) |slot| { slot.unref(); }
|
||||||
|
slots.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// add 0~300
|
||||||
|
for (0..300) |i| {
|
||||||
|
const x: u16 = @intCast(i);
|
||||||
|
try slots.append(try sut.add(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
// removes other than multiples of 3
|
||||||
|
for (0..100) |i| {
|
||||||
|
slots.orderedRemove(300 - (i+1)*3 + 2).unref();
|
||||||
|
slots.orderedRemove(300 - (i+1)*3 + 1).unref();
|
||||||
|
}
|
||||||
|
|
||||||
|
// add multiples of 3 from 300 to 600
|
||||||
|
for (0..100) |i| {
|
||||||
|
const x: u16 = @intCast(i);
|
||||||
|
try slots.append(try sut.add(300 + x*3));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if the list is composed of multiples of 3 from 0~600
|
||||||
|
for (0..200) |i| {
|
||||||
|
const x: u16 = @intCast(i);
|
||||||
|
try std.testing.expectEqual(slots.items[i].item, x*3);
|
||||||
|
}
|
||||||
|
}
|
@ -1,65 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
///
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
|
|
||||||
///
|
|
||||||
id: usize,
|
|
||||||
|
|
||||||
///
|
|
||||||
summary: [:0]const u8,
|
|
||||||
|
|
||||||
///
|
|
||||||
details: [:0]const u8,
|
|
||||||
|
|
||||||
///
|
|
||||||
done: bool,
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn init(alloc: std.mem.Allocator, id: usize) !Self {
|
|
||||||
return Self {
|
|
||||||
.alloc = alloc,
|
|
||||||
.id = id,
|
|
||||||
.summary = try alloc.dupeZ(u8, ""),
|
|
||||||
.details = try alloc.dupeZ(u8, ""),
|
|
||||||
.done = true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
///
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
self.alloc.free(self.details);
|
|
||||||
self.alloc.free(self.summary);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn setSummary(self: *Self, v: []const u8) !void {
|
|
||||||
self.alloc.free(self.summary);
|
|
||||||
self.summary = try self.alloc.dupeZ(u8, v);
|
|
||||||
}
|
|
||||||
///
|
|
||||||
pub fn setDetails(self: *Self, v: []const u8) !void {
|
|
||||||
self.alloc.free(self.details);
|
|
||||||
self.details = try self.alloc.dupeZ(u8, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
var task = try init(std.testing.allocator, 0);
|
|
||||||
defer task.deinit();
|
|
||||||
try std.testing.expectEqual(task.id, 0);
|
|
||||||
try std.testing.expectEqualStrings("", task.summary);
|
|
||||||
try std.testing.expectEqualStrings("", task.details);
|
|
||||||
|
|
||||||
try task.setSummary("helloworld");
|
|
||||||
try std.testing.expectEqualStrings("helloworld", task.summary);
|
|
||||||
try std.testing.expectEqualStrings("", task.details);
|
|
||||||
|
|
||||||
try task.setDetails("good afternoon");
|
|
||||||
try std.testing.expectEqualStrings("helloworld", task.summary);
|
|
||||||
try std.testing.expectEqualStrings("good afternoon", task.details);
|
|
||||||
|
|
||||||
try task.setSummary("goodbye");
|
|
||||||
try std.testing.expectEqualStrings("goodbye", task.summary);
|
|
||||||
try std.testing.expectEqualStrings("good afternoon", task.details);
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Task = @import("./Task.zig");
|
|
||||||
|
|
||||||
///
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
///
|
|
||||||
const Map = std.AutoHashMap(usize, *Task);
|
|
||||||
|
|
||||||
///
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
|
|
||||||
///
|
|
||||||
map: Map,
|
|
||||||
|
|
||||||
///
|
|
||||||
nextId: usize,
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn init(alloc: std.mem.Allocator) Self {
|
|
||||||
return Self {
|
|
||||||
.alloc = alloc,
|
|
||||||
.map = Map.init(alloc),
|
|
||||||
.nextId = 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
///
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
var itr = self.map.valueIterator();
|
|
||||||
while (itr.next()) |task| {
|
|
||||||
task.*.deinit();
|
|
||||||
self.alloc.destroy(task.*);
|
|
||||||
}
|
|
||||||
self.map.deinit();
|
|
||||||
}
|
|
||||||
///
|
|
||||||
pub fn add(self: *Self) !*Task {
|
|
||||||
var task = try self.alloc.create(Task);
|
|
||||||
errdefer self.alloc.destroy(task);
|
|
||||||
|
|
||||||
task.* = try Task.init(self.alloc, self.nextId);
|
|
||||||
errdefer task.deinit();
|
|
||||||
|
|
||||||
try self.map.putNoClobber(task.id, task);
|
|
||||||
|
|
||||||
self.nextId += 1;
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
///
|
|
||||||
pub fn query(self: *const Self, id: usize) !*Task {
|
|
||||||
return self.map.get(id) orelse error.NotFound;
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
var store = init(std.testing.allocator);
|
|
||||||
defer store.deinit();
|
|
||||||
|
|
||||||
const task1 = try store.add();
|
|
||||||
const task2 = try store.add();
|
|
||||||
|
|
||||||
try std.testing.expect(task1.id != task2.id);
|
|
||||||
|
|
||||||
try std.testing.expectEqual(task1, store.query(task1.id));
|
|
||||||
try std.testing.expectEqual(task2, store.query(task2.id));
|
|
||||||
try std.testing.expectError(error.NotFound, store.query(1234));
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const CommandHistory = @import("./CommandHistory.zig");
|
|
||||||
const TaskStore = @import("./TaskStore.zig");
|
|
||||||
|
|
||||||
///
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
///
|
|
||||||
const Perma = struct {
|
|
||||||
tasks: TaskStore,
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
|
||||||
commands: CommandHistory,
|
|
||||||
|
|
||||||
///
|
|
||||||
perma: Perma,
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn init(allocator: std.mem.Allocator) Self {
|
|
||||||
return Self {
|
|
||||||
.commands = CommandHistory.init(allocator),
|
|
||||||
.perma = .{
|
|
||||||
.tasks = TaskStore.init(allocator),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
self.perma.tasks.deinit();
|
|
||||||
self.commands.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
var ws = init(std.testing.allocator);
|
|
||||||
defer ws.deinit();
|
|
||||||
}
|
|
@ -1,137 +0,0 @@
|
|||||||
//!
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
///
|
|
||||||
pub const Self = @This();
|
|
||||||
|
|
||||||
///
|
|
||||||
pub const Error = error {
|
|
||||||
///
|
|
||||||
OutOfMemory,
|
|
||||||
|
|
||||||
///
|
|
||||||
InvalidState,
|
|
||||||
|
|
||||||
///
|
|
||||||
Unknown,
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
|
||||||
pub const VTable = struct {
|
|
||||||
///
|
|
||||||
deinit: *const fn(ctx: *anyopaque) void,
|
|
||||||
|
|
||||||
///
|
|
||||||
apply: *const fn(ctx: *anyopaque) Error!void,
|
|
||||||
|
|
||||||
///
|
|
||||||
revert: *const fn(ctx: *anyopaque) Error!void,
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
|
||||||
pub const Mock = struct {
|
|
||||||
target : ?*i32 = null,
|
|
||||||
destroyed: ?*bool = null,
|
|
||||||
|
|
||||||
pub fn init(alloc: std.mem.Allocator, target: ?*i32, destroyed: ?*bool) !Self {
|
|
||||||
return make(alloc, Mock {
|
|
||||||
.target = target,
|
|
||||||
.destroyed = destroyed,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pub fn deinit(self: *@This(), _: anytype) void {
|
|
||||||
if (self.destroyed) |ptr| {
|
|
||||||
ptr.* = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn apply(self: *@This(), _: anytype) !void {
|
|
||||||
if (self.target) |ptr| {
|
|
||||||
ptr.* += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn revert(self: *@This(), _: anytype) !void {
|
|
||||||
if (self.target) |ptr| {
|
|
||||||
ptr.* -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
///
|
|
||||||
ctx: *anyopaque,
|
|
||||||
|
|
||||||
///
|
|
||||||
vtable: *const VTable,
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn deinit(self: Self) void {
|
|
||||||
self.vtable.deinit(self.ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn apply(self: Self) !void {
|
|
||||||
return self.vtable.apply(self.ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn revert(self: Self) !void {
|
|
||||||
return self.vtable.revert(self.ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn make(alloc: std.mem.Allocator, ctx: anytype) !Self {
|
|
||||||
const Target = @TypeOf(ctx);
|
|
||||||
const Wrapper = struct {
|
|
||||||
const vtable = VTable {
|
|
||||||
.deinit = @This().del,
|
|
||||||
.apply = @This().apply,
|
|
||||||
.revert = @This().revert,
|
|
||||||
};
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
ctx : Target,
|
|
||||||
|
|
||||||
fn new(alloc2: std.mem.Allocator, ctx2: Target) !*@This() {
|
|
||||||
const self = try alloc2.create(@This());
|
|
||||||
self.* = .{
|
|
||||||
.alloc = alloc2,
|
|
||||||
.ctx = ctx2,
|
|
||||||
};
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
fn del(selfp: *anyopaque) void {
|
|
||||||
const self: *@This() = @ptrCast(@alignCast(selfp));
|
|
||||||
self.ctx.deinit(self.alloc);
|
|
||||||
self.alloc.destroy(self);
|
|
||||||
}
|
|
||||||
fn apply(selfp: *anyopaque) Error!void {
|
|
||||||
const self: *@This() = @ptrCast(@alignCast(selfp));
|
|
||||||
return self.ctx.apply(self.alloc);
|
|
||||||
}
|
|
||||||
fn revert(selfp: *anyopaque) Error!void {
|
|
||||||
const self: *@This() = @ptrCast(@alignCast(selfp));
|
|
||||||
return self.ctx.revert(self.alloc);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return Self {
|
|
||||||
.ctx = try Wrapper.new(alloc, ctx),
|
|
||||||
.vtable = &Wrapper.vtable,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
test {
|
|
||||||
var value: i32 = 0;
|
|
||||||
var destroyed = false;
|
|
||||||
{
|
|
||||||
const cmd = try Mock.init(std.testing.allocator, &value, &destroyed);
|
|
||||||
defer cmd.deinit();
|
|
||||||
|
|
||||||
try cmd.apply();
|
|
||||||
try std.testing.expectEqual(value, 1);
|
|
||||||
|
|
||||||
try cmd.revert();
|
|
||||||
try std.testing.expectEqual(value, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
try std.testing.expect(destroyed);
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Interface = @import("./Interface.zig");
|
|
||||||
|
|
||||||
///
|
|
||||||
const Sequence = struct {
|
|
||||||
///
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
///
|
|
||||||
commands: []Interface,
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn init(alloc: std.mem.Allocator, commands: []const Interface) !Interface {
|
|
||||||
const list = try alloc.dupe(Interface, commands);
|
|
||||||
errdefer alloc.free(list);
|
|
||||||
return try Interface.make(alloc, Self {
|
|
||||||
.commands = list,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
|
|
||||||
for (self.commands) |command| {
|
|
||||||
command.deinit();
|
|
||||||
}
|
|
||||||
alloc.free(self.commands);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn apply(self: *Self, _: std.mem.Allocator) Interface.Error!void {
|
|
||||||
for (self.commands) |command| {
|
|
||||||
try command.apply();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn revert(self: *Self, _: std.mem.Allocator) Interface.Error!void {
|
|
||||||
var i = self.commands.len;
|
|
||||||
while (i > 0) {
|
|
||||||
i -= 1;
|
|
||||||
try self.commands[i].revert();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
test {
|
|
||||||
var value1: i32 = 0;
|
|
||||||
var value2: i32 = 0;
|
|
||||||
var value3: i32 = 0;
|
|
||||||
|
|
||||||
var finished1 = false;
|
|
||||||
var finished2 = false;
|
|
||||||
var finished3 = false;
|
|
||||||
|
|
||||||
{
|
|
||||||
var seq = blk: {
|
|
||||||
const alloc = std.testing.allocator;
|
|
||||||
|
|
||||||
var cmd1 = try Interface.Mock.init(alloc, &value1, &finished1);
|
|
||||||
errdefer cmd1.deinit();
|
|
||||||
|
|
||||||
var cmd2 = try Interface.Mock.init(alloc, &value2, &finished2);
|
|
||||||
errdefer cmd2.deinit();
|
|
||||||
|
|
||||||
var cmd3 = try Interface.Mock.init(alloc, &value3, &finished3);
|
|
||||||
errdefer cmd3.deinit();
|
|
||||||
|
|
||||||
break :blk try Sequence.init(std.testing.allocator, &[_]Interface {
|
|
||||||
cmd1, cmd2, cmd3,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
defer seq.deinit();
|
|
||||||
|
|
||||||
try seq.apply();
|
|
||||||
try std.testing.expectEqual(1, value1);
|
|
||||||
try std.testing.expectEqual(1, value2);
|
|
||||||
try std.testing.expectEqual(1, value3);
|
|
||||||
|
|
||||||
try seq.revert();
|
|
||||||
try std.testing.expectEqual(0, value1);
|
|
||||||
try std.testing.expectEqual(0, value2);
|
|
||||||
try std.testing.expectEqual(0, value3);
|
|
||||||
}
|
|
||||||
try std.testing.expect(finished1);
|
|
||||||
try std.testing.expect(finished2);
|
|
||||||
try std.testing.expect(finished3);
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
pub const Interface = @import("./Interface.zig");
|
|
||||||
|
|
||||||
pub const generic = @import("./generic.zig");
|
|
||||||
pub const task = @import("./task.zig");
|
|
||||||
|
|
||||||
test {
|
|
||||||
@import("std").testing.refAllDecls(@This());
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Interface = @import("./Interface.zig");
|
|
||||||
|
|
||||||
const Task = @import("../Task.zig");
|
|
||||||
|
|
||||||
///
|
|
||||||
const SetSummary = struct {
|
|
||||||
///
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
///
|
|
||||||
target: *Task,
|
|
||||||
|
|
||||||
///
|
|
||||||
summary: [:0]const u8,
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn init(alloc: std.mem.Allocator, task: *Task, summary: []const u8) !Interface {
|
|
||||||
const summaryDup = try alloc.dupeZ(u8, summary);
|
|
||||||
errdefer alloc.free(summaryDup);
|
|
||||||
return try Interface.make(alloc, Self {
|
|
||||||
.target = task,
|
|
||||||
.summary = summaryDup,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
|
|
||||||
alloc.free(self.summary);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn apply(self: *Self, alloc: std.mem.Allocator) Interface.Error!void {
|
|
||||||
const old = try alloc.dupeZ(u8, self.target.summary);
|
|
||||||
errdefer alloc.free(old);
|
|
||||||
|
|
||||||
try self.target.setSummary(self.summary);
|
|
||||||
alloc.free(self.summary);
|
|
||||||
|
|
||||||
self.summary = old;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
pub fn revert(self: *Self, alloc: std.mem.Allocator) Interface.Error!void {
|
|
||||||
return self.apply(alloc);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
test {
|
|
||||||
const TaskStore = @import("../TaskStore.zig");
|
|
||||||
|
|
||||||
var tasks = TaskStore.init(std.testing.allocator);
|
|
||||||
defer tasks.deinit();
|
|
||||||
|
|
||||||
var task = try tasks.add();
|
|
||||||
try task.setSummary("helloworld");
|
|
||||||
|
|
||||||
var sut = try SetSummary.init(std.testing.allocator, task, "goodbye");
|
|
||||||
defer sut.deinit();
|
|
||||||
|
|
||||||
try sut.apply();
|
|
||||||
try std.testing.expectEqualStrings("goodbye", task.summary);
|
|
||||||
|
|
||||||
try sut.revert();
|
|
||||||
try std.testing.expectEqualStrings("helloworld", task.summary);
|
|
||||||
}
|
|
@ -1,12 +1,9 @@
|
|||||||
pub const command = @import("./command/root.zig");
|
pub const Mindmap = @import("./Mindmap.zig");
|
||||||
|
pub const Node = @import("./Node.zig");
|
||||||
|
pub const Project = @import("./Project.zig");
|
||||||
|
|
||||||
pub const CommandHistory = @import("./CommandHistory.zig");
|
pub const merge = @import("./Merged.zig").merge;
|
||||||
pub const Stage = @import("./Stage.zig");
|
|
||||||
pub const Task = @import("./Task.zig");
|
|
||||||
pub const TaskStore = @import("./TaskStore.zig");
|
|
||||||
pub const Workspace = @import("./Workspace.zig");
|
|
||||||
|
|
||||||
test {
|
test {
|
||||||
@import("std").testing.refAllDecls(@This());
|
@import("std").testing.refAllDecls(@This());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
54
src/hnet/App.zig
Normal file
54
src/hnet/App.zig
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const dvui = @import("dvui");
|
||||||
|
const hncore = @import("hncore");
|
||||||
|
|
||||||
|
const ui = @import("./ui.zig");
|
||||||
|
|
||||||
|
///
|
||||||
|
pub const App = struct {
|
||||||
|
///
|
||||||
|
project: hncore.Project,
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn init(alloc: std.mem.Allocator) !App {
|
||||||
|
return App {
|
||||||
|
.project = try .init(alloc),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
///
|
||||||
|
pub fn deinit(self: *App) void {
|
||||||
|
self.project.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn gui(self: *App) !void {
|
||||||
|
try self.guiMenu();
|
||||||
|
try self.guiMain();
|
||||||
|
}
|
||||||
|
fn guiMenu(self: *@This()) !void {
|
||||||
|
_ = self;
|
||||||
|
|
||||||
|
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", .{}, .{})) |_| {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn guiMain(self: *@This()) !void {
|
||||||
|
var overlay = try dvui.overlay(@src(), .{
|
||||||
|
.expand = .both,
|
||||||
|
.padding = dvui.Rect.all(8),
|
||||||
|
.color_fill = .{ .name = .fill_window },
|
||||||
|
.background = true,
|
||||||
|
});
|
||||||
|
defer overlay.deinit();
|
||||||
|
|
||||||
|
try ui.manipulator(self.project.mindmap.root, &self.project.mindmap.digraph);
|
||||||
|
}
|
||||||
|
};
|
@ -2,7 +2,7 @@ const std = @import("std");
|
|||||||
const dvui = @import("dvui");
|
const dvui = @import("dvui");
|
||||||
const hncore = @import("hncore");
|
const hncore = @import("hncore");
|
||||||
|
|
||||||
const App = @import("./App.zig");
|
const App = @import("./App.zig").App;
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
53
src/hnet/ui.zig
Normal file
53
src/hnet/ui.zig
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const dvui = @import("dvui");
|
||||||
|
const hncore = @import("hncore");
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn manipulator(root: *const hncore.Mindmap.Node, digraph: *const hncore.Mindmap.NodeDigraph) !void {
|
||||||
|
var overlay = try dvui.overlay(@src(), .{
|
||||||
|
.expand = .both,
|
||||||
|
});
|
||||||
|
defer overlay.deinit();
|
||||||
|
|
||||||
|
_ = try nodeTreeInManipulator(.{ .x = 100, .y = 100, }, root, digraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn nodeTreeInManipulator(base: dvui.Point, node: *const hncore.Mindmap.Node, digraph: *const hncore.Mindmap.NodeDigraph) !dvui.Rect {
|
||||||
|
const size = try nodeInManipulator(base, node);
|
||||||
|
const childrenRect = nodeChildrenInManipulator(
|
||||||
|
.{ .x = base.x + size.x, .y = base.y, }, node, digraph);
|
||||||
|
return .{
|
||||||
|
.x = base.x,
|
||||||
|
.y = base.y,
|
||||||
|
.w = childrenRect.w + size.w,
|
||||||
|
.h = @max(childrenRect.h, size.h),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn nodeInManipulator(base: dvui.Point, node: *const hncore.Mindmap.Node) !dvui.Rect {
|
||||||
|
var box = try dvui.box(@src(), .vertical, .{
|
||||||
|
.id_extra = node.id,
|
||||||
|
.border = .all(1),
|
||||||
|
.rect = .{ .x = base.x, .y = base.y, .w = 100, .h = 20, },
|
||||||
|
});
|
||||||
|
defer box.deinit();
|
||||||
|
return box.wd.borderRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
pub fn nodeChildrenInManipulator(base: dvui.Point, node: *const hncore.Mindmap.Node, digraph: *const hncore.Mindmap.NodeDigraph) dvui.Rect {
|
||||||
|
const parentMargin = 16;
|
||||||
|
const siblingMargin = 8;
|
||||||
|
|
||||||
|
var childDepth : f32 = 0;
|
||||||
|
var childOffset: f32 = 0;
|
||||||
|
for (digraph.getChildrenOf(node)) |conn| {
|
||||||
|
const childRect = nodeTreeInManipulator(
|
||||||
|
.{ .x = base.x + parentMargin, .y = base.y + childOffset, }, conn.to, digraph) catch unreachable;
|
||||||
|
childOffset += childRect.h + siblingMargin;
|
||||||
|
childDepth = @max(childRect.w, childDepth);
|
||||||
|
}
|
||||||
|
return .{ .x = base.x, .y = base.y, .w = childDepth, .h = childOffset, };
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user