╭ changed ────╮ │build.zig.zon│ ╰─────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 3 .version = "0.0.0", │ │ 4 .dependencies = .{ │ │ 5 .xit = .{ │ │ 6 - .url = "git+https://github.com/xit-vcs/xit#008ed65f25d9329b5962aba83ce376755fbd9a81",│ │ 7 - .hash = "xit-0.0.0-Ub3nDDJwTwEKnClMx5uGt87k1VJXo7j803xCnEV8mJs8", │ │ 6 + .url = "git+https://github.com/xit-vcs/xit#f835499d7d5e5c0f60d33d60325d074d650a4726",│ │ 7 + .hash = "xit-0.0.0-Ub3nDAhyTwEwgDsTE9GFbSolbs15C6nJbjGefXvdxhjo", │ │ 8 }, │ │ 9 }, │ │10 .paths = .{ │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────────╮ │src/main_wasm.zig│ ╰─────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │26 │ │27 fn tick() !void { │ │28 const root_ptr = if (root) |*root_value| root_value else return error.NotStarted;│ │29 - try ui.buildRoot(root_ptr, 80, 24); │ │29 + // TODO: make this fit the current size of the browser window │ │30 + try root_ptr.build(.{ │ │31 + .min_size = .{ .width = null, .height = null }, │ │32 + .max_size = .{ .width = 80, .height = 24 }, │ │33 + }, root_ptr.getFocus()); │ │34 try updateHtml(); │ │35 } │ │36 │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ────╮ │src/serve.zig│ ╰─────────────╯ ╭──────────────────────────────────────────────────────────────────╮ │86 web.run(io, allocator, &wui_server, &tasks, err); │ │87 │ │88 if (options.tui) { │ │89 - runTui(io, allocator, &tasks, err); │ │90 - │ │91 - while (!xit.xitui.terminal.quit) { │ │92 - try std.Io.sleep(io, .fromMilliseconds(5), .real);│ │93 - } │ │89 + try ui.run(io, allocator); │ │90 } else { │ │91 try tasks.await(io); │ │92 } │ ╰──────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │ 97 -} │ │ 98 - │ │ 99 -fn runTui( │ │100 - io: std.Io, │ │101 - allocator: std.mem.Allocator, │ │102 - tasks: *std.Io.Group, │ │103 - err: *std.Io.Writer, │ │104 -) void { │ │105 - const TuiTask = struct { │ │106 - io: std.Io, │ │107 - allocator: std.mem.Allocator, │ │108 - err: *std.Io.Writer, │ │109 - │ │110 - fn run(ctx: @This()) void { │ │111 - ui.run(ctx.io, ctx.allocator) catch |ui_err| { │ │112 - if (ui_err == error.Canceled) return; │ │113 - logError(ctx.err, "ui failed: {s}\n", .{@errorName(ui_err)});│ │114 - }; │ │115 - } │ │116 - }; │ │117 - │ │118 - tasks.async(io, TuiTask.run, .{TuiTask{ │ │119 - .io = io, │ │120 - .allocator = allocator, │ │121 - .err = err, │ │122 - }}); │ │ 93 } │ │ 94 │ │ 95 fn runHttpListener( │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─╮ │src/ui.zig│ ╰──────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 7 const inp = xitui.input; │ │ 8 const Grid = xitui.grid.Grid; │ │ 9 const Focus = xitui.focus.Focus; │ │10 + │ │11 +pub fn run(io: std.Io, allocator: std.mem.Allocator) !void { │ │12 + var root = try initRoot(allocator); │ │13 + defer root.deinit(); │ │14 │ │11 -const html_width = 80; │ │12 -const html_height = 24; │ │15 + var terminal = try term.Terminal.init(io, allocator); │ │16 + defer terminal.deinit(io); │ │17 + │ │18 + var last_size = layout.Size{ .width = 0, .height = 0 }; │ │19 + var last_grid = try Grid.init(allocator, last_size); │ │20 + defer last_grid.deinit(); │ │21 + │ │22 + while (!term.quit.load(.monotonic)) { │ │23 + const grid_changed = try terminal.render(&root, &last_grid, &last_size); │ │24 + │ │25 + // process any inputs. │ │26 + // │ │27 + // if the grid didn't change, then first do a blocking │ │28 + // read, so the thread will sleep until further input. │ │29 + // after that, all remaining reads are non-blocking so │ │30 + // we can process the rest of the queued inputs. │ │31 + // │ │32 + // if the grid *did* change, then only do non-blocking │ │33 + // reads. we do not want to sleep the thread because │ │34 + // there may be an animation that requires more looping. │ │35 + var blocking = !grid_changed; │ │36 + while (try terminal.readKey(io, blocking)) |key| { │ │37 + switch (key) { │ │38 + .codepoint => |cp| if (cp == 'q') return else try root.input(key, root.getFocus()),│ │39 + else => try root.input(key, root.getFocus()), │ │40 + } │ │41 + blocking = false; │ │42 + } │ │43 + │ │44 + try root.build(.{ │ │45 + .min_size = .{ .width = null, .height = null }, │ │46 + .max_size = .{ .width = last_size.width, .height = last_size.height }, │ │47 + }, root.getFocus()); │ │48 + } │ │49 +} │ │50 │ │51 pub fn initRoot(allocator: std.mem.Allocator) !Widget { │ │52 var root = Widget{ .widget_list = try WidgetList.init(allocator) }; │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────────────────────╮ │53 errdefer root.deinit(); │ │54 │ │18 - try buildRoot(&root, html_width, html_height); │ │55 + try root.build(.{ │ │56 + .min_size = .{ .width = null, .height = null },│ │57 + .max_size = .{ .width = 80, .height = 24 }, │ │58 + }, root.getFocus()); │ │59 if (root.getFocus().child_id) |child_id| { │ │60 try root.getFocus().setFocus(child_id); │ │61 } │ ╰───────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────╮ │63 return root; │ │64 } │ │65 │ │26 -pub fn buildRoot(root: *Widget, width: usize, height: usize) !void { │ │27 - try root.build(.{ │ │28 - .min_size = .{ .width = null, .height = null }, │ │29 - .max_size = .{ .width = width, .height = height }, │ │30 - }, root.getFocus()); │ │31 -} │ │32 - │ │66 pub fn generateHtml(allocator: std.mem.Allocator, root: *Widget) ![]const u8 {│ │67 const grid = root.getGrid() orelse return error.MissingGrid; │ │68 │ ╰──────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │78 } │ │79 │ │80 return try out.toOwnedSlice(allocator); │ │48 -} │ │49 - │ │50 -pub fn run(io: std.Io, allocator: std.mem.Allocator) !void { │ │51 - var root = try initRoot(allocator); │ │52 - defer root.deinit(); │ │53 - │ │54 - var terminal = try term.Terminal.init(io, allocator); │ │55 - defer terminal.deinit(io); │ │56 - │ │57 - var last_size = layout.Size{ .width = 0, .height = 0 }; │ │58 - var last_grid = try Grid.init(allocator, last_size); │ │59 - defer last_grid.deinit(); │ │60 - │ │61 - while (!term.quit) { │ │62 - try terminal.render(&root, &last_grid, &last_size); │ │63 - │ │64 - while (try terminal.readKey(io)) |key| { │ │65 - switch (key) { │ │66 - .codepoint => |cp| if (cp == 'q') { │ │67 - term.quit = true; │ │68 - return; │ │69 - }, │ │70 - else => {}, │ │71 - } │ │72 - try root.input(key, root.getFocus()); │ │73 - } │ │74 - │ │75 - try buildRoot(&root, last_size.width, last_size.height); │ │76 - │ │77 - try std.Io.sleep(io, .fromMilliseconds(5), .real); │ │78 - } │ │81 } │ │82 │ │83 fn appendEscapedHtml(allocator: std.mem.Allocator, out: *std.ArrayList(u8), input: []const u8) !void {│ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────╯