mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-15 00:07:25 -05:00
Allow any symbol to be an animated symbol
Users can now indicate that a symbol should be treated as a animated
symbol, through the new "Animation Settings" option in the symbol
widget's Advanced menu.
This settings panel allows users to enable animation for the symbol
and set a specific frame rate at which the symbol should be redrawn.
When enabled, the @symbol_frame variable can be used in any
symbol data defined property in order to animate that property.
For instance, setting the symbol's rotation to the data defined
expression
@symbol_frame % 360
will cause the symbol to rotate over time. (with rotation speed
dictated by the symbol's refresh rate)
This commit is contained in:
parent
de3f920f9c
commit
4eede35d55
@ -11,6 +11,58 @@
|
||||
|
||||
typedef QList<QgsSymbolLayer *> QgsSymbolLayerList;
|
||||
|
||||
class QgsSymbolAnimationSettings
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
|
||||
Contains settings relating to symbol animation.
|
||||
|
||||
.. versionadded:: 3.26
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgssymbol.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
void setIsAnimated( bool animated );
|
||||
%Docstring
|
||||
Sets whether the symbol is animated.
|
||||
|
||||
This is a user-facing setting for symbols, which allows users to define whether a
|
||||
symbol is animated, and allows for creation of animated symbols via data
|
||||
defined properties.
|
||||
|
||||
.. seealso:: :py:func:`isAnimated`
|
||||
%End
|
||||
|
||||
bool isAnimated() const;
|
||||
%Docstring
|
||||
Returns ``True`` if the symbol is animated.
|
||||
|
||||
This is a user-facing setting for symbols, which allows users to define whether a
|
||||
symbol is animated, and allows for creation of animated symbols via data
|
||||
defined properties.
|
||||
|
||||
.. seealso:: :py:func:`setIsAnimated`
|
||||
%End
|
||||
|
||||
void setFrameRate( double rate );
|
||||
%Docstring
|
||||
Sets the symbol animation frame ``rate`` (in frames per second).
|
||||
|
||||
.. seealso:: :py:func:`frameRate`
|
||||
%End
|
||||
|
||||
double frameRate() const;
|
||||
%Docstring
|
||||
Returns the symbol animation frame rate (in frames per second).
|
||||
|
||||
.. seealso:: :py:func:`setFrameRate`
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
class QgsSymbol
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
@ -522,6 +574,25 @@ direction.
|
||||
.. seealso:: :py:func:`setForceRHR`
|
||||
|
||||
.. versionadded:: 3.6
|
||||
%End
|
||||
|
||||
QgsSymbolAnimationSettings &animationSettings();
|
||||
%Docstring
|
||||
Returns a reference to the symbol animation settings.
|
||||
|
||||
.. seealso:: :py:func:`setAnimationSettings`
|
||||
|
||||
.. versionadded:: 3.26
|
||||
%End
|
||||
|
||||
|
||||
void setAnimationSettings( const QgsSymbolAnimationSettings &settings );
|
||||
%Docstring
|
||||
Sets a the symbol animation ``settings``.
|
||||
|
||||
.. seealso:: :py:func:`animationSettings`
|
||||
|
||||
.. versionadded:: 3.26
|
||||
%End
|
||||
|
||||
QSet<QString> usedAttributes( const QgsRenderContext &context ) const;
|
||||
@ -704,6 +775,7 @@ Render editing vertex marker at specified point
|
||||
|
||||
|
||||
|
||||
|
||||
private:
|
||||
QgsSymbol( const QgsSymbol & );
|
||||
};
|
||||
|
||||
@ -0,0 +1,89 @@
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/gui/symbology/qgssymbolanimationsettingswidget.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class QgsSymbolAnimationSettingsWidget: QgsPanelWidget
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
A widget for customising animation settings for a symbol.
|
||||
|
||||
.. versionadded:: 3.26
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgssymbolanimationsettingswidget.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsSymbolAnimationSettingsWidget( QWidget *parent /TransferThis/ = 0 );
|
||||
%Docstring
|
||||
Constructor for QgsSymbolAnimationSettingsWidget
|
||||
%End
|
||||
|
||||
void setAnimationSettings( const QgsSymbolAnimationSettings &settings );
|
||||
%Docstring
|
||||
Sets the animation ``settings`` to show in the widget.
|
||||
|
||||
.. seealso:: :py:func:`animationSettings`
|
||||
%End
|
||||
|
||||
QgsSymbolAnimationSettings animationSettings() const;
|
||||
%Docstring
|
||||
Returns the animation settings as defined in the widget.
|
||||
|
||||
.. seealso:: :py:func:`setAnimationSettings`
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
class QgsSymbolAnimationSettingsDialog : QDialog
|
||||
{
|
||||
%Docstring(signature="appended")
|
||||
A dialog for customising animation settings for a symbol.
|
||||
|
||||
.. versionadded:: 3.26
|
||||
%End
|
||||
|
||||
%TypeHeaderCode
|
||||
#include "qgssymbolanimationsettingswidget.h"
|
||||
%End
|
||||
public:
|
||||
|
||||
QgsSymbolAnimationSettingsDialog( QWidget *parent /TransferThis/ = 0, Qt::WindowFlags f = Qt::WindowFlags() );
|
||||
%Docstring
|
||||
Constructor for QgsSymbolAnimationSettingsDialog
|
||||
%End
|
||||
|
||||
void setAnimationSettings( const QgsSymbolAnimationSettings &settings );
|
||||
%Docstring
|
||||
Sets the animation ``settings`` to show in the dialog.
|
||||
|
||||
.. seealso:: :py:func:`animationSettings`
|
||||
%End
|
||||
|
||||
QgsSymbolAnimationSettings animationSettings() const;
|
||||
%Docstring
|
||||
Returns the animation settings as defined in the dialog.
|
||||
|
||||
.. seealso:: :py:func:`setAnimationSettings`
|
||||
%End
|
||||
|
||||
};
|
||||
|
||||
/************************************************************************
|
||||
* This file has been generated automatically from *
|
||||
* *
|
||||
* src/gui/symbology/qgssymbolanimationsettingswidget.h *
|
||||
* *
|
||||
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||
************************************************************************/
|
||||
@ -447,6 +447,7 @@
|
||||
%Include auto_generated/symbology/qgsstylemanagerdialog.sip
|
||||
%Include auto_generated/symbology/qgsstylesavedialog.sip
|
||||
%Include auto_generated/symbology/qgssvgselectorwidget.sip
|
||||
%Include auto_generated/symbology/qgssymbolanimationsettingswidget.sip
|
||||
%Include auto_generated/symbology/qgssymbollayerwidget.sip
|
||||
%Include auto_generated/symbology/qgssymbollevelsdialog.sip
|
||||
%Include auto_generated/symbology/qgssymbolselectordialog.sip
|
||||
|
||||
@ -854,6 +854,7 @@ void QgsExpression::initVariableHelp()
|
||||
sVariableHelpTexts()->insert( QStringLiteral( "symbol_layer_index" ), QCoreApplication::translate( "symbol_layer_index", "Current symbol layer index." ) );
|
||||
sVariableHelpTexts()->insert( QStringLiteral( "symbol_marker_row" ), QCoreApplication::translate( "symbol_marker_row", "Row number for marker (valid for point pattern fills only)." ) );
|
||||
sVariableHelpTexts()->insert( QStringLiteral( "symbol_marker_column" ), QCoreApplication::translate( "symbol_marker_column", "Column number for marker (valid for point pattern fills only)." ) );
|
||||
sVariableHelpTexts()->insert( QStringLiteral( "symbol_frame" ), QCoreApplication::translate( "symbol_frame", "Frame number (for animated symbols only)." ) );
|
||||
|
||||
sVariableHelpTexts()->insert( QStringLiteral( "symbol_label" ), QCoreApplication::translate( "symbol_label", "Label for the symbol (either a user defined label or the default autogenerated label)." ) );
|
||||
sVariableHelpTexts()->insert( QStringLiteral( "symbol_id" ), QCoreApplication::translate( "symbol_id", "Internal ID of the symbol." ) );
|
||||
|
||||
@ -152,6 +152,7 @@ QgsFillSymbol *QgsFillSymbol::clone() const
|
||||
cloneSymbol->setForceRHR( mForceRHR );
|
||||
cloneSymbol->setDataDefinedProperties( dataDefinedProperties() );
|
||||
cloneSymbol->setFlags( mSymbolFlags );
|
||||
cloneSymbol->setAnimationSettings( mAnimationSettings );
|
||||
return cloneSymbol;
|
||||
}
|
||||
|
||||
|
||||
@ -283,5 +283,6 @@ QgsLineSymbol *QgsLineSymbol::clone() const
|
||||
cloneSymbol->setForceRHR( mForceRHR );
|
||||
cloneSymbol->setDataDefinedProperties( dataDefinedProperties() );
|
||||
cloneSymbol->setFlags( mSymbolFlags );
|
||||
cloneSymbol->setAnimationSettings( mAnimationSettings );
|
||||
return cloneSymbol;
|
||||
}
|
||||
|
||||
@ -498,6 +498,7 @@ QgsMarkerSymbol *QgsMarkerSymbol::clone() const
|
||||
cloneSymbol->setForceRHR( mForceRHR );
|
||||
cloneSymbol->setDataDefinedProperties( dataDefinedProperties() );
|
||||
cloneSymbol->setFlags( mSymbolFlags );
|
||||
cloneSymbol->setAnimationSettings( mAnimationSettings );
|
||||
return cloneSymbol;
|
||||
}
|
||||
|
||||
|
||||
@ -352,6 +352,21 @@ void QgsSymbol::setMapUnitScale( const QgsMapUnitScale &scale )
|
||||
}
|
||||
}
|
||||
|
||||
QgsSymbolAnimationSettings &QgsSymbol::animationSettings()
|
||||
{
|
||||
return mAnimationSettings;
|
||||
}
|
||||
|
||||
const QgsSymbolAnimationSettings &QgsSymbol::animationSettings() const
|
||||
{
|
||||
return mAnimationSettings;
|
||||
}
|
||||
|
||||
void QgsSymbol::setAnimationSettings( const QgsSymbolAnimationSettings &settings )
|
||||
{
|
||||
mAnimationSettings = settings;
|
||||
}
|
||||
|
||||
QgsSymbol *QgsSymbol::defaultSymbol( QgsWkbTypes::GeometryType geomType )
|
||||
{
|
||||
std::unique_ptr< QgsSymbol > s;
|
||||
@ -498,6 +513,26 @@ void QgsSymbol::startRender( QgsRenderContext &context, const QgsFields &fields
|
||||
QgsSymbolRenderContext symbolContext( context, QgsUnitTypes::RenderUnknownUnit, mOpacity, false, mRenderHints, nullptr, fields );
|
||||
|
||||
std::unique_ptr< QgsExpressionContextScope > scope( QgsExpressionContextUtils::updateSymbolScope( this, new QgsExpressionContextScope() ) );
|
||||
|
||||
if ( mAnimationSettings.isAnimated() )
|
||||
{
|
||||
const long long mapFrameNumber = context.currentFrame();
|
||||
double animationTimeSeconds = 0;
|
||||
if ( mapFrameNumber >= 0 && context.frameRate() > 0 )
|
||||
{
|
||||
// render is part of an animation, so we base the calculated frame on that
|
||||
animationTimeSeconds = mapFrameNumber / context.frameRate();
|
||||
}
|
||||
else
|
||||
{
|
||||
// render is outside of animation, so base the calculated frame on the current epoch
|
||||
animationTimeSeconds = QDateTime::currentMSecsSinceEpoch() / 1000.0;
|
||||
}
|
||||
|
||||
const long long symbolFrame = static_cast< long long >( std::floor( animationTimeSeconds * mAnimationSettings.frameRate() ) );
|
||||
scope->setVariable( QStringLiteral( "symbol_frame" ), symbolFrame, true );
|
||||
}
|
||||
|
||||
mSymbolRenderContext->setExpressionContextScope( scope.release() );
|
||||
|
||||
mDataDefinedProperties.prepare( context.expressionContext() );
|
||||
|
||||
@ -28,6 +28,61 @@ class QgsLineSymbolLayer;
|
||||
|
||||
typedef QList<QgsSymbolLayer *> QgsSymbolLayerList;
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \class QgsSymbol
|
||||
*
|
||||
* \brief Contains settings relating to symbol animation.
|
||||
*
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
class CORE_EXPORT QgsSymbolAnimationSettings
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Sets whether the symbol is animated.
|
||||
*
|
||||
* This is a user-facing setting for symbols, which allows users to define whether a
|
||||
* symbol is animated, and allows for creation of animated symbols via data
|
||||
* defined properties.
|
||||
*
|
||||
* \see isAnimated()
|
||||
*/
|
||||
void setIsAnimated( bool animated ) { mIsAnimated = animated; }
|
||||
|
||||
/**
|
||||
* Returns TRUE if the symbol is animated.
|
||||
*
|
||||
* This is a user-facing setting for symbols, which allows users to define whether a
|
||||
* symbol is animated, and allows for creation of animated symbols via data
|
||||
* defined properties.
|
||||
*
|
||||
* \see setIsAnimated()
|
||||
*/
|
||||
bool isAnimated() const { return mIsAnimated; }
|
||||
|
||||
/**
|
||||
* Sets the symbol animation frame \a rate (in frames per second).
|
||||
*
|
||||
* \see frameRate()
|
||||
*/
|
||||
void setFrameRate( double rate ) { mFrameRate = rate; }
|
||||
|
||||
/**
|
||||
* Returns the symbol animation frame rate (in frames per second).
|
||||
*
|
||||
* \see setFrameRate()
|
||||
*/
|
||||
double frameRate() const { return mFrameRate; }
|
||||
|
||||
private:
|
||||
|
||||
bool mIsAnimated = false;
|
||||
double mFrameRate = 10;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* \ingroup core
|
||||
* \class QgsSymbol
|
||||
@ -518,6 +573,30 @@ class CORE_EXPORT QgsSymbol
|
||||
*/
|
||||
bool forceRHR() const { return mForceRHR; }
|
||||
|
||||
/**
|
||||
* Returns a reference to the symbol animation settings.
|
||||
*
|
||||
* \see setAnimationSettings()
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
QgsSymbolAnimationSettings &animationSettings();
|
||||
|
||||
/**
|
||||
* Returns a reference to the symbol animation settings.
|
||||
*
|
||||
* \see setAnimationSettings()
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
const QgsSymbolAnimationSettings &animationSettings() const SIP_SKIP;
|
||||
|
||||
/**
|
||||
* Sets a the symbol animation \a settings.
|
||||
*
|
||||
* \see animationSettings()
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
void setAnimationSettings( const QgsSymbolAnimationSettings &settings );
|
||||
|
||||
/**
|
||||
* Returns a list of attributes required to render this feature.
|
||||
* This should include any attributes required by the symbology including
|
||||
@ -721,6 +800,8 @@ class CORE_EXPORT QgsSymbol
|
||||
bool mClipFeaturesToExtent = true;
|
||||
bool mForceRHR = false;
|
||||
|
||||
QgsSymbolAnimationSettings mAnimationSettings;
|
||||
|
||||
Q_DECL_DEPRECATED const QgsVectorLayer *mLayer = nullptr; //current vectorlayer
|
||||
|
||||
private:
|
||||
|
||||
@ -1308,6 +1308,9 @@ QgsSymbol *QgsSymbolLayerUtils::loadSymbol( const QDomElement &element, const Qg
|
||||
flags |= Qgis::SymbolFlag::RendererShouldUseSymbolLevels;
|
||||
symbol->setFlags( flags );
|
||||
|
||||
symbol->animationSettings().setIsAnimated( element.attribute( QStringLiteral( "is_animated" ), QStringLiteral( "0" ) ).toInt() );
|
||||
symbol->animationSettings().setFrameRate( element.attribute( QStringLiteral( "frame_rate" ), QStringLiteral( "10" ) ).toDouble() );
|
||||
|
||||
const QDomElement ddProps = element.firstChildElement( QStringLiteral( "data_defined_properties" ) );
|
||||
if ( !ddProps.isNull() )
|
||||
{
|
||||
@ -1402,6 +1405,9 @@ QDomElement QgsSymbolLayerUtils::saveSymbol( const QString &name, const QgsSymbo
|
||||
if ( symbol->flags() & Qgis::SymbolFlag::RendererShouldUseSymbolLevels )
|
||||
symEl.setAttribute( QStringLiteral( "renderer_should_use_levels" ), QStringLiteral( "1" ) );
|
||||
|
||||
symEl.setAttribute( QStringLiteral( "is_animated" ), symbol->animationSettings().isAnimated() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
|
||||
symEl.setAttribute( QStringLiteral( "frame_rate" ), qgsDoubleToString( symbol->animationSettings().frameRate() ) );
|
||||
|
||||
//QgsDebugMsg( "num layers " + QString::number( symbol->symbolLayerCount() ) );
|
||||
|
||||
QDomElement ddProps = doc.createElement( QStringLiteral( "data_defined_properties" ) );
|
||||
@ -4966,6 +4972,13 @@ double QgsSymbolLayerUtils::rendererFrameRate( const QgsFeatureRenderer *rendere
|
||||
|
||||
void visitSymbol( const QgsSymbol *symbol )
|
||||
{
|
||||
// symbol may be marked as animated on a symbol level (e.g. when it implements animation
|
||||
// via data defined properties)
|
||||
if ( symbol->animationSettings().isAnimated() )
|
||||
{
|
||||
if ( symbol->animationSettings().frameRate() > refreshRate )
|
||||
refreshRate = symbol->animationSettings().frameRate();
|
||||
}
|
||||
for ( int idx = 0; idx < symbol->symbolLayerCount(); idx++ )
|
||||
{
|
||||
const QgsSymbolLayer *sl = symbol->symbolLayer( idx );
|
||||
|
||||
@ -61,6 +61,7 @@ set(QGIS_GUI_SRCS
|
||||
symbology/qgsstylemanagerdialog.cpp
|
||||
symbology/qgsstylesavedialog.cpp
|
||||
symbology/qgssvgselectorwidget.cpp
|
||||
symbology/qgssymbolanimationsettingswidget.cpp
|
||||
symbology/qgssymbollayerwidget.cpp
|
||||
symbology/qgssymbollevelsdialog.cpp
|
||||
symbology/qgssymbolslistwidget.cpp
|
||||
@ -1316,6 +1317,7 @@ set(QGIS_GUI_HDRS
|
||||
symbology/qgsstylemanagerdialog.h
|
||||
symbology/qgsstylesavedialog.h
|
||||
symbology/qgssvgselectorwidget.h
|
||||
symbology/qgssymbolanimationsettingswidget.h
|
||||
symbology/qgssymbollayerwidget.h
|
||||
symbology/qgssymbollevelsdialog.h
|
||||
symbology/qgssymbolselectordialog.h
|
||||
|
||||
@ -281,6 +281,7 @@ QgsExpressionContext QgsLayerPropertiesWidget::createExpressionContext() const
|
||||
expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_layer_index" ), 1, true ) );
|
||||
expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_row" ), 1, true ) );
|
||||
expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_column" ), 1, true ) );
|
||||
expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_frame" ), 1, true ) );
|
||||
|
||||
// additional scopes
|
||||
const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
|
||||
@ -297,7 +298,8 @@ QgsExpressionContext QgsLayerPropertiesWidget::createExpressionContext() const
|
||||
<< QgsExpressionContext::EXPR_GEOMETRY_RING_NUM
|
||||
<< QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT << QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM
|
||||
<< QgsExpressionContext::EXPR_CLUSTER_COLOR << QgsExpressionContext::EXPR_CLUSTER_SIZE
|
||||
<< QStringLiteral( "symbol_layer_count" ) << QStringLiteral( "symbol_layer_index" ) );
|
||||
<< QStringLiteral( "symbol_layer_count" ) << QStringLiteral( "symbol_layer_index" )
|
||||
<< QStringLiteral( "symbol_frame" ) );
|
||||
|
||||
return expContext;
|
||||
}
|
||||
|
||||
85
src/gui/symbology/qgssymbolanimationsettingswidget.cpp
Normal file
85
src/gui/symbology/qgssymbolanimationsettingswidget.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
/***************************************************************************
|
||||
qgssymbolanimationsettingswidget.cpp
|
||||
---------------------
|
||||
begin : April 2022
|
||||
copyright : (C) 2022 by Nyall Dawson
|
||||
email : nyall dot dawson at gmail dot com
|
||||
***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "qgssymbolanimationsettingswidget.h"
|
||||
#include "qgssymbol.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
|
||||
QgsSymbolAnimationSettingsWidget::QgsSymbolAnimationSettingsWidget( QWidget *parent )
|
||||
: QgsPanelWidget( parent )
|
||||
{
|
||||
setupUi( this );
|
||||
|
||||
mFrameRateSpin->setClearValue( 10 );
|
||||
mFrameRateSpin->setShowClearButton( true );
|
||||
|
||||
connect( mFrameRateSpin, qOverload< double >( &QDoubleSpinBox::valueChanged ), this, [ this ]
|
||||
{
|
||||
if ( !mBlockUpdates )
|
||||
emit widgetChanged();
|
||||
} );
|
||||
connect( mIsAnimatedGroup, &QGroupBox::toggled, this, [ this ]
|
||||
{
|
||||
if ( !mBlockUpdates )
|
||||
emit widgetChanged();
|
||||
} );
|
||||
}
|
||||
|
||||
void QgsSymbolAnimationSettingsWidget::setAnimationSettings( const QgsSymbolAnimationSettings &settings )
|
||||
{
|
||||
mBlockUpdates = true;
|
||||
mIsAnimatedGroup->setChecked( settings.isAnimated() );
|
||||
mFrameRateSpin->setValue( settings.frameRate() );
|
||||
mBlockUpdates = false;
|
||||
}
|
||||
|
||||
QgsSymbolAnimationSettings QgsSymbolAnimationSettingsWidget::animationSettings() const
|
||||
{
|
||||
QgsSymbolAnimationSettings settings;
|
||||
settings.setIsAnimated( mIsAnimatedGroup->isChecked() );
|
||||
settings.setFrameRate( mFrameRateSpin->value() );
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// QgsSymbolAnimationSettingsDialog
|
||||
//
|
||||
|
||||
QgsSymbolAnimationSettingsDialog::QgsSymbolAnimationSettingsDialog( QWidget *parent, Qt::WindowFlags f )
|
||||
: QDialog( parent, f )
|
||||
{
|
||||
QVBoxLayout *vLayout = new QVBoxLayout();
|
||||
mWidget = new QgsSymbolAnimationSettingsWidget( );
|
||||
vLayout->addWidget( mWidget );
|
||||
QDialogButtonBox *bbox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal );
|
||||
connect( bbox, &QDialogButtonBox::accepted, this, &QgsSymbolAnimationSettingsDialog::accept );
|
||||
connect( bbox, &QDialogButtonBox::rejected, this, &QgsSymbolAnimationSettingsDialog::reject );
|
||||
vLayout->addWidget( bbox );
|
||||
setLayout( vLayout );
|
||||
setWindowTitle( tr( "Animation Settings" ) );
|
||||
}
|
||||
|
||||
void QgsSymbolAnimationSettingsDialog::setAnimationSettings( const QgsSymbolAnimationSettings &settings )
|
||||
{
|
||||
mWidget->setAnimationSettings( settings );
|
||||
}
|
||||
|
||||
QgsSymbolAnimationSettings QgsSymbolAnimationSettingsDialog::animationSettings() const
|
||||
{
|
||||
return mWidget->animationSettings();
|
||||
}
|
||||
|
||||
96
src/gui/symbology/qgssymbolanimationsettingswidget.h
Normal file
96
src/gui/symbology/qgssymbolanimationsettingswidget.h
Normal file
@ -0,0 +1,96 @@
|
||||
/***************************************************************************
|
||||
qgssymbolanimationsettingswidget.h
|
||||
---------------------
|
||||
begin : April 2022
|
||||
copyright : (C) 2022 by Nyall Dawson
|
||||
email : nyall dot dawson at gmail dot com
|
||||
***************************************************************************
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#ifndef QGSSYMBOLANIMATIONSETTINGSWIDGET_H
|
||||
#define QGSSYMBOLANIMATIONSETTINGSWIDGET_H
|
||||
|
||||
#include "ui_qgssymbolanimationsettingswidgetbase.h"
|
||||
|
||||
#include "qgis_gui.h"
|
||||
#include "qgis_sip.h"
|
||||
#include "qgspanelwidget.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QgsSymbolAnimationSettings;
|
||||
|
||||
|
||||
/**
|
||||
* \ingroup gui
|
||||
* \brief A widget for customising animation settings for a symbol.
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
class GUI_EXPORT QgsSymbolAnimationSettingsWidget: public QgsPanelWidget, private Ui::QgsSymbolAnimationSettingsWidgetBase
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
//! Constructor for QgsSymbolAnimationSettingsWidget
|
||||
QgsSymbolAnimationSettingsWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr );
|
||||
|
||||
/**
|
||||
* Sets the animation \a settings to show in the widget.
|
||||
*
|
||||
* \see animationSettings()
|
||||
*/
|
||||
void setAnimationSettings( const QgsSymbolAnimationSettings &settings );
|
||||
|
||||
/**
|
||||
* Returns the animation settings as defined in the widget.
|
||||
*
|
||||
* \see setAnimationSettings()
|
||||
*/
|
||||
QgsSymbolAnimationSettings animationSettings() const;
|
||||
|
||||
private:
|
||||
|
||||
bool mBlockUpdates = false;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* \ingroup gui
|
||||
* \brief A dialog for customising animation settings for a symbol.
|
||||
* \since QGIS 3.26
|
||||
*/
|
||||
class GUI_EXPORT QgsSymbolAnimationSettingsDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
//! Constructor for QgsSymbolAnimationSettingsDialog
|
||||
QgsSymbolAnimationSettingsDialog( QWidget *parent SIP_TRANSFERTHIS = nullptr, Qt::WindowFlags f = Qt::WindowFlags() );
|
||||
|
||||
/**
|
||||
* Sets the animation \a settings to show in the dialog.
|
||||
*
|
||||
* \see animationSettings()
|
||||
*/
|
||||
void setAnimationSettings( const QgsSymbolAnimationSettings &settings );
|
||||
|
||||
/**
|
||||
* Returns the animation settings as defined in the dialog.
|
||||
*
|
||||
* \see setAnimationSettings()
|
||||
*/
|
||||
QgsSymbolAnimationSettings animationSettings() const;
|
||||
|
||||
private:
|
||||
|
||||
QgsSymbolAnimationSettingsWidget *mWidget = nullptr;
|
||||
|
||||
};
|
||||
|
||||
#endif // QGSSYMBOLANIMATIONSETTINGSWIDGET_H
|
||||
@ -90,6 +90,7 @@ QgsExpressionContext QgsSymbolLayerWidget::createExpressionContext() const
|
||||
expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_layer_index" ), 1, true ) );
|
||||
expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_row" ), 1, true ) );
|
||||
expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_marker_column" ), 1, true ) );
|
||||
expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QStringLiteral( "symbol_frame" ), 1, true ) );
|
||||
|
||||
// additional scopes
|
||||
const auto constAdditionalExpressionContextScopes = mContext.additionalExpressionContextScopes();
|
||||
@ -107,7 +108,7 @@ QgsExpressionContext QgsSymbolLayerWidget::createExpressionContext() const
|
||||
<< QgsExpressionContext::EXPR_GEOMETRY_RING_NUM
|
||||
<< QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT << QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM
|
||||
<< QgsExpressionContext::EXPR_CLUSTER_COLOR << QgsExpressionContext::EXPR_CLUSTER_SIZE
|
||||
<< QStringLiteral( "symbol_layer_count" ) << QStringLiteral( "symbol_layer_index" );
|
||||
<< QStringLiteral( "symbol_layer_count" ) << QStringLiteral( "symbol_layer_index" ) << QStringLiteral( "symbol_frame" );
|
||||
|
||||
|
||||
if ( expContext.hasVariable( QStringLiteral( "zoom_level" ) ) )
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
#include "qgsmarkersymbol.h"
|
||||
#include "qgslinesymbol.h"
|
||||
#include "qgsfillsymbol.h"
|
||||
#include "qgssymbolanimationsettingswidget.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
||||
@ -50,7 +51,8 @@ QgsSymbolsListWidget::QgsSymbolsListWidget( QgsSymbol *symbol, QgsStyle *style,
|
||||
mStandardizeRingsAction = new QAction( tr( "Force Right-Hand-Rule Orientation" ), this );
|
||||
mStandardizeRingsAction->setCheckable( true );
|
||||
connect( mStandardizeRingsAction, &QAction::toggled, this, &QgsSymbolsListWidget::forceRHRToggled );
|
||||
|
||||
mAnimationSettingsAction = new QAction( tr( "Animation Settings…" ), this );
|
||||
connect( mAnimationSettingsAction, &QAction::triggered, this, &QgsSymbolsListWidget::showAnimationSettings );
|
||||
|
||||
// select correct page in stacked widget
|
||||
QgsPropertyOverrideButton *opacityDDBtn = nullptr;
|
||||
@ -136,6 +138,7 @@ QgsSymbolsListWidget::~QgsSymbolsListWidget()
|
||||
// The menu can be passed in the constructor, so may live longer than this widget
|
||||
mStyleItemsListWidget->advancedMenu()->removeAction( mClipFeaturesAction );
|
||||
mStyleItemsListWidget->advancedMenu()->removeAction( mStandardizeRingsAction );
|
||||
mStyleItemsListWidget->advancedMenu()->removeAction( mAnimationSettingsAction );
|
||||
}
|
||||
|
||||
void QgsSymbolsListWidget::registerDataDefinedButton( QgsPropertyOverrideButton *button, QgsSymbolLayer::Property key )
|
||||
@ -260,6 +263,32 @@ void QgsSymbolsListWidget::forceRHRToggled( bool checked )
|
||||
emit changed();
|
||||
}
|
||||
|
||||
void QgsSymbolsListWidget::showAnimationSettings()
|
||||
{
|
||||
QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this );
|
||||
if ( panel && panel->dockMode() )
|
||||
{
|
||||
QgsSymbolAnimationSettingsWidget *widget = new QgsSymbolAnimationSettingsWidget( panel );
|
||||
widget->setPanelTitle( tr( "Animation Settings" ) );
|
||||
widget->setAnimationSettings( mSymbol->animationSettings() );
|
||||
connect( widget, &QgsPanelWidget::widgetChanged, this, [ this, widget ]()
|
||||
{
|
||||
mSymbol->setAnimationSettings( widget->animationSettings() );
|
||||
emit changed();
|
||||
} );
|
||||
panel->openPanel( widget );
|
||||
return;
|
||||
}
|
||||
|
||||
QgsSymbolAnimationSettingsDialog d( this );
|
||||
d.setAnimationSettings( mSymbol->animationSettings() );
|
||||
if ( d.exec() == QDialog::Accepted )
|
||||
{
|
||||
mSymbol->setAnimationSettings( d.animationSettings() );
|
||||
emit changed();
|
||||
}
|
||||
}
|
||||
|
||||
void QgsSymbolsListWidget::saveSymbol()
|
||||
{
|
||||
if ( !mStyle )
|
||||
@ -474,7 +503,8 @@ QgsExpressionContext QgsSymbolsListWidget::createExpressionContext() const
|
||||
<< QgsExpressionContext::EXPR_GEOMETRY_RING_NUM
|
||||
<< QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT << QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM
|
||||
<< QgsExpressionContext::EXPR_CLUSTER_COLOR << QgsExpressionContext::EXPR_CLUSTER_SIZE
|
||||
<< QStringLiteral( "symbol_layer_count" ) << QStringLiteral( "symbol_layer_index" ) );
|
||||
<< QStringLiteral( "symbol_layer_count" ) << QStringLiteral( "symbol_layer_index" )
|
||||
<< QStringLiteral( "symbol_frame" ) );
|
||||
|
||||
return expContext;
|
||||
}
|
||||
@ -546,6 +576,10 @@ void QgsSymbolsListWidget::updateSymbolInfo()
|
||||
{
|
||||
mStyleItemsListWidget->advancedMenu()->removeAction( action );
|
||||
}
|
||||
else if ( mAnimationSettingsAction->text() == action->text() )
|
||||
{
|
||||
mStyleItemsListWidget->advancedMenu()->removeAction( action );
|
||||
}
|
||||
}
|
||||
|
||||
if ( mSymbol->type() == Qgis::SymbolType::Line || mSymbol->type() == Qgis::SymbolType::Fill )
|
||||
@ -557,6 +591,7 @@ void QgsSymbolsListWidget::updateSymbolInfo()
|
||||
{
|
||||
mStyleItemsListWidget->advancedMenu()->addAction( mStandardizeRingsAction );
|
||||
}
|
||||
mStyleItemsListWidget->advancedMenu()->addAction( mAnimationSettingsAction );
|
||||
|
||||
mStyleItemsListWidget->showAdvancedButton( mAdvancedMenu || !mStyleItemsListWidget->advancedMenu()->isEmpty() );
|
||||
|
||||
|
||||
@ -96,6 +96,7 @@ class GUI_EXPORT QgsSymbolsListWidget : public QWidget, private Ui::SymbolsListW
|
||||
void createAuxiliaryField();
|
||||
void createSymbolAuxiliaryField();
|
||||
void forceRHRToggled( bool checked );
|
||||
void showAnimationSettings();
|
||||
void saveSymbol();
|
||||
void updateSymbolDataDefinedProperty();
|
||||
|
||||
@ -109,6 +110,7 @@ class GUI_EXPORT QgsSymbolsListWidget : public QWidget, private Ui::SymbolsListW
|
||||
QMenu *mAdvancedMenu = nullptr;
|
||||
QAction *mClipFeaturesAction = nullptr;
|
||||
QAction *mStandardizeRingsAction = nullptr;
|
||||
QAction *mAnimationSettingsAction = nullptr;
|
||||
QgsVectorLayer *mLayer = nullptr;
|
||||
QgsMapCanvas *mMapCanvas = nullptr;
|
||||
|
||||
|
||||
92
src/ui/symbollayer/qgssymbolanimationsettingswidgetbase.ui
Normal file
92
src/ui/symbollayer/qgssymbolanimationsettingswidgetbase.ui
Normal file
@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QgsSymbolAnimationSettingsWidgetBase</class>
|
||||
<widget class="QgsPanelWidget" name="QgsSymbolAnimationSettingsWidgetBase">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>343</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Dash Space Pattern</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="mIsAnimatedGroup">
|
||||
<property name="title">
|
||||
<string>Is Animated</string>
|
||||
</property>
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2" columnstretch="1,2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Frame rate</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QgsDoubleSpinBox" name="mFrameRateSpin">
|
||||
<property name="suffix">
|
||||
<string> fps</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.010000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1000.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>10.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>The symbol will be animated using the specified frame rate. The <code>@symbol_frame</code> variable can be used in symbol property expressions in order to dynamically alter the symbol's appearance over time.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>QgsPanelWidget</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>qgspanelwidget.h</header>
|
||||
<container>1</container>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QgsDoubleSpinBox</class>
|
||||
<extends>QDoubleSpinBox</extends>
|
||||
<header>qgsdoublespinbox.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@ -636,6 +636,28 @@ class TestQgsSymbol(unittest.TestCase):
|
||||
|
||||
assert self.imageCheck('Reprojection errors linestring', 'reprojection_errors_linestring', image)
|
||||
|
||||
def test_animation_settings(self):
|
||||
s = QgsFillSymbol()
|
||||
self.assertFalse(s.animationSettings().isAnimated())
|
||||
s.animationSettings().setIsAnimated(True)
|
||||
self.assertTrue(s.animationSettings().isAnimated())
|
||||
|
||||
s.animationSettings().setFrameRate(30)
|
||||
self.assertEqual(s.animationSettings().frameRate(), 30)
|
||||
|
||||
s.setForceRHR(True)
|
||||
doc = QDomDocument()
|
||||
context = QgsReadWriteContext()
|
||||
element = QgsSymbolLayerUtils.saveSymbol('test', s, doc, context)
|
||||
|
||||
s2 = QgsSymbolLayerUtils.loadSymbol(element, context)
|
||||
self.assertTrue(s2.animationSettings().isAnimated())
|
||||
self.assertEqual(s2.animationSettings().frameRate(), 30)
|
||||
|
||||
s3 = s2.clone()
|
||||
self.assertTrue(s3.animationSettings().isAnimated())
|
||||
self.assertEqual(s3.animationSettings().frameRate(), 30)
|
||||
|
||||
def renderCollection(self, geom, symbol):
|
||||
f = QgsFeature()
|
||||
f.setGeometry(geom)
|
||||
@ -926,7 +948,24 @@ class TestQgsMarkerSymbol(unittest.TestCase):
|
||||
rendered_image = self.renderGeometry(s, g, QgsMapSettings.DrawSymbolBounds)
|
||||
self.assertTrue(self.imageCheck('marker_bounds_layer_disabled', 'marker_bounds_layer_disabled', rendered_image))
|
||||
|
||||
def renderGeometry(self, symbol, geom, flags=QgsMapSettings.Flags()):
|
||||
def test_animation(self):
|
||||
markerSymbol = QgsMarkerSymbol()
|
||||
markerSymbol.deleteSymbolLayer(0)
|
||||
markerSymbol.appendSymbolLayer(
|
||||
QgsSimpleMarkerSymbolLayer(QgsSimpleMarkerSymbolLayerBase.Triangle, color=QColor(255, 0, 0),
|
||||
strokeColor=QColor(0, 255, 0), size=10, angle=0))
|
||||
markerSymbol[0].setStrokeStyle(Qt.NoPen)
|
||||
|
||||
markerSymbol.animationSettings().setIsAnimated(True)
|
||||
|
||||
markerSymbol[0].setDataDefinedProperty(QgsSymbolLayer.PropertyAngle, QgsProperty.fromExpression('@symbol_frame * 90'))
|
||||
g = QgsGeometry.fromWkt('Point(1 1)')
|
||||
rendered_image = self.renderGeometry(markerSymbol, g, frame=0)
|
||||
self.assertTrue(self.imageCheck('animated_frame1', 'animated_frame1', rendered_image))
|
||||
rendered_image = self.renderGeometry(markerSymbol, g, frame=1)
|
||||
self.assertTrue(self.imageCheck('animated_frame2', 'animated_frame2', rendered_image))
|
||||
|
||||
def renderGeometry(self, symbol, geom, flags=QgsMapSettings.Flags(), frame=None):
|
||||
f = QgsFeature()
|
||||
f.setGeometry(geom)
|
||||
|
||||
@ -945,6 +984,9 @@ class TestQgsMarkerSymbol(unittest.TestCase):
|
||||
|
||||
ms.setExtent(extent)
|
||||
ms.setOutputSize(image.size())
|
||||
if frame is not None:
|
||||
ms.setFrameRate(10)
|
||||
ms.setCurrentFrame(frame)
|
||||
context = QgsRenderContext.fromMapSettings(ms)
|
||||
context.setPainter(painter)
|
||||
context.setScaleFactor(96 / 25.4) # 96 DPI
|
||||
|
||||
@ -635,6 +635,14 @@ class PyQgsSymbolLayerUtils(unittest.TestCase):
|
||||
renderer = QgsSingleSymbolRenderer(marker_symbol)
|
||||
self.assertEqual(QgsSymbolLayerUtils.rendererFrameRate(renderer), 60)
|
||||
|
||||
s = QgsMarkerSymbol()
|
||||
renderer = QgsSingleSymbolRenderer(s.clone())
|
||||
self.assertEqual(QgsSymbolLayerUtils.rendererFrameRate(renderer), -1)
|
||||
s.animationSettings().setIsAnimated(True)
|
||||
s.animationSettings().setFrameRate(30)
|
||||
renderer = QgsSingleSymbolRenderer(s.clone())
|
||||
self.assertEqual(QgsSymbolLayerUtils.rendererFrameRate(renderer), 30)
|
||||
|
||||
def imageCheck(self, name, reference_image, image):
|
||||
self.report += "<h2>Render {}</h2>\n".format(name)
|
||||
temp_dir = QDir.tempPath() + '/'
|
||||
|
||||
BIN
tests/testdata/control_images/symbol/expected_animated_frame1/expected_animated_frame1.png
vendored
Normal file
BIN
tests/testdata/control_images/symbol/expected_animated_frame1/expected_animated_frame1.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 424 B |
BIN
tests/testdata/control_images/symbol/expected_animated_frame2/expected_animated_frame2.png
vendored
Normal file
BIN
tests/testdata/control_images/symbol/expected_animated_frame2/expected_animated_frame2.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 482 B |
Loading…
x
Reference in New Issue
Block a user