synched with master

This commit is contained in:
Uwe Rathmann 2024-12-02 11:03:37 +01:00
parent 6a8d9096be
commit 128ce8f79b
47 changed files with 2042 additions and 935 deletions

View File

@ -1008,7 +1008,7 @@ void Editor::setupPushButtonColors(
const auto text = Q::Text | section | variation;
const auto icon = Q::Icon | section | variation;
for ( const auto state : { QskAspect::NoState, Q::Hovered, Q::Pressed, Q::Disabled } )
for ( const auto state : { QskAspect::NoState, Q::Hovered, Q::Pressed, Q::Checked, Q::Disabled } )
{
QRgb panelColor, borderColor1, borderColor2, textColor;
int graphicRole;
@ -1023,7 +1023,7 @@ void Editor::setupPushButtonColors(
textColor = pal.fillColor.textOnAccent.primary;
graphicRole = W::GraphicRoleFillColorTextOnAccentPrimary;
}
else if ( state == Q::Pressed )
else if ( state == Q::Pressed || state == Q::Checked )
{
panelColor = pal.fillColor.accent.tertiary;
borderColor1 = borderColor2 = pal.strokeColor.control.onAccentDefault;
@ -1056,7 +1056,7 @@ void Editor::setupPushButtonColors(
textColor = pal.fillColor.text.primary;
graphicRole = W::GraphicRoleFillColorTextPrimary;
}
else if ( state == Q::Pressed )
else if ( state == Q::Pressed || state == Q::Checked )
{
panelColor = pal.fillColor.control.tertiary;
borderColor1 = borderColor2 = pal.strokeColor.control.defaultColor;
@ -1450,10 +1450,16 @@ void Editor::setupSliderMetrics()
setShadowMetrics( Q::Handle, { shadowSpread, 0.0 } );
setBoxBorderMetrics( Q::Handle, 5 );
setBoxBorderMetrics( Q::Handle | Q::Hovered, 4 );
setBoxBorderMetrics( Q::Handle | Q::Pressed, 6 );
setBoxBorderMetrics( Q::Handle | Q::Disabled, 6 );
setBoxBorderMetrics( Q::Handle, 5_px );
setBoxBorderMetrics( Q::Handle | Q::Hovered, 4_px );
setBoxBorderMetrics( Q::Handle | Q::Pressed, 6_px );
setBoxBorderMetrics( Q::Handle | Q::Disabled, 6_px );
setFlag( Q::Tick | A::Option, Qsk::Maybe );
setStrutSize( Q::Tick | A::Horizontal, 1_px, -1 );
setStrutSize( Q::Tick | A::Vertical, -1, 1_px );
setAnimation( Q::Handle | A::Metric | A::Position, 100 );
}
void Editor::setupSliderColors(
@ -1484,30 +1490,35 @@ void Editor::setupSliderColors(
for ( auto state : { A::NoState, Q::Hovered, Q::Pressed, Q::Disabled } )
{
QRgb grooveColor, handleColor;
QRgb grooveColor, fillColor, handleColor;
if ( state == A::NoState || state == Q::Hovered )
{
grooveColor = pal.fillColor.controlStrong.defaultColor;
fillColor = pal.fillColor.accent.defaultColor;
handleColor = pal.fillColor.accent.defaultColor;
}
else if ( state == Q::Pressed )
{
grooveColor = pal.fillColor.controlStrong.defaultColor;
handleColor = pal.fillColor.accent.tertiary;
fillColor = pal.fillColor.accent.defaultColor;
}
else if ( state == Q::Disabled )
{
grooveColor = pal.fillColor.controlStrong.disabled;
fillColor = pal.fillColor.accent.disabled;
handleColor = grooveColor;
}
grooveColor = rgbSolid( grooveColor, pal.background.solid.base );
setGradient( Q::Groove | section | state, grooveColor );
setGradient( Q::Fill | section | state, grooveColor );
setGradient( Q::Fill | section | state, fillColor );
setGradient( Q::Handle | section | state, handleColor );
}
setGradient( Q::Tick, pal.fillColor.controlSolid.defaultColor );
}
void Editor::setupSpinBoxMetrics()

View File

@ -46,6 +46,8 @@
#include <QskTextLabel.h>
#include <QskVirtualKeyboard.h>
#include <QskSliderSkinlet.h>
#include <QskAnimationHint.h>
#include <QskAspect.h>
#include <QskBoxBorderColors.h>
@ -773,9 +775,10 @@ void Editor::setupSlider()
{
using A = QskAspect;
using Q = QskSlider;
using SK = QskSliderSkinlet;
using P = QPalette;
const qreal extent = 30_px;
const qreal extent = 16_px;
// Panel
@ -784,6 +787,7 @@ void Editor::setupSlider()
setBoxBorderMetrics( Q::Panel, 0 );
setGradient( Q::Panel, QskGradient() );
// space for the handle
setPadding( Q::Panel | A::Horizontal, QskMargins( 0.5 * extent, 0 ) );
setPadding( Q::Panel | A::Vertical, QskMargins( 0, 0.5 * extent ) );
@ -823,6 +827,39 @@ void Editor::setupSlider()
}
}
{
/*
Tick
QSlider optionally allows to display ticks left/right or top/bottom
of the groove. However this not supported by the skinlets yet.
( Qt/Quick slider does not support showing ticks at all )
For the moment we make something up. TODO ...
*/
setFlag( Q::Tick | A::Option, Qsk::Maybe );
setStrutSize( Q::Tick, 2_px, 2_px );
const QskStateCombination combination(
QskStateCombination::CombinationNoState, Q::Focused | Q::Pressed );
const auto rgb = m_pal.active( P::Text );
setColor( Q::Tick,
QskRgb::interpolated( rgb, m_pal.groove, 0.2 ), combination );
setColor( Q::Tick | Q::Disabled,
QskRgb::interpolated( rgb, m_pal.groove, 0.5 ) );
setColor( Q::Tick | SK::Filled,
m_pal.active( P::HighlightedText ), combination );
setColor( Q::Tick | SK::Filled | Q::Disabled,
m_pal.disabled( P::HighlightedText ) );
}
// Handle
setBoxShape( Q::Handle, 2 );
setBoxBorderMetrics( Q::Handle, 1 );
setBoxBorderColors( Q::Handle, m_pal.outline );
@ -832,7 +869,7 @@ void Editor::setupSlider()
Combination( { Q::Hovered, Q::Pressed } ) );
#endif
setStrutSize( Q::Handle, 16_px, 16_px );
setStrutSize( Q::Handle, extent, extent );
for ( auto state : { A::NoState, Q::Pressed } )
{
@ -845,9 +882,7 @@ void Editor::setupSlider()
QskRgb::lighter( rgb, 102 ) );
}
// move the handle smoothly, when using keys
setAnimation( Q::Handle | A::Metric | A::Position, 2 * qskDuration );
setAnimation( Q::Handle | A::Metric | A::Position | Q::Pressed, 0 );
}
void Editor::setupSpinBox()

View File

