Skip to content
Permalink
Browse files

[feature] Add support for rounded corners for balloon callout styles

Allows for visually please rounded corners by exposing a "corner radius"
setting for balloon callouts.
  • Loading branch information
nyalldawson committed Apr 4, 2021
1 parent 026b841 commit d9d0f9a9dd3ded12073054b6dfd44d255d94cc14
@@ -65,6 +65,7 @@ relevant symbology elements to render them.
Orientation,
Margins,
WedgeWidth,
CornerRadius,
};

enum DrawOrder
@@ -987,7 +988,6 @@ Returns the units for the margins between the outside of the callout frame and t
.. seealso:: :py:func:`margins`
%End


double wedgeWidth() const;
%Docstring
Returns the width of the wedge shape at the side it connects with the label.
@@ -1001,7 +1001,7 @@ Units are specified through :py:func:`~QgsBalloonCallout.wedgeWidthUnit`.

void setWedgeWidth( double width );
%Docstring
Returns the ``width`` of the wedge shape at the side it connects with the label.
Sets the ``width`` of the wedge shape at the side it connects with the label.

Units are specified through :py:func:`~QgsBalloonCallout.setWedgeWidthUnit`.

@@ -1050,6 +1050,69 @@ Returns the map unit scale for the wedge width.
.. seealso:: :py:func:`wedgeWidth`
%End

double cornerRadius() const;
%Docstring
Returns the corner radius of the balloon shapes.

Units are specified through :py:func:`~QgsBalloonCallout.wedgeWidthUnit`.

.. seealso:: :py:func:`setCornerRadius`

.. seealso:: :py:func:`cornerRadiusUnit`
%End

void setCornerRadius( double radius );
%Docstring
Sets the ``radius`` of the corners for the balloon shapes.

Units are specified through :py:func:`~QgsBalloonCallout.setCornerRadiusUnit`.

.. seealso:: :py:func:`cornerRadius`

.. seealso:: :py:func:`setCornerRadiusUnit`
%End

void setCornerRadiusUnit( QgsUnitTypes::RenderUnit unit );
%Docstring
Sets the ``unit`` for the corner radius.

.. seealso:: :py:func:`cornerRadiusUnit`

.. seealso:: :py:func:`setCornerRadius`
%End

QgsUnitTypes::RenderUnit cornerRadiusUnit() const;
%Docstring
Returns the units for the corner radius.

.. seealso:: :py:func:`setCornerRadiusUnit`

.. seealso:: :py:func:`cornerRadius`
%End

void setCornerRadiusMapUnitScale( const QgsMapUnitScale &scale );
%Docstring
Sets the map unit ``scale`` for the corner radius.

.. seealso:: :py:func:`cornerRadiusMapUnitScale`

.. seealso:: :py:func:`setCornerRadiusUnit`

.. seealso:: :py:func:`setCornerRadius`
%End

const QgsMapUnitScale &cornerRadiusMapUnitScale() const;
%Docstring
Returns the map unit scale for the corner radius.

.. seealso:: :py:func:`setCornerRadiusMapUnitScale`

.. seealso:: :py:func:`cornerRadiusUnit`

.. seealso:: :py:func:`cornerRadius`
%End


protected:
virtual void draw( QgsRenderContext &context, const QRectF &bodyBoundingBox, const double angle, const QgsGeometry &anchor, QgsCallout::QgsCalloutContext &calloutContext );

@@ -63,6 +63,7 @@ void QgsCallout::initPropertyDefinitions()
QgsCallout::Margins, QgsPropertyDefinition( "Margins", QgsPropertyDefinition::DataTypeString, QObject::tr( "Margins" ), QObject::tr( "string of four doubles '<b>top,right,bottom,left</b>' or array of doubles <b>[top, right, bottom, left]</b>" ) )
},
{ QgsCallout::WedgeWidth, QgsPropertyDefinition( "WedgeWidth", QObject::tr( "Wedge width" ), QgsPropertyDefinition::DoublePositive, origin ) },
{ QgsCallout::CornerRadius, QgsPropertyDefinition( "CornerRadius", QObject::tr( "Corner radius" ), QgsPropertyDefinition::DoublePositive, origin ) },
};
}

