Kaynağa Gözat

Refactor Property units and Box enums, and introduce NumericValue [breaking change]

- Split out the Unit enum from Property, use enum class for strong type checking.
- Split out Box enums for strong type checking, allow use of BoxArea in other contexts.
- Refactor computing and resolving properties using NumericValue.
Michael Ragazzon 2 yıl önce
ebeveyn
işleme
ce949d2d5b
89 değiştirilmiş dosya ile 1030 ekleme ve 894 silme
  1. 2 0
      CMake/FileList.cmake
  2. 2 0
      Include/RmlUi/Core.h
  3. 18 24
      Include/RmlUi/Core/Box.h
  4. 13 13
      Include/RmlUi/Core/Element.h
  5. 56 0
      Include/RmlUi/Core/NumericValue.h
  6. 6 55
      Include/RmlUi/Core/Property.h
  7. 17 24
      Include/RmlUi/Core/TransformPrimitive.h
  8. 9 1
      Include/RmlUi/Core/TypeConverter.h
  9. 2 2
      Include/RmlUi/Core/Types.h
  10. 113 0
      Include/RmlUi/Core/Unit.h
  11. 2 2
      Include/RmlUi/Core/Variant.h
  12. 22 22
      Samples/basic/animation/src/main.cpp
  13. 1 1
      Samples/basic/demo/src/main.cpp
  14. 2 2
      Samples/basic/drag/src/Inventory.cpp
  15. 2 2
      Samples/basic/transform/src/main.cpp
  16. 2 2
      Samples/invaders/src/DecoratorDefender.cpp
  17. 1 1
      Samples/invaders/src/DecoratorStarfield.cpp
  18. 2 2
      Samples/luainvaders/src/DecoratorDefender.cpp
  19. 1 1
      Samples/luainvaders/src/DecoratorStarfield.cpp
  20. 2 2
      Samples/tutorial/drag/src/Inventory.cpp
  21. 32 29
      Source/Core/Box.cpp
  22. 76 105
      Source/Core/ComputeProperty.cpp
  23. 8 7
      Source/Core/ComputeProperty.h
  24. 8 3
      Source/Core/ComputedValues.cpp
  25. 11 11
      Source/Core/Context.cpp
  26. 3 3
      Source/Core/DecoratorGradient.cpp
  27. 1 1
      Source/Core/DecoratorGradient.h
  28. 11 11
      Source/Core/DecoratorNinePatch.cpp
  29. 3 3
      Source/Core/DecoratorNinePatch.h
  30. 2 2
      Source/Core/DecoratorTiledBox.cpp
  31. 2 2
      Source/Core/DecoratorTiledHorizontal.cpp
  32. 2 2
      Source/Core/DecoratorTiledImage.cpp
  33. 3 3
      Source/Core/DecoratorTiledInstancer.cpp
  34. 2 2
      Source/Core/DecoratorTiledVertical.cpp
  35. 40 47
      Source/Core/Element.cpp
  36. 18 19
      Source/Core/ElementAnimation.cpp
  37. 1 1
      Source/Core/ElementBackgroundBorder.cpp
  38. 1 1
      Source/Core/ElementDecoration.cpp
  39. 6 6
      Source/Core/ElementDocument.cpp
  40. 15 15
      Source/Core/ElementHandle.cpp
  41. 13 11
      Source/Core/ElementScroll.cpp
  42. 60 51
      Source/Core/ElementStyle.cpp
  43. 6 6
      Source/Core/ElementStyle.h
  44. 6 6
      Source/Core/ElementUtilities.cpp
  45. 2 2
      Source/Core/Elements/ElementImage.cpp
  46. 2 2
      Source/Core/Elements/ElementProgress.cpp
  47. 15 15
      Source/Core/Elements/WidgetDropDown.cpp
  48. 42 34
      Source/Core/Elements/WidgetSlider.cpp
  49. 6 6
      Source/Core/Elements/WidgetTextInput.cpp
  50. 5 8
      Source/Core/GeometryBackgroundBorder.cpp
  51. 1 0
      Source/Core/GeometryBackgroundBorder.h
  52. 15 13
      Source/Core/Layout/BlockContainer.cpp
  53. 11 11
      Source/Core/Layout/ContainerBox.cpp
  54. 1 1
      Source/Core/Layout/FlexFormattingContext.cpp
  55. 12 10
      Source/Core/Layout/InlineBox.cpp
  56. 2 1
      Source/Core/Layout/InlineContainer.cpp
  57. 3 3
      Source/Core/Layout/InlineLevelBox.cpp
  58. 34 31
      Source/Core/Layout/LayoutDetails.cpp
  59. 16 12
      Source/Core/Layout/TableFormattingContext.cpp
  60. 12 1
      Source/Core/Property.cpp
  61. 7 23
      Source/Core/PropertyDefinition.cpp
  62. 4 4
      Source/Core/PropertyParserAnimation.cpp
  63. 1 1
      Source/Core/PropertyParserColour.cpp
  64. 2 2
      Source/Core/PropertyParserDecorator.cpp
  65. 2 2
      Source/Core/PropertyParserFontEffect.cpp
  66. 1 1
      Source/Core/PropertyParserKeyword.cpp
  67. 22 22
      Source/Core/PropertyParserNumber.cpp
  68. 3 3
      Source/Core/PropertyParserNumber.h
  69. 1 1
      Source/Core/PropertyParserRatio.cpp
  70. 1 1
      Source/Core/PropertyParserString.cpp
  71. 7 8
      Source/Core/PropertyParserTransform.cpp
  72. 1 6
      Source/Core/PropertyParserTransform.h
  73. 6 6
      Source/Core/StyleSheetContainer.cpp
  74. 9 9
      Source/Core/StyleSheetParser.cpp
  75. 6 6
      Source/Core/StyleSheetSpecification.cpp
  76. 1 1
      Source/Core/Transform.cpp
  77. 33 37
      Source/Core/TransformPrimitive.cpp
  78. 26 35
      Source/Core/TransformUtilities.cpp
  79. 31 0
      Source/Core/TypeConverter.cpp
  80. 28 26
      Source/Core/WidgetScroll.cpp
  81. 1 1
      Source/Debugger/DebuggerPlugin.cpp
  82. 1 1
      Source/Debugger/ElementContextHook.cpp
  83. 13 13
      Source/Debugger/ElementInfo.cpp
  84. 2 2
      Source/Lottie/ElementLottie.cpp
  85. 2 2
      Source/SVG/ElementSVG.cpp
  86. 3 3
      Tests/Source/Benchmarks/BackgroundBorder.cpp
  87. 23 23
      Tests/Source/UnitTests/Element.cpp
  88. 2 2
      Tests/Source/VisualTests/TestViewer.cpp
  89. 7 0
      changelog.md

+ 2 - 0
CMake/FileList.cmake

@@ -178,6 +178,7 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Math.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Matrix4.inl
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/NumericValue.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/ObserverPtr.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Platform.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Plugin.h
@@ -211,6 +212,7 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TypeConverter.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/TypeConverter.inl
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Types.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Unit.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/URL.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Utilities.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Variant.h

+ 2 - 0
Include/RmlUi/Core.h

@@ -65,6 +65,7 @@
 #include "Core/Input.h"
 #include "Core/Log.h"
 #include "Core/Math.h"
+#include "Core/NumericValue.h"
 #include "Core/Plugin.h"
 #include "Core/PropertiesIteratorView.h"
 #include "Core/Property.h"
@@ -87,6 +88,7 @@
 #include "Core/Tween.h"
 #include "Core/TypeConverter.h"
 #include "Core/Types.h"
+#include "Core/Unit.h"
 #include "Core/Vertex.h"
 #include "Core/XMLNodeHandler.h"
 #include "Core/XMLParser.h"

+ 18 - 24
Include/RmlUi/Core/Box.h

@@ -33,6 +33,9 @@
 
 namespace Rml {
 
+enum class BoxEdge { Top, Right, Bottom, Left };
+enum class BoxDirection { Vertical, Horizontal };
+
 /**
     Stores a box with four sized areas; content, padding, a border and margin. See
     http://www.w3.org/TR/REC-CSS2/box.html#box-dimensions for a diagram.
@@ -42,17 +45,8 @@ namespace Rml {
 
 class RMLUICORE_API Box {
 public:
-	enum Area {
-		MARGIN = 0,
-		BORDER = 1,
-		PADDING = 2,
-		CONTENT = 3,
-		NUM_AREAS = 3, // ignores CONTENT
-	};
-
-	enum Edge { TOP = 0, RIGHT = 1, BOTTOM = 2, LEFT = 3, NUM_EDGES = 4 };
-
-	enum Direction { VERTICAL = 0, HORIZONTAL = 1 };
+	static constexpr int num_areas = 3; // ignores content box
+	static constexpr int num_edges = 4;
 
 	/// Initialises a zero-sized box.
 	Box();
@@ -64,14 +58,14 @@ public:
 	/// means the position of the margin area is likely to be negative.
 	/// @param area[in] The desired area.
 	/// @return The position of the area.
-	Vector2f GetPosition(Area area = Box::CONTENT) const;
+	Vector2f GetPosition(BoxArea area = BoxArea::Content) const;
 	/// Returns the size of the box's content area.
 	/// @return The size of the content area.
 	Vector2f GetSize() const;
 	/// Returns the size of one of the box's areas. This will include all inner areas.
 	/// @param area[in] The desired area.
 	/// @return The size of the requested area.
-	Vector2f GetSize(Area area) const;
+	Vector2f GetSize(BoxArea area) const;
 
 	/// Sets the size of the content area.
 	/// @param content[in] The size of the new content area.
@@ -80,30 +74,30 @@ public:
 	/// @param area[in] The area to change.
 	/// @param edge[in] The area edge to change.
 	/// @param size[in] The new size of the area segment.
-	void SetEdge(Area area, Edge edge, float size);
+	void SetEdge(BoxArea area, BoxEdge edge, float size);
 
 	/// Returns the size of one of the area edges.
 	/// @param area[in] The desired area.
 	/// @param edge[in] The desired edge.
 	/// @return The size of the requested area edge.
-	float GetEdge(Area area, Edge edge) const;
+	float GetEdge(BoxArea area, BoxEdge edge) const;
 	/// Returns the cumulative size of one edge up to one of the box's areas.
-	/// @param area[in] The area to measure up to (and including). So, MARGIN will return the width of the margin, and PADDING will be the sum of the
+	/// @param area[in] The area to measure up to (and including). So, Margin will return the width of the margin, and Padding will be the sum of the
 	/// margin, border and padding.
 	/// @param edge[in] The desired edge.
 	/// @return The cumulative size of the edge.
-	float GetCumulativeEdge(Area area, Edge edge) const;
+	float GetCumulativeEdge(BoxArea area, BoxEdge edge) const;
 
-	/// Returns the size along a single direction starting at 'area_outer', up-to and including 'area_inner'.
-	/// @example GetSizeAcross(HORIZONTAL, BORDER, PADDING) returns the total width of the horizontal borders and paddings.
+	/// Returns the size along a single direction of the given 'area', including all inner areas up-to and including 'area_end'.
+	/// @example GetSizeAcross(Horizontal, Border, Padding) returns the total width of the horizontal borders and paddings.
 	/// @param direction The desired direction.
-	/// @param area_outer The widest area to include.
-	/// @param area_inner The last area to include, anything inside this is excluded.
-	float GetSizeAcross(Direction direction, Area area_outer, Area area_inner = Area::CONTENT) const;
+	/// @param area The widest area to include.
+	/// @param area_end The last area to include, anything inside this is excluded.
+	float GetSizeAcross(BoxDirection direction, BoxArea area, BoxArea area_end = BoxArea::Content) const;
 
 	/// Returns the size of the frame defined by the given area, not including inner areas.
 	/// @param area The area to use.
-	Vector2f GetFrameSize(Area area) const;
+	Vector2f GetFrameSize(BoxArea area) const;
 
 	/// Compares the size of the content area and the other area edges.
 	/// @return True if the boxes represent the same area.
@@ -114,7 +108,7 @@ public:
 
 private:
 	Vector2f content;
-	float area_edges[NUM_AREAS][NUM_EDGES] = {};
+	float area_edges[num_areas][num_edges] = {};
 };
 
 } // namespace Rml

+ 13 - 13
Include/RmlUi/Core/Element.h

@@ -121,19 +121,19 @@ public:
 	/// offset parent's top-left border corner.
 	/// @param[in] area The desired area position.
 	/// @return The relative offset.
-	Vector2f GetRelativeOffset(Box::Area area = Box::CONTENT);
+	Vector2f GetRelativeOffset(BoxArea area = BoxArea::Content);
 	/// Returns the position of the top-left corner of one of the areas of this element's primary box, relative to
 	/// the element root.
 	/// @param[in] area The desired area position.
 	/// @return The absolute offset.
-	Vector2f GetAbsoluteOffset(Box::Area area = Box::CONTENT);
+	Vector2f GetAbsoluteOffset(BoxArea area = BoxArea::Content);
 
 	/// Sets an alternate area to use as the client area.
 	/// @param[in] client_area The box area to use as the element's client area.
-	void SetClientArea(Box::Area client_area);
+	void SetClientArea(BoxArea client_area);
 	/// Returns the area the element uses as its client area.
 	/// @return The box area used as the element's client area.
-	Box::Area GetClientArea() const;
+	BoxArea GetClientArea() const;
 
 	/// Sets the dimensions of the element's scrollable overflow rectangle. This is the tightest fitting box surrounding
 	/// all of this element's logical children, and the element's padding box.
@@ -226,17 +226,17 @@ public:
 	/// @return The local properties for this element, or nullptr if no properties defined
 	const PropertyMap& GetLocalStyleProperties();
 
-	/// Resolves a property with units of number, percentage, length, or angle to their canonical unit (unit-less, 'px', or 'rad').
+	/// Resolves a length to its canonical unit ('px').
+	/// @param[in] value The numeric value.
+	/// @return The resolved value in their canonical unit, or zero if it could not be resolved.
+	/// @note Font-relative and context-relative units will be resolved against this element's computed values and its context.
+	float ResolveLength(NumericValue value);
+	/// Resolves a numeric value with units of number, percentage, length, or angle to their canonical unit (unit-less, 'px', or 'rad').
 	/// Numbers and percentages are scaled by the base value and returned.
-	/// @param[in] property The property to resolve the value for.
+	/// @param[in] value The value to be resolved.
 	/// @param[in] base_value The value that is scaled by the number or percentage value, if applicable.
 	/// @return The resolved value in their canonical unit, or zero if it could not be resolved.
-	float ResolveNumericProperty(const Property* property, float base_value);
-	/// Resolves a property with units of number, percentage, length, or angle to their canonical unit (unit-less, 'px', or 'rad').
-	/// Numbers and percentages are scaled according to the relative target of the property definition.
-	/// @param[in] name The property to resolve the value for.
-	/// @return The resolved value in their canonical unit, or zero if it could not be resolved.
-	float ResolveNumericProperty(const String& property_name);
+	float ResolveNumericValue(NumericValue value, float base_value);
 
 	/// Returns the size of the containing block. Often percentages are scaled relative to this.
 	Vector2f GetContainingBlock();
@@ -728,7 +728,7 @@ private:
 	int num_non_dom_children;
 
 	// Defines what box area represents the element's client area; this is usually padding, but may be content.
-	Box::Area client_area;
+	BoxArea client_area;
 
 	// Original tag this element came from.
 	String tag;

+ 56 - 0
Include/RmlUi/Core/NumericValue.h

@@ -0,0 +1,56 @@
+/*
+ * 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_NUMERICVALUE_H
+#define RMLUI_CORE_NUMERICVALUE_H
+
+#include "Unit.h"
+
+namespace Rml {
+
+/**
+    A numeric value is a number combined with a unit.
+ */
+struct NumericValue {
+	NumericValue() noexcept : number(0.f), unit(Unit::UNKNOWN) {}
+	NumericValue(float number, Unit unit) noexcept : number(number), unit(unit) {}
+	float number;
+	Unit unit;
+};
+inline bool operator==(const NumericValue& a, const NumericValue& b)
+{
+	return a.number == b.number && a.unit == b.unit;
+}
+inline bool operator!=(const NumericValue& a, const NumericValue& b)
+{
+	return !(a == b);
+}
+
+} // namespace Rml
+
+#endif

+ 6 - 55
Include/RmlUi/Core/Property.h

@@ -30,6 +30,8 @@
 #define RMLUI_CORE_PROPERTY_H
 
 #include "Header.h"
+#include "NumericValue.h"
+#include "Unit.h"
 #include "Variant.h"
 #include <type_traits>
 
@@ -51,53 +53,6 @@ struct RMLUICORE_API PropertySource {
 
 class RMLUICORE_API Property {
 public:
-	enum Unit {
-		UNKNOWN = 1 << 0,
-
-		KEYWORD = 1 << 1, // generic keyword; fetch as < int >
-
-		STRING = 1 << 2,  // generic string; fetch as < String >
-
-		// Absolute values.
-		NUMBER = 1 << 3, // number unsuffixed; fetch as < float >
-		PX = 1 << 4,     // number suffixed by 'px'; fetch as < float >
-		DEG = 1 << 5,    // number suffixed by 'deg'; fetch as < float >
-		RAD = 1 << 6,    // number suffixed by 'rad'; fetch as < float >
-		COLOUR = 1 << 7, // colour; fetch as < Colourb >
-		DP = 1 << 8,     // density-independent pixel; number suffixed by 'dp'; fetch as < float >
-		X = 1 << 9,      // dots per px unit; number suffixed by 'x'; fetch as < float >
-		VW = 1 << 10,    // viewport-width percentage; number suffixed by 'vw'; fetch as < float >
-		VH = 1 << 11,    // viewport-height percentage; number suffixed by 'vh'; fetch as < float >
-		ABSOLUTE_UNIT = NUMBER | PX | DP | X | DEG | RAD | COLOUR | VW | VH,
-
-		// Relative values.
-		EM = 1 << 12,      // number suffixed by 'em'; fetch as < float >
-		PERCENT = 1 << 13, // number suffixed by '%'; fetch as < float >
-		REM = 1 << 14,     // number suffixed by 'rem'; fetch as < float >
-		RELATIVE_UNIT = EM | REM | PERCENT,
-
-		// Values based on pixels-per-inch.
-		INCH = 1 << 15, // number suffixed by 'in'; fetch as < float >
-		CM = 1 << 16,   // number suffixed by 'cm'; fetch as < float >
-		MM = 1 << 17,   // number suffixed by 'mm'; fetch as < float >
-		PT = 1 << 18,   // number suffixed by 'pt'; fetch as < float >
-		PC = 1 << 19,   // number suffixed by 'pc'; fetch as < float >
-		PPI_UNIT = INCH | CM | MM | PT | PC,
-
-		TRANSFORM = 1 << 20,  // transform; fetch as < TransformPtr >, may be empty
-		TRANSITION = 1 << 21, // transition; fetch as < TransitionList >
-		ANIMATION = 1 << 22,  // animation; fetch as < AnimationList >
-		DECORATOR = 1 << 23,  // decorator; fetch as < DecoratorsPtr >
-		FONTEFFECT = 1 << 24, // font-effect; fetch as < FontEffectsPtr >
-		RATIO = 1 << 25,      // ratio defined as x/y; fetch as < Vector2f >
-
-		LENGTH = PX | DP | PPI_UNIT | EM | REM | VW | VH | X,
-		LENGTH_PERCENT = LENGTH | PERCENT,
-		NUMBER_LENGTH_PERCENT = NUMBER | LENGTH | PERCENT,
-		ABSOLUTE_LENGTH = PX | DP | PPI_UNIT | VH | VW | X,
-		ANGLE = DEG | RAD
-	};
-
 	Property();
 	template <typename PropertyType>
 	Property(PropertyType value, Unit unit, int specificity = -1) : value(value), unit(unit), specificity(specificity)
@@ -106,12 +61,15 @@ public:
 		parser_index = -1;
 	}
 	template <typename EnumType, typename = typename std::enable_if<std::is_enum<EnumType>::value, EnumType>::type>
-	Property(EnumType value) : value(static_cast<int>(value)), unit(KEYWORD), specificity(-1)
+	Property(EnumType value) : value(static_cast<int>(value)), unit(Unit::KEYWORD), specificity(-1)
 	{}
 
 	/// Get the value of the property as a string.
 	String ToString() const;
 
+	/// Get the value of the property as a numeric value, if applicable.
+	NumericValue GetNumericValue() const;
+
 	/// Templatised accessor.
 	template <typename T>
 	T Get() const
@@ -132,12 +90,5 @@ public:
 	SharedPtr<const PropertySource> source;
 };
 
-// OR operator for combining multiple units where applicable.
-inline Property::Unit operator|(Property::Unit lhs, Property::Unit rhs)
-{
-	using underlying_t = std::underlying_type<Property::Unit>::type;
-	return static_cast<Property::Unit>(static_cast<underlying_t>(lhs) | static_cast<underlying_t>(rhs));
-}
-
 } // namespace Rml
 #endif

+ 17 - 24
Include/RmlUi/Core/TransformPrimitive.h

@@ -30,20 +30,13 @@
 #define RMLUI_CORE_TRANSFORMPRIMITIVE_H
 
 #include "Header.h"
-#include "Property.h"
+#include "NumericValue.h"
 #include "Types.h"
