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;
}
}