Browse Source

Implement the 'border-radius' property for backgrounds and borders. Add necessary geometry drawing utilities, merge ElementBackground and ElementBorder into a single unit ElementBackgroundBorder. See #115.

Michael Ragazzon 5 years ago
parent
commit
a603c3b6d1

+ 4 - 4
CMake/FileList.cmake

@@ -21,8 +21,7 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVerticalInstancer.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DecoratorTiledVerticalInstancer.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.h
     ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementAnimation.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementAnimation.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackground.h
-    ${PROJECT_SOURCE_DIR}/Source/Core/ElementBorder.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackgroundBorder.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDecoration.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDecoration.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDefinition.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDefinition.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementHandle.h
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementHandle.h
@@ -55,6 +54,7 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectGlow.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectOutline.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectOutline.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectShadow.h
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectShadow.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.h
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.h
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.h
     ${PROJECT_SOURCE_DIR}/Source/Core/IdNameMap.h
     ${PROJECT_SOURCE_DIR}/Source/Core/IdNameMap.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBox.h
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBox.h
@@ -261,8 +261,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/DocumentHeader.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Element.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Element.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementAnimation.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementAnimation.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackground.cpp
-    ${PROJECT_SOURCE_DIR}/Source/Core/ElementBorder.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/ElementBackgroundBorder.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDecoration.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDecoration.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDefinition.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDefinition.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDocument.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/ElementDocument.cpp
@@ -325,6 +324,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectShadow.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEffectShadow.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEngineInterface.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/FontEngineInterface.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Geometry.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/Geometry.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/GeometryBackgroundBorder.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryDatabase.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryUtilities.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/GeometryUtilities.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBox.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/LayoutBlockBox.cpp

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

@@ -139,6 +139,7 @@ struct ComputedValues
 	Padding padding_top, padding_right, padding_bottom, padding_left;
 	Padding padding_top, padding_right, padding_bottom, padding_left;
 	float border_top_width = 0, border_right_width = 0, border_bottom_width = 0, border_left_width = 0;
 	float border_top_width = 0, border_right_width = 0, border_bottom_width = 0, border_left_width = 0;
 	Colourb border_top_color{ 255, 255, 255 }, border_right_color{ 255, 255, 255 }, border_bottom_color{ 255, 255, 255 }, border_left_color{ 255, 255, 255 };
 	Colourb border_top_color{ 255, 255, 255 }, border_right_color{ 255, 255, 255 }, border_bottom_color{ 255, 255, 255 }, border_left_color{ 255, 255, 255 };
+	float border_top_left_radius = 0, border_top_right_radius = 0, border_bottom_right_radius = 0, border_bottom_left_radius = 0;
 
 
 	Display display = Display::Inline;
 	Display display = Display::Inline;
 	Position position = Position::Static;
 	Position position = Position::Static;

+ 0 - 6
Include/RmlUi/Core/Element.h

@@ -48,8 +48,6 @@ class Decorator;
 class ElementInstancer;
 class ElementInstancer;
 class EventDispatcher;
 class EventDispatcher;
 class EventListener;
 class EventListener;
-class ElementBackground;
-class ElementBorder;
 class ElementDecoration;
 class ElementDecoration;
 class ElementDefinition;
 class ElementDefinition;
 class ElementDocument;
 class ElementDocument;
@@ -552,10 +550,6 @@ public:
 	EventDispatcher* GetEventDispatcher() const;
 	EventDispatcher* GetEventDispatcher() const;
 	/// Returns event types with number of listeners for debugging.
 	/// Returns event types with number of listeners for debugging.
 	String GetEventDispatcherSummary() const;
 	String GetEventDispatcherSummary() const;
-	/// Access the element background.
-	ElementBackground* GetElementBackground() const;
-	/// Access the element border.
-	ElementBorder* GetElementBorder() const;
 	/// Access the element decorators.
 	/// Access the element decorators.
 	ElementDecoration* GetElementDecoration() const;
 	ElementDecoration* GetElementDecoration() const;
 	/// Returns the element's scrollbar functionality.
 	/// Returns the element's scrollbar functionality.

+ 3 - 0
Include/RmlUi/Core/Geometry.h

@@ -86,6 +86,9 @@ public:
 	/// @param[in] clear_buffers True to also clear the vertex and index buffers, false to leave intact.
 	/// @param[in] clear_buffers True to also clear the vertex and index buffers, false to leave intact.
 	void Release(bool clear_buffers = false);
 	void Release(bool clear_buffers = false);
 
 
+	/// Returns true if there is geometry to be rendered.
+	explicit operator bool() const;
+
 private:
 private:
 	// Move members from another geometry.
 	// Move members from another geometry.
 	void MoveFrom(Geometry& other);
 	void MoveFrom(Geometry& other);

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