+#include "Unit.h"
 
 namespace Rml {
 namespace Transforms {
 
-	struct RMLUICORE_API NumericValue {
-		NumericValue() noexcept : number(0.f), unit(Property::UNKNOWN) {}
-		NumericValue(float number, Property::Unit unit) noexcept : number(number), unit(unit) {}
-
-		float number;
-		Property::Unit unit;
-	};
-
 	// A resolved primitive has values that are always independent of an element's properties or layout.
 	template <size_t N>
 	struct RMLUICORE_API ResolvedPrimitive {
@@ -52,8 +45,8 @@ namespace Transforms {
 	protected:
 		ResolvedPrimitive(const float* values) noexcept;
 		ResolvedPrimitive(const NumericValue* values) noexcept;
-		ResolvedPrimitive(const NumericValue* values, Array<Property::Unit, N> base_units) noexcept;
-		ResolvedPrimitive(Array<NumericValue, N> values, Array<Property::Unit, N> base_units) noexcept;
+		ResolvedPrimitive(const NumericValue* values, Array<Unit, N> base_units) noexcept;
+		ResolvedPrimitive(Array<NumericValue, N> values, Array<Unit, N> base_units) noexcept;
 		ResolvedPrimitive(Array<float, N> values) noexcept;
 	};
 
@@ -78,28 +71,28 @@ namespace Transforms {
 
 	struct RMLUICORE_API TranslateX : public UnresolvedPrimitive<1> {
 		TranslateX(const NumericValue* values) noexcept;
-		TranslateX(float x, Property::Unit unit = Property::PX) noexcept;
+		TranslateX(float x, Unit unit = Unit::PX) noexcept;
 	};
 
 	struct RMLUICORE_API TranslateY : public UnresolvedPrimitive<1> {
 		TranslateY(const NumericValue* values) noexcept;
-		TranslateY(float y, Property::Unit unit = Property::PX) noexcept;
+		TranslateY(float y, Unit unit = Unit::PX) noexcept;
 	};
 
 	struct RMLUICORE_API TranslateZ : public UnresolvedPrimitive<1> {
 		TranslateZ(const NumericValue* values) noexcept;
-		TranslateZ(float z, Property::Unit unit = Property::PX) noexcept;
+		TranslateZ(float z, Unit unit = Unit::PX) noexcept;
 	};
 
 	struct RMLUICORE_API Translate2D : public UnresolvedPrimitive<2> {
 		Translate2D(const NumericValue* values) noexcept;
-		Translate2D(float x, float y, Property::Unit units = Property::PX) noexcept;
+		Translate2D(float x, float y, Unit units = Unit::PX) noexcept;
 	};
 
 	struct RMLUICORE_API Translate3D : public UnresolvedPrimitive<3> {
 		Translate3D(const NumericValue* values) noexcept;
 		Translate3D(NumericValue x, NumericValue y, NumericValue z) noexcept;
-		Translate3D(float x, float y, float z, Property::Unit units = Property::PX) noexcept;
+		Translate3D(float x, float y, float z, Unit units = Unit::PX) noexcept;
 	};
 
 	struct RMLUICORE_API ScaleX : public ResolvedPrimitive<1> {
@@ -131,42 +124,42 @@ namespace Transforms {
 
 	struct RMLUICORE_API RotateX : public ResolvedPrimitive<1> {
 		RotateX(const NumericValue* values) noexcept;
-		RotateX(float angle, Property::Unit unit = Property::DEG) noexcept;
+		RotateX(float angle, Unit unit = Unit::DEG) noexcept;
 	};
 
 	struct RMLUICORE_API RotateY : public ResolvedPrimitive<1> {
 		RotateY(const NumericValue* values) noexcept;
-		RotateY(float angle, Property::Unit unit = Property::DEG) noexcept;
+		RotateY(float angle, Unit unit = Unit::DEG) noexcept;
 	};
 
 	struct RMLUICORE_API RotateZ : public ResolvedPrimitive<1> {
 		RotateZ(const NumericValue* values) noexcept;
-		RotateZ(float angle, Property::Unit unit = Property::DEG) noexcept;
+		RotateZ(float angle, Unit unit = Unit::DEG) noexcept;
 	};
 
 	struct RMLUICORE_API Rotate2D : public ResolvedPrimitive<1> {
 		Rotate2D(const NumericValue* values) noexcept;
-		Rotate2D(float angle, Property::Unit unit = Property::DEG) noexcept;
+		Rotate2D(float angle, Unit unit = Unit::DEG) noexcept;
 	};
 
 	struct RMLUICORE_API Rotate3D : public ResolvedPrimitive<4> {
 		Rotate3D(const NumericValue* values) noexcept;
-		Rotate3D(float x, float y, float z, float angle, Property::Unit angle_unit = Property::DEG) noexcept;
+		Rotate3D(float x, float y, float z, float angle, Unit angle_unit = Unit::DEG) noexcept;
 	};
 
 	struct RMLUICORE_API SkewX : public ResolvedPrimitive<1> {
 		SkewX(const NumericValue* values) noexcept;
-		SkewX(float angle, Property::Unit unit = Property::DEG) noexcept;
+		SkewX(float angle, Unit unit = Unit::DEG) noexcept;
 	};
 
 	struct RMLUICORE_API SkewY : public ResolvedPrimitive<1> {
 		SkewY(const NumericValue* values) noexcept;
-		SkewY(float angle, Property::Unit unit = Property::DEG) noexcept;
+		SkewY(float angle, Unit unit = Unit::DEG) noexcept;
 	};
 
 	struct RMLUICORE_API Skew2D : public ResolvedPrimitive<2> {
 		Skew2D(const NumericValue* values) noexcept;
-		Skew2D(float x, float y, Property::Unit unit = Property::DEG) noexcept;
+		Skew2D(float x, float y, Unit unit = Unit::DEG) noexcept;
 	};
 
 	struct RMLUICORE_API Perspective : public UnresolvedPrimitive<1> {

+ 9 - 1
Include/RmlUi/Core/TypeConverter.h

@@ -38,6 +38,8 @@
 
 namespace Rml {
 
+enum class Unit;
+
 /**
     Templatised TypeConverters with Template Specialisation.
 
@@ -45,7 +47,7 @@ namespace Rml {
     They're mainly useful in things like dictionaries and serialisers.
 
     @author Lloyd Weehuizen
- */
+*/
 
 template <typename SourceType, typename DestType>
 class TypeConverter {
@@ -71,6 +73,12 @@ inline T FromString(const String& string, T default_value = T())
 
 // Some more complex types are defined in cpp-file
 
+template <>
+class TypeConverter<Unit, String> {
+public:
+	RMLUICORE_API static bool Convert(const Unit& src, String& dest);
+};
+
 template <>
 class TypeConverter<TransformPtr, TransformPtr> {
 public:

+ 2 - 2
Include/RmlUi/Core/Types.h

@@ -41,8 +41,8 @@ using byte = unsigned char;
 using ScriptObject = void*;
 using std::size_t;
 
-// Unicode code point
-enum class Character : char32_t { Null, Replacement = 0xfffd };
+enum class Character : char32_t { Null, Replacement = 0xfffd }; // Unicode code point
+enum class BoxArea { Margin, Border, Padding, Content, Auto };
 
 } // namespace Rml
 

+ 113 - 0
Include/RmlUi/Core/Unit.h

@@ -0,0 +1,113 @@
+/*
+ * 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_UNIT_H
+#define RMLUI_CORE_UNIT_H
+
+#include "Header.h"
+#include <type_traits>
+
+namespace Rml {
+
+enum class Unit {
+	UNKNOWN = 0,
+
+	// Basic types.
+	KEYWORD = 1 << 0, // generic keyword; fetch as <int>
+	STRING = 1 << 1,  // generic string; fetch as <String>
+	COLOUR = 1 << 2,  // colour; fetch as <Colourb>
+	RATIO = 1 << 3,   // ratio defined as x/y; fetch as <Vector2f>
+
+	// Numeric values.
+	NUMBER = 1 << 4,  // number unsuffixed; fetch as <float>
+	PERCENT = 1 << 5, // number suffixed by '%'; fetch as <float>
+	PX = 1 << 6,      // number suffixed by 'px'; fetch as <float>
+
+	// Context-relative values.
+	DP = 1 << 7, // density-independent pixel; number suffixed by 'dp'; fetch as <float>
+	VW = 1 << 8, // viewport-width percentage; number suffixed by 'vw'; fetch as <float>
+	VH = 1 << 9, // viewport-height percentage; number suffixed by 'vh'; fetch as <float>
+	X = 1 << 10, // dots per px unit; number suffixed by 'x'; fetch as <float>
+
+	// Font-relative values.
+	EM = 1 << 11,  // number suffixed by 'em'; fetch as <float>
+	REM = 1 << 12, // number suffixed by 'rem'; fetch as <float>
+
+	// Values based on pixels-per-inch.
+	INCH = 1 << 13, // number suffixed by 'in'; fetch as <float>
+	CM = 1 << 14,   // number suffixed by 'cm'; fetch as <float>
+	MM = 1 << 15,   // number suffixed by 'mm'; fetch as <float>
+	PT = 1 << 16,   // number suffixed by 'pt'; fetch as <float>
+	PC = 1 << 17,   // number suffixed by 'pc'; fetch as <float>
+	PPI_UNIT = INCH | CM | MM | PT | PC,
+
+	// Angles.
+	DEG = 1 << 18, // number suffixed by 'deg'; fetch as <float>
+	RAD = 1 << 19, // number suffixed by 'rad'; fetch as <float>
+
+	// Values tied to specific types.
+	TRANSFORM = 1 << 20,     // transform; fetch as <TransformPtr>, may be empty
+	TRANSITION = 1 << 21,    // transition; fetch as <TransitionList>
+	ANIMATION = 1 << 22,     // animation; fetch as <AnimationList>
+	DECORATOR = 1 << 23,     // decorator; fetch as <DecoratorsPtr>
+	FONTEFFECT = 1 << 24,    // font-effect; fetch as <FontEffectsPtr>
+	COLORSTOPLIST = 1 << 25, // color stop list; fetch as <ColorStopList>
+	SHADOWLIST = 1 << 26,    // shadow list; fetch as <ShadowList>
+
+	LENGTH = PX | DP | VW | VH | EM | REM | PPI_UNIT,
+	LENGTH_PERCENT = LENGTH | PERCENT,
+	NUMBER_LENGTH_PERCENT = NUMBER | LENGTH | PERCENT,
+	ABSOLUTE_LENGTH = PX | PPI_UNIT,
+	ANGLE = DEG | RAD,
+	NUMERIC = NUMBER_LENGTH_PERCENT | ANGLE | X
+};
+
+// Type alias hint for indicating that the value can contain multiple units OR-ed together.
+using Units = Unit;
+
+// OR operator for combining multiple units where applicable.
+inline Units operator|(Units lhs, Units rhs)
+{
+	using underlying_t = std::underlying_type<Units>::type;
+	return static_cast<Units>(static_cast<underlying_t>(lhs) | static_cast<underlying_t>(rhs));
+}
+// AND operator for combining multiple units where applicable.
+inline Units operator&(Units lhs, Units rhs)
+{
+	using underlying_t = std::underlying_type<Units>::type;
+	return static_cast<Units>(static_cast<underlying_t>(lhs) & static_cast<underlying_t>(rhs));
+}
+
+// Returns true if any unit is set.
+inline bool Any(Units units)
+{
+	return units != Unit::UNKNOWN;
+}
+
+} // namespace Rml
+#endif

+ 2 - 2
Include/RmlUi/Core/Variant.h

@@ -26,8 +26,8 @@
  *
  */
 
-#ifndef RMLUIVARIANT_H
-#define RMLUIVARIANT_H
+#ifndef RMLUI_CORE_VARIANT_H
+#define RMLUI_CORE_VARIANT_H
 
 #include "Animation.h"
 #include "Header.h"

+ 22 - 22
Samples/basic/animation/src/main.cpp

@@ -53,16 +53,16 @@ public:
 			}
 			{
 				auto el = document->GetElementById("high_scores");
-				el->Animate("margin-left", Property(0.f, Property::PX), 0.3f, Tween{Tween::Sine, Tween::In}, 10, true, 1.f);
-				el->AddAnimationKey("margin-left", Property(100.f, Property::PX), 3.0f, Tween{Tween::Circular, Tween::Out});
+				el->Animate("margin-left", Property(0.f, Unit::PX), 0.3f, Tween{Tween::Sine, Tween::In}, 10, true, 1.f);
+				el->AddAnimationKey("margin-left", Property(100.f, Unit::PX), 3.0f, Tween{Tween::Circular, Tween::Out});
 			}
 			{
 				auto el = document->GetElementById("options");
-				el->Animate("image-color", Property(Colourb(128, 255, 255, 255), Property::COLOUR), 0.3f, Tween{}, -1, false);
-				el->AddAnimationKey("image-color", Property(Colourb(128, 128, 255, 255), Property::COLOUR), 0.3f);
-				el->AddAnimationKey("image-color", Property(Colourb(0, 128, 128, 255), Property::COLOUR), 0.3f);
-				el->AddAnimationKey("image-color", Property(Colourb(64, 128, 255, 0), Property::COLOUR), 0.9f);
-				el->AddAnimationKey("image-color", Property(Colourb(255, 255, 255, 255), Property::COLOUR), 0.3f);
+				el->Animate("image-color", Property(Colourb(128, 255, 255, 255), Unit::COLOUR), 0.3f, Tween{}, -1, false);
+				el->AddAnimationKey("image-color", Property(Colourb(128, 128, 255, 255), Unit::COLOUR), 0.3f);
+				el->AddAnimationKey("image-color", Property(Colourb(0, 128, 128, 255), Unit::COLOUR), 0.3f);
+				el->AddAnimationKey("image-color", Property(Colourb(64, 128, 255, 0), Unit::COLOUR), 0.9f);
+				el->AddAnimationKey("image-color", Property(Colourb(255, 255, 255, 255), Unit::COLOUR), 0.3f);
 			}
 			{
 				auto el = document->GetElementById("exit");
@@ -75,34 +75,34 @@ public:
 			{
 				auto el = document->GetElementById("generic");
 				auto p = Transform::MakeProperty(
-					{Transforms::TranslateY{50, Property::PX}, Transforms::Rotate3D{0, 0, 1, -90, Property::DEG}, Transforms::ScaleY{0.8f}});
+					{Transforms::TranslateY{50, Unit::PX}, Transforms::Rotate3D{0, 0, 1, -90, Unit::DEG}, Transforms::ScaleY{0.8f}});
 				el->Animate("transform", p, 1.5f, Tween{Tween::Sine, Tween::InOut}, -1, true);
 			}
 			{
 				auto el = document->GetElementById("combine");
-				auto p = Transform::MakeProperty({Transforms::Translate2D{50, 50, Property::PX}, Transforms::Rotate2D(1215)});
+				auto p = Transform::MakeProperty({Transforms::Translate2D{50, 50, Unit::PX}, Transforms::Rotate2D(1215)});
 				el->Animate("transform", p, 8.0f, Tween{}, -1, true);
 			}
 			{
 				auto el = document->GetElementById("decomposition");
-				auto p = Transform::MakeProperty({Transforms::TranslateY{50, Property::PX}, Transforms::Rotate3D{0.8f, 0, 1, 110, Property::DEG}});
+				auto p = Transform::MakeProperty({Transforms::TranslateY{50, Unit::PX}, Transforms::Rotate3D{0.8f, 0, 1, 110, Unit::DEG}});
 				el->Animate("transform", p, 1.3f, Tween{Tween::Quadratic, Tween::InOut}, -1, true);
 			}
 
 			// Mixed units tests
 			{
 				auto el = document->GetElementById("abs_rel");
-				el->Animate("margin-left", Property(50.f, Property::PERCENT), 1.5f, Tween{}, -1, true);
+				el->Animate("margin-left", Property(50.f, Unit::PERCENT), 1.5f, Tween{}, -1, true);
 			}
 			{
 				auto el = document->GetElementById("abs_rel_transform");
-				auto p = Transform::MakeProperty({Transforms::TranslateX{0, Property::PX}});
+				auto p = Transform::MakeProperty({Transforms::TranslateX{0, Unit::PX}});
 				el->Animate("transform", p, 1.5f, Tween{}, -1, true);
 			}
 			{
 				auto el = document->GetElementById("animation_event");
-				el->Animate("top", Property(Math::RandomReal(250.f), Property::PX), 1.5f, Tween{Tween::Cubic, Tween::InOut});
-				el->Animate("left", Property(Math::RandomReal(250.f), Property::PX), 1.5f, Tween{Tween::Cubic, Tween::InOut});
+				el->Animate("top", Property(Math::RandomReal(250.f), Unit::PX), 1.5f, Tween{Tween::Cubic, Tween::InOut});
+				el->Animate("left", Property(Math::RandomReal(250.f), Unit::PX), 1.5f, Tween{Tween::Cubic, Tween::InOut});
 			}
 
 			document->Show();
@@ -199,26 +199,26 @@ public:
 			{
 				auto el = context->GetRootElement()->GetElementById("keyevent_response");
 				if (el)
-					el->Animate("left", Property{-200.f, Property::DP}, 0.5, Tween{Tween::Cubic});
+					el->Animate("left", Property{-200.f, Unit::DP}, 0.5, Tween{Tween::Cubic});
 			}
 			else if (key_identifier == Rml::Input::KI_RIGHT)
 			{
 				auto el = context->GetRootElement()->GetElementById("keyevent_response");
 				if (el)
-					el->Animate("left", Property{200.f, Property::DP}, 0.5, Tween{Tween::Cubic});
+					el->Animate("left", Property{200.f, Unit::DP}, 0.5, Tween{Tween::Cubic});
 			}
 			else if (key_identifier == Rml::Input::KI_UP)
 			{
 				auto el = context->GetRootElement()->GetElementById("keyevent_response");
-				auto offset_right = Property{200.f, Property::DP};
+				auto offset_right = Property{200.f, Unit::DP};
 				if (el)
-					el->Animate("left", Property{0.f, Property::PX}, 0.5, Tween{Tween::Cubic}, 1, true, 0, &offset_right);
+					el->Animate("left", Property{0.f, Unit::PX}, 0.5, Tween{Tween::Cubic}, 1, true, 0, &offset_right);
 			}
 			else if (key_identifier == Rml::Input::KI_DOWN)
 			{
 				auto el = context->GetRootElement()->GetElementById("keyevent_response");
 				if (el)
-					el->Animate("left", Property{0.f, Property::PX}, 0.5, Tween{Tween::Cubic});
+					el->Animate("left", Property{0.f, Unit::PX}, 0.5, Tween{Tween::Cubic});
 			}
 		}
 		break;
@@ -238,8 +238,8 @@ public:
 			auto el = event.GetTargetElement();
 			if (el->GetId() == "animation_event")
 			{
-				el->Animate("top", Property(Math::RandomReal(200.f), Property::PX), 1.2f, Tween{Tween::Cubic, Tween::InOut});
-				el->Animate("left", Property(Math::RandomReal(100.f), Property::PERCENT), 0.8f, Tween{Tween::Cubic, Tween::InOut});
+				el->Animate("top", Property(Math::RandomReal(200.f), Unit::PX), 1.2f, Tween{Tween::Cubic, Tween::InOut});
+				el->Animate("left", Property(Math::RandomReal(100.f), Unit::PERCENT), 0.8f, Tween{Tween::Cubic, Tween::InOut});
 			}
 		}
 		break;
@@ -339,7 +339,7 @@ int main(int /*argc*/, char** /*argv*/)
 			static float ff = 0.0f;
 			ff += float(nudge) * 0.3f;
 			auto el = window->GetDocument()->GetElementById("exit");
-			el->SetProperty(Rml::PropertyId::MarginLeft, Rml::Property(ff, Rml::Property::PX));
+			el->SetProperty(Rml::PropertyId::MarginLeft, Rml::Property(ff, Rml::Unit::PX));
 			float f_left = el->GetAbsoluteLeft();
 			Rml::Log::Message(Rml::Log::LT_INFO, "margin-left: '%f'   abs: %f.", ff, f_left);
 			nudge = 0;

+ 1 - 1
Samples/basic/demo/src/main.cpp

@@ -281,7 +281,7 @@ public:
 		else if (value == "change_color")
 		{
 			Colourb color((byte)Math::RandomInteger(255), (byte)Math::RandomInteger(255), (byte)Math::RandomInteger(255));
-			element->Animate("image-color", Property(color, Property::COLOUR), tweening_parameters.duration,
+			element->Animate("image-color", Property(color, Unit::COLOUR), tweening_parameters.duration,
 				Tween(tweening_parameters.type, tweening_parameters.direction));
 			event.StopPropagation();
 		}

+ 2 - 2
Samples/basic/drag/src/Inventory.cpp

@@ -37,8 +37,8 @@ Inventory::Inventory(const Rml::String& title, const Rml::Vector2f& position, Rm
 	{
 		using Rml::PropertyId;
 		document->GetElementById("title")->SetInnerRML(title);
-		document->SetProperty(PropertyId::Left, Rml::Property(position.x, Rml::Property::DP));
-		document->SetProperty(PropertyId::Top, Rml::Property(position.y, Rml::Property::DP));
+		document->SetProperty(PropertyId::Left, Rml::Property(position.x, Rml::Unit::DP));
+		document->SetProperty(PropertyId::Top, Rml::Property(position.y, Rml::Unit::DP));
 		document->Show();
 	}
 

+ 2 - 2
Samples/basic/transform/src/main.cpp

@@ -43,8 +43,8 @@ public:
 		if (document)
 		{
 			document->GetElementById("title")->SetInnerRML(title);
-			document->SetProperty(Rml::PropertyId::Left, Rml::Property(position.x, Rml::Property::DP));
-			document->SetProperty(Rml::PropertyId::Top, Rml::Property(position.y, Rml::Property::DP));
+			document->SetProperty(Rml::PropertyId::Left, Rml::Property(position.x, Rml::Unit::DP));
+			document->SetProperty(Rml::PropertyId::Top, Rml::Property(position.y, Rml::Unit::DP));
 			document->Show();
 		}
 	}

+ 2 - 2
Samples/invaders/src/DecoratorDefender.cpp

@@ -56,8 +56,8 @@ void DecoratorDefender::ReleaseElementData(Rml::DecoratorDataHandle /*element_da
 
 void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataHandle /*element_data*/) const
 {
-	Rml::Vector2f position = element->GetAbsoluteOffset(Rml::Box::PADDING);
-	Rml::Vector2f size = element->GetBox().GetSize(Rml::Box::PADDING);
+	Rml::Vector2f position = element->GetAbsoluteOffset(Rml::BoxArea::Padding);
+	Rml::Vector2f size = element->GetBox().GetSize(Rml::BoxArea::Padding);
 	Rml::Math::SnapToPixelGrid(position, size);
 
 	if (Rml::RenderInterface* render_interface = ::Rml::GetRenderInterface())

+ 1 - 1
Samples/invaders/src/DecoratorStarfield.cpp

@@ -74,7 +74,7 @@ Rml::DecoratorDataHandle DecoratorStarfield::GenerateElementData(Rml::Element* e
 		float speed = (top_speed * layer_depth) + (bottom_speed * (1.0f - layer_depth));
 		star_field->star_layers[i].speed = speed;
 
-		star_field->dimensions = element->GetBox().GetSize(Rml::Box::PADDING);
+		star_field->dimensions = element->GetBox().GetSize(Rml::BoxArea::Padding);
 
 		if (star_field->dimensions.x > 0)
 		{

+ 2 - 2
Samples/luainvaders/src/DecoratorDefender.cpp

@@ -56,8 +56,8 @@ void DecoratorDefender::ReleaseElementData(Rml::DecoratorDataHandle /*element_da
 
 void DecoratorDefender::RenderElement(Rml::Element* element, Rml::DecoratorDataHandle /*element_data*/) const
 {
-	Rml::Vector2f position = element->GetAbsoluteOffset(Rml::Box::PADDING);
-	Rml::Vector2f size = element->GetBox().GetSize(Rml::Box::PADDING);
+	Rml::Vector2f position = element->GetAbsoluteOffset(Rml::BoxArea::Padding);
+	Rml::Vector2f size = element->GetBox().GetSize(Rml::BoxArea::Padding);
 	Rml::Math::SnapToPixelGrid(position, size);
 
 	if (Rml::RenderInterface* render_interface = ::Rml::GetRenderInterface())

+ 1 - 1
Samples/luainvaders/src/DecoratorStarfield.cpp

@@ -74,7 +74,7 @@ Rml::DecoratorDataHandle DecoratorStarfield::GenerateElementData(Rml::Element* e
 		float speed = (top_speed * layer_depth) + (bottom_speed * (1.0f - layer_depth));
 		star_field->star_layers[i].speed = speed;
 
-		star_field->dimensions = element->GetBox().GetSize(Rml::Box::PADDING);
+		star_field->dimensions = element->GetBox().GetSize(Rml::BoxArea::Padding);
 
 		if (star_field->dimensions.x > 0)
 		{

+ 2 - 2
Samples/tutorial/drag/src/Inventory.cpp

@@ -7,8 +7,8 @@ Inventory::Inventory(const Rml::String& title, const Rml::Vector2f& position, Rm
 	if (document)
 	{
 		document->GetElementById("title")->SetInnerRML(title);
-		document->SetProperty(Rml::PropertyId::Left, Rml::Property(position.x, Rml::Property::DP));
-		document->SetProperty(Rml::PropertyId::Top, Rml::Property(position.y, Rml::Property::DP));
+		document->SetProperty(Rml::PropertyId::Left, Rml::Property(position.x, Rml::Unit::DP));
+		document->SetProperty(Rml::PropertyId::Top, Rml::Property(position.y, Rml::Unit::DP));
 		document->Show();
 	}
 }

+ 32 - 29
Source/Core/Box.cpp

@@ -36,13 +36,14 @@ Box::Box(Vector2f content) : content(content) {}
 
 Box::~Box() {}
 
-Vector2f Box::GetPosition(Area area) const
+Vector2f Box::GetPosition(BoxArea area) const
 {
-	Vector2f area_position(-area_edges[MARGIN][LEFT], -area_edges[MARGIN][TOP]);
-	for (int i = 0; i < area; i++)
+	RMLUI_ASSERT(area != BoxArea::Auto);
+	Vector2f area_position(-GetEdge(BoxArea::Margin, BoxEdge::Left), -GetEdge(BoxArea::Margin, BoxEdge::Top));
+	for (int i = 0; i < (int)area; i++)
 	{
-		area_position.x += area_edges[i][LEFT];
-		area_position.y += area_edges[i][TOP];
+		area_position.x += area_edges[i][(int)BoxEdge::Left];
+		area_position.y += area_edges[i][(int)BoxEdge::Top];
 	}
 
 	return area_position;
@@ -53,13 +54,14 @@ Vector2f Box::GetSize() const
 	return content;
 }
 
-Vector2f Box::GetSize(Area area) const
+Vector2f Box::GetSize(BoxArea area) const
 {
+	RMLUI_ASSERT(area != BoxArea::Auto);
 	Vector2f area_size(content);
-	for (int i = area; i <= PADDING; i++)
+	for (int i = (int)area; i <= (int)BoxArea::Padding; i++)
 	{
-		area_size.x += (area_edges[i][LEFT] + area_edges[i][RIGHT]);
-		area_size.y += (area_edges[i][TOP] + area_edges[i][BOTTOM]);
+		area_size.x += (area_edges[i][(int)BoxEdge::Left] + area_edges[i][(int)BoxEdge::Right]);
+		area_size.y += (area_edges[i][(int)BoxEdge::Top] + area_edges[i][(int)BoxEdge::Bottom]);
 	}
 
 	return area_size;
@@ -70,60 +72,61 @@ void Box::SetContent(Vector2f _content)
 	content = _content;
 }
 
-void Box::SetEdge(Area area, Edge edge, float size)
+void Box::SetEdge(BoxArea area, BoxEdge edge, float size)
 {
-	area_edges[area][edge] = size;
+	RMLUI_ASSERT(area != BoxArea::Auto);
+	area_edges[(int)area][(int)edge] = size;
 }
 
-float Box::GetEdge(Area area, Edge edge) const
+float Box::GetEdge(BoxArea area, BoxEdge edge) const
 {
-	return area_edges[area][edge];
+	RMLUI_ASSERT(area != BoxArea::Auto);
+	return area_edges[(int)area][(int)edge];
 }
 
-float Box::GetCumulativeEdge(Area area, Edge edge) const
+float Box::GetCumulativeEdge(BoxArea area, BoxEdge edge) const
 {
+	RMLUI_ASSERT(area != BoxArea::Auto);
 	float size = 0;
-	int max_area = Math::Min((int)area, (int)PADDING);
+	int max_area = Math::Min((int)area, (int)BoxArea::Padding);
 	for (int i = 0; i <= max_area; i++)
-		size += area_edges[i][edge];
+		size += area_edges[i][(int)edge];
 
 	return size;
 }
 
-float Box::GetSizeAcross(Direction direction, Area area_outer, Area area_inner) const
+float Box::GetSizeAcross(BoxDirection direction, BoxArea area_outer, BoxArea area_inner) const
 {
-	static_assert(HORIZONTAL == 1 && VERTICAL == 0, "");
-	RMLUI_ASSERT(area_outer <= area_inner && direction <= 1);
+	static_assert((int)BoxDirection::Horizontal == 1 && (int)BoxDirection::Vertical == 0, "");
+	RMLUI_ASSERT((int)area_outer <= (int)area_inner && (int)direction <= 1 && area_inner != BoxArea::Auto);
 
 	float size = 0.0f;
 
-	if (area_inner == CONTENT)
-		size = (direction == HORIZONTAL ? content.x : content.y);
+	if (area_inner == BoxArea::Content)
+		size = (direction == BoxDirection::Horizontal ? content.x : content.y);
 
-	for (int i = area_outer; i <= area_inner && i < CONTENT; i++)
-		size += (area_edges[i][TOP + (int)direction] + area_edges[i][BOTTOM + (int)direction]);
+	for (int i = (int)area_outer; i <= (int)area_inner && i < (int)BoxArea::Content; i++)
+		size += (area_edges[i][(int)BoxEdge::Top + (int)direction] + area_edges[i][(int)BoxEdge::Bottom + (int)direction]);
 
 	return size;
 }
 
-Vector2f Box::GetFrameSize(Area area) const
+Vector2f Box::GetFrameSize(BoxArea area) const
 {
-	if (area == CONTENT)
+	if (area == BoxArea::Content)
 		return content;
 
 	return {
-		area_edges[area][RIGHT] + area_edges[area][LEFT],
-		area_edges[area][TOP] + area_edges[area][BOTTOM],
+		area_edges[(int)area][(int)BoxEdge::Right] + area_edges[(int)area][(int)BoxEdge::Left],
+		area_edges[(int)area][(int)BoxEdge::Top] + area_edges[(int)area][(int)BoxEdge::Bottom],
 	};
 }
 
-// Compares the size of the content area and the other area edges.
 bool Box::operator==(const Box& rhs) const
 {
 	return content == rhs.content && memcmp(area_edges, rhs.area_edges, sizeof(area_edges)) == 0;
 }
 
-// Compares the size of the content area and the other area edges.
 bool Box::operator!=(const Box& rhs) const
 {
 	return !(*this == rhs);

+ 76 - 105
Source/Core/ComputeProperty.cpp

@@ -37,141 +37,110 @@ const Style::ComputedValues DefaultComputedValues{nullptr};
 
 static constexpr float PixelsPerInch = 96.0f;
 
-float ComputeLength(const Property* property, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions)
+float ComputeAbsoluteLength(NumericValue value)
 {
-	RMLUI_ASSERT(property);
-
-	float value = property->value.Get<float>();
-
-	switch (property->unit)
+	if (value.unit == Unit::PX)
 	{
-	case Property::NUMBER:
-	case Property::PX:
-	case Property::RAD: return value;
-
-	case Property::EM: return value * font_size;
-	case Property::REM: return value * document_font_size;
-	case Property::DP: return value * dp_ratio;
-	case Property::VW: return value * vp_dimensions.x * 0.01f;
-	case Property::VH: return value * vp_dimensions.y * 0.01f;
-
-	case Property::DEG: return Math::DegreesToRadians(value);
-	default: break;
+		return value.number;
 	}
-
-	// Values based on pixels-per-inch.
-	if (property->unit & Property::PPI_UNIT)
+	else if (Any(value.unit & Unit::PPI_UNIT))
 	{
-		float inch = value * PixelsPerInch;
+		// Values based on pixels-per-inch.
+		const float inch = value.number * PixelsPerInch;
 
-		switch (property->unit)
+		switch (value.unit)
 		{
-		case Property::INCH: return inch;                // inch
-		case Property::CM: return inch * (1.0f / 2.54f); // centimeter
-		case Property::MM: return inch * (1.0f / 25.4f); // millimeter
-		case Property::PT: return inch * (1.0f / 72.0f); // point
-		case Property::PC: return inch * (1.0f / 6.0f);  // pica
+		case Unit::INCH: return inch;
+		case Unit::CM: return inch * (1.0f / 2.54f);
+		case Unit::MM: return inch * (1.0f / 25.4f);
+		case Unit::PT: return inch * (1.0f / 72.0f);
+		case Unit::PC: return inch * (1.0f / 6.0f);
 		default: break;
 		}
 	}
 
-	// We're not a numeric property; return 0.
-	return 0.0f;
+	RMLUI_ERROR;
+	return 0.f;
 }
 
-float ComputeAbsoluteLength(const Property& property, float dp_ratio, Vector2f vp_dimensions)
+float ComputeLength(NumericValue value, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions)
 {
-	RMLUI_ASSERT(property.unit & Property::ABSOLUTE_LENGTH);
+	if (Any(value.unit & Unit::ABSOLUTE_LENGTH))
+		return ComputeAbsoluteLength(value);
 
-	switch (property.unit)
+	switch (value.unit)
 	{
-	case Property::PX: return property.value.Get<float>();
-	case Property::DP: return property.value.Get<float>() * dp_ratio;
-	case Property::VW: return property.value.Get<float>() * vp_dimensions.x * 0.01f;
-	case Property::VH: return property.value.Get<float>() * vp_dimensions.y * 0.01f;
-	default:
-		// Values based on pixels-per-inch.
-		if (property.unit & Property::PPI_UNIT)
-		{
-			float inch = property.value.Get<float>() * PixelsPerInch;
-
-			switch (property.unit)
-			{
-			case Property::INCH: return inch;                // inch
-			case Property::CM: return inch * (1.0f / 2.54f); // centimeter
-			case Property::MM: return inch * (1.0f / 25.4f); // millimeter
-			case Property::PT: return inch * (1.0f / 72.0f); // point
-			case Property::PC: return inch * (1.0f / 6.0f);  // pica
-			default: break;
-			}
-		}
+	case Unit::EM: return value.number * font_size;
+	case Unit::REM: return value.number * document_font_size;
+	case Unit::DP: return value.number * dp_ratio;
+	case Unit::VW: return value.number * vp_dimensions.x * 0.01f;
+	case Unit::VH: return value.number * vp_dimensions.y * 0.01f;
+	default: break;
 	}
 
 	RMLUI_ERROR;
 	return 0.0f;
 }
 
-float ComputeAngle(const Property& property)
+float ComputeAngle(NumericValue value)
 {
-	float value = property.value.Get<float>();
-
-	switch (property.unit)
+	switch (value.unit)
 	{
-	case Property::NUMBER:
-	case Property::RAD: return value;
+	case Unit::NUMBER:
+	case Unit::RAD: return value.number;
 
-	case Property::DEG: return Math::DegreesToRadians(value);
+	case Unit::DEG: return Math::DegreesToRadians(value.number);
 	default: break;
 	}
 
+	RMLUI_ERROR;
 	return 0.0f;
 }
 
-String ComputeFontFamily(String font_family)
-{
-	return StringUtilities::ToLower(std::move(font_family));
-}
-
-float ComputeFontsize(const Property& property, const Style::ComputedValues& values, const Style::ComputedValues* parent_values,
+float ComputeFontsize(NumericValue value, const Style::ComputedValues& values, const Style::ComputedValues* parent_values,
 	const Style::ComputedValues* document_values, float dp_ratio, Vector2f vp_dimensions)
 {
-	// The calculated value of the font-size property is inherited, so we need to check if this
-	// is an inherited property. If so, then we return our parent's font size instead.
-	if (property.unit & Property::RELATIVE_UNIT)
+	if (Any(value.unit & (Unit::PERCENT | Unit::EM | Unit::REM)))
 	{
+		// Relative values are based on the parent's or document's font size instead of our own.
 		float multiplier = 1.0f;
 
-		switch (property.unit)
+		switch (value.unit)
 		{
-		case Property::PERCENT:
+		case Unit::PERCENT:
 			multiplier = 0.01f;
 			//-fallthrough
-		case Property::EM:
+		case Unit::EM:
 			if (!parent_values)
 				return 0;
-			return property.value.Get<float>() * multiplier * parent_values->font_size();
+			return value.number * multiplier * parent_values->font_size();
 
-		case Property::REM:
-			if (!document_values)
-				return 0;
-			// If the current element is a document, the rem unit is relative to the default size
-			if (&values == document_values)
-				return property.value.Get<float>() * DefaultComputedValues.font_size();
-			// Otherwise it is relative to the document font size
-			return property.value.Get<float>() * document_values->font_size();
-		default: RMLUI_ERRORMSG("A relative unit must be percentage, em or rem.");
+		case Unit::REM:
+			// If the current element is a document, the rem unit is relative to the default size.
+			if (!document_values || &values == document_values)
+				return value.number * DefaultComputedValues.font_size();
+
+			// Otherwise it is relative to the document font size.
+			return value.number * document_values->font_size();
+		default: break;
 		}
 	}
 
-	return ComputeAbsoluteLength(property, dp_ratio, vp_dimensions);
+	// Font-relative lengths handled above, other lengths should be handled as normal.
+	return ComputeLength(value, 0.f, 0.f, dp_ratio, vp_dimensions);
+}
+
+String ComputeFontFamily(String font_family)
+{
+	return StringUtilities::ToLower(std::move(font_family));
 }
 
 Style::Clip ComputeClip(const Property* property)
 {
 	const int value = property->Get<int>();
-	if (property->unit == Property::KEYWORD)
+	if (property->unit == Unit::KEYWORD)
 		return Style::Clip(static_cast<Style::Clip::Type>(value));
-	else if (property->unit == Property::NUMBER)
+	else if (property->unit == Unit::NUMBER)
 		return Style::Clip(Style::Clip::Type::Number, static_cast<int8_t>(value));
 	RMLUI_ERRORMSG("Invalid clip type");
 	return Style::Clip();
@@ -179,9 +148,9 @@ Style::Clip ComputeClip(const Property* property)
 
 Style::LineHeight ComputeLineHeight(const Property* property, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions)
 {
-	if (property->unit & Property::LENGTH)
+	if (Any(property->unit & Unit::LENGTH))
 	{
-		float value = ComputeLength(property, font_size, document_font_size, dp_ratio, vp_dimensions);
+		float value = ComputeLength(property->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions);
 		return Style::LineHeight(value, Style::LineHeight::Length, value);
 	}
 
@@ -189,8 +158,8 @@ Style::LineHeight ComputeLineHeight(const Property* property, float font_size, f
 
 	switch (property->unit)
 	{
-	case Property::NUMBER: scale_factor = property->value.Get<float>(); break;
-	case Property::PERCENT: scale_factor = property->value.Get<float>() * 0.01f; break;
+	case Unit::NUMBER: scale_factor = property->value.Get<float>(); break;
+	case Unit::PERCENT: scale_factor = property->value.Get<float>() * 0.01f; break;
 	default: RMLUI_ERRORMSG("Invalid unit for line-height");
 	}
 
@@ -201,17 +170,17 @@ Style::LineHeight ComputeLineHeight(const Property* property, float font_size, f
 Style::VerticalAlign ComputeVerticalAlign(const Property* property, float line_height, float font_size, float document_font_size, float dp_ratio,
 	Vector2f vp_dimensions)
 {
-	if (property->unit & Property::LENGTH)
+	if (Any(property->unit & Unit::LENGTH))
 	{
-		float value = ComputeLength(property, font_size, document_font_size, dp_ratio, vp_dimensions);
+		float value = ComputeLength(property->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions);
 		return Style::VerticalAlign(value);
 	}
-	else if (property->unit & Property::PERCENT)
+	else if (property->unit == Unit::PERCENT)
 	{
 		return Style::VerticalAlign(property->Get<float>() * line_height * 0.01f);
 	}
 
-	RMLUI_ASSERT(property->unit & Property::KEYWORD);
+	RMLUI_ASSERT(property->unit == Unit::KEYWORD);
 	return Style::VerticalAlign((Style::VerticalAlign::Type)property->Get<int>());
 }
 
@@ -219,23 +188,24 @@ Style::LengthPercentage ComputeLengthPercentage(const Property* property, float
 	Vector2f vp_dimensions)
 {
 	using namespace Style;
-	if (property->unit & Property::PERCENT)
+	if (property->unit == Unit::PERCENT)
 		return LengthPercentage(LengthPercentage::Percentage, property->Get<float>());
 
-	return LengthPercentage(LengthPercentage::Length, ComputeLength(property, font_size, document_font_size, dp_ratio, vp_dimensions));
+	return LengthPercentage(LengthPercentage::Length,
+		ComputeLength(property->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions));
 }
 
 Style::LengthPercentageAuto ComputeLengthPercentageAuto(const Property* property, float font_size, float document_font_size, float dp_ratio,
 	Vector2f vp_dimensions)
 {
 	using namespace Style;
-	// Assuming here that 'auto' is the only possible keyword
-	if (property->unit & Property::PERCENT)
+	if (property->unit == Unit::PERCENT)
 		return LengthPercentageAuto(LengthPercentageAuto::Percentage, property->Get<float>());
-	else if (property->unit & Property::KEYWORD)
+	else if (property->unit == Unit::KEYWORD)
 		return LengthPercentageAuto(LengthPercentageAuto::Auto);
 
-	return LengthPercentageAuto(LengthPercentageAuto::Length, ComputeLength(property, font_size, document_font_size, dp_ratio, vp_dimensions));
+	return LengthPercentageAuto(LengthPercentageAuto::Length,
+		ComputeLength(property->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions));
 }
 
 Style::LengthPercentage ComputeOrigin(const Property* property, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions)
@@ -244,7 +214,7 @@ Style::LengthPercentage ComputeOrigin(const Property* property, float font_size,
 	static_assert(
 		(int)OriginX::Left == (int)OriginY::Top && (int)OriginX::Center == (int)OriginY::Center && (int)OriginX::Right == (int)OriginY::Bottom, "");
 
-	if (property->unit & Property::KEYWORD)
+	if (property->unit == Unit::KEYWORD)
 	{
 		float percent = 0.0f;
 		OriginX origin = (OriginX)property->Get<int>();
@@ -256,21 +226,22 @@ Style::LengthPercentage ComputeOrigin(const Property* property, float font_size,
 		}
 		return LengthPercentage(LengthPercentage::Percentage, percent);
 	}
-	else if (property->unit & Property::PERCENT)
+	else if (property->unit == Unit::PERCENT)
 		return LengthPercentage(LengthPercentage::Percentage, property->Get<float>());
 
-	return LengthPercentage(LengthPercentage::Length, ComputeLength(property, font_size, document_font_size, dp_ratio, vp_dimensions));
+	return LengthPercentage(LengthPercentage::Length,
+		ComputeLength(property->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions));
 }
 
 Style::LengthPercentage ComputeMaxSize(const Property* property, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions)
 {
 	using namespace Style;
-	if (property->unit & Property::KEYWORD)
+	if (Any(property->unit & Unit::KEYWORD))
 		return LengthPercentage(LengthPercentage::Length, FLT_MAX);
-	else if (property->unit & Property::PERCENT)
+	else if (Any(property->unit & Unit::PERCENT))
 		return LengthPercentage(LengthPercentage::Percentage, property->Get<float>());
 
-	const float length = ComputeLength(property, font_size, document_font_size, dp_ratio, vp_dimensions);
+	const float length = ComputeLength(property->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions);
 	return LengthPercentage(LengthPercentage::Length, length < 0.f ? FLT_MAX : length);
 }
 

+ 8 - 7
Source/Core/ComputeProperty.h

@@ -29,24 +29,25 @@
 #ifndef RMLUI_CORE_COMPUTEPROPERTY_H
 #define RMLUI_CORE_COMPUTEPROPERTY_H
 
+#include "../../Include/RmlUi/Core/NumericValue.h"
 #include "../../Include/RmlUi/Core/StyleTypes.h"
 
 namespace Rml {
 
 class Property;
 
-// Note that percentages are not lengths! They have to be resolved elsewhere.
-float ComputeLength(const Property* property, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions);
+// Note that numbers and percentages are not lengths, they have to be resolved elsewhere.
+float ComputeLength(NumericValue value, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions);
 
-float ComputeAbsoluteLength(const Property& property, float dp_ratio, Vector2f vp_dimensions);
+float ComputeAbsoluteLength(NumericValue value);
 
-float ComputeAngle(const Property& property);
+float ComputeAngle(NumericValue value);
 
-String ComputeFontFamily(String font_family);
-
-float ComputeFontsize(const Property& property, const Style::ComputedValues& values, const Style::ComputedValues* parent_values,
+float ComputeFontsize(NumericValue value, const Style::ComputedValues& values, const Style::ComputedValues* parent_values,
 	const Style::ComputedValues* document_values, float dp_ratio, Vector2f vp_dimensions);
 
+String ComputeFontFamily(String font_family);
+
 Style::Clip ComputeClip(const Property* property);
 
 Style::LineHeight ComputeLineHeight(const Property* property, float font_size, float document_font_size, float dp_ratio, Vector2f vp_dimensions);

+ 8 - 3
Source/Core/ComputedValues.cpp

@@ -36,7 +36,7 @@ const AnimationList* Style::ComputedValues::animation() const
 {
 	if (auto p = element->GetLocalProperty(PropertyId::Animation))
 	{
-		if (p->unit == Property::ANIMATION)
+		if (p->unit == Unit::ANIMATION)
 			return &(p->value.GetReference<AnimationList>());
 	}
 	return nullptr;
@@ -46,7 +46,7 @@ const TransitionList* Style::ComputedValues::transition() const
 {
 	if (auto p = element->GetLocalProperty(PropertyId::Transition))
 	{
-		if (p->unit == Property::TRANSITION)
+		if (p->unit == Unit::TRANSITION)
 			return &(p->value.GetReference<TransitionList>());
 	}
 	return nullptr;
@@ -70,7 +70,12 @@ String Style::ComputedValues::cursor() const
 
 float Style::ComputedValues::letter_spacing() const
 {
-	return inherited.has_letter_spacing ? element->ResolveNumericProperty(element->GetProperty(PropertyId::LetterSpacing), 0.f) : 0.f;
+	if (inherited.has_letter_spacing)
+	{
+		if (auto p = element->GetProperty(PropertyId::LetterSpacing))
+			return element->ResolveLength(p->GetNumericValue());
+	}
+	return 0.f;
 }
 
 float ResolveValueOr(Style::LengthPercentageAuto length, float base_value, float default_value)

+ 11 - 11
Source/Core/Context.cpp

@@ -63,7 +63,7 @@ Context::Context(const String& name) :
 	root = Factory::InstanceElement(nullptr, "*", "#root", XMLAttributes());
 	root->SetId(name);
 	root->SetOffset(Vector2f(0, 0), nullptr);
-	root->SetProperty(PropertyId::ZIndex, Property(0, Property::NUMBER));
+	root->SetProperty(PropertyId::ZIndex, Property(0, Unit::NUMBER));
 
 	cursor_proxy = Factory::InstanceElement(nullptr, documents_base_tag, documents_base_tag, XMLAttributes());
 	ElementDocument* cursor_proxy_document = rmlui_dynamic_cast<ElementDocument*>(cursor_proxy.get());
@@ -73,11 +73,11 @@ Context::Context(const String& name) :
 	// The cursor proxy takes the style from its cloned element's document. The latter may define style rules for `<body>` which we don't want on the
 	// proxy. Thus, we override some properties here that we in particular don't want to inherit from the client document, especially those that
 	// result in decoration of the body element.
-	cursor_proxy_document->SetProperty(PropertyId::BackgroundColor, Property(Colourb(255, 255, 255, 0), Property::COLOUR));
-	cursor_proxy_document->SetProperty(PropertyId::BorderTopWidth, Property(0, Property::PX));
-	cursor_proxy_document->SetProperty(PropertyId::BorderRightWidth, Property(0, Property::PX));
-	cursor_proxy_document->SetProperty(PropertyId::BorderBottomWidth, Property(0, Property::PX));
-	cursor_proxy_document->SetProperty(PropertyId::BorderLeftWidth, Property(0, Property::PX));
+	cursor_proxy_document->SetProperty(PropertyId::BackgroundColor, Property(Colourb(255, 255, 255, 0), Unit::COLOUR));
+	cursor_proxy_document->SetProperty(PropertyId::BorderTopWidth, Property(0, Unit::PX));
+	cursor_proxy_document->SetProperty(PropertyId::BorderRightWidth, Property(0, Unit::PX));
+	cursor_proxy_document->SetProperty(PropertyId::BorderBottomWidth, Property(0, Unit::PX));
+	cursor_proxy_document->SetProperty(PropertyId::BorderLeftWidth, Property(0, Unit::PX));
 	cursor_proxy_document->SetProperty(PropertyId::Decorator, Property());
 	cursor_proxy_document->SetProperty(PropertyId::OverflowX, Property(Style::Overflow::Visible));
 	cursor_proxy_document->SetProperty(PropertyId::OverflowY, Property(Style::Overflow::Visible));
@@ -1264,17 +1264,17 @@ void Context::CreateDragClone(Element* element)
 	cursor_proxy->AppendChild(std::move(element_drag_clone));
 
 	// Position the clone. Use projected mouse coordinates to handle any ancestor transforms.
-	const Vector2f absolute_pos = element->GetAbsoluteOffset(Box::BORDER);
+	const Vector2f absolute_pos = element->GetAbsoluteOffset(BoxArea::Border);
 	Vector2f projected_mouse_position = Vector2f(mouse_position);
 	if (Element* parent = element->GetParentNode())
 		parent->Project(projected_mouse_position);
 
 	drag_clone->SetProperty(PropertyId::Position, Property(Style::Position::Absolute));
-	drag_clone->SetProperty(PropertyId::Left, Property(absolute_pos.x - projected_mouse_position.x, Property::PX));
-	drag_clone->SetProperty(PropertyId::Top, Property(absolute_pos.y - projected_mouse_position.y, Property::PX));
+	drag_clone->SetProperty(PropertyId::Left, Property(absolute_pos.x - projected_mouse_position.x, Unit::PX));
+	drag_clone->SetProperty(PropertyId::Top, Property(absolute_pos.y - projected_mouse_position.y, Unit::PX));
 	// We remove margins so that percentage- and auto-margins are evaluated correctly.
-	drag_clone->SetProperty(PropertyId::MarginLeft, Property(0.f, Property::PX));
-	drag_clone->SetProperty(PropertyId::MarginTop, Property(0.f, Property::PX));
+	drag_clone->SetProperty(PropertyId::MarginLeft, Property(0.f, Unit::PX));
+	drag_clone->SetProperty(PropertyId::MarginTop, Property(0.f, Unit::PX));
 	drag_clone->SetPseudoClass("drag", true);
 }
 

+ 3 - 3
Source/Core/DecoratorGradient.cpp

@@ -81,8 +81,8 @@ DecoratorDataHandle DecoratorGradient::GenerateElementData(Element* element) con
 	Colourb colour_stop = stop;
 	colour_stop.alpha = (byte)(opacity * (float)colour_stop.alpha);
 
-	const Vector2f padding_offset = box.GetPosition(Box::PADDING);
-	const Vector2f padding_size = box.GetSize(Box::PADDING);
+	const Vector2f padding_offset = box.GetPosition(BoxArea::Padding);
+	const Vector2f padding_size = box.GetSize(BoxArea::Padding);
 
 	Vector<Vertex>& vertices = geometry->GetVertices();
 
@@ -114,7 +114,7 @@ void DecoratorGradient::ReleaseElementData(DecoratorDataHandle element_data) con
 void DecoratorGradient::RenderElement(Element* element, DecoratorDataHandle element_data) const
 {
 	auto* data = reinterpret_cast<Geometry*>(element_data);
-	data->Render(element->GetAbsoluteOffset(Box::BORDER));
+	data->Render(element->GetAbsoluteOffset(BoxArea::Border));
 }
 
 DecoratorGradientInstancer::DecoratorGradientInstancer()

+ 1 - 1
Source/Core/DecoratorGradient.h

@@ -31,7 +31,7 @@
 
 #include "../../Include/RmlUi/Core/Decorator.h"
 #include "../../Include/RmlUi/Core/DecoratorInstancer.h"
-#include "../../Include/RmlUi/Core/Property.h"
+#include "../../Include/RmlUi/Core/ID.h"
 
 namespace Rml {
 

+ 11 - 11
Source/Core/DecoratorNinePatch.cpp

@@ -39,7 +39,7 @@ DecoratorNinePatch::DecoratorNinePatch() {}
 
 DecoratorNinePatch::~DecoratorNinePatch() {}
 
-bool DecoratorNinePatch::Initialise(const Rectanglef& _rect_outer, const Rectanglef& _rect_inner, const Array<Property, 4>* _edges,
+bool DecoratorNinePatch::Initialise(const Rectanglef& _rect_outer, const Rectanglef& _rect_inner, const Array<NumericValue, 4>* _edges,
 	const Texture& _texture, float _display_scale)
 {
 	rect_outer = _rect_outer;
@@ -48,7 +48,7 @@ bool DecoratorNinePatch::Initialise(const Rectanglef& _rect_outer, const Rectang
 	display_scale = _display_scale;
 
 	if (_edges)
-		edges = MakeUnique<Array<Property, 4>>(*_edges);
+		edges = MakeUnique<Array<NumericValue, 4>>(*_edges);
 
 	int texture_index = AddTexture(_texture);
 	return (texture_index >= 0);
@@ -64,7 +64,7 @@ DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element) co
 	data->SetTexture(texture);
 	const Vector2f texture_dimensions(texture->GetDimensions());
 
-	const Vector2f surface_dimensions = element->GetBox().GetSize(Box::PADDING).Round();
+	const Vector2f surface_dimensions = element->GetBox().GetSize(BoxArea::Padding).Round();
 
 	const float opacity = computed.opacity();
 	Colourb quad_colour = computed.image_color();
@@ -102,10 +102,10 @@ DecoratorDataHandle DecoratorNinePatch::GenerateElementData(Element* element) co
 	if (edges)
 	{
 		float lengths[4]; // top, right, bottom, left
-		lengths[0] = element->ResolveNumericProperty(&(*edges)[0], (surface_pos[1].y - surface_pos[0].y));
-		lengths[1] = element->ResolveNumericProperty(&(*edges)[1], (surface_pos[3].x - surface_pos[2].x));
-		lengths[2] = element->ResolveNumericProperty(&(*edges)[2], (surface_pos[3].y - surface_pos[2].y));
-		lengths[3] = element->ResolveNumericProperty(&(*edges)[3], (surface_pos[1].x - surface_pos[0].x));
+		lengths[0] = element->ResolveNumericValue((*edges)[0], (surface_pos[1].y - surface_pos[0].y));
+		lengths[1] = element->ResolveNumericValue((*edges)[1], (surface_pos[3].x - surface_pos[2].x));
+		lengths[2] = element->ResolveNumericValue((*edges)[2], (surface_pos[3].y - surface_pos[2].y));
+		lengths[3] = element->ResolveNumericValue((*edges)[3], (surface_pos[1].x - surface_pos[0].x));
 
 		surface_pos[1].y = lengths[0];
 		surface_pos[2].x = surface_dimensions.x - lengths[1];
@@ -176,7 +176,7 @@ void DecoratorNinePatch::ReleaseElementData(DecoratorDataHandle element_data) co
 void DecoratorNinePatch::RenderElement(Element* element, DecoratorDataHandle element_data) const
 {
 	Geometry* data = reinterpret_cast<Geometry*>(element_data);
-	data->Render(element->GetAbsoluteOffset(Box::PADDING));
+	data->Render(element->GetAbsoluteOffset(BoxArea::Padding));
 }
 
 DecoratorNinePatchInstancer::DecoratorNinePatchInstancer()
@@ -201,11 +201,11 @@ SharedPtr<Decorator> DecoratorNinePatchInstancer::InstanceDecorator(const String
 	const DecoratorInstancerInterface& instancer_interface)
 {
 	bool edges_set = false;
-	Array<Property, 4> edges;
+	Array<NumericValue, 4> edges;
 	for (int i = 0; i < 4; i++)
 	{
-		edges[i] = *properties.GetProperty(edge_ids[i]);
-		if (edges[i].value.Get(0.0f) != 0.0f)
+		edges[i] = properties.GetProperty(edge_ids[i])->GetNumericValue();
+		if (edges[i].number != 0.0f)
 		{
 			edges_set = true;
 		}

+ 3 - 3
Source/Core/DecoratorNinePatch.h

@@ -31,7 +31,7 @@
 
 #include "../../Include/RmlUi/Core/Decorator.h"
 #include "../../Include/RmlUi/Core/DecoratorInstancer.h"
-#include "../../Include/RmlUi/Core/Property.h"
+#include "../../Include/RmlUi/Core/ID.h"
 #include "../../Include/RmlUi/Core/Spritesheet.h"
 
 namespace Rml {
@@ -41,7 +41,7 @@ public:
 	DecoratorNinePatch();
 	virtual ~DecoratorNinePatch();
 
-	bool Initialise(const Rectanglef& rect_outer, const Rectanglef& rect_inner, const Array<Property, 4>* _edges, const Texture& texture,
+	bool Initialise(const Rectanglef& rect_outer, const Rectanglef& rect_inner, const Array<NumericValue, 4>* _edges, const Texture& texture,
 		float display_scale);
 
 	DecoratorDataHandle GenerateElementData(Element* element) const override;
@@ -52,7 +52,7 @@ public:
 private:
 	Rectanglef rect_outer, rect_inner;
 	float display_scale = 1;
-	UniquePtr<Array<Property, 4>> edges;
+	UniquePtr<Array<NumericValue, 4>> edges;
 };
 
 class DecoratorNinePatchInstancer : public DecoratorInstancer {

+ 2 - 2
Source/Core/DecoratorTiledBox.cpp

@@ -104,7 +104,7 @@ DecoratorDataHandle DecoratorTiledBox::GenerateElementData(Element* element) con
 		tiles[i].CalculateDimensions(*GetTexture(tiles[i].texture_index));
 	}
 
-	Vector2f padded_size = element->GetBox().GetSize(Box::PADDING);
+	Vector2f padded_size = element->GetBox().GetSize(BoxArea::Padding);
 
 	// Calculate the natural dimensions of tile corners and edges.
 	const Vector2f natural_top_left = tiles[TOP_LEFT_CORNER].GetNaturalDimensions(element);
@@ -251,7 +251,7 @@ void DecoratorTiledBox::ReleaseElementData(DecoratorDataHandle element_data) con
 
 void DecoratorTiledBox::RenderElement(Element* element, DecoratorDataHandle element_data) const
 {
-	Vector2f translation = element->GetAbsoluteOffset(Box::PADDING).Round();
+	Vector2f translation = element->GetAbsoluteOffset(BoxArea::Padding).Round();
 	DecoratorTiledBoxData* data = reinterpret_cast<DecoratorTiledBoxData*>(element_data);
 
 	for (int i = 0; i < data->num_textures; i++)

+ 2 - 2
Source/Core/DecoratorTiledHorizontal.cpp

@@ -84,7 +84,7 @@ DecoratorDataHandle DecoratorTiledHorizontal::GenerateElementData(Element* eleme
 	const int num_textures = GetNumTextures();
 	DecoratorTiledHorizontalData* data = new DecoratorTiledHorizontalData(num_textures);
 
-	Vector2f padded_size = element->GetBox().GetSize(Box::PADDING);
+	Vector2f padded_size = element->GetBox().GetSize(BoxArea::Padding);
 
 	Vector2f left_dimensions = tiles[LEFT].GetNaturalDimensions(element);
 	Vector2f right_dimensions = tiles[RIGHT].GetNaturalDimensions(element);
@@ -136,7 +136,7 @@ void DecoratorTiledHorizontal::ReleaseElementData(DecoratorDataHandle element_da
 
 void DecoratorTiledHorizontal::RenderElement(Element* element, DecoratorDataHandle element_data) const
 {
-	Vector2f translation = element->GetAbsoluteOffset(Box::PADDING).Round();
+	Vector2f translation = element->GetAbsoluteOffset(BoxArea::Padding).Round();
 	DecoratorTiledHorizontalData* data = reinterpret_cast<DecoratorTiledHorizontalData*>(element_data);
 
 	for (int i = 0; i < data->num_textures; i++)

+ 2 - 2
Source/Core/DecoratorTiledImage.cpp

@@ -55,7 +55,7 @@ DecoratorDataHandle DecoratorTiledImage::GenerateElementData(Element* element) c
 	const ComputedValues& computed = element->GetComputedValues();
 
 	// Generate the geometry for the tile.
-	tile.GenerateGeometry(data->GetVertices(), data->GetIndices(), computed, Vector2f(0, 0), element->GetBox().GetSize(Box::PADDING),
+	tile.GenerateGeometry(data->GetVertices(), data->GetIndices(), computed, Vector2f(0, 0), element->GetBox().GetSize(BoxArea::Padding),
 		tile.GetNaturalDimensions(element));
 
 	return reinterpret_cast<DecoratorDataHandle>(data);
@@ -69,7 +69,7 @@ void DecoratorTiledImage::ReleaseElementData(DecoratorDataHandle element_data) c
 void DecoratorTiledImage::RenderElement(Element* element, DecoratorDataHandle element_data) const
 {
 	Geometry* data = reinterpret_cast<Geometry*>(element_data);
-	data->Render(element->GetAbsoluteOffset(Box::PADDING).Round());
+	data->Render(element->GetAbsoluteOffset(BoxArea::Padding).Round());
 }
 
 } // namespace Rml

+ 3 - 3
Source/Core/DecoratorTiledInstancer.cpp

@@ -142,7 +142,7 @@ bool DecoratorTiledInstancer::GetTileProperties(DecoratorTiled::Tile* tiles, Tex
 
 				LengthPercentage& align = tile.align[dimension];
 				const Property& property = *align_properties[dimension];
-				if (property.unit == Property::KEYWORD)
+				if (property.unit == Unit::KEYWORD)
 				{
 					enum { TOP_LEFT, CENTER, BOTTOM_RIGHT };
 					switch (property.Get<int>())
@@ -152,11 +152,11 @@ bool DecoratorTiledInstancer::GetTileProperties(DecoratorTiled::Tile* tiles, Tex
 					case BOTTOM_RIGHT: align = LengthPercentage(LengthPercentage::Percentage, 100.0f); break;
 					}
 				}
-				else if (property.unit == Property::PERCENT)
+				else if (property.unit == Unit::PERCENT)
 				{
 					align = LengthPercentage(LengthPercentage::Percentage, property.Get<float>());
 				}
-				else if (property.unit == Property::PX)
+				else if (property.unit == Unit::PX)
 				{
 					align = LengthPercentage(LengthPercentage::Length, property.Get<float>());
 				}

+ 2 - 2
Source/Core/DecoratorTiledVertical.cpp

@@ -85,7 +85,7 @@ DecoratorDataHandle DecoratorTiledVertical::GenerateElementData(Element* element
 	const int num_textures = GetNumTextures();
 	DecoratorTiledVerticalData* data = new DecoratorTiledVerticalData(num_textures);
 
-	Vector2f padded_size = element->GetBox().GetSize(Box::PADDING);
+	Vector2f padded_size = element->GetBox().GetSize(BoxArea::Padding);
 
 	Vector2f top_dimensions = tiles[TOP].GetNaturalDimensions(element);
 	Vector2f bottom_dimensions = tiles[BOTTOM].GetNaturalDimensions(element);
@@ -138,7 +138,7 @@ void DecoratorTiledVertical::ReleaseElementData(DecoratorDataHandle element_data
 
 void DecoratorTiledVertical::RenderElement(Element* element, DecoratorDataHandle element_data) const
 {
-	Vector2f translation = element->GetAbsoluteOffset(Box::PADDING).Round();
+	Vector2f translation = element->GetAbsoluteOffset(BoxArea::Padding).Round();
 	DecoratorTiledVerticalData* data = reinterpret_cast<DecoratorTiledVerticalData*>(element_data);
 
 	for (int i = 0; i < data->num_textures; i++)

+ 40 - 47
Source/Core/Element.cpp

@@ -117,7 +117,7 @@ Element::Element(const String& tag) :
 	owner_document = nullptr;
 	offset_parent = nullptr;
 
-	client_area = Box::PADDING;
+	client_area = BoxArea::Padding;
 
 	baseline = 0.0f;
 
@@ -222,7 +222,7 @@ void Element::Render()
 	// TODO: This is a work-around for the dirty offset not being properly updated when used by containing block 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 (absolute_offset_dirty)
-		GetAbsoluteOffset(Box::BORDER);
+		GetAbsoluteOffset(BoxArea::Border);
 
 	// Rebuild our stacking context if necessary.
 	if (stacking_context_dirty)
@@ -384,19 +384,19 @@ void Element::SetOffset(Vector2f offset, Element* _offset_parent, bool _offset_f
 	}
 }
 
-Vector2f Element::GetRelativeOffset(Box::Area area)
+Vector2f Element::GetRelativeOffset(BoxArea area)
 {
 	return relative_offset_base + relative_offset_position + GetBox().GetPosition(area);
 }
 
-Vector2f Element::GetAbsoluteOffset(Box::Area area)
+Vector2f Element::GetAbsoluteOffset(BoxArea area)
 {
 	if (absolute_offset_dirty)
 	{
 		absolute_offset_dirty = false;
 
 		if (offset_parent)
-			absolute_offset = offset_parent->GetAbsoluteOffset(Box::BORDER) + relative_offset_base + relative_offset_position;
+			absolute_offset = offset_parent->GetAbsoluteOffset(BoxArea::Border) + relative_offset_base + relative_offset_position;
 		else
 			absolute_offset = relative_offset_base + relative_offset_position;
 
@@ -415,12 +415,12 @@ Vector2f Element::GetAbsoluteOffset(Box::Area area)
 	return absolute_offset + GetBox().GetPosition(area);
 }
 
-void Element::SetClientArea(Box::Area _client_area)
+void Element::SetClientArea(BoxArea _client_area)
 {
 	client_area = _client_area;
 }
 
-Box::Area Element::GetClientArea() const
+BoxArea Element::GetClientArea() const
 {
 	return client_area;
 }
@@ -508,7 +508,7 @@ bool Element::IsReplaced()
 
 bool Element::IsPointWithinElement(const Vector2f point)
 {
-	const Vector2f position = GetAbsoluteOffset(Box::BORDER);
+	const Vector2f position = GetAbsoluteOffset(BoxArea::Border);
 
 	for (int i = 0; i < GetNumBoxes(); ++i)
 	{
@@ -516,7 +516,7 @@ bool Element::IsPointWithinElement(const Vector2f point)
 		const Box& box = GetBox(i, box_offset);
 
 		const Vector2f box_position = position + box_offset;
-		const Vector2f box_dimensions = box.GetSize(Box::BORDER);
+		const Vector2f box_dimensions = box.GetSize(BoxArea::Border);
 		if (point.x >= box_position.x && point.x <= (box_position.x + box_dimensions.x) && point.y >= box_position.y &&
 			point.y <= (box_position.y + box_dimensions.y))
 		{
@@ -608,26 +608,19 @@ const PropertyMap& Element::GetLocalStyleProperties()
 	return meta->style.GetLocalStyleProperties();
 }
 
-float Element::ResolveNumericProperty(const Property* property, float base_value)
+float Element::ResolveLength(NumericValue value)
 {
-	return meta->style.ResolveNumericProperty(property, base_value);
+	float result = 0.f;
+	if (Any(value.unit & Unit::LENGTH))
+		result = meta->style.ResolveNumericValue(value, 0.f);
+	return result;
 }
 
-float Element::ResolveNumericProperty(const String& property_name)
+float Element::ResolveNumericValue(NumericValue value, float base_value)
 {
-	auto property = meta->style.GetProperty(StyleSheetSpecification::GetPropertyId(property_name));
-	if (!property)
-		return 0.0f;
-
-	if (property->unit & Property::ANGLE)
-		return ComputeAngle(*property);
-
-	RelativeTarget relative_target = RelativeTarget::None;
-	if (property->definition)
-		relative_target = property->definition->GetRelativeTarget();
-
-	float result = meta->style.ResolveLength(property, relative_target);
-
+	float result = 0.f;
+	if (Any(value.unit & Unit::NUMERIC))
+		result = meta->style.ResolveNumericValue(value, base_value);
 	return result;
 }
 
@@ -647,7 +640,7 @@ Vector2f Element::GetContainingBlock()
 		}
 		else if (position_property == Position::Absolute || position_property == Position::Fixed)
 		{
-			containing_block = parent_box.GetSize(Box::PADDING);
+			containing_block = parent_box.GetSize(BoxArea::Padding);
 		}
 	}
 
@@ -852,12 +845,12 @@ void Element::SetId(const String& _id)
 
 float Element::GetAbsoluteLeft()
 {
-	return GetAbsoluteOffset(Box::BORDER).x;
+	return GetAbsoluteOffset(BoxArea::Border).x;
 }
 
 float Element::GetAbsoluteTop()
 {
-	return GetAbsoluteOffset(Box::BORDER).y;
+	return GetAbsoluteOffset(BoxArea::Border).y;
 }
 
 float Element::GetClientLeft()
@@ -897,12 +890,12 @@ float Element::GetOffsetTop()
 
 float Element::GetOffsetWidth()
 {
-	return GetBox().GetSize(Box::BORDER).x;
+	return GetBox().GetSize(BoxArea::Border).x;
 }
 
 float Element::GetOffsetHeight()
 {
-	return GetBox().GetSize(Box::BORDER).y;
+	return GetBox().GetSize(BoxArea::Border).y;
 }
 
 float Element::GetScrollLeft()
@@ -1190,7 +1183,7 @@ bool Element::DispatchEvent(EventId id, const Dictionary& parameters)
 
 void Element::ScrollIntoView(const ScrollIntoViewOptions options)
 {
-	const Vector2f size = main_box.GetSize(Box::BORDER);
+	const Vector2f size = main_box.GetSize(BoxArea::Border);
 	ScrollBehavior scroll_behavior = options.behavior;
 
 	for (Element* scroll_parent = parent; scroll_parent; scroll_parent = scroll_parent->GetParentNode())
@@ -1205,7 +1198,7 @@ void Element::ScrollIntoView(const ScrollIntoViewOptions options)
 
 		if ((scrollable_box_x && parent_scroll_size.x > parent_client_size.x) || (scrollable_box_y && parent_scroll_size.y > parent_client_size.y))
 		{
-			const Vector2f relative_offset = scroll_parent->GetAbsoluteOffset(Box::BORDER) - GetAbsoluteOffset(Box::BORDER);
+			const Vector2f relative_offset = scroll_parent->GetAbsoluteOffset(BoxArea::Border) - GetAbsoluteOffset(BoxArea::Border);
 
 			const Vector2f old_scroll_offset = {scroll_parent->GetScrollLeft(), scroll_parent->GetScrollTop()};
 			const Vector2f parent_client_offset = {scroll_parent->GetClientLeft(), scroll_parent->GetClientTop()};
@@ -2061,28 +2054,28 @@ void Element::UpdateOffset()
 		if (offset_parent != nullptr)
 		{
 			const Box& parent_box = offset_parent->GetBox();
-			Vector2f containing_block = parent_box.GetSize(Box::PADDING);
+			Vector2f containing_block = parent_box.GetSize(BoxArea::Padding);
 
 			// If the element is anchored left, then the position is offset by that resolved value.
 			if (computed.left().type != Left::Auto)
-				relative_offset_base.x = parent_box.GetEdge(Box::BORDER, Box::LEFT) +
-					(ResolveValue(computed.left(), containing_block.x) + GetBox().GetEdge(Box::MARGIN, Box::LEFT));
+				relative_offset_base.x = parent_box.GetEdge(BoxArea::Border, BoxEdge::Left) +
+					(ResolveValue(computed.left(), containing_block.x) + GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left));
 
 			// If the element is anchored right, then the position is set first so the element's right-most edge
 			// (including margins) will render up against the containing box's right-most content edge, and then
 			// offset by the resolved value.
 			else if (computed.right().type != Right::Auto)
 			{
-				relative_offset_base.x = containing_block.x + parent_box.GetEdge(Box::BORDER, Box::LEFT) -
-					(ResolveValue(computed.right(), containing_block.x) + GetBox().GetSize(Box::BORDER).x +
-						GetBox().GetEdge(Box::MARGIN, Box::RIGHT));
+				relative_offset_base.x = containing_block.x + parent_box.GetEdge(BoxArea::Border, BoxEdge::Left) -
+					(ResolveValue(computed.right(), containing_block.x) + GetBox().GetSize(BoxArea::Border).x +
+						GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right));
 			}
 
 			// If the element is anchored top, then the position is offset by that resolved value.
 			if (computed.top().type != Top::Auto)
 			{
-				relative_offset_base.y = parent_box.GetEdge(Box::BORDER, Box::TOP) +
-					(ResolveValue(computed.top(), containing_block.y) + GetBox().GetEdge(Box::MARGIN, Box::TOP));
+				relative_offset_base.y = parent_box.GetEdge(BoxArea::Border, BoxEdge::Top) +
+					(ResolveValue(computed.top(), containing_block.y) + GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top));
 			}
 
 			// If the element is anchored bottom, then the position is set first so the element's right-most edge
@@ -2090,9 +2083,9 @@ void Element::UpdateOffset()
 			// offset by the resolved value.
 			else if (computed.bottom().type != Bottom::Auto)
 			{
-				relative_offset_base.y = containing_block.y + parent_box.GetEdge(Box::BORDER, Box::TOP) -
-					(ResolveValue(computed.bottom(), containing_block.y) + GetBox().GetSize(Box::BORDER).y +
-						GetBox().GetEdge(Box::MARGIN, Box::BOTTOM));
+				relative_offset_base.y = containing_block.y + parent_box.GetEdge(BoxArea::Border, BoxEdge::Top) -
+					(ResolveValue(computed.bottom(), containing_block.y) + GetBox().GetSize(BoxArea::Border).y +
+						GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom));
 			}
 		}
 	}
@@ -2607,7 +2600,7 @@ void Element::AdvanceAnimations()
 		for (auto& animation : animations)
 		{
 			Property property = animation.UpdateAndGetProperty(time, *this);
-			if (property.unit != Property::UNKNOWN)
+			if (property.unit != Unit::UNKNOWN)
 				SetProperty(animation.GetPropertyId(), property);
 		}
 
@@ -2655,8 +2648,8 @@ void Element::UpdateTransformState()
 
 	const ComputedValues& computed = meta->computed_values;
 
-	const Vector2f pos = GetAbsoluteOffset(Box::BORDER);
-	const Vector2f size = GetBox().GetSize(Box::BORDER);
+	const Vector2f pos = GetAbsoluteOffset(BoxArea::Border);
+	const Vector2f size = GetBox().GetSize(BoxArea::Border);
 
 	bool perspective_or_transform_changed = false;
 
@@ -2817,7 +2810,7 @@ void Element::OnStyleSheetChangeRecursive()
 void Element::OnDpRatioChangeRecursive()
 {
 	GetElementDecoration()->DirtyDecorators();
-	GetStyle()->DirtyPropertiesWithUnits(Property::DP);
+	GetStyle()->DirtyPropertiesWithUnits(Unit::DP);
 
 	OnDpRatioChange();
 

+ 18 - 19
Source/Core/ElementAnimation.cpp

@@ -90,7 +90,7 @@ static bool CombineAndDecompose(Transform& t, Element& e)
 
 static Property InterpolateProperties(const Property& p0, const Property& p1, float alpha, Element& element, const PropertyDefinition* definition)
 {
-	if ((p0.unit & Property::NUMBER_LENGTH_PERCENT) && (p1.unit & Property::NUMBER_LENGTH_PERCENT))
+	if (Any(p0.unit & Unit::NUMBER_LENGTH_PERCENT) && Any(p1.unit & Unit::NUMBER_LENGTH_PERCENT))
 	{
 		if (p0.unit == p1.unit || !definition)
 		{
@@ -104,14 +104,14 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl
 		else
 		{
 			// Otherwise, convert units to pixels.
-			float f0 = element.GetStyle()->ResolveLength(&p0, definition->GetRelativeTarget());
-			float f1 = element.GetStyle()->ResolveLength(&p1, definition->GetRelativeTarget());
+			float f0 = element.GetStyle()->ResolveRelativeLength(p0.GetNumericValue(), definition->GetRelativeTarget());
+			float f1 = element.GetStyle()->ResolveRelativeLength(p1.GetNumericValue(), definition->GetRelativeTarget());
 			float f = (1.0f - alpha) * f0 + alpha * f1;
-			return Property{f, Property::PX};
+			return Property{f, Unit::PX};
 		}
 	}
 
-	if (p0.unit == Property::KEYWORD && p1.unit == Property::KEYWORD)
+	if (p0.unit == Unit::KEYWORD && p1.unit == Unit::KEYWORD)
 	{
 		// Discrete interpolation, swap at alpha = 0.5.
 		// Special case for the 'visibility' property as in the CSS specs:
@@ -127,17 +127,17 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl
 		return alpha < 0.5f ? p0 : p1;
 	}
 
-	if (p0.unit == Property::COLOUR && p1.unit == Property::COLOUR)
+	if (p0.unit == Unit::COLOUR && p1.unit == Unit::COLOUR)
 	{
 		Colourf c0 = ColourToLinearSpace(p0.value.Get<Colourb>());
 		Colourf c1 = ColourToLinearSpace(p1.value.Get<Colourb>());
 
 		Colourf c = c0 * (1.0f - alpha) + c1 * alpha;
 
-		return Property{ColourFromLinearSpace(c), Property::COLOUR};
+		return Property{ColourFromLinearSpace(c), Unit::COLOUR};
 	}
 
-	if (p0.unit == Property::TRANSFORM && p1.unit == Property::TRANSFORM)
+	if (p0.unit == Unit::TRANSFORM && p1.unit == Unit::TRANSFORM)
 	{
 		auto& t0 = p0.value.GetReference<TransformPtr>();
 		auto& t1 = p1.value.GetReference<TransformPtr>();
@@ -148,7 +148,7 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl
 		if (prim0.size() != prim1.size())
 		{
 			RMLUI_ERRORMSG("Transform primitives not of same size during interpolation. Were the transforms properly prepared for interpolation?");
-			return Property{t0, Property::TRANSFORM};
+			return Property{t0, Unit::TRANSFORM};
 		}
 
 		// Build the new, interpolating transform
@@ -161,15 +161,15 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl
 			if (!TransformUtilities::InterpolateWith(p, prim1[i], alpha))
 			{
 				RMLUI_ERRORMSG("Transform primitives can not be interpolated. Were the transforms properly prepared for interpolation?");
-				return Property{t0, Property::TRANSFORM};
+				return Property{t0, Unit::TRANSFORM};
 			}
 			t->AddPrimitive(p);
 		}
 
-		return Property{TransformPtr(std::move(t)), Property::TRANSFORM};
+		return Property{TransformPtr(std::move(t)), Unit::TRANSFORM};
 	}
 
-	if (p0.unit == Property::DECORATOR && p1.unit == Property::DECORATOR)
+	if (p0.unit == Unit::DECORATOR && p1.unit == Unit::DECORATOR)
 	{
 		auto DiscreteInterpolation = [&]() { return alpha < 0.5f ? p0 : p1; };
 
@@ -285,7 +285,7 @@ static Property InterpolateProperties(const Property& p0, const Property& p1, fl
 			}
 		}
 
-		return Property{DecoratorsPtr(std::move(decorator)), Property::DECORATOR};
+		return Property{DecoratorsPtr(std::move(decorator)), Unit::DECORATOR};
 	}
 
 	// Fall back to discrete interpolation for incompatible units.
@@ -481,7 +481,7 @@ static bool PrepareTransforms(Vector<AnimationKey>& keys, Element& element, int
 		auto& prop0 = keys[i - 1].property;
 		auto& prop1 = keys[i].property;
 
-		if (prop0.unit != Property::TRANSFORM || prop1.unit != Property::TRANSFORM)
+		if (prop0.unit != Unit::TRANSFORM || prop1.unit != Unit::TRANSFORM)
 			return false;
 
 		auto& t0 = prop0.value.GetReference<TransformPtr>();
@@ -534,10 +534,9 @@ ElementAnimation::ElementAnimation(PropertyId property_id, ElementAnimationOrigi
 
 bool ElementAnimation::InternalAddKey(float time, const Property& in_property, Element& element, Tween tween)
 {
-	int valid_properties =
-		(Property::NUMBER_LENGTH_PERCENT | Property::ANGLE | Property::COLOUR | Property::TRANSFORM | Property::KEYWORD | Property::DECORATOR);
+	const Units valid_units = (Unit::NUMBER_LENGTH_PERCENT | Unit::ANGLE | Unit::COLOUR | Unit::TRANSFORM | Unit::KEYWORD | Unit::DECORATOR);
 
-	if (!(in_property.unit & valid_properties))
+	if (!Any(in_property.unit & valid_units))
 	{
 		Log::Message(Log::LT_WARNING, "Property value '%s' is not a valid target for interpolation.", in_property.ToString().c_str());
 		return false;
@@ -546,11 +545,11 @@ bool ElementAnimation::InternalAddKey(float time, const Property& in_property, E
 	keys.emplace_back(time, in_property, tween);
 	bool result = true;
 
-	if (keys.back().property.unit == Property::TRANSFORM)
+	if (keys.back().property.unit == Unit::TRANSFORM)
 	{
 		result = PrepareTransforms(keys, element, (int)keys.size() - 1);
 	}
-	else if (keys.back().property.unit == Property::DECORATOR)
+	else if (keys.back().property.unit == Unit::DECORATOR)
 	{
 		PrepareDecorator(keys.back());
 	}

+ 1 - 1
Source/Core/ElementBackgroundBorder.cpp

@@ -47,7 +47,7 @@ void ElementBackgroundBorder::Render(Element* element)
 	}
 
 	if (geometry)
-		geometry.Render(element->GetAbsoluteOffset(Box::BORDER));
+		geometry.Render(element->GetAbsoluteOffset(BoxArea::Border));
 }
 
 void ElementBackgroundBorder::DirtyBackground()

+ 1 - 1
Source/Core/ElementDecoration.cpp

@@ -63,7 +63,7 @@ bool ElementDecoration::ReloadDecorators()
 		return true;
 
 	const Property* property = element->GetLocalProperty(PropertyId::Decorator);
-	if (!property || property->unit != Property::DECORATOR)
+	if (!property || property->unit != Unit::DECORATOR)
 		return false;
 
 	DecoratorsPtr decorators_ptr = property->Get<DecoratorsPtr>();

+ 6 - 6
Source/Core/ElementDocument.cpp

@@ -435,17 +435,17 @@ void ElementDocument::UpdatePosition()
 		if (computed.left().type != Style::Left::Auto)
 			position.x = ResolveValue(computed.left(), containing_block.x);
 		else if (computed.right().type != Style::Right::Auto)
-			position.x = containing_block.x - (box.GetSize(Box::MARGIN).x + ResolveValue(computed.right(), containing_block.x));
+			position.x = containing_block.x - (box.GetSize(BoxArea::Margin).x + ResolveValue(computed.right(), containing_block.x));
 
 		if (computed.top().type != Style::Top::Auto)
 			position.y = ResolveValue(computed.top(), containing_block.y);
 		else if (computed.bottom().type != Style::Bottom::Auto)
-			position.y = containing_block.y - (box.GetSize(Box::MARGIN).y + ResolveValue(computed.bottom(), containing_block.y));
+			position.y = containing_block.y - (box.GetSize(BoxArea::Margin).y + ResolveValue(computed.bottom(), containing_block.y));
 
 		// Add the margin edge to the position, since inset properties (top/right/bottom/left) set the margin edge
 		// position, while offsets use the border edge.
-		position.x += box.GetEdge(Box::MARGIN, Box::LEFT);
-		position.y += box.GetEdge(Box::MARGIN, Box::TOP);
+		position.x += box.GetEdge(BoxArea::Margin, BoxEdge::Left);
+		position.y += box.GetEdge(BoxArea::Margin, BoxEdge::Top);
 
 		SetOffset(position, nullptr);
 	}
@@ -468,7 +468,7 @@ bool ElementDocument::IsLayoutDirty()
 
 void ElementDocument::DirtyVwAndVhProperties()
 {
-	GetStyle()->DirtyPropertiesWithUnitsRecursive(Property::VW | Property::VH);
+	GetStyle()->DirtyPropertiesWithUnitsRecursive(Unit::VW | Unit::VH);
 }
 
 void ElementDocument::OnPropertyChange(const PropertyIdSet& changed_properties)
@@ -477,7 +477,7 @@ void ElementDocument::OnPropertyChange(const PropertyIdSet& changed_properties)
 
 	// If the document's font-size has been changed, we need to dirty all rem properties.
 	if (changed_properties.Contains(PropertyId::FontSize))
-		GetStyle()->DirtyPropertiesWithUnitsRecursive(Property::REM);
+		GetStyle()->DirtyPropertiesWithUnitsRecursive(Unit::REM);
 
 	if (changed_properties.Contains(PropertyId::Top) ||    //
 		changed_properties.Contains(PropertyId::Right) ||  //

+ 15 - 15
Source/Core/ElementHandle.cpp

@@ -81,23 +81,23 @@ void ElementHandle::ProcessDefaultAction(Event& event)
 		}
 
 		auto GetSize = [](const Box& box, const ComputedValues& computed) {
-			return box.GetSize((computed.box_sizing() == Style::BoxSizing::BorderBox) ? Box::BORDER : Box::CONTENT);
+			return box.GetSize((computed.box_sizing() == Style::BoxSizing::BorderBox) ? BoxArea::Border : BoxArea::Content);
 		};
 
 		// Set any auto margins to their current value, since auto-margins may affect the size and position of an element.
 		auto SetDefiniteMargins = [](Element* element, const ComputedValues& computed) {
-			auto SetDefiniteMargin = [](Element* element, PropertyId margin_id, Box::Edge edge) {
-				element->SetProperty(margin_id, Property(Math::Round(element->GetBox().GetEdge(Box::MARGIN, edge)), Property::PX));
+			auto SetDefiniteMargin = [](Element* element, PropertyId margin_id, BoxEdge edge) {
+				element->SetProperty(margin_id, Property(Math::Round(element->GetBox().GetEdge(BoxArea::Margin, edge)), Unit::PX));
 			};
 			using Style::Margin;
 			if (computed.margin_top().type == Margin::Auto)
-				SetDefiniteMargin(element, PropertyId::MarginTop, Box::TOP);
+				SetDefiniteMargin(element, PropertyId::MarginTop, BoxEdge::Top);
 			if (computed.margin_right().type == Margin::Auto)
-				SetDefiniteMargin(element, PropertyId::MarginRight, Box::RIGHT);
+				SetDefiniteMargin(element, PropertyId::MarginRight, BoxEdge::Right);
 			if (computed.margin_bottom().type == Margin::Auto)
-				SetDefiniteMargin(element, PropertyId::MarginBottom, Box::BOTTOM);
+				SetDefiniteMargin(element, PropertyId::MarginBottom, BoxEdge::Bottom);
 			if (computed.margin_left().type == Margin::Auto)
-				SetDefiniteMargin(element, PropertyId::MarginLeft, Box::LEFT);
+				SetDefiniteMargin(element, PropertyId::MarginLeft, BoxEdge::Left);
 		};
 
 		if (event == EventId::Dragstart)
@@ -113,14 +113,14 @@ void ElementHandle::ProcessDefaultAction(Event& event)
 				const auto& computed = move_target->GetComputedValues();
 
 				// Store the initial margin edge position, since top/left properties determine the margin position.
-				move_original_position.x = move_target->GetOffsetLeft() - box.GetEdge(Box::MARGIN, Box::LEFT);
-				move_original_position.y = move_target->GetOffsetTop() - box.GetEdge(Box::MARGIN, Box::TOP);
+				move_original_position.x = move_target->GetOffsetLeft() - box.GetEdge(BoxArea::Margin, BoxEdge::Left);
+				move_original_position.y = move_target->GetOffsetTop() - box.GetEdge(BoxArea::Margin, BoxEdge::Top);
 
 				// Check if we have auto-size together with definite right/bottom; if so, the size needs to be fixed to the current size.
 				if (computed.width().type == Width::Auto && computed.right().type != Right::Auto)
-					move_target->SetProperty(PropertyId::Width, Property(Math::Round(GetSize(box, computed).x), Property::PX));
+					move_target->SetProperty(PropertyId::Width, Property(Math::Round(GetSize(box, computed).x), Unit::PX));
 				if (computed.height().type == Height::Auto && computed.bottom().type != Bottom::Auto)
-					move_target->SetProperty(PropertyId::Height, Property(Math::Round(GetSize(box, computed).y), Property::PX));
+					move_target->SetProperty(PropertyId::Height, Property(Math::Round(GetSize(box, computed).y), Unit::PX));
 
 				SetDefiniteMargins(move_target, computed);
 			}
@@ -142,15 +142,15 @@ void ElementHandle::ProcessDefaultAction(Event& event)
 			if (move_target)
 			{
 				const Vector2f new_position = (move_original_position + delta).Round();
-				move_target->SetProperty(PropertyId::Left, Property(new_position.x, Property::PX));
-				move_target->SetProperty(PropertyId::Top, Property(new_position.y, Property::PX));
+				move_target->SetProperty(PropertyId::Left, Property(new_position.x, Unit::PX));
+				move_target->SetProperty(PropertyId::Top, Property(new_position.y, Unit::PX));
 			}
 
 			if (size_target)
 			{
 				const Vector2f new_size = Math::Max((size_original_size + delta).Round(), Vector2f(0.f));
-				size_target->SetProperty(PropertyId::Width, Property(new_size.x, Property::PX));
-				size_target->SetProperty(PropertyId::Height, Property(new_size.y, Property::PX));
+				size_target->SetProperty(PropertyId::Width, Property(new_size.x, Unit::PX));
+				size_target->SetProperty(PropertyId::Height, Property(new_size.y, Unit::PX));
 			}
 
 			Dictionary parameters;

+ 13 - 11
Source/Core/ElementScroll.cpp

@@ -69,14 +69,15 @@ void ElementScroll::EnableScrollbar(Orientation orientation, float element_width
 	LayoutDetails::BuildBox(box, Vector2f(element_width, element_width), scrollbars[orientation].element);
 
 	if (orientation == VERTICAL)
-		scrollbars[orientation].size = box.GetSize(Box::MARGIN).x;
+		scrollbars[orientation].size = box.GetSize(BoxArea::Margin).x;
 	if (orientation == HORIZONTAL)
 	{
 		if (box.GetSize().y < 0)
-			scrollbars[orientation].size = box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) + box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT) +
+			scrollbars[orientation].size = box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Left) +
+				box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Right) +
 				ResolveValue(scrollbars[orientation].element->GetComputedValues().height(), element_width);
 		else
-			scrollbars[orientation].size = box.GetSize(Box::MARGIN).y;
+			scrollbars[orientation].size = box.GetSize(BoxArea::Margin).y;
 	}
 }
 
@@ -137,7 +138,7 @@ float ElementScroll::GetScrollbarSize(Orientation orientation)
 void ElementScroll::FormatScrollbars()
 {
 	const Box& element_box = element->GetBox();
-	const Vector2f containing_block = element_box.GetSize(Box::PADDING);
+	const Vector2f containing_block = element_box.GetSize(BoxArea::Padding);
 
 	for (int i = 0; i < 2; i++)
 	{
@@ -175,12 +176,12 @@ void ElementScroll::FormatScrollbars()
 		scrollbars[i].widget->FormatElements(containing_block, slider_length);
 
 		int variable_axis = i == VERTICAL ? 0 : 1;
-		Vector2f offset = element_box.GetPosition(Box::PADDING);
+		Vector2f offset = element_box.GetPosition(BoxArea::Padding);
 		offset[variable_axis] += containing_block[variable_axis] -
-			(scrollbars[i].element->GetBox().GetSize(Box::BORDER)[variable_axis] +
-				scrollbars[i].element->GetBox().GetEdge(Box::MARGIN, i == VERTICAL ? Box::RIGHT : Box::BOTTOM));
+			(scrollbars[i].element->GetBox().GetSize(BoxArea::Border)[variable_axis] +
+				scrollbars[i].element->GetBox().GetEdge(BoxArea::Margin, i == VERTICAL ? BoxEdge::Right : BoxEdge::Bottom));
 		// Add the top or left margin (as appropriate) onto the scrollbar's position.
-		offset[1 - variable_axis] += scrollbars[i].element->GetBox().GetEdge(Box::MARGIN, i == VERTICAL ? Box::TOP : Box::LEFT);
+		offset[1 - variable_axis] += scrollbars[i].element->GetBox().GetEdge(BoxArea::Margin, i == VERTICAL ? BoxEdge::Top : BoxEdge::Left);
 		scrollbars[i].element->SetOffset(offset, element, true);
 	}
 
@@ -192,7 +193,8 @@ void ElementScroll::FormatScrollbars()
 		Box corner_box;
 		corner_box.SetContent(Vector2f(scrollbars[VERTICAL].size, scrollbars[HORIZONTAL].size));
 		corner->SetBox(corner_box);
-		corner->SetOffset(containing_block + element_box.GetPosition(Box::PADDING) - Vector2f(scrollbars[VERTICAL].size, scrollbars[HORIZONTAL].size),
+		corner->SetOffset(containing_block + element_box.GetPosition(BoxArea::Padding) -
+				Vector2f(scrollbars[VERTICAL].size, scrollbars[HORIZONTAL].size),
 			element, true);
 
 		corner->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Visible));
@@ -207,7 +209,7 @@ bool ElementScroll::CreateScrollbar(Orientation orientation)
 	ElementPtr scrollbar_element =
 		Factory::InstanceElement(element, "*", orientation == VERTICAL ? "scrollbarvertical" : "scrollbarhorizontal", XMLAttributes());
 	scrollbars[orientation].element = scrollbar_element.get();
-	scrollbars[orientation].element->SetProperty(PropertyId::Clip, Property(1, Property::NUMBER));
+	scrollbars[orientation].element->SetProperty(PropertyId::Clip, Property(1, Unit::NUMBER));
 
 	scrollbars[orientation].widget = MakeUnique<WidgetScroll>(scrollbars[orientation].element);
 	scrollbars[orientation].widget->Initialise(orientation == VERTICAL ? WidgetScroll::VERTICAL : WidgetScroll::HORIZONTAL);
@@ -226,7 +228,7 @@ bool ElementScroll::CreateCorner()
 
 	ElementPtr corner_element = Factory::InstanceElement(element, "*", "scrollbarcorner", XMLAttributes());
 	corner = corner_element.get();
-	corner->SetProperty(PropertyId::Clip, Property(1, Property::NUMBER));
+	corner->SetProperty(PropertyId::Clip, Property(1, Unit::NUMBER));
 
 	Element* child = element->AppendChild(std::move(corner_element), false);
 	UpdateScrollElementProperties(child);

+ 60 - 51
Source/Core/ElementStyle.cpp

@@ -345,53 +345,60 @@ const PropertyMap& ElementStyle::GetLocalStyleProperties() const
 	return inline_properties.GetProperties();
 }
 
-static float ComputeLength(const Property* property, Element* element)
+static float ComputeLength(NumericValue value, Element* element)
 {
-	const float font_size = element->GetComputedValues().font_size();
-	float doc_font_size = DefaultComputedValues.font_size();
+	float font_size = 0.f;
+	float doc_font_size = 0.f;
 	float dp_ratio = 1.0f;
 	Vector2f vp_dimensions(1.0f);
 
-	if (ElementDocument* document = element->GetOwnerDocument())
+	switch (value.unit)
 	{
-		doc_font_size = document->GetComputedValues().font_size();
-
-		if (Context* context = document->GetContext())
-		{
+	case Unit::EM: font_size = element->GetComputedValues().font_size(); break;
+	case Unit::REM: doc_font_size = DefaultComputedValues.font_size(); break;
+	case Unit::DP:
+		if (Context* context = element->GetContext())
 			dp_ratio = context->GetDensityIndependentPixelRatio();
+		break;
+	case Unit::VW:
+	case Unit::VH:
+		if (Context* context = element->GetContext())
 			vp_dimensions = Vector2f(context->GetDimensions());
-		}
+		break;
+	default: break;
 	}
 
-	const float result = ComputeLength(property, font_size, doc_font_size, dp_ratio, vp_dimensions);
+	const float result = ComputeLength(value, font_size, doc_font_size, dp_ratio, vp_dimensions);
 	return result;
 }
 
-float ElementStyle::ResolveNumericProperty(const Property* property, float base_value) const
+float ElementStyle::ResolveNumericValue(NumericValue value, float base_value) const
 {
-	if (!property || !(property->unit & (Property::NUMBER_LENGTH_PERCENT | Property::ANGLE)))
-		return 0.0f;
-
-	if (property->unit & Property::NUMBER)
-		return property->Get<float>() * base_value;
-	else if (property->unit & Property::PERCENT)
-		return property->Get<float>() * base_value * 0.01f;
-	else if (property->unit & Property::ANGLE)
-		return ComputeAngle(*property);
+	if (Any(value.unit & Unit::ABSOLUTE_LENGTH))
+		return ComputeAbsoluteLength(value);
+	else if (Any(value.unit & Unit::LENGTH))
+		return ComputeLength(value, element);
 
-	const float result = ComputeLength(property, element);
+	switch (value.unit)
+	{
+	case Unit::NUMBER: return value.number * base_value;
+	case Unit::PERCENT: return value.number * base_value * 0.01f;
+	case Unit::X: return value.number;
+	case Unit::DEG:
+	case Unit::RAD: return ComputeAngle(value);
+	default: break;
+	}
 
-	return result;
+	RMLUI_ERROR;
+	return 0.f;
 }
 
-float ElementStyle::ResolveLength(const Property* property, RelativeTarget relative_target) const
+float ElementStyle::ResolveRelativeLength(NumericValue value, RelativeTarget relative_target) const
 {
-	RMLUI_ASSERT(property);
-
 	// There is an exception on font-size properties, as 'em' units here refer to parent font size instead
-	if ((property->unit & Property::LENGTH) && !(property->unit == Property::EM && relative_target == RelativeTarget::ParentFontSize))
+	if (Any(value.unit & Unit::LENGTH) && !(value.unit == Unit::EM && relative_target == RelativeTarget::ParentFontSize))
 	{
-		const float result = ComputeLength(property, element);
+		const float result = ComputeLength(value, element);
 		return result;
 	}
 
@@ -414,11 +421,11 @@ float ElementStyle::ResolveLength(const Property* property, RelativeTarget relat
 
 	float scale_value = 0.0f;
 
-	switch (property->unit)
+	switch (value.unit)
 	{
-	case Property::EM:
-	case Property::NUMBER: scale_value = property->value.Get<float>(); break;
-	case Property::PERCENT: scale_value = property->value.Get<float>() * 0.01f; break;
+	case Unit::EM:
+	case Unit::NUMBER: scale_value = value.number; break;
+	case Unit::PERCENT: scale_value = value.number * 0.01f; break;
 	default: break;
 	}
 
@@ -430,7 +437,7 @@ void ElementStyle::DirtyInheritedProperties()
 	dirty_properties |= StyleSheetSpecification::GetRegisteredInheritedProperties();
 }
 
-void ElementStyle::DirtyPropertiesWithUnits(Property::Unit units)
+void ElementStyle::DirtyPropertiesWithUnits(Units units)
 {
 	// Dirty all the properties of this element that use the unit(s).
 	for (auto it = Iterate(); !it.AtEnd(); ++it)
@@ -438,12 +445,12 @@ void ElementStyle::DirtyPropertiesWithUnits(Property::Unit units)
 		auto name_property_pair = *it;
 		PropertyId id = name_property_pair.first;
 		const Property& property = name_property_pair.second;
-		if (property.unit & units)
+		if (Any(property.unit & units))
 			DirtyProperty(id);
 	}
 }
 
-void ElementStyle::DirtyPropertiesWithUnitsRecursive(Property::Unit units)
+void ElementStyle::DirtyPropertiesWithUnitsRecursive(Units units)
 {
 	DirtyPropertiesWithUnits(units);
 
@@ -531,7 +538,7 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 	if (dirty_properties.Contains(PropertyId::FontSize))
 	{
 		if (auto p = GetLocalProperty(PropertyId::FontSize))
-			values.font_size(ComputeFontsize(*p, values, parent_values, document_values, dp_ratio, vp_dimensions));
+			values.font_size(ComputeFontsize(p->GetNumericValue(), values, parent_values, document_values, dp_ratio, vp_dimensions));
 		else if (parent_values)
 			values.font_size(parent_values->font_size());
 
@@ -583,7 +590,7 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 		const PropertyId id = name_property_pair.first;
 		const Property* p = &name_property_pair.second;
 
-		if (dirty_em_properties && p->unit == Property::EM)
+		if (dirty_em_properties && p->unit == Unit::EM)
 			dirty_properties.Insert(id);
 
 		using namespace Style;
@@ -618,16 +625,18 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 			break;
 
 		case PropertyId::BorderTopWidth:
-			values.border_top_width(ComputeBorderWidth(ComputeLength(p, font_size, document_font_size, dp_ratio, vp_dimensions)));
+			values.border_top_width(ComputeBorderWidth(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)));
 			break;
 		case PropertyId::BorderRightWidth:
-			values.border_right_width(ComputeBorderWidth(ComputeLength(p, font_size, document_font_size, dp_ratio, vp_dimensions)));
+			values.border_right_width(
+				ComputeBorderWidth(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)));
 			break;
 		case PropertyId::BorderBottomWidth:
-			values.border_bottom_width(ComputeBorderWidth(ComputeLength(p, font_size, document_font_size, dp_ratio, vp_dimensions)));
+			values.border_bottom_width(
+				ComputeBorderWidth(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)));
 			break;
 		case PropertyId::BorderLeftWidth:
-			values.border_left_width(ComputeBorderWidth(ComputeLength(p, font_size, document_font_size, dp_ratio, vp_dimensions)));
+			values.border_left_width(ComputeBorderWidth(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions)));
 			break;
 
 		case PropertyId::BorderTopColor:
@@ -644,16 +653,16 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 			break;
 
 		case PropertyId::BorderTopLeftRadius:
-			values.border_top_left_radius(ComputeLength(p, font_size, document_font_size, dp_ratio, vp_dimensions));
+			values.border_top_left_radius(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions));
 			break;
 		case PropertyId::BorderTopRightRadius:
-			values.border_top_right_radius(ComputeLength(p, font_size, document_font_size, dp_ratio, vp_dimensions));
+			values.border_top_right_radius(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions));
 			break;
 		case PropertyId::BorderBottomRightRadius:
-			values.border_bottom_right_radius(ComputeLength(p, font_size, document_font_size, dp_ratio, vp_dimensions));
+			values.border_bottom_right_radius(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions));
 			break;
 		case PropertyId::BorderBottomLeftRadius:
-			values.border_bottom_left_radius(ComputeLength(p, font_size, document_font_size, dp_ratio, vp_dimensions));
+			values.border_bottom_left_radius(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions));
 			break;
 
 		case PropertyId::Display:
@@ -687,7 +696,7 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 			break;
 
 		case PropertyId::ZIndex:
-			values.z_index((p->unit == Property::KEYWORD ? ZIndex(ZIndex::Auto) : ZIndex(ZIndex::Number, p->Get<float>())));
+			values.z_index((p->unit == Unit::KEYWORD ? ZIndex(ZIndex::Auto) : ZIndex(ZIndex::Number, p->Get<float>())));
 			break;
 
 		case PropertyId::Width:
@@ -760,7 +769,7 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 			dirty_font_face_handle = true;
 			break;
 		case PropertyId::LetterSpacing:
-			values.has_letter_spacing(p->unit != Property::KEYWORD);
+			values.has_letter_spacing(p->unit != Unit::KEYWORD);
 			dirty_font_face_handle = true;
 			break;
 
@@ -797,7 +806,7 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 			values.focus((Focus)p->Get<int>());
 			break;
 		case PropertyId::ScrollbarMargin:
-			values.scrollbar_margin(ComputeLength(p, font_size, document_font_size, dp_ratio, vp_dimensions));
+			values.scrollbar_margin(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions));
 			break;
 		case PropertyId::OverscrollBehavior:
 			values.overscroll_behavior((OverscrollBehavior)p->Get<int>());
@@ -807,7 +816,7 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 			break;
 
 		case PropertyId::Perspective:
-			values.perspective(ComputeLength(p, font_size, document_font_size, dp_ratio, vp_dimensions));
+			values.perspective(p->unit == Unit::KEYWORD ? 0.f : ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions));
 			values.has_local_perspective(values.perspective() > 0.f);
 			break;
 		case PropertyId::PerspectiveOriginX:
@@ -827,14 +836,14 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 			values.transform_origin_y(ComputeOrigin(p, font_size, document_font_size, dp_ratio, vp_dimensions));
 			break;
 		case PropertyId::TransformOriginZ:
-			values.transform_origin_z(ComputeLength(p, font_size, document_font_size, dp_ratio, vp_dimensions));
+			values.transform_origin_z(ComputeLength(p->GetNumericValue(), font_size, document_font_size, dp_ratio, vp_dimensions));
 			break;
 
 		case PropertyId::Decorator:
-			values.has_decorator(p->unit == Property::DECORATOR);
+			values.has_decorator(p->unit == Unit::DECORATOR);
 			break;
 		case PropertyId::FontEffect:
-			values.has_font_effect((p->unit == Property::FONTEFFECT));
+			values.has_font_effect((p->unit == Unit::FONTEFFECT));
 			break;
 		case PropertyId::FlexBasis:
 			values.flex_basis(ComputeLengthPercentageAuto(p, font_size, document_font_size, dp_ratio, vp_dimensions));

+ 6 - 6
Source/Core/ElementStyle.h

@@ -110,14 +110,14 @@ public:
 	/// Returns the local style properties, excluding any properties from local class.
 	const PropertyMap& GetLocalStyleProperties() const;
 
-	/// Resolves a property with units of number, percentage, length, or angle to their canonical unit (unit-less, 'px', or 'rad').
-	/// @param[in] property The property to resolve the value for.
+	/// Resolves a numeric value with units of number, percentage, length, or angle to their canonical unit (unit-less, 'px', or 'rad').
+	/// @param[in] value The value to be resolved.
 	/// @param[in] base_value The value that is scaled by the number or percentage value, if applicable.
 	/// @return The resolved value in their canonical unit, or zero if it could not be resolved.
-	float ResolveNumericProperty(const Property* property, float base_value) const;
+	float ResolveNumericValue(NumericValue value, float base_value) const;
 	/// Resolves a property with units of number, length, or percentage to a length in 'px' units.
 	/// Numbers and percentages are resolved by scaling the size of the specified target.
-	float ResolveLength(const Property* property, RelativeTarget relative_target) const;
+	float ResolveRelativeLength(NumericValue value, RelativeTarget relative_target) const;
 
 	/// Mark inherited properties dirty.
 	/// Inherited properties will automatically be set when parent inherited properties are changed. However,
@@ -127,9 +127,9 @@ public:
 	// Sets a single property as dirty.
 	void DirtyProperty(PropertyId id);
 	/// Dirties all properties with any of the given units (OR-ed together) on the current element (*not* recursive).
-	void DirtyPropertiesWithUnits(Property::Unit units);
+	void DirtyPropertiesWithUnits(Units units);
 	/// Dirties all properties with any of the given units (OR-ed together) on the current element and recursively on all children.
-	void DirtyPropertiesWithUnitsRecursive(Property::Unit units);
+	void DirtyPropertiesWithUnitsRecursive(Units units);
 
 	/// Returns true if any properties are dirty such that computed values need to be recomputed
 	bool AnyPropertiesDirty() const;

+ 6 - 6
Source/Core/ElementUtilities.cpp

@@ -67,10 +67,10 @@ static void SetBox(Element* element)
 // Positions an element relative to an offset parent.
 static void SetElementOffset(Element* element, Vector2f offset)
 {
-	Vector2f relative_offset = element->GetParentNode()->GetBox().GetPosition(Box::CONTENT);
+	Vector2f relative_offset = element->GetParentNode()->GetBox().GetPosition(BoxArea::Content);
 	relative_offset += offset;
-	relative_offset.x += element->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-	relative_offset.y += element->GetBox().GetEdge(Box::MARGIN, Box::TOP);
+	relative_offset.x += element->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left);
+	relative_offset.y += element->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top);
 
 	element->SetOffset(relative_offset, element->GetParentNode());
 }
@@ -196,7 +196,7 @@ bool ElementUtilities::GetClippingRegion(Vector2i& clip_origin, Vector2i& clip_d
 			if (clip_always || clipping_element->GetClientWidth() < clipping_element->GetScrollWidth() - 0.5f ||
 				clipping_element->GetClientHeight() < clipping_element->GetScrollHeight() - 0.5f)
 			{
-				const Box::Area client_area = clipping_element->GetClientArea();
+				const BoxArea client_area = clipping_element->GetClientArea();
 				Vector2f element_origin_f = clipping_element->GetAbsoluteOffset(client_area);
 				Vector2f element_dimensions_f = clipping_element->GetBox().GetSize(client_area);
 				Math::SnapToPixelGrid(element_origin_f, element_dimensions_f);
@@ -297,8 +297,8 @@ bool ElementUtilities::PositionElement(Element* element, Vector2f offset, Positi
 
 	SetBox(element);
 
-	Vector2f containing_block = element->GetParentNode()->GetBox().GetSize(Box::CONTENT);
-	Vector2f element_block = element->GetBox().GetSize(Box::MARGIN);
+	Vector2f containing_block = element->GetParentNode()->GetBox().GetSize(BoxArea::Content);
+	Vector2f element_block = element->GetBox().GetSize(BoxArea::Margin);
 
 	Vector2f resolved_offset = offset;
 

+ 2 - 2
Source/Core/Elements/ElementImage.cpp

@@ -86,7 +86,7 @@ void ElementImage::OnRender()
 		GenerateGeometry();
 
 	// Render the geometry beginning at this element's content region.
-	geometry.Render(GetAbsoluteOffset(Box::CONTENT));
+	geometry.Render(GetAbsoluteOffset(BoxArea::Content).Round());
 }
 
 void ElementImage::OnAttributeChange(const ElementAttributes& changed_attributes)
@@ -197,7 +197,7 @@ void ElementImage::GenerateGeometry()
 	Colourb quad_colour = computed.image_color();
 	quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha);
 
-	Vector2f quad_size = GetBox().GetSize(Box::CONTENT).Round();
+	Vector2f quad_size = GetBox().GetSize(BoxArea::Content).Round();
 
 	GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), quad_size, quad_colour, texcoords[0], texcoords[1]);
 

+ 2 - 2
Source/Core/Elements/ElementProgress.cpp

@@ -165,8 +165,8 @@ void ElementProgress::OnResize()
 
 	ElementUtilities::BuildBox(fill_box, element_size, fill);
 
-	const Vector2f margin_top_left(fill_box.GetEdge(Box::MARGIN, Box::LEFT), fill_box.GetEdge(Box::MARGIN, Box::TOP));
-	const Vector2f edge_size = fill_box.GetSize(Box::MARGIN) - fill_box.GetSize(Box::CONTENT);
+	const Vector2f margin_top_left(fill_box.GetEdge(BoxArea::Margin, BoxEdge::Left), fill_box.GetEdge(BoxArea::Margin, BoxEdge::Top));
+	const Vector2f edge_size = fill_box.GetSize(BoxArea::Margin) - fill_box.GetSize(BoxArea::Content);
 
 	fill_offset = GetBox().GetPosition() + margin_top_left;
 	fill_size = element_size - edge_size;

+ 15 - 15
Source/Core/Elements/WidgetDropDown.cpp

@@ -61,7 +61,7 @@ WidgetDropDown::WidgetDropDown(ElementFormControl* element)
 	value_element->SetProperty(PropertyId::OverflowY, Property(Style::Overflow::Hidden));
 
 	selection_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
-	selection_element->SetProperty(PropertyId::ZIndex, Property(1.0f, Property::NUMBER));
+	selection_element->SetProperty(PropertyId::ZIndex, Property(1.0f, Unit::NUMBER));
 	selection_element->SetProperty(PropertyId::Clip, Property(Style::Clip::Type::None));
 	selection_element->SetProperty(PropertyId::OverflowY, Property(Style::Overflow::Auto));
 
@@ -163,22 +163,22 @@ void WidgetDropDown::OnRender()
 
 		// The user can use 'margin-left/top/bottom' to offset the box away from the 'select' element, respectively
 		// horizontally, vertically when box below, and vertically when box above.
-		const float offset_x = box.GetEdge(Box::MARGIN, Box::LEFT);
-		const float offset_y_below = parent_element->GetBox().GetSize(Box::BORDER).y + box.GetEdge(Box::MARGIN, Box::TOP);
-		const float offset_y_above = -box.GetEdge(Box::MARGIN, Box::BOTTOM);
+		const float offset_x = box.GetEdge(BoxArea::Margin, BoxEdge::Left);
+		const float offset_y_below = parent_element->GetBox().GetSize(BoxArea::Border).y + box.GetEdge(BoxArea::Margin, BoxEdge::Top);
+		const float offset_y_above = -box.GetEdge(BoxArea::Margin, BoxEdge::Bottom);
 
 		float window_height = 100'000.f;
 		if (Context* context = parent_element->GetContext())
 			window_height = float(context->GetDimensions().y);
 
-		const float absolute_y = parent_element->GetAbsoluteOffset(Box::BORDER).y;
+		const float absolute_y = parent_element->GetAbsoluteOffset(BoxArea::Border).y;
 
 		const float height_below = window_height - absolute_y - offset_y_below;
 		const float height_above = absolute_y + offset_y_above;
 
 		// Format the selection box and retrieve the 'native' height occupied by all the options, while respecting
 		// the 'min/max-height' properties.
-		ElementUtilities::FormatElement(selection_element, parent_element->GetBox().GetSize(Box::BORDER));
+		ElementUtilities::FormatElement(selection_element, parent_element->GetBox().GetSize(BoxArea::Border));
 		const float content_height = selection_element->GetOffsetHeight();
 
 		if (content_height < height_below)
@@ -194,8 +194,8 @@ void WidgetDropDown::OnRender()
 		else
 		{
 			// Shrink box and position either below or above
-			const float padding_border_size = box.GetEdge(Box::BORDER, Box::TOP) + box.GetEdge(Box::BORDER, Box::BOTTOM) +
-				box.GetEdge(Box::PADDING, Box::TOP) + box.GetEdge(Box::PADDING, Box::BOTTOM);
+			const float padding_border_size = box.GetEdge(BoxArea::Border, BoxEdge::Top) + box.GetEdge(BoxArea::Border, BoxEdge::Bottom) +
+				box.GetEdge(BoxArea::Padding, BoxEdge::Top) + box.GetEdge(BoxArea::Padding, BoxEdge::Bottom);
 
 			float height = 0.f;
 			float offset_y = 0.f;
@@ -214,9 +214,9 @@ void WidgetDropDown::OnRender()
 			}
 
 			// Set the height and re-format the selection box.
-			selection_element->SetProperty(PropertyId::Height, Property(height, Property::PX));
+			selection_element->SetProperty(PropertyId::Height, Property(height, Unit::PX));
 			selection_element->GetOwnerDocument()->UpdateDocument();
-			ElementUtilities::FormatElement(selection_element, parent_element->GetBox().GetSize(Box::BORDER));
+			ElementUtilities::FormatElement(selection_element, parent_element->GetBox().GetSize(BoxArea::Border));
 
 			selection_element->SetOffset(Vector2f(offset_x, offset_y), parent_element);
 		}
@@ -226,8 +226,8 @@ void WidgetDropDown::OnRender()
 
 	if (value_layout_dirty)
 	{
-		ElementUtilities::FormatElement(value_element, parent_element->GetBox().GetSize(Box::BORDER));
-		value_element->SetOffset(parent_element->GetBox().GetPosition(Box::CONTENT), parent_element);
+		ElementUtilities::FormatElement(value_element, parent_element->GetBox().GetSize(BoxArea::Border));
+		value_element->SetOffset(parent_element->GetBox().GetPosition(BoxArea::Content), parent_element);
 
 		value_layout_dirty = false;
 	}
@@ -252,10 +252,10 @@ void WidgetDropDown::OnLayout()
 
 	// Calculate the value element position and size.
 	Vector2f size;
-	size.x = parent_element->GetBox().GetSize(Box::CONTENT).x - button_element->GetBox().GetSize(Box::MARGIN).x;
-	size.y = parent_element->GetBox().GetSize(Box::CONTENT).y;
+	size.x = parent_element->GetBox().GetSize(BoxArea::Content).x - button_element->GetBox().GetSize(BoxArea::Margin).x;
+	size.y = parent_element->GetBox().GetSize(BoxArea::Content).y;
 
-	value_element->SetOffset(parent_element->GetBox().GetPosition(Box::CONTENT), parent_element);
+	value_element->SetOffset(parent_element->GetBox().GetPosition(BoxArea::Content), parent_element);
 	value_element->SetBox(Box(size));
 
 	box_layout_dirty = true;

+ 42 - 34
Source/Core/Elements/WidgetSlider.cpp

@@ -257,8 +257,8 @@ void WidgetSlider::FormatElements(const Vector2f containing_block, float slider_
 	ElementUtilities::BuildBox(track_box, parent_box.GetSize(), track);
 	content = track_box.GetSize();
 	content[length_axis] = slider_length -= orientation == VERTICAL
-		? (track_box.GetCumulativeEdge(Box::CONTENT, Box::TOP) + track_box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM))
-		: (track_box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) + track_box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT));
+		? (track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Top) + track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Bottom))
+		: (track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Left) + track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Right));
 	// If no height has been explicitly specified for the track, it'll be initialised to -1 as per normal block
 	// elements. We'll fix that up here.
 	if (orientation == HORIZONTAL && content.y < 0)
@@ -278,7 +278,7 @@ void WidgetSlider::FormatElements(const Vector2f containing_block, float slider_
 		arrows[i]->SetBox(arrow_box);
 
 		// Shrink the track length by the arrow size.
-		content[length_axis] -= arrow_box.GetSize(Box::MARGIN)[length_axis];
+		content[length_axis] -= arrow_box.GetSize(BoxArea::Margin)[length_axis];
 	}
 
 	// Now the track has been sized, we can fix everything into position.
@@ -287,32 +287,32 @@ void WidgetSlider::FormatElements(const Vector2f containing_block, float slider_
 
 	if (orientation == VERTICAL)
 	{
-		Vector2f offset(arrows[0]->GetBox().GetEdge(Box::MARGIN, Box::LEFT), arrows[0]->GetBox().GetEdge(Box::MARGIN, Box::TOP));
+		Vector2f offset(arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left), arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top));
 		arrows[0]->SetOffset(offset, parent);
 
-		offset.x = track->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-		offset.y += arrows[0]->GetBox().GetSize(Box::BORDER).y + arrows[0]->GetBox().GetEdge(Box::MARGIN, Box::BOTTOM) +
-			track->GetBox().GetEdge(Box::MARGIN, Box::TOP);
+		offset.x = track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left);
+		offset.y += arrows[0]->GetBox().GetSize(BoxArea::Border).y + arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom) +
+			track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top);
 		track->SetOffset(offset, parent);
 
-		offset.x = arrows[1]->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-		offset.y += track->GetBox().GetSize(Box::BORDER).y + track->GetBox().GetEdge(Box::MARGIN, Box::BOTTOM) +
-			arrows[1]->GetBox().GetEdge(Box::MARGIN, Box::TOP);
+		offset.x = arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left);
+		offset.y += track->GetBox().GetSize(BoxArea::Border).y + track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom) +
+			arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top);
 		arrows[1]->SetOffset(offset, parent);
 	}
 	else
 	{
-		Vector2f offset(arrows[0]->GetBox().GetEdge(Box::MARGIN, Box::LEFT), arrows[0]->GetBox().GetEdge(Box::MARGIN, Box::TOP));
+		Vector2f offset(arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left), arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top));
 		arrows[0]->SetOffset(offset, parent);
 
-		offset.x += arrows[0]->GetBox().GetSize(Box::BORDER).x + arrows[0]->GetBox().GetEdge(Box::MARGIN, Box::RIGHT) +
-			track->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-		offset.y = track->GetBox().GetEdge(Box::MARGIN, Box::TOP);
+		offset.x += arrows[0]->GetBox().GetSize(BoxArea::Border).x + arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right) +
+			track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left);
+		offset.y = track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top);
 		track->SetOffset(offset, parent);
 
-		offset.x += track->GetBox().GetSize(Box::BORDER).x + track->GetBox().GetEdge(Box::MARGIN, Box::RIGHT) +
-			arrows[1]->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-		offset.y = arrows[1]->GetBox().GetEdge(Box::MARGIN, Box::TOP);
+		offset.x += track->GetBox().GetSize(BoxArea::Border).x + track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right) +
+			arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left);
+		offset.y = arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top);
 		arrows[1]->SetOffset(offset, parent);
 	}
 
