diff options
Diffstat (limited to 'game/client/gui')
37 files changed, 3936 insertions, 3936 deletions
diff --git a/game/client/gui/background.cc b/game/client/gui/background.cc index 50fef01..d1500bb 100644 --- a/game/client/gui/background.cc +++ b/game/client/gui/background.cc @@ -1,39 +1,39 @@ -#include "client/pch.hh" - -#include "client/gui/background.hh" - -#include "core/math/constexpr.hh" - -#include "core/resource/resource.hh" - -#include "client/resource/texture_gui.hh" - -#include "client/globals.hh" - -static resource_ptr<TextureGUI> texture; - -void gui::background::init(void) -{ - texture = resource::load<TextureGUI>("textures/gui/background.png", TEXTURE_GUI_LOAD_VFLIP); - - if(texture == nullptr) { - spdlog::critical("background: texture load failed"); - std::terminate(); - } -} - -void gui::background::shutdown(void) -{ - texture = nullptr; -} - -void gui::background::layout(void) -{ - auto viewport = ImGui::GetMainViewport(); - auto draw_list = ImGui::GetBackgroundDrawList(); - - auto scaled_width = 0.75f * static_cast<float>(globals::width / globals::gui_scale); - auto scaled_height = 0.75f * static_cast<float>(globals::height / globals::gui_scale); - auto scale_uv = ImVec2(scaled_width / static_cast<float>(texture->size.x), scaled_height / static_cast<float>(texture->size.y)); - draw_list->AddImage(texture->handle, ImVec2(0.0f, 0.0f), viewport->Size, ImVec2(0.0f, 0.0f), scale_uv); -} +#include "client/pch.hh"
+
+#include "client/gui/background.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "core/resource/resource.hh"
+
+#include "client/resource/texture_gui.hh"
+
+#include "client/globals.hh"
+
+static resource_ptr<TextureGUI> texture;
+
+void gui::background::init(void)
+{
+ texture = resource::load<TextureGUI>("textures/gui/background.png", TEXTURE_GUI_LOAD_VFLIP);
+
+ if(texture == nullptr) {
+ spdlog::critical("background: texture load failed");
+ std::terminate();
+ }
+}
+
+void gui::background::shutdown(void)
+{
+ texture = nullptr;
+}
+
+void gui::background::layout(void)
+{
+ auto viewport = ImGui::GetMainViewport();
+ auto draw_list = ImGui::GetBackgroundDrawList();
+
+ auto scaled_width = 0.75f * static_cast<float>(globals::width / globals::gui_scale);
+ auto scaled_height = 0.75f * static_cast<float>(globals::height / globals::gui_scale);
+ auto scale_uv = ImVec2(scaled_width / static_cast<float>(texture->size.x), scaled_height / static_cast<float>(texture->size.y));
+ draw_list->AddImage(texture->handle, ImVec2(0.0f, 0.0f), viewport->Size, ImVec2(0.0f, 0.0f), scale_uv);
+}
diff --git a/game/client/gui/background.hh b/game/client/gui/background.hh index 5c72a3f..6145484 100644 --- a/game/client/gui/background.hh +++ b/game/client/gui/background.hh @@ -1,8 +1,8 @@ -#pragma once - -namespace gui::background -{ -void init(void); -void shutdown(void); -void layout(void); -} // namespace gui::background +#pragma once
+
+namespace gui::background
+{
+void init(void);
+void shutdown(void);
+void layout(void);
+} // namespace gui::background
diff --git a/game/client/gui/bother.cc b/game/client/gui/bother.cc index d87f220..1bb7097 100644 --- a/game/client/gui/bother.cc +++ b/game/client/gui/bother.cc @@ -1,163 +1,163 @@ -#include "client/pch.hh" - -#include "client/gui/bother.hh" - -#include "shared/protocol.hh" - -#include "client/globals.hh" - -// Maximum amount of peers used for bothering -constexpr static std::size_t BOTHER_PEERS = 4; - -struct BotherQueueItem final { - unsigned int identity; - std::string hostname; - std::uint16_t port; -}; - -static ENetHost* bother_host; -static entt::dispatcher bother_dispatcher; -static std::unordered_set<unsigned int> bother_set; -static std::deque<BotherQueueItem> bother_queue; - -static void on_status_response_packet(const protocol::StatusResponse& packet) -{ - auto identity = static_cast<unsigned int>(reinterpret_cast<std::uintptr_t>(packet.peer->data)); - - bother_set.erase(identity); - - gui::BotherResponseEvent event; - event.identity = identity; - event.is_server_unreachable = false; - event.protocol_version = packet.version; - event.num_players = packet.num_players; - event.max_players = packet.max_players; - event.motd = packet.motd; - globals::dispatcher.trigger(event); - - enet_peer_disconnect(packet.peer, protocol::CHANNEL); -} - -void gui::bother::init(void) -{ - bother_host = enet_host_create(nullptr, BOTHER_PEERS, 1, 0, 0); - bother_dispatcher.clear(); - bother_set.clear(); - - bother_dispatcher.sink<protocol::StatusResponse>().connect<&on_status_response_packet>(); -} - -void gui::bother::shutdown(void) -{ - enet_host_destroy(bother_host); - bother_dispatcher.clear(); - bother_set.clear(); -} - -void gui::bother::update_late(void) -{ - unsigned int free_peers = 0U; - - // Figure out how much times we can call - // enet_host_connect and reallistically succeed - for(unsigned int i = 0U; i < bother_host->peerCount; ++i) { - if(bother_host->peers[i].state != ENET_PEER_STATE_DISCONNECTED) { - continue; - } - - free_peers += 1U; - } - - for(unsigned int i = 0U; (i < free_peers) && bother_queue.size(); ++i) { - const auto& item = bother_queue.front(); - - ENetAddress address; - enet_address_set_host(&address, item.hostname.c_str()); - address.port = enet_uint16(item.port); - - if(auto peer = enet_host_connect(bother_host, &address, 1, 0)) { - peer->data = reinterpret_cast<void*>(static_cast<std::uintptr_t>(item.identity)); - bother_set.insert(item.identity); - enet_host_flush(bother_host); - } - - bother_queue.pop_front(); - } - - ENetEvent enet_event; - - if(0 < enet_host_service(bother_host, &enet_event, 0)) { - if(enet_event.type == ENET_EVENT_TYPE_CONNECT) { - protocol::StatusRequest packet; - packet.version = protocol::VERSION; - protocol::send(enet_event.peer, protocol::encode(packet)); - return; - } - - if(enet_event.type == ENET_EVENT_TYPE_RECEIVE) { - protocol::decode(bother_dispatcher, enet_event.packet, enet_event.peer); - enet_packet_destroy(enet_event.packet); - return; - } - - if(enet_event.type == ENET_EVENT_TYPE_DISCONNECT) { - auto identity = static_cast<unsigned int>(reinterpret_cast<std::uintptr_t>(enet_event.peer->data)); - - if(bother_set.count(identity)) { - BotherResponseEvent event; - event.identity = identity; - event.is_server_unreachable = true; - globals::dispatcher.trigger(event); - } - - bother_set.erase(identity); - - return; - } - } -} - -void gui::bother::ping(unsigned int identity, std::string_view host, std::uint16_t port) -{ - if(bother_set.count(identity)) { - // Already in the process - return; - } - - for(const auto& item : bother_queue) { - if(item.identity == identity) { - // Already in the queue - return; - } - } - - BotherQueueItem item; - item.identity = identity; - item.hostname = host; - item.port = port; - - bother_queue.push_back(item); -} - -void gui::bother::cancel(unsigned int identity) -{ - bother_set.erase(identity); - - auto item = bother_queue.cbegin(); - - while(item != bother_queue.cend()) { - if(item->identity == identity) { - item = bother_queue.erase(item); - continue; - } - - item = std::next(item); - } - - for(unsigned int i = 0U; i < bother_host->peerCount; ++i) { - if(bother_host->peers[i].data == reinterpret_cast<void*>(static_cast<std::uintptr_t>(identity))) { - enet_peer_reset(&bother_host->peers[i]); - break; - } - } -} +#include "client/pch.hh"
+
+#include "client/gui/bother.hh"
+
+#include "shared/protocol.hh"
+
+#include "client/globals.hh"
+
+// Maximum amount of peers used for bothering
+constexpr static std::size_t BOTHER_PEERS = 4;
+
+struct BotherQueueItem final {
+ unsigned int identity;
+ std::string hostname;
+ std::uint16_t port;
+};
+
+static ENetHost* bother_host;
+static entt::dispatcher bother_dispatcher;
+static std::unordered_set<unsigned int> bother_set;
+static std::deque<BotherQueueItem> bother_queue;
+
+static void on_status_response_packet(const protocol::StatusResponse& packet)
+{
+ auto identity = static_cast<unsigned int>(reinterpret_cast<std::uintptr_t>(packet.peer->data));
+
+ bother_set.erase(identity);
+
+ gui::BotherResponseEvent event;
+ event.identity = identity;
+ event.is_server_unreachable = false;
+ event.protocol_version = packet.version;
+ event.num_players = packet.num_players;
+ event.max_players = packet.max_players;
+ event.motd = packet.motd;
+ globals::dispatcher.trigger(event);
+
+ enet_peer_disconnect(packet.peer, protocol::CHANNEL);
+}
+
+void gui::bother::init(void)
+{
+ bother_host = enet_host_create(nullptr, BOTHER_PEERS, 1, 0, 0);
+ bother_dispatcher.clear();
+ bother_set.clear();
+
+ bother_dispatcher.sink<protocol::StatusResponse>().connect<&on_status_response_packet>();
+}
+
+void gui::bother::shutdown(void)
+{
+ enet_host_destroy(bother_host);
+ bother_dispatcher.clear();
+ bother_set.clear();
+}
+
+void gui::bother::update_late(void)
+{
+ unsigned int free_peers = 0U;
+
+ // Figure out how much times we can call
+ // enet_host_connect and reallistically succeed
+ for(unsigned int i = 0U; i < bother_host->peerCount; ++i) {
+ if(bother_host->peers[i].state != ENET_PEER_STATE_DISCONNECTED) {
+ continue;
+ }
+
+ free_peers += 1U;
+ }
+
+ for(unsigned int i = 0U; (i < free_peers) && bother_queue.size(); ++i) {
+ const auto& item = bother_queue.front();
+
+ ENetAddress address;
+ enet_address_set_host(&address, item.hostname.c_str());
+ address.port = enet_uint16(item.port);
+
+ if(auto peer = enet_host_connect(bother_host, &address, 1, 0)) {
+ peer->data = reinterpret_cast<void*>(static_cast<std::uintptr_t>(item.identity));
+ bother_set.insert(item.identity);
+ enet_host_flush(bother_host);
+ }
+
+ bother_queue.pop_front();
+ }
+
+ ENetEvent enet_event;
+
+ if(0 < enet_host_service(bother_host, &enet_event, 0)) {
+ if(enet_event.type == ENET_EVENT_TYPE_CONNECT) {
+ protocol::StatusRequest packet;
+ packet.version = protocol::VERSION;
+ protocol::send(enet_event.peer, protocol::encode(packet));
+ return;
+ }
+
+ if(enet_event.type == ENET_EVENT_TYPE_RECEIVE) {
+ protocol::decode(bother_dispatcher, enet_event.packet, enet_event.peer);
+ enet_packet_destroy(enet_event.packet);
+ return;
+ }
+
+ if(enet_event.type == ENET_EVENT_TYPE_DISCONNECT) {
+ auto identity = static_cast<unsigned int>(reinterpret_cast<std::uintptr_t>(enet_event.peer->data));
+
+ if(bother_set.count(identity)) {
+ BotherResponseEvent event;
+ event.identity = identity;
+ event.is_server_unreachable = true;
+ globals::dispatcher.trigger(event);
+ }
+
+ bother_set.erase(identity);
+
+ return;
+ }
+ }
+}
+
+void gui::bother::ping(unsigned int identity, std::string_view host, std::uint16_t port)
+{
+ if(bother_set.count(identity)) {
+ // Already in the process
+ return;
+ }
+
+ for(const auto& item : bother_queue) {
+ if(item.identity == identity) {
+ // Already in the queue
+ return;
+ }
+ }
+
+ BotherQueueItem item;
+ item.identity = identity;
+ item.hostname = host;
+ item.port = port;
+
+ bother_queue.push_back(item);
+}
+
+void gui::bother::cancel(unsigned int identity)
+{
+ bother_set.erase(identity);
+
+ auto item = bother_queue.cbegin();
+
+ while(item != bother_queue.cend()) {
+ if(item->identity == identity) {
+ item = bother_queue.erase(item);
+ continue;
+ }
+
+ item = std::next(item);
+ }
+
+ for(unsigned int i = 0U; i < bother_host->peerCount; ++i) {
+ if(bother_host->peers[i].data == reinterpret_cast<void*>(static_cast<std::uintptr_t>(identity))) {
+ enet_peer_reset(&bother_host->peers[i]);
+ break;
+ }
+ }
+}
diff --git a/game/client/gui/bother.hh b/game/client/gui/bother.hh index c83294c..fc5bab4 100644 --- a/game/client/gui/bother.hh +++ b/game/client/gui/bother.hh @@ -1,22 +1,22 @@ -#pragma once - -namespace gui -{ -struct BotherResponseEvent final { - unsigned int identity; - bool is_server_unreachable; - std::uint32_t protocol_version; - std::uint16_t num_players; - std::uint16_t max_players; - std::string motd; -}; -} // namespace gui - -namespace gui::bother -{ -void init(void); -void shutdown(void); -void update_late(void); -void ping(unsigned int identity, std::string_view host, std::uint16_t port); -void cancel(unsigned int identity); -} // namespace gui::bother +#pragma once
+
+namespace gui
+{
+struct BotherResponseEvent final {
+ unsigned int identity;
+ bool is_server_unreachable;
+ std::uint32_t protocol_version;
+ std::uint16_t num_players;
+ std::uint16_t max_players;
+ std::string motd;
+};
+} // namespace gui
+
+namespace gui::bother
+{
+void init(void);
+void shutdown(void);
+void update_late(void);
+void ping(unsigned int identity, std::string_view host, std::uint16_t port);
+void cancel(unsigned int identity);
+} // namespace gui::bother
diff --git a/game/client/gui/chat.cc b/game/client/gui/chat.cc index 6e4498b..aaee8c6 100644 --- a/game/client/gui/chat.cc +++ b/game/client/gui/chat.cc @@ -1,273 +1,273 @@ -#include "client/pch.hh" - -#include "client/gui/chat.hh" - -#include "core/config/number.hh" -#include "core/config/string.hh" - -#include "core/io/config_map.hh" - -#include "core/resource/resource.hh" - -#include "core/utils/string.hh" - -#include "shared/protocol.hh" - -#include "client/config/keybind.hh" - -#include "client/gui/gui_screen.hh" -#include "client/gui/imdraw_ext.hh" -#include "client/gui/language.hh" -#include "client/gui/settings.hh" - -#include "client/io/glfw.hh" - -#include "client/resource/sound_effect.hh" - -#include "client/sound/sound.hh" - -#include "client/game.hh" -#include "client/globals.hh" -#include "client/session.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; -constexpr static unsigned int MAX_HISTORY_SIZE = 128U; - -struct GuiChatMessage final { - std::uint64_t spawn; - std::string text; - ImVec4 color; -}; - -static config::KeyBind key_chat(GLFW_KEY_ENTER); -static config::Unsigned history_size(32U, 0U, MAX_HISTORY_SIZE); - -static std::deque<GuiChatMessage> history; -static std::string chat_input; -static bool needs_focus; - -static resource_ptr<SoundEffect> sfx_chat_message; - -static void append_text_message(const std::string& sender, const std::string& text) -{ - GuiChatMessage message; - message.spawn = globals::curtime; - message.text = std::format("<{}> {}", sender, text); - message.color = ImGui::GetStyleColorVec4(ImGuiCol_Text); - history.push_back(message); - - if(sfx_chat_message && session::is_ingame()) { - sound::play_ui(sfx_chat_message, false, 1.0f); - } -} - -static void append_player_join(const std::string& sender) -{ - GuiChatMessage message; - message.spawn = globals::curtime; - message.text = std::format("{} {}", sender, gui::language::resolve("chat.client_join")); - message.color = ImGui::GetStyleColorVec4(ImGuiCol_DragDropTarget); - history.push_back(message); - - if(sfx_chat_message && session::is_ingame()) { - sound::play_ui(sfx_chat_message, false, 1.0f); - } -} - -static void append_player_leave(const std::string& sender, const std::string& reason) -{ - GuiChatMessage message; - message.spawn = globals::curtime; - message.text = std::format("{} {} ({})", sender, gui::language::resolve("chat.client_left"), gui::language::resolve(reason.c_str())); - message.color = ImGui::GetStyleColorVec4(ImGuiCol_DragDropTarget); - history.push_back(message); - - if(sfx_chat_message && session::is_ingame()) { - sound::play_ui(sfx_chat_message, false, 1.0f); - } -} - -static void on_chat_message_packet(const protocol::ChatMessage& packet) -{ - if(packet.type == protocol::ChatMessage::TEXT_MESSAGE) { - append_text_message(packet.sender, packet.message); - return; - } - - if(packet.type == protocol::ChatMessage::PLAYER_JOIN) { - append_player_join(packet.sender); - return; - } - - if(packet.type == protocol::ChatMessage::PLAYER_LEAVE) { - append_player_leave(packet.sender, packet.message); - return; - } -} - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if(event.action == GLFW_PRESS) { - if((event.key == GLFW_KEY_ENTER) && (globals::gui_screen == GUI_CHAT)) { - if(!utils::is_whitespace(chat_input)) { - protocol::ChatMessage packet; - packet.type = protocol::ChatMessage::TEXT_MESSAGE; - packet.sender = client_game::username.get(); - packet.message = chat_input; - - protocol::send(session::peer, protocol::encode(packet)); - } - - globals::gui_screen = GUI_SCREEN_NONE; - - chat_input.clear(); - - return; - } - - if((event.key == GLFW_KEY_ESCAPE) && (globals::gui_screen == GUI_CHAT)) { - globals::gui_screen = GUI_SCREEN_NONE; - return; - } - - if(key_chat.equals(event.key) && !globals::gui_screen) { - globals::gui_screen = GUI_CHAT; - needs_focus = true; - return; - } - } -} - -void gui::client_chat::init(void) -{ - globals::client_config.add_value("chat.key", key_chat); - globals::client_config.add_value("chat.history_size", history_size); - - settings::add_keybind(2, key_chat, settings_location::KEYBOARD_MISC, "key.chat"); - settings::add_slider(1, history_size, settings_location::VIDEO_GUI, "chat.history_size", false); - - globals::dispatcher.sink<protocol::ChatMessage>().connect<&on_chat_message_packet>(); - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - - sfx_chat_message = resource::load<SoundEffect>("sounds/ui/chat_message.wav"); -} - -void gui::client_chat::init_late(void) -{ -} - -void gui::client_chat::shutdown(void) -{ - sfx_chat_message = nullptr; -} - -void gui::client_chat::update(void) -{ - while(history.size() > history_size.get_value()) { - history.pop_front(); - } -} - -void gui::client_chat::layout(void) -{ - auto viewport = ImGui::GetMainViewport(); - auto window_start = ImVec2(0.0f, 0.0f); - auto window_size = ImVec2(0.75f * viewport->Size.x, viewport->Size.y); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - ImGui::PushFont(globals::font_unscii16, 8.0f); - - if(!ImGui::Begin("###chat", nullptr, WINDOW_FLAGS)) { - ImGui::End(); - return; - } - - auto& padding = ImGui::GetStyle().FramePadding; - auto& spacing = ImGui::GetStyle().ItemSpacing; - auto font = ImGui::GetFont(); - - auto draw_list = ImGui::GetWindowDrawList(); - - // The text input widget occupies the bottom part - // of the chat window, we need to reserve some space for it - auto ypos = window_size.y - 2.5f * ImGui::GetFontSize() - 2.0f * padding.y - 2.0f * spacing.y; - - if(globals::gui_screen == GUI_CHAT) { - if(needs_focus) { - ImGui::SetKeyboardFocusHere(); - needs_focus = false; - } - - ImGui::SetNextItemWidth(window_size.x + 32.0f * padding.x); - ImGui::SetCursorScreenPos(ImVec2(padding.x, ypos)); - ImGui::InputText("###chat.input", &chat_input); - } - - if(!client_game::hide_hud && ((globals::gui_screen == GUI_SCREEN_NONE) || (globals::gui_screen == GUI_CHAT))) { - for(auto it = history.crbegin(); it < history.crend(); ++it) { - auto text_size = ImGui::CalcTextSize(it->text.c_str(), it->text.c_str() + it->text.size(), false, window_size.x); - auto rect_size = ImVec2(window_size.x, text_size.y + 2.0f * padding.y); - - auto rect_pos = ImVec2(padding.x, ypos - text_size.y - 2.0f * padding.y); - auto rect_end = ImVec2(rect_pos.x + rect_size.x, rect_pos.y + rect_size.y); - auto text_pos = ImVec2(rect_pos.x + padding.x, rect_pos.y + padding.y); - - auto fadeout_seconds = 10.0f; - auto fadeout = std::exp(-1.0f * std::pow(1.0e-6 * static_cast<float>(globals::curtime - it->spawn) / fadeout_seconds, 10.0f)); - - float rect_alpha; - float text_alpha; - - if(globals::gui_screen == GUI_CHAT) { - rect_alpha = 0.75f; - text_alpha = 1.00f; - } - else { - rect_alpha = 0.50f * fadeout; - text_alpha = 1.00f * fadeout; - } - - auto rect_col = ImGui::GetColorU32(ImGuiCol_FrameBg, rect_alpha); - auto text_col = ImGui::GetColorU32(ImVec4(it->color.x, it->color.y, it->color.z, it->color.w * text_alpha)); - auto shadow_col = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, text_alpha)); - - draw_list->AddRectFilled(rect_pos, rect_end, rect_col); - - imdraw_ext::text_shadow_w(it->text, text_pos, text_col, shadow_col, font, draw_list, 8.0f, window_size.x); - - ypos -= rect_size.y; - } - } - - ImGui::End(); - ImGui::PopFont(); -} - -void gui::client_chat::clear(void) -{ - history.clear(); -} - -void gui::client_chat::refresh_timings(void) -{ - for(auto it = history.begin(); it < history.end(); ++it) { - // Reset the spawn time so the fadeout timer - // is reset; SpawnPlayer handler might call this - it->spawn = globals::curtime; - } -} - -void gui::client_chat::print(const std::string& text) -{ - GuiChatMessage message = {}; - message.spawn = globals::curtime; - message.text = text; - message.color = ImGui::GetStyleColorVec4(ImGuiCol_Text); - history.push_back(message); - - if(sfx_chat_message && session::is_ingame()) { - sound::play_ui(sfx_chat_message, false, 1.0f); - } -} +#include "client/pch.hh"
+
+#include "client/gui/chat.hh"
+
+#include "core/config/number.hh"
+#include "core/config/string.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/resource/resource.hh"
+
+#include "core/utils/string.hh"
+
+#include "shared/protocol.hh"
+
+#include "client/config/keybind.hh"
+
+#include "client/gui/gui_screen.hh"
+#include "client/gui/imdraw_ext.hh"
+#include "client/gui/language.hh"
+#include "client/gui/settings.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/resource/sound_effect.hh"
+
+#include "client/sound/sound.hh"
+
+#include "client/game.hh"
+#include "client/globals.hh"
+#include "client/session.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+constexpr static unsigned int MAX_HISTORY_SIZE = 128U;
+
+struct GuiChatMessage final {
+ std::uint64_t spawn;
+ std::string text;
+ ImVec4 color;
+};
+
+static config::KeyBind key_chat(GLFW_KEY_ENTER);
+static config::Unsigned history_size(32U, 0U, MAX_HISTORY_SIZE);
+
+static std::deque<GuiChatMessage> history;
+static std::string chat_input;
+static bool needs_focus;
+
+static resource_ptr<SoundEffect> sfx_chat_message;
+
+static void append_text_message(const std::string& sender, const std::string& text)
+{
+ GuiChatMessage message;
+ message.spawn = globals::curtime;
+ message.text = std::format("<{}> {}", sender, text);
+ message.color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
+ history.push_back(message);
+
+ if(sfx_chat_message && session::is_ingame()) {
+ sound::play_ui(sfx_chat_message, false, 1.0f);
+ }
+}
+
+static void append_player_join(const std::string& sender)
+{
+ GuiChatMessage message;
+ message.spawn = globals::curtime;
+ message.text = std::format("{} {}", sender, gui::language::resolve("chat.client_join"));
+ message.color = ImGui::GetStyleColorVec4(ImGuiCol_DragDropTarget);
+ history.push_back(message);
+
+ if(sfx_chat_message && session::is_ingame()) {
+ sound::play_ui(sfx_chat_message, false, 1.0f);
+ }
+}
+
+static void append_player_leave(const std::string& sender, const std::string& reason)
+{
+ GuiChatMessage message;
+ message.spawn = globals::curtime;
+ message.text = std::format("{} {} ({})", sender, gui::language::resolve("chat.client_left"), gui::language::resolve(reason.c_str()));
+ message.color = ImGui::GetStyleColorVec4(ImGuiCol_DragDropTarget);
+ history.push_back(message);
+
+ if(sfx_chat_message && session::is_ingame()) {
+ sound::play_ui(sfx_chat_message, false, 1.0f);
+ }
+}
+
+static void on_chat_message_packet(const protocol::ChatMessage& packet)
+{
+ if(packet.type == protocol::ChatMessage::TEXT_MESSAGE) {
+ append_text_message(packet.sender, packet.message);
+ return;
+ }
+
+ if(packet.type == protocol::ChatMessage::PLAYER_JOIN) {
+ append_player_join(packet.sender);
+ return;
+ }
+
+ if(packet.type == protocol::ChatMessage::PLAYER_LEAVE) {
+ append_player_leave(packet.sender, packet.message);
+ return;
+ }
+}
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if(event.action == GLFW_PRESS) {
+ if((event.key == GLFW_KEY_ENTER) && (globals::gui_screen == GUI_CHAT)) {
+ if(!utils::is_whitespace(chat_input)) {
+ protocol::ChatMessage packet;
+ packet.type = protocol::ChatMessage::TEXT_MESSAGE;
+ packet.sender = client_game::username.get();
+ packet.message = chat_input;
+
+ protocol::send(session::peer, protocol::encode(packet));
+ }
+
+ globals::gui_screen = GUI_SCREEN_NONE;
+
+ chat_input.clear();
+
+ return;
+ }
+
+ if((event.key == GLFW_KEY_ESCAPE) && (globals::gui_screen == GUI_CHAT)) {
+ globals::gui_screen = GUI_SCREEN_NONE;
+ return;
+ }
+
+ if(key_chat.equals(event.key) && !globals::gui_screen) {
+ globals::gui_screen = GUI_CHAT;
+ needs_focus = true;
+ return;
+ }
+ }
+}
+
+void gui::client_chat::init(void)
+{
+ globals::client_config.add_value("chat.key", key_chat);
+ globals::client_config.add_value("chat.history_size", history_size);
+
+ settings::add_keybind(2, key_chat, settings_location::KEYBOARD_MISC, "key.chat");
+ settings::add_slider(1, history_size, settings_location::VIDEO_GUI, "chat.history_size", false);
+
+ globals::dispatcher.sink<protocol::ChatMessage>().connect<&on_chat_message_packet>();
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+
+ sfx_chat_message = resource::load<SoundEffect>("sounds/ui/chat_message.wav");
+}
+
+void gui::client_chat::init_late(void)
+{
+}
+
+void gui::client_chat::shutdown(void)
+{
+ sfx_chat_message = nullptr;
+}
+
+void gui::client_chat::update(void)
+{
+ while(history.size() > history_size.get_value()) {
+ history.pop_front();
+ }
+}
+
+void gui::client_chat::layout(void)
+{
+ auto viewport = ImGui::GetMainViewport();
+ auto window_start = ImVec2(0.0f, 0.0f);
+ auto window_size = ImVec2(0.75f * viewport->Size.x, viewport->Size.y);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ ImGui::PushFont(globals::font_unscii16, 8.0f);
+
+ if(!ImGui::Begin("###chat", nullptr, WINDOW_FLAGS)) {
+ ImGui::End();
+ return;
+ }
+
+ auto& padding = ImGui::GetStyle().FramePadding;
+ auto& spacing = ImGui::GetStyle().ItemSpacing;
+ auto font = ImGui::GetFont();
+
+ auto draw_list = ImGui::GetWindowDrawList();
+
+ // The text input widget occupies the bottom part
+ // of the chat window, we need to reserve some space for it
+ auto ypos = window_size.y - 2.5f * ImGui::GetFontSize() - 2.0f * padding.y - 2.0f * spacing.y;
+
+ if(globals::gui_screen == GUI_CHAT) {
+ if(needs_focus) {
+ ImGui::SetKeyboardFocusHere();
+ needs_focus = false;
+ }
+
+ ImGui::SetNextItemWidth(window_size.x + 32.0f * padding.x);
+ ImGui::SetCursorScreenPos(ImVec2(padding.x, ypos));
+ ImGui::InputText("###chat.input", &chat_input);
+ }
+
+ if(!client_game::hide_hud && ((globals::gui_screen == GUI_SCREEN_NONE) || (globals::gui_screen == GUI_CHAT))) {
+ for(auto it = history.crbegin(); it < history.crend(); ++it) {
+ auto text_size = ImGui::CalcTextSize(it->text.c_str(), it->text.c_str() + it->text.size(), false, window_size.x);
+ auto rect_size = ImVec2(window_size.x, text_size.y + 2.0f * padding.y);
+
+ auto rect_pos = ImVec2(padding.x, ypos - text_size.y - 2.0f * padding.y);
+ auto rect_end = ImVec2(rect_pos.x + rect_size.x, rect_pos.y + rect_size.y);
+ auto text_pos = ImVec2(rect_pos.x + padding.x, rect_pos.y + padding.y);
+
+ auto fadeout_seconds = 10.0f;
+ auto fadeout = std::exp(-1.0f * std::pow(1.0e-6 * static_cast<float>(globals::curtime - it->spawn) / fadeout_seconds, 10.0f));
+
+ float rect_alpha;
+ float text_alpha;
+
+ if(globals::gui_screen == GUI_CHAT) {
+ rect_alpha = 0.75f;
+ text_alpha = 1.00f;
+ }
+ else {
+ rect_alpha = 0.50f * fadeout;
+ text_alpha = 1.00f * fadeout;
+ }
+
+ auto rect_col = ImGui::GetColorU32(ImGuiCol_FrameBg, rect_alpha);
+ auto text_col = ImGui::GetColorU32(ImVec4(it->color.x, it->color.y, it->color.z, it->color.w * text_alpha));
+ auto shadow_col = ImGui::GetColorU32(ImVec4(0.0f, 0.0f, 0.0f, text_alpha));
+
+ draw_list->AddRectFilled(rect_pos, rect_end, rect_col);
+
+ imdraw_ext::text_shadow_w(it->text, text_pos, text_col, shadow_col, font, draw_list, 8.0f, window_size.x);
+
+ ypos -= rect_size.y;
+ }
+ }
+
+ ImGui::End();
+ ImGui::PopFont();
+}
+
+void gui::client_chat::clear(void)
+{
+ history.clear();
+}
+
+void gui::client_chat::refresh_timings(void)
+{
+ for(auto it = history.begin(); it < history.end(); ++it) {
+ // Reset the spawn time so the fadeout timer
+ // is reset; SpawnPlayer handler might call this
+ it->spawn = globals::curtime;
+ }
+}
+
+void gui::client_chat::print(const std::string& text)
+{
+ GuiChatMessage message = {};
+ message.spawn = globals::curtime;
+ message.text = text;
+ message.color = ImGui::GetStyleColorVec4(ImGuiCol_Text);
+ history.push_back(message);
+
+ if(sfx_chat_message && session::is_ingame()) {
+ sound::play_ui(sfx_chat_message, false, 1.0f);
+ }
+}
diff --git a/game/client/gui/chat.hh b/game/client/gui/chat.hh index 6a3ea33..2fc6fb3 100644 --- a/game/client/gui/chat.hh +++ b/game/client/gui/chat.hh @@ -1,17 +1,17 @@ -#pragma once - -namespace gui::client_chat -{ -void init(void); -void init_late(void); -void shutdown(void); -void update(void); -void layout(void); -} // namespace gui::client_chat - -namespace gui::client_chat -{ -void clear(void); -void refresh_timings(void); -void print(const std::string& string); -} // namespace gui::client_chat +#pragma once
+
+namespace gui::client_chat
+{
+void init(void);
+void init_late(void);
+void shutdown(void);
+void update(void);
+void layout(void);
+} // namespace gui::client_chat
+
+namespace gui::client_chat
+{
+void clear(void);
+void refresh_timings(void);
+void print(const std::string& string);
+} // namespace gui::client_chat
diff --git a/game/client/gui/crosshair.cc b/game/client/gui/crosshair.cc index 29b5fe5..b5650a9 100644 --- a/game/client/gui/crosshair.cc +++ b/game/client/gui/crosshair.cc @@ -1,43 +1,43 @@ -#include "client/pch.hh" - -#include "client/gui/crosshair.hh" - -#include "core/math/constexpr.hh" - -#include "core/resource/resource.hh" - -#include "client/resource/texture_gui.hh" - -#include "client/globals.hh" -#include "client/session.hh" - -static resource_ptr<TextureGUI> texture; - -void gui::crosshair::init(void) -{ - texture = resource::load<TextureGUI>( - "textures/gui/hud_crosshair.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T | TEXTURE_GUI_LOAD_VFLIP); - - if(texture == nullptr) { - spdlog::critical("crosshair: texture load failed"); - std::terminate(); - } -} - -void gui::crosshair::shutdown(void) -{ - texture = nullptr; -} - -void gui::crosshair::layout(void) -{ - auto viewport = ImGui::GetMainViewport(); - auto draw_list = ImGui::GetForegroundDrawList(); - - auto scaled_width = math::max<int>(texture->size.x, globals::gui_scale * texture->size.x / 2); - auto scaled_height = math::max<int>(texture->size.y, globals::gui_scale * texture->size.y / 2); - auto start = ImVec2( - static_cast<int>(0.5f * viewport->Size.x) - (scaled_width / 2), static_cast<int>(0.5f * viewport->Size.y) - (scaled_height / 2)); - auto end = ImVec2(start.x + scaled_width, start.y + scaled_height); - draw_list->AddImage(texture->handle, start, end); -} +#include "client/pch.hh"
+
+#include "client/gui/crosshair.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "core/resource/resource.hh"
+
+#include "client/resource/texture_gui.hh"
+
+#include "client/globals.hh"
+#include "client/session.hh"
+
+static resource_ptr<TextureGUI> texture;
+
+void gui::crosshair::init(void)
+{
+ texture = resource::load<TextureGUI>("textures/gui/hud_crosshair.png",
+ TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T | TEXTURE_GUI_LOAD_VFLIP);
+
+ if(texture == nullptr) {
+ spdlog::critical("crosshair: texture load failed");
+ std::terminate();
+ }
+}
+
+void gui::crosshair::shutdown(void)
+{
+ texture = nullptr;
+}
+
+void gui::crosshair::layout(void)
+{
+ auto viewport = ImGui::GetMainViewport();
+ auto draw_list = ImGui::GetForegroundDrawList();
+
+ auto scaled_width = math::max<int>(texture->size.x, globals::gui_scale * texture->size.x / 2);
+ auto scaled_height = math::max<int>(texture->size.y, globals::gui_scale * texture->size.y / 2);
+ auto start = ImVec2(static_cast<int>(0.5f * viewport->Size.x) - (scaled_width / 2),
+ static_cast<int>(0.5f * viewport->Size.y) - (scaled_height / 2));
+ auto end = ImVec2(start.x + scaled_width, start.y + scaled_height);
+ draw_list->AddImage(texture->handle, start, end);
+}
diff --git a/game/client/gui/crosshair.hh b/game/client/gui/crosshair.hh index 589727e..dca5725 100644 --- a/game/client/gui/crosshair.hh +++ b/game/client/gui/crosshair.hh @@ -1,8 +1,8 @@ -#pragma once - -namespace gui::crosshair -{ -void init(void); -void shutdown(void); -void layout(void); -} // namespace gui::crosshair +#pragma once
+
+namespace gui::crosshair
+{
+void init(void);
+void shutdown(void);
+void layout(void);
+} // namespace gui::crosshair
diff --git a/game/client/gui/direct_connection.cc b/game/client/gui/direct_connection.cc index 37372e2..dfbd05b 100644 --- a/game/client/gui/direct_connection.cc +++ b/game/client/gui/direct_connection.cc @@ -1,145 +1,145 @@ -#include "client/pch.hh" - -#include "client/gui/direct_connection.hh" - -#include "core/config/boolean.hh" - -#include "core/utils/string.hh" - -#include "shared/protocol.hh" - -#include "client/gui/gui_screen.hh" -#include "client/gui/language.hh" - -#include "client/io/glfw.hh" - -#include "client/game.hh" -#include "client/globals.hh" -#include "client/session.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; - -static std::string str_title; -static std::string str_connect; -static std::string str_cancel; - -static std::string str_hostname; -static std::string str_password; - -static std::string direct_hostname; -static std::string direct_password; - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if((event.key == GLFW_KEY_ESCAPE) && (event.action == GLFW_PRESS)) { - if(globals::gui_screen == GUI_DIRECT_CONNECTION) { - globals::gui_screen = GUI_PLAY_MENU; - return; - } - } -} - -static void on_language_set(const gui::LanguageSetEvent& event) -{ - str_title = gui::language::resolve("direct_connection.title"); - str_connect = gui::language::resolve_gui("direct_connection.connect"); - str_cancel = gui::language::resolve_gui("direct_connection.cancel"); - - str_hostname = gui::language::resolve("direct_connection.hostname"); - str_password = gui::language::resolve("direct_connection.password"); -} - -static void connect_to_server(void) -{ - auto parts = utils::split(direct_hostname, ":"); - std::string parsed_hostname; - std::uint16_t parsed_port; - - if(!parts[0].empty()) { - parsed_hostname = parts[0]; - } - else { - parsed_hostname = std::string("localhost"); - } - - if(parts.size() >= 2) { - parsed_port = math::clamp<std::uint16_t>(strtoul(parts[1].c_str(), nullptr, 10), 1024, UINT16_MAX); - } - else { - parsed_port = protocol::PORT; - } - - session::connect(parsed_hostname.c_str(), parsed_port, direct_password.c_str()); -} - -void gui::direct_connection::init(void) -{ - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - globals::dispatcher.sink<LanguageSetEvent>().connect<&on_language_set>(); -} - -void gui::direct_connection::layout(void) -{ - auto viewport = ImGui::GetMainViewport(); - auto window_start = ImVec2(0.25f * viewport->Size.x, 0.20f * viewport->Size.y); - auto window_size = ImVec2(0.50f * viewport->Size.x, 0.80f * viewport->Size.y); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###UIDirectConnect", nullptr, WINDOW_FLAGS)) { - const float title_width = ImGui::CalcTextSize(str_title.c_str()).x; - ImGui::SetCursorPosX(0.5f * (window_size.x - title_width)); - ImGui::TextUnformatted(str_title.c_str()); - - ImGui::Dummy(ImVec2(0.0f, 16.0f * globals::gui_scale)); - - ImGuiInputTextFlags hostname_flags = ImGuiInputTextFlags_CharsNoBlank; - - if(client_game::streamer_mode.get_value()) { - // Hide server hostname to avoid things like - // followers flooding the server that is streamed online - hostname_flags |= ImGuiInputTextFlags_Password; - } - - auto avail_width = ImGui::GetContentRegionAvail().x; - - ImGui::PushItemWidth(avail_width); - - ImGui::InputText("###UIDirectConnect_hostname", &direct_hostname, hostname_flags); - - if(ImGui::BeginItemTooltip()) { - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f); - ImGui::TextUnformatted(str_hostname.c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - ImGui::InputText("###UIDirectConnect_password", &direct_password, ImGuiInputTextFlags_Password); - - if(ImGui::BeginItemTooltip()) { - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f); - ImGui::TextUnformatted(str_password.c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - ImGui::PopItemWidth(); - - ImGui::Dummy(ImVec2(0.0f, 4.0f * globals::gui_scale)); - - ImGui::BeginDisabled(utils::is_whitespace(direct_hostname)); - - if(ImGui::Button(str_connect.c_str(), ImVec2(avail_width, 0.0f))) { - connect_to_server(); - } - - ImGui::EndDisabled(); - - if(ImGui::Button(str_cancel.c_str(), ImVec2(avail_width, 0.0f))) { - globals::gui_screen = GUI_PLAY_MENU; - } - } - - ImGui::End(); -} +#include "client/pch.hh"
+
+#include "client/gui/direct_connection.hh"
+
+#include "core/config/boolean.hh"
+
+#include "core/utils/string.hh"
+
+#include "shared/protocol.hh"
+
+#include "client/gui/gui_screen.hh"
+#include "client/gui/language.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/game.hh"
+#include "client/globals.hh"
+#include "client/session.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+
+static std::string str_title;
+static std::string str_connect;
+static std::string str_cancel;
+
+static std::string str_hostname;
+static std::string str_password;
+
+static std::string direct_hostname;
+static std::string direct_password;
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if((event.key == GLFW_KEY_ESCAPE) && (event.action == GLFW_PRESS)) {
+ if(globals::gui_screen == GUI_DIRECT_CONNECTION) {
+ globals::gui_screen = GUI_PLAY_MENU;
+ return;
+ }
+ }
+}
+
+static void on_language_set(const gui::LanguageSetEvent& event)
+{
+ str_title = gui::language::resolve("direct_connection.title");
+ str_connect = gui::language::resolve_gui("direct_connection.connect");
+ str_cancel = gui::language::resolve_gui("direct_connection.cancel");
+
+ str_hostname = gui::language::resolve("direct_connection.hostname");
+ str_password = gui::language::resolve("direct_connection.password");
+}
+
+static void connect_to_server(void)
+{
+ auto parts = utils::split(direct_hostname, ":");
+ std::string parsed_hostname;
+ std::uint16_t parsed_port;
+
+ if(!parts[0].empty()) {
+ parsed_hostname = parts[0];
+ }
+ else {
+ parsed_hostname = std::string("localhost");
+ }
+
+ if(parts.size() >= 2) {
+ parsed_port = math::clamp<std::uint16_t>(strtoul(parts[1].c_str(), nullptr, 10), 1024, UINT16_MAX);
+ }
+ else {
+ parsed_port = protocol::PORT;
+ }
+
+ session::connect(parsed_hostname.c_str(), parsed_port, direct_password.c_str());
+}
+
+void gui::direct_connection::init(void)
+{
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+ globals::dispatcher.sink<LanguageSetEvent>().connect<&on_language_set>();
+}
+
+void gui::direct_connection::layout(void)
+{
+ auto viewport = ImGui::GetMainViewport();
+ auto window_start = ImVec2(0.25f * viewport->Size.x, 0.20f * viewport->Size.y);
+ auto window_size = ImVec2(0.50f * viewport->Size.x, 0.80f * viewport->Size.y);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###UIDirectConnect", nullptr, WINDOW_FLAGS)) {
+ const float title_width = ImGui::CalcTextSize(str_title.c_str()).x;
+ ImGui::SetCursorPosX(0.5f * (window_size.x - title_width));
+ ImGui::TextUnformatted(str_title.c_str());
+
+ ImGui::Dummy(ImVec2(0.0f, 16.0f * globals::gui_scale));
+
+ ImGuiInputTextFlags hostname_flags = ImGuiInputTextFlags_CharsNoBlank;
+
+ if(client_game::streamer_mode.get_value()) {
+ // Hide server hostname to avoid things like
+ // followers flooding the server that is streamed online
+ hostname_flags |= ImGuiInputTextFlags_Password;
+ }
+
+ auto avail_width = ImGui::GetContentRegionAvail().x;
+
+ ImGui::PushItemWidth(avail_width);
+
+ ImGui::InputText("###UIDirectConnect_hostname", &direct_hostname, hostname_flags);
+
+ if(ImGui::BeginItemTooltip()) {
+ ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f);
+ ImGui::TextUnformatted(str_hostname.c_str());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+
+ ImGui::InputText("###UIDirectConnect_password", &direct_password, ImGuiInputTextFlags_Password);
+
+ if(ImGui::BeginItemTooltip()) {
+ ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f);
+ ImGui::TextUnformatted(str_password.c_str());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+
+ ImGui::PopItemWidth();
+
+ ImGui::Dummy(ImVec2(0.0f, 4.0f * globals::gui_scale));
+
+ ImGui::BeginDisabled(utils::is_whitespace(direct_hostname));
+
+ if(ImGui::Button(str_connect.c_str(), ImVec2(avail_width, 0.0f))) {
+ connect_to_server();
+ }
+
+ ImGui::EndDisabled();
+
+ if(ImGui::Button(str_cancel.c_str(), ImVec2(avail_width, 0.0f))) {
+ globals::gui_screen = GUI_PLAY_MENU;
+ }
+ }
+
+ ImGui::End();
+}
diff --git a/game/client/gui/direct_connection.hh b/game/client/gui/direct_connection.hh index aa02d7c..c63156b 100644 --- a/game/client/gui/direct_connection.hh +++ b/game/client/gui/direct_connection.hh @@ -1,7 +1,7 @@ -#pragma once - -namespace gui::direct_connection -{ -void init(void); -void layout(void); -} // namespace gui::direct_connection +#pragma once
+
+namespace gui::direct_connection
+{
+void init(void);
+void layout(void);
+} // namespace gui::direct_connection
diff --git a/game/client/gui/gui_screen.hh b/game/client/gui/gui_screen.hh index 2eae310..c29f701 100644 --- a/game/client/gui/gui_screen.hh +++ b/game/client/gui/gui_screen.hh @@ -1,10 +1,10 @@ -#pragma once - -constexpr static unsigned int GUI_SCREEN_NONE = 0x0000U; -constexpr static unsigned int GUI_MAIN_MENU = 0x0001U; -constexpr static unsigned int GUI_PLAY_MENU = 0x0002U; -constexpr static unsigned int GUI_SETTINGS = 0x0003U; -constexpr static unsigned int GUI_PROGRESS_BAR = 0x0004U; -constexpr static unsigned int GUI_MESSAGE_BOX = 0x0005U; -constexpr static unsigned int GUI_CHAT = 0x0006U; -constexpr static unsigned int GUI_DIRECT_CONNECTION = 0x0007U; +#pragma once
+
+constexpr static unsigned int GUI_SCREEN_NONE = 0x0000U;
+constexpr static unsigned int GUI_MAIN_MENU = 0x0001U;
+constexpr static unsigned int GUI_PLAY_MENU = 0x0002U;
+constexpr static unsigned int GUI_SETTINGS = 0x0003U;
+constexpr static unsigned int GUI_PROGRESS_BAR = 0x0004U;
+constexpr static unsigned int GUI_MESSAGE_BOX = 0x0005U;
+constexpr static unsigned int GUI_CHAT = 0x0006U;
+constexpr static unsigned int GUI_DIRECT_CONNECTION = 0x0007U;
diff --git a/game/client/gui/hotbar.cc b/game/client/gui/hotbar.cc index ca058a3..806d82b 100644 --- a/game/client/gui/hotbar.cc +++ b/game/client/gui/hotbar.cc @@ -1,183 +1,183 @@ -#include "client/pch.hh" - -#include "client/gui/hotbar.hh" - -#include "core/io/config_map.hh" - -#include "core/resource/resource.hh" - -#include "shared/world/item_registry.hh" - -#include "client/config/keybind.hh" - -#include "client/gui/settings.hh" -#include "client/gui/status_lines.hh" - -#include "client/io/glfw.hh" - -#include "client/resource/texture_gui.hh" - -#include "client/globals.hh" - -constexpr static float ITEM_SIZE = 20.0f; -constexpr static float ITEM_PADDING = 2.0f; -constexpr static float SELECTOR_PADDING = 1.0f; -constexpr static float HOTBAR_PADDING = 2.0f; - -unsigned int gui::hotbar::active_slot = 0U; -item_id gui::hotbar::slots[HOTBAR_SIZE]; - -static config::KeyBind hotbar_keys[HOTBAR_SIZE]; - -static resource_ptr<TextureGUI> hotbar_background; -static resource_ptr<TextureGUI> hotbar_selector; - -static ImU32 get_color_alpha(ImGuiCol style_color, float alpha) -{ - const auto& color = ImGui::GetStyleColorVec4(style_color); - return ImGui::GetColorU32(ImVec4(color.x, color.y, color.z, alpha)); -} - -static void update_hotbar_item(void) -{ - if(gui::hotbar::slots[gui::hotbar::active_slot] == NULL_ITEM_ID) { - gui::status_lines::unset(gui::STATUS_HOTBAR); - return; - } - - if(auto info = world::item_registry::find(gui::hotbar::slots[gui::hotbar::active_slot])) { - gui::status_lines::set(gui::STATUS_HOTBAR, info->name, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), 5.0f); - return; - } -} - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if((event.action == GLFW_PRESS) && !globals::gui_screen) { - for(unsigned int i = 0U; i < HOTBAR_SIZE; ++i) { - if(hotbar_keys[i].equals(event.key)) { - gui::hotbar::active_slot = i; - update_hotbar_item(); - break; - } - } - } -} - -static void on_glfw_scroll(const io::GlfwScrollEvent& event) -{ - if(!globals::gui_screen) { - if(event.dy < 0.0) { - gui::hotbar::next_slot(); - return; - } - - if(event.dy > 0.0) { - gui::hotbar::prev_slot(); - return; - } - } -} - -void gui::hotbar::init(void) -{ - hotbar_keys[0].set_key(GLFW_KEY_1); - hotbar_keys[1].set_key(GLFW_KEY_2); - hotbar_keys[2].set_key(GLFW_KEY_3); - hotbar_keys[3].set_key(GLFW_KEY_4); - hotbar_keys[4].set_key(GLFW_KEY_5); - hotbar_keys[5].set_key(GLFW_KEY_6); - hotbar_keys[6].set_key(GLFW_KEY_7); - hotbar_keys[7].set_key(GLFW_KEY_8); - hotbar_keys[8].set_key(GLFW_KEY_9); - - globals::client_config.add_value("hotbar.key.0", hotbar_keys[0]); - globals::client_config.add_value("hotbar.key.1", hotbar_keys[1]); - globals::client_config.add_value("hotbar.key.3", hotbar_keys[2]); - globals::client_config.add_value("hotbar.key.4", hotbar_keys[3]); - globals::client_config.add_value("hotbar.key.5", hotbar_keys[4]); - globals::client_config.add_value("hotbar.key.6", hotbar_keys[5]); - globals::client_config.add_value("hotbar.key.7", hotbar_keys[6]); - globals::client_config.add_value("hotbar.key.8", hotbar_keys[7]); - globals::client_config.add_value("hotbar.key.9", hotbar_keys[8]); - - settings::add_keybind(10, hotbar_keys[0], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.0"); - settings::add_keybind(11, hotbar_keys[1], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.1"); - settings::add_keybind(12, hotbar_keys[2], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.2"); - settings::add_keybind(13, hotbar_keys[3], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.3"); - settings::add_keybind(14, hotbar_keys[4], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.4"); - settings::add_keybind(15, hotbar_keys[5], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.5"); - settings::add_keybind(16, hotbar_keys[6], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.6"); - settings::add_keybind(17, hotbar_keys[7], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.7"); - settings::add_keybind(18, hotbar_keys[8], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.8"); - - hotbar_background = resource::load<TextureGUI>("textures/gui/hud_hotbar.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T); - hotbar_selector = resource::load<TextureGUI>("textures/gui/hud_selector.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T); - - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - globals::dispatcher.sink<io::GlfwScrollEvent>().connect<&on_glfw_scroll>(); -} - -void gui::hotbar::shutdown(void) -{ - hotbar_background = nullptr; - hotbar_selector = nullptr; -} - -void gui::hotbar::layout(void) -{ - auto& style = ImGui::GetStyle(); - - auto item_size = ITEM_SIZE * globals::gui_scale; - auto hotbar_width = HOTBAR_SIZE * item_size; - auto hotbar_padding = HOTBAR_PADDING * globals::gui_scale; - - auto viewport = ImGui::GetMainViewport(); - auto draw_list = ImGui::GetForegroundDrawList(); - - // Draw the hotbar background image - auto background_start = ImVec2(0.5f * viewport->Size.x - 0.5f * hotbar_width, viewport->Size.y - item_size - hotbar_padding); - auto background_end = ImVec2(background_start.x + hotbar_width, background_start.y + item_size); - draw_list->AddImage(hotbar_background->handle, background_start, background_end); - - // Draw the hotbar selector image - auto selector_padding_a = SELECTOR_PADDING * globals::gui_scale; - auto selector_padding_b = SELECTOR_PADDING * globals::gui_scale * 2.0f; - auto selector_start = ImVec2( - background_start.x + gui::hotbar::active_slot * item_size - selector_padding_a, background_start.y - selector_padding_a); - auto selector_end = ImVec2(selector_start.x + item_size + selector_padding_b, selector_start.y + item_size + selector_padding_b); - draw_list->AddImage(hotbar_selector->handle, selector_start, selector_end); - - // Figure out item texture padding values - auto item_padding_a = ITEM_PADDING * globals::gui_scale; - auto item_padding_b = ITEM_PADDING * globals::gui_scale * 2.0f; - - // Draw individual item textures in the hotbar - for(std::size_t i = 0; i < HOTBAR_SIZE; ++i) { - const auto info = world::item_registry::find(gui::hotbar::slots[i]); - - if((info == nullptr) || (info->cached_texture == nullptr)) { - // There's either no item in the slot - // or the item doesn't have a texture - continue; - } - - const auto item_start = ImVec2(background_start.x + i * item_size + item_padding_a, background_start.y + item_padding_a); - const auto item_end = ImVec2(item_start.x + item_size - item_padding_b, item_start.y + item_size - item_padding_b); - draw_list->AddImage(info->cached_texture->handle, item_start, item_end); - } -} - -void gui::hotbar::next_slot(void) -{ - gui::hotbar::active_slot += 1U; - gui::hotbar::active_slot %= HOTBAR_SIZE; - update_hotbar_item(); -} - -void gui::hotbar::prev_slot(void) -{ - gui::hotbar::active_slot += HOTBAR_SIZE - 1U; - gui::hotbar::active_slot %= HOTBAR_SIZE; - update_hotbar_item(); -} +#include "client/pch.hh"
+
+#include "client/gui/hotbar.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/resource/resource.hh"
+
+#include "shared/world/item_registry.hh"
+
+#include "client/config/keybind.hh"
+
+#include "client/gui/settings.hh"
+#include "client/gui/status_lines.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/resource/texture_gui.hh"
+
+#include "client/globals.hh"
+
+constexpr static float ITEM_SIZE = 20.0f;
+constexpr static float ITEM_PADDING = 2.0f;
+constexpr static float SELECTOR_PADDING = 1.0f;
+constexpr static float HOTBAR_PADDING = 2.0f;
+
+unsigned int gui::hotbar::active_slot = 0U;
+item_id gui::hotbar::slots[HOTBAR_SIZE];
+
+static config::KeyBind hotbar_keys[HOTBAR_SIZE];
+
+static resource_ptr<TextureGUI> hotbar_background;
+static resource_ptr<TextureGUI> hotbar_selector;
+
+static ImU32 get_color_alpha(ImGuiCol style_color, float alpha)
+{
+ const auto& color = ImGui::GetStyleColorVec4(style_color);
+ return ImGui::GetColorU32(ImVec4(color.x, color.y, color.z, alpha));
+}
+
+static void update_hotbar_item(void)
+{
+ if(gui::hotbar::slots[gui::hotbar::active_slot] == NULL_ITEM_ID) {
+ gui::status_lines::unset(gui::STATUS_HOTBAR);
+ return;
+ }
+
+ if(auto info = world::item_registry::find(gui::hotbar::slots[gui::hotbar::active_slot])) {
+ gui::status_lines::set(gui::STATUS_HOTBAR, info->name, ImVec4(1.0f, 1.0f, 1.0f, 1.0f), 5.0f);
+ return;
+ }
+}
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if((event.action == GLFW_PRESS) && !globals::gui_screen) {
+ for(unsigned int i = 0U; i < HOTBAR_SIZE; ++i) {
+ if(hotbar_keys[i].equals(event.key)) {
+ gui::hotbar::active_slot = i;
+ update_hotbar_item();
+ break;
+ }
+ }
+ }
+}
+
+static void on_glfw_scroll(const io::GlfwScrollEvent& event)
+{
+ if(!globals::gui_screen) {
+ if(event.dy < 0.0) {
+ gui::hotbar::next_slot();
+ return;
+ }
+
+ if(event.dy > 0.0) {
+ gui::hotbar::prev_slot();
+ return;
+ }
+ }
+}
+
+void gui::hotbar::init(void)
+{
+ hotbar_keys[0].set_key(GLFW_KEY_1);
+ hotbar_keys[1].set_key(GLFW_KEY_2);
+ hotbar_keys[2].set_key(GLFW_KEY_3);
+ hotbar_keys[3].set_key(GLFW_KEY_4);
+ hotbar_keys[4].set_key(GLFW_KEY_5);
+ hotbar_keys[5].set_key(GLFW_KEY_6);
+ hotbar_keys[6].set_key(GLFW_KEY_7);
+ hotbar_keys[7].set_key(GLFW_KEY_8);
+ hotbar_keys[8].set_key(GLFW_KEY_9);
+
+ globals::client_config.add_value("hotbar.key.0", hotbar_keys[0]);
+ globals::client_config.add_value("hotbar.key.1", hotbar_keys[1]);
+ globals::client_config.add_value("hotbar.key.3", hotbar_keys[2]);
+ globals::client_config.add_value("hotbar.key.4", hotbar_keys[3]);
+ globals::client_config.add_value("hotbar.key.5", hotbar_keys[4]);
+ globals::client_config.add_value("hotbar.key.6", hotbar_keys[5]);
+ globals::client_config.add_value("hotbar.key.7", hotbar_keys[6]);
+ globals::client_config.add_value("hotbar.key.8", hotbar_keys[7]);
+ globals::client_config.add_value("hotbar.key.9", hotbar_keys[8]);
+
+ settings::add_keybind(10, hotbar_keys[0], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.0");
+ settings::add_keybind(11, hotbar_keys[1], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.1");
+ settings::add_keybind(12, hotbar_keys[2], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.2");
+ settings::add_keybind(13, hotbar_keys[3], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.3");
+ settings::add_keybind(14, hotbar_keys[4], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.4");
+ settings::add_keybind(15, hotbar_keys[5], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.5");
+ settings::add_keybind(16, hotbar_keys[6], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.6");
+ settings::add_keybind(17, hotbar_keys[7], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.7");
+ settings::add_keybind(18, hotbar_keys[8], settings_location::KEYBOARD_GAMEPLAY, "hotbar.slot.8");
+
+ hotbar_background = resource::load<TextureGUI>("textures/gui/hud_hotbar.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T);
+ hotbar_selector = resource::load<TextureGUI>("textures/gui/hud_selector.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T);
+
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+ globals::dispatcher.sink<io::GlfwScrollEvent>().connect<&on_glfw_scroll>();
+}
+
+void gui::hotbar::shutdown(void)
+{
+ hotbar_background = nullptr;
+ hotbar_selector = nullptr;
+}
+
+void gui::hotbar::layout(void)
+{
+ auto& style = ImGui::GetStyle();
+
+ auto item_size = ITEM_SIZE * globals::gui_scale;
+ auto hotbar_width = HOTBAR_SIZE * item_size;
+ auto hotbar_padding = HOTBAR_PADDING * globals::gui_scale;
+
+ auto viewport = ImGui::GetMainViewport();
+ auto draw_list = ImGui::GetForegroundDrawList();
+
+ // Draw the hotbar background image
+ auto background_start = ImVec2(0.5f * viewport->Size.x - 0.5f * hotbar_width, viewport->Size.y - item_size - hotbar_padding);
+ auto background_end = ImVec2(background_start.x + hotbar_width, background_start.y + item_size);
+ draw_list->AddImage(hotbar_background->handle, background_start, background_end);
+
+ // Draw the hotbar selector image
+ auto selector_padding_a = SELECTOR_PADDING * globals::gui_scale;
+ auto selector_padding_b = SELECTOR_PADDING * globals::gui_scale * 2.0f;
+ auto selector_start = ImVec2(background_start.x + gui::hotbar::active_slot * item_size - selector_padding_a,
+ background_start.y - selector_padding_a);
+ auto selector_end = ImVec2(selector_start.x + item_size + selector_padding_b, selector_start.y + item_size + selector_padding_b);
+ draw_list->AddImage(hotbar_selector->handle, selector_start, selector_end);
+
+ // Figure out item texture padding values
+ auto item_padding_a = ITEM_PADDING * globals::gui_scale;
+ auto item_padding_b = ITEM_PADDING * globals::gui_scale * 2.0f;
+
+ // Draw individual item textures in the hotbar
+ for(std::size_t i = 0; i < HOTBAR_SIZE; ++i) {
+ const auto info = world::item_registry::find(gui::hotbar::slots[i]);
+
+ if((info == nullptr) || (info->cached_texture == nullptr)) {
+ // There's either no item in the slot
+ // or the item doesn't have a texture
+ continue;
+ }
+
+ const auto item_start = ImVec2(background_start.x + i * item_size + item_padding_a, background_start.y + item_padding_a);
+ const auto item_end = ImVec2(item_start.x + item_size - item_padding_b, item_start.y + item_size - item_padding_b);
+ draw_list->AddImage(info->cached_texture->handle, item_start, item_end);
+ }
+}
+
+void gui::hotbar::next_slot(void)
+{
+ gui::hotbar::active_slot += 1U;
+ gui::hotbar::active_slot %= HOTBAR_SIZE;
+ update_hotbar_item();
+}
+
+void gui::hotbar::prev_slot(void)
+{
+ gui::hotbar::active_slot += HOTBAR_SIZE - 1U;
+ gui::hotbar::active_slot %= HOTBAR_SIZE;
+ update_hotbar_item();
+}
diff --git a/game/client/gui/hotbar.hh b/game/client/gui/hotbar.hh index 85d75aa..88ce791 100644 --- a/game/client/gui/hotbar.hh +++ b/game/client/gui/hotbar.hh @@ -1,27 +1,27 @@ -#pragma once - -#include "shared/types.hh" - -// TODO: design an inventory system and an item -// registry and integrate the hotbar into that system - -constexpr static unsigned int HOTBAR_SIZE = 9U; - -namespace gui::hotbar -{ -extern unsigned int active_slot; -extern item_id slots[HOTBAR_SIZE]; -} // namespace gui::hotbar - -namespace gui::hotbar -{ -void init(void); -void shutdown(void); -void layout(void); -} // namespace gui::hotbar - -namespace gui::hotbar -{ -void next_slot(void); -void prev_slot(void); -} // namespace gui::hotbar +#pragma once
+
+#include "shared/types.hh"
+
+// TODO: design an inventory system and an item
+// registry and integrate the hotbar into that system
+
+constexpr static unsigned int HOTBAR_SIZE = 9U;
+
+namespace gui::hotbar
+{
+extern unsigned int active_slot;
+extern item_id slots[HOTBAR_SIZE];
+} // namespace gui::hotbar
+
+namespace gui::hotbar
+{
+void init(void);
+void shutdown(void);
+void layout(void);
+} // namespace gui::hotbar
+
+namespace gui::hotbar
+{
+void next_slot(void);
+void prev_slot(void);
+} // namespace gui::hotbar
diff --git a/game/client/gui/imdraw_ext.cc b/game/client/gui/imdraw_ext.cc index 832f284..c3d40c9 100644 --- a/game/client/gui/imdraw_ext.cc +++ b/game/client/gui/imdraw_ext.cc @@ -1,34 +1,34 @@ -#include "client/pch.hh" - -#include "client/gui/imdraw_ext.hh" - -#include "client/globals.hh" - -void gui::imdraw_ext::text_shadow( - const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, ImDrawList* draw_list) -{ - imdraw_ext::text_shadow(text, position, text_color, shadow_color, font, draw_list, font->LegacySize); -} - -void gui::imdraw_ext::text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, - ImDrawList* draw_list, float font_size) -{ - const auto shadow_position = ImVec2(position.x + 0.5f * globals::gui_scale, position.y + 0.5f * globals::gui_scale); - draw_list->AddText(font, globals::gui_scale * font_size, shadow_position, shadow_color, text.c_str(), text.c_str() + text.size()); - draw_list->AddText(font, globals::gui_scale * font_size, position, text_color, text.c_str(), text.c_str() + text.size()); -} - -void gui::imdraw_ext::text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, - ImDrawList* draw_list, float wrap_width) -{ - imdraw_ext::text_shadow_w(text, position, text_color, shadow_color, font, draw_list, font->LegacySize, wrap_width); -} - -void gui::imdraw_ext::text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, - ImDrawList* draw_list, float font_size, float wrap_width) -{ - const auto shadow_position = ImVec2(position.x + 0.5f * globals::gui_scale, position.y + 0.5f * globals::gui_scale); - draw_list->AddText( - font, globals::gui_scale * font_size, shadow_position, shadow_color, text.c_str(), text.c_str() + text.size(), wrap_width); - draw_list->AddText(font, globals::gui_scale * font_size, position, text_color, text.c_str(), text.c_str() + text.size(), wrap_width); -} +#include "client/pch.hh"
+
+#include "client/gui/imdraw_ext.hh"
+
+#include "client/globals.hh"
+
+void gui::imdraw_ext::text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list)
+{
+ imdraw_ext::text_shadow(text, position, text_color, shadow_color, font, draw_list, font->LegacySize);
+}
+
+void gui::imdraw_ext::text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list, float font_size)
+{
+ const auto shadow_position = ImVec2(position.x + 0.5f * globals::gui_scale, position.y + 0.5f * globals::gui_scale);
+ draw_list->AddText(font, globals::gui_scale * font_size, shadow_position, shadow_color, text.c_str(), text.c_str() + text.size());
+ draw_list->AddText(font, globals::gui_scale * font_size, position, text_color, text.c_str(), text.c_str() + text.size());
+}
+
+void gui::imdraw_ext::text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list, float wrap_width)
+{
+ imdraw_ext::text_shadow_w(text, position, text_color, shadow_color, font, draw_list, font->LegacySize, wrap_width);
+}
+
+void gui::imdraw_ext::text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list, float font_size, float wrap_width)
+{
+ const auto shadow_position = ImVec2(position.x + 0.5f * globals::gui_scale, position.y + 0.5f * globals::gui_scale);
+ draw_list->AddText(font, globals::gui_scale * font_size, shadow_position, shadow_color, text.c_str(), text.c_str() + text.size(),
+ wrap_width);
+ draw_list->AddText(font, globals::gui_scale * font_size, position, text_color, text.c_str(), text.c_str() + text.size(), wrap_width);
+}
diff --git a/game/client/gui/imdraw_ext.hh b/game/client/gui/imdraw_ext.hh index a7e1503..e1475b2 100644 --- a/game/client/gui/imdraw_ext.hh +++ b/game/client/gui/imdraw_ext.hh @@ -1,17 +1,17 @@ -#pragma once - -namespace gui::imdraw_ext -{ -void text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, - ImDrawList* draw_list); -void text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, ImDrawList* draw_list, - float font_size); -} // namespace gui::imdraw_ext - -namespace gui::imdraw_ext -{ -void text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, - ImDrawList* draw_list, float wrap_width); -void text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, - ImDrawList* draw_list, float font_size, float wrap_width); -} // namespace gui::imdraw_ext +#pragma once
+
+namespace gui::imdraw_ext
+{
+void text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list);
+void text_shadow(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font, ImDrawList* draw_list,
+ float font_size);
+} // namespace gui::imdraw_ext
+
+namespace gui::imdraw_ext
+{
+void text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list, float wrap_width);
+void text_shadow_w(const std::string& text, const ImVec2& position, ImU32 text_color, ImU32 shadow_color, ImFont* font,
+ ImDrawList* draw_list, float font_size, float wrap_width);
+} // namespace gui::imdraw_ext
diff --git a/game/client/gui/language.cc b/game/client/gui/language.cc index f63dd99..57a43fe 100644 --- a/game/client/gui/language.cc +++ b/game/client/gui/language.cc @@ -1,201 +1,201 @@ -#include "client/pch.hh" - -#include "client/gui/language.hh" - -#include "core/config/string.hh" - -#include "core/io/config_map.hh" - -#include "client/gui/settings.hh" - -#include "client/globals.hh" - -constexpr static std::string_view DEFAULT_LANGUAGE = "en_US"; - -// Available languages are kept in a special manifest file which -// is essentially a key-value map of semi-IETF-compliant language tags -// and the language's endonym; after reading the manifest, the translation -// system knows what language it can load and will act accordingly -constexpr static std::string_view MANIFEST_PATH = "lang/manifest.json"; - -static gui::LanguageManifest manifest; -static gui::LanguageIterator current_language; -static std::unordered_map<std::string, std::string> language_map; -static std::unordered_map<std::string, gui::LanguageIterator> ietf_map; -static config::String config_language(DEFAULT_LANGUAGE); - -static void send_language_event(gui::LanguageIterator new_language) -{ - gui::LanguageSetEvent event; - event.new_language = new_language; - globals::dispatcher.trigger(event); -} - -void gui::language::init(void) -{ - globals::client_config.add_value("language", config_language); - - settings::add_language_select(0, settings_location::GENERAL, "language"); - - auto file = PHYSFS_openRead(std::string(MANIFEST_PATH).c_str()); - - if(file == nullptr) { - spdlog::critical("language: {}: {}", MANIFEST_PATH, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - std::terminate(); - } - - auto source = std::string(PHYSFS_fileLength(file), char(0x00)); - PHYSFS_readBytes(file, source.data(), source.size()); - PHYSFS_close(file); - - auto jsonv = json_parse_string(source.c_str()); - const auto json = json_value_get_object(jsonv); - const auto count = json_object_get_count(json); - - if((jsonv == nullptr) || (json == nullptr) || (count == 0)) { - spdlog::critical("language: {}: parse error", MANIFEST_PATH); - json_value_free(jsonv); - std::terminate(); - } - - for(std::size_t i = 0; i < count; ++i) { - const auto ietf = json_object_get_name(json, i); - const auto value = json_object_get_value_at(json, i); - const auto endonym = json_value_get_string(value); - - if(ietf && endonym) { - LanguageInfo info; - info.ietf = std::string(ietf); - info.endonym = std::string(endonym); - info.display = std::format("{} ({})", endonym, ietf); - manifest.push_back(info); - } - } - - for(auto it = manifest.cbegin(); it != manifest.cend(); ++it) { - ietf_map.emplace(it->ietf, it); - } - - json_value_free(jsonv); - - // This is temporary! This value will - // be overriden in init_late after the - // config system updates config_language - current_language = manifest.cend(); -} - -void gui::language::init_late(void) -{ - auto user_language = ietf_map.find(config_language.get_value()); - - if(user_language != ietf_map.cend()) { - gui::language::set(user_language->second); - return; - } - - auto fallback = ietf_map.find(std::string(DEFAULT_LANGUAGE)); - - if(fallback != ietf_map.cend()) { - gui::language::set(fallback->second); - return; - } - - spdlog::critical("language: we're doomed!"); - spdlog::critical("language: {} doesn't exist!", DEFAULT_LANGUAGE); - std::terminate(); -} - -void gui::language::set(LanguageIterator new_language) -{ - if(new_language != manifest.cend()) { - auto path = std::format("lang/lang.{}.json", new_language->ietf); - - auto file = PHYSFS_openRead(path.c_str()); - - if(file == nullptr) { - spdlog::warn("language: {}: {}", path, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - send_language_event(new_language); - return; - } - - auto source = std::string(PHYSFS_fileLength(file), char(0x00)); - PHYSFS_readBytes(file, source.data(), source.size()); - PHYSFS_close(file); - - auto jsonv = json_parse_string(source.c_str()); - const auto json = json_value_get_object(jsonv); - const auto count = json_object_get_count(json); - - if((jsonv == nullptr) || (json == nullptr) || (count == 0)) { - spdlog::warn("language: {}: parse error", path); - send_language_event(new_language); - json_value_free(jsonv); - return; - } - - language_map.clear(); - - for(size_t i = 0; i < count; ++i) { - const auto key = json_object_get_name(json, i); - const auto value = json_object_get_value_at(json, i); - const auto value_str = json_value_get_string(value); - - if(key && value_str) { - language_map.emplace(key, value_str); - continue; - } - } - - json_value_free(jsonv); - - current_language = new_language; - config_language.set(new_language->ietf.c_str()); - } - - send_language_event(new_language); -} - -gui::LanguageIterator gui::language::get_current(void) -{ - return current_language; -} - -gui::LanguageIterator gui::language::find(std::string_view ietf) -{ - const auto it = ietf_map.find(std::string(ietf)); - if(it != ietf_map.cend()) { - return it->second; - } - else { - return manifest.cend(); - } -} - -gui::LanguageIterator gui::language::cbegin(void) -{ - return manifest.cbegin(); -} - -gui::LanguageIterator gui::language::cend(void) -{ - return manifest.cend(); -} - -std::string_view gui::language::resolve(std::string_view key) -{ - const auto it = language_map.find(std::string(key)); - - if(it != language_map.cend()) { - return it->second; - } - - return key; -} - -std::string gui::language::resolve_gui(std::string_view key) -{ - // We need window tags to retain their hierarchy when a language - // dynamically changes; ImGui allows to provide hidden unique identifiers - // to GUI primitives that have their name change dynamically, so we're using this - return std::format("{}###{}", gui::language::resolve(key), key); -} +#include "client/pch.hh"
+
+#include "client/gui/language.hh"
+
+#include "core/config/string.hh"
+
+#include "core/io/config_map.hh"
+
+#include "client/gui/settings.hh"
+
+#include "client/globals.hh"
+
+constexpr static std::string_view DEFAULT_LANGUAGE = "en_US";
+
+// Available languages are kept in a special manifest file which
+// is essentially a key-value map of semi-IETF-compliant language tags
+// and the language's endonym; after reading the manifest, the translation
+// system knows what language it can load and will act accordingly
+constexpr static std::string_view MANIFEST_PATH = "lang/manifest.json";
+
+static gui::LanguageManifest manifest;
+static gui::LanguageIterator current_language;
+static std::unordered_map<std::string, std::string> language_map;
+static std::unordered_map<std::string, gui::LanguageIterator> ietf_map;
+static config::String config_language(DEFAULT_LANGUAGE);
+
+static void send_language_event(gui::LanguageIterator new_language)
+{
+ gui::LanguageSetEvent event;
+ event.new_language = new_language;
+ globals::dispatcher.trigger(event);
+}
+
+void gui::language::init(void)
+{
+ globals::client_config.add_value("language", config_language);
+
+ settings::add_language_select(0, settings_location::GENERAL, "language");
+
+ auto file = PHYSFS_openRead(std::string(MANIFEST_PATH).c_str());
+
+ if(file == nullptr) {
+ spdlog::critical("language: {}: {}", MANIFEST_PATH, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ std::terminate();
+ }
+
+ auto source = std::string(PHYSFS_fileLength(file), char(0x00));
+ PHYSFS_readBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+
+ auto jsonv = json_parse_string(source.c_str());
+ const auto json = json_value_get_object(jsonv);
+ const auto count = json_object_get_count(json);
+
+ if((jsonv == nullptr) || (json == nullptr) || (count == 0)) {
+ spdlog::critical("language: {}: parse error", MANIFEST_PATH);
+ json_value_free(jsonv);
+ std::terminate();
+ }
+
+ for(std::size_t i = 0; i < count; ++i) {
+ const auto ietf = json_object_get_name(json, i);
+ const auto value = json_object_get_value_at(json, i);
+ const auto endonym = json_value_get_string(value);
+
+ if(ietf && endonym) {
+ LanguageInfo info;
+ info.ietf = std::string(ietf);
+ info.endonym = std::string(endonym);
+ info.display = std::format("{} ({})", endonym, ietf);
+ manifest.push_back(info);
+ }
+ }
+
+ for(auto it = manifest.cbegin(); it != manifest.cend(); ++it) {
+ ietf_map.emplace(it->ietf, it);
+ }
+
+ json_value_free(jsonv);
+
+ // This is temporary! This value will
+ // be overriden in init_late after the
+ // config system updates config_language
+ current_language = manifest.cend();
+}
+
+void gui::language::init_late(void)
+{
+ auto user_language = ietf_map.find(config_language.get_value());
+
+ if(user_language != ietf_map.cend()) {
+ gui::language::set(user_language->second);
+ return;
+ }
+
+ auto fallback = ietf_map.find(std::string(DEFAULT_LANGUAGE));
+
+ if(fallback != ietf_map.cend()) {
+ gui::language::set(fallback->second);
+ return;
+ }
+
+ spdlog::critical("language: we're doomed!");
+ spdlog::critical("language: {} doesn't exist!", DEFAULT_LANGUAGE);
+ std::terminate();
+}
+
+void gui::language::set(LanguageIterator new_language)
+{
+ if(new_language != manifest.cend()) {
+ auto path = std::format("lang/lang.{}.json", new_language->ietf);
+
+ auto file = PHYSFS_openRead(path.c_str());
+
+ if(file == nullptr) {
+ spdlog::warn("language: {}: {}", path, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ send_language_event(new_language);
+ return;
+ }
+
+ auto source = std::string(PHYSFS_fileLength(file), char(0x00));
+ PHYSFS_readBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+
+ auto jsonv = json_parse_string(source.c_str());
+ const auto json = json_value_get_object(jsonv);
+ const auto count = json_object_get_count(json);
+
+ if((jsonv == nullptr) || (json == nullptr) || (count == 0)) {
+ spdlog::warn("language: {}: parse error", path);
+ send_language_event(new_language);
+ json_value_free(jsonv);
+ return;
+ }
+
+ language_map.clear();
+
+ for(size_t i = 0; i < count; ++i) {
+ const auto key = json_object_get_name(json, i);
+ const auto value = json_object_get_value_at(json, i);
+ const auto value_str = json_value_get_string(value);
+
+ if(key && value_str) {
+ language_map.emplace(key, value_str);
+ continue;
+ }
+ }
+
+ json_value_free(jsonv);
+
+ current_language = new_language;
+ config_language.set(new_language->ietf.c_str());
+ }
+
+ send_language_event(new_language);
+}
+
+gui::LanguageIterator gui::language::get_current(void)
+{
+ return current_language;
+}
+
+gui::LanguageIterator gui::language::find(std::string_view ietf)
+{
+ const auto it = ietf_map.find(std::string(ietf));
+ if(it != ietf_map.cend()) {
+ return it->second;
+ }
+ else {
+ return manifest.cend();
+ }
+}
+
+gui::LanguageIterator gui::language::cbegin(void)
+{
+ return manifest.cbegin();
+}
+
+gui::LanguageIterator gui::language::cend(void)
+{
+ return manifest.cend();
+}
+
+std::string_view gui::language::resolve(std::string_view key)
+{
+ const auto it = language_map.find(std::string(key));
+
+ if(it != language_map.cend()) {
+ return it->second;
+ }
+
+ return key;
+}
+
+std::string gui::language::resolve_gui(std::string_view key)
+{
+ // We need window tags to retain their hierarchy when a language
+ // dynamically changes; ImGui allows to provide hidden unique identifiers
+ // to GUI primitives that have their name change dynamically, so we're using this
+ return std::format("{}###{}", gui::language::resolve(key), key);
+}
diff --git a/game/client/gui/language.hh b/game/client/gui/language.hh index 90132d7..0628941 100644 --- a/game/client/gui/language.hh +++ b/game/client/gui/language.hh @@ -1,42 +1,42 @@ -#pragma once - -namespace gui -{ -struct LanguageInfo final { - std::string endonym; // Language's self-name - std::string display; // Display for the settings GUI - std::string ietf; // Semi-compliant language abbreviation -}; - -using LanguageManifest = std::vector<LanguageInfo>; -using LanguageIterator = LanguageManifest::const_iterator; - -struct LanguageSetEvent final { - LanguageIterator new_language; -}; -} // namespace gui - -namespace gui::language -{ -void init(void); -void init_late(void); -} // namespace gui::language - -namespace gui::language -{ -void set(LanguageIterator new_language); -} // namespace gui::language - -namespace gui::language -{ -LanguageIterator get_current(void); -LanguageIterator find(std::string_view ietf); -LanguageIterator cbegin(void); -LanguageIterator cend(void); -} // namespace gui::language - -namespace gui::language -{ -std::string_view resolve(std::string_view key); -std::string resolve_gui(std::string_view key); -} // namespace gui::language +#pragma once
+
+namespace gui
+{
+struct LanguageInfo final {
+ std::string endonym; // Language's self-name
+ std::string display; // Display for the settings GUI
+ std::string ietf; // Semi-compliant language abbreviation
+};
+
+using LanguageManifest = std::vector<LanguageInfo>;
+using LanguageIterator = LanguageManifest::const_iterator;
+
+struct LanguageSetEvent final {
+ LanguageIterator new_language;
+};
+} // namespace gui
+
+namespace gui::language
+{
+void init(void);
+void init_late(void);
+} // namespace gui::language
+
+namespace gui::language
+{
+void set(LanguageIterator new_language);
+} // namespace gui::language
+
+namespace gui::language
+{
+LanguageIterator get_current(void);
+LanguageIterator find(std::string_view ietf);
+LanguageIterator cbegin(void);
+LanguageIterator cend(void);
+} // namespace gui::language
+
+namespace gui::language
+{
+std::string_view resolve(std::string_view key);
+std::string resolve_gui(std::string_view key);
+} // namespace gui::language
diff --git a/game/client/gui/main_menu.cc b/game/client/gui/main_menu.cc index 3c68612..aa506d3 100644 --- a/game/client/gui/main_menu.cc +++ b/game/client/gui/main_menu.cc @@ -1,172 +1,172 @@ -#include "client/pch.hh" - -#include "client/gui/main_menu.hh" - -#include "core/math/constexpr.hh" - -#include "core/resource/resource.hh" - -#include "core/version.hh" - -#include "client/gui/gui_screen.hh" -#include "client/gui/language.hh" -#include "client/gui/window_title.hh" - -#include "client/io/glfw.hh" - -#include "client/resource/texture_gui.hh" - -#include "client/globals.hh" -#include "client/session.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; - -static std::string str_play; -static std::string str_resume; -static std::string str_settings; -static std::string str_leave; -static std::string str_quit; - -static resource_ptr<TextureGUI> title; -static float title_aspect; - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if(session::is_ingame() && (event.key == GLFW_KEY_ESCAPE) && (event.action == GLFW_PRESS)) { - if(globals::gui_screen == GUI_SCREEN_NONE) { - globals::gui_screen = GUI_MAIN_MENU; - return; - } - - if(globals::gui_screen == GUI_MAIN_MENU) { - globals::gui_screen = GUI_SCREEN_NONE; - return; - } - } -} - -static void on_language_set(const gui::LanguageSetEvent& event) -{ - str_play = gui::language::resolve_gui("main_menu.play"); - str_resume = gui::language::resolve_gui("main_menu.resume"); - str_settings = gui::language::resolve("main_menu.settings"); - str_leave = gui::language::resolve("main_menu.leave"); - str_quit = gui::language::resolve("main_menu.quit"); -} - -void gui::main_menu::init(void) -{ - title = resource::load<TextureGUI>("textures/gui/menu_title.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T); - - if(title == nullptr) { - spdlog::critical("main_menu: texture load failed"); - std::terminate(); - } - - if(title->size.x > title->size.y) { - title_aspect = static_cast<float>(title->size.x) / static_cast<float>(title->size.y); - } - else { - title_aspect = static_cast<float>(title->size.y) / static_cast<float>(title->size.x); - } - - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - globals::dispatcher.sink<LanguageSetEvent>().connect<&on_language_set>(); -} - -void gui::main_menu::shutdown(void) -{ - title = nullptr; -} - -void gui::main_menu::layout(void) -{ - const auto viewport = ImGui::GetMainViewport(); - const auto window_start = ImVec2(0.0f, viewport->Size.y * 0.15f); - const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###main_menu", nullptr, WINDOW_FLAGS)) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 2.0f * globals::gui_scale)); - - if(session::is_ingame()) { - ImGui::Dummy(ImVec2(0.0f, 32.0f * globals::gui_scale)); - } - else { - auto reference_height = 0.225f * window_size.y; - auto image_width = math::min(window_size.x, reference_height * title_aspect); - auto image_height = image_width / title_aspect; - ImGui::SetCursorPosX(0.5f * (window_size.x - image_width)); - ImGui::Image(title->handle, ImVec2(image_width, image_height)); - } - - ImGui::Dummy(ImVec2(0.0f, 24.0f * globals::gui_scale)); - - const float button_width = 240.0f * globals::gui_scale; - const float button_xpos = 0.5f * (window_size.x - button_width); - - if(session::is_ingame()) { - ImGui::SetCursorPosX(button_xpos); - - if(ImGui::Button(str_resume.c_str(), ImVec2(button_width, 0.0f))) { - globals::gui_screen = GUI_SCREEN_NONE; - } - - ImGui::Spacing(); - } - else { - ImGui::SetCursorPosX(button_xpos); - - if(ImGui::Button(str_play.c_str(), ImVec2(button_width, 0.0f))) { - globals::gui_screen = GUI_PLAY_MENU; - } - - ImGui::Spacing(); - } - - ImGui::SetCursorPosX(button_xpos); - - if(ImGui::Button(str_settings.c_str(), ImVec2(button_width, 0.0f))) { - globals::gui_screen = GUI_SETTINGS; - } - - ImGui::Spacing(); - - if(session::is_ingame()) { - ImGui::SetCursorPosX(button_xpos); - - if(ImGui::Button(str_leave.c_str(), ImVec2(button_width, 0.0f))) { - session::disconnect("protocol.client_disconnect"); - globals::gui_screen = GUI_PLAY_MENU; - gui::window_title::update(); - } - - ImGui::Spacing(); - } - else { - ImGui::SetCursorPosX(button_xpos); - - if(ImGui::Button(str_quit.c_str(), ImVec2(button_width, 0.0f))) { - glfwSetWindowShouldClose(globals::window, true); - } - - ImGui::Spacing(); - } - - if(!session::is_ingame()) { - const auto& padding = ImGui::GetStyle().FramePadding; - const auto& spacing = ImGui::GetStyle().ItemSpacing; - - ImGui::PushFont(globals::font_unscii8, 4.0f); - ImGui::SetCursorScreenPos(ImVec2(padding.x + spacing.x, window_size.y - ImGui::GetFontSize() - padding.y - spacing.y)); - ImGui::Text("Voxelius %*s", version::semver.size(), version::semver.data()); // string_view is not always null-terminated - ImGui::PopFont(); - } - - ImGui::PopStyleVar(); - } - - ImGui::End(); -} +#include "client/pch.hh"
+
+#include "client/gui/main_menu.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "core/resource/resource.hh"
+
+#include "core/version.hh"
+
+#include "client/gui/gui_screen.hh"
+#include "client/gui/language.hh"
+#include "client/gui/window_title.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/resource/texture_gui.hh"
+
+#include "client/globals.hh"
+#include "client/session.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+
+static std::string str_play;
+static std::string str_resume;
+static std::string str_settings;
+static std::string str_leave;
+static std::string str_quit;
+
+static resource_ptr<TextureGUI> title;
+static float title_aspect;
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if(session::is_ingame() && (event.key == GLFW_KEY_ESCAPE) && (event.action == GLFW_PRESS)) {
+ if(globals::gui_screen == GUI_SCREEN_NONE) {
+ globals::gui_screen = GUI_MAIN_MENU;
+ return;
+ }
+
+ if(globals::gui_screen == GUI_MAIN_MENU) {
+ globals::gui_screen = GUI_SCREEN_NONE;
+ return;
+ }
+ }
+}
+
+static void on_language_set(const gui::LanguageSetEvent& event)
+{
+ str_play = gui::language::resolve_gui("main_menu.play");
+ str_resume = gui::language::resolve_gui("main_menu.resume");
+ str_settings = gui::language::resolve("main_menu.settings");
+ str_leave = gui::language::resolve("main_menu.leave");
+ str_quit = gui::language::resolve("main_menu.quit");
+}
+
+void gui::main_menu::init(void)
+{
+ title = resource::load<TextureGUI>("textures/gui/menu_title.png", TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T);
+
+ if(title == nullptr) {
+ spdlog::critical("main_menu: texture load failed");
+ std::terminate();
+ }
+
+ if(title->size.x > title->size.y) {
+ title_aspect = static_cast<float>(title->size.x) / static_cast<float>(title->size.y);
+ }
+ else {
+ title_aspect = static_cast<float>(title->size.y) / static_cast<float>(title->size.x);
+ }
+
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+ globals::dispatcher.sink<LanguageSetEvent>().connect<&on_language_set>();
+}
+
+void gui::main_menu::shutdown(void)
+{
+ title = nullptr;
+}
+
+void gui::main_menu::layout(void)
+{
+ const auto viewport = ImGui::GetMainViewport();
+ const auto window_start = ImVec2(0.0f, viewport->Size.y * 0.15f);
+ const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###main_menu", nullptr, WINDOW_FLAGS)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 2.0f * globals::gui_scale));
+
+ if(session::is_ingame()) {
+ ImGui::Dummy(ImVec2(0.0f, 32.0f * globals::gui_scale));
+ }
+ else {
+ auto reference_height = 0.225f * window_size.y;
+ auto image_width = math::min(window_size.x, reference_height * title_aspect);
+ auto image_height = image_width / title_aspect;
+ ImGui::SetCursorPosX(0.5f * (window_size.x - image_width));
+ ImGui::Image(title->handle, ImVec2(image_width, image_height));
+ }
+
+ ImGui::Dummy(ImVec2(0.0f, 24.0f * globals::gui_scale));
+
+ const float button_width = 240.0f * globals::gui_scale;
+ const float button_xpos = 0.5f * (window_size.x - button_width);
+
+ if(session::is_ingame()) {
+ ImGui::SetCursorPosX(button_xpos);
+
+ if(ImGui::Button(str_resume.c_str(), ImVec2(button_width, 0.0f))) {
+ globals::gui_screen = GUI_SCREEN_NONE;
+ }
+
+ ImGui::Spacing();
+ }
+ else {
+ ImGui::SetCursorPosX(button_xpos);
+
+ if(ImGui::Button(str_play.c_str(), ImVec2(button_width, 0.0f))) {
+ globals::gui_screen = GUI_PLAY_MENU;
+ }
+
+ ImGui::Spacing();
+ }
+
+ ImGui::SetCursorPosX(button_xpos);
+
+ if(ImGui::Button(str_settings.c_str(), ImVec2(button_width, 0.0f))) {
+ globals::gui_screen = GUI_SETTINGS;
+ }
+
+ ImGui::Spacing();
+
+ if(session::is_ingame()) {
+ ImGui::SetCursorPosX(button_xpos);
+
+ if(ImGui::Button(str_leave.c_str(), ImVec2(button_width, 0.0f))) {
+ session::disconnect("protocol.client_disconnect");
+ globals::gui_screen = GUI_PLAY_MENU;
+ gui::window_title::update();
+ }
+
+ ImGui::Spacing();
+ }
+ else {
+ ImGui::SetCursorPosX(button_xpos);
+
+ if(ImGui::Button(str_quit.c_str(), ImVec2(button_width, 0.0f))) {
+ glfwSetWindowShouldClose(globals::window, true);
+ }
+
+ ImGui::Spacing();
+ }
+
+ if(!session::is_ingame()) {
+ const auto& padding = ImGui::GetStyle().FramePadding;
+ const auto& spacing = ImGui::GetStyle().ItemSpacing;
+
+ ImGui::PushFont(globals::font_unscii8, 4.0f);
+ ImGui::SetCursorScreenPos(ImVec2(padding.x + spacing.x, window_size.y - ImGui::GetFontSize() - padding.y - spacing.y));
+ ImGui::Text("Voxelius %*s", version::semver.size(), version::semver.data()); // string_view is not always null-terminated
+ ImGui::PopFont();
+ }
+
+ ImGui::PopStyleVar();
+ }
+
+ ImGui::End();
+}
diff --git a/game/client/gui/main_menu.hh b/game/client/gui/main_menu.hh index 205f078..8e30e38 100644 --- a/game/client/gui/main_menu.hh +++ b/game/client/gui/main_menu.hh @@ -1,8 +1,8 @@ -#pragma once - -namespace gui::main_menu -{ -void init(void); -void shutdown(void); -void layout(void); -} // namespace gui::main_menu +#pragma once
+
+namespace gui::main_menu
+{
+void init(void);
+void shutdown(void);
+void layout(void);
+} // namespace gui::main_menu
diff --git a/game/client/gui/message_box.cc b/game/client/gui/message_box.cc index 59e2d33..b7f109a 100644 --- a/game/client/gui/message_box.cc +++ b/game/client/gui/message_box.cc @@ -1,95 +1,95 @@ -#include "client/pch.hh" - -#include "client/gui/message_box.hh" - -#include "client/gui/gui_screen.hh" -#include "client/gui/language.hh" - -#include "client/globals.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; - -struct Button final { - gui::message_box_action action; - std::string str_title; -}; - -static std::string str_title; -static std::string str_subtitle; -static std::vector<Button> buttons; - -void gui::message_box::init(void) -{ - str_title = std::string(); - str_subtitle = std::string(); - buttons.clear(); -} - -void gui::message_box::layout(void) -{ - const auto viewport = ImGui::GetMainViewport(); - const auto window_start = ImVec2(0.0f, viewport->Size.y * 0.30f); - const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y * 0.70f); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###UIProgress", nullptr, WINDOW_FLAGS)) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 1.0f * globals::gui_scale)); - - const float title_width = ImGui::CalcTextSize(str_title.c_str()).x; - ImGui::SetCursorPosX(0.5f * (window_size.x - title_width)); - ImGui::TextUnformatted(str_title.c_str()); - - ImGui::Dummy(ImVec2(0.0f, 8.0f * globals::gui_scale)); - - if(!str_subtitle.empty()) { - const float subtitle_width = ImGui::CalcTextSize(str_subtitle.c_str()).x; - ImGui::SetCursorPosX(0.5f * (window_size.x - subtitle_width)); - ImGui::TextUnformatted(str_subtitle.c_str()); - } - - ImGui::Dummy(ImVec2(0.0f, 32.0f * globals::gui_scale)); - - for(const auto& button : buttons) { - const float button_width = 0.8f * ImGui::CalcItemWidth(); - ImGui::SetCursorPosX(0.5f * (window_size.x - button_width)); - - if(ImGui::Button(button.str_title.c_str(), ImVec2(button_width, 0.0f))) { - if(button.action) { - button.action(); - } - } - } - - ImGui::PopStyleVar(); - } - - ImGui::End(); -} - -void gui::message_box::reset(void) -{ - str_title.clear(); - str_subtitle.clear(); - buttons.clear(); -} - -void gui::message_box::set_title(std::string_view title) -{ - str_title = gui::language::resolve(title); -} - -void gui::message_box::set_subtitle(std::string_view subtitle) -{ - str_subtitle = gui::language::resolve(subtitle); -} - -void gui::message_box::add_button(std::string_view text, const message_box_action& action) -{ - Button button = {}; - button.str_title = std::format("{}###MessageBox_Button{}", gui::language::resolve(text), buttons.size()); - button.action = action; - - buttons.push_back(button); -} +#include "client/pch.hh"
+
+#include "client/gui/message_box.hh"
+
+#include "client/gui/gui_screen.hh"
+#include "client/gui/language.hh"
+
+#include "client/globals.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+
+struct Button final {
+ gui::message_box_action action;
+ std::string str_title;
+};
+
+static std::string str_title;
+static std::string str_subtitle;
+static std::vector<Button> buttons;
+
+void gui::message_box::init(void)
+{
+ str_title = std::string();
+ str_subtitle = std::string();
+ buttons.clear();
+}
+
+void gui::message_box::layout(void)
+{
+ const auto viewport = ImGui::GetMainViewport();
+ const auto window_start = ImVec2(0.0f, viewport->Size.y * 0.30f);
+ const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y * 0.70f);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###UIProgress", nullptr, WINDOW_FLAGS)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 1.0f * globals::gui_scale));
+
+ const float title_width = ImGui::CalcTextSize(str_title.c_str()).x;
+ ImGui::SetCursorPosX(0.5f * (window_size.x - title_width));
+ ImGui::TextUnformatted(str_title.c_str());
+
+ ImGui::Dummy(ImVec2(0.0f, 8.0f * globals::gui_scale));
+
+ if(!str_subtitle.empty()) {
+ const float subtitle_width = ImGui::CalcTextSize(str_subtitle.c_str()).x;
+ ImGui::SetCursorPosX(0.5f * (window_size.x - subtitle_width));
+ ImGui::TextUnformatted(str_subtitle.c_str());
+ }
+
+ ImGui::Dummy(ImVec2(0.0f, 32.0f * globals::gui_scale));
+
+ for(const auto& button : buttons) {
+ const float button_width = 0.8f * ImGui::CalcItemWidth();
+ ImGui::SetCursorPosX(0.5f * (window_size.x - button_width));
+
+ if(ImGui::Button(button.str_title.c_str(), ImVec2(button_width, 0.0f))) {
+ if(button.action) {
+ button.action();
+ }
+ }
+ }
+
+ ImGui::PopStyleVar();
+ }
+
+ ImGui::End();
+}
+
+void gui::message_box::reset(void)
+{
+ str_title.clear();
+ str_subtitle.clear();
+ buttons.clear();
+}
+
+void gui::message_box::set_title(std::string_view title)
+{
+ str_title = gui::language::resolve(title);
+}
+
+void gui::message_box::set_subtitle(std::string_view subtitle)
+{
+ str_subtitle = gui::language::resolve(subtitle);
+}
+
+void gui::message_box::add_button(std::string_view text, const message_box_action& action)
+{
+ Button button = {};
+ button.str_title = std::format("{}###MessageBox_Button{}", gui::language::resolve(text), buttons.size());
+ button.action = action;
+
+ buttons.push_back(button);
+}
diff --git a/game/client/gui/message_box.hh b/game/client/gui/message_box.hh index 74a6fbf..06a3b8b 100644 --- a/game/client/gui/message_box.hh +++ b/game/client/gui/message_box.hh @@ -1,20 +1,20 @@ -#pragma once - -namespace gui -{ -using message_box_action = void (*)(void); -} // namespace gui - -namespace gui::message_box -{ -void init(void); -void layout(void); -void reset(void); -} // namespace gui::message_box - -namespace gui::message_box -{ -void set_title(std::string_view title); -void set_subtitle(std::string_view subtitle); -void add_button(std::string_view text, const message_box_action& action); -} // namespace gui::message_box +#pragma once
+
+namespace gui
+{
+using message_box_action = void (*)(void);
+} // namespace gui
+
+namespace gui::message_box
+{
+void init(void);
+void layout(void);
+void reset(void);
+} // namespace gui::message_box
+
+namespace gui::message_box
+{
+void set_title(std::string_view title);
+void set_subtitle(std::string_view subtitle);
+void add_button(std::string_view text, const message_box_action& action);
+} // namespace gui::message_box
diff --git a/game/client/gui/metrics.cc b/game/client/gui/metrics.cc index 57a6319..ef47880 100644 --- a/game/client/gui/metrics.cc +++ b/game/client/gui/metrics.cc @@ -1,103 +1,103 @@ -#include "client/pch.hh" - -#include "client/gui/metrics.hh" - -#include "core/version.hh" - -#include "shared/entity/grounded.hh" -#include "shared/entity/head.hh" -#include "shared/entity/transform.hh" -#include "shared/entity/velocity.hh" - -#include "shared/world/dimension.hh" - -#include "shared/coord.hh" - -#include "client/entity/camera.hh" - -#include "client/gui/imdraw_ext.hh" - -#include "client/game.hh" -#include "client/globals.hh" -#include "client/session.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs - | ImGuiWindowFlags_NoNav; - -static std::basic_string<GLubyte> r_version; -static std::basic_string<GLubyte> r_renderer; - -void gui::metrics::init(void) -{ - r_version = std::basic_string<GLubyte>(glGetString(GL_VERSION)); - r_renderer = std::basic_string<GLubyte>(glGetString(GL_RENDERER)); -} - -void gui::metrics::layout(void) -{ - if(!session::is_ingame()) { - // Sanity check; we are checking this - // in client_game before calling layout - // on HUD-ish GUI systems but still - return; - } - - auto draw_list = ImGui::GetForegroundDrawList(); - - // FIXME: maybe use style colors instead of hardcoding? - auto text_color = ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, 1.0f)); - auto shadow_color = ImGui::GetColorU32(ImVec4(0.1f, 0.1f, 0.1f, 1.0f)); - - auto font_size = 4.0f; - auto position = ImVec2(8.0f, 8.0f); - auto y_step = 1.5f * globals::gui_scale * font_size; - - // Draw version - auto version_line = std::format("Voxelius {} [{}]", version::semver, version::commit); - gui::imdraw_ext::text_shadow(version_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += 1.5f * y_step; - - // Draw client-side window framerate metrics - auto window_framerate = 1.0f / globals::window_frametime_avg; - auto window_frametime = 1000.0f * globals::window_frametime_avg; - auto window_fps_line = std::format("{:.02f} FPS [{:.02f} ms]", window_framerate, window_frametime); - gui::imdraw_ext::text_shadow(window_fps_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += y_step; - - // Draw world rendering metrics - auto drawcall_line = std::format("World: {} DC / {} TRI", globals::num_drawcalls, globals::num_triangles); - gui::imdraw_ext::text_shadow(drawcall_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += y_step; - - // Draw OpenGL version string - auto r_version_line = std::format("GL_VERSION: {}", reinterpret_cast<const char*>(r_version.c_str())); - gui::imdraw_ext::text_shadow(r_version_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += y_step; - - // Draw OpenGL renderer string - auto r_renderer_line = std::format("GL_RENDERER: {}", reinterpret_cast<const char*>(r_renderer.c_str())); - gui::imdraw_ext::text_shadow(r_renderer_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += 1.5f * y_step; - - const auto& head = globals::dimension->entities.get<entity::Head>(globals::player); - const auto& transform = globals::dimension->entities.get<entity::Transform>(globals::player); - const auto& velocity = globals::dimension->entities.get<entity::Velocity>(globals::player); - - // Draw player voxel position - auto voxel_position = coord::to_voxel(transform.chunk, transform.local); - auto voxel_line = std::format("voxel: [{} {} {}]", voxel_position.x, voxel_position.y, voxel_position.z); - gui::imdraw_ext::text_shadow(voxel_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += y_step; - - // Draw player world position - auto world_line = std::format("world: [{} {} {}] [{:.03f} {:.03f} {:.03f}]", transform.chunk.x, transform.chunk.y, transform.chunk.z, - transform.local.x, transform.local.y, transform.local.z); - gui::imdraw_ext::text_shadow(world_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += y_step; - - // Draw player look angles - auto angles = glm::degrees(transform.angles + head.angles); - auto angle_line = std::format("angle: [{: .03f} {: .03f} {: .03f}]", angles[0], angles[1], angles[2]); - gui::imdraw_ext::text_shadow(angle_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size); - position.y += y_step; -} +#include "client/pch.hh"
+
+#include "client/gui/metrics.hh"
+
+#include "core/version.hh"
+
+#include "shared/entity/grounded.hh"
+#include "shared/entity/head.hh"
+#include "shared/entity/transform.hh"
+#include "shared/entity/velocity.hh"
+
+#include "shared/world/dimension.hh"
+
+#include "shared/coord.hh"
+
+#include "client/entity/camera.hh"
+
+#include "client/gui/imdraw_ext.hh"
+
+#include "client/game.hh"
+#include "client/globals.hh"
+#include "client/session.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs
+ | ImGuiWindowFlags_NoNav;
+
+static std::basic_string<GLubyte> r_version;
+static std::basic_string<GLubyte> r_renderer;
+
+void gui::metrics::init(void)
+{
+ r_version = std::basic_string<GLubyte>(glGetString(GL_VERSION));
+ r_renderer = std::basic_string<GLubyte>(glGetString(GL_RENDERER));
+}
+
+void gui::metrics::layout(void)
+{
+ if(!session::is_ingame()) {
+ // Sanity check; we are checking this
+ // in client_game before calling layout
+ // on HUD-ish GUI systems but still
+ return;
+ }
+
+ auto draw_list = ImGui::GetForegroundDrawList();
+
+ // FIXME: maybe use style colors instead of hardcoding?
+ auto text_color = ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
+ auto shadow_color = ImGui::GetColorU32(ImVec4(0.1f, 0.1f, 0.1f, 1.0f));
+
+ auto font_size = 4.0f;
+ auto position = ImVec2(8.0f, 8.0f);
+ auto y_step = 1.5f * globals::gui_scale * font_size;
+
+ // Draw version
+ auto version_line = std::format("Voxelius {} [{}]", version::semver, version::commit);
+ gui::imdraw_ext::text_shadow(version_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += 1.5f * y_step;
+
+ // Draw client-side window framerate metrics
+ auto window_framerate = 1.0f / globals::window_frametime_avg;
+ auto window_frametime = 1000.0f * globals::window_frametime_avg;
+ auto window_fps_line = std::format("{:.02f} FPS [{:.02f} ms]", window_framerate, window_frametime);
+ gui::imdraw_ext::text_shadow(window_fps_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += y_step;
+
+ // Draw world rendering metrics
+ auto drawcall_line = std::format("World: {} DC / {} TRI", globals::num_drawcalls, globals::num_triangles);
+ gui::imdraw_ext::text_shadow(drawcall_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += y_step;
+
+ // Draw OpenGL version string
+ auto r_version_line = std::format("GL_VERSION: {}", reinterpret_cast<const char*>(r_version.c_str()));
+ gui::imdraw_ext::text_shadow(r_version_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += y_step;
+
+ // Draw OpenGL renderer string
+ auto r_renderer_line = std::format("GL_RENDERER: {}", reinterpret_cast<const char*>(r_renderer.c_str()));
+ gui::imdraw_ext::text_shadow(r_renderer_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += 1.5f * y_step;
+
+ const auto& head = globals::dimension->entities.get<entity::Head>(globals::player);
+ const auto& transform = globals::dimension->entities.get<entity::Transform>(globals::player);
+ const auto& velocity = globals::dimension->entities.get<entity::Velocity>(globals::player);
+
+ // Draw player voxel position
+ auto voxel_position = coord::to_voxel(transform.chunk, transform.local);
+ auto voxel_line = std::format("voxel: [{} {} {}]", voxel_position.x, voxel_position.y, voxel_position.z);
+ gui::imdraw_ext::text_shadow(voxel_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += y_step;
+
+ // Draw player world position
+ auto world_line = std::format("world: [{} {} {}] [{:.03f} {:.03f} {:.03f}]", transform.chunk.x, transform.chunk.y, transform.chunk.z,
+ transform.local.x, transform.local.y, transform.local.z);
+ gui::imdraw_ext::text_shadow(world_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += y_step;
+
+ // Draw player look angles
+ auto angles = glm::degrees(transform.angles + head.angles);
+ auto angle_line = std::format("angle: [{: .03f} {: .03f} {: .03f}]", angles[0], angles[1], angles[2]);
+ gui::imdraw_ext::text_shadow(angle_line, position, text_color, shadow_color, globals::font_unscii8, draw_list, font_size);
+ position.y += y_step;
+}
diff --git a/game/client/gui/metrics.hh b/game/client/gui/metrics.hh index 4898332..6bec5cb 100644 --- a/game/client/gui/metrics.hh +++ b/game/client/gui/metrics.hh @@ -1,7 +1,7 @@ -#pragma once - -namespace gui::metrics -{ -void init(void); -void layout(void); -} // namespace gui::metrics +#pragma once
+
+namespace gui::metrics
+{
+void init(void);
+void layout(void);
+} // namespace gui::metrics
diff --git a/game/client/gui/play_menu.cc b/game/client/gui/play_menu.cc index 2951bf2..5b9887e 100644 --- a/game/client/gui/play_menu.cc +++ b/game/client/gui/play_menu.cc @@ -1,564 +1,564 @@ -#include "client/pch.hh" - -#include "client/gui/play_menu.hh" - -#include "core/config/boolean.hh" - -#include "core/io/config_map.hh" - -#include "core/math/constexpr.hh" - -#include "core/utils/string.hh" - -#include "shared/protocol.hh" - -#include "client/gui/bother.hh" -#include "client/gui/gui_screen.hh" -#include "client/gui/language.hh" - -#include "client/io/glfw.hh" - -#include "client/game.hh" -#include "client/globals.hh" -#include "client/session.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; -constexpr static std::string_view DEFAULT_SERVER_NAME = "Voxelius Server"; -constexpr static std::string_view SERVERS_TXT = "servers.txt"; -constexpr static std::string_view WARNING_TOAST = "[!]"; - -constexpr static std::size_t MAX_SERVER_ITEM_NAME = 24; - -enum class item_status : unsigned int { - UNKNOWN = 0x0000U, - PINGING = 0x0001U, - REACHED = 0x0002U, - FAILURE = 0x0003U, -}; - -struct ServerStatusItem final { - std::string name; - std::string password; - std::string hostname; - std::uint16_t port; - - // Things pulled from bother events - std::uint32_t protocol_version; - std::uint16_t num_players; - std::uint16_t max_players; - std::string motd; - - // Unique identifier that monotonically - // grows with each new server added and - // doesn't reset with each server removed - unsigned int identity; - - item_status status; -}; - -static std::string str_tab_servers; - -static std::string str_join; -static std::string str_connect; -static std::string str_add; -static std::string str_edit; -static std::string str_remove; -static std::string str_refresh; - -static std::string str_status_init; -static std::string str_status_ping; -static std::string str_status_fail; - -static std::string str_outdated_client; -static std::string str_outdated_server; - -static std::string input_itemname; -static std::string input_hostname; -static std::string input_password; - -static unsigned int next_identity; -static std::deque<ServerStatusItem*> servers_deque; -static ServerStatusItem* selected_server; -static bool editing_server; -static bool adding_server; -static bool needs_focus; - -static void parse_hostname(ServerStatusItem* item, const std::string& hostname) -{ - auto parts = utils::split(hostname, ":"); - - if(!parts[0].empty()) { - item->hostname = parts[0]; - } - else { - item->hostname = std::string("localhost"); - } - - if(parts.size() >= 2) { - item->port = math::clamp<std::uint16_t>(strtoul(parts[1].c_str(), nullptr, 10), 1024, UINT16_MAX); - } - else { - item->port = protocol::PORT; - } -} - -static void add_new_server(void) -{ - auto item = new ServerStatusItem(); - item->port = protocol::PORT; - item->protocol_version = protocol::VERSION; - item->max_players = UINT16_MAX; - item->num_players = UINT16_MAX; - item->identity = next_identity; - item->status = item_status::UNKNOWN; - - next_identity += 1U; - - input_itemname = DEFAULT_SERVER_NAME; - input_hostname = std::string(); - input_password = std::string(); - - servers_deque.push_back(item); - selected_server = item; - editing_server = true; - adding_server = true; - needs_focus = true; -} - -static void edit_selected_server(void) -{ - input_itemname = selected_server->name; - - if(selected_server->port != protocol::PORT) { - input_hostname = std::format("{}:{}", selected_server->hostname, selected_server->port); - } - else { - input_hostname = selected_server->hostname; - } - - input_password = selected_server->password; - - editing_server = true; - needs_focus = true; -} - -static void remove_selected_server(void) -{ - gui::bother::cancel(selected_server->identity); - - for(auto it = servers_deque.cbegin(); it != servers_deque.cend(); ++it) { - if(selected_server == (*it)) { - delete selected_server; - selected_server = nullptr; - servers_deque.erase(it); - return; - } - } -} - -static void join_selected_server(void) -{ - if(!session::peer) { - session::connect(selected_server->hostname.c_str(), selected_server->port, selected_server->password.c_str()); - } -} - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if((event.key == GLFW_KEY_ESCAPE) && (event.action == GLFW_PRESS)) { - if(globals::gui_screen == GUI_PLAY_MENU) { - if(editing_server) { - if(adding_server) { - remove_selected_server(); - } - else { - input_itemname.clear(); - input_hostname.clear(); - input_password.clear(); - editing_server = false; - adding_server = false; - return; - } - } - - globals::gui_screen = GUI_MAIN_MENU; - selected_server = nullptr; - return; - } - } -} - -static void on_language_set(const gui::LanguageSetEvent& event) -{ - str_tab_servers = gui::language::resolve_gui("play_menu.tab.servers"); - - str_join = gui::language::resolve_gui("play_menu.join"); - str_connect = gui::language::resolve_gui("play_menu.connect"); - str_add = gui::language::resolve_gui("play_menu.add"); - str_edit = gui::language::resolve_gui("play_menu.edit"); - str_remove = gui::language::resolve_gui("play_menu.remove"); - str_refresh = gui::language::resolve_gui("play_menu.refresh"); - - str_status_init = gui::language::resolve("play_menu.status.init"); - str_status_ping = gui::language::resolve("play_menu.status.ping"); - str_status_fail = gui::language::resolve("play_menu.status.fail"); - - str_outdated_client = gui::language::resolve("play_menu.outdated_client"); - str_outdated_server = gui::language::resolve("play_menu.outdated_server"); -} - -static void on_bother_response(const gui::BotherResponseEvent& event) -{ - for(auto item : servers_deque) { - if(item->identity == event.identity) { - if(event.is_server_unreachable) { - item->protocol_version = 0U; - item->num_players = UINT16_MAX; - item->max_players = UINT16_MAX; - item->motd = str_status_fail; - item->status = item_status::FAILURE; - } - else { - item->protocol_version = event.protocol_version; - item->num_players = event.num_players; - item->max_players = event.max_players; - item->motd = event.motd; - item->status = item_status::REACHED; - } - - break; - } - } -} - -static void layout_server_item(ServerStatusItem* item) -{ - // Preserve the cursor at which we draw stuff - const ImVec2& cursor = ImGui::GetCursorScreenPos(); - const ImVec2& padding = ImGui::GetStyle().FramePadding; - const ImVec2& spacing = ImGui::GetStyle().ItemSpacing; - - const float item_width = ImGui::GetContentRegionAvail().x; - const float line_height = ImGui::GetTextLineHeightWithSpacing(); - const std::string sid = std::format("###play_menu.servers.{}", static_cast<void*>(item)); - if(ImGui::Selectable(sid.c_str(), (item == selected_server), 0, ImVec2(0.0, 2.0f * (line_height + padding.y + spacing.y)))) { - selected_server = item; - editing_server = false; - } - - if(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - // Double clicked - join the selected server - join_selected_server(); - } - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - if(item == selected_server) { - const ImVec2 start = ImVec2(cursor.x, cursor.y); - const ImVec2 end = ImVec2(start.x + item_width, start.y + 2.0f * (line_height + padding.y + spacing.y)); - draw_list->AddRect(start, end, ImGui::GetColorU32(ImGuiCol_Text), 0.0f, 0, globals::gui_scale); - } - - const ImVec2 name_pos = ImVec2(cursor.x + padding.x + 0.5f * spacing.x, cursor.y + padding.y); - draw_list->AddText(name_pos, ImGui::GetColorU32(ImGuiCol_Text), item->name.c_str(), item->name.c_str() + item->name.size()); - - if(item->status == item_status::REACHED) { - auto stats = std::format("{}/{}", item->num_players, item->max_players); - auto stats_width = ImGui::CalcTextSize(stats.c_str(), stats.c_str() + stats.size()).x; - auto stats_pos = ImVec2(cursor.x + item_width - stats_width - padding.x, cursor.y + padding.y); - draw_list->AddText(stats_pos, ImGui::GetColorU32(ImGuiCol_TextDisabled), stats.c_str(), stats.c_str() + stats.size()); - - if(item->protocol_version != protocol::VERSION) { - auto warning_size = ImGui::CalcTextSize(WARNING_TOAST.data(), WARNING_TOAST.data() + WARNING_TOAST.size()); - auto warning_pos = ImVec2(stats_pos.x - warning_size.x - padding.x - 4.0f * globals::gui_scale, cursor.y + padding.y); - auto warning_end = ImVec2(warning_pos.x + warning_size.x, warning_pos.y + warning_size.y); - draw_list->AddText(warning_pos, ImGui::GetColorU32(ImGuiCol_DragDropTarget), WARNING_TOAST.data(), - WARNING_TOAST.data() + WARNING_TOAST.size()); - - if(ImGui::IsMouseHoveringRect(warning_pos, warning_end)) { - ImGui::BeginTooltip(); - - if(item->protocol_version < protocol::VERSION) { - ImGui::TextUnformatted(str_outdated_server.c_str(), str_outdated_server.c_str() + str_outdated_server.size()); - } - else { - ImGui::TextUnformatted(str_outdated_client.c_str(), str_outdated_client.c_str() + str_outdated_client.size()); - } - - ImGui::EndTooltip(); - } - } - } - - ImU32 motd_color = {}; - const std::string* motd_text; - - switch(item->status) { - case item_status::UNKNOWN: - motd_color = ImGui::GetColorU32(ImGuiCol_TextDisabled); - motd_text = &str_status_init; - break; - case item_status::PINGING: - motd_color = ImGui::GetColorU32(ImGuiCol_TextDisabled); - motd_text = &str_status_ping; - break; - case item_status::REACHED: - motd_color = ImGui::GetColorU32(ImGuiCol_TextDisabled); - motd_text = &item->motd; - break; - default: - motd_color = ImGui::GetColorU32(ImGuiCol_PlotLinesHovered); - motd_text = &str_status_fail; - break; - } - - const ImVec2 motd_pos = ImVec2(cursor.x + padding.x + 0.5f * spacing.x, cursor.y + padding.y + line_height); - draw_list->AddText(motd_pos, motd_color, motd_text->c_str(), motd_text->c_str() + motd_text->size()); -} - -static void layout_server_edit(ServerStatusItem* item) -{ - if(needs_focus) { - ImGui::SetKeyboardFocusHere(); - needs_focus = false; - } - - ImGui::SetNextItemWidth(-0.25f * ImGui::GetContentRegionAvail().x); - ImGui::InputText("###play_menu.servers.edit_itemname", &input_itemname); - ImGui::SameLine(); - - const bool ignore_input = utils::is_whitespace(input_itemname) || input_hostname.empty(); - - ImGui::BeginDisabled(ignore_input); - - if(ImGui::Button("OK###play_menu.servers.submit_input", ImVec2(-1.0f, 0.0f)) - || (!ignore_input && ImGui::IsKeyPressed(ImGuiKey_Enter))) { - parse_hostname(item, input_hostname); - item->password = input_password; - item->name = input_itemname.substr(0, MAX_SERVER_ITEM_NAME); - item->status = item_status::UNKNOWN; - editing_server = false; - adding_server = false; - - input_itemname.clear(); - input_hostname.clear(); - - gui::bother::cancel(item->identity); - } - - ImGui::EndDisabled(); - - ImGuiInputTextFlags hostname_flags = ImGuiInputTextFlags_CharsNoBlank; - - if(client_game::streamer_mode.get_value()) { - // Hide server hostname to avoid things like - // followers flooding the server that is streamed online - hostname_flags |= ImGuiInputTextFlags_Password; - } - - ImGui::SetNextItemWidth(-0.50f * ImGui::GetContentRegionAvail().x); - ImGui::InputText("###play_menu.servers.edit_hostname", &input_hostname, hostname_flags); - ImGui::SameLine(); - - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputText("###play_menu.servers.edit_password", &input_password, ImGuiInputTextFlags_Password); -} - -static void layout_servers(void) -{ - if(ImGui::BeginListBox("###play_menu.servers.listbox", ImVec2(-1.0f, -1.0f))) { - for(ServerStatusItem* item : servers_deque) { - if(editing_server && item == selected_server) { - layout_server_edit(item); - } - else { - layout_server_item(item); - } - } - - ImGui::EndListBox(); - } -} - -static void layout_servers_buttons(void) -{ - auto avail_width = ImGui::GetContentRegionAvail().x; - - // Can only join when selected and not editing - ImGui::BeginDisabled(!selected_server || editing_server); - - if(ImGui::Button(str_join.c_str(), ImVec2(-0.50f * avail_width, 0.0f))) { - join_selected_server(); - } - - ImGui::EndDisabled(); - ImGui::SameLine(); - - // Can only connect directly when not editing anything - ImGui::BeginDisabled(editing_server); - - if(ImGui::Button(str_connect.c_str(), ImVec2(-1.00f, 0.0f))) { - globals::gui_screen = GUI_DIRECT_CONNECTION; - } - - ImGui::EndDisabled(); - - // Can only add when not editing anything - ImGui::BeginDisabled(editing_server); - - if(ImGui::Button(str_add.c_str(), ImVec2(-0.75f * avail_width, 0.0f))) { - add_new_server(); - } - - ImGui::EndDisabled(); - ImGui::SameLine(); - - // Can only edit when selected and not editing - ImGui::BeginDisabled(!selected_server || editing_server); - - if(ImGui::Button(str_edit.c_str(), ImVec2(-0.50f * avail_width, 0.0f))) { - edit_selected_server(); - } - - ImGui::EndDisabled(); - ImGui::SameLine(); - - // Can only remove when selected and not editing - ImGui::BeginDisabled(!selected_server || editing_server); - - if(ImGui::Button(str_remove.c_str(), ImVec2(-0.25f * avail_width, 0.0f))) { - remove_selected_server(); - } - - ImGui::EndDisabled(); - ImGui::SameLine(); - - if(ImGui::Button(str_refresh.c_str(), ImVec2(-1.0f, 0.0f))) { - for(ServerStatusItem* item : servers_deque) { - if(item->status != item_status::PINGING) { - if(!editing_server || item != selected_server) { - item->status = item_status::UNKNOWN; - gui::bother::cancel(item->identity); - } - } - } - } -} - -void gui::play_menu::init(void) -{ - if(auto file = PHYSFS_openRead(std::string(SERVERS_TXT).c_str())) { - auto source = std::string(PHYSFS_fileLength(file), char(0x00)); - PHYSFS_readBytes(file, source.data(), source.size()); - PHYSFS_close(file); - - auto stream = std::istringstream(source); - auto line = std::string(); - - while(std::getline(stream, line)) { - auto parts = utils::split(line, "%"); - - auto item = new ServerStatusItem(); - item->port = protocol::PORT; - item->protocol_version = protocol::VERSION; - item->max_players = UINT16_MAX; - item->num_players = UINT16_MAX; - item->identity = next_identity; - item->status = item_status::UNKNOWN; - - next_identity += 1U; - - parse_hostname(item, parts[0]); - - if(parts.size() >= 2) { - item->password = parts[1]; - } - else { - item->password = std::string(); - } - - if(parts.size() >= 3) { - item->name = parts[2].substr(0, MAX_SERVER_ITEM_NAME); - } - else { - item->name = DEFAULT_SERVER_NAME; - } - - servers_deque.push_back(item); - } - } - - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - globals::dispatcher.sink<LanguageSetEvent>().connect<&on_language_set>(); - globals::dispatcher.sink<BotherResponseEvent>().connect<&on_bother_response>(); -} - -void gui::play_menu::shutdown(void) -{ - std::ostringstream stream; - - for(const auto item : servers_deque) { - stream << std::format("{}:{}%{}%{}", item->hostname, item->port, item->password, item->name) << std::endl; - } - - if(auto file = PHYSFS_openWrite(std::string(SERVERS_TXT).c_str())) { - auto source = stream.str(); - PHYSFS_writeBytes(file, source.data(), source.size()); - PHYSFS_close(file); - } - - for(auto item : servers_deque) - delete item; - servers_deque.clear(); -} - -void gui::play_menu::layout(void) -{ - const auto viewport = ImGui::GetMainViewport(); - const auto window_start = ImVec2(viewport->Size.x * 0.05f, viewport->Size.y * 0.05f); - const auto window_size = ImVec2(viewport->Size.x * 0.90f, viewport->Size.y * 0.90f); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###play_menu", nullptr, WINDOW_FLAGS)) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.0f * globals::gui_scale, 3.0f * globals::gui_scale)); - - if(ImGui::BeginTabBar("###play_menu.tabs", ImGuiTabBarFlags_FittingPolicyResizeDown)) { - if(ImGui::TabItemButton("<<")) { - globals::gui_screen = GUI_MAIN_MENU; - selected_server = nullptr; - editing_server = false; - } - - if(ImGui::BeginTabItem(str_tab_servers.c_str())) { - if(ImGui::BeginChild("###play_menu.servers.child", ImVec2(0.0f, -2.0f * ImGui::GetFrameHeightWithSpacing()))) { - layout_servers(); - } - - ImGui::EndChild(); - - layout_servers_buttons(); - - ImGui::EndTabItem(); - } - - ImGui::EndTabBar(); - } - - ImGui::PopStyleVar(); - } - - ImGui::End(); -} - -void gui::play_menu::update_late(void) -{ - for(auto item : servers_deque) { - if(item->status == item_status::UNKNOWN) { - gui::bother::ping(item->identity, item->hostname.c_str(), item->port); - item->status = item_status::PINGING; - continue; - } - } -} +#include "client/pch.hh"
+
+#include "client/gui/play_menu.hh"
+
+#include "core/config/boolean.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "core/utils/string.hh"
+
+#include "shared/protocol.hh"
+
+#include "client/gui/bother.hh"
+#include "client/gui/gui_screen.hh"
+#include "client/gui/language.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/game.hh"
+#include "client/globals.hh"
+#include "client/session.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+constexpr static std::string_view DEFAULT_SERVER_NAME = "Voxelius Server";
+constexpr static std::string_view SERVERS_TXT = "servers.txt";
+constexpr static std::string_view WARNING_TOAST = "[!]";
+
+constexpr static std::size_t MAX_SERVER_ITEM_NAME = 24;
+
+enum class item_status : unsigned int {
+ UNKNOWN = 0x0000U,
+ PINGING = 0x0001U,
+ REACHED = 0x0002U,
+ FAILURE = 0x0003U,
+};
+
+struct ServerStatusItem final {
+ std::string name;
+ std::string password;
+ std::string hostname;
+ std::uint16_t port;
+
+ // Things pulled from bother events
+ std::uint32_t protocol_version;
+ std::uint16_t num_players;
+ std::uint16_t max_players;
+ std::string motd;
+
+ // Unique identifier that monotonically
+ // grows with each new server added and
+ // doesn't reset with each server removed
+ unsigned int identity;
+
+ item_status status;
+};
+
+static std::string str_tab_servers;
+
+static std::string str_join;
+static std::string str_connect;
+static std::string str_add;
+static std::string str_edit;
+static std::string str_remove;
+static std::string str_refresh;
+
+static std::string str_status_init;
+static std::string str_status_ping;
+static std::string str_status_fail;
+
+static std::string str_outdated_client;
+static std::string str_outdated_server;
+
+static std::string input_itemname;
+static std::string input_hostname;
+static std::string input_password;
+
+static unsigned int next_identity;
+static std::deque<ServerStatusItem*> servers_deque;
+static ServerStatusItem* selected_server;
+static bool editing_server;
+static bool adding_server;
+static bool needs_focus;
+
+static void parse_hostname(ServerStatusItem* item, const std::string& hostname)
+{
+ auto parts = utils::split(hostname, ":");
+
+ if(!parts[0].empty()) {
+ item->hostname = parts[0];
+ }
+ else {
+ item->hostname = std::string("localhost");
+ }
+
+ if(parts.size() >= 2) {
+ item->port = math::clamp<std::uint16_t>(strtoul(parts[1].c_str(), nullptr, 10), 1024, UINT16_MAX);
+ }
+ else {
+ item->port = protocol::PORT;
+ }
+}
+
+static void add_new_server(void)
+{
+ auto item = new ServerStatusItem();
+ item->port = protocol::PORT;
+ item->protocol_version = protocol::VERSION;
+ item->max_players = UINT16_MAX;
+ item->num_players = UINT16_MAX;
+ item->identity = next_identity;
+ item->status = item_status::UNKNOWN;
+
+ next_identity += 1U;
+
+ input_itemname = DEFAULT_SERVER_NAME;
+ input_hostname = std::string();
+ input_password = std::string();
+
+ servers_deque.push_back(item);
+ selected_server = item;
+ editing_server = true;
+ adding_server = true;
+ needs_focus = true;
+}
+
+static void edit_selected_server(void)
+{
+ input_itemname = selected_server->name;
+
+ if(selected_server->port != protocol::PORT) {
+ input_hostname = std::format("{}:{}", selected_server->hostname, selected_server->port);
+ }
+ else {
+ input_hostname = selected_server->hostname;
+ }
+
+ input_password = selected_server->password;
+
+ editing_server = true;
+ needs_focus = true;
+}
+
+static void remove_selected_server(void)
+{
+ gui::bother::cancel(selected_server->identity);
+
+ for(auto it = servers_deque.cbegin(); it != servers_deque.cend(); ++it) {
+ if(selected_server == (*it)) {
+ delete selected_server;
+ selected_server = nullptr;
+ servers_deque.erase(it);
+ return;
+ }
+ }
+}
+
+static void join_selected_server(void)
+{
+ if(!session::peer) {
+ session::connect(selected_server->hostname.c_str(), selected_server->port, selected_server->password.c_str());
+ }
+}
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if((event.key == GLFW_KEY_ESCAPE) && (event.action == GLFW_PRESS)) {
+ if(globals::gui_screen == GUI_PLAY_MENU) {
+ if(editing_server) {
+ if(adding_server) {
+ remove_selected_server();
+ }
+ else {
+ input_itemname.clear();
+ input_hostname.clear();
+ input_password.clear();
+ editing_server = false;
+ adding_server = false;
+ return;
+ }
+ }
+
+ globals::gui_screen = GUI_MAIN_MENU;
+ selected_server = nullptr;
+ return;
+ }
+ }
+}
+
+static void on_language_set(const gui::LanguageSetEvent& event)
+{
+ str_tab_servers = gui::language::resolve_gui("play_menu.tab.servers");
+
+ str_join = gui::language::resolve_gui("play_menu.join");
+ str_connect = gui::language::resolve_gui("play_menu.connect");
+ str_add = gui::language::resolve_gui("play_menu.add");
+ str_edit = gui::language::resolve_gui("play_menu.edit");
+ str_remove = gui::language::resolve_gui("play_menu.remove");
+ str_refresh = gui::language::resolve_gui("play_menu.refresh");
+
+ str_status_init = gui::language::resolve("play_menu.status.init");
+ str_status_ping = gui::language::resolve("play_menu.status.ping");
+ str_status_fail = gui::language::resolve("play_menu.status.fail");
+
+ str_outdated_client = gui::language::resolve("play_menu.outdated_client");
+ str_outdated_server = gui::language::resolve("play_menu.outdated_server");
+}
+
+static void on_bother_response(const gui::BotherResponseEvent& event)
+{
+ for(auto item : servers_deque) {
+ if(item->identity == event.identity) {
+ if(event.is_server_unreachable) {
+ item->protocol_version = 0U;
+ item->num_players = UINT16_MAX;
+ item->max_players = UINT16_MAX;
+ item->motd = str_status_fail;
+ item->status = item_status::FAILURE;
+ }
+ else {
+ item->protocol_version = event.protocol_version;
+ item->num_players = event.num_players;
+ item->max_players = event.max_players;
+ item->motd = event.motd;
+ item->status = item_status::REACHED;
+ }
+
+ break;
+ }
+ }
+}
+
+static void layout_server_item(ServerStatusItem* item)
+{
+ // Preserve the cursor at which we draw stuff
+ const ImVec2& cursor = ImGui::GetCursorScreenPos();
+ const ImVec2& padding = ImGui::GetStyle().FramePadding;
+ const ImVec2& spacing = ImGui::GetStyle().ItemSpacing;
+
+ const float item_width = ImGui::GetContentRegionAvail().x;
+ const float line_height = ImGui::GetTextLineHeightWithSpacing();
+ const std::string sid = std::format("###play_menu.servers.{}", static_cast<void*>(item));
+ if(ImGui::Selectable(sid.c_str(), (item == selected_server), 0, ImVec2(0.0, 2.0f * (line_height + padding.y + spacing.y)))) {
+ selected_server = item;
+ editing_server = false;
+ }
+
+ if(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
+ // Double clicked - join the selected server
+ join_selected_server();
+ }
+
+ ImDrawList* draw_list = ImGui::GetWindowDrawList();
+
+ if(item == selected_server) {
+ const ImVec2 start = ImVec2(cursor.x, cursor.y);
+ const ImVec2 end = ImVec2(start.x + item_width, start.y + 2.0f * (line_height + padding.y + spacing.y));
+ draw_list->AddRect(start, end, ImGui::GetColorU32(ImGuiCol_Text), 0.0f, 0, globals::gui_scale);
+ }
+
+ const ImVec2 name_pos = ImVec2(cursor.x + padding.x + 0.5f * spacing.x, cursor.y + padding.y);
+ draw_list->AddText(name_pos, ImGui::GetColorU32(ImGuiCol_Text), item->name.c_str(), item->name.c_str() + item->name.size());
+
+ if(item->status == item_status::REACHED) {
+ auto stats = std::format("{}/{}", item->num_players, item->max_players);
+ auto stats_width = ImGui::CalcTextSize(stats.c_str(), stats.c_str() + stats.size()).x;
+ auto stats_pos = ImVec2(cursor.x + item_width - stats_width - padding.x, cursor.y + padding.y);
+ draw_list->AddText(stats_pos, ImGui::GetColorU32(ImGuiCol_TextDisabled), stats.c_str(), stats.c_str() + stats.size());
+
+ if(item->protocol_version != protocol::VERSION) {
+ auto warning_size = ImGui::CalcTextSize(WARNING_TOAST.data(), WARNING_TOAST.data() + WARNING_TOAST.size());
+ auto warning_pos = ImVec2(stats_pos.x - warning_size.x - padding.x - 4.0f * globals::gui_scale, cursor.y + padding.y);
+ auto warning_end = ImVec2(warning_pos.x + warning_size.x, warning_pos.y + warning_size.y);
+ draw_list->AddText(warning_pos, ImGui::GetColorU32(ImGuiCol_DragDropTarget), WARNING_TOAST.data(),
+ WARNING_TOAST.data() + WARNING_TOAST.size());
+
+ if(ImGui::IsMouseHoveringRect(warning_pos, warning_end)) {
+ ImGui::BeginTooltip();
+
+ if(item->protocol_version < protocol::VERSION) {
+ ImGui::TextUnformatted(str_outdated_server.c_str(), str_outdated_server.c_str() + str_outdated_server.size());
+ }
+ else {
+ ImGui::TextUnformatted(str_outdated_client.c_str(), str_outdated_client.c_str() + str_outdated_client.size());
+ }
+
+ ImGui::EndTooltip();
+ }
+ }
+ }
+
+ ImU32 motd_color = {};
+ const std::string* motd_text;
+
+ switch(item->status) {
+ case item_status::UNKNOWN:
+ motd_color = ImGui::GetColorU32(ImGuiCol_TextDisabled);
+ motd_text = &str_status_init;
+ break;
+ case item_status::PINGING:
+ motd_color = ImGui::GetColorU32(ImGuiCol_TextDisabled);
+ motd_text = &str_status_ping;
+ break;
+ case item_status::REACHED:
+ motd_color = ImGui::GetColorU32(ImGuiCol_TextDisabled);
+ motd_text = &item->motd;
+ break;
+ default:
+ motd_color = ImGui::GetColorU32(ImGuiCol_PlotLinesHovered);
+ motd_text = &str_status_fail;
+ break;
+ }
+
+ const ImVec2 motd_pos = ImVec2(cursor.x + padding.x + 0.5f * spacing.x, cursor.y + padding.y + line_height);
+ draw_list->AddText(motd_pos, motd_color, motd_text->c_str(), motd_text->c_str() + motd_text->size());
+}
+
+static void layout_server_edit(ServerStatusItem* item)
+{
+ if(needs_focus) {
+ ImGui::SetKeyboardFocusHere();
+ needs_focus = false;
+ }
+
+ ImGui::SetNextItemWidth(-0.25f * ImGui::GetContentRegionAvail().x);
+ ImGui::InputText("###play_menu.servers.edit_itemname", &input_itemname);
+ ImGui::SameLine();
+
+ const bool ignore_input = utils::is_whitespace(input_itemname) || input_hostname.empty();
+
+ ImGui::BeginDisabled(ignore_input);
+
+ if(ImGui::Button("OK###play_menu.servers.submit_input", ImVec2(-1.0f, 0.0f))
+ || (!ignore_input && ImGui::IsKeyPressed(ImGuiKey_Enter))) {
+ parse_hostname(item, input_hostname);
+ item->password = input_password;
+ item->name = input_itemname.substr(0, MAX_SERVER_ITEM_NAME);
+ item->status = item_status::UNKNOWN;
+ editing_server = false;
+ adding_server = false;
+
+ input_itemname.clear();
+ input_hostname.clear();
+
+ gui::bother::cancel(item->identity);
+ }
+
+ ImGui::EndDisabled();
+
+ ImGuiInputTextFlags hostname_flags = ImGuiInputTextFlags_CharsNoBlank;
+
+ if(client_game::streamer_mode.get_value()) {
+ // Hide server hostname to avoid things like
+ // followers flooding the server that is streamed online
+ hostname_flags |= ImGuiInputTextFlags_Password;
+ }
+
+ ImGui::SetNextItemWidth(-0.50f * ImGui::GetContentRegionAvail().x);
+ ImGui::InputText("###play_menu.servers.edit_hostname", &input_hostname, hostname_flags);
+ ImGui::SameLine();
+
+ ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
+ ImGui::InputText("###play_menu.servers.edit_password", &input_password, ImGuiInputTextFlags_Password);
+}
+
+static void layout_servers(void)
+{
+ if(ImGui::BeginListBox("###play_menu.servers.listbox", ImVec2(-1.0f, -1.0f))) {
+ for(ServerStatusItem* item : servers_deque) {
+ if(editing_server && item == selected_server) {
+ layout_server_edit(item);
+ }
+ else {
+ layout_server_item(item);
+ }
+ }
+
+ ImGui::EndListBox();
+ }
+}
+
+static void layout_servers_buttons(void)
+{
+ auto avail_width = ImGui::GetContentRegionAvail().x;
+
+ // Can only join when selected and not editing
+ ImGui::BeginDisabled(!selected_server || editing_server);
+
+ if(ImGui::Button(str_join.c_str(), ImVec2(-0.50f * avail_width, 0.0f))) {
+ join_selected_server();
+ }
+
+ ImGui::EndDisabled();
+ ImGui::SameLine();
+
+ // Can only connect directly when not editing anything
+ ImGui::BeginDisabled(editing_server);
+
+ if(ImGui::Button(str_connect.c_str(), ImVec2(-1.00f, 0.0f))) {
+ globals::gui_screen = GUI_DIRECT_CONNECTION;
+ }
+
+ ImGui::EndDisabled();
+
+ // Can only add when not editing anything
+ ImGui::BeginDisabled(editing_server);
+
+ if(ImGui::Button(str_add.c_str(), ImVec2(-0.75f * avail_width, 0.0f))) {
+ add_new_server();
+ }
+
+ ImGui::EndDisabled();
+ ImGui::SameLine();
+
+ // Can only edit when selected and not editing
+ ImGui::BeginDisabled(!selected_server || editing_server);
+
+ if(ImGui::Button(str_edit.c_str(), ImVec2(-0.50f * avail_width, 0.0f))) {
+ edit_selected_server();
+ }
+
+ ImGui::EndDisabled();
+ ImGui::SameLine();
+
+ // Can only remove when selected and not editing
+ ImGui::BeginDisabled(!selected_server || editing_server);
+
+ if(ImGui::Button(str_remove.c_str(), ImVec2(-0.25f * avail_width, 0.0f))) {
+ remove_selected_server();
+ }
+
+ ImGui::EndDisabled();
+ ImGui::SameLine();
+
+ if(ImGui::Button(str_refresh.c_str(), ImVec2(-1.0f, 0.0f))) {
+ for(ServerStatusItem* item : servers_deque) {
+ if(item->status != item_status::PINGING) {
+ if(!editing_server || item != selected_server) {
+ item->status = item_status::UNKNOWN;
+ gui::bother::cancel(item->identity);
+ }
+ }
+ }
+ }
+}
+
+void gui::play_menu::init(void)
+{
+ if(auto file = PHYSFS_openRead(std::string(SERVERS_TXT).c_str())) {
+ auto source = std::string(PHYSFS_fileLength(file), char(0x00));
+ PHYSFS_readBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+
+ auto stream = std::istringstream(source);
+ auto line = std::string();
+
+ while(std::getline(stream, line)) {
+ auto parts = utils::split(line, "%");
+
+ auto item = new ServerStatusItem();
+ item->port = protocol::PORT;
+ item->protocol_version = protocol::VERSION;
+ item->max_players = UINT16_MAX;
+ item->num_players = UINT16_MAX;
+ item->identity = next_identity;
+ item->status = item_status::UNKNOWN;
+
+ next_identity += 1U;
+
+ parse_hostname(item, parts[0]);
+
+ if(parts.size() >= 2) {
+ item->password = parts[1];
+ }
+ else {
+ item->password = std::string();
+ }
+
+ if(parts.size() >= 3) {
+ item->name = parts[2].substr(0, MAX_SERVER_ITEM_NAME);
+ }
+ else {
+ item->name = DEFAULT_SERVER_NAME;
+ }
+
+ servers_deque.push_back(item);
+ }
+ }
+
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+ globals::dispatcher.sink<LanguageSetEvent>().connect<&on_language_set>();
+ globals::dispatcher.sink<BotherResponseEvent>().connect<&on_bother_response>();
+}
+
+void gui::play_menu::shutdown(void)
+{
+ std::ostringstream stream;
+
+ for(const auto item : servers_deque) {
+ stream << std::format("{}:{}%{}%{}", item->hostname, item->port, item->password, item->name) << std::endl;
+ }
+
+ if(auto file = PHYSFS_openWrite(std::string(SERVERS_TXT).c_str())) {
+ auto source = stream.str();
+ PHYSFS_writeBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+ }
+
+ for(auto item : servers_deque)
+ delete item;
+ servers_deque.clear();
+}
+
+void gui::play_menu::layout(void)
+{
+ const auto viewport = ImGui::GetMainViewport();
+ const auto window_start = ImVec2(viewport->Size.x * 0.05f, viewport->Size.y * 0.05f);
+ const auto window_size = ImVec2(viewport->Size.x * 0.90f, viewport->Size.y * 0.90f);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###play_menu", nullptr, WINDOW_FLAGS)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.0f * globals::gui_scale, 3.0f * globals::gui_scale));
+
+ if(ImGui::BeginTabBar("###play_menu.tabs", ImGuiTabBarFlags_FittingPolicyResizeDown)) {
+ if(ImGui::TabItemButton("<<")) {
+ globals::gui_screen = GUI_MAIN_MENU;
+ selected_server = nullptr;
+ editing_server = false;
+ }
+
+ if(ImGui::BeginTabItem(str_tab_servers.c_str())) {
+ if(ImGui::BeginChild("###play_menu.servers.child", ImVec2(0.0f, -2.0f * ImGui::GetFrameHeightWithSpacing()))) {
+ layout_servers();
+ }
+
+ ImGui::EndChild();
+
+ layout_servers_buttons();
+
+ ImGui::EndTabItem();
+ }
+
+ ImGui::EndTabBar();
+ }
+
+ ImGui::PopStyleVar();
+ }
+
+ ImGui::End();
+}
+
+void gui::play_menu::update_late(void)
+{
+ for(auto item : servers_deque) {
+ if(item->status == item_status::UNKNOWN) {
+ gui::bother::ping(item->identity, item->hostname.c_str(), item->port);
+ item->status = item_status::PINGING;
+ continue;
+ }
+ }
+}
diff --git a/game/client/gui/play_menu.hh b/game/client/gui/play_menu.hh index 1b1f003..15f7c89 100644 --- a/game/client/gui/play_menu.hh +++ b/game/client/gui/play_menu.hh @@ -1,9 +1,9 @@ -#pragma once - -namespace gui::play_menu -{ -void init(void); -void shutdown(void); -void layout(void); -void update_late(void); -} // namespace gui::play_menu +#pragma once
+
+namespace gui::play_menu
+{
+void init(void);
+void shutdown(void);
+void layout(void);
+void update_late(void);
+} // namespace gui::play_menu
diff --git a/game/client/gui/progress_bar.cc b/game/client/gui/progress_bar.cc index 8be2f8c..9266ce1 100644 --- a/game/client/gui/progress_bar.cc +++ b/game/client/gui/progress_bar.cc @@ -1,111 +1,111 @@ -#include "client/pch.hh" - -#include "client/gui/progress_bar.hh" - -#include "core/math/constexpr.hh" - -#include "client/gui/language.hh" - -#include "client/globals.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; - -static std::string str_title; -static std::string str_button; -static gui::progress_bar_action button_action; - -void gui::progress_bar::init(void) -{ - str_title = "Loading"; - str_button = std::string(); - button_action = nullptr; -} - -void gui::progress_bar::layout(void) -{ - const auto viewport = ImGui::GetMainViewport(); - const auto window_start = ImVec2(0.0f, viewport->Size.y * 0.30f); - const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y * 0.70f); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###UIProgress", nullptr, WINDOW_FLAGS)) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 1.0f * globals::gui_scale)); - - const float title_width = ImGui::CalcTextSize(str_title.c_str()).x; - ImGui::SetCursorPosX(0.5f * (window_size.x - title_width)); - ImGui::TextUnformatted(str_title.c_str()); - - ImGui::Dummy(ImVec2(0.0f, 8.0f * globals::gui_scale)); - - const ImVec2 cursor = ImGui::GetCursorPos(); - - const std::size_t num_bars = 32; - const float spinner_width = 0.8f * ImGui::CalcItemWidth(); - const float bar_width = spinner_width / static_cast<float>(num_bars); - const float bar_height = 0.5f * ImGui::GetFrameHeight(); - - const float base_xpos = window_start.x + 0.5f * (window_size.x - spinner_width) + 0.5f; - const float base_ypos = window_start.y + cursor.y; - const float phase = 2.0f * ImGui::GetTime(); - - const ImVec4& background = ImGui::GetStyleColorVec4(ImGuiCol_Button); - const ImVec4& foreground = ImGui::GetStyleColorVec4(ImGuiCol_PlotHistogram); - - for(std::size_t i = 0; i < num_bars; ++i) { - const float sinval = std::sin(M_PI * static_cast<float>(i) / static_cast<float>(num_bars) - phase); - const float modifier = std::exp(-8.0f * (0.5f + 0.5f * sinval)); - - ImVec4 color = {}; - color.x = math::lerp(background.x, foreground.x, modifier); - color.y = math::lerp(background.y, foreground.y, modifier); - color.z = math::lerp(background.z, foreground.z, modifier); - color.w = math::lerp(background.w, foreground.w, modifier); - - const ImVec2 start = ImVec2(base_xpos + bar_width * i, base_ypos); - const ImVec2 end = ImVec2(start.x + bar_width, start.y + bar_height); - ImGui::GetWindowDrawList()->AddRectFilled(start, end, ImGui::GetColorU32(color)); - } - - // The NewLine call tricks ImGui into correctly padding the - // next widget that comes after the progress_bar spinner; this - // is needed to ensure the button is located in the correct place - ImGui::NewLine(); - - if(!str_button.empty()) { - ImGui::Dummy(ImVec2(0.0f, 32.0f * globals::gui_scale)); - - const float button_width = 0.8f * ImGui::CalcItemWidth(); - ImGui::SetCursorPosX(0.5f * (window_size.x - button_width)); - - if(ImGui::Button(str_button.c_str(), ImVec2(button_width, 0.0f))) { - if(button_action) { - button_action(); - } - } - } - - ImGui::PopStyleVar(); - } - - ImGui::End(); -} - -void gui::progress_bar::reset(void) -{ - str_title.clear(); - str_button.clear(); - button_action = nullptr; -} - -void gui::progress_bar::set_title(std::string_view title) -{ - str_title = gui::language::resolve(title); -} - -void gui::progress_bar::set_button(std::string_view text, const progress_bar_action& action) -{ - str_button = std::format("{}###ProgressBar_Button", gui::language::resolve(text)); - button_action = action; -} +#include "client/pch.hh"
+
+#include "client/gui/progress_bar.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "client/gui/language.hh"
+
+#include "client/globals.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+
+static std::string str_title;
+static std::string str_button;
+static gui::progress_bar_action button_action;
+
+void gui::progress_bar::init(void)
+{
+ str_title = "Loading";
+ str_button = std::string();
+ button_action = nullptr;
+}
+
+void gui::progress_bar::layout(void)
+{
+ const auto viewport = ImGui::GetMainViewport();
+ const auto window_start = ImVec2(0.0f, viewport->Size.y * 0.30f);
+ const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y * 0.70f);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###UIProgress", nullptr, WINDOW_FLAGS)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0.0f, 1.0f * globals::gui_scale));
+
+ const float title_width = ImGui::CalcTextSize(str_title.c_str()).x;
+ ImGui::SetCursorPosX(0.5f * (window_size.x - title_width));
+ ImGui::TextUnformatted(str_title.c_str());
+
+ ImGui::Dummy(ImVec2(0.0f, 8.0f * globals::gui_scale));
+
+ const ImVec2 cursor = ImGui::GetCursorPos();
+
+ const std::size_t num_bars = 32;
+ const float spinner_width = 0.8f * ImGui::CalcItemWidth();
+ const float bar_width = spinner_width / static_cast<float>(num_bars);
+ const float bar_height = 0.5f * ImGui::GetFrameHeight();
+
+ const float base_xpos = window_start.x + 0.5f * (window_size.x - spinner_width) + 0.5f;
+ const float base_ypos = window_start.y + cursor.y;
+ const float phase = 2.0f * ImGui::GetTime();
+
+ const ImVec4& background = ImGui::GetStyleColorVec4(ImGuiCol_Button);
+ const ImVec4& foreground = ImGui::GetStyleColorVec4(ImGuiCol_PlotHistogram);
+
+ for(std::size_t i = 0; i < num_bars; ++i) {
+ const float sinval = std::sin(M_PI * static_cast<float>(i) / static_cast<float>(num_bars) - phase);
+ const float modifier = std::exp(-8.0f * (0.5f + 0.5f * sinval));
+
+ ImVec4 color = {};
+ color.x = math::lerp(background.x, foreground.x, modifier);
+ color.y = math::lerp(background.y, foreground.y, modifier);
+ color.z = math::lerp(background.z, foreground.z, modifier);
+ color.w = math::lerp(background.w, foreground.w, modifier);
+
+ const ImVec2 start = ImVec2(base_xpos + bar_width * i, base_ypos);
+ const ImVec2 end = ImVec2(start.x + bar_width, start.y + bar_height);
+ ImGui::GetWindowDrawList()->AddRectFilled(start, end, ImGui::GetColorU32(color));
+ }
+
+ // The NewLine call tricks ImGui into correctly padding the
+ // next widget that comes after the progress_bar spinner; this
+ // is needed to ensure the button is located in the correct place
+ ImGui::NewLine();
+
+ if(!str_button.empty()) {
+ ImGui::Dummy(ImVec2(0.0f, 32.0f * globals::gui_scale));
+
+ const float button_width = 0.8f * ImGui::CalcItemWidth();
+ ImGui::SetCursorPosX(0.5f * (window_size.x - button_width));
+
+ if(ImGui::Button(str_button.c_str(), ImVec2(button_width, 0.0f))) {
+ if(button_action) {
+ button_action();
+ }
+ }
+ }
+
+ ImGui::PopStyleVar();
+ }
+
+ ImGui::End();
+}
+
+void gui::progress_bar::reset(void)
+{
+ str_title.clear();
+ str_button.clear();
+ button_action = nullptr;
+}
+
+void gui::progress_bar::set_title(std::string_view title)
+{
+ str_title = gui::language::resolve(title);
+}
+
+void gui::progress_bar::set_button(std::string_view text, const progress_bar_action& action)
+{
+ str_button = std::format("{}###ProgressBar_Button", gui::language::resolve(text));
+ button_action = action;
+}
diff --git a/game/client/gui/progress_bar.hh b/game/client/gui/progress_bar.hh index 7a0581d..5f2e601 100644 --- a/game/client/gui/progress_bar.hh +++ b/game/client/gui/progress_bar.hh @@ -1,19 +1,19 @@ -#pragma once - -namespace gui -{ -using progress_bar_action = void (*)(void); -} // namespace gui - -namespace gui::progress_bar -{ -void init(void); -void layout(void); -} // namespace gui::progress_bar - -namespace gui::progress_bar -{ -void reset(void); -void set_title(std::string_view title); -void set_button(std::string_view text, const progress_bar_action& action); -} // namespace gui::progress_bar +#pragma once
+
+namespace gui
+{
+using progress_bar_action = void (*)(void);
+} // namespace gui
+
+namespace gui::progress_bar
+{
+void init(void);
+void layout(void);
+} // namespace gui::progress_bar
+
+namespace gui::progress_bar
+{
+void reset(void);
+void set_title(std::string_view title);
+void set_button(std::string_view text, const progress_bar_action& action);
+} // namespace gui::progress_bar
diff --git a/game/client/gui/scoreboard.cc b/game/client/gui/scoreboard.cc index fabd753..f875ef2 100644 --- a/game/client/gui/scoreboard.cc +++ b/game/client/gui/scoreboard.cc @@ -1,103 +1,103 @@ -#include "client/pch.hh" - -#include "client/gui/scoreboard.hh" - -#include "core/io/config_map.hh" - -#include "shared/protocol.hh" - -#include "client/config/keybind.hh" - -#include "client/gui/gui_screen.hh" -#include "client/gui/settings.hh" - -#include "client/globals.hh" -#include "client/session.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = - ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground; - -static config::KeyBind list_key(GLFW_KEY_TAB); - -static std::vector<std::string> usernames; -static float max_username_size; - -static void on_scoreboard_update_packet(const protocol::ScoreboardUpdate& packet) -{ - usernames = packet.names; - max_username_size = 0.0f; -} - -void gui::scoreboard::init(void) -{ - globals::client_config.add_value("scoreboard.key", list_key); - - settings::add_keybind(3, list_key, settings_location::KEYBOARD_MISC, "key.scoreboard"); - - globals::dispatcher.sink<protocol::ScoreboardUpdate>().connect<&on_scoreboard_update_packet>(); -} - -void gui::scoreboard::layout(void) -{ - if(globals::gui_screen == GUI_SCREEN_NONE && session::is_ingame() && glfwGetKey(globals::window, list_key.get_key()) == GLFW_PRESS) { - const auto viewport = ImGui::GetMainViewport(); - const auto window_start = ImVec2(0.0f, 0.0f); - const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(!ImGui::Begin("###chat", nullptr, WINDOW_FLAGS)) { - ImGui::End(); - return; - } - - ImGui::PushFont(globals::font_unscii16, 8.0f); - - const auto& padding = ImGui::GetStyle().FramePadding; - const auto& spacing = ImGui::GetStyle().ItemSpacing; - auto font = globals::font_unscii8; - - // Figure out the maximum username size - for(const auto& username : usernames) { - const ImVec2 size = ImGui::CalcTextSize(username.c_str(), username.c_str() + username.size()); - - if(size.x > max_username_size) { - max_username_size = size.x; - } - } - - // Having a minimum size allows for - // generally better in-game visibility - const float true_size = math::max<float>(0.25f * window_size.x, max_username_size); - - // Figure out username rect dimensions - const float rect_start_x = 0.5f * window_size.x - 0.5f * true_size; - const float rect_start_y = 0.15f * window_size.y; - const float rect_size_x = 2.0f * padding.x + true_size; - const float rect_size_y = 2.0f * padding.y + 0.5f * ImGui::GetFontSize(); - - // const ImU32 border_col = ImGui::GetColorU32(ImGuiCol_Border, 1.00f); - const ImU32 rect_col = ImGui::GetColorU32(ImGuiCol_FrameBg, 0.80f); - const ImU32 text_col = ImGui::GetColorU32(ImGuiCol_Text, 1.00f); - - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - // Slightly space apart individual rows - const float row_step_y = rect_size_y + 0.5f * spacing.y; - - for(std::size_t i = 0; i < usernames.size(); ++i) { - const ImVec2 rect_a = ImVec2(rect_start_x, rect_start_y + i * row_step_y); - const ImVec2 rect_b = ImVec2(rect_a.x + rect_size_x, rect_a.y + rect_size_y); - const ImVec2 text_pos = ImVec2(rect_a.x + padding.x, rect_a.y + padding.y); - - // draw_list->AddRect(rect_a, rect_b, border_col, 0.0f, ImDrawFlags_None, globals::gui_scale); - draw_list->AddRectFilled(rect_a, rect_b, rect_col, 0.0f, ImDrawFlags_None); - draw_list->AddText( - font, 0.5f * ImGui::GetFontSize(), text_pos, text_col, usernames[i].c_str(), usernames[i].c_str() + usernames[i].size()); - } - - ImGui::PopFont(); - ImGui::End(); - } -} +#include "client/pch.hh"
+
+#include "client/gui/scoreboard.hh"
+
+#include "core/io/config_map.hh"
+
+#include "shared/protocol.hh"
+
+#include "client/config/keybind.hh"
+
+#include "client/gui/gui_screen.hh"
+#include "client/gui/settings.hh"
+
+#include "client/globals.hh"
+#include "client/session.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs
+ | ImGuiWindowFlags_NoBackground;
+
+static config::KeyBind list_key(GLFW_KEY_TAB);
+
+static std::vector<std::string> usernames;
+static float max_username_size;
+
+static void on_scoreboard_update_packet(const protocol::ScoreboardUpdate& packet)
+{
+ usernames = packet.names;
+ max_username_size = 0.0f;
+}
+
+void gui::scoreboard::init(void)
+{
+ globals::client_config.add_value("scoreboard.key", list_key);
+
+ settings::add_keybind(3, list_key, settings_location::KEYBOARD_MISC, "key.scoreboard");
+
+ globals::dispatcher.sink<protocol::ScoreboardUpdate>().connect<&on_scoreboard_update_packet>();
+}
+
+void gui::scoreboard::layout(void)
+{
+ if(globals::gui_screen == GUI_SCREEN_NONE && session::is_ingame() && glfwGetKey(globals::window, list_key.get_key()) == GLFW_PRESS) {
+ const auto viewport = ImGui::GetMainViewport();
+ const auto window_start = ImVec2(0.0f, 0.0f);
+ const auto window_size = ImVec2(viewport->Size.x, viewport->Size.y);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(!ImGui::Begin("###chat", nullptr, WINDOW_FLAGS)) {
+ ImGui::End();
+ return;
+ }
+
+ ImGui::PushFont(globals::font_unscii16, 8.0f);
+
+ const auto& padding = ImGui::GetStyle().FramePadding;
+ const auto& spacing = ImGui::GetStyle().ItemSpacing;
+ auto font = globals::font_unscii8;
+
+ // Figure out the maximum username size
+ for(const auto& username : usernames) {
+ const ImVec2 size = ImGui::CalcTextSize(username.c_str(), username.c_str() + username.size());
+
+ if(size.x > max_username_size) {
+ max_username_size = size.x;
+ }
+ }
+
+ // Having a minimum size allows for
+ // generally better in-game visibility
+ const float true_size = math::max<float>(0.25f * window_size.x, max_username_size);
+
+ // Figure out username rect dimensions
+ const float rect_start_x = 0.5f * window_size.x - 0.5f * true_size;
+ const float rect_start_y = 0.15f * window_size.y;
+ const float rect_size_x = 2.0f * padding.x + true_size;
+ const float rect_size_y = 2.0f * padding.y + 0.5f * ImGui::GetFontSize();
+
+ // const ImU32 border_col = ImGui::GetColorU32(ImGuiCol_Border, 1.00f);
+ const ImU32 rect_col = ImGui::GetColorU32(ImGuiCol_FrameBg, 0.80f);
+ const ImU32 text_col = ImGui::GetColorU32(ImGuiCol_Text, 1.00f);
+
+ ImDrawList* draw_list = ImGui::GetWindowDrawList();
+
+ // Slightly space apart individual rows
+ const float row_step_y = rect_size_y + 0.5f * spacing.y;
+
+ for(std::size_t i = 0; i < usernames.size(); ++i) {
+ const ImVec2 rect_a = ImVec2(rect_start_x, rect_start_y + i * row_step_y);
+ const ImVec2 rect_b = ImVec2(rect_a.x + rect_size_x, rect_a.y + rect_size_y);
+ const ImVec2 text_pos = ImVec2(rect_a.x + padding.x, rect_a.y + padding.y);
+
+ // draw_list->AddRect(rect_a, rect_b, border_col, 0.0f, ImDrawFlags_None, globals::gui_scale);
+ draw_list->AddRectFilled(rect_a, rect_b, rect_col, 0.0f, ImDrawFlags_None);
+ draw_list->AddText(font, 0.5f * ImGui::GetFontSize(), text_pos, text_col, usernames[i].c_str(),
+ usernames[i].c_str() + usernames[i].size());
+ }
+
+ ImGui::PopFont();
+ ImGui::End();
+ }
+}
diff --git a/game/client/gui/scoreboard.hh b/game/client/gui/scoreboard.hh index 320e185..5f1c59d 100644 --- a/game/client/gui/scoreboard.hh +++ b/game/client/gui/scoreboard.hh @@ -1,7 +1,7 @@ -#pragma once - -namespace gui::scoreboard -{ -void init(void); -void layout(void); -} // namespace gui::scoreboard +#pragma once
+
+namespace gui::scoreboard
+{
+void init(void);
+void layout(void);
+} // namespace gui::scoreboard
diff --git a/game/client/gui/settings.cc b/game/client/gui/settings.cc index 693f0d6..db26d8c 100644 --- a/game/client/gui/settings.cc +++ b/game/client/gui/settings.cc @@ -1,1069 +1,1069 @@ -#include "client/pch.hh" - -#include "client/gui/settings.hh" - -#include "core/config/boolean.hh" -#include "core/config/number.hh" -#include "core/config/string.hh" - -#include "core/io/config_map.hh" - -#include "core/math/constexpr.hh" - -#include "client/config/gamepad_axis.hh" -#include "client/config/gamepad_button.hh" -#include "client/config/keybind.hh" - -#include "client/gui/gui_screen.hh" -#include "client/gui/language.hh" - -#include "client/io/gamepad.hh" -#include "client/io/glfw.hh" - -#include "client/const.hh" -#include "client/globals.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; -constexpr static unsigned int NUM_LOCATIONS = static_cast<unsigned int>(settings_location::COUNT); - -enum class setting_type : unsigned int { - CHECKBOX = 0x0000U, ///< config::Boolean - INPUT_INT = 0x0001U, ///< config::Number<int> - INPUT_FLOAT = 0x0002U, ///< config::Number<float> - INPUT_UINT = 0x0003U, ///< config::Number<unsigned int> - INPUT_STRING = 0x0004U, ///< config::String - SLIDER_INT = 0x0005U, ///< config::Number<int> - SLIDER_FLOAT = 0x0006U, ///< config::Number<float> - SLIDER_UINT = 0x0007U, ///< config::Number<unsigned int> - STEPPER_INT = 0x0008U, ///< config::Number<int> - STEPPER_UINT = 0x0009U, ///< config::Number<unsigned int> - KEYBIND = 0x000AU, ///< config::KeyBind - GAMEPAD_AXIS = 0x000BU, ///< config::GamepadAxis - GAMEPAD_BUTTON = 0x000CU, ///< config::GamepadButton - LANGUAGE_SELECT = 0x000DU, ///< config::String internally -}; - -class SettingValue { -public: - virtual ~SettingValue(void) = default; - virtual void layout(void) const = 0; - void layout_tooltip(void) const; - void layout_label(void) const; - -public: - setting_type type; - std::string tooltip; - std::string title; - std::string name; - bool has_tooltip; - int priority; -}; - -class SettingValueWID : public SettingValue { -public: - virtual ~SettingValueWID(void) = default; - -public: - std::string wid; -}; - -class SettingValue_CheckBox final : public SettingValue { -public: - virtual ~SettingValue_CheckBox(void) = default; - virtual void layout(void) const override; - void refresh_wids(void); - -public: - config::Boolean* value; - std::string wids[2]; -}; - -class SettingValue_InputInt final : public SettingValueWID { -public: - virtual ~SettingValue_InputInt(void) = default; - virtual void layout(void) const override; - -public: - config::Int* value; -}; - -class SettingValue_InputFloat final : public SettingValueWID { -public: - virtual ~SettingValue_InputFloat(void) = default; - virtual void layout(void) const override; - -public: - std::string format; - config::Float* value; -}; - -class SettingValue_InputUnsigned final : public SettingValueWID { -public: - virtual ~SettingValue_InputUnsigned(void) = default; - virtual void layout(void) const override; - -public: - config::Unsigned* value; -}; - -class SettingValue_InputString final : public SettingValueWID { -public: - virtual ~SettingValue_InputString(void) = default; - virtual void layout(void) const override; - -public: - config::String* value; - bool allow_whitespace; -}; - -class SettingValue_SliderInt final : public SettingValueWID { -public: - virtual ~SettingValue_SliderInt(void) = default; - virtual void layout(void) const override; - -public: - config::Int* value; -}; - -class SettingValue_SliderFloat final : public SettingValueWID { -public: - virtual ~SettingValue_SliderFloat(void) = default; - virtual void layout(void) const override; - -public: - std::string format; - config::Float* value; -}; - -class SettingValue_SliderUnsigned final : public SettingValueWID { -public: - virtual ~SettingValue_SliderUnsigned(void) = default; - virtual void layout(void) const override; - -public: - config::Unsigned* value; -}; - -class SettingValue_StepperInt final : public SettingValue { -public: - virtual ~SettingValue_StepperInt(void) = default; - virtual void layout(void) const override; - void refresh_wids(void); - -public: - std::vector<std::string> wids; - config::Int* value; -}; - -class SettingValue_StepperUnsigned final : public SettingValue { -public: - virtual ~SettingValue_StepperUnsigned(void) = default; - virtual void layout(void) const override; - void refresh_wids(void); - -public: - std::vector<std::string> wids; - config::Unsigned* value; -}; - -class SettingValue_KeyBind final : public SettingValue { -public: - virtual ~SettingValue_KeyBind(void) = default; - virtual void layout(void) const override; - void refresh_wids(void); - -public: - std::string wids[2]; - config::KeyBind* value; -}; - -class SettingValue_GamepadAxis final : public SettingValue { -public: - virtual ~SettingValue_GamepadAxis(void) = default; - virtual void layout(void) const override; - void refresh_wids(void); - -public: - std::string wids[2]; - std::string wid_checkbox; - config::GamepadAxis* value; -}; - -class SettingValue_GamepadButton final : public SettingValue { -public: - virtual ~SettingValue_GamepadButton(void) = default; - virtual void layout(void) const override; - void refresh_wids(void); - -public: - std::string wids[2]; - config::GamepadButton* value; -}; - -class SettingValue_Language final : public SettingValueWID { -public: - virtual ~SettingValue_Language(void) = default; - virtual void layout(void) const override; -}; - -static std::string str_checkbox_false; -static std::string str_checkbox_true; - -static std::string str_tab_general; -static std::string str_tab_input; -static std::string str_tab_video; -static std::string str_tab_sound; - -static std::string str_input_keyboard; -static std::string str_input_gamepad; -static std::string str_input_mouse; - -static std::string str_keyboard_movement; -static std::string str_keyboard_gameplay; -static std::string str_keyboard_misc; - -static std::string str_gamepad_movement; -static std::string str_gamepad_gameplay; -static std::string str_gamepad_misc; - -static std::string str_gamepad_axis_prefix; -static std::string str_gamepad_button_prefix; -static std::string str_gamepad_checkbox_tooltip; - -static std::string str_video_gui; - -static std::string str_sound_levels; - -static std::vector<SettingValue*> values_all; -static std::vector<SettingValue*> values[NUM_LOCATIONS]; - -void SettingValue::layout_tooltip(void) const -{ - if(has_tooltip) { - ImGui::SameLine(); - ImGui::TextDisabled("[?]"); - - if(ImGui::BeginItemTooltip()) { - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f); - ImGui::TextUnformatted(tooltip.c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - } -} - -void SettingValue::layout_label(void) const -{ - ImGui::SameLine(); - ImGui::TextUnformatted(title.c_str()); -} - -void SettingValue_CheckBox::refresh_wids(void) -{ - wids[0] = std::format("{}###{}", str_checkbox_false, static_cast<void*>(value)); - wids[1] = std::format("{}###{}", str_checkbox_true, static_cast<void*>(value)); -} - -void SettingValue_CheckBox::layout(void) const -{ - const auto& wid = value->get_value() ? wids[1] : wids[0]; - - if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) { - value->set_value(!value->get_value()); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_InputInt::layout(void) const -{ - auto current_value = value->get_value(); - - if(ImGui::InputInt(wid.c_str(), ¤t_value)) { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_InputFloat::layout(void) const -{ - auto current_value = value->get_value(); - - if(ImGui::InputFloat(wid.c_str(), ¤t_value, 0.0f, 0.0f, format.c_str())) { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_InputUnsigned::layout(void) const -{ - auto current_value = static_cast<std::uint32_t>(value->get_value()); - - if(ImGui::InputScalar(wid.c_str(), ImGuiDataType_U32, ¤t_value)) { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_InputString::layout(void) const -{ - ImGuiInputTextFlags flags; - std::string current_value(value->get_value()); - - if(allow_whitespace) { - flags = ImGuiInputTextFlags_AllowTabInput; - } - else { - flags = 0; - } - - if(ImGui::InputText(wid.c_str(), ¤t_value, flags)) { - value->set(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_SliderInt::layout(void) const -{ - auto current_value = value->get_value(); - - if(ImGui::SliderInt(wid.c_str(), ¤t_value, value->get_min_value(), value->get_max_value())) { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_SliderFloat::layout(void) const -{ - auto current_value = value->get_value(); - - if(ImGui::SliderFloat(wid.c_str(), ¤t_value, value->get_min_value(), value->get_max_value(), format.c_str())) { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_SliderUnsigned::layout(void) const -{ - auto current_value = static_cast<std::uint32_t>(value->get_value()); - auto min_value = static_cast<std::uint32_t>(value->get_min_value()); - auto max_value = static_cast<std::uint32_t>(value->get_max_value()); - - if(ImGui::SliderScalar(wid.c_str(), ImGuiDataType_U32, ¤t_value, &min_value, &max_value)) { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_StepperInt::layout(void) const -{ - auto current_value = value->get_value(); - auto min_value = value->get_min_value(); - auto max_value = value->get_max_value(); - - auto current_wid = current_value - min_value; - - if(ImGui::Button(wids[current_wid].c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) { - current_value += 1; - } - - if(current_value > max_value) { - value->set_value(min_value); - } - else { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_StepperInt::refresh_wids(void) -{ - for(std::size_t i = 0; i < wids.size(); ++i) { - auto key = std::format("settings.value.{}.{}", name, i); - wids[i] = std::format("{}###{}", gui::language::resolve(key.c_str()), static_cast<const void*>(value)); - } -} - -void SettingValue_StepperUnsigned::layout(void) const -{ - auto current_value = value->get_value(); - auto min_value = value->get_min_value(); - auto max_value = value->get_max_value(); - - auto current_wid = current_value - min_value; - - if(ImGui::Button(wids[current_wid].c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) { - current_value += 1U; - } - - if(current_value > max_value) { - value->set_value(min_value); - } - else { - value->set_value(current_value); - } - - layout_label(); - layout_tooltip(); -} - -void SettingValue_StepperUnsigned::refresh_wids(void) -{ - for(std::size_t i = 0; i < wids.size(); ++i) { - auto key = std::format("settings.value.{}.{}", name, i); - wids[i] = std::format("{}###{}", gui::language::resolve(key.c_str()), static_cast<const void*>(value)); - } -} - -void SettingValue_KeyBind::layout(void) const -{ - const auto is_active = ((globals::gui_keybind_ptr == value) && !globals::gui_gamepad_axis_ptr && !globals::gui_gamepad_button_ptr); - const auto& wid = is_active ? wids[0] : wids[1]; - - if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) { - auto& io = ImGui::GetIO(); - io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; - globals::gui_keybind_ptr = value; - } - - layout_label(); -} - -void SettingValue_KeyBind::refresh_wids(void) -{ - wids[0] = std::format("...###{}", static_cast<const void*>(value)); - wids[1] = std::format("{}###{}", value->get(), static_cast<const void*>(value)); -} - -void SettingValue_GamepadAxis::layout(void) const -{ - const auto is_active = ((globals::gui_gamepad_axis_ptr == value) && !globals::gui_keybind_ptr && !globals::gui_gamepad_button_ptr); - const auto& wid = is_active ? wids[0] : wids[1]; - auto is_inverted = value->is_inverted(); - - if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth() - ImGui::GetFrameHeight() - ImGui::GetStyle().ItemSpacing.x, 0.0f))) { - auto& io = ImGui::GetIO(); - io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; - globals::gui_gamepad_axis_ptr = value; - } - - ImGui::SameLine(); - - if(ImGui::Checkbox(wid_checkbox.c_str(), &is_inverted)) { - value->set_inverted(is_inverted); - } - - if(ImGui::BeginItemTooltip()) { - ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f); - ImGui::TextUnformatted(str_gamepad_checkbox_tooltip.c_str()); - ImGui::PopTextWrapPos(); - ImGui::EndTooltip(); - } - - layout_label(); -} - -void SettingValue_GamepadAxis::refresh_wids(void) -{ - wids[0] = std::format("...###{}", static_cast<const void*>(value)); - wids[1] = std::format("{}###{}", value->get_name(), static_cast<const void*>(value)); - wid_checkbox = std::format("###CHECKBOX_{}", static_cast<const void*>(value)); -} - -void SettingValue_GamepadButton::layout(void) const -{ - const auto is_active = ((globals::gui_gamepad_button_ptr == value) && !globals::gui_keybind_ptr && !globals::gui_gamepad_axis_ptr); - const auto& wid = is_active ? wids[0] : wids[1]; - - if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) { - auto& io = ImGui::GetIO(); - io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard; - globals::gui_gamepad_button_ptr = value; - } - - layout_label(); -} - -void SettingValue_GamepadButton::refresh_wids(void) -{ - wids[0] = std::format("...###{}", static_cast<const void*>(value)); - wids[1] = std::format("{}###{}", value->get(), static_cast<const void*>(value)); -} - -void SettingValue_Language::layout(void) const -{ - auto current_language = gui::language::get_current(); - - if(ImGui::BeginCombo(wid.c_str(), current_language->endonym.c_str())) { - for(auto it = gui::language::cbegin(); it != gui::language::cend(); ++it) { - if(ImGui::Selectable(it->display.c_str(), it == current_language)) { - gui::language::set(it); - continue; - } - } - - ImGui::EndCombo(); - } - - layout_label(); - layout_tooltip(); -} - -static void refresh_input_wids(void) -{ - for(SettingValue* value : values_all) { - if(value->type == setting_type::KEYBIND) { - auto keybind = static_cast<SettingValue_KeyBind*>(value); - keybind->refresh_wids(); - continue; - } - - if(value->type == setting_type::GAMEPAD_AXIS) { - auto gamepad_axis = static_cast<SettingValue_GamepadAxis*>(value); - gamepad_axis->refresh_wids(); - continue; - } - - if(value->type == setting_type::GAMEPAD_BUTTON) { - auto gamepad_button = static_cast<SettingValue_GamepadButton*>(value); - gamepad_button->refresh_wids(); - } - } -} - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - if((event.action == GLFW_PRESS) && (event.key != DEBUG_KEY)) { - if(globals::gui_keybind_ptr || globals::gui_gamepad_axis_ptr || globals::gui_gamepad_button_ptr) { - if(event.key == GLFW_KEY_ESCAPE) { - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - - globals::gui_keybind_ptr = nullptr; - globals::gui_gamepad_axis_ptr = nullptr; - globals::gui_gamepad_button_ptr = nullptr; - - return; - } - - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - - globals::gui_keybind_ptr->set_key(event.key); - globals::gui_keybind_ptr = nullptr; - - refresh_input_wids(); - - return; - } - - if((event.key == GLFW_KEY_ESCAPE) && (globals::gui_screen == GUI_SETTINGS)) { - globals::gui_screen = GUI_MAIN_MENU; - return; - } - } -} - -static void on_gamepad_axis(const io::GamepadAxisEvent& event) -{ - if(globals::gui_gamepad_axis_ptr) { - auto& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - - globals::gui_gamepad_axis_ptr->set_axis(event.axis); - globals::gui_gamepad_axis_ptr = nullptr; - - refresh_input_wids(); - - return; - } -} - -static void on_gamepad_button(const io::GamepadButtonEvent& event) -{ - if(globals::gui_gamepad_button_ptr) { - auto& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - - globals::gui_gamepad_button_ptr->set_button(event.button); - globals::gui_gamepad_button_ptr = nullptr; - - refresh_input_wids(); - - return; - } -} - -static void on_language_set(const gui::LanguageSetEvent& event) -{ - str_checkbox_false = gui::language::resolve("settings.checkbox.false"); - str_checkbox_true = gui::language::resolve("settings.checkbox.true"); - - str_tab_general = gui::language::resolve("settings.tab.general"); - str_tab_input = gui::language::resolve("settings.tab.input"); - str_tab_video = gui::language::resolve("settings.tab.video"); - str_tab_sound = gui::language::resolve("settings.tab.sound"); - - str_input_keyboard = gui::language::resolve("settings.input.keyboard"); - str_input_gamepad = gui::language::resolve("settings.input.gamepad"); - str_input_mouse = gui::language::resolve("settings.input.mouse"); - - str_keyboard_movement = gui::language::resolve("settings.keyboard.movement"); - str_keyboard_gameplay = gui::language::resolve("settings.keyboard.gameplay"); - str_keyboard_misc = gui::language::resolve("settings.keyboard.misc"); - - str_gamepad_movement = gui::language::resolve("settings.gamepad.movement"); - str_gamepad_gameplay = gui::language::resolve("settings.gamepad.gameplay"); - str_gamepad_misc = gui::language::resolve("settings.gamepad.misc"); - - str_gamepad_axis_prefix = gui::language::resolve("settings.gamepad.axis"); - str_gamepad_button_prefix = gui::language::resolve("settings.gamepad.button"); - str_gamepad_checkbox_tooltip = gui::language::resolve("settings.gamepad.checkbox_tooltip"); - - str_video_gui = gui::language::resolve("settings.video.gui"); - - str_sound_levels = gui::language::resolve("settings.sound.levels"); - - for(SettingValue* value : values_all) { - if(value->type == setting_type::CHECKBOX) { - auto checkbox = static_cast<SettingValue_CheckBox*>(value); - checkbox->refresh_wids(); - } - - if(value->type == setting_type::STEPPER_INT) { - auto stepper = static_cast<SettingValue_StepperInt*>(value); - stepper->refresh_wids(); - } - - if(value->type == setting_type::STEPPER_UINT) { - auto stepper = static_cast<SettingValue_StepperUnsigned*>(value); - stepper->refresh_wids(); - } - - value->title = gui::language::resolve(std::format("settings.value.{}", value->name).c_str()); - - if(value->has_tooltip) { - value->tooltip = gui::language::resolve(std::format("settings.tooltip.{}", value->name).c_str()); - } - } -} - -static void layout_values(settings_location location) -{ - ImGui::PushItemWidth(ImGui::CalcItemWidth() * 0.70f); - - for(const SettingValue* value : values[static_cast<unsigned int>(location)]) { - value->layout(); - } - - ImGui::PopItemWidth(); -} - -static void layout_general(void) -{ - if(ImGui::BeginChild("###settings.general.child")) { - layout_values(settings_location::GENERAL); - } - - ImGui::EndChild(); -} - -static void layout_input_keyboard(void) -{ - if(ImGui::BeginChild("###settings.input.keyboard.child")) { - ImGui::SeparatorText(str_keyboard_movement.c_str()); - layout_values(settings_location::KEYBOARD_MOVEMENT); - ImGui::SeparatorText(str_keyboard_gameplay.c_str()); - layout_values(settings_location::KEYBOARD_GAMEPLAY); - ImGui::SeparatorText(str_keyboard_misc.c_str()); - layout_values(settings_location::KEYBOARD_MISC); - } - - ImGui::EndChild(); -} - -static void layout_input_gamepad(void) -{ - if(ImGui::BeginChild("###settings.input.gamepad.child")) { - layout_values(settings_location::GAMEPAD); - ImGui::SeparatorText(str_gamepad_movement.c_str()); - layout_values(settings_location::GAMEPAD_MOVEMENT); - ImGui::SeparatorText(str_gamepad_gameplay.c_str()); - layout_values(settings_location::GAMEPAD_GAMEPLAY); - ImGui::SeparatorText(str_gamepad_misc.c_str()); - layout_values(settings_location::GAMEPAD_MISC); - } - - ImGui::EndChild(); -} - -static void layout_input_mouse(void) -{ - if(ImGui::BeginChild("###settings.input.mouse.child")) { - layout_values(settings_location::MOUSE); - } - - ImGui::EndChild(); -} - -static void layout_input(void) -{ - if(ImGui::BeginTabBar("###settings.input.tabs", ImGuiTabBarFlags_FittingPolicyResizeDown)) { - if(ImGui::BeginTabItem(str_input_keyboard.c_str())) { - layout_input_keyboard(); - ImGui::EndTabItem(); - } - - if(io::gamepad::available) { - if(ImGui::BeginTabItem(str_input_gamepad.c_str())) { - globals::gui_keybind_ptr = nullptr; - layout_input_gamepad(); - ImGui::EndTabItem(); - } - } - - if(ImGui::BeginTabItem(str_input_mouse.c_str())) { - globals::gui_keybind_ptr = nullptr; - layout_input_mouse(); - ImGui::EndTabItem(); - } - - ImGui::EndTabBar(); - } -} - -static void layout_video(void) -{ - if(ImGui::BeginChild("###settings.video.child")) { - layout_values(settings_location::VIDEO); - ImGui::SeparatorText(str_video_gui.c_str()); - layout_values(settings_location::VIDEO_GUI); - } - - ImGui::EndChild(); -} - -static void layout_sound(void) -{ - if(ImGui::BeginChild("###settings.sound.child")) { - layout_values(settings_location::SOUND); - ImGui::SeparatorText(str_sound_levels.c_str()); - layout_values(settings_location::SOUND_LEVELS); - } - - ImGui::EndChild(); -} - -void settings::init(void) -{ - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - globals::dispatcher.sink<io::GamepadAxisEvent>().connect<&on_gamepad_axis>(); - globals::dispatcher.sink<io::GamepadButtonEvent>().connect<&on_gamepad_button>(); - globals::dispatcher.sink<gui::LanguageSetEvent>().connect<&on_language_set>(); -} - -void settings::init_late(void) -{ - for(std::size_t i = 0; i < NUM_LOCATIONS; ++i) { - std::sort(values[i].begin(), values[i].end(), [](const SettingValue* a, const SettingValue* b) { - return a->priority < b->priority; - }); - } - - refresh_input_wids(); -} - -void settings::shutdown(void) -{ - for(const SettingValue* value : values_all) - delete value; - for(std::size_t i = 0; i < NUM_LOCATIONS; values[i++].clear()) - ; - values_all.clear(); -} - -void settings::layout(void) -{ - const ImGuiViewport* viewport = ImGui::GetMainViewport(); - const ImVec2 window_start = ImVec2(viewport->Size.x * 0.05f, viewport->Size.y * 0.05f); - const ImVec2 window_size = ImVec2(viewport->Size.x * 0.90f, viewport->Size.y * 0.90f); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###settings", nullptr, WINDOW_FLAGS)) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.0f * globals::gui_scale, 3.0f * globals::gui_scale)); - - if(ImGui::BeginTabBar("###settings.tabs", ImGuiTabBarFlags_FittingPolicyResizeDown)) { - if(ImGui::TabItemButton("<<")) { - globals::gui_screen = GUI_MAIN_MENU; - globals::gui_keybind_ptr = nullptr; - } - - if(ImGui::BeginTabItem(str_tab_general.c_str())) { - globals::gui_keybind_ptr = nullptr; - layout_general(); - ImGui::EndTabItem(); - } - - if(ImGui::BeginTabItem(str_tab_input.c_str())) { - layout_input(); - ImGui::EndTabItem(); - } - - if(ImGui::BeginTabItem(str_tab_video.c_str())) { - globals::gui_keybind_ptr = nullptr; - layout_video(); - ImGui::EndTabItem(); - } - - if(globals::sound_ctx && globals::sound_dev) { - if(ImGui::BeginTabItem(str_tab_sound.c_str())) { - globals::gui_keybind_ptr = nullptr; - layout_sound(); - ImGui::EndTabItem(); - } - } - - ImGui::EndTabBar(); - } - - ImGui::PopStyleVar(); - } - - ImGui::End(); -} - -void settings::add_checkbox(int priority, config::Boolean& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_CheckBox; - setting_value->type = setting_type::CHECKBOX; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->refresh_wids(); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_input(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_InputInt; - setting_value->type = setting_type::INPUT_INT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_input( - int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip, std::string_view fmt) -{ - auto setting_value = new SettingValue_InputFloat; - setting_value->type = setting_type::INPUT_FLOAT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->format = fmt; - setting_value->name = name; - - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_input(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_InputUnsigned; - setting_value->type = setting_type::INPUT_UINT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_input( - int priority, config::String& value, settings_location location, std::string_view name, bool tooltip, bool allow_whitespace) -{ - auto setting_value = new SettingValue_InputString; - setting_value->type = setting_type::INPUT_STRING; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->allow_whitespace = allow_whitespace; - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_slider(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_SliderInt; - setting_value->type = setting_type::SLIDER_INT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_slider( - int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip, std::string_view fmt) -{ - auto setting_value = new SettingValue_SliderFloat; - setting_value->type = setting_type::SLIDER_FLOAT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->format = fmt; - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_slider(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_SliderUnsigned; - setting_value->type = setting_type::SLIDER_UINT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_stepper(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_StepperInt; - setting_value->type = setting_type::STEPPER_INT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->wids.resize(value.get_max_value() - value.get_min_value() + 1); - setting_value->refresh_wids(); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_stepper(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip) -{ - auto setting_value = new SettingValue_StepperUnsigned; - setting_value->type = setting_type::STEPPER_UINT; - setting_value->priority = priority; - setting_value->has_tooltip = tooltip; - setting_value->value = &value; - setting_value->name = name; - - setting_value->wids.resize(value.get_max_value() - value.get_min_value() + 1); - setting_value->refresh_wids(); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_keybind(int priority, config::KeyBind& value, settings_location location, std::string_view name) -{ - auto setting_value = new SettingValue_KeyBind; - setting_value->type = setting_type::KEYBIND; - setting_value->priority = priority; - setting_value->has_tooltip = false; - setting_value->value = &value; - setting_value->name = name; - - setting_value->refresh_wids(); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_gamepad_axis(int priority, config::GamepadAxis& value, settings_location location, std::string_view name) -{ - auto setting_value = new SettingValue_GamepadAxis; - setting_value->type = setting_type::GAMEPAD_AXIS; - setting_value->priority = priority; - setting_value->has_tooltip = false; - setting_value->value = &value; - setting_value->name = name; - - setting_value->refresh_wids(); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_gamepad_button(int priority, config::GamepadButton& value, settings_location location, std::string_view name) -{ - auto setting_value = new SettingValue_GamepadButton; - setting_value->type = setting_type::GAMEPAD_BUTTON; - setting_value->priority = priority; - setting_value->has_tooltip = false; - setting_value->value = &value; - setting_value->name = name; - - setting_value->refresh_wids(); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} - -void settings::add_language_select(int priority, settings_location location, std::string_view name) -{ - auto setting_value = new SettingValue_Language; - setting_value->type = setting_type::LANGUAGE_SELECT; - setting_value->priority = priority; - setting_value->has_tooltip = false; - setting_value->name = name; - - setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value)); - - values[static_cast<unsigned int>(location)].push_back(setting_value); - values_all.push_back(setting_value); -} +#include "client/pch.hh"
+
+#include "client/gui/settings.hh"
+
+#include "core/config/boolean.hh"
+#include "core/config/number.hh"
+#include "core/config/string.hh"
+
+#include "core/io/config_map.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "client/config/gamepad_axis.hh"
+#include "client/config/gamepad_button.hh"
+#include "client/config/keybind.hh"
+
+#include "client/gui/gui_screen.hh"
+#include "client/gui/language.hh"
+
+#include "client/io/gamepad.hh"
+#include "client/io/glfw.hh"
+
+#include "client/const.hh"
+#include "client/globals.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+constexpr static unsigned int NUM_LOCATIONS = static_cast<unsigned int>(settings_location::COUNT);
+
+enum class setting_type : unsigned int {
+ CHECKBOX = 0x0000U, ///< config::Boolean
+ INPUT_INT = 0x0001U, ///< config::Number<int>
+ INPUT_FLOAT = 0x0002U, ///< config::Number<float>
+ INPUT_UINT = 0x0003U, ///< config::Number<unsigned int>
+ INPUT_STRING = 0x0004U, ///< config::String
+ SLIDER_INT = 0x0005U, ///< config::Number<int>
+ SLIDER_FLOAT = 0x0006U, ///< config::Number<float>
+ SLIDER_UINT = 0x0007U, ///< config::Number<unsigned int>
+ STEPPER_INT = 0x0008U, ///< config::Number<int>
+ STEPPER_UINT = 0x0009U, ///< config::Number<unsigned int>
+ KEYBIND = 0x000AU, ///< config::KeyBind
+ GAMEPAD_AXIS = 0x000BU, ///< config::GamepadAxis
+ GAMEPAD_BUTTON = 0x000CU, ///< config::GamepadButton
+ LANGUAGE_SELECT = 0x000DU, ///< config::String internally
+};
+
+class SettingValue {
+public:
+ virtual ~SettingValue(void) = default;
+ virtual void layout(void) const = 0;
+ void layout_tooltip(void) const;
+ void layout_label(void) const;
+
+public:
+ setting_type type;
+ std::string tooltip;
+ std::string title;
+ std::string name;
+ bool has_tooltip;
+ int priority;
+};
+
+class SettingValueWID : public SettingValue {
+public:
+ virtual ~SettingValueWID(void) = default;
+
+public:
+ std::string wid;
+};
+
+class SettingValue_CheckBox final : public SettingValue {
+public:
+ virtual ~SettingValue_CheckBox(void) = default;
+ virtual void layout(void) const override;
+ void refresh_wids(void);
+
+public:
+ config::Boolean* value;
+ std::string wids[2];
+};
+
+class SettingValue_InputInt final : public SettingValueWID {
+public:
+ virtual ~SettingValue_InputInt(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ config::Int* value;
+};
+
+class SettingValue_InputFloat final : public SettingValueWID {
+public:
+ virtual ~SettingValue_InputFloat(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ std::string format;
+ config::Float* value;
+};
+
+class SettingValue_InputUnsigned final : public SettingValueWID {
+public:
+ virtual ~SettingValue_InputUnsigned(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ config::Unsigned* value;
+};
+
+class SettingValue_InputString final : public SettingValueWID {
+public:
+ virtual ~SettingValue_InputString(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ config::String* value;
+ bool allow_whitespace;
+};
+
+class SettingValue_SliderInt final : public SettingValueWID {
+public:
+ virtual ~SettingValue_SliderInt(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ config::Int* value;
+};
+
+class SettingValue_SliderFloat final : public SettingValueWID {
+public:
+ virtual ~SettingValue_SliderFloat(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ std::string format;
+ config::Float* value;
+};
+
+class SettingValue_SliderUnsigned final : public SettingValueWID {
+public:
+ virtual ~SettingValue_SliderUnsigned(void) = default;
+ virtual void layout(void) const override;
+
+public:
+ config::Unsigned* value;
+};
+
+class SettingValue_StepperInt final : public SettingValue {
+public:
+ virtual ~SettingValue_StepperInt(void) = default;
+ virtual void layout(void) const override;
+ void refresh_wids(void);
+
+public:
+ std::vector<std::string> wids;
+ config::Int* value;
+};
+
+class SettingValue_StepperUnsigned final : public SettingValue {
+public:
+ virtual ~SettingValue_StepperUnsigned(void) = default;
+ virtual void layout(void) const override;
+ void refresh_wids(void);
+
+public:
+ std::vector<std::string> wids;
+ config::Unsigned* value;
+};
+
+class SettingValue_KeyBind final : public SettingValue {
+public:
+ virtual ~SettingValue_KeyBind(void) = default;
+ virtual void layout(void) const override;
+ void refresh_wids(void);
+
+public:
+ std::string wids[2];
+ config::KeyBind* value;
+};
+
+class SettingValue_GamepadAxis final : public SettingValue {
+public:
+ virtual ~SettingValue_GamepadAxis(void) = default;
+ virtual void layout(void) const override;
+ void refresh_wids(void);
+
+public:
+ std::string wids[2];
+ std::string wid_checkbox;
+ config::GamepadAxis* value;
+};
+
+class SettingValue_GamepadButton final : public SettingValue {
+public:
+ virtual ~SettingValue_GamepadButton(void) = default;
+ virtual void layout(void) const override;
+ void refresh_wids(void);
+
+public:
+ std::string wids[2];
+ config::GamepadButton* value;
+};
+
+class SettingValue_Language final : public SettingValueWID {
+public:
+ virtual ~SettingValue_Language(void) = default;
+ virtual void layout(void) const override;
+};
+
+static std::string str_checkbox_false;
+static std::string str_checkbox_true;
+
+static std::string str_tab_general;
+static std::string str_tab_input;
+static std::string str_tab_video;
+static std::string str_tab_sound;
+
+static std::string str_input_keyboard;
+static std::string str_input_gamepad;
+static std::string str_input_mouse;
+
+static std::string str_keyboard_movement;
+static std::string str_keyboard_gameplay;
+static std::string str_keyboard_misc;
+
+static std::string str_gamepad_movement;
+static std::string str_gamepad_gameplay;
+static std::string str_gamepad_misc;
+
+static std::string str_gamepad_axis_prefix;
+static std::string str_gamepad_button_prefix;
+static std::string str_gamepad_checkbox_tooltip;
+
+static std::string str_video_gui;
+
+static std::string str_sound_levels;
+
+static std::vector<SettingValue*> values_all;
+static std::vector<SettingValue*> values[NUM_LOCATIONS];
+
+void SettingValue::layout_tooltip(void) const
+{
+ if(has_tooltip) {
+ ImGui::SameLine();
+ ImGui::TextDisabled("[?]");
+
+ if(ImGui::BeginItemTooltip()) {
+ ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f);
+ ImGui::TextUnformatted(tooltip.c_str());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+ }
+}
+
+void SettingValue::layout_label(void) const
+{
+ ImGui::SameLine();
+ ImGui::TextUnformatted(title.c_str());
+}
+
+void SettingValue_CheckBox::refresh_wids(void)
+{
+ wids[0] = std::format("{}###{}", str_checkbox_false, static_cast<void*>(value));
+ wids[1] = std::format("{}###{}", str_checkbox_true, static_cast<void*>(value));
+}
+
+void SettingValue_CheckBox::layout(void) const
+{
+ const auto& wid = value->get_value() ? wids[1] : wids[0];
+
+ if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) {
+ value->set_value(!value->get_value());
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_InputInt::layout(void) const
+{
+ auto current_value = value->get_value();
+
+ if(ImGui::InputInt(wid.c_str(), ¤t_value)) {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_InputFloat::layout(void) const
+{
+ auto current_value = value->get_value();
+
+ if(ImGui::InputFloat(wid.c_str(), ¤t_value, 0.0f, 0.0f, format.c_str())) {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_InputUnsigned::layout(void) const
+{
+ auto current_value = static_cast<std::uint32_t>(value->get_value());
+
+ if(ImGui::InputScalar(wid.c_str(), ImGuiDataType_U32, ¤t_value)) {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_InputString::layout(void) const
+{
+ ImGuiInputTextFlags flags;
+ std::string current_value(value->get_value());
+
+ if(allow_whitespace) {
+ flags = ImGuiInputTextFlags_AllowTabInput;
+ }
+ else {
+ flags = 0;
+ }
+
+ if(ImGui::InputText(wid.c_str(), ¤t_value, flags)) {
+ value->set(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_SliderInt::layout(void) const
+{
+ auto current_value = value->get_value();
+
+ if(ImGui::SliderInt(wid.c_str(), ¤t_value, value->get_min_value(), value->get_max_value())) {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_SliderFloat::layout(void) const
+{
+ auto current_value = value->get_value();
+
+ if(ImGui::SliderFloat(wid.c_str(), ¤t_value, value->get_min_value(), value->get_max_value(), format.c_str())) {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_SliderUnsigned::layout(void) const
+{
+ auto current_value = static_cast<std::uint32_t>(value->get_value());
+ auto min_value = static_cast<std::uint32_t>(value->get_min_value());
+ auto max_value = static_cast<std::uint32_t>(value->get_max_value());
+
+ if(ImGui::SliderScalar(wid.c_str(), ImGuiDataType_U32, ¤t_value, &min_value, &max_value)) {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_StepperInt::layout(void) const
+{
+ auto current_value = value->get_value();
+ auto min_value = value->get_min_value();
+ auto max_value = value->get_max_value();
+
+ auto current_wid = current_value - min_value;
+
+ if(ImGui::Button(wids[current_wid].c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) {
+ current_value += 1;
+ }
+
+ if(current_value > max_value) {
+ value->set_value(min_value);
+ }
+ else {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_StepperInt::refresh_wids(void)
+{
+ for(std::size_t i = 0; i < wids.size(); ++i) {
+ auto key = std::format("settings.value.{}.{}", name, i);
+ wids[i] = std::format("{}###{}", gui::language::resolve(key.c_str()), static_cast<const void*>(value));
+ }
+}
+
+void SettingValue_StepperUnsigned::layout(void) const
+{
+ auto current_value = value->get_value();
+ auto min_value = value->get_min_value();
+ auto max_value = value->get_max_value();
+
+ auto current_wid = current_value - min_value;
+
+ if(ImGui::Button(wids[current_wid].c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) {
+ current_value += 1U;
+ }
+
+ if(current_value > max_value) {
+ value->set_value(min_value);
+ }
+ else {
+ value->set_value(current_value);
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+void SettingValue_StepperUnsigned::refresh_wids(void)
+{
+ for(std::size_t i = 0; i < wids.size(); ++i) {
+ auto key = std::format("settings.value.{}.{}", name, i);
+ wids[i] = std::format("{}###{}", gui::language::resolve(key.c_str()), static_cast<const void*>(value));
+ }
+}
+
+void SettingValue_KeyBind::layout(void) const
+{
+ const auto is_active = ((globals::gui_keybind_ptr == value) && !globals::gui_gamepad_axis_ptr && !globals::gui_gamepad_button_ptr);
+ const auto& wid = is_active ? wids[0] : wids[1];
+
+ if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) {
+ auto& io = ImGui::GetIO();
+ io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard;
+ globals::gui_keybind_ptr = value;
+ }
+
+ layout_label();
+}
+
+void SettingValue_KeyBind::refresh_wids(void)
+{
+ wids[0] = std::format("...###{}", static_cast<const void*>(value));
+ wids[1] = std::format("{}###{}", value->get(), static_cast<const void*>(value));
+}
+
+void SettingValue_GamepadAxis::layout(void) const
+{
+ const auto is_active = ((globals::gui_gamepad_axis_ptr == value) && !globals::gui_keybind_ptr && !globals::gui_gamepad_button_ptr);
+ const auto& wid = is_active ? wids[0] : wids[1];
+ auto is_inverted = value->is_inverted();
+
+ if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth() - ImGui::GetFrameHeight() - ImGui::GetStyle().ItemSpacing.x, 0.0f))) {
+ auto& io = ImGui::GetIO();
+ io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard;
+ globals::gui_gamepad_axis_ptr = value;
+ }
+
+ ImGui::SameLine();
+
+ if(ImGui::Checkbox(wid_checkbox.c_str(), &is_inverted)) {
+ value->set_inverted(is_inverted);
+ }
+
+ if(ImGui::BeginItemTooltip()) {
+ ImGui::PushTextWrapPos(ImGui::GetFontSize() * 16.0f);
+ ImGui::TextUnformatted(str_gamepad_checkbox_tooltip.c_str());
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+
+ layout_label();
+}
+
+void SettingValue_GamepadAxis::refresh_wids(void)
+{
+ wids[0] = std::format("...###{}", static_cast<const void*>(value));
+ wids[1] = std::format("{}###{}", value->get_name(), static_cast<const void*>(value));
+ wid_checkbox = std::format("###CHECKBOX_{}", static_cast<const void*>(value));
+}
+
+void SettingValue_GamepadButton::layout(void) const
+{
+ const auto is_active = ((globals::gui_gamepad_button_ptr == value) && !globals::gui_keybind_ptr && !globals::gui_gamepad_axis_ptr);
+ const auto& wid = is_active ? wids[0] : wids[1];
+
+ if(ImGui::Button(wid.c_str(), ImVec2(ImGui::CalcItemWidth(), 0.0f))) {
+ auto& io = ImGui::GetIO();
+ io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableKeyboard;
+ globals::gui_gamepad_button_ptr = value;
+ }
+
+ layout_label();
+}
+
+void SettingValue_GamepadButton::refresh_wids(void)
+{
+ wids[0] = std::format("...###{}", static_cast<const void*>(value));
+ wids[1] = std::format("{}###{}", value->get(), static_cast<const void*>(value));
+}
+
+void SettingValue_Language::layout(void) const
+{
+ auto current_language = gui::language::get_current();
+
+ if(ImGui::BeginCombo(wid.c_str(), current_language->endonym.c_str())) {
+ for(auto it = gui::language::cbegin(); it != gui::language::cend(); ++it) {
+ if(ImGui::Selectable(it->display.c_str(), it == current_language)) {
+ gui::language::set(it);
+ continue;
+ }
+ }
+
+ ImGui::EndCombo();
+ }
+
+ layout_label();
+ layout_tooltip();
+}
+
+static void refresh_input_wids(void)
+{
+ for(SettingValue* value : values_all) {
+ if(value->type == setting_type::KEYBIND) {
+ auto keybind = static_cast<SettingValue_KeyBind*>(value);
+ keybind->refresh_wids();
+ continue;
+ }
+
+ if(value->type == setting_type::GAMEPAD_AXIS) {
+ auto gamepad_axis = static_cast<SettingValue_GamepadAxis*>(value);
+ gamepad_axis->refresh_wids();
+ continue;
+ }
+
+ if(value->type == setting_type::GAMEPAD_BUTTON) {
+ auto gamepad_button = static_cast<SettingValue_GamepadButton*>(value);
+ gamepad_button->refresh_wids();
+ }
+ }
+}
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ if((event.action == GLFW_PRESS) && (event.key != DEBUG_KEY)) {
+ if(globals::gui_keybind_ptr || globals::gui_gamepad_axis_ptr || globals::gui_gamepad_button_ptr) {
+ if(event.key == GLFW_KEY_ESCAPE) {
+ ImGuiIO& io = ImGui::GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+
+ globals::gui_keybind_ptr = nullptr;
+ globals::gui_gamepad_axis_ptr = nullptr;
+ globals::gui_gamepad_button_ptr = nullptr;
+
+ return;
+ }
+
+ ImGuiIO& io = ImGui::GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+
+ globals::gui_keybind_ptr->set_key(event.key);
+ globals::gui_keybind_ptr = nullptr;
+
+ refresh_input_wids();
+
+ return;
+ }
+
+ if((event.key == GLFW_KEY_ESCAPE) && (globals::gui_screen == GUI_SETTINGS)) {
+ globals::gui_screen = GUI_MAIN_MENU;
+ return;
+ }
+ }
+}
+
+static void on_gamepad_axis(const io::GamepadAxisEvent& event)
+{
+ if(globals::gui_gamepad_axis_ptr) {
+ auto& io = ImGui::GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+
+ globals::gui_gamepad_axis_ptr->set_axis(event.axis);
+ globals::gui_gamepad_axis_ptr = nullptr;
+
+ refresh_input_wids();
+
+ return;
+ }
+}
+
+static void on_gamepad_button(const io::GamepadButtonEvent& event)
+{
+ if(globals::gui_gamepad_button_ptr) {
+ auto& io = ImGui::GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+
+ globals::gui_gamepad_button_ptr->set_button(event.button);
+ globals::gui_gamepad_button_ptr = nullptr;
+
+ refresh_input_wids();
+
+ return;
+ }
+}
+
+static void on_language_set(const gui::LanguageSetEvent& event)
+{
+ str_checkbox_false = gui::language::resolve("settings.checkbox.false");
+ str_checkbox_true = gui::language::resolve("settings.checkbox.true");
+
+ str_tab_general = gui::language::resolve("settings.tab.general");
+ str_tab_input = gui::language::resolve("settings.tab.input");
+ str_tab_video = gui::language::resolve("settings.tab.video");
+ str_tab_sound = gui::language::resolve("settings.tab.sound");
+
+ str_input_keyboard = gui::language::resolve("settings.input.keyboard");
+ str_input_gamepad = gui::language::resolve("settings.input.gamepad");
+ str_input_mouse = gui::language::resolve("settings.input.mouse");
+
+ str_keyboard_movement = gui::language::resolve("settings.keyboard.movement");
+ str_keyboard_gameplay = gui::language::resolve("settings.keyboard.gameplay");
+ str_keyboard_misc = gui::language::resolve("settings.keyboard.misc");
+
+ str_gamepad_movement = gui::language::resolve("settings.gamepad.movement");
+ str_gamepad_gameplay = gui::language::resolve("settings.gamepad.gameplay");
+ str_gamepad_misc = gui::language::resolve("settings.gamepad.misc");
+
+ str_gamepad_axis_prefix = gui::language::resolve("settings.gamepad.axis");
+ str_gamepad_button_prefix = gui::language::resolve("settings.gamepad.button");
+ str_gamepad_checkbox_tooltip = gui::language::resolve("settings.gamepad.checkbox_tooltip");
+
+ str_video_gui = gui::language::resolve("settings.video.gui");
+
+ str_sound_levels = gui::language::resolve("settings.sound.levels");
+
+ for(SettingValue* value : values_all) {
+ if(value->type == setting_type::CHECKBOX) {
+ auto checkbox = static_cast<SettingValue_CheckBox*>(value);
+ checkbox->refresh_wids();
+ }
+
+ if(value->type == setting_type::STEPPER_INT) {
+ auto stepper = static_cast<SettingValue_StepperInt*>(value);
+ stepper->refresh_wids();
+ }
+
+ if(value->type == setting_type::STEPPER_UINT) {
+ auto stepper = static_cast<SettingValue_StepperUnsigned*>(value);
+ stepper->refresh_wids();
+ }
+
+ value->title = gui::language::resolve(std::format("settings.value.{}", value->name).c_str());
+
+ if(value->has_tooltip) {
+ value->tooltip = gui::language::resolve(std::format("settings.tooltip.{}", value->name).c_str());
+ }
+ }
+}
+
+static void layout_values(settings_location location)
+{
+ ImGui::PushItemWidth(ImGui::CalcItemWidth() * 0.70f);
+
+ for(const SettingValue* value : values[static_cast<unsigned int>(location)]) {
+ value->layout();
+ }
+
+ ImGui::PopItemWidth();
+}
+
+static void layout_general(void)
+{
+ if(ImGui::BeginChild("###settings.general.child")) {
+ layout_values(settings_location::GENERAL);
+ }
+
+ ImGui::EndChild();
+}
+
+static void layout_input_keyboard(void)
+{
+ if(ImGui::BeginChild("###settings.input.keyboard.child")) {
+ ImGui::SeparatorText(str_keyboard_movement.c_str());
+ layout_values(settings_location::KEYBOARD_MOVEMENT);
+ ImGui::SeparatorText(str_keyboard_gameplay.c_str());
+ layout_values(settings_location::KEYBOARD_GAMEPLAY);
+ ImGui::SeparatorText(str_keyboard_misc.c_str());
+ layout_values(settings_location::KEYBOARD_MISC);
+ }
+
+ ImGui::EndChild();
+}
+
+static void layout_input_gamepad(void)
+{
+ if(ImGui::BeginChild("###settings.input.gamepad.child")) {
+ layout_values(settings_location::GAMEPAD);
+ ImGui::SeparatorText(str_gamepad_movement.c_str());
+ layout_values(settings_location::GAMEPAD_MOVEMENT);
+ ImGui::SeparatorText(str_gamepad_gameplay.c_str());
+ layout_values(settings_location::GAMEPAD_GAMEPLAY);
+ ImGui::SeparatorText(str_gamepad_misc.c_str());
+ layout_values(settings_location::GAMEPAD_MISC);
+ }
+
+ ImGui::EndChild();
+}
+
+static void layout_input_mouse(void)
+{
+ if(ImGui::BeginChild("###settings.input.mouse.child")) {
+ layout_values(settings_location::MOUSE);
+ }
+
+ ImGui::EndChild();
+}
+
+static void layout_input(void)
+{
+ if(ImGui::BeginTabBar("###settings.input.tabs", ImGuiTabBarFlags_FittingPolicyResizeDown)) {
+ if(ImGui::BeginTabItem(str_input_keyboard.c_str())) {
+ layout_input_keyboard();
+ ImGui::EndTabItem();
+ }
+
+ if(io::gamepad::available) {
+ if(ImGui::BeginTabItem(str_input_gamepad.c_str())) {
+ globals::gui_keybind_ptr = nullptr;
+ layout_input_gamepad();
+ ImGui::EndTabItem();
+ }
+ }
+
+ if(ImGui::BeginTabItem(str_input_mouse.c_str())) {
+ globals::gui_keybind_ptr = nullptr;
+ layout_input_mouse();
+ ImGui::EndTabItem();
+ }
+
+ ImGui::EndTabBar();
+ }
+}
+
+static void layout_video(void)
+{
+ if(ImGui::BeginChild("###settings.video.child")) {
+ layout_values(settings_location::VIDEO);
+ ImGui::SeparatorText(str_video_gui.c_str());
+ layout_values(settings_location::VIDEO_GUI);
+ }
+
+ ImGui::EndChild();
+}
+
+static void layout_sound(void)
+{
+ if(ImGui::BeginChild("###settings.sound.child")) {
+ layout_values(settings_location::SOUND);
+ ImGui::SeparatorText(str_sound_levels.c_str());
+ layout_values(settings_location::SOUND_LEVELS);
+ }
+
+ ImGui::EndChild();
+}
+
+void settings::init(void)
+{
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+ globals::dispatcher.sink<io::GamepadAxisEvent>().connect<&on_gamepad_axis>();
+ globals::dispatcher.sink<io::GamepadButtonEvent>().connect<&on_gamepad_button>();
+ globals::dispatcher.sink<gui::LanguageSetEvent>().connect<&on_language_set>();
+}
+
+void settings::init_late(void)
+{
+ for(std::size_t i = 0; i < NUM_LOCATIONS; ++i) {
+ std::sort(values[i].begin(), values[i].end(), [](const SettingValue* a, const SettingValue* b) {
+ return a->priority < b->priority;
+ });
+ }
+
+ refresh_input_wids();
+}
+
+void settings::shutdown(void)
+{
+ for(const SettingValue* value : values_all)
+ delete value;
+ for(std::size_t i = 0; i < NUM_LOCATIONS; values[i++].clear())
+ ;
+ values_all.clear();
+}
+
+void settings::layout(void)
+{
+ const ImGuiViewport* viewport = ImGui::GetMainViewport();
+ const ImVec2 window_start = ImVec2(viewport->Size.x * 0.05f, viewport->Size.y * 0.05f);
+ const ImVec2 window_size = ImVec2(viewport->Size.x * 0.90f, viewport->Size.y * 0.90f);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###settings", nullptr, WINDOW_FLAGS)) {
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(3.0f * globals::gui_scale, 3.0f * globals::gui_scale));
+
+ if(ImGui::BeginTabBar("###settings.tabs", ImGuiTabBarFlags_FittingPolicyResizeDown)) {
+ if(ImGui::TabItemButton("<<")) {
+ globals::gui_screen = GUI_MAIN_MENU;
+ globals::gui_keybind_ptr = nullptr;
+ }
+
+ if(ImGui::BeginTabItem(str_tab_general.c_str())) {
+ globals::gui_keybind_ptr = nullptr;
+ layout_general();
+ ImGui::EndTabItem();
+ }
+
+ if(ImGui::BeginTabItem(str_tab_input.c_str())) {
+ layout_input();
+ ImGui::EndTabItem();
+ }
+
+ if(ImGui::BeginTabItem(str_tab_video.c_str())) {
+ globals::gui_keybind_ptr = nullptr;
+ layout_video();
+ ImGui::EndTabItem();
+ }
+
+ if(globals::sound_ctx && globals::sound_dev) {
+ if(ImGui::BeginTabItem(str_tab_sound.c_str())) {
+ globals::gui_keybind_ptr = nullptr;
+ layout_sound();
+ ImGui::EndTabItem();
+ }
+ }
+
+ ImGui::EndTabBar();
+ }
+
+ ImGui::PopStyleVar();
+ }
+
+ ImGui::End();
+}
+
+void settings::add_checkbox(int priority, config::Boolean& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_CheckBox;
+ setting_value->type = setting_type::CHECKBOX;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->refresh_wids();
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_input(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_InputInt;
+ setting_value->type = setting_type::INPUT_INT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_input(int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip,
+ std::string_view fmt)
+{
+ auto setting_value = new SettingValue_InputFloat;
+ setting_value->type = setting_type::INPUT_FLOAT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->format = fmt;
+ setting_value->name = name;
+
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_input(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_InputUnsigned;
+ setting_value->type = setting_type::INPUT_UINT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_input(int priority, config::String& value, settings_location location, std::string_view name, bool tooltip,
+ bool allow_whitespace)
+{
+ auto setting_value = new SettingValue_InputString;
+ setting_value->type = setting_type::INPUT_STRING;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->allow_whitespace = allow_whitespace;
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_slider(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_SliderInt;
+ setting_value->type = setting_type::SLIDER_INT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_slider(int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip,
+ std::string_view fmt)
+{
+ auto setting_value = new SettingValue_SliderFloat;
+ setting_value->type = setting_type::SLIDER_FLOAT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->format = fmt;
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_slider(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_SliderUnsigned;
+ setting_value->type = setting_type::SLIDER_UINT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value->value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_stepper(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_StepperInt;
+ setting_value->type = setting_type::STEPPER_INT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->wids.resize(value.get_max_value() - value.get_min_value() + 1);
+ setting_value->refresh_wids();
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_stepper(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip)
+{
+ auto setting_value = new SettingValue_StepperUnsigned;
+ setting_value->type = setting_type::STEPPER_UINT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = tooltip;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->wids.resize(value.get_max_value() - value.get_min_value() + 1);
+ setting_value->refresh_wids();
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_keybind(int priority, config::KeyBind& value, settings_location location, std::string_view name)
+{
+ auto setting_value = new SettingValue_KeyBind;
+ setting_value->type = setting_type::KEYBIND;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = false;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->refresh_wids();
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_gamepad_axis(int priority, config::GamepadAxis& value, settings_location location, std::string_view name)
+{
+ auto setting_value = new SettingValue_GamepadAxis;
+ setting_value->type = setting_type::GAMEPAD_AXIS;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = false;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->refresh_wids();
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_gamepad_button(int priority, config::GamepadButton& value, settings_location location, std::string_view name)
+{
+ auto setting_value = new SettingValue_GamepadButton;
+ setting_value->type = setting_type::GAMEPAD_BUTTON;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = false;
+ setting_value->value = &value;
+ setting_value->name = name;
+
+ setting_value->refresh_wids();
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
+
+void settings::add_language_select(int priority, settings_location location, std::string_view name)
+{
+ auto setting_value = new SettingValue_Language;
+ setting_value->type = setting_type::LANGUAGE_SELECT;
+ setting_value->priority = priority;
+ setting_value->has_tooltip = false;
+ setting_value->name = name;
+
+ setting_value->wid = std::format("###{}", static_cast<const void*>(setting_value));
+
+ values[static_cast<unsigned int>(location)].push_back(setting_value);
+ values_all.push_back(setting_value);
+}
diff --git a/game/client/gui/settings.hh b/game/client/gui/settings.hh index efb8ca4..472a61d 100644 --- a/game/client/gui/settings.hh +++ b/game/client/gui/settings.hh @@ -1,90 +1,90 @@ -#pragma once - -namespace config -{ -class Boolean; -class String; -} // namespace config - -namespace config -{ -class Int; -class Float; -class Unsigned; -} // namespace config - -namespace config -{ -class KeyBind; -class GamepadAxis; -class GamepadButton; -} // namespace config - -enum class settings_location : unsigned int { - GENERAL = 0x0000U, - KEYBOARD_MOVEMENT = 0x0001U, - KEYBOARD_GAMEPLAY = 0x0002U, - KEYBOARD_MISC = 0x0003U, - GAMEPAD = 0x0004U, - GAMEPAD_MOVEMENT = 0x0005U, - GAMEPAD_GAMEPLAY = 0x0006U, - GAMEPAD_MISC = 0x0007U, - MOUSE = 0x0008U, - VIDEO = 0x0009U, - VIDEO_GUI = 0x000AU, - SOUND = 0x000BU, - SOUND_LEVELS = 0x000CU, - COUNT = 0x000DU, -}; - -namespace settings -{ -void init(void); -void init_late(void); -void shutdown(void); -void layout(void); -} // namespace settings - -namespace settings -{ -void add_checkbox(int priority, config::Boolean& value, settings_location location, std::string_view name, bool tooltip); -} // namespace settings - -namespace settings -{ -void add_input(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip); -void add_input(int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip, - std::string_view fmt = "%.3f"); -void add_input(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip); -void add_input(int priority, config::String& value, settings_location location, std::string_view name, bool tooltip, bool allow_whitespace); -} // namespace settings - -namespace settings -{ -void add_slider(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip); -void add_slider(int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip, - std::string_view format = "%.3f"); -void add_slider(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip); -} // namespace settings - -namespace settings -{ -void add_stepper(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip); -void add_stepper(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip); -} // namespace settings - -namespace settings -{ -void add_keybind(int priority, config::KeyBind& value, settings_location location, std::string_view name); -} // namespace settings - -namespace settings -{ -void add_gamepad_axis(int priority, config::GamepadAxis& value, settings_location location, std::string_view name); -void add_gamepad_button(int priority, config::GamepadButton& value, settings_location location, std::string_view name); -} // namespace settings - -namespace settings -{ -void add_language_select(int priority, settings_location location, std::string_view name); -} // namespace settings +#pragma once
+
+namespace config
+{
+class Boolean;
+class String;
+} // namespace config
+
+namespace config
+{
+class Int;
+class Float;
+class Unsigned;
+} // namespace config
+
+namespace config
+{
+class KeyBind;
+class GamepadAxis;
+class GamepadButton;
+} // namespace config
+
+enum class settings_location : unsigned int {
+ GENERAL = 0x0000U,
+ KEYBOARD_MOVEMENT = 0x0001U,
+ KEYBOARD_GAMEPLAY = 0x0002U,
+ KEYBOARD_MISC = 0x0003U,
+ GAMEPAD = 0x0004U,
+ GAMEPAD_MOVEMENT = 0x0005U,
+ GAMEPAD_GAMEPLAY = 0x0006U,
+ GAMEPAD_MISC = 0x0007U,
+ MOUSE = 0x0008U,
+ VIDEO = 0x0009U,
+ VIDEO_GUI = 0x000AU,
+ SOUND = 0x000BU,
+ SOUND_LEVELS = 0x000CU,
+ COUNT = 0x000DU,
+};
+
+namespace settings
+{
+void init(void);
+void init_late(void);
+void shutdown(void);
+void layout(void);
+} // namespace settings
+
+namespace settings
+{
+void add_checkbox(int priority, config::Boolean& value, settings_location location, std::string_view name, bool tooltip);
+} // namespace settings
+
+namespace settings
+{
+void add_input(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip);
+void add_input(int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip,
+ std::string_view fmt = "%.3f");
+void add_input(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip);
+void add_input(int priority, config::String& value, settings_location location, std::string_view name, bool tooltip, bool allow_whitespace);
+} // namespace settings
+
+namespace settings
+{
+void add_slider(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip);
+void add_slider(int priority, config::Float& value, settings_location location, std::string_view name, bool tooltip,
+ std::string_view format = "%.3f");
+void add_slider(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip);
+} // namespace settings
+
+namespace settings
+{
+void add_stepper(int priority, config::Int& value, settings_location location, std::string_view name, bool tooltip);
+void add_stepper(int priority, config::Unsigned& value, settings_location location, std::string_view name, bool tooltip);
+} // namespace settings
+
+namespace settings
+{
+void add_keybind(int priority, config::KeyBind& value, settings_location location, std::string_view name);
+} // namespace settings
+
+namespace settings
+{
+void add_gamepad_axis(int priority, config::GamepadAxis& value, settings_location location, std::string_view name);
+void add_gamepad_button(int priority, config::GamepadButton& value, settings_location location, std::string_view name);
+} // namespace settings
+
+namespace settings
+{
+void add_language_select(int priority, settings_location location, std::string_view name);
+} // namespace settings
diff --git a/game/client/gui/splash.cc b/game/client/gui/splash.cc index 440df49..9990103 100644 --- a/game/client/gui/splash.cc +++ b/game/client/gui/splash.cc @@ -1,178 +1,178 @@ -#include "client/pch.hh" - -#include "client/gui/splash.hh" - -#include "core/io/cmdline.hh" - -#include "core/math/constexpr.hh" - -#include "core/resource/resource.hh" - -#include "core/utils/epoch.hh" - -#include "client/gui/gui_screen.hh" -#include "client/gui/language.hh" - -#include "client/io/glfw.hh" - -#include "client/resource/texture_gui.hh" - -#include "client/globals.hh" - -constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration; - -constexpr static int SPLASH_COUNT = 4; -constexpr static std::size_t DELAY_MICROSECONDS = 2000000; -constexpr static std::string_view SPLASH_PATH = "textures/gui/client_splash.png"; - -static resource_ptr<TextureGUI> texture; -static float texture_aspect; -static float texture_alpha; - -static std::uint64_t end_time; -static std::string current_text; - -static void on_glfw_key(const io::GlfwKeyEvent& event) -{ - end_time = UINT64_C(0); -} - -static void on_glfw_mouse_button(const io::GlfwMouseButtonEvent& event) -{ - end_time = UINT64_C(0); -} - -static void on_glfw_scroll(const io::GlfwScrollEvent& event) -{ - end_time = UINT64_C(0); -} - -void gui::client_splash::init(void) -{ - if(io::cmdline::contains("nosplash")) { - texture = nullptr; - texture_aspect = 0.0f; - texture_alpha = 0.0f; - return; - } - - std::random_device randev; - std::uniform_int_distribution<int> dist(0, SPLASH_COUNT - 1); - - texture = resource::load<TextureGUI>(SPLASH_PATH, TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T); - texture_aspect = 0.0f; - texture_alpha = 0.0f; - - if(texture) { - if(texture->size.x > texture->size.y) { - texture_aspect = static_cast<float>(texture->size.x) / static_cast<float>(texture->size.y); - } - else { - texture_aspect = static_cast<float>(texture->size.y) / static_cast<float>(texture->size.x); - } - - texture_alpha = 1.0f; - } -} - -void gui::client_splash::init_late(void) -{ - if(!texture) { - // We don't have to waste time - // rendering the missing client_splash texture - return; - } - - end_time = utils::unix_microseconds() + DELAY_MICROSECONDS; - - globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>(); - globals::dispatcher.sink<io::GlfwMouseButtonEvent>().connect<&on_glfw_mouse_button>(); - globals::dispatcher.sink<io::GlfwScrollEvent>().connect<&on_glfw_scroll>(); - - current_text = gui::language::resolve("splash.skip_prompt"); - - while(!glfwWindowShouldClose(globals::window)) { - const std::uint64_t curtime = utils::unix_microseconds(); - const std::uint64_t remains = end_time - curtime; - - if(curtime >= end_time) { - break; - } - - texture_alpha = math::smoothstep(0.25f, 0.6f, static_cast<float>(remains) / static_cast<float>(DELAY_MICROSECONDS)); - - gui::client_splash::render(); - } - - globals::dispatcher.sink<io::GlfwKeyEvent>().disconnect<&on_glfw_key>(); - globals::dispatcher.sink<io::GlfwMouseButtonEvent>().disconnect<&on_glfw_mouse_button>(); - globals::dispatcher.sink<io::GlfwScrollEvent>().disconnect<&on_glfw_scroll>(); - - texture = nullptr; - texture_aspect = 0.0f; - texture_alpha = 0.0f; - end_time = UINT64_C(0); -} - -void gui::client_splash::render(void) -{ - if(!texture) { - // We don't have to waste time - // rendering the missing client_splash texture - return; - } - - // The client_splash is rendered outside the main - // render loop, so we have to manually begin - // and render both window and ImGui frames - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - glDisable(GL_DEPTH_TEST); - glBindFramebuffer(GL_FRAMEBUFFER, 0); - glViewport(0, 0, globals::width, globals::height); - - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - auto viewport = ImGui::GetMainViewport(); - auto window_start = ImVec2(0.0f, 0.0f); - auto window_size = ImVec2(viewport->Size.x, viewport->Size.y); - - ImGui::SetNextWindowPos(window_start); - ImGui::SetNextWindowSize(window_size); - - if(ImGui::Begin("###client_splash", nullptr, WINDOW_FLAGS)) { - const float image_width = 0.60f * viewport->Size.x; - const float image_height = image_width / texture_aspect; - const ImVec2 image_size = ImVec2(image_width, image_height); - - const float image_x = 0.5f * (viewport->Size.x - image_width); - const float image_y = 0.5f * (viewport->Size.y - image_height); - const ImVec2 image_pos = ImVec2(image_x, image_y); - - if(!current_text.empty()) { - ImGui::PushFont(globals::font_unscii8, 16.0f); - ImGui::SetCursorPos(ImVec2(16.0f, 16.0f)); - ImGui::TextDisabled("%s", current_text.c_str()); - ImGui::PopFont(); - } - - const ImVec2 uv_a = ImVec2(0.0f, 0.0f); - const ImVec2 uv_b = ImVec2(1.0f, 1.0f); - const ImVec4 tint = ImVec4(1.0f, 1.0f, 1.0f, texture_alpha); - - ImGui::SetCursorPos(image_pos); - ImGui::ImageWithBg(texture->handle, image_size, uv_a, uv_b, ImVec4(0.0f, 0.0f, 0.0f, 0.0f), tint); - } - - ImGui::End(); - - ImGui::Render(); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - glfwSwapBuffers(globals::window); - - glfwPollEvents(); -} +#include "client/pch.hh"
+
+#include "client/gui/splash.hh"
+
+#include "core/io/cmdline.hh"
+
+#include "core/math/constexpr.hh"
+
+#include "core/resource/resource.hh"
+
+#include "core/utils/epoch.hh"
+
+#include "client/gui/gui_screen.hh"
+#include "client/gui/language.hh"
+
+#include "client/io/glfw.hh"
+
+#include "client/resource/texture_gui.hh"
+
+#include "client/globals.hh"
+
+constexpr static ImGuiWindowFlags WINDOW_FLAGS = ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoDecoration;
+
+constexpr static int SPLASH_COUNT = 4;
+constexpr static std::size_t DELAY_MICROSECONDS = 2000000;
+constexpr static std::string_view SPLASH_PATH = "textures/gui/client_splash.png";
+
+static resource_ptr<TextureGUI> texture;
+static float texture_aspect;
+static float texture_alpha;
+
+static std::uint64_t end_time;
+static std::string current_text;
+
+static void on_glfw_key(const io::GlfwKeyEvent& event)
+{
+ end_time = UINT64_C(0);
+}
+
+static void on_glfw_mouse_button(const io::GlfwMouseButtonEvent& event)
+{
+ end_time = UINT64_C(0);
+}
+
+static void on_glfw_scroll(const io::GlfwScrollEvent& event)
+{
+ end_time = UINT64_C(0);
+}
+
+void gui::client_splash::init(void)
+{
+ if(io::cmdline::contains("nosplash")) {
+ texture = nullptr;
+ texture_aspect = 0.0f;
+ texture_alpha = 0.0f;
+ return;
+ }
+
+ std::random_device randev;
+ std::uniform_int_distribution<int> dist(0, SPLASH_COUNT - 1);
+
+ texture = resource::load<TextureGUI>(SPLASH_PATH, TEXTURE_GUI_LOAD_CLAMP_S | TEXTURE_GUI_LOAD_CLAMP_T);
+ texture_aspect = 0.0f;
+ texture_alpha = 0.0f;
+
+ if(texture) {
+ if(texture->size.x > texture->size.y) {
+ texture_aspect = static_cast<float>(texture->size.x) / static_cast<float>(texture->size.y);
+ }
+ else {
+ texture_aspect = static_cast<float>(texture->size.y) / static_cast<float>(texture->size.x);
+ }
+
+ texture_alpha = 1.0f;
+ }
+}
+
+void gui::client_splash::init_late(void)
+{
+ if(!texture) {
+ // We don't have to waste time
+ // rendering the missing client_splash texture
+ return;
+ }
+
+ end_time = utils::unix_microseconds() + DELAY_MICROSECONDS;
+
+ globals::dispatcher.sink<io::GlfwKeyEvent>().connect<&on_glfw_key>();
+ globals::dispatcher.sink<io::GlfwMouseButtonEvent>().connect<&on_glfw_mouse_button>();
+ globals::dispatcher.sink<io::GlfwScrollEvent>().connect<&on_glfw_scroll>();
+
+ current_text = gui::language::resolve("splash.skip_prompt");
+
+ while(!glfwWindowShouldClose(globals::window)) {
+ const std::uint64_t curtime = utils::unix_microseconds();
+ const std::uint64_t remains = end_time - curtime;
+
+ if(curtime >= end_time) {
+ break;
+ }
+
+ texture_alpha = math::smoothstep(0.25f, 0.6f, static_cast<float>(remains) / static_cast<float>(DELAY_MICROSECONDS));
+
+ gui::client_splash::render();
+ }
+
+ globals::dispatcher.sink<io::GlfwKeyEvent>().disconnect<&on_glfw_key>();
+ globals::dispatcher.sink<io::GlfwMouseButtonEvent>().disconnect<&on_glfw_mouse_button>();
+ globals::dispatcher.sink<io::GlfwScrollEvent>().disconnect<&on_glfw_scroll>();
+
+ texture = nullptr;
+ texture_aspect = 0.0f;
+ texture_alpha = 0.0f;
+ end_time = UINT64_C(0);
+}
+
+void gui::client_splash::render(void)
+{
+ if(!texture) {
+ // We don't have to waste time
+ // rendering the missing client_splash texture
+ return;
+ }
+
+ // The client_splash is rendered outside the main
+ // render loop, so we have to manually begin
+ // and render both window and ImGui frames
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplGlfw_NewFrame();
+ ImGui::NewFrame();
+
+ glDisable(GL_DEPTH_TEST);
+ glBindFramebuffer(GL_FRAMEBUFFER, 0);
+ glViewport(0, 0, globals::width, globals::height);
+
+ glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
+ glClear(GL_COLOR_BUFFER_BIT);
+
+ auto viewport = ImGui::GetMainViewport();
+ auto window_start = ImVec2(0.0f, 0.0f);
+ auto window_size = ImVec2(viewport->Size.x, viewport->Size.y);
+
+ ImGui::SetNextWindowPos(window_start);
+ ImGui::SetNextWindowSize(window_size);
+
+ if(ImGui::Begin("###client_splash", nullptr, WINDOW_FLAGS)) {
+ const float image_width = 0.60f * viewport->Size.x;
+ const float image_height = image_width / texture_aspect;
+ const ImVec2 image_size = ImVec2(image_width, image_height);
+
+ const float image_x = 0.5f * (viewport->Size.x - image_width);
+ const float image_y = 0.5f * (viewport->Size.y - image_height);
+ const ImVec2 image_pos = ImVec2(image_x, image_y);
+
+ if(!current_text.empty()) {
+ ImGui::PushFont(globals::font_unscii8, 16.0f);
+ ImGui::SetCursorPos(ImVec2(16.0f, 16.0f));
+ ImGui::TextDisabled("%s", current_text.c_str());
+ ImGui::PopFont();
+ }
+
+ const ImVec2 uv_a = ImVec2(0.0f, 0.0f);
+ const ImVec2 uv_b = ImVec2(1.0f, 1.0f);
+ const ImVec4 tint = ImVec4(1.0f, 1.0f, 1.0f, texture_alpha);
+
+ ImGui::SetCursorPos(image_pos);
+ ImGui::ImageWithBg(texture->handle, image_size, uv_a, uv_b, ImVec4(0.0f, 0.0f, 0.0f, 0.0f), tint);
+ }
+
+ ImGui::End();
+
+ ImGui::Render();
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+
+ glfwSwapBuffers(globals::window);
+
+ glfwPollEvents();
+}
diff --git a/game/client/gui/splash.hh b/game/client/gui/splash.hh index 3ce63e4..a28a9ad 100644 --- a/game/client/gui/splash.hh +++ b/game/client/gui/splash.hh @@ -1,8 +1,8 @@ -#pragma once - -namespace gui::client_splash -{ -void init(void); -void init_late(void); -void render(void); -} // namespace gui::client_splash +#pragma once
+
+namespace gui::client_splash
+{
+void init(void);
+void init_late(void);
+void render(void);
+} // namespace gui::client_splash
diff --git a/game/client/gui/status_lines.cc b/game/client/gui/status_lines.cc index 9d9ac4c..d1a919a 100644 --- a/game/client/gui/status_lines.cc +++ b/game/client/gui/status_lines.cc @@ -1,84 +1,84 @@ -#include "client/pch.hh" - -#include "client/gui/status_lines.hh" - -#include "client/gui/imdraw_ext.hh" - -#include "client/globals.hh" - -static float line_offsets[gui::STATUS_COUNT]; -static ImFont* line_fonts[gui::STATUS_COUNT]; -static float line_sizes[gui::STATUS_COUNT]; - -static ImVec4 line_text_colors[gui::STATUS_COUNT]; -static ImVec4 line_shadow_colors[gui::STATUS_COUNT]; -static std::string line_strings[gui::STATUS_COUNT]; -static std::uint64_t line_spawns[gui::STATUS_COUNT]; -static float line_fadeouts[gui::STATUS_COUNT]; - -void gui::status_lines::init(void) -{ - for(unsigned int i = 0U; i < STATUS_COUNT; ++i) { - line_text_colors[i] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); - line_shadow_colors[i] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); - line_strings[i] = std::string(); - line_spawns[i] = UINT64_MAX; - line_fadeouts[i] = 0.0f; - } -} - -void gui::status_lines::init_late(void) -{ - line_offsets[STATUS_DEBUG] = 64.0f; - line_offsets[STATUS_HOTBAR] = 40.0f; -} - -void gui::status_lines::layout(void) -{ - line_fonts[STATUS_DEBUG] = globals::font_unscii8; - line_sizes[STATUS_DEBUG] = 4.0f; - - line_fonts[STATUS_HOTBAR] = globals::font_unscii16; - line_sizes[STATUS_HOTBAR] = 8.0f; - - auto viewport = ImGui::GetMainViewport(); - auto draw_list = ImGui::GetForegroundDrawList(); - - for(unsigned int i = 0U; i < STATUS_COUNT; ++i) { - auto offset = line_offsets[i] * globals::gui_scale; - auto& text = line_strings[i]; - auto* font = line_fonts[i]; - - auto size = font->CalcTextSizeA(line_sizes[i] * globals::gui_scale, FLT_MAX, 0.0f, text.c_str(), text.c_str() + text.size()); - auto pos = ImVec2(0.5f * (viewport->Size.x - size.x), viewport->Size.y - offset); - - auto spawn = line_spawns[i]; - auto fadeout = line_fadeouts[i]; - auto alpha = std::exp(-1.0f * std::pow(1.0e-6f * static_cast<float>(globals::curtime - spawn) / fadeout, 10.0f)); - - auto& color = line_text_colors[i]; - auto& shadow = line_shadow_colors[i]; - auto color_U32 = ImGui::GetColorU32(ImVec4(color.x, color.y, color.z, color.w * alpha)); - auto shadow_U32 = ImGui::GetColorU32(ImVec4(shadow.x, shadow.y, shadow.z, color.w * alpha)); - - gui::imdraw_ext::text_shadow(text, pos, color_U32, shadow_U32, font, draw_list, line_sizes[i]); - } -} - -void gui::status_lines::set(unsigned int line, const std::string& text, const ImVec4& color, float fadeout) -{ - line_text_colors[line] = ImVec4(color.x, color.y, color.z, color.w); - line_shadow_colors[line] = ImVec4(color.x * 0.1f, color.y * 0.1f, color.z * 0.1f, color.w); - line_strings[line] = std::string(text); - line_spawns[line] = globals::curtime; - line_fadeouts[line] = fadeout; -} - -void gui::status_lines::unset(unsigned int line) -{ - line_text_colors[line] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); - line_shadow_colors[line] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f); - line_strings[line] = std::string(); - line_spawns[line] = UINT64_C(0); - line_fadeouts[line] = 0.0f; -} +#include "client/pch.hh"
+
+#include "client/gui/status_lines.hh"
+
+#include "client/gui/imdraw_ext.hh"
+
+#include "client/globals.hh"
+
+static float line_offsets[gui::STATUS_COUNT];
+static ImFont* line_fonts[gui::STATUS_COUNT];
+static float line_sizes[gui::STATUS_COUNT];
+
+static ImVec4 line_text_colors[gui::STATUS_COUNT];
+static ImVec4 line_shadow_colors[gui::STATUS_COUNT];
+static std::string line_strings[gui::STATUS_COUNT];
+static std::uint64_t line_spawns[gui::STATUS_COUNT];
+static float line_fadeouts[gui::STATUS_COUNT];
+
+void gui::status_lines::init(void)
+{
+ for(unsigned int i = 0U; i < STATUS_COUNT; ++i) {
+ line_text_colors[i] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
+ line_shadow_colors[i] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
+ line_strings[i] = std::string();
+ line_spawns[i] = UINT64_MAX;
+ line_fadeouts[i] = 0.0f;
+ }
+}
+
+void gui::status_lines::init_late(void)
+{
+ line_offsets[STATUS_DEBUG] = 64.0f;
+ line_offsets[STATUS_HOTBAR] = 40.0f;
+}
+
+void gui::status_lines::layout(void)
+{
+ line_fonts[STATUS_DEBUG] = globals::font_unscii8;
+ line_sizes[STATUS_DEBUG] = 4.0f;
+
+ line_fonts[STATUS_HOTBAR] = globals::font_unscii16;
+ line_sizes[STATUS_HOTBAR] = 8.0f;
+
+ auto viewport = ImGui::GetMainViewport();
+ auto draw_list = ImGui::GetForegroundDrawList();
+
+ for(unsigned int i = 0U; i < STATUS_COUNT; ++i) {
+ auto offset = line_offsets[i] * globals::gui_scale;
+ auto& text = line_strings[i];
+ auto* font = line_fonts[i];
+
+ auto size = font->CalcTextSizeA(line_sizes[i] * globals::gui_scale, FLT_MAX, 0.0f, text.c_str(), text.c_str() + text.size());
+ auto pos = ImVec2(0.5f * (viewport->Size.x - size.x), viewport->Size.y - offset);
+
+ auto spawn = line_spawns[i];
+ auto fadeout = line_fadeouts[i];
+ auto alpha = std::exp(-1.0f * std::pow(1.0e-6f * static_cast<float>(globals::curtime - spawn) / fadeout, 10.0f));
+
+ auto& color = line_text_colors[i];
+ auto& shadow = line_shadow_colors[i];
+ auto color_U32 = ImGui::GetColorU32(ImVec4(color.x, color.y, color.z, color.w * alpha));
+ auto shadow_U32 = ImGui::GetColorU32(ImVec4(shadow.x, shadow.y, shadow.z, color.w * alpha));
+
+ gui::imdraw_ext::text_shadow(text, pos, color_U32, shadow_U32, font, draw_list, line_sizes[i]);
+ }
+}
+
+void gui::status_lines::set(unsigned int line, const std::string& text, const ImVec4& color, float fadeout)
+{
+ line_text_colors[line] = ImVec4(color.x, color.y, color.z, color.w);
+ line_shadow_colors[line] = ImVec4(color.x * 0.1f, color.y * 0.1f, color.z * 0.1f, color.w);
+ line_strings[line] = std::string(text);
+ line_spawns[line] = globals::curtime;
+ line_fadeouts[line] = fadeout;
+}
+
+void gui::status_lines::unset(unsigned int line)
+{
+ line_text_colors[line] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
+ line_shadow_colors[line] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
+ line_strings[line] = std::string();
+ line_spawns[line] = UINT64_C(0);
+ line_fadeouts[line] = 0.0f;
+}
diff --git a/game/client/gui/status_lines.hh b/game/client/gui/status_lines.hh index c86e248..7245d68 100644 --- a/game/client/gui/status_lines.hh +++ b/game/client/gui/status_lines.hh @@ -1,21 +1,21 @@ -#pragma once - -namespace gui -{ -constexpr static unsigned int STATUS_DEBUG = 0x0000; // generic debug line -constexpr static unsigned int STATUS_HOTBAR = 0x0001; // hotbar item line -constexpr static unsigned int STATUS_COUNT = 0x0002; -} // namespace gui - -namespace gui::status_lines -{ -void init(void); -void init_late(void); -void layout(void); -} // namespace gui::status_lines - -namespace gui::status_lines -{ -void set(unsigned int line, const std::string& text, const ImVec4& color, float fadeout); -void unset(unsigned int line); -} // namespace gui::status_lines +#pragma once
+
+namespace gui
+{
+constexpr static unsigned int STATUS_DEBUG = 0x0000; // generic debug line
+constexpr static unsigned int STATUS_HOTBAR = 0x0001; // hotbar item line
+constexpr static unsigned int STATUS_COUNT = 0x0002;
+} // namespace gui
+
+namespace gui::status_lines
+{
+void init(void);
+void init_late(void);
+void layout(void);
+} // namespace gui::status_lines
+
+namespace gui::status_lines
+{
+void set(unsigned int line, const std::string& text, const ImVec4& color, float fadeout);
+void unset(unsigned int line);
+} // namespace gui::status_lines
diff --git a/game/client/gui/window_title.cc b/game/client/gui/window_title.cc index 6f46668..6e2387c 100644 --- a/game/client/gui/window_title.cc +++ b/game/client/gui/window_title.cc @@ -1,23 +1,23 @@ -#include "client/pch.hh" - -#include "client/gui/window_title.hh" - -#include "core/version.hh" - -#include "shared/splash.hh" - -#include "client/globals.hh" - -void gui::window_title::update(void) -{ - std::string title; - - if(globals::sound_ctx && globals::sound_dev) { - title = std::format("Voxelius {}: {}", version::semver, splash::get()); - } - else { - title = std::format("Voxelius {}: {} [NOSOUND]", version::semver, splash::get()); - } - - glfwSetWindowTitle(globals::window, title.c_str()); -} +#include "client/pch.hh"
+
+#include "client/gui/window_title.hh"
+
+#include "core/version.hh"
+
+#include "shared/splash.hh"
+
+#include "client/globals.hh"
+
+void gui::window_title::update(void)
+{
+ std::string title;
+
+ if(globals::sound_ctx && globals::sound_dev) {
+ title = std::format("Voxelius {}: {}", version::semver, splash::get());
+ }
+ else {
+ title = std::format("Voxelius {}: {} [NOSOUND]", version::semver, splash::get());
+ }
+
+ glfwSetWindowTitle(globals::window, title.c_str());
+}
diff --git a/game/client/gui/window_title.hh b/game/client/gui/window_title.hh index af1ab7c..86e6a86 100644 --- a/game/client/gui/window_title.hh +++ b/game/client/gui/window_title.hh @@ -1,6 +1,6 @@ -#pragma once - -namespace gui::window_title -{ -void update(void); -} // namespace gui::window_title +#pragma once
+
+namespace gui::window_title
+{
+void update(void);
+} // namespace gui::window_title
|
