1 module nanogui.experimental.utils;
2 
3 import nanogui.common : NanoContext;
4 import nanogui.common : Vector2f, Vector2i;
5 
6 public import auxil.model;
7 public import auxil.traits;
8 
9 private string enumToString(E)(E e) @nogc @safe nothrow
10 	if (is(E == enum))
11 {
12 	import std.traits : Unqual;
13 
14 	// using `if` instead of `final switch` is simple
15 	// workaround of duplicated enum members
16 	static foreach(v; __traits(allMembers, E))
17 		mixin("if (e == E." ~ v ~ ") return `" ~ v ~ "`;");
18 	return "Unrepresentable by " ~ Unqual!E.stringof ~ " value";
19 }
20 
21 void indent(ref NanoContext ctx)
22 {
23 	ctx.position.x += 20;
24 	ctx.size.x -= 20;
25 }
26 
27 void unindent(ref NanoContext ctx)
28 {
29 	ctx.position.x -= 20;
30 	ctx.size.x += 20;
31 }
32 
33 bool isPointInRect(Vector2f topleft, Vector2f size, Vector2f point)
34 {
35 	return point.x >= topleft.x && point.x < topleft.x+size.x &&
36 		point.y >= topleft.y && point.y < topleft.y+size.y;
37 }
38 
39 bool isPointInRect(Vector2i topleft, Vector2i size, Vector2i point)
40 {
41 	return point.x >= topleft.x && point.x < topleft.x+size.x &&
42 		point.y >= topleft.y && point.y < topleft.y+size.y;
43 }
44 
45 auto drawItem(T)(ref NanoContext ctx, float height, T item)
46 {
47 	import std.traits : isSomeString;
48 
49 	static if (isSomeString!T)
50 		return drawString(ctx, height, item);
51 	else
52 		return drawPodType(ctx, height, item);
53 }
54 
55 private auto drawString(Char)(ref NanoContext ctx, float height, const(Char)[] str)
56 {
57 	import nanogui.common : textAlign, text, roundedRect, fill, beginPath, linearGradient, fillPaint, fillColor;
58 
59 	// it is possible if the item is placed out of visible area
60 	if (ctx.size[ctx.orientation] <= 0)
61 		return false;
62 
63 	bool inside;
64 	if (isPointInRect(ctx.position, ctx.size, ctx.mouse))
65 	{
66 		const gradTop = ctx.theme.mButtonGradientTopFocused;
67 		const gradBot = ctx.theme.mButtonGradientBotFocused;
68 
69 		ctx.beginPath;
70 		ctx.roundedRect(
71 			ctx.position.x, ctx.position.y,
72 			ctx.size.x, ctx.size.y,
73 			ctx.theme.mButtonCornerRadius - 1
74 		);
75 
76 		const bg = ctx.linearGradient(
77 			ctx.position.x, ctx.position.y, 
78 			ctx.position.x, ctx.position.y + height,
79 			gradTop, gradBot
80 		);
81 
82 		ctx.fillPaint(bg);
83 		ctx.fill;
84 		ctx.fillColor(ctx.theme.mTextColor);
85 		inside = true;
86 	}
87 	ctx.textAlign(ctx.algn);
88 	ctx.text(ctx.position.x, ctx.position.y, str);
89 	ctx.position.y += height;
90 
91 	return inside;
92 }
93 
94 private auto drawPodType(T)(ref NanoContext ctx, float height, T item)
95 {
96 	import std.format : sformat;
97 	import std.traits : isIntegral, isFloatingPoint, isBoolean, isSomeString,
98 		isPointer, isSomeChar;
99 
100 	enum textBufferSize = 512;
101 	char[textBufferSize] buffer;
102 	size_t l;
103 
104 	// format specifier depends on type
105 	static if (is(T == enum))
106 	{
107 		const s = item.enumToString;
108 		l += sformat(buffer[l..$], "%s", s).length;
109 	}
110 	else static if (isIntegral!T)
111 		l += sformat(buffer[l..$], "%d", item).length;
112 	else static if (isFloatingPoint!T)
113 		l += sformat(buffer[l..$], "%f", item).length;
114 	else static if (isBoolean!T)
115 		l += sformat(buffer[l..$], item ? "true" : "false").length;
116 	else static if (isSomeString!T || isPointer!T)
117 		l += sformat(buffer[l..$], "%s", item).length;
118 	else static if (isSomeChar!T)
119 		l += sformat(buffer[l..$], "%s", item).length;
120 	else
121 		static assert(0, T.stringof);
122 
123 	buffer[l < $ ? l : $-1] = '\0';
124 
125 	return ctx.drawString(height, buffer[0..l]);
126 }
127 
128 mixin template DependencyProperty(T, alias string name)
129 {
130 	import std.string : capitalize;
131 
132 	protected
133 	{
134 		mixin("T m" ~ name.capitalize ~ ";");
135 	}
136 	public 
137 	{
138 		import std.format : format;
139 		mixin (format(q{
140 			final T %1$s() const { return m%2$s; }
141 			final void %1$s(T value)
142 			{ 
143 				if (value == m%2$s) return;
144 				m%2$s = value;
145 				invalidate();
146 			}
147 		}, name, name.capitalize));
148 	}
149 }