a bunch of improvements to playtwitch
This commit is contained in:
parent
4c4139709a
commit
4ecae122c5
|
@ -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,
|
||||||
|
|
|
@ -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 },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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});
|
||||||
|
|
Loading…
Reference in a new issue