#include "MainItem.h" #include "DashboardPage.h" #include "DevicesPage.h" #include "MenuBar.h" #include "MembersPage.h" #include "RoomsPage.h" #include "StatisticsPage.h" #include "StoragePage.h" #include #include #include #include #include #include #include #include #include #include namespace { class PanRecognizer final : public QskPanGestureRecognizer { public: PanRecognizer( MainItem* mainItem ) : QskPanGestureRecognizer( mainItem ) { setOrientations( Qt::Horizontal | Qt::Vertical ); setMinDistance( 50 ); setTimeout( 100 ); setWatchedItem( mainItem ); } }; } QPair< Cube::Position, Cube::Edge > Cube::s_neighbors[ Cube::NumPositions ][ Cube::NumEdges ] = { // neighbors of Left side: { { Cube::BackPos, Cube::BottomEdge }, // going Left { Cube::FrontPos, Cube::BottomEdge }, // going Right { Cube::TopPos, Cube::LeftEdge }, // going Top { Cube::BottomPos, Cube::RightEdge } // going Bottom }, // Right: { { Cube::FrontPos, Cube::BottomEdge }, { Cube::BackPos, Cube::BottomEdge }, { Cube::TopPos, Cube::RightEdge }, { Cube::BottomPos, Cube::LeftEdge } }, // Top: { { Cube::LeftPos, Cube::RightEdge }, { Cube::RightPos, Cube::LeftEdge }, { Cube::BackPos, Cube::TopEdge }, { Cube::FrontPos, Cube::BottomEdge } }, // Bottom: { { Cube::LeftPos, Cube::LeftEdge }, { Cube::RightPos, Cube::RightEdge }, { Cube::FrontPos, Cube::BottomEdge }, { Cube::BackPos, Cube::TopEdge } }, // Front: { { Cube::LeftPos, Cube::BottomEdge }, { Cube::RightPos, Cube::BottomEdge }, { Cube::TopPos, Cube::BottomEdge }, { Cube::BottomPos, Cube::BottomEdge } }, // Back: { { Cube::RightPos, Cube::BottomEdge }, { Cube::LeftPos, Cube::BottomEdge }, { Cube::TopPos, Cube::TopEdge }, { Cube::BottomPos, Cube::BottomEdge } } }; Cube::Edge Cube::s_edgeTransformations[ Cube::NumEdges ][ Cube::NumEdges ] = { // current edge is LeftEdge: { Cube::TopEdge, // Left Cube::BottomEdge, // Right Cube::RightEdge, // Top Cube::LeftEdge }, // Bottom // Right: { Cube::BottomEdge, Cube::TopEdge, Cube::LeftEdge, Cube::RightEdge }, // Top: { Cube::RightEdge, Cube::LeftEdge, Cube::BottomEdge, Cube::TopEdge }, // Bottom: { Cube::LeftEdge, Cube::RightEdge, Cube::TopEdge, Cube::BottomEdge } }; Cube::Cube( QQuickItem* parent ) : QskStackBox( false, parent ) , m_destination( FrontPos ) , m_currentEdge( BottomEdge ) , m_isIntermediateHop( false ) { // The code below covers the case where we need 2 cube movements to get // to the desired position. // We use transientIndexChanged here to be sure to start a new transition // at the end; indexChanged doesn't work here. connect( this, &QskStackBox::transientIndexChanged, this, [ this ]( qreal position ) { const bool animationIsFinished = ( position == qFloor( position ) ); if( animationIsFinished && position != m_destination ) { QTimer::singleShot( 0, this, [this]() { m_isIntermediateHop = true; switchToPosition( m_destination ); } ); } } ); QTimer::singleShot( 0, this, [this]() { Q_EMIT cubeIndexChanged( m_destination ); } ); } void Cube::doSwitch( Qsk::Direction direction, Position position ) { using Animator = QskStackBoxAnimator4; auto animator = qobject_cast< Animator* >( this->animator() ); if ( animator == nullptr ) { animator = new Animator( this ); animator->setDuration( 1000 ); setAnimator( animator ); } if( position == m_destination && !m_isIntermediateHop ) // 1 hop { animator->setEasingCurve( QEasingCurve::InOutQuad ); } else if( !m_isIntermediateHop ) // 1st of 2 hops { animator->setEasingCurve( QEasingCurve::InQuad ); } else // 2nd of 2 hops { animator->setEasingCurve( QEasingCurve::OutQuad ); m_isIntermediateHop = false; } const auto orientation = ( direction == Qsk::LeftToRight || direction == Qsk::RightToLeft ) ? Qt::Horizontal : Qt::Vertical; animator->setOrientation( orientation ); const bool inverted = ( direction == Qsk::LeftToRight || direction == Qsk::TopToBottom ); animator->setInverted( inverted ); updateEdge( direction, position ); setCurrentIndex( position ); if( position == m_destination ) { Q_EMIT cubeIndexChanged( position ); } } void Cube::switchPosition( const Qsk::Direction direction ) { m_destination = neighbor( currentPosition(), direction ); doSwitch( direction, m_destination ); } void Cube::switchToPosition( const Position position ) { if( currentPosition() == position ) return; m_destination = position; const auto direction = this->direction( currentPosition(), position ); const auto nextPosition = neighbor( currentPosition(), direction ); doSwitch( direction, nextPosition ); } Cube::Position Cube::currentPosition() const { return static_cast< Position >( currentIndex() ); } Cube::Position Cube::neighbor( const Position position, const Qsk::Direction direction ) const { const auto index = s_edgeTransformations[ m_currentEdge ][ direction ]; const auto n = s_neighbors[ position ][ index ].first; return n; } Qsk::Direction Cube::direction( const Position from, const Position to ) const { // if direct neighbor: use that direction // otherwise: we need 2 swipes, direction doesn't matter, so choose right to left const auto neighbors = s_neighbors[ from ]; for( int i = 0; i < NumEdges; ++i ) { if( neighbors[ i ].first == to ) { return static_cast< Qsk::Direction >( i ); } } return Qsk::RightToLeft; } void Cube::updateEdge( Qsk::Direction direction, Position position ) { m_currentEdge = s_neighbors[ currentPosition() ][ direction ].second; // When going back to Front, Left etc., switch back to // the bottom edge, otherwise it gets to confusing: if( position != TopPos && position != BottomPos ) { m_currentEdge = BottomEdge; } } MainItem::MainItem( QQuickItem* parent ) : QskControl( parent ) , m_mainLayout( new QskLinearBox( Qt::Horizontal, this ) ) , m_menuBar( new MenuBar( m_mainLayout ) ) , m_cube( new Cube( m_mainLayout ) ) { setAutoLayoutChildren( true ); setAcceptedMouseButtons( Qt::LeftButton ); setFiltersChildMouseEvents( true ); (void) new PanRecognizer( this ); m_mainLayout->setSpacing( 0 ); connect( m_menuBar, &MenuBar::pageChangeRequested, this, [this]( int index ) { const auto position = static_cast< Cube::Position >( index ); m_cube->switchToPosition( position ); } ); connect( m_cube, &Cube::cubeIndexChanged, m_menuBar, &MenuBar::setActivePage ); auto* const dashboardPage = new DashboardPage( m_cube ); auto* const roomsPage = new RoomsPage( m_cube ); auto* const devicesPage = new DevicesPage( m_cube ); auto* const statisticsPage = new StatisticsPage( m_cube ); auto* const storagePage = new StoragePage( m_cube ); auto* const membersPage = new MembersPage( m_cube ); m_cube->insertItem( Cube::LeftPos, statisticsPage ); m_cube->insertItem( Cube::RightPos, roomsPage ); m_cube->insertItem( Cube::TopPos, storagePage ); m_cube->insertItem( Cube::BottomPos, membersPage ); m_cube->insertItem( Cube::FrontPos, dashboardPage ); m_cube->insertItem( Cube::BackPos, devicesPage ); // the current item needs to be the one at the Front: m_cube->setCurrentItem( dashboardPage ); } void MainItem::gestureEvent( QskGestureEvent* event ) { if( event->gesture()->state() == QskGesture::Finished && event->gesture()->type() == QskGesture::Pan ) { const auto* panGesture = static_cast< const QskPanGesture* >( event->gesture().get() ); const auto delta = panGesture->origin() - panGesture->position(); Qsk::Direction direction; if( qAbs( delta.x() ) > qAbs( delta.y() ) ) { direction = ( delta.x() < 0 ) ? Qsk::LeftToRight : Qsk::RightToLeft; } else { direction = ( delta.y() < 0 ) ? Qsk::TopToBottom : Qsk::BottomToTop; } m_cube->switchPosition( direction ); } } void MainItem::keyPressEvent( QKeyEvent* event ) { // maybe using shortcuts ? Qsk::Direction direction; switch( event->key() ) { case Qt::Key_Up: direction = Qsk::TopToBottom; break; case Qt::Key_Down: direction = Qsk::BottomToTop; break; case Qt::Key_Left: direction = Qsk::LeftToRight; break; case Qt::Key_Right: direction = Qsk::RightToLeft; break; default: return; } m_cube->switchPosition( direction ); } #include "moc_MainItem.cpp"