From 404f4b3c7ed25f7787ee7ff4621b971e9920d832 Mon Sep 17 00:00:00 2001 From: johanneshilden Date: Fri, 29 Sep 2017 20:25:42 +0300 Subject: [PATCH] Add Tabs --- components/components.pro | 9 +- components/qtmaterialtabs.cpp | 236 +++++++++++++++++++++++++ components/qtmaterialtabs.h | 58 ++++++ components/qtmaterialtabs_internal.cpp | 178 +++++++++++++++++++ components/qtmaterialtabs_internal.h | 88 +++++++++ components/qtmaterialtabs_p.h | 34 ++++ examples/mainwindow.cpp | 6 +- 7 files changed, 606 insertions(+), 3 deletions(-) create mode 100644 components/qtmaterialtabs.cpp create mode 100644 components/qtmaterialtabs.h create mode 100644 components/qtmaterialtabs_internal.cpp create mode 100644 components/qtmaterialtabs_internal.h create mode 100644 components/qtmaterialtabs_p.h diff --git a/components/components.pro b/components/components.pro index 9fac05b..72914f3 100644 --- a/components/components.pro +++ b/components/components.pro @@ -27,7 +27,9 @@ SOURCES = \ qtmaterialtoggle_internal.cpp \ qtmaterialtoggle.cpp \ qtmaterialtextfield_internal.cpp \ - qtmaterialtextfield.cpp + qtmaterialtextfield.cpp \ + qtmaterialtabs_internal.cpp \ + qtmaterialtabs.cpp HEADERS = \ qtmaterialavatar_p.h \ qtmaterialavatar.h \ @@ -72,6 +74,9 @@ HEADERS = \ qtmaterialtoggle.h \ qtmaterialtextfield_internal.h \ qtmaterialtextfield_p.h \ - qtmaterialtextfield.h + qtmaterialtextfield.h \ + qtmaterialtabs_internal.h \ + qtmaterialtabs_p.h \ + qtmaterialtabs.h RESOURCES += \ resources.qrc diff --git a/components/qtmaterialtabs.cpp b/components/qtmaterialtabs.cpp new file mode 100644 index 0000000..118a2b6 --- /dev/null +++ b/components/qtmaterialtabs.cpp @@ -0,0 +1,236 @@ +#include "qtmaterialtabs.h" +#include "qtmaterialtabs_p.h" +#include +#include "qtmaterialtabs_internal.h" +#include "lib/qtmaterialstyle.h" + +/*! + * \QtMaterialTabsPrivate + * \internal + */ + +QtMaterialTabsPrivate::QtMaterialTabsPrivate(QtMaterialTabs *q) + : q_ptr(q) +{ +} + +QtMaterialTabsPrivate::~QtMaterialTabsPrivate() +{ +} + +void QtMaterialTabsPrivate::QtMaterialTabsPrivate::init() +{ + Q_Q(QtMaterialTabs); + + inkBar = new QtMaterialTabsInkBar(q); + tabLayout = new QHBoxLayout; + rippleStyle = Material::CenteredRipple; + tab = -1; + showHalo = true; + useThemeColors = true; + + q->setLayout(tabLayout); + q->setStyle(&QtMaterialStyle::instance()); + + tabLayout->setSpacing(0); + tabLayout->setMargin(0); +} + +/*! + * \QtMaterialTabs + */ + +QtMaterialTabs::QtMaterialTabs(QWidget *parent) + : QWidget(parent), + d_ptr(new QtMaterialTabsPrivate(this)) +{ + d_func()->init(); +} + +QtMaterialTabs::~QtMaterialTabs() +{ +} + +void QtMaterialTabs::setUseThemeColors(bool value) +{ + Q_D(QtMaterialTabs); + + d->useThemeColors = value; +} + +bool QtMaterialTabs::useThemeColors() const +{ + Q_D(const QtMaterialTabs); + + return d->useThemeColors; +} + +void QtMaterialTabs::setHaloVisible(bool value) +{ + Q_D(QtMaterialTabs); + + d->showHalo = value; + updateTabs(); +} + +bool QtMaterialTabs::isHaloVisible() const +{ + Q_D(const QtMaterialTabs); + + return d->showHalo; +} + +void QtMaterialTabs::setRippleStyle(Material::RippleStyle style) +{ + Q_D(QtMaterialTabs); + + d->rippleStyle = style; + updateTabs(); +} + +Material::RippleStyle QtMaterialTabs::rippleStyle() const +{ + Q_D(const QtMaterialTabs); + + return d->rippleStyle; +} + +void QtMaterialTabs::setInkColor(const QColor &color) +{ + Q_D(QtMaterialTabs); + + d->inkColor = color; + setUseThemeColors(false); + d->inkBar->update(); +} + +QColor QtMaterialTabs::inkColor() const +{ + Q_D(const QtMaterialTabs); + + if (d->useThemeColors || !d->inkColor.isValid()) { + return QtMaterialStyle::instance().themeColor("accent1"); + } else { + return d->inkColor; + } +} + +void QtMaterialTabs::setBackgroundColor(const QColor &color) +{ + Q_D(QtMaterialTabs); + + d->backgroundColor = color; + setUseThemeColors(false); + updateTabs(); +} + +QColor QtMaterialTabs::backgroundColor() const +{ + Q_D(const QtMaterialTabs); + + if (d->useThemeColors || !d->backgroundColor.isValid()) { + return QtMaterialStyle::instance().themeColor("primary1"); + } else { + return d->backgroundColor; + } +} + +void QtMaterialTabs::setTextColor(const QColor &color) +{ + Q_D(QtMaterialTabs); + + d->textColor = color; + setUseThemeColors(false); + updateTabs(); +} + +QColor QtMaterialTabs::textColor() const +{ + Q_D(const QtMaterialTabs); + + if (d->useThemeColors || !d->textColor.isValid()) { + return QtMaterialStyle::instance().themeColor("canvas"); + } else { + return d->textColor; + } +} + +void QtMaterialTabs::setCurrentTab(QtMaterialTab *tab) +{ + Q_D(QtMaterialTabs); + + setCurrentTab(d->tabLayout->indexOf(tab)); +} + +void QtMaterialTabs::setCurrentTab(int index) +{ + Q_D(QtMaterialTabs); + + setTabActive(d->tab, false); + d->tab = index; + setTabActive(index, true); + d->inkBar->animate(); + + emit currentChanged(index); +} + +void QtMaterialTabs::addTab(const QString &text, const QIcon &icon) +{ + Q_D(QtMaterialTabs); + + QtMaterialTab *tab = new QtMaterialTab(this); + tab->setText(text); + tab->setHaloVisible(isHaloVisible()); + tab->setRippleStyle(rippleStyle()); + + if (!icon.isNull()) { + tab->setIcon(icon); + tab->setIconSize(QSize(22, 22)); + } + + d->tabLayout->addWidget(tab); + + if (-1 == d->tab) { + d->tab = 0; + d->inkBar->refreshGeometry(); + d->inkBar->raise(); + tab->setActive(true); + } +} + +int QtMaterialTabs::currentIndex() const +{ + Q_D(const QtMaterialTabs); + + return d->tab; +} + +void QtMaterialTabs::setTabActive(int index, bool active) +{ + Q_D(QtMaterialTabs); + + QtMaterialTab *tab; + + if (index > -1) { + tab = static_cast(d->tabLayout->itemAt(index)->widget()); + if (tab) { + tab->setActive(active); + } + } +} + +void QtMaterialTabs::updateTabs() +{ + Q_D(QtMaterialTabs); + + QtMaterialTab *tab; + for (int i = 0; i < d->tabLayout->count(); ++i) { + QLayoutItem *item = d->tabLayout->itemAt(i); + if ((tab = static_cast(item->widget()))) { + tab->setRippleStyle(d->rippleStyle); + tab->setHaloVisible(d->showHalo); + tab->setBackgroundColor(backgroundColor()); + tab->setForegroundColor(textColor()); + } + } +} diff --git a/components/qtmaterialtabs.h b/components/qtmaterialtabs.h new file mode 100644 index 0000000..11a122e --- /dev/null +++ b/components/qtmaterialtabs.h @@ -0,0 +1,58 @@ +#ifndef QTMATERIALTABS_H +#define QTMATERIALTABS_H + +#include +#include +#include "lib/qtmaterialtheme.h" + +class QtMaterialTabsPrivate; +class QtMaterialTab; + +class QtMaterialTabs : public QWidget +{ + Q_OBJECT + +public: + explicit QtMaterialTabs(QWidget *parent = 0); + ~QtMaterialTabs(); + + void setUseThemeColors(bool value); + bool useThemeColors() const; + + void setHaloVisible(bool value); + bool isHaloVisible() const; + + void setRippleStyle(Material::RippleStyle style); + Material::RippleStyle rippleStyle() const; + + void setInkColor(const QColor &color); + QColor inkColor() const; + + void setBackgroundColor(const QColor &color); + QColor backgroundColor() const; + + void setTextColor(const QColor &color); + QColor textColor() const; + + void addTab(const QString &text, const QIcon &icon = QIcon()); + + void setCurrentTab(QtMaterialTab *tab); + void setCurrentTab(int index); + + int currentIndex() const; + +signals: + void currentChanged(int); + +protected: + void setTabActive(int index, bool active = true); + void updateTabs(); + + const QScopedPointer d_ptr; + +private: + Q_DISABLE_COPY(QtMaterialTabs) + Q_DECLARE_PRIVATE(QtMaterialTabs) +}; + +#endif // QTMATERIALTABS_H diff --git a/components/qtmaterialtabs_internal.cpp b/components/qtmaterialtabs_internal.cpp new file mode 100644 index 0000000..e952c02 --- /dev/null +++ b/components/qtmaterialtabs_internal.cpp @@ -0,0 +1,178 @@ +#include "qtmaterialtabs_internal.h" +#include +#include +#include +#include +#include +#include "qtmaterialtabs.h" +#include + +/*! + * \class QtMaterialTabsInkBar + * \internal + */ + +QtMaterialTabsInkBar::QtMaterialTabsInkBar(QtMaterialTabs *parent) + : QtMaterialOverlayWidget(parent), + m_tabs(parent), + m_animation(new QPropertyAnimation(parent)), + m_tween(0) +{ + Q_ASSERT(parent); + + m_animation->setPropertyName("tweenValue"); + m_animation->setEasingCurve(QEasingCurve::OutCirc); + m_animation->setTargetObject(this); + m_animation->setDuration(700); + + m_tabs->installEventFilter(this); + + setAttribute(Qt::WA_TransparentForMouseEvents); + setAttribute(Qt::WA_NoSystemBackground); +} + +QtMaterialTabsInkBar::~QtMaterialTabsInkBar() +{ +} + +void QtMaterialTabsInkBar::refreshGeometry() +{ + QLayoutItem *item = m_tabs->layout()->itemAt(m_tabs->currentIndex()); + + if (item) + { + const QRect r(item->geometry()); + const qreal s = 1-m_tween; + + if (QAbstractAnimation::Running != m_animation->state()) { + m_geometry = QRect(r.left(), r.bottom()-1, r.width(), 2); + } else { + const qreal left = m_previousGeometry.left()*s + r.left()*m_tween; + const qreal width = m_previousGeometry.width()*s + r.width()*m_tween; + m_geometry = QRect(left, r.bottom()-1, width, 2); + } + m_tabs->update(); + } +} + +void QtMaterialTabsInkBar::animate() +{ + raise(); + + m_previousGeometry = m_geometry; + + m_animation->stop(); + m_animation->setStartValue(0); + m_animation->setEndValue(1); + m_animation->start(); +} + +bool QtMaterialTabsInkBar::eventFilter(QObject *obj, QEvent *event) +{ + switch (event->type()) + { + case QEvent::Move: + case QEvent::Resize: + { + refreshGeometry(); + break; + } + default: + break; + } + return QtMaterialOverlayWidget::eventFilter(obj, event); +} + +void QtMaterialTabsInkBar::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event) + + QPainter painter(this); + + painter.setOpacity(1); + painter.fillRect(m_geometry, m_tabs->inkColor()); +} + +/*! + * \class QtMaterialTab + * \internal + */ + +QtMaterialTab::QtMaterialTab(QtMaterialTabs *parent) + : QtMaterialFlatButton(parent), + m_tabs(parent), + m_active(false) +{ + Q_ASSERT(parent); + + setMinimumHeight(50); + + QFont f(font()); + f.setStyleName("Normal"); + setFont(f); + + setCornerRadius(0); + setRole(Material::Primary); + setBackgroundMode(Qt::OpaqueMode); + setBaseOpacity(0.25); + + connect(this, SIGNAL(clicked(bool)), this, SLOT(activateTab())); +} + +QtMaterialTab::~QtMaterialTab() +{ +} + +QSize QtMaterialTab::sizeHint() const +{ + if (icon().isNull()) { + return QtMaterialFlatButton::sizeHint(); + } else { + return QSize(40, iconSize().height()+46); + } +} + +void QtMaterialTab::activateTab() +{ + m_tabs->setCurrentTab(this); +} + +void QtMaterialTab::paintForeground(QPainter *painter) +{ + painter->setPen(foregroundColor()); + + if (!icon().isNull()) { + painter->translate(0, 12); + } + + QSize textSize(fontMetrics().size(Qt::TextSingleLine, text())); + QSize base(size()-textSize); + + QRect textGeometry(QPoint(base.width(), base.height())/2, textSize); + + painter->drawText(textGeometry, Qt::AlignCenter, text()); + + if (!icon().isNull()) + { + const QSize &size = iconSize(); + QRect iconRect(QPoint((width()-size.width())/2, 0), size); + + QPixmap pixmap = icon().pixmap(iconSize()); + QPainter icon(&pixmap); + icon.setCompositionMode(QPainter::CompositionMode_SourceIn); + icon.fillRect(pixmap.rect(), painter->pen().color()); + painter->drawPixmap(iconRect, pixmap); + } + + if (!m_active) + { + if (!icon().isNull()) { + painter->translate(0, -12); + } + QBrush overlay; + overlay.setStyle(Qt::SolidPattern); + overlay.setColor(backgroundColor()); + painter->setOpacity(0.36); + painter->fillRect(rect(), overlay); + } +} diff --git a/components/qtmaterialtabs_internal.h b/components/qtmaterialtabs_internal.h new file mode 100644 index 0000000..75dc034 --- /dev/null +++ b/components/qtmaterialtabs_internal.h @@ -0,0 +1,88 @@ +#ifndef QTMATERIALTABS_INTERNAL_H +#define QTMATERIALTABS_INTERNAL_H + +#include "lib/qtmaterialoverlaywidget.h" +#include "qtmaterialflatbutton.h" + +class QPropertyAnimation; +class QtMaterialTabs; + +class QtMaterialTabsInkBar : public QtMaterialOverlayWidget +{ + Q_OBJECT + + Q_PROPERTY(qreal tweenValue WRITE setTweenValue READ tweenValue) + +public: + QtMaterialTabsInkBar(QtMaterialTabs *parent); + ~QtMaterialTabsInkBar(); + + inline void setTweenValue(qreal value); + inline qreal tweenValue() const; + + void refreshGeometry(); + void animate(); + +protected: + bool eventFilter(QObject *obj, QEvent *event) Q_DECL_OVERRIDE; + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(QtMaterialTabsInkBar) + + QtMaterialTabs *const m_tabs; + QPropertyAnimation *const m_animation; + QRect m_geometry; + QRect m_previousGeometry; + qreal m_tween; +}; + +inline void QtMaterialTabsInkBar::setTweenValue(qreal value) +{ + m_tween = value; + refreshGeometry(); +} + +inline qreal QtMaterialTabsInkBar::tweenValue() const +{ + return m_tween; +} + +class QtMaterialTab : public QtMaterialFlatButton +{ + Q_OBJECT + +public: + explicit QtMaterialTab(QtMaterialTabs *parent); + ~QtMaterialTab(); + + inline void setActive(bool state); + inline bool isActive() const; + + QSize sizeHint() const Q_DECL_OVERRIDE; + +protected slots: + void activateTab(); + +protected: + void paintForeground(QPainter *painter) Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(QtMaterialTab) + + QtMaterialTabs *const m_tabs; + bool m_active; +}; + +inline void QtMaterialTab::setActive(bool state) +{ + m_active = state; + update(); +} + +inline bool QtMaterialTab::isActive() const +{ + return m_active; +} + +#endif // QTMATERIALTABS_INTERNAL_H diff --git a/components/qtmaterialtabs_p.h b/components/qtmaterialtabs_p.h new file mode 100644 index 0000000..1f32b04 --- /dev/null +++ b/components/qtmaterialtabs_p.h @@ -0,0 +1,34 @@ +#ifndef QTMATERIALTABS_P_H +#define QTMATERIALTABS_P_H + +#include +#include "lib/qtmaterialtheme.h" + +class QHBoxLayout; +class QtMaterialTabs; +class QtMaterialTabsInkBar; + +class QtMaterialTabsPrivate +{ + Q_DISABLE_COPY(QtMaterialTabsPrivate) + Q_DECLARE_PUBLIC(QtMaterialTabs) + +public: + QtMaterialTabsPrivate(QtMaterialTabs *q); + ~QtMaterialTabsPrivate(); + + void init(); + + QtMaterialTabs *const q_ptr; + QtMaterialTabsInkBar *inkBar; + QHBoxLayout *tabLayout; + Material::RippleStyle rippleStyle; + QColor inkColor; + QColor backgroundColor; + QColor textColor; + int tab; + bool showHalo; + bool useThemeColors; +}; + +#endif // QTMATERIALTABS_P_H diff --git a/examples/mainwindow.cpp b/examples/mainwindow.cpp index fd4208d..146dc78 100644 --- a/examples/mainwindow.cpp +++ b/examples/mainwindow.cpp @@ -15,6 +15,7 @@ #include "radiobuttonsettingseditor.h" #include "togglesettingseditor.h" #include "textfieldsettingseditor.h" +#include "tabsexamples.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) @@ -27,7 +28,10 @@ MainWindow::MainWindow(QWidget *parent) QStackedLayout *stack = new QStackedLayout; QListWidget *list = new QListWidget; - setCentralWidget(widget); + TabsExamples *te = new TabsExamples; + + setCentralWidget(te); + return; layout->addWidget(list); layout->addLayout(stack);