feat(xcui): advance core and editor validation flow
This commit is contained in:
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