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 }