Unify editor divider and splitter chrome
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "SplitterChrome.h"
|
||||
#include "StyleTokens.h"
|
||||
|
||||
#include <imgui.h>
|
||||
@@ -9,8 +10,8 @@ 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_Text] = ImVec4(0.88f, 0.88f, 0.88f, 1.00f);
|
||||
colors[ImGuiCol_TextDisabled] = ImVec4(0.58f, 0.58f, 0.58f, 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);
|
||||
@@ -36,12 +37,7 @@ inline void ApplyBaseThemeColors(ImVec4* colors) {
|
||||
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);
|
||||
ApplySplitterThemeColors(colors);
|
||||
colors[ImGuiCol_Tab] = DockTabColor();
|
||||
colors[ImGuiCol_TabHovered] = DockTabHoveredColor();
|
||||
colors[ImGuiCol_TabSelected] = DockTabSelectedColor();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "DividerChrome.h"
|
||||
#include "StyleTokens.h"
|
||||
|
||||
#include <imgui.h>
|
||||
@@ -141,10 +142,7 @@ inline void EndDisabled(bool disabled = true) {
|
||||
}
|
||||
|
||||
inline void DrawCurrentWindowBottomBorder(ImU32 color = PanelDividerColor()) {
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
const ImVec2 min = ImGui::GetWindowPos();
|
||||
const ImVec2 max = ImVec2(min.x + ImGui::GetWindowSize().x, min.y + ImGui::GetWindowSize().y);
|
||||
drawList->AddLine(ImVec2(min.x, max.y - 1.0f), ImVec2(max.x, max.y - 1.0f), color);
|
||||
DrawCurrentWindowBottomDivider(color);
|
||||
}
|
||||
|
||||
inline bool ToolbarButton(const char* label, bool active = false, ImVec2 size = ImVec2(0.0f, 0.0f)) {
|
||||
|
||||
50
editor/src/UI/DividerChrome.h
Normal file
50
editor/src/UI/DividerChrome.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "StyleTokens.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace UI {
|
||||
|
||||
inline void DrawHorizontalDivider(
|
||||
ImDrawList* drawList,
|
||||
float minX,
|
||||
float maxX,
|
||||
float y,
|
||||
ImU32 color = PanelDividerColor(),
|
||||
float thickness = PanelDividerThickness()) {
|
||||
if (!drawList || maxX <= minX) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawList->AddLine(ImVec2(minX, y), ImVec2(maxX, y), color, thickness);
|
||||
}
|
||||
|
||||
inline void DrawVerticalDivider(
|
||||
ImDrawList* drawList,
|
||||
float x,
|
||||
float minY,
|
||||
float maxY,
|
||||
ImU32 color = PanelDividerColor(),
|
||||
float thickness = PanelDividerThickness()) {
|
||||
if (!drawList || maxY <= minY) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawList->AddLine(ImVec2(x, minY), ImVec2(x, maxY), color, thickness);
|
||||
}
|
||||
|
||||
inline void DrawCurrentWindowBottomDivider(
|
||||
ImU32 color = PanelDividerColor(),
|
||||
float thickness = PanelDividerThickness()) {
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
const ImVec2 min = ImGui::GetWindowPos();
|
||||
const ImVec2 max = ImVec2(min.x + ImGui::GetWindowSize().x, min.y + ImGui::GetWindowSize().y);
|
||||
DrawHorizontalDivider(drawList, min.x, max.x, max.y - 1.0f, color, thickness);
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "SplitterChrome.h"
|
||||
#include "StyleTokens.h"
|
||||
|
||||
#include <imgui.h>
|
||||
@@ -24,10 +25,16 @@ public:
|
||||
ImGui::PushStyleColor(ImGuiCol_TabDimmed, DockTabDimmedColor());
|
||||
ImGui::PushStyleColor(ImGuiCol_TabDimmedSelected, DockTabDimmedSelectedColor());
|
||||
ImGui::PushStyleColor(ImGuiCol_TabDimmedSelectedOverline, DockTabDimmedSelectedOverlineColor());
|
||||
ImGui::PushStyleColor(ImGuiCol_Separator, PanelSplitterIdleColor());
|
||||
ImGui::PushStyleColor(ImGuiCol_SeparatorHovered, PanelSplitterHoveredColor());
|
||||
ImGui::PushStyleColor(ImGuiCol_SeparatorActive, PanelSplitterActiveColor());
|
||||
ImGui::PushStyleColor(ImGuiCol_ResizeGrip, ImVec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ResizeGripHovered, PanelSplitterHoveredColor());
|
||||
ImGui::PushStyleColor(ImGuiCol_ResizeGripActive, PanelSplitterActiveColor());
|
||||
}
|
||||
|
||||
~DockHostStyleScope() {
|
||||
ImGui::PopStyleColor(7);
|
||||
ImGui::PopStyleColor(13);
|
||||
ImGui::PopStyleVar(5);
|
||||
}
|
||||
|
||||
|
||||
348
editor/src/UI/DockTabBarChrome.h
Normal file
348
editor/src/UI/DockTabBarChrome.h
Normal file
@@ -0,0 +1,348 @@
|
||||
#pragma once
|
||||
|
||||
#include "DividerChrome.h"
|
||||
#include "StyleTokens.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace UI {
|
||||
|
||||
inline float DockedTabStripHeight() {
|
||||
return 24.0f;
|
||||
}
|
||||
|
||||
inline float DockedTabHorizontalPadding() {
|
||||
return 12.0f;
|
||||
}
|
||||
|
||||
inline float DockedTabTextOffsetY() {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
inline float DockedSelectedTabBottomOverlap() {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
inline std::unordered_map<ImGuiID, std::vector<ImGuiID>>& DockTabOrderCache() {
|
||||
static std::unordered_map<ImGuiID, std::vector<ImGuiID>> cache;
|
||||
return cache;
|
||||
}
|
||||
|
||||
inline ImGuiWindow* FindDockWindowByTabId(ImGuiDockNode& node, ImGuiID tabId) {
|
||||
for (ImGuiWindow* window : node.Windows) {
|
||||
if (window && window->TabId == tabId) {
|
||||
return window;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
inline const char* GetDockWindowLabelEnd(const ImGuiWindow& window) {
|
||||
return ImGui::FindRenderedTextEnd(window.Name);
|
||||
}
|
||||
|
||||
inline int ResolveDockWindowDisplayOrder(const ImGuiWindow& window, int fallbackIndex) {
|
||||
return window.DockOrder >= 0 ? window.DockOrder : 100000 + fallbackIndex;
|
||||
}
|
||||
|
||||
inline void SyncDockTabOrderCache(ImGuiDockNode& node) {
|
||||
auto& cache = DockTabOrderCache();
|
||||
if (!node.IsLeafNode() || node.Windows.Size == 0) {
|
||||
cache.erase(node.ID);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& order = cache[node.ID];
|
||||
auto containsWindow = [&node](ImGuiID tabId) {
|
||||
return FindDockWindowByTabId(node, tabId) != nullptr;
|
||||
};
|
||||
|
||||
order.erase(
|
||||
std::remove_if(
|
||||
order.begin(),
|
||||
order.end(),
|
||||
[&containsWindow](ImGuiID tabId) {
|
||||
return !containsWindow(tabId);
|
||||
}),
|
||||
order.end());
|
||||
|
||||
if (order.empty()) {
|
||||
std::vector<std::pair<int, ImGuiID>> seededOrder;
|
||||
seededOrder.reserve(node.Windows.Size);
|
||||
for (int i = 0; i < node.Windows.Size; ++i) {
|
||||
ImGuiWindow* window = node.Windows[i];
|
||||
if (!window) {
|
||||
continue;
|
||||
}
|
||||
seededOrder.emplace_back(ResolveDockWindowDisplayOrder(*window, i), window->TabId);
|
||||
}
|
||||
std::stable_sort(
|
||||
seededOrder.begin(),
|
||||
seededOrder.end(),
|
||||
[](const std::pair<int, ImGuiID>& lhs, const std::pair<int, ImGuiID>& rhs) {
|
||||
return lhs.first < rhs.first;
|
||||
});
|
||||
for (const auto& entry : seededOrder) {
|
||||
order.push_back(entry.second);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, ImGuiID>> additions;
|
||||
additions.reserve(node.Windows.Size);
|
||||
for (int i = 0; i < node.Windows.Size; ++i) {
|
||||
ImGuiWindow* window = node.Windows[i];
|
||||
if (!window) {
|
||||
continue;
|
||||
}
|
||||
if (std::find(order.begin(), order.end(), window->TabId) == order.end()) {
|
||||
additions.emplace_back(ResolveDockWindowDisplayOrder(*window, i), window->TabId);
|
||||
}
|
||||
}
|
||||
|
||||
std::stable_sort(
|
||||
additions.begin(),
|
||||
additions.end(),
|
||||
[](const std::pair<int, ImGuiID>& lhs, const std::pair<int, ImGuiID>& rhs) {
|
||||
return lhs.first < rhs.first;
|
||||
});
|
||||
for (const auto& entry : additions) {
|
||||
order.push_back(entry.second);
|
||||
}
|
||||
}
|
||||
|
||||
inline std::vector<ImGuiWindow*> GetOrderedDockWindows(ImGuiDockNode& node) {
|
||||
SyncDockTabOrderCache(node);
|
||||
|
||||
std::vector<ImGuiWindow*> orderedWindows;
|
||||
orderedWindows.reserve(node.Windows.Size);
|
||||
|
||||
auto& order = DockTabOrderCache()[node.ID];
|
||||
for (ImGuiID tabId : order) {
|
||||
if (ImGuiWindow* window = FindDockWindowByTabId(node, tabId)) {
|
||||
orderedWindows.push_back(window);
|
||||
}
|
||||
}
|
||||
|
||||
for (ImGuiWindow* window : node.Windows) {
|
||||
if (!window) {
|
||||
continue;
|
||||
}
|
||||
if (std::find(orderedWindows.begin(), orderedWindows.end(), window) == orderedWindows.end()) {
|
||||
orderedWindows.push_back(window);
|
||||
}
|
||||
}
|
||||
|
||||
return orderedWindows;
|
||||
}
|
||||
|
||||
inline void ActivateDockWindow(ImGuiDockNode& node, ImGuiWindow& window) {
|
||||
int currentIndex = -1;
|
||||
for (int i = 0; i < node.Windows.Size; ++i) {
|
||||
if (node.Windows[i] == &window) {
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (currentIndex <= 0) {
|
||||
node.SelectedTabId = window.TabId;
|
||||
node.VisibleWindow = &window;
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiWindow* target = node.Windows[currentIndex];
|
||||
for (int i = currentIndex; i > 0; --i) {
|
||||
node.Windows[i] = node.Windows[i - 1];
|
||||
}
|
||||
node.Windows[0] = target;
|
||||
node.SelectedTabId = window.TabId;
|
||||
node.VisibleWindow = &window;
|
||||
}
|
||||
|
||||
inline bool IsDockWindowSelected(const ImGuiDockNode& node, const ImGuiWindow& window) {
|
||||
return node.SelectedTabId ? node.SelectedTabId == window.TabId : node.VisibleWindow == &window;
|
||||
}
|
||||
|
||||
inline void ConfigureDockTabBarChrome(ImGuiDockNode* node) {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->IsLeafNode()) {
|
||||
SyncDockTabOrderCache(*node);
|
||||
|
||||
if (node->SelectedTabId == 0 && node->Windows.Size > 0 && node->Windows[0]) {
|
||||
node->SelectedTabId = node->Windows[0]->TabId;
|
||||
node->VisibleWindow = node->Windows[0];
|
||||
}
|
||||
|
||||
if (ImGuiWindow* selectedWindow = FindDockWindowByTabId(*node, node->SelectedTabId)) {
|
||||
ActivateDockWindow(*node, *selectedWindow);
|
||||
}
|
||||
|
||||
node->SetLocalFlags(
|
||||
node->LocalFlags |
|
||||
ImGuiDockNodeFlags_NoTabBar |
|
||||
ImGuiDockNodeFlags_NoWindowMenuButton |
|
||||
ImGuiDockNodeFlags_NoCloseButton);
|
||||
}
|
||||
|
||||
ConfigureDockTabBarChrome(node->ChildNodes[0]);
|
||||
ConfigureDockTabBarChrome(node->ChildNodes[1]);
|
||||
}
|
||||
|
||||
inline void ConfigureDockTabBarChrome(ImGuiID dockspaceId) {
|
||||
ConfigureDockTabBarChrome(ImGui::DockBuilderGetNode(dockspaceId));
|
||||
}
|
||||
|
||||
inline ImU32 ResolveCustomDockTabColor(const ImGuiDockNode& node, const ImGuiWindow& window, bool hovered) {
|
||||
if (IsDockWindowSelected(node, window)) {
|
||||
return ImGui::GetColorU32(ImGuiCol_WindowBg);
|
||||
}
|
||||
|
||||
if (hovered) {
|
||||
return window.DockStyle.Colors[ImGuiWindowDockStyleCol_TabHovered];
|
||||
}
|
||||
|
||||
return window.DockStyle.Colors[
|
||||
node.IsFocused ? ImGuiWindowDockStyleCol_TabFocused : ImGuiWindowDockStyleCol_TabDimmed];
|
||||
}
|
||||
|
||||
inline ImU32 ResolveCustomDockOverlineColor(const ImGuiDockNode& node, const ImGuiWindow& window) {
|
||||
return window.DockStyle.Colors[
|
||||
node.IsFocused ? ImGuiWindowDockStyleCol_TabSelectedOverline : ImGuiWindowDockStyleCol_TabDimmedSelectedOverline];
|
||||
}
|
||||
|
||||
inline void DrawCustomDockTab(
|
||||
ImGuiDockNode& node,
|
||||
ImGuiWindow& targetWindow,
|
||||
const ImRect& tabRect,
|
||||
const char* idSuffix) {
|
||||
ImGui::SetCursorScreenPos(tabRect.Min);
|
||||
ImGui::InvisibleButton(idSuffix, tabRect.GetSize());
|
||||
|
||||
const bool hovered = ImGui::IsItemHovered();
|
||||
const bool clicked = ImGui::IsItemClicked(ImGuiMouseButton_Left);
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
ImRect fillRect = tabRect;
|
||||
if (IsDockWindowSelected(node, targetWindow)) {
|
||||
fillRect.Max.y += DockedSelectedTabBottomOverlap();
|
||||
}
|
||||
drawList->AddRectFilled(fillRect.Min, fillRect.Max, ResolveCustomDockTabColor(node, targetWindow, hovered));
|
||||
|
||||
if (IsDockWindowSelected(node, targetWindow)) {
|
||||
const float y = tabRect.Min.y + 0.5f;
|
||||
drawList->AddLine(
|
||||
ImVec2(tabRect.Min.x, y),
|
||||
ImVec2(tabRect.Max.x, y),
|
||||
ResolveCustomDockOverlineColor(node, targetWindow),
|
||||
1.0f);
|
||||
}
|
||||
|
||||
const char* labelEnd = GetDockWindowLabelEnd(targetWindow);
|
||||
const ImVec2 textSize = ImGui::CalcTextSize(targetWindow.Name, labelEnd, true);
|
||||
ImRect textRect = tabRect;
|
||||
textRect.Min.x += DockedTabHorizontalPadding();
|
||||
textRect.Max.x -= DockedTabHorizontalPadding();
|
||||
textRect.Min.y += DockedTabTextOffsetY();
|
||||
textRect.Max.y += DockedTabTextOffsetY();
|
||||
ImGui::RenderTextClipped(
|
||||
textRect.Min,
|
||||
textRect.Max,
|
||||
targetWindow.Name,
|
||||
labelEnd,
|
||||
&textSize,
|
||||
ImVec2(0.5f, 0.5f),
|
||||
&tabRect);
|
||||
|
||||
if (clicked) {
|
||||
ActivateDockWindow(node, targetWindow);
|
||||
ImGui::MarkIniSettingsDirty();
|
||||
}
|
||||
}
|
||||
|
||||
inline void DrawDockedWindowTabStrip() {
|
||||
ImGuiWindow* window = ImGui::GetCurrentWindow();
|
||||
if (!window || !window->DockNode || !window->DockNodeIsVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiDockNode& node = *window->DockNode;
|
||||
if (!node.IsLeafNode() || node.Windows.Size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float stripHeight = DockedTabStripHeight();
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ToolbarBackgroundColor());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0.0f, 0.0f));
|
||||
const bool open = ImGui::BeginChild(
|
||||
"##DockTabStrip",
|
||||
ImVec2(0.0f, stripHeight),
|
||||
false,
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
||||
ImGui::PopStyleVar(2);
|
||||
ImGui::PopStyleColor();
|
||||
if (!open) {
|
||||
ImGui::EndChild();
|
||||
return;
|
||||
}
|
||||
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
const ImVec2 stripMin = ImGui::GetWindowPos();
|
||||
const ImVec2 stripSize = ImGui::GetWindowSize();
|
||||
const ImVec2 stripMax(stripMin.x + stripSize.x, stripMin.y + stripSize.y);
|
||||
drawList->AddRectFilled(stripMin, stripMax, ImGui::GetColorU32(ToolbarBackgroundColor()));
|
||||
|
||||
float cursorX = stripMin.x;
|
||||
const std::vector<ImGuiWindow*> orderedWindows = GetOrderedDockWindows(node);
|
||||
float selectedTabMinX = stripMin.x;
|
||||
float selectedTabMaxX = stripMin.x;
|
||||
bool hasSelectedTab = false;
|
||||
for (ImGuiWindow* dockedWindow : orderedWindows) {
|
||||
if (!dockedWindow) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const char* labelEnd = GetDockWindowLabelEnd(*dockedWindow);
|
||||
const ImVec2 textSize = ImGui::CalcTextSize(dockedWindow->Name, labelEnd, true);
|
||||
const float tabWidth = textSize.x + DockedTabHorizontalPadding() * 2.0f;
|
||||
const ImRect tabRect(cursorX, stripMin.y, cursorX + tabWidth, stripMin.y + stripHeight);
|
||||
const ImGuiID pushId = dockedWindow->TabId ? dockedWindow->TabId : ImGui::GetID(dockedWindow->Name);
|
||||
ImGui::PushID(pushId);
|
||||
DrawCustomDockTab(node, *dockedWindow, tabRect, "##DockTab");
|
||||
ImGui::PopID();
|
||||
|
||||
if (IsDockWindowSelected(node, *dockedWindow)) {
|
||||
selectedTabMinX = tabRect.Min.x;
|
||||
selectedTabMaxX = tabRect.Max.x;
|
||||
hasSelectedTab = true;
|
||||
}
|
||||
|
||||
cursorX += tabWidth;
|
||||
}
|
||||
|
||||
const float dividerY = stripMax.y - 0.5f;
|
||||
if (hasSelectedTab) {
|
||||
if (selectedTabMinX > stripMin.x) {
|
||||
DrawHorizontalDivider(drawList, stripMin.x, selectedTabMinX, dividerY);
|
||||
}
|
||||
if (selectedTabMaxX < stripMax.x) {
|
||||
DrawHorizontalDivider(drawList, selectedTabMaxX, stripMax.x, dividerY);
|
||||
}
|
||||
} else {
|
||||
DrawHorizontalDivider(drawList, stripMin.x, stripMax.x, dividerY);
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
69
editor/src/UI/SplitterChrome.h
Normal file
69
editor/src/UI/SplitterChrome.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include "StyleTokens.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Editor {
|
||||
namespace UI {
|
||||
|
||||
enum class SplitterAxis {
|
||||
Vertical,
|
||||
Horizontal
|
||||
};
|
||||
|
||||
struct SplitterResult {
|
||||
bool hovered = false;
|
||||
bool active = false;
|
||||
float delta = 0.0f;
|
||||
};
|
||||
|
||||
inline void ApplySplitterThemeColors(ImVec4* colors) {
|
||||
colors[ImGuiCol_Separator] = PanelSplitterIdleColor();
|
||||
colors[ImGuiCol_SeparatorHovered] = PanelSplitterHoveredColor();
|
||||
colors[ImGuiCol_SeparatorActive] = PanelSplitterActiveColor();
|
||||
colors[ImGuiCol_ResizeGrip] = ImVec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
colors[ImGuiCol_ResizeGripHovered] = PanelSplitterHoveredColor();
|
||||
colors[ImGuiCol_ResizeGripActive] = PanelSplitterActiveColor();
|
||||
}
|
||||
|
||||
inline SplitterResult DrawSplitter(
|
||||
const char* id,
|
||||
SplitterAxis axis,
|
||||
float length,
|
||||
float hitThickness = PanelSplitterHitThickness()) {
|
||||
const bool vertical = axis == SplitterAxis::Vertical;
|
||||
const ImVec2 min = ImGui::GetCursorScreenPos();
|
||||
const ImVec2 size = vertical ? ImVec2(hitThickness, length) : ImVec2(length, hitThickness);
|
||||
ImGui::InvisibleButton(id, size);
|
||||
|
||||
const bool hovered = ImGui::IsItemHovered();
|
||||
const bool active = ImGui::IsItemActive();
|
||||
const float delta = active
|
||||
? (vertical ? ImGui::GetIO().MouseDelta.x : ImGui::GetIO().MouseDelta.y)
|
||||
: 0.0f;
|
||||
|
||||
if (hovered || active) {
|
||||
ImGui::SetMouseCursor(vertical ? ImGuiMouseCursor_ResizeEW : ImGuiMouseCursor_ResizeNS);
|
||||
}
|
||||
|
||||
const ImVec4 color = active
|
||||
? PanelSplitterActiveColor()
|
||||
: (hovered ? PanelSplitterHoveredColor() : PanelSplitterIdleColor());
|
||||
const float thickness = PanelSplitterVisibleThickness();
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
if (vertical) {
|
||||
const float centerX = min.x + (size.x * 0.5f);
|
||||
drawList->AddLine(ImVec2(centerX, min.y), ImVec2(centerX, min.y + size.y), ImGui::GetColorU32(color), thickness);
|
||||
} else {
|
||||
const float centerY = min.y + (size.y * 0.5f);
|
||||
drawList->AddLine(ImVec2(min.x, centerY), ImVec2(min.x + size.x, centerY), ImGui::GetColorU32(color), thickness);
|
||||
}
|
||||
|
||||
return SplitterResult{ hovered, active, delta };
|
||||
}
|
||||
|
||||
} // namespace UI
|
||||
} // namespace Editor
|
||||
} // namespace XCEngine
|
||||
@@ -58,6 +58,10 @@ inline ImVec2 PanelWindowPadding() {
|
||||
return ImVec2(0.0f, 0.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 HierarchyInspectorPanelBackgroundColor() {
|
||||
return ImVec4(0.235f, 0.235f, 0.235f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 ToolbarPadding() {
|
||||
return ImVec2(8.0f, 6.0f);
|
||||
}
|
||||
@@ -71,7 +75,7 @@ inline float StandardPanelToolbarHeight() {
|
||||
}
|
||||
|
||||
inline float ProjectPanelToolbarHeight() {
|
||||
return 60.0f;
|
||||
return 34.0f;
|
||||
}
|
||||
|
||||
inline float ToolbarSearchTrailingSpacing() {
|
||||
@@ -114,6 +118,66 @@ inline ImVec2 AssetPanelContentPadding() {
|
||||
return ImVec2(10.0f, 10.0f);
|
||||
}
|
||||
|
||||
inline float ProjectBrowserHeaderHeight() {
|
||||
return 28.0f;
|
||||
}
|
||||
|
||||
inline float ProjectNavigationDefaultWidth() {
|
||||
return 248.0f;
|
||||
}
|
||||
|
||||
inline float ProjectNavigationMinWidth() {
|
||||
return 180.0f;
|
||||
}
|
||||
|
||||
inline float ProjectBrowserMinWidth() {
|
||||
return 260.0f;
|
||||
}
|
||||
|
||||
inline float PanelSplitterHitThickness() {
|
||||
return 4.0f;
|
||||
}
|
||||
|
||||
inline float PanelSplitterVisibleThickness() {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
inline ImVec4 PanelSplitterIdleColor() {
|
||||
return ImVec4(0.14f, 0.14f, 0.14f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 PanelSplitterHoveredColor() {
|
||||
return ImVec4(0.30f, 0.30f, 0.30f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 PanelSplitterActiveColor() {
|
||||
return ImVec4(0.34f, 0.34f, 0.34f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 ProjectNavigationPanePadding() {
|
||||
return ImVec2(8.0f, 6.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 ProjectBrowserPanePadding() {
|
||||
return ImVec2(10.0f, 8.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 ProjectTreeNodeFramePadding() {
|
||||
return ImVec2(4.0f, 3.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 ProjectNavigationPaneBackgroundColor() {
|
||||
return ImVec4(0.20f, 0.20f, 0.20f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 ProjectBrowserHeaderBackgroundColor() {
|
||||
return ImVec4(0.18f, 0.18f, 0.18f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 ProjectBrowserPaneBackgroundColor() {
|
||||
return ImVec4(0.22f, 0.22f, 0.22f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 ToolbarBackgroundColor() {
|
||||
return ImVec4(0.19f, 0.19f, 0.19f, 1.0f);
|
||||
}
|
||||
@@ -127,7 +191,11 @@ inline float SearchFieldFrameRounding() {
|
||||
}
|
||||
|
||||
inline ImU32 PanelDividerColor() {
|
||||
return IM_COL32(36, 36, 36, 255);
|
||||
return ImGui::GetColorU32(PanelSplitterIdleColor());
|
||||
}
|
||||
|
||||
inline float PanelDividerThickness() {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
inline ImVec4 EmptyStateSubtitleColor() {
|
||||
@@ -186,6 +254,14 @@ inline float AssetTileRounding() {
|
||||
return 2.0f;
|
||||
}
|
||||
|
||||
inline ImVec4 AssetTileIdleFillColor() {
|
||||
return ImVec4(1.0f, 1.0f, 1.0f, 0.02f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetTileIdleBorderColor() {
|
||||
return ImVec4(1.0f, 1.0f, 1.0f, 0.05f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetTileHoverFillColor() {
|
||||
return ImVec4(1.0f, 1.0f, 1.0f, 0.04f);
|
||||
}
|
||||
@@ -203,11 +279,11 @@ inline ImVec4 AssetTileDraggedOverlayColor() {
|
||||
}
|
||||
|
||||
inline ImVec2 AssetTileIconOffset() {
|
||||
return ImVec2(14.0f, 12.0f);
|
||||
return ImVec2(12.0f, 10.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 AssetTileIconSize() {
|
||||
return ImVec2(28.0f, 22.0f);
|
||||
return ImVec2(32.0f, 24.0f);
|
||||
}
|
||||
|
||||
inline ImVec2 AssetTileTextPadding() {
|
||||
@@ -219,19 +295,19 @@ inline ImVec4 AssetTileTextColor(bool selected = false) {
|
||||
}
|
||||
|
||||
inline ImVec4 AssetFolderIconFillColor() {
|
||||
return ImVec4(0.46f, 0.46f, 0.46f, 1.0f);
|
||||
return ImVec4(0.50f, 0.50f, 0.50f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetFolderIconLineColor() {
|
||||
return ImVec4(0.72f, 0.72f, 0.72f, 0.86f);
|
||||
return ImVec4(0.80f, 0.80f, 0.80f, 0.90f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetFileIconFillColor() {
|
||||
return ImVec4(0.41f, 0.41f, 0.41f, 1.0f);
|
||||
return ImVec4(0.46f, 0.46f, 0.46f, 1.0f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetFileIconLineColor() {
|
||||
return ImVec4(0.65f, 0.65f, 0.65f, 0.86f);
|
||||
return ImVec4(0.74f, 0.74f, 0.74f, 0.90f);
|
||||
}
|
||||
|
||||
inline ImVec4 AssetFileFoldColor() {
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
#include "ConsoleLogFormatter.h"
|
||||
#include "Core.h"
|
||||
#include "DockHostStyle.h"
|
||||
#include "DockTabBarChrome.h"
|
||||
#include "DividerChrome.h"
|
||||
#include "PanelChrome.h"
|
||||
#include "PopupState.h"
|
||||
#include "PropertyGrid.h"
|
||||
#include "SplitterChrome.h"
|
||||
#include "ScalarControls.h"
|
||||
#include "SceneStatusWidget.h"
|
||||
#include "StyleTokens.h"
|
||||
|
||||
@@ -232,6 +232,9 @@ inline AssetTileResult DrawAssetTile(
|
||||
const ImVec2 max = ImVec2(min.x + tileSize.x, min.y + tileSize.y);
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
|
||||
drawList->AddRectFilled(min, max, ImGui::GetColorU32(AssetTileIdleFillColor()), AssetTileRounding());
|
||||
drawList->AddRect(min, max, ImGui::GetColorU32(AssetTileIdleBorderColor()), AssetTileRounding());
|
||||
|
||||
if (hovered || selected) {
|
||||
drawList->AddRectFilled(min, max, ImGui::GetColorU32(selected ? AssetTileSelectedFillColor() : AssetTileHoverFillColor()), AssetTileRounding());
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include "Core/IProjectManager.h"
|
||||
#include "Core/AssetItem.h"
|
||||
#include "UI/UI.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <imgui.h>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -25,32 +28,160 @@ void ProjectPanel::Render() {
|
||||
}
|
||||
|
||||
Actions::ObserveFocusedActionRoute(*m_context, EditorActionRoute::Project);
|
||||
|
||||
|
||||
auto& manager = m_context->GetProjectManager();
|
||||
RenderToolbar();
|
||||
|
||||
UI::PanelToolbarScope toolbar("ProjectToolbar", UI::ProjectPanelToolbarHeight());
|
||||
if (toolbar.IsOpen()) {
|
||||
Actions::DrawProjectNavigateBackAction(manager);
|
||||
ImGui::SameLine();
|
||||
|
||||
size_t pathDepth = manager.GetPathDepth();
|
||||
UI::DrawToolbarBreadcrumbs(
|
||||
"Assets",
|
||||
pathDepth,
|
||||
[&](size_t index) { return manager.GetPathName(index); },
|
||||
[&](size_t index) { manager.NavigateToIndex(index); });
|
||||
|
||||
UI::DrawToolbarRowGap();
|
||||
UI::ToolbarSearchField("##Search", "Search assets", m_searchBuffer, sizeof(m_searchBuffer));
|
||||
UI::PanelContentScope content("ProjectContent", ImVec2(0.0f, 0.0f));
|
||||
if (!content.IsOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
UI::PanelContentScope content(
|
||||
"ProjectContent",
|
||||
UI::AssetPanelContentPadding(),
|
||||
ImGuiWindowFlags_None,
|
||||
true,
|
||||
UI::AssetGridSpacing());
|
||||
if (!content.IsOpen()) {
|
||||
const float totalHeight = ImGui::GetContentRegionAvail().y;
|
||||
const float splitterWidth = UI::PanelSplitterHitThickness();
|
||||
const float availableWidth = ImGui::GetContentRegionAvail().x;
|
||||
const float clampedNavigationWidth = std::clamp(
|
||||
m_navigationWidth,
|
||||
UI::ProjectNavigationMinWidth(),
|
||||
std::max(UI::ProjectNavigationMinWidth(), availableWidth - UI::ProjectBrowserMinWidth() - splitterWidth));
|
||||
m_navigationWidth = clampedNavigationWidth;
|
||||
|
||||
RenderFolderTreePane(manager);
|
||||
ImGui::SameLine(0.0f, 0.0f);
|
||||
const UI::SplitterResult splitter = UI::DrawSplitter("##ProjectPaneSplitter", UI::SplitterAxis::Vertical, totalHeight);
|
||||
if (splitter.active) {
|
||||
m_navigationWidth += splitter.delta;
|
||||
}
|
||||
ImGui::SameLine(0.0f, 0.0f);
|
||||
RenderBrowserPane(manager);
|
||||
|
||||
Actions::DrawProjectCreateFolderDialog(*m_context, m_createFolderDialog);
|
||||
}
|
||||
|
||||
void ProjectPanel::RenderToolbar() {
|
||||
UI::PanelToolbarScope toolbar("ProjectToolbar", UI::ProjectPanelToolbarHeight());
|
||||
if (!toolbar.IsOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::BeginTable("##ProjectToolbarLayout", 2, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_SizingStretchProp)) {
|
||||
ImGui::TableSetupColumn("##Spacer", ImGuiTableColumnFlags_WidthStretch);
|
||||
ImGui::TableSetupColumn("##Search", ImGuiTableColumnFlags_WidthFixed, 220.0f);
|
||||
|
||||
ImGui::TableNextRow();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
UI::ToolbarSearchField("##Search", "Search assets", m_searchBuffer, sizeof(m_searchBuffer));
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectPanel::RenderFolderTreePane(IProjectManager& manager) {
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ProjectNavigationPaneBackgroundColor());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, UI::ProjectNavigationPanePadding());
|
||||
const bool open = ImGui::BeginChild("ProjectFolderTree", ImVec2(m_navigationWidth, 0.0f), false);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
if (!open) {
|
||||
ImGui::EndChild();
|
||||
return;
|
||||
}
|
||||
|
||||
const AssetItemPtr rootFolder = manager.GetRootFolder();
|
||||
const AssetItemPtr currentFolder = manager.GetCurrentFolder();
|
||||
const std::string currentFolderPath = currentFolder ? currentFolder->fullPath : std::string();
|
||||
if (rootFolder) {
|
||||
RenderFolderTreeNode(manager, rootFolder, currentFolderPath);
|
||||
} else {
|
||||
UI::DrawEmptyState("No Assets Folder");
|
||||
}
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void ProjectPanel::RenderFolderTreeNode(
|
||||
IProjectManager& manager,
|
||||
const AssetItemPtr& folder,
|
||||
const std::string& currentFolderPath) {
|
||||
if (!folder || !folder->isFolder) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool hasChildFolders = false;
|
||||
for (const auto& child : folder->children) {
|
||||
if (child && child->isFolder) {
|
||||
hasChildFolders = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiTreeNodeFlags flags =
|
||||
ImGuiTreeNodeFlags_OpenOnArrow |
|
||||
ImGuiTreeNodeFlags_OpenOnDoubleClick |
|
||||
ImGuiTreeNodeFlags_SpanAvailWidth |
|
||||
ImGuiTreeNodeFlags_FramePadding;
|
||||
if (!hasChildFolders) {
|
||||
flags |= ImGuiTreeNodeFlags_Leaf;
|
||||
}
|
||||
if (folder->fullPath == currentFolderPath) {
|
||||
flags |= ImGuiTreeNodeFlags_Selected;
|
||||
}
|
||||
if (IsCurrentTreeBranch(currentFolderPath, folder->fullPath)) {
|
||||
flags |= ImGuiTreeNodeFlags_DefaultOpen;
|
||||
}
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, UI::ProjectTreeNodeFramePadding());
|
||||
const bool open = ImGui::TreeNodeEx((void*)folder.get(), flags, "%s", folder->name.c_str());
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
if (ImGui::IsItemClicked()) {
|
||||
manager.NavigateToFolder(folder);
|
||||
}
|
||||
|
||||
if (open) {
|
||||
for (const auto& child : folder->children) {
|
||||
if (!child || !child->isFolder) {
|
||||
continue;
|
||||
}
|
||||
RenderFolderTreeNode(manager, child, currentFolderPath);
|
||||
}
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectPanel::RenderBrowserPane(IProjectManager& manager) {
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ProjectBrowserPaneBackgroundColor());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||
const bool open = ImGui::BeginChild("ProjectBrowser", ImVec2(0.0f, 0.0f), false);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
if (!open) {
|
||||
ImGui::EndChild();
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<AssetItemPtr> visibleItems;
|
||||
const auto& items = manager.GetCurrentItems();
|
||||
const std::string search = m_searchBuffer;
|
||||
visibleItems.reserve(items.size());
|
||||
for (const auto& item : items) {
|
||||
if (MatchesSearch(item, search)) {
|
||||
visibleItems.push_back(item);
|
||||
}
|
||||
}
|
||||
|
||||
RenderBrowserHeader(manager);
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, UI::ProjectBrowserPanePadding());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, UI::AssetGridSpacing());
|
||||
const bool bodyOpen = ImGui::BeginChild("ProjectBrowserBody", ImVec2(0.0f, 0.0f), false);
|
||||
ImGui::PopStyleVar(2);
|
||||
if (!bodyOpen) {
|
||||
ImGui::EndChild();
|
||||
ImGui::EndChild();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -58,45 +189,106 @@ void ProjectPanel::Render() {
|
||||
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();
|
||||
std::string searchStr = m_searchBuffer;
|
||||
int displayedCount = 0;
|
||||
|
||||
for (int i = 0; i < (int)items.size(); i++) {
|
||||
if (!searchStr.empty()) {
|
||||
if (items[i]->name.find(searchStr) == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (displayedCount > 0 && displayedCount % columns != 0) {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
RenderAssetItem(items[i], i);
|
||||
displayedCount++;
|
||||
if (columns < 1) {
|
||||
columns = 1;
|
||||
}
|
||||
|
||||
if (displayedCount == 0) {
|
||||
AssetItemPtr pendingSelection;
|
||||
AssetItemPtr pendingContextTarget;
|
||||
AssetItemPtr pendingOpenTarget;
|
||||
AssetItemPtr pendingMoveTarget;
|
||||
std::string pendingMoveSourcePath;
|
||||
|
||||
const std::string selectedItemPath = manager.GetSelectedItemPath();
|
||||
for (int visibleIndex = 0; visibleIndex < static_cast<int>(visibleItems.size()); ++visibleIndex) {
|
||||
if (visibleIndex > 0 && visibleIndex % columns != 0) {
|
||||
ImGui::SameLine();
|
||||
}
|
||||
|
||||
const AssetItemPtr& item = visibleItems[visibleIndex];
|
||||
const AssetItemInteraction interaction = RenderAssetItem(item, selectedItemPath == item->fullPath);
|
||||
if (interaction.clicked) {
|
||||
pendingSelection = item;
|
||||
}
|
||||
if (interaction.contextRequested) {
|
||||
pendingContextTarget = item;
|
||||
}
|
||||
if (!interaction.droppedSourcePath.empty()) {
|
||||
pendingMoveSourcePath = interaction.droppedSourcePath;
|
||||
pendingMoveTarget = item;
|
||||
break;
|
||||
}
|
||||
if (interaction.openRequested) {
|
||||
pendingOpenTarget = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (visibleItems.empty()) {
|
||||
UI::DrawEmptyState(
|
||||
searchStr.empty() ? "No Assets" : "No Search Results",
|
||||
searchStr.empty() ? "Current folder is empty" : "No assets match the current search");
|
||||
search.empty() ? "No Assets" : "No Search Results",
|
||||
search.empty() ? "Current folder is empty" : "No assets match the current search");
|
||||
}
|
||||
|
||||
Actions::HandleProjectBackgroundPrimaryClick(manager);
|
||||
if (pendingSelection) {
|
||||
manager.SetSelectedItem(pendingSelection);
|
||||
}
|
||||
if (pendingContextTarget) {
|
||||
Actions::HandleProjectItemContextRequest(manager, pendingContextTarget, m_itemContextMenu);
|
||||
}
|
||||
if (!pendingMoveSourcePath.empty() && pendingMoveTarget) {
|
||||
Commands::MoveAssetToFolder(manager, pendingMoveSourcePath, pendingMoveTarget);
|
||||
}
|
||||
if (pendingOpenTarget) {
|
||||
Actions::OpenProjectAsset(*m_context, pendingOpenTarget);
|
||||
}
|
||||
Actions::DrawProjectItemContextPopup(*m_context, m_itemContextMenu);
|
||||
Actions::RequestProjectEmptyContextPopup(m_emptyContextMenu);
|
||||
Actions::DrawProjectEmptyContextPopup(m_emptyContextMenu, m_createFolderDialog);
|
||||
|
||||
Actions::DrawProjectCreateFolderDialog(*m_context, m_createFolderDialog);
|
||||
ImGui::EndChild();
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
|
||||
auto& manager = m_context->GetProjectManager();
|
||||
bool isSelected = (manager.GetSelectedIndex() == index);
|
||||
|
||||
ImGui::PushID(index);
|
||||
void ProjectPanel::RenderBrowserHeader(IProjectManager& manager) {
|
||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, UI::ProjectBrowserHeaderBackgroundColor());
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(10.0f, 5.0f));
|
||||
const bool open = ImGui::BeginChild(
|
||||
"ProjectBrowserHeader",
|
||||
ImVec2(0.0f, UI::ProjectBrowserHeaderHeight()),
|
||||
false,
|
||||
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopStyleColor();
|
||||
if (!open) {
|
||||
ImGui::EndChild();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::BeginTable("##ProjectBrowserHeaderLayout", 1, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_NoPadOuterX)) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
UI::DrawToolbarBreadcrumbs(
|
||||
"Assets",
|
||||
manager.GetPathDepth(),
|
||||
[&](size_t index) { return manager.GetPathName(index); },
|
||||
[&](size_t index) { manager.NavigateToIndex(index); });
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImDrawList* drawList = ImGui::GetWindowDrawList();
|
||||
const ImVec2 min = ImGui::GetWindowPos();
|
||||
const ImVec2 max(min.x + ImGui::GetWindowSize().x, min.y + ImGui::GetWindowSize().y);
|
||||
UI::DrawHorizontalDivider(drawList, min.x, max.x, max.y - 0.5f);
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
ProjectPanel::AssetItemInteraction ProjectPanel::RenderAssetItem(const AssetItemPtr& item, bool isSelected) {
|
||||
AssetItemInteraction interaction;
|
||||
|
||||
ImGui::PushID(item ? item->fullPath.c_str() : "ProjectItem");
|
||||
const bool isDraggingThisItem = Actions::IsProjectAssetBeingDragged(item);
|
||||
const UI::AssetIconKind iconKind = item->isFolder ? UI::AssetIconKind::Folder : UI::AssetIconKind::File;
|
||||
|
||||
@@ -106,24 +298,57 @@ void ProjectPanel::RenderAssetItem(const AssetItemPtr& item, int index) {
|
||||
isDraggingThisItem,
|
||||
[&](ImDrawList* drawList, const ImVec2& iconMin, const ImVec2& iconMax) {
|
||||
UI::DrawAssetIcon(drawList, iconMin, iconMax, iconKind);
|
||||
});
|
||||
});
|
||||
|
||||
if (tile.clicked) {
|
||||
Actions::HandleProjectItemSelection(manager, index);
|
||||
interaction.clicked = true;
|
||||
}
|
||||
|
||||
if (tile.contextRequested) {
|
||||
Actions::HandleProjectItemContextRequest(manager, index, item, m_itemContextMenu);
|
||||
interaction.contextRequested = true;
|
||||
}
|
||||
|
||||
Actions::AcceptProjectAssetDrop(manager, item);
|
||||
interaction.droppedSourcePath = Actions::AcceptProjectAssetDropPayload(item);
|
||||
Actions::BeginProjectAssetDrag(item, iconKind);
|
||||
|
||||
if (tile.openRequested) {
|
||||
Actions::OpenProjectAsset(*m_context, item);
|
||||
interaction.openRequested = true;
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
return interaction;
|
||||
}
|
||||
|
||||
bool ProjectPanel::MatchesSearch(const AssetItemPtr& item, const std::string& search) {
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
if (search.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto toLower = [](unsigned char c) {
|
||||
return static_cast<char>(std::tolower(c));
|
||||
};
|
||||
|
||||
std::string itemName = item->name;
|
||||
std::string searchText = search;
|
||||
std::transform(itemName.begin(), itemName.end(), itemName.begin(), toLower);
|
||||
std::transform(searchText.begin(), searchText.end(), searchText.begin(), toLower);
|
||||
return itemName.find(searchText) != std::string::npos;
|
||||
}
|
||||
|
||||
bool ProjectPanel::IsCurrentTreeBranch(const std::string& currentFolderPath, const std::string& folderPath) {
|
||||
if (currentFolderPath.empty() || folderPath.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (currentFolderPath == folderPath) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const std::string withForwardSlash = folderPath + "/";
|
||||
const std::string withBackwardSlash = folderPath + "\\";
|
||||
return currentFolderPath.rfind(withForwardSlash, 0) == 0 || currentFolderPath.rfind(withBackwardSlash, 0) == 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -14,9 +14,24 @@ public:
|
||||
void Initialize(const std::string& projectPath);
|
||||
|
||||
private:
|
||||
void RenderAssetItem(const AssetItemPtr& item, int index);
|
||||
struct AssetItemInteraction {
|
||||
bool clicked = false;
|
||||
bool contextRequested = false;
|
||||
bool openRequested = false;
|
||||
std::string droppedSourcePath;
|
||||
};
|
||||
|
||||
void RenderToolbar();
|
||||
void RenderFolderTreePane(IProjectManager& manager);
|
||||
void RenderFolderTreeNode(IProjectManager& manager, const AssetItemPtr& folder, const std::string& currentFolderPath);
|
||||
void RenderBrowserPane(IProjectManager& manager);
|
||||
void RenderBrowserHeader(IProjectManager& manager);
|
||||
AssetItemInteraction RenderAssetItem(const AssetItemPtr& item, bool isSelected);
|
||||
static bool MatchesSearch(const AssetItemPtr& item, const std::string& search);
|
||||
static bool IsCurrentTreeBranch(const std::string& currentFolderPath, const std::string& folderPath);
|
||||
|
||||
char m_searchBuffer[256] = "";
|
||||
float m_navigationWidth = UI::ProjectNavigationDefaultWidth();
|
||||
UI::TextInputPopupState<256> m_createFolderDialog;
|
||||
UI::DeferredPopupState m_emptyContextMenu;
|
||||
UI::TargetedPopupState<AssetItemPtr> m_itemContextMenu;
|
||||
|
||||
Reference in New Issue
Block a user