342 lines
10 KiB
C++
342 lines
10 KiB
C++
#include <XCEditor/Widgets/UIEditorPanelFrame.h>
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
|
|
namespace XCEngine::UI::Editor::Widgets {
|
|
|
|
namespace {
|
|
|
|
using ::XCEngine::UI::UIColor;
|
|
using ::XCEngine::UI::UIDrawList;
|
|
using ::XCEngine::UI::UIPoint;
|
|
using ::XCEngine::UI::UIRect;
|
|
|
|
float ClampNonNegative(float value) {
|
|
return (std::max)(value, 0.0f);
|
|
}
|
|
|
|
float ResolveActionButtonExtent(
|
|
const UIEditorPanelFrameMetrics& metrics,
|
|
float headerHeight) {
|
|
const float inset = (std::max)(metrics.actionInsetX, 0.0f);
|
|
return (std::min)(
|
|
(std::max)(metrics.actionButtonExtent, 0.0f),
|
|
(std::max)(headerHeight - inset * 2.0f, 0.0f));
|
|
}
|
|
|
|
UIColor ResolveActionFillColor(
|
|
bool selected,
|
|
bool hovered,
|
|
const UIEditorPanelFramePalette& palette) {
|
|
if (selected) {
|
|
return palette.actionButtonSelectedColor;
|
|
}
|
|
|
|
if (hovered) {
|
|
return palette.actionButtonHoveredColor;
|
|
}
|
|
|
|
return palette.actionButtonColor;
|
|
}
|
|
|
|
void AppendActionButton(
|
|
UIDrawList& drawList,
|
|
const UIRect& rect,
|
|
std::string_view glyph,
|
|
bool selected,
|
|
bool hovered,
|
|
const UIEditorPanelFramePalette& palette) {
|
|
if (rect.width <= 0.0f || rect.height <= 0.0f) {
|
|
return;
|
|
}
|
|
|
|
drawList.AddFilledRect(
|
|
rect,
|
|
ResolveActionFillColor(selected, hovered, palette),
|
|
5.0f);
|
|
drawList.AddRectOutline(
|
|
rect,
|
|
palette.actionButtonBorderColor,
|
|
1.0f,
|
|
5.0f);
|
|
drawList.AddText(
|
|
UIPoint(rect.x + 5.0f, rect.y + 2.0f),
|
|
std::string(glyph),
|
|
palette.actionGlyphColor,
|
|
12.0f);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
bool IsUIEditorPanelFramePointInside(
|
|
const UIRect& rect,
|
|
const UIPoint& point) {
|
|
return point.x >= rect.x &&
|
|
point.x <= rect.x + rect.width &&
|
|
point.y >= rect.y &&
|
|
point.y <= rect.y + rect.height;
|
|
}
|
|
|
|
bool IsUIEditorPanelFramePinButtonVisible(const UIEditorPanelFrameState& state) {
|
|
return state.pinnable;
|
|
}
|
|
|
|
bool IsUIEditorPanelFrameCloseButtonVisible(const UIEditorPanelFrameState& state) {
|
|
return state.closable;
|
|
}
|
|
|
|
UIEditorPanelFrameLayout BuildUIEditorPanelFrameLayout(
|
|
const UIRect& frameRect,
|
|
const UIEditorPanelFrameState& state,
|
|
const UIEditorPanelFrameMetrics& metrics) {
|
|
UIEditorPanelFrameLayout layout = {};
|
|
layout.frameRect = UIRect(
|
|
frameRect.x,
|
|
frameRect.y,
|
|
ClampNonNegative(frameRect.width),
|
|
ClampNonNegative(frameRect.height));
|
|
|
|
const float headerHeight =
|
|
(std::min)(ClampNonNegative(metrics.headerHeight), layout.frameRect.height);
|
|
const float footerHeight =
|
|
state.showFooter
|
|
? (std::min)(
|
|
ClampNonNegative(metrics.footerHeight),
|
|
(std::max)(layout.frameRect.height - headerHeight, 0.0f))
|
|
: 0.0f;
|
|
|
|
layout.hasFooter = footerHeight > 0.0f;
|
|
layout.showPinButton = IsUIEditorPanelFramePinButtonVisible(state);
|
|
layout.showCloseButton = IsUIEditorPanelFrameCloseButtonVisible(state);
|
|
|
|
layout.headerRect = UIRect(
|
|
layout.frameRect.x,
|
|
layout.frameRect.y,
|
|
layout.frameRect.width,
|
|
headerHeight);
|
|
|
|
if (layout.hasFooter) {
|
|
layout.footerRect = UIRect(
|
|
layout.frameRect.x,
|
|
layout.frameRect.y + layout.frameRect.height - footerHeight,
|
|
layout.frameRect.width,
|
|
footerHeight);
|
|
}
|
|
|
|
const float bodyBandTop = layout.headerRect.y + layout.headerRect.height;
|
|
const float bodyBandBottom = layout.hasFooter
|
|
? layout.footerRect.y
|
|
: layout.frameRect.y + layout.frameRect.height;
|
|
const float contentPadding = ClampNonNegative(metrics.contentPadding);
|
|
|
|
layout.bodyRect = UIRect(
|
|
layout.frameRect.x + contentPadding,
|
|
bodyBandTop + contentPadding,
|
|
(std::max)(layout.frameRect.width - contentPadding * 2.0f, 0.0f),
|
|
(std::max)(bodyBandBottom - bodyBandTop - contentPadding * 2.0f, 0.0f));
|
|
|
|
if (layout.headerRect.height <= 0.0f) {
|
|
return layout;
|
|
}
|
|
|
|
const float buttonExtent = ResolveActionButtonExtent(metrics, layout.headerRect.height);
|
|
if (buttonExtent <= 0.0f) {
|
|
return layout;
|
|
}
|
|
|
|
const float buttonY = layout.headerRect.y + (layout.headerRect.height - buttonExtent) * 0.5f;
|
|
float buttonRight = layout.headerRect.x + layout.headerRect.width - ClampNonNegative(metrics.actionInsetX);
|
|
|
|
if (layout.showCloseButton) {
|
|
layout.closeButtonRect = UIRect(
|
|
buttonRight - buttonExtent,
|
|
buttonY,
|
|
buttonExtent,
|
|
buttonExtent);
|
|
buttonRight = layout.closeButtonRect.x - ClampNonNegative(metrics.actionGap);
|
|
}
|
|
|
|
if (layout.showPinButton) {
|
|
layout.pinButtonRect = UIRect(
|
|
buttonRight - buttonExtent,
|
|
buttonY,
|
|
buttonExtent,
|
|
buttonExtent);
|
|
}
|
|
|
|
return layout;
|
|
}
|
|
|
|
UIColor ResolveUIEditorPanelFrameBorderColor(
|
|
const UIEditorPanelFrameState& state,
|
|
const UIEditorPanelFramePalette& palette) {
|
|
if (state.focused) {
|
|
return palette.focusedBorderColor;
|
|
}
|
|
|
|
if (state.active) {
|
|
return palette.activeBorderColor;
|
|
}
|
|
|
|
if (state.hovered) {
|
|
return palette.hoveredBorderColor;
|
|
}
|
|
|
|
return palette.borderColor;
|
|
}
|
|
|
|
float ResolveUIEditorPanelFrameBorderThickness(
|
|
const UIEditorPanelFrameState& state,
|
|
const UIEditorPanelFrameMetrics& metrics) {
|
|
if (state.focused) {
|
|
return metrics.focusedBorderThickness;
|
|
}
|
|
|
|
if (state.active) {
|
|
return metrics.activeBorderThickness;
|
|
}
|
|
|
|
if (state.hovered) {
|
|
return metrics.hoveredBorderThickness;
|
|
}
|
|
|
|
return metrics.baseBorderThickness;
|
|
}
|
|
|
|
UIEditorPanelFrameAction HitTestUIEditorPanelFrameAction(
|
|
const UIEditorPanelFrameLayout& layout,
|
|
const UIEditorPanelFrameState& state,
|
|
const UIPoint& point) {
|
|
if (layout.showPinButton &&
|
|
IsUIEditorPanelFramePinButtonVisible(state) &&
|
|
IsUIEditorPanelFramePointInside(layout.pinButtonRect, point)) {
|
|
return UIEditorPanelFrameAction::Pin;
|
|
}
|
|
|
|
if (layout.showCloseButton &&
|
|
IsUIEditorPanelFrameCloseButtonVisible(state) &&
|
|
IsUIEditorPanelFramePointInside(layout.closeButtonRect, point)) {
|
|
return UIEditorPanelFrameAction::Close;
|
|
}
|
|
|
|
return UIEditorPanelFrameAction::None;
|
|
}
|
|
|
|
UIEditorPanelFrameHitTarget HitTestUIEditorPanelFrame(
|
|
const UIEditorPanelFrameLayout& layout,
|
|
const UIEditorPanelFrameState& state,
|
|
const UIPoint& point) {
|
|
if (!IsUIEditorPanelFramePointInside(layout.frameRect, point)) {
|
|
return UIEditorPanelFrameHitTarget::None;
|
|
}
|
|
|
|
switch (HitTestUIEditorPanelFrameAction(layout, state, point)) {
|
|
case UIEditorPanelFrameAction::Pin:
|
|
return UIEditorPanelFrameHitTarget::PinButton;
|
|
case UIEditorPanelFrameAction::Close:
|
|
return UIEditorPanelFrameHitTarget::CloseButton;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (IsUIEditorPanelFramePointInside(layout.headerRect, point)) {
|
|
return UIEditorPanelFrameHitTarget::Header;
|
|
}
|
|
|
|
if (layout.hasFooter && IsUIEditorPanelFramePointInside(layout.footerRect, point)) {
|
|
return UIEditorPanelFrameHitTarget::Footer;
|
|
}
|
|
|
|
if (IsUIEditorPanelFramePointInside(layout.bodyRect, point)) {
|
|
return UIEditorPanelFrameHitTarget::Body;
|
|
}
|
|
|
|
return UIEditorPanelFrameHitTarget::Body;
|
|
}
|
|
|
|
void AppendUIEditorPanelFrameBackground(
|
|
UIDrawList& drawList,
|
|
const UIEditorPanelFrameLayout& layout,
|
|
const UIEditorPanelFrameState& state,
|
|
const UIEditorPanelFramePalette& palette,
|
|
const UIEditorPanelFrameMetrics& metrics) {
|
|
drawList.AddFilledRect(layout.frameRect, palette.surfaceColor, metrics.cornerRounding);
|
|
drawList.AddRectOutline(
|
|
layout.frameRect,
|
|
ResolveUIEditorPanelFrameBorderColor(state, palette),
|
|
ResolveUIEditorPanelFrameBorderThickness(state, metrics),
|
|
metrics.cornerRounding);
|
|
drawList.AddFilledRect(layout.headerRect, palette.headerColor, metrics.cornerRounding);
|
|
if (layout.hasFooter) {
|
|
drawList.AddFilledRect(layout.footerRect, palette.footerColor, metrics.cornerRounding);
|
|
}
|
|
}
|
|
|
|
void AppendUIEditorPanelFrameForeground(
|
|
UIDrawList& drawList,
|
|
const UIEditorPanelFrameLayout& layout,
|
|
const UIEditorPanelFrameState& state,
|
|
const UIEditorPanelFrameText& text,
|
|
const UIEditorPanelFramePalette& palette,
|
|
const UIEditorPanelFrameMetrics& metrics) {
|
|
if (!text.title.empty()) {
|
|
drawList.AddText(
|
|
UIPoint(layout.frameRect.x + metrics.titleInsetX, layout.headerRect.y + metrics.titleInsetY),
|
|
std::string(text.title),
|
|
palette.textPrimary,
|
|
16.0f);
|
|
}
|
|
|
|
if (!text.subtitle.empty()) {
|
|
drawList.AddText(
|
|
UIPoint(layout.frameRect.x + metrics.titleInsetX, layout.headerRect.y + metrics.subtitleInsetY),
|
|
std::string(text.subtitle),
|
|
palette.textSecondary,
|
|
12.0f);
|
|
}
|
|
|
|
if (layout.hasFooter && !text.footer.empty()) {
|
|
drawList.AddText(
|
|
UIPoint(layout.footerRect.x + metrics.footerInsetX, layout.footerRect.y + metrics.footerInsetY),
|
|
std::string(text.footer),
|
|
palette.textMuted,
|
|
11.0f);
|
|
}
|
|
|
|
if (layout.showPinButton) {
|
|
AppendActionButton(
|
|
drawList,
|
|
layout.pinButtonRect,
|
|
"P",
|
|
state.pinned,
|
|
state.pinHovered,
|
|
palette);
|
|
}
|
|
|
|
if (layout.showCloseButton) {
|
|
AppendActionButton(
|
|
drawList,
|
|
layout.closeButtonRect,
|
|
"X",
|
|
false,
|
|
state.closeHovered,
|
|
palette);
|
|
}
|
|
}
|
|
|
|
void AppendUIEditorPanelFrame(
|
|
UIDrawList& drawList,
|
|
const UIRect& frameRect,
|
|
const UIEditorPanelFrameState& state,
|
|
const UIEditorPanelFrameText& text,
|
|
const UIEditorPanelFramePalette& palette,
|
|
const UIEditorPanelFrameMetrics& metrics) {
|
|
const UIEditorPanelFrameLayout layout =
|
|
BuildUIEditorPanelFrameLayout(frameRect, state, metrics);
|
|
AppendUIEditorPanelFrameBackground(drawList, layout, state, palette, metrics);
|
|
AppendUIEditorPanelFrameForeground(drawList, layout, state, text, palette, metrics);
|
|
}
|
|
|
|
} // namespace XCEngine::UI::Editor::Widgets
|