#include "Actions/ActionRouting.h" #include "Actions/EditActionRouter.h" #include "Actions/MainMenuActionRouter.h" #include "MenuBar.h" #include "Core/IEditorContext.h" #include "Platform/Win32Utf8.h" #include "UI/UI.h" #include #include #include #include #include namespace XCEngine { namespace Editor { namespace { constexpr float kRunToolbarHeight = 40.0f; constexpr float kRunToolbarButtonExtent = 28.0f; constexpr float kRunToolbarButtonSpacing = 10.0f; constexpr float kRunToolbarIconInset = 5.0f; std::string BuildRunToolbarIconPath(const char* fileName) { const std::filesystem::path exeDir( Platform::Utf8ToWide(Platform::GetExecutableDirectoryUtf8())); const std::filesystem::path iconPath = (exeDir / L".." / L".." / L"resources" / L"Icons" / std::filesystem::path(Platform::Utf8ToWide(fileName))) .lexically_normal(); return Platform::WideToUtf8(iconPath.wstring()); } const std::string& GetRunToolbarIconPath(size_t index) { static const std::array kFileNames = { "play_button.png", "stop_button.png", "step_button.png" }; static std::array s_cachedPaths = {}; std::string& cachedPath = s_cachedPaths[index]; if (!cachedPath.empty()) { return cachedPath; } cachedPath = BuildRunToolbarIconPath(kFileNames[index]); return cachedPath; } bool DrawRunToolbarIconButton( const char* id, const Actions::ActionBinding& action, const std::string& iconPath, const char* fallbackGlyph) { ImGui::BeginDisabled(!action.enabled); ImGui::PushID(id); const ImVec2 buttonSize(kRunToolbarButtonExtent, kRunToolbarButtonExtent); const ImVec2 buttonMin = ImGui::GetCursorScreenPos(); const bool pressed = ImGui::InvisibleButton("##RunToolbarButton", buttonSize); const ImVec2 buttonMax(buttonMin.x + buttonSize.x, buttonMin.y + buttonSize.y); const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled); const bool held = ImGui::IsItemActive(); ImDrawList* drawList = ImGui::GetWindowDrawList(); if (drawList != nullptr) { const ImU32 fillColor = ImGui::GetColorU32( held ? UI::ToolbarButtonActiveColor() : hovered ? UI::ToolbarButtonHoveredColor(action.selected) : UI::ToolbarButtonColor(action.selected)); const ImU32 borderColor = IM_COL32(255, 255, 255, action.selected ? 56 : 28); drawList->AddRectFilled(buttonMin, buttonMax, fillColor, 5.0f); drawList->AddRect(buttonMin, buttonMax, borderColor, 5.0f, 0, 1.0f); const ImVec2 iconMin(buttonMin.x + kRunToolbarIconInset, buttonMin.y + kRunToolbarIconInset); const ImVec2 iconMax(buttonMax.x - kRunToolbarIconInset, buttonMax.y - kRunToolbarIconInset); if (!UI::DrawTextureAssetPreview(drawList, iconMin, iconMax, iconPath)) { const ImVec2 textSize = ImGui::CalcTextSize(fallbackGlyph); drawList->AddText( ImVec2( buttonMin.x + (buttonSize.x - textSize.x) * 0.5f, buttonMin.y + (buttonSize.y - textSize.y) * 0.5f), ImGui::GetColorU32(ImVec4(0.88f, 0.88f, 0.88f, 1.0f)), fallbackGlyph); } } if (hovered) { UI::BeginTitledTooltip(action.label.c_str()); if (!action.shortcutLabel.empty()) { ImGui::Text("Shortcut: %s", action.shortcutLabel.c_str()); } if (!action.enabled && action.label == "Step") { ImGui::TextUnformatted("Available while runtime is paused."); } UI::EndTitledTooltip(); } ImGui::PopID(); ImGui::EndDisabled(); return action.enabled && pressed; } } // namespace MenuBar::MenuBar() : Panel("MenuBar") {} void MenuBar::Render() { if (!m_context) { return; } Actions::HandleMenuBarShortcuts(*m_context); Actions::DrawMainMenuBar(*m_context, m_aboutPopup); RenderRunToolbar(); Actions::DrawMainMenuOverlays(m_context, m_aboutPopup); } void MenuBar::RenderRunToolbar() { ImGuiViewport* viewport = ImGui::GetMainViewport(); if (viewport == nullptr) { return; } constexpr ImGuiWindowFlags kWindowFlags = ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNavFocus; ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f, 6.0f)); ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f); ImGui::PushStyleColor(ImGuiCol_WindowBg, UI::ToolbarBackgroundColor()); const bool open = ImGui::BeginViewportSideBar("##MainRunToolbar", viewport, ImGuiDir_Up, kRunToolbarHeight, kWindowFlags); ImGui::PopStyleColor(); ImGui::PopStyleVar(3); if (open) { const Actions::ActionBinding playAction = Actions::MakeStartPlayModeAction(m_context->GetRuntimeMode(), m_context->GetSceneManager().HasActiveScene()); const Actions::ActionBinding stopAction = Actions::MakeStopPlayModeAction(m_context->GetRuntimeMode()); const Actions::ActionBinding stepAction = Actions::MakeStepPlayModeAction(m_context->GetRuntimeMode() == EditorRuntimeMode::Paused); const float totalWidth = kRunToolbarButtonExtent * 3.0f + kRunToolbarButtonSpacing * 2.0f; const float startX = ImGui::GetCursorPosX() + (std::max)(0.0f, (ImGui::GetContentRegionAvail().x - totalWidth) * 0.5f); const float startY = (std::max)(ImGui::GetCursorPosY(), (ImGui::GetWindowHeight() - kRunToolbarButtonExtent) * 0.5f); ImGui::SetCursorPos(ImVec2(startX, startY)); if (DrawRunToolbarIconButton("Play", playAction, GetRunToolbarIconPath(0), "P")) { Actions::RequestStartPlayMode(*m_context); } ImGui::SameLine(0.0f, kRunToolbarButtonSpacing); if (DrawRunToolbarIconButton("Stop", stopAction, GetRunToolbarIconPath(1), "S")) { Actions::RequestStopPlayMode(*m_context); } ImGui::SameLine(0.0f, kRunToolbarButtonSpacing); if (DrawRunToolbarIconButton("Step", stepAction, GetRunToolbarIconPath(2), ">")) { Actions::RequestStepPlayMode(*m_context); } UI::DrawCurrentWindowBottomBorder(); } ImGui::End(); } } }