Browse Source

WIP: At-rule for decorators

Michael Ragazzon 6 years ago
parent
commit
dfdea4affd

+ 5 - 0
Include/Rocket/Core/Factory.h

@@ -49,6 +49,7 @@ class FontEffect;
 class FontEffectInstancer;
 class StyleSheet;
 class PropertyDictionary;
+class PropertySpecification;
 enum class EventId : uint16_t;
 
 /**
@@ -115,6 +116,10 @@ public:
 	/// @param[in] instancer The instancer to call when the decorator name is encountered.
 	/// @return The added instancer if the registration was successful, NULL otherwise.
 	static DecoratorInstancer* RegisterDecoratorInstancer(const String& name, DecoratorInstancer* instancer);
+	/// Retrieves the property specification of a decorator registered with the factory.
+	/// @param[in] name The name of the decorator.
+	/// @return The property specification if the decorator exists, NULL otherwise.
+	static const PropertySpecification* GetDecoratorPropertySpecification(const String& name);
 	/// Attempts to instance a decorator from an instancer registered with the factory.
 	/// @param[in] name The name of the desired decorator type.
 	/// @param[in] properties The properties associated with the decorator.

+ 10 - 5
Include/Rocket/Core/ID.h

@@ -37,6 +37,11 @@ enum class ShorthandId : uint16_t
 {
 	Invalid,
 
+	/*
+	  The following values define the shorthand ids for the main stylesheet specification.
+	  These values must not be used in places that have their own property specification,
+	  such as decorators and font-effects.
+	*/
 	Margin,
 	Padding,
 	BorderWidth,
@@ -61,6 +66,11 @@ enum class PropertyId : uint16_t
 {
 	Invalid,
 
+	/*
+	  The following values define the property ids for the main stylesheet specification.
+	  These values must not be used in places that have their own property specification,
+	  such as decorators and font-effects.
+	*/
 	MarginTop,
 	MarginRight,
 	MarginBottom,
@@ -106,7 +116,6 @@ enum class PropertyId : uint16_t
 	FontStyle,
 	FontWeight,
 	FontSize,
-	Font,
 	TextAlign,
 	TextDecoration,
 	TextTransform,
@@ -123,14 +132,10 @@ enum class PropertyId : uint16_t
 	TransformOriginX,
 	TransformOriginY,
 	TransformOriginZ,
-	None,
-	All,
 
 	Transition,
 	Animation,
-	Keyframes,
 
-	ScrollDefaultStepSize,
 	Opacity,
 	PointerEvents,
 	Focus,

+ 5 - 1
Include/Rocket/Core/PropertySpecification.h

@@ -64,7 +64,8 @@ public:
 	}
 
 	void AssertAllInserted(ID last_property_inserted) const {
-		ROCKET_ASSERT(name_map.size() == (size_t)last_property_inserted && reverse_map.size() == (size_t)last_property_inserted);
+		ptrdiff_t cnt = std::count_if(name_map.begin(), name_map.end(), [](const String& name) { return !name.empty(); });
+		ROCKET_ASSERT(cnt == (ptrdiff_t)last_property_inserted && reverse_map.size() == (size_t)last_property_inserted);
 	}
 
 	ID GetId(const String& name) const
@@ -184,6 +185,9 @@ public:
 	const ShorthandDefinition* GetShorthand(ShorthandId id) const;
 	const ShorthandDefinition* GetShorthand(const String& shorthand_name) const;
 
+	/// Parse declaration by name, whether its a property or shorthand.
+	bool ParsePropertyDeclaration(PropertyDictionary& dictionary, const String& property_name, const String& property_value, const String& source_file = "", int source_line_number = 0) const;
+
 	bool ParsePropertyDeclaration(PropertyDictionary& dictionary, PropertyId property_id, const String& property_value, const String& source_file = "", int source_line_number = 0) const;
 
 	/// Parses a property declaration, setting any parsed and validated properties on the given dictionary.

+ 11 - 0
Include/Rocket/Core/StyleSheet.h

@@ -50,6 +50,14 @@ struct Keyframes {
 };
 typedef UnorderedMap<String, Keyframes> KeyframesMap;
 
