an experimental irc client

ui: only mark read from the draw function

We only ever mark a channel read when we are drawing. This keeps the
logic very simple: We must be at the bottom of the viewport, have unread
messages, and have focus.

rockorager.dev de640965 aaf43575

verified
+22 -29
+2 -2
build.zig.zon
··· 7 7 .hash = "1220affeb3fe37ef09411b5a213b5fdf9bb6568e9913bade204694648983a8b2776d", 8 8 }, 9 9 .vaxis = .{ 10 - .url = "git+https://github.com/rockorager/libvaxis#bcc1d027cb2ede571bb76669441d2e09944bd3d3", 11 - .hash = "1220fca568653885767394bc659714e0e9ccda935e5c02d4333b1d60b064bc6ac0bc", 10 + .url = "git+https://github.com/rockorager/libvaxis#72f1e223332aa15b48d606570f7af0b686325403", 11 + .hash = "1220e7560c2499f2edca94a918c4b0fadb31c85b92ec4164bef4d39bf762c17b23b2", 12 12 }, 13 13 .zeit = .{ 14 14 .url = "git+https://github.com/rockorager/zeit?ref=main#d943bc4bfe9e18490460dfdd64f48e997065eba8",
+7 -18
src/app.zig
··· 78 78 ctx: ?*vxfw.EventContext, 79 79 last_height: u16, 80 80 81 + /// Whether the application has focus or not 82 + has_focus: bool, 83 + 81 84 const default_rhs: vxfw.Text = .{ .text = "TODO: update this text" }; 82 85 83 86 /// initialize vaxis, lua state ··· 115 118 .title_buf = undefined, 116 119 .ctx = null, 117 120 .last_height = 0, 121 + .has_focus = true, 118 122 }; 119 123 120 124 self.lua = try Lua.init(&self.alloc); ··· 242 246 } 243 247 } 244 248 }, 249 + .focus_out => self.has_focus = false, 250 + .focus_in => self.has_focus = true, 251 + 245 252 else => {}, 246 253 } 247 254 } ··· 329 336 } 330 337 331 338 pub fn nextChannel(self: *App) void { 332 - // When leaving a channel we mark it as read, so we make sure that's done 333 - // before we change to the new channel. 334 - self.markSelectedChannelRead(); 335 339 if (self.ctx) |ctx| { 336 340 self.buffer_list.nextItem(ctx); 337 341 if (self.selectedBuffer()) |buffer| { ··· 348 352 } 349 353 350 354 pub fn prevChannel(self: *App) void { 351 - // When leaving a channel we mark it as read, so we make sure that's done 352 - // before we change to the new channel. 353 - self.markSelectedChannelRead(); 354 355 if (self.ctx) |ctx| { 355 356 self.buffer_list.prevItem(ctx); 356 357 if (self.selectedBuffer()) |buffer| { ··· 555 556 } 556 557 557 558 pub fn selectBuffer(self: *App, buffer: irc.Buffer) void { 558 - self.markSelectedChannelRead(); 559 559 var i: u32 = 0; 560 560 switch (buffer) { 561 561 .client => |target| { ··· 589 589 } 590 590 } 591 591 }, 592 - } 593 - } 594 - 595 - pub fn markSelectedChannelRead(self: *App) void { 596 - const buffer = self.selectedBuffer() orelse return; 597 - 598 - switch (buffer) { 599 - .channel => |channel| { 600 - if (channel.messageViewIsAtBottom()) channel.markRead() catch return; 601 - }, 602 - else => {}, 603 592 } 604 593 } 605 594 };
+13 -9
src/irc.zig
··· 126 126 // The location of the last read indicator. This doesn't necessarily match the state of 127 127 // last_read 128 128 last_read_indicator: u32 = 0, 129 - scroll_to_last_read: bool = true, 129 + scroll_to_last_read: bool = false, 130 130 has_unread: bool = false, 131 131 has_unread_highlight: bool = false, 132 132 ··· 382 382 pub fn doSelect(self: *Channel) void { 383 383 // Set the state of the last_read_indicator 384 384 self.last_read_indicator = self.last_read; 385 + if (self.has_unread) { 386 + self.scroll_to_last_read = true; 387 + } 385 388 } 386 389 387 390 fn typeErasedEventHandler(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void { ··· 912 915 // True when we *don't* need to scroll to last message. False if we do. We will turn this 913 916 // true when we have it the last message 914 917 var did_scroll_to_last_read = !self.scroll_to_last_read; 918 + // We track whether we need to reposition the viewport based on the position of the 919 + // last_read scroll 920 + var needs_reposition = true; 915 921 while (iter.next()) |msg| { 922 + if (row >= 0 and did_scroll_to_last_read) { 923 + needs_reposition = false; 924 + } 916 925 // Break if we have gone past the top of the screen 917 926 if (row < 0 and did_scroll_to_last_read) break; 918 927 ··· 1157 1166 // check that we have messages, and if we do that the top message is outside the viewport. 1158 1167 // If we don't have messages, or the top message is within the viewport, we don't have to 1159 1168 // reposition 1160 - if (self.scroll_to_last_read and 1169 + if (needs_reposition and 1161 1170 children.items.len > 0 and 1162 1171 children.getLast().origin.row < 0) 1163 1172 { ··· 1175 1184 self.scroll_to_last_read = false; 1176 1185 } 1177 1186 1178 - if (self.has_unread and self.messageViewIsAtBottom()) { 1187 + if (self.has_unread and self.client.app.has_focus and self.messageViewIsAtBottom()) { 1179 1188 try self.markRead(); 1180 1189 } 1181 1190 ··· 2225 2234 channel.has_unread_highlight = has_highlight; 2226 2235 channel.has_unread = true; 2227 2236 } 2228 - // If we get a message from the current user mark the channel as 2229 - // read, since they must have just sent the message. 2230 - const sender = msg2.senderNick() orelse ""; 2231 - if (std.mem.eql(u8, sender, client.nickname())) { 2232 - self.app.markSelectedChannelRead(); 2233 - } 2234 2237 2235 2238 // Set the typing time to 0 2239 + const sender = msg2.senderNick() orelse ""; 2236 2240 for (channel.members.items) |*member| { 2237 2241 if (!std.mem.eql(u8, member.user.nick, sender)) { 2238 2242 continue;