╭ 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 stack switches between two whole views; build() selects one based on 24 // whether a user is logged in. the logged-in view holds the settings 25 // controls (currently just the ANSI toggle, but it will grow). 26 const logged_in_index: usize = 0; 27 const logged_out_index: usize = 1; 28 29 pub fn init(allocator: std.mem.Allocator, data: *const Self, session: *ui.Session) !View { 30 var stack = try wgt.Stack(ui.Widget).init(allocator); 31 errdefer stack.deinit(allocator); 32 33 var button_id: usize = undefined; 34 // the logged-in view: the controls shown to a signed-in user. 35 { 36 var box = try wgt.Box(ui.Widget).init(allocator, .{ .border_style = null, .direction = .vert }); 37 errdefer box.deinit(allocator); 38 39 // on the web each control posts to a page-scoped form 40 box.getFocus().kind = .{ .custom = switch (session.data.current_page) { 41 .user, .user_settings, .user_auth => |name| try std.fmt.allocPrint(session.page_arena.allocator(), "form:/user/{s}/ansi", .{name.slice()}), 42 else => "form:/ansi", 43 } }; 44 45 { 46 var button = try wgt.TextBox(ui.Widget).init(allocator, ansiLabel(session), .{ .border_style = .single, .rounded_corners = true, .wrap_kind = .none }); 47 errdefer button.deinit(allocator); 48 // the renderer distinguishes plain clickables from buttons that 49 // should POST to a server route by this kind. 50 button.getFocus().kind = .{ .custom = "submit" }; 51 button.getFocus().focusable = true; 52 button_id = button.getFocus().id; 53 try box.children.put(allocator, button.getFocus().id, .{ .widget = .{ .text_box = button }, .rect = null, .min_size = null }); 54 } 55 56 box.getFocus().child_id = button_id; 57 try stack.children.put(allocator, box.getFocus().id, .{ .box = box }); 58 } 59 60 // the logged-out view: shown when no user is signed in. 61 { 62 const logged_out_message = 63 \\you must be logged in to see settings. 64 \\ 65 \\here's an emoticon from the early 2000s instead: 66 \\ 67 \\<('o'<) ^( '-' )^ (>'o')> 68 \\ 69 \\hope it helps. 70 ; 71 72 var message = try wgt.TextBox(ui.Widget).init(allocator, logged_out_message, .{ .border_style = null, .rounded_corners = false, .wrap_kind = .word }); 73 errdefer message.deinit(allocator); 74 try stack.children.put(allocator, message.getFocus().id, .{ .text_box = message }); 75 } 76 77 return .{ 78 .center = try ui.Center.init(allocator, .{ .stack = stack }, .both), 79 .data = data, 80 .session = session, 81 .button_id = button_id, 82 }; 83 } 84 85 pub fn deinit(self: *View, allocator: std.mem.Allocator) void { 86 self.center.deinit(allocator); 87 } 88 89 pub fn build(self: *View, allocator: std.mem.Allocator, constraint: layout.Constraint, root_focus: *Focus) !void { 90 // select the logged-in view when logged in, else the logged-out view. 91 // decided per-frame since the tty doesn't rebuild the page on login. 92 const logged_in = self.session.data.user_id != null; 93 const stack = &self.center.child.stack; 94 if (logged_in) { 95 const box = &stack.children.values()[logged_in_index].box; 96 const button = &box.children.values()[0].widget.text_box; 97 button.box.children.values()[0].widget.text.content = ansiLabel(self.session); 98 } 99 stack.getFocus().child_id = stack.children.keys()[if (logged_in) logged_in_index else logged_out_index]; 100 try self.center.build(allocator, constraint, root_focus); 101 } 102 103 pub fn input(self: *View, allocator: std.mem.Allocator, key: inp.Key, root_focus: *Focus) !void { 104 _ = allocator; 105 if (self.session.data.user_id == null) return; // blank/disabled when logged out 106 switch (key) { 107 .enter => try self.toggle(), 108 .mouse => |mouse| { 109 if (mouse.action == .press and mouse.action.press == .left) { 110 if (root_focus.children.get(self.button_id)) |entry| { 111 const r = entry.rect; 112 if (mouse.x >= r.x and mouse.y >= r.y and 113 mouse.x < r.x + r.size.width and mouse.y < r.y + r.size.height) 114 { 115 try self.toggle(); 116 } 117 } 118 } 119 }, 120 else => {}, 121 } 122 } 123 124 fn toggle(self: *View) !void { 125 try self.session.push(.toggle_ansi); 126 } 127 128 pub fn clearGrid(self: *View) void { 129 self.center.clearGrid(); 130 } 131 132 pub fn getGrid(self: View) ?Grid { 133 return self.center.getGrid(); 134 } 135 136 pub fn getFocus(self: *View) *Focus { 137 return self.center.getFocus(); 138 } 139 140 pub fn getSelectedIndex(self: View) ?usize { 141 _ = self; 142 return 0; 143 } 144 }; 145 146 fn ansiLabel(session: *const ui.Session) []const u8 { 147 const label_on = "turn off ANSI art"; 148 const label_off = "turn on ANSI art"; 149 return if (session.data.enable_ansi) label_on else label_off; 150 } 151