RulerWidget.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include "RulerWidget.h"
  9. #include "CanvasHelpers.h"
  10. #include "GuideHelpers.h"
  11. #include "QtHelpers.h"
  12. #include "ViewportAddGuideInteraction.h"
  13. #include <LyShine/Bus/UiEditorCanvasBus.h>
  14. #include <QPainter>
  15. #include <QMouseEvent>
  16. RulerWidget::RulerWidget(Orientation orientation, QWidget* parent, EditorWindow* editorWindow)
  17. : QWidget(parent)
  18. , m_orientation(orientation)
  19. , m_cursorPos(0.0f)
  20. , m_editorWindow(editorWindow)
  21. {
  22. // needed so we can cancel interaction on loss of focus
  23. setFocusPolicy(Qt::StrongFocus);
  24. }
  25. void RulerWidget::SetCursorPos(const QPoint& pos)
  26. {
  27. // store the cursor value to use when painting the ruler
  28. QPoint localCursorPos = mapFromGlobal(pos);
  29. m_cursorPos = aznumeric_cast<float>((m_orientation == Orientation::Horizontal) ? localCursorPos.x() : localCursorPos.y());
  30. update();
  31. }
  32. void RulerWidget::DrawForViewport(Draw2dHelper& draw2d)
  33. {
  34. // if there is an interaction in progress then draw the visual aids for the interaction
  35. if (m_dragInteraction)
  36. {
  37. m_dragInteraction->Render(draw2d);
  38. }
  39. }
  40. int RulerWidget::GetRulerBreadth()
  41. {
  42. // This is how wide the rulers are. It is possible that at some point it could be configurable or
  43. // computed from some other UI or stylesheet setting.
  44. const int rulerBreadth = 16;
  45. return rulerBreadth;
  46. }
  47. void RulerWidget::paintEvent([[maybe_unused]] QPaintEvent* event)
  48. {
  49. // Note: If the ruler is hidden it will have a zero width or height. In this case Qt never even calls paintEvent
  50. // get the scale and translation of the viewport
  51. const ViewportInteraction::TranslationAndScale& translationAndScale = m_editorWindow->GetViewport()->GetViewportInteraction()->GetCanvasViewportMatrixProps();
  52. float scale = translationAndScale.scale;
  53. float translation = (m_orientation == Orientation::Horizontal) ? translationAndScale.translation.GetX() : translationAndScale.translation.GetY();
  54. // Convert back to qt widget coords for painting
  55. float dpiScaleFactor = m_editorWindow->GetViewport()->WidgetToViewportFactor();
  56. if (dpiScaleFactor != 0.0f)
  57. {
  58. scale /= dpiScaleFactor;
  59. translation /= dpiScaleFactor;
  60. }
  61. // 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
  62. const float epsilon = 0.00001f;
  63. if (scale < epsilon)
  64. {
  65. return;
  66. }
  67. // Create a painter for doing the drawing
  68. QPainter painter(this);
  69. painter.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing);
  70. QRectF rulerRect = rect();
  71. // We could fill the rect here if we wanted the ruler background to be a diferent color to the default
  72. // e.g.: painter.fillRect(rulerRect, QColor(30,35,40));
  73. // Draw the tick marks and number labels
  74. DrawTickMarksWithLabels(&painter, rulerRect, translation, scale);
  75. // Indicate the position of the mouse on the rulers
  76. DrawCursorPos(&painter, rulerRect);
  77. }
  78. void RulerWidget::mousePressEvent(QMouseEvent* ev)
  79. {
  80. // start a drag interaction to create a guide
  81. AZ::Vector2 localMousePos = QtHelpers::MapGlobalPosToLocalVector2(m_editorWindow->GetViewport(), ev->globalPos());
  82. float dpiScaleFactor = m_editorWindow->GetViewport()->WidgetToViewportFactor();
  83. AZ::Vector2 viewportMousePos = localMousePos * dpiScaleFactor;
  84. bool isVertical = m_orientation == Orientation::Vertical;
  85. m_dragInteraction = new ViewportAddGuideInteraction(m_editorWindow, m_editorWindow->GetCanvas(), isVertical, viewportMousePos);
  86. }
  87. void RulerWidget::mouseMoveEvent(QMouseEvent* ev)
  88. {
  89. // If the mouse press event was on the ruler then we will get move events here even if the mouse is over the viewport.
  90. // We only get the events if the mouse is pressed down. So we only get here when adding a ruler.
  91. if (m_dragInteraction)
  92. {
  93. AZ::Vector2 localMousePos = QtHelpers::MapGlobalPosToLocalVector2(m_editorWindow->GetViewport(), ev->globalPos());
  94. float dpiScaleFactor = m_editorWindow->GetViewport()->WidgetToViewportFactor();
  95. AZ::Vector2 viewportMousePos = localMousePos * dpiScaleFactor;
  96. m_dragInteraction->Update(viewportMousePos);
  97. }
  98. // SetCursorPos does not get called from the viewport while we are dragging from the ruler so update both rulers from here
  99. m_editorWindow->GetViewport()->SetRulerCursorPositions(ev->globalPos());
  100. }
  101. void RulerWidget::mouseReleaseEvent(QMouseEvent* ev)
  102. {
  103. // This is a drag that started on the ruler, this is used to add guides.
  104. // If the mouse is released inside the viewport window then the guide is added, otherwise the add is canceled.
  105. if (m_dragInteraction)
  106. {
  107. // test to see if the mouse position is inside the viewport on each axis
  108. const QPoint& pos = ev->pos();
  109. const QSize& size = m_editorWindow->GetViewport()->size();
  110. ViewportDragInteraction::EndState inViewport;
  111. if (pos.x() >= 0 && pos.x() < size.width())
  112. inViewport = pos.y() >= 0 && pos.y() < size.height() ? ViewportDragInteraction::EndState::Inside : ViewportDragInteraction::EndState::OutsideY;
  113. else
  114. inViewport = pos.y() >= 0 && pos.y() < size.height() ? ViewportDragInteraction::EndState::OutsideX : ViewportDragInteraction::EndState::OutsideXY;
  115. m_dragInteraction->EndInteraction(inViewport);
  116. SAFE_DELETE(m_dragInteraction);
  117. }
  118. }
  119. void RulerWidget::focusOutEvent([[maybe_unused]] QFocusEvent* ev)
  120. {
  121. // If we are in the middle of an interaction and this widget loses focus this is typically because right-mouse button
  122. // or ALT+char etc was pressed while the left mouse button was still down. In this case cancel the interaction so
  123. // that we don't keep displaying the guide position.
  124. SAFE_DELETE(m_dragInteraction);
  125. }
  126. void RulerWidget::DrawTickMarksWithLabels(QPainter* painter, QRectF rulerRect, float translation, float scale)
  127. {
  128. // Internal structure used to define the different scales used depending on the zoom level
  129. struct RulerScale
  130. {
  131. float canvasPixelsPerSection;
  132. int preferredNumSubdivisions;
  133. int reducedNumSubdivisions;
  134. };
  135. // define the different ruler section sizes and the prefered and reduced number of subdivisions in the section
  136. const RulerScale validRulerScales[] = {
  137. { 1.0f, 1, 0 },
  138. { 2.0f, 2, 0 },
  139. { 5.0f, 5, 0 },
  140. { 10.0f, 10, 4 },
  141. { 20.0f, 10, 4 },
  142. { 50.0f, 10, 5 },
  143. { 100.0f, 10, 4 },
  144. { 200.0f, 10, 4 },
  145. { 500.0f, 10, 5 },
  146. { 1000.0f, 10, 5 }
  147. };
  148. // A "ruler section" is one part of the ruler, it contains one "major tick" with a text label
  149. // followed by a number of minor ticks that divide the section into subdivisions.
  150. // For now we assume the the units are always pixels.
  151. // The length of one RulerSection in canvas pixels depends on the scale.
  152. // When the scale is 1 there is one pixel on the screen for every pixel on the canvas.
  153. const float minRulerSectionLengthOnScreen = 40.0f;
  154. const float minDistanceBetweenTicks = 5.0f;
  155. float unroundedCanvasPixelsInSection = minRulerSectionLengthOnScreen / scale;
  156. const int numRulerScales = sizeof(validRulerScales) / sizeof(validRulerScales[0]);
  157. const RulerScale* selectedRulerScale = &validRulerScales[numRulerScales - 1];
  158. for (const RulerScale& rulerScale : validRulerScales)
  159. {
  160. if (unroundedCanvasPixelsInSection <= rulerScale.canvasPixelsPerSection)
  161. {
  162. selectedRulerScale = &rulerScale;
  163. break;
  164. }
  165. }
  166. // Having determined which ruler scale to use we know the number of canvas pixels in a ruler section
  167. float canvasPixelsPerSection = selectedRulerScale->canvasPixelsPerSection;
  168. // Compute the visible range of the ruler
  169. float rulerRectMin = aznumeric_cast<float>((m_orientation == Orientation::Horizontal) ? rulerRect.left() : rulerRect.top());
  170. float rulerRectMax = aznumeric_cast<float>((m_orientation == Orientation::Horizontal) ? rulerRect.right() : rulerRect.bottom());
  171. float rulerStartInCanvasPixels = ((rulerRectMin - translation) / scale) + m_origin;
  172. float rulerEndInCanvasPixels = ((rulerRectMax - translation) / scale) + m_origin;
  173. // We will draw whole ruler sections, relying on the Qt clipping to clip off the non-visible parts.
  174. // So compute the ruler sections we should start and end with
  175. float rulerStartInSections = floorf(rulerStartInCanvasPixels / canvasPixelsPerSection);
  176. float rulerEndInSections = floorf(rulerEndInCanvasPixels / canvasPixelsPerSection);
  177. float firstRulerSectionStart = rulerStartInSections * canvasPixelsPerSection;
  178. float lastRulerSectionStart = rulerEndInSections * canvasPixelsPerSection;
  179. // draw the subdivision hatch marks
  180. float sectionLengthInScreenPixels = selectedRulerScale->canvasPixelsPerSection * scale;
  181. int numSubdivisions = selectedRulerScale->reducedNumSubdivisions;
  182. if (sectionLengthInScreenPixels / selectedRulerScale->preferredNumSubdivisions > minDistanceBetweenTicks)
  183. {
  184. numSubdivisions = selectedRulerScale->preferredNumSubdivisions;
  185. }
  186. // Set the pen to use for drawing all the tick marks
  187. QPen pen(QColor(204, 204, 204), 1);
  188. painter->setPen(pen);
  189. // set the font to use for drawing the ruler labels
  190. QFont font(QWidget::font());
  191. font.setPixelSize(10);
  192. painter->setFont(font);
  193. // for each visible section draw that ruler section
  194. for (float startInCanvasPixels = firstRulerSectionStart; startInCanvasPixels <= lastRulerSectionStart; startInCanvasPixels += canvasPixelsPerSection)
  195. {
  196. DrawRulerSection(painter, rulerRect, startInCanvasPixels, sectionLengthInScreenPixels, numSubdivisions, translation, scale);
  197. }
  198. }
  199. void RulerWidget::DrawRulerSection(QPainter* painter, QRectF rulerRect, float startInCanvasPixels, float sectionLengthInScreenPixels, int numSubdivisions, float translation, float scale)
  200. {
  201. // compute the position in Qt local pixels for the start of this ruler section
  202. float posOnRuler = (startInCanvasPixels - m_origin) * scale + translation;
  203. posOnRuler -= 0.5f; // without this the ticks do not line up with the viewport exactly
  204. // Set the painter translation and scale so that we can do the drawing regardless of whether this is a horizontal or vertical ruler.
  205. // 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.
  206. painter->save();
  207. float rulerBreadth;
  208. float directionAlongSection = 1.0f;
  209. if (m_orientation == Orientation::Horizontal)
  210. {
  211. rulerBreadth = aznumeric_cast<float>(rulerRect.height());
  212. painter->translate(posOnRuler, rulerBreadth);
  213. }
  214. else
  215. {
  216. rulerBreadth = aznumeric_cast<float>(rulerRect.width());
  217. painter->translate(rulerBreadth, posOnRuler);
  218. painter->rotate(-90);
  219. directionAlongSection = -1.0f; // for the vertical section the major tick is visually at the "end" of the section
  220. }
  221. // Constants that can be used to tune the tick marks on the ruler
  222. const float tickLengthRatioSmallTick = 0.33f;
  223. const float tickLengthRatioMediumTick = 0.66f;
  224. const float tickLengthRatioLargeTick = 1.0f;
  225. // Draw major tick
  226. painter->drawLine(QLineF(0, 0, 0, -rulerBreadth * tickLengthRatioLargeTick));
  227. // draw the subdivision hatch marks
  228. if (numSubdivisions > 0)
  229. {
  230. float tickSpacing = sectionLengthInScreenPixels / numSubdivisions;
  231. // The number of minor ticks is the number of subdivisions-1 since a subdivision is the space between ticks
  232. for (int i = 0 ; i < numSubdivisions-1; ++i)
  233. {
  234. // The only time we draw a "medium" tick is when there are 10 subdivisions and the medium tick is the fifth tick
  235. const float tickLengthRatio = (i == 4) ? tickLengthRatioMediumTick : tickLengthRatioSmallTick;
  236. float pos = (i+1) * tickSpacing * directionAlongSection;
  237. painter->drawLine(QLineF(pos, 0, pos, -rulerBreadth * tickLengthRatio));
  238. }
  239. }
  240. // Draw the label text to the right (horizontal) or left (vertical) of the top of the major tick.
  241. // Using QPainterPath is supposed to give better quality text especially when rotated. It looks worse, but it does
  242. // look consistent when rotated (consistently bad). So use just use drawText.
  243. QString label = QString::number(startInCanvasPixels);
  244. float textPosAlongSection;
  245. if (m_orientation == Orientation::Horizontal)
  246. {
  247. textPosAlongSection = 2;
  248. }
  249. else
  250. {
  251. QFontMetrics fontMetrics(painter->font());
  252. textPosAlongSection = aznumeric_cast<float>(-(2 + fontMetrics.horizontalAdvance(label)));
  253. }
  254. painter->drawText(aznumeric_cast<int>(textPosAlongSection), aznumeric_cast<int>(-(rulerBreadth-8)) , label);
  255. // restore the painter translation and rotation
  256. painter->restore();
  257. }
  258. void RulerWidget::DrawCursorPos(QPainter* painter, QRectF rulerRect)
  259. {
  260. // Use a dotted magenta line for the cursor indicator
  261. QPen pen;
  262. pen.setStyle(Qt::DotLine);
  263. pen.setWidth(1);
  264. pen.setBrush(Qt::magenta);
  265. painter->setPen(pen);
  266. float x1, x2, y1, y2;
  267. if (m_orientation == Orientation::Horizontal)
  268. {
  269. x1 = x2 = m_cursorPos;
  270. y1 = aznumeric_cast<float>(rulerRect.top());
  271. y2 = aznumeric_cast<float>(rulerRect.bottom());
  272. }
  273. else
  274. {
  275. y1 = y2 = m_cursorPos;
  276. x1 = aznumeric_cast<float>(rulerRect.left());
  277. x2 = aznumeric_cast<float>(rulerRect.right());
  278. }
  279. painter->drawLine(QLineF(x1,y1,x2,y2));
  280. }
  281. #include <moc_RulerWidget.cpp>