···128128 offset: u16 = 0,
129129 /// Message offset into the list of messages. We use this to lock the viewport if we have a
130130 /// scroll. Otherwise, when offset == 0 this is effectively ignored (and should be 0)
131131- msg_offset: u32 = 0,
131131+ msg_offset: ?u16 = null,
132132133133 /// Pending scroll we have to handle while drawing. This could be up or down. By convention
134134 /// we say positive is a scroll up.
135135- pending: i17 = 0,
135135+ pending: i16 = 0,
136136 } = .{},
137137138138 pub const Member = struct {
···466466467467 /// Consumes any pending scrolls and schedules another tick if needed
468468 fn doScroll(self: *Channel, ctx: *vxfw.EventContext) anyerror!void {
469469+ defer {
470470+ // At the end of this function, we anchor our msg_offset if we have any amount of
471471+ // scroll. This prevents new messages from automatically scrolling us
472472+ if (self.scroll.offset > 0 and self.scroll.msg_offset == null) {
473473+ self.scroll.msg_offset = @intCast(self.messages.items.len);
474474+ }
475475+ // If we have no offset, we reset our anchor
476476+ if (self.scroll.offset == 0) {
477477+ self.scroll.msg_offset = null;
478478+ }
479479+ }
469480 const animation_tick: u32 = 30;
470481 // No pending scroll. Return early
471482 if (self.scroll.pending == 0) return;
···526537527538 var children = std.ArrayList(vxfw.SubSurface).init(ctx.arena);
528539529529- // Row is the row we are printing on.
540540+ // Row is the row we are printing on. We add the offset to achieve our scroll location
530541 var row: i17 = max.height + self.scroll.offset;
531531- var iter = std.mem.reverseIterator(self.messages.items);
542542+543543+ const offset = self.scroll.msg_offset orelse self.messages.items.len;
544544+545545+ var iter = std.mem.reverseIterator(self.messages.items[0..offset]);
532546 const gutter_width = 6;
533547 while (iter.next()) |msg| {
534548 // Break if we have gone past the top of the screen