DVUI

textRunPopulate

Populate the text_run node with character position and word length details.

Parameters

#
self:*AccessKit
*AccessKit
text:[]const u8
[]const u8
opts:TextRunOptions
TextRunOptions
text_info:*std.MultiArrayList(CharPositionInfo)
*std.MultiArrayList(CharPositionInfo)
r:dvui.Rect.Physical
dvui.Rect.Physical

Source

Implementation

#
pub fn textRunPopulate(
    self: *AccessKit,
    text: []const u8,
    opts: TextRunOptions,
    text_info: *std.MultiArrayList(CharPositionInfo),
    r: dvui.Rect.Physical,
) void {
    const window: *dvui.Window = @alignCast(@fieldParentPtr("accesskit", self));

    // Word lengths include all punctuation for the word. e.g. `abc"$.` has a length of 6.
    var word_starts: std.ArrayList(u8) = .empty;
    defer word_starts.deinit(window.arena());

    var prev_char_wordbreak: bool = self.text_run_prev_wordbreak or opts.node_parent_id != self.text_run_prev_parent_id;
    for (text, 0..) |ch, i| {
        if (std.mem.findScalar(u8, dvui.TextLayoutWidget.word_breaks, ch) == null) {
            if (prev_char_wordbreak) {
                // AK TODO: If a line is more than 255 characters, then it needs to be broken up into 2 text runs.
                word_starts.append(window.arena(), @min(i, std.math.maxInt(u8))) catch {};
                prev_char_wordbreak = false;
            }
        } else {
            prev_char_wordbreak = true;
        }
    }
    self.text_run_prev_wordbreak = prev_char_wordbreak;
    self.text_run_prev_parent_id = opts.node_parent_id;

    const ak_node = self.nodes.get(opts.node_id) orelse {
        log.debug("textRunPopulate called for non-existent node_id {x}:{d} with parent {x}:{d}\n", .{ opts.node_id, opts.node_id, opts.node_parent_id, opts.node_parent_id });
        return;
    };
    AccessKit.nodeSetRole(ak_node, Role.text_run.asU8());
    // Make sure text run is at least 1 pixel wide to cater for newlines.
    nodeSetBounds(ak_node, .{ .x0 = r.x, .y0 = r.y, .x1 = r.x + @max(r.w, 1), .y1 = r.y + r.h });
    nodeSetValueWithLength(ak_node, text.ptr, text.len);
    nodeSetCharacterLengths(ak_node, text_info.len, text_info.items(.l).ptr);
    nodeSetCharacterWidths(ak_node, text_info.len, text_info.items(.w).ptr);
    nodeSetCharacterPositions(ak_node, text_info.len, text_info.items(.x).ptr);
    nodeSetWordStarts(ak_node, word_starts.items.len, word_starts.items.ptr);
    nodeSetTextDirection(ak_node, AccessKit.TextDirection.left_to_right);
    if (self.text_runs.items.len > 0) {
        const prev_run = self.text_runs.items[self.text_runs.items.len - 1];
        const prev_node = self.nodes.get(prev_run.node_id) orelse unreachable;

        // text_runs on the same line should be linked together.
        if (prev_run.node_parent_id == opts.node_parent_id and
            prev_run.line == opts.line)
        {
            nodeSetPreviousOnLine(ak_node, prev_run.node_id.asU64());
            nodeSetNextOnLine(prev_node, opts.node_id.asU64());
        }
    }
    self.text_runs.append(window.gpa, opts) catch {}; // If text run can't be added, selection actions will fail this frame.
}