Init Repo

This commit is contained in:
2026-06-03 09:00:11 +08:00
commit 9be48d8b9e
155 changed files with 14827 additions and 0 deletions
+601
View File
@@ -0,0 +1,601 @@
#include "ViewportPanel.h"
#include "../utils/Logger.h"
#include <imgui.h>
#include <algorithm>
#include <string>
namespace UsdLayerManager {
// ---------------------------------------------------------------------------
// Construction
// ---------------------------------------------------------------------------
ViewportPanel::ViewportPanel()
{
EnsureTileCount(1); // start with a single tile
}
ViewportPanel::~ViewportPanel() = default;
// ---------------------------------------------------------------------------
// EnsureTileCount / WireCallbacks
// ---------------------------------------------------------------------------
void ViewportPanel::EnsureTileCount(int count)
{
// Grow
while (static_cast<int>(m_tiles.size()) < count) {
int idx = static_cast<int>(m_tiles.size());
m_tiles.push_back(std::make_unique<ViewportTile>());
if (m_stage) m_tiles.back()->SetStage(m_stage);
if (m_iconManager) m_tiles.back()->SetIconManager(m_iconManager);
if (m_commandHistory) m_tiles.back()->SetCommandHistory(m_commandHistory);
m_tiles.back()->SetSelectedPaths(m_selectedSdfPaths, m_selectedPrimPath);
WireCallbacks(idx);
}
// Shrink
while (static_cast<int>(m_tiles.size()) > count)
m_tiles.pop_back();
// Clamp indices
m_focusedTileIndex = std::min(m_focusedTileIndex,
std::max(0, static_cast<int>(m_tiles.size()) - 1));
if (m_maximizedTileIndex >= static_cast<int>(m_tiles.size()))
m_maximizedTileIndex = -1;
}
void ViewportPanel::WireCallbacks(int i)
{
m_tiles[i]->OnPrimPicked = [this](const std::string& path) {
m_selectedPrimPath = path;
m_selectedSdfPaths.clear();
if (!path.empty())
m_selectedSdfPaths.push_back(pxr::SdfPath(path));
BroadcastSelection();
if (OnPrimPicked) OnPrimPicked(path);
};
m_tiles[i]->OnPrimsPickedRect = [this](const std::vector<std::string>& paths) {
m_selectedSdfPaths.clear();
for (const auto& p : paths)
m_selectedSdfPaths.push_back(pxr::SdfPath(p));
m_selectedPrimPath = m_selectedSdfPaths.empty()
? "" : m_selectedSdfPaths.front().GetString();
BroadcastSelection();
if (OnPrimsPickedRect) OnPrimsPickedRect(paths);
};
}
// ---------------------------------------------------------------------------
// BroadcastSelection / UpdateFocusTile
// ---------------------------------------------------------------------------
void ViewportPanel::BroadcastSelection()
{
for (auto& t : m_tiles)
t->SetSelectedPaths(m_selectedSdfPaths, m_selectedPrimPath);
pxr::SdfPath primary = m_selectedSdfPaths.empty()
? pxr::SdfPath() : m_selectedSdfPaths.front();
m_manipulator.SetSelectedPrim(primary);
}
void ViewportPanel::UpdateFocusTile(int idx)
{
if (idx < 0 || idx >= static_cast<int>(m_tiles.size())) return;
m_focusedTileIndex = idx;
}
// ---------------------------------------------------------------------------
// Public setup
// ---------------------------------------------------------------------------
void ViewportPanel::SetStage(pxr::UsdStageRefPtr stage)
{
m_stage = stage;
m_manipulator.SetStage(stage);
for (auto& t : m_tiles) t->SetStage(stage);
m_selectedSdfPaths.clear();
m_selectedPrimPath.clear();
BroadcastSelection();
}
void ViewportPanel::FrameScene()
{
for (auto& t : m_tiles) t->FrameScene();
}
void ViewportPanel::SetCommandHistory(CommandHistory* history)
{
m_commandHistory = history;
m_manipulator.SetCommandHistory(history);
for (auto& t : m_tiles) t->SetCommandHistory(history);
}
void ViewportPanel::SetIconManager(IconManager* icons)
{
m_iconManager = icons;
for (auto& t : m_tiles) t->SetIconManager(icons);
}
void ViewportPanel::SetSelectedPrimPath(const std::string& path)
{
m_selectedPrimPath = path;
m_selectedSdfPaths.clear();
if (!path.empty())
m_selectedSdfPaths.push_back(pxr::SdfPath(path));
BroadcastSelection();
}
// ---------------------------------------------------------------------------
// Forwarding accessors
// ---------------------------------------------------------------------------
ViewportCamera& ViewportPanel::GetCamera()
{
return m_tiles[static_cast<size_t>(m_focusedTileIndex)]->GetCamera();
}
UsdSceneRenderer& ViewportPanel::GetRenderer()
{
return m_tiles[static_cast<size_t>(m_focusedTileIndex)]->GetRenderer();
}
// ---------------------------------------------------------------------------
// SetLayout
// ---------------------------------------------------------------------------
void ViewportPanel::SetLayout(LayoutMode mode)
{
m_layout = mode;
m_maximizedTileIndex = -1;
switch (mode) {
case LayoutMode::Single: EnsureTileCount(1); break;
case LayoutMode::HSplit: EnsureTileCount(2); break;
case LayoutMode::VSplit: EnsureTileCount(2); break;
case LayoutMode::Quad: EnsureTileCount(4); break;
}
}
// ---------------------------------------------------------------------------
// ComputeTileRects
// ---------------------------------------------------------------------------
std::vector<ViewportPanel::TileRect>
ViewportPanel::ComputeTileRects(ImVec2 origin, ImVec2 total) const
{
std::vector<TileRect> rects;
switch (m_layout) {
case LayoutMode::Single:
rects.push_back({ origin, total });
break;
case LayoutMode::HSplit: {
float leftW = total.x * m_splitH;
float rightW = total.x - leftW;
rects.push_back({ origin, ImVec2(leftW, total.y) });
rects.push_back({ ImVec2(origin.x + leftW, origin.y), ImVec2(rightW, total.y) });
break;
}
case LayoutMode::VSplit: {
float topH = total.y * m_splitV;
float bottomH = total.y - topH;
rects.push_back({ origin, ImVec2(total.x, topH) });
rects.push_back({ ImVec2(origin.x, origin.y + topH), ImVec2(total.x, bottomH) });
break;
}
case LayoutMode::Quad: {
float leftW = total.x * m_splitH;
float rightW = total.x - leftW;
float topH = total.y * m_splitV;
float bottomH = total.y - topH;
rects.push_back({ origin, ImVec2(leftW, topH) });
rects.push_back({ ImVec2(origin.x + leftW, origin.y), ImVec2(rightW, topH) });
rects.push_back({ ImVec2(origin.x, origin.y + topH), ImVec2(leftW, bottomH) });
rects.push_back({ ImVec2(origin.x + leftW, origin.y + topH), ImVec2(rightW, bottomH) });
break;
}
}
// In multi-tile layouts inset every tile by 2px on all sides.
// Adjacent tiles then have a 4px gap (2px inset from each side) so both
// the focused border (2px) and the hovered border (1px) are fully visible.
if (m_layout != LayoutMode::Single) {
for (auto& r : rects) {
// r.pos.x += 2.f;
// r.pos.y += 2.f;
// r.size.x -= 4.f;
// r.size.y -= 4.f;
r.pos.x += 2.f;
r.pos.y += 2.f;
r.size.x -= 2.f;
r.size.y -= 2.f;
}
}
return rects;
}
// ---------------------------------------------------------------------------
bool ViewportPanel::IsMouseOverDivider(ImVec2 origin, ImVec2 total) const
{
if (m_layout == LayoutMode::Single) return false;
if (m_maximizedTileIndex >= 0) return false;
if (m_draggingDivH || m_draggingDivV) return true;
const float kDivHalf = 3.0f;
ImVec2 mouse = ImGui::GetMousePos();
if (m_layout == LayoutMode::HSplit || m_layout == LayoutMode::Quad) {
float divX = origin.x + total.x * m_splitH;
if (mouse.x >= divX - kDivHalf && mouse.x <= divX + kDivHalf &&
mouse.y >= origin.y && mouse.y <= origin.y + total.y)
return true;
}
if (m_layout == LayoutMode::VSplit || m_layout == LayoutMode::Quad) {
float divY = origin.y + total.y * m_splitV;
if (mouse.y >= divY - kDivHalf && mouse.y <= divY + kDivHalf &&
mouse.x >= origin.x && mouse.x <= origin.x + total.x)
return true;
}
return false;
}
// ---------------------------------------------------------------------------
// DrawDividers
// ---------------------------------------------------------------------------
void ViewportPanel::DrawDividers(ImVec2 origin, ImVec2 total)
{
const float kDivThick = 6.0f;
const float kDivVisual = 2.0f;
const float kMinFrac = 0.1f;
const float kMaxFrac = 0.9f;
ImDrawList* dl = ImGui::GetWindowDrawList();
ImVec2 mousePos = ImGui::GetMousePos();
// Use IsMouseClicked (edge-triggered) instead of IsMouseDown so that a
// divider drag only starts on a fresh press. If LMB is already held
// (e.g. the user is mid-rect-select in a tile) the divider is never
// accidentally triggered when the mouse drifts over the hit-zone.
bool lmbClicked = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
bool lmbReleased = ImGui::IsMouseReleased(ImGuiMouseButton_Left);
// Vertical divider (HSplit / Quad)
if (m_layout == LayoutMode::HSplit || m_layout == LayoutMode::Quad) {
float divX = origin.x + total.x * m_splitH;
ImVec2 hMin(divX - kDivThick * 0.5f, origin.y);
ImVec2 hMax(divX + kDivThick * 0.5f, origin.y + total.y);
bool hovering = !m_draggingDivV &&
mousePos.x >= hMin.x && mousePos.x <= hMax.x &&
mousePos.y >= hMin.y && mousePos.y <= hMax.y;
if ((hovering || m_draggingDivH) && !m_draggingDivV)
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
if (hovering && lmbClicked && !m_draggingDivH && !m_draggingDivV)
m_draggingDivH = true;
if (m_draggingDivH) {
float f = (mousePos.x - origin.x) / total.x;
m_splitH = std::max(kMinFrac, std::min(kMaxFrac, f));
if (lmbReleased) m_draggingDivH = false;
}
// ImU32 col = (hovering || m_draggingDivH) ? IM_COL32(66,150,250,200) : IM_COL32(80,80,80,180);
ImU32 col = (hovering || m_draggingDivH) ? IM_COL32(250,150,66,200) : IM_COL32(80,80,80,180);
dl->AddLine(ImVec2(divX, origin.y), ImVec2(divX, origin.y + total.y), col, kDivVisual);
}
// Horizontal divider (VSplit / Quad)
if (m_layout == LayoutMode::VSplit || m_layout == LayoutMode::Quad) {
float divY = origin.y + total.y * m_splitV;
ImVec2 hMin(origin.x, divY - kDivThick * 0.5f);
ImVec2 hMax(origin.x + total.x, divY + kDivThick * 0.5f);
bool hovering = !m_draggingDivH &&
mousePos.x >= hMin.x && mousePos.x <= hMax.x &&
mousePos.y >= hMin.y && mousePos.y <= hMax.y;
if ((hovering || m_draggingDivV) && !m_draggingDivH)
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeNS);
if (hovering && lmbClicked && !m_draggingDivH && !m_draggingDivV)
m_draggingDivV = true;
if (m_draggingDivV) {
float f = (mousePos.y - origin.y) / total.y;
m_splitV = std::max(kMinFrac, std::min(kMaxFrac, f));
if (lmbReleased) m_draggingDivV = false;
}
// ImU32 col = (hovering || m_draggingDivV) ? IM_COL32(66,150,250,200) : IM_COL32(80,80,80,180);
ImU32 col = (hovering || m_draggingDivV) ? IM_COL32(250,150,66,200) : IM_COL32(80,80,80,180);
dl->AddLine(ImVec2(origin.x, divY), ImVec2(origin.x + total.x, divY), col, kDivVisual);
}
}
// ---------------------------------------------------------------------------
// HandleMaximizeInput
// ---------------------------------------------------------------------------
void ViewportPanel::HandleMaximizeInput(int hoveredTileIndex)
{
ImGuiIO& io = ImGui::GetIO();
if (io.WantTextInput) return;
if (ImGui::IsKeyPressed(ImGuiKey_Space)) {
if (m_maximizedTileIndex >= 0) {
m_maximizedTileIndex = -1;
m_layout = m_layoutBeforeMaximize;
m_splitH = m_splitHBefore;
m_splitV = m_splitVBefore;
switch (m_layout) {
case LayoutMode::Single: EnsureTileCount(1); break;
case LayoutMode::HSplit: EnsureTileCount(2); break;
case LayoutMode::VSplit: EnsureTileCount(2); break;
case LayoutMode::Quad: EnsureTileCount(4); break;
}
} else if (m_layout != LayoutMode::Single && hoveredTileIndex >= 0) {
m_layoutBeforeMaximize = m_layout;
m_splitHBefore = m_splitH;
m_splitVBefore = m_splitV;
m_maximizedTileIndex = hoveredTileIndex;
UpdateFocusTile(hoveredTileIndex);
}
}
if (m_maximizedTileIndex >= 0 && ImGui::IsKeyPressed(ImGuiKey_Escape)) {
m_maximizedTileIndex = -1;
m_layout = m_layoutBeforeMaximize;
m_splitH = m_splitHBefore;
m_splitV = m_splitVBefore;
switch (m_layout) {
case LayoutMode::Single: EnsureTileCount(1); break;
case LayoutMode::HSplit: EnsureTileCount(2); break;
case LayoutMode::VSplit: EnsureTileCount(2); break;
case LayoutMode::Quad: EnsureTileCount(4); break;
}
}
}
// ---------------------------------------------------------------------------
// RenderGlobalLeftToolbar
// ---------------------------------------------------------------------------
// Draws a single vertical icon-button toolbar on the left edge of the
// viewport content area. Sections (top to bottom):
// [1][H][V][4] — layout mode
// ─────────────
// [Q][W][E][R] — manipulator tool (global, like Maya)
// ─────────────
// [W|O] — transform space toggle
// ---------------------------------------------------------------------------
void ViewportPanel::RenderGlobalLeftToolbar(ImVec2 contentPos, ImVec2 /*contentSize*/)
{
const float kBtnSize = 32.0f;
const float kIconPad = 5.0f;
const float kRounding = 4.0f;
const float kSpacing = 3.0f;
const float kPadX = 9.0f; // left padding inside the strip
const float kPadY = 10.0f; // top padding
const float kSepH = 1.0f; // separator line height
const float kSepGap = 6.0f; // space around separator
const ImVec2 kBtnSz(kBtnSize, kBtnSize);
ImDrawList* dl = ImGui::GetWindowDrawList();
ImVec2 mouse = ImGui::GetMousePos();
bool lmbClk = ImGui::IsMouseClicked(ImGuiMouseButton_Left);
// Current Y cursor
float x = contentPos.x + kPadX;
float y = contentPos.y + kPadY;
// Helper: draw one square icon button, return true if clicked.
// `active` tints the button blue. Falls back to centred text if no icon.
auto DrawBtn = [&](const char* id,
Icon iconEnum,
const char* fallbackLabel,
bool active,
const char* tooltip) -> bool
{
ImVec2 bMin(x, y);
ImVec2 bMax(x + kBtnSize, y + kBtnSize);
bool hov = mouse.x >= bMin.x && mouse.x <= bMax.x &&
mouse.y >= bMin.y && mouse.y <= bMax.y;
bool clicked = hov && lmbClk;
ImU32 bg = active ? IM_COL32( 66, 150, 250, 230) :
hov ? IM_COL32( 70, 70, 70, 220) :
IM_COL32( 32, 32, 32, 178);
dl->AddRectFilled(bMin, bMax, bg, kRounding);
if (active)
dl->AddRect(bMin, bMax, IM_COL32(100, 180, 255, 200), kRounding, 0, 1.5f);
if (m_iconManager) {
ImTextureID tex = m_iconManager->Get(iconEnum);
dl->AddImage(ImTextureRef(tex),
ImVec2(bMin.x + kIconPad, bMin.y + kIconPad),
ImVec2(bMax.x - kIconPad, bMax.y - kIconPad));
} else {
ImVec2 ts = ImGui::CalcTextSize(fallbackLabel);
dl->AddText(
ImVec2(bMin.x + (kBtnSize - ts.x) * 0.5f,
bMin.y + (kBtnSize - ts.y) * 0.5f),
IM_COL32(255, 255, 255, 255), fallbackLabel);
}
if (hov) ImGui::SetTooltip("%s", tooltip);
y += kBtnSize + kSpacing;
(void)id;
return clicked;
};
// Helper: thin horizontal separator
auto DrawSep = [&]() {
y += kSepGap;
dl->AddLine(ImVec2(x - 2.f, y), ImVec2(x + kBtnSize + 2.f, y),
IM_COL32(80, 80, 80, 160), kSepH);
y += kSepH + kSepGap;
};
// ── Section 1: Layout mode ───────────────────────────────────────────────
// Use Layout icons if available, otherwise render small Unicode glyphs.
// We don't currently have dedicated layout icons in IconManager so we use
// the fallback text path with descriptive single-character labels.
struct LayoutEntry {
LayoutMode mode;
Icon icon;
const char* label; // fallback text when no icon manager
const char* tooltip;
};
static const LayoutEntry kLayouts[] = {
{ LayoutMode::Single, Icon::LayoutSingle, "1", "Single viewport [1]" },
{ LayoutMode::HSplit, Icon::LayoutHSplit, "H", "Split left|right [H]" },
{ LayoutMode::VSplit, Icon::LayoutVSplit, "V", "Split top/bottom [V]" },
{ LayoutMode::Quad, Icon::LayoutQuad, "4", "4-quadrant grid [4]" },
};
for (const auto& lk : kLayouts) {
bool active = (m_layout == lk.mode);
ImVec2 bMin(x, y);
ImVec2 bMax(x + kBtnSize, y + kBtnSize);
bool hov = mouse.x >= bMin.x && mouse.x <= bMax.x &&
mouse.y >= bMin.y && mouse.y <= bMax.y;
bool clicked = hov && lmbClk;
ImU32 bg = active ? IM_COL32( 66, 150, 250, 230) :
hov ? IM_COL32( 70, 70, 70, 220) :
IM_COL32( 32, 32, 32, 178);
dl->AddRectFilled(bMin, bMax, bg, kRounding);
if (active)
dl->AddRect(bMin, bMax, IM_COL32(100, 180, 255, 200), kRounding, 0, 1.5f);
if (m_iconManager) {
ImTextureID tex = m_iconManager->Get(lk.icon);
dl->AddImage(ImTextureRef(tex),
ImVec2(bMin.x + kIconPad, bMin.y + kIconPad),
ImVec2(bMax.x - kIconPad, bMax.y - kIconPad));
} else {
ImVec2 ts = ImGui::CalcTextSize(lk.label);
dl->AddText(
ImVec2(bMin.x + (kBtnSize - ts.x) * 0.5f,
bMin.y + (kBtnSize - ts.y) * 0.5f),
IM_COL32(255, 255, 255, 255), lk.label);
}
if (hov) ImGui::SetTooltip("%s", lk.tooltip);
if (clicked) SetLayout(lk.mode);
y += kBtnSize + kSpacing;
}
DrawSep();
// ── Section 2: Manipulator tool mode (global, Q/W/E/R) ──────────────────
struct ToolEntry {
ManipulatorMode mode;
Icon icon;
const char* label;
const char* tooltip;
};
static const ToolEntry kTools[] = {
{ ManipulatorMode::Select, Icon::ToolSelect, "Q", "Select (Q)" },
{ ManipulatorMode::Move, Icon::ToolMove, "W", "Move (W)" },
{ ManipulatorMode::Rotate, Icon::ToolRotate, "E", "Rotate (E)" },
{ ManipulatorMode::Scale, Icon::ToolScale, "R", "Scale (R)" },
};
ManipulatorMode curMode = m_manipulator.GetMode();
for (const auto& tk : kTools) {
if (DrawBtn(tk.label, tk.icon, tk.label, curMode == tk.mode, tk.tooltip))
m_manipulator.SetMode(tk.mode);
}
DrawSep();
// ── Section 3: Transform space toggle ───────────────────────────────────
bool isWorld = (m_manipulator.GetTransformSpace() == TransformSpace::World);
if (DrawBtn("WO",
isWorld ? Icon::WorldSpace : Icon::LocalSpace,
isWorld ? "W" : "O",
isWorld,
isWorld ? "World space (click → Object)" : "Object space (click → World)"))
{
m_manipulator.SetTransformSpace(isWorld ? TransformSpace::Object
: TransformSpace::World);
}
}
// ---------------------------------------------------------------------------
// Render
// ---------------------------------------------------------------------------
void ViewportPanel::Render()
{
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::Begin("Viewport", nullptr,
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse);
// Full content area (no top toolbar — layout buttons are now in the left toolbar)
ImVec2 contentPos = ImGui::GetCursorScreenPos();
ImVec2 contentSize = ImGui::GetContentRegionAvail();
// Reserve kToolbarW pixels on the left for the global toolbar.
// Tiles occupy the remaining area to the right.
ImVec2 tilesPos (contentPos.x + kToolbarW, contentPos.y);
ImVec2 tilesSize(contentSize.x - kToolbarW, contentSize.y);
// ── Global keyboard shortcuts (Q/W/E/R — no hover gate, truly global) ────
{
ImGuiIO& io = ImGui::GetIO();
if (!io.WantTextInput) {
if (ImGui::IsKeyPressed(ImGuiKey_Q)) m_manipulator.SetMode(ManipulatorMode::Select);
if (ImGui::IsKeyPressed(ImGuiKey_W)) m_manipulator.SetMode(ManipulatorMode::Move);
if (ImGui::IsKeyPressed(ImGuiKey_E)) m_manipulator.SetMode(ManipulatorMode::Rotate);
if (ImGui::IsKeyPressed(ImGuiKey_R)) m_manipulator.SetMode(ManipulatorMode::Scale);
}
}
// ── Render tiles ──────────────────────────────────────────────────────────
int hoveredTileIndex = -1;
if (m_maximizedTileIndex >= 0 &&
m_maximizedTileIndex < static_cast<int>(m_tiles.size()))
{
// Maximised: tile fills the tile area (not the toolbar strip)
int i = m_maximizedTileIndex;
m_tiles[i]->Render(i, tilesPos, tilesSize, /*isFocused=*/true, m_manipulator,
/*dividerActive=*/false);
if (m_tiles[i]->WasClickedThisFrame()) UpdateFocusTile(i);
if (m_tiles[i]->IsHoveredThisFrame()) hoveredTileIndex = i;
ImDrawList* dl = ImGui::GetWindowDrawList();
dl->AddText(
ImVec2(tilesPos.x + tilesSize.x - 200.f, tilesPos.y + 5.f),
IM_COL32(255, 200, 0, 160),
"Maximized [Space / Esc] restore");
}
else
{
bool dividerActive = IsMouseOverDivider(tilesPos, tilesSize);
auto rects = ComputeTileRects(tilesPos, tilesSize);
for (int i = 0; i < static_cast<int>(m_tiles.size()); ++i) {
bool focused = (i == m_focusedTileIndex);
m_tiles[i]->Render(i, rects[i].pos, rects[i].size, focused, m_manipulator,
dividerActive);
if (m_tiles[i]->WasClickedThisFrame()) UpdateFocusTile(i);
if (m_tiles[i]->IsHoveredThisFrame()) hoveredTileIndex = i;
}
if (m_layout != LayoutMode::Single)
DrawDividers(tilesPos, tilesSize);
}
// ── Global left toolbar (layout + Q/W/E/R + space) ────────────────────────
// Drawn after tiles so it renders on top; uses raw screen-pos hit-testing
// so it is not inside any tile's BeginChild scope.
RenderGlobalLeftToolbar(contentPos, contentSize);
// ── Space / Escape maximize ───────────────────────────────────────────────
HandleMaximizeInput(hoveredTileIndex);
ImGui::End();
ImGui::PopStyleVar(); // outer WindowPadding
}
} // namespace UsdLayerManager