diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a21300a..20255f46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Minor: Improved error messaging of the `/clip` command. (#5879) - Minor: Added Linux support for Live Notifications toasts. (#5881, #5971) - Minor: Messages can now be deleted from the context menu in a channel. (#5956) +- Minor: Overlay windows now inherit the global zoom level and can be zoomed independently. (#6016) - Bugfix: Fixed a potential way to escape the Lua Plugin sandbox. (#5846) - Bugfix: Fixed a crash relating to Lua HTTP. (#5800) - Bugfix: Fixed a crash that could occur on Linux and macOS when clicking "Install" from the update prompt. (#5818) diff --git a/src/singletons/Settings.cpp b/src/singletons/Settings.cpp index a62f4658..357c1078 100644 --- a/src/singletons/Settings.cpp +++ b/src/singletons/Settings.cpp @@ -285,6 +285,16 @@ void Settings::setClampedUiScale(float value) this->uiScale.setValue(std::clamp(value, 0.2F, 10.F)); } +float Settings::getClampedOverlayScale() const +{ + return std::clamp(this->overlayScaleFactor.getValue(), 0.2F, 10.F); +} + +void Settings::setClampedOverlayScale(float value) +{ + this->overlayScaleFactor.setValue(std::clamp(value, 0.2F, 10.F)); +} + Settings &Settings::instance() { assert(instance_ != nullptr); diff --git a/src/singletons/Settings.hpp b/src/singletons/Settings.hpp index 7783bd5a..3c26f46e 100644 --- a/src/singletons/Settings.hpp +++ b/src/singletons/Settings.hpp @@ -190,6 +190,7 @@ public: // BoolSetting useCustomWindowFrame = {"/appearance/useCustomWindowFrame", // false}; + FloatSetting overlayScaleFactor = {"/appearance/overlay/scaleFactor", 1}; IntSetting overlayBackgroundOpacity = { "/appearance/overlay/backgroundOpacity", 50}; BoolSetting enableOverlayShadow = {"/appearance/overlay/shadow", true}; @@ -202,6 +203,9 @@ public: IntSetting overlayShadowOffsetY = {"/appearance/overlay/shadowOffsetY", 2}; IntSetting overlayShadowRadius = {"/appearance/overlay/shadowRadius", 8}; + float getClampedOverlayScale() const; + void setClampedOverlayScale(float value); + // Badges BoolSetting showBadgesGlobalAuthority = { "/appearance/badges/GlobalAuthority", true}; diff --git a/src/widgets/BaseWidget.hpp b/src/widgets/BaseWidget.hpp index 4fdc421c..16e98d54 100644 --- a/src/widgets/BaseWidget.hpp +++ b/src/widgets/BaseWidget.hpp @@ -61,6 +61,7 @@ private: std::vector widgets_; friend class BaseWindow; + friend class OverlayWindow; // for setScale() }; } // namespace chatterino diff --git a/src/widgets/BaseWindow.cpp b/src/widgets/BaseWindow.cpp index 31f72a3d..eac71d50 100644 --- a/src/widgets/BaseWindow.cpp +++ b/src/widgets/BaseWindow.cpp @@ -39,6 +39,8 @@ namespace { +using namespace chatterino; + #ifdef USEWINSDK // From kHiddenTaskbarSize in Firefox @@ -194,14 +196,29 @@ RECT windowBordersFor(HWND hwnd, bool isMaximized) #endif +Qt::WindowFlags windowFlagsFor(FlagsEnum flags) +{ + Qt::WindowFlags out; + if (flags.has(BaseWindow::Dialog)) + { + out.setFlag(Qt::Dialog); + } + else + { + out.setFlag(Qt::Window); + } + out.setFlag(Qt::WindowStaysOnTopHint, flags.has(BaseWindow::TopMost)); + out.setFlag(Qt::FramelessWindowHint, flags.has(BaseWindow::Frameless)); + + return out; +} + } // namespace namespace chatterino { BaseWindow::BaseWindow(FlagsEnum _flags, QWidget *parent) - : BaseWidget(parent, (_flags.has(Dialog) ? Qt::Dialog : Qt::Window) | - (_flags.has(TopMost) ? Qt::WindowStaysOnTopHint - : Qt::WindowFlags())) + : BaseWidget(parent, windowFlagsFor(_flags)) , enableCustomFrame_(_flags.has(EnableCustomFrame)) , frameless_(_flags.has(Frameless)) , flags_(_flags) @@ -209,7 +226,6 @@ BaseWindow::BaseWindow(FlagsEnum _flags, QWidget *parent) if (this->frameless_) { this->enableCustomFrame_ = false; - this->setWindowFlag(Qt::FramelessWindowHint); } if (_flags.has(DontFocus)) @@ -955,11 +971,15 @@ void BaseWindow::paintEvent(QPaintEvent *) this->drawCustomWindowFrame(painter); } +float BaseWindow::desiredScale() const +{ + return getSettings()->getClampedUiScale(); +} + void BaseWindow::updateScale() { - auto scale = this->flags_.has(DisableCustomScaling) - ? 1 - : getSettings()->getClampedUiScale(); + auto scale = + this->flags_.has(DisableCustomScaling) ? 1 : this->desiredScale(); this->setScale(scale); diff --git a/src/widgets/BaseWindow.hpp b/src/widgets/BaseWindow.hpp index 77e5c723..cc736ba5 100644 --- a/src/widgets/BaseWindow.hpp +++ b/src/widgets/BaseWindow.hpp @@ -121,6 +121,8 @@ protected: QPointF movingRelativePos; bool moving{}; + /// @returns The scale this window wants to be at. + virtual float desiredScale() const; void updateScale(); std::optional overrideBackgroundColor_; diff --git a/src/widgets/OverlayWindow.cpp b/src/widgets/OverlayWindow.cpp index a32ffbb6..8ef8d30d 100644 --- a/src/widgets/OverlayWindow.cpp +++ b/src/widgets/OverlayWindow.cpp @@ -3,6 +3,7 @@ #include "Application.hpp" #include "common/FlagsEnum.hpp" #include "common/Literals.hpp" +#include "common/QLogging.hpp" #include "controllers/hotkeys/HotkeyController.hpp" #include "singletons/Emotes.hpp" #include "singletons/Settings.hpp" @@ -89,8 +90,11 @@ using namespace std::chrono_literals; OverlayWindow::OverlayWindow(IndirectChannel channel, const QList &filterIDs) - : QWidget(nullptr, - Qt::Window | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint) + : BaseWindow({ + BaseWindow::Frameless, + BaseWindow::TopMost, + BaseWindow::DisableLayoutSave, + }) #ifdef Q_OS_WIN , sizeAllCursor_(::LoadCursor(nullptr, IDC_SIZEALL)) #endif @@ -123,9 +127,10 @@ OverlayWindow::OverlayWindow(IndirectChannel channel, this->channelView_.setChannel(this->channel_.get()); 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()); - }); + this->signalHolder_.managedConnect( + this->channel_.getChannelChanged(), [this]() { + this->channelView_.setChannel(this->channel_.get()); + }); this->channelView_.scrollbar()->setHideThumb(true); this->channelView_.scrollbar()->setHideHighlights(true); @@ -150,24 +155,40 @@ OverlayWindow::OverlayWindow(IndirectChannel channel, } this->applyTheme(); }, - this->holder_); + this->signalHolder_); settings->overlayBackgroundOpacity.connect( [this] { this->channelView_.updateColorTheme(); this->update(); }, - this->holder_, false); + this->signalHolder_, 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); - settings->overlayShadowColor.connect(applyIt, this->holder_, false); + settings->overlayShadowOffsetX.connect(applyIt, this->signalHolder_, false); + settings->overlayShadowOffsetY.connect(applyIt, this->signalHolder_, false); + settings->overlayShadowOpacity.connect(applyIt, this->signalHolder_, false); + settings->overlayShadowRadius.connect(applyIt, this->signalHolder_, false); + settings->overlayShadowColor.connect(applyIt, this->signalHolder_, false); this->addShortcuts(); + this->signalHolder_.managedConnect(getApp()->getHotkeys()->onItemsUpdated, + [this]() { + this->clearShortcuts(); + this->addShortcuts(); + }); + + settings->overlayScaleFactor.connect( + [this] { + this->updateScale(); + }, + this->signalHolder_, false); + std::ignore = this->scaleChanged.connect([this](float /*scale*/) { + this->channelView_.queueLayout(); + }); + this->updateScale(); + this->triggerFirstActivation(); getApp()->getEmotes()->getGIFTimer().registerOpenOverlayWindow(); } @@ -197,6 +218,12 @@ void OverlayWindow::applyTheme() this->update(); } +float OverlayWindow::desiredScale() const +{ + return getSettings()->getClampedUiScale() * + getSettings()->getClampedOverlayScale(); +} + bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event) { #ifndef OVERLAY_NATIVE_MOVE @@ -323,11 +350,9 @@ bool OverlayWindow::nativeEvent(const QByteArray &eventType, void *message, break; default: - return QWidget::nativeEvent(eventType, message, result); + return BaseWindow::nativeEvent(eventType, message, result); } - QWidget::nativeEvent(eventType, message, result); - return returnValue; } @@ -464,23 +489,68 @@ void OverlayWindow::triggerFirstActivation() void OverlayWindow::addShortcuts() { - auto [seq, allOverlays] = toggleIntertiaShortcut(); - if (seq.isEmpty()) - { - return; - } + HotkeyController::HotkeyMap actions{ + {"zoom", + [](const std::vector &arguments) -> QString { + if (arguments.size() == 0) + { + qCWarning(chatterinoHotkeys) + << "zoom shortcut called without arguments. Takes " + "only " + "one argument: \"in\", \"out\", or \"reset\""; + return "zoom shortcut called without arguments. Takes " + "only " + "one argument: \"in\", \"out\", or \"reset\""; + } + auto change = 0.0F; + const auto &direction = arguments.at(0); + if (direction == "reset") + { + getSettings()->uiScale.setValue(1); + return ""; + } - auto *shortcut = new QShortcut(seq, this); - if (allOverlays) + if (direction == u"in") + { + change = 0.1F; + } + else if (direction == u"out") + { + change = -0.1F; + } + else + { + qCWarning(chatterinoHotkeys) + << "Invalid zoom direction, use \"in\", \"out\", or " + "\"reset\""; + return "Invalid zoom direction, use \"in\", \"out\", or " + "\"reset\""; + } + getSettings()->setClampedOverlayScale( + getSettings()->getClampedOverlayScale() + change); + return {}; + }}, + }; + + this->shortcuts_ = getApp()->getHotkeys()->shortcutsForCategory( + HotkeyCategory::Window, actions, this); + + auto [seq, allOverlays] = toggleIntertiaShortcut(); + if (!seq.isEmpty()) { - QObject::connect(shortcut, &QShortcut::activated, this, [] { - getApp()->getWindows()->toggleAllOverlayInertia(); - }); - } - else - { - QObject::connect(shortcut, &QShortcut::activated, this, - &OverlayWindow::toggleInertia); + auto *inertiaShortcut = new QShortcut(seq, this); + if (allOverlays) + { + QObject::connect(inertiaShortcut, &QShortcut::activated, this, [] { + getApp()->getWindows()->toggleAllOverlayInertia(); + }); + } + else + { + QObject::connect(inertiaShortcut, &QShortcut::activated, this, + &OverlayWindow::toggleInertia); + } + this->shortcuts_.push_back(inertiaShortcut); } } diff --git a/src/widgets/OverlayWindow.hpp b/src/widgets/OverlayWindow.hpp index f1c61362..42da931e 100644 --- a/src/widgets/OverlayWindow.hpp +++ b/src/widgets/OverlayWindow.hpp @@ -1,6 +1,7 @@ #pragma once #include "common/Channel.hpp" +#include "widgets/BaseWindow.hpp" #include "widgets/helper/ChannelView.hpp" #include "widgets/helper/OverlayInteraction.hpp" @@ -17,7 +18,7 @@ class QGraphicsDropShadowEffect; namespace chatterino { -class OverlayWindow : public QWidget +class OverlayWindow : public BaseWindow { Q_OBJECT public: @@ -52,11 +53,13 @@ protected: NativeResult *result) override; #endif + void addShortcuts() override; + + float desiredScale() const override; + private: void triggerFirstActivation(); - void addShortcuts(); - void startInteraction(); void startShortInteraction(); void endInteraction(); @@ -70,7 +73,6 @@ private: #endif IndirectChannel channel_; - pajlada::Signals::SignalHolder holder_; ChannelView channelView_; QGraphicsDropShadowEffect *dropShadow_; diff --git a/src/widgets/settingspages/GeneralPage.cpp b/src/widgets/settingspages/GeneralPage.cpp index a257ccec..a056119a 100644 --- a/src/widgets/settingspages/GeneralPage.cpp +++ b/src/widgets/settingspages/GeneralPage.cpp @@ -45,6 +45,11 @@ const QString META_KEY = u"Windows"_s; const QString META_KEY = u"Meta"_s; #endif +const QStringList ZOOM_LEVELS = { + "0.5x", "0.6x", "0.7x", "0.8x", "0.9x", "Default", "1.2x", "1.4x", + "1.6x", "1.8x", "2x", "2.33x", "2.66x", "3x", "3.5x", "4x", +}; + void addKeyboardModifierSetting(GeneralPageView &layout, const QString &title, EnumSetting &setting) { @@ -184,10 +189,7 @@ void GeneralPage::initLayout(GeneralPageView &layout) return fuzzyToInt(args.value, 10); }); layout.addDropdown( - "Zoom", - {"0.5x", "0.6x", "0.7x", "0.8x", "0.9x", "Default", "1.2x", "1.4x", - "1.6x", "1.8x", "2x", "2.33x", "2.66x", "3x", "3.5x", "4x"}, - s.uiScale, + "Zoom", ZOOM_LEVELS, s.uiScale, [](auto val) { if (val == 1) { @@ -1030,6 +1032,21 @@ void GeneralPage::initLayout(GeneralPageView &layout) ->addTo(layout); layout.addSubtitle("Overlay"); + layout.addDropdown( + "Zoom factor", ZOOM_LEVELS, s.overlayScaleFactor, + [](auto val) { + if (val == 1) + { + return u"Default"_s; + } + return QString::number(val) + 'x'; + }, + [](const auto &args) { + return fuzzyToFloat(args.value, 1.F); + }, + true, + "The final scale of the messages in the overlay is computed by " + "multiplying this zoom factor with the global zoom level."); layout.addIntInput( "Background opacity (0-255)", s.overlayBackgroundOpacity, 0, 255, 1, "Controls the opacity of the (possibly alternating) background behind "