Skip to content

Commit

Permalink
Improve UX for modify annotation tool
Browse files Browse the repository at this point in the history
- When a selected item has bounds which depend on the map scale,
update the selection handles after a canvas move/zoom occurs. Avoids
showing ugly outdated bounding box for selected item
- If no item is selected, recheck for potential hovered items
after a canvas pan/zoom occurs. This correctly updates the hovered
item bounds if there's now an item sitting under the mouse cursor
  • Loading branch information
nyalldawson committed Aug 7, 2024
1 parent 2b27f0c commit e1bfad1
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 68 deletions.
157 changes: 89 additions & 68 deletions src/gui/annotations/qgsmaptoolmodifyannotation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ void QgsMapToolModifyAnnotation::deactivate()

void QgsMapToolModifyAnnotation::cadCanvasMoveEvent( QgsMapMouseEvent *event )
{
mLastHoverPoint = event->originalPixelPoint();
event->snapPoint();
mSnapIndicator->setMatch( event->mapPointMatch() );

Expand All @@ -137,73 +138,7 @@ void QgsMapToolModifyAnnotation::cadCanvasMoveEvent( QgsMapMouseEvent *event )
{
case Action::NoAction:
{
QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() );
searchRect.grow( searchRadiusMU( canvas() ) );

const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
if ( !renderedItemResults )
{
clearHoveredItem();
return;
}

const QList<const QgsRenderedAnnotationItemDetails *> items = renderedItemResults->renderedAnnotationItemsInBounds( searchRect );
if ( items.empty() )
{
clearHoveredItem();
return;
}

// find closest item
QgsRectangle itemBounds;
const QgsRenderedAnnotationItemDetails *closestItem = findClosestItemToPoint( mapPoint, items, itemBounds );
if ( !closestItem )
{
clearHoveredItem();
return;
}

if ( closestItem->itemId() != mHoveredItemId || closestItem->layerId() != mHoveredItemLayerId )
{
setHoveredItem( closestItem, itemBounds );
}

// track hovered node too!... here we want to identify the closest node to the cursor position
QgsAnnotationItemNode hoveredNode;
if ( closestItem->itemId() == mSelectedItemId && closestItem->layerId() == mSelectedItemLayerId )
{
double currentNodeDistance = std::numeric_limits< double >::max();
mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, &currentNodeDistance, &mapPoint, this]( int index )-> bool
{
const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
const double nodeDistance = thisNode.point().sqrDist( mapPoint );
if ( nodeDistance < currentNodeDistance )
{
hoveredNode = thisNode;
currentNodeDistance = nodeDistance;
}
return true;
} );
}

if ( hoveredNode.point().isEmpty() )
{
// no hovered node
if ( mHoveredNodeRubberBand )
mHoveredNodeRubberBand->hide();
setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor );
}
else
{
if ( !mHoveredNodeRubberBand )
createHoveredNodeBand();

mHoveredNodeRubberBand->reset( Qgis::GeometryType::Point );
mHoveredNodeRubberBand->addPoint( hoveredNode.point() );
mHoveredNodeRubberBand->show();

setCursor( Qt::ArrowCursor );
}
setHoveredItemFromPoint( mapPoint );
break;
}

Expand Down Expand Up @@ -574,7 +509,16 @@ void QgsMapToolModifyAnnotation::keyPressEvent( QKeyEvent *event )

void QgsMapToolModifyAnnotation::onCanvasRefreshed()
{
if ( mRefreshSelectedItemAfterRedraw )
bool needsSelectedItemRefresh = mRefreshSelectedItemAfterRedraw;
if ( QgsAnnotationItem *item = annotationItemFromId( mSelectedItemLayerId, mSelectedItemId ) )
{
if ( item->flags() & Qgis::AnnotationItemFlag::ScaleDependentBoundingBox )
{
needsSelectedItemRefresh = true;
}
}

if ( needsSelectedItemRefresh )
{
const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
if ( !renderedItemResults )
Expand Down Expand Up @@ -604,6 +548,12 @@ void QgsMapToolModifyAnnotation::onCanvasRefreshed()
mSelectedRubberBand->show();
}
}
else
{
// recheck for hovered item at new mouse point
const QgsPointXY mapPoint = canvas()->mapSettings().mapToPixel().toMapCoordinates( mLastHoverPoint );
setHoveredItemFromPoint( mapPoint );
}
mRefreshSelectedItemAfterRedraw = false;
}

