Просмотр исходного кода

Add geometry utility for creating background geometry on any target area with border radius support

Michael Ragazzon 2 лет назад
Родитель
Сommit
3429285e39

+ 2 - 0
Include/RmlUi/Core/ComputedValues.h

@@ -293,6 +293,8 @@ namespace Style {
 		float             border_top_right_radius()    const { return (float)rare.border_top_right_radius; }
 		float             border_top_right_radius()    const { return (float)rare.border_top_right_radius; }
 		float             border_bottom_right_radius() const { return (float)rare.border_bottom_right_radius; }
 		float             border_bottom_right_radius() const { return (float)rare.border_bottom_right_radius; }
 		float             border_bottom_left_radius()  const { return (float)rare.border_bottom_left_radius; }
 		float             border_bottom_left_radius()  const { return (float)rare.border_bottom_left_radius; }
+		Vector4f          border_radius()              const { return {(float)rare.border_top_left_radius,     (float)rare.border_top_right_radius,
+		                                                               (float)rare.border_bottom_right_radius, (float)rare.border_bottom_left_radius}; }
 		Clip              clip()                       const { return rare.clip; }
 		Clip              clip()                       const { return rare.clip; }
 		Drag              drag()                       const { return rare.drag; }
 		Drag              drag()                       const { return rare.drag; }
 		TabIndex          tab_index()                  const { return rare.tab_index; }
 		TabIndex          tab_index()                  const { return rare.tab_index; }

+ 15 - 4
Include/RmlUi/Core/GeometryUtilities.h

@@ -74,16 +74,27 @@ public:
 	/// @param[in] color The color to draw the line in.
 	/// @param[in] color The color to draw the line in.
 	static void GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, Colourb color);
 	static void GenerateLine(Geometry* geometry, Vector2f position, Vector2f size, Colourb color);
 
 
-	/// Generates a geometry in the same way as element backgrounds and borders are generated, with support for the border-radius property.
-	/// Vertex positions are relative to the border-box, vertex texture coordinates are default initialized.
+	/// Generates the geometry for an element's background and border, with support for the border-radius property.
 	/// @param[out] geometry The geometry to append the newly created vertices and indices into.
 	/// @param[out] geometry The geometry to append the newly created vertices and indices into.
 	/// @param[in] box The box which determines the background and border geometry.
 	/// @param[in] box The box which determines the background and border geometry.
 	/// @param[in] offset Offset the position of the generated vertices.
 	/// @param[in] offset Offset the position of the generated vertices.
 	/// @param[in] border_radius The border radius in pixel units in the following order: top-left, top-right, bottom-right, bottom-left.
 	/// @param[in] border_radius The border radius in pixel units in the following order: top-left, top-right, bottom-right, bottom-left.
 	/// @param[in] background_colour The colour applied to the background, set alpha to zero to not generate the background.
 	/// @param[in] background_colour The colour applied to the background, set alpha to zero to not generate the background.
-	/// @param[in] border_colours Pointer to a four-element array of border colors in top-right-bottom-left order, or nullptr to not generate borders.
+	/// @param[in] border_colours A four-element array of border colors in top-right-bottom-left order.
+	/// @note Vertex positions are relative to the border-box, vertex texture coordinates are default initialized.
 	static void GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb background_colour,
 	static void GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb background_colour,
-		const Colourb* border_colours = nullptr);
+		const Colourb border_colours[4]);
+
+	/// Generates the background geometry for an element's area, with support for border-radius.
+	/// @param[out] geometry The geometry to append the newly created vertices and indices into.
+	/// @param[in] box The box which determines the background geometry.
+	/// @param[in] offset Offset the position of the generated vertices.
+	/// @param[in] border_radius The border radius in pixel units in the following order: top-left, top-right, bottom-right, bottom-left.
+	/// @param[in] colour The colour applied to the background.
+	/// @param[in] area Either the border, padding or content area to be filled.
+	/// @note Vertex positions are relative to the border-box, vertex texture coordinates are default initialized.
+	static void GenerateBackground(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb colour,
+		BoxArea area = BoxArea::Padding);
 
 
 private:
 private:
 	GeometryUtilities();
 	GeometryUtilities();

