1 /**
2 Copyright: Copyright (c) 2013-2014 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.guirenderer;
8 
9 import std.array;
10 import std.stdio;
11 
12 import anchovy.graphics;
13 import anchovy.graphics.texture;
14 import anchovy.graphics.font.fontmanager;
15 import anchovy.graphics.shaderprogram;
16 import anchovy.gui;
17 
18 string textvshader=`
19 #version 330
20 layout (location = 0) in vec2 Position;
21 layout (location = 1) in vec2 TexCoord;
22 uniform vec2 gPosition;
23 uniform vec2 gHalfTarget;
24 uniform sampler2D gSampler;
25 out vec2 TexCoord0;
26 
27 void main()
28 {
29 	gl_Position = vec4(((Position.x+gPosition.x)/gHalfTarget.x)-1, 1-((Position.y+gPosition.y)/gHalfTarget.y), 0.0, 1.0);
30 	ivec2 texSize = textureSize(gSampler, 0);
31 	TexCoord0 = TexCoord/texSize;
32 }`;
33 
34 string textfshader=`
35 #version 330
36 in vec2 TexCoord0;
37 
38 out vec4 FragColor;
39 
40 uniform sampler2D gSampler;
41 uniform vec4 gColor;
42 
43 void main()
44 {
45 	FragColor = texture2D(gSampler, TexCoord0).r*gColor;
46 }
47 `;
48 
49 /// Gives means for rendering widgets and text lines.
50 class SkinnedGuiRenderer : IGuiRenderer
51 {
52 	this(IRenderer renderer, GuiSkin skin)
53 	{
54 		_renderer = renderer;
55 		_fontManager = new FontManager();
56 		_fontTexture = _fontManager.getFontAtlasTex();
57 		_textShader = new ShaderProgram(textvshader, textfshader);
58 		_skin = skin;
59 
60 		if (!_textShader.compile)
61 			writeln("Text shader failed compilation: ", _textShader.errorLog);
62 
63 		_renderer.bindShaderProgram(_textShader);
64 		checkGlError;
65 	}
66 
67 	/// Returns texture that stores cached glyps of all loaded fonts.
68 	override Texture getFontTexture()
69 	{
70 		return _fontTexture;
71 	}
72 
73 	/// Returns font manager used by this renderer.
74 	override ref FontManager fontManager() @property
75 	{
76 		return _fontManager;
77 	}
78 
79 	GuiSkin skin() @property
80 	{
81 		return _skin;
82 	}
83 
84 	/// Creates textline with font from current skin.
85 	/// If font not found then normal font will be used.
86 	override TextLine createTextLine(string fontName = "normal")
87 	{
88 		auto line = new TextLine();
89 		_lines ~= line;
90 
91 		line.fontName = fontName;
92 
93 		Font* font = fontName in _skin.fonts;
94 
95 		if (font is null)
96 		{
97 			font = "normal" in _skin.fonts;
98 		}
99 
100 		line.font = *font;
101 
102 		return line;
103 	}
104 
105 	/// Draws background of widget.
106 	override void drawControlBack(Widget widget, Rect staticRect)
107 	{
108 		if (_clientAreaStack.length > 0)
109 		{
110 			Rect clip = _clientAreaStack.back;
111 			if (clip.width*clip.height == 0) return;
112 		}
113 
114 		string styleName = widget.getPropertyAs!("style", string);
115 		string stateName = widget.getPropertyAs!("state", string);
116 
117 		GuiStyle* styleptr = styleName in _skin.styles;
118 
119 		// No style in skin.
120 		if (styleptr is null)
121 		{
122 			_renderer.setColor(Color(255, 255, 255, 255));
123 			_renderer.fillRect(staticRect);
124 			_renderer.setColor(Color(255, 0, 0, 255));
125 			_renderer.drawRect(staticRect);
126 		}
127 		else // Widget has style.
128 		{
129 			GuiStyle style = *styleptr;
130 
131 			if (!(stateName in style.states)) // Fallback to deafult.
132 			{
133 				stateName = "normal";
134 			}
135 
136 			GuiStyleState state = style[stateName];
137 
138 			TexRectArray[string] geometryList = widget.getPropertyAs!("geometry", TexRectArray[string]);
139 
140 			TexRectArray* geometry = stateName in geometryList;
141 
142 			if (geometry is null) // Geometry for current state is no cached yet.
143 			{
144 				ivec2 size = widget.getPropertyAs!("size", ivec2);
145 				TexRectArray newGeometry = buildWidgetGeometry(size, state);
146 
147 				geometry = &newGeometry;
148 				geometryList[stateName] = newGeometry;
149 
150 				widget.setProperty!"geometry"(geometryList);
151 			}
152 
153 			_renderer.setColor(Color(255, 255, 255, 255));
154 			_renderer.drawTexRectArray(*geometry,
155 										ivec2(staticRect.x - state.outline.left,staticRect.y - state.outline.top),
156 			                           _skin.texture);
157 		}
158 	}
159 
160 	/// Draws line of text aligning it to the position.
161 	override void drawTextLine(TextLine line, ivec2 position, in AlignmentType alignment)
162 	{
163 		if (_clientAreaStack.length > 0)
164 		{
165 			Rect clip = _clientAreaStack.back;
166 			if (clip.width*clip.height == 0) return;
167 		}
168 
169 		if (!line.isInited)
170 		{
171 			return;
172 		}
173 		line.update;
174 
175 		int renderX, renderY;
176 
177 		if (alignment & HoriAlignment.LEFT)
178 			renderX = position.x;
179 		else if (alignment & HoriAlignment.CENTER)
180 			renderX = position.x - (line.width / 2);
181 		else
182 			renderX = position.x - line.width;
183 
184 		if (alignment & VertAlignment.TOP)
185 			renderY = position.y + line.height;
186 		else if (alignment & VertAlignment.CENTER)
187 			renderY = position.y + (line.height / 2);
188 		else
189 			renderY = position.y;
190 
191 		_renderer.drawTexRectArray(line.geometry, ivec2(renderX , renderY), _fontTexture, _textShader);
192 	}
193 
194 	/// Draws line of text aligning it inside rectangle.
195 	override void drawTextLine(TextLine line, in Rect area, in AlignmentType alignment)
196 	{
197 		if (_clientAreaStack.length > 0)
198 		{
199 			Rect clip = _clientAreaStack.back;
200 			if (clip.width*clip.height == 0) return;
201 		}
202 
203 		if (!line.isInited)
204 		{
205 			return;
206 		}
207 		line.update;
208 
209 		int renderX, renderY;
210 
211 		if (alignment & HoriAlignment.LEFT)
212 			renderX = area.x;
213 		else if (alignment & HoriAlignment.CENTER)
214 			renderX = area.x + (area.width/2) - (line.width / 2);
215 		else if (alignment & HoriAlignment.RIGHT)
216 			renderX = area.x + area.width - line.width;
217 		else
218 			renderX = area.x;
219 
220 		if (alignment & VertAlignment.TOP)
221 			renderY = area.y;
222 		else if (alignment & VertAlignment.CENTER)
223 			renderY = area.y + (area.height/2) - (line.height / 2);
224 		else if (alignment & VertAlignment.BOTTOM)
225 			renderY = area.y + area.height - line.height;
226 		else
227 			renderY = area.y;
228 
229 		_renderer.drawTexRectArray(line.geometry, ivec2(renderX , renderY), _fontTexture, _textShader);
230 	}
231 
232 	/// Pushes new clip rect onto stack.
233 	/// Useful for containers which clips their children.
234 	/// Must be called when parent begins drawing its children.
235 	override void pushClientArea(Rect area)
236 	{
237 		_clientAreaStack ~= area;
238 		setClientArea(_clientAreaStack.back);
239 	}
240 
241 	/// Pops clip rect from stack.
242 	/// Useful for containers which clips their children.
243 	/// Must be called when parent ends drawing its children.
244 	override void popClientArea()
245 	{
246 		_clientAreaStack.popBack;
247 
248 		if (_clientAreaStack.empty)
249 		{
250 			glScissor(0, 0, _renderer.windowSize.x, _renderer.windowSize.y);
251 			return;
252 		}
253 
254 		setClientArea(_clientAreaStack.back);
255 	}
256 
257 	/// Sets clip rect to given area.
258 	/// Make sure to call this to reset rendering area for the window.
259 	override void setClientArea(Rect area)
260 	{
261 		glScissor(area.x, _renderer.windowSize.y - area.y - area.height,  area.width, area.height);
262 	}
263 
264 	/// Returns internal render.
265 	IRenderer renderer() @property
266 	{
267 		return _renderer;
268 	}
269 
270 	/// Creates geometry for given gui style state and size.
271 	TexRectArray buildWidgetGeometry(ivec2 size, in GuiStyleState state)
272 	{
273 		TexRectArray geometry = new TexRectArray;
274 		RectOffset fb = state.fixedBorders; // defines splitting of texture in 9 pieces.
275 
276 		int widgetHeight = size.y + state.outline.top + state.outline.bottom;
277 		int widgetWidth = size.x + state.outline.left + state.outline.right;
278 
279 		if (fb.left > 0) // left 3 parts
280 		{
281 
282 			if (fb.top > 0) // left-top
283 			{
284 				Rect texRect;
285 				with(state.atlasRect)
286 					texRect = Rect(x, y, fb.left, fb.top);
287 				geometry.appendQuad(Rect(0, 0, fb.left, fb.top), texRect);
288 			}
289 			if (fb.bottom > 0) // left-bottom
290 			{
291 				Rect texRect;
292 				with(state.atlasRect)
293 					texRect = Rect(x, y + height - fb.bottom, fb.left, fb.bottom);
294 				geometry.appendQuad(Rect(0, widgetHeight - fb.bottom, fb.left, fb.bottom), texRect);
295 			}
296 			if (fb.vertical < widgetHeight) // left
297 			{
298 				Rect texRect;
299 				with(state.atlasRect)
300 					texRect = Rect(x, y + fb.top, fb.left, height - fb.vertical);
301 				geometry.appendQuad(Rect(0, fb.top, fb.left, widgetHeight - fb.vertical), texRect);
302 			}
303 		}
304 
305 		if (fb.top > 0 && fb.horizontal < widgetWidth) // top
306 		{
307 			Rect texRect;
308 			with(state.atlasRect)
309 				texRect = Rect(x + fb.left, y, width - fb.horizontal, fb.top);
310 			geometry.appendQuad(Rect(fb.left, 0, widgetWidth - fb.horizontal, fb.top), texRect);
311 		}
312 
313 		if (fb.horizontal < widgetWidth && fb.vertical < widgetHeight) // center
314 		{
315 			Rect texRect;
316 			with(state.atlasRect)
317 				texRect = Rect(x + fb.left, y + fb.top, width - fb.horizontal, height - fb.vertical);
318 			geometry.appendQuad(Rect(fb.left, fb.top, widgetWidth - fb.horizontal, widgetHeight - fb.vertical), texRect);
319 		}
320 
321 		if (fb.bottom > 0 && fb.horizontal < widgetWidth) // bottom
322 		{
323 			Rect texRect;
324 			with(state.atlasRect)
325 				texRect = Rect(x + fb.left, y + height - fb.bottom, width - fb.horizontal, fb.bottom);
326 			geometry.appendQuad(Rect(fb.left, widgetHeight - fb.bottom, widgetWidth - fb.horizontal, fb.bottom), texRect);
327 		}
328 
329 		if (fb.right > 0) // right 3 parts
330 		{
331 			if (fb.top > 0) // right-top
332 			{
333 				Rect texRect;
334 				with(state.atlasRect)
335 					texRect = Rect((x+width) - fb.right, y, fb.right, fb.top);
336 				geometry.appendQuad(Rect(widgetWidth - fb.right, 0, fb.right, fb.top), texRect);
337 			}
338 			if (fb.bottom > 0) // right-bottom
339 			{
340 				Rect texRect;
341 				with(state.atlasRect)
342 					texRect = Rect((x+width) - fb.right, y + height - fb.bottom, fb.right, fb.bottom);
343 				geometry.appendQuad(Rect(widgetWidth - fb.right, widgetHeight - fb.bottom, fb.right, fb.bottom), texRect);
344 			}
345 			if (fb.vertical < widgetHeight) // right
346 			{
347 				Rect texRect;
348 				with(state.atlasRect)
349 					texRect = Rect((x+width) - fb.right, y + fb.top, fb.right, height - fb.vertical);
350 				geometry.appendQuad(Rect(widgetWidth - fb.right, fb.top, fb.right, widgetHeight - fb.vertical), texRect);
351 			}
352 		}
353 
354 		geometry.load;
355 
356 		return geometry;
357 	}
358 
359 private:
360 
361 	Rect[] _clientAreaStack;
362 	IRenderer _renderer;
363 	FontManager _fontManager;
364 	ShaderProgram _textShader;
365 	Texture _fontTexture;
366 	GuiSkin _skin;
367 	TextLine[] _lines;
368 }