diff options
author | ouwou <26526779+ouwou@users.noreply.github.com> | 2021-01-11 18:27:46 -0500 |
---|---|---|
committer | ouwou <26526779+ouwou@users.noreply.github.com> | 2021-01-11 18:27:46 -0500 |
commit | e8cbb9d3d1ecca25f1e0a31a75fac70c7a3ea0cb (patch) | |
tree | ecb21c74221f320422ff2390c940b921d02331f2 /components | |
parent | def598941a74d6960985171ef5f446bdf8858182 (diff) | |
download | abaddon-portaudio-e8cbb9d3d1ecca25f1e0a31a75fac70c7a3ea0cb.tar.gz abaddon-portaudio-e8cbb9d3d1ecca25f1e0a31a75fac70c7a3ea0cb.zip |
add typing indicator with optional res/typing_indicator.gif
Diffstat (limited to 'components')
-rw-r--r-- | components/chatwindow.cpp | 17 | ||||
-rw-r--r-- | components/chatwindow.hpp | 2 | ||||
-rw-r--r-- | components/typingindicator.cpp | 106 | ||||
-rw-r--r-- | components/typingindicator.hpp | 24 |
4 files changed, 147 insertions, 2 deletions
diff --git a/components/chatwindow.cpp b/components/chatwindow.cpp index 2e688de..de1aa50 100644 --- a/components/chatwindow.cpp +++ b/components/chatwindow.cpp @@ -1,6 +1,7 @@ #include "chatwindow.hpp" #include "chatmessage.hpp" #include "../abaddon.hpp" +#include "typingindicator.hpp" ChatWindow::ChatWindow() { m_main = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); @@ -8,13 +9,15 @@ ChatWindow::ChatWindow() { m_scroll = Gtk::manage(new Gtk::ScrolledWindow); m_input = Gtk::manage(new Gtk::TextView); m_input_scroll = Gtk::manage(new Gtk::ScrolledWindow); + m_typing_indicator = Gtk::manage(new TypingIndicator); + + m_typing_indicator->set_valign(Gtk::ALIGN_END); + m_typing_indicator->show(); m_main->get_style_context()->add_class("messages"); m_list->get_style_context()->add_class("messages"); m_input_scroll->get_style_context()->add_class("message-input"); - m_input->signal_key_press_event().connect(sigc::mem_fun(*this, &ChatWindow::on_key_press_event), false); - m_main->set_hexpand(true); m_main->set_vexpand(true); @@ -27,6 +30,7 @@ ChatWindow::ChatWindow() { m_scroll->set_can_focus(false); m_scroll->set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS); + m_scroll->show(); m_list->signal_size_allocate().connect([this](Gtk::Allocation &) { if (m_should_scroll_to_bottom) @@ -38,16 +42,20 @@ ChatWindow::ChatWindow() { m_list->set_vexpand(true); m_list->set_focus_hadjustment(m_scroll->get_hadjustment()); m_list->set_focus_vadjustment(m_scroll->get_vadjustment()); + m_list->show(); m_input->set_hexpand(false); m_input->set_halign(Gtk::ALIGN_FILL); m_input->set_valign(Gtk::ALIGN_CENTER); m_input->set_wrap_mode(Gtk::WRAP_WORD_CHAR); + m_input->signal_key_press_event().connect(sigc::mem_fun(*this, &ChatWindow::on_key_press_event), false); + m_input->show(); m_input_scroll->set_propagate_natural_height(true); m_input_scroll->set_min_content_height(20); m_input_scroll->set_max_content_height(250); m_input_scroll->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC); + m_input_scroll->show(); m_completer.SetBuffer(m_input->get_buffer()); m_completer.SetGetChannelID([this]() -> auto { @@ -80,11 +88,15 @@ ChatWindow::ChatWindow() { return ret; }); + m_completer.show(); + m_input_scroll->add(*m_input); m_scroll->add(*m_list); m_main->add(*m_scroll); m_main->add(m_completer); m_main->add(*m_input_scroll); + m_main->add(*m_typing_indicator); + m_main->show(); } Gtk::Widget *ChatWindow::GetRoot() const { @@ -114,6 +126,7 @@ void ChatWindow::SetMessages(const std::set<Snowflake> &msgs) { void ChatWindow::SetActiveChannel(Snowflake id) { m_active_channel = id; + m_typing_indicator->SetActiveChannel(id); } void ChatWindow::AddNewMessage(Snowflake id) { diff --git a/components/chatwindow.hpp b/components/chatwindow.hpp index bac27c0..c1740c4 100644 --- a/components/chatwindow.hpp +++ b/components/chatwindow.hpp @@ -6,6 +6,7 @@ #include "chatmessage.hpp" #include "completer.hpp" +class TypingIndicator; class ChatWindow { public: ChatWindow(); @@ -47,6 +48,7 @@ protected: Gtk::ScrolledWindow *m_input_scroll; Completer m_completer; + TypingIndicator *m_typing_indicator; public: typedef sigc::signal<void, Snowflake, Snowflake> type_signal_action_message_delete; diff --git a/components/typingindicator.cpp b/components/typingindicator.cpp new file mode 100644 index 0000000..cc0ae7d --- /dev/null +++ b/components/typingindicator.cpp @@ -0,0 +1,106 @@ +#include <filesystem> +#include "typingindicator.hpp" +#include "../abaddon.hpp" +#include "../util.hpp" + +constexpr static const int MaxUsersInIndicator = 4; + +TypingIndicator::TypingIndicator() + : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL) { + m_label.set_text(""); + m_label.set_ellipsize(Pango::ELLIPSIZE_END); + m_label.set_valign(Gtk::ALIGN_END); + m_img.set_margin_right(5); + get_style_context()->add_class("typing-indicator"); + + Abaddon::Get().GetDiscordClient().signal_typing_start().connect(sigc::mem_fun(*this, &TypingIndicator::OnUserTypingStart)); + Abaddon::Get().GetDiscordClient().signal_message_create().connect(sigc::mem_fun(*this, &TypingIndicator::OnMessageCreate)); + + add(m_img); + add(m_label); + m_label.show(); + + // try loading gif + if (!std::filesystem::exists("./res/typing_indicator.gif")) return; + auto gif_data = ReadWholeFile("./res/typing_indicator.gif"); + auto loader = Gdk::PixbufLoader::create(); + loader->signal_size_prepared().connect([&](int inw, int inh) { + int w, h; + GetImageDimensions(inw, inh, w, h, 20, 10); + loader->set_size(w, h); + }); + loader->write(gif_data.data(), gif_data.size()); + try { + loader->close(); + m_img.property_pixbuf_animation() = loader->get_animation(); + } catch (const std::exception &) {} +} + +void TypingIndicator::AddUser(Snowflake channel_id, const UserData &user, int timeout) { + auto current_connection_it = m_typers[channel_id].find(user.ID); + if (current_connection_it != m_typers.at(channel_id).end()) { + current_connection_it->second.disconnect(); + m_typers.at(channel_id).erase(current_connection_it); + } + + Snowflake user_id = user.ID; + auto cb = [this, user_id, channel_id]() -> bool { + m_typers.at(channel_id).erase(user_id); + ComputeTypingString(); + return false; + }; + m_typers[channel_id][user.ID] = Glib::signal_timeout().connect_seconds(cb, timeout); + ComputeTypingString(); +} + +void TypingIndicator::SetActiveChannel(Snowflake id) { + m_active_channel = id; + ComputeTypingString(); +} + +void TypingIndicator::OnUserTypingStart(Snowflake user_id, Snowflake channel_id) { + const auto &discord = Abaddon::Get().GetDiscordClient(); + const auto user = discord.GetUser(user_id); + if (!user.has_value()) return; + + AddUser(channel_id, *user, 10); +} + +void TypingIndicator::OnMessageCreate(Snowflake message_id) { + const auto msg = Abaddon::Get().GetDiscordClient().GetMessage(message_id); + if (!msg.has_value()) return; + m_typers[msg->ChannelID].erase(msg->Author.ID); + ComputeTypingString(); +} + +void TypingIndicator::SetTypingString(const Glib::ustring &str) { + m_label.set_text(str); + if (str == "") + m_img.hide(); + else if (m_img.property_pixbuf_animation().get_value()) + m_img.show(); +} + +void TypingIndicator::ComputeTypingString() { + const auto &discord = Abaddon::Get().GetDiscordClient(); + std::vector<UserData> typers; + for (const auto &[id, conn] : m_typers[m_active_channel]) { + const auto user = discord.GetUser(id); + if (user.has_value()) + typers.push_back(*user); + } + if (typers.size() == 0) { + SetTypingString(""); + } else if (typers.size() == 1) { + SetTypingString(typers[0].Username + " is typing..."); + } else if (typers.size() == 2) { + SetTypingString(typers[0].Username + " and " + typers[1].Username + " are typing..."); + } else if (typers.size() > 2 && typers.size() <= MaxUsersInIndicator) { + Glib::ustring str; + for (int i = 0; i < typers.size() - 1; i++) + str += typers[i].Username + ", "; + SetTypingString(str + "and " + typers[typers.size() - 1].Username + " are typing..."); + } else { // size() > MaxUsersInIndicator + SetTypingString("Several people are typing..."); + } +} diff --git a/components/typingindicator.hpp b/components/typingindicator.hpp new file mode 100644 index 0000000..d9633f4 --- /dev/null +++ b/components/typingindicator.hpp @@ -0,0 +1,24 @@ +#pragma once +#include <gtkmm.h> +#include <unordered_map> +#include "../discord/snowflake.hpp" +#include "../discord/user.hpp" + +class TypingIndicator : public Gtk::Box { +public: + TypingIndicator(); + void SetActiveChannel(Snowflake id); + +private: + void AddUser(Snowflake channel_id, const UserData &user, int timeout); + void OnUserTypingStart(Snowflake user_id, Snowflake channel_id); + void OnMessageCreate(Snowflake message_id); + void SetTypingString(const Glib::ustring &str); + void ComputeTypingString(); + + Gtk::Image m_img; + Gtk::Label m_label; + + Snowflake m_active_channel; + std::unordered_map<Snowflake, std::unordered_map<Snowflake, sigc::connection>> m_typers; // channel id -> [user id -> connection] +}; |