@@ -374,12 +374,12 @@ void WidgetSlider::ProcessEvent(Event& event)
 			if (orientation == HORIZONTAL)
 			{
 				mouse_position = event.GetParameter<float>("mouse_x", 0);
-				bar_halfsize = 0.5f * bar->GetBox().GetSize(Box::BORDER).x;
+				bar_halfsize = 0.5f * bar->GetBox().GetSize(BoxArea::Border).x;
 			}
 			else
 			{
 				mouse_position = event.GetParameter<float>("mouse_y", 0);
-				bar_halfsize = 0.5f * bar->GetBox().GetSize(Box::BORDER).y;
+				bar_halfsize = 0.5f * bar->GetBox().GetSize(BoxArea::Border).y;
 			}
 
 			float new_bar_position = AbsolutePositionToBarPosition(mouse_position - bar_halfsize);
@@ -501,10 +501,11 @@ float WidgetSlider::AbsolutePositionToBarPosition(float absolute_position) const
 
 	if (orientation == HORIZONTAL)
 	{
-		const float edge_left = bar->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-		const float edge_right = bar->GetBox().GetEdge(Box::MARGIN, Box::RIGHT);
+		const float edge_left = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left);
+		const float edge_right = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right);
 
-		float traversable_track_length = track->GetBox().GetSize(Box::CONTENT).x - bar->GetBox().GetSize(Box::BORDER).x - edge_left - edge_right;
+		float traversable_track_length =
+			track->GetBox().GetSize(BoxArea::Content).x - bar->GetBox().GetSize(BoxArea::Border).x - edge_left - edge_right;
 		if (traversable_track_length > 0)
 		{
 			float traversable_track_origin = track->GetAbsoluteOffset().x + edge_left;
@@ -514,10 +515,11 @@ float WidgetSlider::AbsolutePositionToBarPosition(float absolute_position) const
 	}
 	else
 	{
-		const float edge_top = bar->GetBox().GetEdge(Box::MARGIN, Box::TOP);
-		const float edge_bottom = bar->GetBox().GetEdge(Box::MARGIN, Box::BOTTOM);
+		const float edge_top = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top);
+		const float edge_bottom = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom);
 