+ 1 - 7
Source/Core/DecoratorGradient.cpp

@@ -67,13 +67,7 @@ DecoratorDataHandle DecoratorGradient::GenerateElementData(Element* element) con
 	const ComputedValues& computed = element->GetComputedValues();
 	const ComputedValues& computed = element->GetComputedValues();
 	const float opacity = computed.opacity();
 	const float opacity = computed.opacity();
 
 
-	const Vector4f border_radius{
-		computed.border_top_left_radius(),
-		computed.border_top_right_radius(),
-		computed.border_bottom_right_radius(),
-		computed.border_bottom_left_radius(),
-	};
-	GeometryUtilities::GenerateBackgroundBorder(geometry, element->GetBox(), Vector2f(0), border_radius, Colourb());
+	GeometryUtilities::GenerateBackground(geometry, element->GetBox(), Vector2f(0), computed.border_radius(), Colourb());
 
 
 	// Apply opacity
 	// Apply opacity
 	Colourb colour_start = start;
 	Colourb colour_start = start;

+ 2 - 4
Source/Core/ElementBackgroundBorder.cpp

@@ -71,6 +71,7 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element)
 		computed.border_bottom_color(),
 		computed.border_bottom_color(),
 		computed.border_left_color(),
 		computed.border_left_color(),
 	};
 	};
+	const Vector4f border_radius = computed.border_radius();
 
 
 	// Apply opacity
 	// Apply opacity
 	const float opacity = computed.opacity();
 	const float opacity = computed.opacity();
@@ -85,14 +86,11 @@ void ElementBackgroundBorder::GenerateGeometry(Element* element)
 	geometry.GetVertices().clear();
 	geometry.GetVertices().clear();
 	geometry.GetIndices().clear();
 	geometry.GetIndices().clear();
 
 
-	const Vector4f radii(computed.border_top_left_radius(), computed.border_top_right_radius(), computed.border_bottom_right_radius(),
-		computed.border_bottom_left_radius());
-
 	for (int i = 0; i < element->GetNumBoxes(); i++)
 	for (int i = 0; i < element->GetNumBoxes(); i++)
 	{
 	{
 		Vector2f offset;
 		Vector2f offset;
 		const Box& box = element->GetBox(i, offset);
 		const Box& box = element->GetBox(i, offset);
-		GeometryUtilities::GenerateBackgroundBorder(&geometry, box, offset, radii, background_color, border_colors);
+		GeometryUtilities::GenerateBackgroundBorder(&geometry, box, offset, border_radius, background_color, border_colors);
 	}
 	}
 
 
 	geometry.Release();
 	geometry.Release();

+ 74 - 138
Source/Core/GeometryBackgroundBorder.cpp

@@ -36,185 +36,121 @@ namespace Rml {
 
 
 GeometryBackgroundBorder::GeometryBackgroundBorder(Vector<Vertex>& vertices, Vector<int>& indices) : vertices(vertices), indices(indices) {}
 GeometryBackgroundBorder::GeometryBackgroundBorder(Vector<Vertex>& vertices, Vector<int>& indices) : vertices(vertices), indices(indices) {}
 
 
-void GeometryBackgroundBorder::Draw(Vector<Vertex>& vertices, Vector<int>& indices, CornerSizes radii, const Box& box, const Vector2f offset,
-	const Colourb background_color, const Colourb* border_colors)
+BorderMetrics GeometryBackgroundBorder::ComputeBorderMetrics(Vector2f outer_position, EdgeSizes edge_sizes, Vector2f inner_size,
+	Vector4f outer_radii_def)
 {
 {
-	EdgeSizes border_widths = {
-		Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Top)),
-		Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Right)),
-		Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Bottom)),
-		Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Left)),
-	};
-
-	int num_borders = 0;
-
-	if (border_colors)
-	{
-		for (int i = 0; i < 4; i++)
-			if (border_colors[i].alpha > 0 && border_widths[i] > 0)
-				num_borders += 1;
-	}
-
-	const Vector2f padding_size = box.GetSize(BoxArea::Padding).Round();
-
-	const bool has_background = (background_color.alpha > 0 && padding_size.x > 0 && padding_size.y > 0);
-	const bool has_border = (num_borders > 0);
-
-	if (!has_background && !has_border)
-		return;
+	BorderMetrics metrics = {};
 
 
 	// -- Find the corner positions --
 	// -- Find the corner positions --
 
 