+struct DecoratorSpecification {
+	String decorator_type;
+	PropertyDictionary properties;
+};
+
+using DecoratorSpecificationMap = UnorderedMap<String, DecoratorSpecification>;
+
+
 /**
 	StyleSheet maintains a single stylesheet definition. A stylesheet can be combined with another stylesheet to create
 	a new, merged stylesheet.
@@ -99,6 +107,9 @@ private:
 	// Name of every @keyframes mapped to their keys
 	KeyframesMap keyframes;
 
+	// Name of every @decorator mapped to their specification
+	DecoratorSpecificationMap decorator_map;
+
 	// Map of only nodes with actual style information.
 	NodeIndex styled_node_index;
 	// Map of every node, even empty, un-styled, nodes.

+ 2 - 0
Include/Rocket/Core/StyleSheetSpecification.h

@@ -109,6 +109,8 @@ public:
 
 	static std::vector<PropertyId> GetShorthandUnderlyingProperties(ShorthandId id);
 
+	static const PropertySpecification& GetPropertySpecification();
+
 private:
 	StyleSheetSpecification();
 	~StyleSheetSpecification();

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

@@ -50,8 +50,8 @@ public:
 		{
 			{
 				document->GetElementById("title")->SetInnerRML(title);
-				document->SetProperty("left", Property(position.x, Property::PX));
-				document->SetProperty("top", Property(position.y, Property::PX));
+				document->SetProperty(PropertyId::Left, Property(position.x, Property::PX));
+				document->SetProperty(PropertyId::Top, Property(position.y, Property::PX));
 				//document->Animate("opacity", Property(0.0f, Property::NUMBER), 2.0f, Tween{ Tween::Quadratic, Tween::InOut }, -1, true, 1.0f);
 			}
 
@@ -84,7 +84,7 @@ public:
 				auto el = document->GetElementById("exit");
 				PropertyDictionary pd;
 				StyleSheetSpecification::ParsePropertyDeclaration(pd, "transform", "translate(200px, 200px) rotate(1215deg)");
-				el->Animate("transform", *pd.GetProperty("transform"), 3.f, Tween{ Tween::Bounce, Tween::Out }, -1);
+				el->Animate("transform", *pd.GetProperty(PropertyId::Transform), 3.f, Tween{ Tween::Bounce, Tween::Out }, -1);
 			}
 
 			// Transform tests
@@ -176,7 +176,7 @@ void GameLoop()
 		ff += float(nudge)*0.3f;
 		auto el = window->GetDocument()->GetElementById("exit");
 		auto f = el->GetProperty<float>("margin-left");
-		el->SetProperty("margin-left", Rocket::Core::Property(ff, Rocket::Core::Property::PX));
+		el->SetProperty(Rocket::Core::PropertyId::MarginLeft, Rocket::Core::Property(ff, Rocket::Core::Property::PX));
 		float f_left = el->GetAbsoluteLeft();
 		Rocket::Core::Log::Message(Rocket::Core::Log::LT_INFO, "margin-left: '%f'   abs: %f.", ff, f_left);
 		nudge = 0;

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

@@ -35,9 +35,10 @@ Inventory::Inventory(const Rocket::Core::String& title, const Rocket::Core::Vect
 	document = context->LoadDocument("data/inventory.rml");
 	if (document != NULL)
 	{
+		using Rocket::Core::PropertyId;
 		document->GetElementById("title")->SetInnerRML(title);
-		document->SetProperty("left", Rocket::Core::Property(position.x, Rocket::Core::Property::PX));
-		document->SetProperty("top", Rocket::Core::Property(position.y, Rocket::Core::Property::PX));
+		document->SetProperty(PropertyId::Left, Rocket::Core::Property(position.x, Rocket::Core::Property::PX));
+		document->SetProperty(PropertyId::Top, Rocket::Core::Property(position.y, Rocket::Core::Property::PX));
 		document->Show();
 	}
 

+ 8 - 7
Source/Controls/ElementDataGrid.cpp

@@ -45,24 +45,25 @@ ElementDataGrid::ElementDataGrid(const Rocket::Core::String& tag) : Core::Elemen
 
 	// Create the row for the column headers:
 	header = static_cast< ElementDataGridRow* >(Core::Factory::InstanceElement(this, "#rktctl_datagridrow", "datagridheader", attributes));
-	header->SetProperty("display", Core::Property(Core::Style::Display::Block));
+	header->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::Block));
 	header->Initialise(this);
 	AppendChild(header);
 	header->RemoveReference();
 
 	body = Core::Factory::InstanceElement(this, "*", "datagridbody", attributes);
-	body->SetProperty("display", Core::Property(Core::Style::Display::None));
-	body->SetProperty("width", Core::Property(Core::Style::Width::Auto));
+	body->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::None));
+	body->SetProperty(Core::PropertyId::Width, Core::Property(Core::Style::Width::Auto));
 	AppendChild(body);
 	body->RemoveReference();
 
 	body_visible = false;
 
 	root = static_cast< ElementDataGridRow* >(Core::Factory::InstanceElement(this, "#rktctl_datagridrow", "datagridroot", attributes));
-	root->SetProperty("display", Core::Property(Core::Style::Display::None));
+	root->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::None));
 	root->Initialise(this);
 
-	SetProperty("overflow", Core::Property(Core::Style::Overflow::Auto));
+	SetProperty(Core::PropertyId::OverflowX, Core::Property(Core::Style::Overflow::Auto));
+	SetProperty(Core::PropertyId::OverflowY, Core::Property(Core::Style::Overflow::Auto));
 
 	new_data_source = "";
 }
@@ -108,7 +109,7 @@ void ElementDataGrid::AddColumn(const Rocket::Core::String& fields, const Rocket
 	// The header elements are added to the header row at the top of the table.
 	if (header_element)
 	{
-		header_element->SetProperty("display", Core::Property(Core::Style::Display::InlineBlock));
+		header_element->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::InlineBlock));
 
 		// Push all the width properties from the column onto the header element.
 		Rocket::Core::String width = header_element->GetAttribute<Rocket::Core::String>("width", "100%");
@@ -241,7 +242,7 @@ void ElementDataGrid::OnUpdate()
 	
 	if (!body_visible && (!any_new_children || root->GetNumLoadedChildren() >= GetAttribute("min-rows", 0)))
 	{
-		body->SetProperty("display", Core::Property(Core::Style::Display::Block));
+		body->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::Block));
 		body_visible = true;
 	}
 }

+ 1 - 1
Source/Controls/ElementDataGridCell.cpp

@@ -52,7 +52,7 @@ void ElementDataGridCell::Initialise(int _column, Core::Element* _header)
 	{
 		header->AddReference();
 		if(auto p = header->GetLocalProperty("width"))
-			SetProperty("width", *p);
+			SetProperty(Core::PropertyId::Width, *p);
 	}
 }
 

+ 6 - 6
Source/Controls/ElementDataGridRow.cpp

@@ -53,8 +53,8 @@ ElementDataGridRow::ElementDataGridRow(const Rocket::Core::String& tag) : Core::
 	dirty_children = false;
 	row_expanded = true;
 
-	SetProperty("white-space", Core::Property(Core::Style::WhiteSpace::Nowrap));
-	SetProperty("display", Core::Property(Core::Style::Display::InlineBlock));
+	SetProperty(Core::PropertyId::WhiteSpace, Core::Property(Core::Style::WhiteSpace::Nowrap));
+	SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::InlineBlock));
 }
 
 ElementDataGridRow::~ElementDataGridRow()
@@ -85,7 +85,7 @@ void ElementDataGridRow::Initialise(ElementDataGrid* _parent_grid, ElementDataGr
 	{
 		ElementDataGridCell* cell = dynamic_cast< ElementDataGridCell* >(Core::Factory::InstanceElement(this, "#rktctl_datagridcell", "datagridcell", cell_attributes));
 		cell->Initialise(i, header_row->GetChild(i));
-		cell->SetProperty("display", Core::Property(Core::Style::Display::InlineBlock));
+		cell->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::InlineBlock));
 		AppendChild(cell);
 		cell->RemoveReference();
 	}
@@ -386,7 +386,7 @@ void ElementDataGridRow::AddChildren(int first_row_added, int num_rows_added)
 
 			if (!row_expanded)
 			{
-				new_row->SetProperty("display", Core::Property(Core::Style::Display::None));
+				new_row->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::None));
 			}
 		}
 
@@ -674,7 +674,7 @@ void ElementDataGridRow::DirtyRow()
 // Sets this row's child rows to be visible.
 void ElementDataGridRow::Show()
 {
-	SetProperty("display", Core::Property(Core::Style::Display::InlineBlock));
+	SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::InlineBlock));
 
 	if (row_expanded)
 	{
@@ -688,7 +688,7 @@ void ElementDataGridRow::Show()
 // Sets this row's children to be invisible.
 void ElementDataGridRow::Hide()
 {
-	SetProperty("display", Core::Property(Core::Style::Display::None));
+	SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::None));
 
 	for (size_t i = 0; i < children.size(); i++)
 	{

+ 2 - 2
Source/Controls/ElementFormControl.cpp

@@ -33,7 +33,7 @@ namespace Controls {
 
 ElementFormControl::ElementFormControl(const Rocket::Core::String& tag) : Core::Element(tag)
 {
-	SetProperty("tab-index", Core::Property(Core::Style::TabIndex::Auto));
+	SetProperty(Core::PropertyId::TabIndex, Core::Property(Core::Style::TabIndex::Auto));
 }
 
 ElementFormControl::~ElementFormControl()
@@ -87,7 +87,7 @@ void ElementFormControl::OnAttributeChange(const Core::ElementAttributes& change
 		// events (when originating from user inputs, see Context) to reach the element.
 		if (is_disabled)
 		{
-			SetProperty("focus", Core::Property(Core::Style::Focus::None));
+			SetProperty(Core::PropertyId::Focus, Core::Property(Core::Style::Focus::None));
 			Blur();
 		}
 		else

+ 7 - 6
Source/Controls/ElementFormControlTextArea.cpp

@@ -39,8 +39,9 @@ ElementFormControlTextArea::ElementFormControlTextArea(const Rocket::Core::Strin
 {
 	widget = new WidgetTextInputMultiLine(this);
 
-	SetProperty("overflow", Core::Property(Core::Style::Overflow::Auto));
-	SetProperty("white-space", Core::Property(Core::Style::WhiteSpace::Prewrap));
+	SetProperty(Core::PropertyId::OverflowX, Core::Property(Core::Style::Overflow::Auto));
+	SetProperty(Core::PropertyId::OverflowY, Core::Property(Core::Style::Overflow::Auto));
+	SetProperty(Core::PropertyId::WhiteSpace, Core::Property(Core::Style::WhiteSpace::Prewrap));
 }
 
 ElementFormControlTextArea::~ElementFormControlTextArea()
@@ -151,9 +152,9 @@ void ElementFormControlTextArea::OnAttributeChange(const Core::ElementAttributes
 	if (changed_attributes.find("wrap") != changed_attributes.end())
 	{
 		if (GetWordWrap())
-			SetProperty("white-space", Core::Property(Core::Style::WhiteSpace::Prewrap));
+			SetProperty(Core::PropertyId::WhiteSpace, Core::Property(Core::Style::WhiteSpace::Prewrap));
 		else
-			SetProperty("white-space", Core::Property(Core::Style::WhiteSpace::Pre));
+			SetProperty(Core::PropertyId::WhiteSpace, Core::Property(Core::Style::WhiteSpace::Pre));
 	}
 
 	if (changed_attributes.find("rows") != changed_attributes.end() ||
@@ -172,8 +173,8 @@ void ElementFormControlTextArea::OnPropertyChange(const Core::PropertyNameList&
 {
 	ElementFormControl::OnPropertyChange(changed_properties);
 
-	if (changed_properties.find("color") != changed_properties.end() ||
-		changed_properties.find("background-color") != changed_properties.end())
+	if (changed_properties.find(Core::PropertyId::Color) != changed_properties.end() ||
+		changed_properties.find(Core::PropertyId::BackgroundColor) != changed_properties.end())
 		widget->UpdateSelectionColours();
 }
 

+ 5 - 5
Source/Controls/ElementTabSet.cpp

@@ -124,9 +124,9 @@ void ElementTabSet::SetActiveTab(int tab_index)
 		Core::Element* new_window = windows->GetChild(tab_index);
 
 		if (old_window)
-			old_window->SetProperty("display", Core::Property(Core::Style::Display::None));
+			old_window->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::None));
 		if (new_window)
-			new_window->SetProperty("display", Core::Property(Core::Style::Display::InlineBlock));
+			new_window->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::InlineBlock));
 
 		active_tab = tab_index;
 
@@ -178,7 +178,7 @@ void ElementTabSet::OnChildAdd(Core::Element* child)
 	if (child->GetParentNode() == GetChildByTag("tabs"))
 	{
 		// Set up the new button and append it
-		child->SetProperty("display", Core::Property(Core::Style::Display::InlineBlock));
+		child->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::InlineBlock));
 		child->AddEventListener(Core::EventId::Click, this);
 
 		if (child->GetParentNode()->GetChild(active_tab) == child)
@@ -188,11 +188,11 @@ void ElementTabSet::OnChildAdd(Core::Element* child)
 	if (child->GetParentNode() == GetChildByTag("panels"))
 	{
 		// Hide the new tab window
-		child->SetProperty("display", Core::Property(Core::Style::Display::None));
+		child->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::None));
 		
 		// Make the new element visible if its the active tab
 		if (child->GetParentNode()->GetChild(active_tab) == child)
-			child->SetProperty("display", Core::Property(Core::Style::Display::InlineBlock));
+			child->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::InlineBlock));
 	}
 }
 

+ 2 - 2
Source/Controls/ElementTextSelection.cpp

@@ -55,8 +55,8 @@ void ElementTextSelection::OnPropertyChange(const Rocket::Core::PropertyNameList
 		return;
 
 	// Check for a colour change.
-	if (changed_properties.find("color") != changed_properties.end() ||
-		changed_properties.find("background-color") != changed_properties.end())
+	if (changed_properties.find(Core::PropertyId::Color) != changed_properties.end() ||
+		changed_properties.find(Core::PropertyId::BackgroundColor) != changed_properties.end())
 	{
 		widget->UpdateSelectionColours();
 	}

+ 2 - 2
Source/Controls/InputTypeText.cpp

@@ -98,8 +98,8 @@ bool InputTypeText::OnAttributeChange(const Core::ElementAttributes& changed_att
 // Called when properties on the control are changed.
 void InputTypeText::OnPropertyChange(const Core::PropertyNameList& changed_properties)
 {
-	if (changed_properties.find("color") != changed_properties.end() ||
-		changed_properties.find("background-color") != changed_properties.end())
+	if (changed_properties.find(Core::PropertyId::Color) != changed_properties.end() ||
+		changed_properties.find(Core::PropertyId::BackgroundColor) != changed_properties.end())
 		widget->UpdateSelectionColours();
 }
 

+ 9 - 8
Source/Controls/WidgetDropDown.cpp

@@ -52,11 +52,12 @@ WidgetDropDown::WidgetDropDown(ElementFormControl* element)
 	value_element = Core::Factory::InstanceElement(element, "*", "selectvalue", Rocket::Core::XMLAttributes());
 	selection_element = Core::Factory::InstanceElement(parent_element, "*", "selectbox", Rocket::Core::XMLAttributes());
 
-	value_element->SetProperty("overflow", Core::Property(Core::Style::Overflow::Hidden));
+	value_element->SetProperty(Core::PropertyId::OverflowX, Core::Property(Core::Style::Overflow::Hidden));
+	value_element->SetProperty(Core::PropertyId::OverflowY, Core::Property(Core::Style::Overflow::Hidden));
 
-	selection_element->SetProperty("visibility", Core::Property(Core::Style::Visibility::Hidden));
-	selection_element->SetProperty("z-index", Core::Property(1.0f, Core::Property::NUMBER));
-	selection_element->SetProperty("clip", Core::Property(Core::Style::Clip::None));
+	selection_element->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Hidden));
+	selection_element->SetProperty(Core::PropertyId::ZIndex, Core::Property(1.0f, Core::Property::NUMBER));
+	selection_element->SetProperty(Core::PropertyId::Clip, Core::Property(Core::Style::Clip::None));
 
 	parent_element->AddEventListener(Core::EventId::Click, this, true);
 	parent_element->AddEventListener(Core::EventId::Blur, this);
@@ -222,8 +223,8 @@ int WidgetDropDown::AddOption(const Rocket::Core::String& rml, const Rocket::Cor
 	Core::Element* element = Core::Factory::InstanceElement(selection_element, "*", "option", Rocket::Core::XMLAttributes());
 
 	// Force to block display and inject the RML. Register a click handler so we can be notified of selection.
-	element->SetProperty("display", Core::Property(Core::Style::Display::Block));
-	element->SetProperty("clip", Core::Property(Core::Style::Clip::Auto));
+	element->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::Block));
+	element->SetProperty(Core::PropertyId::Clip, Core::Property(Core::Style::Clip::Auto));
 	element->SetInnerRML(rml);
 	element->AddEventListener(Core::EventId::Click, this);
 
@@ -385,13 +386,13 @@ void WidgetDropDown::ShowSelectBox(bool show)
 {
 	if (show)
 	{
-		selection_element->SetProperty("visibility", Core::Property(Core::Style::Visibility::Visible));
+		selection_element->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Visible));
 		value_element->SetPseudoClass("checked", true);
 		button_element->SetPseudoClass("checked", true);
 	}
 	else
 	{
-		selection_element->SetProperty("visibility", Core::Property(Core::Style::Visibility::Hidden));
+		selection_element->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Hidden));
 		value_element->SetPseudoClass("checked", false);
 		button_element->SetPseudoClass("checked", false);
 	}

+ 3 - 3
Source/Controls/WidgetSlider.cpp

@@ -91,7 +91,7 @@ WidgetSlider::~WidgetSlider()
 bool WidgetSlider::Initialise()
 {
 	Core::Property drag_property = Core::Property(Core::Style::Drag::Drag);
-	parent->SetProperty("drag", drag_property);
+	parent->SetProperty(Core::PropertyId::Drag, drag_property);
 
 	// Create all of our child elements as standard elements, and abort if we can't create them.
 	track = Core::Factory::InstanceElement(parent, "*", "slidertrack", Rocket::Core::XMLAttributes());
@@ -100,8 +100,8 @@ bool WidgetSlider::Initialise()
 
 	arrows[0] = Core::Factory::InstanceElement(parent, "*", "sliderarrowdec", Rocket::Core::XMLAttributes());
 	arrows[1] = Core::Factory::InstanceElement(parent, "*", "sliderarrowinc", Rocket::Core::XMLAttributes());
-	arrows[0]->SetProperty("drag", drag_property);
-	arrows[1]->SetProperty("drag", drag_property);
+	arrows[0]->SetProperty(Core::PropertyId::Drag, drag_property);
+	arrows[1]->SetProperty(Core::PropertyId::Drag, drag_property);
 
 	if (track == NULL ||
 		bar == NULL ||

+ 5 - 4
Source/Controls/WidgetTextInput.cpp

@@ -42,9 +42,10 @@ WidgetTextInput::WidgetTextInput(ElementFormControl* _parent) : internal_dimensi
 	keyboard_showed = false;
 	
 	parent = _parent;
-	parent->SetProperty("white-space", Core::Property(Core::Style::WhiteSpace::Pre));
-	parent->SetProperty("overflow", Core::Property(Core::Style::Overflow::Hidden));
-	parent->SetProperty("drag", Core::Property(Core::Style::Drag::Drag));
+	parent->SetProperty(Core::PropertyId::WhiteSpace, Core::Property(Core::Style::WhiteSpace::Pre));
+	parent->SetProperty(Core::PropertyId::OverflowX, Core::Property(Core::Style::Overflow::Hidden));
+	parent->SetProperty(Core::PropertyId::OverflowY, Core::Property(Core::Style::Overflow::Hidden));
+	parent->SetProperty(Core::PropertyId::Drag, Core::Property(Core::Style::Drag::Drag));
 	parent->SetClientArea(Rocket::Core::Box::CONTENT);
 
 	parent->AddEventListener(Core::EventId::Keydown, this, true);
@@ -163,7 +164,7 @@ void WidgetTextInput::UpdateSelectionColours()
 	}
 
 	// Set the computed text colour on the element holding the selected text.
-	selected_text_element->SetProperty("color", Rocket::Core::Property(colour, Rocket::Core::Property::COLOUR));
+	selected_text_element->SetProperty(Core::PropertyId::Color, Rocket::Core::Property(colour, Rocket::Core::Property::COLOUR));
 
 	// If the 'background-color' property has been set on the 'selection' element, use that as the
 	// background colour for the selected text. Otherwise, use the inverse of the selected text

+ 6 - 0
Source/Core/Element.cpp

@@ -640,6 +640,12 @@ const Property* Element::GetProperty(const String& name)
 	return style->GetProperty(StyleSheetSpecification::GetPropertyId(name));
 }
 
+// Returns one of this element's properties.
+const Property* Element::GetProperty(PropertyId id)
+{
+	return style->GetProperty(id);
+}
+
 // Returns one of this element's properties.
 const Property* Element::GetLocalProperty(const String& name)
 {

+ 0 - 42
Source/Core/ElementDecoration.cpp

@@ -55,48 +55,6 @@ bool ElementDecoration::ReloadDecorators()
 	if (definition == NULL)
 		return true;
 
-	// Generate the decorator sets for pseudo-classes with overrides.
-	const PseudoClassDecoratorMap& pseudo_class_decorators = definition->GetPseudoClassDecorators();
-	for (PseudoClassDecoratorMap::const_iterator i = pseudo_class_decorators.begin(); i != pseudo_class_decorators.end(); ++i)
-	{
-		for (DecoratorMap::const_iterator j = (*i).second.begin(); j != (*i).second.end(); ++j)
-		{
-			int index = LoadDecorator((*j).second);
-
-			// Add it into the index. If a decorator with the same name already exists for this element, then we add it
-			// into the list at the right position (sorted by specificity, descending).
-			PseudoClassDecoratorIndexList* pseudo_class_decorator_index = NULL;
-			DecoratorIndex::iterator index_iterator = decorator_index.find((*j).first);
-			if (index_iterator == decorator_index.end())
-				pseudo_class_decorator_index = &(*decorator_index.insert(DecoratorIndex::value_type((*j).first, PseudoClassDecoratorIndexList())).first).second;
-			else
-				pseudo_class_decorator_index = &(*index_iterator).second;
-
-			// Add the decorator index at the right point to maintain the order of the list.
-			PseudoClassDecoratorIndexList::iterator k = pseudo_class_decorator_index->begin();
-			for (; k != pseudo_class_decorator_index->end(); ++k)
-			{
-				if (decorators[(*k).second].decorator->GetSpecificity() < decorators[index].decorator->GetSpecificity())
-					break;
-			}
-
-			pseudo_class_decorator_index->insert(k, PseudoClassDecoratorIndex(PseudoClassList((*i).first.begin(), (*i).first.end()), index));
-		}
-	}
-
-	// Put the decorators for the element's default state at the end of any index lists.
-	const DecoratorMap& default_decorators = definition->GetDecorators();
-	for (DecoratorMap::const_iterator i = default_decorators.begin(); i != default_decorators.end(); ++i)
-	{
-		int index = LoadDecorator((*i).second);
-
-		DecoratorIndex::iterator index_iterator = decorator_index.find((*i).first);
-		if (index_iterator == decorator_index.end())
-			decorator_index.insert(DecoratorIndex::value_type((*i).first, PseudoClassDecoratorIndexList(1, PseudoClassDecoratorIndex(PseudoClassList(), index))));
-		else
-			(*index_iterator).second.push_back(PseudoClassDecoratorIndex(PseudoClassList(), index));
-	}
-
 	decorators_dirty = false;
 	active_decorators_dirty = true;
 

+ 1 - 276
Source/Core/ElementDefinition.cpp

@@ -27,9 +27,7 @@
 
 #include "precompiled.h"
 #include "ElementDefinition.h"
-#include "../../Include/Rocket/Core/Decorator.h"
-#include "../../Include/Rocket/Core/Factory.h"
-#include "../../Include/Rocket/Core/FontDatabase.h"
+#include "StyleSheetNode.h"
 #include "../../Include/Rocket/Core/Log.h"
 #include "../../Include/Rocket/Core/PropertyIterators.h"
 
@@ -43,20 +41,6 @@ ElementDefinition::ElementDefinition()
 
 ElementDefinition::~ElementDefinition()
 {
-	for (DecoratorMap::iterator i = decorators.begin(); i != decorators.end(); ++i)
-		(*i).second->RemoveReference();
-
-	for (PseudoClassDecoratorMap::iterator i = pseudo_class_decorators.begin(); i != pseudo_class_decorators.end(); ++i)
-	{
-		for (DecoratorMap::iterator j = (*i).second.begin(); j != (*i).second.end(); ++j)
-		{
-			if ((*j).second != NULL)
-				(*j).second->RemoveReference();
-		}
-	}
-
-	for (size_t i = 0; i < font_effects.size(); ++i)
-		font_effects[i]->RemoveReference();
 }
 
 // Initialises the element definition from a list of style sheet nodes.
@@ -121,9 +105,6 @@ void ElementDefinition::Initialise(const std::vector< const StyleSheetNode* >& s
 			}
 		}
 	}
-
-	InstanceDecorators(merged_pseudo_class_properties);
-	InstanceFontEffects(merged_pseudo_class_properties);
 }
 
 // Returns a specific property from the element definition's base properties.
@@ -227,47 +208,6 @@ void ElementDefinition::GetDefinedProperties(PropertyNameList& property_names, c
 	}
 }
 
-// Returns the list of the element definition's instanced decorators in the default state.
-const DecoratorMap& ElementDefinition::GetDecorators() const
-{
-	return decorators;
-}
-
-// Returns the map of pseudo-class names to overriding instanced decorators.
-const PseudoClassDecoratorMap& ElementDefinition::GetPseudoClassDecorators() const
-{
-	return pseudo_class_decorators;
-}
-
-// Appends this definition's font effects into a provided map of effects.
-void ElementDefinition::GetFontEffects(FontEffectMap& applicable_font_effects, const PseudoClassList& pseudo_classes) const
-{
-	// Check each set of named effects, looking for applicable ones.
-	for (FontEffectIndex::const_iterator i = font_effect_index.begin(); i != font_effect_index.end(); ++i)
-	{
-		// Search through this list, finding the first effect that is valid (depending on
-		// pseudo-classes).
-		const PseudoClassFontEffectIndex& index = i->second;
-		for (size_t j = 0; j < index.size(); ++j)
-		{
-			if (IsPseudoClassRuleApplicable(index[j].first, pseudo_classes))
-			{
-				// This is the most specific valid font effect this element has under the name. If
-				// the map of effects already has an effect with the same name, the effect with the
-				// highest specificity will prevail.
-				FontEffect* applicable_effect = font_effects[index[j].second];
-
-				FontEffectMap::iterator map_iterator = applicable_font_effects.find(i->first);
-				if (map_iterator == applicable_font_effects.end() ||
-					map_iterator->second->GetSpecificity() < applicable_effect->GetSpecificity())
-					applicable_font_effects[i->first] = applicable_effect;
-
-				break;
-			}
-		}
-	}
-}
-
 // Returns the volatility of a pseudo-class.
 ElementDefinition::PseudoClassVolatility ElementDefinition::GetPseudoClassVolatility(const String& pseudo_class) const
 {
@@ -298,221 +238,6 @@ void ElementDefinition::OnReferenceDeactivate()
 	delete this;
 }
 
-// Finds all propery declarations for a group.
-void ElementDefinition::BuildPropertyGroup(PropertyGroupMap& groups, const String& group_type, const PropertyDictionary& element_properties, const PropertyGroupMap* default_properties)
-{
-	String property_suffix = "-" + group_type;
-
-	for (PropertyMap::const_iterator property_iterator = element_properties.GetProperties().begin(); property_iterator != element_properties.GetProperties().end(); ++property_iterator)
-	{
-		const String& property_name = (*property_iterator).first;
-		if (property_name.size() > property_suffix.size() &&
-			strcasecmp(property_name.c_str() + (property_name.size() - property_suffix.size()), property_suffix.c_str()) == 0)
-		{
-			// We've found a group declaration!
-			String group_name = property_name.substr(0, property_name.size() - (group_type.size() + 1));
-			String group_class = (*property_iterator).second.value.Get< String >();
-			PropertyDictionary* group_properties = NULL;		
-
-			// Check if we have an existing definition by this name; if so, we're only overriding the type.
-			PropertyGroupMap::iterator existing_definition = groups.find(group_name);
-			if (existing_definition != groups.end())
-			{
-				(*existing_definition).second.first = group_class;
-				group_properties = &(*existing_definition).second.second;
-			}
-			else
-			{
-				// Check if we have any default decorator definitions, and if the new decorator has a default. If so,
-				// we make a copy of the default properties for the new decorator.
-				if (default_properties != NULL)
-				{
-					PropertyGroupMap::const_iterator default_definition = default_properties->find(group_name);
-					if (default_definition != default_properties->end())
-						group_properties = &(*groups.insert(PropertyGroupMap::value_type(group_name, PropertyGroup(group_class, (*default_definition).second.second))).first).second.second;
-				}
-
-				// If we still haven't got somewhere to put the properties for the new decorator, make a new
-				// definition.
-				if (group_properties == NULL)
-					group_properties = &(*groups.insert(PropertyGroupMap::value_type(group_name, PropertyGroup(group_class, PropertyDictionary()))).first).second.second;
-			}
-
-			// Now find all of this decorator's properties.
-			BuildPropertyGroupDictionary(*group_properties, group_type, group_name, element_properties);
-		}
-	}
-
-	// Now go through all the default decorator definitions and see if the new property list redefines any properties
-	// used by them.
-	if (default_properties != NULL)
-	{
-		for (PropertyGroupMap::const_iterator default_definition_iterator = default_properties->begin(); default_definition_iterator != default_properties->end(); ++default_definition_iterator)
-		{
-			const String& default_definition_name = (*default_definition_iterator).first;
-
-			// Check the list of new definitions hasn't defined this decorator already; if so, it overrode the
-			// decorator type and so has inherited all the properties anyway.
-			if (groups.find(default_definition_name) == groups.end())
-			{
-				// Nope! Make a copy of the decorator's properties and see if the new dictionary overrides any of the
-				// properties.
-				PropertyDictionary decorator_properties = (*default_definition_iterator).second.second;
-				if (BuildPropertyGroupDictionary(decorator_properties, group_type, default_definition_name, element_properties) > 0)
-					groups[default_definition_name] = PropertyGroup((*default_definition_iterator).second.first, decorator_properties);
-			}
-		}
-	}
-}
-
-// Updates a property dictionary of all properties for a single group.
-int ElementDefinition::BuildPropertyGroupDictionary(PropertyDictionary& group_properties, const String& ROCKET_UNUSED_PARAMETER(group_type), const String& group_name, const PropertyDictionary& element_properties)
-{
-	ROCKET_UNUSED(group_type);
-
-	int num_properties = 0;
-
-	for (PropertyMap::const_iterator property_iterator = element_properties.GetProperties().begin(); property_iterator != element_properties.GetProperties().end(); ++property_iterator)
-	{
-		const String& full_property_name = (*property_iterator).first;
-		if (full_property_name.size() > group_name.size() + 1 &&
-			strncasecmp(full_property_name.c_str(), group_name.c_str(), group_name.size()) == 0 &&
-			full_property_name[group_name.size()] == '-')
-		{
-			String property_name = full_property_name.substr(group_name.size() + 1);
-//			if (property_name == group_type)
-//				continue;
-
-			group_properties.SetProperty(property_name, (*property_iterator).second);
-			num_properties++;
-		}
-	}
-
-	return num_properties;
-}
-
-// Builds decorator definitions from the parsed properties and instances decorators as appropriate.
-void ElementDefinition::InstanceDecorators(const PseudoClassPropertyMap& merged_pseudo_class_properties)
-{
-	// Now we have the complete property list, we can compile decorator properties and instance as appropriate.
-	PropertyGroupMap decorator_definitions;
-	BuildPropertyGroup(decorator_definitions, "decorator", properties);
-	for (PropertyGroupMap::iterator i = decorator_definitions.begin(); i != decorator_definitions.end(); ++i)
-		InstanceDecorator((*i).first, (*i).second.first, (*i).second.second);
-
-	// Now go through all the pseudo-class properties ...
-	for (PseudoClassPropertyMap::const_iterator pseudo_class_iterator = merged_pseudo_class_properties.begin(); pseudo_class_iterator != merged_pseudo_class_properties.end(); ++pseudo_class_iterator)
-	{
-		PropertyGroupMap pseudo_class_decorator_definitions;
-		BuildPropertyGroup(pseudo_class_decorator_definitions, "decorator", (*pseudo_class_iterator).second, &decorator_definitions);
-		for (PropertyGroupMap::iterator i = pseudo_class_decorator_definitions.begin(); i != pseudo_class_decorator_definitions.end(); ++i)
-			InstanceDecorator((*i).first, (*i).second.first, (*i).second.second, (*pseudo_class_iterator).first);
-	}
-}
-
-// Attempts to instance a decorator into a given list.
-bool ElementDefinition::InstanceDecorator(const String& name, const String& type, const PropertyDictionary& properties, const StringList& pseudo_classes)
-{
-	Decorator* decorator = Factory::InstanceDecorator(type, properties);
-	if (decorator == NULL)
-	{
-		Log::Message(Log::LT_WARNING, "Failed to instance decorator '%s' of type '%s'.", name.c_str(), type.c_str());
-		return false;
-	}
-
-	if (pseudo_classes.empty())
-	{
-		if (decorator != NULL)
-			decorators[name] = decorator;
-	}
-	else
-	{
-		PseudoClassDecoratorMap::iterator i = pseudo_class_decorators.find(pseudo_classes);
-		if (i == pseudo_class_decorators.end())
-		{
-			DecoratorMap decorators;
-			decorators[name] = decorator;
-
-			pseudo_class_decorators[pseudo_classes] = decorators;
-		}
-		else
-			(*i).second[name] = decorator;
-	}
-
-	return true;
-}
-
-// Builds font effect definitions from the parsed properties and instances font effects as appropriate.
-void ElementDefinition::InstanceFontEffects(const PseudoClassPropertyMap& merged_pseudo_class_properties)
-{
-	// Now we have the complete property list, we can compile font-effect properties and instance as appropriate.
-	PropertyGroupMap font_effect_definitions;
-	BuildPropertyGroup(font_effect_definitions, "font-effect", properties);
-	for (PropertyGroupMap::iterator i = font_effect_definitions.begin(); i != font_effect_definitions.end(); ++i)
-		InstanceFontEffect((*i).first, (*i).second.first, (*i).second.second);
-
-	// Now go through all the pseudo-class properties ...
-	for (PseudoClassPropertyMap::const_iterator pseudo_class_iterator = merged_pseudo_class_properties.begin(); pseudo_class_iterator != merged_pseudo_class_properties.end(); ++pseudo_class_iterator)
-	{
-		PropertyGroupMap pseudo_class_font_effect_definitions;
-		BuildPropertyGroup(pseudo_class_font_effect_definitions, "font-effect", (*pseudo_class_iterator).second, &font_effect_definitions);
-		for (PropertyGroupMap::iterator i = pseudo_class_font_effect_definitions.begin(); i != pseudo_class_font_effect_definitions.end(); ++i)
-			InstanceFontEffect((*i).first, (*i).second.first, (*i).second.second, (*pseudo_class_iterator).first);
-	}
-}
-
-// Attempts to instance a font effect.
-bool ElementDefinition::InstanceFontEffect(const String& name, const String& type, const PropertyDictionary& properties, const StringList& pseudo_classes)
-{
-	FontEffect* font_effect = FontDatabase::GetFontEffect(type, properties);
-	if (font_effect == NULL)
-	{
-		Log::Message(Log::LT_WARNING, "Failed to instance font effect '%s' of type '%s'.", name.c_str(), type.c_str());
-		return false;
-	}
-
-	// Push the instanced effect into the list of effects.
-	int effect_index = (int) font_effects.size();
-	font_effects.push_back(font_effect);
-
-	// Get a reference to the index list we're adding this effect to.
-	PseudoClassFontEffectIndex* index = NULL;
-	FontEffectIndex::iterator index_iterator = font_effect_index.find(name);
-	if (index_iterator == font_effect_index.end())
-	{
-		// No others; create a new index for this name.
-		index = &(font_effect_index.insert(FontEffectIndex::value_type(name, PseudoClassFontEffectIndex())).first->second);
-	}
-	else
-	{
-		index = &(index_iterator->second);
-	}
-
-	// Add the new effect into the index.
-	PseudoClassFontEffectIndex::iterator insert_iterator;
-	for (insert_iterator = index->begin(); insert_iterator != index->end(); ++insert_iterator)
-	{
-		// Keep iterating until we find an effect whose specificity is below the new effect's. The
-		// new effect will be inserted before it in the list.
-		if (font_effects[insert_iterator->second]->GetSpecificity() < font_effect->GetSpecificity())
-			break;
-	}
-
-	index->insert(insert_iterator, PseudoClassFontEffectIndex::value_type(pseudo_classes, effect_index));
-
-
-	// Mark the effect's pseudo-classes as volatile.
-	for (size_t i = 0; i < pseudo_classes.size(); ++i)
-	{
-		PseudoClassVolatilityMap::const_iterator j = pseudo_class_volatility.find(pseudo_classes[i]);
-		if (j == pseudo_class_volatility.end())
-			pseudo_class_volatility[pseudo_classes[i]] = FONT_VOLATILE;
-	}
-
-
-	return true;
-}
-
 // Returns true if the pseudo-class requirement of a rule is met by a list of an element's pseudo-classes.
 bool ElementDefinition::IsPseudoClassRuleApplicable(const StringList& rule_pseudo_classes, const PseudoClassList& element_pseudo_classes)
 {

+ 1 - 55
Source/Core/ElementDefinition.h

@@ -30,20 +30,13 @@
 
 #include "../../Include/Rocket/Core/Dictionary.h"
 #include "../../Include/Rocket/Core/ReferenceCountable.h"
-#include <map>
-#include "../../Include/Rocket/Core/FontEffect.h"
-#include "StyleSheetNode.h"
 
 namespace Rocket {
 namespace Core {
 
-class Decorator;
-class FontEffect;
+class StyleSheetNode;
 class ElementDefinitionIterator;
 
-typedef SmallUnorderedMap< String, Decorator* > DecoratorMap;
-typedef std::map< StringList, DecoratorMap > PseudoClassDecoratorMap;
-
 /**
 	@author Peter Curry
  */
