1
0
mirror of https://github.com/RPCS3/rpcs3.git synced 2024-11-22 02:32:36 +01:00

overlays: add friends list to home menu

This commit is contained in:
Megamouse 2024-10-01 01:44:39 +02:00
parent 73fe420d09
commit 0a57c459b6
17 changed files with 858 additions and 29 deletions

View File

@ -512,6 +512,7 @@ target_sources(rpcs3_emu PRIVATE
RSX/NV47/HW/nv308a.cpp RSX/NV47/HW/nv308a.cpp
RSX/NV47/HW/nv406e.cpp RSX/NV47/HW/nv406e.cpp
RSX/NV47/HW/nv4097.cpp RSX/NV47/HW/nv4097.cpp
RSX/Overlays/FriendsList/overlay_friends_list_dialog.cpp
RSX/Overlays/HomeMenu/overlay_home_menu.cpp RSX/Overlays/HomeMenu/overlay_home_menu.cpp
RSX/Overlays/HomeMenu/overlay_home_menu_components.cpp RSX/Overlays/HomeMenu/overlay_home_menu_components.cpp
RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp

View File

@ -768,7 +768,7 @@ namespace np
if (g_cfg.net.psn_status >= np_psn_status::psn_fake) if (g_cfg.net.psn_status >= np_psn_status::psn_fake)
{ {
g_cfg_rpcn.load(); // Ensures config is loaded even if rpcn is not running for simulated g_cfg_rpcn.load(); // Ensures config is loaded even if rpcn is not running for simulated
std::string s_npid = g_cfg_rpcn.get_npid(); const std::string s_npid = g_cfg_rpcn.get_npid();
ensure(!s_npid.empty()); // It should have been generated before this ensure(!s_npid.empty()); // It should have been generated before this
string_to_npid(s_npid, npid); string_to_npid(s_npid, npid);

View File

@ -75,7 +75,7 @@ namespace rpcn
} }
} }
void overlay_friend_callback(void* param, rpcn::NotificationType ntype, const std::string& username, bool status) void overlay_friend_callback(void* /*param*/, rpcn::NotificationType ntype, const std::string& username, bool status)
{ {
if (!g_cfg.misc.show_rpcn_popups) if (!g_cfg.misc.show_rpcn_popups)
return; return;
@ -88,6 +88,7 @@ namespace rpcn
case rpcn::NotificationType::FriendNew: loc_id = localized_string_id::RPCN_FRIEND_ADDED; break; case rpcn::NotificationType::FriendNew: loc_id = localized_string_id::RPCN_FRIEND_ADDED; break;
case rpcn::NotificationType::FriendLost: loc_id = localized_string_id::RPCN_FRIEND_LOST; break; case rpcn::NotificationType::FriendLost: loc_id = localized_string_id::RPCN_FRIEND_LOST; break;
case rpcn::NotificationType::FriendStatus: loc_id = status ? localized_string_id::RPCN_FRIEND_LOGGED_IN : localized_string_id::RPCN_FRIEND_LOGGED_OUT; break; case rpcn::NotificationType::FriendStatus: loc_id = status ? localized_string_id::RPCN_FRIEND_LOGGED_IN : localized_string_id::RPCN_FRIEND_LOGGED_OUT; break;
case rpcn::NotificationType::FriendPresenceChanged: return;
default: rpcn_log.fatal("An unhandled notification type was received by the overlay friend callback!"); break; default: rpcn_log.fatal("An unhandled notification type was received by the overlay friend callback!"); break;
} }
@ -2572,6 +2573,12 @@ namespace rpcn
return state; return state;
} }
void rpcn_client::get_friends(friend_data& friend_infos)
{
std::lock_guard lock(mutex_friends);
friend_infos = this->friend_infos;
}
void rpcn_client::get_friends_and_register_cb(friend_data& friend_infos, friend_cb_func cb_func, void* cb_param) void rpcn_client::get_friends_and_register_cb(friend_data& friend_infos, friend_cb_func cb_func, void* cb_param)
{ {
std::lock_guard lock(mutex_friends); std::lock_guard lock(mutex_friends);
@ -2619,7 +2626,7 @@ namespace rpcn
{ {
case NotificationType::FriendQuery: // Other user sent a friend request case NotificationType::FriendQuery: // Other user sent a friend request
{ {
std::string username = vdata.get_string(false); const std::string username = vdata.get_string(false);
if (vdata.is_error()) if (vdata.is_error())
{ {
rpcn_log.error("Error parsing FriendQuery notification"); rpcn_log.error("Error parsing FriendQuery notification");
@ -2632,8 +2639,8 @@ namespace rpcn
} }
case NotificationType::FriendNew: // Add a friend to the friendlist(either accepted a friend request or friend accepted it) case NotificationType::FriendNew: // Add a friend to the friendlist(either accepted a friend request or friend accepted it)
{ {
bool online = !!vdata.get<u8>(); const bool online = !!vdata.get<u8>();
std::string username = vdata.get_string(false); const std::string username = vdata.get_string(false);
if (vdata.is_error()) if (vdata.is_error())
{ {
rpcn_log.error("Error parsing FriendNew notification"); rpcn_log.error("Error parsing FriendNew notification");
@ -2649,22 +2656,24 @@ namespace rpcn
} }
case NotificationType::FriendLost: // Remove friend from the friendlist(user removed friend or friend removed friend) case NotificationType::FriendLost: // Remove friend from the friendlist(user removed friend or friend removed friend)
{ {
std::string username = vdata.get_string(false); const std::string username = vdata.get_string(false);
if (vdata.is_error()) if (vdata.is_error())
{ {
rpcn_log.error("Error parsing FriendLost notification"); rpcn_log.error("Error parsing FriendLost notification");
break; break;
} }
friend_infos.requests_received.erase(username);
friend_infos.requests_sent.erase(username);
friend_infos.friends.erase(username); friend_infos.friends.erase(username);
call_callbacks(ntype, username, false); call_callbacks(ntype, username, false);
break; break;
} }
case NotificationType::FriendStatus: // Set status of friend to Offline or Online case NotificationType::FriendStatus: // Set status of friend to Offline or Online
{ {
bool online = !!vdata.get<u8>(); const bool online = !!vdata.get<u8>();
u64 timestamp = vdata.get<u64>(); const u64 timestamp = vdata.get<u64>();
std::string username = vdata.get_string(false); const std::string username = vdata.get_string(false);
if (vdata.is_error()) if (vdata.is_error())
{ {
rpcn_log.error("Error parsing FriendStatus notification"); rpcn_log.error("Error parsing FriendStatus notification");
@ -2692,7 +2701,7 @@ namespace rpcn
} }
case NotificationType::FriendPresenceChanged: case NotificationType::FriendPresenceChanged:
{ {
std::string npid = vdata.get_string(true); const std::string username = vdata.get_string(true);
SceNpCommunicationId pr_com_id = vdata.get_com_id(); SceNpCommunicationId pr_com_id = vdata.get_com_id();
std::string pr_title = fmt::truncate(vdata.get_string(true), SCE_NP_BASIC_PRESENCE_TITLE_SIZE_MAX - 1); std::string pr_title = fmt::truncate(vdata.get_string(true), SCE_NP_BASIC_PRESENCE_TITLE_SIZE_MAX - 1);
std::string pr_status = fmt::truncate(vdata.get_string(true), SCE_NP_BASIC_PRESENCE_EXTENDED_STATUS_SIZE_MAX - 1); std::string pr_status = fmt::truncate(vdata.get_string(true), SCE_NP_BASIC_PRESENCE_EXTENDED_STATUS_SIZE_MAX - 1);
@ -2709,7 +2718,7 @@ namespace rpcn
break; break;
} }
if (auto u = friend_infos.friends.find(npid); u != friend_infos.friends.end()) if (auto u = friend_infos.friends.find(username); u != friend_infos.friends.end())
{ {
u->second.pr_com_id = std::move(pr_com_id); u->second.pr_com_id = std::move(pr_com_id);
u->second.pr_title = std::move(pr_title); u->second.pr_title = std::move(pr_title);
@ -2718,9 +2727,10 @@ namespace rpcn
u->second.pr_data = std::move(pr_data); u->second.pr_data = std::move(pr_data);
std::lock_guard lock(mutex_presence_updates); std::lock_guard lock(mutex_presence_updates);
presence_updates.insert_or_assign(std::move(npid), u->second); presence_updates.insert_or_assign(username, u->second);
} }
call_callbacks(ntype, username, false);
break; break;
} }
default: default:

View File

@ -439,6 +439,7 @@ namespace rpcn
rpcn_state wait_for_authentified(); rpcn_state wait_for_authentified();
bool terminate_connection(); bool terminate_connection();
void get_friends(friend_data& friend_infos);
void get_friends_and_register_cb(friend_data& friend_infos, friend_cb_func cb_func, void* cb_param); void get_friends_and_register_cb(friend_data& friend_infos, friend_cb_func cb_func, void* cb_param);
void register_friend_cb(friend_cb_func, void* cb_param); void register_friend_cb(friend_cb_func, void* cb_param);
void remove_friend_cb(friend_cb_func, void* cb_param); void remove_friend_cb(friend_cb_func, void* cb_param);

View File

@ -0,0 +1,679 @@
#include "stdafx.h"
#include "../overlay_manager.h"
#include "overlay_friends_list_dialog.h"
#include "Emu/System.h"
#include "Emu/NP/rpcn_config.h"
#include "Emu/vfs_config.h"
namespace rsx
{
namespace overlays
{
void friend_callback(void* param, rpcn::NotificationType ntype, const std::string& username, bool status)
{
auto* dlg = static_cast<friends_list_dialog*>(param);
dlg->callback_handler(ntype, username, status);
}
friends_list_dialog::friends_list_entry::friends_list_entry(friends_list_dialog_page page, const std::string& username, const rpcn::friend_online_data& data)
{
std::unique_ptr<overlay_element> image = std::make_unique<image_view>();
image->set_size(160, 110);
image->set_padding(36, 36, 11, 11); // Square image, 88x88
std::string avatar_path = g_cfg_vfs.get_dev_flash() + "vsh/resource/explore/user/";
std::string text;
switch (page)
{
case friends_list_dialog_page::friends:
{
avatar_path += data.online ? "013.png" : "009.png";
text = get_localized_string(data.online ? localized_string_id::HOME_MENU_FRIENDS_STATUS_ONLINE : localized_string_id::HOME_MENU_FRIENDS_STATUS_OFFLINE);
if (data.online)
{
if (!data.pr_title.empty())
{
fmt::append(text, " - %s", data.pr_title);
}
if (!data.pr_status.empty())
{
fmt::append(text, " - %s", data.pr_status);
}
}
break;
}
case friends_list_dialog_page::invites:
{
// We use "online" to show whether an invite was sent or received
avatar_path += data.online ? "012.png" : "011.png";
text = get_localized_string(data.online ? localized_string_id::HOME_MENU_FRIENDS_REQUEST_RECEIVED : localized_string_id::HOME_MENU_FRIENDS_REQUEST_SENT);
break;
}
case friends_list_dialog_page::blocked:
{
avatar_path += "010.png";
text = get_localized_string(localized_string_id::HOME_MENU_FRIENDS_STATUS_BLOCKED);
break;
}
}
if (fs::exists(avatar_path))
{
icon_data = std::make_unique<image_info>(avatar_path.c_str());
static_cast<image_view*>(image.get())->set_raw_image(icon_data.get());
}
else
{
// Fallback
// TODO: use proper icon
static_cast<image_view*>(image.get())->set_image_resource(resource_config::standard_image_resource::square);
}
std::unique_ptr<overlay_element> text_stack = std::make_unique<vertical_layout>();
std::unique_ptr<overlay_element> padding = std::make_unique<spacer>();
std::unique_ptr<overlay_element> header_text = std::make_unique<label>(username);
std::unique_ptr<overlay_element> subtext = std::make_unique<label>(text);
padding->set_size(1, 1);
header_text->set_size(800, 40);
header_text->set_font("Arial", 16);
header_text->set_wrap_text(true);
subtext->set_size(800, 0);
subtext->set_font("Arial", 14);
subtext->set_wrap_text(true);
static_cast<label*>(subtext.get())->auto_resize(true);
// Make back color transparent for text
header_text->back_color.a = 0.f;
subtext->back_color.a = 0.f;
static_cast<vertical_layout*>(text_stack.get())->pack_padding = 5;
static_cast<vertical_layout*>(text_stack.get())->add_element(padding);
static_cast<vertical_layout*>(text_stack.get())->add_element(header_text);
static_cast<vertical_layout*>(text_stack.get())->add_element(subtext);
if (text_stack->h > image->h)
{
std::unique_ptr<overlay_element> padding2 = std::make_unique<spacer>();
padding2->set_size(1, 5);
static_cast<vertical_layout*>(text_stack.get())->add_element(padding2);
}
// Pack
this->pack_padding = 15;
add_element(image);
add_element(text_stack);
}
friends_list_dialog::friends_list_dialog()
: m_page_btn(120, 30)
, m_extra_btn(120, 30)
{
m_dim_background = std::make_unique<overlay_element>();
m_dim_background->set_size(virtual_width, virtual_height);
m_dim_background->back_color.a = 0.5f;
m_list = std::make_unique<list_view>(virtual_width - 2 * 20, 540);
m_list->set_pos(20, 85);
m_message_box = std::make_shared<home_menu_message_box>(20, 85, virtual_width - 2 * 20, 540);
m_description = std::make_unique<label>();
m_description->set_font("Arial", 20);
m_description->set_pos(20, 37);
m_description->set_text("Select user"); // Fallback. I don't think this will ever be used, so I won't localize it.
m_description->auto_resize();
m_description->back_color.a = 0.f;
fade_animation.duration_sec = 0.15f;
return_code = selection_code::canceled;
m_extra_btn.set_image_resource(resource_config::standard_image_resource::triangle);
m_extra_btn.set_pos(330, m_list->y + m_list->h + 20);
m_extra_btn.set_text("");
m_extra_btn.set_font("Arial", 16);
m_page_btn.set_image_resource(resource_config::standard_image_resource::square);
m_page_btn.set_pos(m_list->x + m_list->w - (30 + 120), m_list->y + m_list->h + 20);
m_page_btn.set_text(get_localized_string(localized_string_id::HOME_MENU_FRIENDS_NEXT_LIST));
m_page_btn.set_font("Arial", 16);
}
void friends_list_dialog::update(u64 timestamp_us)
{
if (fade_animation.active)
{
fade_animation.update(timestamp_us);
}
}
void friends_list_dialog::on_button_pressed(pad_button button_press, bool is_auto_repeat)
{
if (fade_animation.active) return;
if (m_message_box && m_message_box->visible())
{
const page_navigation navigation = m_message_box->handle_button_press(button_press);
if (navigation != page_navigation::stay)
{
m_message_box->hide();
refresh();
}
return;
}
std::lock_guard lock(m_list_mutex);
bool close_dialog = false;
switch (button_press)
{
case pad_button::cross:
case pad_button::triangle:
{
if (!m_list || m_list->m_items.empty())
break;
if (button_press == pad_button::triangle && m_current_page != friends_list_dialog_page::invites)
break;
const usz index = static_cast<usz>(m_list->get_selected_index());
switch (m_current_page)
{
case friends_list_dialog_page::friends:
{
// Get selected user
usz user_index = 0;
std::string selected_username;
for (const auto& [username, data] : m_friend_data.friends)
{
if (data.online && user_index++ == index)
{
selected_username = username;
break;
}
}
for (const auto& [username, data] : m_friend_data.friends)
{
if (!selected_username.empty())
break;
if (!data.online && user_index++ == index)
{
selected_username = username;
break;
}
}
if (!selected_username.empty() && m_message_box && !m_message_box->visible())
{
m_message_box->show(get_localized_string(localized_string_id::HOME_MENU_FRIENDS_REMOVE_USER_MSG, selected_username.c_str()), [this, selected_username]()
{
m_rpcn->remove_friend(selected_username);
});
refresh();
}
break;
}
case friends_list_dialog_page::invites:
{
// Get selected user
usz user_index = 0;
std::string selected_username;
for (const std::string& username : m_friend_data.requests_received)
{
if (user_index == index)
{
selected_username = username;
break;
}
user_index++;
}
for (const std::string& username : m_friend_data.requests_sent)
{
if (!selected_username.empty())
break;
if (user_index == index)
{
selected_username = username;
break;
}
user_index++;
}
if (!selected_username.empty() && m_message_box && !m_message_box->visible())
{
if (user_index < m_friend_data.requests_received.size())
{
if (button_press == pad_button::triangle)
{
m_message_box->show(get_localized_string(localized_string_id::HOME_MENU_FRIENDS_REJECT_REQUEST_MSG, selected_username.c_str()), [this, selected_username]()
{
m_rpcn->remove_friend(selected_username);
});
}
else
{
m_message_box->show(get_localized_string(localized_string_id::HOME_MENU_FRIENDS_ACCEPT_REQUEST_MSG, selected_username.c_str()), [this, selected_username]()
{
m_rpcn->add_friend(selected_username);
});
}
}
else
{
m_message_box->show(get_localized_string(localized_string_id::HOME_MENU_FRIENDS_CANCEL_REQUEST_MSG, selected_username.c_str()), [this, selected_username]()
{
m_rpcn->remove_friend(selected_username);
});
}
refresh();
}
break;
}
case friends_list_dialog_page::blocked:
{
// Get selected user
usz user_index = 0;
std::string selected_username;
for (const std::string& username : m_friend_data.blocked)
{
if (user_index++ == index)
{
selected_username = username;
break;
}
}
if (!selected_username.empty() && m_message_box && !m_message_box->visible())
{
m_message_box->show(get_localized_string(localized_string_id::HOME_MENU_FRIENDS_UNBLOCK_USER_MSG, selected_username.c_str()), []()
{
// TODO
});
refresh();
}
break;
}
}
Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_decide.wav");
return;
}
case pad_button::circle:
Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cancel.wav");
close_dialog = true;
break;
case pad_button::square:
switch (m_current_page)
{
case friends_list_dialog_page::friends: m_current_page = friends_list_dialog_page::invites; break;
case friends_list_dialog_page::invites: m_current_page = friends_list_dialog_page::blocked; break;
case friends_list_dialog_page::blocked: m_current_page = friends_list_dialog_page::friends; break;
}
m_list_dirty = true;
break;
case pad_button::dpad_up:
case pad_button::ls_up:
if (m_list)
m_list->select_previous();
break;
case pad_button::dpad_down:
case pad_button::ls_down:
if (m_list)
m_list->select_next();
break;
case pad_button::L1:
if (m_list)
m_list->select_previous(10);
break;
case pad_button::R1:
if (m_list)
m_list->select_next(10);
break;
default:
rsx_log.trace("[ui] Button %d pressed", static_cast<u8>(button_press));
break;
}
if (close_dialog)
{
fade_animation.current = color4f(1.f);
fade_animation.end = color4f(0.f);
fade_animation.active = true;
fade_animation.on_finish = [this]
{
close(true, true);
};
}
// Play a sound unless this is a fast auto repeat which would induce a nasty noise
else if (!is_auto_repeat || m_auto_repeat_ms_interval >= m_auto_repeat_ms_interval_default)
{
Emu.GetCallbacks().play_sound(fs::get_config_dir() + "sounds/snd_cursor.wav");
}
}
compiled_resource friends_list_dialog::get_compiled()
{
if (!visible)
{
return {};
}
compiled_resource result;
result.add(m_dim_background->get_compiled());
{
std::lock_guard lock(m_list_mutex);
if (m_list_dirty.exchange(false))
{
if (m_current_page != m_last_page)
{
localized_string_id title_id = localized_string_id::INVALID;
switch (m_current_page)
{
case friends_list_dialog_page::friends: title_id = localized_string_id::HOME_MENU_FRIENDS; break;
case friends_list_dialog_page::invites: title_id = localized_string_id::HOME_MENU_FRIENDS_REQUESTS; break;
case friends_list_dialog_page::blocked: title_id = localized_string_id::HOME_MENU_FRIENDS_BLOCKED; break;
}
m_description->set_text(get_localized_string(title_id));
m_description->auto_resize();
}
reload();
m_last_page.store(m_current_page);
}
if (m_message_box && m_message_box->visible())
{
result.add(m_message_box->get_compiled());
}
else
{
if (m_list)
{
result.add(m_list->get_compiled());
if (!m_list->m_items.empty() && m_current_page == friends_list_dialog_page::invites)
{
// Get selected user
const usz index = static_cast<usz>(m_list->get_selected_index());
if (index < m_friend_data.requests_received.size())
{
m_extra_btn.set_text(get_localized_string(localized_string_id::HOME_MENU_FRIENDS_REJECT_REQUEST));
result.add(m_extra_btn.get_compiled());
}
}
}
result.add(m_page_btn.get_compiled());
}
}
result.add(m_description->get_compiled());
fade_animation.apply(result);
return result;
}
void friends_list_dialog::callback_handler(rpcn::NotificationType ntype, const std::string& /*username*/, bool /*status*/)
{
switch (ntype)
{
case rpcn::NotificationType::FriendNew: // Add a friend to the friendlist(either accepted a friend request or friend accepted it)
case rpcn::NotificationType::FriendStatus: // Set status of friend to Offline or Online
case rpcn::NotificationType::FriendQuery: // Other user sent a friend request
case rpcn::NotificationType::FriendLost: // Remove friend from the friendlist(user removed friend or friend removed friend)
case rpcn::NotificationType::FriendPresenceChanged:
{
m_list_dirty = true;
break;
}
default:
{
rsx_log.fatal("An unhandled notification type was received by the RPCN friends dialog callback!");
break;
}
}
}
void friends_list_dialog::reload()
{
std::vector<std::unique_ptr<overlay_element>> entries;
std::string selected_user;
s32 selected_index = 0;
// Get selected user name
if (m_list && m_current_page == m_last_page)
{
const s32 old_index = m_list->get_selected_index();
s32 i = 0;
switch (m_current_page)
{
case friends_list_dialog_page::friends:
{
for (const auto& [username, data] : m_friend_data.friends)
{
if (i++ == old_index)
{
selected_user = username;
break;
}
}
break;
}
case friends_list_dialog_page::invites:
{
for (const std::string& username : m_friend_data.requests_received)
{
if (i++ == old_index)
{
selected_user = username;
break;
}
}
for (const std::string& username : m_friend_data.requests_sent)
{
if (!selected_user.empty())
break;
if (i++ == old_index)
{
selected_user = username;
break;
}
}
break;
}
case friends_list_dialog_page::blocked:
{
for (const std::string& username : m_friend_data.blocked)
{
if (i++ == old_index)
{
selected_user = username;
break;
}
}
break;
}
}
}
if (auto res = m_rpcn->wait_for_connection(); res != rpcn::rpcn_state::failure_no_failure)
{
rsx_log.error("Failed to connect to RPCN: %s", rpcn::rpcn_state_to_string(res));
status_flags |= status_bits::invalidate_image_cache;
m_list.reset();
return;
}
if (auto res = m_rpcn->wait_for_authentified(); res != rpcn::rpcn_state::failure_no_failure)
{
rsx_log.error("Failed to authentify to RPCN: %s", rpcn::rpcn_state_to_string(res));
status_flags |= status_bits::invalidate_image_cache;
m_list.reset();
return;
}
// Get friends, setup callback and setup comboboxes
m_rpcn->get_friends(m_friend_data);
switch (m_current_page)
{
case friends_list_dialog_page::friends:
{
// Sort users by online status
std::vector<std::pair<const std::string&, const rpcn::friend_online_data&>> friends_online;
std::vector<std::pair<const std::string&, const rpcn::friend_online_data&>> friends_offline;
for (const auto& [username, data] : m_friend_data.friends)
{
if (data.online)
{
friends_online.push_back({ username, data });
}
else
{
friends_offline.push_back({ username, data });
}
}
// Add users and try to find the old selected user again
for (const auto& [username, data] : friends_online)
{
if (username == selected_user)
{
selected_index = ::size32(entries);
}
std::unique_ptr<overlay_element> entry = std::make_unique<friends_list_entry>(m_current_page, username, data);
entries.emplace_back(std::move(entry));
}
for (const auto& [username, data] : friends_offline)
{
if (username == selected_user)
{
selected_index = ::size32(entries);
}
std::unique_ptr<overlay_element> entry = std::make_unique<friends_list_entry>(m_current_page, username, data);
entries.emplace_back(std::move(entry));
}
break;
}
case friends_list_dialog_page::invites:
{
for (const std::string& username : m_friend_data.requests_received)
{
if (username == selected_user)
{
selected_index = ::size32(entries);
}
std::unique_ptr<overlay_element> entry = std::make_unique<friends_list_entry>(m_current_page, username, rpcn::friend_online_data(true, 0));
entries.emplace_back(std::move(entry));
}
for (const std::string& username : m_friend_data.requests_sent)
{
if (username == selected_user)
{
selected_index = ::size32(entries);
}
std::unique_ptr<overlay_element> entry = std::make_unique<friends_list_entry>(m_current_page, username, rpcn::friend_online_data(false, 0));
entries.emplace_back(std::move(entry));
}
break;
}
case friends_list_dialog_page::blocked:
{
for (const std::string& username : m_friend_data.blocked)
{
if (username == selected_user)
{
selected_index = ::size32(entries);
}
std::unique_ptr<overlay_element> entry = std::make_unique<friends_list_entry>(m_current_page, username, rpcn::friend_online_data(false, 0));
entries.emplace_back(std::move(entry));
}
break;
}
}
// Recreate list
if (m_list)
{
status_flags |= status_bits::invalidate_image_cache;
}
m_list = std::make_unique<list_view>(virtual_width - 2 * 20, 540);
m_list->set_pos(20, 85);
for (auto& entry : entries)
{
m_list->add_entry(entry);
}
if (!m_list->m_items.empty())
{
// Only select an entry if there are entries available
m_list->select_entry(selected_index);
}
}
error_code friends_list_dialog::show(bool enable_overlay, std::function<void(s32 status)> on_close)
{
visible = false;
if (enable_overlay)
{
m_dim_background->back_color.a = 0.9f;
}
else
{
m_dim_background->back_color.a = 0.5f;
}
g_cfg_rpcn.load(); // Ensures config is loaded even if rpcn is not running for simulated
m_rpcn = rpcn::rpcn_client::get_instance();
m_rpcn->register_friend_cb(friend_callback, this);
m_description->set_text(get_localized_string(localized_string_id::HOME_MENU_FRIENDS));
m_description->auto_resize();
fade_animation.current = color4f(0.f);
fade_animation.end = color4f(1.f);
fade_animation.active = true;
this->on_close = std::move(on_close);
visible = true;
const auto notify = std::make_shared<atomic_t<u32>>(0);
auto& overlayman = g_fxo->get<display_manager>();
overlayman.attach_thread_input(
uid, "Friends list dialog",
[notify]() { *notify = true; notify->notify_one(); }
);
while (!Emu.IsStopped() && !*notify)
{
notify->wait(0, atomic_wait_timeout{1'000'000});
}
return CELL_OK;
}
} // namespace overlays
} // namespace RSX

View File

@ -0,0 +1,64 @@
#pragma once
#include "../overlays.h"
#include "../overlay_list_view.hpp"
#include "../HomeMenu/overlay_home_menu_message_box.h"
#include "Emu/Cell/ErrorCodes.h"
#include "Emu/NP/rpcn_client.h"
namespace rsx
{
namespace overlays
{
enum class friends_list_dialog_page
{
friends,
invites,
blocked
};
struct friends_list_dialog : public user_interface
{
private:
struct friends_list_entry : horizontal_layout
{
private:
std::unique_ptr<image_info> icon_data;
public:
friends_list_entry(friends_list_dialog_page page, const std::string& username, const rpcn::friend_online_data& data);
};
std::mutex m_list_mutex;
std::vector<u32> m_entry_ids;
std::unique_ptr<overlay_element> m_dim_background;
std::unique_ptr<list_view> m_list;
std::unique_ptr<label> m_description;
image_button m_page_btn;
image_button m_extra_btn;
std::shared_ptr<home_menu_message_box> m_message_box;
animation_color_interpolate fade_animation;
std::shared_ptr<rpcn::rpcn_client> m_rpcn;
rpcn::friend_data m_friend_data;
atomic_t<bool> m_list_dirty { true };
atomic_t<friends_list_dialog_page> m_current_page { friends_list_dialog_page::friends };
atomic_t<friends_list_dialog_page> m_last_page { friends_list_dialog_page::friends };
void reload();
public:
friends_list_dialog();
void update(u64 timestamp_us) override;
void on_button_pressed(pad_button button_press, bool is_auto_repeat) override;
compiled_resource get_compiled() override;
error_code show(bool enable_overlay, std::function<void(s32 status)> on_close);
void callback_handler(rpcn::NotificationType ntype, const std::string& username, bool status);
};
}
}

View File

@ -1,6 +1,8 @@
#include "stdafx.h" #include "stdafx.h"
#include "overlay_home_menu_main_menu.h" #include "overlay_home_menu_main_menu.h"
#include "overlay_home_menu_components.h" #include "overlay_home_menu_components.h"
#include "Emu/RSX/Overlays/FriendsList/overlay_friends_list_dialog.h"
#include "Emu/RSX/Overlays/overlay_manager.h"
#include "Emu/System.h" #include "Emu/System.h"
#include "Emu/system_config.h" #include "Emu/system_config.h"
@ -31,6 +33,27 @@ namespace rsx
add_page(std::make_shared<home_menu_settings>(x, y, width, height, use_separators, this)); add_page(std::make_shared<home_menu_settings>(x, y, width, height, use_separators, this));
std::unique_ptr<overlay_element> friends = std::make_unique<home_menu_entry>(get_localized_string(localized_string_id::HOME_MENU_FRIENDS));
add_item(friends, [](pad_button btn) -> page_navigation
{
if (btn != pad_button::cross) return page_navigation::stay;
rsx_log.notice("User selected friends in home menu");
Emu.CallFromMainThread([]()
{
if (auto manager = g_fxo->try_get<rsx::overlays::display_manager>())
{
const error_code result = manager->create<rsx::overlays::friends_list_dialog>()->show(true, [](s32 status)
{
rsx_log.notice("Closing friends list with status %d", status);
});
(result ? rsx_log.error : rsx_log.notice)("Opened friends list with result %d", s32{result});
}
});
return page_navigation::stay;
});
std::unique_ptr<overlay_element> screenshot = std::make_unique<home_menu_entry>(get_localized_string(localized_string_id::HOME_MENU_SCREENSHOT)); std::unique_ptr<overlay_element> screenshot = std::make_unique<home_menu_entry>(get_localized_string(localized_string_id::HOME_MENU_SCREENSHOT));
add_item(screenshot, [](pad_button btn) -> page_navigation add_item(screenshot, [](pad_button btn) -> page_navigation
{ {

View File

@ -412,6 +412,7 @@ namespace rsx
switch (ntype) switch (ntype)
{ {
case rpcn::NotificationType::FriendQuery: // Other user sent a friend request case rpcn::NotificationType::FriendQuery: // Other user sent a friend request
case rpcn::NotificationType::FriendPresenceChanged:
break; break;
case rpcn::NotificationType::FriendNew: // Add a friend to the friendlist(either accepted a friend request or friend accepted it) case rpcn::NotificationType::FriendNew: // Add a friend to the friendlist(either accepted a friend request or friend accepted it)
{ {

View File

@ -648,8 +648,8 @@ namespace rsx
void layout_container::set_pos(s16 _x, s16 _y) void layout_container::set_pos(s16 _x, s16 _y)
{ {
s16 dx = _x - x; const s16 dx = _x - x;
s16 dy = _y - y; const s16 dy = _y - y;
translate(dx, dy); translate(dx, dy);
} }

View File

@ -180,6 +180,22 @@ enum class localized_string_id
HOME_MENU_EXIT_GAME, HOME_MENU_EXIT_GAME,
HOME_MENU_RESTART, HOME_MENU_RESTART,
HOME_MENU_RESUME, HOME_MENU_RESUME,
HOME_MENU_FRIENDS,
HOME_MENU_FRIENDS_REQUESTS,
HOME_MENU_FRIENDS_BLOCKED,
HOME_MENU_FRIENDS_STATUS_ONLINE,
HOME_MENU_FRIENDS_STATUS_OFFLINE,
HOME_MENU_FRIENDS_STATUS_BLOCKED,
HOME_MENU_FRIENDS_REQUEST_SENT,
HOME_MENU_FRIENDS_REQUEST_RECEIVED,
HOME_MENU_FRIENDS_BLOCK_USER_MSG,
HOME_MENU_FRIENDS_UNBLOCK_USER_MSG,
HOME_MENU_FRIENDS_REMOVE_USER_MSG,
HOME_MENU_FRIENDS_ACCEPT_REQUEST_MSG,
HOME_MENU_FRIENDS_CANCEL_REQUEST_MSG,
HOME_MENU_FRIENDS_REJECT_REQUEST_MSG,
HOME_MENU_FRIENDS_REJECT_REQUEST,
HOME_MENU_FRIENDS_NEXT_LIST,
HOME_MENU_SETTINGS, HOME_MENU_SETTINGS,
HOME_MENU_SETTINGS_SAVE, HOME_MENU_SETTINGS_SAVE,
HOME_MENU_SETTINGS_SAVE_BUTTON, HOME_MENU_SETTINGS_SAVE_BUTTON,

View File

@ -112,6 +112,7 @@
<ClCompile Include="Emu\RSX\NV47\HW\nv308a.cpp" /> <ClCompile Include="Emu\RSX\NV47\HW\nv308a.cpp" />
<ClCompile Include="Emu\RSX\NV47\HW\nv406e.cpp" /> <ClCompile Include="Emu\RSX\NV47\HW\nv406e.cpp" />
<ClCompile Include="Emu\RSX\NV47\HW\nv4097.cpp" /> <ClCompile Include="Emu\RSX\NV47\HW\nv4097.cpp" />
<ClCompile Include="Emu\RSX\Overlays\FriendsList\overlay_friends_list_dialog.cpp" />
<ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu.cpp" /> <ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu.cpp" />
<ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_components.cpp" /> <ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_components.cpp" />
<ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_message_box.cpp" /> <ClCompile Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_message_box.cpp" />
@ -631,6 +632,7 @@
<ClInclude Include="Emu\RSX\NV47\HW\nv47.h" /> <ClInclude Include="Emu\RSX\NV47\HW\nv47.h" />
<ClInclude Include="Emu\RSX\NV47\HW\common.h" /> <ClInclude Include="Emu\RSX\NV47\HW\common.h" />
<ClInclude Include="Emu\RSX\NV47\HW\nv47_sync.hpp" /> <ClInclude Include="Emu\RSX\NV47\HW\nv47_sync.hpp" />
<ClInclude Include="Emu\RSX\Overlays\FriendsList\overlay_friends_list_dialog.h" />
<ClInclude Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu.h" /> <ClInclude Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu.h" />
<ClInclude Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_components.h" /> <ClInclude Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_components.h" />
<ClInclude Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_message_box.h" /> <ClInclude Include="Emu\RSX\Overlays\HomeMenu\overlay_home_menu_message_box.h" />

View File

@ -112,6 +112,9 @@
<Filter Include="Emu\CPU\Backends\AArch64"> <Filter Include="Emu\CPU\Backends\AArch64">
<UniqueIdentifier>{73650043-acf8-4890-9f84-5bfa7a8c946b}</UniqueIdentifier> <UniqueIdentifier>{73650043-acf8-4890-9f84-5bfa7a8c946b}</UniqueIdentifier>
</Filter> </Filter>
<Filter Include="Emu\GPU\RSX\Overlays\FriendsList">
<UniqueIdentifier>{a9d2ff9f-b5cd-4fd4-afca-424e876aa656}</UniqueIdentifier>
</Filter>
<Filter Include="Emu\GPU\RSX\Decoders"> <Filter Include="Emu\GPU\RSX\Decoders">
<UniqueIdentifier>{40f6640a-ed40-4096-b79b-995ae5be3052}</UniqueIdentifier> <UniqueIdentifier>{40f6640a-ed40-4096-b79b-995ae5be3052}</UniqueIdentifier>
</Filter> </Filter>
@ -1258,6 +1261,9 @@
<ClCompile Include="Emu\NP\rpcn_countries.cpp"> <ClCompile Include="Emu\NP\rpcn_countries.cpp">
<Filter>Emu\NP</Filter> <Filter>Emu\NP</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Emu\RSX\Overlays\FriendsList\overlay_friends_list_dialog.cpp">
<Filter>Emu\GPU\RSX\Overlays\FriendsList</Filter>
</ClCompile>
<ClCompile Include="Emu\RSX\RSXTexture.cpp"> <ClCompile Include="Emu\RSX\RSXTexture.cpp">
<Filter>Emu\GPU\RSX\Decoders</Filter> <Filter>Emu\GPU\RSX\Decoders</Filter>
</ClCompile> </ClCompile>
@ -2557,6 +2563,9 @@
<ClInclude Include="Emu\Cell\Modules\cellSysutilAvc.h"> <ClInclude Include="Emu\Cell\Modules\cellSysutilAvc.h">
<Filter>Emu\Cell\Modules</Filter> <Filter>Emu\Cell\Modules</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="Emu\RSX\Overlays\FriendsList\overlay_friends_list_dialog.h">
<Filter>Emu\GPU\RSX\Overlays\FriendsList</Filter>
</ClInclude>
<ClInclude Include="Emu\RSX\RSXTexture.h"> <ClInclude Include="Emu\RSX\RSXTexture.h">
<Filter>Emu\GPU\RSX\Decoders</Filter> <Filter>Emu\GPU\RSX\Decoders</Filter>
</ClInclude> </ClInclude>

View File

@ -129,6 +129,7 @@ EmuCallbacks main_application::CreateCallbacks()
case keyboard_handler::basic: case keyboard_handler::basic:
{ {
basic_keyboard_handler* ret = g_fxo->init<KeyboardHandlerBase, basic_keyboard_handler>(Emu.DeserialManager()); basic_keyboard_handler* ret = g_fxo->init<KeyboardHandlerBase, basic_keyboard_handler>(Emu.DeserialManager());
ensure(ret);
ret->moveToThread(get_thread()); ret->moveToThread(get_thread());
ret->SetTargetWindow(m_game_window); ret->SetTargetWindow(m_game_window);
break; break;
@ -165,6 +166,7 @@ EmuCallbacks main_application::CreateCallbacks()
case mouse_handler::basic: case mouse_handler::basic:
{ {
basic_mouse_handler* ret = g_fxo->init<MouseHandlerBase, basic_mouse_handler>(Emu.DeserialManager()); basic_mouse_handler* ret = g_fxo->init<MouseHandlerBase, basic_mouse_handler>(Emu.DeserialManager());
ensure(ret);
ret->moveToThread(get_thread()); ret->moveToThread(get_thread());
ret->SetTargetWindow(m_game_window); ret->SetTargetWindow(m_game_window);
break; break;

View File

@ -200,6 +200,22 @@ private:
case localized_string_id::HOME_MENU_TITLE: return tr("Home Menu"); case localized_string_id::HOME_MENU_TITLE: return tr("Home Menu");
case localized_string_id::HOME_MENU_EXIT_GAME: return tr("Exit Game"); case localized_string_id::HOME_MENU_EXIT_GAME: return tr("Exit Game");
case localized_string_id::HOME_MENU_RESUME: return tr("Resume Game"); case localized_string_id::HOME_MENU_RESUME: return tr("Resume Game");
case localized_string_id::HOME_MENU_FRIENDS: return tr("Friends");
case localized_string_id::HOME_MENU_FRIENDS_REQUESTS: return tr("Pending Friend Requests");
case localized_string_id::HOME_MENU_FRIENDS_BLOCKED: return tr("Blocked Users");
case localized_string_id::HOME_MENU_FRIENDS_STATUS_ONLINE: return tr("Online");
case localized_string_id::HOME_MENU_FRIENDS_STATUS_OFFLINE: return tr("Offline");
case localized_string_id::HOME_MENU_FRIENDS_STATUS_BLOCKED: return tr("Blocked");
case localized_string_id::HOME_MENU_FRIENDS_REQUEST_SENT: return tr("You sent a friend request");
case localized_string_id::HOME_MENU_FRIENDS_REQUEST_RECEIVED: return tr("Sent you a friend request");
case localized_string_id::HOME_MENU_FRIENDS_BLOCK_USER_MSG: return tr("Block this user?\n\n%0").arg(std::forward<Args>(args)...);
case localized_string_id::HOME_MENU_FRIENDS_UNBLOCK_USER_MSG: return tr("Unblock this user?\n\n%0").arg(std::forward<Args>(args)...);
case localized_string_id::HOME_MENU_FRIENDS_REMOVE_USER_MSG: return tr("Remove this user?\n\n%0").arg(std::forward<Args>(args)...);
case localized_string_id::HOME_MENU_FRIENDS_ACCEPT_REQUEST_MSG: return tr("Accept Request?\n\n%0").arg(std::forward<Args>(args)...);
case localized_string_id::HOME_MENU_FRIENDS_CANCEL_REQUEST_MSG: return tr("Cancel Request?\n\n%0").arg(std::forward<Args>(args)...);
case localized_string_id::HOME_MENU_FRIENDS_REJECT_REQUEST_MSG: return tr("Reject Request?\n\n%0").arg(std::forward<Args>(args)...);
case localized_string_id::HOME_MENU_FRIENDS_REJECT_REQUEST: return tr("Reject Request");
case localized_string_id::HOME_MENU_FRIENDS_NEXT_LIST: return tr("Next list");
case localized_string_id::HOME_MENU_RESTART: return tr("Restart Game"); case localized_string_id::HOME_MENU_RESTART: return tr("Restart Game");
case localized_string_id::HOME_MENU_SETTINGS: return tr("Settings"); case localized_string_id::HOME_MENU_SETTINGS: return tr("Settings");
case localized_string_id::HOME_MENU_SETTINGS_SAVE: return tr("Save custom configuration?"); case localized_string_id::HOME_MENU_SETTINGS_SAVE: return tr("Save custom configuration?");

View File

@ -1054,9 +1054,9 @@ rpcn_friends_dialog::rpcn_friends_dialog(QWidget* parent)
rpcn::friend_data data; rpcn::friend_data data;
m_rpcn->get_friends_and_register_cb(data, friend_callback, this); m_rpcn->get_friends_and_register_cb(data, friend_callback, this);
for (const auto& fr : data.friends) for (const auto& [username, data] : data.friends)
{ {
add_update_list(m_lst_friends, QString::fromStdString(fr.first), fr.second.online ? m_icon_online : m_icon_offline, fr.second.online); add_update_list(m_lst_friends, QString::fromStdString(username), data.online ? m_icon_online : m_icon_offline, data.online);
} }
for (const auto& fr_req : data.requests_sent) for (const auto& fr_req : data.requests_sent)
@ -1297,27 +1297,27 @@ void rpcn_friends_dialog::remove_list(QListWidget* list, const QString& name)
} }
} }
void rpcn_friends_dialog::add_update_friend(QString name, bool status) void rpcn_friends_dialog::add_update_friend(const QString& name, bool status)
{ {
add_update_list(m_lst_friends, name, status ? m_icon_online : m_icon_offline, status); add_update_list(m_lst_friends, name, status ? m_icon_online : m_icon_offline, status);
remove_list(m_lst_requests, name); remove_list(m_lst_requests, name);
} }
void rpcn_friends_dialog::remove_friend(QString name) void rpcn_friends_dialog::remove_friend(const QString& name)
{ {
remove_list(m_lst_friends, name); remove_list(m_lst_friends, name);
remove_list(m_lst_requests, name); remove_list(m_lst_requests, name);
} }
void rpcn_friends_dialog::add_query(QString name) void rpcn_friends_dialog::add_query(const QString& name)
{ {
add_update_list(m_lst_requests, name, m_icon_request_received, QVariant(true)); add_update_list(m_lst_requests, name, m_icon_request_received, QVariant(true));
remove_list(m_lst_history, name); remove_list(m_lst_history, name);
} }
void rpcn_friends_dialog::callback_handler(rpcn::NotificationType ntype, std::string username, bool status) void rpcn_friends_dialog::callback_handler(rpcn::NotificationType ntype, const std::string& username, bool status)
{ {
QString qtr_username = QString::fromStdString(username); const QString qtr_username = QString::fromStdString(username);
switch (ntype) switch (ntype)
{ {
case rpcn::NotificationType::FriendQuery: // Other user sent a friend request case rpcn::NotificationType::FriendQuery: // Other user sent a friend request
@ -1340,6 +1340,10 @@ void rpcn_friends_dialog::callback_handler(rpcn::NotificationType ntype, std::st
Q_EMIT signal_add_update_friend(qtr_username, status); Q_EMIT signal_add_update_friend(qtr_username, status);
break; break;
} }
case rpcn::NotificationType::FriendPresenceChanged:
{
break;
}
default: default:
{ {
rpcn_settings_log.fatal("An unhandled notification type was received by the RPCN friends dialog callback!"); rpcn_settings_log.fatal("An unhandled notification type was received by the RPCN friends dialog callback!");

View File

@ -106,7 +106,7 @@ class rpcn_friends_dialog : public QDialog
public: public:
rpcn_friends_dialog(QWidget* parent = nullptr); rpcn_friends_dialog(QWidget* parent = nullptr);
~rpcn_friends_dialog(); ~rpcn_friends_dialog();
void callback_handler(rpcn::NotificationType ntype, std::string username, bool status); void callback_handler(rpcn::NotificationType ntype, const std::string& username, bool status);
bool is_ok() const; bool is_ok() const;
private: private:
@ -114,14 +114,14 @@ private:
void remove_list(QListWidget* list, const QString& name); void remove_list(QListWidget* list, const QString& name);
private Q_SLOTS: private Q_SLOTS:
void add_update_friend(QString name, bool status); void add_update_friend(const QString& name, bool status);
void remove_friend(QString name); void remove_friend(const QString& name);
void add_query(QString name); void add_query(const QString& name);
Q_SIGNALS: Q_SIGNALS:
void signal_add_update_friend(QString name, bool status); void signal_add_update_friend(const QString& name, bool status);
void signal_remove_friend(QString name); void signal_remove_friend(const QString& name);
void signal_add_query(QString name); void signal_add_query(const QString& name);
private: private:
QIcon m_icon_online; QIcon m_icon_online;

View File

@ -161,6 +161,7 @@ void sendmessage_dialog_frame::callback_handler(u16 ntype, const std::string& us
switch (ntype) switch (ntype)
{ {
case rpcn::NotificationType::FriendQuery: // Other user sent a friend request case rpcn::NotificationType::FriendQuery: // Other user sent a friend request
case rpcn::NotificationType::FriendPresenceChanged:
break; break;
case rpcn::NotificationType::FriendNew: // Add a friend to the friendlist(either accepted a friend request or friend accepted it) case rpcn::NotificationType::FriendNew: // Add a friend to the friendlist(either accepted a friend request or friend accepted it)
{ {