Browse Source

TableLayout: Make auto margins work also after size changes. Respect min/max width and height. Make column percentage width >= 100% act as custom flexible width.

Michael Ragazzon 5 years ago
parent
commit
c313b56fce
3 changed files with 73 additions and 67 deletions
  1. 30 18
      Source/Core/LayoutEngine.cpp
  2. 35 42
      Source/Core/LayoutTable.cpp
  3. 8 7
      Source/Core/LayoutTable.h

+ 30 - 18
Source/Core/LayoutEngine.cpp

@@ -260,31 +260,43 @@ bool LayoutEngine::FormatElementInlineBlock(LayoutBlockBox* block_context_box, E
 
 bool LayoutEngine::FormatElementTable(LayoutBlockBox* block_context_box, Element* element_table)
 {
-	LayoutBlockBox* table_block_context_box = nullptr;
-	
-	{
-		Box box;
-		float min_height, max_height;
-		LayoutDetails::BuildBox(box, min_height, max_height, block_context_box, element_table, false);
+	const ComputedValues& computed_table = element_table->GetComputedValues();
+
+	const Vector2f containing_block = LayoutDetails::GetContainingBlock(block_context_box);
+
+	// Build the initial box as specified by the table's style, as if it were a normal block element.
+	Box box;
+	LayoutDetails::BuildBox(box, containing_block, element_table, false);
+
+	Vector2f min_size, max_size;
+	LayoutDetails::GetMinMaxWidth(min_size.x, max_size.x, computed_table, box, containing_block.x);
+	LayoutDetails::GetMinMaxHeight(min_size.y, max_size.y, computed_table, box, containing_block.y);
+	const Vector2f initial_content_size = box.GetSize();
+
+	// Format the table, this may adjust the box content size.
+	const Vector2f table_content_overflow_size = LayoutTable::FormatTable(box, min_size, max_size, element_table);
 
-		table_block_context_box = block_context_box->AddBlockElement(element_table, box, min_height, max_height);
+	const Vector2f final_content_size = box.GetSize();
+	RMLUI_ASSERT(final_content_size.y >= 0);
+
+	if (final_content_size != initial_content_size)
+	{
+		// Perform this step to re-evaluate any auto margins.
+		LayoutDetails::BuildBoxSizeAndMargins(box, min_size, max_size, containing_block, element_table, false, true);
 	}
 
+	// Now that the box is finalized, we can add table as a block element. If we did it earlier, eg. just before formatting the table,
+	// then the table element's offset would not be correct in cases where table size and auto-margins were adjusted.
+	LayoutBlockBox* table_block_context_box = block_context_box->AddBlockElement(element_table, box, final_content_size.y, final_content_size.y);
 	if (!table_block_context_box)
 		return false;
 
-	for (int i = 0; i < 2; i++)
-	{
-		LayoutBlockBox::CloseResult result = LayoutTable::FormatTable(table_block_context_box, element_table);
+	// Set the inner content size so that any overflow can be caught.
+	table_block_context_box->ExtendInnerContentSize(table_content_overflow_size);
 
-		// If the close failed, it probably means that the table or its parent produced scrollbars. Try again, but only once.
-		if (result == LayoutBlockBox::LAYOUT_SELF)
-			continue;
-		else if (result == LayoutBlockBox::LAYOUT_PARENT)
-			return false;
-		else if (result == LayoutBlockBox::OK)
-			break;
-	}
+	// If the close failed, it probably means that its parent produced scrollbars.
+	if (table_block_context_box->Close() != LayoutBlockBox::OK)
+		return false;
 
 	return true;
 }

+ 35 - 42
Source/Core/LayoutTable.cpp

@@ -38,27 +38,21 @@
 
 namespace Rml {
 
-static void SnapToPixelGrid(float& x, float& width)
-{
-	float rounded_x = Math::RoundFloat(x);
-	width = Math::RoundFloat(x + width) - rounded_x;
-	x = rounded_x;
-}
-static void SnapToPixelGrid(Vector2f& position, Vector2f& size)
-{
-	Vector2f rounded_position = position.Round();
-	size = (position + size).Round() - rounded_position;
-	position = rounded_position;
-}
 
-LayoutTable::CloseResult LayoutTable::FormatTable(LayoutBlockBox* table_block_context_box, Element* element_table)
+Vector2f LayoutTable::FormatTable(Box& box, Vector2f min_size, Vector2f max_size, Element* element_table)
 {
-	Vector2f table_content_offset = table_block_context_box->GetBox().GetPosition();
-	Vector2f table_initial_content_size = Vector2f(table_block_context_box->GetBox().GetSize().x, Math::Max(0.0f, table_block_context_box->GetBox().GetSize().y));
+	const ComputedValues& computed_table = element_table->GetComputedValues();
 
-	SnapToPixelGrid(table_content_offset, table_initial_content_size);
+	Vector2f table_content_offset = box.GetPosition();
+	Vector2f table_initial_content_size = Vector2f(box.GetSize().x, Math::Max(0.0f, box.GetSize().y));
 
-	const ComputedValues& computed_table = element_table->GetComputedValues();
+	// When width or height is set, they act as minimum width or height, just as in CSS.
+	if (computed_table.width.type != Style::Width::Auto)
+		min_size.x = Math::Max(min_size.x, table_initial_content_size.x);
+	if (computed_table.height.type != Style::Height::Auto)
+		min_size.y = Math::Max(min_size.y, table_initial_content_size.y);
+
+	Math::SnapToPixelGrid(table_content_offset, table_initial_content_size);
 
 	const Vector2f table_gap = Vector2f(
 		ResolveValue(computed_table.column_gap, table_initial_content_size.x), 
@@ -66,26 +60,19 @@ LayoutTable::CloseResult LayoutTable::FormatTable(LayoutBlockBox* table_block_co
 	);
 
 	// Construct the layout object and format the table.
-	LayoutTable layout_table(element_table, table_gap, table_content_offset, table_initial_content_size);
+	LayoutTable layout_table(element_table, table_gap, table_content_offset, table_initial_content_size, min_size, max_size);
 
 	layout_table.FormatTable();
 
-	// Set the new size of the table on the block box.
-	if (layout_table.table_resulting_content_size != table_initial_content_size)
-	{
-		table_block_context_box->GetBox().SetContent(layout_table.table_resulting_content_size);
-	}
-
-	// Set the inner content size so that any overflow can be caught.
-	table_block_context_box->ExtendInnerContentSize(layout_table.table_content_overflow_size);
+	// Update the box size based on the new table size.
+	box.SetContent(layout_table.table_resulting_content_size);
 
-	CloseResult result = table_block_context_box->Close();
-	return result;
+	return layout_table.table_content_overflow_size;
 }
 
 
-LayoutTable::LayoutTable(Element* element_table, Vector2f table_gap, Vector2f table_content_offset, Vector2f table_initial_content_size)
-	: element_table(element_table), table_gap(table_gap), table_content_offset(table_content_offset), table_initial_content_size(table_initial_content_size)
+LayoutTable::LayoutTable(Element* element_table, Vector2f table_gap, Vector2f table_content_offset, Vector2f table_initial_content_size, Vector2f table_min_size, Vector2f table_max_size)
+	: element_table(element_table), table_min_size(table_min_size), table_max_size(table_max_size), table_gap(table_gap), table_content_offset(table_content_offset), table_initial_content_size(table_initial_content_size)
 {
 	table_resulting_content_size = table_initial_content_size;
 }
@@ -120,7 +107,7 @@ void LayoutTable::FormatTable()
 			}
 			else if (display != Display::TableColumn && display != Display::TableColumnGroup && display != Display::None)
 			{
-				Log::Message(Log::LT_WARNING, "Only table columns and table rows are valid children of tables. Ignoring element %s.", element->GetAddress().c_str());
+				Log::Message(Log::LT_WARNING, "Only table columns, column groups, rows, and row groups are valid children of tables. Ignoring element %s.", element->GetAddress().c_str());
 			}
 			continue;
 		}
@@ -179,7 +166,7 @@ void LayoutTable::FormatTable()
 		}
 	}
 
