╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────╮ │← all files│ ╰───────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │11 const Key = xitui.input.Key; │ │12 const Grid = xitui.grid.Grid; │ │13 const Focus = xitui.focus.Focus; │ │14 +const ansi_arts: []const []const u8 = if (builtin.cpu.arch == .wasm32) &.{} else @import("ansi_art").art;│ │15 const evt = @import("./event.zig"); │ │16 const inp = @import("./ui/input.zig"); │ │17 │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────╮ │1418 current_page: RoutablePage = .default, │ │1419 // whether to render the ANSI art backdrop │ │1420 enable_ansi: bool = true, │ │1421 + // the selected ANSI art. the server serializes one image for WASM so │ │1422 + // the browser does not need the whole build-generated collection. │ │1423 + ansi_art: []const u8 = "", │ │1424 // the bound clone-service ports. absent in local mode, where clone urls│ │1425 // are not shown. │ │1426 clone_http_port: ?u16 = null, │ ╰──────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────╮ │1920 self.arena = entry.arena; │ │1921 session.page_arena = entry.arena; │ │1922 session.data.current_page = entry.route; │ │1923 + chooseAnsiArtForNavigation(session); │ │1924 return; │ │1925 } │ │1926 // nothing to go back to; switch to the quit confirmation│ ╰───────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────╮ │1987 .repo => |*p| .{ .repo = try .init(allocator, p, session) }, │ │1988 }; │ │1989 │ │1985 - const demon_art = @embedFile("embed/demon.ans"); │ │1990 + chooseAnsiArtForNavigation(session); │ │1991 │ │1992 // on the TUI/SSH, the page sits above a one-row footer showing the url│ │1993 var root = if (session.is_terminal) blk: { │ ╰─────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │1994 var box = try wgt.Box(Widget).init(allocator, .{ .border_style = null, .direction = .vert }); │ │1995 errdefer box.deinit(allocator); │ │1996 const bg_id = bg_blk: { │ │1992 - var background = try AnsiBackground.init(allocator, page_widget, demon_art, session); │ │1997 + var background = try AnsiBackground.init(allocator, page_widget, session); │ │1998 errdefer background.deinit(allocator); │ │1999 const id = background.getFocus().id; │ │2000 try box.children.put(allocator, id, .{ .widget = .{ .background = background }, .rect = null, .min_size = null });│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2007 } │ │2008 box.getFocus().child_id = bg_id; │ │2009 break :blk Widget{ .box = box }; │ │2005 - } else Widget{ .background = try AnsiBackground.init(allocator, page_widget, demon_art, session) };│ │2010 + } else Widget{ .background = try AnsiBackground.init(allocator, page_widget, session) }; │ │2011 errdefer root.deinit(allocator); │ │2012 │ │2013 // input-owning views build their TextInputs in init — so reset the │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │2023 return root; │ │2024 } │ │2025 │ │2026 +// native and server-side sessions choose from the embedded collection. WASM│ │2027 +// keeps the art serialized by the server instead. │ │2028 +fn chooseAnsiArtForNavigation(session: *Session) void { │ │2029 + if (comptime ansi_arts.len == 0) return; │ │2030 + const io = session.io orelse return; │ │2031 + var random: usize = undefined; │ │2032 + io.random(std.mem.asBytes(&random)); │ │2033 + session.data.ansi_art = ansi_arts[random % ansi_arts.len]; │ │2034 +} │ │2035 + │ │2036 pub const Widget = union(enum) { │ │2037 text: wgt.Text(Widget), │ │2038 box: wgt.Box(Widget), │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │2963 const len = std.unicode.utf8ByteSequenceLength(byte) catch 1; │ │2964 const end = @min(content.len, i + len); │ │2965 const rune = content[i..end]; │ │2951 - const transparent = end - i == 1 and rune[0] == ' ' and │ │2952 - style.fg == null and style.bg == null and !style.inverted; │ │2966 + // a foreground color does not make a space visible. only an │ │2967 + // effective background makes it opaque; foreground state often │ │2968 + // remains active through the padding at the end of a row. │ │2969 + const effective_background = if (style.inverted) style.fg else style.bg; │ │2970 + const transparent = end - i == 1 and rune[0] == ' ' and effective_background == null;│ │2971 try row.append(allocator, .{ │ │2972 .rune = if (transparent) null else rune, │ │2973 .style = style, │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │3063 art: AnsiArt, │ │3064 session: *Session, │ │3065 │ │3048 - pub fn init(allocator: std.mem.Allocator, child_widget: Widget, art_content: []const u8, session: *Session) !AnsiBackground {│ │3066 + pub fn init(allocator: std.mem.Allocator, child_widget: Widget, session: *Session) !AnsiBackground { │ │3067 var cw = child_widget; │ │3068 const child = allocator.create(Widget) catch |e| { │ │3069 cw.deinit(allocator); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │3074 child.deinit(allocator); │ │3075 allocator.destroy(child); │ │3076 } │ │3059 - return .{ .grid = null, .child = child, .art = try AnsiArt.init(allocator, art_content), .session = session }; │ │3077 + return .{ .grid = null, .child = child, .art = try AnsiArt.init(allocator, session.data.ansi_art), .session = session };│ │3078 } │ │3079 │ │3080 pub fn deinit(self: *AnsiBackground, allocator: std.mem.Allocator) void { │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