mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 03:00:46 +03:00
Add rainfall effect
This is a proof-of-concept example of inplementing a msgtype not found in the spec.
This commit is contained in:
parent
0096226aeb
commit
296385e6fe
7 changed files with 152 additions and 12 deletions
|
@ -364,7 +364,9 @@ Item {
|
|||
onClicked: Rooms.resetCurrentRoom()
|
||||
}
|
||||
|
||||
ParticleSystem { id: confettiParticleSystem
|
||||
ParticleSystem {
|
||||
id: confettiParticleSystem
|
||||
|
||||
Component.onCompleted: pause();
|
||||
paused: !shouldEffectsRun
|
||||
}
|
||||
|
@ -420,6 +422,42 @@ Item {
|
|||
angle: 90
|
||||
}
|
||||
|
||||
ParticleSystem {
|
||||
id: rainfallParticleSystem
|
||||
|
||||
Component.onCompleted: pause();
|
||||
paused: !shouldEffectsRun
|
||||
}
|
||||
|
||||
Emitter {
|
||||
id: rainfallEmitter
|
||||
|
||||
width: parent.width
|
||||
enabled: false
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
y: -60
|
||||
emitRate: parent.width / 50
|
||||
lifeSpan: 10000
|
||||
system: rainfallParticleSystem
|
||||
velocity: PointDirection {
|
||||
x: 0
|
||||
y: 300
|
||||
xVariation: 0
|
||||
yVariation: 75
|
||||
}
|
||||
|
||||
ItemParticle {
|
||||
system: rainfallParticleSystem
|
||||
fade: false
|
||||
delegate: Rectangle {
|
||||
width: 2
|
||||
height: 30 + 30 * Math.random()
|
||||
radius: 2
|
||||
color: "#0099ff"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NhekoDropArea {
|
||||
anchors.fill: parent
|
||||
roomid: room ? room.roomId : ""
|
||||
|
@ -428,7 +466,7 @@ Item {
|
|||
Timer {
|
||||
id: effectsTimer
|
||||
onTriggered: shouldEffectsRun = false;
|
||||
interval: confettiEmitter.lifeSpan
|
||||
interval: Math.max(confettiEmitter.lifeSpan, rainfallEmitter.lifeSpan)
|
||||
repeat: false
|
||||
running: false
|
||||
}
|
||||
|
@ -471,7 +509,25 @@ Item {
|
|||
if (!Settings.fancyEffects)
|
||||
return
|
||||
|
||||
effectsTimer.start();
|
||||
effectsTimer.restart();
|
||||
}
|
||||
|
||||
function onRainfall()
|
||||
{
|
||||
if (!Settings.fancyEffects)
|
||||
return
|
||||
|
||||
shouldEffectsRun = true;
|
||||
rainfallEmitter.pulse(parent.height * 7.5)
|
||||
room.markSpecialEffectsDone()
|
||||
}
|
||||
|
||||
function onRainfallDone()
|
||||
{
|
||||
if (!Settings.fancyEffects)
|
||||
return
|
||||
|
||||
effectsTimer.restart();
|
||||
}
|
||||
|
||||
target: room
|
||||
|
|
|
@ -87,6 +87,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const
|
|||
return QStringLiteral("/confetti ");
|
||||
case RainbowConfetti:
|
||||
return QStringLiteral("/rainbowconfetti ");
|
||||
case Rainfall:
|
||||
return QStringLiteral("/rainfall ");
|
||||
case RainbowRain:
|
||||
return QStringLiteral("/rainbowrain ");
|
||||
case Goto:
|
||||
return QStringLiteral("/goto ");
|
||||
case ConvertToDm:
|
||||
|
@ -156,6 +160,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const
|
|||
return tr("/confetti [message]");
|
||||
case RainbowConfetti:
|
||||
return tr("/rainbowconfetti [message]");
|
||||
case Rainfall:
|
||||
return tr("/rainfall [message]");
|
||||
case RainbowRain:
|
||||
return tr("/rainbowrain [message]");
|
||||
case Goto:
|
||||
return tr("/goto <message reference>");
|
||||
case ConvertToDm:
|
||||
|
@ -225,6 +233,10 @@ CommandCompleter::data(const QModelIndex &index, int role) const
|
|||
return tr("Send a message with confetti.");
|
||||
case RainbowConfetti:
|
||||
return tr("Send a message in rainbow colors with confetti.");
|
||||
case Rainfall:
|
||||
return tr("Send a message with rain.");
|
||||
case RainbowRain:
|
||||
return tr("Send a message in rainbow colors with rain.");
|
||||
case Goto:
|
||||
return tr("Go to a specific message using an event id, index or matrix: link");
|
||||
case ConvertToDm:
|
||||
|
|
|
@ -46,6 +46,8 @@ public:
|
|||
RainbowNotice,
|
||||
Confetti,
|
||||
RainbowConfetti,
|
||||
Rainfall,
|
||||
RainbowRain,
|
||||
Goto,
|
||||
ConvertToDm,
|
||||
ConvertToRoom,
|
||||
|
|
|
@ -281,6 +281,8 @@ InputBar::updateTextContentProperties(const QString &t)
|
|||
QStringLiteral("rainbownotice"),
|
||||
QStringLiteral("confetti"),
|
||||
QStringLiteral("rainbowconfetti"),
|
||||
QStringLiteral("rain"),
|
||||
QStringLiteral("rainbowrain"),
|
||||
QStringLiteral("goto"),
|
||||
QStringLiteral("converttodm"),
|
||||
QStringLiteral("converttoroom")};
|
||||
|
@ -623,6 +625,30 @@ InputBar::confetti(const QString &body, bool rainbowify)
|
|||
room->sendMessageEvent(confetti, mtx::events::EventType::RoomMessage);
|
||||
}
|
||||
|
||||
void
|
||||
InputBar::rainfall(const QString &body, bool rainbowify)
|
||||
{
|
||||
auto html = utils::markdownToHtml(body, rainbowify);
|
||||
|
||||
mtx::events::msg::Unknown rain;
|
||||
rain.msgtype = "io.element.effect.rainfall";
|
||||
rain.body = body.trimmed().toStdString();
|
||||
|
||||
if (html != body.trimmed().toHtmlEscaped() &&
|
||||
ChatPage::instance()->userSettings()->markdown()) {
|
||||
nlohmann::json j;
|
||||
j["formatted_body"] = html.toStdString();
|
||||
j["format"] = "org.matrix.custom.html";
|
||||
rain.content = j.dump();
|
||||
// Remove markdown links by completer
|
||||
rain.body = replaceMatrixToMarkdownLink(body.trimmed()).toStdString();
|
||||
}
|
||||
|
||||
rain.relations = generateRelations();
|
||||
|
||||
room->sendMessageEvent(rain, mtx::events::EventType::RoomMessage);
|
||||
}
|
||||
|
||||
void
|
||||
InputBar::image(const QString &filename,
|
||||
const std::optional<mtx::crypto::EncryptedFile> &file,
|
||||
|
@ -890,6 +916,10 @@ InputBar::command(const QString &command, QString args)
|
|||
confetti(args, false);
|
||||
} else if (command == QLatin1String("rainbowconfetti")) {
|
||||
confetti(args, true);
|
||||
} else if (command == QLatin1String("rainfall")) {
|
||||
rainfall(args, false);
|
||||
} else if (command == QLatin1String("rainbowrain")) {
|
||||
rainfall(args, true);
|
||||
} else if (command == QLatin1String("goto")) {
|
||||
// Goto has three different modes:
|
||||
// 1 - Going directly to a given event ID
|
||||
|
|
|
@ -242,6 +242,7 @@ private:
|
|||
void emote(const QString &body, bool rainbowify);
|
||||
void notice(const QString &body, bool rainbowify);
|
||||
void confetti(const QString &body, bool rainbowify);
|
||||
void rainfall(const QString &body, bool rainbowify);
|
||||
bool command(const QString &name, QString args);
|
||||
void image(const QString &filename,
|
||||
const std::optional<mtx::crypto::EncryptedFile> &file,
|
||||
|
|
|
@ -1081,19 +1081,29 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
|
|||
} else if (std::holds_alternative<RoomEvent<mtx::events::msg::Text>>(e)) {
|
||||
if (auto msg = QString::fromStdString(
|
||||
std::get<RoomEvent<mtx::events::msg::Text>>(e).content.body);
|
||||
msg.contains("🎉") || msg.contains("🎊"))
|
||||
msg.contains("🎉") || msg.contains("🎊")) {
|
||||
needsSpecialEffects_ = true;
|
||||
specialEffects_.setFlag(Confetti);
|
||||
}
|
||||
} else if (std::holds_alternative<RoomEvent<mtx::events::msg::Unknown>>(e)) {
|
||||
if (auto msg = QString::fromStdString(
|
||||
std::get<RoomEvent<mtx::events::msg::Unknown>>(e).content.body);
|
||||
msg.contains("🎉") || msg.contains("🎊"))
|
||||
msg.contains("🎉") || msg.contains("🎊")) {
|
||||
needsSpecialEffects_ = true;
|
||||
} else if (std::holds_alternative<RoomEvent<mtx::events::msg::Confetti>>(e))
|
||||
specialEffects_.setFlag(Confetti);
|
||||
} else if (std::get<RoomEvent<mtx::events::msg::Unknown>>(e).content.msgtype ==
|
||||
"io.element.effect.rainfall") {
|
||||
needsSpecialEffects_ = true;
|
||||
specialEffects_.setFlag(Rainfall);
|
||||
}
|
||||
} else if (std::holds_alternative<RoomEvent<mtx::events::msg::Confetti>>(e)) {
|
||||
needsSpecialEffects_ = true;
|
||||
specialEffects_.setFlag(Confetti);
|
||||
}
|
||||
}
|
||||
|
||||
if (needsSpecialEffects_)
|
||||
emit confetti();
|
||||
triggerSpecialEffects();
|
||||
|
||||
if (avatarChanged)
|
||||
emit roomAvatarUrlChanged();
|
||||
|
@ -2056,7 +2066,14 @@ TimelineModel::triggerSpecialEffects()
|
|||
{
|
||||
if (needsSpecialEffects_) {
|
||||
// Note (Loren): Without the timer, this apparently emits before QML is ready
|
||||
if (specialEffects_.testFlag(Confetti)) {
|
||||
QTimer::singleShot(1, this, [this] { emit confetti(); });
|
||||
specialEffects_.setFlag(Confetti, false);
|
||||
}
|
||||
if (specialEffects_.testFlag(Rainfall)) {
|
||||
QTimer::singleShot(1, this, [this] { emit rainfall(); });
|
||||
specialEffects_.setFlag(Rainfall, false);
|
||||
}
|
||||
needsSpecialEffects_ = false;
|
||||
}
|
||||
}
|
||||
|
@ -2066,6 +2083,10 @@ TimelineModel::markSpecialEffectsDone()
|
|||
{
|
||||
needsSpecialEffects_ = false;
|
||||
emit confettiDone();
|
||||
emit rainfallDone();
|
||||
|
||||
specialEffects_.setFlag(Confetti, false);
|
||||
specialEffects_.setFlag(Rainfall, false);
|
||||
}
|
||||
|
||||
QString
|
||||
|
@ -2928,7 +2949,8 @@ TimelineModel::setEdit(const QString &newEdit)
|
|||
if (msgType == mtx::events::MessageType::Text ||
|
||||
msgType == mtx::events::MessageType::Notice ||
|
||||
msgType == mtx::events::MessageType::Emote ||
|
||||
msgType == mtx::events::MessageType::Confetti) {
|
||||
msgType == mtx::events::MessageType::Confetti ||
|
||||
msgType == mtx::events::MessageType::Unknown) {
|
||||
auto relInfo = relatedInfo(newEdit);
|
||||
auto editText = relInfo.quoted_body;
|
||||
|
||||
|
@ -2951,8 +2973,14 @@ TimelineModel::setEdit(const QString &newEdit)
|
|||
input()->setText("/me " + editText);
|
||||
else if (msgType == mtx::events::MessageType::Confetti)
|
||||
input()->setText("/confetti " + editText);
|
||||
else if (msgType == mtx::events::MessageType::Unknown) {
|
||||
if (auto u = std::get_if<mtx::events::RoomEvent<mtx::events::msg::Unknown>>(&e);
|
||||
u && u->content.msgtype == "io.element.effect.rainfall")
|
||||
input()->setText("/rainfall " + editText);
|
||||
else
|
||||
input()->setText(editText);
|
||||
} else
|
||||
input()->setText(editText);
|
||||
} else {
|
||||
input()->setText(QLatin1String(""));
|
||||
}
|
||||
|
|
|
@ -267,6 +267,13 @@ public:
|
|||
};
|
||||
Q_ENUM(Roles);
|
||||
|
||||
enum SpecialEffect
|
||||
{
|
||||
Confetti,
|
||||
Rainfall,
|
||||
};
|
||||
Q_DECLARE_FLAGS(SpecialEffects, SpecialEffect)
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
@ -451,6 +458,8 @@ signals:
|
|||
void scrollToIndex(int index);
|
||||
void confetti();
|
||||
void confettiDone();
|
||||
void rainfall();
|
||||
void rainfallDone();
|
||||
|
||||
void lastMessageChanged();
|
||||
void notificationsChanged();
|
||||
|
@ -522,8 +531,8 @@ private:
|
|||
std::string last_event_id;
|
||||
std::string fullyReadEventId_;
|
||||
|
||||
// TODO (Loren): This should hopefully handle more than just confetti in the future
|
||||
bool needsSpecialEffects_ = false;
|
||||
QFlags<SpecialEffect> specialEffects_;
|
||||
|
||||
std::unique_ptr<RoomSummary, DeleteLaterDeleter> parentSummary = nullptr;
|
||||
bool parentChecked = false;
|
||||
|
@ -531,6 +540,8 @@ private:
|
|||
friend void EventStore::refetchOnlineKeyBackupKeys(TimelineModel *room);
|
||||
};
|
||||
|
||||
Q_DECLARE_OPERATORS_FOR_FLAGS(TimelineModel::SpecialEffects)
|
||||
|
||||
template<class T>
|
||||
void
|
||||
TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType)
|
||||
|
|
Loading…
Reference in a new issue