bloxstrap/Bloxstrap/UI/Elements/Bootstrapper/CustomDialog.Creator.cs
Matt 9d356b0b71
Some checks are pending
CI (Debug) / build (push) Waiting to run
CI (Release) / build (push) Waiting to run
CI (Release) / release (push) Blocked by required conditions
CI (Release) / release-test (push) Blocked by required conditions
Custom bootstrapper themes (#4380)
* add custom bootstrappers

* add avalonedit to licenses page

* add gif support

* add stretch & stretchdirection to images

* dont create a bitmapimage for gifs

* remove maxheight and maxwidth sets

* remove comment

* add isenabled

* add more textblock properties

* add markdowntextblocks

* update how transform elements are stored

* overhaul textbox content

* dont set fontsize if not set

* fix warnings

* add foreground property to control

* add background property to textblock

* count descendants and increase element cap

* add auto complete

* dont display completion window if there is no data

* sort schema elements and types

* make ! close the completion window

* add end tag auto complete

* fix pos being wrong

* dont treat comments as elements

* add imagebrushes

* follow same conventions as brushes

* fix exception messages

* fix them again

* update schema

* fix crash

* now it works

* wrong attribute name

* add solidcolorbrush

* move converters into a separate file

* add lineargradientbrushes

* unify handlers

* update schema

* add fake BloxstrapCustomBootstrapper

* stop adding an extra end character

* add property element auto-complete

* add title attribute to custombloxstrapbootstrapper

* add shapes

* add string translation support

* use default wpf size instead of 100x100

* update min height of window

* fix verticalalignment not working

* uncap height and width

* add effects

* move transformation handler inside frameworkelement

* fix title bar effect & transformation removal

* add more frameworkelement properties

* add layout transform

* add font properties to control

* improve window border stuff

* make sure file contents are in CRLF

* add cornerradius to progress bar

* add progressring

* Update wpfui

* update schema

* update function names

* add children check to content

* make sure only one content is defined

* add fontfamily

* update schema

* only allow file uris for images

* disable backdrop

* move text setter to textblock handler from base

* split up creator into multiple files

* turn version into a constant

* add grids

* cleanup converters

* add IgnoreTitleBarInset

* add Version to schema

* reveal custom bootstrapper stuff on selection

* increase listbox height

* only set statustext binding in textblock

* update ui

* rename ZIndex to Panel.ZIndex

* add stackpanel

* add border

* fix being unable to apply transforms on grids

* rearrange and add new editor button

* use snackbars for saving

* add close confirmation message

* use viewmodel variable

* remove pointless onpropertychanged call

* add version string format

* start editor window in the centre

* update licenses page

also resized the about window so everything could fit nicely

* fix border not inheriting frameworkelement

* add WindowCornerPreference

* add the import dialog

* add an export theme button

* update version number

* localise CustomDialog exceptions

* localise custom theme editor

* localise custom theme add dialog

* localise frontend

* localise appearance menu page

* change customtheme error strings namespace

* change icons on appearance page

* update button margin on appearance page
2025-03-11 19:18:54 +00:00

152 lines
5.9 KiB
C#

using System.Windows;
using System.Xml.Linq;
namespace Bloxstrap.UI.Elements.Bootstrapper
{
public partial class CustomDialog
{
const int Version = 1;
private class DummyFrameworkElement : FrameworkElement { }
private const int MaxElements = 100;
private bool _initialised = false;
// prevent users from creating elements with the same name multiple times
private List<string> UsedNames { get; } = new List<string>();
private string ThemeDir { get; set; } = "";
delegate object HandleXmlElementDelegate(CustomDialog dialog, XElement xmlElement);
private static Dictionary<string, HandleXmlElementDelegate> _elementHandlerMap = new Dictionary<string, HandleXmlElementDelegate>()
{
["BloxstrapCustomBootstrapper"] = HandleXmlElement_BloxstrapCustomBootstrapper_Fake,
["TitleBar"] = HandleXmlElement_TitleBar,
["Button"] = HandleXmlElement_Button,
["ProgressBar"] = HandleXmlElement_ProgressBar,
["ProgressRing"] = HandleXmlElement_ProgressRing,
["TextBlock"] = HandleXmlElement_TextBlock,
["MarkdownTextBlock"] = HandleXmlElement_MarkdownTextBlock,
["Image"] = HandleXmlElement_Image,
["Grid"] = HandleXmlElement_Grid,
["StackPanel"] = HandleXmlElement_StackPanel,
["Border"] = HandleXmlElement_Border,
["SolidColorBrush"] = HandleXmlElement_SolidColorBrush,
["ImageBrush"] = HandleXmlElement_ImageBrush,
["LinearGradientBrush"] = HandleXmlElement_LinearGradientBrush,
["GradientStop"] = HandleXmlElement_GradientStop,
["ScaleTransform"] = HandleXmlElement_ScaleTransform,
["SkewTransform"] = HandleXmlElement_SkewTransform,
["RotateTransform"] = HandleXmlElement_RotateTransform,
["TranslateTransform"] = HandleXmlElement_TranslateTransform,
["BlurEffect"] = HandleXmlElement_BlurEffect,
["DropShadowEffect"] = HandleXmlElement_DropShadowEffect,
["Ellipse"] = HandleXmlElement_Ellipse,
["Line"] = HandleXmlElement_Line,
["Rectangle"] = HandleXmlElement_Rectangle,
["RowDefinition"] = HandleXmlElement_RowDefinition,
["ColumnDefinition"] = HandleXmlElement_ColumnDefinition
};
private static T HandleXml<T>(CustomDialog dialog, XElement xmlElement) where T : class
{
if (!_elementHandlerMap.ContainsKey(xmlElement.Name.ToString()))
throw new CustomThemeException("CustomTheme.Errors.UnknownElement", xmlElement.Name);
var element = _elementHandlerMap[xmlElement.Name.ToString()](dialog, xmlElement);
if (element is not T)
throw new CustomThemeException("CustomTheme.Errors.ElementInvalidChild", xmlElement.Parent!.Name, xmlElement.Name);
return (T)element;
}
private static void AddXml(CustomDialog dialog, XElement xmlElement)
{
if (xmlElement.Name.ToString().StartsWith($"{xmlElement.Parent!.Name}."))
return; // not an xml element
var uiElement = HandleXml<UIElement>(dialog, xmlElement);
if (uiElement is not DummyFrameworkElement)
dialog.ElementGrid.Children.Add(uiElement);
}
private static void AssertThemeVersion(string? versionStr)
{
if (string.IsNullOrEmpty(versionStr))
throw new CustomThemeException("CustomTheme.Errors.VersionNotSet", "BloxstrapCustomBootstrapper");
if (!uint.TryParse(versionStr, out uint version))
throw new CustomThemeException("CustomTheme.Errors.VersionNotNumber", "BloxstrapCustomBootstrapper");
switch (version)
{
case Version:
break;
case 0: // Themes made between Oct 19, 2024 to Mar 11, 2025 (on the feature/custom-bootstrappers branch)
throw new CustomThemeException("CustomTheme.Errors.VersionNotSupported", "BloxstrapCustomBootstrapper", version);
default:
throw new CustomThemeException("CustomTheme.Errors.VersionNotRecognised", "BloxstrapCustomBootstrapper", version);
}
}
private void HandleXmlBase(XElement xml)
{
if (_initialised)
throw new CustomThemeException("CustomTheme.Errors.DialogAlreadyInitialised");
if (xml.Name != "BloxstrapCustomBootstrapper")
throw new CustomThemeException("CustomTheme.Errors.InvalidRoot", "BloxstrapCustomBootstrapper");
AssertThemeVersion(xml.Attribute("Version")?.Value);
if (xml.Descendants().Count() > MaxElements)
throw new CustomThemeException("CustomTheme.Errors.TooManyElements", MaxElements, xml.Descendants().Count());
_initialised = true;
// handle root
HandleXmlElement_BloxstrapCustomBootstrapper(this, xml);
// handle everything else
foreach (var child in xml.Elements())
AddXml(this, child);
}
#region Public APIs
public void ApplyCustomTheme(string name, string contents)
{
ThemeDir = System.IO.Path.Combine(Paths.CustomThemes, name);
XElement xml;
try
{
using (MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(contents)))
xml = XElement.Load(ms);
}
catch (Exception ex)
{
throw new CustomThemeException(ex, "CustomTheme.Errors.XMLParseFailed", ex.Message);
}
HandleXmlBase(xml);
}
public void ApplyCustomTheme(string name)
{
string path = System.IO.Path.Combine(Paths.CustomThemes, name, "Theme.xml");
ApplyCustomTheme(name, File.ReadAllText(path));
}
#endregion
}
}