Merge branch 'main/rendor-staging' into fixes
[ryzomcore.git] / nel / src / gui / css_background_renderer.cpp
blob230807068413c9da8e5225f6a92f30480c45397d
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2019 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 #include "stdpch.h"
19 #include "nel/gui/css_background_renderer.h"
20 #include "nel/gui/css_border_renderer.h"
21 #include "nel/gui/view_renderer.h"
22 #include "nel/gui/widget_manager.h"
23 #include "nel/gui/view_bitmap.h"
25 using namespace std;
26 using namespace NLMISC;
28 #ifdef DEBUG_NEW
29 #define new DEBUG_NEW
30 #endif
32 namespace NLGUI
34 // ----------------------------------------------------------------------------
35 CSSBackgroundRenderer::CSSBackgroundRenderer()
36 : CurrentAlpha(255), TextureId(-1),
37 m_BorderX(0), m_BorderY(0), m_BorderW(0), m_BorderH(0),
38 m_PaddingX(0), m_PaddingY(0), m_PaddingW(0), m_PaddingH(0),
39 m_ContentX(0), m_ContentY(0), m_ContentW(0), m_ContentH(0),
40 m_RootFontSize(16.f), m_FontSize(16.f), m_Viewport(NULL),
41 m_RenderLayer(0), m_ModulateGlobalColor(false), m_FillViewport(false),
42 m_Dirty(false)
46 // ----------------------------------------------------------------------------
47 CSSBackgroundRenderer::~CSSBackgroundRenderer()
49 if (TextureId != -1)
50 CViewRenderer::getInstance()->deleteTexture(TextureId);
53 // ----------------------------------------------------------------------------
54 void CSSBackgroundRenderer::clear()
56 m_Dirty = true;
58 if (TextureId != -1)
59 CViewRenderer::getInstance()->deleteTexture(TextureId);
61 TextureId = -1;
62 m_Background.image.clear();
63 m_Background.color.A = 0;
66 // ----------------------------------------------------------------------------
67 void CSSBackgroundRenderer::setBackground(const CSSBackground &bg)
69 m_Dirty = true;
70 // TODO: CSSBackground should keep track of TextureId
71 CViewRenderer &rVR = *CViewRenderer::getInstance();
72 if (bg.image != m_Background.image && TextureId != -1)
73 rVR.deleteTexture(TextureId);
75 m_Background = bg;
76 // TODO: does not accept urls
77 if (TextureId == -1 && !bg.image.empty())
79 // TODO: make CViewRenderer accept urls
80 if (bg.image.find("://") != std::string::npos)
81 TextureId = rVR.createTexture(bg.image, 0, 0, -1, -1, false);
85 // ----------------------------------------------------------------------------
86 void CSSBackgroundRenderer::setImage(const std::string &bgtex)
88 m_Dirty = true;
89 // TODO: CSSBackground should keep track of TextureId
90 CViewRenderer &rVR = *CViewRenderer::getInstance();
91 if (bgtex != m_Background.image && TextureId != -1)
93 rVR.deleteTexture(TextureId);
94 TextureId = -1;
96 m_Background.image = bgtex;
98 if (TextureId == -1 && !bgtex.empty())
100 // TODO: make CViewRenderer accept urls
101 if (bgtex.find("://") != std::string::npos)
102 TextureId = rVR.createTexture(bgtex, 0, 0, -1, -1, false);
106 // ----------------------------------------------------------------------------
107 void CSSBackgroundRenderer::setImageRepeat(bool b)
109 m_Background.repeatX = b ? CSS_VALUE_REPEAT : CSS_VALUE_NOREPEAT;
110 m_Background.repeatY = b ? CSS_VALUE_REPEAT : CSS_VALUE_NOREPEAT;
113 // ----------------------------------------------------------------------------
114 void CSSBackgroundRenderer::setImageCover(bool b)
116 m_Background.size = b ? CSS_VALUE_COVER : CSS_VALUE_AUTO;
119 // ----------------------------------------------------------------------------
120 void CSSBackgroundRenderer::updateCoords()
122 m_Dirty = false;
123 m_DrawQueue.clear();
125 // TODO: color from last background layer
126 buildColorQuads(m_Background);
128 // -------------------------------------------------------------------
129 // background-image
130 buildImageQuads(m_Background, TextureId);
133 // ----------------------------------------------------------------------------
134 void CSSBackgroundRenderer::draw() {
135 if (m_Dirty) updateCoords();
136 if (m_DrawQueue.empty()) return;
138 CViewRenderer &rVR = *CViewRenderer::getInstance();
140 // flush draw cache to ensure correct draw order
141 rVR.flush();
143 // TODO: no need for widget manager, if global color is set from parent
144 CRGBA globalColor;
145 if (m_ModulateGlobalColor)
146 globalColor = CWidgetManager::getInstance()->getGlobalColor();
148 // TODO: there might be issue on draw order IF using multiple textures
149 // and second (top) texture is created before first one.
150 for(uint i = 0; i < m_DrawQueue.size(); ++i)
152 CRGBA color = m_DrawQueue[i].Color;
153 if (m_ModulateGlobalColor)
154 color.modulateFromColor (color, globalColor);
156 color.A = (uint8) (((uint16) CurrentAlpha * (uint16) color.A) >> 8);
157 rVR.drawQuad(m_RenderLayer, m_DrawQueue[i].Quad, m_DrawQueue[i].TextureId, color, false);
160 // flush draw cache to ensure correct draw order
161 rVR.flush();
164 // ----------------------------------------------------------------------------
165 void CSSBackgroundRenderer::getPositioningArea(const CSSBackground &bg, sint32 &areaX, sint32 &areaY, sint32 &areaW, sint32 &areaH) const
167 switch(bg.origin)
169 case CSS_VALUE_PADDING_BOX:
170 areaX = m_PaddingX;
171 areaY = m_PaddingY;
172 areaW = m_PaddingW;
173 areaH = m_PaddingH;
174 break;
175 case CSS_VALUE_CONTENT_BOX:
176 areaX = m_ContentX;
177 areaY = m_ContentY;
178 areaW = m_ContentW;
179 areaH = m_ContentH;
180 break;
181 case CSS_VALUE_BORDER_BOX:
182 // fall thru
183 default:
184 areaX = m_BorderX;
185 areaY = m_BorderY;
186 areaW = m_BorderW;
187 areaH = m_BorderH;
188 break;
192 // ----------------------------------------------------------------------------
193 void CSSBackgroundRenderer::getPaintingArea(const CSSBackground &bg, sint32 &areaX, sint32 &areaY, sint32 &areaW, sint32 &areaH) const
195 switch(bg.clip)
197 case CSS_VALUE_PADDING_BOX:
198 areaX = m_PaddingX;
199 areaY = m_PaddingY;
200 areaW = m_PaddingW;
201 areaH = m_PaddingH;
202 break;
203 case CSS_VALUE_CONTENT_BOX:
204 areaX = m_ContentX;
205 areaY = m_ContentY;
206 areaW = m_ContentW;
207 areaH = m_ContentH;
208 break;
209 case CSS_VALUE_BORDER_BOX:
210 // fall thru
211 default:
212 areaX = m_BorderX;
213 areaY = m_BorderY;
214 areaW = m_BorderW;
215 areaH = m_BorderH;
216 break;
219 if (m_FillViewport && m_Viewport)
221 sint32 newX = std::min(areaX, m_Viewport->getXReal());
222 sint32 newY = std::min(areaY, m_Viewport->getYReal());
223 areaW = std::max(areaX + areaW, m_Viewport->getXReal() + m_Viewport->getWReal()) - newX;
224 areaH = std::max(areaY + areaH, m_Viewport->getYReal() + m_Viewport->getHReal()) - newY;
225 areaX = newX;
226 areaY = newY;
230 // ----------------------------------------------------------------------------
231 void CSSBackgroundRenderer::calculateSize(const CSSBackground &bg, sint32 &texW, sint32 &texH) const
233 sint32 areaX, areaY, areaW, areaH;
234 getPositioningArea(bg, areaX, areaY, areaW, areaH);
236 sint32 vpW=0;
237 sint32 vpH=0;
238 if (m_Viewport)
240 vpW = m_Viewport->getWReal();
241 vpH = m_Viewport->getHReal();
244 float whRatio = (float)texW / (float)texH;
245 switch(bg.size)
247 case CSS_VALUE_LENGTH:
249 if (bg.width.isAuto() && bg.height.isAuto())
251 // no-op
253 else if (bg.width.isAuto())
255 texH = bg.height.calculate(areaH, m_FontSize, m_RootFontSize, vpW, vpH);
256 texW = texH * whRatio;
258 else if (bg.height.isAuto())
260 // calculate Height
261 texW = bg.width.calculate(areaW, m_FontSize, m_RootFontSize, vpW, vpH);
262 texH = texW / whRatio;
264 else
266 texW = bg.width.calculate(areaW, m_FontSize, m_RootFontSize, vpW, vpH);
267 texH = bg.height.calculate(areaH, m_FontSize, m_RootFontSize, vpW, vpH);
269 break;
271 case CSS_VALUE_AUTO:
273 // no-op
274 break;
276 case CSS_VALUE_COVER:
278 float canvasRatio = (float)areaW / (float)areaH;
279 if (whRatio < canvasRatio)
281 texW = areaW;
282 texH = areaW / whRatio;
283 } else {
284 texW = areaH * whRatio;
285 texH = areaH;
287 break;
289 case CSS_VALUE_CONTAIN:
291 // same as covert, but ratio check is reversed
292 float canvasRatio = (float)areaW / (float)areaH;
293 if (whRatio > canvasRatio)
295 texW = areaW;
296 texH = areaW / whRatio;
297 } else {
298 texW = areaH * whRatio;
299 texH = areaH;
301 break;
306 // ----------------------------------------------------------------------------
307 void CSSBackgroundRenderer::calculatePosition(const CSSBackground &bg, sint32 &texX, sint32 &texY, sint32 &texW, sint32 &texH) const
309 sint32 areaX, areaY, areaW, areaH;
310 getPositioningArea(bg, areaX, areaY, areaW, areaH);
312 sint32 vpW=0;
313 sint32 vpH=0;
314 if (m_Viewport)
316 vpW = m_Viewport->getWReal();
317 vpH = m_Viewport->getHReal();
320 float ofsX = bg.xPosition.calculate(1, m_FontSize, m_RootFontSize, vpW, vpH);
321 float ofsY = bg.yPosition.calculate(1, m_FontSize, m_RootFontSize, vpW, vpH);
323 if (bg.xPosition.isPercent() || bg.xAnchor == CSS_VALUE_CENTER)
325 if (bg.xAnchor == CSS_VALUE_RIGHT)
326 ofsX = 1.f - ofsX;
327 else if (bg.xAnchor == CSS_VALUE_CENTER)
328 ofsX = 0.5f;
330 ofsX = (float)(areaW - texW) * ofsX;
332 else if (bg.xAnchor == CSS_VALUE_RIGHT)
334 ofsX = areaW - texW - ofsX;
337 // areaY is bottom edge, areaY+areaH is top edge
338 if (bg.yPosition.isPercent() || bg.yAnchor == CSS_VALUE_CENTER)
340 if (bg.yAnchor == CSS_VALUE_TOP)
341 ofsY = 1.f - ofsY;
342 else if (bg.yAnchor == CSS_VALUE_CENTER)
343 ofsY = 0.5f;
345 ofsY = (float)(areaH - texH) * ofsY;
347 else if (bg.yAnchor == CSS_VALUE_TOP)
349 ofsY = areaH - texH - ofsY;
352 texX = areaX + ofsX;
353 texY = areaY + ofsY;
356 // ----------------------------------------------------------------------------
357 void CSSBackgroundRenderer::getImageTile(sint32 &tilePos, sint32 &tileSize, sint32 &spacing, sint32 &tiles, sint32 areaPos, sint32 areaSize, CSSValueType repeat) const
359 switch(repeat)
361 case CSS_VALUE_NOREPEAT:
363 tiles = 1;
364 spacing = 0;
365 break;
367 case CSS_VALUE_SPACE:
369 // if no space for 2+ tiles, then show single one on calculated tilePos
370 if (tileSize * 2 > areaSize)
372 // set spacing large enough to only display single tile
373 tiles = 1;
374 spacing = areaSize;
376 else
378 // available for middle tiles
379 sint32 midSize = (areaSize - tileSize*2);
380 // number of middle tiles
381 sint32 midTiles = midSize / tileSize;
383 tiles = 2 + midTiles;
384 tilePos = areaPos;
385 // int div for floor()
386 spacing = ( midSize - tileSize * midTiles) / (midTiles + 1);
388 break;
390 case CSS_VALUE_ROUND:
391 // fall-thru - size is already calculated
392 case CSS_VALUE_REPEAT:
393 // fall-thru
394 default:
396 tilePos -= std::ceil(abs(tilePos - areaPos)/(float)tileSize)*tileSize;
397 tiles = std::ceil((std::abs(areaPos - tilePos) + areaSize) / (float)tileSize);
398 spacing = 0;
399 break;
404 // ----------------------------------------------------------------------------
405 void CSSBackgroundRenderer::calculateTiles(const CSSBackground &bg, sint32 &texX, sint32 &texY, sint32 &texW, sint32 &texH, sint32 &tilesX, sint32 &tilesY, sint32 &spacingX, sint32 &spacingY) const
407 sint32 areaX, areaY, areaW, areaH;
408 getPositioningArea(bg, areaX, areaY, areaW, areaH);
410 // texX,texY is for position area (ie content-box), but painting area can be bigger (ie border-box)
411 sint32 paintX, paintY, paintW, paintH;
412 getPaintingArea(bg, paintX, paintY, paintW, paintH);
413 if (paintX < areaX)
414 areaX -= std::ceil((areaX - paintX) / (float)texW) * texW;
415 if ((paintX + paintW) > (areaX + areaW))
416 areaW += std::ceil(((paintX + paintW) - (areaX + areaW)) / (float)texW) * texW;
417 if (paintY < areaY)
418 areaY -= std::ceil((areaY - paintY) / (float)texH) * texH;
419 if ((paintY + paintH) > (areaY + areaH))
420 areaH += std::ceil(((paintY + paintH) - (areaY + areaH)) / (float)texH) * texH;
422 if (texW <= 0 || texH <= 0 || areaW <= 0 || areaH <= 0)
424 tilesX = tilesY = 0;
425 spacingX = spacingY = 0;
426 return;
429 if (bg.repeatX == CSS_VALUE_ROUND)
431 sint numTiles = std::max(1, (sint)std::round((float)areaW / texW));
432 texW = areaW / numTiles;
433 if (bg.height.isAuto() && bg.repeatY != CSS_VALUE_ROUND)
435 float aspect = (float)areaW / (numTiles * texW);
436 texH = texW * aspect;
440 if (bg.repeatY == CSS_VALUE_ROUND)
442 sint numTiles = std::max(1, (sint)std::round((float)areaH / texH));
443 texH = areaH / numTiles;
444 if (bg.width.isAuto() && bg.repeatX != CSS_VALUE_ROUND)
446 float aspect = (float)areaH / (numTiles * texH);
447 texW = texH * aspect;
451 getImageTile(texX, texW, spacingX, tilesX, areaX, areaW, bg.repeatX);
452 getImageTile(texY, texH, spacingY, tilesY, areaY, areaH, bg.repeatY);
455 // ----------------------------------------------------------------------------
456 void CSSBackgroundRenderer::buildColorQuads(const CSSBackground &bg)
458 if (bg.color.A == 0)
459 return;
461 // painting area defined with background-clip
462 sint32 x, y, w, h;
463 getPaintingArea(bg, x, y, w, h);
465 if (w <= 0 || h <= 0)
466 return;
468 CViewRenderer &rVR = *CViewRenderer::getInstance();
470 SDrawQueue shape;
471 shape.Quad.Uv0.set(0.f, 1.f);
472 shape.Quad.Uv1.set(1.f, 1.f);
473 shape.Quad.Uv2.set(1.f, 0.f);
474 shape.Quad.Uv3.set(0.f, 0.f);
476 shape.Quad.V0.set(x, y, 0);
477 shape.Quad.V1.set(x+w, y, 0);
478 shape.Quad.V2.set(x+w, y+h, 0);
479 shape.Quad.V3.set(x, y+h, 0);
481 shape.Color = bg.color;
482 shape.TextureId = rVR.getBlankTextureId();
484 m_DrawQueue.push_back(shape);
487 // ----------------------------------------------------------------------------
488 void CSSBackgroundRenderer::buildImageQuads(const CSSBackground &bg, sint32 textureId)
490 // TODO: m_Background should have textureId and that should be "reserved" in CViewRenderer
491 // even before download is finished
492 if (textureId < 0)
493 return;
495 CViewRenderer &rVR = *CViewRenderer::getInstance();
497 sint32 texW = 0;
498 sint32 texH = 0;
499 rVR.getTextureSizeFromId(textureId, texW, texH);
500 if (texW <= 0 || texH <= 0)
501 return;
503 // get requested texture size
504 calculateSize(m_Background, texW, texH);
505 if(texW <= 0 || texH <= 0)
506 return;
508 // get texture left/top corner
509 sint32 texX, texY;
510 calculatePosition(m_Background, texX, texY, texW, texH);
512 sint32 tilesX, tilesY;
513 sint32 spacingX, spacingY;
514 calculateTiles(m_Background, texX, texY, texW, texH, tilesX, tilesY, spacingX, spacingY);
516 sint32 clipL, clipB, clipR, clipT;
517 getPaintingArea(m_Background, clipL, clipB, clipR, clipT);
518 clipR += clipL;
519 clipT += clipB;
521 m_DrawQueue.reserve(tilesX * tilesY + m_DrawQueue.size());
522 for(sint32 tileX = 0; tileX < tilesX; tileX++)
524 for(sint32 tileY = 0; tileY < tilesY; tileY++)
526 sint32 tileL = texX + tileX * (texW + spacingX);
527 sint32 tileB = texY + tileY * (texH + spacingY);
528 sint32 tileR = tileL + texW;
529 sint32 tileT = tileB + texH;
531 // tile is outside clip area
532 if (tileT <= clipB || tileB >= clipT || tileR <= clipL || tileL >= clipR)
533 continue;
535 CUV uv0(0,1);
536 CUV uv1(1,1);
537 CUV uv2(1,0);
538 CUV uv3(0,0);
540 // clip if tile not totally inside clip area
541 if (!(tileL >= clipL && tileR <= clipR && tileB >= clipB && tileT <= clipT))
543 float ratio;
544 if (tileL < clipL)
546 ratio = ((float)(clipL - tileL))/((float)(tileR - tileL));
547 tileL = clipL;
548 uv0.U += ratio*(uv1.U-uv0.U);
549 uv3.U += ratio*(uv2.U-uv3.U);
552 if (tileB < clipB)
554 ratio = ((float)(clipB - tileB))/((float)(tileT - tileB));
555 tileB = clipB;
556 uv0.V += ratio*(uv3.V-uv0.V);
557 uv1.V += ratio*(uv2.V-uv1.V);
560 if (tileR > clipR)
562 ratio = ((float)(clipR - tileR))/((float)(tileL - tileR));
563 tileR = clipR;
564 uv2.U += ratio*(uv3.U-uv2.U);
565 uv1.U += ratio*(uv0.U-uv1.U);
568 if (tileT > clipT)
570 ratio = ((float)(clipT - tileT))/((float)(tileB - tileT));
571 tileT = clipT;
572 uv2.V += ratio*(uv1.V-uv2.V);
573 uv3.V += ratio*(uv0.V-uv3.V);
577 SDrawQueue shape;
578 shape.Quad.Uv0 = uv0;
579 shape.Quad.Uv1 = uv1;
580 shape.Quad.Uv2 = uv2;
581 shape.Quad.Uv3 = uv3;
583 shape.Color = CRGBA::White;
584 shape.TextureId = textureId;
586 shape.Quad.V0.set(tileL, tileB, 0);
587 shape.Quad.V1.set(tileR, tileB, 0);
588 shape.Quad.V2.set(tileR, tileT, 0);
589 shape.Quad.V3.set(tileL, tileT, 0);
591 m_DrawQueue.push_back(shape);
596 }//namespace