@@ -82,19 +75,6 @@ public:
 	/// @param[in] pseudo_class The pseudo-class that was just activated or deactivated.
 	void GetDefinedProperties(PropertyNameList& property_names, const PseudoClassList& pseudo_classes, const String& pseudo_class) const;
 
-	/// Returns the list of the element definition's instanced decorators in the default state.
-	/// @return The list of instanced decorators.
-	const DecoratorMap& GetDecorators() const;
-	/// Returns the map of pseudo-class names to overriding instanced decorators.
-	/// @return The map of the overriding decorators for each pseudo-class.
-	const PseudoClassDecoratorMap& GetPseudoClassDecorators() const;
-
-	/// Appends this definition's font effects (appropriately for the given pseudo classes) into a
-	/// provided map of effects.
-	/// @param[out] font_effects The outgoing map of font effects.
-	/// @param[in] pseudo_classes Pseudo-classes active on the querying element.
-	void GetFontEffects(FontEffectMap& font_effects, const PseudoClassList& pseudo_classes) const;
-
 	/// Returns the volatility of a pseudo-class.
 	/// @param[in] pseudo_class The name of the pseudo-class to check for volatility.
 	/// @return The volatility of the pseudo-class.
@@ -118,47 +98,13 @@ protected:
 	void OnReferenceDeactivate();
 
 private:
