From 3d2f5abce4c591e35d64d428cc1df06407128211 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 17 Nov 2023 01:24:57 -0500 Subject: [PATCH] reimplement status indicator in member list --- README.md | 5 - src/components/cellrenderermemberlist.cpp | 34 +++++- src/components/cellrenderermemberlist.hpp | 3 + src/components/channels.cpp | 1 - src/components/memberlist.cpp | 19 +++- src/components/memberlist.hpp | 3 + src/components/statusindicator.cpp | 122 ---------------------- src/components/statusindicator.hpp | 29 ----- 8 files changed, 56 insertions(+), 160 deletions(-) delete mode 100644 src/components/statusindicator.cpp delete mode 100644 src/components/statusindicator.hpp diff --git a/README.md b/README.md index 0038f1fa..89e7d3dc 100644 --- a/README.md +++ b/README.md @@ -199,11 +199,6 @@ spam filter's wrath: | `.embed-field-value` | The value of an embed field | | `.embed-footer` | The footer of an embed | | `.member-list` | Container of the member list | -| `.status-indicator` | The status indicator | -| `.online` | Applied to status indicators when the associated user is online | -| `.idle` | Applied to status indicators when the associated user is away | -| `.dnd` | Applied to status indicators when the associated user is on do not disturb | -| `.offline` | Applied to status indicators when the associated user is offline | | `.typing-indicator` | The typing indicator (also used for replies) | Used in reorderable list implementation: diff --git a/src/components/cellrenderermemberlist.cpp b/src/components/cellrenderermemberlist.cpp index 66b223e2..ee1ea3ea 100644 --- a/src/components/cellrenderermemberlist.cpp +++ b/src/components/cellrenderermemberlist.cpp @@ -6,7 +6,8 @@ CellRendererMemberList::CellRendererMemberList() , m_property_id(*this, "id") , m_property_name(*this, "name") , m_property_pixbuf(*this, "pixbuf") - , m_property_color(*this, "color") { + , m_property_color(*this, "color") + , m_property_status(*this, "status") { property_mode() = Gtk::CELL_RENDERER_MODE_ACTIVATABLE; property_xpad() = 2; property_ypad() = 2; @@ -35,6 +36,10 @@ Glib::PropertyProxy CellRendererMemberList::property_color() { return m_property_color.get_proxy(); } +Glib::PropertyProxy CellRendererMemberList::property_status() { + return m_property_status.get_proxy(); +} + void CellRendererMemberList::get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const { switch (m_property_type.get_value()) { case MemberListRenderType::Role: @@ -117,8 +122,9 @@ void CellRendererMemberList::get_preferred_height_for_width_vfunc_member(Gtk::Wi } void CellRendererMemberList::render_vfunc_member(const Cairo::RefPtr &cr, Gtk::Widget &widget, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area, Gtk::CellRendererState flags) { + // Text Gdk::Rectangle text_cell_area = cell_area; - text_cell_area.set_x(22); + text_cell_area.set_x(31); const auto color = m_property_color.get_value(); if (color.get_alpha_u() > 0) { m_renderer_text.property_foreground_rgba().set_value(color); @@ -126,6 +132,30 @@ void CellRendererMemberList::render_vfunc_member(const Cairo::RefPtrbegin_new_path(); + switch (m_property_status.get_value()) { + case PresenceStatus::Online: + cr->set_source_rgb(33.0 / 255.0, 157.0 / 255.0, 86.0 / 255.0); + break; + case PresenceStatus::Idle: + cr->set_source_rgb(230.0 / 255.0, 170.0 / 255.0, 48.0 / 255.0); + break; + case PresenceStatus::DND: + cr->set_source_rgb(233.0 / 255.0, 61.0 / 255.0, 65.0 / 255.0); + break; + case PresenceStatus::Offline: + cr->set_source_rgb(122.0 / 255.0, 126.0 / 255.0, 135.0 / 255.0); + break; + } + + cr->arc(background_area.get_x() + 6.0 + 16.0 + 6.0, background_area.get_y() + background_area.get_height() / 2.0, 2.0, 0.0, 2 * (4 * std::atan(1))); + cr->close_path(); + cr->fill_preserve(); + cr->stroke(); + + // Icon const double icon_x = background_area.get_x() + 6.0; const double icon_y = background_area.get_y() + background_area.get_height() / 2.0 - 8.0; Gdk::Cairo::set_source_pixbuf(cr, m_property_pixbuf.get_value(), icon_x, icon_y); diff --git a/src/components/cellrenderermemberlist.hpp b/src/components/cellrenderermemberlist.hpp index 7a49ccf0..79d32e32 100644 --- a/src/components/cellrenderermemberlist.hpp +++ b/src/components/cellrenderermemberlist.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include "discord/activity.hpp" enum class MemberListRenderType : uint8_t { Role, @@ -16,6 +17,7 @@ class CellRendererMemberList : public Gtk::CellRenderer { Glib::PropertyProxy property_name(); Glib::PropertyProxy> property_pixbuf(); Glib::PropertyProxy property_color(); + Glib::PropertyProxy property_status(); protected: void get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const override; @@ -56,6 +58,7 @@ class CellRendererMemberList : public Gtk::CellRenderer { Glib::Property m_property_name; Glib::Property> m_property_pixbuf; Glib::Property m_property_color; + Glib::Property m_property_status; using type_signal_render = sigc::signal; type_signal_render m_signal_render; diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 9fd4abd7..6ed3ca46 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -1,6 +1,5 @@ #include "channels.hpp" #include "imgmanager.hpp" -#include "statusindicator.hpp" #include #include #include diff --git a/src/components/memberlist.cpp b/src/components/memberlist.cpp index b082daa2..7046b526 100644 --- a/src/components/memberlist.cpp +++ b/src/components/memberlist.cpp @@ -28,6 +28,7 @@ MemberList::MemberList() column->add_attribute(renderer->property_name(), m_columns.m_name); column->add_attribute(renderer->property_pixbuf(), m_columns.m_pixbuf); column->add_attribute(renderer->property_color(), m_columns.m_color); + column->add_attribute(renderer->property_status(), m_columns.m_status); m_view.append_column(*column); m_model->set_sort_column(m_columns.m_sort, Gtk::SORT_ASCENDING); @@ -44,6 +45,8 @@ MemberList::MemberList() m_menu_role_copy_id.signal_activate().connect([this]() { Gtk::Clipboard::get()->set_text(std::to_string((*m_model->get_iter(m_path_for_menu))[m_columns.m_id])); }); + + Abaddon::Get().GetDiscordClient().signal_presence_update().connect(sigc::mem_fun(*this, &MemberList::OnPresenceUpdate)); } Gtk::Widget *MemberList::GetRoot() { @@ -73,6 +76,7 @@ void MemberList::UpdateMemberList() { row[m_columns.m_color] = color_transparent; row[m_columns.m_av_requested] = false; row[m_columns.m_pixbuf] = Abaddon::Get().GetImageManager().GetPlaceholder(16); + row[m_columns.m_status] = Abaddon::Get().GetDiscordClient().GetUserStatus(user.ID); m_pending_avatars[user.ID] = row_iter; } } @@ -126,6 +130,7 @@ void MemberList::UpdateMemberList() { row[m_columns.m_id] = user.ID; row[m_columns.m_name] = user.GetDisplayNameEscaped(); row[m_columns.m_pixbuf] = Abaddon::Get().GetImageManager().GetPlaceholder(16); + row[m_columns.m_status] = Abaddon::Get().GetDiscordClient().GetUserStatus(user.ID); row[m_columns.m_av_requested] = false; if (const auto iter = user_to_color.find(user.ID); iter != user_to_color.end()) { row[m_columns.m_color] = IntToRGBA(iter->second); @@ -241,12 +246,24 @@ int MemberList::SortFunc(const Gtk::TreeModel::iterator &a, const Gtk::TreeModel return 0; } +void MemberList::OnPresenceUpdate(const UserData &user, PresenceStatus status) { + for (auto &role : m_model->children()) { + for (auto &member : role.children()) { + if ((*member)[m_columns.m_id] == user.ID) { + (*member)[m_columns.m_status] = status; + return; + } + } + } +} + MemberList::ModelColumns::ModelColumns() { add(m_type); add(m_id); add(m_name); add(m_pixbuf); - add(m_av_requested); add(m_color); + add(m_status); add(m_sort); + add(m_av_requested); } diff --git a/src/components/memberlist.hpp b/src/components/memberlist.hpp index cb6c1916..1c4aaf49 100644 --- a/src/components/memberlist.hpp +++ b/src/components/memberlist.hpp @@ -26,6 +26,8 @@ class MemberList { int SortFunc(const Gtk::TreeModel::iterator &a, const Gtk::TreeModel::iterator &b); + void OnPresenceUpdate(const UserData &user, PresenceStatus status); + class ModelColumns : public Gtk::TreeModel::ColumnRecord { public: ModelColumns(); @@ -35,6 +37,7 @@ class MemberList { Gtk::TreeModelColumn m_name; Gtk::TreeModelColumn> m_pixbuf; Gtk::TreeModelColumn m_color; + Gtk::TreeModelColumn m_status; Gtk::TreeModelColumn m_sort; Gtk::TreeModelColumn m_av_requested; diff --git a/src/components/statusindicator.cpp b/src/components/statusindicator.cpp deleted file mode 100644 index 47e8b443..00000000 --- a/src/components/statusindicator.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "statusindicator.hpp" - -static const constexpr int Diameter = 8; - -StatusIndicator::StatusIndicator(Snowflake user_id) - : Glib::ObjectBase("statusindicator") - , Gtk::Widget() - , m_id(user_id) - , m_status(static_cast(-1)) { - set_has_window(true); - set_name("status-indicator"); - - get_style_context()->add_class("status-indicator"); - - Abaddon::Get().GetDiscordClient().signal_guild_member_list_update().connect(sigc::hide(sigc::mem_fun(*this, &StatusIndicator::CheckStatus))); - auto cb = [this](const UserData &user, PresenceStatus status) { - if (user.ID == m_id) CheckStatus(); - }; - Abaddon::Get().GetDiscordClient().signal_presence_update().connect(sigc::track_obj(cb, *this)); - - CheckStatus(); -} - -void StatusIndicator::CheckStatus() { - const auto status = Abaddon::Get().GetDiscordClient().GetUserStatus(m_id); - const auto last_status = m_status; - get_style_context()->remove_class("online"); - get_style_context()->remove_class("dnd"); - get_style_context()->remove_class("idle"); - get_style_context()->remove_class("offline"); - get_style_context()->add_class(GetPresenceString(status)); - m_status = status; - - if (last_status != m_status) - queue_draw(); -} - -Gtk::SizeRequestMode StatusIndicator::get_request_mode_vfunc() const { - return Gtk::Widget::get_request_mode_vfunc(); -} - -void StatusIndicator::get_preferred_width_vfunc(int &minimum_width, int &natural_width) const { - minimum_width = 0; - natural_width = Diameter; -} - -void StatusIndicator::get_preferred_height_for_width_vfunc(int width, int &minimum_height, int &natural_height) const { - minimum_height = 0; - natural_height = Diameter; -} - -void StatusIndicator::get_preferred_height_vfunc(int &minimum_height, int &natural_height) const { - minimum_height = 0; - natural_height = Diameter; -} - -void StatusIndicator::get_preferred_width_for_height_vfunc(int height, int &minimum_width, int &natural_width) const { - minimum_width = 0; - natural_width = Diameter; -} - -void StatusIndicator::on_size_allocate(Gtk::Allocation &allocation) { - set_allocation(allocation); - - if (m_window) - m_window->move_resize(allocation.get_x(), allocation.get_y(), allocation.get_width(), allocation.get_height()); -} - -void StatusIndicator::on_map() { - Gtk::Widget::on_map(); -} - -void StatusIndicator::on_unmap() { - Gtk::Widget::on_unmap(); -} - -void StatusIndicator::on_realize() { - set_realized(true); - - if (!m_window) { - GdkWindowAttr attributes; - std::memset(&attributes, 0, sizeof(attributes)); - - auto allocation = get_allocation(); - - attributes.x = allocation.get_x(); - attributes.y = allocation.get_y(); - attributes.width = allocation.get_width(); - attributes.height = allocation.get_height(); - - attributes.event_mask = get_events() | Gdk::EXPOSURE_MASK; - attributes.window_type = GDK_WINDOW_CHILD; - attributes.wclass = GDK_INPUT_OUTPUT; - - m_window = Gdk::Window::create(get_parent_window(), &attributes, GDK_WA_X | GDK_WA_Y); - set_window(m_window); - - m_window->set_user_data(gobj()); - } -} - -void StatusIndicator::on_unrealize() { - m_window.reset(); - - Gtk::Widget::on_unrealize(); -} - -bool StatusIndicator::on_draw(const Cairo::RefPtr &cr) { - const auto allocation = get_allocation(); - const auto width = allocation.get_width(); - const auto height = allocation.get_height(); - - const auto color = get_style_context()->get_color(Gtk::STATE_FLAG_NORMAL); - - cr->set_source_rgb(color.get_red(), color.get_green(), color.get_blue()); - cr->arc(width / 2.0, height / 2.0, width / 3.0, 0.0, 2 * (4 * std::atan(1))); - cr->close_path(); - cr->fill_preserve(); - cr->stroke(); - - return true; -} diff --git a/src/components/statusindicator.hpp b/src/components/statusindicator.hpp deleted file mode 100644 index edd64ea4..00000000 --- a/src/components/statusindicator.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once -#include "discord/snowflake.hpp" -#include "discord/activity.hpp" - -class StatusIndicator : public Gtk::Widget { -public: - StatusIndicator(Snowflake user_id); - ~StatusIndicator() override = default; - -protected: - Gtk::SizeRequestMode get_request_mode_vfunc() const override; - void get_preferred_width_vfunc(int &minimum_width, int &natural_width) const override; - void get_preferred_width_for_height_vfunc(int height, int &minimum_width, int &natural_width) const override; - void get_preferred_height_vfunc(int &minimum_height, int &natural_height) const override; - void get_preferred_height_for_width_vfunc(int width, int &minimum_height, int &natural_height) const override; - void on_size_allocate(Gtk::Allocation &allocation) override; - void on_map() override; - void on_unmap() override; - void on_realize() override; - void on_unrealize() override; - bool on_draw(const Cairo::RefPtr &cr) override; - - Glib::RefPtr m_window; - - void CheckStatus(); - - Snowflake m_id; - PresenceStatus m_status; -};