mirror of
https://github.com/bloxstraplabs/bloxstrap.git
synced 2025-04-22 18:41:26 -07:00
* 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
301 lines
11 KiB
C#
301 lines
11 KiB
C#
using System.Windows;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Effects;
|
|
using System.Xml.Linq;
|
|
|
|
namespace Bloxstrap.UI.Elements.Bootstrapper
|
|
{
|
|
public partial class CustomDialog
|
|
{
|
|
struct GetImageSourceDataResult
|
|
{
|
|
public bool IsIcon = false;
|
|
public Uri? Uri = null;
|
|
|
|
public GetImageSourceDataResult()
|
|
{
|
|
}
|
|
}
|
|
|
|
private static string GetXmlAttribute(XElement element, string attributeName, string? defaultValue = null)
|
|
{
|
|
var attribute = element.Attribute(attributeName);
|
|
|
|
if (attribute == null)
|
|
{
|
|
if (defaultValue != null)
|
|
return defaultValue;
|
|
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeMissing", element.Name, attributeName);
|
|
}
|
|
|
|
return attribute.Value.ToString();
|
|
}
|
|
|
|
private static T ParseXmlAttribute<T>(XElement element, string attributeName, T? defaultValue = null) where T : struct
|
|
{
|
|
var attribute = element.Attribute(attributeName);
|
|
|
|
if (attribute == null)
|
|
{
|
|
if (defaultValue != null)
|
|
return (T)defaultValue;
|
|
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeMissing", element.Name, attributeName);
|
|
}
|
|
|
|
T? parsed = ConvertValue<T>(attribute.Value);
|
|
if (parsed == null)
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeInvalidType", element.Name, attributeName, typeof(T).Name);
|
|
|
|
return (T)parsed;
|
|
}
|
|
|
|
/// <summary>
|
|
/// ParseXmlAttribute but the default value is always null
|
|
/// </summary>
|
|
private static T? ParseXmlAttributeNullable<T>(XElement element, string attributeName) where T : struct
|
|
{
|
|
var attribute = element.Attribute(attributeName);
|
|
|
|
if (attribute == null)
|
|
return null;
|
|
|
|
T? parsed = ConvertValue<T>(attribute.Value);
|
|
if (parsed == null)
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeInvalidType", element.Name, attributeName, typeof(T).Name);
|
|
|
|
return (T)parsed;
|
|
}
|
|
|
|
private static void ValidateXmlElement(string elementName, string attributeName, int value, int? min = null, int? max = null)
|
|
{
|
|
if (min != null && value < min)
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeMustBeLargerThanMin", elementName, attributeName, min);
|
|
if (max != null && value > max)
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeMustBeSmallerThanMax", elementName, attributeName, max);
|
|
}
|
|
|
|
private static void ValidateXmlElement(string elementName, string attributeName, double value, double? min = null, double? max = null)
|
|
{
|
|
if (min != null && value < min)
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeMustBeLargerThanMin", elementName, attributeName, min);
|
|
if (max != null && value > max)
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeMustBeSmallerThanMax", elementName, attributeName, max);
|
|
}
|
|
|
|
// You can't do numeric only generics in .NET 6. The feature is exclusive to .NET 7+.
|
|
private static int ParseXmlAttributeClamped(XElement element, string attributeName, int? defaultValue = null, int? min = null, int? max = null)
|
|
{
|
|
int value = ParseXmlAttribute<int>(element, attributeName, defaultValue);
|
|
ValidateXmlElement(element.Name.ToString(), attributeName, value, min, max);
|
|
return value;
|
|
}
|
|
|
|
private static FontWeight GetFontWeightFromXElement(XElement element)
|
|
{
|
|
string? value = element.Attribute("FontWeight")?.Value?.ToString();
|
|
if (string.IsNullOrEmpty(value))
|
|
value = "Normal";
|
|
|
|
// bruh
|
|
// https://learn.microsoft.com/en-us/dotnet/api/system.windows.fontweights?view=windowsdesktop-6.0
|
|
switch (value)
|
|
{
|
|
case "Thin":
|
|
return FontWeights.Thin;
|
|
|
|
case "ExtraLight":
|
|
case "UltraLight":
|
|
return FontWeights.ExtraLight;
|
|
|
|
case "Medium":
|
|
return FontWeights.Medium;
|
|
|
|
case "Normal":
|
|
case "Regular":
|
|
return FontWeights.Normal;
|
|
|
|
case "DemiBold":
|
|
case "SemiBold":
|
|
return FontWeights.DemiBold;
|
|
|
|
case "Bold":
|
|
return FontWeights.Bold;
|
|
|
|
case "ExtraBold":
|
|
case "UltraBold":
|
|
return FontWeights.ExtraBold;
|
|
|
|
case "Black":
|
|
case "Heavy":
|
|
return FontWeights.Black;
|
|
|
|
case "ExtraBlack":
|
|
case "UltraBlack":
|
|
return FontWeights.UltraBlack;
|
|
|
|
default:
|
|
throw new CustomThemeException("CustomTheme.Errors.UnknownEnumValue", element.Name, "FontWeight", value);
|
|
}
|
|
}
|
|
|
|
private static FontStyle GetFontStyleFromXElement(XElement element)
|
|
{
|
|
string? value = element.Attribute("FontStyle")?.Value?.ToString();
|
|
if (string.IsNullOrEmpty(value))
|
|
value = "Normal";
|
|
|
|
switch (value)
|
|
{
|
|
case "Normal":
|
|
return FontStyles.Normal;
|
|
|
|
case "Italic":
|
|
return FontStyles.Italic;
|
|
|
|
case "Oblique":
|
|
return FontStyles.Oblique;
|
|
|
|
default:
|
|
throw new CustomThemeException("CustomTheme.Errors.UnknownEnumValue", element.Name, "FontStyle", value);
|
|
}
|
|
}
|
|
|
|
private static TextDecorationCollection? GetTextDecorationsFromXElement(XElement element)
|
|
{
|
|
string? value = element.Attribute("TextDecorations")?.Value?.ToString();
|
|
if (string.IsNullOrEmpty(value))
|
|
return null;
|
|
|
|
switch (value)
|
|
{
|
|
case "Baseline":
|
|
return TextDecorations.Baseline;
|
|
|
|
case "OverLine":
|
|
return TextDecorations.OverLine;
|
|
|
|
case "Strikethrough":
|
|
return TextDecorations.Strikethrough;
|
|
|
|
case "Underline":
|
|
return TextDecorations.Underline;
|
|
|
|
default:
|
|
throw new CustomThemeException("CustomTheme.Errors.UnknownEnumValue", element.Name, "TextDecorations", value);
|
|
}
|
|
}
|
|
|
|
private static string? GetTranslatedText(string? text)
|
|
{
|
|
if (text == null || !text.StartsWith('{') || !text.EndsWith('}'))
|
|
return text; // can't be translated (not in the correct format)
|
|
|
|
string resourceName = text[1..^1];
|
|
|
|
if (resourceName == "Version")
|
|
return App.Version;
|
|
|
|
return Strings.ResourceManager.GetStringSafe(resourceName);
|
|
}
|
|
|
|
private static string? GetFullPath(CustomDialog dialog, string? sourcePath)
|
|
{
|
|
if (sourcePath == null)
|
|
return null;
|
|
|
|
// TODO: this is bad :(
|
|
return sourcePath.Replace("theme://", $"{dialog.ThemeDir}\\");
|
|
}
|
|
|
|
private static GetImageSourceDataResult GetImageSourceData(CustomDialog dialog, string name, XElement xmlElement)
|
|
{
|
|
string path = GetXmlAttribute(xmlElement, name);
|
|
|
|
if (path == "{Icon}")
|
|
return new GetImageSourceDataResult { IsIcon = true };
|
|
|
|
path = GetFullPath(dialog, path)!;
|
|
|
|
if (!Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out Uri? result))
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeParseError", xmlElement.Name, name, "Uri");
|
|
|
|
if (result == null)
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeParseErrorNull", xmlElement.Name, name, "Uri");
|
|
|
|
if (result.Scheme != "file")
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeBlacklistedUriScheme", xmlElement.Name, name, result.Scheme);
|
|
|
|
return new GetImageSourceDataResult { Uri = result };
|
|
}
|
|
|
|
private static object? GetContentFromXElement(CustomDialog dialog, XElement xmlElement)
|
|
{
|
|
var contentAttr = xmlElement.Attribute("Content");
|
|
var contentElement = xmlElement.Element($"{xmlElement.Name}.Content");
|
|
if (contentAttr != null && contentElement != null)
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeMultipleDefinitions", xmlElement.Name, "Content");
|
|
|
|
if (contentAttr != null)
|
|
return GetTranslatedText(contentAttr.Value);
|
|
|
|
if (contentElement == null)
|
|
return null;
|
|
|
|
var children = contentElement.Elements();
|
|
if (children.Count() > 1)
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeMultipleChildren", xmlElement.Name, "Content");
|
|
|
|
var first = contentElement.FirstNode as XElement;
|
|
if (first == null)
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeMissingChild", xmlElement.Name, "Content");
|
|
|
|
var uiElement = HandleXml<UIElement>(dialog, first);
|
|
return uiElement;
|
|
}
|
|
|
|
private static void ApplyEffects_UIElement(CustomDialog dialog, UIElement uiElement, XElement xmlElement)
|
|
{
|
|
var effectElement = xmlElement.Element($"{xmlElement.Name}.Effect");
|
|
if (effectElement == null)
|
|
return;
|
|
|
|
var children = effectElement.Elements();
|
|
if (children.Count() > 1)
|
|
throw new CustomThemeException("CustomTheme.Errors.ElementAttributeMultipleChildren", xmlElement.Name, "Effect");
|
|
|
|
var child = children.FirstOrDefault();
|
|
if (child == null)
|
|
return;
|
|
|
|
Effect effect = HandleXml<Effect>(dialog, child);
|
|
uiElement.Effect = effect;
|
|
}
|
|
|
|
private static void ApplyTransformation_UIElement(CustomDialog dialog, string name, DependencyProperty property, UIElement uiElement, XElement xmlElement)
|
|
{
|
|
var transformElement = xmlElement.Element($"{xmlElement.Name}.{name}");
|
|
|
|
if (transformElement == null)
|
|
return;
|
|
|
|
var tg = new TransformGroup();
|
|
|
|
foreach (var child in transformElement.Elements())
|
|
{
|
|
Transform element = HandleXml<Transform>(dialog, child);
|
|
tg.Children.Add(element);
|
|
}
|
|
|
|
uiElement.SetValue(property, tg);
|
|
}
|
|
|
|
private static void ApplyTransformations_UIElement(CustomDialog dialog, UIElement uiElement, XElement xmlElement)
|
|
{
|
|
ApplyTransformation_UIElement(dialog, "RenderTransform", FrameworkElement.RenderTransformProperty, uiElement, xmlElement);
|
|
ApplyTransformation_UIElement(dialog, "LayoutTransform", FrameworkElement.LayoutTransformProperty, uiElement, xmlElement);
|
|
}
|
|
}
|
|
}
|