-	typedef std::pair< String, PropertyDictionary > PropertyGroup;
-	typedef SmallUnorderedMap< String, PropertyGroup > PropertyGroupMap;
-
-	typedef std::vector< std::pair< StringList, int > > PseudoClassFontEffectIndex;
-	typedef SmallUnorderedMap< String, PseudoClassFontEffectIndex > FontEffectIndex;
-
 	typedef SmallUnorderedMap< String, PseudoClassVolatility > PseudoClassVolatilityMap;
 
-	// Finds all propery declarations for a group.
-	void BuildPropertyGroup(PropertyGroupMap& groups, const String& group_type, const PropertyDictionary& element_properties, const PropertyGroupMap* default_properties = NULL);
-	// Updates a property dictionary of all properties for a single group.
-	int BuildPropertyGroupDictionary(PropertyDictionary& group_properties, const String& group_type, const String& group_name, const PropertyDictionary& element_properties);
-
-	// Builds decorator definitions from the parsed properties and instances decorators as
-	// appropriate.
-	void InstanceDecorators(const PseudoClassPropertyMap& merged_pseudo_class_properties);
-	// Attempts to instance a decorator.
-	bool InstanceDecorator(const String& name, const String& type, const PropertyDictionary& properties, const StringList& pseudo_class = StringList());
-
-	// Builds font effect definitions from the parsed properties and instances font effects as
-	// appropriate.
-	void InstanceFontEffects(const PseudoClassPropertyMap& merged_pseudo_class_properties);
-	// Attempts to instance a font effect.
-	bool InstanceFontEffect(const String& name, const String& type, const PropertyDictionary& properties, const StringList& pseudo_class = StringList());
-
 	// The attributes for the default state of the element, with no pseudo-classes.
 	PropertyDictionary properties;
 	// The overridden attributes for the element's pseudo-classes.
 	PseudoClassPropertyDictionary pseudo_class_properties;
 
