612 lines
24 KiB
C++
612 lines
24 KiB
C++
#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()->SetTimeCodes(m_displayTime, m_editTime);
|
|
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::SetTimeCodes(pxr::UsdTimeCode displayTime,
|
|
pxr::UsdTimeCode editTime)
|
|
{
|
|
m_displayTime = displayTime;
|
|
m_editTime = editTime;
|
|
for (auto& t : m_tiles) t->SetTimeCodes(displayTime, editTime);
|
|
m_manipulator.SetTimeCodes(displayTime, editTime);
|
|
}
|
|
|
|
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
|