diff --git a/abaddon.hpp b/abaddon.hpp index 6bcb385..c8c4351 100644 --- a/abaddon.hpp +++ b/abaddon.hpp @@ -84,13 +84,13 @@ public: void ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_id); + void ManageHeapWindow(Gtk::Window *window); + protected: void ShowGuildVerificationGateDialog(Snowflake guild_id); void SetupUserMenu(); - void ManageHeapWindow(Gtk::Window *window); - Snowflake m_shown_user_menu_id; Snowflake m_shown_user_menu_guild_id; diff --git a/components/friendslist.cpp b/components/friendslist.cpp new file mode 100644 index 0000000..35f2291 --- /dev/null +++ b/components/friendslist.cpp @@ -0,0 +1,173 @@ +#include "friendslist.hpp" +#include "../abaddon.hpp" + +FriendsList::FriendsList() + : Gtk::Box(Gtk::ORIENTATION_VERTICAL) + , m_filter_mode(FILTER_FRIENDS) { + for (const auto &[id, type] : Abaddon::Get().GetDiscordClient().GetRelationships()) { + const auto user = Abaddon::Get().GetDiscordClient().GetUser(id); + if (!user.has_value()) continue; + auto *row = Gtk::manage(new FriendsListFriendRow(type, *user)); + m_list.add(*row); + row->show(); + } + + constexpr static std::array strs = { + "Friends", + "Online", + "Pending", + "Blocked", + }; + for (const auto &x : strs) { + auto *btn = Gtk::manage(new Gtk::RadioButton(m_group, x)); + m_buttons.add(*btn); + btn->show(); + btn->signal_toggled().connect([this, btn, str = x] { + if (!btn->get_active()) return; + switch (str[0]) { // hehe + case 'F': + m_filter_mode = FILTER_FRIENDS; + break; + case 'O': + m_filter_mode = FILTER_ONLINE; + break; + case 'P': + m_filter_mode = FILTER_PENDING; + break; + case 'B': + m_filter_mode = FILTER_BLOCKED; + break; + } + m_list.invalidate_filter(); + }); + } + m_buttons.set_homogeneous(true); + m_buttons.set_halign(Gtk::ALIGN_CENTER); + + m_add.set_halign(Gtk::ALIGN_CENTER); + m_add.set_margin_top(5); + m_add.set_margin_bottom(5); + + m_scroll.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + + m_list.set_sort_func(sigc::mem_fun(*this, &FriendsList::ListSortFunc)); + m_list.set_filter_func(sigc::mem_fun(*this, &FriendsList::ListFilterFunc)); + m_list.set_selection_mode(Gtk::SELECTION_NONE); + m_list.set_hexpand(true); + m_list.set_vexpand(true); + m_scroll.add(m_list); + add(m_add); + add(m_buttons); + add(m_scroll); + + m_add.show(); + m_scroll.show(); + m_buttons.show(); + m_list.show(); +} + +int FriendsList::ListSortFunc(Gtk::ListBoxRow *a_, Gtk::ListBoxRow *b_) { + auto *a = dynamic_cast(a_); + auto *b = dynamic_cast(b_); + if (a == nullptr || b == nullptr) return 0; + return a->Name.compare(b->Name); +} + +bool FriendsList::ListFilterFunc(Gtk::ListBoxRow *row_) { + auto *row = dynamic_cast(row_); + if (row == nullptr) return false; + switch (m_filter_mode) { + case FILTER_FRIENDS: + return row->Type == RelationshipType::Friend; + case FILTER_ONLINE: + return false; // blah + case FILTER_PENDING: + return row->Type == RelationshipType::PendingIncoming || row->Type == RelationshipType::PendingOutgoing; + case FILTER_BLOCKED: + return row->Type == RelationshipType::Blocked; + default: + return false; + } +} + +FriendsListAddComponent::FriendsListAddComponent() + : Gtk::Box(Gtk::ORIENTATION_VERTICAL) + , m_label("Add a Friend", Gtk::ALIGN_START) + , m_box(Gtk::ORIENTATION_HORIZONTAL) + , m_add("Add") + , m_status("Failed to whatever lol", Gtk::ALIGN_START) { + m_box.add(m_entry); + m_box.add(m_add); + m_box.add(m_status); + + m_label.set_halign(Gtk::ALIGN_CENTER); + + m_entry.set_placeholder_text("Enter a Username#1234"); + + add(m_label); + add(m_box); + + show_all_children(); +} + +FriendsListFriendRow::FriendsListFriendRow(RelationshipType type, const UserData &data) + : Name(data.Username + "#" + data.Discriminator) + , Type(type) + , ID(data.ID) { + auto *ev = Gtk::manage(new Gtk::EventBox); + auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); + auto *img = Gtk::manage(new Gtk::Image(Abaddon::Get().GetImageManager().GetPlaceholder(32))); + auto *namelbl = Gtk::manage(new Gtk::Label(Name, Gtk::ALIGN_START)); + const auto status = Abaddon::Get().GetDiscordClient().GetUserStatus(data.ID); + auto *statuslbl = Gtk::manage(new Gtk::Label(GetPresenceDisplayString(status), Gtk::ALIGN_START)); + auto *lblbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + + AddWidgetMenuHandler(ev, m_menu, [this] { + switch (Type) { + case RelationshipType::Blocked: + case RelationshipType::Friend: + m_remove.set_label("Remove"); + break; + case RelationshipType::PendingIncoming: + m_remove.set_label("Ignore"); + break; + case RelationshipType::PendingOutgoing: + m_remove.set_label("Cancel"); + break; + default: + break; + } + }); + + m_remove.signal_activate().connect([this] { + m_signal_remove.emit(); + }); + m_menu.append(m_remove); + m_menu.show_all(); + + lblbox->set_valign(Gtk::ALIGN_CENTER); + + img->set_margin_end(5); + + lblbox->add(*namelbl); + lblbox->add(*statuslbl); + + box->add(*img); + box->add(*lblbox); + + ev->add(*box); + add(*ev); + show_all_children(); +} + +FriendsListFriendRow::type_signal_remove FriendsListFriendRow::signal_action_remove() { + return m_signal_remove; +} + +FriendsListWindow::FriendsListWindow() { + add(m_friends); + set_default_size(500, 500); + get_style_context()->add_class("app-window"); + get_style_context()->add_class("app-popup"); + m_friends.show(); +} diff --git a/components/friendslist.hpp b/components/friendslist.hpp new file mode 100644 index 0000000..8d05c1e --- /dev/null +++ b/components/friendslist.hpp @@ -0,0 +1,66 @@ +#pragma once +#include +#include "../discord/relationship.hpp" + +class FriendsListAddComponent : public Gtk::Box { +public: + FriendsListAddComponent(); + +private: + Gtk::Label m_label; + Gtk::Label m_status; + Gtk::Entry m_entry; + Gtk::Button m_add; + Gtk::Box m_box; +}; + +class FriendsList : public Gtk::Box { +public: + FriendsList(); + +private: + enum FilterMode { + FILTER_FRIENDS, + FILTER_ONLINE, + FILTER_PENDING, + FILTER_BLOCKED, + }; + + FilterMode m_filter_mode; + + int ListSortFunc(Gtk::ListBoxRow *a, Gtk::ListBoxRow *b); + bool ListFilterFunc(Gtk::ListBoxRow *row); + + FriendsListAddComponent m_add; + Gtk::RadioButtonGroup m_group; + Gtk::ButtonBox m_buttons; + Gtk::ScrolledWindow m_scroll; + Gtk::ListBox m_list; +}; + +class FriendsListFriendRow : public Gtk::ListBoxRow { +public: + FriendsListFriendRow(RelationshipType type, const UserData &str); + + Snowflake ID; + RelationshipType Type; + Glib::ustring Name; + +private: + Gtk::Menu m_menu; + Gtk::MenuItem m_remove; // or cancel or ignore + + using type_signal_remove = sigc::signal; + type_signal_remove m_signal_remove; + +public: + type_signal_remove signal_action_remove(); +}; + +class FriendsListWindow : public Gtk::Window { +public: + FriendsListWindow(); + +private: + FriendsList m_friends; +}; diff --git a/discord/activity.hpp b/discord/activity.hpp index b50d176..76ba9cd 100644 --- a/discord/activity.hpp +++ b/discord/activity.hpp @@ -26,6 +26,20 @@ constexpr inline const char *GetPresenceString(PresenceStatus s) { return ""; } +constexpr inline const char* GetPresenceDisplayString(PresenceStatus s) { + switch (s) { + case PresenceStatus::Online: + return "Online"; + case PresenceStatus::Offline: + return "Offline"; + case PresenceStatus::Idle: + return "Away"; + case PresenceStatus::DND: + return "Do Not Disturb"; + } + return ""; +} + enum class ActivityType : int { Game = 0, Streaming = 1, diff --git a/discord/discord.cpp b/discord/discord.cpp index c85ad6e..e25b138 100644 --- a/discord/discord.cpp +++ b/discord/discord.cpp @@ -850,6 +850,10 @@ PresenceStatus DiscordClient::GetUserStatus(Snowflake id) const { return PresenceStatus::Offline; } +std::unordered_map DiscordClient::GetRelationships() const { + return m_user_relationships; +} + std::unordered_set DiscordClient::GetRelationships(RelationshipType type) const { std::unordered_set ret; for (const auto &[id, rtype] : m_user_relationships) diff --git a/discord/discord.hpp b/discord/discord.hpp index 2983ddf..e3e43a1 100644 --- a/discord/discord.hpp +++ b/discord/discord.hpp @@ -178,6 +178,7 @@ public: PresenceStatus GetUserStatus(Snowflake id) const; + std::unordered_map GetRelationships() const; std::unordered_set GetRelationships(RelationshipType type) const; private: diff --git a/windows/mainwindow.cpp b/windows/mainwindow.cpp index b6d3ca5..778e52a 100644 --- a/windows/mainwindow.cpp +++ b/windows/mainwindow.cpp @@ -1,5 +1,6 @@ #include "mainwindow.hpp" #include "../abaddon.hpp" +#include "../components/friendslist.hpp" MainWindow::MainWindow() : m_main_box(Gtk::ORIENTATION_VERTICAL) @@ -39,8 +40,14 @@ MainWindow::MainWindow() m_menu_file_sub.append(m_menu_file_reload_css); m_menu_file_sub.append(m_menu_file_clear_cache); + m_menu_view.set_label("View"); + m_menu_view.set_submenu(m_menu_view_sub); + m_menu_view_friends.set_label("Friends"); + m_menu_view_sub.append(m_menu_view_friends); + m_menu_bar.append(m_menu_file); m_menu_bar.append(m_menu_discord); + m_menu_bar.append(m_menu_view); m_menu_bar.show_all(); m_menu_discord_connect.signal_activate().connect([this] { @@ -79,6 +86,13 @@ MainWindow::MainWindow() m_signal_action_add_recipient.emit(GetChatActiveChannel()); }); + m_menu_view_friends.signal_activate().connect([this] { + auto *window = new FriendsListWindow; + window->set_position(Gtk::WIN_POS_CENTER); + window->show(); + Abaddon::Get().ManageHeapWindow(window); + }); + m_content_box.set_hexpand(true); m_content_box.set_vexpand(true); m_content_box.show(); diff --git a/windows/mainwindow.hpp b/windows/mainwindow.hpp index 0be4bc0..261d23b 100644 --- a/windows/mainwindow.hpp +++ b/windows/mainwindow.hpp @@ -93,4 +93,8 @@ protected: Gtk::MenuItem m_menu_file_reload_settings; Gtk::MenuItem m_menu_file_reload_css; Gtk::MenuItem m_menu_file_clear_cache; + + Gtk::MenuItem m_menu_view; + Gtk::Menu m_menu_view_sub; + Gtk::MenuItem m_menu_view_friends; };