diff --git a/nel/include/nel/gui/css_background.h b/nel/include/nel/gui/css_background.h
new file mode 100644
index 000000000..87ce87d0e
--- /dev/null
+++ b/nel/include/nel/gui/css_background.h
@@ -0,0 +1,83 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010-2019 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef CL_CSS_BACKGROUND_H
+#define CL_CSS_BACKGROUND_H
+
+#include "nel/misc/types_nl.h"
+#include "nel/misc/rgba.h"
+#include "nel/gui/css_types.h"
+#include "nel/gui/css_length.h"
+
+namespace NLGUI
+{
+ /**
+ * \brief CSS background info
+ * \date 2021-07-02 11:36 GMT
+ * \author Meelis Mägi (Nimetu)
+ */
+ class CSSBackground
+ {
+ public:
+ CSSBackground()
+ :color(NLMISC::CRGBA::Transparent),
+ repeatX(CSS_VALUE_REPEAT), repeatY(CSS_VALUE_REPEAT), attachment(CSS_VALUE_SCROLL),
+ xAnchor(CSS_VALUE_LEFT), yAnchor(CSS_VALUE_TOP),
+ clip(CSS_VALUE_BORDER_BOX), origin(CSS_VALUE_PADDING_BOX), size(CSS_VALUE_AUTO)
+ {}
+
+ void setImage(const std::string &value);
+ void setPosition(const std::string &value);
+ void setSize(const std::string &value);
+ void setRepeat(const std::string &value);
+ void setOrigin(const std::string &value);
+ void setClip(const std::string &value);
+ void setAttachment(const std::string &value);
+ void setColor(const std::string &value);
+
+ public:
+ // TODO: only final layer has color
+ NLMISC::CRGBA color;
+ std::string image;
+
+ CSSValueType repeatX;
+ CSSValueType repeatY;
+ CSSValueType attachment;
+
+ CSSValueType xAnchor;
+ CSSValueType yAnchor;
+ CSSLength xPosition;
+ CSSLength yPosition;
+
+ CSSValueType clip;
+ CSSValueType origin;
+
+ CSSValueType size;
+ CSSLength width;
+ CSSLength height;
+
+ private:
+ void positionFromOne(const std::vector &parts);
+ void positionFromTwo(const std::vector &parts);
+ void positionFromThree(const std::vector &parts);
+ void positionFromFour(const std::vector &parts);
+ };
+
+}//namespace
+
+#endif // CL_CSS_BACKGROUND_H
+
+
diff --git a/nel/include/nel/gui/css_background_renderer.h b/nel/include/nel/gui/css_background_renderer.h
new file mode 100644
index 000000000..86b31cb3d
--- /dev/null
+++ b/nel/include/nel/gui/css_background_renderer.h
@@ -0,0 +1,193 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010-2019 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+
+
+#ifndef NL_CSS_BACKGROUND_RENDERER_H
+#define NL_CSS_BACKGROUND_RENDERER_H
+
+#include "nel/misc/types_nl.h"
+#include "nel/misc/rgba.h"
+#include "nel/misc/geom_ext.h"
+#include "nel/gui/css_types.h"
+#include "nel/gui/css_background.h"
+
+namespace NLGUI
+{
+class CInterfaceElement;
+
+/**
+ * \brief Border renderer for GUI classes
+ * \date 2021-06-29 15:17 GMT
+ * \author Meelis Mägi (Nimetu)
+ */
+class CSSBackgroundRenderer
+{
+public:
+ // alpha value from parent
+ uint8 CurrentAlpha;
+
+ // TODO: should be moved to CSSBackground
+ sint32 TextureId;
+
+public:
+ CSSBackgroundRenderer();
+ ~CSSBackgroundRenderer();
+
+ // return true if no background is set
+ bool isEmpty() const
+ {
+ return m_Background.image.empty() && m_Background.color.A == 0;
+ }
+
+ void setModulateGlobalColor(bool m) { m_ModulateGlobalColor = m; }
+
+ void updateCoords();
+ void invalidateCoords() { m_Dirty = true; }
+ void invalidateContent() { m_Dirty = true; };
+
+ void clear();
+ void setBackground(const CSSBackground &bg);
+
+ // helper function to change background image
+ void setImage(const std::string &bgtex);
+ void setImageRepeat(bool b);
+ void setImageCover(bool b);
+
+ // helper function to change background color
+ void setColor(const NLMISC::CRGBA &color)
+ {
+ m_Dirty = true;
+ m_Background.color = color;
+ }
+
+ NLMISC::CRGBA getColor() const
+ {
+ return m_Background.color;
+ }
+
+ // override painting area to be at least the size of viewport (ie, background)
+ void setFillViewport(bool b) {
+ m_Dirty = true;
+ m_FillViewport = b;
+ }
+
+ void setViewport(CInterfaceElement *root)
+ {
+ m_Dirty = true;
+ m_Viewport = root;
+ }
+
+ void setBorderArea(sint32 x, sint32 y, sint32 w, sint32 h)
+ {
+ m_Dirty = true;
+ m_BorderX = x;
+ m_BorderY = y;
+ m_BorderW = w;
+ m_BorderH = h;
+ }
+
+ void setPaddingArea(sint32 x, sint32 y, sint32 w, sint32 h)
+ {
+ m_Dirty = true;
+ m_PaddingX = x;
+ m_PaddingY = y;
+ m_PaddingW = w;
+ m_PaddingH = h;
+ }
+
+ void setContentArea(sint32 x, sint32 y, sint32 w, sint32 h)
+ {
+ m_Dirty = true;
+ m_ContentX = x;
+ m_ContentY = y;
+ m_ContentW = w;
+ m_ContentH = h;
+ }
+
+ // sizes for em, rem units
+ void setFontSize(float rootFontSize, float fontSize)
+ {
+ m_Dirty = true;
+ m_RootFontSize = rootFontSize;
+ m_FontSize = fontSize;
+ }
+
+ void draw();
+
+private:
+ sint32 m_BorderX, m_BorderY, m_BorderW, m_BorderH;
+ sint32 m_PaddingX, m_PaddingY, m_PaddingW, m_PaddingH;
+ sint32 m_ContentX, m_ContentY, m_ContentW, m_ContentH;
+
+ // font size for 'rem'
+ float m_RootFontSize;
+
+ // font size for 'em'
+ float m_FontSize;
+
+ // viewport element for vw,wh,vmin,vmax
+ CInterfaceElement* m_Viewport;
+
+ struct SDrawQueue
+ {
+ sint32 TextureId;
+ NLMISC::CQuadUV Quad;
+ NLMISC::CRGBA Color;
+ };
+ std::vector m_DrawQueue;
+
+ const sint8 m_RenderLayer;
+ bool m_ModulateGlobalColor;
+ // if true, painting area returns area at least the size of viewport (ie, background)
+ bool m_FillViewport;
+
+ // if true, then updateCoords() is called from draw()
+ bool m_Dirty;
+
+ CSSBackground m_Background;
+
+ // get clip area based on background-clip
+ void getPaintingArea(const CSSBackground &bg, sint32 &areaX, sint32 &areaY, sint32 &areaW, sint32 &areaH) const;
+
+ // get positioning area based on background-origin
+ void getPositioningArea(const CSSBackground &bg, sint32 &areaX, sint32 &areaY, sint32 &areaW, sint32 &areaH) const;
+
+ // calculate image size based on background-size
+ void calculateSize(const CSSBackground &bg, sint32 &texW, sint32 &texH) const;
+
+ // calculate image position based on background-position
+ void calculatePosition(const CSSBackground &bg, sint32 &texX, sint32 &texY, sint32 &texW, sint32 &texH) const;
+
+ // calculate image tile position, size, count, and spacing based on background-repeat
+ void calculateTiles(const CSSBackground &bg, sint32 &texX, sint32 &texY, sint32 &texW, sint32 &texH, sint32 &tilesX, sint32 &tilesY, sint32 &spacingX, sint32 &spacingY) const;
+
+ // position, size, and count for first tile to cover an area
+ void getImageTile(sint32 &tilePos, sint32 &tileSize, sint32 &spacing, sint32 &tiles, sint32 areaPos, sint32 areaSize, CSSValueType repeat) const;
+
+ // push background color to draw queue
+ void buildColorQuads(const CSSBackground &bg);
+
+ // push background image to draw quque
+ void buildImageQuads(const CSSBackground &bg, sint32 textureId);
+
+}; // CSSBackgroundRenderer
+
+}//namespace
+
+#endif // NL_CSS_BACKGROUND_RENDERER_H
+
+
diff --git a/nel/include/nel/gui/css_length.h b/nel/include/nel/gui/css_length.h
new file mode 100644
index 000000000..d7b8c657b
--- /dev/null
+++ b/nel/include/nel/gui/css_length.h
@@ -0,0 +1,76 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010-2021 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#ifndef CL_CSS_LENGTH_H
+#define CL_CSS_LENGTH_H
+
+#include "nel/misc/types_nl.h"
+#include "nel/gui/css_types.h"
+
+namespace NLGUI
+{
+ /**
+ * \brief CSS types used in GUI classes
+ * \date 2021-07-02 11:36 GMT
+ * \author Meelis Mägi (Nimetu)
+ */
+
+ class CSSLength
+ {
+ public:
+ enum Kind {
+ Auto, Relative, Fixed
+ };
+
+ CSSLength(float value = 0, CSSUnitType unit = CSS_UNIT_NONE, Kind kind = Auto)
+ : m_Value(value), m_Unit(unit), m_Kind(Auto)
+ {}
+
+ void setAuto() { m_Kind = Auto; }
+ bool parseValue(const std::string &value, bool allowPercent = true, bool allowNegative = false);
+ void setFloatValue(float f, const std::string &unit);
+
+ float getValue() const;
+ float getFloat() const { return m_Value; }
+
+ bool isPercent() const { return m_Unit == CSS_UNIT_PERCENT; }
+
+ bool isAuto() const { return m_Kind == Auto; }
+ bool isRelative() const { return m_Kind == Relative; }
+
+ // % uses relValue
+ // em uses emSize
+ // rem uses remSize
+ // vw,vh,vi,vb,vmin,vmax uses vwSize/vhSize
+ float calculate(uint32 relValue, uint32 emSize, uint32 remSize, uint32 vwSize, uint32 whSize) const;
+
+ CSSUnitType getUnit() const { return m_Unit; }
+
+ std::string toString() const;
+
+ private:
+ void setUnit(const std::string &unit);
+
+ float m_Value;
+ CSSUnitType m_Unit;
+ Kind m_Kind;
+ };
+
+}//namespace
+
+#endif // CL_CSS_LENGTH_H
+
+
diff --git a/nel/include/nel/gui/css_types.h b/nel/include/nel/gui/css_types.h
index dd80e308f..ac59af422 100644
--- a/nel/include/nel/gui/css_types.h
+++ b/nel/include/nel/gui/css_types.h
@@ -47,6 +47,44 @@ namespace NLGUI
CSS_LINE_WIDTH_THICK = 5
};
+ enum CSSUnitType {
+ CSS_UNIT_NONE = 0,
+ CSS_UNIT_EM,
+ CSS_UNIT_REM,
+ CSS_UNIT_PERCENT,
+ CSS_UNIT_PX,
+ CSS_UNIT_PT,
+ CSS_UNIT_VW,
+ CSS_UNIT_VH,
+ CSS_UNIT_VI,
+ CSS_UNIT_VB,
+ CSS_UNIT_VMIN,
+ CSS_UNIT_VMAX
+ };
+
+ enum CSSValueType {
+ CSS_VALUE_NONE = 0,
+ CSS_VALUE_REPEAT,
+ CSS_VALUE_SPACE,
+ CSS_VALUE_ROUND,
+ CSS_VALUE_NOREPEAT,
+ CSS_VALUE_FIXED,
+ CSS_VALUE_LOCAL,
+ CSS_VALUE_SCROLL,
+ CSS_VALUE_LEFT,
+ CSS_VALUE_CENTER,
+ CSS_VALUE_RIGHT,
+ CSS_VALUE_TOP,
+ CSS_VALUE_BOTTOM,
+ CSS_VALUE_BORDER_BOX,
+ CSS_VALUE_PADDING_BOX,
+ CSS_VALUE_CONTENT_BOX,
+ CSS_VALUE_LENGTH,
+ CSS_VALUE_AUTO,
+ CSS_VALUE_COVER,
+ CSS_VALUE_CONTAIN
+ };
+
}//namespace
#endif // CL_CSS_TYPES_H
diff --git a/nel/src/gui/css_background.cpp b/nel/src/gui/css_background.cpp
new file mode 100644
index 000000000..4665a0c42
--- /dev/null
+++ b/nel/src/gui/css_background.cpp
@@ -0,0 +1,427 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010-2021 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+
+#include
+#include "nel/gui/libwww.h"
+#include "nel/gui/css_length.h"
+#include "nel/gui/css_background.h"
+
+using namespace NLMISC;
+
+#ifdef DEBUG_NEW
+#define new DEBUG_NEW
+#endif
+
+namespace NLGUI
+{
+
+void CSSBackground::setImage(const std::string &value)
+{
+ image = value;
+}
+
+void CSSBackground::setPosition(const std::string &value)
+{
+ std::vector parts;
+ splitString(toLowerAscii(value), " ", parts);
+
+ if (parts.empty() || parts.size() > 4)
+ return;
+
+ switch(parts.size())
+ {
+ case 1:
+ positionFromOne(parts);
+ break;
+ case 2:
+ positionFromTwo(parts);
+ break;
+ case 3:
+ positionFromThree(parts);
+ break;
+ case 4:
+ positionFromFour(parts);
+ break;
+ default:
+ return;
+ }
+}
+
+void CSSBackground::setSize(const std::string &value)
+{
+ std::vector parts;
+ splitString(toLowerAscii(value), " ", parts);
+ if (parts.size() > 2)
+ return;
+
+ if (parts.size() == 1 && (parts[0] == "cover" || parts[0] == "contain"))
+ {
+ if (parts[0] == "cover")
+ size = CSS_VALUE_COVER;
+ else
+ size = CSS_VALUE_CONTAIN;
+
+ width.setAuto();
+ height.setAuto();
+ return;
+ }
+
+ // height will default to 'auto' if not set
+ if (parts.size() == 1)
+ parts.push_back("auto");
+
+ if (parts[0] == "auto" && parts[1] == "auto")
+ {
+ size = CSS_VALUE_AUTO;
+ width.setAuto();
+ height.setAuto();
+ return;
+ }
+
+ CSSLength newW, newH;
+ bool success = true;
+ if (parts[0] == "auto")
+ {
+ newW.setAuto();
+ }
+ else
+ {
+ float fval;
+ std::string unit;
+ if (!getCssLength(fval, unit, parts[0]))
+ {
+ nlwarning("Failed to parse background-size[0] '%s'", parts[0].c_str());
+ return;
+ }
+ newW.setFloatValue(fval, unit);
+ }
+
+ if (parts[1] == "auto")
+ {
+ newH.setAuto();
+ }
+ else
+ {
+ float fval;
+ std::string unit;
+ if (!getCssLength(fval, unit, parts[1]))
+ {
+ nlwarning("Failed to parse background-size[1] '%s'", parts[1].c_str());
+ return;
+ }
+ newH.setFloatValue(fval, unit);
+ }
+
+ size = CSS_VALUE_LENGTH;
+ width = newW;
+ height = newH;
+}
+
+void CSSBackground::setRepeat(const std::string &value)
+{
+ std::vector parts;
+ splitString(toLowerAscii(value), " ", parts);
+ if (parts.size() == 0 || parts.size() > 2)
+ return;
+
+ if (parts.size() == 1)
+ {
+ if (parts[0] == "repeat-x")
+ parts.push_back("no-repeat");
+ else if (parts[0] == "repeat-y")
+ parts.insert(parts.begin(), "no-repeat");
+ else //repeat, space, round, no-repeat
+ parts.push_back(parts[0]);
+ }
+
+
+ if (parts[0] == "repeat") repeatX = CSS_VALUE_REPEAT;
+ else if (parts[0] == "no-repeat") repeatX = CSS_VALUE_NOREPEAT;
+ else if (parts[0] == "space") repeatX = CSS_VALUE_SPACE;
+ else if (parts[0] == "round") repeatX = CSS_VALUE_ROUND;
+ else repeatX = CSS_VALUE_REPEAT;
+
+ if (parts[1] == "repeat") repeatY = CSS_VALUE_REPEAT;
+ else if (parts[1] == "no-repeat") repeatY = CSS_VALUE_NOREPEAT;
+ else if (parts[1] == "space") repeatY = CSS_VALUE_SPACE;
+ else if (parts[1] == "round") repeatY = CSS_VALUE_ROUND;
+ else repeatY = CSS_VALUE_REPEAT;
+}
+
+void CSSBackground::setOrigin(const std::string &value)
+{
+ if (value == "border-box") origin = CSS_VALUE_BORDER_BOX;
+ else if (value == "padding-box") origin = CSS_VALUE_PADDING_BOX;
+ else if (value == "content-box") origin = CSS_VALUE_CONTENT_BOX;
+ else origin = CSS_VALUE_PADDING_BOX;
+}
+
+void CSSBackground::setClip(const std::string &value)
+{
+ if (value == "border-box") clip = CSS_VALUE_BORDER_BOX;
+ else if (value == "padding-box") clip = CSS_VALUE_PADDING_BOX;
+ else if (value == "content-box") clip = CSS_VALUE_CONTENT_BOX;
+ //else if (value == "text") clip = CSSValueType::Text;
+ else clip = CSS_VALUE_PADDING_BOX;
+}
+
+void CSSBackground::setAttachment(const std::string &value)
+{
+ if (value == "fixed") attachment = CSS_VALUE_FIXED;
+ else if (value == "local") attachment = CSS_VALUE_LOCAL;
+ else if (value == "scroll") attachment = CSS_VALUE_SCROLL;
+ else attachment = CSS_VALUE_SCROLL;
+}
+
+void CSSBackground::setColor(const std::string &value)
+{
+ NLMISC::CRGBA tmp;
+ if (scanHTMLColor(value.c_str(), tmp))
+ color = tmp;
+}
+
+static bool isHorizontalKeyword(const std::string &val)
+{
+ return val == "left" || val == "right";
+}
+
+static bool isVerticalKeyword(const std::string &val)
+{
+ return val == "top" || val == "bottom";
+}
+
+void CSSBackground::positionFromOne(const std::vector &parts)
+{
+ CSSValueType newH = CSS_VALUE_LEFT;
+ CSSValueType newV = CSS_VALUE_TOP;
+ CSSLength newX, newY;
+ newX.setFloatValue(0, "%");
+ newY.setFloatValue(0, "%");
+
+ uint index = 0;
+ float fval;
+ std::string unit;
+ if (isHorizontalKeyword(parts[index]))
+ {
+ newH = parts[index] == "left" ? CSS_VALUE_LEFT : CSS_VALUE_RIGHT;
+ newV = CSS_VALUE_CENTER;
+ }
+ else if (isVerticalKeyword(parts[index]))
+ {
+ newH = CSS_VALUE_CENTER;
+ newV = parts[index] == "top" ? CSS_VALUE_TOP : CSS_VALUE_BOTTOM;
+ }
+ else if (parts[index] == "center")
+ {
+ newH = CSS_VALUE_CENTER;
+ newV = CSS_VALUE_CENTER;
+ }
+ else if (getCssLength(fval, unit, parts[index], true))
+ {
+ newX.setFloatValue(fval, unit);
+ newV = CSS_VALUE_CENTER;
+ }
+ else
+ {
+ return;
+ }
+
+ xAnchor = newH;
+ yAnchor = newV;
+ xPosition = newX;
+ yPosition = newY;
+}
+
+void CSSBackground::positionFromTwo(const std::vector &parts)
+{
+ CSSValueType newH = CSS_VALUE_LEFT;
+ CSSValueType newV = CSS_VALUE_TOP;
+ CSSLength newX, newY;
+ newX.setFloatValue(0, "%");
+ newY.setFloatValue(0, "%");
+
+ float fval;
+ std::string unit;
+ uint index = 0;
+ bool hasCenter = false;
+ bool hasX = false;
+ bool hasY = false;
+ for (uint index = 0; index < parts.size(); index++)
+ {
+ if (parts[index] == "center")
+ {
+ hasCenter = true;
+ }
+ else if (isHorizontalKeyword(parts[index]))
+ {
+ if (hasX) return;
+ hasX = true;
+ newH = parts[index] == "left" ? CSS_VALUE_LEFT : CSS_VALUE_RIGHT;
+ }
+ else if (isVerticalKeyword(parts[index]))
+ {
+ if (hasY) return;
+ hasY = true;
+ newV = parts[index] == "top" ? CSS_VALUE_TOP : CSS_VALUE_BOTTOM;
+ }
+ else if (getCssLength(fval, unit, parts[index], true))
+ {
+ // invalid: 'top 50%';
+ if (hasY) return;
+ if (!hasX)
+ {
+ hasX = true;
+ newX.setFloatValue(fval, unit);
+ }
+ else
+ {
+ hasY = true;
+ newY.setFloatValue(fval, unit);
+ }
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ if (hasCenter)
+ {
+ if (!hasX)
+ newH = CSS_VALUE_CENTER;
+ if (!hasY)
+ newV = CSS_VALUE_CENTER;
+ }
+
+ xAnchor = newH;
+ yAnchor = newV;
+ xPosition = newX;
+ yPosition = newY;
+}
+
+void CSSBackground::positionFromThree(const std::vector &parts)
+{
+ CSSValueType newH = CSS_VALUE_LEFT;
+ CSSValueType newV = CSS_VALUE_TOP;
+ CSSLength newX, newY;
+ newX.setFloatValue(0, "%");
+ newY.setFloatValue(0, "%");
+
+ float fval;
+ std::string unit;
+ bool hasCenter = false;
+ bool hasX = false;
+ bool hasY = false;
+ for(uint index = 0; index < 3; index++)
+ {
+ if (parts[index] == "center")
+ {
+ if (hasCenter) return;
+ hasCenter = true;
+ }
+ else if (isHorizontalKeyword(parts[index]))
+ {
+ if (hasX) return;
+ hasX = true;
+ newH = parts[index] == "left" ? CSS_VALUE_LEFT : CSS_VALUE_RIGHT;
+ if ((index+1) < parts.size() && getCssLength(fval, unit, parts[index+1], true))
+ {
+ newX.setFloatValue(fval, unit);
+ index++;
+ }
+ }
+ else if (isVerticalKeyword(parts[index]))
+ {
+ if (hasY) return;
+ hasY = true;
+ newV = parts[index] == "top" ? CSS_VALUE_TOP : CSS_VALUE_BOTTOM;
+ if ((index+1) < parts.size() && getCssLength(fval, unit, parts[index+1], true))
+ {
+ newY.setFloatValue(fval, unit);
+ index++;
+ }
+ }
+ else
+ {
+ return;
+ }
+ }
+ if (hasCenter)
+ {
+ if (hasX && hasY)
+ return;
+
+ if (!hasX)
+ newH = CSS_VALUE_CENTER;
+ else
+ newV = CSS_VALUE_CENTER;
+ }
+
+ xAnchor = newH;
+ yAnchor = newV;
+ xPosition = newX;
+ yPosition = newY;
+}
+
+void CSSBackground::positionFromFour(const std::vector &parts)
+{
+ CSSValueType newH = CSS_VALUE_LEFT;
+ CSSValueType newV = CSS_VALUE_TOP;
+ CSSLength newX, newY;
+ newX.setFloatValue(0, "%");
+ newY.setFloatValue(0, "%");
+
+ float fval;
+ std::string unit;
+ bool hasX = false;
+ bool hasY = false;
+ for(uint index = 0; index<4; index+=2)
+ {
+ if (parts[index] == "center")
+ return;
+
+ if (isHorizontalKeyword(parts[index]))
+ {
+ if (hasX) return;
+ hasX = true;
+ if (!getCssLength(fval, unit, parts[index+1], true)) return;
+ newH = parts[index] == "left" ? CSS_VALUE_LEFT : CSS_VALUE_RIGHT;
+ newX.setFloatValue(fval, unit);
+ }
+ else if (isVerticalKeyword(parts[index]))
+ {
+ if (hasY) return;
+ hasY = true;
+ if (!getCssLength(fval, unit, parts[index+1], true)) return;
+ newV = parts[index] == "top" ? CSS_VALUE_TOP : CSS_VALUE_BOTTOM;
+ newY.setFloatValue(fval, unit);
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ xAnchor = newH;
+ yAnchor = newV;
+ xPosition = newX;
+ yPosition = newY;
+}
+
+} // namespace
+
diff --git a/nel/src/gui/css_background_renderer.cpp b/nel/src/gui/css_background_renderer.cpp
new file mode 100644
index 000000000..4d7df021e
--- /dev/null
+++ b/nel/src/gui/css_background_renderer.cpp
@@ -0,0 +1,607 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010-2019 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+
+#include "stdpch.h"
+#include "nel/gui/css_background_renderer.h"
+#include "nel/gui/css_border_renderer.h"
+#include "nel/gui/view_renderer.h"
+#include "nel/gui/widget_manager.h"
+#include "nel/gui/view_bitmap.h"
+
+using namespace std;
+using namespace NLMISC;
+
+#ifdef DEBUG_NEW
+#define new DEBUG_NEW
+#endif
+
+namespace NLGUI
+{
+ // ----------------------------------------------------------------------------
+ CSSBackgroundRenderer::CSSBackgroundRenderer()
+ : CurrentAlpha(255), TextureId(-1),
+ m_BorderX(0), m_BorderY(0), m_BorderW(0), m_BorderH(0),
+ m_PaddingX(0), m_PaddingY(0), m_PaddingW(0), m_PaddingH(0),
+ m_ContentX(0), m_ContentY(0), m_ContentW(0), m_ContentH(0),
+ m_RootFontSize(16.f), m_FontSize(16.f), m_Viewport(NULL),
+ m_RenderLayer(0), m_ModulateGlobalColor(false), m_FillViewport(false),
+ m_Dirty(false)
+ {
+ }
+
+ // ----------------------------------------------------------------------------
+ CSSBackgroundRenderer::~CSSBackgroundRenderer()
+ {
+ if (TextureId != -1)
+ CViewRenderer::getInstance()->deleteTexture(TextureId);
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::clear()
+ {
+ m_Dirty = true;
+
+ if (TextureId != -1)
+ CViewRenderer::getInstance()->deleteTexture(TextureId);
+
+ TextureId = -1;
+ m_Background.image.clear();
+ m_Background.color.A = 0;
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::setBackground(const CSSBackground &bg)
+ {
+ m_Dirty = true;
+ // TODO: CSSBackground should keep track of TextureId
+ CViewRenderer &rVR = *CViewRenderer::getInstance();
+ if (bg.image != m_Background.image && TextureId != -1)
+ rVR.deleteTexture(TextureId);
+
+ m_Background = bg;
+ // TODO: does not accept urls
+ if (TextureId == -1 && !bg.image.empty())
+ {
+ // TODO: make CViewRenderer accept urls
+ if (bg.image.find("://") != std::string::npos)
+ TextureId = rVR.createTexture(bg.image, 0, 0, -1, -1, false);
+ }
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::setImage(const std::string &bgtex)
+ {
+ m_Dirty = true;
+ // TODO: CSSBackground should keep track of TextureId
+ CViewRenderer &rVR = *CViewRenderer::getInstance();
+ if (bgtex != m_Background.image && TextureId != -1)
+ {
+ rVR.deleteTexture(TextureId);
+ TextureId = -1;
+ }
+ m_Background.image = bgtex;
+
+ if (TextureId == -1 && !bgtex.empty())
+ {
+ // TODO: make CViewRenderer accept urls
+ if (bgtex.find("://") != std::string::npos)
+ TextureId = rVR.createTexture(bgtex, 0, 0, -1, -1, false);
+ }
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::setImageRepeat(bool b)
+ {
+ m_Background.repeatX = b ? CSS_VALUE_REPEAT : CSS_VALUE_NOREPEAT;
+ m_Background.repeatY = b ? CSS_VALUE_REPEAT : CSS_VALUE_NOREPEAT;
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::setImageCover(bool b)
+ {
+ m_Background.size = b ? CSS_VALUE_COVER : CSS_VALUE_AUTO;
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::updateCoords()
+ {
+ m_Dirty = false;
+ m_DrawQueue.clear();
+
+ // TODO: color from last background layer
+ buildColorQuads(m_Background);
+
+ // -------------------------------------------------------------------
+ // background-image
+ buildImageQuads(m_Background, TextureId);
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::draw() {
+ if (m_Dirty) updateCoords();
+ if (m_DrawQueue.empty()) return;
+
+ CViewRenderer &rVR = *CViewRenderer::getInstance();
+
+ // flush draw cache to ensure correct draw order
+ rVR.flush();
+
+ sint32 clipX, clipY, clipW, clipH;
+ if (m_Viewport)
+ {
+ rVR.getClipWindow(clipX, clipY, clipW, clipH);
+ rVR.setClipWindow(m_Viewport->getXReal(), m_Viewport->getYReal(), m_Viewport->getWReal(), m_Viewport->getHReal());
+ }
+
+ // TODO: no need for widget manager, if global color is set from parent
+ CRGBA globalColor;
+ if (m_ModulateGlobalColor)
+ globalColor = CWidgetManager::getInstance()->getGlobalColor();
+
+ // TODO: there might be issue on draw order IF using multiple textures
+ // and second (top) texture is created before first one.
+ for(uint i = 0; i < m_DrawQueue.size(); ++i)
+ {
+ CRGBA color = m_DrawQueue[i].Color;
+ if (m_ModulateGlobalColor)
+ color.modulateFromColor (color, globalColor);
+
+ color.A = (uint8) (((uint16) CurrentAlpha * (uint16) color.A) >> 8);
+ rVR.drawQuad(m_RenderLayer, m_DrawQueue[i].Quad, m_DrawQueue[i].TextureId, color, false);
+ }
+
+ // flush draw cache to ensure correct draw order
+ rVR.flush();
+
+ if (m_Viewport)
+ rVR.setClipWindow(clipX, clipY, clipW, clipH);
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::getPositioningArea(const CSSBackground &bg, sint32 &areaX, sint32 &areaY, sint32 &areaW, sint32 &areaH) const
+ {
+ switch(bg.origin)
+ {
+ case CSS_VALUE_PADDING_BOX:
+ areaX = m_PaddingX;
+ areaY = m_PaddingY;
+ areaW = m_PaddingW;
+ areaH = m_PaddingH;
+ break;
+ case CSS_VALUE_CONTENT_BOX:
+ areaX = m_ContentX;
+ areaY = m_ContentY;
+ areaW = m_ContentW;
+ areaH = m_ContentH;
+ break;
+ case CSS_VALUE_BORDER_BOX:
+ // fall thru
+ default:
+ areaX = m_BorderX;
+ areaY = m_BorderY;
+ areaW = m_BorderW;
+ areaH = m_BorderH;
+ break;
+ }
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::getPaintingArea(const CSSBackground &bg, sint32 &areaX, sint32 &areaY, sint32 &areaW, sint32 &areaH) const
+ {
+ switch(bg.clip)
+ {
+ case CSS_VALUE_PADDING_BOX:
+ areaX = m_PaddingX;
+ areaY = m_PaddingY;
+ areaW = m_PaddingW;
+ areaH = m_PaddingH;
+ break;
+ case CSS_VALUE_CONTENT_BOX:
+ areaX = m_ContentX;
+ areaY = m_ContentY;
+ areaW = m_ContentW;
+ areaH = m_ContentH;
+ break;
+ case CSS_VALUE_BORDER_BOX:
+ // fall thru
+ default:
+ areaX = m_BorderX;
+ areaY = m_BorderY;
+ areaW = m_BorderW;
+ areaH = m_BorderH;
+ break;
+ }
+
+ if (m_FillViewport && m_Viewport)
+ {
+ sint32 newX = std::min(areaX, m_Viewport->getXReal());
+ sint32 newY = std::min(areaY, m_Viewport->getYReal());
+ areaW = std::max(areaX + areaW, m_Viewport->getXReal() + m_Viewport->getWReal()) - newX;
+ areaH = std::max(areaY + areaH, m_Viewport->getYReal() + m_Viewport->getHReal()) - newY;
+ areaX = newX;
+ areaY = newY;
+ }
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::calculateSize(const CSSBackground &bg, sint32 &texW, sint32 &texH) const
+ {
+ sint32 areaX, areaY, areaW, areaH;
+ getPositioningArea(bg, areaX, areaY, areaW, areaH);
+
+ sint32 vpW=0;
+ sint32 vpH=0;
+ if (m_Viewport)
+ {
+ vpW = m_Viewport->getWReal();
+ vpH = m_Viewport->getHReal();
+ }
+
+ float whRatio = (float)texW / (float)texH;
+ switch(bg.size)
+ {
+ case CSS_VALUE_LENGTH:
+ {
+ if (bg.width.isAuto() && bg.height.isAuto())
+ {
+ // no-op
+ }
+ else if (bg.width.isAuto())
+ {
+ texH = bg.height.calculate(areaH, m_FontSize, m_RootFontSize, vpW, vpH);
+ texW = texH * whRatio;
+ }
+ else if (bg.height.isAuto())
+ {
+ // calculate Height
+ texW = bg.width.calculate(areaW, m_FontSize, m_RootFontSize, vpW, vpH);
+ texH = texW / whRatio;
+ }
+ else
+ {
+ texW = bg.width.calculate(areaW, m_FontSize, m_RootFontSize, vpW, vpH);
+ texH = bg.height.calculate(areaH, m_FontSize, m_RootFontSize, vpW, vpH);
+ }
+ break;
+ }
+ case CSS_VALUE_AUTO:
+ {
+ // no-op
+ break;
+ }
+ case CSS_VALUE_COVER:
+ {
+ float canvasRatio = (float)areaW / (float)areaH;
+ if (whRatio < canvasRatio)
+ {
+ texW = areaW;
+ texH = areaW / whRatio;
+ } else {
+ texW = areaH * whRatio;
+ texH = areaH;
+ }
+ break;
+ }
+ case CSS_VALUE_CONTAIN:
+ {
+ // same as covert, but ratio check is reversed
+ float canvasRatio = (float)areaW / (float)areaH;
+ if (whRatio > canvasRatio)
+ {
+ texW = areaW;
+ texH = areaW / whRatio;
+ } else {
+ texW = areaH * whRatio;
+ texH = areaH;
+ }
+ break;
+ }
+ }
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::calculatePosition(const CSSBackground &bg, sint32 &texX, sint32 &texY, sint32 &texW, sint32 &texH) const
+ {
+ sint32 areaX, areaY, areaW, areaH;
+ getPositioningArea(bg, areaX, areaY, areaW, areaH);
+
+ sint32 vpW=0;
+ sint32 vpH=0;
+ if (m_Viewport)
+ {
+ vpW = m_Viewport->getWReal();
+ vpH = m_Viewport->getHReal();
+ }
+
+ float ofsX = bg.xPosition.calculate(1, m_FontSize, m_RootFontSize, vpW, vpH);
+ float ofsY = bg.yPosition.calculate(1, m_FontSize, m_RootFontSize, vpW, vpH);
+
+ if (bg.xPosition.isPercent() || bg.xAnchor == CSS_VALUE_CENTER)
+ {
+ if (bg.xAnchor == CSS_VALUE_RIGHT)
+ ofsX = 1.f - ofsX;
+ else if (bg.xAnchor == CSS_VALUE_CENTER)
+ ofsX = 0.5f;
+
+ ofsX = (float)(areaW - texW) * ofsX;
+ }
+ else if (bg.xAnchor == CSS_VALUE_RIGHT)
+ {
+ ofsX = areaW - texW - ofsX;
+ }
+
+ // areaY is bottom edge, areaY+areaH is top edge
+ if (bg.yPosition.isPercent() || bg.yAnchor == CSS_VALUE_CENTER)
+ {
+ if (bg.yAnchor == CSS_VALUE_TOP)
+ ofsY = 1.f - ofsY;
+ else if (bg.yAnchor == CSS_VALUE_CENTER)
+ ofsY = 0.5f;
+
+ ofsY = (float)(areaH - texH) * ofsY;
+ }
+ else if (bg.yAnchor == CSS_VALUE_TOP)
+ {
+ ofsY = areaH - texH - ofsY;
+ }
+
+ texX = areaX + ofsX;
+ texY = areaY + ofsY;
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::getImageTile(sint32 &tilePos, sint32 &tileSize, sint32 &spacing, sint32 &tiles, sint32 areaPos, sint32 areaSize, CSSValueType repeat) const
+ {
+ switch(repeat)
+ {
+ case CSS_VALUE_NOREPEAT:
+ {
+ tiles = 1;
+ spacing = 0;
+ break;
+ }
+ case CSS_VALUE_SPACE:
+ {
+ // if no space for 2+ tiles, then show single one on calculated tilePos
+ if (tileSize * 2 > areaSize)
+ {
+ // set spacing large enough to only display single tile
+ tiles = 1;
+ spacing = areaSize;
+ }
+ else
+ {
+ // available for middle tiles
+ sint32 midSize = (areaSize - tileSize*2);
+ // number of middle tiles
+ sint32 midTiles = midSize / tileSize;
+
+ tiles = 2 + midTiles;
+ tilePos = areaPos;
+ // int div for floor()
+ spacing = ( midSize - tileSize * midTiles) / (midTiles + 1);
+ }
+ break;
+ }
+ case CSS_VALUE_ROUND:
+ // fall-thru - size is already calculated
+ case CSS_VALUE_REPEAT:
+ // fall-thru
+ default:
+ {
+ tilePos -= std::ceil(abs(tilePos - areaPos)/(float)tileSize)*tileSize;
+ tiles = std::ceil((std::abs(areaPos - tilePos) + areaSize) / (float)tileSize);
+ spacing = 0;
+ break;
+ }
+ }
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::calculateTiles(const CSSBackground &bg, sint32 &texX, sint32 &texY, sint32 &texW, sint32 &texH, sint32 &tilesX, sint32 &tilesY, sint32 &spacingX, sint32 &spacingY) const
+ {
+ sint32 areaX, areaY, areaW, areaH;
+ getPositioningArea(bg, areaX, areaY, areaW, areaH);
+
+ // texX,texY is for position area (ie content-box), but painting area can be bigger (ie border-box)
+ sint32 paintX, paintY, paintW, paintH;
+ getPaintingArea(bg, paintX, paintY, paintW, paintH);
+ if (paintX < areaX)
+ areaX -= std::ceil((areaX - paintX) / (float)texW) * texW;
+ if ((paintX + paintW) > (areaX + areaW))
+ areaW += std::ceil(((paintX + paintW) - (areaX + areaW)) / (float)texW) * texW;
+ if (paintY < areaY)
+ areaY -= std::ceil((areaY - paintY) / (float)texH) * texH;
+ if ((paintY + paintH) > (areaY + areaH))
+ areaH += std::ceil(((paintY + paintH) - (areaY + areaH)) / (float)texH) * texH;
+
+ if (texW <= 0 || texH <= 0 || areaW <= 0 || areaH <= 0)
+ {
+ tilesX = tilesY = 0;
+ spacingX = spacingY = 0;
+ return;
+ }
+
+ if (bg.repeatX == CSS_VALUE_ROUND)
+ {
+ sint numTiles = std::max(1, (sint)std::round((float)areaW / texW));
+ texW = areaW / numTiles;
+ if (bg.height.isAuto() && bg.repeatY != CSS_VALUE_ROUND)
+ {
+ float aspect = (float)areaW / (numTiles * texW);
+ texH = texW * aspect;
+ }
+ }
+
+ if (bg.repeatY == CSS_VALUE_ROUND)
+ {
+ sint numTiles = std::max(1, (sint)std::round((float)areaH / texH));
+ texH = areaH / numTiles;
+ if (bg.width.isAuto() && bg.repeatX != CSS_VALUE_ROUND)
+ {
+ float aspect = (float)areaH / (numTiles * texH);
+ texW = texH * aspect;
+ }
+ }
+
+ getImageTile(texX, texW, spacingX, tilesX, areaX, areaW, bg.repeatX);
+ getImageTile(texY, texH, spacingY, tilesY, areaY, areaH, bg.repeatY);
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::buildColorQuads(const CSSBackground &bg)
+ {
+ if (bg.color.A == 0)
+ return;
+
+ // painting area defined with background-clip
+ sint32 x, y, w, h;
+ getPaintingArea(bg, x, y, w, h);
+
+ if (w <= 0 || h <= 0)
+ return;
+
+ CViewRenderer &rVR = *CViewRenderer::getInstance();
+
+ SDrawQueue shape;
+ shape.Quad.Uv0.set(0.f, 1.f);
+ shape.Quad.Uv1.set(1.f, 1.f);
+ shape.Quad.Uv2.set(1.f, 0.f);
+ shape.Quad.Uv3.set(0.f, 0.f);
+
+ shape.Quad.V0.set(x, y, 0);
+ shape.Quad.V1.set(x+w, y, 0);
+ shape.Quad.V2.set(x+w, y+h, 0);
+ shape.Quad.V3.set(x, y+h, 0);
+
+ shape.Color = bg.color;
+ shape.TextureId = rVR.getBlankTextureId();
+
+ m_DrawQueue.push_back(shape);
+ }
+
+ // ----------------------------------------------------------------------------
+ void CSSBackgroundRenderer::buildImageQuads(const CSSBackground &bg, sint32 textureId)
+ {
+ // TODO: m_Background should have textureId and that should be "reserved" in CViewRenderer
+ // even before download is finished
+ if (textureId < 0)
+ return;
+
+ CViewRenderer &rVR = *CViewRenderer::getInstance();
+
+ sint32 texW = 0;
+ sint32 texH = 0;
+ rVR.getTextureSizeFromId(textureId, texW, texH);
+ if (texW <= 0 || texH <= 0)
+ return;
+
+ // get requested texture size
+ calculateSize(m_Background, texW, texH);
+ if(texW <= 0 || texH <= 0)
+ return;
+
+ // get texture left/top corner
+ sint32 texX, texY;
+ calculatePosition(m_Background, texX, texY, texW, texH);
+
+ sint32 tilesX, tilesY;
+ sint32 spacingX, spacingY;
+ calculateTiles(m_Background, texX, texY, texW, texH, tilesX, tilesY, spacingX, spacingY);
+
+ sint32 clipL, clipB, clipR, clipT;
+ getPaintingArea(m_Background, clipL, clipB, clipR, clipT);
+ clipR += clipL;
+ clipT += clipB;
+
+ m_DrawQueue.reserve(tilesX * tilesY + m_DrawQueue.size());
+ for(sint32 tileX = 0; tileX < tilesX; tileX++)
+ {
+ for(sint32 tileY = 0; tileY < tilesY; tileY++)
+ {
+ sint32 tileL = texX + tileX * (texW + spacingX);
+ sint32 tileB = texY + tileY * (texH + spacingY);
+ sint32 tileR = tileL + texW;
+ sint32 tileT = tileB + texH;
+
+ // tile is outside clip area
+ if (tileT <= clipB || tileB >= clipT || tileR <= clipL || tileL >= clipR)
+ continue;
+
+ CUV uv0(0,1);
+ CUV uv1(1,1);
+ CUV uv2(1,0);
+ CUV uv3(0,0);
+
+ // clip if tile not totally inside clip area
+ if (!(tileL >= clipL && tileR <= clipR && tileB >= clipB && tileT <= clipT))
+ {
+ float ratio;
+ if (tileL < clipL)
+ {
+ ratio = ((float)(clipL - tileL))/((float)(tileR - tileL));
+ tileL = clipL;
+ uv0.U += ratio*(uv1.U-uv0.U);
+ uv3.U += ratio*(uv2.U-uv3.U);
+ }
+
+ if (tileB < clipB)
+ {
+ ratio = ((float)(clipB - tileB))/((float)(tileT - tileB));
+ tileB = clipB;
+ uv0.V += ratio*(uv3.V-uv0.V);
+ uv1.V += ratio*(uv2.V-uv1.V);
+ }
+
+ if (tileR > clipR)
+ {
+ ratio = ((float)(clipR - tileR))/((float)(tileL - tileR));
+ tileR = clipR;
+ uv2.U += ratio*(uv3.U-uv2.U);
+ uv1.U += ratio*(uv0.U-uv1.U);
+ }
+
+ if (tileT > clipT)
+ {
+ ratio = ((float)(clipT - tileT))/((float)(tileB - tileT));
+ tileT = clipT;
+ uv2.V += ratio*(uv1.V-uv2.V);
+ uv3.V += ratio*(uv0.V-uv3.V);
+ }
+ }
+
+ SDrawQueue shape;
+ shape.Quad.Uv0 = uv0;
+ shape.Quad.Uv1 = uv1;
+ shape.Quad.Uv2 = uv2;
+ shape.Quad.Uv3 = uv3;
+
+ shape.Color = CRGBA::White;
+ shape.TextureId = textureId;
+
+ shape.Quad.V0.set(tileL, tileB, 0);
+ shape.Quad.V1.set(tileR, tileB, 0);
+ shape.Quad.V2.set(tileR, tileT, 0);
+ shape.Quad.V3.set(tileL, tileT, 0);
+
+ m_DrawQueue.push_back(shape);
+ }
+ }
+ }
+
+}//namespace
+
diff --git a/nel/src/gui/css_length.cpp b/nel/src/gui/css_length.cpp
new file mode 100644
index 000000000..fde47b644
--- /dev/null
+++ b/nel/src/gui/css_length.cpp
@@ -0,0 +1,222 @@
+// Ryzom - MMORPG Framework
+// Copyright (C) 2010-2021 Winch Gate Property Limited
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+#include "stdpch.h"
+
+#include
+#include "nel/gui/css_length.h"
+
+using namespace NLMISC;
+
+#ifdef DEBUG_NEW
+#define new DEBUG_NEW
+#endif
+
+namespace NLGUI
+{
+
+bool CSSLength::parseValue(const std::string &value, bool allowPercent, bool allowNegative)
+{
+ static const std::set knownUnits = {
+ "%", "rem", "em", "px", "pt", "vw", "vh", "vi", "vb", "vmin", "vmax"
+ };
+
+ std::string::size_type pos = 0;
+ std::string::size_type len = value.size();
+ if (len == 0)
+ return false;
+
+ if (len == 1 && value[0] == '0')
+ {
+ m_Value = 0;
+ m_Kind = Auto;
+ return true;
+ }
+
+ // +100px; -100px
+ if (value[0] == '+')
+ pos++;
+ else if (allowNegative && value[0] == '-')
+ pos++;
+
+ while(pos < len)
+ {
+ bool isNumeric = (value[pos] >= '0' && value[pos] <= '9')
+ || (pos == 0 && value[pos] == '.')
+ || (pos > 0 && value[pos] == '.' && value[pos-1] >= '0' && value[pos-1] <= '9');
+
+ if (!isNumeric)
+ break;
+
+ pos++;
+ }
+
+ std::string unit = toLowerAscii(value.substr(pos));
+ if (knownUnits.count(unit))
+ {
+ std::string tmpstr = value.substr(0, pos);
+ return fromString(tmpstr, m_Value);
+ }
+
+ return false;
+}
+
+float CSSLength::getValue() const
+{
+ if (m_Unit == CSS_UNIT_PERCENT)
+ return m_Value / 100.f;
+
+ return m_Value;
+}
+void CSSLength::setFloatValue(float f, const std::string &unit)
+{
+ m_Value = f;
+ setUnit(unit);
+}
+
+void CSSLength::setUnit(const std::string &unit)
+{
+ if (unit.empty())
+ {
+ m_Unit = CSS_UNIT_NONE;
+ m_Kind = Fixed;
+ }
+ else if (unit == "px")
+ {
+ m_Unit = CSS_UNIT_PX;
+ m_Kind = Fixed;
+ } else if (unit == "pt")
+ {
+ m_Unit = CSS_UNIT_PT;
+ m_Kind = Fixed;
+ } else if (unit == "%")
+ {
+ m_Unit = CSS_UNIT_PERCENT;
+ m_Kind = Relative;
+ } else if (unit == "em")
+ {
+ m_Unit = CSS_UNIT_EM;
+ m_Kind = Relative;
+ } else if (unit == "rem")
+ {
+ m_Unit = CSS_UNIT_REM;
+ m_Kind = Relative;
+ } else if (unit == "vw")
+ {
+ m_Unit = CSS_UNIT_VW;
+ m_Kind = Relative;
+ } else if (unit == "vh")
+ {
+ m_Unit = CSS_UNIT_VH;
+ m_Kind = Relative;
+ } else if (unit == "vi")
+ {
+ m_Unit = CSS_UNIT_VI;
+ m_Kind = Relative;
+ } else if (unit == "vb")
+ {
+ m_Unit = CSS_UNIT_VB;
+ m_Kind = Relative;
+ } else if (unit == "vmin")
+ {
+ m_Unit = CSS_UNIT_VMIN;
+ m_Kind = Relative;
+ } else if (unit == "vmax")
+ {
+ m_Unit = CSS_UNIT_VMAX;
+ m_Kind = Relative;
+ } else if (unit == "auto")
+ {
+ m_Unit = CSS_UNIT_NONE;
+ m_Kind = Auto;
+ } else
+ {
+ // fallback to auto
+ m_Unit = CSS_UNIT_NONE;
+ m_Kind = Auto;
+ }
+}
+
+float CSSLength::calculate(uint32 relValue, uint32 emSize, uint32 remSize, uint32 vwSize, uint32 vhSize = 0) const
+{
+ float value = getValue();
+ switch(m_Unit)
+ {
+ case CSS_UNIT_EM:
+ return emSize * value;
+ case CSS_UNIT_REM:
+ return remSize * value;
+ case CSS_UNIT_PERCENT:
+ return relValue * value;
+ case CSS_UNIT_PX:
+ case CSS_UNIT_PT:
+ return value;
+ case CSS_UNIT_VW:
+ case CSS_UNIT_VI:
+ // Vi for horizontal writing mode only
+ return (float)vwSize*0.01f;
+ case CSS_UNIT_VH:
+ case CSS_UNIT_VB:
+ // Vb for horizontal writing mode only
+ return (float)vhSize*0.01f;
+ case CSS_UNIT_VMIN:
+ return (float)std::min(vwSize, vhSize)*0.01f;
+ case CSS_UNIT_VMAX:
+ return (float)std::max(vwSize, vhSize)*0.01f;
+ }
+
+ nldebug("Unknown CSS unit '%s'", toString().c_str());
+ return value;
+}
+
+std::string CSSLength::toString() const
+{
+ if (m_Kind == Auto)
+ return "auto";
+
+ std::string ret;
+ ret += NLMISC::toString("%f", m_Value);
+
+ size_t pos = ret.find(".");
+ for( ; pos < ret.size(); ++pos)
+ {
+ if (ret[pos] != '0')
+ break;
+ }
+ if (pos == ret.size())
+ ret = ret.substr(0, ret.find("."));
+
+ switch(m_Unit)
+ {
+ case CSS_UNIT_NONE: break;
+ case CSS_UNIT_EM: ret += "em"; break;
+ case CSS_UNIT_REM: ret += "rem"; break;
+ case CSS_UNIT_PERCENT: ret += "%"; break;
+ case CSS_UNIT_PX: ret += "px"; break;
+ case CSS_UNIT_PT: ret += "pt"; break;
+ case CSS_UNIT_VW: ret += "vw"; break;
+ case CSS_UNIT_VH: ret += "vh"; break;
+ case CSS_UNIT_VI: ret += "vi"; break;
+ case CSS_UNIT_VB: ret += "vb"; break;
+ case CSS_UNIT_VMIN: ret += "vmin"; break;
+ case CSS_UNIT_VMAX: ret += "vmax"; break;
+ }
+
+ return ret;
+}
+
+} // namespace
+