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 config = @import("config.zig");
const log = std.log.scoped(.state); 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 { pub const ChannelEntry = struct {
name: []const u8, name: []const u8,
comment: ?[]const u8, comment: ?[]const u8,
@ -23,7 +30,7 @@ chatty: bool,
chatty_alive: bool, chatty_alive: bool,
/// an array of channels, composed of slices into `channels_file_data` /// an array of channels, composed of slices into `channels_file_data`
channels: ?[]ChannelEntry, channels: ?[]Entry,
/// the data of the channels configuration file /// the data of the channels configuration file
channels_file_data: ?[]u8, channels_file_data: ?[]u8,

View file

@ -26,16 +26,16 @@ pub fn configLoaderThread(state: *State) !void {
defer file.close(); defer file.close();
const channels_data = try file.readToEndAlloc(std.heap.c_allocator, std.math.maxInt(usize)); 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| { 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 = line_iter.next() orelse continue;
const channel_trimmed = std.mem.trim(u8, channel, " \n\r"); 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; continue;
const comment_trimmed = blk: { const comment_trimmed = blk: {
@ -49,16 +49,24 @@ pub fn configLoaderThread(state: *State) !void {
break :blk comment_trimmed; 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, .name = channel_trimmed,
.comment = comment_trimmed, .comment = comment_trimmed,
}); } });
} }
const end_time = std.time.milliTimestamp(); const end_time = std.time.milliTimestamp();
log.info( log.info(
"Loaded {d} channels in {d}ms", "Loaded {d} channel items in {d}ms",
.{ channels.items.len, end_time - start_time }, .{ channels.items.len, end_time - start_time },
); );

View file