@@ -1021,6 +1022,9 @@ QgsBalloonCallout::QgsBalloonCallout( const QgsBalloonCallout &other )
, mWedgeWidth( other.mWedgeWidth )
, mWedgeWidthUnit( other.mWedgeWidthUnit )
, mWedgeWidthScale( other.mWedgeWidthScale )
, mCornerRadius( other.mCornerRadius )
, mCornerRadiusUnit( other.mCornerRadiusUnit )
, mCornerRadiusScale( other.mCornerRadiusScale )
{

}
@@ -1062,6 +1066,10 @@ QVariantMap QgsBalloonCallout::properties( const QgsReadWriteContext &context )
props[ QStringLiteral( "wedgeWidthUnit" ) ] = QgsUnitTypes::encodeUnit( mWedgeWidthUnit );
props[ QStringLiteral( "wedgeWidthMapUnitScale" ) ] = QgsSymbolLayerUtils::encodeMapUnitScale( mWedgeWidthScale );

props[ QStringLiteral( "cornerRadius" ) ] = mCornerRadius;
props[ QStringLiteral( "cornerRadiusUnit" ) ] = QgsUnitTypes::encodeUnit( mCornerRadiusUnit );
props[ QStringLiteral( "cornerRadiusMapUnitScale" ) ] = QgsSymbolLayerUtils::encodeMapUnitScale( mCornerRadiusScale );

return props;
}

@@ -1087,6 +1095,10 @@ void QgsBalloonCallout::readProperties( const QVariantMap &props, const QgsReadW
mWedgeWidth = props.value( QStringLiteral( "wedgeWidth" ), 2.64 ).toDouble();
mWedgeWidthUnit = QgsUnitTypes::decodeRenderUnit( props.value( QStringLiteral( "wedgeWidthUnit" ) ).toString() );
mWedgeWidthScale = QgsSymbolLayerUtils::decodeMapUnitScale( props.value( QStringLiteral( "wedgeWidthMapUnitScale" ) ).toString() );

mCornerRadius = props.value( QStringLiteral( "cornerRadius" ), 0 ).toDouble();
mCornerRadiusUnit = QgsUnitTypes::decodeRenderUnit( props.value( QStringLiteral( "cornerRadiusUnit" ) ).toString() );
mCornerRadiusScale = QgsSymbolLayerUtils::decodeMapUnitScale( props.value( QStringLiteral( "cornerRadiusMapUnitScale" ) ).toString() );
}

void QgsBalloonCallout::startRender( QgsRenderContext &context )
@@ -1181,6 +1193,14 @@ QPolygonF QgsBalloonCallout::getPoints( QgsRenderContext &context, QgsPointXY or
}
segmentPointWidth = context.convertToPainterUnits( segmentPointWidth, mWedgeWidthUnit, mWedgeWidthScale );

double cornerRadius = mCornerRadius;
if ( dataDefinedProperties().isActive( QgsCallout::CornerRadius ) )
{
context.expressionContext().setOriginalValueVariable( cornerRadius );
cornerRadius = dataDefinedProperties().valueAsDouble( QgsCallout::CornerRadius, context.expressionContext(), cornerRadius );
}
cornerRadius = context.convertToPainterUnits( cornerRadius, mCornerRadiusUnit, mCornerRadiusScale );

double left = mMargins.left();
double right = mMargins.right();
double top = mMargins.top();
@@ -1247,10 +1267,14 @@ QPolygonF QgsBalloonCallout::getPoints( QgsRenderContext &context, QgsPointXY or
rect.width() + marginLeft + marginRight,
rect.height() - marginTop - marginBottom );

// IMPORTANT -- check for degenerate height is >=0, because QRectF are not normalized and we are using painter
// IMPORTANT -- check for degenerate height is sometimes >=0, because QRectF are not normalized and we are using painter
// coordinates with descending vertical axis!
if ( expandedRect.width() <= 0 || expandedRect.height() >= 0 )
if ( expandedRect.width() <= 0 || ( rect.height() < 0 && expandedRect.height() >= 0 ) || ( rect.height() > 0 && expandedRect.height() <= 0 ) )
return QPolygonF();

