#include "LobbyState.hpp" #include "ClientArgs.hpp" #include "imgui.h" #include 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 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(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 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