diff --git a/app/app.pro b/app/app.pro index c109d73e22..edc8d9c68f 100644 --- a/app/app.pro +++ b/app/app.pro @@ -76,6 +76,8 @@ PRECOMPILED_HEADER = src/app-pch.h HEADERS += \ src/addtransparencytopaperdialog.h \ src/app-pch.h \ + src/appearance.h \ + src/buttonappearancewatcher.h \ src/importlayersdialog.h \ src/importpositiondialog.h \ src/layeropacitydialog.h \ @@ -92,6 +94,7 @@ HEADERS += \ src/timelinepage.h \ src/toolboxwidget.h \ src/toolspage.h \ + src/titlebarwidget.h \ src/basedockwidget.h \ src/colorbox.h \ src/colorinspector.h \ @@ -128,6 +131,7 @@ HEADERS += \ SOURCES += \ src/addtransparencytopaperdialog.cpp \ + src/buttonappearancewatcher.cpp \ src/importlayersdialog.cpp \ src/importpositiondialog.cpp \ src/layeropacitydialog.cpp \ @@ -145,6 +149,7 @@ SOURCES += \ src/timelinepage.cpp \ src/toolboxwidget.cpp \ src/toolspage.cpp \ + src/titlebarwidget.cpp \ src/basedockwidget.cpp \ src/colorbox.cpp \ src/colorinspector.cpp \ diff --git a/app/data/app.qrc b/app/data/app.qrc index f0b7abb962..5b9116dab2 100644 --- a/app/data/app.qrc +++ b/app/data/app.qrc @@ -94,6 +94,12 @@ icons/blue.png icons/green.png icons/red.png + icons/themes/playful/window/window-float-button-active.svg + icons/themes/playful/window/window-close-button-active.svg + icons/themes/playful/window/window-close-button-normal-darkm.svg + icons/themes/playful/window/window-float-button-normal-darkm.svg + icons/themes/playful/window/window-float-button-normal.svg + icons/themes/playful/window/window-close-button-normal.svg pencil2d_quick_guide.pdf diff --git a/app/data/icons/themes/playful/window/window-close-button-active.svg b/app/data/icons/themes/playful/window/window-close-button-active.svg new file mode 100644 index 0000000000..ff4396864e --- /dev/null +++ b/app/data/icons/themes/playful/window/window-close-button-active.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/data/icons/themes/playful/window/window-close-button-normal-darkm.svg b/app/data/icons/themes/playful/window/window-close-button-normal-darkm.svg new file mode 100644 index 0000000000..71a8c3ca19 --- /dev/null +++ b/app/data/icons/themes/playful/window/window-close-button-normal-darkm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/data/icons/themes/playful/window/window-close-button-normal.svg b/app/data/icons/themes/playful/window/window-close-button-normal.svg new file mode 100644 index 0000000000..d8c2851217 --- /dev/null +++ b/app/data/icons/themes/playful/window/window-close-button-normal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/data/icons/themes/playful/window/window-float-button-active.svg b/app/data/icons/themes/playful/window/window-float-button-active.svg new file mode 100644 index 0000000000..f88c395e5f --- /dev/null +++ b/app/data/icons/themes/playful/window/window-float-button-active.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/data/icons/themes/playful/window/window-float-button-normal-darkm.svg b/app/data/icons/themes/playful/window/window-float-button-normal-darkm.svg new file mode 100644 index 0000000000..196b68a4de --- /dev/null +++ b/app/data/icons/themes/playful/window/window-float-button-normal-darkm.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/data/icons/themes/playful/window/window-float-button-normal.svg b/app/data/icons/themes/playful/window/window-float-button-normal.svg new file mode 100644 index 0000000000..52831817cf --- /dev/null +++ b/app/data/icons/themes/playful/window/window-float-button-normal.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/appearance.h b/app/src/appearance.h new file mode 100644 index 0000000000..81faecfa9c --- /dev/null +++ b/app/src/appearance.h @@ -0,0 +1,38 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2008-2009 Mj Mendoza IV +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ +#ifndef APPEARANCE_H +#define APPEARANCE_H + +#include + +struct IconResource +{ + QIcon lightMode; + QIcon darkMode; + + const QIcon& iconForMode(bool darkmodeEnabled) const + { + if (darkmodeEnabled) { + return darkMode; + } else { + return lightMode; + } + } +}; + +#endif // APPEARANCE_H diff --git a/app/src/basedockwidget.cpp b/app/src/basedockwidget.cpp index 4d9cac3662..6a8c9a0b40 100644 --- a/app/src/basedockwidget.cpp +++ b/app/src/basedockwidget.cpp @@ -20,6 +20,7 @@ GNU General Public License for more details. #include "basedockwidget.h" #include "platformhandler.h" +#include "titlebarwidget.h" BaseDockWidget::BaseDockWidget(QWidget* pParent) : QDockWidget(pParent, Qt::Tool) @@ -34,8 +35,52 @@ BaseDockWidget::BaseDockWidget(QWidget* pParent) "border-width: 1px; }"); } #endif + + mTitleBarWidget = new TitleBarWidget(pParent); + mNoTitleBarWidget = new QWidget(pParent); + + setTitleBarWidget(mTitleBarWidget); + + connect(mTitleBarWidget, &TitleBarWidget::closeButtonPressed, this, &BaseDockWidget::close); + + connect(mTitleBarWidget, &TitleBarWidget::undockButtonPressed, this, [this] { + setFloating(!isFloating()); + }); + + connect(this, &QDockWidget::topLevelChanged, mTitleBarWidget, &TitleBarWidget::setIsFloating); + connect(this, &QDockWidget::windowTitleChanged, mTitleBarWidget, &TitleBarWidget::setTitle); } BaseDockWidget::~BaseDockWidget() { } + +void BaseDockWidget::lock(bool locked) +{ + // https://doc.qt.io/qt-5/qdockwidget.html#setTitleBarWidget + // A empty QWidget results in the title bar being hidden. + // nullptr means removing the custom title bar and restoring the default one + + if (locked) { + setTitleBarWidget(mNoTitleBarWidget); + } else { + setTitleBarWidget(mTitleBarWidget); + } + + mLocked = locked; +} + +void BaseDockWidget::setTitle(const QString& title) +{ + if (!mTitleBarWidget) { return; } + mTitleBarWidget->setTitle(title); +} + +void BaseDockWidget::resizeEvent(QResizeEvent *event) +{ + QDockWidget::resizeEvent(event); + + if (mTitleBarWidget) { + mTitleBarWidget->resizeEvent(event); + } +} diff --git a/app/src/basedockwidget.h b/app/src/basedockwidget.h index 2f4e349264..fd4323c4a1 100644 --- a/app/src/basedockwidget.h +++ b/app/src/basedockwidget.h @@ -21,7 +21,7 @@ GNU General Public License for more details. #include class Editor; - +class TitleBarWidget; class BaseDockWidget : public QDockWidget { @@ -34,11 +34,24 @@ class BaseDockWidget : public QDockWidget virtual void initUI() = 0; virtual void updateUI() = 0; + void lock(bool locked); + void setTitle(const QString& title); + + bool isLocked() const { return mLocked; } + Editor* editor() const { return mEditor; } void setEditor( Editor* e ) { mEditor = e; } +protected: + void resizeEvent(QResizeEvent* event) override; + private: Editor* mEditor = nullptr; + + QWidget* mNoTitleBarWidget = nullptr; + TitleBarWidget* mTitleBarWidget = nullptr; + + bool mLocked = false; }; #endif // BASEDOCKWIDGET_H diff --git a/app/src/buttonappearancewatcher.cpp b/app/src/buttonappearancewatcher.cpp new file mode 100644 index 0000000000..0b41583a94 --- /dev/null +++ b/app/src/buttonappearancewatcher.cpp @@ -0,0 +1,85 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ +#include "buttonappearancewatcher.h" + +#include +#include + +#include "platformhandler.h" + +ButtonAppearanceWatcher::ButtonAppearanceWatcher(IconResource normalIconResource, + IconResource hoverIconResource, + QObject* parent) : + QObject(parent), + mNormalIconResource(normalIconResource), + mHoverIconResource(hoverIconResource) +{} + +bool ButtonAppearanceWatcher::eventFilter(QObject* watched, QEvent* event) +{ + QAbstractButton* button = qobject_cast(watched); + if (!button) { + return false; + } + + IconResource res = mNormalIconResource; + AppearanceEventType apType = determineAppearanceEvent(event); + + if (shouldUpdateResource(event, apType)) { + if (event->type() == QEvent::ApplicationPaletteChange) { + res = mNormalIconResource; + } + else if (event->type() == QEvent::Enter) { + res = mHoverIconResource; + } + else if (event->type() == QEvent::Leave) { + res = mNormalIconResource; + } + mOldAppearanceType = apType; + + bool isDarkmode = PlatformHandler::isDarkMode(); + button->setIcon(res.iconForMode(isDarkmode)); + return true; + } + + return false; +} + +AppearanceEventType ButtonAppearanceWatcher::determineAppearanceEvent(QEvent *event) const +{ + if (event->type() == QEvent::ApplicationPaletteChange) { + bool isDarkmode = PlatformHandler::isDarkMode(); + if (isDarkmode) { + return AppearanceEventType::DARK_APPEARANCE; + } else { + return AppearanceEventType::LIGHT_APPEARANCE; + } + } else if (event->type() == QEvent::Enter) { + return AppearanceEventType::ICON_ACTIVE; + } else if (event->type() == QEvent::Leave) { + return AppearanceEventType::ICON_NORMAL; + } + + return AppearanceEventType::NONE; +} + +bool ButtonAppearanceWatcher::shouldUpdateResource(QEvent* event, AppearanceEventType appearanceType) const +{ + if (appearanceType == mOldAppearanceType) { return false; } + + return determineAppearanceEvent(event) != AppearanceEventType::NONE; +} diff --git a/app/src/buttonappearancewatcher.h b/app/src/buttonappearancewatcher.h new file mode 100644 index 0000000000..9a9d3728ee --- /dev/null +++ b/app/src/buttonappearancewatcher.h @@ -0,0 +1,51 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ +#ifndef BUTTONAPPEARANCEWATCHER_H +#define BUTTONAPPEARANCEWATCHER_H + +enum class AppearanceEventType +{ + NONE, + LIGHT_APPEARANCE, + DARK_APPEARANCE, + ICON_NORMAL, + ICON_ACTIVE +}; + +#include "appearance.h" + +class ButtonAppearanceWatcher : public QObject +{ + Q_OBJECT + +public: + explicit ButtonAppearanceWatcher(IconResource normalIconResource, + IconResource hoverIconResource, + QObject * parent = nullptr); + virtual bool eventFilter(QObject * watched, QEvent * event) override; + +private: + bool shouldUpdateResource(QEvent* event, AppearanceEventType appearanceType) const; + AppearanceEventType determineAppearanceEvent(QEvent* event) const; + + const IconResource mNormalIconResource; + const IconResource mHoverIconResource; + + AppearanceEventType mOldAppearanceType = AppearanceEventType::NONE; +}; + +#endif // BUTTONAPPEARANCEWATCHER_H diff --git a/app/src/mainwindow2.cpp b/app/src/mainwindow2.cpp index ba05e3338c..f46df590b1 100644 --- a/app/src/mainwindow2.cpp +++ b/app/src/mainwindow2.cpp @@ -1030,15 +1030,10 @@ void MainWindow2::lockWidgets(bool shouldLock) QDockWidget::DockWidgetFeature::DockWidgetMovable | QDockWidget::DockWidgetFeature::DockWidgetFloatable); - for (QDockWidget* d : mDockWidgets) + for (BaseDockWidget* d : mDockWidgets) { d->setFeatures(feat); - - // https://doc.qt.io/qt-5/qdockwidget.html#setTitleBarWidget - // A empty QWidget looks like the tittle bar is hidden. - // nullptr means removing the custom title bar and restoring the default one - QWidget* customTitleBarWidget = shouldLock ? (new QWidget) : nullptr; - d->setTitleBarWidget(customTitleBarWidget); + d->lock(shouldLock); } } diff --git a/app/src/titlebarwidget.cpp b/app/src/titlebarwidget.cpp new file mode 100644 index 0000000000..d7aabb95da --- /dev/null +++ b/app/src/titlebarwidget.cpp @@ -0,0 +1,188 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ +#include "titlebarwidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "platformhandler.h" +#include "buttonappearancewatcher.h" + +TitleBarWidget::TitleBarWidget(QWidget* parent) + : QWidget(parent) +{ + + QVBoxLayout* vLayout = new QVBoxLayout(); + + vLayout->setContentsMargins(3,4,3,4); + vLayout->setSpacing(0); + + vLayout->addWidget(createCustomTitleBarWidget(this)); + + setLayout(vLayout); +} + +TitleBarWidget::~TitleBarWidget() +{ +} + +QWidget* TitleBarWidget::createCustomTitleBarWidget(QWidget* parent) +{ + bool isDarkmode = PlatformHandler::isDarkMode(); + QWidget* containerWidget = new QWidget(parent); + + QHBoxLayout* containerLayout = new QHBoxLayout(parent); + + mCloseButton = new QToolButton(parent); + + mCloseButton->setStyleSheet(flatButtonStylesheet()); + + QSize iconSize = QSize(14,14); + QSize padding = QSize(2,2); + + IconResource closeButtonRes; + closeButtonRes.lightMode = QIcon("://icons/themes/playful/window/window-close-button-normal.svg"); + closeButtonRes.darkMode = QIcon("://icons/themes/playful/window/window-close-button-normal-darkm.svg"); + + QIcon closeIcon = closeButtonRes.iconForMode(isDarkmode); + + IconResource closeHoverButtonRes; + closeHoverButtonRes.lightMode = QIcon("://icons/themes/playful/window/window-close-button-active.svg"); + closeHoverButtonRes.darkMode = closeHoverButtonRes.lightMode; + + mCloseButton->setIcon(closeIcon); + mCloseButton->setIconSize(iconSize); + mCloseButton->setFixedSize(iconSize + padding); + mCloseButton->installEventFilter(new ButtonAppearanceWatcher(closeButtonRes, + closeHoverButtonRes, + this)); + + connect(mCloseButton, &QToolButton::clicked, this, &TitleBarWidget::closeButtonPressed); + + IconResource dockButtonRes; + dockButtonRes.lightMode = QIcon("://icons/themes/playful/window/window-float-button-normal.svg"); + dockButtonRes.darkMode = QIcon("://icons/themes/playful/window/window-float-button-normal-darkm.svg"); + + IconResource dockHoverButtonRes; + dockHoverButtonRes.lightMode = QIcon("://icons/themes/playful/window/window-float-button-active.svg"); + dockHoverButtonRes.darkMode = dockHoverButtonRes.lightMode; + + mDockButton = new QToolButton(parent); + + QIcon dockIcon = dockButtonRes.iconForMode(isDarkmode); + mDockButton->setIcon(dockIcon); + mDockButton->setStyleSheet(flatButtonStylesheet()); + + mDockButton->setIconSize(iconSize); + mDockButton->setFixedSize(iconSize + padding); + mDockButton->installEventFilter(new ButtonAppearanceWatcher(dockButtonRes, dockHoverButtonRes, this)); + + connect(mDockButton, &QToolButton::clicked, this, &TitleBarWidget::undockButtonPressed); + + mTitleLabel = new QLabel(parent); + mTitleLabel->setAlignment(Qt::AlignVCenter); + +#ifdef __APPLE__ + containerLayout->addWidget(mCloseButton); + containerLayout->addWidget(mDockButton); + containerLayout->addWidget(mTitleLabel); +#else + containerLayout->addWidget(mTitleLabel); + containerLayout->addWidget(mDockButton); + containerLayout->addWidget(mCloseButton); +#endif + + containerLayout->setSpacing(3); + containerLayout->setContentsMargins(0,0,0,0); + + containerWidget->setLayout(containerLayout); + containerWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); + containerWidget->setMinimumSize(QSize(1,1)); + + return containerWidget; +} + +QString TitleBarWidget::flatButtonStylesheet() const +{ + return "QToolButton { border: 0; }"; +} + +void TitleBarWidget::setTitle(const QString &title) +{ + mTitleLabel->setText(title); +} + +void TitleBarWidget::hideButtons(bool hide) +{ + mCloseButton->setHidden(hide); + mDockButton->setHidden(hide); +} + +void TitleBarWidget::resizeEvent(QResizeEvent *resizeEvent) +{ + QWidget::resizeEvent(resizeEvent); + + hideButtonsIfNeeded(resizeEvent->size().width()); +} + +void TitleBarWidget::hideButtonsIfNeeded(int width) +{ + if (width <= mWidthOfFullLayout) { + hideButtons(true); + } else { + hideButtons(false); + } +} + +void TitleBarWidget::showEvent(QShowEvent* event) +{ + QWidget::showEvent(event); + + // This is to ensure that after the titlebar has been hidden with buttons hidden + // the layout width is smaller, so we enable them again briefly to get the correct width. + hideButtons(false); + + mWidthOfFullLayout = layout()->sizeHint().width(); + hideButtonsIfNeeded(size().width()); +} + +void TitleBarWidget::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + + painter.save(); + painter.setBrush(palette().color(QPalette::Midlight)); + painter.setPen(Qt::NoPen); + painter.drawRect(this->rect()); + + QPen pen(palette().color(QPalette::Mid)); + int penWidth = 1; + pen.setWidth(penWidth); + painter.setPen(pen); + painter.drawLine(QPoint(this->rect().x(), + this->rect().height()-penWidth), + QPoint(this->rect().width(), + this->rect().height()-penWidth)); + painter.restore(); +} diff --git a/app/src/titlebarwidget.h b/app/src/titlebarwidget.h new file mode 100644 index 0000000000..70f1482725 --- /dev/null +++ b/app/src/titlebarwidget.h @@ -0,0 +1,61 @@ +/* + +Pencil2D - Traditional Animation Software +Copyright (C) 2005-2007 Patrick Corrieri & Pascal Naidon +Copyright (C) 2012-2020 Matthew Chiawen Chang + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; version 2 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +*/ +#ifndef TITLEBARWIDGET_H +#define TITLEBARWIDGET_H + +#include + +#include "appearance.h" + +class QLabel; +class QToolButton; + +class TitleBarWidget : public QWidget +{ + Q_OBJECT +public: + explicit TitleBarWidget(QWidget* parent = nullptr); + ~TitleBarWidget(); + + void resizeEvent(QResizeEvent* resizeEvent) override; + void setTitle(const QString& title); + void paintEvent(QPaintEvent*) override; + + void setIsFloating(bool floating) { mIsFloating = floating; } + +signals: + void closeButtonPressed(); + void undockButtonPressed(); + +private: + QString flatButtonStylesheet() const; + void showEvent(QShowEvent* event) override; + void hideButtons(bool hide); + void hideButtonsIfNeeded(int width); + + QWidget* createCustomTitleBarWidget(QWidget* parent); + + QLabel* mTitleLabel = nullptr; + QToolButton* mCloseButton = nullptr; + QToolButton* mDockButton = nullptr; + + bool mIsFloating = false; + + int mWidthOfFullLayout = 0; +}; + +#endif // TITLEBARWIDGET_H diff --git a/core_lib/src/external/linux/linux.cpp b/core_lib/src/external/linux/linux.cpp index 16e86c17d9..7b3ba80af3 100644 --- a/core_lib/src/external/linux/linux.cpp +++ b/core_lib/src/external/linux/linux.cpp @@ -28,6 +28,8 @@ namespace PlatformHandler { void configurePlatformSpecificSettings() {} + bool isDarkMode() { return false; } + void initialise() { /* If running as an AppImage, sets GStreamer environment variables to ensure diff --git a/core_lib/src/external/win32/win32.cpp b/core_lib/src/external/win32/win32.cpp index bb9385344f..48e04ab0fc 100644 --- a/core_lib/src/external/win32/win32.cpp +++ b/core_lib/src/external/win32/win32.cpp @@ -28,7 +28,7 @@ GNU General Public License for more details. namespace PlatformHandler { void configurePlatformSpecificSettings() {} - bool isDarkMode() { return false; }; + bool isDarkMode() { return false; } void initialise() { #if _WIN32_WINNT >= _WIN32_WINNT_WIN7