#pragma once #include "FractoriumPch.h" /// <summary> /// CurvesGraphicsView and EllipseItem classes. /// </summary> class EllipseItem; /// <summary> /// Enumeration used for setting values on a specific curve. /// </summary> enum class CurveIndex : et { ALL, RED, GREEN, BLUE }; /// <summary> /// Derivation to display points on bezier cuves for the user to drag. /// Selection logic and updating is handled by the base class. /// Changes here will affect the current ember and vice versa. /// The points, axis lines and pens are kept as members and the curves /// themselves are drawn on the fly during each paint event. /// Pointers to these are kept in arrays to make manipulating them in loops easier. /// Note that this must work off a fixed rect size, hence why the control is not resizeable. /// </summary> class CurvesGraphicsView : public QGraphicsView { Q_OBJECT public: CurvesGraphicsView(QWidget* parent = nullptr); void PointChanged(int curveIndex, int pointIndex, const QPointF& point); QPointF Get(int curveIndex, int pointIndex); void Set(int curveIndex, int pointIndex, const QPointF& point); void Set(Curves<float>& curves); void SetTop(CurveIndex curveIndex); size_t SelectedCurveIndex() const { return m_Index; } Q_SIGNALS: void PointChangedSignal(int curveIndex, int pointIndex, const QPointF& point); void PointAddedSignal(size_t curveIndex, const QPointF& point); void PointRemovedSignal(size_t curveIndex, int pointIndex); protected: virtual void paintEvent(QPaintEvent* e) override; virtual void mousePressEvent(QMouseEvent* e) override; size_t m_Index = 0; QPen m_APen; QPen m_RPen; QPen m_GPen; QPen m_BPen; QPen m_AxisPen; std::vector<EllipseItem*> m_AllP; std::vector<EllipseItem*> m_RedP; std::vector<EllipseItem*> m_GrnP; std::vector<EllipseItem*> m_BluP; QGraphicsLineItem* m_XLine; QGraphicsLineItem* m_YLine; std::array<QPen*, 4> m_Pens; QGraphicsScene m_Scene; QRectF m_OriginalRect; std::array<std::vector<EllipseItem*>, 4> m_Points; }; /// <summary> /// Derivation for draggable points needed to trigger an event whenever the item is changed. /// Custom drawing is also done to omit drawing a selection rectangle. /// </summary> class EllipseItem : public QGraphicsEllipseItem { public: /// <summary> /// Construct the point and specify the curve index it's part of, as well as the /// point index within the curve. /// </summary> /// <param name="rect">Pass to the parent</param> /// <param name="curveIndex">The curve's index this point is a part of, 0-3.</param> /// <param name="pointIndex">The point index within the curve</param> /// <param name="viewParent">The graphics view this point is displayed on</param> /// <param name="p">The parent widget of this item</param> EllipseItem(const QRectF& rect, int curveIndex, int pointIndex, CurvesGraphicsView* viewParent, QGraphicsItem* parent = nullptr) : QGraphicsEllipseItem(rect, parent) { m_CurveIndex = curveIndex; m_PointIndex = pointIndex; m_ViewParent = viewParent; setFlag(QGraphicsItem::ItemSendsScenePositionChanges); setFlag(QGraphicsItem::ItemIsSelectable); setFlag(QGraphicsItem::ItemIsMovable); setPen(Qt::NoPen); } /// <summary> /// Set whether this item is selectable, which means this curve is the current one. /// </summary> /// <param name="b">True if selected, else false.</param> void SetCurrent(bool b) { setFlag(QGraphicsItem::ItemIsMovable, b); setZValue(b ? 2 : 1); } /// <summary> /// Index properties, getters only. /// </summary> int CurveIndex() const { return m_CurveIndex; } int PointIndex() const { return m_PointIndex; } protected: /// <summary> /// Overridden paint event to disable the selection rectangle. /// </summary> /// <param name="painter">Unused and just passed to QGraphicsEllipseItem::paint()</param> /// <param name="option">Drawing options used which will have the QStyle::State_Selected flag unset</param> /// <param name="widget">Unused and just passed to QGraphicsEllipseItem::paint()</param> void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override { QStyleOptionGraphicsItem myOption(*option); myOption.state &= ~QStyle::State_Selected; QGraphicsEllipseItem::paint(painter, &myOption, widget); } /// <summary> /// Overridden itemChange event to notify the parent control that it has moved. /// Movement is also restricted to the scene rect. /// </summary> /// <param name="change">Action is only taken if this value equals ItemPositionChange</param> /// <param name="value">The new position. This will be clamped to the scene rect.</param> /// <returns>The new position</returns> QVariant itemChange(GraphicsItemChange change, const QVariant& value) override { if ((change == ItemPositionChange) && scene()) { //Value is the new position. auto newPos = value.toPointF(); const auto rect = scene()->sceneRect(); if (!rect.contains(newPos)) { //Keep the item inside the scene rect. newPos.setX(qMin(rect.right(), qMax(newPos.x(), rect.left()))); newPos.setY(qMin(rect.bottom(), qMax(newPos.y(), rect.top()))); } m_ViewParent->PointChanged(m_CurveIndex, m_PointIndex, newPos); return newPos; } return QGraphicsEllipseItem::itemChange(change, value); } int m_CurveIndex; int m_PointIndex; CurvesGraphicsView* m_ViewParent; };