Browse Source

WIP, decorators partially working.

Michael Ragazzon 6 years ago
parent
commit
ac40b7177c

+ 3 - 0
Include/Rocket/Core/ComputedValues.h

@@ -198,6 +198,9 @@ struct ComputedValues
 
 	TransitionList transition;
 	AnimationList animation;
+
+	String decorator;
+	String font_effect;
 };
 }
 

+ 3 - 0
Include/Rocket/Core/ID.h

@@ -140,6 +140,9 @@ enum class PropertyId : uint16_t
 	PointerEvents,
 	Focus,
 
+	Decorator,
+	FontEffect,
+
 	NumDefinedIds,
 	FirstCustomId = NumDefinedIds
 };

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

@@ -214,6 +214,7 @@ private:
 	PropertyIdNameMap property_map;
 	ShorthandIdNameMap shorthand_map;
 
+	// todo: Do we really need these?
 	PropertyNameList property_names;
 	PropertyNameList inherited_property_names;
 

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

@@ -39,6 +39,7 @@ namespace Core {
 class Element;
 class ElementDefinition;
 class StyleSheetNode;
+class Decorator;
 
 struct KeyframeBlock {
 	float normalized_time;  // [0, 1]
@@ -53,6 +54,7 @@ typedef UnorderedMap<String, Keyframes> KeyframesMap;
 struct DecoratorSpecification {
 	String decorator_type;
 	PropertyDictionary properties;
+	Decorator* decorator = nullptr;
 };
 
 using DecoratorSpecificationMap = UnorderedMap<String, DecoratorSpecification>;
@@ -85,6 +87,9 @@ public:
 	/// Returns the Keyframes of the given name, or null if it does not exist.
 	Keyframes* GetKeyframes(const String& name);
 
+	/// Returns the Decorator of the given name, or null if it does not exist.
+	Decorator* GetDecorator(const String& name) const;
+
 	/// Returns the compiled element definition for a given element hierarchy. A reference count will be added for the
 	/// caller, so another should not be added. The definition should be released by removing the reference count.
 	ElementDefinition* GetElementDefinition(const Element* element) const;

+ 18 - 13
Samples/assets/invader.rcss

@@ -45,6 +45,12 @@ div#title_bar div#icon
 	icon-image-src: invader.tga;
 }
 
+@decorator title-bar : tiled-horizontal {
+	left-image: invader.tga 147px 0px 229px 85px;
+	center-image: invader.tga stretch 229px 0px 230px 85px;
+	right-image: invader.tga 231px 0px 246px 85px;
+}
+
 div#title_bar span
 {
 	padding-left: 85px;
@@ -59,12 +65,19 @@ div#title_bar span
 	outline-width: 1px;
 	outline-color: black;
 
-	background-decorator: tiled-horizontal;
-	background-left-image: invader.tga 147px 0px 229px 85px;
-	background-center-image: invader.tga stretch 229px 0px 230px 85px;
-	background-right-image: invader.tga 231px 0px 246px 85px;
+	decorator: title-bar;
 }
 
+@decorator window : tiled-box {
+	top-left-image: invader.tga 0px 0px 133px 140px;
+	top-right-image: invader.tga 136px 0px 146px 140px;
+	top-image: invader.tga stretch 134px 0px 135px 140px;
+	bottom-left-image: invader.tga 0px 140px 11px 151px;
+	bottom-right-image: invader.tga 136px 140px 146px 151px;
+	bottom-image: invader.tga stretch 11px 140px 12px 151px;
+	left-image: invader.tga stretch 0px 139px 10px 140px;
+	center-image: invader.tga stretch 11px 139px 12px 140px;
+}
 
 
 div#window
@@ -72,15 +85,7 @@ div#window
 	width: auto;
 	padding: 10px 15px;
 
-	background-decorator: tiled-box;
-	background-top-left-image: invader.tga 0px 0px 133px 140px;
-	background-top-right-image: invader.tga 136px 0px 146px 140px;
-	background-top-image: invader.tga stretch 134px 0px 135px 140px;
-	background-bottom-left-image: invader.tga 0px 140px 11px 151px;
-	background-bottom-right-image: invader.tga 136px 140px 146px 151px;
-	background-bottom-image: invader.tga stretch 11px 140px 12px 151px;
-	background-left-image: invader.tga stretch 0px 139px 10px 140px;
-	background-center-image: invader.tga stretch 11px 139px 12px 140px;
+	decorator: window;
 }
 
 div#content

