implement data structure
This commit is contained in:
parent
0bff9feb35
commit
88252a151b
@ -69,6 +69,11 @@ fn TaskCardCtx(T: type) type {
|
|||||||
ctx : T,
|
ctx : T,
|
||||||
task: Task,
|
task: Task,
|
||||||
|
|
||||||
|
pub fn open(self: *Self) void {
|
||||||
|
_ = self;
|
||||||
|
std.debug.print("OPEN\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn id(self: *const Self) usize {
|
pub fn id(self: *const Self) usize {
|
||||||
return self.task.id;
|
return self.task.id;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const dvui = @import("dvui");
|
const dvui = @import("dvui");
|
||||||
|
|
||||||
|
const compo = @import("./root.zig");
|
||||||
|
|
||||||
pub const Task = struct {
|
pub const Task = struct {
|
||||||
id: usize,
|
id: usize,
|
||||||
name: [:0]const u8,
|
summary: [:0]const u8,
|
||||||
mark: bool,
|
done: bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn gui(ctx: anytype) !void {
|
pub fn gui(ctx: anytype) !void {
|
||||||
@ -24,39 +26,70 @@ pub fn gui(ctx: anytype) !void {
|
|||||||
});
|
});
|
||||||
defer reorder.deinit();
|
defer reorder.deinit();
|
||||||
|
|
||||||
var list = try dvui.box(@src(), .vertical, .{ .expand = .both, });
|
var list = try dvui.box(@src(), .vertical, .{
|
||||||
|
.expand = .both,
|
||||||
|
.padding = dvui.Rect.all(4),
|
||||||
|
});
|
||||||
defer list.deinit();
|
defer list.deinit();
|
||||||
|
|
||||||
const tasks = ctx.get_tasks();
|
const tasks = ctx.get_tasks();
|
||||||
for (0.., tasks) |idx, task| {
|
for (0.., tasks) |idx, task| {
|
||||||
var reorderable = try reorder.reorderable(@src(), .{}, .{
|
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,
|
.id_extra = idx,
|
||||||
.expand = .horizontal,
|
.expand = .horizontal,
|
||||||
});
|
});
|
||||||
defer reorderable.deinit();
|
defer reorderable.deinit();
|
||||||
|
|
||||||
var hbox = try dvui.box(@src(), .horizontal, .{
|
var subctx = TaskCardCtx(@TypeOf(ctx)) {
|
||||||
.expand = .both,
|
.ctx = ctx,
|
||||||
.background = true,
|
.task = task,
|
||||||
.border = dvui.Rect.all(1),
|
.reorderable = reorderable,
|
||||||
});
|
};
|
||||||
defer hbox.deinit();
|
try compo.taskcard.gui(@src(), &subctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var mark: bool = task.mark;
|
fn TaskCardCtx(T: type) type {
|
||||||
if (try dvui.checkbox(@src(), &mark, null, .{})) {
|
return struct {
|
||||||
ctx.mark(idx, mark);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
try dvui.label(@src(), "#{d} {s}", .{ task.id, task.name, }, .{});
|
pub fn id(self: *const Self) usize {
|
||||||
|
return self.task.id;
|
||||||
_ = try dvui.ReorderWidget.draggable(@src(), .{ .reorderable = reorderable, }, .{
|
|
||||||
.expand = .vertical,
|
|
||||||
.gravity_x = 1.0,
|
|
||||||
.gravity_y = 0.5,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
pub fn summary(self: *const Self) []const u8 {
|
||||||
try dvui.labelNoFmt(@src(), "no tasks anymore :)", .{ .gravity_x = 0.5 });
|
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 {
|
pub const Mock = struct {
|
||||||
@ -69,8 +102,10 @@ pub const Mock = struct {
|
|||||||
var tasks = TaskList.init(alloc);
|
var tasks = TaskList.init(alloc);
|
||||||
errdefer tasks.deinit();
|
errdefer tasks.deinit();
|
||||||
|
|
||||||
try tasks.append(Task { .id = 0, .name = "helloworld", .mark = true, });
|
for (0..100) |id| {
|
||||||
try tasks.append(Task { .id = 1, .name = "goodbye", .mark = false, });
|
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 {
|
return Mock {
|
||||||
.tasks = tasks,
|
.tasks = tasks,
|
||||||
@ -81,7 +116,7 @@ pub const Mock = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn mark(self: *Self, idx: usize, check: bool) void {
|
pub fn mark(self: *Self, idx: usize, check: bool) void {
|
||||||
self.tasks.items[idx].mark = check;
|
self.tasks.items[idx].done = check;
|
||||||
}
|
}
|
||||||
pub fn get_tasks(self: *const Self) []const Task {
|
pub fn get_tasks(self: *const Self) []const Task {
|
||||||
return self.tasks.items;
|
return self.tasks.items;
|
||||||
|
@ -4,14 +4,8 @@ const dvui = @import("dvui");
|
|||||||
const ui = @import("../ui/root.zig");
|
const ui = @import("../ui/root.zig");
|
||||||
|
|
||||||
pub fn gui(src: std.builtin.SourceLocation, ctx: anytype) !void {
|
pub fn gui(src: std.builtin.SourceLocation, ctx: anytype) !void {
|
||||||
const id = ctx.id();
|
|
||||||
const summary = ctx.summary();
|
|
||||||
const done = ctx.done();
|
|
||||||
const archived = ctx.archived();
|
|
||||||
const staged = ctx.staged();
|
|
||||||
|
|
||||||
var card = try dvui.box(src, .horizontal, .{
|
var card = try dvui.box(src, .horizontal, .{
|
||||||
.id_extra = id,
|
.id_extra = ctx.id(),
|
||||||
.expand = .horizontal,
|
.expand = .horizontal,
|
||||||
.background = true,
|
.background = true,
|
||||||
.border = dvui.Rect.all(1),
|
.border = dvui.Rect.all(1),
|
||||||
@ -19,7 +13,7 @@ pub fn gui(src: std.builtin.SourceLocation, ctx: anytype) !void {
|
|||||||
defer card.deinit();
|
defer card.deinit();
|
||||||
|
|
||||||
{
|
{
|
||||||
var check = done;
|
var check = ctx.done();
|
||||||
const changed = try dvui.checkbox(@src(), &check, null, .{
|
const changed = try dvui.checkbox(@src(), &check, null, .{
|
||||||
.gravity_y = 0.5,
|
.gravity_y = 0.5,
|
||||||
.gravity_x = 0,
|
.gravity_x = 0,
|
||||||
@ -37,7 +31,7 @@ pub fn gui(src: std.builtin.SourceLocation, ctx: anytype) !void {
|
|||||||
});
|
});
|
||||||
defer icons.deinit();
|
defer icons.deinit();
|
||||||
|
|
||||||
if (staged) {
|
if (ctx.staged()) {
|
||||||
if (try buttonIcon(@src(), "unstage", dvui.entypo.light_down)) {
|
if (try buttonIcon(@src(), "unstage", dvui.entypo.light_down)) {
|
||||||
std.debug.print("UNSTAGE\n", .{});
|
std.debug.print("UNSTAGE\n", .{});
|
||||||
}
|
}
|
||||||
@ -46,7 +40,7 @@ pub fn gui(src: std.builtin.SourceLocation, ctx: anytype) !void {
|
|||||||
std.debug.print("STAGE\n", .{});
|
std.debug.print("STAGE\n", .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (archived) {
|
if (ctx.archived()) {
|
||||||
if (try buttonIcon(@src(), "unarchive", dvui.entypo.back_in_time)) {
|
if (try buttonIcon(@src(), "unarchive", dvui.entypo.back_in_time)) {
|
||||||
std.debug.print("UNARCHIVE\n", .{});
|
std.debug.print("UNARCHIVE\n", .{});
|
||||||
}
|
}
|
||||||
@ -57,12 +51,12 @@ pub fn gui(src: std.builtin.SourceLocation, ctx: anytype) !void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const open = try dvui.labelClick(@src(), "#{d} {s}", .{ ctx.id(), summary, }, .{
|
const rc = try title(ctx, card.data().rectScale().r.topLeft());
|
||||||
.expand = .horizontal,
|
|
||||||
.gravity_x = 0,
|
// dragging link
|
||||||
});
|
if (!@hasDecl(@TypeOf(ctx.*), "startDragging")) {
|
||||||
if (open) {
|
// WIP
|
||||||
std.debug.print("OPEN\n", .{});
|
// dvui.pathStroke([_]dvui.Point{rc.topLeft(), cw.});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,3 +67,119 @@ fn buttonIcon(src: std.builtin.SourceLocation, name: []const u8, icon: []const u
|
|||||||
.gravity_y = 0.5,
|
.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;
|
||||||
|
}
|
||||||
|
51
src/hncore/Command.zig
Normal file
51
src/hncore/Command.zig
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
//!
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
///
|
||||||
|
pub const Self = @This();
|
||||||
|
|
||||||
|
///
|
||||||
|
pub const Error = error {
|
||||||
|
///
|
||||||
|
AllocationFailure,
|
||||||
|
|
||||||
|
///
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
///
|
||||||
|
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);
|
||||||
|
}
|
149
src/hncore/CommandHistory.zig
Normal file
149
src/hncore/CommandHistory.zig
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Command = @import("./Command.zig");
|
||||||
|
|
||||||
|
///
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
///
|
||||||
|
const CommandList = std.ArrayList(Command);
|
||||||
|
|
||||||
|
///
|
||||||
|
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: Command) !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 {
|
||||||
|
const Mock = struct {
|
||||||
|
const vt = Command.VTable {
|
||||||
|
.deinit = deinit_,
|
||||||
|
.apply = apply_,
|
||||||
|
.revert = revert_,
|
||||||
|
};
|
||||||
|
count: i32 = 0,
|
||||||
|
fn deinit_(_: *anyopaque) void { }
|
||||||
|
fn apply_(ctx: *anyopaque) Command.Error!void {
|
||||||
|
var self: *@This() = @ptrCast(@alignCast(ctx));
|
||||||
|
self.count += 1;
|
||||||
|
}
|
||||||
|
fn revert_(ctx: *anyopaque) Command.Error!void {
|
||||||
|
var self: *@This() = @ptrCast(@alignCast(ctx));
|
||||||
|
self.count -= 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var mock1 = Mock {};
|
||||||
|
var mock2 = Mock {};
|
||||||
|
|
||||||
|
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(Command { .ctx = &mock1, .vtable = &Mock.vt, });
|
||||||
|
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||||
|
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
||||||
|
try std.testing.expectEqual(mock1.count, 1);
|
||||||
|
|
||||||
|
try history.undo();
|
||||||
|
try std.testing.expectEqual(history.isUndoAvailable(), false);
|
||||||
|
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
||||||
|
try std.testing.expectEqual(mock1.count, 0);
|
||||||
|
|
||||||
|
try history.redo();
|
||||||
|
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||||
|
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
||||||
|
try std.testing.expectEqual(mock1.count, 1);
|
||||||
|
|
||||||
|
try history.exec(Command { .ctx = &mock2, .vtable = &Mock.vt, });
|
||||||
|
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||||
|
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
||||||
|
try std.testing.expectEqual(mock1.count, 1);
|
||||||
|
try std.testing.expectEqual(mock2.count, 1);
|
||||||
|
|
||||||
|
try history.undo();
|
||||||
|
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||||
|
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
||||||
|
try std.testing.expectEqual(mock1.count, 1);
|
||||||
|
try std.testing.expectEqual(mock2.count, 0);
|
||||||
|
|
||||||
|
try history.redo();
|
||||||
|
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||||
|
try std.testing.expectEqual(history.isRedoAvailable(), false);
|
||||||
|
try std.testing.expectEqual(mock1.count, 1);
|
||||||
|
try std.testing.expectEqual(mock2.count, 1);
|
||||||
|
|
||||||
|
try history.undo();
|
||||||
|
try std.testing.expectEqual(history.isUndoAvailable(), true);
|
||||||
|
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
||||||
|
try std.testing.expectEqual(mock1.count, 1);
|
||||||
|
try std.testing.expectEqual(mock2.count, 0);
|
||||||
|
|
||||||
|
try history.undo();
|
||||||
|
try std.testing.expectEqual(history.isUndoAvailable(), false);
|
||||||
|
try std.testing.expectEqual(history.isRedoAvailable(), true);
|
||||||
|
try std.testing.expectEqual(mock1.count, 0);
|
||||||
|
try std.testing.expectEqual(mock2.count, 0);
|
||||||
|
}
|
71
src/hncore/Task.zig
Normal file
71
src/hncore/Task.zig
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
///
|
||||||
|
alloc: std.mem.Allocator,
|
||||||
|
|
||||||
|
///
|
||||||
|
id: usize,
|
||||||
|
|
||||||
|
///
|
||||||
|
summary: [:0]const u8,
|
||||||
|
|
||||||
|
///
|
||||||
|
details: [:0]const u8,
|
||||||
|
|
||||||
|
///
|
||||||
|
done: bool,
|
||||||
|
|
||||||
|
///
|
||||||
|
archived: 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,
|
||||||
|
.archived = 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) ![:0]const u8 {
|
||||||
|
self.alloc.free(self.summary);
|
||||||
|
self.summary = try self.alloc.dupeZ(u8, v);
|
||||||
|
return self.summary;
|
||||||
|
}
|
||||||
|
///
|
||||||
|
pub fn setDetails(self: *Self, v: []const u8) ![:0]const u8 {
|
||||||
|
self.alloc.free(self.details);
|
||||||
|
self.details = try self.alloc.dupeZ(u8, v);
|
||||||
|
return self.details;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
59
src/hncore/TaskStore.zig
Normal file
59
src/hncore/TaskStore.zig
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
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));
|
||||||
|
}
|
23
src/hncore/Workspace.zig
Normal file
23
src/hncore/Workspace.zig
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const CommandHistory = @import("./CommandHistory.zig");
|
||||||
|
const TaskStore = @import("./TaskStore.zig");
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
tasks: TaskStore,
|
||||||
|
commands: CommandHistory,
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) Self {
|
||||||
|
return Self {
|
||||||
|
.commands = CommandHistory.init(allocator),
|
||||||
|
.tasks = TaskStore.init(allocator),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.tasks.deinit();
|
||||||
|
}
|
||||||
|
test {
|
||||||
|
var ws = init(std.testing.allocator);
|
||||||
|
defer ws.deinit();
|
||||||
|
}
|
@ -1,3 +1,9 @@
|
|||||||
|
pub const Command = @import("./Command.zig");
|
||||||
|
pub const CommandHistory = @import("./CommandHistory.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());
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user