a bunch of improvements to playtwitch

This commit is contained in:
LordMZTE 2022-11-22 23:58:49 +01:00
parent 4c4139709a
commit 4ecae122c5
Signed by: LordMZTE
GPG key ID: B64802DC33A64FF6
4 changed files with 196 additions and 117 deletions

View file

@ -3,6 +3,13 @@ const c = @import("ffi.zig").c;
const config = @import("config.zig");
const log = std.log.scoped(.state);
pub const Entry = union(enum) {
channel: ChannelEntry,
/// a seperator in the channel list, the optional string is a heading.
separator: ?[]const u8,
};
pub const ChannelEntry = struct {
name: []const u8,
comment: ?[]const u8,
@ -23,7 +30,7 @@ chatty: bool,
chatty_alive: bool,
/// an array of channels, composed of slices into `channels_file_data`
channels: ?[]ChannelEntry,
channels: ?[]Entry,
/// the data of the channels configuration file
channels_file_data: ?[]u8,

View file

@ -26,16 +26,16 @@ pub fn configLoaderThread(state: *State) !void {
defer file.close();
const channels_data = try file.readToEndAlloc(std.heap.c_allocator, std.math.maxInt(usize));
var channels = std.ArrayList(State.ChannelEntry).init(std.heap.c_allocator);
var channels = std.ArrayList(State.Entry).init(std.heap.c_allocator);
var channels_iter = std.mem.split(u8, channels_data, "\n");
var channels_iter = std.mem.tokenize(u8, channels_data, "\n");
while (channels_iter.next()) |line| {
var line_iter = std.mem.split(u8, line, ":");
var line_iter = std.mem.tokenize(u8, line, ":");
const channel = line_iter.next() orelse continue;
const channel_trimmed = std.mem.trim(u8, channel, " \n\r");
if (channel_trimmed.len == 0 or channel_trimmed[0] == '#')
if (channel_trimmed.len <= 0 or channel_trimmed[0] == '#')
continue;
const comment_trimmed = blk: {
@ -49,16 +49,24 @@ pub fn configLoaderThread(state: *State) !void {
break :blk comment_trimmed;
};
try channels.append(.{
// dashes act as separator
if (std.mem.allEqual(u8, channel_trimmed, '-')) {
// separators can have comments to act as headings
try channels.append(.{ .separator = comment_trimmed });
continue;
}
try channels.append(.{ .channel = .{
.name = channel_trimmed,
.comment = comment_trimmed,
});
} });
}
const end_time = std.time.milliTimestamp();
log.info(
"Loaded {d} channels in {d}ms",
"Loaded {d} channel items in {d}ms",
.{ channels.items.len, end_time - start_time },
);

View file

@ -16,85 +16,100 @@ pub fn winContent(state: *State) !void {
var start: StartType = .none;
// Chatty checkbox
_ = c.igCheckbox("Start Chatty", &state.chatty);
// Quality input
igu.sliceText("Quality ");
c.igSameLine(0.0, 0.0);
if (c.igInputText(
"##quality_input",
&state.quality_buf,
state.quality_buf.len,
c.ImGuiInputTextFlags_EnterReturnsTrue,
null,
null,
if (c.igBeginTable(
"##text_inputs",
2,
0,
.{ .x = 0.0, .y = 0.0 },
0.0,
)) {
start = .channel_bar;
}
defer c.igEndTable();
var quality_popup_pos: c.ImVec2 = undefined;
c.igGetItemRectMin(&quality_popup_pos);
var quality_popup_size: c.ImVec2 = undefined;
c.igGetItemRectSize(&quality_popup_size);
c.igTableSetupColumn("##label", c.ImGuiTableColumnFlags_WidthFixed, 85.0, 0);
c.igTableSetupColumn("##input", 0, 0.0, 0);
c.igSameLine(0.0, 0.0);
if (c.igArrowButton("##open_quality_popup", c.ImGuiDir_Down)) {
c.igOpenPopup_Str("quality_popup", 0);
}
// open popup on arrow button click
c.igOpenPopupOnItemClick("quality_popup", 0);
_ = c.igTableNextRow(0, 0.0);
var btn_size: c.ImVec2 = undefined;
c.igGetItemRectSize(&btn_size);
// Quality input
_ = c.igTableSetColumnIndex(0);
igu.sliceText("Quality");
const preset_qualities = [_][:0]const u8{
"best",
"1080p60",
"720p60",
"480p",
"360p",
"worst",
"audio_only",
};
_ = c.igTableSetColumnIndex(1);
if (c.igInputText(
"##quality_input",
&state.quality_buf,
state.quality_buf.len,
c.ImGuiInputTextFlags_EnterReturnsTrue,
null,
null,
)) {
start = .channel_bar;
}
quality_popup_pos.y += quality_popup_size.y;
quality_popup_size.x += btn_size.x;
quality_popup_size.y += 5 + (quality_popup_size.y * @intToFloat(
f32,
preset_qualities.len,
));
var quality_popup_pos: c.ImVec2 = undefined;
c.igGetItemRectMin(&quality_popup_pos);
var quality_popup_size: c.ImVec2 = undefined;
c.igGetItemRectSize(&quality_popup_size);
c.igSetNextWindowPos(quality_popup_pos, c.ImGuiCond_Always, .{ .x = 0.0, .y = 0.0 });
c.igSetNextWindowSize(quality_popup_size, c.ImGuiCond_Always);
c.igSameLine(0.0, 0.0);
if (c.igArrowButton("##open_quality_popup", c.ImGuiDir_Down)) {
c.igOpenPopup_Str("quality_popup", 0);
}
// open popup on arrow button click
c.igOpenPopupOnItemClick("quality_popup", 0);
if (c.igBeginPopup("quality_popup", c.ImGuiWindowFlags_NoMove)) {
defer c.igEndPopup();
var btn_size: c.ImVec2 = undefined;
c.igGetItemRectSize(&btn_size);
for (preset_qualities) |quality| {
if (c.igSelectable_Bool(quality.ptr, false, 0, .{ .x = 0.0, .y = 0.0 })) {
std.mem.set(u8, &state.quality_buf, 0);
std.mem.copy(u8, &state.quality_buf, quality);
const preset_qualities = [_][:0]const u8{
"best",
"1080p60",
"720p60",
"480p",
"360p",
"worst",
"audio_only",
};
quality_popup_pos.y += quality_popup_size.y;
quality_popup_size.x += btn_size.x;
quality_popup_size.y += 5 + (quality_popup_size.y * @intToFloat(
f32,
preset_qualities.len,
));
c.igSetNextWindowPos(quality_popup_pos, c.ImGuiCond_Always, .{ .x = 0.0, .y = 0.0 });
c.igSetNextWindowSize(quality_popup_size, c.ImGuiCond_Always);
if (c.igBeginPopup("quality_popup", c.ImGuiWindowFlags_NoMove)) {
defer c.igEndPopup();
for (preset_qualities) |quality| {
if (c.igSelectable_Bool(quality.ptr, false, 0, .{ .x = 0.0, .y = 0.0 })) {
std.mem.set(u8, &state.quality_buf, 0);
std.mem.copy(u8, &state.quality_buf, quality);
}
}
}
}
igu.sliceText("Play Channel ");
c.igSameLine(0.0, 0.0);
if (c.igInputText(
"##play_channel_input",
&state.channel_name_buf,
state.channel_name_buf.len,
c.ImGuiInputTextFlags_EnterReturnsTrue,
null,
null,
)) {
start = .channel_bar;
}
c.igSameLine(0.0, 0.0);
if (c.igButton("Play!", .{ .x = 0.0, .y = 0.0 })) {
start = .channel_bar;
_ = c.igTableNextRow(0, 0.0);
_ = c.igTableSetColumnIndex(0);
igu.sliceText("Play Channel");
_ = c.igTableSetColumnIndex(1);
if (c.igInputText(
"##play_channel_input",
&state.channel_name_buf,
state.channel_name_buf.len,
c.ImGuiInputTextFlags_EnterReturnsTrue,
null,
null,
)) {
start = .channel_bar;
}
c.igSameLine(0.0, 0.0);
if (c.igButton("Play!", .{ .x = 0.0, .y = 0.0 })) {
start = .channel_bar;
}
}
if (state.channels != null) {
@ -104,8 +119,13 @@ pub fn winContent(state: *State) !void {
(try std.Thread.spawn(.{}, @import("live.zig").reloadLiveThread, .{state}))
.detach();
}
c.igSameLine(0, 5.0);
}
// Chatty checkbox
_ = c.igCheckbox("Start Chatty", &state.chatty);
if (state.channels != null and c.igBeginChild_Str(
"Quick Pick",
.{ .x = 0.0, .y = 0.0 },
@ -133,52 +153,91 @@ pub fn winContent(state: *State) !void {
_ = c.igTableSetColumnIndex(2);
c.igTableHeader("Live?");
for (state.channels.?) |ch, i| {
var ch_buf: [256]u8 = undefined;
const formatted = try std.fmt.bufPrintZ(
&ch_buf,
"{s}",
.{ch.name},
);
for (state.channels.?) |entry, i| {
c.igPushID_Int(@intCast(c_int, i));
defer c.igPopID();
_ = c.igTableNextRow(0, 0.0);
_ = c.igTableSetColumnIndex(0);
if (c.igSelectable_Bool(
formatted.ptr,
false,
c.ImGuiSelectableFlags_SpanAllColumns,
.{ .x = 0.0, .y = 0.0 },
)) {
start = .{ .channels_idx = i };
switch (entry) {
.channel => |ch| {
var ch_buf: [256]u8 = undefined;
const formatted = try std.fmt.bufPrintZ(
&ch_buf,
"{s}",
.{ch.name},
);
if (c.igSelectable_Bool(
formatted.ptr,
false,
c.ImGuiSelectableFlags_SpanAllColumns,
.{ .x = 0.0, .y = 0.0 },
)) {
start = .{ .channels_idx = i };
}
_ = c.igTableSetColumnIndex(1);
if (ch.comment) |comment| {
igu.sliceText(comment);
}
_ = c.igTableSetColumnIndex(2);
const live_color = switch (ch.live) {
.loading => c.ImVec4{ .x = 1.0, .y = 1.0, .z = 0.0, .w = 1.0 },
.live => c.ImVec4{ .x = 0.0, .y = 1.0, .z = 0.0, .w = 1.0 },
.offline => c.ImVec4{ .x = 1.0, .y = 0.0, .z = 0.0, .w = 1.0 },
};
const live_label = switch (ch.live) {
.loading => "Loading...",
.live => "Live",
.offline => "Offline",
};
const prev_col = c.igGetStyle().*.Colors[c.ImGuiCol_Text];
c.igGetStyle().*.Colors[c.ImGuiCol_Text] = live_color;
igu.sliceText(live_label);
c.igGetStyle().*.Colors[c.ImGuiCol_Text] = prev_col;
},
.separator => |heading| {
if (heading) |h| {
const spacer_size = c.ImVec2{ .x = 0.0, .y = 2.0 };
c.igDummy(spacer_size);
const prev_col = c.igGetStyle().*.Colors[c.ImGuiCol_Text];
c.igGetStyle().*.Colors[c.ImGuiCol_Text] = c.ImVec4{
.x = 0.7,
.y = 0.2,
.z = 0.9,
.w = 1.0,
};
igu.sliceText(h);
c.igGetStyle().*.Colors[c.ImGuiCol_Text] = prev_col;
// TODO: is this the best way to do the alignment?
c.igSeparator();
_ = c.igTableSetColumnIndex(1);
c.igDummy(spacer_size);
c.igDummy(c.ImVec2{ .x = 0.0, .y = c.igGetTextLineHeight() });
c.igSeparator();
_ = c.igTableSetColumnIndex(2);
c.igDummy(spacer_size);
c.igDummy(c.ImVec2{ .x = 0.0, .y = c.igGetTextLineHeight() });
c.igSeparator();
} else {
c.igSeparator();
_ = c.igTableSetColumnIndex(1);
c.igSeparator();
_ = c.igTableSetColumnIndex(2);
c.igSeparator();
}
},
}
_ = c.igTableSetColumnIndex(1);
if (ch.comment) |comment| {
igu.sliceText(comment);
}
_ = c.igTableSetColumnIndex(2);
const live_color = switch (ch.live) {
.loading => c.ImVec4{ .x = 1.0, .y = 1.0, .z = 0.0, .w = 1.0 },
.live => c.ImVec4{ .x = 0.0, .y = 1.0, .z = 0.0, .w = 1.0 },
.offline => c.ImVec4{ .x = 1.0, .y = 0.0, .z = 0.0, .w = 1.0 },
};
const live_label = switch (ch.live) {
.loading => "Loading...",
.live => "Live",
.offline => "Offline",
};
const prev_col = c.igGetStyle().*.Colors[c.ImGuiCol_Text];
c.igGetStyle().*.Colors[c.ImGuiCol_Text] = live_color;
igu.sliceText(live_label);
c.igGetStyle().*.Colors[c.ImGuiCol_Text] = prev_col;
}
}
@ -227,7 +286,7 @@ pub fn winContent(state: *State) !void {
},
.channels_idx => |idx| {
c.glfwHideWindow(state.win);
try launch.launchChildren(state, state.channels.?[idx].name);
try launch.launchChildren(state, state.channels.?[idx].channel.name);
},
}
}

View file

@ -9,7 +9,10 @@ pub fn reloadLiveThread(s: *State) !void {
defer s.mutex.unlock();
for (s.channels.?) |*chan| {
chan.live = .loading;
switch (chan.*) {
.channel => |*ch| ch.live = .loading,
else => {},
}
}
}
@ -45,7 +48,9 @@ pub fn fetchChannelsLive(s: *State) !void {
// we shouldn't need to aquire the mutex here, this data isnt being read and we're
// only doing atomic writes.
var fmt_buf: [512]u8 = undefined;
for (s.channels.?) |*chan| {
for (s.channels.?) |*entry| {
const chan = if (entry.* == .channel) &entry.channel else continue;
page_buf.clearRetainingCapacity();
log.info("requesting live state for channel {s}", .{chan.name});