mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Add transparent overlay window (#4746)
This commit is contained in:
parent
9ba7ef324d
commit
afa8067a20
|
@ -5,6 +5,7 @@
|
||||||
- Major: Add option to show pronouns in user card. (#5442, #5583)
|
- Major: Add option to show pronouns in user card. (#5442, #5583)
|
||||||
- Major: Release plugins alpha. (#5288)
|
- Major: Release plugins alpha. (#5288)
|
||||||
- Major: Improve high-DPI support on Windows. (#4868, #5391)
|
- Major: Improve high-DPI support on Windows. (#4868, #5391)
|
||||||
|
- Major: Added transparent overlay window (default keybind: <kbd>CTRL</kbd> + <kbd>ALT</kbd> + <kbd>N</kbd>). (#4746)
|
||||||
- Minor: Removed the Ctrl+Shift+L hotkey for toggling the "live only" tab visibility state. (#5530)
|
- Minor: Removed the Ctrl+Shift+L hotkey for toggling the "live only" tab visibility state. (#5530)
|
||||||
- Minor: Add support for Shared Chat messages. Shared chat messages can be filtered with the `flags.shared` filter variable, or with search using `is:shared`. Some messages like subscriptions are filtered on purpose to avoid confusion for the broadcaster. If you have both channels participating in Shared Chat open, only one of the message triggering your highlight will trigger. (#5606, #5625)
|
- Minor: Add support for Shared Chat messages. Shared chat messages can be filtered with the `flags.shared` filter variable, or with search using `is:shared`. Some messages like subscriptions are filtered on purpose to avoid confusion for the broadcaster. If you have both channels participating in Shared Chat open, only one of the message triggering your highlight will trigger. (#5606, #5625)
|
||||||
- Minor: Moved tab visibility control to a submenu, without any toggle actions. (#5530)
|
- Minor: Moved tab visibility control to a submenu, without any toggle actions. (#5530)
|
||||||
|
|
|
@ -215,6 +215,47 @@
|
||||||
"text": { "$ref": "#/definitions/qt-color" }
|
"text": { "$ref": "#/definitions/qt-color" }
|
||||||
},
|
},
|
||||||
"required": ["backgrounds", "line", "text"]
|
"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",
|
"type": "object",
|
||||||
|
@ -229,37 +270,12 @@
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"backgrounds": {
|
"backgrounds": { "$ref": "#/definitions/message-backgrounds" },
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": false,
|
|
||||||
"properties": {
|
|
||||||
"alternate": { "$ref": "#/definitions/qt-color" },
|
|
||||||
"regular": { "$ref": "#/definitions/qt-color" }
|
|
||||||
},
|
|
||||||
"required": ["alternate", "regular"]
|
|
||||||
},
|
|
||||||
"disabled": { "$ref": "#/definitions/qt-color" },
|
"disabled": { "$ref": "#/definitions/qt-color" },
|
||||||
"highlightAnimationEnd": { "$ref": "#/definitions/qt-color" },
|
"highlightAnimationEnd": { "$ref": "#/definitions/qt-color" },
|
||||||
"highlightAnimationStart": { "$ref": "#/definitions/qt-color" },
|
"highlightAnimationStart": { "$ref": "#/definitions/qt-color" },
|
||||||
"selection": { "$ref": "#/definitions/qt-color" },
|
"selection": { "$ref": "#/definitions/qt-color" },
|
||||||
"textColors": {
|
"textColors": { "$ref": "#/definitions/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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"backgrounds",
|
"backgrounds",
|
||||||
|
@ -270,6 +286,27 @@
|
||||||
"textColors"
|
"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",
|
||||||
|
"description": "Note: The alpha value is ignored (set through the settings)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"backgrounds",
|
||||||
|
"disabled",
|
||||||
|
"selection",
|
||||||
|
"textColors",
|
||||||
|
"background"
|
||||||
|
]
|
||||||
|
},
|
||||||
"scrollbars": {
|
"scrollbars": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
|
@ -376,6 +413,7 @@
|
||||||
"required": [
|
"required": [
|
||||||
"accent",
|
"accent",
|
||||||
"messages",
|
"messages",
|
||||||
|
"overlayMessages",
|
||||||
"scrollbars",
|
"scrollbars",
|
||||||
"splits",
|
"splits",
|
||||||
"tabs",
|
"tabs",
|
||||||
|
|
|
@ -22,6 +22,22 @@
|
||||||
"system": "#8c7f7f"
|
"system": "#8c7f7f"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"overlayMessages": {
|
||||||
|
"backgrounds": {
|
||||||
|
"alternate": "#32000000",
|
||||||
|
"regular": "transparent"
|
||||||
|
},
|
||||||
|
"disabled": "#64000000",
|
||||||
|
"selection": "#40ffffff",
|
||||||
|
"textColors": {
|
||||||
|
"caret": "#ffffff",
|
||||||
|
"chatPlaceholder": "#5d5555",
|
||||||
|
"link": "#4286f4",
|
||||||
|
"regular": "#ffffff",
|
||||||
|
"system": "#8c7f7f"
|
||||||
|
},
|
||||||
|
"background": "#000"
|
||||||
|
},
|
||||||
"scrollbars": {
|
"scrollbars": {
|
||||||
"background": "#00000000",
|
"background": "#00000000",
|
||||||
"thumb": "#4d4d4d",
|
"thumb": "#4d4d4d",
|
||||||
|
|
|
@ -22,6 +22,22 @@
|
||||||
"system": "#8c7f7f"
|
"system": "#8c7f7f"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"overlayMessages": {
|
||||||
|
"backgrounds": {
|
||||||
|
"alternate": "#32000000",
|
||||||
|
"regular": "transparent"
|
||||||
|
},
|
||||||
|
"disabled": "#64000000",
|
||||||
|
"selection": "#40ffffff",
|
||||||
|
"textColors": {
|
||||||
|
"caret": "#ffffff",
|
||||||
|
"chatPlaceholder": "#5d5555",
|
||||||
|
"link": "#4286f4",
|
||||||
|
"regular": "#ffffff",
|
||||||
|
"system": "#8c7f7f"
|
||||||
|
},
|
||||||
|
"background": "#000"
|
||||||
|
},
|
||||||
"scrollbars": {
|
"scrollbars": {
|
||||||
"background": "#00000000",
|
"background": "#00000000",
|
||||||
"thumb": "#575757",
|
"thumb": "#575757",
|
||||||
|
|
|
@ -22,6 +22,22 @@
|
||||||
"system": "#8c7f7f"
|
"system": "#8c7f7f"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"overlayMessages": {
|
||||||
|
"backgrounds": {
|
||||||
|
"alternate": "#32000000",
|
||||||
|
"regular": "transparent"
|
||||||
|
},
|
||||||
|
"disabled": "#64000000",
|
||||||
|
"selection": "#40ffffff",
|
||||||
|
"textColors": {
|
||||||
|
"caret": "#ffffff",
|
||||||
|
"chatPlaceholder": "#5d5555",
|
||||||
|
"link": "#4286f4",
|
||||||
|
"regular": "#ffffff",
|
||||||
|
"system": "#8c7f7f"
|
||||||
|
},
|
||||||
|
"background": "#333"
|
||||||
|
},
|
||||||
"scrollbars": {
|
"scrollbars": {
|
||||||
"background": "#00000000",
|
"background": "#00000000",
|
||||||
"thumb": "#a8a8a8",
|
"thumb": "#a8a8a8",
|
||||||
|
|
|
@ -22,6 +22,22 @@
|
||||||
"system": "#8c7f7f"
|
"system": "#8c7f7f"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"overlayMessages": {
|
||||||
|
"backgrounds": {
|
||||||
|
"alternate": "#32000000",
|
||||||
|
"regular": "transparent"
|
||||||
|
},
|
||||||
|
"disabled": "#64000000",
|
||||||
|
"selection": "#40ffffff",
|
||||||
|
"textColors": {
|
||||||
|
"caret": "#ffffff",
|
||||||
|
"chatPlaceholder": "#5d5555",
|
||||||
|
"link": "#4286f4",
|
||||||
|
"regular": "#ffffff",
|
||||||
|
"system": "#8c7f7f"
|
||||||
|
},
|
||||||
|
"background": "#333"
|
||||||
|
},
|
||||||
"scrollbars": {
|
"scrollbars": {
|
||||||
"background": "#00000000",
|
"background": "#00000000",
|
||||||
"thumb": "#b3b3b3",
|
"thumb": "#b3b3b3",
|
||||||
|
|
|
@ -551,6 +551,8 @@ set(SOURCE_FILES
|
||||||
widgets/Label.hpp
|
widgets/Label.hpp
|
||||||
widgets/Notebook.cpp
|
widgets/Notebook.cpp
|
||||||
widgets/Notebook.hpp
|
widgets/Notebook.hpp
|
||||||
|
widgets/OverlayWindow.cpp
|
||||||
|
widgets/OverlayWindow.hpp
|
||||||
widgets/Scrollbar.cpp
|
widgets/Scrollbar.cpp
|
||||||
widgets/Scrollbar.hpp
|
widgets/Scrollbar.hpp
|
||||||
widgets/TooltipEntryWidget.cpp
|
widgets/TooltipEntryWidget.cpp
|
||||||
|
@ -638,6 +640,8 @@ set(SOURCE_FILES
|
||||||
widgets/helper/NotebookButton.hpp
|
widgets/helper/NotebookButton.hpp
|
||||||
widgets/helper/NotebookTab.cpp
|
widgets/helper/NotebookTab.cpp
|
||||||
widgets/helper/NotebookTab.hpp
|
widgets/helper/NotebookTab.hpp
|
||||||
|
widgets/helper/OverlayInteraction.cpp
|
||||||
|
widgets/helper/OverlayInteraction.hpp
|
||||||
widgets/helper/RegExpItemDelegate.cpp
|
widgets/helper/RegExpItemDelegate.cpp
|
||||||
widgets/helper/RegExpItemDelegate.hpp
|
widgets/helper/RegExpItemDelegate.hpp
|
||||||
widgets/helper/ResizingTextEdit.cpp
|
widgets/helper/ResizingTextEdit.cpp
|
||||||
|
|
|
@ -181,6 +181,20 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
|
||||||
{"showSearch", ActionDefinition{"Search current channel"}},
|
{"showSearch", ActionDefinition{"Search current channel"}},
|
||||||
{"showGlobalSearch", ActionDefinition{"Search all channels"}},
|
{"showGlobalSearch", ActionDefinition{"Search all channels"}},
|
||||||
{"debug", ActionDefinition{"Show debug popup"}},
|
{"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,
|
{HotkeyCategory::SplitInput,
|
||||||
{
|
{
|
||||||
|
@ -259,7 +273,8 @@ inline const std::map<HotkeyCategory, ActionDefinitionMap> actionNames{
|
||||||
{"moveTab",
|
{"moveTab",
|
||||||
ActionDefinition{
|
ActionDefinition{
|
||||||
"Move tab",
|
"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,
|
1,
|
||||||
}},
|
}},
|
||||||
{"newSplit", ActionDefinition{"Create a new split"}},
|
{"newSplit", ActionDefinition{"Create a new split"}},
|
||||||
|
|
|
@ -403,6 +403,12 @@ void HotkeyController::addDefaults(std::set<QString> &addedHotkeys)
|
||||||
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
|
this->tryAddDefault(addedHotkeys, HotkeyCategory::Split,
|
||||||
QKeySequence("F10"), "debug",
|
QKeySequence("F10"), "debug",
|
||||||
std::vector<QString>(), "open debug popup");
|
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
|
// split input
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#include "messages/MessageColor.hpp"
|
#include "messages/MessageColor.hpp"
|
||||||
|
|
||||||
#include "singletons/Theme.hpp"
|
#include "messages/layouts/MessageLayoutContext.hpp"
|
||||||
|
|
||||||
namespace chatterino {
|
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_)
|
switch (this->type_)
|
||||||
{
|
{
|
||||||
case Type::Custom:
|
case Type::Custom:
|
||||||
return this->customColor_;
|
return this->customColor_;
|
||||||
case Type::Text:
|
case Type::Text:
|
||||||
return themeManager.messages.textColors.regular;
|
return colors.regularText;
|
||||||
case Type::System:
|
case Type::System:
|
||||||
return themeManager.messages.textColors.system;
|
return colors.systemText;
|
||||||
case Type::Link:
|
case Type::Link:
|
||||||
return themeManager.messages.textColors.link;
|
return colors.linkText;
|
||||||
}
|
}
|
||||||
|
|
||||||
static QColor _default;
|
static QColor _default;
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
class Theme;
|
|
||||||
|
struct MessageColors;
|
||||||
|
|
||||||
struct MessageColor {
|
struct MessageColor {
|
||||||
enum Type { Custom, Text, Link, System };
|
enum Type { Custom, Text, Link, System };
|
||||||
|
@ -12,7 +13,7 @@ struct MessageColor {
|
||||||
MessageColor(const QColor &color);
|
MessageColor(const QColor &color);
|
||||||
MessageColor(Type type_ = Text);
|
MessageColor(Type type_ = Text);
|
||||||
|
|
||||||
const QColor &getColor(Theme &themeManager) const;
|
const QColor &getColor(const MessageColors &colors) const;
|
||||||
|
|
||||||
QString toString() const;
|
QString toString() const;
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "messages/Emote.hpp"
|
#include "messages/Emote.hpp"
|
||||||
#include "messages/Image.hpp"
|
#include "messages/Image.hpp"
|
||||||
#include "messages/layouts/MessageLayoutContainer.hpp"
|
#include "messages/layouts/MessageLayoutContainer.hpp"
|
||||||
|
#include "messages/layouts/MessageLayoutContext.hpp"
|
||||||
#include "messages/layouts/MessageLayoutElement.hpp"
|
#include "messages/layouts/MessageLayoutElement.hpp"
|
||||||
#include "providers/emoji/Emojis.hpp"
|
#include "providers/emoji/Emojis.hpp"
|
||||||
#include "singletons/Emotes.hpp"
|
#include "singletons/Emotes.hpp"
|
||||||
|
@ -119,9 +120,9 @@ ImageElement::ImageElement(ImagePtr image, MessageElementFlags flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageElement::addToContainer(MessageLayoutContainer &container,
|
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(),
|
auto size = QSize(this->image_->width() * container.getScale(),
|
||||||
this->image_->height() * container.getScale());
|
this->image_->height() * container.getScale());
|
||||||
|
@ -151,9 +152,9 @@ CircularImageElement::CircularImageElement(ImagePtr image, int padding,
|
||||||
}
|
}
|
||||||
|
|
||||||
void CircularImageElement::addToContainer(MessageLayoutContainer &container,
|
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()) *
|
auto imgSize = QSize(this->image_->width(), this->image_->height()) *
|
||||||
container.getScale();
|
container.getScale();
|
||||||
|
@ -192,11 +193,11 @@ EmotePtr EmoteElement::getEmote() const
|
||||||
}
|
}
|
||||||
|
|
||||||
void EmoteElement::addToContainer(MessageLayoutContainer &container,
|
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(
|
auto image = this->emote_->images.getImageOrLoaded(
|
||||||
container.getImageScale());
|
container.getImageScale());
|
||||||
|
@ -217,8 +218,9 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
|
||||||
{
|
{
|
||||||
if (this->textElement_)
|
if (this->textElement_)
|
||||||
{
|
{
|
||||||
this->textElement_->addToContainer(container,
|
auto textCtx = ctx;
|
||||||
MessageElementFlag::Misc);
|
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,
|
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());
|
auto images = this->getLoadedImages(container.getImageScale());
|
||||||
if (images.empty())
|
if (images.empty())
|
||||||
|
@ -291,8 +293,9 @@ void LayeredEmoteElement::addToContainer(MessageLayoutContainer &container,
|
||||||
{
|
{
|
||||||
if (this->textElement_)
|
if (this->textElement_)
|
||||||
{
|
{
|
||||||
this->textElement_->addToContainer(container,
|
auto textCtx = ctx;
|
||||||
MessageElementFlag::Misc);
|
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,
|
void BadgeElement::addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags)
|
const MessageLayoutContext &ctx)
|
||||||
{
|
{
|
||||||
if (flags.hasAny(this->getFlags()))
|
if (ctx.flags.hasAny(this->getFlags()))
|
||||||
{
|
{
|
||||||
auto image =
|
auto image =
|
||||||
this->emote_->images.getImageOrLoaded(container.getImageScale());
|
this->emote_->images.getImageOrLoaded(container.getImageScale());
|
||||||
|
@ -574,11 +577,11 @@ TextElement::TextElement(const QString &text, MessageElementFlags flags,
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextElement::addToContainer(MessageLayoutContainer &container,
|
void TextElement::addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags)
|
const MessageLayoutContext &ctx)
|
||||||
{
|
{
|
||||||
auto *app = getApp();
|
auto *app = getApp();
|
||||||
|
|
||||||
if (flags.hasAny(this->getFlags()))
|
if (ctx.flags.hasAny(this->getFlags()))
|
||||||
{
|
{
|
||||||
QFontMetrics metrics =
|
QFontMetrics metrics =
|
||||||
app->getFonts()->getFontMetrics(this->style_, container.getScale());
|
app->getFonts()->getFontMetrics(this->style_, container.getScale());
|
||||||
|
@ -589,7 +592,7 @@ void TextElement::addToContainer(MessageLayoutContainer &container,
|
||||||
|
|
||||||
auto getTextLayoutElement = [&](QString text, int width,
|
auto getTextLayoutElement = [&](QString text, int width,
|
||||||
bool hasTrailingSpace) {
|
bool hasTrailingSpace) {
|
||||||
auto color = this->color_.getColor(*app->getThemes());
|
auto color = this->color_.getColor(ctx.messageColors);
|
||||||
app->getThemes()->normalizeColor(color);
|
app->getThemes()->normalizeColor(color);
|
||||||
|
|
||||||
auto *e = new TextLayoutElement(
|
auto *e = new TextLayoutElement(
|
||||||
|
@ -697,18 +700,18 @@ SingleLineTextElement::SingleLineTextElement(const QString &text,
|
||||||
}
|
}
|
||||||
|
|
||||||
void SingleLineTextElement::addToContainer(MessageLayoutContainer &container,
|
void SingleLineTextElement::addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags)
|
const MessageLayoutContext &ctx)
|
||||||
{
|
{
|
||||||
auto *app = getApp();
|
auto *app = getApp();
|
||||||
|
|
||||||
if (flags.hasAny(this->getFlags()))
|
if (ctx.flags.hasAny(this->getFlags()))
|
||||||
{
|
{
|
||||||
QFontMetrics metrics =
|
QFontMetrics metrics =
|
||||||
app->getFonts()->getFontMetrics(this->style_, container.getScale());
|
app->getFonts()->getFontMetrics(this->style_, container.getScale());
|
||||||
|
|
||||||
auto getTextLayoutElement = [&](QString text, int width,
|
auto getTextLayoutElement = [&](QString text, int width,
|
||||||
bool hasTrailingSpace) {
|
bool hasTrailingSpace) {
|
||||||
auto color = this->color_.getColor(*app->getThemes());
|
auto color = this->color_.getColor(ctx.messageColors);
|
||||||
app->getThemes()->normalizeColor(color);
|
app->getThemes()->normalizeColor(color);
|
||||||
|
|
||||||
auto *e = new TextLayoutElement(
|
auto *e = new TextLayoutElement(
|
||||||
|
@ -838,11 +841,11 @@ LinkElement::LinkElement(const Parsed &parsed, const QString &fullUrl,
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinkElement::addToContainer(MessageLayoutContainer &container,
|
void LinkElement::addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags)
|
const MessageLayoutContext &ctx)
|
||||||
{
|
{
|
||||||
this->words_ =
|
this->words_ =
|
||||||
getSettings()->lowercaseDomains ? this->lowercase_ : this->original_;
|
getSettings()->lowercaseDomains ? this->lowercase_ : this->original_;
|
||||||
TextElement::addToContainer(container, flags);
|
TextElement::addToContainer(container, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
Link LinkElement::getLink() const
|
Link LinkElement::getLink() const
|
||||||
|
@ -873,7 +876,7 @@ MentionElement::MentionElement(const QString &displayName, QString loginName_,
|
||||||
}
|
}
|
||||||
|
|
||||||
void MentionElement::addToContainer(MessageLayoutContainer &container,
|
void MentionElement::addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags)
|
const MessageLayoutContext &ctx)
|
||||||
{
|
{
|
||||||
if (getSettings()->colorUsernames)
|
if (getSettings()->colorUsernames)
|
||||||
{
|
{
|
||||||
|
@ -893,7 +896,7 @@ void MentionElement::addToContainer(MessageLayoutContainer &container,
|
||||||
this->style_ = FontStyle::ChatMedium;
|
this->style_ = FontStyle::ChatMedium;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextElement::addToContainer(container, flags);
|
TextElement::addToContainer(container, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageElement *MentionElement::setLink(const Link &link)
|
MessageElement *MentionElement::setLink(const Link &link)
|
||||||
|
@ -943,9 +946,9 @@ TimestampElement::TimestampElement(QTime time)
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimestampElement::addToContainer(MessageLayoutContainer &container,
|
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_)
|
if (getSettings()->timestampFormat != this->format_)
|
||||||
{
|
{
|
||||||
|
@ -953,7 +956,7 @@ void TimestampElement::addToContainer(MessageLayoutContainer &container,
|
||||||
this->element_.reset(this->formatTime(this->time_));
|
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,
|
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),
|
QSize size(int(container.getScale() * 16),
|
||||||
int(container.getScale() * 16));
|
int(container.getScale() * 16));
|
||||||
|
@ -1026,9 +1029,9 @@ LinebreakElement::LinebreakElement(MessageElementFlags flags)
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinebreakElement::addToContainer(MessageLayoutContainer &container,
|
void LinebreakElement::addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags)
|
const MessageLayoutContext &ctx)
|
||||||
{
|
{
|
||||||
if (flags.hasAny(this->getFlags()))
|
if (ctx.flags.hasAny(this->getFlags()))
|
||||||
{
|
{
|
||||||
container.breakLine();
|
container.breakLine();
|
||||||
}
|
}
|
||||||
|
@ -1050,9 +1053,9 @@ ScalingImageElement::ScalingImageElement(ImageSet images,
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScalingImageElement::addToContainer(MessageLayoutContainer &container,
|
void ScalingImageElement::addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags)
|
const MessageLayoutContext &ctx)
|
||||||
{
|
{
|
||||||
if (flags.hasAny(this->getFlags()))
|
if (ctx.flags.hasAny(this->getFlags()))
|
||||||
{
|
{
|
||||||
const auto &image =
|
const auto &image =
|
||||||
this->images_.getImageOrLoaded(container.getImageScale());
|
this->images_.getImageOrLoaded(container.getImageScale());
|
||||||
|
@ -1083,14 +1086,14 @@ ReplyCurveElement::ReplyCurveElement()
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReplyCurveElement::addToContainer(MessageLayoutContainer &container,
|
void ReplyCurveElement::addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags)
|
const MessageLayoutContext &ctx)
|
||||||
{
|
{
|
||||||
static const int width = 18; // Overall width
|
static const int width = 18; // Overall width
|
||||||
static const float thickness = 1.5; // Pen width
|
static const float thickness = 1.5; // Pen width
|
||||||
static const int radius = 6; // Radius of the top left corner
|
static const int radius = 6; // Radius of the top left corner
|
||||||
static const int margin = 2; // Top/Left/Bottom margin
|
static const int margin = 2; // Top/Left/Bottom margin
|
||||||
|
|
||||||
if (flags.hasAny(this->getFlags()))
|
if (ctx.flags.hasAny(this->getFlags()))
|
||||||
{
|
{
|
||||||
float scale = container.getScale();
|
float scale = container.getScale();
|
||||||
container.addElement(
|
container.addElement(
|
||||||
|
|
|
@ -23,6 +23,7 @@ namespace chatterino {
|
||||||
class Channel;
|
class Channel;
|
||||||
struct MessageLayoutContainer;
|
struct MessageLayoutContainer;
|
||||||
class MessageLayoutElement;
|
class MessageLayoutElement;
|
||||||
|
struct MessageLayoutContext;
|
||||||
|
|
||||||
class Image;
|
class Image;
|
||||||
using ImagePtr = std::shared_ptr<Image>;
|
using ImagePtr = std::shared_ptr<Image>;
|
||||||
|
@ -184,7 +185,7 @@ public:
|
||||||
void addFlags(MessageElementFlags flags);
|
void addFlags(MessageElementFlags flags);
|
||||||
|
|
||||||
virtual void addToContainer(MessageLayoutContainer &container,
|
virtual void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags) = 0;
|
const MessageLayoutContext &ctx) = 0;
|
||||||
|
|
||||||
virtual QJsonObject toJson() const;
|
virtual QJsonObject toJson() const;
|
||||||
|
|
||||||
|
@ -205,7 +206,7 @@ public:
|
||||||
ImageElement(ImagePtr image, MessageElementFlags flags);
|
ImageElement(ImagePtr image, MessageElementFlags flags);
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
|
|
||||||
QJsonObject toJson() const override;
|
QJsonObject toJson() const override;
|
||||||
|
|
||||||
|
@ -221,7 +222,7 @@ public:
|
||||||
MessageElementFlags flags);
|
MessageElementFlags flags);
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
|
|
||||||
QJsonObject toJson() const override;
|
QJsonObject toJson() const override;
|
||||||
|
|
||||||
|
@ -241,7 +242,7 @@ public:
|
||||||
~TextElement() override = default;
|
~TextElement() override = default;
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
|
|
||||||
QJsonObject toJson() const override;
|
QJsonObject toJson() const override;
|
||||||
|
|
||||||
|
@ -262,7 +263,7 @@ public:
|
||||||
~SingleLineTextElement() override = default;
|
~SingleLineTextElement() override = default;
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
|
|
||||||
QJsonObject toJson() const override;
|
QJsonObject toJson() const override;
|
||||||
|
|
||||||
|
@ -298,7 +299,7 @@ public:
|
||||||
LinkElement &operator=(LinkElement &&) = delete;
|
LinkElement &operator=(LinkElement &&) = delete;
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
|
|
||||||
Link getLink() const override;
|
Link getLink() const override;
|
||||||
|
|
||||||
|
@ -338,7 +339,7 @@ public:
|
||||||
MentionElement &operator=(MentionElement &&) = delete;
|
MentionElement &operator=(MentionElement &&) = delete;
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
|
|
||||||
MessageElement *setLink(const Link &link) override;
|
MessageElement *setLink(const Link &link) override;
|
||||||
Link getLink() const override;
|
Link getLink() const override;
|
||||||
|
@ -369,7 +370,7 @@ public:
|
||||||
const MessageColor &textElementColor = MessageColor::Text);
|
const MessageColor &textElementColor = MessageColor::Text);
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags_) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
EmotePtr getEmote() const;
|
EmotePtr getEmote() const;
|
||||||
|
|
||||||
QJsonObject toJson() const override;
|
QJsonObject toJson() const override;
|
||||||
|
@ -401,7 +402,7 @@ public:
|
||||||
void addEmoteLayer(const Emote &emote);
|
void addEmoteLayer(const Emote &emote);
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
|
|
||||||
// Returns a concatenation of each emote layer's cleaned copy string
|
// Returns a concatenation of each emote layer's cleaned copy string
|
||||||
QString getCleanCopyString() const;
|
QString getCleanCopyString() const;
|
||||||
|
@ -433,7 +434,7 @@ public:
|
||||||
BadgeElement(const EmotePtr &data, MessageElementFlags flags_);
|
BadgeElement(const EmotePtr &data, MessageElementFlags flags_);
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags_) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
|
|
||||||
EmotePtr getEmote() const;
|
EmotePtr getEmote() const;
|
||||||
|
|
||||||
|
@ -494,7 +495,7 @@ public:
|
||||||
~TimestampElement() override = default;
|
~TimestampElement() override = default;
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
|
|
||||||
TextElement *formatTime(const QTime &time);
|
TextElement *formatTime(const QTime &time);
|
||||||
|
|
||||||
|
@ -514,7 +515,7 @@ public:
|
||||||
TwitchModerationElement();
|
TwitchModerationElement();
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
|
|
||||||
QJsonObject toJson() const override;
|
QJsonObject toJson() const override;
|
||||||
};
|
};
|
||||||
|
@ -526,7 +527,7 @@ public:
|
||||||
LinebreakElement(MessageElementFlags flags);
|
LinebreakElement(MessageElementFlags flags);
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
|
|
||||||
QJsonObject toJson() const override;
|
QJsonObject toJson() const override;
|
||||||
};
|
};
|
||||||
|
@ -538,7 +539,7 @@ public:
|
||||||
ScalingImageElement(ImageSet images, MessageElementFlags flags);
|
ScalingImageElement(ImageSet images, MessageElementFlags flags);
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
|
|
||||||
QJsonObject toJson() const override;
|
QJsonObject toJson() const override;
|
||||||
|
|
||||||
|
@ -552,7 +553,7 @@ public:
|
||||||
ReplyCurveElement();
|
ReplyCurveElement();
|
||||||
|
|
||||||
void addToContainer(MessageLayoutContainer &container,
|
void addToContainer(MessageLayoutContainer &container,
|
||||||
MessageElementFlags flags) override;
|
const MessageLayoutContext &ctx) override;
|
||||||
|
|
||||||
QJsonObject toJson() const override;
|
QJsonObject toJson() const override;
|
||||||
};
|
};
|
||||||
|
|
|
@ -68,8 +68,7 @@ int MessageLayout::getWidth() const
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
// return true if redraw is required
|
// return true if redraw is required
|
||||||
bool MessageLayout::layout(int width, float scale, float imageScale,
|
bool MessageLayout::layout(const MessageLayoutContext &ctx,
|
||||||
MessageElementFlags flags,
|
|
||||||
bool shouldInvalidateBuffer)
|
bool shouldInvalidateBuffer)
|
||||||
{
|
{
|
||||||
// BenchmarkGuard benchmark("MessageLayout::layout()");
|
// BenchmarkGuard benchmark("MessageLayout::layout()");
|
||||||
|
@ -77,9 +76,9 @@ bool MessageLayout::layout(int width, float scale, float imageScale,
|
||||||
bool layoutRequired = false;
|
bool layoutRequired = false;
|
||||||
|
|
||||||
// check if width changed
|
// check if width changed
|
||||||
bool widthChanged = width != this->currentLayoutWidth_;
|
bool widthChanged = ctx.width != this->currentLayoutWidth_;
|
||||||
layoutRequired |= widthChanged;
|
layoutRequired |= widthChanged;
|
||||||
this->currentLayoutWidth_ = width;
|
this->currentLayoutWidth_ = ctx.width;
|
||||||
|
|
||||||
// check if layout state changed
|
// check if layout state changed
|
||||||
const auto layoutGeneration = getApp()->getWindows()->getGeneration();
|
const auto layoutGeneration = getApp()->getWindows()->getGeneration();
|
||||||
|
@ -91,18 +90,18 @@ bool MessageLayout::layout(int width, float scale, float imageScale,
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if work mask changed
|
// check if work mask changed
|
||||||
layoutRequired |= this->currentWordFlags_ != flags;
|
layoutRequired |= this->currentWordFlags_ != ctx.flags;
|
||||||
this->currentWordFlags_ = flags; // getSettings()->getWordTypeMask();
|
this->currentWordFlags_ = ctx.flags; // getSettings()->getWordTypeMask();
|
||||||
|
|
||||||
// check if layout was requested manually
|
// check if layout was requested manually
|
||||||
layoutRequired |= this->flags.has(MessageLayoutFlag::RequiresLayout);
|
layoutRequired |= this->flags.has(MessageLayoutFlag::RequiresLayout);
|
||||||
this->flags.unset(MessageLayoutFlag::RequiresLayout);
|
this->flags.unset(MessageLayoutFlag::RequiresLayout);
|
||||||
|
|
||||||
// check if dpi changed
|
// check if dpi changed
|
||||||
layoutRequired |= this->scale_ != scale;
|
layoutRequired |= this->scale_ != ctx.scale;
|
||||||
this->scale_ = scale;
|
this->scale_ = ctx.scale;
|
||||||
layoutRequired |= this->imageScale_ != imageScale;
|
layoutRequired |= this->imageScale_ != ctx.imageScale;
|
||||||
this->imageScale_ = imageScale;
|
this->imageScale_ = ctx.imageScale;
|
||||||
|
|
||||||
if (!layoutRequired)
|
if (!layoutRequired)
|
||||||
{
|
{
|
||||||
|
@ -115,7 +114,7 @@ bool MessageLayout::layout(int width, float scale, float imageScale,
|
||||||
}
|
}
|
||||||
|
|
||||||
int oldHeight = this->container_.getHeight();
|
int oldHeight = this->container_.getHeight();
|
||||||
this->actuallyLayout(width, flags);
|
this->actuallyLayout(ctx);
|
||||||
if (widthChanged || this->container_.getHeight() != oldHeight)
|
if (widthChanged || this->container_.getHeight() != oldHeight)
|
||||||
{
|
{
|
||||||
this->deleteBuffer();
|
this->deleteBuffer();
|
||||||
|
@ -125,7 +124,7 @@ bool MessageLayout::layout(int width, float scale, float imageScale,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageLayout::actuallyLayout(int width, MessageElementFlags flags)
|
void MessageLayout::actuallyLayout(const MessageLayoutContext &ctx)
|
||||||
{
|
{
|
||||||
#ifdef FOURTF
|
#ifdef FOURTF
|
||||||
this->layoutCount_++;
|
this->layoutCount_++;
|
||||||
|
@ -134,7 +133,7 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags)
|
||||||
auto messageFlags = this->message_->flags;
|
auto messageFlags = this->message_->flags;
|
||||||
|
|
||||||
if (this->flags.has(MessageLayoutFlag::Expanded) ||
|
if (this->flags.has(MessageLayoutFlag::Expanded) ||
|
||||||
(flags.has(MessageElementFlag::ModeratorTools) &&
|
(ctx.flags.has(MessageElementFlag::ModeratorTools) &&
|
||||||
!this->message_->flags.has(MessageFlag::Disabled)))
|
!this->message_->flags.has(MessageFlag::Disabled)))
|
||||||
{
|
{
|
||||||
messageFlags.unset(MessageFlag::Collapsed);
|
messageFlags.unset(MessageFlag::Collapsed);
|
||||||
|
@ -143,9 +142,9 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags)
|
||||||
bool hideModerated = getSettings()->hideModerated;
|
bool hideModerated = getSettings()->hideModerated;
|
||||||
bool hideModerationActions = getSettings()->hideModerationActions;
|
bool hideModerationActions = getSettings()->hideModerationActions;
|
||||||
bool hideSimilar = getSettings()->hideSimilar;
|
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);
|
messageFlags);
|
||||||
|
|
||||||
for (const auto &element : this->message_->elements)
|
for (const auto &element : this->message_->elements)
|
||||||
|
@ -177,7 +176,7 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags flags)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
element->addToContainer(this->container_, flags);
|
element->addToContainer(this->container_, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this->height_ != this->container_.getHeight())
|
if (this->height_ != this->container_.getHeight())
|
||||||
|
@ -201,10 +200,15 @@ MessagePaintResult MessageLayout::paint(const MessagePaintContext &ctx)
|
||||||
{
|
{
|
||||||
MessagePaintResult result;
|
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 (!this->bufferValid_)
|
||||||
{
|
{
|
||||||
|
if (ctx.messageColors.hasTransparency)
|
||||||
|
{
|
||||||
|
pixmap->fill(Qt::transparent);
|
||||||
|
}
|
||||||
this->updateBuffer(pixmap, ctx);
|
this->updateBuffer(pixmap, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +282,7 @@ MessagePaintResult MessageLayout::paint(const MessagePaintContext &ctx)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QPixmap *MessageLayout::ensureBuffer(QPainter &painter, int width)
|
QPixmap *MessageLayout::ensureBuffer(QPainter &painter, int width, bool clear)
|
||||||
{
|
{
|
||||||
if (this->buffer_ != nullptr)
|
if (this->buffer_ != nullptr)
|
||||||
{
|
{
|
||||||
|
@ -292,6 +296,11 @@ QPixmap *MessageLayout::ensureBuffer(QPainter &painter, int width)
|
||||||
painter.device()->devicePixelRatioF()));
|
painter.device()->devicePixelRatioF()));
|
||||||
this->buffer_->setDevicePixelRatio(painter.device()->devicePixelRatioF());
|
this->buffer_->setDevicePixelRatio(painter.device()->devicePixelRatioF());
|
||||||
|
|
||||||
|
if (clear)
|
||||||
|
{
|
||||||
|
this->buffer_->fill(Qt::transparent);
|
||||||
|
}
|
||||||
|
|
||||||
this->bufferValid_ = false;
|
this->bufferValid_ = false;
|
||||||
DebugCount::increase("message drawing buffers");
|
DebugCount::increase("message drawing buffers");
|
||||||
return this->buffer_.get();
|
return this->buffer_.get();
|
||||||
|
@ -313,10 +322,10 @@ void MessageLayout::updateBuffer(QPixmap *buffer,
|
||||||
if (ctx.preferences.alternateMessages &&
|
if (ctx.preferences.alternateMessages &&
|
||||||
this->flags.has(MessageLayoutFlag::AlternateBackground))
|
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) &&
|
if (this->message_->flags.has(MessageFlag::ElevatedMessage) &&
|
||||||
|
|
|
@ -18,6 +18,7 @@ struct Selection;
|
||||||
struct MessageLayoutContainer;
|
struct MessageLayoutContainer;
|
||||||
class MessageLayoutElement;
|
class MessageLayoutElement;
|
||||||
struct MessagePaintContext;
|
struct MessagePaintContext;
|
||||||
|
struct MessageLayoutContext;
|
||||||
|
|
||||||
enum class MessageElementFlag : int64_t;
|
enum class MessageElementFlag : int64_t;
|
||||||
using MessageElementFlags = FlagsEnum<MessageElementFlag>;
|
using MessageElementFlags = FlagsEnum<MessageElementFlag>;
|
||||||
|
@ -56,8 +57,7 @@ public:
|
||||||
|
|
||||||
MessageLayoutFlags flags;
|
MessageLayoutFlags flags;
|
||||||
|
|
||||||
bool layout(int width, float scale_, float imageScale,
|
bool layout(const MessageLayoutContext &ctx, bool shouldInvalidateBuffer);
|
||||||
MessageElementFlags flags, bool shouldInvalidateBuffer);
|
|
||||||
|
|
||||||
// Painting
|
// Painting
|
||||||
MessagePaintResult paint(const MessagePaintContext &ctx);
|
MessagePaintResult paint(const MessagePaintContext &ctx);
|
||||||
|
@ -112,11 +112,11 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// methods
|
// methods
|
||||||
void actuallyLayout(int width, MessageElementFlags flags);
|
void actuallyLayout(const MessageLayoutContext &ctx);
|
||||||
void updateBuffer(QPixmap *buffer, const MessagePaintContext &ctx);
|
void updateBuffer(QPixmap *buffer, const MessagePaintContext &ctx);
|
||||||
|
|
||||||
// Create new buffer if required, returning the buffer
|
// Create new buffer if required, returning the buffer
|
||||||
QPixmap *ensureBuffer(QPainter &painter, int width);
|
QPixmap *ensureBuffer(QPainter &painter, int width, bool clear);
|
||||||
|
|
||||||
// variables
|
// variables
|
||||||
const MessagePtr message_;
|
const MessagePtr message_;
|
||||||
|
|
|
@ -3,21 +3,44 @@
|
||||||
#include "singletons/Settings.hpp"
|
#include "singletons/Settings.hpp"
|
||||||
#include "singletons/Theme.hpp"
|
#include "singletons/Theme.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace chatterino {
|
namespace chatterino {
|
||||||
|
|
||||||
void MessageColors::applyTheme(Theme *theme)
|
void MessageColors::applyTheme(Theme *theme, bool isOverlay,
|
||||||
|
int backgroundOpacity)
|
||||||
{
|
{
|
||||||
this->regular = theme->messages.backgrounds.regular;
|
auto applyColors = [this](const auto &src) {
|
||||||
this->alternate = theme->messages.backgrounds.alternate;
|
this->regularBg = src.backgrounds.regular;
|
||||||
|
this->alternateBg = src.backgrounds.alternate;
|
||||||
|
|
||||||
this->disabled = theme->messages.disabled;
|
this->disabled = src.disabled;
|
||||||
this->selection = theme->messages.selection;
|
this->selection = src.selection;
|
||||||
this->system = theme->messages.textColors.system;
|
|
||||||
|
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->messageSeperator = theme->splits.messageSeperator;
|
||||||
|
|
||||||
this->focusedLastMessageLine = theme->tabs.selected.backgrounds.regular;
|
this->focusedLastMessageLine = theme->tabs.selected.backgrounds.regular;
|
||||||
this->unfocusedLastMessageLine = theme->tabs.selected.backgrounds.unfocused;
|
this->unfocusedLastMessageLine = theme->tabs.selected.backgrounds.unfocused;
|
||||||
|
|
||||||
|
this->hasTransparency =
|
||||||
|
this->regularBg.alpha() != 255 || this->alternateBg.alpha() != 255;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessagePreferences::connectSettings(Settings *settings,
|
void MessagePreferences::connectSettings(Settings *settings,
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "messages/MessageElement.hpp"
|
||||||
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
|
|
||||||
|
@ -16,18 +18,27 @@ struct Selection;
|
||||||
|
|
||||||
// TODO: Figure out if this could be a subset of Theme instead (e.g. Theme::MessageColors)
|
// TODO: Figure out if this could be a subset of Theme instead (e.g. Theme::MessageColors)
|
||||||
struct MessageColors {
|
struct MessageColors {
|
||||||
QColor regular;
|
QColor channelBackground;
|
||||||
QColor alternate;
|
|
||||||
|
// true if any of the background colors have transparency
|
||||||
|
bool hasTransparency = false;
|
||||||
|
|
||||||
|
QColor regularBg;
|
||||||
|
QColor alternateBg;
|
||||||
|
|
||||||
QColor disabled;
|
QColor disabled;
|
||||||
QColor selection;
|
QColor selection;
|
||||||
QColor system;
|
|
||||||
|
QColor regularText;
|
||||||
|
QColor linkText;
|
||||||
|
QColor systemText;
|
||||||
|
|
||||||
QColor messageSeperator;
|
QColor messageSeperator;
|
||||||
|
|
||||||
QColor focusedLastMessageLine;
|
QColor focusedLastMessageLine;
|
||||||
QColor unfocusedLastMessageLine;
|
QColor unfocusedLastMessageLine;
|
||||||
|
|
||||||
void applyTheme(Theme *theme);
|
void applyTheme(Theme *theme, bool isOverlay, int backgroundOpacity);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Explore if we can let settings own this
|
// TODO: Explore if we can let settings own this
|
||||||
|
@ -72,4 +83,13 @@ struct MessagePaintContext {
|
||||||
bool isLastReadMessage{};
|
bool isLastReadMessage{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MessageLayoutContext {
|
||||||
|
const MessageColors &messageColors;
|
||||||
|
MessageElementFlags flags;
|
||||||
|
|
||||||
|
int width = 1;
|
||||||
|
float scale = 1;
|
||||||
|
float imageScale = 1;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace chatterino
|
} // namespace chatterino
|
||||||
|
|
|
@ -555,7 +555,7 @@ void TextIconLayoutElement::paint(QPainter &painter,
|
||||||
|
|
||||||
QFont font = app->getFonts()->getFont(FontStyle::Tiny, this->scale);
|
QFont font = app->getFonts()->getFont(FontStyle::Tiny, this->scale);
|
||||||
|
|
||||||
painter.setPen(messageColors.system);
|
painter.setPen(messageColors.systemText);
|
||||||
painter.setFont(font);
|
painter.setFont(font);
|
||||||
|
|
||||||
QTextOption option;
|
QTextOption option;
|
||||||
|
|
|
@ -183,6 +183,18 @@ public:
|
||||||
// BoolSetting useCustomWindowFrame = {"/appearance/useCustomWindowFrame",
|
// BoolSetting useCustomWindowFrame = {"/appearance/useCustomWindowFrame",
|
||||||
// false};
|
// 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
|
// Badges
|
||||||
BoolSetting showBadgesGlobalAuthority = {
|
BoolSetting showBadgesGlobalAuthority = {
|
||||||
"/appearance/badges/GlobalAuthority", true};
|
"/appearance/badges/GlobalAuthority", true};
|
||||||
|
@ -523,6 +535,7 @@ public:
|
||||||
|
|
||||||
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
IntSetting startUpNotification = {"/misc/startUpNotification", 0};
|
||||||
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
QStringSetting currentVersion = {"/misc/currentVersion", ""};
|
||||||
|
IntSetting overlayKnowledgeLevel = {"/misc/overlayKnowledgeLevel", 0};
|
||||||
|
|
||||||
BoolSetting loadTwitchMessageHistoryOnConnect = {
|
BoolSetting loadTwitchMessageHistoryOnConnect = {
|
||||||
"/misc/twitch/loadMessageHistoryOnConnect", true};
|
"/misc/twitch/loadMessageHistoryOnConnect", true};
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QElapsedTimer>
|
#include <QElapsedTimer>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
#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);
|
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,
|
void parseMessages(const QJsonObject &messages,
|
||||||
const QJsonObject &messagesFallback,
|
const QJsonObject &messagesFallback,
|
||||||
chatterino::Theme &theme)
|
chatterino::Theme &theme)
|
||||||
{
|
{
|
||||||
{
|
parseTextColors(messages["textColors"_L1].toObject(),
|
||||||
const auto textColors = messages["textColors"_L1].toObject();
|
messagesFallback["textColors"_L1].toObject(),
|
||||||
const auto textColorsFallback =
|
theme.messages);
|
||||||
messagesFallback["textColors"_L1].toObject();
|
parseMessageBackgrounds(messages["backgrounds"_L1].toObject(),
|
||||||
parseColor(theme.messages, textColors, regular);
|
messagesFallback["backgrounds"_L1].toObject(),
|
||||||
parseColor(theme.messages, textColors, caret);
|
theme.messages);
|
||||||
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);
|
|
||||||
}
|
|
||||||
parseColor(theme, messages, disabled);
|
parseColor(theme, messages, disabled);
|
||||||
parseColor(theme, messages, selection);
|
parseColor(theme, messages, selection);
|
||||||
parseColor(theme, messages, highlightAnimationStart);
|
parseColor(theme, messages, highlightAnimationStart);
|
||||||
parseColor(theme, messages, highlightAnimationEnd);
|
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,
|
void parseScrollbars(const QJsonObject &scrollbars,
|
||||||
const QJsonObject &scrollbarsFallback,
|
const QJsonObject &scrollbarsFallback,
|
||||||
chatterino::Theme &theme)
|
chatterino::Theme &theme)
|
||||||
|
@ -198,6 +222,9 @@ void parseColors(const QJsonObject &root, const QJsonObject &fallbackTheme,
|
||||||
fallbackColors["tabs"_L1].toObject(), theme);
|
fallbackColors["tabs"_L1].toObject(), theme);
|
||||||
parseMessages(colors["messages"_L1].toObject(),
|
parseMessages(colors["messages"_L1].toObject(),
|
||||||
fallbackColors["messages"_L1].toObject(), theme);
|
fallbackColors["messages"_L1].toObject(), theme);
|
||||||
|
parseOverlayMessages(colors["overlayMessages"_L1].toObject(),
|
||||||
|
fallbackColors["overlayMessages"_L1].toObject(),
|
||||||
|
theme);
|
||||||
parseScrollbars(colors["scrollbars"_L1].toObject(),
|
parseScrollbars(colors["scrollbars"_L1].toObject(),
|
||||||
fallbackColors["scrollbars"_L1].toObject(), theme);
|
fallbackColors["scrollbars"_L1].toObject(), theme);
|
||||||
parseSplits(colors["splits"_L1].toObject(),
|
parseSplits(colors["splits"_L1].toObject(),
|
||||||
|
|
|
@ -62,6 +62,19 @@ public:
|
||||||
} line;
|
} line;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TextColors {
|
||||||
|
QColor regular;
|
||||||
|
QColor caret;
|
||||||
|
QColor link;
|
||||||
|
QColor system;
|
||||||
|
QColor chatPlaceholder;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct MessageBackgrounds {
|
||||||
|
QColor regular;
|
||||||
|
QColor alternate;
|
||||||
|
};
|
||||||
|
|
||||||
QColor accent{"#00aeef"};
|
QColor accent{"#00aeef"};
|
||||||
|
|
||||||
/// WINDOW
|
/// WINDOW
|
||||||
|
@ -84,18 +97,8 @@ public:
|
||||||
|
|
||||||
/// MESSAGES
|
/// MESSAGES
|
||||||
struct {
|
struct {
|
||||||
struct {
|
TextColors textColors;
|
||||||
QColor regular;
|
MessageBackgrounds backgrounds;
|
||||||
QColor caret;
|
|
||||||
QColor link;
|
|
||||||
QColor system;
|
|
||||||
QColor chatPlaceholder;
|
|
||||||
} textColors;
|
|
||||||
|
|
||||||
struct {
|
|
||||||
QColor regular;
|
|
||||||
QColor alternate;
|
|
||||||
} backgrounds;
|
|
||||||
|
|
||||||
QColor disabled;
|
QColor disabled;
|
||||||
QColor selection;
|
QColor selection;
|
||||||
|
@ -104,6 +107,15 @@ public:
|
||||||
QColor highlightAnimationEnd;
|
QColor highlightAnimationEnd;
|
||||||
} messages;
|
} messages;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
TextColors textColors;
|
||||||
|
MessageBackgrounds backgrounds;
|
||||||
|
|
||||||
|
QColor disabled;
|
||||||
|
QColor selection;
|
||||||
|
QColor background;
|
||||||
|
} overlayMessages;
|
||||||
|
|
||||||
/// SCROLLBAR
|
/// SCROLLBAR
|
||||||
struct {
|
struct {
|
||||||
QColor background;
|
QColor background;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include "widgets/FramelessEmbedWindow.hpp"
|
#include "widgets/FramelessEmbedWindow.hpp"
|
||||||
#include "widgets/helper/NotebookTab.hpp"
|
#include "widgets/helper/NotebookTab.hpp"
|
||||||
#include "widgets/Notebook.hpp"
|
#include "widgets/Notebook.hpp"
|
||||||
|
#include "widgets/OverlayWindow.hpp"
|
||||||
#include "widgets/splits/Split.hpp"
|
#include "widgets/splits/Split.hpp"
|
||||||
#include "widgets/splits/SplitContainer.hpp"
|
#include "widgets/splits/SplitContainer.hpp"
|
||||||
#include "widgets/Window.hpp"
|
#include "widgets/Window.hpp"
|
||||||
|
@ -544,6 +545,37 @@ void WindowManager::queueSave()
|
||||||
this->saveTimer->start(10s);
|
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,
|
void WindowManager::encodeTab(SplitContainer *tab, bool isSelected,
|
||||||
QJsonObject &obj)
|
QJsonObject &obj)
|
||||||
{
|
{
|
||||||
|
|
|
@ -128,6 +128,9 @@ public:
|
||||||
// again
|
// again
|
||||||
void queueSave();
|
void queueSave();
|
||||||
|
|
||||||
|
/// Toggles the inertia in all open overlay windows
|
||||||
|
void toggleAllOverlayInertia();
|
||||||
|
|
||||||
/// Signals
|
/// Signals
|
||||||
pajlada::Signals::NoArgSignal gifRepaintRequested;
|
pajlada::Signals::NoArgSignal gifRepaintRequested;
|
||||||
|
|
||||||
|
|
|
@ -1633,4 +1633,20 @@ void SplitNotebook::select(QWidget *page, bool focusPage)
|
||||||
this->Notebook::select(page, 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
|
} // namespace chatterino
|
||||||
|
|
|
@ -18,6 +18,7 @@ class UpdateDialog;
|
||||||
class NotebookButton;
|
class NotebookButton;
|
||||||
class NotebookTab;
|
class NotebookTab;
|
||||||
class SplitContainer;
|
class SplitContainer;
|
||||||
|
class Split;
|
||||||
|
|
||||||
enum NotebookTabLocation { Top = 0, Left = 1, Right = 2, Bottom = 3 };
|
enum NotebookTabLocation { Top = 0, Left = 1, Right = 2, Bottom = 3 };
|
||||||
|
|
||||||
|
@ -229,6 +230,8 @@ public:
|
||||||
|
|
||||||
void addNotebookActionsToMenu(QMenu *menu) override;
|
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
|
* Toggles between the "Show all tabs" and "Hide all tabs" tab visibility states
|
||||||
*/
|
*/
|
||||||
|
|
530
src/widgets/OverlayWindow.cpp
Normal file
530
src/widgets/OverlayWindow.cpp
Normal file
|
@ -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
|
87
src/widgets/OverlayWindow.hpp
Normal file
87
src/widgets/OverlayWindow.hpp
Normal file
|
@ -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
|
|
@ -285,18 +285,21 @@ void Scrollbar::paintEvent(QPaintEvent * /*event*/)
|
||||||
bool enableElevatedMessageHighlights =
|
bool enableElevatedMessageHighlights =
|
||||||
getSettings()->enableElevatedMessageHighlight;
|
getSettings()->enableElevatedMessageHighlight;
|
||||||
|
|
||||||
this->thumbRect_.setX(xOffset);
|
if (this->showThumb_)
|
||||||
|
{
|
||||||
|
this->thumbRect_.setX(xOffset);
|
||||||
|
|
||||||
// mouse over thumb
|
// mouse over thumb
|
||||||
if (this->mouseDownLocation_ == MouseLocation::InsideThumb)
|
if (this->mouseDownLocation_ == MouseLocation::InsideThumb)
|
||||||
{
|
{
|
||||||
painter.fillRect(this->thumbRect_,
|
painter.fillRect(this->thumbRect_,
|
||||||
this->theme->scrollbars.thumbSelected);
|
this->theme->scrollbars.thumbSelected);
|
||||||
}
|
}
|
||||||
// mouse not over thumb
|
// mouse not over thumb
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
painter.fillRect(this->thumbRect_, this->theme->scrollbars.thumb);
|
painter.fillRect(this->thumbRect_, this->theme->scrollbars.thumb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// draw highlights
|
// draw highlights
|
||||||
|
@ -449,6 +452,17 @@ void Scrollbar::updateScroll()
|
||||||
this->update();
|
this->update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Scrollbar::setShowThumb(bool showThumb)
|
||||||
|
{
|
||||||
|
if (this->showThumb_ == showThumb)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->showThumb_ = showThumb;
|
||||||
|
this->update();
|
||||||
|
}
|
||||||
|
|
||||||
Scrollbar::MouseLocation Scrollbar::locationOfMouseEvent(
|
Scrollbar::MouseLocation Scrollbar::locationOfMouseEvent(
|
||||||
QMouseEvent *event) const
|
QMouseEvent *event) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -127,6 +127,8 @@ public:
|
||||||
/// unaffected by simultaneous shifts of minimum and maximum.
|
/// unaffected by simultaneous shifts of minimum and maximum.
|
||||||
qreal getRelativeCurrentValue() const;
|
qreal getRelativeCurrentValue() const;
|
||||||
|
|
||||||
|
void setShowThumb(bool showthumb);
|
||||||
|
|
||||||
// offset the desired value without breaking smooth scolling
|
// offset the desired value without breaking smooth scolling
|
||||||
void offset(qreal value);
|
void offset(qreal value);
|
||||||
pajlada::Signals::NoArgSignal &getCurrentValueChanged();
|
pajlada::Signals::NoArgSignal &getCurrentValueChanged();
|
||||||
|
@ -169,6 +171,7 @@ private:
|
||||||
boost::circular_buffer<ScrollbarHighlight> highlights_;
|
boost::circular_buffer<ScrollbarHighlight> highlights_;
|
||||||
|
|
||||||
bool atBottom_{false};
|
bool atBottom_{false};
|
||||||
|
bool showThumb_ = true;
|
||||||
|
|
||||||
MouseLocation mouseOverLocation_ = MouseLocation::Outside;
|
MouseLocation mouseOverLocation_ = MouseLocation::Outside;
|
||||||
MouseLocation mouseDownLocation_ = MouseLocation::Outside;
|
MouseLocation mouseDownLocation_ = MouseLocation::Outside;
|
||||||
|
|
|
@ -362,7 +362,8 @@ ChannelView::ChannelView(InternalCtor /*tag*/, QWidget *parent, Split *split,
|
||||||
this->queueUpdate();
|
this->queueUpdate();
|
||||||
});
|
});
|
||||||
|
|
||||||
this->messageColors_.applyTheme(getTheme());
|
this->messageColors_.applyTheme(getTheme(), this->isOverlay_,
|
||||||
|
getSettings()->overlayBackgroundOpacity);
|
||||||
this->messagePreferences_.connectSettings(getSettings(),
|
this->messagePreferences_.connectSettings(getSettings(),
|
||||||
this->signalHolder_);
|
this->signalHolder_);
|
||||||
}
|
}
|
||||||
|
@ -450,6 +451,11 @@ void ChannelView::initializeSignals()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Scrollbar *ChannelView::scrollbar()
|
||||||
|
{
|
||||||
|
return this->scrollBar_;
|
||||||
|
}
|
||||||
|
|
||||||
bool ChannelView::pausable() const
|
bool ChannelView::pausable() const
|
||||||
{
|
{
|
||||||
return pausable_;
|
return pausable_;
|
||||||
|
@ -574,7 +580,19 @@ void ChannelView::themeChangedEvent()
|
||||||
|
|
||||||
this->setupHighlightAnimationColors();
|
this->setupHighlightAnimationColors();
|
||||||
this->queueLayout();
|
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()
|
void ChannelView::setupHighlightAnimationColors()
|
||||||
|
@ -680,9 +698,15 @@ void ChannelView::layoutVisibleMessages(
|
||||||
const auto &message = messages[i];
|
const auto &message = messages[i];
|
||||||
|
|
||||||
redrawRequired |= message->layout(
|
redrawRequired |= message->layout(
|
||||||
layoutWidth, this->scale(),
|
{
|
||||||
this->scale() * static_cast<float>(this->devicePixelRatio()),
|
.messageColors = this->messageColors_,
|
||||||
flags, this->bufferInvalidationQueued_);
|
.flags = flags,
|
||||||
|
.width = layoutWidth,
|
||||||
|
.scale = this->scale(),
|
||||||
|
.imageScale = this->scale() *
|
||||||
|
static_cast<float>(this->devicePixelRatio()),
|
||||||
|
},
|
||||||
|
this->bufferInvalidationQueued_);
|
||||||
|
|
||||||
y += message->getHeight();
|
y += message->getHeight();
|
||||||
}
|
}
|
||||||
|
@ -717,8 +741,14 @@ void ChannelView::updateScrollbar(
|
||||||
auto *message = messages[i].get();
|
auto *message = messages[i].get();
|
||||||
|
|
||||||
message->layout(
|
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);
|
false);
|
||||||
|
|
||||||
h -= message->getHeight();
|
h -= message->getHeight();
|
||||||
|
@ -1486,7 +1516,7 @@ void ChannelView::paintEvent(QPaintEvent *event)
|
||||||
|
|
||||||
QPainter painter(this);
|
QPainter painter(this);
|
||||||
|
|
||||||
painter.fillRect(rect(), this->theme->splits.background);
|
painter.fillRect(rect(), this->messageColors_.channelBackground);
|
||||||
|
|
||||||
// draw messages
|
// draw messages
|
||||||
this->drawMessages(painter, event->rect());
|
this->drawMessages(painter, event->rect());
|
||||||
|
@ -1705,10 +1735,16 @@ void ChannelView::wheelEvent(QWheelEvent *event)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
snapshot[i - 1]->layout(
|
snapshot[i - 1]->layout(
|
||||||
this->getLayoutWidth(), this->scale(),
|
{
|
||||||
this->scale() *
|
.messageColors = this->messageColors_,
|
||||||
static_cast<float>(this->devicePixelRatio()),
|
.flags = this->getFlags(),
|
||||||
this->getFlags(), false);
|
.width = this->getLayoutWidth(),
|
||||||
|
.scale = this->scale(),
|
||||||
|
.imageScale =
|
||||||
|
this->scale() *
|
||||||
|
static_cast<float>(this->devicePixelRatio()),
|
||||||
|
},
|
||||||
|
false);
|
||||||
scrollFactor = 1;
|
scrollFactor = 1;
|
||||||
currentScrollLeft = snapshot[i - 1]->getHeight();
|
currentScrollLeft = snapshot[i - 1]->getHeight();
|
||||||
}
|
}
|
||||||
|
@ -1742,10 +1778,16 @@ void ChannelView::wheelEvent(QWheelEvent *event)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
snapshot[i + 1]->layout(
|
snapshot[i + 1]->layout(
|
||||||
this->getLayoutWidth(), this->scale(),
|
{
|
||||||
this->scale() *
|
.messageColors = this->messageColors_,
|
||||||
static_cast<float>(this->devicePixelRatio()),
|
.flags = this->getFlags(),
|
||||||
this->getFlags(), false);
|
.width = this->getLayoutWidth(),
|
||||||
|
.scale = this->scale(),
|
||||||
|
.imageScale =
|
||||||
|
this->scale() *
|
||||||
|
static_cast<float>(this->devicePixelRatio()),
|
||||||
|
},
|
||||||
|
false);
|
||||||
|
|
||||||
scrollFactor = 1;
|
scrollFactor = 1;
|
||||||
currentScrollLeft = snapshot[i + 1]->getHeight();
|
currentScrollLeft = snapshot[i + 1]->getHeight();
|
||||||
|
|
|
@ -202,6 +202,16 @@ public:
|
||||||
*/
|
*/
|
||||||
bool mayContainMessage(const MessagePtr &message);
|
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::Signal<QMouseEvent *> mouseDown;
|
||||||
pajlada::Signals::NoArgSignal selectionChanged;
|
pajlada::Signals::NoArgSignal selectionChanged;
|
||||||
pajlada::Signals::Signal<HighlightState> tabHighlightRequested;
|
pajlada::Signals::Signal<HighlightState> tabHighlightRequested;
|
||||||
|
@ -377,6 +387,8 @@ private:
|
||||||
|
|
||||||
bool onlyUpdateEmotes_ = false;
|
bool onlyUpdateEmotes_ = false;
|
||||||
|
|
||||||
|
bool isOverlay_ = false;
|
||||||
|
|
||||||
// Mouse event variables
|
// Mouse event variables
|
||||||
bool isLeftMouseDown_ = false;
|
bool isLeftMouseDown_ = false;
|
||||||
bool isRightMouseDown_ = false;
|
bool isRightMouseDown_ = false;
|
||||||
|
|
|
@ -97,8 +97,8 @@ void MessageView::paintEvent(QPaintEvent * /*event*/)
|
||||||
|
|
||||||
void MessageView::themeChangedEvent()
|
void MessageView::themeChangedEvent()
|
||||||
{
|
{
|
||||||
this->messageColors_.applyTheme(getTheme());
|
this->messageColors_.applyTheme(getTheme(), false, 255);
|
||||||
this->messageColors_.regular = getTheme()->splits.input.background;
|
this->messageColors_.regularBg = getTheme()->splits.input.background;
|
||||||
if (this->messageLayout_)
|
if (this->messageLayout_)
|
||||||
{
|
{
|
||||||
this->messageLayout_->invalidateBuffer();
|
this->messageLayout_->invalidateBuffer();
|
||||||
|
@ -120,9 +120,15 @@ void MessageView::layoutMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
bool updateRequired = this->messageLayout_->layout(
|
bool updateRequired = this->messageLayout_->layout(
|
||||||
this->width_, this->scale(),
|
{
|
||||||
this->scale() * static_cast<float>(this->devicePixelRatio()),
|
.messageColors = this->messageColors_,
|
||||||
MESSAGE_FLAGS, false);
|
.flags = MESSAGE_FLAGS,
|
||||||
|
.width = this->width_,
|
||||||
|
.scale = this->scale(),
|
||||||
|
.imageScale =
|
||||||
|
this->scale() * static_cast<float>(this->devicePixelRatio()),
|
||||||
|
},
|
||||||
|
false);
|
||||||
|
|
||||||
if (updateRequired)
|
if (updateRequired)
|
||||||
{
|
{
|
||||||
|
|
123
src/widgets/helper/OverlayInteraction.cpp
Normal file
123
src/widgets/helper/OverlayInteraction.cpp
Normal file
|
@ -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
|
47
src/widgets/helper/OverlayInteraction.hpp
Normal file
47
src/widgets/helper/OverlayInteraction.hpp
Normal file
|
@ -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",
|
layout.addCheckbox("Use custom FrankerFaceZ VIP badges",
|
||||||
s.useCustomFfzVipBadges);
|
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");
|
layout.addSubtitle("Miscellaneous");
|
||||||
|
|
||||||
if (supportsIncognitoLinks())
|
if (supportsIncognitoLinks())
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
#include "widgets/helper/ResizingTextEdit.hpp"
|
#include "widgets/helper/ResizingTextEdit.hpp"
|
||||||
#include "widgets/helper/SearchPopup.hpp"
|
#include "widgets/helper/SearchPopup.hpp"
|
||||||
#include "widgets/Notebook.hpp"
|
#include "widgets/Notebook.hpp"
|
||||||
|
#include "widgets/OverlayWindow.hpp"
|
||||||
#include "widgets/Scrollbar.hpp"
|
#include "widgets/Scrollbar.hpp"
|
||||||
#include "widgets/splits/DraggedSplit.hpp"
|
#include "widgets/splits/DraggedSplit.hpp"
|
||||||
#include "widgets/splits/SplitContainer.hpp"
|
#include "widgets/splits/SplitContainer.hpp"
|
||||||
|
@ -765,6 +766,47 @@ void Split::addShortcuts()
|
||||||
}
|
}
|
||||||
return "";
|
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(
|
this->shortcuts_ = getApp()->getHotkeys()->shortcutsForCategory(
|
||||||
|
@ -1104,6 +1146,20 @@ void Split::popup()
|
||||||
window.show();
|
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()
|
void Split::clear()
|
||||||
{
|
{
|
||||||
this->view_->clearMessages();
|
this->view_->clearMessages();
|
||||||
|
|
|
@ -21,6 +21,7 @@ class SplitInput;
|
||||||
class SplitContainer;
|
class SplitContainer;
|
||||||
class SplitOverlay;
|
class SplitOverlay;
|
||||||
class SelectChannelDialog;
|
class SelectChannelDialog;
|
||||||
|
class OverlayWindow;
|
||||||
|
|
||||||
// Each ChatWidget consists of three sub-elements that handle their own part of
|
// Each ChatWidget consists of three sub-elements that handle their own part of
|
||||||
// the chat widget: ChatWidgetHeader
|
// the chat widget: ChatWidgetHeader
|
||||||
|
@ -80,6 +81,8 @@ public:
|
||||||
// This is called on window focus lost
|
// This is called on window focus lost
|
||||||
void unpause();
|
void unpause();
|
||||||
|
|
||||||
|
OverlayWindow *overlayWindow();
|
||||||
|
|
||||||
static pajlada::Signals::Signal<Qt::KeyboardModifiers>
|
static pajlada::Signals::Signal<Qt::KeyboardModifiers>
|
||||||
modifierStatusChanged;
|
modifierStatusChanged;
|
||||||
static Qt::KeyboardModifiers modifierStatus;
|
static Qt::KeyboardModifiers modifierStatus;
|
||||||
|
@ -158,6 +161,8 @@ private:
|
||||||
SplitInput *const input_;
|
SplitInput *const input_;
|
||||||
SplitOverlay *const overlay_;
|
SplitOverlay *const overlay_;
|
||||||
|
|
||||||
|
QPointer<OverlayWindow> overlayWindow_;
|
||||||
|
|
||||||
QPointer<SelectChannelDialog> selectChannelDialog_;
|
QPointer<SelectChannelDialog> selectChannelDialog_;
|
||||||
|
|
||||||
pajlada::Signals::Connection channelIDChangedConnection_;
|
pajlada::Signals::Connection channelIDChangedConnection_;
|
||||||
|
@ -179,6 +184,7 @@ public slots:
|
||||||
void explainMoving();
|
void explainMoving();
|
||||||
void explainSplitting();
|
void explainSplitting();
|
||||||
void popup();
|
void popup();
|
||||||
|
void showOverlayWindow();
|
||||||
void clear();
|
void clear();
|
||||||
void openInBrowser();
|
void openInBrowser();
|
||||||
void openModViewInBrowser();
|
void openModViewInBrowser();
|
||||||
|
|
|
@ -390,6 +390,9 @@ std::unique_ptr<QMenu> SplitHeader::createMainMenu()
|
||||||
menu->addAction(
|
menu->addAction(
|
||||||
"Popup", this->split_, &Split::popup,
|
"Popup", this->split_, &Split::popup,
|
||||||
h->getDisplaySequence(HotkeyCategory::Window, "popup", {{"split"}}));
|
h->getDisplaySequence(HotkeyCategory::Window, "popup", {{"split"}}));
|
||||||
|
menu->addAction(
|
||||||
|
"Popup overlay", this->split_, &Split::showOverlayWindow,
|
||||||
|
h->getDisplaySequence(HotkeyCategory::Split, "popupOverlay"));
|
||||||
menu->addAction(
|
menu->addAction(
|
||||||
"Search", this->split_,
|
"Search", this->split_,
|
||||||
[this] {
|
[this] {
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "Application.hpp"
|
#include "Application.hpp"
|
||||||
#include "controllers/accounts/AccountController.hpp"
|
#include "controllers/accounts/AccountController.hpp"
|
||||||
|
#include "messages/layouts/MessageLayoutContext.hpp"
|
||||||
#include "messages/MessageBuilder.hpp"
|
#include "messages/MessageBuilder.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
#include "mocks/BaseApplication.hpp"
|
#include "mocks/BaseApplication.hpp"
|
||||||
|
@ -55,7 +56,16 @@ public:
|
||||||
builder.append(
|
builder.append(
|
||||||
std::make_unique<TextElement>(text, MessageElementFlag::Text));
|
std::make_unique<TextElement>(text, MessageElementFlag::Text));
|
||||||
this->layout = std::make_unique<MessageLayout>(builder.release());
|
this->layout = std::make_unique<MessageLayout>(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;
|
MockApplication mockApplication;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "common/Literals.hpp"
|
#include "common/Literals.hpp"
|
||||||
#include "messages/Emote.hpp"
|
#include "messages/Emote.hpp"
|
||||||
|
#include "messages/layouts/MessageLayoutContext.hpp"
|
||||||
#include "messages/layouts/MessageLayoutElement.hpp"
|
#include "messages/layouts/MessageLayoutElement.hpp"
|
||||||
#include "messages/Message.hpp"
|
#include "messages/Message.hpp"
|
||||||
#include "messages/MessageElement.hpp"
|
#include "messages/MessageElement.hpp"
|
||||||
|
@ -107,16 +108,25 @@ TEST_P(MessageLayoutContainerTest, RtlReordering)
|
||||||
{
|
{
|
||||||
auto [inputText, expected, expectedDirection] = GetParam();
|
auto [inputText, expected, expectedDirection] = GetParam();
|
||||||
MessageLayoutContainer container;
|
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);
|
auto elements = makeElements(inputText);
|
||||||
for (const auto &element : elements)
|
for (const auto &element : elements)
|
||||||
{
|
{
|
||||||
element->addToContainer(container, {
|
element->addToContainer(container, ctx);
|
||||||
MessageElementFlag::Text,
|
|
||||||
MessageElementFlag::Username,
|
|
||||||
MessageElementFlag::TwitchEmote,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
container.endLayout();
|
container.endLayout();
|
||||||
ASSERT_EQ(container.line_, 1) << "unexpected linebreak";
|
ASSERT_EQ(container.line_, 1) << "unexpected linebreak";
|
||||||
|
|
Loading…
Reference in a new issue