diff --git a/CHANGELOG.md b/CHANGELOG.md index c85dba7d..c1e40845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Minor: Message character length label is now monospaced. (#6366) - Minor: Add a setting under Moderation -> Logs to customize the timestamp used for chat logs. (#6338) - Minor: Add a setting under Moderation -> Logs to use server timestamp from the message instead of the local clock time for logging. (#6346) +- Minor: Add a setting under Chat that, when enabled, pulses the text input whenever one of your messages is successfully sent to the chat. (#6380) - Minor: Add feature to search for only zero-width emotes when prepended by `:~`. (#6362) - Minor: The follow and sub dates now show the duration in a tooltip. (#6384) - Minor: Usercards now show a live indicator if the user is currently streaming. (#6383) diff --git a/docs/ChatterinoTheme.schema.json b/docs/ChatterinoTheme.schema.json index 7e23e73a..a726affe 100644 --- a/docs/ChatterinoTheme.schema.json +++ b/docs/ChatterinoTheme.schema.json @@ -351,6 +351,7 @@ "additionalProperties": false, "properties": { "background": { "$ref": "#/definitions/qt-color" }, + "backgroundPulse": { "$ref": "#/definitions/qt-color" }, "text": { "$ref": "#/definitions/qt-color" } }, "required": ["background", "text"] diff --git a/resources/themes/Black.json b/resources/themes/Black.json index c194aa79..87dfbf97 100644 --- a/resources/themes/Black.json +++ b/resources/themes/Black.json @@ -59,6 +59,7 @@ }, "input": { "background": "#080808", + "backgroundPulse": "#083808", "text": "#ffffff" }, "messageSeperator": "#3c3c3c", diff --git a/resources/themes/Dark.json b/resources/themes/Dark.json index 2b85545d..4b658a44 100644 --- a/resources/themes/Dark.json +++ b/resources/themes/Dark.json @@ -59,6 +59,7 @@ }, "input": { "background": "#242424", + "backgroundPulse": "#245424", "text": "#ffffff" }, "messageSeperator": "#3c3c3c", diff --git a/resources/themes/Light.json b/resources/themes/Light.json index ed610e31..62349a5b 100644 --- a/resources/themes/Light.json +++ b/resources/themes/Light.json @@ -59,6 +59,7 @@ }, "input": { "background": "#dbdbdb", + "backgroundPulse": "#dbffdb", "text": "#000000" }, "messageSeperator": "#7f7f7f", diff --git a/resources/themes/White.json b/resources/themes/White.json index 89b317f3..5e926687 100644 --- a/resources/themes/White.json +++ b/resources/themes/White.json @@ -59,6 +59,7 @@ }, "input": { "background": "#f2f2f2", + "backgroundPulse": "#d2ffd2", "text": "#000000" }, "messageSeperator": "#7f7f7f", diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 79f39596..aeff2d6f 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -285,6 +285,11 @@ public: false, }; + BoolSetting pulseTextInputOnSelfMessage = { + "/appearance/pulseTextInputOnSelfMessage", + false, + }; + /// Behaviour BoolSetting allowDuplicateMessages = {"/behaviour/allowDuplicateMessages", true}; diff --git a/src/singletons/Theme.cpp b/src/singletons/Theme.cpp index 6bf0d25d..bbb74b54 100644 --- a/src/singletons/Theme.cpp +++ b/src/singletons/Theme.cpp @@ -205,6 +205,7 @@ void parseSplits(const QJsonObject &splits, const QJsonObject &splitsFallback, const auto input = splits["input"_L1].toObject(); const auto inputFallback = splitsFallback["input"_L1].toObject(); parseColor(theme.splits, input, background); + parseColor(theme.splits, input, backgroundPulse); parseColor(theme.splits, input, text); } } diff --git a/src/singletons/Theme.hpp b/src/singletons/Theme.hpp index 63e8cf08..100082a2 100644 --- a/src/singletons/Theme.hpp +++ b/src/singletons/Theme.hpp @@ -146,7 +146,9 @@ public: struct { QColor background; + QColor backgroundPulse; QColor text; + // Used by the ChatterListWidget & QuickSwitcherPopup QString styleSheet; } input; } splits; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 3afc7468..3b80cd72 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -975,6 +975,7 @@ void ChannelView::setChannel(const ChannelPtr &underlyingChannel) } this->channel_->addMessage(message, MessageContext::Repost, overridingFlags); + this->messageAddedToChannel(message); } }); diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index 1aaf6d53..61a5333c 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -228,6 +228,9 @@ public: pajlada::Signals::Signal openChannelIn; + /// This signal fires when a message passed filters and was added to the channel view + Q_SIGNAL void messageAddedToChannel(MessagePtr &message); + protected: void themeChangedEvent() override; void scaleChangedEvent(float scale) override; diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index bbf3cc54..a9016f4b 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -496,6 +496,14 @@ void GeneralPage::initLayout(GeneralPageView &layout) ->addKeywords({"scroll bar"}) ->addTo(layout); + SettingWidget::checkbox( + "Pulse text input when one of your messages is successfully sent", + s.pulseTextInputOnSelfMessage) + ->setTooltip( + "Pulses the text input in a green color whenever a message of " + "yours is successfully sent in the matching channel.") + ->addTo(layout); + layout.addTitle("Messages"); SettingWidget::checkbox("Separate with lines", s.separateMessages) diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index 7e998934..46fd5827 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -854,6 +854,21 @@ void Split::setChannel(IndirectChannel newChannel) this->actionRequested.invoke(Action::RefreshTab); }); + QObject::connect( + this->view_, &ChannelView::messageAddedToChannel, this, + [this](MessagePtr &message) { + if (!getSettings()->pulseTextInputOnSelfMessage) + { + return; + } + auto user = getApp()->getAccounts()->twitch.getCurrent(); + if (!user->isAnon() && message->userID == user->getUserId()) + { + // A message from yourself was just received in this split + this->input_->triggerSelfMessageReceived(); + } + }); + this->channelChanged.invoke(); this->actionRequested.invoke(Action::RefreshTab); diff --git a/src/widgets/splits/SplitInput.cpp b/src/widgets/splits/SplitInput.cpp index 5df7904e..b2f2be3c 100644 --- a/src/widgets/splits/SplitInput.cpp +++ b/src/widgets/splits/SplitInput.cpp @@ -33,8 +33,24 @@ #include +using namespace Qt::Literals; + namespace chatterino { +namespace { + +// Current function: https://www.desmos.com/calculator/vdyamchjwh +qreal highlightEasingFunction(qreal progress) +{ + if (progress <= 0.1) + { + return 1.0 - pow(10.0 * progress, 3.0); + } + return 1.0 + pow((20.0 / 9.0) * (0.5 * progress - 0.5), 3.0); +} + +} // namespace + SplitInput::SplitInput(Split *_chatWidget, bool enableInlineReplying) : SplitInput(_chatWidget, _chatWidget, _chatWidget->view_, enableInlineReplying) @@ -47,6 +63,7 @@ SplitInput::SplitInput(QWidget *parent, Split *_chatWidget, , split_(_chatWidget) , channelView_(_channelView) , enableInlineReplying_(enableInlineReplying) + , backgroundColorAnimation(this, "backgroundColor"_ba) { this->installEventFilter(this); this->initLayout(); @@ -75,6 +92,11 @@ SplitInput::SplitInput(QWidget *parent, Split *_chatWidget, this->clearShortcuts(); this->addShortcuts(); }); + + QEasingCurve curve; + curve.setCustomType(highlightEasingFunction); + this->backgroundColorAnimation.setDuration(500); + this->backgroundColorAnimation.setEasingCurve(curve); } void SplitInput::initLayout() @@ -140,6 +162,7 @@ void SplitInput::initLayout() hboxLayout.emplace().assign(&this->ui_.textEdit); connect(textEdit.getElement(), &ResizingTextEdit::textChanged, this, &SplitInput::editTextChanged); + textEdit->setFrameStyle(QFrame::NoFrame); hboxLayout.emplace("SEND").assign(&this->ui_.sendButton); this->ui_.sendButton->hide(); @@ -226,6 +249,16 @@ void SplitInput::initLayout() this->managedConnections_); } +void SplitInput::triggerSelfMessageReceived() +{ + if (this->backgroundColorAnimation.state() != QPropertyAnimation::Stopped) + { + this->backgroundColorAnimation.stop(); + } + this->backgroundColorAnimation.setDirection(QPropertyAnimation::Forward); + this->backgroundColorAnimation.start(); +} + void SplitInput::scaleChangedEvent(float scale) { auto *app = getApp(); @@ -246,13 +279,6 @@ void SplitInput::scaleChangedEvent(float scale) this->ui_.textEdit->setFont( app->getFonts()->getFont(FontStyle::ChatMedium, scale)); - QPalette placeholderPalette; - placeholderPalette.setColor( - QPalette::PlaceholderText, - this->theme->messages.textColors.chatPlaceholder); - - this->ui_.textEdit->setStyleSheet(this->theme->splits.input.styleSheet); - this->ui_.textEdit->setPalette(placeholderPalette); // TODO: This font does _not_ get updated when you change your chat font // NOTE: We're using TimestampMedium here to get a font that uses the tnum font feature, // meaning numbers get equal width & don't bounce around while the user is typing. @@ -266,17 +292,19 @@ void SplitInput::scaleChangedEvent(float scale) void SplitInput::themeChangedEvent() { QPalette palette; - QPalette placeholderPalette; palette.setColor(QPalette::WindowText, this->theme->splits.input.text); - placeholderPalette.setColor( - QPalette::PlaceholderText, - this->theme->messages.textColors.chatPlaceholder); this->ui_.textEditLength->setPalette(palette); - this->ui_.textEdit->setStyleSheet(this->theme->splits.input.styleSheet); - this->ui_.textEdit->setPalette(placeholderPalette); + // Theme changed, reset current background color + this->setBackgroundColor(this->theme->splits.input.background); + this->backgroundColorAnimation.setStartValue( + this->theme->splits.input.backgroundPulse); + this->backgroundColorAnimation.setEndValue( + this->theme->splits.input.background); + this->backgroundColorAnimation.stop(); + this->updateTextEditPalette(); if (this->theme->isLightTheme()) { @@ -1277,4 +1305,39 @@ int SplitInput::replyMessageWidth() const return this->ui_.inputWrapper->width() - 1 - 10; } +void SplitInput::updateTextEditPalette() +{ + QPalette p; + + // Placeholder text color + p.setColor(QPalette::PlaceholderText, + this->theme->messages.textColors.chatPlaceholder); + + // Text color + p.setColor(QPalette::Text, this->theme->messages.textColors.regular); + + // Selection background color + p.setBrush(QPalette::Highlight, + this->theme->isLightTheme() + ? QColor(u"#68B1FF"_s) + : this->theme->tabs.selected.backgrounds.regular); + + // Background color + p.setBrush(QPalette::Base, this->backgroundColor()); + + this->ui_.textEdit->setPalette(p); +} + +QColor SplitInput::backgroundColor() const +{ + return this->backgroundColor_; +} + +void SplitInput::setBackgroundColor(QColor newColor) +{ + this->backgroundColor_ = newColor; + + this->updateTextEditPalette(); +} + } // namespace chatterino diff --git a/src/widgets/splits/SplitInput.hpp b/src/widgets/splits/SplitInput.hpp index 9a0aa6cd..14a04905 100644 --- a/src/widgets/splits/SplitInput.hpp +++ b/src/widgets/splits/SplitInput.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -75,6 +76,8 @@ public: */ void setInputText(const QString &newInputText); + void triggerSelfMessageReceived(); + pajlada::Signals::Signal textChanged; pajlada::Signals::NoArgSignal selectionChanged; @@ -169,6 +172,22 @@ protected: // set the height of the split input to 0 if we're supposed to be hidden instead bool hidden{false}; + /// Updates the text edit palette using the current theme + /// and current "backgroundColor" property + void updateTextEditPalette(); + + // the background color defines the current background color of this split input + // instead of reading straight from the theme, we store a property here + // to be used by a property to be able to pulse a highlight color on demand + Q_PROPERTY( + QColor backgroundColor READ backgroundColor WRITE setBackgroundColor); + + QColor backgroundColor_{"#000000"}; + QColor backgroundColor() const; + void setBackgroundColor(QColor newColor); + + QPropertyAnimation backgroundColorAnimation; + private Q_SLOTS: void editTextChanged();