-		float traversable_track_length = track->GetBox().GetSize(Box::CONTENT).y - bar->GetBox().GetSize(Box::BORDER).y - edge_top - edge_bottom;
+		float traversable_track_length =
+			track->GetBox().GetSize(BoxArea::Content).y - bar->GetBox().GetSize(BoxArea::Border).y - edge_top - edge_bottom;
 		if (traversable_track_length > 0)
 		{
 			float traversable_track_origin = track->GetAbsoluteOffset().y + edge_top;
@@ -532,26 +534,32 @@ float WidgetSlider::AbsolutePositionToBarPosition(float absolute_position) const
 void WidgetSlider::PositionBar()
 {
 	const Vector2f track_dimensions = track->GetBox().GetSize();
-	const Vector2f bar_dimensions = bar->GetBox().GetSize(Box::BORDER);
+	const Vector2f bar_dimensions = bar->GetBox().GetSize(BoxArea::Border);
 
 	if (orientation == VERTICAL)
 	{
-		const float edge_top = bar->GetBox().GetEdge(Box::MARGIN, Box::TOP);
-		const float edge_bottom = bar->GetBox().GetEdge(Box::MARGIN, Box::BOTTOM);
+		const float edge_top = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top);
+		const float edge_bottom = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom);
 
 		float traversable_track_length = track_dimensions.y - bar_dimensions.y - edge_top - edge_bottom;
-		bar->SetOffset(Vector2f(bar->GetBox().GetEdge(Box::MARGIN, Box::LEFT),
-						   track->GetRelativeOffset().y + edge_top + traversable_track_length * bar_position),
+		bar->SetOffset(
+			Vector2f{
+				bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left),
+				track->GetRelativeOffset().y + edge_top + traversable_track_length * bar_position,
+			},
 			parent);
 	}
 	else
 	{
-		const float edge_left = bar->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-		const float edge_right = bar->GetBox().GetEdge(Box::MARGIN, Box::RIGHT);
+		const float edge_left = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left);
+		const float edge_right = bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right);
 
 		float traversable_track_length = track_dimensions.x - bar_dimensions.x - edge_left - edge_right;
