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