@@ -36,6 +36,7 @@
 
 
 namespace Rml {
 namespace Rml {
 
 
+class Box;
 class Geometry;
 class Geometry;
 
 
 /**
 /**
@@ -54,7 +55,7 @@ public:
 	/// @param[in] dimensions The dimensions of the quad to generate.
 	/// @param[in] dimensions The dimensions of the quad to generate.
 	/// @param[in] colour The colour to be assigned to each of the quad's vertices.
 	/// @param[in] colour The colour to be assigned to each of the quad's vertices.
 	/// @param[in] index_offset The offset to be added to the generated indices; this should be the number of vertices already in the array.
 	/// @param[in] index_offset The offset to be added to the generated indices; this should be the number of vertices already in the array.
-	static void GenerateQuad(Vertex* vertices, int* indices, const Vector2f& origin, const Vector2f& dimensions, const Colourb& colour, int index_offset = 0);
+	static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, int index_offset = 0);
 	/// Generates a quad from a position, size, colour and texture coordinates.
 	/// Generates a quad from a position, size, colour and texture coordinates.
 	/// @param[out] vertices An array of at least four vertices that the generated vertex data will be written into.
 	/// @param[out] vertices An array of at least four vertices that the generated vertex data will be written into.
 	/// @param[out] indices An array of at least six indices that the generated index data will be written into.
 	/// @param[out] indices An array of at least six indices that the generated index data will be written into.
@@ -64,15 +65,25 @@ public:
 	/// @param[in] top_left_texcoord The texture coordinates at the top-left of the quad.
 	/// @param[in] top_left_texcoord The texture coordinates at the top-left of the quad.
 	/// @param[in] bottom_right_texcoord The texture coordinates at the bottom-right of the quad.
 	/// @param[in] bottom_right_texcoord The texture coordinates at the bottom-right of the quad.
 	/// @param[in] index_offset The offset to be added to the generated indices; this should be the number of vertices already in the array.
 	/// @param[in] index_offset The offset to be added to the generated indices; this should be the number of vertices already in the array.
-	static void GenerateQuad(Vertex* vertices, int* indices, const Vector2f& origin, const Vector2f& dimensions, const Colourb& colour, const Vector2f& top_left_texcoord, const Vector2f& bottom_right_texcoord, int index_offset = 0);
-	
+	static void GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, Vector2f top_left_texcoord, Vector2f bottom_right_texcoord, int index_offset = 0);
+
 	/// Generates the geometry required to render a line above, below or through a line of text.
 	/// Generates the geometry required to render a line above, below or through a line of text.
+	/// @param[in] font_face_handle The font face handle to derive the line's metrics from.
 	/// @param[out] geometry The geometry to append the newly created geometry into.
 	/// @param[out] geometry The geometry to append the newly created geometry into.
 	/// @param[in] position The position of the baseline of the lined text.
 	/// @param[in] position The position of the baseline of the lined text.
 	/// @param[in] width The width of the string to line.
 	/// @param[in] width The width of the string to line.
 	/// @param[in] decoration_type The type for vertical positioning of line.
 	/// @param[in] decoration_type The type for vertical positioning of line.
 	/// @param[in] colour The colour to draw the line in.
 	/// @param[in] colour The colour to draw the line in.
-	static void GenerateLine(FontFaceHandle font_face_handle, Geometry* geometry, const Vector2f& position, int width, Style::TextDecoration decoration_type, const Colourb& colour);
+	static void GenerateLine(FontFaceHandle font_face_handle, Geometry* geometry, Vector2f position, int width, Style::TextDecoration decoration_type, Colourb colour);
+
+	/// 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.
+	/// @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] 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] border_colours Pointer to a four-element array of border colors in top-right-bottom-left order, or nullptr to not generate borders.
+	static void GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector4f border_radius, Colourb background_colour, const Colourb* border_colours = nullptr);
 
 
 private:
 private:
 	GeometryUtilities();
 	GeometryUtilities();

+ 5 - 0
Include/RmlUi/Core/ID.h

@@ -52,6 +52,7 @@ enum class ShorthandId : uint8_t
 	BorderBottom,
 	BorderBottom,
 	BorderLeft,
 	BorderLeft,
 	Border,
 	Border,
+	BorderRadius,
 	Overflow,
 	Overflow,
 	Background,
 	Background,
 	Font,
 	Font,
@@ -91,6 +92,10 @@ enum class PropertyId : uint8_t
 	BorderRightColor,
 	BorderRightColor,
 	BorderBottomColor,
 	BorderBottomColor,
 	BorderLeftColor,
 	BorderLeftColor,
+	BorderTopLeftRadius,
+	BorderTopRightRadius,
+	BorderBottomRightRadius,
+	BorderBottomLeftRadius,
 	Display,
 	Display,
 	Position,
 	Position,
 	Top,
 	Top,

+ 6 - 0
Include/RmlUi/Core/Math.h

@@ -68,6 +68,12 @@ Type Clamp(Type value, Type min, Type max)
 	return (value < min) ? min : (value > max) ? max : value;
 	return (value < min) ? min : (value > max) ? max : value;
 }
 }
 
 
+template< typename Type >
+Type Lerp(float t, Type v0, Type v1)
+{
+	return v0 * (1.0f - t) + v1 * t;
+}
+
 /// Evaluates if a number is, or close to, zero.
 /// Evaluates if a number is, or close to, zero.
 /// @param[in] value The number to compare to zero.
 /// @param[in] value The number to compare to zero.
 /// @return True if the number if zero or close to it, false otherwise.
 /// @return True if the number if zero or close to it, false otherwise.

+ 39 - 36
Source/Core/Element.cpp

@@ -46,8 +46,7 @@
 #include "Clock.h"
 #include "Clock.h"
 #include "ComputeProperty.h"
 #include "ComputeProperty.h"
 #include "ElementAnimation.h"
 #include "ElementAnimation.h"
-#include "ElementBackground.h"
-#include "ElementBorder.h"
+#include "ElementBackgroundBorder.h"
 #include "ElementDefinition.h"
 #include "ElementDefinition.h"
 #include "ElementStyle.h"
 #include "ElementStyle.h"
 #include "EventDispatcher.h"
 #include "EventDispatcher.h"
@@ -100,11 +99,10 @@ static constexpr int ChildNotifyLevels = 2;
 // Meta objects for element collected in a single struct to reduce memory allocations
 // Meta objects for element collected in a single struct to reduce memory allocations
 struct ElementMeta
 struct ElementMeta
 {
 {
-	ElementMeta(Element* el) : event_dispatcher(el), style(el), background(el), border(el), decoration(el), scroll(el) {}
+	ElementMeta(Element* el) : event_dispatcher(el), style(el), background_border(el), decoration(el), scroll(el) {}
 	EventDispatcher event_dispatcher;
 	EventDispatcher event_dispatcher;
 	ElementStyle style;
 	ElementStyle style;
-	ElementBackground background;
-	ElementBorder border;
+	ElementBackgroundBorder background_border;
 	ElementDecoration decoration;
 	ElementDecoration decoration;
 	ElementScroll scroll;
 	ElementScroll scroll;
 	Style::ComputedValues computed_values;
 	Style::ComputedValues computed_values;
@@ -248,6 +246,11 @@ void Element::Render()
 	RMLUI_ZoneText(name.c_str(), name.size());
 	RMLUI_ZoneText(name.c_str(), name.size());
 #endif
 #endif
 
 
+	// TODO: This is a work-around for the dirty offset not being properly updated when used by (stacking context?) children. This results
+	// in scrolling not working properly. We don't care about the return value, the call is only used to force the absolute offset to update.
+	if (offset_dirty)
+		GetAbsoluteOffset(Box::BORDER);
+
 	// Rebuild our stacking context if necessary.
 	// Rebuild our stacking context if necessary.
 	if (stacking_context_dirty)
 	if (stacking_context_dirty)
 		BuildLocalStackingContext();
 		BuildLocalStackingContext();
@@ -265,8 +268,7 @@ void Element::Render()
 	// Set up the clipping region for this element.
 	// Set up the clipping region for this element.
 	if (ElementUtilities::SetClippingRegion(this))
 	if (ElementUtilities::SetClippingRegion(this))
 	{
 	{
-		meta->background.RenderBackground();
-		meta->border.RenderBorder();
+		meta->background_border.Render(this);
 		meta->decoration.RenderDecorators();
 		meta->decoration.RenderDecorators();
 
 
 		{
 		{
@@ -495,8 +497,8 @@ void Element::SetBox(const Box& box)
 
 
 		OnResize();
 		OnResize();
 
 
-		meta->background.DirtyBackground();
-		meta->border.DirtyBorder();
+		meta->background_border.DirtyBackground();
+		meta->background_border.DirtyBorder();
 		meta->decoration.DirtyDecorators();
 		meta->decoration.DirtyDecorators();
 	}
 	}
 }
 }
@@ -508,8 +510,8 @@ void Element::AddBox(const Box& box)
 
 
 	OnResize();
 	OnResize();
 
 
-	meta->background.DirtyBackground();
-	meta->border.DirtyBorder();
+	meta->background_border.DirtyBackground();
+	meta->background_border.DirtyBorder();
 	meta->decoration.DirtyDecorators();
 	meta->decoration.DirtyDecorators();
 }
 }
 
 
@@ -1560,18 +1562,6 @@ String Element::GetEventDispatcherSummary() const
 	return meta->event_dispatcher.ToString();
 	return meta->event_dispatcher.ToString();
 }
 }
 
 
-// Access the element background.
-ElementBackground* Element::GetElementBackground() const
-{
-	return &meta->background;
-}
-
-// Access the element border.
-ElementBorder* Element::GetElementBorder() const
-{
-	return &meta->border;
-}
-
 // Access the element decorators
 // Access the element decorators
 ElementDecoration* Element::GetElementDecoration() const
 ElementDecoration* Element::GetElementDecoration() const
 {
 {
@@ -1716,6 +1706,13 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties)
 			DirtyLayout();
 			DirtyLayout();
 	}
 	}
 
 
+	const bool border_radius_changed = (
+		changed_properties.Contains(PropertyId::BorderTopLeftRadius) ||
+		changed_properties.Contains(PropertyId::BorderTopRightRadius) ||
+		changed_properties.Contains(PropertyId::BorderBottomRightRadius) ||
+		changed_properties.Contains(PropertyId::BorderBottomLeftRadius)
+	);
+
 
 
 	// Update the visibility.
 	// Update the visibility.
 	if (changed_properties.Contains(PropertyId::Visibility) ||
 	if (changed_properties.Contains(PropertyId::Visibility) ||
@@ -1800,21 +1797,17 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties)
 	}
 	}
 
 
 	// Dirty the background if it's changed.
 	// Dirty the background if it's changed.
-    if (changed_properties.Contains(PropertyId::BackgroundColor) ||
+    if (border_radius_changed ||
+		changed_properties.Contains(PropertyId::BackgroundColor) ||
 		changed_properties.Contains(PropertyId::Opacity) ||
 		changed_properties.Contains(PropertyId::Opacity) ||
-		changed_properties.Contains(PropertyId::ImageColor)) {
-		meta->background.DirtyBackground();
+		changed_properties.Contains(PropertyId::ImageColor))
+	{
+		meta->background_border.DirtyBackground();
     }
     }
-	
-	// Dirty the decoration if it's changed.
-	if (changed_properties.Contains(PropertyId::Decorator) ||
-		changed_properties.Contains(PropertyId::Opacity) ||
-		changed_properties.Contains(PropertyId::ImageColor)) {
-		meta->decoration.DirtyDecorators();
-	}
 
 
 	// Dirty the border if it's changed.
 	// Dirty the border if it's changed.
-	if (changed_properties.Contains(PropertyId::BorderTopWidth) ||
+	if (border_radius_changed ||
+		changed_properties.Contains(PropertyId::BorderTopWidth) ||
 		changed_properties.Contains(PropertyId::BorderRightWidth) ||
 		changed_properties.Contains(PropertyId::BorderRightWidth) ||
 		changed_properties.Contains(PropertyId::BorderBottomWidth) ||
 		changed_properties.Contains(PropertyId::BorderBottomWidth) ||
 		changed_properties.Contains(PropertyId::BorderLeftWidth) ||
 		changed_properties.Contains(PropertyId::BorderLeftWidth) ||
@@ -1823,8 +1816,18 @@ void Element::OnPropertyChange(const PropertyIdSet& changed_properties)
 		changed_properties.Contains(PropertyId::BorderBottomColor) ||
 		changed_properties.Contains(PropertyId::BorderBottomColor) ||
 		changed_properties.Contains(PropertyId::BorderLeftColor) ||
 		changed_properties.Contains(PropertyId::BorderLeftColor) ||
 		changed_properties.Contains(PropertyId::Opacity))
 		changed_properties.Contains(PropertyId::Opacity))
-		meta->border.DirtyBorder();
-
+	{
+		meta->background_border.DirtyBorder();
+	}
+	
+	// Dirty the decoration if it's changed.
+	if (border_radius_changed ||
+		changed_properties.Contains(PropertyId::Decorator) ||
+		changed_properties.Contains(PropertyId::Opacity) ||
+		changed_properties.Contains(PropertyId::ImageColor))
+	{
+		meta->decoration.DirtyDecorators();
+	}
 	
 	
 	// Check for clipping state changes
 	// Check for clipping state changes
 	if (changed_properties.Contains(PropertyId::Clip) ||
 	if (changed_properties.Contains(PropertyId::Clip) ||

+ 0 - 134
Source/Core/ElementBackground.cpp

@@ -1,134 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-
-#include "ElementBackground.h"
-#include "../../Include/RmlUi/Core/Element.h"
-#include "../../Include/RmlUi/Core/GeometryUtilities.h"
-#include "../../Include/RmlUi/Core/Property.h"
-#include "../../Include/RmlUi/Core/Profiling.h"
-
-
-namespace Rml {
-
-ElementBackground::ElementBackground(Element* _element) : geometry(_element)
-{
-	element = _element;
-	background_dirty = true;
-}
-
-ElementBackground::~ElementBackground()
-{
-}
-
-// Renders the element's background, if it has one.
-void ElementBackground::RenderBackground()
-{
-	if (background_dirty)
-	{
-		background_dirty = false;
-		GenerateBackground();
-	}
-
-	geometry.Render(element->GetAbsoluteOffset(Box::PADDING));
-}
-
-// Marks the background geometry as dirty.
-void ElementBackground::DirtyBackground()
-{
-	background_dirty = true;
-}
-
-// Generates the background geometry for the element.
-void ElementBackground::GenerateBackground()
-{
-	RMLUI_ZoneScoped;
-
-	// Fetch the new colour for the background. If the colour is transparent, then we don't render any background.
-	auto& computed = element->GetComputedValues();
-	Colourb colour = computed.background_color;
-	float opacity = computed.opacity;
-
-	// Apply opacity
-	colour.alpha = (byte)(opacity * (float)colour.alpha);
-
-	if (colour.alpha <= 0)
-	{
-		geometry.GetVertices().clear();
-		geometry.GetIndices().clear();
-		geometry.Release();
-
-		return;
-	}
-
-	// Work out how many boxes we need to generate geometry for.
-	int num_boxes = 0;
-
-	for (int i = 0; i < element->GetNumBoxes(); ++i)
-	{
-		const Box& box = element->GetBox(i);
-		Vector2f size = box.GetSize(Box::PADDING);
-		if (size.x > 0 && size.y > 0)
-			num_boxes++;
-	}
-
-	Vector< Vertex >& vertices = geometry.GetVertices();
-	Vector< int >& indices = geometry.GetIndices();
-
-	int index_offset = 0;
-	vertices.resize(4 * num_boxes);
-	indices.resize(6 * num_boxes);
-
-	if (num_boxes > 0)
-	{
-		Vertex* raw_vertices = &vertices[0];
-		int* raw_indices = &indices[0];
-
-		for (int i = 0; i < element->GetNumBoxes(); ++i)
-			GenerateBackground(raw_vertices, raw_indices, index_offset, element->GetBox(i), colour);
-	}
-
-	geometry.Release();
-}
-
-// Generates the background geometry for a single box.
-void ElementBackground::GenerateBackground(Vertex*& vertices, int*& indices, int& index_offset, const Box& box, const Colourb& colour)
-{
-	Vector2f padded_size = box.GetSize(Box::PADDING);
-	if (padded_size.x <= 0 ||
-		padded_size.y <= 0)
-		return;
-
-	GeometryUtilities::GenerateQuad(vertices, indices, box.GetOffset(), padded_size, colour, index_offset);
-
-	vertices += 4;
-	indices += 6;
-	index_offset += 4;
-}
-
-} // namespace Rml

+ 0 - 70
Source/Core/ElementBackground.h

@@ -1,70 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
-#ifndef RMLUI_CORE_ELEMENTBACKGROUND_H
-#define RMLUI_CORE_ELEMENTBACKGROUND_H
-
-#include "../../Include/RmlUi/Core/Geometry.h"
-
-namespace Rml {
-
-class Box;
-class Element;
-
-/**
-	@author Peter Curry
- */
-
-class ElementBackground
-{
-public:
-	ElementBackground(Element* element);
-	~ElementBackground();
-
-	/// Renders the element's border, if it has one.
-	void RenderBackground();
-
-	/// Marks the border geometry as dirty.
-	void DirtyBackground();
-
-private:
-	// Generates the border geometry for the element.
-	void GenerateBackground();
-	// Generates the border geometry for a single box.
-	void GenerateBackground(Vertex*& vertices, int*& indices, int& index_offset, const Box& box, const Colourb& colour);
-
-	Element* element;
-
-	// The background geometry.
-	Geometry geometry;
-
-	bool background_dirty;
-};
-
-} // namespace Rml
-#endif

+ 105 - 0
Source/Core/ElementBackgroundBorder.cpp

@@ -0,0 +1,105 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "ElementBackgroundBorder.h"
+#include "../../Include/RmlUi/Core/Box.h"
+#include "../../Include/RmlUi/Core/ComputedValues.h"
+#include "../../Include/RmlUi/Core/Element.h"
+#include "../../Include/RmlUi/Core/GeometryUtilities.h"
+
+namespace Rml {
+
+
+ElementBackgroundBorder::ElementBackgroundBorder(Element* element) : geometry(element)
+{}
+
+void ElementBackgroundBorder::Render(Element * element)
+{
+	if (background_dirty || border_dirty)
+	{
+		GenerateGeometry(element);
+
+		background_dirty = false;
+		border_dirty = false;
+	}
+
+	if (geometry)
+		geometry.Render(element->GetAbsoluteOffset(Box::BORDER));
+}
+
+void ElementBackgroundBorder::DirtyBackground()
+{
+	background_dirty = true;
+}
+
+void ElementBackgroundBorder::DirtyBorder()
+{
+	border_dirty = true;
+}
+
+void ElementBackgroundBorder::GenerateGeometry(Element* element)
+{
+	const ComputedValues& computed = element->GetComputedValues();
+
+	Colourb background_color = computed.background_color;
+	Colourb border_colors[4] = {
+		computed.border_top_color,
+		computed.border_right_color,
+		computed.border_bottom_color,
+		computed.border_left_color,
+	};
+	
+	// Apply opacity
+	const float opacity = computed.opacity;
+	background_color.alpha = (byte)(opacity * (float)background_color.alpha);
+
+	if (opacity < 1)
+	{
+		for (int i = 0; i < 4; ++i)
+			border_colors[i].alpha = (byte)(opacity * (float)border_colors[i].alpha);
+	}
+
+	geometry.GetVertices().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++)
+	{
+		GeometryUtilities::GenerateBackgroundBorder(&geometry, element->GetBox(i), radii, background_color, border_colors);
+	}
+
+	geometry.Release();
+}
+
+} // namespace Rml

