Pārlūkot izejas kodu

Improve stacking context such that the paint order of elements more closely follows CSS

Michael Ragazzon 2 gadi atpakaļ
vecāks
revīzija
e6d52f5c80
2 mainītis faili ar 129 papildinājumiem un 98 dzēšanām
  1. 3 3
      Include/RmlUi/Core/Element.h
  2. 126 95
      Source/Core/Element.cpp

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

@@ -64,7 +64,7 @@ class StyleSheet;
 class StyleSheetContainer;
 class TransformState;
 struct ElementMeta;
-struct StackingOrderedChild;
+struct StackingContextChild;
 
 enum class ScrollAlignment {
 	Start,   // Align to the top or left edge of the parent element.
@@ -684,8 +684,8 @@ private:
 	void SetBaseline(float baseline);
 
 	void BuildLocalStackingContext();
-	void BuildStackingContext(ElementList* stacking_context);
-	static void BuildStackingContextForTable(Vector<StackingOrderedChild>& ordered_children, Element* child);
+	void AddChildrenToStackingContext(Vector<StackingContextChild>& stacking_children);
+	void AddToStackingContext(Vector<StackingContextChild>& stacking_children, bool is_flex_item, bool is_non_dom_element);
 	void DirtyStackingContext();
 
 	void UpdateDefinition();

+ 126 - 95
Source/Core/Element.cpp

@@ -2263,128 +2263,159 @@ void Element::SetBaseline(float in_baseline)
 	baseline = in_baseline;
 }
 
-void Element::BuildLocalStackingContext()
+enum class RenderOrder {
+	StackNegative, // Local stacking context with z < 0.
+	Block,
+	TableColumnGroup,
+	TableColumn,
+	TableRowGroup,
+	TableRow,
+	TableCell,
+	Floating,
+	Inline,
+	Positioned,    // Positioned element, or local stacking context with z == 0.
+	StackPositive, // Local stacking context with z > 0.
+};
+struct StackingContextChild {
+	Element* element = nullptr;
+	RenderOrder order = {};
+};
+static bool operator<(const StackingContextChild& lhs, const StackingContextChild& rhs)
 {
-	stacking_context_dirty = false;
-	stacking_context.clear();
-
-	BuildStackingContext(&stacking_context);
-	std::stable_sort(stacking_context.begin(), stacking_context.end(), [](const Element* lhs, const Element* rhs) { return lhs->GetZIndex() < rhs->GetZIndex(); });
+	if (int(lhs.order) == int(rhs.order))
+		return lhs.element->GetZIndex() < rhs.element->GetZIndex();
+	return int(lhs.order) < int(rhs.order);
 }
 
-enum class RenderOrder { Block, TableColumnGroup, TableColumn, TableRowGroup, TableRow, TableCell, Inline, Floating, Positioned };
-struct StackingOrderedChild {
-	Element* element;
-	RenderOrder order;
-	bool include_children;
-};
-
-void Element::BuildStackingContext(ElementList* new_stacking_context)
+// Treat all children in the range [index_begin, end) as if the parent created a new stacking context, by sorting them
+// separately and then assigning their parent's paint order. However, positioned and descendants which create a new
+// stacking context should be considered part of the parent stacking context. See CSS 2, Appendix E.
+static void StackingContext_MakeAtomicRange(Vector<StackingContextChild>& stacking_children, size_t index_begin, RenderOrder parent_render_order)
 {
-	RMLUI_ZoneScoped;
-
-	// Build the list of ordered children. Our child list is sorted within the stacking context so stacked elements
-	// will render in the right order; ie, positioned elements will render on top of inline elements, which will render
-	// on top of floated elements, which will render on top of block elements.
-	Vector< StackingOrderedChild > ordered_children;
+	std::stable_sort(stacking_children.begin() + index_begin, stacking_children.end());
 
-	const size_t num_children = children.size();
-	ordered_children.reserve(num_children);
-
-	if (GetDisplay() == Style::Display::Table)
+	for (auto it = stacking_children.begin() + index_begin; it != stacking_children.end(); ++it)
 	{
-		BuildStackingContextForTable(ordered_children, this);
+		auto order = it->order;
+		if (order != RenderOrder::StackNegative && order != RenderOrder::Positioned && order != RenderOrder::StackPositive)
+			it->order = parent_render_order;
 	}
-	else
-	{
-		for (size_t i = 0; i < num_children; ++i)
-		{
-			Element* child = children[i].get();
-
-			if (!child->IsVisible())
-				continue;
-
-			ordered_children.emplace_back();
-			StackingOrderedChild& ordered_child = ordered_children.back();
-
-			ordered_child.element = child;
-			ordered_child.order = RenderOrder::Inline;
-			ordered_child.include_children = !child->local_stacking_context;
+}
 
-			const Style::Display child_display = child->GetDisplay();
+void Element::BuildLocalStackingContext()
+{
+	stacking_context_dirty = false;
 
-			if (child->GetPosition() != Style::Position::Static)
-				ordered_child.order = RenderOrder::Positioned;
-			else if (child->GetFloat() != Style::Float::None)
-				ordered_child.order = RenderOrder::Floating;
-			else if (child_display == Style::Display::Block || child_display == Style::Display::Table || child_display == Style::Display::Flex)
-				ordered_child.order = RenderOrder::Block;
-			else
-				ordered_child.order = RenderOrder::Inline;
-		}
-	}
+	Vector<StackingContextChild> stacking_children;
+	AddChildrenToStackingContext(stacking_children);
+	std::stable_sort(stacking_children.begin(), stacking_children.end());
 
-	// Sort the list!
-	std::stable_sort(ordered_children.begin(), ordered_children.end(), [](const StackingOrderedChild& lhs, const StackingOrderedChild& rhs) { return int(lhs.order) < int(rhs.order); });
+	stacking_context.resize(stacking_children.size());
+	for (size_t i = 0; i < stacking_children.size(); i++)
+		stacking_context[i] = stacking_children[i].element;
+}
 
