290 lines
8.1 KiB
C++
Raw Normal View History

/******************************************************************************
* QSkinny - Copyright (C) 2016 Uwe Rathmann
* This file may be used under the terms of the 3-clause BSD License
*****************************************************************************/
#include <SkinnyShapeFactory.h>
2018-08-03 08:15:28 +02:00
#include <SkinnyShortcut.h>
2017-07-30 14:31:05 +02:00
#include <QskAspect.h>
#include <QskBoxBorderColors.h>
2018-08-03 08:15:28 +02:00
#include <QskBoxBorderMetrics.h>
#include <QskBoxShapeMetrics.h>
#include <QskFocusIndicator.h>
2018-08-03 08:15:28 +02:00
#include <QskGraphic.h>
#include <QskLinearBox.h>
#include <QskObjectCounter.h>
#include <QskPushButton.h>
#include <QskScrollArea.h>
#include <QskQuick.h>
2018-08-03 08:15:28 +02:00
#include <QskWindow.h>
#include <QGuiApplication>
#include <QPainter>
2020-03-12 09:53:55 +01:00
#include <cstdlib>
#define HIDE_NODES 1
const int gridSize = 20;
const int thumbnailSize = 150;
static QColor randomColor()
{
static const char* colors[] =
{
"HotPink",
"MediumVioletRed",
"FireBrick",
"PeachPuff",
"Gold",
"RosyBrown",
"Maroon",
"Turquoise",
"CadetBlue",
"Teal",
"LightSteelBlue",
"CornflowerBlue",
"Thistle",
"Plum",
"DarkViolet",
"DarkSlateBlue",
"AntiqueWhite",
"MistyRose",
"Silver",
"DarkSlateGray"
};
2020-03-12 09:53:55 +01:00
const int index = std::rand() % int( ( sizeof( colors ) / sizeof( colors[ 0 ] ) ) );
return QColor( colors[ index ] );
}
static int randomShape()
{
2020-03-12 09:53:55 +01:00
return std::rand() % SkinnyShapeFactory::ShapeCount;
}
class Thumbnail : public QskPushButton
{
2018-08-03 08:15:28 +02:00
public:
Thumbnail( const QColor& color, int shape, QQuickItem* parentItem )
: QskPushButton( parentItem )
{
const QSizeF size( thumbnailSize, thumbnailSize );
setGraphic( thumbnailGraphic( color, shape, size ) );
setFixedSize( size );
setFlat( true );
}
private:
QskGraphic thumbnailGraphic( const QColor& color,
int shape, const QSizeF& size ) const
{
const auto path = SkinnyShapeFactory::shapePath(
static_cast< SkinnyShapeFactory::Shape >( shape ), size );
QskGraphic graphic;
QPen pen( Qt::black, 3 );
pen.setJoinStyle( Qt::MiterJoin );
pen.setCosmetic( true );
QPainter painter( &graphic );
painter.setRenderHint( QPainter::Antialiasing, true );
painter.setPen( pen );
painter.setBrush( color );
painter.drawPath( path );
painter.end();
return graphic;
}
};
class IconGrid : public QskLinearBox
{
2018-08-03 08:15:28 +02:00
public:
IconGrid( QQuickItem* parentItem = nullptr )
: QskLinearBox( Qt::Horizontal, gridSize, parentItem )
{
setMargins( 20 );
setSpacing( 20 );
for ( int col = 0; col < gridSize; col++ )
{
for ( int row = 0; row < gridSize; row++ )
2018-08-03 08:15:28 +02:00
( void ) new Thumbnail( randomColor(), randomShape(), this );
}
#if HIDE_NODES
/*
When having too many nodes, the scene graph becomes horribly slow.
So we explicitely hide all items outside the visible area
( see updateVisibilities below ) and make use of the DeferredUpdate and
CleanupOnVisibility features of QskQuickItem.
*/
setSize( sizeConstraint() );
updateLayout(); // so that every item has its initial geometry
2020-11-11 10:31:39 +01:00
for ( int i = 0; i < elementCount(); i++ )
{
if ( auto item = qobject_cast< QskControl* > ( itemAtIndex( i ) ) )
{
// to support the optimizations in ScrollArea::updateVisibilities
item->setLayoutHint( RetainSizeWhenHidden, true );
item->setVisible( false );
}
}
#endif
}
#if HIDE_NODES
void updateVisibilities( const QRectF& viewPort )
{
if ( !isEmpty() && viewPort != m_viewPort )
{
setItemsVisible( m_viewPort, false );
setItemsVisible( viewPort, true );
m_viewPort = viewPort;
}
}
private:
void setItemsVisible( const QRectF& rect, bool on )
{
const int dim = dimension();
// we know, that all items have the same size
const auto itemSize = qskItemSize( itemAtIndex( 0 ) );
const int rowMin = rect.top() / ( itemSize.height() + spacing() );
const int rowMax = rect.bottom() / ( itemSize.height() + spacing() );
const int colMin = rect.left() / ( itemSize.width() + spacing() );
const int colMax = rect.right() / ( itemSize.height() + spacing() );
for ( int row = rowMin; row <= rowMax; row++ )
{
for ( int col = colMin; col <= colMax; col++ )
{
if ( auto item = itemAtIndex( row * dim + col ) )
item->setVisible( on );
}
}
}
QRectF m_viewPort;
#endif
};
class ScrollArea : public QskScrollArea
{
2018-08-03 08:15:28 +02:00
public:
ScrollArea( QQuickItem* parentItem = nullptr )
: QskScrollArea( parentItem )
{
using namespace QskAspect;
// settings usually done in the skins
2017-10-18 20:00:06 +02:00
setBoxBorderMetricsHint( Viewport, 2 );
setBoxShapeHint( Viewport, 20 );
for ( auto subControl : { HorizontalScrollBar, VerticalScrollBar } )
setMetric( subControl | Size, 20 );
2017-10-18 20:00:06 +02:00
setBoxBorderMetricsHint( VerticalScrollHandle, 1 );
setBoxShapeHint( VerticalScrollHandle, 8 );
2017-10-18 20:00:06 +02:00
setBoxBorderMetricsHint( HorizontalScrollHandle, 1 );
setBoxShapeHint( HorizontalScrollHandle, 8 );
2019-04-04 18:38:58 +02:00
setFlickRecognizerTimeout( 300 );
connect( this, &QskScrollView::scrollPosChanged,
this, &ScrollArea::updateVisibilities );
}
protected:
void geometryChangeEvent( QskGeometryChangeEvent* event ) override
{
QskScrollArea::geometryChangeEvent( event );
updateVisibilities();
}
private:
void updateVisibilities()
{
#if HIDE_NODES
const auto box = static_cast< IconGrid* >( scrolledItem() );
if ( box )
{
const QRectF viewPort( scrollPos(), viewContentsRect().size() );
box->updateVisibilities( viewPort );
}
#endif
}
};
int main( int argc, char* argv[] )
{
#ifdef ITEM_STATISTICS
QskObjectCounter counter( true );
#endif
QGuiApplication app( argc, argv );
SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts );
/*
2017-07-30 14:31:05 +02:00
In a real world application a thumbnail viewer would probably be implemented
with QskScrollView using scene graph node composition - like done
2017-07-30 14:31:05 +02:00
with QskListView.
The thumbnails are implemented as buttons, so that we can see if the gesture
2018-08-03 08:15:28 +02:00
recognition for the flicking works without stopping the buttons from being functional.
This example also shows, that blocking of the scene graph node creation
2018-08-03 08:15:28 +02:00
( QskControl::DeferredUpdate + QskControl::CleanupOnVisibility )
2017-07-30 14:31:05 +02:00
could be improved to also respect being inside the window or a clip region.
To have a similar effect with what is possible today, the code
explicitely hide/show(s) the buttons out/inside the viewport.
But here we only want to demonstrate how QskScrollArea works.
*/
auto box = new QskLinearBox( Qt::Vertical );
box->setMargins( 20 );
auto buttonBox = new QskLinearBox( Qt::Horizontal, box );
buttonBox->setSizePolicy( Qt::Vertical, QskSizePolicy::Fixed );
2018-08-03 08:15:28 +02:00
new QskPushButton( "Push Me", buttonBox );
new QskPushButton( "Push Me", buttonBox );
auto iconGrid = new IconGrid();
// don't allow to shrink the iconGrid
iconGrid->setSizePolicy( QskSizePolicy::MinimumExpanding,
QskSizePolicy::MinimumExpanding );
auto scrollArea = new ScrollArea( box );
2017-07-30 14:31:05 +02:00
scrollArea->setMargins( QMarginsF( 25, 25, 5, 5 ) );
scrollArea->setScrolledItem( iconGrid );
auto focusIndicator = new QskFocusIndicator();
focusIndicator->setBoxBorderColorsHint( QskFocusIndicator::Panel, Qt::darkRed );
QskWindow window;
window.resize( 600, 600 );
2017-07-30 14:31:05 +02:00
window.setColor( "SteelBlue" );
window.addItem( box );
window.addItem( focusIndicator );
window.show();
return app.exec();
}