-		bar->SetOffset(Vector2f(track->GetRelativeOffset().x + edge_left + traversable_track_length * bar_position,
-						   bar->GetBox().GetEdge(Box::MARGIN, Box::TOP)),
+		bar->SetOffset(
+			Vector2f{
+				track->GetRelativeOffset().x + edge_left + traversable_track_length * bar_position,
+				bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top),
+			},
 			parent);
 	}
 }

+ 6 - 6
Source/Core/Elements/WidgetTextInput.cpp

@@ -116,7 +116,7 @@ WidgetTextInput::WidgetTextInput(ElementFormControl* _parent) :
 	parent->SetProperty(PropertyId::Drag, Property(Style::Drag::Drag));
 	parent->SetProperty(PropertyId::WordBreak, Property(Style::WordBreak::BreakWord));
 	parent->SetProperty(PropertyId::TextTransform, Property(Style::TextTransform::None));
-	parent->SetClientArea(Box::CONTENT);
+	parent->SetClientArea(BoxArea::Content);
 
 	parent->AddEventListener(EventId::Keydown, this, true);
 	parent->AddEventListener(EventId::Textinput, this, true);
@@ -299,7 +299,7 @@ void WidgetTextInput::UpdateSelectionColours()
 	}
 
 	// Set the computed text colour on the element holding the selected text.
-	selected_text_element->SetProperty(PropertyId::Color, Property(colour, Property::COLOUR));
+	selected_text_element->SetProperty(PropertyId::Color, Property(colour, Unit::COLOUR));
 
 	// If the 'background-color' property has been set on the 'selection' element, use that as the
 	// background colour for the selected text. Otherwise, use the inverse of the selected text
