Skip to content

Commit 6418611

Browse files
feat(scene): add text node
1 parent db46e7b commit 6418611

File tree

4 files changed

+211
-1
lines changed

4 files changed

+211
-1
lines changed

src/phantom/scene/backends/fb.zig

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,34 @@ pub const NodeRect = @import("../nodes/rect.zig").NodeRect(struct {
6464
}).rect(vizops.vector.UsizeVector2.zero(), size, radius, buffer);
6565
}
6666
});
67+
68+
pub const NodeText = @import("../nodes/text.zig").NodeText(struct {
69+
const Self = @This();
70+
pub const Scene = @import("fb/scene.zig");
71+
72+
pub fn frame(self: *NodeText, scene: *Self.Scene, subscene: ?BaseScene.Sub) anyerror!void {
73+
const startPos: vizops.vector.UsizeVector2 = if (subscene) |sub| sub.pos else .{};
74+
75+
const view = try std.unicode.Utf8View.init(self.options.text);
76+
var viewIter = view.iterator();
77+
78+
var origin = vizops.vector.UsizeVector2.zero();
79+
80+
while (viewIter.nextCodepoint()) |cp| {
81+
const glyph = try self.options.font.lookupGlyph(cp);
82+
83+
try glyph.fb.blt(.to, scene.buffer, .{
84+
.destOffset = origin.add(startPos).add(.{
85+
glyph.bearing.value[0],
86+
-glyph.bearing.value[1],
87+
}),
88+
.size = glyph.size,
89+
});
90+
91+
origin = origin.add(.{
92+
glyph.advance.value[0],
93+
-glyph.advance.value[1],
94+
});
95+
}
96+
}
97+
});
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub const Scene = @import("headless/scene.zig");
22

33
pub const NodeArc = @import("../nodes/arc.zig").NodeArc(struct {});
4-
pub const NodeRect = @import("../nodes/rect.zig").NodeRect(struct {});
54
pub const NodeFrameBuffer = @import("../nodes/fb.zig").NodeFb(struct {});
5+
pub const NodeRect = @import("../nodes/rect.zig").NodeRect(struct {});
6+
pub const NodeText = @import("../nodes/text.zig").NodeText(struct {});

src/phantom/scene/nodes.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
pub const NodeArc = @import("nodes/arc.zig");
22
pub const NodeFrameBuffer = @import("nodes/fb.zig");
33
pub const NodeRect = @import("nodes/rect.zig");
4+
pub const NodeText = @import("nodes/text.zig");

