diff --git a/Editors/VisualShaderEditor.cpp b/Editors/VisualShaderEditor.cpp index 32d74da52..d650bbef2 100644 --- a/Editors/VisualShaderEditor.cpp +++ b/Editors/VisualShaderEditor.cpp @@ -571,7 +571,6 @@ VisualShaderGraphicsScene::VisualShaderGraphicsScene(VisualShader* vs, QObject* VisualShaderNodeGraphicsObject* n_o {new VisualShaderNodeGraphicsObject(vs, node_id)}; node_graphics_objects[node_id] = n_o; this->addItem(n_o); - this->update(); } // Populate the scene with connections @@ -580,13 +579,16 @@ VisualShaderGraphicsScene::VisualShaderGraphicsScene(VisualShader* vs, QObject* for (const int& connection_id : connections) { const VisualShader::Connection connection {vs->get_connection(connection_id)}; - VisualShaderConnectionGraphicsObject* c_o {new VisualShaderConnectionGraphicsObject()}; + VisualShaderConnectionGraphicsObject* c_o {new VisualShaderConnectionGraphicsObject(connection.from_node, connection.from_port)}; connection_graphics_objects[connection_id] = c_o; this->addItem(c_o); - this->update(); } QObject::connect(this, &VisualShaderGraphicsScene::node_moved, this, &VisualShaderGraphicsScene::on_node_moved); + + VisualShaderConnectionGraphicsObject* c_o {new VisualShaderConnectionGraphicsObject(1, 0)}; + connection_graphics_objects[0] = c_o; + this->addItem(c_o); } VisualShaderGraphicsScene::~VisualShaderGraphicsScene() { @@ -659,7 +661,7 @@ VisualShaderConnectionGraphicsObject* VisualShaderGraphicsScene::get_connection_ void VisualShaderGraphicsScene::on_node_moved(const int& n_id, const QPointF& new_position) { // Here we will move all connections that are connected to the node - std::cout << "Node moved: " << n_id << " to " << new_position.x() << ", " << new_position.y() << std::endl; + // std::cout << "Node moved: " << n_id << " to " << new_position.x() << ", " << new_position.y() << std::endl; } /**********************************************************************/ @@ -990,9 +992,6 @@ VisualShaderNodeGraphicsObject::~VisualShaderNodeGraphicsObject() { QRectF VisualShaderNodeGraphicsObject::boundingRect() const { QRectF r({0.0f, 0.0f}, this->size); - float rect_x {(float)r.topLeft().x()}; - float rect_y {(float)r.topLeft().y()}; - float rect_w {(float)r.width()}; float rect_h {(float)r.height()}; @@ -1312,7 +1311,10 @@ VisualShaderInputPortGraphicsObject::~VisualShaderInputPortGraphicsObject() { } QRectF VisualShaderInputPortGraphicsObject::boundingRect() const { - return rect; + QFont f; + QFontMetrics fm(f); + QRect text_rect {fm.boundingRect(name)}; + return rect.united(text_rect); } void VisualShaderInputPortGraphicsObject::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { @@ -1381,7 +1383,10 @@ VisualShaderOutputPortGraphicsObject::~VisualShaderOutputPortGraphicsObject() { } QRectF VisualShaderOutputPortGraphicsObject::boundingRect() const { - return rect; + QFont f; + QFontMetrics fm(f); + QRect text_rect {fm.boundingRect(name)}; + return rect.united(text_rect); } void VisualShaderOutputPortGraphicsObject::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { @@ -1437,8 +1442,18 @@ void VisualShaderOutputPortGraphicsObject::paint(QPainter *painter, const QStyle /**********************************************************************/ /**********************************************************************/ -VisualShaderConnectionGraphicsObject::VisualShaderConnectionGraphicsObject(QGraphicsItem* parent) : QGraphicsObject(parent) { - +VisualShaderConnectionGraphicsObject::VisualShaderConnectionGraphicsObject(const int& from_n_id, + const int& from_p_index, + QGraphicsItem* parent) : QGraphicsObject(parent), + from_n_id(from_n_id), + from_p_index(from_p_index), + start_graphics_object(nullptr), + end_graphics_object(nullptr) { + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIsFocusable, true); + setFlag(QGraphicsItem::ItemIsSelectable, true); + + setZValue(-1.0f); } VisualShaderConnectionGraphicsObject::~VisualShaderConnectionGraphicsObject() { @@ -1446,12 +1461,310 @@ VisualShaderConnectionGraphicsObject::~VisualShaderConnectionGraphicsObject() { } QRectF VisualShaderConnectionGraphicsObject::boundingRect() const { - return QRectF(); + QRectF r {calculate_bounding_rect_from_coordinates(start_coordinate, end_coordinate)}; + + // Calculate the rect padding + // We add a safe area around the rect to make it easier to get an accurate coordinate of the size + this->rect_padding = 10.0f; + + r.adjust(-rect_padding, -rect_padding, rect_padding, rect_padding); + + return r; } void VisualShaderConnectionGraphicsObject::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { painter->setClipRect(option->exposedRect); + // Get the rect without the padding + QRectF r {this->boundingRect()}; + + // { + // // Draw rect + // QColor rect_color {Qt::red}; + + // QPen p(rect_color, 3.0f); + // painter->setPen(p); + + // painter->setBrush(Qt::NoBrush); + + // painter->drawRect(r); + // } + + // Add the padding to the rect + r.adjust(rect_padding, rect_padding, -rect_padding, -rect_padding); + + float rect_x {(float)r.topLeft().x()}; + float rect_y {(float)r.topLeft().y()}; + + float rect_w {(float)r.width()}; + float rect_h {(float)r.height()}; + + float min_side {(float)qMin(rect_w, rect_h)}; + + { + QPen pen; + pen.setWidth(min_side * 0.05f); + pen.setColor(this->construction_color); + pen.setStyle(Qt::DashLine); + + painter->setPen(pen); + painter->setBrush(Qt::NoBrush); + + std::pair control_points {calculate_control_points(start_coordinate, end_coordinate)}; + + QPainterPath cubic(start_coordinate); + cubic.cubicTo(control_points.first, control_points.second, end_coordinate); + + // cubic spline + painter->drawPath(cubic); + } + + { + // draw normal line + QPen p; + p.setWidth(min_side * 0.05f); + + const bool selected {this->isSelected()}; + + std::pair control_points {calculate_control_points(start_coordinate, end_coordinate)}; + + QPainterPath cubic(start_coordinate); + cubic.cubicTo(control_points.first, control_points.second, end_coordinate); + + p.setColor(this->normal_color); + + if (selected) { + p.setColor(this->selected_color); + } + + painter->setPen(p); + painter->setBrush(Qt::NoBrush); + + painter->drawPath(cubic); + } + + { + // Draw start and end points + if (!start_graphics_object) { + QRectF start_rect(start_coordinate.x(), start_coordinate.y(), min_side * 0.2f, min_side * 0.2f); + + // Adjust the port rect to be centered + start_rect.adjust(-start_rect.width() * 0.5f, -start_rect.height() * 0.5f, -start_rect.width() * 0.5f, -start_rect.height() * 0.5f); + + VisualShaderConnectionStartGraphicsObject* s_o {new VisualShaderConnectionStartGraphicsObject(start_rect, this)}; + start_graphics_object = s_o; + } + + if (!end_graphics_object) { + QRectF end_rect(end_coordinate.x(), end_coordinate.y(), min_side * 0.2f, min_side * 0.2f); + + // Adjust the port rect to be centered + end_rect.adjust(-end_rect.width() * 0.5f, -end_rect.height() * 0.5f, -end_rect.width() * 0.5f, -end_rect.height() * 0.5f); + + VisualShaderConnectionEndGraphicsObject* e_o {new VisualShaderConnectionEndGraphicsObject(end_rect, this)}; + end_graphics_object = e_o; + } + } +} + +int VisualShaderConnectionGraphicsObject::detect_quadrant(const QPointF& reference, const QPointF& target) const { + float relative_x {(float)(target.x() - reference.x())}; + float relative_y {(float)(target.y() - reference.y())}; + + // Check if the point is on an axis or the origin + if (relative_x == 0 && relative_y == 0) { + return 0; // Stack on the reference + } else if (relative_y == 0) { + return (relative_x > 0) ? 5 : 6; // On X-axis + } else if (relative_x == 0) { + return (relative_y < 0) ? 7 : 8; // On Y-axis + } + + // Determine the quadrant based on the relative coordinates + if (relative_x > 0 && relative_y < 0) { + return 1; // Quadrant I + } else if (relative_x < 0 && relative_y < 0) { + return 2; // Quadrant II + } else if (relative_x < 0 && relative_y > 0) { + return 3; // Quadrant III + } else if (relative_x > 0 && relative_y > 0) { + return 4; // Quadrant IV + } + + // Default case (should not reach here) + return -1; +} + +QRectF VisualShaderConnectionGraphicsObject::calculate_bounding_rect_from_coordinates(const QPointF& start_coordinate, const QPointF& end_coordinate) const { + float x1 {(float)start_coordinate.x()}; + float y1 {(float)start_coordinate.y()}; + float x2 {(float)end_coordinate.x()}; + float y2 {(float)end_coordinate.y()}; + + // Calculate the expanded rect + float min_x {(float)qMin(x1, x2)}; + float min_y {(float)qMin(y1, y2)}; + float max_x {(float)qMax(x1, x2)}; + float max_y {(float)qMax(y1, y2)}; + + QRectF r({min_x, min_y}, QSizeF(max_x - min_x, max_y - min_y)); + + bool in_abnormal_region {x2 < (x1 + min_h_distance)}; + + float a_width_expansion {((x1 + min_h_distance) - x2) * abnormal_face_to_back_control_width_expansion_factor}; + + if (in_abnormal_region) { + // The connection is not going from left to right normally + // Our control points will be outside the end_coordinate and start_coordinate bounding rect + // We will expand the bounding rect horizontally to make it easier to get an accurate coordinate of the size + r.adjust(-a_width_expansion, 0.0f, a_width_expansion, 0.0f); + } + + return r; +} + +std::pair VisualShaderConnectionGraphicsObject::calculate_control_points(const QPointF& start_coordinate, const QPointF& end_coordinated) const { + QPointF cp1; + QPointF cp2; + + float x1 {(float)start_coordinate.x()}; + float y1 {(float)start_coordinate.y()}; + float x2 {(float)end_coordinate.x()}; + float y2 {(float)end_coordinate.y()}; + + QRectF r {calculate_bounding_rect_from_coordinates(start_coordinate, end_coordinate)}; + + bool in_abnormal_region {x2 < (x1 + min_h_distance)}; + + int quadrant {detect_quadrant({x1, y1}, {x2, y2})}; + + float face_to_face_control_width_expansion_factor {0.8f}; + float face_to_face_control_height_expansion_factor {0.25f}; + + float width_expansion {(x2 - x1) * face_to_face_control_width_expansion_factor}; + + float a_width_expansion {((x1 + min_h_distance) - x2) * abnormal_face_to_back_control_width_expansion_factor}; + float a_height_expansion {a_width_expansion * abnormal_face_to_back_control_height_expansion_factor}; + + if (in_abnormal_region) { + r.adjust(-a_width_expansion, 0.0f, a_width_expansion, 0.0f); + } + + switch(quadrant) { + case 1: // Quadrant I: Normal face to back + // Find out if the connection is going from left to right normally + if (in_abnormal_region) { + // The connection is not going from left to right normally + // Our control points will be outside the end_coordinate and start_coordinate bounding rect + // We will expand the bounding rect horizontally to make it easier to get an accurate coordinate of the size + + // Here we cover cases of nodes not facing each other. + // This means we can't just send the path straight to the node. + + // Treated as inside Quadrant II + cp1 = {x1 + a_width_expansion, y1}; + cp2 = {x2 - a_width_expansion, y2}; + + } else { + // Treated as inside Quadrant I + cp1 = {x1 + width_expansion, y1}; + cp2 = {x2 - width_expansion, y2}; + } + break; + case 2: // Quadrant II: Abnormal face to back + cp1 = {x1 + a_width_expansion, y1}; + cp2 = {x2 - a_width_expansion, y2}; + break; + case 3: // Quadrant III: Abnormal face to back + cp1 = {x1 + a_width_expansion, y1 + a_height_expansion}; + cp2 = {x2 - a_width_expansion, y2 - a_height_expansion}; + break; + case 4: // Quadrant IV: Normal face to back + if (in_abnormal_region) { + // Treated as inside Quadrant III + cp1 = {x1 + a_width_expansion, y1}; + cp2 = {x2 - a_width_expansion, y2}; + } else { + // Treated as inside Quadrant IV + cp1 = {x1 + width_expansion, y1}; + cp2 = {x2 - width_expansion, y2}; + } + break; + case 5: // On +ve X-axis: Normal face to back + // Straight line + cp1 = {x1, y1}; + cp2 = {x2, y2}; + break; + case 6: // On -ve X-axis: Abnormal face to back + r.adjust(0.0f, -a_height_expansion, 0.0f, a_height_expansion); + cp1 = {x1 + a_width_expansion, y1}; + cp2 = {x2 - a_width_expansion, y2}; + break; + case 7: // On +ve Y-axis: Abnormal face to back + r.adjust(0.0f, -a_height_expansion, 0.0f, a_height_expansion); + cp1 = {x1 + a_width_expansion, y1}; + cp2 = {x2 - a_width_expansion, y2}; + break; + case 8: // On -ve Y-axis: Abnormal face to back + r.adjust(0.0f, -a_height_expansion, 0.0f, a_height_expansion); + cp1 = {x1 + a_width_expansion, y1}; + cp2 = {x2 - a_width_expansion, y2}; + break; + default: + return std::make_pair(start_coordinate, end_coordinate); + } + + return std::make_pair(cp1, cp2); +} + +VisualShaderConnectionStartGraphicsObject::VisualShaderConnectionStartGraphicsObject(const QRectF& rect, QGraphicsItem* parent) : QGraphicsObject(parent), + rect(rect) { + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIsFocusable, true); + setFlag(QGraphicsItem::ItemIsSelectable, true); + + setVisible(true); + setOpacity(this->opacity); + + setZValue(-1.0f); +} + +VisualShaderConnectionStartGraphicsObject::~VisualShaderConnectionStartGraphicsObject() {} + +QRectF VisualShaderConnectionStartGraphicsObject::boundingRect() const { + return rect; +} + +void VisualShaderConnectionStartGraphicsObject::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { + painter->setClipRect(option->exposedRect); + + painter->setBrush(this->connection_point_color); + painter->drawEllipse(rect); +} + +VisualShaderConnectionEndGraphicsObject::VisualShaderConnectionEndGraphicsObject(const QRectF& rect, QGraphicsItem* parent) : QGraphicsObject(parent), + rect(rect) { + setFlag(QGraphicsItem::ItemIsMovable, true); + setFlag(QGraphicsItem::ItemIsFocusable, true); + setFlag(QGraphicsItem::ItemIsSelectable, true); + + setVisible(true); + setOpacity(this->opacity); + + setZValue(-1.0f); +} + +VisualShaderConnectionEndGraphicsObject::~VisualShaderConnectionEndGraphicsObject() {} + +QRectF VisualShaderConnectionEndGraphicsObject::boundingRect() const { + return rect; +} + +void VisualShaderConnectionEndGraphicsObject::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { + painter->setClipRect(option->exposedRect); + + painter->setBrush(this->connection_point_color); + painter->drawEllipse(rect); } /**********************************************************************/ diff --git a/Editors/VisualShaderEditor.h b/Editors/VisualShaderEditor.h index 49acf6297..6b1139cfa 100644 --- a/Editors/VisualShaderEditor.h +++ b/Editors/VisualShaderEditor.h @@ -429,17 +429,82 @@ class VisualShaderOutputPortGraphicsObject : public QGraphicsObject { /**********************************************************************/ /**********************************************************************/ +class VisualShaderConnectionStartGraphicsObject; +class VisualShaderConnectionEndGraphicsObject; + class VisualShaderConnectionGraphicsObject : public QGraphicsObject { Q_OBJECT public: - VisualShaderConnectionGraphicsObject(QGraphicsItem *parent = nullptr); + VisualShaderConnectionGraphicsObject(const int& from_n_id, const int& from_p_index, QGraphicsItem *parent = nullptr); ~VisualShaderConnectionGraphicsObject(); private: + int from_n_id; + int to_n_id; + int from_p_index; + int to_p_index; + + QPointF start_coordinate = QPointF(100, 100); + QPointF end_coordinate = QPointF(200, 200); + + VisualShaderConnectionStartGraphicsObject* start_graphics_object; + VisualShaderConnectionEndGraphicsObject* end_graphics_object; + + // Style + QColor construction_color = QColor(169, 169, 169); + QColor normal_color = QColor(0, 255, 255); + QColor selected_color = QColor(100, 100, 100); + QColor selected_halo_color = QColor(255, 165, 0); + + float line_width = 3.0f; + float construction_line_width = 2.0f; + float point_diameter = 10.0f; + + mutable float rect_padding; // Calculated in boundingRect() + + float min_h_distance = 50.0f; + float abnormal_face_to_back_control_width_expansion_factor = 0.5f; + float abnormal_face_to_back_control_height_expansion_factor = 2.0f; + QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override; + int detect_quadrant(const QPointF& reference, const QPointF& target) const; + QRectF calculate_bounding_rect_from_coordinates(const QPointF& start_coordinate, const QPointF& end_coordinate) const; + std::pair calculate_control_points(const QPointF& start_coordinate, const QPointF& end_coordinate) const; +}; + +class VisualShaderConnectionStartGraphicsObject : public QGraphicsObject { +public: + VisualShaderConnectionStartGraphicsObject(const QRectF& rect, QGraphicsItem* parent = nullptr); + ~VisualShaderConnectionStartGraphicsObject(); + + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + +private: + QRectF rect; + + // Style + QColor connection_point_color = QColor(169, 169, 169); + float opacity = 1.0f; +}; + +class VisualShaderConnectionEndGraphicsObject : public QGraphicsObject { +public: + VisualShaderConnectionEndGraphicsObject(const QRectF& rect, QGraphicsItem* parent = nullptr); + ~VisualShaderConnectionEndGraphicsObject(); + + QRectF boundingRect() const override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + +private: + QRectF rect; + + // Style + QColor connection_point_color = QColor(169, 169, 169); + float opacity = 1.0f; }; /**********************************************************************/