Add transparent overlay window (#4746)
This commit is contained in:
@@ -551,6 +551,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/TooltipEntryWidget.cpp
|
||||
@@ -638,6 +640,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
|
||||
|
||||
@@ -181,6 +181,20 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
|
||||
{"showSearch", ActionDefinition{"Search current channel"}},
|
||||
{"showGlobalSearch", ActionDefinition{"Search all channels"}},
|
||||
{"debug", ActionDefinition{"Show debug popup"}},
|
||||
{"popupOverlay", ActionDefinition{"New overlay popup"}},
|
||||
{"toggleOverlayInertia",
|
||||
ActionDefinition{
|
||||
.displayName = "Toggle overlay click-through",
|
||||
.argumentDescription = "<target popup: this or thisOrAll or all>",
|
||||
.minCountArguments = 1,
|
||||
.maxCountArguments = 1,
|
||||
.possibleArguments{
|
||||
{"This", {"this"}},
|
||||
{"All", {"all"}},
|
||||
{"This or all", {"thisOrAll"}},
|
||||
},
|
||||
.argumentsPrompt = "Target popup:",
|
||||
}},
|
||||
}},
|
||||
{HotkeyCategory::SplitInput,
|
||||
{
|
||||
@@ -259,7 +273,8 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
|
||||
{"moveTab",
|
||||
ActionDefinition{
|
||||
"Move tab",
|
||||
"<where to move the tab: next, previous, or new index of tab>",
|
||||
"<where to move the tab: next, previous, or new index of "
|
||||
"tab>",
|
||||
1,
|
||||
}},
|
||||
{"newSplit", ActionDefinition{"Create a new split"}},
|
||||
|
||||
@@ -403,6 +403,12 @@ void HotkeyController::addDefaults(std::set<QString> &addedHotkeys)
|
||||
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
|
||||
QKeySequence("F10"), "debug",
|
||||
std::vector<QString>(), "open debug popup");
|
||||
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
|
||||
QKeySequence("Ctrl+Alt+N"), "popupOverlay", {},
|
||||
"open overlay");
|
||||
this->tryAddDefault(
|
||||
addedHotkeys, HotkeyCategory::Split, QKeySequence("Ctrl+Shift+U"),
|
||||
"toggleOverlayInertia", {"all"}, "toggle overlay click-through");
|
||||
}
|
||||
|
||||
// split input
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
#include <QString>
|
||||
|
||||
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;
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -23,6 +23,7 @@ namespace chatterino {
|
||||
class Channel;
|
||||
struct MessageLayoutContainer;
|
||||
class MessageLayoutElement;
|
||||
struct MessageLayoutContext;
|
||||
|
||||
class Image;
|
||||
using ImagePtr = std::shared_ptr<Image>;
|
||||
@@ -184,7 +185,7 @@ public:
|
||||
void addFlags(MessageElementFlags flags);
|
||||
|
||||
virtual void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElementFlags flags) = 0;
|
||||
const MessageLayoutContext &ctx) = 0;
|
||||
|
||||
virtual QJsonObject toJson() const;
|
||||
|
||||
@@ -205,7 +206,7 @@ public:
|
||||
ImageElement(ImagePtr image, MessageElementFlags flags);
|
||||
|
||||
void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElementFlags flags) override;
|
||||
const MessageLayoutContext &ctx) override;
|
||||
|
||||
QJsonObject toJson() const override;
|
||||
|
||||
@@ -221,7 +222,7 @@ public:
|
||||
MessageElementFlags flags);
|
||||
|
||||
void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElementFlags flags) override;
|
||||
const MessageLayoutContext &ctx) override;
|
||||
|
||||
QJsonObject toJson() const override;
|
||||
|
||||
@@ -241,7 +242,7 @@ public:
|
||||
~TextElement() override = default;
|
||||
|
||||
void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElementFlags flags) override;
|
||||
const MessageLayoutContext &ctx) override;
|
||||
|
||||
QJsonObject toJson() const override;
|
||||
|
||||
@@ -262,7 +263,7 @@ public:
|
||||
~SingleLineTextElement() override = default;
|
||||
|
||||
void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElementFlags flags) override;
|
||||
const MessageLayoutContext &ctx) override;
|
||||
|
||||
QJsonObject toJson() const override;
|
||||
|
||||
@@ -298,7 +299,7 @@ public:
|
||||
LinkElement &operator=(LinkElement &&) = delete;
|
||||
|
||||
void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElementFlags flags) override;
|
||||
const MessageLayoutContext &ctx) override;
|
||||
|
||||
Link getLink() const override;
|
||||
|
||||
@@ -338,7 +339,7 @@ public:
|
||||
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 @@ public:
|
||||
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 @@ public:
|
||||
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 @@ public:
|
||||
BadgeElement(const EmotePtr &data, MessageElementFlags flags_);
|
||||
|
||||
void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElementFlags flags_) override;
|
||||
const MessageLayoutContext &ctx) override;
|
||||
|
||||
EmotePtr getEmote() const;
|
||||
|
||||
@@ -494,7 +495,7 @@ public:
|
||||
~TimestampElement() override = default;
|
||||
|
||||
void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElementFlags flags) override;
|
||||
const MessageLayoutContext &ctx) override;
|
||||
|
||||
TextElement *formatTime(const QTime &time);
|
||||
|
||||
@@ -514,7 +515,7 @@ public:
|
||||
TwitchModerationElement();
|
||||
|
||||
void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElementFlags flags) override;
|
||||
const MessageLayoutContext &ctx) override;
|
||||
|
||||
QJsonObject toJson() const override;
|
||||
};
|
||||
@@ -526,7 +527,7 @@ public:
|
||||
LinebreakElement(MessageElementFlags flags);
|
||||
|
||||
void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElementFlags flags) override;
|
||||
const MessageLayoutContext &ctx) override;
|
||||
|
||||
QJsonObject toJson() const override;
|
||||
};
|
||||
@@ -538,7 +539,7 @@ public:
|
||||
ScalingImageElement(ImageSet images, MessageElementFlags flags);
|
||||
|
||||
void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElementFlags flags) override;
|
||||
const MessageLayoutContext &ctx) override;
|
||||
|
||||
QJsonObject toJson() const override;
|
||||
|
||||
@@ -552,7 +553,7 @@ public:
|
||||
ReplyCurveElement();
|
||||
|
||||
void addToContainer(MessageLayoutContainer &container,
|
||||
MessageElementFlags flags) override;
|
||||
const MessageLayoutContext &ctx) override;
|
||||
|
||||
QJsonObject toJson() const override;
|
||||
};
|
||||
|
||||
@@ -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())
|
||||
@@ -201,10 +200,15 @@ MessagePaintResult MessageLayout::paint(const MessagePaintContext &ctx)
|
||||
{
|
||||
MessagePaintResult result;
|
||||
|
||||
QPixmap *pixmap = this->ensureBuffer(ctx.painter, ctx.canvasWidth);
|
||||
QPixmap *pixmap = this->ensureBuffer(ctx.painter, ctx.canvasWidth,
|
||||
ctx.messageColors.hasTransparency);
|
||||
|
||||
if (!this->bufferValid_)
|
||||
{
|
||||
if (ctx.messageColors.hasTransparency)
|
||||
{
|
||||
pixmap->fill(Qt::transparent);
|
||||
}
|
||||
this->updateBuffer(pixmap, ctx);
|
||||
}
|
||||
|
||||
@@ -278,7 +282,7 @@ MessagePaintResult MessageLayout::paint(const MessagePaintContext &ctx)
|
||||
return result;
|
||||
}
|
||||
|
||||
QPixmap *MessageLayout::ensureBuffer(QPainter &painter, int width)
|
||||
QPixmap *MessageLayout::ensureBuffer(QPainter &painter, int width, bool clear)
|
||||
{
|
||||
if (this->buffer_ != nullptr)
|
||||
{
|
||||
@@ -292,6 +296,11 @@ QPixmap *MessageLayout::ensureBuffer(QPainter &painter, int width)
|
||||
painter.device()->devicePixelRatioF()));
|
||||
this->buffer_->setDevicePixelRatio(painter.device()->devicePixelRatioF());
|
||||
|
||||
if (clear)
|
||||
{
|
||||
this->buffer_->fill(Qt::transparent);
|
||||
}
|
||||
|
||||
this->bufferValid_ = false;
|
||||
DebugCount::increase("message drawing buffers");
|
||||
return this->buffer_.get();
|
||||
@@ -313,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) &&
|
||||
|
||||
@@ -18,6 +18,7 @@ struct Selection;
|
||||
struct MessageLayoutContainer;
|
||||
class MessageLayoutElement;
|
||||
struct MessagePaintContext;
|
||||
struct MessageLayoutContext;
|
||||
|
||||
enum class MessageElementFlag : int64_t;
|
||||
using MessageElementFlags = FlagsEnum<MessageElementFlag>;
|
||||
@@ -56,8 +57,7 @@ public:
|
||||
|
||||
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,11 +112,11 @@ public:
|
||||
|
||||
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
|
||||
QPixmap *ensureBuffer(QPainter &painter, int width);
|
||||
QPixmap *ensureBuffer(QPainter &painter, int width, bool clear);
|
||||
|
||||
// variables
|
||||
const MessagePtr message_;
|
||||
|
||||
@@ -3,21 +3,44 @@
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/Theme.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
void MessageColors::applyTheme(Theme *theme)
|
||||
void MessageColors::applyTheme(Theme *theme, bool isOverlay,
|
||||
int backgroundOpacity)
|
||||
{
|
||||
this->regular = theme->messages.backgrounds.regular;
|
||||
this->alternate = theme->messages.backgrounds.alternate;
|
||||
auto applyColors = [this](const auto &src) {
|
||||
this->regularBg = src.backgrounds.regular;
|
||||
this->alternateBg = src.backgrounds.alternate;
|
||||
|
||||
this->disabled = theme->messages.disabled;
|
||||
this->selection = theme->messages.selection;
|
||||
this->system = theme->messages.textColors.system;
|
||||
this->disabled = src.disabled;
|
||||
this->selection = src.selection;
|
||||
|
||||
this->regularText = src.textColors.regular;
|
||||
this->linkText = src.textColors.link;
|
||||
this->systemText = 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;
|
||||
|
||||
this->focusedLastMessageLine = theme->tabs.selected.backgrounds.regular;
|
||||
this->unfocusedLastMessageLine = theme->tabs.selected.backgrounds.unfocused;
|
||||
|
||||
this->hasTransparency =
|
||||
this->regularBg.alpha() != 255 || this->alternateBg.alpha() != 255;
|
||||
}
|
||||
|
||||
void MessagePreferences::connectSettings(Settings *settings,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "messages/MessageElement.hpp"
|
||||
|
||||
#include <QColor>
|
||||
#include <QPainter>
|
||||
|
||||
@@ -16,18 +18,27 @@ struct Selection;
|
||||
|
||||
// TODO: Figure out if this could be a subset of Theme instead (e.g. Theme::MessageColors)
|
||||
struct MessageColors {
|
||||
QColor regular;
|
||||
QColor alternate;
|
||||
QColor channelBackground;
|
||||
|
||||
// true if any of the background colors have transparency
|
||||
bool hasTransparency = false;
|
||||
|
||||
QColor regularBg;
|
||||
QColor alternateBg;
|
||||
|
||||
QColor disabled;
|
||||
QColor selection;
|
||||
QColor system;
|
||||
|
||||
QColor regularText;
|
||||
QColor linkText;
|
||||
QColor systemText;
|
||||
|
||||
QColor messageSeperator;
|
||||
|
||||
QColor focusedLastMessageLine;
|
||||
QColor unfocusedLastMessageLine;
|
||||
|
||||
void applyTheme(Theme *theme);
|
||||
void applyTheme(Theme *theme, bool isOverlay, int backgroundOpacity);
|
||||
};
|
||||
|
||||
// TODO: Explore if we can let settings own this
|
||||
@@ -72,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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -183,6 +183,18 @@ public:
|
||||
// BoolSetting useCustomWindowFrame = {"/appearance/useCustomWindowFrame",
|
||||
// false};
|
||||
|
||||
IntSetting overlayBackgroundOpacity = {
|
||||
"/appearance/overlay/backgroundOpacity", 50};
|
||||
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};
|
||||
IntSetting overlayShadowRadius = {"/appearance/overlay/shadowRadius", 8};
|
||||
|
||||
// Badges
|
||||
BoolSetting showBadgesGlobalAuthority = {
|
||||
"/appearance/badges/GlobalAuthority", true};
|
||||
@@ -523,6 +535,7 @@ public:
|
||||
|
||||
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
||||
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
||||
IntSetting overlayKnowledgeLevel = {"/misc/overlayKnowledgeLevel", 0};
|
||||
|
||||
BoolSetting loadTwitchMessageHistoryOnConnect = {
|
||||
"/misc/twitch/loadMessageHistoryOnConnect", true};
|
||||
|
||||
+44
-17
@@ -11,6 +11,7 @@
|
||||
#include <QDir>
|
||||
#include <QElapsedTimer>
|
||||
#include <QFile>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QSet>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
@@ -118,33 +119,56 @@ void parseTabs(const QJsonObject &tabs, const QJsonObject &tabsFallback,
|
||||
tabsFallback["selected"_L1].toObject(), theme.tabs.selected);
|
||||
}
|
||||
|
||||
void parseTextColors(const QJsonObject &textColors,
|
||||
const QJsonObject &textColorsFallback, 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,
|
||||
const QJsonObject &backgroundsFallback,
|
||||
auto &messages)
|
||||
{
|
||||
parseColor(messages, backgrounds, regular);
|
||||
parseColor(messages, backgrounds, alternate);
|
||||
}
|
||||
|
||||
void parseMessages(const QJsonObject &messages,
|
||||
const QJsonObject &messagesFallback,
|
||||
chatterino::Theme &theme)
|
||||
{
|
||||
{
|
||||
const auto textColors = messages["textColors"_L1].toObject();
|
||||
const auto textColorsFallback =
|
||||
messagesFallback["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();
|
||||
const auto backgroundsFallback =
|
||||
messagesFallback["backgrounds"_L1].toObject();
|
||||
parseColor(theme.messages, backgrounds, regular);
|
||||
parseColor(theme.messages, backgrounds, alternate);
|
||||
}
|
||||
parseTextColors(messages["textColors"_L1].toObject(),
|
||||
messagesFallback["textColors"_L1].toObject(),
|
||||
theme.messages);
|
||||
parseMessageBackgrounds(messages["backgrounds"_L1].toObject(),
|
||||
messagesFallback["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,
|
||||
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(),
|
||||
theme.overlayMessages);
|
||||
parseColor(theme, overlayMessages, disabled);
|
||||
parseColor(theme, overlayMessages, selection);
|
||||
parseColor(theme, overlayMessages, background);
|
||||
}
|
||||
|
||||
void parseScrollbars(const QJsonObject &scrollbars,
|
||||
const QJsonObject &scrollbarsFallback,
|
||||
chatterino::Theme &theme)
|
||||
@@ -198,6 +222,9 @@ void parseColors(const QJsonObject &root, const QJsonObject &fallbackTheme,
|
||||
fallbackColors["tabs"_L1].toObject(), theme);
|
||||
parseMessages(colors["messages"_L1].toObject(),
|
||||
fallbackColors["messages"_L1].toObject(), theme);
|
||||
parseOverlayMessages(colors["overlayMessages"_L1].toObject(),
|
||||
fallbackColors["overlayMessages"_L1].toObject(),
|
||||
theme);
|
||||
parseScrollbars(colors["scrollbars"_L1].toObject(),
|
||||
fallbackColors["scrollbars"_L1].toObject(), theme);
|
||||
parseSplits(colors["splits"_L1].toObject(),
|
||||
|
||||
+24
-12
@@ -62,6 +62,19 @@ public:
|
||||
} line;
|
||||
};
|
||||
|
||||
struct TextColors {
|
||||
QColor regular;
|
||||
QColor caret;
|
||||
QColor link;
|
||||
QColor system;
|
||||
QColor chatPlaceholder;
|
||||
};
|
||||
|
||||
struct MessageBackgrounds {
|
||||
QColor regular;
|
||||
QColor alternate;
|
||||
};
|
||||
|
||||
QColor accent{"#00aeef"};
|
||||
|
||||
/// WINDOW
|
||||
@@ -84,18 +97,8 @@ public:
|
||||
|
||||
/// 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;
|
||||
@@ -104,6 +107,15 @@ public:
|
||||
QColor highlightAnimationEnd;
|
||||
} messages;
|
||||
|
||||
struct {
|
||||
TextColors textColors;
|
||||
MessageBackgrounds backgrounds;
|
||||
|
||||
QColor disabled;
|
||||
QColor selection;
|
||||
QColor background;
|
||||
} overlayMessages;
|
||||
|
||||
/// SCROLLBAR
|
||||
struct {
|
||||
QColor background;
|
||||
|
||||
@@ -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,37 @@ void WindowManager::queueSave()
|
||||
this->saveTimer->start(10s);
|
||||
}
|
||||
|
||||
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->setInert(anyNonInert);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void WindowManager::encodeTab(SplitContainer *tab, bool isSelected,
|
||||
QJsonObject &obj)
|
||||
{
|
||||
|
||||
@@ -128,6 +128,9 @@ public:
|
||||
// again
|
||||
void queueSave();
|
||||
|
||||
/// Toggles the inertia in all open overlay windows
|
||||
void toggleAllOverlayInertia();
|
||||
|
||||
/// Signals
|
||||
pajlada::Signals::NoArgSignal gifRepaintRequested;
|
||||
|
||||
|
||||
@@ -1633,4 +1633,20 @@ void SplitNotebook::select(QWidget *page, bool focusPage)
|
||||
this->Notebook::select(page, focusPage);
|
||||
}
|
||||
|
||||
void SplitNotebook::forEachSplit(const std::function<void(Split *)> &cb)
|
||||
{
|
||||
for (const auto &item : this->items())
|
||||
{
|
||||
auto *page = dynamic_cast<SplitContainer *>(item.page);
|
||||
if (!page)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (auto *split : page->getSplits())
|
||||
{
|
||||
cb(split);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
|
||||
@@ -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 @@ public:
|
||||
|
||||
void addNotebookActionsToMenu(QMenu *menu) override;
|
||||
|
||||
void forEachSplit(const std::function<void(Split *)> &cb);
|
||||
|
||||
/**
|
||||
* Toggles between the "Show all tabs" and "Hide all tabs" tab visibility states
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,530 @@
|
||||
#include "widgets/OverlayWindow.hpp"
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/FlagsEnum.hpp"
|
||||
#include "common/Literals.hpp"
|
||||
#include "controllers/hotkeys/HotkeyController.hpp"
|
||||
#include "singletons/Settings.hpp"
|
||||
#include "singletons/WindowManager.hpp"
|
||||
#include "widgets/BaseWidget.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
#include "widgets/helper/InvisibleSizeGrip.hpp"
|
||||
#include "widgets/Scrollbar.hpp"
|
||||
#include "widgets/splits/Split.hpp"
|
||||
|
||||
#include <QBoxLayout>
|
||||
#include <QCursor>
|
||||
#include <QGraphicsEffect>
|
||||
#include <QGridLayout>
|
||||
#include <QKeySequence>
|
||||
#include <QSizeGrip>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# include <Windows.h>
|
||||
# include <windowsx.h>
|
||||
|
||||
// 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 { // NOLINT(performance-enum-size)
|
||||
None = 0,
|
||||
// User opened the overlay at least once
|
||||
Activation = 1 << 0,
|
||||
};
|
||||
|
||||
bool hasKnowledge(Knowledge knowledge)
|
||||
{
|
||||
FlagsEnum<Knowledge> current(static_cast<Knowledge>(
|
||||
getSettings()->overlayKnowledgeLevel.getValue()));
|
||||
return current.has(knowledge);
|
||||
}
|
||||
|
||||
void acquireKnowledge(Knowledge knowledge)
|
||||
{
|
||||
FlagsEnum<Knowledge> current(static_cast<Knowledge>(
|
||||
getSettings()->overlayKnowledgeLevel.getValue()));
|
||||
current.set(knowledge);
|
||||
getSettings()->overlayKnowledgeLevel =
|
||||
static_cast<std::underlying_type_t<Knowledge>>(current.value());
|
||||
}
|
||||
|
||||
/// Returns [seq?, toggleAllOverlays]
|
||||
std::pair<QKeySequence, bool> toggleIntertiaShortcut()
|
||||
{
|
||||
auto seq = getApp()->getHotkeys()->getDisplaySequence(
|
||||
HotkeyCategory::Split, u"toggleOverlayInertia"_s, {{u"this"_s}});
|
||||
if (!seq.isEmpty())
|
||||
{
|
||||
return {seq, false};
|
||||
}
|
||||
seq = getApp()->getHotkeys()->getDisplaySequence(
|
||||
HotkeyCategory::Split, u"toggleOverlayInertia"_s, {{u"thisOrAll"_s}});
|
||||
if (!seq.isEmpty())
|
||||
{
|
||||
return {seq, false};
|
||||
}
|
||||
return {
|
||||
getApp()->getHotkeys()->getDisplaySequence(HotkeyCategory::Split,
|
||||
u"toggleOverlayInertia"_s),
|
||||
true,
|
||||
};
|
||||
}
|
||||
|
||||
} // 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)
|
||||
, interaction_(this)
|
||||
{
|
||||
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);
|
||||
this->interaction_.attach(grid);
|
||||
#ifndef OVERLAY_NATIVE_MOVE
|
||||
grid->addWidget(new InvisibleSizeGrip(this), 0, 0,
|
||||
Qt::AlignBottom | Qt::AlignRight);
|
||||
#endif
|
||||
|
||||
// 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->channelView_.installEventFilter(this);
|
||||
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->channelView_.scrollbar()->setShowThumb(false);
|
||||
|
||||
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);
|
||||
|
||||
auto *settings = getSettings();
|
||||
settings->enableOverlayShadow.connect(
|
||||
[this](bool value) {
|
||||
if (value)
|
||||
{
|
||||
this->dropShadow_ = new QGraphicsDropShadowEffect;
|
||||
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->update();
|
||||
},
|
||||
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);
|
||||
settings->overlayShadowColor.connect(applyIt, this->holder_, false);
|
||||
|
||||
this->addShortcuts();
|
||||
this->triggerFirstActivation();
|
||||
}
|
||||
|
||||
OverlayWindow::~OverlayWindow()
|
||||
{
|
||||
#ifdef Q_OS_WIN
|
||||
::DestroyCursor(this->sizeAllCursor_);
|
||||
#endif
|
||||
}
|
||||
|
||||
void OverlayWindow::applyTheme()
|
||||
{
|
||||
auto *settings = getSettings();
|
||||
|
||||
if (this->dropShadow_)
|
||||
{
|
||||
QColor shadowColor(settings->overlayShadowColor.getValue());
|
||||
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);
|
||||
}
|
||||
this->update();
|
||||
}
|
||||
|
||||
bool OverlayWindow::eventFilter(QObject * /*object*/, QEvent *event)
|
||||
{
|
||||
#ifndef OVERLAY_NATIVE_MOVE
|
||||
switch (event->type())
|
||||
{
|
||||
case QEvent::MouseButtonPress: {
|
||||
auto *evt = dynamic_cast<QMouseEvent *>(event);
|
||||
this->moving_ = true;
|
||||
this->moveOrigin_ = evt->globalPos();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case QEvent::MouseButtonRelease: {
|
||||
if (this->moving_)
|
||||
{
|
||||
this->moving_ = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case QEvent::MouseMove: {
|
||||
auto *evt = dynamic_cast<QMouseEvent *>(event);
|
||||
if (this->moving_)
|
||||
{
|
||||
auto newPos = evt->globalPos() - this->moveOrigin_;
|
||||
this->move(newPos + this->pos());
|
||||
this->moveOrigin_ = evt->globalPos();
|
||||
return true;
|
||||
}
|
||||
if (this->interaction_.isInteracting())
|
||||
{
|
||||
this->setOverrideCursor(Qt::SizeAllCursor);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
(void)event;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void OverlayWindow::setOverrideCursor(const QCursor &cursor)
|
||||
{
|
||||
this->channelView_.setCursor(cursor);
|
||||
this->setCursor(cursor);
|
||||
}
|
||||
|
||||
bool OverlayWindow::isInert() const
|
||||
{
|
||||
return this->inert_;
|
||||
}
|
||||
|
||||
void OverlayWindow::toggleInertia()
|
||||
{
|
||||
this->setInert(!this->inert_);
|
||||
}
|
||||
|
||||
void OverlayWindow::enterEvent(EnterEvent * /*event*/)
|
||||
{
|
||||
#ifndef OVERLAY_NATIVE_MOVE
|
||||
this->startInteraction();
|
||||
#endif
|
||||
}
|
||||
|
||||
void OverlayWindow::leaveEvent(QEvent * /*event*/)
|
||||
{
|
||||
#ifndef OVERLAY_NATIVE_MOVE
|
||||
this->endInteraction();
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
bool OverlayWindow::nativeEvent(const QByteArray &eventType, void *message,
|
||||
NativeResult *result)
|
||||
{
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
MSG *msg = reinterpret_cast<MSG *>(message);
|
||||
|
||||
bool returnValue = false;
|
||||
|
||||
switch (msg->message)
|
||||
{
|
||||
# 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);
|
||||
}
|
||||
|
||||
QWidget::nativeEvent(eventType, message, result);
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
void OverlayWindow::handleNCHITTEST(MSG *msg, NativeResult *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)
|
||||
{
|
||||
*result = HTLEFT;
|
||||
}
|
||||
// right border
|
||||
if (x >= rect.right() - borderWidth)
|
||||
{
|
||||
*result = HTRIGHT;
|
||||
}
|
||||
|
||||
// bottom border
|
||||
if (y >= rect.bottom() - borderWidth)
|
||||
{
|
||||
*result = HTBOTTOM;
|
||||
}
|
||||
// top border
|
||||
if (y < rect.top() + borderWidth)
|
||||
{
|
||||
*result = HTTOP;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
if (*result == 0)
|
||||
{
|
||||
auto *closeButton = this->interaction_.closeButton();
|
||||
if (closeButton->isVisible() && closeButton->geometry().contains(point))
|
||||
{
|
||||
*result = HTCLIENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
*result = HTCAPTION;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void OverlayWindow::triggerFirstActivation()
|
||||
{
|
||||
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. "_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
|
||||
"<br><br>"
|
||||
"By default, the overlay is interactive. ";
|
||||
|
||||
auto [actualShortcut, allOverlays] = 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"<br><br>"_s
|
||||
"This is still an early version and some features are "
|
||||
"missing. Please provide feedback <a "
|
||||
"href=\"https://github.com/Chatterino/chatterino2/"
|
||||
"discussions\">on GitHub</a>.";
|
||||
|
||||
auto *box =
|
||||
new QMessageBox(QMessageBox::Information, u"Chatterino - Overlay"_s,
|
||||
welcomeText, QMessageBox::Ok, this);
|
||||
box->open();
|
||||
}
|
||||
|
||||
void OverlayWindow::addShortcuts()
|
||||
{
|
||||
auto [seq, allOverlays] = toggleIntertiaShortcut();
|
||||
if (seq.isEmpty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto *shortcut = new QShortcut(seq, this);
|
||||
if (allOverlays)
|
||||
{
|
||||
QObject::connect(shortcut, &QShortcut::activated, this, [] {
|
||||
getApp()->getWindows()->toggleAllOverlayInertia();
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
QObject::connect(shortcut, &QShortcut::activated, this,
|
||||
&OverlayWindow::toggleInertia);
|
||||
}
|
||||
}
|
||||
|
||||
void OverlayWindow::startInteraction()
|
||||
{
|
||||
if (this->inert_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->interaction_.startInteraction();
|
||||
this->shortInteraction_.stop();
|
||||
}
|
||||
|
||||
void OverlayWindow::startShortInteraction()
|
||||
{
|
||||
if (this->inert_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->interaction_.startInteraction();
|
||||
this->shortInteraction_.start();
|
||||
}
|
||||
|
||||
void OverlayWindow::endInteraction()
|
||||
{
|
||||
this->interaction_.endInteraction();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
if (inert)
|
||||
{
|
||||
if (this->channelView_.scrollbar()->isVisible())
|
||||
{
|
||||
this->channelView_.scrollbar()->scrollToBottom();
|
||||
}
|
||||
this->interaction_.hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
this->interaction_.show();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace chatterino
|
||||
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/Channel.hpp"
|
||||
#include "widgets/helper/ChannelView.hpp"
|
||||
#include "widgets/helper/OverlayInteraction.hpp"
|
||||
|
||||
#include <pajlada/signals/scoped-connection.hpp>
|
||||
#include <pajlada/signals/signalholder.hpp>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
# include <QtGui/qwindowdefs_win.h>
|
||||
#endif
|
||||
|
||||
class QGraphicsDropShadowEffect;
|
||||
|
||||
namespace chatterino {
|
||||
|
||||
class OverlayWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
OverlayWindow(IndirectChannel channel);
|
||||
~OverlayWindow() override;
|
||||
OverlayWindow(const OverlayWindow &) = delete;
|
||||
OverlayWindow(OverlayWindow &&) = delete;
|
||||
OverlayWindow &operator=(const OverlayWindow &) = delete;
|
||||
OverlayWindow &operator=(OverlayWindow &&) = delete;
|
||||
|
||||
void setOverrideCursor(const QCursor &cursor);
|
||||
|
||||
bool isInert() const;
|
||||
void setInert(bool inert);
|
||||
void toggleInertia();
|
||||
|
||||
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 enterEvent(EnterEvent *event) override;
|
||||
void leaveEvent(QEvent *event) override;
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
bool nativeEvent(const QByteArray &eventType, void *message,
|
||||
NativeResult *result) override;
|
||||
#endif
|
||||
|
||||
private:
|
||||
void triggerFirstActivation();
|
||||
|
||||
void addShortcuts();
|
||||
|
||||
void startInteraction();
|
||||
void startShortInteraction();
|
||||
void endInteraction();
|
||||
|
||||
void applyTheme();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
void handleNCHITTEST(MSG *msg, NativeResult *result);
|
||||
|
||||
HCURSOR sizeAllCursor_;
|
||||
#endif
|
||||
|
||||
IndirectChannel channel_;
|
||||
pajlada::Signals::SignalHolder holder_;
|
||||
|
||||
ChannelView channelView_;
|
||||
QGraphicsDropShadowEffect *dropShadow_;
|
||||
|
||||
bool inert_ = false;
|
||||
|
||||
bool moving_ = false;
|
||||
QPoint moveOrigin_;
|
||||
|
||||
OverlayInteraction interaction_;
|
||||
QTimer shortInteraction_;
|
||||
};
|
||||
|
||||
} // namespace chatterino
|
||||
+25
-11
@@ -285,18 +285,21 @@ void Scrollbar::paintEvent(QPaintEvent * /*event*/)
|
||||
bool enableElevatedMessageHighlights =
|
||||
getSettings()->enableElevatedMessageHighlight;
|
||||
|
||||
this->thumbRect_.setX(xOffset);
|
||||
if (this->showThumb_)
|
||||
{
|
||||
this->thumbRect_.setX(xOffset);
|
||||
|
||||
// mouse over thumb
|
||||
if (this->mouseDownLocation_ == MouseLocation::InsideThumb)
|
||||
{
|
||||
painter.fillRect(this->thumbRect_,
|
||||
this->theme->scrollbars.thumbSelected);
|
||||
}
|
||||
// mouse not over thumb
|
||||
else
|
||||
{
|
||||
painter.fillRect(this->thumbRect_, this->theme->scrollbars.thumb);
|
||||
// mouse over thumb
|
||||
if (this->mouseDownLocation_ == MouseLocation::InsideThumb)
|
||||
{
|
||||
painter.fillRect(this->thumbRect_,
|
||||
this->theme->scrollbars.thumbSelected);
|
||||
}
|
||||
// mouse not over thumb
|
||||
else
|
||||
{
|
||||
painter.fillRect(this->thumbRect_, this->theme->scrollbars.thumb);
|
||||
}
|
||||
}
|
||||
|
||||
// draw highlights
|
||||
@@ -449,6 +452,17 @@ void Scrollbar::updateScroll()
|
||||
this->update();
|
||||
}
|
||||
|
||||
void Scrollbar::setShowThumb(bool showThumb)
|
||||
{
|
||||
if (this->showThumb_ == showThumb)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
this->showThumb_ = showThumb;
|
||||
this->update();
|
||||
}
|
||||
|
||||
Scrollbar::MouseLocation Scrollbar::locationOfMouseEvent(
|
||||
QMouseEvent *event) const
|
||||
{
|
||||
|
||||
@@ -127,6 +127,8 @@ public:
|
||||
/// unaffected by simultaneous shifts of minimum and maximum.
|
||||
qreal getRelativeCurrentValue() const;
|
||||
|
||||
void setShowThumb(bool showthumb);
|
||||
|
||||
// offset the desired value without breaking smooth scolling
|
||||
void offset(qreal value);
|
||||
pajlada::Signals::NoArgSignal &getCurrentValueChanged();
|
||||
@@ -169,6 +171,7 @@ private:
|
||||
boost::circular_buffer<ScrollbarHighlight> highlights_;
|
||||
|
||||
bool atBottom_{false};
|
||||
bool showThumb_ = true;
|
||||
|
||||
MouseLocation mouseOverLocation_ = MouseLocation::Outside;
|
||||
MouseLocation mouseDownLocation_ = MouseLocation::Outside;
|
||||
|
||||
@@ -362,7 +362,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_);
|
||||
}
|
||||
@@ -450,6 +451,11 @@ void ChannelView::initializeSignals()
|
||||
});
|
||||
}
|
||||
|
||||
Scrollbar *ChannelView::scrollbar()
|
||||
{
|
||||
return this->scrollBar_;
|
||||
}
|
||||
|
||||
bool ChannelView::pausable() const
|
||||
{
|
||||
return pausable_;
|
||||
@@ -574,7 +580,19 @@ void ChannelView::themeChangedEvent()
|
||||
|
||||
this->setupHighlightAnimationColors();
|
||||
this->queueLayout();
|
||||
this->messageColors_.applyTheme(getTheme());
|
||||
this->messageColors_.applyTheme(getTheme(), this->isOverlay_,
|
||||
getSettings()->overlayBackgroundOpacity);
|
||||
}
|
||||
|
||||
void ChannelView::updateColorTheme()
|
||||
{
|
||||
this->themeChangedEvent();
|
||||
}
|
||||
|
||||
void ChannelView::setIsOverlay(bool isOverlay)
|
||||
{
|
||||
this->isOverlay_ = isOverlay;
|
||||
this->themeChangedEvent();
|
||||
}
|
||||
|
||||
void ChannelView::setupHighlightAnimationColors()
|
||||
@@ -680,9 +698,15 @@ void ChannelView::layoutVisibleMessages(
|
||||
const auto &message = messages[i];
|
||||
|
||||
redrawRequired |= message->layout(
|
||||
layoutWidth, this->scale(),
|
||||
this->scale() * static_cast<float>(this->devicePixelRatio()),
|
||||
flags, this->bufferInvalidationQueued_);
|
||||
{
|
||||
.messageColors = this->messageColors_,
|
||||
.flags = flags,
|
||||
.width = layoutWidth,
|
||||
.scale = this->scale(),
|
||||
.imageScale = this->scale() *
|
||||
static_cast<float>(this->devicePixelRatio()),
|
||||
},
|
||||
this->bufferInvalidationQueued_);
|
||||
|
||||
y += message->getHeight();
|
||||
}
|
||||
@@ -717,8 +741,14 @@ void ChannelView::updateScrollbar(
|
||||
auto *message = messages[i].get();
|
||||
|
||||
message->layout(
|
||||
layoutWidth, this->scale(),
|
||||
this->scale() * static_cast<float>(this->devicePixelRatio()), flags,
|
||||
{
|
||||
.messageColors = this->messageColors_,
|
||||
.flags = flags,
|
||||
.width = layoutWidth,
|
||||
.scale = this->scale(),
|
||||
.imageScale = this->scale() *
|
||||
static_cast<float>(this->devicePixelRatio()),
|
||||
},
|
||||
false);
|
||||
|
||||
h -= message->getHeight();
|
||||
@@ -1486,7 +1516,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, event->rect());
|
||||
@@ -1705,10 +1735,16 @@ void ChannelView::wheelEvent(QWheelEvent *event)
|
||||
else
|
||||
{
|
||||
snapshot[i - 1]->layout(
|
||||
this->getLayoutWidth(), this->scale(),
|
||||
this->scale() *
|
||||
static_cast<float>(this->devicePixelRatio()),
|
||||
this->getFlags(), false);
|
||||
{
|
||||
.messageColors = this->messageColors_,
|
||||
.flags = this->getFlags(),
|
||||
.width = this->getLayoutWidth(),
|
||||
.scale = this->scale(),
|
||||
.imageScale =
|
||||
this->scale() *
|
||||
static_cast<float>(this->devicePixelRatio()),
|
||||
},
|
||||
false);
|
||||
scrollFactor = 1;
|
||||
currentScrollLeft = snapshot[i - 1]->getHeight();
|
||||
}
|
||||
@@ -1742,10 +1778,16 @@ void ChannelView::wheelEvent(QWheelEvent *event)
|
||||
else
|
||||
{
|
||||
snapshot[i + 1]->layout(
|
||||
this->getLayoutWidth(), this->scale(),
|
||||
this->scale() *
|
||||
static_cast<float>(this->devicePixelRatio()),
|
||||
this->getFlags(), false);
|
||||
{
|
||||
.messageColors = this->messageColors_,
|
||||
.flags = this->getFlags(),
|
||||
.width = this->getLayoutWidth(),
|
||||
.scale = this->scale(),
|
||||
.imageScale =
|
||||
this->scale() *
|
||||
static_cast<float>(this->devicePixelRatio()),
|
||||
},
|
||||
false);
|
||||
|
||||
scrollFactor = 1;
|
||||
currentScrollLeft = snapshot[i + 1]->getHeight();
|
||||
|
||||
@@ -202,6 +202,16 @@ public:
|
||||
*/
|
||||
bool mayContainMessage(const MessagePtr &message);
|
||||
|
||||
void updateColorTheme();
|
||||
|
||||
/// @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();
|
||||
|
||||
pajlada::Signals::Signal<QMouseEvent *> mouseDown;
|
||||
pajlada::Signals::NoArgSignal selectionChanged;
|
||||
pajlada::Signals::Signal<HighlightState> tabHighlightRequested;
|
||||
@@ -377,6 +387,8 @@ private:
|
||||
|
||||
bool onlyUpdateEmotes_ = false;
|
||||
|
||||
bool isOverlay_ = false;
|
||||
|
||||
// Mouse event variables
|
||||
bool isLeftMouseDown_ = false;
|
||||
bool isRightMouseDown_ = false;
|
||||
|
||||
@@ -97,8 +97,8 @@ void MessageView::paintEvent(QPaintEvent * /*event*/)
|
||||
|
||||
void MessageView::themeChangedEvent()
|
||||
{
|
||||
this->messageColors_.applyTheme(getTheme());
|
||||
this->messageColors_.regular = getTheme()->splits.input.background;
|
||||
this->messageColors_.applyTheme(getTheme(), false, 255);
|
||||
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<float>(this->devicePixelRatio()),
|
||||
MESSAGE_FLAGS, false);
|
||||
{
|
||||
.messageColors = this->messageColors_,
|
||||
.flags = MESSAGE_FLAGS,
|
||||
.width = this->width_,
|
||||
.scale = this->scale(),
|
||||
.imageScale =
|
||||
this->scale() * static_cast<float>(this->devicePixelRatio()),
|
||||
},
|
||||
false);
|
||||
|
||||
if (updateRequired)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "widgets/helper/OverlayInteraction.hpp"
|
||||
|
||||
#include "common/Literals.hpp"
|
||||
#include "widgets/OverlayWindow.hpp"
|
||||
|
||||
#include <QGridLayout>
|
||||
|
||||
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
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "widgets/helper/TitlebarButton.hpp"
|
||||
|
||||
#include <QPropertyAnimation>
|
||||
#include <QWidget>
|
||||
|
||||
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
|
||||
@@ -969,6 +969,40 @@ void GeneralPage::initLayout(GeneralPageView &layout)
|
||||
layout.addCheckbox("Use custom FrankerFaceZ VIP badges",
|
||||
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.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 "
|
||||
"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");
|
||||
|
||||
layout.addSubtitle("Miscellaneous");
|
||||
|
||||
if (supportsIncognitoLinks())
|
||||
|
||||
@@ -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"
|
||||
@@ -765,6 +766,47 @@ void Split::addShortcuts()
|
||||
}
|
||||
return "";
|
||||
}},
|
||||
{"popupOverlay",
|
||||
[this](const auto &) -> QString {
|
||||
this->showOverlayWindow();
|
||||
return {};
|
||||
}},
|
||||
{"toggleOverlayInertia",
|
||||
[this](const auto &args) -> QString {
|
||||
if (args.empty())
|
||||
{
|
||||
return "No arguments provided to toggleOverlayInertia "
|
||||
"(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()->toggleAllOverlayInertia();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
if (arg == "all")
|
||||
{
|
||||
getApp()->getWindows()->toggleAllOverlayInertia();
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}},
|
||||
};
|
||||
|
||||
this->shortcuts_ = getApp()->getHotkeys()->shortcutsForCategory(
|
||||
@@ -1104,6 +1146,20 @@ void Split::popup()
|
||||
window.show();
|
||||
}
|
||||
|
||||
OverlayWindow *Split::overlayWindow()
|
||||
{
|
||||
return this->overlayWindow_.data();
|
||||
}
|
||||
|
||||
void Split::showOverlayWindow()
|
||||
{
|
||||
if (!this->overlayWindow_)
|
||||
{
|
||||
this->overlayWindow_ = new OverlayWindow(this->getIndirectChannel());
|
||||
}
|
||||
this->overlayWindow_->show();
|
||||
}
|
||||
|
||||
void Split::clear()
|
||||
{
|
||||
this->view_->clearMessages();
|
||||
|
||||
@@ -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 @@ public:
|
||||
// This is called on window focus lost
|
||||
void unpause();
|
||||
|
||||
OverlayWindow *overlayWindow();
|
||||
|
||||
static pajlada::Signals::Signal<Qt::KeyboardModifiers>
|
||||
modifierStatusChanged;
|
||||
static Qt::KeyboardModifiers modifierStatus;
|
||||
@@ -158,6 +161,8 @@ private:
|
||||
SplitInput *const input_;
|
||||
SplitOverlay *const overlay_;
|
||||
|
||||
QPointer<OverlayWindow> overlayWindow_;
|
||||
|
||||
QPointer<SelectChannelDialog> selectChannelDialog_;
|
||||
|
||||
pajlada::Signals::Connection channelIDChangedConnection_;
|
||||
@@ -179,6 +184,7 @@ public slots:
|
||||
void explainMoving();
|
||||
void explainSplitting();
|
||||
void popup();
|
||||
void showOverlayWindow();
|
||||
void clear();
|
||||
void openInBrowser();
|
||||
void openModViewInBrowser();
|
||||
|
||||
@@ -390,6 +390,9 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
||||
menu->addAction(
|
||||
"Popup", this->split_, &Split::popup,
|
||||
h->getDisplaySequence(HotkeyCategory::Window, "popup", {{"split"}}));
|
||||
menu->addAction(
|
||||
"Popup overlay", this->split_, &Split::showOverlayWindow,
|
||||
h->getDisplaySequence(HotkeyCategory::Split, "popupOverlay"));
|
||||
menu->addAction(
|
||||
"Search", this->split_,
|
||||
[this] {
|
||||
|
||||
Reference in New Issue
Block a user