DVUI

textFieldWidget

Display slices and/or arrays of u8 and const u8. If a slice, the slice will be assigned to a duplicated copy of the text widget's buffer.

Parameters

#
src:std.builtin.SourceLocation
std.builtin.SourceLocation
field_name:[]const u8
[]const u8
field_value_ptr:anytype
anytype
opt:TextFieldOptions
TextFieldOptions
alignment:*dvui.Alignment
*dvui.Alignment
backing:StringBackingType
StringBackingType

Source

Implementation

#
pub fn textFieldWidget(
    src: std.builtin.SourceLocation,
    field_name: []const u8,
    field_value_ptr: anytype,
    opt: TextFieldOptions,
    alignment: *dvui.Alignment,
    backing: StringBackingType,
) void {
    validateFieldPtrTypeString(null, "textFieldWidget", @TypeOf(field_value_ptr));
    if (opt.display == .none) return;
    const T = @TypeOf(field_value_ptr.*);

    var box = dvui.box(src, .{ .dir = .horizontal }, .{});
    defer box.deinit();

    const sentinel_terminated = @typeInfo(T).pointer.sentinel_ptr != null;

    var read_only = @typeInfo(@TypeOf(field_value_ptr)).pointer.is_const or opt.display != .read_write;

    if (opt.display == .read_write and read_only) {
        // Note all string arrays are currently treated as read-only, even if they are var.
        // It would be possible to support in-place editing, preferably by implementing a new display option.
        log.debug("field {s} display option is set to read_write for read_only string or an array. Displaying as read_only.", .{field_name});
    } else if (opt.display == .read_write and sentinel_terminated) {
        // Sentinel terminated strings cannot be edited.
        // Would require keeping 2 string maps, for sentinel vs non-sentinel strings.
        log.debug("field {s} display option is set to read_write for sentinel terminated string or an array. Displaying as read_only.", .{field_name});
        read_only = true;
    }

    dvui.label(@src(), "{s}", .{opt.label orelse field_name}, .{ .margin = .{ .y = 4 } });
    if (!read_only) {
        // If the original string is heap allocated, then add it to the map so it will be freed before a new string
        // is allocated.
        if (backing == .gpa and opt.heap_allocated and !string_map.contains(field_value_ptr)) {
            string_map.put(dvui.currentWindow().gpa, field_value_ptr, field_value_ptr.*) catch |err| {
                dvui.logError(@src(), err, "Error adding to struct_ui.string_map. This will leak memory", .{});
            };
        }
        var hbox_aligned = dvui.box(@src(), .{ .dir = .horizontal }, .{ .margin = alignment.margin(box.data().id) });
        defer hbox_aligned.deinit();
        alignment.record(box.data().id, hbox_aligned.data());

        const textbox_width = dvui.themeGet().font_body.sizeM(dvui.TextEntryWidget.defaultMWidth, 1).w;
        const text_box = dvui.textEntry(@src(), if (backing == .buffer) .{ .text = .{ .buffer = backing.buffer }, .multiline = opt.multiline } else .{ .multiline = opt.multiline }, .{
            .min_size_content = if (opt.multiline) .{ .h = dvui.themeGet().font_body.lineHeight() * 2, .w = textbox_width } else null,
            .max_size_content = if (opt.multiline) .{ .h = dvui.themeGet().font_body.lineHeight() * 4, .w = textbox_width } else null,
        });
        defer text_box.deinit();
        if (!text_box.text_changed and !std.mem.eql(u8, text_box.getText(), field_value_ptr.*)) {
            text_box.textSet(field_value_ptr.*, false);
        }
        if (!@typeInfo(@TypeOf(field_value_ptr)).pointer.is_const and !sentinel_terminated) {
            if (text_box.text_changed and !std.mem.eql(u8, text_box.getText(), field_value_ptr.*)) {
                switch (backing) {
                    .gpa => {
                        if (string_map.getEntry(field_value_ptr)) |entry| {
                            backing.gpa.free(entry.value_ptr.*);
                        }
                        // Memory leaks from this line are caused by not calling struct_ui.deinit()
                        field_value_ptr.* = backing.gpa.dupe(u8, text_box.getText()) catch "";
                        string_map.put(dvui.currentWindow().gpa, field_value_ptr, field_value_ptr.*) catch |err| {
                            dvui.logError(@src(), err, "Error adding to struct_ui.string_map. This will leak memory", .{});
                        };
                    },
                    .buffer => {
                        field_value_ptr.* = text_box.getText();
                    },
                }
            }
        }
    } else {
        var hbox_aligned = dvui.box(@src(), .{ .dir = .horizontal }, .{ .margin = alignment.margin(box.data().id) });
        defer hbox_aligned.deinit();
        alignment.record(box.data().id, hbox_aligned.data());

        dvui.label(@src(), "{s}", .{field_value_ptr.*}, .{ .margin = .{ .y = 4 } });
    }
}