Files
XCEngine/new_editor/src/Widgets/UIEditorStatusBar.cpp

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