-	table_resulting_content_size.y = Math::Max(table_cursor_y - table_content_offset.y, 0.0f);
+	table_resulting_content_size.y = Math::Clamp(table_cursor_y - table_content_offset.y, table_min_size.y, table_max_size.y);
 
 	// Size and position the column and column group elements.
 	auto FormatColumn = [this](Element* element, float content_width, float offset_x) {
@@ -354,6 +341,8 @@ void LayoutTable::DetermineColumnWidths()
 	// Fill the column metric with fixed, flexible and min/max widths, based on the element's computed values.
 	auto InitializeColumnWidths = [this, GetEdgeWidths](ColumnMetric& metric, float& margin_left, float& margin_right, float& padding_border_left, float& padding_border_right, const ComputedValues& computed, int span, Style::BoxSizing column_target_box)
 	{
+		RMLUI_ASSERT(span >= 1);
+
 		GetEdgeWidths(margin_left, margin_right, padding_border_left, padding_border_right, computed);
 
 		const float padding_border_sum = padding_border_left + padding_border_right;
@@ -381,6 +370,11 @@ void LayoutTable::DetermineColumnWidths()
 		{
 			metric.flex_width = 1;
 		}
+		else if (computed.width.type == Style::Width::Percentage && computed.width.value >= 100.f)
+		{
+			// Percentages >= 100% are resolved as flexible widths.
+			metric.flex_width = Math::Max(0.01f * computed.width.value / float(span), 0.f);
+		}
 		else
 		{
 			float width = ResolveValue(computed.width, table_initial_content_size.x);
@@ -399,7 +393,7 @@ void LayoutTable::DetermineColumnWidths()
 
 		if (span > 1)
 		{
-			// Distribute any fixed widths over the columns we are spanning.
+			// Account for distribution of fixed widths over the columns we are spanning.
 			const float width_factor = 1.f / float(span);
 			metric.fixed_width *= width_factor;
 			metric.min_width *= width_factor;
@@ -648,7 +642,7 @@ void LayoutTable::DetermineColumnWidths()
 		}
 	}
 
-	// Fill in the resulting columns.
+	// Generate the column results based on the metrics.
 	columns.resize(column_metrics.size());
 	float cursor_x = 0;
 
@@ -673,23 +667,22 @@ void LayoutTable::DetermineColumnWidths()
 			cursor_x += table_gap.x;
 	}
 	
-	// Extend the table content width if the summed column widths and spacing is larger. Include a margin for floating-point imprecision.
-	if (cursor_x - table_initial_content_size.x > 0.5f)
-		table_resulting_content_size.x = cursor_x;
+	// Adjust the table content width based on the accumulated column widths and spacing.
+	table_resulting_content_size.x = Math::Clamp(cursor_x, table_min_size.x, table_max_size.x);
 
 	// Extend column and column group widths to cover multiple columns for spanning column (group) elements.
 	for (size_t i = 0; i < column_metrics.size(); i++)
 	{
 		const ColumnMetric& metric = column_metrics[i];
 
-		if (metric.element_column && metric.column_span > 1 && i + metric.column_span - 1 < (int)column_metrics.size())
+		if (metric.element_column && metric.column_span > 1 && i + metric.column_span - 1 < column_metrics.size())
 		{
 			Column& col = columns[i];
 			Column& col_last_span = columns[i + metric.column_span - 1];
 			col.column_width = col_last_span.cell_width + (col_last_span.cell_offset - col.cell_offset);
 		}
 
-		if (metric.element_group && metric.group_span > 1 && i + metric.group_span - 1 < (int)column_metrics.size())
+		if (metric.element_group && metric.group_span > 1 && i + metric.group_span - 1 < column_metrics.size())
 		{
 			Column& col = columns[i];
 			Column& col_last_span = columns[i + metric.group_span - 1];
@@ -700,9 +693,9 @@ void LayoutTable::DetermineColumnWidths()
 	// Finally, snap boxes to the pixel grid.
 	for (Column& col : columns)
 	{
-		SnapToPixelGrid(col.cell_offset, col.cell_width);
-		SnapToPixelGrid(col.column_offset, col.column_width);
-		SnapToPixelGrid(col.group_offset, col.group_width);
+		Math::SnapToPixelGrid(col.cell_offset, col.cell_width);
+		Math::SnapToPixelGrid(col.column_offset, col.column_width);
+		Math::SnapToPixelGrid(col.group_offset, col.group_width);
 	}
 }
 

+ 8 - 7
Source/Core/LayoutTable.h

@@ -39,19 +39,19 @@ class Box;
 class LayoutTable
 {
 public:
-	using CloseResult = LayoutBlockBox::CloseResult;
-
 	/// Formats and positions a table, including all elements contained within.
-	/// @param[in] block_context_box The open block box to layout the element in.
+	/// @param[inout] box The box used for dimensioning the table, the resulting table size is set on the box.
+	/// @param[in] min_size Minimum width and height of the table.
+	/// @param[in] max_size Maximum width and height of the table.
 	/// @param[in] element_table The table element.
-	/// @returns A close result which tells whether the table or parent need to be reformatted.
-	static CloseResult FormatTable(LayoutBlockBox* table_block_context_box, Element* element_table);
+	/// @return The content size of the table's overflowing content.
+	static Vector2f FormatTable(Box& box, Vector2f min_size, Vector2f max_size, Element* element_table);
 
 private:
-	LayoutTable(Element* element_table, Vector2f table_gap, Vector2f table_content_offset, Vector2f table_initial_content_size);
+	LayoutTable(Element* element_table, Vector2f table_gap, Vector2f table_content_offset, Vector2f table_initial_content_size, Vector2f table_min_size, Vector2f table_max_size);
 
 	struct Column {
-		Element* element_column = nullptr; // The '<col>' element which begins at this column, or nullptr if the column is spanned from a previous column or defined by cells in the first row.
+		Element* element_column = nullptr; // The '<col>' element which begins at this column, or nullptr if there is no such element or if the column is spanned from a previous column.
 		Element* element_group = nullptr;  // The '<colgroup>' element which begins at this column, otherwise nullptr.
 		float cell_width = 0;              // The *border* width of cells in this column.
 		float cell_offset = 0;             // Horizontal offset from the table content box to the border box of cells in this column.
@@ -83,6 +83,7 @@ private:
 
 	Element* const element_table;
 
+	const Vector2f table_min_size, table_max_size;
 	const Vector2f table_gap;
 	const Vector2f table_content_offset;
 	const Vector2f table_initial_content_size;