#include #include #include 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(frame.texture.width), static_cast(frame.texture.height)); } if (frame.requestedSize.width > 0.0f && frame.requestedSize.height > 0.0f) { return frame.requestedSize; } return fallback; } 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; } return palette.surfaceBorderColor; } float ResolveSurfaceBorderThickness( const UIEditorViewportSlotState& state, const UIEditorViewportSlotMetrics& metrics) { if (state.inputCaptured) { 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(item.label.size()) * metrics.estimatedGlyphWidth; } UIEditorViewportSlotLayout BuildUIEditorViewportSlotLayout( const UIRect& bounds, const UIEditorViewportSlotChrome& chrome, const UIEditorViewportSlotFrame& frame, const std::vector& toolItems, const std::vector& 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 = 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& toolItems, const std::vector& statusSegments, const UIEditorViewportSlotState& state, const UIEditorViewportSlotPalette& palette, const UIEditorViewportSlotMetrics& metrics) { drawList.AddFilledRect(layout.bounds, palette.frameColor, metrics.cornerRounding); drawList.AddRectOutline( layout.bounds, palette.borderColor, metrics.outerBorderThickness, metrics.cornerRounding); if (layout.hasTopBar) { drawList.AddFilledRect(layout.topBarRect, palette.topBarColor, metrics.cornerRounding); } drawList.AddFilledRect(layout.surfaceRect, palette.surfaceColor); 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& toolItems, const std::vector& 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, 13.0f); } if (!chrome.subtitle.empty()) { drawList.AddText( UIPoint(layout.subtitleRect.x, layout.topBarRect.y + metrics.subtitleInsetY), std::string(chrome.subtitle), palette.textSecondary, 10.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 + 3.0f), toolItems[index].label, toolItems[index].enabled ? palette.textPrimary : palette.textMuted, 11.0f); } } if (frame.hasTexture && frame.texture.IsValid()) { drawList.AddImage(layout.textureRect, frame.texture, palette.imageTint); } 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& toolItems, const std::vector& 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