+ 10 - 24
Source/Core/ElementBorder.h → Source/Core/ElementBackgroundBorder.h

@@ -26,44 +26,30 @@
  *
  *
  */
  */
 
 
-#ifndef RMLUI_CORE_ELEMENTBORDER_H
-#define RMLUI_CORE_ELEMENTBORDER_H
+#ifndef RMLUI_CORE_ELEMENTBACKGROUNDBORDER_H
+#define RMLUI_CORE_ELEMENTBACKGROUNDBORDER_H
 
 
+#include "../../Include/RmlUi/Core/Types.h"
 #include "../../Include/RmlUi/Core/Geometry.h"
 #include "../../Include/RmlUi/Core/Geometry.h"
 
 
 namespace Rml {
 namespace Rml {
 
 
-class Box;
-class Element;
-
-/**
-	@author Peter Curry
- */
-
-class ElementBorder
-{
+class ElementBackgroundBorder {
 public:
 public:
-	ElementBorder(Element* element);
-	~ElementBorder();
+	ElementBackgroundBorder(Element* element);
 
 
-	/// Renders the element's border, if it has one.
-	void RenderBorder();
+	void Render(Element* element);
 
 
-	/// Marks the border geometry as dirty.
+	void DirtyBackground();
 	void DirtyBorder();
 	void DirtyBorder();
 
 
 private:
 private:
-	// Generates the border geometry for the element.
-	void GenerateBorder();
-	// Generates the border geometry for a single box.
-	void GenerateBorder(Vertex*& vertices, int*& indices, int& index_offset, const Box& box, const Colourb* colours);
+	void GenerateGeometry(Element* element);
 
 
-	Element* element;
+	bool background_dirty = false;
+	bool border_dirty = false;
 
 
-	// The border geometry.
 	Geometry geometry;
 	Geometry geometry;
-
-	bool border_dirty;
 };
 };
 
 
 } // namespace Rml
 } // namespace Rml

+ 0 - 161
Source/Core/ElementBorder.cpp

@@ -1,161 +0,0 @@
-/*
- * This source file is part of RmlUi, the HTML/CSS Interface Middleware
- *
- * For the latest information, see http://github.com/mikke89/RmlUi
- *
- * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
- * Copyright (c) 2019 The RmlUi Team, and contributors
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- *
- */
-
- 
-#include "ElementBorder.h"
-#include "../../Include/RmlUi/Core/Element.h"
-#include "../../Include/RmlUi/Core/Property.h"
-#include "../../Include/RmlUi/Core/Profiling.h"
-
-namespace Rml {
-
-ElementBorder::ElementBorder(Element* _element) : geometry(_element)
-{
-	element = _element;
-	border_dirty = true;
-}
-
-ElementBorder::~ElementBorder()
-{
-}
-
-// Renders the element's border, if it has one.
-void ElementBorder::RenderBorder()
-{
-	RMLUI_ZoneScoped;
-	if (border_dirty)
-	{
-		border_dirty = false;
-		GenerateBorder();
-	}
-
-	geometry.Render(element->GetAbsoluteOffset(Box::BORDER));
-}
-
-// Marks the border geometry as dirty.
-void ElementBorder::DirtyBorder()
-{
-	border_dirty = true;
-}
-
-// Generates the border geometry for the element.
-void ElementBorder::GenerateBorder()
-{
-	int num_edges = 0;
-
-	for (int i = 0; i < element->GetNumBoxes(); ++i)
-	{
-		const Box& box = element->GetBox(i);
-		for (int j = 0; j < 4; j++)
-		{
-			if (box.GetEdge(Box::BORDER, (Box::Edge) j) > 0)
-				num_edges++;
-		}
-	}
-
-	Vector< Vertex >& vertices = geometry.GetVertices();
-	Vector< int >& indices = geometry.GetIndices();
-
-	int index_offset = 0;
-	vertices.resize(4 * num_edges);
-	indices.resize(6 * num_edges);
-
-	if (num_edges > 0)
-	{
-		Vertex* raw_vertices = &vertices[0];
-		int* raw_indices = &indices[0];
-		const ComputedValues& computed = element->GetComputedValues();
-
-		Colourb border_colours[4];
-		border_colours[0] = computed.border_top_color;
-		border_colours[1] = computed.border_right_color;
-		border_colours[2] = computed.border_bottom_color;
-		border_colours[3] = computed.border_left_color;
-
-		// Apply opacity to the border
-		float opacity = computed.opacity;
-		for(int i = 0; i < 4; ++i) {
-			border_colours[i].alpha = (byte)(opacity * (float)border_colours[i].alpha);
-		}
-
-		for (int i = 0; i < element->GetNumBoxes(); ++i)
-			GenerateBorder(raw_vertices, raw_indices, index_offset, element->GetBox(i), border_colours);
-	}
-
-	geometry.Release();
-}
-
-// Generates the border geometry for a single box.
-void ElementBorder::GenerateBorder(Vertex*& vertices, int*& indices, int& index_offset, const Box& box, const Colourb* colours)
-{
-	// The axis of extrusion for each of the edges.
-	Vector2f box_extrusions[4] =
-	{
-		Vector2f(0, -1 * box.GetEdge(Box::BORDER, Box::TOP)),
-		Vector2f(box.GetEdge(Box::BORDER, Box::RIGHT), 0),
-		Vector2f(0, box.GetEdge(Box::BORDER, Box::BOTTOM)),
-		Vector2f(-1 * box.GetEdge(Box::BORDER, Box::LEFT), 0)
-	};
-
-	// The position of each of the corners of the inner border.
-	Vector2f box_corners[4];
-	box_corners[0] = box.GetPosition(Box::PADDING);
-	box_corners[2] = box_corners[0] + box.GetSize(Box::PADDING);
-	box_corners[1] = Vector2f(box_corners[2].x, box_corners[0].y);
-	box_corners[3] = Vector2f(box_corners[0].x, box_corners[2].y);
-
-	for (int i = 0; i < 4; i++)
-	{
-		float border_width = box.GetEdge(Box::BORDER, (Box::Edge) i);
-		if (border_width <= 0)
-			continue;
-
-		vertices[0].position = box_corners[i];
-		vertices[1].position = box_corners[i] + box_extrusions[i] + box_extrusions[i == 0 ? 3 : i - 1];
-		vertices[2].position = box_corners[i == 3 ? 0 : i + 1];
-		vertices[3].position = vertices[2].position + box_extrusions[i] + box_extrusions[i == 3 ? 0 : i + 1];
-
-		vertices[0].colour = colours[i];
-		vertices[1].colour = colours[i];
-		vertices[2].colour = colours[i];
-		vertices[3].colour = colours[i];
-
-		indices[0] = index_offset;
-		indices[1] = index_offset + 3;
-		indices[2] = index_offset + 1;
-		indices[3] = index_offset;
-		indices[4] = index_offset + 2;
-		indices[5] = index_offset + 3;
-
-		vertices += 4;
-		indices += 6;
-		index_offset += 4;
-	}
-}
-
-} // namespace Rml

