1 module walker;
2 
3 import common;
4 
5 struct WidgetRange
6 {
7 	float x, delta, xend;
8 
9 	invariant
10 	{
11 		import std.math, std.conv;
12 
13 		assert(x >= 0, x.text);
14 		assert(xend.isNaN || xend >= 0);
15 		assert(!x.isNaN);
16 		assert(!delta.isNaN);
17 	}
18 
19 	bool empty() const
20 	{
21 		return x >= xend;
22 	}
23 
24 	auto front()
25 	{
26 		import std.typecons : tuple;
27 		return tuple(x, delta);
28 	}
29 
30 	void popFront()
31 	{
32 		import std.math : isNaN;
33 		x += xend.isNaN ? 0 : delta;
34 	}
35 }
36 
37 struct Walker
38 {
39 	WorkArea area;
40 	auto direction = Direction.row;
41 	auto alignment = Alignment.stretch;
42 	auto justification = Justification.around;
43 
44 	WidgetRange[] xWidgetRange, yWidgetRange;
45 
46 	auto wrapping = false;
47 	int nestingLevel;
48 
49 	// for debug output in console
50 	string indentPrefix;
51 	// for debug rendering
52 	RenderState[] renderlog;
53 
54 	import std.stdio;
55 	import traverse;
56 
57 	auto render(Data, Dom)(Data data, Dom dom)
58 	{
59 		assert(dom);
60 		import std.math : isNaN;
61 
62 		// defaulting
63 		if (area.margin.isNaN)
64 			area.margin = 0;
65 		if (area.padding.isNaN)
66 			area.padding = 0;
67 
68 		traverseImpl!(leaf, nodeEnter, nodeLeave)(this, data, dom);
69 	}
70 
71 	static auto leaf(Data, Dom)(ref typeof(this) ctx, Data data, Dom dom)
72 	{
73 		with(ctx)
74 		{
75 			writeln(indentPrefix, data);
76 			writeln(indentPrefix, ctx.area.w, "x", ctx.area.h);
77 			writeln(indentPrefix, dom.attributes.direction);
78 			if (!dom.attributes.direction.isNull)
79 				direction = dom.attributes.direction.get;
80 			if (!dom.attributes.margin.isNull)
81 				area.margin = dom.attributes.margin.get;
82 			if (!dom.attributes.padding.isNull)
83 				area.padding = dom.attributes.padding.get;
84 
85 			renderlog ~= RenderState(dom.name, area, direction, 1, nestingLevel);
86 			auto a = area;
87 			a.x += area.margin;
88 			a.y += area.margin;
89 			a.w -= area.margin*2;
90 			a.h -= area.margin*2;
91 			renderlog ~= RenderState(dom.name, a, direction, 2, nestingLevel);
92 			a.x += area.padding;
93 			a.y += area.padding;
94 			a.w -= area.padding*2;
95 			a.h -= area.padding*2;
96 			renderlog ~= RenderState(dom.name, a, direction, 3, nestingLevel);
97 		}
98 	}
99 
100 	static auto nodeEnter(Data, Dom)(ref typeof(this) ctx, Data data, Dom dom)
101 	{
102 		with(ctx)
103 		{
104 			writeln(indentPrefix, data);
105 			// writeln(indentPrefix, ctx.area.x, ", ", ctx.area.y, " ", ctx.area.w, "x", ctx.area.h);
106 			writeln(indentPrefix, ctx.area.margin);
107 
108 			if (!dom.attributes.direction.isNull)
109 				direction = dom.attributes.direction.get;
110 			if (!dom.attributes.margin.isNull)
111 				area.margin = dom.attributes.margin.get;
112 			if (!dom.attributes.padding.isNull)
113 				area.padding = dom.attributes.padding.get;
114 
115 			renderlog ~= RenderState(dom.name, area, direction, 1, nestingLevel);
116 			auto a = area;
117 			a.x += area.margin;
118 			a.y += area.margin;
119 			a.w -= area.margin*2;
120 			a.h -= area.margin*2;
121 			renderlog ~= RenderState(dom.name, a, direction, 2, nestingLevel);
122 			a.x += area.padding;
123 			a.y += area.padding;
124 			a.w -= area.padding*2;
125 			a.h -= area.padding*2;
126 			renderlog ~= RenderState(dom.name, a, direction, 3, nestingLevel);
127 
128 			import std.math : isNaN;
129 			assert(!area.margin.isNaN);
130 			final switch (direction)
131 			{
132 				case Direction.row:
133 					xWidgetRange ~= WidgetRange(area.x + area.margin + area.padding, (area.w - 2*(area.margin + area.padding))/dom.child.length, area.x+area.w - 2*(area.margin + area.padding));
134 					yWidgetRange ~= WidgetRange(area.y + area.margin + area.padding, area.h - 2*(area.margin + area.padding), float.nan);
135 					break;
136 				case Direction.rowReverse:
137 					assert(0, "not implemented");
138 					// break;
139 				case Direction.column:
140 					xWidgetRange ~= WidgetRange(area.x + area.margin + area.padding, area.w - 2*(area.margin + area.padding), float.nan);
141 					yWidgetRange ~= WidgetRange(area.y + area.margin + area.padding, (area.h - 2*(area.margin + area.padding))/dom.child.length, area.y+area.h - 2*(area.margin + area.padding));
142 					break;
143 				case Direction.columnReverse:
144 					assert(0, "not implemented");
145 					// break;
146 			}
147 
148 			indentPrefix ~= "\t";
149 		}
150 	}
151 
152 	static auto nodeLeave(Data, Dom)(ref typeof(this) ctx, Data data, Dom dom)
153 	{
154 		ctx.indentPrefix = ctx.indentPrefix[0..$-1];
155 		ctx.xWidgetRange  = ctx.xWidgetRange [0..$-1];
156 		ctx.yWidgetRange  = ctx.yWidgetRange [0..$-1];
157 	}
158 }