Browse Source

Media queries (#169)

Maximilian Stark 4 years ago
parent
commit
dd99820ecd

+ 4 - 0
CMake/FileList.cmake

@@ -76,6 +76,7 @@ set(Core_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.h
+    ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserRatio.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserString.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserString.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserTransform.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserTransform.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyShorthandDefinition.h
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyShorthandDefinition.h
@@ -202,6 +203,7 @@ set(Core_PUB_HDR_FILES
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StreamMemory.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StreamMemory.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StringUtilities.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StringUtilities.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StyleSheet.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StyleSheet.h
+    ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StyleSheetContainer.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StyleSheetSpecification.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/StyleSheetSpecification.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/SystemInterface.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/SystemInterface.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Texture.h
     ${PROJECT_SOURCE_DIR}/Include/RmlUi/Core/Texture.h
@@ -351,6 +353,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserColour.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserKeyword.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserNumber.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserRatio.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserString.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserString.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserTransform.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertyParserTransform.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertySpecification.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/PropertySpecification.cpp
@@ -361,6 +364,7 @@ set(Core_SRC_FILES
     ${PROJECT_SOURCE_DIR}/Source/Core/StreamMemory.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StreamMemory.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StringUtilities.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StringUtilities.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheet.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheet.cpp
+    ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetContainer.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetFactory.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetFactory.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNode.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNode.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelector.cpp
     ${PROJECT_SOURCE_DIR}/Source/Core/StyleSheetNodeSelector.cpp

+ 1 - 0
Include/RmlUi/Core.h

@@ -79,6 +79,7 @@
 #include "Core/Spritesheet.h"
 #include "Core/Spritesheet.h"
 #include "Core/StringUtilities.h"
 #include "Core/StringUtilities.h"
 #include "Core/StyleSheet.h"
 #include "Core/StyleSheet.h"
+#include "Core/StyleSheetContainer.h"
 #include "Core/StyleSheetSpecification.h"
 #include "Core/StyleSheetSpecification.h"
 #include "Core/SystemInterface.h"
 #include "Core/SystemInterface.h"
 #include "Core/Texture.h"
 #include "Core/Texture.h"

+ 2 - 1
Include/RmlUi/Core/Element.h

@@ -60,6 +60,7 @@ class PropertiesIteratorView;
 class PropertyDictionary;
 class PropertyDictionary;
 class RenderInterface;
 class RenderInterface;
 class StyleSheet;
 class StyleSheet;
+class StyleSheetContainer;
 class TransformState;
 class TransformState;
 struct ElementMeta;
 struct ElementMeta;
 struct StackingOrderedChild;
 struct StackingOrderedChild;
@@ -104,7 +105,7 @@ public:
 
 
 	/// Returns the active style sheet for this element. This may be nullptr.
 	/// Returns the active style sheet for this element. This may be nullptr.
 	/// @return The element's style sheet.
 	/// @return The element's style sheet.
-	virtual const SharedPtr<StyleSheet>& GetStyleSheet() const;
+	virtual const StyleSheet* GetStyleSheet() const;
 
 
 	/// Returns the element's definition.
 	/// Returns the element's definition.
 	/// @return The element's definition.
 	/// @return The element's definition.

+ 12 - 5
Include/RmlUi/Core/ElementDocument.h

@@ -38,6 +38,7 @@ class Stream;
 class DocumentHeader;
 class DocumentHeader;
 class ElementText;
 class ElementText;
 class StyleSheet;
 class StyleSheet;
+class StyleSheetContainer;
 
 
 /**
 /**
 	 ModalFlag used for controlling the modal state of the document.
 	 ModalFlag used for controlling the modal state of the document.
@@ -83,10 +84,13 @@ public:
 	/// Returns the source address of this document.
 	/// Returns the source address of this document.
 	const String& GetSourceURL() const;
 	const String& GetSourceURL() const;
 
 
-	/// Sets the style sheet this document, and all of its children, uses.
-	void SetStyleSheet(SharedPtr<StyleSheet> style_sheet);
 	/// Returns the document's style sheet.
 	/// Returns the document's style sheet.
-	const SharedPtr<StyleSheet>& GetStyleSheet() const override;
+	const StyleSheet* GetStyleSheet() const override;
+	/// Sets the style sheet this document, and all of its children, uses.
+	void SetStyleSheetContainer(SharedPtr<StyleSheetContainer> style_sheet);
+	/// Returns the active style sheet container for this element.
+	/// @return The element's style sheet container.
+	const SharedPtr<StyleSheetContainer>& GetStyleSheetContainer() const;
 	/// Reload the document's style sheet from source files.
 	/// Reload the document's style sheet from source files.
 	/// Styles will be reloaded from <style> tags and external style sheets, but not inline 'style' attributes.
 	/// Styles will be reloaded from <style> tags and external style sheets, but not inline 'style' attributes.
 	/// @note The source url originally used to load the document must still be a valid RML document.
 	/// @note The source url originally used to load the document must still be a valid RML document.
@@ -155,6 +159,9 @@ private:
 	/// Returns true if the document has been marked as needing a re-layout.
 	/// Returns true if the document has been marked as needing a re-layout.
 	bool IsLayoutDirty() override;
 	bool IsLayoutDirty() override;
 
 
+	/// Notify the document that media query related properties have changed and that stylesheets need to be re-evaluated.
+	void DirtyMediaQueries();
+	
 	/// Updates all sizes defined by the 'dp' unit.
 	/// Updates all sizes defined by the 'dp' unit.
 	void DirtyDpProperties();
 	void DirtyDpProperties();
 
 
@@ -175,8 +182,8 @@ private:
 	// The original path this document came from
 	// The original path this document came from
 	String source_url;
 	String source_url;
 
 
-	// The document's style sheet.
-	SharedPtr<StyleSheet> style_sheet;
+	// The document's style sheet container.
+	SharedPtr<StyleSheetContainer> style_sheet_container;
 
 
 	Context* context;
 	Context* context;
 
 

+ 4 - 4
Include/RmlUi/Core/Factory.h

@@ -49,7 +49,7 @@ class EventListener;
 class EventListenerInstancer;
 class EventListenerInstancer;
 class FontEffect;
 class FontEffect;
 class FontEffectInstancer;
 class FontEffectInstancer;
-class StyleSheet;
+class StyleSheetContainer;
 class PropertyDictionary;
 class PropertyDictionary;
 class PropertySpecification;
 class PropertySpecification;
 class DecoratorInstancerInterface;
 class DecoratorInstancerInterface;
@@ -141,15 +141,15 @@ public:
 	/// Creates a style sheet from a user-generated string.
 	/// Creates a style sheet from a user-generated string.
 	/// @param[in] string The contents of the style sheet.
 	/// @param[in] string The contents of the style sheet.
 	/// @return A pointer to the newly created style sheet.
 	/// @return A pointer to the newly created style sheet.
-	static SharedPtr<StyleSheet> InstanceStyleSheetString(const String& string);
+	static SharedPtr<StyleSheetContainer> InstanceStyleSheetString(const String& string);
 	/// Creates a style sheet from a file.
 	/// Creates a style sheet from a file.
 	/// @param[in] file_name The location of the style sheet file.
 	/// @param[in] file_name The location of the style sheet file.
 	/// @return A pointer to the newly created style sheet.
 	/// @return A pointer to the newly created style sheet.
-	static SharedPtr<StyleSheet> InstanceStyleSheetFile(const String& file_name);
+	static SharedPtr<StyleSheetContainer> InstanceStyleSheetFile(const String& file_name);
 	/// Creates a style sheet from an Stream.
 	/// Creates a style sheet from an Stream.
 	/// @param[in] stream A pointer to the stream containing the style sheet's contents.
 	/// @param[in] stream A pointer to the stream containing the style sheet's contents.
 	/// @return A pointer to the newly created style sheet.
 	/// @return A pointer to the newly created style sheet.
-	static SharedPtr<StyleSheet> InstanceStyleSheetStream(Stream* stream);
+	static SharedPtr<StyleSheetContainer> InstanceStyleSheetStream(Stream* stream);
 	/// Clears the style sheet cache. This will force style sheets to be reloaded.
 	/// Clears the style sheet cache. This will force style sheets to be reloaded.
 	static void ClearStyleSheetCache();
 	static void ClearStyleSheetCache();
 	/// Clears the template cache. This will force template to be reloaded.
 	/// Clears the template cache. This will force template to be reloaded.

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

@@ -166,6 +166,30 @@ enum class PropertyId : uint8_t
 	MaxNumIds = 128
 	MaxNumIds = 128
 };
 };
 
 
+enum class MediaQueryId : uint8_t
+{
+	Invalid, 
+
+	Width,
+	MinWidth,
+	MaxWidth,
+	Height,
+	MinHeight,
+	MaxHeight,
+	AspectRatio,
+	MinAspectRatio,
+	MaxAspectRatio,
+	Resolution,
+	MinResolution,
+	MaxResolution,
+	Orientation,
+
+	NumDefinedIds,
+	FirstCustomId = NumDefinedIds,
+
+	// The maximum number of IDs. This limits the number of possible custom IDs to MaxNumIds - FirstCustomId.
+	MaxNumIds = 128
+};
 
 
 enum class EventId : uint16_t 
 enum class EventId : uint16_t 
 {
 {

+ 25 - 18
Include/RmlUi/Core/Property.h

@@ -68,34 +68,35 @@ public:
 		RAD = 1 << 6,               // number suffixed by 'rad'; fetch as < float >
 		RAD = 1 << 6,               // number suffixed by 'rad'; fetch as < float >
 		COLOUR = 1 << 7,            // colour; fetch as < Colourb >
 		COLOUR = 1 << 7,            // colour; fetch as < Colourb >
 		DP = 1 << 8,                // density-independent pixel; number suffixed by 'dp'; fetch as < float >
 		DP = 1 << 8,                // density-independent pixel; number suffixed by 'dp'; fetch as < float >
-		VW = 1 << 9,                // viewport-width percentage; number suffixed by 'vw'; fetch as < float >
-		VH = 1 << 10,               // viewport-height percentage; number suffixed by 'vh'; fetch as < float >
-		ABSOLUTE_UNIT = NUMBER | PX | DP | DEG | RAD | COLOUR | VW | VH,
+		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.
 		// Relative values.
-		EM = 1 << 11,               // number suffixed by 'em'; fetch as < float >
-		PERCENT = 1 << 12,          // number suffixed by '%'; fetch as < float >
-		REM = 1 << 13,              // number suffixed by 'rem'; fetch as < float >
+		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,
 		RELATIVE_UNIT = EM | REM | PERCENT,
 
 
 		// Values based on pixels-per-inch.
 		// Values based on pixels-per-inch.
-		INCH = 1 << 14,             // number suffixed by 'in'; fetch as < float >
-		CM = 1 << 15,               // number suffixed by 'cm'; fetch as < float >
-		MM = 1 << 16,               // number suffixed by 'mm'; fetch as < float >
-		PT = 1 << 17,               // number suffixed by 'pt'; fetch as < float >
-		PC = 1 << 18,               // number suffixed by 'pc'; fetch as < float >
+		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,
 		PPI_UNIT = INCH | CM | MM | PT | PC,
 
 
-		TRANSFORM = 1 << 19,        // transform; fetch as < TransformPtr >, may be empty
-		TRANSITION = 1 << 20,       // transition; fetch as < TransitionList >
-		ANIMATION = 1 << 21,        // animation; fetch as < AnimationList >
-		DECORATOR = 1 << 22,        // decorator; fetch as < DecoratorsPtr >
-		FONTEFFECT = 1 << 23,       // font-effect; fetch as < FontEffectsPtr >
+		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 >
 
 
-		LENGTH = PX | DP | PPI_UNIT | EM | REM | VW | VH,
+		LENGTH = PX | DP | PPI_UNIT | EM | REM | VW | VH | X,
 		LENGTH_PERCENT = LENGTH | PERCENT,
 		LENGTH_PERCENT = LENGTH | PERCENT,
 		NUMBER_LENGTH_PERCENT = NUMBER | LENGTH | PERCENT,
 		NUMBER_LENGTH_PERCENT = NUMBER | LENGTH | PERCENT,
-		ABSOLUTE_LENGTH = PX | DP | PPI_UNIT | VH | VW,
+		ABSOLUTE_LENGTH = PX | DP | PPI_UNIT | VH | VW | X,
 		ANGLE = DEG | RAD
 		ANGLE = DEG | RAD
 	};
 	};
 
 
@@ -139,5 +140,11 @@ inline Property::Unit operator|(Property::Unit lhs, Property::Unit rhs)
 	return static_cast<Property::Unit>(static_cast<underlying_t>(lhs) | static_cast<underlying_t>(rhs));
 	return static_cast<Property::Unit>(static_cast<underlying_t>(lhs) | static_cast<underlying_t>(rhs));
 }
 }
 
 
+template <typename Id>
+inline PropertyId CastId(Id id)
+{
+	return static_cast<PropertyId>(id);
+}
+
 } // namespace Rml
 } // namespace Rml
 #endif
 #endif

+ 14 - 24
Include/RmlUi/Core/StyleSheet.h

@@ -32,6 +32,7 @@
 #include "Traits.h"
 #include "Traits.h"
 #include "PropertyDictionary.h"
 #include "PropertyDictionary.h"
 #include "Spritesheet.h"
 #include "Spritesheet.h"
+#include "StyleSheetTypes.h"
 
 
 namespace Rml {
 namespace Rml {
 
 
@@ -42,27 +43,11 @@ class Decorator;
 class FontEffect;
 class FontEffect;
 class SpritesheetList;
 class SpritesheetList;
 class Stream;
 class Stream;
+class StyleSheetContainer;
+class StyleSheetParser;
 struct Sprite;
 struct Sprite;
 struct Spritesheet;
 struct Spritesheet;
 
 
-struct KeyframeBlock {
-	KeyframeBlock(float normalized_time) : normalized_time(normalized_time) {}
-	float normalized_time;  // [0, 1]
-	PropertyDictionary properties;
-};
-struct Keyframes {
-	Vector<PropertyId> property_ids;
-	Vector<KeyframeBlock> blocks;
-};
-using KeyframesMap = UnorderedMap<String, Keyframes>;
-
-struct DecoratorSpecification {
-	String decorator_type;
-	PropertyDictionary properties;
-	SharedPtr<Decorator> decorator;
-};
-using DecoratorSpecificationMap = UnorderedMap<String, DecoratorSpecification>;
-
 /**
 /**
 	StyleSheet maintains a single stylesheet definition. A stylesheet can be combined with another stylesheet to create
 	StyleSheet maintains a single stylesheet definition. A stylesheet can be combined with another stylesheet to create
 	a new, merged stylesheet.
 	a new, merged stylesheet.
@@ -76,14 +61,14 @@ public:
 	typedef Vector< StyleSheetNode* > NodeList;
 	typedef Vector< StyleSheetNode* > NodeList;
 	typedef UnorderedMap< size_t, NodeList > NodeIndex;
 	typedef UnorderedMap< size_t, NodeList > NodeIndex;
 
 
-	StyleSheet();
 	virtual ~StyleSheet();
 	virtual ~StyleSheet();
 
 
-	/// Loads a style from a CSS definition.
-	bool LoadStyleSheet(Stream* stream, int begin_line_number = 1);
-
 	/// Combines this style sheet with another one, producing a new sheet.
 	/// Combines this style sheet with another one, producing a new sheet.
-	SharedPtr<StyleSheet> CombineStyleSheet(const StyleSheet& sheet) const;
+	UniquePtr<StyleSheet> CombineStyleSheet(const StyleSheet& sheet) const;
+
+	/// Creates an exact copy of this style sheet.
+	UniquePtr<StyleSheet> Clone() const;
+
 	/// Builds the node index for a combined style sheet.
 	/// Builds the node index for a combined style sheet.
 	void BuildNodeIndex();
 	void BuildNodeIndex();
 	/// Optimizes some properties for faster retrieval.
 	/// Optimizes some properties for faster retrieval.
@@ -91,7 +76,7 @@ public:
 	void OptimizeNodeProperties();
 	void OptimizeNodeProperties();
 
 
 	/// Returns the Keyframes of the given name, or null if it does not exist.
 	/// Returns the Keyframes of the given name, or null if it does not exist.
-	Keyframes* GetKeyframes(const String& name);
+	const Keyframes* GetKeyframes(const String& name) const;
 
 
 	/// Returns the Decorator of the given name, or null if it does not exist.
 	/// Returns the Decorator of the given name, or null if it does not exist.
 	SharedPtr<Decorator> GetDecorator(const String& name) const;
 	SharedPtr<Decorator> GetDecorator(const String& name) const;
@@ -113,6 +98,8 @@ public:
 	static size_t NodeHash(const String& tag, const String& id);
 	static size_t NodeHash(const String& tag, const String& id);
 
 
 private:
 private:
+	StyleSheet();
+	
 	// Root level node, attributes from special nodes like "body" get added to this node
 	// Root level node, attributes from special nodes like "body" get added to this node
 	UniquePtr<StyleSheetNode> root;
 	UniquePtr<StyleSheetNode> root;
 
 
@@ -138,6 +125,9 @@ private:
 	using ElementDefinitionCache = UnorderedMap< size_t, SharedPtr<ElementDefinition> >;
 	using ElementDefinitionCache = UnorderedMap< size_t, SharedPtr<ElementDefinition> >;
 	// Index of node sets to element definitions.
 	// Index of node sets to element definitions.
 	mutable ElementDefinitionCache node_cache;
 	mutable ElementDefinitionCache node_cache;
+
+	friend StyleSheetParser;
+	friend StyleSheetContainer;
 };
 };
 
 
 } // namespace Rml
 } // namespace Rml

+ 76 - 0
Include/RmlUi/Core/StyleSheetContainer.h

@@ -0,0 +1,76 @@
+/*
+ * 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_STYLESHEETCONTAINER_H
+#define RMLUI_CORE_STYLESHEETCONTAINER_H
+
+#include "Traits.h"
+#include "PropertyDictionary.h"
+#include "StyleSheetTypes.h"
+
+namespace Rml {
+
+class Stream;
+class StyleSheet;
+
+/**
+	StyleSheetContainer contains a list of media blocks and creates a combined style sheet when getting
+	properties of the current context regarding the available media features.
+
+	@author Maximilian Stark
+ */
+
+class RMLUICORE_API StyleSheetContainer : public NonCopyMoveable
+{
+public:
+	StyleSheetContainer();
+	virtual ~StyleSheetContainer();
+
+	/// Loads a style from a CSS definition.
+	bool LoadStyleSheetContainer(Stream* stream, int begin_line_number = 1);
+
+	/// Returns the currently compiled style sheet that has been merged from incorporating all matching media blocks
+	/// or creates it ad-hoc if the given properties differ from the currently stored values.
+	/// @param[in] dp_ratio The current context ratio of 'dp' units to 'px' units
+	/// @param[in] vp_dimensions The current context viewport dimensions
+	StyleSheet* GetCompiledStyleSheet(float dp_ratio, Vector2f vp_dimensions);
+
+	/// Combines this style sheet container with another one, producing a new sheet container.
+	SharedPtr<StyleSheetContainer> CombineStyleSheetContainer(const StyleSheetContainer& container) const;
+
+private:
+	Vector<MediaBlock> media_blocks;
+
+	UniquePtr<StyleSheet> compiled_style_sheet;
+
+	Vector2f current_dimensions;
+	float current_density_ratio;
+};
+
+} // namespace Rml
+#endif

+ 68 - 0
Include/RmlUi/Core/StyleSheetTypes.h

@@ -0,0 +1,68 @@
+/*
+ * 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_STYLESHEETTYPES_H
+#define RMLUI_CORE_STYLESHEETTYPES_H
+
+#include "Traits.h"
+#include "PropertyDictionary.h"
+#include "Spritesheet.h"
+
+namespace Rml {
+
+class StyleSheet;
+
+struct KeyframeBlock {
+	KeyframeBlock(float normalized_time) : normalized_time(normalized_time) {}
+	float normalized_time;  // [0, 1]
+	PropertyDictionary properties;
+};
+struct Keyframes {
+	Vector<PropertyId> property_ids;
+	Vector<KeyframeBlock> blocks;
+};
+using KeyframesMap = UnorderedMap<String, Keyframes>;
+
+struct DecoratorSpecification {
+	String decorator_type;
+	PropertyDictionary properties;
+	SharedPtr<Decorator> decorator;
+};
+using DecoratorSpecificationMap = UnorderedMap<String, DecoratorSpecification>;
+
+struct MediaBlock
+{
+	PropertyDictionary properties;
+	UniquePtr<StyleSheet> stylesheet;
+
+	MediaBlock() {}
+	MediaBlock(PropertyDictionary _properties, UniquePtr<StyleSheet> _stylesheet) : properties(_properties), stylesheet(std::move(_stylesheet)) {}
+};
+
+} // namespace Rml
+#endif

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

@@ -88,6 +88,7 @@ struct TransitionList;
 struct Rectangle;
 struct Rectangle;
 enum class EventId : uint16_t;
 enum class EventId : uint16_t;
 enum class PropertyId : uint8_t;
 enum class PropertyId : uint8_t;
+enum class MediaQueryId : uint8_t;
 enum class FamilyId : int;
 enum class FamilyId : int;
 
 
 // Types for external interfaces.
 // Types for external interfaces.

+ 9 - 0
Samples/assets/test.rcss

@@ -0,0 +1,9 @@
+div {
+    background: #ccc;
+}
+
+body {
+    display:block;
+    width: 100px;
+    height: 100px;
+}

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

@@ -102,8 +102,8 @@ public:
 				Rml::StreamMemory stream((Rml::byte*)style_sheet_content.data(), style_sheet_content.size());
 				Rml::StreamMemory stream((Rml::byte*)style_sheet_content.data(), style_sheet_content.size());
 				stream.SetSourceURL("sandbox://default_rcss");
 				stream.SetSourceURL("sandbox://default_rcss");
 
 
-				rml_basic_style_sheet = MakeShared<Rml::StyleSheet>();
-				rml_basic_style_sheet->LoadStyleSheet(&stream);
+				rml_basic_style_sheet = MakeShared<Rml::StyleSheetContainer>();
+				rml_basic_style_sheet->LoadStyleSheetContainer(&stream);
 			}
 			}
 
 
 			// Add sandbox style sheet text.
 			// Add sandbox style sheet text.
@@ -224,13 +224,13 @@ public:
 	{
 	{
 		if (iframe && rml_basic_style_sheet)
 		if (iframe && rml_basic_style_sheet)
 		{
 		{
-			auto style = Rml::MakeShared<Rml::StyleSheet>();
+			auto style = Rml::MakeShared<Rml::StyleSheetContainer>();
 			Rml::StreamMemory stream((const Rml::byte*)string.data(), string.size());
 			Rml::StreamMemory stream((const Rml::byte*)string.data(), string.size());
 			stream.SetSourceURL("sandbox://rcss");
 			stream.SetSourceURL("sandbox://rcss");
 
 
-			style->LoadStyleSheet(&stream);
-			style = rml_basic_style_sheet->CombineStyleSheet(*style);
-			iframe->SetStyleSheet(style);
+			style->LoadStyleSheetContainer(&stream);
+			style = rml_basic_style_sheet->CombineStyleSheetContainer(*style);
+			iframe->SetStyleSheetContainer(style);
 		}
 		}
 	}
 	}
 
 
@@ -246,7 +246,7 @@ private:
 	Rml::ElementDocument *document = nullptr;
 	Rml::ElementDocument *document = nullptr;
 	Rml::ElementDocument *iframe = nullptr;
 	Rml::ElementDocument *iframe = nullptr;
 	Rml::Element *gauge = nullptr, *progress_horizontal = nullptr;
 	Rml::Element *gauge = nullptr, *progress_horizontal = nullptr;
-	Rml::SharedPtr<Rml::StyleSheet> rml_basic_style_sheet;
+	Rml::SharedPtr<Rml::StyleSheetContainer> rml_basic_style_sheet;
 
 
 	bool submitting = false;
 	bool submitting = false;
 	double submitting_start_time = 0;
 	double submitting_start_time = 0;

+ 4 - 1
Source/Core/Context.cpp

@@ -125,6 +125,7 @@ void Context::SetDimensions(const Vector2i _dimensions)
 			ElementDocument* document = root->GetChild(i)->GetOwnerDocument();
 			ElementDocument* document = root->GetChild(i)->GetOwnerDocument();
 			if (document != nullptr)
 			if (document != nullptr)
 			{
 			{
+				document->DirtyMediaQueries();
 				document->DirtyVwAndVhProperties();
 				document->DirtyVwAndVhProperties();
 				document->DirtyLayout();
 				document->DirtyLayout();
 				document->DirtyPosition();
 				document->DirtyPosition();
@@ -153,6 +154,7 @@ void Context::SetDensityIndependentPixelRatio(float _density_independent_pixel_r
 			ElementDocument* document = root->GetChild(i)->GetOwnerDocument();
 			ElementDocument* document = root->GetChild(i)->GetOwnerDocument();
 			if (document != nullptr)
 			if (document != nullptr)
 			{
 			{
+				document->DirtyMediaQueries();
 				document->DirtyDpProperties();
 				document->DirtyDpProperties();
 			}
 			}
 		}
 		}
@@ -1199,7 +1201,8 @@ void Context::CreateDragClone(Element* element)
 	cursor_proxy->AppendChild(std::move(element_drag_clone));
 	cursor_proxy->AppendChild(std::move(element_drag_clone));
 
 
 	// Set the style sheet on the cursor proxy.
 	// Set the style sheet on the cursor proxy.
-	static_cast<ElementDocument&>(*cursor_proxy).SetStyleSheet(element->GetStyleSheet());
+	if(ElementDocument* document = element->GetOwnerDocument())
+		static_cast<ElementDocument&>(*cursor_proxy).SetStyleSheetContainer(document->GetStyleSheetContainer());
 
 
 	// Set all the required properties and pseudo-classes on the clone.
 	// Set all the required properties and pseudo-classes on the clone.
 	drag_clone->SetPseudoClass("drag", true);
 	drag_clone->SetPseudoClass("drag", true);

+ 4 - 5
Source/Core/Element.cpp

@@ -291,12 +291,11 @@ String Element::GetClassNames() const
 }
 }
 
 
 // Returns the active style sheet for this element. This may be nullptr.
 // Returns the active style sheet for this element. This may be nullptr.
-const SharedPtr<StyleSheet>& Element::GetStyleSheet() const
+const StyleSheet* Element::GetStyleSheet() const
 {
 {
 	if (ElementDocument * document = GetOwnerDocument())
 	if (ElementDocument * document = GetOwnerDocument())
 		return document->GetStyleSheet();
 		return document->GetStyleSheet();
-	static SharedPtr<StyleSheet> null_style_sheet;
-	return null_style_sheet;
+	return nullptr;
 }
 }
 
 
 // Returns the element's definition.
 // Returns the element's definition.
@@ -2533,10 +2532,10 @@ void Element::HandleAnimationProperty()
 
 
 		const AnimationList& animation_list = meta->computed_values.animation;
 		const AnimationList& animation_list = meta->computed_values.animation;
 		bool element_has_animations = (!animation_list.empty() || !animations.empty());
 		bool element_has_animations = (!animation_list.empty() || !animations.empty());
-		StyleSheet* stylesheet = nullptr;
+		const StyleSheet* stylesheet = nullptr;
 
 
 		if (element_has_animations)
 		if (element_has_animations)
-			stylesheet = GetStyleSheet().get();
+			stylesheet = GetStyleSheet();
 
 
 		if (stylesheet)
 		if (stylesheet)
 		{
 		{

+ 27 - 22
Source/Core/ElementDocument.cpp

@@ -33,6 +33,7 @@
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/Profiling.h"
 #include "../../Include/RmlUi/Core/StreamMemory.h"
 #include "../../Include/RmlUi/Core/StreamMemory.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
+#include "../../Include/RmlUi/Core/StyleSheetContainer.h"
 #include "DocumentHeader.h"
 #include "DocumentHeader.h"
 #include "ElementStyle.h"
 #include "ElementStyle.h"
 #include "EventDispatcher.h"
 #include "EventDispatcher.h"
@@ -47,7 +48,6 @@ namespace Rml {
 
 
 ElementDocument::ElementDocument(const String& tag) : Element(tag)
 ElementDocument::ElementDocument(const String& tag) : Element(tag)
 {
 {
-	style_sheet = nullptr;
 	context = nullptr;
 	context = nullptr;
 
 
 	modal = false;
 	modal = false;
@@ -95,22 +95,22 @@ void ElementDocument::ProcessHeader(const DocumentHeader* document_header)
 
 
 	// If a style-sheet (or sheets) has been specified for this element, then we load them and set the combined sheet
 	// If a style-sheet (or sheets) has been specified for this element, then we load them and set the combined sheet
 	// on the element; all of its children will inherit it by default.
 	// on the element; all of its children will inherit it by default.
-	SharedPtr<StyleSheet> new_style_sheet;
+	SharedPtr<StyleSheetContainer> new_style_sheet;
 
 
 	// Combine any inline sheets.
 	// Combine any inline sheets.
 	for (const DocumentHeader::Resource& rcss : header.rcss)
 	for (const DocumentHeader::Resource& rcss : header.rcss)
 	{
 	{
 		if (rcss.is_inline)
 		if (rcss.is_inline)
 		{
 		{
-			UniquePtr<StyleSheet> inline_sheet = MakeUnique<StyleSheet>();
+			UniquePtr<StyleSheetContainer> inline_sheet = MakeUnique<StyleSheetContainer>();
 			auto stream = MakeUnique<StreamMemory>((const byte*)rcss.content.c_str(), rcss.content.size());
 			auto stream = MakeUnique<StreamMemory>((const byte*)rcss.content.c_str(), rcss.content.size());
 			stream->SetSourceURL(rcss.path);
 			stream->SetSourceURL(rcss.path);
 
 
-			if (inline_sheet->LoadStyleSheet(stream.get(), rcss.line))
+			if (inline_sheet->LoadStyleSheetContainer(stream.get(), rcss.line))
 			{
 			{
 				if (new_style_sheet)
 				if (new_style_sheet)
 				{
 				{
-					SharedPtr<StyleSheet> combined_sheet = new_style_sheet->CombineStyleSheet(*inline_sheet);
+					SharedPtr<StyleSheetContainer> combined_sheet = new_style_sheet->CombineStyleSheetContainer(*inline_sheet);
 					new_style_sheet = combined_sheet;
 					new_style_sheet = combined_sheet;
 				}
 				}
 				else
 				else
@@ -121,12 +121,12 @@ void ElementDocument::ProcessHeader(const DocumentHeader* document_header)
 		}
 		}
 		else
 		else
 		{
 		{
-			SharedPtr<StyleSheet> sub_sheet = StyleSheetFactory::GetStyleSheet(rcss.path);
+			SharedPtr<StyleSheetContainer> sub_sheet = StyleSheetFactory::GetStyleSheetContainer(rcss.path);
 			if (sub_sheet)
 			if (sub_sheet)
 			{
 			{
 				if (new_style_sheet)
 				if (new_style_sheet)
 				{
 				{
-					SharedPtr<StyleSheet> combined_sheet = new_style_sheet->CombineStyleSheet(*sub_sheet);
+					SharedPtr<StyleSheetContainer> combined_sheet = new_style_sheet->CombineStyleSheetContainer(*sub_sheet);
 					new_style_sheet = std::move(combined_sheet);
 					new_style_sheet = std::move(combined_sheet);
 				}
 				}
 				else
 				else
@@ -139,9 +139,7 @@ void ElementDocument::ProcessHeader(const DocumentHeader* document_header)
 
 
 	// If a style sheet is available, set it on the document and release it.
 	// If a style sheet is available, set it on the document and release it.
 	if (new_style_sheet)
 	if (new_style_sheet)
-	{
-		SetStyleSheet(std::move(new_style_sheet));
-	}
+		SetStyleSheetContainer(std::move(new_style_sheet));
 
 
 	// Load scripts.
 	// Load scripts.
 	for (const DocumentHeader::Resource& script : header.scripts)
 	for (const DocumentHeader::Resource& script : header.scripts)
@@ -189,28 +187,30 @@ const String& ElementDocument::GetSourceURL() const
 }
 }
 
 
 // Sets the style sheet this document, and all of its children, uses.
 // Sets the style sheet this document, and all of its children, uses.
-void ElementDocument::SetStyleSheet(SharedPtr<StyleSheet> _style_sheet)
+void ElementDocument::SetStyleSheetContainer(SharedPtr<StyleSheetContainer> _style_sheet_container)
 {
 {
 	RMLUI_ZoneScoped;
 	RMLUI_ZoneScoped;
 
 
-	if (style_sheet == _style_sheet)
+	if (style_sheet_container == _style_sheet_container)
 		return;
 		return;
 
 
-	style_sheet = std::move(_style_sheet);
+	style_sheet_container = std::move(_style_sheet_container);
 	
 	
-	if (style_sheet)
-	{
-		style_sheet->BuildNodeIndex();
-		style_sheet->OptimizeNodeProperties();
-	}
-
 	GetStyle()->DirtyDefinition();
 	GetStyle()->DirtyDefinition();
 }
 }
 
 
 // Returns the document's style sheet.
 // Returns the document's style sheet.
-const SharedPtr<StyleSheet>& ElementDocument::GetStyleSheet() const
+const StyleSheet* ElementDocument::GetStyleSheet() const
 {
 {
-	return style_sheet;
+	if(context && style_sheet_container)
+		return style_sheet_container->GetCompiledStyleSheet(context->GetDensityIndependentPixelRatio(), Vector2f(context->GetDimensions()));
+	return nullptr;
+}
+
+// Returns the document's style sheet container.
+const SharedPtr<StyleSheetContainer>& ElementDocument::GetStyleSheetContainer() const
+{
+	return style_sheet_container;
 }
 }
 
 
 // Reload the document's style sheet from source files.
 // Reload the document's style sheet from source files.
@@ -234,7 +234,12 @@ void ElementDocument::ReloadStyleSheet()
 		return;
 		return;
 	}
 	}
 
 
-	SetStyleSheet(temp_doc->GetStyleSheet());
+	SetStyleSheetContainer(static_cast<ElementDocument*>(temp_doc.get())->GetStyleSheetContainer());
+}
+
+void ElementDocument::DirtyMediaQueries()
+{
+	GetStyle()->DirtyDefinition();
 }
 }
 
 
 // Brings the document to the front of the document stack.
 // Brings the document to the front of the document stack.

+ 3 - 3
Source/Core/ElementStyle.cpp

@@ -186,7 +186,7 @@ void ElementStyle::UpdateDefinition()
 
 
 		SharedPtr<ElementDefinition> new_definition;
 		SharedPtr<ElementDefinition> new_definition;
 		
 		
-		if (auto& style_sheet = element->GetStyleSheet())
+		if (const StyleSheet* style_sheet = element->GetStyleSheet())
 		{
 		{
 			new_definition = style_sheet->GetElementDefinition(element);
 			new_definition = style_sheet->GetElementDefinition(element);
 		}
 		}
@@ -911,7 +911,7 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 			{
 			{
 				// Usually the decorator is converted from string after the style sheet is set on the ElementDocument. However, if the
 				// Usually the decorator is converted from string after the style sheet is set on the ElementDocument. However, if the
 				// user sets a decorator on the element's style, we may still get a string here which must be parsed and instanced.
 				// user sets a decorator on the element's style, we may still get a string here which must be parsed and instanced.
-				if (auto & style_sheet = element->GetStyleSheet())
+				if (const StyleSheet* style_sheet = element->GetStyleSheet())
 				{
 				{
 					// The property source will not be set if the property is defined in inline style. However, we may need it in order to locate
 					// The property source will not be set if the property is defined in inline style. However, we may need it in order to locate
 					// resource files (typically images). In this case, generate one from the document's source URL.
 					// resource files (typically images). In this case, generate one from the document's source URL.
@@ -939,7 +939,7 @@ PropertyIdSet ElementStyle::ComputeValues(Style::ComputedValues& values, const S
 			}
 			}
 			else if (p->unit == Property::STRING)
 			else if (p->unit == Property::STRING)
 			{
 			{
-				if (auto & style_sheet = element->GetStyleSheet())
+				if (const StyleSheet* style_sheet = element->GetStyleSheet())
 				{
 				{
 					const String& value = p->value.GetReference<String>();
 					const String& value = p->value.GetReference<String>();
 					values.font_effect = style_sheet->InstanceFontEffectsFromString(value, p->source);
 					values.font_effect = style_sheet->InstanceFontEffectsFromString(value, p->source);

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

@@ -213,7 +213,7 @@ bool ElementImage::LoadTexture()
 
 
 		if (ElementDocument* document = GetOwnerDocument())
 		if (ElementDocument* document = GetOwnerDocument())
 		{
 		{
-			if (auto& style_sheet = document->GetStyleSheet())
+			if (const StyleSheet* style_sheet = document->GetStyleSheet())
 			{
 			{
 				if (const Sprite* sprite = style_sheet->GetSprite(sprite_name))
 				if (const Sprite* sprite = style_sheet->GetSprite(sprite_name))
 				{
 				{

+ 1 - 1
Source/Core/Elements/ElementProgressBar.cpp

@@ -347,7 +347,7 @@ bool ElementProgressBar::LoadTexture()
 	if(!name.empty() && document)
 	if(!name.empty() && document)
 	{
 	{
 		// Check for a sprite first, this takes precedence.
 		// Check for a sprite first, this takes precedence.
-		if (auto& style_sheet = document->GetStyleSheet())
+		if (const StyleSheet* style_sheet = document->GetStyleSheet())
 		{
 		{
 			if (const Sprite* sprite = style_sheet->GetSprite(name))
 			if (const Sprite* sprite = style_sheet->GetSprite(name))
 			{
 			{

+ 7 - 6
Source/Core/Factory.cpp

@@ -37,6 +37,7 @@
 #include "../../Include/RmlUi/Core/EventListenerInstancer.h"
 #include "../../Include/RmlUi/Core/EventListenerInstancer.h"
 #include "../../Include/RmlUi/Core/StreamMemory.h"
 #include "../../Include/RmlUi/Core/StreamMemory.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
+#include "../../Include/RmlUi/Core/StyleSheetContainer.h"
 #include "../../Include/RmlUi/Core/SystemInterface.h"
 #include "../../Include/RmlUi/Core/SystemInterface.h"
 
 
 #include "../../Include/RmlUi/Core/Elements/ElementForm.h"
 #include "../../Include/RmlUi/Core/Elements/ElementForm.h"
@@ -537,14 +538,14 @@ FontEffectInstancer* Factory::GetFontEffectInstancer(const String& name)
 
 
 
 
 // Creates a style sheet containing the passed in styles.
 // Creates a style sheet containing the passed in styles.
-SharedPtr<StyleSheet> Factory::InstanceStyleSheetString(const String& string)
+SharedPtr<StyleSheetContainer> Factory::InstanceStyleSheetString(const String& string)
 {
 {
 	auto memory_stream = MakeUnique<StreamMemory>((const byte*) string.c_str(), string.size());
 	auto memory_stream = MakeUnique<StreamMemory>((const byte*) string.c_str(), string.size());
 	return InstanceStyleSheetStream(memory_stream.get());
 	return InstanceStyleSheetStream(memory_stream.get());
 }
 }
 
 
 // Creates a style sheet from a file.
 // Creates a style sheet from a file.
-SharedPtr<StyleSheet> Factory::InstanceStyleSheetFile(const String& file_name)
+SharedPtr<StyleSheetContainer> Factory::InstanceStyleSheetFile(const String& file_name)
 {
 {
 	auto file_stream = MakeUnique<StreamFile>();
 	auto file_stream = MakeUnique<StreamFile>();
 	file_stream->Open(file_name);
 	file_stream->Open(file_name);
@@ -552,12 +553,12 @@ SharedPtr<StyleSheet> Factory::InstanceStyleSheetFile(const String& file_name)
 }
 }
 
 
 // Creates a style sheet from an Stream.
 // Creates a style sheet from an Stream.
-SharedPtr<StyleSheet> Factory::InstanceStyleSheetStream(Stream* stream)
+SharedPtr<StyleSheetContainer> Factory::InstanceStyleSheetStream(Stream* stream)
 {
 {
-	SharedPtr<StyleSheet> style_sheet = MakeShared<StyleSheet>();
-	if (style_sheet->LoadStyleSheet(stream))
+	SharedPtr<StyleSheetContainer> style_sheet_container = MakeShared<StyleSheetContainer>();
+	if (style_sheet_container->LoadStyleSheetContainer(stream))
 	{
 	{
-		return style_sheet;
+		return style_sheet_container;
 	}
 	}
 	return nullptr;
 	return nullptr;
 }
 }

+ 1 - 0
Source/Core/PropertyParserNumber.cpp

@@ -37,6 +37,7 @@ static const UnorderedMap<String, Property::Unit> g_property_unit_string_map =
 	{"%", Property::PERCENT},
 	{"%", Property::PERCENT},
 	{"px", Property::PX},
 	{"px", Property::PX},
 	{"dp", Property::DP},
 	{"dp", Property::DP},
+	{"x", Property::X},
 	{"vw", Property::VW},
 	{"vw", Property::VW},
 	{"vh", Property::VH},
 	{"vh", Property::VH},
 	{"em", Property::EM},
 	{"em", Property::EM},

+ 74 - 0
Source/Core/PropertyParserRatio.cpp

@@ -0,0 +1,74 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "PropertyParserRatio.h"
+
+namespace Rml {
+
+PropertyParserRatio::PropertyParserRatio()
+{
+}
+
+PropertyParserRatio::~PropertyParserRatio()
+{
+}
+
+// Called to parse a RCSS string declaration.
+bool PropertyParserRatio::ParseValue(Property& property, const String& value, const ParameterMap& RMLUI_UNUSED_PARAMETER(parameters)) const
+{
+	RMLUI_UNUSED(parameters);
+
+	StringList parts;
+	StringUtilities::ExpandString(parts, value, '/');
+
+	if(parts.size() != 2)
+	{
+		return false;
+	}
+
+	float first_value = 0;
+	if (!TypeConverter<String, float>::Convert(parts[0], first_value))
+	{
+		// Number conversion failed
+		return false;
+	}
+	
+	float second_value = 0;
+	if (!TypeConverter<String, float>::Convert(parts[1], second_value))
+	{
+		// Number conversion failed
+		return false;
+	}
+
+	property.value = Variant(first_value / second_value);
+	property.unit = Property::NUMBER;
+
+	return true;
+}
+
+} // namespace Rml

+ 57 - 0
Source/Core/PropertyParserRatio.h

@@ -0,0 +1,57 @@
+/*
+ * 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_PROPERTYPARSERRATIO_H
+#define RMLUI_CORE_PROPERTYPARSERRATIO_H
+
+#include "../../Include/RmlUi/Core/PropertyParser.h"
+
+namespace Rml {
+
+/**
+	A property parser that parses an ratio in the format of x/y, like 16/9.
+
+	@author Maximilian Stark
+ */
+
+class PropertyParserRatio : public PropertyParser
+{
+public:
+	PropertyParserRatio();
+	virtual ~PropertyParserRatio();
+
+	/// Called to parse a RCSS string declaration.
+	/// @param[out] property The property to set the parsed value on.
+	/// @param[in] value The raw value defined for this property.
+	/// @param[in] parameters The parameters defined for this property; not used for this parser.
+	/// @return True if the value was validated successfully, false otherwise.
+	bool ParseValue(Property& property, const String& value, const ParameterMap& parameters) const override;
+};
+
+} // namespace Rml
+#endif

+ 8 - 11
Source/Core/StyleSheet.cpp

@@ -30,7 +30,6 @@
 #include "ElementDefinition.h"
 #include "ElementDefinition.h"
 #include "StyleSheetFactory.h"
 #include "StyleSheetFactory.h"
 #include "StyleSheetNode.h"
 #include "StyleSheetNode.h"
-#include "StyleSheetParser.h"
 #include "Utilities.h"
 #include "Utilities.h"
 #include "../../Include/RmlUi/Core/DecoratorInstancer.h"
 #include "../../Include/RmlUi/Core/DecoratorInstancer.h"
 #include "../../Include/RmlUi/Core/Element.h"
 #include "../../Include/RmlUi/Core/Element.h"
@@ -60,19 +59,12 @@ StyleSheet::~StyleSheet()
 {
 {
 }
 }
 
 
-bool StyleSheet::LoadStyleSheet(Stream* stream, int begin_line_number)
-{
-	StyleSheetParser parser;
-	specificity_offset = parser.Parse(root.get(), stream, *this, keyframes, decorator_map, spritesheet_list, begin_line_number);
-	return specificity_offset >= 0;
-}
-
 /// Combines this style sheet with another one, producing a new sheet
 /// Combines this style sheet with another one, producing a new sheet
-SharedPtr<StyleSheet> StyleSheet::CombineStyleSheet(const StyleSheet& other_sheet) const
+UniquePtr<StyleSheet> StyleSheet::CombineStyleSheet(const StyleSheet& other_sheet) const
 {
 {
 	RMLUI_ZoneScoped;
 	RMLUI_ZoneScoped;
 
 
-	SharedPtr<StyleSheet> new_sheet = MakeShared<StyleSheet>();
+	UniquePtr<StyleSheet> new_sheet = UniquePtr<StyleSheet>(new StyleSheet());
 	
 	
 	new_sheet->root = root->DeepCopy();
 	new_sheet->root = root->DeepCopy();
 	new_sheet->root->MergeHierarchy(other_sheet.root.get(), specificity_offset);
 	new_sheet->root->MergeHierarchy(other_sheet.root.get(), specificity_offset);
@@ -104,6 +96,11 @@ SharedPtr<StyleSheet> StyleSheet::CombineStyleSheet(const StyleSheet& other_shee
 	return new_sheet;
 	return new_sheet;
 }
 }
 
 
+UniquePtr<StyleSheet> StyleSheet::Clone() const
+{
+	return CombineStyleSheet(StyleSheet{});
+}
+
 // Builds the node index for a combined style sheet.
 // Builds the node index for a combined style sheet.
 void StyleSheet::BuildNodeIndex()
 void StyleSheet::BuildNodeIndex()
 {
 {
@@ -121,7 +118,7 @@ void StyleSheet::OptimizeNodeProperties()
 }
 }
 
 
 // Returns the Keyframes of the given name, or null if it does not exist.
 // Returns the Keyframes of the given name, or null if it does not exist.
-Keyframes * StyleSheet::GetKeyframes(const String & name)
+const Keyframes * StyleSheet::GetKeyframes(const String & name) const
 {
 {
 	auto it = keyframes.find(name);
 	auto it = keyframes.find(name);
 	if (it != keyframes.end())
 	if (it != keyframes.end())

+ 185 - 0
Source/Core/StyleSheetContainer.cpp

@@ -0,0 +1,185 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ * 
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "../../Include/RmlUi/Core/PropertyDictionary.h"
+#include "../../Include/RmlUi/Core/StyleSheetContainer.h"
+#include "../../Include/RmlUi/Core/StyleSheet.h"
+#include "ComputeProperty.h"
+#include "StyleSheetParser.h"
+#include "Utilities.h"
+
+namespace Rml {
+
+StyleSheetContainer::StyleSheetContainer()
+{
+}
+
+StyleSheetContainer::~StyleSheetContainer()
+{
+}
+
+bool StyleSheetContainer::LoadStyleSheetContainer(Stream* stream, int begin_line_number)
+{
+	StyleSheetParser parser;
+	int rule_count = parser.Parse(media_blocks, stream, begin_line_number);
+	return rule_count >= 0;
+}
+
+StyleSheet* StyleSheetContainer::GetCompiledStyleSheet(float dp_ratio, Vector2f vp_dimensions)
+{
+    if(compiled_style_sheet && vp_dimensions == current_dimensions && dp_ratio == current_density_ratio)
+        return compiled_style_sheet.get();
+
+    UniquePtr<StyleSheet> new_sheet = UniquePtr<StyleSheet>(new StyleSheet());
+
+    float font_size = DefaultComputedValues.font_size;
+
+    for(auto const& media_block : media_blocks)
+    {
+        bool all_match = true;
+        for(auto const& property : media_block.properties.GetProperties())
+        {
+            switch(static_cast<MediaQueryId>(property.first)) 
+            {
+            case MediaQueryId::Width:
+                if(vp_dimensions.x != ComputeLength(&property.second, 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))
+                    all_match = false;
+                break;
+            case MediaQueryId::MaxWidth:
+                if(vp_dimensions.x > ComputeLength(&property.second, 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))
+                    all_match = false;
+                break;
+            case MediaQueryId::MinHeight:
+                if(vp_dimensions.y < ComputeLength(&property.second, 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))
+                    all_match = false;
+                break;
+            case MediaQueryId::AspectRatio:
+                if((vp_dimensions.x / vp_dimensions.y) != property.second.Get<float>())
+                    all_match = false;
+                break;
+            case MediaQueryId::MinAspectRatio:
+                if((vp_dimensions.x / vp_dimensions.y) < property.second.Get<float>())
+                    all_match = false;
+                break;
+            case MediaQueryId::MaxAspectRatio:
+                if((vp_dimensions.x / vp_dimensions.y) > property.second.Get<float>())
+                    all_match = false;
+                break;
+            case MediaQueryId::Resolution:
+                if(dp_ratio != property.second.Get<float>())
+                    all_match = false;
+                break;
+            case MediaQueryId::MinResolution:
+                if(dp_ratio < property.second.Get<float>())
+                    all_match = false;
+                break;
+            case MediaQueryId::MaxResolution:
+                if(dp_ratio > property.second.Get<float>())
+                    all_match = false;
+                break;
+            case MediaQueryId::Orientation:
+                // Landscape (x > y) = 0 
+                // Portrait (x <= y) = 1
+                if((vp_dimensions.x <= vp_dimensions.y) != property.second.Get<bool>())
+                    all_match = false;
+                break;  
+            // Invalid properties
+            case MediaQueryId::Invalid:
+            case MediaQueryId::NumDefinedIds:
+            case MediaQueryId::MaxNumIds:
+                break;
+            }
+            
+            if(!all_match)
+                break;
+        }
+
+        if(all_match)
+        {
+            new_sheet = new_sheet->CombineStyleSheet(*media_block.stylesheet);
+        }
+    }
+    
+    new_sheet->BuildNodeIndex();
+    new_sheet->OptimizeNodeProperties();
+
+    compiled_style_sheet = std::move(new_sheet);
+    current_dimensions = vp_dimensions;
+    current_density_ratio = dp_ratio;
+    return compiled_style_sheet.get();
+}
+
+/// Combines this style sheet container with another one, producing a new sheet container.
+SharedPtr<StyleSheetContainer> StyleSheetContainer::CombineStyleSheetContainer(const StyleSheetContainer& container) const
+{
+    SharedPtr<StyleSheetContainer> new_sheet = MakeShared<StyleSheetContainer>();
+
+    for(auto const& pair : media_blocks)
+    {      
+        PropertyDictionary dict;
+        dict.Import(pair.properties);
+        new_sheet->media_blocks.emplace_back(dict, pair.stylesheet->Clone());
+    }
+
+    for(auto const& pair : container.media_blocks)
+    {      
+        bool block_found = false;
+        for(auto& media_block : new_sheet->media_blocks)
+        {
+            if(pair.properties.GetProperties() == media_block.properties.GetProperties())
+            {
+                media_block.stylesheet = media_block.stylesheet->CombineStyleSheet(*pair.stylesheet);
+                block_found = true;
+                break;
+            }
+        }
+
+        if (!block_found)
+        {
+            PropertyDictionary dict;
+            dict.Import(pair.properties);
+            new_sheet->media_blocks.emplace_back(dict, pair.stylesheet->Clone());
+        }
+    }
+
+    return new_sheet;
+}
+
+} // namespace Rml

+ 11 - 15
Source/Core/StyleSheetFactory.cpp

@@ -27,7 +27,7 @@
  */
  */
 
 
 #include "StyleSheetFactory.h"
 #include "StyleSheetFactory.h"
-#include "../../Include/RmlUi/Core/StyleSheet.h"
+#include "../../Include/RmlUi/Core/StyleSheetContainer.h"
 #include "StyleSheetNode.h"
 #include "StyleSheetNode.h"
 #include "StreamFile.h"
 #include "StreamFile.h"
 #include "StyleSheetNodeSelectorNthChild.h"
 #include "StyleSheetNodeSelectorNthChild.h"
@@ -90,7 +90,7 @@ void StyleSheetFactory::Shutdown()
 	}
 	}
 }
 }
 
 
-SharedPtr<StyleSheet> StyleSheetFactory::GetStyleSheet(const String& sheet_name)
+SharedPtr<StyleSheetContainer> StyleSheetFactory::GetStyleSheetContainer(const String& sheet_name)
 {
 {
 	// Look up the sheet definition in the cache
 	// Look up the sheet definition in the cache
 	StyleSheets::iterator itr = instance->stylesheets.find(sheet_name);
 	StyleSheets::iterator itr = instance->stylesheets.find(sheet_name);
@@ -100,19 +100,17 @@ SharedPtr<StyleSheet> StyleSheetFactory::GetStyleSheet(const String& sheet_name)
 	}
 	}
 
 
 	// Don't currently have the sheet, attempt to load it
 	// Don't currently have the sheet, attempt to load it
-	SharedPtr<StyleSheet> sheet = instance->LoadStyleSheet(sheet_name);
+	SharedPtr<StyleSheetContainer> sheet = instance->LoadStyleSheetContainer(sheet_name);
 	if (!sheet)
 	if (!sheet)
 		return nullptr;
 		return nullptr;
 
 
-	sheet->OptimizeNodeProperties();
-
 	// Add it to the cache, and add a reference count so the cache will keep hold of it.
 	// Add it to the cache, and add a reference count so the cache will keep hold of it.
 	instance->stylesheets[sheet_name] = sheet;
 	instance->stylesheets[sheet_name] = sheet;
 
 
 	return sheet;
 	return sheet;
 }
 }
 
 
