added colon emote popup for ffz and bttv
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
#include "EmoteInputItem.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
EmoteInputItem::EmoteInputItem(const EmotePtr &emote, const QString &text,
|
||||
ActionCallback action)
|
||||
: emote_(emote)
|
||||
, text_(text)
|
||||
, action_(action)
|
||||
{
|
||||
}
|
||||
|
||||
void EmoteInputItem::action()
|
||||
{
|
||||
if (this->action_ && this->emote_)
|
||||
this->action_(this->emote_->name.string);
|
||||
}
|
||||
|
||||
void EmoteInputItem::paint(QPainter *painter, const QRect &rect) const
|
||||
{
|
||||
if (this->emote_)
|
||||
{
|
||||
if (auto image = this->emote_->images.getImage(4))
|
||||
{
|
||||
if (auto pixmap = image->pixmapOrLoad())
|
||||
{
|
||||
painter->drawPixmap(QRect(rect.x(), rect.y(), ICON_SIZE.width(),
|
||||
ICON_SIZE.height()),
|
||||
*pixmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QRect iconRect(rect.topLeft(), ICON_SIZE);
|
||||
QRect textRect =
|
||||
QRect(iconRect.topRight(),
|
||||
QSize(rect.width() - iconRect.width(), iconRect.height()));
|
||||
painter->drawText(textRect, Qt::AlignLeft | Qt::AlignVCenter, this->text_);
|
||||
}
|
||||
|
||||
QSize EmoteInputItem::sizeHint(const QRect &rect) const
|
||||
{
|
||||
return QSize(rect.width(), ICON_SIZE.height());
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include "messages/Emote.hpp"
|
||||
#include "widgets/listview/GenericListItem.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class EmoteInputItem : public GenericListItem
|
||||
{
|
||||
using ActionCallback = std::function<void(const QString &)>;
|
||||
|
||||
public:
|
||||
EmoteInputItem(const EmotePtr &emote, const QString &text,
|
||||
ActionCallback action);
|
||||
|
||||
// GenericListItem interface
|
||||
public:
|
||||
virtual void action() override;
|
||||
virtual void paint(QPainter *painter, const QRect &rect) const override;
|
||||
virtual QSize sizeHint(const QRect &rect) const override;
|
||||
|
||||
private:
|
||||
EmotePtr emote_;
|
||||
QString text_;
|
||||
ActionCallback action_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,106 @@
|
||||
#include "EmoteInputPopup.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
#include "providers/bttv/BttvEmotes.hpp"
|
||||
#include "providers/ffz/FfzEmotes.hpp"
|
||||
#include "providers/twitch/TwitchChannel.hpp"
|
||||
#include "singletons/Emotes.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/listview/GenericListView.hpp"
|
||||
#include "widgets/splits/EmoteInputItem.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
namespace {
|
||||
|
||||
struct _Emote {
|
||||
EmotePtr emote;
|
||||
QString providerName;
|
||||
};
|
||||
|
||||
void addEmotes(std::vector<_Emote> &out, const EmoteMap &map,
|
||||
const QString &text, const QString &providerName)
|
||||
{
|
||||
for (auto &&emote : map)
|
||||
if (emote.first.string.contains(text, Qt::CaseInsensitive))
|
||||
out.push_back({emote.second, providerName});
|
||||
}
|
||||
} // namespace
|
||||
|
||||
EmoteInputPopup::EmoteInputPopup(QWidget *parent)
|
||||
: BasePopup({BasePopup::EnableCustomFrame, BasePopup::Frameless,
|
||||
BasePopup::DontFocus},
|
||||
parent)
|
||||
, model_(this)
|
||||
{
|
||||
this->initLayout();
|
||||
|
||||
// this->connections_.addConnection(
|
||||
// getApp()->emotes->gifTimer.signal.connect([this] {
|
||||
// if (this->isVisible())
|
||||
// {
|
||||
// // redraw listview somehow
|
||||
// }
|
||||
// }));
|
||||
}
|
||||
|
||||
void EmoteInputPopup::initLayout()
|
||||
{
|
||||
LayoutCreator creator = {this};
|
||||
|
||||
auto listView =
|
||||
creator.emplace<GenericListView>().assign(&this->ui_.listView);
|
||||
|
||||
listView->setModel(&this->model_);
|
||||
QObject::connect(listView.getElement(), &GenericListView::closeRequested,
|
||||
this, [this] { this->close(); });
|
||||
}
|
||||
|
||||
void EmoteInputPopup::updateEmotes(const QString &text, ChannelPtr channel)
|
||||
{
|
||||
std::vector<_Emote> emotes;
|
||||
|
||||
if (auto tc = dynamic_cast<TwitchChannel *>(channel.get()))
|
||||
{
|
||||
// TODO extract "Channel BetterTTV" text into a #define.
|
||||
if (auto bttv = tc->bttvEmotes())
|
||||
addEmotes(emotes, *bttv, text, "Channel BetterTTV");
|
||||
if (auto ffz = tc->ffzEmotes())
|
||||
addEmotes(emotes, *ffz, text, "Channel FrankerFaceZ");
|
||||
|
||||
if (auto bttvG = tc->globalBttv().emotes())
|
||||
addEmotes(emotes, *bttvG, text, "Global BetterTTV");
|
||||
if (auto ffzG = tc->globalFfz().emotes())
|
||||
addEmotes(emotes, *ffzG, text, "Global FrankerFaceZ");
|
||||
}
|
||||
|
||||
this->model_.clear();
|
||||
|
||||
int count = 0;
|
||||
for (auto &&emote : emotes)
|
||||
{
|
||||
this->model_.addItem(std::make_unique<EmoteInputItem>(
|
||||
emote.emote, emote.emote->name.string + " - " + emote.providerName,
|
||||
this->callback_));
|
||||
|
||||
if (count++ == maxLineCount)
|
||||
break;
|
||||
}
|
||||
|
||||
if (!emotes.empty())
|
||||
{
|
||||
this->ui_.listView->setCurrentIndex(this->model_.index(0));
|
||||
}
|
||||
}
|
||||
|
||||
bool EmoteInputPopup::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
return this->ui_.listView->eventFilter(watched, event);
|
||||
}
|
||||
|
||||
void EmoteInputPopup::setInputAction(ActionCallback callback)
|
||||
{
|
||||
this->callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include "common/Channel.hpp"
|
||||
#include "widgets/BasePopup.hpp"
|
||||
#include "widgets/listview/GenericListModel.hpp"
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class GenericListView;
|
||||
|
||||
class EmoteInputPopup : public BasePopup
|
||||
{
|
||||
using ActionCallback = std::function<void(const QString &)>;
|
||||
|
||||
constexpr static int maxLineCount = 10;
|
||||
|
||||
public:
|
||||
EmoteInputPopup(QWidget *parent = nullptr);
|
||||
|
||||
void updateEmotes(const QString &text, ChannelPtr channel);
|
||||
virtual bool eventFilter(QObject *, QEvent *event) override;
|
||||
|
||||
void setInputAction(ActionCallback callback);
|
||||
|
||||
private:
|
||||
void initLayout();
|
||||
|
||||
struct {
|
||||
GenericListView *listView;
|
||||
} ui_;
|
||||
|
||||
GenericListModel model_;
|
||||
ActionCallback callback_;
|
||||
// pajlada::Signals::SignalHolder connections_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "providers/twitch/TwitchIrcServer.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
#include "util/Clamp.hpp"
|
||||
#include "util/LayoutCreator.hpp"
|
||||
#include "widgets/Notebook.hpp"
|
||||
#include "widgets/Scrollbar.hpp"
|
||||
@@ -14,6 +15,7 @@
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
#include "widgets/helper/EffectLabel.hpp"
|
||||
#include "widgets/helper/ResizingTextEdit.hpp"
|
||||
#include "widgets/splits/EmoteInputPopup.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
#include "widgets/splits/SplitContainer.hpp"
|
||||
#include "widgets/splits/SplitInput.hpp"
|
||||
@@ -41,6 +43,7 @@ SplitInput::SplitInput(Split *_chatWidget)
|
||||
|
||||
// misc
|
||||
this->installKeyPressedEvent();
|
||||
this->ui_.textEdit->focusLost.connect([this] { this->hideColonMenu(); });
|
||||
this->scaleChangedEvent(this->scale());
|
||||
}
|
||||
|
||||
@@ -78,6 +81,8 @@ void SplitInput::initLayout()
|
||||
// set edit font
|
||||
this->ui_.textEdit->setFont(
|
||||
app->fonts->getFont(FontStyle::ChatMedium, this->scale()));
|
||||
QObject::connect(this->ui_.textEdit, &QTextEdit::cursorPositionChanged,
|
||||
this, &SplitInput::onCursorPositionChanged);
|
||||
|
||||
this->managedConnections_.push_back(app->fonts->fontChanged.connect([=]() {
|
||||
this->ui_.textEdit->setFont(
|
||||
@@ -187,6 +192,18 @@ void SplitInput::installKeyPressedEvent()
|
||||
auto app = getApp();
|
||||
|
||||
this->ui_.textEdit->keyPressed.connect([this, app](QKeyEvent *event) {
|
||||
if (auto popup = this->emoteInputPopup_.get())
|
||||
{
|
||||
if (popup->isVisible())
|
||||
{
|
||||
if (popup->eventFilter(nullptr, event))
|
||||
{
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)
|
||||
{
|
||||
auto c = this->split_->getChannel();
|
||||
@@ -435,6 +452,102 @@ void SplitInput::installKeyPressedEvent()
|
||||
});
|
||||
}
|
||||
|
||||
void SplitInput::onCursorPositionChanged()
|
||||
{
|
||||
this->updateColonMenu();
|
||||
}
|
||||
|
||||
void SplitInput::updateColonMenu()
|
||||
{
|
||||
if (!dynamic_cast<TwitchChannel *>(this->split_->getChannel().get()))
|
||||
{
|
||||
this->hideColonMenu();
|
||||
}
|
||||
|
||||
// check if in :
|
||||
auto &edit = *this->ui_.textEdit;
|
||||
|
||||
auto text = edit.toPlainText();
|
||||
auto position = edit.textCursor().position();
|
||||
|
||||
if (text.length() == 0)
|
||||
{
|
||||
this->hideColonMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = clamp(position, 0, text.length() - 1); i >= 0; i--)
|
||||
{
|
||||
if (text[i] == ' ')
|
||||
{
|
||||
this->hideColonMenu();
|
||||
return;
|
||||
}
|
||||
else if (text[i] == ':')
|
||||
{
|
||||
this->showColonMenu(text.mid(i, position - i).mid(1));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this->hideColonMenu();
|
||||
}
|
||||
|
||||
void SplitInput::showColonMenu(const QString &text)
|
||||
{
|
||||
if (!this->emoteInputPopup_.get())
|
||||
{
|
||||
this->emoteInputPopup_ = new EmoteInputPopup(this);
|
||||
this->emoteInputPopup_->setInputAction(
|
||||
[that = QObjectRef(this)](const QString &text) mutable {
|
||||
if (auto this2 = that.get())
|
||||
{
|
||||
this2->insertColonText(text);
|
||||
this2->hideColonMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
auto popup = this->emoteInputPopup_.get();
|
||||
assert(popup);
|
||||
|
||||
popup->updateEmotes(text, this->split_->getChannel());
|
||||
|
||||
auto pos = this->mapToGlobal({0, 0}) - QPoint(0, popup->height()) +
|
||||
QPoint((this->width() - popup->width()) / 2, 0);
|
||||
|
||||
popup->move(pos);
|
||||
popup->show();
|
||||
}
|
||||
|
||||
void SplitInput::hideColonMenu()
|
||||
{
|
||||
if (auto popup = this->emoteInputPopup_.get())
|
||||
popup->hide();
|
||||
}
|
||||
|
||||
void SplitInput::insertColonText(const QString &input)
|
||||
{
|
||||
auto &edit = *this->ui_.textEdit;
|
||||
|
||||
auto text = edit.toPlainText();
|
||||
auto position = edit.textCursor().position();
|
||||
|
||||
for (int i = clamp(position, 0, text.length() - 1); i >= 0; i--)
|
||||
{
|
||||
if (text[i] == ':')
|
||||
{
|
||||
auto cursor = edit.textCursor();
|
||||
|
||||
edit.setText(text.remove(i, position - i).insert(i, input));
|
||||
|
||||
cursor.setPosition(i + input.size());
|
||||
edit.setTextCursor(cursor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SplitInput::clearSelection()
|
||||
{
|
||||
QTextCursor c = this->ui_.textEdit->textCursor();
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace chatterino {
|
||||
|
||||
class Split;
|
||||
class EmotePopup;
|
||||
class EmoteInputPopup;
|
||||
class EffectLabel;
|
||||
class ResizingTextEdit;
|
||||
|
||||
@@ -44,11 +45,17 @@ protected:
|
||||
private:
|
||||
void initLayout();
|
||||
void installKeyPressedEvent();
|
||||
void onCursorPositionChanged();
|
||||
void updateEmoteButton();
|
||||
void updateColonMenu();
|
||||
void showColonMenu(const QString &text);
|
||||
void hideColonMenu();
|
||||
void insertColonText(const QString &text);
|
||||
void openEmotePopup();
|
||||
|
||||
Split *const split_;
|
||||
QObjectRef<EmotePopup> emotePopup_;
|
||||
QObjectRef<EmoteInputPopup> emoteInputPopup_;
|
||||
|
||||
struct {
|
||||
ResizingTextEdit *textEdit;
|
||||
|
||||
Reference in New Issue
Block a user