1 module auxil.default_visitor;
2 
3 import std.typecons : Flag;
4 
5 import auxil.location : Axis, Location, SizeType, Order;
6 import auxil.model : Orientation;
7 
8 alias SizeEnabled     = Flag!"SizeEnabled";
9 alias TreePathEnabled = Flag!"TreePathEnabled";
10 
11 private struct Default
12 {
13 	void enterTree(Order order, Data, Model)(ref const(Data) data, ref Model model) {}
14 	void enterNode(Order order, Data, Model)(ref const(Data) data, ref Model model) {}
15 	void leaveNode(Order order, Data, Model)(ref const(Data) data, ref Model model) {}
16 	void beforeChildren(Order order, Data, Model)(ref const(Data) data, ref Model model) {}
17 	void afterChildren(Order order, Data, Model)(ref const(Data) data, ref Model model) {}
18 }
19 
20 alias MeasuringVisitor = MeasuringVisitorImpl!Default;
21 
22 /// Visitor to measure size of tree nodes
23 struct MeasuringVisitorImpl(Derived = Default)
24 {
25 	SizeType[2] size;
26 
27 	this(SizeType[2] s) @safe @nogc nothrow
28 	{
29 		size = s;
30 	}
31 
32 	enum treePathEnabled = TreePathEnabled.no;
33 
34 	Orientation orientation = Orientation.Vertical;
35 	Orientation old_orientation;
36 
37 	bool complete() @safe @nogc
38 	{
39 		return false;
40 	}
41 
42 	bool engaged() @safe @nogc nothrow
43 	{
44 		return true;
45 	}
46 
47 	void doEnterTree(Order order, Data, Model)(auto ref const(Data) data, ref Model model)
48 	{
49 		() @trusted { (cast(Derived*) &this).enterTree!(order, Data, Model)(data, model); }();
50 	}
51 
52 	void doEnterNode(Order order, Data, Model)(ref const(Data) data, ref Model model)
53 	{
54 		static if (Model.Collapsable)
55 		{
56 			model.size = size[model.orientation] + model.Spacing;
57 			final switch (model.orientation)
58 			{
59 				case Orientation.Vertical:
60 					model.header_size = model.size;
61 				break;
62 				case Orientation.Horizontal:
63 					model.header_size = size[Orientation.Vertical] + model.Spacing;
64 				break;
65 			}
66 			old_orientation = orientation;
67 			orientation  = model.orientation;
68 		}
69 		else
70 			model.size = size[orientation] + model.Spacing;
71 
72 		final switch (orientation)
73 		{
74 			// shrink size of the window
75 			case Orientation.Vertical:
76 				size[Orientation.Horizontal] -= size[Orientation.Vertical] + model.Spacing;
77 			break;
78 			case Orientation.Horizontal:
79 				// do nothing
80 			break;
81 		}
82 
83 		if (engaged)
84 		{
85 			() @trusted { (cast(Derived*) &this).enterNode!(order, Data, Model)(data, model); }();
86 		}
87 	}
88 
89 	void doLeaveNode(Order order, Data, Model)(ref const(Data) data, ref Model model)
90 	{
91 		if (engaged)
92 		{
93 			() @trusted { (cast(Derived*) &this).leaveNode!(order, Data, Model)(data, model); }();
94 		}
95 
96 		final switch (this.orientation)
97 		{
98 			// restore shrinked size of the window
99 			case Orientation.Vertical:
100 				size[Orientation.Horizontal] += size[Orientation.Vertical] + model.Spacing;
101 			break;
102 			case Orientation.Horizontal:
103 				// do nothing again
104 			break;
105 		}
106 
107 		static if (Model.Collapsable)
108 			orientation = old_orientation;
109 	}
110 
111 	/// returns true if the current children shouldn't be processed
112 	/// but traversing should be continued
113 	bool doBeforeChildren(Order order, Data, Model)(ref const(Data) data, ref Model model)
114 	{
115 		if (orientation == Orientation.Horizontal)
116 		{
117 			size[orientation] -= size[Orientation.Vertical] + model.Spacing;
118 		}
119 
120 		() @trusted { (cast(Derived*) &this).beforeChildren!(order, Data, Model)(data, model); }();
121 
122 		return false;
123 	}
124 
125 	void doAfterChildren(Order order, Data, Model)(ref const(Data) data, ref Model model)
126 	{
127 		() @trusted { (cast(Derived*) &this).afterChildren!(order, Data, Model)(data, model); }();
128 
129 		if (orientation == Orientation.Horizontal)
130 		{
131 			size[orientation] += size[Orientation.Vertical] + model.Spacing;
132 		}
133 	}
134 
135 	auto startValue(Order order)(size_t len)
136 	{
137 		return 0;
138 	}
139 
140 	auto setPath(int i)
141 	{
142 	}
143 
144 	auto setChildSize(Model, ChildModel)(ref Model model, ref ChildModel child_model, int len, ref float residual)
145 	{
146 		final switch(model.orientation)
147 		{
148 			case Orientation.Horizontal:
149 				double sf = cast(double)(model.size)/len;
150 				SizeType sz = cast(SizeType)sf;
151 				residual += sf - sz;
152 				if (residual >= 1.0)
153 				{
154 					residual -= 1;
155 					sz += 1;
156 				}
157 				child_model.size = sz;
158 			break;
159 			case Orientation.Vertical:
160 				model.size += modelSize(child_model, orientation);
161 			break;
162 		}
163 	}
164 
165 	private auto modelSize(Model)(ref const(Model) model, Orientation orientation)
166 	{
167 		static if (Model.Collapsable)
168 		{
169 			// if modelSizeIsValid is true the model size is defined by the model
170 			// in other case it is defined by the visitor
171 			static if (Model.DynamicCollapsable)
172 				bool modelSizeIsValid = model.rtCollapsable;
173 			else
174 				bool modelSizeIsValid = true;
175 
176 			if (modelSizeIsValid && orientation == model.orientation)
177 				return model.size;
178 		}
179 		return size[orientation] + model.Spacing;
180 	}
181 }
182 
183 struct State
184 {
185 	Axis x, y;
186 	Orientation orientation;
187 }
188 
189 struct StateStack
190 {
191 	import automem : Vector;
192 	import std.experimental.allocator.mallocator : Mallocator;
193 
194 	Vector!(State, Mallocator) stack;
195 
196 	auto put(State s) @trusted @nogc
197 	{
198 		import std.algorithm : move;
199 		stack.put(move(s));
200 	}
201 
202 	ref back() @nogc return
203 	{
204 		return stack[$-1];
205 	}
206 
207 	void popBack() @nogc @trusted
208 	{
209 		assert(!stack.empty);
210 		stack.popBack;
211 	}
212 }
213 
214 /// Visitor for traversing tree to do useful job
215 struct TreePathVisitorImpl(Derived = Default)
216 {
217 	SizeType[2] size;
218 
219 	@disable this(this);
220 
221 	this(SizeType[2] s) @safe @nogc nothrow
222 	{
223 		size = s;
224 	}
225 
226 	enum treePathEnabled = TreePathEnabled.yes;
227 
228 	Location loc;
229 	Orientation orientation = Orientation.Vertical;
230 	StateStack stateStack;
231 
232 	bool complete() @safe @nogc
233 	{
234 		return loc.checkState;
235 	}
236 
237 	bool engaged() @safe @nogc nothrow
238 	{
239 		return loc.stateFirstOrRest;
240 	}
241 
242 	void doEnterTree(Order order, Data, Model)(auto ref const(Data) data, ref Model model)
243 	{
244 		final switch (this.orientation)
245 		{
246 			case Orientation.Vertical:
247 				loc.x.size = size[Orientation.Horizontal] + model.Spacing;
248 				static if (Model.Collapsable)
249 					loc.y.size = model.header_size;
250 				else
251 					loc.y.size = model.size;
252 			break;
253 			case Orientation.Horizontal:
254 				assert(0, "Not implemented");
255 		}
256 
257 		() @trusted { (cast(Derived*) &this).enterTree!(order, Data, Model)(data, model); }();
258 	}
259 
260 	void doEnterNode(Order order, Data, Model)(ref const(Data) data, ref Model model)
261 	{
262 		if (!engaged) 
263 		{
264 			static if (Model.Collapsable) 
265 				stateStack.put(State(loc.x, loc.y, orientation));
266 			return;
267 		}
268 
269 		static if (order == Order.Sinking) with(loc)
270 		{
271 			final switch(orientation)
272 			{
273 				case Orientation.Vertical:
274 					y.position = y.position + y.change;
275 					static if (Model.Collapsable)
276 						y.change = model.header_size;
277 					else
278 						y.change = model.size;
279 				break;
280 				case Orientation.Horizontal:
281 					x.position = x.position + x.change;
282 					static if (Model.Collapsable)
283 						x.change = model.header_size;
284 					else
285 						x.change = model.size;
286 				break;
287 			}
288 
289 			scope(exit)
290 			{
291 				final switch(orientation)
292 				{
293 					case Orientation.Vertical:
294 						if (y.position + y.change > y.destination)
295 						{
296 							path = current_path;
297 							_state = State.finishing;
298 						}
299 					break;
300 					case Orientation.Horizontal:
301 						version(none) if (x.position + x.change > x.destination)
302 						{
303 							path = current_path;
304 							_state = State.finishing;
305 						}
306 					break;
307 				}
308 			}
309 		}
310 
311 		static if (Model.Collapsable)
312 		{
313 			stateStack.put(State(loc.x, loc.y, orientation));
314 
315 			orientation  = model.orientation;
316 		}
317 		else // static if (!Model.Collapsable)
318 		{
319 			final switch (orientation)
320 			{
321 				case Orientation.Vertical:
322 				break;
323 				case Orientation.Horizontal:
324 					loc.x.size = model.size;
325 				break;
326 			}
327 			scope(exit)
328 			{
329 				final switch (this.orientation)
330 				{
331 					case Orientation.Vertical:
332 					break;
333 					case Orientation.Horizontal:
334 						loc.x.position += model.size;
335 					break;
336 				}
337 			}
338 		}
339 
340 		() @trusted { (cast(Derived*) &this).enterNode!(order, Data, Model)(data, model); }();
341 	}
342 
343 	void doLeaveNode(Order order, Data, Model)(ref const(Data) data, ref Model model)
344 	{
345 		static if (Model.Collapsable)
346 		{
347 			orientation = stateStack.back.orientation;
348 
349 			if (orientation == Orientation.Vertical)
350 				loc.x = stateStack.back.x;
351 			if (orientation == Orientation.Horizontal)
352 				loc.y = stateStack.back.y;
353 			stateStack.popBack;
354 		}
355 
356 		if (engaged)
357 		{
358 			static if (order == Order.Bubbling) with(loc)
359 			{
360 				final switch(orientation)
361 				{
362 					case Orientation.Vertical:
363 						y.position = y.position + y.change;
364 						static if (Model.Collapsable)
365 							y.change = -model.header_size;
366 						else
367 							y.change = -model.size;
368 
369 						if (y.position <= y.destination)
370 						{
371 							_state = State.finishing;
372 							path = current_path;
373 						}
374 					break;
375 					case Orientation.Horizontal:
376 						x.position = x.position + x.change;
377 						static if (Model.Collapsable)
378 							x.change = -model.header_size;
379 						else
380 							x.change = -model.size;
381 
382 						version(none) if (x.position <= x.destination)
383 						{
384 							_state = State.finishing;
385 							path = current_path;
386 						}
387 					break;
388 				}
389 			}
390 
391 			static if (!Model.Collapsable)
392 			{
393 				final switch (orientation)
394 				{
395 					case Orientation.Vertical:
396 					break;
397 					case Orientation.Horizontal:
398 						loc.x.size = model.size;
399 					break;
400 				}
401 			}
402 
403 			() @trusted { (cast(Derived*) &this).leaveNode!(order, Data, Model)(data, model); }();
404 		}
405 	}
406 
407 	/// returns true if the current children shouldn't be processed
408 	/// but traversing should be continued
409 	bool doBeforeChildren(Order order, Data, Model)(ref const(Data) data, ref Model model)
410 		if (Model.Collapsable)
411 	{
412 		// set proper current size for children
413 		if (model.orientation == Orientation.Vertical)
414 		{
415 			loc.y.size = loc.y.change;
416 		}
417 		else
418 		{
419 			loc.x.size = loc.y.change;
420 		}
421 
422 		() @trusted { (cast(Derived*) &this).beforeChildren!(order, Data, Model)(data, model); }();
423 
424 		static if (order == Order.Bubbling)
425 		{
426 			// Edge case if the start path starts from this collapsable exactly
427 			// then the childs of the collapsable aren't processed
428 			if (loc.path.value.length && loc.current_path.value[] == loc.path.value[])
429 				return true;
430 		}
431 
432 		loc.indent;
433 
434 		with(loc) final switch (model.orientation)
435 		{
436 			case Orientation.Vertical:
437 				x.position = x.position + model.header_size;
438 				x.size = x.size - model.header_size;
439 			break;
440 			case Orientation.Horizontal:
441 			break;
442 		}
443 
444 		return false;
445 	}
446 
447 	void doAfterChildren(Order order, Data, Model)(ref const(Data) data, ref Model model)
448 		if (Model.Collapsable)
449 	{
450 		loc.unindent;
451 
452 		with(loc) final switch (model.orientation)
453 		{
454 			case Orientation.Vertical:
455 				x.position = x.position - model.header_size;
456 				x.size = x.size + model.header_size;
457 			break;
458 			case Orientation.Horizontal:
459 			break;
460 		}
461 
462 		() @trusted { (cast(Derived*) &this).afterChildren!(order, Data, Model)(data, model); }();
463 	}
464 
465 	auto startValue(Order order)(size_t len)
466 	{
467 		return loc.startValue!order(len);
468 	}
469 
470 	auto setPath(int i)
471 	{
472 		loc.setPath(i);
473 	}
474 
475 	auto setChildSize(Model, ChildModel)(ref Model model, ref ChildModel child_model, int len, ref float residual)
476 	{
477 	}
478 }