@ -3,11 +3,28 @@
# SPDX-License-Identifier: BSD-3-Clause
############################################################################
set(SOURCES
QskMaterial3Global.h QskMaterial3Skin.h QskMaterial3Skin.cpp
QskMaterial3SkinFactory.h QskMaterial3SkinFactory.cpp
list(APPEND HEADERS
QskMaterial3Global.h QskMaterial3Skin.h QskMaterial3SkinFactory.h
)
list(APPEND PRIVATE_HEADERS
QskMaterial3ProgressBarSkinlet.h
QskMaterial3SliderSkinlet.h
)
list(APPEND SOURCES
QskMaterial3Skin.cpp
QskMaterial3SkinFactory.cpp
QskMaterial3ProgressBarSkinlet.cpp
QskMaterial3SliderSkinlet.cpp
)
qt_add_resources(SOURCES QskMaterial3Icons.qrc)
qsk_add_plugin(material3skin skins QskMaterial3SkinFactory ${SOURCES})
set_target_properties(material3skin PROPERTIES DEFINE_SYMBOL QSK_MATERIAL3_MAKEDLL )
qsk_add_plugin(material3skin skins QskMaterial3SkinFactory
${SOURCES} ${HEADERS} ${PRIVATE_HEADERS}
)
set_target_properties(material3skin PROPERTIES
DEFINE_SYMBOL QSK_MATERIAL3_MAKEDLL
)

View File

@ -0,0 +1,96 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskMaterial3ProgressBarSkinlet.h"
#include <QskProgressBar.h>
#include <QskBoxShapeMetrics.h>
#include <QskBoxBorderMetrics.h>
#include <QskBoxBorderColors.h>
#include <QskMargins.h>
#include <QskClipNode.h>
#include <QskSGNode.h>
using Q = QskProgressBar;
QskMaterial3ProgressBarSkinlet::QskMaterial3ProgressBarSkinlet( QskSkin* skin )
: Inherited( skin )
{
appendNodeRoles( { StopIndicatorRole } );
}
QSGNode* QskMaterial3ProgressBarSkinlet::updateSubNode(
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
auto progressBar = static_cast< const QskProgressBar* >( skinnable );
switch( nodeRole )
{
case GrooveRole:
{
auto clippedNode = QskSGNode::findChildNode( node, GrooveRole );
clippedNode = Inherited::updateSubNode( skinnable, nodeRole, clippedNode );
if ( clippedNode == nullptr )
return nullptr;
auto clipNode = updateGrooveClipNode( progressBar, node );
QskSGNode::setNodeRole( clippedNode, nodeRole );
QskSGNode::setParentNode( clippedNode, clipNode );
return clipNode;
}
case StopIndicatorRole:
{
if ( !( progressBar->isIndeterminate() || progressBar->hasOrigin() ) )
return updateStopIndicatorNode( progressBar, node );
return nullptr;
}
}
return Inherited::updateSubNode( skinnable, nodeRole, node );
}
QSGNode* QskMaterial3ProgressBarSkinlet::updateStopIndicatorNode(
const QskProgressBar* progressBar, QSGNode* node ) const
{
auto rect = progressBar->subControlRect( Q::Groove );
if ( rect.isEmpty() )
return nullptr;
if( progressBar->orientation() == Qt::Horizontal )
rect.setLeft( rect.right() - rect.height() );
else
rect.setBottom( rect.top() + rect.width() );
const auto color = progressBar->gradientHint( Q::Fill ).endColor();
const auto shape = progressBar->boxShapeHint( Q::Fill );
return updateBoxNode( progressBar, node, rect, shape,
QskBoxBorderMetrics(), QskBoxBorderColors(), color );
}
QSGNode* QskMaterial3ProgressBarSkinlet::updateGrooveClipNode(
const QskProgressBar* progressBar, QSGNode* node ) const
{
auto rect = progressBar->subControlRect( Q::Fill );
if ( rect.isEmpty() )
return nullptr;
QskMargins margins;
if ( progressBar->orientation() == Qt::Horizontal )
margins.setMargins( rect.height(), 0.0 );
else
margins.setMargins( 0.0, rect.width() );
rect = rect.marginsAdded( margins );
auto clipNode = QskSGNode::ensureNode< QskClipNode >( node );
clipNode->setRegion( progressBar->subControlRect( Q::Groove ), rect );
return clipNode;
}
#include "moc_QskMaterial3ProgressBarSkinlet.cpp"

View File

@ -0,0 +1,37 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_MATERIAL3_PROGRESSBAR_SKINLET_H
#define QSK_MATERIAL3_PROGRESSBAR_SKINLET_H
#include <QskProgressBarSkinlet.h>
class QskProgressBar;
class QskMaterial3ProgressBarSkinlet : QskProgressBarSkinlet
{
Q_GADGET
using Inherited = QskProgressBarSkinlet;
public:
enum NodeRole
{
StopIndicatorRole = Inherited::RoleCount,
RoleCount
};
Q_INVOKABLE QskMaterial3ProgressBarSkinlet( QskSkin* = nullptr );
protected:
QSGNode* updateSubNode( const QskSkinnable*,
quint8 nodeRole, QSGNode* ) const override;
private:
QSGNode* updateStopIndicatorNode( const QskProgressBar*, QSGNode* ) const;
QSGNode* updateGrooveClipNode( const QskProgressBar*, QSGNode* ) const;
};
#endif

View File

@ -9,6 +9,8 @@
*/
#include "QskMaterial3Skin.h"
#include "QskMaterial3ProgressBarSkinlet.h"
#include "QskMaterial3SliderSkinlet.h"
#include <QskSkinHintTableEditor.h>
@ -486,20 +488,13 @@ void Editor::setupProgressBar()
using A = QskAspect;
using Q = QskProgressBar;
auto size = 4_dp;
for ( auto subControl : { Q::Groove, Q::Fill } )
{
setMetric( subControl | A::Size, size );
setPadding( subControl, 0 );
setBoxShape( subControl, 0 );
setBoxBorderMetrics( subControl, 0 );
setBoxShape( subControl, { 100, Qt::RelativeSize } );
setMetric( subControl | A::Size, 4_dp );
}
setMetric( Q::Groove | A::Size, size );
setGradient( Q::Groove, m_pal.surfaceContainerHighest );
setGradient( Q::Groove | Q::Disabled, m_pal.onSurface12 );
setGradient( Q::Fill, m_pal.primary );
@ -510,9 +505,15 @@ void Editor::setupProgressRing()
{
using Q = QskProgressRing;
setArcMetrics( Q::Groove, 90, -360, 4_dp );
setGradient( Q::Groove, m_pal.surfaceContainerHighest );
setGradient( Q::Groove | Q::Disabled, m_pal.onSurface12 );
setSpacing( Q::Fill, 10 );
setStrutSize( Q::Fill, { 48_dp, 48_dp } );
setGradient( Q::Fill, m_pal.primary );
setArcMetrics( Q::Fill, 90, -360, 4_dp );
setGradient( Q::Fill, m_pal.primary );
setGradient( Q::Fill | Q::Disabled, m_pal.onSurface38 );
}
void Editor::setupRadioBox()
@ -725,6 +726,7 @@ void Editor::setupPushButton()
setBoxShape( Q::Splash, 40_dp );
setAnimation( Q::Splash | QskAspect::Color, qskDuration );
const auto checkedOpacity = m_pal.focusOpacity + m_pal.pressedOpacity;
// elevated buttons:
@ -751,8 +753,11 @@ void Editor::setupPushButton()
setGradient( Q::Panel | M3::Elevated | Q::Focused, elevatedPressedColor );
setShadowMetrics( Q::Panel | M3::Elevated | Q::Focused, m_pal.elevation1 );
setGradient( Q::Panel | M3::Elevated | Q::Pressed, elevatedPressedColor );
setShadowMetrics( Q::Panel | M3::Elevated | Q::Pressed, m_pal.elevation1 );
for( const auto state: { Q::Pressed, Q::Checked } )
{
setGradient( Q::Panel | M3::Elevated | state, elevatedPressedColor );
setShadowMetrics( Q::Panel | M3::Elevated | state, m_pal.elevation1 );
}
// normal buttons (i.e. Filled):
@ -769,6 +774,8 @@ void Editor::setupPushButton()
setGradient( Q::Panel | Q::Focused, focusColor );
setGradient( Q::Panel | Q::Pressed, focusColor );
setGradient( Q::Panel | Q::Checked,
flattenedColor( m_pal.onPrimary, m_pal.primary, checkedOpacity ) );
setGradient( Q::Splash, stateLayerColor( m_pal.onPrimary, m_pal.hoverOpacity ) );
@ -806,6 +813,10 @@ void Editor::setupPushButton()
setGradient( Q::Panel | M3::Tonal | Q::Pressed, tonalPressedColor );
setShadowMetrics( Q::Panel | M3::Tonal | Q::Pressed, m_pal.elevation0 );
const auto tonalCheckedColor = flattenedColor( m_pal.onSecondaryContainer,
m_pal.secondaryContainer, checkedOpacity );
setGradient( Q::Panel | M3::Tonal | Q::Checked, tonalCheckedColor );
// outlined buttons:
@ -831,6 +842,7 @@ void Editor::setupPushButton()
setGradient( Q::Panel | M3::Outlined | Q::Pressed, m_pal.primary12 );
setGradient( Q::Panel | M3::Outlined | Q::Checked, m_pal.primary12 );
/*
text buttons:
@ -857,6 +869,8 @@ void Editor::setupPushButton()
setGradient( Q::Panel | M3::Text | Q::Focused, m_pal.primary12 );
setGradient( Q::Panel | M3::Text | Q::Pressed, m_pal.primary12 );
setGradient( Q::Panel | M3::Text | Q::Checked, m_pal.primary12 );
}
void Editor::setupDialogButtonBox()
@ -892,41 +906,60 @@ void Editor::setupSlider()
{
using A = QskAspect;
using Q = QskSlider;
using SK = QskSliderSkinlet;
const QSizeF sliderSize( 48_dp, 44_dp );
setStrutSize( Q::Panel | A::Horizontal, sliderSize );
setStrutSize( Q::Panel | A::Vertical, sliderSize.transposed() );
const auto extentGroove = 16_dp;
const auto extentPanel = 44_dp;
setBoxShape( Q::Groove | A::Horizontal, { 0, 100, 0, 100, Qt::RelativeSize } );
setBoxShape( Q::Groove | A::Vertical, { 100, 100, 0, 0, Qt::RelativeSize } );
setMetric( Q::Groove | A::Size, 16_dp );
setMargin( Q::Groove | A::Horizontal, { 6_dp, 0, 0, 0 } );
setMargin( Q::Groove | A::Vertical, {0, 0, 0, 6_dp } );
setStrutSize( Q::Panel | A::Horizontal, 3 * extentGroove, extentPanel );
setStrutSize( Q::Panel | A::Vertical, extentPanel, 3 * extentGroove );
setMetric( Q::Groove | A::Size, extentGroove );
setMetric( Q::Fill | A::Size, extentGroove );
setGradient( Q::Groove, m_pal.primaryContainer );
setGradient( Q::Groove | Q::Disabled, m_pal.onSurface12 );
setBoxShape( Q::Fill | A::Horizontal, { 100, 0, 100, 0, Qt::RelativeSize } );
setBoxShape( Q::Fill | A::Vertical, { 0, 0, 100, 100, Qt::RelativeSize } );
setMetric( Q::Fill | A::Size, 16_dp );
setMargin( Q::Fill | A::Horizontal, { 0, 0, 6_dp, 0 } );
setMargin( Q::Fill | A::Vertical, {0, 6_dp, 0, 0 } );
setGradient( Q::Fill, m_pal.primary );
setGradient( Q::Fill | Q::Disabled, m_pal.onSurface38 );
setBoxShape( Q::Handle, 100, Qt::RelativeSize );
setBoxBorderMetrics( Q::Handle, 0 );
setBoxShape( Q::Groove, 100, Qt::RelativeSize );
setBoxShape( Q::Fill, 100, Qt::RelativeSize );
const QSizeF handleSize( 4_dp, 44_dp );
const QSizeF handleSizeFocusedPressed( 2_dp, 44_dp );
setStrutSize( Q::Handle | A::Horizontal, handleSize );
setStrutSize( Q::Handle | A::Horizontal, handleSizeFocusedPressed,
{ QskStateCombination::Combination, Q::Focused | Q::Pressed } );
setStrutSize( Q::Tick, { 4_dp, 4_dp } );
setBoxShape( Q::Tick, 100, Qt::RelativeSize );
setStrutSize( Q::Handle | A::Vertical, handleSize.transposed() );
setStrutSize( Q::Handle | A::Vertical, handleSizeFocusedPressed.transposed(),
{ QskStateCombination::Combination, Q::Focused | Q::Pressed } );
setGradient( Q::Tick, m_pal.primary );
setGradient( Q::Tick | Q::Disabled, m_pal.onSurface );
setGradient( Q::Tick | SK::Filled, m_pal.secondaryContainer,
{ QskStateCombination::CombinationNoState, Q::Focused | Q::Pressed } );
setGradient( Q::Tick | SK::Filled | Q::Disabled, m_pal.inverseOnSurface );
setFlag( Q::Fill | A::Option, Qsk::Maybe );
setFlag( Q::Tick | A::Option, Qsk::Maybe );
for ( const auto variation : { A::Horizontal, A::Vertical } )
{
QSizeF handleSize( extentGroove, extentPanel );
QskMargins margin1{ 6_dp, 0_dp };
QskMargins margin2{ 7_dp, 0_dp };
if ( variation == A::Vertical )
{
handleSize = handleSize.transposed();
margin1 = margin1.rotated();
margin2 = margin2.rotated();
}
const auto aspect = Q::Handle | variation;
setStrutSize( aspect, handleSize );
setMargin( aspect, margin1 );
setMargin( aspect, margin2,
{ QskStateCombination::Combination, Q::Focused | Q::Pressed } );
}
setGradient( Q::Handle, m_pal.primary );
setGradient( Q::Handle | Q::Pressed, m_pal.primary );
@ -934,23 +967,6 @@ void Editor::setupSlider()
const auto disabledColor = flattenedColor( m_pal.onSurface, m_pal.background, 0.38 );
setGradient( Q::Handle | Q::Disabled, disabledColor );
for( auto indicator : { Q::GrooveStopIndicators, Q::FillStopIndicators } )
{
setStrutSize( indicator, { 4_dp, 4_dp } );
setBoxShape( indicator, 100, Qt::RelativeSize );
}
const auto p = 6_dp;
setPadding( Q::GrooveStopIndicators | A::Horizontal, { p, 0, p, 0 } );
setPadding( Q::GrooveStopIndicators | A::Vertical, { 0, p, 0, p } );
setPadding( Q::FillStopIndicators | A::Horizontal, { p, 0, p, 0 } );
setPadding( Q::FillStopIndicators | A::Vertical, { 0, p, 0, p } );
setGradient( Q::GrooveStopIndicators, m_pal.primary );
setGradient( Q::GrooveStopIndicators | Q::Disabled, m_pal.onSurface );
setGradient( Q::FillStopIndicators, m_pal.secondaryContainer );
setGradient( Q::FillStopIndicators | Q::Disabled, m_pal.inverseOnSurface );
for( const auto state : { Q::Focused, Q::Pressed } )
{
setStrutSize( Q::LabelContainer | state, 48_dp, 44_dp,
@ -966,9 +982,7 @@ void Editor::setupSlider()
setColor( Q::LabelText, m_pal.inverseOnSurface );
setAlignment( Q::LabelText, Qt::AlignCenter );
// move the handle smoothly when using keys
setAnimation( Q::Handle | A::Metric | A::Position, 2 * qskDuration );
setAnimation( Q::Handle | A::Metric | A::Position | Q::Pressed, 0 );
}
void Editor::setupSpinBox()
@ -1596,6 +1610,8 @@ qreal QskMaterial3Theme::stateOpacity( int state ) const
QskMaterial3Skin::QskMaterial3Skin( QObject* parent )
: Inherited( parent )
{
declareSkinlet< QskProgressBar, QskMaterial3ProgressBarSkinlet >();
declareSkinlet< QskSlider, QskMaterial3SliderSkinlet >();
}
QskMaterial3Skin::~QskMaterial3Skin()

View File

@ -0,0 +1,124 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskMaterial3SliderSkinlet.h"
#include <QskSlider.h>
#include <QskClipNode.h>
#include <QskSGNode.h>
#include <QskFunctions.h>
using Q = QskSlider;
static inline bool qskHasOrigin( const QskSlider* slider )
{
return !qskFuzzyCompare( slider->origin(), slider->minimum() );
}
QskMaterial3SliderSkinlet::QskMaterial3SliderSkinlet( QskSkin* skin )
: Inherited( skin )
{
}
QRectF QskMaterial3SliderSkinlet::subControlRect( const QskSkinnable* skinnable,
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
{
if ( subControl == Q::Scale )
{
auto r = Inherited::subControlRect( skinnable, contentsRect, Q::Scale );
const auto handleSize = skinnable->strutSizeHint( Q::Handle );
const auto slider = static_cast< const QskSlider* >( skinnable );
if( slider->orientation() == Qt::Horizontal )
{
const auto m = 0.5 * handleSize.width();
r.adjust( m, 0.0, -m, 0.0 );
}
{
const auto m = 0.5 * handleSize.height();
r.adjust( 0.0, m, 0.0, -m );
}
return r;
}
return Inherited::subControlRect( skinnable, contentsRect, subControl );
}
QSGNode* QskMaterial3SliderSkinlet::updateSubNode(
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
switch( nodeRole )
{
case GrooveRole:
case FillRole:
{
auto clippedNode = QskSGNode::findChildNode( node, nodeRole );
clippedNode = Inherited::updateSubNode( skinnable, nodeRole, clippedNode );
if ( clippedNode )
{
const auto slider = static_cast< const QskSlider* >( skinnable );
auto clipNode = QskSGNode::ensureNode< QskClipNode >( node );
clipNode->setRegion( slider->subControlRect( Q::Panel ),
slider->subControlRect( Q::Handle ) );
QskSGNode::setNodeRole( clippedNode, nodeRole );
QskSGNode::setParentNode( clippedNode, clipNode );
return clipNode;
}
return nullptr;
}
}
return Inherited::updateSubNode( skinnable, nodeRole, node );
}
QVector< qreal > QskMaterial3SliderSkinlet::graduation( const QskSlider* slider ) const
{
QVector< qreal > graduation;
if ( hasGraduation( slider ) )
{
const auto g = Inherited::graduation( slider );
// adding the boundaries
graduation.reserve( g.count() + 2 );
graduation += slider->minimum();
graduation += g;
graduation += slider->maximum();
}
else
{
const auto policy = slider->flagHint< Qsk::Policy >(
Q::Tick | QskAspect::Option, Qsk::Maybe );
if ( policy != Qsk::Never )
{
if ( qskHasOrigin( slider ) )
{
graduation.reserve( 3 );
graduation += slider->minimum();
graduation += slider->origin();
graduation += slider->maximum();
}
else
{
graduation.reserve( 1 );
graduation += slider->maximum();
}
}
}
return graduation;
}
#include "moc_QskMaterial3SliderSkinlet.cpp"

View File

@ -0,0 +1,30 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_MATERIAL3_SLIDER_SKINLET_H
#define QSK_MATERIAL3_SLIDER_SKINLET_H
#include <QskSliderSkinlet.h>
class QskMaterial3SliderSkinlet : QskSliderSkinlet
{
Q_GADGET
using Inherited = QskSliderSkinlet;
public:
Q_INVOKABLE QskMaterial3SliderSkinlet( QskSkin* = nullptr );
QRectF subControlRect( const QskSkinnable*,
const QRectF& rect, QskAspect::Subcontrol ) const override;
protected:
QSGNode* updateSubNode( const QskSkinnable*,
quint8 nodeRole, QSGNode* ) const override;
QVector< qreal > graduation( const QskSlider* ) const override;
};
#endif

View File

@ -1,4 +1,4 @@
# Doxyfile 1.9.3
# Doxyfile 1.9.8
# This file describes the settings to be used by the documentation system
# doxygen (www.doxygen.org) for a project.
@ -12,6 +12,16 @@
# For lists, items can also be appended using:
# TAG += value [value, ...]
# Values that contain spaces should be placed between quotes (\" \").
#
# Note:
#
# Use doxygen to compare the used configuration file with the template
# configuration file:
# doxygen -x [configFile]
# Use doxygen to compare the used configuration file with the template
# configuration file without replacing the environment variables or CMake type
# replacement variables:
# doxygen -x_noenv [configFile]
#---------------------------------------------------------------------------
# Project related configuration options
@ -38,13 +48,13 @@ PROJECT_NAME = QSkinny
# could be handy for archiving the generated documentation or if some version
# control system is used.
PROJECT_NUMBER = 0.0.1
PROJECT_NUMBER = 0.8.0
# Using the PROJECT_BRIEF tag one can provide an optional one line description
# for a project that appears at the top of each page and should give viewer a
# quick idea about the purpose of the project. Keep the description short.
PROJECT_BRIEF =
PROJECT_BRIEF = C++/Qt UI toolkit
# With the PROJECT_LOGO tag one can specify a logo or an icon that is included
# in the documentation. The maximum height of the logo should not exceed 55
@ -58,18 +68,30 @@ PROJECT_LOGO =
# entered, it will be relative to the location where doxygen was started. If
# left blank the current directory will be used.
OUTPUT_DIRECTORY =
OUTPUT_DIRECTORY = api
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-
# directories (in 2 levels) under the output directory of each output format and
# will distribute the generated files over these directories. Enabling this
# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096
# sub-directories (in 2 levels) under the output directory of each output format
# and will distribute the generated files over these directories. Enabling this
# option can be useful when feeding doxygen a huge amount of source files, where
# putting all generated files in the same directory would otherwise causes
# performance problems for the file system.
# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to
# control the number of sub-directories.
# The default value is: NO.
CREATE_SUBDIRS = NO
# Controls the number of sub-directories that will be created when
# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every
# level increment doubles the number of directories, resulting in 4096
# directories at level 8 which is the default and also the maximum value. The
# sub-directories are organized in 2 levels, the first level always has a fixed
# number of 16 directories.
# Minimum value: 0, maximum value: 8, default value: 8.
# This tag requires that the tag CREATE_SUBDIRS is set to YES.
CREATE_SUBDIRS_LEVEL = 8
# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII
# characters to appear in the names of generated files. If set to NO, non-ASCII
# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode
@ -81,14 +103,14 @@ ALLOW_UNICODE_NAMES = NO
# The OUTPUT_LANGUAGE tag is used to specify the language in which all
# documentation generated by doxygen is written. Doxygen will use this
# information to generate all constant output in the proper language.
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,
# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),
# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,
# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),
# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,
# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,
# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,
# Ukrainian and Vietnamese.
# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian,
# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English
# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek,
# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with
# English messages), Korean, Korean-en (Korean with English messages), Latvian,
# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese,
# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish,
# Swedish, Turkish, Ukrainian and Vietnamese.
# The default value is: English.
OUTPUT_LANGUAGE = English
@ -336,6 +358,17 @@ MARKDOWN_SUPPORT = YES
TOC_INCLUDE_HEADINGS = 5
# The MARKDOWN_ID_STYLE tag can be used to specify the algorithm used to
# generate identifiers for the Markdown headings. Note: Every identifier is
# unique.
# Possible values are: DOXYGEN use a fixed 'autotoc_md' string followed by a
# sequence number starting at 0 and GITHUB use the lower case version of title
# with any whitespace replaced by '-' and punctuation characters removed.
# The default value is: DOXYGEN.
# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.
MARKDOWN_ID_STYLE = DOXYGEN
# When enabled doxygen tries to link words that correspond to documented
# classes, or namespaces to their corresponding documentation. Such a link can
# be prevented in individual cases by putting a % sign in front of the word or
@ -447,7 +480,7 @@ TYPEDEF_HIDES_STRUCT = NO
LOOKUP_CACHE_SIZE = 0
# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use
# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use
# during processing. When set to 0 doxygen will based this on the number of
# cores available in the system. You can set it explicitly to a value larger
# than 0 to get more control over the balance between CPU load and processing
@ -460,6 +493,14 @@ LOOKUP_CACHE_SIZE = 0
NUM_PROC_THREADS = 1
# If the TIMESTAMP tag is set different from NO then each generated page will
# contain the date or date and time when the page was generated. Setting this to
# NO can help when comparing the output of multiple runs.
# Possible values are: YES, NO, DATETIME and DATE.
# The default value is: NO.
TIMESTAMP = NO
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
@ -541,7 +582,8 @@ HIDE_UNDOC_MEMBERS = NO
# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all
# undocumented classes that are normally visible in the class hierarchy. If set
# to NO, these classes will be included in the various overviews. This option
# has no effect if EXTRACT_ALL is enabled.
# will also hide undocumented C++ concepts if enabled. This option has no effect
# if EXTRACT_ALL is enabled.
# The default value is: NO.
HIDE_UNDOC_CLASSES = NO
@ -572,14 +614,15 @@ INTERNAL_DOCS = NO
# filesystem is case sensitive (i.e. it supports files in the same directory
# whose names only differ in casing), the option must be set to YES to properly
# deal with such files in case they appear in the input. For filesystems that
# are not case sensitive the option should be be set to NO to properly deal with
# are not case sensitive the option should be set to NO to properly deal with
# output files written for symbols that only differ in casing, such as for two
# classes, one named CLASS and the other named Class, and to also support
# references to files without having to specify the exact matching casing. On
# Windows (including Cygwin) and MacOS, users should typically set this option
# to NO, whereas on Linux or other Unix flavors it should typically be set to
# YES.
# The default value is: system dependent.
# Possible values are: SYSTEM, NO and YES.
# The default value is: SYSTEM.
CASE_SENSE_NAMES = YES
@ -831,11 +874,26 @@ WARN_IF_INCOMPLETE_DOC = YES
WARN_NO_PARAMDOC = NO
# If WARN_IF_UNDOC_ENUM_VAL option is set to YES, doxygen will warn about
# undocumented enumeration values. If set to NO, doxygen will accept
# undocumented enumeration values. If EXTRACT_ALL is set to YES then this flag
# will automatically be disabled.
# The default value is: NO.
WARN_IF_UNDOC_ENUM_VAL = NO
# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when
# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS
# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but
# at the end of the doxygen process doxygen will return with a non-zero status.
# Possible values are: NO, YES and FAIL_ON_WARNINGS.
# If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS_PRINT then doxygen behaves
# like FAIL_ON_WARNINGS but in case no WARN_LOGFILE is defined doxygen will not
# write the warning messages in between other messages but write them at the end
# of a run, in case a WARN_LOGFILE is defined the warning messages will be
# besides being in the defined file also be shown at the end of a run, unless
# the WARN_LOGFILE is defined as - i.e. standard output (stdout) in that case
# the behavior will remain as with the setting FAIL_ON_WARNINGS.
# Possible values are: NO, YES, FAIL_ON_WARNINGS and FAIL_ON_WARNINGS_PRINT.
# The default value is: NO.
WARN_AS_ERROR = NO
@ -846,10 +904,21 @@ WARN_AS_ERROR = NO
# and the warning text. Optionally the format may contain $version, which will
# be replaced by the version of the file (if it could be obtained via
# FILE_VERSION_FILTER)
# See also: WARN_LINE_FORMAT
# The default value is: $file:$line: $text.
WARN_FORMAT = "$file:$line: $text"
# In the $text part of the WARN_FORMAT command it is possible that a reference
# to a more specific place is given. To make it easier to jump to this place
# (outside of doxygen) the user can define a custom "cut" / "paste" string.
# Example:
# WARN_LINE_FORMAT = "'vi $file +$line'"
# See also: WARN_FORMAT
# The default value is: at line $line of file $file.
WARN_LINE_FORMAT = "at line $line of file $file"
# The WARN_LOGFILE tag can be used to specify a file to which warning and error
# messages should be written. If left blank the output is written to standard
# error (stderr). In case the file specified cannot be opened for writing the
@ -878,10 +947,21 @@ INPUT = . \
# libiconv (or the iconv built into libc) for the transcoding. See the libiconv
# documentation (see:
# https://www.gnu.org/software/libiconv/) for the list of possible encodings.
# See also: INPUT_FILE_ENCODING
# The default value is: UTF-8.
INPUT_ENCODING = UTF-8
# This tag can be used to specify the character encoding of the source files
# that doxygen parses The INPUT_FILE_ENCODING tag can be used to specify
# character encoding on a per file pattern basis. Doxygen will compare the file
# name with each pattern and apply the encoding instead of the default
# INPUT_ENCODING) if there is a match. The character encodings are a list of the
# form: pattern=encoding (like *.php=ISO-8859-1). See cfg_input_encoding
# "INPUT_ENCODING" for further information on supported encodings.
INPUT_FILE_ENCODING =
# If the value of the INPUT tag contains directories, you can use the
# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and
# *.h) to filter out the source-files in the directories.
@ -893,12 +973,12 @@ INPUT_ENCODING = UTF-8
# Note the list of default checked file patterns might differ from the list of
# default file extension mappings.
#
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,
# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,
# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,
# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C
# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,
# *.vhdl, *.ucf, *.qsf and *.ice.
# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cxxm,
# *.cpp, *.cppm, *.c++, *.c++m, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl,
# *.ddl, *.odl, *.h, *.hh, *.hxx, *.hpp, *.h++, *.ixx, *.l, *.cs, *.d, *.php,
# *.php4, *.php5, *.phtml, *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be
# provided as doxygen C comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08,
# *.f18, *.f, *.for, *.vhd, *.vhdl, *.ucf, *.qsf and *.ice.
FILE_PATTERNS =
@ -931,20 +1011,22 @@ EXCLUDE_SYMLINKS = NO
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories for example use the pattern */test/*
EXCLUDE_PATTERNS = *Private.* moc*.cpp *.moc
EXCLUDE_PATTERNS = *Private.* \
moc*.cpp \
*.moc
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
# output. The symbol name can be a fully qualified name, a word, or if the
# wildcard * is used, a substring. Examples: ANamespace, AClass,
# ANamespace::AClass, ANamespace::*Test
#
# Note that the wildcards are matched against the file with absolute path, so to
# exclude all test directories use the pattern */test/*
EXCLUDE_SYMBOLS = QskPainterCommand::PixmapData \
QskPainterCommand::ImageData \
QskPainterCommand::StateData
QskPainterCommand::StateData \
QHash \
QList \
std
# The EXAMPLE_PATH tag can be used to specify one or more files or directories
# that contain example code fragments that are included (see the \include
@ -987,6 +1069,11 @@ IMAGE_PATH = images
# code is scanned, but not when the output code is generated. If lines are added
# or removed, the anchors will not be placed correctly.
#
# Note that doxygen will use the data processed and written to standard output
# for further processing, therefore nothing else, like debug statements or used
# commands (so in case of a Windows batch file always use @echo OFF), should be
# written to standard output.
#
# Note that for custom extensions or not directly supported extensions you also
# need to set EXTENSION_MAPPING for the extension otherwise the files are not
# properly processed by doxygen.
@ -1028,6 +1115,15 @@ FILTER_SOURCE_PATTERNS =
USE_MDFILE_AS_MAINPAGE =
# The Fortran standard specifies that for fixed formatted Fortran code all
# characters from position 72 are to be considered as comment. A common
# extension is to allow longer lines before the automatic comment starts. The
# setting FORTRAN_COMMENT_AFTER will also make it possible that longer lines can
# be processed before the automatic comment starts.
# Minimum value: 7, maximum value: 10000, default value: 72.
FORTRAN_COMMENT_AFTER = 72
#---------------------------------------------------------------------------
# Configuration options related to source browsing
#---------------------------------------------------------------------------
@ -1114,6 +1210,46 @@ USE_HTAGS = NO
VERBATIM_HEADERS = YES
# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the
# clang parser (see:
# http://clang.llvm.org/) for more accurate parsing at the cost of reduced
# performance. This can be particularly helpful with template rich C++ code for
# which doxygen's built-in parser lacks the necessary type information.
# Note: The availability of this option depends on whether or not doxygen was
# generated with the -Duse_libclang=ON option for CMake.
# The default value is: NO.
CLANG_ASSISTED_PARSING = NO
# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS
# tag is set to YES then doxygen will add the directory of each input to the
# include path.
# The default value is: YES.
# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
CLANG_ADD_INC_PATHS = YES
# If clang assisted parsing is enabled you can provide the compiler with command
# line options that you would normally use when invoking the compiler. Note that
# the include paths will already be set by doxygen for the files and directories
# specified with INPUT and INCLUDE_PATH.
# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.
CLANG_OPTIONS =
# If clang assisted parsing is enabled you can provide the clang parser with the
# path to the directory containing a file called compile_commands.json. This
# file is the compilation database (see:
# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the
# options used when the source files were built. This is equivalent to
# specifying the -p option to a clang tool, such as clang-check. These options
# will then be passed to the parser. Any options specified with CLANG_OPTIONS
# will be added as well.
# Note: The availability of this option depends on whether or not doxygen was
# generated with the -Duse_libclang=ON option for CMake.
CLANG_DATABASE_PATH =
#---------------------------------------------------------------------------
# Configuration options related to the alphabetical class index
#---------------------------------------------------------------------------
@ -1125,10 +1261,11 @@ VERBATIM_HEADERS = YES
ALPHABETICAL_INDEX = YES
# In case all classes in a project start with a common prefix, all classes will
# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag
# can be used to specify a prefix (or a list of prefixes) that should be ignored
# while generating the index headers.
# The IGNORE_PREFIX tag can be used to specify a prefix (or a list of prefixes)
# that should be ignored while generating the index headers. The IGNORE_PREFIX
# tag works for classes, function and member names. The entity will be placed in
# the alphabetical list under the first letter of the entity name that remains
# after removing the prefix.
# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.
IGNORE_PREFIX = Qsk \
@ -1208,10 +1345,15 @@ HTML_STYLESHEET =
# Doxygen will copy the style sheet files to the output directory.
# Note: The order of the extra style sheet files is of importance (e.g. the last
# style sheet in the list overrules the setting of the previous ones in the
# list). For an example see the documentation.
# list).
# Note: Since the styling of scrollbars can currently not be overruled in
# Webkit/Chromium, the styling will be left out of the default doxygen.css if
# one or more extra stylesheets have been specified. So if scrollbar
# customization is desired it has to be added explicitly. For an example see the
# documentation.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_EXTRA_STYLESHEET =
HTML_EXTRA_STYLESHEET = ./customdoxygen.css
# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
# other source files which should be copied to the HTML output directory. Note
@ -1223,6 +1365,19 @@ HTML_EXTRA_STYLESHEET =
HTML_EXTRA_FILES =
# The HTML_COLORSTYLE tag can be used to specify if the generated HTML output
# should be rendered with a dark or light theme.
# Possible values are: LIGHT always generate light mode output, DARK always
# generate dark mode output, AUTO_LIGHT automatically set the mode according to
# the user preference, use light mode if no preference is set (the default),
# AUTO_DARK automatically set the mode according to the user preference, use
# dark mode if no preference is set and TOGGLE allow to user to switch between
# light and dark mode via a button.
# The default value is: AUTO_LIGHT.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE = AUTO_LIGHT
# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen
# will adjust the colors in the style sheet and background images according to
# this color. Hue is specified as an angle on a color-wheel, see
@ -1232,7 +1387,7 @@ HTML_EXTRA_FILES =
# Minimum value: 0, maximum value: 359, default value: 220.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE_HUE = 30
HTML_COLORSTYLE_HUE = 220
# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors
# in the HTML output. For a value of 0 the output will use gray-scales only. A
@ -1251,16 +1406,7 @@ HTML_COLORSTYLE_SAT = 100
# Minimum value: 40, maximum value: 240, default value: 80.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_COLORSTYLE_GAMMA = 130
# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
# page will contain the date and time when the page was generated. Setting this
# to YES can help to show when doxygen was last run and thus if the
# documentation is up to date.
# The default value is: NO.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_TIMESTAMP = NO
HTML_COLORSTYLE_GAMMA = 80
# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML
# documentation will contain a main index with vertical navigation menus that
@ -1281,6 +1427,13 @@ HTML_DYNAMIC_MENUS = YES
HTML_DYNAMIC_SECTIONS = NO
# If the HTML_CODE_FOLDING tag is set to YES then classes and functions can be
# dynamically folded and expanded in the generated HTML source code.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
HTML_CODE_FOLDING = YES
# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries
# shown in the various tree structured indices initially; the user can expand
# and collapse entries dynamically later on. Doxygen will expand the tree to
@ -1411,6 +1564,16 @@ BINARY_TOC = NO
TOC_EXPAND = NO
# The SITEMAP_URL tag is used to specify the full URL of the place where the
# generated documentation will be placed on the server by the user during the
# deployment of the documentation. The generated sitemap is called sitemap.xml
# and placed on the directory specified by HTML_OUTPUT. In case no SITEMAP_URL
# is specified no sitemap is generated. For information about the sitemap
# protocol see https://www.sitemaps.org
# This tag requires that the tag GENERATE_HTML is set to YES.
SITEMAP_URL =
# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that
# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help
@ -1586,17 +1749,6 @@ HTML_FORMULA_FORMAT = svg
FORMULA_FONTSIZE = 10
# Use the FORMULA_TRANSPARENT tag to determine whether or not the images
# generated for formulas are transparent PNGs. Transparent PNGs are not
# supported properly for IE 6.0, but are supported on all modern browsers.
#
# Note that when changing this option you need to delete any form_*.png files in
# the HTML output directory before the changes have effect.
# The default value is: YES.
# This tag requires that the tag GENERATE_HTML is set to YES.
FORMULA_TRANSPARENT = YES
# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands
# to create new LaTeX commands to be used in formulas as building blocks. See
# the section "Including formulas" for details.
@ -1910,9 +2062,16 @@ PDF_HYPERLINKS = YES
USE_PDFLATEX = YES
# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \batchmode
# command to the generated LaTeX files. This will instruct LaTeX to keep running
# if errors occur, instead of asking the user for help.
# The LATEX_BATCHMODE tag signals the behavior of LaTeX in case of an error.
# Possible values are: NO same as ERROR_STOP, YES same as BATCH, BATCH In batch
# mode nothing is printed on the terminal, errors are scrolled as if <return> is
# hit at every error; missing files that TeX tries to input or request from
# keyboard input (\read on a not open input stream) cause the job to abort,
# NON_STOP In nonstop mode the diagnostic message will appear on the terminal,
# but there is no possibility of user interaction just like in batch mode,
# SCROLL In scroll mode, TeX will stop only for missing files to input or if
# keyboard input is necessary and ERROR_STOP In errorstop mode, TeX will stop at
# each error, asking for user intervention.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
@ -1933,14 +2092,6 @@ LATEX_HIDE_INDICES = NO
LATEX_BIB_STYLE = plain
# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated
# page will contain the date and time when the page was generated. Setting this
# to NO can help when comparing the output of multiple runs.
# The default value is: NO.
# This tag requires that the tag GENERATE_LATEX is set to YES.
LATEX_TIMESTAMP = NO
# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)
# path from which the emoji images will be read. If a relative path is entered,
# it will be relative to the LATEX_OUTPUT directory. If left blank the
@ -2106,13 +2257,39 @@ DOCBOOK_OUTPUT = docbook
#---------------------------------------------------------------------------
# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an
# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures
# AutoGen Definitions (see https://autogen.sourceforge.net/) file that captures
# the structure of the code including all documentation. Note that this feature
# is still experimental and incomplete at the moment.
# The default value is: NO.
GENERATE_AUTOGEN_DEF = NO
#---------------------------------------------------------------------------
# Configuration options related to Sqlite3 output
#---------------------------------------------------------------------------
# If the GENERATE_SQLITE3 tag is set to YES doxygen will generate a Sqlite3
# database with symbols found by doxygen stored in tables.
# The default value is: NO.
GENERATE_SQLITE3 = NO
# The SQLITE3_OUTPUT tag is used to specify where the Sqlite3 database will be
# put. If a relative path is entered the value of OUTPUT_DIRECTORY will be put
# in front of it.
# The default directory is: sqlite3.
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
SQLITE3_OUTPUT = sqlite3
# The SQLITE3_OVERWRITE_DB tag is set to YES, the existing doxygen_sqlite3.db
# database file will be recreated with each doxygen run. If set to NO, doxygen
# will warn if an a database file is already found and not modify it.
# The default value is: YES.
# This tag requires that the tag GENERATE_SQLITE3 is set to YES.
SQLITE3_RECREATE_DB = YES
#---------------------------------------------------------------------------
# Configuration options related to the Perl module output
#---------------------------------------------------------------------------
@ -2187,7 +2364,8 @@ SEARCH_INCLUDES = YES
# The INCLUDE_PATH tag can be used to specify one or more directories that
# contain include files that are not input files but should be processed by the
# preprocessor.
# preprocessor. Note that the INCLUDE_PATH is not recursive, so the setting of
# RECURSIVE has no effect here.
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
INCLUDE_PATH =
@ -2255,15 +2433,15 @@ TAGFILES =
GENERATE_TAGFILE =
# If the ALLEXTERNALS tag is set to YES, all external class will be listed in
# the class index. If set to NO, only the inherited external classes will be
# listed.
# If the ALLEXTERNALS tag is set to YES, all external classes and namespaces
# will be listed in the class and namespace index. If set to NO, only the
# inherited external classes will be listed.
# The default value is: NO.
ALLEXTERNALS = NO
# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed
# in the modules index. If set to NO, only the current project's groups will be
# in the topic index. If set to NO, only the current project's groups will be
# listed.
# The default value is: YES.
@ -2277,16 +2455,9 @@ EXTERNAL_GROUPS = YES
EXTERNAL_PAGES = YES
#---------------------------------------------------------------------------
# Configuration options related to the dot tool
# Configuration options related to diagram generator tools
#---------------------------------------------------------------------------
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
# If left empty dia is assumed to be found in the default search path.
DIA_PATH =
# If set to YES the inheritance and collaboration graphs will hide inheritance
# and usage relations if the target is undocumented or is not a class.
# The default value is: YES.
@ -2295,10 +2466,10 @@ HIDE_UNDOC_RELATIONS = YES
# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is
# available from the path. This tool is part of Graphviz (see:
# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
# https://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent
# Bell Labs. The other options in this section have no effect if this option is
# set to NO
# The default value is: NO.
# The default value is: YES.
HAVE_DOT = YES
@ -2312,37 +2483,51 @@ HAVE_DOT = YES
DOT_NUM_THREADS = 0
# When you want a differently looking font in the dot files that doxygen
# generates you can specify the font name using DOT_FONTNAME. You need to make
# sure dot is able to find the font, which can be done by putting it in a
# standard location or by setting the DOTFONTPATH environment variable or by
# setting DOT_FONTPATH to the directory containing the font.
# The default value is: Helvetica.
# DOT_COMMON_ATTR is common attributes for nodes, edges and labels of
# subgraphs. When you want a differently looking font in the dot files that
# doxygen generates you can specify fontname, fontcolor and fontsize attributes.
# For details please see <a href=https://graphviz.org/doc/info/attrs.html>Node,
# Edge and Graph Attributes specification</a> You need to make sure dot is able
# to find the font, which can be done by putting it in a standard location or by
# setting the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
# directory containing the font. Default graphviz fontsize is 14.
# The default value is: fontname=Helvetica,fontsize=10.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTNAME = Helvetica
DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10"
# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of
# dot graphs.
# Minimum value: 4, maximum value: 24, default value: 10.
# DOT_EDGE_ATTR is concatenated with DOT_COMMON_ATTR. For elegant style you can
# add 'arrowhead=open, arrowtail=open, arrowsize=0.5'. <a
# href=https://graphviz.org/doc/info/arrows.html>Complete documentation about
# arrows shapes.</a>
# The default value is: labelfontname=Helvetica,labelfontsize=10.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTSIZE = 10
DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10"
# By default doxygen will tell dot to use the default font as specified with
# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set
# the path where dot can find it using this tag.
# DOT_NODE_ATTR is concatenated with DOT_COMMON_ATTR. For view without boxes
# around nodes set 'shape=plain' or 'shape=plaintext' <a
# href=https://www.graphviz.org/doc/info/shapes.html>Shapes specification</a>
# The default value is: shape=box,height=0.2,width=0.4.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4"
# You can set the path where dot can find font specified with fontname in
# DOT_COMMON_ATTR and others dot attributes.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_FONTPATH =
# If the CLASS_GRAPH tag is set to YES (or GRAPH) then doxygen will generate a
# graph for each documented class showing the direct and indirect inheritance
# relations. In case HAVE_DOT is set as well dot will be used to draw the graph,
# otherwise the built-in generator will be used. If the CLASS_GRAPH tag is set
# to TEXT the direct and indirect inheritance relations will be shown as texts /
# links.
# Possible values are: NO, YES, TEXT and GRAPH.
# If the CLASS_GRAPH tag is set to YES or GRAPH or BUILTIN then doxygen will
# generate a graph for each documented class showing the direct and indirect
# inheritance relations. In case the CLASS_GRAPH tag is set to YES or GRAPH and
# HAVE_DOT is enabled as well, then dot will be used to draw the graph. In case
# the CLASS_GRAPH tag is set to YES and HAVE_DOT is disabled or if the
# CLASS_GRAPH tag is set to BUILTIN, then the built-in generator will be used.
# If the CLASS_GRAPH tag is set to TEXT the direct and indirect inheritance
# relations will be shown as texts / links.
# Possible values are: NO, YES, TEXT, GRAPH and BUILTIN.
# The default value is: YES.
CLASS_GRAPH = YES
@ -2350,14 +2535,21 @@ CLASS_GRAPH = YES
# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a
# graph for each documented class showing the direct and indirect implementation
# dependencies (inheritance, containment, and class references variables) of the
# class with other documented classes.
# class with other documented classes. Explicit enabling a collaboration graph,
# when COLLABORATION_GRAPH is set to NO, can be accomplished by means of the
# command \collaborationgraph. Disabling a collaboration graph can be
# accomplished by means of the command \hidecollaborationgraph.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
COLLABORATION_GRAPH = NO
# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for
# groups, showing the direct groups dependencies.
# groups, showing the direct groups dependencies. Explicit enabling a group
# dependency graph, when GROUP_GRAPHS is set to NO, can be accomplished by means
# of the command \groupgraph. Disabling a directory graph can be accomplished by
# means of the command \hidegroupgraph. See also the chapter Grouping in the
# manual.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
@ -2417,7 +2609,9 @@ TEMPLATE_RELATIONS = NO
# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to
# YES then doxygen will generate a graph for each documented file showing the
# direct and indirect include dependencies of the file with other documented
# files.
# files. Explicit enabling an include graph, when INCLUDE_GRAPH is is set to NO,
# can be accomplished by means of the command \includegraph. Disabling an
# include graph can be accomplished by means of the command \hideincludegraph.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
@ -2426,7 +2620,10 @@ INCLUDE_GRAPH = YES
# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are
# set to YES then doxygen will generate a graph for each documented file showing
# the direct and indirect include dependencies of the file with other documented
# files.
# files. Explicit enabling an included by graph, when INCLUDED_BY_GRAPH is set
# to NO, can be accomplished by means of the command \includedbygraph. Disabling
# an included by graph can be accomplished by means of the command
# \hideincludedbygraph.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
@ -2466,7 +2663,10 @@ GRAPHICAL_HIERARCHY = YES
# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the
# dependencies a directory has on other directories in a graphical way. The
# dependency relations are determined by the #include relations between the
# files in the directories.
# files in the directories. Explicit enabling a directory graph, when
# DIRECTORY_GRAPH is set to NO, can be accomplished by means of the command
# \directorygraph. Disabling a directory graph can be accomplished by means of
# the command \hidedirectorygraph.
# The default value is: YES.
# This tag requires that the tag HAVE_DOT is set to YES.
@ -2482,12 +2682,13 @@ DIR_GRAPH_MAX_DEPTH = 1
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
# generated by dot. For an explanation of the image formats see the section
# output formats in the documentation of the dot tool (Graphviz (see:
# http://www.graphviz.org/)).
# https://www.graphviz.org/)).
# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order
# to make the SVG files visible in IE 9+ (other browsers do not have this
# requirement).
# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,
# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
# Possible values are: png, jpg, jpg:cairo, jpg:cairo:gd, jpg:gd, jpg:gd:gd,
# gif, gif:cairo, gif:cairo:gd, gif:gd, gif:gd:gd, svg, png:gd, png:gd:gd,
# png:cairo, png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and
# png:gdiplus:gdiplus.
# The default value is: png.
# This tag requires that the tag HAVE_DOT is set to YES.
@ -2519,11 +2720,12 @@ DOT_PATH =
DOTFILE_DIRS =
# The MSCFILE_DIRS tag can be used to specify one or more directories that
# contain msc files that are included in the documentation (see the \mscfile
# command).
# You can include diagrams made with dia in doxygen documentation. Doxygen will
# then run dia to produce the diagram and insert it in the documentation. The
# DIA_PATH tag allows you to specify the directory where the dia binary resides.
# If left empty dia is assumed to be found in the default search path.
MSCFILE_DIRS =
DIA_PATH =
# The DIAFILE_DIRS tag can be used to specify one or more directories that
# contain dia files that are included in the documentation (see the \diafile
@ -2573,18 +2775,6 @@ DOT_GRAPH_MAX_NODES = 20
MAX_DOT_GRAPH_DEPTH = 0
# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent
# background. This is disabled by default, because dot on Windows does not seem
# to support this out of the box.
#
# Warning: Depending on the platform used, enabling this option may lead to
# badly anti-aliased labels on the edges of a graph (i.e. they become hard to
# read).
# The default value is: NO.
# This tag requires that the tag HAVE_DOT is set to YES.
DOT_TRANSPARENT = YES
# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output
# files in one run (i.e. multiple -o and -T options on the command line). This
# makes dot run faster, but since only newer versions of dot (>1.8.10) support
@ -2612,3 +2802,19 @@ GENERATE_LEGEND = NO
# The default value is: YES.
DOT_CLEANUP = YES
# You can define message sequence charts within doxygen comments using the \msc
# command. If the MSCGEN_TOOL tag is left empty (the default), then doxygen will
# use a built-in version of mscgen tool to produce the charts. Alternatively,
# the MSCGEN_TOOL tag can also specify the name an external tool. For instance,
# specifying prog as the value, doxygen will call the tool as prog -T
# <outfile_format> -o <outputfile> <inputfile>. The external tool should support
# output file formats "png", "eps", "svg", and "ismap".
MSCGEN_TOOL =
# The MSCFILE_DIRS tag can be used to specify one or more directories that
# contain msc files that are included in the documentation (see the \mscfile
# command).
MSCFILE_DIRS =

182
doc/customdoxygen.css Normal file
View File

@ -0,0 +1,182 @@
/* Skia overrides for doxygen CSS. */
html {
--blue: rgb(0,114,178);
--green: rgb(0,158,115);
--red: rgb(213,94,0);
--orange: rgb(230,159,0);
--purple: rgb(204,121,167);
--brown: rgb(177,89,40);
--gray: rgb(79,79,79);
--light-blue: rgb(128,185,217);
--light-green: rgb(128,207,185);
--light-red: rgb(234,175,128);
--light-orange: rgb(243,207,128);
--light-purple: rgb(230,188,211);
--light-brown: rgb(216,172,148);
--light-gray: rgb(168,168,168);
--dark-blue: rgb(0,65,101);
--dark-red: rgb(156,44,8);
--white: rgb(254,254,254);
--dark-white: rgb(240,240,240);
--black: rgb(10,10,10);
}
#titlearea {
/* background matches Skia logo. */
background: rgb(248,248,248);
color: var(--blue);
}
#main-nav .sm {
background-image: none;
}
h2.groupheader {
border-bottom: var(--gray);
color: var(--dark-blue);
}
div.qindex, div.navtab{
background-color: var(--light-gray);
border: 1px solid var(--light-blue);
}
a {
color: var(--blue);
}
.contents a:visited {
color: var(--blue);
}
a.qindexHL {
background-color: var(--light-gray);
color: var(--white);
border: 1px double var(--gray);
}
.contents a.qindexHL:visited {
color: var(--white);
}
a.code, a.code:visited, a.line, a.line:visited {
color: var(--blue);
}
a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited {
color: var(--blue);
}
pre.fragment {
border: 1px solid var(--orange);
background-color: var(--dark-white);
}
div.fragment {
background-color: var(--dark-white);
border: 1px solid var(--orange);
}
span.lineno {
border-right: 2px solid var(--green);
background-color: var(-light-gray);
}
span.lineno a {
background-color: var(--light-gray);
}
span.lineno a:hover {
background-color: var(--light-gray);
color: var(--blue);
}
div.ah, span.ah {
background-color: var(--black);
color: var(--white);
border: solid thin var(--gray);
box-shadow: 2px 2px 3px var(light-gray);
background-image: none;
}
td.indexkey {
background-color: var(--light-gray);
border: 1px solid var(--orange);
}
td.indexvalue {
background-color: var(--light-gray);
border: 1px solid var(--orange);
}
tr.memlist {
background-color: var(--light-gray);
}
span.keyword {
color: var(--green);
}
span.keywordtype {
color: var(--brown);
}
span.keywordflow {
color: var(--brown);
}
span.comment {
color: var(--brown);
}
span.charliteral {
color: var(--green);
}
span.vhdldigit {
color: var(--purple);
}
span.vhdlchar {
color: var(--black);
}
blockquote {
background-color: var(--light-gray);
border-left: 2px solid var(--gray);
}
.memtitle {
background-image: none;
}
.memdoc, dl.reflist dd {
background-image: none;
}
.paramname {
color: var(--dark-red);
}
.tabsearch {
background-image: none;
}
.navpath ul {
background-image: none;
}
.navpath li {
background-image: none;
}
.navpath li.navelem a:hover {
color: var(--blue)
}
.navpath li.footer {
background-image:none;
}

