feat: Partially implement new text layout & rendering system: Spans
Chunks are NYI
This commit is contained in:
parent
3446418b49
commit
9a2080673c
|
@ -1,6 +1,7 @@
|
|||
//! Style options for the button widget.
|
||||
const BackgroundStyle = @import("../background_style.zig").BackgroundStyle;
|
||||
const Color = @import("../Color.zig");
|
||||
const Style = @import("../text/Style.zig");
|
||||
|
||||
background: BackgroundStyle,
|
||||
|
||||
|
@ -9,7 +10,4 @@ background_hovered: BackgroundStyle,
|
|||
/// Spacing between the text and the button's outer bounds.
|
||||
padding: usize,
|
||||
|
||||
/// Font size to use for the label.
|
||||
font_size: usize,
|
||||
|
||||
text_color: Color,
|
||||
font_style: Style,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//! This attreebute may be used in a widget that needs to render text to chose a font to pass to the
|
||||
//! painter. A user should set this if text rendering is required.
|
||||
const Font = @import("../font.zig").Font;
|
||||
const Font = @import("../text/font.zig").Font;
|
||||
|
||||
font: *Font,
|
||||
|
|
96
src/font.zig
96
src/font.zig
|
@ -1,96 +0,0 @@
|
|||
const std = @import("std");
|
||||
const statspatch = @import("statspatch");
|
||||
|
||||
const zenolith = @import("main.zig");
|
||||
|
||||
const Size = @import("layout/Size.zig");
|
||||
|
||||
fn FontPrototype(comptime Self: type) type {
|
||||
std.debug.assert(Self == Font);
|
||||
|
||||
return struct {
|
||||
/// Performs layout on the given string of UTF-8 encoded text.
|
||||
/// The wrap parameter controls how text should be wrapped. If set to none, no text wrapping
|
||||
/// should be done, if set to word or glyph it should wrap whole words or whole glyphs
|
||||
/// respectively. The size parameter is in px. Platforms where this isn't applicable
|
||||
/// should try to make the font size match closely.
|
||||
pub fn layout(
|
||||
self: *Self,
|
||||
text: []const u8,
|
||||
size: usize,
|
||||
wrap: TextWrap,
|
||||
) !Chunk {
|
||||
return try statspatch.implcall(
|
||||
self,
|
||||
.ptr,
|
||||
"layout",
|
||||
anyerror!Chunk,
|
||||
.{ text, size, wrap },
|
||||
);
|
||||
}
|
||||
|
||||
/// Free resources associated with this font.
|
||||
pub fn deinit(self: Self) void {
|
||||
statspatch.implcallOptional(self, .self, "deinit", void, .{}) orelse {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn ChunkPrototype(comptime Self: type) type {
|
||||
std.debug.assert(Self == Chunk);
|
||||
|
||||
return struct {
|
||||
/// Returns the total size of the chunk.
|
||||
/// This is often used for layout to determine the size of text.
|
||||
pub fn getSize(self: Self) Size {
|
||||
return statspatch.implcall(self, .self, "getSize", Size, .{});
|
||||
}
|
||||
|
||||
/// Free resources associated with this chunk.
|
||||
pub fn deinit(self: Self) void {
|
||||
return statspatch.implcallOptional(self, .self, "deinit", void, .{}) orelse {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Determines how text should be wrapped when doing text layout.
|
||||
pub const TextWrap = union(enum) {
|
||||
/// Don't wrap text.
|
||||
none,
|
||||
/// Wrap words after the given maximum width.
|
||||
word: usize,
|
||||
/// Wrap individual glyphs after the given maximum width.
|
||||
glyph: usize,
|
||||
};
|
||||
|
||||
pub const font_impls = impls: {
|
||||
var implementations: []const type = &.{};
|
||||
|
||||
for (zenolith.platform_impls) |pi| {
|
||||
if (@hasDecl(pi, "Font")) {
|
||||
implementations = implementations ++ &[1]type{pi.Font};
|
||||
}
|
||||
}
|
||||
|
||||
break :impls implementations;
|
||||
};
|
||||
|
||||
pub const chunk_impls = impls: {
|
||||
var implementations: [font_impls.len]type = undefined;
|
||||
for (&implementations, font_impls) |*chunk, font| {
|
||||
chunk.* = font.Chunk;
|
||||
}
|
||||
|
||||
break :impls &implementations;
|
||||
};
|
||||
|
||||
/// The font type is a backend-specific statspatch type which encapsulates a font.
|
||||
/// This is used with the painter API to draw text.
|
||||
/// Platforms should specify their font implementation by declaring a Font declaration.
|
||||
pub const Font = statspatch.StatspatchType(FontPrototype, void, font_impls);
|
||||
|
||||
/// A chunk represents a laid-out piece of text. This is created by the font using the layout
|
||||
/// function. It typically contains information about the positions of individual glyphs, although
|
||||
/// this is up to the backend.
|
||||
/// Fonts should specify their chunk implementation by declaring a Chunk declaration.
|
||||
pub const Chunk = statspatch.StatspatchType(ChunkPrototype, void, chunk_impls);
|
|
@ -1,11 +1,13 @@
|
|||
test {
|
||||
_ = Constraints;
|
||||
_ = Offset;
|
||||
_ = Position;
|
||||
_ = Rectangle;
|
||||
_ = Size;
|
||||
}
|
||||
|
||||
pub const Constraints = @import("layout/Constraints.zig");
|
||||
pub const Offset = @import("layout/Offset.zig");
|
||||
pub const Position = @import("layout/Position.zig");
|
||||
pub const Rectangle = @import("layout/Rectangle.zig");
|
||||
pub const Size = @import("layout/Size.zig");
|
||||
|
|
4
src/layout/Offset.zig
Normal file
4
src/layout/Offset.zig
Normal file
|
@ -0,0 +1,4 @@
|
|||
//! An offset represents a signed, 2D Vector that can be added onto Positions.
|
||||
|
||||
x: isize,
|
||||
y: isize,
|
|
@ -1,5 +1,6 @@
|
|||
//! A 2-Dimensional position, typically used to denote where a widget is relative to the top left.
|
||||
const Size = @import("Size.zig");
|
||||
const Offset = @import("Offset.zig");
|
||||
|
||||
x: usize,
|
||||
y: usize,
|
||||
|
@ -36,3 +37,12 @@ pub inline fn size(self: Position) Size {
|
|||
.height = self.y,
|
||||
};
|
||||
}
|
||||
|
||||
/// Offsets this Position by the given Offset.
|
||||
/// The caller asserts that this will not over or underflow.
|
||||
pub inline fn offset(self: Position, by: Offset) Position {
|
||||
return .{
|
||||
.x = self.x +% @as(usize, @bitCast(by.x)),
|
||||
.y = self.y +% @as(usize, @bitCast(by.y)),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@ const root = @import("root");
|
|||
test {
|
||||
_ = attreebute;
|
||||
_ = backevent;
|
||||
_ = font;
|
||||
_ = layout;
|
||||
_ = painter;
|
||||
_ = platform;
|
||||
_ = text;
|
||||
_ = texture;
|
||||
_ = treevent;
|
||||
_ = util;
|
||||
|
@ -19,10 +19,10 @@ test {
|
|||
|
||||
pub const attreebute = @import("attreebute.zig");
|
||||
pub const backevent = @import("backevent.zig");
|
||||
pub const font = @import("font.zig");
|
||||
pub const layout = @import("layout.zig");
|
||||
pub const painter = @import("painter.zig");
|
||||
pub const platform = @import("platform.zig");
|
||||
pub const text = @import("text.zig");
|
||||
pub const texture = @import("texture.zig");
|
||||
pub const treevent = @import("treevent.zig");
|
||||
pub const util = @import("util.zig");
|
||||
|
|
|
@ -12,12 +12,12 @@ const std = @import("std");
|
|||
const statspatch = @import("statspatch");
|
||||
|
||||
const zenolith = @import("main.zig");
|
||||
const font = @import("font.zig");
|
||||
|
||||
const Color = @import("Color.zig");
|
||||
const Position = @import("layout/Position.zig");
|
||||
const Rectangle = @import("layout/Rectangle.zig");
|
||||
const Size = @import("layout/Size.zig");
|
||||
const Span = @import("text/Span.zig");
|
||||
const Texture = @import("texture.zig").Texture;
|
||||
|
||||
fn Prototype(comptime Self: type) type {
|
||||
|
@ -138,22 +138,10 @@ fn Prototype(comptime Self: type) type {
|
|||
);
|
||||
}
|
||||
|
||||
/// Draw a given Chunk of laid-out text, typically obtained through the font at the given
|
||||
/// position. The caller asserts that the given chunk is compatible with this painter's
|
||||
/// underlying platform.
|
||||
pub fn text(
|
||||
self: *Self,
|
||||
pos: Position,
|
||||
chunk: font.Chunk,
|
||||
color: Color,
|
||||
) !void {
|
||||
return statspatch.implcall(
|
||||
self,
|
||||
.ptr,
|
||||
"text",
|
||||
anyerror!void,
|
||||
.{ pos, chunk, color },
|
||||
);
|
||||
/// Draw the given span of text at the given position.
|
||||
/// The caller asserts that the font of the span is from the same platform as this painter.
|
||||
pub fn span(self: *Self, pos: Position, text_span: Span) !void {
|
||||
return statspatch.implcall(self, .ptr, "span", anyerror!void, .{ pos, text_span });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
11
src/text.zig
Normal file
11
src/text.zig
Normal file
|
@ -0,0 +1,11 @@
|
|||
test {
|
||||
_ = Font;
|
||||
_ = Glyph;
|
||||
_ = Span;
|
||||
_ = Style;
|
||||
}
|
||||
|
||||
pub const Font = @import("text/font.zig").Font;
|
||||
pub const Glyph = @import("text/Glyph.zig");
|
||||
pub const Span = @import("text/Span.zig");
|
||||
pub const Style = @import("text/Style.zig");
|
23
src/text/Glyph.zig
Normal file
23
src/text/Glyph.zig
Normal file
|
@ -0,0 +1,23 @@
|
|||
//! A glyph represents the smallet possible unit of text with positioning information.
|
||||
//! It is used for text layout and rendering.
|
||||
const Size = @import("../layout/Size.zig");
|
||||
const Offset = @import("../layout/Offset.zig");
|
||||
|
||||
/// The unicode codepoint this glyph corresponds to.
|
||||
/// This is intentionally only one codepoint. While Unicode glyphs an consist of up to 7
|
||||
/// codepoints (citation needed), these are not supported. Support might be implemented in the
|
||||
/// future, but this is currently not planned.
|
||||
codepoint: u21,
|
||||
|
||||
/// The size of this glyph. This is typically the boundig box of the glyph as drawn.
|
||||
size: Size,
|
||||
|
||||
/// The offset of the character's top-left corner from the baseline of the text span.
|
||||
/// When performing span layout, this is added onto the position of the cursor.
|
||||
/// This being 0/0 will thus result in the glyph being aligned below the baseline.
|
||||
/// This is typically has a negative Y offset to align the glyph above the baseline.
|
||||
bearing: Offset,
|
||||
|
||||
/// How much the cursor should move to the right after this glyph.
|
||||
advance: usize,
|
||||
|
128
src/text/Span.zig
Normal file
128
src/text/Span.zig
Normal file
|
@ -0,0 +1,128 @@
|
|||
//! A span is a one-line piece of text. It consists of glyphs and performs single-line layout on them.
|
||||
//! It also has information on the font, style, color as well as bounding boxes.
|
||||
const std = @import("std");
|
||||
|
||||
const Font = @import("font.zig").Font;
|
||||
const Glyph = @import("Glyph.zig");
|
||||
const Position = @import("../layout/Position.zig");
|
||||
const Size = @import("../layout/Size.zig");
|
||||
const Style = @import("Style.zig");
|
||||
|
||||
glyphs: std.MultiArrayList(PositionedGlyph) = .{},
|
||||
font: *Font,
|
||||
style: Style,
|
||||
|
||||
/// The Y-coordinate of the baseline of this span relative to it's top.
|
||||
/// The chunker uses this for aligning spans.
|
||||
baseline_y: usize = 0,
|
||||
|
||||
const Span = @This();
|
||||
|
||||
pub const PositionedGlyph = struct {
|
||||
glyph: Glyph,
|
||||
position: Position,
|
||||
};
|
||||
|
||||
pub const InitOptions = struct {
|
||||
font: *Font,
|
||||
style: Style = .{},
|
||||
text: []const u8,
|
||||
};
|
||||
|
||||
/// Initializes this span with some text. This performs layout.
|
||||
/// The caller must call deinit when done to free memory.
|
||||
pub fn init(alloc: std.mem.Allocator, opts: InitOptions) !Span {
|
||||
var self = Span{
|
||||
.font = opts.font,
|
||||
.style = opts.style,
|
||||
};
|
||||
|
||||
try self.updateGlyphs(alloc, .{ .text = opts.text });
|
||||
self.layout();
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
pub const UpdateGlyphsOptions = struct {
|
||||
font: ?*Font = null,
|
||||
style: ?Style = null,
|
||||
text: []const u8,
|
||||
};
|
||||
|
||||
/// Update the glyphs in this span to those of the given string.
|
||||
/// Note that this does not recalculate positions! The caller must assure that `layout` is called
|
||||
/// after this.
|
||||
pub fn updateGlyphs(
|
||||
self: *Span,
|
||||
alloc: std.mem.Allocator,
|
||||
opts: UpdateGlyphsOptions,
|
||||
) !void {
|
||||
if (opts.style) |style| self.style = style;
|
||||
if (opts.font) |font| self.font = font;
|
||||
|
||||
self.glyphs.shrinkRetainingCapacity(0);
|
||||
|
||||
var iter = std.unicode.Utf8Iterator{ .i = 0, .bytes = opts.text };
|
||||
while (iter.nextCodepoint()) |codepoint| {
|
||||
try self.glyphs.append(alloc, .{
|
||||
.glyph = try self.font.getGlyph(codepoint, self.style),
|
||||
.position = Position.zero,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Positions the glyphs of the span and sets baseline_y.
|
||||
pub fn layout(self: *Span) void {
|
||||
const glyphslice = self.glyphs.slice();
|
||||
|
||||
// We start at the middle of the "coordinate system" here to leave as much space as possible
|
||||
// in all directions. This is later compensated for.
|
||||
var cursor = Position{
|
||||
.x = std.math.maxInt(usize) / 2,
|
||||
.y = std.math.maxInt(usize) / 2,
|
||||
};
|
||||
|
||||
// Minimum position coordinate of the glyphs.
|
||||
var min_pos = Position{
|
||||
.x = std.math.maxInt(usize),
|
||||
.y = std.math.maxInt(usize),
|
||||
};
|
||||
|
||||
for (glyphslice.items(.glyph), glyphslice.items(.position)) |glyph, *position| {
|
||||
position.* = cursor.offset(glyph.bearing);
|
||||
|
||||
if (position.x < min_pos.x) min_pos.x = position.x;
|
||||
if (position.y < min_pos.y) min_pos.y = position.y;
|
||||
|
||||
cursor.x += glyph.advance;
|
||||
}
|
||||
|
||||
for (glyphslice.items(.position)) |*pos| {
|
||||
pos.* = pos.sub(min_pos);
|
||||
}
|
||||
|
||||
self.baseline_y = cursor.y - min_pos.y;
|
||||
}
|
||||
|
||||
/// Free owned data. Caller must provide the same allocator as to init!
|
||||
pub fn deinit(self_: Span, alloc: std.mem.Allocator) void {
|
||||
var self = self_;
|
||||
self.glyphs.deinit(alloc);
|
||||
}
|
||||
|
||||
/// This determines the size of the span as rendered. This fully contains the glyphs.
|
||||
pub fn renderSize(self: Span) Size {
|
||||
var size = Size.zero;
|
||||
|
||||
const glyphslice = self.glyphs.slice();
|
||||
|
||||
for (glyphslice.items(.glyph), glyphslice.items(.position)) |glyph, pos| {
|
||||
const xmax = glyph.size.width + pos.x;
|
||||
const ymax = glyph.size.height + pos.y;
|
||||
|
||||
if (xmax > size.width) size.width = xmax;
|
||||
if (ymax > size.height) size.height = ymax;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
10
src/text/Style.zig
Normal file
10
src/text/Style.zig
Normal file
|
@ -0,0 +1,10 @@
|
|||
//! Style is text theming information applied to a font. It is contained within spans.
|
||||
const Color = @import("../Color.zig");
|
||||
|
||||
/// This is the Size of the font in pixels. Some backends (namely, TUI-based ones) may not support this.
|
||||
size: usize = 24,
|
||||
|
||||
bold: bool = false,
|
||||
italic: bool = false,
|
||||
underlined: bool = false,
|
||||
color: Color = Color.white(0xff),
|
51
src/text/font.zig
Normal file
51
src/text/font.zig
Normal file
|
@ -0,0 +1,51 @@
|
|||
const std = @import("std");
|
||||
const statspatch = @import("statspatch");
|
||||
|
||||
const zenolith = @import("../main.zig");
|
||||
|
||||
const Size = @import("../layout/Size.zig");
|
||||
const Style = @import("../text/Style.zig");
|
||||
const Glyph = @import("Glyph.zig");
|
||||
|
||||
fn FontPrototype(comptime Self: type) type {
|
||||
std.debug.assert(Self == Font);
|
||||
|
||||
return struct {
|
||||
/// For a given font size in pixels, returns the offset between lines.
|
||||
pub fn yOffset(self: *Self, size: usize) usize {
|
||||
return statspatch.implcall(self, .ptr, "yOffset", usize, .{size});
|
||||
}
|
||||
|
||||
pub fn getGlyph(self: *Self, codepoint: u21, style: Style) !Glyph {
|
||||
return try statspatch.implcall(
|
||||
self,
|
||||
.ptr,
|
||||
"getGlyph",
|
||||
anyerror!Glyph,
|
||||
.{ codepoint, style },
|
||||
);
|
||||
}
|
||||
|
||||
/// Free resources associated with this font.
|
||||
pub fn deinit(self: *Self) void {
|
||||
statspatch.implcallOptional(self, .ptr, "deinit", void, .{}) orelse {};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const font_impls = impls: {
|
||||
var implementations: []const type = &.{};
|
||||
|
||||
for (zenolith.platform_impls) |pi| {
|
||||
if (@hasDecl(pi, "Font")) {
|
||||
implementations = implementations ++ &[1]type{pi.Font};
|
||||
}
|
||||
}
|
||||
|
||||
break :impls implementations;
|
||||
};
|
||||
|
||||
/// The font type is a backend-specific statspatch type which encapsulates a font.
|
||||
/// This is used with the painter API to draw text.
|
||||
/// Platforms should specify their font implementation by declaring a Font declaration.
|
||||
pub const Font = statspatch.StatspatchType(FontPrototype, void, font_impls);
|
|
@ -175,7 +175,7 @@ test "widget" {
|
|||
});
|
||||
|
||||
widget.data.attreebutes = AttreebuteMap.init();
|
||||
_ = try widget.data.attreebutes.?.put(std.testing.allocator, u32, 42);
|
||||
(try widget.data.attreebutes.?.mod(std.testing.allocator, u32)).* = 42;
|
||||
|
||||
try std.testing.expectEqual(@as(u32, 42), widget.getAttreebute(u32).?.*);
|
||||
}
|
||||
|
|
|
@ -4,15 +4,15 @@ const std = @import("std");
|
|||
|
||||
const attreebute = @import("../attreebute.zig");
|
||||
const backevent = @import("../backevent.zig");
|
||||
const font = @import("../font.zig");
|
||||
const treev = @import("../treevent.zig");
|
||||
const layout = @import("../layout.zig");
|
||||
|
||||
const Color = @import("../Color.zig");
|
||||
const Span = @import("../text/Span.zig");
|
||||
const Widget = @import("../widget.zig").Widget;
|
||||
|
||||
label_str: []const u8,
|
||||
chunk: ?font.Chunk,
|
||||
span: ?Span,
|
||||
hovered: bool,
|
||||
|
||||
const Button = @This();
|
||||
|
@ -20,7 +20,7 @@ const Button = @This();
|
|||
pub fn init(alloc: std.mem.Allocator, label: []const u8) !*Widget {
|
||||
const self = Button{
|
||||
.label_str = label,
|
||||
.chunk = null,
|
||||
.span = null,
|
||||
.hovered = false,
|
||||
};
|
||||
|
||||
|
@ -28,9 +28,7 @@ pub fn init(alloc: std.mem.Allocator, label: []const u8) !*Widget {
|
|||
}
|
||||
|
||||
pub fn deinit(self: *Button, selfw: *Widget) void {
|
||||
_ = selfw;
|
||||
if (self.chunk) |chunk|
|
||||
chunk.deinit();
|
||||
if (self.span) |span| span.deinit(selfw.data.allocator);
|
||||
}
|
||||
|
||||
pub fn treevent(self: *Button, selfw: *Widget, tv: anytype) !void {
|
||||
|
@ -38,22 +36,30 @@ pub fn treevent(self: *Button, selfw: *Widget, tv: anytype) !void {
|
|||
treev.LayoutSize => {
|
||||
const style = selfw.getAttreebute(attreebute.ButtonStyle) orelse
|
||||
@panic("The Button widget must have the ButtonStyle attreebute set!");
|
||||
if (self.chunk == null) {
|
||||
if (self.span == null) {
|
||||
const curfont = (selfw.getAttreebute(attreebute.CurrentFont) orelse
|
||||
@panic("The Button widget must have the CurrentFont attreebute set!")).font;
|
||||
self.chunk = try curfont.layout(self.label_str, style.font_size, .none);
|
||||
self.span = try Span.init(selfw.data.allocator, .{
|
||||
.font = curfont,
|
||||
.style = style.font_style,
|
||||
.text = self.label_str,
|
||||
});
|
||||
}
|
||||
|
||||
selfw.data.size = self.chunk.?.getSize().add(layout.Size.two(style.padding * 2));
|
||||
selfw.data.size = self.span.?.renderSize().add(layout.Size.two(style.padding * 2));
|
||||
},
|
||||
|
||||
treev.Draw => {
|
||||
const style = selfw.getAttreebute(attreebute.ButtonStyle) orelse
|
||||
@panic("The Button widget must have the ButtonStyle attreebute set!");
|
||||
if (self.chunk == null) {
|
||||
if (self.span == null) {
|
||||
const curfont = (selfw.getAttreebute(attreebute.CurrentFont) orelse
|
||||
@panic("The Button widget must have the CurrentFont attreebute set!")).font;
|
||||
self.chunk = try curfont.layout(self.label_str, style.font_size, .none);
|
||||
self.span = try Span.init(selfw.data.allocator, .{
|
||||
.font = curfont,
|
||||
.style = style.font_style,
|
||||
.text = self.label_str,
|
||||
});
|
||||
}
|
||||
|
||||
try (if (self.hovered) style.background_hovered else style.background).drawBackground(
|
||||
|
@ -61,10 +67,9 @@ pub fn treevent(self: *Button, selfw: *Widget, tv: anytype) !void {
|
|||
.{ .pos = selfw.data.position, .size = selfw.data.size },
|
||||
);
|
||||
|
||||
try tv.painter.text(
|
||||
try tv.painter.span(
|
||||
selfw.data.position.add(layout.Position.two(style.padding)),
|
||||
self.chunk.?,
|
||||
style.text_color,
|
||||
self.span.?,
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
@ -1,66 +1,41 @@
|
|||
//! A simple text label with a given color and size.
|
||||
// TODO: use CurrentFont
|
||||
// TODO: free self.chunk
|
||||
const std = @import("std");
|
||||
|
||||
const font = @import("../font.zig");
|
||||
const font = @import("../text/font.zig");
|
||||
const treev = @import("../treevent.zig");
|
||||
const layout = @import("../layout.zig");
|
||||
|
||||
const Color = @import("../Color.zig");
|
||||
const Span = @import("../text/Span.zig");
|
||||
const Widget = @import("../widget.zig").Widget;
|
||||
|
||||
font: *font.Font,
|
||||
chunk: font.Chunk,
|
||||
color: Color,
|
||||
size: usize,
|
||||
span: Span,
|
||||
|
||||
const Label = @This();
|
||||
|
||||
pub const LabelOptions = struct {
|
||||
alloc: std.mem.Allocator,
|
||||
font: *font.Font,
|
||||
text: []const u8,
|
||||
size: usize = 32,
|
||||
color: Color = Color.white(0xff),
|
||||
};
|
||||
|
||||
pub const UpdateOptions = struct {
|
||||
text: []const u8,
|
||||
font: ?*font.Font = null,
|
||||
size: ?usize = null,
|
||||
color: ?Color = null,
|
||||
};
|
||||
|
||||
pub fn init(opts: LabelOptions) !*Widget {
|
||||
pub fn init(alloc: std.mem.Allocator, opts: Span.InitOptions) !*Widget {
|
||||
const self = Label{
|
||||
.font = opts.font,
|
||||
.chunk = try opts.font.layout(opts.text, opts.size, .none),
|
||||
.color = opts.color,
|
||||
.size = opts.size,
|
||||
.span = try Span.init(alloc, opts),
|
||||
};
|
||||
errdefer self.chunk.deinit();
|
||||
errdefer self.span.deinit(alloc);
|
||||
|
||||
return try Widget.init(opts.alloc, self);
|
||||
return try Widget.init(alloc, self);
|
||||
}
|
||||
|
||||
pub fn update(self: *Label, opts: UpdateOptions) !void {
|
||||
if (opts.font) |f| self.font = f;
|
||||
if (opts.size) |s| self.size = s;
|
||||
if (opts.color) |col| self.color = col;
|
||||
|
||||
const oldchunk = self.chunk;
|
||||
self.chunk = try self.font.layout(opts.text, self.size, .none);
|
||||
defer oldchunk.deinit();
|
||||
pub fn deinit(self: *Label, selfw: *Widget) void {
|
||||
self.span.deinit(selfw.data.allocator);
|
||||
}
|
||||
|
||||
pub fn treevent(self: *Label, selfw: *Widget, tv: anytype) !void {
|
||||
switch (@TypeOf(tv)) {
|
||||
treev.LayoutSize => {
|
||||
selfw.data.size = tv.constraints.clamp(self.chunk.getSize());
|
||||
selfw.data.size = tv.constraints.clamp(self.span.renderSize());
|
||||
},
|
||||
treev.Draw => {
|
||||
try tv.painter.text(selfw.data.position, self.chunk, self.color);
|
||||
try tv.painter.span(selfw.data.position, self.span);
|
||||
},
|
||||
else => try tv.dispatch(selfw),
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue