feat(xcui): add tab strip and workspace compose foundations
This commit is contained in:
133
engine/include/XCEngine/UI/Layout/UITabStripLayout.h
Normal file
133
engine/include/XCEngine/UI/Layout/UITabStripLayout.h
Normal file
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/UI/Layout/LayoutTypes.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
namespace Layout {
|
||||
|
||||
struct UITabStripMetrics {
|
||||
float headerHeight = 34.0f;
|
||||
float tabMinWidth = 72.0f;
|
||||
float tabHorizontalPadding = 12.0f;
|
||||
float tabGap = 1.0f;
|
||||
};
|
||||
|
||||
struct UITabStripMeasureItem {
|
||||
float desiredHeaderLabelWidth = 0.0f;
|
||||
UISize desiredContentSize = {};
|
||||
UISize minimumContentSize = {};
|
||||
};
|
||||
|
||||
struct UITabStripMeasureResult {
|
||||
UISize desiredSize = {};
|
||||
UISize minimumSize = {};
|
||||
};
|
||||
|
||||
struct UITabStripLayoutResult {
|
||||
UIRect headerRect = {};
|
||||
UIRect contentRect = {};
|
||||
std::vector<UIRect> tabHeaderRects = {};
|
||||
};
|
||||
|
||||
inline float MeasureUITabStripHeaderWidth(
|
||||
float labelWidth,
|
||||
const UITabStripMetrics& metrics) {
|
||||
return (std::max)(
|
||||
metrics.tabMinWidth,
|
||||
(std::max)(0.0f, labelWidth) + (std::max)(0.0f, metrics.tabHorizontalPadding) * 2.0f);
|
||||
}
|
||||
|
||||
inline UITabStripMeasureResult MeasureUITabStrip(
|
||||
const std::vector<UITabStripMeasureItem>& items,
|
||||
const UITabStripMetrics& metrics) {
|
||||
UITabStripMeasureResult result = {};
|
||||
|
||||
const float gap = (std::max)(0.0f, metrics.tabGap);
|
||||
float desiredHeaderWidth = 0.0f;
|
||||
float minimumHeaderWidth = 0.0f;
|
||||
float desiredContentWidth = 0.0f;
|
||||
float desiredContentHeight = 0.0f;
|
||||
float minimumContentWidth = 0.0f;
|
||||
float minimumContentHeight = 0.0f;
|
||||
|
||||
for (std::size_t index = 0; index < items.size(); ++index) {
|
||||
const UITabStripMeasureItem& item = items[index];
|
||||
desiredHeaderWidth += MeasureUITabStripHeaderWidth(
|
||||
item.desiredHeaderLabelWidth,
|
||||
metrics);
|
||||
minimumHeaderWidth += metrics.tabMinWidth;
|
||||
if (index > 0u) {
|
||||
desiredHeaderWidth += gap;
|
||||
minimumHeaderWidth += gap;
|
||||
}
|
||||
|
||||
desiredContentWidth = (std::max)(desiredContentWidth, item.desiredContentSize.width);
|
||||
desiredContentHeight = (std::max)(desiredContentHeight, item.desiredContentSize.height);
|
||||
minimumContentWidth = (std::max)(minimumContentWidth, item.minimumContentSize.width);
|
||||
minimumContentHeight = (std::max)(minimumContentHeight, item.minimumContentSize.height);
|
||||
}
|
||||
|
||||
const float headerHeight = items.empty() ? 0.0f : (std::max)(0.0f, metrics.headerHeight);
|
||||
result.desiredSize = UISize(
|
||||
(std::max)(desiredHeaderWidth, desiredContentWidth),
|
||||
headerHeight + desiredContentHeight);
|
||||
result.minimumSize = UISize(
|
||||
(std::max)(minimumHeaderWidth, minimumContentWidth),
|
||||
headerHeight + minimumContentHeight);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline UITabStripLayoutResult ArrangeUITabStrip(
|
||||
const UIRect& bounds,
|
||||
const std::vector<float>& desiredHeaderWidths,
|
||||
const UITabStripMetrics& metrics) {
|
||||
UITabStripLayoutResult result = {};
|
||||
|
||||
const float headerHeight = desiredHeaderWidths.empty()
|
||||
? 0.0f
|
||||
: (std::min)((std::max)(0.0f, metrics.headerHeight), bounds.height);
|
||||
result.headerRect = UIRect(bounds.x, bounds.y, bounds.width, headerHeight);
|
||||
result.contentRect = UIRect(
|
||||
bounds.x,
|
||||
bounds.y + headerHeight,
|
||||
bounds.width,
|
||||
(std::max)(0.0f, bounds.height - headerHeight));
|
||||
|
||||
result.tabHeaderRects.resize(desiredHeaderWidths.size());
|
||||
if (desiredHeaderWidths.empty()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const float gap = (std::max)(0.0f, metrics.tabGap);
|
||||
const float totalGapWidth = gap * static_cast<float>(desiredHeaderWidths.size() - 1u);
|
||||
const float availableTabsWidth = (std::max)(0.0f, bounds.width - totalGapWidth);
|
||||
|
||||
float totalDesiredWidth = 0.0f;
|
||||
for (float width : desiredHeaderWidths) {
|
||||
totalDesiredWidth += (std::max)(0.0f, width);
|
||||
}
|
||||
|
||||
const float scale = totalDesiredWidth > 0.0f && totalDesiredWidth > availableTabsWidth
|
||||
? availableTabsWidth / totalDesiredWidth
|
||||
: 1.0f;
|
||||
|
||||
float cursorX = bounds.x;
|
||||
for (std::size_t index = 0; index < desiredHeaderWidths.size(); ++index) {
|
||||
const float width = totalDesiredWidth > 0.0f
|
||||
? (std::max)(0.0f, desiredHeaderWidths[index]) * scale
|
||||
: availableTabsWidth / static_cast<float>(desiredHeaderWidths.size());
|
||||
result.tabHeaderRects[index] = UIRect(cursorX, bounds.y, width, headerHeight);
|
||||
cursorX += width + gap;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Layout
|
||||
} // namespace UI
|
||||
} // namespace XCEngine
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <XCEngine/UI/Runtime/UIScreenTypes.h>
|
||||
#include <XCEngine/UI/Widgets/UISplitterInteraction.h>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
@@ -91,6 +92,7 @@ private:
|
||||
UIInputDispatcher m_inputDispatcher;
|
||||
std::unordered_map<std::string, float> m_verticalScrollOffsets = {};
|
||||
std::unordered_map<std::string, float> m_splitterRatios = {};
|
||||
std::unordered_map<std::string, std::size_t> m_tabStripSelectedIndices = {};
|
||||
PointerState m_pointerState = {};
|
||||
SplitterDragRuntimeState m_splitterDragState = {};
|
||||
InputDebugSnapshot m_inputDebugSnapshot = {};
|
||||
|
||||
110
engine/include/XCEngine/UI/Widgets/UITabStripModel.h
Normal file
110
engine/include/XCEngine/UI/Widgets/UITabStripModel.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
namespace Widgets {
|
||||
|
||||
class UITabStripModel {
|
||||
public:
|
||||
static constexpr std::size_t InvalidIndex = static_cast<std::size_t>(-1);
|
||||
|
||||
std::size_t GetItemCount() const {
|
||||
return m_itemCount;
|
||||
}
|
||||
|
||||
bool SetItemCount(std::size_t itemCount) {
|
||||
m_itemCount = itemCount;
|
||||
return ClampSelection();
|
||||
}
|
||||
|
||||
bool HasSelection() const {
|
||||
return m_itemCount > 0u && m_selectedIndex != InvalidIndex;
|
||||
}
|
||||
|
||||
std::size_t GetSelectedIndex() const {
|
||||
return m_selectedIndex;
|
||||
}
|
||||
|
||||
bool SetSelectedIndex(std::size_t index) {
|
||||
if (m_itemCount == 0u) {
|
||||
index = InvalidIndex;
|
||||
} else if (index >= m_itemCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_selectedIndex == index) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_selectedIndex = index;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SelectFirst() {
|
||||
return SetSelectedIndex(m_itemCount > 0u ? 0u : InvalidIndex);
|
||||
}
|
||||
|
||||
bool SelectLast() {
|
||||
return SetSelectedIndex(m_itemCount > 0u ? (m_itemCount - 1u) : InvalidIndex);
|
||||
}
|
||||
|
||||
bool SelectNext() {
|
||||
if (m_itemCount == 0u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_selectedIndex == InvalidIndex) {
|
||||
m_selectedIndex = 0u;
|
||||
return true;
|
||||
}
|
||||
|
||||
return SetSelectedIndex((std::min)(m_selectedIndex + 1u, m_itemCount - 1u));
|
||||
}
|
||||
|
||||
bool SelectPrevious() {
|
||||
if (m_itemCount == 0u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_selectedIndex == InvalidIndex) {
|
||||
m_selectedIndex = 0u;
|
||||
return true;
|
||||
}
|
||||
|
||||
return SetSelectedIndex(m_selectedIndex > 0u ? m_selectedIndex - 1u : 0u);
|
||||
}
|
||||
|
||||
private:
|
||||
bool ClampSelection() {
|
||||
if (m_itemCount == 0u) {
|
||||
if (m_selectedIndex == InvalidIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_selectedIndex = InvalidIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_selectedIndex == InvalidIndex) {
|
||||
m_selectedIndex = 0u;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_selectedIndex < m_itemCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_selectedIndex = m_itemCount - 1u;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::size_t m_itemCount = 0u;
|
||||
std::size_t m_selectedIndex = InvalidIndex;
|
||||
};
|
||||
|
||||
} // namespace Widgets
|
||||
} // namespace UI
|
||||
} // namespace XCEngine
|
||||
Reference in New Issue
Block a user