╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │22 pub const Quit = @import("./ui/Quit.zig"); │ │23 pub const CloneUrl = @import("./ui/CloneUrl.zig"); │ │24 pub const Unauthorized = @import("./ui/Unauthorized.zig"); │ │25 + │ │26 +pub const clipped_bottom_label_max_len = 10 * 4; │ │27 │ │28 +pub fn clippedBottomLabel(buffer: []u8, text: []const u8) ![]const u8 { │ │29 + var iter = (try std.unicode.Utf8View.init(text)).iterator(); │ │30 + var end: usize = 0; │ │31 + var clipped_end: usize = 0; │ │32 + for (0..10) |i| { │ │33 + const codepoint = iter.nextCodepointSlice() orelse break; │ │34 + end += codepoint.len; │ │35 + if (i == 7) clipped_end = end; │ │36 + } │ │37 + const clipped = iter.nextCodepointSlice() != null; │ │38 + return try std.fmt.bufPrint(buffer, "{s}{s}", .{ text[0..if (clipped) clipped_end else end], if (clipped) ".." else "" });│ │39 +} │ │40 + │ │41 pub const PageKind = enum { │ │42 home, │ │43 user, │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/ui/AuthTab.zig│ ╰──────────────────╯ ╭───────────────────────────────────────╮ │1 const std = @import("std"); │ │2 -const evt = @import("../event.zig");│ │2 const ui = @import("../ui.zig"); │ │3 const xit = @import("xit"); │ │4 const xitui = xit.xitui; │ ╰───────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │17 pub const View = struct { │ │18 text_box: wgt.TextBox(ui.Widget), │ │19 session: *ui.Session, │ │21 - // backs the tab's text, which borrows it. sized for the longest name, so │ │22 - // build never allocates. │ │23 - text_buf: [logout_prefix.len + evt.User.name_max_len]u8, │ │24 - │ │25 - const logout_prefix = "logout "; │ │20 + // backs the bottom label, which borrows it │ │21 + bottom_label_buf: [ui.clipped_bottom_label_max_len]u8, │ │22 │ │23 pub fn init(allocator: std.mem.Allocator, session: *ui.Session) !View { │ │24 var text_box = try wgt.TextBox(ui.Widget).init(allocator, "login", .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none });│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────╮ │28 return .{ │ │29 .text_box = text_box, │ │30 .session = session, │ │35 - .text_buf = undefined, │ │31 + .bottom_label_buf = undefined,│ │32 }; │ │33 } │ │34 │ ╰──────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │36 self.text_box.deinit(allocator); │ │37 } │ │38 │ │43 - // the action the tab performs, naming whoever is logged in │ │44 - fn text(self: *View) []const u8 { │ │45 - if (self.session.data.user_id == null) return "login"; │ │46 - const name = self.session.data.user_name orelse return "logout"; │ │47 - // validateName caps the name, so the buffer always fits it │ │48 - return std.fmt.bufPrint(&self.text_buf, logout_prefix ++ "{s}", .{name}) catch unreachable; │ │39 + fn text(self: *const View) []const u8 { │ │40 + return if (self.session.data.user_id == null) "login" else "logout"; │ │41 } │ │42 │ │51 - // what the tab's text needs, plus its border │ │43 + fn bottomLabel(self: *View) []const u8 { │ │44 + if (self.session.data.user_id == null) return ""; │ │45 + const name = self.session.data.user_name orelse return ""; │ │46 + return ui.clippedBottomLabel(&self.bottom_label_buf, name) catch unreachable; │ │47 + } │ │48 + │ │49 pub fn minWidth(self: *View) usize { │ │53 - return self.text().len + 2; │ │50 + return @max(self.text().len, self.bottomLabel().len) + 2; │ │51 } │ │52 │ │53 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void {│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────╮ │58 const text_widget = &self.text_box.box.children.values()[0].widget.text;│ │59 text_widget.content = self.text(); │ │60 } │ │61 + self.text_box.options.bottom_label = self.bottomLabel(); │ │62 try self.text_box.build(allocator, constraint, root_focus); │ │63 } │ │64 │ ╰────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────────╮ │src/ui/Repo/Header.zig│ ╰──────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │55 errdefer tab_ids.deinit(allocator); │ │56 │ │57 const aa = session.page_arena.allocator(); │ │58 + const ref_name = std.Uri.percentDecodeInPlace(try aa.dupe(u8, data.ref_or_oid_value)); │ │59 + const bottom_label = try ui.clippedBottomLabel(try aa.alloc(u8, ui.clipped_bottom_label_max_len), ref_name);│ │60 + const bottom_label_width = try xitui.width.displayWidth(bottom_label); │ │61 │ │62 // the user's name (local mode has no user pages to link to) │ │63 if (!session.data.is_local) { │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │150 │ │151 // files tab │ │152 { │ │150 - var text_box = try wgt.TextBox(ui.Widget).init(allocator, "files", .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); │ │153 + var text_box = try wgt.TextBox(ui.Widget).init(allocator, "files", .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none, .bottom_label = bottom_label });│ │154 errdefer text_box.deinit(allocator); │ │155 text_box.getFocus().focusable = true; │ │156 text_box.getFocus().kind = .{ .custom = files_link }; │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │159 try box.children.put(allocator, text_box.getFocus().id, .{ │ │160 .widget = .{ .text_box = text_box }, │ │161 .rect = null, │ │159 - .min_size = .{ .width = "files".len + 2, .height = null }, │ │162 + .min_size = .{ .width = @max("files".len, bottom_label_width) + 2, .height = null },│ │163 }); │ │164 } │ │165 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │166 // commits tab │ │167 { │ │165 - var text_box = try wgt.TextBox(ui.Widget).init(allocator, "commits", .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); │ │168 + var text_box = try wgt.TextBox(ui.Widget).init(allocator, "commits", .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none, .bottom_label = bottom_label });│ │169 errdefer text_box.deinit(allocator); │ │170 text_box.getFocus().focusable = true; │ │171 text_box.getFocus().kind = .{ .custom = commits_link }; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