1 /*! 2 @file 3 @author Albert Semenov 4 @date 01/2009 5 */ 6 #ifndef BASE_GRAPH_VIEW_H_ 7 #define BASE_GRAPH_VIEW_H_ 8 9 #include <MyGUI.h> 10 #include "MyGUI_Canvas.h" 11 #include "BaseGraphNode.h" 12 #include "ConnectionInfo.h" 13 14 namespace wraps 15 { 16 17 class BaseGraphView : 18 public BaseLayout, 19 public IGraphController 20 { 21 public: 22 typedef std::vector<BaseGraphNode*> VectorGraphNode; 23 typedef MyGUI::Enumerator<VectorGraphNode> EnumeratorNode; 24 BaseGraphView(const std::string & _layout,MyGUI::Widget * _parent)25 BaseGraphView(const std::string& _layout, MyGUI::Widget* _parent) : 26 BaseLayout(_layout, _parent), 27 mCanvas(nullptr), 28 mIsDrug(false), 29 mConnectionStart(nullptr), 30 mInvalidate(false), 31 mCurrentIndexConnection(0) 32 { 33 MyGUI::Gui::getInstance().eventFrameStart += MyGUI::newDelegate(this, &BaseGraphView::notifyFrameStart); 34 } 35 ~BaseGraphView()36 virtual ~BaseGraphView() 37 { 38 MyGUI::Gui::getInstance().eventFrameStart -= MyGUI::newDelegate(this, &BaseGraphView::notifyFrameStart); 39 } 40 notifyFrameStart(float _time)41 void notifyFrameStart(float _time) 42 { 43 if (mInvalidate) 44 { 45 mInvalidate = false; 46 updateCanvasImpl(); 47 } 48 } 49 addItem(BaseGraphNode * _node)50 void addItem(BaseGraphNode* _node) 51 { 52 mNodes.push_back(_node); 53 _node->_initialise(mCanvas, this); 54 55 changePosition(_node); 56 } 57 removeItem(BaseGraphNode * _node)58 void removeItem(BaseGraphNode* _node) 59 { 60 VectorGraphNode::iterator item = std::find(mNodes.begin(), mNodes.end(), _node); 61 MYGUI_ASSERT(item != mNodes.end(), "Item not found"); 62 63 removeAllConnections(_node); 64 _node->_shutdown(); 65 66 mNodes.erase(item); 67 68 changePosition(nullptr); 69 } 70 removeAllItems()71 void removeAllItems() 72 { 73 for (VectorGraphNode::iterator item = mNodes.begin(); item != mNodes.end(); ++item) 74 { 75 removeAllConnections((*item)); 76 (*item)->_shutdown(); 77 (*item) = nullptr; 78 } 79 mNodes.clear(); 80 81 changePosition(nullptr); 82 } 83 getNodeEnumerator()84 EnumeratorNode getNodeEnumerator() const 85 { 86 return EnumeratorNode(mNodes); 87 } 88 isConnecting(BaseGraphConnection * _from,BaseGraphConnection * _to)89 bool isConnecting(BaseGraphConnection* _from, BaseGraphConnection* _to) const 90 { 91 EnumeratorConnection conn = _from->getConnectionEnumerator(); 92 while (conn.next()) 93 { 94 if (conn.current() == _to) 95 { 96 return true; 97 } 98 } 99 return false; 100 } 101 getClient()102 MyGUI::Widget* getClient() const 103 { 104 return mCanvas; 105 } 106 107 /*event:*/ 108 /** Request : Connection point.\n 109 signature : void method(wraps::BaseGraphView* _sender, wraps::BaseGraphConnection* _from, wraps::BaseGraphConnection* _to, bool& _result) 110 @param _sender 111 @param _from 112 @param _to 113 @param _result 114 */ 115 MyGUI::delegates::CDelegate4<BaseGraphView*, BaseGraphConnection*, BaseGraphConnection*, bool&> requestConnectPoint; 116 117 /** Request : Disconnection point.\n 118 signature : void method(wraps::BaseGraphView* _sender, wraps::BaseGraphConnection* _from, wraps::BaseGraphConnection* _to, bool& _result) 119 @param _sender 120 @param _from 121 @param _to 122 @param _result 123 */ 124 MyGUI::delegates::CDelegate4<BaseGraphView*, BaseGraphConnection*, BaseGraphConnection*, bool&> requestDisconnectPoint; 125 126 /** Event : Connection point.\n 127 signature : void method(wraps::BaseGraphView* _sender, wraps::BaseGraphConnection* _from, wraps::BaseGraphConnection* _to) 128 @param _sender 129 @param _from 130 @param _to 131 */ 132 MyGUI::delegates::CDelegate3<BaseGraphView*, BaseGraphConnection*, BaseGraphConnection*> eventConnectPoint; 133 134 /** Event : Disconnection point.\n 135 signature : void method(wraps::BaseGraphView* _sender, wraps::BaseGraphConnection* _from, wraps::BaseGraphConnection* _to) 136 @param _sender 137 @param _from 138 @param _to 139 */ 140 MyGUI::delegates::CDelegate3<BaseGraphView*, BaseGraphConnection*, BaseGraphConnection*> eventDisconnectPoint; 141 142 /** Event : Change size.\n 143 signature : void method(wraps::BaseGraphView* _sender, MyGUI::IntSize _size) 144 @param _sender 145 @param _size 146 */ 147 MyGUI::delegates::CDelegate2<BaseGraphView*, MyGUI::IntSize> eventChangeSize; 148 149 /** Event : Node closed.\n 150 signature : void method(wraps::BaseGraphView* _sender, wraps::BaseGraphNode* _node) 151 @param _sender 152 @param _id 153 */ 154 MyGUI::delegates::CDelegate2<BaseGraphView*, BaseGraphNode*> eventNodeClosed; 155 156 protected: setCanvasWidget(const std::string & _widgetName)157 void setCanvasWidget(const std::string& _widgetName) 158 { 159 assignWidget(mCanvas, _widgetName); 160 161 updateCanvas(); 162 } 163 164 private: removeAllConnections(BaseGraphNode * _node)165 void removeAllConnections(BaseGraphNode* _node) 166 { 167 EnumeratorConnection node_conn = _node->getConnectionEnumerator(); 168 while (node_conn.next()) 169 { 170 // удаляем прямые соединения 171 while (node_conn.current()->isAnyConnection()) 172 { 173 EnumeratorConnection conn = node_conn.current()->getConnectionEnumerator(); 174 while (conn.next()) 175 { 176 eventDisconnectPoint(this, node_conn.current(), conn.current()); 177 node_conn.current()->removeConnectionPoint(conn.current()); 178 break; 179 } 180 } 181 182 // удаляем обратные соединения 183 while (node_conn.current()->isAnyReverseConnection()) 184 { 185 EnumeratorConnection conn = node_conn.current()->getReverseConnectionEnumerator(); 186 while (conn.next()) 187 { 188 eventDisconnectPoint(this, conn.current(), node_conn.current()); 189 conn.current()->removeConnectionPoint(node_conn.current()); 190 break; 191 } 192 } 193 } 194 } 195 close(BaseGraphNode * _node)196 virtual void close(BaseGraphNode* _node) 197 { 198 eventNodeClosed(this, _node); 199 } 200 startDrag(BaseGraphConnection * _connection)201 virtual void startDrag(BaseGraphConnection* _connection) 202 { 203 if ( ! mIsDrug ) 204 { 205 bool result = false; 206 requestConnectPoint(this, _connection, nullptr, result); 207 208 // тащим новый конект 209 if (result) 210 { 211 mIsDrug = true; 212 213 const MyGUI::IntCoord& coord = _connection->getAbsoluteCoord(); 214 215 mDrugLine.colour.set(1, 1, 1, 1); 216 mDrugLine.start_offset.clear(); 217 mDrugLine.end_offset.clear(); 218 mDrugLine.point_start.set( 219 coord.left + (coord.width / 2) - mCanvas->getAbsoluteLeft(), 220 coord.top + (coord.height / 2) - mCanvas->getAbsoluteTop() 221 ); 222 mDrugLine.point_end = mDrugLine.point_start; 223 224 mConnectionStart = _connection; 225 226 updateCanvas(); 227 } 228 // разрываем существующий 229 else 230 { 231 BaseGraphConnection* drag_node = nullptr; 232 bool disconect = false; 233 // прямое сочленение 234 if (_connection->isAnyConnection()) 235 { 236 EnumeratorConnection conn = _connection->getConnectionEnumerator(); 237 while (conn.next()) 238 { 239 result = false; 240 requestDisconnectPoint(this, _connection, conn.current(), result); 241 if (result) 242 { 243 drag_node = _connection; 244 eventDisconnectPoint(this, _connection, conn.current()); 245 _connection->removeConnectionPoint(conn.current()); 246 disconect = true; 247 } 248 break; 249 } 250 } 251 else 252 { 253 // обратное сочленение 254 EnumeratorConnection conn = _connection->getReverseConnectionEnumerator(); 255 while (conn.next()) 256 { 257 result = false; 258 requestDisconnectPoint(this, conn.current(), _connection, result); 259 if (result) 260 { 261 drag_node = conn.current(); 262 eventDisconnectPoint(this, conn.current(), _connection); 263 conn.current()->removeConnectionPoint(_connection); 264 disconect = true; 265 } 266 break; 267 } 268 } 269 270 // тащим разорваную связь 271 if (disconect) 272 { 273 mIsDrug = true; 274 275 const MyGUI::IntCoord& coord = drag_node->getAbsoluteCoord(); 276 277 mDrugLine.colour.set(1, 1, 1, 1); 278 mDrugLine.start_offset.clear(); 279 mDrugLine.end_offset.clear(); 280 mDrugLine.point_start.set( 281 coord.left + (coord.width / 2) - mCanvas->getAbsoluteLeft(), 282 coord.top + (coord.height / 2) - mCanvas->getAbsoluteTop() 283 ); 284 mDrugLine.point_end = mDrugLine.point_start; 285 286 mConnectionStart = drag_node; 287 288 updateCanvas(); 289 290 updateDrag(nullptr); 291 } 292 293 } 294 } 295 } 296 stopDrag(BaseGraphConnection * _connection)297 virtual void stopDrag(BaseGraphConnection* _connection) 298 { 299 if (mIsDrug) 300 { 301 // нод откуда тянется не всегда может быть сендером 302 _connection = mConnectionStart; 303 304 connectPoint(_connection); 305 306 mIsDrug = false; 307 mConnectionStart = nullptr; 308 309 updateCanvas(); 310 } 311 } 312 updateDrag(BaseGraphConnection * _connection)313 virtual void updateDrag(BaseGraphConnection* _connection) 314 { 315 if (mIsDrug) 316 { 317 // нод откуда тянется не всегда может быть сендером 318 _connection = mConnectionStart; 319 320 const MyGUI::IntPoint& mouse = MyGUI::InputManager::getInstance().getMousePosition(); 321 //const MyGUI::IntCoord& coord = _node->getAbsoluteCoord(); 322 mDrugLine.point_end.set(mouse.left - mCanvas->getAbsoluteLeft(), mouse.top - mCanvas->getAbsoluteTop()); 323 324 // устанавлваем длинну загиба от дистанции 325 double distance = ((mDrugLine.point_end.left - mDrugLine.point_start.left) * (mDrugLine.point_end.left - mDrugLine.point_start.left)) + 326 ((mDrugLine.point_end.top - mDrugLine.point_start.top) * (mDrugLine.point_end.top - mDrugLine.point_start.top)); 327 distance = std::sqrt(distance); 328 329 mDrugLine.start_offset = _connection->getOffset(); 330 331 const int offset = 30; 332 distance *= 0.5; 333 if (distance < 1) distance = 1; 334 else if (distance > offset) distance = offset; 335 if (mDrugLine.start_offset.height != 0) 336 { 337 if (mDrugLine.start_offset.height < 0) mDrugLine.start_offset.height = -(int)distance; 338 else mDrugLine.start_offset.height = (int)distance; 339 } 340 if (mDrugLine.start_offset.width != 0) 341 { 342 if (mDrugLine.start_offset.width < 0) mDrugLine.start_offset.width = -(int)distance; 343 else mDrugLine.start_offset.width = (int)distance; 344 } 345 346 // пикаем виджет под нами 347 MyGUI::Widget* widget = MyGUI::LayerManager::getInstance().getWidgetFromPoint(mouse.left, mouse.top); 348 if (widget != nullptr) 349 { 350 BaseGraphConnection** connection = widget->getUserData<BaseGraphConnection*>(false); 351 if (connection != nullptr) 352 { 353 bool accept = requestConnectToPoint(_connection, *connection); 354 if (accept) 355 mDrugLine.colour = MyGUI::Colour::Green; 356 else 357 mDrugLine.colour = MyGUI::Colour::Red; 358 } 359 else 360 { 361 mDrugLine.colour = MyGUI::Colour::White; 362 } 363 } 364 else 365 { 366 mDrugLine.colour = MyGUI::Colour::White; 367 } 368 369 updateCanvas(); 370 } 371 } 372 changePosition(BaseGraphNode * _node)373 virtual void changePosition(BaseGraphNode* _node) 374 { 375 eventChangeSize(this, getViewSize()); 376 377 updateCanvas(); 378 } 379 updateCanvas()380 void updateCanvas() 381 { 382 mInvalidate = true; 383 } 384 updateCanvasImpl()385 void updateCanvasImpl() 386 { 387 clearCanvas(); 388 389 // проходим по всем нодам и перерисовываем связи 390 for (size_t index = 0; index < mNodes.size(); ++index) 391 { 392 EnumeratorConnection node_point = mNodes[index]->getConnectionEnumerator(); 393 while (node_point.next()) 394 { 395 const MyGUI::IntCoord& coord_from = node_point->getAbsoluteCoord(); 396 EnumeratorConnection connect_point = node_point->getConnectionEnumerator(); 397 while (connect_point.next()) 398 { 399 const MyGUI::IntCoord& coord_to = connect_point->getAbsoluteCoord(); 400 401 ConnectionInfo info( 402 coord_from.point() - mCanvas->getAbsolutePosition() + MyGUI::IntPoint(coord_from.width / 2, coord_from.height / 2), 403 coord_to.point() - mCanvas->getAbsolutePosition() + MyGUI::IntPoint(coord_to.width / 2, coord_to.height / 2), 404 MyGUI::Colour::White, 405 node_point->getOffset(), 406 connect_point->getOffset()); 407 408 drawCurve(info); 409 } 410 } 411 } 412 413 // ниточка для драга 414 if (mIsDrug) 415 drawCurve(mDrugLine); 416 } 417 connectPoint(BaseGraphConnection * _connection)418 void connectPoint(BaseGraphConnection* _connection) 419 { 420 const MyGUI::IntPoint& mouse = MyGUI::InputManager::getInstance().getMousePosition(); 421 422 // пикаем виджет под нами 423 MyGUI::Widget* widget = MyGUI::LayerManager::getInstance().getWidgetFromPoint(mouse.left, mouse.top); 424 if (widget != nullptr) 425 { 426 BaseGraphConnection** connection = widget->getUserData<BaseGraphConnection*>(false); 427 if (connection != nullptr) 428 { 429 bool accept = requestConnectToPoint(_connection, *connection); 430 if (accept) 431 { 432 eventConnectToPoint(_connection, *connection); 433 } 434 } 435 } 436 } 437 requestConnectToPoint(BaseGraphConnection * _from,BaseGraphConnection * _to)438 bool requestConnectToPoint(BaseGraphConnection* _from, BaseGraphConnection* _to) 439 { 440 bool result = false; 441 requestConnectPoint(this, _from, _to, result); 442 443 return result; 444 } 445 eventConnectToPoint(BaseGraphConnection * _from,BaseGraphConnection * _to)446 void eventConnectToPoint(BaseGraphConnection* _from, BaseGraphConnection* _to) 447 { 448 _from->addConnectionPoint(_to); 449 eventConnectPoint(this, _from, _to); 450 } 451 clearCanvas()452 void clearCanvas() 453 { 454 for (MyGUI::VectorWidgetPtr::iterator item = mConnections.begin(); item != mConnections.end(); ++item) 455 (*item)->setVisible(false); 456 mCurrentIndexConnection = 0; 457 } 458 getNextWidget()459 MyGUI::Widget* getNextWidget() 460 { 461 MyGUI::Widget* result = nullptr; 462 463 if (mCurrentIndexConnection < mConnections.size()) 464 { 465 result = mConnections[mCurrentIndexConnection]; 466 } 467 else 468 { 469 result = mCanvas->createWidget<MyGUI::Widget>("PolygonalSkin", mCanvas->getCoord(), MyGUI::Align::Default); 470 result->setNeedMouseFocus(false); 471 mConnections.push_back(result); 472 } 473 474 mCurrentIndexConnection++; 475 476 result->setVisible(true); 477 return result; 478 } 479 drawSpline(const ConnectionInfo & _info,int _offset,const MyGUI::Colour & _colour)480 void drawSpline(const ConnectionInfo& _info, int _offset, const MyGUI::Colour& _colour) 481 { 482 MyGUI::Widget* widget = getNextWidget(); 483 widget->setColour(_colour); 484 485 MyGUI::ISubWidget* main = widget->getSubWidgetMain(); 486 MyGUI::PolygonalSkin* polygonalSkin = main->castType<MyGUI::PolygonalSkin>(); 487 polygonalSkin->setWidth(4.0f); 488 489 const size_t PointsNumber = 16; 490 std::vector<MyGUI::FloatPoint> basePoints; 491 basePoints.push_back( 492 MyGUI::FloatPoint((float)_info.point_start.left, (float)_info.point_start.top + _offset)); 493 basePoints.push_back( 494 MyGUI::FloatPoint((float)_info.point_start.left + _info.start_offset.width, (float)_info.point_start.top + _info.start_offset.height + _offset)); 495 basePoints.push_back( 496 MyGUI::FloatPoint((float)_info.point_end.left + _info.end_offset.width, (float)_info.point_end.top + _info.end_offset.height + _offset)); 497 basePoints.push_back( 498 MyGUI::FloatPoint((float)_info.point_end.left, (float)_info.point_end.top + _offset)); 499 std::vector<MyGUI::FloatPoint> splinePoints; 500 splinePoints.reserve(PointsNumber); 501 for (size_t i = 0; i < PointsNumber; ++i) 502 { 503 float t = float(i) / (PointsNumber - 1); 504 float left = basePoints[0].left * pow(1 - t, 3) + 3 * basePoints[1].left * pow(1 - t, 2) * t + 3 * basePoints[2].left * (1 - t) * t * t + t * t * t * basePoints[3].left; 505 float top = basePoints[0].top * pow(1 - t, 3) + 3 * basePoints[1].top * pow(1 - t, 2) * t + 3 * basePoints[2].top * (1 - t) * t * t + t * t * t * basePoints[3].top; 506 splinePoints.push_back(MyGUI::FloatPoint(left, top)); 507 } 508 polygonalSkin->setPoints(splinePoints); 509 } 510 drawCurve(const ConnectionInfo & _info)511 void drawCurve(const ConnectionInfo& _info) 512 { 513 drawSpline(_info, 3, MyGUI::Colour(0.3f, 0.3f, 0.3f, 0.8f)); 514 drawSpline(_info, 0, _info.colour); 515 } 516 getViewSize()517 MyGUI::IntSize getViewSize() 518 { 519 MyGUI::IntSize result; 520 for (size_t index = 0; index < mNodes.size(); ++index) 521 { 522 const MyGUI::IntCoord& coord = mNodes[index]->getCoord(); 523 if (coord.right() > result.width) result.width = coord.right(); 524 if (coord.bottom() > result.height) result.height = coord.bottom(); 525 } 526 527 // для соединений справа 528 result.width += 10; 529 result.height += 10; 530 531 return result; 532 } 533 534 private: 535 VectorGraphNode mNodes; 536 537 MyGUI::Widget* mCanvas; 538 bool mIsDrug; 539 ConnectionInfo mDrugLine; 540 BaseGraphConnection* mConnectionStart; 541 bool mInvalidate; 542 MyGUI::VectorWidgetPtr mConnections; 543 size_t mCurrentIndexConnection; 544 }; 545 546 } // namespace wraps 547 548 #endif // BASE_GRAPH_VIEW_H_ 549