Files
XCEngine/editor/src/Shell/UIEditorShellCompose.cpp
2026-04-25 16:46:01 +08:00

401 lines
13 KiB
C++

#include <XCEditor/Shell/UIEditorShellCompose.h>
#include "Rendering/Assets/BuiltInIcons.h"
#include <algorithm>
namespace XCEngine::UI::Editor {
namespace {
using ::XCEngine::UI::UIColor;
using ::XCEngine::UI::UIPoint;
using ::XCEngine::UI::UIRect;
using Widgets::AppendUIEditorMenuBarBackground;
using Widgets::AppendUIEditorMenuBarForeground;
using Widgets::AppendUIEditorStatusBarBackground;
using Widgets::AppendUIEditorStatusBarForeground;
using Widgets::BuildUIEditorMenuBarLayout;
using Widgets::BuildUIEditorStatusBarLayout;
float ClampNonNegative(float value) {
return (std::max)(value, 0.0f);
}
UIRect InsetRect(const UIRect& rect, float inset) {
const float clampedInset = ClampNonNegative(inset);
const float insetX = (std::min)(clampedInset, rect.width * 0.5f);
const float insetY = (std::min)(clampedInset, rect.height * 0.5f);
return UIRect(
rect.x + insetX,
rect.y + insetY,
(std::max)(0.0f, rect.width - insetX * 2.0f),
(std::max)(0.0f, rect.height - insetY * 2.0f));
}
UIEditorShellToolbarLayout BuildUIEditorShellToolbarLayout(
const UIRect& bounds,
const std::vector<UIEditorShellToolbarButton>& buttons,
const UIEditorShellToolbarMetrics& metrics) {
UIEditorShellToolbarLayout layout = {};
layout.bounds = bounds;
if (buttons.empty() || bounds.width <= 0.0f || bounds.height <= 0.0f) {
return layout;
}
const float buttonWidth = ClampNonNegative(metrics.buttonWidth);
const float buttonHeight = ClampNonNegative(metrics.buttonHeight);
const float buttonGap = ClampNonNegative(metrics.buttonGap);
const float groupPaddingX = ClampNonNegative(metrics.groupPaddingX);
const float groupPaddingY = ClampNonNegative(metrics.groupPaddingY);
const float totalButtonWidth =
buttonWidth * static_cast<float>(buttons.size()) +
buttonGap * static_cast<float>((std::max)(std::ptrdiff_t(0), static_cast<std::ptrdiff_t>(buttons.size()) - 1));
const float groupWidth = totalButtonWidth + groupPaddingX * 2.0f;
const float groupHeight =
(std::min)(bounds.height, buttonHeight + groupPaddingY * 2.0f);
layout.groupRect = UIRect(
bounds.x + (std::max)(0.0f, (bounds.width - groupWidth) * 0.5f),
bounds.y + (std::max)(0.0f, (bounds.height - groupHeight) * 0.5f),
(std::min)(groupWidth, bounds.width),
groupHeight);
const float buttonY =
layout.groupRect.y + (std::max)(0.0f, (layout.groupRect.height - buttonHeight) * 0.5f);
float buttonX = layout.groupRect.x + groupPaddingX;
layout.buttonRects.reserve(buttons.size());
for (std::size_t index = 0; index < buttons.size(); ++index) {
layout.buttonRects.push_back(UIRect(buttonX, buttonY, buttonWidth, buttonHeight));
buttonX += buttonWidth + buttonGap;
}
return layout;
}
void AppendToolbarGlyph(
::XCEngine::UI::UIDrawList& drawList,
const UIRect& rect,
std::uint8_t iconKind,
const App::BuiltInIcons* icons,
const UIColor& tintColor) {
if (icons == nullptr) {
return;
}
const ::XCEngine::UI::UITextureHandle& texture = icons->Resolve(static_cast<App::BuiltInIconKind>(iconKind));
if (!texture.IsValid()) {
return;
}
const float inset = 2.0f;
const UIRect iconRect(
rect.x + inset,
rect.y + inset,
rect.width - inset * 2.0f,
rect.height - inset * 2.0f);
drawList.AddImage(iconRect, texture, tintColor);
}
void AppendUIEditorShellToolbar(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorShellToolbarLayout& layout,
const std::vector<UIEditorShellToolbarButton>& buttons,
const UIEditorShellToolbarPalette& palette,
const UIEditorShellToolbarMetrics& metrics,
const App::BuiltInIcons* builtInIcons) {
if (layout.bounds.width <= 0.0f || layout.bounds.height <= 0.0f) {
return;
}
drawList.AddFilledRect(layout.bounds, palette.barColor);
drawList.AddLine(
UIPoint(layout.bounds.x, layout.bounds.y + layout.bounds.height - 1.0f),
UIPoint(layout.bounds.x + layout.bounds.width, layout.bounds.y + layout.bounds.height - 1.0f),
palette.groupBorderColor,
metrics.borderThickness);
if (buttons.empty() || layout.groupRect.width <= 0.0f || layout.groupRect.height <= 0.0f) {
return;
}
const std::size_t buttonCount = (std::min)(buttons.size(), layout.buttonRects.size());
for (std::size_t index = 0; index < buttonCount; ++index) {
const UIRect& buttonRect = layout.buttonRects[index];
drawList.AddFilledRect(
buttonRect,
palette.buttonColor,
metrics.buttonCornerRounding);
drawList.AddRectOutline(
buttonRect,
palette.buttonBorderColor,
metrics.borderThickness,
metrics.buttonCornerRounding);
AppendToolbarGlyph(
drawList,
buttonRect,
buttons[index].iconKind,
builtInIcons,
palette.iconColor);
}
}
} // namespace
UIEditorShellComposeLayout BuildUIEditorShellComposeLayout(
const UIRect& bounds,
const std::vector<Widgets::UIEditorMenuBarItem>& menuBarItems,
const std::vector<Widgets::UIEditorStatusBarSegment>& statusSegments,
const UIEditorShellComposeMetrics& metrics) {
return BuildUIEditorShellComposeLayout(
bounds,
menuBarItems,
{},
statusSegments,
metrics);
}
UIEditorShellComposeLayout BuildUIEditorShellComposeLayout(
const UIRect& bounds,
const std::vector<Widgets::UIEditorMenuBarItem>& menuBarItems,
const std::vector<UIEditorShellToolbarButton>& toolbarButtons,
const std::vector<Widgets::UIEditorStatusBarSegment>& statusSegments,
const UIEditorShellComposeMetrics& metrics) {
UIEditorShellComposeLayout layout = {};
layout.bounds = UIRect(
bounds.x,
bounds.y,
ClampNonNegative(bounds.width),
ClampNonNegative(bounds.height));
layout.contentRect = InsetRect(layout.bounds, metrics.outerPadding);
const float menuBarHeight = ClampNonNegative(metrics.menuBarMetrics.barHeight);
const float toolbarHeight = ClampNonNegative(metrics.toolbarMetrics.barHeight);
const float statusBarHeight = ClampNonNegative(metrics.statusBarMetrics.barHeight);
const float sectionGap = ClampNonNegative(metrics.sectionGap);
const bool hasMenuBar = menuBarHeight > 0.0f && !menuBarItems.empty();
const bool hasToolbar = toolbarHeight > 0.0f && !toolbarButtons.empty();
const bool hasStatusBar = statusBarHeight > 0.0f && !statusSegments.empty();
float gapCount = 0.0f;
if (hasMenuBar) {
gapCount += 1.0f;
}
if (hasToolbar) {
gapCount += 1.0f;
}
if (hasStatusBar) {
gapCount += 1.0f;
}
const float availableHeight = layout.contentRect.height;
const float consumedHeight =
(hasMenuBar ? menuBarHeight : 0.0f) +
(hasToolbar ? toolbarHeight : 0.0f) +
(hasStatusBar ? statusBarHeight : 0.0f) +
sectionGap * gapCount;
const float workspaceHeight =
(std::max)(0.0f, availableHeight - consumedHeight);
float cursorY = layout.contentRect.y;
if (hasMenuBar) {
layout.menuBarRect = UIRect(
layout.contentRect.x,
cursorY,
layout.contentRect.width,
menuBarHeight);
cursorY += menuBarHeight + sectionGap;
}
if (hasToolbar) {
layout.toolbarRect = UIRect(
layout.contentRect.x,
cursorY,
layout.contentRect.width,
toolbarHeight);
cursorY += toolbarHeight + sectionGap;
}
layout.workspaceRect = UIRect(
layout.contentRect.x,
cursorY,
layout.contentRect.width,
workspaceHeight);
if (hasStatusBar) {
layout.statusBarRect = UIRect(
layout.contentRect.x,
layout.contentRect.y + layout.contentRect.height - statusBarHeight,
layout.contentRect.width,
statusBarHeight);
}
layout.menuBarLayout =
BuildUIEditorMenuBarLayout(layout.menuBarRect, menuBarItems, metrics.menuBarMetrics);
layout.toolbarLayout =
BuildUIEditorShellToolbarLayout(layout.toolbarRect, toolbarButtons, metrics.toolbarMetrics);
layout.statusBarLayout =
BuildUIEditorStatusBarLayout(layout.statusBarRect, statusSegments, metrics.statusBarMetrics);
return layout;
}
UIEditorShellComposeRequest ResolveUIEditorShellComposeRequest(
const UIRect& bounds,
const UIEditorShellComposeModel& model,
const UIEditorShellComposeMetrics& metrics) {
UIEditorShellComposeRequest request = {};
request.layout = BuildUIEditorShellComposeLayout(
bounds,
model.menuBarItems,
model.toolbarButtons,
model.statusSegments,
metrics);
return request;
}
UIEditorShellComposeFrame UpdateUIEditorShellCompose(
UIEditorShellComposeState& state,
const UIRect& bounds,
const UIEditorPanelRegistry& panelRegistry,
const UIEditorWorkspaceModel& workspace,
const UIEditorWorkspaceSession& session,
const UIEditorShellComposeModel& model,
const std::vector<::XCEngine::UI::UIInputEvent>& inputEvents,
const Widgets::UIEditorDockHostState& dockHostState,
const UIEditorShellComposeMetrics& metrics) {
UIEditorShellComposeFrame frame = {};
frame.layout = BuildUIEditorShellComposeLayout(
bounds,
model.menuBarItems,
model.toolbarButtons,
model.statusSegments,
metrics);
(void)UpdateUIEditorWorkspaceCompose(
state.workspaceState,
frame.layout.workspaceRect,
panelRegistry,
workspace,
session,
model.workspacePresentations,
inputEvents,
dockHostState,
metrics.dockHostMetrics,
metrics.viewportMetrics);
return frame;
}
void AppendUIEditorShellCompose(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorShellComposeFrame& frame,
const UIEditorWorkspaceComposeFrame& workspaceFrame,
const UIEditorShellComposeModel& model,
const UIEditorShellComposeState& state,
const UIEditorShellComposePalette& palette,
const UIEditorShellComposeMetrics& metrics,
const App::BuiltInIcons* builtInIcons) {
AppendUIEditorShellComposeBase(
drawList,
frame,
model,
state,
palette,
metrics,
builtInIcons);
AppendUIEditorWorkspaceCompose(
drawList,
workspaceFrame,
palette.dockHostPalette,
metrics.dockHostMetrics,
palette.viewportPalette,
metrics.viewportMetrics,
UIEditorWorkspaceComposeAppendOptions{ true });
AppendUIEditorShellComposeStatusBar(
drawList,
frame,
model,
state,
palette,
metrics);
}
void AppendUIEditorShellComposeBase(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorShellComposeFrame& frame,
const UIEditorShellComposeModel& model,
const UIEditorShellComposeState& state,
const UIEditorShellComposePalette& palette,
const UIEditorShellComposeMetrics& metrics,
const App::BuiltInIcons* builtInIcons) {
drawList.AddFilledRect(
frame.layout.bounds,
palette.surfaceColor,
metrics.surfaceCornerRounding);
drawList.AddRectOutline(
frame.layout.bounds,
palette.surfaceBorderColor,
1.0f,
metrics.surfaceCornerRounding);
AppendUIEditorMenuBarBackground(
drawList,
frame.layout.menuBarLayout,
model.menuBarItems,
state.menuBarState,
palette.menuBarPalette,
metrics.menuBarMetrics);
AppendUIEditorMenuBarForeground(
drawList,
frame.layout.menuBarLayout,
model.menuBarItems,
state.menuBarState,
palette.menuBarPalette,
metrics.menuBarMetrics);
AppendUIEditorShellToolbar(
drawList,
frame.layout.toolbarLayout,
model.toolbarButtons,
palette.toolbarPalette,
metrics.toolbarMetrics,
builtInIcons);
}
void AppendUIEditorShellComposeStatusBar(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorShellComposeFrame& frame,
const UIEditorShellComposeModel& model,
const UIEditorShellComposeState& state,
const UIEditorShellComposePalette& palette,
const UIEditorShellComposeMetrics& metrics) {
AppendUIEditorStatusBarBackground(
drawList,
frame.layout.statusBarLayout,
model.statusSegments,
state.statusBarState,
palette.statusBarPalette,
metrics.statusBarMetrics);
AppendUIEditorStatusBarForeground(
drawList,
frame.layout.statusBarLayout,
model.statusSegments,
state.statusBarState,
palette.statusBarPalette,
metrics.statusBarMetrics);
}
void AppendUIEditorShellComposeOverlay(
::XCEngine::UI::UIDrawList& drawList,
const UIEditorWorkspaceComposeFrame& workspaceFrame,
const UIEditorShellComposePalette& palette,
const UIEditorShellComposeMetrics& metrics) {
AppendUIEditorWorkspaceComposeOverlay(
drawList,
workspaceFrame,
palette.dockHostPalette,
metrics.dockHostMetrics);
}
} // namespace XCEngine::UI::Editor