src/phantom/scene/nodes/text.zig

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
const std = @import("std");
2+
const Allocator = std.mem.Allocator;
3+
const vizops = @import("vizops");
4+
const fonts = @import("../../fonts.zig");
5+
const Scene = @import("../base.zig");
6+
const Node = @import("../node.zig");
7+
8+
pub const Options = struct {
9+
font: *fonts.Font,
10+
text: []const u21,
11+
};
12+
13+
pub fn NodeText(comptime Impl: type) type {
14+
return struct {
15+
const Self = @This();
16+
const ImplState = if (@hasDecl(Impl, "State")) Impl.State else void;
17+
const ImplScene = Impl.Scene;
18+
19+
const State = struct {
20+
font: *fonts.Font,
21+
text: []const u21,
22+
implState: ImplState,
23+
24+
pub fn init(alloc: Allocator, options: Options) Allocator.Error!*State {
25+
const self = try alloc.create(State);
26+
self.* = .{
27+
.font = options.font,
28+
.text = try alloc.dupe(u21, options.text),
29+
.implState = if (ImplState != void) ImplState.init(alloc, options) else {},
30+
};
31+
return self;
32+
}
33+
34+
pub fn equal(self: *State, other: *State) bool {
35+
return std.simd.countTrues(@Vector(2, bool){
36+
self.font == other.font,
37+
std.mem.eql(u8, self.text, other.text),
38+
if (ImplState != void) self.implState.equal(other.implState) else true,
39+
}) == 2;
40+
}
41+
42+
pub fn deinit(self: *State, alloc: Allocator) void {
43+
if (ImplState != void) self.implState.deinit(alloc);
44+
alloc.free(self.text);
45+
alloc.destroy(self);
46+
}
47+
};
48+
49+
options: Options,
50+
node: Node,
51+
impl: Impl,
52+
53+
pub fn new(alloc: Allocator, id: ?usize, options: Options) Allocator.Error!*Node {
54+
const self = try alloc.create(Self);
55+
self.* = .{
56+
.options = .{
57+
.font = options.font,
58+
.text = try alloc.dupe(u21, options.text),
59+
},
60+
.node = .{
61+
.allocator = alloc,
62+
.ptr = self,
63+
.type = @typeName(Self),
64+
.id = id orelse @returnAddress(),
65+
.vtable = &.{
66+
.dupe = dupe,
67+
.state = state,
68+
.preFrame = preFrame,
69+
.frame = frame,
70+
.deinit = deinit,
71+
.format = format,
72+
},
73+
},
74+
.impl = undefined,
75+
};
76+
77+
if (@hasDecl(Impl, "new")) {
78+
try Impl.init(self);
79+
}
80+
return &self.node;
81+
}
82+
83+
fn stateEqual(ctx: *anyopaque, otherctx: *anyopaque) bool {
84+
const self: *State = @ptrCast(@alignCast(ctx));
85+
const other: *State = @ptrCast(@alignCast(otherctx));
86+
return self.equal(other);
87+
}
88+
89+
fn stateFree(ctx: *anyopaque, alloc: std.mem.Allocator) void {
90+
const self: *State = @ptrCast(@alignCast(ctx));
91+
self.deinit(alloc);
92+
}
93+
94+
fn calcSize(self: *Self) !vizops.vector.UsizeVector2 {
95+
const view = try std.unicode.Utf8View.init(self.options.text);
96+
var viewIter = view.iterator();
97+
98+
var width: usize = 0;
99+
var yMaxMin = vizops.vector.UsizeVector2.zero();
100+
101+
while (viewIter.nextCodepoint()) |cp| {
102+
const glyph = try self.options.font.lookupGlyph(cp);
103+
104+
width += glyph.advance.value[0];
105+
yMaxMin.value[0] = @max(yMaxMin.value[0], glyph.bearing.value[1]);
106+
yMaxMin.value[1] = @min(yMaxMin.value[1], glyph.bearing.value[1] - glyph.size.value[1]);
107+
}
108+
109+
return .{ .value = .{ width, yMaxMin.value[0] - yMaxMin.value[1] } };
110+
}
111+
112+
fn dupe(ctx: *anyopaque) anyerror!*Node {
113+
const self: *Self = @ptrCast(@alignCast(ctx));
114+
return try new(self.node.allocator, @returnAddress(), self.options);
115+
}
116+
117+
fn state(ctx: *anyopaque, frameInfo: Node.FrameInfo) anyerror!Node.State {
118+
const self: *Self = @ptrCast(@alignCast(ctx));
119+
return .{
120+
.size = try calcSize(self),
121+
.frame_info = frameInfo,
122+
.allocator = self.node.allocator,
123+
.ptr = try State.init(self.node.allocator, self.options),
124+
.ptrEqual = stateEqual,
125+
.ptrFree = stateFree,
126+
.type = @typeName(Self),
127+
};
128+
}
129+
130+
fn preFrame(ctx: *anyopaque, frameInfo: Node.FrameInfo, baseScene: *Scene) anyerror!Node.State {
131+
const self: *Self = @ptrCast(@alignCast(ctx));
132+
133+
if (@hasDecl(Impl, "preFrame")) {
134+
try Impl.preFrame(self, frameInfo, @ptrCast(@alignCast(baseScene.ptr)), baseScene.subscene);
135+
}
136+
137+
return .{
138+
.size = try calcSize(self),
139+
.frame_info = frameInfo,
140+
.allocator = self.node.allocator,
141+
.ptr = try State.init(self.node.allocator, self.options),
142+
.ptrEqual = stateEqual,
143+
.ptrFree = stateFree,
144+
.type = @typeName(Self),
145+
};
146+
}
147+
148+
fn frame(ctx: *anyopaque, baseScene: *Scene) anyerror!void {
149+
const self: *Self = @ptrCast(@alignCast(ctx));
150+
151+
if (@hasDecl(Impl, "frame")) {
152+
try Impl.frame(self, @ptrCast(@alignCast(baseScene.ptr)), baseScene.subscene);
153+
}
154+
}
155+
156+
fn deinit(ctx: *anyopaque) void {
157+
const self: *Self = @ptrCast(@alignCast(ctx));
158+
159+
if (@hasDecl(Impl, "deinit")) {
160+
Impl.deinit(self);
161+
}
162+
163+
self.node.allocator.free(self.options.text);
164+
self.node.allocator.destroy(self);
165+
}
166+
167+
fn format(ctx: *anyopaque, _: ?Allocator) anyerror!std.ArrayList(u8) {
168+
const self: *Self = @ptrCast(@alignCast(ctx));
169+
170+
var output = std.ArrayList(u8).init(self.node.allocator);
171+
errdefer output.deinit();
172+
173+
try output.writer().print("{{ .font = {}, .text = \"{s}\" }}", .{ self.options.font, self.options.text });
174+
return output;
175+
}
176+
};
177+
}

0 commit comments

Comments
 (0)