tangled
alpha
login
or
join now
rockorager.dev
/
comlink
2
fork
atom
an experimental irc client
2
fork
atom
overview
issues
pulls
pipelines
ui: message scrolling
rockorager.dev
1 year ago
8fc02e57
c3bfb030
+88
-8
2 changed files
expand all
collapse all
unified
split
build.zig.zon
src
irc.zig
+2
-2
build.zig.zon
···
7
7
.hash = "1220affeb3fe37ef09411b5a213b5fdf9bb6568e9913bade204694648983a8b2776d",
8
8
},
9
9
.vaxis = .{
10
10
-
.url = "git+https://github.com/rockorager/libvaxis#dbf7e0bf09118a80f7e6184cde1d8f096f82a7da",
11
11
-
.hash = "12207fb15ef5b259a95b581e663713883b172a98855e536a4487ae224b730290b564",
10
10
+
.url = "git+https://github.com/rockorager/libvaxis#0fb96df48ede1823107f9ea7dc9b4dc8f8399d9d",
11
11
+
.hash = "1220f03c86f6352a1e9ee24529e7c031b0bbe802145e5fe1b83beb7c524bfcaa02ba",
12
12
},
13
13
.zeit = .{
14
14
.url = "git+https://github.com/rockorager/zeit?ref=main#d943bc4bfe9e18490460dfdd64f48e997065eba8",
+86
-6
src/irc.zig
···
120
120
has_mouse: bool = false,
121
121
122
122
view: vxfw.SplitView,
123
123
-
message_view: vxfw.ListView,
124
123
member_view: vxfw.ListView,
125
124
text_field: vxfw.TextField,
125
125
+
126
126
+
scroll: struct {
127
127
+
/// Line offset from the bottom message
128
128
+
offset: u16 = 0,
129
129
+
/// Message offset into the list of messages. We use this to lock the viewport if we have a
130
130
+
/// scroll. Otherwise, when offset == 0 this is effectively ignored (and should be 0)
131
131
+
msg_offset: u32 = 0,
132
132
+
133
133
+
/// Pending scroll we have to handle while drawing. This could be up or down. By convention
134
134
+
/// we say positive is a scroll up.
135
135
+
pending: i17 = 0,
136
136
+
} = .{},
126
137
127
138
pub const Member = struct {
128
139
user: *User,
···
183
194
.width = 16,
184
195
.constrain = .rhs,
185
196
},
186
186
-
.message_view = .{ .children = .{ .slice = &.{} } },
187
197
.member_view = .{
188
198
.children = .{
189
199
.builder = .{
···
433
443
};
434
444
}
435
445
446
446
+
fn handleMessageViewEvent(ptr: *anyopaque, ctx: *vxfw.EventContext, event: vxfw.Event) anyerror!void {
447
447
+
const self: *Channel = @ptrCast(@alignCast(ptr));
448
448
+
switch (event) {
449
449
+
.mouse => |mouse| {
450
450
+
if (mouse.button == .wheel_down) {
451
451
+
self.scroll.pending -|= 3;
452
452
+
ctx.consume_event = true;
453
453
+
}
454
454
+
if (mouse.button == .wheel_up) {
455
455
+
self.scroll.pending +|= 3;
456
456
+
ctx.consume_event = true;
457
457
+
}
458
458
+
if (self.scroll.pending != 0) {
459
459
+
return self.doScroll(ctx);
460
460
+
}
461
461
+
},
462
462
+
.tick => try self.doScroll(ctx),
463
463
+
else => {},
464
464
+
}
465
465
+
}
466
466
+
467
467
+
/// Consumes any pending scrolls and schedules another tick if needed
468
468
+
fn doScroll(self: *Channel, ctx: *vxfw.EventContext) anyerror!void {
469
469
+
const animation_tick: u32 = 30;
470
470
+
// No pending scroll. Return early
471
471
+
if (self.scroll.pending == 0) return;
472
472
+
473
473
+
// Scroll up
474
474
+
if (self.scroll.pending > 0) {
475
475
+
// TODO: check if we need to get more history
476
476
+
// TODO: cehck if we are at oldest, and shouldn't scroll up anymore
477
477
+
478
478
+
// Consume 1 line, and schedule a tick
479
479
+
self.scroll.offset += 1;
480
480
+
self.scroll.pending -= 1;
481
481
+
ctx.redraw = true;
482
482
+
return ctx.tick(animation_tick, self.messageViewWidget());
483
483
+
}
484
484
+
485
485
+
// From here, we only scroll down. First, we check if we are at the bottom already. If we
486
486
+
// are, we have nothing to do
487
487
+
if (self.scroll.offset == 0) {
488
488
+
// Already at bottom. Nothing to do
489
489
+
self.scroll.pending = 0;
490
490
+
return;
491
491
+
}
492
492
+
493
493
+
// Scroll down
494
494
+
if (self.scroll.pending < 0) {
495
495
+
// Consume 1 line, and schedule a tick
496
496
+
self.scroll.offset -= 1;
497
497
+
self.scroll.pending += 1;
498
498
+
ctx.redraw = true;
499
499
+
return ctx.tick(animation_tick, self.messageViewWidget());
500
500
+
}
501
501
+
}
502
502
+
503
503
+
fn messageViewWidget(self: *Channel) vxfw.Widget {
504
504
+
return .{
505
505
+
.userdata = self,
506
506
+
.eventHandler = Channel.handleMessageViewEvent,
507
507
+
.drawFn = Channel.typeErasedDrawMessageView,
508
508
+
};
509
509
+
}
510
510
+
511
511
+
fn typeErasedDrawMessageView(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
512
512
+
const self: *Channel = @ptrCast(@alignCast(ptr));
513
513
+
return self.drawMessageView(ctx);
514
514
+
}
515
515
+
436
516
fn drawMessageView(self: *Channel, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
437
517
const max = ctx.max.size();
438
518
if (max.width == 0 or max.height == 0) {
439
519
return .{
440
520
.size = max,
441
441
-
.widget = self.contentWidget(),
521
521
+
.widget = self.messageViewWidget(),
442
522
.buffer = &.{},
443
523
.children = &.{},
444
524
};
···
447
527
var children = std.ArrayList(vxfw.SubSurface).init(ctx.arena);
448
528
449
529
// Row is the row we are printing on.
450
450
-
var row: i17 = max.height;
530
530
+
var row: i17 = max.height + self.scroll.offset;
451
531
var iter = std.mem.reverseIterator(self.messages.items);
452
532
const gutter_width = 6;
453
533
while (iter.next()) |msg| {
···
458
538
const text: vxfw.Text = .{ .text = msg.bytes };
459
539
const child_ctx = ctx.withConstraints(
460
540
.{ .height = 0, .width = 0 },
461
461
-
.{ .width = max.width - gutter_width, .height = null },
541
541
+
.{ .width = max.width -| gutter_width, .height = null },
462
542
);
463
543
const surface = try text.draw(child_ctx);
464
544
···
491
571
492
572
return .{
493
573
.size = max,
494
494
-
.widget = self.contentWidget(),
574
574
+
.widget = self.messageViewWidget(),
495
575
.buffer = &.{},
496
576
.children = children.items,
497
577
};