Skip to content

Commit

Permalink
preview: Enable control template preview #12
Browse files Browse the repository at this point in the history
  • Loading branch information
roxk committed Apr 17, 2024
1 parent 0ea24fd commit c059628
Show file tree
Hide file tree
Showing 6 changed files with 258 additions and 107 deletions.
122 changes: 15 additions & 107 deletions WinUI3XamlPreview/WinUI3XamlPreview/MainPage.xaml.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@
#include "MainPage.g.cpp"
#endif
#include <winrt/Windows.Storage.Streams.h>
#include <winrt/Microsoft.UI.Text.h>
#include <winrt/Windows.Data.Xml.Dom.h>
#include "Preview.h"
#include <regex>
#include "XamlProcessor.h"

using namespace winrt;
using namespace std::string_view_literals;
namespace mut = winrt::Microsoft::UI::Text;
namespace wdxd = Windows::Data::Xml::Dom;

double GetScaleComboBoxSelectedScalePercentage(muxc::ComboBox const& comboBox)
{
Expand All @@ -23,101 +21,6 @@ double GetScaleComboBoxSelectedScalePercentage(muxc::ComboBox const& comboBox)

namespace winrt::WinUI3XamlPreview::implementation
{
bool IsAttrValid(wdxd::XmlDocument const& doc, wdxd::XmlElement const& element, wdxd::XmlAttribute const& attr)
{
auto copied = element.CloneNode(false);
auto attributes = copied.Attributes();
while (true)
{
auto iter = attributes.First();
if (!iter.HasCurrent())
{
break;
}
auto attrNode = iter.Current();
if (attrNode.NodeType() != wdxd::NodeType::AttributeNode)
{
return false;
}
auto victim = attrNode.try_as<wdxd::XmlAttribute>();
if (victim == nullptr)
{
return false;
}
attributes.RemoveNamedItem(victim.as<wdxd::XmlAttribute>().Name());
}
auto defaultNsAttr = doc.CreateAttribute(L"xmlns");
defaultNsAttr.Value(L"http://schemas.microsoft.com/winfx/2006/xaml/presentation");
attributes.SetNamedItem(defaultNsAttr);
auto copiedAttr = attr.CloneNode(true);
auto namespaceUri = attr.NamespaceUri().try_as<winrt::hstring>();
if (namespaceUri == L"")
{
attributes.SetNamedItem(copiedAttr);
}
else
{
attributes.SetNamedItemNS(copiedAttr);
}
auto xml = copied.GetXml();
try
{
muxm::XamlReader::Load(xml);
return true;
}
catch (...)
{
return false;
}
}
void VisitAndTrim(wdxd::XmlDocument const& doc, wdxd::IXmlNode const& candidate)
{
for (auto&& node : candidate.ChildNodes())
{
auto element = node.try_as<wdxd::XmlElement>();
if (element == nullptr)
{
continue;
}
if (element.NodeName() == L"Window")
{
auto border = doc.CreateElement(L"Border");
auto children = element.ChildNodes();
for (auto&& child : children)
{
border.AppendChild(child.CloneNode(true));
}
auto attributes = element.Attributes();
for (auto&& attrNode : attributes)
{
border.SetAttributeNode(attrNode.CloneNode(true).as<wdxd::XmlAttribute>());
}
candidate.RemoveChild(element);
candidate.AppendChild(border);
element = border;
}
auto attributes = element.Attributes();
for (int i = 0; i < attributes.Size(); ++i)
{
auto attrNode = attributes.GetAt(i);
if (attrNode.NodeType() != wdxd::NodeType::AttributeNode)
{
continue;
}
auto attr = attrNode.try_as<wdxd::XmlAttribute>();
if (attr == nullptr)
{
continue;
}
auto isAttrValid = IsAttrValid(doc, element, attr);
if (!isAttrValid)
{
element.RemoveAttributeNode(attr);
}
}
VisitAndTrim(doc, element);
}
}
MainPage::MainPage()
{
_filePathChangedToken = Preview::InstanceInternal()->FilePathChanged({get_weak(), &MainPage::OnFilePathChanged});
Expand All @@ -138,17 +41,22 @@ namespace winrt::WinUI3XamlPreview::implementation
{
try
{
wdxd::XmlDocument doc;
doc.LoadXml(xaml);
VisitAndTrim(doc, doc);
auto trimmedXaml = doc.GetXml();
auto tree = muxm::XamlReader::Load(trimmedXaml);
auto element = tree.try_as<mux::UIElement>();
if (element == nullptr)
auto processResult{ XamlProcessor::ProcessXaml(xaml) };
if (auto singleElement = std::get_if<SingleElement>(&processResult); singleElement != nullptr)
{
elementWrapper().Child(singleElement->element);
}
else if (auto multipleElement = std::get_if<MultipleElement>(&processResult); multipleElement != nullptr)
{
return;
auto& elements = multipleElement->elements;
if (elements.empty())
{
toast().ShowError(L"Nothing to show",
L"ResourceDictionary doesn't contain any control template");
return;
}
elementWrapper().Child(elements.front());
}
elementWrapper().Child(element);
}
catch (...)
{
Expand Down
2 changes: 2 additions & 0 deletions WinUI3XamlPreview/WinUI3XamlPreview/WinUI3XamlPreview.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
<DependentUpon>Toast.xaml</DependentUpon>
<SubType>Code</SubType>
</ClInclude>
<ClInclude Include="XamlProcessor.h" />
</ItemGroup>
<ItemGroup>
<Page Include="MainPage.xaml">
Expand Down Expand Up @@ -151,6 +152,7 @@
<DependentUpon>Toast.xaml</DependentUpon>
<SubType>Code</SubType>
</ClCompile>
<ClCompile Include="XamlProcessor.cpp" />
</ItemGroup>
<ItemGroup>
<Midl Include="MainPage.idl">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@
<ItemGroup>
<ClCompile Include="pch.cpp" />
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="XamlProcessor.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="XamlProcessor.h" />
</ItemGroup>
<ItemGroup>
<Image Include="Assets\Wide310x150Logo.scale-200.png">
Expand Down
211 changes: 211 additions & 0 deletions WinUI3XamlPreview/WinUI3XamlPreview/XamlProcessor.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
#include "pch.h"
#include "XamlProcessor.h"

using namespace winrt;
namespace mut = winrt::Microsoft::UI::Text;
namespace wdxd = Windows::Data::Xml::Dom;

constexpr auto defaultNamespace = L"http://schemas.microsoft.com/winfx/2006/xaml/presentation";

std::vector<wdxd::XmlAttribute> GetNamespaces(wdxd::XmlDocument const& doc)
{
std::vector<wdxd::XmlAttribute> namespaces;
auto docAttributes{ doc.DocumentElement().Attributes() };
for (auto&& docAttr : docAttributes)
{
if (std::wstring_view(docAttr.NodeName())._Starts_with(L"xmlns"))
{
namespaces.emplace_back(docAttr.CloneNode(true).as<wdxd::XmlAttribute>());
}
}
return namespaces;
}
bool IsAttrValid(wdxd::XmlDocument const& doc, wdxd::XmlElement const& element, wdxd::XmlAttribute const& attr)
{
auto copied = element.CloneNode(false);
auto attributes = copied.Attributes();
while (true)
{
auto iter = attributes.First();
if (!iter.HasCurrent())
{
break;
}
auto attrNode = iter.Current();
if (attrNode.NodeType() != wdxd::NodeType::AttributeNode)
{
return false;
}
auto victim = attrNode.try_as<wdxd::XmlAttribute>();
if (victim == nullptr)
{
return false;
}
attributes.RemoveNamedItem(victim.as<wdxd::XmlAttribute>().Name());
}
auto copiedAttr = attr.CloneNode(true);
auto namespaceUri = attr.NamespaceUri().try_as<winrt::hstring>();
if (namespaceUri == L"")
{
attributes.SetNamedItem(copiedAttr);
}
else
{
attributes.SetNamedItemNS(copiedAttr);
}
auto namespaces{ GetNamespaces(doc) };
for (auto&& aNamespace : namespaces)
{
copied.Attributes().SetNamedItemNS(aNamespace);
}
auto xml = copied.GetXml();
try
{
muxm::XamlReader::Load(xml);
return true;
}
catch (...)
{
return false;
}
}
void VisitAndTrim(wdxd::XmlDocument const& doc, wdxd::IXmlNode const& candidate, std::vector<winrt::hstring>& templateTargetTypes)
{
for (auto&& node : candidate.ChildNodes())
{
auto element = node.try_as<wdxd::XmlElement>();
if (element == nullptr)
{
continue;
}
if (element.NodeName() == L"Window")
{
auto border = doc.CreateElementNS(box_value(winrt::hstring(defaultNamespace)), L"Border");
auto children = element.ChildNodes();
for (auto&& child : children)
{
border.AppendChild(child.CloneNode(true));
}
auto attributes = element.Attributes();
for (auto&& attrNode : attributes)
{
border.SetAttributeNode(attrNode.CloneNode(true).as<wdxd::XmlAttribute>());
}
candidate.RemoveChild(element);
candidate.AppendChild(border);
element = border;
}
auto attributes = element.Attributes();
// ControlTempalte is a sealed class so it's OK to just check default NS + tag name to see if it's a ControlTemplate
auto elemetName = element.LocalName().try_as<winrt::hstring>();
auto elementNamespace = element.NamespaceUri().try_as<winrt::hstring>();
auto isControlTemplate = elemetName == L"ControlTemplate" && elementNamespace == defaultNamespace;
auto isSetter = elemetName == L"Setter" && elementNamespace == defaultNamespace;
if (isSetter)
{
VisitAndTrim(doc, element, templateTargetTypes);
continue;
}
for (uint32_t i = 0; i < attributes.Size(); ++i)
{
auto attrNode = attributes.GetAt(i);
if (attrNode.NodeType() != wdxd::NodeType::AttributeNode)
{
continue;
}
auto attr = attrNode.try_as<wdxd::XmlAttribute>();
if (attr == nullptr)
{
continue;
}
auto isAttrValid = IsAttrValid(doc, element, attr);
if (!isAttrValid)
{
element.RemoveAttributeNode(attr);
}
else
{
auto name = attr.NodeName();
if (isControlTemplate && attr.Name() == L"TargetType")
{
templateTargetTypes.emplace_back(attr.Value());
}
}
}
VisitAndTrim(doc, element, templateTargetTypes);
}
}
std::pair<std::wstring_view, std::wstring_view> SplitTargetTypeToNamespaceAndLocalName(std::wstring_view targetType)
{
auto colonIndex = targetType.find(L":");
if (colonIndex == -1)
{
return { targetType.substr(0, 0), targetType};
}
return { targetType.substr(0, colonIndex), targetType.substr(colonIndex + 1) };
}
winrt::WinUI3XamlPreview::ProcessResult winrt::WinUI3XamlPreview::XamlProcessor::ProcessXaml(winrt::hstring const& xaml)
{
wdxd::XmlDocument doc;
doc.LoadXml(xaml);
std::vector<winrt::hstring> templateTargetTypes;
VisitAndTrim(doc, doc, templateTargetTypes);
auto processedXaml = doc.GetXml();
auto tree = muxm::XamlReader::Load(processedXaml);
if (auto element = tree.try_as<mux::UIElement>(); element != nullptr)
{
return SingleElement{ std::move(element) };
}
if (auto dict = tree.try_as<mux::ResourceDictionary>(); dict != nullptr)
{
if (templateTargetTypes.empty())
{
return MultipleElement{};
}
std::vector<mux::UIElement> elements;
for (auto&& targetType : templateTargetTypes)
{
auto elementRoot = doc.CreateElementNS(box_value(winrt::hstring(defaultNamespace)), L"Border");
auto namespaces{ GetNamespaces(doc) };
for (auto&& aNamespace : namespaces)
{
elementRoot.Attributes().SetNamedItem(aNamespace);
}
auto elementResource = doc.CreateElementNS(box_value(winrt::hstring(defaultNamespace)), L"Border.Resources");
elementResource.AppendChild(doc.DocumentElement().CloneNode(true));
elementRoot.AppendChild(elementResource);
auto targetTypeParts{ SplitTargetTypeToNamespaceAndLocalName(targetType) };
auto& targetTypePrefix{ targetTypeParts.first };
winrt::hstring targetTypeNamespaceUri;
for (auto&& aNamespace : namespaces)
{
if (aNamespace.LocalName().try_as<winrt::hstring>() == targetTypePrefix)
{
targetTypeNamespaceUri = aNamespace.Value();
break;
}
}
if (targetTypeNamespaceUri == L"")
{
// Can't find the namespace for the target type...
continue;
}
auto targetTypeElement = doc.CreateElementNS(box_value(targetTypeNamespaceUri), targetTypeParts.second);
elementRoot.AppendChild(targetTypeElement);
auto elementRootXaml = elementRoot.GetXml();
// TODO: Assign control template's style name
auto targetTypeTree = muxm::XamlReader::Load(elementRootXaml);
if (auto element = targetTypeTree.try_as<mux::UIElement>(); element != nullptr)
{
elements.emplace_back(std::move(element));
}
else
{
// TODO: Log?
continue;
}
}
return MultipleElement{ std::move(elements) };
}
throw hresult_not_implemented();
}
Loading

0 comments on commit c059628

Please sign in to comment.