280 lines
9.4 KiB
C++
280 lines
9.4 KiB
C++
#include <XCEditor/Widgets/UIEditorStatusBar.h>
|
|
|
|
#include <algorithm>
|
|
|
|
namespace XCEngine::UI::Editor::Widgets {
|
|
|
|
namespace {
|
|
|
|
using ::XCEngine::UI::UIColor;
|
|
using ::XCEngine::UI::UIDrawList;
|
|
using ::XCEngine::UI::UIPoint;
|
|
using ::XCEngine::UI::UIRect;
|
|
|
|
bool ContainsPoint(const UIRect& rect, const UIPoint& point) {
|
|
return point.x >= rect.x &&
|
|
point.x <= rect.x + rect.width &&
|
|
point.y >= rect.y &&
|
|
point.y <= rect.y + rect.height;
|
|
}
|
|
|
|
bool HasArea(const UIRect& rect) {
|
|
return rect.width > 0.0f && rect.height > 0.0f;
|
|
}
|
|
|
|
float ClampNonNegative(float value) {
|
|
return (std::max)(value, 0.0f);
|
|
}
|
|
|
|
UIColor ResolveSegmentFillColor(
|
|
bool hovered,
|
|
bool active,
|
|
const UIEditorStatusBarPalette& palette) {
|
|
if (active) {
|
|
return palette.segmentActiveColor;
|
|
}
|
|
|
|
if (hovered) {
|
|
return palette.segmentHoveredColor;
|
|
}
|
|
|
|
return palette.segmentColor;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
float ResolveUIEditorStatusBarDesiredSegmentWidth(
|
|
const UIEditorStatusBarSegment& segment,
|
|
const UIEditorStatusBarMetrics& metrics) {
|
|
if (segment.desiredWidth > 0.0f) {
|
|
return segment.desiredWidth;
|
|
}
|
|
|
|
return segment.label.empty()
|
|
? metrics.segmentPaddingX * 2.0f
|
|
: metrics.segmentPaddingX * 2.0f +
|
|
static_cast<float>(segment.label.size()) * metrics.estimatedGlyphWidth;
|
|
}
|
|
|
|
UIEditorStatusBarLayout BuildUIEditorStatusBarLayout(
|
|
const UIRect& bounds,
|
|
const std::vector<UIEditorStatusBarSegment>& segments,
|
|
const UIEditorStatusBarMetrics& metrics) {
|
|
UIEditorStatusBarLayout layout = {};
|
|
layout.bounds = UIRect(
|
|
bounds.x,
|
|
bounds.y,
|
|
ClampNonNegative(bounds.width),
|
|
ClampNonNegative(bounds.height));
|
|
layout.segmentRects.resize(segments.size(), UIRect{});
|
|
layout.separatorRects.resize(segments.size(), UIRect{});
|
|
|
|
const float contentTop = layout.bounds.y;
|
|
const float contentHeight = layout.bounds.height;
|
|
const float leftStart = layout.bounds.x + metrics.outerPaddingX;
|
|
const float rightLimit = layout.bounds.x + layout.bounds.width - metrics.outerPaddingX;
|
|
|
|
float leadingCursor = leftStart;
|
|
float leadingRight = leftStart;
|
|
for (std::size_t index = 0u; index < segments.size(); ++index) {
|
|
const auto& segment = segments[index];
|
|
if (segment.slot != UIEditorStatusBarSlot::Leading) {
|
|
continue;
|
|
}
|
|
|
|
const float segmentWidth = ResolveUIEditorStatusBarDesiredSegmentWidth(segment, metrics);
|
|
layout.segmentRects[index] = UIRect(
|
|
leadingCursor,
|
|
contentTop,
|
|
segmentWidth,
|
|
contentHeight);
|
|
leadingCursor += segmentWidth;
|
|
leadingRight = leadingCursor;
|
|
|
|
if (segment.showSeparator) {
|
|
layout.separatorRects[index] = UIRect(
|
|
leadingCursor,
|
|
contentTop + metrics.separatorInsetY,
|
|
metrics.separatorWidth,
|
|
(std::max)(contentHeight - metrics.separatorInsetY * 2.0f, 0.0f));
|
|
leadingCursor += metrics.separatorWidth;
|
|
}
|
|
|
|
leadingCursor += metrics.segmentGap;
|
|
leadingRight = (std::max)(leadingRight, leadingCursor - metrics.segmentGap);
|
|
}
|
|
|
|
float trailingCursor = rightLimit;
|
|
float trailingLeft = rightLimit;
|
|
for (std::size_t reverseIndex = segments.size(); reverseIndex > 0u; --reverseIndex) {
|
|
const std::size_t index = reverseIndex - 1u;
|
|
const auto& segment = segments[index];
|
|
if (segment.slot != UIEditorStatusBarSlot::Trailing) {
|
|
continue;
|
|
}
|
|
|
|
const float segmentWidth = ResolveUIEditorStatusBarDesiredSegmentWidth(segment, metrics);
|
|
const bool hasSeparator = segment.showSeparator;
|
|
|
|
if (hasSeparator) {
|
|
trailingCursor -= metrics.separatorWidth;
|
|
layout.separatorRects[index] = UIRect(
|
|
trailingCursor,
|
|
contentTop + metrics.separatorInsetY,
|
|
metrics.separatorWidth,
|
|
(std::max)(contentHeight - metrics.separatorInsetY * 2.0f, 0.0f));
|
|
trailingCursor -= metrics.segmentGap;
|
|
}
|
|
|
|
trailingCursor -= segmentWidth;
|
|
layout.segmentRects[index] = UIRect(
|
|
trailingCursor,
|
|
contentTop,
|
|
segmentWidth,
|
|
contentHeight);
|
|
trailingCursor -= metrics.segmentGap;
|
|
trailingLeft = (std::min)(trailingLeft, layout.segmentRects[index].x);
|
|
}
|
|
|
|
const float leadingWidth =
|
|
leadingRight > leftStart ? leadingRight - leftStart : 0.0f;
|
|
layout.leadingSlotRect = UIRect(leftStart, contentTop, leadingWidth, contentHeight);
|
|
|
|
const float trailingWidth =
|
|
trailingLeft < rightLimit ? rightLimit - trailingLeft : 0.0f;
|
|
layout.trailingSlotRect = UIRect(trailingLeft, contentTop, trailingWidth, contentHeight);
|
|
|
|
if (HasArea(layout.leadingSlotRect) &&
|
|
HasArea(layout.trailingSlotRect) &&
|
|
layout.leadingSlotRect.x + layout.leadingSlotRect.width + metrics.slotGapMin >
|
|
layout.trailingSlotRect.x) {
|
|
const float overlap =
|
|
layout.leadingSlotRect.x + layout.leadingSlotRect.width + metrics.slotGapMin -
|
|
layout.trailingSlotRect.x;
|
|
layout.trailingSlotRect.x += overlap;
|
|
layout.trailingSlotRect.width = (std::max)(layout.trailingSlotRect.width - overlap, 0.0f);
|
|
}
|
|
|
|
return layout;
|
|
}
|
|
|
|
UIEditorStatusBarHitTarget HitTestUIEditorStatusBar(
|
|
const UIEditorStatusBarLayout& layout,
|
|
const UIPoint& point) {
|
|
if (!ContainsPoint(layout.bounds, point)) {
|
|
return {};
|
|
}
|
|
|
|
for (std::size_t index = 0u; index < layout.separatorRects.size(); ++index) {
|
|
if (HasArea(layout.separatorRects[index]) &&
|
|
ContainsPoint(layout.separatorRects[index], point)) {
|
|
return { UIEditorStatusBarHitTargetKind::Separator, index };
|
|
}
|
|
}
|
|
|
|
for (std::size_t index = 0u; index < layout.segmentRects.size(); ++index) {
|
|
if (HasArea(layout.segmentRects[index]) &&
|
|
ContainsPoint(layout.segmentRects[index], point)) {
|
|
return { UIEditorStatusBarHitTargetKind::Segment, index };
|
|
}
|
|
}
|
|
|
|
return { UIEditorStatusBarHitTargetKind::Background, UIEditorStatusBarInvalidIndex };
|
|
}
|
|
|
|
UIColor ResolveUIEditorStatusBarTextColor(
|
|
UIEditorStatusBarTextTone tone,
|
|
const UIEditorStatusBarPalette& palette) {
|
|
switch (tone) {
|
|
case UIEditorStatusBarTextTone::Muted:
|
|
return palette.textMuted;
|
|
case UIEditorStatusBarTextTone::Accent:
|
|
return palette.textAccent;
|
|
case UIEditorStatusBarTextTone::Primary:
|
|
default:
|
|
return palette.textPrimary;
|
|
}
|
|
}
|
|
|
|
void AppendUIEditorStatusBarBackground(
|
|
UIDrawList& drawList,
|
|
const UIEditorStatusBarLayout& layout,
|
|
const std::vector<UIEditorStatusBarSegment>& segments,
|
|
const UIEditorStatusBarState& state,
|
|
const UIEditorStatusBarPalette& palette,
|
|
const UIEditorStatusBarMetrics& metrics) {
|
|
drawList.AddFilledRect(layout.bounds, palette.surfaceColor, metrics.cornerRounding);
|
|
drawList.AddRectOutline(
|
|
layout.bounds,
|
|
state.focused ? palette.focusedBorderColor : palette.borderColor,
|
|
state.focused ? metrics.focusedBorderThickness : metrics.borderThickness,
|
|
metrics.cornerRounding);
|
|
|
|
for (std::size_t index = 0u; index < segments.size(); ++index) {
|
|
if (!HasArea(layout.segmentRects[index])) {
|
|
continue;
|
|
}
|
|
|
|
const bool hovered = state.hoveredIndex == index;
|
|
const bool active = state.activeIndex == index;
|
|
if (!hovered && !active) {
|
|
continue;
|
|
}
|
|
|
|
drawList.AddFilledRect(
|
|
layout.segmentRects[index],
|
|
ResolveSegmentFillColor(hovered, active, palette),
|
|
6.0f);
|
|
drawList.AddRectOutline(
|
|
layout.segmentRects[index],
|
|
palette.segmentBorderColor,
|
|
1.0f,
|
|
6.0f);
|
|
}
|
|
|
|
for (const UIRect& separatorRect : layout.separatorRects) {
|
|
if (!HasArea(separatorRect)) {
|
|
continue;
|
|
}
|
|
|
|
drawList.AddFilledRect(separatorRect, palette.separatorColor);
|
|
}
|
|
}
|
|
|
|
void AppendUIEditorStatusBarForeground(
|
|
UIDrawList& drawList,
|
|
const UIEditorStatusBarLayout& layout,
|
|
const std::vector<UIEditorStatusBarSegment>& segments,
|
|
const UIEditorStatusBarState&,
|
|
const UIEditorStatusBarPalette& palette,
|
|
const UIEditorStatusBarMetrics& metrics) {
|
|
for (std::size_t index = 0u; index < segments.size(); ++index) {
|
|
if (!HasArea(layout.segmentRects[index]) || segments[index].label.empty()) {
|
|
continue;
|
|
}
|
|
|
|
drawList.AddText(
|
|
UIPoint(
|
|
layout.segmentRects[index].x + metrics.segmentPaddingX,
|
|
layout.segmentRects[index].y + metrics.segmentPaddingY),
|
|
segments[index].label,
|
|
ResolveUIEditorStatusBarTextColor(segments[index].tone, palette),
|
|
12.0f);
|
|
}
|
|
}
|
|
|
|
void AppendUIEditorStatusBar(
|
|
UIDrawList& drawList,
|
|
const UIRect& bounds,
|
|
const std::vector<UIEditorStatusBarSegment>& segments,
|
|
const UIEditorStatusBarState& state,
|
|
const UIEditorStatusBarPalette& palette,
|
|
const UIEditorStatusBarMetrics& metrics) {
|
|
const UIEditorStatusBarLayout layout =
|
|
BuildUIEditorStatusBarLayout(bounds, segments, metrics);
|
|
AppendUIEditorStatusBarBackground(drawList, layout, segments, state, palette, metrics);
|
|
AppendUIEditorStatusBarForeground(drawList, layout, segments, state, palette, metrics);
|
|
}
|
|
|
|
} // namespace XCEngine::UI::Editor::Widgets
|