Expand Down Expand Up @@ -787,6 +737,77 @@ QgsAnnotationItem *QgsMapToolModifyAnnotation::annotationItemFromId( const QStri
return layer ? layer->item( itemId ) : nullptr;
}

void QgsMapToolModifyAnnotation::setHoveredItemFromPoint( const QgsPointXY &mapPoint )
{
QgsRectangle searchRect = QgsRectangle( mapPoint.x(), mapPoint.y(), mapPoint.x(), mapPoint.y() );
searchRect.grow( searchRadiusMU( canvas() ) );

const QgsRenderedItemResults *renderedItemResults = canvas()->renderedItemResults( false );
if ( !renderedItemResults )
{
clearHoveredItem();
return;
}

const QList<const QgsRenderedAnnotationItemDetails *> items = renderedItemResults->renderedAnnotationItemsInBounds( searchRect );
if ( items.empty() )
{
clearHoveredItem();
return;
}

// find closest item
QgsRectangle itemBounds;
const QgsRenderedAnnotationItemDetails *closestItem = findClosestItemToPoint( mapPoint, items, itemBounds );
if ( !closestItem )
{
clearHoveredItem();
return;
}

if ( closestItem->itemId() != mHoveredItemId || closestItem->layerId() != mHoveredItemLayerId )
{
setHoveredItem( closestItem, itemBounds );
}

// track hovered node too!... here we want to identify the closest node to the cursor position
QgsAnnotationItemNode hoveredNode;
if ( closestItem->itemId() == mSelectedItemId && closestItem->layerId() == mSelectedItemLayerId )
{
double currentNodeDistance = std::numeric_limits< double >::max();
mHoveredItemNodesSpatialIndex->intersects( searchRect, [&hoveredNode, &currentNodeDistance, &mapPoint, this]( int index )-> bool
{
const QgsAnnotationItemNode &thisNode = mHoveredItemNodes.at( index );
const double nodeDistance = thisNode.point().sqrDist( mapPoint );
if ( nodeDistance < currentNodeDistance )
{
hoveredNode = thisNode;
currentNodeDistance = nodeDistance;
}
return true;
} );
}

if ( hoveredNode.point().isEmpty() )
{
// no hovered node
if ( mHoveredNodeRubberBand )
mHoveredNodeRubberBand->hide();
setCursor( mHoveredItemId == mSelectedItemId && mHoveredItemLayerId == mSelectedItemLayerId ? Qt::OpenHandCursor : Qt::ArrowCursor );
}
else
{
if ( !mHoveredNodeRubberBand )
createHoveredNodeBand();

mHoveredNodeRubberBand->reset( Qgis::GeometryType::Point );
mHoveredNodeRubberBand->addPoint( hoveredNode.point() );
mHoveredNodeRubberBand->show();

setCursor( Qt::ArrowCursor );
}
}

void QgsMapToolModifyAnnotation::clearHoveredItem()
{
if ( mHoverRubberBand )
Expand Down
2 changes: 2 additions & 0 deletions src/gui/annotations/qgsmaptoolmodifyannotation.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ class GUI_EXPORT QgsMapToolModifyAnnotation : public QgsMapToolAdvancedDigitizin
QgsAnnotationLayer *annotationLayerFromId( const QString &layerId );
QgsAnnotationItem *annotationItemFromId( const QString &layerId, const QString &itemId );

void setHoveredItemFromPoint( const QgsPointXY &mapPoint );
void setHoveredItem( const QgsRenderedAnnotationItemDetails *item, const QgsRectangle &itemMapBounds );

/**
Expand All @@ -114,6 +115,7 @@ class GUI_EXPORT QgsMapToolModifyAnnotation : public QgsMapToolAdvancedDigitizin
QObjectUniquePtr<QgsRubberBand> mSelectedRubberBand;
QObjectUniquePtr<QgsRubberBand> mTemporaryRubberBand;

QPoint mLastHoverPoint;
QString mHoveredItemId;
QString mHoveredItemLayerId;
QgsRectangle mHoveredItemBounds;
Expand Down

0 comments on commit e1bfad1

Please sign in to comment.