关键节点

This commit is contained in:
2026-04-25 16:46:01 +08:00
parent 6002d86a7e
commit ef41c44464
516 changed files with 6175 additions and 12401 deletions

View File

@@ -0,0 +1,340 @@
#include <XCEditor/Viewport/UIEditorViewportInputBridge.h>
#include <algorithm>
#include <utility>
namespace XCEngine::UI::Editor {
namespace {
using ::XCEngine::UI::UIInputEvent;
using ::XCEngine::UI::UIInputEventType;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIPointerButton;
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;
}
UIPoint ToLocalPoint(const UIRect& rect, const UIPoint& point) {
return UIPoint(point.x - rect.x, point.y - rect.y);
}
std::uint8_t ButtonMask(UIPointerButton button) {
switch (button) {
case UIPointerButton::Left: return 1u << 0u;
case UIPointerButton::Right: return 1u << 1u;
case UIPointerButton::Middle: return 1u << 2u;
case UIPointerButton::X1: return 1u << 3u;
case UIPointerButton::X2: return 1u << 4u;
case UIPointerButton::None:
default:
return 0u;
}
}
std::uint8_t ButtonMaskFromModifiers(const ::XCEngine::UI::UIInputModifiers& modifiers) {
std::uint8_t mask = 0u;
if (modifiers.leftMouse) {
mask |= ButtonMask(UIPointerButton::Left);
}
if (modifiers.rightMouse) {
mask |= ButtonMask(UIPointerButton::Right);
}
if (modifiers.middleMouse) {
mask |= ButtonMask(UIPointerButton::Middle);
}
if (modifiers.x1Mouse) {
mask |= ButtonMask(UIPointerButton::X1);
}
if (modifiers.x2Mouse) {
mask |= ButtonMask(UIPointerButton::X2);
}
return mask;
}
bool AnyPointerButtonsDown(const UIEditorViewportInputBridgeState& state) {
return state.pointerButtonsDownMask != 0u;
}
void ClearCapture(UIEditorViewportInputBridgeState& state) {
state.captured = false;
state.captureButton = UIPointerButton::None;
}
void ClearInputState(UIEditorViewportInputBridgeState& state) {
state.pressedKeys.clear();
state.pointerButtonsDownMask = 0u;
ClearCapture(state);
state.hovered = false;
}
void ApplyFocusRequest(
UIEditorViewportInputBridgeState& state,
UIEditorViewportInputBridgeFrame& frame,
const UIEditorViewportInputBridgeRequest& request) {
if (request.focusMode != UIEditorViewportInputBridgeFocusMode::External ||
state.focused == request.focused) {
return;
}
if (!request.focused) {
frame.focusLost = state.focused;
frame.captureEnded = frame.captureEnded || state.captured;
state.focused = false;
ClearInputState(state);
return;
}
state.focused = true;
frame.focusGained = true;
}
void UpdatePointerPosition(
UIEditorViewportInputBridgeState& state,
UIEditorViewportInputBridgeFrame& frame,
const UIRect& localRect,
const UIPoint& screenPosition,
const ::XCEngine::UI::UIInputModifiers& modifiers) {
if (state.hasPointerPosition) {
frame.pointerDelta.x += screenPosition.x - state.lastScreenPointerPosition.x;
frame.pointerDelta.y += screenPosition.y - state.lastScreenPointerPosition.y;
frame.pointerMoved =
frame.pointerMoved ||
screenPosition.x != state.lastScreenPointerPosition.x ||
screenPosition.y != state.lastScreenPointerPosition.y;
}
state.lastScreenPointerPosition = screenPosition;
state.lastLocalPointerPosition = ToLocalPoint(localRect, screenPosition);
state.hasPointerPosition = true;
state.modifiers = modifiers;
frame.screenPointerPosition = state.lastScreenPointerPosition;
frame.localPointerPosition = state.lastLocalPointerPosition;
frame.modifiers = state.modifiers;
}
void ReconcilePointerCapture(
UIEditorViewportInputBridgeState& state,
UIEditorViewportInputBridgeFrame& frame) {
if (!state.captured) {
return;
}
const std::uint8_t captureMask = ButtonMask(state.captureButton);
if (captureMask == 0u ||
(state.pointerButtonsDownMask & captureMask) == 0u) {
ClearCapture(state);
frame.captureEnded = true;
}
}
void AppendPointerButtonTransition(
UIEditorViewportInputBridgeFrame& frame,
UIPointerButton button,
bool pressed,
bool inside,
const UIPoint& screenPosition,
const UIPoint& localPointerPosition,
const ::XCEngine::UI::UIInputModifiers& modifiers) {
UIEditorViewportPointerButtonTransition transition = {};
transition.button = button;
transition.pressed = pressed;
transition.inside = inside;
transition.screenPosition = screenPosition;
transition.localPointerPosition = localPointerPosition;
transition.modifiers = modifiers;
frame.pointerButtonTransitions.push_back(std::move(transition));
}
} // namespace
bool IsUIEditorViewportInputBridgeKeyDown(
const UIEditorViewportInputBridgeState& state,
std::int32_t keyCode) {
return state.pressedKeys.contains(keyCode);
}
bool IsUIEditorViewportInputBridgePointerButtonDown(
const UIEditorViewportInputBridgeState& state,
UIPointerButton button) {
const std::uint8_t mask = ButtonMask(button);
return mask != 0u && (state.pointerButtonsDownMask & mask) != 0u;
}
UIEditorViewportInputBridgeFrame UpdateUIEditorViewportInputBridge(
UIEditorViewportInputBridgeState& state,
const UIRect& inputRect,
const std::vector<UIInputEvent>& events,
const UIEditorViewportInputBridgeConfig& config,
const UIEditorViewportInputBridgeRequest& request) {
return UpdateUIEditorViewportInputBridge(
state,
inputRect,
inputRect,
events,
config,
request);
}
UIEditorViewportInputBridgeFrame UpdateUIEditorViewportInputBridge(
UIEditorViewportInputBridgeState& state,
const UIRect& interactionRect,
const UIRect& localRect,
const std::vector<UIInputEvent>& events,
const UIEditorViewportInputBridgeConfig& config,
const UIEditorViewportInputBridgeRequest& request) {
UIEditorViewportInputBridgeFrame frame = {};
frame.hasPointerPosition = state.hasPointerPosition;
frame.screenPointerPosition = state.lastScreenPointerPosition;
frame.localPointerPosition = state.lastLocalPointerPosition;
frame.modifiers = state.modifiers;
ApplyFocusRequest(state, frame, request);
for (const UIInputEvent& event : events) {
const bool inside = ContainsPoint(interactionRect, event.position);
switch (event.type) {
case UIInputEventType::PointerEnter:
case UIInputEventType::PointerMove:
UpdatePointerPosition(state, frame, localRect, event.position, event.modifiers);
state.hovered = inside;
state.pointerButtonsDownMask = ButtonMaskFromModifiers(event.modifiers);
ReconcilePointerCapture(state, frame);
break;
case UIInputEventType::PointerLeave:
state.hovered = false;
state.lastScreenPointerPosition = event.position;
state.lastLocalPointerPosition = ToLocalPoint(localRect, event.position);
frame.screenPointerPosition = state.lastScreenPointerPosition;
frame.localPointerPosition = state.lastLocalPointerPosition;
frame.modifiers = state.modifiers;
break;
case UIInputEventType::PointerButtonDown:
UpdatePointerPosition(state, frame, localRect, event.position, event.modifiers);
state.hovered = inside;
state.pointerButtonsDownMask =
static_cast<std::uint8_t>(
ButtonMaskFromModifiers(event.modifiers) |
ButtonMask(event.pointerButton));
frame.changedPointerButton = event.pointerButton;
if (inside) {
frame.pointerPressedInside = true;
if (request.focusMode == UIEditorViewportInputBridgeFocusMode::SelfManaged &&
config.focusOnPointerDownInside &&
!state.focused) {
state.focused = true;
frame.focusGained = true;
}
if (config.capturePointerOnPointerDownInside && !state.captured) {
state.captured = true;
state.captureButton = event.pointerButton;
frame.captureStarted = true;
}
} else if (
request.focusMode == UIEditorViewportInputBridgeFocusMode::SelfManaged &&
config.clearFocusOnPointerDownOutside) {
if (state.focused) {
state.focused = false;
frame.focusLost = true;
}
if (state.captured) {
ClearCapture(state);
frame.captureEnded = true;
}
}
AppendPointerButtonTransition(
frame,
event.pointerButton,
true,
inside,
state.lastScreenPointerPosition,
state.lastLocalPointerPosition,
state.modifiers);
break;
case UIInputEventType::PointerButtonUp:
UpdatePointerPosition(state, frame, localRect, event.position, event.modifiers);
state.hovered = inside;
state.pointerButtonsDownMask = ButtonMaskFromModifiers(event.modifiers);
frame.changedPointerButton = event.pointerButton;
if (inside) {
frame.pointerReleasedInside = true;
}
AppendPointerButtonTransition(
frame,
event.pointerButton,
false,
inside,
state.lastScreenPointerPosition,
state.lastLocalPointerPosition,
state.modifiers);
ReconcilePointerCapture(state, frame);
break;
case UIInputEventType::PointerWheel:
UpdatePointerPosition(state, frame, localRect, event.position, event.modifiers);
state.hovered = inside;
state.pointerButtonsDownMask = ButtonMaskFromModifiers(event.modifiers);
ReconcilePointerCapture(state, frame);
if (inside || state.captured) {
frame.wheelDelta += event.wheelDelta;
}
break;
case UIInputEventType::KeyDown:
state.modifiers = event.modifiers;
frame.modifiers = state.modifiers;
if (state.focused) {
state.pressedKeys.insert(event.keyCode);
frame.pressedKeyCodes.push_back(event.keyCode);
}
break;
case UIInputEventType::KeyUp:
state.modifiers = event.modifiers;
frame.modifiers = state.modifiers;
if (state.focused) {
state.pressedKeys.erase(event.keyCode);
frame.releasedKeyCodes.push_back(event.keyCode);
}
break;
case UIInputEventType::Character:
if (state.focused) {
frame.characters.push_back(event.character);
}
break;
case UIInputEventType::FocusLost:
if (state.focused) {
frame.focusLost = true;
}
if (state.captured) {
frame.captureEnded = true;
}
state.focused = false;
ClearInputState(state);
break;
case UIInputEventType::FocusGained:
state.modifiers = event.modifiers;
frame.modifiers = state.modifiers;
break;
case UIInputEventType::None:
default:
break;
}
}
frame.hasPointerPosition = state.hasPointerPosition;
frame.pointerInside =
state.hasPointerPosition &&
ContainsPoint(interactionRect, state.lastScreenPointerPosition);
frame.hovered = state.hovered;
frame.focused = state.focused;
frame.captured = state.captured;
frame.screenPointerPosition = state.lastScreenPointerPosition;
frame.localPointerPosition = state.lastLocalPointerPosition;
frame.modifiers = state.modifiers;
return frame;
}
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,129 @@
#include <XCEditor/Viewport/UIEditorViewportShell.h>
namespace XCEngine::UI::Editor {
namespace {
using Widgets::BuildUIEditorViewportSlotLayout;
using Widgets::UIEditorViewportSlotFrame;
using Widgets::UIEditorViewportSlotLayout;
using Widgets::UIEditorViewportSlotState;
bool ContainsPoint(
const ::XCEngine::UI::UIRect& rect,
const ::XCEngine::UI::UIPoint& point) {
return point.x >= rect.x &&
point.x <= rect.x + rect.width &&
point.y >= rect.y &&
point.y <= rect.y + rect.height;
}
UIEditorViewportSlotLayout BuildViewportShellLayout(
const ::XCEngine::UI::UIRect& bounds,
const UIEditorViewportShellSpec& spec,
const UIEditorViewportSlotFrame& frame,
const Widgets::UIEditorViewportSlotMetrics& metrics) {
return BuildUIEditorViewportSlotLayout(
bounds,
spec.chrome,
frame,
spec.toolItems,
spec.statusSegments,
metrics);
}
UIEditorViewportSlotState BuildViewportShellSlotState(
const UIEditorViewportShellVisualState& visualState,
const UIEditorViewportInputBridgeFrame& inputFrame,
const UIEditorViewportSlotLayout& slotLayout) {
UIEditorViewportSlotState slotState = {};
slotState.focused = inputFrame.focused;
slotState.surfaceHovered =
inputFrame.hasPointerPosition &&
ContainsPoint(slotLayout.inputRect, inputFrame.screenPointerPosition);
slotState.surfaceActive = inputFrame.focused || inputFrame.captured;
slotState.inputCaptured = inputFrame.captured;
slotState.hoveredToolIndex = visualState.hoveredToolIndex;
slotState.activeToolIndex = visualState.activeToolIndex;
slotState.statusBarState = visualState.statusBarState;
slotState.statusBarState.focused =
slotState.statusBarState.focused || inputFrame.focused;
return slotState;
}
} // namespace
UIEditorViewportShellModel ResolveUIEditorViewportShellMeasuredModel(
const UIEditorViewportShellModel& model,
const Widgets::UIEditorViewportSlotMetrics& metrics,
const UIEditorTextMeasurer* textMeasurer) {
UIEditorViewportShellModel measuredModel = model;
for (Widgets::UIEditorViewportSlotToolItem& toolItem : measuredModel.spec.toolItems) {
toolItem.measuredLabelWidth = Widgets::ResolveUIEditorViewportSlotMeasuredToolLabelWidth(
toolItem,
metrics,
textMeasurer);
}
for (Widgets::UIEditorStatusBarSegment& segment : measuredModel.spec.statusSegments) {
segment.measuredLabelWidth = Widgets::ResolveUIEditorStatusBarMeasuredLabelWidth(
segment,
metrics.statusBarMetrics,
textMeasurer);
}
return measuredModel;
}
UIEditorViewportShellRequest ResolveUIEditorViewportShellRequest(
const ::XCEngine::UI::UIRect& bounds,
const UIEditorViewportShellSpec& spec,
const Widgets::UIEditorViewportSlotMetrics& metrics) {
UIEditorViewportShellRequest request = {};
request.slotLayout = BuildViewportShellLayout(bounds, spec, {}, metrics);
request.requestedViewportSize = request.slotLayout.requestedSurfaceSize;
return request;
}
UIEditorViewportShellFrame UpdateUIEditorViewportShell(
UIEditorViewportShellState& state,
const UIEditorViewportShellRequest& request,
const UIEditorViewportShellModel& model,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const UIEditorViewportInputBridgeRequest& inputRequest) {
UIEditorViewportShellFrame frame = {};
frame.slotLayout = request.slotLayout;
frame.requestedViewportSize = request.requestedViewportSize;
frame.inputFrame = UpdateUIEditorViewportInputBridge(
state.inputBridgeState,
frame.slotLayout.bounds,
frame.slotLayout.inputRect,
inputEvents,
model.spec.inputBridgeConfig,
inputRequest);
frame.slotState =
BuildViewportShellSlotState(
model.spec.visualState,
frame.inputFrame,
frame.slotLayout);
return frame;
}
UIEditorViewportShellFrame UpdateUIEditorViewportShell(
UIEditorViewportShellState& state,
const ::XCEngine::UI::UIRect& bounds,
const UIEditorViewportShellModel& model,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const Widgets::UIEditorViewportSlotMetrics& metrics,
const UIEditorViewportInputBridgeRequest& inputRequest) {
const UIEditorViewportShellRequest request =
ResolveUIEditorViewportShellRequest(bounds, model.spec, metrics);
return UpdateUIEditorViewportShell(
state,
request,
model,
inputEvents,
inputRequest);
}
} // namespace XCEngine::UI::Editor