View File

@ -4,66 +4,65 @@
You will need:
1. The `documentation-xml-website` branch of the Edelhirsch QSkinny repository
at https://github.com/edelhirsch/qskinny/tree/documentation-xml-website .
1. A recent version of doxygen; The currently used version is 1.9.1 built from
github sources. The `doxygen` binary needs to be in the $PATH.
1. A recent version of doxybook2 with some custom patches. The script
`generate-website.sh` should download and build the right version, however
the script might need some adaptation of paths.
For reference, the required version can be found at
https://github.com/edelhirsch/doxybook2/tree/jekyll .
1. A recent version of doxygen; The currently used version is 1.9.8.
The `doxygen` binary needs to be in the $PATH.
1. The `m4` command, which can be installed with `sudo apt install m4`.
1. A recent version of Jekyll (see https://jekyllrb.com/), which will generate
the static html pages. This and other required packages can be installed via
the static html pages. The currently used jekyll version is 4.2.2.
Also a recent version of bundler is required; this can be installed with:
```
gem install jekyll:3.9.0
gem install jekyll:4.2.2
gem install bundler:2.1.4
```
There might be some packages missing from the list above; in this case the
Gemfile in the qskinny-website repository might help.
1. Checkout the current website repository via
1. Checkout the repo to generate the website via
`git clone git@github.com:peter-ha/qskinny-website.git`
1. Checkout the live website repository via
`git clone git@github.com:qskinny/qskinny.github.io.git`
## Generating the website
Generating the static HTML sites is done with the `generate-website.sh` script
in the `qskinny/doc` directory. The script has some hardcoded paths and probably
needs some adaptation to run correctly.
### Generating the API documentation with doxygen
```
cd ~/dev/qskinny/doc
export PATH=.:$PATH
doxygen
```
This will generate the documentation into the `api` folder.
It will do the following:
### Testing and building the website locally
1. Generate HTML from doxygen. This step is needed because for some reason when
generating XML from doxygen there are no images with dependency graphs.
*Note*: This step is only executed if the `html` folder doesn't exist,
because otherwise it would take too long.
1. Generate XML from doxygen. The generated XML is used with doxybook2 in the
next step.
*Note*: This step is only executed if the `xml` folder doesn't exist,
because otherwise it would take too long.
1. Generate markdown from XML with doxybook2. This markdown will be used by
Jekyll to either server the website content locally or generate static
HTML from it, see below.
First copy the generated files from above to the website repo:
### Generating the website locally
```
cp -r api ~/dev/qskinny-website/docs/
```
When the command line switch `-local` is used with the `generate-website.sh`
script, it will generate the content to a local folder `doxybook-out`. This is
meant to be able to copy selected files to the website directory at
`~/dev/qskinny-website`.
Otherwise, the script will copy the content to the website repository for
uploading (again, paths are hardcoded as of now). So when generating content
for the first time, just run the script without any switches, which should
generate the website to `~/dev/qskinny-website`.
Then test the website locally:
### Testing the website locally
```
cd ~/dev/qskinny-website
bundle exec jekyll serve --livereload
```
After having generated the website as described above, go to
`~/dev/qskinny-website` and run `jekyll serve --livereload`. This should start
a browser at http://127.0.0.1:4000/, which will display the website.
Then direct your browser to `http://127.0.0.1:4000/`.
### Generating the website publicly
If all looks good, generate the static HTML website:
When the command line switch `-publish` is used, the script will automatically
generate a new version of the homepage and publish it at
https://qskinny.github.io . This wil only work with the proper user rights of
course.
```
bundle exec jekyll build
```
### Publishing the website
Just copy over the generated HTML files to the public website repo and push a
new version of the homepage:
```
cp -r _site/* ~/dev/qskinny.github.io/
cd ~/dev/qskinny.github.io/
git commit -a -m "new version" # you might want to add new files
gith push
```
That's it, the new website is now published at https://qskinny.github.io/ .

View File

@ -17,7 +17,7 @@ namespace
public:
enum Style
{
Continous,
Continuous,
Discrete,
Centered
};
@ -33,15 +33,15 @@ namespace
{
case Discrete:
{
setSnap( true );
setSnapping( true );
setStepSize( 5 );
setPageSteps( 4 );
break;
}
case Continous:
case Continuous:
{
setSnap( false );
setSnapping( false );
setStepSize( 1 );
setPageSteps( 10 );
@ -98,14 +98,20 @@ InputPage::InputPage( QQuickItem* parent )
{
Slider* continous;
Slider* discrete;
Slider* centered;
} sliders[2];
for ( int i = 0; i < 2; i++ )
{
const auto orientation = static_cast< Qt::Orientation >( i + 1 );
sliders[i].continous = new Slider( orientation, Slider::Continous );
sliders[i].continous = new Slider( orientation, Slider::Continuous );
sliders[i].discrete = new Slider( orientation, Slider::Discrete );
auto slider = new Slider( orientation, Slider::Continuous );
slider->setOrigin( slider->minimum()
+ 0.5 * ( slider->maximum() - slider->minimum() ) );
sliders[i].centered = slider;
}
auto spinBox = new QskSpinBox( 0.0, 100.0, 1.0 );
@ -120,6 +126,7 @@ InputPage::InputPage( QQuickItem* parent )
vBox->addItem( sliders[0].continous );
vBox->addItem( sliders[0].discrete );
vBox->addItem( sliders[0].centered );
vBox->addItem( inputBox );
vBox->addItem( spinBox );
@ -127,6 +134,7 @@ InputPage::InputPage( QQuickItem* parent )
mainBox->setSpacing( 30 );
mainBox->addItem( sliders[1].continous );
mainBox->addItem( sliders[1].discrete );
mainBox->addItem( sliders[1].centered );
mainBox->addItem( vBox );
auto inputs = findChildren< QskBoundedValueInput* >();

View File

@ -18,7 +18,7 @@ QSK_SUBCONTROL( LightDisplay, Tickmarks )
QSK_SUBCONTROL( LightDisplay, ValueText )
QSK_SUBCONTROL( LightDisplay, LeftLabel )
QSK_SUBCONTROL( LightDisplay, RightLabel )
QSK_SUBCONTROL( LightDisplay, Knob )
QSK_SUBCONTROL( LightDisplay, Handle )
QSK_STATE( LightDisplay, Pressed, ( QskAspect::FirstUserState << 1 ) )
@ -44,6 +44,8 @@ LightDisplay::LightDisplay( QQuickItem* parent )
setAlignmentHint( ValueText, Qt::AlignRight );
setBoundaries( 0, 100 );
setStepSize( 1.0 );
setPageSteps( 10 );
}
bool LightDisplay::isPressed() const
@ -53,16 +55,14 @@ bool LightDisplay::isPressed() const
void LightDisplay::mousePressEvent( QMouseEvent* event )
{
QRectF handleRect = subControlRect( LightDisplay::Knob );
const auto handleRect = subControlRect( LightDisplay::Handle );
if ( handleRect.contains( event->pos() ) )
{
setSkinStateFlag( Pressed );
return;
}
else
{
QskBoundedValueInput::mousePressEvent( event );
}
Inherited::mousePressEvent( event );
}
void LightDisplay::mouseMoveEvent( QMouseEvent* event )
@ -73,19 +73,17 @@ void LightDisplay::mouseMoveEvent( QMouseEvent* event )
const auto mousePos = qskMousePosition( event );
const auto rect = subControlRect( ColdAndWarmArc );
bool arcContainsMousePos = arcContainsPoint( rect, mousePos );
if( !arcContainsMousePos )
if( !arcContainsPoint( rect, mousePos ) )
{
setSkinStateFlag( Pressed, false );
return;
}
const auto metrics = arcMetricsHint( ColdAndWarmArc );
qreal angle = angleFromPoint( rect, mousePos );
const int tolerance = 20;
auto angle = angleFromPoint( rect, mousePos );
if( !angleInRange( metrics, angle ) )
{
// we're slightly outside the range, but don't want to give up
@ -101,7 +99,7 @@ void LightDisplay::mouseMoveEvent( QMouseEvent* event )
}
}
qreal ratio = ( metrics.spanAngle() - angle ) / metrics.spanAngle();
const auto ratio = ( metrics.spanAngle() - angle ) / metrics.spanAngle();
setValueAsRatio( ratio );
}
@ -110,14 +108,29 @@ void LightDisplay::mouseReleaseEvent( QMouseEvent* /*event*/ )
setSkinStateFlag( Pressed, false );
}
void LightDisplay::keyPressEvent( QKeyEvent* event )
{
switch( event->key() )
{
case Qt::Key_Left:
increment( -stepSize() );
return;
case Qt::Key_Right:
increment( stepSize() );
return;
}
Inherited::keyPressEvent( event );
}
qreal LightDisplay::angleFromPoint( const QRectF& rect, const QPointF& point ) const
{
QPointF circlePos( point.x() - rect.center().x(),
const QPointF circlePos( point.x() - rect.center().x(),
rect.center().y() - point.y() );
const qreal atan = qAtan2( circlePos.y(), circlePos.x() );
const qreal angle = qRadiansToDegrees( atan );
return angle;
const qreal angle = qAtan2( circlePos.y(), circlePos.x() );
return qRadiansToDegrees( angle );
}
bool LightDisplay::arcContainsPoint( const QRectF& rect, const QPointF& point ) const
@ -148,9 +161,7 @@ bool LightDisplay::arcContainsPoint( const QRectF& rect, const QPointF& point )
const bool pointOnArc = ( polarRadius + tolerance ) > radiusMin
&& ( polarRadius - tolerance ) < radiusMax;
bool ret = angleWithinRange && pointOnArc;
return ret;
return angleWithinRange && pointOnArc;
}
#include "moc_LightDisplay.cpp"

