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