#include "Widget.h" #include "../UserInterface/Primitives.h" #include namespace UI { std::weak_ptr Widget::GetLifetimeToken() const { return lifetimeToken; } void Widget::AddChild(std::unique_ptr child) { if (!child) return; child->parent = this; children.push_back(std::move(child)); MarkDirty(DirtyFlags::Layout | DirtyFlags::Hierarchy); } std::unique_ptr Widget::RemoveChild(Widget* child) { auto it = std::find_if(children.begin(), children.end(), [child](const std::unique_ptr& p) { return p.get() == child; }); if (it != children.end()) { std::unique_ptr removed = std::move(*it); removed->parent = nullptr; children.erase(it); MarkDirty(DirtyFlags::Layout | DirtyFlags::Hierarchy); return removed; } return nullptr; } void Widget::MarkDirty(DirtyFlags flags) { dirtyFlags |= flags; // Propagate layout dirty flags upwards to parent containers if (parent && (flags & DirtyFlags::Layout) != DirtyFlags::Clean) { parent->MarkDirty(DirtyFlags::Layout); } } void Widget::ClearDirty(DirtyFlags flags) { dirtyFlags &= flags; } bool Widget::IsDirty(DirtyFlags flags) const { return (dirtyFlags & flags) != DirtyFlags::Clean; } void Widget::Measure(Size availableSpace) { // Basic leaf/default measure implementation: // Takes available space minus margins and calculates desired size based on children float availW = std::max(0.0f, availableSpace.width - margin.HorizontalTotal()); float availH = std::max(0.0f, availableSpace.height - margin.VerticalTotal()); Size childConstraint{availW, availH}; float maxChildW = 0.0f; float maxChildH = 0.0f; for (auto& child : children) { if (!child->IsVisible()) continue; child->Measure(childConstraint); maxChildW = std::max(maxChildW, child->GetDesiredSize().width); maxChildH = std::max(maxChildH, child->GetDesiredSize().height); } float widthWithPadding = maxChildW + padding.HorizontalTotal(); float heightWithPadding = maxChildH + padding.VerticalTotal(); desiredSize.width = std::clamp(widthWithPadding + margin.HorizontalTotal(), minSize.width, maxSize.width); desiredSize.height = std::clamp(heightWithPadding + margin.VerticalTotal(), minSize.height, maxSize.height); } void Widget::ArrangeCore(Rect finalBounds) { localBounds = finalBounds; if (parent) { globalBounds.X = parent->GetGlobalBounds().X + localBounds.X; globalBounds.Y = parent->GetGlobalBounds().Y + localBounds.Y; } else { globalBounds.X = localBounds.X; globalBounds.Y = localBounds.Y; } globalBounds.Width = localBounds.Width; globalBounds.Height = localBounds.Height; } void Widget::Arrange(Rect finalBounds) { ArrangeCore(finalBounds); Rect contentBounds{padding.Left, padding.Top, localBounds.Width - padding.HorizontalTotal(), localBounds.Height - padding.VerticalTotal()}; for (auto& child : children) { if (!child->IsVisible()) continue; Size childDesired = child->GetDesiredSize(); Thickness childMargin = child->GetMargin(); float availW = std::max(0.0f, contentBounds.Width - childMargin.HorizontalTotal()); float availH = std::max(0.0f, contentBounds.Height - childMargin.VerticalTotal()); float naturalW = std::max(0.0f, childDesired.width - childMargin.HorizontalTotal()); float naturalH = std::max(0.0f, childDesired.height - childMargin.VerticalTotal()); auto horiz = CalculateAxisAlignment(child->GetHorizontalAlignment(), availW, naturalW, childMargin.Left); auto vert = CalculateAxisAlignment(child->GetVerticalAlignment(), availH, naturalH, childMargin.Top); child->Arrange(Rect(contentBounds.X + horiz.offset, contentBounds.Y + vert.offset, horiz.size, vert.size)); } ClearDirty(DirtyFlags::Layout); } void Widget::Draw() { if (!visible) return; if (clipContents) { Primitives::PushClip(globalBounds); } for (auto& child : children) { if (child->IsVisible()) { child->Draw(); } } if (clipContents) { Primitives::PopClip(); } ClearDirty(DirtyFlags::Render); } void Widget::Update(float deltaSeconds) { for (auto& child : children) { if (child->IsVisible()) { child->Update(deltaSeconds); } } } void Widget::SetScrollOffset(Point /*offset*/) { } bool Widget::OnPointerStream(const std::shared_ptr& stream) { const auto event = stream->PeekLatestInput(); // Default implementation passes event down to children in reverse z-order for (auto it = children.rbegin(); it != children.rend(); ++it) { if ((*it)->IsVisible() && (*it)->IsEnabled() && (*it)->globalBounds.Contains(event.screenX, event.screenY)) { if ((*it)->OnPointerStream(stream)) { return true; } } } return false; } Widget* Widget::HitTest(Point screenPoint) { if (!visible || !enabled) return nullptr; if (!globalBounds.Contains(screenPoint)) return nullptr; // Search children in reverse z-order (topmost first) for (auto it = children.rbegin(); it != children.rend(); ++it) { Widget* hit = (*it)->HitTest(screenPoint); if (hit) return hit; } return this; } }