╭ Settings.zig ─────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ╰───────────────────────────────────────────────────────────────╯
╭────╮ ╭ clone ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╮ │http│ ssh │ │ ╰────╯ ╰ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ╯
1 const std = @import("std"); 2 const ui = @import("../ui.zig"); 3 const xit = @import("xit"); 4 const xitui = xit.xitui; 5 const wgt = xitui.widget; 6 const layout = xitui.layout; 7 const inp = xitui.input; 8 const Grid = xitui.grid.Grid; 9 const Focus = xitui.focus.Focus; 10 11 const Self = @This(); 12 13 pub fn init() Self { 14 return .{}; 15 } 16 17 pub const View = struct { 18 center: ui.Center, 19 data: *const Self, 20 session: *ui.Session, 21 button_id: usize, 22 23 // the box holds the toggle button and the logged-out message; build() shows 24 // exactly one of them based on whether a user is logged in. 25 const button_index: usize = 0; 26 const message_index: usize = 1; 27 28 pub fn init(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !View { 29 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .rounded_corners = true, .direction = .vert }); 30 errdefer box.deinit(allocator); 31 // on the web the toggle is an overlay form posting to a page-scoped /ansi 32 // path; only mark it a form when logged in so a logged-out page stays 33 // blank (the web rebuilds per request, so this is correct there; the tty 34 // ignores the form kind and toggles in-process via input). 35 if (session.data.user_id != null) { 36 box.getFocus().kind = .{ .custom = switch (session.data.current_page) { 37 .user, .user_settings, .user_auth => |name| try std.fmt.allocPrint(session.page_arena.allocator(), "form:/user/{s}/ansi", .{name.slice()}), 38 else => "form:/ansi", 39 } }; 40 } 41 42 var button_id: usize = undefined; 43 { 44 var button = try wgt.TextBox(ui.Widget).init(allocator, ansiLabel(session), .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); 45 errdefer button.deinit(allocator); 46 // the renderer distinguishes plain clickables from buttons that 47 // should POST to a server route by this kind. 48 button.getFocus().kind = .{ .custom = "submit" }; 49 button_id = button.getFocus().id; 50 try box.children.put(allocator, button.getFocus().id, .{ 51 .widget = .{ .text_box = button }, 52 .rect = null, 53 .min_size = null, 54 }); 55 } 56 57 // the logged-out message (shown in place of the toggle) 58 { 59 const logged_out_message = 60 \\you must be logged in to see settings :( 61 \\ 62 \\instead, here's an emoticon from the early 2000s: 63 \\<('o'<) ^( '-' )^ (>'o')> 64 \\ 65 \\hope it helps. 66 ; 67 68 var message = try wgt.TextBox(ui.Widget).init(allocator, logged_out_message, .{ .border_style = null, .rounded_corners = false, .wrap_kind = .word }); 69 errdefer message.deinit(allocator); 70 try box.children.put(allocator, message.getFocus().id, .{ 71 .widget = .{ .text_box = message }, 72 .rect = null, 73 .min_size = null, 74 }); 75 } 76 77 box.getFocus().child_id = button_id; 78 79 return .{ 80 .center = try ui.Center.init(allocator, .{ .box = box }, .both), 81 .data = data, 82 .session = session, 83 .button_id = button_id, 84 }; 85 } 86 87 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { 88 self.center.deinit(allocator); 89 } 90 91 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 92 // show the toggle when logged in, else the message. decided per-frame 93 // since the tty doesn't rebuild the page on login. a zero max height 94 // hides the inactive one (it builds to nothing). 95 const logged_in = self.session.data.user_id != null; 96 const box = &self.center.child.box; 97 const button = &box.children.values()[button_index].widget.text_box; 98 if (logged_in) button.box.children.values()[0].widget.text.content = ansiLabel(self.session); 99 button.getFocus().focusable = logged_in; 100 const hidden = layout.MaybeSize{ .width = null, .height = 0 }; 101 box.children.values()[button_index].max_size = if (logged_in) null else hidden; 102 box.children.values()[message_index].max_size = if (logged_in) hidden else null; 103 try self.center.build(allocator, constraint, root_focus); 104 } 105 106 pub fn input(self: *View, allocator: std.mem.Allocator, key: inp.Key, root_focus: *Focus) !void { 107 _ = allocator; 108 if (self.session.data.user_id == null) return; // blank/disabled when logged out 109 switch (key) { 110 .enter => try self.toggle(), 111 .mouse => |mouse| { 112 if (mouse.action == .press and mouse.action.press == .left) { 113 if (root_focus.children.get(self.button_id)) |entry| { 114 const r = entry.rect; 115 if (mouse.x >= r.x and mouse.y >= r.y and 116 mouse.x < r.x + r.size.width and mouse.y < r.y + r.size.height) 117 { 118 try self.toggle(); 119 } 120 } 121 } 122 }, 123 else => {}, 124 } 125 } 126 127 // enqueue the toggle; the host drains it (applying + persisting) this frame. 128 fn toggle(self: *View) !void { 129 try self.session.push(.toggle_ansi); 130 } 131 132 pub fn clearGrid(self: *View) void { 133 self.center.clearGrid(); 134 } 135 136 pub fn getGrid(self: View) ?Grid { 137 return self.center.getGrid(); 138 } 139 140 pub fn getFocus(self: *View) *Focus { 141 return self.center.getFocus(); 142 } 143 144 pub fn getSelectedIndex(self: View) ?usize { 145 _ = self; 146 return 0; 147 } 148 }; 149 150 fn ansiLabel(session: *const ui.Session) []const u8 { 151 const label_on = "turn off ANSI art"; 152 const label_off = "turn on ANSI art"; 153 return if (session.data.enable_ansi) label_on else label_off; 154 } 155