Add XCUI layout engine MVP and archive subplan 02
This commit is contained in:
485
engine/include/XCEngine/UI/Layout/LayoutEngine.h
Normal file
485
engine/include/XCEngine/UI/Layout/LayoutEngine.h
Normal file
@@ -0,0 +1,485 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/Layout/LayoutTypes.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
namespace Layout {
|
||||
|
||||
struct UILayoutPassResult {
|
||||
UISize desiredSize = UISize(0.0f, 0.0f);
|
||||
std::vector<UILayoutChildResult> children = {};
|
||||
};
|
||||
|
||||
namespace Detail {
|
||||
|
||||
inline bool IsFiniteExtent(float value) {
|
||||
return std::isfinite(value);
|
||||
}
|
||||
|
||||
inline float ClampExtent(float value, float minValue, float maxValue) {
|
||||
value = (std::max)(value, minValue);
|
||||
if (IsFiniteExtent(maxValue)) {
|
||||
value = (std::min)(value, maxValue);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
inline float ReduceAvailableExtent(float value, float reduction) {
|
||||
if (!IsFiniteExtent(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return (std::max)(0.0f, value - reduction);
|
||||
}
|
||||
|
||||
inline UISize DeflateSize(const UISize& size, const UILayoutThickness& thickness) {
|
||||
return UISize(
|
||||
ReduceAvailableExtent(size.width, thickness.Horizontal()),
|
||||
ReduceAvailableExtent(size.height, thickness.Vertical()));
|
||||
}
|
||||
|
||||
inline UIRect DeflateRect(const UIRect& rect, const UILayoutThickness& thickness) {
|
||||
const float width = (std::max)(0.0f, rect.width - thickness.Horizontal());
|
||||
const float height = (std::max)(0.0f, rect.height - thickness.Vertical());
|
||||
return UIRect(rect.x + thickness.left, rect.y + thickness.top, width, height);
|
||||
}
|
||||
|
||||
inline float ResolveMeasuredExtent(
|
||||
const UILayoutLength& length,
|
||||
float desiredContentExtent,
|
||||
float minExtent,
|
||||
float maxExtent,
|
||||
float availableExtent) {
|
||||
float resolved = desiredContentExtent;
|
||||
switch (length.unit) {
|
||||
case UILayoutLengthUnit::Pixels:
|
||||
resolved = length.value;
|
||||
break;
|
||||
case UILayoutLengthUnit::Stretch:
|
||||
case UILayoutLengthUnit::Auto:
|
||||
default:
|
||||
resolved = desiredContentExtent;
|
||||
break;
|
||||
}
|
||||
|
||||
float effectiveMaxExtent = maxExtent;
|
||||
if (IsFiniteExtent(availableExtent)) {
|
||||
effectiveMaxExtent = IsFiniteExtent(effectiveMaxExtent)
|
||||
? (std::min)(effectiveMaxExtent, availableExtent)
|
||||
: availableExtent;
|
||||
}
|
||||
|
||||
return ClampExtent(resolved, minExtent, effectiveMaxExtent);
|
||||
}
|
||||
|
||||
inline UISize MeasureItemBaseSize(const UILayoutItem& item, const UILayoutConstraints& constraints) {
|
||||
UISize measuredSize = {};
|
||||
measuredSize.width = ResolveMeasuredExtent(
|
||||
item.width,
|
||||
item.desiredContentSize.width,
|
||||
item.minSize.width,
|
||||
item.maxSize.width,
|
||||
constraints.maxSize.width);
|
||||
measuredSize.height = ResolveMeasuredExtent(
|
||||
item.height,
|
||||
item.desiredContentSize.height,
|
||||
item.minSize.height,
|
||||
item.maxSize.height,
|
||||
constraints.maxSize.height);
|
||||
return measuredSize;
|
||||
}
|
||||
|
||||
inline float GetMainExtent(const UISize& size, UILayoutAxis axis) {
|
||||
return axis == UILayoutAxis::Horizontal ? size.width : size.height;
|
||||
}
|
||||
|
||||
inline float GetCrossExtent(const UISize& size, UILayoutAxis axis) {
|
||||
return axis == UILayoutAxis::Horizontal ? size.height : size.width;
|
||||
}
|
||||
|
||||
inline float GetLeadingMargin(const UILayoutThickness& thickness, UILayoutAxis axis) {
|
||||
return axis == UILayoutAxis::Horizontal ? thickness.left : thickness.top;
|
||||
}
|
||||
|
||||
inline float GetTrailingMargin(const UILayoutThickness& thickness, UILayoutAxis axis) {
|
||||
return axis == UILayoutAxis::Horizontal ? thickness.right : thickness.bottom;
|
||||
}
|
||||
|
||||
inline float GetMainMargin(const UILayoutThickness& thickness, UILayoutAxis axis) {
|
||||
return GetLeadingMargin(thickness, axis) + GetTrailingMargin(thickness, axis);
|
||||
}
|
||||
|
||||
inline float GetCrossMargin(const UILayoutThickness& thickness, UILayoutAxis axis) {
|
||||
return axis == UILayoutAxis::Horizontal
|
||||
? thickness.top + thickness.bottom
|
||||
: thickness.left + thickness.right;
|
||||
}
|
||||
|
||||
inline UILayoutLength GetMainLength(const UILayoutItem& item, UILayoutAxis axis) {
|
||||
return axis == UILayoutAxis::Horizontal ? item.width : item.height;
|
||||
}
|
||||
|
||||
inline UILayoutLength GetCrossLength(const UILayoutItem& item, UILayoutAxis axis) {
|
||||
return axis == UILayoutAxis::Horizontal ? item.height : item.width;
|
||||
}
|
||||
|
||||
inline float GetMinMainExtent(const UILayoutItem& item, UILayoutAxis axis) {
|
||||
return axis == UILayoutAxis::Horizontal ? item.minSize.width : item.minSize.height;
|
||||
}
|
||||
|
||||
inline float GetMinCrossExtent(const UILayoutItem& item, UILayoutAxis axis) {
|
||||
return axis == UILayoutAxis::Horizontal ? item.minSize.height : item.minSize.width;
|
||||
}
|
||||
|
||||
inline float GetMaxMainExtent(const UILayoutItem& item, UILayoutAxis axis) {
|
||||
return axis == UILayoutAxis::Horizontal ? item.maxSize.width : item.maxSize.height;
|
||||
}
|
||||
|
||||
inline float GetMaxCrossExtent(const UILayoutItem& item, UILayoutAxis axis) {
|
||||
return axis == UILayoutAxis::Horizontal ? item.maxSize.height : item.maxSize.width;
|
||||
}
|
||||
|
||||
inline UILayoutAlignment GetCrossAlignment(const UILayoutItem& item, UILayoutAxis axis) {
|
||||
return axis == UILayoutAxis::Horizontal ? item.verticalAlignment : item.horizontalAlignment;
|
||||
}
|
||||
|
||||
inline UILayoutAlignment GetHorizontalAlignment(const UILayoutItem& item) {
|
||||
return item.horizontalAlignment;
|
||||
}
|
||||
|
||||
inline UILayoutAlignment GetVerticalAlignment(const UILayoutItem& item) {
|
||||
return item.verticalAlignment;
|
||||
}
|
||||
|
||||
inline float ComputeAlignmentOffset(
|
||||
UILayoutAlignment alignment,
|
||||
float availableExtent,
|
||||
float childExtent) {
|
||||
const float remainingExtent = (std::max)(0.0f, availableExtent - childExtent);
|
||||
switch (alignment) {
|
||||
case UILayoutAlignment::Center:
|
||||
return remainingExtent * 0.5f;
|
||||
case UILayoutAlignment::End:
|
||||
return remainingExtent;
|
||||
case UILayoutAlignment::Start:
|
||||
case UILayoutAlignment::Stretch:
|
||||
default:
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
inline float ResolveArrangedCrossExtent(
|
||||
const UILayoutItem& item,
|
||||
UILayoutAxis axis,
|
||||
float measuredCrossExtent,
|
||||
float availableCrossExtent) {
|
||||
const UILayoutLength crossLength = GetCrossLength(item, axis);
|
||||
const UILayoutAlignment crossAlignment = GetCrossAlignment(item, axis);
|
||||
float resolvedCrossExtent = measuredCrossExtent;
|
||||
|
||||
if ((crossLength.IsStretch() || crossAlignment == UILayoutAlignment::Stretch) &&
|
||||
IsFiniteExtent(availableCrossExtent)) {
|
||||
resolvedCrossExtent = availableCrossExtent;
|
||||
}
|
||||
|
||||
float effectiveMaxExtent = GetMaxCrossExtent(item, axis);
|
||||
if (IsFiniteExtent(availableCrossExtent)) {
|
||||
effectiveMaxExtent = IsFiniteExtent(effectiveMaxExtent)
|
||||
? (std::min)(effectiveMaxExtent, availableCrossExtent)
|
||||
: availableCrossExtent;
|
||||
}
|
||||
|
||||
return ClampExtent(resolvedCrossExtent, GetMinCrossExtent(item, axis), effectiveMaxExtent);
|
||||
}
|
||||
|
||||
inline float ResolveArrangedMainExtent(
|
||||
const UILayoutItem& item,
|
||||
UILayoutAxis axis,
|
||||
float measuredMainExtent,
|
||||
float extraMainExtent,
|
||||
float availableMainExtent) {
|
||||
float resolvedMainExtent = measuredMainExtent;
|
||||
if (GetMainLength(item, axis).IsStretch()) {
|
||||
resolvedMainExtent += extraMainExtent;
|
||||
}
|
||||
|
||||
float effectiveMaxExtent = GetMaxMainExtent(item, axis);
|
||||
if (IsFiniteExtent(availableMainExtent)) {
|
||||
effectiveMaxExtent = IsFiniteExtent(effectiveMaxExtent)
|
||||
? (std::min)(effectiveMaxExtent, availableMainExtent)
|
||||
: availableMainExtent;
|
||||
}
|
||||
|
||||
return ClampExtent(resolvedMainExtent, GetMinMainExtent(item, axis), effectiveMaxExtent);
|
||||
}
|
||||
|
||||
} // namespace Detail
|
||||
|
||||
inline UILayoutPassResult MeasureStackLayout(
|
||||
const UIStackLayoutOptions& options,
|
||||
const std::vector<UILayoutItem>& items,
|
||||
const UILayoutConstraints& constraints = UILayoutConstraints::Unbounded()) {
|
||||
UILayoutPassResult result = {};
|
||||
result.children.resize(items.size());
|
||||
|
||||
const UISize innerMaxSize = Detail::DeflateSize(constraints.maxSize, options.padding);
|
||||
|
||||
float accumulatedMainExtent = 0.0f;
|
||||
float maxCrossExtent = 0.0f;
|
||||
std::size_t visibleCount = 0;
|
||||
|
||||
for (std::size_t index = 0; index < items.size(); ++index) {
|
||||
const UILayoutItem& item = items[index];
|
||||
UILayoutChildResult& childResult = result.children[index];
|
||||
childResult.visible = item.visible;
|
||||
if (!item.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UILayoutConstraints childConstraints = {};
|
||||
childConstraints.maxSize = UISize(
|
||||
Detail::ReduceAvailableExtent(innerMaxSize.width, item.margin.Horizontal()),
|
||||
Detail::ReduceAvailableExtent(innerMaxSize.height, item.margin.Vertical()));
|
||||
childResult.measuredSize = Detail::MeasureItemBaseSize(item, childConstraints);
|
||||
|
||||
accumulatedMainExtent +=
|
||||
Detail::GetMainExtent(childResult.measuredSize, options.axis) +
|
||||
Detail::GetMainMargin(item.margin, options.axis);
|
||||
maxCrossExtent = (std::max)(
|
||||
maxCrossExtent,
|
||||
Detail::GetCrossExtent(childResult.measuredSize, options.axis) +
|
||||
Detail::GetCrossMargin(item.margin, options.axis));
|
||||
++visibleCount;
|
||||
}
|
||||
|
||||
if (visibleCount > 1u) {
|
||||
accumulatedMainExtent += options.spacing * static_cast<float>(visibleCount - 1u);
|
||||
}
|
||||
|
||||
if (options.axis == UILayoutAxis::Horizontal) {
|
||||
result.desiredSize.width = accumulatedMainExtent + options.padding.Horizontal();
|
||||
result.desiredSize.height = maxCrossExtent + options.padding.Vertical();
|
||||
} else {
|
||||
result.desiredSize.width = maxCrossExtent + options.padding.Horizontal();
|
||||
result.desiredSize.height = accumulatedMainExtent + options.padding.Vertical();
|
||||
}
|
||||
|
||||
result.desiredSize.width = Detail::ClampExtent(
|
||||
result.desiredSize.width,
|
||||
constraints.minSize.width,
|
||||
constraints.maxSize.width);
|
||||
result.desiredSize.height = Detail::ClampExtent(
|
||||
result.desiredSize.height,
|
||||
constraints.minSize.height,
|
||||
constraints.maxSize.height);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline UILayoutPassResult ArrangeStackLayout(
|
||||
const UIStackLayoutOptions& options,
|
||||
const std::vector<UILayoutItem>& items,
|
||||
const UIRect& bounds) {
|
||||
UILayoutPassResult result =
|
||||
MeasureStackLayout(options, items, UILayoutConstraints::Bounded(bounds.width, bounds.height));
|
||||
|
||||
const UIRect innerBounds = Detail::DeflateRect(bounds, options.padding);
|
||||
const float innerMainExtent = options.axis == UILayoutAxis::Horizontal ? innerBounds.width : innerBounds.height;
|
||||
const float innerCrossExtent = options.axis == UILayoutAxis::Horizontal ? innerBounds.height : innerBounds.width;
|
||||
|
||||
float totalBaseMainExtent = 0.0f;
|
||||
float totalStretchWeight = 0.0f;
|
||||
std::size_t visibleCount = 0u;
|
||||
for (std::size_t index = 0; index < items.size(); ++index) {
|
||||
const UILayoutItem& item = items[index];
|
||||
const UILayoutChildResult& childResult = result.children[index];
|
||||
if (!item.visible || !childResult.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
totalBaseMainExtent +=
|
||||
Detail::GetMainExtent(childResult.measuredSize, options.axis) +
|
||||
Detail::GetMainMargin(item.margin, options.axis);
|
||||
if (Detail::GetMainLength(item, options.axis).IsStretch()) {
|
||||
totalStretchWeight += Detail::GetMainLength(item, options.axis).value;
|
||||
}
|
||||
++visibleCount;
|
||||
}
|
||||
|
||||
if (visibleCount > 1u) {
|
||||
totalBaseMainExtent += options.spacing * static_cast<float>(visibleCount - 1u);
|
||||
}
|
||||
|
||||
const float remainingMainExtent = (std::max)(0.0f, innerMainExtent - totalBaseMainExtent);
|
||||
float mainCursor = options.axis == UILayoutAxis::Horizontal ? innerBounds.x : innerBounds.y;
|
||||
|
||||
for (std::size_t index = 0; index < items.size(); ++index) {
|
||||
const UILayoutItem& item = items[index];
|
||||
UILayoutChildResult& childResult = result.children[index];
|
||||
if (!item.visible || !childResult.visible) {
|
||||
childResult.arrangedRect = UIRect(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
const float stretchWeight = Detail::GetMainLength(item, options.axis).IsStretch()
|
||||
? Detail::GetMainLength(item, options.axis).value
|
||||
: 0.0f;
|
||||
const float allocatedExtraMainExtent =
|
||||
totalStretchWeight > 0.0f
|
||||
? remainingMainExtent * (stretchWeight / totalStretchWeight)
|
||||
: 0.0f;
|
||||
|
||||
const float availableMainExtent = Detail::ReduceAvailableExtent(
|
||||
innerMainExtent,
|
||||
Detail::GetMainMargin(item.margin, options.axis));
|
||||
const float resolvedMainExtent = Detail::ResolveArrangedMainExtent(
|
||||
item,
|
||||
options.axis,
|
||||
Detail::GetMainExtent(childResult.measuredSize, options.axis),
|
||||
allocatedExtraMainExtent,
|
||||
availableMainExtent);
|
||||
|
||||
const float availableCrossExtent = Detail::ReduceAvailableExtent(
|
||||
innerCrossExtent,
|
||||
Detail::GetCrossMargin(item.margin, options.axis));
|
||||
const float resolvedCrossExtent = Detail::ResolveArrangedCrossExtent(
|
||||
item,
|
||||
options.axis,
|
||||
Detail::GetCrossExtent(childResult.measuredSize, options.axis),
|
||||
availableCrossExtent);
|
||||
|
||||
const float crossOrigin = options.axis == UILayoutAxis::Horizontal
|
||||
? innerBounds.y + item.margin.top
|
||||
: innerBounds.x + item.margin.left;
|
||||
const float crossOffset = Detail::ComputeAlignmentOffset(
|
||||
Detail::GetCrossAlignment(item, options.axis),
|
||||
availableCrossExtent,
|
||||
resolvedCrossExtent);
|
||||
|
||||
const float mainOrigin = mainCursor + Detail::GetLeadingMargin(item.margin, options.axis);
|
||||
if (options.axis == UILayoutAxis::Horizontal) {
|
||||
childResult.arrangedRect = UIRect(
|
||||
mainOrigin,
|
||||
crossOrigin + crossOffset,
|
||||
resolvedMainExtent,
|
||||
resolvedCrossExtent);
|
||||
} else {
|
||||
childResult.arrangedRect = UIRect(
|
||||
crossOrigin + crossOffset,
|
||||
mainOrigin,
|
||||
resolvedCrossExtent,
|
||||
resolvedMainExtent);
|
||||
}
|
||||
|
||||
mainCursor +=
|
||||
Detail::GetLeadingMargin(item.margin, options.axis) +
|
||||
resolvedMainExtent +
|
||||
Detail::GetTrailingMargin(item.margin, options.axis) +
|
||||
options.spacing;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline UILayoutPassResult MeasureOverlayLayout(
|
||||
const UIOverlayLayoutOptions& options,
|
||||
const std::vector<UILayoutItem>& items,
|
||||
const UILayoutConstraints& constraints = UILayoutConstraints::Unbounded()) {
|
||||
UILayoutPassResult result = {};
|
||||
result.children.resize(items.size());
|
||||
|
||||
const UISize innerMaxSize = Detail::DeflateSize(constraints.maxSize, options.padding);
|
||||
|
||||
float desiredWidth = 0.0f;
|
||||
float desiredHeight = 0.0f;
|
||||
for (std::size_t index = 0; index < items.size(); ++index) {
|
||||
const UILayoutItem& item = items[index];
|
||||
UILayoutChildResult& childResult = result.children[index];
|
||||
childResult.visible = item.visible;
|
||||
if (!item.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UILayoutConstraints childConstraints = {};
|
||||
childConstraints.maxSize = UISize(
|
||||
Detail::ReduceAvailableExtent(innerMaxSize.width, item.margin.Horizontal()),
|
||||
Detail::ReduceAvailableExtent(innerMaxSize.height, item.margin.Vertical()));
|
||||
childResult.measuredSize = Detail::MeasureItemBaseSize(item, childConstraints);
|
||||
|
||||
desiredWidth = (std::max)(desiredWidth, childResult.measuredSize.width + item.margin.Horizontal());
|
||||
desiredHeight = (std::max)(desiredHeight, childResult.measuredSize.height + item.margin.Vertical());
|
||||
}
|
||||
|
||||
result.desiredSize.width = desiredWidth + options.padding.Horizontal();
|
||||
result.desiredSize.height = desiredHeight + options.padding.Vertical();
|
||||
result.desiredSize.width = Detail::ClampExtent(
|
||||
result.desiredSize.width,
|
||||
constraints.minSize.width,
|
||||
constraints.maxSize.width);
|
||||
result.desiredSize.height = Detail::ClampExtent(
|
||||
result.desiredSize.height,
|
||||
constraints.minSize.height,
|
||||
constraints.maxSize.height);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline UILayoutPassResult ArrangeOverlayLayout(
|
||||
const UIOverlayLayoutOptions& options,
|
||||
const std::vector<UILayoutItem>& items,
|
||||
const UIRect& bounds) {
|
||||
UILayoutPassResult result =
|
||||
MeasureOverlayLayout(options, items, UILayoutConstraints::Bounded(bounds.width, bounds.height));
|
||||
|
||||
const UIRect innerBounds = Detail::DeflateRect(bounds, options.padding);
|
||||
for (std::size_t index = 0; index < items.size(); ++index) {
|
||||
const UILayoutItem& item = items[index];
|
||||
UILayoutChildResult& childResult = result.children[index];
|
||||
if (!item.visible || !childResult.visible) {
|
||||
childResult.arrangedRect = UIRect(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
continue;
|
||||
}
|
||||
|
||||
const float availableWidth = Detail::ReduceAvailableExtent(innerBounds.width, item.margin.Horizontal());
|
||||
const float availableHeight = Detail::ReduceAvailableExtent(innerBounds.height, item.margin.Vertical());
|
||||
|
||||
float resolvedWidth = childResult.measuredSize.width;
|
||||
if ((item.width.IsStretch() || item.horizontalAlignment == UILayoutAlignment::Stretch) &&
|
||||
Detail::IsFiniteExtent(availableWidth)) {
|
||||
resolvedWidth = availableWidth;
|
||||
}
|
||||
resolvedWidth = Detail::ClampExtent(resolvedWidth, item.minSize.width, item.maxSize.width);
|
||||
if (Detail::IsFiniteExtent(availableWidth)) {
|
||||
resolvedWidth = (std::min)(resolvedWidth, availableWidth);
|
||||
}
|
||||
|
||||
float resolvedHeight = childResult.measuredSize.height;
|
||||
if ((item.height.IsStretch() || item.verticalAlignment == UILayoutAlignment::Stretch) &&
|
||||
Detail::IsFiniteExtent(availableHeight)) {
|
||||
resolvedHeight = availableHeight;
|
||||
}
|
||||
resolvedHeight = Detail::ClampExtent(resolvedHeight, item.minSize.height, item.maxSize.height);
|
||||
if (Detail::IsFiniteExtent(availableHeight)) {
|
||||
resolvedHeight = (std::min)(resolvedHeight, availableHeight);
|
||||
}
|
||||
|
||||
const float originX =
|
||||
innerBounds.x +
|
||||
item.margin.left +
|
||||
Detail::ComputeAlignmentOffset(item.horizontalAlignment, availableWidth, resolvedWidth);
|
||||
const float originY =
|
||||
innerBounds.y +
|
||||
item.margin.top +
|
||||
Detail::ComputeAlignmentOffset(item.verticalAlignment, availableHeight, resolvedHeight);
|
||||
childResult.arrangedRect = UIRect(originX, originY, resolvedWidth, resolvedHeight);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Layout
|
||||
} // namespace UI
|
||||
} // namespace XCEngine
|
||||
145
engine/include/XCEngine/UI/Layout/LayoutTypes.h
Normal file
145
engine/include/XCEngine/UI/Layout/LayoutTypes.h
Normal file
@@ -0,0 +1,145 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/Types.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
namespace Layout {
|
||||
|
||||
inline float GetUnboundedLayoutExtent() {
|
||||
return std::numeric_limits<float>::infinity();
|
||||
}
|
||||
|
||||
enum class UILayoutAxis : std::uint8_t {
|
||||
Horizontal = 0,
|
||||
Vertical
|
||||
};
|
||||
|
||||
enum class UILayoutLengthUnit : std::uint8_t {
|
||||
Auto = 0,
|
||||
Pixels,
|
||||
Stretch
|
||||
};
|
||||
|
||||
enum class UILayoutAlignment : std::uint8_t {
|
||||
Start = 0,
|
||||
Center,
|
||||
End,
|
||||
Stretch
|
||||
};
|
||||
|
||||
struct UILayoutLength {
|
||||
UILayoutLengthUnit unit = UILayoutLengthUnit::Auto;
|
||||
float value = 0.0f;
|
||||
|
||||
static UILayoutLength Auto() {
|
||||
return {};
|
||||
}
|
||||
|
||||
static UILayoutLength Pixels(float pixels) {
|
||||
UILayoutLength length = {};
|
||||
length.unit = UILayoutLengthUnit::Pixels;
|
||||
length.value = (std::max)(0.0f, pixels);
|
||||
return length;
|
||||
}
|
||||
|
||||
static UILayoutLength Stretch(float weight = 1.0f) {
|
||||
UILayoutLength length = {};
|
||||
length.unit = UILayoutLengthUnit::Stretch;
|
||||
length.value = (std::max)(0.0f, weight);
|
||||
return length;
|
||||
}
|
||||
|
||||
bool IsStretch() const {
|
||||
return unit == UILayoutLengthUnit::Stretch && value > 0.0f;
|
||||
}
|
||||
};
|
||||
|
||||
struct UILayoutThickness {
|
||||
float left = 0.0f;
|
||||
float top = 0.0f;
|
||||
float right = 0.0f;
|
||||
float bottom = 0.0f;
|
||||
|
||||
UILayoutThickness() = default;
|
||||
|
||||
UILayoutThickness(float leftValue, float topValue, float rightValue, float bottomValue)
|
||||
: left((std::max)(0.0f, leftValue))
|
||||
, top((std::max)(0.0f, topValue))
|
||||
, right((std::max)(0.0f, rightValue))
|
||||
, bottom((std::max)(0.0f, bottomValue)) {
|
||||
}
|
||||
|
||||
static UILayoutThickness Uniform(float value) {
|
||||
return UILayoutThickness(value, value, value, value);
|
||||
}
|
||||
|
||||
static UILayoutThickness Symmetric(float horizontal, float vertical) {
|
||||
return UILayoutThickness(horizontal, vertical, horizontal, vertical);
|
||||
}
|
||||
|
||||
float Horizontal() const {
|
||||
return left + right;
|
||||
}
|
||||
|
||||
float Vertical() const {
|
||||
return top + bottom;
|
||||
}
|
||||
};
|
||||
|
||||
struct UILayoutConstraints {
|
||||
UISize minSize = UISize(0.0f, 0.0f);
|
||||
UISize maxSize = UISize(GetUnboundedLayoutExtent(), GetUnboundedLayoutExtent());
|
||||
|
||||
static UILayoutConstraints Unbounded() {
|
||||
return {};
|
||||
}
|
||||
|
||||
static UILayoutConstraints Bounded(float width, float height) {
|
||||
UILayoutConstraints constraints = {};
|
||||
constraints.maxSize = UISize((std::max)(0.0f, width), (std::max)(0.0f, height));
|
||||
return constraints;
|
||||
}
|
||||
|
||||
static UILayoutConstraints Tight(float width, float height) {
|
||||
UILayoutConstraints constraints = Bounded(width, height);
|
||||
constraints.minSize = constraints.maxSize;
|
||||
return constraints;
|
||||
}
|
||||
};
|
||||
|
||||
struct UILayoutItem {
|
||||
UISize desiredContentSize = UISize(0.0f, 0.0f);
|
||||
UILayoutLength width = UILayoutLength::Auto();
|
||||
UILayoutLength height = UILayoutLength::Auto();
|
||||
UISize minSize = UISize(0.0f, 0.0f);
|
||||
UISize maxSize = UISize(GetUnboundedLayoutExtent(), GetUnboundedLayoutExtent());
|
||||
UILayoutThickness margin = {};
|
||||
UILayoutAlignment horizontalAlignment = UILayoutAlignment::Start;
|
||||
UILayoutAlignment verticalAlignment = UILayoutAlignment::Start;
|
||||
bool visible = true;
|
||||
};
|
||||
|
||||
struct UIStackLayoutOptions {
|
||||
UILayoutAxis axis = UILayoutAxis::Vertical;
|
||||
float spacing = 0.0f;
|
||||
UILayoutThickness padding = {};
|
||||
};
|
||||
|
||||
struct UIOverlayLayoutOptions {
|
||||
UILayoutThickness padding = {};
|
||||
};
|
||||
|
||||
struct UILayoutChildResult {
|
||||
UISize measuredSize = UISize(0.0f, 0.0f);
|
||||
UIRect arrangedRect = UIRect(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
bool visible = false;
|
||||
};
|
||||
|
||||
} // namespace Layout
|
||||
} // namespace UI
|
||||
} // namespace XCEngine
|
||||
Reference in New Issue
Block a user