-	// The instanced decorators for this element definition.
-	DecoratorMap decorators;
-	// The overridden decorators for the element's pseudo-classes.
-	PseudoClassDecoratorMap pseudo_class_decorators;
-
-	// The list of every decorator used by this element in every class.
-	FontEffectList font_effects;
-	// For each unique decorator name, this stores (in order of specificity) the name of the
-	// pseudo-class that has a definition for it, and the index into the list of decorators.
-	FontEffectIndex font_effect_index;
-
 	// The list of volatile pseudo-classes in this definition, and how volatile they are.
 	PseudoClassVolatilityMap pseudo_class_volatility;
 

+ 9 - 0
Source/Core/Factory.cpp

@@ -343,6 +343,15 @@ DecoratorInstancer* Factory::RegisterDecoratorInstancer(const String& name, Deco
 	return instancer;
 }
 
+const PropertySpecification* Factory::GetDecoratorPropertySpecification(const String& name)
+{
+	auto iterator = decorator_instancers.find(name);
+	if (iterator == decorator_instancers.end())
+		return nullptr;
+	
+	return &iterator->second->GetPropertySpecification();
+}
+
 // Attempts to instance a decorator from an instancer registered with the factory.
 Decorator* Factory::InstanceDecorator(const String& name, const PropertyDictionary& properties)
 {

+ 15 - 0
Source/Core/PropertySpecification.cpp

@@ -208,6 +208,21 @@ const ShorthandDefinition* PropertySpecification::GetShorthand(const String& sho
 	return GetShorthand(shorthand_map.GetId(shorthand_name));
 }
 
+bool PropertySpecification::ParsePropertyDeclaration(PropertyDictionary& dictionary, const String& property_name, const String& property_value, const String& source_file, int source_line_number) const
+{
+	// Try as a property first
+	PropertyId property_id = property_map.GetId(property_name);
+	if (property_id != PropertyId::Invalid)
+		return ParsePropertyDeclaration(dictionary, property_id, property_value, source_file, source_line_number);
+
+	// Then, as a shorthand
+	ShorthandId shorthand_id = shorthand_map.GetId(property_name);
+	if (shorthand_id != ShorthandId::Invalid)
+		return ParseShorthandDeclaration(dictionary, shorthand_id, property_value, source_file, source_line_number);
+
+	return false;
+}
+
 bool PropertySpecification::ParsePropertyDeclaration(PropertyDictionary& dictionary, PropertyId property_id, const String& property_value, const String& source_file, int source_line_number) const
 {
 	// Parse as a single property.

+ 9 - 1
Source/Core/StyleSheet.cpp

@@ -73,7 +73,7 @@ StyleSheet::~StyleSheet()
 bool StyleSheet::LoadStyleSheet(Stream* stream)
 {
 	StyleSheetParser parser;
-	specificity_offset = parser.Parse(root, keyframes, stream);
+	specificity_offset = parser.Parse(root, keyframes, decorator_map, stream);
 	return specificity_offset >= 0;
 }
 
@@ -89,11 +89,19 @@ StyleSheet* StyleSheet::CombineStyleSheet(const StyleSheet* other_sheet) const
 	}
 
 	// Any matching @keyframe names are overridden as per CSS rules
+	new_sheet->keyframes = keyframes;
 	for (auto& other_keyframes : other_sheet->keyframes)
 	{
 		new_sheet->keyframes[other_keyframes.first] = other_keyframes.second;
 	}
 
+	// Any matching @decorator names are overridden
+	new_sheet->decorator_map = other_sheet->decorator_map;
+	for (auto& other_decorator: other_sheet->decorator_map)
+	{
+		new_sheet->decorator_map[other_decorator.first] = other_decorator.second;
+	}
+
 	new_sheet->specificity_offset = specificity_offset + other_sheet->specificity_offset;
 	return new_sheet;
 }

