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