DVUI

init

It's expected to call this when self is undefined

Parameters

#
self:*TextLayoutWidget
*TextLayoutWidget
src:std.builtin.SourceLocation
std.builtin.SourceLocation
init_opts:InitOptions
InitOptions
opts:Options
Options

Source

Implementation

#
pub fn init(self: *TextLayoutWidget, src: std.builtin.SourceLocation, init_opts: InitOptions, opts: Options) void {
    const options = defaults.override(opts);

    self.* = .{
        .wd = WidgetData.init(src, .{}, options),
        .break_lines = init_opts.break_lines,
        .cache_layout = init_opts.cache_layout,
        .kerning = init_opts.kerning,
        .touch_edit_just_focused = init_opts.touch_edit_just_focused,

        // SAFETY: set bellow
        .selection = undefined,
    };
    self.selection = if (init_opts.selection) |sel_in| sel_in else dvui.dataGetPtrDefault(null, self.wd.id, "_selection", Selection, .{});

    if (dvui.dataGet(null, self.wd.id, "_touch_editing", bool)) |val| self.touch_editing = val;
    if (dvui.dataGet(null, self.wd.id, "_te_first", bool)) |val| self.te_first = val;
    if (dvui.dataGet(null, self.wd.id, "_te_show_draggables", bool)) |val| self.te_show_draggables = val;
    if (dvui.dataGet(null, self.wd.id, "_te_show_context_menu", bool)) |val| self.te_show_context_menu = val;
    if (dvui.dataGet(null, self.wd.id, "_te_focus_on_touchdown", bool)) |val| self.te_focus_on_touchdown = val;
    if (dvui.dataGet(null, self.wd.id, "_sel_start_r", Rect)) |val| self.sel_start_r = val;
    if (dvui.dataGet(null, self.wd.id, "_sel_end_r", Rect)) |val| self.sel_end_r = val;
    if (dvui.dataGet(null, self.wd.id, "_click_num", u8)) |val| self.click_num = val;
    if (dvui.dataGet(null, self.wd.id, "_click_num_pt", dvui.Point.Physical)) |val| self.click_num_pt = val;
    if (dvui.dataGetSlice(null, self.wd.id, "_byte_heights", []ByteHeight)) |bh| self.byte_heights = bh;
    if (dvui.dataGetSlice(null, self.wd.id, "__line_ascents", []LineAscent)) |la| self.line_ascents = la;

    if (dvui.dataGet(null, self.wd.id, "_scroll_to_cursor", bool) orelse false) {
        dvui.dataRemove(null, self.wd.id, "_scroll_to_cursor");
        self.scroll_to_cursor = true;
    }

    const scale_old = dvui.dataGetPtrDefault(null, self.wd.id, "_scale", f32, dvui.parentGet().screenRectScale(Rect{}).s);
    const scale_new = dvui.parentGet().screenRectScale(Rect{}).s;
    if (self.cache_layout and scale_old.* != scale_new) {
        dvui.log.debug("{x} TextLayoutWidget forcing cache_layout false due to scale change", .{self.data().id});
        self.cache_layout = false;
    }
    scale_old.* = scale_new;

    const break_lines_old = dvui.dataGetPtrDefault(null, self.wd.id, "_break_lines", bool, self.break_lines);
    if (self.cache_layout and break_lines_old.* != self.break_lines) {
        dvui.log.debug("{x} TextLayoutWidget forcing cache_layout false due to break_lines change", .{self.data().id});
        self.cache_layout = false;
    }
    break_lines_old.* = self.break_lines;

    const width_old = dvui.dataGetPtrDefault(null, self.wd.id, "_width", f32, self.data().rect.w);
    if (self.cache_layout and self.break_lines and width_old.* != self.data().rect.w) {
        dvui.log.debug("{x} TextLayoutWidget forcing cache_layout false due to width change while break_lines", .{self.data().id});
        self.cache_layout = false;
    }
    width_old.* = self.data().rect.w;

    self.focus_at_start = init_opts.focused orelse (self.data().id == dvui.focusedWidgetId());

    self.data().register();
    dvui.parentSet(self.widget());

    if (dvui.captured(self.data().id)) {
        if (dvui.dataGet(null, self.data().id, "_sel_move_mouse_byte", usize)) |p| {
            self.sel_move = .{ .mouse = .{ .byte = p } };
        }

        if (dvui.dataGet(null, self.data().id, "_sel_move_expand_pt_which", @TypeOf(self.sel_move.expand_pt.which))) |w| {
            if (dvui.dataGet(null, self.data().id, "_sel_move_expand_pt_bytes", [2]usize)) |bytes| {
                // set done to true, only matters if we are dragging which sets it back to false
                self.sel_move = .{ .expand_pt = .{ .which = w, .bytes = bytes, .done = true } };
            }
        }
    }

    if (dvui.dataGet(null, self.data().id, "_sel_move_cursor_updown_pt", Point)) |p| {
        self.sel_move = .{ .cursor_updown = .{ .pt = p } };
        dvui.dataRemove(null, self.data().id, "_sel_move_cursor_updown_pt");
        if (dvui.dataGet(null, self.data().id, "_sel_move_cursor_updown_select", bool)) |cud| {
            self.sel_move.cursor_updown.select = cud;
            dvui.dataRemove(null, self.data().id, "_sel_move_cursor_updown_select");
        }
    }

    const control_opts: Options = .{};

    const rs = self.data().contentRectScale();

    self.data().borderAndBackground(.{});

    // clip to background rect for possible corner widgets, addTextEx clips to content rect
    self.prevClip = dvui.clip(self.data().backgroundRectScale().r);

    if (init_opts.show_touch_draggables and self.touch_editing and self.te_show_draggables and self.focus_at_start and self.data().visible()) {
        const size = 36;
        {

            // calculate visible before FloatingWidget changes clip

            // We only draw if visible (to prevent drawing way outside the
            // textLayout), but we always process the floating window so that
            // we maintain capture.  That way you can drag a draggable off the
            // textLayout (so it's not visible), which causes a scroll, but
            // when the draggable shows back up you are still dragging it.

            // sel_start_r might be just off the right-hand edge, so widen it
            var cursor = self.sel_start_r;
            cursor.x -= 1;
            cursor.w += 1;
            const visible = !dvui.clipGet().intersect(rs.rectToPhysical(cursor)).empty();

            var rect = self.sel_start_r;
            rect.y += rect.h; // move to below the line
            const srs = self.screenRectScale(rect);
            rect = dvui.windowRectScale().rectFromPhysical(srs.r);
            rect.x -= size;
            rect.w = size;
            rect.h = size;

            var fc: dvui.FloatingWidget = undefined;
            fc.init(@src(), .{}, .{ .rect = rect });

            var offset: Point.Physical = dvui.dataGet(null, fc.data().id, "_offset", Point.Physical) orelse .{};

            const fcrs = fc.data().rectScale();
            const evts = dvui.events();
            for (evts) |*e| {
                if (!dvui.eventMatch(e, .{ .id = fc.data().id, .r = fcrs.r }))
                    continue;

                if (e.evt == .mouse) {
                    const me = e.evt.mouse;
                    if (me.action == .press and me.button.touch()) {
                        dvui.captureMouse(fc.data(), e.num);
                        self.te_show_context_menu = false;
                        offset = fcrs.r.topRight().diff(me.p);

                        // give an extra offset of half the cursor height
                        offset.y -= self.sel_start_r.h * 0.5 * rs.s;
                    } else if (me.action == .release and me.button.touch()) {
                        dvui.captureMouse(null, e.num);
                        dvui.dragEnd();
                    } else if (me.action == .motion and dvui.captured(fc.data().id)) {
                        const corner = me.p.plus(offset);
                        self.sel_pts[0] = self.data().contentRectScale().pointFromPhysical(corner);
                        self.sel_pts[1] = self.sel_end_r.topLeft().plus(.{ .y = self.sel_end_r.h / 2 });

                        self.sel_pts[0].?.y = @min(self.sel_pts[0].?.y, self.sel_pts[1].?.y);

                        dvui.scrollDrag(.{
                            .mouse_pt = e.evt.mouse.p,
                            .screen_rect = self.data().rectScale().r,
                        });
                    }
                }
            }

            if (visible) {
                var path: dvui.Path.Builder = .init(dvui.currentWindow().lifo());
                defer path.deinit();

                path.addPoint(.{ .x = fcrs.r.x + fcrs.r.w, .y = fcrs.r.y });
                path.addArc(.{ .x = fcrs.r.x + fcrs.r.w / 2, .y = fcrs.r.y + fcrs.r.h / 2 }, fcrs.r.w / 2, std.math.pi, 0, true);

                path.build().fillConvex(.{ .color = control_opts.color(.fill) });
                path.build().stroke(.{ .thickness = 1.0 * fcrs.s, .color = self.data().options.color(.border), .closed = true });
            }

            dvui.dataSet(null, fc.data().id, "_offset", offset);
            fc.deinit();
        }

        {
            // calculate visible before FloatingWidget changes clip

            // sel_end_r might be just off the right-hand edge, so widen it
            var cursor = self.sel_end_r;
            cursor.x -= 1;
            cursor.w += 1;
            const visible = !dvui.clipGet().intersect(rs.rectToPhysical(cursor)).empty();

            var rect = self.sel_end_r;
            rect.y += rect.h; // move to below the line
            const srs = self.screenRectScale(rect);
            rect = dvui.windowRectScale().rectFromPhysical(srs.r);
            rect.w = size;
            rect.h = size;

            var fc: dvui.FloatingWidget = undefined;
            fc.init(@src(), .{}, .{ .rect = rect });

            var offset: Point.Physical = dvui.dataGet(null, fc.data().id, "_offset", Point.Physical) orelse .{};

            const fcrs = fc.data().rectScale();
            const evts = dvui.events();
            for (evts) |*e| {
                if (!dvui.eventMatch(e, .{ .id = fc.data().id, .r = fcrs.r }))
                    continue;

                if (e.evt == .mouse) {
                    const me = e.evt.mouse;
                    if (me.action == .press and me.button.touch()) {
                        dvui.captureMouse(fc.data(), e.num);
                        self.te_show_context_menu = false;
                        offset = fcrs.r.topLeft().diff(me.p);

                        // give an extra offset of half the cursor height
                        offset.y -= self.sel_start_r.h * 0.5 * rs.s;
                    } else if (me.action == .release and me.button.touch()) {
                        dvui.captureMouse(null, e.num);
                        dvui.dragEnd();
                    } else if (me.action == .motion and dvui.captured(fc.data().id)) {
                        const corner = me.p.plus(offset);
                        self.sel_pts[0] = self.sel_start_r.topLeft().plus(.{ .y = self.sel_start_r.h / 2 });
                        self.sel_pts[1] = self.data().contentRectScale().pointFromPhysical(corner);

                        self.sel_pts[1].?.y = @max(self.sel_pts[0].?.y, self.sel_pts[1].?.y);

                        dvui.scrollDrag(.{
                            .mouse_pt = e.evt.mouse.p,
                            .screen_rect = self.data().rectScale().r,
                        });
                    }
                }
            }

            if (visible) {
                var path: dvui.Path.Builder = .init(dvui.currentWindow().lifo());
                defer path.deinit();

                path.addPoint(.{ .x = fcrs.r.x, .y = fcrs.r.y });
                path.addArc(.{ .x = fcrs.r.x + fcrs.r.w / 2, .y = fcrs.r.y + fcrs.r.h / 2 }, fcrs.r.w / 2, std.math.pi, 0, true);

                path.build().fillConvex(.{ .color = control_opts.color(.fill) });
                path.build().stroke(.{ .thickness = 1.0 * fcrs.s, .color = self.data().options.color(.border), .closed = true });
            }

            dvui.dataSet(null, fc.data().id, "_offset", offset);
            fc.deinit();
        }
    }

    if (self.data().accesskit_node()) |ak_node| {
        dvui.AccessKit.nodeSetReadOnly(ak_node);
    }

    if (dvui.accesskit_enabled and options.role.? != .none) {
        var vp = dvui.virtualParent(@src(), .{ .role = .label });
        defer vp.deinit();
        var cw = dvui.currentWindow();
        self.textrun_parent_prev = cw.accesskit.text_run_parent;
        cw.accesskit.text_run_parent = vp.data().id;
    }
}