+ 13 - 2
Source/Core/ElementStyle.cpp

@@ -41,8 +41,6 @@
 #include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "../../Include/RmlUi/Core/TransformPrimitive.h"
 #include "../../Include/RmlUi/Core/TransformPrimitive.h"
-#include "ElementBackground.h"
-#include "ElementBorder.h"
 #include "ElementDecoration.h"
 #include "ElementDecoration.h"
 #include "ElementDefinition.h"
 #include "ElementDefinition.h"
 #include "ComputeProperty.h"
 #include "ComputeProperty.h"
@@ -674,6 +672,19 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 			values.border_left_color = p->Get<Colourb>();
 			values.border_left_color = p->Get<Colourb>();
 			break;
 			break;
 
 
+		case PropertyId::BorderTopLeftRadius:
+			values.border_top_left_radius = ComputeLength(p, font_size, document_font_size, dp_ratio);
+			break;
+		case PropertyId::BorderTopRightRadius:
+			values.border_top_right_radius = ComputeLength(p, font_size, document_font_size, dp_ratio);
+			break;
+		case PropertyId::BorderBottomRightRadius:
+			values.border_bottom_right_radius = ComputeLength(p, font_size, document_font_size, dp_ratio);
+			break;
+		case PropertyId::BorderBottomLeftRadius:
+			values.border_bottom_left_radius = ComputeLength(p, font_size, document_font_size, dp_ratio);
+			break;
+
 		case PropertyId::Display:
 		case PropertyId::Display:
 			values.display = (Display)p->Get<int>();
 			values.display = (Display)p->Get<int>();
 			break;
 			break;

+ 5 - 0
Source/Core/Geometry.cpp

@@ -181,6 +181,11 @@ void Geometry::Release(bool clear_buffers)
 	}
 	}
 }
 }
 
 
