diff --git a/docs/plan/Editor重构3.26.md b/docs/plan/Editor重构3.26.md index fc0e81bf..dfb0f6ab 100644 --- a/docs/plan/Editor重构3.26.md +++ b/docs/plan/Editor重构3.26.md @@ -105,6 +105,8 @@ - `Edit` 动作解析与菜单绘制 / shortcut 分发已开始从 `MenuBar` 抽成共享 router - `Hierarchy / Project` 的上下文菜单与创建弹窗也开始下沉到 shared action router - `Project` 右键菜单目标已不再依赖 panel 内裸索引字段,而是改成 targeted popup state +- `Inspector / Console` 的局部 action 组装也开始继续下沉到 shared router +- `Inspector` 的 component section header 菜单已开始改成 callback/router 驱动,而不是在 widget 层硬编码动作 ### 5. Dock / Layout 层 @@ -134,6 +136,8 @@ - panel 的 attach / detach / render 顺序有了统一入口 - 后续继续拆 panel 或补 panel 时,不需要再改一大片壳层代码 - startup scene / dock attach / panel tree 组装已继续从 `EditorLayer` 收口到 `EditorWorkspace` +- `Inspector` 的 selection 订阅已从 `Render()` 懒接线改回生命周期接线 +- `Hierarchy` 的事件订阅也已从析构清理改回显式 `OnAttach / OnDetach` 生命周期清理 ### 7. Application / ImGui Session 层 @@ -151,6 +155,7 @@ - Win32 window/message pump 已抽成 `Platform/Win32EditorHost.h` - DX12 swapchain / render target / present / resize 已抽成 `Platform/D3D12WindowRenderer.h` - scene title 拼装已抽成 `Core/EditorWindowTitle.h` +- crash filter / stderr redirect / logging sink 初始化已继续从 `Application.cpp` 抽离 ## 主要面板状态 @@ -212,6 +217,8 @@ - 组件内容编辑大部分已走 property grid - Add Component 按钮与 popup 项已接 action 层 - Add Component popup 已接 shared popup state +- Add Component popup 菜单项组装已开始从 panel 下沉到 shared inspector action router +- 组件 section header 的移除动作已开始从 widget 层硬编码迁回 inspector action router 仍待完成: @@ -226,6 +233,7 @@ - 日志行 hover 表现已统一 - `Clear / Filter` 已接 action 层 - console filter 状态已从 panel 裸布尔字段收成独立 state object +- console toolbar action 与日志文本格式化已继续从 panel 下沉到共享层 仍待完成: diff --git a/editor/src/Actions/ConsoleActionRouter.h b/editor/src/Actions/ConsoleActionRouter.h new file mode 100644 index 00000000..af511f10 --- /dev/null +++ b/editor/src/Actions/ConsoleActionRouter.h @@ -0,0 +1,29 @@ +#pragma once + +#include "EditorActions.h" +#include "Core/EditorConsoleSink.h" +#include "UI/ConsoleFilterState.h" +#include "UI/UI.h" + +namespace XCEngine { +namespace Editor { +namespace Actions { + +inline void DrawConsoleToolbarActions(Debug::EditorConsoleSink& sink, UI::ConsoleFilterState& filterState) { + if (DrawToolbarAction(MakeClearConsoleAction())) { + sink.Clear(); + } + + ImGui::SameLine(); + UI::DrawToolbarLabel("Filter"); + ImGui::SameLine(); + DrawToolbarToggleAction(MakeConsoleInfoFilterAction(filterState.ShowInfo()), filterState.ShowInfo()); + ImGui::SameLine(); + DrawToolbarToggleAction(MakeConsoleWarningFilterAction(filterState.ShowWarning()), filterState.ShowWarning()); + ImGui::SameLine(); + DrawToolbarToggleAction(MakeConsoleErrorFilterAction(filterState.ShowError()), filterState.ShowError()); +} + +} // namespace Actions +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/Actions/EditorActions.h b/editor/src/Actions/EditorActions.h index bf519622..1f753168 100644 --- a/editor/src/Actions/EditorActions.h +++ b/editor/src/Actions/EditorActions.h @@ -161,6 +161,10 @@ inline ActionBinding MakeAddComponentMenuAction(const std::string& label, bool e return MakeAction(label, nullptr, false, enabled); } +inline ActionBinding MakeRemoveComponentAction(bool enabled = true) { + return MakeAction("Remove Component", nullptr, false, enabled); +} + inline ActionBinding MakeClearConsoleAction(bool enabled = true) { return MakeAction("Clear", nullptr, false, enabled); } diff --git a/editor/src/Actions/InspectorActionRouter.h b/editor/src/Actions/InspectorActionRouter.h new file mode 100644 index 00000000..966d7c6d --- /dev/null +++ b/editor/src/Actions/InspectorActionRouter.h @@ -0,0 +1,65 @@ +#pragma once + +#include "EditorActions.h" +#include "Commands/ComponentCommands.h" +#include "ComponentEditors/ComponentEditorRegistry.h" +#include "Core/IEditorContext.h" +#include "UI/UI.h" + +#include + +namespace XCEngine { +namespace Editor { +namespace Actions { + +inline std::string BuildAddComponentMenuLabel(const IComponentEditor& editor, ::XCEngine::Components::GameObject* gameObject) { + std::string label = editor.GetDisplayName(); + if (Commands::CanAddComponent(editor, gameObject)) { + return label; + } + + const char* reason = editor.GetAddDisabledReason(gameObject); + if (reason && reason[0] != '\0') { + label += " ("; + label += reason; + label += ")"; + } + + return label; +} + +inline bool DrawInspectorAddComponentMenu(IEditorContext& context, ::XCEngine::Components::GameObject* gameObject) { + bool drewAnyEntry = false; + + for (const auto& editor : ComponentEditorRegistry::Get().GetEditors()) { + if (!editor || !editor->ShowInAddComponentMenu()) { + continue; + } + + drewAnyEntry = true; + const bool canAdd = Commands::CanAddComponent(*editor, gameObject); + const std::string label = BuildAddComponentMenuLabel(*editor, gameObject); + DrawMenuAction(MakeAddComponentMenuAction(label, canAdd), [&]() { + if (Commands::AddComponent(context, *editor, gameObject)) { + ImGui::CloseCurrentPopup(); + } + }); + } + + return drewAnyEntry; +} + +inline bool DrawInspectorComponentMenu( + IEditorContext& context, + ::XCEngine::Components::Component* component, + ::XCEngine::Components::GameObject* gameObject, + const IComponentEditor* editor) { + const bool canRemove = Commands::CanRemoveComponent(component, editor); + return DrawMenuAction(MakeRemoveComponentAction(canRemove), [&]() { + Commands::RemoveComponent(context, component, gameObject, editor); + }); +} + +} // namespace Actions +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/Application.cpp b/editor/src/Application.cpp index 565dfa5e..29a38486 100644 --- a/editor/src/Application.cpp +++ b/editor/src/Application.cpp @@ -1,43 +1,14 @@ #include "Application.h" +#include "Core/EditorLoggingSetup.h" #include "Core/EditorWindowTitle.h" #include "Layers/EditorLayer.h" #include "Core/EditorContext.h" -#include "Core/EditorConsoleSink.h" #include "Core/EditorEvents.h" #include "Core/EventBus.h" #include "Platform/Win32Utf8.h" -#include -#include -#include -#include +#include "Platform/WindowsProcessDiagnostics.h" #include -namespace { -std::string GetExecutableLogPath(const char* fileName) { - return XCEngine::Editor::Platform::GetExecutableDirectoryUtf8() + "\\" + fileName; -} - -} // namespace - -static LONG WINAPI GlobalExceptionFilter(EXCEPTION_POINTERS* exceptionPointers) { - const std::string logPath = GetExecutableLogPath("crash.log"); - - FILE* f = nullptr; - fopen_s(&f, logPath.c_str(), "a"); - if (f) { - fprintf(f, "[CRASH] ExceptionCode=0x%08X, Address=0x%p\n", - exceptionPointers->ExceptionRecord->ExceptionCode, - exceptionPointers->ExceptionRecord->ExceptionAddress); - fclose(f); - } - - fprintf(stderr, "[CRASH] ExceptionCode=0x%08X, Address=0x%p\n", - exceptionPointers->ExceptionRecord->ExceptionCode, - exceptionPointers->ExceptionRecord->ExceptionAddress); - - return EXCEPTION_EXECUTE_HANDLER; -} - namespace XCEngine { namespace Editor { @@ -47,21 +18,11 @@ Application& Application::Get() { } bool Application::Initialize(HWND hwnd) { - SetUnhandledExceptionFilter(GlobalExceptionFilter); - - { - const std::string stderrPath = GetExecutableLogPath("stderr.log"); - freopen(stderrPath.c_str(), "w", stderr); - } - - Debug::Logger::Get().AddSink(std::make_unique()); - Debug::Logger::Get().AddSink(std::make_unique()); + Platform::InstallCrashExceptionFilter(); + Platform::RedirectStderrToExecutableLog(); const std::string exeDir = Platform::GetExecutableDirectoryUtf8(); - std::string logPath = exeDir + "\\editor.log"; - Debug::Logger::Get().AddSink(std::make_unique(logPath.c_str())); - Debug::Logger::Get().Info(Debug::LogCategory::General, "Editor Application starting..."); - Debug::Logger::Get().Info(Debug::LogCategory::General, ("Log file: " + logPath).c_str()); + ConfigureEditorLogging(exeDir); m_hwnd = hwnd; diff --git a/editor/src/Core/EditorLoggingSetup.h b/editor/src/Core/EditorLoggingSetup.h new file mode 100644 index 00000000..5b0a3de9 --- /dev/null +++ b/editor/src/Core/EditorLoggingSetup.h @@ -0,0 +1,27 @@ +#pragma once + +#include "Core/EditorConsoleSink.h" + +#include +#include +#include + +#include +#include + +namespace XCEngine { +namespace Editor { + +inline void ConfigureEditorLogging(const std::string& executableDirectory) { + auto& logger = Debug::Logger::Get(); + logger.AddSink(std::make_unique()); + logger.AddSink(std::make_unique()); + + const std::string logPath = executableDirectory + "\\editor.log"; + logger.AddSink(std::make_unique(logPath.c_str())); + logger.Info(Debug::LogCategory::General, "Editor Application starting..."); + logger.Info(Debug::LogCategory::General, ("Log file: " + logPath).c_str()); +} + +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/Platform/WindowsProcessDiagnostics.h b/editor/src/Platform/WindowsProcessDiagnostics.h new file mode 100644 index 00000000..82c750fa --- /dev/null +++ b/editor/src/Platform/WindowsProcessDiagnostics.h @@ -0,0 +1,51 @@ +#pragma once + +#include "Platform/Win32Utf8.h" + +#include +#include +#include + +namespace XCEngine { +namespace Editor { +namespace Platform { + +inline std::string GetExecutableLogPath(const char* fileName) { + return GetExecutableDirectoryUtf8() + "\\" + fileName; +} + +inline LONG WINAPI CrashExceptionFilter(EXCEPTION_POINTERS* exceptionPointers) { + const std::string logPath = GetExecutableLogPath("crash.log"); + + FILE* file = nullptr; + fopen_s(&file, logPath.c_str(), "a"); + if (file) { + fprintf( + file, + "[CRASH] ExceptionCode=0x%08X, Address=0x%p\n", + exceptionPointers->ExceptionRecord->ExceptionCode, + exceptionPointers->ExceptionRecord->ExceptionAddress); + fclose(file); + } + + fprintf( + stderr, + "[CRASH] ExceptionCode=0x%08X, Address=0x%p\n", + exceptionPointers->ExceptionRecord->ExceptionCode, + exceptionPointers->ExceptionRecord->ExceptionAddress); + + return EXCEPTION_EXECUTE_HANDLER; +} + +inline void InstallCrashExceptionFilter() { + SetUnhandledExceptionFilter(CrashExceptionFilter); +} + +inline void RedirectStderrToExecutableLog() { + const std::string stderrPath = GetExecutableLogPath("stderr.log"); + freopen(stderrPath.c_str(), "w", stderr); +} + +} // namespace Platform +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/UI/ConsoleLogFormatter.h b/editor/src/UI/ConsoleLogFormatter.h new file mode 100644 index 00000000..76b6e71e --- /dev/null +++ b/editor/src/UI/ConsoleLogFormatter.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include + +#include + +namespace XCEngine { +namespace Editor { +namespace UI { + +inline const char* ConsoleLogPrefix(::XCEngine::Debug::LogLevel level) { + switch (level) { + case ::XCEngine::Debug::LogLevel::Verbose: + case ::XCEngine::Debug::LogLevel::Debug: + case ::XCEngine::Debug::LogLevel::Info: + return "[INFO] "; + case ::XCEngine::Debug::LogLevel::Warning: + return "[WARN] "; + case ::XCEngine::Debug::LogLevel::Error: + case ::XCEngine::Debug::LogLevel::Fatal: + return "[ERROR] "; + } + + return "[LOG] "; +} + +inline std::string BuildConsoleLogText(const ::XCEngine::Debug::LogEntry& log) { + const char* category = ::XCEngine::Debug::LogCategoryToString(log.category); + return std::string(ConsoleLogPrefix(log.level)) + "[" + category + "] " + log.message.CStr(); +} + +} // namespace UI +} // namespace Editor +} // namespace XCEngine diff --git a/editor/src/UI/UI.h b/editor/src/UI/UI.h index de7258e6..cd481fc9 100644 --- a/editor/src/UI/UI.h +++ b/editor/src/UI/UI.h @@ -2,6 +2,7 @@ #include "BaseTheme.h" #include "ConsoleFilterState.h" +#include "ConsoleLogFormatter.h" #include "Core.h" #include "DockHostStyle.h" #include "PanelChrome.h" diff --git a/editor/src/UI/Widgets.h b/editor/src/UI/Widgets.h index 8b18f1fc..61a9b9f3 100644 --- a/editor/src/UI/Widgets.h +++ b/editor/src/UI/Widgets.h @@ -11,7 +11,6 @@ namespace UI { struct ComponentSectionResult { bool open = false; - bool removeRequested = false; }; struct HierarchyNodeResult { @@ -281,10 +280,11 @@ inline void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& drawList->AddLine(foldA, foldB, lineColor); } +template inline ComponentSectionResult BeginComponentSection( const void* id, const char* label, - bool canRemove, + DrawMenuFn&& drawMenu, bool defaultOpen = true) { const ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, InspectorSectionFramePadding()); @@ -302,15 +302,19 @@ inline ComponentSectionResult BeginComponentSection( const bool open = ImGui::TreeNodeEx(id, flags, "%s", label); ImGui::PopStyleVar(2); - bool removeRequested = false; if (BeginPopupContextItem("##ComponentSettings")) { - DrawMenuCommand(MenuCommand::Action("Remove Component", nullptr, false, canRemove), [&]() { - removeRequested = true; - }); + drawMenu(); EndPopup(); } - return ComponentSectionResult{ open, removeRequested }; + return ComponentSectionResult{ open }; +} + +inline ComponentSectionResult BeginComponentSection( + const void* id, + const char* label, + bool defaultOpen = true) { + return BeginComponentSection(id, label, []() {}, defaultOpen); } inline void EndComponentSection() { diff --git a/editor/src/panels/ConsolePanel.cpp b/editor/src/panels/ConsolePanel.cpp index 55506764..5902ee36 100644 --- a/editor/src/panels/ConsolePanel.cpp +++ b/editor/src/panels/ConsolePanel.cpp @@ -1,10 +1,8 @@ +#include "Actions/ConsoleActionRouter.h" #include "Actions/ActionRouting.h" -#include "Actions/EditorActions.h" #include "ConsolePanel.h" #include "Core/EditorConsoleSink.h" #include "UI/UI.h" -#include -#include #include namespace XCEngine { @@ -26,18 +24,7 @@ void ConsolePanel::Render() { { UI::PanelToolbarScope toolbar("ConsoleToolbar", UI::StandardPanelToolbarHeight()); if (toolbar.IsOpen()) { - if (Actions::DrawToolbarAction(Actions::MakeClearConsoleAction())) { - sink->Clear(); - } - ImGui::SameLine(); - - UI::DrawToolbarLabel("Filter"); - ImGui::SameLine(); - Actions::DrawToolbarToggleAction(Actions::MakeConsoleInfoFilterAction(m_filterState.ShowInfo()), m_filterState.ShowInfo()); - ImGui::SameLine(); - Actions::DrawToolbarToggleAction(Actions::MakeConsoleWarningFilterAction(m_filterState.ShowWarning()), m_filterState.ShowWarning()); - ImGui::SameLine(); - Actions::DrawToolbarToggleAction(Actions::MakeConsoleErrorFilterAction(m_filterState.ShowError()), m_filterState.ShowError()); + Actions::DrawConsoleToolbarActions(*sink, m_filterState); } } @@ -52,28 +39,10 @@ void ConsolePanel::Render() { if (!m_filterState.Allows(log.level)) { continue; } - - const char* prefix; - - switch (log.level) { - case ::XCEngine::Debug::LogLevel::Verbose: - case ::XCEngine::Debug::LogLevel::Debug: - case ::XCEngine::Debug::LogLevel::Info: - prefix = "[INFO] "; - break; - case ::XCEngine::Debug::LogLevel::Warning: - prefix = "[WARN] "; - break; - case ::XCEngine::Debug::LogLevel::Error: - case ::XCEngine::Debug::LogLevel::Fatal: - prefix = "[ERROR] "; - break; - } - + ImGui::PushID(static_cast(logIndex)); - const char* category = ::XCEngine::Debug::LogCategoryToString(log.category); - std::string fullMessage = std::string(prefix) + "[" + category + "] " + log.message.CStr(); + const std::string fullMessage = UI::BuildConsoleLogText(log); if (UI::DrawConsoleLogRow(fullMessage.c_str())) { ImGui::SetClipboardText(fullMessage.c_str()); } diff --git a/editor/src/panels/HierarchyPanel.cpp b/editor/src/panels/HierarchyPanel.cpp index a49e89a1..4137173f 100644 --- a/editor/src/panels/HierarchyPanel.cpp +++ b/editor/src/panels/HierarchyPanel.cpp @@ -16,14 +16,11 @@ namespace Editor { HierarchyPanel::HierarchyPanel() : Panel("Hierarchy") { } -HierarchyPanel::~HierarchyPanel() { - if (m_context) { - m_context->GetEventBus().Unsubscribe(m_selectionHandlerId); - m_context->GetEventBus().Unsubscribe(m_renameRequestHandlerId); - } -} - void HierarchyPanel::OnAttach() { + if (!m_context || m_selectionHandlerId || m_renameRequestHandlerId) { + return; + } + m_selectionHandlerId = m_context->GetEventBus().Subscribe( [this](const SelectionChangedEvent& event) { OnSelectionChanged(event); @@ -36,6 +33,21 @@ void HierarchyPanel::OnAttach() { ); } +void HierarchyPanel::OnDetach() { + if (!m_context) { + return; + } + + if (m_selectionHandlerId) { + m_context->GetEventBus().Unsubscribe(m_selectionHandlerId); + m_selectionHandlerId = 0; + } + if (m_renameRequestHandlerId) { + m_context->GetEventBus().Unsubscribe(m_renameRequestHandlerId); + m_renameRequestHandlerId = 0; + } +} + void HierarchyPanel::OnSelectionChanged(const SelectionChangedEvent& event) { if (m_renameState.IsActive() && event.primarySelection != m_renameState.Item()) { CancelRename(); diff --git a/editor/src/panels/HierarchyPanel.h b/editor/src/panels/HierarchyPanel.h index 5d29a5ed..e0144e7e 100644 --- a/editor/src/panels/HierarchyPanel.h +++ b/editor/src/panels/HierarchyPanel.h @@ -13,9 +13,9 @@ enum class SortMode { Name, ComponentCount, TransformFirst }; class HierarchyPanel : public Panel { public: HierarchyPanel(); - ~HierarchyPanel(); void OnAttach() override; + void OnDetach() override; void Render() override; private: diff --git a/editor/src/panels/InspectorPanel.cpp b/editor/src/panels/InspectorPanel.cpp index 99a1e6ff..07b7c600 100644 --- a/editor/src/panels/InspectorPanel.cpp +++ b/editor/src/panels/InspectorPanel.cpp @@ -1,6 +1,6 @@ +#include "Actions/InspectorActionRouter.h" #include "Actions/ActionRouting.h" #include "Actions/EditorActions.h" -#include "Commands/ComponentCommands.h" #include "InspectorPanel.h" #include "Core/IEditorContext.h" #include "Core/ISceneManager.h" @@ -19,9 +19,28 @@ namespace Editor { InspectorPanel::InspectorPanel() : Panel("Inspector") {} InspectorPanel::~InspectorPanel() { - if (m_context) { - m_context->GetEventBus().Unsubscribe(m_selectionHandlerId); +} + +void InspectorPanel::OnAttach() { + if (!m_context || m_selectionHandlerId) { + return; } + + m_selectionHandlerId = m_context->GetEventBus().Subscribe( + [this](const SelectionChangedEvent& event) { + OnSelectionChanged(event); + } + ); + m_selectedEntityId = m_context->GetSelectionManager().GetSelectedEntity(); +} + +void InspectorPanel::OnDetach() { + if (!m_context || !m_selectionHandlerId) { + return; + } + + m_context->GetEventBus().Unsubscribe(m_selectionHandlerId); + m_selectionHandlerId = 0; } void InspectorPanel::OnSelectionChanged(const SelectionChangedEvent& event) { @@ -39,17 +58,7 @@ void InspectorPanel::Render() { } Actions::ObserveInactiveActionRoute(*m_context); - - if (!m_selectionHandlerId && m_context) { - m_selectionHandlerId = m_context->GetEventBus().Subscribe( - [this](const SelectionChangedEvent& event) { - OnSelectionChanged(event); - } - ); - } - - m_selectedEntityId = m_context->GetSelectionManager().GetSelectedEntity(); - + if (m_selectedEntityId) { auto& sceneManager = m_context->GetSceneManager(); auto* gameObject = sceneManager.GetEntity(m_selectedEntityId); @@ -107,32 +116,7 @@ void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject* return; } - bool drewAnyEntry = false; - for (const auto& editor : ComponentEditorRegistry::Get().GetEditors()) { - if (!editor || !editor->ShowInAddComponentMenu()) { - continue; - } - - drewAnyEntry = true; - const bool canAdd = Commands::CanAddComponent(*editor, gameObject); - std::string label = editor->GetDisplayName(); - if (!canAdd) { - const char* reason = editor->GetAddDisabledReason(gameObject); - if (reason && reason[0] != '\0') { - label += " ("; - label += reason; - label += ")"; - } - } - - Actions::DrawMenuAction(Actions::MakeAddComponentMenuAction(label, canAdd), [&]() { - if (Commands::AddComponent(*m_context, *editor, gameObject)) { - ImGui::CloseCurrentPopup(); - } - }); - } - - if (!drewAnyEntry) { + if (!Actions::DrawInspectorAddComponentMenu(*m_context, gameObject)) { UI::DrawHintText("No registered component editors"); } @@ -145,14 +129,15 @@ void InspectorPanel::RenderComponent(::XCEngine::Components::Component* componen IComponentEditor* editor = ComponentEditorRegistry::Get().FindEditor(component); const std::string name = component->GetName(); - const UI::ComponentSectionResult section = - UI::BeginComponentSection( - (void*)typeid(*component).hash_code(), - name.c_str(), - Commands::CanRemoveComponent(component, editor)); + bool removed = false; + const UI::ComponentSectionResult section = UI::BeginComponentSection( + (void*)typeid(*component).hash_code(), + name.c_str(), + [&]() { + removed = Actions::DrawInspectorComponentMenu(*m_context, component, gameObject, editor); + }); - if (section.removeRequested) { - Commands::RemoveComponent(*m_context, component, gameObject, editor); + if (removed) { return; } diff --git a/editor/src/panels/InspectorPanel.h b/editor/src/panels/InspectorPanel.h index e4881c7f..cd51be94 100644 --- a/editor/src/panels/InspectorPanel.h +++ b/editor/src/panels/InspectorPanel.h @@ -17,7 +17,9 @@ class InspectorPanel : public Panel { public: InspectorPanel(); ~InspectorPanel(); - + + void OnAttach() override; + void OnDetach() override; void Render() override; private: