1 /**
2 Copyright: Copyright (c) 2013 Andrey Penechko.
3 License: a$(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 
7 module anchovy.gui.behaviors.dockingrootbehavior;
8 
9 import anchovy.gui;
10 import anchovy.gui.interfaces.iwidgetbehavior;
11 import std.math : abs;
12 
13 // version = Button_debug;
14 
15 enum dragTreshold = 10;
16 
17 class DockingRootBehavior : IWidgetBehavior
18 {
19 protected:
20 	Widget _dockingRoot;
21 	Widget _undockedStorage;
22 	Rect _dropRect;
23 	Widget _hoveredDocked;
24 	Widget _hoveredDockedParent;
25 	Sides _hoveredSide;
26 
27 	VerticalLayout vlayout;
28 	HorizontalLayout hlayout;
29 	HorizontalLayout boxlayout;
30 
31 public:
32 
33 	this()
34 	{
35 		vlayout = new VerticalLayout;
36 		hlayout = new HorizontalLayout;
37 		boxlayout = hlayout;
38 	}
39 
40 	override void attachTo(Widget widget)
41 	{
42 		_dockingRoot = widget;
43 		_dockingRoot.addEventHandler(&handleDraw);
44 		_dockingRoot["isDocked"] = true;
45 	}
46 
47 	void registerUndockedStorage(Widget storage)
48 	{
49 		assert(storage);
50 
51 		_undockedStorage = storage;
52 	}
53 
54 	void registerFrame(Widget frame)
55 	{
56 		assert(frame);
57 		assert(_undockedStorage);
58 
59 		frame["isDocked"] = false;
60 
61 		// extract frame header
62 		auto frameHeader = frame["subwidgets"].get!(Widget[string]).get("header", null);
63 
64 		if (frameHeader && frameHeader.getWidgetBehavior!DragableBehavior)
65 		{
66 			// replace handler
67 			frameHeader.removeEventHandlers!DragEvent;
68 			frameHeader.addEventHandler(&handleFrameHeaderDrag);
69 			frameHeader.addEventHandler(&handleFrameHeaderDragEnd);
70 		}
71 
72 		_undockedStorage.addChild(frame);
73 	}
74 
75 	bool handleFrameHeaderDrag(Widget frameHeader, DragEvent event)
76 	{
77 		Widget frame = frameHeader["root"].get!Widget;
78 
79 		if (frame["isDocked"].get!bool == true)
80 		{
81 			if (abs(event.delta.x) > dragTreshold || abs(event.delta.y) > dragTreshold)
82 			{
83 				handleUndock(frame);
84 				// Center header after undocking
85 				ivec2 newPosition = event.pointerPosition - frame.getPropertyAs!("prefSize", ivec2) / 2;
86 				frame["position"] = newPosition;
87 				event.dragOffset = event.pointerPosition - newPosition;
88 			}
89 		}
90 		else
91 		{
92 			frame["position"] = frame.getPropertyAs!("position", ivec2) + event.delta;
93 			checkForDropAt(event.pointerPosition);
94 		}
95 
96 		return true;
97 	}
98 
99 	bool handleFrameHeaderDragEnd(Widget frameHeader, DragEndEvent event)
100 	{
101 		Widget frame = frameHeader["root"].get!Widget;
102 
103 		if (_hoveredDocked)
104 		{
105 			handleDock(frame, _hoveredDocked, _hoveredDockedParent, _hoveredSide);
106 		}
107 
108 		return true;
109 	}
110 
111 	void checkForDropAt(ivec2 position)
112 	{
113 		Widget[] widgetChain = buildPathToLeaf!(EventDispatcher.containsPointer)(_dockingRoot, position);
114 
115 		_hoveredDocked = null;
116 		_hoveredDockedParent = null;
117 
118 		// Find docked widget
119 		foreach_reverse(widget; widgetChain)
120 		{
121 			if (widget.hasProperty!"isDocked" && widget.getPropertyAs!("isDocked", bool))
122 			{
123 				_hoveredDocked = widget;
124 				break;
125 			}
126 		}
127 
128 		Rect widgetRect;
129 
130 		// If docked is found get its rect, or return otherwise
131 		if (_hoveredDocked)
132 		{
133 			widgetRect = _hoveredDocked.getPropertyAs!("staticRect", Rect);
134 			if (_hoveredDocked !is _dockingRoot)
135 			{
136 				_hoveredDockedParent = _hoveredDocked.getPropertyAs!("parent", Widget);
137 			}
138 		}
139 		else
140 		{
141 			return;
142 		}
143 
144 		// Find corner positions
145 		ivec2 leftTop = widgetRect.position;
146 		ivec2 rightBottom = widgetRect.position + widgetRect.size;
147 		ivec2 rightTop = widgetRect.position;
148 			rightTop.x += widgetRect.size.x;
149 		ivec2 leftBottom = widgetRect.position;
150 			leftBottom.y += widgetRect.size.y;
151 
152 		// Find the sector of rect hit by cursor
153 		// \T/
154 		// LXR
155 		// /B\
156 
157 		// D = (х3 - х1) * (у2 - у1) - (у3 - у1) * (х2 - х1)
158 		// diagonal \
159 		int cursorPos1 = (position.x - leftTop.x) * (rightBottom.y - leftTop.y)
160 						- (position.y - leftTop.y) * (rightBottom.x - leftTop.x);
161 
162 		// diagonal /
163 		int cursorPos2 = (position.x - leftBottom.x) * (rightTop.y - leftBottom.y)
164 						- (position.y - leftBottom.y) * (rightTop.x - leftBottom.x);
165 
166 		// Calculate rect that will be highlighted
167 		_dropRect = widgetRect;
168 
169 		if (cursorPos1 > 0) //right top
170 		{
171 			if (cursorPos2 > 0) // left top
172 			{
173 				_dropRect.height /= 2;
174 				_hoveredSide = Sides.top;
175 			}
176 			else // right bottom
177 			{
178 				_dropRect.width /= 2;
179 				_dropRect.x += _dropRect.width;
180 				_hoveredSide = Sides.right;
181 			}
182 		}
183 		else // left bottom
184 		{
185 			if (cursorPos2 > 0) // left top
186 			{
187 				_dropRect.width /= 2;
188 				_hoveredSide = Sides.left;
189 			}
190 			else // right bottom
191 			{
192 				_dropRect.height /= 2;
193 				_dropRect.y += _dropRect.height;
194 				_hoveredSide = Sides.bottom;
195 			}
196 		}
197 	}
198 
199 	void handleDock(Widget floating, Widget docked, Widget dockedParent, Sides side)
200 	{
201 		assert(docked);
202 		assert(floating);
203 
204 		floating["isDocked"] = true;
205 		floating.detachFromParent;
206 
207 		// Drop over the empty dockRoot
208 		if (!dockedParent)
209 		{
210 			docked.addChild(floating);
211 			docked.setProperty!("layout", ILayout)(boxlayout);
212 		}
213 		else
214 		{
215 			auto layout = dockedParent.getPropertyAs!("layout", ILayout);
216 
217 			void splitDocked(Widget floating, bool after, ILayout layout)
218 			{
219 				auto context = floating.getPropertyAs!("context", GuiContext);
220 				auto container = context.createWidget("dockContainer");
221 
222 				container.setProperty!("layout", ILayout)(layout);
223 
224 				dockedParent.replaceChildBy(docked, container);
225 
226 				if (after)
227 				{
228 					container.addChild(docked);
229 					container.addChild(floating);
230 				}
231 				else
232 				{
233 					container.addChild(floating);
234 					container.addChild(docked);
235 				}
236 			}
237 
238 			if (layout is hlayout)
239 			{
240 				final switch(side)
241 				{
242 					case Sides.left:
243 						dockedParent.addChildBefore(floating, docked);
244 						break;
245 					case Sides.right:
246 						dockedParent.addChildAfter(floating, docked);
247 						break;
248 					case Sides.top:
249 						splitDocked(floating, false, vlayout);
250 						break;
251 					case Sides.bottom:
252 						splitDocked(floating, true, vlayout);
253 						break;
254 				}
255 			}
256 			else
257 			{
258 				final switch(side)
259 				{
260 					case Sides.left:
261 						splitDocked(floating, false, hlayout);
262 						break;
263 					case Sides.right:
264 						splitDocked(floating, true, hlayout);
265 						break;
266 					case Sides.top:
267 						dockedParent.addChildBefore(floating, docked);
268 						break;
269 					case Sides.bottom:
270 						dockedParent.addChildAfter(floating, docked);
271 						break;
272 				}
273 			}
274 		}
275 
276 		_hoveredDocked = null;
277 		_hoveredDockedParent = null;
278 	}
279 
280 	void foldContainer(Widget container)
281 	{
282 		assert(container !is _dockingRoot);
283 
284 		Widget[] children = container.getPropertyAs!("children", Widget[]);
285 		Widget child = children[0];
286 		child.detachFromParent;
287 
288 		Widget parent = container.getPropertyAs!("parent", Widget);
289 
290 		parent.replaceChildBy(container, child);
291 	}
292 
293 	void handleUndock(Widget docked)
294 	{
295 		Widget container = docked.getPropertyAs!("parent", Widget);
296 
297 		docked.detachFromParent;
298 		_undockedStorage.addChild(docked);
299 		docked["isDocked"] = false;
300 
301 		Widget[] children = container.getPropertyAs!("children", Widget[]);
302 		if (children.length == 1 && container !is _dockingRoot)
303 		{
304 			foldContainer(container);
305 		}
306 	}
307 
308 	bool handleDraw(Widget dockRoot, DrawEvent event)
309 	{
310 		if (_hoveredDocked)
311 		{
312 			event.guiRenderer.renderer.setColor(Color(0,0,255, 64));
313 			event.guiRenderer.renderer.fillRect(_dropRect);
314 		}
315 
316 		return true;
317 	}
318 }
319