+Geometry::operator bool() const
+{
+	return !indices.empty();
+}
+
 // Returns the host context's render interface.
 // Returns the host context's render interface.
 RenderInterface* Geometry::GetRenderInterface()
 RenderInterface* Geometry::GetRenderInterface()
 {
 {

+ 406 - 0
Source/Core/GeometryBackgroundBorder.cpp

@@ -0,0 +1,406 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "GeometryBackgroundBorder.h"
+#include "../../Include/RmlUi/Core/Box.h"
+#include "../../Include/RmlUi/Core/Math.h"
+#include <algorithm>
+#include <float.h>
+
+namespace Rml {
+
+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 Colourb background_color, const Colourb* border_colors)
+{
+	using Edge = Box::Edge;
+
+	EdgeSizes border_widths = {
+		box.GetEdge(Box::BORDER, Edge::TOP),
+		box.GetEdge(Box::BORDER, Edge::RIGHT),
+		box.GetEdge(Box::BORDER, Edge::BOTTOM),
+		box.GetEdge(Box::BORDER, Edge::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(Box::PADDING);
+
+	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;
+
+
+	// -- Find the corner positions --
+
+	const Vector2f padding_position = box.GetPosition(Box::PADDING);
+	const Vector2f border_position = padding_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)
+	};
+
+	// 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)
+	};
+
+
+	// -- 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 bool has_radius = (sum_radius > 0);
+
+	// 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)
+	{
+		// Scale the radii such that we have no overlapping curves.
+		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
+
+		if (scale_factor < 1)
+		{
+			for (int i = 0; i < 4; i++)
+				radii[i] *= scale_factor;
+		}
+
+		// 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]
+		};
+
+		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])
+		};
+	}
+
+	// -- Generate the geometry --
+
+	GeometryBackgroundBorder geometry(vertices, indices);
+
+	{
+		// 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);
+	}
+
+	// Draw the background
+	if (has_background)
+	{
+		const int offset_vertices = (int)vertices.size();
+
+		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);
+
+		geometry.FillBackground(offset_vertices);
+	}
+
+	// Draw the border
+	if (has_border)
+	{
+		using Edge = Box::Edge;
+		const int offset_vertices = (int)vertices.size();
+
+		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[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);
+
+			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());
+			}
+		}
+	}
+
+#ifdef RMLUI_DEBUG
+	const int num_vertices = (int)vertices.size();
+	for (int index : indices)
+	{
+		RMLUI_ASSERT(index < num_vertices);
+	}
+#endif
+}
+
+
+
+void GeometryBackgroundBorder::DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, Colourb color)
+{
+	if (R == 0 || r.x <= 0 || r.y <= 0)
+	{
+		DrawPoint(pos_inner, color);
+	}
+	else if (r.x > 0 && r.y > 0)
+	{
+		const float a0 = float((int)corner + 2) * 0.5f * Math::RMLUI_PI;
+		const float a1 = float((int)corner + 3) * 0.5f * Math::RMLUI_PI;
+		const int num_points = GetNumPoints(R);
+		DrawArc(pos_circle_center, r, a0, a1, color, color, num_points);
+	}
+}
+
+void GeometryBackgroundBorder::DrawPoint(Vector2f pos, Colourb color)
+{
+	const int offset_vertices = (int)vertices.size();
+
+	vertices.resize(offset_vertices + 1);
+
+	vertices[offset_vertices].position = pos;
+	vertices[offset_vertices].colour = color;
+}
+
+void GeometryBackgroundBorder::DrawArc(Vector2f pos_center, Vector2f r, float a0, float a1, Colourb color0, Colourb color1, int num_points)
+{
+	RMLUI_ASSERT(num_points >= 2 && r.x > 0 && r.y > 0);
+
+	const int offset_vertices = (int)vertices.size();
+
+	vertices.resize(offset_vertices + num_points);
+
+	for (int i = 0; i < num_points; i++)
+	{
+		const float t = float(i) / float(num_points - 1);
+
+		const float a = Math::Lerp(t, a0, a1);
+		const Colourb color = Math::Lerp(t, color0, color1);
+
+		const Vector2f unit_vector(Math::Cos(a), Math::Sin(a));
+
+		vertices[offset_vertices + i].position = unit_vector * r + pos_center;
+		vertices[offset_vertices + i].colour = color;
+	}
+}
+
+void GeometryBackgroundBorder::FillBackground(int index_start)
+{
+	const int num_added_vertices = (int)vertices.size() - index_start;
+	const int offset_indices = (int)indices.size();
+
+	const int num_triangles = (num_added_vertices - 2);
+
+	indices.resize(offset_indices + 3 * num_triangles);
+
+	for (int i = 0; i < num_triangles; i++)
+	{
+		indices[offset_indices + 3 * i] = index_start;
+		indices[offset_indices + 3 * i + 1] = index_start + i + 2;
+		indices[offset_indices + 3 * i + 2] = index_start + i + 1;
+	}
+}
+
+void GeometryBackgroundBorder::DrawBorderCorner(Corner corner, Vector2f pos_outer, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, Colourb color0, Colourb color1)
+{
+	const float a0 = float((int)corner + 2) * 0.5f * Math::RMLUI_PI;
+	const float a1 = float((int)corner + 3) * 0.5f * Math::RMLUI_PI;
+
+	if (R == 0)
+	{
+		DrawPointPoint(pos_outer, pos_inner, color0, color1);
+	}
+	else if (r.x > 0 && r.y > 0)
+	{
+		DrawArcArc(pos_circle_center, R, r, a0, a1, color0, color1, GetNumPoints(R));
+	}
+	else
+	{
+		DrawArcPoint(pos_circle_center, pos_inner, R, a0, a1, color0, color1, GetNumPoints(R));
+	}
+}
+
+void GeometryBackgroundBorder::DrawPointPoint(Vector2f pos_outer, Vector2f pos_inner, Colourb color0, Colourb color1)
+{
+	const bool different_color = (color0 != color1);
+
+	vertices.reserve((int)vertices.size() + (different_color ? 4 : 2));
+
+	DrawPoint(pos_inner, color0);
+	DrawPoint(pos_outer, color0);
+
+	if (different_color)
+	{
+		DrawPoint(pos_inner, color1);
+		DrawPoint(pos_outer, color1);
+	}
+}
+
+void GeometryBackgroundBorder::DrawArcArc(Vector2f pos_center, float R, Vector2f r, float a0, float a1, Colourb color0, Colourb color1, int num_points)
+{
+	RMLUI_ASSERT(num_points >= 2 && R > 0 && r.x > 0 && r.y > 0);
+
+	const int num_triangles = 2 * (num_points - 1);
+
+	const int offset_vertices = (int)vertices.size();
+	const int offset_indices = (int)indices.size();
+
+	vertices.resize(offset_vertices + 2 * num_points);
+	indices.resize(offset_indices + 3 * num_triangles);
+
+	for (int i = 0; i < num_points; i++)
+	{
+		const float t = float(i) / float(num_points - 1);
+
+		const float a = Math::Lerp(t, a0, a1);
+		const Colourb color = Math::Lerp(t, color0, color1);
+
+		const Vector2f unit_vector(Math::Cos(a), Math::Sin(a));
+
+		vertices[offset_vertices + 2 * i].position = unit_vector * r + pos_center;
+		vertices[offset_vertices + 2 * i].colour = color;
+		vertices[offset_vertices + 2 * i + 1].position = unit_vector * R + pos_center;
+		vertices[offset_vertices + 2 * i + 1].colour = color;
+	}
+
+	for (int i = 0; i < num_triangles; i += 2)
+	{
+		indices[offset_indices + 3 * i + 0] = offset_vertices + i + 0;
+		indices[offset_indices + 3 * i + 1] = offset_vertices + i + 2;
+		indices[offset_indices + 3 * i + 2] = offset_vertices + i + 1;
+
+		indices[offset_indices + 3 * i + 3] = offset_vertices + i + 1;
+		indices[offset_indices + 3 * i + 4] = offset_vertices + i + 2;
+		indices[offset_indices + 3 * i + 5] = offset_vertices + i + 3;
+	}
+}
+
+void GeometryBackgroundBorder::DrawArcPoint(Vector2f pos_center, Vector2f pos_inner, float R, float a0, float a1, Colourb color0, Colourb color1, int num_points)
+{
+	RMLUI_ASSERT(R > 0 && num_points >= 2);
+
+	const int offset_vertices = (int)vertices.size();
+	vertices.reserve(offset_vertices + num_points + 2);
+
+	// Generate the vertices. We could also split the arc mid-way to create a sharp color transition.
+	DrawPoint(pos_inner, color0);
+	DrawArc(pos_center, Vector2f(R), a0, a1, color0, color1, num_points);
+	DrawPoint(pos_inner, color1);
+
+	RMLUI_ASSERT((int)vertices.size() - offset_vertices == num_points + 2);
+
+	// Swap the last two vertices such that the outer edge vertex is last, see the comment for the border drawing functions. Their colors should already be the same.
+	const int last_vertex = (int)vertices.size() - 1;
+	std::swap(vertices[last_vertex - 1].position, vertices[last_vertex].position);
+
+	// Generate the indices
+	const int num_triangles = (num_points - 1);
+
+	const int i_vertex_inner0 = offset_vertices;
+	const int i_vertex_inner1 = last_vertex - 1;
+
+	const int offset_indices = (int)indices.size();
+	indices.resize(offset_indices + 3 * num_triangles);
+
+	for (int i = 0; i < num_triangles; i++)
+	{
+		indices[offset_indices + 3 * i + 0] = (i > num_triangles / 2 ? i_vertex_inner1 : i_vertex_inner0);
+		indices[offset_indices + 3 * i + 1] = offset_vertices + i + 1;
+		indices[offset_indices + 3 * i + 2] = offset_vertices + i;
+	}
+
+	// Since we swapped the last two vertices we also need to change the last triangle.
+	indices[offset_indices + 3 * (num_triangles - 1) + 1] = last_vertex;
+}
+
+void GeometryBackgroundBorder::FillEdge(int index_next_corner)
+{
+	const int offset_indices = (int)indices.size();
+	const int num_vertices = (int)vertices.size();
+	RMLUI_ASSERT(num_vertices >= 2);
+
+	indices.resize(offset_indices + 6);
+
+	indices[offset_indices + 0] = num_vertices - 2;
+	indices[offset_indices + 1] = index_next_corner;
+	indices[offset_indices + 2] = num_vertices - 1;
+
+	indices[offset_indices + 3] = num_vertices - 1;
+	indices[offset_indices + 4] = index_next_corner;
+	indices[offset_indices + 5] = index_next_corner + 1;
+}
+
+int GeometryBackgroundBorder::GetNumPoints(float R) const
+{
+	return Math::Clamp(2 + int(R / 5.f), 2, 100);
+}
+
+} // namespace Rml

