You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
339 lines
14 KiB
C++
339 lines
14 KiB
C++
/*
|
|
* 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>
|