-	const Vector2f border_position = offset.Round();
-	const Vector2f padding_position = border_position + Vector2f(border_widths[Edge::LEFT], border_widths[Edge::TOP]);
-	const Vector2f border_size =
-		padding_size + Vector2f(border_widths[Edge::LEFT] + border_widths[Edge::RIGHT], border_widths[Edge::TOP] + border_widths[Edge::BOTTOM]);
-
-	// Border edge positions
-	CornerPositions positions_outer = {
-		border_position,
-		border_position + Vector2f(border_size.x, 0),
-		border_position + border_size,
-		border_position + Vector2f(0, border_size.y),
+	const Vector2f inner_position = outer_position + Vector2f(edge_sizes[LEFT], edge_sizes[TOP]);
+	const Vector2f outer_size = inner_size + Vector2f(edge_sizes[LEFT] + edge_sizes[RIGHT], edge_sizes[TOP] + edge_sizes[BOTTOM]);
+
+	metrics.positions_outer = {
+		outer_position,
+		outer_position + Vector2f(outer_size.x, 0),
+		outer_position + outer_size,
+		outer_position + Vector2f(0, outer_size.y),
 	};
 	};
 
 
-	// Padding edge positions
-	CornerPositions positions_inner = {
-		padding_position,
-		padding_position + Vector2f(padding_size.x, 0),
-		padding_position + padding_size,
-		padding_position + Vector2f(0, padding_size.y),
+	metrics.positions_inner = {
+		inner_position,
+		inner_position + Vector2f(inner_size.x, 0),
+		inner_position + inner_size,
+		inner_position + Vector2f(0, inner_size.y),
 	};
 	};
 
 
 	// -- For curved borders, find the positions to draw ellipses around, and the scaled outer and inner radii --
 	// -- For curved borders, find the positions to draw ellipses around, and the scaled outer and inner radii --
 
 
-	const float sum_radius = (radii[TOP_LEFT] + radii[TOP_RIGHT] + radii[BOTTOM_RIGHT] + radii[BOTTOM_LEFT]);
+	const float sum_radius = (outer_radii_def[TOP_LEFT] + outer_radii_def[TOP_RIGHT] + outer_radii_def[BOTTOM_RIGHT] + outer_radii_def[BOTTOM_LEFT]);
 	const bool has_radius = (sum_radius > 1.f);
 	const bool has_radius = (sum_radius > 1.f);
 
 