+ 115 - 0
Source/Core/GeometryBackgroundBorder.h

@@ -0,0 +1,115 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#ifndef RMLUI_CORE_GEOMETRYBACKGROUNDBORDER_H
+#define RMLUI_CORE_GEOMETRYBACKGROUNDBORDER_H
+
+#include "../../Include/RmlUi/Core/Types.h"
+#include "../../Include/RmlUi/Core/Vertex.h"
+
+namespace Rml {
+
+class Box;
+
+// Ordered by top, right, bottom, left.
+using EdgeSizes = Array<float, 4>;
+
+// Ordered by top-left, top-right, bottom-right, bottom-left.
+using CornerSizes = Array<float, 4>;
+using CornerSizes2 = Array<Vector2f, 4>;
+using CornerPositions = Array<Vector2f, 4>;
+
+
+class GeometryBackgroundBorder {
+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] 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, Colourb background_color, const Colourb* border_colors);
+
+private:
+	enum Corner { TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT };
+
+	GeometryBackgroundBorder(Vector<Vertex>& vertices, Vector<int>& indices);
+
+	// -- Background --
+	// All draw operations place vertices in clockwise order.
+
+	// Draw the corner, delegate to the specific corner shape drawing function.
+	void DrawBackgroundCorner(Corner corner, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, Colourb color);
+
+	// Add a single point.
+	void DrawPoint(Vector2f pos, Colourb color);
+
+	// Draw an arc by placing vertices along the ellipse formed by the two-axis radius r, spaced evenly between angles a0,a1 (inclusive). Colors are interpolated.
+	void DrawArc(Vector2f pos_center, Vector2f r, float a0, float a1, Colourb color0, Colourb color1, int num_points);
+
+	// Generates triangles by connecting the added vertices.
+	void FillBackground(int index_start);
+
+
+	// -- Border --
+	// All draw operations place the first and last vertices in the following manner.
+	// Let N be the number of vertices placed, and the numbers be indices into the newly placed vertices:
+	//   0: Inner edge, aligned with the previous corner
+	//   1: Outer edge, aligned with the previous corner
+	//   N-2: Inner edge, aligned with the next corner
+	//   N-1: Outer edge, aligned with the next corner
+	// Where 'next' corner means along the clockwise direction. This way we can easily fill the triangles of the edges in FillEdge().
+
+	// Draw the corner, delegate to the specific corner shape drawing function.
+	void DrawBorderCorner(Corner corner, Vector2f pos_outer, Vector2f pos_inner, Vector2f pos_circle_center, float R, Vector2f r, Colourb color0, Colourb color1);
+
+	// Draw a sharp border corner, ie. no border-radius. Does not produce any triangles.
+	void DrawPointPoint(Vector2f pos_outer, Vector2f pos_inner, Colourb color0, Colourb color1);
+
+	// Draw an arc along the outer edge (radius R), and an arc along the inner edge (two-axis radius r),
+	// spaced evenly between angles a0,a1 (inclusive). Connect them by triangles. Colors are interpolated.
+	void DrawArcArc(Vector2f pos_center, float R, Vector2f r, float a0, float a1, Colourb color0, Colourb color1, int num_points);
+
+	// Draw an arc along the outer edge, and connect them by triangles to a point on the inner edge.
+	void DrawArcPoint(Vector2f pos_center, Vector2f pos_inner, float R, float a0, float a1, Colourb color0, Colourb color1, int num_points);
+
+	// Add triangles between the previous corner to another one specified by the index (possibly yet-to-be-drawn).
+	void FillEdge(int index_next_corner);
+
+
+	// -- Tools --
+	int GetNumPoints(float R) const;
+
+	Vector<Vertex>& vertices;
+	Vector<int>& indices;
+};
+
+} // namespace Rml
+#endif

+ 13 - 3
Source/Core/GeometryUtilities.cpp

@@ -31,6 +31,7 @@
 #include "../../Include/RmlUi/Core/FontEngineInterface.h"
 #include "../../Include/RmlUi/Core/FontEngineInterface.h"
 #include "../../Include/RmlUi/Core/Geometry.h"
 #include "../../Include/RmlUi/Core/Geometry.h"
 #include "../../Include/RmlUi/Core/Types.h"
 #include "../../Include/RmlUi/Core/Types.h"