+ 138 - 27
Source/Core/StyleSheetParser.cpp

@@ -157,16 +157,73 @@ bool StyleSheetParser::ParseKeyframeBlock(KeyframesMap& keyframes_map, const Str
 	return true;
 }
 
-int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Stream* _stream)
+bool StyleSheetParser::ParseDecoratorBlock(DecoratorSpecificationMap& decorator_map, const String& at_name)
+{
+	StringList name_type;
+	StringUtilities::ExpandString(name_type, at_name, ':');
+
+	if (name_type.size() != 2 || name_type[0].empty() || name_type[1].empty())
+	{
+		Log::Message(Log::LT_WARNING, "Decorator syntax error at %s:%d. Use syntax: '@decorator name : type { ... }'.", stream_file_name.c_str(), line_number);
+		return false;
+	}
+
+	const String& name = name_type[0];
+	String decorator_type = name_type[1];
+
+	auto it_find = decorator_map.find(name);
+	if (it_find != decorator_map.end())
+	{
+		Log::Message(Log::LT_WARNING, "Decorator with name '%s' already declared, ignoring decorator at %s:%d.", name.c_str(), stream_file_name.c_str(), line_number);
+		return false;
+	}
+
+	// Get the property specification associated with the decorator type
+	const PropertySpecification* property_specification = Factory::GetDecoratorPropertySpecification(decorator_type);
+	PropertyDictionary properties;
+
+	if(!property_specification)
+	{
+		// Type is not a declared decorator type, instead, see if it is another decorator name, then we inherit its properties.
+		auto it = decorator_map.find(decorator_type);
+		if (it != decorator_map.end())
+		{
+			// Yes, try to retrieve the property specification from the parent type, and add its property values.
+			property_specification = Factory::GetDecoratorPropertySpecification(it->first);
+			properties = it->second.properties;
+			decorator_type = it->second.decorator_type;
+		}
+
+		// If we still don't have a property specification, we cannot continue.
+		if (!property_specification)
+		{
+			Log::Message(Log::LT_WARNING, "Invalid decorator type '%s' declared at %s:%d.", decorator_type.c_str(), stream_file_name.c_str(), line_number);
+			return false;
+		}
+	}
+
+	if (!ReadProperties(properties, *property_specification))
+		return false;
+
+	decorator_map.emplace(name, DecoratorSpecification{ std::move(decorator_type), std::move(properties) });
+
+	return true;
+}
+
+int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, DecoratorSpecificationMap& decorator_map, Stream* _stream)
 {
 	int rule_count = 0;
 	line_number = 0;
 	stream = _stream;
 	stream_file_name = Replace(stream->GetSourceURL().GetURL(), "|", ":");
 
-	enum class State { Global, KeyframesIdentifier, KeyframesRules, Invalid };
+	enum class State { Global, AtRuleIdentifier, AtRuleBlock, Invalid };
 	State state = State::Global;
-	String keyframes_identifier;
+
+	// At-rules given by the following syntax in global space: @identifier name { block }
+	enum class AtRule { None, Keyframes, Decorator };
+	AtRule at_rule = AtRule::None;
+	String at_rule_name;
 
 	// Look for more styles while data is available
 	while (FillBuffer())
@@ -183,7 +240,7 @@ int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Strea
 				{
 					// Read the attributes
 					PropertyDictionary properties;
-					if (!ReadProperties(properties))
+					if (!ReadProperties(properties, StyleSheetSpecification::GetPropertySpecification()))
 						continue;
 
 					StringList style_name_list;
@@ -197,7 +254,7 @@ int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Strea
 				}
 				else if (token == '@')
 				{
-					state = State::KeyframesIdentifier;
+					state = State::AtRuleIdentifier;
 				}
 				else
 				{
@@ -205,45 +262,99 @@ int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Strea
 				}
 			}
 			break;
