From 6a0fc4c438dcc827b6ed4e0a22a2e54676682ea0 Mon Sep 17 00:00:00 2001 From: Aestek Date: Tue, 2 Feb 2016 16:35:27 +0100 Subject: [PATCH 1/3] Improve netplay setup dialog UX * Focus "Hash Code" / "IP address" text box by default in "Connect" * Focus game list in "Host" tab * RETURN keypress now host/join depending on selected tab * Remember last hosted game * Remove PanicAlertT: * Simply log message to netplay window * Remove them when they are useless * Show some netplay message in OSD * Chat messages * Pad buffer changes * Desync alerts * Stop the game consistently when another player disconnects / crashes * Prettify chat textbox * Log netplay ping to OSD Join scenario: * Copy netplay code * Open netplay * Paste code * Press enter Host scenario: * Open netplay * Go to host tab * Press enter --- Source/Core/Common/TraversalClient.cpp | 19 -- Source/Core/Core/NetPlayClient.cpp | 61 ++++--- Source/Core/Core/NetPlayClient.h | 6 + Source/Core/Core/NetPlayServer.cpp | 26 ++- .../DolphinWX/NetPlay/NetPlaySetupFrame.cpp | 158 ++++++++++++----- .../DolphinWX/NetPlay/NetPlaySetupFrame.h | 14 +- Source/Core/DolphinWX/NetPlay/NetWindow.cpp | 162 ++++++++++++++---- Source/Core/DolphinWX/NetPlay/NetWindow.h | 26 ++- Source/Core/DolphinWX/VideoConfigDiag.cpp | 23 ++- Source/Core/VideoCommon/OnScreenDisplay.cpp | 63 ++++--- Source/Core/VideoCommon/OnScreenDisplay.h | 42 ++++- Source/Core/VideoCommon/VideoConfig.cpp | 4 + Source/Core/VideoCommon/VideoConfig.h | 2 + 13 files changed, 428 insertions(+), 178 deletions(-) diff --git a/Source/Core/Common/TraversalClient.cpp b/Source/Core/Common/TraversalClient.cpp index 81d592d4c3..67b4676769 100644 --- a/Source/Core/Common/TraversalClient.cpp +++ b/Source/Core/Common/TraversalClient.cpp @@ -208,25 +208,6 @@ void TraversalClient::OnFailure(FailureReason reason) m_State = Failure; m_FailureReason = reason; - switch (reason) - { - case TraversalClient::BadHost: - PanicAlertT("Couldn't look up central server %s", m_Server.c_str()); - break; - case TraversalClient::VersionTooOld: - PanicAlertT("Dolphin too old for traversal server"); - break; - case TraversalClient::ServerForgotAboutUs: - PanicAlertT("Disconnected from traversal server"); - break; - case TraversalClient::SocketSendError: - PanicAlertT("Socket error sending to traversal server"); - break; - case TraversalClient::ResendTimeout: - PanicAlertT("Timeout connecting to traversal server"); - break; - } - if (m_Client) m_Client->OnTraversalStateChanged(); } diff --git a/Source/Core/Core/NetPlayClient.cpp b/Source/Core/Core/NetPlayClient.cpp index 9d974d3342..19c073c020 100644 --- a/Source/Core/Core/NetPlayClient.cpp +++ b/Source/Core/Core/NetPlayClient.cpp @@ -27,6 +27,8 @@ #include "Core/Movie.h" #include "Core/NetPlayClient.h" #include "InputCommon/GCAdapter.h" +#include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/VideoConfig.h" static std::mutex crit_netplay_client; static NetPlayClient* netplay_client = nullptr; @@ -351,6 +353,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) packet >> size; m_target_buffer_size = size; + m_dialog->OnPadBufferChanged(size); } break; @@ -428,17 +431,10 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) break; case NP_MSG_STOP_GAME: - { - m_dialog->OnMsgStopGame(); - } - break; - case NP_MSG_DISABLE_GAME: { - PanicAlertT("Other client disconnected while game is running!! NetPlay is disabled. You must " - "manually stop the game."); - m_is_running.store(false); - NetPlay_Disable(); + StopGame(); + m_dialog->OnMsgStopGame(); } break; @@ -466,6 +462,7 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) packet >> player.ping; } + DisplayPlayersPing(); m_dialog->Update(); } break; @@ -476,18 +473,15 @@ unsigned int NetPlayClient::OnData(sf::Packet& packet) u32 frame; packet >> pid_to_blame; packet >> frame; - const char* blame_str = ""; - const char* blame_name = ""; + + std::string player = "??"; std::lock_guard lkp(m_crit.players); - if (pid_to_blame != -1) { auto it = m_players.find(pid_to_blame); - blame_str = " from player "; - blame_name = it != m_players.end() ? it->second.name.c_str() : "??"; + if (it != m_players.end()) + player = it->second.name; } - - m_dialog->AppendChat(StringFromFormat("/!\\ Possible desync detected%s%s on frame %u", - blame_str, blame_name, frame)); + m_dialog->OnDesync(frame, player); } break; @@ -571,6 +565,24 @@ void NetPlayClient::Send(sf::Packet& packet) enet_peer_send(m_server, 0, epac); } +void NetPlayClient::DisplayPlayersPing() +{ + if (!g_ActiveConfig.bShowNetPlayPing) + return; + + OSD::AddTypedMessage(OSD::MessageType::NetPlayPing, + StringFromFormat("Ping: %u", GetPlayersMaxPing()), OSD::Duration::SHORT, + OSD::Color::CYAN); +} + +u32 NetPlayClient::GetPlayersMaxPing() const +{ + return std::max_element( + m_players.begin(), m_players.end(), + [](const auto& a, const auto& b) { return a.second.ping < b.second.ping; }) + ->second.ping; +} + void NetPlayClient::Disconnect() { ENetEvent netEvent; @@ -636,13 +648,11 @@ void NetPlayClient::ThreadFunc() enet_packet_destroy(netEvent.packet); break; case ENET_EVENT_TYPE_DISCONNECT: - m_is_running.store(false); - NetPlay_Disable(); - m_dialog->AppendChat("< LOST CONNECTION TO SERVER >"); - PanicAlertT("Lost connection to server!"); - m_do_loop.store(false); + m_dialog->OnConnectionLost(); + + if (m_is_running.load()) + StopGame(); - netEvent.peer->data = nullptr; break; default: break; @@ -786,8 +796,6 @@ bool NetPlayClient::StartGame(const std::string& path) return false; } - m_dialog->AppendChat(" -- STARTING GAME -- "); - m_timebase_frame = 0; m_is_running.store(true); @@ -895,6 +903,7 @@ void NetPlayClient::OnTraversalStateChanged() m_traversal_client->m_State == TraversalClient::Failure) { Disconnect(); + m_dialog->OnTraversalError(m_traversal_client->m_FailureReason); } } @@ -1080,8 +1089,6 @@ bool NetPlayClient::StopGame() return false; } - m_dialog->AppendChat(" -- STOPPING GAME -- "); - m_is_running.store(false); NetPlay_Disable(); diff --git a/Source/Core/Core/NetPlayClient.h b/Source/Core/Core/NetPlayClient.h index 196066d457..4eddc36adf 100644 --- a/Source/Core/Core/NetPlayClient.h +++ b/Source/Core/Core/NetPlayClient.h @@ -32,6 +32,10 @@ public: virtual void OnMsgChangeGame(const std::string& filename) = 0; virtual void OnMsgStartGame() = 0; virtual void OnMsgStopGame() = 0; + virtual void OnPadBufferChanged(u32 buffer) = 0; + virtual void OnDesync(u32 frame, const std::string& player) = 0; + virtual void OnConnectionLost() = 0; + virtual void OnTraversalError(int error) = 0; virtual bool IsRecording() = 0; virtual std::string FindGame(const std::string& game) = 0; virtual void ShowMD5Dialog(const std::string& file_identifier) = 0; @@ -157,6 +161,8 @@ private: void Disconnect(); bool Connect(); void ComputeMD5(const std::string& file_identifier); + void DisplayPlayersPing(); + u32 GetPlayersMaxPing() const; bool m_is_connected = false; ConnectionState m_connection_state = ConnectionState::Failure; diff --git a/Source/Core/Core/NetPlayServer.cpp b/Source/Core/Core/NetPlayServer.cpp index 3e3a6e16b4..5bc9793811 100644 --- a/Source/Core/Core/NetPlayServer.cpp +++ b/Source/Core/Core/NetPlayServer.cpp @@ -350,15 +350,13 @@ unsigned int NetPlayServer::OnDisconnect(Client& player) { if (mapping == pid && pid != 1) { - PanicAlertT("Client disconnect while game is running!! NetPlay is disabled. You must " - "manually stop the game."); std::lock_guard lkg(m_crit.game); m_is_running = false; sf::Packet spac; spac << (MessageId)NP_MSG_DISABLE_GAME; // this thread doesn't need players lock - SendToClients(spac, 1); + SendToClients(spac, -1); break; } } @@ -636,19 +634,15 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) })) { int pid_to_blame = -1; - if (timebases.size() > 2) + for (auto pair : timebases) { - for (auto pair : timebases) + if (std::all_of(timebases.begin(), timebases.end(), [&](std::pair other) { + return other.first == pair.first || other.second != pair.second; + })) { - if (std::all_of(timebases.begin(), timebases.end(), - [&](std::pair other) { - return other.first == pair.first || other.second != pair.second; - })) - { - // we are the only outlier - pid_to_blame = pair.first; - break; - } + // we are the only outlier + pid_to_blame = pair.first; + break; } } @@ -720,8 +714,8 @@ unsigned int NetPlayServer::OnData(sf::Packet& packet, Client& player) void NetPlayServer::OnTraversalStateChanged() { - if (m_dialog) - m_dialog->Update(); + if (m_dialog && m_traversal_client->m_State == TraversalClient::Failure) + m_dialog->OnTraversalError(m_traversal_client->m_FailureReason); } // called from ---GUI--- thread diff --git a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp index 1e845d59bb..73bdb92bb2 100644 --- a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp +++ b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp @@ -43,10 +43,11 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl : wxFrame(parent, wxID_ANY, _("Dolphin NetPlay Setup")), m_game_list(game_list) { IniFile inifile; - inifile.Load(File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"); + inifile.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); wxPanel* const panel = new wxPanel(this); + panel->Bind(wxEVT_CHAR_HOOK, &NetPlaySetupFrame::OnKeyDown, this); // top row wxBoxSizer* const trav_szr = new wxBoxSizer(wxHORIZONTAL); @@ -57,7 +58,7 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl panel, wxID_ANY, _("Connection Type:"), wxDefaultPosition, wxSize(100, -1)); m_direct_traversal = new wxChoice(panel, wxID_ANY, wxDefaultPosition, wxSize(150, -1)); - m_direct_traversal->Bind(wxEVT_CHOICE, &NetPlaySetupFrame::OnChoice, this); + m_direct_traversal->Bind(wxEVT_CHOICE, &NetPlaySetupFrame::OnDirectTraversalChoice, this); m_direct_traversal->Append(_("Direct Connection")); m_direct_traversal->Append(_("Traversal Server")); @@ -106,19 +107,26 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl centralServer + ":" + centralPort); } // tabs - wxNotebook* const notebook = new wxNotebook(panel, wxID_ANY); - wxPanel* const connect_tab = new wxPanel(notebook, wxID_ANY); - notebook->AddPage(connect_tab, _("Connect")); - wxPanel* const host_tab = new wxPanel(notebook, wxID_ANY); - notebook->AddPage(host_tab, _("Host")); + m_notebook = new wxNotebook(panel, wxID_ANY); + wxPanel* const connect_tab = new wxPanel(m_notebook, wxID_ANY); + m_notebook->AddPage(connect_tab, _("Connect")); + wxPanel* const host_tab = new wxPanel(m_notebook, wxID_ANY); + m_notebook->AddPage(host_tab, _("Host")); // connect tab { m_ip_lbl = new wxStaticText(connect_tab, wxID_ANY, _("Host Code :")); - std::string address; - netplay_section.Get("HostCode", &address, "00000000"); - m_connect_ip_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(address)); + std::string last_hash_code; + netplay_section.Get("HostCode", &last_hash_code, "00000000"); + std::string last_ip_address; + netplay_section.Get("Address", &last_ip_address, "127.0.0.1"); + m_connect_ip_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(last_ip_address)); + m_connect_hashcode_text = new wxTextCtrl(connect_tab, wxID_ANY, StrToWxStr(last_hash_code)); + + // Will be overridden by OnDirectTraversalChoice, but is necessary + // so that both inputs do not take up space + m_connect_hashcode_text->Hide(); m_client_port_lbl = new wxStaticText(connect_tab, wxID_ANY, _("Port :")); @@ -144,7 +152,8 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl top_szr->Add(m_ip_lbl, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); top_szr->Add(m_connect_ip_text, 3); - top_szr->Add(m_client_port_lbl, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT | wxLEFT, 5); + top_szr->Add(m_connect_hashcode_text, 3); + top_szr->Add(m_client_port_lbl, 0, wxCENTER | wxRIGHT | wxLEFT, 5); top_szr->Add(m_connect_port_text, 1); wxBoxSizer* const con_szr = new wxBoxSizer(wxVERTICAL); @@ -190,6 +199,10 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl NetPlayDialog::FillWithGameNames(m_game_lbox, *game_list); + std::string last_hosted_game; + if (netplay_section.Get("SelectedHostGame", &last_hosted_game, "")) + m_game_lbox->SetStringSelection(last_hosted_game); + wxBoxSizer* const top_szr = new wxBoxSizer(wxHORIZONTAL); top_szr->Add(m_host_port_lbl, 0, wxCENTER | wxRIGHT, 5); top_szr->Add(m_host_port_text, 0); @@ -221,7 +234,7 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl main_szr->Add(trav_szr, 0, wxALL | wxALIGN_LEFT, 5); main_szr->Add(nick_szr, 0, wxALL | wxALIGN_LEFT, 5); main_szr->Add(m_traversal_lbl, 0, wxALL | wxALIGN_LEFT, 5); - main_szr->Add(notebook, 1, wxLEFT | wxRIGHT | wxEXPAND, 5); + main_szr->Add(m_notebook, 1, wxLEFT | wxRIGHT | wxEXPAND, 5); main_szr->Add(quit_btn, 0, wxALL | wxALIGN_RIGHT, 5); panel->SetSizerAndFit(main_szr); @@ -237,37 +250,35 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl // Needs to be done last or it set up the spacing on the page correctly wxCommandEvent ev; - OnChoice(ev); + OnDirectTraversalChoice(ev); } NetPlaySetupFrame::~NetPlaySetupFrame() { IniFile inifile; - const std::string dolphin_ini = File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"; + const std::string dolphin_ini = File::GetUserPath(F_DOLPHINCONFIG_IDX); inifile.Load(dolphin_ini); IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); - std::string travChoice = "traversal"; - if (m_direct_traversal->GetSelection() == 1) - { - netplay_section.Set("TraversalChoice", travChoice); - } - else + std::string travChoice; + switch (m_direct_traversal->GetSelection()) { + case TRAVERSAL_CHOICE: + travChoice = "traversal"; + break; + case DIRECT_CHOICE: travChoice = "direct"; - netplay_section.Set("TraversalChoice", travChoice); + break; } + netplay_section.Set("TraversalChoice", travChoice); netplay_section.Set("Nickname", WxStrToStr(m_nickname_text->GetValue())); - if (m_direct_traversal->GetCurrentSelection() == 0) - { + if (m_direct_traversal->GetCurrentSelection() == DIRECT_CHOICE) netplay_section.Set("Address", WxStrToStr(m_connect_ip_text->GetValue())); - } else - { - netplay_section.Set("HostCode", WxStrToStr(m_connect_ip_text->GetValue())); - } + netplay_section.Set("HostCode", WxStrToStr(m_connect_hashcode_text->GetValue())); + netplay_section.Set("ConnectPort", WxStrToStr(m_connect_port_text->GetValue())); netplay_section.Set("HostPort", WxStrToStr(m_host_port_text->GetValue())); netplay_section.Set("ListenPort", m_traversal_listen_port_enabled->IsChecked() ? @@ -283,21 +294,19 @@ void NetPlaySetupFrame::MakeNetPlayDiag(int port, const std::string& game, bool NetPlayDialog*& npd = NetPlayDialog::GetInstance(); NetPlayClient*& netplay_client = NetPlayDialog::GetNetPlayClient(); + bool trav = !is_hosting && m_direct_traversal->GetCurrentSelection() == TRAVERSAL_CHOICE; + std::string ip; npd = new NetPlayDialog(m_parent, m_game_list, game, is_hosting); if (is_hosting) ip = "127.0.0.1"; + else if (trav) + ip = WxStrToStr(m_connect_hashcode_text->GetValue()); else ip = WxStrToStr(m_connect_ip_text->GetValue()); - bool trav; - if (!is_hosting && m_direct_traversal->GetCurrentSelection() == 1) - trav = true; - else - trav = false; - IniFile inifile; - inifile.Load(File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"); + inifile.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); std::string centralPortString; @@ -323,6 +332,16 @@ void NetPlaySetupFrame::MakeNetPlayDiag(int port, const std::string& game, bool void NetPlaySetupFrame::OnHost(wxCommandEvent&) { + DoHost(); +} + +void NetPlaySetupFrame::DoHost() +{ + IniFile ini_file; + const std::string dolphin_ini = File::GetUserPath(F_DOLPHINCONFIG_IDX); + ini_file.Load(dolphin_ini); + IniFile::Section& netplay_section = *ini_file.GetOrCreateSection("NetPlay"); + NetPlayDialog*& npd = NetPlayDialog::GetInstance(); NetPlayServer*& netplay_server = NetPlayDialog::GetNetPlayServer(); @@ -354,10 +373,6 @@ void NetPlaySetupFrame::OnHost(wxCommandEvent&) m_host_port_text->GetValue().ToULong(&listen_port); } - IniFile inifile; - inifile.Load(File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"); - IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); - std::string centralPortString; GetTraversalPort(netplay_section, ¢ralPortString); unsigned long int centralPort; @@ -377,6 +392,9 @@ void NetPlaySetupFrame::OnHost(wxCommandEvent&) #endif MakeNetPlayDiag(netplay_server->GetPort(), game, true); netplay_server->SetNetPlayUI(NetPlayDialog::GetInstance()); + + netplay_section.Set("SelectedHostGame", game); + ini_file.Save(dolphin_ini); } else { @@ -390,6 +408,11 @@ void NetPlaySetupFrame::OnHost(wxCommandEvent&) } void NetPlaySetupFrame::OnJoin(wxCommandEvent&) +{ + DoJoin(); +} + +void NetPlaySetupFrame::DoJoin() { NetPlayDialog*& npd = NetPlayDialog::GetInstance(); if (npd) @@ -406,7 +429,7 @@ void NetPlaySetupFrame::OnJoin(wxCommandEvent&) void NetPlaySetupFrame::OnResetTraversal(wxCommandEvent& event) { IniFile inifile; - const std::string dolphin_ini = File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"; + const std::string dolphin_ini = File::GetUserPath(F_DOLPHINCONFIG_IDX); inifile.Load(dolphin_ini); IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); netplay_section.Set("TraversalServer", (std::string) "stun.dolphin-emu.org"); @@ -421,26 +444,23 @@ void NetPlaySetupFrame::OnTraversalListenPortChanged(wxCommandEvent& event) m_traversal_listen_port->Enable(m_traversal_listen_port_enabled->IsChecked()); } -void NetPlaySetupFrame::OnChoice(wxCommandEvent& event) +void NetPlaySetupFrame::OnDirectTraversalChoice(wxCommandEvent& event) { int sel = m_direct_traversal->GetSelection(); IniFile inifile; - inifile.Load(File::GetUserPath(D_CONFIG_IDX) + "Dolphin.ini"); + inifile.Load(File::GetUserPath(F_DOLPHINCONFIG_IDX)); IniFile::Section& netplay_section = *inifile.GetOrCreateSection("NetPlay"); - if (sel == 1) + if (sel == TRAVERSAL_CHOICE) { m_traversal_lbl->Show(); m_trav_reset_btn->Show(); + m_connect_hashcode_text->Show(); + m_connect_ip_text->Hide(); // Traversal // client tab { m_ip_lbl->SetLabelText("Host Code: "); - - std::string address; - netplay_section.Get("HostCode", &address, "00000000"); - m_connect_ip_text->SetLabelText(address); - m_client_port_lbl->Hide(); m_connect_port_text->Hide(); } @@ -460,6 +480,8 @@ void NetPlaySetupFrame::OnChoice(wxCommandEvent& event) { m_traversal_lbl->Hide(); m_trav_reset_btn->Hide(); + m_connect_hashcode_text->Hide(); + m_connect_ip_text->Show(); // Direct // Client tab { @@ -482,6 +504,48 @@ void NetPlaySetupFrame::OnChoice(wxCommandEvent& event) m_upnp_chk->Show(); #endif } + m_connect_ip_text->GetParent()->Layout(); + DispatchFocus(); +} + +void NetPlaySetupFrame::OnKeyDown(wxKeyEvent& event) +{ + // Let the event propagate + event.Skip(); + + if (event.GetKeyCode() != wxKeyCode::WXK_RETURN) + return; + + int current_tab = m_notebook->GetSelection(); + + switch (current_tab) + { + case CONNECT_TAB: + DoJoin(); + break; + case HOST_TAB: + DoHost(); + break; + } +} + +void NetPlaySetupFrame::DispatchFocus() +{ + int current_tab = m_notebook->GetSelection(); + + switch (current_tab) + { + case CONNECT_TAB: + if (m_direct_traversal->GetCurrentSelection() == TRAVERSAL_CHOICE) + m_connect_hashcode_text->SetFocus(); + else + m_connect_ip_text->SetFocus(); + break; + + case HOST_TAB: + m_game_lbox->SetFocus(); + break; + } } void NetPlaySetupFrame::OnQuit(wxCommandEvent&) diff --git a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h index c2ddb569e1..24acc21b79 100644 --- a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h +++ b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h @@ -23,12 +23,22 @@ public: ~NetPlaySetupFrame(); private: + static constexpr int CONNECT_TAB = 0; + static constexpr int HOST_TAB = 1; + + static constexpr int DIRECT_CHOICE = 0; + static constexpr int TRAVERSAL_CHOICE = 1; + void OnJoin(wxCommandEvent& event); void OnHost(wxCommandEvent& event); + void DoJoin(); + void DoHost(); void OnQuit(wxCommandEvent& event); - void OnChoice(wxCommandEvent& event); + void OnDirectTraversalChoice(wxCommandEvent& event); void OnResetTraversal(wxCommandEvent& event); void OnTraversalListenPortChanged(wxCommandEvent& event); + void OnKeyDown(wxKeyEvent& event); + void DispatchFocus(); void MakeNetPlayDiag(int port, const std::string& game, bool is_hosting); @@ -39,11 +49,13 @@ private: wxTextCtrl* m_host_port_text; wxTextCtrl* m_connect_port_text; wxTextCtrl* m_connect_ip_text; + wxTextCtrl* m_connect_hashcode_text; wxChoice* m_direct_traversal; wxStaticText* m_traversal_lbl; wxButton* m_trav_reset_btn; wxCheckBox* m_traversal_listen_port_enabled; wxSpinCtrl* m_traversal_listen_port; + wxNotebook* m_notebook; wxListBox* m_game_lbox; #ifdef USE_UPNP diff --git a/Source/Core/DolphinWX/NetPlay/NetWindow.cpp b/Source/Core/DolphinWX/NetPlay/NetWindow.cpp index bbd5308e03..200597b9ca 100644 --- a/Source/Core/DolphinWX/NetPlay/NetWindow.cpp +++ b/Source/Core/DolphinWX/NetPlay/NetWindow.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -48,29 +49,13 @@ #include "DolphinWX/WxUtils.h" #include "MD5Dialog.h" +#include "VideoCommon/OnScreenDisplay.h" +#include "VideoCommon/VideoConfig.h" + NetPlayServer* NetPlayDialog::netplay_server = nullptr; NetPlayClient* NetPlayDialog::netplay_client = nullptr; NetPlayDialog* NetPlayDialog::npd = nullptr; -static wxString FailureReasonStringForHostLabel(int reason) -{ - switch (reason) - { - case TraversalClient::BadHost: - return _("(Error: Bad host)"); - case TraversalClient::VersionTooOld: - return _("(Error: Dolphin too old)"); - case TraversalClient::ServerForgotAboutUs: - return _("(Error: Disconnected)"); - case TraversalClient::SocketSendError: - return _("(Error: Socket)"); - case TraversalClient::ResendTimeout: - return _("(Error: Timeout)"); - default: - return _("(Error: Unknown)"); - } -} - static std::string BuildGameName(const GameListItem& game) { // Lang needs to be consistent @@ -280,13 +265,13 @@ NetPlayDialog::~NetPlayDialog() void NetPlayDialog::OnChat(wxCommandEvent&) { - wxString text = m_chat_msg_text->GetValue(); + std::string text = WxStrToStr(m_chat_msg_text->GetValue()); if (!text.empty()) { - netplay_client->SendChatMessage(WxStrToStr(text)); - m_chat_text->AppendText(text.Prepend(" >> ").Append('\n')); + netplay_client->SendChatMessage(text); m_chat_msg_text->Clear(); + AddChatMessage(ChatMessageType::UserOut, text); } } @@ -406,11 +391,46 @@ void NetPlayDialog::OnAdjustBuffer(wxCommandEvent& event) { const int val = ((wxSpinCtrl*)event.GetEventObject())->GetValue(); netplay_server->AdjustPadBufferSize(val); +} - std::ostringstream ss; - ss << "< Pad Buffer: " << val << " >"; - netplay_client->SendChatMessage(ss.str()); - m_chat_text->AppendText(StrToWxStr(ss.str()).Append('\n')); +void NetPlayDialog::OnPadBufferChanged(u32 buffer) +{ + m_pad_buffer = buffer; + wxThreadEvent evt(wxEVT_THREAD, NP_GUI_EVT_PAD_BUFFER_CHANGE); + GetEventHandler()->AddPendingEvent(evt); +} + +void NetPlayDialog::OnDesync(u32 frame, const std::string& player) +{ + m_desync_frame = frame; + m_desync_player = player; + wxThreadEvent evt(wxEVT_THREAD, NP_GUI_EVT_DESYNC); + GetEventHandler()->AddPendingEvent(evt); +} + +void NetPlayDialog::OnConnectionLost() +{ + wxThreadEvent evt(wxEVT_THREAD, NP_GUI_EVT_CONNECTION_LOST); + GetEventHandler()->AddPendingEvent(evt); +} + +void NetPlayDialog::OnTraversalError(int error) +{ + switch (error) + { + case TraversalClient::BadHost: + PanicAlertT("Couldn't look up central server"); + break; + case TraversalClient::VersionTooOld: + PanicAlertT("Dolphin is too old for traversal server"); + break; + case TraversalClient::ServerForgotAboutUs: + case TraversalClient::SocketSendError: + case TraversalClient::ResendTimeout: + wxThreadEvent evt(wxEVT_THREAD, NP_GUI_EVT_TRAVERSAL_CONNECTION_ERROR); + GetEventHandler()->AddPendingEvent(evt); + break; + } } void NetPlayDialog::OnQuit(wxCommandEvent&) @@ -477,17 +497,16 @@ void NetPlayDialog::OnThread(wxThreadEvent& event) case NP_GUI_EVT_START_GAME: // client start game :/ { - std::string game = FindCurrentGame(); - if (game.empty()) - WxUtils::ShowErrorDialog(_("Game not found!")); - else - netplay_client->StartGame(game); + netplay_client->StartGame(FindCurrentGame()); + std::string msg = "Starting game"; + AddChatMessage(ChatMessageType::Info, msg); } break; case NP_GUI_EVT_STOP_GAME: // client stop game { - netplay_client->StopGame(); + std::string msg = "Stopping game"; + AddChatMessage(ChatMessageType::Info, msg); } break; case NP_GUI_EVT_DISPLAY_MD5_DIALOG: @@ -515,6 +534,42 @@ void NetPlayDialog::OnThread(wxThreadEvent& event) m_MD5_dialog->SetResult(payload.first, payload.second); } break; + case NP_GUI_EVT_PAD_BUFFER_CHANGE: + { + std::string msg = StringFromFormat("Pad buffer: %d", m_pad_buffer); + + if (g_ActiveConfig.bShowNetPlayMessages) + { + OSD::AddTypedMessage(OSD::MessageType::NetPlayBuffer, msg, OSD::Duration::NORMAL); + } + + AddChatMessage(ChatMessageType::Info, msg); + } + break; + case NP_GUI_EVT_DESYNC: + { + std::string msg = "Possible desync detected from player " + m_desync_player + " on frame " + + std::to_string(m_desync_frame); + + AddChatMessage(ChatMessageType::Error, msg); + + if (g_ActiveConfig.bShowNetPlayMessages) + { + OSD::AddMessage(msg, OSD::Duration::VERY_LONG, OSD::Color::RED); + } + } + break; + case NP_GUI_EVT_CONNECTION_LOST: + { + std::string msg = "Lost connection to server"; + AddChatMessage(ChatMessageType::Error, msg); + } + break; + case NP_GUI_EVT_TRAVERSAL_CONNECTION_ERROR: + { + std::string msg = "Traversal server connection error"; + AddChatMessage(ChatMessageType::Error, msg); + } } // chat messages @@ -522,8 +577,12 @@ void NetPlayDialog::OnThread(wxThreadEvent& event) { std::string s; chat_msgs.Pop(s); - // PanicAlert("message: %s", s.c_str()); - m_chat_text->AppendText(StrToWxStr(s).Append('\n')); + AddChatMessage(ChatMessageType::UserIn, s); + + if (g_ActiveConfig.bShowNetPlayMessages) + { + OSD::AddMessage(s, OSD::Duration::NORMAL, OSD::Color::GREEN); + } } } @@ -690,7 +749,7 @@ void NetPlayDialog::UpdateHostLabel() break; case TraversalClient::Failure: m_host_label->SetForegroundColour(*wxBLACK); - m_host_label->SetLabel(FailureReasonStringForHostLabel(g_TraversalClient->m_FailureReason)); + m_host_label->SetLabel("..."); m_host_copy_btn->SetLabel(_("Retry")); m_host_copy_btn->Enable(); m_host_copy_btn_is_retry = true; @@ -724,3 +783,36 @@ void NetPlayDialog::UpdateHostLabel() } } } + +void NetPlayDialog::AddChatMessage(ChatMessageType type, const std::string& msg) +{ + wxColour colour = *wxBLACK; + std::string printed_msg = msg; + + switch (type) + { + case ChatMessageType::Info: + colour = wxColour(0, 150, 150); // cyan + break; + + case ChatMessageType::Error: + colour = *wxRED; + break; + + case ChatMessageType::UserIn: + colour = wxColour(0, 150, 0); // green + printed_msg = "▶ " + msg; + break; + + case ChatMessageType::UserOut: + colour = wxColour(100, 100, 100); // grey + printed_msg = "◀ " + msg; + break; + } + + if (type == ChatMessageType::Info || type == ChatMessageType::Error) + printed_msg = "― " + msg + " ―"; + + m_chat_text->SetDefaultStyle(wxTextAttr(colour)); + m_chat_text->AppendText(StrToWxStr(printed_msg + "\n")); +} diff --git a/Source/Core/DolphinWX/NetPlay/NetWindow.h b/Source/Core/DolphinWX/NetPlay/NetWindow.h index b7fb088b99..b3cb410440 100644 --- a/Source/Core/DolphinWX/NetPlay/NetWindow.h +++ b/Source/Core/DolphinWX/NetPlay/NetWindow.h @@ -31,6 +31,10 @@ enum NP_GUI_EVT_DISPLAY_MD5_DIALOG, NP_GUI_EVT_MD5_PROGRESS, NP_GUI_EVT_MD5_RESULT, + NP_GUI_EVT_PAD_BUFFER_CHANGE, + NP_GUI_EVT_DESYNC, + NP_GUI_EVT_CONNECTION_LOST, + NP_GUI_EVT_TRAVERSAL_CONNECTION_ERROR, }; enum @@ -38,6 +42,18 @@ enum INITIAL_PAD_BUFFER_SIZE = 5 }; +enum class ChatMessageType +{ + // Info messages logged to chat + Info, + // Error messages logged to chat + Error, + // Incoming user chat messages + UserIn, + // Outcoming user chat messages + UserOut, +}; + // IDs are UI-dependent here enum class MD5Target { @@ -72,6 +88,10 @@ public: void OnMsgChangeGame(const std::string& filename) override; void OnMsgStartGame() override; void OnMsgStopGame() override; + void OnPadBufferChanged(u32 buffer) override; + void OnDesync(u32 frame, const std::string& player) override; + void OnConnectionLost() override; + void OnTraversalError(int error) override; static NetPlayDialog*& GetInstance() { return npd; } static NetPlayClient*& GetNetPlayClient() { return netplay_client; } @@ -92,7 +112,8 @@ private: void OnPlayerSelect(wxCommandEvent& event); void GetNetSettings(NetSettings& settings); std::string FindCurrentGame(); - std::string FindGame(const std::string& game) override; + std::string FindGame(const std::string& game); + void AddChatMessage(ChatMessageType type, const std::string& msg); void OnCopyIP(wxCommandEvent&); void OnChoice(wxCommandEvent& event); @@ -116,6 +137,9 @@ private: MD5Dialog* m_MD5_dialog = nullptr; bool m_host_copy_btn_is_retry; bool m_is_hosting; + u32 m_pad_buffer; + u32 m_desync_frame; + std::string m_desync_player; std::vector m_playerids; diff --git a/Source/Core/DolphinWX/VideoConfigDiag.cpp b/Source/Core/DolphinWX/VideoConfigDiag.cpp index 9378d6fd7d..86d37f52ed 100644 --- a/Source/Core/DolphinWX/VideoConfigDiag.cpp +++ b/Source/Core/DolphinWX/VideoConfigDiag.cpp @@ -201,13 +201,20 @@ static wxString disable_fog_desc = "detail.\nDisabling fog will break some games which rely on proper fog " "emulation.\n\nIf unsure, leave this unchecked."); static wxString show_fps_desc = - wxTRANSLATE("Show the number of frames rendered per second as a measure of emulation " - "speed.\n\nIf unsure, leave this unchecked."); -static wxString log_render_time_to_file_desc = wxTRANSLATE( - "Log the render time of every frame to User/Logs/render_time.txt. Use this feature when you " - "want to measure the performance of Dolphin.\n\nIf unsure, leave this unchecked."); + wxTRANSLATE("Show the number of frames rendered per second as a measure of " + "emulation speed.\n\nIf unsure, leave this unchecked."); +static wxString show_netplay_ping_desc = + wxTRANSLATE("Show the players' maximum Ping while playing on " + "NetPlay.\n\nIf unsure, leave this unchecked."); +static wxString log_render_time_to_file_desc = + wxTRANSLATE("Log the render time of every frame to User/Logs/render_time.txt. Use this " + "feature when you want to measure the performance of Dolphin.\n\nIf " + "unsure, leave this unchecked."); static wxString show_stats_desc = wxTRANSLATE("Show various rendering statistics.\n\nIf unsure, leave this unchecked."); +static wxString show_netplay_messages_desc = + wxTRANSLATE("When playing on NetPlay, show chat messages, buffer changes and " + "desync alerts.\n\nIf unsure, leave this unchecked."); static wxString texfmt_desc = wxTRANSLATE("Modify textures to show the format they're encoded in. Needs an emulation reset " "in most cases.\n\nIf unsure, leave this unchecked."); @@ -442,12 +449,18 @@ VideoConfigDiag::VideoConfigDiag(wxWindow* parent, const std::string& title) { szr_other->Add(CreateCheckBox(page_general, _("Show FPS"), wxGetTranslation(show_fps_desc), vconfig.bShowFPS)); + szr_other->Add(CreateCheckBox(page_general, _("Show NetPlay Ping"), + wxGetTranslation(show_netplay_ping_desc), + vconfig.bShowNetPlayPing)); szr_other->Add(CreateCheckBox(page_general, _("Log Render Time to File"), wxGetTranslation(log_render_time_to_file_desc), vconfig.bLogRenderTimeToFile)); szr_other->Add(CreateCheckBox(page_general, _("Auto adjust Window Size"), wxGetTranslation(auto_window_size_desc), SConfig::GetInstance().bRenderWindowAutoSize)); + szr_other->Add(CreateCheckBox(page_general, _("Show NetPlay Messages"), + wxGetTranslation(show_netplay_messages_desc), + vconfig.bShowNetPlayMessages)); szr_other->Add(CreateCheckBox(page_general, _("Keep Window on Top"), wxGetTranslation(keep_window_on_top_desc), SConfig::GetInstance().bKeepWindowOnTop)); diff --git a/Source/Core/VideoCommon/OnScreenDisplay.cpp b/Source/Core/VideoCommon/OnScreenDisplay.cpp index 3cc2e823d8..62997cd150 100644 --- a/Source/Core/VideoCommon/OnScreenDisplay.cpp +++ b/Source/Core/VideoCommon/OnScreenDisplay.cpp @@ -17,21 +17,30 @@ namespace OSD { -struct Message -{ - Message() {} - Message(const std::string& s, u32 ts, u32 rgba) : m_str(s), m_timestamp(ts), m_rgba(rgba) {} - std::string m_str; - u32 m_timestamp; - u32 m_rgba; -}; - static std::multimap s_callbacks; -static std::list s_msgList; +static std::multimap s_messages; +static std::mutex s_messages_mutex; -void AddMessage(const std::string& str, u32 ms, u32 rgba) +void AddTypedMessage(MessageType type, const std::string& message, u32 ms, u32 rgba) { - s_msgList.emplace_back(str, Common::Timer::GetTimeMs() + ms, rgba); + std::lock_guard lock(s_messages_mutex); + s_messages.erase(type); + s_messages.emplace(type, Message(message, Common::Timer::GetTimeMs() + ms, rgba)); +} + +void AddMessage(const std::string& message, u32 ms, u32 rgba) +{ + std::lock_guard lock(s_messages_mutex); + s_messages.emplace(MessageType::Typeless, + Message(message, Common::Timer::GetTimeMs() + ms, rgba)); +} + +void DrawMessage(const Message& msg, int top, int left, int time_left) +{ + float alpha = std::min(1.0f, std::max(0.0f, time_left / 1024.0f)); + u32 color = (msg.m_rgba & 0xFFFFFF) | ((u32)((msg.m_rgba >> 24) * alpha) << 24); + + g_renderer->RenderText(msg.m_str, left, top, color); } void DrawMessages() @@ -39,28 +48,32 @@ void DrawMessages() if (!SConfig::GetInstance().bOnScreenDisplayMessages) return; - int left = 25, top = 15; - auto it = s_msgList.begin(); - while (it != s_msgList.end()) { - int time_left = (int)(it->m_timestamp - Common::Timer::GetTimeMs()); - float alpha = std::max(1.0f, std::min(0.0f, time_left / 1024.0f)); - u32 color = (it->m_rgba & 0xFFFFFF) | ((u32)((it->m_rgba >> 24) * alpha) << 24); + std::lock_guard lock(s_messages_mutex); - g_renderer->RenderText(it->m_str, left, top, color); + u32 now = Common::Timer::GetTimeMs(); + int left = 20, top = 35; - top += 15; + auto it = s_messages.begin(); + while (it != s_messages.end()) + { + const Message& msg = it->second; + int time_left = (int)(msg.m_timestamp - now); + DrawMessage(msg, top, left, time_left); - if (time_left <= 0) - it = s_msgList.erase(it); - else - ++it; + if (time_left <= 0) + it = s_messages.erase(it); + else + ++it; + top += 15; + } } } void ClearMessages() { - s_msgList.clear(); + std::lock_guard lock(s_messages_mutex); + s_messages.clear(); } // On-Screen Display Callbacks diff --git a/Source/Core/VideoCommon/OnScreenDisplay.h b/Source/Core/VideoCommon/OnScreenDisplay.h index ab74201bca..a05f4058ef 100644 --- a/Source/Core/VideoCommon/OnScreenDisplay.h +++ b/Source/Core/VideoCommon/OnScreenDisplay.h @@ -11,9 +11,47 @@ namespace OSD { +struct Message +{ + Message() {} + Message(const std::string& s, u32 ts, u32 rgba) : m_str(s), m_timestamp(ts), m_rgba(rgba) {} + std::string m_str; + u32 m_timestamp; + u32 m_rgba; +}; + +enum class MessageType +{ + NetPlayPing, + NetPlayBuffer, + + // This entry must be kept last so that persistent typed messages are + // displayed before other messages + Typeless, +}; + +namespace Color +{ +constexpr u32 CYAN = 0xFF00FFFF; +constexpr u32 GREEN = 0xFF00FF00; +constexpr u32 RED = 0xFFFF0000; +constexpr u32 YELLOW = 0xFFFFFF30; +}; + +namespace Duration +{ +constexpr u32 SHORT = 2000; +constexpr u32 NORMAL = 5000; +constexpr u32 VERY_LONG = 10000; +}; + // On-screen message display (colored yellow by default) -void AddMessage(const std::string& str, u32 ms = 2000, u32 rgba = 0xFFFFFF30); -void DrawMessages(); // draw the current messages on the screen. Only call once per frame. +void AddMessage(const std::string& message, u32 ms = Duration::SHORT, u32 rgba = Color::YELLOW); +void AddTypedMessage(MessageType type, const std::string& message, u32 ms = Duration::SHORT, + u32 rgba = Color::YELLOW); +void DrawMessage(const Message& msg, int top, int left, int time_left); // draw one message +void DrawMessages(); // draw the current messages on the screen. Only call once + // per frame. void ClearMessages(); // On-screen callbacks diff --git a/Source/Core/VideoCommon/VideoConfig.cpp b/Source/Core/VideoCommon/VideoConfig.cpp index c19528d7d9..7f8def5450 100644 --- a/Source/Core/VideoCommon/VideoConfig.cpp +++ b/Source/Core/VideoCommon/VideoConfig.cpp @@ -60,6 +60,8 @@ void VideoConfig::Load(const std::string& ini_file) settings->Get("UseRealXFB", &bUseRealXFB, 0); settings->Get("SafeTextureCacheColorSamples", &iSafeTextureCache_ColorSamples, 128); settings->Get("ShowFPS", &bShowFPS, false); + settings->Get("ShowNetPlayPing", &bShowNetPlayPing, false); + settings->Get("ShowNetPlayMessages", &bShowNetPlayMessages, false); settings->Get("LogRenderTimeToFile", &bLogRenderTimeToFile, false); settings->Get("OverlayStats", &bOverlayStats, false); settings->Get("OverlayProjStats", &bOverlayProjStats, false); @@ -267,6 +269,8 @@ void VideoConfig::Save(const std::string& ini_file) settings->Set("UseRealXFB", bUseRealXFB); settings->Set("SafeTextureCacheColorSamples", iSafeTextureCache_ColorSamples); settings->Set("ShowFPS", bShowFPS); + settings->Set("ShowNetPlayPing", bShowNetPlayPing); + settings->Set("ShowNetPlayMessages", bShowNetPlayMessages); settings->Set("LogRenderTimeToFile", bLogRenderTimeToFile); settings->Set("OverlayStats", bOverlayStats); settings->Set("OverlayProjStats", bOverlayProjStats); diff --git a/Source/Core/VideoCommon/VideoConfig.h b/Source/Core/VideoCommon/VideoConfig.h index d78c63ba90..9df952a18d 100644 --- a/Source/Core/VideoCommon/VideoConfig.h +++ b/Source/Core/VideoCommon/VideoConfig.h @@ -82,6 +82,8 @@ struct VideoConfig final // Information bool bShowFPS; + bool bShowNetPlayPing; + bool bShowNetPlayMessages; bool bOverlayStats; bool bOverlayProjStats; bool bTexFmtOverlayEnable; From 2ba4b22e88109d0278c9f91a01b87554fbf99975 Mon Sep 17 00:00:00 2001 From: JDV Date: Tue, 12 Jul 2016 12:31:04 -0600 Subject: [PATCH 2/3] Standardizes constant use --- Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp index 73bdb92bb2..60cb5b437a 100644 --- a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp +++ b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp @@ -91,11 +91,11 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl netplay_section.Get("TraversalChoice", &travChoice, "direct"); if (travChoice == "traversal") { - m_direct_traversal->Select(1); + m_direct_traversal->Select(TRAVERSAL_CHOICE); } else { - m_direct_traversal->Select(0); + m_direct_traversal->Select(DIRECT_CHOICE); } std::string centralPort; @@ -361,7 +361,7 @@ void NetPlaySetupFrame::DoHost() bool trav; unsigned long listen_port = 0; - if (m_direct_traversal->GetCurrentSelection() == 1) + if (m_direct_traversal->GetCurrentSelection() == TRAVERSAL_CHOICE) { trav = true; listen_port = From 67eb8143206e3f8d64d2ad7e63290a95cf3f1e84 Mon Sep 17 00:00:00 2001 From: JDV Date: Tue, 12 Jul 2016 12:31:35 -0600 Subject: [PATCH 3/3] Fixes focus not being set on gamelist after tab change Once a tab is selected the focus can be set directly from the page changed event. However once the tab was shown, the first tab order control element was given the focus by default. This was fixed by delaying the action and setting the focus after the default focus had been assigned. --- .../DolphinWX/NetPlay/NetPlaySetupFrame.cpp | 20 +++++++++++++++++++ .../DolphinWX/NetPlay/NetPlaySetupFrame.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp index 60cb5b437a..a1126429d6 100644 --- a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp +++ b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.cpp @@ -239,6 +239,9 @@ NetPlaySetupFrame::NetPlaySetupFrame(wxWindow* const parent, const CGameListCtrl panel->SetSizerAndFit(main_szr); + // Handle focus on tab changes + panel->Bind(wxEVT_NOTEBOOK_PAGE_CHANGED, &NetPlaySetupFrame::OnTabChanged, this); + // wxBoxSizer* const diag_szr = new wxBoxSizer(wxVERTICAL); // diag_szr->Add(panel, 1, wxEXPAND); // SetSizerAndFit(diag_szr); @@ -529,6 +532,23 @@ void NetPlaySetupFrame::OnKeyDown(wxKeyEvent& event) } } +void NetPlaySetupFrame::OnTabChanged(wxCommandEvent& event) +{ + // Propagate event + event.Skip(); + + // Delaying action so the first tab order element doesn't override the focus + m_notebook->Bind(wxEVT_IDLE, &NetPlaySetupFrame::OnAfterTabChange, this); +} + +void NetPlaySetupFrame::OnAfterTabChange(wxIdleEvent&) +{ + // Unbinding so we don't hog the idle event + m_notebook->Unbind(wxEVT_IDLE, &NetPlaySetupFrame::OnAfterTabChange, this); + + DispatchFocus(); +} + void NetPlaySetupFrame::DispatchFocus() { int current_tab = m_notebook->GetSelection(); diff --git a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h index 24acc21b79..87fc1468a9 100644 --- a/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h +++ b/Source/Core/DolphinWX/NetPlay/NetPlaySetupFrame.h @@ -38,6 +38,8 @@ private: void OnResetTraversal(wxCommandEvent& event); void OnTraversalListenPortChanged(wxCommandEvent& event); void OnKeyDown(wxKeyEvent& event); + void OnTabChanged(wxCommandEvent& event); + void OnAfterTabChange(wxIdleEvent& event); void DispatchFocus(); void MakeNetPlayDiag(int port, const std::string& game, bool is_hosting);