+ 11 - 4
Source/Core/Element.cpp

@@ -503,7 +503,7 @@ void Element::SetBox(const Box& box)
 		box_dirty = true;
 		background->DirtyBackground();
 		border->DirtyBorder();
-		decoration->DirtyDecorators(true);
+		decoration->DirtyDecorators();
 	}
 }
 
@@ -515,7 +515,7 @@ void Element::AddBox(const Box& box)
 	box_dirty = true;
 	background->DirtyBackground();
 	border->DirtyBorder();
-	decoration->DirtyDecorators(true);
+	decoration->DirtyDecorators();
 }
 
 // Returns one of the boxes describing the size of the element.
@@ -1897,9 +1897,16 @@ void Element::OnPropertyChange(const PropertyNameList& changed_properties)
         changed_properties.find(PropertyId::BackgroundColor) != changed_properties.end() ||
 		changed_properties.find(PropertyId::Opacity) != changed_properties.end() ||
 		changed_properties.find(PropertyId::ImageColor) != changed_properties.end()) {
-        background->DirtyBackground();
-        decoration->DirtyDecorators(true);
+		background->DirtyBackground();
     }
+	
+	// Dirty the decoration if it's changed.
+	if (all_dirty ||
+		changed_properties.find(PropertyId::Decorator) != changed_properties.end() ||
+		changed_properties.find(PropertyId::Opacity) != changed_properties.end() ||
+		changed_properties.find(PropertyId::ImageColor) != changed_properties.end()) {
+		decoration->DirtyDecorators();
+	}
 
 	// Dirty the border if it's changed.
 	if (all_dirty || 

+ 26 - 66
Source/Core/ElementDecoration.cpp

@@ -37,7 +37,6 @@ namespace Core {
 ElementDecoration::ElementDecoration(Element* _element)
 {
 	element = _element;
-	active_decorators_dirty = false;
 	decorators_dirty = false;
 }
 
@@ -51,12 +50,25 @@ bool ElementDecoration::ReloadDecorators()
 {
 	ReleaseDecorators();
 
-	const ElementDefinition* definition = element->GetDefinition();
-	if (definition == NULL)
+	const StyleSheet* stylesheet = element->GetStyleSheet();
+	if (!stylesheet)
 		return true;
 
-	decorators_dirty = false;
-	active_decorators_dirty = true;
+	const String& decorator_value = element->GetComputedValues().decorator;
+	if (decorator_value.empty())
+		return true;
+
+	// @performance: Can optimize for the case of only one decorator
+	StringList decorator_list;
+	StringUtilities::ExpandString(decorator_list, decorator_value);
+
+	for (const String& name : decorator_list)
+	{
+		Decorator* decorator = stylesheet->GetDecorator(name);
+
+		if (decorator)
+			LoadDecorator(decorator);
+	}
 
 	return true;
 }
@@ -85,65 +97,29 @@ void ElementDecoration::ReleaseDecorators()
 	}
 
 	decorators.clear();
-	active_decorators.clear();
-	decorator_index.clear();
 }
 
