Merge 4ed5f7e431
into 16754b4f9d
This commit is contained in:
commit
6efb914bfd
|
@ -6,6 +6,7 @@ add_subdirectory(invoker)
|
|||
add_subdirectory(shadows)
|
||||
add_subdirectory(shapes)
|
||||
add_subdirectory(charts)
|
||||
add_subdirectory(levelingsensor)
|
||||
|
||||
if (BUILD_INPUTCONTEXT)
|
||||
add_subdirectory(inputpanel)
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
############################################################################
|
||||
# QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
############################################################################
|
||||
|
||||
set(SOURCES
|
||||
SkinFactory.h SkinFactory.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
qsk_add_example(levelingsensor ${SOURCES})
|
|
@ -0,0 +1,170 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "SkinFactory.h"
|
||||
|
||||
#include <QskBoxBorderColors.h>
|
||||
#include <QskGradient.h>
|
||||
#include <QskGradientStop.h>
|
||||
#include <QskPlatform.h>
|
||||
#include <QskRgbValue.h>
|
||||
#include <QskSkin.h>
|
||||
#include <QskSkinHintTableEditor.h>
|
||||
#include <QskTextLabel.h>
|
||||
|
||||
#include <QskLevelingSensor.h>
|
||||
#include <QskLevelingSensorSkinlet.h>
|
||||
|
||||
#include <QskSlider.h>
|
||||
#include <QskSliderSkinlet.h>
|
||||
|
||||
#include <QskTextLabel.h>
|
||||
#include <QskTextLabelSkinlet.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
class Skin : public QskSkin
|
||||
{
|
||||
template< typename T >
|
||||
void style( QskSkinHintTableEditor& editor );
|
||||
|
||||
template< typename Skinnable, typename Skinlet >
|
||||
void declareSkinlet()
|
||||
{
|
||||
QskSkin::declareSkinlet< Skinnable, Skinlet >();
|
||||
}
|
||||
|
||||
template< typename Skinnable, typename Skinlet >
|
||||
void declareSkinlet( QskSkinHintTableEditor& editor )
|
||||
{
|
||||
QskSkin::declareSkinlet< Skinnable, Skinlet >();
|
||||
style< Skinnable >( editor );
|
||||
}
|
||||
|
||||
public:
|
||||
Skin()
|
||||
{
|
||||
QskSkinHintTableEditor editor( &hintTable() );
|
||||
declareSkinlet< QskSlider, QskSliderSkinlet >( editor );
|
||||
declareSkinlet< QskTextLabel, QskTextLabelSkinlet >();
|
||||
declareSkinlet< QskLevelingSensor, QskLevelingSensorSkinlet >( editor );
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
void Skin::style< QskSlider >( QskSkinHintTableEditor& editor )
|
||||
{
|
||||
using A = QskAspect;
|
||||
using Q = QskSlider;
|
||||
|
||||
const qreal extent = 40;
|
||||
|
||||
// Panel
|
||||
|
||||
for ( auto variation : { A::Horizontal, A::Vertical } )
|
||||
{
|
||||
const auto aspect = Q::Panel | variation;
|
||||
|
||||
editor.setMetric( aspect | A::Size, extent );
|
||||
editor.setBoxBorderMetrics( aspect, 0 );
|
||||
editor.setBoxShape( aspect, 0 );
|
||||
editor.setGradient( aspect, QskGradient() );
|
||||
}
|
||||
|
||||
// Groove, Fill
|
||||
|
||||
for ( auto variation : { A::Horizontal, A::Vertical } )
|
||||
{
|
||||
for ( auto subControl : { Q::Groove, Q::Fill } )
|
||||
{
|
||||
const auto aspect = subControl | variation;
|
||||
|
||||
editor.setMetric( aspect | A::Size, 0.3 * extent );
|
||||
|
||||
editor.setBoxBorderMetrics( aspect, 0 );
|
||||
editor.setBoxShape( aspect, 0.1 * extent );
|
||||
}
|
||||
|
||||
editor.setGradient( Q::Groove | variation, Qt::lightGray );
|
||||
editor.setGradient( Q::Fill | variation, Qt::darkGray );
|
||||
}
|
||||
|
||||
// Handle
|
||||
|
||||
for ( auto variation : { A::Horizontal, A::Vertical } )
|
||||
{
|
||||
const auto aspect = Q::Handle | variation;
|
||||
editor.setColor( aspect, Qt::black );
|
||||
|
||||
editor.setBoxShape( aspect, 20.0, Qt::RelativeSize );
|
||||
|
||||
const qreal sz = 0.75 * extent;
|
||||
editor.setStrutSize( aspect, sz, sz );
|
||||
}
|
||||
}
|
||||
|
||||
template<>
|
||||
void Skin::style< QskLevelingSensor >( QskSkinHintTableEditor& editor )
|
||||
{
|
||||
using Q = QskLevelingSensor;
|
||||
|
||||
static constexpr auto r1 = 0.9;
|
||||
static constexpr auto r2 = 1.0;
|
||||
|
||||
QskGradient gradient{ {
|
||||
{ 0.5, Qt::lightGray },
|
||||
{ 0.5, Qt::lightGray },
|
||||
{ 0.5, Qt::darkGray },
|
||||
{ 1.0, Qt::darkGray },
|
||||
} };
|
||||
gradient.setLinearDirection( Qt::Vertical );
|
||||
|
||||
editor.setColor( Q::Background, "dimgray" );
|
||||
|
||||
editor.setStrutSize( Q::OuterDisk, { r2, r2 } );
|
||||
editor.setColor( Q::OuterDisk, Qt::white );
|
||||
|
||||
editor.setGradient( Q::Horizon, gradient );
|
||||
editor.setStrutSize( Q::Horizon, { r1, r1 } );
|
||||
|
||||
editor.setColor( Q::TickmarksX, Qt::black );
|
||||
editor.setStrutSize( Q::TickmarksX, { r1, 0.2 } ); // w %, h %
|
||||
editor.setHint( Q::TickmarksX, QVector3D{ 0.50, 0.75, 1.0 } ); // %
|
||||
editor.setAlignment( Q::TickmarksX, Qt::AlignCenter );
|
||||
|
||||
editor.setStrutSize( Q::TickmarksXLabels, { r1, 0.15 } ); // w %, h %
|
||||
editor.setAlignment( Q::TickmarksXLabels, Qt::AlignTop | Qt::AlignHCenter );
|
||||
|
||||
editor.setColor( Q::TickmarksY, Qt::black );
|
||||
editor.setStrutSize( Q::TickmarksY, { 0.1, r1 } ); // w %, h %
|
||||
editor.setHint( Q::TickmarksY, QVector3D{ 0.50, 0.75, 1.00 } ); // %
|
||||
editor.setAlignment( Q::TickmarksY, Qt::AlignCenter );
|
||||
|
||||
editor.setStrutSize( Q::TickmarksYLabels, { 0.15, r1 } ); // w %, h %
|
||||
editor.setAlignment( Q::TickmarksYLabels, Qt::AlignCenter );
|
||||
|
||||
editor.setColor( Q::TickmarksZ, "silver" );
|
||||
editor.setStrutSize( Q::TickmarksZ, { 0.90, 0.95 } );
|
||||
editor.setHint( Q::TickmarksZ, QVector3D{ 0.50, 0.75, 1.00 } ); // %
|
||||
|
||||
editor.setStrutSize( Q::TickmarksZLabels, { 0.9, 0.0 } ); // r1 %, r2 %
|
||||
editor.setAlignment( Q::TickmarksZLabels, Qt::AlignCenter );
|
||||
}
|
||||
}
|
||||
|
||||
QStringList SkinFactory::skinNames() const
|
||||
{
|
||||
return { "Skin" };
|
||||
}
|
||||
|
||||
QskSkin* SkinFactory::createSkin( const QString& skinName )
|
||||
{
|
||||
if ( skinName == "Skin" )
|
||||
return new Skin();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#include "moc_SkinFactory.cpp"
|
|
@ -0,0 +1,17 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QskSkinFactory.h>
|
||||
|
||||
class SkinFactory : public QskSkinFactory
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
QStringList skinNames() const override;
|
||||
QskSkin* createSkin( const QString& ) override;
|
||||
};
|
|
@ -0,0 +1,213 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "SkinFactory.h"
|
||||
|
||||
#include <SkinnyShortcut.h>
|
||||
|
||||
#include <QskObjectCounter.h>
|
||||
#include <QskSkinManager.h>
|
||||
#include <QskWindow.h>
|
||||
|
||||
#include <QskIntervalF.h>
|
||||
#include <QskLevelingSensor.h>
|
||||
#include <QskLinearBox.h>
|
||||
#include <QskScaleTickmarks.h>
|
||||
#include <QskSlider.h>
|
||||
#include <QskTextLabel.h>
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QQuickWindow>
|
||||
|
||||
namespace
|
||||
{
|
||||
void updateTickmarks( const Qt::Axis axis, const QskIntervalF& intervalA,
|
||||
const QskIntervalF& intervalB, QskLevelingSensor* const sensor )
|
||||
{
|
||||
QskScaleTickmarks tickmarks;
|
||||
QVector< qreal > major;
|
||||
QVector< qreal > medium;
|
||||
QVector< qreal > minor;
|
||||
|
||||
for ( const auto& interval : { intervalA, intervalB } )
|
||||
{
|
||||
for ( int deg = ( int ) interval.lowerBound(); deg < ( int ) interval.upperBound();
|
||||
++deg )
|
||||
{
|
||||
if ( deg % 45 == 0 )
|
||||
{
|
||||
major << deg;
|
||||
}
|
||||
if ( deg % 45 != 0 && deg % 5 == 0 )
|
||||
{
|
||||
medium << deg;
|
||||
}
|
||||
if ( deg % 45 != 0 && deg % 5 != 0 )
|
||||
{
|
||||
minor << deg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tickmarks.setMajorTicks( major );
|
||||
tickmarks.setMediumTicks( medium );
|
||||
tickmarks.setMinorTicks( minor );
|
||||
sensor->setTickmarks( axis, tickmarks );
|
||||
}
|
||||
|
||||
void updateTickmarksLabels( const Qt::Axis axis, const QskIntervalF& intervalA,
|
||||
const QskIntervalF& intervalB, int step, QskLevelingSensor* const sensor )
|
||||
{
|
||||
QskLevelingSensor::TickmarksLabels labels;
|
||||
for ( const auto& interval : { intervalA, intervalB } )
|
||||
{
|
||||
for ( int deg = ( ( int ) interval.lowerBound() / step ) * step;
|
||||
deg < ( int ) interval.upperBound(); deg += step )
|
||||
{
|
||||
labels << QskLevelingSensor::TickmarksLabels::value_type{ ( float ) deg,
|
||||
QString( deg > 0 ? "+" : "" ) + QString::number( deg ) };
|
||||
}
|
||||
}
|
||||
sensor->setTickmarksLabels( axis, labels );
|
||||
}
|
||||
|
||||
Q_REQUIRED_RESULT QskSlider* makeAngleSlider( const Qt::Axis axis,
|
||||
QskLevelingSensor* const sensor, double min = 0, double max = 90, QQuickItem* const parent = nullptr)
|
||||
{
|
||||
auto* const slider = new QskSlider( Qt::Horizontal, parent );
|
||||
slider->setMinimum( min );
|
||||
slider->setMaximum( max );
|
||||
slider->setValue( sensor->angles()[ axis ] );
|
||||
|
||||
QObject::connect(slider, &QskSlider::valueChanged, sensor, [axis, sensor](const qreal v){
|
||||
auto angles = sensor->angles();
|
||||
angles[axis] = v;
|
||||
sensor->setAngle(angles);
|
||||
});
|
||||
|
||||
return slider;
|
||||
}
|
||||
|
||||
Q_REQUIRED_RESULT QskSlider* makeTickmarksSlider( const Qt::Axis axis,
|
||||
QskLevelingSensor* const sensor, int min, int max,
|
||||
std::function< QskIntervalF( qreal ) > intervalA,
|
||||
std::function< QskIntervalF( qreal ) > intervalB, QQuickItem* const parent = nullptr )
|
||||
{
|
||||
auto* const slider = new QskSlider( Qt::Horizontal, parent );
|
||||
slider->setMinimum( min );
|
||||
slider->setMaximum( max );
|
||||
|
||||
QObject::connect( slider, &QskSlider::valueChanged, sensor, [ = ]( const qreal degree ) {
|
||||
updateTickmarks( axis, intervalA( degree ), intervalB( degree ), sensor );
|
||||
updateTickmarksLabels( axis, intervalA( degree ), intervalB( degree ), 10, sensor );
|
||||
} );
|
||||
|
||||
return slider;
|
||||
}
|
||||
|
||||
Q_REQUIRED_RESULT QskSlider* makeRotationSlider( const Qt::Axis axis,
|
||||
QskLevelingSensor* const sensor, const QskAspect::Subcontrol subControl,
|
||||
QQuickItem* const parent = nullptr )
|
||||
{
|
||||
auto* const slider = new QskSlider( Qt::Horizontal, parent );
|
||||
slider->setMinimum( -360 );
|
||||
slider->setMaximum( +360 );
|
||||
|
||||
QObject::connect( sensor, &QskLevelingSensor::subControlRotationChanged, slider,
|
||||
[ = ]( const QskAspect::Subcontrol control, const QVector3D& degree ) {
|
||||
if ( control == subControl )
|
||||
{
|
||||
slider->setValue( degree[ axis ] );
|
||||
}
|
||||
} );
|
||||
|
||||
QObject::connect( slider, &QskSlider::valueChanged, sensor, [ = ]( const qreal degree ) {
|
||||
auto d = sensor->subControlRotation( subControl );
|
||||
d[ axis ] = degree;
|
||||
sensor->setSubControlRotation( subControl, d );
|
||||
} );
|
||||
|
||||
return slider;
|
||||
}
|
||||
|
||||
class Window : public QskWindow
|
||||
{
|
||||
public:
|
||||
Window()
|
||||
{
|
||||
auto* const root = new QskLinearBox( Qt::Horizontal, contentItem() );
|
||||
root->setSpacing( 8 );
|
||||
root->setMargins( 8 );
|
||||
auto* const left = new QskLinearBox( Qt::Vertical, root );
|
||||
auto* const right = new QskLinearBox( Qt::Vertical, root );
|
||||
auto* const sensor = new QskLevelingSensor( left );
|
||||
|
||||
auto linearIntervalA = []( const qreal degree ) -> QskIntervalF {
|
||||
return { -degree, +degree };
|
||||
};
|
||||
auto linearIntervalB = []( const qreal /*degree*/ ) -> QskIntervalF { return {}; };
|
||||
|
||||
auto radialIntervalA = []( const qreal degree ) -> QskIntervalF {
|
||||
return { -degree, +degree };
|
||||
};
|
||||
auto radialIntervalB = []( const qreal degree ) -> QskIntervalF {
|
||||
return { 180 - degree, 180 + degree };
|
||||
};
|
||||
|
||||
( void ) new QskTextLabel( "Angles XYZ", right );
|
||||
(void) makeAngleSlider(Qt::XAxis, sensor, 0, 45, right);
|
||||
(void) makeAngleSlider(Qt::YAxis, sensor, 0, 45, right);
|
||||
(void) makeAngleSlider(Qt::ZAxis, sensor, 0, 45, right);
|
||||
|
||||
( void ) new QskTextLabel( "Tickmarks XXZ", right );
|
||||
auto* const sliderTickmarksX = makeTickmarksSlider(
|
||||
Qt::XAxis, sensor, 0, 90, linearIntervalA, linearIntervalB, right );
|
||||
auto* const sliderTickmarksY = makeTickmarksSlider(
|
||||
Qt::YAxis, sensor, 0, 90, linearIntervalA, linearIntervalB, right );
|
||||
auto* const sliderTickmarksZ = makeTickmarksSlider(
|
||||
Qt::ZAxis, sensor, 0, 90, radialIntervalA, radialIntervalB, right );
|
||||
|
||||
( void ) new QskTextLabel( "Rotation X Plane", right );
|
||||
( void ) makeRotationSlider( Qt::XAxis, sensor, QskLevelingSensor::TickmarksX, right );
|
||||
( void ) makeRotationSlider( Qt::YAxis, sensor, QskLevelingSensor::TickmarksX, right );
|
||||
( void ) makeRotationSlider( Qt::ZAxis, sensor, QskLevelingSensor::TickmarksX, right );
|
||||
( void ) new QskTextLabel( "Rotation Y Plane", right );
|
||||
( void ) makeRotationSlider( Qt::XAxis, sensor, QskLevelingSensor::TickmarksY, right );
|
||||
( void ) makeRotationSlider( Qt::YAxis, sensor, QskLevelingSensor::TickmarksY, right );
|
||||
( void ) makeRotationSlider( Qt::ZAxis, sensor, QskLevelingSensor::TickmarksY, right );
|
||||
( void ) new QskTextLabel( "Rotation Z Plane", right );
|
||||
( void ) makeRotationSlider( Qt::XAxis, sensor, QskLevelingSensor::TickmarksZ, right );
|
||||
( void ) makeRotationSlider( Qt::YAxis, sensor, QskLevelingSensor::TickmarksZ, right );
|
||||
( void ) makeRotationSlider( Qt::ZAxis, sensor, QskLevelingSensor::TickmarksZ, right );
|
||||
( void ) new QskTextLabel( "Horizon", right );
|
||||
( void ) makeRotationSlider( Qt::XAxis, sensor, QskLevelingSensor::Horizon, right );
|
||||
( void ) makeRotationSlider( Qt::YAxis, sensor, QskLevelingSensor::Horizon, right );
|
||||
( void ) makeRotationSlider( Qt::ZAxis, sensor, QskLevelingSensor::Horizon, right );
|
||||
|
||||
sliderTickmarksX->setValue( 15 );
|
||||
sliderTickmarksY->setValue( 15 );
|
||||
sliderTickmarksZ->setValue( 30 );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
int main( int argc, char** argv )
|
||||
{
|
||||
#ifdef ITEM_STATISTICS
|
||||
QskObjectCounter counter( true );
|
||||
#endif
|
||||
|
||||
qskSkinManager->setPluginPaths( QStringList() ); // no skin plugins
|
||||
qskSkinManager->registerFactory( QStringLiteral( "sample" ), new SkinFactory() );
|
||||
|
||||
QGuiApplication app( argc, argv );
|
||||
|
||||
SkinnyShortcut::enable( SkinnyShortcut::AllShortcuts );
|
||||
|
||||
Window window;
|
||||
window.showMaximized();
|
||||
|
||||
return app.exec();
|
||||
}
|
|
@ -440,6 +440,22 @@ list(APPEND SOURCES
|
|||
inputpanel/QskVirtualKeyboard.cpp
|
||||
)
|
||||
|
||||
list(APPEND HEADERS
|
||||
controls/QskLevelingSensor.h
|
||||
controls/QskLevelingSensorSkinlet.h
|
||||
)
|
||||
|
||||
list(APPEND PRIVATE_HEADERS
|
||||
controls/private/QskSGNodeUtility.h
|
||||
controls/private/QskLevelingSensorNodes.h
|
||||
controls/private/QskLevelingSensorUtility.h
|
||||
)
|
||||
|
||||
list(APPEND SOURCES
|
||||
controls/private/QskLevelingSensor.cpp
|
||||
controls/private/QskLevelingSensorSkinlet.cpp
|
||||
)
|
||||
|
||||
if(ENABLE_PINYIN)
|
||||
list(APPEND HEADERS inputpanel/QskPinyinTextPredictor.h)
|
||||
list(APPEND SOURCES inputpanel/QskPinyinTextPredictor.cpp)
|
||||
|
@ -499,6 +515,7 @@ set_target_properties(${target}
|
|||
|
||||
list(TRANSFORM HEADERS PREPEND "${CMAKE_CURRENT_LIST_DIR}/")
|
||||
set_target_properties(${target} PROPERTIES PUBLIC_HEADER "${HEADERS}")
|
||||
|
||||
set_target_properties(${target} PROPERTIES
|
||||
VERSION ${CMAKE_PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} )
|
||||
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef QSK_LEVELING_SENSOR_H
|
||||
#define QSK_LEVELING_SENSOR_H
|
||||
|
||||
#include <QskControl.h>
|
||||
#include <QskScaleTickmarks.h>
|
||||
#include <memory>
|
||||
|
||||
class QSK_EXPORT QskLevelingSensor : public QskControl
|
||||
{
|
||||
Q_OBJECT
|
||||
using Inherited = QskControl;
|
||||
|
||||
public:
|
||||
QSK_SUBCONTROLS( OuterDisk, Horizon, TickmarksX, TickmarksXLabels, TickmarksY, TickmarksYLabels,
|
||||
TickmarksZ, TickmarksZLabels )
|
||||
|
||||
using Tickmarks = QskScaleTickmarks;
|
||||
using TickmarksLabels = QVector< QPair< qreal, QString > >;
|
||||
|
||||
explicit QskLevelingSensor( QQuickItem* parent = nullptr );
|
||||
~QskLevelingSensor();
|
||||
|
||||
public Q_SLOTS:
|
||||
void setTickmarks( Qt::Axis axis, Tickmarks tickmarks );
|
||||
void setTickmarksLabels( Qt::Axis axis, TickmarksLabels labels );
|
||||
void setAngle( const QVector3D& degrees );
|
||||
void setSubControlRotation( QskAspect::Subcontrol subControl, const QVector3D& degrees );
|
||||
|
||||
Q_SIGNALS:
|
||||
void anglesChanged( const QVector3D& degree );
|
||||
void subControlRotationChanged( QskAspect::Subcontrol subControl, const QVector3D& degrees );
|
||||
|
||||
public:
|
||||
Q_REQUIRED_RESULT Tickmarks tickmarks( Qt::Axis axis ) const;
|
||||
Q_REQUIRED_RESULT TickmarksLabels tickmarkLabels( Qt::Axis axis ) const;
|
||||
Q_REQUIRED_RESULT QVector3D angles() const;
|
||||
Q_REQUIRED_RESULT QVector3D subControlRotation( QskAspect::Subcontrol subControl ) const;
|
||||
|
||||
private:
|
||||
class PrivateData;
|
||||
std::unique_ptr< PrivateData > m_data;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,60 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef QSK_LEVELING_SENSOR_SKINLET_H
|
||||
#define QSK_LEVELING_SENSOR_SKINLET_H
|
||||
|
||||
#include <QSGNode>
|
||||
#include <QskSkinlet.h>
|
||||
|
||||
class QskLevelingSensor;
|
||||
|
||||
class QSK_EXPORT QskLevelingSensorSkinlet : public QskSkinlet
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
using Inherited = QskSkinlet;
|
||||
|
||||
public:
|
||||
enum NodeRole
|
||||
{
|
||||
OuterDisk,
|
||||
Horizon,
|
||||
HorizonClip,
|
||||
TickmarksX,
|
||||
TickmarksXLabels,
|
||||
TickmarksY,
|
||||
TickmarksYLabels,
|
||||
TickmarksZ,
|
||||
TickmarksZLabels,
|
||||
TriangleBar,
|
||||
TickmarksYIndicator,
|
||||
RoleCount
|
||||
};
|
||||
|
||||
Q_INVOKABLE QskLevelingSensorSkinlet( QskSkin* skin = nullptr );
|
||||
~QskLevelingSensorSkinlet() override = default;
|
||||
|
||||
Q_REQUIRED_RESULT static float outerRadius( const QskSkinnable* const skinnable );
|
||||
Q_REQUIRED_RESULT static float innerRadius( const QskSkinnable* const skinnable );
|
||||
Q_REQUIRED_RESULT static QPointF center( const QskSkinnable* const skinnable );
|
||||
|
||||
protected:
|
||||
Q_REQUIRED_RESULT QRectF subControlRect( const QskSkinnable* skinnable,
|
||||
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const override;
|
||||
|
||||
Q_REQUIRED_RESULT QSGNode* updateSubNode(
|
||||
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const override;
|
||||
|
||||
template< NodeRole >
|
||||
Q_REQUIRED_RESULT QRectF subControlRect(
|
||||
const QskLevelingSensor* sensor, const QRectF& contentsRect ) const;
|
||||
|
||||
template< NodeRole >
|
||||
Q_REQUIRED_RESULT QSGNode* updateSubNode(
|
||||
const QskLevelingSensor* sensor, quint8 nodeRole, QSGNode* node ) const;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,141 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include <QskFunctions.h>
|
||||
#include <QskLevelingSensor.h>
|
||||
#include <QskScaleTickmarks.h>
|
||||
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <QVector2D>
|
||||
#include <QVector3D>
|
||||
|
||||
namespace
|
||||
{
|
||||
template< typename T >
|
||||
bool compareExchange( T& dst, const T& src )
|
||||
{
|
||||
if ( dst != src )
|
||||
{
|
||||
dst = src;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool compareExchange< float >( float& dst, const float& src )
|
||||
{
|
||||
if ( !qskFuzzyCompare( dst, src ) )
|
||||
{
|
||||
dst = src;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<>
|
||||
bool compareExchange< QVector3D >( QVector3D& dst, const QVector3D& src )
|
||||
{
|
||||
auto dirty = false;
|
||||
dirty |= compareExchange( dst[ Qt::XAxis ], src[ Qt::XAxis ] );
|
||||
dirty |= compareExchange( dst[ Qt::YAxis ], src[ Qt::YAxis ] );
|
||||
dirty |= compareExchange( dst[ Qt::ZAxis ], src[ Qt::ZAxis ] );
|
||||
return dirty;
|
||||
}
|
||||
|
||||
inline bool isAxis( const Qt::Axis axis )
|
||||
{
|
||||
return axis == Qt::XAxis || axis == Qt::YAxis || axis == Qt::ZAxis;
|
||||
}
|
||||
}
|
||||
|
||||
QSK_SUBCONTROL( QskLevelingSensor, OuterDisk )
|
||||
QSK_SUBCONTROL( QskLevelingSensor, Horizon )
|
||||
QSK_SUBCONTROL( QskLevelingSensor, TickmarksX )
|
||||
QSK_SUBCONTROL( QskLevelingSensor, TickmarksXLabels )
|
||||
QSK_SUBCONTROL( QskLevelingSensor, TickmarksY )
|
||||
QSK_SUBCONTROL( QskLevelingSensor, TickmarksYLabels )
|
||||
QSK_SUBCONTROL( QskLevelingSensor, TickmarksZ )
|
||||
QSK_SUBCONTROL( QskLevelingSensor, TickmarksZLabels )
|
||||
|
||||
#define RETURN_IF_FALSE( expr ) \
|
||||
if ( !( expr ) ) \
|
||||
return;
|
||||
|
||||
class QskLevelingSensor::PrivateData
|
||||
{
|
||||
public:
|
||||
QVector3D m_angles = { 45, 45, 45 };
|
||||
Tickmarks m_tickmarks[ 3 ];
|
||||
TickmarksLabels m_tickmarksLabels[ 3 ];
|
||||
std::unordered_map< QskAspect::Subcontrol, QVector3D > m_subControlRotation;
|
||||
};
|
||||
|
||||
QskLevelingSensor::QskLevelingSensor( QQuickItem* const parent )
|
||||
: Inherited( parent )
|
||||
, m_data( new QskLevelingSensor::PrivateData )
|
||||
{
|
||||
}
|
||||
|
||||
QskLevelingSensor::~QskLevelingSensor() = default;
|
||||
|
||||
void QskLevelingSensor::setTickmarks( const Qt::Axis axis, QskScaleTickmarks tickmarks )
|
||||
{
|
||||
RETURN_IF_FALSE( isAxis( axis ) );
|
||||
m_data->m_tickmarks[ axis ] = std::move( tickmarks );
|
||||
update();
|
||||
}
|
||||
|
||||
void QskLevelingSensor::setTickmarksLabels( const Qt::Axis axis, TickmarksLabels labels )
|
||||
{
|
||||
RETURN_IF_FALSE( isAxis( axis ) );
|
||||
m_data->m_tickmarksLabels[ axis ] = std::move( labels );
|
||||
update();
|
||||
}
|
||||
|
||||
void QskLevelingSensor::setAngle( const QVector3D& degrees )
|
||||
{
|
||||
if ( compareExchange( m_data->m_angles, degrees ) )
|
||||
{
|
||||
update();
|
||||
Q_EMIT anglesChanged( m_data->m_angles );
|
||||
}
|
||||
}
|
||||
|
||||
QskScaleTickmarks QskLevelingSensor::tickmarks( const Qt::Axis axis ) const
|
||||
{
|
||||
return isAxis( axis ) ? m_data->m_tickmarks[ axis ] : QskScaleTickmarks{};
|
||||
}
|
||||
|
||||
QskLevelingSensor::TickmarksLabels QskLevelingSensor::tickmarkLabels( const Qt::Axis axis ) const
|
||||
{
|
||||
return isAxis( axis ) ? m_data->m_tickmarksLabels[ axis ]
|
||||
: QskLevelingSensor::TickmarksLabels{};
|
||||
}
|
||||
|
||||
QVector3D QskLevelingSensor::angles() const
|
||||
{
|
||||
return m_data->m_angles;
|
||||
}
|
||||
|
||||
QVector3D QskLevelingSensor::subControlRotation( const QskAspect::Subcontrol subControl ) const
|
||||
{
|
||||
const auto found = m_data->m_subControlRotation.find( subControl );
|
||||
return found != m_data->m_subControlRotation.end() ? found->second : QVector3D{};
|
||||
}
|
||||
|
||||
void QskLevelingSensor::setSubControlRotation(
|
||||
const QskAspect::Subcontrol subControl, const QVector3D& degrees )
|
||||
{
|
||||
if ( compareExchange( m_data->m_subControlRotation[ subControl ], degrees ) )
|
||||
{
|
||||
update();
|
||||
Q_EMIT subControlRotationChanged( subControl, degrees );
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_QskLevelingSensor.cpp"
|
|
@ -0,0 +1,238 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef QSK_LEVELING_SENSOR_NODES_H
|
||||
#define QSK_LEVELING_SENSOR_NODES_H
|
||||
|
||||
#include "QskLevelingSensorUtility.h"
|
||||
|
||||
#include <QskAspect.h>
|
||||
#include <QskScaleTickmarks.h>
|
||||
#include <QskSkinlet.h>
|
||||
#include <QskSkinnable.h>
|
||||
#include <QskTextNode.h>
|
||||
#include <QskTextOptions.h>
|
||||
|
||||
#include <QSGFlatColorMaterial>
|
||||
#include <QSGGeometry>
|
||||
#include <QSGGeometryNode>
|
||||
|
||||
#include <QFontMetricsF>
|
||||
#include <qmath.h>
|
||||
|
||||
class RadialTickmarksNode final : public QSGGeometryNode
|
||||
{
|
||||
public:
|
||||
RadialTickmarksNode()
|
||||
: m_geometry( QSGGeometry::defaultAttributes_Point2D(), 0 )
|
||||
{
|
||||
m_geometry.setDrawingMode( QSGGeometry::DrawLines );
|
||||
m_geometry.setVertexDataPattern( QSGGeometry::StaticPattern );
|
||||
|
||||
setGeometry( &m_geometry );
|
||||
setMaterial( &m_material );
|
||||
}
|
||||
|
||||
void setMaterialProperties( const QColor& color )
|
||||
{
|
||||
if ( m_material.color() != color )
|
||||
{
|
||||
m_material.setColor( color );
|
||||
markDirty( QSGNode::DirtyMaterial );
|
||||
}
|
||||
}
|
||||
|
||||
void setGeometryProperties( const QskScaleTickmarks& tickmarks, const qreal r1 = 0.0,
|
||||
const QVector3D& r2 = { 0.5, 0.75, 1.0 }, float lineWidth = 1.0 )
|
||||
{
|
||||
auto dirty = false;
|
||||
|
||||
if ( dirty |= ( m_geometry.lineWidth() != lineWidth ) )
|
||||
{
|
||||
m_geometry.setLineWidth( lineWidth );
|
||||
}
|
||||
|
||||
dirty |= compareExchange( m_r1, r1 );
|
||||
dirty |= compareExchange( m_r2, r2 );
|
||||
dirty |= compareExchange( m_tickmarksHash, tickmarks.hash() );
|
||||
|
||||
if ( dirty )
|
||||
{
|
||||
update( tickmarks );
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void update( const QskScaleTickmarks& tickmarks )
|
||||
{
|
||||
if ( m_geometry.vertexCount() != tickmarks.tickCount() )
|
||||
{
|
||||
m_geometry.allocate( tickmarks.tickCount() * 2 );
|
||||
}
|
||||
|
||||
auto* vertexData = m_geometry.vertexDataAsPoint2D();
|
||||
|
||||
using T = QskScaleTickmarks::TickType;
|
||||
for ( auto type : { T::MinorTick, T::MediumTick, T::MajorTick } )
|
||||
{
|
||||
for ( const auto tick : tickmarks.ticks( type ) )
|
||||
{
|
||||
const auto i = static_cast< int >( type );
|
||||
const auto angleRad = qDegreesToRadians( tick );
|
||||
const auto x1 = qFastCos( angleRad ) * m_r1;
|
||||
const auto y1 = qFastSin( angleRad ) * m_r1;
|
||||
const auto x2 = qFastCos( angleRad ) * m_r2[ i ];
|
||||
const auto y2 = qFastSin( angleRad ) * m_r2[ i ];
|
||||
|
||||
vertexData[ 0 ].set( x1, y1 );
|
||||
vertexData[ 1 ].set( x2, y2 );
|
||||
vertexData += 2;
|
||||
}
|
||||
}
|
||||
|
||||
m_geometry.markVertexDataDirty();
|
||||
markDirty( QSGNode::DirtyGeometry );
|
||||
}
|
||||
|
||||
QSGGeometry m_geometry;
|
||||
QSGFlatColorMaterial m_material;
|
||||
qreal m_r1 = 0.0;
|
||||
QVector3D m_r2 = { 0.5, 0.75, 1.0 };
|
||||
QskHashValue m_tickmarksHash{ 0 };
|
||||
};
|
||||
|
||||
class RadialClipNode final : public QSGClipNode
|
||||
{
|
||||
public:
|
||||
RadialClipNode()
|
||||
: m_geometry( QSGGeometry::defaultAttributes_Point2D(), 0 )
|
||||
{
|
||||
m_geometry.setVertexDataPattern( QSGGeometry::DynamicPattern );
|
||||
m_geometry.setDrawingMode( QSGGeometry::DrawTriangleFan );
|
||||
setGeometry( &m_geometry );
|
||||
setIsRectangular( false );
|
||||
}
|
||||
|
||||
void setGeometryProperties( const qreal radius = 1.0, const qreal cx = 0.0,
|
||||
const qreal cy = 0.0, const int count = 360 )
|
||||
{
|
||||
auto dirty = false;
|
||||
dirty |= compareExchange( m_radius, radius );
|
||||
dirty |= compareExchange( m_cx, cx );
|
||||
dirty |= compareExchange( m_cy, cy );
|
||||
dirty |= compareExchange( m_count, count );
|
||||
|
||||
if ( dirty )
|
||||
{
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void update()
|
||||
{
|
||||
const auto step = 2.0 * M_PI / m_count;
|
||||
|
||||
if ( m_geometry.vertexCount() != m_count )
|
||||
{
|
||||
m_geometry.allocate( m_count );
|
||||
}
|
||||
|
||||
auto* vertices = m_geometry.vertexDataAsPoint2D();
|
||||
|
||||
for ( int i = 0; i < m_count; ++i )
|
||||
{
|
||||
vertices[ i ].x = qFastCos( i * step ) * m_radius + m_cx;
|
||||
vertices[ i ].y = qFastSin( i * step ) * m_radius + m_cy;
|
||||
}
|
||||
|
||||
m_geometry.markVertexDataDirty();
|
||||
markDirty( QSGNode::DirtyGeometry );
|
||||
}
|
||||
|
||||
using QSGClipNode::setClipRect;
|
||||
using QSGClipNode::setIsRectangular;
|
||||
|
||||
QSGGeometry m_geometry;
|
||||
qreal m_radius = 1.0;
|
||||
qreal m_cx = 0;
|
||||
qreal m_cy = 0;
|
||||
int m_count = 360;
|
||||
};
|
||||
|
||||
template< typename CRTP >
|
||||
struct TickmarksLabelsNode : public QSGNode
|
||||
{
|
||||
public:
|
||||
QVector2D value( const QVector2D& v, const QVector2D& s, const QVector2D& o ) const
|
||||
{
|
||||
return static_cast< const CRTP* >( this )->value( v, s, o );
|
||||
}
|
||||
|
||||
void update( const QskSkinnable* const skinnable, const QskAspect::Subcontrol subControl,
|
||||
const QVector< QPair< double, QString > >& labels, const QVector2D& scale = { 1.0, 0.0 },
|
||||
const QVector2D& offset = {} )
|
||||
{
|
||||
const auto count = labels.count();
|
||||
|
||||
for ( int i = childCount(); i > count; --i )
|
||||
{
|
||||
removeChildNode( lastChild() );
|
||||
}
|
||||
|
||||
for ( int i = childCount(); i < count; ++i )
|
||||
{
|
||||
appendChildNode( new QskTextNode );
|
||||
}
|
||||
|
||||
const QFontMetricsF metrics( skinnable->effectiveFont( subControl ) );
|
||||
const auto h = skinnable->effectiveFontHeight( subControl );
|
||||
const auto a = skinnable->alignmentHint( subControl );
|
||||
|
||||
auto* textNode = static_cast< QskTextNode* >( firstChild() );
|
||||
for ( const auto& label : qAsConst( labels ) )
|
||||
{
|
||||
const auto v = value( { ( float ) label.first, ( float ) label.first }, scale, offset );
|
||||
auto x = v.x();
|
||||
auto y = v.y();
|
||||
|
||||
const auto w = metrics.horizontalAdvance( label.second );
|
||||
|
||||
x -= a.testFlag( Qt::AlignRight ) ? w : 0;
|
||||
x -= a.testFlag( Qt::AlignHCenter ) ? w / 2 : 0;
|
||||
|
||||
y -= a.testFlag( Qt::AlignBottom ) ? h : 0;
|
||||
y -= a.testFlag( Qt::AlignVCenter ) ? h / 2 : 0;
|
||||
|
||||
QskSkinlet::updateTextNode(
|
||||
skinnable, textNode, { x, y, w, h }, a, label.second, subControl );
|
||||
|
||||
textNode = static_cast< QskTextNode* >( textNode->nextSibling() );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct LinearTickmarksLabelsNode final : public TickmarksLabelsNode< LinearTickmarksLabelsNode >
|
||||
{
|
||||
public:
|
||||
QVector2D value( const QVector2D& v, const QVector2D& s, const QVector2D& o ) const
|
||||
{
|
||||
return v * s + o;
|
||||
}
|
||||
};
|
||||
|
||||
struct RadialTickmarksLabelsNode final : public TickmarksLabelsNode< RadialTickmarksLabelsNode >
|
||||
{
|
||||
public:
|
||||
QVector2D value( const QVector2D& v, const QVector2D& s, const QVector2D& o ) const
|
||||
{
|
||||
return QVector2D{
|
||||
( float ) qFastCos( qDegreesToRadians( v.x() ) ),
|
||||
( float ) qFastSin( qDegreesToRadians( v.y() ) )
|
||||
} * s + o;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,492 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#include "QskLevelingSensorNodes.h"
|
||||
#include "QskLevelingSensorUtility.h"
|
||||
#include "QskSGNodeUtility.h"
|
||||
#include <QskLevelingSensor.h>
|
||||
#include <QskLevelingSensorSkinlet.h>
|
||||
|
||||
#include <QskArcMetrics.h>
|
||||
#include <QskBoxBorderColors.h>
|
||||
#include <QskBoxBorderMetrics.h>
|
||||
#include <QskBoxNode.h>
|
||||
#include <QskBoxShapeMetrics.h>
|
||||
#include <QskGradient.h>
|
||||
#include <QskGraphic.h>
|
||||
#include <QskScaleEngine.h>
|
||||
#include <QskScaleTickmarks.h>
|
||||
#include <QskTextColors.h>
|
||||
#include <QskTextNode.h>
|
||||
#include <QskTextOptions.h>
|
||||
#include <QskTickmarksNode.h>
|
||||
|
||||
#include <QFontMetrics>
|
||||
#include <QSGClipNode>
|
||||
#include <QSGFlatColorMaterial>
|
||||
#include <QSGGeometryNode>
|
||||
#include <qmath.h>
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
template< typename T >
|
||||
struct State;
|
||||
|
||||
template<>
|
||||
struct State< QskLevelingSensor >
|
||||
{
|
||||
qreal r1 = 0.0;
|
||||
qreal r2 = 0.0;
|
||||
qreal cX = 0.0;
|
||||
qreal cY = 0.0;
|
||||
qreal sX = 0.0;
|
||||
qreal sY = 0.0;
|
||||
qreal sZ = 0.0;
|
||||
|
||||
explicit State( const QskLevelingSensor* const sensor )
|
||||
: r1( QskLevelingSensorSkinlet::innerRadius( sensor ) )
|
||||
, r2( QskLevelingSensorSkinlet::outerRadius( sensor ) )
|
||||
, cX( QskLevelingSensorSkinlet::center( sensor ).x() )
|
||||
, cY( QskLevelingSensorSkinlet::center( sensor ).y() )
|
||||
, sX( r1 / sensor->angles().x() )
|
||||
, sY( r1 / sensor->angles().y() )
|
||||
, sZ( r1 / sensor->angles().z() )
|
||||
{
|
||||
}
|
||||
|
||||
Q_REQUIRED_RESULT QVector2D center() const noexcept
|
||||
{
|
||||
return { ( float ) cX, ( float ) cY };
|
||||
}
|
||||
|
||||
Q_REQUIRED_RESULT QVector3D scale() const noexcept
|
||||
{
|
||||
return { ( float ) sX, ( float ) sY, ( float ) sZ };
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct State< QskAspect::Subcontrol > : State< QskLevelingSensor >
|
||||
{
|
||||
qreal rX = 0.0;
|
||||
qreal rY = 0.0;
|
||||
qreal rZ = 0.0;
|
||||
qreal tX = 0.0;
|
||||
qreal tY = 0.0;
|
||||
qreal tZ = 0.0;
|
||||
|
||||
State( const QskLevelingSensor* const sensor, QskAspect::Subcontrol subcontrol )
|
||||
: State< QskLevelingSensor >( sensor )
|
||||
, rX( sensor->subControlRotation( subcontrol ).x() )
|
||||
, rY( sensor->subControlRotation( subcontrol ).y() )
|
||||
, rZ( sensor->subControlRotation( subcontrol ).z() )
|
||||
, tX( rY * sX )
|
||||
, tY( rX * sY )
|
||||
, tZ( 0.0 )
|
||||
{
|
||||
}
|
||||
|
||||
Q_REQUIRED_RESULT QVector3D translation() const noexcept
|
||||
{
|
||||
return { ( float ) tX, ( float ) tY, ( float ) tZ };
|
||||
}
|
||||
|
||||
Q_REQUIRED_RESULT QMatrix4x4 matrix() const noexcept
|
||||
{
|
||||
return matrix_deg( rZ, cX, cY );
|
||||
}
|
||||
};
|
||||
|
||||
Q_REQUIRED_RESULT std::optional< std::pair< qreal, qreal > > minmax(
|
||||
const QVector< qreal >& tickmarks )
|
||||
{
|
||||
if ( tickmarks.empty() )
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto [ pmin, pmax ] = std::minmax_element( tickmarks.begin(), tickmarks.end() );
|
||||
return std::make_pair( *pmin, *pmax );
|
||||
}
|
||||
|
||||
Q_REQUIRED_RESULT std::optional< std::pair< qreal, qreal > > minmax(
|
||||
const QskScaleTickmarks& tickmarks )
|
||||
{
|
||||
if ( tickmarks.tickCount() == 0 )
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto majorTicks = tickmarks.majorTicks();
|
||||
const auto mediumTicks = tickmarks.mediumTicks();
|
||||
const auto minorTicks = tickmarks.minorTicks();
|
||||
|
||||
const auto item = !majorTicks.empty() ? majorTicks[ 0 ]
|
||||
: !mediumTicks.empty() ? mediumTicks[ 0 ]
|
||||
: !minorTicks.empty() ? minorTicks[ 0 ]
|
||||
: std::numeric_limits< qreal >::quiet_NaN();
|
||||
|
||||
Q_ASSERT_X( !std::isnan( item ), __func__, "NaN should not be possible!" );
|
||||
|
||||
std::array< qreal, 6 > local = { item };
|
||||
|
||||
if ( const auto opt = minmax( tickmarks.majorTicks() ) )
|
||||
{
|
||||
local[ 0 ] = opt->first;
|
||||
local[ 1 ] = opt->second;
|
||||
}
|
||||
if ( const auto opt = minmax( tickmarks.mediumTicks() ) )
|
||||
{
|
||||
local[ 2 ] = opt->first;
|
||||
local[ 3 ] = opt->second;
|
||||
}
|
||||
if ( const auto opt = minmax( tickmarks.minorTicks() ) )
|
||||
{
|
||||
local[ 4 ] = opt->first;
|
||||
local[ 5 ] = opt->second;
|
||||
}
|
||||
|
||||
const auto [ pmin, pmax ] = std::minmax_element( local.begin(), local.end() );
|
||||
return std::make_pair( *pmin, *pmax );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using Q = QskLevelingSensor;
|
||||
using R = QskLevelingSensorSkinlet::NodeRole;
|
||||
|
||||
using namespace QskSGNode;
|
||||
|
||||
template< typename Root, typename... Children >
|
||||
Q_REQUIRED_RESULT inline Root* ensureNodes( QSGNode* root = nullptr )
|
||||
{
|
||||
return ensureNodes< AppendMode::Recursive, Root, Children... >( root );
|
||||
}
|
||||
|
||||
float QskLevelingSensorSkinlet::outerRadius( const QskSkinnable* const skinnable )
|
||||
{
|
||||
const auto* const sensor = static_cast< const Q* >( skinnable );
|
||||
const auto contentsRect = sensor->contentsRect();
|
||||
return 0.5f * qMin( contentsRect.width(), contentsRect.height() );
|
||||
}
|
||||
|
||||
float QskLevelingSensorSkinlet::innerRadius( const QskSkinnable* const skinnable )
|
||||
{
|
||||
const auto scale = skinnable->strutSizeHint( Q::Horizon );
|
||||
return outerRadius( skinnable ) * scale.width();
|
||||
}
|
||||
|
||||
QPointF QskLevelingSensorSkinlet::center( const QskSkinnable* const skinnable )
|
||||
{
|
||||
const auto* const sensor = static_cast< const Q* >( skinnable );
|
||||
return sensor->contentsRect().center();
|
||||
}
|
||||
|
||||
QskLevelingSensorSkinlet::QskLevelingSensorSkinlet( QskSkin* skin )
|
||||
: Inherited( skin )
|
||||
{
|
||||
setNodeRoles( {
|
||||
OuterDisk,
|
||||
Horizon,
|
||||
HorizonClip,
|
||||
TickmarksX,
|
||||
TickmarksXLabels,
|
||||
TickmarksY,
|
||||
TickmarksYLabels,
|
||||
TickmarksZ,
|
||||
TickmarksZLabels,
|
||||
} );
|
||||
}
|
||||
|
||||
template<>
|
||||
Q_REQUIRED_RESULT QRectF QskLevelingSensorSkinlet::subControlRect< R::OuterDisk >(
|
||||
const QskLevelingSensor* const sensor, const QRectF& contentsRect ) const
|
||||
{
|
||||
const auto radius = outerRadius( sensor );
|
||||
const auto scale = sensor->strutSizeHint( Q::OuterDisk );
|
||||
const auto width = 2 * radius * scale.width();
|
||||
const auto height = width;
|
||||
const auto x = contentsRect.center().x() - width / 2;
|
||||
const auto y = contentsRect.center().y() - height / 2;
|
||||
return { x, y, width, height };
|
||||
}
|
||||
|
||||
template<>
|
||||
Q_REQUIRED_RESULT QRectF QskLevelingSensorSkinlet::subControlRect< R::Horizon >(
|
||||
const QskLevelingSensor* const sensor, const QRectF& contentsRect ) const
|
||||
{
|
||||
Q_UNUSED( contentsRect )
|
||||
const auto scale = sensor->strutSizeHint( Q::Horizon );
|
||||
const auto width = 2 * innerRadius( sensor ) * scale.width();
|
||||
const auto height = width;
|
||||
return { center( sensor ).x() - width / 2, center( sensor ).y() - height / 2, width, height };
|
||||
}
|
||||
|
||||
QRectF QskLevelingSensorSkinlet::subControlRect( const QskSkinnable* skinnable,
|
||||
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
|
||||
{
|
||||
const auto* const sensor = static_cast< const Q* >( skinnable );
|
||||
|
||||
if ( subControl == Q::OuterDisk )
|
||||
{
|
||||
return subControlRect< OuterDisk >( sensor, contentsRect );
|
||||
}
|
||||
if ( subControl == Q::Horizon )
|
||||
{
|
||||
return subControlRect< Horizon >( sensor, contentsRect );
|
||||
}
|
||||
|
||||
return Inherited::subControlRect( skinnable, contentsRect, subControl );
|
||||
}
|
||||
|
||||
template<>
|
||||
QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::OuterDisk >(
|
||||
const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const
|
||||
{
|
||||
Q_UNUSED( nodeRole )
|
||||
|
||||
const auto subControl = Q::OuterDisk;
|
||||
const auto contentsRect = sensor->contentsRect();
|
||||
const auto boxRect = subControlRect< OuterDisk >( sensor, contentsRect );
|
||||
const auto boxShapeMetrics = QskBoxShapeMetrics{ boxRect.width() / 2 };
|
||||
const auto boxBorderMetrics = sensor->boxBorderMetricsHint( subControl );
|
||||
const auto boxBorderColors = sensor->boxBorderColorsHint( subControl );
|
||||
const auto boxGradient = sensor->gradientHint( subControl );
|
||||
|
||||
auto* const root = ensureNodes< QSGTransformNode, QskBoxNode >( node );
|
||||
auto* const bNode = static_cast< QskBoxNode* >( root->firstChild() );
|
||||
|
||||
const auto size = outerRadius( sensor ) * sensor->strutSizeHint( Q::OuterDisk ).width();
|
||||
updateBoxNode( sensor, bNode, { 0, 0, 2 * size, 2 * size }, boxShapeMetrics, boxBorderMetrics,
|
||||
boxBorderColors, boxGradient );
|
||||
|
||||
const auto cX = center( sensor ).x();
|
||||
const auto cY = center( sensor ).y();
|
||||
const auto rZ = 0.0;
|
||||
|
||||
const auto matrix =
|
||||
matrix_deg( 0.0, cX, cY ) * matrix_deg( rZ, 0, 0 ) * matrix_deg( 0.0, -size, -size );
|
||||
|
||||
root->setMatrix( matrix );
|
||||
return root;
|
||||
}
|
||||
|
||||
template<>
|
||||
QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::Horizon >(
|
||||
const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const
|
||||
{
|
||||
Q_UNUSED( nodeRole )
|
||||
|
||||
const auto subControl = Q::Horizon;
|
||||
const State< QskAspect::Subcontrol > state( sensor, subControl );
|
||||
|
||||
const auto dY = 2 * sensor->angles().y();
|
||||
const auto pY = qBound( 0.0, 0.5 + ( -state.rX / dY ), 1.0 );
|
||||
|
||||
const auto shape = QskBoxShapeMetrics{ state.r1 };
|
||||
const auto metrics = sensor->boxBorderMetricsHint( subControl );
|
||||
const auto colors = sensor->boxBorderColorsHint( subControl );
|
||||
|
||||
auto gradient = sensor->gradientHint( Q::Horizon );
|
||||
gradient.setDirection( QskGradient::Linear );
|
||||
gradient.setLinearDirection( Qt::Vertical );
|
||||
gradient.setStops( { { 0.0, gradient.startColor() }, { pY, gradient.startColor() },
|
||||
{ pY, gradient.endColor() }, { 1.0, gradient.endColor() } } );
|
||||
|
||||
auto* const tNode = ensureNodes< QSGTransformNode, QskBoxNode >( node );
|
||||
auto* const boxNode = static_cast< QskBoxNode* >( tNode->firstChild() );
|
||||
updateBoxNode(
|
||||
sensor, boxNode, { 0, 0, 2 * state.r1, 2 * state.r1 }, shape, metrics, colors, gradient );
|
||||
|
||||
const auto matrix = matrix_deg( 0, state.cX, state.cY ) * matrix_deg( state.rZ, 0, 0 ) *
|
||||
matrix_deg( 0, -state.r1, -state.r1 );
|
||||
|
||||
tNode->setMatrix( matrix );
|
||||
return tNode;
|
||||
}
|
||||
|
||||
Q_REQUIRED_RESULT QSGNode* updateLinearTickmarksNode( const QskLevelingSensor* const sensor,
|
||||
const QskAspect::Subcontrol subControl, const QskScaleTickmarks& tickmarks,
|
||||
const Qt::Orientation orientation, QSGNode* const node )
|
||||
{
|
||||
const auto state = State< QskAspect::Subcontrol >( sensor, subControl );
|
||||
const auto color = sensor->color( subControl );
|
||||
const auto scale = sensor->strutSizeHint( subControl );
|
||||
const auto width = state.r1 * scale.width();
|
||||
const auto height = state.r1 * scale.height();
|
||||
const auto alignment = sensor->alignmentHint( subControl );
|
||||
|
||||
const auto opt = minmax( tickmarks );
|
||||
const auto min = opt ? opt->first : 0.0;
|
||||
const auto max = opt ? opt->second : 0.0;
|
||||
const auto interval = QskIntervalF{ min, max };
|
||||
|
||||
const auto rect =
|
||||
orientation == Qt::Horizontal
|
||||
? QRectF{ QPointF{ min * state.sX, -height }, QPointF{ max * state.sX, +height } }
|
||||
: QRectF{ QPointF{ -width, min * state.sY }, QPointF{ +width, max * state.sY } };
|
||||
|
||||
const auto translation = QPointF{ state.tX + state.cX, state.tY + state.cY };
|
||||
|
||||
auto* const cNode = ensureNodes< RadialClipNode, QSGTransformNode, QskTickmarksNode >( node );
|
||||
auto* const tNode = static_cast< QSGTransformNode* >( cNode->firstChild() );
|
||||
auto* const qNode = static_cast< QskTickmarksNode* >( tNode->firstChild() );
|
||||
|
||||
cNode->setGeometryProperties( state.r1, state.cX, state.cY );
|
||||
tNode->setMatrix( matrix_deg( state.rZ, translation.x(), translation.y() ) );
|
||||
qNode->update( color, rect, interval, tickmarks, 1, orientation, alignment );
|
||||
|
||||
return cNode;
|
||||
}
|
||||
|
||||
template<>
|
||||
QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::TickmarksX >(
|
||||
const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const
|
||||
{
|
||||
Q_UNUSED( nodeRole )
|
||||
return updateLinearTickmarksNode(
|
||||
sensor, Q::TickmarksX, sensor->tickmarks( Qt::XAxis ), Qt::Horizontal, node );
|
||||
}
|
||||
|
||||
template<>
|
||||
QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::TickmarksY >(
|
||||
const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const
|
||||
{
|
||||
Q_UNUSED( nodeRole )
|
||||
return updateLinearTickmarksNode(
|
||||
sensor, Q::TickmarksY, sensor->tickmarks( Qt::YAxis ), Qt::Vertical, node );
|
||||
}
|
||||
|
||||
template<>
|
||||
QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::TickmarksZ >(
|
||||
const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const
|
||||
{
|
||||
Q_UNUSED( nodeRole )
|
||||
const auto subControl = Q::TickmarksZ;
|
||||
const State< QskAspect::Subcontrol > state( sensor, subControl );
|
||||
const auto color = sensor->color( subControl );
|
||||
|
||||
const auto r3 = qvariant_cast< QVector3D >( sensor->effectiveSkinHint( subControl ) ) *
|
||||
( state.r2 - state.r1 ) +
|
||||
QVector3D{ ( float ) state.r1, ( float ) state.r1, ( float ) state.r1 };
|
||||
|
||||
auto* const transform = ensureNodes< QSGTransformNode, RadialTickmarksNode >( node );
|
||||
auto* const tickmarksNode = static_cast< RadialTickmarksNode* >( transform->firstChild() );
|
||||
tickmarksNode->setMaterialProperties( color );
|
||||
tickmarksNode->setGeometryProperties( sensor->tickmarks( Qt::ZAxis ), state.r1, r3 );
|
||||
|
||||
const auto matrix = matrix_deg( state.rZ, state.cX, state.cY );
|
||||
transform->setMatrix( matrix );
|
||||
return transform;
|
||||
}
|
||||
|
||||
template<>
|
||||
QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::TickmarksXLabels >(
|
||||
const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const
|
||||
{
|
||||
Q_UNUSED( nodeRole )
|
||||
const auto subControl = Q::TickmarksXLabels;
|
||||
const State< QskAspect::Subcontrol > state( sensor, subControl );
|
||||
const auto r3 = state.r1 * sensor->strutSizeHint( Q::TickmarksX ).height();
|
||||
const auto dX = qFastCos( qDegreesToRadians( 90 + state.rZ ) ) * r3;
|
||||
const auto dY = qFastSin( qDegreesToRadians( 90 + state.rZ ) ) * r3;
|
||||
|
||||
auto* const cNode =
|
||||
ensureNodes< RadialClipNode, QSGTransformNode, LinearTickmarksLabelsNode >( node );
|
||||
auto* const tNode = static_cast< QSGTransformNode* >( cNode->firstChild() );
|
||||
auto* const lNode = static_cast< LinearTickmarksLabelsNode* >( tNode->firstChild() );
|
||||
cNode->setGeometryProperties( state.r1, state.cX, state.cY );
|
||||
tNode->setMatrix( matrix_deg( state.rZ, state.cX + state.tX + dX, state.cY + state.tY + dY ) );
|
||||
lNode->update(
|
||||
sensor, subControl, sensor->tickmarkLabels( Qt::XAxis ), { state.scale().x(), 0.0 } );
|
||||
return cNode;
|
||||
}
|
||||
|
||||
template<>
|
||||
QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::TickmarksYLabels >(
|
||||
const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const
|
||||
{
|
||||
Q_UNUSED( nodeRole )
|
||||
const auto subControl = Q::TickmarksYLabels;
|
||||
const State< QskAspect::Subcontrol > state( sensor, subControl );
|
||||
const auto r3 = state.r1 * sensor->strutSizeHint( Q::TickmarksY ).width();
|
||||
const auto dX = qFastCos( qDegreesToRadians( state.rZ ) ) * r3;
|
||||
const auto dY = qFastSin( qDegreesToRadians( state.rZ ) ) * r3;
|
||||
|
||||
auto* const cNode =
|
||||
ensureNodes< RadialClipNode, QSGTransformNode, LinearTickmarksLabelsNode >( node );
|
||||
auto* const tNode = static_cast< QSGTransformNode* >( cNode->firstChild() );
|
||||
auto* const lNode = static_cast< LinearTickmarksLabelsNode* >( tNode->firstChild() );
|
||||
cNode->setGeometryProperties( state.r1, state.cX, state.cY );
|
||||
tNode->setMatrix( matrix_deg( state.rZ, state.cX + state.tX + dX, state.cY + state.tY + dY ) );
|
||||
lNode->update(
|
||||
sensor, subControl, sensor->tickmarkLabels( Qt::YAxis ), { 0.0, state.scale().y() } );
|
||||
return cNode;
|
||||
}
|
||||
|
||||
template<>
|
||||
QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::TickmarksZLabels >(
|
||||
const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const
|
||||
{
|
||||
Q_UNUSED( nodeRole )
|
||||
const auto subControl = Q::TickmarksZLabels;
|
||||
const State< QskAspect::Subcontrol > state( sensor, subControl );
|
||||
auto* const tNode = ensureNodes< QSGTransformNode, RadialTickmarksLabelsNode >( node );
|
||||
auto* const lNode = static_cast< RadialTickmarksLabelsNode* >( tNode->firstChild() );
|
||||
const auto r3 = static_cast< float >( state.r1 * sensor->strutSizeHint( subControl ).width() );
|
||||
lNode->update( sensor, subControl, sensor->tickmarkLabels( Qt::ZAxis ), { r3, r3 } );
|
||||
tNode->setMatrix( state.matrix() );
|
||||
return tNode;
|
||||
}
|
||||
|
||||
template<>
|
||||
QSGNode* QskLevelingSensorSkinlet::updateSubNode< R::HorizonClip >(
|
||||
const QskLevelingSensor* const sensor, const quint8 nodeRole, QSGNode* const node ) const
|
||||
{
|
||||
Q_UNUSED( nodeRole )
|
||||
const auto cX = center( sensor ).x();
|
||||
const auto cY = center( sensor ).y();
|
||||
const auto r1 = innerRadius( sensor );
|
||||
|
||||
auto* const clipNode = ensureNodes< RadialClipNode >( node );
|
||||
clipNode->setGeometryProperties( r1, cX, cY );
|
||||
return clipNode;
|
||||
}
|
||||
|
||||
QSGNode* QskLevelingSensorSkinlet::updateSubNode(
|
||||
const QskSkinnable* const skinnable, const quint8 nodeRole, QSGNode* const node ) const
|
||||
{
|
||||
const auto* const sensor = static_cast< const Q* >( skinnable );
|
||||
|
||||
switch ( static_cast< R >( nodeRole ) )
|
||||
{
|
||||
case OuterDisk:
|
||||
return updateSubNode< OuterDisk >( sensor, nodeRole, node );
|
||||
case Horizon:
|
||||
return updateSubNode< Horizon >( sensor, nodeRole, node );
|
||||
case HorizonClip:
|
||||
return updateSubNode< HorizonClip >( sensor, nodeRole, node );
|
||||
case TickmarksX:
|
||||
return updateSubNode< TickmarksX >( sensor, nodeRole, node );
|
||||
case TickmarksXLabels:
|
||||
return updateSubNode< TickmarksXLabels >( sensor, nodeRole, node );
|
||||
case TickmarksY:
|
||||
return updateSubNode< TickmarksY >( sensor, nodeRole, node );
|
||||
case TickmarksYLabels:
|
||||
return updateSubNode< TickmarksYLabels >( sensor, nodeRole, node );
|
||||
case TickmarksZ:
|
||||
return updateSubNode< TickmarksZ >( sensor, nodeRole, node );
|
||||
case TickmarksZLabels:
|
||||
return updateSubNode< TickmarksZLabels >( sensor, nodeRole, node );
|
||||
default:
|
||||
return Inherited::updateSubNode( sensor, nodeRole, node );
|
||||
}
|
||||
}
|
||||
|
||||
#include "moc_QskLevelingSensorSkinlet.cpp"
|
|
@ -0,0 +1,81 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef QSK_LEVELING_SENSOR_UTILITY_H
|
||||
#define QSK_LEVELING_SENSOR_UTILITY_H
|
||||
|
||||
#include <qmath.h>
|
||||
#include <qmatrix4x4.h>
|
||||
#include <qtransform.h>
|
||||
|
||||
#include <QskFunctions.h>
|
||||
#include <QskScaleTickmarks.h>
|
||||
|
||||
Q_REQUIRED_RESULT inline QMatrix4x4 matrix_deg( float rZ = 0.0f, float tX = 0.0f, float tY = 0.0f )
|
||||
{
|
||||
QTransform transform;
|
||||
transform.translate( tX, tY );
|
||||
transform.rotate( rZ, Qt::ZAxis );
|
||||
return transform;
|
||||
}
|
||||
|
||||
template< typename T >
|
||||
Q_REQUIRED_RESULT inline bool compareExchange( T& dst, const T& src )
|
||||
{
|
||||
if ( dst != src )
|
||||
{
|
||||
dst = src;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<>
|
||||
Q_REQUIRED_RESULT inline bool compareExchange< float >( float& dst, const float& src )
|
||||
{
|
||||
if ( !qskFuzzyCompare( dst, src ) )
|
||||
{
|
||||
dst = src;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<>
|
||||
Q_REQUIRED_RESULT inline bool compareExchange< qreal >( qreal& dst, const qreal& src )
|
||||
{
|
||||
if ( !qskFuzzyCompare( dst, src ) )
|
||||
{
|
||||
dst = src;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Q_REQUIRED_RESULT inline QskScaleTickmarks filtered( const QskScaleTickmarks& tickmarks,
|
||||
const std::function< bool( QskScaleTickmarks::TickType, qreal ) >& predicate )
|
||||
{
|
||||
QskScaleTickmarks result;
|
||||
QVector< qreal > ticks[ 3 ];
|
||||
|
||||
using T = QskScaleTickmarks::TickType;
|
||||
for ( auto type : { T::MinorTick, T::MediumTick, T::MajorTick } )
|
||||
{
|
||||
for ( const auto tick : tickmarks.ticks( type ) )
|
||||
{
|
||||
if ( predicate( type, tick ) )
|
||||
{
|
||||
ticks[ type ] << tick;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.setMinorTicks( ticks[ QskScaleTickmarks::MinorTick ] );
|
||||
result.setMediumTicks( ticks[ QskScaleTickmarks::MediumTick ] );
|
||||
result.setMajorTicks( ticks[ QskScaleTickmarks::MajorTick ] );
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,47 @@
|
|||
/******************************************************************************
|
||||
* QSkinny - Copyright (C) 2016 Uwe Rathmann
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*****************************************************************************/
|
||||
|
||||
#ifndef QSK_SGNODE_UTILITY_H
|
||||
#define QSK_SGNODE_UTILITY_H
|
||||
|
||||
#include <QSGNode>
|
||||
|
||||
namespace QskSGNode
|
||||
{
|
||||
enum AppendMode
|
||||
{
|
||||
Sequential,
|
||||
Recursive
|
||||
};
|
||||
|
||||
template< AppendMode mode, typename Root, typename... Children >
|
||||
Q_REQUIRED_RESULT inline Root* ensureNodes( QSGNode* root = nullptr )
|
||||
{
|
||||
if ( root == nullptr )
|
||||
{
|
||||
root = new Root;
|
||||
}
|
||||
|
||||
if constexpr ( mode == Recursive )
|
||||
{
|
||||
QSGNode* current = root;
|
||||
Q_UNUSED( current )
|
||||
(
|
||||
[ ¤t ]( QSGNode* const child ) mutable {
|
||||
current->appendChildNode( child );
|
||||
current = child;
|
||||
}( new Children ),
|
||||
... );
|
||||
}
|
||||
else
|
||||
{
|
||||
( root->appendChildNode( new Children ), ... );
|
||||
}
|
||||
|
||||
return static_cast< Root* >( root );
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue