init
It's expected to call this when self is undefined
Parameters
- self:*TextEntryWidget
- *TextEntryWidget
- src:std.builtin.SourceLocation
- std.builtin.SourceLocation
- init_opts:InitOptions
- InitOptions
- opts:Options
- Options
Source
Implementation
pub fn init(self: *TextEntryWidget, src: std.builtin.SourceLocation, init_opts: InitOptions, opts: Options) void {
var scroll_init_opts = ScrollAreaWidget.InitOpts{
.vertical = if (init_opts.scroll_vertical orelse init_opts.multiline) .auto else .none,
.vertical_bar = init_opts.scroll_vertical_bar orelse .auto,
.horizontal = if (init_opts.scroll_horizontal orelse true) .auto else .none,
.horizontal_bar = init_opts.scroll_horizontal_bar orelse (if (init_opts.multiline) .auto else .hide),
};
var options = defaults.min_sizeM(defaultMWidth, 1);
if (init_opts.password_char != null) {
options.role = .password_input;
} else if (init_opts.multiline) {
options.role = .multiline_text_input;
}
options = options.override(opts);
if (!init_opts.multiline and options.max_size_content == null) {
options = options.override(.{ .max_size_content = .size(options.min_size_contentGet()) });
}
// padding is interpreted as the padding for the TextLayoutWidget, but
// we also need to add it to content size because TextLayoutWidget is
// inside the scroll area
const padding = options.paddingGet();
options.padding = null;
options.min_size_content.?.w += padding.x + padding.w;
options.min_size_content.?.h += padding.y + padding.h;
if (options.max_size_content != null) {
options.max_size_content.?.w += padding.x + padding.w;
options.max_size_content.?.h += padding.y + padding.h;
}
const wd = WidgetData.init(src, .{}, options);
scroll_init_opts.focus_id = wd.id;
var text: []u8 = undefined;
var find_zero = true;
var len_utf8_boundary: usize = undefined;
switch (init_opts.text) {
.buffer => |b| text = b,
.buffer_dynamic => |b| text = b.backing.*,
.internal => text = dvui.dataGetSliceDefault(null, wd.id, "_buffer", []u8, &.{}),
.array_list => |al| {
find_zero = false;
text = al.backing.items.ptr[0..@min(al.limit, al.backing.capacity)];
len_utf8_boundary = dvui.findUtf8Start(text, al.backing.items.len);
},
}
if (find_zero) {
const len_byte = std.mem.findScalar(u8, text, 0) orelse text.len;
len_utf8_boundary = dvui.findUtf8Start(text[0..len_byte], len_byte);
}
self.* = .{
.wd = wd,
.padding = padding,
.init_opts = init_opts,
.text = text,
.len = len_utf8_boundary,
// SAFETY: The following fields are set bellow
.prevClip = undefined,
.scroll = undefined,
.scrollClip = undefined,
.textLayout = undefined,
.textClip = undefined,
};
self.data().register();
dvui.tabIndexSet(self.data().id, self.data().options.tab_index, self.data().rectScale().r);
dvui.parentSet(self.widget());
self.data().borderAndBackground(.{});
self.prevClip = dvui.clip(self.data().borderRectScale().r);
const borderClip = dvui.clipGet();
// We do this dance with last_focused_id_this_frame so scroll will process
// key events we skip (like page up/down). Normally it would not (text
// entry is not a child of scroll). So with this we make scroll think that
// text entry ran as a child.
const focused = (self.data().id == dvui.lastFocusedIdInFrame());
if (focused) dvui.currentWindow().last_focused_id_this_frame = .zero;
// scrollbars process mouse events here
self.scroll.init(@src(), scroll_init_opts, self.data().options.strip().override(.{ .role = .none, .expand = .both }));
if (focused) dvui.currentWindow().last_focused_id_this_frame = self.data().id;
self.scrollClip = dvui.clipGet();
self.edited_outside_last_frame = dvui.dataGetPtrDefault(null, self.data().id, "_edited_outside", bool, false);
if (self.init_opts.cache_layout and self.edited_outside_last_frame.*) {
dvui.log.debug("TextEntryWidget forcing cache_layout false due to text being edited after drawing last frame", .{});
self.init_opts.cache_layout = false;
self.edited_outside_last_frame.* = false;
self.text_changed = true; // trigger tree_sitter full reparse
}
self.textLayout.init(@src(), .{
.break_lines = self.init_opts.break_lines,
.kerning = self.init_opts.kerning,
.touch_edit_just_focused = false,
.cache_layout = self.init_opts.cache_layout,
.focused = self.data().id == dvui.focusedWidgetId(),
.show_touch_draggables = (self.len > 0),
}, self.data().options.strip().override(.{
.role = .none,
.expand = .both,
.padding = self.padding,
}));
// if textLayout forced cache_layout to false, we need to honor that
self.init_opts.cache_layout = self.textLayout.cache_layout;
self.textClip = dvui.clipGet();
if (self.textLayout.touchEditing()) |floating_widget| {
defer floating_widget.deinit();
var hbox = dvui.box(@src(), .{ .dir = .horizontal }, .{
.corners = dvui.ButtonWidget.defaults.cornersGet(),
.background = true,
.border = dvui.Rect.all(1),
});
defer hbox.deinit();
if (dvui.buttonIcon(@src(), "paste", dvui.entypo.clipboard, .{}, .{}, .{
.min_size_content = .{ .h = 20 },
.margin = Rect.all(2),
})) {
self.paste();
}
if (dvui.buttonIcon(@src(), "select all", dvui.entypo.swap, .{}, .{}, .{
.min_size_content = .{ .h = 20 },
.margin = Rect.all(2),
})) {
self.textLayout.selection.selectAll();
}
if (dvui.buttonIcon(@src(), "cut", dvui.entypo.scissors, .{}, .{}, .{
.min_size_content = .{ .h = 20 },
.margin = Rect.all(2),
})) {
self.cut();
}
if (dvui.buttonIcon(@src(), "copy", dvui.entypo.copy, .{}, .{}, .{
.min_size_content = .{ .h = 20 },
.margin = Rect.all(2),
})) {
self.copy();
}
}
// don't call textLayout.processEvents here, we forward events inside our own processEvents
// textLayout is maintaining the selection for us, but if the text
// changed, we need to update the selection to be valid before we
// process any events
var sel = self.textLayout.selection;
sel.start = dvui.findUtf8Start(self.text[0..self.len], sel.start);
sel.cursor = dvui.findUtf8Start(self.text[0..self.len], sel.cursor);
sel.end = dvui.findUtf8Start(self.text[0..self.len], sel.end);
// textLayout clips to its content, but we need to get events out to our border
dvui.clipSet(borderClip);
if (self.data().accesskit_node()) |ak_node| {
AccessKit.nodeAddAction(ak_node, AccessKit.Action.focus);
AccessKit.nodeAddAction(ak_node, AccessKit.Action.set_value);
AccessKit.nodeAddAction(ak_node, AccessKit.Action.set_text_selection);
AccessKit.nodeAddAction(ak_node, AccessKit.Action.replace_selected_text);
AccessKit.nodeAddAction(ak_node, AccessKit.Action.scroll_into_view); // AK TODO - not yet implemented
AccessKit.nodeSetClipsChildren(ak_node); // AK TODO: Check this is correct?
if (self.data().options.role != .password_input) {
const str = self.text[0..self.len];
AccessKit.nodeSetValueWithLength(ak_node, str.ptr, str.len);
}
}
}