addTextDone
Parameters
- self:*TextLayoutWidget
- *TextLayoutWidget
- opts:Options
- Options
Source
Implementation
pub fn addTextDone(self: *TextLayoutWidget, opts: Options) void {
if (self.add_text_done) {
dvui.log.debug("TextLayoutWidget {x} addTextDone() called multiple times", .{self.data().id});
}
self.add_text_done = true;
self.checkAscent();
if (self.cache_layout and self.byte_heights.len > 0) {
var edit_height: f32 = undefined;
if (self.byte_height_after_idx) |i| {
// this is not the final one
const bh = self.byte_heights[i];
// we expected to end at bh.height without edits, this is the extra
// height the edits gave (might be negative)
edit_height = self.insert_pt.y - bh.height;
const edit_bytes: i64 = @as(i64, @intCast(self.bytes_seen)) - @as(i64, @intCast(bh.byte));
const edit_lines: i64 = @as(i64, @intCast(self.line)) - @as(i64, @intCast(bh.line));
// these are the height and bytes we are skipping
const extra_height = self.byte_heights[self.byte_heights.len - 1].height - bh.height;
const extra_bytes = self.byte_heights[self.byte_heights.len - 1].byte - bh.byte;
self.bytes_seen += extra_bytes;
// set min height
const end_size = self.data().options.padSize(.{ .h = self.insert_pt.y + extra_height });
self.data().min_size.h = @max(self.data().min_size.h, end_size.h);
// adjust for edits
for (self.byte_heights[i..self.byte_heights.len]) |*bhh| {
bhh.height += edit_height;
if (edit_bytes >= 0) {
bhh.byte += @intCast(edit_bytes);
} else {
bhh.byte -= @intCast(-edit_bytes);
}
if (edit_lines >= 0) {
bhh.line += @intCast(edit_lines);
} else {
bhh.line -= @intCast(-edit_lines);
}
}
// copy all the ByteHeights we skipped, but not the final one
self.byte_heights_new.appendSlice(dvui.currentWindow().arena(), self.byte_heights[i .. self.byte_heights.len - 1]) catch {};
var k: usize = self.line_ascents_idx;
while (k < self.line_ascents.len and self.line_ascents[k].line < self.line) k += 1;
for (self.line_ascents[k..self.line_ascents.len]) |*la| {
if (edit_lines >= 0) {
la.line += @intCast(edit_lines);
} else {
la.line -= @intCast(-edit_lines);
}
}
self.line_ascents_new.appendSlice(dvui.currentWindow().arena(), self.line_ascents[k..self.line_ascents.len]) catch {};
} else {
// use the final one
var bh = &self.byte_heights[self.byte_heights.len - 1];
// we expected to end at bh.height without edits, this is the extra
// height the edits gave (might be negative)
const os = self.data().options;
const contentMinSize = self.data().min_size.padNeg(os.paddingGet()).padNeg(os.borderGet()).padNeg(os.marginGet());
edit_height = contentMinSize.h - bh.height;
// adjust previous height for sanity check below
bh.height += edit_height;
}
std.debug.assert(self.cache_layout_bytes_seen == self.bytes_seen);
//std.debug.print("edit_height {d}\n", .{edit_height});
// TODO: if edit_height is negative, we might not render some text for a frame - need to scan further in byte_heights until we find one that is not visible
}
const os = self.data().options;
const contentMinSize = self.data().min_size.padNeg(os.paddingGet()).padNeg(os.borderGet()).padNeg(os.marginGet());
self.byte_heights_new.append(dvui.currentWindow().arena(), .{ .byte = self.bytes_seen, .height = contentMinSize.h, .line = self.line }) catch {};
if (self.cache_layout and self.byte_heights.len > 0) {
// sanity check
const old = self.byte_heights[self.byte_heights.len - 1].height;
const new = self.byte_heights_new.items[self.byte_heights_new.items.len - 1].height;
if (new < (old - 1.0) or new > (old + 1.0)) {
dvui.logError(@src(), error.CacheLayoutError, "the height of the processed text changed by {d}, cache_layout should have been false this frame", .{new - old});
self.byte_heights_new.clearAndFree(dvui.currentWindow().arena());
}
}
//std.debug.print("final height: {d} at {d}\n", .{ contentMinSize.h, self.bytes_seen });
//const crs = self.data().contentRectScale();
//for (self.byte_heights_new.items) |bhn| {
// //std.debug.print("bh: {d} - {d}\n", .{ bhn.byte, bhn.height });
// const p: dvui.Path = .{ .points = &.{
// crs.pointToPhysical(.{ .x = 0, .y = bhn.height }),
// crs.pointToPhysical(.{ .x = 100, .y = bhn.height }),
// } };
// p.stroke(.{ .thickness = 1, .color = .red });
//}
self.selection.cursor = @min(self.selection.cursor, self.bytes_seen);
self.selection.start = @min(self.selection.start, self.bytes_seen);
self.selection.end = @min(self.selection.end, self.bytes_seen);
const options = self.data().options.override(opts);
const text_height = options.fontGet().textHeight();
if (!self.cursor_seen) {
self.cursor_rect = Rect{ .x = self.insert_pt.x, .y = self.insert_pt.y, .w = 1, .h = text_height };
self.cursorSeen();
}
if (self.copy_sel) |_| {
// we are copying to clipboard and never stopped
dvui.clipboardTextSet(self.copy_slice orelse "");
self.copy_sel = null;
if (self.copy_slice) |cs| {
dvui.currentWindow().arena().free(cs);
}
self.copy_slice = null;
}
// handle selection movement
// - this logic must work even if addText() is never called
switch (self.sel_move) {
.none => {},
.mouse => |*m| {
if (m.down_pt) |_| {
m.byte = self.bytes_seen;
self.selection.moveCursor(self.bytes_seen, false);
m.down_pt = null;
}
if (m.drag_pt) |_| {
self.selection.cursor = self.bytes_seen;
self.selection.start = @min(m.byte.?, self.bytes_seen);
self.selection.end = @max(m.byte.?, self.bytes_seen);
m.drag_pt = null;
}
},
.expand_pt => |*ep| {
if (!ep.done and !ep.select) {
self.selection.moveCursor(self.selection.cursor, false);
}
},
.char_left_right => {},
.cursor_updown => |*cud| {
if (cud.pt) |_| {
self.selection.moveCursor(self.bytes_seen, cud.select);
cud.pt = null;
}
},
.word_left_right => {},
}
if (self.sel_start_r_new) |start_r| {
if (!self.sel_start_r.equals(start_r)) {
dvui.refresh(null, @src(), self.data().id);
}
self.sel_start_r = start_r;
}
if (self.selection.start > self.bytes_seen or self.bytes_seen == 0) {
self.sel_start_r = .{ .x = self.insert_pt.x, .y = self.insert_pt.y, .w = 1, .h = text_height };
if (self.selection.start > self.bytes_seen) {
dvui.refresh(null, @src(), self.data().id);
}
}
if (self.sel_end_r_new) |end_r| {
if (!self.sel_end_r.equals(end_r)) {
dvui.refresh(null, @src(), self.data().id);
}
self.sel_end_r = end_r;
}
if (self.selection.end > self.bytes_seen or self.bytes_seen == 0) {
self.sel_end_r = .{ .x = self.insert_pt.x, .y = self.insert_pt.y, .w = 1, .h = text_height };
if (self.selection.end > self.bytes_seen) {
dvui.refresh(null, @src(), self.data().id);
}
}
const cw = dvui.currentWindow();
if (dvui.accesskit_enabled) if (cw.accesskit.text_run_parent) |text_run_parent| {
if (cw.accesskit.nodes.get(text_run_parent)) |parent_node| {
if (self.bytes_seen == 0 or self.newline) {
// No empty text run was created as no text was rendered. Create one here.
self.textRunCreateEmpty(if (self.data().options.role.? == .none) cw.accesskit.text_run_parent.? else self.data().id, self.bytes_seen == 0);
}
if (!self.selection.empty()) {
self.textrun_anchor = self.textrun_anchor orelse self.textrun_last;
self.textrun_focus = self.textrun_focus orelse self.textrun_last;
if (self.textrun_anchor) |anchor| {
if (self.textrun_focus) |focus| {
AccessKit.nodeSetTextSelection(parent_node, .{
.anchor = .{ .node = anchor.node_id.asU64(), .character_index = anchor.pos },
.focus = .{ .node = focus.node_id.asU64(), .character_index = focus.pos },
});
}
}
} else {
// if we didn't find the cursor, it must be at the end
if (self.textrun_cursor orelse self.textrun_last) |cursor| {
AccessKit.nodeSetTextSelection(parent_node, .{
.anchor = .{ .node = cursor.node_id.asU64(), .character_index = cursor.pos },
.focus = .{ .node = cursor.node_id.asU64(), .character_index = cursor.pos },
});
}
}
}
};
}