diff --git a/src/QuickEditor/QuickEditor.h b/src/QuickEditor/QuickEditor.h --- a/src/QuickEditor/QuickEditor.h +++ b/src/QuickEditor/QuickEditor.h @@ -71,18 +71,20 @@ void mouseDoubleClickEvent(QMouseEvent* event) override; void paintEvent(QPaintEvent*) override; void drawBottomHelpText(QPainter& painter); - void drawDragHandles(QPainter& painter); + void drawDragHandles(QPainter& painter, qreal minEdgeLength); void drawMagnifier(QPainter& painter); void drawMidHelpText(QPainter& painter); - void drawSelectionSizeTooltip(QPainter& painter); + void drawSelectionSizeTooltip(QPainter& painter, bool dragHandlesVisible); void setBottomHelpText(); void layoutBottomHelpText(); void setMouseCursor(const QPointF& pos); MouseState mouseLocation(const QPointF& pos); static const qreal mouseAreaSize; - static const qreal cornerHandleRadius; - static const qreal midHandleRadius; + static const qreal handleRadius; + static const qreal increaseDragAreaFactor; + static const int minSpacingBetweenHandles; + static const int selectionSizeThreshold; static const int selectionBoxPaddingX; @@ -131,7 +133,10 @@ QRect mPrimaryScreenGeo; int mbottomHelpLength; - Q_SIGNALS: + //Midpoints of handles + QVector mHandlePositions = QVector {8}; + +Q_SIGNALS: void grabDone(const QPixmap &thePixmap); void grabCancelled(); diff --git a/src/QuickEditor/QuickEditor.cpp b/src/QuickEditor/QuickEditor.cpp --- a/src/QuickEditor/QuickEditor.cpp +++ b/src/QuickEditor/QuickEditor.cpp @@ -20,18 +20,21 @@ #include #include #include +#include #include "QuickEditor.h" #include "SpectacleConfig.h" const qreal QuickEditor::mouseAreaSize = 20.0; -const qreal QuickEditor::cornerHandleRadius = 8.0; -const qreal QuickEditor::midHandleRadius = 5.0; +const qreal QuickEditor::handleRadius = 7.5; +const qreal QuickEditor::increaseDragAreaFactor = 2.0; +const int QuickEditor::minSpacingBetweenHandles = 20; + const int QuickEditor::selectionSizeThreshold = 100; const int QuickEditor::selectionBoxPaddingX = 5; const int QuickEditor::selectionBoxPaddingY = 4; -const int QuickEditor::selectionBoxMarginY = 2; +const int QuickEditor::selectionBoxMarginY = 5; bool QuickEditor::bottomHelpTextPrepared = false; const int QuickEditor::bottomHelpBoxPaddingX = 12; @@ -288,7 +291,7 @@ void QuickEditor::mousePressEvent(QMouseEvent* event) { if (event->button() & Qt::LeftButton) { - /* NOTE Workaround for Bug 407843 + /* NOTE Workaround for Bug 407843 * If we show the selection Widget when a right click menu is open we lose focus on X. * When the user clicks we get the mouse back. We can only grab the keyboard if we already * have mouse focus. So just grab it undconditionally here. @@ -436,7 +439,7 @@ if(mMouseDragState == MouseState::Inside) { setCursor(Qt::OpenHandCursor); } - else if(mMouseDragState == MouseState::Outside && mReleaseToCapture) { + else if (mMouseDragState == MouseState::Outside && mReleaseToCapture) { event->accept(); mMouseDragState = MouseState::None; return acceptSelection(); @@ -481,15 +484,14 @@ painter.fillRect(rect, mMaskColor); } - drawSelectionSizeTooltip(painter); - if (mMouseDragState == MouseState::None) { // mouse is up - if ((mSelection.width() > 20) && (mSelection.height() > 20)) { - drawDragHandles(painter); - } - + bool dragHandlesVisible = false; + if (mMouseDragState == MouseState::None) { + dragHandlesVisible = true; + drawDragHandles(painter, qMin(mSelection.width(), mSelection.height())); } else if (mMagnifierAllowed && (mShowMagnifier ^ mToggleMagnifier)) { drawMagnifier(painter); } + drawSelectionSizeTooltip(painter, dragHandlesVisible); drawBottomHelpText(painter); } else { drawMidHelpText(painter); @@ -607,63 +609,54 @@ } } -void QuickEditor::drawDragHandles(QPainter& painter) +void QuickEditor::drawDragHandles(QPainter &painter, qreal minEdgeLength) { + // rectangular region const qreal left = mSelection.x(); - const qreal width = mSelection.width(); - const qreal centerX = left + width / 2.0; - const qreal right = left + width; - + const qreal centerX = left + mSelection.width() / 2.0; + const qreal right = left + mSelection.width(); const qreal top = mSelection.y(); - const qreal height = mSelection.height(); - const qreal centerY = top + height / 2.0; - const qreal bottom = top + height; - - // start a path - QPainterPath path; - - const qreal cornerHandleDiameter = 2 * cornerHandleRadius; - - // x and y coordinates of handle arcs - const qreal leftHandle = left - cornerHandleRadius; - const qreal topHandle = top - cornerHandleRadius; - const qreal rightHandle = right - cornerHandleRadius; - const qreal bottomHandle = bottom - cornerHandleRadius; - const qreal centerHandleX = centerX - midHandleRadius; - const qreal centerHandleY = centerY - midHandleRadius; - - // top-left handle - path.moveTo(left, top); - path.arcTo(leftHandle, topHandle, cornerHandleDiameter, cornerHandleDiameter, 0, -90); - - // top-right handle - path.moveTo(right, top); - path.arcTo(rightHandle, topHandle, cornerHandleDiameter, cornerHandleDiameter, 180, 90); - - // bottom-left handle - path.moveTo(left, bottom); - path.arcTo(leftHandle, bottomHandle, cornerHandleDiameter, cornerHandleDiameter, 0, 90); + const qreal centerY = top + mSelection.height() / 2.0; + const qreal bottom = top + mSelection.height(); + + // Find out if there is enough space to display the handles, if not apply a offset + qreal offset = 0; + const qreal minDragHandleSpace = 4 * handleRadius + 2 * minSpacingBetweenHandles; + // rectangle too small: make handles free-floating (priority) + if (minEdgeLength < minDragHandleSpace) { + offset = (minDragHandleSpace - minEdgeLength) / 2.0; + } else { + qreal minDistanceToScreenEdge = qMin(qMin(left - mPrimaryScreenGeo.left(), mPrimaryScreenGeo.right() - right), qMin(top - mPrimaryScreenGeo.top(), mPrimaryScreenGeo.bottom() - bottom)); + // rectangle too close to screen edges: move them inside the rectangle, so they are still visible + if (minDistanceToScreenEdge < handleRadius) { + offset = -(handleRadius - minDistanceToScreenEdge); + } + } + //top-left handle + this->mHandlePositions[0] = QPointF {left - offset, top - offset}; + //top-right handle + this->mHandlePositions[1] = QPointF {right + offset, top - offset}; // bottom-right handle - path.moveTo(right, bottom); - path.arcTo(rightHandle, bottomHandle, cornerHandleDiameter, cornerHandleDiameter, 180, -90); - - const qreal midHandleDiameter = 2 * midHandleRadius; + this->mHandlePositions[2] = QPointF {right + offset, bottom + offset}; + // bottom-left + this->mHandlePositions[3] = QPointF {left - offset, bottom + offset}; // top-center handle - path.moveTo(centerX, top); - path.arcTo(centerHandleX, top - midHandleRadius, midHandleDiameter, midHandleDiameter, 0, -180); - + this->mHandlePositions[4] = QPointF {centerX, top - offset}; // right-center handle - path.moveTo(right, centerY); - path.arcTo(right - midHandleRadius, centerHandleY, midHandleDiameter, midHandleDiameter, 90, 180); - + this->mHandlePositions[5] = QPointF {right + offset, centerY}; // bottom-center handle - path.moveTo(centerX, bottom); - path.arcTo(centerHandleX, bottom - midHandleRadius, midHandleDiameter, midHandleDiameter, 0, 180); - + this->mHandlePositions[6] = QPointF {centerX, bottom + offset}; // left-center handle - path.moveTo(left, centerY); - path.arcTo(left - midHandleRadius, centerHandleY, midHandleDiameter, midHandleDiameter, 90, -180); + this->mHandlePositions[7] = QPointF {left - offset, centerY}; + + // start path + QPainterPath path; + + // add handles to the path + for (const QPointF &handlePosition : this->mHandlePositions) { + path.addEllipse(handlePosition, handleRadius, handleRadius); + } // draw the path painter.fillPath(path, mStrokeColor); @@ -739,7 +732,7 @@ painter.drawText(QRect(pos, textSize.size()), Qt::AlignCenter, mMidHelpText); } -void QuickEditor::drawSelectionSizeTooltip(QPainter &painter) +void QuickEditor::drawSelectionSizeTooltip(QPainter &painter, bool dragHandlesVisible) { // Set the selection size and finds the most appropriate position: // - vertically centered inside the selection if the box is not covering the a large part of selection @@ -761,11 +754,17 @@ // show inside the box selectionBoxY = static_cast(mSelection.y() + (mSelection.height() - selectionSizeTextRect.height()) / 2); } else { - // show on top by default - selectionBoxY = static_cast(mSelection.y() - selectionBoxHeight - selectionBoxMarginY); - if (selectionBoxY < 0) { - // show at the bottom - selectionBoxY = static_cast(mSelection.y() + mSelection.height() + selectionBoxMarginY); + // show on top by default, above the drag Handles if they're visible + if (dragHandlesVisible) { + selectionBoxY = static_cast(mHandlePositions[4].y() - handleRadius - selectionBoxHeight - selectionBoxMarginY); + if (selectionBoxY < 0) { + selectionBoxY = static_cast(mHandlePositions[6].y() + handleRadius + selectionBoxMarginY); + } + } else { + selectionBoxY = static_cast(mSelection.y() - selectionBoxHeight - selectionBoxMarginY); + if (selectionBoxY < 0) { + selectionBoxY = static_cast(mSelection.y() + mSelection.height() + selectionBoxMarginY); + } } } @@ -805,42 +804,28 @@ QuickEditor::MouseState QuickEditor::mouseLocation(const QPointF& pos) { - if (mSelection.contains(pos)) { - const qreal verSize = qMin(mouseAreaSize, mSelection.height() / 2); - const qreal horSize = qMin(mouseAreaSize, mSelection.width() / 2); - - auto withinThreshold = [](const qreal offset, const qreal size) { - return offset <= size && offset >= 0; - }; - - const bool withinTopEdge = withinThreshold(pos.y() - mSelection.top(), verSize); - const bool withinRightEdge = withinThreshold(mSelection.right() - pos.x(), horSize); - const bool withinBottomEdge = !withinTopEdge && withinThreshold(mSelection.bottom() - pos.y(), verSize); - const bool withinLeftEdge = !withinRightEdge && withinThreshold(pos.x() - mSelection.left(), horSize); - - if (withinTopEdge) { - if (withinRightEdge) { - return MouseState::TopRight; - } else if (withinLeftEdge) { - return MouseState::TopLeft; - } else { - return MouseState::Top; - } - } else if (withinBottomEdge) { - if (withinRightEdge) { - return MouseState::BottomRight; - } else if (withinLeftEdge) { - return MouseState::BottomLeft; - } else { - return MouseState::Bottom; - } - } else if (withinRightEdge) { - return MouseState::Right; - } else if (withinLeftEdge) { - return MouseState::Left; - } else { - return MouseState::Inside; - } + auto isPointInsideCircle = [](const QPointF & circleCenter, qreal radius, const QPointF & point) { + return (qPow(point.x() - circleCenter.x(), 2) + qPow(point.y() - circleCenter.y(), 2) <= qPow(radius, 2)) ? true : false; + }; + + if (isPointInsideCircle(mHandlePositions[0], handleRadius * increaseDragAreaFactor, pos)) { + return MouseState::TopLeft; + } else if (isPointInsideCircle(mHandlePositions[1], handleRadius * increaseDragAreaFactor, pos)) { + return MouseState::TopRight; + } else if (isPointInsideCircle(mHandlePositions[2], handleRadius * increaseDragAreaFactor, pos)) { + return MouseState::BottomRight; + } else if (isPointInsideCircle(mHandlePositions[3], handleRadius * increaseDragAreaFactor, pos)) { + return MouseState::BottomLeft; + } else if (isPointInsideCircle(mHandlePositions[4], handleRadius * increaseDragAreaFactor, pos)) { + return MouseState::Top; + } else if (isPointInsideCircle(mHandlePositions[5], handleRadius * increaseDragAreaFactor, pos)) { + return MouseState::Right; + } else if (isPointInsideCircle(mHandlePositions[6], handleRadius * increaseDragAreaFactor, pos)) { + return MouseState::Bottom; + } else if (isPointInsideCircle(mHandlePositions[7], handleRadius * increaseDragAreaFactor, pos)) { + return MouseState::Left; + } else if (mSelection.contains(pos)) { + return MouseState::Inside; } else { return MouseState::Outside; }