tangled
alpha
login
or
join now
rockorager.dev
/
comlink
2
fork
atom
an experimental irc client
2
fork
atom
overview
issues
pulls
pipelines
ui: flesh out channel view
rockorager.dev
1 year ago
c43db93c
aebbc4a5
+163
-38
3 changed files
expand all
collapse all
unified
split
src
app.zig
irc.zig
main.zig
+17
-30
src/app.zig
···
83
83
write_queue: comlink.WriteQueue,
84
84
write_thread: std.Thread,
85
85
86
86
-
lhs: vxfw.SplitView,
87
87
-
rhs: vxfw.SplitView,
86
86
+
view: vxfw.SplitView,
88
87
buffer_list: vxfw.ListView,
88
88
+
unicode: *const vaxis.Unicode,
89
89
+
90
90
+
const default_rhs: vxfw.Text = .{ .text = "TODO: update this text" };
89
91
90
92
/// initialize vaxis, lua state
91
91
-
pub fn init(self: *App, gpa: std.mem.Allocator) !void {
93
93
+
pub fn init(self: *App, gpa: std.mem.Allocator, unicode: *const vaxis.Unicode) !void {
92
94
self.* = .{
93
95
.alloc = gpa,
94
96
.state = .{},
···
100
102
.lua = undefined,
101
103
.write_queue = .{},
102
104
.write_thread = undefined,
103
103
-
.lhs = .{
105
105
+
.view = .{
104
106
.width = self.state.buffers.width,
105
107
.lhs = self.buffer_list.widget(),
106
106
-
.rhs = self.rhs.widget(),
107
107
-
},
108
108
-
.rhs = .{
109
109
-
.width = self.state.members.width,
110
110
-
.constrain = .rhs,
111
111
-
.lhs = self.contentWidget(),
112
112
-
.rhs = self.memberWidget(),
108
108
+
.rhs = default_rhs.widget(),
113
109
},
114
110
.explicit_join = false,
115
111
.bundle = .{},
···
125
121
},
126
122
.draw_cursor = false,
127
123
},
124
124
+
.unicode = unicode,
128
125
};
129
126
130
127
self.lua = try Lua.init(&self.alloc);
···
233
230
234
231
fn typeErasedDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
235
232
const self: *App = @ptrCast(@alignCast(ptr));
233
233
+
if (self.selectedBuffer()) |buffer| {
234
234
+
switch (buffer) {
235
235
+
.client => |client| self.view.rhs = client.view(),
236
236
+
.channel => |channel| self.view.rhs = channel.view.widget(),
237
237
+
}
238
238
+
} else self.view.rhs = default_rhs.widget();
236
239
237
240
var children = std.ArrayList(vxfw.SubSurface).init(ctx.arena);
238
241
_ = &children;
···
248
251
249
252
const sub: vxfw.SubSurface = .{
250
253
.origin = .{ .col = 0, .row = 0 },
251
251
-
.surface = try self.lhs.widget().draw(ctx),
254
254
+
.surface = try self.view.widget().draw(ctx),
252
255
};
253
256
try children.append(sub);
254
257
···
260
263
};
261
264
}
262
265
263
263
-
fn bufferWidget(self: *App) vxfw.Widget {
264
264
-
return .{
265
265
-
.userdata = self,
266
266
-
.captureHandler = null,
267
267
-
.eventHandler = null,
268
268
-
.drawFn = App.typeErasedBufferDrawFn,
269
269
-
};
270
270
-
}
271
271
-
272
266
fn bufferBuilderFn(ptr: *const anyopaque, idx: usize, cursor: usize) ?vxfw.Widget {
273
267
const self: *const App = @ptrCast(@alignCast(ptr));
274
268
var i: usize = 0;
···
281
275
}
282
276
}
283
277
return null;
284
284
-
}
285
285
-
286
286
-
fn typeErasedBufferDrawFn(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
287
287
-
const self: *App = @ptrCast(@alignCast(ptr));
288
288
-
_ = self;
289
289
-
const text: vxfw.Text = .{ .text = "buffers" };
290
290
-
return text.draw(ctx);
291
278
}
292
279
293
280
fn contentWidget(self: *App) vxfw.Widget {
···
1154
1141
pub fn selectedBuffer(self: *App) ?irc.Buffer {
1155
1142
var i: usize = 0;
1156
1143
for (self.clients.items) |client| {
1157
1157
-
if (i == self.state.buffers.selected_idx) return .{ .client = client };
1144
1144
+
if (i == self.buffer_list.cursor) return .{ .client = client };
1158
1145
i += 1;
1159
1146
for (client.channels.items) |channel| {
1160
1160
-
if (i == self.state.buffers.selected_idx) return .{ .channel = channel };
1147
1147
+
if (i == self.buffer_list.cursor) return .{ .channel = channel };
1161
1148
i += 1;
1162
1149
}
1163
1150
}
+145
-7
src/irc.zig
···
119
119
120
120
has_mouse: bool = false,
121
121
122
122
+
view: vxfw.SplitView,
123
123
+
message_view: vxfw.ListView,
124
124
+
member_view: vxfw.ListView,
125
125
+
text_field: vxfw.TextField,
126
126
+
122
127
pub const Member = struct {
123
128
user: *User,
124
129
···
133
138
else
134
139
std.ascii.orderIgnoreCase(lhs.user.nick, rhs.user.nick).compare(.lt);
135
140
}
141
141
+
142
142
+
pub fn widget(self: *Member) vxfw.Widget {
143
143
+
return .{
144
144
+
.userdata = self,
145
145
+
.drawFn = Member.draw,
146
146
+
};
147
147
+
}
148
148
+
149
149
+
pub fn draw(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
150
150
+
const self: *Member = @ptrCast(@alignCast(ptr));
151
151
+
const style: vaxis.Style = if (self.user.away)
152
152
+
.{ .dim = true }
153
153
+
else
154
154
+
.{ .fg = self.user.color };
155
155
+
var prefix = try ctx.arena.alloc(u8, 1);
156
156
+
prefix[0] = self.prefix;
157
157
+
const text: vxfw.RichText = .{
158
158
+
.text = &.{
159
159
+
.{ .text = prefix, .style = style },
160
160
+
.{ .text = self.user.nick, .style = style },
161
161
+
},
162
162
+
.softwrap = false,
163
163
+
};
164
164
+
return text.draw(ctx);
165
165
+
}
136
166
};
137
167
138
138
-
pub fn deinit(self: *const Channel, alloc: std.mem.Allocator) void {
168
168
+
pub fn init(
169
169
+
self: *Channel,
170
170
+
gpa: Allocator,
171
171
+
client: *Client,
172
172
+
name: []const u8,
173
173
+
unicode: *const vaxis.Unicode,
174
174
+
) Allocator.Error!void {
175
175
+
self.* = .{
176
176
+
.name = try gpa.dupe(u8, name),
177
177
+
.members = std.ArrayList(Channel.Member).init(gpa),
178
178
+
.messages = std.ArrayList(Message).init(gpa),
179
179
+
.client = client,
180
180
+
.view = .{
181
181
+
.lhs = self.contentWidget(),
182
182
+
.rhs = self.member_view.widget(),
183
183
+
.width = 16,
184
184
+
.constrain = .rhs,
185
185
+
},
186
186
+
.message_view = .{ .children = .{ .slice = &.{} } },
187
187
+
.member_view = .{
188
188
+
.children = .{
189
189
+
.builder = .{
190
190
+
.userdata = self,
191
191
+
.buildFn = Channel.buildMemberList,
192
192
+
},
193
193
+
},
194
194
+
.draw_cursor = false,
195
195
+
},
196
196
+
.text_field = vxfw.TextField.init(gpa, unicode),
197
197
+
};
198
198
+
}
199
199
+
200
200
+
pub fn deinit(self: *Channel, alloc: std.mem.Allocator) void {
139
201
alloc.free(self.name);
140
202
self.members.deinit();
141
203
if (self.topic) |topic| {
···
145
207
alloc.free(msg.bytes);
146
208
}
147
209
self.messages.deinit();
210
210
+
self.text_field.deinit();
148
211
}
149
212
150
213
pub fn compare(_: void, lhs: *Channel, rhs: *Channel) bool {
···
188
251
try ctx.setMouseShape(.pointer);
189
252
if (mouse.type == .press and mouse.button == .left) {
190
253
self.client.app.selectBuffer(.{ .channel = self });
254
254
+
try ctx.requestFocus(self.text_field.widget());
191
255
return ctx.consumeAndRedraw();
192
256
}
193
257
},
···
283
347
time_tag,
284
348
},
285
349
);
350
350
+
}
351
351
+
352
352
+
pub fn contentWidget(self: *Channel) vxfw.Widget {
353
353
+
return .{
354
354
+
.userdata = self,
355
355
+
.drawFn = Channel.typeErasedViewDraw,
356
356
+
};
357
357
+
}
358
358
+
359
359
+
fn typeErasedViewDraw(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
360
360
+
const self: *Channel = @ptrCast(@alignCast(ptr));
361
361
+
362
362
+
const max = ctx.max.size();
363
363
+
var children = std.ArrayList(vxfw.SubSurface).init(ctx.arena);
364
364
+
365
365
+
{
366
366
+
// Draw the topic
367
367
+
const topic: vxfw.Text = .{
368
368
+
.text = self.topic orelse "",
369
369
+
.softwrap = false,
370
370
+
};
371
371
+
372
372
+
const topic_sub: vxfw.SubSurface = .{
373
373
+
.origin = .{ .col = 0, .row = 0 },
374
374
+
.surface = try topic.draw(ctx),
375
375
+
};
376
376
+
377
377
+
try children.append(topic_sub);
378
378
+
379
379
+
// Draw a border below the topic
380
380
+
const bot = "─";
381
381
+
var writer = try std.ArrayList(u8).initCapacity(ctx.arena, bot.len * max.width);
382
382
+
try writer.writer().writeBytesNTimes(bot, max.width);
383
383
+
384
384
+
const border: vxfw.Text = .{
385
385
+
.text = writer.items,
386
386
+
.softwrap = false,
387
387
+
};
388
388
+
389
389
+
const topic_border: vxfw.SubSurface = .{
390
390
+
.origin = .{ .col = 0, .row = 1 },
391
391
+
.surface = try border.draw(ctx),
392
392
+
};
393
393
+
try children.append(topic_border);
394
394
+
}
395
395
+
396
396
+
// Draw the text field
397
397
+
try children.append(.{
398
398
+
.origin = .{ .col = 0, .row = max.height - 1 },
399
399
+
.surface = try self.text_field.draw(ctx),
400
400
+
});
401
401
+
402
402
+
return .{
403
403
+
.size = max,
404
404
+
.widget = self.contentWidget(),
405
405
+
.buffer = &.{},
406
406
+
.children = children.items,
407
407
+
};
408
408
+
}
409
409
+
410
410
+
fn buildMemberList(ptr: *const anyopaque, idx: usize, _: usize) ?vxfw.Widget {
411
411
+
const self: *const Channel = @ptrCast(@alignCast(ptr));
412
412
+
if (idx < self.members.items.len) {
413
413
+
return self.members.items[idx].widget();
414
414
+
}
415
415
+
return null;
286
416
}
287
417
};
288
418
···
630
760
}
631
761
batches.deinit();
632
762
self.fifo.deinit();
763
763
+
}
764
764
+
765
765
+
pub fn view(self: *Client) vxfw.Widget {
766
766
+
return .{
767
767
+
.userdata = self,
768
768
+
.drawFn = Client.typeErasedViewDraw,
769
769
+
};
770
770
+
}
771
771
+
772
772
+
fn typeErasedViewDraw(ptr: *anyopaque, ctx: vxfw.DrawContext) Allocator.Error!vxfw.Surface {
773
773
+
_ = ptr;
774
774
+
const text: vxfw.Text = .{ .text = "content" };
775
775
+
return text.draw(ctx);
633
776
}
634
777
635
778
pub fn nameWidget(self: *Client, selected: bool) vxfw.Widget {
···
1350
1493
if (caseFold(name, channel.name)) return channel;
1351
1494
}
1352
1495
const channel = try self.alloc.create(Channel);
1353
1353
-
channel.* = .{
1354
1354
-
.name = try self.alloc.dupe(u8, name),
1355
1355
-
.members = std.ArrayList(Channel.Member).init(self.alloc),
1356
1356
-
.messages = std.ArrayList(Message).init(self.alloc),
1357
1357
-
.client = self,
1358
1358
-
};
1496
1496
+
try channel.init(self.alloc, self, name, self.app.unicode);
1359
1497
try self.channels.append(channel);
1360
1498
1361
1499
std.sort.insertion(*Channel, self.channels.items, {}, Channel.compare);
+1
-1
src/main.zig
···
89
89
// defer app.deinit();
90
90
91
91
var comlink_app: comlink.App = undefined;
92
92
-
try comlink_app.init(gpa.allocator());
92
92
+
try comlink_app.init(gpa.allocator(), &app.vx.unicode);
93
93
defer comlink_app.deinit();
94
94
95
95
try app.run(comlink_app.widget(), .{});