reimplement status indicator in member list

This commit is contained in:
ouwou 2023-11-17 01:24:57 -05:00
parent 8e02e7c870
commit 3d2f5abce4
8 changed files with 56 additions and 160 deletions

View File

@ -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:

View File

@ -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<Gdk::RGBA> CellRendererMemberList::property_color() {
return m_property_color.get_proxy();
}
Glib::PropertyProxy<PresenceStatus> 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<Cairo::Context> &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::RefPtr<Cairo::Cont
m_renderer_text.render(cr, widget, background_area, text_cell_area, flags);
m_renderer_text.property_foreground_set().set_value(false);
// Status indicator
// TODO: reintroduce custom status colors... somehow
cr->begin_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);

View File

@ -1,5 +1,6 @@
#pragma once
#include <gtkmm/cellrenderer.h>
#include "discord/activity.hpp"
enum class MemberListRenderType : uint8_t {
Role,
@ -16,6 +17,7 @@ public:
Glib::PropertyProxy<Glib::ustring> property_name();
Glib::PropertyProxy<Glib::RefPtr<Gdk::Pixbuf>> property_pixbuf();
Glib::PropertyProxy<Gdk::RGBA> property_color();
Glib::PropertyProxy<PresenceStatus> property_status();
protected:
void get_preferred_width_vfunc(Gtk::Widget &widget, int &minimum_width, int &natural_width) const override;
@ -56,6 +58,7 @@ private:
Glib::Property<Glib::ustring> m_property_name;
Glib::Property<Glib::RefPtr<Gdk::Pixbuf>> m_property_pixbuf;
Glib::Property<Gdk::RGBA> m_property_color;
Glib::Property<PresenceStatus> m_property_status;
using type_signal_render = sigc::signal<void(uint64_t)>;
type_signal_render m_signal_render;

View File

@ -1,6 +1,5 @@
#include "channels.hpp"
#include "imgmanager.hpp"
#include "statusindicator.hpp"
#include <algorithm>
#include <map>
#include <unordered_map>

View File

@ -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);
}

View File

@ -26,6 +26,8 @@ private:
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 @@ private:
Gtk::TreeModelColumn<Glib::ustring> m_name;
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf>> m_pixbuf;
Gtk::TreeModelColumn<Gdk::RGBA> m_color;
Gtk::TreeModelColumn<PresenceStatus> m_status;
Gtk::TreeModelColumn<int> m_sort;
Gtk::TreeModelColumn<bool> m_av_requested;

View File

@ -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<PresenceStatus>(-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<Cairo::Context> &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;
}

View File

@ -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<Cairo::Context> &cr) override;
Glib::RefPtr<Gdk::Window> m_window;
void CheckStatus();
Snowflake m_id;
PresenceStatus m_status;
};