View File

@@ -0,0 +1,511 @@
#include <XCEditor/Viewport/UIEditorViewportSlot.h>
#include <XCEditor/Foundation/UIEditorTextLayout.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;
}
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;
}
float ResolveCenteredTextTop(
const UIRect& rect,
float fontSize,
float insetY) {
return rect.y + (std::max)(0.0f, (rect.height - fontSize) * 0.5f) + insetY;
}
} // namespace
float ResolveUIEditorViewportSlotMeasuredToolLabelWidth(
const UIEditorViewportSlotToolItem& item,
const UIEditorViewportSlotMetrics& metrics,
const UIEditorTextMeasurer* textMeasurer) {
return ResolveUIEditorMeasuredTextWidth(
item.label,
item.measuredLabelWidth,
metrics.toolLabelFontSize,
metrics.estimatedGlyphWidth,
textMeasurer);
}
float ResolveUIEditorViewportSlotDesiredToolWidth(
const UIEditorViewportSlotToolItem& item,
const UIEditorViewportSlotMetrics& metrics) {
return item.label.empty()
? metrics.toolPaddingX * 2.0f
: metrics.toolPaddingX * 2.0f +
ResolveUIEditorViewportSlotMeasuredToolLabelWidth(item, metrics);
}
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,
metrics.statusBarMetrics);
}
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<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,
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,
metrics.statusBarMetrics);
}
}
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,
const UIEditorViewportSlotForegroundAppendOptions& options) {
if (layout.hasTopBar) {
if (!chrome.title.empty()) {
drawList.AddText(
UIPoint(
layout.titleRect.x,
ResolveCenteredTextTop(
layout.topBarRect,
metrics.titleFontSize,
metrics.titleInsetY)),
chrome.title,
palette.textPrimary,
metrics.titleFontSize);
}
if (!chrome.subtitle.empty()) {
drawList.AddText(
UIPoint(
layout.subtitleRect.x,
ResolveCenteredTextTop(
layout.topBarRect,
metrics.subtitleFontSize,
metrics.subtitleInsetY)),
chrome.subtitle,
palette.textSecondary,
metrics.subtitleFontSize);
}
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,
ResolveCenteredTextTop(
layout.toolItemRects[index],
metrics.toolLabelFontSize,
metrics.toolLabelInsetY)),
toolItems[index].label,
toolItems[index].enabled ? palette.textPrimary : palette.textMuted,
metrics.toolLabelFontSize);
}
}
if (options.includeSurfaceTexture &&
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,
metrics.statusBarMetrics);
}
}
void AppendUIEditorViewportSlotSurfaceTexture(
UIDrawList& drawList,
const UIEditorViewportSlotLayout& layout,
const UIEditorViewportSlotFrame& frame,
const UIEditorViewportSlotPalette& palette) {
if (!frame.hasTexture || !frame.texture.IsValid()) {
return;
}
drawList.AddImage(layout.textureRect, frame.texture, palette.imageTint);
}
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