Refactor editor UI architecture
271
docs/plan/Editor重构3.26.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# Editor重构 3.26
|
||||
|
||||
## 当前判断
|
||||
|
||||
截至 2026-03-26,Editor 的 UI 架构重构如果只计算“架构层整理”,不计算 Viewport/RHI 和 Scene/Game 真正内容开发,整体进度大约在 **90%**。
|
||||
|
||||
如果把“编辑器整体可用度”也算进去,则大约在 **65%** 左右,因为 `Scene` / `Game` 面板目前仍然只是占位壳,Viewport 仍未回归,且部分编辑语义与回归验证还未补齐。
|
||||
|
||||
## 已完成的架构层
|
||||
|
||||
### 1. UI 主题与样式 Token 层
|
||||
|
||||
已将大量直接散落在 panel 中的颜色、间距、控件尺寸、Dock 外观收口到统一 UI 基础层。
|
||||
|
||||
当前主要入口:
|
||||
|
||||
- `editor/src/UI/BaseTheme.h`
|
||||
- `editor/src/UI/StyleTokens.h`
|
||||
- `editor/src/UI/DockHostStyle.h`
|
||||
- `editor/src/UI/PanelChrome.h`
|
||||
- `editor/src/UI/Core.h`
|
||||
- `editor/src/UI/Widgets.h`
|
||||
- `editor/src/UI/PopupState.h`
|
||||
- `editor/src/UI/PropertyGrid.h`
|
||||
- `editor/src/UI/UI.h`
|
||||
|
||||
这一层的意义是:
|
||||
|
||||
- 主题值不再零散写在各个 panel 里
|
||||
- panel chrome、toolbar、popup、property row、asset tile 等基础外观开始统一
|
||||
- 后续继续调 UI 观感时,优先改 shared layer,而不是到处修 panel
|
||||
|
||||
### 2. 面板共享控件层
|
||||
|
||||
已抽出并统一了:
|
||||
|
||||
- toolbar 搜索框
|
||||
- toolbar 按钮 / 切换按钮
|
||||
- 空状态绘制
|
||||
- 面包屑
|
||||
- hierarchy tree node
|
||||
- asset tile
|
||||
- deferred popup / modal state
|
||||
- inspector component section
|
||||
- dialog action row
|
||||
- property grid / scalar / vector 控件
|
||||
|
||||
当前结果是 `Hierarchy / Project / Inspector / Console / MenuBar` 的基础表现逻辑已经大面积转向 shared widget,而不是每个 panel 手搓一遍。
|
||||
|
||||
### 3. Command 层
|
||||
|
||||
已经从 panel 中抽出了大部分直接业务操作,形成内部命令层:
|
||||
|
||||
- `editor/src/Commands/SceneCommands.h`
|
||||
- `editor/src/Commands/EntityCommands.h`
|
||||
- `editor/src/Commands/ProjectCommands.h`
|
||||
- `editor/src/Commands/ComponentCommands.h`
|
||||
|
||||
当前已进入 command 的行为包括:
|
||||
|
||||
- 新建 / 打开 / 保存场景
|
||||
- 启动场景加载与退出时脏场景保存
|
||||
- 创建 / 复制 / 粘贴 / Duplicate / 删除 / 重命名 / 重挂接实体
|
||||
- 创建文件夹、删除资源、打开资源、移动资源
|
||||
- 添加 / 移除组件
|
||||
|
||||
这一层已经把大量 `undo / dirty / selection reset / scene switch confirm` 从 panel 中剥离出来。
|
||||
|
||||
### 4. Action / Shortcut 层
|
||||
|
||||
这一层是 3.26 新补上的,用于承接:
|
||||
|
||||
- 文案
|
||||
- shortcut 文本
|
||||
- enabled 状态
|
||||
- 文本输入时是否允许触发
|
||||
- menu 与 shortcut 的统一绑定
|
||||
- button / toolbar / inspector action 的统一接线
|
||||
|
||||
当前文件:
|
||||
|
||||
- `editor/src/Actions/ActionBinding.h`
|
||||
- `editor/src/Actions/EditorActions.h`
|
||||
|
||||
已覆盖的区域:
|
||||
|
||||
- `MenuBar`
|
||||
- `Hierarchy`
|
||||
- `Project`
|
||||
- `Inspector`
|
||||
- `Console`
|
||||
|
||||
这一层已经不再只是 `MenuBar + Hierarchy` 的试点,而是开始成为 editor 范围内的统一动作定义入口。
|
||||
|
||||
### 5. Dock / Layout 层
|
||||
|
||||
`Dock / Layout` 已从 `EditorLayer::setupDockspace()` 中抽出,形成独立 layout controller:
|
||||
|
||||
- `editor/src/Layout/DockLayoutController.h`
|
||||
|
||||
当前已完成:
|
||||
|
||||
- dock host 渲染与默认布局重建从 `EditorLayer` 中移出
|
||||
- `Reset Layout` 已接入真正行为
|
||||
- `MenuBar -> EventBus -> DockLayoutController` 的事件链已经打通
|
||||
- ImGui layout 已持久化到 `<project>/.xceditor/imgui_layout.ini`
|
||||
|
||||
当前仍待完成:
|
||||
|
||||
- 默认布局参数进一步数据化
|
||||
- 更细致的 layout 状态管理
|
||||
|
||||
### 6. Panel 生命周期层
|
||||
|
||||
已开始把 `EditorLayer` 内手写的 panel 创建、context 注入、attach、detach、update、event、render 流程,收口到统一 panel collection。
|
||||
|
||||
当前意义是:
|
||||
|
||||
- `EditorLayer` 不再保存一串分散的 panel 生命周期样板代码
|
||||
- panel 的 attach / detach / render 顺序有了统一入口
|
||||
- 后续继续拆 panel 或补 panel 时,不需要再改一大片壳层代码
|
||||
|
||||
## 主要面板状态
|
||||
|
||||
### MenuBar
|
||||
|
||||
已完成:
|
||||
|
||||
- 文件菜单场景动作走 command
|
||||
- undo / redo / copy / paste 动作开始统一
|
||||
- shortcut 接入 action 层
|
||||
- scene 状态显示统一
|
||||
- `Reset Layout` 已接入事件驱动行为
|
||||
- `About` 已接入真实 modal popup
|
||||
- `Exit` 已通过事件驱动关闭 editor
|
||||
|
||||
仍待完成:
|
||||
|
||||
- help / app 级动作继续向更统一的 application shell 收口
|
||||
|
||||
### Hierarchy
|
||||
|
||||
已完成:
|
||||
|
||||
- 搜索栏、节点样式、上下文菜单、创建菜单已共享化
|
||||
- 创建 / 删除 / 重命名 / 复制 / 粘贴 / Duplicate / 重挂接走 command
|
||||
- 快捷键已接 action 层
|
||||
- 重命名状态已收成 `Begin / Commit / Cancel`
|
||||
|
||||
仍待完成:
|
||||
|
||||
- 重命名状态机进一步下沉
|
||||
- 拖拽 / 空白区域目标等细节继续统一
|
||||
|
||||
### Project
|
||||
|
||||
已完成:
|
||||
|
||||
- toolbar、breadcrumb、search、asset grid、empty state 已共享化
|
||||
- 打开资源、删除资源、创建文件夹、拖拽移动走 command
|
||||
- 上下文菜单、返回按钮、创建文件夹对话框已接 action 层
|
||||
- 资源图标绘制与图标配色已下沉到 shared UI token / widget
|
||||
- 创建文件夹弹窗已改成 shared popup state 驱动
|
||||
|
||||
仍待完成:
|
||||
|
||||
- 更多资源对话框 / 多选语义继续统一
|
||||
|
||||
### Inspector
|
||||
|
||||
已完成:
|
||||
|
||||
- component section 已共享化
|
||||
- Add Component 已放到底部
|
||||
- 组件增删已走 command
|
||||
- 组件内容编辑大部分已走 property grid
|
||||
- Add Component 按钮与 popup 项已接 action 层
|
||||
|
||||
仍待完成:
|
||||
|
||||
- section header 的动作继续统一
|
||||
- 组件编辑交互与 undo 提交边界继续收口
|
||||
|
||||
### Console
|
||||
|
||||
已完成:
|
||||
|
||||
- 工具栏与内容区布局已共享化
|
||||
- 日志行 hover 表现已统一
|
||||
- `Clear / Filter` 已接 action 层
|
||||
|
||||
仍待完成:
|
||||
|
||||
- 控制台状态与动作定义继续统一
|
||||
|
||||
### Scene / Game
|
||||
|
||||
当前状态:
|
||||
|
||||
- 只保留空壳 panel
|
||||
- 暂时不做内容
|
||||
|
||||
这符合当前阶段决策,因为 Viewport 依赖的 RHI 模块仍在重构中。
|
||||
|
||||
## 还未完成的架构事项
|
||||
|
||||
### 高优先级
|
||||
|
||||
1. 整理 `Application.cpp` 和 `EditorLayer.cpp` 中剩余的 UI 壳逻辑
|
||||
包括字体初始化、主 dock host 初始化、部分样式 push / pop 的收口。
|
||||
|
||||
### 中优先级
|
||||
|
||||
2. 继续将 panel 的本地状态机抽离
|
||||
包括:
|
||||
- hierarchy rename state
|
||||
- project popup / dialog state
|
||||
- inspector component popup state
|
||||
- console filter state
|
||||
|
||||
3. 将剩余少量视觉硬编码继续下沉到 token / widget 层
|
||||
尤其是 `ProjectPanel` 中的 icon color 和一部分 panel 局部尺寸。
|
||||
|
||||
4. 统一 editor 范围内的编辑语义
|
||||
例如:
|
||||
- 哪些 panel 消费 Copy / Paste / Delete
|
||||
- 哪些动作受 selection 驱动
|
||||
- 哪些动作在文本输入时必须屏蔽
|
||||
|
||||
### 低优先级
|
||||
|
||||
5. 再整理一次目录边界
|
||||
当前 `UI / Commands / Actions / Layout` 方向是对的,但后续还可以再决定哪些继续 header-only,哪些拆 `.cpp` 更合适。
|
||||
|
||||
6. 补一份更正式的 editor 架构说明
|
||||
说明 panel、actions、commands、layout、managers 的职责边界,便于后续继续迭代。
|
||||
|
||||
7. 根据条件补最小回归测试或最小验证脚本
|
||||
重点验证 shortcut、undo、scene dirty/save switch、drag-drop/reparent。
|
||||
|
||||
## 下一阶段建议执行顺序
|
||||
|
||||
### 第一阶段
|
||||
|
||||
1. 清理 `Application + EditorLayer` UI 壳层
|
||||
2. 清理 panel 内剩余局部状态逻辑
|
||||
|
||||
### 第二阶段
|
||||
|
||||
3. 收口剩余视觉硬编码
|
||||
4. 统一 editor 范围内的编辑语义
|
||||
|
||||
### 第三阶段
|
||||
|
||||
5. 根据需要继续细分 `Actions / Commands / Layout` 的目录边界
|
||||
6. 补最小回归验证
|
||||
7. 为 Viewport 回归预留 editor shell 接口
|
||||
|
||||
## 阶段性结论
|
||||
|
||||
当前 editor 重构已经从“到处修视觉 bug”切换到了“逐层收口架构”的状态,方向是正确的。
|
||||
|
||||
最关键的变化不是某个 panel 看起来更像 Unity,而是已经开始形成稳定分层:
|
||||
|
||||
- `UI` 负责外观与共享控件
|
||||
- `Actions` 负责动作定义与 shortcut / button 绑定
|
||||
- `Commands` 负责编辑器业务行为
|
||||
- `Layout` 负责 dock host 与默认布局
|
||||
- `Panels` 逐步退化为纯 UI 壳
|
||||
|
||||
只要继续沿这个方向收口,后面再做 Scene/Game 接入、Viewport 回归时,成本会明显低于继续在 panel 内部堆逻辑。
|
||||
BIN
editor/resources/Icons/Camera.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
editor/resources/Icons/cloud.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
editor/resources/Icons/cloud_icon.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
editor/resources/Icons/directory_empty_icon.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
editor/resources/Icons/directory_icon.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
editor/resources/Icons/image_icon.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
editor/resources/Icons/light.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
editor/resources/Icons/logo.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
BIN
editor/resources/Icons/material_icon.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
editor/resources/Icons/mesh_icon.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
editor/resources/Icons/pause_button.png
Normal file
|
After Width: | Height: | Size: 119 B |
BIN
editor/resources/Icons/play_button.png
Normal file
|
After Width: | Height: | Size: 357 B |
BIN
editor/resources/Icons/scene_icon.png
Normal file
|
After Width: | Height: | Size: 9.4 KiB |
BIN
editor/resources/Icons/script_icon.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
editor/resources/Icons/step_button.png
Normal file
|
After Width: | Height: | Size: 438 B |
BIN
editor/resources/Icons/stop_button.png
Normal file
|
After Width: | Height: | Size: 115 B |
BIN
editor/resources/Icons/texture_icon.png
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
editor/resources/Icons/简单模型.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
152
editor/src/Actions/ActionBinding.h
Normal file
@@ -0,0 +1,152 @@
|
||||
#pragma once
|
||||
|
||||
#include "UI/UI.h"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace Actions {
|
||||
|
||||
struct ShortcutChord {
|
||||
ImGuiKey key = ImGuiKey_None;
|
||||
bool ctrl = false;
|
||||
bool shift = false;
|
||||
bool alt = false;
|
||||
};
|
||||
|
||||
struct ActionBinding {
|
||||
std::string label;
|
||||
std::string shortcutLabel;
|
||||
bool selected = false;
|
||||
bool enabled = true;
|
||||
bool allowWhenTextInput = false;
|
||||
std::array<ShortcutChord, 2> shortcuts{};
|
||||
size_t shortcutCount = 0;
|
||||
};
|
||||
|
||||
inline ShortcutChord Shortcut(ImGuiKey key, bool ctrl = false, bool shift = false, bool alt = false) {
|
||||
return ShortcutChord{ key, ctrl, shift, alt };
|
||||
}
|
||||
|
||||
inline ActionBinding MakeAction(
|
||||
std::string label,
|
||||
const char* shortcutLabel = nullptr,
|
||||
bool selected = false,
|
||||
bool enabled = true,
|
||||
bool allowWhenTextInput = false,
|
||||
ShortcutChord primaryShortcut = {},
|
||||
ShortcutChord secondaryShortcut = {}) {
|
||||
ActionBinding action;
|
||||
action.label = std::move(label);
|
||||
action.shortcutLabel = shortcutLabel ? shortcutLabel : "";
|
||||
action.selected = selected;
|
||||
action.enabled = enabled;
|
||||
action.allowWhenTextInput = allowWhenTextInput;
|
||||
|
||||
if (primaryShortcut.key != ImGuiKey_None) {
|
||||
action.shortcuts[action.shortcutCount++] = primaryShortcut;
|
||||
}
|
||||
if (secondaryShortcut.key != ImGuiKey_None) {
|
||||
action.shortcuts[action.shortcutCount++] = secondaryShortcut;
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
inline UI::MenuCommand ToMenuCommand(const ActionBinding& action) {
|
||||
return UI::MenuCommand::Action(
|
||||
action.label.c_str(),
|
||||
action.shortcutLabel.empty() ? nullptr : action.shortcutLabel.c_str(),
|
||||
action.selected,
|
||||
action.enabled);
|
||||
}
|
||||
|
||||
template <typename ExecuteFn>
|
||||
inline bool DrawMenuAction(const ActionBinding& action, ExecuteFn&& execute) {
|
||||
return UI::DrawMenuCommand(ToMenuCommand(action), std::forward<ExecuteFn>(execute));
|
||||
}
|
||||
|
||||
inline void DrawMenuSeparator() {
|
||||
UI::DrawMenuCommand(UI::MenuCommand::Separator(), []() {});
|
||||
}
|
||||
|
||||
inline bool DrawButtonAction(const ActionBinding& action, ImVec2 size = ImVec2(0.0f, 0.0f)) {
|
||||
ImGui::BeginDisabled(!action.enabled);
|
||||
const bool pressed = ImGui::Button(action.label.c_str(), size);
|
||||
ImGui::EndDisabled();
|
||||
return action.enabled && pressed;
|
||||
}
|
||||
|
||||
inline bool DrawInspectorAction(const ActionBinding& action, ImVec2 size = ImVec2(-1.0f, 0.0f)) {
|
||||
ImGui::BeginDisabled(!action.enabled);
|
||||
const bool pressed = UI::InspectorActionButton(action.label.c_str(), size);
|
||||
ImGui::EndDisabled();
|
||||
return action.enabled && pressed;
|
||||
}
|
||||
|
||||
inline bool DrawToolbarAction(const ActionBinding& action, ImVec2 size = ImVec2(0.0f, 0.0f)) {
|
||||
ImGui::BeginDisabled(!action.enabled);
|
||||
const bool pressed = UI::ToolbarButton(action.label.c_str(), action.selected, size);
|
||||
ImGui::EndDisabled();
|
||||
return action.enabled && pressed;
|
||||
}
|
||||
|
||||
inline bool DrawToolbarToggleAction(const ActionBinding& action, bool& active, ImVec2 size = ImVec2(0.0f, 0.0f)) {
|
||||
ImGui::BeginDisabled(!action.enabled);
|
||||
const bool pressed = UI::ToolbarButton(action.label.c_str(), active, size);
|
||||
ImGui::EndDisabled();
|
||||
if (!action.enabled || !pressed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
active = !active;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool IsShortcutPressed(const ShortcutChord& shortcut, const ImGuiIO& io) {
|
||||
if (shortcut.key == ImGuiKey_None) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (io.KeyCtrl != shortcut.ctrl || io.KeyShift != shortcut.shift || io.KeyAlt != shortcut.alt) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ImGui::IsKeyPressed(shortcut.key, false);
|
||||
}
|
||||
|
||||
inline bool IsShortcutPressed(const ActionBinding& action) {
|
||||
if (!action.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
if (!action.allowWhenTextInput && io.WantTextInput) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < action.shortcutCount; ++i) {
|
||||
if (IsShortcutPressed(action.shortcuts[i], io)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename ExecuteFn>
|
||||
inline bool HandleShortcut(const ActionBinding& action, ExecuteFn&& execute) {
|
||||
if (!IsShortcutPressed(action)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::forward<ExecuteFn>(execute)();
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Actions
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
184
editor/src/Actions/EditorActions.h
Normal file
@@ -0,0 +1,184 @@
|
||||
#pragma once
|
||||
|
||||
#include "ActionBinding.h"
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/ISelectionManager.h"
|
||||
#include "Core/ISceneManager.h"
|
||||
#include "Core/IUndoManager.h"
|
||||
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace Actions {
|
||||
|
||||
inline ActionBinding MakeNewSceneAction() {
|
||||
return MakeAction("New Scene", "Ctrl+N", false, true, true, Shortcut(ImGuiKey_N, true));
|
||||
}
|
||||
|
||||
inline ActionBinding MakeOpenSceneAction() {
|
||||
return MakeAction("Open Scene", "Ctrl+O", false, true, true, Shortcut(ImGuiKey_O, true));
|
||||
}
|
||||
|
||||
inline ActionBinding MakeSaveSceneAction() {
|
||||
return MakeAction("Save Scene", "Ctrl+S", false, true, true, Shortcut(ImGuiKey_S, true));
|
||||
}
|
||||
|
||||
inline ActionBinding MakeSaveSceneAsAction() {
|
||||
return MakeAction("Save Scene As...", "Ctrl+Shift+S", false, true, true, Shortcut(ImGuiKey_S, true, true));
|
||||
}
|
||||
|
||||
inline ActionBinding MakeUndoAction(IEditorContext& context) {
|
||||
auto& undoManager = context.GetUndoManager();
|
||||
const std::string label = undoManager.CanUndo() ? "Undo " + undoManager.GetUndoLabel() : "Undo";
|
||||
return MakeAction(label, "Ctrl+Z", false, undoManager.CanUndo(), false, Shortcut(ImGuiKey_Z, true));
|
||||
}
|
||||
|
||||
inline ActionBinding MakeRedoAction(IEditorContext& context) {
|
||||
auto& undoManager = context.GetUndoManager();
|
||||
const std::string label = undoManager.CanRedo() ? "Redo " + undoManager.GetRedoLabel() : "Redo";
|
||||
return MakeAction(
|
||||
label,
|
||||
"Ctrl+Y",
|
||||
false,
|
||||
undoManager.CanRedo(),
|
||||
false,
|
||||
Shortcut(ImGuiKey_Y, true),
|
||||
Shortcut(ImGuiKey_Z, true, true));
|
||||
}
|
||||
|
||||
inline ActionBinding MakeCutAction(bool enabled = false) {
|
||||
return MakeAction("Cut", "Ctrl+X", false, enabled, false, Shortcut(ImGuiKey_X, true));
|
||||
}
|
||||
|
||||
inline ActionBinding MakeCopyEntityAction(::XCEngine::Components::GameObject* gameObject) {
|
||||
return MakeAction("Copy", "Ctrl+C", false, gameObject != nullptr, false, Shortcut(ImGuiKey_C, true));
|
||||
}
|
||||
|
||||
inline ActionBinding MakePasteEntityAction(IEditorContext& context) {
|
||||
return MakeAction(
|
||||
"Paste",
|
||||
"Ctrl+V",
|
||||
false,
|
||||
context.GetSceneManager().HasClipboardData(),
|
||||
false,
|
||||
Shortcut(ImGuiKey_V, true));
|
||||
}
|
||||
|
||||
inline ActionBinding MakeDuplicateEntityAction(::XCEngine::Components::GameObject* gameObject) {
|
||||
return MakeAction("Duplicate", "Ctrl+D", false, gameObject != nullptr, false, Shortcut(ImGuiKey_D, true));
|
||||
}
|
||||
|
||||
inline ActionBinding MakeDeleteEntityAction(::XCEngine::Components::GameObject* gameObject) {
|
||||
return MakeAction("Delete", "Delete", false, gameObject != nullptr, false, Shortcut(ImGuiKey_Delete));
|
||||
}
|
||||
|
||||
inline ActionBinding MakeRenameEntityAction(::XCEngine::Components::GameObject* gameObject) {
|
||||
return MakeAction("Rename", "F2", false, gameObject != nullptr, false, Shortcut(ImGuiKey_F2));
|
||||
}
|
||||
|
||||
inline ActionBinding MakeCreateChildEntityAction(::XCEngine::Components::GameObject* gameObject) {
|
||||
return MakeAction("Create Child", nullptr, false, gameObject != nullptr);
|
||||
}
|
||||
|
||||
inline ActionBinding MakeDetachEntityAction(::XCEngine::Components::GameObject* gameObject) {
|
||||
return MakeAction(
|
||||
"Detach from Parent",
|
||||
nullptr,
|
||||
false,
|
||||
gameObject != nullptr && gameObject->GetParent() != nullptr);
|
||||
}
|
||||
|
||||
inline ActionBinding MakeCreateEmptyEntityAction() {
|
||||
return MakeAction("Empty Object");
|
||||
}
|
||||
|
||||
inline ActionBinding MakeCreateCameraEntityAction() {
|
||||
return MakeAction("Camera");
|
||||
}
|
||||
|
||||
inline ActionBinding MakeCreateLightEntityAction() {
|
||||
return MakeAction("Light");
|
||||
}
|
||||
|
||||
inline ActionBinding MakeCreateCubeEntityAction() {
|
||||
return MakeAction("Cube");
|
||||
}
|
||||
|
||||
inline ActionBinding MakeCreateSphereEntityAction() {
|
||||
return MakeAction("Sphere");
|
||||
}
|
||||
|
||||
inline ActionBinding MakeCreatePlaneEntityAction() {
|
||||
return MakeAction("Plane");
|
||||
}
|
||||
|
||||
inline ActionBinding MakeResetLayoutAction() {
|
||||
return MakeAction("Reset Layout");
|
||||
}
|
||||
|
||||
inline ActionBinding MakeAboutAction() {
|
||||
return MakeAction("About");
|
||||
}
|
||||
|
||||
inline ActionBinding MakeExitAction() {
|
||||
return MakeAction("Exit", "Alt+F4");
|
||||
}
|
||||
|
||||
inline ActionBinding MakeNavigateBackAction(bool enabled) {
|
||||
return MakeAction("<", nullptr, false, enabled);
|
||||
}
|
||||
|
||||
inline ActionBinding MakeOpenAssetAction(bool enabled) {
|
||||
return MakeAction("Open", nullptr, false, enabled);
|
||||
}
|
||||
|
||||
inline ActionBinding MakeDeleteAssetAction(bool enabled = true) {
|
||||
return MakeAction("Delete", nullptr, false, enabled);
|
||||
}
|
||||
|
||||
inline ActionBinding MakeCreateFolderAction(bool enabled = true) {
|
||||
return MakeAction("Create Folder", nullptr, false, enabled);
|
||||
}
|
||||
|
||||
inline ActionBinding MakeConfirmCreateAction(bool enabled) {
|
||||
return MakeAction("Create", nullptr, false, enabled);
|
||||
}
|
||||
|
||||
inline ActionBinding MakeCancelAction(bool enabled = true) {
|
||||
return MakeAction("Cancel", nullptr, false, enabled);
|
||||
}
|
||||
|
||||
inline ActionBinding MakeAddComponentButtonAction(bool enabled = true) {
|
||||
return MakeAction("Add Component", nullptr, false, enabled);
|
||||
}
|
||||
|
||||
inline ActionBinding MakeAddComponentMenuAction(const std::string& label, bool enabled) {
|
||||
return MakeAction(label, nullptr, false, enabled);
|
||||
}
|
||||
|
||||
inline ActionBinding MakeClearConsoleAction(bool enabled = true) {
|
||||
return MakeAction("Clear", nullptr, false, enabled);
|
||||
}
|
||||
|
||||
inline ActionBinding MakeConsoleInfoFilterAction(bool active, bool enabled = true) {
|
||||
return MakeAction("Info", nullptr, active, enabled);
|
||||
}
|
||||
|
||||
inline ActionBinding MakeConsoleWarningFilterAction(bool active, bool enabled = true) {
|
||||
return MakeAction("Warn", nullptr, active, enabled);
|
||||
}
|
||||
|
||||
inline ActionBinding MakeConsoleErrorFilterAction(bool active, bool enabled = true) {
|
||||
return MakeAction("Error", nullptr, active, enabled);
|
||||
}
|
||||
|
||||
inline ::XCEngine::Components::GameObject* GetSelectedGameObject(IEditorContext& context) {
|
||||
return context.GetSceneManager().GetEntity(context.GetSelectionManager().GetSelectedEntity());
|
||||
}
|
||||
|
||||
} // namespace Actions
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "Layers/EditorLayer.h"
|
||||
#include "Core/EditorContext.h"
|
||||
#include "Core/EditorConsoleSink.h"
|
||||
#include "Core/EditorEvents.h"
|
||||
#include "Core/EventBus.h"
|
||||
#include <XCEngine/Debug/Logger.h>
|
||||
#include <XCEngine/Debug/FileLogSink.h>
|
||||
#include <XCEngine/Debug/ConsoleLogSink.h>
|
||||
@@ -64,6 +66,10 @@ std::string GetExecutableLogPath(const char* fileName) {
|
||||
return GetExecutableDirectoryUtf8() + "\\" + fileName;
|
||||
}
|
||||
|
||||
std::string BuildEditorConfigDirectory(const std::string& projectPath) {
|
||||
return (std::filesystem::path(projectPath) / ".xceditor").string();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
static LONG WINAPI GlobalExceptionFilter(EXCEPTION_POINTERS* exceptionPointers) {
|
||||
@@ -128,28 +134,21 @@ bool Application::Initialize(HWND hwnd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
|
||||
io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", 16.0f);
|
||||
io.Fonts->AddFontDefault();
|
||||
|
||||
unsigned char* pixels;
|
||||
int width, height;
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
||||
|
||||
ApplyUnityDarkTheme();
|
||||
m_editorContext = std::make_shared<EditorContext>();
|
||||
m_editorContext->SetProjectPath(exeDir);
|
||||
m_exitRequestedHandlerId = m_editorContext->GetEventBus().Subscribe<EditorExitRequestedEvent>(
|
||||
[this](const EditorExitRequestedEvent&) {
|
||||
if (m_hwnd) {
|
||||
PostMessageW(m_hwnd, WM_CLOSE, 0, 0);
|
||||
}
|
||||
});
|
||||
ConfigureImGui(m_editorContext->GetProjectPath());
|
||||
|
||||
ImGui_ImplWin32_Init(hwnd);
|
||||
ImGui_ImplDX12_Init(m_device, 3, DXGI_FORMAT_R8G8B8A8_UNORM, m_srvHeap,
|
||||
m_srvHeap->GetCPUDescriptorHandleForHeapStart(),
|
||||
m_srvHeap->GetGPUDescriptorHandleForHeapStart());
|
||||
|
||||
m_editorContext = std::make_shared<EditorContext>();
|
||||
m_editorContext->SetProjectPath(exeDir);
|
||||
|
||||
m_editorLayer = new EditorLayer();
|
||||
m_editorLayer->SetContext(m_editorContext);
|
||||
m_layerStack.pushLayer(std::unique_ptr<Core::Layer>(m_editorLayer));
|
||||
@@ -160,6 +159,12 @@ bool Application::Initialize(HWND hwnd) {
|
||||
|
||||
void Application::Shutdown() {
|
||||
m_layerStack.onDetach();
|
||||
SaveImGuiSettings();
|
||||
|
||||
if (m_editorContext && m_exitRequestedHandlerId) {
|
||||
m_editorContext->GetEventBus().Unsubscribe<EditorExitRequestedEvent>(m_exitRequestedHandlerId);
|
||||
m_exitRequestedHandlerId = 0;
|
||||
}
|
||||
|
||||
ImGui_ImplDX12_Shutdown();
|
||||
ImGui_ImplWin32_Shutdown();
|
||||
@@ -200,7 +205,7 @@ void Application::Render() {
|
||||
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
||||
m_commandList->ResourceBarrier(1, &barrier);
|
||||
|
||||
float clearColor[4] = { 0.12f, 0.12f, 0.12f, 1.0f };
|
||||
float clearColor[4] = { 0.22f, 0.22f, 0.22f, 1.0f };
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_rtvHeap->GetCPUDescriptorHandleForHeapStart();
|
||||
rtvHandle.ptr += m_frameIndex * m_rtvDescriptorSize;
|
||||
m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
|
||||
@@ -228,6 +233,45 @@ void Application::Render() {
|
||||
}
|
||||
}
|
||||
|
||||
void Application::ConfigureImGui(const std::string& projectPath) {
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
|
||||
ConfigureImGuiIniFile(projectPath, io);
|
||||
|
||||
if (ImFont* uiFont = io.Fonts->AddFontFromFileTTF("C:/Windows/Fonts/msyh.ttc", 15.0f)) {
|
||||
io.FontDefault = uiFont;
|
||||
} else {
|
||||
io.FontDefault = io.Fonts->AddFontDefault();
|
||||
}
|
||||
|
||||
unsigned char* pixels = nullptr;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
|
||||
|
||||
ApplyUnityDarkTheme();
|
||||
}
|
||||
|
||||
void Application::ConfigureImGuiIniFile(const std::string& projectPath, ImGuiIO& io) {
|
||||
const std::filesystem::path configDir = BuildEditorConfigDirectory(projectPath);
|
||||
std::error_code ec;
|
||||
std::filesystem::create_directories(configDir, ec);
|
||||
|
||||
m_imguiIniPath = (configDir / "imgui_layout.ini").string();
|
||||
io.IniFilename = m_imguiIniPath.c_str();
|
||||
}
|
||||
|
||||
void Application::SaveImGuiSettings() {
|
||||
if (m_imguiIniPath.empty() || ImGui::GetCurrentContext() == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::SaveIniSettingsToDisk(m_imguiIniPath.c_str());
|
||||
}
|
||||
|
||||
void Application::UpdateWindowTitle() {
|
||||
if (!m_hwnd || !m_editorContext) {
|
||||
return;
|
||||
@@ -249,7 +293,7 @@ void Application::UpdateWindowTitle() {
|
||||
sceneName += std::filesystem::path(sceneManager.GetCurrentScenePath()).filename().string();
|
||||
}
|
||||
|
||||
const std::wstring title = Utf8ToWide(sceneName + " - XCVolumeRenderer - Unity Style Editor");
|
||||
const std::wstring title = Utf8ToWide(sceneName + " - XCEngine Editor");
|
||||
if (title != m_lastWindowTitle) {
|
||||
SetWindowTextW(m_hwnd, title.c_str());
|
||||
m_lastWindowTitle = title;
|
||||
|
||||
@@ -32,6 +32,9 @@ private:
|
||||
Application() = default;
|
||||
~Application() = default;
|
||||
|
||||
void ConfigureImGui(const std::string& projectPath);
|
||||
void ConfigureImGuiIniFile(const std::string& projectPath, ImGuiIO& io);
|
||||
void SaveImGuiSettings();
|
||||
bool CreateDevice();
|
||||
bool CreateRenderTarget();
|
||||
void CleanupRenderTarget();
|
||||
@@ -57,6 +60,8 @@ private:
|
||||
Core::LayerStack m_layerStack;
|
||||
EditorLayer* m_editorLayer = nullptr;
|
||||
std::shared_ptr<IEditorContext> m_editorContext;
|
||||
uint64_t m_exitRequestedHandlerId = 0;
|
||||
std::string m_imguiIniPath;
|
||||
std::wstring m_lastWindowTitle;
|
||||
};
|
||||
|
||||
|
||||
74
editor/src/Commands/ComponentCommands.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include "ComponentEditors/ComponentEditorRegistry.h"
|
||||
#include "ComponentEditors/IComponentEditor.h"
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/ISceneManager.h"
|
||||
#include "Utils/UndoUtils.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace Commands {
|
||||
|
||||
inline bool CanAddComponent(const IComponentEditor& editor, ::XCEngine::Components::GameObject* gameObject) {
|
||||
return gameObject != nullptr && editor.CanAddTo(gameObject);
|
||||
}
|
||||
|
||||
inline bool AddComponent(
|
||||
IEditorContext& context,
|
||||
const IComponentEditor& editor,
|
||||
::XCEngine::Components::GameObject* gameObject,
|
||||
const std::string& commandLabel = {}) {
|
||||
if (!CanAddComponent(editor, gameObject)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool added = false;
|
||||
const std::string label =
|
||||
commandLabel.empty() ? std::string("Add ") + editor.GetDisplayName() + " Component" : commandLabel;
|
||||
UndoUtils::ExecuteSceneCommand(context, label, [&]() {
|
||||
added = editor.AddTo(gameObject) != nullptr;
|
||||
if (added) {
|
||||
context.GetSceneManager().MarkSceneDirty();
|
||||
}
|
||||
});
|
||||
return added;
|
||||
}
|
||||
|
||||
inline bool CanRemoveComponent(
|
||||
::XCEngine::Components::Component* component,
|
||||
const IComponentEditor* editor = nullptr) {
|
||||
if (!component) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const IComponentEditor* resolvedEditor =
|
||||
editor ? editor : ComponentEditorRegistry::Get().FindEditor(component);
|
||||
return resolvedEditor != nullptr && resolvedEditor->CanRemove(component);
|
||||
}
|
||||
|
||||
inline bool RemoveComponent(
|
||||
IEditorContext& context,
|
||||
::XCEngine::Components::Component* component,
|
||||
::XCEngine::Components::GameObject* gameObject,
|
||||
const IComponentEditor* editor = nullptr,
|
||||
const std::string& commandLabel = {}) {
|
||||
if (!gameObject || !CanRemoveComponent(component, editor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string label = commandLabel.empty()
|
||||
? std::string("Remove ") + component->GetName() + " Component"
|
||||
: commandLabel;
|
||||
UndoUtils::ExecuteSceneCommand(context, label, [&]() {
|
||||
gameObject->RemoveComponent(component);
|
||||
context.GetSceneManager().MarkSceneDirty();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Commands
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
217
editor/src/Commands/EntityCommands.h
Normal file
@@ -0,0 +1,217 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/ISelectionManager.h"
|
||||
#include "Core/ISceneManager.h"
|
||||
#include "Utils/UndoUtils.h"
|
||||
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
#include <XCEngine/Components/LightComponent.h>
|
||||
#include <XCEngine/Core/Math/Quaternion.h>
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace Commands {
|
||||
|
||||
struct NoopEntitySetup {
|
||||
void operator()(::XCEngine::Components::GameObject&, ISceneManager&) const {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename SetupFn = NoopEntitySetup>
|
||||
inline ::XCEngine::Components::GameObject* CreateEntity(
|
||||
IEditorContext& context,
|
||||
const std::string& commandLabel,
|
||||
const std::string& entityName,
|
||||
::XCEngine::Components::GameObject* parent = nullptr,
|
||||
SetupFn setup = {}) {
|
||||
::XCEngine::Components::GameObject* created = nullptr;
|
||||
UndoUtils::ExecuteSceneCommand(context, commandLabel, [&]() {
|
||||
auto& sceneManager = context.GetSceneManager();
|
||||
created = sceneManager.CreateEntity(entityName, parent);
|
||||
if (!created) {
|
||||
return;
|
||||
}
|
||||
|
||||
setup(*created, sceneManager);
|
||||
context.GetSelectionManager().SetSelectedEntity(created->GetID());
|
||||
});
|
||||
return created;
|
||||
}
|
||||
|
||||
inline ::XCEngine::Components::GameObject* CreateEmptyEntity(
|
||||
IEditorContext& context,
|
||||
::XCEngine::Components::GameObject* parent = nullptr,
|
||||
const std::string& commandLabel = "Create Entity",
|
||||
const std::string& entityName = "GameObject") {
|
||||
return CreateEntity(context, commandLabel, entityName, parent);
|
||||
}
|
||||
|
||||
inline ::XCEngine::Components::GameObject* CreateCameraEntity(
|
||||
IEditorContext& context,
|
||||
::XCEngine::Components::GameObject* parent = nullptr,
|
||||
const std::string& commandLabel = "Create Camera",
|
||||
const std::string& entityName = "Camera") {
|
||||
return CreateEntity(
|
||||
context,
|
||||
commandLabel,
|
||||
entityName,
|
||||
parent,
|
||||
[](::XCEngine::Components::GameObject& entity, ISceneManager&) {
|
||||
entity.AddComponent<::XCEngine::Components::CameraComponent>();
|
||||
});
|
||||
}
|
||||
|
||||
inline ::XCEngine::Components::GameObject* CreateLightEntity(
|
||||
IEditorContext& context,
|
||||
::XCEngine::Components::GameObject* parent = nullptr,
|
||||
const std::string& commandLabel = "Create Light",
|
||||
const std::string& entityName = "Light") {
|
||||
return CreateEntity(
|
||||
context,
|
||||
commandLabel,
|
||||
entityName,
|
||||
parent,
|
||||
[](::XCEngine::Components::GameObject& entity, ISceneManager&) {
|
||||
entity.AddComponent<::XCEngine::Components::LightComponent>();
|
||||
});
|
||||
}
|
||||
|
||||
inline bool RenameEntity(
|
||||
IEditorContext& context,
|
||||
::XCEngine::Components::GameObject::ID entityId,
|
||||
const std::string& newName,
|
||||
const std::string& commandLabel = "Rename Entity") {
|
||||
if (newName.empty() || !context.GetSceneManager().GetEntity(entityId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UndoUtils::ExecuteSceneCommand(context, commandLabel, [&]() {
|
||||
context.GetSceneManager().RenameEntity(entityId, newName);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool DeleteEntity(
|
||||
IEditorContext& context,
|
||||
::XCEngine::Components::GameObject::ID entityId,
|
||||
const std::string& commandLabel = "Delete Entity") {
|
||||
if (!context.GetSceneManager().GetEntity(entityId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UndoUtils::ExecuteSceneCommand(context, commandLabel, [&]() {
|
||||
context.GetSceneManager().DeleteEntity(entityId);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool CopyEntity(IEditorContext& context, ::XCEngine::Components::GameObject::ID entityId) {
|
||||
if (!context.GetSceneManager().GetEntity(entityId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
context.GetSceneManager().CopyEntity(entityId);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline ::XCEngine::Components::GameObject::ID PasteEntity(
|
||||
IEditorContext& context,
|
||||
::XCEngine::Components::GameObject::ID parentId = 0,
|
||||
const std::string& commandLabel = "Paste Entity") {
|
||||
::XCEngine::Components::GameObject::ID newId = 0;
|
||||
UndoUtils::ExecuteSceneCommand(context, commandLabel, [&]() {
|
||||
newId = context.GetSceneManager().PasteEntity(parentId);
|
||||
if (newId != 0) {
|
||||
context.GetSelectionManager().SetSelectedEntity(newId);
|
||||
}
|
||||
});
|
||||
return newId;
|
||||
}
|
||||
|
||||
inline ::XCEngine::Components::GameObject::ID DuplicateEntity(
|
||||
IEditorContext& context,
|
||||
::XCEngine::Components::GameObject::ID entityId,
|
||||
const std::string& commandLabel = "Duplicate Entity") {
|
||||
if (!context.GetSceneManager().GetEntity(entityId)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
::XCEngine::Components::GameObject::ID newId = 0;
|
||||
UndoUtils::ExecuteSceneCommand(context, commandLabel, [&]() {
|
||||
newId = context.GetSceneManager().DuplicateEntity(entityId);
|
||||
if (newId != 0) {
|
||||
context.GetSelectionManager().SetSelectedEntity(newId);
|
||||
}
|
||||
});
|
||||
return newId;
|
||||
}
|
||||
|
||||
inline bool CanReparentEntity(
|
||||
const ::XCEngine::Components::GameObject* source,
|
||||
const ::XCEngine::Components::GameObject* newParent) {
|
||||
if (!source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto* current = newParent; current != nullptr; current = current->GetParent()) {
|
||||
if (current == source) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool ReparentEntityPreserveWorldTransform(
|
||||
IEditorContext& context,
|
||||
::XCEngine::Components::GameObject* source,
|
||||
::XCEngine::Components::GameObject::ID newParentId,
|
||||
const std::string& commandLabel = "Reparent Entity") {
|
||||
if (!source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ::XCEngine::Components::GameObject::ID currentParentId =
|
||||
source->GetParent() ? source->GetParent()->GetID() : 0;
|
||||
if (currentParentId == newParentId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
UndoUtils::ExecuteSceneCommand(context, commandLabel, [&]() {
|
||||
auto& sceneManager = context.GetSceneManager();
|
||||
auto* transform = source->GetTransform();
|
||||
if (!transform) {
|
||||
sceneManager.MoveEntity(source->GetID(), newParentId);
|
||||
return;
|
||||
}
|
||||
|
||||
const ::XCEngine::Math::Vector3 worldPos = transform->GetPosition();
|
||||
const ::XCEngine::Math::Quaternion worldRot = transform->GetRotation();
|
||||
const ::XCEngine::Math::Vector3 worldScale = transform->GetScale();
|
||||
|
||||
sceneManager.MoveEntity(source->GetID(), newParentId);
|
||||
transform->SetPosition(worldPos);
|
||||
transform->SetRotation(worldRot);
|
||||
transform->SetScale(worldScale);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool DetachEntity(
|
||||
IEditorContext& context,
|
||||
::XCEngine::Components::GameObject* entity,
|
||||
const std::string& commandLabel = "Reparent Entity") {
|
||||
if (!entity || entity->GetParent() == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ReparentEntityPreserveWorldTransform(context, entity, 0, commandLabel);
|
||||
}
|
||||
|
||||
} // namespace Commands
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
66
editor/src/Commands/ProjectCommands.h
Normal file
@@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core/AssetItem.h"
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/IProjectManager.h"
|
||||
#include "SceneCommands.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace Commands {
|
||||
|
||||
inline bool CanOpenAsset(const AssetItemPtr& item) {
|
||||
return item != nullptr && (item->isFolder || item->type == "Scene");
|
||||
}
|
||||
|
||||
inline bool OpenAsset(IEditorContext& context, const AssetItemPtr& item) {
|
||||
if (!CanOpenAsset(item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item->isFolder) {
|
||||
context.GetProjectManager().NavigateToFolder(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
return LoadScene(context, item->fullPath);
|
||||
}
|
||||
|
||||
inline bool CreateFolder(IProjectManager& projectManager, const std::string& name) {
|
||||
if (name.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
projectManager.CreateFolder(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool DeleteAsset(IProjectManager& projectManager, int index) {
|
||||
if (index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
projectManager.DeleteItem(index);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool MoveAssetToFolder(
|
||||
IProjectManager& projectManager,
|
||||
const std::string& sourceFullPath,
|
||||
const AssetItemPtr& targetFolder) {
|
||||
if (sourceFullPath.empty() || !targetFolder || !targetFolder->isFolder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceFullPath == targetFolder->fullPath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return projectManager.MoveItem(sourceFullPath, targetFolder->fullPath);
|
||||
}
|
||||
|
||||
} // namespace Commands
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
108
editor/src/Commands/SceneCommands.h
Normal file
@@ -0,0 +1,108 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/IProjectManager.h"
|
||||
#include "Core/ISelectionManager.h"
|
||||
#include "Core/ISceneManager.h"
|
||||
#include "Core/IUndoManager.h"
|
||||
#include "Utils/SceneEditorUtils.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace Commands {
|
||||
|
||||
inline void ResetSceneEditingState(IEditorContext& context) {
|
||||
context.GetSelectionManager().ClearSelection();
|
||||
context.GetUndoManager().ClearHistory();
|
||||
}
|
||||
|
||||
inline bool NewScene(IEditorContext& context, const std::string& sceneName = "Untitled Scene") {
|
||||
if (!SceneEditorUtils::ConfirmSceneSwitch(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
context.GetSceneManager().NewScene(sceneName);
|
||||
ResetSceneEditingState(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool LoadScene(IEditorContext& context, const std::string& filePath, bool confirmSwitch = true) {
|
||||
if (filePath.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (confirmSwitch && !SceneEditorUtils::ConfirmSceneSwitch(context)) {
|
||||
return false;
|
||||
}
|
||||
if (!context.GetSceneManager().LoadScene(filePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ResetSceneEditingState(context);
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool OpenSceneWithDialog(IEditorContext& context) {
|
||||
if (!SceneEditorUtils::ConfirmSceneSwitch(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string filePath = SceneEditorUtils::OpenSceneFileDialog(
|
||||
context.GetProjectPath(),
|
||||
context.GetSceneManager().GetCurrentScenePath());
|
||||
if (filePath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return LoadScene(context, filePath, false);
|
||||
}
|
||||
|
||||
inline bool SaveCurrentScene(IEditorContext& context) {
|
||||
return SceneEditorUtils::SaveCurrentScene(context);
|
||||
}
|
||||
|
||||
inline bool SaveSceneAsWithDialog(IEditorContext& context) {
|
||||
auto& sceneManager = context.GetSceneManager();
|
||||
const std::string filePath = SceneEditorUtils::SaveSceneFileDialog(
|
||||
context.GetProjectPath(),
|
||||
sceneManager.GetCurrentScenePath(),
|
||||
sceneManager.GetCurrentSceneName());
|
||||
if (filePath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool saved = sceneManager.SaveSceneAs(filePath);
|
||||
if (saved) {
|
||||
context.GetProjectManager().RefreshCurrentFolder();
|
||||
}
|
||||
return saved;
|
||||
}
|
||||
|
||||
inline bool LoadStartupScene(IEditorContext& context) {
|
||||
const bool loaded = context.GetSceneManager().LoadStartupScene(context.GetProjectPath());
|
||||
context.GetProjectManager().RefreshCurrentFolder();
|
||||
ResetSceneEditingState(context);
|
||||
return loaded;
|
||||
}
|
||||
|
||||
inline bool SaveDirtySceneWithFallback(IEditorContext& context, const std::string& fallbackPath) {
|
||||
auto& sceneManager = context.GetSceneManager();
|
||||
if (!sceneManager.HasActiveScene() || !sceneManager.IsSceneDirty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sceneManager.SaveScene()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (fallbackPath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return sceneManager.SaveSceneAs(fallbackPath);
|
||||
}
|
||||
|
||||
} // namespace Commands
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -25,72 +25,75 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr const char* kUndoLabel = "Modify Camera";
|
||||
|
||||
int projectionType = static_cast<int>(camera->GetProjectionType());
|
||||
const char* projectionLabels[] = { "Perspective", "Orthographic" };
|
||||
bool changed = false;
|
||||
if (ImGui::Combo("Projection", &projectionType, projectionLabels, 2)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Camera");
|
||||
}
|
||||
camera->SetProjectionType(static_cast<::XCEngine::Components::CameraProjectionType>(projectionType));
|
||||
changed = true;
|
||||
}
|
||||
const int newProjectionType = UI::DrawPropertyCombo("Projection", projectionType, projectionLabels, 2);
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
newProjectionType != projectionType,
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
camera->SetProjectionType(static_cast<::XCEngine::Components::CameraProjectionType>(newProjectionType));
|
||||
});
|
||||
|
||||
if (camera->GetProjectionType() == ::XCEngine::Components::CameraProjectionType::Perspective) {
|
||||
float fieldOfView = camera->GetFieldOfView();
|
||||
if (UI::DrawSliderFloat("Field Of View", fieldOfView, 1.0f, 179.0f, 100.0f, "%.1f")) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Camera");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertySliderFloat("Field Of View", fieldOfView, 1.0f, 179.0f, "%.1f"),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
camera->SetFieldOfView(fieldOfView);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
float orthographicSize = camera->GetOrthographicSize();
|
||||
if (UI::DrawFloat("Orthographic Size", orthographicSize, 100.0f, 0.1f, 0.001f)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Camera");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertyFloat("Orthographic Size", orthographicSize, 0.1f, 0.001f),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
camera->SetOrthographicSize(orthographicSize);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
float nearClip = camera->GetNearClipPlane();
|
||||
if (UI::DrawFloat("Near Clip", nearClip, 100.0f, 0.01f, 0.001f)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Camera");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertyFloat("Near Clip", nearClip, 0.01f, 0.001f),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
camera->SetNearClipPlane(nearClip);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
float farClip = camera->GetFarClipPlane();
|
||||
if (UI::DrawFloat("Far Clip", farClip, 100.0f, 0.1f, nearClip + 0.001f)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Camera");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertyFloat("Far Clip", farClip, 0.1f, nearClip + 0.001f),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
camera->SetFarClipPlane(farClip);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
float depth = camera->GetDepth();
|
||||
if (UI::DrawFloat("Depth", depth, 100.0f, 0.1f)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Camera");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertyFloat("Depth", depth, 0.1f),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
camera->SetDepth(depth);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
bool primary = camera->IsPrimary();
|
||||
if (UI::DrawBool("Primary", primary)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Camera");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertyBool("Primary", primary),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
camera->SetPrimary(primary);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
float clearColor[4] = {
|
||||
camera->GetClearColor().r,
|
||||
@@ -98,13 +101,13 @@ public:
|
||||
camera->GetClearColor().b,
|
||||
camera->GetClearColor().a
|
||||
};
|
||||
if (UI::DrawColor4("Clear Color", clearColor)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Camera");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertyColor4("Clear Color", clearColor),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
camera->SetClearColor(::XCEngine::Math::Color(clearColor[0], clearColor[1], clearColor[2], clearColor[3]));
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -25,16 +25,19 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr const char* kUndoLabel = "Modify Light";
|
||||
|
||||
int lightType = static_cast<int>(light->GetLightType());
|
||||
const char* lightTypeLabels[] = { "Directional", "Point", "Spot" };
|
||||
bool changed = false;
|
||||
if (ImGui::Combo("Type", &lightType, lightTypeLabels, 3)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Light");
|
||||
}
|
||||
light->SetLightType(static_cast<::XCEngine::Components::LightType>(lightType));
|
||||
changed = true;
|
||||
}
|
||||
const int newLightType = UI::DrawPropertyCombo("Type", lightType, lightTypeLabels, 3);
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
newLightType != lightType,
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
light->SetLightType(static_cast<::XCEngine::Components::LightType>(newLightType));
|
||||
});
|
||||
|
||||
float color[4] = {
|
||||
light->GetColor().r,
|
||||
@@ -42,53 +45,53 @@ public:
|
||||
light->GetColor().b,
|
||||
light->GetColor().a
|
||||
};
|
||||
if (UI::DrawColor4("Color", color)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Light");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertyColor4("Color", color),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
light->SetColor(::XCEngine::Math::Color(color[0], color[1], color[2], color[3]));
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
float intensity = light->GetIntensity();
|
||||
if (UI::DrawFloat("Intensity", intensity, 100.0f, 0.1f, 0.0f)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Light");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertyFloat("Intensity", intensity, 0.1f, 0.0f),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
light->SetIntensity(intensity);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (light->GetLightType() != ::XCEngine::Components::LightType::Directional) {
|
||||
float range = light->GetRange();
|
||||
if (UI::DrawFloat("Range", range, 100.0f, 0.1f, 0.001f)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Light");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertyFloat("Range", range, 0.1f, 0.001f),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
light->SetRange(range);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (light->GetLightType() == ::XCEngine::Components::LightType::Spot) {
|
||||
float spotAngle = light->GetSpotAngle();
|
||||
if (UI::DrawSliderFloat("Spot Angle", spotAngle, 1.0f, 179.0f, 100.0f, "%.1f")) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Light");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertySliderFloat("Spot Angle", spotAngle, 1.0f, 179.0f, "%.1f"),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
light->SetSpotAngle(spotAngle);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool castsShadows = light->GetCastsShadows();
|
||||
if (UI::DrawBool("Cast Shadows", castsShadows)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Light");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertyBool("Cast Shadows", castsShadows),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
light->SetCastsShadows(castsShadows);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include "UI/UI.h"
|
||||
|
||||
#include <XCEngine/Components/TransformComponent.h>
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
@@ -25,34 +27,46 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr const char* kUndoLabel = "Modify Transform";
|
||||
|
||||
RotationEditState& rotationState = m_rotationStates[transform];
|
||||
::XCEngine::Math::Vector3 position = transform->GetLocalPosition();
|
||||
::XCEngine::Math::Vector3 rotation = transform->GetLocalEulerAngles();
|
||||
::XCEngine::Math::Vector3 scale = transform->GetLocalScale();
|
||||
const ::XCEngine::Math::Quaternion currentRotation = transform->GetLocalRotation();
|
||||
if (!rotationState.initialized || (!rotationState.isEditing && !SameRotation(currentRotation, rotationState.lastRotation))) {
|
||||
rotationState.displayedEuler = transform->GetLocalEulerAngles();
|
||||
rotationState.lastRotation = currentRotation;
|
||||
rotationState.initialized = true;
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
|
||||
if (UI::DrawVec3("Position", position, 0.0f, 80.0f, 0.1f)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Transform");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertyVec3Input("Position", position, 0.1f),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
transform->SetLocalPosition(position);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (UI::DrawVec3("Rotation", rotation, 0.0f, 80.0f, 1.0f)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Transform");
|
||||
}
|
||||
transform->SetLocalEulerAngles(rotation);
|
||||
changed = true;
|
||||
}
|
||||
bool rotationActive = false;
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertyVec3Input("Rotation", rotationState.displayedEuler, 1.0f, &rotationActive),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
transform->SetLocalEulerAngles(rotationState.displayedEuler);
|
||||
rotationState.lastRotation = transform->GetLocalRotation();
|
||||
});
|
||||
rotationState.isEditing = rotationActive;
|
||||
|
||||
if (UI::DrawVec3("Scale", scale, 1.0f, 80.0f, 0.1f)) {
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange("Modify Transform");
|
||||
}
|
||||
changed |= UI::ApplyPropertyChange(
|
||||
UI::DrawPropertyVec3Input("Scale", scale, 0.1f),
|
||||
undoManager,
|
||||
kUndoLabel,
|
||||
[&]() {
|
||||
transform->SetLocalScale(scale);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
|
||||
return changed;
|
||||
}
|
||||
@@ -71,6 +85,20 @@ public:
|
||||
(void)component;
|
||||
return false;
|
||||
}
|
||||
|
||||
private:
|
||||
struct RotationEditState {
|
||||
::XCEngine::Math::Vector3 displayedEuler = ::XCEngine::Math::Vector3::Zero();
|
||||
::XCEngine::Math::Quaternion lastRotation = ::XCEngine::Math::Quaternion::Identity();
|
||||
bool initialized = false;
|
||||
bool isEditing = false;
|
||||
};
|
||||
|
||||
static bool SameRotation(const ::XCEngine::Math::Quaternion& a, const ::XCEngine::Math::Quaternion& b) {
|
||||
return std::abs(a.Dot(b)) > 0.9999f;
|
||||
}
|
||||
|
||||
std::unordered_map<::XCEngine::Components::TransformComponent*, RotationEditState> m_rotationStates;
|
||||
};
|
||||
|
||||
} // namespace Editor
|
||||
|
||||
@@ -48,5 +48,11 @@ struct EditorModeChangedEvent {
|
||||
int newMode;
|
||||
};
|
||||
|
||||
struct DockLayoutResetRequestedEvent {
|
||||
};
|
||||
|
||||
struct EditorExitRequestedEvent {
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "Commands/SceneCommands.h"
|
||||
#include "EditorLayer.h"
|
||||
#include "Layout/DockLayoutController.h"
|
||||
#include "panels/MenuBar.h"
|
||||
#include "panels/HierarchyPanel.h"
|
||||
#include "panels/SceneViewPanel.h"
|
||||
@@ -9,14 +11,20 @@
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/EditorContext.h"
|
||||
#include "Core/IUndoManager.h"
|
||||
#include "UI/DockHostStyle.h"
|
||||
#include <filesystem>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string BuildFallbackScenePath(const IEditorContext& context) {
|
||||
return (std::filesystem::path(context.GetProjectPath()) / "Assets" / "Scenes" / "Main.xc").string();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EditorLayer::EditorLayer() : Layer("Editor") {}
|
||||
|
||||
void EditorLayer::SetContext(std::shared_ptr<IEditorContext> context) {
|
||||
@@ -28,137 +36,48 @@ void EditorLayer::onAttach() {
|
||||
m_context = std::make_shared<EditorContext>();
|
||||
}
|
||||
|
||||
m_menuBar = std::make_unique<MenuBar>();
|
||||
m_hierarchyPanel = std::make_unique<HierarchyPanel>();
|
||||
m_sceneViewPanel = std::make_unique<SceneViewPanel>();
|
||||
m_gameViewPanel = std::make_unique<GameViewPanel>();
|
||||
m_inspectorPanel = std::make_unique<InspectorPanel>();
|
||||
m_consolePanel = std::make_unique<ConsolePanel>();
|
||||
m_projectPanel = std::make_unique<ProjectPanel>();
|
||||
|
||||
m_menuBar->SetContext(m_context.get());
|
||||
m_hierarchyPanel->SetContext(m_context.get());
|
||||
m_sceneViewPanel->SetContext(m_context.get());
|
||||
m_gameViewPanel->SetContext(m_context.get());
|
||||
m_inspectorPanel->SetContext(m_context.get());
|
||||
m_consolePanel->SetContext(m_context.get());
|
||||
m_projectPanel->SetContext(m_context.get());
|
||||
m_panels.Clear();
|
||||
m_panels.SetContext(m_context.get());
|
||||
m_panels.Emplace<MenuBar>();
|
||||
m_panels.Emplace<HierarchyPanel>();
|
||||
m_panels.Emplace<SceneViewPanel>();
|
||||
m_panels.Emplace<GameViewPanel>();
|
||||
m_panels.Emplace<InspectorPanel>();
|
||||
m_panels.Emplace<ConsolePanel>();
|
||||
m_projectPanel = &m_panels.Emplace<ProjectPanel>();
|
||||
m_dockLayoutController = std::make_unique<DockLayoutController>();
|
||||
|
||||
m_projectPanel->Initialize(m_context->GetProjectPath());
|
||||
m_context->GetSceneManager().LoadStartupScene(m_context->GetProjectPath());
|
||||
m_context->GetProjectManager().RefreshCurrentFolder();
|
||||
m_context->GetUndoManager().ClearHistory();
|
||||
|
||||
m_menuBar->OnAttach();
|
||||
m_hierarchyPanel->OnAttach();
|
||||
m_sceneViewPanel->OnAttach();
|
||||
m_gameViewPanel->OnAttach();
|
||||
m_inspectorPanel->OnAttach();
|
||||
m_consolePanel->OnAttach();
|
||||
m_projectPanel->OnAttach();
|
||||
Commands::LoadStartupScene(*m_context);
|
||||
m_dockLayoutController->Attach(*m_context);
|
||||
m_panels.AttachAll();
|
||||
}
|
||||
|
||||
void EditorLayer::onDetach() {
|
||||
auto& sceneManager = m_context->GetSceneManager();
|
||||
if (sceneManager.HasActiveScene() && sceneManager.IsSceneDirty()) {
|
||||
if (!sceneManager.SaveScene()) {
|
||||
const std::string fallbackPath =
|
||||
(std::filesystem::path(m_context->GetProjectPath()) / "Assets" / "Scenes" / "Main.xc").string();
|
||||
sceneManager.SaveSceneAs(fallbackPath);
|
||||
}
|
||||
if (m_context) {
|
||||
Commands::SaveDirtySceneWithFallback(*m_context, BuildFallbackScenePath(*m_context));
|
||||
}
|
||||
|
||||
m_menuBar->OnDetach();
|
||||
m_hierarchyPanel->OnDetach();
|
||||
m_sceneViewPanel->OnDetach();
|
||||
m_gameViewPanel->OnDetach();
|
||||
m_inspectorPanel->OnDetach();
|
||||
m_consolePanel->OnDetach();
|
||||
m_projectPanel->OnDetach();
|
||||
if (m_dockLayoutController) {
|
||||
m_dockLayoutController->Detach();
|
||||
}
|
||||
|
||||
m_panels.DetachAll();
|
||||
m_panels.Clear();
|
||||
m_projectPanel = nullptr;
|
||||
}
|
||||
|
||||
void EditorLayer::onUpdate(float dt) {
|
||||
m_panels.UpdateAll(dt);
|
||||
}
|
||||
|
||||
void EditorLayer::onEvent(void* event) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
// TODO: These functions don't exist - need to implement them
|
||||
// if (ImGui::IsKeyPressed(ImGuiKey_F5)) {
|
||||
// TogglePlay();
|
||||
// }
|
||||
//
|
||||
// if (ImGui::IsKeyPressed(ImGuiKey_F6)) {
|
||||
// if (GetEditorMode() != EditorMode::Edit) {
|
||||
// TogglePause();
|
||||
// }
|
||||
// }
|
||||
m_panels.DispatchEvent(event);
|
||||
}
|
||||
|
||||
void EditorLayer::onImGuiRender() {
|
||||
setupDockspace();
|
||||
renderAllPanels();
|
||||
}
|
||||
|
||||
void EditorLayer::setupDockspace() {
|
||||
static ImGuiDockNodeFlags dockspaceFlags = ImGuiDockNodeFlags_NoWindowMenuButton;
|
||||
|
||||
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
|
||||
|
||||
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(viewport->Pos);
|
||||
ImGui::SetNextWindowSize(viewport->Size);
|
||||
ImGui::SetNextWindowViewport(viewport->ID);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
windowFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
|
||||
windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::Begin("MainDockspace", nullptr, windowFlags);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
ImGuiID dockspaceId = ImGui::GetID("MyDockspace");
|
||||
{
|
||||
UI::DockHostStyleScope dockHostStyle;
|
||||
ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockspaceFlags);
|
||||
}
|
||||
|
||||
static bool firstTime = true;
|
||||
if (firstTime) {
|
||||
firstTime = false;
|
||||
ImGui::DockBuilderRemoveNode(dockspaceId);
|
||||
ImGui::DockBuilderAddNode(dockspaceId, dockspaceFlags | ImGuiDockNodeFlags_DockSpace);
|
||||
ImGui::DockBuilderSetNodeSize(dockspaceId, viewport->Size);
|
||||
|
||||
ImGuiID dockMain = dockspaceId;
|
||||
ImGuiID dockBottom = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Down, 0.25f, nullptr, &dockMain);
|
||||
ImGuiID dockLeft = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Left, 0.15f, nullptr, &dockMain);
|
||||
ImGuiID dockRight = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Right, 0.25f, nullptr, &dockMain);
|
||||
|
||||
ImGui::DockBuilderDockWindow("Hierarchy", dockLeft);
|
||||
ImGui::DockBuilderDockWindow("Scene", dockMain);
|
||||
ImGui::DockBuilderDockWindow("Game", dockMain);
|
||||
ImGui::DockBuilderDockWindow("Inspector", dockRight);
|
||||
ImGui::DockBuilderDockWindow("Console", dockBottom);
|
||||
ImGui::DockBuilderDockWindow("Project", dockBottom);
|
||||
|
||||
ImGui::DockBuilderFinish(dockspaceId);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void EditorLayer::renderAllPanels() {
|
||||
m_menuBar->Render();
|
||||
|
||||
m_hierarchyPanel->Render();
|
||||
m_sceneViewPanel->Render();
|
||||
m_gameViewPanel->Render();
|
||||
m_inspectorPanel->Render();
|
||||
m_consolePanel->Render();
|
||||
m_projectPanel->Render();
|
||||
m_dockLayoutController->RenderDockspace();
|
||||
m_panels.RenderAll();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "panels/PanelCollection.h"
|
||||
|
||||
#include <XCEngine/Core/Layer.h>
|
||||
#include <XCEngine/Core/LayerStack.h>
|
||||
#include <memory>
|
||||
@@ -16,6 +18,7 @@ class GameViewPanel;
|
||||
class InspectorPanel;
|
||||
class ConsolePanel;
|
||||
class ProjectPanel;
|
||||
class DockLayoutController;
|
||||
|
||||
class EditorLayer : public Core::Layer {
|
||||
public:
|
||||
@@ -31,18 +34,10 @@ public:
|
||||
void SetContext(std::shared_ptr<IEditorContext> context);
|
||||
|
||||
private:
|
||||
void setupDockspace();
|
||||
void renderAllPanels();
|
||||
|
||||
std::shared_ptr<IEditorContext> m_context;
|
||||
|
||||
std::unique_ptr<MenuBar> m_menuBar;
|
||||
std::unique_ptr<HierarchyPanel> m_hierarchyPanel;
|
||||
std::unique_ptr<SceneViewPanel> m_sceneViewPanel;
|
||||
std::unique_ptr<GameViewPanel> m_gameViewPanel;
|
||||
std::unique_ptr<InspectorPanel> m_inspectorPanel;
|
||||
std::unique_ptr<ConsolePanel> m_consolePanel;
|
||||
std::unique_ptr<ProjectPanel> m_projectPanel;
|
||||
std::unique_ptr<DockLayoutController> m_dockLayoutController;
|
||||
PanelCollection m_panels;
|
||||
ProjectPanel* m_projectPanel = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
101
editor/src/Layout/DockLayoutController.h
Normal file
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core/EditorEvents.h"
|
||||
#include "Core/EventBus.h"
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "UI/DockHostStyle.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
class DockLayoutController {
|
||||
public:
|
||||
void Attach(IEditorContext& context) {
|
||||
if (m_context == &context) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_context = &context;
|
||||
m_resetLayoutHandlerId = context.GetEventBus().Subscribe<DockLayoutResetRequestedEvent>(
|
||||
[this](const DockLayoutResetRequestedEvent&) {
|
||||
RequestReset();
|
||||
});
|
||||
}
|
||||
|
||||
void Detach() {
|
||||
if (m_context && m_resetLayoutHandlerId) {
|
||||
m_context->GetEventBus().Unsubscribe<DockLayoutResetRequestedEvent>(m_resetLayoutHandlerId);
|
||||
}
|
||||
|
||||
m_context = nullptr;
|
||||
m_resetLayoutHandlerId = 0;
|
||||
m_layoutDirty = true;
|
||||
}
|
||||
|
||||
void RequestReset() {
|
||||
m_layoutDirty = true;
|
||||
}
|
||||
|
||||
void RenderDockspace() {
|
||||
ImGuiWindowFlags windowFlags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoDocking;
|
||||
|
||||
ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(viewport->Pos);
|
||||
ImGui::SetNextWindowSize(viewport->Size);
|
||||
ImGui::SetNextWindowViewport(viewport->ID);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||
windowFlags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
|
||||
windowFlags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::Begin("MainDockspace", nullptr, windowFlags);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
const ImGuiID dockspaceId = ImGui::GetID("MainDockspace.Root");
|
||||
{
|
||||
UI::DockHostStyleScope dockHostStyle;
|
||||
ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), m_dockspaceFlags);
|
||||
}
|
||||
|
||||
if (m_layoutDirty) {
|
||||
BuildDefaultLayout(dockspaceId, viewport->Size);
|
||||
m_layoutDirty = false;
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
private:
|
||||
void BuildDefaultLayout(ImGuiID dockspaceId, const ImVec2& dockspaceSize) {
|
||||
ImGui::DockBuilderRemoveNode(dockspaceId);
|
||||
ImGui::DockBuilderAddNode(dockspaceId, m_dockspaceFlags | ImGuiDockNodeFlags_DockSpace);
|
||||
ImGui::DockBuilderSetNodeSize(dockspaceId, dockspaceSize);
|
||||
|
||||
ImGuiID dockMain = dockspaceId;
|
||||
ImGuiID dockBottom = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Down, 0.25f, nullptr, &dockMain);
|
||||
ImGuiID dockLeft = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Left, 0.15f, nullptr, &dockMain);
|
||||
ImGuiID dockRight = ImGui::DockBuilderSplitNode(dockMain, ImGuiDir_Right, 0.25f, nullptr, &dockMain);
|
||||
|
||||
ImGui::DockBuilderDockWindow("Hierarchy", dockLeft);
|
||||
ImGui::DockBuilderDockWindow("Scene", dockMain);
|
||||
ImGui::DockBuilderDockWindow("Game", dockMain);
|
||||
ImGui::DockBuilderDockWindow("Inspector", dockRight);
|
||||
ImGui::DockBuilderDockWindow("Console", dockBottom);
|
||||
ImGui::DockBuilderDockWindow("Project", dockBottom);
|
||||
|
||||
ImGui::DockBuilderFinish(dockspaceId);
|
||||
}
|
||||
|
||||
IEditorContext* m_context = nullptr;
|
||||
uint64_t m_resetLayoutHandlerId = 0;
|
||||
bool m_layoutDirty = true;
|
||||
ImGuiDockNodeFlags m_dockspaceFlags = ImGuiDockNodeFlags_NoWindowMenuButton;
|
||||
};
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,97 +1,12 @@
|
||||
#include "Theme.h"
|
||||
#include "UI/StyleTokens.h"
|
||||
#include "UI/BaseTheme.h"
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
void ApplyUnityDarkTheme() {
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImVec4* colors = style.Colors;
|
||||
|
||||
colors[ImGuiCol_Text] = ImVec4(0.80f, 0.80f, 0.80f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.53f, 0.53f, 0.53f, 1.00f);
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f);
|
||||
colors[ImGuiCol_ChildBg] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f);
|
||||
colors[ImGuiCol_PopupBg] = ImVec4(0.17f, 0.17f, 0.17f, 0.98f);
|
||||
colors[ImGuiCol_Border] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
|
||||
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.21f, 0.21f, 0.21f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.24f, 0.24f, 0.24f, 1.00f);
|
||||
colors[ImGuiCol_TitleBg] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_MenuBarBg] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.36f, 0.36f, 0.36f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.72f, 0.72f, 0.72f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(0.44f, 0.44f, 0.44f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.54f, 0.54f, 0.54f, 1.00f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.24f, 0.24f, 0.24f, 1.00f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.28f, 0.28f, 1.00f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
|
||||
colors[ImGuiCol_Header] = ImVec4(0.24f, 0.24f, 0.24f, 1.00f);
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.27f, 0.27f, 0.27f, 1.00f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.13f, 0.13f, 0.13f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorActive] = ImVec4(0.34f, 0.34f, 0.34f, 1.00f);
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(0.24f, 0.24f, 0.24f, 0.00f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.36f, 0.36f, 0.36f, 0.25f);
|
||||
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.52f, 0.52f, 0.52f, 0.25f);
|
||||
colors[ImGuiCol_Tab] = UI::DockTabColor();
|
||||
colors[ImGuiCol_TabHovered] = UI::DockTabHoveredColor();
|
||||
colors[ImGuiCol_TabSelected] = UI::DockTabSelectedColor();
|
||||
colors[ImGuiCol_TabSelectedOverline] = UI::DockTabSelectedOverlineColor();
|
||||
colors[ImGuiCol_TabDimmed] = UI::DockTabDimmedColor();
|
||||
colors[ImGuiCol_TabDimmedSelected] = UI::DockTabDimmedSelectedColor();
|
||||
colors[ImGuiCol_TabDimmedSelectedOverline] = UI::DockTabDimmedSelectedOverlineColor();
|
||||
colors[ImGuiCol_DockingPreview] = ImVec4(0.58f, 0.58f, 0.58f, 0.22f);
|
||||
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
|
||||
colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.76f, 0.76f, 0.76f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogram] = ImVec4(0.56f, 0.56f, 0.56f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.66f, 0.66f, 0.66f, 1.00f);
|
||||
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.21f, 0.21f, 0.21f, 1.00f);
|
||||
colors[ImGuiCol_TableBorderStrong] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
|
||||
colors[ImGuiCol_TableBorderLight] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.03f);
|
||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.48f, 0.48f, 0.48f, 0.28f);
|
||||
colors[ImGuiCol_DragDropTarget] = ImVec4(0.62f, 0.62f, 0.62f, 0.72f);
|
||||
colors[ImGuiCol_NavHighlight] = ImVec4(0.62f, 0.62f, 0.62f, 0.52f);
|
||||
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||||
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
|
||||
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
|
||||
|
||||
style.WindowRounding = 0.0f;
|
||||
style.ChildRounding = 0.0f;
|
||||
style.FrameRounding = 2.0f;
|
||||
style.GrabRounding = 2.0f;
|
||||
style.PopupRounding = 0.0f;
|
||||
style.ScrollbarRounding = 2.0f;
|
||||
style.TabRounding = 0.0f;
|
||||
style.WindowBorderSize = 1.0f;
|
||||
style.ChildBorderSize = 0.0f;
|
||||
style.PopupBorderSize = 1.0f;
|
||||
style.FrameBorderSize = 1.0f;
|
||||
style.TabBorderSize = 0.0f;
|
||||
style.TabBarBorderSize = 1.0f;
|
||||
style.TabBarOverlineSize = 2.0f;
|
||||
style.WindowPadding = ImVec2(6.0f, 6.0f);
|
||||
style.FramePadding = ImVec2(6.0f, 4.0f);
|
||||
style.ItemSpacing = ImVec2(6.0f, 4.0f);
|
||||
style.ItemInnerSpacing = ImVec2(5.0f, 4.0f);
|
||||
style.CellPadding = ImVec2(5.0f, 4.0f);
|
||||
style.IndentSpacing = 14.0f;
|
||||
style.ScrollbarSize = 14.0f;
|
||||
style.GrabMinSize = 10.0f;
|
||||
style.WindowTitleAlign = ImVec2(0.0f, 0.5f);
|
||||
style.ButtonTextAlign = ImVec2(0.5f, 0.5f);
|
||||
style.SelectableTextAlign = ImVec2(0.0f, 0.5f);
|
||||
UI::ApplyBaseTheme(ImGui::GetStyle());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
106
editor/src/UI/BaseTheme.h
Normal file
@@ -0,0 +1,106 @@
|
||||
#pragma once
|
||||
|
||||
#include "StyleTokens.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace UI {
|
||||
|
||||
inline void ApplyBaseThemeColors(ImVec4* colors) {
|
||||
colors[ImGuiCol_Text] = ImVec4(0.80f, 0.80f, 0.80f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.53f, 0.53f, 0.53f, 1.00f);
|
||||
colors[ImGuiCol_WindowBg] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f);
|
||||
colors[ImGuiCol_ChildBg] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f);
|
||||
colors[ImGuiCol_PopupBg] = ImVec4(0.17f, 0.17f, 0.17f, 0.98f);
|
||||
colors[ImGuiCol_Border] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
|
||||
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.21f, 0.21f, 0.21f, 1.00f);
|
||||
colors[ImGuiCol_FrameBgActive] = ImVec4(0.24f, 0.24f, 0.24f, 1.00f);
|
||||
colors[ImGuiCol_TitleBg] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgActive] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
|
||||
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_MenuBarBg] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.36f, 0.36f, 0.36f, 1.00f);
|
||||
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
|
||||
colors[ImGuiCol_CheckMark] = ImVec4(0.72f, 0.72f, 0.72f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrab] = ImVec4(0.44f, 0.44f, 0.44f, 1.00f);
|
||||
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.54f, 0.54f, 0.54f, 1.00f);
|
||||
colors[ImGuiCol_Button] = ImVec4(0.24f, 0.24f, 0.24f, 1.00f);
|
||||
colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.28f, 0.28f, 1.00f);
|
||||
colors[ImGuiCol_ButtonActive] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
|
||||
colors[ImGuiCol_Header] = ImVec4(0.24f, 0.24f, 0.24f, 1.00f);
|
||||
colors[ImGuiCol_HeaderHovered] = ImVec4(0.27f, 0.27f, 0.27f, 1.00f);
|
||||
colors[ImGuiCol_HeaderActive] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
|
||||
colors[ImGuiCol_Separator] = ImVec4(0.13f, 0.13f, 0.13f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
|
||||
colors[ImGuiCol_SeparatorActive] = ImVec4(0.34f, 0.34f, 0.34f, 1.00f);
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(0.24f, 0.24f, 0.24f, 0.00f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.36f, 0.36f, 0.36f, 0.25f);
|
||||
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.52f, 0.52f, 0.52f, 0.25f);
|
||||
colors[ImGuiCol_Tab] = DockTabColor();
|
||||
colors[ImGuiCol_TabHovered] = DockTabHoveredColor();
|
||||
colors[ImGuiCol_TabSelected] = DockTabSelectedColor();
|
||||
colors[ImGuiCol_TabSelectedOverline] = DockTabSelectedOverlineColor();
|
||||
colors[ImGuiCol_TabDimmed] = DockTabDimmedColor();
|
||||
colors[ImGuiCol_TabDimmedSelected] = DockTabDimmedSelectedColor();
|
||||
colors[ImGuiCol_TabDimmedSelectedOverline] = DockTabDimmedSelectedOverlineColor();
|
||||
colors[ImGuiCol_DockingPreview] = ImVec4(0.58f, 0.58f, 0.58f, 0.22f);
|
||||
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
|
||||
colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.76f, 0.76f, 0.76f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogram] = ImVec4(0.56f, 0.56f, 0.56f, 1.00f);
|
||||
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.66f, 0.66f, 0.66f, 1.00f);
|
||||
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.21f, 0.21f, 0.21f, 1.00f);
|
||||
colors[ImGuiCol_TableBorderStrong] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
|
||||
colors[ImGuiCol_TableBorderLight] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
|
||||
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
|
||||
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.03f);
|
||||
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.48f, 0.48f, 0.48f, 0.28f);
|
||||
colors[ImGuiCol_DragDropTarget] = ImVec4(0.62f, 0.62f, 0.62f, 0.72f);
|
||||
colors[ImGuiCol_NavHighlight] = ImVec4(0.62f, 0.62f, 0.62f, 0.52f);
|
||||
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
|
||||
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
|
||||
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
|
||||
}
|
||||
|
||||
inline void ApplyBaseThemeMetrics(ImGuiStyle& style) {
|
||||
style.WindowRounding = 0.0f;
|
||||
style.ChildRounding = 0.0f;
|
||||
style.FrameRounding = 2.0f;
|
||||
style.GrabRounding = 2.0f;
|
||||
style.PopupRounding = 0.0f;
|
||||
style.ScrollbarRounding = 2.0f;
|
||||
style.TabRounding = 0.0f;
|
||||
style.WindowBorderSize = 1.0f;
|
||||
style.ChildBorderSize = 0.0f;
|
||||
style.PopupBorderSize = 1.0f;
|
||||
style.FrameBorderSize = 1.0f;
|
||||
style.TabBorderSize = 0.0f;
|
||||
style.TabBarBorderSize = 1.0f;
|
||||
style.TabBarOverlineSize = 2.0f;
|
||||
style.WindowPadding = ImVec2(6.0f, 6.0f);
|
||||
style.FramePadding = ImVec2(6.0f, 4.0f);
|
||||
style.ItemSpacing = ImVec2(6.0f, 4.0f);
|
||||
style.ItemInnerSpacing = ImVec2(5.0f, 4.0f);
|
||||
style.CellPadding = ImVec2(5.0f, 4.0f);
|
||||
style.IndentSpacing = 14.0f;
|
||||
style.ScrollbarSize = 14.0f;
|
||||
style.GrabMinSize = 10.0f;
|
||||
style.WindowTitleAlign = ImVec2(0.0f, 0.5f);
|
||||
style.ButtonTextAlign = ImVec2(0.5f, 0.5f);
|
||||
style.SelectableTextAlign = ImVec2(0.0f, 0.5f);
|
||||
}
|
||||
|
||||
inline void ApplyBaseTheme(ImGuiStyle& style) {
|
||||
ApplyBaseThemeColors(style.Colors);
|
||||
ApplyBaseThemeMetrics(style);
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -9,15 +9,15 @@ namespace Editor {
|
||||
namespace UI {
|
||||
|
||||
inline float DefaultControlLabelWidth() {
|
||||
return 104.0f;
|
||||
return InspectorPropertyLabelWidth();
|
||||
}
|
||||
|
||||
inline ImVec2 DefaultControlCellPadding() {
|
||||
return ImVec2(0.0f, 2.0f);
|
||||
return ControlCellPadding();
|
||||
}
|
||||
|
||||
inline ImVec2 DefaultControlFramePadding() {
|
||||
return ImVec2(6.0f, 3.0f);
|
||||
return ControlFramePadding();
|
||||
}
|
||||
|
||||
inline void PushControlRowStyles() {
|
||||
@@ -25,6 +25,37 @@ inline void PushControlRowStyles() {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, DefaultControlFramePadding());
|
||||
}
|
||||
|
||||
template <typename DrawControlFn>
|
||||
inline auto DrawControlRow(
|
||||
const char* label,
|
||||
float columnWidth,
|
||||
DrawControlFn&& drawControl) -> decltype(drawControl()) {
|
||||
using Result = decltype(drawControl());
|
||||
|
||||
Result result{};
|
||||
ImGui::PushID(label);
|
||||
PushControlRowStyles();
|
||||
|
||||
if (ImGui::BeginTable("##ControlRow", 2, ImGuiTableFlags_NoSavedSettings)) {
|
||||
ImGui::TableSetupColumn("##label", ImGuiTableColumnFlags_WidthFixed, columnWidth);
|
||||
ImGui::TableSetupColumn("##control", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetFontSize() + ControlRowHeightOffset());
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted(label);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
result = drawControl();
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopID();
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void StyleVarPush(ImGuiStyleVar idx, float val) {
|
||||
ImGui::PushStyleVar(idx, val);
|
||||
}
|
||||
@@ -49,8 +80,12 @@ inline void PopStyleColor(int count = 1) {
|
||||
ImGui::PopStyleColor(count);
|
||||
}
|
||||
|
||||
inline void PushPopupWindowStyle() {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, PopupWindowPadding());
|
||||
}
|
||||
|
||||
inline bool BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(12.0f, 10.0f));
|
||||
PushPopupWindowStyle();
|
||||
bool is_open = ImGui::BeginPopup(str_id, flags);
|
||||
if (!is_open) {
|
||||
ImGui::PopStyleVar();
|
||||
@@ -58,6 +93,36 @@ inline bool BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0) {
|
||||
return is_open;
|
||||
}
|
||||
|
||||
inline bool BeginPopupContextItem(const char* str_id = nullptr, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_MouseButtonRight) {
|
||||
PushPopupWindowStyle();
|
||||
bool is_open = ImGui::BeginPopupContextItem(str_id, popup_flags);
|
||||
if (!is_open) {
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
return is_open;
|
||||
}
|
||||
|
||||
inline bool BeginPopupContextWindow(const char* str_id = nullptr, ImGuiPopupFlags popup_flags = ImGuiPopupFlags_MouseButtonRight) {
|
||||
PushPopupWindowStyle();
|
||||
bool is_open = ImGui::BeginPopupContextWindow(str_id, popup_flags);
|
||||
if (!is_open) {
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
return is_open;
|
||||
}
|
||||
|
||||
inline bool BeginModalPopup(
|
||||
const char* name,
|
||||
bool* p_open = nullptr,
|
||||
ImGuiWindowFlags flags = ImGuiWindowFlags_AlwaysAutoResize) {
|
||||
PushPopupWindowStyle();
|
||||
bool is_open = ImGui::BeginPopupModal(name, p_open, flags);
|
||||
if (!is_open) {
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
return is_open;
|
||||
}
|
||||
|
||||
inline void EndPopup() {
|
||||
ImGui::EndPopup();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
82
editor/src/UI/PopupState.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace UI {
|
||||
|
||||
class DeferredPopupState {
|
||||
public:
|
||||
void RequestOpen() {
|
||||
m_openRequested = true;
|
||||
}
|
||||
|
||||
void ConsumeOpenRequest(const char* popupId) {
|
||||
if (!m_openRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::OpenPopup(popupId);
|
||||
m_openRequested = false;
|
||||
}
|
||||
|
||||
bool HasPendingOpenRequest() const {
|
||||
return m_openRequested;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_openRequested = false;
|
||||
};
|
||||
|
||||
template <size_t BufferCapacity>
|
||||
class TextInputPopupState {
|
||||
public:
|
||||
void RequestOpen(const char* initialValue = "") {
|
||||
SetValue(initialValue);
|
||||
m_popup.RequestOpen();
|
||||
}
|
||||
|
||||
void ConsumeOpenRequest(const char* popupId) {
|
||||
m_popup.ConsumeOpenRequest(popupId);
|
||||
}
|
||||
|
||||
void SetValue(const char* value) {
|
||||
if (value && value[0] != '\0') {
|
||||
strcpy_s(m_buffer, value);
|
||||
return;
|
||||
}
|
||||
|
||||
m_buffer[0] = '\0';
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
m_buffer[0] = '\0';
|
||||
}
|
||||
|
||||
bool Empty() const {
|
||||
return m_buffer[0] == '\0';
|
||||
}
|
||||
|
||||
char* Buffer() {
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
const char* Buffer() const {
|
||||
return m_buffer;
|
||||
}
|
||||
|
||||
constexpr size_t BufferSize() const {
|
||||
return BufferCapacity;
|
||||
}
|
||||
|
||||
private:
|
||||
DeferredPopupState m_popup;
|
||||
char m_buffer[BufferCapacity] = {};
|
||||
};
|
||||
|
||||
} // namespace UI
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
131
editor/src/UI/PropertyGrid.h
Normal file
@@ -0,0 +1,131 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core/IUndoManager.h"
|
||||
#include "ScalarControls.h"
|
||||
#include "StyleTokens.h"
|
||||
#include "VectorControls.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace UI {
|
||||
|
||||
template <typename ApplyFn>
|
||||
inline bool ApplyPropertyChange(
|
||||
bool changedByWidget,
|
||||
::XCEngine::Editor::IUndoManager* undoManager,
|
||||
const char* undoLabel,
|
||||
ApplyFn&& apply) {
|
||||
if (!changedByWidget) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (undoManager) {
|
||||
undoManager->BeginInteractiveChange(undoLabel);
|
||||
}
|
||||
|
||||
apply();
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool DrawPropertyFloat(
|
||||
const char* label,
|
||||
float& value,
|
||||
float dragSpeed = 0.1f,
|
||||
float min = 0.0f,
|
||||
float max = 0.0f,
|
||||
const char* format = "%.2f"
|
||||
) {
|
||||
return DrawFloat(label, value, InspectorPropertyLabelWidth(), dragSpeed, min, max, format);
|
||||
}
|
||||
|
||||
inline bool DrawPropertyInt(
|
||||
const char* label,
|
||||
int& value,
|
||||
int step = 1,
|
||||
int min = 0,
|
||||
int max = 0
|
||||
) {
|
||||
return DrawInt(label, value, InspectorPropertyLabelWidth(), step, min, max);
|
||||
}
|
||||
|
||||
inline bool DrawPropertyBool(
|
||||
const char* label,
|
||||
bool& value
|
||||
) {
|
||||
return DrawBool(label, value, InspectorPropertyLabelWidth());
|
||||
}
|
||||
|
||||
inline bool DrawPropertyColor3(
|
||||
const char* label,
|
||||
float color[3]
|
||||
) {
|
||||
return DrawColor3(label, color, InspectorPropertyLabelWidth());
|
||||
}
|
||||
|
||||
inline bool DrawPropertyColor4(
|
||||
const char* label,
|
||||
float color[4]
|
||||
) {
|
||||
return DrawColor4(label, color, InspectorPropertyLabelWidth());
|
||||
}
|
||||
|
||||
inline bool DrawPropertySliderFloat(
|
||||
const char* label,
|
||||
float& value,
|
||||
float min,
|
||||
float max,
|
||||
const char* format = "%.2f"
|
||||
) {
|
||||
return DrawSliderFloat(label, value, min, max, InspectorPropertyLabelWidth(), format);
|
||||
}
|
||||
|
||||
inline bool DrawPropertySliderInt(
|
||||
const char* label,
|
||||
int& value,
|
||||
int min,
|
||||
int max
|
||||
) {
|
||||
return DrawSliderInt(label, value, min, max, InspectorPropertyLabelWidth());
|
||||
}
|
||||
|
||||
inline int DrawPropertyCombo(
|
||||
const char* label,
|
||||
int currentItem,
|
||||
const char* const items[],
|
||||
int itemCount,
|
||||
int heightInItems = -1
|
||||
) {
|
||||
return DrawCombo(label, currentItem, items, itemCount, InspectorPropertyLabelWidth(), heightInItems);
|
||||
}
|
||||
|
||||
inline bool DrawPropertyVec2(
|
||||
const char* label,
|
||||
::XCEngine::Math::Vector2& values,
|
||||
float resetValue = 0.0f,
|
||||
float dragSpeed = 0.1f
|
||||
) {
|
||||
return DrawVec2(label, values, resetValue, InspectorPropertyLabelWidth(), dragSpeed);
|
||||
}
|
||||
|
||||
inline bool DrawPropertyVec3(
|
||||
const char* label,
|
||||
::XCEngine::Math::Vector3& values,
|
||||
float resetValue = 0.0f,
|
||||
float dragSpeed = 0.1f,
|
||||
bool* isActive = nullptr
|
||||
) {
|
||||
return DrawVec3(label, values, resetValue, InspectorPropertyLabelWidth(), dragSpeed, isActive);
|
||||
}
|
||||
|
||||
inline bool DrawPropertyVec3Input(
|
||||
const char* label,
|
||||
::XCEngine::Math::Vector3& values,
|
||||
float dragSpeed = 0.1f,
|
||||
bool* isActive = nullptr
|
||||
) {
|
||||
return DrawVec3Input(label, values, InspectorPropertyLabelWidth(), dragSpeed, isActive);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core.h"
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -9,194 +10,58 @@ namespace UI {
|
||||
inline bool DrawFloat(
|
||||
const char* label,
|
||||
float& value,
|
||||
float columnWidth = 100.0f,
|
||||
float columnWidth = DefaultControlLabelWidth(),
|
||||
float dragSpeed = 0.1f,
|
||||
float min = 0.0f,
|
||||
float max = 0.0f,
|
||||
const char* format = "%.2f"
|
||||
) {
|
||||
bool changed = false;
|
||||
ImGui::PushID(label);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{0, 1});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 1});
|
||||
|
||||
if (ImGui::BeginTable("##FloatTable", 2, ImGuiTableFlags_NoSavedSettings)) {
|
||||
ImGui::TableSetupColumn("##label", ImGuiTableColumnFlags_WidthFixed, columnWidth);
|
||||
ImGui::TableSetupColumn("##control", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetFontSize() + 2.0f);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text(label);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
if (max != min) {
|
||||
if (ImGui::SliderFloat("##value", &value, min, max, format)) {
|
||||
changed = true;
|
||||
}
|
||||
} else {
|
||||
if (ImGui::DragFloat("##value", &value, dragSpeed, min, max, format)) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopID();
|
||||
|
||||
return changed;
|
||||
return DrawControlRow(label, columnWidth, [&]() {
|
||||
return ImGui::DragFloat("##value", &value, dragSpeed, min, max, format);
|
||||
});
|
||||
}
|
||||
|
||||
inline bool DrawInt(
|
||||
const char* label,
|
||||
int& value,
|
||||
float columnWidth = 100.0f,
|
||||
float columnWidth = DefaultControlLabelWidth(),
|
||||
int step = 1,
|
||||
int min = 0,
|
||||
int max = 0
|
||||
) {
|
||||
bool changed = false;
|
||||
ImGui::PushID(label);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{0, 1});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 1});
|
||||
|
||||
if (ImGui::BeginTable("##IntTable", 2, ImGuiTableFlags_NoSavedSettings)) {
|
||||
ImGui::TableSetupColumn("##label", ImGuiTableColumnFlags_WidthFixed, columnWidth);
|
||||
ImGui::TableSetupColumn("##control", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetFontSize() + 2.0f);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text(label);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
if (ImGui::DragInt("##value", &value, static_cast<float>(step), min, max)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopID();
|
||||
|
||||
return changed;
|
||||
return DrawControlRow(label, columnWidth, [&]() {
|
||||
return ImGui::DragInt("##value", &value, static_cast<float>(step), min, max);
|
||||
});
|
||||
}
|
||||
|
||||
inline bool DrawBool(
|
||||
const char* label,
|
||||
bool& value,
|
||||
float columnWidth = 100.0f
|
||||
float columnWidth = DefaultControlLabelWidth()
|
||||
) {
|
||||
bool changed = false;
|
||||
ImGui::PushID(label);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{0, 1});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 1});
|
||||
|
||||
if (ImGui::BeginTable("##BoolTable", 2, ImGuiTableFlags_NoSavedSettings)) {
|
||||
ImGui::TableSetupColumn("##label", ImGuiTableColumnFlags_WidthFixed, columnWidth);
|
||||
ImGui::TableSetupColumn("##control", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetFontSize() + 2.0f);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text(label);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
if (ImGui::Checkbox("##value", &value)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopID();
|
||||
|
||||
return changed;
|
||||
return DrawControlRow(label, columnWidth, [&]() {
|
||||
return ImGui::Checkbox("##value", &value);
|
||||
});
|
||||
}
|
||||
|
||||
inline bool DrawColor3(
|
||||
const char* label,
|
||||
float color[3],
|
||||
float columnWidth = 100.0f
|
||||
float columnWidth = DefaultControlLabelWidth()
|
||||
) {
|
||||
bool changed = false;
|
||||
ImGui::PushID(label);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{0, 1});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 1});
|
||||
|
||||
if (ImGui::BeginTable("##Color3Table", 2, ImGuiTableFlags_NoSavedSettings)) {
|
||||
ImGui::TableSetupColumn("##label", ImGuiTableColumnFlags_WidthFixed, columnWidth);
|
||||
ImGui::TableSetupColumn("##control", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetFontSize() + 2.0f);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text(label);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
if (ImGui::ColorEdit3("##value", color, ImGuiColorEditFlags_NoInputs)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopID();
|
||||
|
||||
return changed;
|
||||
return DrawControlRow(label, columnWidth, [&]() {
|
||||
return ImGui::ColorEdit3("##value", color, ImGuiColorEditFlags_NoInputs);
|
||||
});
|
||||
}
|
||||
|
||||
inline bool DrawColor4(
|
||||
const char* label,
|
||||
float color[4],
|
||||
float columnWidth = 100.0f
|
||||
float columnWidth = DefaultControlLabelWidth()
|
||||
) {
|
||||
bool changed = false;
|
||||
ImGui::PushID(label);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{0, 1});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 1});
|
||||
|
||||
if (ImGui::BeginTable("##Color4Table", 2, ImGuiTableFlags_NoSavedSettings)) {
|
||||
ImGui::TableSetupColumn("##label", ImGuiTableColumnFlags_WidthFixed, columnWidth);
|
||||
ImGui::TableSetupColumn("##control", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetFontSize() + 2.0f);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text(label);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
if (ImGui::ColorEdit4("##value", color, ImGuiColorEditFlags_NoInputs)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopID();
|
||||
|
||||
return changed;
|
||||
return DrawControlRow(label, columnWidth, [&]() {
|
||||
return ImGui::ColorEdit4("##value", color, ImGuiColorEditFlags_NoInputs);
|
||||
});
|
||||
}
|
||||
|
||||
inline bool DrawSliderFloat(
|
||||
@@ -204,38 +69,12 @@ inline bool DrawSliderFloat(
|
||||
float& value,
|
||||
float min,
|
||||
float max,
|
||||
float columnWidth = 100.0f,
|
||||
float columnWidth = DefaultControlLabelWidth(),
|
||||
const char* format = "%.2f"
|
||||
) {
|
||||
bool changed = false;
|
||||
ImGui::PushID(label);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{0, 1});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 1});
|
||||
|
||||
if (ImGui::BeginTable("##SliderTable", 2, ImGuiTableFlags_NoSavedSettings)) {
|
||||
ImGui::TableSetupColumn("##label", ImGuiTableColumnFlags_WidthFixed, columnWidth);
|
||||
ImGui::TableSetupColumn("##control", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetFontSize() + 2.0f);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text(label);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
if (ImGui::SliderFloat("##value", &value, min, max, format)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopID();
|
||||
|
||||
return changed;
|
||||
return DrawControlRow(label, columnWidth, [&]() {
|
||||
return ImGui::SliderFloat("##value", &value, min, max, format);
|
||||
});
|
||||
}
|
||||
|
||||
inline bool DrawSliderInt(
|
||||
@@ -243,37 +82,11 @@ inline bool DrawSliderInt(
|
||||
int& value,
|
||||
int min,
|
||||
int max,
|
||||
float columnWidth = 100.0f
|
||||
float columnWidth = DefaultControlLabelWidth()
|
||||
) {
|
||||
bool changed = false;
|
||||
ImGui::PushID(label);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{0, 1});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 1});
|
||||
|
||||
if (ImGui::BeginTable("##SliderIntTable", 2, ImGuiTableFlags_NoSavedSettings)) {
|
||||
ImGui::TableSetupColumn("##label", ImGuiTableColumnFlags_WidthFixed, columnWidth);
|
||||
ImGui::TableSetupColumn("##control", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetFontSize() + 2.0f);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text(label);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
if (ImGui::SliderInt("##value", &value, min, max)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopID();
|
||||
|
||||
return changed;
|
||||
return DrawControlRow(label, columnWidth, [&]() {
|
||||
return ImGui::SliderInt("##value", &value, min, max);
|
||||
});
|
||||
}
|
||||
|
||||
inline int DrawCombo(
|
||||
@@ -281,39 +94,17 @@ inline int DrawCombo(
|
||||
int currentItem,
|
||||
const char* const items[],
|
||||
int itemCount,
|
||||
float columnWidth = 100.0f,
|
||||
float columnWidth = DefaultControlLabelWidth(),
|
||||
int heightInItems = -1
|
||||
) {
|
||||
ImGui::PushID(label);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{0, 1});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 1});
|
||||
|
||||
int changedItem = currentItem;
|
||||
|
||||
if (ImGui::BeginTable("##ComboTable", 2, ImGuiTableFlags_NoSavedSettings)) {
|
||||
ImGui::TableSetupColumn("##label", ImGuiTableColumnFlags_WidthFixed, columnWidth);
|
||||
ImGui::TableSetupColumn("##control", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetFontSize() + 2.0f);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text(label);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
DrawControlRow(label, columnWidth, [&]() {
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
if (ImGui::Combo("##value", ¤tItem, items, itemCount, heightInItems)) {
|
||||
changedItem = currentItem;
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopID();
|
||||
|
||||
return false;
|
||||
});
|
||||
return changedItem;
|
||||
}
|
||||
|
||||
|
||||
@@ -66,10 +66,46 @@ inline ImVec2 ToolbarItemSpacing() {
|
||||
return ImVec2(6.0f, 6.0f);
|
||||
}
|
||||
|
||||
inline float StandardPanelToolbarHeight() {
|
||||
return 34.0f;
|
||||
}
|
||||
|
||||
inline float ProjectPanelToolbarHeight() {
|
||||
return 60.0f;
|
||||
}
|
||||
|
||||
inline float ToolbarSearchTrailingSpacing() {
|
||||
return 4.0f;
|
||||
}
|
||||
|
||||
inline float ToolbarRowGap() {
|
||||
return 2.0f;
|
||||
}
|
||||
|
||||
inline ImVec2 AssetGridSpacing() {
|
||||
return ImVec2(8.0f, 10.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 DefaultPanelContentPadding() {
|
||||
return ImVec2(8.0f, 6.0f);
|
||||
}
|
||||
|
||||
inline float InspectorPropertyLabelWidth() {
|
||||
return 104.0f;
|
||||
}
|
||||
|
||||
inline ImVec2 ControlCellPadding() {
|
||||
return ImVec2(0.0f, 2.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 ControlFramePadding() {
|
||||
return ImVec2(6.0f, 3.0f);
|
||||
}
|
||||
|
||||
inline float ControlRowHeightOffset() {
|
||||
return 2.0f;
|
||||
}
|
||||
|
||||
inline ImVec2 InspectorPanelContentPadding() {
|
||||
return ImVec2(10.0f, 0.0f);
|
||||
}
|
||||
@@ -82,10 +118,162 @@ inline ImVec4 ToolbarBackgroundColor() {
|
||||
return ImVec4(0.19f, 0.19f, 0.19f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 SearchFieldFramePadding() {
|
||||
return ImVec2(7.0f, 4.0f);
|
||||
}
|
||||
|
||||
inline float SearchFieldFrameRounding() {
|
||||
return 2.0f;
|
||||
}
|
||||
|
||||
inline ImU32 PanelDividerColor() {
|
||||
return IM_COL32(36, 36, 36, 255);
|
||||
}
|
||||
|
||||
inline ImVec4 EmptyStateSubtitleColor() {
|
||||
return ImVec4(0.55f, 0.55f, 0.55f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 HintTextColor() {
|
||||
return ImVec4(0.53f, 0.53f, 0.53f, 1.0f);
|
||||
}
|
||||
|
||||
inline float EmptyStateLineOffset() {
|
||||
return 20.0f;
|
||||
}
|
||||
|
||||
inline ImVec2 InspectorSectionFramePadding() {
|
||||
return ImVec2(6.0f, 4.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 InspectorSectionItemSpacing() {
|
||||
return ImVec2(6.0f, 0.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 InspectorActionButtonPadding() {
|
||||
return ImVec2(8.0f, 5.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 PopupWindowPadding() {
|
||||
return ImVec2(12.0f, 10.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 HierarchyNodeFramePadding() {
|
||||
return ImVec2(4.0f, 3.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 AssetTileSize() {
|
||||
return ImVec2(104.0f, 82.0f);
|
||||
}
|
||||
|
||||
inline float HierarchyOverflowButtonWidth() {
|
||||
return 26.0f;
|
||||
}
|
||||
|
||||
inline ImVec2 ProjectBackButtonSize() {
|
||||
return ImVec2(28.0f, 0.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 DialogActionButtonSize() {
|
||||
return ImVec2(80.0f, 0.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 AssetDragPreviewSize() {
|
||||
return ImVec2(24.0f, 20.0f);
|
||||
}
|
||||
|
||||
inline float AssetTileRounding() {
|
||||
return 2.0f;
|
||||
}
|
||||
|
||||
inline ImVec4 AssetTileHoverFillColor() {
|
||||
return ImVec4(1.0f, 1.0f, 1.0f, 0.04f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetTileSelectedFillColor() {
|
||||
return ImVec4(0.61f, 0.61f, 0.61f, 0.12f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetTileSelectedBorderColor() {
|
||||
return ImVec4(0.74f, 0.74f, 0.74f, 0.43f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetTileDraggedOverlayColor() {
|
||||
return ImVec4(0.0f, 0.0f, 0.0f, 0.24f);
|
||||
}
|
||||
|
||||
inline ImVec2 AssetTileIconOffset() {
|
||||
return ImVec2(14.0f, 12.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 AssetTileIconSize() {
|
||||
return ImVec2(28.0f, 22.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 AssetTileTextPadding() {
|
||||
return ImVec2(6.0f, 10.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetTileTextColor(bool selected = false) {
|
||||
return selected ? ImVec4(0.93f, 0.93f, 0.93f, 1.0f) : ImVec4(0.76f, 0.76f, 0.76f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetFolderIconFillColor() {
|
||||
return ImVec4(0.46f, 0.46f, 0.46f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetFolderIconLineColor() {
|
||||
return ImVec4(0.72f, 0.72f, 0.72f, 0.86f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetFileIconFillColor() {
|
||||
return ImVec4(0.41f, 0.41f, 0.41f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetFileIconLineColor() {
|
||||
return ImVec4(0.65f, 0.65f, 0.65f, 0.86f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetFileFoldColor() {
|
||||
return ImVec4(0.92f, 0.92f, 0.92f, 0.16f);
|
||||
}
|
||||
|
||||
inline ImVec4 ConsoleRowHoverFillColor() {
|
||||
return ImVec4(1.0f, 1.0f, 1.0f, 0.03f);
|
||||
}
|
||||
|
||||
inline float MenuBarStatusRightPadding() {
|
||||
return 20.0f;
|
||||
}
|
||||
|
||||
inline ImVec4 MenuBarStatusIdleColor() {
|
||||
return ImVec4(0.62f, 0.62f, 0.62f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 MenuBarStatusDirtyColor() {
|
||||
return ImVec4(0.82f, 0.82f, 0.82f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 VectorAxisInputSpacing() {
|
||||
return ImVec2(6.0f, 0.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 VectorAxisControlSpacing() {
|
||||
return ImVec2(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
inline float VectorAxisResetButtonExtraWidth() {
|
||||
return 3.0f;
|
||||
}
|
||||
|
||||
inline ImVec4 VectorAxisButtonColor() {
|
||||
return ImVec4(0.28f, 0.28f, 0.28f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 VectorAxisButtonHoveredColor() {
|
||||
return ImVec4(0.34f, 0.34f, 0.34f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 ToolbarButtonColor(bool active) {
|
||||
return active ? ImVec4(0.33f, 0.33f, 0.33f, 1.0f) : ImVec4(0.24f, 0.24f, 0.24f, 1.0f);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "BaseTheme.h"
|
||||
#include "Core.h"
|
||||
#include "DockHostStyle.h"
|
||||
#include "PanelChrome.h"
|
||||
#include "PopupState.h"
|
||||
#include "PropertyGrid.h"
|
||||
#include "ScalarControls.h"
|
||||
#include "StyleTokens.h"
|
||||
#include "VectorControls.h"
|
||||
#include "ScalarControls.h"
|
||||
#include "Widgets.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
@@ -1,148 +1,155 @@
|
||||
#pragma once
|
||||
|
||||
#include "Core.h"
|
||||
#include <imgui.h>
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
#include <XCEngine/Core/Math/Vector2.h>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace UI {
|
||||
|
||||
inline bool DrawVec3(
|
||||
const char* label,
|
||||
::XCEngine::Math::Vector3& values,
|
||||
float resetValue = 0.0f,
|
||||
float columnWidth = 100.0f,
|
||||
float dragSpeed = 0.1f
|
||||
struct AxisFloatControlSpec {
|
||||
const char* label = nullptr;
|
||||
float* value = nullptr;
|
||||
};
|
||||
|
||||
inline bool DrawAxisFloatControls(
|
||||
const AxisFloatControlSpec* axes,
|
||||
int axisCount,
|
||||
float dragSpeed,
|
||||
bool useResetButtons,
|
||||
float resetValue,
|
||||
bool* isActive = nullptr
|
||||
) {
|
||||
bool changed = false;
|
||||
ImGui::PushID(label);
|
||||
bool anyItemActive = false;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{0, 1});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 1});
|
||||
ImGui::PushStyleVar(
|
||||
ImGuiStyleVar_ItemSpacing,
|
||||
useResetButtons ? VectorAxisControlSpacing() : VectorAxisInputSpacing());
|
||||
|
||||
if (ImGui::BeginTable("##Vec3Table", 2, ImGuiTableFlags_NoSavedSettings)) {
|
||||
ImGui::TableSetupColumn("##label", ImGuiTableColumnFlags_WidthFixed, columnWidth);
|
||||
ImGui::TableSetupColumn("##controls", ImGuiTableColumnFlags_WidthStretch);
|
||||
const float availableWidth = ImGui::GetContentRegionAvail().x;
|
||||
const float spacing = ImGui::GetStyle().ItemSpacing.x;
|
||||
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetFontSize() + 2.0f);
|
||||
if (useResetButtons) {
|
||||
const float lineHeight = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.0f;
|
||||
const ImVec2 buttonSize(lineHeight + VectorAxisResetButtonExtraWidth(), lineHeight);
|
||||
float itemWidth = (availableWidth - buttonSize.x * static_cast<float>(axisCount)) / static_cast<float>(axisCount);
|
||||
if (itemWidth < 0.0f) {
|
||||
itemWidth = 0.0f;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text(label);
|
||||
const ImVec4 buttonColor = VectorAxisButtonColor();
|
||||
const ImVec4 buttonHoverColor = VectorAxisButtonHoveredColor();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
for (int i = 0; i < axisCount; ++i) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, buttonColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, buttonHoverColor);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, buttonColor);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2{0, 0});
|
||||
|
||||
float lineHeight = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.0f;
|
||||
ImVec2 buttonSize = {lineHeight + 3.0f, lineHeight};
|
||||
float itemWidth = (ImGui::GetContentRegionAvail().x - buttonSize.x * 3.0f) / 3.0f;
|
||||
|
||||
auto drawAxisControl = [&](const char* axisLabel, float& value, ImVec4 color, ImVec4 colorHovered) {
|
||||
bool axisChanged = false;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, color);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colorHovered);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, color);
|
||||
|
||||
if (ImGui::Button(axisLabel, buttonSize)) {
|
||||
value = resetValue;
|
||||
axisChanged = true;
|
||||
if (ImGui::Button(axes[i].label, buttonSize)) {
|
||||
*axes[i].value = resetValue;
|
||||
changed = true;
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(itemWidth);
|
||||
|
||||
if (ImGui::DragFloat((std::string("##") + axisLabel).c_str(), &value, dragSpeed, 0.0f, 0.0f, "%.2f")) {
|
||||
axisChanged = true;
|
||||
if (ImGui::DragFloat((std::string("##") + axes[i].label).c_str(), axes[i].value, dragSpeed, 0.0f, 0.0f, "%.2f")) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
anyItemActive = anyItemActive || ImGui::IsItemActive();
|
||||
if (i + 1 < axisCount) {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const float axisLabelWidth = ImGui::CalcTextSize("Z").x;
|
||||
float itemWidth = (availableWidth - axisLabelWidth * static_cast<float>(axisCount) - spacing * static_cast<float>(axisCount * 2 - 1)) / static_cast<float>(axisCount);
|
||||
if (itemWidth < 0.0f) {
|
||||
itemWidth = 0.0f;
|
||||
}
|
||||
|
||||
return axisChanged;
|
||||
};
|
||||
for (int i = 0; i < axisCount; ++i) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextDisabled("%s", axes[i].label);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(itemWidth);
|
||||
if (ImGui::DragFloat((std::string("##") + axes[i].label).c_str(), axes[i].value, dragSpeed, 0.0f, 0.0f, "%.2f")) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
changed |= drawAxisControl("X", values.x, ImVec4{0.8f, 0.1f, 0.15f, 1.0f}, ImVec4{0.9f, 0.2f, 0.2f, 1.0f});
|
||||
changed |= drawAxisControl("Y", values.y, ImVec4{0.2f, 0.7f, 0.2f, 1.0f}, ImVec4{0.3f, 0.8f, 0.3f, 1.0f});
|
||||
changed |= drawAxisControl("Z", values.z, ImVec4{0.1f, 0.25f, 0.8f, 1.0f}, ImVec4{0.2f, 0.35f, 0.9f, 1.0f});
|
||||
anyItemActive = anyItemActive || ImGui::IsItemActive();
|
||||
if (i + 1 < axisCount) {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::EndTable();
|
||||
|
||||
if (isActive) {
|
||||
*isActive = anyItemActive;
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopID();
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
inline bool DrawVec3Input(
|
||||
const char* label,
|
||||
::XCEngine::Math::Vector3& values,
|
||||
float columnWidth = DefaultControlLabelWidth(),
|
||||
float dragSpeed = 0.1f,
|
||||
bool* isActive = nullptr
|
||||
) {
|
||||
const AxisFloatControlSpec axes[] = {
|
||||
{ "X", &values.x },
|
||||
{ "Y", &values.y },
|
||||
{ "Z", &values.z }
|
||||
};
|
||||
|
||||
return DrawControlRow(label, columnWidth, [&]() {
|
||||
return DrawAxisFloatControls(axes, 3, dragSpeed, false, 0.0f, isActive);
|
||||
});
|
||||
}
|
||||
|
||||
inline bool DrawVec3(
|
||||
const char* label,
|
||||
::XCEngine::Math::Vector3& values,
|
||||
float resetValue = 0.0f,
|
||||
float columnWidth = DefaultControlLabelWidth(),
|
||||
float dragSpeed = 0.1f,
|
||||
bool* isActive = nullptr
|
||||
) {
|
||||
const AxisFloatControlSpec axes[] = {
|
||||
{ "X", &values.x },
|
||||
{ "Y", &values.y },
|
||||
{ "Z", &values.z }
|
||||
};
|
||||
|
||||
return DrawControlRow(label, columnWidth, [&]() {
|
||||
return DrawAxisFloatControls(axes, 3, dragSpeed, true, resetValue, isActive);
|
||||
});
|
||||
}
|
||||
|
||||
inline bool DrawVec2(
|
||||
const char* label,
|
||||
::XCEngine::Math::Vector2& values,
|
||||
float resetValue = 0.0f,
|
||||
float columnWidth = 100.0f,
|
||||
float columnWidth = DefaultControlLabelWidth(),
|
||||
float dragSpeed = 0.1f
|
||||
) {
|
||||
bool changed = false;
|
||||
ImGui::PushID(label);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{0, 1});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{4, 1});
|
||||
|
||||
if (ImGui::BeginTable("##Vec2Table", 2, ImGuiTableFlags_NoSavedSettings)) {
|
||||
ImGui::TableSetupColumn("##label", ImGuiTableColumnFlags_WidthFixed, columnWidth);
|
||||
ImGui::TableSetupColumn("##controls", ImGuiTableColumnFlags_WidthStretch);
|
||||
|
||||
ImGui::TableNextRow(ImGuiTableRowFlags_None, ImGui::GetFontSize() + 2.0f);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::Text(label);
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2{0, 0});
|
||||
|
||||
float lineHeight = ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.y * 2.0f;
|
||||
ImVec2 buttonSize = {lineHeight + 3.0f, lineHeight};
|
||||
float itemWidth = (ImGui::GetContentRegionAvail().x - buttonSize.x * 2.0f) / 2.0f;
|
||||
|
||||
auto drawAxisControl = [&](const char* axisLabel, float& value, ImVec4 color, ImVec4 colorHovered) {
|
||||
bool axisChanged = false;
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, color);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, colorHovered);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, color);
|
||||
|
||||
if (ImGui::Button(axisLabel, buttonSize)) {
|
||||
value = resetValue;
|
||||
axisChanged = true;
|
||||
}
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(itemWidth);
|
||||
|
||||
if (ImGui::DragFloat((std::string("##") + axisLabel).c_str(), &value, dragSpeed, 0.0f, 0.0f, "%.2f")) {
|
||||
axisChanged = true;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
return axisChanged;
|
||||
const AxisFloatControlSpec axes[] = {
|
||||
{ "X", &values.x },
|
||||
{ "Y", &values.y }
|
||||
};
|
||||
|
||||
changed |= drawAxisControl("X", values.x, ImVec4{0.8f, 0.1f, 0.15f, 1.0f}, ImVec4{0.9f, 0.2f, 0.2f, 1.0f});
|
||||
changed |= drawAxisControl("Y", values.y, ImVec4{0.2f, 0.7f, 0.2f, 1.0f}, ImVec4{0.3f, 0.8f, 0.3f, 1.0f});
|
||||
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopID();
|
||||
|
||||
return changed;
|
||||
return DrawControlRow(label, columnWidth, [&]() {
|
||||
return DrawAxisFloatControls(axes, 2, dragSpeed, true, resetValue);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
403
editor/src/UI/Widgets.h
Normal file
@@ -0,0 +1,403 @@
|
||||
#pragma once
|
||||
|
||||
#include "StyleTokens.h"
|
||||
|
||||
#include <imgui.h>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace UI {
|
||||
|
||||
struct ComponentSectionResult {
|
||||
bool open = false;
|
||||
bool removeRequested = false;
|
||||
};
|
||||
|
||||
struct HierarchyNodeResult {
|
||||
bool open = false;
|
||||
bool clicked = false;
|
||||
bool doubleClicked = false;
|
||||
};
|
||||
|
||||
struct AssetTileResult {
|
||||
bool clicked = false;
|
||||
bool contextRequested = false;
|
||||
bool openRequested = false;
|
||||
bool hovered = false;
|
||||
ImVec2 min = ImVec2(0.0f, 0.0f);
|
||||
ImVec2 max = ImVec2(0.0f, 0.0f);
|
||||
};
|
||||
|
||||
enum class AssetIconKind {
|
||||
Folder,
|
||||
File
|
||||
};
|
||||
|
||||
enum class DialogActionResult {
|
||||
None,
|
||||
Primary,
|
||||
Secondary
|
||||
};
|
||||
|
||||
enum class MenuCommandKind {
|
||||
Action,
|
||||
Separator
|
||||
};
|
||||
|
||||
struct MenuCommand {
|
||||
MenuCommandKind kind = MenuCommandKind::Action;
|
||||
const char* label = nullptr;
|
||||
const char* shortcut = nullptr;
|
||||
bool selected = false;
|
||||
bool enabled = true;
|
||||
|
||||
static MenuCommand Action(
|
||||
const char* label,
|
||||
const char* shortcut = nullptr,
|
||||
bool selected = false,
|
||||
bool enabled = true) {
|
||||
return MenuCommand{ MenuCommandKind::Action, label, shortcut, selected, enabled };
|
||||
}
|
||||
|
||||
static MenuCommand Separator() {
|
||||
return MenuCommand{ MenuCommandKind::Separator, nullptr, nullptr, false, true };
|
||||
}
|
||||
};
|
||||
|
||||
template <typename DrawContentFn>
|
||||
inline bool DrawMenuScope(const char* label, DrawContentFn&& drawContent) {
|
||||
if (!ImGui::BeginMenu(label)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
drawContent();
|
||||
ImGui::EndMenu();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename ExecuteFn>
|
||||
inline bool DrawMenuCommand(const MenuCommand& command, ExecuteFn&& execute) {
|
||||
if (command.kind == MenuCommandKind::Separator) {
|
||||
ImGui::Separator();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ImGui::MenuItem(command.label, command.shortcut, command.selected, command.enabled)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
execute();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <size_t N, typename ExecuteFn>
|
||||
inline void DrawMenuCommands(const MenuCommand (&commands)[N], ExecuteFn&& execute) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
DrawMenuCommand(commands[i], [&]() { execute(i); });
|
||||
}
|
||||
}
|
||||
|
||||
inline bool ToolbarSearchField(
|
||||
const char* id,
|
||||
const char* hint,
|
||||
char* buffer,
|
||||
size_t bufferSize,
|
||||
float trailingWidth = 0.0f) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, SearchFieldFramePadding());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, SearchFieldFrameRounding());
|
||||
const float width = ImGui::GetContentRegionAvail().x - trailingWidth;
|
||||
ImGui::SetNextItemWidth(width > 0.0f ? width : 0.0f);
|
||||
const bool changed = ImGui::InputTextWithHint(id, hint, buffer, bufferSize);
|
||||
ImGui::PopStyleVar(2);
|
||||
return changed;
|
||||
}
|
||||
|
||||
inline void DrawToolbarLabel(const char* text) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextColored(HintTextColor(), "%s", text);
|
||||
}
|
||||
|
||||
inline bool ToolbarToggleButton(const char* label, bool& active, ImVec2 size = ImVec2(0.0f, 0.0f)) {
|
||||
if (!ToolbarButton(label, active, size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
active = !active;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void DrawToolbarRowGap() {
|
||||
ImGui::Dummy(ImVec2(0.0f, ToolbarRowGap()));
|
||||
}
|
||||
|
||||
inline void DrawHintText(const char* text) {
|
||||
ImGui::TextColored(HintTextColor(), "%s", text);
|
||||
}
|
||||
|
||||
inline void DrawEmptyState(
|
||||
const char* title,
|
||||
const char* subtitle = nullptr,
|
||||
ImVec2 start = ImVec2(10.0f, 10.0f)) {
|
||||
ImGui::SetCursorPos(start);
|
||||
ImGui::TextUnformatted(title);
|
||||
|
||||
if (subtitle && subtitle[0] != '\0') {
|
||||
ImGui::SetCursorPos(ImVec2(start.x, start.y + EmptyStateLineOffset()));
|
||||
ImGui::TextColored(EmptyStateSubtitleColor(), "%s", subtitle);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename GetNameFn, typename NavigateFn>
|
||||
inline void DrawToolbarBreadcrumbs(
|
||||
const char* rootLabel,
|
||||
size_t segmentCount,
|
||||
GetNameFn&& getName,
|
||||
NavigateFn&& navigateToSegment) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
|
||||
if (segmentCount == 0) {
|
||||
ImGui::TextUnformatted(rootLabel);
|
||||
ImGui::PopStyleColor(2);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < segmentCount; ++i) {
|
||||
if (i > 0) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("/");
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
const std::string label = getName(i);
|
||||
if (i + 1 < segmentCount) {
|
||||
if (ImGui::Button(label.c_str())) {
|
||||
navigateToSegment(i);
|
||||
}
|
||||
} else {
|
||||
ImGui::Text("%s", label.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(2);
|
||||
}
|
||||
|
||||
inline HierarchyNodeResult DrawHierarchyNode(
|
||||
const void* id,
|
||||
const char* label,
|
||||
bool selected,
|
||||
bool leaf) {
|
||||
ImGuiTreeNodeFlags flags =
|
||||
ImGuiTreeNodeFlags_OpenOnArrow |
|
||||
ImGuiTreeNodeFlags_SpanAvailWidth |
|
||||
ImGuiTreeNodeFlags_FramePadding;
|
||||
if (leaf) {
|
||||
flags |= ImGuiTreeNodeFlags_Leaf;
|
||||
}
|
||||
if (selected) {
|
||||
flags |= ImGuiTreeNodeFlags_Selected;
|
||||
}
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, HierarchyNodeFramePadding());
|
||||
const bool open = ImGui::TreeNodeEx(id, flags, "%s", label);
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
return HierarchyNodeResult{
|
||||
open,
|
||||
ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen(),
|
||||
ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)
|
||||
};
|
||||
}
|
||||
|
||||
inline void EndHierarchyNode() {
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
template <typename DrawIconFn>
|
||||
inline AssetTileResult DrawAssetTile(
|
||||
const char* label,
|
||||
bool selected,
|
||||
bool dimmed,
|
||||
DrawIconFn&& drawIcon) {
|
||||
const ImVec2 tileSize = AssetTileSize();
|
||||
ImGui::InvisibleButton("##AssetBtn", tileSize);
|
||||
|
||||
const bool clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
|
||||
const bool contextRequested = ImGui::IsItemClicked(ImGuiMouseButton_Right);
|
||||
const bool openRequested = ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0);
|
||||
const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
||||
|
||||
const ImVec2 min = ImGui::GetItemRectMin();
|
||||
const ImVec2 max = ImVec2(min.x + tileSize.x, min.y + tileSize.y);
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
|
||||
if (hovered || selected) {
|
||||
drawList->AddRectFilled(min, max, ImGui::GetColorU32(selected ? AssetTileSelectedFillColor() : AssetTileHoverFillColor()), AssetTileRounding());
|
||||
}
|
||||
if (selected) {
|
||||
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileSelectedBorderColor()), AssetTileRounding());
|
||||
}
|
||||
if (dimmed) {
|
||||
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileDraggedOverlayColor()), 0.0f);
|
||||
}
|
||||
|
||||
const ImVec2 iconOffset = AssetTileIconOffset();
|
||||
const ImVec2 iconSize = AssetTileIconSize();
|
||||
const ImVec2 iconMin(min.x + iconOffset.x, min.y + iconOffset.y);
|
||||
const ImVec2 iconMax(iconMin.x + iconSize.x, iconMin.y + iconSize.y);
|
||||
drawIcon(drawList, iconMin, iconMax);
|
||||
|
||||
const ImVec2 textSize = ImGui::CalcTextSize(label);
|
||||
const float textY = max.y - textSize.y - AssetTileTextPadding().y;
|
||||
ImGui::PushClipRect(ImVec2(min.x + AssetTileTextPadding().x, min.y), ImVec2(max.x - AssetTileTextPadding().x, max.y), true);
|
||||
drawList->AddText(ImVec2(min.x + AssetTileTextPadding().x, textY), ImGui::GetColorU32(AssetTileTextColor(selected)), label);
|
||||
ImGui::PopClipRect();
|
||||
|
||||
return AssetTileResult{ clicked, contextRequested, openRequested, hovered, min, max };
|
||||
}
|
||||
|
||||
inline void DrawAssetIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, AssetIconKind kind) {
|
||||
if (kind == AssetIconKind::Folder) {
|
||||
const ImU32 fillColor = ImGui::GetColorU32(AssetFolderIconFillColor());
|
||||
const ImU32 lineColor = ImGui::GetColorU32(AssetFolderIconLineColor());
|
||||
const float width = max.x - min.x;
|
||||
const float height = max.y - min.y;
|
||||
const ImVec2 tabMax(min.x + width * 0.45f, min.y + height * 0.35f);
|
||||
drawList->AddRectFilled(ImVec2(min.x, min.y + height * 0.14f), tabMax, fillColor, 2.0f);
|
||||
drawList->AddRectFilled(ImVec2(min.x, min.y + height * 0.28f), max, fillColor, 2.0f);
|
||||
drawList->AddRect(ImVec2(min.x, min.y + height * 0.14f), tabMax, lineColor, 2.0f);
|
||||
drawList->AddRect(ImVec2(min.x, min.y + height * 0.28f), max, lineColor, 2.0f);
|
||||
return;
|
||||
}
|
||||
|
||||
const ImU32 fillColor = ImGui::GetColorU32(AssetFileIconFillColor());
|
||||
const ImU32 lineColor = ImGui::GetColorU32(AssetFileIconLineColor());
|
||||
const ImVec2 foldA(max.x - 8.0f, min.y);
|
||||
const ImVec2 foldB(max.x, min.y + 8.0f);
|
||||
drawList->AddRectFilled(min, max, fillColor, 2.0f);
|
||||
drawList->AddRect(min, max, lineColor, 2.0f);
|
||||
drawList->AddTriangleFilled(foldA, ImVec2(max.x, min.y), foldB, ImGui::GetColorU32(AssetFileFoldColor()));
|
||||
drawList->AddLine(foldA, foldB, lineColor);
|
||||
}
|
||||
|
||||
inline ComponentSectionResult BeginComponentSection(
|
||||
const void* id,
|
||||
const char* label,
|
||||
bool canRemove,
|
||||
bool defaultOpen = true) {
|
||||
const ImGuiStyle& style = ImGui::GetStyle();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, InspectorSectionFramePadding());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, InspectorSectionItemSpacing().y));
|
||||
|
||||
ImGuiTreeNodeFlags flags =
|
||||
ImGuiTreeNodeFlags_Framed |
|
||||
ImGuiTreeNodeFlags_SpanAvailWidth |
|
||||
ImGuiTreeNodeFlags_FramePadding |
|
||||
ImGuiTreeNodeFlags_AllowOverlap;
|
||||
if (defaultOpen) {
|
||||
flags |= ImGuiTreeNodeFlags_DefaultOpen;
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
EndPopup();
|
||||
}
|
||||
|
||||
return ComponentSectionResult{ open, removeRequested };
|
||||
}
|
||||
|
||||
inline void EndComponentSection() {
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
inline bool InspectorActionButton(const char* label, ImVec2 size = ImVec2(-1.0f, 0.0f)) {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, InspectorActionButtonPadding());
|
||||
const bool pressed = ImGui::Button(label, size);
|
||||
ImGui::PopStyleVar();
|
||||
return pressed;
|
||||
}
|
||||
|
||||
inline bool BeginTitledPopup(const char* id, const char* title) {
|
||||
const bool open = BeginPopup(id);
|
||||
if (!open) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (title && title[0] != '\0') {
|
||||
ImGui::TextUnformatted(title);
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline void EndTitledPopup() {
|
||||
EndPopup();
|
||||
}
|
||||
|
||||
inline DialogActionResult DrawDialogActionRow(
|
||||
const char* primaryLabel,
|
||||
const char* secondaryLabel,
|
||||
bool primaryEnabled = true,
|
||||
bool secondaryEnabled = true) {
|
||||
DialogActionResult result = DialogActionResult::None;
|
||||
|
||||
ImGui::BeginDisabled(!primaryEnabled);
|
||||
if (ImGui::Button(primaryLabel, DialogActionButtonSize())) {
|
||||
result = DialogActionResult::Primary;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::BeginDisabled(!secondaryEnabled);
|
||||
if (ImGui::Button(secondaryLabel, DialogActionButtonSize())) {
|
||||
result = DialogActionResult::Secondary;
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void DrawRightAlignedText(const char* text, const ImVec4& color, float rightPadding = MenuBarStatusRightPadding()) {
|
||||
const ImVec2 textSize = ImGui::CalcTextSize(text);
|
||||
const float targetX = ImGui::GetWindowWidth() - textSize.x - rightPadding;
|
||||
if (targetX > ImGui::GetCursorPosX()) {
|
||||
ImGui::SetCursorPosX(targetX);
|
||||
}
|
||||
ImGui::TextColored(color, "%s", text);
|
||||
}
|
||||
|
||||
inline void BeginTitledTooltip(const char* title) {
|
||||
ImGui::BeginTooltip();
|
||||
if (title && title[0] != '\0') {
|
||||
ImGui::TextUnformatted(title);
|
||||
ImGui::Separator();
|
||||
}
|
||||
}
|
||||
|
||||
inline void EndTitledTooltip() {
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
inline bool DrawConsoleLogRow(const char* text) {
|
||||
ImGui::TextUnformatted(text);
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
drawList->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), ImGui::GetColorU32(ConsoleRowHoverFillColor()));
|
||||
}
|
||||
|
||||
return ImGui::IsItemClicked();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
#include "Actions/EditorActions.h"
|
||||
#include "ConsolePanel.h"
|
||||
#include "Core/EditorConsoleSink.h"
|
||||
#include "UI/Core.h"
|
||||
#include "UI/PanelChrome.h"
|
||||
#include "UI/UI.h"
|
||||
#include <XCEngine/Debug/LogCategory.h>
|
||||
#include <XCEngine/Debug/Logger.h>
|
||||
#include <imgui.h>
|
||||
@@ -9,12 +9,6 @@
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kConsoleToolbarHeight = 34.0f;
|
||||
|
||||
} // namespace
|
||||
|
||||
ConsolePanel::ConsolePanel() : Panel("Console") {
|
||||
}
|
||||
|
||||
@@ -27,26 +21,20 @@ void ConsolePanel::Render() {
|
||||
auto* sink = Debug::EditorConsoleSink::GetInstance();
|
||||
|
||||
{
|
||||
UI::PanelToolbarScope toolbar("ConsoleToolbar", kConsoleToolbarHeight);
|
||||
UI::PanelToolbarScope toolbar("ConsoleToolbar", UI::StandardPanelToolbarHeight());
|
||||
if (toolbar.IsOpen()) {
|
||||
if (UI::ToolbarButton("Clear")) {
|
||||
if (Actions::DrawToolbarAction(Actions::MakeClearConsoleAction())) {
|
||||
sink->Clear();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::TextDisabled("Filter");
|
||||
UI::DrawToolbarLabel("Filter");
|
||||
ImGui::SameLine();
|
||||
if (UI::ToolbarButton("Info", m_showInfo)) {
|
||||
m_showInfo = !m_showInfo;
|
||||
}
|
||||
Actions::DrawToolbarToggleAction(Actions::MakeConsoleInfoFilterAction(m_showInfo), m_showInfo);
|
||||
ImGui::SameLine();
|
||||
if (UI::ToolbarButton("Warn", m_showWarning)) {
|
||||
m_showWarning = !m_showWarning;
|
||||
}
|
||||
Actions::DrawToolbarToggleAction(Actions::MakeConsoleWarningFilterAction(m_showWarning), m_showWarning);
|
||||
ImGui::SameLine();
|
||||
if (UI::ToolbarButton("Error", m_showError)) {
|
||||
m_showError = !m_showError;
|
||||
}
|
||||
Actions::DrawToolbarToggleAction(Actions::MakeConsoleErrorFilterAction(m_showError), m_showError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,9 +87,7 @@ void ConsolePanel::Render() {
|
||||
|
||||
const char* category = ::XCEngine::Debug::LogCategoryToString(log.category);
|
||||
std::string fullMessage = std::string(prefix) + "[" + category + "] " + log.message.CStr();
|
||||
ImGui::TextUnformatted(fullMessage.c_str());
|
||||
|
||||
if (ImGui::IsItemClicked()) {
|
||||
if (UI::DrawConsoleLogRow(fullMessage.c_str())) {
|
||||
ImGui::SetClipboardText(fullMessage.c_str());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "GameViewPanel.h"
|
||||
#include "UI/PanelChrome.h"
|
||||
#include "UI/UI.h"
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
|
||||
@@ -9,9 +9,6 @@ class GameViewPanel : public Panel {
|
||||
public:
|
||||
GameViewPanel();
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
void RenderGameView();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
#include "Actions/EditorActions.h"
|
||||
#include "Commands/EntityCommands.h"
|
||||
#include "HierarchyPanel.h"
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/ISceneManager.h"
|
||||
@@ -5,25 +7,13 @@
|
||||
#include "Core/IUndoManager.h"
|
||||
#include "Core/EditorEvents.h"
|
||||
#include "Core/EventBus.h"
|
||||
#include "UI/Core.h"
|
||||
#include "UI/PanelChrome.h"
|
||||
#include "Utils/UndoUtils.h"
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
#include <XCEngine/Core/Math/Quaternion.h>
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
#include <XCEngine/Components/LightComponent.h>
|
||||
#include "UI/UI.h"
|
||||
#include <imgui.h>
|
||||
#include <cstring>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kHierarchyToolbarHeight = 34.0f;
|
||||
|
||||
} // namespace
|
||||
|
||||
HierarchyPanel::HierarchyPanel() : Panel("Hierarchy") {
|
||||
}
|
||||
|
||||
@@ -76,9 +66,9 @@ void HierarchyPanel::Render() {
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupContextWindow("HierarchyContextMenu", ImGuiPopupFlags_MouseButtonRight)) {
|
||||
if (UI::BeginPopupContextWindow("HierarchyContextMenu", ImGuiPopupFlags_MouseButtonRight)) {
|
||||
RenderCreateMenu(nullptr);
|
||||
ImGui::EndPopup();
|
||||
UI::EndPopup();
|
||||
}
|
||||
|
||||
ImGui::InvisibleButton("##DragTarget", ImVec2(-1, -1));
|
||||
@@ -86,20 +76,7 @@ void HierarchyPanel::Render() {
|
||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_PTR")) {
|
||||
::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data;
|
||||
if (sourceGameObject && sourceGameObject->GetParent() != nullptr) {
|
||||
auto& sceneManager = m_context->GetSceneManager();
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Reparent Entity", [&]() {
|
||||
auto* srcTransform = sourceGameObject->GetTransform();
|
||||
Math::Vector3 worldPos = srcTransform->GetPosition();
|
||||
Math::Quaternion worldRot = srcTransform->GetRotation();
|
||||
Math::Vector3 worldScale = srcTransform->GetScale();
|
||||
|
||||
sceneManager.MoveEntity(sourceGameObject->GetID(), 0);
|
||||
|
||||
srcTransform->SetPosition(worldPos);
|
||||
srcTransform->SetRotation(worldRot);
|
||||
srcTransform->SetScale(worldScale);
|
||||
sceneManager.MarkSceneDirty();
|
||||
});
|
||||
Commands::ReparentEntityPreserveWorldTransform(*m_context, sourceGameObject, 0);
|
||||
}
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
@@ -108,36 +85,47 @@ void HierarchyPanel::Render() {
|
||||
}
|
||||
|
||||
void HierarchyPanel::RenderSearchBar() {
|
||||
UI::PanelToolbarScope toolbar("HierarchyToolbar", kHierarchyToolbarHeight);
|
||||
UI::PanelToolbarScope toolbar("HierarchyToolbar", UI::StandardPanelToolbarHeight());
|
||||
if (!toolbar.IsOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(7.0f, 4.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 2.0f);
|
||||
|
||||
const float buttonWidth = 26.0f;
|
||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x - buttonWidth - 4.0f);
|
||||
ImGui::InputTextWithHint("##Search", "Search hierarchy", m_searchBuffer, sizeof(m_searchBuffer));
|
||||
const float buttonWidth = UI::HierarchyOverflowButtonWidth();
|
||||
UI::ToolbarSearchField(
|
||||
"##Search",
|
||||
"Search hierarchy",
|
||||
m_searchBuffer,
|
||||
sizeof(m_searchBuffer),
|
||||
buttonWidth + UI::ToolbarSearchTrailingSpacing());
|
||||
ImGui::SameLine();
|
||||
if (UI::ToolbarButton("...", false, ImVec2(buttonWidth, 0.0f))) {
|
||||
ImGui::OpenPopup("HierarchyOptions");
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopup("HierarchyOptions")) {
|
||||
if (ImGui::MenuItem("Sort By Name", nullptr, m_sortMode == SortMode::Name)) {
|
||||
m_sortMode = SortMode::Name;
|
||||
}
|
||||
if (ImGui::MenuItem("Sort By Component Count", nullptr, m_sortMode == SortMode::ComponentCount)) {
|
||||
m_sortMode = SortMode::ComponentCount;
|
||||
}
|
||||
if (ImGui::MenuItem("Transform First", nullptr, m_sortMode == SortMode::TransformFirst)) {
|
||||
m_sortMode = SortMode::TransformFirst;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
if (UI::BeginPopup("HierarchyOptions")) {
|
||||
const UI::MenuCommand commands[] = {
|
||||
UI::MenuCommand::Action("Sort By Name", nullptr, m_sortMode == SortMode::Name),
|
||||
UI::MenuCommand::Action("Sort By Component Count", nullptr, m_sortMode == SortMode::ComponentCount),
|
||||
UI::MenuCommand::Action("Transform First", nullptr, m_sortMode == SortMode::TransformFirst)
|
||||
};
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
UI::DrawMenuCommands(commands, [&](size_t index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
m_sortMode = SortMode::Name;
|
||||
break;
|
||||
case 1:
|
||||
m_sortMode = SortMode::ComponentCount;
|
||||
break;
|
||||
case 2:
|
||||
m_sortMode = SortMode::TransformFirst;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
UI::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject, const std::string& filter) {
|
||||
@@ -149,19 +137,6 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject
|
||||
|
||||
ImGui::PushID(static_cast<int>(gameObject->GetID()));
|
||||
|
||||
ImGuiTreeNodeFlags flags =
|
||||
ImGuiTreeNodeFlags_OpenOnArrow |
|
||||
ImGuiTreeNodeFlags_SpanAvailWidth |
|
||||
ImGuiTreeNodeFlags_FramePadding;
|
||||
|
||||
if (gameObject->GetChildCount() == 0) {
|
||||
flags |= ImGuiTreeNodeFlags_Leaf;
|
||||
}
|
||||
|
||||
if (m_context->GetSelectionManager().IsSelected(gameObject->GetID())) {
|
||||
flags |= ImGuiTreeNodeFlags_Selected;
|
||||
}
|
||||
|
||||
if (m_renaming && m_renamingEntity == gameObject) {
|
||||
if (m_renameJustStarted) {
|
||||
ImGui::SetKeyboardFocusHere();
|
||||
@@ -170,30 +145,22 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject
|
||||
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
if (ImGui::InputText("##Rename", m_renameBuffer, sizeof(m_renameBuffer), ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) {
|
||||
if (strlen(m_renameBuffer) > 0) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Rename Entity", [&]() {
|
||||
m_context->GetSceneManager().RenameEntity(gameObject->GetID(), m_renameBuffer);
|
||||
});
|
||||
}
|
||||
m_renaming = false;
|
||||
m_renamingEntity = nullptr;
|
||||
CommitRename();
|
||||
}
|
||||
|
||||
if (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)) {
|
||||
if (strlen(m_renameBuffer) > 0) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Rename Entity", [&]() {
|
||||
m_context->GetSceneManager().RenameEntity(gameObject->GetID(), m_renameBuffer);
|
||||
});
|
||||
}
|
||||
m_renaming = false;
|
||||
m_renamingEntity = nullptr;
|
||||
if (ImGui::IsItemActive() && ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||
CancelRename();
|
||||
} else if (!ImGui::IsItemActive() && ImGui::IsMouseClicked(0)) {
|
||||
CommitRename();
|
||||
}
|
||||
} else {
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(4.0f, 3.0f));
|
||||
bool isOpen = ImGui::TreeNodeEx((void*)gameObject->GetUUID(), flags, "%s", gameObject->GetName().c_str());
|
||||
ImGui::PopStyleVar();
|
||||
const UI::HierarchyNodeResult node = UI::DrawHierarchyNode(
|
||||
(void*)gameObject->GetUUID(),
|
||||
gameObject->GetName().c_str(),
|
||||
m_context->GetSelectionManager().IsSelected(gameObject->GetID()),
|
||||
gameObject->GetChildCount() == 0);
|
||||
|
||||
if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
|
||||
if (node.clicked) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (io.KeyCtrl) {
|
||||
if (!m_context->GetSelectionManager().IsSelected(gameObject->GetID())) {
|
||||
@@ -204,25 +171,22 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
|
||||
m_renaming = true;
|
||||
m_renamingEntity = gameObject;
|
||||
strcpy_s(m_renameBuffer, gameObject->GetName().c_str());
|
||||
m_renameJustStarted = true;
|
||||
if (node.doubleClicked) {
|
||||
BeginRename(gameObject);
|
||||
}
|
||||
|
||||
HandleDragDrop(gameObject);
|
||||
|
||||
if (ImGui::BeginPopupContextItem("EntityContextMenu")) {
|
||||
if (UI::BeginPopupContextItem("EntityContextMenu")) {
|
||||
RenderContextMenu(gameObject);
|
||||
ImGui::EndPopup();
|
||||
UI::EndPopup();
|
||||
}
|
||||
|
||||
if (isOpen) {
|
||||
if (node.open) {
|
||||
for (size_t i = 0; i < gameObject->GetChildCount(); i++) {
|
||||
RenderEntity(gameObject->GetChild(i), filter);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
UI::EndHierarchyNode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,132 +194,87 @@ void HierarchyPanel::RenderEntity(::XCEngine::Components::GameObject* gameObject
|
||||
}
|
||||
|
||||
void HierarchyPanel::RenderContextMenu(::XCEngine::Components::GameObject* gameObject) {
|
||||
auto& sceneManager = m_context->GetSceneManager();
|
||||
auto& selectionManager = m_context->GetSelectionManager();
|
||||
|
||||
if (ImGui::BeginMenu("Create")) {
|
||||
if (UI::DrawMenuScope("Create", [&]() {
|
||||
RenderCreateMenu(gameObject);
|
||||
ImGui::EndMenu();
|
||||
})) {
|
||||
}
|
||||
|
||||
if (gameObject != nullptr && ImGui::MenuItem("Create Child")) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Create Child", [&]() {
|
||||
auto* child = sceneManager.CreateEntity("GameObject", gameObject);
|
||||
selectionManager.SetSelectedEntity(child->GetID());
|
||||
Actions::DrawMenuAction(Actions::MakeCreateChildEntityAction(gameObject), [&]() {
|
||||
Commands::CreateEmptyEntity(*m_context, gameObject, "Create Child", "GameObject");
|
||||
});
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (gameObject != nullptr && gameObject->GetParent() != nullptr) {
|
||||
if (ImGui::MenuItem("Detach from Parent")) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Reparent Entity", [&]() {
|
||||
sceneManager.MoveEntity(gameObject->GetID(), 0);
|
||||
Actions::DrawMenuSeparator();
|
||||
Actions::DrawMenuAction(Actions::MakeDetachEntityAction(gameObject), [&]() {
|
||||
Commands::DetachEntity(*m_context, gameObject);
|
||||
});
|
||||
}
|
||||
Actions::DrawMenuAction(Actions::MakeRenameEntityAction(gameObject), [&]() {
|
||||
BeginRename(gameObject);
|
||||
});
|
||||
Actions::DrawMenuAction(Actions::MakeDeleteEntityAction(gameObject), [&]() {
|
||||
Commands::DeleteEntity(*m_context, gameObject->GetID());
|
||||
});
|
||||
Actions::DrawMenuSeparator();
|
||||
Actions::DrawMenuAction(Actions::MakeCopyEntityAction(gameObject), [&]() {
|
||||
Commands::CopyEntity(*m_context, gameObject->GetID());
|
||||
});
|
||||
Actions::DrawMenuAction(Actions::MakePasteEntityAction(*m_context), [&]() {
|
||||
Commands::PasteEntity(*m_context, gameObject->GetID());
|
||||
});
|
||||
Actions::DrawMenuAction(Actions::MakeDuplicateEntityAction(gameObject), [&]() {
|
||||
Commands::DuplicateEntity(*m_context, gameObject->GetID());
|
||||
});
|
||||
}
|
||||
|
||||
void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent) {
|
||||
Actions::DrawMenuAction(Actions::MakeCreateEmptyEntityAction(), [&]() {
|
||||
Commands::CreateEmptyEntity(*m_context, parent, "Create Entity", "GameObject");
|
||||
});
|
||||
Actions::DrawMenuSeparator();
|
||||
Actions::DrawMenuAction(Actions::MakeCreateCameraEntityAction(), [&]() {
|
||||
Commands::CreateCameraEntity(*m_context, parent);
|
||||
});
|
||||
Actions::DrawMenuAction(Actions::MakeCreateLightEntityAction(), [&]() {
|
||||
Commands::CreateLightEntity(*m_context, parent);
|
||||
});
|
||||
Actions::DrawMenuSeparator();
|
||||
Actions::DrawMenuAction(Actions::MakeCreateCubeEntityAction(), [&]() {
|
||||
Commands::CreateEmptyEntity(*m_context, parent, "Create Cube", "Cube");
|
||||
});
|
||||
Actions::DrawMenuAction(Actions::MakeCreateSphereEntityAction(), [&]() {
|
||||
Commands::CreateEmptyEntity(*m_context, parent, "Create Sphere", "Sphere");
|
||||
});
|
||||
Actions::DrawMenuAction(Actions::MakeCreatePlaneEntityAction(), [&]() {
|
||||
Commands::CreateEmptyEntity(*m_context, parent, "Create Plane", "Plane");
|
||||
});
|
||||
}
|
||||
|
||||
void HierarchyPanel::BeginRename(::XCEngine::Components::GameObject* gameObject) {
|
||||
if (!gameObject) {
|
||||
CancelRename();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Rename", "F2")) {
|
||||
if (gameObject) {
|
||||
m_renaming = true;
|
||||
m_renamingEntity = gameObject;
|
||||
strcpy_s(m_renameBuffer, gameObject->GetName().c_str());
|
||||
m_renameJustStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Delete", "Delete")) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Delete Entity", [&]() {
|
||||
sceneManager.DeleteEntity(gameObject->GetID());
|
||||
});
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem("Copy", "Ctrl+C")) {
|
||||
sceneManager.CopyEntity(gameObject->GetID());
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Paste", "Ctrl+V", false, sceneManager.HasClipboardData())) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Paste Entity", [&]() {
|
||||
const uint64_t newId = sceneManager.PasteEntity(gameObject->GetID());
|
||||
if (newId != 0) {
|
||||
selectionManager.SetSelectedEntity(newId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Duplicate", "Ctrl+D")) {
|
||||
uint64_t newId = 0;
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Duplicate Entity", [&]() {
|
||||
newId = sceneManager.DuplicateEntity(gameObject->GetID());
|
||||
if (newId != 0) {
|
||||
selectionManager.SetSelectedEntity(newId);
|
||||
}
|
||||
});
|
||||
if (newId != 0) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HierarchyPanel::RenderCreateMenu(::XCEngine::Components::GameObject* parent) {
|
||||
auto& sceneManager = m_context->GetSceneManager();
|
||||
auto& selectionManager = m_context->GetSelectionManager();
|
||||
|
||||
if (ImGui::MenuItem("Empty Object")) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Create Entity", [&]() {
|
||||
auto* newEntity = sceneManager.CreateEntity("GameObject", parent);
|
||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||
});
|
||||
void HierarchyPanel::CommitRename() {
|
||||
if (m_renamingEntity && strlen(m_renameBuffer) > 0) {
|
||||
Commands::RenameEntity(*m_context, m_renamingEntity->GetID(), m_renameBuffer);
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
CancelRename();
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Camera")) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Create Camera", [&]() {
|
||||
auto* newEntity = sceneManager.CreateEntity("Camera", parent);
|
||||
newEntity->AddComponent<::XCEngine::Components::CameraComponent>();
|
||||
sceneManager.MarkSceneDirty();
|
||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||
});
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Light")) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Create Light", [&]() {
|
||||
auto* newEntity = sceneManager.CreateEntity("Light", parent);
|
||||
newEntity->AddComponent<::XCEngine::Components::LightComponent>();
|
||||
sceneManager.MarkSceneDirty();
|
||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||
});
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::MenuItem("Cube")) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Create Cube", [&]() {
|
||||
auto* newEntity = sceneManager.CreateEntity("Cube", parent);
|
||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||
});
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Sphere")) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Create Sphere", [&]() {
|
||||
auto* newEntity = sceneManager.CreateEntity("Sphere", parent);
|
||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||
});
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem("Plane")) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Create Plane", [&]() {
|
||||
auto* newEntity = sceneManager.CreateEntity("Plane", parent);
|
||||
selectionManager.SetSelectedEntity(newEntity->GetID());
|
||||
});
|
||||
}
|
||||
void HierarchyPanel::CancelRename() {
|
||||
m_renaming = false;
|
||||
m_renamingEntity = nullptr;
|
||||
m_renameBuffer[0] = '\0';
|
||||
m_renameJustStarted = false;
|
||||
}
|
||||
|
||||
void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* gameObject) {
|
||||
auto& sceneManager = m_context->GetSceneManager();
|
||||
|
||||
if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_None)) {
|
||||
ImGui::SetDragDropPayload("ENTITY_PTR", &gameObject, sizeof(::XCEngine::Components::GameObject*));
|
||||
ImGui::Text("%s", gameObject->GetName().c_str());
|
||||
@@ -365,32 +284,8 @@ void HierarchyPanel::HandleDragDrop(::XCEngine::Components::GameObject* gameObje
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("ENTITY_PTR")) {
|
||||
::XCEngine::Components::GameObject* sourceGameObject = *(::XCEngine::Components::GameObject**)payload->Data;
|
||||
if (sourceGameObject != gameObject && sourceGameObject != nullptr) {
|
||||
bool isValidMove = true;
|
||||
::XCEngine::Components::GameObject* checkParent = gameObject;
|
||||
while (checkParent != nullptr) {
|
||||
if (checkParent == sourceGameObject) {
|
||||
isValidMove = false;
|
||||
break;
|
||||
}
|
||||
checkParent = checkParent->GetParent();
|
||||
}
|
||||
|
||||
if (isValidMove) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Reparent Entity", [&]() {
|
||||
auto* srcTransform = sourceGameObject->GetTransform();
|
||||
Math::Vector3 worldPos = srcTransform->GetPosition();
|
||||
Math::Quaternion worldRot = srcTransform->GetRotation();
|
||||
Math::Vector3 worldScale = srcTransform->GetScale();
|
||||
|
||||
sceneManager.MoveEntity(sourceGameObject->GetID(), gameObject->GetID());
|
||||
|
||||
srcTransform->SetPosition(worldPos);
|
||||
srcTransform->SetRotation(worldRot);
|
||||
srcTransform->SetScale(worldScale);
|
||||
sceneManager.MarkSceneDirty();
|
||||
});
|
||||
}
|
||||
if (sourceGameObject != gameObject && Commands::CanReparentEntity(sourceGameObject, gameObject)) {
|
||||
Commands::ReparentEntityPreserveWorldTransform(*m_context, sourceGameObject, gameObject->GetID());
|
||||
}
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
@@ -404,53 +299,21 @@ void HierarchyPanel::HandleKeyboardShortcuts() {
|
||||
::XCEngine::Components::GameObject* selectedGameObject = sceneManager.GetEntity(selectionManager.GetSelectedEntity());
|
||||
|
||||
if (ImGui::IsWindowFocused()) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Delete)) {
|
||||
if (selectedGameObject != nullptr) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Delete Entity", [&]() {
|
||||
sceneManager.DeleteEntity(selectedGameObject->GetID());
|
||||
Actions::HandleShortcut(Actions::MakeDeleteEntityAction(selectedGameObject), [&]() {
|
||||
Commands::DeleteEntity(*m_context, selectedGameObject->GetID());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_F2)) {
|
||||
if (selectedGameObject != nullptr) {
|
||||
m_renaming = true;
|
||||
m_renamingEntity = selectedGameObject;
|
||||
strcpy_s(m_renameBuffer, selectedGameObject->GetName().c_str());
|
||||
m_renameJustStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (io.KeyCtrl) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_C)) {
|
||||
if (selectedGameObject != nullptr) {
|
||||
sceneManager.CopyEntity(selectedGameObject->GetID());
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_V)) {
|
||||
if (sceneManager.HasClipboardData()) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Paste Entity", [&]() {
|
||||
const uint64_t newId = sceneManager.PasteEntity(selectedGameObject ? selectedGameObject->GetID() : 0);
|
||||
if (newId != 0) {
|
||||
selectionManager.SetSelectedEntity(newId);
|
||||
}
|
||||
Actions::HandleShortcut(Actions::MakeRenameEntityAction(selectedGameObject), [&]() {
|
||||
BeginRename(selectedGameObject);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_D)) {
|
||||
if (selectedGameObject != nullptr) {
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, "Duplicate Entity", [&]() {
|
||||
const uint64_t newId = sceneManager.DuplicateEntity(selectedGameObject->GetID());
|
||||
if (newId != 0) {
|
||||
selectionManager.SetSelectedEntity(newId);
|
||||
}
|
||||
Actions::HandleShortcut(Actions::MakeCopyEntityAction(selectedGameObject), [&]() {
|
||||
Commands::CopyEntity(*m_context, selectedGameObject->GetID());
|
||||
});
|
||||
Actions::HandleShortcut(Actions::MakePasteEntityAction(*m_context), [&]() {
|
||||
Commands::PasteEntity(*m_context, selectedGameObject ? selectedGameObject->GetID() : 0);
|
||||
});
|
||||
Actions::HandleShortcut(Actions::MakeDuplicateEntityAction(selectedGameObject), [&]() {
|
||||
Commands::DuplicateEntity(*m_context, selectedGameObject->GetID());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,9 @@ private:
|
||||
void RenderEntity(::XCEngine::Components::GameObject* gameObject, const std::string& filter);
|
||||
void RenderContextMenu(::XCEngine::Components::GameObject* gameObject);
|
||||
void RenderCreateMenu(::XCEngine::Components::GameObject* parent);
|
||||
void BeginRename(::XCEngine::Components::GameObject* gameObject);
|
||||
void CommitRename();
|
||||
void CancelRename();
|
||||
void HandleDragDrop(::XCEngine::Components::GameObject* gameObject);
|
||||
void HandleKeyboardShortcuts();
|
||||
bool PassesFilter(::XCEngine::Components::GameObject* gameObject, const std::string& filter);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#include "Actions/EditorActions.h"
|
||||
#include "Commands/ComponentCommands.h"
|
||||
#include "InspectorPanel.h"
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/ISceneManager.h"
|
||||
#include "Core/ISelectionManager.h"
|
||||
#include "Core/IUndoManager.h"
|
||||
#include "Core/EventBus.h"
|
||||
#include "Core/EditorEvents.h"
|
||||
#include "ComponentEditors/ComponentEditorRegistry.h"
|
||||
#include "ComponentEditors/IComponentEditor.h"
|
||||
#include "UI/PanelChrome.h"
|
||||
#include "Utils/UndoUtils.h"
|
||||
#include "UI/UI.h"
|
||||
#include <imgui.h>
|
||||
#include <string>
|
||||
|
||||
@@ -52,14 +52,10 @@ void InspectorPanel::Render() {
|
||||
if (gameObject) {
|
||||
RenderGameObject(gameObject);
|
||||
} else {
|
||||
ImGui::SetCursorPos(ImVec2(10.0f, 10.0f));
|
||||
ImGui::Text("Object not found");
|
||||
RenderEmptyState("Object not found");
|
||||
}
|
||||
} else {
|
||||
ImGui::SetCursorPos(ImVec2(10.0f, 10.0f));
|
||||
ImGui::Text("No Selection");
|
||||
ImGui::SetCursorPos(ImVec2(10.0f, 30.0f));
|
||||
ImGui::TextColored(ImVec4(0.55f, 0.55f, 0.55f, 1.0f), "Select an object in Hierarchy");
|
||||
RenderEmptyState("No Selection", "Select an object in Hierarchy");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -81,7 +77,7 @@ void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameOb
|
||||
RenderComponent(component, gameObject);
|
||||
}
|
||||
|
||||
if (ImGui::Button("Add Component", ImVec2(-1.0f, 0.0f))) {
|
||||
if (Actions::DrawInspectorAction(Actions::MakeAddComponentButtonAction(gameObject != nullptr))) {
|
||||
ImGui::OpenPopup("AddComponent");
|
||||
}
|
||||
RenderAddComponentPopup(gameObject);
|
||||
@@ -91,13 +87,19 @@ void InspectorPanel::RenderGameObject(::XCEngine::Components::GameObject* gameOb
|
||||
}
|
||||
}
|
||||
|
||||
void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject) {
|
||||
if (!ImGui::BeginPopup("AddComponent")) {
|
||||
void InspectorPanel::RenderEmptyState(const char* title, const char* subtitle) {
|
||||
UI::PanelContentScope content("InspectorEmptyState", UI::InspectorPanelContentPadding());
|
||||
if (!content.IsOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::Text("Components");
|
||||
ImGui::Separator();
|
||||
UI::DrawEmptyState(title, subtitle);
|
||||
}
|
||||
|
||||
void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject) {
|
||||
if (!UI::BeginTitledPopup("AddComponent", "Components")) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool drewAnyEntry = false;
|
||||
for (const auto& editor : ComponentEditorRegistry::Get().GetEditors()) {
|
||||
@@ -106,7 +108,7 @@ void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject*
|
||||
}
|
||||
|
||||
drewAnyEntry = true;
|
||||
const bool canAdd = editor->CanAddTo(gameObject);
|
||||
const bool canAdd = Commands::CanAddComponent(*editor, gameObject);
|
||||
std::string label = editor->GetDisplayName();
|
||||
if (!canAdd) {
|
||||
const char* reason = editor->GetAddDisabledReason(gameObject);
|
||||
@@ -117,88 +119,49 @@ void InspectorPanel::RenderAddComponentPopup(::XCEngine::Components::GameObject*
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::MenuItem(label.c_str(), nullptr, false, canAdd)) {
|
||||
bool added = false;
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, std::string("Add ") + editor->GetDisplayName() + " Component", [&]() {
|
||||
added = editor->AddTo(gameObject) != nullptr;
|
||||
if (added) {
|
||||
m_context->GetSceneManager().MarkSceneDirty();
|
||||
}
|
||||
});
|
||||
if (added) {
|
||||
Actions::DrawMenuAction(Actions::MakeAddComponentMenuAction(label, canAdd), [&]() {
|
||||
if (Commands::AddComponent(*m_context, *editor, gameObject)) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!drewAnyEntry) {
|
||||
ImGui::TextDisabled("No registered component editors");
|
||||
UI::DrawHintText("No registered component editors");
|
||||
}
|
||||
|
||||
ImGui::EndPopup();
|
||||
UI::EndTitledPopup();
|
||||
}
|
||||
|
||||
void InspectorPanel::RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject) {
|
||||
if (!component) return;
|
||||
|
||||
IComponentEditor* editor = ComponentEditorRegistry::Get().FindEditor(component);
|
||||
ImGuiStyle& style = ImGui::GetStyle();
|
||||
|
||||
const std::string name = component->GetName();
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2{6, 4});
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(style.ItemSpacing.x, 0.0f));
|
||||
const UI::ComponentSectionResult section =
|
||||
UI::BeginComponentSection(
|
||||
(void*)typeid(*component).hash_code(),
|
||||
name.c_str(),
|
||||
Commands::CanRemoveComponent(component, editor));
|
||||
|
||||
ImGuiTreeNodeFlags flags =
|
||||
ImGuiTreeNodeFlags_DefaultOpen |
|
||||
ImGuiTreeNodeFlags_Framed |
|
||||
ImGuiTreeNodeFlags_SpanAvailWidth |
|
||||
ImGuiTreeNodeFlags_FramePadding |
|
||||
ImGuiTreeNodeFlags_AllowOverlap;
|
||||
|
||||
bool open = ImGui::TreeNodeEx((void*)typeid(*component).hash_code(), flags, "%s", name.c_str());
|
||||
|
||||
ImGui::PopStyleVar(2);
|
||||
|
||||
bool removeComponent = false;
|
||||
const bool canRemoveComponent = editor ? editor->CanRemove(component) : false;
|
||||
if (ImGui::BeginPopupContextItem("ComponentSettings")) {
|
||||
if (ImGui::MenuItem("Remove Component", nullptr, false, canRemoveComponent)) {
|
||||
removeComponent = true;
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
if (removeComponent) {
|
||||
RemoveComponentByType(component, gameObject);
|
||||
if (section.removeRequested) {
|
||||
Commands::RemoveComponent(*m_context, component, gameObject, editor);
|
||||
return;
|
||||
}
|
||||
|
||||
if (open) {
|
||||
if (section.open) {
|
||||
if (editor) {
|
||||
if (editor->Render(component, &m_context->GetUndoManager())) {
|
||||
m_context->GetSceneManager().MarkSceneDirty();
|
||||
}
|
||||
} else {
|
||||
ImGui::TextDisabled("No registered editor for this component");
|
||||
UI::DrawHintText("No registered editor for this component");
|
||||
}
|
||||
|
||||
ImGui::TreePop();
|
||||
UI::EndComponentSection();
|
||||
}
|
||||
}
|
||||
|
||||
void InspectorPanel::RemoveComponentByType(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject) {
|
||||
if (!component || !gameObject) return;
|
||||
|
||||
if (dynamic_cast<::XCEngine::Components::TransformComponent*>(component)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UndoUtils::ExecuteSceneCommand(*m_context, std::string("Remove ") + component->GetName() + " Component", [&]() {
|
||||
gameObject->RemoveComponent(component);
|
||||
m_context->GetSceneManager().MarkSceneDirty();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ private:
|
||||
void RenderGameObject(::XCEngine::Components::GameObject* gameObject);
|
||||
void RenderAddComponentPopup(::XCEngine::Components::GameObject* gameObject);
|
||||
void RenderComponent(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject);
|
||||
void RemoveComponentByType(::XCEngine::Components::Component* component, ::XCEngine::Components::GameObject* gameObject);
|
||||
void RenderEmptyState(const char* title, const char* subtitle = nullptr);
|
||||
void OnSelectionChanged(const struct SelectionChangedEvent& event);
|
||||
|
||||
uint64_t m_selectionHandlerId = 0;
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
#include "Actions/EditorActions.h"
|
||||
#include "Commands/EntityCommands.h"
|
||||
#include "Commands/SceneCommands.h"
|
||||
#include "MenuBar.h"
|
||||
#include "Core/EditorEvents.h"
|
||||
#include "Core/EventBus.h"
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/ISceneManager.h"
|
||||
#include "Core/IUndoManager.h"
|
||||
#include "Core/ISelectionManager.h"
|
||||
#include "Utils/SceneEditorUtils.h"
|
||||
#include "UI/UI.h"
|
||||
#include <filesystem>
|
||||
#include <imgui.h>
|
||||
|
||||
@@ -23,56 +28,8 @@ void MenuBar::Render() {
|
||||
RenderSceneStatus();
|
||||
ImGui::EndMainMenuBar();
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::NewScene() {
|
||||
if (!m_context || !SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_context->GetSceneManager().NewScene();
|
||||
m_context->GetSelectionManager().ClearSelection();
|
||||
m_context->GetUndoManager().ClearHistory();
|
||||
}
|
||||
|
||||
void MenuBar::OpenScene() {
|
||||
if (!m_context || !SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string filePath = SceneEditorUtils::OpenSceneFileDialog(
|
||||
m_context->GetProjectPath(),
|
||||
m_context->GetSceneManager().GetCurrentScenePath());
|
||||
if (!filePath.empty()) {
|
||||
if (m_context->GetSceneManager().LoadScene(filePath)) {
|
||||
m_context->GetSelectionManager().ClearSelection();
|
||||
m_context->GetUndoManager().ClearHistory();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::SaveScene() {
|
||||
if (!m_context) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SceneEditorUtils::SaveCurrentScene(*m_context)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MenuBar::SaveSceneAs() {
|
||||
if (!m_context) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string filePath = SceneEditorUtils::SaveSceneFileDialog(
|
||||
m_context->GetProjectPath(),
|
||||
m_context->GetSceneManager().GetCurrentScenePath(),
|
||||
m_context->GetSceneManager().GetCurrentSceneName());
|
||||
if (!filePath.empty() && m_context->GetSceneManager().SaveSceneAs(filePath)) {
|
||||
m_context->GetProjectManager().RefreshCurrentFolder();
|
||||
}
|
||||
RenderAboutPopup();
|
||||
}
|
||||
|
||||
void MenuBar::HandleShortcuts() {
|
||||
@@ -80,92 +37,83 @@ void MenuBar::HandleShortcuts() {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (!io.KeyCtrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& undoManager = m_context->GetUndoManager();
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_N, false)) {
|
||||
NewScene();
|
||||
}
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_O, false)) {
|
||||
OpenScene();
|
||||
}
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_S, false)) {
|
||||
if (io.KeyShift) {
|
||||
SaveSceneAs();
|
||||
} else {
|
||||
SaveScene();
|
||||
}
|
||||
}
|
||||
if (!io.WantTextInput) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Z, false)) {
|
||||
if (io.KeyShift) {
|
||||
if (undoManager.CanRedo()) {
|
||||
undoManager.Redo();
|
||||
}
|
||||
} else if (undoManager.CanUndo()) {
|
||||
undoManager.Undo();
|
||||
}
|
||||
}
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Y, false) && undoManager.CanRedo()) {
|
||||
undoManager.Redo();
|
||||
}
|
||||
}
|
||||
Actions::HandleShortcut(Actions::MakeNewSceneAction(), [&]() { Commands::NewScene(*m_context); });
|
||||
Actions::HandleShortcut(Actions::MakeOpenSceneAction(), [&]() { Commands::OpenSceneWithDialog(*m_context); });
|
||||
Actions::HandleShortcut(Actions::MakeSaveSceneAction(), [&]() { Commands::SaveCurrentScene(*m_context); });
|
||||
Actions::HandleShortcut(Actions::MakeSaveSceneAsAction(), [&]() { Commands::SaveSceneAsWithDialog(*m_context); });
|
||||
Actions::HandleShortcut(Actions::MakeUndoAction(*m_context), [&]() { m_context->GetUndoManager().Undo(); });
|
||||
Actions::HandleShortcut(Actions::MakeRedoAction(*m_context), [&]() { m_context->GetUndoManager().Redo(); });
|
||||
}
|
||||
|
||||
void MenuBar::ShowFileMenu() {
|
||||
if (ImGui::BeginMenu("File")) {
|
||||
if (ImGui::MenuItem("New Scene", "Ctrl+N")) {
|
||||
NewScene();
|
||||
}
|
||||
if (ImGui::MenuItem("Open Scene", "Ctrl+O")) {
|
||||
OpenScene();
|
||||
}
|
||||
if (ImGui::MenuItem("Save Scene", "Ctrl+S")) {
|
||||
SaveScene();
|
||||
}
|
||||
if (ImGui::MenuItem("Save Scene As...", "Ctrl+Shift+S")) {
|
||||
SaveSceneAs();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Exit", "Alt+F4")) {}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
UI::DrawMenuScope("File", [&]() {
|
||||
Actions::DrawMenuAction(Actions::MakeNewSceneAction(), [&]() { Commands::NewScene(*m_context); });
|
||||
Actions::DrawMenuAction(Actions::MakeOpenSceneAction(), [&]() { Commands::OpenSceneWithDialog(*m_context); });
|
||||
Actions::DrawMenuAction(Actions::MakeSaveSceneAction(), [&]() { Commands::SaveCurrentScene(*m_context); });
|
||||
Actions::DrawMenuAction(Actions::MakeSaveSceneAsAction(), [&]() { Commands::SaveSceneAsWithDialog(*m_context); });
|
||||
Actions::DrawMenuSeparator();
|
||||
Actions::DrawMenuAction(Actions::MakeExitAction(), [&]() {
|
||||
m_context->GetEventBus().Publish(EditorExitRequestedEvent{});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void MenuBar::ShowEditMenu() {
|
||||
if (ImGui::BeginMenu("Edit")) {
|
||||
auto& undoManager = m_context->GetUndoManager();
|
||||
const std::string undoLabel = undoManager.CanUndo() ? "Undo " + undoManager.GetUndoLabel() : "Undo";
|
||||
const std::string redoLabel = undoManager.CanRedo() ? "Redo " + undoManager.GetRedoLabel() : "Redo";
|
||||
if (ImGui::MenuItem(undoLabel.c_str(), "Ctrl+Z", false, undoManager.CanUndo())) {
|
||||
undoManager.Undo();
|
||||
}
|
||||
if (ImGui::MenuItem(redoLabel.c_str(), "Ctrl+Y", false, undoManager.CanRedo())) {
|
||||
undoManager.Redo();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("Cut", "Ctrl+X")) {}
|
||||
if (ImGui::MenuItem("Copy", "Ctrl+C")) {}
|
||||
if (ImGui::MenuItem("Paste", "Ctrl+V")) {}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
::XCEngine::Components::GameObject* selectedGameObject = Actions::GetSelectedGameObject(*m_context);
|
||||
|
||||
UI::DrawMenuScope("Edit", [&]() {
|
||||
Actions::DrawMenuAction(Actions::MakeUndoAction(*m_context), [&]() { m_context->GetUndoManager().Undo(); });
|
||||
Actions::DrawMenuAction(Actions::MakeRedoAction(*m_context), [&]() { m_context->GetUndoManager().Redo(); });
|
||||
Actions::DrawMenuSeparator();
|
||||
Actions::DrawMenuAction(Actions::MakeCutAction(false), []() {});
|
||||
Actions::DrawMenuAction(Actions::MakeCopyEntityAction(selectedGameObject), [&]() {
|
||||
Commands::CopyEntity(*m_context, selectedGameObject->GetID());
|
||||
});
|
||||
Actions::DrawMenuAction(Actions::MakePasteEntityAction(*m_context), [&]() {
|
||||
Commands::PasteEntity(*m_context, selectedGameObject ? selectedGameObject->GetID() : 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void MenuBar::ShowViewMenu() {
|
||||
if (ImGui::BeginMenu("View")) {
|
||||
if (ImGui::MenuItem("Reset Layout")) {}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
UI::DrawMenuScope("View", [&]() {
|
||||
Actions::DrawMenuAction(Actions::MakeResetLayoutAction(), [&]() {
|
||||
m_context->GetEventBus().Publish(DockLayoutResetRequestedEvent{});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void MenuBar::ShowHelpMenu() {
|
||||
if (ImGui::BeginMenu("Help")) {
|
||||
if (ImGui::MenuItem("About")) {}
|
||||
ImGui::EndMenu();
|
||||
UI::DrawMenuScope("Help", [&]() {
|
||||
Actions::DrawMenuAction(Actions::MakeAboutAction(), [&]() {
|
||||
m_aboutPopup.RequestOpen();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void MenuBar::RenderAboutPopup() {
|
||||
m_aboutPopup.ConsumeOpenRequest("About XCEngine Editor");
|
||||
|
||||
if (!UI::BeginModalPopup("About XCEngine Editor")) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::TextUnformatted("XCEngine Editor");
|
||||
ImGui::Separator();
|
||||
UI::DrawHintText("Unity-like editor shell built on Dear ImGui.");
|
||||
ImGui::Spacing();
|
||||
ImGui::Text("Date: 2026-03-26");
|
||||
ImGui::Text("UI Refactor: Actions / Commands / Layout in progress");
|
||||
if (m_context) {
|
||||
ImGui::Text("Project: %s", m_context->GetProjectPath().c_str());
|
||||
}
|
||||
ImGui::Spacing();
|
||||
|
||||
if (Actions::DrawButtonAction(Actions::MakeAction("Close"), UI::DialogActionButtonSize())) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
UI::EndPopup();
|
||||
}
|
||||
|
||||
void MenuBar::RenderSceneStatus() {
|
||||
@@ -184,27 +132,18 @@ void MenuBar::RenderSceneStatus() {
|
||||
: std::filesystem::path(sceneManager.GetCurrentScenePath()).filename().string();
|
||||
|
||||
const bool dirty = sceneManager.IsSceneDirty();
|
||||
const std::string statusText = std::string("Scene: ") + fileLabel + (dirty ? " Modified" : " Saved");
|
||||
const ImVec2 textSize = ImGui::CalcTextSize(statusText.c_str());
|
||||
const float targetX = ImGui::GetWindowWidth() - textSize.x - 20.0f;
|
||||
if (targetX > ImGui::GetCursorPosX()) {
|
||||
ImGui::SetCursorPosX(targetX);
|
||||
}
|
||||
|
||||
const ImVec4 accentColor = dirty
|
||||
? ImVec4(0.94f, 0.68f, 0.20f, 1.0f)
|
||||
: ImVec4(0.48f, 0.78f, 0.49f, 1.0f);
|
||||
ImGui::TextColored(accentColor, "%s", statusText.c_str());
|
||||
const std::string statusText = dirty ? (std::string("* ") + fileLabel) : fileLabel;
|
||||
UI::DrawRightAlignedText(
|
||||
statusText.c_str(),
|
||||
dirty ? UI::MenuBarStatusDirtyColor() : UI::MenuBarStatusIdleColor());
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::Text("Scene");
|
||||
ImGui::Separator();
|
||||
UI::BeginTitledTooltip("Scene");
|
||||
ImGui::Text("Name: %s", sceneLabel.c_str());
|
||||
ImGui::Text("File: %s", fileLabel.c_str());
|
||||
ImGui::Text("State: %s", dirty ? "Modified" : "Saved");
|
||||
ImGui::Text("Path: %s", sceneManager.GetCurrentScenePath().empty() ? "(not saved yet)" : sceneManager.GetCurrentScenePath().c_str());
|
||||
ImGui::EndTooltip();
|
||||
UI::EndTitledTooltip();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
#include "UI/PopupState.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
@@ -12,15 +13,14 @@ public:
|
||||
|
||||
private:
|
||||
void HandleShortcuts();
|
||||
void NewScene();
|
||||
void OpenScene();
|
||||
void SaveScene();
|
||||
void SaveSceneAs();
|
||||
void RenderSceneStatus();
|
||||
void RenderAboutPopup();
|
||||
void ShowFileMenu();
|
||||
void ShowEditMenu();
|
||||
void ShowViewMenu();
|
||||
void ShowHelpMenu();
|
||||
|
||||
UI::DeferredPopupState m_aboutPopup;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
85
editor/src/panels/PanelCollection.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include "Panel.h"
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
|
||||
class IEditorContext;
|
||||
|
||||
class PanelCollection {
|
||||
public:
|
||||
void SetContext(IEditorContext* context) {
|
||||
m_context = context;
|
||||
|
||||
if (!m_context) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& panel : m_panels) {
|
||||
if (!panel->HasContext()) {
|
||||
panel->SetContext(m_context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TPanel, typename... Args>
|
||||
TPanel& Emplace(Args&&... args) {
|
||||
static_assert(std::is_base_of_v<Panel, TPanel>, "TPanel must derive from Panel");
|
||||
|
||||
auto panel = std::make_unique<TPanel>(std::forward<Args>(args)...);
|
||||
if (m_context) {
|
||||
panel->SetContext(m_context);
|
||||
}
|
||||
|
||||
TPanel* panelPtr = panel.get();
|
||||
m_panels.push_back(std::move(panel));
|
||||
return *panelPtr;
|
||||
}
|
||||
|
||||
void AttachAll() {
|
||||
for (const auto& panel : m_panels) {
|
||||
panel->OnAttach();
|
||||
}
|
||||
}
|
||||
|
||||
void DetachAll() {
|
||||
for (auto it = m_panels.rbegin(); it != m_panels.rend(); ++it) {
|
||||
(*it)->OnDetach();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateAll(float dt) {
|
||||
for (const auto& panel : m_panels) {
|
||||
panel->OnUpdate(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void DispatchEvent(void* event) {
|
||||
for (const auto& panel : m_panels) {
|
||||
panel->OnEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderAll() {
|
||||
for (const auto& panel : m_panels) {
|
||||
panel->Render();
|
||||
}
|
||||
}
|
||||
|
||||
void Clear() {
|
||||
m_panels.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
IEditorContext* m_context = nullptr;
|
||||
std::vector<std::unique_ptr<Panel>> m_panels;
|
||||
};
|
||||
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,13 +1,10 @@
|
||||
#include "Actions/EditorActions.h"
|
||||
#include "Commands/ProjectCommands.h"
|
||||
#include "ProjectPanel.h"
|
||||
#include "Core/IEditorContext.h"
|
||||
#include "Core/IProjectManager.h"
|
||||
#include "Core/ISceneManager.h"
|
||||
#include "Core/IUndoManager.h"
|
||||
#include "Core/ISelectionManager.h"
|
||||
#include "Core/AssetItem.h"
|
||||
#include "UI/Core.h"
|
||||
#include "UI/PanelChrome.h"
|
||||
#include "Utils/SceneEditorUtils.h"
|
||||
#include "UI/UI.h"
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
@@ -16,31 +13,6 @@ namespace Editor {
|
||||
|
||||
const char* DRAG_DROP_TYPE = "ASSET_ITEM";
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr float kProjectToolbarHeight = 60.0f;
|
||||
|
||||
void DrawFolderIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, ImU32 fillColor, ImU32 lineColor) {
|
||||
const float width = max.x - min.x;
|
||||
const float height = max.y - min.y;
|
||||
const ImVec2 tabMax(min.x + width * 0.45f, min.y + height * 0.35f);
|
||||
drawList->AddRectFilled(ImVec2(min.x, min.y + height * 0.14f), tabMax, fillColor, 2.0f);
|
||||
drawList->AddRectFilled(ImVec2(min.x, min.y + height * 0.28f), max, fillColor, 2.0f);
|
||||
drawList->AddRect(ImVec2(min.x, min.y + height * 0.14f), tabMax, lineColor, 2.0f);
|
||||
drawList->AddRect(ImVec2(min.x, min.y + height * 0.28f), max, lineColor, 2.0f);
|
||||
}
|
||||
|
||||
void DrawFileIcon(ImDrawList* drawList, const ImVec2& min, const ImVec2& max, ImU32 fillColor, ImU32 lineColor) {
|
||||
const ImVec2 foldA(max.x - 8.0f, min.y);
|
||||
const ImVec2 foldB(max.x, min.y + 8.0f);
|
||||
drawList->AddRectFilled(min, max, fillColor, 2.0f);
|
||||
drawList->AddRect(min, max, lineColor, 2.0f);
|
||||
drawList->AddTriangleFilled(foldA, ImVec2(max.x, min.y), foldB, IM_COL32(235, 235, 235, 40));
|
||||
drawList->AddLine(foldA, foldB, lineColor);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ProjectPanel::ProjectPanel() : Panel("Project") {
|
||||
}
|
||||
|
||||
@@ -63,46 +35,24 @@ void ProjectPanel::Render() {
|
||||
|
||||
auto& manager = m_context->GetProjectManager();
|
||||
|
||||
UI::PanelToolbarScope toolbar("ProjectToolbar", kProjectToolbarHeight);
|
||||
UI::PanelToolbarScope toolbar("ProjectToolbar", UI::ProjectPanelToolbarHeight());
|
||||
if (toolbar.IsOpen()) {
|
||||
|
||||
bool canGoBack = manager.CanNavigateBack();
|
||||
ImGui::BeginDisabled(!canGoBack);
|
||||
if (UI::ToolbarButton("<", false, ImVec2(28.0f, 0.0f))) {
|
||||
if (canGoBack) {
|
||||
if (Actions::DrawToolbarAction(Actions::MakeNavigateBackAction(canGoBack), UI::ProjectBackButtonSize())) {
|
||||
manager.NavigateBack();
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
size_t pathDepth = manager.GetPathDepth();
|
||||
if (pathDepth == 0) {
|
||||
ImGui::TextUnformatted("Assets");
|
||||
} else {
|
||||
for (size_t i = 0; i < pathDepth; i++) {
|
||||
if (i > 0) {
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("/");
|
||||
ImGui::SameLine();
|
||||
}
|
||||
std::string name = manager.GetPathName(i);
|
||||
if (i < pathDepth - 1) {
|
||||
if (ImGui::Button(name.c_str())) {
|
||||
manager.NavigateToIndex(i);
|
||||
}
|
||||
} else {
|
||||
ImGui::Text("%s", name.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor(2);
|
||||
UI::DrawToolbarBreadcrumbs(
|
||||
"Assets",
|
||||
pathDepth,
|
||||
[&](size_t index) { return manager.GetPathName(index); },
|
||||
[&](size_t index) { manager.NavigateToIndex(index); });
|
||||
|
||||
ImGui::Dummy(ImVec2(0.0f, 2.0f));
|
||||
ImGui::SetNextItemWidth(-1.0f);
|
||||
ImGui::InputTextWithHint("##Search", "Search assets", m_searchBuffer, sizeof(m_searchBuffer));
|
||||
UI::DrawToolbarRowGap();
|
||||
UI::ToolbarSearchField("##Search", "Search assets", m_searchBuffer, sizeof(m_searchBuffer));
|
||||
}
|
||||
|
||||
UI::PanelContentScope content(
|
||||
@@ -110,15 +60,15 @@ void ProjectPanel::Render() {
|
||||
UI::AssetPanelContentPadding(),
|
||||
ImGuiWindowFlags_None,
|
||||
true,
|
||||
ImVec2(8.0f, 10.0f));
|
||||
UI::AssetGridSpacing());
|
||||
if (!content.IsOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
float buttonWidth = 104.0f;
|
||||
float padding = 8.0f;
|
||||
float panelWidth = ImGui::GetContentRegionAvail().x;
|
||||
int columns = (int)(panelWidth / (buttonWidth + padding));
|
||||
const float tileWidth = UI::AssetTileSize().x;
|
||||
const float spacing = UI::AssetGridSpacing().x;
|
||||
const float panelWidth = ImGui::GetContentRegionAvail().x;
|
||||
int columns = static_cast<int>((panelWidth + spacing) / (tileWidth + spacing));
|
||||
if (columns < 1) columns = 1;
|
||||
|
||||
auto& items = manager.GetCurrentItems();
|
||||
@@ -139,63 +89,60 @@ void ProjectPanel::Render() {
|
||||
displayedCount++;
|
||||
}
|
||||
|
||||
if (displayedCount == 0) {
|
||||
UI::DrawEmptyState(
|
||||
searchStr.empty() ? "No Assets" : "No Search Results",
|
||||
searchStr.empty() ? "Current folder is empty" : "No assets match the current search");
|
||||
}
|
||||
|
||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(0) && !ImGui::IsAnyItemHovered()) {
|
||||
manager.SetSelectedIndex(-1);
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopup("ItemContextMenu")) {
|
||||
if (UI::BeginPopup("ItemContextMenu")) {
|
||||
if (m_contextMenuIndex >= 0 && m_contextMenuIndex < (int)items.size()) {
|
||||
auto& item = items[m_contextMenuIndex];
|
||||
if (item->isFolder || item->type == "Scene") {
|
||||
if (ImGui::MenuItem("Open")) {
|
||||
if (item->isFolder) {
|
||||
manager.NavigateToFolder(item);
|
||||
} else if (SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||
if (m_context->GetSceneManager().LoadScene(item->fullPath)) {
|
||||
m_context->GetSelectionManager().ClearSelection();
|
||||
m_context->GetUndoManager().ClearHistory();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::Separator();
|
||||
}
|
||||
if (ImGui::MenuItem("Delete")) {
|
||||
manager.DeleteItem(m_contextMenuIndex);
|
||||
const bool canOpen = item->isFolder || item->type == "Scene";
|
||||
Actions::DrawMenuAction(Actions::MakeOpenAssetAction(canOpen), [&]() {
|
||||
Commands::OpenAsset(*m_context, item);
|
||||
});
|
||||
Actions::DrawMenuSeparator();
|
||||
Actions::DrawMenuAction(Actions::MakeDeleteAssetAction(), [&]() {
|
||||
Commands::DeleteAsset(manager, m_contextMenuIndex);
|
||||
m_contextMenuIndex = -1;
|
||||
});
|
||||
}
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
UI::EndPopup();
|
||||
}
|
||||
|
||||
if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(1) && !ImGui::IsAnyItemHovered()) {
|
||||
ImGui::OpenPopup("EmptyContextMenu");
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopup("EmptyContextMenu")) {
|
||||
if (ImGui::MenuItem("Create Folder")) {
|
||||
m_showCreateFolderPopup = true;
|
||||
strcpy_s(m_newFolderName, "NewFolder");
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
if (UI::BeginPopup("EmptyContextMenu")) {
|
||||
Actions::DrawMenuAction(Actions::MakeCreateFolderAction(), [&]() {
|
||||
m_createFolderDialog.RequestOpen("NewFolder");
|
||||
});
|
||||
UI::EndPopup();
|
||||
}
|
||||
|
||||
if (m_showCreateFolderPopup) {
|
||||
ImGui::OpenPopup("Create Folder");
|
||||
m_showCreateFolderPopup = false;
|
||||
}
|
||||
m_createFolderDialog.ConsumeOpenRequest("Create Folder");
|
||||
|
||||
if (ImGui::BeginPopupModal("Create Folder", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::InputText("Name", m_newFolderName, sizeof(m_newFolderName));
|
||||
if (UI::BeginModalPopup("Create Folder")) {
|
||||
ImGui::InputText("Name", m_createFolderDialog.Buffer(), m_createFolderDialog.BufferSize());
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("Create", ImVec2(80, 0))) {
|
||||
CreateNewFolder(m_newFolderName);
|
||||
const Actions::ActionBinding createAction = Actions::MakeConfirmCreateAction(!m_createFolderDialog.Empty());
|
||||
const Actions::ActionBinding cancelAction = Actions::MakeCancelAction();
|
||||
if (Actions::DrawButtonAction(createAction, UI::DialogActionButtonSize())) {
|
||||
if (Commands::CreateFolder(manager, m_createFolderDialog.Buffer())) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", ImVec2(80, 0))) {
|
||||
if (Actions::DrawButtonAction(cancelAction, UI::DialogActionButtonSize())) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
UI::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,67 +151,33 @@ void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
|
||||
bool isSelected = (manager.GetSelectedIndex() == index);
|
||||
|
||||
ImGui::PushID(index);
|
||||
const bool isDraggingThisItem = !m_draggingPath.empty() && item->fullPath == m_draggingPath;
|
||||
const UI::AssetIconKind iconKind = item->isFolder ? UI::AssetIconKind::Folder : UI::AssetIconKind::File;
|
||||
|
||||
ImVec2 buttonSize(104.0f, 82.0f);
|
||||
const UI::AssetTileResult tile = UI::DrawAssetTile(
|
||||
item->name.c_str(),
|
||||
isSelected,
|
||||
isDraggingThisItem,
|
||||
[&](ImDrawList* drawList, const ImVec2& iconMin, const ImVec2& iconMax) {
|
||||
UI::DrawAssetIcon(drawList, iconMin, iconMax, iconKind);
|
||||
});
|
||||
|
||||
ImGui::InvisibleButton("##AssetBtn", buttonSize);
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||
if (tile.clicked) {
|
||||
manager.SetSelectedIndex(index);
|
||||
}
|
||||
|
||||
bool doubleClicked = false;
|
||||
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(0)) {
|
||||
doubleClicked = true;
|
||||
}
|
||||
|
||||
bool openContextMenu = false;
|
||||
if (ImGui::IsItemClicked(1)) {
|
||||
if (tile.contextRequested) {
|
||||
manager.SetSelectedIndex(index);
|
||||
m_contextMenuIndex = index;
|
||||
openContextMenu = true;
|
||||
}
|
||||
|
||||
ImVec2 min = ImGui::GetItemRectMin();
|
||||
ImVec2 max = ImVec2(min.x + buttonSize.x, min.y + buttonSize.y);
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
|
||||
const bool hovered = ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem);
|
||||
if (hovered || isSelected) {
|
||||
drawList->AddRectFilled(min, max, isSelected ? IM_COL32(156, 156, 156, 30) : IM_COL32(255, 255, 255, 10), 2.0f);
|
||||
}
|
||||
if (isSelected) {
|
||||
drawList->AddRect(min, max, IM_COL32(188, 188, 188, 110), 2.0f);
|
||||
}
|
||||
|
||||
if (!m_draggingPath.empty() && item->fullPath == m_draggingPath) {
|
||||
drawList->AddRectFilled(min, max, IM_COL32(0, 0, 0, 60), 0.0f);
|
||||
}
|
||||
|
||||
ImU32 iconFillColor = item->isFolder ? IM_COL32(118, 118, 118, 255) : IM_COL32(104, 104, 104, 255);
|
||||
ImU32 iconLineColor = item->isFolder ? IM_COL32(184, 184, 184, 220) : IM_COL32(166, 166, 166, 220);
|
||||
|
||||
const ImVec2 iconMin(min.x + 14.0f, min.y + 12.0f);
|
||||
const ImVec2 iconMax(iconMin.x + 28.0f, iconMin.y + 22.0f);
|
||||
if (item->isFolder) {
|
||||
DrawFolderIcon(drawList, iconMin, iconMax, iconFillColor, iconLineColor);
|
||||
} else {
|
||||
DrawFileIcon(drawList, iconMin, iconMax, iconFillColor, iconLineColor);
|
||||
}
|
||||
|
||||
ImVec4 textColor = isSelected ? ImVec4(0.93f, 0.93f, 0.93f, 1.0f) : ImVec4(0.76f, 0.76f, 0.76f, 1.0f);
|
||||
ImVec2 textSize = ImGui::CalcTextSize(item->name.c_str());
|
||||
float textY = max.y - textSize.y - 10.0f;
|
||||
|
||||
ImGui::PushClipRect(ImVec2(min.x + 6.0f, min.y), ImVec2(max.x - 6.0f, max.y), true);
|
||||
drawList->AddText(ImVec2(min.x + 6.0f, textY), ImGui::GetColorU32(textColor), item->name.c_str());
|
||||
ImGui::PopClipRect();
|
||||
|
||||
if (item->isFolder) {
|
||||
if (ImGui::BeginDragDropTarget()) {
|
||||
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(DRAG_DROP_TYPE)) {
|
||||
const char* draggedPath = (const char*)payload->Data;
|
||||
std::string sourcePath(draggedPath);
|
||||
manager.MoveItem(sourcePath, item->fullPath);
|
||||
Commands::MoveAssetToFolder(manager, draggedPath, item);
|
||||
}
|
||||
ImGui::EndDragDropTarget();
|
||||
}
|
||||
@@ -275,28 +188,16 @@ void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
|
||||
ImGui::SetDragDropPayload(DRAG_DROP_TYPE, item->fullPath.c_str(), item->fullPath.length() + 1);
|
||||
|
||||
ImVec2 previewMin = ImGui::GetMousePos();
|
||||
ImVec2 previewMax = ImVec2(previewMin.x + 24.0f, previewMin.y + 20.0f);
|
||||
if (item->isFolder) {
|
||||
DrawFolderIcon(ImGui::GetForegroundDrawList(), previewMin, previewMax, iconFillColor, iconLineColor);
|
||||
} else {
|
||||
DrawFileIcon(ImGui::GetForegroundDrawList(), previewMin, previewMax, iconFillColor, iconLineColor);
|
||||
}
|
||||
const ImVec2 previewSize = UI::AssetDragPreviewSize();
|
||||
ImVec2 previewMax = ImVec2(previewMin.x + previewSize.x, previewMin.y + previewSize.y);
|
||||
UI::DrawAssetIcon(ImGui::GetForegroundDrawList(), previewMin, previewMax, iconKind);
|
||||
|
||||
ImGui::EndDragDropSource();
|
||||
}
|
||||
}
|
||||
|
||||
if (doubleClicked) {
|
||||
if (item->isFolder) {
|
||||
manager.NavigateToFolder(item);
|
||||
} else if (item->type == "Scene") {
|
||||
if (SceneEditorUtils::ConfirmSceneSwitch(*m_context)) {
|
||||
if (m_context->GetSceneManager().LoadScene(item->fullPath)) {
|
||||
m_context->GetSelectionManager().ClearSelection();
|
||||
m_context->GetUndoManager().ClearHistory();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tile.openRequested) {
|
||||
Commands::OpenAsset(*m_context, item);
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
@@ -306,14 +207,5 @@ void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectPanel::CreateNewFolder(const std::string& name) {
|
||||
auto& manager = m_context->GetProjectManager();
|
||||
manager.CreateFolder(name);
|
||||
}
|
||||
|
||||
bool ProjectPanel::HandleDrop(const AssetItemPtr& targetFolder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "Panel.h"
|
||||
#include "Core/AssetItem.h"
|
||||
#include "UI/PopupState.h"
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
@@ -14,12 +15,9 @@ public:
|
||||
|
||||
private:
|
||||
void RenderAssetItem(const AssetItemPtr& item, int index);
|
||||
void CreateNewFolder(const std::string& name);
|
||||
bool HandleDrop(const AssetItemPtr& targetFolder);
|
||||
|
||||
char m_searchBuffer[256] = "";
|
||||
bool m_showCreateFolderPopup = false;
|
||||
char m_newFolderName[256] = "NewFolder";
|
||||
UI::TextInputPopupState<256> m_createFolderDialog;
|
||||
int m_contextMenuIndex = -1;
|
||||
std::string m_draggingPath;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#include "SceneViewPanel.h"
|
||||
#include "UI/PanelChrome.h"
|
||||
#include "UI/UI.h"
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
|
||||
@@ -9,9 +9,6 @@ class SceneViewPanel : public Panel {
|
||||
public:
|
||||
SceneViewPanel();
|
||||
void Render() override;
|
||||
|
||||
private:
|
||||
void RenderGrid();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
BIN
editor/tab.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
editor/unity.png
Normal file
|
After Width: | Height: | Size: 786 KiB |