-	// Curved borders are drawn as circles (outer border) and ellipses (inner border) around the centers.
-	CornerPositions positions_circle_center;
-
-	// Radii of the padding edges, 2-dimensional as these can be ellipses.
-	// The inner radii is effectively the (signed) distance from the circle center to the padding edge.
-	// They can also be zero or negative, in which case a sharp corner should be drawn instead of an arc.
-	CornerSizes2 inner_radii;
-
 	if (has_radius)
 	if (has_radius)
 	{
 	{
+		auto& outer_radii = metrics.outer_radii;
+		outer_radii = {outer_radii_def.x, outer_radii_def.y, outer_radii_def.z, outer_radii_def.w};
+
 		// Scale the radii such that we have no overlapping curves.
 		// Scale the radii such that we have no overlapping curves.
 		float scale_factor = FLT_MAX;
 		float scale_factor = FLT_MAX;
-		scale_factor = Math::Min(scale_factor, padding_size.x / (radii[TOP_LEFT] + radii[TOP_RIGHT]));       // Top
-		scale_factor = Math::Min(scale_factor, padding_size.y / (radii[TOP_RIGHT] + radii[BOTTOM_RIGHT]));   // Right
-		scale_factor = Math::Min(scale_factor, padding_size.x / (radii[BOTTOM_RIGHT] + radii[BOTTOM_LEFT])); // Bottom
-		scale_factor = Math::Min(scale_factor, padding_size.y / (radii[BOTTOM_LEFT] + radii[TOP_LEFT]));     // Left
-
+		scale_factor = Math::Min(scale_factor, inner_size.x / (outer_radii[TOP_LEFT] + outer_radii[TOP_RIGHT]));       // Top
+		scale_factor = Math::Min(scale_factor, inner_size.y / (outer_radii[TOP_RIGHT] + outer_radii[BOTTOM_RIGHT]));   // Right
+		scale_factor = Math::Min(scale_factor, inner_size.x / (outer_radii[BOTTOM_RIGHT] + outer_radii[BOTTOM_LEFT])); // Bottom
+		scale_factor = Math::Min(scale_factor, inner_size.y / (outer_radii[BOTTOM_LEFT] + outer_radii[TOP_LEFT]));     // Left
 		scale_factor = Math::Min(1.0f, scale_factor);
 		scale_factor = Math::Min(1.0f, scale_factor);
 
 
-		for (float& radius : radii)
+		for (float& radius : outer_radii)
 			radius = Math::Round(radius * scale_factor);
 			radius = Math::Round(radius * scale_factor);
 
 
 		// Place the circle/ellipse centers
 		// Place the circle/ellipse centers
-		positions_circle_center = {
-			positions_outer[TOP_LEFT] + Vector2f(1, 1) * radii[TOP_LEFT],
-			positions_outer[TOP_RIGHT] + Vector2f(-1, 1) * radii[TOP_RIGHT],
-			positions_outer[BOTTOM_RIGHT] + Vector2f(-1, -1) * radii[BOTTOM_RIGHT],
-			positions_outer[BOTTOM_LEFT] + Vector2f(1, -1) * radii[BOTTOM_LEFT],
+		metrics.positions_circle_center = {
+			metrics.positions_outer[TOP_LEFT] + Vector2f(1, 1) * outer_radii[TOP_LEFT],
+			metrics.positions_outer[TOP_RIGHT] + Vector2f(-1, 1) * outer_radii[TOP_RIGHT],
+			metrics.positions_outer[BOTTOM_RIGHT] + Vector2f(-1, -1) * outer_radii[BOTTOM_RIGHT],
+			metrics.positions_outer[BOTTOM_LEFT] + Vector2f(1, -1) * outer_radii[BOTTOM_LEFT],
 		};
 		};
 
 
-		inner_radii = {
-			Vector2f(radii[TOP_LEFT]) - Vector2f(border_widths[Edge::LEFT], border_widths[Edge::TOP]),
-			Vector2f(radii[TOP_RIGHT]) - Vector2f(border_widths[Edge::RIGHT], border_widths[Edge::TOP]),
-			Vector2f(radii[BOTTOM_RIGHT]) - Vector2f(border_widths[Edge::RIGHT], border_widths[Edge::BOTTOM]),
-			Vector2f(radii[BOTTOM_LEFT]) - Vector2f(border_widths[Edge::LEFT], border_widths[Edge::BOTTOM]),
+		metrics.inner_radii = {
+			Vector2f(outer_radii[TOP_LEFT]) - Vector2f(edge_sizes[LEFT], edge_sizes[TOP]),
+			Vector2f(outer_radii[TOP_RIGHT]) - Vector2f(edge_sizes[RIGHT], edge_sizes[TOP]),
+			Vector2f(outer_radii[BOTTOM_RIGHT]) - Vector2f(edge_sizes[RIGHT], edge_sizes[BOTTOM]),
+			Vector2f(outer_radii[BOTTOM_LEFT]) - Vector2f(edge_sizes[LEFT], edge_sizes[BOTTOM]),
 		};
 		};
 	}
 	}
 
 