View File

@ -12,9 +12,12 @@ class LightDisplay : public QskBoundedValueInput
{
Q_OBJECT
using Inherited = QskBoundedValueInput;
public:
QSK_SUBCONTROLS( Panel, Groove, ColdAndWarmArc, Tickmarks, ValueText,
LeftLabel, RightLabel, Knob ) // ### rename knob to handle?
QSK_SUBCONTROLS( Panel, Groove, ColdAndWarmArc, Tickmarks,
ValueText, LeftLabel, RightLabel, Handle )
QSK_STATES( Pressed )
LightDisplay( QQuickItem* parent = nullptr );
@ -22,9 +25,11 @@ class LightDisplay : public QskBoundedValueInput
bool isPressed() const;
protected:
void mousePressEvent( QMouseEvent* e ) override;
void mouseMoveEvent( QMouseEvent* e ) override;
void mouseReleaseEvent( QMouseEvent* e ) override;
void mousePressEvent( QMouseEvent* ) override;
void mouseMoveEvent( QMouseEvent* ) override;
void mouseReleaseEvent( QMouseEvent* ) override;
void keyPressEvent( QKeyEvent* ) override;
private:
qreal angleFromPoint( const QRectF&, const QPointF& ) const;

View File

@ -105,12 +105,12 @@ QRectF LightDisplaySkinlet::subControlRect( const QskSkinnable* skinnable,
return rect;
}
else if( subControl == LightDisplay::Knob )
else if( subControl == LightDisplay::Handle )
{
const auto arcRect = subControlRect( skinnable,
contentsRect, LightDisplay::ColdAndWarmArc );
const auto arcMetrics = display->arcMetricsHint( LightDisplay::ColdAndWarmArc );
const auto knobSize = display->strutSizeHint( LightDisplay::Knob );
const auto knobSize = display->strutSizeHint( LightDisplay::Handle );
const qreal radius = ( arcRect.width() - arcMetrics.thickness() ) / 2;
const qreal angle = display->valueAsRatio() * 180;
@ -183,7 +183,7 @@ QSGNode* LightDisplaySkinlet::updateSubNode(
}
case KnobRole:
{
return updateBoxNode( skinnable, node, LightDisplay::Knob );
return updateBoxNode( skinnable, node, LightDisplay::Handle );
}
}

View File

@ -241,9 +241,9 @@ void Skin::initHints()
ed.setFontRole( LightDisplay::ValueText, QskFontRole::Headline );
ed.setColor( LightDisplay::ValueText, 0xff929cb2 );
ed.setStrutSize( LightDisplay::Knob, { 20, 20 } );
ed.setBoxBorderMetrics( LightDisplay::Knob, 1 );
ed.setBoxShape( LightDisplay::Knob, 100, Qt::RelativeSize );
ed.setStrutSize( LightDisplay::Handle, { 20, 20 } );
ed.setBoxBorderMetrics( LightDisplay::Handle, 1 );
ed.setBoxShape( LightDisplay::Handle, 100, Qt::RelativeSize );
// palette dependent skin hints:
ed.setGradient( MenuBar::Panel, palette.menuBar );
@ -265,9 +265,9 @@ void Skin::initHints()
ed.setShadowColor( UsageDiagramBox::Panel, palette.shadow );
ed.setGradient( LightDisplay::Panel, palette.box );
ed.setGradient( LightDisplay::Knob, palette.box );
ed.setGradient( LightDisplay::Handle, palette.box );
ed.setGradient( LightDisplay::ColdAndWarmArc, palette.lightDisplayColdAndWarmArc );
ed.setBoxBorderColors( LightDisplay::Knob, palette.lightDisplayKnobBorder );
ed.setBoxBorderColors( LightDisplay::Handle, palette.lightDisplayKnobBorder );
ed.setShadowMetrics( LightDisplay::Groove, { 0, 20 } );
ed.setShadowColor( LightDisplay::Groove, palette.shadow );
ed.setGradient( LightDisplay::Groove, palette.box );

View File

@ -16,6 +16,7 @@ QSK_SUBCONTROL( Dial, Needle )
Dial::Dial( QQuickItem* parent )
: QskBoundedValueInput( parent )
{
setReadOnly( true );
}
QVector< QString > Dial::tickLabels() const

View File

@ -11,7 +11,7 @@
#include <QskSkinlet.h>
#include <QskQuick.h>
#include <QskBoxClipNode.h>
#include <QskClipNode.h>
#include <QskBoxBorderMetrics.h>
#include <qsgnode.h>
@ -315,7 +315,7 @@ void QskPlotView::updateNode( QSGNode* node )
if ( m_data->needsClipping() )
{
if ( itemsNode == nullptr || itemsNode->type() != QSGNode::ClipNodeType )
itemsNode = new QskBoxClipNode();
itemsNode = new QskClipNode();
}
else
{

View File

@ -20,7 +20,7 @@ Slider::Slider( const QString& text, qreal min, qreal max,
m_slider = new QskSlider( this );
m_slider->setBoundaries( min, max );
m_slider->setStepSize( step );
m_slider->setSnap( true );
m_slider->setSnapping( true );
m_slider->setValue( value );
m_valueLabel = new QskTextLabel( this );

View File

@ -105,13 +105,13 @@ list(APPEND HEADERS
nodes/QskArcRenderNode.h
nodes/QskBasicLinesNode.h
nodes/QskBoxNode.h
nodes/QskBoxClipNode.h
nodes/QskBoxRectangleNode.h
nodes/QskBoxRenderer.h
nodes/QskBoxMetrics.h
nodes/QskBoxBasicStroker.h
nodes/QskBoxGradientStroker.h
nodes/QskBoxShadowNode.h
nodes/QskClipNode.h
nodes/QskColorRamp.h
nodes/QskFillNode.h
nodes/QskGraduationNode.h
@ -145,13 +145,13 @@ list(APPEND SOURCES
nodes/QskArcRenderNode.cpp
nodes/QskBasicLinesNode.cpp
nodes/QskBoxNode.cpp
nodes/QskBoxClipNode.cpp
nodes/QskBoxRectangleNode.cpp
nodes/QskBoxRenderer.cpp
nodes/QskBoxMetrics.cpp
nodes/QskBoxBasicStroker.cpp
nodes/QskBoxGradientStroker.cpp
nodes/QskBoxShadowNode.cpp
nodes/QskClipNode.cpp
nodes/QskColorRamp.cpp
nodes/QskFillNode.cpp
nodes/QskGraduationNode.cpp

View File

@ -13,6 +13,14 @@ namespace Qsk
{
Q_NAMESPACE_EXPORT( QSK_EXPORT )
enum Policy
{
Maybe,
Always,
Never
};
Q_ENUM_NS( Policy )
enum Direction
{
LeftToRight,

View File

@ -53,9 +53,9 @@ class QSK_EXPORT QskBoundedControl : public QskControl
void componentComplete() override;
private:
void adjustBoundaries( bool increasing );
private:
qreal m_minimum;
qreal m_maximum;
};

View File

@ -18,7 +18,7 @@ class QskBoundedInput::PrivateData
qreal stepSize = 0.1;
uint pageSteps = 1;
bool snap = false;
bool snapping = false;
};
QskBoundedInput::QskBoundedInput( QQuickItem* parent )
@ -53,7 +53,7 @@ void QskBoundedInput::setStepSize( qreal stepSize )
if ( isComponentComplete() )
{
if ( m_data->snap && stepSize )
if ( m_data->snapping && stepSize )
alignInput();
}
}
@ -102,21 +102,21 @@ void QskBoundedInput::pageDown()
increment( -pageSize() );
}
void QskBoundedInput::setSnap( bool snap )
void QskBoundedInput::setSnapping( bool on )
{
if ( m_data->snap == snap )
if ( m_data->snapping == on )
return;
m_data->snap = snap;
Q_EMIT snapChanged( snap );
m_data->snapping = on;
Q_EMIT snappingChanged( on );
if ( isComponentComplete() && snap )
if ( isComponentComplete() && m_data->snapping )
alignInput();
}
bool QskBoundedInput::snap() const
bool QskBoundedInput::isSnapping() const
{
return m_data->snap;
return m_data->snapping;
}
void QskBoundedInput::componentComplete()
@ -134,38 +134,6 @@ void QskBoundedInput::alignInput()
{
}
qreal QskBoundedInput::alignedValue( qreal value ) const
{
value = boundedValue( value );
if ( value > minimum() && value < maximum() )
{
if ( m_data->snap && m_data->stepSize )
{
value = qRound( value / m_data->stepSize ) * m_data->stepSize;
value = boundedValue( value );
}
}
return value;
}
QskIntervalF QskBoundedInput::alignedInterval( const QskIntervalF& interval ) const
{
if ( m_data->snap )
{
if ( const auto step = m_data->stepSize )
{
const qreal lower = std::floor( interval.lowerBound() / step ) * step;
const qreal upper = std::ceil( interval.upperBound() / step ) * step;
return QskIntervalF( lower, upper );
}
}
return interval;
}
void QskBoundedInput::setReadOnly( bool readOnly )
{
if ( readOnly == isReadOnly() )
@ -204,15 +172,6 @@ qreal QskBoundedInput::incrementForKey( const QKeyEvent* event ) const
case Qt::Key_PageDown:
return -pageSize();
default:
{
if ( qskIsStandardKeyInput( event, QKeySequence::MoveToNextChar ) )
return m_data->stepSize;
if ( qskIsStandardKeyInput( event, QKeySequence::MoveToPreviousChar ) )
return -m_data->stepSize;
}
}
return 0.0;

View File

@ -17,7 +17,7 @@ class QSK_EXPORT QskBoundedInput : public QskBoundedControl
Q_PROPERTY( qreal stepSize READ stepSize WRITE setStepSize NOTIFY stepSizeChanged )
Q_PROPERTY( uint pageSteps READ pageSteps WRITE setPageSteps NOTIFY pageStepsChanged )
Q_PROPERTY( bool snap READ snap WRITE setSnap NOTIFY snapChanged )
Q_PROPERTY( bool snapping READ isSnapping WRITE setSnapping NOTIFY snappingChanged )
Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly NOTIFY readOnlyChanged )
using Inherited = QskBoundedControl;
@ -30,10 +30,10 @@ class QSK_EXPORT QskBoundedInput : public QskBoundedControl
qreal stepSize() const;
qreal pageSize() const; // pageSteps() * stepSize()
uint pageSteps() const;
uint pageSteps() const;
void setSnap( bool );
bool snap() const;
void setSnapping( bool );
bool isSnapping() const;
void setReadOnly( bool );
bool isReadOnly() const;
@ -52,7 +52,7 @@ class QSK_EXPORT QskBoundedInput : public QskBoundedControl
Q_SIGNALS:
void stepSizeChanged( qreal );
void pageStepsChanged( qreal );
void snapChanged( bool );
void snappingChanged( bool );
void readOnlyChanged( bool );
@ -66,9 +66,6 @@ class QSK_EXPORT QskBoundedInput : public QskBoundedControl
void componentComplete() override;
virtual void alignInput();
qreal alignedValue( qreal ) const;
QskIntervalF alignedInterval( const QskIntervalF& ) const;
qreal incrementForKey( const QKeyEvent* ) const;
private:

View File

@ -88,7 +88,9 @@ void QskBoundedRangeInput::setRange( const QskIntervalF& range )
if ( isComponentComplete() )
{
newRange = alignedInterval( newRange );
if ( isSnapping() && stepSize() )
newRange = newRange.fuzzyAligned( stepSize() );
newRange = fixupRange( newRange );
}
@ -128,7 +130,11 @@ QskIntervalF QskBoundedRangeInput::range() const
void QskBoundedRangeInput::alignInput()
{
setRangeInternal( alignedInterval( m_range ) );
auto newRange = m_range;
if ( isSnapping() && stepSize() )
newRange = newRange.fuzzyAligned( stepSize() );
setRangeInternal( newRange );
}
QskIntervalF QskBoundedRangeInput::fixupRange( const QskIntervalF& range ) const

View File

@ -9,6 +9,24 @@
#include <qlocale.h>
#include <cfloat>
static qreal qskAlignedValue( const QskBoundedValueInput* input, qreal value )
{
value = input->boundedValue( value );
if ( value > input->minimum() && value < input->maximum() )
{
if ( input->isSnapping() && input->stepSize() )
{
const auto step = input->stepSize();
value = qRound( value / step ) * step;
value = input->boundedValue( value );
}
}
return value;
}
class QskBoundedValueInput::PrivateData
{
public:
@ -43,9 +61,25 @@ int QskBoundedValueInput::decimals() const
return m_data->decimals;
}
void QskBoundedValueInput::keyPressEvent( QKeyEvent* event )
{
switch( event->key() )
{
case Qt::Key_Home:
setValue( minimum() );
break;
case Qt::Key_End:
setValue( maximum() );
break;
}
Inherited::keyPressEvent( event );
}
void QskBoundedValueInput::alignInput()
{
auto value = alignedValue( m_data->value );
auto value = qskAlignedValue( this, m_data->value );
value = fixupValue( value );
setValueInternal( value );
@ -71,7 +105,7 @@ void QskBoundedValueInput::setValue( qreal value )
{
if ( isComponentComplete() )
{
value = alignedValue( value );
value = qskAlignedValue( this, value );
value = fixupValue( value );
}

View File

@ -51,6 +51,8 @@ class QSK_EXPORT QskBoundedValueInput : public QskBoundedInput
void decimalsChanged( int );
protected:
void keyPressEvent( QKeyEvent* ) override;
virtual qreal fixupValue( qreal ) const;
void alignInput() override;

View File

@ -59,7 +59,7 @@ class QskPageIndicator::PrivateData
int pressedIndex = -1;
int count;
Qt::Orientation orientation : 2;
Qt::Orientation orientation;
};
QskPageIndicator::QskPageIndicator( int count, QQuickItem* parent )

View File

@ -13,44 +13,76 @@
using Q = QskProgressBar;
namespace
static QskIntervalF qskFillInterval( const QskProgressIndicator* indicator )
{
QskIntervalF qskFillInterval( const QskProgressIndicator* indicator )
qreal pos1, pos2;
if ( indicator->isIndeterminate() )
{
qreal pos1, pos2;
const auto pos = indicator->positionHint( Q::Fill );
if ( indicator->isIndeterminate() )
{
const auto pos = indicator->positionHint( QskProgressIndicator::Fill );
static const QEasingCurve curve( QEasingCurve::InOutCubic );
static const QEasingCurve curve( QEasingCurve::InOutCubic );
const qreal off = 0.15;
const qreal off = 0.15;
pos1 = curve.valueForProgress( qMax( pos - off, 0.0 ) );
pos2 = curve.valueForProgress( qMin( pos + off, 1.0 ) );
}
else
{
pos1 = indicator->valueAsRatio( indicator->origin() );
pos2 = indicator->valueAsRatio( indicator->value() );
}
auto bar = static_cast< const QskProgressBar* >( indicator );
if( bar->orientation() == Qt::Horizontal )
{
if ( bar->layoutMirroring() )
{
pos1 = 1.0 - pos1;
pos2 = 1.0 - pos2;
}
}
if ( pos1 > pos2 )
std::swap( pos1, pos2 );
return QskIntervalF( pos1, pos2 );
pos1 = curve.valueForProgress( qMax( pos - off, 0.0 ) );
pos2 = curve.valueForProgress( qMin( pos + off, 1.0 ) );
}
else
{
pos1 = indicator->valueAsRatio( indicator->origin() );
pos2 = indicator->valueAsRatio( indicator->value() );
}
auto bar = static_cast< const QskProgressBar* >( indicator );
if( bar->orientation() == Qt::Horizontal )
{
if ( bar->layoutMirroring() )
{
pos1 = 1.0 - pos1;
pos2 = 1.0 - pos2;
}
}
if ( pos1 > pos2 )
std::swap( pos1, pos2 );
return QskIntervalF( pos1, pos2 );
}
static QskGradient qskFillGradient( const QskProgressBar* progressBar )
{
auto gradient = progressBar->gradientHint( Q::Fill );
if ( gradient.isVisible() && !gradient.isMonochrome()
&& ( gradient.type() == QskGradient::Stops ) )
{
/*
When having stops only we use a linear gradient,
where the colors are increasing in direction of the
progress value. We interprete the gradient as a
definition for the 100% situation and have to adjust
the stops for smaller bars.
For this situation it would be more convenient to
adjust the start/stop positions, but the box renderer is
not supporting this yet. TODO ...
*/
const auto intv = qskFillInterval( progressBar );
const auto stops = qskExtractedGradientStops(
gradient.stops(), intv.lowerBound(), intv.upperBound() );
gradient.setStops( stops );
gradient.setLinearDirection( progressBar->orientation() );
if ( progressBar->orientation() == Qt::Vertical || progressBar->layoutMirroring() )
gradient.reverse();
}
return gradient;
}
QskProgressBarSkinlet::QskProgressBarSkinlet( QskSkin* skin )
@ -69,28 +101,10 @@ QRectF QskProgressBarSkinlet::subControlRect(
const auto bar = static_cast< const Q* >( skinnable );
if( subControl == Q::Groove )
{
const auto grooveSize = bar->metric( Q::Groove | QskAspect::Size );
auto rect = contentsRect;
if ( bar->orientation() == Qt::Horizontal )
{
rect.setY( rect.y() + 0.5 * ( rect.height() - grooveSize ) );
rect.setHeight( grooveSize );
}
else
{
rect.setX( rect.x() + 0.5 * ( rect.width() - grooveSize ) );
rect.setWidth( grooveSize );
}
return rect;
}
return grooveRect( bar, contentsRect );
if( subControl == Q::Fill )
{
return barRect( bar );
}
return fillRect( bar );
return Inherited::subControlRect( skinnable, contentsRect, subControl );
}
@ -104,76 +118,61 @@ QSGNode* QskProgressBarSkinlet::updateGrooveNode(
QSGNode* QskProgressBarSkinlet::updateFillNode(
const QskProgressIndicator* indicator, QSGNode* node ) const
{
const auto bar = static_cast< const Q* >( indicator );
const auto subControl = Q::Fill;
const auto rect = indicator->subControlRect( subControl );
const auto rect = indicator->subControlRect( Q::Fill );
if ( rect.isEmpty() )
return nullptr;
auto gradient = indicator->gradientHint( subControl );
if ( !gradient.isVisible() )
return nullptr;
if ( ( gradient.type() == QskGradient::Stops ) && !gradient.isMonochrome() )
{
/*
When having stops only we use a linear gradient,
where the colors are increasing in direction of the
progress value. We interprete the gradient as a
definition for the 100% situation and have to adjust
the stops for smaller bars.
For this situation it would be more convenient to
adjust the start/stop positions, but the box renderer is
not supporting this yet. TODO ...
*/
const auto intv = qskFillInterval( bar );
const auto stops = qskExtractedGradientStops( gradient.stops(),
intv.lowerBound(), intv.upperBound() );
gradient.setStops( stops );
gradient.setLinearDirection( static_cast< Qt::Orientation >( bar->orientation() ) );
if ( bar->orientation() == Qt::Vertical || bar->layoutMirroring() )
gradient.reverse();
}
return updateBoxNode( indicator, node, rect, gradient, subControl );
const auto progressBar = static_cast< const Q* >( indicator );
return updateBoxNode( indicator, node, rect,
qskFillGradient( progressBar ), Q::Fill );
}
QRectF QskProgressBarSkinlet::barRect( const Q* bar ) const
QRectF QskProgressBarSkinlet::grooveRect(
const QskProgressBar* progressBar, const QRectF& contentsRect ) const
{
const auto subControl = Q::Groove;
const auto size = progressBar->metric( Q::Groove | QskAspect::Size );
const auto barSize = bar->metric( Q::Fill | QskAspect::Size );
auto rect = bar->subControlRect( subControl );
if ( bar->orientation() == Qt::Horizontal )
auto rect = contentsRect;
if ( progressBar->orientation() == Qt::Horizontal )
{
rect.setY( rect.y() + 0.5 * ( rect.height() - barSize ) );
rect.setHeight( barSize );
rect.setY( rect.y() + 0.5 * ( rect.height() - size ) );
rect.setHeight( size );
}
else
{
rect.setX( rect.x() + 0.5 * ( rect.width() - barSize ) );
rect.setWidth( barSize );
rect.setX( rect.x() + 0.5 * ( rect.width() - size ) );
rect.setWidth( size );
}
const auto borderMetrics = bar->boxBorderMetricsHint( subControl );
return rect;
}
auto m = bar->paddingHint( subControl );
QRectF QskProgressBarSkinlet::fillRect( const QskProgressBar* progressBar ) const
{
const auto size = progressBar->metric( Q::Fill | QskAspect::Size );
auto rect = progressBar->subControlRect( Q::Groove );
if ( progressBar->orientation() == Qt::Horizontal )
{
rect.setY( rect.y() + 0.5 * ( rect.height() - size ) );
rect.setHeight( size );
}
else
{
rect.setX( rect.x() + 0.5 * ( rect.width() - size ) );
rect.setWidth( size );
}
const auto borderMetrics = progressBar->boxBorderMetricsHint( Q::Groove );
auto m = progressBar->paddingHint( Q::Groove );
m += 0.5 * borderMetrics.toAbsolute( rect.size() ).widths();
rect = rect.marginsRemoved( m );
const auto intv = qskFillInterval( bar );
const auto intv = qskFillInterval( progressBar );
if( bar->orientation() == Qt::Horizontal )
if( progressBar->orientation() == Qt::Horizontal )
{
const auto w = rect.width();
@ -197,11 +196,12 @@ QSizeF QskProgressBarSkinlet::sizeHint( const QskSkinnable* skinnable,
if ( which != Qt::PreferredSize )
return QSizeF();
const auto bar = static_cast< const Q* >( skinnable );
auto extent = skinnable->metric( Q::Groove | QskAspect::Size );
extent = qMax( extent, skinnable->metric( Q::Fill | QskAspect::Size ) );
const auto extent = bar->extent();
const auto progressBar = static_cast< const QskProgressBar* >( skinnable );
if ( bar->orientation() == Qt::Horizontal )
if ( progressBar->orientation() == Qt::Horizontal )
return QSizeF( -1, extent );
else
return QSizeF( extent, -1 );

View File

@ -31,7 +31,8 @@ class QSK_EXPORT QskProgressBarSkinlet : public QskProgressIndicatorSkinlet
QSGNode* updateFillNode( const QskProgressIndicator*, QSGNode* ) const override;
private:
QRectF barRect( const QskProgressBar* ) const;
QRectF fillRect( const QskProgressBar* ) const;
QRectF grooveRect( const QskProgressBar*, const QRectF& ) const;
};
#endif

View File

@ -148,9 +148,7 @@ void QskProgressIndicator::resetExtent()
qreal QskProgressIndicator::extent() const
{
auto grooveSize = metric( Groove | QskAspect::Size );
auto fillSize = metric( Fill | QskAspect::Size );
return qMax( grooveSize, fillSize );
return metric( Groove | QskAspect::Size );
}
void QskProgressIndicator::setOrigin( qreal origin )
@ -189,6 +187,11 @@ qreal QskProgressIndicator::origin() const
return minimum();
}
bool QskProgressIndicator::hasOrigin() const
{
return m_data->hasOrigin;
}
void QskProgressIndicator::setValue( qreal value )
{
if ( isComponentComplete() )

View File

@ -51,6 +51,7 @@ class QSK_EXPORT QskProgressIndicator : public QskBoundedControl
void resetOrigin();
qreal origin() const;
bool hasOrigin() const;
qreal value() const;
qreal valueAsRatio() const; // [0.0, 1.0]
@ -73,7 +74,6 @@ class QSK_EXPORT QskProgressIndicator : public QskBoundedControl
private:
void setValueInternal( qreal value );
void adjustBoundaries( bool increasing );
void adjustValue();
class PrivateData;

View File

@ -10,6 +10,36 @@
using Q = QskProgressRing;
static QskIntervalF qskFillInterval( const QskProgressIndicator* indicator )
{
qreal pos1, pos2;
if ( indicator->isIndeterminate() )
{
pos1 = indicator->positionHint( QskProgressIndicator::Fill );
pos2 = 2 * pos1;
}
else
{
pos1 = indicator->valueAsRatio( indicator->origin() );
pos2 = indicator->valueAsRatio( indicator->value() );
}
if ( pos1 > pos2 )
std::swap( pos1, pos2 );
return QskIntervalF( pos1, pos2 );
}
static inline QPair< qreal, qreal > qskFillAngles(
const QskArcMetrics& metrics, const QskIntervalF& intv )
{
const auto startAngle = metrics.startAngle() + intv.lowerBound() * metrics.spanAngle();
const auto endAngle = metrics.startAngle() + intv.upperBound() * metrics.spanAngle();
return { startAngle, endAngle };
}
QskProgressRingSkinlet::QskProgressRingSkinlet( QskSkin* skin )
: Inherited( skin )
{
@ -32,6 +62,34 @@ QRectF QskProgressRingSkinlet::subControlRect(
QSGNode* QskProgressRingSkinlet::updateGrooveNode(
const QskProgressIndicator* indicator, QSGNode* node ) const
{
const auto ring = static_cast< const Q* >( indicator );
const auto spacing = ring->spacingHint( Q::Fill ); // degrees
if( spacing > 0.0 )
{
const auto fillMetrics = ring->arcMetricsHint( Q::Fill );
if ( fillMetrics.isClosed() )
{
const auto fillAngles = qskFillAngles( fillMetrics, qskFillInterval( ring ) );
qreal startAngle, endAngle;
if ( fillAngles.second > fillAngles.first )
{
startAngle = fillAngles.second + spacing;
endAngle = fillAngles.first + 360.0 - spacing;
}
else
{
startAngle = fillAngles.second - spacing;
endAngle = fillAngles.first - 360.0 + spacing;
}
return updateArcNode( ring, node,
startAngle, endAngle - startAngle, Q::Groove );
}
}
return updateArcNode( indicator, node, Q::Groove );
}
@ -54,7 +112,7 @@ QSGNode* QskProgressRingSkinlet::updateFillNode(
if ( !gradient.isVisible() )
return nullptr;
const auto intv = fillInterval( ring );
const auto intv = qskFillInterval( ring );
if ( ( gradient.type() == QskGradient::Stops ) && !gradient.isMonochrome() )
{
@ -67,10 +125,10 @@ QSGNode* QskProgressRingSkinlet::updateFillNode(
gradient.reverse();
}
const auto startAngle = metrics.startAngle() + intv.lowerBound() * metrics.spanAngle();
const auto spanAngle = intv.upperBound() * metrics.spanAngle();
const auto angles = qskFillAngles( metrics, intv );
return updateArcNode( ring, node, rect, gradient, startAngle, spanAngle, subControl );
return updateArcNode( ring, node, rect, gradient,
angles.first, angles.second - angles.first, subControl );
}
QSizeF QskProgressRingSkinlet::sizeHint( const QskSkinnable* skinnable,
@ -88,32 +146,11 @@ QSizeF QskProgressRingSkinlet::sizeHint( const QskSkinnable* skinnable,
if ( constraint.width() >= 0.0 )
hint.setHeight( constraint.width() / aspectRatio );
else
else
hint.setWidth( constraint.height() * aspectRatio );
}
return hint;
}
QskIntervalF QskProgressRingSkinlet::fillInterval(
const QskProgressIndicator* indicator ) const
{
qreal pos1, pos2;
if ( indicator->isIndeterminate() )
{
pos1 = pos2 = indicator->positionHint( QskProgressIndicator::Fill );
}
else
{
pos1 = indicator->valueAsRatio( indicator->origin() );
pos2 = indicator->valueAsRatio( indicator->value() );
}
if ( pos1 > pos2 )
std::swap( pos1, pos2 );
return QskIntervalF( pos1, pos2 );
}
#include "moc_QskProgressRingSkinlet.cpp"

View File

@ -29,8 +29,6 @@ class QSK_EXPORT QskProgressRingSkinlet : public QskProgressIndicatorSkinlet
protected:
QSGNode* updateGrooveNode( const QskProgressIndicator*, QSGNode* ) const override;
QSGNode* updateFillNode( const QskProgressIndicator*, QSGNode* ) const override;
QskIntervalF fillInterval( const QskProgressIndicator* ) const;
};
#endif

View File

@ -11,10 +11,10 @@
#include "QskBoxBorderColors.h"
#include "QskBoxBorderMetrics.h"
#include "QskBoxNode.h"
#include "QskBoxClipNode.h"
#include "QskBoxRectangleNode.h"
#include "QskBoxShapeMetrics.h"
#include "QskBoxHints.h"
#include "QskClipNode.h"
#include "QskColorFilter.h"
#include "QskControl.h"
#include "QskFunctions.h"
@ -641,15 +641,14 @@ QSGNode* QskSkinlet::updateBoxClipNode( const QskSkinnable* skinnable,
QSGNode* QskSkinlet::updateBoxClipNode( const QskSkinnable* skinnable,
QSGNode* node, const QRectF& rect, QskAspect::Subcontrol subControl )
{
auto clipNode = QskSGNode::ensureNode< QskBoxClipNode >( node );
auto clipNode = QskSGNode::ensureNode< QskClipNode >( node );
const auto margins = skinnable->marginHint( subControl );
const auto clipRect = rect.marginsRemoved( margins );
if ( clipRect.isEmpty() )
{
clipNode->setIsRectangular( true );
clipNode->setClipRect( clipRect );
clipNode->setRect( clipRect );
}
else
{

View File

@ -437,6 +437,14 @@ qreal QskSkinnable::metric( const QskAspect aspect, QskSkinHintStatus* status )
return qskMetric< qreal >( this, aspect, status );
}
qreal QskSkinnable::metric( QskAspect aspect, qreal defaultValue ) const
{
QskSkinHintStatus status;
const auto value = qskMetric< qreal >( this, aspect, &status );
return status.isValid() ? value : defaultValue;
}
bool QskSkinnable::setPositionHint( QskAspect aspect, qreal position )
{
return qskSetMetric( this, aspect | QskAspect::Position, position );

View File

@ -175,6 +175,7 @@ class QSK_EXPORT QskSkinnable
bool moveMetric( QskAspect, qreal );
bool resetMetric( QskAspect );
qreal metric( QskAspect, QskSkinHintStatus* = nullptr ) const;
qreal metric( QskAspect, qreal defaultValue ) const;
bool setFlagHint( QskAspect, int flag );
template< typename T > T flagHint( QskAspect, T = T() ) const;

View File

@ -6,35 +6,96 @@
#include "QskSlider.h"
#include "QskAnimationHint.h"
#include "QskAspect.h"
#include "QskIntervalF.h"
#include "QskEvent.h"
#include "QskFunctions.h"
QSK_SUBCONTROL( QskSlider, Panel )
QSK_SUBCONTROL( QskSlider, Groove )
QSK_SUBCONTROL( QskSlider, Fill )
QSK_SUBCONTROL( QskSlider, Scale )
QSK_SUBCONTROL( QskSlider, Tick )
QSK_SUBCONTROL( QskSlider, Handle )
QSK_SUBCONTROL( QskSlider, GrooveStopIndicators )
QSK_SUBCONTROL( QskSlider, FillStopIndicators )
QSK_SUBCONTROL( QskSlider, LabelContainer )
QSK_SUBCONTROL( QskSlider, LabelText )
QSK_SYSTEM_STATE( QskSlider, Pressed, QskAspect::FirstSystemState << 2 )
static QRectF qskHandleSelectionRect( const QskSlider* slider )
{
return slider->subControlRect( QskSlider::Handle );
}
static QRectF qskSliderSelectionRect( const QskSlider* slider )
{
const qreal margin = 10.0;
const auto scaleRect = slider->subControlRect( QskSlider::Scale );
const auto handleRect = qskHandleSelectionRect( slider );
auto r = slider->subControlRect( QskSlider::Panel );
if ( slider->orientation() == Qt::Horizontal )
{
r.setTop( qMin( r.top(), handleRect.top() ) );
r.setBottom( qMax( r.bottom(), handleRect.bottom() ) );
r.setLeft( scaleRect.left() - margin );
r.setRight( scaleRect.right() + margin );
}
else
{
r.setLeft( qMin( r.left(), handleRect.left() ) );
r.setRight( qMax( r.right(), handleRect.right() ) );
r.setTop( scaleRect.top() - margin );
r.setBottom( scaleRect.bottom() + margin );
}
return r;
}
static inline int qskKeyOffset( Qt::Orientation orientation, int key )
{
if ( orientation == Qt::Horizontal )
{
if ( key == Qt::Key_Left )
return -1;
if ( key == Qt::Key_Right )
return 1;
}
else
{
if ( key == Qt::Key_Down )
return -1;
if ( key == Qt::Key_Up )
return 1;
}
return 0;
}
class QskSlider::PrivateData
{
public:
PrivateData( Qt::Orientation orientation )
: pressedValue( 0 )
, hasOrigin( false )
, inverted( false )
, tracking( true )
, dragging( false )
, orientation( orientation )
{
}
QPointF pressedPos;
qreal pressedValue;
qreal origin = 0.0;
bool hasOrigin : 1;
bool inverted : 1;
bool tracking : 1;
Qt::Orientation orientation : 2;
bool dragging : 1;
uint orientation : 2;
};
QskSlider::QskSlider( QQuickItem* parent )
@ -62,11 +123,6 @@ QskSlider::~QskSlider()
{
}
bool QskSlider::isPressed() const
{
return hasSkinState( Pressed );
}
void QskSlider::setOrientation( Qt::Orientation orientation )
{
if ( orientation != m_data->orientation )
@ -79,13 +135,68 @@ void QskSlider::setOrientation( Qt::Orientation orientation )
resetImplicitSize();
update();
Q_EMIT orientationChanged( m_data->orientation );
Q_EMIT orientationChanged( this->orientation() );
}
}
Qt::Orientation QskSlider::orientation() const
{
return m_data->orientation;
return static_cast< Qt::Orientation >( m_data->orientation );
}
void QskSlider::setInverted( bool on )
{
if ( on != m_data->inverted )
{
m_data->inverted = on;
update();
Q_EMIT invertedChanged( on );
}
}
bool QskSlider::isInverted() const
{
return m_data->inverted;
}
void QskSlider::setOrigin( qreal origin )
{
if ( isComponentComplete() )
origin = boundedValue( origin );
if( !m_data->hasOrigin || !qskFuzzyCompare( m_data->origin, origin ) )
{
m_data->hasOrigin = true;
m_data->origin = origin;
update();
Q_EMIT originChanged( origin );
}
}
void QskSlider::resetOrigin()
{
if ( m_data->hasOrigin )
{
m_data->hasOrigin = false;
update();
Q_EMIT originChanged( origin() );
}
}
qreal QskSlider::origin() const
{
if ( m_data->hasOrigin )
return boundedValue( m_data->origin );
return minimum();
}
bool QskSlider::hasOrigin() const
{
return m_data->hasOrigin;
}
QskAspect::Variation QskSlider::effectiveVariation() const
@ -107,137 +218,139 @@ bool QskSlider::isTracking() const
return m_data->tracking;
}
void QskSlider::componentComplete()
{
Inherited::componentComplete();
if ( m_data->hasOrigin )
m_data->origin = boundedValue( m_data->origin );
}
void QskSlider::aboutToShow()
{
setPositionHint( Handle, valueAsRatio() );
Inherited::aboutToShow();
}
QSizeF QskSlider::handleSize() const
{
return handleRect().size();
}
QRectF QskSlider::handleRect() const
{
auto rect = subControlRect( Handle );
#if 1 // minimum handle strut size hardcoded here for now
const QSizeF strutSize( 60, 60 );
const auto w = qMax( ( strutSize.width() - rect.width() ) / 2, 0.0 );
const auto h = qMax( ( strutSize.height() - rect.height() ) / 2, 0.0 );
#endif
return rect.marginsAdded( { w, h, w, h } );
}
void QskSlider::mousePressEvent( QMouseEvent* event )
{
if ( handleRect().contains( event->pos() ) )
const auto pos = qskMousePosition( event );
if ( !qskHandleSelectionRect( this ).contains( pos ) )
{
// Case 1: press started in the handle, start sliding
const auto r = qskSliderSelectionRect( this );
if ( !r.contains( pos ) )
{
Inherited::mousePressEvent( event );
return;
}
m_data->pressedPos = event->pos();
m_data->pressedValue = value();
setSkinStateFlag( Pressed );
Q_EMIT pressedChanged( true );
}
else if ( pageSteps() == 0 )
{
// Case 2: pageSize is not used, we're done here
}
else
{
// Case 3: pressed outside of the handle, page the scroller in
// the direction of the press requires an auto-repeat behavior
// until the slider reaches the destination, or it simply jumps
// there (configurable)
qreal ratio;
const auto scaleRect = subControlRect( Scale );
if ( m_data->orientation == Qt::Horizontal )
ratio = ( pos.x() - scaleRect.left() ) / scaleRect.width();
else
ratio = ( scaleRect.bottom() - pos.y() ) / scaleRect.height();
if ( m_data->inverted )
ratio = 1.0 - ratio;
setValue( valueFromRatio( ratio ) );
}
setSkinStateFlag( Pressed );
m_data->pressedPos = pos;
m_data->pressedValue = value();
}
void QskSlider::mouseMoveEvent( QMouseEvent* event )
{
if ( !isPressed() )
if ( !hasSkinState( Pressed ) )
return;
const auto mousePos = qskMousePosition( event );
const auto r = subControlRect( Scale );
auto length = boundaryLength();
if ( m_data->inverted )
length = -length;
qreal newValue;
if ( m_data->orientation == Qt::Horizontal )
{
const auto distance = mousePos.x() - m_data->pressedPos.x();
newValue = m_data->pressedValue + distance / r.width() * boundaryLength();
newValue = m_data->pressedValue + distance / r.width() * length;
}
else
{
const auto distance = mousePos.y() - m_data->pressedPos.y();
newValue = m_data->pressedValue - distance / r.height() * boundaryLength();
newValue = m_data->pressedValue - distance / r.height() * length;
}
if ( m_data->tracking )
{
m_data->dragging = true;
setValue( newValue );
m_data->dragging = false;
}
else
{
// moving the handle without changing the value
moveHandleTo( newValue, QskAnimationHint() );
}
}
void QskSlider::mouseReleaseEvent( QMouseEvent* event )
void QskSlider::mouseReleaseEvent( QMouseEvent* )
{
if ( !isPressed() ) // Page event
{
const auto mousePos = qskMousePosition( event );
const auto szHandle = handleSize();
const auto rect = contentsRect();
bool up;
if ( m_data->orientation == Qt::Horizontal )
{
const qreal w = szHandle.width();
const qreal x = ( mousePos.x() - rect.x() - w * 0.5 ) / ( rect.width() - w );
up = x > valueAsRatio();
}
else
{
const qreal h = szHandle.height();
const qreal y = ( mousePos.y() - rect.y() - h * 0.5 ) / ( rect.height() - h );
up = y < 1.0 - valueAsRatio();
}
if ( up )
pageUp();
else
pageDown();
}
else
{
if ( !m_data->tracking )
{
const auto pos = handlePosition();
setValue( valueFromRatio( pos ) );
}
}
if ( !m_data->tracking && ( m_data->pressedValue != value() ) )
Q_EMIT valueChanged( value() );
setSkinStateFlag( Pressed, false );
Q_EMIT pressedChanged( false );
}
qreal QskSlider::handlePosition() const
void QskSlider::keyPressEvent( QKeyEvent* event )
{
return positionHint( Handle );
if ( auto offset = qskKeyOffset( orientation(), event->key() ) )
{
if ( m_data->inverted )
offset = -offset;
increment( offset * stepSize() );
return;
}
if ( m_data->hasOrigin )
{
switch( event->key() )
{
case Qt::Key_Home:
{
setValue( origin() );
return;
}
case Qt::Key_End:
{
// we have 2 endpoints - better do nothing
return;
}
}
}
Inherited::keyPressEvent( event );
}
void QskSlider::moveHandle()
{
const auto aspect = Handle | QskAspect::Metric | QskAspect::Position;
moveHandleTo( value(), animationHint( aspect | skinStates() ) );
QskAnimationHint hint;
if ( !m_data->dragging )
{
const auto aspect = Handle | QskAspect::Metric | QskAspect::Position;
hint = animationHint( aspect | skinStates() );
}
moveHandleTo( value(), hint );
}
void QskSlider::moveHandleTo( qreal value, const QskAnimationHint& hint )

View File

@ -7,26 +7,29 @@
#define QSK_SLIDER_H
#include "QskBoundedValueInput.h"
#include "QskNamespace.h"
class QSK_EXPORT QskSlider : public QskBoundedValueInput
{
Q_OBJECT
Q_PROPERTY( bool isPressed READ isPressed NOTIFY pressedChanged )
Q_PROPERTY( Qt::Orientation orientation READ orientation
WRITE setOrientation NOTIFY orientationChanged )
Q_PROPERTY( bool inverted READ isInverted
WRITE setInverted NOTIFY invertedChanged )
Q_PROPERTY( bool tracking READ isTracking
WRITE setTracking NOTIFY trackingChanged )
Q_PROPERTY( qreal handlePosition READ handlePosition )
Q_PROPERTY( qreal origin READ origin
WRITE setOrigin RESET resetOrigin NOTIFY originChanged )
using Inherited = QskBoundedValueInput;
public:
QSK_SUBCONTROLS( Panel, Groove, Fill, Scale, Handle,
GrooveStopIndicators, FillStopIndicators, LabelContainer, LabelText )
QSK_SUBCONTROLS( Panel, Groove, Fill, Scale, Tick, Handle,
LabelContainer, LabelText )
QSK_STATES( Pressed )
explicit QskSlider( QQuickItem* parent = nullptr );
@ -34,11 +37,16 @@ class QSK_EXPORT QskSlider : public QskBoundedValueInput
~QskSlider() override;
bool isPressed() const;
void setOrientation( Qt::Orientation );
Qt::Orientation orientation() const;
void setInverted( bool );
bool isInverted() const;
void resetOrigin();
qreal origin() const;
bool hasOrigin() const;
void setTracking( bool );
bool isTracking() const;
@ -46,20 +54,24 @@ class QSK_EXPORT QskSlider : public QskBoundedValueInput
QskAspect::Variation effectiveVariation() const override;
public Q_SLOTS:
void setOrigin( qreal );
Q_SIGNALS:
void pressedChanged( bool );
void orientationChanged( Qt::Orientation );
void invertedChanged( bool );
void trackingChanged( bool );
void originChanged( qreal );
protected:
void mousePressEvent( QMouseEvent* ) override;
void mouseMoveEvent( QMouseEvent* ) override;
void mouseReleaseEvent( QMouseEvent* ) override;
QSizeF handleSize() const;
QRectF handleRect() const;
void keyPressEvent( QKeyEvent* ) override;
void aboutToShow() override;
void componentComplete() override;
private:
void moveHandle();

View File

@ -5,58 +5,91 @@
#include "QskSliderSkinlet.h"
#include "QskSlider.h"
#include "QskAspect.h"
#include "QskBoxBorderMetrics.h"
#include "QskFunctions.h"
#include "QskIntervalF.h"
#include <QFontMetricsF>
#include <QtMath>
#include <qvector.h>
#include <qpair.h>
#include <qmath.h>
#include <qfontmetrics.h>
// the color of graduation ticks might different, when being on top of the filling
QSK_SYSTEM_STATE( QskSliderSkinlet, Filled, QskAspect::FirstUserState >> 1 )
using Q = QskSlider;
namespace
static inline qreal qskSubcontrolExtent(
const QskSkinnable* skinnable, QskAspect::Subcontrol subControl )
{
inline QRectF qskInnerPanelRect(
const QskSlider* slider, const QRectF& contentsRect )
return skinnable->metric( subControl | QskAspect::Size, -1.0 );
}
static inline bool qskHasFilling( const QskSlider* slider )
{
const auto policy = slider->flagHint< Qsk::Policy >(
Q::Fill | QskAspect::Option, Qsk::Always );
switch( policy )
{
#if 1
auto padding = slider->paddingHint( Q::Panel );
padding += slider->boxBorderMetricsHint( Q::Panel ).widths();
case Qsk::Never:
return false;
auto r = slider->subControlRect( contentsRect, Q::Panel );
r = r.marginsRemoved( padding );
#else
r = slider->subControlContentsRect( contentsRect, Q::Panel );
#endif
case Qsk::Maybe:
return qskFuzzyCompare( slider->origin(), slider->minimum() );
return r;
}
QRectF qskInnerValueRect( const QskSlider* slider, const QRectF& contentsRect )
{
// For M3 the stop indicators have some padding related to the groove (and fill),
// so we use the rect between first and last stop indicator as authoritative for
// indicators, handle etc.
const auto grooveIndicatorMargins = slider->paddingHint( Q::GrooveStopIndicators );
const auto r = qskInnerPanelRect( slider, contentsRect ).marginsRemoved( grooveIndicatorMargins );
return r;
default:
return true;
}
}
static QRectF qskInnerRect( const QskSlider* slider,
const QRectF& contentsRect, QskAspect::Subcontrol subControl )
{
auto r = slider->subControlContentsRect( contentsRect, Q::Panel );
const qreal extent = qskSubcontrolExtent( slider, subControl );
if ( extent >= 0.0 )
{
if ( slider->orientation() == Qt::Horizontal )
{
if ( extent < r.height() )
{
r.setTop( r.center().y() - 0.5 * extent );
r.setHeight( extent );
}
}
else
{
if ( extent < r.width() )
{
r.setLeft( r.center().x() - 0.5 * extent );
r.setWidth( extent );
}
}
}
return r;
}
static inline QPair< qreal, qreal > qskTickSpan( qreal min, qreal max, qreal length )
{
if ( length >= 0.0 )
{
// using the center of [min,max]
min += 0.5 * ( max - min - length );
max = min + length;
}
return { min, max };
}
QskSliderSkinlet::QskSliderSkinlet( QskSkin* skin )
: Inherited( skin )
{
setNodeRoles( {
PanelRole,
GrooveRole,
FillRole,
FillStopIndicatorsRole,
GrooveStopIndicatorsRole,
HandleRole,
LabelContainerRole,
LabelTextRole,
} );
setNodeRoles( { PanelRole, GrooveRole, FillRole, TicksRole, HandleRole,
LabelContainerRole, LabelTextRole } );
}
QskSliderSkinlet::~QskSliderSkinlet()
@ -69,107 +102,29 @@ QRectF QskSliderSkinlet::subControlRect( const QskSkinnable* skinnable,
const auto slider = static_cast< const QskSlider* >( skinnable );
if ( subControl == Q::Panel )
{
return panelRect( slider, contentsRect );
}
if ( subControl == Q::Groove )
{
return grooveRect( slider, contentsRect );
}
return qskInnerRect( slider, contentsRect, Q::Groove );
if ( subControl == Q::Fill )
{
return fillRect( slider, contentsRect );
}
if ( subControl == Q::Handle )
{
return handleRect( slider, contentsRect );
}
if ( subControl == Q::Scale )
{
return scaleRect( slider, contentsRect );
}
return subControlRect( skinnable, contentsRect, Q::Groove );
if ( subControl == Q::Handle )
return handleRect( slider, contentsRect );
if ( subControl == Q::LabelContainer )
{
return labelContainerRect( slider, contentsRect );
}
if ( subControl == Q::LabelText )
{
return labelContainerRect( slider, contentsRect );
}
return Inherited::subControlRect( skinnable, contentsRect, subControl );
}
int QskSliderSkinlet::sampleCount( const QskSkinnable* skinnable,
QskAspect::Subcontrol subControl ) const
{
const auto slider = static_cast< const QskSlider* >( skinnable );
if( slider->snap() )
{
const auto num = qCeil( slider->boundaryLength() / slider->stepSize() ) + 1;
return num;
}
else
{
return ( subControl == Q::GrooveStopIndicators ) ? 1 : 0;
}
}
QRectF QskSliderSkinlet::sampleRect(
const QskSkinnable* skinnable, const QRectF& contentsRect,
QskAspect::Subcontrol subControl, int index ) const
{
const auto slider = static_cast< const QskSlider* >( skinnable );
auto r = qskInnerValueRect( slider, contentsRect );
const auto size = slider->strutSizeHint( subControl );
const auto filledPoints = qFloor( ( slider->value() - slider->minimum() ) / slider->stepSize() );
if( slider->snap())
{
if( slider->snap()
&& ( ( index >= filledPoints && subControl == Q::FillStopIndicators )
|| ( index < filledPoints && subControl == Q::GrooveStopIndicators ) ) )
{
return {};
}
}
const auto pos = slider->snap() ? slider->minimum() + index * slider->stepSize() : slider->maximum();
if( slider->orientation() == Qt::Horizontal )
{
r.setTop( r.center().y() - size.height() / 2 );
const auto x = r.left() + slider->valueAsRatio( pos ) * r.width() - size.width() / 2;
r.setLeft( x );
}
else
{
r.setLeft( r.center().x() - size.width() / 2 );
const auto y = r.bottom() - slider->valueAsRatio( pos ) * r.height() - size.height() / 2;
r.setTop( y );
}
r.setHeight( size.height() );
r.setWidth( size.width() );
return r;
}
QskAspect::States QskSliderSkinlet::sampleStates( const QskSkinnable*, QskAspect::Subcontrol, int ) const
{
return {};
}
QSGNode* QskSliderSkinlet::updateSubNode(
const QskSkinnable* skinnable, quint8 nodeRole, QSGNode* node ) const
{
@ -178,56 +133,102 @@ QSGNode* QskSliderSkinlet::updateSubNode(
switch ( nodeRole )
{
case PanelRole:
{
return updateBoxNode( slider, node, Q::Panel );
}
case GrooveRole:
{
return updateBoxNode( slider, node, Q::Groove );
}
case FillRole:
{
return updateBoxNode( slider, node, Q::Fill );
}
case GrooveStopIndicatorsRole:
{
return updateSeriesNode( slider, Q::GrooveStopIndicators, node );
}
case FillStopIndicatorsRole:
{
return updateSeriesNode( slider, Q::FillStopIndicators, node );
}
case HandleRole:
{
return updateBoxNode( slider, node, Q::Handle );
}
case TicksRole:
return updateSeriesNode( slider, Q::Tick, node );
case LabelContainerRole:
{
return updateBoxNode( slider, node, Q::LabelContainer );
}
case LabelTextRole:
{
return updateTextNode( slider, node, slider->valueText(), Q::LabelText );
}
}
return Inherited::updateSubNode( skinnable, nodeRole, node );
}
int QskSliderSkinlet::sampleCount( const QskSkinnable* skinnable,
QskAspect::Subcontrol subControl ) const
{
if ( subControl == Q::Tick )
{
const auto slider = static_cast< const QskSlider* >( skinnable );
return graduation( slider ).count();
}
return Inherited::sampleCount( skinnable, subControl );
}
QVariant QskSliderSkinlet::sampleAt( const QskSkinnable* skinnable,
QskAspect::Subcontrol subControl, int index ) const
{
if ( subControl == Q::Tick )
{
const auto slider = static_cast< const QskSlider* >( skinnable );
return graduation( slider ).value( index );
}
return Inherited::sampleAt( skinnable, subControl, index );
}
QskAspect::States QskSliderSkinlet::sampleStates(
const QskSkinnable* skinnable, QskAspect::Subcontrol subControl, int index ) const
{
auto states = Inherited::sampleStates( skinnable, subControl, index );
const auto slider = static_cast< const QskSlider* >( skinnable );
if ( subControl == Q::Tick && qskHasFilling( slider ) )
{
const auto tickValue = sampleAt( skinnable, subControl, index );
if ( tickValue.canConvert< qreal >() )
{
const auto intv = QskIntervalF::normalized(
slider->origin(), slider->value() );
if ( intv.contains( tickValue.value< qreal >() ) )
states |= Filled;
}
}
return states;
}
QRectF QskSliderSkinlet::sampleRect(
const QskSkinnable* skinnable, const QRectF& contentsRect,
QskAspect::Subcontrol subControl, int index ) const
{
if ( subControl == Q::Tick )
{
const auto slider = static_cast< const QskSlider* >( skinnable );
return tickRect( slider, contentsRect, index );
}
return Inherited::sampleRect( skinnable, contentsRect, subControl, index );
}
QSGNode* QskSliderSkinlet::updateSampleNode( const QskSkinnable* skinnable,
QskAspect::Subcontrol subControl, int index, QSGNode* node ) const
{
const auto slider = static_cast< const QskSlider* >( skinnable );
const auto rect = sampleRect( slider, slider->contentsRect(), subControl, index );
if ( subControl == Q::Tick )
{
const auto slider = static_cast< const QskSlider* >( skinnable );
const auto rect = sampleRect( slider, slider->contentsRect(), subControl, index );
return updateBoxNode( skinnable, node, rect, subControl );
return updateBoxNode( skinnable, node, rect, subControl );
}
return Inherited::updateSampleNode( skinnable, subControl, index, node );
}
QRectF QskSliderSkinlet::panelRect(
@ -235,104 +236,73 @@ QRectF QskSliderSkinlet::panelRect(
{
auto r = contentsRect;
const qreal size = slider->metric( Q::Panel | QskAspect::Size ); // 0: no hint
if ( size > 0 && size < r.height() )
const qreal extent = qskSubcontrolExtent( slider, Q::Panel );
if ( extent >= 0 && extent < r.height() )
{
const auto alignment = slider->alignmentHint( Q::Panel );
if ( slider->orientation() == Qt::Horizontal )
r = qskAlignedRectF( r, r.width(), size, alignment & Qt::AlignVertical_Mask );
{
r = qskAlignedRectF( r, r.width(),
extent, alignment & Qt::AlignVertical_Mask );
}
else
r = qskAlignedRectF( r, size, r.height(), alignment & Qt::AlignHorizontal_Mask );
}
return r;
}
QRectF QskSliderSkinlet::innerRect( const QskSlider* slider,
const QRectF& contentsRect, QskAspect::Subcontrol subControl ) const
{
auto r = qskInnerPanelRect( slider, contentsRect );
QskSkinHintStatus status;
const qreal extent = slider->metric( subControl | QskAspect::Size, &status );
if ( slider->orientation() == Qt::Horizontal )
{
if ( status.isValid() && ( extent < r.height() ) )
{
r.setTop( r.center().y() - 0.5 * extent );
r.setHeight( extent );
}
}
else
{
if ( status.isValid() && ( extent < r.width() ) )
{
r.setLeft( r.center().x() - 0.5 * extent );
r.setWidth( extent );
r = qskAlignedRectF( r, extent, r.height(),
alignment & Qt::AlignHorizontal_Mask );
}
}
return r;
}
QRectF QskSliderSkinlet::grooveRect(
const QskSlider* slider, const QRectF& contentsRect ) const
{
const auto r = qskInnerPanelRect( slider, contentsRect );
auto grooveRect = innerRect( slider, contentsRect, Q::Groove );
const auto handleRect = slider->subControlRect( Q::Handle );
if ( slider->orientation() == Qt::Horizontal )
{
grooveRect.setLeft( handleRect.right() );
grooveRect.setRight( r.right() );
}
else
{
grooveRect.setBottom( handleRect.top() );
grooveRect.setTop( r.top() );
}
return grooveRect;
}
QRectF QskSliderSkinlet::scaleRect(
const QskSlider* slider, const QRectF& rect ) const
{
return innerRect( slider, rect, Q::Groove );
}
QRectF QskSliderSkinlet::fillRect(
const QskSlider* slider, const QRectF& contentsRect ) const
{
const auto r = qskInnerPanelRect( slider, contentsRect );
const auto handleRect = slider->subControlRect( Q::Handle );
if ( !qskHasFilling( slider ) )
return QRectF();
auto pos1 = slider->valueAsRatio( slider->origin() );
auto pos2 = qBound( 0.0, slider->positionHint( Q::Handle ), 1.0 );
if ( slider->isInverted() )
{
pos1 = 1.0 - pos1;
pos2 = 1.0 - pos2;
}
if ( pos1 > pos2 )
qSwap( pos1, pos2 );
auto r = qskInnerRect( slider, contentsRect, Q::Fill );
auto scaleRect = subControlRect( slider, contentsRect, Q::Scale );
auto fillRect = innerRect( slider, contentsRect, Q::Fill );
if ( slider->orientation() == Qt::Horizontal )
{
fillRect.setLeft( r.left() );
fillRect.setRight( handleRect.left() );
if ( !qFuzzyIsNull( pos1 ) )
r.setLeft( scaleRect.left() + pos1 * scaleRect.width() );
r.setRight( scaleRect.left() + pos2 * scaleRect.width() );
}
else
{
fillRect.setBottom( r.bottom() );
fillRect.setTop( handleRect.bottom() );
if ( !qFuzzyIsNull( pos1 ) )
r.setBottom( scaleRect.bottom() - pos1 * scaleRect.height() );
r.setTop( scaleRect.bottom() - pos2 * scaleRect.height() );
}
return fillRect;
return r;
}
QRectF QskSliderSkinlet::handleRect(
const QskSlider* slider, const QRectF& contentsRect ) const
{
auto handleSize = slider->strutSizeHint( Q::Handle );
const auto pos = qBound( 0.0, slider->handlePosition(), 1.0 );
const auto pos = qBound( 0.0, slider->positionHint( Q::Handle ), 1.0 );
const auto r = qskInnerValueRect( slider, contentsRect );
const auto r = subControlRect( slider, contentsRect, Q::Scale );
auto center = r.center();
if ( slider->orientation() == Qt::Horizontal )
@ -343,7 +313,10 @@ QRectF QskSliderSkinlet::handleRect(
if ( handleSize.width() < 0.0 )
handleSize.setWidth( handleSize.height() );
center.setX( r.left() + pos * r.width() );
if ( slider->isInverted() )
center.setX( r.right() - pos * r.width() );
else
center.setX( r.left() + pos * r.width() );
}
else
{
@ -353,17 +326,56 @@ QRectF QskSliderSkinlet::handleRect(
if ( handleSize.height() < 0.0 )
handleSize.setHeight( handleSize.width() );
center.setY( r.bottom() - pos * r.height() );
if ( slider->isInverted() )
center.setY( r.top() + pos * r.height() );
else
center.setY( r.bottom() - pos * r.height() );
}
QRectF handleRect( 0, 0, handleSize.width(), handleSize.height() );
handleRect.moveCenter( center );
handleRect = handleRect.marginsRemoved( slider->marginHint( Q::Handle ) );
return handleRect;
}
QRectF QskSliderSkinlet::tickRect( const QskSlider* slider,
const QRectF& contentsRect, int index ) const
{
const auto tickValue = sampleAt( slider, Q::Tick, index );
if ( !tickValue.canConvert< qreal >() )
return QRectF();
auto tickPos = slider->valueAsRatio( tickValue.value< qreal >() );
if ( slider->isInverted() )
tickPos = 1.0 - tickPos;
const auto r = subControlRect( slider, contentsRect, Q::Scale );
const auto padding = slider->paddingHint( Q::Scale );
const auto size = slider->strutSizeHint( Q::Tick );
if( slider->orientation() == Qt::Horizontal )
{
const auto x = tickPos * r.width() - 0.5 * size.width();
const auto span = qskTickSpan(
padding.top(), r.height() - padding.bottom(), size.height() );
return QRectF( r.x() + x, r.y() + span.first,
size.width(), span.second - span.first );
}
else
{
const auto y = tickPos * r.height() + 0.5 * size.height();
const auto span = qskTickSpan(
padding.left(), r.width() - padding.right(), size.width() );
return QRectF( r.x() + span.first, r.bottom() - y,
span.second - span.first, size.height() );
}
}
QRectF QskSliderSkinlet::labelContainerRect(
const QskSlider* slider, const QRectF& rect ) const
{
@ -398,24 +410,73 @@ QRectF QskSliderSkinlet::labelContainerRect(
return QRectF( x, y, size.width(), size.height() );
}
QSizeF QskSliderSkinlet::sizeHint( const QskSkinnable* skinnable,
Qt::SizeHint which, const QSizeF& ) const
{
if ( which != Qt::PreferredSize )
return QSizeF();
const auto panelHint = skinnable->strutSizeHint( Q::Panel );
const auto grooveHint = skinnable->strutSizeHint( Q::Groove );
const auto fillHint = skinnable->strutSizeHint( Q::Fill );
const auto handleHint = skinnable->strutSizeHint( Q::Handle );
auto extent = qskSubcontrolExtent( skinnable, Q::Panel );
extent = qMax( extent, qskSubcontrolExtent( skinnable, Q::Groove ) );
extent = qMax( extent, qskSubcontrolExtent( skinnable, Q::Fill ) );
auto hint = panelHint;
hint = hint.expandedTo( grooveHint );
hint = hint.expandedTo( fillHint );
hint = hint.expandedTo( handleHint );
const auto slider = static_cast< const QskSlider* >( skinnable );
auto hint = skinnable->strutSizeHint( Q::Handle );
if ( slider->orientation() == Qt::Horizontal )
hint.setHeight( qMax( hint.height(), extent ) );
else
hint.setWidth( qMax( hint.width(), extent ) );
return hint;
}
bool QskSliderSkinlet::hasGraduation( const QskSlider* slider ) const
{
if ( slider->stepSize() )
{
const auto policy = slider->flagHint< Qsk::Policy >(
Q::Tick | QskAspect::Option, Qsk::Never );
switch( policy )
{
case Qsk::Always:
return true;
case Qsk::Maybe:
return slider->isSnapping();
case Qsk::Never:
return false;
}
}
return false;
}
QVector< qreal > QskSliderSkinlet::graduation( const QskSlider* slider ) const
{
QVector< qreal > graduation;
if ( hasGraduation( slider ) )
{
const auto from = slider->minimum();
const auto to = slider->maximum();
auto step = slider->stepSize();
if ( from > to )
step = -step;
const auto n = qCeil( ( to - from ) / step ) - 1;
graduation.reserve( n );
for ( int i = 1; i <= n; i++ )
graduation += from + i * step;
}
return graduation;
}
#include "moc_QskSliderSkinlet.cpp"

View File

@ -17,13 +17,14 @@ class QSK_EXPORT QskSliderSkinlet : public QskSkinlet
using Inherited = QskSkinlet;
public:
QSK_STATES( Filled )
enum NodeRole
{
PanelRole,
GrooveRole,
FillRole,
GrooveStopIndicatorsRole,
FillStopIndicatorsRole,
TicksRole,
HandleRole,
LabelContainerRole,
LabelTextRole,
@ -45,6 +46,9 @@ class QSK_EXPORT QskSliderSkinlet : public QskSkinlet
QRectF sampleRect( const QskSkinnable*,
const QRectF&, QskAspect::Subcontrol, int index ) const override;
QVariant sampleAt( const QskSkinnable*,
QskAspect::Subcontrol, int index ) const override;
QskAspect::States sampleStates( const QskSkinnable*,
QskAspect::Subcontrol, int ) const override;
@ -55,15 +59,16 @@ class QSK_EXPORT QskSliderSkinlet : public QskSkinlet
QSGNode* updateSampleNode( const QskSkinnable*,
QskAspect::Subcontrol, int index, QSGNode* ) const override;
virtual QVector< qreal > graduation( const QskSlider* ) const;
bool hasGraduation( const QskSlider* ) const;
private:
QRectF panelRect( const QskSlider*, const QRectF& ) const;
QRectF grooveRect( const QskSlider*, const QRectF& ) const;
QRectF fillRect( const QskSlider*, const QRectF& ) const;
QRectF handleRect( const QskSlider*, const QRectF& ) const;
QRectF scaleRect( const QskSlider*, const QRectF& ) const;
QRectF labelContainerRect( const QskSlider*, const QRectF& ) const;
QRectF tickRect( const QskSlider*, const QRectF&, int index ) const;
QRectF innerRect( const QskSlider*, const QRectF&, QskAspect::Subcontrol ) const;
QRectF labelContainerRect( const QskSlider*, const QRectF& ) const;
};
#endif

View File

@ -248,8 +248,7 @@ static inline void qskCreateFill(
using namespace QskVertex;
using namespace Qt;
const auto cn = m_metrics.corners;
const bool isHorizontal = m_metrics.preferredOrientation == Qt::Horizontal;
const bool isHorizontal = ( m_metrics.preferredOrientation == Qt::Horizontal );
if ( !m_metrics.isInsideRounded )
{
@ -258,7 +257,7 @@ static inline void qskCreateFill(
}
else if ( m_metrics.isOutsideSymmetric )
{
const int stepCount = cn[ 0 ].stepCount;
const int stepCount = m_metrics.corners[ 0 ].innerStepCount();
if ( isHorizontal )
{

View File

@ -1,85 +0,0 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskBoxClipNode.h"
#include "QskBoxBorderMetrics.h"
#include "QskBoxRenderer.h"
#include "QskBoxShapeMetrics.h"
#include "QskFunctions.h"
#include <qquickitem.h>
static inline QskHashValue qskMetricsHash(
const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border )
{
QskHashValue hash = 13000;
hash = shape.hash( hash );
return border.hash( hash );
}
QskBoxClipNode::QskBoxClipNode()
: m_hash( 0 )
, m_geometry( QSGGeometry::defaultAttributes_Point2D(), 0 )
{
setGeometry( &m_geometry );
}
QskBoxClipNode::~QskBoxClipNode()
{
}
void QskBoxClipNode::setBox( const QQuickWindow* window, const QRectF& rect,
const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border )
{
const auto hash = qskMetricsHash( shape, border );
if ( hash == m_hash && rect == m_rect )
return;
m_rect = rect;
m_hash = hash;
bool isRectangular = false;
#if 0
/*
Depending on isRectangular the "renderer can use scissoring instead of stencil,
which is significantly faster."
However the batch renderer ( qsgbatchrenderer.cpp ) is rounding the clip rectangle
to integers and the clip might become too small/large.
So we always have to use stencil clipping - even if it might have a negative
impact on the performance. TODO ...
*/
if ( shape.isRectangle() )
isRectangular = true;
#endif
if ( isRectangular )
{
if ( m_geometry.vertexCount() > 0 )
m_geometry.allocate( 0 );
setIsRectangular( true );
}
else
{
setIsRectangular( false );
QskBoxRenderer renderer( window );
renderer.setFillLines( rect, shape, border, m_geometry );
}
/*
Even in situations, where the clipping is not rectangular, it is
useful to know its bounding rectangle
*/
setClipRect( qskValidOrEmptyInnerRect( rect, border.widths() ) );
m_geometry.markVertexDataDirty();
markDirty( QSGNode::DirtyGeometry );
}

View File

@ -168,7 +168,8 @@ void QskBoxRenderer::setColoredBorderLines( const QRectF& rect,
geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
geometry.markVertexDataDirty();
const QskBoxBasicStroker stroker( QskBoxMetrics( rect, shape, border ), borderColors );
const QskBoxMetrics metrics( rect, shape, border );
const QskBoxBasicStroker stroker( metrics, borderColors );
if ( auto lines = qskAllocateColoredLines( geometry, stroker.borderCount() ) )
stroker.setBoxLines( lines, nullptr );

151
src/nodes/QskClipNode.cpp Normal file
View File

@ -0,0 +1,151 @@
/******************************************************************************
* QSkinny - Copyright (C) The authors
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#include "QskClipNode.h"
#include "QskBoxBorderMetrics.h"
#include "QskBoxRenderer.h"
#include "QskBoxShapeMetrics.h"
#include "QskFunctions.h"
#include "QskVertex.h"
#include <qquickwindow.h>
static inline QskHashValue qskMetricsHash(
const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border )
{
QskHashValue hash = 13000;
hash = shape.hash( hash );
return border.hash( hash );
}
static inline void qskSetBoundingRect( QSGClipNode* node, const QRectF& rect )
{
/*
Depending on isRectangular: the "scene graph renderer can use
scissoring instead of stencil, which is significantly faster."
However the batch renderer ( qsgbatchrenderer.cpp ) is rounding
the clip rectangle to integers and the clip might become too small/large.
So we always have to use stencil clipping - even if it might have a negative
impact on the performance.
When isRectangular is set to false the clipRect is not used from the
renderer and we use the memory for the storing the bounding rectangle.
*/
node->setIsRectangular( false );
node->setClipRect( rect );
}
static inline QskVertex::Line* qskAllocateLines(
QSGGeometry& geometry, int lineCount )
{
geometry.allocate( 2 * lineCount ); // 2 points per line
return reinterpret_cast< QskVertex::Line* >( geometry.vertexData() );
}
QskClipNode::QskClipNode()
: m_hash( 0 )
, m_geometry( QSGGeometry::defaultAttributes_Point2D(), 0 )
{
setGeometry( &m_geometry );
}
QskClipNode::~QskClipNode()
{
}
void QskClipNode::setRect( const QRectF& rect )
{
setRegion( rect, QRectF() );
}
void QskClipNode::setRegion( const QRectF& rect, const QRectF& excludedRect )
{
if ( rect.isEmpty() )
{
/*
what about rectangles having a width/height
of 0 ( f.e lines ) TODO ...
*/
reset();
return;
}
const auto innerRect = excludedRect.isEmpty()
? QRectF() : excludedRect.intersected( rect );
const auto hash = qHashBits( &innerRect, sizeof( innerRect ), 1450 );
if ( ( hash == m_hash ) && ( rect == Inherited::clipRect() ) )
return;
qskSetBoundingRect( this, rect );
m_hash = hash;
m_geometry.setDrawingMode( QSGGeometry::DrawTriangleStrip );
if ( innerRect.isEmpty() )
{
const auto l = qskAllocateLines( m_geometry, 2 );
l[0].setLine( rect.topLeft(), rect.topRight() );
l[1].setLine( rect.bottomLeft(), rect.bottomRight() );
}
else
{
const auto l = qskAllocateLines( m_geometry, 5 );
l[0].setLine( rect.topLeft(), innerRect.topLeft() );
l[1].setLine( rect.topRight(), innerRect.topRight() );
l[2].setLine( rect.bottomRight(), innerRect.bottomRight() );
l[3].setLine( rect.bottomLeft(), innerRect.bottomLeft() );
l[4] = l[0];
}
m_geometry.markVertexDataDirty();
markDirty( QSGNode::DirtyGeometry );
}
void QskClipNode::setBox( const QQuickWindow* window, const QRectF& rect,
const QskBoxShapeMetrics& shape, const QskBoxBorderMetrics& border )
{
if ( rect.isEmpty() )
{
reset();
return;
}
const auto hash = qskMetricsHash( shape, border );
if ( hash == m_hash && rect == boundingRectangle() )
return;
qskSetBoundingRect( this, rect );
m_hash = hash;
QskBoxRenderer renderer( window );
renderer.setFillLines( rect, shape, border, m_geometry );
m_geometry.markVertexDataDirty();
markDirty( QSGNode::DirtyGeometry );
}
void QskClipNode::reset()
{
Inherited::setIsRectangular( true );
Inherited::setClipRect( QRectF() );
if ( m_geometry.vertexData() )
{
m_geometry.allocate( 0 );
m_geometry.markVertexDataDirty();
}
if ( m_hash != 0 )
{
m_hash = 0;
markDirty( QSGNode::DirtyGeometry );
}
}

View File

@ -3,8 +3,8 @@
* SPDX-License-Identifier: BSD-3-Clause
*****************************************************************************/
#ifndef QSK_BOX_CLIP_NODE_H
#define QSK_BOX_CLIP_NODE_H
#ifndef QSK_CLIP_NODE_H
#define QSK_CLIP_NODE_H
#include "QskGlobal.h"
#include <qsgnode.h>
@ -14,20 +14,36 @@ class QskBoxBorderMetrics;
class QQuickWindow;
class QSK_EXPORT QskBoxClipNode : public QSGClipNode
class QSK_EXPORT QskClipNode : public QSGClipNode
{
using Inherited = QSGClipNode;
public:
QskBoxClipNode();
~QskBoxClipNode() override;
QskClipNode();
~QskClipNode() override;
void setRect( const QRectF& );
void setRegion( const QRectF&, const QRectF& excludedRect );
void setBox( const QQuickWindow*, const QRectF&,
const QskBoxShapeMetrics&, const QskBoxBorderMetrics& );
private:
QskHashValue m_hash;
QRectF m_rect;
QRectF boundingRectangle() const;
private:
void reset();
void setIsRectangular( bool ) = delete;
void setClipRect( const QRectF& ) = delete;
QRectF clipRect() const = delete;
QskHashValue m_hash;
QSGGeometry m_geometry;
};
inline QRectF QskClipNode::boundingRectangle() const
{
return Inherited::clipRect();
}
#endif

View File

@ -88,13 +88,16 @@ void QskSGNode::setParentNode( QSGNode* node, QSGNode* parent )
QSGNode* QskSGNode::findChildNode( QSGNode* parent, quint8 role )
{
auto node = parent->firstChild();
while ( node )
if ( parent )
{
if ( nodeRole( node ) == role )
return node;
auto node = parent->firstChild();
while ( node )
{
if ( nodeRole( node ) == role )
return node;
node = node->nextSibling();
node = node->nextSibling();
}
}
return nullptr;