#include #include "Rendering/Assets/BuiltInIcons.h" #include 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& 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(buttons.size()) + buttonGap * static_cast((std::max)(std::ptrdiff_t(0), static_cast(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(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& 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& menuBarItems, const std::vector& statusSegments, const UIEditorShellComposeMetrics& metrics) { return BuildUIEditorShellComposeLayout( bounds, menuBarItems, {}, statusSegments, metrics); } UIEditorShellComposeLayout BuildUIEditorShellComposeLayout( const UIRect& bounds, const std::vector& menuBarItems, const std::vector& toolbarButtons, const std::vector& 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