-// Updates the list of active decorators (if necessary).
-void ElementDecoration::UpdateActiveDecorators()
-{
-	if (active_decorators_dirty)
-	{
-		active_decorators.clear();
-
-		for (DecoratorIndex::iterator i = decorator_index.begin(); i != decorator_index.end(); ++i)
-		{
-			PseudoClassDecoratorIndexList& indices = (*i).second;
-			for (size_t j = 0; j < indices.size(); ++j)
-			{
-				if (element->ArePseudoClassesSet(indices[j].first))
-				{
-					// Insert the new index into the list of active decorators, ordered by z-index.
-					float z_index = decorators[indices[j].second].decorator->GetZIndex();
-					std::vector< int >::iterator insert_iterator = active_decorators.begin();
-					while (insert_iterator != active_decorators.end() &&
-						   z_index > decorators[(*insert_iterator)].decorator->GetZIndex())
-						++insert_iterator;
-
-					active_decorators.insert(insert_iterator, indices[j].second);
-
-					break;
-				}
-			}
-		}
-
-		active_decorators_dirty = false;
-	}
-}
 
 void ElementDecoration::RenderDecorators()
 {
+	// @performance: Ignore dirty flag if e.g. pseudo classes do not affect the decorators
 	if (decorators_dirty)
 	{
 		decorators_dirty = false;
 		ReloadDecorators();
 	}
 
-	UpdateActiveDecorators();
-
 	// Render the decorators attached to this element in its current state.
-	for (size_t i = 0; i < active_decorators.size(); i++)
+	for (size_t i = 0; i < decorators.size(); i++)
 	{
-		DecoratorHandle& decorator = decorators[active_decorators[i]];
+		DecoratorHandle& decorator = decorators[i];
 		decorator.decorator->RenderElement(element, decorator.decorator_data);
 	}
 }
 
-void ElementDecoration::DirtyDecorators(bool full_reload)
+void ElementDecoration::DirtyDecorators()
 {
-	if (full_reload)
-		decorators_dirty = true;
-	active_decorators_dirty = true;
+	decorators_dirty = true;
 }
 
 // Iterates over all active decorators attached to the decoration's element.
@@ -152,27 +128,11 @@ bool ElementDecoration::IterateDecorators(int& index, PseudoClassList& pseudo_cl
 	if (index < 0)
 		return false;
 
-	size_t count = 0;
-
-	for (DecoratorIndex::const_iterator index_iterator = decorator_index.begin(); index_iterator != decorator_index.end(); ++index_iterator)
+	if (index < (int)decorators.size())
 	{
-		// This is the list of all pseudo-classes that have a decorator under this name.
-		const PseudoClassDecoratorIndexList& decorator_index_list = index_iterator->second;
-		if (count + decorator_index_list.size() <= (size_t) index)
-		{
-			count += decorator_index_list.size();
-			continue;
-		}
-
-		// This is the one we're looking for.
-		name = index_iterator->first;
-
-		int relative_index = index - (int)count;
-		pseudo_classes = decorator_index_list[relative_index].first;
-
-		const DecoratorHandle& decorator_handle = decorators[decorator_index_list[relative_index].second];
-		decorator = decorator_handle.decorator;
-		decorator_data = decorator_handle.decorator_data;
+		decorator = decorators[index].decorator;
+		decorator_data = decorators[index].decorator_data;
+		name = ":not implemented:";
 
 		index += 1;
 		return true;

+ 3 - 15
Source/Core/ElementDecoration.h

@@ -53,9 +53,8 @@ public:
 	/// Renders all appropriate decorators.
 	void RenderDecorators();
 
-	/// Mark decorators as dirty and force them to reset themselves. If full_reload is true, the
-	/// decorators will be reloaded, otherwise only the active_decorators will be updated.
-	void DirtyDecorators(bool full_reload);
+	/// Mark decorators as dirty and force them to reset themselves.
+	void DirtyDecorators();
 
 	/// Iterates over all active decorators attached to the decoration's element.
 	/// @param[inout] index Index to fetch. This is incremented after the fetch.
@@ -73,8 +72,6 @@ private:
 	bool ReloadDecorators();
 	// Releases all existing decorators and frees their data.
 	void ReleaseDecorators();
-	// Updates the list of active decorators (if necessary)
-	void UpdateActiveDecorators();
 
 	struct DecoratorHandle
 	{
@@ -83,24 +80,15 @@ private:
 	};
 
 	typedef std::vector< DecoratorHandle > DecoratorList;
-	typedef std::pair< PseudoClassList, int > PseudoClassDecoratorIndex;
-	typedef std::vector< PseudoClassDecoratorIndex > PseudoClassDecoratorIndexList;
-	typedef UnorderedMap< String, PseudoClassDecoratorIndexList > DecoratorIndex;
 
 	// The element this decorator belongs to
 	Element* element;
 
 	// The list of every decorator used by this element in every class.
 	DecoratorList decorators;
-	// The list of currently active decorators.
-	std::vector< int > active_decorators;
-	bool active_decorators_dirty;
+
 	// If set, a full reload is necessary
 	bool decorators_dirty;
-
-	// 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.
-	DecoratorIndex decorator_index;
 };
 
 }

