╭ changed ───────────────╮ │src/ui/Repo/Branches.zig│ ╰────────────────────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭──────────╮ │← previous│ ╰──────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │161 return &self.scroll.child.center.child.box; │ │162 } │ │163 │ │156 - fn addBox(self: *View) *wgt.Box(ui.Widget) { │ │157 - return &self.contentBox().children.values()[add_index].widget.box; │ │164 + // a row holding one text input in its own form │ │165 + fn addFormRow(allocator: std.mem.Allocator, box: *wgt.Box(ui.Widget), session: *ui.Session, form: []const u8, label: []const u8, content: []const u8) !void { │ │166 + var row = try wgt.Box(ui.Widget).init(allocator, .{ .border = null, .direction = .horiz }); │ │167 + errdefer row.deinit(allocator); │ │168 + row.getFocus().kind = .{ .custom = form }; │ │169 + var name = try wgt.TextInput.init(allocator, .{ .top_label = .{ .text = label }, .name = "name", .visible_width = 30, .round_corners = true, .render_content = session.is_terminal });│ │170 + errdefer name.deinit(allocator); │ │171 + name.getFocus().mode = .all; │ │172 + try name.setContent(allocator, content); │ │173 + row.getFocus().child_id = name.getFocus().id; │ │174 + try row.children.put(allocator, name.getFocus().id, .{ .widget = .{ .text_input = name }, .rect = null, .min_size = .{ .width = null, .height = 3 } }); │ │175 + try box.children.put(allocator, row.getFocus().id, .{ .widget = .{ .box = row }, .rect = null, .min_size = .{ .width = null, .height = 3 } }); │ │176 } │ │177 │ │160 - fn nameInput(self: *View) *wgt.TextInput { │ │161 - return &self.addBox().children.values()[0].widget.text_input; │ │178 + fn formInput(self: *View, index: usize) *wgt.TextInput { │ │179 + return &self.contentBox().children.values()[index].widget.box.children.values()[0].widget.text_input; │ │180 } │ │181 │ │164 - fn addActive(self: *View) bool { │ │165 - return self.contentBox().getFocus().child_id == self.addBox().getFocus().id; │ │182 + // the form row with focus, if any │ │183 + fn activeForm(self: *View) ?usize { │ │184 + const rows = self.contentBox(); │ │185 + const index = rows.children.getIndex(rows.getFocus().child_id orelse return null) orelse return null; │ │186 + return if (index == default_index or index == add_index) index else null; │ │187 } │ │188 │ │189 fn aboutBox(self: *View) *wgt.Box(ui.Widget) { │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │192 │ │193 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void {│ │194 self.clearGrid(); │ │174 - try self.labelAddInput(root_focus); │ │195 + try self.labelInputs(root_focus); │ │196 ui.widget.markAboutFocused(self.aboutBox(), root_focus); │ │197 // the window's height as a minimum centers rows that fit │ │198 try self.scroll.build(allocator, .{ │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │201 }, root_focus); │ │202 } │ │203 │ │183 - // label the add input for this frame and register it for the web form │ │184 - // handling │ │185 - fn labelAddInput(self: *View, root_focus: *Focus) !void { │ │186 - const name = self.nameInput(); │ │187 - const failure = if (self.session.formFeedback(.repo_branches)) |saved| saved.failure else null; │ │188 - name.options.top_label.text = if (failure) |value| switch (value) { │ │189 - .not_found => " add a protected branch (not found) ", │ │190 - .already_added => " add a protected branch (already added) ", │ │191 - .too_many => " add a protected branch (too many branches) ", │ │192 - .too_long => " add a protected branch (name too long) ", │ │193 - } else " add a protected branch "; │ │194 - name.options.bottom_label.text = if (root_focus.grandchild_id == name.getFocus().id) " press enter " else ""; │ │195 - try self.session.text_inputs.put(self.session.arena.allocator(), name.getFocus().id, name); │ │204 + // label the inputs for this frame and register them for the web form │ │205 + // handling. a failure labels the input it came from. │ │206 + fn labelInputs(self: *View, root_focus: *Focus) !void { │ │207 + const saved = self.session.formFeedback(.repo_branches); │ │208 + for ([_]usize{ default_index, add_index }) |index| { │ │209 + const name = self.formInput(index); │ │210 + const default = index == default_index; │ │211 + const failure = if (saved) |value| (if (value.default == default) value.failure else null) else null; │ │212 + name.options.top_label.text = if (failure) |value| switch (value) { │ │213 + .not_found => if (default) " default branch (not found) " else " add a protected branch (not found) ", │ │214 + .already_added => " add a protected branch (already added) ", │ │215 + .too_many => " add a protected branch (too many branches) ", │ │216 + .too_long => if (default) " default branch (name too long) " else " add a protected branch (name too long) ",│ │217 + } else if (default) " default branch " else " add a protected branch "; │ │218 + name.options.bottom_label.text = if (root_focus.grandchild_id == name.getFocus().id) " press enter " else ""; │ │219 + try self.session.text_inputs.put(self.session.arena.allocator(), name.getFocus().id, name); │ │220 + } │ │221 } │ │222 │ │223 pub fn input(self: *View, allocator: std.mem.Allocator, key: Key, root_focus: *Focus) !void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │199 - // the add row keeps every key but the vertical ones, which move between rows │ │200 - if (self.addActive() and inp.vertDirection(key) == .none) { │ │224 + // a form row keeps every key but the vertical ones, which move between rows │ │225 + if (self.activeForm()) |index| if (inp.vertDirection(key) == .none) { │ │226 if (key == .enter) { │ │202 - const name = try self.nameInput().text(allocator); │ │227 + const name = try self.formInput(index).text(allocator); │ │228 defer allocator.free(name); │ │204 - try self.act(allocator, .protect, name); │ │205 - } else try self.nameInput().input(allocator, key, root_focus); │ │229 + try self.act(allocator, if (index == default_index) .default else .protect, name); │ │230 + } else try self.formInput(index).input(allocator, key, root_focus); │ │231 return; │ │207 - } │ │232 + }; │ │233 │ │234 const rows = self.contentBox(); │ │235 const current = rows.children.getIndex(rows.getFocus().child_id orelse return) orelse return;│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │240 switch (key) { │ │241 .arrow_left, .arrow_right => _ = ui.widget.moveInSelectedRow(rows, root_focus, key == .arrow_right),│ │242 else => { │ │218 - // the text and the add row sit above the rows, and a row's name is a link the host follows │ │243 + // the text and the form rows sit above the rows, and a row's name is a link the host follows │ │244 if (current <= add_index) return; │ │245 const row_box = &rows.children.values()[current].widget.box; │ │246 const button_id = row_box.getFocus().child_id orelse return; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │257 const users_dir = self.session.users_dir orelse return; │ │258 const actor = (try self.session.authorize(self.data.identity, .owner, null)) orelse return; │ │259 if (try perform(io, allocator, users_dir, actor, self.data.identity, action, branch)) |failure| { │ │235 - self.session.data.form_feedback = .{ .repo_branches = .{ .failure = failure, .name = try self.session.arena.allocator().dupe(u8, branch) } }; │ │260 + self.session.data.form_feedback = .{ .repo_branches = .{ .failure = failure, .name = try self.session.arena.allocator().dupe(u8, branch), .default = action == .default } };│ │261 return; │ │262 } │ │263 try self.session.navigate(ui.RoutablePage.repoBranchesRoute(self.data.identity) orelse return error.RouteTooLong); │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