-			case State::KeyframesIdentifier:
+			case State::AtRuleIdentifier:
 			{
 				if (token == '{')
 				{
-					keyframes_identifier.clear();
-					if (pre_token_str.substr(0, KEYFRAMES.size()) == KEYFRAMES)
+					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()));
+
+					if (at_rule_identifier == KEYFRAMES)
+					{
+						at_rule = AtRule::Keyframes;
+					}
+					else if (at_rule_identifier == "decorator")
 					{
-						keyframes_identifier = StringUtilities::StripWhitespace(pre_token_str.substr(KEYFRAMES.size()));
+						at_rule = AtRule::Decorator;
 					}
-					state = State::KeyframesRules;
+					else
+					{
+						// Invalid identifier, should ignore
+						at_rule = AtRule::None;
+						Log::Message(Log::LT_WARNING, "Invalid at-rule identifier '%s' found in stylesheet at %s:%d", at_rule_identifier.c_str(), stream_file_name.c_str(), line_number);
+					}
+					state = State::AtRuleBlock;
+
 				}
 				else
 				{
-					Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing keyframes identifier in stylesheet at %s:%d", token, stream_file_name.c_str(), line_number);
+					Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing at-rule identifier in stylesheet at %s:%d", token, stream_file_name.c_str(), line_number);
 					state = State::Invalid;
 				}
 			}
 			break;
-			case State::KeyframesRules:
+			case State::AtRuleBlock:
 			{
-				if (token == '{')
+				switch (at_rule)
+				{
+				case AtRule::Keyframes:
 				{
-					state = State::KeyframesRules;
+					if (token == '{')
+					{
+						// Each keyframe in keyframes has its own block which is processed here
+						state = State::AtRuleBlock;
 
-					PropertyDictionary properties;
-					if (!ReadProperties(properties))
-						continue;
+						PropertyDictionary properties;
+						if (!ReadProperties(properties, StyleSheetSpecification::GetPropertySpecification()))
+							continue;
 
-					if (!ParseKeyframeBlock(keyframes, keyframes_identifier, pre_token_str, properties))
-						continue;
+						if (!ParseKeyframeBlock(keyframes, at_rule_name, pre_token_str, properties))
+							continue;
+					}
+					else if (token == '}')
+					{
+						at_rule = AtRule::None;
+						at_rule_name.clear();
+						state = State::Global;
+					}
+					else
+					{
+						Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing at-rule block in stylesheet at %s:%d", token, stream_file_name.c_str(), line_number);
+						state = State::Invalid;
+					}
 				}
-				else if (token == '}')
+				break;
+				case AtRule::Decorator:
 				{
-					state = State::Global;
+					if (token == '}')
+					{
+						// Process the decorator
+						ParseDecoratorBlock(decorator_map, at_rule_name);
+
+						at_rule = AtRule::None;
+						at_rule_name.clear();
+						state = State::Global;
+					}
+					else
+					{
+						Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing at-rule block in stylesheet at %s:%d", token, stream_file_name.c_str(), line_number);
+						state = State::Invalid;
+					}
 				}
-				else
+				break;
+				case AtRule::None:
 				{
-					Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing keyframes in stylesheet at %s:%d", token, stream_file_name.c_str(), line_number);
-					state = State::Invalid;
+					// Invalid at-rule, trying to continue
+					if (token == '}')
+					{
+						at_rule = AtRule::None;
+						at_rule_name.clear();
+						state = State::Global;
+					}
+				}
+				break;
+				default:
+					ROCKET_ERROR;
 				}
 			}
 			break;
@@ -269,13 +380,13 @@ int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Strea
 bool StyleSheetParser::ParseProperties(PropertyDictionary& parsed_properties, const String& properties)
 {
 	stream = new StreamMemory((const byte*)properties.c_str(), properties.size());
-	bool success = ReadProperties(parsed_properties);
+	bool success = ReadProperties(parsed_properties, StyleSheetSpecification::GetPropertySpecification());
 	stream->RemoveReference();
 	stream = NULL;
 	return success;
 }
 
