╭ changed ────────╮ │src/ui/widget.zig│ ╰─────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭───────────────────────────────────────────────────────────────────────────────────╮ │1047 } │ │1048 }; │ │1049 │ │1050 -// a row of bordered options posted as one form field. the selection is the │ │1051 -// focused option, so a click selects on the web too. │ │1050 +// a wrapping row of bordered options posted as one form field. the selection│ │1051 +// is the focused option, so a click selects on the web too. │ │1052 pub const Radio = struct { │ │1053 + // holds the options' flow, framed under the label when there is one │ │1054 box: wgt.Box(Widget), │ │1055 name: []const u8, │ │1056 values: []const []const u8, │ ╰───────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1057 │ │1057 - pub fn init(allocator: std.mem.Allocator, session: *ui.Session, name: []const u8, values: []const []const u8, initial: []const u8) !Radio { │ │1058 - var box = try wgt.Box(Widget).init(allocator, .{ .border = null, .direction = .horiz }); │ │1058 + pub fn init(allocator: std.mem.Allocator, session: *ui.Session, name: []const u8, values: []const []const u8, initial: []const u8, label: ?[]const u8) !Radio { │ │1059 + var box = try wgt.Box(Widget).init(allocator, .{ │ │1060 + .border = if (label != null) .single else null, │ │1061 + .round_corners = true, │ │1062 + .direction = .vert, │ │1063 + .top_label = .{ .text = label orelse "" }, │ │1064 + }); │ │1065 errdefer box.deinit(allocator); │ │1060 - for (values) |value| { │ │1061 - var option = try wgt.TextBox.init(allocator, value, .{ .border = .single, .round_corners = true, .wrap_kind = .none }); │ │1062 - errdefer option.deinit(allocator); │ │1063 - option.getFocus().mode = .all; │ │1064 - option.getFocus().kind = .{ .custom = try session.page_arena.allocator().print(ui.radio_prefix ++ "{s}={s}", .{ name, value }) }; │ │1065 - if (std.mem.eql(u8, value, initial)) box.getFocus().child_id = option.getFocus().id; │ │1066 - try box.children.put(allocator, option.getFocus().id, .{ .widget = .{ .text_box = option }, .rect = null, .min_size = .{ .width = try xitui.width.displayWidth(value) + 2, .height = 3 } });│ │1066 + │ │1067 + var flow = try WordFlow.init(allocator); │ │1068 + errdefer flow.deinit(allocator); │ │1069 + const pa = session.page_arena.allocator(); │ │1070 + const items = try pa.alloc(WordFlow.Item, values.len); │ │1071 + for (items, values) |*item, value| item.* = .{ .text = value, .link = try pa.print(ui.radio_prefix ++ "{s}={s}", .{ name, value }) }; │ │1072 + try flow.setItems(allocator, items); │ │1073 + for (flow.text_boxes.items, values) |*option, value| { │ │1074 + if (std.mem.eql(u8, value, initial)) flow.focus.child_id = option.getFocus().id; │ │1075 } │ │1076 + │ │1077 + box.getFocus().child_id = flow.getFocus().id; │ │1078 + try box.children.put(allocator, flow.getFocus().id, .{ .widget = .{ .word_flow = flow }, .rect = null, .min_size = null }); │ │1079 return .{ .box = box, .name = name, .values = values }; │ │1080 } │ │1081 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────╮ │1082 pub fn deinit(self: *Radio, allocator: std.mem.Allocator) void {│ │1083 self.box.deinit(allocator); │ │1084 + } │ │1085 + │ │1086 + fn optionFlow(self: *Radio) *WordFlow { │ │1087 + return &self.box.children.values()[0].widget.word_flow; │ │1088 } │ │1089 │ │1090 // the focused option's value │ ╰──────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1091 pub fn selected(self: *Radio) []const u8 { │ │1077 - const index = self.box.children.getIndex(self.box.getFocus().child_id orelse unreachable) orelse unreachable; │ │1078 - return self.values[index]; │ │1092 + const options = self.optionFlow(); │ │1093 + return self.values[options.indexOfFocusId(options.focus.child_id orelse unreachable) orelse unreachable]; │ │1094 } │ │1095 │ │1096 pub fn build(self: *Radio, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void {│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1097 self.clearGrid(); │ │1083 - const selected_id = self.box.getFocus().child_id; │ │1084 - for (self.box.children.keys(), self.box.children.values()) |id, *child| { │ │1085 - child.widget.text_box.options.border = if (id == selected_id) .single else .hidden; │ │1098 + const options = self.optionFlow(); │ │1099 + const selected_id = options.focus.child_id; │ │1100 + for (options.text_boxes.items) |*option| { │ │1101 + option.options.border = if (option.getFocus().id == selected_id) .single else .hidden;│ │1102 } │ │1103 try self.box.build(allocator, constraint, root_focus); │ │1104 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1105 │ │1106 + // left and right step through the options in order, however they wrap │ │1107 pub fn input(self: *Radio, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { │ │1108 _ = allocator; │ │1092 - const ids = self.box.children.keys(); │ │1093 - const index = self.box.children.getIndex(self.box.getFocus().child_id orelse return) orelse return; │ │1109 + const options = self.optionFlow(); │ │1110 + const index = options.indexOfFocusId(options.focus.child_id orelse return) orelse return; │ │1111 + const option_boxes = options.text_boxes.items; │ │1112 switch (key) { │ │1095 - .arrow_left => if (index > 0) root_focus.setFocus(ids[index - 1]), │ │1096 - .arrow_right => if (index + 1 < ids.len) root_focus.setFocus(ids[index + 1]), │ │1113 + .arrow_left => if (index > 0) root_focus.setFocus(option_boxes[index - 1].getFocus().id), │ │1114 + .arrow_right => if (index + 1 < option_boxes.len) root_focus.setFocus(option_boxes[index + 1].getFocus().id),│ │1115 else => {}, │ │1116 } │ │1117 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