-	// Add the list of ordered children into the stacking context in order.
-	for (size_t i = 0; i < ordered_children.size(); ++i)
+void Element::AddChildrenToStackingContext(Vector<StackingContextChild>& stacking_children)
+{
+	bool is_flex_container = (GetDisplay() == Style::Display::Flex);
+	const int num_children = (int)children.size();
+	for (int i = 0; i < num_children; ++i)
 	{
-		new_stacking_context->push_back(ordered_children[i].element);
-
-		if (ordered_children[i].include_children)
-			ordered_children[i].element->BuildStackingContext(new_stacking_context);
+		const bool is_non_dom_element = (i >= num_children - num_non_dom_children);
+		children[i]->AddToStackingContext(stacking_children, is_flex_container, is_non_dom_element);
 	}
 }
 
-void Element::BuildStackingContextForTable(Vector<StackingOrderedChild>& ordered_children, Element* parent)
+void Element::AddToStackingContext(Vector<StackingContextChild>& stacking_children, bool is_flex_item, bool is_non_dom_element)
 {
-	const size_t num_children = parent->children.size();
+	using Style::Display;
 
-	for (size_t i = 0; i < num_children; ++i)
-	{
-		Element* child = parent->children[i].get();
+	if (!IsVisible())
+		return;
 
-		if (!child->IsVisible())
-			continue;
+	const Display display = GetDisplay();
 
-		ordered_children.emplace_back();
-		StackingOrderedChild& ordered_child = ordered_children.back();
-		ordered_child.element = child;
-		ordered_child.order = RenderOrder::Inline;
-		ordered_child.include_children = false;
+	RenderOrder order = RenderOrder::Inline;
+	bool include_children = true;
+	bool render_as_atomic_unit = false;
 
-		bool recurse_into_children = false;
+	if (local_stacking_context)
+	{
+		if (z_index > 0.f)
+			order = RenderOrder::StackPositive;
+		else if (z_index < 0.f)
+			order = RenderOrder::StackNegative;
+		else
+			order = RenderOrder::Positioned;
 
-		switch (child->GetDisplay())
+		include_children = false;
+	}
+	else if (display == Display::TableRow || display == Display::TableRowGroup || display == Display::TableColumn ||
+		display == Display::TableColumnGroup)
+	{
+		// Handle internal display values taking priority over position and float.
+		switch (display)
 		{
-		case Style::Display::TableRow:
-			ordered_child.order = RenderOrder::TableRow;
-			recurse_into_children = true;
-			break;
-		case Style::Display::TableRowGroup:
-			ordered_child.order = RenderOrder::TableRowGroup;
-			recurse_into_children = true;
-			break;
-		case Style::Display::TableColumn:
-			ordered_child.order = RenderOrder::TableColumn;
-			break;
-		case Style::Display::TableColumnGroup:
-			ordered_child.order = RenderOrder::TableColumnGroup;
-			recurse_into_children = true;
+		case Display::TableRow: order = RenderOrder::TableRow; break;
+		case Display::TableRowGroup: order = RenderOrder::TableRowGroup; break;
+		case Display::TableColumn: order = RenderOrder::TableColumn; break;
+		case Display::TableColumnGroup: order = RenderOrder::TableColumnGroup; break;
+		default: break;
+		}
+	}
+	else if (GetPosition() != Style::Position::Static)
+	{
+		order = RenderOrder::Positioned;
+		render_as_atomic_unit = true;
+	}
+	else if (GetFloat() != Style::Float::None)
+	{
+		order = RenderOrder::Floating;
+		render_as_atomic_unit = true;
+	}
+	else
+	{
+		switch (display)
+		{
+		case Display::Block:
+		case Display::Table:
+		case Display::Flex:
+			order = RenderOrder::Block;
+			render_as_atomic_unit = (display == Display::Table || is_flex_item);
 			break;
-		case Style::Display::TableCell:
-			ordered_child.order = RenderOrder::TableCell;
-			ordered_child.include_children = !child->local_stacking_context;
+
+		case Display::Inline:
+		case Display::InlineBlock:
+			order = RenderOrder::Inline;
+			render_as_atomic_unit = (display == Display::InlineBlock || is_flex_item);
 			break;
-		default:
-			ordered_child.order = RenderOrder::Positioned;
-			ordered_child.include_children = !child->local_stacking_context;
+
+		case Display::TableCell:
+			order = RenderOrder::TableCell;
+			render_as_atomic_unit = true;
 			break;
+
+		case Display::TableRow:
+		case Display::TableRowGroup:
+		case Display::TableColumn:
+		case Display::TableColumnGroup:
+		case Display::None: RMLUI_ERROR; break; /* Handled above */
 		}
+	}
+
+	if (is_non_dom_element)
+		render_as_atomic_unit = true;
+
+	stacking_children.push_back(StackingContextChild{this, order});
+
+	if (include_children && !children.empty())
+	{
+		const size_t index_child_begin = stacking_children.size();
+
+		AddChildrenToStackingContext(stacking_children);
 
-		if (recurse_into_children)
-			BuildStackingContextForTable(ordered_children, child);
+		if (render_as_atomic_unit)
+			StackingContext_MakeAtomicRange(stacking_children, index_child_begin, order);
 	}
 }