return QgsShapeGenerator::createBalloon( origin, expandedRect, segmentPointWidth );
const QPainterPath path = QgsShapeGenerator::createBalloon( origin, expandedRect, segmentPointWidth, cornerRadius );
QTransform t = QTransform::fromScale( 100, 100 );
QTransform ti = t.inverted();
QPolygonF poly = path.toFillPolygon( t );
return ti.map( poly );
}
@@ -93,6 +93,7 @@ class CORE_EXPORT QgsCallout
Orientation, //!< Orientation of curved line callouts (since QGIS 3.20)
Margins, //!< Margin from text (since QGIS 3.20)
WedgeWidth, //!< Balloon callout wedge width (since QGIS 3.20)
CornerRadius, //!< Balloon callout corner radius (since QGIS 3.20)
};

//! Options for draw order (stacking) of callouts
@@ -991,7 +992,6 @@ class CORE_EXPORT QgsBalloonCallout : public QgsCallout
*/
QgsUnitTypes::RenderUnit marginsUnit() const { return mMarginUnit; }


/**
* Returns the width of the wedge shape at the side it connects with the label.
*
@@ -1003,7 +1003,7 @@ class CORE_EXPORT QgsBalloonCallout : public QgsCallout
double wedgeWidth() const { return mWedgeWidth; }

/**
* Returns the \a width of the wedge shape at the side it connects with the label.
* Sets the \a width of the wedge shape at the side it connects with the label.
*
* Units are specified through setWedgeWidthUnit().
*
@@ -1046,6 +1046,61 @@ class CORE_EXPORT QgsBalloonCallout : public QgsCallout
*/
const QgsMapUnitScale &wedgeWidthMapUnitScale() const { return mWedgeWidthScale; }

/**
* Returns the corner radius of the balloon shapes.
*
* Units are specified through wedgeWidthUnit().
*
* \see setCornerRadius()
* \see cornerRadiusUnit()
*/
double cornerRadius() const { return mCornerRadius; }

/**
* Sets the \a radius of the corners for the balloon shapes.
*
* Units are specified through setCornerRadiusUnit().
*
* \see cornerRadius()
* \see setCornerRadiusUnit()
*/
void setCornerRadius( double radius ) { mCornerRadius = radius; }

/**
* Sets the \a unit for the corner radius.
*
* \see cornerRadiusUnit()
* \see setCornerRadius()
*/
void setCornerRadiusUnit( QgsUnitTypes::RenderUnit unit ) { mCornerRadiusUnit = unit; }

/**
* Returns the units for the corner radius.
*
* \see setCornerRadiusUnit()
* \see cornerRadius()
*/
QgsUnitTypes::RenderUnit cornerRadiusUnit() const { return mCornerRadiusUnit; }

/**
* Sets the map unit \a scale for the corner radius.
*
* \see cornerRadiusMapUnitScale()
* \see setCornerRadiusUnit()
* \see setCornerRadius()
*/
void setCornerRadiusMapUnitScale( const QgsMapUnitScale &scale ) { mCornerRadiusScale = scale; }

/**
* Returns the map unit scale for the corner radius.
*
* \see setCornerRadiusMapUnitScale()
* \see cornerRadiusUnit()
* \see cornerRadius()
*/
const QgsMapUnitScale &cornerRadiusMapUnitScale() const { return mCornerRadiusScale; }


protected:
void draw( QgsRenderContext &context, const QRectF &bodyBoundingBox, const double angle, const QgsGeometry &anchor, QgsCallout::QgsCalloutContext &calloutContext ) override;

@@ -1071,6 +1126,10 @@ class CORE_EXPORT QgsBalloonCallout : public QgsCallout
QgsUnitTypes::RenderUnit mWedgeWidthUnit = QgsUnitTypes::RenderMillimeters;
QgsMapUnitScale mWedgeWidthScale;

double mCornerRadius = 0.0;
QgsUnitTypes::RenderUnit mCornerRadiusUnit = QgsUnitTypes::RenderMillimeters;
QgsMapUnitScale mCornerRadiusScale;

};


0 comments on commit d9d0f9a

Please sign in to comment.