-	// -- Generate the geometry --
-
-	GeometryBackgroundBorder geometry(vertices, indices);
+	return metrics;
+}
 
 
-	{
-		// Reserve geometry. A conservative estimate, does not take border-radii into account and assumes same-colored borders.
-		const int estimated_num_vertices = 4 * int(has_background) + 2 * num_borders;
-		const int estimated_num_triangles = 2 * int(has_background) + 2 * num_borders;
+void GeometryBackgroundBorder::DrawBackground(const BorderMetrics& metrics, Colourb color)
+{
+	const int offset_vertices = (int)vertices.size();
 
 
-		vertices.reserve((int)vertices.size() + estimated_num_vertices);
-		indices.reserve((int)indices.size() + 3 * estimated_num_triangles);
-	}
+	for (int corner = 0; corner < 4; corner++)
+		DrawBackgroundCorner(Corner(corner), metrics.positions_inner[corner], metrics.positions_circle_center[corner], metrics.outer_radii[corner],
+			metrics.inner_radii[corner], color);
 
 
-	// Draw the background
-	if (has_background)
-	{
-		const int offset_vertices = (int)vertices.size();
+	FillBackground(offset_vertices);
+}
 
 
-		for (int corner = 0; corner < 4; corner++)
-			geometry.DrawBackgroundCorner(Corner(corner), positions_inner[corner], positions_circle_center[corner], radii[corner],
-				inner_radii[corner], background_color);
+void GeometryBackgroundBorder::DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const Colourb border_colors[4])
+{
+	RMLUI_ASSERT(border_colors);
 
 
-		geometry.FillBackground(offset_vertices);
-	}
+	const int offset_vertices = (int)vertices.size();
 
 
-	// Draw the border
-	if (has_border)
-	{
-		const int offset_vertices = (int)vertices.size();
+	const bool draw_edge[4] = {
+		edge_sizes[TOP] > 0 && border_colors[TOP].alpha > 0,
+		edge_sizes[RIGHT] > 0 && border_colors[RIGHT].alpha > 0,
+		edge_sizes[BOTTOM] > 0 && border_colors[BOTTOM].alpha > 0,
+		edge_sizes[LEFT] > 0 && border_colors[LEFT].alpha > 0,
+	};
 
 
-		const bool draw_edge[4] = {
-			border_widths[Edge::TOP] > 0 && border_colors[Edge::TOP].alpha > 0,
-			border_widths[Edge::RIGHT] > 0 && border_colors[Edge::RIGHT].alpha > 0,
-			border_widths[Edge::BOTTOM] > 0 && border_colors[Edge::BOTTOM].alpha > 0,
-			border_widths[Edge::LEFT] > 0 && border_colors[Edge::LEFT].alpha > 0,
-		};
+	const bool draw_corner[4] = {
+		draw_edge[TOP] || draw_edge[LEFT],
+		draw_edge[TOP] || draw_edge[RIGHT],
+		draw_edge[BOTTOM] || draw_edge[RIGHT],
+		draw_edge[BOTTOM] || draw_edge[LEFT],
+	};
 
 
-		const bool draw_corner[4] = {
-			draw_edge[Edge::TOP] || draw_edge[Edge::LEFT],
-			draw_edge[Edge::TOP] || draw_edge[Edge::RIGHT],
-			draw_edge[Edge::BOTTOM] || draw_edge[Edge::RIGHT],
-			draw_edge[Edge::BOTTOM] || draw_edge[Edge::LEFT],
-		};
+	for (int corner = 0; corner < 4; corner++)
+	{
+		const Edge edge0 = Edge((corner + 3) % 4);
+		const Edge edge1 = Edge(corner);
 
 
-		for (int corner = 0; corner < 4; corner++)
+		if (draw_corner[corner])
 		{
 		{
-			const Edge edge0 = Edge((corner + 3) % 4);
-			const Edge edge1 = Edge(corner);
-
-			if (draw_corner[corner])
-				geometry.DrawBorderCorner(Corner(corner), positions_outer[corner], positions_inner[corner], positions_circle_center[corner],
-					radii[corner], inner_radii[corner], border_colors[edge0], border_colors[edge1]);
-
-			if (draw_edge[edge1])
-			{
-				RMLUI_ASSERTMSG(draw_corner[corner] && draw_corner[(corner + 1) % 4],
-					"Border edges can only be drawn if both of its connected corners are drawn.");
-				geometry.FillEdge(edge1 == Edge::LEFT ? offset_vertices : (int)vertices.size());
-			}
+			DrawBorderCorner(Corner(corner), metrics.positions_outer[corner], metrics.positions_inner[corner],
+				metrics.positions_circle_center[corner], metrics.outer_radii[corner], metrics.inner_radii[corner], border_colors[edge0],
+				border_colors[edge1]);
 		}
 		}
-	}
-
-#if 0
-	// Debug draw vertices
-	if (has_radius)
-	{
-		const int num_vertices = vertices.size();
-		const int num_indices = indices.size();
-
-		vertices.resize(num_vertices + 4 * num_vertices);
-		indices.resize(num_indices + 6 * num_indices);
 
 
-		for (int i = 0; i < num_vertices; i++)
+		if (draw_edge[edge1])
 		{
 		{
-			GeometryUtilities::GenerateQuad(vertices.data() + num_vertices + 4 * i, indices.data() + num_indices + 6 * i, vertices[i].position, Vector2f(3, 3), Colourb(255, 0, (i % 2) == 0 ? 0 : 255), num_vertices + 4 * i);
-		}
-	}
-#endif
+			RMLUI_ASSERTMSG(draw_corner[corner] && draw_corner[(corner + 1) % 4],
+				"Border edges can only be drawn if both of its connected corners are drawn.");
 
 
-#ifdef RMLUI_DEBUG
-	const int num_vertices = (int)vertices.size();
-	for (int index : indices)
-	{
-		RMLUI_ASSERT(index < num_vertices);
+			FillEdge(edge1 == LEFT ? offset_vertices : (int)vertices.size());
+		}
 	}
 	}
