DVUI

unionFieldWidget

Allow the selection of the active union member. returns the tag of the active member.

Parameters

#
src:std.builtin.SourceLocation
std.builtin.SourceLocation
field_name:[]const u8
[]const u8
field_value_ptr:anytype
anytype
opt:FieldOptions
FieldOptions

Source

Implementation

#
pub fn unionFieldWidget(
    src: std.builtin.SourceLocation,
    field_name: []const u8,
    field_value_ptr: anytype,
    opt: FieldOptions,
) UnionTagType(@TypeOf(field_value_ptr)) {
    const T = @TypeOf(field_value_ptr.*);
    if (@typeInfo(T).@"union".tag_type == null) {
        @compileError(std.fmt.comptimePrint("union field {s}: Only tagged unions are supported", .{field_name}));
    }

    if (opt.displayMode() == .none) {
        return field_value_ptr.*;
    }
    const read_only = @typeInfo(@TypeOf(field_value_ptr)).pointer.is_const or opt.displayMode() != .read_write;

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

    const entries = std.meta.fields(T);
    var active_tag = std.meta.activeTag(field_value_ptr.*);

    // This loop is being used, rather than @intFromEnum etc as there is no guarantee that
    // the enum int values are the same as the field order. i.e. custom enums can be used as tags.
    // There should be a better way to do this than converting to string? But UnionField doesn't store the tag.
    const choice_names: []const []const u8, var active_choice_num: usize = blk: {
        var choice_names: [entries.len][]const u8 = undefined;
        var active_choice_num: usize = 0;
        inline for (0..entries.len) |i| {
            if (std.meta.stringToEnum(@TypeOf(active_tag), entries[i].name).? == std.meta.activeTag(field_value_ptr.*)) {
                active_choice_num = i;
            }
            choice_names[i] = entries[i].name;
        }
        break :blk .{ &choice_names, active_choice_num };
    };

    {
        var hbox = dvui.box(@src(), .{}, .{});
        defer hbox.deinit();
        if (read_only) {
            switch (active_tag) {
                // Don't display active tag, if it will be displayed as a field. i.e. it is not a void union member.
                // TODO: It is not strictly just void here. It is really any type that can't be displayed by
                // displayField(). Need to add a lookup for displayable types and use that here.
                inline else => |t| if (@FieldType(T, @tagName(t)) == void) {
                    dvui.labelNoFmt(@src(), @tagName(active_tag), .{}, .{ .margin = .{ .y = 4 } });
                },
            }
        } else {
            if (dvui.dropdown(@src(), choice_names, .{ .choice = &active_choice_num }, .{}, .{})) {
                active_tag = std.meta.stringToEnum(@TypeOf(active_tag), choice_names[active_choice_num]).?; // This should never fail.
            }
        }
    }
    return active_tag;
}