diff --git a/src/singletons/thememanager.cpp b/src/singletons/thememanager.cpp index a844ede2c..6b05e3f47 100644 --- a/src/singletons/thememanager.cpp +++ b/src/singletons/thememanager.cpp @@ -53,7 +53,23 @@ void ThemeManager::update() // multiplier: 1 = white, 0.8 = light, -0.8 dark, -1 black void ThemeManager::actuallyUpdate(double hue, double multiplier) { - lightTheme = multiplier > 0; + isLight = multiplier > 0; + bool isLightTabs; + + QColor themeColor = QColor::fromHslF(hue, 0.5, 0.5); + QColor themeColorNoSat = QColor::fromHslF(hue, 0.5, 0.5); + +#ifdef USEWINSDK + QColor tabFg = isLight ? "#000" : "#fff"; + this->windowBg = isLight ? "#fff" : "#444"; + + isLightTabs = isLight; +#else + QColor tabFg = lightTheme ? "#000" : "#fff"; + this->windowBg = "#fff"; + + isLightTabs = true; +#endif qreal sat = 0.05; @@ -70,30 +86,30 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier) // message (referenced later) this->messages.textColors.caret = // - this->messages.textColors.regular = lightTheme ? QColor(0, 0, 0) : QColor(255, 255, 255); + this->messages.textColors.regular = isLight ? "#000" : "#fff"; // tabs // text, {regular, hover, unfocused} - this->tabs.regular = {QColor(0, 0, 0), - {QColor(255, 255, 255), QColor(200, 200, 200), QColor(255, 255, 255)}}; + this->tabs.regular = {tabFg, {windowBg, blendColors(windowBg, "#999", 0.5), windowBg}}; - this->tabs.selected = {QColor(255, 255, 255), - {QColor::fromHslF(hue, 0.5, 0.5), QColor::fromHslF(hue, 0.5, 0.5), - QColor::fromHslF(hue, 0, 0.5)}}; + this->tabs.selected = {"#fff", {themeColor, themeColor, QColor::fromHslF(hue, 0, 0.5)}}; - this->tabs.newMessage = {QColor(0, 0, 0), - {QBrush(QColor::fromHslF(hue, 0.5, 0.8), Qt::DiagCrossPattern), - QBrush(QColor::fromHslF(hue, 0.5, 0.7), Qt::DiagCrossPattern), - QBrush(QColor::fromHslF(hue, 0, 0.8), Qt::DiagCrossPattern)}}; + this->tabs.newMessage = { + tabFg, + {QBrush(blendColors(themeColor, windowBg, 0.5), Qt::DiagCrossPattern), + QBrush(blendColors(themeColor, windowBg, 0.3), Qt::DiagCrossPattern), + QBrush(blendColors(themeColorNoSat, windowBg, 0.5), Qt::DiagCrossPattern)}}; - this->tabs.highlighted = {QColor(0, 0, 0), - {QColor::fromHslF(hue, 0.5, 0.8), QColor::fromHslF(hue, 0.5, 0.7), - QColor::fromHslF(hue, 0, 0.8)}}; + this->tabs.highlighted = { + tabFg, + {QBrush(blendColors(themeColor, windowBg, 0.5), Qt::DiagCrossPattern), + QBrush(blendColors(themeColor, windowBg, 0.3), Qt::DiagCrossPattern), + QBrush(blendColors(themeColorNoSat, windowBg, 0.5), Qt::DiagCrossPattern)}}; // Split - bool flat = lightTheme; + bool flat = isLight; - this->splits.messageSeperator = lightTheme ? QColor(127, 127, 127) : QColor(80, 80, 80); + this->splits.messageSeperator = isLight ? QColor(127, 127, 127) : QColor(80, 80, 80); this->splits.background = getColor(0, sat, 1); this->splits.dropPreview = getColor(hue, 0.5, 0.5, 0.6); // this->splits.border @@ -113,7 +129,7 @@ void ThemeManager::actuallyUpdate(double hue, double multiplier) "selection-background-color:" + this->tabs.selected.backgrounds.regular.color().name(); // Message - this->messages.textColors.link = lightTheme ? QColor(66, 134, 244) : QColor(66, 134, 244); + this->messages.textColors.link = isLight ? QColor(66, 134, 244) : QColor(66, 134, 244); this->messages.textColors.system = QColor(140, 127, 127); this->messages.backgrounds.regular = splits.background; @@ -151,7 +167,7 @@ QColor ThemeManager::blendColors(const QColor &color1, const QColor &color2, qre void ThemeManager::normalizeColor(QColor &color) { - if (this->lightTheme) { + if (this->isLight) { if (color.lightnessF() > 0.5f) { color.setHslF(color.hueF(), color.saturationF(), 0.5f); } diff --git a/src/singletons/thememanager.hpp b/src/singletons/thememanager.hpp index c64636650..fa8c0a0c5 100644 --- a/src/singletons/thememanager.hpp +++ b/src/singletons/thememanager.hpp @@ -20,7 +20,7 @@ public: inline bool isLightTheme() const { - return this->lightTheme; + return this->isLight; } struct TabColors { @@ -97,6 +97,9 @@ public: QColor background; } tooltip; + QColor windowBg; + QColor windowText; + void normalizeColor(QColor &color); void update(); @@ -116,7 +119,7 @@ private: void fillLookupTableValues(double (&array)[360], double from, double to, double fromValue, double toValue); - bool lightTheme = false; + bool isLight = false; pajlada::Signals::NoArgSignal repaintVisibleChatWidgets; diff --git a/src/widgets/basewidget.cpp b/src/widgets/basewidget.cpp index f977086f1..808a59412 100644 --- a/src/widgets/basewidget.cpp +++ b/src/widgets/basewidget.cpp @@ -11,22 +11,22 @@ namespace chatterino { namespace widgets { -BaseWidget::BaseWidget(singletons::ThemeManager &_themeManager, QWidget *parent) - : QWidget(parent) +BaseWidget::BaseWidget(singletons::ThemeManager &_themeManager, QWidget *parent, Qt::WindowFlags f) + : QWidget(parent, f) , themeManager(_themeManager) { this->init(); } -BaseWidget::BaseWidget(BaseWidget *parent) - : QWidget(parent) +BaseWidget::BaseWidget(BaseWidget *parent, Qt::WindowFlags f) + : QWidget(parent, f) , themeManager(singletons::ThemeManager::getInstance()) { this->init(); } -BaseWidget::BaseWidget(QWidget *parent) - : QWidget(parent) +BaseWidget::BaseWidget(QWidget *parent, Qt::WindowFlags f) + : QWidget(parent, f) , themeManager(singletons::ThemeManager::getInstance()) { } diff --git a/src/widgets/basewidget.hpp b/src/widgets/basewidget.hpp index b2e495b3b..8030655ed 100644 --- a/src/widgets/basewidget.hpp +++ b/src/widgets/basewidget.hpp @@ -14,9 +14,10 @@ class BaseWidget : public QWidget Q_OBJECT public: - explicit BaseWidget(singletons::ThemeManager &_themeManager, QWidget *parent); - explicit BaseWidget(BaseWidget *parent); - explicit BaseWidget(QWidget *parent = nullptr); + explicit BaseWidget(singletons::ThemeManager &_themeManager, QWidget *parent, + Qt::WindowFlags f = Qt::WindowFlags()); + explicit BaseWidget(BaseWidget *parent, Qt::WindowFlags f = Qt::WindowFlags()); + explicit BaseWidget(QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags()); singletons::ThemeManager &themeManager; diff --git a/src/widgets/basewindow.cpp b/src/widgets/basewindow.cpp index 54c5c517b..dd6dd14c3 100644 --- a/src/widgets/basewindow.cpp +++ b/src/widgets/basewindow.cpp @@ -4,28 +4,46 @@ #include "util/nativeeventhelper.hpp" #include "widgets/tooltipwidget.hpp" +#include #include #include +#ifdef USEWINSDK +#include +#include +#include #include +#include +#pragma comment(lib, "Dwmapi.lib") + +#include +#include +#include "widgets/helper/rippleeffectlabel.hpp" + +#define WM_DPICHANGED 0x02E0 +#endif namespace chatterino { namespace widgets { -BaseWindow::BaseWindow(singletons::ThemeManager &_themeManager, QWidget *parent) - : BaseWidget(_themeManager, parent) +BaseWindow::BaseWindow(singletons::ThemeManager &_themeManager, QWidget *parent, + bool _enableCustomFrame) + : BaseWidget(_themeManager, parent, Qt::Window) + , enableCustomFrame(_enableCustomFrame) { this->init(); } -BaseWindow::BaseWindow(BaseWidget *parent) - : BaseWidget(parent) +BaseWindow::BaseWindow(BaseWidget *parent, bool _enableCustomFrame) + : BaseWidget(parent, Qt::Window) + , enableCustomFrame(_enableCustomFrame) { this->init(); } -BaseWindow::BaseWindow(QWidget *parent) - : BaseWidget(parent) +BaseWindow::BaseWindow(QWidget *parent, bool _enableCustomFrame) + : BaseWidget(parent, Qt::Window) + , enableCustomFrame(_enableCustomFrame) { this->init(); } @@ -35,6 +53,51 @@ void BaseWindow::init() this->setWindowIcon(QIcon(":/images/icon.png")); #ifdef USEWINSDK + if (this->hasCustomWindowFrame()) { + // CUSTOM WINDOW FRAME + QVBoxLayout *layout = new QVBoxLayout; + layout->setMargin(1); + this->setLayout(layout); + { + QHBoxLayout *buttons = this->titlebarBox = new QHBoxLayout; + buttons->setMargin(0); + layout->addLayout(buttons); + + // title + QLabel *titleLabel = new QLabel("Chatterino"); + buttons->addWidget(titleLabel); + this->titleLabel = titleLabel; + + // buttons + RippleEffectLabel *min = new RippleEffectLabel; + min->getLabel().setText("min"); + min->setFixedSize(46, 30); + RippleEffectLabel *max = new RippleEffectLabel; + max->setFixedSize(46, 30); + max->getLabel().setText("max"); + RippleEffectLabel *exit = new RippleEffectLabel; + exit->setFixedSize(46, 30); + exit->getLabel().setText("exit"); + + this->minButton = min; + this->maxButton = max; + this->exitButton = exit; + + this->widgets.push_back(min); + this->widgets.push_back(max); + this->widgets.push_back(exit); + + buttons->addStretch(1); + buttons->addWidget(min); + buttons->addWidget(max); + buttons->addWidget(exit); + } + this->layoutBase = new QWidget(this); + this->widgets.push_back(this->layoutBase); + layout->addWidget(this->layoutBase); + } + + // DPI auto dpi = util::getWindowDpi(this->winId()); if (dpi) { @@ -49,6 +112,40 @@ void BaseWindow::init() } } +QWidget *BaseWindow::getLayoutContainer() +{ + if (this->enableCustomFrame) { + return this->layoutBase; + } else { + return this; + } +} + +bool BaseWindow::hasCustomWindowFrame() +{ +#ifdef Q_OS_WIN + return this->enableCustomFrame; +#else + return false; +#endif +} + +void BaseWindow::refreshTheme() +{ + QPalette palette; + palette.setColor(QPalette::Background, this->themeManager.windowBg); + palette.setColor(QPalette::Foreground, this->themeManager.windowText); + this->setPalette(palette); +} + +void BaseWindow::addTitleBarButton(const QString &text) +{ + RippleEffectLabel *label = new RippleEffectLabel; + label->getLabel().setText(text); + this->widgets.push_back(label); + this->titlebarBox->insertWidget(2, label); +} + void BaseWindow::changeEvent(QEvent *) { TooltipWidget::getInstance()->hide(); @@ -64,26 +161,156 @@ bool BaseWindow::nativeEvent(const QByteArray &eventType, void *message, long *r { MSG *msg = reinterpret_cast(message); - // WM_DPICHANGED - if (msg->message == 0x02E0) { - qDebug() << "dpi changed"; - int dpi = HIWORD(msg->wParam); + switch (msg->message) { + case WM_DPICHANGED: { + qDebug() << "dpi changed"; + int dpi = HIWORD(msg->wParam); - float oldDpiMultiplier = this->dpiMultiplier; - this->dpiMultiplier = dpi / 96.f; - float scale = this->dpiMultiplier / oldDpiMultiplier; + float oldDpiMultiplier = this->dpiMultiplier; + this->dpiMultiplier = dpi / 96.f; + float scale = this->dpiMultiplier / oldDpiMultiplier; - this->dpiMultiplierChanged(oldDpiMultiplier, this->dpiMultiplier); + this->dpiMultiplierChanged(oldDpiMultiplier, this->dpiMultiplier); - this->resize(static_cast(this->width() * scale), - static_cast(this->height() * scale)); + this->resize(static_cast(this->width() * scale), + static_cast(this->height() * scale)); - return true; + return true; + } + case WM_NCCALCSIZE: { + if (this->hasCustomWindowFrame()) { + // this kills the window frame and title bar we added with + // WS_THICKFRAME and WS_CAPTION + *result = 0; + return true; + } else { + return QWidget::nativeEvent(eventType, message, result); + } + break; + } + case WM_NCHITTEST: { + if (this->hasCustomWindowFrame()) { + *result = 0; + const LONG border_width = 8; // in pixels + RECT winrect; + GetWindowRect((HWND)winId(), &winrect); + + long x = GET_X_LPARAM(msg->lParam); + long y = GET_Y_LPARAM(msg->lParam); + + bool resizeWidth = minimumWidth() != maximumWidth(); + bool resizeHeight = minimumHeight() != maximumHeight(); + + if (resizeWidth) { + // left border + if (x >= winrect.left && x < winrect.left + border_width) { + *result = HTLEFT; + } + // right border + if (x < winrect.right && x >= winrect.right - border_width) { + *result = HTRIGHT; + } + } + if (resizeHeight) { + // bottom border + if (y < winrect.bottom && y >= winrect.bottom - border_width) { + *result = HTBOTTOM; + } + // top border + if (y >= winrect.top && y < winrect.top + border_width) { + *result = HTTOP; + } + } + if (resizeWidth && resizeHeight) { + // bottom left corner + if (x >= winrect.left && x < winrect.left + border_width && + y < winrect.bottom && y >= winrect.bottom - border_width) { + *result = HTBOTTOMLEFT; + } + // bottom right corner + if (x < winrect.right && x >= winrect.right - border_width && + y < winrect.bottom && y >= winrect.bottom - border_width) { + *result = HTBOTTOMRIGHT; + } + // top left corner + if (x >= winrect.left && x < winrect.left + border_width && y >= winrect.top && + y < winrect.top + border_width) { + *result = HTTOPLEFT; + } + // top right corner + if (x < winrect.right && x >= winrect.right - border_width && + y >= winrect.top && y < winrect.top + border_width) { + *result = HTTOPRIGHT; + } + } + + if (*result == 0) { + bool client = false; + + QPoint point(x - winrect.left, y - winrect.top); + for (QWidget *widget : this->widgets) { + if (widget->geometry().contains(point)) { + client = true; + } + } + + if (client) { + *result = HTCLIENT; + } else { + *result = HTCAPTION; + } + } + + qDebug() << *result; + + return true; + } else { + return QWidget::nativeEvent(eventType, message, result); + } + break; + } // end case WM_NCHITTEST + case WM_CLOSE: { + if (this->enableCustomFrame) { + return close(); + } + break; + } + default: + return QWidget::nativeEvent(eventType, message, result); } +} - return QWidget::nativeEvent(eventType, message, result); -} // namespace widgets +void BaseWindow::showEvent(QShowEvent *) +{ + if (this->isVisible() && this->hasCustomWindowFrame()) { + SetWindowLongPtr((HWND)this->winId(), GWL_STYLE, + WS_POPUP | WS_CAPTION | WS_THICKFRAME | WS_MAXIMIZEBOX | WS_MINIMIZEBOX); + const MARGINS shadow = {1, 1, 1, 1}; + DwmExtendFrameIntoClientArea((HWND)this->winId(), &shadow); + + SetWindowPos((HWND)this->winId(), 0, 0, 0, 0, 0, + SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE); + } +} + +void BaseWindow::paintEvent(QPaintEvent *event) +{ + BaseWidget::paintEvent(event); + + if (this->hasCustomWindowFrame()) { + QPainter painter(this); + + bool windowFocused = this->window() == QApplication::activeWindow(); + + if (windowFocused) { + painter.setPen(this->themeManager.tabs.selected.backgrounds.regular.color()); + } else { + painter.setPen(this->themeManager.tabs.selected.backgrounds.unfocused.color()); + } + painter.drawRect(0, 0, this->width() - 1, this->height() - 1); + } +} #endif } // namespace widgets diff --git a/src/widgets/basewindow.hpp b/src/widgets/basewindow.hpp index fdad9fe65..5e1374f9b 100644 --- a/src/widgets/basewindow.hpp +++ b/src/widgets/basewindow.hpp @@ -2,26 +2,47 @@ #include "basewidget.hpp" +class QHBoxLayout; + namespace chatterino { namespace widgets { class BaseWindow : public BaseWidget { public: - explicit BaseWindow(singletons::ThemeManager &_themeManager, QWidget *parent); - explicit BaseWindow(BaseWidget *parent); - explicit BaseWindow(QWidget *parent = nullptr); + explicit BaseWindow(singletons::ThemeManager &_themeManager, QWidget *parent, + bool enableCustomFrame = false); + explicit BaseWindow(BaseWidget *parent, bool enableCustomFrame = false); + explicit BaseWindow(QWidget *parent = nullptr, bool enableCustomFrame = false); + + QWidget *getLayoutContainer(); + bool hasCustomWindowFrame(); + void addTitleBarButton(const QString &text); protected: #ifdef USEWINSDK + virtual void showEvent(QShowEvent *); virtual bool nativeEvent(const QByteArray &eventType, void *message, long *result) override; + virtual void paintEvent(QPaintEvent *event) override; #endif virtual void changeEvent(QEvent *) override; virtual void leaveEvent(QEvent *) override; + virtual void refreshTheme() override; + private: void init(); + + bool enableCustomFrame; + + QHBoxLayout *titlebarBox; + QWidget *titleLabel; + QWidget *minButton; + QWidget *maxButton; + QWidget *exitButton; + QWidget *layoutBase; + std::vector widgets; }; } // namespace widgets } // namespace chatterino diff --git a/src/widgets/helper/rippleeffectlabel.hpp b/src/widgets/helper/rippleeffectlabel.hpp index 430793f58..4e1f47800 100644 --- a/src/widgets/helper/rippleeffectlabel.hpp +++ b/src/widgets/helper/rippleeffectlabel.hpp @@ -15,7 +15,7 @@ namespace widgets { class RippleEffectLabel : public RippleEffectButton { public: - explicit RippleEffectLabel(BaseWidget *parent, int spacing = 6); + explicit RippleEffectLabel(BaseWidget *parent = nullptr, int spacing = 6); SignalLabel &getLabel() { diff --git a/src/widgets/notebook.cpp b/src/widgets/notebook.cpp index 2321e0887..c204e5d0f 100644 --- a/src/widgets/notebook.cpp +++ b/src/widgets/notebook.cpp @@ -24,6 +24,7 @@ namespace widgets { Notebook::Notebook(Window *parent, bool _showButtons, const std::string &settingPrefix) : BaseWidget(parent) + , parentWindow(parent) , settingRoot(fS("{}/notebook", settingPrefix)) , addButton(this) , settingsButton(this) @@ -196,14 +197,15 @@ void Notebook::performLayout(bool animated) int x = 0, y = 0; float scale = this->getDpiMultiplier(); + bool customFrame = this->parentWindow->hasCustomWindowFrame(); - if (!showButtons || settings.hidePreferencesButton) { + if (!this->showButtons || settings.hidePreferencesButton || customFrame) { this->settingsButton.hide(); } else { this->settingsButton.show(); x += settingsButton.width(); } - if (!showButtons || settings.hideUserButton) { + if (!this->showButtons || settings.hideUserButton || customFrame) { this->userButton.hide(); } else { this->userButton.move(x, 0); @@ -211,7 +213,8 @@ void Notebook::performLayout(bool animated) x += userButton.width(); } - if (!showButtons || (settings.hideUserButton && settings.hidePreferencesButton)) { + if (customFrame || !this->showButtons || + (settings.hideUserButton && settings.hidePreferencesButton)) { x += (int)(scale * 2); } diff --git a/src/widgets/notebook.hpp b/src/widgets/notebook.hpp index b0b9a1551..8cbe3bc85 100644 --- a/src/widgets/notebook.hpp +++ b/src/widgets/notebook.hpp @@ -57,6 +57,8 @@ public slots: void addPageButtonClicked(); private: + Window *parentWindow; + QList pages; NotebookButton addButton; diff --git a/src/widgets/window.cpp b/src/widgets/window.cpp index 805c88c70..ba78195b3 100644 --- a/src/widgets/window.cpp +++ b/src/widgets/window.cpp @@ -18,7 +18,7 @@ namespace widgets { Window::Window(const QString &windowName, singletons::ThemeManager &_themeManager, bool _isMainWindow) - : BaseWindow(_themeManager, nullptr) + : BaseWindow(_themeManager, nullptr, true) , settingRoot(fS("/windows/{}", windowName)) , windowGeometry(this->settingRoot) , dpi(this->getDpiMultiplier()) @@ -34,6 +34,11 @@ Window::Window(const QString &windowName, singletons::ThemeManager &_themeManage } }); + if (this->hasCustomWindowFrame()) { + this->addTitleBarButton("Preferences"); + this->addTitleBarButton("User"); + } + QVBoxLayout *layout = new QVBoxLayout(this); // add titlebar @@ -42,7 +47,7 @@ Window::Window(const QString &windowName, singletons::ThemeManager &_themeManage // } layout->addWidget(&this->notebook); - setLayout(layout); + this->getLayoutContainer()->setLayout(layout); // set margin // if (SettingsManager::getInstance().useCustomWindowFrame.get()) { @@ -139,14 +144,6 @@ void Window::closeEvent(QCloseEvent *) this->closed(); } -void Window::refreshTheme() -{ - QPalette palette; - palette.setColor(QPalette::Background, - this->themeManager.tabs.regular.backgrounds.regular.color()); - this->setPalette(palette); -} - void Window::loadGeometry() { bool doSetGeometry = false; diff --git a/src/widgets/window.hpp b/src/widgets/window.hpp index c8c91cb92..475eff728 100644 --- a/src/widgets/window.hpp +++ b/src/widgets/window.hpp @@ -63,8 +63,6 @@ private: float dpi; - virtual void refreshTheme() override; - void loadGeometry(); Notebook notebook;