1 module nanogui.experimental.utils;
2 
3 import nanogui.common : NanoContext;
4 import nanogui.common : Vector2i;
5 
6 void indent(ref NanoContext ctx)
7 {
8 	ctx.position.x += 20;
9 }
10 
11 void unindent(ref NanoContext ctx)
12 {
13 	ctx.position.x -= 20;
14 }
15 
16 bool isPointInRect(Vector2i topleft, Vector2i size, Vector2i point)
17 {
18 	return point.x >= topleft.x && point.x < topleft.x+size.x &&
19 		point.y >= topleft.y && point.y < topleft.y+size.y;
20 }
21 
22 auto drawItem(T)(ref NanoContext ctx, float height, T item)
23 {
24 	import std.traits : isSomeString;
25 
26 	static if (isSomeString!T)
27 		return drawString(ctx, height, item);
28 	else
29 		return drawPodType(ctx, height, item);
30 }
31 
32 private auto drawString(Char)(ref NanoContext ctx, float height, const(Char)[] str)
33 {
34 	import nanogui.common : textAlign, text, roundedRect, fill, beginPath, linearGradient, fillPaint, fillColor;
35 	const rect_size = Vector2i(cast(int)ctx.current_size, cast(int)height);
36 	bool inside;
37 	if (isPointInRect(ctx.position, rect_size, ctx.mouse))
38 	{
39 		const gradTop = ctx.theme.mButtonGradientTopFocused;
40 		const gradBot = ctx.theme.mButtonGradientBotFocused;
41 
42 		ctx.beginPath;
43 		ctx.roundedRect(
44 			ctx.position.x, ctx.position.y,
45 			ctx.current_size, height,
46 			ctx.theme.mButtonCornerRadius - 1
47 		);
48 
49 		const bg = ctx.linearGradient(
50 			ctx.position.x, ctx.position.y, 
51 			ctx.position.x, ctx.position.y + height,
52 			gradTop, gradBot
53 		);
54 
55 		ctx.fillPaint(bg);
56 		ctx.fill;
57 		ctx.fillColor(ctx.theme.mTextColor);
58 		inside = true;
59 	}
60 	ctx.textAlign(ctx.algn);
61 	ctx.text(ctx.position.x, ctx.position.y, str);
62 	ctx.position.y += cast(int) height;
63 
64 	return inside;
65 }
66 
67 private auto drawPodType(T)(ref NanoContext ctx, float height, T item)
68 {
69 	import std.format : sformat;
70 	import std.traits : isIntegral, isFloatingPoint, isBoolean;
71 
72 	enum textBufferSize = 512;
73 	char[textBufferSize] buffer;
74 	size_t l;
75 
76 	// format specifier depends on type
77 	static if (is(T == enum))
78 	{
79 		const s = item.enumToString;
80 		l += sformat(buffer[l..$], min(buffer.length-l, s.length), "%s", s).length;
81 	}
82 	else static if (isIntegral!T)
83 		l += sformat(buffer[l..$], "%d", item).length;
84 	else static if (isFloatingPoint!T)
85 		l += sformat(buffer[l..$], "%f", item).length;
86 	else static if (isBoolean!T)
87 		l += sformat(buffer[l..$], item ? "true\0" : "false\0").length;
88 	else static if (isSomeString!T)
89 		l += sformat(buffer[l..$], "%s", item).length;
90 	else
91 		static assert(0, T.stringof);
92 
93 	return ctx.drawItem(height, buffer[0..l]);
94 }
95 
96 struct DataItem(T)
97 {
98 	import std.traits : isAggregateType, isPointer, isArray, isSomeString, isAssociativeArray;
99 	import nanogui.common : Vector2i;
100 
101 	enum textBufferSize = 1024;
102 
103 	T content;
104 	private Vector2i _size;
105 
106 	this(string c, Vector2i s)
107 	{
108 		content = c;
109 		_size = s;
110 	}
111 
112 	auto draw(Context)(ref Context ctx, const(char)[] header, float height) const
113 		if (!isAggregateType!T && 
114 			!isPointer!T &&
115 			(!isArray!T || isSomeString!T) &&
116 			!isAssociativeArray!T)
117 	{
118 		// import core.stdc.stdio : snprintf;
119 		import std.format : sformat;
120 		import std.traits : isIntegral, isFloatingPoint, isBoolean;
121 
122 		char[textBufferSize] buffer;
123 		size_t l;
124 		if (header.length)
125 			l = sformat(buffer, "%s: ", header).length;
126 
127 		// format specifier depends on type
128 		static if (is(T == enum))
129 		{
130 			const s = content.enumToString;
131 			l += sformat(buffer[l..$], min(buffer.length-l, s.length), "%s", s).length;
132 		}
133 		else static if (isIntegral!T)
134 			l += sformat(buffer[l..$], "%d", content).length;
135 		else static if (isFloatingPoint!T)
136 			l += sformat(buffer[l..$], "%f", content).length;
137 		else static if (isBoolean!T)
138 			l += sformat(buffer[l..$], content ? "true\0" : "false\0").length;
139 		else static if (isSomeString!T)
140 			l += sformat(buffer[l..$], "%s", content).length;
141 		else
142 			static assert(0, T.stringof);
143 
144 		return ctx.drawItem(height, buffer[0..l]);
145 	}
146 
147 	// auto draw(Context)(Context ctx, const(char)[] header)
148 	// 	if (isAggregateType!T)// && !isInstanceOf!(TaggedAlgebraic, T) && !isNullable!T)
149 	// {
150 	// 	// static if (DrawnAsAvailable)
151 	// 	// {
152 	// 	// 	nk_layout_row_dynamic(ctx, itemHeight, 1);
153 	// 	// 	static if (Cached)
154 	// 	// 		state_drawn_as.draw(ctx, header, cached);
155 	// 	// 	else
156 	// 	// 		state_drawn_as.draw(ctx, "", t.drawnAs);
157 	// 	// }
158 	// 	// else
159 	// 	static if (DrawableMembers!t.length == 1)
160 	// 	{
161 	// 		static foreach(member; DrawableMembers!t)
162 	// 			mixin("state_" ~ member ~ ".draw(ctx, \"" ~ member ~"\", t." ~ member ~ ");");
163 	// 	}
164 	// 	else
165 	// 	{
166 	// 		import core.stdc.stdio : sprintf;
167 			
168 	// 		char[textBufferSize] buffer;
169 	// 		snprintf(buffer.ptr, buffer.length, "%s", header.ptr);
170 
171 	// 		if (nk_tree_state_push(ctx, NK_TREE_NODE, buffer.ptr, &collapsed))
172 	// 		{
173 	// 			scope(exit)
174 	// 				nk_tree_pop(ctx);
175 				
176 	// 			static foreach(member; DrawableMembers!t) 
177 	// 			{
178 	// 				mixin("height += state_" ~ member ~ ".draw(ctx, \"" ~ member ~"\", t." ~ member ~ ");");
179 	// 			}
180 	// 		}
181 	// 	}
182 	// }
183 
184 	auto visible() const nothrow @safe pure @nogc { return true; }
185 	auto performLayout(NVG)(NVG ctx) { };
186 
187 	auto size() const nothrow @safe pure @nogc { return _size; }
188 	auto size(Vector2i v) nothrow @safe pure @nogc { _size = v; }
189 }
190 
191 mixin template DependencyProperty(T, alias string name)
192 {
193 	import std.string : capitalize;
194 
195 	protected
196 	{
197 		mixin("T m" ~ name.capitalize ~ ";");
198 	}
199 	public 
200 	{
201 		import std.format : format;
202 		mixin (format(q{
203 			final T %1$s() const { return m%2$s; }
204 			final void %1$s(T value)
205 			{ 
206 				if (value == m%2$s) return;
207 				m%2$s = value;
208 				invalidate();
209 			}
210 		}, name, name.capitalize));
211 	}
212 }
213 
214 // helpers
215 
216 private string enumToString(E)(E e) @nogc @safe nothrow
217 	if (is(E == enum))
218 {
219 	import std.traits : Unqual;
220 
221 	// using `if` instead of `final switch` is simple
222 	// workaround of duplicated enum members
223 	static foreach(v; __traits(allMembers, E))
224 		mixin("if (e == E." ~ v ~ ") return `" ~ v ~ "`;");
225 	return "Unrepresentable by " ~ Unqual!E.stringof ~ " value";
226 }
227 
228 import std.traits : isTypeTuple;
229 
230 private template isNullable(T)
231 {
232 	import std.traits : hasMember;
233 
234 	static if (
235 		hasMember!(T, "isNull") &&
236 		is(typeof(__traits(getMember, T, "isNull")) == bool) &&
237 		(
238 			(hasMember!(T, "get") && !is(typeof(__traits(getMember, T, "get")) == void)) ||
239 			(hasMember!(T, "value") && !is(typeof(__traits(getMember, T, "value")) == void))
240 		) &&
241 		hasMember!(T, "nullify") &&
242 		is(typeof(__traits(getMember, T, "nullify")) == void)
243 	)
244 	{
245 		enum isNullable = true;
246 	}
247 	else
248 	{
249 		enum isNullable = false;
250 	}
251 }
252 
253 private bool privateOrPackage()(string protection)
254 {
255 	return protection == "private" || protection == "package";
256 }
257 
258 // check if the member is readable/writeble?
259 private enum isReadableAndWritable(alias aggregate, string member) = __traits(compiles, __traits(getMember, aggregate, member) = __traits(getMember, aggregate, member));
260 private enum isPublic(alias aggregate, string member) = !__traits(getProtection, __traits(getMember, aggregate, member)).privateOrPackage;
261 
262 // check if the member is property with const qualifier
263 private template isConstProperty(alias aggregate, string member)
264 {
265 	import std.traits : isSomeFunction, hasFunctionAttributes;
266 
267 	static if(isSomeFunction!(__traits(getMember, aggregate, member)))
268 		enum isConstProperty = hasFunctionAttributes!(__traits(getMember, aggregate, member), "const", "@property");
269 	else
270 		enum isConstProperty = false;
271 }
272 
273 // check if the member is readable
274 private enum isReadable(alias aggregate, string member) = __traits(compiles, { auto _val = __traits(getMember, aggregate, member); });
275 
276 private template isItSequence(T...)
277 {
278 	static if (T.length < 2)
279 		enum isItSequence = false;
280 	else
281 		enum isItSequence = true;
282 }
283 
284 private template hasProtection(alias aggregate, string member)
285 {
286 	enum hasProtection = __traits(compiles, { enum pl = __traits(getProtection, __traits(getMember, aggregate, member)); });
287 }
288 
289 // This trait defines what members should be drawn -
290 // public members that are either readable and writable or getter properties
291 private template Drawable(alias value, string member)
292 {
293 	import std.algorithm : among;
294 	import std.traits : isTypeTuple, isSomeFunction;
295 
296 	static if (isItSequence!value)
297 		enum Drawable = false;
298 	else
299 	static if (hasProtection!(value, member) && !isPublic!(value, member))
300 			enum Drawable = false;
301 	else
302 	static if (isItSequence!(__traits(getMember, value, member)))
303 		enum Drawable = false;
304 	else
305 	static if(member.among("__ctor", "__dtor"))
306 		enum Drawable = false;
307 	else
308 	static if (isReadableAndWritable!(value, member) && !isSomeFunction!(__traits(getMember, value, member)))
309 		enum Drawable = true;
310 	else
311 	static if (isReadable!(value, member))
312 		enum Drawable = isConstProperty!(value, member); // a readable property is getter
313 	else
314 		enum Drawable = false;
315 }
316 
317 /// returns alias sequence, members of which are members of value
318 /// that should be drawn
319 private template DrawableMembers(alias A)
320 {
321 	import std.meta : ApplyLeft, Filter, AliasSeq;
322 	import std.traits : isType, Unqual;
323 
324 	static if (isType!A)
325 	{
326 		alias Type = Unqual!A;
327 	}
328 	else
329 	{
330 		alias Type = Unqual!(typeof(A));
331 	}
332 
333 	Type symbol;
334 
335 	alias AllMembers = AliasSeq!(__traits(allMembers, Type));
336 	alias isProper = ApplyLeft!(Drawable, symbol);
337 	alias DrawableMembers = Filter!(isProper, AllMembers);
338 
339 }