mirror of
https://github.com/celisej567/abaddon.git
synced 2026-09-13 20:18:50 +03:00
add typing indicator with optional res/typing_indicator.gif
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
106
components/typingindicator.cpp
Normal file
106
components/typingindicator.cpp
Normal file
@@ -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...");
|
||||
}
|
||||
}
|
||||
24
components/typingindicator.hpp
Normal file
24
components/typingindicator.hpp
Normal file
@@ -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]
|
||||
};
|
||||
Reference in New Issue
Block a user