From a5cf5acad92ae9f8957215b4cd3f4b203ee408bd Mon Sep 17 00:00:00 2001 From: Rick Vogel Date: Wed, 4 Jan 2023 17:17:17 +0100 Subject: [PATCH] iot storage page --- examples/iotdashboard/Skin.cpp | 2 + examples/iotdashboard/StorageBar.cpp | 207 +++++++++++++++++++++++++ examples/iotdashboard/StorageBar.h | 48 ++++++ examples/iotdashboard/StorageMeter.cpp | 46 ++++++ examples/iotdashboard/StorageMeter.h | 14 ++ examples/iotdashboard/StoragePage.cpp | 155 ++++++++++++++++-- examples/iotdashboard/iotdashboard.pro | 8 +- 7 files changed, 465 insertions(+), 15 deletions(-) create mode 100644 examples/iotdashboard/StorageBar.cpp create mode 100644 examples/iotdashboard/StorageBar.h create mode 100644 examples/iotdashboard/StorageMeter.cpp create mode 100644 examples/iotdashboard/StorageMeter.h diff --git a/examples/iotdashboard/Skin.cpp b/examples/iotdashboard/Skin.cpp index bf546719..3a04fcad 100644 --- a/examples/iotdashboard/Skin.cpp +++ b/examples/iotdashboard/Skin.cpp @@ -19,6 +19,7 @@ #include "RoomsPage.h" #include "RoundedIcon.h" #include "RoundButton.h" +#include "StoragePage.h" #include "TopBar.h" #include "UsageBox.h" #include "UsageDiagram.h" @@ -241,6 +242,7 @@ void Skin::initHints( const Palette& palette ) ed.setGradient( MenuBar::Panel, palette.menuBar ); ed.setGradient( DashboardPage::Panel, palette.mainContent ); ed.setGradient( RoomsPage::Panel, palette.mainContent ); + ed.setGradient( StoragePage::Panel, palette.mainContent ); ed.setColor( Box::Panel, palette.box ); QskShadowMetrics shadowMetrics( 0, 10 ); diff --git a/examples/iotdashboard/StorageBar.cpp b/examples/iotdashboard/StorageBar.cpp new file mode 100644 index 00000000..1a9c261e --- /dev/null +++ b/examples/iotdashboard/StorageBar.cpp @@ -0,0 +1,207 @@ +#include "StorageBar.h" +#include +#include +#include +#include + +QSK_SUBCONTROL( StorageBar, Pictures) +QSK_SUBCONTROL( StorageBar, Music) +QSK_SUBCONTROL( StorageBar, Videos) +QSK_SUBCONTROL( StorageBar, Documents) +QSK_SUBCONTROL( StorageBar, Others ) +QSK_SUBCONTROL( StorageBar, Free ) + +class StorageBarSkinlet final : public QskSkinlet +{ + Q_GADGET +public: + + using Inherited = QskSkinlet; + enum NodeRole { Pictures, Music, Videos, Documents, Others, Free }; + Q_INVOKABLE StorageBarSkinlet( QskSkin* skin = nullptr ) : Inherited(skin) + { + setNodeRoles( { Pictures, Music, Videos, Documents, Others, Free } ); + setOwnedBySkinnable(true); + } + +private: + + QRectF subControlRect( const QskSkinnable* skinnable, const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const override + { + using S = StorageBar; + const auto bar = static_cast< const S* >( skinnable ); + + auto x = contentsRect.x(); + const auto y = contentsRect.y(); + const auto h = contentsRect.height(); + + const QRectF p {x, y, contentsRect.width() * bar->pictures(), h}; + if(subControl == S::Pictures){ return p; } + x += p.width(); + + const QRectF m {x, y, contentsRect.width() * bar->music(), h}; + if(subControl == S::Music){ return m; } + x += m.width(); + + const QRectF v {x, y, contentsRect.width() * bar->videos(), h}; + if(subControl == S::Videos){ return v; } + x += v.width(); + + const QRectF d {x, y, contentsRect.width() * bar->documents(), h}; + if(subControl == S::Documents){ return d; } + x += d.width(); + + const QRectF o {x, y, contentsRect.width() * bar->others(), h}; + if(subControl == S::Others){ return o; } + x += o.width(); + + const QRectF f {x, y, contentsRect.width() * bar->free(), h}; + if(subControl == S::Free){ return f; } + + return Inherited::subControlRect( skinnable, contentsRect, subControl ); + } + + QSGNode* updateSubNode( const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node) const override + { + using S = StorageBar; + const auto bar = static_cast< const S* >( skinnable ); + + if ( nodeRole == Pictures) + { + const QskGradient g( { { 0.0, bar->color(S::Pictures).lighter() },{ 1.0, bar->color(S::Pictures) } } ); + return updateBoxNode( bar, node, bar->subControlRect( S::Pictures ), g, S::Pictures ); + } + + if ( nodeRole == Music) + { + const QskGradient g( { { 0.0, bar->color(S::Music).lighter() },{ 1.0, bar->color(S::Music) } } ); + return updateBoxNode( bar, node, bar->subControlRect( S::Music ), g, S::Music ); + } + + if ( nodeRole == Videos) + { + const QskGradient g( { { 0.0, bar->color(S::Videos).lighter() },{ 1.0, bar->color(S::Videos) } } ); + return updateBoxNode( bar, node, bar->subControlRect( S::Videos ), g, S::Videos ); + } + + if ( nodeRole == Documents) + { + const QskGradient g( { { 0.0, bar->color(S::Documents).lighter() },{ 1.0, bar->color(S::Documents) } } ); + return updateBoxNode( bar, node, bar->subControlRect( S::Documents ), g, S::Documents ); + } + + if ( nodeRole == Others) + { + const QskGradient g( { { 0.0, bar->color(S::Others).lighter() },{ 1.0, bar->color(S::Others) } } ); + return updateBoxNode( bar, node, bar->subControlRect( S::Others ), g, S::Others ); + } + + if ( nodeRole == Free) + { + const QskGradient g( { { 0.0, bar->color(S::Free).lighter() },{ 1.0, bar->color(S::Free)} } ); + return updateBoxNode( bar, node, bar->subControlRect( S::Free ), g, S::Free ); + } + + return Inherited::updateSubNode( skinnable, nodeRole, node ); + } +}; + +StorageBar::StorageBar(QskQuickItem * const parent) : Inherited(parent) +{ + setSkinlet(new StorageBarSkinlet()); + + using S = StorageBar; + + // TODO move into skin? + setColor(S::Pictures, "#FFBE0B"); + setColor(S::Music,"#FB5607"); + setColor(S::Videos,"#FF006E"); + setColor(S::Documents, "#8338EC"); + setColor(S::Others, "#3A86FF"); + setColor(S::Free, "lightgray"); + + static constexpr qreal size = 16.0; + static constexpr qreal radius = size / 2.0; + + setMinimumSize(-1, size); + setMaximumSize(-1, size); + + setBoxShapeHint(S::Pictures, {radius, 0.0, radius, 0.0}); + setBoxShapeHint(S::Free, { 0.0, radius, 0.0, radius}); +} + +qreal StorageBar::pictures() const +{ + return m_pictures; +} + +void StorageBar::setPictures(qreal newPictures) +{ + if (qFuzzyCompare(m_pictures, newPictures)) + {return;} + + m_pictures = newPictures; + Q_EMIT picturesChanged(m_pictures); + update(); +} + +qreal StorageBar::music() const +{ + return m_music; +} + +void StorageBar::setMusic(qreal newMusic) +{ + if (qFuzzyCompare(m_music, newMusic)) + {return;} + m_music = newMusic; + Q_EMIT musicChanged(m_music); + update(); +} + +qreal StorageBar::videos() const +{ + return m_videos; +} + +void StorageBar::setVideos(qreal newVideos) +{ + if (qFuzzyCompare(m_videos, newVideos)) + {return;} + m_videos = newVideos; + Q_EMIT videosChanged(m_videos); + update(); +} + +qreal StorageBar::documents() const +{ + return m_documents; +} + +void StorageBar::setDocuments(qreal newDocuments) +{ + if (qFuzzyCompare(m_documents, newDocuments)) + {return;} + m_documents = newDocuments; + Q_EMIT documentsChanged(m_documents); + update(); +} + +qreal StorageBar::others() const +{ + return m_others; +} + +void StorageBar::setOthers(qreal newOthers) +{ + if (qFuzzyCompare(m_others, newOthers)) + {return;} + m_others = newOthers; + Q_EMIT othersChanged(m_others); + update(); +} + +qreal StorageBar::free() const +{ + return 1.0 - m_pictures - m_music - m_videos - m_documents - m_others; +} diff --git a/examples/iotdashboard/StorageBar.h b/examples/iotdashboard/StorageBar.h new file mode 100644 index 00000000..f3466468 --- /dev/null +++ b/examples/iotdashboard/StorageBar.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +class StorageBar final : public QskControl +{ + using Inherited = QskControl; + + Q_OBJECT + Q_PROPERTY(qreal pictures READ pictures WRITE setPictures NOTIFY picturesChanged) + Q_PROPERTY(qreal music READ music WRITE setMusic NOTIFY musicChanged) + Q_PROPERTY(qreal videos READ videos WRITE setVideos NOTIFY videosChanged) + Q_PROPERTY(qreal documents READ documents WRITE setDocuments NOTIFY documentsChanged) + Q_PROPERTY(qreal others READ others WRITE setOthers NOTIFY othersChanged) + +public: + + QSK_SUBCONTROLS( Pictures, Music, Videos, Documents, Others, Free ) + explicit StorageBar(QskQuickItem* parent = nullptr); + + qreal pictures() const; + void setPictures(qreal newPictures); + qreal music() const; + void setMusic(qreal newMusic); + qreal videos() const; + void setVideos(qreal newVideos); + qreal documents() const; + void setDocuments(qreal newDocuments); + qreal others() const; + void setOthers(qreal newOthers); + qreal free() const; + +Q_SIGNALS: + + void picturesChanged(qreal value); + void musicChanged(qreal value); + void videosChanged(qreal value); + void documentsChanged(qreal value); + void othersChanged(qreal value); + +private: + + qreal m_pictures; + qreal m_music; + qreal m_videos; + qreal m_documents; + qreal m_others; +}; diff --git a/examples/iotdashboard/StorageMeter.cpp b/examples/iotdashboard/StorageMeter.cpp new file mode 100644 index 00000000..fc6dd236 --- /dev/null +++ b/examples/iotdashboard/StorageMeter.cpp @@ -0,0 +1,46 @@ +#include "StorageMeter.h" +#include "CircularProgressBar.h" +#include + +QSK_SUBCONTROL( StorageMeter, Fill ) + +StorageMeter::StorageMeter(QQuickItem *parent) noexcept + : EnergyMeter(QColor{}, QskGradient{}, 0, parent) +{ + const auto gradient = gradientHint(StorageMeter::Fill); + setGradientHint(StorageMeter::Fill, QskGradient(QskGradientStops{ + {0.0,Qt::green}, + {0.5,"darkorange"}, + {1.0,Qt::red} + })); +} + +qreal StorageMeter::progress() const noexcept +{ + if( auto* const bar = findChild() ) + { + return bar->value() / 100.0; + } + return 0.0; +} + +void StorageMeter::setProgress(qreal progress) noexcept +{ + const auto value = qBound(0.0, progress, 1.0); + + const auto gradient = gradientHint(StorageMeter::Fill); + + const auto color = gradient.extracted( value, value ).startColor(); + + if( auto* const bar = findChild() ) + { + bar->setGradientHint( CircularProgressBar::Bar, {color, color.darker(100)}); + bar->setValue(value * 100.0); + } + + if ( auto* const label = findChild() ) + { + label->setTextColor( color ); + label->setText( locale().toString( static_cast(value * 100.0) ) + " " + locale().percent() ); + } +} diff --git a/examples/iotdashboard/StorageMeter.h b/examples/iotdashboard/StorageMeter.h new file mode 100644 index 00000000..3f4ed665 --- /dev/null +++ b/examples/iotdashboard/StorageMeter.h @@ -0,0 +1,14 @@ +#pragma once + +#include "EnergyMeter.h" +#include + +class StorageMeter final : public EnergyMeter +{ + public: + QSK_SUBCONTROLS( Fill ) + explicit StorageMeter(QQuickItem* parent = nullptr) noexcept; + qreal progress() const noexcept; + public Q_SLOTS: + void setProgress(qreal progress) noexcept; +}; diff --git a/examples/iotdashboard/StoragePage.cpp b/examples/iotdashboard/StoragePage.cpp index 934b4f59..36c9ef7f 100644 --- a/examples/iotdashboard/StoragePage.cpp +++ b/examples/iotdashboard/StoragePage.cpp @@ -1,21 +1,148 @@ -/****************************************************************************** - * Copyright (C) 2021 Edelhirsch Software GmbH - * This file may be used under the terms of the 3-clause BSD License - *****************************************************************************/ - #include "StoragePage.h" - +#include "CircularProgressBar.h" +#include "Box.h" +#include "EnergyMeter.h" +#include "Diagram.h" +#include "StorageMeter.h" +#include "StorageBar.h" +#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include QSK_SUBCONTROL( StoragePage, Panel ) -StoragePage::StoragePage( QQuickItem* parent ) - : QskLinearBox( Qt::Vertical, parent ) -{ - auto* const textLabel = new QskTextLabel( "storage page", this ); - textLabel->setAlignmentHint( QskTextLabel::Text, Qt::AlignCenter ); - textLabel->setFontRole( QskSkin::HugeFont ); -} +struct Media { + qreal pictures = 0; + qreal music = 0; + qreal videos = 0; + qreal documents = 0; + qreal others = 0; -#include "moc_StoragePage.cpp" + inline constexpr bool operator==(const Media& rhs) const noexcept + { + return pictures == rhs.pictures && music == rhs.music && videos == rhs.videos && documents == rhs.documents && others == rhs.others; + } + + inline constexpr qreal free() const noexcept + { + return 1.0 - pictures - music - videos - documents - others; + } +}; + +StoragePage::StoragePage(QQuickItem * const parent) : QskLinearBox(Qt::Vertical, parent) +{ + const auto createProgressBar = [](const qreal value, QQuickItem* const parent) + { + auto* const bar = new StorageMeter(parent); + bar->setProgress(value); + bar->setMinimumSize(16,16); + bar->setMaximumSize(64,64); + bar->setSizePolicy( QskSizePolicy::Preferred, QskSizePolicy::Constrained ); + return bar; + }; + + const auto createLinearBox = [](Qt::Orientation orientation = Qt::Vertical, QskSizePolicy::Policy hp = QskSizePolicy::Fixed , QskSizePolicy::Policy vp = QskSizePolicy::Fixed, QQuickItem* const parent = nullptr){ + auto* const layout = new QskLinearBox(orientation, parent); + layout->setSizePolicy( hp, vp ); + return layout; + }; + + const auto createRow = [&](const QString& titleText, const QString& subTitleText, const Media& media, QQuickItem* const parent){ + auto* const box = new Box("Network Storage", parent); + auto* const layout = new QskLinearBox(Qt::Horizontal, box); + auto* const left = new QskLinearBox(Qt::Vertical, layout); + auto* const center = new QskLinearBox(Qt::Vertical, layout); + auto* const right = new QskLinearBox(Qt::Vertical, layout); + auto* const stack = new QskStackBox(left); + stack->setAutoLayoutChildren(true); + const auto percent = 1.0 - media.free(); + auto* const bar = createProgressBar(percent, createLinearBox(Qt::Vertical, QskSizePolicy::Preferred, QskSizePolicy::Preferred, stack)); + + stack->addItem(bar); + stack->setCurrentItem(bar); + auto* title = new QskTextLabel(titleText, center); + title->setFontRole(QskSkin::LargeFont); + auto* subtitle = new QskTextLabel(subTitleText, center); + subtitle->setFontRole(QskSkin::MediumFont); + center->addSpacer(1,99); + + auto* const storageBar = new StorageBar(right); + storageBar->setPictures(media.pictures); + storageBar->setMusic(media.music); + storageBar->setVideos(media.videos); + storageBar->setDocuments(media.documents); + storageBar->setOthers(media.others); + + auto* const mediaLegend = new QskLinearBox(Qt::Horizontal, right); + mediaLegend->setSpacing(12); + mediaLegend->addSpacer(1,999); + auto* const sync = new QskPushButton("Update", mediaLegend); + sync->setFontRoleHint(QskPushButton::Text, QskSkin::SmallFont); + connect(sync, &QskPushButton::clicked, storageBar, [bar, storageBar, media](){ + struct PropertyAnimator final : public QskAnimator + { + void advance( qreal value ) override { callback(value); } + void done() override { delete this; } + std::function callback = [](qreal){}; + }; + + auto* const animator = new PropertyAnimator(); + animator->setEasingCurve(QEasingCurve::InQuad); + animator->setWindow(storageBar->window()); + animator->callback = [bar, storageBar, media](qreal v){ + bar->setProgress((1.0 - media.free()) * v); + storageBar->setPictures(media.pictures * v); + storageBar->setMusic(media.music * v); + storageBar->setVideos(media.videos * v); + storageBar->setDocuments(media.documents * v); + storageBar->setOthers(media.others * v); + }; + animator->setDuration(400); + animator->start(); + }); + + using S = StorageBar; + const auto subcontrols = QVector{S::Pictures, S::Music, S::Videos, S::Documents, S::Others, S::Free}; + const auto subcontrolNames = QStringList{"Picture", "Music", "Videos", "Documents", "Others", "Free"}; + Q_ASSERT(subcontrolNames.size() == subcontrols.size()); + + for(int i = 0; i < subcontrolNames.size(); ++i) + { + auto* const dot = new QskBox(mediaLegend); + dot->setBoxShapeHint(QskBox::Panel,{4}); + dot->setMinimumSize(8,8); + dot->setMaximumSize(8,8); + const auto color = storageBar->color(subcontrols[i]); + dot->setGradientHint(QskBox::Panel, {color.lighter(), color}); + auto* const label = new QskTextLabel(subcontrolNames[i], mediaLegend); + label->setFontRole(QskSkin::SmallFont); + } + + layout->setStretchFactor(left,1); + layout->setStretchFactor(center,2); + layout->setStretchFactor(right,5); + return layout; + }; + + setPanel( true ); + setSizePolicy( QskSizePolicy::Expanding, QskSizePolicy::Expanding ); + setDefaultAlignment( Qt::AlignTop ); + setSpacing( 24 ); + setMargins(30); + setSizePolicy(Qt::Horizontal, QskSizePolicy::Minimum); + + setSubcontrolProxy( QskBox::Panel, StoragePage::Panel ); + + createRow("Backup (B:)","Used for daily backups", Media{0.1,0.1,0.1,0.02, 0.01}, this); + createRow("Share (S:)","Used for sharing files publicly",Media{0.05,0.05,0.2,0.2,0.01}, this); + createRow("Exchange (X:)","Used for exchanging large files", Media{0.1,0.1,0.1,0.1,0.5}, this); + addSpacer(1, 99); +} diff --git a/examples/iotdashboard/iotdashboard.pro b/examples/iotdashboard/iotdashboard.pro index e4ba4777..0e70ac80 100644 --- a/examples/iotdashboard/iotdashboard.pro +++ b/examples/iotdashboard/iotdashboard.pro @@ -28,6 +28,9 @@ SOURCES += \ RoundButton.cpp \ UsageBox.cpp \ UsageDiagram.cpp \ + StoragePage.cpp \ + StorageMeter.cpp \ + StorageBar.cpp \ main.cpp \ SOURCES += \ @@ -62,7 +65,10 @@ HEADERS += \ TopBar.h \ RoundButton.h \ UsageBox.h \ - UsageDiagram.h + UsageDiagram.h \ + StoragePage.h \ + StorageMeter.h \ + StorageBar.h \ HEADERS += \ nodes/DiagramDataNode.h \