#include "UILayout.h" #include "UIWidget.h" #include #include namespace grove { // ============================================================================= // Measurement (Bottom-Up) // ============================================================================= LayoutMeasurement UILayout::measure(UIWidget* widget) { if (!widget) { return {0.0f, 0.0f}; } LayoutMeasurement result; // Choose measurement algorithm based on layout mode switch (widget->layoutProps.mode) { case LayoutMode::Vertical: result = measureVertical(widget); break; case LayoutMode::Horizontal: result = measureHorizontal(widget); break; case LayoutMode::Stack: result = measureStack(widget); break; case LayoutMode::Absolute: default: // For absolute layout, use explicit size or measure children result.preferredWidth = widget->width; result.preferredHeight = widget->height; // If size is 0, measure children and use their bounds if (result.preferredWidth == 0.0f || result.preferredHeight == 0.0f) { float maxX = 0.0f, maxY = 0.0f; for (auto& child : widget->children) { if (child->visible) { auto childMeasure = measure(child.get()); maxX = std::max(maxX, child->x + childMeasure.preferredWidth); maxY = std::max(maxY, child->y + childMeasure.preferredHeight); } } if (result.preferredWidth == 0.0f) result.preferredWidth = maxX; if (result.preferredHeight == 0.0f) result.preferredHeight = maxY; } break; } // Add padding result.preferredWidth += widget->layoutProps.getTotalPaddingX(); result.preferredHeight += widget->layoutProps.getTotalPaddingY(); // Apply min/max constraints result.preferredWidth = clampSize(result.preferredWidth, widget->layoutProps.minWidth, widget->layoutProps.maxWidth); result.preferredHeight = clampSize(result.preferredHeight, widget->layoutProps.minHeight, widget->layoutProps.maxHeight); // If explicit size is set, use it if (widget->width > 0) result.preferredWidth = widget->width; if (widget->height > 0) result.preferredHeight = widget->height; return result; } LayoutMeasurement UILayout::measureVertical(UIWidget* widget) { LayoutMeasurement result{0.0f, 0.0f}; bool hasVisibleChild = false; for (auto& child : widget->children) { if (!child->visible) continue; auto childMeasure = measure(child.get()); result.preferredWidth = std::max(result.preferredWidth, childMeasure.preferredWidth); result.preferredHeight += childMeasure.preferredHeight; if (hasVisibleChild) { result.preferredHeight += widget->layoutProps.spacing; } hasVisibleChild = true; } return result; } LayoutMeasurement UILayout::measureHorizontal(UIWidget* widget) { LayoutMeasurement result{0.0f, 0.0f}; bool hasVisibleChild = false; for (auto& child : widget->children) { if (!child->visible) continue; auto childMeasure = measure(child.get()); result.preferredWidth += childMeasure.preferredWidth; result.preferredHeight = std::max(result.preferredHeight, childMeasure.preferredHeight); if (hasVisibleChild) { result.preferredWidth += widget->layoutProps.spacing; } hasVisibleChild = true; } return result; } LayoutMeasurement UILayout::measureStack(UIWidget* widget) { LayoutMeasurement result{0.0f, 0.0f}; for (auto& child : widget->children) { if (!child->visible) continue; auto childMeasure = measure(child.get()); result.preferredWidth = std::max(result.preferredWidth, childMeasure.preferredWidth); result.preferredHeight = std::max(result.preferredHeight, childMeasure.preferredHeight); } return result; } // ============================================================================= // Layout (Top-Down) // ============================================================================= void UILayout::layout(UIWidget* widget, float availableWidth, float availableHeight) { if (!widget) return; // Apply size constraints widget->width = clampSize(availableWidth, widget->layoutProps.minWidth, widget->layoutProps.maxWidth); widget->height = clampSize(availableHeight, widget->layoutProps.minHeight, widget->layoutProps.maxHeight); // Calculate content area (available space minus padding) float contentWidth = widget->width - widget->layoutProps.getTotalPaddingX(); float contentHeight = widget->height - widget->layoutProps.getTotalPaddingY(); // Layout children based on mode switch (widget->layoutProps.mode) { case LayoutMode::Vertical: layoutVertical(widget, contentWidth, contentHeight); break; case LayoutMode::Horizontal: layoutHorizontal(widget, contentWidth, contentHeight); break; case LayoutMode::Stack: layoutStack(widget, contentWidth, contentHeight); break; case LayoutMode::Absolute: default: // For absolute layout, just layout children with their preferred sizes for (auto& child : widget->children) { if (!child->visible) continue; auto childMeasure = measure(child.get()); layout(child.get(), childMeasure.preferredWidth, childMeasure.preferredHeight); } break; } } void UILayout::layoutVertical(UIWidget* widget, float availableWidth, float availableHeight) { // Count visible children and calculate flex total int visibleCount = 0; float totalFlex = 0.0f; float fixedHeight = 0.0f; for (auto& child : widget->children) { if (!child->visible) continue; visibleCount++; totalFlex += child->layoutProps.flex; if (child->layoutProps.flex == 0.0f) { auto childMeasure = measure(child.get()); fixedHeight += childMeasure.preferredHeight; } } if (visibleCount == 0) return; // Calculate spacing height float totalSpacing = (visibleCount - 1) * widget->layoutProps.spacing; float remainingHeight = availableHeight - fixedHeight - totalSpacing; // First pass: assign sizes std::vector childHeights; for (auto& child : widget->children) { if (!child->visible) { childHeights.push_back(0.0f); continue; } float childHeight; if (child->layoutProps.flex > 0.0f && totalFlex > 0.0f) { childHeight = (child->layoutProps.flex / totalFlex) * remainingHeight; } else { auto childMeasure = measure(child.get()); childHeight = childMeasure.preferredHeight; } childHeights.push_back(childHeight); } // Second pass: position children float offsetY = widget->layoutProps.getTopPadding(); for (size_t i = 0; i < widget->children.size(); i++) { auto& child = widget->children[i]; if (!child->visible) continue; float childHeight = childHeights[i]; float childWidth; // Handle alignment if (widget->layoutProps.align == Alignment::Stretch) { childWidth = availableWidth; } else { auto childMeasure = measure(child.get()); childWidth = childMeasure.preferredWidth; } // Position based on alignment float childX = widget->layoutProps.getLeftPadding(); switch (widget->layoutProps.align) { case Alignment::Center: childX += (availableWidth - childWidth) * 0.5f; break; case Alignment::End: childX += availableWidth - childWidth; break; default: break; } child->x = childX; child->y = offsetY; layout(child.get(), childWidth, childHeight); offsetY += childHeight + widget->layoutProps.spacing; } } void UILayout::layoutHorizontal(UIWidget* widget, float availableWidth, float availableHeight) { // Count visible children and calculate flex total int visibleCount = 0; float totalFlex = 0.0f; float fixedWidth = 0.0f; for (auto& child : widget->children) { if (!child->visible) continue; visibleCount++; totalFlex += child->layoutProps.flex; if (child->layoutProps.flex == 0.0f) { auto childMeasure = measure(child.get()); fixedWidth += childMeasure.preferredWidth; } } if (visibleCount == 0) return; // Calculate spacing width float totalSpacing = (visibleCount - 1) * widget->layoutProps.spacing; float remainingWidth = availableWidth - fixedWidth - totalSpacing; // First pass: assign sizes std::vector childWidths; for (auto& child : widget->children) { if (!child->visible) { childWidths.push_back(0.0f); continue; } float childWidth; if (child->layoutProps.flex > 0.0f && totalFlex > 0.0f) { childWidth = (child->layoutProps.flex / totalFlex) * remainingWidth; } else { auto childMeasure = measure(child.get()); childWidth = childMeasure.preferredWidth; } childWidths.push_back(childWidth); } // Second pass: position children float offsetX = widget->layoutProps.getLeftPadding(); for (size_t i = 0; i < widget->children.size(); i++) { auto& child = widget->children[i]; if (!child->visible) continue; float childWidth = childWidths[i]; float childHeight; // Handle alignment if (widget->layoutProps.align == Alignment::Stretch) { childHeight = availableHeight; } else { auto childMeasure = measure(child.get()); childHeight = childMeasure.preferredHeight; } // Position based on alignment float childY = widget->layoutProps.getTopPadding(); switch (widget->layoutProps.align) { case Alignment::Center: childY += (availableHeight - childHeight) * 0.5f; break; case Alignment::End: childY += availableHeight - childHeight; break; default: break; } child->x = offsetX; child->y = childY; layout(child.get(), childWidth, childHeight); offsetX += childWidth + widget->layoutProps.spacing; } } void UILayout::layoutStack(UIWidget* widget, float availableWidth, float availableHeight) { float offsetX = widget->layoutProps.getLeftPadding(); float offsetY = widget->layoutProps.getTopPadding(); for (auto& child : widget->children) { if (!child->visible) continue; float childWidth, childHeight; // Handle alignment if (widget->layoutProps.align == Alignment::Stretch) { childWidth = availableWidth; childHeight = availableHeight; } else { auto childMeasure = measure(child.get()); childWidth = childMeasure.preferredWidth; childHeight = childMeasure.preferredHeight; } // Position based on alignment float childX = offsetX; float childY = offsetY; switch (widget->layoutProps.align) { case Alignment::Center: childX += (availableWidth - childWidth) * 0.5f; childY += (availableHeight - childHeight) * 0.5f; break; case Alignment::End: childX += availableWidth - childWidth; childY += availableHeight - childHeight; break; default: break; } child->x = childX; child->y = childY; layout(child.get(), childWidth, childHeight); } } // ============================================================================= // Utilities // ============================================================================= float UILayout::clampSize(float size, float minSize, float maxSize) { if (minSize > 0.0f) { size = std::max(size, minSize); } if (maxSize > 0.0f) { size = std::min(size, maxSize); } return size; } } // namespace grove