From f3bdb6b1b22e94ddbb5cdafe8bfde3ce7efaee0c Mon Sep 17 00:00:00 2001 From: spycrab Date: Sun, 17 Mar 2019 01:09:06 +0100 Subject: [PATCH] Add imgui-based Netplay Chat --- Source/Core/DolphinQt/Host.cpp | 5 +- .../Core/DolphinQt/NetPlay/NetPlayDialog.cpp | 44 +++++---- Source/Core/DolphinQt/NetPlay/NetPlayDialog.h | 2 + Source/Core/DolphinQt/Settings.cpp | 4 + Source/Core/VideoCommon/CMakeLists.txt | 1 + Source/Core/VideoCommon/NetPlayChatUI.cpp | 97 +++++++++++++++++++ Source/Core/VideoCommon/NetPlayChatUI.h | 33 +++++++ Source/Core/VideoCommon/RenderBase.cpp | 9 ++ Source/Core/VideoCommon/RenderBase.h | 5 + Source/Core/VideoCommon/VideoCommon.vcxproj | 2 + .../VideoCommon/VideoCommon.vcxproj.filters | 6 ++ 11 files changed, 186 insertions(+), 22 deletions(-) create mode 100644 Source/Core/VideoCommon/NetPlayChatUI.cpp create mode 100644 Source/Core/VideoCommon/NetPlayChatUI.h diff --git a/Source/Core/DolphinQt/Host.cpp b/Source/Core/DolphinQt/Host.cpp index 63f03e307444..2cf602495d21 100644 --- a/Source/Core/DolphinQt/Host.cpp +++ b/Source/Core/DolphinQt/Host.cpp @@ -8,6 +8,8 @@ #include #include +#include + #include "Common/Common.h" #include "Core/ConfigManager.h" @@ -149,7 +151,8 @@ void Host_RequestRenderWindowSize(int w, int h) bool Host_UINeedsControllerState() { - return Settings::Instance().IsControllerStateNeeded(); + return Settings::Instance().IsControllerStateNeeded() || + (ImGui::GetCurrentContext() && ImGui::GetIO().WantCaptureKeyboard); } bool Host_UIBlocksControllerState() diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp index f400ef37e2d7..f0d425f71ec8 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.cpp @@ -54,6 +54,8 @@ #include "UICommon/GameFile.h" #include "UICommon/UICommon.h" +#include "VideoCommon/NetPlayChatUI.h" +#include "VideoCommon/RenderBase.h" #include "VideoCommon/VideoConfig.h" NetPlayDialog::NetPlayDialog(QWidget* parent) @@ -355,6 +357,15 @@ void NetPlayDialog::ConnectWidgets() connect(m_sync_all_wii_saves_action, &QAction::toggled, this, &NetPlayDialog::SaveSettings); } +void NetPlayDialog::SendMessage(const std::string& msg) +{ + Settings::Instance().GetNetPlayClient()->SendChatMessage(msg); + + DisplayMessage(QStringLiteral("%1: %2").arg(QString::fromStdString(m_nickname).toHtmlEscaped(), + QString::fromStdString(msg).toHtmlEscaped()), + "#1d6ed8"); +} + void NetPlayDialog::OnChat() { QueueOnObject(this, [this] { @@ -363,12 +374,9 @@ void NetPlayDialog::OnChat() if (msg.empty()) return; - Settings::Instance().GetNetPlayClient()->SendChatMessage(msg); m_chat_type_edit->clear(); - DisplayMessage(QStringLiteral("%1: %2").arg(QString::fromStdString(m_nickname).toHtmlEscaped(), - QString::fromStdString(msg).toHtmlEscaped()), - "#1d6ed8"); + SendMessage(msg); }); } @@ -762,23 +770,12 @@ void NetPlayDialog::DisplayMessage(const QString& msg, const std::string& color, QStringLiteral("%2").arg(QString::fromStdString(color), msg)); }); - if (g_ActiveConfig.bShowNetPlayMessages && Core::IsRunning()) - { - u32 osd_color; - - // Convert the color string to a OSD color - if (color == "red") - osd_color = OSD::Color::RED; - else if (color == "cyan") - osd_color = OSD::Color::CYAN; - else if (color == "green") - osd_color = OSD::Color::GREEN; - else - osd_color = OSD::Color::YELLOW; + QColor c(QString::fromStdString(color)); - OSD::AddTypedMessage(OSD::MessageType::NetPlayBuffer, msg.toStdString(), OSD::Duration::NORMAL, - osd_color); - } + if (g_ActiveConfig.bShowNetPlayMessages && Core::IsRunning()) + g_renderer->GetNetPlayChatUI()->AppendChat(msg.toStdString(), {static_cast(c.redF()), + static_cast(c.greenF()), + static_cast(c.blueF())}); } void NetPlayDialog::AppendChat(const std::string& msg) @@ -827,8 +824,12 @@ void NetPlayDialog::OnMsgStartGame() { DisplayMessage(tr("Started game"), "green"); + g_renderer->GetNetPlayChatUI() = + std::make_unique([this](const std::string& message) { SendMessage(message); }); + QueueOnObject(this, [this] { auto client = Settings::Instance().GetNetPlayClient(); + if (client) client->StartGame(FindGame(m_current_game)); UpdateDiscordPresence(); @@ -837,6 +838,7 @@ void NetPlayDialog::OnMsgStartGame() void NetPlayDialog::OnMsgStopGame() { + g_renderer->GetNetPlayChatUI().reset(); QueueOnObject(this, [this] { UpdateDiscordPresence(); }); } @@ -856,7 +858,7 @@ void NetPlayDialog::OnPadBufferChanged(u32 buffer) DisplayMessage(m_host_input_authority && !IsHosting() ? tr("Max buffer size changed to %1").arg(buffer) : tr("Buffer size changed to %1").arg(buffer), - ""); + "yellow"); m_buffer_size = static_cast(buffer); } diff --git a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h index 38e28cb2d9c7..49acc0fd42f3 100644 --- a/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h +++ b/Source/Core/DolphinQt/NetPlay/NetPlayDialog.h @@ -95,6 +95,8 @@ class NetPlayDialog : public QDialog, public NetPlay::NetPlayUI void SetGame(const QString& game_path); + void SendMessage(const std::string& message); + // Chat QGroupBox* m_chat_box; QTextEdit* m_chat_edit; diff --git a/Source/Core/DolphinQt/Settings.cpp b/Source/Core/DolphinQt/Settings.cpp index d8abe9b72319..116f1cde6997 100644 --- a/Source/Core/DolphinQt/Settings.cpp +++ b/Source/Core/DolphinQt/Settings.cpp @@ -27,6 +27,9 @@ #include "InputCommon/ControllerInterface/ControllerInterface.h" #include "InputCommon/InputConfig.h" +#include "VideoCommon/NetPlayChatUI.h" +#include "VideoCommon/RenderBase.h" + Settings::Settings() { qRegisterMetaType(); @@ -293,6 +296,7 @@ std::shared_ptr Settings::GetNetPlayClient() void Settings::ResetNetPlayClient(NetPlay::NetPlayClient* client) { m_client.reset(client); + g_renderer->GetNetPlayChatUI().reset(); } std::shared_ptr Settings::GetNetPlayServer() diff --git a/Source/Core/VideoCommon/CMakeLists.txt b/Source/Core/VideoCommon/CMakeLists.txt index b118080eb583..459fbcb7aa1c 100644 --- a/Source/Core/VideoCommon/CMakeLists.txt +++ b/Source/Core/VideoCommon/CMakeLists.txt @@ -22,6 +22,7 @@ add_library(videocommon ImageWrite.cpp IndexGenerator.cpp LightingShaderGen.cpp + NetPlayChatUI.cpp OnScreenDisplay.cpp OpcodeDecoding.cpp PerfQueryBase.cpp diff --git a/Source/Core/VideoCommon/NetPlayChatUI.cpp b/Source/Core/VideoCommon/NetPlayChatUI.cpp new file mode 100644 index 000000000000..71109f7b5da1 --- /dev/null +++ b/Source/Core/VideoCommon/NetPlayChatUI.cpp @@ -0,0 +1,97 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "VideoCommon/NetPlayChatUI.h" + +#include + +constexpr float DEFAULT_WINDOW_WIDTH = 220.0f; +constexpr float DEFAULT_WINDOW_HEIGHT = 400.0f; + +constexpr size_t MAX_BACKLOG_SIZE = 100; + +NetPlayChatUI::NetPlayChatUI(std::function callback) +{ + m_message_callback = std::move(callback); +} + +void NetPlayChatUI::Display() +{ + const float scale = ImGui::GetIO().DisplayFramebufferScale.x; + + ImGui::SetNextWindowPos(ImVec2(10.0f * scale, 10.0f * scale), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSizeConstraints( + ImVec2(DEFAULT_WINDOW_WIDTH * scale, DEFAULT_WINDOW_HEIGHT * scale), + ImGui::GetIO().DisplaySize); + + if (!ImGui::Begin("Chat", nullptr, ImGuiWindowFlags_None)) + { + ImGui::End(); + return; + } + + ImGui::BeginChild("Scrolling", ImVec2(0, -30 * scale), true, ImGuiWindowFlags_None); + for (const auto& msg : m_messages) + { + auto c = msg.second; + ImGui::PushTextWrapPos(0.0f); + ImGui::TextColored(ImVec4(c[0], c[1], c[2], 1.0f), "%s", msg.first.c_str()); + ImGui::PopTextWrapPos(); + } + + if (m_scroll_to_bottom) + { + ImGui::SetScrollHere(1.0f); + m_scroll_to_bottom = false; + } + + m_is_scrolled_to_bottom = ImGui::GetScrollY() == ImGui::GetScrollMaxY(); + + ImGui::EndChild(); + + ImGui::Spacing(); + + ImGui::PushItemWidth(-50.0f * scale); + + if (ImGui::InputText("", m_message_buf, IM_ARRAYSIZE(m_message_buf), + ImGuiInputTextFlags_EnterReturnsTrue)) + { + SendMessage(); + ImGui::SetKeyboardFocusHere(-1); + } + + ImGui::PopItemWidth(); + + ImGui::SameLine(); + + if (ImGui::Button("Send")) + SendMessage(); + + ImGui::End(); +} + +void NetPlayChatUI::AppendChat(const std::string& message, NetPlayChatUI::Color color) +{ + if (m_messages.size() > MAX_BACKLOG_SIZE) + m_messages.pop_front(); + + m_messages.push_back({message, color}); + + // Only scroll to bottom, if we were at the bottom previously + if (m_is_scrolled_to_bottom) + m_scroll_to_bottom = true; +} + +void NetPlayChatUI::SendMessage() +{ + // Check whether the input field is empty + if (m_message_buf[0] != '\0') + { + if (m_message_callback) + m_message_callback(m_message_buf); + + // 'Empty' the buffer + m_message_buf[0] = '\0'; + } +} diff --git a/Source/Core/VideoCommon/NetPlayChatUI.h b/Source/Core/VideoCommon/NetPlayChatUI.h new file mode 100644 index 000000000000..d078ae0df3bc --- /dev/null +++ b/Source/Core/VideoCommon/NetPlayChatUI.h @@ -0,0 +1,33 @@ +// Copyright 2019 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include + +class NetPlayChatUI +{ +public: + explicit NetPlayChatUI(std::function callback); + ~NetPlayChatUI() = default; + + using Color = std::array; + + void Display(); + void AppendChat(const std::string& message, Color color); + void SendMessage(); + +private: + char m_message_buf[256] = {}; + bool m_scroll_to_bottom = false; + bool m_is_scrolled_to_bottom = true; + + std::deque> m_messages; + std::function m_message_callback; +}; diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index ba56d9fc7fa9..107fcd11270b 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -57,6 +57,7 @@ #include "VideoCommon/FPSCounter.h" #include "VideoCommon/FramebufferManager.h" #include "VideoCommon/ImageWrite.h" +#include "VideoCommon/NetPlayChatUI.h" #include "VideoCommon/OnScreenDisplay.h" #include "VideoCommon/PixelEngine.h" #include "VideoCommon/PixelShaderManager.h" @@ -523,6 +524,9 @@ void Renderer::DrawDebugText() if (g_ActiveConfig.bOverlayStats) Statistics::Display(); + if (g_ActiveConfig.bShowNetPlayMessages && m_netplay_chat_ui) + m_netplay_chat_ui->Display(); + if (g_ActiveConfig.bOverlayProjStats) Statistics::DisplayProj(); } @@ -1642,3 +1646,8 @@ std::unique_ptr Renderer::CreateAsyncShaderCom { return std::make_unique(); } + +std::unique_ptr& Renderer::GetNetPlayChatUI() +{ + return m_netplay_chat_ui; +} diff --git a/Source/Core/VideoCommon/RenderBase.h b/Source/Core/VideoCommon/RenderBase.h index deaaa89aa944..70e622f5cf3b 100644 --- a/Source/Core/VideoCommon/RenderBase.h +++ b/Source/Core/VideoCommon/RenderBase.h @@ -41,6 +41,7 @@ class AbstractShader; class AbstractTexture; class AbstractStagingTexture; class NativeVertexFormat; +class NetPlayChatUI; struct TextureConfig; struct ComputePipelineConfig; struct AbstractPipelineConfig; @@ -242,6 +243,8 @@ class Renderer void BeginUIFrame(); void EndUIFrame(); + std::unique_ptr& GetNetPlayChatUI(); + protected: // Bitmask containing information about which configuration has changed for the backend. enum ConfigChangeBits : u32 @@ -384,6 +387,8 @@ class Renderer // Ensures all encoded frames have been written to the output file. void FinishFrameData(); + + std::unique_ptr m_netplay_chat_ui; }; extern std::unique_ptr g_renderer; diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj b/Source/Core/VideoCommon/VideoCommon.vcxproj index f4ab78c03289..d18f7f444e26 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj @@ -57,6 +57,7 @@ + @@ -120,6 +121,7 @@ + diff --git a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters index f804839f5431..c1ecc02dbd2e 100644 --- a/Source/Core/VideoCommon/VideoCommon.vcxproj.filters +++ b/Source/Core/VideoCommon/VideoCommon.vcxproj.filters @@ -197,6 +197,9 @@ Base + + Util + @@ -386,6 +389,9 @@ Shader Generators + + Util +