@@ -340,7 +340,7 @@ void WidgetTextInput::OnResize()
 {
 	GenerateCursor();
 
-	Vector2f text_position = parent->GetBox().GetPosition(Box::CONTENT);
+	Vector2f text_position = parent->GetBox().GetPosition(BoxArea::Content);
 	text_element->SetOffset(text_position, parent);
 	selected_text_element->SetOffset(text_position, parent);
 
@@ -364,7 +364,7 @@ void WidgetTextInput::OnLayout()
 {
 	if (force_formatting_on_next_layout)
 	{
-		internal_dimensions = parent->GetBox().GetSize(Box::CONTENT);
+		internal_dimensions = parent->GetBox().GetSize(BoxArea::Content);
 		FormatElement();
 		UpdateCursorPosition(true);
 		force_formatting_on_next_layout = false;
@@ -990,7 +990,7 @@ void WidgetTextInput::FormatElement()
 {
 	using namespace Style;
 	ElementScroll* scroll = parent->GetElementScroll();
-	const float width = parent->GetBox().GetSize(Box::PADDING).x;
+	float width = parent->GetBox().GetSize(BoxArea::Padding).x;
 
 	const Overflow x_overflow_property = parent->GetComputedValues().overflow_x();
 	const Overflow y_overflow_property = parent->GetComputedValues().overflow_y();
@@ -1206,7 +1206,7 @@ void WidgetTextInput::GenerateCursor()
 
 	if (const Property* property = parent->GetProperty(PropertyId::CaretColor))
 	{
-		if (property->unit == Property::COLOUR)
+		if (property->unit == Unit::COLOUR)
 			color = property->Get<Colourb>();
 	}
 

+ 5 - 8
Source/Core/GeometryBackgroundBorder.cpp

@@ -39,13 +39,11 @@ GeometryBackgroundBorder::GeometryBackgroundBorder(Vector<Vertex>& vertices, Vec
 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)
 {
-	using Edge = Box::Edge;
-
 	EdgeSizes border_widths = {
-		Math::Round(box.GetEdge(Box::BORDER, Edge::TOP)),
-		Math::Round(box.GetEdge(Box::BORDER, Edge::RIGHT)),
-		Math::Round(box.GetEdge(Box::BORDER, Edge::BOTTOM)),
-		Math::Round(box.GetEdge(Box::BORDER, Edge::LEFT)),
+		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;
@@ -57,7 +55,7 @@ void GeometryBackgroundBorder::Draw(Vector<Vertex>& vertices, Vector<int>& indic
 				num_borders += 1;
 	}
 
-	const Vector2f padding_size = box.GetSize(Box::PADDING).Round();
+	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);
@@ -159,7 +157,6 @@ void GeometryBackgroundBorder::Draw(Vector<Vertex>& vertices, Vector<int>& indic
 	// Draw the border
 	if (has_border)
 	{
-		using Edge = Box::Edge;
 		const int offset_vertices = (int)vertices.size();
 
 		const bool draw_edge[4] = {

+ 1 - 0
Source/Core/GeometryBackgroundBorder.h

@@ -58,6 +58,7 @@ public:
 		const Colourb* border_colors);
 
 private:
+	enum Edge { TOP, RIGHT, BOTTOM, LEFT };
 	enum Corner { TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT };
 
 	GeometryBackgroundBorder(Vector<Vertex>& vertices, Vector<int>& indices);

+ 15 - 13
Source/Core/Layout/BlockContainer.cpp

@@ -87,8 +87,8 @@ bool BlockContainer::Close(BlockContainer* parent_block_container)
 		RMLUI_ASSERTMSG(GetParent() == parent_block_container, "Mismatched parent box.");
 
 		// If this close fails, it means this block box has caused our parent box to generate an automatic vertical scrollbar.
-		if (!parent_block_container->EncloseChildBox(this, position, box.GetSizeAcross(Box::VERTICAL, Box::BORDER),
-				box.GetEdge(Box::MARGIN, Box::BOTTOM)))
+		if (!parent_block_container->EncloseChildBox(this, position, box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border),
+				box.GetEdge(BoxArea::Margin, BoxEdge::Bottom)))
 			return false;
 	}
 
@@ -109,7 +109,8 @@ bool BlockContainer::Close(BlockContainer* parent_block_container)
 		// local block formatting context), convert it to the element's local coordinates.
 		if (found_baseline)
 		{
-			const float bottom_position = position.y + box.GetSizeAcross(Box::VERTICAL, Box::BORDER) + box.GetEdge(Box::MARGIN, Box::BOTTOM);
+			const float bottom_position =
+				position.y + box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border) + box.GetEdge(BoxArea::Margin, BoxEdge::Bottom);
 			element_baseline = bottom_position - baseline;
 		}
 	}
@@ -181,8 +182,8 @@ LayoutBox* BlockContainer::AddBlockLevelBox(UniquePtr<LayoutBox> block_level_box
 	LayoutBox* block_level_box = block_level_box_ptr.get();
 	child_boxes.push_back(std::move(block_level_box_ptr));
 
-	if (!EncloseChildBox(block_level_box, child_position, child_box.GetSizeAcross(Box::VERTICAL, Box::BORDER),
-			child_box.GetEdge(Box::MARGIN, Box::BOTTOM)))
+	if (!EncloseChildBox(block_level_box, child_position, child_box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border),
+			child_box.GetEdge(BoxArea::Margin, BoxEdge::Bottom)))
 		return nullptr;
 
 	return block_level_box;
@@ -241,7 +242,7 @@ void BlockContainer::AddFloatElement(Element* element, Vector2f visible_overflow
 		Vector2f line_size;
 		if (queued_float_elements.empty() && inline_container->GetOpenLineBoxDimensions(line_position_top, line_size))
 		{
-			const Vector2f margin_size = element->GetBox().GetSize(Box::MARGIN);
+			const Vector2f margin_size = element->GetBox().GetSize(BoxArea::Margin);
 			const Style::Float float_property = element->GetComputedValues().float_();
 			const Style::Clear clear_property = element->GetComputedValues().clear();
 
@@ -299,11 +300,11 @@ Vector2f BlockContainer::NextBoxPosition() const
 
 Vector2f BlockContainer::NextBoxPosition(const Box& child_box, Style::Clear clear_property) const
 {
-	const float child_top_margin = child_box.GetEdge(Box::MARGIN, Box::TOP);
+	const float child_top_margin = child_box.GetEdge(BoxArea::Margin, BoxEdge::Top);
 
 	Vector2f box_position = NextBoxPosition();
 
-	box_position.x += child_box.GetEdge(Box::MARGIN, Box::LEFT);
+	box_position.x += child_box.GetEdge(BoxArea::Margin, BoxEdge::Left);
 	box_position.y += child_top_margin;
 
 	float clear_margin = space->DetermineClearPosition(box_position.y, clear_property) - box_position.y;
@@ -316,7 +317,7 @@ Vector2f BlockContainer::NextBoxPosition(const Box& child_box, Style::Clear clea
 		// Check for a collapsing vertical margin with our last child, which will be vertically adjacent to the new box.
 		if (const Box* open_box = block_box->GetIfBox())
 		{
-			const float open_bottom_margin = open_box->GetEdge(Box::MARGIN, Box::BOTTOM);
+			const float open_bottom_margin = open_box->GetEdge(BoxArea::Margin, BoxEdge::Bottom);
 			const float margin_sum = open_bottom_margin + child_top_margin;
 
 			// The collapsed margin size depends on the sign of each margin, according to CSS behavior. The margins have
@@ -372,7 +373,7 @@ float BlockContainer::GetShrinkToFitWidth() const
 			const float child_inner_width = block_box->GetShrinkToFitWidth();
 			float child_edges_width = 0.f;
 			if (const Box* child_box = block_box->GetIfBox())
-				child_edges_width = child_box->GetSizeAcross(Box::HORIZONTAL, Box::MARGIN, Box::PADDING);
+				child_edges_width = child_box->GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding);
 
 			content_width = Math::Max(content_width, child_edges_width + child_inner_width);
 		}
@@ -526,11 +527,12 @@ void BlockContainer::PlaceFloat(Element* element, float vertical_position, Vecto
 {
 	const Box& element_box = element->GetBox();
 
-	const Vector2f border_size = element_box.GetSize(Box::BORDER);
+	const Vector2f border_size = element_box.GetSize(BoxArea::Border);
 	visible_overflow_size = Math::Max(border_size, visible_overflow_size);
 
-	const Vector2f margin_top_left = {element_box.GetEdge(Box::MARGIN, Box::LEFT), element_box.GetEdge(Box::MARGIN, Box::TOP)};
-	const Vector2f margin_bottom_right = {element_box.GetEdge(Box::MARGIN, Box::RIGHT), element_box.GetEdge(Box::MARGIN, Box::BOTTOM)};
+	const Vector2f margin_top_left = {element_box.GetEdge(BoxArea::Margin, BoxEdge::Left), element_box.GetEdge(BoxArea::Margin, BoxEdge::Top)};
+	const Vector2f margin_bottom_right = {element_box.GetEdge(BoxArea::Margin, BoxEdge::Right),
+		element_box.GetEdge(BoxArea::Margin, BoxEdge::Bottom)};
 	const Vector2f margin_size = border_size + margin_top_left + margin_bottom_right;
 
 	Style::Float float_property = element->GetComputedValues().float_();

+ 11 - 11
Source/Core/Layout/ContainerBox.cpp

@@ -42,12 +42,12 @@ void ContainerBox::ResetScrollbars(const Box& box)
 {
 	RMLUI_ASSERT(element);
 	if (overflow_x == Style::Overflow::Scroll)
-		element->GetElementScroll()->EnableScrollbar(ElementScroll::HORIZONTAL, box.GetSizeAcross(Box::HORIZONTAL, Box::PADDING));
+		element->GetElementScroll()->EnableScrollbar(ElementScroll::HORIZONTAL, box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Padding));
 	else
 		element->GetElementScroll()->DisableScrollbar(ElementScroll::HORIZONTAL);
 
 	if (overflow_y == Style::Overflow::Scroll)
-		element->GetElementScroll()->EnableScrollbar(ElementScroll::VERTICAL, box.GetSizeAcross(Box::HORIZONTAL, Box::PADDING));
+		element->GetElementScroll()->EnableScrollbar(ElementScroll::VERTICAL, box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Padding));
 	else
 		element->GetElementScroll()->DisableScrollbar(ElementScroll::VERTICAL);
 }
@@ -93,7 +93,7 @@ void ContainerBox::ClosePositionedElements()
 			// chain of the static position offset parent, and (2) that all offsets in this chain has been set already.
 			Vector2f relative_position;
 			for (Element* ancestor = static_position_offset_parent; ancestor && ancestor != element; ancestor = ancestor->GetOffsetParent())
-				relative_position += ancestor->GetRelativeOffset(Box::BORDER);
+				relative_position += ancestor->GetRelativeOffset(BoxArea::Border);
 
 			// Now simply add the result to the stored static position to get the static position in our local space.
 			Vector2f offset = relative_position + static_position;
@@ -104,8 +104,8 @@ void ContainerBox::ClosePositionedElements()
 			// Now that the element's box has been built, we can offset the position we determined was appropriate for
 			// it by the element's margin. This is necessary because the coordinate system for the box begins at the
 			// border, not the margin.
-			offset.x += absolute_element->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-			offset.y += absolute_element->GetBox().GetEdge(Box::MARGIN, Box::TOP);
+			offset.x += absolute_element->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left);
+			offset.y += absolute_element->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top);
 
 			// Set the offset of the element; the element itself will take care of any RCSS-defined positional offsets.
 			absolute_element->SetOffset(offset, element);
@@ -141,8 +141,8 @@ bool ContainerBox::CatchOverflow(const Vector2f content_overflow_size, const Box
 	if (!IsScrollContainer())
 		return true;
 
-	const Vector2f padding_bottom_right = {box.GetEdge(Box::PADDING, Box::RIGHT), box.GetEdge(Box::PADDING, Box::BOTTOM)};
-	const float padding_width = box.GetSizeAcross(Box::HORIZONTAL, Box::PADDING);
+	const Vector2f padding_bottom_right = {box.GetEdge(BoxArea::Padding, BoxEdge::Right), box.GetEdge(BoxArea::Padding, BoxEdge::Bottom)};
+	const float padding_width = box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Padding);
 
 	Vector2f available_space = box.GetSize();
 	if (available_space.y < 0.f)
@@ -205,8 +205,8 @@ bool ContainerBox::SubmitBox(const Vector2f content_overflow_size, const Box& bo
 		if (!CatchOverflow(content_overflow_size, box, max_height))
 			return false;
 
-		const Vector2f padding_top_left = {box.GetEdge(Box::PADDING, Box::LEFT), box.GetEdge(Box::PADDING, Box::TOP)};
-		const Vector2f padding_bottom_right = {box.GetEdge(Box::PADDING, Box::RIGHT), box.GetEdge(Box::PADDING, Box::BOTTOM)};
+		const Vector2f padding_top_left = {box.GetEdge(BoxArea::Padding, BoxEdge::Left), box.GetEdge(BoxArea::Padding, BoxEdge::Top)};
+		const Vector2f padding_bottom_right = {box.GetEdge(BoxArea::Padding, BoxEdge::Right), box.GetEdge(BoxArea::Padding, BoxEdge::Bottom)};
 		const Vector2f padding_size = box.GetSize() + padding_top_left + padding_bottom_right;
 
 		const bool is_scroll_container = IsScrollContainer();
@@ -221,7 +221,7 @@ bool ContainerBox::SubmitBox(const Vector2f content_overflow_size, const Box& bo
 		element->SetBox(box);
 		element->SetScrollableOverflowRectangle(scrollable_overflow_size);
 
-		const Vector2f border_size = padding_size + box.GetFrameSize(Box::BORDER);
+		const Vector2f border_size = padding_size + box.GetFrameSize(BoxArea::Border);
 
 		// Set the visible overflow size so that ancestors can catch any overflow produced by us. That is, hiding it or
 		// providing a scrolling mechanism. If this box is a scroll container, we catch our own overflow here; then,
@@ -235,7 +235,7 @@ bool ContainerBox::SubmitBox(const Vector2f content_overflow_size, const Box& bo
 		}
 		else
 		{
-			const Vector2f border_top_left = {box.GetEdge(Box::BORDER, Box::LEFT), box.GetEdge(Box::BORDER, Box::TOP)};
+			const Vector2f border_top_left = {box.GetEdge(BoxArea::Border, BoxEdge::Left), box.GetEdge(BoxArea::Border, BoxEdge::Top)};
 			visible_overflow_size = Math::Max(border_size, content_overflow_size + border_top_left + padding_top_left);
 		}
 	}

+ 1 - 1
Source/Core/Layout/FlexFormattingContext.cpp

@@ -106,7 +106,7 @@ UniquePtr<LayoutBox> FlexFormattingContext::Format(ContainerBox* parent_containe
 
 		// Change the flex baseline coordinates to the element baseline, which is defined as the distance from the element's bottom margin edge.
 		const float element_baseline =
-			sized_box.GetSizeAcross(Box::VERTICAL, Box::BORDER) + sized_box.GetEdge(Box::MARGIN, Box::BOTTOM) - flex_baseline;
+			sized_box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border) + sized_box.GetEdge(BoxArea::Margin, BoxEdge::Bottom) - flex_baseline;
 
 		// Close the box, and break out of the loop if it did not produce any new scrollbars, otherwise continue to format the flexbox again.
 		if (flex_container_box->Close(content_overflow_size, sized_box, element_baseline))

+ 12 - 10
Source/Core/Layout/InlineBox.cpp

@@ -33,11 +33,11 @@
 
 namespace Rml {
 
-static void ZeroBoxEdge(Box& box, Box::Edge edge)
+static void ZeroBoxEdge(Box& box, BoxEdge edge)
 {
-	box.SetEdge(Box::PADDING, edge, 0.f);
-	box.SetEdge(Box::BORDER, edge, 0.f);
-	box.SetEdge(Box::MARGIN, edge, 0.f);
+	box.SetEdge(BoxArea::Padding, edge, 0.f);
+	box.SetEdge(BoxArea::Border, edge, 0.f);
+	box.SetEdge(BoxArea::Margin, edge, 0.f);
 }
 
 InlineLevelBox* InlineBoxBase::AddChild(UniquePtr<InlineLevelBox> child)
@@ -101,13 +101,14 @@ InlineBox::InlineBox(const InlineLevelBox* parent, Element* element, const Box&
 	GetStrut(height_above_baseline, depth_below_baseline);
 	SetHeightAndVerticalAlignment(height_above_baseline, depth_below_baseline, parent);
 
-	const float edge_left = box.GetCumulativeEdge(Box::PADDING, Box::LEFT);
-	const float edge_right = box.GetCumulativeEdge(Box::PADDING, Box::RIGHT);
+	const float edge_left = box.GetCumulativeEdge(BoxArea::Padding, BoxEdge::Left);
+	const float edge_right = box.GetCumulativeEdge(BoxArea::Padding, BoxEdge::Right);
 	SetInlineBoxSpacing(edge_left, edge_right);
 
 	// Vertically position the box so that its content box is equally spaced around its font ascent and descent metrics.
 	const float half_leading = 0.5f * (inner_height - (font_metrics.ascent + font_metrics.descent));
-	baseline_to_border_height = font_metrics.ascent + half_leading + box.GetEdge(Box::BORDER, Box::TOP) + box.GetEdge(Box::PADDING, Box::TOP);
+	baseline_to_border_height =
+		font_metrics.ascent + half_leading + box.GetEdge(BoxArea::Border, BoxEdge::Top) + box.GetEdge(BoxArea::Padding, BoxEdge::Top);
 }
 
 FragmentConstructor InlineBox::CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool /*first_box*/,
@@ -128,13 +129,14 @@ void InlineBox::Submit(const PlacedFragment& placed_fragment)
 	element_box.SetContent({placed_fragment.layout_width, element_box.GetSize().y});
 
 	if (placed_fragment.split_left)
-		ZeroBoxEdge(element_box, Box::LEFT);
+		ZeroBoxEdge(element_box, BoxEdge::Left);
 	if (placed_fragment.split_right)
-		ZeroBoxEdge(element_box, Box::RIGHT);
+		ZeroBoxEdge(element_box, BoxEdge::Right);
 
 	// In inline layout, fragments are positioned in terms of (x: margin edge, y: baseline), while element offsets are
 	// specified relative to their border box. Thus, find the offset from the fragment position to the border edge.
-	const Vector2f border_position = placed_fragment.position + Vector2f{element_box.GetEdge(Box::MARGIN, Box::LEFT), -baseline_to_border_height};
+	const Vector2f border_position =
+		placed_fragment.position + Vector2f{element_box.GetEdge(BoxArea::Margin, BoxEdge::Left), -baseline_to_border_height};
 
 	// We can determine the principal fragment based on its left split: Only the principal one has its left side intact,
 	// subsequent fragments have their left side split.

+ 2 - 1
Source/Core/Layout/InlineContainer.cpp

@@ -81,7 +81,8 @@ InlineBox* InlineContainer::AddInlineElement(Element* element, const Box& box)
 		inline_level_box = parent_box->AddChild(std::move(inline_box_ptr));
 	}
 
-	const float minimum_line_height = Math::Max(element_line_height, (box.GetSize().y >= 0.f ? box.GetSizeAcross(Box::VERTICAL, Box::MARGIN) : 0.f));
+	const float minimum_line_height =
+		Math::Max(element_line_height, (box.GetSize().y >= 0.f ? box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Margin) : 0.f));
 
 	LayoutOverflowHandle overflow_handle = {};
 	float minimum_width_next = 0.f;

+ 3 - 3
Source/Core/Layout/InlineLevelBox.cpp

@@ -136,7 +136,7 @@ InlineLevelBox_Atomic::InlineLevelBox_Atomic(const InlineLevelBox* parent, Eleme
 	RMLUI_ASSERT(parent && element);
 	RMLUI_ASSERT(box.GetSize().x >= 0.f && box.GetSize().y >= 0.f);
 
-	const float outer_height = box.GetSizeAcross(Box::VERTICAL, Box::MARGIN);
+	const float outer_height = box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Margin);
 
 	const float descent = GetElement()->GetBaseline();
 	const float ascent = outer_height - descent;
@@ -146,7 +146,7 @@ InlineLevelBox_Atomic::InlineLevelBox_Atomic(const InlineLevelBox* parent, Eleme
 FragmentConstructor InlineLevelBox_Atomic::CreateFragment(InlineLayoutMode mode, float available_width, float right_spacing_width, bool /*first_box*/,
 	LayoutOverflowHandle /*overflow_handle*/)
 {
-	const float outer_width = box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN);
+	const float outer_width = box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin);
 
 	if (mode != InlineLayoutMode::WrapAny || outer_width + right_spacing_width <= available_width)
 		return FragmentConstructor{FragmentType::SizedBox, outer_width, {}, {}};
@@ -157,7 +157,7 @@ FragmentConstructor InlineLevelBox_Atomic::CreateFragment(InlineLayoutMode mode,
 void InlineLevelBox_Atomic::Submit(const PlacedFragment& placed_fragment)
 {
 	const Vector2f margin_position = {placed_fragment.position.x, placed_fragment.position.y - GetHeightAboveBaseline()};
-	const Vector2f margin_edge = {box.GetEdge(Box::MARGIN, Box::LEFT), box.GetEdge(Box::MARGIN, Box::TOP)};
+	const Vector2f margin_edge = {box.GetEdge(BoxArea::Margin, BoxEdge::Left), box.GetEdge(BoxArea::Margin, BoxEdge::Top)};
 	const Vector2f border_position = margin_position + margin_edge;
 
 	GetElement()->SetOffset(border_position, placed_fragment.offset_parent);

+ 34 - 31
Source/Core/Layout/LayoutDetails.cpp

@@ -60,16 +60,16 @@ void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* eleme
 	const ComputedValues& computed = element->GetComputedValues();
 
 	// Calculate the padding area.
-	box.SetEdge(Box::PADDING, Box::TOP, Math::Max(0.0f, ResolveValue(computed.padding_top(), containing_block.x)));
-	box.SetEdge(Box::PADDING, Box::RIGHT, Math::Max(0.0f, ResolveValue(computed.padding_right(), containing_block.x)));
-	box.SetEdge(Box::PADDING, Box::BOTTOM, Math::Max(0.0f, ResolveValue(computed.padding_bottom(), containing_block.x)));
-	box.SetEdge(Box::PADDING, Box::LEFT, Math::Max(0.0f, ResolveValue(computed.padding_left(), containing_block.x)));
+	box.SetEdge(BoxArea::Padding, BoxEdge::Top, Math::Max(0.0f, ResolveValue(computed.padding_top(), containing_block.x)));
+	box.SetEdge(BoxArea::Padding, BoxEdge::Right, Math::Max(0.0f, ResolveValue(computed.padding_right(), containing_block.x)));
+	box.SetEdge(BoxArea::Padding, BoxEdge::Bottom, Math::Max(0.0f, ResolveValue(computed.padding_bottom(), containing_block.x)));
+	box.SetEdge(BoxArea::Padding, BoxEdge::Left, Math::Max(0.0f, ResolveValue(computed.padding_left(), containing_block.x)));
 
 	// Calculate the border area.
-	box.SetEdge(Box::BORDER, Box::TOP, Math::Max(0.0f, computed.border_top_width()));
-	box.SetEdge(Box::BORDER, Box::RIGHT, Math::Max(0.0f, computed.border_right_width()));
-	box.SetEdge(Box::BORDER, Box::BOTTOM, Math::Max(0.0f, computed.border_bottom_width()));
-	box.SetEdge(Box::BORDER, Box::LEFT, Math::Max(0.0f, computed.border_left_width()));
+	box.SetEdge(BoxArea::Border, BoxEdge::Top, Math::Max(0.0f, computed.border_top_width()));
+	box.SetEdge(BoxArea::Border, BoxEdge::Right, Math::Max(0.0f, computed.border_right_width()));
+	box.SetEdge(BoxArea::Border, BoxEdge::Bottom, Math::Max(0.0f, computed.border_bottom_width()));
+	box.SetEdge(BoxArea::Border, BoxEdge::Left, Math::Max(0.0f, computed.border_left_width()));
 
 	// Prepare sizing of the content area.
 	Vector2f content_area(-1, -1);
@@ -101,8 +101,8 @@ void LayoutDetails::BuildBox(Box& box, Vector2f containing_block, Element* eleme
 		// Adjust sizes for the given box sizing model.
 		if (computed.box_sizing() == Style::BoxSizing::BorderBox)
 		{
-			const float border_padding_width = box.GetSizeAcross(Box::HORIZONTAL, Box::BORDER, Box::PADDING);
-			const float border_padding_height = box.GetSizeAcross(Box::VERTICAL, Box::BORDER, Box::PADDING);
+			const float border_padding_width = box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Border, BoxArea::Padding);
+			const float border_padding_height = box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border, BoxArea::Padding);
 
 			min_size.x = BorderSizeToContentSize(min_size.x, border_padding_width);
 			max_size.x = BorderSizeToContentSize(max_size.x, border_padding_width);
@@ -135,7 +135,7 @@ void LayoutDetails::GetMinMaxWidth(float& min_width, float& max_width, const Com
 
 	if (computed.box_sizing() == Style::BoxSizing::BorderBox)
 	{
-		const float border_padding_width = box.GetSizeAcross(Box::HORIZONTAL, Box::BORDER, Box::PADDING);
+		const float border_padding_width = box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Border, BoxArea::Padding);
 		min_width = BorderSizeToContentSize(min_width, border_padding_width);
 		max_width = BorderSizeToContentSize(max_width, border_padding_width);
 	}
@@ -149,7 +149,7 @@ void LayoutDetails::GetMinMaxHeight(float& min_height, float& max_height, const
 
 	if (computed.box_sizing() == Style::BoxSizing::BorderBox)
 	{
-		const float border_padding_height = box.GetSizeAcross(Box::VERTICAL, Box::BORDER, Box::PADDING);
+		const float border_padding_height = box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border, BoxArea::Padding);
 		min_height = BorderSizeToContentSize(min_height, border_padding_height);
 		max_height = BorderSizeToContentSize(max_height, border_padding_height);
 	}
@@ -176,7 +176,7 @@ ContainingBlock LayoutDetails::GetContainingBlock(ContainerBox* parent_container
 	using Style::Position;
 
 	ContainerBox* container = parent_container;
-	Box::Area area = Box::CONTENT;
+	BoxArea area = BoxArea::Content;
 
 	// For absolutely positioned boxes we look for the first positioned ancestor. We deviate from the CSS specs by using
 	// the same rules for fixed boxes, as that is particularly helpful on handles and other widgets that should not
@@ -184,7 +184,7 @@ ContainingBlock LayoutDetails::GetContainingBlock(ContainerBox* parent_container
 	// behavior may be reconsidered in the future.
 	if (position == Position::Absolute || position == Position::Fixed)
 	{
-		area = Box::PADDING;
+		area = BoxArea::Padding;
 
 		auto EstablishesAbsoluteContainingBlock = [](ContainerBox* container) -> bool {
 			return container->GetPositionProperty() != Position::Static || container->HasLocalTransformOrPerspective();
@@ -230,10 +230,10 @@ void LayoutDetails::BuildBoxSizeAndMargins(Box& box, Vector2f min_size, Vector2f
 	{
 		// For inline elements, their calculations are straightforward. No worrying about auto margins and dimensions, etc.
 		// Evaluate the margins. Any declared as 'auto' will resolve to 0.
-		box.SetEdge(Box::MARGIN, Box::TOP, ResolveValue(computed.margin_top(), containing_block.x));
-		box.SetEdge(Box::MARGIN, Box::RIGHT, ResolveValue(computed.margin_right(), containing_block.x));
-		box.SetEdge(Box::MARGIN, Box::BOTTOM, ResolveValue(computed.margin_bottom(), containing_block.x));
-		box.SetEdge(Box::MARGIN, Box::LEFT, ResolveValue(computed.margin_left(), containing_block.x));
+		box.SetEdge(BoxArea::Margin, BoxEdge::Top, ResolveValue(computed.margin_top(), containing_block.x));
+		box.SetEdge(BoxArea::Margin, BoxEdge::Right, ResolveValue(computed.margin_right(), containing_block.x));
+		box.SetEdge(BoxArea::Margin, BoxEdge::Bottom, ResolveValue(computed.margin_bottom(), containing_block.x));
+		box.SetEdge(BoxArea::Margin, BoxEdge::Left, ResolveValue(computed.margin_left(), containing_block.x));
 	}
 	else
 	{
@@ -274,7 +274,7 @@ float LayoutDetails::GetShrinkToFitWidth(Element* element, Vector2f containing_b
 	RootBox root(Math::Max(containing_block, Vector2f(0.f)));
 	UniquePtr<LayoutBox> layout_box = FormattingContext::FormatIndependent(&root, element, &box, FormattingContextType::Block);
 
-	const float available_width = Math::Max(0.f, containing_block.x - box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN, Box::PADDING));
+	const float available_width = Math::Max(0.f, containing_block.x - box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding));
 
 	return Math::Min(available_width, layout_box->GetShrinkToFitWidth());
 }
@@ -418,12 +418,12 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, floa
 		{
 			margins_auto[i] = true;
 			num_auto_margins++;
-			box.SetEdge(Box::MARGIN, i == 0 ? Box::LEFT : Box::RIGHT, 0);
+			box.SetEdge(BoxArea::Margin, i == 0 ? BoxEdge::Left : BoxEdge::Right, 0);
 		}
 		else
 		{
 			margins_auto[i] = false;
-			box.SetEdge(Box::MARGIN, i == 0 ? Box::LEFT : Box::RIGHT, ResolveValue(margin_value, containing_block.x));
+			box.SetEdge(BoxArea::Margin, i == 0 ? BoxEdge::Left : BoxEdge::Right, ResolveValue(margin_value, containing_block.x));
 		}
 	}
 
@@ -450,7 +450,7 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, floa
 		if (!shrink_to_fit)
 		{
 			// The width is set to whatever remains of the containing block.
-			content_area.x = containing_block.x - (GetInsetWidth() + box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN, Box::PADDING));
+			content_area.x = containing_block.x - (GetInsetWidth() + box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding));
 			content_area.x = Math::Max(0.0f, content_area.x);
 		}
 		else if (override_shrink_to_fit_width >= 0)
@@ -466,12 +466,13 @@ void LayoutDetails::BuildBoxWidth(Box& box, const ComputedValues& computed, floa
 	// Otherwise, the margins that are set to auto will pick up the remaining width of the containing block.
 	else if (num_auto_margins > 0)
 	{
-		const float margin = (containing_block.x - (GetInsetWidth() + box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN))) / float(num_auto_margins);
+		const float margin =
+			(containing_block.x - (GetInsetWidth() + box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin))) / float(num_auto_margins);
 
 		if (margins_auto[0])
-			box.SetEdge(Box::MARGIN, Box::LEFT, margin);
+			box.SetEdge(BoxArea::Margin, BoxEdge::Left, margin);
 		if (margins_auto[1])
-			box.SetEdge(Box::MARGIN, Box::RIGHT, margin);
+			box.SetEdge(BoxArea::Margin, BoxEdge::Right, margin);
 	}
 
 	// Clamp the calculated width; if the width is changed by the clamp, then the margins need to be recalculated if
@@ -506,12 +507,12 @@ void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, flo
 		{
 			margins_auto[i] = true;
 			num_auto_margins++;
-			box.SetEdge(Box::MARGIN, i == 0 ? Box::TOP : Box::BOTTOM, 0);
+			box.SetEdge(BoxArea::Margin, i == 0 ? BoxEdge::Top : BoxEdge::Bottom, 0);
 		}
 		else
 		{
 			margins_auto[i] = false;
-			box.SetEdge(Box::MARGIN, i == 0 ? Box::TOP : Box::BOTTOM, ResolveValue(margin_value, containing_block_height));
+			box.SetEdge(BoxArea::Margin, i == 0 ? BoxEdge::Top : BoxEdge::Bottom, ResolveValue(margin_value, containing_block_height));
 		}
 	}
 
@@ -537,19 +538,21 @@ void LayoutDetails::BuildBoxHeight(Box& box, const ComputedValues& computed, flo
 		if (absolutely_positioned && !inset_auto)
 		{
 			// The height is set to whatever remains of the containing block.
-			content_area.y = containing_block_height - (GetInsetHeight() + box.GetSizeAcross(Box::VERTICAL, Box::MARGIN, Box::PADDING));
+			content_area.y =
+				containing_block_height - (GetInsetHeight() + box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Margin, BoxArea::Padding));
 			content_area.y = Math::Max(0.0f, content_area.y);
 		}
 	}
 	// Otherwise, the margins that are set to auto will pick up the remaining height of the containing block.
 	else if (num_auto_margins > 0)
 	{
-		const float margin = (containing_block_height - (GetInsetHeight() + box.GetSizeAcross(Box::VERTICAL, Box::MARGIN))) / float(num_auto_margins);
+		const float margin =
+			(containing_block_height - (GetInsetHeight() + box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Margin))) / float(num_auto_margins);
 
 		if (margins_auto[0])
-			box.SetEdge(Box::MARGIN, Box::TOP, margin);
+			box.SetEdge(BoxArea::Margin, BoxEdge::Top, margin);
 		if (margins_auto[1])
-			box.SetEdge(Box::MARGIN, Box::BOTTOM, margin);
+			box.SetEdge(BoxArea::Margin, BoxEdge::Bottom, margin);
 	}
 
 	if (content_area.y >= 0)

+ 16 - 12
Source/Core/Layout/TableFormattingContext.cpp

@@ -105,7 +105,8 @@ UniquePtr<LayoutBox> TableFormattingContext::Format(ContainerBox* parent_contain
 	}
 
 	// Change the table baseline coordinates to the element baseline, which is defined as the distance from the element's bottom margin edge.
-	const float element_baseline = box.GetSizeAcross(Box::VERTICAL, Box::BORDER) + box.GetEdge(Box::MARGIN, Box::BOTTOM) - table_baseline;
+	const float element_baseline =
+		box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border) + box.GetEdge(BoxArea::Margin, BoxEdge::Bottom) - table_baseline;
 
 	table_wrapper_box->Close(table_overflow_size, box, element_baseline);
 
@@ -215,7 +216,8 @@ void TableFormattingContext::InitializeCellBoxes(BoxList& cells, const TrackBoxL
 
 		// Determine the cell's content width. Include any spanning columns in the cell width.
 		const float cell_border_width = GetSpanningCellBorderSize(columns, grid.cells[i].column_begin, grid.cells[i].column_last);
-		const float content_width = Math::Max(0.0f, cell_border_width - box.GetSizeAcross(Box::HORIZONTAL, Box::BORDER, Box::PADDING));
+		const float content_width =
+			Math::Max(0.0f, cell_border_width - box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Border, BoxArea::Padding));
 		box.SetContent(Vector2f(content_width, box.GetSize().y));
 	}
 }
@@ -319,7 +321,7 @@ void TableFormattingContext::DetermineRowHeights(TrackBoxList& rows, BoxList& ce
 							metric.group_padding_border_a + metric.group_padding_border_b + metric.sum_margin_a + metric.sum_margin_b;
 					});
 
-				const float cell_inrow_height = box.GetSizeAcross(Box::VERTICAL, Box::BORDER) - height_from_spanning_rows;
+				const float cell_inrow_height = box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border) - height_from_spanning_rows;
 
 				// Now we have the height of the cell, increase the row height to accompany the cell.
 				row_metric.fixed_size = Math::Max(row_metric.fixed_size, cell_inrow_height);
@@ -350,11 +352,12 @@ void TableFormattingContext::FormatRows(const TrackBoxList& rows, float table_co
 		Box box;
 		// We use inline build mode here because we only care about padding, border, and (non-auto) margin.
 		LayoutDetails::BuildBox(box, table_initial_content_size, element, BuildBoxMode::Inline);
-		const Vector2f content_size(table_content_width - box.GetSizeAcross(Box::HORIZONTAL, Box::MARGIN, Box::PADDING), content_height);
+		const Vector2f content_size(table_content_width - box.GetSizeAcross(BoxDirection::Horizontal, BoxArea::Margin, BoxArea::Padding),
+			content_height);
 		box.SetContent(content_size);
 		element->SetBox(box);
 
-		element->SetOffset(table_content_offset + Vector2f(box.GetEdge(Box::MARGIN, Box::LEFT), offset_y), element_table);
+		element->SetOffset(table_content_offset + Vector2f(box.GetEdge(BoxArea::Margin, BoxEdge::Left), offset_y), element_table);
 	};
 
 	for (int i = 0; i < (int)rows.size(); i++)
@@ -379,11 +382,12 @@ void TableFormattingContext::FormatColumns(const TrackBoxList& columns, float ta
 		Box box;
 		// We use inline build mode here because we only care about padding, border, and (non-auto) margin.
 		LayoutDetails::BuildBox(box, table_initial_content_size, element, BuildBoxMode::Inline);
-		const Vector2f content_size(content_width, table_content_height - box.GetSizeAcross(Box::VERTICAL, Box::MARGIN, Box::PADDING));
+		const Vector2f content_size(content_width,
+			table_content_height - box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Margin, BoxArea::Padding));
 		box.SetContent(content_size);
 		element->SetBox(box);
 
-		element->SetOffset(table_content_offset + Vector2f(offset_x, box.GetEdge(Box::MARGIN, Box::TOP)), element_table);
+		element->SetOffset(table_content_offset + Vector2f(offset_x, box.GetEdge(BoxArea::Margin, BoxEdge::Top)), element_table);
 	};
 
 	for (int i = 0; i < (int)columns.size(); i++)
@@ -431,12 +435,12 @@ void TableFormattingContext::FormatCells(BoxList& cells, Vector2f& table_overflo
 			else
 			{
 				// We don't need to add any padding and can thus avoid formatting, just set the height to the row height.
-				box.SetContent(
-					Vector2f(box.GetSize().x, Math::Max(0.0f, cell_border_height - box.GetSizeAcross(Box::VERTICAL, Box::BORDER, Box::PADDING))));
+				box.SetContent(Vector2f(box.GetSize().x,
+					Math::Max(0.0f, cell_border_height - box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border, BoxArea::Padding))));
 			}
 		}
 
-		const float available_height = cell_border_height - box.GetSizeAcross(Box::VERTICAL, Box::BORDER);
+		const float available_height = cell_border_height - box.GetSizeAcross(BoxDirection::Vertical, BoxArea::Border);
 
 		if (available_height > 0)
 		{
@@ -461,8 +465,8 @@ void TableFormattingContext::FormatCells(BoxList& cells, Vector2f& table_overflo
 				break;
 			}
 
-			box.SetEdge(Box::PADDING, Box::TOP, box.GetEdge(Box::PADDING, Box::TOP) + add_padding_top);
-			box.SetEdge(Box::PADDING, Box::BOTTOM, box.GetEdge(Box::PADDING, Box::BOTTOM) + add_padding_bottom);
+			box.SetEdge(BoxArea::Padding, BoxEdge::Top, box.GetEdge(BoxArea::Padding, BoxEdge::Top) + add_padding_top);
+			box.SetEdge(BoxArea::Padding, BoxEdge::Bottom, box.GetEdge(BoxArea::Padding, BoxEdge::Bottom) + add_padding_bottom);
 		}
 
 		// Format the cell in a new block formatting context.