-SharedPtr<StyleSheet> StyleSheetFactory::GetStyleSheet(const StringList& sheets)
+SharedPtr<StyleSheetContainer> StyleSheetFactory::GetStyleSheetContainer(const StringList& sheets)
 {
 {
 	// Generate a unique key for these sheets
 	// Generate a unique key for these sheets
 	String combined_key;
 	String combined_key;
@@ -130,15 +128,15 @@ SharedPtr<StyleSheet> StyleSheetFactory::GetStyleSheet(const StringList& sheets)
 	}
 	}
 
 
 	// Load and combine the sheets.
 	// Load and combine the sheets.
-	SharedPtr<StyleSheet> sheet;
+	SharedPtr<StyleSheetContainer> sheet;
 	for (size_t i = 0; i < sheets.size(); i++)
 	for (size_t i = 0; i < sheets.size(); i++)
 	{
 	{
-		SharedPtr<StyleSheet> sub_sheet = GetStyleSheet(sheets[i]);
+		SharedPtr<StyleSheetContainer> sub_sheet = GetStyleSheetContainer(sheets[i]);
 		if (sub_sheet)
 		if (sub_sheet)
 		{
 		{
 			if (sheet)
 			if (sheet)
 			{
 			{
-				SharedPtr<StyleSheet> new_sheet = sheet->CombineStyleSheet(*sub_sheet);
+				SharedPtr<StyleSheetContainer> new_sheet = sheet->CombineStyleSheetContainer(*sub_sheet);
 				sheet = std::move(new_sheet);
 				sheet = std::move(new_sheet);
 			}
 			}
 			else
 			else
@@ -151,8 +149,6 @@ SharedPtr<StyleSheet> StyleSheetFactory::GetStyleSheet(const StringList& sheets)
 	if (!sheet)
 	if (!sheet)
 		return nullptr;
 		return nullptr;
 
 
-	sheet->OptimizeNodeProperties();
-
 	// Add to cache, and a reference to the sheet to hold it in the cache.
 	// Add to cache, and a reference to the sheet to hold it in the cache.
 	instance->stylesheet_cache[combined_key] = sheet;
 	instance->stylesheet_cache[combined_key] = sheet;
 	return sheet;
 	return sheet;
@@ -244,17 +240,17 @@ StructuralSelector StyleSheetFactory::GetSelector(const String& name)
 	return StructuralSelector(it->second, a, b);
 	return StructuralSelector(it->second, a, b);
 }
 }
 
 
-SharedPtr<StyleSheet> StyleSheetFactory::LoadStyleSheet(const String& sheet)
+SharedPtr<StyleSheetContainer> StyleSheetFactory::LoadStyleSheetContainer(const String& sheet)
 {
 {
-	SharedPtr<StyleSheet> new_style_sheet;
+	SharedPtr<StyleSheetContainer> new_style_sheet;
 
 
 	// Open stream, construct new sheet and pass the stream into the sheet
 	// Open stream, construct new sheet and pass the stream into the sheet
 	// TODO: Make this support ASYNC
 	// TODO: Make this support ASYNC
 	auto stream = MakeUnique<StreamFile>();
 	auto stream = MakeUnique<StreamFile>();
 	if (stream->Open(sheet))
 	if (stream->Open(sheet))
 	{
 	{
-		new_style_sheet = MakeShared<StyleSheet>();
-		if (!new_style_sheet->LoadStyleSheet(stream.get()))
+		new_style_sheet = MakeShared<StyleSheetContainer>();
+		if (!new_style_sheet->LoadStyleSheetContainer(stream.get()))
 		{
 		{
 			new_style_sheet = nullptr;
 			new_style_sheet = nullptr;
 		}
 		}

+ 5 - 5
Source/Core/StyleSheetFactory.h

@@ -33,7 +33,7 @@
 
 
 namespace Rml {
 namespace Rml {
 
 
-class StyleSheet;
+class StyleSheetContainer;
 class StyleSheetNodeSelector;
 class StyleSheetNodeSelector;
 struct StructuralSelector;
 struct StructuralSelector;
 
 
@@ -53,12 +53,12 @@ public:
 
 
 	/// Gets the named sheet, retrieving it from the cache if its already been loaded
 	/// Gets the named sheet, retrieving it from the cache if its already been loaded
 	/// @param sheet name of sheet to load
 	/// @param sheet name of sheet to load
-	static SharedPtr<StyleSheet> GetStyleSheet(const String& sheet);
+	static SharedPtr<StyleSheetContainer> GetStyleSheetContainer(const String& sheet);
 
 
 	/// Builds and returns a stylesheet based on the list of input sheets
 	/// Builds and returns a stylesheet based on the list of input sheets
 	/// Generated sheets will be cached for later use
 	/// Generated sheets will be cached for later use
 	/// @param sheets List of sheets to combine into one	
 	/// @param sheets List of sheets to combine into one	
-	static SharedPtr<StyleSheet> GetStyleSheet(const StringList& sheets);
+	static SharedPtr<StyleSheetContainer> GetStyleSheetContainer(const StringList& sheets);
 
 
 	/// Clear the style sheet cache.
 	/// Clear the style sheet cache.
 	static void ClearStyleSheetCache();
 	static void ClearStyleSheetCache();
@@ -73,10 +73,10 @@ private:
 	~StyleSheetFactory();
 	~StyleSheetFactory();
 
 
 	// Loads an individual style sheet
 	// Loads an individual style sheet
-	SharedPtr<StyleSheet> LoadStyleSheet(const String& sheet);
+	SharedPtr<StyleSheetContainer> LoadStyleSheetContainer(const String& sheet);
 
 
 	// Individual loaded stylesheets
 	// Individual loaded stylesheets
-	typedef UnorderedMap<String, SharedPtr<StyleSheet>> StyleSheets;
+	typedef UnorderedMap<String, SharedPtr<StyleSheetContainer>> StyleSheets;
 	StyleSheets stylesheets;
 	StyleSheets stylesheets;
 
 
 	// Cache of combined style sheets
 	// Cache of combined style sheets

+ 216 - 8
Source/Core/StyleSheetParser.cpp

@@ -38,6 +38,7 @@
 #include "../../Include/RmlUi/Core/PropertySpecification.h"
 #include "../../Include/RmlUi/Core/PropertySpecification.h"
 #include "../../Include/RmlUi/Core/StreamMemory.h"
 #include "../../Include/RmlUi/Core/StreamMemory.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
 #include "../../Include/RmlUi/Core/StyleSheet.h"
+#include "../../Include/RmlUi/Core/StyleSheetContainer.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include "../../Include/RmlUi/Core/StyleSheetSpecification.h"
 #include <algorithm>
 #include <algorithm>
 #include <string.h>
 #include <string.h>
@@ -140,6 +141,58 @@ public:
 
 
 static UniquePtr<SpritesheetPropertyParser> spritesheet_property_parser;
 static UniquePtr<SpritesheetPropertyParser> spritesheet_property_parser;
 
 
+/*
+ * Media queries need a special parser because they have unique properties that 
+ * aren't admissible in other property declaration contexts and the syntax of
+*/
+class MediaQueryPropertyParser final : public AbstractPropertyParser {
+private:
+	// The dictionary to store the properties in.
+	PropertyDictionary* properties;
+
+	PropertySpecification specification;
+
+public:
+	MediaQueryPropertyParser() : specification(14, 0) 
+	{	
+		specification.RegisterProperty("width", "", false, false, CastId(MediaQueryId::Width)).AddParser("length");
+		specification.RegisterProperty("min-width", "", false, false, CastId(MediaQueryId::MinWidth)).AddParser("length");
+		specification.RegisterProperty("max-width", "", false, false, CastId(MediaQueryId::MaxWidth)).AddParser("length");
+
+		specification.RegisterProperty("height", "", false, false, CastId(MediaQueryId::Height)).AddParser("length");
+		specification.RegisterProperty("min-height", "", false, false, CastId(MediaQueryId::MinHeight)).AddParser("length");
+		specification.RegisterProperty("max-height", "", false, false, CastId(MediaQueryId::MaxHeight)).AddParser("length");
+
+		specification.RegisterProperty("aspect-ratio", "", false, false, CastId(MediaQueryId::AspectRatio)).AddParser("ratio");
+		specification.RegisterProperty("min-aspect-ratio", "", false, false, CastId(MediaQueryId::MinAspectRatio)).AddParser("ratio");
+		specification.RegisterProperty("max-aspect-ratio", "", false, false, CastId(MediaQueryId::MaxAspectRatio)).AddParser("ratio");
+
+		specification.RegisterProperty("resolution", "", false, false, CastId(MediaQueryId::Resolution)).AddParser("resolution");
+		specification.RegisterProperty("min-resolution", "", false, false, CastId(MediaQueryId::MinResolution)).AddParser("resolution");
+		specification.RegisterProperty("max-resolution", "", false, false, CastId(MediaQueryId::MaxResolution)).AddParser("resolution");
+
+		specification.RegisterProperty("orientation", "", false, false, CastId(MediaQueryId::Orientation)).AddParser("keyword", "landscape, portrait");
+	}
+
+	void SetTargetProperties(PropertyDictionary* _properties)
+	{
+		properties = _properties;
+	}
+
+	void Clear() {
+		properties = nullptr;
+	}
+
+	bool Parse(const String& name, const String& value) override
+	{
+		RMLUI_ASSERT(properties);
+		return specification.ParsePropertyDeclaration(*properties, name, value);
+	}
+};
+
+
+static UniquePtr<MediaQueryPropertyParser> media_query_property_parser;
+
 
 
 StyleSheetParser::StyleSheetParser()
 StyleSheetParser::StyleSheetParser()
 {
 {
@@ -155,11 +208,13 @@ StyleSheetParser::~StyleSheetParser()
 void StyleSheetParser::Initialise()
 void StyleSheetParser::Initialise()
 {
 {
 	spritesheet_property_parser = MakeUnique<SpritesheetPropertyParser>();
 	spritesheet_property_parser = MakeUnique<SpritesheetPropertyParser>();
+	media_query_property_parser = MakeUnique<MediaQueryPropertyParser>();
 }
 }
 
 
 void StyleSheetParser::Shutdown()
 void StyleSheetParser::Shutdown()
 {
 {
 	spritesheet_property_parser.reset();
 	spritesheet_property_parser.reset();
+	media_query_property_parser.reset();
 }
 }
 
 
 static bool IsValidIdentifier(const String& str)
 static bool IsValidIdentifier(const String& str)
@@ -337,7 +392,100 @@ bool StyleSheetParser::ParseDecoratorBlock(const String& at_name, DecoratorSpeci
 	return true;
 	return true;
 }
 }
 
 
-int StyleSheetParser::Parse(StyleSheetNode* node, Stream* _stream, const StyleSheet& style_sheet, KeyframesMap& keyframes, DecoratorSpecificationMap& decorator_map, SpritesheetList& spritesheet_list, int begin_line_number)
+bool StyleSheetParser::ParseMediaFeatureMap(PropertyDictionary& properties, const String & rules)
+{
+	media_query_property_parser->SetTargetProperties(&properties);
+
+	enum ParseState { Global, Name, Value };
+	ParseState state = Name;
+
+	char character = 0;
+
+	size_t cursor = 0;
+
+	String name;
+
+	String current_string;
+
+	while(cursor++ < rules.length())
+	{
+		character = rules[cursor];
+
+		switch(character)
+		{
+		case '(':
+		{
+			if (state != Global)
+			{
+				Log::Message(Log::LT_WARNING, "Unexpected '(' in @media query list at %s:%d.", stream_file_name.c_str(), line_number);
+				return false;
+			}
+
+			current_string = StringUtilities::StripWhitespace(StringUtilities::ToLower(current_string));
+
+			if (current_string != "and")
+			{
+				Log::Message(Log::LT_WARNING, "Unexpected '%s' in @media query list at %s:%d. Expected 'and'.", current_string.c_str(), stream_file_name.c_str(), line_number);
+				return false;
+			}
+
+			current_string.clear();
+			state = Name;
+		}
+		break;
+		case ')':
+		{
+			if (state != Value)
+			{
+				Log::Message(Log::LT_WARNING, "Unexpected ')' in @media query list at %s:%d.", stream_file_name.c_str(), line_number);
+				return false;
+			}
+
+			current_string = StringUtilities::StripWhitespace(current_string);
+
+			if(!media_query_property_parser->Parse(name, current_string))
+				Log::Message(Log::LT_WARNING, "Syntax error parsing media-query property declaration '%s: %s;' in %s: %d.", name.c_str(), current_string.c_str(), stream_file_name.c_str(), line_number);
+
+			current_string.clear();
+			state = Global;
+		}
+		break;
+		case ':':
+		{
+			if (state != Name)
+			{
+				Log::Message(Log::LT_WARNING, "Unexpected ':' in @media query list at %s:%d.", stream_file_name.c_str(), line_number);
+				return false;
+			}
+
+			current_string = StringUtilities::StripWhitespace(StringUtilities::ToLower(current_string));
+
+			if (!IsValidIdentifier(current_string))
+			{
+				Log::Message(Log::LT_WARNING, "Malformed property name '%s' in @media query list at %s:%d.", current_string.c_str(), stream_file_name.c_str(), line_number);
+				return false;
+			}
+
+			name = current_string;
+			current_string.clear();
+
+			state = Value;
+		}
+		break;
+		default:
+			current_string += character;
+		}
+	}
+
+	if (properties.GetNumProperties() == 0)
+	{
+		Log::Message(Log::LT_WARNING, "Media query list parsing yielded no properties at %s:%d.", stream_file_name.c_str(), line_number);
+	}
+
+	return true;
+}
+
+int StyleSheetParser::Parse(MediaBlockList& style_sheets, Stream* _stream, int begin_line_number)
 {
 {
 	RMLUI_ZoneScoped;
 	RMLUI_ZoneScoped;
 
 
@@ -349,6 +497,12 @@ int StyleSheetParser::Parse(StyleSheetNode* node, Stream* _stream, const StyleSh
 	enum class State { Global, AtRuleIdentifier, KeyframeBlock, Invalid };
 	enum class State { Global, AtRuleIdentifier, KeyframeBlock, Invalid };
 	State state = State::Global;
 	State state = State::Global;
 
 
+
+	MediaBlock current_block = {};
+
+	// Need to track whether currently inside a nested media block or not, since the default scope is also a media block
+	bool inside_media_block = false;
+
 	// At-rules given by the following syntax in global space: @identifier name { block }
 	// At-rules given by the following syntax in global space: @identifier name { block }
 	String at_rule_name;
 	String at_rule_name;
 
 
@@ -365,6 +519,12 @@ int StyleSheetParser::Parse(StyleSheetNode* node, Stream* _stream, const StyleSh
 			{
 			{
 				if (token == '{')
 				if (token == '{')
 				{
 				{
+					// Initialize current block if not present
+					if (!current_block.stylesheet)
+					{
+						current_block = MediaBlock{PropertyDictionary{}, UniquePtr<StyleSheet>(new StyleSheet())};
+					}
+
 					const int rule_line_number = (int)line_number;
 					const int rule_line_number = (int)line_number;
 					
 					
 					// Read the attributes
 					// Read the attributes
@@ -381,7 +541,7 @@ int StyleSheetParser::Parse(StyleSheetNode* node, Stream* _stream, const StyleSh
 					{
 					{
 						auto source = MakeShared<PropertySource>(stream_file_name, rule_line_number, rule_name_list[i]);
 						auto source = MakeShared<PropertySource>(stream_file_name, rule_line_number, rule_name_list[i]);
 						properties.SetSourceOfAllProperties(source);
 						properties.SetSourceOfAllProperties(source);
-						ImportProperties(node, rule_name_list[i], properties, rule_count);
+						ImportProperties(current_block.stylesheet->root.get(), rule_name_list[i], properties, rule_count);
 					}
 					}
 
 
 					rule_count++;
 					rule_count++;
@@ -390,6 +550,17 @@ int StyleSheetParser::Parse(StyleSheetNode* node, Stream* _stream, const StyleSh
 				{
 				{
 					state = State::AtRuleIdentifier;
 					state = State::AtRuleIdentifier;
 				}
 				}
+				else if (inside_media_block && token == '}')
+				{
+					// Complete current block
+					PostprocessKeyframes(current_block.stylesheet->keyframes);
+					current_block.stylesheet->specificity_offset = rule_count;
+					style_sheets.push_back(std::move(current_block));
+					current_block = {};
+
+					inside_media_block = false;
+					break;
+				}
 				else
 				else
 				{
 				{
 					Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing stylesheet at %s:%d. Trying to proceed.", token, stream_file_name.c_str(), line_number);
 					Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing stylesheet at %s:%d. Trying to proceed.", token, stream_file_name.c_str(), line_number);
@@ -399,7 +570,13 @@ int StyleSheetParser::Parse(StyleSheetNode* node, Stream* _stream, const StyleSh
 			case State::AtRuleIdentifier:
 			case State::AtRuleIdentifier:
 			{
 			{
 				if (token == '{')
 				if (token == '{')
-				{
+				{					
+					// Initialize current block if not present
+					if (!current_block.stylesheet)
+					{
+						current_block = {PropertyDictionary{}, UniquePtr<StyleSheet>(new StyleSheet())};
+					}
+
 					String at_rule_identifier = pre_token_str.substr(0, pre_token_str.find(' '));
 					String at_rule_identifier = pre_token_str.substr(0, pre_token_str.find(' '));
 					at_rule_name = StringUtilities::StripWhitespace(pre_token_str.substr(at_rule_identifier.size()));
 					at_rule_name = StringUtilities::StripWhitespace(pre_token_str.substr(at_rule_identifier.size()));
 
 
@@ -410,7 +587,7 @@ int StyleSheetParser::Parse(StyleSheetNode* node, Stream* _stream, const StyleSh
 					else if (at_rule_identifier == "decorator")
 					else if (at_rule_identifier == "decorator")
 					{
 					{
 						auto source = MakeShared<PropertySource>(stream_file_name, (int)line_number, pre_token_str);
 						auto source = MakeShared<PropertySource>(stream_file_name, (int)line_number, pre_token_str);
-						ParseDecoratorBlock(at_rule_name, decorator_map, style_sheet, source);
+						ParseDecoratorBlock(at_rule_name, current_block.stylesheet->decorator_map, *current_block.stylesheet, source);
 						
 						
 						at_rule_name.clear();
 						at_rule_name.clear();
 						state = State::Global;
 						state = State::Global;
@@ -437,13 +614,32 @@ int StyleSheetParser::Parse(StyleSheetNode* node, Stream* _stream, const StyleSh
 						}
 						}
 						else
 						else
 						{
 						{
-							spritesheet_list.AddSpriteSheet(at_rule_name, image_source, stream_file_name, (int)line_number, sprite_definitions);
+							current_block.stylesheet->spritesheet_list.AddSpriteSheet(at_rule_name, image_source, stream_file_name, (int)line_number, sprite_definitions);
 						}
 						}
 
 
 						spritesheet_property_parser->Clear();
 						spritesheet_property_parser->Clear();
 						at_rule_name.clear();
 						at_rule_name.clear();
 						state = State::Global;
 						state = State::Global;
 					}
 					}
+					else if (at_rule_identifier == "media")
+					{
+						// complete the current "global" block if present and start a new block
+						if (current_block.stylesheet)
+						{
+							PostprocessKeyframes(current_block.stylesheet->keyframes);
+							current_block.stylesheet->specificity_offset = rule_count;
+							style_sheets.push_back(std::move(current_block));
+							current_block = {};
+						}
+
+						// parse media query list into block
+						PropertyDictionary feature_map;
+						ParseMediaFeatureMap(feature_map, at_rule_name);
+						current_block = {std::move(feature_map), UniquePtr<StyleSheet>(new StyleSheet())};
+
+						inside_media_block = true;
+						state = State::Global;
+					}
 					else
 					else
 					{
 					{
 						// Invalid identifier, should ignore
 						// Invalid identifier, should ignore
@@ -463,14 +659,20 @@ int StyleSheetParser::Parse(StyleSheetNode* node, Stream* _stream, const StyleSh
 			case State::KeyframeBlock:
 			case State::KeyframeBlock:
 			{
 			{
 				if (token == '{')
 				if (token == '{')
-				{
+				{	
+					// Initialize current block if not present
+					if (!current_block.stylesheet)
+					{
+						current_block = {PropertyDictionary{}, UniquePtr<StyleSheet>(new StyleSheet())};
+					}
+
 					// Each keyframe in keyframes has its own block which is processed here
 					// Each keyframe in keyframes has its own block which is processed here
 					PropertyDictionary properties;
 					PropertyDictionary properties;
 					PropertySpecificationParser parser(properties, StyleSheetSpecification::GetPropertySpecification());
 					PropertySpecificationParser parser(properties, StyleSheetSpecification::GetPropertySpecification());
 					if(!ReadProperties(parser))
 					if(!ReadProperties(parser))
 						continue;
 						continue;
 
 
-					if (!ParseKeyframeBlock(keyframes, at_rule_name, pre_token_str, properties))
+					if (!ParseKeyframeBlock(current_block.stylesheet->keyframes, at_rule_name, pre_token_str, properties))
 						continue;
 						continue;
 				}
 				}
 				else if (token == '}')
 				else if (token == '}')
@@ -499,7 +701,13 @@ int StyleSheetParser::Parse(StyleSheetNode* node, Stream* _stream, const StyleSh
 			break;
 			break;
 	}	
 	}	
 
 
-	PostprocessKeyframes(keyframes);
+	// Complete last block if present
+	if (current_block.stylesheet)
+	{
+		PostprocessKeyframes(current_block.stylesheet->keyframes);
+		current_block.stylesheet->specificity_offset = rule_count;
+		style_sheets.push_back(std::move(current_block));
+	}
 
 
 	return rule_count;
 	return rule_count;
 }
 }

+ 8 - 2
Source/Core/StyleSheetParser.h

@@ -36,10 +36,13 @@ namespace Rml {
 
 
 class PropertyDictionary;
 class PropertyDictionary;
 class Stream;
 class Stream;
+class StyleSheetContainer;
+struct MediaBlock;
 class StyleSheetNode;
 class StyleSheetNode;
 class AbstractPropertyParser;
 class AbstractPropertyParser;
 struct PropertySource;
 struct PropertySource;
 using StyleSheetNodeListRaw = Vector<StyleSheetNode*>;
 using StyleSheetNodeListRaw = Vector<StyleSheetNode*>;
+using MediaBlockList = Vector<MediaBlock>;
 
 
 /**
 /**
 	Helper class for parsing a style sheet into its memory representation.
 	Helper class for parsing a style sheet into its memory representation.
@@ -54,10 +57,10 @@ public:
 	~StyleSheetParser();
 	~StyleSheetParser();
 
 
 	/// Parses the given stream into the style sheet
 	/// Parses the given stream into the style sheet
-	/// @param node The root node the stream will be parsed into
+	/// @param style_sheets The collection of style sheets to write into, organized into media blocks
 	/// @param stream The stream to read
 	/// @param stream The stream to read
 	/// @return The number of parsed rules, or -1 if an error occured.
 	/// @return The number of parsed rules, or -1 if an error occured.
-	int Parse(StyleSheetNode* node, Stream* stream, const StyleSheet& style_sheet, KeyframesMap& keyframes, DecoratorSpecificationMap& decorator_map, SpritesheetList& spritesheet_list, int begin_line_number);
+	int Parse(MediaBlockList& style_sheets, Stream* stream, int begin_line_number);
 
 
 	/// Parses the given string into the property dictionary
 	/// Parses the given string into the property dictionary
 	/// @param parsed_properties The properties dictionary the properties will be read into
 	/// @param parsed_properties The properties dictionary the properties will be read into
@@ -107,6 +110,9 @@ private:
 	// Attempts to parse a @decorator block
 	// Attempts to parse a @decorator block
 	bool ParseDecoratorBlock(const String& at_name, DecoratorSpecificationMap& decorator_map, const StyleSheet& style_sheet, const SharedPtr<const PropertySource>& source);
 	bool ParseDecoratorBlock(const String& at_name, DecoratorSpecificationMap& decorator_map, const StyleSheet& style_sheet, const SharedPtr<const PropertySource>& source);
 
 
+	// Attempts to parse the properties of a @media query
+	bool ParseMediaFeatureMap(PropertyDictionary& properties, const String& rules);
+
 	// Attempts to find one of the given character tokens in the active stream
 	// Attempts to find one of the given character tokens in the active stream
 	// If it's found, buffer is filled with all content up until the token
 	// If it's found, buffer is filled with all content up until the token
 	// @param buffer The buffer that receives the content
 	// @param buffer The buffer that receives the content

+ 6 - 1
Source/Core/StyleSheetSpecification.cpp

@@ -31,6 +31,7 @@
 #include "../../Include/RmlUi/Core/PropertyDefinition.h"
 #include "../../Include/RmlUi/Core/PropertyDefinition.h"
 #include "PropertyParserNumber.h"
 #include "PropertyParserNumber.h"
 #include "PropertyParserAnimation.h"
 #include "PropertyParserAnimation.h"
+#include "PropertyParserRatio.h"
 #include "PropertyParserColour.h"
 #include "PropertyParserColour.h"
 #include "PropertyParserKeyword.h"
 #include "PropertyParserKeyword.h"
 #include "PropertyParserString.h"
 #include "PropertyParserString.h"
@@ -55,6 +56,8 @@ struct DefaultStyleSheetParsers {
 	PropertyParserAnimation transition = PropertyParserAnimation(PropertyParserAnimation::TRANSITION_PARSER);
 	PropertyParserAnimation transition = PropertyParserAnimation(PropertyParserAnimation::TRANSITION_PARSER);
 	PropertyParserColour color = PropertyParserColour();
 	PropertyParserColour color = PropertyParserColour();
 	PropertyParserTransform transform = PropertyParserTransform();
 	PropertyParserTransform transform = PropertyParserTransform();
+	PropertyParserRatio ratio = PropertyParserRatio();
+	PropertyParserNumber resolution = PropertyParserNumber(Property::X);
 };
 };
 
 
 StyleSheetSpecification::StyleSheetSpecification() : 
 StyleSheetSpecification::StyleSheetSpecification() : 
@@ -247,6 +250,8 @@ void StyleSheetSpecification::RegisterDefaultParsers()
 	RegisterParser("transition", &default_parsers->transition);
 	RegisterParser("transition", &default_parsers->transition);
 	RegisterParser("color", &default_parsers->color);
 	RegisterParser("color", &default_parsers->color);
 	RegisterParser("transform", &default_parsers->transform);
 	RegisterParser("transform", &default_parsers->transform);
+	RegisterParser("ratio", &default_parsers->ratio);
+	RegisterParser("resolution", &default_parsers->resolution);
 }
 }
 
 
 
 
@@ -411,8 +416,8 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 	// Rare properties (not added to computed values)
 	// Rare properties (not added to computed values)
 	RegisterProperty(PropertyId::FillImage, "fill-image", "", false, false).AddParser("string");
 	RegisterProperty(PropertyId::FillImage, "fill-image", "", false, false).AddParser("string");
 
 
-	RMLUI_ASSERTMSG(instance->properties.property_map->AssertAllInserted(PropertyId::NumDefinedIds), "Missing specification for one or more Property IDs.");
 	RMLUI_ASSERTMSG(instance->properties.shorthand_map->AssertAllInserted(ShorthandId::NumDefinedIds), "Missing specification for one or more Shorthand IDs.");
 	RMLUI_ASSERTMSG(instance->properties.shorthand_map->AssertAllInserted(ShorthandId::NumDefinedIds), "Missing specification for one or more Shorthand IDs.");
+	RMLUI_ASSERTMSG(instance->properties.property_map->AssertAllInserted(PropertyId::NumDefinedIds), "Missing specification for one or more Property IDs.");
 }
 }
 
 
 } // namespace Rml
 } // namespace Rml

+ 2 - 2
Source/Debugger/DebuggerPlugin.cpp

@@ -298,7 +298,7 @@ bool DebuggerPlugin::LoadMenuElement()
 	menu_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
 	menu_element->SetProperty(PropertyId::Visibility, Property(Style::Visibility::Hidden));
 	menu_element->SetInnerRML(menu_rml);
 	menu_element->SetInnerRML(menu_rml);
 
 
-	SharedPtr<StyleSheet> style_sheet = Factory::InstanceStyleSheetString(menu_rcss);
+	SharedPtr<StyleSheetContainer> style_sheet = Factory::InstanceStyleSheetString(menu_rcss);
 	if (!style_sheet)
 	if (!style_sheet)
 	{
 	{
 		host_context->UnloadDocument(menu_element);
 		host_context->UnloadDocument(menu_element);
@@ -306,7 +306,7 @@ bool DebuggerPlugin::LoadMenuElement()
 		return false;
 		return false;
 	}
 	}
 
 
-	menu_element->SetStyleSheet(std::move(style_sheet));
+	menu_element->SetStyleSheetContainer(std::move(style_sheet));
 
 
 	// Set the version info in the menu.
 	// Set the version info in the menu.
 	menu_element->GetElementById("version-number")->SetInnerRML("v" + Rml::GetVersion());
 	menu_element->GetElementById("version-number")->SetInnerRML("v" + Rml::GetVersion());

+ 2 - 2
Source/Debugger/ElementInfo.cpp

@@ -71,11 +71,11 @@ bool ElementInfo::Initialise()
 	AddEventListener(EventId::Mouseover, this);
 	AddEventListener(EventId::Mouseover, this);
 	AddEventListener(EventId::Mouseout, this);
 	AddEventListener(EventId::Mouseout, this);
 
 
-	SharedPtr<StyleSheet> style_sheet = Factory::InstanceStyleSheetString(String(common_rcss) + String(info_rcss));
+	SharedPtr<StyleSheetContainer> style_sheet = Factory::InstanceStyleSheetString(String(common_rcss) + String(info_rcss));
 	if (!style_sheet)
 	if (!style_sheet)
 		return false;
 		return false;
 
 
-	SetStyleSheet(std::move(style_sheet));
+	SetStyleSheetContainer(std::move(style_sheet));
 
 
 	return true;
 	return true;
 }
 }

+ 3 - 3
Source/Debugger/ElementLog.cpp

@@ -94,11 +94,11 @@ bool ElementLog::Initialise()
 		message_content->AddEventListener(EventId::Resize, this);
 		message_content->AddEventListener(EventId::Resize, this);
 	}
 	}
 
 
-	SharedPtr<StyleSheet> style_sheet = Factory::InstanceStyleSheetString(String(common_rcss) + String(log_rcss));
+	SharedPtr<StyleSheetContainer> style_sheet = Factory::InstanceStyleSheetString(String(common_rcss) + String(log_rcss));
 	if (!style_sheet)
 	if (!style_sheet)
 		return false;
 		return false;
 
 
-	SetStyleSheet(std::move(style_sheet));
+	SetStyleSheetContainer(std::move(style_sheet));
 
 
 	AddEventListener(EventId::Click, this);
 	AddEventListener(EventId::Click, this);
 
 
@@ -123,7 +123,7 @@ bool ElementLog::Initialise()
 		return false;
 		return false;
 	}
 	}
 
 
-	beacon->SetStyleSheet(style_sheet);
+	beacon->SetStyleSheetContainer(style_sheet);
 
 
 	return true;
 	return true;
 }
 }

+ 282 - 0
Tests/Source/UnitTests/MediaQueries.cpp

@@ -0,0 +1,282 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "../Common/TestsShell.h"
+#include <RmlUi/Core/Context.h>
+#include <RmlUi/Core/Element.h>
+#include <RmlUi/Core/ElementDocument.h>
+#include <doctest.h>
+
+using namespace Rml;
+
+static const String document_media_query1_rml = R"(
+<rml>
+<head>
+	<title>Test</title>
+	<link type="text/rcss" href="/assets/rml.rcss"/>
+	<style>
+		body {
+			left: 0;
+			top: 0;
+			right: 0;
+			bottom: 0;
+		}
+
+		div {
+			height: 48px;
+			width: 48px;
+		}
+
+		@media (min-width: 640px) {
+			div {
+				height: 32px;
+				width: 32px;
+			}
+		}
+
+		@media (max-width: 639px) {
+			div {
+				height: 64px;
+				width: 64px;
+			}
+		}
+	</style>
+</head>
+
+<body>
+<div/>
+</body>
+</rml>
+)";
+
+static const String document_media_query2_rml = R"(
+<rml>
+<head>
+	<title>Test</title>
+	<link type="text/rcss" href="/assets/rml.rcss"/>
+	<style>
+		body {
+			left: 0;
+			top: 0;
+			right: 0;
+			bottom: 0;
+		}
+
+		div {
+			height: 48px;
+			width: 48px;
+		}
+
+		@media (orientation: landscape) {
+			div {
+				height: 32px;
+				width: 32px;
+			}
+		}
+
+		@media (max-aspect-ratio: 3/4) {
+			div {
+				height: 64px;
+				width: 64px;
+			}
+		}
+
+		@media (min-resolution: 2x) {
+			div {
+				height: 128px;
+			}
+		}
+	</style>
+</head>
+
+<body>
+<div/>
+</body>
+</rml>
+)";
+
+static const String document_media_query3_rml = R"(
+<rml>
+<head>
+	<title>Test</title>
+	<link type="text/rcss" href="/assets/rml.rcss"/>
+	<style>
+		body {
+			left: 0;
+			top: 0;
+			right: 0;
+			bottom: 0;
+		}
+
+		div {
+			height: 48px;
+			width: 48px;
+		}
+
+		@media (orientation: landscape) and (min-width: 640px) {
+			div {
+				height: 32px;
+				width: 32px;
+			}
+		}
+
+		@media (max-aspect-ratio: 4/3) {
+			div {
+				height: 64px;
+				width: 64px;
+			}
+		}
+	</style>
+</head>
+
+<body>
+<div/>
+</body>
+</rml>
+)";
+
+
+TEST_CASE("mediaqueries.basic")
+{
+	Context* context = TestsShell::GetContext();
+	REQUIRE(context);
+
+	// There should be no warnings loading this document. There should be one div of 32px width & height
+	ElementDocument* document = context->LoadDocumentFromMemory(document_media_query1_rml, "assets/");
+	REQUIRE(document);
+	document->Show();
+
+	context->Update();
+	context->Render();
+
+	TestsShell::RenderLoop();
+
+	// Shell default window dimensions are 1500, 800
+
+	ElementList elems;
+	document->GetElementsByTagName(elems, "div");
+	CHECK(elems.size() == 1);
+
+	CHECK(elems[0]->GetBox() == Box(Vector2f(32.0f, 32.0f)));
+
+	document->Close();
+
+	TestsShell::ShutdownShell();
+}
+
+TEST_CASE("mediaqueries.dynamic")
+{
+	Context* context = TestsShell::GetContext();
+	REQUIRE(context);
+
+	// There should be no warnings loading this document. There should be one div of 32px width & height
+	ElementDocument* document = context->LoadDocumentFromMemory(document_media_query1_rml, "assets/");
+	REQUIRE(document);
+	document->Show();
+
+	context->Update();
+	context->Render();
+
+	TestsShell::RenderLoop();
+
+	// Shell default window dimensions are 1500, 800
+
+	ElementList elems;
+	document->GetElementsByTagName(elems, "div");
+	CHECK(elems.size() == 1);
+
+	CHECK(elems[0]->GetBox() == Box(Vector2f(32.0f, 32.0f)));
+
+	context->SetDimensions(Vector2i(480, 320));
+
+	context->Update();
+
+	CHECK(elems[0]->GetBox() == Box(Vector2f(64.0f, 64.0f)));
+
+	document->Close();
+
+	TestsShell::ShutdownShell();
+}
+
+TEST_CASE("mediaqueries.custom_properties")
+{
+	Context* context = TestsShell::GetContext();
+	REQUIRE(context);
+
+	context->SetDensityIndependentPixelRatio(2.0f);
+
+	// There should be no warnings loading this document. There should be one div of 32px width & height
+	ElementDocument* document = context->LoadDocumentFromMemory(document_media_query2_rml, "assets/");
+	REQUIRE(document);
+	document->Show();
+
+	context->Update();
+	context->Render();
+
+	TestsShell::RenderLoop();
+
+	// Shell default window dimensions are 1500, 800
+
+	ElementList elems;
+	document->GetElementsByTagName(elems, "div");
+	CHECK(elems.size() == 1);
+
+	CHECK(elems[0]->GetBox() == Box(Vector2f(32.0f, 128.0f)));
+
+	document->Close();
+
+	TestsShell::ShutdownShell();
+}
+
+TEST_CASE("mediaqueries.composite")
+{
+	Context* context = TestsShell::GetContext();
+	REQUIRE(context);
+
+	// There should be no warnings loading this document. There should be one div of 32px width & height
+	ElementDocument* document = context->LoadDocumentFromMemory(document_media_query3_rml, "assets/");
+	REQUIRE(document);
+	document->Show();
+
+	context->Update();
+	context->Render();
+
+	TestsShell::RenderLoop();
+
+	// Shell default window dimensions are 1500, 800
+
+	ElementList elems;
+	document->GetElementsByTagName(elems, "div");
+	CHECK(elems.size() == 1);
+
+	CHECK(elems[0]->GetBox() == Box(Vector2f(32.0f, 32.0f)));
+
+	document->Close();
+
+	TestsShell::ShutdownShell();
+}

+ 75 - 0
Tests/Source/UnitTests/StyleSheet.cpp

@@ -0,0 +1,75 @@
+/*
+ * This source file is part of RmlUi, the HTML/CSS Interface Middleware
+ *
+ * For the latest information, see http://github.com/mikke89/RmlUi
+ *
+ * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
+ * Copyright (c) 2019 The RmlUi Team, and contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+#include "../Common/TestsShell.h"
+#include <RmlUi/Core/Context.h>
+#include <RmlUi/Core/Element.h>
+#include <RmlUi/Core/ElementDocument.h>
+#include <doctest.h>
+
+using namespace Rml;
+
+static const String simple_doc_rml = R"(
+<rml>
+<head>
+	<title>Test</title>
+	<link type="text/rcss" href="/assets/test.rcss"/>
+	<style>
+		body {
+			width: 48px;
+		}
+	</style>
+</head>
+
+<body>
+<div/>
+</body>
+</rml>
+)";
+
+TEST_CASE("stylesheet.override_basic")
+{
+	Context* context = TestsShell::GetContext();
+	REQUIRE(context);
+
+	// There should be no warnings loading this document.
+	ElementDocument* document = context->LoadDocumentFromMemory(simple_doc_rml, "assets/");
+	REQUIRE(document);
+	document->Show();
+
+	context->Update();
+	context->Render();
+
+	TestsShell::RenderLoop();
+
+	CHECK(document->GetBox() == Box(Vector2f(48.0f, 100.0f)));
+
+	document->Close();
+
+	TestsShell::ShutdownShell();
+}