+ 11 - 4
Source/Core/ElementStyle.cpp

@@ -196,7 +196,7 @@ void ElementStyle::UpdateDefinition()
 			// Since we had no definition before there is a likelihood that everything is dirty.
 			// We could do as in the next else-if block, but this is considerably faster.
 			dirty_properties.DirtyAll();
-			element->GetElementDecoration()->DirtyDecorators(true);
+			element->GetElementDecoration()->DirtyDecorators();
 			definition = new_definition;
 		}
 		else if (new_definition != definition)
@@ -217,14 +217,14 @@ void ElementStyle::UpdateDefinition()
 			definition = new_definition;
 			
 			DirtyProperties(properties);
-			element->GetElementDecoration()->DirtyDecorators(true);
+			element->GetElementDecoration()->DirtyDecorators();
 		}
 		else if (!new_definition)
 		{
 			// Both definitions empty
 			ROCKET_ASSERT(!definition);
 			// Is this really necessary?
-			element->GetElementDecoration()->DirtyDecorators(true);
+			element->GetElementDecoration()->DirtyDecorators();
 		}
 		else if (new_definition)
 		{
@@ -260,7 +260,7 @@ void ElementStyle::SetPseudoClass(const String& pseudo_class, bool activate)
 
 	if (pseudo_classes.size() != num_pseudo_classes)
 	{
-		element->GetElementDecoration()->DirtyDecorators(false);
+		element->GetElementDecoration()->DirtyDecorators();
 
 		if (definition != NULL)
 		{
@@ -938,6 +938,13 @@ DirtyPropertyList ElementStyle::ComputeValues(Style::ComputedValues& values, con
 		case PropertyId::Animation:
 			values.animation = p->Get<AnimationList>();
 			break;
+
+		case PropertyId::Decorator:
+			values.decorator = p->Get<String>();
+			break;
+		case PropertyId::FontEffect:
+			values.font_effect = p->Get<String>();
+			break;
 		}
 	}
 

+ 11 - 10
Source/Core/PropertySpecification.cpp

@@ -31,7 +31,6 @@
 #include "../../Include/Rocket/Core/Log.h"
 #include "../../Include/Rocket/Core/PropertyDefinition.h"
 #include "../../Include/Rocket/Core/PropertyDictionary.h"
-#include "../../Include/Rocket/Core/StyleSheetSpecification.h"
 
 namespace Rocket {
 namespace Core {
@@ -65,7 +64,7 @@ PropertyDefinition& PropertySpecification::RegisterProperty(const String& proper
 		// We don't want to owerwrite an existing entry.
 		if (properties[index])
 		{
-			Log::Message(Log::LT_ERROR, "While registering property '%s': The property is already registered, ignoring.", StyleSheetSpecification::GetPropertyName(id).c_str());
+			Log::Message(Log::LT_ERROR, "While registering property '%s': The property is already registered, ignoring.", property_name.c_str());
 			return *properties[index];
 		}
 	}
@@ -79,6 +78,9 @@ PropertyDefinition& PropertySpecification::RegisterProperty(const String& proper
 	PropertyDefinition* property_definition = new PropertyDefinition(id, default_value, inherited, forces_layout);
 
 	properties[index] = property_definition;
+	property_names.insert(id);
+	if (inherited)
+		inherited_property_names.insert(id);
 
 	return *property_definition;
 }
@@ -124,12 +126,12 @@ bool PropertySpecification::RegisterShorthand(const String& shorthand_name, cons
 
 	for (auto& name : property_list)
 	{
-		PropertyId property_id = StyleSheetSpecification::GetPropertyId(name);
+		PropertyId property_id = property_map.GetId(name);
 		if (property_id != PropertyId::Invalid)
 			id_list.emplace_back(property_id);
 		else
 		{
-			ShorthandId shorthand_id = StyleSheetSpecification::GetShorthandId(name);
+			ShorthandId shorthand_id = shorthand_map.GetId(name);
 			if (shorthand_id != ShorthandId::Invalid)
 				id_list.emplace_back(shorthand_id);
 		}
@@ -142,7 +144,7 @@ bool PropertySpecification::RegisterShorthand(const String& shorthand_name, cons
 		return false;
 
 	// Construct the new shorthand definition and resolve its properties.
-	ShorthandDefinition* property_shorthand = new ShorthandDefinition();
+	std::unique_ptr<ShorthandDefinition> property_shorthand(new ShorthandDefinition());
 	for (size_t i = 0; i < id_list.size(); i++)
 	{
 		ShorthandItem item;
@@ -162,14 +164,13 @@ bool PropertySpecification::RegisterShorthand(const String& shorthand_name, cons
 
 		if (item.type == ShorthandItemType::Invalid)
 		{
-			Log::Message(Log::LT_ERROR, "Shorthand property '%s' was registered with invalid property '%s'.", StyleSheetSpecification::GetShorthandName(id).c_str(), property_list[i].c_str());
-			delete property_shorthand;
-
+			Log::Message(Log::LT_ERROR, "Shorthand property '%s' was registered with invalid property '%s'.", shorthand_name.c_str(), property_list[i].c_str());
 			return false;
 		}
 		property_shorthand->items.push_back(item);
 	}
 
+	property_shorthand->id = id;
 	property_shorthand->type = type;
 
 	const size_t index = (size_t)id;
@@ -180,7 +181,7 @@ bool PropertySpecification::RegisterShorthand(const String& shorthand_name, cons
 		// We don't want to owerwrite an existing entry.
 		if (shorthands[index])
 		{
-			Log::Message(Log::LT_ERROR, "While registering shorthand '%s': The shorthand is already registered, ignoring.", StyleSheetSpecification::GetShorthandName(id).c_str());
+			Log::Message(Log::LT_ERROR, "The shorthand '%s' already exists, ignoring.", shorthand_name.c_str());
 			return false;
 		}
 	}
@@ -190,7 +191,7 @@ bool PropertySpecification::RegisterShorthand(const String& shorthand_name, cons
 		shorthands.resize((index * 3) / 2 + 1, nullptr);
 	}
 
-	shorthands[index] = property_shorthand;
+	shorthands[index] = property_shorthand.release();
 	return true;
 }
 

+ 19 - 3
Source/Core/StyleSheet.cpp

@@ -62,6 +62,10 @@ StyleSheet::~StyleSheet()
 {
 	delete root;
 
+	// Release decorators
+	for (auto& pair : decorator_map)
+		pair.second.decorator->RemoveReference();
+
 	// Release our reference count on the cached element definitions.
 	for (ElementDefinitionCache::iterator cache_iterator = address_cache.begin(); cache_iterator != address_cache.end(); ++cache_iterator)
 		(*cache_iterator).second->RemoveReference();
@@ -95,12 +99,15 @@ StyleSheet* StyleSheet::CombineStyleSheet(const StyleSheet* other_sheet) const
 		new_sheet->keyframes[other_keyframes.first] = other_keyframes.second;
 	}
 
-	// Any matching @decorator names are overridden
-	new_sheet->decorator_map = other_sheet->decorator_map;
+	// Copy over the decorators, and replace any matching decorator names from other_sheet
+	// @todo / @leak: Add and remove references as appropriate, not sufficient as is!
+	new_sheet->decorator_map = decorator_map;
 	for (auto& other_decorator: other_sheet->decorator_map)
 	{
 		new_sheet->decorator_map[other_decorator.first] = other_decorator.second;
 	}
+	for (auto& pair : new_sheet->decorator_map)
+		pair.second.decorator->AddReference();
 
 	new_sheet->specificity_offset = specificity_offset + other_sheet->specificity_offset;
 	return new_sheet;
@@ -119,13 +126,22 @@ void StyleSheet::BuildNodeIndex()
 }
 
 // Returns the Keyframes of the given name, or null if it does not exist.
-Keyframes * StyleSheet::GetKeyframes(const String & name) {
+Keyframes * StyleSheet::GetKeyframes(const String & name)
+{
 	auto it = keyframes.find(name);
 	if (it != keyframes.end())
 		return &(it->second);
 	return nullptr;
 }
 
+Decorator* StyleSheet::GetDecorator(const String& name) const
+{
+	auto it = decorator_map.find(name);
+	if (it == decorator_map.end())
+		return nullptr;
+	return it->second.decorator;
+}
+
 // Returns the compiled element definition for a given element hierarchy.
 ElementDefinition* StyleSheet::GetElementDefinition(const Element* element) const
 {

+ 19 - 9
Source/Core/StyleSheetParser.cpp

@@ -205,7 +205,14 @@ bool StyleSheetParser::ParseDecoratorBlock(DecoratorSpecificationMap& decorator_
 	if (!ReadProperties(properties, *property_specification))
 		return false;
 
-	decorator_map.emplace(name, DecoratorSpecification{ std::move(decorator_type), std::move(properties) });
+	Decorator* decorator = Factory::InstanceDecorator(decorator_type, properties);
+	if (!decorator)
+	{
+		Log::Message(Log::LT_WARNING, "Could not instance decorator of type '%s' declared at %s:%d.", decorator_type.c_str(), stream_file_name.c_str(), line_number);
+		return false;
+	}
+
+	decorator_map.emplace(name, DecoratorSpecification{ std::move(decorator_type), std::move(properties), decorator });
 
 	return true;
 }
@@ -272,18 +279,24 @@ int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Decor
 					if (at_rule_identifier == KEYFRAMES)
 					{
 						at_rule = AtRule::Keyframes;
+						state = State::AtRuleBlock;
 					}
 					else if (at_rule_identifier == "decorator")
 					{
-						at_rule = AtRule::Decorator;
+						ParseDecoratorBlock(decorator_map, at_rule_name);
+						
+						at_rule = AtRule::None;
+						at_rule_name.clear();
+						state = State::Global;
 					}
 					else
 					{
 						// Invalid identifier, should ignore
 						at_rule = AtRule::None;
+						at_rule_name.clear();
+						state = State::Global;
 						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
@@ -319,25 +332,22 @@ int StyleSheetParser::Parse(StyleSheetNode* node, KeyframesMap& keyframes, Decor
 					}
 					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);
+						Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing keyframe block in stylesheet at %s:%d", token, stream_file_name.c_str(), line_number);
 						state = State::Invalid;
 					}
 				}
 				break;
 				case AtRule::Decorator:
 				{
-					if (token == '}')
+					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);
+						Log::Message(Log::LT_WARNING, "Invalid character '%c' found while parsing decorator block in stylesheet at %s:%d", token, stream_file_name.c_str(), line_number);
 						state = State::Invalid;
 					}
 				}

+ 3 - 0
Source/Core/StyleSheetSpecification.cpp

@@ -365,6 +365,9 @@ void StyleSheetSpecification::RegisterDefaultProperties()
 	RegisterProperty(PropertyId::Transition, TRANSITION, "none", false, false).AddParser(TRANSITION);
 	RegisterProperty(PropertyId::Animation, ANIMATION, "none", false, false).AddParser(ANIMATION);
 
+	RegisterProperty(PropertyId::Decorator, "decorator", "", false, false).AddParser("string");
+	RegisterProperty(PropertyId::FontEffect, "font-effect", "", false, false).AddParser("string");
+
 	instance->properties.property_map.AssertAllInserted(PropertyId::NumDefinedIds);
 	instance->properties.shorthand_map.AssertAllInserted(ShorthandId::NumDefinedIds);
 }

+ 1 - 1
Source/Debugger/CommonSource.h

@@ -93,7 +93,7 @@ static const char* common_rcss =
 "	background: white;\n"
 "	border-width: 2px;\n"
 "	border-color: #888;\n"
-"	border-width-top: 0px;\n"
+"	border-top-width: 0px;\n"
 "}\n"
 ".error\n"
 "{\n"