Skip to content

Commit

Permalink
basic time feed example
Browse files Browse the repository at this point in the history
  • Loading branch information
zenith391 committed Feb 5, 2023
1 parent ab6d8df commit 62181cd
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 4 deletions.
1 change: 1 addition & 0 deletions android/src/jni.zig
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ pub const JNI = opaque {
return jni.invokeJni(.CallObjectMethod, .{ object, method_id } ++ args);
}

// This is required as float arguments get corrupted when passed as varargs
fn argsToValueArray(args: anytype) [args.len]android.jvalue {
var values: [args.len]android.jvalue = undefined;
inline for (args) |arg, i| {
Expand Down
2 changes: 1 addition & 1 deletion examples/graph.zig
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pub const LineGraph_Impl = struct {
ctx.setColor(0, 0, 0);
ctx.line(oldX, oldY, dx, dy);
ctx.stroke();
ctx.ellipse(oldX, oldY, 3, 3);
ctx.ellipse(oldX - 3, oldY - 3, 6, 6);
ctx.fill();
oldX = dx;
oldY = dy;
Expand Down
105 changes: 105 additions & 0 deletions examples/time-feed.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
const std = @import("std");
const capy = @import("capy");
pub usingnamespace capy.cross_platform;

// All time values are in UNIX timestamp
const TimeActivity = struct {
start: u64,
end: u64,
description: []const u8,
};

const ListModel = struct {
size: capy.DataWrapper(usize) = capy.DataWrapper(usize).of(0),
arena: std.heap.ArenaAllocator = std.heap.ArenaAllocator.init(capy.internal.lasting_allocator),
data: std.ArrayList(TimeActivity),

pub fn add(self: *ListModel, activity: TimeActivity) !void {
try self.data.append(activity);
self.size.set(self.size.get() + 1);
}

pub fn getComponent(self: *ListModel, index: usize) capy.Container_Impl {
const activity = self.data.items[index];
const start_epoch = std.time.epoch.EpochSeconds{ .secs = activity.start };
const start_day = start_epoch.getDaySeconds();

const end_epoch = std.time.epoch.EpochSeconds{ .secs = activity.end };
const end_day = end_epoch.getDaySeconds();
return Card(capy.Column(.{}, .{
capy.Label(.{
.text = std.fmt.allocPrintZ(self.arena.allocator(), "{d:0>2}:{d:0>2} - {d:0>2}:{d:0>2}", .{
start_day.getHoursIntoDay(),
start_day.getMinutesIntoHour(),
end_day.getHoursIntoDay(),
end_day.getMinutesIntoHour(),
}) catch unreachable,
}),
capy.Label(.{ .text = activity.description }),
capy.Align(.{ .x = 1 }, capy.Button(.{ .label = "Edit" })),
})) catch unreachable;
}
};

pub fn Card(child: anytype) anyerror!capy.Container_Impl {
return try capy.Stack(.{
capy.Rect(.{ .color = capy.Color.comptimeFromString("#ffffff") }),
capy.Margin(capy.Rectangle.init(10, 10, 10, 10), try child),
});
}

var submitDesc = capy.StringDataWrapper.of("");
var submitEnabled = capy.DataWrapper(bool).of(false);
var list_model: ListModel = undefined;

fn onSubmit(_: *anyopaque) !void {
try list_model.add(.{
.start = @intCast(u64, std.time.timestamp() - 1000),
.end = @intCast(u64, std.time.timestamp()),
.description = submitDesc.get(),
});

// clear description
submitDesc.set("");
}

pub fn InsertCard() anyerror!capy.Container_Impl {
submitEnabled.dependOn(.{&submitDesc}, &(struct {
fn callback(description: []const u8) bool {
return description.len > 0;
}
}.callback)) catch unreachable;

return try capy.Column(.{}, .{
// TODO: TextArea when it supports data wrappers
capy.TextField(.{ .name = "description" })
.bind("text", &submitDesc), // placeholder = "Task description..."
capy.Label(.{ .text = "Going on since.. 00:00:20" }),
capy.Align(.{ .x = 1 }, capy.Row(.{}, .{
capy.Button(.{ .label = "Submit", .onclick = onSubmit })
.bind("enabled", &submitEnabled),
capy.Button(.{ .label = "Delete" }), // TODO: icon
})),
});
}

pub fn main() !void {
try capy.backend.init();

list_model = ListModel{
.data = std.ArrayList(TimeActivity).init(capy.internal.lasting_allocator),
};
var window = try capy.Window.init();
try window.set(capy.Column(.{}, .{
capy.Label(.{ .text = "Feed" }), // TODO: capy.Heading ?
InsertCard(),
// TODO: days labels / list categories
capy.ColumnList(.{}, &list_model),
}));

window.setTitle("Time Feed");
window.resize(250, 100);
window.show();

capy.runEventLoop();
}
15 changes: 13 additions & 2 deletions src/containers.zig
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ pub fn MarginLayout(peer: Callbacks, widgets: []Widget) void {
//const finalSize = Size.intersect(preferredSize, available);
_ = preferredSize;
const finalSize = available;

//peer.moveResize(peer.userdata, widgetPeer, 0, 0, finalSize.width, finalSize.height);
peer.moveResize(peer.userdata, widgetPeer, left, top, finalSize.width -| left -| right, finalSize.height -| top -| bottom);
}
Expand Down Expand Up @@ -359,7 +359,18 @@ pub const Container_Impl = struct {
.computingPreferredSize = false,
.layoutConfig = self.layoutConfig,
};
self.layout(callbacks, self.childrens.items);

var tempItems = std.ArrayList(Widget).init(self.childrens.allocator);
defer tempItems.deinit();
for (self.childrens.items) |child| {
if (child.isDisplayed()) {
tempItems.append(child) catch return;
} else {
peer.remove(child.peer.?);
}
}

self.layout(callbacks, tempItems.items);
self.relayouting.store(false, .SeqCst);
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/internal.zig
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,12 @@ pub fn Widgeting(comptime T: type) type {
.preferredSizeFn = getPreferredSizeWidget,
.setWidgetFn = setWidgetFn,
.getParentFn = widget_getParent,
.isDisplayedFn = isDisplayedFn,
};

pub const DataWrappers = struct {
opacity: DataWrapper(f32) = DataWrapper(f32).of(1.0),
displayed: DataWrapper(bool) = DataWrapper(bool).of(true),
name: DataWrapper(?[]const u8) = DataWrapper(?[]const u8).of(null),

/// The widget representing this component
Expand All @@ -91,6 +93,11 @@ pub fn Widgeting(comptime T: type) type {
component.dataWrappers.widget = widget;
}

pub fn isDisplayedFn(widget: *const Widget) bool {
const component = widget.as(T);
return component.dataWrappers.displayed.get();
}

pub fn deinitWidget(widget: *Widget) void {
const component = widget.as(T);
component.deinit();
Expand Down
4 changes: 3 additions & 1 deletion src/text.zig
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,12 @@ pub const TextField_Impl = struct {
_wrapperTextBlock: std.atomic.Atomic(bool) = std.atomic.Atomic(bool).init(false),

pub fn init(config: TextField_Impl.Config) TextField_Impl {
return TextField_Impl.init_events(TextField_Impl{
var field = TextField_Impl.init_events(TextField_Impl{
.text = StringDataWrapper.of(config.text),
.readOnly = DataWrapper(bool).of(config.readOnly),
});
field.setName(config.name);
return field;
}

/// Internal function used at initialization.
Expand Down
5 changes: 5 additions & 0 deletions src/widget.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub const Class = struct {
/// unknown. This function is thus called internally to pair the widget.
setWidgetFn: *const fn (widget: *Widget) void,
getParentFn: *const fn (widget: *const Widget) ?*Widget,
isDisplayedFn: *const fn (widget: *const Widget) bool,
// offset into a list of updater optional pointers
//updaters: []const usize,
};
Expand Down Expand Up @@ -53,6 +54,10 @@ pub const Widget = struct {
return self.class.getParentFn(self);
}

pub fn isDisplayed(self: *const Widget) bool {
return self.class.isDisplayedFn(self);
}

/// Asserts widget data is of type T
pub fn as(self: *const Widget, comptime T: type) *T {
if (std.debug.runtime_safety) {
Expand Down

0 comments on commit 62181cd

Please sign in to comment.