2026-03-26 22:10:43 +08:00
|
|
|
#include "Actions/ActionRouting.h"
|
2026-03-26 22:31:22 +08:00
|
|
|
#include "Actions/EditActionRouter.h"
|
2026-03-27 00:15:38 +08:00
|
|
|
#include "Actions/MainMenuActionRouter.h"
|
2026-03-20 17:08:06 +08:00
|
|
|
#include "MenuBar.h"
|
2026-03-26 01:26:26 +08:00
|
|
|
#include "Core/IEditorContext.h"
|
2026-04-02 21:11:08 +08:00
|
|
|
#include "Platform/Win32Utf8.h"
|
|
|
|
|
#include "UI/UI.h"
|
2026-03-20 17:08:06 +08:00
|
|
|
#include <imgui.h>
|
2026-04-02 21:11:08 +08:00
|
|
|
#include <imgui_internal.h>
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
#include <array>
|
|
|
|
|
#include <filesystem>
|
2026-03-20 17:08:06 +08:00
|
|
|
|
2026-03-24 20:02:38 +08:00
|
|
|
namespace XCEngine {
|
|
|
|
|
namespace Editor {
|
2026-03-20 17:08:06 +08:00
|
|
|
|
2026-04-02 21:11:08 +08:00
|
|
|
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<const char*, 3> kFileNames = {
|
|
|
|
|
"play_button.png",
|
|
|
|
|
"stop_button.png",
|
|
|
|
|
"step_button.png"
|
|
|
|
|
};
|
|
|
|
|
static std::array<std::string, kFileNames.size()> 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
|
|
|
|
|
|
2026-03-20 17:08:06 +08:00
|
|
|
MenuBar::MenuBar() : Panel("MenuBar") {}
|
|
|
|
|
|
|
|
|
|
void MenuBar::Render() {
|
2026-03-26 21:18:33 +08:00
|
|
|
if (!m_context) {
|
2026-03-26 01:26:26 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-27 12:06:24 +08:00
|
|
|
Actions::HandleMenuBarShortcuts(*m_context);
|
|
|
|
|
Actions::DrawMainMenuBar(*m_context, m_aboutPopup);
|
2026-04-02 21:11:08 +08:00
|
|
|
RenderRunToolbar();
|
2026-03-27 12:06:24 +08:00
|
|
|
Actions::DrawMainMenuOverlays(m_context, m_aboutPopup);
|
2026-03-26 01:26:26 +08:00
|
|
|
}
|
|
|
|
|
|
2026-04-02 21:11:08 +08:00
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 01:26:26 +08:00
|
|
|
}
|
2026-03-24 20:02:38 +08:00
|
|
|
}
|