#1 - quicr module

This commit is contained in:
Martin Slachta
2026-07-22 17:34:44 +02:00
parent a04f0dc262
commit f4174eb0c7
177 changed files with 5309 additions and 2265 deletions
+182
View File
@@ -0,0 +1,182 @@
#include "LobbyState.hpp"
#include "ClientArgs.hpp"
#include "imgui.h"
#include <cstdio>
namespace tw::app {
namespace {
const ImVec4 FAVOURITES_COLOUR{1.0f, 0.8f, 0.2f, 1.0f};
const ImVec4 RECENT_COLOUR{0.7f, 0.7f, 0.7f, 1.0f};
}
LobbyState::LobbyState(std::optional<tw::net::Address> auto_connect) {
m_recent.load();
m_favourites.load();
set_address_input(m_recent.entries().empty()
? "127.0.0.1:8080"
: m_recent.entries().front());
if(auto_connect) {
begin_connect(*auto_connect);
}
}
void LobbyState::set_address_input(const std::string& address) {
std::snprintf(m_address_input, sizeof(m_address_input), "%s", address.c_str());
}
void LobbyState::begin_connect(tw::net::Address address) {
m_error.clear();
m_connection = std::make_unique<tw::net::ServerConnection>(address);
auto started = m_connection->start();
if(!started) {
m_error = started.error().message();
m_connection.reset();
}
}
void LobbyState::draw_form() {
const float input_width = 200.0f;
ImGui::SetNextItemWidth(input_width);
bool submitted = ImGui::InputText("##address", m_address_input, sizeof(m_address_input),
ImGuiInputTextFlags_EnterReturnsTrue);
ImGui::SameLine();
submitted |= ImGui::Button("Connect");
if(!submitted) {
return;
}
auto parsed = parse_address(m_address_input);
if(parsed) {
begin_connect(*parsed);
} else {
m_error = parsed.error();
}
}
void LobbyState::draw_favourite_toggle(const std::string& entry) {
const char* label = m_favourites.contains(entry) ? "[*]" : "[ ]";
// The label alone would collide between the rows drawn in one frame, so the
// entry it acts on is what identifies the button.
std::string button_id = std::string(label) + "##fav_" + entry;
if(ImGui::SmallButton(button_id.c_str())) {
toggle_favourite(entry);
}
}
void LobbyState::toggle_favourite(const std::string& entry) {
if(m_favourites.contains(entry)) {
m_favourites.remove(entry);
} else {
m_favourites.add(entry);
}
m_favourites.save();
}
void LobbyState::draw_provider(ServerAddressProvider& provider, const ImVec4& header_colour) {
ImGui::PushStyleColor(ImGuiCol_Text, header_colour);
ImGui::SeparatorText(provider.name());
ImGui::PopStyleColor();
// BeginChild is one of the two calls whose End must run even when it
// returns false, so the result only decides whether rows are submitted.
if(ImGui::BeginChild(provider.name(), ImVec2(0, 120), ImGuiChildFlags_Borders)) {
for(const auto& entry : provider.entries()) {
draw_favourite_toggle(entry);
ImGui::SameLine();
if(ImGui::Selectable(entry.c_str(), false)) {
set_address_input(entry);
}
if(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
auto parsed = parse_address(entry);
if(parsed) {
begin_connect(*parsed);
}
}
}
}
ImGui::EndChild();
}
void LobbyState::draw_status() {
if(m_connection) {
switch(m_connection->status()) {
case tw::net::ConnectionStatus::Idle:
break;
case tw::net::ConnectionStatus::Connecting: {
ImGui::TextUnformatted("Connecting...");
ImGui::SameLine();
draw_favourite_toggle(m_connection->address().to_string());
ImGui::SameLine();
if(ImGui::Button("Cancel")) {
m_connection.reset();
}
break;
}
case tw::net::ConnectionStatus::Connected:
ImGui::TextColored(ImVec4(0, 1, 0, 1), "Connected!");
m_recent.add(m_connection->address().to_string());
m_recent.save();
m_result = LobbyResult{std::move(m_connection)};
break;
case tw::net::ConnectionStatus::Failed: {
std::string failed_msg = "Connection failed: " + m_connection->error();
ImGui::TextColored(ImVec4(1, 0, 0, 1), "%s", failed_msg.c_str());
if(ImGui::Button("Dismiss")) {
m_connection.reset();
}
break;
}
}
} else if(!m_error.empty()) {
std::string error_msg = "Error: " + m_error;
ImGui::TextColored(ImVec4(1, 0, 0, 1), "%s", error_msg.c_str());
}
}
void LobbyState::update(double delta_time) {
if(m_connection) {
m_connection->update();
}
// Center the window
ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_FirstUseEver,
ImVec2(0.5f, 0.5f));
ImGui::SetNextWindowSize(ImVec2(400, 0), ImGuiCond_FirstUseEver);
ImGui::Begin("Lobby", nullptr, ImGuiWindowFlags_NoMove);
draw_form();
draw_provider(m_favourites, FAVOURITES_COLOUR);
draw_provider(m_recent, RECENT_COLOUR);
ImGui::Separator();
draw_status();
ImGui::End();
}
std::optional<LobbyResult> LobbyState::take_result() {
// Moving out of an optional leaves it engaged, which would hand the caller
// a second, empty result on the next frame.
auto result = std::move(m_result);
m_result.reset();
return result;
}
} // namespace tw::app