diff --git a/CharacterMap/CharacterMap.CX/DWriteFontFace.h b/CharacterMap/CharacterMap.CX/DWriteFontFace.h index ad67f3c0..d879b042 100644 --- a/CharacterMap/CharacterMap.CX/DWriteFontFace.h +++ b/CharacterMap/CharacterMap.CX/DWriteFontFace.h @@ -129,7 +129,6 @@ namespace CharacterMapCX { m_font = font; m_dwProperties = properties; - }; ComPtr GetFontCollection() diff --git a/CharacterMap/CharacterMap.CX/FontAnalysis.h b/CharacterMap/CharacterMap.CX/FontAnalysis.h index e3ca1fcc..79538cb6 100644 --- a/CharacterMap/CharacterMap.CX/FontAnalysis.h +++ b/CharacterMap/CharacterMap.CX/FontAnalysis.h @@ -18,21 +18,21 @@ namespace CharacterMapCX { public: - property bool HasBitmapGlyphs { bool get() { return m_hasBitmap; } } + property bool HasBitmapGlyphs { bool get() { ReadTables(); return m_hasBitmap; } } - property bool HasCOLRGlyphs { bool get() { return m_hasCOLR; } } + property bool HasCOLRGlyphs { bool get() { ReadTables(); return m_hasCOLR; } } - property bool HasSVGGlyphs { bool get() { return m_hasSVG; } } + property bool HasSVGGlyphs { bool get() { ReadTables(); return m_hasSVG; } } - property bool ContainsVectorColorGlyphs { bool get() { return m_hasSVG || m_hasCOLR; } } + property bool ContainsVectorColorGlyphs { bool get() { ReadTables(); return m_hasSVG || m_hasCOLR; } } - property bool HasGlyphNames { bool get() { return m_hasGlyphNames; } } + property bool HasGlyphNames { bool get() { ReadTables(); return m_hasGlyphNames; } } - property int COLRVersion { int get() { return m_colrVersion; } } + property int COLRVersion { int get() { ReadTables(); return m_colrVersion; } } - property bool SupportsCOLRv1 { bool get() { return m_colrVersion >= 1; } } + property bool SupportsCOLRv1 { bool get() { ReadTables(); return m_colrVersion >= 1; } } - property bool HasVariationAxis { bool get() { return m_variableAxis != nullptr && m_variableAxis->Size > 0; } } + property bool HasVariationAxis { bool get() { GetVariableProperties(); return m_variableAxis != nullptr && m_variableAxis->Size > 0; } } property bool IsRemote { bool get() { return m_isRemote; } } @@ -52,16 +52,19 @@ namespace CharacterMapCX property IVectorView^ Axis { - IVectorView^ get() { return m_axis; } + IVectorView^ get() { GetVariableProperties(); return m_axis; } } property IVectorView^ VariableAxis { - IVectorView^ get() { return m_variableAxis; } + IVectorView^ get() { GetVariableProperties(); return m_variableAxis; } } void ResetVariableAxis() { + if (m_variableAxis == nullptr) + return; + for each (auto a in m_variableAxis) a->Value = a->DefaultValue; } @@ -69,16 +72,15 @@ namespace CharacterMapCX /// /// Mappings of glyph index to font-provided glyph names /// - property IMapView^ GlyphNameMappings; + property IMapView^ GlyphNameMappings { IMapView^ get() { ReadTables(); return m_mappings; } } FontAnalysis() { } FontAnalysis(DWriteFontFace^ fontFace) { - ComPtr ref = fontFace->GetReference(); - AnalyseTables(ref); - GetFileProperties(ref); + m_ref = fontFace->GetReference(); + GetFileProperties(m_ref); } private: @@ -97,9 +99,30 @@ namespace CharacterMapCX IVectorView^ m_variableAxis; IVectorView^ m_axis; - void GetFileProperties(ComPtr faceRef) + IMapView^ m_mappings; + ComPtr m_ref; + + bool m_tables = false; + bool m_var = false; + + void ReadTables() { - m_axis = DirectWrite::GetAxis(faceRef); + if (m_tables) + return; + + m_tables = true; + AnalyseTables(); + } + + + void GetVariableProperties() + { + if (m_var) + return; + + m_var = true; + + m_axis = DirectWrite::GetAxis(m_ref); // Check for variable font axis Vector^ variable = ref new Vector(); @@ -109,7 +132,10 @@ namespace CharacterMapCX variable->Append(a); } m_variableAxis = variable->GetView(); + } + void GetFileProperties(ComPtr faceRef) + { // Get File Size m_fileSize = faceRef->GetFileSize(); @@ -138,6 +164,8 @@ namespace CharacterMapCX { m_filePath = ref new Platform::String(buffer); } + + delete[] buffer; } } } @@ -148,10 +176,10 @@ namespace CharacterMapCX } } - void AnalyseTables(ComPtr faceRef) + void AnalyseTables() { ComPtr f3; - faceRef->CreateFontFace(&f3); + m_ref->CreateFontFace(&f3); ComPtr face; f3.As(&face); @@ -228,7 +256,7 @@ namespace CharacterMapCX if (exists) { auto reader = ref new PostTableReader(tableData, tableSize); - GlyphNameMappings = reader->Mapping; + m_mappings = reader->Mapping; m_hasGlyphNames = GlyphNameMappings != nullptr && GlyphNameMappings->Size > 0; delete reader; } diff --git a/CharacterMap/CharacterMap.Generators/Common/GeneratorExtensions.cs b/CharacterMap/CharacterMap.Generators/Common/GeneratorExtensions.cs index 3ca1c546..037430ec 100644 --- a/CharacterMap/CharacterMap.Generators/Common/GeneratorExtensions.cs +++ b/CharacterMap/CharacterMap.Generators/Common/GeneratorExtensions.cs @@ -13,7 +13,7 @@ public static class GeneratorExtensions public static AttributeArgumentSyntax GetArgument(this AttributeSyntax attribute, string name) { - return attribute.ArgumentList.Arguments.FirstOrDefault(a => a.NameEquals?.Name.Identifier.ValueText == name); + return attribute.ArgumentList?.Arguments.FirstOrDefault(a => a.NameEquals?.Name?.Identifier.ValueText == name); } public static PropertyDeclarationSyntax GetProperty(this AttributeSyntax attribute, string name) diff --git a/CharacterMap/CharacterMap.Generators/Readers/AttachedPropertyReader.cs b/CharacterMap/CharacterMap.Generators/Readers/AttachedPropertyReader.cs new file mode 100644 index 00000000..dafbe7eb --- /dev/null +++ b/CharacterMap/CharacterMap.Generators/Readers/AttachedPropertyReader.cs @@ -0,0 +1,90 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CharacterMap.Generators.Readers; + +public class AttachedPropertyReader : SyntaxReader +{ + string TEMPLATE = + " public static {2} Get{0}(DependencyObject obj) => ({2})obj.GetValue({0}Property);\r\n\r\n" + + " public static void Set{0}(DependencyObject obj, {2} value) => obj.SetValue({0}Property, value);\r\n\r\n" + + " public static readonly DependencyProperty {0}Property =\r\n" + + " DependencyProperty.RegisterAttached(\"{0}\", typeof({2}), typeof({1}), new PropertyMetadata({3}, (d,e) => On{0}Changed(d,e)));\r\n\r\n" + + " static partial void On{0}Changed(DependencyObject d, DependencyPropertyChangedEventArgs e);\r\n"; + + List data = []; + + public override void Read(IEnumerable nodes) + { + foreach (var n in nodes.OfType() + .Where(c => c.HasGenericAttribute("AttachedProperty"))) + { + DPData src = new() + { + ParentClass = n.Identifier.ValueText, + ParentNamespace = n.GetNamespace(), + Usings = (n.Parent?.Parent as CompilationUnitSyntax)?.Usings.Select(u => $"using {u.Name.ToString()};")?.ToList() ?? new() + }; + + foreach (var a in n.AttributeLists + .SelectMany(s => s.Attributes) + .Where(a => a.Name.ToString().StartsWith("AttachedProperty"))) + { + string type = null; + if (a.Name is GenericNameSyntax gen) + type = gen.TypeArgumentList.Arguments[0].ToString(); + + var d = a.GetArgument("Name") is { } na && na.NameEquals is { } ne // Attribute property path, + ? src with + { + Name = a.GetArgument("Name")?.GetValue(), + Default = a.GetArgument("Default")?.GetValue() ?? "default", + Type = type ?? a.GetArgument("Type")?.GetValue()?.Replace("typeof(", string.Empty).Replace(")", string.Empty) ?? "object" + } + : src with // Constructor path - preferred + { + Name = a.ArgumentList?.Arguments[0].GetValue() ?? type, + Type = type ?? "object", + Default = a.ArgumentList?.Arguments.Skip(1)?.FirstOrDefault()?.GetValue() ?? "default" + }; + + data.Add(d); + } + } + } + + public override void Write(GeneratorExecutionContext context) + { + if (data.Count == 0) + return; + + foreach (var group in data.GroupBy(d => $"{d.ParentNamespace}.{d.ParentClass}.g.ap.cs")) + { + string file = group.Key; + string ns = group.First().ParentNamespace; + string target = group.First().ParentClass; + List usings = group.First().Usings; + + StringBuilder sb = new(); + + foreach (DPData dp in group) + sb.AppendLine( + string.Format(TEMPLATE, dp.Name, dp.ParentClass, dp.Type, dp.Default, dp.GetCastType("e.OldValue"), dp.GetCastType("e.NewValue"))); + + var s = sb.ToString(); + context.AddSource(file, SourceText.From( +$@"{string.Join("\r\n", usings)} + +namespace {ns}; + +partial class {target} +{{ +{sb} +}}", Encoding.UTF8)); + } + } +} \ No newline at end of file diff --git a/CharacterMap/CharacterMap.Generators/Readers/DependencyPropertyReader.cs b/CharacterMap/CharacterMap.Generators/Readers/DependencyPropertyReader.cs index 96d8f417..ef2625c9 100644 --- a/CharacterMap/CharacterMap.Generators/Readers/DependencyPropertyReader.cs +++ b/CharacterMap/CharacterMap.Generators/Readers/DependencyPropertyReader.cs @@ -7,6 +7,53 @@ namespace CharacterMap.Generators.Readers; +internal record class DPData +{ + public string Default { get; init; } + public string Container { get; init; } + public string Callback { get; init; } + public string Type { get; init; } + public string Name { get; init; } + public string TypeNamespace { get; init; } + + public string ParentClass { get; init; } + public string ParentNamespace { get; init; } + public List Usings { get; init; } + + public string GetCastType(string value) => IsPrimitive(Type) ? $"({Type})({value} ?? ({Type})default)" : $"{value} as {Type}"; + + public string GetDefault() + { + if (Type == "string" && Default != null + && Default != "null" + && Default != "default" + && Default.StartsWith("nameof") is false) + return $"\"{Default}\""; + + return Default; + } + + public string GetCallback() + { + if (!string.IsNullOrWhiteSpace(Callback) && !Callback.Contains("(")) + return $"{Callback}()"; + + return Callback; + } + + // Terrible way of doing this + private bool IsPrimitive(string type) + { + return type is "bool" or "int" or "double" or "float" + or "Visibility" or "CornerRadius" or "CharacterCasing" + or "FlowDirection" or "ContentPlacement" or "GridLength" + or "Orientation" or "GlyphAnnotation" or "FlyoutPlacementMode" + or "TextWrapping" or "Stretch" or "Thickness" + or "TextAlignment" or "TimeSpan" or "BlendEffectMode" + or "Point" or "TextLineBounds"; + } +} + public class DependencyPropertyReader : SyntaxReader { string TEMPLATE = @@ -20,36 +67,23 @@ public class DependencyPropertyReader : SyntaxReader " {{\r\n" + " if (d is {1} o && e.NewValue is {2} i)\r\n" + //" {{\r\n" + - " o.On{0}Changed(e.OldValue as {4}, i);\r\n" + - // " }}\r\n" + + " o.On{0}Changed({4}, {5});\r\n" + + // " }}\r\n" + " }}));\r\n\r\n" + - " partial void On{0}Changed({4} oldValue, {2} newValue);\r\n"; - + " partial void On{0}Changed({2} o, {2} n);\r\n"; - - record class DPData - { - public string Default { get; init; } - public string Container { get; init; } - public string Type { get; init; } - public string Name { get; init; } - public string TypeNamespace { get; init; } - - public string ParentClass { get; init; } - public string ParentNamespace { get; init; } - public List Usings { get; init; } - - public string GetCastType() => IsPrimitive(Type) ? $"{Type}?" : Type; - - // Terrible way of doing this - private bool IsPrimitive(string type) - { - return type is "bool" or "int" or "double" or "float" - or "Visibility" or "CornerRadius" or "CharacterCasing" - or "FlowDirection" or "ContentPlacement" or "GridLength" - or "Orientation" or "GlyphAnnotation" or "FlyoutPlacementMode"; - } - } + string TEMPLATE2 = + " public {2} {0}\r\n" + + " {{\r\n" + + " get {{ return ({2})GetValue({0}Property); }}\r\n" + + " set {{ SetValue({0}Property, value); }}\r\n" + + " }}\r\n\r\n" + + " public static readonly DependencyProperty {0}Property =\r\n" + + " DependencyProperty.Register(nameof({0}), typeof({2}), typeof({1}), new PropertyMetadata({3}, (d, e) =>\r\n" + + " {{\r\n" + + " if (d is {1} o)\r\n" + + " o.{6};\r\n" + + " }}));\r\n\r\n"; List data = []; @@ -60,10 +94,11 @@ public override void Read(IEnumerable nodes) { DPData src = new() { - ParentClass = n.Identifier.ValueText, - ParentNamespace = n.GetNamespace(), - Usings = (n.Parent?.Parent as CompilationUnitSyntax)?.Usings.Select(u => $"using {u.Name.ToString()};")?.ToList() ?? new() - }; + ParentClass = n.Identifier.ValueText, + ParentNamespace = n.GetNamespace(), + // Does not support static usings or using alias' + Usings = (n.Parent?.Parent as CompilationUnitSyntax)?.Usings.Select(u => $"using {u.Name.ToString()};")?.ToList() ?? new() + }; foreach (var a in n.AttributeLists .SelectMany(s => s.Attributes) @@ -84,9 +119,16 @@ public override void Read(IEnumerable nodes) { Name = a.ArgumentList.Arguments[0].GetValue(), Type = type ?? "object", - Default = a.ArgumentList.Arguments.Skip(1)?.FirstOrDefault()?.GetValue() ?? "default" + Default = a.ArgumentList.Arguments.Skip(1)?.FirstOrDefault()?.GetValue() ?? "default", + Callback = FormatCallback(a.ArgumentList.Arguments.Skip(2)?.FirstOrDefault()?.GetValue() ?? null) }; + static string FormatCallback(string input) + { + if (input != null && input.StartsWith("nameof(")) + input = input.Remove(0, "nameof(".Length)[0..^1]; + return input; + } data.Add(d); } @@ -105,11 +147,16 @@ public override void Write(GeneratorExecutionContext context) string target = group.First().ParentClass; List usings = group.First().Usings; - StringBuilder sb = new (); + if (usings.Contains("using Windows.UI.Xaml;") is false) + usings.Add("using Windows.UI.Xaml;"); + + StringBuilder sb = new(); foreach (DPData dp in group) sb.AppendLine( - string.Format(TEMPLATE, dp.Name, dp.ParentClass, dp.Type, dp.Default, dp.GetCastType())); + string.Format( + string.IsNullOrWhiteSpace(dp.Callback) ? TEMPLATE : TEMPLATE2, + dp.Name, dp.ParentClass, dp.Type, dp.GetDefault(), dp.GetCastType("e.OldValue"), dp.GetCastType("e.NewValue"), dp.GetCallback())); var s = sb.ToString(); context.AddSource(file, SourceText.From( diff --git a/CharacterMap/CharacterMap.Generators/Readers/SqliteStatementReader.cs b/CharacterMap/CharacterMap.Generators/Readers/SqliteStatementReader.cs new file mode 100644 index 00000000..30a23fcf --- /dev/null +++ b/CharacterMap/CharacterMap.Generators/Readers/SqliteStatementReader.cs @@ -0,0 +1,193 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CharacterMap.Generators.Readers; + +internal record class SQLSTData +{ + public string Name { get; init; } + public string Type { get; init; } + public bool Single { get; init; } + public IReadOnlyList<(string CastType, string PropertyName, string ReadType, int index)> Columns { get; init; } + +} +public class SqliteStatementReader : SyntaxReader +{ + List data = []; + + public override void Read(IEnumerable nodes) + { + static string GetName(string s) + { + var i = s.IndexOf("(") + 1; + if (i > 0) + { + s = s.Remove(0, i); + s = s.Remove(s.LastIndexOf(")")); + } + + if (s.Length > 2 && s.LastIndexOf(".") is int d && d > 0) + s = s.Remove(0, d + 1); + + return s; + } + + foreach (var n in nodes.OfType() + .Where(c => c.HasGenericAttribute("SQLReader"))) + { + string type = null; + string name = null; + bool single = false; + + // 1. Get the class type + foreach (var a in n.AttributeLists + .SelectMany(s => s.Attributes) + .Where(a => a.Name.ToString().StartsWith("SQLReader") && !a.Name.ToString().StartsWith("SQLReaderMapping"))) + { + type = ((GenericNameSyntax)a.Name).TypeArgumentList.Arguments[0].ToString(); + name = GetName(a.ArgumentList.Arguments[0].GetValue()); + + if (a.ArgumentList.Arguments.Skip(1).FirstOrDefault() is AttributeArgumentSyntax v + && v.GetValue() == "true") + single = true; + } + + List<(string, string, string, int)> columns = []; + + // 2. Get the column mappings + foreach (var a in n.AttributeLists + .SelectMany(s => s.Attributes) + .Where(a => a.Name.ToString().StartsWith("SQLReaderMapping"))) + { + var mappingtype = ((GenericNameSyntax)a.Name).TypeArgumentList.Arguments[0].ToString(); + var mappingpro = GetName(a.ArgumentList.Arguments[0].GetValue()); + string t = mappingtype; + int i = -1; + + if (a.ArgumentList.Arguments.Skip(1).FirstOrDefault() is AttributeArgumentSyntax v) + t = GetName(v.GetValue()); + + if (a.ArgumentList.Arguments.Skip(2).FirstOrDefault() is AttributeArgumentSyntax v2) + i = Convert.ToInt32(v2.GetValue()); + + columns.Add((mappingtype, mappingpro, t, i)); + } + + data.Add(new() { Name = name, Type = type, Single = single, Columns = columns }); + } + } + + string TEMPLATE_0 = + "using SQLite;\r\n" + + "\r\n" + + "namespace CharacterMap.Services;\r\n" + + "\r\n" + + "public static partial class SQLite3Readers\r\n" + + "{{\r\n" + + "{0}\r\n" + + "}}"; + + //string TEMPLATE = + // " public static List<{0}> ReadAs{1}s(this SQLitePCL.sqlite3_stmt stmt)\r\n" + + // " {{\r\n" + + // " List<{0}> data = new();\r\n" + + // " while (SQLite3.Step(stmt) == SQLite3.Result.Row)\r\n" + + // " {{\r\n" + + // " data.Add(\r\n" + + // " new {0}()\r\n" + + // " {{\r\n" + + // "{2}\r\n" + + // " }});\r\n" + + // " }}\r\n\r\n" + + // " return data;\r\n" + + // " }}\r\n"; + + string TEMPLATE = + " public static List<{0}> ReadAs{1}s(this SQLiteCommand cmd)\r\n" + + " {{\r\n" + + " var stmt = cmd.Prepare();\r\n" + + "\r\n" + + " try\r\n" + + " {{\r\n" + + " List<{0}> data = new();\r\n" + + " while (SQLite3.Step(stmt) == SQLite3.Result.Row)\r\n" + + " {{\r\n" + + " data.Add(\r\n" + + " new {0}()\r\n" + + " {{\r\n" + + "{2}\r\n" + + " }});\r\n" + + " }}\r\n" + + " return data;\r\n" + + " }}\r\n" + + " finally\r\n" + + " {{\r\n" + + " stmt.Dispose();\r\n" + + " }}\r\n" + + " }}\r\n"; + + string TEMPLATE_S = + " public static {0} ReadAs{1}(this SQLiteCommand cmd)\r\n" + + " {{\r\n" + + " var stmt = cmd.Prepare();\r\n" + + " try\r\n" + + " {{\r\n" + + " while (SQLite3.Step(stmt) == SQLite3.Result.Row)\r\n" + + " return new {0}()\r\n" + + " {{\r\n" + + "{2}\r\n" + + " }};\r\n" + + " }}\r\n" + + " finally\r\n" + + " {{\r\n" + + " stmt.Dispose();\r\n" + + " }}\r\n" + + " return default;\r\n" + + " }}"; + + public override void Write(GeneratorExecutionContext context) + { + base.Write(context); + + if (data.Count == 0) + return; + + StringBuilder main = new (); + StringBuilder sb = new (); + + foreach (var item in data) + { + sb.Clear(); + + int i = 0; + foreach (var d in item.Columns) + { + int idx = d.index >= 0 ? d.index : i; + var line = $"{d.PropertyName} = ({d.CastType})SQLite3.Column{GetReader(d.ReadType)}(stmt, {idx}),"; + sb.AppendLine(6, line); + i++; + } + + main.AppendLine( + string.Format( + item.Single ? TEMPLATE_S : TEMPLATE, + item.Type, + item.Name, + sb.ToString().TrimEnd().TrimEnd(','))); + } + + static string GetReader(string type) + { + return $"{char.ToUpper(type[0])}{type[1..^0]}"; + } + + + context.AddSource("SQLite3Readers.g.cs", + SourceText.From(string.Format(TEMPLATE_0, main.ToString().TrimEnd()), Encoding.UTF8)); + } +} diff --git a/CharacterMap/CharacterMap.Generators/SourceGenerator.cs b/CharacterMap/CharacterMap.Generators/SourceGenerator.cs index ff0b1289..85f9fc4f 100644 --- a/CharacterMap/CharacterMap.Generators/SourceGenerator.cs +++ b/CharacterMap/CharacterMap.Generators/SourceGenerator.cs @@ -25,6 +25,12 @@ public void Execute(GeneratorExecutionContext context) // 3. Create Dependency Properties new DependencyPropertyReader().Process(tree, context); + + // 4. Create Attached Properties + new AttachedPropertyReader().Process(tree, context); + + // 5. Generate SQLite Readers + new SqliteStatementReader().Process(tree, context); } } catch (Exception) diff --git a/CharacterMap/CharacterMap/App.xaml b/CharacterMap/CharacterMap/App.xaml index 60c3f980..aa4f4e92 100644 --- a/CharacterMap/CharacterMap/App.xaml +++ b/CharacterMap/CharacterMap/App.xaml @@ -33,6 +33,8 @@ + "ms-appx:///Assets/AdobeBlank.otf" + diff --git a/CharacterMap/CharacterMap/CharacterMap.csproj b/CharacterMap/CharacterMap/CharacterMap.csproj index 482c8868..fccce05c 100644 --- a/CharacterMap/CharacterMap/CharacterMap.csproj +++ b/CharacterMap/CharacterMap/CharacterMap.csproj @@ -240,6 +240,7 @@ + @@ -249,6 +250,7 @@ + @@ -323,6 +325,7 @@ + diff --git a/CharacterMap/CharacterMap/Controls/AutoGrid.cs b/CharacterMap/CharacterMap/Controls/AutoGrid.cs index e157c2f8..f9a10b79 100644 --- a/CharacterMap/CharacterMap/Controls/AutoGrid.cs +++ b/CharacterMap/CharacterMap/Controls/AutoGrid.cs @@ -3,23 +3,49 @@ namespace CharacterMap.Controls; -public class AutoGrid : Grid +[DependencyProperty("Definitions")] +[DependencyProperty("Orientation", Orientation.Vertical, nameof(InvalidateArrange))] +public partial class AutoGrid : Grid { + partial void OnDefinitionsChanged(string o, string n) => Properties.SetGridDefinitions(this, n); + protected override Size ArrangeOverride(Size finalSize) { - var rows = this.Children.OfType().Count(c => Grid.GetColumn(c) == 0); - while (this.RowDefinitions.Count > rows) - this.RowDefinitions.RemoveAt(0); - while (this.RowDefinitions.Count < rows) - this.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); - - int row = -1; - foreach (var child in this.Children.OfType()) + if (Orientation is Orientation.Vertical) + { + var rows = this.Children.OfType().Count(c => Grid.GetColumn(c) == 0); + while (this.RowDefinitions.Count > rows) + this.RowDefinitions.RemoveAt(0); + while (this.RowDefinitions.Count < rows) + this.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto }); + + int row = -1; + foreach (var child in this.Children.OfType()) + { + if (Grid.GetColumn(child) == 0) + row++; + + if (child.ReadLocalValue(Grid.RowProperty) == DependencyProperty.UnsetValue) + Grid.SetRow(child, row); + } + } + else { - if (Grid.GetColumn(child) == 0) - row++; + var columns = this.Children.OfType().Count(c => Grid.GetRow(c) == 0); + while (this.ColumnDefinitions.Count > columns) + this.ColumnDefinitions.RemoveAt(0); + while (this.ColumnDefinitions.Count < columns) + this.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }); + + int col = -1; + foreach (var child in this.Children.OfType()) + { + if (Grid.GetRow(child) == 0) + col++; - Grid.SetRow(child, row); + if (child.ReadLocalValue(Grid.ColumnProperty) == DependencyProperty.UnsetValue) + Grid.SetColumn(child, col); + } } return base.ArrangeOverride(finalSize); diff --git a/CharacterMap/CharacterMap/Controls/CharacterGridView.cs b/CharacterMap/CharacterMap/Controls/CharacterGridView.cs index e921b1a5..75645d70 100644 --- a/CharacterMap/CharacterMap/Controls/CharacterGridView.cs +++ b/CharacterMap/CharacterMap/Controls/CharacterGridView.cs @@ -31,13 +31,14 @@ internal class CharacterGridViewTemplateSettings [DependencyProperty("ItemTypography")] [DependencyProperty("ItemFontVariant")] [DependencyProperty("ItemAnnotation")] +[AttachedProperty("ToolTipData")] public partial class CharacterGridView : GridView { public event EventHandler ItemDoubleTapped; #region Dependency Properties - partial void OnItemSizeChanged(double? oldValue, double n) => _templateSettings.Size = n; + partial void OnItemSizeChanged(double oldValue, double n) => _templateSettings.Size = n; partial void OnItemFontFamilyChanged(FontFamily oldValue, FontFamily n) => _templateSettings.FontFamily = n; @@ -52,29 +53,23 @@ partial void OnItemTypographyChanged(TypographyFeatureInfo oldValue, TypographyF } } - partial void OnShowColorGlyphsChanged(bool? oldValue, bool n) + partial void OnShowColorGlyphsChanged(bool oldValue, bool n) { _templateSettings.ShowColorGlyphs = n; UpdateColorsFonts(n); } - partial void OnEnableResizeAnimationChanged(bool? oldValue, bool n) + partial void OnEnableResizeAnimationChanged(bool oldValue, bool n) { _templateSettings.EnableReposition = n && CompositionFactory.UISettings.AnimationsEnabled; UpdateAnimation(n); } - //public GlyphAnnotation ItemAnnotation - //{ - // get { return (GlyphAnnotation)GetValue(ItemAnnotationProperty); } - // set { SetValue(ItemAnnotationProperty, value); } - //} - - //public static readonly DP ItemAnnotationProperty = DP(GlyphAnnotation.None, (d, o, n) => - //{ - // d._templateSettings.Annotation = n; - // d.UpdateUnicode(n); - //}); + partial void OnItemAnnotationChanged(GlyphAnnotation o, GlyphAnnotation n) + { + _templateSettings.Annotation = n; + UpdateUnicode(n); + } #endregion @@ -91,7 +86,7 @@ public CharacterGridView() this.ChoosingItemContainer += OnChoosingItemContainer; } - private class ItemTooltipData + public class ItemTooltipData { public Character Char { get; set; } public FontVariant Variant { get; set; } @@ -125,7 +120,7 @@ private void OnContainerContentChanging(ListViewBase sender, ContainerContentCha t.Placement = Windows.UI.Xaml.Controls.Primitives.PlacementMode.Top; t.Loaded += (d, e) => { - if (d is ToolTip tt && tt.Tag is ItemTooltipData data) + if (d is ToolTip tt && CharacterGridView.GetToolTipData(tt) is ItemTooltipData data) { tt.PlacementRect = new(0, 0, data.Container.ActualWidth, data.Container.ActualHeight); @@ -142,7 +137,8 @@ private void OnContainerContentChanging(ListViewBase sender, ContainerContentCha }; ToolTipService.SetToolTip(item, t); } - t.Tag = new ItemTooltipData { Char = c, Container = item, Variant = ItemFontVariant }; + + CharacterGridView.SetToolTipData(t, new ItemTooltipData { Char = c, Container = item, Variant = ItemFontVariant }); } } diff --git a/CharacterMap/CharacterMap/Controls/CharacterPickerButton.cs b/CharacterMap/CharacterMap/Controls/CharacterPickerButton.cs index 4668b4d5..bf866cf9 100644 --- a/CharacterMap/CharacterMap/Controls/CharacterPickerButton.cs +++ b/CharacterMap/CharacterMap/Controls/CharacterPickerButton.cs @@ -7,7 +7,7 @@ namespace CharacterMap.Controls; [DependencyProperty("Target", typeof(Control))] [DependencyProperty("Options", typeof(CharacterRenderingOptions))] -[DependencyProperty("Placement", FlyoutPlacementMode.Bottom)] +[DependencyProperty("Placement", FlyoutPlacementMode.Bottom, nameof(UpdatePlacement))] public sealed partial class CharacterPickerButton : ContentControl { private static PropertyMetadata NULL_META = new(null); @@ -19,8 +19,6 @@ public CharacterPickerButton() this.DefaultStyleKey = typeof(CharacterPickerButton); } - partial void OnPlacementChanged(FlyoutPlacementMode? o, FlyoutPlacementMode n) => UpdatePlacement(); - protected override void OnApplyTemplate() { base.OnApplyTemplate(); diff --git a/CharacterMap/CharacterMap/Controls/CreateCollectionDialog.xaml b/CharacterMap/CharacterMap/Controls/CreateCollectionDialog.xaml index 0e6049b7..2cb053b8 100644 --- a/CharacterMap/CharacterMap/Controls/CreateCollectionDialog.xaml +++ b/CharacterMap/CharacterMap/Controls/CreateCollectionDialog.xaml @@ -2,6 +2,8 @@ x:Class="CharacterMap.Controls.CreateCollectionDialog" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:controls="using:CharacterMap.Controls" + xmlns:core="using:CharacterMap.Core" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" x:Uid="DigCreateCollection" @@ -14,10 +16,118 @@ Style="{StaticResource DefaultThemeContentDialogStyle}" mc:Ignorable="d"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CharacterMap/CharacterMap/Controls/CreateCollectionDialog.xaml.cs b/CharacterMap/CharacterMap/Controls/CreateCollectionDialog.xaml.cs index 855758e8..eeed7d4d 100644 --- a/CharacterMap/CharacterMap/Controls/CreateCollectionDialog.xaml.cs +++ b/CharacterMap/CharacterMap/Controls/CreateCollectionDialog.xaml.cs @@ -1,8 +1,9 @@ -using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; namespace CharacterMap.Controls; -public class CreateCollectionDialogTemplateSettings : ViewModelBase +public partial class CreateCollectionDialogTemplateSettings : ViewModelBase { private string _collectionTitle; public string CollectionTitle @@ -11,45 +12,95 @@ public string CollectionTitle set { if (Set(ref _collectionTitle, value)) OnCollectionTitleChanged(); } } - private bool _isCollectionTitleValid; - public bool IsCollectionTitleValid - { - get => _isCollectionTitleValid; - private set => Set(ref _isCollectionTitleValid, value); - } + [ObservableProperty] bool _isCollectionTitleValid; + + [ObservableProperty] bool _isSmartCollection; + [ObservableProperty] string _filterFilePath; + [ObservableProperty] string _filterFoundry; + [ObservableProperty] string _filterDesigner; + private void OnCollectionTitleChanged() { IsCollectionTitleValid = !string.IsNullOrWhiteSpace(CollectionTitle); } + + public void Populate(IFontCollection c) + { + CollectionTitle = c.Name; + + if (c is SmartFontCollection u) + { + IsSmartCollection = true; + + Populate("filepath:", s => FilterFilePath = s); + Populate("foundry:", s => FilterFoundry = s); + Populate("designer:", s => FilterDesigner = s); + + void Populate(string id, Action set) + { + foreach (var s in u.Filters) + { + if (s.StartsWith(id)) + set(s.Replace(id, string.Empty).Trim()); + } + } + } + } + + public List GetFilterList() + { + List filters = []; + Add("filepath:", _filterFilePath); + Add("foundry:", _filterFoundry); + Add("designer:", _filterDesigner); + return filters; + + void Add(string id, string field) + { + if (!String.IsNullOrWhiteSpace(field)) + filters.Add($"{id} {field.Replace(id, string.Empty).Trim()}"); + } + } } +//[DependencyProperty("AllowSmartCollection")] public sealed partial class CreateCollectionDialog : ContentDialog { public CreateCollectionDialogTemplateSettings TemplateSettings { get; } - public bool IsRenameMode { get; } + public bool IsEditMode { get; } + + public bool AllowSmartCollection { get; private set; } public object Result { get; private set; } - private UserFontCollection _collection = null; + private IFontCollection _collection = null; - public CreateCollectionDialog(UserFontCollection collection = null) + public CreateCollectionDialog(IFontCollection collection = null) { _collection = collection; - TemplateSettings = new CreateCollectionDialogTemplateSettings(); + TemplateSettings = new (); this.InitializeComponent(); if (_collection != null) { - IsRenameMode = true; - this.Title = Localization.Get("DigRenameCollection/Title"); - this.PrimaryButtonText = Localization.Get("DigRenameCollection/PrimaryButtonText"); - TemplateSettings.CollectionTitle = _collection.Name; + IsEditMode = true; + this.Title = Localization.Get("DigEditCollection/Title"); + this.PrimaryButtonText = Localization.Get("DigEditCollection/PrimaryButtonText"); + TemplateSettings.Populate(_collection); } } + public CreateCollectionDialog SetDataContext(object o) + { + this.DataContext = o; + if (o is null) + AllowSmartCollection = IsEditMode is false; + return this; + } + private async void ContentDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) { @@ -57,13 +108,18 @@ private async void ContentDialog_PrimaryButtonClick(ContentDialog sender, Conten var collections = Ioc.Default.GetService(); - if (IsRenameMode) + if (IsEditMode) { this.IsPrimaryButtonEnabled = false; this.IsSecondaryButtonEnabled = false; InputBox.IsEnabled = false; - await collections.RenameCollectionAsync(TemplateSettings.CollectionTitle, _collection); + _collection.Name = TemplateSettings.CollectionTitle; + + if (_collection is SmartFontCollection s) + s.Filters = TemplateSettings.GetFilterList(); + + await collections.UpdateCollectionAsync(_collection); d.Complete(); await Task.Yield(); @@ -72,21 +128,34 @@ private async void ContentDialog_PrimaryButtonClick(ContentDialog sender, Conten else { AddToCollectionResult result = null; - UserFontCollection collection = await collections.CreateCollectionAsync(TemplateSettings.CollectionTitle); - - if (this.DataContext is InstalledFont font) - result = await collections.AddToCollectionAsync(font, collection); - else if (this.DataContext is IList fonts) - result = await collections.AddToCollectionAsync(fonts, collection); - else if (this.DataContext is null) - result = new AddToCollectionResult(true, null, collection); - - Result = result; - d.Complete(); - await Task.Yield(); - if (result is not null && result.Success && result.Fonts is not null) - WeakReferenceMessenger.Default.Send(new AppNotificationMessage(true, result)); + // Check if we are creating a Smart Filter + if (TemplateSettings.GetFilterList() is { Count: > 0} filters) + { + SmartFontCollection collection = await collections.CreateSmartCollectionAsync( + TemplateSettings.CollectionTitle, + filters); + + d.Complete(); + } + else + { + UserFontCollection collection = await collections.CreateCollectionAsync(TemplateSettings.CollectionTitle); + + if (this.DataContext is InstalledFont font) + result = await collections.AddToCollectionAsync(font, collection); + else if (this.DataContext is IReadOnlyList fonts) + result = await collections.AddToCollectionAsync(fonts, collection); + else if (this.DataContext is null) + result = new AddToCollectionResult(true, null, collection); + + Result = result; + d.Complete(); + await Task.Yield(); + if (result is not null && result.Success && result.Fonts is not null) + WeakReferenceMessenger.Default.Send(new AppNotificationMessage(true, result)); + + } } } } diff --git a/CharacterMap/CharacterMap/Controls/DirectTextBlock.cs b/CharacterMap/CharacterMap/Controls/DirectTextBlock.cs index 5f7a51af..b4fe46a5 100644 --- a/CharacterMap/CharacterMap/Controls/DirectTextBlock.cs +++ b/CharacterMap/CharacterMap/Controls/DirectTextBlock.cs @@ -7,45 +7,11 @@ namespace CharacterMap.Controls; -public sealed class DirectTextBlock : Control +[DependencyProperty("Text", null, nameof(Update))] +[DependencyProperty("FontFace", null, nameof(Update))] +[DependencyProperty("Typography", null, nameof(Update))] +public sealed partial class DirectTextBlock : Control { - public CanvasFontFace FontFace - { - get { return (CanvasFontFace)GetValue(FontFaceProperty); } - set { SetValue(FontFaceProperty, value); } - } - - public static readonly DependencyProperty FontFaceProperty = - DependencyProperty.Register(nameof(FontFace), typeof(CanvasFontFace), typeof(DirectTextBlock), new PropertyMetadata(null, (d, e) => - { - ((DirectTextBlock)d).Update(); - })); - - - public TypographyFeatureInfo Typography - { - get { return (TypographyFeatureInfo)GetValue(TypographyProperty); } - set { SetValue(TypographyProperty, value); } - } - - public static readonly DependencyProperty TypographyProperty = - DependencyProperty.Register(nameof(Typography), typeof(TypographyFeatureInfo), typeof(DirectTextBlock), new PropertyMetadata(null, (d, e) => - { - ((DirectTextBlock)d).Update(); - })); - - public string Text - { - get { return (string)GetValue(TextProperty); } - set { SetValue(TextProperty, value); } - } - - public static readonly DependencyProperty TextProperty = - DependencyProperty.Register(nameof(Text), typeof(string), typeof(DirectTextBlock), new PropertyMetadata(null, (d, e) => - { - ((DirectTextBlock)d).Update(); - })); - private CanvasControl m_canvas = null; private CanvasTextLayout m_layout = null; bool m_isStale = true; diff --git a/CharacterMap/CharacterMap/Controls/ExtendedListView.cs b/CharacterMap/CharacterMap/Controls/ExtendedListView.cs index b6c9a125..c1f62805 100644 --- a/CharacterMap/CharacterMap/Controls/ExtendedListView.cs +++ b/CharacterMap/CharacterMap/Controls/ExtendedListView.cs @@ -43,7 +43,22 @@ public ExtendedListView() this.Unloaded += ExtendedListView_Unloaded; } - partial void OnBindableSelectedItemsChanged(INotifyCollectionChanged o, INotifyCollectionChanged n) => OnSelectedItemsChanged(o, n); + partial void OnBindableSelectedItemsChanged(INotifyCollectionChanged o, INotifyCollectionChanged n) + { + if (o is not null) + { + o.CollectionChanged -= SelectedItems_CollectionChanged; + } + + if (this is null) + return; + + if (n is not null) + { + n.CollectionChanged -= SelectedItems_CollectionChanged; + n.CollectionChanged += SelectedItems_CollectionChanged; + } + } private void ExtendedListView_Loaded(object sender, RoutedEventArgs e) { @@ -104,19 +119,7 @@ protected void OnAttached() private void OnSelectedItemsChanged(object old, object nw) { - if (old is INotifyCollectionChanged c) - { - c.CollectionChanged -= SelectedItems_CollectionChanged; - } - - if (this is null) - return; - - if (nw is INotifyCollectionChanged n) - { - n.CollectionChanged -= SelectedItems_CollectionChanged; - n.CollectionChanged += SelectedItems_CollectionChanged; - } + } void ItemsSourceChanged(DependencyObject source, DependencyProperty property) diff --git a/CharacterMap/CharacterMap/Controls/ExtendedSplitView.cs b/CharacterMap/CharacterMap/Controls/ExtendedSplitView.cs index 5e72e430..d1d731b3 100644 --- a/CharacterMap/CharacterMap/Controls/ExtendedSplitView.cs +++ b/CharacterMap/CharacterMap/Controls/ExtendedSplitView.cs @@ -3,7 +3,7 @@ namespace CharacterMap.Controls; -[DependencyProperty("EnableAnimation", true)] +[DependencyProperty("EnableAnimation", true, nameof(UpdateAnimationStates))] public partial class ExtendedSplitView : SplitView { FrameworkElement _contentRoot = null; @@ -16,8 +16,6 @@ public ExtendedSplitView() this.Unloaded += ExtendedSplitView_Unloaded; } - partial void OnEnableAnimationChanged(bool? oldValue, bool newValue) => UpdateAnimationStates(); - protected override void OnApplyTemplate() { base.OnApplyTemplate(); diff --git a/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs b/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs index 4f1dd603..fabef563 100644 --- a/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs +++ b/CharacterMap/CharacterMap/Controls/FilterFlyout.xaml.cs @@ -5,28 +5,12 @@ namespace CharacterMap.Controls; +[DependencyProperty("FilterCommand")] +[DependencyProperty("CollectionSelectedCommand")] public sealed partial class FilterFlyout : MenuFlyout { private int _defaultCount = 0; - public ICommand FilterCommand - { - get { return (ICommand)GetValue(FilterCommandProperty); } - set { SetValue(FilterCommandProperty, value); } - } - - public static readonly DependencyProperty FilterCommandProperty = - DependencyProperty.Register(nameof(FilterCommand), typeof(ICommand), typeof(FilterFlyout), new PropertyMetadata(null)); - - public ICommand CollectionSelectedCommand - { - get { return (ICommand)GetValue(CollectionSelectedCommandProperty); } - set { SetValue(CollectionSelectedCommandProperty, value); } - } - - public static readonly DependencyProperty CollectionSelectedCommandProperty = - DependencyProperty.Register(nameof(CollectionSelectedCommand), typeof(ICommand), typeof(FilterFlyout), new PropertyMetadata(null)); - private MenuFlyoutItemBase _variableOption = null; private MenuFlyoutItemBase _remoteOption = null; private MenuFlyoutItemBase _appxOption = null; @@ -179,7 +163,7 @@ public IEnumerable AllMenuItems() private void MenuFlyout_Opening(object sender, object e) { this.AreOpenCloseAnimationsEnabled = ResourceHelper.AllowAnimation; - var collections = Ioc.Default.GetService().Items; + var collections = Ioc.Default.GetService().All; Style style = ResourceHelper.Get - - @@ -695,6 +623,8 @@ Padding="{TemplateBinding Padding}" AutomationProperties.AccessibilityView="Raw" CornerRadius="{TemplateBinding CornerRadius}" + FontStretch="{TemplateBinding FontStretch}" + FontStyle="{TemplateBinding FontStyle}" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" diff --git a/CharacterMap/CharacterMap/Themes/ClassicThemeStyles.xaml b/CharacterMap/CharacterMap/Themes/ClassicThemeStyles.xaml index 1d30f2c2..99c61e6d 100644 --- a/CharacterMap/CharacterMap/Themes/ClassicThemeStyles.xaml +++ b/CharacterMap/CharacterMap/Themes/ClassicThemeStyles.xaml @@ -407,7 +407,165 @@ - - @@ -2541,6 +2699,7 @@ x:Name="SuggestionsList" MaxHeight="{ThemeResource AutoSuggestListMaxHeight}" Margin="{ThemeResource AutoSuggestListPadding}" + core:Properties.ToolTipMemberPath="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=(core:Properties.ToolTipMemberPath)}" DisplayMemberPath="{TemplateBinding DisplayMemberPath}" Footer="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=(core:Properties.Footer)}" IsItemClickEnabled="True" diff --git a/CharacterMap/CharacterMap/Themes/FluentThemeStyles.xaml b/CharacterMap/CharacterMap/Themes/FluentThemeStyles.xaml index f39d128f..6530d8b8 100644 --- a/CharacterMap/CharacterMap/Themes/FluentThemeStyles.xaml +++ b/CharacterMap/CharacterMap/Themes/FluentThemeStyles.xaml @@ -958,6 +958,7 @@ TextTrimming="{ThemeResource MenuFlyoutItemTextTrimming}" /> + + @@ -2169,6 +2182,7 @@ x:Name="SuggestionsList" MaxHeight="{TemplateBinding MaxSuggestionListHeight}" Margin="{ThemeResource AutoSuggestListPadding}" + core:Properties.ToolTipMemberPath="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=(core:Properties.ToolTipMemberPath)}" DisplayMemberPath="{TemplateBinding DisplayMemberPath}" Footer="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=(core:Properties.Footer)}" IsItemClickEnabled="True" diff --git a/CharacterMap/CharacterMap/Themes/ZuneThemeStyles.xaml b/CharacterMap/CharacterMap/Themes/ZuneThemeStyles.xaml index 72dc3a80..6df8e9c7 100644 --- a/CharacterMap/CharacterMap/Themes/ZuneThemeStyles.xaml +++ b/CharacterMap/CharacterMap/Themes/ZuneThemeStyles.xaml @@ -3312,6 +3312,7 @@ x:Name="SuggestionsList" MaxHeight="{ThemeResource AutoSuggestListMaxHeight}" Margin="{ThemeResource AutoSuggestListPadding}" + core:Properties.ToolTipMemberPath="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=(core:Properties.ToolTipMemberPath)}" DisplayMemberPath="{TemplateBinding DisplayMemberPath}" FontWeight="SemiBold" Footer="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=(core:Properties.Footer)}" @@ -3338,6 +3339,163 @@