183 lines
5.3 KiB
C++
183 lines
5.3 KiB
C++
|
|
#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
|