+#include "GeometryBackgroundBorder.h"
 
 
 namespace Rml {
 namespace Rml {
 
 
@@ -43,13 +44,13 @@ GeometryUtilities::~GeometryUtilities()
 }
 }
 
 
 // Generates a quad from a position, size and colour.
 // Generates a quad from a position, size and colour.
-void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, const Vector2f& origin, const Vector2f& dimensions, const Colourb& colour, int index_offset)
+void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, int index_offset)
 {
 {
 	GenerateQuad(vertices, indices, origin, dimensions, colour, Vector2f(0, 0), Vector2f(1, 1), index_offset);
 	GenerateQuad(vertices, indices, origin, dimensions, colour, Vector2f(0, 0), Vector2f(1, 1), index_offset);
 }
 }
 
 
 // Generates a quad from a position, size, colour and texture coordinates.
 // Generates a quad from a position, size, colour and texture coordinates.
-void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, const Vector2f& origin, const Vector2f& dimensions, const Colourb& colour, const Vector2f& top_left_texcoord, const Vector2f& bottom_right_texcoord, int index_offset)
+void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, Vector2f origin, Vector2f dimensions, Colourb colour, Vector2f top_left_texcoord, Vector2f bottom_right_texcoord, int index_offset)
 {
 {
 	vertices[0].position = origin;
 	vertices[0].position = origin;
 	vertices[0].colour = colour;
 	vertices[0].colour = colour;
@@ -77,7 +78,7 @@ void GeometryUtilities::GenerateQuad(Vertex* vertices, int* indices, const Vecto
 }
 }
 
 
 // Generates the geometry required to render a line above, below or through a line of text.
 // Generates the geometry required to render a line above, below or through a line of text.
-void GeometryUtilities::GenerateLine(FontFaceHandle font_face_handle, Geometry* geometry, const Vector2f& position, int width, Style::TextDecoration height, const Colourb& colour)
+void GeometryUtilities::GenerateLine(FontFaceHandle font_face_handle, Geometry* geometry, Vector2f position, int width, Style::TextDecoration height, Colourb colour)
 {
 {
 	Vector< Vertex >& line_vertices = geometry->GetVertices();
 	Vector< Vertex >& line_vertices = geometry->GetVertices();
 	Vector< int >& line_indices = geometry->GetIndices();
 	Vector< int >& line_indices = geometry->GetIndices();
@@ -107,4 +108,13 @@ void GeometryUtilities::GenerateLine(FontFaceHandle font_face_handle, Geometry*
 									);
 									);
 }
 }
 
 
+void GeometryUtilities::GenerateBackgroundBorder(Geometry* geometry, const Box& box, Vector4f border_radius, Colourb background_colour, const Colourb* border_colours)
+{
+	Vector<Vertex>& vertices = geometry->GetVertices();
+	Vector<int>& indices = geometry->GetIndices();
+
+	CornerSizes corner_sizes{ border_radius.x, border_radius.y, border_radius.z, border_radius.w };
+	GeometryBackgroundBorder::Draw(vertices, indices, corner_sizes, box, background_colour, border_colours);
+}
+
 } // namespace Rml
 } // namespace Rml

+ 6 - 0
Source/Core/StyleSheetSpecification.cpp

@@ -302,6 +302,12 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 	RegisterShorthand(ShorthandId::BorderLeft, "border-left", "border-left-width, border-left-color", ShorthandType::FallThrough);
 	RegisterShorthand(ShorthandId::BorderLeft, "border-left", "border-left-width, border-left-color", ShorthandType::FallThrough);
 	RegisterShorthand(ShorthandId::Border, "border", "border-top, border-right, border-bottom, border-left", ShorthandType::RecursiveRepeat);
 	RegisterShorthand(ShorthandId::Border, "border", "border-top, border-right, border-bottom, border-left", ShorthandType::RecursiveRepeat);
 
 
+	RegisterProperty(PropertyId::BorderTopLeftRadius, "border-top-left-radius", "0px", false, false).AddParser("length");
+	RegisterProperty(PropertyId::BorderTopRightRadius, "border-top-right-radius", "0px", false, false).AddParser("length");
+	RegisterProperty(PropertyId::BorderBottomRightRadius, "border-bottom-right-radius", "0px", false, false).AddParser("length");
+	RegisterProperty(PropertyId::BorderBottomLeftRadius, "border-bottom-left-radius", "0px", false, false).AddParser("length");
+	RegisterShorthand(ShorthandId::BorderRadius, "border-radius", "border-top-left-radius, border-top-right-radius, border-bottom-right-radius, border-bottom-left-radius", ShorthandType::Box);
+
 	RegisterProperty(PropertyId::Display, "display", "inline", false, true).AddParser("keyword", "none, block, inline, inline-block");
 	RegisterProperty(PropertyId::Display, "display", "inline", false, true).AddParser("keyword", "none, block, inline, inline-block");
 	RegisterProperty(PropertyId::Position, "position", "static", false, true).AddParser("keyword", "static, relative, absolute, fixed");
 	RegisterProperty(PropertyId::Position, "position", "static", false, true).AddParser("keyword", "static, relative, absolute, fixed");
 	RegisterProperty(PropertyId::Top, "top", "auto", false, false)
 	RegisterProperty(PropertyId::Top, "top", "auto", false, false)

+ 53 - 0
Tests/Data/VisualTests/border_radius.rml

@@ -0,0 +1,53 @@
+<rml>
+<head>
+    <title>Border-radius</title>
+    <link type="text/rcss" href="../style.rcss"/>
+	<link rel="help" href="https://drafts.csswg.org/css-backgrounds-3/#border-radius" />
+	<meta name="Description" content="Border-radius property" />
+	<style>
+		body {
+			background: #ddd;
+			color: #444;
+		}
+		div {
+			margin: 20px auto;
+			width: 200px;
+			height: 120px;
+			background: #c3c3c3;
+			border-color: #55f #f57 #55f #afa;
+		}
+		.thin {
+			border-width: 10px 5px 25px 20px;
+			border-radius: 80px 30px;
+		}
+		.thick {
+			border-width: 40px 20px;
+			border-radius: 30px;
+		}
+		.arc {
+			width: 80px;
+			height: 80px;
+			border-width: 40px;
+			border-color: #ff8400 #ff8400 #ffd34f #ffd34f;
+			border-radius: 0 150px;
+		}
+		.split {
+			line-height: 2.5;
+		}
+		.split span {
+			background: #c3c3c3;
+			border-color: #55f #f57 #55f #afa;
+			border-width: 5px;
+			border-radius: 8px;
+		}
+	</style>
+</head>
+
+<body>
+<p>The following boxes should produce curved borders. There should be no overlapping triangles, and their backgrounds should not be visible outside their borders.</p>
+<div class="thin"/>
+<div class="thick"/>
+<div class="arc"/>
+<p class="split">The following text <span>should wrap down to the<br/> next line</span> and produce borders on each line.</p>
+</body>
+</rml>