@ -16,85 +16,100 @@ pub fn winContent(state: *State) !void {
var start: StartType = .none; var start: StartType = .none;
// Chatty checkbox if (c.igBeginTable(
_ = c.igCheckbox("Start Chatty", &state.chatty); "##text_inputs",
2,
// Quality input 0,
igu.sliceText("Quality "); .{ .x = 0.0, .y = 0.0 },
c.igSameLine(0.0, 0.0); 0.0,
if (c.igInputText(
"##quality_input",
&state.quality_buf,
state.quality_buf.len,
c.ImGuiInputTextFlags_EnterReturnsTrue,
null,
null,
)) { )) {
start = .channel_bar; defer c.igEndTable();
}
var quality_popup_pos: c.ImVec2 = undefined; c.igTableSetupColumn("##label", c.ImGuiTableColumnFlags_WidthFixed, 85.0, 0);
c.igGetItemRectMin(&quality_popup_pos); c.igTableSetupColumn("##input", 0, 0.0, 0);
var quality_popup_size: c.ImVec2 = undefined;
c.igGetItemRectSize(&quality_popup_size);
c.igSameLine(0.0, 0.0); _ = c.igTableNextRow(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);
var btn_size: c.ImVec2 = undefined; // Quality input
c.igGetItemRectSize(&btn_size); _ = c.igTableSetColumnIndex(0);
igu.sliceText("Quality");
const preset_qualities = [_][:0]const u8{ _ = c.igTableSetColumnIndex(1);
"best", if (c.igInputText(
"1080p60", "##quality_input",
"720p60", &state.quality_buf,
"480p", state.quality_buf.len,
"360p", c.ImGuiInputTextFlags_EnterReturnsTrue,
"worst", null,
"audio_only", null,
}; )) {
start = .channel_bar;
}
quality_popup_pos.y += quality_popup_size.y; var quality_popup_pos: c.ImVec2 = undefined;
quality_popup_size.x += btn_size.x; c.igGetItemRectMin(&quality_popup_pos);
quality_popup_size.y += 5 + (quality_popup_size.y * @intToFloat( var quality_popup_size: c.ImVec2 = undefined;
f32, c.igGetItemRectSize(&quality_popup_size);
preset_qualities.len,
));
c.igSetNextWindowPos(quality_popup_pos, c.ImGuiCond_Always, .{ .x = 0.0, .y = 0.0 }); c.igSameLine(0.0, 0.0);
c.igSetNextWindowSize(quality_popup_size, c.ImGuiCond_Always); 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)) { var btn_size: c.ImVec2 = undefined;
defer c.igEndPopup(); c.igGetItemRectSize(&btn_size);
for (preset_qualities) |quality| { const preset_qualities = [_][:0]const u8{
if (c.igSelectable_Bool(quality.ptr, false, 0, .{ .x = 0.0, .y = 0.0 })) { "best",
std.mem.set(u8, &state.quality_buf, 0); "1080p60",
std.mem.copy(u8, &state.quality_buf, quality); "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.igTableNextRow(0, 0.0);
c.igSameLine(0.0, 0.0); _ = c.igTableSetColumnIndex(0);
if (c.igInputText( igu.sliceText("Play Channel");
"##play_channel_input", _ = c.igTableSetColumnIndex(1);
&state.channel_name_buf, if (c.igInputText(
state.channel_name_buf.len, "##play_channel_input",
c.ImGuiInputTextFlags_EnterReturnsTrue, &state.channel_name_buf,
null, state.channel_name_buf.len,
null, c.ImGuiInputTextFlags_EnterReturnsTrue,
)) { null,
start = .channel_bar; null,
} )) {
c.igSameLine(0.0, 0.0); start = .channel_bar;
if (c.igButton("Play!", .{ .x = 0.0, .y = 0.0 })) { }
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) { if (state.channels != null) {
@ -104,8 +119,13 @@ pub fn winContent(state: *State) !void {
(try std.Thread.spawn(.{}, @import("live.zig").reloadLiveThread, .{state})) (try std.Thread.spawn(.{}, @import("live.zig").reloadLiveThread, .{state}))
.detach(); .detach();
} }
c.igSameLine(0, 5.0);
} }
// Chatty checkbox
_ = c.igCheckbox("Start Chatty", &state.chatty);
if (state.channels != null and c.igBeginChild_Str( if (state.channels != null and c.igBeginChild_Str(
"Quick Pick", "Quick Pick",
.{ .x = 0.0, .y = 0.0 }, .{ .x = 0.0, .y = 0.0 },
@ -133,52 +153,91 @@ pub fn winContent(state: *State) !void {
_ = c.igTableSetColumnIndex(2); _ = c.igTableSetColumnIndex(2);
c.igTableHeader("Live?"); c.igTableHeader("Live?");
for (state.channels.?) |ch, i| { for (state.channels.?) |entry, i| {
var ch_buf: [256]u8 = undefined;
const formatted = try std.fmt.bufPrintZ(
&ch_buf,
"{s}",
.{ch.name},
);
c.igPushID_Int(@intCast(c_int, i)); c.igPushID_Int(@intCast(c_int, i));
defer c.igPopID(); defer c.igPopID();
_ = c.igTableNextRow(0, 0.0); _ = c.igTableNextRow(0, 0.0);
_ = c.igTableSetColumnIndex(0); _ = c.igTableSetColumnIndex(0);
if (c.igSelectable_Bool( switch (entry) {
formatted.ptr, .channel => |ch| {
false, var ch_buf: [256]u8 = undefined;
c.ImGuiSelectableFlags_SpanAllColumns, const formatted = try std.fmt.bufPrintZ(
.{ .x = 0.0, .y = 0.0 }, &ch_buf,
)) { "{s}",
start = .{ .channels_idx = i }; .{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| { .channels_idx => |idx| {
c.glfwHideWindow(state.win); 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(); defer s.mutex.unlock();
for (s.channels.?) |*chan| { 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 // we shouldn't need to aquire the mutex here, this data isnt being read and we're
// only doing atomic writes. // only doing atomic writes.
var fmt_buf: [512]u8 = undefined; 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(); page_buf.clearRetainingCapacity();
log.info("requesting live state for channel {s}", .{chan.name}); log.info("requesting live state for channel {s}", .{chan.name});