-bool StyleSheetParser::ReadProperties(PropertyDictionary& properties)
+bool StyleSheetParser::ReadProperties(PropertyDictionary& properties, const PropertySpecification& property_specification)
 {
 	int rule_line_number = (int)line_number;
 	String name;
@@ -326,7 +437,7 @@ bool StyleSheetParser::ReadProperties(PropertyDictionary& properties)
 				{
 					value = StringUtilities::StripWhitespace(value);
 
-					if (!StyleSheetSpecification::ParsePropertyDeclaration(properties, name, value, stream_file_name, rule_line_number))
+					if (!property_specification.ParsePropertyDeclaration(properties, name, value, stream_file_name, rule_line_number))
 						Log::Message(Log::LT_WARNING, "Syntax error parsing property declaration '%s: %s;' in %s: %d.", name.c_str(), value.c_str(), stream_file_name.c_str(), line_number);
 
 					name.clear();

+ 7 - 2
Source/Core/StyleSheetParser.h

@@ -32,6 +32,7 @@ namespace Rocket {
 namespace Core {
 
 class PropertyDictionary;
+class PropertySpecification;
 class Stream;
 class StyleSheetNode;
 
@@ -51,7 +52,7 @@ public:
 	/// @param node The root node the stream will be parsed into
 	/// @param stream The stream to read
 	/// @return The number of parsed rules, or -1 if an error occured.
-	int Parse(StyleSheetNode* node, KeyframesMap& keyframes, Stream* stream);	
+	int Parse(StyleSheetNode* node, KeyframesMap& keyframes, DecoratorSpecificationMap& decorator_map, Stream* stream);
 
 	/// Parses the given string into the property dictionary
 	/// @param parsed_properties The properties dictionary the properties will be read into
@@ -74,7 +75,8 @@ private:
 
 	// Parses properties from the parse buffer into the dictionary
 	// @param properties The dictionary to store the properties in
-	bool ReadProperties(PropertyDictionary& properties);
+	// @param property_specification The specification used to parse the values. Normally the default stylesheet specification, but not for e.g. all at-rules such as decorators.
+	bool ReadProperties(PropertyDictionary& properties, const PropertySpecification& property_specification);
 
 	// Import properties into the stylesheet node
 	// @param node Node to import into
@@ -86,6 +88,9 @@ private:
 	// Attempts to parse a @keyframes block
 	bool ParseKeyframeBlock(KeyframesMap & keyframes_map, const String & identifier, const String & rules, const PropertyDictionary & properties);
 
+	// Attempts to parse a @keyframes block
+	bool ParseDecoratorBlock(DecoratorSpecificationMap& decorator_map, const String& at_name);
+
 	// 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
 	// @param buffer The buffer that receives the content

+ 6 - 11
Source/Core/StyleSheetSpecification.cpp

@@ -164,17 +164,7 @@ const ShorthandDefinition* StyleSheetSpecification::GetShorthand(ShorthandId id)
 // Parses a property declaration, setting any parsed and validated properties on the given dictionary.
 bool StyleSheetSpecification::ParsePropertyDeclaration(PropertyDictionary& dictionary, const String& property_name, const String& property_value, const String& source_file, int source_line_number)
 {
-	// Try as a property first
-	PropertyId property_id = GetPropertyId(property_name);
-	if(property_id != PropertyId::Invalid)
-		return instance->properties.ParsePropertyDeclaration(dictionary, property_id, property_value, source_file, source_line_number);
-
-	// Then, as a shorthand
-	ShorthandId shorthand_id = GetShorthandId(property_name);
-	if(shorthand_id != ShorthandId::Invalid)
-		return instance->properties.ParseShorthandDeclaration(dictionary, shorthand_id, property_value, source_file, source_line_number);
-
-	return false;
+	return instance->properties.ParsePropertyDeclaration(dictionary, property_name, property_value, source_file, source_line_number);
 }
 
 PropertyId StyleSheetSpecification::GetPropertyId(const String& property_name)
@@ -221,6 +211,11 @@ std::vector<PropertyId> StyleSheetSpecification::GetShorthandUnderlyingPropertie
 	return result;
 }
 
+const PropertySpecification& StyleSheetSpecification::GetPropertySpecification()
+{
+	return instance->properties;
+}
+
 // Registers Rocket's default parsers.
 void StyleSheetSpecification::RegisterDefaultParsers()
 {

+ 8 - 6
Source/Debugger/ElementInfo.cpp

@@ -30,6 +30,7 @@
 #include "../../Include/Rocket/Core/PropertyIterators.h"
 #include "../../Include/Rocket/Core/Factory.h"
 #include "../../Include/Rocket/Core/StyleSheet.h"
+#include "../../Include/Rocket/Core/StyleSheetSpecification.h"
 #include "Geometry.h"
 #include "CommonSource.h"
 #include "InfoSource.h"
@@ -134,15 +135,15 @@ void ElementInfo::ProcessEvent(Core::Event& event)
 				{
 					Core::Element* panel = target_element->GetNextSibling();
 					if (panel->IsVisible())
-						panel->SetProperty("display", Core::Property(Core::Style::Display::None));
+						panel->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::None));
 					else
-						panel->SetProperty("display", Core::Property(Core::Style::Display::Block));
+						panel->SetProperty(Core::PropertyId::Display, Core::Property(Core::Style::Display::Block));
 					event.StopPropagation();
 				}
 				else if (event.GetTargetElement()->GetId() == "close_button")
 				{
 					if (IsVisible())
-						SetProperty("visibility", Core::Property(Core::Style::Visibility::Hidden));
+						SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Hidden));
 				}
 				// Check if the id is in the form "a %d" or "c %d" - these are the ancestor or child labels.
 				else
@@ -273,7 +274,7 @@ void ElementInfo::UpdateSourceElement()
 					{
 						for (auto nvp : *local_properties)
 						{
-							auto& prop_name = nvp.first;
+							auto& prop_name = Core::StyleSheetSpecification::GetPropertyName(nvp.first);
 							auto prop_value = nvp.second.ToString();
 							value += Core::CreateString(prop_name.size() + prop_value.size() + 12, "%s: %s; ", prop_name.c_str(), prop_value.c_str());
 						}
@@ -444,12 +445,13 @@ void ElementInfo::BuildElementPropertiesRML(Core::String& property_rml, Core::El
 	auto it_end = element->IteratePropertiesEnd();
 	for(auto it = element->IteratePropertiesBegin(); it != it_end; ++it)
 	{
-		const Core::String& property_name = (*it).first;
+		const Core::PropertyId property_id = (*it).first;
+		const Core::String& property_name = Core::StyleSheetSpecification::GetPropertyName((*it).first);
 		const Core::Property* property = &(*it).second;
 		const Core::PseudoClassList* property_pseudo_classes_ptr = it.pseudo_class_list();
 
 		// Check that this property isn't overridden or just not inherited.
-		if (primary_element->GetProperty(property_name) != property)
+		if (primary_element->GetProperty(property_id) != property)
 			continue;
 
 		const Core::PseudoClassList& property_pseudo_classes = (property_pseudo_classes_ptr ? *property_pseudo_classes_ptr : empty_property_pseudo_classes);

+ 5 - 5
Source/Debugger/ElementLog.cpp

@@ -104,7 +104,7 @@ bool ElementLog::Initialise()
 		return false;
 
 	beacon->SetId("rkt-debug-log-beacon");
-	beacon->SetProperty("visibility", Core::Property(Core::Style::Visibility::Hidden));
+	beacon->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Hidden));
 	beacon->SetInnerRML(beacon_rml);
 
 	// Remove the initial reference on the beacon.
@@ -164,7 +164,7 @@ void ElementLog::AddLogMessage(Core::Log::Type type, const Core::String& message
 			{
 				if (type < current_beacon_level)
 				{
-					beacon->SetProperty("visibility", Core::Property(Core::Style::Visibility::Visible));
+					beacon->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Visible));
 
 					current_beacon_level = type;
 					Rocket::Core::Element* beacon_button = beacon->GetFirstChild();
@@ -233,15 +233,15 @@ void ElementLog::ProcessEvent(Core::Event& event)
 			if (event.GetTargetElement() == beacon->GetFirstChild())
 			{
 				if (!IsVisible())
-					SetProperty("visibility", Core::Property(Core::Style::Visibility::Visible));
+					SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Visible));
 
-				beacon->SetProperty("visibility", Core::Property(Core::Style::Visibility::Hidden));
+				beacon->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Hidden));
 				current_beacon_level = Core::Log::LT_MAX;
 			}
 			else if (event.GetTargetElement()->GetId() == "close_button")
 			{
 				if (IsVisible())
-					SetProperty("visibility", Core::Property(Core::Style::Visibility::Hidden));
+					SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Hidden));
 			}
 			else
 			{

+ 9 - 9
Source/Debugger/Plugin.cpp

@@ -143,9 +143,9 @@ bool Plugin::SetContext(Core::Context* context)
 void Plugin::SetVisible(bool visibility)
 {
 	if (visibility)
-		menu_element->SetProperty("visibility", Core::Property(Core::Style::Visibility::Visible));
+		menu_element->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Visible));
 	else
-		menu_element->SetProperty("visibility", Core::Property(Core::Style::Visibility::Hidden));
+		menu_element->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Hidden));
 }
 
 // Returns the visibility of the debugger.
@@ -261,16 +261,16 @@ void Plugin::ProcessEvent(Core::Event& event)
 		if (event.GetTargetElement()->GetId() == "event-log-button")
 		{
 			if (log_element->IsVisible())
-				log_element->SetProperty("visibility", Core::Property(Core::Style::Visibility::Hidden));
+				log_element->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Hidden));
 			else
-				log_element->SetProperty("visibility", Core::Property(Core::Style::Visibility::Visible));
+				log_element->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Visible));
 		}
 		else if (event.GetTargetElement()->GetId() == "debug-info-button")
 		{
 			if (info_element->IsVisible())
-				info_element->SetProperty("visibility", Core::Property(Core::Style::Visibility::Hidden));
+				info_element->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Hidden));
 			else
-				info_element->SetProperty("visibility", Core::Property(Core::Style::Visibility::Visible));
+				info_element->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Visible));
 		}
 		else if (event.GetTargetElement()->GetId() == "outlines-button")
 		{
@@ -297,7 +297,7 @@ bool Plugin::LoadMenuElement()
 		return false;
 
 	menu_element->SetId("rkt-debug-menu");
-	menu_element->SetProperty("visibility", Core::Property(Core::Style::Visibility::Hidden));
+	menu_element->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Hidden));
 	menu_element->SetInnerRML(menu_rml);
 
 	// Remove our reference on the document.
@@ -339,7 +339,7 @@ bool Plugin::LoadInfoElement()
 	if (info_element == NULL)
 		return false;
 
-	info_element->SetProperty("visibility", Core::Property(Core::Style::Visibility::Hidden));
+	info_element->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Hidden));
 
 	if (!info_element->Initialise())
 	{
@@ -360,7 +360,7 @@ bool Plugin::LoadLogElement()
 	if (log_element == NULL)
 		return false;
 
-	log_element->SetProperty("visibility", Core::Property(Core::Style::Visibility::Hidden));
+	log_element->SetProperty(Core::PropertyId::Visibility, Core::Property(Core::Style::Visibility::Hidden));
 
 	if (!log_element->Initialise())
 	{