From 415ef7e9c7b9a44a3f5da725cd252a454e4969f8 Mon Sep 17 00:00:00 2001 From: Konstantinos Sideris Date: Wed, 26 Apr 2017 02:23:12 +0300 Subject: [PATCH] Add spinner to hide uninitialized layout after login --- CMakeLists.txt | 3 + include/MainWindow.h | 10 +- include/ui/CircularProgress.h | 114 ++++++++++++++++++++ include/ui/OverlayModal.h | 60 +++++++++++ include/ui/OverlayWidget.h | 4 +- src/MainWindow.cc | 41 ++++++++ src/ui/CircularProgress.cc | 190 ++++++++++++++++++++++++++++++++++ src/ui/OverlayModal.cc | 72 +++++++++++++ src/ui/OverlayWidget.cc | 9 +- 9 files changed, 494 insertions(+), 9 deletions(-) create mode 100644 include/ui/CircularProgress.h create mode 100644 include/ui/OverlayModal.h create mode 100644 src/ui/CircularProgress.cc create mode 100644 src/ui/OverlayModal.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index b008ab45..aa272a5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -100,7 +100,9 @@ set(SRC_FILES src/ui/Avatar.cc src/ui/Badge.cc + src/ui/CircularProgress.cc src/ui/FlatButton.cc + src/ui/OverlayModal.cc src/ui/RaisedButton.cc src/ui/Ripple.cc src/ui/RippleOverlay.cc @@ -142,6 +144,7 @@ qt5_wrap_cpp(MOC_HEADERS include/ui/Avatar.h include/ui/Badge.h + include/ui/CircularProgress.h include/ui/FlatButton.h include/ui/OverlayWidget.h include/ui/RaisedButton.h diff --git a/include/MainWindow.h b/include/MainWindow.h index 89d1df45..f1582f85 100644 --- a/include/MainWindow.h +++ b/include/MainWindow.h @@ -22,8 +22,10 @@ #include #include "ChatPage.h" +#include "CircularProgress.h" #include "LoginPage.h" #include "MatrixClient.h" +#include "OverlayModal.h" #include "RegisterPage.h" #include "SlidingStackWidget.h" #include "WelcomePage.h" @@ -41,7 +43,7 @@ public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); -public slots: +private slots: // Show the welcome page in the main window. void showWelcomePage(); @@ -54,6 +56,8 @@ public slots: // Show the chat page and start communicating with the given access token. void showChatPage(QString user_id, QString home_server, QString token); + void removeOverlayProgressBar(); + private: // The UI component of the main window. Ui::MainWindow *ui_; @@ -73,6 +77,10 @@ private: // The main chat area. ChatPage *chat_page_; + // Used to hide undefined states between page transitions. + OverlayModal *progress_modal_; + CircularProgress *spinner_; + // Matrix Client API provider. QSharedPointer client_; }; diff --git a/include/ui/CircularProgress.h b/include/ui/CircularProgress.h new file mode 100644 index 00000000..bb8156ae --- /dev/null +++ b/include/ui/CircularProgress.h @@ -0,0 +1,114 @@ +#ifndef UI_CIRCULAR_PROGRESS_H +#define UI_CIRCULAR_PROGRESS_H + +#include +#include + +#include "Theme.h" + +class CircularProgressDelegate; + +class CircularProgress : public QProgressBar +{ + Q_OBJECT + + Q_PROPERTY(qreal lineWidth WRITE setLineWidth READ lineWidth) + Q_PROPERTY(qreal size WRITE setSize READ size) + Q_PROPERTY(QColor color WRITE setColor READ color) + +public: + explicit CircularProgress(QWidget *parent = nullptr); + ~CircularProgress(); + + void setProgressType(ui::ProgressType type); + void setLineWidth(qreal width); + void setSize(int size); + void setColor(const QColor &color); + + ui::ProgressType progressType() const; + qreal lineWidth() const; + int size() const; + QColor color() const; + + QSize sizeHint() const override; + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + CircularProgressDelegate *delegate_; + + ui::ProgressType progress_type_; + + QColor color_; + + // Circle width. + qreal width_; + + // Circle radius. + int size_; +}; + +class CircularProgressDelegate : public QObject +{ + Q_OBJECT + + Q_PROPERTY(qreal dashOffset WRITE setDashOffset READ dashOffset) + Q_PROPERTY(qreal dashLength WRITE setDashLength READ dashLength) + Q_PROPERTY(int angle WRITE setAngle READ angle) + +public: + explicit CircularProgressDelegate(CircularProgress *parent); + ~CircularProgressDelegate(); + + inline void setDashOffset(qreal offset); + inline void setDashLength(qreal length); + inline void setAngle(int angle); + + inline qreal dashOffset() const; + inline qreal dashLength() const; + inline int angle() const; + +private: + CircularProgress *const progress_; + + qreal dash_offset_; + qreal dash_length_; + + int angle_; +}; + +inline void CircularProgressDelegate::setDashOffset(qreal offset) +{ + dash_offset_ = offset; + progress_->update(); +} + +inline void CircularProgressDelegate::setDashLength(qreal length) +{ + dash_length_ = length; + progress_->update(); +} + +inline void CircularProgressDelegate::setAngle(int angle) +{ + angle_ = angle; + progress_->update(); +} + +inline qreal CircularProgressDelegate::dashOffset() const +{ + return dash_offset_; +} + +inline qreal CircularProgressDelegate::dashLength() const +{ + return dash_length_; +} + +inline int CircularProgressDelegate::angle() const +{ + return angle_; +} + +#endif // UI_CIRCULAR_PROGRESS_H diff --git a/include/ui/OverlayModal.h b/include/ui/OverlayModal.h new file mode 100644 index 00000000..aff93d02 --- /dev/null +++ b/include/ui/OverlayModal.h @@ -0,0 +1,60 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef UI_OVERLAY_MODAL_H +#define UI_OVERLAY_MODAL_H + +#include +#include +#include + +#include "OverlayWidget.h" + +class OverlayModal : public OverlayWidget +{ +public: + explicit OverlayModal(QWidget *parent, QWidget *content); + + void fadeIn(); + void fadeOut(); + +public: + inline void setDuration(int duration); + inline void setColor(QColor color); + +protected: + void paintEvent(QPaintEvent *event) override; + +private: + int duration_; + QColor color_; + + QGraphicsOpacityEffect *opacity_; + QPropertyAnimation *animation_; +}; + +inline void OverlayModal::setDuration(int duration) +{ + duration_ = duration; +} + +inline void OverlayModal::setColor(QColor color) +{ + color_ = color; +} + +#endif // UI_OVERLAY_MODAL_H diff --git a/include/ui/OverlayWidget.h b/include/ui/OverlayWidget.h index 020393ad..36b42dc9 100644 --- a/include/ui/OverlayWidget.h +++ b/include/ui/OverlayWidget.h @@ -2,7 +2,6 @@ #define UI_OVERLAY_WIDGET_H #include -#include #include class OverlayWidget : public QWidget @@ -10,8 +9,7 @@ class OverlayWidget : public QWidget Q_OBJECT public: - explicit OverlayWidget(QWidget *parent = 0); - ~OverlayWidget(); + explicit OverlayWidget(QWidget *parent = nullptr); protected: bool event(QEvent *event) override; diff --git a/src/MainWindow.cc b/src/MainWindow.cc index 3d591755..e43cd383 100644 --- a/src/MainWindow.cc +++ b/src/MainWindow.cc @@ -25,6 +25,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui_(new Ui::MainWindow) + , progress_modal_{nullptr} + , spinner_{nullptr} { ui_->setupUi(this); client_ = QSharedPointer(new MatrixClient("matrix.org")); @@ -53,12 +55,40 @@ MainWindow::MainWindow(QWidget *parent) connect(chat_page_, SIGNAL(close()), this, SLOT(showWelcomePage())); connect(chat_page_, SIGNAL(changeWindowTitle(QString)), this, SLOT(setWindowTitle(QString))); + connect(client_.data(), + SIGNAL(initialSyncCompleted(const SyncResponse &)), + this, + SLOT(removeOverlayProgressBar())); + connect(client_.data(), SIGNAL(loginSuccess(QString, QString, QString)), this, SLOT(showChatPage(QString, QString, QString))); } +void MainWindow::removeOverlayProgressBar() +{ + QTimer *timer = new QTimer(this); + timer->setSingleShot(true); + + connect(timer, &QTimer::timeout, [=]() { + timer->deleteLater(); + + if (progress_modal_ != nullptr) { + progress_modal_->deleteLater(); + progress_modal_->fadeOut(); + } + + if (progress_modal_ != nullptr) + spinner_->deleteLater(); + + progress_modal_ = nullptr; + spinner_ = nullptr; + }); + + timer->start(500); +} + void MainWindow::showChatPage(QString userid, QString homeserver, QString token) { QSettings settings; @@ -69,6 +99,17 @@ void MainWindow::showChatPage(QString userid, QString homeserver, QString token) int index = sliding_stack_->getWidgetIndex(chat_page_); sliding_stack_->slideInIndex(index, SlidingStackWidget::AnimationDirection::LEFT_TO_RIGHT); + if (spinner_ == nullptr) { + spinner_ = new CircularProgress(this); + spinner_->setColor("#acc7dc"); + spinner_->setSize(100); + } + + if (progress_modal_ == nullptr) { + progress_modal_ = new OverlayModal(this, spinner_); + progress_modal_->fadeIn(); + } + login_page_->reset(); chat_page_->bootstrap(userid, homeserver, token); } diff --git a/src/ui/CircularProgress.cc b/src/ui/CircularProgress.cc new file mode 100644 index 00000000..fa74b64c --- /dev/null +++ b/src/ui/CircularProgress.cc @@ -0,0 +1,190 @@ +#include +#include +#include +#include + +#include "CircularProgress.h" +#include "Theme.h" + +CircularProgress::CircularProgress(QWidget *parent) + : QProgressBar{parent} + , progress_type_{ui::ProgressType::IndeterminateProgress} + , width_{6.25} + , size_{64} +{ + delegate_ = new CircularProgressDelegate(this); + + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + + auto group = new QParallelAnimationGroup(this); + group->setLoopCount(-1); + + auto length_animation = new QPropertyAnimation(this); + length_animation->setPropertyName("dashLength"); + length_animation->setTargetObject(delegate_); + length_animation->setEasingCurve(QEasingCurve::InOutQuad); + length_animation->setStartValue(0.1); + length_animation->setKeyValueAt(0.15, 0.2); + length_animation->setKeyValueAt(0.6, 20); + length_animation->setKeyValueAt(0.7, 20); + length_animation->setEndValue(20); + length_animation->setDuration(2050); + + auto offset_animation = new QPropertyAnimation(this); + offset_animation->setPropertyName("dashOffset"); + offset_animation->setTargetObject(delegate_); + offset_animation->setEasingCurve(QEasingCurve::InOutSine); + offset_animation->setStartValue(0); + offset_animation->setKeyValueAt(0.15, 0); + offset_animation->setKeyValueAt(0.6, -7); + offset_animation->setKeyValueAt(0.7, -7); + offset_animation->setEndValue(-25); + offset_animation->setDuration(2050); + + auto angle_animation = new QPropertyAnimation(this); + angle_animation->setPropertyName("angle"); + angle_animation->setTargetObject(delegate_); + angle_animation->setStartValue(0); + angle_animation->setEndValue(719); + angle_animation->setDuration(2050); + + group->addAnimation(length_animation); + group->addAnimation(offset_animation); + group->addAnimation(angle_animation); + + group->start(); +} + +void CircularProgress::setProgressType(ui::ProgressType type) +{ + progress_type_ = type; + update(); +} + +void CircularProgress::setLineWidth(qreal width) +{ + width_ = width; + update(); + updateGeometry(); +} + +void CircularProgress::setSize(int size) +{ + size_ = size; + update(); + updateGeometry(); +} + +ui::ProgressType CircularProgress::progressType() const +{ + return progress_type_; +} + +qreal CircularProgress::lineWidth() const +{ + return width_; +} + +int CircularProgress::size() const +{ + return size_; +} + +void CircularProgress::setColor(const QColor &color) +{ + color_ = color; +} + +QColor CircularProgress::color() const +{ + if (!color_.isValid()) { + return QColor("red"); + } + + return color_; +} + +QSize CircularProgress::sizeHint() const +{ + const qreal s = size_ + width_ + 8; + return QSize(s, s); +} + +void CircularProgress::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + + /* + * If the progress bar is disabled draw an X instead + */ + if (!isEnabled()) { + QPen pen; + pen.setCapStyle(Qt::RoundCap); + pen.setWidthF(lineWidth()); + pen.setColor("gray"); + + auto center = rect().center(); + + painter.setPen(pen); + painter.drawLine(center - QPointF(20, 20), center + QPointF(20, 20)); + painter.drawLine(center + QPointF(20, -20), center - QPointF(20, -20)); + + return; + } + + if (progress_type_ == ui::ProgressType::IndeterminateProgress) { + painter.translate(width() / 2, height() / 2); + painter.rotate(delegate_->angle()); + } + + QPen pen; + pen.setCapStyle(Qt::RoundCap); + pen.setWidthF(width_); + pen.setColor(color()); + + if (ui::IndeterminateProgress == progress_type_) { + QVector pattern; + pattern << delegate_->dashLength() * size_ / 50 << 30 * size_ / 50; + + pen.setDashOffset(delegate_->dashOffset() * size_ / 50); + pen.setDashPattern(pattern); + + painter.setPen(pen); + + painter.drawEllipse(QPoint(0, 0), size_ / 2, size_ / 2); + } else { + painter.setPen(pen); + + const qreal x = (width() - size_) / 2; + const qreal y = (height() - size_) / 2; + + const qreal a = 360 * (value() - minimum()) / (maximum() - minimum()); + + QPainterPath path; + path.arcMoveTo(x, y, size_, size_, 0); + path.arcTo(x, y, size_, size_, 0, a); + + painter.drawPath(path); + } +} + +CircularProgress::~CircularProgress() +{ +} + +CircularProgressDelegate::CircularProgressDelegate(CircularProgress *parent) + : QObject(parent) + , progress_(parent) + , dash_offset_(0) + , dash_length_(89) + , angle_(0) +{ + Q_ASSERT(parent); +} + +CircularProgressDelegate::~CircularProgressDelegate() +{ +} diff --git a/src/ui/OverlayModal.cc b/src/ui/OverlayModal.cc new file mode 100644 index 00000000..7af29268 --- /dev/null +++ b/src/ui/OverlayModal.cc @@ -0,0 +1,72 @@ +/* + * nheko Copyright (C) 2017 Konstantinos Sideris + * + * 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, either version 3 of the License, or + * (at your option) any later version. + * + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "OverlayModal.h" + +OverlayModal::OverlayModal(QWidget *parent, QWidget *content) + : OverlayWidget(parent) + , duration_{500} + , color_{QColor(55, 55, 55)} +{ + setAttribute(Qt::WA_TranslucentBackground); + + auto layout = new QVBoxLayout(); + layout->addWidget(content); + layout->setAlignment(Qt::AlignCenter); + + setLayout(layout); + + opacity_ = new QGraphicsOpacityEffect(this); + setGraphicsEffect(opacity_); + + opacity_->setOpacity(1); + animation_ = new QPropertyAnimation(opacity_, "opacity", this); + animation_->setStartValue(1); + animation_->setEndValue(0); + animation_->setDuration(duration_); + animation_->setEasingCurve(QEasingCurve::Linear); + + connect(animation_, &QPropertyAnimation::finished, [this]() { + if (animation_->direction() == QAbstractAnimation::Forward) + this->close(); + }); +} + +void OverlayModal::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + + QPainter painter(this); + painter.fillRect(rect(), color_); +} + +void OverlayModal::fadeIn() +{ + animation_->setDirection(QAbstractAnimation::Backward); + animation_->start(); + show(); +} + +void OverlayModal::fadeOut() +{ + animation_->setDirection(QAbstractAnimation::Forward); + animation_->start(); +} diff --git a/src/ui/OverlayWidget.cc b/src/ui/OverlayWidget.cc index b4dfb918..d7e6337b 100644 --- a/src/ui/OverlayWidget.cc +++ b/src/ui/OverlayWidget.cc @@ -4,12 +4,11 @@ OverlayWidget::OverlayWidget(QWidget *parent) : QWidget(parent) { - if (parent) + if (parent) { parent->installEventFilter(this); -} - -OverlayWidget::~OverlayWidget() -{ + setGeometry(overlayGeometry()); + raise(); + } } bool OverlayWidget::event(QEvent *event)