+ 12 - 1
Source/Core/Property.cpp

@@ -31,7 +31,7 @@
 
 namespace Rml {
 
-Property::Property() : unit(UNKNOWN), specificity(-1)
+Property::Property() : unit(Unit::UNKNOWN), specificity(-1)
 {
 	definition = nullptr;
 	parser_index = -1;
@@ -47,4 +47,15 @@ String Property::ToString() const
 	return string;
 }
 
+NumericValue Property::GetNumericValue() const
+{
+	NumericValue result;
+	if (Any(unit & Unit::NUMERIC))
+	{
+		if (value.GetInto(result.number))
+			result.unit = unit;
+	}
+	return result;
+}
+
 } // namespace Rml

+ 7 - 23
Source/Core/PropertyDefinition.cpp

@@ -33,7 +33,7 @@
 namespace Rml {
 
 PropertyDefinition::PropertyDefinition(PropertyId id, const String& _default_value, bool _inherited, bool _forces_layout) :
-	id(id), default_value(_default_value, Property::UNKNOWN), relative_target(RelativeTarget::None)
+	id(id), default_value(_default_value, Unit::UNKNOWN), relative_target(RelativeTarget::None)
 {
 	inherited = _inherited;
 	forces_layout = _forces_layout;
@@ -83,7 +83,7 @@ PropertyDefinition& PropertyDefinition::AddParser(const String& parser_name, con
 	parsers.push_back(new_parser);
 
 	// If the default value has not been parsed successfully yet, run it through the new parser.
-	if (default_value.unit == Property::UNKNOWN)
+	if (default_value.unit == Unit::UNKNOWN)
 	{
 		String unparsed_value = default_value.value.Get<String>();
 		if (new_parser.parser->ParseValue(default_value, unparsed_value, new_parser.parameters))
@@ -93,7 +93,7 @@ PropertyDefinition& PropertyDefinition::AddParser(const String& parser_name, con
 		else
 		{
 			default_value.value = unparsed_value;
-			default_value.unit = Property::UNKNOWN;
+			default_value.unit = Unit::UNKNOWN;
 		}
 	}
 
@@ -112,7 +112,7 @@ bool PropertyDefinition::ParseValue(Property& property, const String& value) con
 		}
 	}
 
-	property.unit = Property::UNKNOWN;
+	property.unit = Unit::UNKNOWN;
 	return false;
 }
 
@@ -122,7 +122,7 @@ bool PropertyDefinition::GetValue(String& value, const Property& property) const
 
 	switch (property.unit)
 	{
-	case Property::KEYWORD:
+	case Unit::KEYWORD:
 	{
 		int parser_index = property.parser_index;
 		if (parser_index < 0 || parser_index >= (int)parsers.size())
@@ -156,30 +156,14 @@ bool PropertyDefinition::GetValue(String& value, const Property& property) const
 	}
 	break;
 
-	case Property::COLOUR:
+	case Unit::COLOUR:
 	{
 		Colourb colour = property.value.Get<Colourb>();
 		value = CreateString(32, "rgba(%d,%d,%d,%d)", colour.red, colour.green, colour.blue, colour.alpha);
 	}
 	break;
 
-	// clang-format off
-	case Property::PX:      value += "px";  break;
-	case Property::VW:      value += "vw";  break;
-	case Property::VH:      value += "vh";  break;
-	case Property::DEG:     value += "deg"; break;
-	case Property::RAD:     value += "rad"; break;
-	case Property::DP:      value += "dp";  break;
-	case Property::EM:      value += "em";  break;
-	case Property::REM:     value += "rem"; break;
-	case Property::PERCENT: value += "%";   break;
-	case Property::INCH:    value += "in";  break;
-	case Property::CM:      value += "cm";  break;
-	case Property::MM:      value += "mm";  break;
-	case Property::PT:      value += "pt";  break;
-	case Property::PC:      value += "pc";  break;
-	// clang-format on
-	default: break;
+	default: value += ToString(property.unit); break;
 	}
 
 	return true;

+ 4 - 4
Source/Core/PropertyParserAnimation.cpp

@@ -135,7 +135,7 @@ static bool ParseAnimation(Property& property, const StringList& animation_value
 				{
 					if (animation_list.size() > 0) // The none keyword can not be part of multiple definitions
 						return false;
-					property = Property{AnimationList{}, Property::ANIMATION};
+					property = Property{AnimationList{}, Unit::ANIMATION};
 					return true;
 				}
 				break;
@@ -206,7 +206,7 @@ static bool ParseAnimation(Property& property, const StringList& animation_value
 	}
 
 	property.value = std::move(animation_list);
-	property.unit = Property::ANIMATION;
+	property.unit = Unit::ANIMATION;
 
 	return true;
 }
@@ -240,7 +240,7 @@ static bool ParseTransition(Property& property, const StringList& transition_val
 				{
 					if (transition_list.transitions.size() > 0) // The none keyword can not be part of multiple definitions
 						return false;
-					property = Property{TransitionList{true, false, {}}, Property::TRANSITION};
+					property = Property{TransitionList{true, false, {}}, Unit::TRANSITION};
 					return true;
 				}
 				else if (it->second.type == KeywordType::All)
@@ -340,7 +340,7 @@ static bool ParseTransition(Property& property, const StringList& transition_val
 	}
 
 	property.value = std::move(transition_list);
-	property.unit = Property::TRANSITION;
+	property.unit = Unit::TRANSITION;
 
 	return true;
 }

+ 1 - 1
Source/Core/PropertyParserColour.cpp

@@ -156,7 +156,7 @@ bool PropertyParserColour::ParseValue(Property& property, const String& value, c
 	}
 
 	property.value = Variant(colour);
-	property.unit = Property::COLOUR;
+	property.unit = Unit::COLOUR;
 
 	return true;
 }

+ 2 - 2
Source/Core/PropertyParserDecorator.cpp

@@ -51,7 +51,7 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor
 	if (decorator_string_value.empty() || decorator_string_value == "none")
 	{
 		property.value = Variant(DecoratorsPtr());
-		property.unit = Property::DECORATOR;
+		property.unit = Unit::DECORATOR;
 		return true;
 	}
 
@@ -113,7 +113,7 @@ bool PropertyParserDecorator::ParseValue(Property& property, const String& decor
 		return false;
 
 	property.value = Variant(MakeShared<DecoratorDeclarationList>(std::move(decorators)));
-	property.unit = Property::DECORATOR;
+	property.unit = Unit::DECORATOR;
 
 	return true;
 }

+ 2 - 2
Source/Core/PropertyParserFontEffect.cpp

@@ -51,7 +51,7 @@ bool PropertyParserFontEffect::ParseValue(Property& property, const String& font
 	if (font_effect_string_value.empty() || font_effect_string_value == "none")
 	{
 		property.value = Variant();
-		property.unit = Property::UNKNOWN;
+		property.unit = Unit::UNKNOWN;
 		return true;
 	}
 
@@ -139,7 +139,7 @@ bool PropertyParserFontEffect::ParseValue(Property& property, const String& font
 		[](const SharedPtr<const FontEffect>& effect) { return effect->GetLayer() == FontEffect::Layer::Back; });
 
 	property.value = Variant(MakeShared<FontEffects>(std::move(font_effects)));
-	property.unit = Property::FONTEFFECT;
+	property.unit = Unit::FONTEFFECT;
 
 	return true;
 }

+ 1 - 1
Source/Core/PropertyParserKeyword.cpp

@@ -41,7 +41,7 @@ bool PropertyParserKeyword::ParseValue(Property& property, const String& value,
 		return false;
 
 	property.value = Variant((*iterator).second);
-	property.unit = Property::KEYWORD;
+	property.unit = Unit::KEYWORD;
 
 	return true;
 }

+ 22 - 22
Source/Core/PropertyParserNumber.cpp

