diff --git a/CHANGELOG.md b/CHANGELOG.md index b52db477..c48f9805 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,6 +78,7 @@ - Dev: Fixed incorrect lua generation of static methods for typescript plugins. (#6190, #6223) - Dev: Merged top/bottom and left/right notebook layouts. (#6215) - Dev: Refactored `Button` and friends. (#6102, #6255, #6266, #6302, #6268, #6334) +- Dev: Made "add split" button (part of the split header) a natively rendered button. (#6349) - Dev: Made Settings & Account button on Linux/macOS SVGs. (#6267) - Dev: Some more setting widget refactors. (#6317) - Dev: Emoji style / set is now stored lowercase (and matched case-insensitively). Changing emoji style from this point on and then running an old version might mean you will use the Twitter emoji style by default. (#6300) diff --git a/resources/buttons/addSplit.png b/resources/buttons/addSplit.png deleted file mode 100644 index 13a8891d..00000000 Binary files a/resources/buttons/addSplit.png and /dev/null differ diff --git a/resources/buttons/addSplitDark.png b/resources/buttons/addSplitDark.png deleted file mode 100644 index 814aa9f1..00000000 Binary files a/resources/buttons/addSplitDark.png and /dev/null differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 960f4320..fe9fb02d 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -613,6 +613,8 @@ set(SOURCE_FILES widgets/buttons/Button.hpp widgets/buttons/DimButton.cpp widgets/buttons/DimButton.hpp + widgets/buttons/DrawnButton.cpp + widgets/buttons/DrawnButton.hpp widgets/buttons/InitUpdateButton.cpp widgets/buttons/InitUpdateButton.hpp widgets/buttons/LabelButton.cpp diff --git a/src/widgets/buttons/Button.cpp b/src/widgets/buttons/Button.cpp index dc7304b7..2ab8cfb8 100644 --- a/src/widgets/buttons/Button.cpp +++ b/src/widgets/buttons/Button.cpp @@ -148,6 +148,7 @@ void Button::enterEvent(QEvent * /*event*/) { this->mouseOver_ = true; this->update(); + this->mouseOverUpdated(); } } @@ -157,6 +158,7 @@ void Button::leaveEvent(QEvent * /*event*/) { this->mouseOver_ = false; this->update(); + this->mouseOverUpdated(); } } diff --git a/src/widgets/buttons/Button.hpp b/src/widgets/buttons/Button.hpp index d75691ed..667e996c 100644 --- a/src/widgets/buttons/Button.hpp +++ b/src/widgets/buttons/Button.hpp @@ -183,6 +183,11 @@ protected: /// Queue up the click animation at the given position void addClickEffect(QPoint position); + /// This is fired when the mouse over state changes + virtual void mouseOverUpdated() + { + } + private: void onMouseEffectTimeout(); void showMenu(); diff --git a/src/widgets/buttons/DrawnButton.cpp b/src/widgets/buttons/DrawnButton.cpp new file mode 100644 index 00000000..51634a4c --- /dev/null +++ b/src/widgets/buttons/DrawnButton.cpp @@ -0,0 +1,163 @@ +#include "widgets/buttons/DrawnButton.hpp" + +#include "singletons/Theme.hpp" + +#include + +namespace chatterino { + +DrawnButton::DrawnButton(Symbol symbol_, Options options, BaseWidget *parent) + : Button(parent) + , symbol(symbol_) +{ + this->setContentCacheEnabled(true); + + // Set user-defined options + this->setOptions(options); + + // Ensure symbol-specific options are initially set + this->themeChangedEvent(); +} + +void DrawnButton::setOptions(Options options_) +{ + this->options = options_; + + this->invalidateContent(); +} + +void DrawnButton::themeChangedEvent() +{ + Button::themeChangedEvent(); + + auto &o = this->symbolOptions; + + switch (this->symbol) + { + case Symbol::Plus: { + o.padding = 3; + o.thickness = 1; + + o.foreground = this->theme->messages.textColors.system; + o.foregroundHover = this->theme->messages.textColors.regular; + } + break; + } + + this->invalidateContent(); +} + +void DrawnButton::mouseOverUpdated() +{ + this->invalidateContent(); +} + +void DrawnButton::paintContent(QPainter &painter) +{ + QColor fg; + QColor bg; + + if (this->mouseOver()) + { + bg = this->getBackgroundHover(); + fg = this->getForegroundHover(); + } + else + { + bg = this->getBackground(); + fg = this->getForeground(); + } + + if (bg.isValid()) + { + painter.fillRect(this->rect(), bg); + } + + auto thickness = this->getThickness(); + auto padding = this->getPadding(); + + switch (this->symbol) + { + case Symbol::Plus: { + QPen pen; + pen.setColor(fg); + pen.setWidth(thickness); + painter.setPen(pen); + + auto innerSize = this->rect().size(); + innerSize.setHeight(innerSize.width()); + QRect inner; + inner.setSize(innerSize); + inner.moveCenter(this->rect().center()); + + auto top = inner.top(); + auto bottom = inner.bottom(); + auto center = inner.center(); + auto left = inner.left(); + auto right = inner.right(); + + QLine vertical(center.x(), top + padding, center.x(), + bottom - padding); + painter.drawLine(vertical); + QLine horizontal(left + padding, center.y(), right - padding, + center.y()); + painter.drawLine(horizontal); + } + break; + } +} + +int DrawnButton::getPadding() const +{ + auto v = + this->options.padding.value_or(this->symbolOptions.padding.value_or(0)); + + return static_cast(std::round(static_cast(v) * this->scale())); +} + +int DrawnButton::getThickness() const +{ + auto v = this->options.thickness.value_or( + this->symbolOptions.thickness.value_or(1)); + + return std::max(1, static_cast( + std::round(static_cast(v) * this->scale()))); +} + +QColor DrawnButton::getBackground() const +{ + auto v = this->options.background.value_or( + this->symbolOptions.background.value_or(QColor())); + + return v; +} + +QColor DrawnButton::getBackgroundHover() const +{ + auto v = this->options.backgroundHover.value_or( + this->symbolOptions.backgroundHover.value_or(QColor())); + + return v; +} + +QColor DrawnButton::getForeground() const +{ + auto v = this->options.foreground.value_or( + this->symbolOptions.foreground.value_or(QColor())); + + assert(v.isValid()); + + return v; +} + +QColor DrawnButton::getForegroundHover() const +{ + auto v = this->options.foregroundHover.value_or( + this->symbolOptions.foregroundHover.value_or(QColor())); + + assert(v.isValid()); + + return v; +} + +} // namespace chatterino diff --git a/src/widgets/buttons/DrawnButton.hpp b/src/widgets/buttons/DrawnButton.hpp new file mode 100644 index 00000000..6a83b28f --- /dev/null +++ b/src/widgets/buttons/DrawnButton.hpp @@ -0,0 +1,102 @@ +#pragma once + +#include "widgets/buttons/Button.hpp" + +#include + +#include + +namespace chatterino { + +/// A button displaying custom-drawn primitive symbols. +class DrawnButton : public Button +{ + Q_OBJECT + +public: + enum class Symbol : std::uint8_t { + /// + + /// + /// Default values: + /// Padding: 2px + /// Thickness: 1px + Plus, + }; + + struct Options { + /// The horizontal and vertical padding, in unscaled pixels, that will be applied when drawing the symbol + std::optional padding; + + /// The base line thickness that will be used when drawing the symbol. + /// + /// Each symbol might use the thickness slightly different + std::optional thickness; + + std::optional background; + std::optional backgroundHover; + + std::optional foreground; + std::optional foregroundHover; + }; + + DrawnButton(Symbol symbol_, Options options_, BaseWidget *parent); + + /// Update the override options for this button + void setOptions(Options options_); + +protected: + void themeChangedEvent() override; + + void mouseOverUpdated() override; + + void paintContent(QPainter &painter) override; + +private: + /// Returns the padding (horizontal and vertical) to be used when drawing this button, taking the widget scale into consideration. + /// + /// Prioritizes the user-defined options over the symbol-defined options. + int getPadding() const; + + /// Returns the line thickness to be used when drawing this button, taking the widget scale into consideration. + /// + /// Prioritizes the user-defined options over the symbol-defined options. + int getThickness() const; + + /// Returns the background to be used when drawing this button. + /// + /// The color may be invalid, meaning no background is drawn. + /// + /// Prioritizes the user-defined options over the symbol-defined options. + QColor getBackground() const; + + /// Returns the background to be used when drawing this button, and the user is hovering over it. + /// + /// The color may be invalid, meaning no background is drawn. + /// + /// Prioritizes the user-defined options over the symbol-defined options. + QColor getBackgroundHover() const; + + /// Returns the foreground color to be used when drawing this button. + /// + /// The color must not be invalid. + /// + /// Prioritizes the user-defined options over the symbol-defined options. + QColor getForeground() const; + + /// Returns the foreground color to be used when drawing this button, and the user is hovering over it. + /// + /// The color must not be invalid. + /// + /// Prioritizes the user-defined options over the symbol-defined options. + QColor getForegroundHover() const; + + /// The options defined by the consumer / class wanting to draw this button + Options options; + + /// The default options defined by the symbol + Options symbolOptions; + + const Symbol symbol; +}; + +} // namespace chatterino diff --git a/src/widgets/splits/SplitHeader.cpp b/src/widgets/splits/SplitHeader.cpp index 9d34776d..f7ba89e9 100644 --- a/src/widgets/splits/SplitHeader.cpp +++ b/src/widgets/splits/SplitHeader.cpp @@ -20,6 +20,7 @@ #include "singletons/WindowManager.hpp" #include "util/Helpers.hpp" #include "util/LayoutHelper.hpp" +#include "widgets/buttons/DrawnButton.hpp" #include "widgets/buttons/LabelButton.hpp" #include "widgets/buttons/PixmapButton.hpp" #include "widgets/dialogs/SettingsDialog.hpp" @@ -42,6 +43,14 @@ namespace { using namespace chatterino; +/// The width of the standard button. +constexpr const int BUTTON_WIDTH = 28; + +/// The width of the "Add split" button. +/// +/// This matches the scrollbar's full width. +constexpr const int ADD_SPLIT_BUTTON_WIDTH = 16; + // 5 minutes constexpr const qint64 THUMBNAIL_MAX_AGE_MS = 5LL * 60 * 1000; @@ -280,6 +289,13 @@ void SplitHeader::initializeLayout() { assert(this->layout() == nullptr); + this->addButton_ = new DrawnButton(DrawnButton::Symbol::Plus, + { + .padding = 3, + .thickness = 1, + }, + this); + auto *layout = makeLayout({ // space makeWidget([](auto w) { @@ -353,14 +369,11 @@ void SplitHeader::initializeLayout() }); }), // add split - this->addButton_ = makeWidget([&](auto w) { - w->setPixmap(getResources().buttons.addSplitDark); - w->setMarginEnabled(false); + this->addButton_, + }); - QObject::connect(w, &Button::leftClicked, this, [this]() { - this->split_->addSibling(); - }); - }), + QObject::connect(this->addButton_, &Button::leftClicked, this, [this]() { + this->split_->addSibling(); }); getSettings()->customURIScheme.connect( @@ -791,13 +804,15 @@ void SplitHeader::handleChannelChanged() void SplitHeader::scaleChangedEvent(float scale) { - int w = int(28 * scale); + int w = int(BUTTON_WIDTH * scale); + int addSplitWidth = int(ADD_SPLIT_BUTTON_WIDTH * scale); this->setFixedHeight(w); this->dropdownButton_->setFixedWidth(w); this->moderationButton_->setFixedWidth(w); this->chattersButton_->setFixedWidth(w); - this->addButton_->setFixedWidth(w * 5 / 8); + + this->addButton_->setFixedWidth(addSplitWidth); } void SplitHeader::setAddButtonVisible(bool value) @@ -1061,18 +1076,21 @@ void SplitHeader::themeChangedEvent() } this->titleLabel_->setPalette(palette); + this->addButton_->setOptions({ + .background = this->theme->messages.backgrounds.regular, + .backgroundHover = this->theme->messages.backgrounds.regular, + }); + // -- if (this->theme->isLightTheme()) { this->chattersButton_->setPixmap(getResources().buttons.chattersDark); this->dropdownButton_->setPixmap(getResources().buttons.menuDark); - this->addButton_->setPixmap(getResources().buttons.addSplit); } else { this->chattersButton_->setPixmap(getResources().buttons.chattersLight); this->dropdownButton_->setPixmap(getResources().buttons.menuLight); - this->addButton_->setPixmap(getResources().buttons.addSplitDark); } this->update(); diff --git a/src/widgets/splits/SplitHeader.hpp b/src/widgets/splits/SplitHeader.hpp index 7976c921..da3ffab6 100644 --- a/src/widgets/splits/SplitHeader.hpp +++ b/src/widgets/splits/SplitHeader.hpp @@ -16,6 +16,7 @@ namespace chatterino { +class DrawnButton; class PixmapButton; class LabelButton; class Label; @@ -87,7 +88,7 @@ private: PixmapButton *moderationButton_{}; PixmapButton *chattersButton_{}; - PixmapButton *addButton_{}; + DrawnButton *addButton_{}; // states QPoint dragStart_{}; diff --git a/tests/src/MessageLayoutContainer.cpp b/tests/src/MessageLayoutContainer.cpp index 2f1b810f..84f5c4f5 100644 --- a/tests/src/MessageLayoutContainer.cpp +++ b/tests/src/MessageLayoutContainer.cpp @@ -70,7 +70,7 @@ std::vector> makeElements(const QString &text) auto emote = std::make_shared(Emote{ .name = EmoteName{word}, .images = ImageSet{Image::fromResourcePixmap( - getResources().buttons.addSplit)}, + getResources().twitch.automod)}, .tooltip = {}, .homePage = {}, .id = {},