Refactor XCUI editor module layout
This commit is contained in:
532
new_editor/src/Shell/UIEditorViewportSlot.cpp
Normal file
532
new_editor/src/Shell/UIEditorViewportSlot.cpp
Normal file
@@ -0,0 +1,532 @@
|
||||
#include <XCEditor/Shell/UIEditorViewportSlot.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine::UI::Editor::Widgets {
|
||||
|
||||
namespace {
|
||||
|
||||
using ::XCEngine::UI::UIColor;
|
||||
using ::XCEngine::UI::UIDrawList;
|
||||
using ::XCEngine::UI::UIPoint;
|
||||
using ::XCEngine::UI::UIRect;
|
||||
using ::XCEngine::UI::UISize;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
UIRect InsetRect(const UIRect& rect, float inset) {
|
||||
const float clampedInset = ClampNonNegative(inset);
|
||||
return UIRect(
|
||||
rect.x + clampedInset,
|
||||
rect.y + clampedInset,
|
||||
(std::max)(rect.width - clampedInset * 2.0f, 0.0f),
|
||||
(std::max)(rect.height - clampedInset * 2.0f, 0.0f));
|
||||
}
|
||||
|
||||
UISize ResolveFrameAspectSize(const UIEditorViewportSlotFrame& frame, const UISize& fallback) {
|
||||
if (frame.presentedSize.width > 0.0f && frame.presentedSize.height > 0.0f) {
|
||||
return frame.presentedSize;
|
||||
}
|
||||
|
||||
if (frame.texture.IsValid()) {
|
||||
return UISize(
|
||||
static_cast<float>(frame.texture.width),
|
||||
static_cast<float>(frame.texture.height));
|
||||
}
|
||||
|
||||
if (frame.requestedSize.width > 0.0f && frame.requestedSize.height > 0.0f) {
|
||||
return frame.requestedSize;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
UIRect FitRectToAspect(const UIRect& container, const UISize& size) {
|
||||
if (!HasArea(container) || size.width <= 0.0f || size.height <= 0.0f) {
|
||||
return container;
|
||||
}
|
||||
|
||||
const float containerAspect = container.width / container.height;
|
||||
const float frameAspect = size.width / size.height;
|
||||
if (frameAspect <= 0.0f) {
|
||||
return container;
|
||||
}
|
||||
|
||||
if (frameAspect >= containerAspect) {
|
||||
const float fittedHeight = container.width / frameAspect;
|
||||
return UIRect(
|
||||
container.x,
|
||||
container.y + (container.height - fittedHeight) * 0.5f,
|
||||
container.width,
|
||||
fittedHeight);
|
||||
}
|
||||
|
||||
const float fittedWidth = container.height * frameAspect;
|
||||
return UIRect(
|
||||
container.x + (container.width - fittedWidth) * 0.5f,
|
||||
container.y,
|
||||
fittedWidth,
|
||||
container.height);
|
||||
}
|
||||
|
||||
UIColor ResolveToolFillColor(
|
||||
const UIEditorViewportSlotToolItem& item,
|
||||
bool hovered,
|
||||
bool active,
|
||||
const UIEditorViewportSlotPalette& palette) {
|
||||
if (!item.enabled) {
|
||||
return palette.toolDisabledColor;
|
||||
}
|
||||
|
||||
if (active || item.selected) {
|
||||
return palette.toolSelectedColor;
|
||||
}
|
||||
|
||||
if (hovered) {
|
||||
return palette.toolHoveredColor;
|
||||
}
|
||||
|
||||
return palette.toolColor;
|
||||
}
|
||||
|
||||
UIColor ResolveSurfaceBorderColor(
|
||||
const UIEditorViewportSlotState& state,
|
||||
const UIEditorViewportSlotPalette& palette) {
|
||||
if (state.inputCaptured) {
|
||||
return palette.surfaceCapturedBorderColor;
|
||||
}
|
||||
|
||||
if (state.surfaceActive) {
|
||||
return palette.surfaceActiveBorderColor;
|
||||
}
|
||||
|
||||
if (state.surfaceHovered) {
|
||||
return palette.surfaceHoveredBorderColor;
|
||||
}
|
||||
|
||||
return palette.surfaceBorderColor;
|
||||
}
|
||||
|
||||
float ResolveSurfaceBorderThickness(
|
||||
const UIEditorViewportSlotState& state,
|
||||
const UIEditorViewportSlotMetrics& metrics) {
|
||||
if (state.inputCaptured || state.focused) {
|
||||
return metrics.focusedSurfaceBorderThickness;
|
||||
}
|
||||
|
||||
return metrics.surfaceBorderThickness;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
float ResolveUIEditorViewportSlotDesiredToolWidth(
|
||||
const UIEditorViewportSlotToolItem& item,
|
||||
const UIEditorViewportSlotMetrics& metrics) {
|
||||
if (item.desiredWidth > 0.0f) {
|
||||
return item.desiredWidth;
|
||||
}
|
||||
|
||||
return item.label.empty()
|
||||
? metrics.toolPaddingX * 2.0f
|
||||
: metrics.toolPaddingX * 2.0f +
|
||||
static_cast<float>(item.label.size()) * metrics.estimatedGlyphWidth;
|
||||
}
|
||||
|
||||
UIEditorViewportSlotLayout BuildUIEditorViewportSlotLayout(
|
||||
const UIRect& bounds,
|
||||
const UIEditorViewportSlotChrome& chrome,
|
||||
const UIEditorViewportSlotFrame& frame,
|
||||
const std::vector<UIEditorViewportSlotToolItem>& toolItems,
|
||||
const std::vector<UIEditorStatusBarSegment>& statusSegments,
|
||||
const UIEditorViewportSlotMetrics& metrics) {
|
||||
UIEditorViewportSlotLayout layout = {};
|
||||
layout.bounds = UIRect(
|
||||
bounds.x,
|
||||
bounds.y,
|
||||
ClampNonNegative(bounds.width),
|
||||
ClampNonNegative(bounds.height));
|
||||
layout.toolItemRects.resize(toolItems.size(), UIRect{});
|
||||
|
||||
float remainingHeight = layout.bounds.height;
|
||||
const float topBarHeight =
|
||||
chrome.showTopBar
|
||||
? (std::min)(ClampNonNegative(chrome.topBarHeight), remainingHeight)
|
||||
: 0.0f;
|
||||
remainingHeight = (std::max)(remainingHeight - topBarHeight, 0.0f);
|
||||
const float bottomBarHeight =
|
||||
chrome.showBottomBar
|
||||
? (std::min)(ClampNonNegative(chrome.bottomBarHeight), remainingHeight)
|
||||
: 0.0f;
|
||||
|
||||
layout.hasTopBar = topBarHeight > 0.0f;
|
||||
layout.hasBottomBar = bottomBarHeight > 0.0f;
|
||||
|
||||
if (layout.hasTopBar) {
|
||||
layout.topBarRect = UIRect(
|
||||
layout.bounds.x,
|
||||
layout.bounds.y,
|
||||
layout.bounds.width,
|
||||
topBarHeight);
|
||||
}
|
||||
|
||||
if (layout.hasBottomBar) {
|
||||
layout.bottomBarRect = UIRect(
|
||||
layout.bounds.x,
|
||||
layout.bounds.y + layout.bounds.height - bottomBarHeight,
|
||||
layout.bounds.width,
|
||||
bottomBarHeight);
|
||||
layout.statusBarLayout =
|
||||
BuildUIEditorStatusBarLayout(layout.bottomBarRect, statusSegments);
|
||||
}
|
||||
|
||||
const float surfaceTop = layout.hasTopBar
|
||||
? layout.topBarRect.y + layout.topBarRect.height
|
||||
: layout.bounds.y;
|
||||
const float surfaceBottom = layout.hasBottomBar
|
||||
? layout.bottomBarRect.y
|
||||
: layout.bounds.y + layout.bounds.height;
|
||||
layout.surfaceRect = UIRect(
|
||||
layout.bounds.x,
|
||||
surfaceTop,
|
||||
layout.bounds.width,
|
||||
(std::max)(surfaceBottom - surfaceTop, 0.0f));
|
||||
layout.inputRect = InsetRect(layout.surfaceRect, metrics.surfaceInset);
|
||||
layout.requestedSurfaceSize =
|
||||
UISize(layout.inputRect.width, layout.inputRect.height);
|
||||
layout.textureRect = frame.hasTexture
|
||||
? FitRectToAspect(
|
||||
layout.inputRect,
|
||||
ResolveFrameAspectSize(frame, layout.requestedSurfaceSize))
|
||||
: layout.inputRect;
|
||||
|
||||
if (!layout.hasTopBar) {
|
||||
return layout;
|
||||
}
|
||||
|
||||
const float toolbarTop = layout.topBarRect.y + metrics.topBarPaddingY;
|
||||
const float toolbarHeight =
|
||||
(std::max)(layout.topBarRect.height - metrics.topBarPaddingY * 2.0f, 0.0f);
|
||||
const float innerLeft = layout.topBarRect.x + metrics.topBarPaddingX;
|
||||
const float innerRight =
|
||||
layout.topBarRect.x + layout.topBarRect.width - metrics.topBarPaddingX;
|
||||
|
||||
float leadingCursor = innerLeft;
|
||||
float trailingCursor = innerRight;
|
||||
float leadingRight = innerLeft;
|
||||
float trailingLeft = innerRight;
|
||||
|
||||
for (std::size_t index = 0u; index < toolItems.size(); ++index) {
|
||||
const auto& item = toolItems[index];
|
||||
const float itemWidth = ResolveUIEditorViewportSlotDesiredToolWidth(item, metrics);
|
||||
if (item.slot != UIEditorViewportSlotToolSlot::Leading) {
|
||||
continue;
|
||||
}
|
||||
|
||||
layout.toolItemRects[index] = UIRect(
|
||||
leadingCursor,
|
||||
toolbarTop,
|
||||
itemWidth,
|
||||
toolbarHeight);
|
||||
leadingCursor += itemWidth + metrics.toolGap;
|
||||
leadingRight = layout.toolItemRects[index].x + layout.toolItemRects[index].width;
|
||||
}
|
||||
|
||||
for (std::size_t reverseIndex = toolItems.size(); reverseIndex > 0u; --reverseIndex) {
|
||||
const std::size_t index = reverseIndex - 1u;
|
||||
const auto& item = toolItems[index];
|
||||
const float itemWidth = ResolveUIEditorViewportSlotDesiredToolWidth(item, metrics);
|
||||
if (item.slot != UIEditorViewportSlotToolSlot::Trailing) {
|
||||
continue;
|
||||
}
|
||||
|
||||
trailingCursor -= itemWidth;
|
||||
layout.toolItemRects[index] = UIRect(
|
||||
trailingCursor,
|
||||
toolbarTop,
|
||||
itemWidth,
|
||||
toolbarHeight);
|
||||
trailingLeft = trailingCursor;
|
||||
trailingCursor -= metrics.toolGap;
|
||||
}
|
||||
|
||||
if (leadingRight > innerLeft) {
|
||||
layout.toolbarLeadingRect = UIRect(
|
||||
innerLeft,
|
||||
layout.topBarRect.y,
|
||||
leadingRight - innerLeft,
|
||||
layout.topBarRect.height);
|
||||
}
|
||||
|
||||
if (trailingLeft < innerRight) {
|
||||
layout.toolbarTrailingRect = UIRect(
|
||||
trailingLeft,
|
||||
layout.topBarRect.y,
|
||||
innerRight - trailingLeft,
|
||||
layout.topBarRect.height);
|
||||
}
|
||||
|
||||
float titleLeft = innerLeft;
|
||||
if (HasArea(layout.toolbarLeadingRect)) {
|
||||
titleLeft = layout.toolbarLeadingRect.x + layout.toolbarLeadingRect.width + metrics.titleGap;
|
||||
}
|
||||
|
||||
float titleRight = innerRight;
|
||||
if (HasArea(layout.toolbarTrailingRect)) {
|
||||
titleRight = layout.toolbarTrailingRect.x - metrics.titleGap;
|
||||
}
|
||||
|
||||
layout.titleRect = UIRect(
|
||||
titleLeft,
|
||||
layout.topBarRect.y,
|
||||
(std::max)(titleRight - titleLeft, 0.0f),
|
||||
layout.topBarRect.height);
|
||||
layout.subtitleRect = layout.titleRect;
|
||||
return layout;
|
||||
}
|
||||
|
||||
UIEditorViewportSlotHitTarget HitTestUIEditorViewportSlot(
|
||||
const UIEditorViewportSlotLayout& layout,
|
||||
const UIPoint& point) {
|
||||
if (!ContainsPoint(layout.bounds, point)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (layout.hasTopBar) {
|
||||
for (std::size_t index = 0u; index < layout.toolItemRects.size(); ++index) {
|
||||
if (HasArea(layout.toolItemRects[index]) &&
|
||||
ContainsPoint(layout.toolItemRects[index], point)) {
|
||||
return { UIEditorViewportSlotHitTargetKind::ToolItem, index };
|
||||
}
|
||||
}
|
||||
|
||||
if (HasArea(layout.titleRect) && ContainsPoint(layout.titleRect, point)) {
|
||||
return { UIEditorViewportSlotHitTargetKind::Title, UIEditorViewportSlotInvalidIndex };
|
||||
}
|
||||
|
||||
if (ContainsPoint(layout.topBarRect, point)) {
|
||||
return { UIEditorViewportSlotHitTargetKind::TopBar, UIEditorViewportSlotInvalidIndex };
|
||||
}
|
||||
}
|
||||
|
||||
if (layout.hasBottomBar && ContainsPoint(layout.bottomBarRect, point)) {
|
||||
const UIEditorStatusBarHitTarget hit =
|
||||
HitTestUIEditorStatusBar(layout.statusBarLayout, point);
|
||||
switch (hit.kind) {
|
||||
case UIEditorStatusBarHitTargetKind::Segment:
|
||||
return { UIEditorViewportSlotHitTargetKind::StatusSegment, hit.index };
|
||||
case UIEditorStatusBarHitTargetKind::Separator:
|
||||
return { UIEditorViewportSlotHitTargetKind::StatusSeparator, hit.index };
|
||||
case UIEditorStatusBarHitTargetKind::Background:
|
||||
return { UIEditorViewportSlotHitTargetKind::BottomBar, UIEditorViewportSlotInvalidIndex };
|
||||
case UIEditorStatusBarHitTargetKind::None:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ContainsPoint(layout.inputRect, point) || ContainsPoint(layout.surfaceRect, point)) {
|
||||
return { UIEditorViewportSlotHitTargetKind::Surface, UIEditorViewportSlotInvalidIndex };
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void AppendUIEditorViewportSlotBackground(
|
||||
UIDrawList& drawList,
|
||||
const UIEditorViewportSlotLayout& layout,
|
||||
const std::vector<UIEditorViewportSlotToolItem>& toolItems,
|
||||
const std::vector<UIEditorStatusBarSegment>& statusSegments,
|
||||
const UIEditorViewportSlotState& state,
|
||||
const UIEditorViewportSlotPalette& palette,
|
||||
const UIEditorViewportSlotMetrics& metrics) {
|
||||
drawList.AddFilledRect(layout.bounds, palette.frameColor, metrics.cornerRounding);
|
||||
drawList.AddRectOutline(
|
||||
layout.bounds,
|
||||
state.focused ? palette.focusedBorderColor : palette.borderColor,
|
||||
state.focused ? metrics.focusedBorderThickness : metrics.outerBorderThickness,
|
||||
metrics.cornerRounding);
|
||||
|
||||
if (layout.hasTopBar) {
|
||||
drawList.AddFilledRect(layout.topBarRect, palette.topBarColor, metrics.cornerRounding);
|
||||
}
|
||||
|
||||
drawList.AddFilledRect(layout.surfaceRect, palette.surfaceColor);
|
||||
|
||||
if (state.surfaceHovered) {
|
||||
drawList.AddFilledRect(layout.inputRect, palette.surfaceHoverOverlayColor);
|
||||
}
|
||||
if (state.surfaceActive) {
|
||||
drawList.AddFilledRect(layout.inputRect, palette.surfaceActiveOverlayColor);
|
||||
}
|
||||
if (state.inputCaptured) {
|
||||
drawList.AddFilledRect(layout.inputRect, palette.captureOverlayColor);
|
||||
}
|
||||
|
||||
drawList.AddRectOutline(
|
||||
layout.inputRect,
|
||||
ResolveSurfaceBorderColor(state, palette),
|
||||
ResolveSurfaceBorderThickness(state, metrics));
|
||||
|
||||
for (std::size_t index = 0u; index < toolItems.size(); ++index) {
|
||||
if (!HasArea(layout.toolItemRects[index])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const bool hovered = state.hoveredToolIndex == index;
|
||||
const bool active = state.activeToolIndex == index;
|
||||
drawList.AddFilledRect(
|
||||
layout.toolItemRects[index],
|
||||
ResolveToolFillColor(toolItems[index], hovered, active, palette),
|
||||
metrics.toolCornerRounding);
|
||||
drawList.AddRectOutline(
|
||||
layout.toolItemRects[index],
|
||||
palette.toolBorderColor,
|
||||
1.0f,
|
||||
metrics.toolCornerRounding);
|
||||
}
|
||||
|
||||
if (layout.hasBottomBar) {
|
||||
AppendUIEditorStatusBarBackground(
|
||||
drawList,
|
||||
layout.statusBarLayout,
|
||||
statusSegments,
|
||||
state.statusBarState,
|
||||
palette.statusBarPalette);
|
||||
}
|
||||
}
|
||||
|
||||
void AppendUIEditorViewportSlotForeground(
|
||||
UIDrawList& drawList,
|
||||
const UIEditorViewportSlotLayout& layout,
|
||||
const UIEditorViewportSlotChrome& chrome,
|
||||
const UIEditorViewportSlotFrame& frame,
|
||||
const std::vector<UIEditorViewportSlotToolItem>& toolItems,
|
||||
const std::vector<UIEditorStatusBarSegment>& statusSegments,
|
||||
const UIEditorViewportSlotState& state,
|
||||
const UIEditorViewportSlotPalette& palette,
|
||||
const UIEditorViewportSlotMetrics& metrics) {
|
||||
if (layout.hasTopBar) {
|
||||
if (!chrome.title.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(layout.titleRect.x, layout.topBarRect.y + metrics.titleInsetY),
|
||||
std::string(chrome.title),
|
||||
palette.textPrimary,
|
||||
15.0f);
|
||||
}
|
||||
|
||||
if (!chrome.subtitle.empty()) {
|
||||
drawList.AddText(
|
||||
UIPoint(layout.subtitleRect.x, layout.topBarRect.y + metrics.subtitleInsetY),
|
||||
std::string(chrome.subtitle),
|
||||
palette.textSecondary,
|
||||
11.0f);
|
||||
}
|
||||
|
||||
for (std::size_t index = 0u; index < toolItems.size(); ++index) {
|
||||
if (!HasArea(layout.toolItemRects[index]) || toolItems[index].label.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
drawList.AddText(
|
||||
UIPoint(
|
||||
layout.toolItemRects[index].x + metrics.toolPaddingX,
|
||||
layout.toolItemRects[index].y + 5.0f),
|
||||
toolItems[index].label,
|
||||
toolItems[index].enabled ? palette.textPrimary : palette.textMuted,
|
||||
12.0f);
|
||||
}
|
||||
}
|
||||
|
||||
const UISize frameSize = ResolveFrameAspectSize(frame, layout.requestedSurfaceSize);
|
||||
if (frame.hasTexture && frame.texture.IsValid()) {
|
||||
drawList.AddImage(layout.textureRect, frame.texture, palette.imageTint);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.inputRect.x + 14.0f, layout.inputRect.y + 14.0f),
|
||||
"Texture " +
|
||||
std::to_string(static_cast<int>(frameSize.width)) +
|
||||
"x" +
|
||||
std::to_string(static_cast<int>(frameSize.height)),
|
||||
palette.textMuted,
|
||||
12.0f);
|
||||
} else {
|
||||
const std::string statusText = frame.statusText.empty()
|
||||
? std::string("Viewport is waiting for frame")
|
||||
: frame.statusText;
|
||||
drawList.AddText(
|
||||
UIPoint(layout.inputRect.x + 16.0f, layout.inputRect.y + 18.0f),
|
||||
statusText,
|
||||
palette.textPrimary,
|
||||
14.0f);
|
||||
drawList.AddText(
|
||||
UIPoint(layout.inputRect.x + 16.0f, layout.inputRect.y + 42.0f),
|
||||
"Requested surface: " +
|
||||
std::to_string(static_cast<int>(layout.requestedSurfaceSize.width)) +
|
||||
"x" +
|
||||
std::to_string(static_cast<int>(layout.requestedSurfaceSize.height)),
|
||||
palette.textMuted,
|
||||
12.0f);
|
||||
}
|
||||
|
||||
if (layout.hasBottomBar) {
|
||||
AppendUIEditorStatusBarForeground(
|
||||
drawList,
|
||||
layout.statusBarLayout,
|
||||
statusSegments,
|
||||
state.statusBarState,
|
||||
palette.statusBarPalette);
|
||||
}
|
||||
}
|
||||
|
||||
void AppendUIEditorViewportSlot(
|
||||
UIDrawList& drawList,
|
||||
const UIRect& bounds,
|
||||
const UIEditorViewportSlotChrome& chrome,
|
||||
const UIEditorViewportSlotFrame& frame,
|
||||
const std::vector<UIEditorViewportSlotToolItem>& toolItems,
|
||||
const std::vector<UIEditorStatusBarSegment>& statusSegments,
|
||||
const UIEditorViewportSlotState& state,
|
||||
const UIEditorViewportSlotPalette& palette,
|
||||
const UIEditorViewportSlotMetrics& metrics) {
|
||||
const UIEditorViewportSlotLayout layout =
|
||||
BuildUIEditorViewportSlotLayout(
|
||||
bounds,
|
||||
chrome,
|
||||
frame,
|
||||
toolItems,
|
||||
statusSegments,
|
||||
metrics);
|
||||
AppendUIEditorViewportSlotBackground(
|
||||
drawList,
|
||||
layout,
|
||||
toolItems,
|
||||
statusSegments,
|
||||
state,
|
||||
palette,
|
||||
metrics);
|
||||
AppendUIEditorViewportSlotForeground(
|
||||
drawList,
|
||||
layout,
|
||||
chrome,
|
||||
frame,
|
||||
toolItems,
|
||||
statusSegments,
|
||||
state,
|
||||
palette,
|
||||
metrics);
|
||||
}
|
||||
|
||||
} // namespace XCEngine::UI::Editor::Widgets
|
||||
Reference in New Issue
Block a user