-#endif
 }
 }
 
 
 void GeometryBackgroundBorder::DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, Colourb color)
 void GeometryBackgroundBorder::DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, Colourb color)

+ 33 - 12
Source/Core/GeometryBackgroundBorder.h

@@ -44,25 +44,46 @@ using CornerSizes = Array<float, 4>;
 using CornerSizes2 = Array<Vector2f, 4>;
 using CornerSizes2 = Array<Vector2f, 4>;
 using CornerPositions = Array<Vector2f, 4>;
 using CornerPositions = Array<Vector2f, 4>;
 
 
+// The background-border metrics specify an inner and an outer rectangular area, whose corners can be rounded.
+struct BorderMetrics {
+	// Outer corner positions (e.g. at border edge)
+	CornerPositions positions_outer;
+	// Inner corner positions (e.g. at padding edge)
+	CornerPositions positions_inner;
+	// Curved borders are drawn as circles (outer border) and ellipses (inner border) around the centers.
+	CornerPositions positions_circle_center;
+
+	// Radii of the outer edges, always circles.
+	CornerSizes outer_radii;
+	// Radii of the inner edges, 2-dimensional as these can be elliptic.
+	// The inner radii is effectively the (signed) distance from the circle center to the inner edge.
+	// They can also be zero or negative, in which case a sharp corner should be drawn instead of an arc.
+	CornerSizes2 inner_radii;
+};
+
 class GeometryBackgroundBorder {
 class GeometryBackgroundBorder {
 public:
 public:
-	/// Generate geometry for background and borders.
-	/// @param[out] vertices Destination vector for generated vertices.
-	/// @param[out] indices Destination vector for generated indices.
-	/// @param[in] radii The radius of each corner.
-	/// @param[in] box The box used for positioning and sizing of the background and borders.
-	/// @param[in] offset Offset the position of the generated vertices.
-	/// @param[in] background_color Color of the background, set alpha to zero to not generate a background.
-	/// @param[in] border_colors Pointer to a four-element array of border colors in top-right-bottom-left order, or nullptr to not generate borders.
-	static void Draw(Vector<Vertex>& vertices, Vector<int>& indices, CornerSizes radii, const Box& box, Vector2f offset, Colourb background_color,
-		const Colourb* border_colors);
+	// Construct the background-border geometry drawer, passing in the target vertex and index lists to be filled by later draw operations.
+	GeometryBackgroundBorder(Vector<Vertex>& vertices, Vector<int>& indices);
+
+	/// Compute background-border metrics used by later draw operations.
+	/// @param outer_position Top-left position of the outer edge.
+	/// @param edge_sizes Widths of the border.
+	/// @param inner_size Size of the inner area.
+	/// @param outer_radii The radius of the outer edge at each corner.
+	/// @return The computed metrics.
+	static BorderMetrics ComputeBorderMetrics(Vector2f outer_position, EdgeSizes edge_sizes, Vector2f inner_size, Vector4f outer_radii);
+
+	// Generate geometry for the background, defined by the inner area of the border metrics.
+	void DrawBackground(const BorderMetrics& metrics, Colourb color);
+
+	/// Generate geometry for the border, defined by the intersection of the outer and inner areas of the border metrics.
+	void DrawBorder(const BorderMetrics& metrics, EdgeSizes edge_sizes, const Colourb border_colors[4]);
 
 
 private:
 private:
 	enum Edge { TOP, RIGHT, BOTTOM, LEFT };
 	enum Edge { TOP, RIGHT, BOTTOM, LEFT };
 	enum Corner { TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT };
 	enum Corner { TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT };
 
 
-	GeometryBackgroundBorder(Vector<Vertex>& vertices, Vector<int>& indices);
-
 	// -- Background --
 	// -- Background --
 	// All draw operations place vertices in clockwise order.
 	// All draw operations place vertices in clockwise order.
 
 

+ 106 - 6
Source/Core/GeometryUtilities.cpp

@@ -27,6 +27,7 @@
  */
  */
 
 
 #include "../../Include/RmlUi/Core/GeometryUtilities.h"
 #include "../../Include/RmlUi/Core/GeometryUtilities.h"
+#include "../../Include/RmlUi/Core/Box.h"
 #include "../../Include/RmlUi/Core/Core.h"
 #include "../../Include/RmlUi/Core/Core.h"
 #include "../../Include/RmlUi/Core/FontEngineInterface.h"
 #include "../../Include/RmlUi/Core/FontEngineInterface.h"
 #include "../../Include/RmlUi/Core/Geometry.h"
 #include "../../Include/RmlUi/Core/Geometry.h"
@@ -88,14 +89,113 @@ void GeometryUtilities::GenerateLine(Geometry* geometry, Vector2f position, Vect
 	GeometryUtilities::GenerateQuad(line_vertices.data() + vertices_i0, line_indices.data() + indices_i0, position, size, color, vertices_i0);
 	GeometryUtilities::GenerateQuad(line_vertices.data() + vertices_i0, line_indices.data() + indices_i0, position, size, color, vertices_i0);
 }
 }
 
 
-void GeometryUtilities::GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector2f offset, Vector4f border_radius,
-	Colourb background_colour, const Colourb* border_colours)
+void GeometryUtilities::GenerateBackgroundBorder(Geometry* out_geometry, const Box& box, Vector2f offset, Vector4f border_radius,
+	Colourb background_color, const Colourb border_colors[4])
 {
 {
-	Vector<Vertex>& vertices = geometry->GetVertices();
-	Vector<int>& indices = geometry->GetIndices();
+	RMLUI_ASSERT(border_colors);
+
+	Vector<Vertex>& vertices = out_geometry->GetVertices();
+	Vector<int>& indices = out_geometry->GetIndices();
+
+	EdgeSizes border_widths = {
+		// TODO: Move rounding to computed values (round border only).
+		Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Top)),
+		Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Right)),
+		Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Bottom)),
+		Math::Round(box.GetEdge(BoxArea::Border, BoxEdge::Left)),
+	};
+
+	int num_borders = 0;
+
+	for (int i = 0; i < 4; i++)
+		if (border_colors[i].alpha > 0 && border_widths[i] > 0)
+			num_borders += 1;
+
+	const Vector2f padding_size = box.GetSize(BoxArea::Padding).Round();
+
+	const bool has_background = (background_color.alpha > 0 && padding_size.x > 0 && padding_size.y > 0);
+	const bool has_border = (num_borders > 0);
+
+	if (!has_background && !has_border)
+		return;
+
+	// Reserve geometry. A conservative estimate, does not take border-radii into account and assumes same-colored borders.
+	const int estimated_num_vertices = 4 * int(has_background) + 2 * num_borders;
+	const int estimated_num_triangles = 2 * int(has_background) + 2 * num_borders;
+	vertices.reserve((int)vertices.size() + estimated_num_vertices);
+	indices.reserve((int)indices.size() + 3 * estimated_num_triangles);
+
+	// Generate the geometry.
+	GeometryBackgroundBorder geometry(vertices, indices);
+	const BorderMetrics metrics = GeometryBackgroundBorder::ComputeBorderMetrics(offset.Round(), border_widths, padding_size, border_radius);
+
+	if (has_background)
+		geometry.DrawBackground(metrics, background_color);
+
+	if (has_border)
+		geometry.DrawBorder(metrics, border_widths, border_colors);
+
+#if 0
+	// Debug draw vertices
+	if (border_radius != Vector4f(0))
+	{
+		const int num_vertices = (int)vertices.size();
+		const int num_indices = (int)indices.size();
+
+		vertices.resize(num_vertices + 4 * num_vertices);
+		indices.resize(num_indices + 6 * num_indices);
+
+		for (int i = 0; i < num_vertices; i++)
+		{
+			GeometryUtilities::GenerateQuad(vertices.data() + num_vertices + 4 * i, indices.data() + num_indices + 6 * i, vertices[i].position,
+				Vector2f(3, 3), Colourb(255, 0, (i % 2) == 0 ? 0 : 255), num_vertices + 4 * i);
+		}
+	}
+#endif
+
+#ifdef RMLUI_DEBUG
+	const int num_vertices = (int)vertices.size();
+	for (int index : indices)
+	{
+		RMLUI_ASSERT(index < num_vertices);
+	}
+#endif
+}
+
+void GeometryUtilities::GenerateBackground(Geometry* out_geometry, const Box& box, Vector2f offset, Vector4f border_radius, Colourb color,
+	BoxArea fill_area)
+{
+	RMLUI_ASSERTMSG(fill_area >= BoxArea::Border && fill_area <= BoxArea::Content,
+		"Rectangle geometry only supports border, padding and content boxes.");
+
+	EdgeSizes edge_sizes = {};
+	for (int area = (int)BoxArea::Border; area < (int)fill_area; area++)
+	{
+		// TODO: Move rounding to computed values (round border only).
+		edge_sizes[0] += Math::Round(box.GetEdge(BoxArea(area), BoxEdge::Top));
+		edge_sizes[1] += Math::Round(box.GetEdge(BoxArea(area), BoxEdge::Right));
+		edge_sizes[2] += Math::Round(box.GetEdge(BoxArea(area), BoxEdge::Bottom));
+		edge_sizes[3] += Math::Round(box.GetEdge(BoxArea(area), BoxEdge::Left));
+	}
+
+	const Vector2f inner_size = box.GetSize(fill_area).Round();
+
+	const bool has_background = (color.alpha > 0 && inner_size.x > 0 && inner_size.y > 0);
+	if (!has_background)
+		return;
+
+	const BorderMetrics metrics = GeometryBackgroundBorder::ComputeBorderMetrics(offset.Round(), edge_sizes, inner_size, border_radius);
+
+	Vector<Vertex>& vertices = out_geometry->GetVertices();
+	Vector<int>& indices = out_geometry->GetIndices();
+
+	// Reserve geometry. A conservative estimate, does not take border-radii into account.
+	vertices.reserve((int)vertices.size() + 4);
+	indices.reserve((int)indices.size() + 6);
 
 
-	CornerSizes corner_sizes{border_radius.x, border_radius.y, border_radius.z, border_radius.w};
-	GeometryBackgroundBorder::Draw(vertices, indices, corner_sizes, box, offset, background_colour, border_colours);
+	// Generate the geometry
+	GeometryBackgroundBorder geometry(vertices, indices);
+	geometry.DrawBackground(metrics, color);
 }
 }
 
 
 } // namespace Rml
 } // namespace Rml