feat(xcui): advance core and editor validation flow
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Resources/UI/UIDocumentTypes.h>
|
||||
#include <XCEngine/UI/Style/Theme.h>
|
||||
#include <XCEngine/UI/Style/StyleSet.h>
|
||||
#include <XCEngine/UI/DrawData.h>
|
||||
|
||||
#include <cstddef>
|
||||
@@ -30,7 +32,10 @@ struct UIScreenDocument {
|
||||
std::vector<std::string> dependencies = {};
|
||||
Resources::UIDocumentModel viewDocument = {};
|
||||
Resources::UIDocumentModel themeDocument = {};
|
||||
Style::UITheme runtimeTheme = {};
|
||||
Style::UIStyleSheet runtimeStyleSheet = {};
|
||||
bool hasThemeDocument = false;
|
||||
bool hasRuntimeTheme = false;
|
||||
|
||||
bool IsValid() const {
|
||||
return !sourcePath.empty();
|
||||
@@ -39,6 +44,14 @@ struct UIScreenDocument {
|
||||
const Resources::UIDocumentModel* GetThemeDocument() const {
|
||||
return hasThemeDocument ? &themeDocument : nullptr;
|
||||
}
|
||||
|
||||
const Style::UITheme* GetRuntimeTheme() const {
|
||||
return hasRuntimeTheme ? &runtimeTheme : nullptr;
|
||||
}
|
||||
|
||||
const Style::UIStyleSheet* GetRuntimeStyleSheet() const {
|
||||
return hasRuntimeTheme ? &runtimeStyleSheet : nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
struct UIScreenLoadResult {
|
||||
|
||||
33
engine/include/XCEngine/UI/Style/DocumentStyleCompiler.h
Normal file
33
engine/include/XCEngine/UI/Style/DocumentStyleCompiler.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "StyleResolver.h"
|
||||
|
||||
#include <XCEngine/Resources/UI/UIDocumentTypes.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
namespace Style {
|
||||
|
||||
struct UIDocumentStyleCompileResult {
|
||||
bool succeeded = false;
|
||||
std::string errorMessage = {};
|
||||
UITheme theme = {};
|
||||
UIStyleSheet styleSheet = {};
|
||||
};
|
||||
|
||||
UIDocumentStyleCompileResult CompileDocumentStyle(
|
||||
const Resources::UIDocumentModel& themeDocument);
|
||||
|
||||
bool TryCompileDocumentStyle(
|
||||
const Resources::UIDocumentModel& themeDocument,
|
||||
UITheme& outTheme,
|
||||
UIStyleSheet& outStyleSheet,
|
||||
std::string* outErrorMessage = nullptr);
|
||||
|
||||
UIStyleSet BuildInlineStyle(const Resources::UIDocumentNode& node);
|
||||
|
||||
} // namespace Style
|
||||
} // namespace UI
|
||||
} // namespace XCEngine
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <XCEngine/Resources/UI/UIDocumentCompiler.h>
|
||||
#include <XCEngine/UI/Layout/LayoutEngine.h>
|
||||
#include <XCEngine/UI/Layout/UITabStripLayout.h>
|
||||
#include <XCEngine/UI/Style/DocumentStyleCompiler.h>
|
||||
#include <XCEngine/UI/Widgets/UITabStripModel.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -34,6 +35,7 @@ using XCEngine::Resources::UIDocumentCompileResult;
|
||||
using XCEngine::Resources::UIDocumentKind;
|
||||
using XCEngine::Resources::UIDocumentNode;
|
||||
namespace Layout = XCEngine::UI::Layout;
|
||||
namespace Style = XCEngine::UI::Style;
|
||||
|
||||
constexpr float kDefaultFontSize = 16.0f;
|
||||
constexpr float kSmallFontSize = 13.0f;
|
||||
@@ -78,6 +80,8 @@ struct RuntimeLayoutNode {
|
||||
bool tabSelected = false;
|
||||
bool hasShortcutBinding = false;
|
||||
UIShortcutBinding shortcutBinding = {};
|
||||
Style::UIStyleSet localStyle = {};
|
||||
Style::UIResolvedStyle resolvedStyle = {};
|
||||
enum class ShortcutScopeRoot : std::uint8_t {
|
||||
None = 0,
|
||||
Window,
|
||||
@@ -580,6 +584,166 @@ Layout::UILayoutThickness ParsePadding(
|
||||
return Layout::UILayoutThickness::Uniform(ParseFloatAttribute(node, "padding", fallback));
|
||||
}
|
||||
|
||||
Layout::UILayoutThickness ToLayoutThickness(const Style::UIThickness& thickness) {
|
||||
return Layout::UILayoutThickness(
|
||||
thickness.left,
|
||||
thickness.top,
|
||||
thickness.right,
|
||||
thickness.bottom);
|
||||
}
|
||||
|
||||
const Style::UIStylePropertyResolution* FindResolvedProperty(
|
||||
const RuntimeLayoutNode& node,
|
||||
Style::UIStylePropertyId propertyId) {
|
||||
return node.resolvedStyle.FindProperty(propertyId);
|
||||
}
|
||||
|
||||
float ResolveNodeFontSize(
|
||||
const RuntimeLayoutNode& node,
|
||||
float fallback) {
|
||||
const Style::UIStylePropertyResolution* resolution =
|
||||
FindResolvedProperty(node, Style::UIStylePropertyId::FontSize);
|
||||
if (resolution == nullptr) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (const float* value = resolution->value.TryGetFloat()) {
|
||||
return (std::max)(1.0f, *value);
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
float ResolveNodeGap(
|
||||
const RuntimeLayoutNode& node,
|
||||
float fallback) {
|
||||
const Style::UIStylePropertyResolution* resolution =
|
||||
FindResolvedProperty(node, Style::UIStylePropertyId::Gap);
|
||||
if (resolution == nullptr) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (const float* value = resolution->value.TryGetFloat()) {
|
||||
return (std::max)(0.0f, *value);
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
Layout::UILayoutThickness ResolveNodePadding(
|
||||
const RuntimeLayoutNode& node,
|
||||
float fallback) {
|
||||
const Style::UIStylePropertyResolution* resolution =
|
||||
FindResolvedProperty(node, Style::UIStylePropertyId::Padding);
|
||||
if (resolution == nullptr) {
|
||||
return Layout::UILayoutThickness::Uniform(fallback);
|
||||
}
|
||||
|
||||
if (const Style::UIThickness* thickness = resolution->value.TryGetThickness()) {
|
||||
return ToLayoutThickness(*thickness);
|
||||
}
|
||||
|
||||
return Layout::UILayoutThickness::Uniform(fallback);
|
||||
}
|
||||
|
||||
float ResolveNodeBorderWidth(
|
||||
const RuntimeLayoutNode& node,
|
||||
float fallback) {
|
||||
const Style::UIStylePropertyResolution* resolution =
|
||||
FindResolvedProperty(node, Style::UIStylePropertyId::BorderWidth);
|
||||
if (resolution == nullptr) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (const float* value = resolution->value.TryGetFloat()) {
|
||||
return (std::max)(0.0f, *value);
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
float ResolveNodeCornerRadius(
|
||||
const RuntimeLayoutNode& node,
|
||||
float fallback) {
|
||||
const Style::UIStylePropertyResolution* resolution =
|
||||
FindResolvedProperty(node, Style::UIStylePropertyId::CornerRadius);
|
||||
if (resolution == nullptr) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (const Style::UICornerRadius* radius = resolution->value.TryGetCornerRadius()) {
|
||||
return (std::max)({
|
||||
0.0f,
|
||||
radius->topLeft,
|
||||
radius->topRight,
|
||||
radius->bottomRight,
|
||||
radius->bottomLeft
|
||||
});
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
Color AdjustColorBrightness(const Color& color, float delta) {
|
||||
auto clampChannel = [delta](float value) {
|
||||
return (std::clamp)(value + delta, 0.0f, 1.0f);
|
||||
};
|
||||
|
||||
return Color(
|
||||
clampChannel(color.r),
|
||||
clampChannel(color.g),
|
||||
clampChannel(color.b),
|
||||
color.a);
|
||||
}
|
||||
|
||||
Color ResolveLegacyBaseBackgroundColor(const UIDocumentNode& node) {
|
||||
const std::string tone = GetAttribute(node, "tone");
|
||||
const std::string tagName = ToStdString(node.tagName);
|
||||
|
||||
if (tagName == "View") {
|
||||
return Color(0.11f, 0.11f, 0.11f, 1.0f);
|
||||
}
|
||||
if (tone == "accent") {
|
||||
return Color(0.25f, 0.25f, 0.25f, 1.0f);
|
||||
}
|
||||
if (tone == "accent-alt") {
|
||||
return Color(0.22f, 0.22f, 0.22f, 1.0f);
|
||||
}
|
||||
if (tagName == "Button") {
|
||||
return Color(0.24f, 0.24f, 0.24f, 1.0f);
|
||||
}
|
||||
|
||||
return Color(0.16f, 0.16f, 0.16f, 1.0f);
|
||||
}
|
||||
|
||||
Color ResolveLegacyBaseBorderColor(const UIDocumentNode& node) {
|
||||
const std::string tone = GetAttribute(node, "tone");
|
||||
if (tone == "accent") {
|
||||
return Color(0.42f, 0.42f, 0.42f, 1.0f);
|
||||
}
|
||||
if (tone == "accent-alt") {
|
||||
return Color(0.34f, 0.34f, 0.34f, 1.0f);
|
||||
}
|
||||
|
||||
return Color(0.30f, 0.30f, 0.30f, 1.0f);
|
||||
}
|
||||
|
||||
Color ResolveStyledColor(
|
||||
const RuntimeLayoutNode& node,
|
||||
Style::UIStylePropertyId propertyId,
|
||||
const Color& fallback) {
|
||||
const Style::UIStylePropertyResolution* resolution = FindResolvedProperty(node, propertyId);
|
||||
if (resolution == nullptr) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (const Color* color = resolution->value.TryGetColor()) {
|
||||
return *color;
|
||||
}
|
||||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
Layout::UILayoutItem BuildLayoutItem(
|
||||
const RuntimeLayoutNode& child,
|
||||
Layout::UILayoutAxis parentAxis,
|
||||
@@ -1283,61 +1447,68 @@ UIInputPath ResolveHoveredPath(
|
||||
}
|
||||
|
||||
Color ResolveBackgroundColor(
|
||||
const UIDocumentNode& node,
|
||||
const RuntimeLayoutNode& node,
|
||||
const RuntimeNodeVisualState& state) {
|
||||
const std::string tone = GetAttribute(node, "tone");
|
||||
const std::string tagName = ToStdString(node.tagName);
|
||||
|
||||
if (tagName == "View") {
|
||||
return Color(0.11f, 0.11f, 0.11f, 1.0f);
|
||||
}
|
||||
if (tone == "accent") {
|
||||
return Color(0.25f, 0.25f, 0.25f, 1.0f);
|
||||
}
|
||||
if (tone == "accent-alt") {
|
||||
return Color(0.22f, 0.22f, 0.22f, 1.0f);
|
||||
}
|
||||
const std::string tagName = ToStdString(node.source->tagName);
|
||||
const Color baseColor = ResolveStyledColor(
|
||||
node,
|
||||
Style::UIStylePropertyId::BackgroundColor,
|
||||
ResolveLegacyBaseBackgroundColor(*node.source));
|
||||
if (tagName == "Button") {
|
||||
if (state.active || state.capture) {
|
||||
return Color(0.30f, 0.30f, 0.30f, 1.0f);
|
||||
return AdjustColorBrightness(baseColor, 0.06f);
|
||||
}
|
||||
if (state.hovered) {
|
||||
return Color(0.27f, 0.27f, 0.27f, 1.0f);
|
||||
return AdjustColorBrightness(baseColor, 0.03f);
|
||||
}
|
||||
return Color(0.24f, 0.24f, 0.24f, 1.0f);
|
||||
}
|
||||
|
||||
return Color(0.16f, 0.16f, 0.16f, 1.0f);
|
||||
return baseColor;
|
||||
}
|
||||
|
||||
Color ResolveBorderColor(
|
||||
const UIDocumentNode& node,
|
||||
const RuntimeLayoutNode& node,
|
||||
const RuntimeNodeVisualState& state) {
|
||||
const Color baseColor = ResolveStyledColor(
|
||||
node,
|
||||
Style::UIStylePropertyId::BorderColor,
|
||||
ResolveLegacyBaseBorderColor(*node.source));
|
||||
if (state.capture) {
|
||||
return Color(0.82f, 0.82f, 0.82f, 1.0f);
|
||||
return AdjustColorBrightness(baseColor, 0.40f);
|
||||
}
|
||||
|
||||
if (state.focused || state.active) {
|
||||
return Color(0.62f, 0.62f, 0.62f, 1.0f);
|
||||
return AdjustColorBrightness(baseColor, 0.24f);
|
||||
}
|
||||
|
||||
if (state.hovered) {
|
||||
return Color(0.45f, 0.45f, 0.45f, 1.0f);
|
||||
return AdjustColorBrightness(baseColor, 0.12f);
|
||||
}
|
||||
|
||||
const std::string tone = GetAttribute(node, "tone");
|
||||
if (tone == "accent") {
|
||||
return Color(0.42f, 0.42f, 0.42f, 1.0f);
|
||||
}
|
||||
if (tone == "accent-alt") {
|
||||
return Color(0.34f, 0.34f, 0.34f, 1.0f);
|
||||
}
|
||||
|
||||
return Color(0.30f, 0.30f, 0.30f, 1.0f);
|
||||
return baseColor;
|
||||
}
|
||||
|
||||
float ResolveBorderThickness(const RuntimeNodeVisualState& state) {
|
||||
return (state.focused || state.active || state.capture) ? 2.0f : 1.0f;
|
||||
float ResolveBorderThickness(
|
||||
const RuntimeLayoutNode& node,
|
||||
const RuntimeNodeVisualState& state) {
|
||||
const float baseThickness = ResolveNodeBorderWidth(node, 1.0f);
|
||||
if (state.focused || state.active || state.capture) {
|
||||
return (std::max)(baseThickness, 2.0f);
|
||||
}
|
||||
|
||||
return baseThickness;
|
||||
}
|
||||
|
||||
Color ResolveForegroundColor(
|
||||
const RuntimeLayoutNode& node,
|
||||
const RuntimeNodeVisualState& state,
|
||||
const Color& fallback) {
|
||||
Color color = ResolveStyledColor(node, Style::UIStylePropertyId::ForegroundColor, fallback);
|
||||
if (ToStdString(node.source->tagName) == "Button" &&
|
||||
(state.capture || state.focused || state.active)) {
|
||||
color = AdjustColorBrightness(color, 0.08f);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
bool IsPathTarget(
|
||||
@@ -1360,6 +1531,7 @@ RuntimeNodeVisualState ResolveNodeVisualState(
|
||||
|
||||
RuntimeLayoutNode BuildLayoutTree(
|
||||
const UIDocumentNode& source,
|
||||
const UIScreenDocument& document,
|
||||
const std::string& parentStateKey,
|
||||
const UIInputPath& parentInputPath,
|
||||
std::size_t siblingIndex,
|
||||
@@ -1388,10 +1560,19 @@ RuntimeLayoutNode BuildLayoutTree(
|
||||
ParseRatioAttribute(source, "ratio", 0.5f));
|
||||
node.shortcutScopeRoot = ParseShortcutScopeRoot(source);
|
||||
node.hasShortcutBinding = TryBuildShortcutBinding(source, node.elementId, node.shortcutBinding);
|
||||
node.localStyle = Style::BuildInlineStyle(source);
|
||||
Style::UIStyleResolveContext styleContext = {};
|
||||
styleContext.theme = document.GetRuntimeTheme();
|
||||
styleContext.styleSheet = document.GetRuntimeStyleSheet();
|
||||
styleContext.selector.typeName = tagName;
|
||||
styleContext.selector.styleName = GetAttribute(source, "style");
|
||||
styleContext.localStyle = &node.localStyle;
|
||||
node.resolvedStyle = Style::ResolveStyle(styleContext);
|
||||
node.children.reserve(source.children.Size());
|
||||
for (std::size_t index = 0; index < source.children.Size(); ++index) {
|
||||
node.children.push_back(BuildLayoutTree(
|
||||
source.children[index],
|
||||
document,
|
||||
node.stateKey,
|
||||
node.inputPath,
|
||||
index,
|
||||
@@ -1417,17 +1598,22 @@ UISize MeasureNode(RuntimeLayoutNode& node) {
|
||||
|
||||
if (tagName == "Text") {
|
||||
const std::string text = ResolveNodeText(source);
|
||||
const float fontSize = ResolveNodeFontSize(node, kDefaultFontSize);
|
||||
node.desiredSize = UISize(
|
||||
MeasureTextWidth(text, kDefaultFontSize),
|
||||
MeasureTextHeight(kDefaultFontSize));
|
||||
MeasureTextWidth(text, fontSize),
|
||||
MeasureTextHeight(fontSize));
|
||||
node.minimumSize = node.desiredSize;
|
||||
return node.desiredSize;
|
||||
}
|
||||
|
||||
if (!IsContainerTag(source)) {
|
||||
const float fontSize = ResolveNodeFontSize(
|
||||
node,
|
||||
tagName == "Button" ? kButtonFontSize : kDefaultFontSize);
|
||||
const Layout::UILayoutThickness padding = ResolveNodePadding(node, 12.0f);
|
||||
node.desiredSize = UISize(
|
||||
(std::max)(160.0f, MeasureTextWidth(ResolveNodeText(source), kDefaultFontSize) + 24.0f),
|
||||
44.0f);
|
||||
(std::max)(160.0f, MeasureTextWidth(ResolveNodeText(source), fontSize) + padding.Horizontal()),
|
||||
(std::max)(44.0f, MeasureTextHeight(fontSize) + padding.Vertical()));
|
||||
node.minimumSize = node.desiredSize;
|
||||
return node.desiredSize;
|
||||
}
|
||||
@@ -1532,12 +1718,11 @@ UISize MeasureNode(RuntimeLayoutNode& node) {
|
||||
options.axis = IsHorizontalTag(tagName)
|
||||
? Layout::UILayoutAxis::Horizontal
|
||||
: Layout::UILayoutAxis::Vertical;
|
||||
options.spacing = ParseFloatAttribute(
|
||||
source,
|
||||
"gap",
|
||||
options.spacing = ResolveNodeGap(
|
||||
node,
|
||||
options.axis == Layout::UILayoutAxis::Horizontal ? 10.0f : 8.0f);
|
||||
options.padding = ParsePadding(
|
||||
source,
|
||||
options.padding = ResolveNodePadding(
|
||||
node,
|
||||
tagName == "View" ? 16.0f : 12.0f);
|
||||
|
||||
std::vector<Layout::UILayoutItem> desiredItems = {};
|
||||
@@ -1676,12 +1861,11 @@ void ArrangeNode(
|
||||
options.axis = IsHorizontalTag(tagName)
|
||||
? Layout::UILayoutAxis::Horizontal
|
||||
: Layout::UILayoutAxis::Vertical;
|
||||
options.spacing = ParseFloatAttribute(
|
||||
source,
|
||||
"gap",
|
||||
options.spacing = ResolveNodeGap(
|
||||
node,
|
||||
options.axis == Layout::UILayoutAxis::Horizontal ? 10.0f : 8.0f);
|
||||
options.padding = ParsePadding(
|
||||
source,
|
||||
options.padding = ResolveNodePadding(
|
||||
node,
|
||||
tagName == "View" ? 16.0f : 12.0f);
|
||||
|
||||
const float headerHeight = node.isTab ? 0.0f : MeasureHeaderHeight(source);
|
||||
@@ -2414,15 +2598,16 @@ void EmitNode(
|
||||
++stats.nodeCount;
|
||||
|
||||
if (tagName == "View" || tagName == "Card" || tagName == "Button") {
|
||||
drawList.AddFilledRect(node.rect, ToUIColor(ResolveBackgroundColor(source, visualState)), 10.0f);
|
||||
const float cornerRadius = ResolveNodeCornerRadius(node, 10.0f);
|
||||
drawList.AddFilledRect(node.rect, ToUIColor(ResolveBackgroundColor(node, visualState)), cornerRadius);
|
||||
++stats.filledRectCommandCount;
|
||||
|
||||
if (tagName != "View") {
|
||||
drawList.AddRectOutline(
|
||||
node.rect,
|
||||
ToUIColor(ResolveBorderColor(source, visualState)),
|
||||
ResolveBorderThickness(visualState),
|
||||
10.0f);
|
||||
ToUIColor(ResolveBorderColor(node, visualState)),
|
||||
ResolveBorderThickness(node, visualState),
|
||||
cornerRadius);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2539,22 +2724,27 @@ void EmitNode(
|
||||
}
|
||||
|
||||
if (tagName == "Text") {
|
||||
const float fontSize = ResolveNodeFontSize(node, kDefaultFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(node.rect.x, node.rect.y),
|
||||
ResolveNodeText(source),
|
||||
ToUIColor(Color(0.92f, 0.94f, 0.97f, 1.0f)),
|
||||
kDefaultFontSize);
|
||||
ToUIColor(ResolveForegroundColor(node, visualState, Color(0.92f, 0.94f, 0.97f, 1.0f))),
|
||||
fontSize);
|
||||
++stats.textCommandCount;
|
||||
}
|
||||
|
||||
if (tagName == "Button" && title.empty() && subtitle.empty()) {
|
||||
const float fontSize = ResolveNodeFontSize(node, kButtonFontSize);
|
||||
drawList.AddText(
|
||||
UIPoint(node.rect.x + 12.0f, ComputeCenteredTextTop(node.rect, kButtonFontSize)),
|
||||
UIPoint(node.rect.x + 12.0f, ComputeCenteredTextTop(node.rect, fontSize)),
|
||||
ResolveNodeText(source),
|
||||
ToUIColor(visualState.capture || visualState.focused
|
||||
? Color(1.0f, 1.0f, 1.0f, 1.0f)
|
||||
: Color(0.95f, 0.97f, 1.0f, 1.0f)),
|
||||
kButtonFontSize);
|
||||
ToUIColor(ResolveForegroundColor(
|
||||
node,
|
||||
visualState,
|
||||
visualState.capture || visualState.focused
|
||||
? Color(1.0f, 1.0f, 1.0f, 1.0f)
|
||||
: Color(0.95f, 0.97f, 1.0f, 1.0f))),
|
||||
fontSize);
|
||||
++stats.textCommandCount;
|
||||
}
|
||||
|
||||
@@ -2667,6 +2857,19 @@ UIScreenLoadResult UIDocumentScreenHost::LoadScreen(const UIScreenAsset& asset)
|
||||
|
||||
result.document.themeDocument = themeResult.document;
|
||||
result.document.hasThemeDocument = true;
|
||||
std::string runtimeThemeError = {};
|
||||
if (!Style::TryCompileDocumentStyle(
|
||||
result.document.themeDocument,
|
||||
result.document.runtimeTheme,
|
||||
result.document.runtimeStyleSheet,
|
||||
&runtimeThemeError)) {
|
||||
result = {};
|
||||
result.errorMessage = runtimeThemeError.empty()
|
||||
? "Failed to compile runtime UI theme styles."
|
||||
: runtimeThemeError;
|
||||
return result;
|
||||
}
|
||||
result.document.hasRuntimeTheme = true;
|
||||
if (seenDependencies.insert(asset.themePath).second) {
|
||||
result.document.dependencies.push_back(asset.themePath);
|
||||
}
|
||||
@@ -2693,6 +2896,7 @@ UIScreenFrameResult UIDocumentScreenHost::BuildFrame(
|
||||
: document.sourcePath;
|
||||
RuntimeLayoutNode root = BuildLayoutTree(
|
||||
document.viewDocument.rootNode,
|
||||
document,
|
||||
stateRoot,
|
||||
UIInputPath(),
|
||||
0u,
|
||||
|
||||
598
engine/src/UI/Style/DocumentStyleCompiler.cpp
Normal file
598
engine/src/UI/Style/DocumentStyleCompiler.cpp
Normal file
@@ -0,0 +1,598 @@
|
||||
#include <XCEngine/UI/Style/DocumentStyleCompiler.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace UI {
|
||||
namespace Style {
|
||||
|
||||
namespace {
|
||||
|
||||
using XCEngine::Math::Color;
|
||||
using XCEngine::Resources::UIDocumentAttribute;
|
||||
using XCEngine::Resources::UIDocumentModel;
|
||||
using XCEngine::Resources::UIDocumentNode;
|
||||
|
||||
std::string ToStdString(const Containers::String& value) {
|
||||
return value.Empty() || value.CStr() == nullptr
|
||||
? std::string()
|
||||
: std::string(value.CStr());
|
||||
}
|
||||
|
||||
std::string TrimAscii(std::string value) {
|
||||
std::size_t start = 0u;
|
||||
while (start < value.size() &&
|
||||
std::isspace(static_cast<unsigned char>(value[start])) != 0) {
|
||||
++start;
|
||||
}
|
||||
|
||||
std::size_t end = value.size();
|
||||
while (end > start &&
|
||||
std::isspace(static_cast<unsigned char>(value[end - 1u])) != 0) {
|
||||
--end;
|
||||
}
|
||||
|
||||
return value.substr(start, end - start);
|
||||
}
|
||||
|
||||
std::string ToLowerAscii(std::string value) {
|
||||
std::transform(
|
||||
value.begin(),
|
||||
value.end(),
|
||||
value.begin(),
|
||||
[](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
const UIDocumentAttribute* FindAttribute(const UIDocumentNode& node, const char* name) {
|
||||
for (const UIDocumentAttribute& attribute : node.attributes) {
|
||||
if (attribute.name == name) {
|
||||
return &attribute;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string GetAttribute(
|
||||
const UIDocumentNode& node,
|
||||
const char* name,
|
||||
const std::string& fallback = {}) {
|
||||
const UIDocumentAttribute* attribute = FindAttribute(node, name);
|
||||
return attribute != nullptr ? ToStdString(attribute->value) : fallback;
|
||||
}
|
||||
|
||||
bool TryParseFloat(const std::string& text, float& outValue) {
|
||||
const std::string trimmed = TrimAscii(text);
|
||||
if (trimmed.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char* end = nullptr;
|
||||
const float value = std::strtof(trimmed.c_str(), &end);
|
||||
if (end == trimmed.c_str()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while (*end != '\0') {
|
||||
if (std::isspace(static_cast<unsigned char>(*end)) == 0) {
|
||||
return false;
|
||||
}
|
||||
++end;
|
||||
}
|
||||
|
||||
outValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
int HexToInt(char ch) {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
return ch - '0';
|
||||
}
|
||||
if (ch >= 'a' && ch <= 'f') {
|
||||
return 10 + (ch - 'a');
|
||||
}
|
||||
if (ch >= 'A' && ch <= 'F') {
|
||||
return 10 + (ch - 'A');
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool TryParseHexByte(std::string_view text, std::uint8_t& outValue) {
|
||||
if (text.size() != 2u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int high = HexToInt(text[0]);
|
||||
const int low = HexToInt(text[1]);
|
||||
if (high < 0 || low < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = static_cast<std::uint8_t>((high << 4) | low);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryParseColorValue(const std::string& text, Color& outColor) {
|
||||
const std::string trimmed = TrimAscii(text);
|
||||
if (trimmed.size() != 7u && trimmed.size() != 9u) {
|
||||
return false;
|
||||
}
|
||||
if (trimmed.front() != '#') {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::uint8_t r = 0u;
|
||||
std::uint8_t g = 0u;
|
||||
std::uint8_t b = 0u;
|
||||
std::uint8_t a = 255u;
|
||||
if (!TryParseHexByte(std::string_view(trimmed).substr(1u, 2u), r) ||
|
||||
!TryParseHexByte(std::string_view(trimmed).substr(3u, 2u), g) ||
|
||||
!TryParseHexByte(std::string_view(trimmed).substr(5u, 2u), b)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (trimmed.size() == 9u &&
|
||||
!TryParseHexByte(std::string_view(trimmed).substr(7u, 2u), a)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr float kInv255 = 1.0f / 255.0f;
|
||||
outColor = Color(
|
||||
static_cast<float>(r) * kInv255,
|
||||
static_cast<float>(g) * kInv255,
|
||||
static_cast<float>(b) * kInv255,
|
||||
static_cast<float>(a) * kInv255);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryParseFloatList(const std::string& text, std::vector<float>& outValues) {
|
||||
outValues.clear();
|
||||
|
||||
std::string token = {};
|
||||
auto flushToken = [&]() {
|
||||
if (token.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
float value = 0.0f;
|
||||
if (!TryParseFloat(token, value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValues.push_back(value);
|
||||
token.clear();
|
||||
return true;
|
||||
};
|
||||
|
||||
for (char ch : text) {
|
||||
if (std::isspace(static_cast<unsigned char>(ch)) != 0 ||
|
||||
ch == ',' ||
|
||||
ch == ';') {
|
||||
if (!flushToken()) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
token.push_back(ch);
|
||||
}
|
||||
|
||||
return flushToken() && !outValues.empty();
|
||||
}
|
||||
|
||||
bool TryParseThicknessValue(const std::string& text, UIThickness& outThickness) {
|
||||
std::vector<float> values = {};
|
||||
if (!TryParseFloatList(text, values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (values.size() == 1u) {
|
||||
outThickness = UIThickness::Uniform(values[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (values.size() == 2u) {
|
||||
const float vertical = values[0];
|
||||
const float horizontal = values[1];
|
||||
outThickness = UIThickness{ horizontal, vertical, horizontal, vertical };
|
||||
return true;
|
||||
}
|
||||
|
||||
if (values.size() == 4u) {
|
||||
const float top = values[0];
|
||||
const float right = values[1];
|
||||
const float bottom = values[2];
|
||||
const float left = values[3];
|
||||
outThickness = UIThickness{ left, top, right, bottom };
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryParseCornerRadiusValue(const std::string& text, UICornerRadius& outRadius) {
|
||||
std::vector<float> values = {};
|
||||
if (!TryParseFloatList(text, values)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (values.size() == 1u) {
|
||||
outRadius = UICornerRadius::Uniform(values[0]);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (values.size() == 2u) {
|
||||
outRadius = UICornerRadius{ values[0], values[1], values[0], values[1] };
|
||||
return true;
|
||||
}
|
||||
|
||||
if (values.size() == 4u) {
|
||||
outRadius = UICornerRadius{ values[0], values[1], values[2], values[3] };
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryMapPropertyName(const std::string& name, UIStylePropertyId& outPropertyId) {
|
||||
const std::string normalized = ToLowerAscii(TrimAscii(name));
|
||||
if (normalized == "background" || normalized == "backgroundcolor") {
|
||||
outPropertyId = UIStylePropertyId::BackgroundColor;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "foreground" || normalized == "foregroundcolor" || normalized == "textcolor") {
|
||||
outPropertyId = UIStylePropertyId::ForegroundColor;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "bordercolor") {
|
||||
outPropertyId = UIStylePropertyId::BorderColor;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "borderwidth") {
|
||||
outPropertyId = UIStylePropertyId::BorderWidth;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "radius" || normalized == "cornerradius") {
|
||||
outPropertyId = UIStylePropertyId::CornerRadius;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "padding") {
|
||||
outPropertyId = UIStylePropertyId::Padding;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "gap" || normalized == "spacing") {
|
||||
outPropertyId = UIStylePropertyId::Gap;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "fontsize") {
|
||||
outPropertyId = UIStylePropertyId::FontSize;
|
||||
return true;
|
||||
}
|
||||
if (normalized == "linewidth") {
|
||||
outPropertyId = UIStylePropertyId::LineWidth;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryParseTokenReferenceValue(const std::string& text, UIStyleValue& outValue) {
|
||||
const std::string trimmed = TrimAscii(text);
|
||||
if (trimmed.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = UIStyleValue::Token(trimmed);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryParsePropertyValue(
|
||||
UIStylePropertyId propertyId,
|
||||
const std::string& text,
|
||||
UIStyleValue& outValue) {
|
||||
const std::string trimmed = TrimAscii(text);
|
||||
if (trimmed.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const UIStyleValueType expectedType = GetExpectedValueType(propertyId);
|
||||
switch (expectedType) {
|
||||
case UIStyleValueType::Color: {
|
||||
Color color = {};
|
||||
if (TryParseColorValue(trimmed, color)) {
|
||||
outValue = UIStyleValue(color);
|
||||
return true;
|
||||
}
|
||||
return TryParseTokenReferenceValue(trimmed, outValue);
|
||||
}
|
||||
case UIStyleValueType::Float: {
|
||||
float value = 0.0f;
|
||||
if (TryParseFloat(trimmed, value)) {
|
||||
outValue = UIStyleValue(value);
|
||||
return true;
|
||||
}
|
||||
return TryParseTokenReferenceValue(trimmed, outValue);
|
||||
}
|
||||
case UIStyleValueType::Thickness: {
|
||||
UIThickness thickness = {};
|
||||
if (TryParseThicknessValue(trimmed, thickness)) {
|
||||
outValue = UIStyleValue(thickness);
|
||||
return true;
|
||||
}
|
||||
|
||||
float uniform = 0.0f;
|
||||
if (TryParseFloat(trimmed, uniform)) {
|
||||
outValue = UIStyleValue(uniform);
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryParseTokenReferenceValue(trimmed, outValue);
|
||||
}
|
||||
case UIStyleValueType::CornerRadius: {
|
||||
UICornerRadius radius = {};
|
||||
if (TryParseCornerRadiusValue(trimmed, radius)) {
|
||||
outValue = UIStyleValue(radius);
|
||||
return true;
|
||||
}
|
||||
|
||||
float uniform = 0.0f;
|
||||
if (TryParseFloat(trimmed, uniform)) {
|
||||
outValue = UIStyleValue(uniform);
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryParseTokenReferenceValue(trimmed, outValue);
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool TryParseThemeTokenValue(const UIDocumentNode& tokenNode, UIStyleValue& outValue) {
|
||||
const std::string tokenTag = ToLowerAscii(ToStdString(tokenNode.tagName));
|
||||
const std::string valueText = GetAttribute(tokenNode, "value");
|
||||
if (valueText.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (tokenTag == "color") {
|
||||
Color color = {};
|
||||
if (TryParseColorValue(valueText, color)) {
|
||||
outValue = UIStyleValue(color);
|
||||
return true;
|
||||
}
|
||||
return TryParseTokenReferenceValue(valueText, outValue);
|
||||
}
|
||||
|
||||
if (tokenTag == "spacing" || tokenTag == "float" || tokenTag == "number") {
|
||||
float value = 0.0f;
|
||||
if (TryParseFloat(valueText, value)) {
|
||||
outValue = UIStyleValue(value);
|
||||
return true;
|
||||
}
|
||||
return TryParseTokenReferenceValue(valueText, outValue);
|
||||
}
|
||||
|
||||
if (tokenTag == "radius") {
|
||||
UICornerRadius radius = {};
|
||||
if (TryParseCornerRadiusValue(valueText, radius)) {
|
||||
outValue = UIStyleValue(radius);
|
||||
return true;
|
||||
}
|
||||
|
||||
float uniform = 0.0f;
|
||||
if (TryParseFloat(valueText, uniform)) {
|
||||
outValue = UIStyleValue(uniform);
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryParseTokenReferenceValue(valueText, outValue);
|
||||
}
|
||||
|
||||
if (tokenTag == "padding" || tokenTag == "thickness") {
|
||||
UIThickness thickness = {};
|
||||
if (TryParseThicknessValue(valueText, thickness)) {
|
||||
outValue = UIStyleValue(thickness);
|
||||
return true;
|
||||
}
|
||||
|
||||
float uniform = 0.0f;
|
||||
if (TryParseFloat(valueText, uniform)) {
|
||||
outValue = UIStyleValue(uniform);
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryParseTokenReferenceValue(valueText, outValue);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
UIStyleSet& SelectStyleSetForWidget(
|
||||
const UIDocumentNode& widgetNode,
|
||||
UIStyleSheet& styleSheet) {
|
||||
const std::string styleName = TrimAscii(GetAttribute(widgetNode, "style"));
|
||||
if (!styleName.empty()) {
|
||||
if (ToLowerAscii(styleName) == "default") {
|
||||
return styleSheet.DefaultStyle();
|
||||
}
|
||||
return styleSheet.GetOrCreateNamedStyle(styleName);
|
||||
}
|
||||
|
||||
const std::string typeName = TrimAscii(GetAttribute(widgetNode, "type"));
|
||||
if (typeName.empty()) {
|
||||
return styleSheet.DefaultStyle();
|
||||
}
|
||||
|
||||
const std::string normalized = ToLowerAscii(typeName);
|
||||
if (normalized == "*" || normalized == "default") {
|
||||
return styleSheet.DefaultStyle();
|
||||
}
|
||||
|
||||
return styleSheet.GetOrCreateTypeStyle(typeName);
|
||||
}
|
||||
|
||||
bool ParseTokensNode(
|
||||
const UIDocumentNode& tokensNode,
|
||||
UIThemeDefinition& outDefinition,
|
||||
std::string& outErrorMessage) {
|
||||
for (const UIDocumentNode& tokenNode : tokensNode.children) {
|
||||
const std::string name = TrimAscii(GetAttribute(tokenNode, "name"));
|
||||
if (name.empty()) {
|
||||
outErrorMessage = "Theme token is missing required 'name' attribute.";
|
||||
return false;
|
||||
}
|
||||
|
||||
UIStyleValue value = {};
|
||||
if (!TryParseThemeTokenValue(tokenNode, value)) {
|
||||
outErrorMessage = "Theme token '" + name + "' has an unsupported value.";
|
||||
return false;
|
||||
}
|
||||
|
||||
outDefinition.SetToken(name, value);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseWidgetsNode(
|
||||
const UIDocumentNode& widgetsNode,
|
||||
UIStyleSheet& outStyleSheet,
|
||||
std::string& outErrorMessage) {
|
||||
for (const UIDocumentNode& widgetNode : widgetsNode.children) {
|
||||
if (widgetNode.tagName != "Widget") {
|
||||
outErrorMessage = "Theme <Widgets> only supports <Widget> children.";
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string styleName = TrimAscii(GetAttribute(widgetNode, "style"));
|
||||
const std::string typeName = TrimAscii(GetAttribute(widgetNode, "type"));
|
||||
if (styleName.empty() && typeName.empty()) {
|
||||
outErrorMessage = "Theme <Widget> must declare either 'type' or 'style'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
UIStyleSet& styleSet = SelectStyleSetForWidget(widgetNode, outStyleSheet);
|
||||
for (const UIDocumentNode& propertyNode : widgetNode.children) {
|
||||
if (propertyNode.tagName != "Property") {
|
||||
outErrorMessage = "Theme <Widget> only supports <Property> children.";
|
||||
return false;
|
||||
}
|
||||
|
||||
UIStylePropertyId propertyId = UIStylePropertyId::BackgroundColor;
|
||||
const std::string propertyName = GetAttribute(propertyNode, "name");
|
||||
if (!TryMapPropertyName(propertyName, propertyId)) {
|
||||
outErrorMessage = "Theme property '" + propertyName + "' is unsupported.";
|
||||
return false;
|
||||
}
|
||||
|
||||
UIStyleValue value = {};
|
||||
if (!TryParsePropertyValue(propertyId, GetAttribute(propertyNode, "value"), value)) {
|
||||
outErrorMessage = "Theme property '" + propertyName + "' has an unsupported value.";
|
||||
return false;
|
||||
}
|
||||
|
||||
styleSet.SetProperty(propertyId, value);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
UIDocumentStyleCompileResult CompileDocumentStyle(const UIDocumentModel& themeDocument) {
|
||||
UIDocumentStyleCompileResult result = {};
|
||||
result.succeeded = TryCompileDocumentStyle(
|
||||
themeDocument,
|
||||
result.theme,
|
||||
result.styleSheet,
|
||||
&result.errorMessage);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TryCompileDocumentStyle(
|
||||
const UIDocumentModel& themeDocument,
|
||||
UITheme& outTheme,
|
||||
UIStyleSheet& outStyleSheet,
|
||||
std::string* outErrorMessage) {
|
||||
outTheme = {};
|
||||
outStyleSheet = {};
|
||||
if (outErrorMessage != nullptr) {
|
||||
outErrorMessage->clear();
|
||||
}
|
||||
|
||||
if (!themeDocument.valid || themeDocument.rootNode.tagName.Empty()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "Theme document is invalid.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (themeDocument.rootNode.tagName != "Theme") {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "Theme document root tag must be <Theme>.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UIThemeDefinition definition = {};
|
||||
definition.name = GetAttribute(themeDocument.rootNode, "name");
|
||||
|
||||
std::string errorMessage = {};
|
||||
for (const UIDocumentNode& childNode : themeDocument.rootNode.children) {
|
||||
if (childNode.tagName == "Tokens") {
|
||||
if (!ParseTokensNode(childNode, definition, errorMessage)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = errorMessage;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (childNode.tagName == "Widgets") {
|
||||
if (!ParseWidgetsNode(childNode, outStyleSheet, errorMessage)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = errorMessage;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outTheme = BuildTheme(definition);
|
||||
return true;
|
||||
}
|
||||
|
||||
UIStyleSet BuildInlineStyle(const UIDocumentNode& node) {
|
||||
UIStyleSet localStyle = {};
|
||||
for (const UIDocumentAttribute& attribute : node.attributes) {
|
||||
UIStylePropertyId propertyId = UIStylePropertyId::BackgroundColor;
|
||||
if (!TryMapPropertyName(ToStdString(attribute.name), propertyId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
UIStyleValue value = {};
|
||||
if (!TryParsePropertyValue(propertyId, ToStdString(attribute.value), value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
localStyle.SetProperty(propertyId, value);
|
||||
}
|
||||
|
||||
return localStyle;
|
||||
}
|
||||
|
||||
} // namespace Style
|
||||
} // namespace UI
|
||||
} // namespace XCEngine
|
||||
@@ -6,6 +6,36 @@ namespace Style {
|
||||
|
||||
namespace {
|
||||
|
||||
bool TryCoerceResolvedValue(
|
||||
const UIStyleValue& assignedValue,
|
||||
UIStyleValueType expectedType,
|
||||
UIStyleValue& outValue) {
|
||||
if (!assignedValue.IsSet()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (expectedType == UIStyleValueType::None || assignedValue.GetType() == expectedType) {
|
||||
outValue = assignedValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (expectedType == UIStyleValueType::Thickness) {
|
||||
if (const float* uniform = assignedValue.TryGetFloat()) {
|
||||
outValue = UIStyleValue(UIThickness::Uniform(*uniform));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (expectedType == UIStyleValueType::CornerRadius) {
|
||||
if (const float* uniform = assignedValue.TryGetFloat()) {
|
||||
outValue = UIStyleValue(UICornerRadius::Uniform(*uniform));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryResolveAssignedValue(
|
||||
const UIStyleValue& assignedValue,
|
||||
UIStyleValueType expectedType,
|
||||
@@ -20,21 +50,15 @@ bool TryResolveAssignedValue(
|
||||
return false;
|
||||
}
|
||||
|
||||
const UITokenResolveResult tokenResult = theme->ResolveToken(tokenReference->name, expectedType);
|
||||
const UITokenResolveResult tokenResult = theme->ResolveToken(tokenReference->name, UIStyleValueType::None);
|
||||
if (tokenResult.status != UITokenResolveStatus::Resolved) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = tokenResult.value;
|
||||
return true;
|
||||
return TryCoerceResolvedValue(tokenResult.value, expectedType, outValue);
|
||||
}
|
||||
|
||||
if (expectedType != UIStyleValueType::None && assignedValue.GetType() != expectedType) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = assignedValue;
|
||||
return true;
|
||||
return TryCoerceResolvedValue(assignedValue, expectedType, outValue);
|
||||
}
|
||||
|
||||
const UIStyleSet* GetStyleSetForLayer(UIStyleLayer layer, const UIStyleResolveContext& context) {
|
||||
|
||||
Reference in New Issue
Block a user