DVUI

bytesNeeded

Parameters

#
self:*TextLayoutWidget
*TextLayoutWidget
edit_start:usize
usize
edit_end:usize
usize
edit_added:i64
i64

Source

Implementation

#
pub fn bytesNeeded(self: *TextLayoutWidget, edit_start: usize, edit_end: usize, edit_added: i64) ?bytesNeededReturn {
    if (self.byte_heights.len == 0) return null;

    // intersect our content rect with the clipping rect
    const clip_logical = self.data().contentRectScale().rectFromPhysical(dvui.clipGet());
    const vr = self.data().contentRect().justSize().intersect(clip_logical);

    var start_byte: usize = 0;
    var end_byte: usize = self.byte_heights[self.byte_heights.len - 1].byte;

    const Context = struct { height: f32, byte: usize };
    var context: Context = .{ .height = vr.y, .byte = edit_start };
    var sel_end: usize = edit_end;
    var end_height = vr.y + vr.h;

    if (self.copy_sel) |sel| {
        context.byte = @min(context.byte, sel.start);
        sel_end = @max(sel_end, sel.end);
    }

    var include_cursor = self.scroll_to_cursor;

    // if we are moving the cursor, need to process the text around where we are moving it
    switch (self.sel_move) {
        .none => {},
        .mouse => {}, // all in visible region, excepted below
        .expand_pt => |*ep| {
            switch (ep.which) {
                .word, .line => {}, // all in visible region
                .home, .end => include_cursor = true,
            }
        },
        .char_left_right => include_cursor = true,
        .cursor_updown => |*cud| {
            if (cud.pt) |p| {
                // found cursor last frame, need to include p this frame
                context.height = @min(context.height, p.y);
                end_height = @max(end_height, p.y);
            } else {
                // we are looking for the cursor to move from
                include_cursor = true;
            }
        },
        .word_left_right => include_cursor = true,
    }

    if (include_cursor and self.sel_move != .mouse) {
        context.byte = @min(context.byte, self.selection.cursor);
        sel_end = @max(sel_end, self.selection.cursor);
    }

    // binary search for the start
    const predicateFn = struct {
        fn predicateFn(ctx: Context, item: ByteHeight) bool {
            return item.height <= ctx.height and item.byte < ctx.byte;
        }
    }.predicateFn;

    var first_past_height = std.sort.partitionPoint(ByteHeight, self.byte_heights, context, predicateFn);
    if (first_past_height == self.byte_heights.len) {
        // can't start at the final
        first_past_height -|= 1;
    }

    if (first_past_height > 0) {
        // starting not at the top
        const startBH = self.byte_heights[first_past_height - 1];
        start_byte = startBH.byte;

        self.insert_pt.y = startBH.height;
        self.line = startBH.line;
        self.bytes_seen = start_byte;

        if (!include_cursor and (self.selection.cursor < self.bytes_seen)) {
            std.debug.assert(self.cursor_seen == false);
            self.cursor_rect = Rect{ .x = self.insert_pt.x, .y = self.insert_pt.y, .w = 1, .h = 10 };
            self.cursorSeen();
        }

        switch (self.sel_move) {
            .word_left_right => |*wlr| {
                if (wlr.count < 0) {
                    // update default so that if someone does tons of word left in
                    // the same frame (so they move to before we started processing
                    // text, they will only go back to this index (instead of 0)
                    for (&wlr.word_start_idx) |*i| {
                        i.* = start_byte;
                    }
                }
            },
            else => {},
        }

        //std.debug.print("setting min height to {d}\n", .{self.insert_pt.y});

        // set min height just to make sure it happens
        const start_size = self.data().options.padSize(.{ .h = self.insert_pt.y });
        self.data().min_size.h = @max(self.data().min_size.h, start_size.h);

        // copy all the ByteHeights we skipped
        self.byte_heights_new.appendSlice(dvui.currentWindow().arena(), self.byte_heights[0..first_past_height]) catch {};

        // copy all the LineAscents we skipped
        var i: usize = 0;
        while (i < self.line_ascents.len and self.line_ascents[i].line < startBH.line) i += 1;
        self.line_ascents_new.appendSlice(dvui.currentWindow().arena(), self.line_ascents[0..i]) catch {};
        self.line_ascents_idx = i;
    }

    // linear scan for the end (but not the final)
    for (self.byte_heights[first_past_height .. self.byte_heights.len - 1], first_past_height..) |bh, i| {
        if (bh.height >= end_height and bh.byte > sel_end) {
            //std.debug.print("found end {d} {d} bh height {d} vr {d} {d} {d}\n", .{ i, self.byte_heights.len, bh.height, vr.y, vr.h, vr.y + vr.h });
            end_byte = bh.byte;

            self.byte_height_after_idx = i;
            break;
        }
    }

    // assume min width stays the same
    self.data().min_size.w = (dvui.minSizeGet(self.data().id) orelse Size.all(0)).w;

    // adjust end_byte for any edits
    if (edit_added >= 0) {
        end_byte += @intCast(edit_added);
    } else {
        end_byte -= @intCast(-edit_added);
    }

    //std.debug.print("bytesNeeded end {d} {d} {d}\n", .{ start_byte, end_byte, edit_added });

    return .{ .start = start_byte, .end = end_byte };
}