@@ -31,26 +31,26 @@
 
 namespace Rml {
 
-static const UnorderedMap<String, Property::Unit> g_property_unit_string_map = {
-	{"", Property::NUMBER},
-	{"%", Property::PERCENT},
-	{"px", Property::PX},
-	{"dp", Property::DP},
-	{"x", Property::X},
-	{"vw", Property::VW},
-	{"vh", Property::VH},
-	{"em", Property::EM},
-	{"rem", Property::REM},
-	{"in", Property::INCH},
-	{"cm", Property::CM},
-	{"mm", Property::MM},
-	{"pt", Property::PT},
-	{"pc", Property::PC},
-	{"deg", Property::DEG},
-	{"rad", Property::RAD},
+static const UnorderedMap<String, Unit> g_property_unit_string_map = {
+	{"", Unit::NUMBER},
+	{"%", Unit::PERCENT},
+	{"px", Unit::PX},
+	{"dp", Unit::DP},
+	{"x", Unit::X},
+	{"vw", Unit::VW},
+	{"vh", Unit::VH},
+	{"em", Unit::EM},
+	{"rem", Unit::REM},
+	{"in", Unit::INCH},
+	{"cm", Unit::CM},
+	{"mm", Unit::MM},
+	{"pt", Unit::PT},
+	{"pc", Unit::PC},
+	{"deg", Unit::DEG},
+	{"rad", Unit::RAD},
 };
 
-PropertyParserNumber::PropertyParserNumber(int units, Property::Unit zero_unit) : units(units), zero_unit(zero_unit) {}
+PropertyParserNumber::PropertyParserNumber(Units units, Unit zero_unit) : units(units), zero_unit(zero_unit) {}
 
 PropertyParserNumber::~PropertyParserNumber() {}
 
@@ -86,9 +86,9 @@ bool PropertyParserNumber::ParseValue(Property& property, const String& value, c
 		return false;
 	}
 
-	const Property::Unit unit = it->second;
+	const Unit unit = it->second;
 
-	if (unit & units)
+	if (Any(unit & units))
 	{
 		property.value = float_value;
 		property.unit = unit;
@@ -97,9 +97,9 @@ bool PropertyParserNumber::ParseValue(Property& property, const String& value, c
 
 	// Detected unit not allowed.
 	// However, we allow a value of "0" if zero_unit is set and no unit specified (that is, unit is a pure NUMBER).
-	if (unit == Property::NUMBER)
+	if (unit == Unit::NUMBER)
 	{
-		if (zero_unit != Property::UNKNOWN && float_value == 0.0f)
+		if (zero_unit != Unit::UNKNOWN && float_value == 0.0f)
 		{
 			property.unit = zero_unit;
 			property.value = Variant(0.0f);

+ 3 - 3
Source/Core/PropertyParserNumber.h

@@ -41,7 +41,7 @@ namespace Rml {
 
 class PropertyParserNumber : public PropertyParser {
 public:
-	PropertyParserNumber(int units, Property::Unit zero_unit = Property::UNKNOWN);
+	PropertyParserNumber(Units units, Unit zero_unit = Unit::UNKNOWN);
 	virtual ~PropertyParserNumber();
 
 	/// Called to parse a RCSS number declaration.
@@ -53,10 +53,10 @@ public:
 
 private:
 	// Stores a bit mask of allowed units.
-	int units;
+	Units units;
 
 	// If zero unit is set and pure numbers are not allowed, parsing of "0" is still allowed and assigned the given unit.
-	Property::Unit zero_unit;
+	Unit zero_unit;
 };
 
 } // namespace Rml

+ 1 - 1
Source/Core/PropertyParserRatio.cpp

@@ -59,7 +59,7 @@ bool PropertyParserRatio::ParseValue(Property& property, const String& value, co
 	}
 
 	property.value = Variant(Vector2f(first_value, second_value));
-	property.unit = Property::RATIO;
+	property.unit = Unit::RATIO;
 
 	return true;
 }

+ 1 - 1
Source/Core/PropertyParserString.cpp

@@ -37,7 +37,7 @@ PropertyParserString::~PropertyParserString() {}
 bool PropertyParserString::ParseValue(Property& property, const String& value, const ParameterMap& /*parameters*/) const
 {
 	property.value = Variant(value);
-	property.unit = Property::STRING;
+	property.unit = Unit::STRING;
 
 	return true;
 }

+ 7 - 8
Source/Core/PropertyParserTransform.cpp

@@ -27,15 +27,14 @@
  */
 
 #include "PropertyParserTransform.h"
+#include "../../Include/RmlUi/Core/NumericValue.h"
 #include "../../Include/RmlUi/Core/Transform.h"
 #include "../../Include/RmlUi/Core/TransformPrimitive.h"
 #include <string.h>
 
 namespace Rml {
 
-PropertyParserTransform::PropertyParserTransform() :
-	number(Property::NUMBER), length(Property::LENGTH_PERCENT, Property::PX), angle(Property::ANGLE, Property::RAD)
-{}
+PropertyParserTransform::PropertyParserTransform() : number(Unit::NUMBER), length(Unit::LENGTH_PERCENT, Unit::PX), angle(Unit::ANGLE, Unit::RAD) {}
 
 PropertyParserTransform::~PropertyParserTransform() {}
 
@@ -44,7 +43,7 @@ bool PropertyParserTransform::ParseValue(Property& property, const String& value
 	if (value == "none")
 	{
 		property.value = Variant(TransformPtr());
-		property.unit = Property::TRANSFORM;
+		property.unit = Unit::TRANSFORM;
 		return true;
 	}
 
@@ -52,7 +51,7 @@ bool PropertyParserTransform::ParseValue(Property& property, const String& value
 
 	char const* next = value.c_str();
 
-	Transforms::NumericValue args[16];
+	NumericValue args[16];
 
 	const PropertyParser* angle1[] = {&angle};
 	const PropertyParser* angle2[] = {&angle, &angle};
@@ -173,13 +172,13 @@ bool PropertyParserTransform::ParseValue(Property& property, const String& value
 	}
 
 	property.value = Variant(std::move(transform));
-	property.unit = Property::TRANSFORM;
+	property.unit = Unit::TRANSFORM;
 
 	return true;
 }
 
-bool PropertyParserTransform::Scan(int& out_bytes_read, const char* str, const char* keyword, const PropertyParser** parsers,
-	Transforms::NumericValue* args, int nargs) const
+bool PropertyParserTransform::Scan(int& out_bytes_read, const char* str, const char* keyword, const PropertyParser** parsers, NumericValue* args,
+	int nargs) const
 {
 	out_bytes_read = 0;
 	int total_bytes_read = 0, bytes_read = 0;

+ 1 - 6
Source/Core/PropertyParserTransform.h

@@ -34,10 +34,6 @@
 
 namespace Rml {
 
-namespace Transforms {
-	struct NumericValue;
-}
-
 /**
     A property parser that parses a RCSS transform property specification.
 
@@ -64,8 +60,7 @@ private:
 	/// @param[out] args The numeric arguments encountered
 	/// @param[in] nargs The number of numeric arguments expected
 	/// @return True if parsed successfully, false otherwise.
-	bool Scan(int& out_bytes_read, const char* str, const char* keyword, const PropertyParser** parsers, Transforms::NumericValue* args,
-		int nargs) const;
+	bool Scan(int& out_bytes_read, const char* str, const char* keyword, const PropertyParser** parsers, NumericValue* args, int nargs) const;
 
 	PropertyParserNumber number, length, angle;
 };

+ 6 - 6
Source/Core/StyleSheetContainer.cpp

@@ -73,27 +73,27 @@ bool StyleSheetContainer::UpdateCompiledStyleSheet(const Context* context)
 			switch (id)
 			{
 			case MediaQueryId::Width:
-				if (vp_dimensions.x != ComputeLength(&property.second, font_size, font_size, dp_ratio, vp_dimensions))
+				if (vp_dimensions.x != ComputeLength(property.second.GetNumericValue(), font_size, font_size, dp_ratio, vp_dimensions))
 					all_match = false;
 				break;
 			case MediaQueryId::MinWidth:
-				if (vp_dimensions.x < ComputeLength(&property.second, font_size, font_size, dp_ratio, vp_dimensions))
+				if (vp_dimensions.x < ComputeLength(property.second.GetNumericValue(), font_size, font_size, dp_ratio, vp_dimensions))
 					all_match = false;
 				break;
 			case MediaQueryId::MaxWidth:
-				if (vp_dimensions.x > ComputeLength(&property.second, font_size, font_size, dp_ratio, vp_dimensions))
+				if (vp_dimensions.x > ComputeLength(property.second.GetNumericValue(), font_size, font_size, dp_ratio, vp_dimensions))
 					all_match = false;
 				break;
 			case MediaQueryId::Height:
-				if (vp_dimensions.y != ComputeLength(&property.second, font_size, font_size, dp_ratio, vp_dimensions))
+				if (vp_dimensions.y != ComputeLength(property.second.GetNumericValue(), font_size, font_size, dp_ratio, vp_dimensions))
 					all_match = false;
 				break;
 			case MediaQueryId::MinHeight:
-				if (vp_dimensions.y < ComputeLength(&property.second, font_size, font_size, dp_ratio, vp_dimensions))
+				if (vp_dimensions.y < ComputeLength(property.second.GetNumericValue(), font_size, font_size, dp_ratio, vp_dimensions))
 					all_match = false;
 				break;
 			case MediaQueryId::MaxHeight:
-				if (vp_dimensions.y > ComputeLength(&property.second, font_size, font_size, dp_ratio, vp_dimensions))
+				if (vp_dimensions.y > ComputeLength(property.second.GetNumericValue(), font_size, font_size, dp_ratio, vp_dimensions))
 					all_match = false;
 				break;
 			case MediaQueryId::AspectRatio:

+ 9 - 9
Source/Core/StyleSheetParser.cpp

@@ -124,7 +124,7 @@ public:
 
 			if (const Property* property = properties.GetProperty(id_resolution))
 			{
-				if (property->unit == Property::X)
+				if (property->unit == Unit::X)
 					image_resolution_factor = property->Get<float>();
 			}
 		}
@@ -134,14 +134,14 @@ public:
 				return false;
 
 			Vector2f position, size;
-			if (auto property = properties.GetProperty(id_rx))
-				position.x = ComputeAbsoluteLength(*property, 1.f, Vector2f(1.f));
-			if (auto property = properties.GetProperty(id_ry))
-				position.y = ComputeAbsoluteLength(*property, 1.f, Vector2f(1.f));
-			if (auto property = properties.GetProperty(id_rw))
-				size.x = ComputeAbsoluteLength(*property, 1.f, Vector2f(1.f));
-			if (auto property = properties.GetProperty(id_rh))
-				size.y = ComputeAbsoluteLength(*property, 1.f, Vector2f(1.f));
+			if (auto p = properties.GetProperty(id_rx))
+				position.x = p->Get<float>();
+			if (auto p = properties.GetProperty(id_ry))
+				position.y = p->Get<float>();
+			if (auto p = properties.GetProperty(id_rw))
+				size.x = p->Get<float>();
+			if (auto p = properties.GetProperty(id_rh))
+				size.y = p->Get<float>();
 
 			sprite_definitions.emplace_back(name, Rectanglef::FromPositionSize(position, size));
 		}

+ 6 - 6
Source/Core/StyleSheetSpecification.cpp

@@ -46,11 +46,11 @@ namespace Rml {
 static StyleSheetSpecification* instance = nullptr;
 
 struct DefaultStyleSheetParsers {
-	PropertyParserNumber number = PropertyParserNumber(Property::NUMBER);
-	PropertyParserNumber length = PropertyParserNumber(Property::LENGTH, Property::PX);
-	PropertyParserNumber length_percent = PropertyParserNumber(Property::LENGTH_PERCENT, Property::PX);
-	PropertyParserNumber number_length_percent = PropertyParserNumber(Property::NUMBER_LENGTH_PERCENT, Property::PX);
-	PropertyParserNumber angle = PropertyParserNumber(Property::ANGLE, Property::RAD);
+	PropertyParserNumber number = PropertyParserNumber(Unit::NUMBER);
+	PropertyParserNumber length = PropertyParserNumber(Unit::LENGTH, Unit::PX);
+	PropertyParserNumber length_percent = PropertyParserNumber(Unit::LENGTH_PERCENT, Unit::PX);
+	PropertyParserNumber number_length_percent = PropertyParserNumber(Unit::NUMBER_LENGTH_PERCENT, Unit::PX);
+	PropertyParserNumber angle = PropertyParserNumber(Unit::ANGLE, Unit::RAD);
 	PropertyParserKeyword keyword = PropertyParserKeyword();
 	PropertyParserString string = PropertyParserString();
 	PropertyParserAnimation animation = PropertyParserAnimation(PropertyParserAnimation::ANIMATION_PARSER);
@@ -60,7 +60,7 @@ struct DefaultStyleSheetParsers {
 	PropertyParserFontEffect font_effect = PropertyParserFontEffect();
 	PropertyParserTransform transform = PropertyParserTransform();
 	PropertyParserRatio ratio = PropertyParserRatio();
-	PropertyParserNumber resolution = PropertyParserNumber(Property::X);
+	PropertyParserNumber resolution = PropertyParserNumber(Unit::X);
 };
 
 StyleSheetSpecification::StyleSheetSpecification() :

+ 1 - 1
Source/Core/Transform.cpp

@@ -39,7 +39,7 @@ Transform::Transform(PrimitiveList primitives) : primitives(std::move(primitives
 
 Property Transform::MakeProperty(PrimitiveList primitives)
 {
-	Property p(MakeShared<Transform>(std::move(primitives)), Property::TRANSFORM);
+	Property p(MakeShared<Transform>(std::move(primitives)), Unit::TRANSFORM);
 	p.definition = StyleSheetSpecification::GetProperty(PropertyId::Transform);
 	return p;
 }

+ 33 - 37
Source/Core/TransformPrimitive.cpp

@@ -36,21 +36,21 @@ namespace Transforms {
 	/// Returns the numeric value converted to 'base_unit'. Only accepts base units of 'Number' or 'Rad':
 	///   'Number' will pass-through the provided value.
 	///   'Rad' will convert {Rad, Deg, %} -> Rad.
-	static float ResolvePrimitiveAbsoluteValue(NumericValue value, Property::Unit base_unit) noexcept
+	static float ResolvePrimitiveAbsoluteValue(NumericValue value, Unit base_unit) noexcept
 	{
-		RMLUI_ASSERT(base_unit == Property::RAD || base_unit == Property::NUMBER);
+		RMLUI_ASSERT(base_unit == Unit::RAD || base_unit == Unit::NUMBER);
 
-		if (base_unit == Property::RAD)
+		if (base_unit == Unit::RAD)
 		{
 			switch (value.unit)
 			{
-			case Property::RAD: return value.number;
-			case Property::DEG: return Math::DegreesToRadians(value.number);
-			case Property::PERCENT: return value.number * 0.01f * 2.0f * Math::RMLUI_PI;
+			case Unit::RAD: return value.number;
+			case Unit::DEG: return Math::DegreesToRadians(value.number);
+			case Unit::PERCENT: return value.number * 0.01f * 2.0f * Math::RMLUI_PI;
 			default: Log::Message(Log::LT_WARNING, "Trying to pass a non-angle unit to a property expecting an angle.");
 			}
 		}
-		else if (base_unit == Property::NUMBER && value.unit != Property::NUMBER)
+		else if (base_unit == Unit::NUMBER && value.unit != Unit::NUMBER)
 		{
 			Log::Message(Log::LT_WARNING, "A unit was passed to a property which expected a unit-less number.");
 		}
@@ -73,14 +73,14 @@ namespace Transforms {
 	}
 
 	template <size_t N>
-	inline ResolvedPrimitive<N>::ResolvedPrimitive(const NumericValue* values, Array<Property::Unit, N> base_units) noexcept
+	inline ResolvedPrimitive<N>::ResolvedPrimitive(const NumericValue* values, Array<Unit, N> base_units) noexcept
 	{
 		for (size_t i = 0; i < N; ++i)
 			this->values[i] = ResolvePrimitiveAbsoluteValue(values[i], base_units[i]);
 	}
 
 	template <size_t N>
-	inline ResolvedPrimitive<N>::ResolvedPrimitive(Array<NumericValue, N> values, Array<Property::Unit, N> base_units) noexcept
+	inline ResolvedPrimitive<N>::ResolvedPrimitive(Array<NumericValue, N> values, Array<Unit, N> base_units) noexcept
 	{
 		for (size_t i = 0; i < N; ++i)
 			this->values[i] = ResolvePrimitiveAbsoluteValue(values[i], base_units[i]);
@@ -107,21 +107,20 @@ namespace Transforms {
 	Matrix3D::Matrix3D(const Matrix4f& matrix) noexcept : ResolvedPrimitive(matrix.data()) {}
 
 	TranslateX::TranslateX(const NumericValue* values) noexcept : UnresolvedPrimitive(values) {}
-	TranslateX::TranslateX(float x, Property::Unit unit) noexcept : UnresolvedPrimitive({NumericValue(x, unit)}) {}
+	TranslateX::TranslateX(float x, Unit unit) noexcept : UnresolvedPrimitive({NumericValue(x, unit)}) {}
 
 	TranslateY::TranslateY(const NumericValue* values) noexcept : UnresolvedPrimitive(values) {}
-	TranslateY::TranslateY(float y, Property::Unit unit) noexcept : UnresolvedPrimitive({NumericValue(y, unit)}) {}
+	TranslateY::TranslateY(float y, Unit unit) noexcept : UnresolvedPrimitive({NumericValue(y, unit)}) {}
 
 	TranslateZ::TranslateZ(const NumericValue* values) noexcept : UnresolvedPrimitive(values) {}
-	TranslateZ::TranslateZ(float z, Property::Unit unit) noexcept : UnresolvedPrimitive({NumericValue(z, unit)}) {}
+	TranslateZ::TranslateZ(float z, Unit unit) noexcept : UnresolvedPrimitive({NumericValue(z, unit)}) {}
 
 	Translate2D::Translate2D(const NumericValue* values) noexcept : UnresolvedPrimitive(values) {}
-	Translate2D::Translate2D(float x, float y, Property::Unit units) noexcept : UnresolvedPrimitive({NumericValue(x, units), NumericValue(y, units)})
-	{}
+	Translate2D::Translate2D(float x, float y, Unit units) noexcept : UnresolvedPrimitive({NumericValue(x, units), NumericValue(y, units)}) {}
 
 	Translate3D::Translate3D(const NumericValue* values) noexcept : UnresolvedPrimitive(values) {}
 	Translate3D::Translate3D(NumericValue x, NumericValue y, NumericValue z) noexcept : UnresolvedPrimitive({x, y, z}) {}
-	Translate3D::Translate3D(float x, float y, float z, Property::Unit units) noexcept :
+	Translate3D::Translate3D(float x, float y, float z, Unit units) noexcept :
 		UnresolvedPrimitive({NumericValue(x, units), NumericValue(y, units), NumericValue(z, units)})
 	{}
 
@@ -142,36 +141,33 @@ namespace Transforms {
 	Scale3D::Scale3D(float xyz) noexcept : ResolvedPrimitive({xyz, xyz, xyz}) {}
 	Scale3D::Scale3D(float x, float y, float z) noexcept : ResolvedPrimitive({x, y, z}) {}
 
-	RotateX::RotateX(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Property::RAD}) {}
-	RotateX::RotateX(float angle, Property::Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Property::RAD}) {}
+	RotateX::RotateX(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD}) {}
+	RotateX::RotateX(float angle, Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Unit::RAD}) {}
 
-	RotateY::RotateY(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Property::RAD}) {}
-	RotateY::RotateY(float angle, Property::Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Property::RAD}) {}
+	RotateY::RotateY(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD}) {}
+	RotateY::RotateY(float angle, Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Unit::RAD}) {}
 
-	RotateZ::RotateZ(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Property::RAD}) {}
-	RotateZ::RotateZ(float angle, Property::Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Property::RAD}) {}
+	RotateZ::RotateZ(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD}) {}
+	RotateZ::RotateZ(float angle, Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Unit::RAD}) {}
 
-	Rotate2D::Rotate2D(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Property::RAD}) {}
-	Rotate2D::Rotate2D(float angle, Property::Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Property::RAD}) {}
+	Rotate2D::Rotate2D(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD}) {}
+	Rotate2D::Rotate2D(float angle, Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Unit::RAD}) {}
 
-	Rotate3D::Rotate3D(const NumericValue* values) noexcept :
-		ResolvedPrimitive(values, {Property::NUMBER, Property::NUMBER, Property::NUMBER, Property::RAD})
-	{}
-	Rotate3D::Rotate3D(float x, float y, float z, float angle, Property::Unit angle_unit) noexcept :
-		ResolvedPrimitive({NumericValue{x, Property::NUMBER}, NumericValue{y, Property::NUMBER}, NumericValue{z, Property::NUMBER},
-							  NumericValue{angle, angle_unit}},
-			{Property::NUMBER, Property::NUMBER, Property::NUMBER, Property::RAD})
+	Rotate3D::Rotate3D(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::NUMBER, Unit::NUMBER, Unit::NUMBER, Unit::RAD}) {}
+	Rotate3D::Rotate3D(float x, float y, float z, float angle, Unit angle_unit) noexcept :
+		ResolvedPrimitive(
+			{NumericValue{x, Unit::NUMBER}, NumericValue{y, Unit::NUMBER}, NumericValue{z, Unit::NUMBER}, NumericValue{angle, angle_unit}},
+			{Unit::NUMBER, Unit::NUMBER, Unit::NUMBER, Unit::RAD})
 	{}
 
-	SkewX::SkewX(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Property::RAD}) {}
-	SkewX::SkewX(float angle, Property::Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Property::RAD}) {}
+	SkewX::SkewX(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD}) {}
+	SkewX::SkewX(float angle, Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Unit::RAD}) {}
 
-	SkewY::SkewY(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Property::RAD}) {}
-	SkewY::SkewY(float angle, Property::Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Property::RAD}) {}
+	SkewY::SkewY(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD}) {}
+	SkewY::SkewY(float angle, Unit unit) noexcept : ResolvedPrimitive({NumericValue{angle, unit}}, {Unit::RAD}) {}
 
-	Skew2D::Skew2D(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Property::RAD, Property::RAD}) {}
-	Skew2D::Skew2D(float x, float y, Property::Unit unit) noexcept :
-		ResolvedPrimitive({NumericValue{x, unit}, {NumericValue{y, unit}}}, {Property::RAD, Property::RAD})
+	Skew2D::Skew2D(const NumericValue* values) noexcept : ResolvedPrimitive(values, {Unit::RAD, Unit::RAD}) {}
+	Skew2D::Skew2D(float x, float y, Unit unit) noexcept : ResolvedPrimitive({NumericValue{x, unit}, {NumericValue{y, unit}}}, {Unit::RAD, Unit::RAD})
 	{}
 
 	Perspective::Perspective(const NumericValue* values) noexcept : UnresolvedPrimitive(values) {}

+ 26 - 35
Source/Core/TransformUtilities.cpp

@@ -69,38 +69,29 @@ static Vector4f QuaternionSlerp(const Vector4f a, const Vector4f b, float alpha)
 	return result;
 }
 
-/// Resolve a numeric property value for an element.
-static inline float ResolveLengthPercentage(NumericValue value, Element& e, float base) noexcept
-{
-	Property prop;
-	prop.value = Variant(value.number);
-	prop.unit = value.unit;
-	return e.ResolveNumericProperty(&prop, base);
-}
-
-/// Resolve a numeric property value with the element's width as relative base value.
+// Resolve a numeric property value with the element's width as relative base value.
 static inline float ResolveWidth(NumericValue value, Element& e) noexcept
 {
-	if (value.unit & (Property::PX | Property::NUMBER))
+	if (value.unit == Unit::PX || value.unit == Unit::NUMBER)
 		return value.number;
-	return ResolveLengthPercentage(value, e, e.GetBox().GetSize(Box::BORDER).x);
+	return e.ResolveNumericValue(value, e.GetBox().GetSize(BoxArea::Border).x);
 }
 
-/// Resolve a numeric property value with the element's height as relative base value.
+// Resolve a numeric property value with the element's height as relative base value.
 static inline float ResolveHeight(NumericValue value, Element& e) noexcept
 {
-	if (value.unit & (Property::PX | Property::NUMBER))
+	if (value.unit == Unit::PX || value.unit == Unit::NUMBER)
 		return value.number;
-	return ResolveLengthPercentage(value, e, e.GetBox().GetSize(Box::BORDER).y);
+	return e.ResolveNumericValue(value, e.GetBox().GetSize(BoxArea::Border).y);
 }
 
-/// Resolve a numeric property value with the element's depth as relative base value.
+// Resolve a numeric property value with the element's depth as relative base value.
 static inline float ResolveDepth(NumericValue value, Element& e) noexcept
 {
-	if (value.unit & (Property::PX | Property::NUMBER))
+	if (value.unit == Unit::PX || value.unit == Unit::NUMBER)
 		return value.number;
-	Vector2f size = e.GetBox().GetSize(Box::BORDER);
-	return ResolveLengthPercentage(value, e, Math::Max(size.x, size.y));
+	Vector2f size = e.GetBox().GetSize(BoxArea::Border);
+	return e.ResolveNumericValue(value, Math::Max(size.x, size.y));
 }
 
 static inline String ToString(NumericValue value) noexcept
@@ -270,30 +261,30 @@ struct PrepareVisitor {
 
 	bool operator()(TranslateX& p)
 	{
-		p.values[0] = NumericValue{ResolveWidth(p.values[0], e), Property::PX};
+		p.values[0] = NumericValue{ResolveWidth(p.values[0], e), Unit::PX};
 		return true;
 	}
 	bool operator()(TranslateY& p)
 	{
-		p.values[0] = NumericValue{ResolveHeight(p.values[0], e), Property::PX};
+		p.values[0] = NumericValue{ResolveHeight(p.values[0], e), Unit::PX};
 		return true;
 	}
 	bool operator()(TranslateZ& p)
 	{
-		p.values[0] = NumericValue{ResolveDepth(p.values[0], e), Property::PX};
+		p.values[0] = NumericValue{ResolveDepth(p.values[0], e), Unit::PX};
 		return true;
 	}
 	bool operator()(Translate2D& p)
 	{
-		p.values[0] = NumericValue{ResolveWidth(p.values[0], e), Property::PX};
-		p.values[1] = NumericValue{ResolveHeight(p.values[1], e), Property::PX};
+		p.values[0] = NumericValue{ResolveWidth(p.values[0], e), Unit::PX};
+		p.values[1] = NumericValue{ResolveHeight(p.values[1], e), Unit::PX};
 		return true;
 	}
 	bool operator()(Translate3D& p)
 	{
-		p.values[0] = NumericValue{ResolveWidth(p.values[0], e), Property::PX};
-		p.values[1] = NumericValue{ResolveHeight(p.values[1], e), Property::PX};
-		p.values[2] = NumericValue{ResolveDepth(p.values[2], e), Property::PX};
+		p.values[0] = NumericValue{ResolveWidth(p.values[0], e), Unit::PX};
+		p.values[1] = NumericValue{ResolveHeight(p.values[1], e), Unit::PX};
+		p.values[2] = NumericValue{ResolveDepth(p.values[2], e), Unit::PX};
 		return true;
 	}
 	template <size_t N>
@@ -406,18 +397,18 @@ struct GetGenericTypeVisitor {
 };
 
 struct ConvertToGenericTypeVisitor {
-	Translate3D operator()(const TranslateX& p) { return Translate3D{p.values[0], {0.0f, Property::PX}, {0.0f, Property::PX}}; }
-	Translate3D operator()(const TranslateY& p) { return Translate3D{{0.0f, Property::PX}, p.values[0], {0.0f, Property::PX}}; }
-	Translate3D operator()(const TranslateZ& p) { return Translate3D{{0.0f, Property::PX}, {0.0f, Property::PX}, p.values[0]}; }
-	Translate3D operator()(const Translate2D& p) { return Translate3D{p.values[0], p.values[1], {0.0f, Property::PX}}; }
+	Translate3D operator()(const TranslateX& p) { return Translate3D{p.values[0], {0.0f, Unit::PX}, {0.0f, Unit::PX}}; }
+	Translate3D operator()(const TranslateY& p) { return Translate3D{{0.0f, Unit::PX}, p.values[0], {0.0f, Unit::PX}}; }
+	Translate3D operator()(const TranslateZ& p) { return Translate3D{{0.0f, Unit::PX}, {0.0f, Unit::PX}, p.values[0]}; }
+	Translate3D operator()(const Translate2D& p) { return Translate3D{p.values[0], p.values[1], {0.0f, Unit::PX}}; }
 	Scale3D operator()(const ScaleX& p) { return Scale3D{p.values[0], 1.0f, 1.0f}; }
 	Scale3D operator()(const ScaleY& p) { return Scale3D{1.0f, p.values[0], 1.0f}; }
 	Scale3D operator()(const ScaleZ& p) { return Scale3D{1.0f, 1.0f, p.values[0]}; }
 	Scale3D operator()(const Scale2D& p) { return Scale3D{p.values[0], p.values[1], 1.0f}; }
-	Rotate3D operator()(const RotateX& p) { return Rotate3D{1, 0, 0, p.values[0], Property::RAD}; }
-	Rotate3D operator()(const RotateY& p) { return Rotate3D{0, 1, 0, p.values[0], Property::RAD}; }
-	Rotate3D operator()(const RotateZ& p) { return Rotate3D{0, 0, 1, p.values[0], Property::RAD}; }
-	Rotate3D operator()(const Rotate2D& p) { return Rotate3D{0, 0, 1, p.values[0], Property::RAD}; }
+	Rotate3D operator()(const RotateX& p) { return Rotate3D{1, 0, 0, p.values[0], Unit::RAD}; }
+	Rotate3D operator()(const RotateY& p) { return Rotate3D{0, 1, 0, p.values[0], Unit::RAD}; }
+	Rotate3D operator()(const RotateZ& p) { return Rotate3D{0, 0, 1, p.values[0], Unit::RAD}; }
+	Rotate3D operator()(const Rotate2D& p) { return Rotate3D{0, 0, 1, p.values[0], Unit::RAD}; }
 
 	template <typename T>
 	TransformPrimitive operator()(const T& p)

+ 31 - 0
Source/Core/TypeConverter.cpp

@@ -39,6 +39,37 @@
 
 namespace Rml {
 
+bool TypeConverter<Unit, String>::Convert(const Unit& src, String& dest)
+{
+	switch (src)
+	{
+	// clang-format off
+	case Unit::NUMBER:  dest = "";    return true;
+	case Unit::PERCENT: dest = "%";   return true;
+
+	case Unit::PX:      dest = "px";  return true;
+	case Unit::DP:      dest = "dp";  return true;
+	case Unit::VW:      dest = "vw";  return true;
+	case Unit::VH:      dest = "vh";  return true;
+	case Unit::X:       dest = "x";   return true;
+	case Unit::EM:      dest = "em";  return true;
+	case Unit::REM:     dest = "rem"; return true;
+
+	case Unit::INCH:    dest = "in";  return true;
+	case Unit::CM:      dest = "cm";  return true;
+	case Unit::MM:      dest = "mm";  return true;
+	case Unit::PT:      dest = "pt";  return true;
+	case Unit::PC:      dest = "pc";  return true;
+
+	case Unit::DEG:     dest = "deg"; return true;
+	case Unit::RAD:     dest = "rad"; return true;
+	// clang-format on
+	default: break;
+	}
+
+	return false;
+}
+
 bool TypeConverter<TransformPtr, TransformPtr>::Convert(const TransformPtr& src, TransformPtr& dest)
 {
 	dest = src;

+ 28 - 26
Source/Core/WidgetScroll.cpp

@@ -214,8 +214,8 @@ void WidgetScroll::FormatElements(const Vector2f containing_block, bool resize_e
 	Box parent_box;
 	LayoutDetails::BuildBox(parent_box, containing_block, parent);
 	slider_length -= orientation == VERTICAL
-		? (parent_box.GetCumulativeEdge(Box::CONTENT, Box::TOP) + parent_box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM))
-		: (parent_box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) + parent_box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT));
+		? (parent_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Top) + parent_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Bottom))
+		: (parent_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Left) + parent_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Right));
 
 	// Set the length of the slider.
 	Vector2f content = parent_box.GetSize();
@@ -230,8 +230,8 @@ void WidgetScroll::FormatElements(const Vector2f containing_block, bool resize_e
 	LayoutDetails::BuildBox(track_box, parent_box.GetSize(), track);
 	content = track_box.GetSize();
 	content[length_axis] = slider_length -= orientation == VERTICAL
-		? (track_box.GetCumulativeEdge(Box::CONTENT, Box::TOP) + track_box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM))
-		: (track_box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) + track_box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT));
+		? (track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Top) + track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Bottom))
+		: (track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Left) + track_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Right));
 	// If no height has been explicitly specified for the track, it'll be initialised to -1 as per normal block
 	// elements. We'll fix that up here.
 	if (orientation == HORIZONTAL && content.y < 0)
@@ -251,7 +251,7 @@ void WidgetScroll::FormatElements(const Vector2f containing_block, bool resize_e
 		arrows[i]->SetBox(arrow_box);
 
 		// Shrink the track length by the arrow size.
-		content[length_axis] -= arrow_box.GetSize(Box::MARGIN)[length_axis];
+		content[length_axis] -= arrow_box.GetSize(BoxArea::Margin)[length_axis];
 	}
 
 	// Now the track has been sized, we can fix everything into position.
@@ -260,32 +260,32 @@ void WidgetScroll::FormatElements(const Vector2f containing_block, bool resize_e
 
 	if (orientation == VERTICAL)
 	{
-		Vector2f offset(arrows[0]->GetBox().GetEdge(Box::MARGIN, Box::LEFT), arrows[0]->GetBox().GetEdge(Box::MARGIN, Box::TOP));
+		Vector2f offset(arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left), arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top));
 		arrows[0]->SetOffset(offset, parent);
 
-		offset.x = track->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-		offset.y += arrows[0]->GetBox().GetSize(Box::BORDER).y + arrows[0]->GetBox().GetEdge(Box::MARGIN, Box::BOTTOM) +
-			track->GetBox().GetEdge(Box::MARGIN, Box::TOP);
+		offset.x = track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left);
+		offset.y += arrows[0]->GetBox().GetSize(BoxArea::Border).y + arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom) +
+			track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top);
 		track->SetOffset(offset, parent);
 
-		offset.x = arrows[1]->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-		offset.y += track->GetBox().GetSize(Box::BORDER).y + track->GetBox().GetEdge(Box::MARGIN, Box::BOTTOM) +
-			arrows[1]->GetBox().GetEdge(Box::MARGIN, Box::TOP);
+		offset.x = arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left);
+		offset.y += track->GetBox().GetSize(BoxArea::Border).y + track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Bottom) +
+			arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top);
 		arrows[1]->SetOffset(offset, parent);
 	}
 	else
 	{
-		Vector2f offset(arrows[0]->GetBox().GetEdge(Box::MARGIN, Box::LEFT), arrows[0]->GetBox().GetEdge(Box::MARGIN, Box::TOP));
+		Vector2f offset(arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left), arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top));
 		arrows[0]->SetOffset(offset, parent);
 
-		offset.x += arrows[0]->GetBox().GetSize(Box::BORDER).x + arrows[0]->GetBox().GetEdge(Box::MARGIN, Box::RIGHT) +
-			track->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-		offset.y = track->GetBox().GetEdge(Box::MARGIN, Box::TOP);
+		offset.x += arrows[0]->GetBox().GetSize(BoxArea::Border).x + arrows[0]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right) +
+			track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left);
+		offset.y = track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top);
 		track->SetOffset(offset, parent);
 
-		offset.x += track->GetBox().GetSize(Box::BORDER).x + track->GetBox().GetEdge(Box::MARGIN, Box::RIGHT) +
-			arrows[1]->GetBox().GetEdge(Box::MARGIN, Box::LEFT);
-		offset.y = arrows[1]->GetBox().GetEdge(Box::MARGIN, Box::TOP);
+		offset.x += track->GetBox().GetSize(BoxArea::Border).x + track->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Right) +
+			arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left);
+		offset.y = arrows[1]->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top);
 		arrows[1]->SetOffset(offset, parent);
 	}
 
@@ -315,8 +315,8 @@ void WidgetScroll::FormatBar(float bar_length)
 
 		if (orientation == VERTICAL)
 		{
-			float track_length =
-				track_size.y - (bar_box.GetCumulativeEdge(Box::CONTENT, Box::TOP) + bar_box.GetCumulativeEdge(Box::CONTENT, Box::BOTTOM));
+			float track_length = track_size.y -
+				(bar_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Top) + bar_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Bottom));
 
 			if (height.type == height.Auto)
 			{
@@ -336,8 +336,8 @@ void WidgetScroll::FormatBar(float bar_length)
 		}
 		else
 		{
-			float track_length =
-				track_size.x - (bar_box.GetCumulativeEdge(Box::CONTENT, Box::LEFT) + bar_box.GetCumulativeEdge(Box::CONTENT, Box::RIGHT));
+			float track_length = track_size.x -
+				(bar_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Left) + bar_box.GetCumulativeEdge(BoxArea::Content, BoxEdge::Right));
 
 			if (width.type == width.Auto)
 			{
@@ -452,18 +452,20 @@ void WidgetScroll::ProcessEvent(Event& event)
 void WidgetScroll::PositionBar()
 {
 	const Vector2f track_dimensions = track->GetBox().GetSize();
-	const Vector2f bar_dimensions = bar->GetBox().GetSize(Box::BORDER);
+	const Vector2f bar_dimensions = bar->GetBox().GetSize(BoxArea::Border);
 
 	if (orientation == VERTICAL)
 	{
 		float traversable_track_length = track_dimensions.y - bar_dimensions.y;
 		bar->SetOffset(
-			Vector2f(bar->GetBox().GetEdge(Box::MARGIN, Box::LEFT), track->GetRelativeOffset().y + traversable_track_length * bar_position), parent);
+			Vector2f(bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Left), track->GetRelativeOffset().y + traversable_track_length * bar_position),
+			parent);
 	}
 	else
 	{
 		float traversable_track_length = track_dimensions.x - bar_dimensions.x;
-		bar->SetOffset(Vector2f(track->GetRelativeOffset().x + traversable_track_length * bar_position, bar->GetBox().GetEdge(Box::MARGIN, Box::TOP)),
+		bar->SetOffset(
+			Vector2f(track->GetRelativeOffset().x + traversable_track_length * bar_position, bar->GetBox().GetEdge(BoxArea::Margin, BoxEdge::Top)),
 			parent);
 	}
 }

+ 1 - 1
Source/Debugger/DebuggerPlugin.cpp

@@ -169,7 +169,7 @@ void DebuggerPlugin::Render()
 					{
 						Vector2f box_offset;
 						const Box& box = element->GetBox(j, box_offset);
-						Geometry::RenderOutline(element->GetAbsoluteOffset(Box::BORDER) + box_offset, box.GetSize(Box::BORDER),
+						Geometry::RenderOutline(element->GetAbsoluteOffset(BoxArea::Border) + box_offset, box.GetSize(BoxArea::Border),
 							Colourb(255, 0, 0, 128), 1);
 					}
 

+ 1 - 1
Source/Debugger/ElementContextHook.cpp

@@ -42,7 +42,7 @@ ElementContextHook::~ElementContextHook() {}
 void ElementContextHook::Initialise(DebuggerPlugin* _debugger)
 {
 	SetId("rmlui-debug-hook");
-	SetProperty(PropertyId::ZIndex, Property(999'999, Property::NUMBER));
+	SetProperty(PropertyId::ZIndex, Property(999'999, Unit::NUMBER));
 	debugger = _debugger;
 }
 

+ 13 - 13
Source/Debugger/ElementInfo.cpp

@@ -141,9 +141,9 @@ void ElementInfo::RenderHoverElement()
 			// Render the content area.
 			Vector2f box_offset;
 			const Box& element_box = hover_element->GetBox(i, box_offset);
-			Vector2f size = element_box.GetSize(Box::BORDER);
+			Vector2f size = element_box.GetSize(BoxArea::Border);
 			size = Vector2f(std::max(size.x, 2.0f), std::max(size.y, 2.0f));
-			Geometry::RenderOutline(hover_element->GetAbsoluteOffset(Box::BORDER) + box_offset, size, Colourb(255, 0, 0, 255), 1);
+			Geometry::RenderOutline(hover_element->GetAbsoluteOffset(BoxArea::Border) + box_offset, size, Colourb(255, 0, 0, 255), 1);
 		}
 	}
 }
@@ -157,23 +157,23 @@ void ElementInfo::RenderSourceElement()
 		for (int i = 0; i < source_element->GetNumBoxes(); i++)
 		{
 			Vector2f box_offset;
-			const Box element_box = source_element->GetBox(i, box_offset);
-			const Vector2f border_offset = box_offset + source_element->GetAbsoluteOffset(Box::BORDER);
+			const Box& element_box = source_element->GetBox(i, box_offset);
+			const Vector2f border_offset = box_offset + source_element->GetAbsoluteOffset(BoxArea::Border);
 
 			// Content area:
-			Geometry::RenderBox(border_offset + element_box.GetPosition(Box::CONTENT), element_box.GetSize(), Colourb(158, 214, 237, 128));
+			Geometry::RenderBox(border_offset + element_box.GetPosition(BoxArea::Content), element_box.GetSize(), Colourb(158, 214, 237, 128));
 
 			// Padding area:
-			Geometry::RenderBox(border_offset + element_box.GetPosition(Box::PADDING), element_box.GetSize(Box::PADDING),
-				border_offset + element_box.GetPosition(Box::CONTENT), element_box.GetSize(), Colourb(135, 122, 214, 128));
+			Geometry::RenderBox(border_offset + element_box.GetPosition(BoxArea::Padding), element_box.GetSize(BoxArea::Padding),
+				border_offset + element_box.GetPosition(BoxArea::Content), element_box.GetSize(), Colourb(135, 122, 214, 128));
 
 			// Border area:
-			Geometry::RenderBox(border_offset + element_box.GetPosition(Box::BORDER), element_box.GetSize(Box::BORDER),
-				border_offset + element_box.GetPosition(Box::PADDING), element_box.GetSize(Box::PADDING), Colourb(133, 133, 133, 128));
+			Geometry::RenderBox(border_offset + element_box.GetPosition(BoxArea::Border), element_box.GetSize(BoxArea::Border),
+				border_offset + element_box.GetPosition(BoxArea::Padding), element_box.GetSize(BoxArea::Padding), Colourb(133, 133, 133, 128));
 
 			// Border area:
-			Geometry::RenderBox(border_offset + element_box.GetPosition(Box::MARGIN), element_box.GetSize(Box::MARGIN),
-				border_offset + element_box.GetPosition(Box::BORDER), element_box.GetSize(Box::BORDER), Colourb(240, 255, 131, 128));
+			Geometry::RenderBox(border_offset + element_box.GetPosition(BoxArea::Margin), element_box.GetSize(BoxArea::Margin),
+				border_offset + element_box.GetPosition(BoxArea::Border), element_box.GetSize(BoxArea::Border), Colourb(240, 255, 131, 128));
 		}
 	}
 }
@@ -527,8 +527,8 @@ void ElementInfo::UpdateSourceElement()
 		// left, top, width, height.
 		if (source_element != nullptr)
 		{
-			const Vector2f element_offset = source_element->GetRelativeOffset(Box::BORDER);
-			const Vector2f element_size = source_element->GetBox().GetSize(Box::BORDER);
+			const Vector2f element_offset = source_element->GetRelativeOffset(BoxArea::Border);
+			const Vector2f element_size = source_element->GetBox().GetSize(BoxArea::Border);
 			Element* offset_parent = source_element->GetOffsetParent();
 			const String offset_parent_rml =
 				(offset_parent ? StringUtilities::EncodeRml(offset_parent->GetAddress(false, false)) : String("<em>none</em>"));

+ 2 - 2
Source/Lottie/ElementLottie.cpp

@@ -88,7 +88,7 @@ void ElementLottie::OnRender()
 			GenerateGeometry();
 
 		UpdateTexture();
-		geometry.Render(GetAbsoluteOffset(Box::CONTENT).Round());
+		geometry.Render(GetAbsoluteOffset(BoxArea::Content).Round());
 	}
 }
 
@@ -140,7 +140,7 @@ void ElementLottie::GenerateGeometry()
 	Colourb quad_colour = computed.image_color();
 	quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha);
 
-	const Vector2f render_dimensions_f = GetBox().GetSize(Box::CONTENT).Round();
+	const Vector2f render_dimensions_f = GetBox().GetSize(BoxArea::Content).Round();
 	render_dimensions = Vector2i(render_dimensions_f);
 
 	GeometryUtilities::GenerateQuad(&vertices[0], &indices[0], Vector2f(0, 0), render_dimensions_f, quad_colour, texcoords[0], texcoords[1]);

+ 2 - 2
Source/SVG/ElementSVG.cpp

@@ -76,7 +76,7 @@ void ElementSVG::OnRender()
 			GenerateGeometry();
 
 		UpdateTexture();
-		geometry.Render(GetAbsoluteOffset(Box::CONTENT));
+		geometry.Render(GetAbsoluteOffset(BoxArea::Content));
 	}
 }
 
@@ -133,7 +133,7 @@ void ElementSVG::GenerateGeometry()
 	Colourb quad_colour = computed.image_color();
 	quad_colour.alpha = (byte)(opacity * (float)quad_colour.alpha);
 
-	const Vector2f render_dimensions_f = GetBox().GetSize(Box::CONTENT).Round();
+	const Vector2f render_dimensions_f = GetBox().GetSize(BoxArea::Content).Round();
 	render_dimensions.x = int(render_dimensions_f.x);
 	render_dimensions.y = int(render_dimensions_f.y);
 

+ 3 - 3
Tests/Source/Benchmarks/BackgroundBorder.cpp

@@ -110,7 +110,7 @@ TEST_CASE("backgrounds_and_borders")
 		bench.run("Background all", [&] {
 			// Force regeneration of backgrounds without changing layout
 			for (auto& element : elements)
-				element->SetProperty(Rml::PropertyId::BackgroundColor, Rml::Property(Colourb(), Property::COLOUR));
+				element->SetProperty(Rml::PropertyId::BackgroundColor, Rml::Property(Colourb(), Unit::COLOUR));
 			context->Update();
 			context->Render();
 		});
@@ -118,7 +118,7 @@ TEST_CASE("backgrounds_and_borders")
 		bench.run("Border all", [&] {
 			// Force regeneration of borders without changing layout
 			for (auto& element : elements)
-				element->SetProperty(Rml::PropertyId::BorderLeftColor, Rml::Property(Colourb(), Property::COLOUR));
+				element->SetProperty(Rml::PropertyId::BorderLeftColor, Rml::Property(Colourb(), Unit::COLOUR));
 			context->Update();
 			context->Render();
 		});
@@ -132,7 +132,7 @@ TEST_CASE("backgrounds_and_borders")
 
 		bench.run("Border " + id, [&] {
 			for (auto& element : elements)
-				element->SetProperty(Rml::PropertyId::BorderLeftColor, Rml::Property(Colourb(), Property::COLOUR));
+				element->SetProperty(Rml::PropertyId::BorderLeftColor, Rml::Property(Colourb(), Unit::COLOUR));
 			context->Update();
 			context->Render();
 		});

+ 23 - 23
Tests/Source/UnitTests/Element.cpp

@@ -319,9 +319,9 @@ TEST_CASE("Element.ScrollIntoView")
 		}
 	}
 
-	REQUIRE(cells[0][0]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(0, 0));
-	REQUIRE(cells[2][2]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(100, 100));
-	REQUIRE(cells[3][3]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(150, 150));
+	REQUIRE(cells[0][0]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(0, 0));
+	REQUIRE(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(100, 100));
+	REQUIRE(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(150, 150));
 	REQUIRE(scrollable->GetScrollLeft() == 0);
 	REQUIRE(scrollable->GetScrollTop() == 0);
 
@@ -331,9 +331,9 @@ TEST_CASE("Element.ScrollIntoView")
 
 		Run(context);
 
-		CHECK(cells[0][0]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(-50, -100));
-		CHECK(cells[2][2]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(50, 0));
-		CHECK(cells[3][3]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(100, 50));
+		CHECK(cells[0][0]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(-50, -100));
+		CHECK(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(50, 0));
+		CHECK(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(100, 50));
 		CHECK(scrollable->GetScrollLeft() == 50);
 		CHECK(scrollable->GetScrollTop() == 100);
 
@@ -341,9 +341,9 @@ TEST_CASE("Element.ScrollIntoView")
 
 		Run(context);
 
-		CHECK(cells[0][0]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(-50, -50));
-		CHECK(cells[2][2]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(50, 50));
-		CHECK(cells[3][3]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(100, 100));
+		CHECK(cells[0][0]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(-50, -50));
+		CHECK(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(50, 50));
+		CHECK(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(100, 100));
 		CHECK(scrollable->GetScrollLeft() == 50);
 		CHECK(scrollable->GetScrollTop() == 50);
 	}
@@ -354,9 +354,9 @@ TEST_CASE("Element.ScrollIntoView")
 
 		Run(context);
 
-		CHECK(cells[0][0]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(-75, -75));
-		CHECK(cells[2][2]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(25, 25));
-		CHECK(cells[3][3]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(75, 75));
+		CHECK(cells[0][0]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(-75, -75));
+		CHECK(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(25, 25));
+		CHECK(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(75, 75));
 
 		SUBCASE("NearestAlready")
 		{
@@ -364,9 +364,9 @@ TEST_CASE("Element.ScrollIntoView")
 
 			Run(context);
 
-			CHECK(cells[0][0]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(-75, -75));
-			CHECK(cells[2][2]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(25, 25));
-			CHECK(cells[3][3]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(75, 75));
+			CHECK(cells[0][0]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(-75, -75));
+			CHECK(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(25, 25));
+			CHECK(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(75, 75));
 		}
 
 		SUBCASE("NearestBefore")
@@ -375,9 +375,9 @@ TEST_CASE("Element.ScrollIntoView")
 
 			Run(context);
 
-			CHECK(cells[0][0]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(-50, -50));
-			CHECK(cells[1][1]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(0, 0));
-			CHECK(cells[2][2]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(50, 50));
+			CHECK(cells[0][0]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(-50, -50));
+			CHECK(cells[1][1]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(0, 0));
+			CHECK(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(50, 50));
 		}
 
 		SUBCASE("NearestAfter")
@@ -386,9 +386,9 @@ TEST_CASE("Element.ScrollIntoView")
 
 			Run(context);
 
-			CHECK(cells[1][1]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(-50, -50));
-			CHECK(cells[2][2]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(0, 0));
-			CHECK(cells[3][3]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(50, 50));
+			CHECK(cells[1][1]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(-50, -50));
+			CHECK(cells[2][2]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(0, 0));
+			CHECK(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(50, 50));
 		}
 
 		SUBCASE("Smoothscroll")
@@ -402,7 +402,7 @@ TEST_CASE("Element.ScrollIntoView")
 			Run(context);
 
 			// We don't define the exact offset at this time step, but it should be somewhere between the start and end offsets.
-			Vector2f offset = cells[3][3]->GetAbsoluteOffset(Rml::Box::Area::BORDER);
+			Vector2f offset = cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border);
 			CHECK(offset.x > 50.f);
 			CHECK(offset.y > 50.f);
 			CHECK(offset.x < 75.f);
@@ -414,7 +414,7 @@ TEST_CASE("Element.ScrollIntoView")
 				system_interface->SetTime(t);
 				Run(context);
 			}
-			CHECK(cells[3][3]->GetAbsoluteOffset(Rml::Box::Area::BORDER) == Vector2f(50, 50));
+			CHECK(cells[3][3]->GetAbsoluteOffset(Rml::BoxArea::Border) == Vector2f(50, 50));
 		}
 	}
 

+ 2 - 2
Tests/Source/VisualTests/TestViewer.cpp

@@ -242,7 +242,7 @@ bool TestViewer::LoadTest(const Rml::String& directory, const Rml::String& filen
 				document_reference = context->LoadDocumentFromMemory(source_reference, Rml::StringUtilities::Replace(reference_path, ':', '|'));
 				if (document_reference)
 				{
-					document_reference->SetProperty(PropertyId::Left, Property(510.f, Property::DP));
+					document_reference->SetProperty(PropertyId::Left, Property(510.f, Unit::DP));
 					document_reference->Show(ModalFlag::None, FocusFlag::None);
 				}
 			}
@@ -335,7 +335,7 @@ void TestViewer::SetGoToText(const Rml::String& rml)
 void TestViewer::SetAttention(bool active)
 {
 	if (active)
-		document_description->SetProperty(Rml::PropertyId::BackgroundColor, Rml::Property(Rml::Colourb(100, 100, 30), Rml::Property::COLOUR));
+		document_description->SetProperty(Rml::PropertyId::BackgroundColor, Rml::Property(Rml::Colourb(100, 100, 30), Rml::Unit::COLOUR));
 	else
 		document_description->RemoveProperty(Rml::PropertyId::BackgroundColor);
 }

+ 7 - 0
changelog.md

@@ -41,6 +41,13 @@ More details to be posted later. Expect some possible layout shifts in existing
 - Possible layout changes, usually due to better CSS conformance.
 - Reworked font engine interface, in particular in terms of font metrics and letter-spacing.
 
+Changed `Box` enums and `Property` units as follows:
+- `Box::Area` -> `BoxArea` (e.g. `Box::BORDER` -> `BoxArea::Border`, values now in titlecase).
+- `Box::Edge` -> `BoxEdge` (e.g. `Box::TOP` -> `BoxEdge::Top`, values now in titlecase).
+- `Box::Direction` -> `BoxDirection` (e.g. `Box::VERTICAL` -> `BoxDirection::Vertical`, values now in titlecase).
+- `Property::Unit` -> `Unit` (e.g. `Property::PX` -> `Unit::PX`).
+- Replaced `Element::ResolveNumericProperty` with `Element::ResolveLength` and `Element::ResolveNumericValue`. Can be used together with `Property::GetNumericValue`.
+
 Removed deprecated functionality:
 - Removed the `<datagrid>` and `<dataselect>` elements, related utilities, and associated tutorials. Users are encouraged to replace this functionality by [tables](https://mikke89.github.io/RmlUiDoc/pages/rcss/tables.html), [select boxes](https://mikke89.github.io/RmlUiDoc/pages/rml/forms.html#select), and [data bindings](https://mikke89.github.io/RmlUiDoc/pages/data_bindings.html).