From 266d61a6fa11e4b3b13d0162f92cc7133ba9b79d Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Wed, 2 Aug 2023 19:38:32 +0200 Subject: [PATCH 01/62] feat: add transparent overlay window --- docs/ChatterinoTheme.schema.json | 89 ++++++--- resources/themes/Black.json | 16 ++ resources/themes/Dark.json | 16 ++ resources/themes/Light.json | 16 ++ resources/themes/White.json | 16 ++ src/CMakeLists.txt | 2 + src/controllers/hotkeys/ActionNames.hpp | 4 +- src/controllers/hotkeys/HotkeyController.cpp | 3 + src/messages/layouts/MessageLayout.cpp | 13 +- src/messages/layouts/MessageLayout.hpp | 2 +- src/messages/layouts/MessageLayoutContext.cpp | 20 ++ src/messages/layouts/MessageLayoutContext.hpp | 6 + src/singletons/Theme.cpp | 44 +++-- src/singletons/Theme.hpp | 36 ++-- src/widgets/OverlayWindow.cpp | 187 ++++++++++++++++++ src/widgets/OverlayWindow.hpp | 46 +++++ src/widgets/Window.cpp | 16 +- src/widgets/helper/ChannelView.cpp | 14 +- src/widgets/helper/ChannelView.hpp | 5 + 19 files changed, 493 insertions(+), 58 deletions(-) create mode 100644 src/widgets/OverlayWindow.cpp create mode 100644 src/widgets/OverlayWindow.hpp diff --git a/docs/ChatterinoTheme.schema.json b/docs/ChatterinoTheme.schema.json index a0c7972dd37..44398866ac3 100644 --- a/docs/ChatterinoTheme.schema.json +++ b/docs/ChatterinoTheme.schema.json @@ -215,6 +215,47 @@ "text": { "$ref": "#/definitions/qt-color" } }, "required": ["backgrounds", "line", "text"] + }, + "text-colors": { + "type": "object", + "additionalProperties": false, + "properties": { + "caret": { "$ref": "#/definitions/qt-color" }, + "chatPlaceholder": { "$ref": "#/definitions/qt-color" }, + "link": { "$ref": "#/definitions/qt-color" }, + "regular": { "$ref": "#/definitions/qt-color" }, + "system": { "$ref": "#/definitions/qt-color" } + }, + "required": ["caret", "chatPlaceholder", "link", "regular", "system"] + }, + "message-backgrounds": { + "type": "object", + "additionalProperties": false, + "properties": { + "alternate": { "$ref": "#/definitions/qt-color" }, + "regular": { "$ref": "#/definitions/qt-color" } + }, + "required": ["alternate", "regular"] + }, + "message-colors": { + "type": "object", + "additionalProperties": false, + "properties": { + "backgrounds": { "$ref": "#/definitions/message-backgrounds" }, + "disabled": { "$ref": "#/definitions/qt-color" }, + "highlightAnimationEnd": { "$ref": "#/definitions/qt-color" }, + "highlightAnimationStart": { "$ref": "#/definitions/qt-color" }, + "selection": { "$ref": "#/definitions/qt-color" }, + "textColors": { "$ref": "#/definitions/text-colors" } + }, + "required": [ + "backgrounds", + "disabled", + "highlightAnimationEnd", + "highlightAnimationStart", + "selection", + "textColors" + ] } }, "type": "object", @@ -229,37 +270,12 @@ "type": "object", "additionalProperties": false, "properties": { - "backgrounds": { - "type": "object", - "additionalProperties": false, - "properties": { - "alternate": { "$ref": "#/definitions/qt-color" }, - "regular": { "$ref": "#/definitions/qt-color" } - }, - "required": ["alternate", "regular"] - }, + "backgrounds": { "$ref": "#/definitions/message-backgrounds" }, "disabled": { "$ref": "#/definitions/qt-color" }, "highlightAnimationEnd": { "$ref": "#/definitions/qt-color" }, "highlightAnimationStart": { "$ref": "#/definitions/qt-color" }, "selection": { "$ref": "#/definitions/qt-color" }, - "textColors": { - "type": "object", - "additionalProperties": false, - "properties": { - "caret": { "$ref": "#/definitions/qt-color" }, - "chatPlaceholder": { "$ref": "#/definitions/qt-color" }, - "link": { "$ref": "#/definitions/qt-color" }, - "regular": { "$ref": "#/definitions/qt-color" }, - "system": { "$ref": "#/definitions/qt-color" } - }, - "required": [ - "caret", - "chatPlaceholder", - "link", - "regular", - "system" - ] - } + "textColors": { "$ref": "#/definitions/text-colors" } }, "required": [ "backgrounds", @@ -270,6 +286,24 @@ "textColors" ] }, + "overlayMessages": { + "type": "object", + "additionalProperties": false, + "properties": { + "backgrounds": { "$ref": "#/definitions/message-backgrounds" }, + "disabled": { "$ref": "#/definitions/qt-color" }, + "selection": { "$ref": "#/definitions/qt-color" }, + "textColors": { "$ref": "#/definitions/text-colors" }, + "background": { "$ref": "#/definitions/qt-color" } + }, + "required": [ + "backgrounds", + "disabled", + "selection", + "textColors", + "background" + ] + }, "scrollbars": { "type": "object", "additionalProperties": false, @@ -374,6 +408,7 @@ "required": [ "accent", "messages", + "overlayMessages", "scrollbars", "splits", "tabs", diff --git a/resources/themes/Black.json b/resources/themes/Black.json index 79f46dc76b7..42f60c7d018 100644 --- a/resources/themes/Black.json +++ b/resources/themes/Black.json @@ -22,6 +22,22 @@ "system": "#8c7f7f" } }, + "overlayMessages": { + "backgrounds": { + "alternate": "#32000000", + "regular": "transparent" + }, + "disabled": "#64000000", + "selection": "#40ffffff", + "textColors": { + "caret": "#ffffff", + "chatPlaceholder": "#5d5555", + "link": "#4286f4", + "regular": "#ffffff", + "system": "#8c7f7f" + }, + "background": "#32000000" + }, "scrollbars": { "background": "#00000000", "thumb": "#4d4d4d", diff --git a/resources/themes/Dark.json b/resources/themes/Dark.json index 036ce18a3a3..c417bda17a1 100644 --- a/resources/themes/Dark.json +++ b/resources/themes/Dark.json @@ -22,6 +22,22 @@ "system": "#8c7f7f" } }, + "overlayMessages": { + "backgrounds": { + "alternate": "#32000000", + "regular": "transparent" + }, + "disabled": "#64000000", + "selection": "#40ffffff", + "textColors": { + "caret": "#ffffff", + "chatPlaceholder": "#5d5555", + "link": "#4286f4", + "regular": "#ffffff", + "system": "#8c7f7f" + }, + "background": "#32000000" + }, "scrollbars": { "background": "#00000000", "thumb": "#575757", diff --git a/resources/themes/Light.json b/resources/themes/Light.json index 338c642e25a..f0c5d7115be 100644 --- a/resources/themes/Light.json +++ b/resources/themes/Light.json @@ -22,6 +22,22 @@ "system": "#8c7f7f" } }, + "overlayMessages": { + "backgrounds": { + "alternate": "#32ffffff", + "regular": "transparent" + }, + "disabled": "#64ffffff", + "selection": "#40ffffff", + "textColors": { + "caret": "#000000", + "chatPlaceholder": "#af9f9f", + "link": "#4286f4", + "regular": "#000000", + "system": "#8c7f7f" + }, + "background": "#32ffffff" + }, "scrollbars": { "background": "#00000000", "thumb": "#a8a8a8", diff --git a/resources/themes/White.json b/resources/themes/White.json index 7676ac629fa..2b6dc731cc4 100644 --- a/resources/themes/White.json +++ b/resources/themes/White.json @@ -22,6 +22,22 @@ "system": "#8c7f7f" } }, + "overlayMessages": { + "backgrounds": { + "alternate": "#32ffffff", + "regular": "transparent" + }, + "disabled": "#64ffffff", + "selection": "#40ffffff", + "textColors": { + "caret": "#000000", + "chatPlaceholder": "#af9f9f", + "link": "#4286f4", + "regular": "#000000", + "system": "#8c7f7f" + }, + "background": "#32ffffff" + }, "scrollbars": { "background": "#00000000", "thumb": "#b3b3b3", diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ccaf4adaae9..c28fe4949c0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -461,6 +461,8 @@ set(SOURCE_FILES widgets/Label.hpp widgets/Notebook.cpp widgets/Notebook.hpp + widgets/OverlayWindow.cpp + widgets/OverlayWindow.hpp widgets/Scrollbar.cpp widgets/Scrollbar.hpp widgets/StreamView.cpp diff --git a/src/controllers/hotkeys/ActionNames.hpp b/src/controllers/hotkeys/ActionNames.hpp index 03907ca6d4c..599dca6a4eb 100644 --- a/src/controllers/hotkeys/ActionNames.hpp +++ b/src/controllers/hotkeys/ActionNames.hpp @@ -258,7 +258,8 @@ inline const std::map actionNames{ {"moveTab", ActionDefinition{ "Move tab", - "", + "", 1, }}, {"newSplit", ActionDefinition{"Create a new split"}}, @@ -285,6 +286,7 @@ inline const std::map actionNames{ .argumentsPromptHover = "What should be included in the new popup", }}, + {"popupOverlay", ActionDefinition{"New overlay popup"}}, {"quit", ActionDefinition{"Quit Chatterino"}}, {"removeTab", ActionDefinition{"Remove current tab"}}, {"reopenSplit", ActionDefinition{"Reopen closed split"}}, diff --git a/src/controllers/hotkeys/HotkeyController.cpp b/src/controllers/hotkeys/HotkeyController.cpp index 16a1e035665..bc4768b0dfa 100644 --- a/src/controllers/hotkeys/HotkeyController.cpp +++ b/src/controllers/hotkeys/HotkeyController.cpp @@ -467,6 +467,9 @@ void HotkeyController::addDefaults(std::set &addedHotkeys) QKeySequence("Ctrl+Shift+N"), "popup", {"window"}, "new popup window from tab"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, + QKeySequence("Ctrl+Alt+N"), "popupOverlay", {}, + "new popup overlay"); this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, QKeySequence::ZoomIn, "zoom", {"in"}, "zoom in"); diff --git a/src/messages/layouts/MessageLayout.cpp b/src/messages/layouts/MessageLayout.cpp index e5b1800917f..51fc44c5740 100644 --- a/src/messages/layouts/MessageLayout.cpp +++ b/src/messages/layouts/MessageLayout.cpp @@ -198,10 +198,15 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags) // Painting void MessageLayout::paint(const MessagePaintContext &ctx) { - QPixmap *pixmap = this->ensureBuffer(ctx.painter, ctx.canvasWidth); + QPixmap *pixmap = + this->ensureBuffer(ctx.canvasWidth, ctx.messageColors.hasTransparency); if (!this->bufferValid_ || !ctx.selection.isEmpty()) { + if (ctx.messageColors.hasTransparency) + { + pixmap->fill(Qt::transparent); + } this->updateBuffer(pixmap, ctx); } @@ -272,7 +277,7 @@ void MessageLayout::paint(const MessagePaintContext &ctx) this->bufferValid_ = true; } -QPixmap *MessageLayout::ensureBuffer(QPainter &painter, int width) +QPixmap *MessageLayout::ensureBuffer(int width, bool clear) { if (this->buffer_ != nullptr) { @@ -290,6 +295,10 @@ QPixmap *MessageLayout::ensureBuffer(QPainter &painter, int width) this->buffer_ = std::make_unique( width, std::max(16, this->container_.getHeight())); #endif + if (clear) + { + this->buffer_->fill(Qt::transparent); + } this->bufferValid_ = false; DebugCount::increase("message drawing buffers"); diff --git a/src/messages/layouts/MessageLayout.hpp b/src/messages/layouts/MessageLayout.hpp index 81d7e403508..c5406ecd227 100644 --- a/src/messages/layouts/MessageLayout.hpp +++ b/src/messages/layouts/MessageLayout.hpp @@ -74,7 +74,7 @@ class MessageLayout : boost::noncopyable void updateBuffer(QPixmap *buffer, const MessagePaintContext &ctx); // Create new buffer if required, returning the buffer - QPixmap *ensureBuffer(QPainter &painter, int width); + QPixmap *ensureBuffer(int width, bool clear); // variables MessagePtr message_; diff --git a/src/messages/layouts/MessageLayoutContext.cpp b/src/messages/layouts/MessageLayoutContext.cpp index 82b158485fe..bc83a620e09 100644 --- a/src/messages/layouts/MessageLayoutContext.cpp +++ b/src/messages/layouts/MessageLayoutContext.cpp @@ -7,6 +7,8 @@ namespace chatterino { void MessageColors::applyTheme(Theme *theme) { + this->channelBackground = theme->splits.background; + this->regular = theme->messages.backgrounds.regular; this->alternate = theme->messages.backgrounds.alternate; @@ -18,6 +20,24 @@ void MessageColors::applyTheme(Theme *theme) this->focusedLastMessageLine = theme->tabs.selected.backgrounds.regular; this->unfocusedLastMessageLine = theme->tabs.selected.backgrounds.unfocused; + + this->hasTransparency = + this->regular.alpha() != 255 || this->alternate.alpha() != 255; +} + +void MessageColors::applyOverlay(Theme *theme) +{ + this->channelBackground = theme->overlayMessages.background; + + this->regular = theme->overlayMessages.backgrounds.regular; + this->alternate = theme->overlayMessages.backgrounds.alternate; + + this->disabled = theme->overlayMessages.disabled; + this->selection = theme->overlayMessages.selection; + this->system = theme->overlayMessages.textColors.system; + + this->hasTransparency = + this->regular.alpha() != 255 || this->alternate.alpha() != 255; } void MessagePreferences::connectSettings(Settings *settings, diff --git a/src/messages/layouts/MessageLayoutContext.hpp b/src/messages/layouts/MessageLayoutContext.hpp index a64d98bb448..ba628501c92 100644 --- a/src/messages/layouts/MessageLayoutContext.hpp +++ b/src/messages/layouts/MessageLayoutContext.hpp @@ -16,6 +16,11 @@ struct Selection; // TODO: Figure out if this could be a subset of Theme instead (e.g. Theme::MessageColors) struct MessageColors { + QColor channelBackground; + + // true if any of the background colors have transparency + bool hasTransparency = false; + QColor regular; QColor alternate; QColor disabled; @@ -28,6 +33,7 @@ struct MessageColors { QColor unfocusedLastMessageLine; void applyTheme(Theme *theme); + void applyOverlay(Theme *theme); }; // TODO: Explore if we can let settings own this diff --git a/src/singletons/Theme.cpp b/src/singletons/Theme.cpp index e8a110d538f..311487bc8a4 100644 --- a/src/singletons/Theme.cpp +++ b/src/singletons/Theme.cpp @@ -78,27 +78,44 @@ void parseTabs(const QJsonObject &tabs, chatterino::Theme &theme) parseTabColors(tabs["selected"_L1].toObject(), theme.tabs.selected); } +void parseTextColors(const QJsonObject &textColors, auto &messages) +{ + parseColor(messages, textColors, regular); + parseColor(messages, textColors, caret); + parseColor(messages, textColors, link); + parseColor(messages, textColors, system); + parseColor(messages, textColors, chatPlaceholder); +} + +void parseMessageBackgrounds(const QJsonObject &backgrounds, auto &messages) +{ + parseColor(messages, backgrounds, regular); + parseColor(messages, backgrounds, alternate); +} + void parseMessages(const QJsonObject &messages, chatterino::Theme &theme) { - { - const auto textColors = messages["textColors"_L1].toObject(); - parseColor(theme.messages, textColors, regular); - parseColor(theme.messages, textColors, caret); - parseColor(theme.messages, textColors, link); - parseColor(theme.messages, textColors, system); - parseColor(theme.messages, textColors, chatPlaceholder); - } - { - const auto backgrounds = messages["backgrounds"_L1].toObject(); - parseColor(theme.messages, backgrounds, regular); - parseColor(theme.messages, backgrounds, alternate); - } + parseTextColors(messages["textColors"_L1].toObject(), theme.messages); + parseMessageBackgrounds(messages["backgrounds"_L1].toObject(), + theme.messages); parseColor(theme, messages, disabled); parseColor(theme, messages, selection); parseColor(theme, messages, highlightAnimationStart); parseColor(theme, messages, highlightAnimationEnd); } +void parseOverlayMessages(const QJsonObject &overlayMessages, + chatterino::Theme &theme) +{ + parseTextColors(overlayMessages["textColors"_L1].toObject(), + theme.overlayMessages); + parseMessageBackgrounds(overlayMessages["backgrounds"_L1].toObject(), + theme.overlayMessages); + parseColor(theme, overlayMessages, disabled); + parseColor(theme, overlayMessages, selection); + parseColor(theme, overlayMessages, background); +} + void parseScrollbars(const QJsonObject &scrollbars, chatterino::Theme &theme) { parseColor(theme, scrollbars, background); @@ -142,6 +159,7 @@ void parseColors(const QJsonObject &root, chatterino::Theme &theme) parseWindow(colors["window"_L1].toObject(), theme); parseTabs(colors["tabs"_L1].toObject(), theme); parseMessages(colors["messages"_L1].toObject(), theme); + parseOverlayMessages(colors["overlayMessages"_L1].toObject(), theme); parseScrollbars(colors["scrollbars"_L1].toObject(), theme); parseSplits(colors["splits"_L1].toObject(), theme); } diff --git a/src/singletons/Theme.hpp b/src/singletons/Theme.hpp index d2165543c4a..f539f05aa71 100644 --- a/src/singletons/Theme.hpp +++ b/src/singletons/Theme.hpp @@ -61,6 +61,19 @@ class Theme final : public Singleton } line; }; + struct TextColors { + QColor regular; + QColor caret; + QColor link; + QColor system; + QColor chatPlaceholder; + }; + + struct MessageBackgrounds { + QColor regular; + QColor alternate; + }; + QColor accent{"#00aeef"}; /// WINDOW @@ -80,18 +93,8 @@ class Theme final : public Singleton /// MESSAGES struct { - struct { - QColor regular; - QColor caret; - QColor link; - QColor system; - QColor chatPlaceholder; - } textColors; - - struct { - QColor regular; - QColor alternate; - } backgrounds; + TextColors textColors; + MessageBackgrounds backgrounds; QColor disabled; QColor selection; @@ -100,6 +103,15 @@ class Theme final : public Singleton QColor highlightAnimationEnd; } messages; + struct { + TextColors textColors; + MessageBackgrounds backgrounds; + + QColor disabled; + QColor selection; + QColor background; + } overlayMessages; + /// SCROLLBAR struct { QColor background; diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp new file mode 100644 index 00000000000..fbd00ab50ab --- /dev/null +++ b/src/widgets/OverlayWindow.cpp @@ -0,0 +1,187 @@ +#include "widgets/OverlayWindow.hpp" + +#include "widgets/BaseWidget.hpp" +#include "widgets/helper/ChannelView.hpp" +#include "widgets/helper/TitlebarButton.hpp" + +#include +#include +#include +#include + +namespace { + +class Grippy : public QSizeGrip +{ +public: + Grippy(QWidget *parent) + : QSizeGrip(parent) + { + } + +protected: + void paintEvent(QPaintEvent *event) override + { + } +}; + +} // namespace + +namespace chatterino { + +OverlayWindow::OverlayWindow(ChannelPtr channel, Split *split) + : QWidget(nullptr, + Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint) + , channel_(std::move(channel)) + , channelView_(nullptr, split) + , interactAnimation_(this, QByteArrayLiteral("interactionProgress")) +{ + auto *grid = new QGridLayout(this); + grid->addWidget(&this->channelView_, 0, 0); + grid->addWidget(new Grippy(this), 0, 0, Qt::AlignBottom | Qt::AlignRight); + grid->addWidget(&this->closeButton_, 0, 0, Qt::AlignTop | Qt::AlignRight); + grid->setContentsMargins(0, 0, 0, 0); + + this->closeButton_.setButtonStyle(TitleBarButtonStyle::Close); + this->closeButton_.setScaleIndependantSize(46, 30); + this->closeButton_.hide(); + connect(&this->closeButton_, &TitleBarButton::leftClicked, [this]() { + this->close(); + }); + + this->channelView_.installEventFilter(this); + this->channelView_.setChannel(this->channel_); + this->channelView_.setColorVisitor([](MessageColors &colors, Theme *theme) { + colors.applyOverlay(theme); + }); + this->channelView_.setAttribute(Qt::WA_TranslucentBackground); + + this->setAutoFillBackground(false); + this->resize(300, 500); + this->move(QCursor::pos() - this->rect().center()); + this->setContentsMargins(0, 0, 0, 0); + this->setAttribute(Qt::WA_TranslucentBackground); + + this->interactAnimation_.setStartValue(0.0); + this->interactAnimation_.setEndValue(1.0); + this->interactAnimation_.setDuration(150); +} + +bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) +{ + switch (event->type()) + { + case QEvent::MouseButtonPress: { + auto *evt = dynamic_cast(event); + if (evt->modifiers().testFlag(Qt::ShiftModifier)) + { + this->moving_ = true; + this->moveOrigin_ = evt->globalPos(); + return true; + } + return false; + } + break; + case QEvent::MouseButtonRelease: { + if (this->moving_) + { + this->moving_ = false; + return true; + } + return false; + } + break; + case QEvent::MouseMove: { + auto *evt = dynamic_cast(event); + if (this->moving_) + { + auto newPos = evt->globalPos() - this->moveOrigin_; + this->move(newPos + this->pos()); + this->moveOrigin_ = evt->globalPos(); + return true; + } + return false; + } + break; + default: + return false; + } +} + +void OverlayWindow::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Shift) + { + this->startInteraction(); + } +} + +void OverlayWindow::keyReleaseEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Shift) + { + this->endInteraction(); + } +} + +void OverlayWindow::paintEvent(QPaintEvent *event) +{ + if (this->interactionProgress() <= 0.0) + { + QWidget::paintEvent(event); + return; + } + + QPainter painter(this); + QColor highlightColor(255, 255, 255, + int(255.0 * this->interactionProgress())); + painter.setPen({highlightColor, 2}); + painter.drawRect(this->rect()); + + painter.setBrush(highlightColor); + painter.setPen(Qt::transparent); + auto br = this->rect().bottomRight(); + std::array triangle = {br - QPoint{10, 0}, br, + br - QPoint{0, 10}}; + painter.drawPolygon(triangle.data(), triangle.size()); + + painter.end(); + + QWidget::paintEvent(event); +} + +double OverlayWindow::interactionProgress() const +{ + return this->interactionProgress_; +} + +void OverlayWindow::setInteractionProgress(double progress) +{ + this->interactionProgress_ = progress; +} + +void OverlayWindow::startInteraction() +{ + if (this->interactAnimation_.state() != QPropertyAnimation::Stopped) + { + this->interactAnimation_.stop(); + } + this->interactAnimation_.setDirection(QPropertyAnimation::Forward); + this->interactAnimation_.start(); + this->setCursor(Qt::DragMoveCursor); + this->closeButton_.show(); +} + +void OverlayWindow::endInteraction() +{ + if (this->interactAnimation_.state() != QPropertyAnimation::Stopped) + { + this->interactAnimation_.stop(); + } + this->interactAnimation_.setDirection(QPropertyAnimation::Backward); + this->interactAnimation_.start(); + this->setCursor(Qt::ArrowCursor); + this->closeButton_.hide(); +} + +} // namespace chatterino diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp new file mode 100644 index 00000000000..f52f0fe2440 --- /dev/null +++ b/src/widgets/OverlayWindow.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include "common/Channel.hpp" +#include "widgets/helper/ChannelView.hpp" +#include "widgets/helper/TitlebarButton.hpp" + +#include +#include + +namespace chatterino { + +class OverlayWindow : public QWidget +{ + Q_OBJECT +public: + OverlayWindow(ChannelPtr channel, Split *split); + +protected: + bool eventFilter(QObject *object, QEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + +private: + Q_PROPERTY(double interactionProgress READ interactionProgress WRITE + setInteractionProgress) + + double interactionProgress() const; + void setInteractionProgress(double progress); + + void startInteraction(); + void endInteraction(); + + ChannelPtr channel_; + ChannelView channelView_; + + bool moving_ = false; + QPoint moveOrigin_; + + double interactionProgress_ = 0.0; + QPropertyAnimation interactAnimation_; + + TitleBarButton closeButton_; +}; + +} // namespace chatterino diff --git a/src/widgets/Window.cpp b/src/widgets/Window.cpp index 504ee746ee2..49186844246 100644 --- a/src/widgets/Window.cpp +++ b/src/widgets/Window.cpp @@ -24,6 +24,7 @@ #include "widgets/helper/NotebookTab.hpp" #include "widgets/helper/TitlebarButton.hpp" #include "widgets/Notebook.hpp" +#include "widgets/OverlayWindow.hpp" #include "widgets/splits/ClosedSplits.hpp" #include "widgets/splits/Split.hpp" #include "widgets/splits/SplitContainer.hpp" @@ -399,10 +400,23 @@ void Window::addShortcuts() } else { - return "Invalid popup target. Use \"split\" or \"window\"."; + return "Invalid popup target. Use \"split\" or " + "\"window\"."; } return ""; }}, + {"popupOverlay", + [this](const auto &) -> QString { + if (auto *page = dynamic_cast( + this->notebook_->getSelectedPage())) + { + if (auto *split = page->getSelectedSplit()) + { + (new OverlayWindow(split->getChannel(), split))->show(); + } + } + return {}; + }}, {"zoom", [](std::vector arguments) -> QString { if (arguments.size() == 0) diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 89fd07502bc..71d7038ad88 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -384,6 +384,18 @@ void ChannelView::themeChangedEvent() this->setupHighlightAnimationColors(); this->queueLayout(); this->messageColors_.applyTheme(getTheme()); + if (this->colorVisitor_) + { + this->colorVisitor_(this->messageColors_, getTheme()); + } +} + +void ChannelView::setColorVisitor( + const std::function &visitor) +{ + Q_ASSERT_X(this->colorVisitor_ == nullptr, "ChannelView::setColorVisitor", + "The color visitor should only be set once."); + this->colorVisitor_ = visitor; } void ChannelView::setupHighlightAnimationColors() @@ -1225,7 +1237,7 @@ void ChannelView::paintEvent(QPaintEvent * /*event*/) QPainter painter(this); - painter.fillRect(rect(), this->theme->splits.background); + painter.fillRect(rect(), this->messageColors_.channelBackground); // draw messages this->drawMessages(painter); diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index f5a7da1742a..f01ee9e4f4e 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -151,6 +151,9 @@ class ChannelView final : public BaseWidget */ bool mayContainMessage(const MessagePtr &message); + void setColorVisitor( + const std::function &visitor); + pajlada::Signals::Signal mouseDown; pajlada::Signals::NoArgSignal selectionChanged; pajlada::Signals::Signal tabHighlightRequested; @@ -347,6 +350,8 @@ class ChannelView final : public BaseWidget MessageColors messageColors_; MessagePreferences messagePreferences_; + std::function colorVisitor_; + static constexpr int leftPadding = 8; static constexpr int scrollbarPadding = 8; From 6534c5e10905e805538eb7762ef2825ae3731c75 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Wed, 2 Aug 2023 20:00:37 +0200 Subject: [PATCH 02/62] chore: add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f8c817dcf5..3c757d8a3b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - Minor: 7TV badges now automatically update upon changing. (#4512) - Minor: Stream status requests are now batched. (#4713) - Minor: Added `/c2-theme-autoreload` command to automatically reload a custom theme. This is useful for when you're developing your own theme. (#4718) +- Minor: Added transparent overlay window (default keybind: `CTRL + ALT + N`). (#4746) - Bugfix: Increased amount of blocked users loaded from 100 to 1,000. (#4721) - Bugfix: Fixed generation of crashdumps by the browser-extension process when the browser was closed. (#4667) - Bugfix: Fix spacing issue with mentions inside RTL text. (#4677) From 9b45a7c14f8e38a77bca0446af6d4d6be3a14361 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Wed, 2 Aug 2023 20:15:39 +0200 Subject: [PATCH 03/62] fix: yea but painter --- src/messages/layouts/MessageLayout.cpp | 7 ++++--- src/messages/layouts/MessageLayout.hpp | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/messages/layouts/MessageLayout.cpp b/src/messages/layouts/MessageLayout.cpp index 51fc44c5740..fb5fed0220a 100644 --- a/src/messages/layouts/MessageLayout.cpp +++ b/src/messages/layouts/MessageLayout.cpp @@ -198,8 +198,8 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags) // Painting void MessageLayout::paint(const MessagePaintContext &ctx) { - QPixmap *pixmap = - this->ensureBuffer(ctx.canvasWidth, ctx.messageColors.hasTransparency); + QPixmap *pixmap = this->ensureBuffer(ctx.painter, ctx.canvasWidth, + ctx.messageColors.hasTransparency); if (!this->bufferValid_ || !ctx.selection.isEmpty()) { @@ -277,7 +277,7 @@ void MessageLayout::paint(const MessagePaintContext &ctx) this->bufferValid_ = true; } -QPixmap *MessageLayout::ensureBuffer(int width, bool clear) +QPixmap *MessageLayout::ensureBuffer(QPainter &painter, int width, bool clear) { if (this->buffer_ != nullptr) { @@ -292,6 +292,7 @@ QPixmap *MessageLayout::ensureBuffer(int width, bool clear) painter.device()->devicePixelRatioF())); this->buffer_->setDevicePixelRatio(painter.device()->devicePixelRatioF()); #else + Q_UNUSED(painter); this->buffer_ = std::make_unique( width, std::max(16, this->container_.getHeight())); #endif diff --git a/src/messages/layouts/MessageLayout.hpp b/src/messages/layouts/MessageLayout.hpp index c5406ecd227..97faac30894 100644 --- a/src/messages/layouts/MessageLayout.hpp +++ b/src/messages/layouts/MessageLayout.hpp @@ -74,7 +74,7 @@ class MessageLayout : boost::noncopyable void updateBuffer(QPixmap *buffer, const MessagePaintContext &ctx); // Create new buffer if required, returning the buffer - QPixmap *ensureBuffer(int width, bool clear); + QPixmap *ensureBuffer(QPainter &painter, int width, bool clear); // variables MessagePtr message_; From 0685e38654561d3c8734e8c20206ed68a38b091b Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Wed, 2 Aug 2023 20:26:53 +0200 Subject: [PATCH 04/62] fix: PCH thingy --- src/widgets/OverlayWindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index fbd00ab50ab..dfde49be415 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -9,6 +9,8 @@ #include #include +#include + namespace { class Grippy : public QSizeGrip From 32dbdcc1ad6b86fe772f292aca38cc6f1ef67c5f Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Thu, 3 Aug 2023 10:09:57 +0200 Subject: [PATCH 05/62] fix: use indirect channel --- src/widgets/OverlayWindow.cpp | 7 +++++-- src/widgets/OverlayWindow.hpp | 7 +++++-- src/widgets/Window.cpp | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index dfde49be415..6767e6f9c88 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -31,7 +31,7 @@ class Grippy : public QSizeGrip namespace chatterino { -OverlayWindow::OverlayWindow(ChannelPtr channel, Split *split) +OverlayWindow::OverlayWindow(IndirectChannel channel, Split *split) : QWidget(nullptr, Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint) , channel_(std::move(channel)) @@ -52,11 +52,14 @@ OverlayWindow::OverlayWindow(ChannelPtr channel, Split *split) }); this->channelView_.installEventFilter(this); - this->channelView_.setChannel(this->channel_); + this->channelView_.setChannel(this->channel_.get()); this->channelView_.setColorVisitor([](MessageColors &colors, Theme *theme) { colors.applyOverlay(theme); }); this->channelView_.setAttribute(Qt::WA_TranslucentBackground); + this->holder_.managedConnect(this->channel_.getChannelChanged(), [this]() { + this->channelView_.setChannel(this->channel_.get()); + }); this->setAutoFillBackground(false); this->resize(300, 500); diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index f52f0fe2440..ce12ffd9c66 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -6,6 +6,8 @@ #include #include +#include +#include namespace chatterino { @@ -13,7 +15,7 @@ class OverlayWindow : public QWidget { Q_OBJECT public: - OverlayWindow(ChannelPtr channel, Split *split); + OverlayWindow(IndirectChannel channel, Split *split); protected: bool eventFilter(QObject *object, QEvent *event) override; @@ -31,7 +33,8 @@ class OverlayWindow : public QWidget void startInteraction(); void endInteraction(); - ChannelPtr channel_; + IndirectChannel channel_; + pajlada::Signals::SignalHolder holder_; ChannelView channelView_; bool moving_ = false; diff --git a/src/widgets/Window.cpp b/src/widgets/Window.cpp index 49186844246..9904cc33663 100644 --- a/src/widgets/Window.cpp +++ b/src/widgets/Window.cpp @@ -412,7 +412,8 @@ void Window::addShortcuts() { if (auto *split = page->getSelectedSplit()) { - (new OverlayWindow(split->getChannel(), split))->show(); + (new OverlayWindow(split->getIndirectChannel(), split)) + ->show(); } } return {}; From 665b3769e06c9b5ead8a0a6cc7c08c65312c8598 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Thu, 3 Aug 2023 14:15:03 +0200 Subject: [PATCH 06/62] fix: use weak references to splits --- src/widgets/dialogs/ReplyThreadPopup.cpp | 77 +++++++++++++++--------- src/widgets/dialogs/ReplyThreadPopup.hpp | 6 +- src/widgets/dialogs/UserInfoPopup.cpp | 6 +- src/widgets/dialogs/UserInfoPopup.hpp | 5 +- src/widgets/helper/ChannelView.cpp | 25 +++++--- src/widgets/helper/ChannelView.hpp | 5 +- 6 files changed, 78 insertions(+), 46 deletions(-) diff --git a/src/widgets/dialogs/ReplyThreadPopup.cpp b/src/widgets/dialogs/ReplyThreadPopup.cpp index b2abd5fd9d8..c9281277818 100644 --- a/src/widgets/dialogs/ReplyThreadPopup.cpp +++ b/src/widgets/dialogs/ReplyThreadPopup.cpp @@ -19,15 +19,21 @@ #include +#include + const QString TEXT_TITLE("Reply Thread - @%1 in #%2"); namespace chatterino { ReplyThreadPopup::ReplyThreadPopup(bool closeAutomatically, QWidget *parent, - Split *split) + ChannelPtr channel, QPointer split) : DraggablePopup(closeAutomatically, parent) - , split_(split) + , channel_(std::move(channel)) + , split_(std::move(split)) { + Q_ASSERT_X(this->channel_ != nullptr, "ReplyThreadPopup", + "A reply thread popup must have a source channel"); + this->setWindowTitle(QStringLiteral("Reply Thread")); this->setStayInScreenRect(true); @@ -90,8 +96,26 @@ ReplyThreadPopup::ReplyThreadPopup(bool closeAutomatically, QWidget *parent, }); // Create SplitInput with inline replying disabled - this->ui_.replyInput = - new SplitInput(this, this->split_, this->ui_.threadView, false); + if (this->split_) + { + this->ui_.replyInput = + new SplitInput(this, this->split_, this->ui_.threadView, false); + // remove the input when the split is removed + connect(this->split_, &QObject::destroyed, this, [this]() { + if (this->ui_.replyInput) + { + this->ui_.replyInput->deleteLater(); + this->ui_.replyInput = nullptr; + } + }); + // clear ChannelView selection when selecting in SplitInput + this->ui_.replyInput->selectionChanged.connect([this]() { + if (this->ui_.replyInput && this->ui_.threadView->hasSelection()) + { + this->ui_.threadView->clearSelection(); + } + }); + } this->bSignals_.emplace_back( getApp()->accounts->twitch.currentUserChanged.connect([this] { @@ -100,20 +124,12 @@ ReplyThreadPopup::ReplyThreadPopup(bool closeAutomatically, QWidget *parent, // clear SplitInput selection when selecting in ChannelView this->ui_.threadView->selectionChanged.connect([this]() { - if (this->ui_.replyInput->hasSelection()) + if (this->ui_.replyInput && this->ui_.replyInput->hasSelection()) { this->ui_.replyInput->clearSelection(); } }); - // clear ChannelView selection when selecting in SplitInput - this->ui_.replyInput->selectionChanged.connect([this]() { - if (this->ui_.threadView->hasSelection()) - { - this->ui_.threadView->clearSelection(); - } - }); - auto layout = LayoutCreator(this->getLayoutContainer()) .setLayoutType(); @@ -167,13 +183,19 @@ ReplyThreadPopup::ReplyThreadPopup(bool closeAutomatically, QWidget *parent, } layout->addWidget(this->ui_.threadView, 1); - layout->addWidget(this->ui_.replyInput); + if (this->ui_.replyInput) + { + layout->addWidget(this->ui_.replyInput); + } } void ReplyThreadPopup::setThread(std::shared_ptr thread) { this->thread_ = std::move(thread); - this->ui_.replyInput->setReply(this->thread_); + if (this->ui_.replyInput) + { + this->ui_.replyInput->setReply(this->thread_); + } this->addMessagesFromThread(); this->updateInputUI(); @@ -204,23 +226,22 @@ void ReplyThreadPopup::addMessagesFromThread() return; } - const auto &sourceChannel = this->split_->getChannel(); this->setWindowTitle(TEXT_TITLE.arg(this->thread_->root()->loginName, - sourceChannel->getName())); + this->channel_->getName())); - if (sourceChannel->isTwitchChannel()) + if (this->channel_->isTwitchChannel()) { this->virtualChannel_ = - std::make_shared(sourceChannel->getName()); + std::make_shared(this->channel_->getName()); } else { this->virtualChannel_ = std::make_shared( - sourceChannel->getName(), Channel::Type::None); + this->channel_->getName(), Channel::Type::None); } this->ui_.threadView->setChannel(this->virtualChannel_); - this->ui_.threadView->setSourceChannel(sourceChannel); + this->ui_.threadView->setSourceChannel(this->channel_); auto overrideFlags = boost::optional(this->thread_->root()->flags); @@ -240,8 +261,8 @@ void ReplyThreadPopup::addMessagesFromThread() this->messageConnection_ = std::make_unique( - sourceChannel->messageAppended.connect([this](MessagePtr &message, - auto) { + this->channel_->messageAppended.connect([this](MessagePtr &message, + auto) { if (message->replyThread == this->thread_) { auto overrideFlags = @@ -256,15 +277,14 @@ void ReplyThreadPopup::addMessagesFromThread() void ReplyThreadPopup::updateInputUI() { - auto channel = this->split_->getChannel(); // Bail out if not a twitch channel. // Special twitch channels will hide their reply input box. - if (!channel || !channel->isTwitchChannel()) + if (!this->channel_->isTwitchChannel() || !this->ui_.replyInput) { return; } - this->ui_.replyInput->setVisible(channel->isWritable()); + this->ui_.replyInput->setVisible(this->channel_->isWritable()); auto user = getApp()->accounts->twitch.getCurrent(); QString placeholderText; @@ -285,7 +305,10 @@ void ReplyThreadPopup::updateInputUI() void ReplyThreadPopup::giveFocus(Qt::FocusReason reason) { - this->ui_.replyInput->giveFocus(reason); + if (this->ui_.replyInput) + { + this->ui_.replyInput->giveFocus(reason); + } } void ReplyThreadPopup::focusInEvent(QFocusEvent *event) diff --git a/src/widgets/dialogs/ReplyThreadPopup.hpp b/src/widgets/dialogs/ReplyThreadPopup.hpp index 567812ce016..6e4c7da33ad 100644 --- a/src/widgets/dialogs/ReplyThreadPopup.hpp +++ b/src/widgets/dialogs/ReplyThreadPopup.hpp @@ -6,6 +6,7 @@ #include #include #include +#include class QCheckBox; @@ -20,7 +21,8 @@ class ReplyThreadPopup final : public DraggablePopup Q_OBJECT public: - ReplyThreadPopup(bool closeAutomatically, QWidget *parent, Split *split); + ReplyThreadPopup(bool closeAutomatically, QWidget *parent, + ChannelPtr channel_, QPointer split); void setThread(std::shared_ptr thread); void giveFocus(Qt::FocusReason reason); @@ -38,7 +40,7 @@ class ReplyThreadPopup final : public DraggablePopup ChannelPtr channel_; // The channel for the `threadView` ChannelPtr virtualChannel_; - Split *split_; + QPointer split_; struct { ChannelView *threadView = nullptr; diff --git a/src/widgets/dialogs/UserInfoPopup.cpp b/src/widgets/dialogs/UserInfoPopup.cpp index 2c0fa10fd1f..3958999ad4d 100644 --- a/src/widgets/dialogs/UserInfoPopup.cpp +++ b/src/widgets/dialogs/UserInfoPopup.cpp @@ -134,13 +134,11 @@ namespace { } // namespace UserInfoPopup::UserInfoPopup(bool closeAutomatically, QWidget *parent, - Split *split) + QPointer split) : DraggablePopup(closeAutomatically, parent) - , split_(split) + , split_(std::move(split)) , closeAutomatically_(closeAutomatically) { - assert(split != nullptr && - "split being nullptr causes lots of bugs down the road"); this->setWindowTitle("Usercard"); this->setStayInScreenRect(true); diff --git a/src/widgets/dialogs/UserInfoPopup.hpp b/src/widgets/dialogs/UserInfoPopup.hpp index 831f96da7b8..10e7346437f 100644 --- a/src/widgets/dialogs/UserInfoPopup.hpp +++ b/src/widgets/dialogs/UserInfoPopup.hpp @@ -4,6 +4,7 @@ #include #include +#include #include @@ -23,7 +24,7 @@ class UserInfoPopup final : public DraggablePopup public: UserInfoPopup(bool closeAutomatically, QWidget *parent, - Split *split = nullptr); + QPointer split = {}); void setData(const QString &name, const ChannelPtr &channel); void setData(const QString &name, const ChannelPtr &contextChannel, @@ -42,7 +43,7 @@ class UserInfoPopup final : public DraggablePopup bool isMod_; bool isBroadcaster_; - Split *split_; + QPointer split_; QString userName_; QString userId_; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 71d7038ad88..a442e37454a 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -145,10 +145,10 @@ namespace { } } // namespace -ChannelView::ChannelView(BaseWidget *parent, Split *split, Context context, - size_t messagesLimit) +ChannelView::ChannelView(BaseWidget *parent, QPointer split, + Context context, size_t messagesLimit) : BaseWidget(parent) - , split_(split) + , split_(std::move(split)) , scrollBar_(new Scrollbar(messagesLimit, this)) , highlightAnimation_(this) , context_(context) @@ -1184,7 +1184,10 @@ bool ChannelView::scrollToMessage(const MessagePtr &message) } this->scrollToMessageLayout(messagesSnapshot[messageIdx].get(), messageIdx); - getApp()->windows->select(this->split_); + if (!this->split_.isNull()) + { + getApp()->windows->select(this->split_); + } return true; } @@ -1213,7 +1216,10 @@ bool ChannelView::scrollToMessageId(const QString &messageId) } this->scrollToMessageLayout(messagesSnapshot[messageIdx].get(), messageIdx); - getApp()->windows->select(this->split_); + if (!this->split_.isNull()) + { + getApp()->windows->select(this->split_); + } return true; } @@ -2388,7 +2394,7 @@ void ChannelView::addMessageContextMenuItems( bool isSearch = this->context_ == Context::Search; bool isReplyOrUserCard = (this->context_ == Context::ReplyThread || this->context_ == Context::UserCard) && - this->split_; + !this->split_.isNull(); bool isMentions = this->channel()->getType() == Channel::Type::TwitchMentions; if (isSearch || isMentions || isReplyOrUserCard) @@ -2959,7 +2965,7 @@ void ChannelView::scrollUpdateRequested() void ChannelView::setInputReply(const MessagePtr &message) { - if (message == nullptr) + if (message == nullptr || this->split_.isNull()) { return; } @@ -3016,8 +3022,9 @@ void ChannelView::showReplyThreadPopup(const MessagePtr &message) auto popupParent = static_cast(&(getApp()->windows->getMainWindow())); - auto popup = new ReplyThreadPopup(getSettings()->autoCloseThreadPopup, - popupParent, this->split_); + auto popup = + new ReplyThreadPopup(getSettings()->autoCloseThreadPopup, popupParent, + this->underlyingChannel_, this->split_); popup->setThread(message->replyThread); diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index f01ee9e4f4e..4d0773f7675 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -74,7 +74,8 @@ class ChannelView final : public BaseWidget Search, }; - explicit ChannelView(BaseWidget *parent = nullptr, Split *split = nullptr, + explicit ChannelView(BaseWidget *parent = nullptr, + QPointer split = {}, Context context = Context::None, size_t messagesLimit = 1000); @@ -286,7 +287,7 @@ class ChannelView final : public BaseWidget ChannelPtr channel_ = nullptr; ChannelPtr underlyingChannel_ = nullptr; ChannelPtr sourceChannel_ = nullptr; - Split *split_ = nullptr; + QPointer split_; Scrollbar *scrollBar_; EffectLabel *goToBottom_; From e7fbe18ee79f2f732ab824f28ec45ad858ac551d Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Thu, 3 Aug 2023 14:26:51 +0200 Subject: [PATCH 07/62] chore: add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c757d8a3b8..02e8482ac95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ - Bugfix: Fixed highlights sometimes not working after changing sound device, or switching users in your operating system. (#4729) - Bugfix: Fixed key bindings not showing in context menus on Mac. (#4722) - Bugfix: Fixed tab completion rarely completing the wrong word. (#4735) +- Bugfix: Fixed crashes when interacting with dialogs originating from splits that were deleted in the meantime. (#4747) - Dev: Added command to set Qt's logging filter/rules at runtime (`/c2-set-logging-rules`). (#4637) - Dev: Added the ability to see & load custom themes from the Themes directory. No stable promises are made of this feature, changes might be made that breaks custom themes without notice. (#4570) - Dev: Added test cases for emote and tab completion. (#4644) From 4d63b7f0e27a95c4414d7389b70fd279d67129a6 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Thu, 3 Aug 2023 14:52:51 +0200 Subject: [PATCH 08/62] fix: qt5 --- src/widgets/helper/ChannelView.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index 4d0773f7675..3af10cf32f6 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include #include + namespace chatterino { enum class HighlightState; From 8dd5971c2699796bce595bb120fa9f11f9285376 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Thu, 3 Aug 2023 15:01:56 +0200 Subject: [PATCH 09/62] fix: no idea why clangd/-format does this --- src/widgets/helper/ChannelView.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index 3af10cf32f6..e766f55022d 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -21,7 +21,6 @@ #include #include - namespace chatterino { enum class HighlightState; From 37c605426b06f321b0d86b414de4eb5ddd1cc299 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Wed, 16 Aug 2023 11:53:39 +0200 Subject: [PATCH 10/62] fix: include split --- src/widgets/OverlayWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 6767e6f9c88..d5da48967cc 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -3,6 +3,7 @@ #include "widgets/BaseWidget.hpp" #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/TitlebarButton.hpp" +#include "widgets/splits/Split.hpp" #include #include From 80707ab13ed8246f7af9dec8f072c29453b6c30d Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Wed, 16 Aug 2023 12:58:51 +0200 Subject: [PATCH 11/62] feat: add drop shadow --- docs/ChatterinoTheme.schema.json | 22 ++++++++++++++++-- resources/themes/Black.json | 7 +++++- resources/themes/Dark.json | 7 +++++- resources/themes/Light.json | 7 +++++- resources/themes/White.json | 7 +++++- src/singletons/Theme.cpp | 40 ++++++++++++++++++++++++++++++++ src/singletons/Theme.hpp | 6 +++++ src/widgets/OverlayWindow.cpp | 13 +++++++++++ src/widgets/OverlayWindow.hpp | 6 +++-- 9 files changed, 107 insertions(+), 8 deletions(-) diff --git a/docs/ChatterinoTheme.schema.json b/docs/ChatterinoTheme.schema.json index 44398866ac3..ca21221f006 100644 --- a/docs/ChatterinoTheme.schema.json +++ b/docs/ChatterinoTheme.schema.json @@ -256,6 +256,13 @@ "selection", "textColors" ] + }, + "qpointf": { + "type": "array", + "items": { "type": "number" }, + "minItems": 2, + "maxItems": 2, + "additionalItems": false } }, "type": "object", @@ -294,14 +301,25 @@ "disabled": { "$ref": "#/definitions/qt-color" }, "selection": { "$ref": "#/definitions/qt-color" }, "textColors": { "$ref": "#/definitions/text-colors" }, - "background": { "$ref": "#/definitions/qt-color" } + "background": { "$ref": "#/definitions/qt-color" }, + "shadow": { + "type": "object", + "additionalProperties": false, + "properties": { + "color": { "$ref": "#/definitions/qt-color" }, + "offset": { "$ref": "#/definitions/qpointf" }, + "blurRadius": { "type": "number", "minimum": 0 } + }, + "required": ["color", "offset", "blurRadius"] + } }, "required": [ "backgrounds", "disabled", "selection", "textColors", - "background" + "background", + "shadow" ] }, "scrollbars": { diff --git a/resources/themes/Black.json b/resources/themes/Black.json index 42f60c7d018..e503fc8cf6f 100644 --- a/resources/themes/Black.json +++ b/resources/themes/Black.json @@ -36,7 +36,12 @@ "regular": "#ffffff", "system": "#8c7f7f" }, - "background": "#32000000" + "background": "#32000000", + "shadow": { + "color": "#000", + "offset": [2, 2], + "blurRadius": 8 + } }, "scrollbars": { "background": "#00000000", diff --git a/resources/themes/Dark.json b/resources/themes/Dark.json index c417bda17a1..37a367a458a 100644 --- a/resources/themes/Dark.json +++ b/resources/themes/Dark.json @@ -36,7 +36,12 @@ "regular": "#ffffff", "system": "#8c7f7f" }, - "background": "#32000000" + "background": "#32000000", + "shadow": { + "color": "#000", + "offset": [2, 2], + "blurRadius": 8 + } }, "scrollbars": { "background": "#00000000", diff --git a/resources/themes/Light.json b/resources/themes/Light.json index f0c5d7115be..821da7bd162 100644 --- a/resources/themes/Light.json +++ b/resources/themes/Light.json @@ -36,7 +36,12 @@ "regular": "#000000", "system": "#8c7f7f" }, - "background": "#32ffffff" + "background": "#32ffffff", + "shadow": { + "color": "#000", + "offset": [2, 2], + "blurRadius": 8 + } }, "scrollbars": { "background": "#00000000", diff --git a/resources/themes/White.json b/resources/themes/White.json index 2b6dc731cc4..227538d807e 100644 --- a/resources/themes/White.json +++ b/resources/themes/White.json @@ -36,7 +36,12 @@ "regular": "#000000", "system": "#8c7f7f" }, - "background": "#32ffffff" + "background": "#32ffffff", + "shadow": { + "color": "#000", + "offset": [2, 2], + "blurRadius": 8 + } }, "scrollbars": { "background": "#00000000", diff --git a/src/singletons/Theme.cpp b/src/singletons/Theme.cpp index 311487bc8a4..54081da0ddb 100644 --- a/src/singletons/Theme.cpp +++ b/src/singletons/Theme.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -42,6 +43,38 @@ void parseInto(const QJsonObject &obj, QLatin1String key, QColor &color) color = parsed; } +void parseInto(const QJsonObject &obj, QLatin1String key, QPointF &point) +{ + const auto &jsonValue = obj[key]; + if (!jsonValue.isArray()) [[unlikely]] + { + qCWarning(chatterinoTheme) << key + << "was expected but not found in the " + "current theme - using previous value."; + return; + } + auto arr = jsonValue.toArray(); + if (arr.size() != 2) [[unlikely]] + { + qCWarning(chatterinoTheme) << key << "must be an array of two numbers."; + return; + } + point = {arr[0].toDouble(), arr[1].toDouble()}; +} + +void parseInto(const QJsonObject &obj, QLatin1String key, double &target) +{ + const auto &jsonValue = obj[key]; + if (!jsonValue.isDouble()) [[unlikely]] + { + qCWarning(chatterinoTheme) << key + << "was expected but not found in the " + "current theme - using previous value."; + return; + } + target = jsonValue.toDouble(); +} + // NOLINTBEGIN(cppcoreguidelines-macro-usage) #define _c2StringLit(s, ty) s##ty #define parseColor(to, from, key) \ @@ -114,6 +147,13 @@ void parseOverlayMessages(const QJsonObject &overlayMessages, parseColor(theme, overlayMessages, disabled); parseColor(theme, overlayMessages, selection); parseColor(theme, overlayMessages, background); + + { + const auto shadow = overlayMessages["shadow"_L1].toObject(); + parseColor(theme.overlayMessages, shadow, color); + parseColor(theme.overlayMessages, shadow, offset); + parseColor(theme.overlayMessages, shadow, blurRadius); + } } void parseScrollbars(const QJsonObject &scrollbars, chatterino::Theme &theme) diff --git a/src/singletons/Theme.hpp b/src/singletons/Theme.hpp index f539f05aa71..250a146b766 100644 --- a/src/singletons/Theme.hpp +++ b/src/singletons/Theme.hpp @@ -110,6 +110,12 @@ class Theme final : public Singleton QColor disabled; QColor selection; QColor background; + + struct { + QColor color; + QPointF offset; + double blurRadius = 8.0; + } shadow; } overlayMessages; /// SCROLLBAR diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index d5da48967cc..182a9e32e66 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -1,5 +1,6 @@ #include "widgets/OverlayWindow.hpp" +#include "singletons/Theme.hpp" #include "widgets/BaseWidget.hpp" #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/TitlebarButton.hpp" @@ -7,6 +8,7 @@ #include #include +#include #include #include @@ -61,6 +63,7 @@ OverlayWindow::OverlayWindow(IndirectChannel channel, Split *split) this->holder_.managedConnect(this->channel_.getChannelChanged(), [this]() { this->channelView_.setChannel(this->channel_.get()); }); + this->channelView_.setGraphicsEffect(&this->dropShadow_); this->setAutoFillBackground(false); this->resize(300, 500); @@ -71,6 +74,16 @@ OverlayWindow::OverlayWindow(IndirectChannel channel, Split *split) this->interactAnimation_.setStartValue(0.0); this->interactAnimation_.setEndValue(1.0); this->interactAnimation_.setDuration(150); + + auto applyTheme = [this]() { + auto *theme = getTheme(); + this->dropShadow_.setColor(theme->overlayMessages.shadow.color); + this->dropShadow_.setOffset(theme->overlayMessages.shadow.offset); + this->dropShadow_.setBlurRadius( + theme->overlayMessages.shadow.blurRadius); + }; + applyTheme(); + this->holder_.managedConnect(getTheme()->updated, applyTheme); } bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index ce12ffd9c66..7968291e5cd 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -4,10 +4,11 @@ #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/TitlebarButton.hpp" -#include -#include #include #include +#include +#include +#include namespace chatterino { @@ -36,6 +37,7 @@ class OverlayWindow : public QWidget IndirectChannel channel_; pajlada::Signals::SignalHolder holder_; ChannelView channelView_; + QGraphicsDropShadowEffect dropShadow_; bool moving_ = false; QPoint moveOrigin_; From 200d1c232ee0521305f0c32d36dd4af858f689a1 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Wed, 16 Aug 2023 13:20:07 +0200 Subject: [PATCH 12/62] feat: add setting for shadow --- src/singletons/Settings.hpp | 2 ++ src/widgets/OverlayWindow.cpp | 32 ++++++++++++++++++----- src/widgets/OverlayWindow.hpp | 5 ++-- src/widgets/settingspages/GeneralPage.cpp | 5 ++++ 4 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index d3a1e3894c2..ca8aa7aa07d 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -171,6 +171,8 @@ class Settings : public ABSettings, public ConcurrentSettings // BoolSetting useCustomWindowFrame = {"/appearance/useCustomWindowFrame", // false}; + BoolSetting enableOverlayShadow = {"/appearance/overlay/shadow", true}; + // Badges BoolSetting showBadgesGlobalAuthority = { "/appearance/badges/GlobalAuthority", true}; diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 182a9e32e66..1313c2dd8fd 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -1,5 +1,7 @@ #include "widgets/OverlayWindow.hpp" +#include "BaseSettings.hpp" +#include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "widgets/BaseWidget.hpp" #include "widgets/helper/ChannelView.hpp" @@ -8,12 +10,14 @@ #include #include +#include #include #include #include #include + namespace { class Grippy : public QSizeGrip @@ -63,7 +67,6 @@ OverlayWindow::OverlayWindow(IndirectChannel channel, Split *split) this->holder_.managedConnect(this->channel_.getChannelChanged(), [this]() { this->channelView_.setChannel(this->channel_.get()); }); - this->channelView_.setGraphicsEffect(&this->dropShadow_); this->setAutoFillBackground(false); this->resize(300, 500); @@ -75,15 +78,30 @@ OverlayWindow::OverlayWindow(IndirectChannel channel, Split *split) this->interactAnimation_.setEndValue(1.0); this->interactAnimation_.setDuration(150); - auto applyTheme = [this]() { + auto applyDropShadowTheme = [this]() { auto *theme = getTheme(); - this->dropShadow_.setColor(theme->overlayMessages.shadow.color); - this->dropShadow_.setOffset(theme->overlayMessages.shadow.offset); - this->dropShadow_.setBlurRadius( + this->dropShadow_->setColor(theme->overlayMessages.shadow.color); + this->dropShadow_->setOffset(theme->overlayMessages.shadow.offset); + this->dropShadow_->setBlurRadius( theme->overlayMessages.shadow.blurRadius); }; - applyTheme(); - this->holder_.managedConnect(getTheme()->updated, applyTheme); + getSettings()->enableOverlayShadow.connect( + [this, applyDropShadowTheme](bool value) { + if (value) + { + this->dropShadow_ = new QGraphicsDropShadowEffect; + applyDropShadowTheme(); + this->channelView_.setGraphicsEffect(this->dropShadow_); + } + else + { + this->channelView_.setGraphicsEffect(nullptr); + } + }, + this->holder_); + + applyDropShadowTheme(); + this->holder_.managedConnect(getTheme()->updated, applyDropShadowTheme); } bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index 7968291e5cd..4c051ca1069 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -6,10 +6,11 @@ #include #include -#include #include #include +class QGraphicsDropShadowEffect; + namespace chatterino { class OverlayWindow : public QWidget @@ -37,7 +38,7 @@ class OverlayWindow : public QWidget IndirectChannel channel_; pajlada::Signals::SignalHolder holder_; ChannelView channelView_; - QGraphicsDropShadowEffect dropShadow_; + QGraphicsDropShadowEffect *dropShadow_; bool moving_ = false; QPoint moveOrigin_; diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index c6f78ac1ef8..fe9cc5aed77 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -862,6 +862,11 @@ void GeneralPage::initLayout(GeneralPageView &layout) layout.addCheckbox("Use custom FrankerFaceZ VIP badges", s.useCustomFfzVipBadges); + layout.addSubtitle("Overlay"); + layout.addCheckbox("Enable Shadow", s.enableOverlayShadow, false, + "Enables a drop shadow on the overlay. This will use " + "more processing power."); + layout.addSubtitle("Miscellaneous"); if (supportsIncognitoLinks()) From 256c1aa18370c86a1ab855410fefb00856ac2588 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Wed, 16 Aug 2023 13:29:51 +0200 Subject: [PATCH 13/62] fix: improve ui when holding shift --- src/widgets/OverlayWindow.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 1313c2dd8fd..b3cb03d9cc2 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -17,7 +17,6 @@ #include - namespace { class Grippy : public QSizeGrip @@ -172,16 +171,26 @@ void OverlayWindow::paintEvent(QPaintEvent *event) QPainter painter(this); QColor highlightColor(255, 255, 255, int(255.0 * this->interactionProgress())); + painter.setPen({highlightColor, 2}); - painter.drawRect(this->rect()); + // outline + auto bounds = this->rect(); + painter.drawRect(bounds); painter.setBrush(highlightColor); painter.setPen(Qt::transparent); - auto br = this->rect().bottomRight(); - std::array triangle = {br - QPoint{10, 0}, br, - br - QPoint{0, 10}}; + + // bottom resize triangle + auto br = bounds.bottomRight(); + std::array triangle = {br - QPoint{20, 0}, br, + br - QPoint{0, 20}}; painter.drawPolygon(triangle.data(), triangle.size()); + // close button + auto buttonSize = this->closeButton_.size(); + painter.drawRect( + QRect{bounds.topRight() - QPoint{buttonSize.width(), 0}, buttonSize}); + painter.end(); QWidget::paintEvent(event); From 536aff0a640f3077f491b1ec53f7aa865f65f840 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Thu, 24 Aug 2023 23:19:10 +0200 Subject: [PATCH 14/62] feat: add global shortcuts Windows only rn --- src/CMakeLists.txt | 10 + src/controllers/hotkeys/GlobalShortcut.cpp | 178 +++++++++++++ src/controllers/hotkeys/GlobalShortcut.hpp | 52 ++++ src/controllers/hotkeys/GlobalShortcutFwd.hpp | 26 ++ src/platform/GlobalShortcutPrivate.hpp | 73 ++++++ .../windows/GlobalShortcutPrivate.cpp | 247 ++++++++++++++++++ 6 files changed, 586 insertions(+) create mode 100644 src/controllers/hotkeys/GlobalShortcut.cpp create mode 100644 src/controllers/hotkeys/GlobalShortcut.hpp create mode 100644 src/controllers/hotkeys/GlobalShortcutFwd.hpp create mode 100644 src/platform/GlobalShortcutPrivate.hpp create mode 100644 src/platform/windows/GlobalShortcutPrivate.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c28fe4949c0..bf97f64a71b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -122,6 +122,9 @@ set(SOURCE_FILES controllers/highlights/UserHighlightModel.hpp controllers/hotkeys/ActionNames.hpp + controllers/hotkeys/GlobalShortcut.cpp + controllers/hotkeys/GlobalShortcut.hpp + controllers/hotkeys/GlobalShortcutFwd.hpp controllers/hotkeys/Hotkey.cpp controllers/hotkeys/Hotkey.hpp controllers/hotkeys/HotkeyCategory.hpp @@ -230,6 +233,8 @@ set(SOURCE_FILES messages/search/SubtierPredicate.cpp messages/search/SubtierPredicate.hpp + platform/GlobalShortcutPrivate.hpp + providers/Crashpad.cpp providers/Crashpad.hpp providers/IvrApi.cpp @@ -625,6 +630,11 @@ if (WIN32) list(APPEND SOURCE_FILES "${CMAKE_SOURCE_DIR}/resources/windows.rc") endif () + # Platform specific implementations + list(APPEND SOURCE_FILES + platform/windows/GlobalShortcutPrivate.cpp + ) + elseif (APPLE) set(MACOS_BUNDLE_ICON_FILE "${CMAKE_SOURCE_DIR}/resources/chatterino.icns") list(APPEND SOURCE_FILES "${MACOS_BUNDLE_ICON_FILE}") diff --git a/src/controllers/hotkeys/GlobalShortcut.cpp b/src/controllers/hotkeys/GlobalShortcut.cpp new file mode 100644 index 00000000000..aaae6866482 --- /dev/null +++ b/src/controllers/hotkeys/GlobalShortcut.cpp @@ -0,0 +1,178 @@ +#include "controllers/hotkeys/GlobalShortcut.hpp" + +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT + +# include "common/Literals.hpp" +# include "common/QLogging.hpp" +# include "platform/GlobalShortcutPrivate.hpp" + +# include +# include +# include + +namespace chatterino { + +using namespace literals; + +// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) +# ifndef Q_OS_MAC +size_t GlobalShortcutPrivate::REFCOUNT = 0; +# endif // Q_OS_MAC + +decltype(GlobalShortcutPrivate::SHORTCUTS) GlobalShortcutPrivate::SHORTCUTS; +// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) + +GlobalShortcutPrivate::GlobalShortcutPrivate(GlobalShortcut *owner) + : enabled(true) + , key(Qt::Key(0)) + , mods(Qt::NoModifier) + , owner_(owner) +{ +# ifndef Q_OS_MAC + if (REFCOUNT == 0) + { + QAbstractEventDispatcher::instance()->installNativeEventFilter(this); + } + REFCOUNT++; +# endif // Q_OS_MAC +} + +GlobalShortcutPrivate::~GlobalShortcutPrivate() +{ +# ifndef Q_OS_MAC + REFCOUNT--; + if (REFCOUNT == 0) + { + QAbstractEventDispatcher *dispatcher = + QAbstractEventDispatcher::instance(); + if (dispatcher != nullptr) + { + dispatcher->removeNativeEventFilter(this); + } + } +# endif // Q_OS_MAC +} + +bool GlobalShortcutPrivate::setShortcut(const QKeySequence &shortcut) +{ + if (shortcut.count() > 1) + { + qCWarning(chatterinoHotkeys) + << u"Global shortcuts must be composed of exactly one key with optional modifiers."_s; + } + + auto allMods = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | + Qt::MetaModifier; + this->key = shortcut.isEmpty() + ? Qt::Key(0) + : Qt::Key((shortcut[0] ^ allMods) & shortcut[0]); + this->mods = shortcut.isEmpty() + ? Qt::KeyboardModifiers(0) + : Qt::KeyboardModifiers(shortcut[0] & allMods); + + quint32 nativeKey = nativeKeycode(this->key); + quint32 nativeMods = nativeModifiers(this->mods); + auto res = registerShortcut(nativeKey, nativeMods); + if (res.ok) + { + SHORTCUTS.emplace(std::make_pair(nativeKey, nativeMods), this->owner_); + + return true; + } + + qCWarning(chatterinoHotkeys) + << "GlobalShortcut failed to register:" << QKeySequence(key + mods) + << "(native) error:" << res.error; + + return false; +} + +bool GlobalShortcutPrivate::unsetShortcut() +{ + const quint32 nativeKey = nativeKeycode(key); + const quint32 nativeMods = nativeModifiers(mods); + auto it = SHORTCUTS.find(qMakePair(nativeKey, nativeMods)); + if (it != SHORTCUTS.end()) + { + auto res = unregisterShortcut(nativeKey, nativeMods); + if (res.ok) + { + SHORTCUTS.erase(it); + } + else + { + qWarning() << "GlobalShortcut failed to unregister:" + << QKeySequence(key + mods) + << "(native) error:" << res.error; + } + key = Qt::Key(0); + mods = Qt::KeyboardModifiers(0); + return res.ok; + } + + return false; +} + +void GlobalShortcutPrivate::activateShortcut(quint32 nativeKey, + quint32 nativeMods) +{ + auto it = SHORTCUTS.find(std::make_pair(nativeKey, nativeMods)); + if (it != SHORTCUTS.end() && it->second->isEnabled()) + { + emit it->second->activated(); + } +} + +GlobalShortcut::GlobalShortcut(QObject *parent) + : QObject(parent) + , private_(std::make_unique(this)) +{ +} + +GlobalShortcut::GlobalShortcut(const QKeySequence &shortcut, QObject *parent) + : QObject(parent) + , private_(std::make_unique(this)) +{ + setShortcut(shortcut); +} + +GlobalShortcut::~GlobalShortcut() +{ + if (this->private_->key != 0) + { + this->private_->unsetShortcut(); + } +} + +QKeySequence GlobalShortcut::shortcut() const +{ + return {this->private_->key | this->private_->mods}; +} + +bool GlobalShortcut::setShortcut(const QKeySequence &shortcut) +{ + if (this->private_->key != 0) + { + this->private_->unsetShortcut(); + } + return this->private_->setShortcut(shortcut); +} + +bool GlobalShortcut::isEnabled() const +{ + return this->private_->enabled; +} + +void GlobalShortcut::setEnabled(bool enabled) +{ + this->private_->enabled = enabled; +} + +void GlobalShortcut::setDisabled(bool disabled) +{ + this->private_->enabled = !disabled; +} + +} // namespace chatterino + +#endif diff --git a/src/controllers/hotkeys/GlobalShortcut.hpp b/src/controllers/hotkeys/GlobalShortcut.hpp new file mode 100644 index 00000000000..687b09edaf5 --- /dev/null +++ b/src/controllers/hotkeys/GlobalShortcut.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "controllers/hotkeys/GlobalShortcutFwd.hpp" + +#include + +#include +#include + +class QKeySequence; + +namespace chatterino { + +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT + +class GlobalShortcutPrivate; + +/// This implementation is an adapted version of the QxtGlobalShortcut (from libqxt): +/// https://bitbucket.org/libqxt/libqxt/src/08d08b58c362000798e19c3b5979ad6a1e6e880a/src/widgets/qxtglobalshortcut.h +/// +/// Backends are found in `platform/`. +class GlobalShortcut : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) + Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut) + +public: + explicit GlobalShortcut(QObject *parent = nullptr); + explicit GlobalShortcut(const QKeySequence &shortcut, + QObject *parent = nullptr); + ~GlobalShortcut() override; + + QKeySequence shortcut() const; + bool setShortcut(const QKeySequence &shortcut); + + bool isEnabled() const; + +public Q_SLOTS: + void setEnabled(bool enabled = true); + void setDisabled(bool disabled = true); + +Q_SIGNALS: + void activated(); + +private: + std::unique_ptr private_; +}; + +#endif + +} // namespace chatterino diff --git a/src/controllers/hotkeys/GlobalShortcutFwd.hpp b/src/controllers/hotkeys/GlobalShortcutFwd.hpp new file mode 100644 index 00000000000..7cc7c6abe0f --- /dev/null +++ b/src/controllers/hotkeys/GlobalShortcutFwd.hpp @@ -0,0 +1,26 @@ +#pragma once + +// This is a helper to define `CHATTERINO_HAS_GLOBAL_SHORTCUT` and forward declare the related classes. + +#if __has_include() +# include +#else +# include +#endif + +#if defined(Q_OS_WIN) +// If this is defined, then a backend for global shortcuts exists. +# define CHATTERINO_HAS_GLOBAL_SHORTCUT +#endif + +namespace chatterino { + +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT + +class GlobalShortcutPrivate; + +class GlobalShortcut; + +#endif + +} // namespace chatterino diff --git a/src/platform/GlobalShortcutPrivate.hpp b/src/platform/GlobalShortcutPrivate.hpp new file mode 100644 index 00000000000..50f6575d0ec --- /dev/null +++ b/src/platform/GlobalShortcutPrivate.hpp @@ -0,0 +1,73 @@ +#pragma once + +#include + +#include +#ifndef Q_OS_MAC +# include +#endif + +#include + +#include +#include + +namespace chatterino { + +class GlobalShortcut; + +struct GlobalShortcutResult { + bool ok; + uint32_t error; +}; + +class GlobalShortcutPrivate +#ifndef Q_OS_MAC + : public QAbstractNativeEventFilter +#endif +{ +public: + GlobalShortcutPrivate(GlobalShortcut *owner); + ~GlobalShortcutPrivate() override; + + bool enabled; + Qt::Key key; + Qt::KeyboardModifiers mods; + + bool setShortcut(const QKeySequence &shortcut); + bool unsetShortcut(); + +#ifndef Q_OS_MAC +# if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) + using NativeEventResult = qintptr; +# else + using NativeEventResult = long; +# endif + bool nativeEventFilter(const QByteArray &eventType, void *message, + NativeEventResult *result) override; +#endif + + static void activateShortcut(quint32 nativeKey, quint32 nativeMods); + +private: + static quint32 nativeKeycode(Qt::Key keycode); + static quint32 nativeModifiers(Qt::KeyboardModifiers modifiers); + + static GlobalShortcutResult registerShortcut(quint32 nativeKey, + quint32 nativeMods); + static GlobalShortcutResult unregisterShortcut(quint32 nativeKey, + quint32 nativeMods); + + // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) + static std::unordered_map, GlobalShortcut *, + boost::hash>> + SHORTCUTS; +#ifndef Q_OS_MAC + static size_t REFCOUNT; +#endif + // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) + + GlobalShortcut *owner_ = nullptr; +}; + +} // namespace chatterino diff --git a/src/platform/windows/GlobalShortcutPrivate.cpp b/src/platform/windows/GlobalShortcutPrivate.cpp new file mode 100644 index 00000000000..6efa9253c26 --- /dev/null +++ b/src/platform/windows/GlobalShortcutPrivate.cpp @@ -0,0 +1,247 @@ +#include "platform/GlobalShortcutPrivate.hpp" + +#include "Windows.h" + +#include + +namespace { + +using namespace chatterino; + +GlobalShortcutResult wrapWinError(BOOL success) +{ + if (success == TRUE) + { + return {.ok = true, .error = 0}; + } + + return {.ok = false, .error = GetLastError()}; +} + +} // namespace + +namespace chatterino { + +bool GlobalShortcutPrivate::nativeEventFilter(const QByteArray & /*eventType*/, + void *message, + NativeEventResult * /*result*/) +{ + MSG *msg = static_cast(message); + if (msg->message == WM_HOTKEY) + { + const quint32 keycode = HIWORD(msg->lParam); + const quint32 modifiers = LOWORD(msg->lParam); + activateShortcut(keycode, modifiers); + } + + return false; +} + +quint32 GlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) +{ + // MOD_ALT, MOD_CONTROL, (MOD_KEYUP), MOD_SHIFT, MOD_WIN + quint32 native = 0; + if (modifiers.testFlag(Qt::ShiftModifier)) + { + native |= MOD_SHIFT; + } + if (modifiers.testFlag(Qt::ControlModifier)) + { + native |= MOD_CONTROL; + } + if (modifiers.testFlag(Qt::AltModifier)) + { + native |= MOD_ALT; + } + if (modifiers.testFlag(Qt::MetaModifier)) + { + native |= MOD_WIN; + } + + Q_ASSERT_X( + !modifiers.testAnyFlags({Qt::KeypadModifier, Qt::GroupSwitchModifier}), + "GlobalShortcutPrivate::nativeModifiers", + "KeypadModifier and GroupSwitchModifier can't be expressed on Windows"); + + return native; +} + +quint32 GlobalShortcutPrivate::nativeKeycode(Qt::Key key) +{ + switch (key) + { + case Qt::Key_Escape: + return VK_ESCAPE; + case Qt::Key_Tab: + case Qt::Key_Backtab: + return VK_TAB; + case Qt::Key_Backspace: + return VK_BACK; + case Qt::Key_Return: + case Qt::Key_Enter: + return VK_RETURN; + case Qt::Key_Insert: + return VK_INSERT; + case Qt::Key_Delete: + return VK_DELETE; + case Qt::Key_Pause: + return VK_PAUSE; + case Qt::Key_Print: + return VK_PRINT; + case Qt::Key_Clear: + return VK_CLEAR; + case Qt::Key_Home: + return VK_HOME; + case Qt::Key_End: + return VK_END; + case Qt::Key_Left: + return VK_LEFT; + case Qt::Key_Up: + return VK_UP; + case Qt::Key_Right: + return VK_RIGHT; + case Qt::Key_Down: + return VK_DOWN; + case Qt::Key_PageUp: + return VK_PRIOR; + case Qt::Key_PageDown: + return VK_NEXT; + case Qt::Key_F1: + return VK_F1; + case Qt::Key_F2: + return VK_F2; + case Qt::Key_F3: + return VK_F3; + case Qt::Key_F4: + return VK_F4; + case Qt::Key_F5: + return VK_F5; + case Qt::Key_F6: + return VK_F6; + case Qt::Key_F7: + return VK_F7; + case Qt::Key_F8: + return VK_F8; + case Qt::Key_F9: + return VK_F9; + case Qt::Key_F10: + return VK_F10; + case Qt::Key_F11: + return VK_F11; + case Qt::Key_F12: + return VK_F12; + case Qt::Key_F13: + return VK_F13; + case Qt::Key_F14: + return VK_F14; + case Qt::Key_F15: + return VK_F15; + case Qt::Key_F16: + return VK_F16; + case Qt::Key_F17: + return VK_F17; + case Qt::Key_F18: + return VK_F18; + case Qt::Key_F19: + return VK_F19; + case Qt::Key_F20: + return VK_F20; + case Qt::Key_F21: + return VK_F21; + case Qt::Key_F22: + return VK_F22; + case Qt::Key_F23: + return VK_F23; + case Qt::Key_F24: + return VK_F24; + case Qt::Key_Space: + return VK_SPACE; + case Qt::Key_Asterisk: + return VK_MULTIPLY; + case Qt::Key_Plus: + return VK_ADD; + case Qt::Key_Comma: + return VK_SEPARATOR; + case Qt::Key_Minus: + return VK_SUBTRACT; + case Qt::Key_Slash: + return VK_DIVIDE; + case Qt::Key_MediaNext: + return VK_MEDIA_NEXT_TRACK; + case Qt::Key_MediaPrevious: + return VK_MEDIA_PREV_TRACK; + case Qt::Key_MediaPlay: + return VK_MEDIA_PLAY_PAUSE; + case Qt::Key_MediaStop: + return VK_MEDIA_STOP; + // couldn't find those in VK_* + //case Qt::Key_MediaLast: + //case Qt::Key_MediaRecord: + case Qt::Key_VolumeDown: + return VK_VOLUME_DOWN; + case Qt::Key_VolumeUp: + return VK_VOLUME_UP; + case Qt::Key_VolumeMute: + return VK_VOLUME_MUTE; + + // numbers + case Qt::Key_0: + case Qt::Key_1: + case Qt::Key_2: + case Qt::Key_3: + case Qt::Key_4: + case Qt::Key_5: + case Qt::Key_6: + case Qt::Key_7: + case Qt::Key_8: + case Qt::Key_9: + // letters + case Qt::Key_A: + case Qt::Key_B: + case Qt::Key_C: + case Qt::Key_D: + case Qt::Key_E: + case Qt::Key_F: + case Qt::Key_G: + case Qt::Key_H: + case Qt::Key_I: + case Qt::Key_J: + case Qt::Key_K: + case Qt::Key_L: + case Qt::Key_M: + case Qt::Key_N: + case Qt::Key_O: + case Qt::Key_P: + case Qt::Key_Q: + case Qt::Key_R: + case Qt::Key_S: + case Qt::Key_T: + case Qt::Key_U: + case Qt::Key_V: + case Qt::Key_W: + case Qt::Key_X: + case Qt::Key_Y: + case Qt::Key_Z: + return key; + + default: + return 0; + } +} + +GlobalShortcutResult GlobalShortcutPrivate::registerShortcut(quint32 nativeKey, + quint32 nativeMods) +{ + return wrapWinError( + RegisterHotKey(nullptr, std::bit_cast(nativeMods) ^ nativeKey, + nativeMods, nativeKey)); +} + +GlobalShortcutResult GlobalShortcutPrivate::unregisterShortcut( + quint32 nativeKey, quint32 nativeMods) +{ + return wrapWinError( + UnregisterHotKey(nullptr, std::bit_cast(nativeMods) ^ nativeKey)); +} + +} // namespace chatterino From 0d3b16f9045f0ae5ca19a51c9562b9554c59c5ac Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Thu, 24 Aug 2023 23:20:06 +0200 Subject: [PATCH 15/62] feat: add shortcut for inert mode --- src/singletons/Settings.hpp | 5 ++ src/widgets/OverlayWindow.cpp | 26 +++++++ src/widgets/OverlayWindow.hpp | 7 ++ src/widgets/settingspages/GeneralPage.cpp | 4 ++ src/widgets/settingspages/GeneralPageView.cpp | 70 +++++++++++++++++++ src/widgets/settingspages/GeneralPageView.hpp | 3 + 6 files changed, 115 insertions(+) diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index ca8aa7aa07d..5d3a9f3787c 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -4,6 +4,7 @@ #include "common/Channel.hpp" #include "common/enums/MessageOverflow.hpp" #include "common/SignalVector.hpp" +#include "controllers/hotkeys/GlobalShortcutFwd.hpp" #include "controllers/logging/ChannelLog.hpp" #include "singletons/Toasts.hpp" #include "util/RapidJsonSerializeQString.hpp" @@ -172,6 +173,10 @@ class Settings : public ABSettings, public ConcurrentSettings // false}; BoolSetting enableOverlayShadow = {"/appearance/overlay/shadow", true}; +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT + QStringSetting overlayInertShortcut = {"/behaviour/overlay/inert", + "Ctrl+Alt+Shift+U"}; +#endif // Badges BoolSetting showBadgesGlobalAuthority = { diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index b3cb03d9cc2..abbaeb9c15a 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -1,6 +1,7 @@ #include "widgets/OverlayWindow.hpp" #include "BaseSettings.hpp" +#include "controllers/hotkeys/GlobalShortcut.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "widgets/BaseWidget.hpp" @@ -13,6 +14,7 @@ #include #include #include +#include #include #include @@ -101,8 +103,32 @@ OverlayWindow::OverlayWindow(IndirectChannel channel, Split *split) applyDropShadowTheme(); this->holder_.managedConnect(getTheme()->updated, applyDropShadowTheme); + +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT + getSettings()->overlayInertShortcut.connect( + [this](const auto &value) { + this->shortcut_ = std::make_unique( + QKeySequence::fromString(value, QKeySequence::PortableText), + this); + QObject::connect(this->shortcut_.get(), &GlobalShortcut::activated, + this, [this] { + qDebug() << "!!!!!!!"; + this->inert_ = !this->inert_; + this->setWindowFlag( + Qt::WindowTransparentForInput, + this->inert_); + if (this->isHidden()) + { + this->show(); + } + }); + }, + this->holder_); +#endif } +OverlayWindow::~OverlayWindow() = default; + bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) { switch (event->type()) diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index 4c051ca1069..0e7447b28e0 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -1,6 +1,7 @@ #pragma once #include "common/Channel.hpp" +#include "controllers/hotkeys/GlobalShortcutFwd.hpp" #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/TitlebarButton.hpp" @@ -18,6 +19,7 @@ class OverlayWindow : public QWidget Q_OBJECT public: OverlayWindow(IndirectChannel channel, Split *split); + ~OverlayWindow() override; protected: bool eventFilter(QObject *object, QEvent *event) override; @@ -47,6 +49,11 @@ class OverlayWindow : public QWidget QPropertyAnimation interactAnimation_; TitleBarButton closeButton_; + +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT + std::unique_ptr shortcut_; + bool inert_ = false; +#endif }; } // namespace chatterino diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index fe9cc5aed77..bdae90e63e7 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -866,6 +866,10 @@ void GeneralPage::initLayout(GeneralPageView &layout) layout.addCheckbox("Enable Shadow", s.enableOverlayShadow, false, "Enables a drop shadow on the overlay. This will use " "more processing power."); +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT + layout.addGlobalShortcut("Clickthrough Hotkey", s.overlayInertShortcut, + "Toggles the inertia of the overlay."); +#endif layout.addSubtitle("Miscellaneous"); diff --git a/src/widgets/settingspages/GeneralPageView.cpp b/src/widgets/settingspages/GeneralPageView.cpp index 0d1ab0b25a4..8d9d313271f 100644 --- a/src/widgets/settingspages/GeneralPageView.cpp +++ b/src/widgets/settingspages/GeneralPageView.cpp @@ -1,16 +1,25 @@ #include "widgets/settingspages/GeneralPageView.hpp" #include "Application.hpp" +#include "common/Literals.hpp" #include "util/LayoutHelper.hpp" #include "util/RapidJsonSerializeQString.hpp" #include "widgets/dialogs/ColorPickerDialog.hpp" #include "widgets/helper/ColorButton.hpp" #include "widgets/helper/Line.hpp" +#include +#include +#include +#include +#include +#include #include #include #include +#include + namespace { constexpr int MAX_TOOLTIP_LINE_LENGTH = 50; @@ -22,6 +31,8 @@ const QRegularExpression MAX_TOOLTIP_LINE_LENGTH_REGEX( namespace chatterino { +using namespace literals; + GeneralPageView::GeneralPageView(QWidget *parent) : QWidget(parent) { @@ -249,6 +260,65 @@ QSpinBox *GeneralPageView::addIntInput(const QString &text, IntSetting &setting, return input; } +QPushButton *GeneralPageView::addGlobalShortcut( + const QString &text, pajlada::Settings::Setting &setting, + QString toolTipText) +{ + auto *button = new QPushButton(setting); + auto *layout = new QHBoxLayout(); + auto *label = new QLabel(text + ':'); + + layout->addWidget(label); + layout->addStretch(1); + layout->addWidget(button); + + this->addToolTip(*label, std::move(toolTipText)); + this->addLayout(layout); + + QObject::connect(button, &QPushButton::clicked, [this, &setting, button] { + auto *dialog = new QDialog(this); + auto *layout = new QVBoxLayout(dialog); + auto *edit = new QKeySequenceEdit( + QKeySequence::fromString(setting, QKeySequence::PortableText)); + layout->addWidget(edit, 1); + + auto *btn = new QDialogButtonBox(QDialogButtonBox::Cancel | + QDialogButtonBox::Ok); + layout->addWidget(btn, 0, Qt::AlignRight | Qt::AlignBottom); + QObject::connect(btn, &QDialogButtonBox::accepted, dialog, + &QDialog::accept); + QObject::connect(btn, &QDialogButtonBox::rejected, dialog, + &QDialog::reject); + + dialog->resize(300, 200); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setWindowTitle("Edit Hotkey"); + dialog->show(); + + QObject::connect( + dialog, &QDialog::accepted, [this, &setting, edit, button] { + if (edit->keySequence().isEmpty() || + edit->keySequence().count() > 1) + { + QMessageBox::warning( + this, u"Key Editor"_s, + u"There must be exactly one key pressed (with optional modifiers)."_s, + QMessageBox::Ok); + return; + } + auto text = + edit->keySequence().toString(QKeySequence::PortableText); + setting.setValue(text); + button->setText(text); + }); + }); + + this->groups_.back().widgets.push_back({label, {text}}); + this->groups_.back().widgets.push_back({button, {text}}); + + return button; +} + void GeneralPageView::addNavigationSpacing() { this->navigationLayout_->addSpacing(24); diff --git a/src/widgets/settingspages/GeneralPageView.hpp b/src/widgets/settingspages/GeneralPageView.hpp index 101b0b7b9cd..fa129d1aef4 100644 --- a/src/widgets/settingspages/GeneralPageView.hpp +++ b/src/widgets/settingspages/GeneralPageView.hpp @@ -113,6 +113,9 @@ class GeneralPageView : public QWidget QString toolTipText = {}); QSpinBox *addIntInput(const QString &text, IntSetting &setting, int min, int max, int step, QString toolTipText = {}); + QPushButton *addGlobalShortcut(const QString &text, + pajlada::Settings::Setting &setting, + QString toolTipText = {}); void addNavigationSpacing(); template From cc599a1caf921414a8b475e510ab979a8dcb5e44 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Fri, 25 Aug 2023 12:00:31 +0200 Subject: [PATCH 16/62] refactor: improve global shortcuts * Align code style * Support multiple shortcuts --- src/controllers/hotkeys/GlobalShortcut.cpp | 149 ++++++++++-------- src/controllers/hotkeys/GlobalShortcut.hpp | 16 +- src/platform/GlobalShortcutPrivate.hpp | 44 ++++-- .../windows/GlobalShortcutPrivate.cpp | 19 ++- 4 files changed, 132 insertions(+), 96 deletions(-) diff --git a/src/controllers/hotkeys/GlobalShortcut.cpp b/src/controllers/hotkeys/GlobalShortcut.cpp index aaae6866482..eb2de7a0b34 100644 --- a/src/controllers/hotkeys/GlobalShortcut.cpp +++ b/src/controllers/hotkeys/GlobalShortcut.cpp @@ -2,7 +2,6 @@ #ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT -# include "common/Literals.hpp" # include "common/QLogging.hpp" # include "platform/GlobalShortcutPrivate.hpp" @@ -12,8 +11,6 @@ namespace chatterino { -using namespace literals; - // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) # ifndef Q_OS_MAC size_t GlobalShortcutPrivate::REFCOUNT = 0; @@ -23,8 +20,7 @@ decltype(GlobalShortcutPrivate::SHORTCUTS) GlobalShortcutPrivate::SHORTCUTS; // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) GlobalShortcutPrivate::GlobalShortcutPrivate(GlobalShortcut *owner) - : enabled(true) - , key(Qt::Key(0)) + : key(Qt::Key(0)) , mods(Qt::NoModifier) , owner_(owner) { @@ -55,74 +51,114 @@ GlobalShortcutPrivate::~GlobalShortcutPrivate() bool GlobalShortcutPrivate::setShortcut(const QKeySequence &shortcut) { + // our caller already unset the shortcut + + if (shortcut.isEmpty()) + { + // same as unsetShortcut + return false; + } + if (shortcut.count() > 1) { qCWarning(chatterinoHotkeys) - << u"Global shortcuts must be composed of exactly one key with optional modifiers."_s; + << "Global shortcuts must be composed of exactly one key with " + "optional modifiers."; + return false; } - auto allMods = Qt::ShiftModifier | Qt::ControlModifier | Qt::AltModifier | - Qt::MetaModifier; - this->key = shortcut.isEmpty() - ? Qt::Key(0) - : Qt::Key((shortcut[0] ^ allMods) & shortcut[0]); - this->mods = shortcut.isEmpty() - ? Qt::KeyboardModifiers(0) - : Qt::KeyboardModifiers(shortcut[0] & allMods); - - quint32 nativeKey = nativeKeycode(this->key); - quint32 nativeMods = nativeModifiers(this->mods); - auto res = registerShortcut(nativeKey, nativeMods); - if (res.ok) - { - SHORTCUTS.emplace(std::make_pair(nativeKey, nativeMods), this->owner_); +# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + this->key = shortcut[0].key(); + this->mods = shortcut[0].keyboardModifiers(); +# else + this->key = Qt::Key(shortcut[0] & ~Qt::KeyboardModifierMask); + this->mods = Qt::KeyboardModifiers(shortcut[0] & Qt::KeyboardModifierMask); +# endif + auto native = this->native(); + auto it = SHORTCUTS.find(native); + if (it != SHORTCUTS.end()) + { + // tap into the curren shortcut + it->second.emplace_back(this->owner_); return true; } - qCWarning(chatterinoHotkeys) - << "GlobalShortcut failed to register:" << QKeySequence(key + mods) - << "(native) error:" << res.error; + // no previous shortcut registered + auto res = registerShortcut(native); + if (!res.ok) + { + qCWarning(chatterinoHotkeys) << "GlobalShortcut failed to register:" + << QKeySequence(this->key | this->mods) + << "(native) error:" << res.error; + return false; + } + SHORTCUTS.emplace(native, std::vector{this->owner_}); - return false; + return true; } bool GlobalShortcutPrivate::unsetShortcut() { - const quint32 nativeKey = nativeKeycode(key); - const quint32 nativeMods = nativeModifiers(mods); - auto it = SHORTCUTS.find(qMakePair(nativeKey, nativeMods)); - if (it != SHORTCUTS.end()) + auto native = this->native(); + auto shortcut = SHORTCUTS.find(native); + if (shortcut == SHORTCUTS.end()) { - auto res = unregisterShortcut(nativeKey, nativeMods); - if (res.ok) - { - SHORTCUTS.erase(it); - } - else - { - qWarning() << "GlobalShortcut failed to unregister:" - << QKeySequence(key + mods) - << "(native) error:" << res.error; - } - key = Qt::Key(0); - mods = Qt::KeyboardModifiers(0); - return res.ok; + return false; } - return false; + auto it = std::find(shortcut->second.begin(), shortcut->second.end(), + this->owner_); + if (it == shortcut->second.end()) + { + return false; + } + + shortcut->second.erase(it); + if (!shortcut->second.empty()) + { + return true; + } + + // No remaining shortcut -> unregister + auto res = GlobalShortcutPrivate::unregisterShortcut(native); + if (res.ok) + { + SHORTCUTS.erase(shortcut); + } + else + { + qWarning() << "GlobalShortcut failed to unregister:" + << QKeySequence(this->key | this->mods) + << "(native) error:" << res.error; + } + this->key = Qt::Key(0); + this->mods = Qt::NoModifier; + return res.ok; } -void GlobalShortcutPrivate::activateShortcut(quint32 nativeKey, - quint32 nativeMods) +void GlobalShortcutPrivate::activateShortcut(Native native) { - auto it = SHORTCUTS.find(std::make_pair(nativeKey, nativeMods)); - if (it != SHORTCUTS.end() && it->second->isEnabled()) + auto it = SHORTCUTS.find(native); + if (it != SHORTCUTS.end()) { - emit it->second->activated(); + bool singleConsumer = it->second.size() == 1; + for (size_t i = 0; auto *consumer : it->second) + { + emit consumer->activated(i, singleConsumer); + i++; + } } } +GlobalShortcutPrivate::Native GlobalShortcutPrivate::native() const +{ + return { + .key = GlobalShortcutPrivate::nativeKeycode(this->key), + .modifiers = GlobalShortcutPrivate::nativeModifiers(this->mods), + }; +} + GlobalShortcut::GlobalShortcut(QObject *parent) : QObject(parent) , private_(std::make_unique(this)) @@ -158,21 +194,6 @@ bool GlobalShortcut::setShortcut(const QKeySequence &shortcut) return this->private_->setShortcut(shortcut); } -bool GlobalShortcut::isEnabled() const -{ - return this->private_->enabled; -} - -void GlobalShortcut::setEnabled(bool enabled) -{ - this->private_->enabled = enabled; -} - -void GlobalShortcut::setDisabled(bool disabled) -{ - this->private_->enabled = !disabled; -} - } // namespace chatterino #endif diff --git a/src/controllers/hotkeys/GlobalShortcut.hpp b/src/controllers/hotkeys/GlobalShortcut.hpp index 687b09edaf5..aa70c4d1c36 100644 --- a/src/controllers/hotkeys/GlobalShortcut.hpp +++ b/src/controllers/hotkeys/GlobalShortcut.hpp @@ -2,13 +2,12 @@ #include "controllers/hotkeys/GlobalShortcutFwd.hpp" +#include #include #include #include -class QKeySequence; - namespace chatterino { #ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT @@ -19,10 +18,13 @@ class GlobalShortcutPrivate; /// https://bitbucket.org/libqxt/libqxt/src/08d08b58c362000798e19c3b5979ad6a1e6e880a/src/widgets/qxtglobalshortcut.h /// /// Backends are found in `platform/`. +/// +/// In this implementation, multiple `GlobalShortcut`s can listen to the same key combination. +/// All listeners will get the event. Through the `consumerID` (n-th listener) and `singleConsumer`, +/// listeners can decide whether to accept the signal. class GlobalShortcut : public QObject { Q_OBJECT - Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut) public: @@ -34,14 +36,8 @@ class GlobalShortcut : public QObject QKeySequence shortcut() const; bool setShortcut(const QKeySequence &shortcut); - bool isEnabled() const; - -public Q_SLOTS: - void setEnabled(bool enabled = true); - void setDisabled(bool disabled = true); - Q_SIGNALS: - void activated(); + void activated(size_t consumerID, bool singleConsumer); private: std::unique_ptr private_; diff --git a/src/platform/GlobalShortcutPrivate.hpp b/src/platform/GlobalShortcutPrivate.hpp index 50f6575d0ec..3f83a102ae1 100644 --- a/src/platform/GlobalShortcutPrivate.hpp +++ b/src/platform/GlobalShortcutPrivate.hpp @@ -1,16 +1,16 @@ #pragma once -#include - #include #ifndef Q_OS_MAC # include #endif #include +#include #include #include +#include namespace chatterino { @@ -27,10 +27,19 @@ class GlobalShortcutPrivate #endif { public: + struct Native { + quint32 key; + quint32 modifiers; + + bool operator==(Native rhs) const noexcept + { + return this->key == rhs.key && this->modifiers == rhs.modifiers; + } + }; + GlobalShortcutPrivate(GlobalShortcut *owner); ~GlobalShortcutPrivate() override; - bool enabled; Qt::Key key; Qt::KeyboardModifiers mods; @@ -47,27 +56,38 @@ class GlobalShortcutPrivate NativeEventResult *result) override; #endif - static void activateShortcut(quint32 nativeKey, quint32 nativeMods); + static void activateShortcut(Native native); private: + Native native() const; + static quint32 nativeKeycode(Qt::Key keycode); static quint32 nativeModifiers(Qt::KeyboardModifiers modifiers); - static GlobalShortcutResult registerShortcut(quint32 nativeKey, - quint32 nativeMods); - static GlobalShortcutResult unregisterShortcut(quint32 nativeKey, - quint32 nativeMods); + static GlobalShortcutResult registerShortcut(Native); + static GlobalShortcutResult unregisterShortcut(Native); // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) - static std::unordered_map, GlobalShortcut *, - boost::hash>> - SHORTCUTS; + static std::unordered_map> SHORTCUTS; #ifndef Q_OS_MAC static size_t REFCOUNT; #endif // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) - GlobalShortcut *owner_ = nullptr; + GlobalShortcut *owner_; }; } // namespace chatterino + +template <> +struct std::hash { + std::size_t operator()( + chatterino::GlobalShortcutPrivate::Native const &n) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, n.key); + boost::hash_combine(seed, n.modifiers); + + return seed; + } +}; diff --git a/src/platform/windows/GlobalShortcutPrivate.cpp b/src/platform/windows/GlobalShortcutPrivate.cpp index 6efa9253c26..ffba1d41365 100644 --- a/src/platform/windows/GlobalShortcutPrivate.cpp +++ b/src/platform/windows/GlobalShortcutPrivate.cpp @@ -31,7 +31,8 @@ bool GlobalShortcutPrivate::nativeEventFilter(const QByteArray & /*eventType*/, { const quint32 keycode = HIWORD(msg->lParam); const quint32 modifiers = LOWORD(msg->lParam); - activateShortcut(keycode, modifiers); + GlobalShortcutPrivate::activateShortcut( + {.key = keycode, .modifiers = modifiers}); } return false; @@ -229,19 +230,17 @@ quint32 GlobalShortcutPrivate::nativeKeycode(Qt::Key key) } } -GlobalShortcutResult GlobalShortcutPrivate::registerShortcut(quint32 nativeKey, - quint32 nativeMods) +GlobalShortcutResult GlobalShortcutPrivate::registerShortcut(Native native) { - return wrapWinError( - RegisterHotKey(nullptr, std::bit_cast(nativeMods) ^ nativeKey, - nativeMods, nativeKey)); + return wrapWinError(RegisterHotKey( + nullptr, std::bit_cast(native.key) ^ native.modifiers, + native.modifiers, native.key)); } -GlobalShortcutResult GlobalShortcutPrivate::unregisterShortcut( - quint32 nativeKey, quint32 nativeMods) +GlobalShortcutResult GlobalShortcutPrivate::unregisterShortcut(Native native) { - return wrapWinError( - UnregisterHotKey(nullptr, std::bit_cast(nativeMods) ^ nativeKey)); + return wrapWinError(UnregisterHotKey( + nullptr, std::bit_cast(native.key) ^ native.modifiers)); } } // namespace chatterino From 79c81fc4f12e47bb2af2f20114969dd5e97b0500 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Fri, 25 Aug 2023 13:21:37 +0200 Subject: [PATCH 17/62] refactor: indicate unlocked window --- src/widgets/OverlayWindow.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index abbaeb9c15a..84b7281c142 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -121,6 +121,7 @@ OverlayWindow::OverlayWindow(IndirectChannel channel, Split *split) { this->show(); } + this->endInteraction(); }); }, this->holder_); @@ -186,23 +187,27 @@ void OverlayWindow::keyReleaseEvent(QKeyEvent *event) } } -void OverlayWindow::paintEvent(QPaintEvent *event) +void OverlayWindow::paintEvent(QPaintEvent * /*event*/) { - if (this->interactionProgress() <= 0.0) + if (this->inert_) { - QWidget::paintEvent(event); return; } QPainter painter(this); - QColor highlightColor(255, 255, 255, - int(255.0 * this->interactionProgress())); + QColor highlightColor( + 255, 255, 255, std::max(int(255.0 * this->interactionProgress()), 50)); painter.setPen({highlightColor, 2}); // outline auto bounds = this->rect(); painter.drawRect(bounds); + if (this->interactionProgress() <= 0.0) + { + return; + } + painter.setBrush(highlightColor); painter.setPen(Qt::transparent); @@ -216,10 +221,6 @@ void OverlayWindow::paintEvent(QPaintEvent *event) auto buttonSize = this->closeButton_.size(); painter.drawRect( QRect{bounds.topRight() - QPoint{buttonSize.width(), 0}, buttonSize}); - - painter.end(); - - QWidget::paintEvent(event); } double OverlayWindow::interactionProgress() const @@ -234,6 +235,11 @@ void OverlayWindow::setInteractionProgress(double progress) void OverlayWindow::startInteraction() { + if (this->inert_) + { + return; + } + if (this->interactAnimation_.state() != QPropertyAnimation::Stopped) { this->interactAnimation_.stop(); From 0d0ea814b2a917e725bb3724b14e5fe92e3449dc Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Fri, 25 Aug 2023 13:25:53 +0200 Subject: [PATCH 18/62] fix: start interaction when moving --- src/widgets/OverlayWindow.cpp | 10 +++++++++- src/widgets/OverlayWindow.hpp | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 84b7281c142..82ed33c1d0f 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -140,6 +140,7 @@ bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) { this->moving_ = true; this->moveOrigin_ = evt->globalPos(); + this->startInteraction(); return true; } return false; @@ -235,11 +236,12 @@ void OverlayWindow::setInteractionProgress(double progress) void OverlayWindow::startInteraction() { - if (this->inert_) + if (this->inert_ || this->interacting_) { return; } + this->interacting_ = true; if (this->interactAnimation_.state() != QPropertyAnimation::Stopped) { this->interactAnimation_.stop(); @@ -252,6 +254,12 @@ void OverlayWindow::startInteraction() void OverlayWindow::endInteraction() { + if (!this->interacting_) + { + return; + } + + this->interacting_ = false; if (this->interactAnimation_.state() != QPropertyAnimation::Stopped) { this->interactAnimation_.stop(); diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index 0e7447b28e0..45f3b7c1990 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -42,6 +42,7 @@ class OverlayWindow : public QWidget ChannelView channelView_; QGraphicsDropShadowEffect *dropShadow_; + bool interacting_ = false; bool moving_ = false; QPoint moveOrigin_; From 2ff69d458f64cbb05dabf8474c149ca6032d14d2 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Fri, 25 Aug 2023 13:36:17 +0200 Subject: [PATCH 19/62] fix: debug log --- src/widgets/OverlayWindow.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 82ed33c1d0f..bdfd6a4d9f0 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -112,7 +112,6 @@ OverlayWindow::OverlayWindow(IndirectChannel channel, Split *split) this); QObject::connect(this->shortcut_.get(), &GlobalShortcut::activated, this, [this] { - qDebug() << "!!!!!!!"; this->inert_ = !this->inert_; this->setWindowFlag( Qt::WindowTransparentForInput, From 9b54769a2b4334131da899be189d22a9fe21cf2e Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 27 Aug 2023 22:44:36 +0200 Subject: [PATCH 20/62] fix: duplicate include --- src/widgets/OverlayWindow.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index bdfd6a4d9f0..fae17297b23 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -11,7 +11,6 @@ #include #include -#include #include #include #include From 09ebc49f20e1449abc9a309d953359bebda51cc4 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 27 Aug 2023 22:45:21 +0200 Subject: [PATCH 21/62] feat: add welcome message --- src/singletons/Settings.hpp | 1 + src/widgets/OverlayWindow.cpp | 53 +++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 5d3a9f3787c..1bb91633878 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -483,6 +483,7 @@ class Settings : public ABSettings, public ConcurrentSettings IntSetting startUpNotification = {"/misc/startUpNotification", 0}; QStringSetting currentVersion = {"/misc/currentVersion", ""}; + IntSetting overlayKnowledgeLevel = {"/misc/overlayKnowledgeLevel", 0}; BoolSetting loadTwitchMessageHistoryOnConnect = { "/misc/twitch/loadMessageHistoryOnConnect", true}; diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index fae17297b23..73242b2f991 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -1,6 +1,7 @@ #include "widgets/OverlayWindow.hpp" #include "BaseSettings.hpp" +#include "common/Literals.hpp" #include "controllers/hotkeys/GlobalShortcut.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" @@ -20,6 +21,15 @@ namespace { +using namespace chatterino; +using namespace literals; + +enum class Knowledge : std::int32_t { + None = 0, + // User opened the overlay at least once + Activation = 1 << 0, +}; + class Grippy : public QSizeGrip { public: @@ -34,6 +44,47 @@ class Grippy : public QSizeGrip } }; +bool hasKnowledge(Knowledge knowledge) +{ + QFlags current(std::bit_cast( + getSettings()->overlayKnowledgeLevel.getValue())); + return current.testFlag(knowledge); +} + +void acquireKnowledge(Knowledge knowledge) +{ + QFlags current(std::bit_cast( + getSettings()->overlayKnowledgeLevel.getValue())); + current.setFlag(knowledge); + getSettings()->overlayKnowledgeLevel = current; +} + +void triggerFirstActivation(QWidget *parent) +{ + if (hasKnowledge(Knowledge::Activation)) + { + return; + } + acquireKnowledge(Knowledge::Activation); + + auto welcomeText = + u"Hey! It looks like this is the first time you're using the overlay. You can move the overlay holding SHIFT and dragging it with your mouse. To resize the window, drag on the bottom right corner."_s; +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT + auto actualShortcut = + QKeySequence::fromString(getSettings()->overlayInertShortcut, + QKeySequence::PortableText) + .toString(QKeySequence::PortableText); + welcomeText += + u"By default the overlay is interactive. To click through the overlay, press %1 (customizable in the settings)."_s + .arg(actualShortcut); +#endif + + auto *box = + new QMessageBox(QMessageBox::Information, u"Chatterino - Overlay"_s, + welcomeText, QMessageBox::Ok, parent); + box->open(); +} + } // namespace namespace chatterino { @@ -124,6 +175,8 @@ OverlayWindow::OverlayWindow(IndirectChannel channel, Split *split) }, this->holder_); #endif + + triggerFirstActivation(this); } OverlayWindow::~OverlayWindow() = default; From f44252df06e1d0fd0bdca70d5977d2c732b93b95 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 27 Aug 2023 22:47:22 +0200 Subject: [PATCH 22/62] fix: use the awesome byte array literal suffix --- src/widgets/OverlayWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 73242b2f991..6c457d7441b 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -94,7 +94,7 @@ OverlayWindow::OverlayWindow(IndirectChannel channel, Split *split) Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint) , channel_(std::move(channel)) , channelView_(nullptr, split) - , interactAnimation_(this, QByteArrayLiteral("interactionProgress")) + , interactAnimation_(this, "interactionProgress"_ba) { auto *grid = new QGridLayout(this); grid->addWidget(&this->channelView_, 0, 0); From 76b4e760bf67f2e3cecf64d9eef9e28476662629 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 27 Aug 2023 22:47:40 +0200 Subject: [PATCH 23/62] fix: set a title --- src/widgets/OverlayWindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 6c457d7441b..2a4428e3c6c 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -96,6 +96,8 @@ OverlayWindow::OverlayWindow(IndirectChannel channel, Split *split) , channelView_(nullptr, split) , interactAnimation_(this, "interactionProgress"_ba) { + this->setWindowTitle(u"Chatterino - Overlay"_s); + auto *grid = new QGridLayout(this); grid->addWidget(&this->channelView_, 0, 0); grid->addWidget(new Grippy(this), 0, 0, Qt::AlignBottom | Qt::AlignRight); From fa8e9b37a7dde0ab1da844d9ddbd69ea91ad374f Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 27 Aug 2023 22:53:14 +0200 Subject: [PATCH 24/62] fix: mention toggle --- src/widgets/OverlayWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 2a4428e3c6c..df3498f8240 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -75,7 +75,7 @@ void triggerFirstActivation(QWidget *parent) QKeySequence::PortableText) .toString(QKeySequence::PortableText); welcomeText += - u"By default the overlay is interactive. To click through the overlay, press %1 (customizable in the settings)."_s + u"By default the overlay is interactive. To toggle the click-through mode, press %1 (customizable in the settings)."_s .arg(actualShortcut); #endif From a5ce8ff9e3181030f94ba81a9696688b9971d93c Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 17 Dec 2023 22:30:16 +0100 Subject: [PATCH 25/62] fix: bad merge --- src/widgets/dialogs/ReplyThreadPopup.cpp | 49 +++++++----------------- src/widgets/dialogs/UserInfoPopup.cpp | 2 + src/widgets/helper/ChannelView.hpp | 10 ----- 3 files changed, 16 insertions(+), 45 deletions(-) diff --git a/src/widgets/dialogs/ReplyThreadPopup.cpp b/src/widgets/dialogs/ReplyThreadPopup.cpp index 19f4efbe014..708b8f0a27a 100644 --- a/src/widgets/dialogs/ReplyThreadPopup.cpp +++ b/src/widgets/dialogs/ReplyThreadPopup.cpp @@ -20,8 +20,6 @@ #include -#include - const QString TEXT_TITLE("Reply Thread - @%1 in #%2"); namespace chatterino { @@ -96,26 +94,8 @@ ReplyThreadPopup::ReplyThreadPopup(bool closeAutomatically, Split *split) }); // Create SplitInput with inline replying disabled - if (this->split_) - { - this->ui_.replyInput = - new SplitInput(this, this->split_, this->ui_.threadView, false); - // remove the input when the split is removed - connect(this->split_, &QObject::destroyed, this, [this]() { - if (this->ui_.replyInput) - { - this->ui_.replyInput->deleteLater(); - this->ui_.replyInput = nullptr; - } - }); - // clear ChannelView selection when selecting in SplitInput - this->ui_.replyInput->selectionChanged.connect([this]() { - if (this->ui_.replyInput && this->ui_.threadView->hasSelection()) - { - this->ui_.threadView->clearSelection(); - } - }); - } + this->ui_.replyInput = + new SplitInput(this, this->split_, this->ui_.threadView, false); this->bSignals_.emplace_back( getApp()->accounts->twitch.currentUserChanged.connect([this] { @@ -241,22 +221,23 @@ void ReplyThreadPopup::addMessagesFromThread() return; } + const auto &sourceChannel = this->split_->getChannel(); this->setWindowTitle(TEXT_TITLE.arg(this->thread_->root()->loginName, - this->channel_->getName())); + sourceChannel->getName())); - if (this->channel_->isTwitchChannel()) + if (sourceChannel->isTwitchChannel()) { this->virtualChannel_ = - std::make_shared(this->channel_->getName()); + std::make_shared(sourceChannel->getName()); } else { this->virtualChannel_ = std::make_shared( - this->channel_->getName(), Channel::Type::None); + sourceChannel->getName(), Channel::Type::None); } this->ui_.threadView->setChannel(this->virtualChannel_); - this->ui_.threadView->setSourceChannel(this->channel_); + this->ui_.threadView->setSourceChannel(sourceChannel); auto rootOverrideFlags = std::optional(this->thread_->root()->flags); @@ -276,8 +257,8 @@ void ReplyThreadPopup::addMessagesFromThread() this->messageConnection_ = std::make_unique( - this->channel_->messageAppended.connect([this](MessagePtr &message, - auto) { + sourceChannel->messageAppended.connect([this](MessagePtr &message, + auto) { if (message->replyThread == this->thread_) { auto overrideFlags = @@ -292,14 +273,15 @@ void ReplyThreadPopup::addMessagesFromThread() void ReplyThreadPopup::updateInputUI() { + auto channel = this->split_->getChannel(); // Bail out if not a twitch channel. // Special twitch channels will hide their reply input box. - if (!this->channel_->isTwitchChannel() || !this->ui_.replyInput) + if (!channel || !channel->isTwitchChannel()) { return; } - this->ui_.replyInput->setVisible(this->channel_->isWritable()); + this->ui_.replyInput->setVisible(channel->isWritable()); auto user = getApp()->accounts->twitch.getCurrent(); QString placeholderText; @@ -320,10 +302,7 @@ void ReplyThreadPopup::updateInputUI() void ReplyThreadPopup::giveFocus(Qt::FocusReason reason) { - if (this->ui_.replyInput) - { - this->ui_.replyInput->giveFocus(reason); - } + this->ui_.replyInput->giveFocus(reason); } void ReplyThreadPopup::focusInEvent(QFocusEvent *event) diff --git a/src/widgets/dialogs/UserInfoPopup.cpp b/src/widgets/dialogs/UserInfoPopup.cpp index d350262b47e..79fd11c8e47 100644 --- a/src/widgets/dialogs/UserInfoPopup.cpp +++ b/src/widgets/dialogs/UserInfoPopup.cpp @@ -139,6 +139,8 @@ UserInfoPopup::UserInfoPopup(bool closeAutomatically, Split *split) , split_(split) , closeAutomatically_(closeAutomatically) { + assert(split != nullptr && + "split being nullptr causes lots of bugs down the road"); this->setWindowTitle("Usercard"); HotkeyController::HotkeyMap actions{ diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index a07aefe0812..dfe8682f1f7 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -360,16 +360,6 @@ class ChannelView final : public BaseWidget std::function colorVisitor_; - static constexpr int leftPadding = 8; - static constexpr int scrollbarPadding = 8; - -private slots: - void wordFlagsChanged() - { - queueLayout(); - update(); - } - void scrollUpdateRequested(); TooltipWidget *const tooltipWidget_{}; From 4ef099231330e310455ba68a41a527d5e7ced34f Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 17 Dec 2023 22:32:50 +0100 Subject: [PATCH 26/62] fix: json schema --- docs/ChatterinoTheme.schema.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/ChatterinoTheme.schema.json b/docs/ChatterinoTheme.schema.json index ca21221f006..e7690738c0c 100644 --- a/docs/ChatterinoTheme.schema.json +++ b/docs/ChatterinoTheme.schema.json @@ -261,8 +261,7 @@ "type": "array", "items": { "type": "number" }, "minItems": 2, - "maxItems": 2, - "additionalItems": false + "maxItems": 2 } }, "type": "object", From 3aad361c2bc2c24767852e74205ea05997df514a Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 17 Dec 2023 22:33:26 +0100 Subject: [PATCH 27/62] fix: cmake formatting --- src/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9c67945ee3a..9a525f2b824 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -698,12 +698,12 @@ set(SOURCE_FILES ${CMAKE_SOURCE_DIR}/resources/resources.qrc ) -if (WIN32) +if(WIN32) # Platform specific implementations list(APPEND SOURCE_FILES platform/windows/GlobalShortcutPrivate.cpp ) -endif () +endif() if (APPLE) set(MACOS_BUNDLE_ICON_FILE "${CMAKE_SOURCE_DIR}/resources/chatterino.icns") From ff99d9a772bfa4436933756b7473196f05cf7859 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 17 Dec 2023 22:35:20 +0100 Subject: [PATCH 28/62] fix: formatting --- src/singletons/Settings.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index fba7845eda1..f9946f2f416 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -23,7 +23,6 @@ #include #include - using TimeoutButton = std::pair; namespace chatterino { From a152476ebf13de33573b8b120d0a62e53f3dae9b Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 17 Dec 2023 22:38:04 +0100 Subject: [PATCH 29/62] fix: no base --- src/widgets/OverlayWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index df3498f8240..69696ace349 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -1,6 +1,5 @@ #include "widgets/OverlayWindow.hpp" -#include "BaseSettings.hpp" #include "common/Literals.hpp" #include "controllers/hotkeys/GlobalShortcut.hpp" #include "singletons/Settings.hpp" @@ -19,6 +18,7 @@ #include + namespace { using namespace chatterino; From 35038dbbe23a9ec53077e51500da54fe6f736e02 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 17 Dec 2023 22:49:22 +0100 Subject: [PATCH 30/62] fix: overlaywindow --- src/widgets/OverlayWindow.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 69696ace349..0f29ccca1a7 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -18,7 +18,6 @@ #include - namespace { using namespace chatterino; @@ -243,10 +242,12 @@ void OverlayWindow::keyReleaseEvent(QKeyEvent *event) void OverlayWindow::paintEvent(QPaintEvent * /*event*/) { +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT if (this->inert_) { return; } +#endif QPainter painter(this); QColor highlightColor( @@ -289,7 +290,14 @@ void OverlayWindow::setInteractionProgress(double progress) void OverlayWindow::startInteraction() { - if (this->inert_ || this->interacting_) +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT + if (this->inert_) + { + return; + } +#endif + + if (this->interacting_) { return; } From c6e374ffe3565f9ed7baf84952a8767201c30ea2 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 17 Dec 2023 23:10:05 +0100 Subject: [PATCH 31/62] fix: stuff --- src/widgets/OverlayWindow.cpp | 5 +++-- src/widgets/OverlayWindow.hpp | 2 +- src/widgets/Window.cpp | 3 +-- src/widgets/helper/ChannelView.cpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 0f29ccca1a7..ee5e1fa31a6 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -88,13 +88,14 @@ void triggerFirstActivation(QWidget *parent) namespace chatterino { -OverlayWindow::OverlayWindow(IndirectChannel channel, Split *split) +OverlayWindow::OverlayWindow(IndirectChannel channel) : QWidget(nullptr, Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint) , channel_(std::move(channel)) - , channelView_(nullptr, split) + , channelView_(nullptr) , interactAnimation_(this, "interactionProgress"_ba) { + this->setAttribute(Qt::WA_DeleteOnClose); this->setWindowTitle(u"Chatterino - Overlay"_s); auto *grid = new QGridLayout(this); diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index 45f3b7c1990..ccba983bd83 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -18,7 +18,7 @@ class OverlayWindow : public QWidget { Q_OBJECT public: - OverlayWindow(IndirectChannel channel, Split *split); + OverlayWindow(IndirectChannel channel); ~OverlayWindow() override; protected: diff --git a/src/widgets/Window.cpp b/src/widgets/Window.cpp index de934bca3bc..41f3f154674 100644 --- a/src/widgets/Window.cpp +++ b/src/widgets/Window.cpp @@ -416,8 +416,7 @@ void Window::addShortcuts() { if (auto *split = page->getSelectedSplit()) { - (new OverlayWindow(split->getIndirectChannel(), split)) - ->show(); + (new OverlayWindow(split->getIndirectChannel()))->show(); } } return {}; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index e46b48a0783..cdbd04e500d 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -294,7 +294,7 @@ ChannelView::ChannelView(QWidget *parent, Split *split, Context context, ChannelView::ChannelView(InternalCtor /*tag*/, QWidget *parent, Split *split, Context context, size_t messagesLimit) : BaseWidget(parent) - , split_(std::move(split)) + , split_(split) , scrollBar_(new Scrollbar(messagesLimit, this)) , highlightAnimation_(this) , context_(context) From e63fcf141eecd99275359880aefe5600467ad75e Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Mon, 18 Dec 2023 21:53:59 +0100 Subject: [PATCH 32/62] fix: more cursor and theme stuff --- src/widgets/OverlayWindow.cpp | 28 ++++++++++++++++++++++++++-- src/widgets/OverlayWindow.hpp | 2 ++ src/widgets/helper/ChannelView.cpp | 1 + 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index ee5e1fa31a6..7402b528141 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -110,6 +110,7 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) connect(&this->closeButton_, &TitleBarButton::leftClicked, [this]() { this->close(); }); + this->closeButton_.setCursor(Qt::PointingHandCursor); this->channelView_.installEventFilter(this); this->channelView_.setChannel(this->channel_.get()); @@ -210,6 +211,18 @@ bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) break; case QEvent::MouseMove: { auto *evt = dynamic_cast(event); + auto shiftPressed = evt->modifiers().testFlag(Qt::ShiftModifier); + if (!this->interacting_ && shiftPressed) + { + this->startInteraction(); + return true; + } + if (this->interacting_ && !shiftPressed) + { + this->endInteraction(); + return true; + } + if (this->moving_) { auto newPos = evt->globalPos() - this->moveOrigin_; @@ -217,6 +230,11 @@ bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) this->moveOrigin_ = evt->globalPos(); return true; } + if (this->interacting_) + { + this->setOverrideCursor(Qt::SizeAllCursor); + return true; + } return false; } break; @@ -225,6 +243,12 @@ bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) } } +void OverlayWindow::setOverrideCursor(const QCursor &cursor) +{ + this->channelView_.setCursor(cursor); + this->setCursor(cursor); +} + void OverlayWindow::keyPressEvent(QKeyEvent *event) { if (event->key() == Qt::Key_Shift) @@ -310,7 +334,7 @@ void OverlayWindow::startInteraction() } this->interactAnimation_.setDirection(QPropertyAnimation::Forward); this->interactAnimation_.start(); - this->setCursor(Qt::DragMoveCursor); + this->setOverrideCursor(Qt::SizeAllCursor); this->closeButton_.show(); } @@ -328,7 +352,7 @@ void OverlayWindow::endInteraction() } this->interactAnimation_.setDirection(QPropertyAnimation::Backward); this->interactAnimation_.start(); - this->setCursor(Qt::ArrowCursor); + this->setOverrideCursor(Qt::ArrowCursor); this->closeButton_.hide(); } diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index ccba983bd83..09cc1d2f84b 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -37,6 +37,8 @@ class OverlayWindow : public QWidget void startInteraction(); void endInteraction(); + void setOverrideCursor(const QCursor &cursor); + IndirectChannel channel_; pajlada::Signals::SignalHolder holder_; ChannelView channelView_; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index cdbd04e500d..44093fe25b8 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -557,6 +557,7 @@ void ChannelView::setColorVisitor( Q_ASSERT_X(this->colorVisitor_ == nullptr, "ChannelView::setColorVisitor", "The color visitor should only be set once."); this->colorVisitor_ = visitor; + this->themeChangedEvent(); } void ChannelView::setupHighlightAnimationColors() From 9a5cc34d99631de6ed39ad16866d0967aa820960 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Mon, 18 Dec 2023 22:02:30 +0100 Subject: [PATCH 33/62] fix: old compiler stuff --- src/widgets/OverlayWindow.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 7402b528141..be39552da77 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -45,14 +45,14 @@ class Grippy : public QSizeGrip bool hasKnowledge(Knowledge knowledge) { - QFlags current(std::bit_cast( + QFlags current(static_cast( getSettings()->overlayKnowledgeLevel.getValue())); return current.testFlag(knowledge); } void acquireKnowledge(Knowledge knowledge) { - QFlags current(std::bit_cast( + QFlags current(static_cast( getSettings()->overlayKnowledgeLevel.getValue())); current.setFlag(knowledge); getSettings()->overlayKnowledgeLevel = current; From 2d1947deb6f2c27cd2ca81009084f101a1c79ddc Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Mon, 18 Dec 2023 22:03:43 +0100 Subject: [PATCH 34/62] fix: old qt --- src/platform/windows/GlobalShortcutPrivate.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/platform/windows/GlobalShortcutPrivate.cpp b/src/platform/windows/GlobalShortcutPrivate.cpp index ffba1d41365..05c4d0177a9 100644 --- a/src/platform/windows/GlobalShortcutPrivate.cpp +++ b/src/platform/windows/GlobalShortcutPrivate.cpp @@ -60,7 +60,8 @@ quint32 GlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) } Q_ASSERT_X( - !modifiers.testAnyFlags({Qt::KeypadModifier, Qt::GroupSwitchModifier}), + !modifiers.testFlag(Qt::KeypadModifier) && + !modifiers.testFlag(Qt::GroupSwitchModifier), "GlobalShortcutPrivate::nativeModifiers", "KeypadModifier and GroupSwitchModifier can't be expressed on Windows"); From df2d66d0db4772dab5b8819f60e1c3be277ef710 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Mon, 18 Dec 2023 22:16:22 +0100 Subject: [PATCH 35/62] fix: more stuff --- src/platform/GlobalShortcutPrivate.hpp | 6 ++++-- src/widgets/OverlayWindow.cpp | 18 +++--------------- src/widgets/OverlayWindow.hpp | 4 ++++ 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/platform/GlobalShortcutPrivate.hpp b/src/platform/GlobalShortcutPrivate.hpp index 3f83a102ae1..a16cf11edea 100644 --- a/src/platform/GlobalShortcutPrivate.hpp +++ b/src/platform/GlobalShortcutPrivate.hpp @@ -1,6 +1,5 @@ #pragma once -#include #ifndef Q_OS_MAC # include #endif @@ -9,7 +8,6 @@ #include #include -#include #include namespace chatterino { @@ -39,6 +37,10 @@ class GlobalShortcutPrivate GlobalShortcutPrivate(GlobalShortcut *owner); ~GlobalShortcutPrivate() override; + GlobalShortcutPrivate(const GlobalShortcutPrivate &) = delete; + GlobalShortcutPrivate(GlobalShortcutPrivate &&) = delete; + GlobalShortcutPrivate &operator=(const GlobalShortcutPrivate &) = delete; + GlobalShortcutPrivate &operator=(GlobalShortcutPrivate &&) = delete; Qt::Key key; Qt::KeyboardModifiers mods; diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index be39552da77..4e4dcb4b618 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -6,6 +6,7 @@ #include "singletons/Theme.hpp" #include "widgets/BaseWidget.hpp" #include "widgets/helper/ChannelView.hpp" +#include "widgets/helper/InvisibleSizeGrip.hpp" #include "widgets/helper/TitlebarButton.hpp" #include "widgets/splits/Split.hpp" @@ -29,20 +30,6 @@ enum class Knowledge : std::int32_t { Activation = 1 << 0, }; -class Grippy : public QSizeGrip -{ -public: - Grippy(QWidget *parent) - : QSizeGrip(parent) - { - } - -protected: - void paintEvent(QPaintEvent *event) override - { - } -}; - bool hasKnowledge(Knowledge knowledge) { QFlags current(static_cast( @@ -100,7 +87,8 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) auto *grid = new QGridLayout(this); grid->addWidget(&this->channelView_, 0, 0); - grid->addWidget(new Grippy(this), 0, 0, Qt::AlignBottom | Qt::AlignRight); + grid->addWidget(new InvisibleSizeGrip(this), 0, 0, + Qt::AlignBottom | Qt::AlignRight); grid->addWidget(&this->closeButton_, 0, 0, Qt::AlignTop | Qt::AlignRight); grid->setContentsMargins(0, 0, 0, 0); diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index 09cc1d2f84b..a1a90fd69bd 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -20,6 +20,10 @@ class OverlayWindow : public QWidget public: OverlayWindow(IndirectChannel channel); ~OverlayWindow() override; + OverlayWindow(const OverlayWindow &) = delete; + OverlayWindow(OverlayWindow &&) = delete; + OverlayWindow &operator=(const OverlayWindow &) = delete; + OverlayWindow &operator=(OverlayWindow &&) = delete; protected: bool eventFilter(QObject *object, QEvent *event) override; From d68478084f4fa939fbce8e0fa5ab14d985617593 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Mon, 18 Dec 2023 22:58:37 +0100 Subject: [PATCH 36/62] refactor: native shortcuts --- src/CMakeLists.txt | 6 +- src/controllers/hotkeys/GlobalShortcut.cpp | 155 +--------------- src/controllers/hotkeys/GlobalShortcut.hpp | 5 +- .../hotkeys/GlobalShortcutPrivate.cpp | 172 ++++++++++++++++++ .../hotkeys/GlobalShortcutPrivate.hpp | 134 ++++++++++++++ src/platform/GlobalShortcutPrivate.hpp | 95 ---------- ...ivate.cpp => GlobalShortcutPrivateWin.cpp} | 10 +- 7 files changed, 319 insertions(+), 258 deletions(-) create mode 100644 src/controllers/hotkeys/GlobalShortcutPrivate.cpp create mode 100644 src/controllers/hotkeys/GlobalShortcutPrivate.hpp delete mode 100644 src/platform/GlobalShortcutPrivate.hpp rename src/platform/windows/{GlobalShortcutPrivate.cpp => GlobalShortcutPrivateWin.cpp} (95%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9a525f2b824..2eeaf91700d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -181,6 +181,8 @@ set(SOURCE_FILES controllers/hotkeys/GlobalShortcut.cpp controllers/hotkeys/GlobalShortcut.hpp controllers/hotkeys/GlobalShortcutFwd.hpp + controllers/hotkeys/GlobalShortcutPrivate.cpp + controllers/hotkeys/GlobalShortcutPrivate.hpp controllers/hotkeys/Hotkey.cpp controllers/hotkeys/Hotkey.hpp controllers/hotkeys/HotkeyCategory.hpp @@ -292,8 +294,6 @@ set(SOURCE_FILES messages/search/SubtierPredicate.cpp messages/search/SubtierPredicate.hpp - platform/GlobalShortcutPrivate.hpp - providers/Crashpad.cpp providers/Crashpad.hpp providers/IvrApi.cpp @@ -701,7 +701,7 @@ set(SOURCE_FILES if(WIN32) # Platform specific implementations list(APPEND SOURCE_FILES - platform/windows/GlobalShortcutPrivate.cpp + platform/windows/GlobalShortcutPrivateWin.cpp ) endif() diff --git a/src/controllers/hotkeys/GlobalShortcut.cpp b/src/controllers/hotkeys/GlobalShortcut.cpp index eb2de7a0b34..58068834238 100644 --- a/src/controllers/hotkeys/GlobalShortcut.cpp +++ b/src/controllers/hotkeys/GlobalShortcut.cpp @@ -2,163 +2,10 @@ #ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT -# include "common/QLogging.hpp" -# include "platform/GlobalShortcutPrivate.hpp" - -# include -# include -# include +# include "controllers/hotkeys/GlobalShortcutPrivate.hpp" namespace chatterino { -// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) -# ifndef Q_OS_MAC -size_t GlobalShortcutPrivate::REFCOUNT = 0; -# endif // Q_OS_MAC - -decltype(GlobalShortcutPrivate::SHORTCUTS) GlobalShortcutPrivate::SHORTCUTS; -// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) - -GlobalShortcutPrivate::GlobalShortcutPrivate(GlobalShortcut *owner) - : key(Qt::Key(0)) - , mods(Qt::NoModifier) - , owner_(owner) -{ -# ifndef Q_OS_MAC - if (REFCOUNT == 0) - { - QAbstractEventDispatcher::instance()->installNativeEventFilter(this); - } - REFCOUNT++; -# endif // Q_OS_MAC -} - -GlobalShortcutPrivate::~GlobalShortcutPrivate() -{ -# ifndef Q_OS_MAC - REFCOUNT--; - if (REFCOUNT == 0) - { - QAbstractEventDispatcher *dispatcher = - QAbstractEventDispatcher::instance(); - if (dispatcher != nullptr) - { - dispatcher->removeNativeEventFilter(this); - } - } -# endif // Q_OS_MAC -} - -bool GlobalShortcutPrivate::setShortcut(const QKeySequence &shortcut) -{ - // our caller already unset the shortcut - - if (shortcut.isEmpty()) - { - // same as unsetShortcut - return false; - } - - if (shortcut.count() > 1) - { - qCWarning(chatterinoHotkeys) - << "Global shortcuts must be composed of exactly one key with " - "optional modifiers."; - return false; - } - -# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - this->key = shortcut[0].key(); - this->mods = shortcut[0].keyboardModifiers(); -# else - this->key = Qt::Key(shortcut[0] & ~Qt::KeyboardModifierMask); - this->mods = Qt::KeyboardModifiers(shortcut[0] & Qt::KeyboardModifierMask); -# endif - - auto native = this->native(); - auto it = SHORTCUTS.find(native); - if (it != SHORTCUTS.end()) - { - // tap into the curren shortcut - it->second.emplace_back(this->owner_); - return true; - } - - // no previous shortcut registered - auto res = registerShortcut(native); - if (!res.ok) - { - qCWarning(chatterinoHotkeys) << "GlobalShortcut failed to register:" - << QKeySequence(this->key | this->mods) - << "(native) error:" << res.error; - return false; - } - SHORTCUTS.emplace(native, std::vector{this->owner_}); - - return true; -} - -bool GlobalShortcutPrivate::unsetShortcut() -{ - auto native = this->native(); - auto shortcut = SHORTCUTS.find(native); - if (shortcut == SHORTCUTS.end()) - { - return false; - } - - auto it = std::find(shortcut->second.begin(), shortcut->second.end(), - this->owner_); - if (it == shortcut->second.end()) - { - return false; - } - - shortcut->second.erase(it); - if (!shortcut->second.empty()) - { - return true; - } - - // No remaining shortcut -> unregister - auto res = GlobalShortcutPrivate::unregisterShortcut(native); - if (res.ok) - { - SHORTCUTS.erase(shortcut); - } - else - { - qWarning() << "GlobalShortcut failed to unregister:" - << QKeySequence(this->key | this->mods) - << "(native) error:" << res.error; - } - this->key = Qt::Key(0); - this->mods = Qt::NoModifier; - return res.ok; -} - -void GlobalShortcutPrivate::activateShortcut(Native native) -{ - auto it = SHORTCUTS.find(native); - if (it != SHORTCUTS.end()) - { - bool singleConsumer = it->second.size() == 1; - for (size_t i = 0; auto *consumer : it->second) - { - emit consumer->activated(i, singleConsumer); - i++; - } - } -} - -GlobalShortcutPrivate::Native GlobalShortcutPrivate::native() const -{ - return { - .key = GlobalShortcutPrivate::nativeKeycode(this->key), - .modifiers = GlobalShortcutPrivate::nativeModifiers(this->mods), - }; -} - GlobalShortcut::GlobalShortcut(QObject *parent) : QObject(parent) , private_(std::make_unique(this)) diff --git a/src/controllers/hotkeys/GlobalShortcut.hpp b/src/controllers/hotkeys/GlobalShortcut.hpp index aa70c4d1c36..7aaf58b06f3 100644 --- a/src/controllers/hotkeys/GlobalShortcut.hpp +++ b/src/controllers/hotkeys/GlobalShortcut.hpp @@ -6,7 +6,6 @@ #include #include -#include namespace chatterino { @@ -32,6 +31,10 @@ class GlobalShortcut : public QObject explicit GlobalShortcut(const QKeySequence &shortcut, QObject *parent = nullptr); ~GlobalShortcut() override; + GlobalShortcut(const GlobalShortcut &) = delete; + GlobalShortcut(GlobalShortcut &&) = delete; + GlobalShortcut &operator=(const GlobalShortcut &) = delete; + GlobalShortcut &operator=(GlobalShortcut &&) = delete; QKeySequence shortcut() const; bool setShortcut(const QKeySequence &shortcut); diff --git a/src/controllers/hotkeys/GlobalShortcutPrivate.cpp b/src/controllers/hotkeys/GlobalShortcutPrivate.cpp new file mode 100644 index 00000000000..8fc1be90184 --- /dev/null +++ b/src/controllers/hotkeys/GlobalShortcutPrivate.cpp @@ -0,0 +1,172 @@ +#include "controllers/hotkeys/GlobalShortcutPrivate.hpp" + +#include "debug/AssertInGuiThread.hpp" + +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT + +# include "common/QLogging.hpp" +# include "controllers/hotkeys/GlobalShortcut.hpp" + +# include +# include +# include + +namespace chatterino { + +// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) +# ifndef Q_OS_MAC +size_t GlobalShortcutPrivate::REFCOUNT = 0; +GlobalShortcutPrivate::EventFilter *GlobalShortcutPrivate::CURRENT_FILTER = + nullptr; +# endif // Q_OS_MAC + +decltype(GlobalShortcutPrivate::SHORTCUTS) GlobalShortcutPrivate::SHORTCUTS; +// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) + +GlobalShortcutPrivate::GlobalShortcutPrivate(GlobalShortcut *owner) + : key(Qt::Key(0)) + , mods(Qt::NoModifier) + , owner_(owner) +{ +# ifndef Q_OS_MAC + assertInGuiThread(); + + if (REFCOUNT == 0) + { + assert(CURRENT_FILTER == nullptr); + CURRENT_FILTER = new EventFilter; + QAbstractEventDispatcher::instance()->installNativeEventFilter( + CURRENT_FILTER); + } + REFCOUNT++; +# endif // Q_OS_MAC +} + +GlobalShortcutPrivate::~GlobalShortcutPrivate() +{ +# ifndef Q_OS_MAC + assertInGuiThread(); + + REFCOUNT--; + if (REFCOUNT == 0) + { + assert(CURRENT_FILTER != nullptr); + delete CURRENT_FILTER; // removes the filter from the app + CURRENT_FILTER = nullptr; + } +# endif // Q_OS_MAC +} + +bool GlobalShortcutPrivate::setShortcut(const QKeySequence &shortcut) +{ + // our caller already unset the shortcut + + if (shortcut.isEmpty()) + { + // same as unsetShortcut + return false; + } + + if (shortcut.count() > 1) + { + qCWarning(chatterinoHotkeys) + << "Global shortcuts must be composed of exactly one key with " + "optional modifiers."; + return false; + } + +# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + this->key = shortcut[0].key(); + this->mods = shortcut[0].keyboardModifiers(); +# else + this->key = Qt::Key(shortcut[0] & ~Qt::KeyboardModifierMask); + this->mods = Qt::KeyboardModifiers(shortcut[0] & Qt::KeyboardModifierMask); +# endif + + auto native = this->native(); + auto it = SHORTCUTS.find(native); + if (it != SHORTCUTS.end()) + { + // tap into the current shortcut + it->second.emplace_back(this->owner_); + return true; + } + + // no previous shortcut registered + auto res = registerShortcut(native); + if (!res.ok) + { + qCWarning(chatterinoHotkeys) << "GlobalShortcut failed to register:" + << QKeySequence(this->key | this->mods) + << "(native) error:" << res.error; + return false; + } + SHORTCUTS.emplace(native, std::vector{this->owner_}); + + return true; +} + +bool GlobalShortcutPrivate::unsetShortcut() +{ + auto native = this->native(); + auto shortcut = SHORTCUTS.find(native); + if (shortcut == SHORTCUTS.end()) + { + return false; + } + + auto it = std::find(shortcut->second.begin(), shortcut->second.end(), + this->owner_); + if (it == shortcut->second.end()) + { + return false; + } + + shortcut->second.erase(it); + if (!shortcut->second.empty()) + { + return true; + } + + // No remaining shortcut -> unregister + auto res = GlobalShortcutPrivate::unregisterShortcut(native); + if (res.ok) + { + SHORTCUTS.erase(shortcut); + } + else + { + qWarning() << "GlobalShortcut failed to unregister:" + << QKeySequence(this->key | this->mods) + << "(native) error:" << res.error; + } + this->key = Qt::Key(0); + this->mods = Qt::NoModifier; + return res.ok; +} + +void GlobalShortcutPrivate::activateShortcut(Native native) +{ + auto it = SHORTCUTS.find(native); + if (it != SHORTCUTS.end()) + { + bool singleConsumer = it->second.size() == 1; + for (size_t i = 0; auto *consumer : it->second) + { + emit consumer->activated(i, singleConsumer); + i++; + } + } +} + +GlobalShortcutPrivate::Native GlobalShortcutPrivate::native() const +{ + return { + .key = GlobalShortcutPrivate::nativeKeycode(this->key), + .modifiers = GlobalShortcutPrivate::nativeModifiers(this->mods), + }; +} + +} // namespace chatterino + +#endif diff --git a/src/controllers/hotkeys/GlobalShortcutPrivate.hpp b/src/controllers/hotkeys/GlobalShortcutPrivate.hpp new file mode 100644 index 00000000000..e828e9b2e88 --- /dev/null +++ b/src/controllers/hotkeys/GlobalShortcutPrivate.hpp @@ -0,0 +1,134 @@ +#pragma once + +#include "controllers/hotkeys/GlobalShortcutFwd.hpp" + +#ifndef Q_OS_MAC +# include +#endif + +#include +#include + +#include +#include + +namespace chatterino { + +class GlobalShortcut; + +struct GlobalShortcutResult { + bool ok; + uint32_t error; +}; + +class GlobalShortcutPrivate +{ +public: + /// A native representation of a key and its modifiers. + struct Native { + quint32 key; + quint32 modifiers; + + bool operator==(Native rhs) const noexcept + { + return this->key == rhs.key && this->modifiers == rhs.modifiers; + } + }; + + GlobalShortcutPrivate(GlobalShortcut *owner); + ~GlobalShortcutPrivate(); + GlobalShortcutPrivate(const GlobalShortcutPrivate &) = delete; + GlobalShortcutPrivate(GlobalShortcutPrivate &&) = delete; + GlobalShortcutPrivate &operator=(const GlobalShortcutPrivate &) = delete; + GlobalShortcutPrivate &operator=(GlobalShortcutPrivate &&) = delete; + + Qt::Key key; + Qt::KeyboardModifiers mods; + + /// Try to register the shortcut globally + /// @returns true if the shortcut was registered + bool setShortcut(const QKeySequence &shortcut); + /// Try to unregister the shortcut globally + /// @returns true if the shortcut was unregistered + bool unsetShortcut(); + + /// Handles activation of the spcified shortcut. + static void activateShortcut(Native native); + +private: + /// @returns the native keycode of this shortcut + Native native() const; + + /// Convert a Qt key to a native keycode. + /// If no native keycode maps to the specified Qt key, `0` is returned. + /// + /// This is implemented per platform. + static quint32 nativeKeycode(Qt::Key keycode); + /// Convert a Qt modifier to a native modifier-code. + /// If no native code maps to the specified Qt modifier, `0` is returned. + /// + /// This is implemented per platform. + static quint32 nativeModifiers(Qt::KeyboardModifiers modifiers); + + /// Registers the shortcut on the platform. + /// This is only called if this shortcut wasn't registered yet. + /// + /// This is implemented per platform. + static GlobalShortcutResult registerShortcut(Native native); + /// Unregisters the shortcut on the platform. + /// This is only called if this shortcut was registered. + /// + /// This is implemented per platform. + static GlobalShortcutResult unregisterShortcut(Native native); + + // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) + + /// Global table with all registered shortcuts and all handlers for them. + static std::unordered_map> SHORTCUTS; + +#ifndef Q_OS_MAC + struct EventFilter : public QAbstractNativeEventFilter { + public: +# if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) + using NativeEventResult = qintptr; +# else + using NativeEventResult = long; +# endif + /// Filter for global hotkeys. + /// + /// This is implemented per platform. + /// Implementations call #activateShortcut() when a shortcut is triggered. + /// + /// @sa #activateShortcut() + bool nativeEventFilter(const QByteArray &eventType, void *message, + NativeEventResult *result) override; + }; + + /// Global reference-count of instances of this class. + /// Used to manage the event-filter. + static size_t REFCOUNT; + /// The currently active event filter. + /// There is at most one event filter active. + /// Allocated with `new`. + static EventFilter *CURRENT_FILTER; +#endif + + // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) + + GlobalShortcut *owner_; +}; + +} // namespace chatterino + +template <> +struct std::hash { + std::size_t operator()( + chatterino::GlobalShortcutPrivate::Native const &n) const noexcept + { + std::size_t seed = 0; + boost::hash_combine(seed, n.key); + boost::hash_combine(seed, n.modifiers); + + return seed; + } +}; diff --git a/src/platform/GlobalShortcutPrivate.hpp b/src/platform/GlobalShortcutPrivate.hpp deleted file mode 100644 index a16cf11edea..00000000000 --- a/src/platform/GlobalShortcutPrivate.hpp +++ /dev/null @@ -1,95 +0,0 @@ -#pragma once - -#ifndef Q_OS_MAC -# include -#endif - -#include -#include - -#include -#include - -namespace chatterino { - -class GlobalShortcut; - -struct GlobalShortcutResult { - bool ok; - uint32_t error; -}; - -class GlobalShortcutPrivate -#ifndef Q_OS_MAC - : public QAbstractNativeEventFilter -#endif -{ -public: - struct Native { - quint32 key; - quint32 modifiers; - - bool operator==(Native rhs) const noexcept - { - return this->key == rhs.key && this->modifiers == rhs.modifiers; - } - }; - - GlobalShortcutPrivate(GlobalShortcut *owner); - ~GlobalShortcutPrivate() override; - GlobalShortcutPrivate(const GlobalShortcutPrivate &) = delete; - GlobalShortcutPrivate(GlobalShortcutPrivate &&) = delete; - GlobalShortcutPrivate &operator=(const GlobalShortcutPrivate &) = delete; - GlobalShortcutPrivate &operator=(GlobalShortcutPrivate &&) = delete; - - Qt::Key key; - Qt::KeyboardModifiers mods; - - bool setShortcut(const QKeySequence &shortcut); - bool unsetShortcut(); - -#ifndef Q_OS_MAC -# if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) - using NativeEventResult = qintptr; -# else - using NativeEventResult = long; -# endif - bool nativeEventFilter(const QByteArray &eventType, void *message, - NativeEventResult *result) override; -#endif - - static void activateShortcut(Native native); - -private: - Native native() const; - - static quint32 nativeKeycode(Qt::Key keycode); - static quint32 nativeModifiers(Qt::KeyboardModifiers modifiers); - - static GlobalShortcutResult registerShortcut(Native); - static GlobalShortcutResult unregisterShortcut(Native); - - // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) - static std::unordered_map> SHORTCUTS; -#ifndef Q_OS_MAC - static size_t REFCOUNT; -#endif - // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) - - GlobalShortcut *owner_; -}; - -} // namespace chatterino - -template <> -struct std::hash { - std::size_t operator()( - chatterino::GlobalShortcutPrivate::Native const &n) const noexcept - { - std::size_t seed = 0; - boost::hash_combine(seed, n.key); - boost::hash_combine(seed, n.modifiers); - - return seed; - } -}; diff --git a/src/platform/windows/GlobalShortcutPrivate.cpp b/src/platform/windows/GlobalShortcutPrivateWin.cpp similarity index 95% rename from src/platform/windows/GlobalShortcutPrivate.cpp rename to src/platform/windows/GlobalShortcutPrivateWin.cpp index 05c4d0177a9..5b42db83788 100644 --- a/src/platform/windows/GlobalShortcutPrivate.cpp +++ b/src/platform/windows/GlobalShortcutPrivateWin.cpp @@ -1,6 +1,6 @@ -#include "platform/GlobalShortcutPrivate.hpp" +#include "controllers/hotkeys/GlobalShortcutPrivate.hpp" -#include "Windows.h" +#include #include @@ -22,9 +22,9 @@ GlobalShortcutResult wrapWinError(BOOL success) namespace chatterino { -bool GlobalShortcutPrivate::nativeEventFilter(const QByteArray & /*eventType*/, - void *message, - NativeEventResult * /*result*/) +bool GlobalShortcutPrivate::EventFilter::nativeEventFilter( + const QByteArray & /*eventType*/, void *message, + NativeEventResult * /*result*/) { MSG *msg = static_cast(message); if (msg->message == WM_HOTKEY) From 6b34b29fc21738df956707d1682d82d59aa3de8f Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Tue, 26 Mar 2024 15:43:11 +0100 Subject: [PATCH 37/62] refactor: add more settings --- docs/ChatterinoTheme.schema.json | 14 +++-- resources/themes/Black.json | 6 +-- resources/themes/Dark.json | 6 +-- resources/themes/Light.json | 6 +-- resources/themes/White.json | 6 +-- src/messages/layouts/MessageLayoutContext.cpp | 5 +- src/messages/layouts/MessageLayoutContext.hpp | 2 +- src/singletons/Settings.hpp | 8 +++ src/singletons/Theme.cpp | 38 -------------- src/singletons/Theme.hpp | 2 - src/widgets/OverlayWindow.cpp | 52 ++++++++++++++----- src/widgets/OverlayWindow.hpp | 3 ++ src/widgets/helper/ChannelView.cpp | 11 ++-- src/widgets/helper/ChannelView.hpp | 1 + src/widgets/settingspages/GeneralPage.cpp | 26 ++++++++++ 15 files changed, 107 insertions(+), 79 deletions(-) diff --git a/docs/ChatterinoTheme.schema.json b/docs/ChatterinoTheme.schema.json index a4749d111de..0a6a0344752 100644 --- a/docs/ChatterinoTheme.schema.json +++ b/docs/ChatterinoTheme.schema.json @@ -300,16 +300,20 @@ "disabled": { "$ref": "#/definitions/qt-color" }, "selection": { "$ref": "#/definitions/qt-color" }, "textColors": { "$ref": "#/definitions/text-colors" }, - "background": { "$ref": "#/definitions/qt-color" }, + "background": { + "$ref": "#/definitions/qt-color", + "description": "Note: The alpha value is ignored (set through the settings)" + }, "shadow": { "type": "object", "additionalProperties": false, "properties": { - "color": { "$ref": "#/definitions/qt-color" }, - "offset": { "$ref": "#/definitions/qpointf" }, - "blurRadius": { "type": "number", "minimum": 0 } + "color": { + "$ref": "#/definitions/qt-color", + "description": "Note: The alpha value is ignored (set through the settings)" + } }, - "required": ["color", "offset", "blurRadius"] + "required": ["color"] } }, "required": [ diff --git a/resources/themes/Black.json b/resources/themes/Black.json index 712123ce514..ed7a57b4c7b 100644 --- a/resources/themes/Black.json +++ b/resources/themes/Black.json @@ -36,11 +36,9 @@ "regular": "#ffffff", "system": "#8c7f7f" }, - "background": "#32000000", + "background": "#000", "shadow": { - "color": "#000", - "offset": [2, 2], - "blurRadius": 8 + "color": "#000" } }, "scrollbars": { diff --git a/resources/themes/Dark.json b/resources/themes/Dark.json index ba2769ef5f3..cf0d9b781fb 100644 --- a/resources/themes/Dark.json +++ b/resources/themes/Dark.json @@ -36,11 +36,9 @@ "regular": "#ffffff", "system": "#8c7f7f" }, - "background": "#32000000", + "background": "#000", "shadow": { - "color": "#000", - "offset": [2, 2], - "blurRadius": 8 + "color": "#000" } }, "scrollbars": { diff --git a/resources/themes/Light.json b/resources/themes/Light.json index 8427efa92da..a80d747e4b9 100644 --- a/resources/themes/Light.json +++ b/resources/themes/Light.json @@ -36,11 +36,9 @@ "regular": "#000000", "system": "#8c7f7f" }, - "background": "#32ffffff", + "background": "#fff", "shadow": { - "color": "#000", - "offset": [2, 2], - "blurRadius": 8 + "color": "#000" } }, "scrollbars": { diff --git a/resources/themes/White.json b/resources/themes/White.json index ee9e6fd0781..bc539d0bdbd 100644 --- a/resources/themes/White.json +++ b/resources/themes/White.json @@ -36,11 +36,9 @@ "regular": "#000000", "system": "#8c7f7f" }, - "background": "#32ffffff", + "background": "#fff", "shadow": { - "color": "#000", - "offset": [2, 2], - "blurRadius": 8 + "color": "#000" } }, "scrollbars": { diff --git a/src/messages/layouts/MessageLayoutContext.cpp b/src/messages/layouts/MessageLayoutContext.cpp index a5ce4902fa7..db587dc16e6 100644 --- a/src/messages/layouts/MessageLayoutContext.cpp +++ b/src/messages/layouts/MessageLayoutContext.cpp @@ -3,6 +3,8 @@ #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" +#include + namespace chatterino { void MessageColors::applyTheme(Theme *theme) @@ -25,9 +27,10 @@ void MessageColors::applyTheme(Theme *theme) this->regular.alpha() != 255 || this->alternate.alpha() != 255; } -void MessageColors::applyOverlay(Theme *theme) +void MessageColors::applyOverlay(Theme *theme, int backgroundOpacity) { this->channelBackground = theme->overlayMessages.background; + this->channelBackground.setAlpha(std::clamp(backgroundOpacity, 0, 255)); this->regular = theme->overlayMessages.backgrounds.regular; this->alternate = theme->overlayMessages.backgrounds.alternate; diff --git a/src/messages/layouts/MessageLayoutContext.hpp b/src/messages/layouts/MessageLayoutContext.hpp index 83c7d436ef8..5c0e45b7c77 100644 --- a/src/messages/layouts/MessageLayoutContext.hpp +++ b/src/messages/layouts/MessageLayoutContext.hpp @@ -33,7 +33,7 @@ struct MessageColors { QColor unfocusedLastMessageLine; void applyTheme(Theme *theme); - void applyOverlay(Theme *theme); + void applyOverlay(Theme *theme, int backgroundOpacity); }; // TODO: Explore if we can let settings own this diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index d0ef239ea95..e923c3132ef 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -188,7 +188,15 @@ class Settings // BoolSetting useCustomWindowFrame = {"/appearance/useCustomWindowFrame", // false}; + IntSetting overlayBackgroundOpacity = { + "/appearance/overlay/backgroundOpacity", 50}; BoolSetting enableOverlayShadow = {"/appearance/overlay/shadow", true}; + IntSetting overlayShadowOpacity = {"/appearance/overlay/shadowOpacity", + 255}; + // These should be floats, but there's no good input UI for them + IntSetting overlayShadowOffsetX = {"/appearance/overlay/shadowOffsetX", 2}; + IntSetting overlayShadowOffsetY = {"/appearance/overlay/shadowOffsetY", 2}; + IntSetting overlayShadowRadius = {"/appearance/overlay/shadowRadius", 8}; #ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT QStringSetting overlayInertShortcut = {"/behaviour/overlay/inert", "Ctrl+Alt+Shift+U"}; diff --git a/src/singletons/Theme.cpp b/src/singletons/Theme.cpp index 2b72d28cb54..8c3462796bd 100644 --- a/src/singletons/Theme.cpp +++ b/src/singletons/Theme.cpp @@ -69,42 +69,6 @@ void parseInto(const QJsonObject &obj, const QJsonObject &fallbackObj, "current theme, and no fallback value found."; } -void parseInto(const QJsonObject &obj, - [[maybe_unused]] const QJsonObject &fallback, QLatin1String key, - QPointF &point) -{ - const auto &jsonValue = obj[key]; - if (!jsonValue.isArray()) [[unlikely]] - { - qCWarning(chatterinoTheme) << key - << "was expected but not found in the " - "current theme - using previous value."; - return; - } - auto arr = jsonValue.toArray(); - if (arr.size() != 2) [[unlikely]] - { - qCWarning(chatterinoTheme) << key << "must be an array of two numbers."; - return; - } - point = {arr[0].toDouble(), arr[1].toDouble()}; -} - -void parseInto(const QJsonObject &obj, - [[maybe_unused]] const QJsonObject &fallback, QLatin1String key, - double &target) -{ - const auto &jsonValue = obj[key]; - if (!jsonValue.isDouble()) [[unlikely]] - { - qCWarning(chatterinoTheme) << key - << "was expected but not found in the " - "current theme - using previous value."; - return; - } - target = jsonValue.toDouble(); -} - // NOLINTBEGIN(cppcoreguidelines-macro-usage) #define _c2StringLit(s, ty) s##ty #define parseColor(to, from, key) \ @@ -209,8 +173,6 @@ void parseOverlayMessages(const QJsonObject &overlayMessages, const auto shadowFallback = overlayMessagesFallback["shadow"_L1].toObject(); parseColor(theme.overlayMessages, shadow, color); - parseColor(theme.overlayMessages, shadow, offset); - parseColor(theme.overlayMessages, shadow, blurRadius); } } diff --git a/src/singletons/Theme.hpp b/src/singletons/Theme.hpp index 7345f1816b9..5dc3ec7fb9f 100644 --- a/src/singletons/Theme.hpp +++ b/src/singletons/Theme.hpp @@ -117,8 +117,6 @@ class Theme final : public Singleton struct { QColor color; - QPointF offset; - double blurRadius = 8.0; } shadow; } overlayMessages; diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 4e4dcb4b618..52f3fd4ef6d 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -103,7 +103,7 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) this->channelView_.installEventFilter(this); this->channelView_.setChannel(this->channel_.get()); this->channelView_.setColorVisitor([](MessageColors &colors, Theme *theme) { - colors.applyOverlay(theme); + colors.applyOverlay(theme, getSettings()->overlayBackgroundOpacity); }); this->channelView_.setAttribute(Qt::WA_TranslucentBackground); this->holder_.managedConnect(this->channel_.getChannelChanged(), [this]() { @@ -120,30 +120,39 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) this->interactAnimation_.setEndValue(1.0); this->interactAnimation_.setDuration(150); - auto applyDropShadowTheme = [this]() { - auto *theme = getTheme(); - this->dropShadow_->setColor(theme->overlayMessages.shadow.color); - this->dropShadow_->setOffset(theme->overlayMessages.shadow.offset); - this->dropShadow_->setBlurRadius( - theme->overlayMessages.shadow.blurRadius); - }; - getSettings()->enableOverlayShadow.connect( - [this, applyDropShadowTheme](bool value) { + auto *settings = getSettings(); + settings->enableOverlayShadow.connect( + [this](bool value) { if (value) { this->dropShadow_ = new QGraphicsDropShadowEffect; - applyDropShadowTheme(); this->channelView_.setGraphicsEffect(this->dropShadow_); } else { this->channelView_.setGraphicsEffect(nullptr); + this->dropShadow_ = nullptr; // deleted by setGraphicsEffect } + this->applyTheme(); }, this->holder_); + settings->overlayBackgroundOpacity.connect( + [this] { + this->channelView_.updateColorTheme(); + }, + this->holder_, false); + + auto applyIt = [this](auto /*unused*/) { + this->applyTheme(); + }; + settings->overlayShadowOffsetX.connect(applyIt, this->holder_, false); + settings->overlayShadowOffsetY.connect(applyIt, this->holder_, false); + settings->overlayShadowOpacity.connect(applyIt, this->holder_, false); + settings->overlayShadowRadius.connect(applyIt, this->holder_, false); - applyDropShadowTheme(); - this->holder_.managedConnect(getTheme()->updated, applyDropShadowTheme); + this->holder_.managedConnect(getTheme()->updated, [this] { + this->applyTheme(); + }); #ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT getSettings()->overlayInertShortcut.connect( @@ -172,6 +181,23 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) OverlayWindow::~OverlayWindow() = default; +void OverlayWindow::applyTheme() +{ + auto *theme = getTheme(); + auto *settings = getSettings(); + + if (this->dropShadow_) + { + auto shadowColor = theme->overlayMessages.shadow.color; + shadowColor.setAlpha( + std::clamp(settings->overlayShadowOpacity.getValue(), 0, 255)); + this->dropShadow_->setColor(shadowColor); + this->dropShadow_->setOffset(settings->overlayShadowOffsetX, + settings->overlayShadowOffsetY); + this->dropShadow_->setBlurRadius(settings->overlayShadowRadius); + } +} + bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) { switch (event->type()) diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index a1a90fd69bd..bba8b3b97e6 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -43,8 +43,11 @@ class OverlayWindow : public QWidget void setOverrideCursor(const QCursor &cursor); + void applyTheme(); + IndirectChannel channel_; pajlada::Signals::SignalHolder holder_; + ChannelView channelView_; QGraphicsDropShadowEffect *dropShadow_; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 6d5acfdfb3c..234c5211b3d 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -588,13 +588,18 @@ void ChannelView::themeChangedEvent() } } +void ChannelView::updateColorTheme() +{ + this->themeChangedEvent(); +} + void ChannelView::setColorVisitor( const std::function &visitor) { - Q_ASSERT_X(this->colorVisitor_ == nullptr, "ChannelView::setColorVisitor", - "The color visitor should only be set once."); + assert(this->colorVisitor_ == nullptr && + "The color visitor should only be set once."); this->colorVisitor_ = visitor; - this->themeChangedEvent(); + this->updateColorTheme(); } void ChannelView::setupHighlightAnimationColors() diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index b9fe5892166..d07898b0b71 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -192,6 +192,7 @@ class ChannelView final : public BaseWidget */ bool mayContainMessage(const MessagePtr &message); + void updateColorTheme(); void setColorVisitor( const std::function &visitor); diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index 9f78094076e..54e0edcc92d 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -962,9 +962,35 @@ void GeneralPage::initLayout(GeneralPageView &layout) s.useCustomFfzVipBadges); layout.addSubtitle("Overlay"); + layout.addIntInput( + "Background opacity (0-255)", s.overlayBackgroundOpacity, 0, 255, 1, + "Controls the opacity of the (possibly alternating) background behind " + "messages. The color is set through the current theme. 255 corresponds " + "to a fully opaque background."); layout.addCheckbox("Enable Shadow", s.enableOverlayShadow, false, "Enables a drop shadow on the overlay. This will use " "more processing power."); + layout.addIntInput("Shadow opacity (0-255)", s.overlayShadowOpacity, 0, 255, + 1, + "Controls the opacity of the added drop shadow. 255 " + "corresponds to a fully opaque shadow."); + layout + .addIntInput("Shadow radius", s.overlayShadowRadius, 0, 40, 1, + "Controls how far the shadow is spread (the blur " + "radius) in device-independent pixels.") + ->setSuffix("dp"); + layout + .addIntInput("Shadow offset x", s.overlayShadowOffsetX, -20, 20, 1, + "Controls how far the shadow is offset on the x axis in " + "device-independent pixels. A negative value offsets to " + "the left and a positive to the right.") + ->setSuffix("dp"); + layout + .addIntInput("Shadow offset y", s.overlayShadowOffsetY, -20, 20, 1, + "Controls how far the shadow is offset on the y axis in " + "device-independent pixels. A negative value offsets to " + "the top and a positive to the bottom.") + ->setSuffix("dp"); #ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT layout.addGlobalShortcut("Clickthrough Hotkey", s.overlayInertShortcut, "Toggles the inertia of the overlay."); From 2835fc3e84643df269a0c0e257307619fdec22b1 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Tue, 26 Mar 2024 15:48:06 +0100 Subject: [PATCH 38/62] fix: more update() --- src/widgets/OverlayWindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 52f3fd4ef6d..b1d7f033f82 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -196,6 +196,7 @@ void OverlayWindow::applyTheme() settings->overlayShadowOffsetY); this->dropShadow_->setBlurRadius(settings->overlayShadowRadius); } + this->update(); } bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) @@ -325,6 +326,7 @@ double OverlayWindow::interactionProgress() const void OverlayWindow::setInteractionProgress(double progress) { this->interactionProgress_ = progress; + this->update(); } void OverlayWindow::startInteraction() From 3cf61a697e7f96e0b5aa7dcabe5ca4b941824f3e Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Tue, 26 Mar 2024 16:04:17 +0100 Subject: [PATCH 39/62] feat: hide scrollbar in inertia --- src/widgets/OverlayWindow.cpp | 35 +++++++++++++++++++++-------- src/widgets/OverlayWindow.hpp | 2 ++ src/widgets/Scrollbar.cpp | 36 +++++++++++++++++++++--------- src/widgets/Scrollbar.hpp | 5 +++-- src/widgets/helper/ChannelView.cpp | 5 +++++ src/widgets/helper/ChannelView.hpp | 2 ++ 6 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index b1d7f033f82..14a89b7083d 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -8,6 +8,7 @@ #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/InvisibleSizeGrip.hpp" #include "widgets/helper/TitlebarButton.hpp" +#include "widgets/Scrollbar.hpp" #include "widgets/splits/Split.hpp" #include @@ -162,15 +163,7 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) this); QObject::connect(this->shortcut_.get(), &GlobalShortcut::activated, this, [this] { - this->inert_ = !this->inert_; - this->setWindowFlag( - Qt::WindowTransparentForInput, - this->inert_); - if (this->isHidden()) - { - this->show(); - } - this->endInteraction(); + this->setInert(!this->inert_); }); }, this->holder_); @@ -372,4 +365,28 @@ void OverlayWindow::endInteraction() this->closeButton_.hide(); } +void OverlayWindow::setInert(bool inert) +{ + if (this->inert_ == inert) + { + return; + } + + this->inert_ = inert; + + this->setWindowFlag(Qt::WindowTransparentForInput, inert); + if (this->isHidden()) + { + this->show(); + } + this->endInteraction(); + + auto *scrollbar = this->channelView_.scrollbar(); + scrollbar->setShowThumb(!inert); + if (inert) + { + scrollbar->scrollToBottom(); + } +} + } // namespace chatterino diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index bba8b3b97e6..b0e44191111 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -45,6 +45,8 @@ class OverlayWindow : public QWidget void applyTheme(); + void setInert(bool inert); + IndirectChannel channel_; pajlada::Signals::SignalHolder holder_; diff --git a/src/widgets/Scrollbar.cpp b/src/widgets/Scrollbar.cpp index 6ca0e248e96..dd707643522 100644 --- a/src/widgets/Scrollbar.cpp +++ b/src/widgets/Scrollbar.cpp @@ -286,18 +286,21 @@ void Scrollbar::paintEvent(QPaintEvent *) // width(), this->buttonHeight), // this->themeManager->ScrollbarArrow); - this->thumbRect_.setX(xOffset); - - // mouse over thumb - if (this->mouseDownIndex_ == 2) + if (this->showThumb_) { - painter.fillRect(this->thumbRect_, - this->theme->scrollbars.thumbSelected); - } - // mouse not over thumb - else - { - painter.fillRect(this->thumbRect_, this->theme->scrollbars.thumb); + this->thumbRect_.setX(xOffset); + + // mouse over thumb + if (this->mouseDownIndex_ == 2) + { + painter.fillRect(this->thumbRect_, + this->theme->scrollbars.thumbSelected); + } + // mouse not over thumb + else + { + painter.fillRect(this->thumbRect_, this->theme->scrollbars.thumb); + } } // draw highlights @@ -510,4 +513,15 @@ void Scrollbar::updateScroll() this->update(); } +void Scrollbar::setShowThumb(bool showThumb) +{ + if (this->showThumb_ == showThumb) + { + return; + } + + this->showThumb_ = showThumb; + this->update(); +} + } // namespace chatterino diff --git a/src/widgets/Scrollbar.hpp b/src/widgets/Scrollbar.hpp index d025539c12a..f88d234c611 100644 --- a/src/widgets/Scrollbar.hpp +++ b/src/widgets/Scrollbar.hpp @@ -50,6 +50,8 @@ class Scrollbar : public BaseWidget qreal getCurrentValue() const; qreal getRelativeCurrentValue() const; + void setShowThumb(bool showthumb); + // offset the desired value without breaking smooth scolling void offset(qreal value); pajlada::Signals::NoArgSignal &getCurrentValueChanged(); @@ -74,8 +76,6 @@ class Scrollbar : public BaseWidget LimitedQueueSnapshot &getHighlightSnapshot(); void updateScroll(); - QMutex mutex_; - QPropertyAnimation currentValueAnimation_; LimitedQueue highlights_; @@ -83,6 +83,7 @@ class Scrollbar : public BaseWidget LimitedQueueSnapshot highlightSnapshot_; bool atBottom_{false}; + bool showThumb_ = true; int mouseOverIndex_ = -1; int mouseDownIndex_ = -1; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 234c5211b3d..5fa316ee347 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -460,6 +460,11 @@ void ChannelView::initializeSignals() }); } +Scrollbar *ChannelView::scrollbar() +{ + return this->scrollBar_; +} + bool ChannelView::pausable() const { return pausable_; diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index d07898b0b71..4260ef5b9e7 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -196,6 +196,8 @@ class ChannelView final : public BaseWidget void setColorVisitor( const std::function &visitor); + Scrollbar *scrollbar(); + pajlada::Signals::Signal mouseDown; pajlada::Signals::NoArgSignal selectionChanged; pajlada::Signals::Signal tabHighlightRequested; From 278187030e926636b42dbdb4aeb580e845618704 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Tue, 26 Mar 2024 16:38:11 +0100 Subject: [PATCH 40/62] chore: more shortcut docs --- src/controllers/hotkeys/GlobalShortcut.cpp | 25 +++++++++++---- src/controllers/hotkeys/GlobalShortcut.hpp | 31 ++++++++++++++++--- .../hotkeys/GlobalShortcutPrivate.cpp | 2 ++ .../hotkeys/GlobalShortcutPrivate.hpp | 12 +++++++ 4 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/controllers/hotkeys/GlobalShortcut.cpp b/src/controllers/hotkeys/GlobalShortcut.cpp index 58068834238..41c4e2f7ee9 100644 --- a/src/controllers/hotkeys/GlobalShortcut.cpp +++ b/src/controllers/hotkeys/GlobalShortcut.cpp @@ -3,6 +3,7 @@ #ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT # include "controllers/hotkeys/GlobalShortcutPrivate.hpp" +# include "debug/AssertInGuiThread.hpp" namespace chatterino { @@ -16,24 +17,25 @@ GlobalShortcut::GlobalShortcut(const QKeySequence &shortcut, QObject *parent) : QObject(parent) , private_(std::make_unique(this)) { - setShortcut(shortcut); + assertInGuiThread(); + this->setShortcut(shortcut); } GlobalShortcut::~GlobalShortcut() { - if (this->private_->key != 0) - { - this->private_->unsetShortcut(); - } + assertInGuiThread(); + this->unsetShortcut(); } -QKeySequence GlobalShortcut::shortcut() const +QKeySequence GlobalShortcut::shortcut() const noexcept { + assertInGuiThread(); return {this->private_->key | this->private_->mods}; } bool GlobalShortcut::setShortcut(const QKeySequence &shortcut) { + assertInGuiThread(); if (this->private_->key != 0) { this->private_->unsetShortcut(); @@ -41,6 +43,17 @@ bool GlobalShortcut::setShortcut(const QKeySequence &shortcut) return this->private_->setShortcut(shortcut); } +bool GlobalShortcut::unsetShortcut() +{ + assertInGuiThread(); + if (this->private_->key != 0) + { + return this->private_->unsetShortcut(); + } + + return true; +} + } // namespace chatterino #endif diff --git a/src/controllers/hotkeys/GlobalShortcut.hpp b/src/controllers/hotkeys/GlobalShortcut.hpp index 7aaf58b06f3..fe334a31f8e 100644 --- a/src/controllers/hotkeys/GlobalShortcut.hpp +++ b/src/controllers/hotkeys/GlobalShortcut.hpp @@ -13,14 +13,19 @@ namespace chatterino { class GlobalShortcutPrivate; +/// @brief This class is used to create global keyboard shortcuts +/// +/// Multiple instances can listen to the same shortcut/key combination. The +/// implementation takes care of de-duplication. All listeners will get the +/// event. Through the `consumerID` (n-th listener) and `singleConsumer`, +/// listeners can decide whether to accept the signal. +/// +/// This class can ony be used from the GUI thread. +/// /// This implementation is an adapted version of the QxtGlobalShortcut (from libqxt): /// https://bitbucket.org/libqxt/libqxt/src/08d08b58c362000798e19c3b5979ad6a1e6e880a/src/widgets/qxtglobalshortcut.h /// /// Backends are found in `platform/`. -/// -/// In this implementation, multiple `GlobalShortcut`s can listen to the same key combination. -/// All listeners will get the event. Through the `consumerID` (n-th listener) and `singleConsumer`, -/// listeners can decide whether to accept the signal. class GlobalShortcut : public QObject { Q_OBJECT @@ -36,9 +41,25 @@ class GlobalShortcut : public QObject GlobalShortcut &operator=(const GlobalShortcut &) = delete; GlobalShortcut &operator=(GlobalShortcut &&) = delete; - QKeySequence shortcut() const; + /// Returns the current key combination + [[nodiscard]] QKeySequence shortcut() const noexcept; + + /// @brief Connects the listener to the @a shortcut + /// + /// If there isn't any global shortcut registered on the platform, this + /// will register one. + /// + /// @returns @c true if the listener was successfully connected bool setShortcut(const QKeySequence &shortcut); + /// @brief Disconnects this listener from the shortcut + /// + /// If this listener is the only one, the shortcut will be unregistered on + /// the platform. + /// + /// @returns @c true if the listener was disconnected + bool unsetShortcut(); + Q_SIGNALS: void activated(size_t consumerID, bool singleConsumer); diff --git a/src/controllers/hotkeys/GlobalShortcutPrivate.cpp b/src/controllers/hotkeys/GlobalShortcutPrivate.cpp index 8fc1be90184..af84d58c267 100644 --- a/src/controllers/hotkeys/GlobalShortcutPrivate.cpp +++ b/src/controllers/hotkeys/GlobalShortcutPrivate.cpp @@ -125,6 +125,8 @@ bool GlobalShortcutPrivate::unsetShortcut() shortcut->second.erase(it); if (!shortcut->second.empty()) { + this->key = Qt::Key(0); + this->mods = Qt::NoModifier; return true; } diff --git a/src/controllers/hotkeys/GlobalShortcutPrivate.hpp b/src/controllers/hotkeys/GlobalShortcutPrivate.hpp index e828e9b2e88..0ceba6c265a 100644 --- a/src/controllers/hotkeys/GlobalShortcutPrivate.hpp +++ b/src/controllers/hotkeys/GlobalShortcutPrivate.hpp @@ -21,6 +21,18 @@ struct GlobalShortcutResult { uint32_t error; }; +/// @brief Platform specific functionality for global shortcuts +/// +/// This is partially implemented per platform. +/// The following methods must be implemented per platform: +/// +/// - nativeKeycode() +/// - nativeModifiers() +/// - registerShortcut() +/// - unregisterShortcut() +/// - EventFilter::nativeEventFilter() (not on macOS) +/// +/// This class is instantiated for every shortcut (at most once). class GlobalShortcutPrivate { public: From ed3b0f5d04013e61a79f596c6692c427fbea9587 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Tue, 26 Mar 2024 16:51:49 +0100 Subject: [PATCH 41/62] fix: hide `inert` --- src/widgets/OverlayWindow.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index b0e44191111..9d7b97e9010 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -45,8 +45,6 @@ class OverlayWindow : public QWidget void applyTheme(); - void setInert(bool inert); - IndirectChannel channel_; pajlada::Signals::SignalHolder holder_; @@ -63,6 +61,8 @@ class OverlayWindow : public QWidget TitleBarButton closeButton_; #ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT + void setInert(bool inert); + std::unique_ptr shortcut_; bool inert_ = false; #endif From 41816a391ba268542f0397f442889819e619c3f7 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Tue, 26 Mar 2024 16:52:28 +0100 Subject: [PATCH 42/62] fix: hide inert2 because git is slow or something --- src/widgets/OverlayWindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 14a89b7083d..23529a59e0d 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -365,6 +365,7 @@ void OverlayWindow::endInteraction() this->closeButton_.hide(); } +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT void OverlayWindow::setInert(bool inert) { if (this->inert_ == inert) @@ -388,5 +389,6 @@ void OverlayWindow::setInert(bool inert) scrollbar->scrollToBottom(); } } +#endif } // namespace chatterino From ee8dec43d650f01198f7b00df91c36bb38024bed Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 8 Sep 2024 13:29:53 +0200 Subject: [PATCH 43/62] feat: more stuff and I think it works good enough for me --- src/CMakeLists.txt | 2 + src/controllers/hotkeys/ActionNames.hpp | 2 +- src/controllers/hotkeys/HotkeyController.cpp | 6 +- src/widgets/OverlayWindow.cpp | 373 ++++++++++++------- src/widgets/OverlayWindow.hpp | 50 ++- src/widgets/Window.cpp | 13 - src/widgets/helper/OverlayInteraction.cpp | 123 ++++++ src/widgets/helper/OverlayInteraction.hpp | 47 +++ src/widgets/helper/TitlebarButtons.cpp | 74 +++- src/widgets/helper/TitlebarButtons.hpp | 3 + src/widgets/splits/Split.cpp | 12 + src/widgets/splits/Split.hpp | 1 + src/widgets/splits/SplitHeader.cpp | 3 + 13 files changed, 521 insertions(+), 188 deletions(-) create mode 100644 src/widgets/helper/OverlayInteraction.cpp create mode 100644 src/widgets/helper/OverlayInteraction.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ce1709c13b4..ba4244af198 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -636,6 +636,8 @@ set(SOURCE_FILES widgets/helper/NotebookButton.hpp widgets/helper/NotebookTab.cpp widgets/helper/NotebookTab.hpp + widgets/helper/OverlayInteraction.cpp + widgets/helper/OverlayInteraction.hpp widgets/helper/RegExpItemDelegate.cpp widgets/helper/RegExpItemDelegate.hpp widgets/helper/ResizingTextEdit.cpp diff --git a/src/controllers/hotkeys/ActionNames.hpp b/src/controllers/hotkeys/ActionNames.hpp index 9dac22321e1..5ee5b617786 100644 --- a/src/controllers/hotkeys/ActionNames.hpp +++ b/src/controllers/hotkeys/ActionNames.hpp @@ -181,6 +181,7 @@ inline const std::map actionNames{ {"showSearch", ActionDefinition{"Search current channel"}}, {"showGlobalSearch", ActionDefinition{"Search all channels"}}, {"debug", ActionDefinition{"Show debug popup"}}, + {"popupOverlay", ActionDefinition{"New overlay popup"}}, }}, {HotkeyCategory::SplitInput, { @@ -287,7 +288,6 @@ inline const std::map actionNames{ .argumentsPromptHover = "What should be included in the new popup", }}, - {"popupOverlay", ActionDefinition{"New overlay popup"}}, {"quit", ActionDefinition{"Quit Chatterino"}}, {"removeTab", ActionDefinition{"Remove current tab"}}, {"reopenSplit", ActionDefinition{"Reopen closed split"}}, diff --git a/src/controllers/hotkeys/HotkeyController.cpp b/src/controllers/hotkeys/HotkeyController.cpp index b50e1e11f66..e8d36f21693 100644 --- a/src/controllers/hotkeys/HotkeyController.cpp +++ b/src/controllers/hotkeys/HotkeyController.cpp @@ -403,6 +403,9 @@ void HotkeyController::addDefaults(std::set &addedHotkeys) this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, QKeySequence("F10"), "debug", std::vector(), "open debug popup"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Ctrl+Alt+N"), "popupOverlay", {}, + "open overlay"); } // split input @@ -487,9 +490,6 @@ void HotkeyController::addDefaults(std::set &addedHotkeys) QKeySequence("Ctrl+Shift+N"), "popup", {"window"}, "new popup window from tab"); - this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, - QKeySequence("Ctrl+Alt+N"), "popupOverlay", {}, - "new popup overlay"); this->tryAddDefault(addedHotkeys, HotkeyCategory::Window, QKeySequence::ZoomIn, "zoom", {"in"}, "zoom in"); diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 23529a59e0d..0e69710b106 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -1,5 +1,6 @@ #include "widgets/OverlayWindow.hpp" +#include "common/FlagsEnum.hpp" #include "common/Literals.hpp" #include "controllers/hotkeys/GlobalShortcut.hpp" #include "singletons/Settings.hpp" @@ -7,7 +8,6 @@ #include "widgets/BaseWidget.hpp" #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/InvisibleSizeGrip.hpp" -#include "widgets/helper/TitlebarButton.hpp" #include "widgets/Scrollbar.hpp" #include "widgets/splits/Split.hpp" @@ -18,13 +18,22 @@ #include #include -#include +#ifdef Q_OS_WIN +# include +# include + +// This definition can be used to test the move interaction for other platforms +// on Windows by commenting it out. In a final build, Windows must always use +// this, as it's much smoother. +# define OVERLAY_NATIVE_MOVE +#endif namespace { using namespace chatterino; using namespace literals; +/// Progress the user has made in exploring the overlay enum class Knowledge : std::int32_t { None = 0, // User opened the overlay at least once @@ -33,73 +42,54 @@ enum class Knowledge : std::int32_t { bool hasKnowledge(Knowledge knowledge) { - QFlags current(static_cast( + FlagsEnum current(static_cast( getSettings()->overlayKnowledgeLevel.getValue())); - return current.testFlag(knowledge); + return current.has(knowledge); } void acquireKnowledge(Knowledge knowledge) { - QFlags current(static_cast( + FlagsEnum current(static_cast( getSettings()->overlayKnowledgeLevel.getValue())); - current.setFlag(knowledge); - getSettings()->overlayKnowledgeLevel = current; -} - -void triggerFirstActivation(QWidget *parent) -{ - if (hasKnowledge(Knowledge::Activation)) - { - return; - } - acquireKnowledge(Knowledge::Activation); - - auto welcomeText = - u"Hey! It looks like this is the first time you're using the overlay. You can move the overlay holding SHIFT and dragging it with your mouse. To resize the window, drag on the bottom right corner."_s; -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT - auto actualShortcut = - QKeySequence::fromString(getSettings()->overlayInertShortcut, - QKeySequence::PortableText) - .toString(QKeySequence::PortableText); - welcomeText += - u"By default the overlay is interactive. To toggle the click-through mode, press %1 (customizable in the settings)."_s - .arg(actualShortcut); -#endif - - auto *box = - new QMessageBox(QMessageBox::Information, u"Chatterino - Overlay"_s, - welcomeText, QMessageBox::Ok, parent); - box->open(); + current.set(knowledge); + getSettings()->overlayKnowledgeLevel = + static_cast>(current.value()); } } // namespace namespace chatterino { +using namespace std::chrono_literals; + OverlayWindow::OverlayWindow(IndirectChannel channel) : QWidget(nullptr, Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint) +#ifdef Q_OS_WIN + , sizeAllCursor_(::LoadCursor(nullptr, IDC_SIZEALL)) +#endif , channel_(std::move(channel)) , channelView_(nullptr) - , interactAnimation_(this, "interactionProgress"_ba) + , interaction_(this) { this->setAttribute(Qt::WA_DeleteOnClose); this->setWindowTitle(u"Chatterino - Overlay"_s); auto *grid = new QGridLayout(this); grid->addWidget(&this->channelView_, 0, 0); +#ifndef OVERLAY_NATIVE_MOVE grid->addWidget(new InvisibleSizeGrip(this), 0, 0, Qt::AlignBottom | Qt::AlignRight); - grid->addWidget(&this->closeButton_, 0, 0, Qt::AlignTop | Qt::AlignRight); - grid->setContentsMargins(0, 0, 0, 0); - - this->closeButton_.setButtonStyle(TitleBarButtonStyle::Close); - this->closeButton_.setScaleIndependantSize(46, 30); - this->closeButton_.hide(); - connect(&this->closeButton_, &TitleBarButton::leftClicked, [this]() { - this->close(); +#endif + this->interaction_.attach(grid); + + // the interaction overlay currently captures all events + this->interaction_.installEventFilter(this); + + this->shortInteraction_.setInterval(750ms); + QObject::connect(&this->shortInteraction_, &QTimer::timeout, [this] { + this->endInteraction(); }); - this->closeButton_.setCursor(Qt::PointingHandCursor); this->channelView_.installEventFilter(this); this->channelView_.setChannel(this->channel_.get()); @@ -110,6 +100,7 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) this->holder_.managedConnect(this->channel_.getChannelChanged(), [this]() { this->channelView_.setChannel(this->channel_.get()); }); + this->channelView_.scrollbar()->setShowThumb(false); this->setAutoFillBackground(false); this->resize(300, 500); @@ -117,10 +108,6 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) this->setContentsMargins(0, 0, 0, 0); this->setAttribute(Qt::WA_TranslucentBackground); - this->interactAnimation_.setStartValue(0.0); - this->interactAnimation_.setEndValue(1.0); - this->interactAnimation_.setDuration(150); - auto *settings = getSettings(); settings->enableOverlayShadow.connect( [this](bool value) { @@ -169,10 +156,15 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) this->holder_); #endif - triggerFirstActivation(this); + this->triggerFirstActivation(); } -OverlayWindow::~OverlayWindow() = default; +OverlayWindow::~OverlayWindow() +{ +#ifdef Q_OS_WIN + ::DestroyCursor(this->sizeAllCursor_); +#endif +} void OverlayWindow::applyTheme() { @@ -194,18 +186,14 @@ void OverlayWindow::applyTheme() bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) { +#ifndef OVERLAY_NATIVE_MOVE switch (event->type()) { case QEvent::MouseButtonPress: { auto *evt = dynamic_cast(event); - if (evt->modifiers().testFlag(Qt::ShiftModifier)) - { - this->moving_ = true; - this->moveOrigin_ = evt->globalPos(); - this->startInteraction(); - return true; - } - return false; + this->moving_ = true; + this->moveOrigin_ = evt->globalPos(); + return true; } break; case QEvent::MouseButtonRelease: { @@ -219,18 +207,6 @@ bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) break; case QEvent::MouseMove: { auto *evt = dynamic_cast(event); - auto shiftPressed = evt->modifiers().testFlag(Qt::ShiftModifier); - if (!this->interacting_ && shiftPressed) - { - this->startInteraction(); - return true; - } - if (this->interacting_ && !shiftPressed) - { - this->endInteraction(); - return true; - } - if (this->moving_) { auto newPos = evt->globalPos() - this->moveOrigin_; @@ -238,7 +214,7 @@ bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) this->moveOrigin_ = evt->globalPos(); return true; } - if (this->interacting_) + if (this->interaction_.isInteracting()) { this->setOverrideCursor(Qt::SizeAllCursor); return true; @@ -249,6 +225,10 @@ bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) default: return false; } +#else + (void)event; + return false; +#endif } void OverlayWindow::setOverrideCursor(const QCursor &cursor) @@ -257,69 +237,193 @@ void OverlayWindow::setOverrideCursor(const QCursor &cursor) this->setCursor(cursor); } -void OverlayWindow::keyPressEvent(QKeyEvent *event) +void OverlayWindow::enterEvent(EnterEvent * /*event*/) { - if (event->key() == Qt::Key_Shift) - { - this->startInteraction(); - } +#ifndef OVERLAY_NATIVE_MOVE + this->startInteraction(); +#endif } -void OverlayWindow::keyReleaseEvent(QKeyEvent *event) +void OverlayWindow::leaveEvent(QEvent * /*event*/) { - if (event->key() == Qt::Key_Shift) - { - this->endInteraction(); - } +#ifndef OVERLAY_NATIVE_MOVE + this->endInteraction(); +#endif } -void OverlayWindow::paintEvent(QPaintEvent * /*event*/) +#ifdef Q_OS_WIN +bool OverlayWindow::nativeEvent(const QByteArray &eventType, void *message, + NativeResult *result) { -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT - if (this->inert_) + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) + MSG *msg = reinterpret_cast(message); + + bool returnValue = false; + + switch (msg->message) { - return; +# ifdef OVERLAY_NATIVE_MOVE + case WM_NCHITTEST: + this->handleNCHITTEST(msg, result); + returnValue = true; + break; + case WM_MOUSEMOVE: + case WM_NCMOUSEMOVE: + this->startShortInteraction(); + break; + case WM_ENTERSIZEMOVE: + this->startInteraction(); + break; + case WM_EXITSIZEMOVE: + // wait a few seconds before hiding + this->startShortInteraction(); + break; + case WM_SETCURSOR: { + // When the window can be moved, the size-all cursor should be + // shown. Qt doesn't provide an interface to do this, so this + // manually sets the cursor. + if (LOWORD(msg->lParam) == HTCAPTION) + { + ::SetCursor(this->sizeAllCursor_); + *result = TRUE; + returnValue = true; + } + } + break; +# endif + + default: + return QWidget::nativeEvent(eventType, message, result); } -#endif - QPainter painter(this); - QColor highlightColor( - 255, 255, 255, std::max(int(255.0 * this->interactionProgress()), 50)); + QWidget::nativeEvent(eventType, message, result); - painter.setPen({highlightColor, 2}); - // outline - auto bounds = this->rect(); - painter.drawRect(bounds); + return returnValue; +} - if (this->interactionProgress() <= 0.0) +void OverlayWindow::handleNCHITTEST(MSG *msg, qintptr *result) +{ + // This implementation is similar to the one of BaseWindow, but has the + // following differences: + // - The window can always be resized (or: it can't be maximized) + // - The close button is advertised as HTCLIENT instead of HTCLOSE + // - There isn't any other client area (the entire window can be moved) + const LONG borderWidth = 8; // in device independent pixels + + auto rect = this->rect(); + + POINT p{GET_X_LPARAM(msg->lParam), GET_Y_LPARAM(msg->lParam)}; + ScreenToClient(msg->hwnd, &p); + + QPoint point(p.x, p.y); + point /= this->devicePixelRatio(); + + auto x = point.x(); + auto y = point.y(); + + *result = 0; + + // left border + if (x < rect.left() + borderWidth) { - return; + *result = HTLEFT; + } + // right border + if (x >= rect.right() - borderWidth) + { + *result = HTRIGHT; } - painter.setBrush(highlightColor); - painter.setPen(Qt::transparent); + // bottom border + if (y >= rect.bottom() - borderWidth) + { + *result = HTBOTTOM; + } + // top border + if (y < rect.top() + borderWidth) + { + *result = HTTOP; + } - // bottom resize triangle - auto br = bounds.bottomRight(); - std::array triangle = {br - QPoint{20, 0}, br, - br - QPoint{0, 20}}; - painter.drawPolygon(triangle.data(), triangle.size()); + // bottom left corner + if (x >= rect.left() && x < rect.left() + borderWidth && + y < rect.bottom() && y >= rect.bottom() - borderWidth) + { + *result = HTBOTTOMLEFT; + } + // bottom right corner + if (x < rect.right() && x >= rect.right() - borderWidth && + y < rect.bottom() && y >= rect.bottom() - borderWidth) + { + *result = HTBOTTOMRIGHT; + } + // top left corner + if (x >= rect.left() && x < rect.left() + borderWidth && y >= rect.top() && + y < rect.top() + borderWidth) + { + *result = HTTOPLEFT; + } + // top right corner + if (x < rect.right() && x >= rect.right() - borderWidth && + y >= rect.top() && y < rect.top() + borderWidth) + { + *result = HTTOPRIGHT; + } - // close button - auto buttonSize = this->closeButton_.size(); - painter.drawRect( - QRect{bounds.topRight() - QPoint{buttonSize.width(), 0}, buttonSize}); + if (*result == 0) + { + auto *closeButton = this->interaction_.closeButton(); + if (closeButton->isVisible() && closeButton->geometry().contains(point)) + { + *result = HTCLIENT; + } + else + { + *result = HTCAPTION; + } + } } +#endif -double OverlayWindow::interactionProgress() const +void OverlayWindow::triggerFirstActivation() { - return this->interactionProgress_; -} + if (hasKnowledge(Knowledge::Activation)) + { + return; + } + acquireKnowledge(Knowledge::Activation); -void OverlayWindow::setInteractionProgress(double progress) -{ - this->interactionProgress_ = progress; - this->update(); + auto welcomeText = + u"Hey! It looks like this is the first time you're using the overlay. "_s + "You can move the overlay by dragging it with your mouse. " +#ifdef OVERLAY_NATIVE_MOVE + "To resize the window, drag on any edge." +#else + "To resize the window, drag on the bottom right corner." +#endif + ; + +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT + auto actualShortcut = + QKeySequence::fromString(getSettings()->overlayInertShortcut, + QKeySequence::PortableText) + .toString(QKeySequence::PortableText); + welcomeText += u"

"_s + "By default, the overlay is interactive. " + "To toggle the click-through mode, press %1 (customizable " + "in the settings).".arg(actualShortcut); +#endif + + welcomeText += u"

"_s + "This is still an early version and some features are " + "missing. Please provide feedback on GitHub."; + + auto *box = + new QMessageBox(QMessageBox::Information, u"Chatterino - Overlay"_s, + welcomeText, QMessageBox::Ok, this); + box->open(); } void OverlayWindow::startInteraction() @@ -331,38 +435,26 @@ void OverlayWindow::startInteraction() } #endif - if (this->interacting_) + this->interaction_.startInteraction(); + this->shortInteraction_.stop(); +} + +void OverlayWindow::startShortInteraction() +{ +#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT + if (this->inert_) { return; } +#endif - this->interacting_ = true; - if (this->interactAnimation_.state() != QPropertyAnimation::Stopped) - { - this->interactAnimation_.stop(); - } - this->interactAnimation_.setDirection(QPropertyAnimation::Forward); - this->interactAnimation_.start(); - this->setOverrideCursor(Qt::SizeAllCursor); - this->closeButton_.show(); + this->interaction_.startInteraction(); + this->shortInteraction_.start(); } void OverlayWindow::endInteraction() { - if (!this->interacting_) - { - return; - } - - this->interacting_ = false; - if (this->interactAnimation_.state() != QPropertyAnimation::Stopped) - { - this->interactAnimation_.stop(); - } - this->interactAnimation_.setDirection(QPropertyAnimation::Backward); - this->interactAnimation_.start(); - this->setOverrideCursor(Qt::ArrowCursor); - this->closeButton_.hide(); + this->interaction_.endInteraction(); } #ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT @@ -382,11 +474,14 @@ void OverlayWindow::setInert(bool inert) } this->endInteraction(); - auto *scrollbar = this->channelView_.scrollbar(); - scrollbar->setShowThumb(!inert); if (inert) { - scrollbar->scrollToBottom(); + this->channelView_.scrollbar()->scrollToBottom(); + this->interaction_.hide(); + } + else + { + this->interaction_.show(); } } #endif diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index 9d7b97e9010..f674f629347 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -3,13 +3,17 @@ #include "common/Channel.hpp" #include "controllers/hotkeys/GlobalShortcutFwd.hpp" #include "widgets/helper/ChannelView.hpp" -#include "widgets/helper/TitlebarButton.hpp" +#include "widgets/helper/OverlayInteraction.hpp" #include #include -#include +#include #include +#ifdef Q_OS_WIN +# include +#endif + class QGraphicsDropShadowEffect; namespace chatterino { @@ -25,40 +29,52 @@ class OverlayWindow : public QWidget OverlayWindow &operator=(const OverlayWindow &) = delete; OverlayWindow &operator=(OverlayWindow &&) = delete; + void setOverrideCursor(const QCursor &cursor); + protected: +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + using NativeResult = qintptr; + using EnterEvent = QEnterEvent; +#else + using NativeResult = long; + using EnterEvent = QEvent; +#endif + bool eventFilter(QObject *object, QEvent *event) override; - void paintEvent(QPaintEvent *event) override; - void keyPressEvent(QKeyEvent *event) override; - void keyReleaseEvent(QKeyEvent *event) override; + void enterEvent(EnterEvent *event) override; + void leaveEvent(QEvent *event) override; -private: - Q_PROPERTY(double interactionProgress READ interactionProgress WRITE - setInteractionProgress) +#ifdef Q_OS_WIN + bool nativeEvent(const QByteArray &eventType, void *message, + NativeResult *result) override; +#endif - double interactionProgress() const; - void setInteractionProgress(double progress); +private: + void triggerFirstActivation(); void startInteraction(); + void startShortInteraction(); void endInteraction(); - void setOverrideCursor(const QCursor &cursor); - void applyTheme(); +#ifdef Q_OS_WIN + void handleNCHITTEST(MSG *msg, qintptr *result); + + HCURSOR sizeAllCursor_; +#endif + IndirectChannel channel_; pajlada::Signals::SignalHolder holder_; ChannelView channelView_; QGraphicsDropShadowEffect *dropShadow_; - bool interacting_ = false; bool moving_ = false; QPoint moveOrigin_; - double interactionProgress_ = 0.0; - QPropertyAnimation interactAnimation_; - - TitleBarButton closeButton_; + OverlayInteraction interaction_; + QTimer shortInteraction_; #ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT void setInert(bool inert); diff --git a/src/widgets/Window.cpp b/src/widgets/Window.cpp index 9e7bf6d94d6..47276cb4549 100644 --- a/src/widgets/Window.cpp +++ b/src/widgets/Window.cpp @@ -27,7 +27,6 @@ #include "widgets/helper/NotebookTab.hpp" #include "widgets/helper/TitlebarButton.hpp" #include "widgets/Notebook.hpp" -#include "widgets/OverlayWindow.hpp" #include "widgets/splits/ClosedSplits.hpp" #include "widgets/splits/Split.hpp" #include "widgets/splits/SplitContainer.hpp" @@ -422,18 +421,6 @@ void Window::addShortcuts() } return ""; }}, - {"popupOverlay", - [this](const auto &) -> QString { - if (auto *page = dynamic_cast( - this->notebook_->getSelectedPage())) - { - if (auto *split = page->getSelectedSplit()) - { - (new OverlayWindow(split->getIndirectChannel()))->show(); - } - } - return {}; - }}, {"zoom", [](std::vector arguments) -> QString { if (arguments.size() == 0) diff --git a/src/widgets/helper/OverlayInteraction.cpp b/src/widgets/helper/OverlayInteraction.cpp new file mode 100644 index 00000000000..d4a0d84feb7 --- /dev/null +++ b/src/widgets/helper/OverlayInteraction.cpp @@ -0,0 +1,123 @@ +#include "widgets/helper/OverlayInteraction.hpp" + +#include "common/Literals.hpp" +#include "widgets/OverlayWindow.hpp" + +#include + +namespace chatterino { + +using namespace literals; + +OverlayInteraction::OverlayInteraction(OverlayWindow *parent) + : QWidget(nullptr) + , interactAnimation_(this, "interactionProgress"_ba) + , window_(parent) +{ + this->interactAnimation_.setStartValue(0.0); + this->interactAnimation_.setEndValue(1.0); + + this->closeButton_.setButtonStyle(TitleBarButtonStyle::Close); + this->closeButton_.setScaleIndependantSize(46, 30); + this->closeButton_.hide(); + this->closeButton_.setCursor(Qt::PointingHandCursor); +} + +void OverlayInteraction::attach(QGridLayout *layout) +{ + layout->addWidget(this, 0, 0); + layout->addWidget(&this->closeButton_, 0, 0, Qt::AlignTop | Qt::AlignRight); + layout->setContentsMargins(0, 0, 0, 0); + + QObject::connect(&this->closeButton_, &TitleBarButton::leftClicked, + [this]() { + this->window_->close(); + }); +} + +QWidget *OverlayInteraction::closeButton() +{ + return &this->closeButton_; +} + +void OverlayInteraction::startInteraction() +{ + if (this->interacting_) + { + return; + } + + this->interacting_ = true; + if (this->interactAnimation_.state() != QPropertyAnimation::Stopped) + { + this->interactAnimation_.stop(); + } + this->interactAnimation_.setDirection(QPropertyAnimation::Forward); + this->interactAnimation_.setDuration(100); + this->interactAnimation_.start(); + this->window_->setOverrideCursor(Qt::SizeAllCursor); + this->closeButton_.show(); +} + +void OverlayInteraction::endInteraction() +{ + if (!this->interacting_) + { + return; + } + + this->interacting_ = false; + if (this->interactAnimation_.state() != QPropertyAnimation::Stopped) + { + this->interactAnimation_.stop(); + } + this->interactAnimation_.setDirection(QPropertyAnimation::Backward); + this->interactAnimation_.setDuration(200); + this->interactAnimation_.start(); + this->window_->setOverrideCursor(Qt::ArrowCursor); + this->closeButton_.hide(); +} + +bool OverlayInteraction::isInteracting() const +{ + return this->interacting_; +} + +void OverlayInteraction::paintEvent(QPaintEvent * /*event*/) +{ + QPainter painter(this); + QColor highlightColor( + 255, 255, 255, std::max(int(255.0 * this->interactionProgress()), 50)); + + painter.setPen({highlightColor, 2}); + // outline + auto bounds = this->rect(); + painter.drawRect(bounds); + + if (this->interactionProgress() <= 0.0) + { + return; + } + + highlightColor.setAlpha(highlightColor.alpha() / 4); + painter.setBrush(highlightColor); + painter.setPen(Qt::transparent); + + // close button + auto buttonSize = this->closeButton_.size(); + painter.drawRect( + QRect{bounds.topRight() - QPoint{buttonSize.width(), 0}, buttonSize}); +} + +double OverlayInteraction::interactionProgress() const +{ + return this->interactionProgress_; +} + +void OverlayInteraction::setInteractionProgress(double progress) +{ + this->interactionProgress_ = progress; + this->update(); +} + +} // namespace chatterino diff --git a/src/widgets/helper/OverlayInteraction.hpp b/src/widgets/helper/OverlayInteraction.hpp new file mode 100644 index 00000000000..25be810adfa --- /dev/null +++ b/src/widgets/helper/OverlayInteraction.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "widgets/helper/TitlebarButton.hpp" + +#include +#include + +class QGridLayout; + +namespace chatterino { + +class OverlayWindow; +class OverlayInteraction : public QWidget +{ + Q_OBJECT +public: + OverlayInteraction(OverlayWindow *parent); + + void attach(QGridLayout *layout); + + QWidget *closeButton(); + + void startInteraction(); + void endInteraction(); + + bool isInteracting() const; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + Q_PROPERTY(double interactionProgress READ interactionProgress WRITE + setInteractionProgress) + + TitleBarButton closeButton_; + + double interactionProgress() const; + void setInteractionProgress(double progress); + + bool interacting_ = false; + double interactionProgress_ = 0.0; + QPropertyAnimation interactAnimation_; + + OverlayWindow *window_; +}; + +} // namespace chatterino diff --git a/src/widgets/helper/TitlebarButtons.cpp b/src/widgets/helper/TitlebarButtons.cpp index 42a1c93aa07..4906094803f 100644 --- a/src/widgets/helper/TitlebarButtons.cpp +++ b/src/widgets/helper/TitlebarButtons.cpp @@ -47,33 +47,65 @@ void TitleBarButtons::hover(size_t ht, QPoint at) assert(false && "TitleBarButtons::hover precondition violated"); return; } - hovered->ncEnter(); - hovered->ncMove(hovered->mapFromGlobal(at)); - other1->ncLeave(); - other2->ncLeave(); + + if (hovered) + { + hovered->ncEnter(); + hovered->ncMove(hovered->mapFromGlobal(at)); + } + + if (other1) + { + other1->ncLeave(); + } + + if (other2) + { + other2->ncLeave(); + } } void TitleBarButtons::leave() { - this->minButton_->ncLeave(); - this->maxButton_->ncLeave(); - this->closeButton_->ncLeave(); + if (this->minButton_) + { + this->minButton_->ncLeave(); + } + if (this->maxButton_) + { + this->maxButton_->ncLeave(); + } + if (this->closeButton_) + { + this->closeButton_->ncLeave(); + } } void TitleBarButtons::mousePress(size_t ht, QPoint at) { auto *button = this->buttonForHt(ht); - button->ncMousePress(button->mapFromGlobal(at)); + if (button) + { + button->ncMousePress(button->mapFromGlobal(at)); + } } void TitleBarButtons::mouseRelease(size_t ht, QPoint at) { auto *button = this->buttonForHt(ht); - button->ncMouseRelease(button->mapFromGlobal(at)); + if (button) + { + button->ncMouseRelease(button->mapFromGlobal(at)); + } } void TitleBarButtons::updateMaxButton() { + if (!this->maxButton_) + { + return; + } + this->maxButton_->setButtonStyle( this->window_->windowState().testFlag(Qt::WindowMaximized) ? TitleBarButtonStyle::Unmaximize @@ -82,16 +114,28 @@ void TitleBarButtons::updateMaxButton() void TitleBarButtons::setSmallSize() { - this->minButton_->setScaleIndependantSize(30, 30); - this->maxButton_->setScaleIndependantSize(30, 30); - this->closeButton_->setScaleIndependantSize(30, 30); + this->setSize(30, 30); +} + +void TitleBarButtons::setSize(int width, int height) +{ + if (this->minButton_) + { + this->minButton_->setScaleIndependantSize(width, height); + } + if (this->maxButton_) + { + this->maxButton_->setScaleIndependantSize(width, height); + } + if (this->closeButton_) + { + this->closeButton_->setScaleIndependantSize(width, height); + } } void TitleBarButtons::setRegularSize() { - this->minButton_->setScaleIndependantSize(46, 30); - this->maxButton_->setScaleIndependantSize(46, 30); - this->closeButton_->setScaleIndependantSize(46, 30); + this->setSize(46, 30); } TitleBarButton *TitleBarButtons::buttonForHt(size_t ht) const diff --git a/src/widgets/helper/TitlebarButtons.hpp b/src/widgets/helper/TitlebarButtons.hpp index e7ee3eb5bf5..d92d16f076d 100644 --- a/src/widgets/helper/TitlebarButtons.hpp +++ b/src/widgets/helper/TitlebarButtons.hpp @@ -59,6 +59,9 @@ class TitleBarButtons : QObject /// @pre ht must be one of { HTMAXBUTTON, HTMINBUTTON, HTCLOSE }. TitleBarButton *buttonForHt(size_t ht) const; + /// Sets the size for all buttons + void setSize(int width, int height); + QWidget *window_ = nullptr; TitleBarButton *minButton_ = nullptr; TitleBarButton *maxButton_ = nullptr; diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index 641a87a1018..6a45bce1483 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -33,6 +33,7 @@ #include "widgets/helper/ResizingTextEdit.hpp" #include "widgets/helper/SearchPopup.hpp" #include "widgets/Notebook.hpp" +#include "widgets/OverlayWindow.hpp" #include "widgets/Scrollbar.hpp" #include "widgets/splits/DraggedSplit.hpp" #include "widgets/splits/SplitContainer.hpp" @@ -58,6 +59,7 @@ #include #include + namespace { using namespace chatterino; @@ -765,6 +767,11 @@ void Split::addShortcuts() } return ""; }}, + {"popupOverlay", + [this](const auto &) -> QString { + this->popupOverlay(); + return {}; + }}, }; this->shortcuts_ = getApp()->getHotkeys()->shortcutsForCategory( @@ -1104,6 +1111,11 @@ void Split::popup() window.show(); } +void Split::popupOverlay() +{ + (new OverlayWindow(this->getIndirectChannel()))->show(); +} + void Split::clear() { this->view_->clearMessages(); diff --git a/src/widgets/splits/Split.hpp b/src/widgets/splits/Split.hpp index ab6322d5cdd..a050a9aebe1 100644 --- a/src/widgets/splits/Split.hpp +++ b/src/widgets/splits/Split.hpp @@ -179,6 +179,7 @@ public slots: void explainMoving(); void explainSplitting(); void popup(); + void popupOverlay(); void clear(); void openInBrowser(); void openModViewInBrowser(); diff --git a/src/widgets/splits/SplitHeader.cpp b/src/widgets/splits/SplitHeader.cpp index 09725879b23..1658e602c08 100644 --- a/src/widgets/splits/SplitHeader.cpp +++ b/src/widgets/splits/SplitHeader.cpp @@ -390,6 +390,9 @@ std::unique_ptr SplitHeader::createMainMenu() menu->addAction( "Popup", this->split_, &Split::popup, h->getDisplaySequence(HotkeyCategory::Window, "popup", {{"split"}})); + menu->addAction( + "Popup overlay", this->split_, &Split::popupOverlay, + h->getDisplaySequence(HotkeyCategory::Split, "popupOverlay")); menu->addAction( "Search", this->split_, [this] { From 9e72c0ca2df1d023b91292b4bc998cbf99063380 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 8 Sep 2024 13:46:21 +0200 Subject: [PATCH 44/62] fix: changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c4af9a1cc0c..b2f0033a9e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,9 @@ ## Unversioned - Major: Add option to show pronouns in user card. (#5442, #5583) -- Minor: Added transparent overlay window (default keybind: CTRL + ALT + N). (#4746) - Major: Release plugins alpha. (#5288) - Major: Improve high-DPI support on Windows. (#4868, #5391) +- Major: Added transparent overlay window (default keybind: CTRL + ALT + N). (#4746) - Minor: Removed the Ctrl+Shift+L hotkey for toggling the "live only" tab visibility state. (#5530) - Minor: Moved tab visibility control to a submenu, without any toggle actions. (#5530) - Minor: Add option to customise Moderation buttons with images. (#5369) From 3e86071e31a2f267af9eedc896402cebf0025f0e Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 8 Sep 2024 13:48:11 +0200 Subject: [PATCH 45/62] fix: formatting Why does it insert a newline there ...??? --- src/widgets/splits/Split.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index 6a45bce1483..d457ba8f700 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -59,7 +59,6 @@ #include #include - namespace { using namespace chatterino; From 513536674352922f840ba7dd2bdf3cc1907b2cb0 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 8 Sep 2024 14:05:29 +0200 Subject: [PATCH 46/62] fix: use NativeResult --- src/widgets/OverlayWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 0e69710b106..057235bb1b6 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -301,7 +301,7 @@ bool OverlayWindow::nativeEvent(const QByteArray &eventType, void *message, return returnValue; } -void OverlayWindow::handleNCHITTEST(MSG *msg, qintptr *result) +void OverlayWindow::handleNCHITTEST(MSG *msg, NativeResult *result) { // This implementation is similar to the one of BaseWindow, but has the // following differences: From 72734630f5a6ea552a9be462cfb1bf055435c189 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 8 Sep 2024 14:18:13 +0200 Subject: [PATCH 47/62] fix: aaaaaaaaaaaaaaaaaaaaaa --- src/widgets/OverlayWindow.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index f674f629347..117daaa6ed6 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -59,7 +59,7 @@ class OverlayWindow : public QWidget void applyTheme(); #ifdef Q_OS_WIN - void handleNCHITTEST(MSG *msg, qintptr *result); + void handleNCHITTEST(MSG *msg, NativeResult *result); HCURSOR sizeAllCursor_; #endif From bc5fbdd919dd2779053751df930cfd44d8bd96d1 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 8 Sep 2024 22:17:47 +0200 Subject: [PATCH 48/62] fix: global -> local shortcut --- src/CMakeLists.txt | 12 - src/controllers/hotkeys/ActionNames.hpp | 13 + src/controllers/hotkeys/GlobalShortcut.cpp | 59 ----- src/controllers/hotkeys/GlobalShortcut.hpp | 72 ----- src/controllers/hotkeys/GlobalShortcutFwd.hpp | 26 -- .../hotkeys/GlobalShortcutPrivate.cpp | 174 ------------ .../hotkeys/GlobalShortcutPrivate.hpp | 146 ----------- src/controllers/hotkeys/HotkeyController.cpp | 3 + .../windows/GlobalShortcutPrivateWin.cpp | 247 ------------------ src/singletons/Settings.hpp | 1 - src/singletons/WindowManager.cpp | 15 ++ src/singletons/WindowManager.hpp | 3 + src/widgets/Notebook.cpp | 16 ++ src/widgets/Notebook.hpp | 3 + src/widgets/OverlayWindow.cpp | 89 ++++--- src/widgets/OverlayWindow.hpp | 16 +- src/widgets/settingspages/GeneralPage.cpp | 4 - src/widgets/splits/Split.cpp | 51 +++- src/widgets/splits/Split.hpp | 7 +- src/widgets/splits/SplitHeader.cpp | 2 +- 20 files changed, 173 insertions(+), 786 deletions(-) delete mode 100644 src/controllers/hotkeys/GlobalShortcut.cpp delete mode 100644 src/controllers/hotkeys/GlobalShortcut.hpp delete mode 100644 src/controllers/hotkeys/GlobalShortcutFwd.hpp delete mode 100644 src/controllers/hotkeys/GlobalShortcutPrivate.cpp delete mode 100644 src/controllers/hotkeys/GlobalShortcutPrivate.hpp delete mode 100644 src/platform/windows/GlobalShortcutPrivateWin.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1f887a383ee..69edc676c66 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -186,11 +186,6 @@ set(SOURCE_FILES controllers/highlights/UserHighlightModel.hpp controllers/hotkeys/ActionNames.hpp - controllers/hotkeys/GlobalShortcut.cpp - controllers/hotkeys/GlobalShortcut.hpp - controllers/hotkeys/GlobalShortcutFwd.hpp - controllers/hotkeys/GlobalShortcutPrivate.cpp - controllers/hotkeys/GlobalShortcutPrivate.hpp controllers/hotkeys/Hotkey.cpp controllers/hotkeys/Hotkey.hpp controllers/hotkeys/HotkeyCategory.hpp @@ -732,13 +727,6 @@ set(SOURCE_FILES ${CMAKE_SOURCE_DIR}/resources/resources.qrc ) -if(WIN32) - # Platform specific implementations - list(APPEND SOURCE_FILES - platform/windows/GlobalShortcutPrivateWin.cpp - ) -endif() - if (APPLE) set(MACOS_BUNDLE_ICON_FILE "${CMAKE_SOURCE_DIR}/resources/chatterino.icns") list(APPEND SOURCE_FILES "${MACOS_BUNDLE_ICON_FILE}") diff --git a/src/controllers/hotkeys/ActionNames.hpp b/src/controllers/hotkeys/ActionNames.hpp index 5ee5b617786..d4209b2c3c7 100644 --- a/src/controllers/hotkeys/ActionNames.hpp +++ b/src/controllers/hotkeys/ActionNames.hpp @@ -182,6 +182,19 @@ inline const std::map actionNames{ {"showGlobalSearch", ActionDefinition{"Search all channels"}}, {"debug", ActionDefinition{"Show debug popup"}}, {"popupOverlay", ActionDefinition{"New overlay popup"}}, + {"togglePopupInertia", + ActionDefinition{ + .displayName = "Toggle overlay click-through", + .argumentDescription = "", + .minCountArguments = 1, + .maxCountArguments = 1, + .possibleArguments{ + {"This", {"this"}}, + {"All", {"all"}}, + {"This or all", {"thisOrAll"}}, + }, + .argumentsPrompt = "Target popup:", + }}, }}, {HotkeyCategory::SplitInput, { diff --git a/src/controllers/hotkeys/GlobalShortcut.cpp b/src/controllers/hotkeys/GlobalShortcut.cpp deleted file mode 100644 index 41c4e2f7ee9..00000000000 --- a/src/controllers/hotkeys/GlobalShortcut.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "controllers/hotkeys/GlobalShortcut.hpp" - -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT - -# include "controllers/hotkeys/GlobalShortcutPrivate.hpp" -# include "debug/AssertInGuiThread.hpp" - -namespace chatterino { - -GlobalShortcut::GlobalShortcut(QObject *parent) - : QObject(parent) - , private_(std::make_unique(this)) -{ -} - -GlobalShortcut::GlobalShortcut(const QKeySequence &shortcut, QObject *parent) - : QObject(parent) - , private_(std::make_unique(this)) -{ - assertInGuiThread(); - this->setShortcut(shortcut); -} - -GlobalShortcut::~GlobalShortcut() -{ - assertInGuiThread(); - this->unsetShortcut(); -} - -QKeySequence GlobalShortcut::shortcut() const noexcept -{ - assertInGuiThread(); - return {this->private_->key | this->private_->mods}; -} - -bool GlobalShortcut::setShortcut(const QKeySequence &shortcut) -{ - assertInGuiThread(); - if (this->private_->key != 0) - { - this->private_->unsetShortcut(); - } - return this->private_->setShortcut(shortcut); -} - -bool GlobalShortcut::unsetShortcut() -{ - assertInGuiThread(); - if (this->private_->key != 0) - { - return this->private_->unsetShortcut(); - } - - return true; -} - -} // namespace chatterino - -#endif diff --git a/src/controllers/hotkeys/GlobalShortcut.hpp b/src/controllers/hotkeys/GlobalShortcut.hpp deleted file mode 100644 index fe334a31f8e..00000000000 --- a/src/controllers/hotkeys/GlobalShortcut.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -#include "controllers/hotkeys/GlobalShortcutFwd.hpp" - -#include -#include - -#include - -namespace chatterino { - -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT - -class GlobalShortcutPrivate; - -/// @brief This class is used to create global keyboard shortcuts -/// -/// Multiple instances can listen to the same shortcut/key combination. The -/// implementation takes care of de-duplication. All listeners will get the -/// event. Through the `consumerID` (n-th listener) and `singleConsumer`, -/// listeners can decide whether to accept the signal. -/// -/// This class can ony be used from the GUI thread. -/// -/// This implementation is an adapted version of the QxtGlobalShortcut (from libqxt): -/// https://bitbucket.org/libqxt/libqxt/src/08d08b58c362000798e19c3b5979ad6a1e6e880a/src/widgets/qxtglobalshortcut.h -/// -/// Backends are found in `platform/`. -class GlobalShortcut : public QObject -{ - Q_OBJECT - Q_PROPERTY(QKeySequence shortcut READ shortcut WRITE setShortcut) - -public: - explicit GlobalShortcut(QObject *parent = nullptr); - explicit GlobalShortcut(const QKeySequence &shortcut, - QObject *parent = nullptr); - ~GlobalShortcut() override; - GlobalShortcut(const GlobalShortcut &) = delete; - GlobalShortcut(GlobalShortcut &&) = delete; - GlobalShortcut &operator=(const GlobalShortcut &) = delete; - GlobalShortcut &operator=(GlobalShortcut &&) = delete; - - /// Returns the current key combination - [[nodiscard]] QKeySequence shortcut() const noexcept; - - /// @brief Connects the listener to the @a shortcut - /// - /// If there isn't any global shortcut registered on the platform, this - /// will register one. - /// - /// @returns @c true if the listener was successfully connected - bool setShortcut(const QKeySequence &shortcut); - - /// @brief Disconnects this listener from the shortcut - /// - /// If this listener is the only one, the shortcut will be unregistered on - /// the platform. - /// - /// @returns @c true if the listener was disconnected - bool unsetShortcut(); - -Q_SIGNALS: - void activated(size_t consumerID, bool singleConsumer); - -private: - std::unique_ptr private_; -}; - -#endif - -} // namespace chatterino diff --git a/src/controllers/hotkeys/GlobalShortcutFwd.hpp b/src/controllers/hotkeys/GlobalShortcutFwd.hpp deleted file mode 100644 index 7cc7c6abe0f..00000000000 --- a/src/controllers/hotkeys/GlobalShortcutFwd.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -// This is a helper to define `CHATTERINO_HAS_GLOBAL_SHORTCUT` and forward declare the related classes. - -#if __has_include() -# include -#else -# include -#endif - -#if defined(Q_OS_WIN) -// If this is defined, then a backend for global shortcuts exists. -# define CHATTERINO_HAS_GLOBAL_SHORTCUT -#endif - -namespace chatterino { - -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT - -class GlobalShortcutPrivate; - -class GlobalShortcut; - -#endif - -} // namespace chatterino diff --git a/src/controllers/hotkeys/GlobalShortcutPrivate.cpp b/src/controllers/hotkeys/GlobalShortcutPrivate.cpp deleted file mode 100644 index af84d58c267..00000000000 --- a/src/controllers/hotkeys/GlobalShortcutPrivate.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#include "controllers/hotkeys/GlobalShortcutPrivate.hpp" - -#include "debug/AssertInGuiThread.hpp" - -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT - -# include "common/QLogging.hpp" -# include "controllers/hotkeys/GlobalShortcut.hpp" - -# include -# include -# include - -namespace chatterino { - -// NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) -# ifndef Q_OS_MAC -size_t GlobalShortcutPrivate::REFCOUNT = 0; -GlobalShortcutPrivate::EventFilter *GlobalShortcutPrivate::CURRENT_FILTER = - nullptr; -# endif // Q_OS_MAC - -decltype(GlobalShortcutPrivate::SHORTCUTS) GlobalShortcutPrivate::SHORTCUTS; -// NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) - -GlobalShortcutPrivate::GlobalShortcutPrivate(GlobalShortcut *owner) - : key(Qt::Key(0)) - , mods(Qt::NoModifier) - , owner_(owner) -{ -# ifndef Q_OS_MAC - assertInGuiThread(); - - if (REFCOUNT == 0) - { - assert(CURRENT_FILTER == nullptr); - CURRENT_FILTER = new EventFilter; - QAbstractEventDispatcher::instance()->installNativeEventFilter( - CURRENT_FILTER); - } - REFCOUNT++; -# endif // Q_OS_MAC -} - -GlobalShortcutPrivate::~GlobalShortcutPrivate() -{ -# ifndef Q_OS_MAC - assertInGuiThread(); - - REFCOUNT--; - if (REFCOUNT == 0) - { - assert(CURRENT_FILTER != nullptr); - delete CURRENT_FILTER; // removes the filter from the app - CURRENT_FILTER = nullptr; - } -# endif // Q_OS_MAC -} - -bool GlobalShortcutPrivate::setShortcut(const QKeySequence &shortcut) -{ - // our caller already unset the shortcut - - if (shortcut.isEmpty()) - { - // same as unsetShortcut - return false; - } - - if (shortcut.count() > 1) - { - qCWarning(chatterinoHotkeys) - << "Global shortcuts must be composed of exactly one key with " - "optional modifiers."; - return false; - } - -# if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - this->key = shortcut[0].key(); - this->mods = shortcut[0].keyboardModifiers(); -# else - this->key = Qt::Key(shortcut[0] & ~Qt::KeyboardModifierMask); - this->mods = Qt::KeyboardModifiers(shortcut[0] & Qt::KeyboardModifierMask); -# endif - - auto native = this->native(); - auto it = SHORTCUTS.find(native); - if (it != SHORTCUTS.end()) - { - // tap into the current shortcut - it->second.emplace_back(this->owner_); - return true; - } - - // no previous shortcut registered - auto res = registerShortcut(native); - if (!res.ok) - { - qCWarning(chatterinoHotkeys) << "GlobalShortcut failed to register:" - << QKeySequence(this->key | this->mods) - << "(native) error:" << res.error; - return false; - } - SHORTCUTS.emplace(native, std::vector{this->owner_}); - - return true; -} - -bool GlobalShortcutPrivate::unsetShortcut() -{ - auto native = this->native(); - auto shortcut = SHORTCUTS.find(native); - if (shortcut == SHORTCUTS.end()) - { - return false; - } - - auto it = std::find(shortcut->second.begin(), shortcut->second.end(), - this->owner_); - if (it == shortcut->second.end()) - { - return false; - } - - shortcut->second.erase(it); - if (!shortcut->second.empty()) - { - this->key = Qt::Key(0); - this->mods = Qt::NoModifier; - return true; - } - - // No remaining shortcut -> unregister - auto res = GlobalShortcutPrivate::unregisterShortcut(native); - if (res.ok) - { - SHORTCUTS.erase(shortcut); - } - else - { - qWarning() << "GlobalShortcut failed to unregister:" - << QKeySequence(this->key | this->mods) - << "(native) error:" << res.error; - } - this->key = Qt::Key(0); - this->mods = Qt::NoModifier; - return res.ok; -} - -void GlobalShortcutPrivate::activateShortcut(Native native) -{ - auto it = SHORTCUTS.find(native); - if (it != SHORTCUTS.end()) - { - bool singleConsumer = it->second.size() == 1; - for (size_t i = 0; auto *consumer : it->second) - { - emit consumer->activated(i, singleConsumer); - i++; - } - } -} - -GlobalShortcutPrivate::Native GlobalShortcutPrivate::native() const -{ - return { - .key = GlobalShortcutPrivate::nativeKeycode(this->key), - .modifiers = GlobalShortcutPrivate::nativeModifiers(this->mods), - }; -} - -} // namespace chatterino - -#endif diff --git a/src/controllers/hotkeys/GlobalShortcutPrivate.hpp b/src/controllers/hotkeys/GlobalShortcutPrivate.hpp deleted file mode 100644 index 0ceba6c265a..00000000000 --- a/src/controllers/hotkeys/GlobalShortcutPrivate.hpp +++ /dev/null @@ -1,146 +0,0 @@ -#pragma once - -#include "controllers/hotkeys/GlobalShortcutFwd.hpp" - -#ifndef Q_OS_MAC -# include -#endif - -#include -#include - -#include -#include - -namespace chatterino { - -class GlobalShortcut; - -struct GlobalShortcutResult { - bool ok; - uint32_t error; -}; - -/// @brief Platform specific functionality for global shortcuts -/// -/// This is partially implemented per platform. -/// The following methods must be implemented per platform: -/// -/// - nativeKeycode() -/// - nativeModifiers() -/// - registerShortcut() -/// - unregisterShortcut() -/// - EventFilter::nativeEventFilter() (not on macOS) -/// -/// This class is instantiated for every shortcut (at most once). -class GlobalShortcutPrivate -{ -public: - /// A native representation of a key and its modifiers. - struct Native { - quint32 key; - quint32 modifiers; - - bool operator==(Native rhs) const noexcept - { - return this->key == rhs.key && this->modifiers == rhs.modifiers; - } - }; - - GlobalShortcutPrivate(GlobalShortcut *owner); - ~GlobalShortcutPrivate(); - GlobalShortcutPrivate(const GlobalShortcutPrivate &) = delete; - GlobalShortcutPrivate(GlobalShortcutPrivate &&) = delete; - GlobalShortcutPrivate &operator=(const GlobalShortcutPrivate &) = delete; - GlobalShortcutPrivate &operator=(GlobalShortcutPrivate &&) = delete; - - Qt::Key key; - Qt::KeyboardModifiers mods; - - /// Try to register the shortcut globally - /// @returns true if the shortcut was registered - bool setShortcut(const QKeySequence &shortcut); - /// Try to unregister the shortcut globally - /// @returns true if the shortcut was unregistered - bool unsetShortcut(); - - /// Handles activation of the spcified shortcut. - static void activateShortcut(Native native); - -private: - /// @returns the native keycode of this shortcut - Native native() const; - - /// Convert a Qt key to a native keycode. - /// If no native keycode maps to the specified Qt key, `0` is returned. - /// - /// This is implemented per platform. - static quint32 nativeKeycode(Qt::Key keycode); - /// Convert a Qt modifier to a native modifier-code. - /// If no native code maps to the specified Qt modifier, `0` is returned. - /// - /// This is implemented per platform. - static quint32 nativeModifiers(Qt::KeyboardModifiers modifiers); - - /// Registers the shortcut on the platform. - /// This is only called if this shortcut wasn't registered yet. - /// - /// This is implemented per platform. - static GlobalShortcutResult registerShortcut(Native native); - /// Unregisters the shortcut on the platform. - /// This is only called if this shortcut was registered. - /// - /// This is implemented per platform. - static GlobalShortcutResult unregisterShortcut(Native native); - - // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables) - - /// Global table with all registered shortcuts and all handlers for them. - static std::unordered_map> SHORTCUTS; - -#ifndef Q_OS_MAC - struct EventFilter : public QAbstractNativeEventFilter { - public: -# if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) - using NativeEventResult = qintptr; -# else - using NativeEventResult = long; -# endif - /// Filter for global hotkeys. - /// - /// This is implemented per platform. - /// Implementations call #activateShortcut() when a shortcut is triggered. - /// - /// @sa #activateShortcut() - bool nativeEventFilter(const QByteArray &eventType, void *message, - NativeEventResult *result) override; - }; - - /// Global reference-count of instances of this class. - /// Used to manage the event-filter. - static size_t REFCOUNT; - /// The currently active event filter. - /// There is at most one event filter active. - /// Allocated with `new`. - static EventFilter *CURRENT_FILTER; -#endif - - // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables) - - GlobalShortcut *owner_; -}; - -} // namespace chatterino - -template <> -struct std::hash { - std::size_t operator()( - chatterino::GlobalShortcutPrivate::Native const &n) const noexcept - { - std::size_t seed = 0; - boost::hash_combine(seed, n.key); - boost::hash_combine(seed, n.modifiers); - - return seed; - } -}; diff --git a/src/controllers/hotkeys/HotkeyController.cpp b/src/controllers/hotkeys/HotkeyController.cpp index e8d36f21693..0682d3b3a3c 100644 --- a/src/controllers/hotkeys/HotkeyController.cpp +++ b/src/controllers/hotkeys/HotkeyController.cpp @@ -406,6 +406,9 @@ void HotkeyController::addDefaults(std::set &addedHotkeys) this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, QKeySequence("Ctrl+Alt+N"), "popupOverlay", {}, "open overlay"); + this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, + QKeySequence("Ctrl+Shift+U"), "togglePopupInertia", + {"thisOrAll"}, "toggle overlay click-through"); } // split input diff --git a/src/platform/windows/GlobalShortcutPrivateWin.cpp b/src/platform/windows/GlobalShortcutPrivateWin.cpp deleted file mode 100644 index 5b42db83788..00000000000 --- a/src/platform/windows/GlobalShortcutPrivateWin.cpp +++ /dev/null @@ -1,247 +0,0 @@ -#include "controllers/hotkeys/GlobalShortcutPrivate.hpp" - -#include - -#include - -namespace { - -using namespace chatterino; - -GlobalShortcutResult wrapWinError(BOOL success) -{ - if (success == TRUE) - { - return {.ok = true, .error = 0}; - } - - return {.ok = false, .error = GetLastError()}; -} - -} // namespace - -namespace chatterino { - -bool GlobalShortcutPrivate::EventFilter::nativeEventFilter( - const QByteArray & /*eventType*/, void *message, - NativeEventResult * /*result*/) -{ - MSG *msg = static_cast(message); - if (msg->message == WM_HOTKEY) - { - const quint32 keycode = HIWORD(msg->lParam); - const quint32 modifiers = LOWORD(msg->lParam); - GlobalShortcutPrivate::activateShortcut( - {.key = keycode, .modifiers = modifiers}); - } - - return false; -} - -quint32 GlobalShortcutPrivate::nativeModifiers(Qt::KeyboardModifiers modifiers) -{ - // MOD_ALT, MOD_CONTROL, (MOD_KEYUP), MOD_SHIFT, MOD_WIN - quint32 native = 0; - if (modifiers.testFlag(Qt::ShiftModifier)) - { - native |= MOD_SHIFT; - } - if (modifiers.testFlag(Qt::ControlModifier)) - { - native |= MOD_CONTROL; - } - if (modifiers.testFlag(Qt::AltModifier)) - { - native |= MOD_ALT; - } - if (modifiers.testFlag(Qt::MetaModifier)) - { - native |= MOD_WIN; - } - - Q_ASSERT_X( - !modifiers.testFlag(Qt::KeypadModifier) && - !modifiers.testFlag(Qt::GroupSwitchModifier), - "GlobalShortcutPrivate::nativeModifiers", - "KeypadModifier and GroupSwitchModifier can't be expressed on Windows"); - - return native; -} - -quint32 GlobalShortcutPrivate::nativeKeycode(Qt::Key key) -{ - switch (key) - { - case Qt::Key_Escape: - return VK_ESCAPE; - case Qt::Key_Tab: - case Qt::Key_Backtab: - return VK_TAB; - case Qt::Key_Backspace: - return VK_BACK; - case Qt::Key_Return: - case Qt::Key_Enter: - return VK_RETURN; - case Qt::Key_Insert: - return VK_INSERT; - case Qt::Key_Delete: - return VK_DELETE; - case Qt::Key_Pause: - return VK_PAUSE; - case Qt::Key_Print: - return VK_PRINT; - case Qt::Key_Clear: - return VK_CLEAR; - case Qt::Key_Home: - return VK_HOME; - case Qt::Key_End: - return VK_END; - case Qt::Key_Left: - return VK_LEFT; - case Qt::Key_Up: - return VK_UP; - case Qt::Key_Right: - return VK_RIGHT; - case Qt::Key_Down: - return VK_DOWN; - case Qt::Key_PageUp: - return VK_PRIOR; - case Qt::Key_PageDown: - return VK_NEXT; - case Qt::Key_F1: - return VK_F1; - case Qt::Key_F2: - return VK_F2; - case Qt::Key_F3: - return VK_F3; - case Qt::Key_F4: - return VK_F4; - case Qt::Key_F5: - return VK_F5; - case Qt::Key_F6: - return VK_F6; - case Qt::Key_F7: - return VK_F7; - case Qt::Key_F8: - return VK_F8; - case Qt::Key_F9: - return VK_F9; - case Qt::Key_F10: - return VK_F10; - case Qt::Key_F11: - return VK_F11; - case Qt::Key_F12: - return VK_F12; - case Qt::Key_F13: - return VK_F13; - case Qt::Key_F14: - return VK_F14; - case Qt::Key_F15: - return VK_F15; - case Qt::Key_F16: - return VK_F16; - case Qt::Key_F17: - return VK_F17; - case Qt::Key_F18: - return VK_F18; - case Qt::Key_F19: - return VK_F19; - case Qt::Key_F20: - return VK_F20; - case Qt::Key_F21: - return VK_F21; - case Qt::Key_F22: - return VK_F22; - case Qt::Key_F23: - return VK_F23; - case Qt::Key_F24: - return VK_F24; - case Qt::Key_Space: - return VK_SPACE; - case Qt::Key_Asterisk: - return VK_MULTIPLY; - case Qt::Key_Plus: - return VK_ADD; - case Qt::Key_Comma: - return VK_SEPARATOR; - case Qt::Key_Minus: - return VK_SUBTRACT; - case Qt::Key_Slash: - return VK_DIVIDE; - case Qt::Key_MediaNext: - return VK_MEDIA_NEXT_TRACK; - case Qt::Key_MediaPrevious: - return VK_MEDIA_PREV_TRACK; - case Qt::Key_MediaPlay: - return VK_MEDIA_PLAY_PAUSE; - case Qt::Key_MediaStop: - return VK_MEDIA_STOP; - // couldn't find those in VK_* - //case Qt::Key_MediaLast: - //case Qt::Key_MediaRecord: - case Qt::Key_VolumeDown: - return VK_VOLUME_DOWN; - case Qt::Key_VolumeUp: - return VK_VOLUME_UP; - case Qt::Key_VolumeMute: - return VK_VOLUME_MUTE; - - // numbers - case Qt::Key_0: - case Qt::Key_1: - case Qt::Key_2: - case Qt::Key_3: - case Qt::Key_4: - case Qt::Key_5: - case Qt::Key_6: - case Qt::Key_7: - case Qt::Key_8: - case Qt::Key_9: - // letters - case Qt::Key_A: - case Qt::Key_B: - case Qt::Key_C: - case Qt::Key_D: - case Qt::Key_E: - case Qt::Key_F: - case Qt::Key_G: - case Qt::Key_H: - case Qt::Key_I: - case Qt::Key_J: - case Qt::Key_K: - case Qt::Key_L: - case Qt::Key_M: - case Qt::Key_N: - case Qt::Key_O: - case Qt::Key_P: - case Qt::Key_Q: - case Qt::Key_R: - case Qt::Key_S: - case Qt::Key_T: - case Qt::Key_U: - case Qt::Key_V: - case Qt::Key_W: - case Qt::Key_X: - case Qt::Key_Y: - case Qt::Key_Z: - return key; - - default: - return 0; - } -} - -GlobalShortcutResult GlobalShortcutPrivate::registerShortcut(Native native) -{ - return wrapWinError(RegisterHotKey( - nullptr, std::bit_cast(native.key) ^ native.modifiers, - native.modifiers, native.key)); -} - -GlobalShortcutResult GlobalShortcutPrivate::unregisterShortcut(Native native) -{ - return wrapWinError(UnregisterHotKey( - nullptr, std::bit_cast(native.key) ^ native.modifiers)); -} - -} // namespace chatterino diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 8ff8c619bb6..ed85190fe44 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -8,7 +8,6 @@ #include "controllers/highlights/HighlightBadge.hpp" #include "controllers/highlights/HighlightBlacklistUser.hpp" #include "controllers/highlights/HighlightPhrase.hpp" -#include "controllers/hotkeys/GlobalShortcutFwd.hpp" #include "controllers/ignores/IgnorePhrase.hpp" #include "controllers/logging/ChannelLog.hpp" #include "controllers/moderationactions/ModerationAction.hpp" diff --git a/src/singletons/WindowManager.cpp b/src/singletons/WindowManager.cpp index bf0192e73ac..d2ffec09c68 100644 --- a/src/singletons/WindowManager.cpp +++ b/src/singletons/WindowManager.cpp @@ -16,6 +16,7 @@ #include "widgets/FramelessEmbedWindow.hpp" #include "widgets/helper/NotebookTab.hpp" #include "widgets/Notebook.hpp" +#include "widgets/OverlayWindow.hpp" #include "widgets/splits/Split.hpp" #include "widgets/splits/SplitContainer.hpp" #include "widgets/Window.hpp" @@ -544,6 +545,20 @@ void WindowManager::queueSave() this->saveTimer->start(10s); } +void WindowManager::toggleAllPopupInertia() +{ + for (auto *window : this->windows_) + { + window->getNotebook().forEachSplit([&](auto *split) { + auto *overlay = split->overlayWindow(); + if (overlay) + { + overlay->toggleInertia(); + } + }); + } +} + void WindowManager::encodeTab(SplitContainer *tab, bool isSelected, QJsonObject &obj) { diff --git a/src/singletons/WindowManager.hpp b/src/singletons/WindowManager.hpp index 7f0dcdee6b2..83cc4032615 100644 --- a/src/singletons/WindowManager.hpp +++ b/src/singletons/WindowManager.hpp @@ -128,6 +128,9 @@ class WindowManager final // again void queueSave(); + /// Toggles the inertia in all open popup windows + void toggleAllPopupInertia(); + /// Signals pajlada::Signals::NoArgSignal gifRepaintRequested; diff --git a/src/widgets/Notebook.cpp b/src/widgets/Notebook.cpp index db2bcac4948..2a01020fc8f 100644 --- a/src/widgets/Notebook.cpp +++ b/src/widgets/Notebook.cpp @@ -1633,4 +1633,20 @@ void SplitNotebook::select(QWidget *page, bool focusPage) this->Notebook::select(page, focusPage); } +void SplitNotebook::forEachSplit(const std::function &cb) +{ + for (const auto &item : this->items()) + { + auto *page = dynamic_cast(item.page); + if (!page) + { + continue; + } + for (auto *split : page->getSplits()) + { + cb(split); + } + } +} + } // namespace chatterino diff --git a/src/widgets/Notebook.hpp b/src/widgets/Notebook.hpp index e5cbd17d107..ac6c4dad79f 100644 --- a/src/widgets/Notebook.hpp +++ b/src/widgets/Notebook.hpp @@ -18,6 +18,7 @@ class UpdateDialog; class NotebookButton; class NotebookTab; class SplitContainer; +class Split; enum NotebookTabLocation { Top = 0, Left = 1, Right = 2, Bottom = 3 }; @@ -229,6 +230,8 @@ class SplitNotebook : public Notebook void addNotebookActionsToMenu(QMenu *menu) override; + void forEachSplit(const std::function &cb); + /** * Toggles between the "Show all tabs" and "Hide all tabs" tab visibility states */ diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 057235bb1b6..30274320593 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -1,8 +1,9 @@ #include "widgets/OverlayWindow.hpp" +#include "Application.hpp" #include "common/FlagsEnum.hpp" #include "common/Literals.hpp" -#include "controllers/hotkeys/GlobalShortcut.hpp" +#include "controllers/hotkeys/HotkeyController.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" #include "widgets/BaseWidget.hpp" @@ -56,6 +57,24 @@ void acquireKnowledge(Knowledge knowledge) static_cast>(current.value()); } +QKeySequence toggleIntertiaShortcut() +{ + auto seq = getApp()->getHotkeys()->getDisplaySequence( + HotkeyCategory::Split, u"togglePopupInertia"_s, {{u"this"_s}}); + if (!seq.isEmpty()) + { + return seq; + } + seq = getApp()->getHotkeys()->getDisplaySequence( + HotkeyCategory::Split, u"togglePopupInertia"_s, {{u"thisOrAll"_s}}); + if (!seq.isEmpty()) + { + return seq; + } + return getApp()->getHotkeys()->getDisplaySequence(HotkeyCategory::Split, + u"togglePopupInertia"_s); +} + } // namespace namespace chatterino { @@ -75,6 +94,7 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) this->setAttribute(Qt::WA_DeleteOnClose); this->setWindowTitle(u"Chatterino - Overlay"_s); + // QGridLayout is (ab)used to stack widgets and position them auto *grid = new QGridLayout(this); grid->addWidget(&this->channelView_, 0, 0); #ifndef OVERLAY_NATIVE_MOVE @@ -142,20 +162,7 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) this->applyTheme(); }); -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT - getSettings()->overlayInertShortcut.connect( - [this](const auto &value) { - this->shortcut_ = std::make_unique( - QKeySequence::fromString(value, QKeySequence::PortableText), - this); - QObject::connect(this->shortcut_.get(), &GlobalShortcut::activated, - this, [this] { - this->setInert(!this->inert_); - }); - }, - this->holder_); -#endif - + this->addShortcuts(); this->triggerFirstActivation(); } @@ -237,6 +244,11 @@ void OverlayWindow::setOverrideCursor(const QCursor &cursor) this->setCursor(cursor); } +void OverlayWindow::toggleInertia() +{ + this->setInert(!this->inert_); +} + void OverlayWindow::enterEvent(EnterEvent * /*event*/) { #ifndef OVERLAY_NATIVE_MOVE @@ -401,18 +413,24 @@ void OverlayWindow::triggerFirstActivation() #else "To resize the window, drag on the bottom right corner." #endif - ; + "

" + "By default, the overlay is interactive. "; -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT - auto actualShortcut = - QKeySequence::fromString(getSettings()->overlayInertShortcut, - QKeySequence::PortableText) - .toString(QKeySequence::PortableText); - welcomeText += u"

"_s - "By default, the overlay is interactive. " - "To toggle the click-through mode, press %1 (customizable " - "in the settings).".arg(actualShortcut); -#endif + auto actualShortcut = toggleIntertiaShortcut(); + if (actualShortcut.isEmpty()) + { + welcomeText += + u"To toggle the click-through mode, " + "add a hotkey for \"Toggle overlay click-through\" in the split " + "category to press while any Chatterino window is focused."_s; + } + else + { + welcomeText += + u"To toggle the click-through mode, press %1 (customizable "_s + "in the settings) while any Chatterino window is focused.".arg( + actualShortcut.toString()); + } welcomeText += u"

"_s "This is still an early version and some features are " @@ -426,14 +444,25 @@ void OverlayWindow::triggerFirstActivation() box->open(); } +void OverlayWindow::addShortcuts() +{ + auto seq = toggleIntertiaShortcut(); + if (seq.isEmpty()) + { + return; + } + + auto *shortcut = new QShortcut(seq, this); + QObject::connect(shortcut, &QShortcut::activated, this, + &OverlayWindow::toggleInertia); +} + void OverlayWindow::startInteraction() { -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT if (this->inert_) { return; } -#endif this->interaction_.startInteraction(); this->shortInteraction_.stop(); @@ -441,12 +470,10 @@ void OverlayWindow::startInteraction() void OverlayWindow::startShortInteraction() { -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT if (this->inert_) { return; } -#endif this->interaction_.startInteraction(); this->shortInteraction_.start(); @@ -457,7 +484,6 @@ void OverlayWindow::endInteraction() this->interaction_.endInteraction(); } -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT void OverlayWindow::setInert(bool inert) { if (this->inert_ == inert) @@ -484,6 +510,5 @@ void OverlayWindow::setInert(bool inert) this->interaction_.show(); } } -#endif } // namespace chatterino diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index 117daaa6ed6..6c8907a8d52 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -1,7 +1,6 @@ #pragma once #include "common/Channel.hpp" -#include "controllers/hotkeys/GlobalShortcutFwd.hpp" #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/OverlayInteraction.hpp" @@ -31,6 +30,8 @@ class OverlayWindow : public QWidget void setOverrideCursor(const QCursor &cursor); + void toggleInertia(); + protected: #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) using NativeResult = qintptr; @@ -52,10 +53,14 @@ class OverlayWindow : public QWidget private: void triggerFirstActivation(); + void addShortcuts(); + void startInteraction(); void startShortInteraction(); void endInteraction(); + void setInert(bool inert); + void applyTheme(); #ifdef Q_OS_WIN @@ -70,18 +75,13 @@ class OverlayWindow : public QWidget ChannelView channelView_; QGraphicsDropShadowEffect *dropShadow_; + bool inert_ = false; + bool moving_ = false; QPoint moveOrigin_; OverlayInteraction interaction_; QTimer shortInteraction_; - -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT - void setInert(bool inert); - - std::unique_ptr shortcut_; - bool inert_ = false; -#endif }; } // namespace chatterino diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index bbf58290d70..67d8f448ed8 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -999,10 +999,6 @@ void GeneralPage::initLayout(GeneralPageView &layout) "device-independent pixels. A negative value offsets to " "the top and a positive to the bottom.") ->setSuffix("dp"); -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT - layout.addGlobalShortcut("Clickthrough Hotkey", s.overlayInertShortcut, - "Toggles the inertia of the overlay."); -#endif layout.addSubtitle("Miscellaneous"); diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index d457ba8f700..5ba34a95654 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -768,7 +768,43 @@ void Split::addShortcuts() }}, {"popupOverlay", [this](const auto &) -> QString { - this->popupOverlay(); + this->showOverlayWindow(); + return {}; + }}, + {"togglePopupInertia", + [this](const auto &args) -> QString { + if (args.empty()) + { + return "No arguments provided to togglePopupInertia (expected " + "one)"; + } + const auto &arg = args.front(); + + if (arg == "this") + { + if (this->overlayWindow_) + { + this->overlayWindow_->toggleInertia(); + } + return {}; + } + if (arg == "thisOrAll") + { + if (this->overlayWindow_) + { + this->overlayWindow_->toggleInertia(); + } + else + { + getApp()->getWindows()->toggleAllPopupInertia(); + } + return {}; + } + if (arg == "all") + { + getApp()->getWindows()->toggleAllPopupInertia(); + return {}; + } return {}; }}, }; @@ -1110,9 +1146,18 @@ void Split::popup() window.show(); } -void Split::popupOverlay() +OverlayWindow *Split::overlayWindow() { - (new OverlayWindow(this->getIndirectChannel()))->show(); + return this->overlayWindow_.get(); +} + +void Split::showOverlayWindow() +{ + if (!this->overlayWindow_) + { + this->overlayWindow_ = new OverlayWindow(this->getIndirectChannel()); + } + this->overlayWindow_->show(); } void Split::clear() diff --git a/src/widgets/splits/Split.hpp b/src/widgets/splits/Split.hpp index a050a9aebe1..361363df1ad 100644 --- a/src/widgets/splits/Split.hpp +++ b/src/widgets/splits/Split.hpp @@ -21,6 +21,7 @@ class SplitInput; class SplitContainer; class SplitOverlay; class SelectChannelDialog; +class OverlayWindow; // Each ChatWidget consists of three sub-elements that handle their own part of // the chat widget: ChatWidgetHeader @@ -80,6 +81,8 @@ class Split : public BaseWidget // This is called on window focus lost void unpause(); + OverlayWindow *overlayWindow(); + static pajlada::Signals::Signal modifierStatusChanged; static Qt::KeyboardModifiers modifierStatus; @@ -158,6 +161,8 @@ class Split : public BaseWidget SplitInput *const input_; SplitOverlay *const overlay_; + QPointer overlayWindow_ = nullptr; + QPointer selectChannelDialog_; pajlada::Signals::Connection channelIDChangedConnection_; @@ -179,7 +184,7 @@ public slots: void explainMoving(); void explainSplitting(); void popup(); - void popupOverlay(); + void showOverlayWindow(); void clear(); void openInBrowser(); void openModViewInBrowser(); diff --git a/src/widgets/splits/SplitHeader.cpp b/src/widgets/splits/SplitHeader.cpp index 1658e602c08..b82f817be66 100644 --- a/src/widgets/splits/SplitHeader.cpp +++ b/src/widgets/splits/SplitHeader.cpp @@ -391,7 +391,7 @@ std::unique_ptr SplitHeader::createMainMenu() "Popup", this->split_, &Split::popup, h->getDisplaySequence(HotkeyCategory::Window, "popup", {{"split"}})); menu->addAction( - "Popup overlay", this->split_, &Split::popupOverlay, + "Popup overlay", this->split_, &Split::showOverlayWindow, h->getDisplaySequence(HotkeyCategory::Split, "popupOverlay")); menu->addAction( "Search", this->split_, From db8e18b767a549cde6caf00fcda7223a4b68544a Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 8 Sep 2024 22:21:21 +0200 Subject: [PATCH 49/62] fix: size-grip --- src/widgets/OverlayWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 30274320593..24a552c5231 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -97,11 +97,11 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) // QGridLayout is (ab)used to stack widgets and position them auto *grid = new QGridLayout(this); grid->addWidget(&this->channelView_, 0, 0); + this->interaction_.attach(grid); #ifndef OVERLAY_NATIVE_MOVE grid->addWidget(new InvisibleSizeGrip(this), 0, 0, Qt::AlignBottom | Qt::AlignRight); #endif - this->interaction_.attach(grid); // the interaction overlay currently captures all events this->interaction_.installEventFilter(this); From 0e298a0cc8ee004ed72718dbca32aa047014cecd Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 8 Sep 2024 22:34:44 +0200 Subject: [PATCH 50/62] fix: random stuff i noticed --- docs/ChatterinoTheme.schema.json | 6 -- src/singletons/Settings.hpp | 4 - src/widgets/Window.cpp | 3 +- src/widgets/helper/TitlebarButtons.cpp | 74 ++++--------------- src/widgets/helper/TitlebarButtons.hpp | 3 - src/widgets/settingspages/GeneralPageView.cpp | 70 ------------------ src/widgets/settingspages/GeneralPageView.hpp | 3 - 7 files changed, 16 insertions(+), 147 deletions(-) diff --git a/docs/ChatterinoTheme.schema.json b/docs/ChatterinoTheme.schema.json index 0a6a0344752..130fac6af5d 100644 --- a/docs/ChatterinoTheme.schema.json +++ b/docs/ChatterinoTheme.schema.json @@ -256,12 +256,6 @@ "selection", "textColors" ] - }, - "qpointf": { - "type": "array", - "items": { "type": "number" }, - "minItems": 2, - "maxItems": 2 } }, "type": "object", diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index ed85190fe44..c536122b53f 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -192,10 +192,6 @@ class Settings IntSetting overlayShadowOffsetX = {"/appearance/overlay/shadowOffsetX", 2}; IntSetting overlayShadowOffsetY = {"/appearance/overlay/shadowOffsetY", 2}; IntSetting overlayShadowRadius = {"/appearance/overlay/shadowRadius", 8}; -#ifdef CHATTERINO_HAS_GLOBAL_SHORTCUT - QStringSetting overlayInertShortcut = {"/behaviour/overlay/inert", - "Ctrl+Alt+Shift+U"}; -#endif // Badges BoolSetting showBadgesGlobalAuthority = { diff --git a/src/widgets/Window.cpp b/src/widgets/Window.cpp index 47276cb4549..d91ca41dfdc 100644 --- a/src/widgets/Window.cpp +++ b/src/widgets/Window.cpp @@ -416,8 +416,7 @@ void Window::addShortcuts() } else { - return "Invalid popup target. Use \"split\" or " - "\"window\"."; + return "Invalid popup target. Use \"split\" or \"window\"."; } return ""; }}, diff --git a/src/widgets/helper/TitlebarButtons.cpp b/src/widgets/helper/TitlebarButtons.cpp index 4906094803f..42a1c93aa07 100644 --- a/src/widgets/helper/TitlebarButtons.cpp +++ b/src/widgets/helper/TitlebarButtons.cpp @@ -47,65 +47,33 @@ void TitleBarButtons::hover(size_t ht, QPoint at) assert(false && "TitleBarButtons::hover precondition violated"); return; } - - if (hovered) - { - hovered->ncEnter(); - hovered->ncMove(hovered->mapFromGlobal(at)); - } - - if (other1) - { - other1->ncLeave(); - } - - if (other2) - { - other2->ncLeave(); - } + hovered->ncEnter(); + hovered->ncMove(hovered->mapFromGlobal(at)); + other1->ncLeave(); + other2->ncLeave(); } void TitleBarButtons::leave() { - if (this->minButton_) - { - this->minButton_->ncLeave(); - } - if (this->maxButton_) - { - this->maxButton_->ncLeave(); - } - if (this->closeButton_) - { - this->closeButton_->ncLeave(); - } + this->minButton_->ncLeave(); + this->maxButton_->ncLeave(); + this->closeButton_->ncLeave(); } void TitleBarButtons::mousePress(size_t ht, QPoint at) { auto *button = this->buttonForHt(ht); - if (button) - { - button->ncMousePress(button->mapFromGlobal(at)); - } + button->ncMousePress(button->mapFromGlobal(at)); } void TitleBarButtons::mouseRelease(size_t ht, QPoint at) { auto *button = this->buttonForHt(ht); - if (button) - { - button->ncMouseRelease(button->mapFromGlobal(at)); - } + button->ncMouseRelease(button->mapFromGlobal(at)); } void TitleBarButtons::updateMaxButton() { - if (!this->maxButton_) - { - return; - } - this->maxButton_->setButtonStyle( this->window_->windowState().testFlag(Qt::WindowMaximized) ? TitleBarButtonStyle::Unmaximize @@ -114,28 +82,16 @@ void TitleBarButtons::updateMaxButton() void TitleBarButtons::setSmallSize() { - this->setSize(30, 30); -} - -void TitleBarButtons::setSize(int width, int height) -{ - if (this->minButton_) - { - this->minButton_->setScaleIndependantSize(width, height); - } - if (this->maxButton_) - { - this->maxButton_->setScaleIndependantSize(width, height); - } - if (this->closeButton_) - { - this->closeButton_->setScaleIndependantSize(width, height); - } + this->minButton_->setScaleIndependantSize(30, 30); + this->maxButton_->setScaleIndependantSize(30, 30); + this->closeButton_->setScaleIndependantSize(30, 30); } void TitleBarButtons::setRegularSize() { - this->setSize(46, 30); + this->minButton_->setScaleIndependantSize(46, 30); + this->maxButton_->setScaleIndependantSize(46, 30); + this->closeButton_->setScaleIndependantSize(46, 30); } TitleBarButton *TitleBarButtons::buttonForHt(size_t ht) const diff --git a/src/widgets/helper/TitlebarButtons.hpp b/src/widgets/helper/TitlebarButtons.hpp index d92d16f076d..e7ee3eb5bf5 100644 --- a/src/widgets/helper/TitlebarButtons.hpp +++ b/src/widgets/helper/TitlebarButtons.hpp @@ -59,9 +59,6 @@ class TitleBarButtons : QObject /// @pre ht must be one of { HTMAXBUTTON, HTMINBUTTON, HTCLOSE }. TitleBarButton *buttonForHt(size_t ht) const; - /// Sets the size for all buttons - void setSize(int width, int height); - QWidget *window_ = nullptr; TitleBarButton *minButton_ = nullptr; TitleBarButton *maxButton_ = nullptr; diff --git a/src/widgets/settingspages/GeneralPageView.cpp b/src/widgets/settingspages/GeneralPageView.cpp index 93ee4e4ae6b..2939925814d 100644 --- a/src/widgets/settingspages/GeneralPageView.cpp +++ b/src/widgets/settingspages/GeneralPageView.cpp @@ -1,25 +1,16 @@ #include "widgets/settingspages/GeneralPageView.hpp" #include "Application.hpp" -#include "common/Literals.hpp" #include "util/LayoutHelper.hpp" #include "util/RapidJsonSerializeQString.hpp" #include "widgets/dialogs/ColorPickerDialog.hpp" #include "widgets/helper/color/ColorButton.hpp" #include "widgets/helper/Line.hpp" -#include -#include -#include -#include -#include -#include #include #include #include -#include - namespace { constexpr int MAX_TOOLTIP_LINE_LENGTH = 50; @@ -31,8 +22,6 @@ const QRegularExpression MAX_TOOLTIP_LINE_LENGTH_REGEX( namespace chatterino { -using namespace literals; - GeneralPageView::GeneralPageView(QWidget *parent) : QWidget(parent) { @@ -290,65 +279,6 @@ QSpinBox *GeneralPageView::addIntInput(const QString &text, IntSetting &setting, return input; } -QPushButton *GeneralPageView::addGlobalShortcut( - const QString &text, pajlada::Settings::Setting &setting, - QString toolTipText) -{ - auto *button = new QPushButton(setting); - auto *layout = new QHBoxLayout(); - auto *label = new QLabel(text + ':'); - - layout->addWidget(label); - layout->addStretch(1); - layout->addWidget(button); - - this->addToolTip(*label, std::move(toolTipText)); - this->addLayout(layout); - - QObject::connect(button, &QPushButton::clicked, [this, &setting, button] { - auto *dialog = new QDialog(this); - auto *layout = new QVBoxLayout(dialog); - auto *edit = new QKeySequenceEdit( - QKeySequence::fromString(setting, QKeySequence::PortableText)); - layout->addWidget(edit, 1); - - auto *btn = new QDialogButtonBox(QDialogButtonBox::Cancel | - QDialogButtonBox::Ok); - layout->addWidget(btn, 0, Qt::AlignRight | Qt::AlignBottom); - QObject::connect(btn, &QDialogButtonBox::accepted, dialog, - &QDialog::accept); - QObject::connect(btn, &QDialogButtonBox::rejected, dialog, - &QDialog::reject); - - dialog->resize(300, 200); - dialog->setAttribute(Qt::WA_DeleteOnClose); - dialog->setWindowTitle("Edit Hotkey"); - dialog->show(); - - QObject::connect( - dialog, &QDialog::accepted, [this, &setting, edit, button] { - if (edit->keySequence().isEmpty() || - edit->keySequence().count() > 1) - { - QMessageBox::warning( - this, u"Key Editor"_s, - u"There must be exactly one key pressed (with optional modifiers)."_s, - QMessageBox::Ok); - return; - } - auto text = - edit->keySequence().toString(QKeySequence::PortableText); - setting.setValue(text); - button->setText(text); - }); - }); - - this->groups_.back().widgets.push_back({label, {text}}); - this->groups_.back().widgets.push_back({button, {text}}); - - return button; -} - void GeneralPageView::addNavigationSpacing() { this->navigationLayout_->addSpacing(24); diff --git a/src/widgets/settingspages/GeneralPageView.hpp b/src/widgets/settingspages/GeneralPageView.hpp index b6f2544caaa..4ce1c5b4e54 100644 --- a/src/widgets/settingspages/GeneralPageView.hpp +++ b/src/widgets/settingspages/GeneralPageView.hpp @@ -119,9 +119,6 @@ class GeneralPageView : public QWidget QString toolTipText = {}); QSpinBox *addIntInput(const QString &text, IntSetting &setting, int min, int max, int step, QString toolTipText = {}); - QPushButton *addGlobalShortcut(const QString &text, - pajlada::Settings::Setting &setting, - QString toolTipText = {}); void addNavigationSpacing(); template From f2e5a56ea3e305a5507cf54cdfbfd8813f0f9e78 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 8 Sep 2024 22:53:09 +0200 Subject: [PATCH 51/62] fix: qt5 --- src/widgets/splits/Split.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/splits/Split.hpp b/src/widgets/splits/Split.hpp index 361363df1ad..6d604bdf8d5 100644 --- a/src/widgets/splits/Split.hpp +++ b/src/widgets/splits/Split.hpp @@ -161,7 +161,7 @@ class Split : public BaseWidget SplitInput *const input_; SplitOverlay *const overlay_; - QPointer overlayWindow_ = nullptr; + QPointer overlayWindow_; QPointer selectChannelDialog_; From 682a87af75e6f13c024b56ef8dea2bb897c29cf7 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 8 Sep 2024 23:10:35 +0200 Subject: [PATCH 52/62] fix: qt5 again --- src/widgets/splits/Split.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index 5ba34a95654..77397b10e2d 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -1148,7 +1148,7 @@ void Split::popup() OverlayWindow *Split::overlayWindow() { - return this->overlayWindow_.get(); + return this->overlayWindow_.data(); } void Split::showOverlayWindow() From 2422c69c6de95d05871983cf90fabc44ef2ac323 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 29 Sep 2024 18:48:52 +0200 Subject: [PATCH 53/62] fix: all by default and all-or-nothing shortcut --- src/controllers/hotkeys/HotkeyController.cpp | 2 +- src/singletons/WindowManager.cpp | 21 ++++++++- src/singletons/WindowManager.hpp | 4 +- src/widgets/OverlayWindow.cpp | 45 +++++++++++++++----- src/widgets/OverlayWindow.hpp | 4 +- src/widgets/splits/Split.cpp | 4 +- 6 files changed, 60 insertions(+), 20 deletions(-) diff --git a/src/controllers/hotkeys/HotkeyController.cpp b/src/controllers/hotkeys/HotkeyController.cpp index 0682d3b3a3c..c4717235537 100644 --- a/src/controllers/hotkeys/HotkeyController.cpp +++ b/src/controllers/hotkeys/HotkeyController.cpp @@ -408,7 +408,7 @@ void HotkeyController::addDefaults(std::set &addedHotkeys) "open overlay"); this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, QKeySequence("Ctrl+Shift+U"), "togglePopupInertia", - {"thisOrAll"}, "toggle overlay click-through"); + {"all"}, "toggle overlay click-through"); } // split input diff --git a/src/singletons/WindowManager.cpp b/src/singletons/WindowManager.cpp index d2ffec09c68..f64eb47e77c 100644 --- a/src/singletons/WindowManager.cpp +++ b/src/singletons/WindowManager.cpp @@ -545,15 +545,32 @@ void WindowManager::queueSave() this->saveTimer->start(10s); } -void WindowManager::toggleAllPopupInertia() +void WindowManager::toggleAllOverlayInertia() { + // check if any window is not inert + bool anyNonInert = false; + for (auto *window : this->windows_) + { + if (anyNonInert) + { + break; + } + window->getNotebook().forEachSplit([&](auto *split) { + auto *overlay = split->overlayWindow(); + if (overlay) + { + anyNonInert = anyNonInert || !overlay->isInert(); + } + }); + } + for (auto *window : this->windows_) { window->getNotebook().forEachSplit([&](auto *split) { auto *overlay = split->overlayWindow(); if (overlay) { - overlay->toggleInertia(); + overlay->setInert(anyNonInert); } }); } diff --git a/src/singletons/WindowManager.hpp b/src/singletons/WindowManager.hpp index 83cc4032615..8e737147d27 100644 --- a/src/singletons/WindowManager.hpp +++ b/src/singletons/WindowManager.hpp @@ -128,8 +128,8 @@ class WindowManager final // again void queueSave(); - /// Toggles the inertia in all open popup windows - void toggleAllPopupInertia(); + /// Toggles the inertia in all open overlay windows + void toggleAllOverlayInertia(); /// Signals pajlada::Signals::NoArgSignal gifRepaintRequested; diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 24a552c5231..743c76a30c2 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -6,6 +6,7 @@ #include "controllers/hotkeys/HotkeyController.hpp" #include "singletons/Settings.hpp" #include "singletons/Theme.hpp" +#include "singletons/WindowManager.hpp" #include "widgets/BaseWidget.hpp" #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/InvisibleSizeGrip.hpp" @@ -19,6 +20,7 @@ #include #include + #ifdef Q_OS_WIN # include # include @@ -35,7 +37,7 @@ using namespace chatterino; using namespace literals; /// Progress the user has made in exploring the overlay -enum class Knowledge : std::int32_t { +enum class Knowledge : std::int32_t { // NOLINT(performance-enum-size) None = 0, // User opened the overlay at least once Activation = 1 << 0, @@ -57,22 +59,26 @@ void acquireKnowledge(Knowledge knowledge) static_cast>(current.value()); } -QKeySequence toggleIntertiaShortcut() +/// Returns [seq?, toggleAllOverlays] +std::pair toggleIntertiaShortcut() { auto seq = getApp()->getHotkeys()->getDisplaySequence( HotkeyCategory::Split, u"togglePopupInertia"_s, {{u"this"_s}}); if (!seq.isEmpty()) { - return seq; + return {seq, false}; } seq = getApp()->getHotkeys()->getDisplaySequence( HotkeyCategory::Split, u"togglePopupInertia"_s, {{u"thisOrAll"_s}}); if (!seq.isEmpty()) { - return seq; + return {seq, false}; } - return getApp()->getHotkeys()->getDisplaySequence(HotkeyCategory::Split, - u"togglePopupInertia"_s); + return { + getApp()->getHotkeys()->getDisplaySequence(HotkeyCategory::Split, + u"togglePopupInertia"_s), + true, + }; } } // namespace @@ -244,6 +250,11 @@ void OverlayWindow::setOverrideCursor(const QCursor &cursor) this->setCursor(cursor); } +bool OverlayWindow::isInert() const +{ + return this->inert_; +} + void OverlayWindow::toggleInertia() { this->setInert(!this->inert_); @@ -416,7 +427,7 @@ void OverlayWindow::triggerFirstActivation() "

" "By default, the overlay is interactive. "; - auto actualShortcut = toggleIntertiaShortcut(); + auto [actualShortcut, allOverlays] = toggleIntertiaShortcut(); if (actualShortcut.isEmpty()) { welcomeText += @@ -446,15 +457,24 @@ void OverlayWindow::triggerFirstActivation() void OverlayWindow::addShortcuts() { - auto seq = toggleIntertiaShortcut(); + auto [seq, allOverlays] = toggleIntertiaShortcut(); if (seq.isEmpty()) { return; } auto *shortcut = new QShortcut(seq, this); - QObject::connect(shortcut, &QShortcut::activated, this, - &OverlayWindow::toggleInertia); + if (allOverlays) + { + QObject::connect(shortcut, &QShortcut::activated, this, [] { + getApp()->getWindows()->toggleAllOverlayInertia(); + }); + } + else + { + QObject::connect(shortcut, &QShortcut::activated, this, + &OverlayWindow::toggleInertia); + } } void OverlayWindow::startInteraction() @@ -502,7 +522,10 @@ void OverlayWindow::setInert(bool inert) if (inert) { - this->channelView_.scrollbar()->scrollToBottom(); + if (this->channelView_.scrollbar()->isVisible()) + { + this->channelView_.scrollbar()->scrollToBottom(); + } this->interaction_.hide(); } else diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index 6c8907a8d52..322dae468ab 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -30,6 +30,8 @@ class OverlayWindow : public QWidget void setOverrideCursor(const QCursor &cursor); + bool isInert() const; + void setInert(bool inert); void toggleInertia(); protected: @@ -59,8 +61,6 @@ class OverlayWindow : public QWidget void startShortInteraction(); void endInteraction(); - void setInert(bool inert); - void applyTheme(); #ifdef Q_OS_WIN diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index 77397b10e2d..a28cd4ac51b 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -796,13 +796,13 @@ void Split::addShortcuts() } else { - getApp()->getWindows()->toggleAllPopupInertia(); + getApp()->getWindows()->toggleAllOverlayInertia(); } return {}; } if (arg == "all") { - getApp()->getWindows()->toggleAllPopupInertia(); + getApp()->getWindows()->toggleAllOverlayInertia(); return {}; } return {}; From eb4c8460e159d5bda97d33ec8cd0482d8d46cce6 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 29 Sep 2024 19:47:31 +0200 Subject: [PATCH 54/62] refactor: no more visitor --- src/messages/layouts/MessageLayoutContext.cpp | 47 +++++++++---------- src/messages/layouts/MessageLayoutContext.hpp | 3 +- src/widgets/OverlayWindow.cpp | 5 +- src/widgets/helper/ChannelView.cpp | 19 +++----- src/widgets/helper/ChannelView.hpp | 12 +++-- src/widgets/helper/MessageView.cpp | 2 +- 6 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/messages/layouts/MessageLayoutContext.cpp b/src/messages/layouts/MessageLayoutContext.cpp index db587dc16e6..f449b19f85e 100644 --- a/src/messages/layouts/MessageLayoutContext.cpp +++ b/src/messages/layouts/MessageLayoutContext.cpp @@ -7,16 +7,29 @@ namespace chatterino { -void MessageColors::applyTheme(Theme *theme) +void MessageColors::applyTheme(Theme *theme, bool isOverlay, + int backgroundOpacity) { - this->channelBackground = theme->splits.background; - - this->regular = theme->messages.backgrounds.regular; - this->alternate = theme->messages.backgrounds.alternate; - - this->disabled = theme->messages.disabled; - this->selection = theme->messages.selection; - this->system = theme->messages.textColors.system; + auto applyColors = [this](const auto &src) { + this->regular = src.backgrounds.regular; + this->alternate = src.backgrounds.alternate; + + this->disabled = src.disabled; + this->selection = src.selection; + this->system = src.textColors.system; + }; + + if (isOverlay) + { + this->channelBackground = theme->overlayMessages.background; + this->channelBackground.setAlpha(std::clamp(backgroundOpacity, 0, 255)); + applyColors(theme->overlayMessages); + } + else + { + this->channelBackground = theme->splits.background; + applyColors(theme->messages); + } this->messageSeperator = theme->splits.messageSeperator; @@ -27,22 +40,6 @@ void MessageColors::applyTheme(Theme *theme) this->regular.alpha() != 255 || this->alternate.alpha() != 255; } -void MessageColors::applyOverlay(Theme *theme, int backgroundOpacity) -{ - this->channelBackground = theme->overlayMessages.background; - this->channelBackground.setAlpha(std::clamp(backgroundOpacity, 0, 255)); - - this->regular = theme->overlayMessages.backgrounds.regular; - this->alternate = theme->overlayMessages.backgrounds.alternate; - - this->disabled = theme->overlayMessages.disabled; - this->selection = theme->overlayMessages.selection; - this->system = theme->overlayMessages.textColors.system; - - this->hasTransparency = - this->regular.alpha() != 255 || this->alternate.alpha() != 255; -} - void MessagePreferences::connectSettings(Settings *settings, pajlada::Signals::SignalHolder &holder) { diff --git a/src/messages/layouts/MessageLayoutContext.hpp b/src/messages/layouts/MessageLayoutContext.hpp index 5c0e45b7c77..c4bc520bb08 100644 --- a/src/messages/layouts/MessageLayoutContext.hpp +++ b/src/messages/layouts/MessageLayoutContext.hpp @@ -32,8 +32,7 @@ struct MessageColors { QColor focusedLastMessageLine; QColor unfocusedLastMessageLine; - void applyTheme(Theme *theme); - void applyOverlay(Theme *theme, int backgroundOpacity); + void applyTheme(Theme *theme, bool isOverlay, int backgroundOpacity); }; // TODO: Explore if we can let settings own this diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 743c76a30c2..9d98cbd6938 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -20,7 +20,6 @@ #include #include - #ifdef Q_OS_WIN # include # include @@ -119,9 +118,7 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) this->channelView_.installEventFilter(this); this->channelView_.setChannel(this->channel_.get()); - this->channelView_.setColorVisitor([](MessageColors &colors, Theme *theme) { - colors.applyOverlay(theme, getSettings()->overlayBackgroundOpacity); - }); + this->channelView_.setIsOverlay(true); // use overlay colors this->channelView_.setAttribute(Qt::WA_TranslucentBackground); this->holder_.managedConnect(this->channel_.getChannelChanged(), [this]() { this->channelView_.setChannel(this->channel_.get()); diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 39af0758035..a4194b5bd4e 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -388,7 +388,8 @@ ChannelView::ChannelView(InternalCtor /*tag*/, QWidget *parent, Split *split, this->queueUpdate(); }); - this->messageColors_.applyTheme(getTheme()); + this->messageColors_.applyTheme(getTheme(), this->isOverlay_, + getSettings()->overlayBackgroundOpacity); this->messagePreferences_.connectSettings(getSettings(), this->signalHolder_); } @@ -605,11 +606,8 @@ void ChannelView::themeChangedEvent() this->setupHighlightAnimationColors(); this->queueLayout(); - this->messageColors_.applyTheme(getTheme()); - if (this->colorVisitor_) - { - this->colorVisitor_(this->messageColors_, getTheme()); - } + this->messageColors_.applyTheme(getTheme(), this->isOverlay_, + getSettings()->overlayBackgroundOpacity); } void ChannelView::updateColorTheme() @@ -617,13 +615,10 @@ void ChannelView::updateColorTheme() this->themeChangedEvent(); } -void ChannelView::setColorVisitor( - const std::function &visitor) +void ChannelView::setIsOverlay(bool isOverlay) { - assert(this->colorVisitor_ == nullptr && - "The color visitor should only be set once."); - this->colorVisitor_ = visitor; - this->updateColorTheme(); + this->isOverlay_ = isOverlay; + this->themeChangedEvent(); } void ChannelView::setupHighlightAnimationColors() diff --git a/src/widgets/helper/ChannelView.hpp b/src/widgets/helper/ChannelView.hpp index 355f10114b4..7042107125a 100644 --- a/src/widgets/helper/ChannelView.hpp +++ b/src/widgets/helper/ChannelView.hpp @@ -203,8 +203,12 @@ class ChannelView final : public BaseWidget bool mayContainMessage(const MessagePtr &message); void updateColorTheme(); - void setColorVisitor( - const std::function &visitor); + + /// @brief Adjusts the colors this view uses + /// + /// If @a isOverlay is true, the overlay colors (as specified in the theme) + /// will be used. Otherwise, regular message-colors will be used. + void setIsOverlay(bool isOverlay); Scrollbar *scrollbar(); @@ -383,6 +387,8 @@ class ChannelView final : public BaseWidget bool onlyUpdateEmotes_ = false; + bool isOverlay_ = false; + // Mouse event variables bool isLeftMouseDown_ = false; bool isRightMouseDown_ = false; @@ -426,8 +432,6 @@ class ChannelView final : public BaseWidget MessageColors messageColors_; MessagePreferences messagePreferences_; - std::function colorVisitor_; - void scrollUpdateRequested(); TooltipWidget *const tooltipWidget_{}; diff --git a/src/widgets/helper/MessageView.cpp b/src/widgets/helper/MessageView.cpp index 6383ec5a6e5..a26e0d00596 100644 --- a/src/widgets/helper/MessageView.cpp +++ b/src/widgets/helper/MessageView.cpp @@ -97,7 +97,7 @@ void MessageView::paintEvent(QPaintEvent * /*event*/) void MessageView::themeChangedEvent() { - this->messageColors_.applyTheme(getTheme()); + this->messageColors_.applyTheme(getTheme(), false, 255); this->messageColors_.regular = getTheme()->splits.input.background; if (this->messageLayout_) { From c829dcb4f347bdbf2e156e093d85cce5a28c7e61 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 29 Sep 2024 20:02:52 +0200 Subject: [PATCH 55/62] refactor: specify shadow color in settings --- docs/ChatterinoTheme.schema.json | 14 +------------- resources/themes/Black.json | 5 +---- resources/themes/Dark.json | 5 +---- resources/themes/Light.json | 5 +---- resources/themes/White.json | 5 +---- src/singletons/Settings.hpp | 2 ++ src/singletons/Theme.cpp | 7 ------- src/singletons/Theme.hpp | 4 ---- src/widgets/OverlayWindow.cpp | 7 ++----- src/widgets/settingspages/GeneralPage.cpp | 3 +++ 10 files changed, 12 insertions(+), 45 deletions(-) diff --git a/docs/ChatterinoTheme.schema.json b/docs/ChatterinoTheme.schema.json index 130fac6af5d..7e23e73a2a3 100644 --- a/docs/ChatterinoTheme.schema.json +++ b/docs/ChatterinoTheme.schema.json @@ -297,17 +297,6 @@ "background": { "$ref": "#/definitions/qt-color", "description": "Note: The alpha value is ignored (set through the settings)" - }, - "shadow": { - "type": "object", - "additionalProperties": false, - "properties": { - "color": { - "$ref": "#/definitions/qt-color", - "description": "Note: The alpha value is ignored (set through the settings)" - } - }, - "required": ["color"] } }, "required": [ @@ -315,8 +304,7 @@ "disabled", "selection", "textColors", - "background", - "shadow" + "background" ] }, "scrollbars": { diff --git a/resources/themes/Black.json b/resources/themes/Black.json index ed7a57b4c7b..c194aa793ad 100644 --- a/resources/themes/Black.json +++ b/resources/themes/Black.json @@ -36,10 +36,7 @@ "regular": "#ffffff", "system": "#8c7f7f" }, - "background": "#000", - "shadow": { - "color": "#000" - } + "background": "#000" }, "scrollbars": { "background": "#00000000", diff --git a/resources/themes/Dark.json b/resources/themes/Dark.json index cf0d9b781fb..2b85545d766 100644 --- a/resources/themes/Dark.json +++ b/resources/themes/Dark.json @@ -36,10 +36,7 @@ "regular": "#ffffff", "system": "#8c7f7f" }, - "background": "#000", - "shadow": { - "color": "#000" - } + "background": "#000" }, "scrollbars": { "background": "#00000000", diff --git a/resources/themes/Light.json b/resources/themes/Light.json index a80d747e4b9..d9a4c2c28a7 100644 --- a/resources/themes/Light.json +++ b/resources/themes/Light.json @@ -36,10 +36,7 @@ "regular": "#000000", "system": "#8c7f7f" }, - "background": "#fff", - "shadow": { - "color": "#000" - } + "background": "#fff" }, "scrollbars": { "background": "#00000000", diff --git a/resources/themes/White.json b/resources/themes/White.json index bc539d0bdbd..46953ba9580 100644 --- a/resources/themes/White.json +++ b/resources/themes/White.json @@ -36,10 +36,7 @@ "regular": "#000000", "system": "#8c7f7f" }, - "background": "#fff", - "shadow": { - "color": "#000" - } + "background": "#fff" }, "scrollbars": { "background": "#00000000", diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index c536122b53f..3566815e87e 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -188,6 +188,8 @@ class Settings BoolSetting enableOverlayShadow = {"/appearance/overlay/shadow", true}; IntSetting overlayShadowOpacity = {"/appearance/overlay/shadowOpacity", 255}; + QStringSetting overlayShadowColor = {"/appearance/overlay/shadowColor", + "#000"}; // These should be floats, but there's no good input UI for them IntSetting overlayShadowOffsetX = {"/appearance/overlay/shadowOffsetX", 2}; IntSetting overlayShadowOffsetY = {"/appearance/overlay/shadowOffsetY", 2}; diff --git a/src/singletons/Theme.cpp b/src/singletons/Theme.cpp index 8e1bf38dceb..0c29d429ca2 100644 --- a/src/singletons/Theme.cpp +++ b/src/singletons/Theme.cpp @@ -167,13 +167,6 @@ void parseOverlayMessages(const QJsonObject &overlayMessages, parseColor(theme, overlayMessages, disabled); parseColor(theme, overlayMessages, selection); parseColor(theme, overlayMessages, background); - - { - const auto shadow = overlayMessages["shadow"_L1].toObject(); - const auto shadowFallback = - overlayMessagesFallback["shadow"_L1].toObject(); - parseColor(theme.overlayMessages, shadow, color); - } } void parseScrollbars(const QJsonObject &scrollbars, diff --git a/src/singletons/Theme.hpp b/src/singletons/Theme.hpp index 1e9cd3c3c1d..ac9fa070c1d 100644 --- a/src/singletons/Theme.hpp +++ b/src/singletons/Theme.hpp @@ -114,10 +114,6 @@ class Theme final QColor disabled; QColor selection; QColor background; - - struct { - QColor color; - } shadow; } overlayMessages; /// SCROLLBAR diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 9d98cbd6938..1499c73827c 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -160,10 +160,7 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) settings->overlayShadowOffsetY.connect(applyIt, this->holder_, false); settings->overlayShadowOpacity.connect(applyIt, this->holder_, false); settings->overlayShadowRadius.connect(applyIt, this->holder_, false); - - this->holder_.managedConnect(getTheme()->updated, [this] { - this->applyTheme(); - }); + settings->overlayShadowColor.connect(applyIt, this->holder_, false); this->addShortcuts(); this->triggerFirstActivation(); @@ -183,7 +180,7 @@ void OverlayWindow::applyTheme() if (this->dropShadow_) { - auto shadowColor = theme->overlayMessages.shadow.color; + QColor shadowColor(settings->overlayShadowColor.getValue()); shadowColor.setAlpha( std::clamp(settings->overlayShadowOpacity.getValue(), 0, 255)); this->dropShadow_->setColor(shadowColor); diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index 67d8f448ed8..7103fb52739 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -982,6 +982,9 @@ void GeneralPage::initLayout(GeneralPageView &layout) 1, "Controls the opacity of the added drop shadow. 255 " "corresponds to a fully opaque shadow."); + layout.addColorButton("Shadow color", + QColor(getSettings()->overlayShadowColor.getValue()), + getSettings()->overlayShadowColor); layout .addIntInput("Shadow radius", s.overlayShadowRadius, 0, 40, 1, "Controls how far the shadow is spread (the blur " From c27127244ea617fb5a5e96a0c5c15dd19b89a704 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Mon, 30 Sep 2024 11:48:10 +0200 Subject: [PATCH 56/62] chore: remove unused overlay theme keys These weren't used, but I'd like to have something like this in the future. Not eaxactly sure what the api should be (settings/theme) --- docs/ChatterinoTheme.schema.json | 9 +-------- resources/themes/Black.json | 7 ------- resources/themes/Dark.json | 7 ------- resources/themes/Light.json | 7 ------- resources/themes/White.json | 7 ------- src/singletons/Theme.cpp | 3 --- src/singletons/Theme.hpp | 1 - 7 files changed, 1 insertion(+), 40 deletions(-) diff --git a/docs/ChatterinoTheme.schema.json b/docs/ChatterinoTheme.schema.json index 7e23e73a2a3..e00332392a4 100644 --- a/docs/ChatterinoTheme.schema.json +++ b/docs/ChatterinoTheme.schema.json @@ -293,19 +293,12 @@ "backgrounds": { "$ref": "#/definitions/message-backgrounds" }, "disabled": { "$ref": "#/definitions/qt-color" }, "selection": { "$ref": "#/definitions/qt-color" }, - "textColors": { "$ref": "#/definitions/text-colors" }, "background": { "$ref": "#/definitions/qt-color", "description": "Note: The alpha value is ignored (set through the settings)" } }, - "required": [ - "backgrounds", - "disabled", - "selection", - "textColors", - "background" - ] + "required": ["backgrounds", "disabled", "selection", "background"] }, "scrollbars": { "type": "object", diff --git a/resources/themes/Black.json b/resources/themes/Black.json index c194aa793ad..6263a54927c 100644 --- a/resources/themes/Black.json +++ b/resources/themes/Black.json @@ -29,13 +29,6 @@ }, "disabled": "#64000000", "selection": "#40ffffff", - "textColors": { - "caret": "#ffffff", - "chatPlaceholder": "#5d5555", - "link": "#4286f4", - "regular": "#ffffff", - "system": "#8c7f7f" - }, "background": "#000" }, "scrollbars": { diff --git a/resources/themes/Dark.json b/resources/themes/Dark.json index 2b85545d766..875fb2754b0 100644 --- a/resources/themes/Dark.json +++ b/resources/themes/Dark.json @@ -29,13 +29,6 @@ }, "disabled": "#64000000", "selection": "#40ffffff", - "textColors": { - "caret": "#ffffff", - "chatPlaceholder": "#5d5555", - "link": "#4286f4", - "regular": "#ffffff", - "system": "#8c7f7f" - }, "background": "#000" }, "scrollbars": { diff --git a/resources/themes/Light.json b/resources/themes/Light.json index d9a4c2c28a7..0a40b1b0842 100644 --- a/resources/themes/Light.json +++ b/resources/themes/Light.json @@ -29,13 +29,6 @@ }, "disabled": "#64ffffff", "selection": "#40ffffff", - "textColors": { - "caret": "#000000", - "chatPlaceholder": "#af9f9f", - "link": "#4286f4", - "regular": "#000000", - "system": "#8c7f7f" - }, "background": "#fff" }, "scrollbars": { diff --git a/resources/themes/White.json b/resources/themes/White.json index 46953ba9580..0151041fa12 100644 --- a/resources/themes/White.json +++ b/resources/themes/White.json @@ -29,13 +29,6 @@ }, "disabled": "#64ffffff", "selection": "#40ffffff", - "textColors": { - "caret": "#000000", - "chatPlaceholder": "#af9f9f", - "link": "#4286f4", - "regular": "#000000", - "system": "#8c7f7f" - }, "background": "#fff" }, "scrollbars": { diff --git a/src/singletons/Theme.cpp b/src/singletons/Theme.cpp index 0c29d429ca2..ecc317b301f 100644 --- a/src/singletons/Theme.cpp +++ b/src/singletons/Theme.cpp @@ -157,9 +157,6 @@ void parseOverlayMessages(const QJsonObject &overlayMessages, const QJsonObject &overlayMessagesFallback, chatterino::Theme &theme) { - parseTextColors(overlayMessages["textColors"_L1].toObject(), - overlayMessagesFallback["textColors"_L1].toObject(), - theme.overlayMessages); parseMessageBackgrounds( overlayMessages["backgrounds"_L1].toObject(), overlayMessagesFallback["backgrounds"_L1].toObject(), diff --git a/src/singletons/Theme.hpp b/src/singletons/Theme.hpp index ac9fa070c1d..a9528094e55 100644 --- a/src/singletons/Theme.hpp +++ b/src/singletons/Theme.hpp @@ -108,7 +108,6 @@ class Theme final } messages; struct { - TextColors textColors; MessageBackgrounds backgrounds; QColor disabled; From 4617a6eaf99bf927e4823c6aed670acf16800bbc Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Mon, 30 Sep 2024 12:12:22 +0200 Subject: [PATCH 57/62] Revert "chore: remove unused overlay theme keys" This reverts commit c27127244ea617fb5a5e96a0c5c15dd19b89a704. --- docs/ChatterinoTheme.schema.json | 9 ++++++++- resources/themes/Black.json | 7 +++++++ resources/themes/Dark.json | 7 +++++++ resources/themes/Light.json | 7 +++++++ resources/themes/White.json | 7 +++++++ src/singletons/Theme.cpp | 3 +++ src/singletons/Theme.hpp | 1 + 7 files changed, 40 insertions(+), 1 deletion(-) diff --git a/docs/ChatterinoTheme.schema.json b/docs/ChatterinoTheme.schema.json index e00332392a4..7e23e73a2a3 100644 --- a/docs/ChatterinoTheme.schema.json +++ b/docs/ChatterinoTheme.schema.json @@ -293,12 +293,19 @@ "backgrounds": { "$ref": "#/definitions/message-backgrounds" }, "disabled": { "$ref": "#/definitions/qt-color" }, "selection": { "$ref": "#/definitions/qt-color" }, + "textColors": { "$ref": "#/definitions/text-colors" }, "background": { "$ref": "#/definitions/qt-color", "description": "Note: The alpha value is ignored (set through the settings)" } }, - "required": ["backgrounds", "disabled", "selection", "background"] + "required": [ + "backgrounds", + "disabled", + "selection", + "textColors", + "background" + ] }, "scrollbars": { "type": "object", diff --git a/resources/themes/Black.json b/resources/themes/Black.json index 6263a54927c..c194aa793ad 100644 --- a/resources/themes/Black.json +++ b/resources/themes/Black.json @@ -29,6 +29,13 @@ }, "disabled": "#64000000", "selection": "#40ffffff", + "textColors": { + "caret": "#ffffff", + "chatPlaceholder": "#5d5555", + "link": "#4286f4", + "regular": "#ffffff", + "system": "#8c7f7f" + }, "background": "#000" }, "scrollbars": { diff --git a/resources/themes/Dark.json b/resources/themes/Dark.json index 875fb2754b0..2b85545d766 100644 --- a/resources/themes/Dark.json +++ b/resources/themes/Dark.json @@ -29,6 +29,13 @@ }, "disabled": "#64000000", "selection": "#40ffffff", + "textColors": { + "caret": "#ffffff", + "chatPlaceholder": "#5d5555", + "link": "#4286f4", + "regular": "#ffffff", + "system": "#8c7f7f" + }, "background": "#000" }, "scrollbars": { diff --git a/resources/themes/Light.json b/resources/themes/Light.json index 0a40b1b0842..d9a4c2c28a7 100644 --- a/resources/themes/Light.json +++ b/resources/themes/Light.json @@ -29,6 +29,13 @@ }, "disabled": "#64ffffff", "selection": "#40ffffff", + "textColors": { + "caret": "#000000", + "chatPlaceholder": "#af9f9f", + "link": "#4286f4", + "regular": "#000000", + "system": "#8c7f7f" + }, "background": "#fff" }, "scrollbars": { diff --git a/resources/themes/White.json b/resources/themes/White.json index 0151041fa12..46953ba9580 100644 --- a/resources/themes/White.json +++ b/resources/themes/White.json @@ -29,6 +29,13 @@ }, "disabled": "#64ffffff", "selection": "#40ffffff", + "textColors": { + "caret": "#000000", + "chatPlaceholder": "#af9f9f", + "link": "#4286f4", + "regular": "#000000", + "system": "#8c7f7f" + }, "background": "#fff" }, "scrollbars": { diff --git a/src/singletons/Theme.cpp b/src/singletons/Theme.cpp index ecc317b301f..0c29d429ca2 100644 --- a/src/singletons/Theme.cpp +++ b/src/singletons/Theme.cpp @@ -157,6 +157,9 @@ void parseOverlayMessages(const QJsonObject &overlayMessages, const QJsonObject &overlayMessagesFallback, chatterino::Theme &theme) { + parseTextColors(overlayMessages["textColors"_L1].toObject(), + overlayMessagesFallback["textColors"_L1].toObject(), + theme.overlayMessages); parseMessageBackgrounds( overlayMessages["backgrounds"_L1].toObject(), overlayMessagesFallback["backgrounds"_L1].toObject(), diff --git a/src/singletons/Theme.hpp b/src/singletons/Theme.hpp index a9528094e55..ac9fa070c1d 100644 --- a/src/singletons/Theme.hpp +++ b/src/singletons/Theme.hpp @@ -108,6 +108,7 @@ class Theme final } messages; struct { + TextColors textColors; MessageBackgrounds backgrounds; QColor disabled; From 75e3dbfea5ff2fd8e7e78c1db76b5360c34baa09 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Mon, 30 Sep 2024 12:51:07 +0200 Subject: [PATCH 58/62] feat: use colors from theme for text in overlay --- resources/themes/Light.json | 12 +-- resources/themes/White.json | 12 +-- src/messages/MessageColor.cpp | 10 +-- src/messages/MessageColor.hpp | 5 +- src/messages/MessageElement.cpp | 77 ++++++++++--------- src/messages/MessageElement.hpp | 31 ++++---- src/messages/layouts/MessageLayout.cpp | 35 ++++----- src/messages/layouts/MessageLayout.hpp | 6 +- src/messages/layouts/MessageLayoutContext.cpp | 11 ++- src/messages/layouts/MessageLayoutContext.hpp | 21 ++++- src/messages/layouts/MessageLayoutElement.cpp | 2 +- src/widgets/helper/ChannelView.cpp | 50 ++++++++---- src/widgets/helper/MessageView.cpp | 14 +++- 13 files changed, 169 insertions(+), 117 deletions(-) diff --git a/resources/themes/Light.json b/resources/themes/Light.json index d9a4c2c28a7..ed610e313d9 100644 --- a/resources/themes/Light.json +++ b/resources/themes/Light.json @@ -24,19 +24,19 @@ }, "overlayMessages": { "backgrounds": { - "alternate": "#32ffffff", + "alternate": "#32000000", "regular": "transparent" }, - "disabled": "#64ffffff", + "disabled": "#64000000", "selection": "#40ffffff", "textColors": { - "caret": "#000000", - "chatPlaceholder": "#af9f9f", + "caret": "#ffffff", + "chatPlaceholder": "#5d5555", "link": "#4286f4", - "regular": "#000000", + "regular": "#ffffff", "system": "#8c7f7f" }, - "background": "#fff" + "background": "#333" }, "scrollbars": { "background": "#00000000", diff --git a/resources/themes/White.json b/resources/themes/White.json index 46953ba9580..89b317f3742 100644 --- a/resources/themes/White.json +++ b/resources/themes/White.json @@ -24,19 +24,19 @@ }, "overlayMessages": { "backgrounds": { - "alternate": "#32ffffff", + "alternate": "#32000000", "regular": "transparent" }, - "disabled": "#64ffffff", + "disabled": "#64000000", "selection": "#40ffffff", "textColors": { - "caret": "#000000", - "chatPlaceholder": "#af9f9f", + "caret": "#ffffff", + "chatPlaceholder": "#5d5555", "link": "#4286f4", - "regular": "#000000", + "regular": "#ffffff", "system": "#8c7f7f" }, - "background": "#fff" + "background": "#333" }, "scrollbars": { "background": "#00000000", diff --git a/src/messages/MessageColor.cpp b/src/messages/MessageColor.cpp index 674f4a6867e..ff5f01aca6f 100644 --- a/src/messages/MessageColor.cpp +++ b/src/messages/MessageColor.cpp @@ -1,6 +1,6 @@ #include "messages/MessageColor.hpp" -#include "singletons/Theme.hpp" +#include "messages/layouts/MessageLayoutContext.hpp" namespace chatterino { @@ -15,18 +15,18 @@ MessageColor::MessageColor(Type type) { } -const QColor &MessageColor::getColor(Theme &themeManager) const +const QColor &MessageColor::getColor(const MessageColors &colors) const { switch (this->type_) { case Type::Custom: return this->customColor_; case Type::Text: - return themeManager.messages.textColors.regular; + return colors.regularText; case Type::System: - return themeManager.messages.textColors.system; + return colors.systemText; case Type::Link: - return themeManager.messages.textColors.link; + return colors.linkText; } static QColor _default; diff --git a/src/messages/MessageColor.hpp b/src/messages/MessageColor.hpp index 5592b973515..8eb07d3504e 100644 --- a/src/messages/MessageColor.hpp +++ b/src/messages/MessageColor.hpp @@ -4,7 +4,8 @@ #include namespace chatterino { -class Theme; + +struct MessageColors; struct MessageColor { enum Type { Custom, Text, Link, System }; @@ -12,7 +13,7 @@ struct MessageColor { MessageColor(const QColor &color); MessageColor(Type type_ = Text); - const QColor &getColor(Theme &themeManager) const; + const QColor &getColor(const MessageColors &colors) const; QString toString() const; diff --git a/src/messages/MessageElement.cpp b/src/messages/MessageElement.cpp index a4fd6fe846d..d61613b750f 100644 --- a/src/messages/MessageElement.cpp +++ b/src/messages/MessageElement.cpp @@ -7,6 +7,7 @@ #include "messages/Emote.hpp" #include "messages/Image.hpp" #include "messages/layouts/MessageLayoutContainer.hpp" +#include "messages/layouts/MessageLayoutContext.hpp" #include "messages/layouts/MessageLayoutElement.hpp" #include "providers/emoji/Emojis.hpp" #include "singletons/Emotes.hpp" @@ -119,9 +120,9 @@ ImageElement::ImageElement(ImagePtr image, MessageElementFlags flags) } void ImageElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { - if (flags.hasAny(this->getFlags())) + if (ctx.flags.hasAny(this->getFlags())) { auto size = QSize(this->image_->width() * container.getScale(), this->image_->height() * container.getScale()); @@ -151,9 +152,9 @@ CircularImageElement::CircularImageElement(ImagePtr image, int padding, } void CircularImageElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { - if (flags.hasAny(this->getFlags())) + if (ctx.flags.hasAny(this->getFlags())) { auto imgSize = QSize(this->image_->width(), this->image_->height()) * container.getScale(); @@ -192,11 +193,11 @@ EmotePtr EmoteElement::getEmote() const } void EmoteElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { - if (flags.hasAny(this->getFlags())) + if (ctx.flags.hasAny(this->getFlags())) { - if (flags.has(MessageElementFlag::EmoteImages)) + if (ctx.flags.has(MessageElementFlag::EmoteImages)) { auto image = this->emote_->images.getImageOrLoaded( container.getImageScale()); @@ -217,8 +218,9 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container, { if (this->textElement_) { - this->textElement_->addToContainer(container, - MessageElementFlag::Misc); + auto textCtx = ctx; + textCtx.flags = MessageElementFlag::Misc; + this->textElement_->addToContainer(container, textCtx); } } } @@ -260,11 +262,11 @@ void LayeredEmoteElement::addEmoteLayer(const LayeredEmoteElement::Emote &emote) } void LayeredEmoteElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { - if (flags.hasAny(this->getFlags())) + if (ctx.flags.hasAny(this->getFlags())) { - if (flags.has(MessageElementFlag::EmoteImages)) + if (ctx.flags.has(MessageElementFlag::EmoteImages)) { auto images = this->getLoadedImages(container.getImageScale()); if (images.empty()) @@ -291,8 +293,9 @@ void LayeredEmoteElement::addToContainer(MessageLayoutContainer &container, { if (this->textElement_) { - this->textElement_->addToContainer(container, - MessageElementFlag::Misc); + auto textCtx = ctx; + textCtx.flags = MessageElementFlag::Misc; + this->textElement_->addToContainer(container, textCtx); } } } @@ -447,9 +450,9 @@ BadgeElement::BadgeElement(const EmotePtr &emote, MessageElementFlags flags) } void BadgeElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { - if (flags.hasAny(this->getFlags())) + if (ctx.flags.hasAny(this->getFlags())) { auto image = this->emote_->images.getImageOrLoaded(container.getImageScale()); @@ -574,11 +577,11 @@ TextElement::TextElement(const QString &text, MessageElementFlags flags, } void TextElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { auto *app = getApp(); - if (flags.hasAny(this->getFlags())) + if (ctx.flags.hasAny(this->getFlags())) { QFontMetrics metrics = app->getFonts()->getFontMetrics(this->style_, container.getScale()); @@ -589,7 +592,7 @@ void TextElement::addToContainer(MessageLayoutContainer &container, auto getTextLayoutElement = [&](QString text, int width, bool hasTrailingSpace) { - auto color = this->color_.getColor(*app->getThemes()); + auto color = this->color_.getColor(ctx.messageColors); app->getThemes()->normalizeColor(color); auto *e = new TextLayoutElement( @@ -697,18 +700,18 @@ SingleLineTextElement::SingleLineTextElement(const QString &text, } void SingleLineTextElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { auto *app = getApp(); - if (flags.hasAny(this->getFlags())) + if (ctx.flags.hasAny(this->getFlags())) { QFontMetrics metrics = app->getFonts()->getFontMetrics(this->style_, container.getScale()); auto getTextLayoutElement = [&](QString text, int width, bool hasTrailingSpace) { - auto color = this->color_.getColor(*app->getThemes()); + auto color = this->color_.getColor(ctx.messageColors); app->getThemes()->normalizeColor(color); auto *e = new TextLayoutElement( @@ -838,11 +841,11 @@ LinkElement::LinkElement(const Parsed &parsed, const QString &fullUrl, } void LinkElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { this->words_ = getSettings()->lowercaseDomains ? this->lowercase_ : this->original_; - TextElement::addToContainer(container, flags); + TextElement::addToContainer(container, ctx); } Link LinkElement::getLink() const @@ -873,7 +876,7 @@ MentionElement::MentionElement(const QString &displayName, QString loginName_, } void MentionElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { if (getSettings()->colorUsernames) { @@ -893,7 +896,7 @@ void MentionElement::addToContainer(MessageLayoutContainer &container, this->style_ = FontStyle::ChatMedium; } - TextElement::addToContainer(container, flags); + TextElement::addToContainer(container, ctx); } MessageElement *MentionElement::setLink(const Link &link) @@ -943,9 +946,9 @@ TimestampElement::TimestampElement(QTime time) } void TimestampElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { - if (flags.hasAny(this->getFlags())) + if (ctx.flags.hasAny(this->getFlags())) { if (getSettings()->timestampFormat != this->format_) { @@ -953,7 +956,7 @@ void TimestampElement::addToContainer(MessageLayoutContainer &container, this->element_.reset(this->formatTime(this->time_)); } - this->element_->addToContainer(container, flags); + this->element_->addToContainer(container, ctx); } } @@ -985,9 +988,9 @@ TwitchModerationElement::TwitchModerationElement() } void TwitchModerationElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { - if (flags.has(MessageElementFlag::ModeratorTools)) + if (ctx.flags.has(MessageElementFlag::ModeratorTools)) { QSize size(int(container.getScale() * 16), int(container.getScale() * 16)); @@ -1026,9 +1029,9 @@ LinebreakElement::LinebreakElement(MessageElementFlags flags) } void LinebreakElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { - if (flags.hasAny(this->getFlags())) + if (ctx.flags.hasAny(this->getFlags())) { container.breakLine(); } @@ -1050,9 +1053,9 @@ ScalingImageElement::ScalingImageElement(ImageSet images, } void ScalingImageElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { - if (flags.hasAny(this->getFlags())) + if (ctx.flags.hasAny(this->getFlags())) { const auto &image = this->images_.getImageOrLoaded(container.getImageScale()); @@ -1083,14 +1086,14 @@ ReplyCurveElement::ReplyCurveElement() } void ReplyCurveElement::addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) + const MessageLayoutContext &ctx) { static const int width = 18; // Overall width static const float thickness = 1.5; // Pen width static const int radius = 6; // Radius of the top left corner static const int margin = 2; // Top/Left/Bottom margin - if (flags.hasAny(this->getFlags())) + if (ctx.flags.hasAny(this->getFlags())) { float scale = container.getScale(); container.addElement( diff --git a/src/messages/MessageElement.hpp b/src/messages/MessageElement.hpp index 041593cac20..0e6c07b93ba 100644 --- a/src/messages/MessageElement.hpp +++ b/src/messages/MessageElement.hpp @@ -23,6 +23,7 @@ namespace chatterino { class Channel; struct MessageLayoutContainer; class MessageLayoutElement; +struct MessageLayoutContext; class Image; using ImagePtr = std::shared_ptr; @@ -184,7 +185,7 @@ class MessageElement void addFlags(MessageElementFlags flags); virtual void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) = 0; + const MessageLayoutContext &ctx) = 0; virtual QJsonObject toJson() const; @@ -205,7 +206,7 @@ class ImageElement : public MessageElement ImageElement(ImagePtr image, MessageElementFlags flags); void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) override; + const MessageLayoutContext &ctx) override; QJsonObject toJson() const override; @@ -221,7 +222,7 @@ class CircularImageElement : public MessageElement MessageElementFlags flags); void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) override; + const MessageLayoutContext &ctx) override; QJsonObject toJson() const override; @@ -241,7 +242,7 @@ class TextElement : public MessageElement ~TextElement() override = default; void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) override; + const MessageLayoutContext &ctx) override; QJsonObject toJson() const override; @@ -262,7 +263,7 @@ class SingleLineTextElement : public MessageElement ~SingleLineTextElement() override = default; void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) override; + const MessageLayoutContext &ctx) override; QJsonObject toJson() const override; @@ -298,7 +299,7 @@ class LinkElement : public TextElement LinkElement &operator=(LinkElement &&) = delete; void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) override; + const MessageLayoutContext &ctx) override; Link getLink() const override; @@ -338,7 +339,7 @@ class MentionElement : public TextElement MentionElement &operator=(MentionElement &&) = delete; void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) override; + const MessageLayoutContext &ctx) override; MessageElement *setLink(const Link &link) override; Link getLink() const override; @@ -369,7 +370,7 @@ class EmoteElement : public MessageElement const MessageColor &textElementColor = MessageColor::Text); void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags_) override; + const MessageLayoutContext &ctx) override; EmotePtr getEmote() const; QJsonObject toJson() const override; @@ -401,7 +402,7 @@ class LayeredEmoteElement : public MessageElement void addEmoteLayer(const Emote &emote); void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) override; + const MessageLayoutContext &ctx) override; // Returns a concatenation of each emote layer's cleaned copy string QString getCleanCopyString() const; @@ -433,7 +434,7 @@ class BadgeElement : public MessageElement BadgeElement(const EmotePtr &data, MessageElementFlags flags_); void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags_) override; + const MessageLayoutContext &ctx) override; EmotePtr getEmote() const; @@ -494,7 +495,7 @@ class TimestampElement : public MessageElement ~TimestampElement() override = default; void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) override; + const MessageLayoutContext &ctx) override; TextElement *formatTime(const QTime &time); @@ -514,7 +515,7 @@ class TwitchModerationElement : public MessageElement TwitchModerationElement(); void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) override; + const MessageLayoutContext &ctx) override; QJsonObject toJson() const override; }; @@ -526,7 +527,7 @@ class LinebreakElement : public MessageElement LinebreakElement(MessageElementFlags flags); void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) override; + const MessageLayoutContext &ctx) override; QJsonObject toJson() const override; }; @@ -538,7 +539,7 @@ class ScalingImageElement : public MessageElement ScalingImageElement(ImageSet images, MessageElementFlags flags); void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) override; + const MessageLayoutContext &ctx) override; QJsonObject toJson() const override; @@ -552,7 +553,7 @@ class ReplyCurveElement : public MessageElement ReplyCurveElement(); void addToContainer(MessageLayoutContainer &container, - MessageElementFlags flags) override; + const MessageLayoutContext &ctx) override; QJsonObject toJson() const override; }; diff --git a/src/messages/layouts/MessageLayout.cpp b/src/messages/layouts/MessageLayout.cpp index 06d6f618277..81c75c6b471 100644 --- a/src/messages/layouts/MessageLayout.cpp +++ b/src/messages/layouts/MessageLayout.cpp @@ -68,8 +68,7 @@ int MessageLayout::getWidth() const // Layout // return true if redraw is required -bool MessageLayout::layout(int width, float scale, float imageScale, - MessageElementFlags flags, +bool MessageLayout::layout(const MessageLayoutContext &ctx, bool shouldInvalidateBuffer) { // BenchmarkGuard benchmark("MessageLayout::layout()"); @@ -77,9 +76,9 @@ bool MessageLayout::layout(int width, float scale, float imageScale, bool layoutRequired = false; // check if width changed - bool widthChanged = width != this->currentLayoutWidth_; + bool widthChanged = ctx.width != this->currentLayoutWidth_; layoutRequired |= widthChanged; - this->currentLayoutWidth_ = width; + this->currentLayoutWidth_ = ctx.width; // check if layout state changed const auto layoutGeneration = getApp()->getWindows()->getGeneration(); @@ -91,18 +90,18 @@ bool MessageLayout::layout(int width, float scale, float imageScale, } // check if work mask changed - layoutRequired |= this->currentWordFlags_ != flags; - this->currentWordFlags_ = flags; // getSettings()->getWordTypeMask(); + layoutRequired |= this->currentWordFlags_ != ctx.flags; + this->currentWordFlags_ = ctx.flags; // getSettings()->getWordTypeMask(); // check if layout was requested manually layoutRequired |= this->flags.has(MessageLayoutFlag::RequiresLayout); this->flags.unset(MessageLayoutFlag::RequiresLayout); // check if dpi changed - layoutRequired |= this->scale_ != scale; - this->scale_ = scale; - layoutRequired |= this->imageScale_ != imageScale; - this->imageScale_ = imageScale; + layoutRequired |= this->scale_ != ctx.scale; + this->scale_ = ctx.scale; + layoutRequired |= this->imageScale_ != ctx.imageScale; + this->imageScale_ = ctx.imageScale; if (!layoutRequired) { @@ -115,7 +114,7 @@ bool MessageLayout::layout(int width, float scale, float imageScale, } int oldHeight = this->container_.getHeight(); - this->actuallyLayout(width, flags); + this->actuallyLayout(ctx); if (widthChanged || this->container_.getHeight() != oldHeight) { this->deleteBuffer(); @@ -125,7 +124,7 @@ bool MessageLayout::layout(int width, float scale, float imageScale, return true; } -void MessageLayout::actuallyLayout(int width, MessageElementFlags flags) +void MessageLayout::actuallyLayout(const MessageLayoutContext &ctx) { #ifdef FOURTF this->layoutCount_++; @@ -134,7 +133,7 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags) auto messageFlags = this->message_->flags; if (this->flags.has(MessageLayoutFlag::Expanded) || - (flags.has(MessageElementFlag::ModeratorTools) && + (ctx.flags.has(MessageElementFlag::ModeratorTools) && !this->message_->flags.has(MessageFlag::Disabled))) { messageFlags.unset(MessageFlag::Collapsed); @@ -143,9 +142,9 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags) bool hideModerated = getSettings()->hideModerated; bool hideModerationActions = getSettings()->hideModerationActions; bool hideSimilar = getSettings()->hideSimilar; - bool hideReplies = !flags.has(MessageElementFlag::RepliedMessage); + bool hideReplies = !ctx.flags.has(MessageElementFlag::RepliedMessage); - this->container_.beginLayout(width, this->scale_, this->imageScale_, + this->container_.beginLayout(ctx.width, this->scale_, this->imageScale_, messageFlags); for (const auto &element : this->message_->elements) @@ -177,7 +176,7 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags) continue; } - element->addToContainer(this->container_, flags); + element->addToContainer(this->container_, ctx); } if (this->height_ != this->container_.getHeight()) @@ -323,10 +322,10 @@ void MessageLayout::updateBuffer(QPixmap *buffer, if (ctx.preferences.alternateMessages && this->flags.has(MessageLayoutFlag::AlternateBackground)) { - return ctx.messageColors.alternate; + return ctx.messageColors.alternateBg; } - return ctx.messageColors.regular; + return ctx.messageColors.regularBg; }(); if (this->message_->flags.has(MessageFlag::ElevatedMessage) && diff --git a/src/messages/layouts/MessageLayout.hpp b/src/messages/layouts/MessageLayout.hpp index 0f2625f679b..841fd328d9f 100644 --- a/src/messages/layouts/MessageLayout.hpp +++ b/src/messages/layouts/MessageLayout.hpp @@ -18,6 +18,7 @@ struct Selection; struct MessageLayoutContainer; class MessageLayoutElement; struct MessagePaintContext; +struct MessageLayoutContext; enum class MessageElementFlag : int64_t; using MessageElementFlags = FlagsEnum; @@ -56,8 +57,7 @@ class MessageLayout MessageLayoutFlags flags; - bool layout(int width, float scale_, float imageScale, - MessageElementFlags flags, bool shouldInvalidateBuffer); + bool layout(const MessageLayoutContext &ctx, bool shouldInvalidateBuffer); // Painting MessagePaintResult paint(const MessagePaintContext &ctx); @@ -112,7 +112,7 @@ class MessageLayout private: // methods - void actuallyLayout(int width, MessageElementFlags flags); + void actuallyLayout(const MessageLayoutContext &ctx); void updateBuffer(QPixmap *buffer, const MessagePaintContext &ctx); // Create new buffer if required, returning the buffer diff --git a/src/messages/layouts/MessageLayoutContext.cpp b/src/messages/layouts/MessageLayoutContext.cpp index f449b19f85e..f754ea408f3 100644 --- a/src/messages/layouts/MessageLayoutContext.cpp +++ b/src/messages/layouts/MessageLayoutContext.cpp @@ -11,12 +11,15 @@ void MessageColors::applyTheme(Theme *theme, bool isOverlay, int backgroundOpacity) { auto applyColors = [this](const auto &src) { - this->regular = src.backgrounds.regular; - this->alternate = src.backgrounds.alternate; + this->regularBg = src.backgrounds.regular; + this->alternateBg = src.backgrounds.alternate; this->disabled = src.disabled; this->selection = src.selection; - this->system = src.textColors.system; + + this->regularText = src.textColors.regular; + this->linkText = src.textColors.link; + this->systemText = src.textColors.system; }; if (isOverlay) @@ -37,7 +40,7 @@ void MessageColors::applyTheme(Theme *theme, bool isOverlay, this->unfocusedLastMessageLine = theme->tabs.selected.backgrounds.unfocused; this->hasTransparency = - this->regular.alpha() != 255 || this->alternate.alpha() != 255; + this->regularBg.alpha() != 255 || this->alternateBg.alpha() != 255; } void MessagePreferences::connectSettings(Settings *settings, diff --git a/src/messages/layouts/MessageLayoutContext.hpp b/src/messages/layouts/MessageLayoutContext.hpp index c4bc520bb08..b7a9b6886cf 100644 --- a/src/messages/layouts/MessageLayoutContext.hpp +++ b/src/messages/layouts/MessageLayoutContext.hpp @@ -1,5 +1,7 @@ #pragma once +#include "messages/MessageElement.hpp" + #include #include @@ -21,11 +23,15 @@ struct MessageColors { // true if any of the background colors have transparency bool hasTransparency = false; - QColor regular; - QColor alternate; + QColor regularBg; + QColor alternateBg; + QColor disabled; QColor selection; - QColor system; + + QColor regularText; + QColor linkText; + QColor systemText; QColor messageSeperator; @@ -77,4 +83,13 @@ struct MessagePaintContext { bool isLastReadMessage{}; }; +struct MessageLayoutContext { + const MessageColors &messageColors; + MessageElementFlags flags; + + int width = 1; + float scale = 1; + float imageScale = 1; +}; + } // namespace chatterino diff --git a/src/messages/layouts/MessageLayoutElement.cpp b/src/messages/layouts/MessageLayoutElement.cpp index 3bfe773304a..7b5e3fb5842 100644 --- a/src/messages/layouts/MessageLayoutElement.cpp +++ b/src/messages/layouts/MessageLayoutElement.cpp @@ -555,7 +555,7 @@ void TextIconLayoutElement::paint(QPainter &painter, QFont font = app->getFonts()->getFont(FontStyle::Tiny, this->scale); - painter.setPen(messageColors.system); + painter.setPen(messageColors.systemText); painter.setFont(font); QTextOption option; diff --git a/src/widgets/helper/ChannelView.cpp b/src/widgets/helper/ChannelView.cpp index 7932f8d0e3e..0ffa02bbc95 100644 --- a/src/widgets/helper/ChannelView.cpp +++ b/src/widgets/helper/ChannelView.cpp @@ -698,9 +698,15 @@ void ChannelView::layoutVisibleMessages( const auto &message = messages[i]; redrawRequired |= message->layout( - layoutWidth, this->scale(), - this->scale() * static_cast(this->devicePixelRatio()), - flags, this->bufferInvalidationQueued_); + { + .messageColors = this->messageColors_, + .flags = flags, + .width = layoutWidth, + .scale = this->scale(), + .imageScale = this->scale() * + static_cast(this->devicePixelRatio()), + }, + this->bufferInvalidationQueued_); y += message->getHeight(); } @@ -735,8 +741,14 @@ void ChannelView::updateScrollbar( auto *message = messages[i].get(); message->layout( - layoutWidth, this->scale(), - this->scale() * static_cast(this->devicePixelRatio()), flags, + { + .messageColors = this->messageColors_, + .flags = flags, + .width = layoutWidth, + .scale = this->scale(), + .imageScale = this->scale() * + static_cast(this->devicePixelRatio()), + }, false); h -= message->getHeight(); @@ -1723,10 +1735,16 @@ void ChannelView::wheelEvent(QWheelEvent *event) else { snapshot[i - 1]->layout( - this->getLayoutWidth(), this->scale(), - this->scale() * - static_cast(this->devicePixelRatio()), - this->getFlags(), false); + { + .messageColors = this->messageColors_, + .flags = this->getFlags(), + .width = this->getLayoutWidth(), + .scale = this->scale(), + .imageScale = + this->scale() * + static_cast(this->devicePixelRatio()), + }, + false); scrollFactor = 1; currentScrollLeft = snapshot[i - 1]->getHeight(); } @@ -1760,10 +1778,16 @@ void ChannelView::wheelEvent(QWheelEvent *event) else { snapshot[i + 1]->layout( - this->getLayoutWidth(), this->scale(), - this->scale() * - static_cast(this->devicePixelRatio()), - this->getFlags(), false); + { + .messageColors = this->messageColors_, + .flags = this->getFlags(), + .width = this->getLayoutWidth(), + .scale = this->scale(), + .imageScale = + this->scale() * + static_cast(this->devicePixelRatio()), + }, + false); scrollFactor = 1; currentScrollLeft = snapshot[i + 1]->getHeight(); diff --git a/src/widgets/helper/MessageView.cpp b/src/widgets/helper/MessageView.cpp index a26e0d00596..ceb004c2795 100644 --- a/src/widgets/helper/MessageView.cpp +++ b/src/widgets/helper/MessageView.cpp @@ -98,7 +98,7 @@ void MessageView::paintEvent(QPaintEvent * /*event*/) void MessageView::themeChangedEvent() { this->messageColors_.applyTheme(getTheme(), false, 255); - this->messageColors_.regular = getTheme()->splits.input.background; + this->messageColors_.regularBg = getTheme()->splits.input.background; if (this->messageLayout_) { this->messageLayout_->invalidateBuffer(); @@ -120,9 +120,15 @@ void MessageView::layoutMessage() } bool updateRequired = this->messageLayout_->layout( - this->width_, this->scale(), - this->scale() * static_cast(this->devicePixelRatio()), - MESSAGE_FLAGS, false); + { + .messageColors = this->messageColors_, + .flags = MESSAGE_FLAGS, + .width = this->width_, + .scale = this->scale(), + .imageScale = + this->scale() * static_cast(this->devicePixelRatio()), + }, + false); if (updateRequired) { From 8f2bbddd324b9cafb10901eab675d7e89301371e Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Mon, 30 Sep 2024 12:52:21 +0200 Subject: [PATCH 59/62] fix: remove unused theme --- src/widgets/OverlayWindow.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index 1499c73827c..f9e2685a032 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -5,7 +5,6 @@ #include "common/Literals.hpp" #include "controllers/hotkeys/HotkeyController.hpp" #include "singletons/Settings.hpp" -#include "singletons/Theme.hpp" #include "singletons/WindowManager.hpp" #include "widgets/BaseWidget.hpp" #include "widgets/helper/ChannelView.hpp" @@ -175,7 +174,6 @@ OverlayWindow::~OverlayWindow() void OverlayWindow::applyTheme() { - auto *theme = getTheme(); auto *settings = getSettings(); if (this->dropShadow_) From 30624638bf79ef4d9d0b7614dde2ae3bee04bd69 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Mon, 30 Sep 2024 13:10:50 +0200 Subject: [PATCH 60/62] fix: tests --- tests/src/MessageLayout.cpp | 12 +++++++++++- tests/src/MessageLayoutContainer.cpp | 22 ++++++++++++++++------ 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/tests/src/MessageLayout.cpp b/tests/src/MessageLayout.cpp index 06fb4db5932..72b50f872de 100644 --- a/tests/src/MessageLayout.cpp +++ b/tests/src/MessageLayout.cpp @@ -2,6 +2,7 @@ #include "Application.hpp" #include "controllers/accounts/AccountController.hpp" +#include "messages/layouts/MessageLayoutContext.hpp" #include "messages/MessageBuilder.hpp" #include "messages/MessageElement.hpp" #include "mocks/BaseApplication.hpp" @@ -55,7 +56,16 @@ class MessageLayoutTest builder.append( std::make_unique(text, MessageElementFlag::Text)); this->layout = std::make_unique(builder.release()); - this->layout->layout(WIDTH, 1, 1, MessageElementFlag::Text, false); + MessageColors colors; + this->layout->layout( + { + .messageColors = colors, + .flags = MessageElementFlag::Text, + .width = WIDTH, + .scale = 1, + .imageScale = 1, + }, + false); } MockApplication mockApplication; diff --git a/tests/src/MessageLayoutContainer.cpp b/tests/src/MessageLayoutContainer.cpp index b0aa4f30e4a..2f1b810fd02 100644 --- a/tests/src/MessageLayoutContainer.cpp +++ b/tests/src/MessageLayoutContainer.cpp @@ -2,6 +2,7 @@ #include "common/Literals.hpp" #include "messages/Emote.hpp" +#include "messages/layouts/MessageLayoutContext.hpp" #include "messages/layouts/MessageLayoutElement.hpp" #include "messages/Message.hpp" #include "messages/MessageElement.hpp" @@ -107,16 +108,25 @@ TEST_P(MessageLayoutContainerTest, RtlReordering) { auto [inputText, expected, expectedDirection] = GetParam(); MessageLayoutContainer container; - container.beginLayout(10000, 1.0F, 1.0F, {MessageFlag::Collapsed}); + MessageLayoutContext ctx{ + .messageColors = {}, + .flags = + { + MessageElementFlag::Text, + MessageElementFlag::Username, + MessageElementFlag::TwitchEmote, + }, + .width = 10000, + .scale = 1.0F, + .imageScale = 1.0F, + }; + container.beginLayout(ctx.width, ctx.scale, ctx.imageScale, + {MessageFlag::Collapsed}); auto elements = makeElements(inputText); for (const auto &element : elements) { - element->addToContainer(container, { - MessageElementFlag::Text, - MessageElementFlag::Username, - MessageElementFlag::TwitchEmote, - }); + element->addToContainer(container, ctx); } container.endLayout(); ASSERT_EQ(container.line_, 1) << "unexpected linebreak"; From cd72595441143c5d0fe265c490dcb5ae6da6efc6 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 6 Oct 2024 12:12:24 +0200 Subject: [PATCH 61/62] fix: `update` after `overlayBackgroundOpacity` changes --- src/widgets/OverlayWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index f9e2685a032..b2becb203c0 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -149,6 +149,7 @@ OverlayWindow::OverlayWindow(IndirectChannel channel) settings->overlayBackgroundOpacity.connect( [this] { this->channelView_.updateColorTheme(); + this->update(); }, this->holder_, false); From f0cbddedeed4203a358fffcbf89d606470343dd0 Mon Sep 17 00:00:00 2001 From: Nerixyz Date: Sun, 6 Oct 2024 12:23:58 +0200 Subject: [PATCH 62/62] refactor: `togglePopupInertia` -> `toggleOverlayInertia` --- src/controllers/hotkeys/ActionNames.hpp | 2 +- src/controllers/hotkeys/HotkeyController.cpp | 6 +++--- src/widgets/OverlayWindow.cpp | 6 +++--- src/widgets/splits/Split.cpp | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/controllers/hotkeys/ActionNames.hpp b/src/controllers/hotkeys/ActionNames.hpp index d4209b2c3c7..f15d2e2026b 100644 --- a/src/controllers/hotkeys/ActionNames.hpp +++ b/src/controllers/hotkeys/ActionNames.hpp @@ -182,7 +182,7 @@ inline const std::map actionNames{ {"showGlobalSearch", ActionDefinition{"Search all channels"}}, {"debug", ActionDefinition{"Show debug popup"}}, {"popupOverlay", ActionDefinition{"New overlay popup"}}, - {"togglePopupInertia", + {"toggleOverlayInertia", ActionDefinition{ .displayName = "Toggle overlay click-through", .argumentDescription = "", diff --git a/src/controllers/hotkeys/HotkeyController.cpp b/src/controllers/hotkeys/HotkeyController.cpp index c4717235537..1aa6e7c9664 100644 --- a/src/controllers/hotkeys/HotkeyController.cpp +++ b/src/controllers/hotkeys/HotkeyController.cpp @@ -406,9 +406,9 @@ void HotkeyController::addDefaults(std::set &addedHotkeys) this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, QKeySequence("Ctrl+Alt+N"), "popupOverlay", {}, "open overlay"); - this->tryAddDefault(addedHotkeys, HotkeyCategory::Split, - QKeySequence("Ctrl+Shift+U"), "togglePopupInertia", - {"all"}, "toggle overlay click-through"); + this->tryAddDefault( + addedHotkeys, HotkeyCategory::Split, QKeySequence("Ctrl+Shift+U"), + "toggleOverlayInertia", {"all"}, "toggle overlay click-through"); } // split input diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index b2becb203c0..f81ebf0ca86 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -61,20 +61,20 @@ void acquireKnowledge(Knowledge knowledge) std::pair toggleIntertiaShortcut() { auto seq = getApp()->getHotkeys()->getDisplaySequence( - HotkeyCategory::Split, u"togglePopupInertia"_s, {{u"this"_s}}); + HotkeyCategory::Split, u"toggleOverlayInertia"_s, {{u"this"_s}}); if (!seq.isEmpty()) { return {seq, false}; } seq = getApp()->getHotkeys()->getDisplaySequence( - HotkeyCategory::Split, u"togglePopupInertia"_s, {{u"thisOrAll"_s}}); + HotkeyCategory::Split, u"toggleOverlayInertia"_s, {{u"thisOrAll"_s}}); if (!seq.isEmpty()) { return {seq, false}; } return { getApp()->getHotkeys()->getDisplaySequence(HotkeyCategory::Split, - u"togglePopupInertia"_s), + u"toggleOverlayInertia"_s), true, }; } diff --git a/src/widgets/splits/Split.cpp b/src/widgets/splits/Split.cpp index a28cd4ac51b..2356653d628 100644 --- a/src/widgets/splits/Split.cpp +++ b/src/widgets/splits/Split.cpp @@ -771,12 +771,12 @@ void Split::addShortcuts() this->showOverlayWindow(); return {}; }}, - {"togglePopupInertia", + {"toggleOverlayInertia", [this](const auto &args) -> QString { if (args.empty()) { - return "No arguments provided to togglePopupInertia (expected " - "one)"; + return "No arguments provided to toggleOverlayInertia " + "(expected one)"; } const auto &arg = args.front();