关键节点
This commit is contained in:
340
editor/src/Viewport/UIEditorViewportInputBridge.cpp
Normal file
340
editor/src/Viewport/UIEditorViewportInputBridge.cpp
Normal 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
|
||||
129
editor/src/Viewport/UIEditorViewportShell.cpp
Normal file
129
editor/src/Viewport/UIEditorViewportShell.cpp
Normal 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
|
||||
511
editor/src/Viewport/UIEditorViewportSlot.cpp
Normal file
511
editor/src/Viewport/UIEditorViewportSlot.cpp
Normal 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
|
||||
Reference in New Issue
Block a user