123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include "RulerWidget.h"
- #include "CanvasHelpers.h"
- #include "GuideHelpers.h"
- #include "QtHelpers.h"
- #include "ViewportAddGuideInteraction.h"
- #include <LyShine/Bus/UiEditorCanvasBus.h>
- #include <QPainter>
- #include <QMouseEvent>
- RulerWidget::RulerWidget(Orientation orientation, QWidget* parent, EditorWindow* editorWindow)
- : QWidget(parent)
- , m_orientation(orientation)
- , m_cursorPos(0.0f)
- , m_editorWindow(editorWindow)
- {
- // needed so we can cancel interaction on loss of focus
- setFocusPolicy(Qt::StrongFocus);
- }
- void RulerWidget::SetCursorPos(const QPoint& pos)
- {
- // store the cursor value to use when painting the ruler
- QPoint localCursorPos = mapFromGlobal(pos);
- m_cursorPos = aznumeric_cast<float>((m_orientation == Orientation::Horizontal) ? localCursorPos.x() : localCursorPos.y());
- update();
- }
- void RulerWidget::DrawForViewport(Draw2dHelper& draw2d)
- {
- // if there is an interaction in progress then draw the visual aids for the interaction
- if (m_dragInteraction)
- {
- m_dragInteraction->Render(draw2d);
- }
- }
- int RulerWidget::GetRulerBreadth()
- {
- // This is how wide the rulers are. It is possible that at some point it could be configurable or
- // computed from some other UI or stylesheet setting.
- const int rulerBreadth = 16;
- return rulerBreadth;
- }
- void RulerWidget::paintEvent([[maybe_unused]] QPaintEvent* event)
- {
- // Note: If the ruler is hidden it will have a zero width or height. In this case Qt never even calls paintEvent
- // get the scale and translation of the viewport
- const ViewportInteraction::TranslationAndScale& translationAndScale = m_editorWindow->GetViewport()->GetViewportInteraction()->GetCanvasViewportMatrixProps();
- float scale = translationAndScale.scale;
- float translation = (m_orientation == Orientation::Horizontal) ? translationAndScale.translation.GetX() : translationAndScale.translation.GetY();
- // Convert back to qt widget coords for painting
- float dpiScaleFactor = m_editorWindow->GetViewport()->WidgetToViewportFactor();
- if (dpiScaleFactor != 0.0f)
- {
- scale /= dpiScaleFactor;
- translation /= dpiScaleFactor;
- }
- // If the viewport is really small then scale can be zero (or very close) which would cause a divide by zero in later math so we just don't paint anything
- const float epsilon = 0.00001f;
- if (scale < epsilon)
- {
- return;
- }
- // Create a painter for doing the drawing
- QPainter painter(this);
- painter.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing);
- QRectF rulerRect = rect();
-
- // We could fill the rect here if we wanted the ruler background to be a diferent color to the default
- // e.g.: painter.fillRect(rulerRect, QColor(30,35,40));
- // Draw the tick marks and number labels
- DrawTickMarksWithLabels(&painter, rulerRect, translation, scale);
- // Indicate the position of the mouse on the rulers
- DrawCursorPos(&painter, rulerRect);
- }
- void RulerWidget::mousePressEvent(QMouseEvent* ev)
- {
- // start a drag interaction to create a guide
- AZ::Vector2 localMousePos = QtHelpers::MapGlobalPosToLocalVector2(m_editorWindow->GetViewport(), ev->globalPos());
- float dpiScaleFactor = m_editorWindow->GetViewport()->WidgetToViewportFactor();
- AZ::Vector2 viewportMousePos = localMousePos * dpiScaleFactor;
- bool isVertical = m_orientation == Orientation::Vertical;
- m_dragInteraction = new ViewportAddGuideInteraction(m_editorWindow, m_editorWindow->GetCanvas(), isVertical, viewportMousePos);
- }
- void RulerWidget::mouseMoveEvent(QMouseEvent* ev)
- {
- // If the mouse press event was on the ruler then we will get move events here even if the mouse is over the viewport.
- // We only get the events if the mouse is pressed down. So we only get here when adding a ruler.
- if (m_dragInteraction)
- {
- AZ::Vector2 localMousePos = QtHelpers::MapGlobalPosToLocalVector2(m_editorWindow->GetViewport(), ev->globalPos());
- float dpiScaleFactor = m_editorWindow->GetViewport()->WidgetToViewportFactor();
- AZ::Vector2 viewportMousePos = localMousePos * dpiScaleFactor;
- m_dragInteraction->Update(viewportMousePos);
- }
- // SetCursorPos does not get called from the viewport while we are dragging from the ruler so update both rulers from here
- m_editorWindow->GetViewport()->SetRulerCursorPositions(ev->globalPos());
- }
- void RulerWidget::mouseReleaseEvent(QMouseEvent* ev)
- {
- // This is a drag that started on the ruler, this is used to add guides.
- // If the mouse is released inside the viewport window then the guide is added, otherwise the add is canceled.
- if (m_dragInteraction)
- {
- // test to see if the mouse position is inside the viewport on each axis
- const QPoint& pos = ev->pos();
- const QSize& size = m_editorWindow->GetViewport()->size();
- ViewportDragInteraction::EndState inViewport;
- if (pos.x() >= 0 && pos.x() < size.width())
- inViewport = pos.y() >= 0 && pos.y() < size.height() ? ViewportDragInteraction::EndState::Inside : ViewportDragInteraction::EndState::OutsideY;
- else
- inViewport = pos.y() >= 0 && pos.y() < size.height() ? ViewportDragInteraction::EndState::OutsideX : ViewportDragInteraction::EndState::OutsideXY;
- m_dragInteraction->EndInteraction(inViewport);
- SAFE_DELETE(m_dragInteraction);
- }
- }
- void RulerWidget::focusOutEvent([[maybe_unused]] QFocusEvent* ev)
- {
- // If we are in the middle of an interaction and this widget loses focus this is typically because right-mouse button
- // or ALT+char etc was pressed while the left mouse button was still down. In this case cancel the interaction so
- // that we don't keep displaying the guide position.
- SAFE_DELETE(m_dragInteraction);
- }
- void RulerWidget::DrawTickMarksWithLabels(QPainter* painter, QRectF rulerRect, float translation, float scale)
- {
- // Internal structure used to define the different scales used depending on the zoom level
- struct RulerScale
- {
- float canvasPixelsPerSection;
- int preferredNumSubdivisions;
- int reducedNumSubdivisions;
- };
- // define the different ruler section sizes and the prefered and reduced number of subdivisions in the section
- const RulerScale validRulerScales[] = {
- { 1.0f, 1, 0 },
- { 2.0f, 2, 0 },
- { 5.0f, 5, 0 },
- { 10.0f, 10, 4 },
- { 20.0f, 10, 4 },
- { 50.0f, 10, 5 },
- { 100.0f, 10, 4 },
- { 200.0f, 10, 4 },
- { 500.0f, 10, 5 },
- { 1000.0f, 10, 5 }
- };
- // A "ruler section" is one part of the ruler, it contains one "major tick" with a text label
- // followed by a number of minor ticks that divide the section into subdivisions.
- // For now we assume the the units are always pixels.
- // The length of one RulerSection in canvas pixels depends on the scale.
- // When the scale is 1 there is one pixel on the screen for every pixel on the canvas.
- const float minRulerSectionLengthOnScreen = 40.0f;
- const float minDistanceBetweenTicks = 5.0f;
- float unroundedCanvasPixelsInSection = minRulerSectionLengthOnScreen / scale;
-
- const int numRulerScales = sizeof(validRulerScales) / sizeof(validRulerScales[0]);
- const RulerScale* selectedRulerScale = &validRulerScales[numRulerScales - 1];
- for (const RulerScale& rulerScale : validRulerScales)
- {
- if (unroundedCanvasPixelsInSection <= rulerScale.canvasPixelsPerSection)
- {
- selectedRulerScale = &rulerScale;
- break;
- }
- }
- // Having determined which ruler scale to use we know the number of canvas pixels in a ruler section
- float canvasPixelsPerSection = selectedRulerScale->canvasPixelsPerSection;
- // Compute the visible range of the ruler
- float rulerRectMin = aznumeric_cast<float>((m_orientation == Orientation::Horizontal) ? rulerRect.left() : rulerRect.top());
- float rulerRectMax = aznumeric_cast<float>((m_orientation == Orientation::Horizontal) ? rulerRect.right() : rulerRect.bottom());
- float rulerStartInCanvasPixels = ((rulerRectMin - translation) / scale) + m_origin;
- float rulerEndInCanvasPixels = ((rulerRectMax - translation) / scale) + m_origin;
- // We will draw whole ruler sections, relying on the Qt clipping to clip off the non-visible parts.
- // So compute the ruler sections we should start and end with
- float rulerStartInSections = floorf(rulerStartInCanvasPixels / canvasPixelsPerSection);
- float rulerEndInSections = floorf(rulerEndInCanvasPixels / canvasPixelsPerSection);
- float firstRulerSectionStart = rulerStartInSections * canvasPixelsPerSection;
- float lastRulerSectionStart = rulerEndInSections * canvasPixelsPerSection;
- // draw the subdivision hatch marks
- float sectionLengthInScreenPixels = selectedRulerScale->canvasPixelsPerSection * scale;
- int numSubdivisions = selectedRulerScale->reducedNumSubdivisions;
- if (sectionLengthInScreenPixels / selectedRulerScale->preferredNumSubdivisions > minDistanceBetweenTicks)
- {
- numSubdivisions = selectedRulerScale->preferredNumSubdivisions;
- }
- // Set the pen to use for drawing all the tick marks
- QPen pen(QColor(204, 204, 204), 1);
- painter->setPen(pen);
- // set the font to use for drawing the ruler labels
- QFont font(QWidget::font());
- font.setPixelSize(10);
- painter->setFont(font);
- // for each visible section draw that ruler section
- for (float startInCanvasPixels = firstRulerSectionStart; startInCanvasPixels <= lastRulerSectionStart; startInCanvasPixels += canvasPixelsPerSection)
- {
- DrawRulerSection(painter, rulerRect, startInCanvasPixels, sectionLengthInScreenPixels, numSubdivisions, translation, scale);
- }
- }
- void RulerWidget::DrawRulerSection(QPainter* painter, QRectF rulerRect, float startInCanvasPixels, float sectionLengthInScreenPixels, int numSubdivisions, float translation, float scale)
- {
- // compute the position in Qt local pixels for the start of this ruler section
- float posOnRuler = (startInCanvasPixels - m_origin) * scale + translation;
- posOnRuler -= 0.5f; // without this the ticks do not line up with the viewport exactly
- // Set the painter translation and scale so that we can do the drawing regardless of whether this is a horizontal or vertical ruler.
- // This sets to origin to the "bottom left" of the ruler section. I.e. where the major tick ends on the viewport side of the ruler.
- painter->save();
- float rulerBreadth;
- float directionAlongSection = 1.0f;
- if (m_orientation == Orientation::Horizontal)
- {
- rulerBreadth = aznumeric_cast<float>(rulerRect.height());
- painter->translate(posOnRuler, rulerBreadth);
- }
- else
- {
- rulerBreadth = aznumeric_cast<float>(rulerRect.width());
- painter->translate(rulerBreadth, posOnRuler);
- painter->rotate(-90);
- directionAlongSection = -1.0f; // for the vertical section the major tick is visually at the "end" of the section
- }
- // Constants that can be used to tune the tick marks on the ruler
- const float tickLengthRatioSmallTick = 0.33f;
- const float tickLengthRatioMediumTick = 0.66f;
- const float tickLengthRatioLargeTick = 1.0f;
- // Draw major tick
- painter->drawLine(QLineF(0, 0, 0, -rulerBreadth * tickLengthRatioLargeTick));
- // draw the subdivision hatch marks
- if (numSubdivisions > 0)
- {
- float tickSpacing = sectionLengthInScreenPixels / numSubdivisions;
- // The number of minor ticks is the number of subdivisions-1 since a subdivision is the space between ticks
- for (int i = 0 ; i < numSubdivisions-1; ++i)
- {
- // The only time we draw a "medium" tick is when there are 10 subdivisions and the medium tick is the fifth tick
- const float tickLengthRatio = (i == 4) ? tickLengthRatioMediumTick : tickLengthRatioSmallTick;
- float pos = (i+1) * tickSpacing * directionAlongSection;
- painter->drawLine(QLineF(pos, 0, pos, -rulerBreadth * tickLengthRatio));
- }
- }
- // Draw the label text to the right (horizontal) or left (vertical) of the top of the major tick.
- // Using QPainterPath is supposed to give better quality text especially when rotated. It looks worse, but it does
- // look consistent when rotated (consistently bad). So use just use drawText.
- QString label = QString::number(startInCanvasPixels);
- float textPosAlongSection;
- if (m_orientation == Orientation::Horizontal)
- {
- textPosAlongSection = 2;
- }
- else
- {
- QFontMetrics fontMetrics(painter->font());
- textPosAlongSection = aznumeric_cast<float>(-(2 + fontMetrics.horizontalAdvance(label)));
- }
- painter->drawText(aznumeric_cast<int>(textPosAlongSection), aznumeric_cast<int>(-(rulerBreadth-8)) , label);
- // restore the painter translation and rotation
- painter->restore();
- }
- void RulerWidget::DrawCursorPos(QPainter* painter, QRectF rulerRect)
- {
- // Use a dotted magenta line for the cursor indicator
- QPen pen;
- pen.setStyle(Qt::DotLine);
- pen.setWidth(1);
- pen.setBrush(Qt::magenta);
- painter->setPen(pen);
- float x1, x2, y1, y2;
- if (m_orientation == Orientation::Horizontal)
- {
- x1 = x2 = m_cursorPos;
- y1 = aznumeric_cast<float>(rulerRect.top());
- y2 = aznumeric_cast<float>(rulerRect.bottom());
- }
- else
- {
- y1 = y2 = m_cursorPos;
- x1 = aznumeric_cast<float>(rulerRect.left());
- x2 = aznumeric_cast<float>(rulerRect.right());
- }
- painter->drawLine(QLineF(x1,y1,x2,y2));
- }
- #include <moc_RulerWidget.cpp>
|