╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 94 } │ │ 95 } │ │ 96 │ │ 86 -pub fn initRoot(allocator: std.mem.Allocator, page: *const Page) !Widget { │ │ 97 +pub fn initRoot(allocator: std.mem.Allocator, page: *const Page, session: *Session) !Widget {│ │ 98 var root: Widget = switch (page.*) { │ │ 88 - .home => |*p| .{ .home = try Home.View.init(allocator, p) }, │ │ 99 + .home => |*p| .{ .home = try Home.View.init(allocator, p, session) }, │ │100 }; │ │101 errdefer root.deinit(); │ │102 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────╮ │115 text: wgt.Text(Widget), │ │116 box: wgt.Box(Widget), │ │117 text_box: wgt.TextBox(Widget), │ │118 + text_input: wgt.TextInput(Widget),│ │119 scroll: wgt.Scroll(Widget), │ │120 stack: wgt.Stack(Widget), │ │121 selectable_list: SelectableList, │ ╰───────────────────────────────────────────╯ ╭───────────────────────────────────────╮ │122 + spacer: Spacer, │ │123 + center: Center, │ │124 home: Home.View, │ │125 home_header: Home.Header.View,│ │126 home_users: Home.Users.View, │ ╰───────────────────────────────────────╯ ╭─────────────────────────────────────────────────╮ │127 home_repos: Home.Repos.View, │ │128 + home_auth_tab: Home.Header.AuthTab.View,│ │129 + home_auth: Home.Auth.View, │ │130 │ │131 pub fn deinit(self: *Widget) void { │ │132 switch (self.*) { │ ╰─────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │329 const inner_box = &self.scroll.child.box; │ │330 if (inner_box.children.values()[index].rect) |rect| { │ │331 self.scroll.scrollToRect(rect); │ │332 + } │ │333 + } │ │334 +}; │ │335 + │ │336 +// an invisible widget that fills the horizontal space granted by its parent. │ │337 +// used inside Box(horiz) with a min_size so the box reserves space for the │ │338 +// children that follow, pushing them to the right. │ │339 +pub const Spacer = struct { │ │340 + allocator: std.mem.Allocator, │ │341 + focus: Focus, │ │342 + grid: ?Grid, │ │343 + │ │344 + pub fn init(allocator: std.mem.Allocator) Spacer { │ │345 + return .{ │ │346 + .allocator = allocator, │ │347 + .focus = Focus.init(allocator, .container), │ │348 + .grid = null, │ │349 + }; │ │350 + } │ │351 + │ │352 + pub fn deinit(self: *Spacer) void { │ │353 + self.focus.deinit(); │ │354 + if (self.grid) |*grid| { │ │355 + grid.deinit(); │ │356 + self.grid = null; │ │357 + } │ │358 + } │ │359 + │ │360 + pub fn build(self: *Spacer, constraint: layout.Constraint, root_focus: *Focus) !void { │ │361 + _ = root_focus; │ │362 + self.clearGrid(); │ │363 + const width = constraint.max_size.width orelse return; │ │364 + if (width == 0) return; │ │365 + self.grid = try Grid.init(self.allocator, .{ .width = width, .height = 1 }); │ │366 + } │ │367 + │ │368 + pub fn input(self: *Spacer, key: inp.Key, root_focus: *Focus) !void { │ │369 + _ = self; │ │370 + _ = key; │ │371 + _ = root_focus; │ │372 + } │ │373 + │ │374 + pub fn clearGrid(self: *Spacer) void { │ │375 + if (self.grid) |*grid| { │ │376 + grid.deinit(); │ │377 + self.grid = null; │ │378 + } │ │379 + } │ │380 + │ │381 + pub fn getGrid(self: Spacer) ?Grid { │ │382 + return self.grid; │ │383 + } │ │384 + │ │385 + pub fn getFocus(self: *Spacer) *Focus { │ │386 + return &self.focus; │ │387 + } │ │388 +}; │ │389 + │ │390 +// a single-child wrapper that builds the child at its natural size and │ │391 +// positions its grid in the middle of the area granted by the parent │ │392 +pub const Center = struct { │ │393 + allocator: std.mem.Allocator, │ │394 + focus: Focus, │ │395 + grid: ?Grid, │ │396 + child: *Widget, │ │397 + direction: Direction, │ │398 + │ │399 + pub const Direction = enum { both, horiz, vert }; │ │400 + │ │401 + pub fn init(allocator: std.mem.Allocator, child_widget: Widget, direction: Direction) !Center {│ │402 + const child = try allocator.create(Widget); │ │403 + errdefer allocator.destroy(child); │ │404 + child.* = child_widget; │ │405 + return .{ │ │406 + .allocator = allocator, │ │407 + .focus = Focus.init(allocator, .container), │ │408 + .grid = null, │ │409 + .child = child, │ │410 + .direction = direction, │ │411 + }; │ │412 + } │ │413 + │ │414 + pub fn deinit(self: *Center) void { │ │415 + self.focus.deinit(); │ │416 + if (self.grid) |*grid| { │ │417 + grid.deinit(); │ │418 + self.grid = null; │ │419 } │ │420 + self.child.deinit(); │ │421 + self.allocator.destroy(self.child); │ │422 } │ │423 + │ │424 + pub fn build(self: *Center, constraint: layout.Constraint, root_focus: *Focus) !void { │ │425 + self.clearGrid(); │ │426 + self.getFocus().clear(); │ │427 + │ │428 + // build the child without forcing it to fill min_size; it sizes │ │429 + // itself to its content within the available max. │ │430 + try self.child.build(.{ │ │431 + .min_size = .{ .width = null, .height = null }, │ │432 + .max_size = constraint.max_size, │ │433 + }, root_focus); │ │434 + │ │435 + const child_grid = self.child.getGrid() orelse return; │ │436 + │ │437 + const width = constraint.max_size.width orelse child_grid.size.width; │ │438 + const height = constraint.max_size.height orelse child_grid.size.height; │ │439 + if (width == 0 or height == 0) return; │ │440 + │ │441 + const offset_x: usize = if (self.direction == .horiz or self.direction == .both) │ │442 + (width -| child_grid.size.width) / 2 │ │443 + else │ │444 + 0; │ │445 + const offset_y: usize = if (self.direction == .vert or self.direction == .both) │ │446 + (height -| child_grid.size.height) / 2 │ │447 + else │ │448 + 0; │ │449 + │ │450 + var grid = try Grid.init(self.allocator, .{ .width = width, .height = height }); │ │451 + errdefer grid.deinit(); │ │452 + try grid.drawGrid(child_grid, offset_x, offset_y); │ │453 + try self.getFocus().addChild(self.child.getFocus(), child_grid.size, offset_x, offset_y); │ │454 + self.getFocus().child_id = self.child.getFocus().id; │ │455 + │ │456 + self.grid = grid; │ │457 + } │ │458 + │ │459 + pub fn input(self: *Center, key: inp.Key, root_focus: *Focus) !void { │ │460 + try self.child.input(key, root_focus); │ │461 + } │ │462 + │ │463 + pub fn clearGrid(self: *Center) void { │ │464 + if (self.grid) |*grid| { │ │465 + grid.deinit(); │ │466 + self.grid = null; │ │467 + } │ │468 + self.child.clearGrid(); │ │469 + } │ │470 + │ │471 + pub fn getGrid(self: Center) ?Grid { │ │472 + return self.grid; │ │473 + } │ │474 + │ │475 + pub fn getFocus(self: *Center) *Focus { │ │476 + return &self.focus; │ │477 + } │ │478 }; │ │479 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────╮ │src/ui/Home.zig│ ╰───────────────╯ ╭────────────────────────────────────────────────────╮ │13 pub const Users = @import("./Home/Users.zig"); │ │14 pub const Repos = @import("./Home/Repos.zig"); │ │15 pub const Header = @import("./Home/Header.zig");│ │16 +pub const Auth = @import("./Home/Auth.zig"); │ │17 │ │18 header: Header, │ │19 users: Users, │ ╰────────────────────────────────────────────────────╯ ╭────────────────────────────────╮ │20 repos: Repos, │ │21 +auth: Auth, │ │22 +user_id: ?[]const u8 = null,│ │23 │ │24 const Self = @This(); │ │25 │ ╰────────────────────────────────╯ ╭───────────────────────────────────────────────╮ │27 comptime repo_opts: rp.RepoOpts(.xit), │ │28 arena: *std.heap.ArenaAllocator, │ │29 repo: *rp.Repo(.xit, repo_opts), │ │30 + session: *ui.Session, │ │31 ) !Self { │ │32 const DB = rp.Repo(.xit, repo_opts).DB;│ │33 │ ╰───────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │48 │ │49 const haxy_moment_cursor = try haxy_moments.getCursor(hash.bytesToInt(repo_opts.hash, &last_object_id)) orelse return error.NotFound;│ │50 const haxy_moment = try DB.HashMap(.read_only).init(haxy_moment_cursor); │ │51 + │ │52 + // hand the cursor + arena off to the session so Login (and any future │ │53 + // page that needs DB access at input time) can use them on demand. │ │54 + session.arena = arena; │ │55 + session.haxy_moment = haxy_moment; │ │56 │ │57 return .{ │ │58 .header = Header.init(), │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────────────╮ │59 .users = try Users.init(repo_opts, arena, haxy_moment),│ │60 .repos = try Repos.init(repo_opts, arena, haxy_moment),│ │61 + .auth = Auth.init(), │ │62 + .user_id = session.user_id, │ │63 }; │ │64 } │ │65 │ ╰───────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