diff --git a/.gitattributes b/.gitattributes index 1ff0c423..a84222f3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,7 +2,6 @@ # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto - ############################################################################### # Set default behavior for command prompt diff. # @@ -11,7 +10,6 @@ # Note: This is only used by command line ############################################################################### #*.cs diff=csharp - ############################################################################### # Set the merge driver for project and solution files # @@ -34,7 +32,6 @@ #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary - ############################################################################### # behavior for image files # @@ -43,7 +40,6 @@ #*.jpg binary #*.png binary #*.gif binary - ############################################################################### # diff behavior for common document formats # @@ -61,3 +57,6 @@ #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain +CharacterMap/CharacterMap/Assets/Data/GlyphData.db filter=lfs diff=lfs merge=lfs -text +CharacterMap/CharacterMap/Assets/Data/Unihan_Readings.txt filter=lfs diff=lfs merge=lfs -text +0_artifacts/*.png filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 15904c85..4d1f0135 100644 --- a/.gitignore +++ b/.gitignore @@ -222,3 +222,4 @@ ModelManifest.xml *.appx CharacterMap/CharacterMap.CX/Generated Files CharacterMap/enc_temp_folder +lfs diff --git a/0_artifacts/Screen_02.png b/0_artifacts/Screen_02.png new file mode 100644 index 00000000..46b1bb1e --- /dev/null +++ b/0_artifacts/Screen_02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b2e0dedde8c50abd757afb38257cb60f9ded9b8801272004902fdd6b4a11282a +size 100640 diff --git a/0_artifacts/Screen_03.png b/0_artifacts/Screen_03.png new file mode 100644 index 00000000..e2c15e89 --- /dev/null +++ b/0_artifacts/Screen_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96d34d624c57171ea498dd96fcada4c0450733dc12ed5e081445e065a66d2402 +size 86645 diff --git a/0_artifacts/Screen_04.png b/0_artifacts/Screen_04.png new file mode 100644 index 00000000..ee4d2dd6 --- /dev/null +++ b/0_artifacts/Screen_04.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99926ad06709d301d6d63887c6279699c7711ab85320891563cdccf5aae04c18 +size 50168 diff --git a/0_artifacts/Screen_05.png b/0_artifacts/Screen_05.png new file mode 100644 index 00000000..4477dbf5 --- /dev/null +++ b/0_artifacts/Screen_05.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:09d0c7951b751784d2ed474034afc9d4645027fee6db1efcd9d5b4dd8202dd58 +size 49704 diff --git a/0_artifacts/Screen_06.png b/0_artifacts/Screen_06.png new file mode 100644 index 00000000..d8cfd7a1 --- /dev/null +++ b/0_artifacts/Screen_06.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3eb3554346812bd278c1ddb16dc9c8ea45e8629b3d8a44c6e712094c9bc6d24b +size 230851 diff --git a/0_artifacts/StoreListing.en.md b/0_artifacts/StoreListing.en.md index c3fcdf71..d2f6455b 100644 --- a/0_artifacts/StoreListing.en.md +++ b/0_artifacts/StoreListing.en.md @@ -2,16 +2,17 @@ A modern, native UWP replacement for the Win32 Character Map and Windows Font Viewer with flawless high DPI and touch support. Features include: -- View all fonts families and font faces installed on your device and import your own, including support for .WOFF and .WOFF2 fonts, Variable fonts and Color fonts. +- View all fonts families and font faces installed on your device and import your own, including support for .WOFF and .WOFF2 fonts, Variable fonts and Color fonts (including COLR V1 on supported versions of Windows 11) - View all the Characters in a font face, along with typographic and color variants - Explore and change Variable font axis for fonts in realtime in Type Ramp view - Practice drawing characters in your chosen font in Calligraphy view - Compare all fonts at once in Compare view, or compare individual families or specific faces with Quick Compare view +- Create and manage custom font collections - View font properties like their designer, description, licenses, PANOSE, etc -- Copy Characters to your clipboard +- Copy Characters to your clipboard as text, SVG or PNG - Copy XAML, C++, C#, MAUI code and more for Characters for programming features like FontIcon, SymbolIcon and text properties -- Export Characters individually or in bulk as PNG and SVG files, or print off entire character sheets -- Search for characters inside fonts by unicode character name, unicode index, or the font's own custom named glyphs, including native search supported for Segoe MDL2/Fluent Icons -- Create and manage custom font collections +- Export Characters individually or in bulk as PNG and SVG files, and print off entire character sheets +- Search for characters inside fonts by unicode character name, unicode index, or the font's own custom named glyphs, including native search support for Segoe MDL2/Fluent Icons - Export font files from the app including system fonts to share with others, and you can even export entire collections as a single ZIP archive -- Use as the default .WOFF and .WOFF2 font viewer applications, allowing you to preview .WOFF and .WOFF2 files from Windows Explorer \ No newline at end of file +- Use as the default .WOFF and .WOFF2 font viewer applications, allowing you to preview .WOFF and .WOFF2 files from Windows Explorer +- Allows you to install .WOFF and .WOFF2 fonts to use in all your Windows applications diff --git a/0_artifacts/main.png b/0_artifacts/main.png index 6910dfd2..d30a6807 100644 Binary files a/0_artifacts/main.png and b/0_artifacts/main.png differ diff --git a/CharacterMap/.editorconfig b/CharacterMap/.editorconfig index 76bac021..f2ac64ee 100644 --- a/CharacterMap/.editorconfig +++ b/CharacterMap/.editorconfig @@ -14,3 +14,137 @@ dotnet_diagnostic.IDE0058.severity = none # Default severity for analyzer diagnostics with category 'Style' dotnet_analyzer_diagnostic.category-Style.severity = none + +[*.cs] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.private_prop_should_be_begins_with_underscore.severity = suggestion +dotnet_naming_rule.private_prop_should_be_begins_with_underscore.symbols = private_prop +dotnet_naming_rule.private_prop_should_be_begins_with_underscore.style = begins_with_underscore + +# Symbol specifications + +dotnet_naming_symbols.private_prop.applicable_kinds = property, field +dotnet_naming_symbols.private_prop.applicable_accessibilities = private +dotnet_naming_symbols.private_prop.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_underscore.required_prefix = _ +dotnet_naming_style.begins_with_underscore.required_suffix = +dotnet_naming_style.begins_with_underscore.word_separator = +dotnet_naming_style.begins_with_underscore.capitalization = camel_case +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = file_scoped:suggestion +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_indent_labels = one_less_than_current +csharp_space_around_binary_operators = before_and_after +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_style_throw_expression = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent +csharp_prefer_static_local_function = true:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent +csharp_style_conditional_delegate_call = true:suggestion + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion +dotnet_style_readonly_field = true:suggestion +dotnet_style_predefined_type_for_locals_parameters_members = true:silent +dotnet_style_predefined_type_for_member_access = true:silent +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_allow_multiple_blank_lines_experimental = true:silent +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent diff --git a/CharacterMap/CharacterMap.CX/DWriteFontFace.h b/CharacterMap/CharacterMap.CX/DWriteFontFace.h index ca9dbf3b..ad67f3c0 100644 --- a/CharacterMap/CharacterMap.CX/DWriteFontFace.h +++ b/CharacterMap/CharacterMap.CX/DWriteFontFace.h @@ -61,8 +61,7 @@ namespace CharacterMapCX ThrowIfFailed(m_font->GetInformationalStrings( static_cast(fontInformation), &localizedStrings, &exists)); - - if (localizedStrings) + if (exists) { const uint32_t stringCount = localizedStrings->GetCount(); @@ -74,10 +73,16 @@ namespace CharacterMapCX wchar_t* name = new wchar_t[length + 1]; localizedStrings->GetString(i, name, length); + wchar_t* locale; localizedStrings->GetLocaleNameLength(i, &length); - length++; - wchar_t* locale = new wchar_t[length + 1]; - localizedStrings->GetLocaleName(i, locale, length); + if (length == 0) + locale = name; + else + { + length++; + locale = new wchar_t[length + 1]; + localizedStrings->GetLocaleName(i, locale, length); + } ThrowIfFailed(map->Insert( ref new String(locale), ref new String(name))); diff --git a/CharacterMap/CharacterMap/Activation/ActivationHandler.cs b/CharacterMap/CharacterMap/Activation/ActivationHandler.cs index e5ef4a1d..2f1255dc 100644 --- a/CharacterMap/CharacterMap/Activation/ActivationHandler.cs +++ b/CharacterMap/CharacterMap/Activation/ActivationHandler.cs @@ -1,30 +1,27 @@ -using System.Threading.Tasks; +namespace CharacterMap.Activation; -namespace CharacterMap.Activation +public abstract class ActivationHandler { - public abstract class ActivationHandler + public abstract bool CanHandle(object args); + public abstract Task HandleAsync(object args); +} + +public abstract class ActivationHandler : ActivationHandler where T : class +{ + protected abstract Task HandleInternalAsync(T args); + + public override async Task HandleAsync(object args) { - public abstract bool CanHandle(object args); - public abstract Task HandleAsync(object args); + await HandleInternalAsync(args as T); } - public abstract class ActivationHandler : ActivationHandler where T : class + public override bool CanHandle(object args) { - protected abstract Task HandleInternalAsync(T args); - - public override async Task HandleAsync(object args) - { - await HandleInternalAsync(args as T); - } - - public override bool CanHandle(object args) - { - return args is T && CanHandleInternal(args as T); - } + return args is T && CanHandleInternal(args as T); + } - protected virtual bool CanHandleInternal(T args) - { - return true; - } + protected virtual bool CanHandleInternal(T args) + { + return true; } } diff --git a/CharacterMap/CharacterMap/Activation/DefaultLaunchActivationHandler.cs b/CharacterMap/CharacterMap/Activation/DefaultLaunchActivationHandler.cs index fb4608b6..708bd605 100644 --- a/CharacterMap/CharacterMap/Activation/DefaultLaunchActivationHandler.cs +++ b/CharacterMap/CharacterMap/Activation/DefaultLaunchActivationHandler.cs @@ -1,38 +1,33 @@ -using System; -using System.Threading.Tasks; -using CharacterMap.Services; -using CommunityToolkit.Mvvm.DependencyInjection; using Windows.ApplicationModel.Activation; -namespace CharacterMap.Activation +namespace CharacterMap.Activation; + +internal class DefaultLaunchActivationHandler : ActivationHandler { - internal class DefaultLaunchActivationHandler : ActivationHandler + private readonly string _navElement; + + private NavigationServiceEx NavigationService => Ioc.Default.GetService(); + + public DefaultLaunchActivationHandler(Type navElement) { - private readonly string _navElement; - - private NavigationServiceEx NavigationService => Ioc.Default.GetService(); + _navElement = navElement.FullName; + } - public DefaultLaunchActivationHandler(Type navElement) - { - _navElement = navElement.FullName; - } - - protected override async Task HandleInternalAsync(ILaunchActivatedEventArgs args) - { - // When the navigation stack isn't restored navigate to the first page, - // configuring the new page by passing required information as a navigation - // parameter - NavigationService.Navigate(_navElement, args.Arguments); + protected override async Task HandleInternalAsync(ILaunchActivatedEventArgs args) + { + // When the navigation stack isn't restored navigate to the first page, + // configuring the new page by passing required information as a navigation + // parameter + NavigationService.Navigate(_navElement, args.Arguments); - // You can use this sample to create toast notifications where needed in your app. - // Singleton.Instance.ShowSavedNotification(); - await Task.CompletedTask; - } + // You can use this sample to create toast notifications where needed in your app. + // Singleton.Instance.ShowSavedNotification(); + await Task.CompletedTask; + } - protected override bool CanHandleInternal(ILaunchActivatedEventArgs args) - { - // None of the ActivationHandlers has handled the app activation - return NavigationService.Frame.Content == null; - } + protected override bool CanHandleInternal(ILaunchActivatedEventArgs args) + { + // None of the ActivationHandlers has handled the app activation + return NavigationService.Frame.Content == null; } } diff --git a/CharacterMap/CharacterMap/Activation/FileActivationHandler.cs b/CharacterMap/CharacterMap/Activation/FileActivationHandler.cs index e3762009..3f9f38af 100644 --- a/CharacterMap/CharacterMap/Activation/FileActivationHandler.cs +++ b/CharacterMap/CharacterMap/Activation/FileActivationHandler.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Windows.ApplicationModel.Activation; +using Windows.ApplicationModel.Activation; namespace CharacterMap.Activation { diff --git a/CharacterMap/CharacterMap/App.xaml b/CharacterMap/CharacterMap/App.xaml index 5fd5e16b..8ec93ac1 100644 --- a/CharacterMap/CharacterMap/App.xaml +++ b/CharacterMap/CharacterMap/App.xaml @@ -31,6 +31,7 @@ + + - + + + + + + diff --git a/CharacterMap/CharacterMap/Themes/Generic.xaml b/CharacterMap/CharacterMap/Themes/Generic.xaml index e94e4d61..f358aa7e 100644 --- a/CharacterMap/CharacterMap/Themes/Generic.xaml +++ b/CharacterMap/CharacterMap/Themes/Generic.xaml @@ -161,6 +161,7 @@ + @@ -249,27 +250,28 @@ VerticalContentAlignment="Center" TextWrapping="WrapWholeWords" /> - + CornerRadius="8" + Foreground="White" + Style="{StaticResource MapInfoButtonStyle}"> - + @@ -906,4 +908,48 @@ + + diff --git a/CharacterMap/CharacterMap/Themes/SystemThemes.xaml b/CharacterMap/CharacterMap/Themes/SystemThemes.xaml index 04b5b962..694eb4af 100644 --- a/CharacterMap/CharacterMap/Themes/SystemThemes.xaml +++ b/CharacterMap/CharacterMap/Themes/SystemThemes.xaml @@ -13,6 +13,7 @@ 40 32 0 + 0 Segoe MDL2 Assets @@ -47,6 +48,9 @@ + + + diff --git a/CharacterMap/CharacterMap/Themes/ZuneThemeStyles.xaml b/CharacterMap/CharacterMap/Themes/ZuneThemeStyles.xaml index 0b9418d1..6e9b3edd 100644 --- a/CharacterMap/CharacterMap/Themes/ZuneThemeStyles.xaml +++ b/CharacterMap/CharacterMap/Themes/ZuneThemeStyles.xaml @@ -19,6 +19,7 @@ 32 0 + 0 @@ -137,6 +138,7 @@ + @@ -785,6 +787,17 @@ + + + + + + + + + + + diff --git a/CharacterMap/CharacterMap/ViewModels/CalligraphyViewModel.cs b/CharacterMap/CharacterMap/ViewModels/CalligraphyViewModel.cs index e9b41b02..9fc0be9e 100644 --- a/CharacterMap/CharacterMap/ViewModels/CalligraphyViewModel.cs +++ b/CharacterMap/CharacterMap/ViewModels/CalligraphyViewModel.cs @@ -1,425 +1,408 @@ -using CharacterMap.Core; -using CharacterMap.Models; -using CharacterMapCX; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Messaging; -using Microsoft.Graphics.Canvas; +using Microsoft.Graphics.Canvas; using Microsoft.Graphics.Canvas.Geometry; using Microsoft.Graphics.Canvas.Svg; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; using Windows.ApplicationModel; -using Windows.Foundation; -using Windows.Storage; -using Windows.Storage.Pickers; using Windows.UI; using Windows.UI.Input.Inking; using Windows.UI.Xaml.Media.Imaging; -namespace CharacterMap.ViewModels +namespace CharacterMap.ViewModels; + +public partial class CalligraphyViewModel : ViewModelBase { - public partial class CalligraphyViewModel : ViewModelBase - { - protected override bool TrackAnimation => true; + protected override bool TrackAnimation => true; - [ObservableProperty] private bool _isOverlayVisible = true; + [ObservableProperty] private bool _isOverlayVisible = true; - [ObservableProperty] private string _text; + [ObservableProperty] private string _text; - [ObservableProperty] private double _fontSize = 220d; + [ObservableProperty] private double _fontSize = 220d; - [ObservableProperty] InkStrokeManager _inkManager = null; + [ObservableProperty] InkStrokeManager _inkManager = null; - public FontVariant Face { get; } + public FontVariant Face { get; } - public CharacterRenderingOptions Options { get; } + public CharacterRenderingOptions Options { get; } - public ObservableCollection Histories { get; } = new(); + public ObservableCollection Histories { get; } = new(); - public CalligraphyViewModel(CharacterRenderingOptions options) - { - Face = options.Variant; - Options = options; - } + public CalligraphyViewModel(CharacterRenderingOptions options) + { + Face = options.Variant; + Options = options; + } - void EnsureManager(InkStrokeContainer container) - { - if (_inkManager is null) - InkManager = new InkStrokeManager(container); - } + void EnsureManager(InkStrokeContainer container) + { + if (_inkManager is null) + InkManager = new InkStrokeManager(container); + } - public async Task AddToHistoryAsync() - { - CalligraphyHistoryItem h = await InkManager.CreateHistoryItemAsync(FontSize, Text); - Histories.Add(h); - } + public async Task AddToHistoryAsync() + { + CalligraphyHistoryItem h = await InkManager.CreateHistoryItemAsync(FontSize, Text); + Histories.Add(h); + } - /// - /// Clear contents of Ink container and the Redo stack - /// - /// - public void Clear() - { - _inkManager.Clear(); - } + /// + /// Clear contents of Ink container and the Redo stack + /// + /// + public void Clear() + { + _inkManager.Clear(); + } - internal void OnStrokesErased(InkStrokeContainer container, IReadOnlyList strokes) - { - InkManager.OnErased(strokes); - } + internal void OnStrokesErased(InkStrokeContainer container, IReadOnlyList strokes) + { + InkManager.OnErased(strokes); + } - internal void OnStrokeDrawn(InkStrokeContainer container, IReadOnlyList strokes) - { - EnsureManager(container); - InkManager.OnDrawn(strokes); - } + internal void OnStrokeDrawn(InkStrokeContainer container, IReadOnlyList strokes) + { + EnsureManager(container); + InkManager.OnDrawn(strokes); + } - public async Task SaveAsync(IReadOnlyList strokes, ExportFormat format, ICanvasResourceCreatorWithDpi device, Rect bounds) + public async Task SaveAsync(IReadOnlyList strokes, ExportFormat format, ICanvasResourceCreatorWithDpi device, Rect bounds) + { + // 1. Setup and render ink strokes + bool isPng = format == ExportFormat.Png; + using CanvasSvgDocument svgDocument = new(device); + using CanvasRenderTarget target = new CanvasRenderTarget(device, (float)bounds.Width, (float)bounds.Height); + using (var ds = target.CreateDrawingSession()) { - // 1. Setup and render ink strokes - bool isPng = format == ExportFormat.Png; - using CanvasSvgDocument svgDocument = new(device); - using CanvasRenderTarget target = new CanvasRenderTarget(device, (float)bounds.Width, (float)bounds.Height); - using (var ds = target.CreateDrawingSession()) - { - if (isPng) - RenderBitmap(strokes, ds, bounds); - else - RenderSVG(strokes, svgDocument, ds, bounds); - } - - // 2. Pick save file - string ext = isPng ? "png" : "svg"; - - FileSavePicker picker = new (); - picker.DefaultFileExtension = $".{ext}"; - picker.FileTypeChoices.Add(ext.ToUpper(), new List { $".{ext}" }); - - // 3. Write output to save file - if (await picker.PickSaveFileAsync() is StorageFile file) - { - using var stream = await file.OpenAsync(FileAccessMode.ReadWrite); - - if (isPng) - await target.SaveAsync(stream, CanvasBitmapFileFormat.Png); - else - await svgDocument.SaveAsync(stream); - - // 3.1. If we're overwriting an existing file that was bigger - // than our new content, remove the old data. - stream.Size = stream.Position; - - // 4. Send In-app Notification - Messenger.Send(new AppNotificationMessage(true, new ExportResult(ExportState.Succeeded, file))); - } + if (isPng) + RenderBitmap(strokes, ds, bounds); + else + RenderSVG(strokes, svgDocument, ds, bounds); } - private static void RenderBitmap(IReadOnlyList sourceStrokes, CanvasDrawingSession ds, Rect bounds) - { - // We only want to render the drawn parts of the canvas but our strokes are - // relative to the entire size of the canvas, so we need to move them back. - - // 1. Create a translation to move drawing to 0,0 - Matrix3x2 translate = Matrix3x2.CreateTranslation((float)-bounds.X, (float)-bounds.Y); - - // 2. Clone the strokes and apply the translation to each of them - var strokes = sourceStrokes.Select(s => s.Clone()).ToList(); - foreach (var st in strokes) - st.PointTransform *= translate; - - // 3. Render the transformed strokes - ds.Clear(Colors.Transparent); - ds.DrawInk(strokes); - } + // 2. Pick save file + string ext = isPng ? "png" : "svg"; - private static void RenderSVG(IReadOnlyList strokes, CanvasSvgDocument svgDocument, CanvasDrawingSession ds, Rect bounds) + FileSavePicker picker = new(); + picker.DefaultFileExtension = $".{ext}"; + picker.FileTypeChoices.Add(ext.ToUpper(), new List { $".{ext}" }); + + // 3. Write output to save file + if (await picker.PickSaveFileAsync() is StorageFile file) { - // 1. We only want to render the drawn parts of the canvas but our strokes are - // relative to the entire size of the canvas, so set the viewBox to only - // the drawn bounds. - svgDocument.Root.SetRectangleAttribute("viewBox", bounds); - - // 2. Create an SVG path for each stroke - InkStroke[] s = new InkStroke[1]; - foreach (InkStroke stroke in strokes) - { - s[0] = stroke; - SVGPathReciever pathReceiver = new (CanvasGeometry.CreateInk(ds, s)); - - CanvasSvgNamedElement element = svgDocument.Root.CreateAndAppendNamedChildElement("path"); - element.SetStringAttribute("d", pathReceiver.GetPathData()); - element.SetColorAttribute("fill", stroke.DrawingAttributes.Color); - } + using var stream = await file.OpenAsync(FileAccessMode.ReadWrite); + + if (isPng) + await target.SaveAsync(stream, CanvasBitmapFileFormat.Png); + else + await svgDocument.SaveAsync(stream); + + // 3.1. If we're overwriting an existing file that was bigger + // than our new content, remove the old data. + stream.Size = stream.Position; + + // 4. Send In-app Notification + Messenger.Send(new AppNotificationMessage(true, new ExportResult(ExportState.Succeeded, file))); } } - /// - /// Helper class to maintain reference between strokes in - /// Undo/Redo/Container ink strokes, including when cloned. - /// - public class InkStrokeReference + private static void RenderBitmap(IReadOnlyList sourceStrokes, CanvasDrawingSession ds, Rect bounds) { - public InkStroke ActiveStroke { get; private set; } + // We only want to render the drawn parts of the canvas but our strokes are + // relative to the entire size of the canvas, so we need to move them back. + + // 1. Create a translation to move drawing to 0,0 + Matrix3x2 translate = Matrix3x2.CreateTranslation((float)-bounds.X, (float)-bounds.Y); - public InkStrokeReference(InkStroke stroke) + // 2. Clone the strokes and apply the translation to each of them + var strokes = sourceStrokes.Select(s => s.Clone()).ToList(); + foreach (var st in strokes) + st.PointTransform *= translate; + + // 3. Render the transformed strokes + ds.Clear(Colors.Transparent); + ds.DrawInk(strokes); + } + + private static void RenderSVG(IReadOnlyList strokes, CanvasSvgDocument svgDocument, CanvasDrawingSession ds, Rect bounds) + { + // 1. We only want to render the drawn parts of the canvas but our strokes are + // relative to the entire size of the canvas, so set the viewBox to only + // the drawn bounds. + svgDocument.Root.SetRectangleAttribute("viewBox", bounds); + + // 2. Create an SVG path for each stroke + InkStroke[] s = new InkStroke[1]; + foreach (InkStroke stroke in strokes) { - ActiveStroke = stroke; - } + s[0] = stroke; + SVGPathReciever pathReceiver = new(CanvasGeometry.CreateInk(ds, s)); - public InkStroke Clone() => (ActiveStroke = ActiveStroke.Clone()); + CanvasSvgNamedElement element = svgDocument.Root.CreateAndAppendNamedChildElement("path"); + element.SetStringAttribute("d", pathReceiver.GetPathData()); + element.SetColorAttribute("fill", stroke.DrawingAttributes.Color); + } } +} + +/// +/// Helper class to maintain reference between strokes in +/// Undo/Redo/Container ink strokes, including when cloned. +/// +public class InkStrokeReference +{ + public InkStroke ActiveStroke { get; private set; } - [ObservableObject] - public partial class InkStrokeManager + public InkStrokeReference(InkStroke stroke) { - [ObservableProperty] private bool _hasStrokes; + ActiveStroke = stroke; + } - [ObservableProperty] private bool _canUndo; + public InkStroke Clone() => (ActiveStroke = ActiveStroke.Clone()); +} - [ObservableProperty] private bool _canRedo; +[ObservableObject] +public partial class InkStrokeManager +{ + [ObservableProperty] private bool _hasStrokes; - private Stack _redoStack { get; } = new(); - private Stack _undoStack { get; } = new(); - private HashSet _strokeSet { get; } = new HashSet(); + [ObservableProperty] private bool _canUndo; - private InkStrokeContainer _container; + [ObservableProperty] private bool _canRedo; - public InkStrokeManager(InkStrokeContainer container) - { - _container = container; - } + private Stack _redoStack { get; } = new(); + private Stack _undoStack { get; } = new(); + private HashSet _strokeSet { get; } = new HashSet(); - /// - /// Clears the ink canvas, undo & redo stacks and updates UI bindings. - /// - public void Clear() - { - _strokeSet.Clear(); - _container.Clear(); + private InkStrokeContainer _container; - _redoStack.Clear(); - _undoStack.Clear(); + public InkStrokeManager(InkStrokeContainer container) + { + _container = container; + } - UpdateControls(); - } + /// + /// Clears the ink canvas, undo & redo stacks and updates UI bindings. + /// + public void Clear() + { + _strokeSet.Clear(); + _container.Clear(); - /// - /// Gets an existing reference too an InkStroke or creates a new one - /// if one doesn't exist - /// - /// - /// - public InkStrokeReference GetReference(InkStroke stroke) - { - if (FindReference(stroke) is InkStrokeReference r) - return r; + _redoStack.Clear(); + _undoStack.Clear(); - r = new InkStrokeReference(stroke); - _strokeSet.Add(r); + UpdateControls(); + } + + /// + /// Gets an existing reference too an InkStroke or creates a new one + /// if one doesn't exist + /// + /// + /// + public InkStrokeReference GetReference(InkStroke stroke) + { + if (FindReference(stroke) is InkStrokeReference r) return r; - } - /// - /// Finds an existing reference too an InkStroke - /// - /// - /// - public InkStrokeReference FindReference(InkStroke stroke) - { - return _strokeSet.FirstOrDefault(s => s.ActiveStroke == stroke); - } + r = new InkStrokeReference(stroke); + _strokeSet.Add(r); + return r; + } - public void UpdateControls() - { - HasStrokes = _container.GetStrokes().Any(); - CanUndo = _undoStack.Count > 0; - CanRedo = _redoStack.Count > 0; - } + /// + /// Finds an existing reference too an InkStroke + /// + /// + /// + public InkStrokeReference FindReference(InkStroke stroke) + { + return _strokeSet.FirstOrDefault(s => s.ActiveStroke == stroke); + } - internal void OnErased(IReadOnlyList strokes) - { - _undoStack.Push(new StrokeErasedAction(this, strokes)); - _redoStack.Clear(); - UpdateControls(); - } + public void UpdateControls() + { + HasStrokes = _container.GetStrokes().Any(); + CanUndo = _undoStack.Count > 0; + CanRedo = _redoStack.Count > 0; + } - internal void OnDrawn(IReadOnlyList strokes) - { - _undoStack.Push(new StrokeDrawnAction(this, strokes)); - CanUndo = true; - HasStrokes = true; + internal void OnErased(IReadOnlyList strokes) + { + _undoStack.Push(new StrokeErasedAction(this, strokes)); + _redoStack.Clear(); + UpdateControls(); + } - // When user draws a stroke manually, clear the redo stack - _redoStack.Clear(); - CanRedo = false; - } + internal void OnDrawn(IReadOnlyList strokes) + { + _undoStack.Push(new StrokeDrawnAction(this, strokes)); + CanUndo = true; + HasStrokes = true; - public bool Redo() - { - if (CanRedo) - { - // 1. Get and execute the most recent redo command - var command = _redoStack.Pop(); - command.Redo(_container); + // When user draws a stroke manually, clear the redo stack + _redoStack.Clear(); + CanRedo = false; + } - // 2. Push it to the undo stack - _undoStack.Push(command); + public bool Redo() + { + if (CanRedo) + { + // 1. Get and execute the most recent redo command + var command = _redoStack.Pop(); + command.Redo(_container); - // 3. Update UI commands - UpdateControls(); - return true; - } + // 2. Push it to the undo stack + _undoStack.Push(command); - return false; + // 3. Update UI commands + UpdateControls(); + return true; } - public bool Undo() - { - if (CanUndo) - { - // 1. Get and execute the most recent undo command - var command = _undoStack.Pop(); - command.Undo(_container); + return false; + } - // 2. Push it to the redo stack - _redoStack.Push(command); + public bool Undo() + { + if (CanUndo) + { + // 1. Get and execute the most recent undo command + var command = _undoStack.Pop(); + command.Undo(_container); - // 3. Update UI commands - UpdateControls(); - return true; - } + // 2. Push it to the redo stack + _redoStack.Push(command); - return false; + // 3. Update UI commands + UpdateControls(); + return true; } - public async Task CreateHistoryItemAsync(double fontSize, string text) - { - // 1. Create a History Item - CalligraphyHistoryItem h = new(_container.GetStrokes(), fontSize, text, _container.BoundingRect); + return false; + } + + public async Task CreateHistoryItemAsync(double fontSize, string text) + { + // 1. Create a History Item + CalligraphyHistoryItem h = new(_container.GetStrokes(), fontSize, text, _container.BoundingRect); - // 2. Render a thumbnail of the drawing to a memory stream - using MemoryStream m = new(); - await _container.SaveAsync(m.AsOutputStream()); - m.Seek(0, SeekOrigin.Begin); + // 2. Render a thumbnail of the drawing to a memory stream + using MemoryStream m = new(); + await _container.SaveAsync(m.AsOutputStream()); + m.Seek(0, SeekOrigin.Begin); - // 3. Create a XAML BitmapImage from the memory stream - BitmapImage b = new(); - b.DecodePixelType = DecodePixelType.Logical; - b.DecodePixelWidth = 150; - await b.SetSourceAsync(m.AsRandomAccessStream()); + // 3. Create a XAML BitmapImage from the memory stream + BitmapImage b = new(); + b.DecodePixelType = DecodePixelType.Logical; + b.DecodePixelWidth = 150; + await b.SetSourceAsync(m.AsRandomAccessStream()); - // 4. Add the thumbnail to the History Item - h.Thumbnail = b; + // 4. Add the thumbnail to the History Item + h.Thumbnail = b; - return h; - } + return h; } +} + +public abstract class InkActionBase +{ + public abstract void Undo(InkStrokeContainer container); + public abstract void Redo(InkStrokeContainer container); +} - public abstract class InkActionBase +public class StrokeDrawnAction : InkActionBase +{ + private readonly List _strokes; + + public StrokeDrawnAction(InkStrokeManager manager, IEnumerable strokes) { - public abstract void Undo(InkStrokeContainer container); - public abstract void Redo(InkStrokeContainer container); + _strokes = strokes.Select(s => manager.GetReference(s)).ToList(); } - public class StrokeDrawnAction : InkActionBase + public override void Redo(InkStrokeContainer container) { - private readonly List _strokes; + foreach (var stroke in _strokes) + container.AddStroke(stroke.Clone()); + } - public StrokeDrawnAction(InkStrokeManager manager, IEnumerable strokes) + public override void Undo(InkStrokeContainer container) + { + // Delete the strokes from the canvas + foreach (var stroke in _strokes) { - _strokes = strokes.Select(s => manager.GetReference(s)).ToList(); + var cStroke = stroke.ActiveStroke; + cStroke.Selected = true; + container.DeleteSelected(); + cStroke.Selected = false; } + } +} - public override void Redo(InkStrokeContainer container) - { - foreach (var stroke in _strokes) - container.AddStroke(stroke.Clone()); - } +public class StrokeErasedAction : InkActionBase +{ + private readonly List _strokes; - public override void Undo(InkStrokeContainer container) - { - // Delete the strokes from the canvas - foreach (var stroke in _strokes) - { - var cStroke = stroke.ActiveStroke; - cStroke.Selected = true; - container.DeleteSelected(); - cStroke.Selected = false; - } - } + public StrokeErasedAction(InkStrokeManager manager, IEnumerable strokes) + { + _strokes = strokes.Select(s => manager.GetReference(s)).ToList(); } - public class StrokeErasedAction : InkActionBase + public override void Redo(InkStrokeContainer container) { - private readonly List _strokes; - - public StrokeErasedAction(InkStrokeManager manager, IEnumerable strokes) + foreach (var stroke in _strokes) { - _strokes = strokes.Select(s => manager.GetReference(s)).ToList(); + stroke.ActiveStroke.Selected = true; + container.DeleteSelected(); } + } - public override void Redo(InkStrokeContainer container) + public override void Undo(InkStrokeContainer container) + { + foreach (var stroke in _strokes) { - foreach (var stroke in _strokes) - { - stroke.ActiveStroke.Selected = true; - container.DeleteSelected(); - } + container.AddStroke(stroke.Clone()); + stroke.ActiveStroke.Selected = true; // select it so InkToolbar deletion works properly } - public override void Undo(InkStrokeContainer container) - { - foreach (var stroke in _strokes) - { - container.AddStroke(stroke.Clone()); - stroke.ActiveStroke.Selected = true; // select it so InkToolbar deletion works properly - } - - // Unselect everything to stop container.DeleteSelected() deleting everything - foreach (var stroke in _strokes) - stroke.ActiveStroke.Selected = false; - } + // Unselect everything to stop container.DeleteSelected() deleting everything + foreach (var stroke in _strokes) + stroke.ActiveStroke.Selected = false; } +} - public class CalligraphyHistoryItem +public class CalligraphyHistoryItem +{ + /// + /// Only for use by VS Designer + /// + public CalligraphyHistoryItem() { - /// - /// Only for use by VS Designer - /// - public CalligraphyHistoryItem() - { - if (DesignMode.DesignModeEnabled is false) - throw new InvalidOperationException("Only for use by VS Designer"); - } + if (DesignMode.DesignModeEnabled is false) + throw new InvalidOperationException("Only for use by VS Designer"); + } - public CalligraphyHistoryItem(IReadOnlyList strokes, double fontSize, string text, Rect bounds) - { - _strokes = strokes.Select(s => s.Clone()).ToList(); - FontSize = fontSize; - Text = text; - Bounds = bounds; - } + public CalligraphyHistoryItem(IReadOnlyList strokes, double fontSize, string text, Rect bounds) + { + _strokes = strokes.Select(s => s.Clone()).ToList(); + FontSize = fontSize; + Text = text; + Bounds = bounds; + } - private IReadOnlyList _strokes { get; } + private IReadOnlyList _strokes { get; } - public BitmapImage Thumbnail { get; set; } + public BitmapImage Thumbnail { get; set; } - public double FontSize { get; } + public double FontSize { get; } - public string Text { get; } + public string Text { get; } - public Rect Bounds { get; } + public Rect Bounds { get; } - public List GetStrokes() - { - return _strokes.Select(s => s.Clone()).ToList(); - } + public List GetStrokes() + { + return _strokes.Select(s => s.Clone()).ToList(); } - } diff --git a/CharacterMap/CharacterMap/ViewModels/CollectionManagementViewModel.cs b/CharacterMap/CharacterMap/ViewModels/CollectionManagementViewModel.cs index 30697652..e844c0f9 100644 --- a/CharacterMap/CharacterMap/ViewModels/CollectionManagementViewModel.cs +++ b/CharacterMap/CharacterMap/ViewModels/CollectionManagementViewModel.cs @@ -1,173 +1,164 @@ -using CharacterMap.Core; -using CharacterMap.Models; -using CharacterMap.Services; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using CommunityToolkit.Mvvm.DependencyInjection; -using CharacterMap.Helpers; -using CommunityToolkit.Mvvm.ComponentModel; - -namespace CharacterMap.ViewModels +namespace CharacterMap.ViewModels; + +internal partial class CollectionManagementViewModel : ViewModelBase { - internal partial class CollectionManagementViewModel : ViewModelBase - { - protected override bool CaptureContext => true; + protected override bool CaptureContext => true; - #region Properties + #region Properties - [ObservableProperty] string _collectionExportProgress; - [ObservableProperty] bool _isSaving = false; - [ObservableProperty] bool _isExporting = false; + [ObservableProperty] string _collectionExportProgress; + [ObservableProperty] bool _isSaving = false; + [ObservableProperty] bool _isExporting = false; - [ObservableProperty] List _collections; - [ObservableProperty] ObservableCollection _fontList; - [ObservableProperty] ObservableCollection _collectionFonts; + [ObservableProperty] List _collections; + [ObservableProperty] ObservableCollection _fontList; + [ObservableProperty] ObservableCollection _collectionFonts; - public ObservableCollection SelectedFonts = new(); + public ObservableCollection SelectedFonts = new(); - public ObservableCollection SelectedCollectionFonts = new(); - public UserCollectionsService CollectionService { get; private set; } = null; + public ObservableCollection SelectedCollectionFonts = new(); + public UserCollectionsService CollectionService { get; private set; } = null; - private UserFontCollection _selectedCollection; - public UserFontCollection SelectedCollection + private UserFontCollection _selectedCollection; + public UserFontCollection SelectedCollection + { + get => _selectedCollection; + set { - get => _selectedCollection; - set - { - if (Set(ref _selectedCollection, value) && value != null) - RefreshFontLists(); - } + if (Set(ref _selectedCollection, value) && value != null) + RefreshFontLists(); } + } - #endregion + #endregion - public void Activate() - { - if (CollectionService is null) - CollectionService = Ioc.Default.GetService(); + public void Activate() + { + if (CollectionService is null) + CollectionService = Ioc.Default.GetService(); - RefreshCollections(); - RefreshFontLists(); - } + RefreshCollections(); + RefreshFontLists(); + } - public void Deactivate() - { - _ = SaveAsync(); - SelectedCollection = null; - RefreshFontLists(); - } + public void Deactivate() + { + _ = SaveAsync(); + SelectedCollection = null; + RefreshFontLists(); + } - void RefreshCollections() - { - Collections = CollectionService.Items; - } + public void RefreshCollections() + { + // To work around a bug that this is technically + // the same collection every time we need to + // make sure we call .ToList() or UI bindings will + // not work correctly. + Collections = CollectionService.Items.ToList(); + OnPropertyChanged(nameof(Collections)); + } - public void RefreshFontLists() + public void RefreshFontLists() + { + if (SelectedCollection is null) { - if (SelectedCollection is null) - { - // clear all the things - CollectionFonts = new(); - FontList = new (); - return; - } - - // 1. Get list of fonts in and not in the collection - var collectionFonts = FontFinder.Fonts.Where(f => SelectedCollection.Fonts.Contains(f.Name)).ToList(); - var systemFonts = FontFinder.Fonts.Except(collectionFonts).ToList(); - - // 2. Create binding lists - FontList = new (systemFonts); - CollectionFonts = new (collectionFonts); + // clear all the things + CollectionFonts = new(); + FontList = new(); + return; } - public void AddToCollection() - { - if (SelectedFonts is null || SelectedFonts.Count == 0) - return; + // 1. Get list of fonts in and not in the collection + var collectionFonts = FontFinder.Fonts.Where(f => SelectedCollection.Fonts.Contains(f.Name)).ToList(); + var systemFonts = FontFinder.Fonts.Except(collectionFonts).ToList(); - var fonts = SelectedFonts.ToList(); - foreach (var font in fonts) - if (FontList.Remove(font)) - CollectionFonts.AddSorted(font); + // 2. Create binding lists + FontList = new(systemFonts); + CollectionFonts = new(collectionFonts); + } - StartSave(); - } + public void AddToCollection() + { + if (SelectedFonts is null || SelectedFonts.Count == 0) + return; - public void RemoveFromCollection() - { - if (SelectedCollectionFonts is null || SelectedCollectionFonts.Count == 0) - return; + var fonts = SelectedFonts.ToList(); + foreach (var font in fonts) + if (FontList.Remove(font)) + CollectionFonts.AddSorted(font); - var fonts = SelectedCollectionFonts.ToList(); - foreach (var font in fonts) - if (CollectionFonts.Remove(font)) - FontList.AddSorted(font); + StartSave(); + } - StartSave(); - } + public void RemoveFromCollection() + { + if (SelectedCollectionFonts is null || SelectedCollectionFonts.Count == 0) + return; + + var fonts = SelectedCollectionFonts.ToList(); + foreach (var font in fonts) + if (CollectionFonts.Remove(font)) + FontList.AddSorted(font); + + StartSave(); + } + + public void StartSave() + { + _ = SaveAsync(); + } + + async Task SaveAsync() + { + if (SelectedCollection is null || IsSaving) + return; + + IsSaving = true; - public void StartSave() + try { - _ = SaveAsync(); + SelectedCollection.Fonts = new HashSet(CollectionFonts.Select(c => c.Name)); + await CollectionService.SaveCollectionAsync(SelectedCollection); } - - async Task SaveAsync() + finally { - if (SelectedCollection is null || IsSaving) - return; - - IsSaving = true; - - try - { - SelectedCollection.Fonts = new HashSet(CollectionFonts.Select(c => c.Name)); - await CollectionService.SaveCollectionAsync(SelectedCollection); - } - finally - { - IsSaving = false; - } + IsSaving = false; } + } + + internal async void ExportAsZip() + { + IsExporting = true; - internal async void ExportAsZip() + try + { + await ExportManager.ExportCollectionAsZipAsync( + CollectionFonts, + SelectedCollection, + p => OnSyncContext(() => CollectionExportProgress = p)); + } + finally { - IsExporting = true; - - try - { - await ExportManager.ExportCollectionAsZipAsync( - CollectionFonts, - SelectedCollection, - p => OnSyncContext(() => CollectionExportProgress = p)); - } - finally - { - IsExporting = false; - } + IsExporting = false; } + } + + internal async void ExportAsFolder() + { + IsExporting = true; - internal async void ExportAsFolder() + try + { + await ExportManager.ExportCollectionToFolderAsync( + CollectionFonts, + p => OnSyncContext(() => CollectionExportProgress = p)); + } + finally { - IsExporting = true; - - try - { - await ExportManager.ExportCollectionToFolderAsync( - CollectionFonts, - p => OnSyncContext(() => CollectionExportProgress = p)); - } - finally - { - IsExporting = false; - } + IsExporting = false; } } } diff --git a/CharacterMap/CharacterMap/ViewModels/ExportViewModel.cs b/CharacterMap/CharacterMap/ViewModels/ExportViewModel.cs index 6bda5bf9..6ec15d29 100644 --- a/CharacterMap/CharacterMap/ViewModels/ExportViewModel.cs +++ b/CharacterMap/CharacterMap/ViewModels/ExportViewModel.cs @@ -1,198 +1,178 @@ -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CommunityToolkit.Mvvm.Messaging; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Windows.Storage; -using Windows.System; +using Windows.System; using Windows.UI; using Windows.UI.Xaml; -namespace CharacterMap.ViewModels +namespace CharacterMap.ViewModels; + +public class ExportViewModel : ViewModelBase { - public class ExportViewModel : ViewModelBase - { - #region Properties + #region Properties - protected override bool TrackAnimation => true; + protected override bool TrackAnimation => true; - private InstalledFont _font { get; } - public FontVariant Font { get; set; } + private InstalledFont _font { get; } + public FontVariant Font { get; set; } - public CharacterRenderingOptions Options { get; set; } + public CharacterRenderingOptions Options { get; set; } - public IReadOnlyList Characters { get => GetV>(); private set => Set(value); } - public IList Categories { get => GetV>(); private set => Set(value); } + public IReadOnlyList Characters { get => GetV>(); private set => Set(value); } + public IList Categories { get => GetV>(); private set => Set(value); } - public bool HideWhitespace { get => GetV(true); set => Set(value); } - public bool SkipBlankGlyphs { get => GetV(true); set => Set(value); } - public double GlyphSize { get => GetV(0d); set => Set(value); } - public Color GlyphColor { get => GetV(Colors.White); set => Set(value); } - public bool ExportColor { get => GetV(true); set => Set(value); } - public bool IsWhiteChecked { get => GetV(false); set => Set(value); } - public bool IsBlackChecked { get => GetV(false); set => Set(value); } - public bool IsExporting { get => GetV(false); set => Set(value); } - public bool ShowCancelExport { get => GetV(false); set => Set(value); } - public int SelectedFormat { get => GetV((int)ExportFormat.Png); set => Set(value); } - public string ExportMessage { get => Get(); set => Set(value); } - public string Summary { get => Get(); private set => Set(value); } - public string SelectedText { get => Get(); private set => Set(value); } - public ElementTheme PreviewTheme { get => GetV(ResourceHelper.GetEffectiveTheme()); set => Set(value); } + public bool HideWhitespace { get => GetV(true); set => Set(value); } + public bool SkipBlankGlyphs { get => GetV(true); set => Set(value); } + public double GlyphSize { get => GetV(0d); set => Set(value); } + public Color GlyphColor { get => GetV(Colors.White); set => Set(value); } + public bool ExportColor { get => GetV(true); set => Set(value); } + public bool IsWhiteChecked { get => GetV(false); set => Set(value); } + public bool IsBlackChecked { get => GetV(false); set => Set(value); } + public bool IsExporting { get => GetV(false); set => Set(value); } + public bool ShowCancelExport { get => GetV(false); set => Set(value); } + public int SelectedFormat { get => GetV((int)ExportFormat.Png); set => Set(value); } + public string ExportMessage { get => Get(); set => Set(value); } + public string Summary { get => Get(); private set => Set(value); } + public string SelectedText { get => Get(); private set => Set(value); } + public ElementTheme PreviewTheme { get => GetV(ResourceHelper.GetEffectiveTheme()); set => Set(value); } - public bool CanContinue => Characters.Count > 0; - public bool IsPngFormat => SelectedFormat == (int)ExportFormat.Png; + public bool CanContinue => Characters.Count > 0; + public bool IsPngFormat => SelectedFormat == (int)ExportFormat.Png; - #endregion + #endregion - private CancellationTokenSource _currentToken = null; + private CancellationTokenSource _currentToken = null; - DispatcherQueue _dispatcherQueue { get; } + DispatcherQueue _dispatcherQueue { get; } - public ExportViewModel(FontMapViewModel viewModel) - { - _font = viewModel.SelectedFont.Font; - Categories = viewModel.SelectedGlyphCategories.ToList(); // Makes a copy of the list - Font = viewModel.RenderingOptions.Variant; - Options = viewModel.RenderingOptions; - GlyphSize = viewModel.Settings.PngSize; - ExportColor = viewModel.ShowColorGlyphs; + public ExportViewModel(FontMapViewModel viewModel) + { + _font = viewModel.SelectedFont.Font; + Categories = viewModel.SelectedGlyphCategories.ToList(); // Makes a copy of the list + Font = viewModel.RenderingOptions.Variant; + Options = viewModel.RenderingOptions; + GlyphSize = viewModel.Settings.PngSize; + ExportColor = viewModel.ShowColorGlyphs; - IsWhiteChecked = ResourceHelper.GetEffectiveTheme() == ElementTheme.Dark; - IsBlackChecked = ResourceHelper.GetEffectiveTheme() == ElementTheme.Light; + IsWhiteChecked = ResourceHelper.GetEffectiveTheme() == ElementTheme.Dark; + IsBlackChecked = ResourceHelper.GetEffectiveTheme() == ElementTheme.Light; - _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); + _dispatcherQueue = DispatcherQueue.GetForCurrentThread(); - UpdateCharacters(); - } + UpdateCharacters(); + } - protected override void OnPropertyChangeNotified(string property) + protected override void OnPropertyChangeNotified(string property) + { + switch (property) { - switch (property) - { - case nameof(GlyphColor): - if (GlyphColor != Colors.Black) - IsBlackChecked = false; - if (GlyphColor != Colors.White) - IsWhiteChecked = false; - break; - - case nameof(IsWhiteChecked) when IsWhiteChecked: - GlyphColor = Colors.White; - break; - - case nameof(IsBlackChecked) when IsBlackChecked: - GlyphColor = Colors.Black; - break; - - case nameof(HideWhitespace): - UpdateCharacters(); - break; - - case nameof(SelectedFormat): - OnPropertyChanged(nameof(IsPngFormat)); - UpdateSummary(); - break; - - case nameof(Characters): - UpdateSummary(); - OnPropertyChanged(nameof(CanContinue)); - break; - } + case nameof(GlyphColor): + if (GlyphColor != Colors.Black) + IsBlackChecked = false; + if (GlyphColor != Colors.White) + IsWhiteChecked = false; + break; + + case nameof(IsWhiteChecked) when IsWhiteChecked: + GlyphColor = Colors.White; + break; + + case nameof(IsBlackChecked) when IsBlackChecked: + GlyphColor = Colors.Black; + break; + + case nameof(HideWhitespace): + UpdateCharacters(); + break; + + case nameof(SelectedFormat): + OnPropertyChanged(nameof(IsPngFormat)); + UpdateSummary(); + break; + + case nameof(Characters): + UpdateSummary(); + OnPropertyChanged(nameof(CanContinue)); + break; } + } - private void UpdateCharacters() + private void UpdateCharacters() + { + // Fast path : all characters; + if (!Categories.Any(c => !c.IsSelected) && !HideWhitespace) { - // Fast path : all characters; - if (!Categories.Any(c => !c.IsSelected) && !HideWhitespace) - { - Characters = Font.Characters; - return; - } - - // Filter characters - var chars = Font.Characters.AsEnumerable(); - if (HideWhitespace) - chars = Font.Characters.Where(c => !Unicode.IsWhiteSpaceOrControl(c.UnicodeIndex)); - - foreach (var cat in Categories.Where(c => !c.IsSelected)) - chars = chars.Where(c => c.Range != cat.Range); - - Characters = chars.ToList(); + Characters = Font.Characters; + return; } - public void UpdateCategories(IList value) - { - Set(value, nameof(Categories), false); - UpdateCharacters(); - OnPropertyChanged(nameof(Categories)); - } + // Filter characters + Characters = Unicode.FilterCharacters(Font.Characters, Categories, HideWhitespace); + } + + public void UpdateCategories(IList value) + { + Set(value, nameof(Categories), false); + UpdateCharacters(); + OnPropertyChanged(nameof(Categories)); + } - public void UpdateSummary() + public void UpdateSummary() + { + SelectedText = Localization.Get( + "ExportGlyphSelectedCharacters/Text", + Characters.Count, + Font.Characters.Count); + + Summary = Localization.Get( + "ExportGlyphsSummary/Text", + Characters.Count, + ((ExportFormat)SelectedFormat).ToString().ToUpper()); + } + + public async void StartExport() + { + ExportOptions export = new( + (ExportFormat)SelectedFormat, + ExportColor ? ExportStyle.ColorGlyph : ExportStyle.Black) { - SelectedText = Localization.Get( - "ExportGlyphSelectedCharacters/Text", - Characters.Count, - Font.Characters.Count); - - Summary = Localization.Get( - "ExportGlyphsSummary/Text", - Characters.Count, - ((ExportFormat)SelectedFormat).ToString().ToUpper()); - } + PreferredColor = GlyphColor, + PreferredSize = GlyphSize, + SkipEmptyGlyphs = SkipBlankGlyphs + }; - public async void StartExport() + IsExporting = true; + + _currentToken = new CancellationTokenSource(); + + int exported = 0; + ExportGlyphsResult result = await ExportManager.ExportGlyphsToFolderAsync( + _font, Options, Characters, export, (index, count) => { - ExportOptions export = new( - (ExportFormat)SelectedFormat, - ExportColor ? ExportStyle.ColorGlyph : ExportStyle.Black) - { - PreferredColor = GlyphColor, - PreferredSize = GlyphSize, - SkipEmptyGlyphs = SkipBlankGlyphs - }; - - IsExporting = true; - - _currentToken = new CancellationTokenSource(); - - int exported = 0; - ExportGlyphsResult result = await ExportManager.ExportGlyphsToFolderAsync( - _font, Options, Characters, export, (index, count) => + exported = index; + _dispatcherQueue.TryEnqueue(() => { - exported = index; - _dispatcherQueue.TryEnqueue(() => - { - ShowCancelExport = true; - ExportMessage = Localization.Get("ExportGlyphsProgressMessage/Text", index, count); - }); - }, _currentToken.Token); - - if (result is not null) - { - int count = _currentToken.IsCancellationRequested ? exported - 1 : exported; - WeakReferenceMessenger.Default.Send( - new AppNotificationMessage(true, result)); - } - - ExportMessage = ""; - ShowCancelExport = false; - IsExporting = false; - } + ShowCancelExport = true; + ExportMessage = Localization.Get("ExportGlyphsProgressMessage/Text", index, count); + }); + }, _currentToken.Token); - public void CancelExport() + if (result is not null) { - _currentToken?.Cancel(); + int count = _currentToken.IsCancellationRequested ? exported - 1 : exported; + WeakReferenceMessenger.Default.Send( + new AppNotificationMessage(true, result)); } - public void ToggleTheme() - { - PreviewTheme = PreviewTheme == ElementTheme.Dark ? ElementTheme.Light : ElementTheme.Dark; - } + ExportMessage = ""; + ShowCancelExport = false; + IsExporting = false; + } + + public void CancelExport() + { + _currentToken?.Cancel(); + } + + public void ToggleTheme() + { + PreviewTheme = PreviewTheme == ElementTheme.Dark ? ElementTheme.Light : ElementTheme.Dark; } } diff --git a/CharacterMap/CharacterMap/ViewModels/FontMapViewModel.cs b/CharacterMap/CharacterMap/ViewModels/FontMapViewModel.cs index c0b74835..a50186cb 100644 --- a/CharacterMap/CharacterMap/ViewModels/FontMapViewModel.cs +++ b/CharacterMap/CharacterMap/ViewModels/FontMapViewModel.cs @@ -1,731 +1,756 @@ -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Provider; -using CharacterMap.Services; -using CharacterMap.Views; -using CharacterMapCX; -using CommunityToolkit.Mvvm.ComponentModel; +using CharacterMap.Views; using CommunityToolkit.Mvvm.Input; -using CommunityToolkit.Mvvm.Messaging; using Microsoft.Graphics.Canvas.Text; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Threading.Tasks; +using System.Collections; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.ApplicationModel.Core; -using Windows.Storage; +using Windows.System; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; -namespace CharacterMap.ViewModels +namespace CharacterMap.ViewModels; + +public enum FontDisplayMode { - public enum FontDisplayMode - { - CharacterMap = 0, - GlyphMap = 1, - TypeRamp = 2 - } + CharacterMap = 0, + GlyphMap = 1, + TypeRamp = 2 +} + +public partial class RampOption : ObservableObject +{ + public int FontSize { get; set; } = 12; + [ObservableProperty] + CharacterRenderingOptions _option; +} - public partial class RampOption : ObservableObject +public partial class FontMapViewModel : ViewModelBase +{ + #region Properties + + private bool _blockChar = false; + + protected override bool TrackAnimation => true; + + private NativeInterop _interop { get; } + + private Debouncer _searchDebouncer { get; } + + private ConcurrencyToken.ConcurrencyTokenGenerator _searchTokenFactory { get; } + + private int[] _rampSizes { get; } = new[] { 12, 18, 24, 48, 72, 96, 110, 134 }; + + public AppSettings Settings { get; } + + public StorageFile SourceFile { get => Get(); set { if (Set(value)) { OnPropertyChanged(nameof(IsInstallable)); } } } + + public ExportStyle BlackColor { get; } = ExportStyle.Black; + public ExportStyle WhiteColor { get; } = ExportStyle.White; + public ExportStyle GlyphColor { get; } = ExportStyle.ColorGlyph; + + public IDialogService DialogService { get; } + public RelayCommand CommandSavePng { get; } + public RelayCommand CommandSaveSvg { get; } + public RelayCommand ToggleDev { get; } + public DWriteFallbackFont FallbackFont => FontFinder.Fallback; // Do *not* use { get;} here + public bool IsExternalFile { get; set; } + internal bool IsLoadingCharacters { get; private set; } + + public TypographyFeatureInfo SelectedTypography { get => GetV(TypographyFeatureInfo.None); set => Set(value ?? TypographyFeatureInfo.None); } + public TypographyFeatureInfo SelectedCharTypography { get => GetV(TypographyFeatureInfo.None); set => Set(value ?? TypographyFeatureInfo.None); } + public List SelectedGlyphCategories { get => Get>(); private set => Set(value); } + public List Ramps { get; } + + [ObservableProperty] CharacterRenderingOptions _renderingOptions; + [ObservableProperty] CanvasTextLayoutAnalysis _selectedCharAnalysis; + [ObservableProperty] List _selectedCharVariations; + [ObservableProperty] UnihanData _unihanData; + [ObservableProperty] IReadOnlyList _rampOptions; + [ObservableProperty] IReadOnlyList _chars; + [ObservableProperty] IEnumerable _searchResults; + [ObservableProperty] IReadOnlyList _variationAxis; + [ObservableProperty] IReadOnlyList _providers; + [ObservableProperty] IReadOnlyList _typographyFeatures; + [ObservableProperty] ObservableCollection _groupedChars; + + [ObservableProperty] bool _showColorGlyphs = true; + [ObservableProperty] bool _importButtonEnabled = true; + [ObservableProperty] bool _showingUnihan = true; + [ObservableProperty] bool _isLoading; + [ObservableProperty] bool _isSearchGrouped; + [ObservableProperty] bool _hasFontOptions; + [ObservableProperty] bool _isSvgChar; + [ObservableProperty] bool _isSequenceRootVisible; + [ObservableProperty] bool _isMDL2Font; + [ObservableProperty] bool _isFiltered; + [ObservableProperty] string _titlePrefix; + [ObservableProperty] string _xamlPath; + [ObservableProperty] string _sequence = string.Empty; + [ObservableProperty] FontItem _selectedFont; + [ObservableProperty] FontFamily _fontFamily; + [ObservableProperty] FolderContents _folder; + [ObservableProperty] DevProviderBase _selectedProvider; + public FontDisplayMode DisplayMode { get => Get(); set { if (Set(value)) { UpdateTypography(); } } } + public FontAnalysis SelectedVariantAnalysis { get => Get(); set { if (Set(value)) { UpdateVariations(); } } } + + public bool IsInstallable => + IsExternalFile + && SourceFile is not null + && SourceFile.FileType.ToLower() is ".woff" or ".woff2"; + + partial void OnShowColorGlyphsChanged(bool value) { - public int FontSize { get; set; } = 12; - [ObservableProperty] - CharacterRenderingOptions _option; + if (RenderingOptions is not null) + RenderingOptions = RenderingOptions with { IsColourFontEnabled = value }; + if (DisplayMode == FontDisplayMode.TypeRamp) + UpdateRampOptions(); } - public partial class FontMapViewModel : ViewModelBase + partial void OnSelectedFontChanging(FontItem value) { - #region Properties - - private bool _blockChar = false; - - protected override bool TrackAnimation => true; - - private NativeInterop _interop { get; } - - private Debouncer _searchDebouncer { get; } - - private ConcurrencyToken.ConcurrencyTokenGenerator _searchTokenFactory { get; } - - private int[] _rampSizes { get; } = new[] { 12, 18, 24, 48, 72, 96, 110, 134 }; - - - public AppSettings Settings { get; } - - public StorageFile SourceFile { get; set; } - - public ExportStyle BlackColor { get; } = ExportStyle.Black; - public ExportStyle WhiteColor { get; } = ExportStyle.White; - public ExportStyle GlyphColor { get; } = ExportStyle.ColorGlyph; - - public IDialogService DialogService { get; } - public RelayCommand CommandSavePng { get; } - public RelayCommand CommandSaveSvg { get; } - public RelayCommand ToggleDev { get; } - public DWriteFallbackFont FallbackFont => FontFinder.Fallback; // Do *not* use { get;} here - public bool IsExternalFile { get; set; } - internal bool IsLoadingCharacters { get; private set; } - - public TypographyFeatureInfo SelectedTypography { get => GetV(TypographyFeatureInfo.None); set => Set(value ?? TypographyFeatureInfo.None); } - public TypographyFeatureInfo SelectedCharTypography { get => GetV(TypographyFeatureInfo.None); set => Set(value ?? TypographyFeatureInfo.None); } - public List SelectedGlyphCategories { get => Get>(); private set => Set(value); } - public List Ramps { get; } - - [ObservableProperty] CharacterRenderingOptions _renderingOptions; - [ObservableProperty] CanvasTextLayoutAnalysis _selectedCharAnalysis; - [ObservableProperty] List _selectedCharVariations; - [ObservableProperty] IReadOnlyList _rampOptions; - [ObservableProperty] IReadOnlyList _chars; - [ObservableProperty] IReadOnlyList _searchResults; - [ObservableProperty] IReadOnlyList _variationAxis; - [ObservableProperty] IReadOnlyList _providers; - [ObservableProperty] IReadOnlyList _typographyFeatures; - [ObservableProperty] ObservableCollection _groupedChars; - - [ObservableProperty] bool _showColorGlyphs = true; - [ObservableProperty] bool _importButtonEnabled = true; - [ObservableProperty] bool _isLoading; - [ObservableProperty] bool _hasFontOptions; - [ObservableProperty] bool _isSvgChar; - [ObservableProperty] bool _isSequenceRootVisible; - [ObservableProperty] string _titlePrefix; - [ObservableProperty] string _xamlPath; - [ObservableProperty] string _sequence = string.Empty; - [ObservableProperty] FontItem _selectedFont; - [ObservableProperty] FontFamily _fontFamily; - [ObservableProperty] FolderContents _folder; - [ObservableProperty] DevProviderBase _selectedProvider; - public FontDisplayMode DisplayMode { get => Get(); set { if (Set(value)) { UpdateTypography(); } } } - public FontAnalysis SelectedVariantAnalysis { get => Get(); set { if (Set(value)) { UpdateVariations(); } } } - - partial void OnShowColorGlyphsChanged(bool value) - { - if (RenderingOptions is not null) - RenderingOptions = RenderingOptions with { IsColourFontEnabled = value }; - if (DisplayMode == FontDisplayMode.TypeRamp) - UpdateRampOptions(); - } + // Remove property changed listener from old font + if (SelectedFont is not null) + SelectedFont.PropertyChanged -= SelectedFont_PropertyChanged; + } - partial void OnSelectedFontChanging(FontItem value) - { - // Remove property changed listener from old font - if (SelectedFont is not null) - SelectedFont.PropertyChanged -= SelectedFont_PropertyChanged; - } + partial void OnSelectedFontChanged(FontItem value) + { + TitleBarHelper.SetTitle(value?.Font?.Name); - partial void OnSelectedFontChanged(FontItem value) + if (SelectedFont is not null) { - TitleBarHelper.SetTitle(value?.Font?.Name); - - if (SelectedFont is not null) - { - // Add property changed listener to new font - SelectedFont.PropertyChanged -= SelectedFont_PropertyChanged; - SelectedFont.PropertyChanged += SelectedFont_PropertyChanged; + // Add property changed listener to new font + SelectedFont.PropertyChanged -= SelectedFont_PropertyChanged; + SelectedFont.PropertyChanged += SelectedFont_PropertyChanged; - TitlePrefix = value.Font.Name + " -"; - SelectedVariant = value.Selected; + TitlePrefix = value.Font.Name + " -"; + SelectedVariant = value.Selected; - if (Set(SelectedFont.DisplayMode, nameof(DisplayMode), false)) - UpdateTypography(); - } - else - SelectedVariant = null; + if (Set(SelectedFont.DisplayMode, nameof(DisplayMode), false)) + UpdateTypography(); } + else + SelectedVariant = null; + } - private FontVariant _selectedVariant; - public FontVariant SelectedVariant + private FontVariant _selectedVariant; + public FontVariant SelectedVariant + { + get => _selectedVariant; + set { - get => _selectedVariant; - set + if (value != _selectedVariant) { - if (value != _selectedVariant) - { - Chars = null; - _selectedVariant = value; - FontFamily = value == null ? null : new FontFamily(value.Source); - int idx = Settings.LastSelectedCharIndex; - LoadVariant(value); - OnPropertyChanged(); - UpdateTypography(); - SetDefaultChar(idx); - SelectedTypography = TypographyFeatures.FirstOrDefault() ?? TypographyFeatureInfo.None; - UpdateDevValues(); - - if (value is not null) - SelectedFont.Selected = value; - } + Chars = null; + _selectedVariant = value; + FontFamily = value == null ? null : new FontFamily(value.Source); + int idx = Settings.LastSelectedCharIndex; + LoadVariant(value); + OnPropertyChanged(); + UpdateTypography(); + SetDefaultChar(idx); + SelectedTypography = TypographyFeatures.FirstOrDefault() ?? TypographyFeatureInfo.None; + UpdateDevValues(); + + if (value is not null) + SelectedFont.Selected = value; } } + } - private string _searchQuery; - public string SearchQuery + private string _searchQuery; + public string SearchQuery + { + get => _searchQuery; + set { - get => _searchQuery; - set - { - if (Set(ref _searchQuery, value)) - DebounceSearch(value, Settings.InstantSearchDelay, SearchSource.AutoProperty); - } + if (Set(ref _searchQuery, value)) + DebounceSearch(value, Settings.InstantSearchDelay, SearchSource.AutoProperty); } + } - private Character _selectedChar; - public Character SelectedChar + private Character _selectedChar; + public Character SelectedChar + { + get => _selectedChar; + set { - get => _selectedChar; - set - { - if (_selectedChar == value || _blockChar) return; - _selectedChar = value; - if (value is not null) - Settings.LastSelectedCharIndex = (int)value.UnicodeIndex; - OnPropertyChanged(); - UpdateCharAnalysis(); - UpdateDevValues(); - } + if (_selectedChar == value || _blockChar) return; + _selectedChar = value; + if (value is not null) + Settings.LastSelectedCharIndex = (int)value.UnicodeIndex; + OnPropertyChanged(); + UpdateCharAnalysis(); + UpdateDevValues(); } + } - private string _typeRampText; - public string TypeRampText + private string _typeRampText; + public string TypeRampText + { + get => _typeRampText; + set { - get => _typeRampText; - set - { - if (value != null && value.Length > 100) - value = value.Substring(0, 100); + if (value != null && value.Length > 100) + value = value.Substring(0, 100); - Set(ref _typeRampText, value); - } + Set(ref _typeRampText, value); } + } - #endregion + #endregion - public FontMapViewModel(IDialogService dialogService, AppSettings settings) - { - DialogService = dialogService; - Settings = settings; + public FontMapViewModel(IDialogService dialogService, AppSettings settings) + { + DialogService = dialogService; + Settings = settings; - CommandSavePng = new RelayCommand(async (b) => await SavePngAsync(b)); - CommandSaveSvg = new RelayCommand(async (b) => await SaveSvgAsync(b)); - ToggleDev = new RelayCommand(t => SetDev(t)); - SelectedGlyphCategories = Unicode.CreateRangesList(); + CommandSavePng = new RelayCommand(async (b) => await SavePngAsync(b)); + CommandSaveSvg = new RelayCommand(async (b) => await SaveSvgAsync(b)); + ToggleDev = new RelayCommand(t => SetDev(t)); + SelectedGlyphCategories = Unicode.CreateRangesList(); - Ramps = _rampSizes.Select(r => new RampOption { FontSize = r }).ToList(); + Ramps = _rampSizes.Select(r => new RampOption { FontSize = r }).ToList(); - if (DesignMode.DesignModeEnabled is false) - _interop = Utils.GetInterop(); + if (DesignMode.DesignModeEnabled is false) + _interop = Utils.GetInterop(); - _searchDebouncer = new Debouncer(); - _searchTokenFactory = new ConcurrencyToken.ConcurrencyTokenGenerator(); - Register(m => UpdateTextOptions()); - } + _searchDebouncer = new Debouncer(); + _searchTokenFactory = new ConcurrencyToken.ConcurrencyTokenGenerator(); + Register(m => UpdateTextOptions()); + } - public void Deactivated() - { - Messenger.UnregisterAll(this); - } + public void Deactivated() + { + Messenger.UnregisterAll(this); + } - protected override void OnPropertyChangeNotified(string propertyName) + protected override void OnPropertyChangeNotified(string propertyName) + { + switch (propertyName) { - switch (propertyName) - { - case nameof(SelectedTypography): - SelectedCharTypography = SelectedTypography; - break; - case nameof(SelectedCharTypography): - UpdateDevValues(); - break; - case nameof(DisplayMode) when SelectedFont is not null: - SelectedFont.DisplayMode = DisplayMode; - break; - } + case nameof(SelectedTypography): + SelectedCharTypography = SelectedTypography; + break; + case nameof(SelectedCharTypography): + UpdateDevValues(); + break; + case nameof(DisplayMode) when SelectedFont is not null: + SelectedFont.DisplayMode = DisplayMode; + break; } + } - private void UpdateTextOptions() - { - OnSyncContext(() => { RampOptions = GlyphService.GetRampOptions(); }); - } + private void UpdateTextOptions() + { + OnSyncContext(() => { RampOptions = GlyphService.GetRampOptions(); }); + } - private void SelectedFont_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + private void SelectedFont_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (sender is FontItem item) { - if (sender is FontItem item) - { - if (e.PropertyName == nameof(FontItem.Selected)) - SelectedVariant = item.Selected; - } + if (e.PropertyName == nameof(FontItem.Selected)) + SelectedVariant = item.Selected; } + } + + public void UpdateCategories(IList value) + { + SelectedGlyphCategories = value.ToList(); + UpdateCharacters(); + } - public void UpdateCategories(IList value) + private void UpdateCharacters() + { + int last = Settings.LastSelectedCharIndex; + _blockChar = true; + if (!SelectedGlyphCategories.Any(c => !c.IsSelected)) { - SelectedGlyphCategories = value.ToList(); - UpdateCharacters(); + // Fast path : all characters; + Chars = SelectedVariant?.GetCharacters(); + GroupedChars = UnicodeRangeGroup.CreateGroups(Chars, IsMDL2Font); + IsFiltered = false; } - - private void UpdateCharacters() + else { - int last = Settings.LastSelectedCharIndex; - _blockChar = true; - if (!SelectedGlyphCategories.Any(c => !c.IsSelected)) + // Filter characters + var items = Unicode.FilterCharacters(SelectedVariant?.GetCharacters(), SelectedGlyphCategories, false); + + // Only change the character source if we actually need too + if (Chars is null || items.Count != Chars.Count) { - // Fast path : all characters; - Chars = SelectedVariant?.GetCharacters(); - GroupedChars = UnicodeRangeGroup.CreateGroups(Chars); + Chars = items; + GroupedChars = UnicodeRangeGroup.CreateGroups(items, IsMDL2Font); } else { - // Filter characters - var chars = SelectedVariant?.GetCharacters().AsEnumerable(); - foreach (var range in SelectedGlyphCategories.Where(c => !c.IsSelected)) - chars = chars.Where(c => !range.Range.Contains(c.UnicodeIndex)); - - // Only change the character source if we actually need too - var items = chars.ToList(); - if (items.Count != Chars.Count) - { - Chars = items; - GroupedChars = UnicodeRangeGroup.CreateGroups(items); - } - else - { - for (int i = 0; i ranges.Any(g => g.Name == r.Name)) - .Select(r => new UnicodeRangeModel(r)) - .ToList(); + // 1. Update categories + IsMDL2Font = FontFinder.IsMDL2(variant); + SelectedGlyphCategories = Unicode.GetCategories(SelectedVariant, IsMDL2Font); - UpdateCharacters(); - if (variant != null) - { - var analysis = variant.GetAnalysis(); - TypographyAnalyzer.PrepareSearchMap(variant, analysis); - analysis.ResetVariableAxis(); - SelectedVariantAnalysis = analysis; - HasFontOptions = SelectedVariantAnalysis.ContainsVectorColorGlyphs || SelectedVariant.HasXamlTypographyFeatures; - ShowColorGlyphs = variant.DirectWriteProperties.IsColorFont; - - // Update Unicode Categories - } - else - { - SelectedVariantAnalysis = new FontAnalysis(); - HasFontOptions = false; - ShowColorGlyphs = false; - ImportButtonEnabled = false; - } + // 2. Update characters + UpdateCharacters(); - RampOptions = GetRampOptions(variant); - SelectedTypography = TypographyFeatureInfo.None; - SearchResults = null; - DebounceSearch(SearchQuery, 100); - IsLoadingCharacters = false; - } - catch + // 3. Load variant data + if (variant != null) { - /* - * Hack to avoid crash. - * When launching the app by double clicking on a font file when the app is closed, - * creating a CanvasTextLayout can fail for some unknown reason. So we retry it. - * If we get caught in a never ending loop here, something horrible has occurred. - */ - IsLoadingCharacters = false; - _ = Window.Current.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Low, async () => - { - await Task.Delay(100); - if (variant == SelectedVariant) - LoadVariant(variant); - }); + var analysis = variant.GetAnalysis(); + TypographyAnalyzer.PrepareSearchMap(variant, analysis); + analysis.ResetVariableAxis(); + SelectedVariantAnalysis = analysis; + HasFontOptions = SelectedVariantAnalysis.ContainsVectorColorGlyphs || SelectedVariant.HasXamlTypographyFeatures; + ShowColorGlyphs = variant.DirectWriteProperties.IsColorFont; } - } - private IReadOnlyList GetRampOptions(FontVariant variant) - { - if (variant == null) - return new List(); - - var list = GlyphService.GetRampOptions(); - - if (variant?.TryGetSampleText() is String s) - list.Insert(0, new Suggestion(Localization.Get("SuggestOptionSample/Text"), s)); - - if (Unicode.ContainsRange(variant, UnicodeRange.Emoticons)) + else { - string emoji = "😂😍😭💁👍💋🐱🦉🌺🌲🍓🍕🎂🏰🏠🚄🚒🛫🛍"; - if (!list.Any(s => s.Text == emoji)) - list.Add(new Suggestion(Localization.Get("SuggestOptionEmoji/Text"), emoji)); + SelectedVariantAnalysis = new FontAnalysis(); + HasFontOptions = false; + ShowColorGlyphs = false; + ImportButtonEnabled = false; } - return list; + RampOptions = GetRampOptions(variant); + SelectedTypography = TypographyFeatureInfo.None; + SearchResults = null; + DebounceSearch(SearchQuery, 100); + IsLoadingCharacters = false; + } + catch + { + /* + * Hack to avoid crash. + * When launching the app by double clicking on a font file when the app is closed, + * creating a CanvasTextLayout can fail for some unknown reason. So we retry it. + * If we get caught in a never ending loop here, something horrible has occurred. + */ + IsLoadingCharacters = false; + _ = Window.Current.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Low, async () => + { + await Task.Delay(100); + if (variant == SelectedVariant) + LoadVariant(variant); + }); } + } + private IReadOnlyList GetRampOptions(FontVariant variant) + { + if (variant == null) + return new List(); + + var list = GlyphService.GetRampOptions(); + + if (variant?.TryGetSampleText() is String s) + list.Insert(0, new Suggestion(Localization.Get("SuggestOptionSample/Text"), s)); - internal void UpdateVariations() + if (Unicode.ContainsRange(variant, UnicodeRange.Emoticons)) { - VariationAxis = SelectedVariantAnalysis?.Axis?.Where(a => a.Attribute == DWriteFontAxisAttribute.Variable).ToList() ?? new List(); - UpdateRampOptions(); + string emoji = "😂😍😭💁👍💋🐱🦉🌺🌲🍓🍕🎂🏰🏠🚄🚒🛫🛍"; + if (!list.Any(s => s.Text == emoji)) + list.Add(new Suggestion(Localization.Get("SuggestOptionEmoji/Text"), emoji)); } - private void UpdateTypography() - { - var current = this.SelectedTypography; + return list; + } - if (SelectedVariant == null) - TypographyFeatures = new List(); - else if (DisplayMode == FontDisplayMode.TypeRamp) - TypographyFeatures = SelectedVariant.TypographyFeatures; - else - TypographyFeatures = SelectedVariant.XamlTypographyFeatures; + internal void UpdateVariations() + { + VariationAxis = SelectedVariantAnalysis?.Axis?.Where(a => a.Attribute == DWriteFontAxisAttribute.Variable).ToList() ?? new List(); + UpdateRampOptions(); + } - // Ensure ColorFont option propagates - if (DisplayMode is FontDisplayMode.TypeRamp) - UpdateRampOptions(); + private void UpdateTypography() + { + var current = this.SelectedTypography; - this.SelectedTypography = TypographyFeatures.FirstOrDefault(t => t.Feature == current.Feature); - OnPropertyChanged(nameof(SelectedTypography)); // Required. - } + if (SelectedVariant == null) + TypographyFeatures = new List(); + else if (DisplayMode == FontDisplayMode.TypeRamp) + TypographyFeatures = SelectedVariant.TypographyFeatures; + else + TypographyFeatures = SelectedVariant.XamlTypographyFeatures; - private void UpdateCharAnalysis() - { - if (SelectedChar == null) - { - SelectedCharAnalysis = new (); - IsSvgChar = false; - SelectedCharVariations = new (); - return; - } + // Ensure ColorFont option propagates + if (DisplayMode is FontDisplayMode.TypeRamp) + UpdateRampOptions(); - SelectedCharAnalysis = GetCharAnalysis(SelectedChar); - SelectedCharVariations = TypographyAnalyzer.GetCharacterVariants(SelectedVariant, SelectedChar); - IsSvgChar = SelectedCharAnalysis.GlyphFormats.Contains(GlyphImageFormat.Svg); - } + this.SelectedTypography = TypographyFeatures.FirstOrDefault(t => t.Feature == current.Feature); + OnPropertyChanged(nameof(SelectedTypography)); // Required. + } - internal CanvasTextLayoutAnalysis GetCharAnalysis(Character c) + private void UpdateCharAnalysis() + { + if (SelectedChar == null) { - using CanvasTextLayout layout = new (Utils.CanvasDevice, $"{c.Char}", new() - { - FontSize = (float)Core.Converters.GetFontSize(Settings.GridSize), - FontFamily = SelectedVariant.Source, - FontStretch = SelectedVariant.DirectWriteProperties.Stretch, - FontWeight = SelectedVariant.DirectWriteProperties.Weight, - FontStyle = SelectedVariant.DirectWriteProperties.Style, - HorizontalAlignment = CanvasHorizontalAlignment.Left, - }, Settings.GridSize, Settings.GridSize); - - // This doesn't work if it's set during the property constructor. - // Leave it as a separate line. - layout.Options = CanvasDrawTextOptions.EnableColorFont; - - ApplyEffectiveTypography(layout); - return _interop.AnalyzeCharacterLayout(layout); + UnihanData = null; + SelectedCharAnalysis = new(); + IsSvgChar = false; + SelectedCharVariations = new(); + return; } - private CanvasTypography GetEffectiveTypography(TypographyFeatureInfo typography = null) - { - if (typography == null) - typography = SelectedTypography; + SelectedCharAnalysis = GetCharAnalysis(SelectedChar); + SelectedCharVariations = TypographyAnalyzer.GetCharacterVariants(SelectedVariant, SelectedChar); + IsSvgChar = SelectedCharAnalysis.GlyphFormats.Contains(GlyphImageFormat.Svg); + UnihanData = GlyphService.GetUnihanData(SelectedChar.UnicodeIndex); + } + + internal CanvasTextLayoutAnalysis GetCharAnalysis(Character c) + { + using CanvasTextLayout layout = new(Utils.CanvasDevice, $"{c.Char}", new() + { + FontSize = (float)Core.Converters.GetFontSize(Settings.GridSize), + FontFamily = SelectedVariant.Source, + FontStretch = SelectedVariant.DirectWriteProperties.Stretch, + FontWeight = SelectedVariant.DirectWriteProperties.Weight, + FontStyle = SelectedVariant.DirectWriteProperties.Style, + HorizontalAlignment = CanvasHorizontalAlignment.Left, + }, Settings.GridSize, Settings.GridSize); + + // This doesn't work if it's set during the property constructor. + // Leave it as a separate line. + layout.Options = CanvasDrawTextOptions.EnableColorFont; + + ApplyEffectiveTypography(layout); + return _interop.AnalyzeCharacterLayout(layout); + } + + private CanvasTypography GetEffectiveTypography(TypographyFeatureInfo typography = null) + { + if (typography == null) + typography = SelectedTypography; - CanvasTypography typo = new (); - if (typography != null && typography.Feature != CanvasTypographyFeatureName.None) - typo.AddFeature(typography.Feature, 1u); + CanvasTypography typo = new(); + if (typography != null && typography.Feature != CanvasTypographyFeatureName.None) + typo.AddFeature(typography.Feature, 1u); - return typo; - } + return typo; + } + + private void ApplyEffectiveTypography(CanvasTextLayout layout) + { + using var type = GetEffectiveTypography(); + layout.SetTypography(0, 1, type); + } - private void ApplyEffectiveTypography(CanvasTextLayout layout) + internal void UpdateDevValues() + { + if (SelectedVariant == null || SelectedChar == null) { - using var type = GetEffectiveTypography(); - layout.SetTypography(0, 1, type); + // Do nothing. } - - internal void UpdateDevValues() + else { - if (SelectedVariant == null || SelectedChar == null) - { - // Do nothing. - } - else - { - var t = SelectedProvider?.Type ?? Settings.SelectedDevProvider; + var t = SelectedProvider?.Type ?? Settings.SelectedDevProvider; - RenderingOptions = new CharacterRenderingOptions( - SelectedVariant, - new() { SelectedCharTypography }, - 64, - SelectedCharAnalysis, - VariationAxis); + RenderingOptions = new CharacterRenderingOptions( + SelectedVariant, + new() { SelectedCharTypography }, + 64, + SelectedCharAnalysis, + VariationAxis); - UpdateRampOptions(); + UpdateRampOptions(); - Providers = RenderingOptions.GetDevProviders(SelectedChar); - SetDev(t); + Providers = RenderingOptions.GetDevProviders(SelectedChar); + SetDev(t); - XamlPath = $"{SelectedVariant.FileName}#{SelectedVariant.FamilyName}"; - } + XamlPath = $"{SelectedVariant.FileName}#{SelectedVariant.FamilyName}"; } + } - public void UpdateRampOptions() - { - if (RenderingOptions is null) - return; - - var ops = RenderingOptions with { Axis = VariationAxis }; - foreach (var ramp in Ramps) - ramp.Option = ops; - } + public void UpdateRampOptions() + { + if (RenderingOptions is null) + return; - public void SetDefaultChar(int idx = -1) - { - if (Chars == null) - return; + var ops = RenderingOptions with { Axis = VariationAxis }; + foreach (var ramp in Ramps) + ramp.Option = ops; + } - bool set = idx >= 0; - if (idx < 0) - idx = Settings.LastSelectedCharIndex; + public void SetDefaultChar(int idx = -1) + { + if (Chars == null) + return; - if (Chars.FirstOrDefault(i => i.UnicodeIndex == idx) - is Character lastSelectedChar - && SelectedVariant.Face.HasCharacter((uint)lastSelectedChar.UnicodeIndex)) - { - SelectedChar = lastSelectedChar; - } - else - { - SelectedChar = Chars?.FirstOrDefault( - c => !Windows.Data.Text.UnicodeCharacters.IsWhitespace((uint)c.UnicodeIndex)) ?? Chars.FirstOrDefault(); - } + bool set = idx >= 0; + if (idx < 0) + idx = Settings.LastSelectedCharIndex; - if (set) - _blockChar = false; + if (Chars.FirstOrDefault(i => i.UnicodeIndex == idx) + is Character lastSelectedChar + && SelectedVariant.Face.HasCharacter((uint)lastSelectedChar.UnicodeIndex)) + { + SelectedChar = lastSelectedChar; } - - public void ChangeDisplayMode() + else { - if (DisplayMode == FontDisplayMode.TypeRamp) - DisplayMode = FontDisplayMode.CharacterMap; - else - DisplayMode = FontDisplayMode.TypeRamp; + SelectedChar = Chars?.FirstOrDefault( + c => !Windows.Data.Text.UnicodeCharacters.IsWhitespace((uint)c.UnicodeIndex)) ?? Chars.FirstOrDefault(); } - public string GetCharName(Character c) - { - if (SelectedVariant == null || c == null) - return null; + if (set) + _blockChar = false; + } - return SelectedVariant.GetDescription(c); - } + public void ChangeDisplayMode() + { + if (DisplayMode == FontDisplayMode.TypeRamp) + DisplayMode = FontDisplayMode.CharacterMap; + else + DisplayMode = FontDisplayMode.TypeRamp; + } - public string GetCharDescription(Character c) - { - if (SelectedVariant == null || c == null) - return null; + public string GetCharName(Character c) + { + if (SelectedVariant == null || c == null) + return null; - if (GlyphService.GetCharacterKeystroke(c.UnicodeIndex) is string k) - return $"{c.UnicodeString} - {k}"; - else - return c.UnicodeString; - } + return SelectedVariant.GetDescription(c, allowUnihan: true); + } - public void DebounceSearch(string query, int delayMilliseconds = 500, SearchSource from = SearchSource.AutoProperty) - { - if (from == SearchSource.AutoProperty && !Settings.UseInstantSearch) - return; + public string GetCharDescription(Character c) + { + if (SelectedVariant == null || c == null) + return null; - if (from == SearchSource.ManualSubmit || delayMilliseconds <= 0) - Search(query); - else - _searchDebouncer.Debounce(delayMilliseconds, () => Search(query)); - } + if (GlyphService.GetCharacterKeystroke(c.UnicodeIndex) is string k) + return $"{c.UnicodeString} - {k}"; + else + return c.UnicodeString; + } - internal async void Search(string query) - { - var token = _searchTokenFactory.GenerateToken(); - if (await GlyphService.SearchAsync(query, SelectedVariant) is IReadOnlyList results - && token.IsValid()) - { - SearchResults = results; - } - } + public void DebounceSearch(string query, int delayMilliseconds = 500, SearchSource from = SearchSource.AutoProperty) + { + if (from == SearchSource.AutoProperty && !Settings.UseInstantSearch) + return; - private void SetDev(DevProviderType type, bool save = true) + if (from == SearchSource.ManualSubmit || delayMilliseconds <= 0) + Search(query); + else + _searchDebouncer.Debounce(delayMilliseconds, () => Search(query)); + } + + internal async void Search(string query) + { + var token = _searchTokenFactory.GenerateToken(); + if (await GlyphService.SearchAsync(query, SelectedVariant) is IReadOnlyList results + && token.IsValid()) { - if (Providers?.FirstOrDefault(p => p.Type == type) is DevProviderBase p) + SearchResults = null; + IsSearchGrouped = false; + + if (results.Count == 0) + results = null; + + // If we have filtered out any characters from the character list we should + // group the search results into characters that are shown and characters + // that are hidden by filtering + if (results != null && SelectedGlyphCategories.Any(c => c.IsSelected is false)) { - SelectedProvider = p; - if (save) - Settings.SelectedDevProvider = type; + if (SearchResultsGroup.CreateGroups(results, SelectedGlyphCategories) is { } groups + && groups.HasHiddenResults) + { + IsSearchGrouped = true; + SearchResults = groups; + return; + } } - } - internal Task SavePngAsync(ExportParameters args, Character c = null) - { - return SaveGlyphAsync(ExportFormat.Png, args, c); + SearchResults = results; } + } - internal Task SaveSvgAsync(ExportParameters args, Character c = null) + private void SetDev(DevProviderType type, bool save = true) + { + if (Providers?.FirstOrDefault(p => p.Type == type) is DevProviderBase p) { - return SaveGlyphAsync(ExportFormat.Svg, args, c); + SelectedProvider = p; + if (save) + Settings.SelectedDevProvider = type; } + } - internal async Task SaveGlyphAsync(ExportFormat format, ExportParameters args, Character c = null) - { - Character character = SelectedChar; - CanvasTextLayoutAnalysis analysis = SelectedCharAnalysis; + internal Task SavePngAsync(ExportParameters args, Character c = null) + { + return SaveGlyphAsync(ExportFormat.Png, args, c); + } - if (c != null) - { - character = c; - analysis = GetCharAnalysis(c); - } + internal Task SaveSvgAsync(ExportParameters args, Character c = null) + { + return SaveGlyphAsync(ExportFormat.Svg, args, c); + } - ExportResult result = await ExportManager.ExportGlyphAsync( - new(format, args.Style), - SelectedFont.Font, - RenderingOptions with { Analysis = analysis, Typography = new List() { args.Typography } }, - character); + internal async Task SaveGlyphAsync(ExportFormat format, ExportParameters args, Character c = null) + { + Character character = SelectedChar; + CanvasTextLayoutAnalysis analysis = SelectedCharAnalysis; - if (result.State == ExportState.Succeeded) - Messenger.Send(new AppNotificationMessage(true, result)); + if (c != null) + { + character = c; + analysis = GetCharAnalysis(c); } - public async Task LoadFromFileArgsAsync(FileActivatedEventArgs args) + ExportResult result = await ExportManager.ExportGlyphAsync( + new(format, args.Style), + SelectedFont.Font, + RenderingOptions with { Analysis = analysis, Typography = new List() { args.Typography } }, + character); + + if (result.State == ExportState.Succeeded) + Messenger.Send(new AppNotificationMessage(true, result)); + } + + public async Task LoadFromFileArgsAsync(FileActivatedEventArgs args) + { + IsExternalFile = true; + IsLoading = true; + try { - IsExternalFile = true; - IsLoading = true; - try + if (args.Files.FirstOrDefault() is StorageFile file + && await FontFinder.LoadFromFileAsync(file) is { } font) { - if (args.Files.FirstOrDefault() is StorageFile file - && await FontFinder.LoadFromFileAsync(file) is { } font) - { - SourceFile = file; - IsLoading = false; + SourceFile = file; + IsLoading = false; - SelectedFont = new (font); - SetDefaultChar(); - return true; - } + SelectedFont = new(font); + SetDefaultChar(); + return true; + } - await DialogService.ShowMessageAsync( - Localization.Get("InvalidFontMessage"), - Localization.Get("InvalidFontTitle")); + await DialogService.ShowMessageAsync( + Localization.Get("InvalidFontMessage"), + Localization.Get("InvalidFontTitle")); - WindowService.CloseForCurrentView(); + WindowService.CloseForCurrentView(); - return false; - } - finally - { - IsLoading = false; - } + return false; } - - public async void ImportFile() + finally { - ImportButtonEnabled = false; + IsLoading = false; + } + } - IsLoading = true; - try + public async void ImportFile() + { + ImportButtonEnabled = false; + + IsLoading = true; + try + { + List items = new() { SourceFile }; + if (await FontFinder.ImportFontsAsync(items) is { } result + && (result.Imported.Count > 0 || result.Existing.Count > 0)) { - List items = new() { SourceFile }; - if (await FontFinder.ImportFontsAsync(items) is { } result - && (result.Imported.Count > 0 || result.Existing.Count > 0)) + await WindowService.ActivateMainWindowAsync(); + await Task.Delay(100); + await CoreApplication.MainView.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Low, () => { - await WindowService.ActivateMainWindowAsync(); - await Task.Delay(100); - await CoreApplication.MainView.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Low, () => - { - Messenger.Send(new ImportMessage(result)); - }); - } - } - finally - { - IsLoading = false; + Messenger.Send(new ImportMessage(result)); + }); } } - - public void OpenSelectedFontInWindow() + finally { - if (SelectedFont is FontItem item) - { - _ = FontMapView.CreateNewViewForFontAsync(item.Font, null, RenderingOptions); - } + IsLoading = false; } + } - public void OpenQuickCompare() + public void OpenSelectedFontInWindow() + { + if (SelectedFont is FontItem item) { - _ = QuickCompareView.CreateWindowAsync(new(false)); + _ = FontMapView.CreateNewViewForFontAsync(item.Font, null, RenderingOptions); } + } + + public void OpenQuickCompare() + { + _ = QuickCompareView.CreateWindowAsync(new(false)); + } - public async Task RequestCopyToClipboardAsync(CopyToClipboardMessage message) + public async Task RequestCopyToClipboardAsync(CopyToClipboardMessage message) + { + if (message.CopyType is DevValueType.Char + && await Utils.TryCopyToClipboardAsync(message, this)) { - if (message.CopyType is DevValueType.Char - && await Utils.TryCopyToClipboardAsync(message, this)) + string key = message.DataType switch { - string key = message.DataType switch - { - CopyDataType.SVG => "NotificationCopiedSVG", - CopyDataType.PNG => "NotificationCopiedPNG", - _ => "NotificationCopied" - }; + CopyDataType.SVG => "NotificationCopiedSVG", + CopyDataType.PNG => "NotificationCopiedPNG", + _ => "NotificationCopied" + }; - Messenger.Send(new AppNotificationMessage(true, Localization.Get(key), 2500)); - } - } - - public async void CopySequence() - { - if (await Utils.TryCopyToClipboardAsync(Sequence, this)) - Messenger.Send(new AppNotificationMessage(true, Localization.Get("NotificationCopied"), 2500)); - } - public void ClearSequence() => Sequence = string.Empty; - public void IncreaseCharacterSize() => Settings.ChangeGridSize(4); - public void DecreaseCharacterSize() => Settings.ChangeGridSize(-4); - public void ShowPane() => Settings.EnablePreviewPane = true; - public void HidePane() => Settings.EnablePreviewPane = false; - public void ShowCopyPane() => Settings.EnableCopyPane = true; - public void HideCopyPane() => Settings.EnableCopyPane = false; - - public void AddCharToSequence(int start, int length, Character c) - { - if (c is null) - return; - - var s = Sequence ?? string.Empty; - start = Math.Min(start, s.Length); - if (s.Length > 0) - s = s.Remove(start, length); - - Sequence = s.Insert(start, c.Char); + Messenger.Send(new AppNotificationMessage(true, Localization.Get(key), 2500)); } + } + public async void LaunchInstall() + { + var path = FontFinder.GetAppPath(SelectedVariantAnalysis.FilePath).ToLower(); + var file = await StorageFile.GetFileFromApplicationUriAsync(new Uri(path)); + var result = await Launcher.LaunchFileAsync(file, new LauncherOptions { DisplayApplicationPicker = true }); } - public enum SearchSource + public async void CopySequence() { - AutoProperty, - ManualSubmit + if (await Utils.TryCopyToClipboardAsync(Sequence, this)) + Messenger.Send(new AppNotificationMessage(true, Localization.Get("NotificationCopied"), 2500)); } + public void ClearSequence() => Sequence = string.Empty; + public void IncreaseCharacterSize() => Settings.ChangeGridSize(4); + public void DecreaseCharacterSize() => Settings.ChangeGridSize(-4); + public void ShowPane() => Settings.EnablePreviewPane = true; + public void HidePane() => Settings.EnablePreviewPane = false; + public void ShowCopyPane() => Settings.EnableCopyPane = true; + public void HideCopyPane() => Settings.EnableCopyPane = false; + public void ToggleUnihan() => ShowingUnihan = !ShowingUnihan; + + public void AddCharToSequence(int start, int length, Character c) + { + if (c is null) + return; + + var s = Sequence ?? string.Empty; + start = Math.Min(start, s.Length); + if (s.Length > 0) + s = s.Remove(start, length); + + Sequence = s.Insert(start, c.Char); + } + +} + +public enum SearchSource +{ + AutoProperty, + ManualSubmit } diff --git a/CharacterMap/CharacterMap/ViewModels/MainViewModel.cs b/CharacterMap/CharacterMap/ViewModels/MainViewModel.cs index c8917435..fca50e43 100644 --- a/CharacterMap/CharacterMap/ViewModels/MainViewModel.cs +++ b/CharacterMap/CharacterMap/ViewModels/MainViewModel.cs @@ -1,637 +1,622 @@ using CharacterMap.Controls; -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Services; using CharacterMap.Views; -using CharacterMapCX; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.DependencyInjection; using CommunityToolkit.Mvvm.Input; -using CommunityToolkit.Mvvm.Messaging; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.Linq; -using System.Threading.Tasks; using Windows.ApplicationModel.Core; -using Windows.Storage; -namespace CharacterMap.ViewModels +namespace CharacterMap.ViewModels; + +public partial class MainViewModel : ViewModelBase { - public partial class MainViewModel : ViewModelBase - { - public event EventHandler FontListCreated; + public event EventHandler FontListCreated; - private Debouncer _searchDebouncer { get; } = new Debouncer(); + private Debouncer _searchDebouncer { get; } = new Debouncer(); - private Debouncer _settingsDebouncer { get; } = new Debouncer(); + private Debouncer _settingsDebouncer { get; } = new Debouncer(); - private Exception _startUpException = null; + private Exception _startUpException = null; - #region Properties + #region Properties - protected override bool TrackAnimation => true; + protected override bool TrackAnimation => true; - public Task InitialLoad { get; } + public Task InitialLoad { get; } - public AppSettings Settings { get; } + public AppSettings Settings { get; } - public IDialogService DialogService { get; } + public IDialogService DialogService { get; } - public RelayCommand CommandToggleFullScreen { get; } + public RelayCommand CommandToggleFullScreen { get; } - public UserCollectionsService FontCollections { get; } + public UserCollectionsService FontCollections { get; } - public ObservableCollection Fonts { get; } = new(); + public ObservableCollection Fonts { get; } = new(); - public bool IsSecondaryView { get; } + public bool IsSecondaryView { get; } - [NotifyPropertyChangedFor(nameof(CurrentFont))] - [ObservableProperty] int _tabIndex = 0; - [ObservableProperty] double _progress = 0d; - [ObservableProperty] string _titlePrefix; - [ObservableProperty] string _fontSearch; - [ObservableProperty] string _filterTitle; - [ObservableProperty] string _collectionExportProgress; - [ObservableProperty] bool _canFilter = true; - [ObservableProperty] bool _isLoadingFonts; - [ObservableProperty] bool _isSearchResults; - [ObservableProperty] bool _isLoadingFontsFailed; - [ObservableProperty] bool _hasFonts; - [ObservableProperty] bool _isFontSetExpired; - [ObservableProperty] bool _isCollectionExportEnabled = true; - [ObservableProperty] ObservableCollection> _groupedFontList; - [ObservableProperty] BasicFontFilter _fontListFilter = BasicFontFilter.All; - [ObservableProperty] List _fontList; + [NotifyPropertyChangedFor(nameof(CurrentFont))] + [ObservableProperty] int _tabIndex = 0; + [ObservableProperty] double _progress = 0d; + [ObservableProperty] string _titlePrefix; + [ObservableProperty] string _fontSearch; + [ObservableProperty] string _filterTitle; + [ObservableProperty] string _collectionExportProgress; + [ObservableProperty] bool _canFilter = true; + [ObservableProperty] bool _isLoadingFonts; + [ObservableProperty] bool _isSearchResults; + [ObservableProperty] bool _isLoadingFontsFailed; + [ObservableProperty] bool _hasFonts; + [ObservableProperty] bool _isFontSetExpired; + [ObservableProperty] bool _isCollectionExportEnabled = true; + [ObservableProperty] ObservableCollection> _groupedFontList; + [ObservableProperty] BasicFontFilter _fontListFilter = BasicFontFilter.All; + [ObservableProperty] List _fontList; - public FontItem CurrentFont => Fonts.Count > 0 && TabIndex < Fonts.Count && TabIndex > -1 - ? Fonts[TabIndex] : null; + public FontItem CurrentFont => Fonts.Count > 0 && TabIndex < Fonts.Count && TabIndex > -1 + ? Fonts[TabIndex] : null; - private UserFontCollection _selectedCollection; - public UserFontCollection SelectedCollection + private UserFontCollection _selectedCollection; + public UserFontCollection SelectedCollection + { + get => _selectedCollection; + set { - get => _selectedCollection; - set + if (value != null && value.IsSystemSymbolCollection) { - if (value != null && value.IsSystemSymbolCollection) - { - FontListFilter = BasicFontFilter.SymbolFonts; - return; - } - - if (Set(ref _selectedCollection, value) && value != null) - RefreshFontList(value); + FontListFilter = BasicFontFilter.SymbolFonts; + return; } + + if (Set(ref _selectedCollection, value) && value != null) + RefreshFontList(value); } + } - private InstalledFont _selectedFont; - public InstalledFont SelectedFont + private InstalledFont _selectedFont; + public InstalledFont SelectedFont + { + get => _selectedFont; + set { - get => _selectedFont; - set - { - _selectedFont = value; - if (null != _selectedFont) - TitlePrefix = value.Name + " -"; - else - TitlePrefix = string.Empty; - - TitleBarHelper.SetTitle(value?.Name); - OnPropertyChanged(); - } + _selectedFont = value; + if (null != _selectedFont) + TitlePrefix = value.Name + " -"; + else + TitlePrefix = string.Empty; + + TitleBarHelper.SetTitle(value?.Name); + OnPropertyChanged(); } + } - #endregion + #endregion - public FolderContents Folder { get; set; } = null; + public FolderContents Folder { get; set; } = null; - // This constructor is used by the IoC container; - public MainViewModel(IDialogService dialogService, AppSettings settings) - : this(new MainViewModelArgs(dialogService, settings, null)) { } + // This constructor is used by the IoC container; + public MainViewModel(IDialogService dialogService, AppSettings settings) + : this(new MainViewModelArgs(dialogService, settings, null)) { } - public MainViewModel(MainViewModelArgs args) - { - DialogService = args.DialogService; - Settings = args.Settings; + public MainViewModel(MainViewModelArgs args) + { + DialogService = args.DialogService; + Settings = args.Settings; - if (args.Folder is not null) - { - IsSecondaryView = true; - Folder = args.Folder; - } + if (args.Folder is not null) + { + IsSecondaryView = true; + Folder = args.Folder; + } - CommandToggleFullScreen = new RelayCommand(Utils.ToggleFullScreenMode); + CommandToggleFullScreen = new RelayCommand(Utils.ToggleFullScreenMode); - FontCollections = Ioc.Default.GetService(); - InitialLoad = LoadAsync(true); + FontCollections = Ioc.Default.GetService(); + InitialLoad = LoadAsync(true); - Fonts.CollectionChanged += Fonts_CollectionChanged; - } - protected override void OnPropertyChangeNotified(string propertyName) + Fonts.CollectionChanged += Fonts_CollectionChanged; + } + protected override void OnPropertyChangeNotified(string propertyName) + { + switch (propertyName) { - switch (propertyName) - { - case nameof(FontList): - CreateFontListGroup(); - break; - case nameof(FontListFilter): - RefreshFontList(); - break; - case nameof(TabIndex) when TabIndex > -1 && IsSecondaryView is false: - Settings.LastTabIndex = TabIndex; - break; - case nameof(SelectedFont) when SelectedFont is not null && IsSecondaryView is false: - Settings.LastSelectedFontName = SelectedFont.Name; - break; - case nameof(FontSearch): - _searchDebouncer.Debounce(FontSearch.Length == 0 ? 100 : 500, () => RefreshFontList(SelectedCollection)); - break; - } + case nameof(FontList): + CreateFontListGroup(); + break; + case nameof(FontListFilter): + RefreshFontList(); + break; + case nameof(TabIndex) when TabIndex > -1 && IsSecondaryView is false: + Settings.LastTabIndex = TabIndex; + break; + case nameof(SelectedFont) when SelectedFont is not null && IsSecondaryView is false: + Settings.LastSelectedFontName = SelectedFont.Name; + break; + case nameof(FontSearch): + _searchDebouncer.Debounce(FontSearch.Length == 0 ? 100 : 500, () => RefreshFontList(SelectedCollection)); + break; } + } - private async Task LoadAsync(bool isFirstLoad = false) - { - IsLoadingFonts = true; - - try - { - if (IsSecondaryView is false) - { - _ = Utils.DeleteAsync(ApplicationData.Current.TemporaryFolder); - await Task.WhenAll( - GlyphService.InitializeAsync(), - FontFinder.LoadFontsAsync(!isFirstLoad), - FontCollections.LoadCollectionsAsync()); - - NativeInterop interop = Utils.GetInterop(); - interop.FontSetInvalidated -= FontSetInvalidated; - interop.FontSetInvalidated += FontSetInvalidated; - } + private async Task LoadAsync(bool isFirstLoad = false) + { + IsLoadingFonts = true; - RefreshFontList(); - if (isFirstLoad) - RestoreOpenFonts(); - } - catch (Exception ex) + try + { + if (IsSecondaryView is false) { - // For whatever reason, this exception doesn't get caught by the app's - // UnhandledExceptionHandler, so we need to manually catch and handle it. - _startUpException = ex; - ShowStartUpException(); - IsLoadingFonts = false; - IsLoadingFontsFailed = true; - return; + _ = Utils.DeleteAsync(ApplicationData.Current.TemporaryFolder); + await Task.WhenAll( + GlyphService.InitializeAsync(), + FontFinder.LoadFontsAsync(!isFirstLoad), + FontCollections.LoadCollectionsAsync()); + + NativeInterop interop = Utils.GetInterop(); + interop.FontSetInvalidated -= FontSetInvalidated; + interop.FontSetInvalidated += FontSetInvalidated; } - IsLoadingFonts = false; + RefreshFontList(); + if (isFirstLoad) + RestoreOpenFonts(); } - - private void FontSetInvalidated(NativeInterop sender, object args) + catch (Exception ex) { - _ = MainPage.MainDispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => - { - IsFontSetExpired = true; - }); + // For whatever reason, this exception doesn't get caught by the app's + // UnhandledExceptionHandler, so we need to manually catch and handle it. + _startUpException = ex; + ShowStartUpException(); + IsLoadingFonts = false; + IsLoadingFontsFailed = true; + return; } - public void ShowStartUpException() - { - UnhandledExceptionDialog.Show(_startUpException); - } + IsLoadingFonts = false; + } - public void ReloadFontSet() + private void FontSetInvalidated(NativeInterop sender, object args) + { + _ = MainPage.MainDispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () => { - // Bug #152: Sometimes XAML font cache doesn't update after a new font is installed on system. - // Currently only way to force this is to reload the app from scratch - //_ = ReloadFontSetAsync(); - _ = CoreApplication.RequestRestartAsync(string.Empty); - } + IsFontSetExpired = true; + }); + } - public async Task ReloadFontSetAsync() - { - IsLoadingFonts = true; - IsFontSetExpired = false; - SelectedFont = FontFinder.DefaultFont; - await FontFinder.LoadFontsAsync(); - RefreshFontList(SelectedCollection); - IsLoadingFonts = false; - } + public void ShowStartUpException() + { + UnhandledExceptionDialog.Show(_startUpException); + } + + public void ReloadFontSet() + { + // Bug #152: Sometimes XAML font cache doesn't update after a new font is installed on system. + // Currently only way to force this is to reload the app from scratch + //_ = ReloadFontSetAsync(); + _ = CoreApplication.RequestRestartAsync(string.Empty); + } + + public async Task ReloadFontSetAsync() + { + IsLoadingFonts = true; + IsFontSetExpired = false; + SelectedFont = FontFinder.DefaultFont; + await FontFinder.LoadFontsAsync(); + RefreshFontList(SelectedCollection); + IsLoadingFonts = false; + } - public async void RefreshFontList(UserFontCollection collection = null) + public async void RefreshFontList(UserFontCollection collection = null) + { + if (CanFilter == false) + return; + try { - if (CanFilter == false) - return; - try + IEnumerable fontList = Folder?.Fonts ?? FontFinder.Fonts; + + if (collection != null) { - IEnumerable fontList = Folder?.Fonts ?? FontFinder.Fonts; + FilterTitle = collection.Name; + fontList = fontList.Where(f => collection.Fonts.Contains(f.Name)); + } + else + { + SelectedCollection = null; + FilterTitle = FontListFilter.FilterTitle; - if (collection != null) - { - FilterTitle = collection.Name; - fontList = fontList.Where(f => collection.Fonts.Contains(f.Name)); - } + if (FontListFilter == BasicFontFilter.ImportedFonts) + fontList = FontFinder.ImportedFonts; else { - SelectedCollection = null; - FilterTitle = FontListFilter.FilterTitle; - - if (FontListFilter == BasicFontFilter.ImportedFonts) - fontList = FontFinder.ImportedFonts; - else + if (FontListFilter.RequiresAsync) { - if (FontListFilter.RequiresAsync) - { - CanFilter = false; - fontList = await Task.Run(() => FontListFilter.Query(fontList, FontCollections)); - FontListFilter.RequiresAsync = false; - } - else - fontList = FontListFilter.Query(fontList, FontCollections); + CanFilter = false; + fontList = await Task.Run(() => FontListFilter.Query(fontList, FontCollections)); + FontListFilter.RequiresAsync = false; } + else + fontList = FontListFilter.Query(fontList, FontCollections); } - - if (!string.IsNullOrWhiteSpace(FontSearch)) - { - fontList = fontList.Where(f => f.Name.Contains(FontSearch, StringComparison.OrdinalIgnoreCase)); - string prefix = FontListFilter == BasicFontFilter.All ? "" : FontListFilter.FilterTitle + " "; - FilterTitle = $"{(collection != null ? collection.Name + " " : prefix)}\"{FontSearch}\""; - IsSearchResults = true; - } - else - IsSearchResults = false; - - FontList = fontList.ToList(); - } - catch (Exception e) - { - DialogService.ShowMessageBox( - e.Message, Localization.Get("LoadingFontListError")); } - finally + + if (!string.IsNullOrWhiteSpace(FontSearch)) { - CanFilter = true; + fontList = fontList.Where(f => f.Name.Contains(FontSearch, StringComparison.OrdinalIgnoreCase)); + string prefix = FontListFilter == BasicFontFilter.All ? "" : FontListFilter.FilterTitle + " "; + FilterTitle = $"{(collection != null ? collection.Name + " " : prefix)}\"{FontSearch}\""; + IsSearchResults = true; } + else + IsSearchResults = false; + + FontList = fontList.ToList(); + } + catch (Exception e) + { + DialogService.ShowMessageBox( + e.Message, Localization.Get("LoadingFontListError")); + } + finally + { + CanFilter = true; } + } - private void Fonts_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void Fonts_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + // Our primary concern here is saving the Tab list + // of opened fonts. We only need to do this for the + // primary window. + if (IsSecondaryView is false) { - // Our primary concern here is saving the Tab list - // of opened fonts. We only need to do this for the - // primary window. - if (IsSecondaryView is false) - { - OnPropertyChanged(nameof(CurrentFont)); + OnPropertyChanged(nameof(CurrentFont)); - // 1. Ensure child items are listened too - if (e.Action is NotifyCollectionChangedAction.Remove or NotifyCollectionChangedAction.Replace) - { - foreach (var item in e.OldItems.Cast()) - item.PropertyChanged -= Item_PropertyChanged; - } - else if (e.Action is NotifyCollectionChangedAction.Add or NotifyCollectionChangedAction.Reset - && e.NewItems is not null) + // 1. Ensure child items are listened too + if (e.Action is NotifyCollectionChangedAction.Remove or NotifyCollectionChangedAction.Replace) + { + foreach (var item in e.OldItems.Cast()) + item.PropertyChanged -= Item_PropertyChanged; + } + else if (e.Action is NotifyCollectionChangedAction.Add or NotifyCollectionChangedAction.Reset + && e.NewItems is not null) + { + foreach (var item in e.NewItems.Cast()) { - foreach (var item in e.NewItems.Cast()) - { - item.PropertyChanged -= Item_PropertyChanged; - item.PropertyChanged += Item_PropertyChanged; - } + item.PropertyChanged -= Item_PropertyChanged; + item.PropertyChanged += Item_PropertyChanged; } - - // 2. Save current open items - Save(); } + // 2. Save current open items + Save(); + } - /// - /// HELPERS - /// - void Item_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) - { - // If the selected variant is changed, resave the font list - if (e.PropertyName is nameof(FontItem.Selected) or nameof(FontItem.DisplayMode)) - Save(); - } - void Save() + /// + /// HELPERS + /// + void Item_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + // If the selected variant is changed, resave the font list + if (e.PropertyName is nameof(FontItem.Selected) or nameof(FontItem.DisplayMode)) + Save(); + } + + void Save() + { + if (IsSecondaryView) + return; + + _settingsDebouncer.Debounce(200, () => { - if (IsSecondaryView) - return; + Settings.LastOpenFonts = Fonts.SelectMany(f => + new List { + f.Font.Name, + f.Font.Variants.IndexOf(f.Selected).ToString(), + ((int)f.DisplayMode).ToString() + }).ToList(); + Settings.LastTabIndex = TabIndex; + + if (SelectedFont is not null) + Settings.LastSelectedFontName = SelectedFont.Name; + else if (TabIndex < Fonts.Count) + Settings.LastSelectedFontName = Fonts[TabIndex].Font.Name; - _settingsDebouncer.Debounce(200, () => - { - Settings.LastOpenFonts = Fonts.SelectMany(f => - new List { - f.Font.Name, - f.Font.Variants.IndexOf(f.Selected).ToString(), - ((int)f.DisplayMode).ToString() - }).ToList(); - Settings.LastTabIndex = TabIndex; - - if (SelectedFont is not null) - Settings.LastSelectedFontName = SelectedFont.Name; - else if (TabIndex < Fonts.Count) - Settings.LastSelectedFontName = Fonts[TabIndex].Font.Name; - - }); - } + }); } + } - public void RestoreOpenFonts() + public void RestoreOpenFonts() + { + if (IsSecondaryView is false + && Settings.LastOpenFonts is IList list + && list.Count > 0) { - if (IsSecondaryView is false - && Settings.LastOpenFonts is IList list - && list.Count > 0) - { - bool removed = false; - Fonts.Clear(); + bool removed = false; + Fonts.Clear(); - // 1. Parse list of saved fonts - for (int i = 0; i < list.Count; i++) + // 1. Parse list of saved fonts + for (int i = 0; i < list.Count; i++) + { + // 1.1. Ensure the saved font hasn't been deleted. If it hasn't, + // add it to the list. + if (FontFinder.FontDictionary.TryGetValue(list[i], out InstalledFont font)) { - // 1.1. Ensure the saved font hasn't been deleted. If it hasn't, - // add it to the list. - if (FontFinder.FontDictionary.TryGetValue(list[i], out InstalledFont font)) + // 1.2. The selected font face (or another in the family) may have been + // deleted making the stored index invalid. Make sure the index is + // still within a valid range. + int faceIdx = Convert.ToInt32(list[++i]); + if ((faceIdx < font.Variants.Count) is false) + faceIdx = font.Variants.IndexOf(font.DefaultVariant); + + Fonts.Add(new(font) { - // 1.2. The selected font face (or another in the family) may have been - // deleted making the stored index invalid. Make sure the index is - // still within a valid range. - int faceIdx = Convert.ToInt32(list[++i]); - if ((faceIdx < font.Variants.Count) is false) - faceIdx = font.Variants.IndexOf(font.DefaultVariant); - - Fonts.Add(new(font) - { - Selected = font.Variants[faceIdx], - DisplayMode = (FontDisplayMode)Convert.ToInt32(list[++i]) - }); - } - else - { - // Font has probably been uninstalled - ++i; // Skip over saved variant - ++i; // Skip over saved display mode + Selected = font.Variants[faceIdx], + DisplayMode = (FontDisplayMode)Convert.ToInt32(list[++i]) + }); + } + else + { + // Font has probably been uninstalled + ++i; // Skip over saved variant + ++i; // Skip over saved display mode - removed = true; - } + removed = true; } + } - // 2. Handle restoring fonts - if (Fonts.Count == 0) + // 2. Handle restoring fonts + if (Fonts.Count == 0) + { + // If no fonts have been restored, either this is a first run, or user has uninstalled + // all the previously open fonts. In this case we use the first font we can find. + if (FontList.FirstOrDefault() is InstalledFont first) { - // If no fonts have been restored, either this is a first run, or user has uninstalled - // all the previously open fonts. In this case we use the first font we can find. - if (FontList.FirstOrDefault() is InstalledFont first) - { - Fonts.Add(new(first)); - SelectedFont = first; - TabIndex = 0; - } - else - { - // No fonts, do nuffin' - } + Fonts.Add(new(first)); + SelectedFont = first; + TabIndex = 0; } else { - int tabIndex = -1; - // 3. Try to restore SelectedFont & TabIndex. - // First, check if the SelectedFont still actually exists, as the user may have - // uninstalled it between application runs. - if (FontFinder.FontDictionary.TryGetValue(Settings.LastSelectedFontName, out InstalledFont last)) - { - // 3.1. Restore TabIndex. - // If a font was removed between runs TabIndex may no longer be valid, - // so find the first matching font - if (removed) - tabIndex = Fonts.Select(f => f.Font).ToList().IndexOf(last); - else - tabIndex = Settings.LastTabIndex; - - // 3.2. If TabIndex doesn't match the font, ignore both values and use the first font - if (tabIndex == -1 || tabIndex >= Fonts.Count || Fonts[tabIndex].Font != last) - tabIndex = 0; - } + // No fonts, do nuffin' + } + } + else + { + int tabIndex = -1; + // 3. Try to restore SelectedFont & TabIndex. + // First, check if the SelectedFont still actually exists, as the user may have + // uninstalled it between application runs. + if (FontFinder.FontDictionary.TryGetValue(Settings.LastSelectedFontName, out InstalledFont last)) + { + // 3.1. Restore TabIndex. + // If a font was removed between runs TabIndex may no longer be valid, + // so find the first matching font + if (removed) + tabIndex = Fonts.Select(f => f.Font).ToList().IndexOf(last); else - { - // 3.3. The last selected font has been deleted. Use the first one we have. + tabIndex = Settings.LastTabIndex; + + // 3.2. If TabIndex doesn't match the font, ignore both values and use the first font + if (tabIndex == -1 || tabIndex >= Fonts.Count || Fonts[tabIndex].Font != last) tabIndex = 0; - } + } + else + { + // 3.3. The last selected font has been deleted. Use the first one we have. + tabIndex = 0; + } - // 3.4. Set deduced TabIndex safely - TabIndex = Math.Max(0, Math.Min(Fonts.Count - 1, tabIndex)); + // 3.4. Set deduced TabIndex safely + TabIndex = Math.Max(0, Math.Min(Fonts.Count - 1, tabIndex)); - // 4. Restore SelectedFont. This may not longer match LastSelectedFontName if - // we found out-of-sync values above. - SelectedFont = Fonts[TabIndex].Font; - } - } - else if (FontList.FirstOrDefault() is InstalledFont first) - { - // Fallback to first font available - Fonts.Add(new(first)); - SelectedFont = first; - TabIndex = 0; + // 4. Restore SelectedFont. This may not longer match LastSelectedFontName if + // we found out-of-sync values above. + SelectedFont = Fonts[TabIndex].Font; } } - public bool IsCreating { get; private set; } - private void CreateFontListGroup() + else if (FontList.FirstOrDefault() is InstalledFont first) { - try + // Fallback to first font available + Fonts.Add(new(first)); + SelectedFont = first; + TabIndex = 0; + } + } + public bool IsCreating { get; private set; } + private void CreateFontListGroup() + { + try + { + IsCreating = true; + + // 1. Cache last selected now as setting GroupedFontList can change it. + // Use TabIndex as SelectedFont may be inaccurate when inside a filter + // with tabs that aren't inside the current FontList + InstalledFont selected = + Fonts.Count > 0 + ? (TabIndex > -1 ? Fonts[TabIndex].Font : SelectedFont) + : Fonts.FirstOrDefault()?.Font; + + // 2. Group the font list + var list = AlphaKeyGroup.CreateGroups(FontList, f => f.Name.Substring(0, 1)); + GroupedFontList = new(list); + HasFonts = FontList.Count > 0; + + // 3. If empty, close everything + if (FontList.Count == 0) { - IsCreating = true; - - // 1. Cache last selected now as setting GroupedFontList can change it. - // Use TabIndex as SelectedFont may be inaccurate when inside a filter - // with tabs that aren't inside the current FontList - InstalledFont selected = - Fonts.Count > 0 - ? (TabIndex > -1 ? Fonts[TabIndex].Font : SelectedFont) - : Fonts.FirstOrDefault()?.Font; - - // 2. Group the font list - var list = AlphaKeyGroup.CreateGroups(FontList, f => f.Name.Substring(0, 1)); - GroupedFontList = new (list); - HasFonts = FontList.Count > 0; - - // 3. If empty, close everything - if (FontList.Count == 0) - { - SelectedFont = null; - foreach (var font in Fonts.ToList()) - font.IsCompact = true; - return; - } + SelectedFont = null; + foreach (var font in Fonts.ToList()) + font.IsCompact = true; + return; + } - // Clear Font List selection on left pane if needed - if (IsLoadingFonts is false && FontList.Contains(selected) is false) - SelectedFont = null; + // Clear Font List selection on left pane if needed + if (IsLoadingFonts is false && FontList.Contains(selected) is false) + SelectedFont = null; - // 4. Set the correct selected font and remove tabs that are no longer in the list - if (selected is not null) + // 4. Set the correct selected font and remove tabs that are no longer in the list + if (selected is not null) + { + // 4.1. Update tab size + foreach (var font in Fonts.ToList()) { - // 4.1. Update tab size - foreach (var font in Fonts.ToList()) - { - font.IsCompact = FontList.Contains(font.Font) is false; - } + font.IsCompact = FontList.Contains(font.Font) is false; + } - // 4.2. Handle selected font - if (SelectedFont == null || selected != SelectedFont) - { - var lastSelectedFont = FontList.Contains(selected); - SelectedFont = selected; - } - else - { - OnPropertyChanged("FontSelectionDebounce"); - } + // 4.2. Handle selected font + if (SelectedFont == null || selected != SelectedFont) + { + var lastSelectedFont = FontList.Contains(selected); + SelectedFont = selected; } else { - //SelectedFont = FontList.FirstOrDefault(); + OnPropertyChanged("FontSelectionDebounce"); } - - FontListCreated?.Invoke(this, EventArgs.Empty); } - catch (Exception e) + else { - DialogService.ShowMessageBox( - e.Message, Localization.Get("LoadingFontListError")); + //SelectedFont = FontList.FirstOrDefault(); } - IsCreating = false; + FontListCreated?.Invoke(this, EventArgs.Empty); } - - public void OpenTab(InstalledFont font) + catch (Exception e) { - Fonts.Insert(TabIndex + 1, new(font)); + DialogService.ShowMessageBox( + e.Message, Localization.Get("LoadingFontListError")); } - public bool TryCloseTab(int idx) - { - if (Fonts.Count > 1) - { - Fonts.RemoveAt(idx); - return true; - } + IsCreating = false; + } - return false; - } + public void OpenTab(InstalledFont font) + { + Fonts.Insert(TabIndex + 1, new(font)); + } - public void NotifyTabs() + public bool TryCloseTab(int idx) + { + if (Fonts.Count > 1) { - // Fires a faux notification for changing the "Font" in a FontItem, - // causing the binding used for choosing which font to display to update. - foreach (var font in Fonts) - font.NotifyFontChange(); + Fonts.RemoveAt(idx); + return true; } - internal void TrySetSelectionFromImport(FontImportResult result) - { - StorageFile file = result.Imported.FirstOrDefault() ?? result.Existing.FirstOrDefault(); - if (file != null - && FontList.FirstOrDefault(f => - f.HasImportedFiles && f.DefaultVariant.FileName == file.Name) is InstalledFont font) - { - SelectedFont = font; - FontListCreated?.Invoke(this, EventArgs.Empty); - } - } + return false; + } + + public void NotifyTabs() + { + // Fires a faux notification for changing the "Font" in a FontItem, + // causing the binding used for choosing which font to display to update. + foreach (var font in Fonts) + font.NotifyFontChange(); + } - internal async void TryRemoveFont(InstalledFont font) + internal void TrySetSelectionFromImport(FontImportResult result) + { + StorageFile file = result.Imported.FirstOrDefault() ?? result.Existing.FirstOrDefault(); + if (file != null + && FontList.FirstOrDefault(f => + f.HasImportedFiles && f.DefaultVariant.FileName == file.Name) is InstalledFont font) { - IsLoadingFonts = true; + SelectedFont = font; + FontListCreated?.Invoke(this, EventArgs.Empty); + } + } - /* Yes, this is hack. The UI needs to time to remove references to the - * current Font otherwise we won't be able to delete it because the file will - * be "in use". 16ms works fine on my test machines, but better safe than - * sorry - this isn't a fast operation in sum anyway because we reload - * all fonts, so extra 150ms is nothing... - */ - SelectedFont = FontFinder.DefaultFont; + internal async void TryRemoveFont(InstalledFont font) + { + IsLoadingFonts = true; - // Remove from open tabs - var items = Fonts.Where(f => f.Font == font).ToList(); - foreach (var item in items) - Fonts.Remove(item); + /* Yes, this is hack. The UI needs to time to remove references to the + * current Font otherwise we won't be able to delete it because the file will + * be "in use". 16ms works fine on my test machines, but better safe than + * sorry - this isn't a fast operation in sum anyway because we reload + * all fonts, so extra 150ms is nothing... + */ + SelectedFont = FontFinder.DefaultFont; - await Task.Delay(150); + // Remove from open tabs + var items = Fonts.Where(f => f.Font == font).ToList(); + foreach (var item in items) + Fonts.Remove(item); - bool result = await FontFinder.RemoveFontAsync(font); - if (result) - { - _ = FontCollections.RemoveFromAllCollectionsAsync(font); - } + await Task.Delay(150); + bool result = await FontFinder.RemoveFontAsync(font); + if (result) + { + _ = FontCollections.RemoveFromAllCollectionsAsync(font); + } - RefreshFontList(SelectedCollection); - RestoreOpenFonts(); - IsLoadingFonts = false; + RefreshFontList(SelectedCollection); + RestoreOpenFonts(); - if (!result) - { - /* looks like we couldn't delete some fonts :'(. - * We'll get em next time the app launches! */ - Messenger.Send( - new AppNotificationMessage(true, Localization.Get("FontsClearedOnNextLaunchNotice"), 6000)); - } - } + IsLoadingFonts = false; - internal async void ExportAsZip() + if (!result) { - IsCollectionExportEnabled = false; - - try - { - await ExportManager.ExportCollectionAsZipAsync( - FontList, - SelectedCollection, - p => OnSyncContext(() => CollectionExportProgress = p)); - } - finally - { - IsCollectionExportEnabled = true; - } + /* looks like we couldn't delete some fonts :'(. + * We'll get em next time the app launches! */ + Messenger.Send( + new AppNotificationMessage(true, Localization.Get("FontsClearedOnNextLaunchNotice"), 6000)); } + } - internal async void ExportAsFolder() - { - IsCollectionExportEnabled = false; + internal async void ExportAsZip() + { + IsCollectionExportEnabled = false; - try - { - await ExportManager.ExportCollectionToFolderAsync( - FontList, - p => OnSyncContext(() => CollectionExportProgress = p)); - } - finally - { - IsCollectionExportEnabled = true; - } + try + { + await ExportManager.ExportCollectionAsZipAsync( + FontList, + SelectedCollection, + p => OnSyncContext(() => CollectionExportProgress = p)); } - - public void OpenSourceFolder() + finally { - if (Folder is not null) - _ = Folder.LaunchSourceAsync(); + IsCollectionExportEnabled = true; } } - public class MainViewModelArgs + internal async void ExportAsFolder() { - public MainViewModelArgs(IDialogService dialogService, AppSettings settings, FolderContents folder) + IsCollectionExportEnabled = false; + + try { - DialogService = dialogService; - Settings = settings; - Folder = folder; + await ExportManager.ExportCollectionToFolderAsync( + FontList, + p => OnSyncContext(() => CollectionExportProgress = p)); } + finally + { + IsCollectionExportEnabled = true; + } + } - public IDialogService DialogService { get; } - public AppSettings Settings { get; } - public FolderContents Folder { get; } + public void OpenSourceFolder() + { + if (Folder is not null) + _ = Folder.LaunchSourceAsync(); + } +} + +public class MainViewModelArgs +{ + public MainViewModelArgs(IDialogService dialogService, AppSettings settings, FolderContents folder) + { + DialogService = dialogService; + Settings = settings; + Folder = folder; } + + public IDialogService DialogService { get; } + public AppSettings Settings { get; } + public FolderContents Folder { get; } } \ No newline at end of file diff --git a/CharacterMap/CharacterMap/ViewModels/PrintViewModel.cs b/CharacterMap/CharacterMap/ViewModels/PrintViewModel.cs index a4b0303d..344ea700 100644 --- a/CharacterMap/CharacterMap/ViewModels/PrintViewModel.cs +++ b/CharacterMap/CharacterMap/ViewModels/PrintViewModel.cs @@ -1,183 +1,169 @@ using CharacterMap.Controls; -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using System; -using System.Collections.Generic; -using System.Linq; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Media; -namespace CharacterMap.ViewModels +namespace CharacterMap.ViewModels; + +public class PrintViewModel : ViewModelBase { - public class PrintViewModel : ViewModelBase - { - protected override bool TrackAnimation => true; + protected override bool TrackAnimation => true; - public FontVariant Font { get; set; } + public FontVariant Font { get; set; } - public TypographyFeatureInfo Typography { get; set; } + public TypographyFeatureInfo Typography { get; set; } - public FontFamily FontFamily { get; set; } + public FontFamily FontFamily { get; set; } - private bool _showMargins = false; - public bool ShowMargins - { - get => _showMargins; - set => Set(ref _showMargins, value); - } + private bool _showMargins = false; + public bool ShowMargins + { + get => _showMargins; + set => Set(ref _showMargins, value); + } - private bool _showBorders = false; - public bool ShowBorders - { - get => _showBorders; - set => Set(ref _showBorders, value); - } + private bool _showBorders = false; + public bool ShowBorders + { + get => _showBorders; + set => Set(ref _showBorders, value); + } - private bool _hideWhitespace = true; - public bool HideWhitespace + private bool _hideWhitespace = true; + public bool HideWhitespace + { + get => _hideWhitespace; + set { - get => _hideWhitespace; - set + if (value != _hideWhitespace) { - if (value != _hideWhitespace) - { - _hideWhitespace = value; - UpdateCharacters(); - OnPropertyChanged(); - } + _hideWhitespace = value; + UpdateCharacters(); + OnPropertyChanged(); } } + } - private bool _showColorGlyphs = true; - public bool ShowColorGlyphs - { - get => _showColorGlyphs; - set => Set(ref _showColorGlyphs, value); - } + private bool _showColorGlyphs = true; + public bool ShowColorGlyphs + { + get => _showColorGlyphs; + set => Set(ref _showColorGlyphs, value); + } - private double _glyphSize = 70d; - public double GlyphSize - { - get => _glyphSize; - set => Set(ref _glyphSize, value); - } + private double _glyphSize = 70d; + public double GlyphSize + { + get => _glyphSize; + set => Set(ref _glyphSize, value); + } - private double _horizontalMargin = 44d; - public double HorizontalMargin - { - get => _horizontalMargin; - set => Set(ref _horizontalMargin, value); - } + private double _horizontalMargin = 44d; + public double HorizontalMargin + { + get => _horizontalMargin; + set => Set(ref _horizontalMargin, value); + } - private double _verticalMargin = 44d; - public double VerticalMargin - { - get => _verticalMargin; - set => Set(ref _verticalMargin, value); - } + private double _verticalMargin = 44d; + public double VerticalMargin + { + get => _verticalMargin; + set => Set(ref _verticalMargin, value); + } - private GlyphAnnotation _annotation = GlyphAnnotation.None; - public GlyphAnnotation Annotation - { - get => _annotation; - set => Set(ref _annotation, value); - } + private GlyphAnnotation _annotation = GlyphAnnotation.None; + public GlyphAnnotation Annotation + { + get => _annotation; + set => Set(ref _annotation, value); + } - private PrintLayout _layout = PrintLayout.Grid; - public PrintLayout Layout - { - get => _layout; - set => Set(ref _layout, value); - } + private PrintLayout _layout = PrintLayout.Grid; + public PrintLayout Layout + { + get => _layout; + set => Set(ref _layout, value); + } - private Orientation _orientation = Orientation.Vertical; - public Orientation Orientation - { - get => _orientation; - set => Set(ref _orientation, value); - } - public IReadOnlyList Characters { get; set; } + private Orientation _orientation = Orientation.Vertical; + public Orientation Orientation + { + get => _orientation; + set => Set(ref _orientation, value); + } + public IReadOnlyList Characters { get; set; } - private IList _categories; - public IList Categories - { - get => _categories; - private set => Set(ref _categories, value); - } + private IList _categories; + public IList Categories + { + get => _categories; + private set => Set(ref _categories, value); + } - private int _firstPage = 1; - public int FirstPage - { - get => _firstPage; - set => Set(ref _firstPage, value); - } + private int _firstPage = 1; + public int FirstPage + { + get => _firstPage; + set => Set(ref _firstPage, value); + } - private int _pageCount = 50; - public int PagesToPrint - { - get => _pageCount; - set => Set(ref _pageCount, value); - } + private int _pageCount = 50; + public int PagesToPrint + { + get => _pageCount; + set => Set(ref _pageCount, value); + } - public bool IsPortrait => Orientation == Orientation.Vertical; + public bool IsPortrait => Orientation == Orientation.Vertical; - internal CharacterGridViewTemplateSettings GetTemplateSettings() - { - return new CharacterGridViewTemplateSettings - { - Size = GlyphSize, - ShowColorGlyphs = ShowColorGlyphs, - Annotation = GlyphAnnotation.None, - Typography = Typography, - FontFamily = FontFamily, - FontFace = Font.Face - }; - } + internal CharacterGridViewTemplateSettings GetTemplateSettings() + { + return new CharacterGridViewTemplateSettings + { + Size = GlyphSize, + ShowColorGlyphs = ShowColorGlyphs, + Annotation = GlyphAnnotation.None, + Typography = Typography, + FontFamily = FontFamily, + FontFace = Font.Face + }; + } - public void UpdateCategories(IList value) - { - _categories = value; - UpdateCharacters(); - OnPropertyChanged(nameof(Categories)); - } + public void UpdateCategories(IList value) + { + _categories = value; + UpdateCharacters(); + OnPropertyChanged(nameof(Categories)); + } - private void UpdateCharacters() + private void UpdateCharacters() + { + // Fast path : all characters; + if (!Categories.Any(c => !c.IsSelected) && !HideWhitespace) { - // Fast path : all characters; - if (!Categories.Any(c => !c.IsSelected) && !HideWhitespace) - { - Characters = Font.Characters; - return; - } - - // Filter characters - var chars = Font.Characters.AsEnumerable(); - if (HideWhitespace) - chars = Font.Characters.Where(c => !Unicode.IsWhiteSpaceOrControl(c.UnicodeIndex)); - - foreach (var cat in Categories.Where(c => !c.IsSelected)) - chars = chars.Where(c => c.Range != cat.Range); - - Characters = chars.ToList(); + Characters = Font.Characters; + return; } - private PrintViewModel() { } + // Filter characters + Characters = Unicode.FilterCharacters(Font.Characters, Categories, HideWhitespace); + } - public static PrintViewModel Create(FontMapViewModel viewModel) - { - PrintViewModel model = new() - { - ShowColorGlyphs = viewModel.ShowColorGlyphs, - Typography = viewModel.SelectedTypography, - FontFamily = viewModel.FontFamily, - Font = viewModel.SelectedVariant, - Annotation = viewModel.Settings.GlyphAnnotation, - Categories = viewModel.SelectedGlyphCategories.Select(c => c.Clone()).ToList() - }; - - model.UpdateCharacters(); - return model; - } + private PrintViewModel() { } + + public static PrintViewModel Create(FontMapViewModel viewModel) + { + PrintViewModel model = new() + { + ShowColorGlyphs = viewModel.ShowColorGlyphs, + Typography = viewModel.SelectedTypography, + FontFamily = viewModel.FontFamily, + Font = viewModel.SelectedVariant, + Annotation = viewModel.Settings.GlyphAnnotation, + Categories = viewModel.SelectedGlyphCategories.Select(c => c.Clone()).ToList() + }; + + model.UpdateCharacters(); + return model; } } diff --git a/CharacterMap/CharacterMap/ViewModels/QuickCompareViewModel.cs b/CharacterMap/CharacterMap/ViewModels/QuickCompareViewModel.cs index 5a131419..885cf724 100644 --- a/CharacterMap/CharacterMap/ViewModels/QuickCompareViewModel.cs +++ b/CharacterMap/CharacterMap/ViewModels/QuickCompareViewModel.cs @@ -1,233 +1,221 @@ -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Services; -using CharacterMap.Views; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.DependencyInjection; +using CharacterMap.Views; using CommunityToolkit.Mvvm.Input; -using CommunityToolkit.Mvvm.Messaging; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Collections.Specialized; -using System.Linq; using System.Windows.Input; using Windows.ApplicationModel; -namespace CharacterMap.ViewModels +namespace CharacterMap.ViewModels; + +public class QuickCompareArgs { - public class QuickCompareArgs - { - public FolderContents Folder { get; set; } + public FolderContents Folder { get; set; } - public bool IsQuickCompare { get; set; } + public bool IsQuickCompare { get; set; } - public bool IsFolderView => Folder is not null; + public bool IsFolderView => Folder is not null; - public UserFontCollection SelectedCollection { get; init; } + public UserFontCollection SelectedCollection { get; init; } - public QuickCompareArgs(bool isQuickCompare, FolderContents folder = null) - { - IsQuickCompare = isQuickCompare; - Folder = folder; - } + public QuickCompareArgs(bool isQuickCompare, FolderContents folder = null) + { + IsQuickCompare = isQuickCompare; + Folder = folder; } +} - public partial class QuickCompareViewModel : ViewModelBase - { - public static WindowInformation QuickCompareWindow { get; set; } +public partial class QuickCompareViewModel : ViewModelBase +{ + public static WindowInformation QuickCompareWindow { get; set; } - protected override bool TrackAnimation => true; + protected override bool TrackAnimation => true; - [ObservableProperty] - IReadOnlyList _textOptions = null; + [ObservableProperty] + IReadOnlyList _textOptions = null; - public string Title { get => Get(); set => Set(value); } + public string Title { get => Get(); set => Set(value); } - public string Text { get => Get(); set => Set(value); } + public string Text { get => Get(); set => Set(value); } - public string FilterTitle { get => Get(); set => Set(value); } - - public InstalledFont SelectedFont { get => Get(); set => Set(value); } + public string FilterTitle { get => Get(); set => Set(value); } - public ObservableCollection FontList { get => Get>(); set => Set(value); } + public InstalledFont SelectedFont { get => Get(); set => Set(value); } - private BasicFontFilter _fontListFilter = BasicFontFilter.All; - public BasicFontFilter FontListFilter - { - get => _fontListFilter; - set { if (Set(ref _fontListFilter, value)) RefreshFontList(); } - } + public ObservableCollection FontList { get => Get>(); set => Set(value); } - public ObservableCollection QuickFonts { get; } + private BasicFontFilter _fontListFilter = BasicFontFilter.All; + public BasicFontFilter FontListFilter + { + get => _fontListFilter; + set { if (Set(ref _fontListFilter, value)) RefreshFontList(); } + } - private UserFontCollection _selectedCollection; - public UserFontCollection SelectedCollection + public ObservableCollection QuickFonts { get; } + + private UserFontCollection _selectedCollection; + public UserFontCollection SelectedCollection + { + get => _selectedCollection; + set { - get => _selectedCollection; - set + if (value != null && value.IsSystemSymbolCollection) { - if (value != null && value.IsSystemSymbolCollection) - { - FontListFilter = BasicFontFilter.SymbolFonts; - return; - } - - if (Set(ref _selectedCollection, value) && value != null) - RefreshFontList(value); + FontListFilter = BasicFontFilter.SymbolFonts; + return; } + + if (Set(ref _selectedCollection, value) && value != null) + RefreshFontList(value); } + } - public INotifyCollectionChanged ItemsSource => IsQuickCompare ? QuickFonts : FontList; + public INotifyCollectionChanged ItemsSource => IsQuickCompare ? QuickFonts : FontList; - public UserCollectionsService FontCollections { get; } + public UserCollectionsService FontCollections { get; } - public ICommand FilterCommand { get; } + public ICommand FilterCommand { get; } - public ICommand CollectionSelectedCommand { get; } + public ICommand CollectionSelectedCommand { get; } - public bool IsQuickCompare { get; } + public bool IsQuickCompare { get; } - public bool IsFolderMode { get; } + public bool IsFolderMode { get; } - FolderContents _folder = null; + FolderContents _folder = null; - public QuickCompareViewModel(QuickCompareArgs args) - { - // N.B: arg.IsQuickCompare denotes the singleton QuickCompare view. - // IsQuickCompare controls the behaviour of this page - they are - // two different things. We can have many windows with IsQuickCompare - // behaviour, but only one can act as the main QuickCompare singleton. + public QuickCompareViewModel(QuickCompareArgs args) + { + // N.B: arg.IsQuickCompare denotes the singleton QuickCompare view. + // IsQuickCompare controls the behaviour of this page - they are + // two different things. We can have many windows with IsQuickCompare + // behaviour, but only one can act as the main QuickCompare singleton. - IsQuickCompare = args.IsQuickCompare || (args.Folder?.UseQuickCompare is bool b && b); + IsQuickCompare = args.IsQuickCompare || (args.Folder?.UseQuickCompare is bool b && b); - if (DesignMode.DesignModeEnabled) - return; + if (DesignMode.DesignModeEnabled) + return; - if (IsQuickCompare) + if (IsQuickCompare) + { + if (args.IsQuickCompare) { - if (args.IsQuickCompare) + // This is the universal quick-compare window + QuickFonts = new(); + Register(m => { - // This is the universal quick-compare window - QuickFonts = new(); - Register(m => - { - // Only add the font variant if it's not already in the list. - // Once we start accepting custom typography this comparison - // will have to change. - if (!QuickFonts.Any(q => m.IsCompareMatch(q))) - QuickFonts.Add(m); - }, nameof(QuickCompareViewModel)); - } - else - { - // This is probably the tab bar compare window - QuickFonts = new(args.Folder.Variants.Select(v => CharacterRenderingOptions.CreateDefault(v))); - } + // Only add the font variant if it's not already in the list. + // Once we start accepting custom typography this comparison + // will have to change. + if (!QuickFonts.Any(q => m.IsCompareMatch(q))) + QuickFonts.Add(m); + }, nameof(QuickCompareViewModel)); } else { - _folder = args.Folder; - if (_folder is not null) - IsFolderMode = true; - - FontCollections = Ioc.Default.GetService(); - SelectedCollection = args.SelectedCollection; - RefreshFontList(SelectedCollection); - - FilterCommand = new RelayCommand(e => OnFilterClick(e)); - CollectionSelectedCommand = new RelayCommand(e => SelectedCollection = e as UserFontCollection); - - + // This is probably the tab bar compare window + QuickFonts = new(args.Folder.Variants.Select(v => CharacterRenderingOptions.CreateDefault(v))); } - - UpdateTextOptions(); - Register(m => UpdateTextOptions()); - - // Set Title - if (IsQuickCompare && args.IsQuickCompare) - Title = Localization.Get("QuickCompareTitle/Text"); - else if (IsQuickCompare && args.Folder.IsFamilyCompare) - Title = string.Format(Localization.Get("CompareFamilyTitle/Text"), QuickFonts.FirstOrDefault()?.Variant.FamilyName); - else if (IsQuickCompare) - Title = Localization.Get("CompareFontFaceTitle/Text"); - else if (IsFolderMode && _folder.Source is not null) - Title = _folder.Source.Name; - else - Title = Localization.Get("CompareFontsTitle/Text"); } - - public void Deactivated() + else { - if (IsQuickCompare) - QuickCompareWindow = null; + _folder = args.Folder; + if (_folder is not null) + IsFolderMode = true; + + FontCollections = Ioc.Default.GetService(); + SelectedCollection = args.SelectedCollection; + RefreshFontList(SelectedCollection); + + FilterCommand = new RelayCommand(e => OnFilterClick(e)); + CollectionSelectedCommand = new RelayCommand(e => SelectedCollection = e as UserFontCollection); - Messenger.UnregisterAll(this); - } - protected override void OnPropertyChangeNotified(string propertyName) - { - if (propertyName is nameof(FontList) or nameof(QuickFonts)) - OnPropertyChanged(nameof(ItemsSource)); } - private void UpdateTextOptions() + UpdateTextOptions(); + Register(m => UpdateTextOptions()); + + // Set Title + if (IsQuickCompare && args.IsQuickCompare) + Title = Localization.Get("QuickCompareTitle/Text"); + else if (IsQuickCompare && args.Folder.IsFamilyCompare) + Title = string.Format(Localization.Get("CompareFamilyTitle/Text"), QuickFonts.FirstOrDefault()?.Variant.FamilyName); + else if (IsQuickCompare) + Title = Localization.Get("CompareFontFaceTitle/Text"); + else if (IsFolderMode && _folder.Source is not null) + Title = _folder.Source.Name; + else + Title = Localization.Get("CompareFontsTitle/Text"); + } + + public void Deactivated() + { + if (IsQuickCompare) + QuickCompareWindow = null; + + Messenger.UnregisterAll(this); + } + + protected override void OnPropertyChangeNotified(string propertyName) + { + if (propertyName is nameof(FontList) or nameof(QuickFonts)) + OnPropertyChanged(nameof(ItemsSource)); + } + + private void UpdateTextOptions() + { + OnSyncContext(() => { TextOptions = GlyphService.GetRampOptions(); }); + } + + private void OnFilterClick(object e) + { + if (e is BasicFontFilter filter) { - OnSyncContext(() => { TextOptions = GlyphService.GetRampOptions(); }); + if (filter == FontListFilter) + RefreshFontList(); + else + FontListFilter = filter; } + } - private void OnFilterClick(object e) + internal void RefreshFontList(UserFontCollection collection = null) + { + try { - if (e is BasicFontFilter filter) + IEnumerable fontList = _folder?.Fonts ?? FontFinder.Fonts; + + if (collection != null) { - if (filter == FontListFilter) - RefreshFontList(); - else - FontListFilter = filter; + FilterTitle = collection.Name; + fontList = fontList.Where(f => collection.Fonts.Contains(f.Name)); } - } - - internal void RefreshFontList(UserFontCollection collection = null) - { - try + else { - IEnumerable fontList = _folder?.Fonts ?? FontFinder.Fonts; + SelectedCollection = null; + FilterTitle = FontListFilter.FilterTitle; - if (collection != null) - { - FilterTitle = collection.Name; - fontList = fontList.Where(f => collection.Fonts.Contains(f.Name)); - } + if (FontListFilter == BasicFontFilter.ImportedFonts) + fontList = FontFinder.ImportedFonts; else - { - SelectedCollection = null; - FilterTitle = FontListFilter.FilterTitle; - - if (FontListFilter == BasicFontFilter.ImportedFonts) - fontList = FontFinder.ImportedFonts; - else - fontList = FontListFilter.Query(fontList, FontCollections); - } - - FontList = new (fontList); + fontList = FontListFilter.Query(fontList, FontCollections); } - catch (Exception e) - { - } + FontList = new(fontList); } - - public void OpenCurrentFont() + catch (Exception e) { - if (SelectedFont is not null) - _ = FontMapView.CreateNewViewForFontAsync(SelectedFont); - } - public void ShowEditSuggestions() - { - Messenger.Send(new EditSuggestionsRequested()); } } + + public void OpenCurrentFont() + { + if (SelectedFont is not null) + _ = FontMapView.CreateNewViewForFontAsync(SelectedFont); + } + + public void ShowEditSuggestions() + { + Messenger.Send(new EditSuggestionsRequested()); + } } diff --git a/CharacterMap/CharacterMap/ViewModels/SettingsViewModel.cs b/CharacterMap/CharacterMap/ViewModels/SettingsViewModel.cs index ac67dd88..0cf97323 100644 --- a/CharacterMap/CharacterMap/ViewModels/SettingsViewModel.cs +++ b/CharacterMap/CharacterMap/ViewModels/SettingsViewModel.cs @@ -1,308 +1,311 @@ -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Services; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.DependencyInjection; -using CommunityToolkit.Mvvm.Messaging; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using Windows.ApplicationModel.Core; +using Windows.ApplicationModel.Core; using Windows.Globalization; using Windows.UI.Xaml; using Windows.UI.Xaml.Media; -namespace CharacterMap.ViewModels +namespace CharacterMap.ViewModels; + +public partial class SettingsViewModel : ViewModelBase { - public partial class SettingsViewModel : ViewModelBase + private Random _random { get; } = new Random(); + + protected override bool TrackAnimation => true; + + public List Annotations { get; } = new() { - private Random _random { get; } = new Random(); + GlyphAnnotation.None, + GlyphAnnotation.UnicodeHex, + GlyphAnnotation.UnicodeIndex + }; - protected override bool TrackAnimation => true; + [ObservableProperty] string _rampInput; + [ObservableProperty] FontFamily _previewFontSource; + [ObservableProperty] List _previewFonts; + [ObservableProperty] bool _isCollectionExportEnabled = true; + [ObservableProperty] bool _isSystemExportEnabled = true; + [ObservableProperty] ObservableCollection _rampOptions = null; - public List Annotations { get; } = new () - { - GlyphAnnotation.None, - GlyphAnnotation.UnicodeHex, - GlyphAnnotation.UnicodeIndex - }; + [ObservableProperty] int _systemFamilyCount = 0; + [ObservableProperty] int _systemFaceCount = 0; + [ObservableProperty] int _importedFamilyCount = 0; + [ObservableProperty] int _importedFaceCount = 0; + [ObservableProperty] string _systemExportProgress; + [ObservableProperty] string _importedExportProgress; - [ObservableProperty] string _rampInput; - [ObservableProperty] FontFamily _previewFontSource; - [ObservableProperty] List _previewFonts; - [ObservableProperty] bool _isCollectionExportEnabled = true; - [ObservableProperty] bool _isSystemExportEnabled = true; - [ObservableProperty] ObservableCollection _rampOptions = null; - [ObservableProperty] int _systemFamilyCount = 0; - [ObservableProperty] int _systemFaceCount = 0; - [ObservableProperty] string _systemExportProgress; - [ObservableProperty] string _importedExportProgress; + public bool ThemeHasChanged => Settings.ApplicationDesignTheme != _originalDesign; + private List _changelog; + public List Changelog => _changelog ??= CreateChangelog(); - public bool ThemeHasChanged => Settings.ApplicationDesignTheme != _originalDesign; + private List _supportedLanguages; + public List SupportedLanguages => _supportedLanguages ??= GetSupportedLanguages(); - private List _changelog; - public List Changelog => _changelog ??= CreateChangelog(); + private int _originalDesign { get; } - private List _supportedLanguages; - public List SupportedLanguages => _supportedLanguages ??= GetSupportedLanguages(); + private AppSettings Settings { get; } = ResourceHelper.AppSettings; - private int _originalDesign { get; } + public SettingsViewModel() + { + _originalDesign = Settings.ApplicationDesignTheme; + } - private AppSettings Settings { get; } = ResourceHelper.AppSettings; + public void UpdatePreviews(InstalledFont font, FontVariant variant) + { + bool isSymbol = Ioc.Default.GetService().IsSymbolFont(font); - public SettingsViewModel() - { - _originalDesign = Settings.ApplicationDesignTheme; - } + // 1. Update "A B Y" Character grid previews + // Note: it is legal for both "variant" and "font" to be NULL + // when calling, so test both cases. + PreviewFontSource = variant != null && !isSymbol + ? new FontFamily(variant.XamlFontSource) + : FontFamily.XamlAutoFontFamily; - public void UpdatePreviews(InstalledFont font, FontVariant variant) - { - bool isSymbol = Ioc.Default.GetService().IsSymbolFont(font); - - // 1. Update "A B Y" Character grid previews - // Note: it is legal for both "variant" and "font" to be NULL - // when calling, so test both cases. - PreviewFontSource = variant != null && !isSymbol - ? new FontFamily(variant.XamlFontSource) - : FontFamily.XamlAutoFontFamily; - - // 2. Update FontList Previews - var items = Enumerable.Range(1, 5).Select(i => FontFinder.Fonts[_random.Next(0, FontFinder.Fonts.Count - 1)]) - .OrderBy(f => f.Name) - .ToList(); - - if (font != null && !isSymbol && !items.Contains(font)) - { - items.RemoveAt(0); - items.Add(font); - } - - PreviewFonts = items.OrderBy(f => f.Name).ToList(); - - // 3. Update Ramp Options - if (RampOptions is not null) - RampOptions.CollectionChanged -= RampOptions_CollectionChanged; - RampOptions = new(Settings.CustomRampOptions); - RampOptions.CollectionChanged += RampOptions_CollectionChanged; - RampInput = null; - - // 4. Update other things - SystemFamilyCount = FontFinder.SystemFamilyCount; - SystemFaceCount = FontFinder.SystemFaceCount; - } + // 2. Update FontList Previews + var items = Enumerable.Range(1, 5).Select(i => FontFinder.Fonts[_random.Next(0, FontFinder.Fonts.Count - 1)]) + .OrderBy(f => f.Name) + .ToList(); - private void RampOptions_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + if (font != null && !isSymbol && !items.Contains(font)) { - Settings.CustomRampOptions = RampOptions; - Messenger.Send(new RampOptionsUpdatedMessage()); + items.RemoveAt(0); + items.Add(font); } - public void ResetFontPreview() - { - // Causes Bindings to re-evaluate so the UI can - // regenerate using the correct fonts - var list = PreviewFonts; - PreviewFonts = null; - PreviewFonts = list?.ToList(); - } + PreviewFonts = items.OrderBy(f => f.Name).ToList(); - private List CreateChangelog() - { - // Could read this from a text file, but that's a waste of performance. - // Naught wrong with this :P - - // Not really including bug fixes in here, just key features. The main idea - // is to try and expose features people may not be aware exist inside the - // application, rather than things like bug-fixes or visual changes. - return new() - { - new("Latest Update (October 2023)", // October 2023 - "- Added option to export installed fonts in Settings->Font Management"), - new("2023.7.0.0 (August 2023)", // August 2023 - "- Added support for Right-to-Left text in Type Ramp view, Quick Compare and Compare Fonts view\n" + - "- Added Unicode developer tools showing Unicode codepoint and UTF-16 value\n" + - "- Added experimental COLRv1 support to Preview Pane, Type Ramp View, Quick Compare and Compare Fonts View for Windows 11 builds > 25905\n" + - " • If a glyph has both COLRv1 and COLRv0 versions, only the COLRv1 version can be seen in these views\n" + - " • SVG Export or copying as SVG will only use COLRv0 glyphs\n" + - " • PNG Export or copying as PNG will favour the COLRv1 glyphs"), - new("2023.6.0.0 (June 2023)", // June 2023 - "- Added ability to group characters by Unicode Range on character map (Ctrl + G)\n" + - "- Added ability to Copy as SVG (Ctrl + Shift + C)\n" + - "- Added ability to Copy as PNG (Ctrl + Alt + C)\n" + - "- Character filtering is now by Unicode Range"), - new("2023.3.0.0 (Apr 2023)", // April 2023 - "- Added ability to open and import WOFF2 fonts (WOFF2 fonts are converted to OTF during import)\n" + - "- Added font PANOSE information to Font Properties flyout"), - new("2023.2.4.0 (Mar 2023)", // March 2023 - "- Add ability to add default preview strings for Type Ramp view and Compare window through the suggestions popup or inside Settings view"), - new("2023.1.2.0 (Jan 2023)", // Jan 2023 - "- Added tabbed interface support to the Windows 11 theme\n" + - "- Added ability to compare all faces in a font family from Font List context menu or the \"...\" menu"), - new("2022.3.0.0 (Dec 2022)", // Dec 2022 - "- Added Calligraphy view to practice drawing characters in the style of the chosen font (Ctrl + I)"), - new("2022.2.0.0 (May 2022)", // May 2022 - "- Added support for opening folders of fonts using the Open button (Ctrl + Shift + O)\n" + - "- Added keyboard shortcut for opening individual font files from main window (Ctrl + O)\n" + - "- Added support for selecting a .ZIP archive when opening a font file and showing all the fonts in the .ZIP\n" + - "- Added C# WinUI 3 & C++/WinRT WinUI 3 developer features"), - new("2022.1.2.0 (March 2022)", - "- Added support for bulk adding and removing fonts from Collections (in Settings)"), - new("2021.7.4.0 (October 2021)", // October - "- Added support for navigating backwards using mouse and keyboard navigation buttons, and Alt + Left\n" + - "- Added support for changing application design with themes for Windows 11, Classic Windows and Zune Desktop"), - new("2021.4.0.0 (July 2021)", // July - "- Added Export Characters view (Ctrl + E)\n" + - "- Quick Compare (Ctrl + Q) now supports comparing typography variations and variable axis on the same font face\n" + - "- Copy pane (Ctrl + B) now supports editing and cursor positioning\n" + - "- Double clicking a character will now add it to the copy pane"), - new("2021.3.0.0 (May 2021)", - "- Added glyph name and search support for Segoe Fluent Icons\n" + - "- Added Visual Basic developer features\n" + - "- Added ability to compare individual Font Face's with Quick Compare view (Ctrl + Q)"), - new("2021.2.0.0 (Feb 2021)", - "- Added ability to open and import WOFF fonts (WOFF fonts are converted to OTF during import)\n" + - "- Added C++/CX, C++/WinRT & Xamarin.Forms developer features\n" + - "- Copying Path Icon from developer code now copies the path with typography applied\n" + - "- Added Adobe Glyph List mapping support for a font's post table names\n" + - "- Added character filtering by Unicode category to main view\n" + - "- Added Fullscreen keyboard shortcut (F11)\n" + - "- Added Compare Fonts view (Ctrl + K)"), - new("2021.1.0.0 (Jan 2021)", - "- Added Font list search\n" + - "- Added ability to see all typographic variations for a single character from the character preview pane\n" + - "- Added support for a Font's own custom glyph names in search and character preview"), - new("2020.18.0.0 (Aug 2020)", - "- Added copy pane (Ctrl + B)\n" + - "- Added 'Toggle Preview Pane' keyboard shortcut (Ctrl + R)\n" + - "- Added 'Toggle Font List' keyboard shortcut (Ctrl + L)\n" + - "- Added 'Increase Font size' keyboard shortcut (Ctrl + +)\n" + - "- Added 'Decrease Font size' keyboard shortcut (Ctrl + -)\n" + - "- Added 'Focus Search' keyboard shortcut (Ctrl + F)\n" + - "- Added a context menu to the character grid allowing you to save or copy any glyph without selecting\n" + - "- Added compact overlay support"), - new("2020.15.0.0 (May 2020)", - "- Added 'Type-Ramp' view with support for Variable Font axis (Ctrl + T)"), - new("2020.12.0.0 (April 2020)", - "- Added advanced Font List filters (by supported script, emoji, characters sets, etc.)\n" + - "- Added ability to export fonts in custom collections to ZIP files or to folders\n" + - "- Added PathIcon developer code\n" + - "- Added ability to export any Font to a Font file (Ctrl + S)\n" + - "- New Fluent UI design"), - new("2020.9.0.0 (March 2020)", - "- Added printing support (Ctrl + P)\n" + - "- Added ability to export colour glyphs (COLR) to SVG files with correct colour layers\n" + - "- New Settings UI"), - new("2020.3.0.0 (February 2020)", - "- Added ability to export raw SVG glyphs from SVG fonts\n" + - "- Added ability to export raw PNG glyphs from fonts with Bitmap PNG glyphs\n" + - "- Added support for user created font collections\n" + - "- App can now detect when a user installs new fonts to the system"), - }; - } + // 3. Update Ramp Options + if (RampOptions is not null) + RampOptions.CollectionChanged -= RampOptions_CollectionChanged; + RampOptions = new(Settings.CustomRampOptions); + RampOptions.CollectionChanged += RampOptions_CollectionChanged; + RampInput = null; - public static List GetSupportedLanguages() - { - List list = new( - ApplicationLanguages.ManifestLanguages - .Select(language => new SupportedLanguage(language))); - - list.Insert(0, SupportedLanguage.SystemLanguage); - return list; - } + // 4. Update other things + SystemFamilyCount = FontFinder.SystemFamilyCount; + SystemFaceCount = FontFinder.SystemFaceCount; + ImportedFamilyCount = FontFinder.ImportedFamilyCount; + ImportedFaceCount = FontFinder.ImportedFaceCount; + } - internal async void ExportSystemAsZip() - { - IsSystemExportEnabled = false; - try { - await ExportManager.ExportFontsAsZipAsync( - FontFinder.GetSystemVariants(), - Localization.Get("OptionSystemFonts/Text"), - p => OnSyncContext(() => SystemExportProgress = p)); - } - finally { IsSystemExportEnabled = true; } - } + private void RampOptions_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + Settings.CustomRampOptions = RampOptions; + Messenger.Send(new RampOptionsUpdatedMessage()); + } - internal async void ExportSystemToFolder() - { - IsSystemExportEnabled = false; - try { - await ExportManager.ExportFontsToFolderAsync( - FontFinder.GetSystemVariants(), - p => OnSyncContext(() => SystemExportProgress = p)); - } - finally { IsSystemExportEnabled = true; } - } + public void ResetFontPreview() + { + // Causes Bindings to re-evaluate so the UI can + // regenerate using the correct fonts + var list = PreviewFonts; + PreviewFonts = null; + PreviewFonts = list?.ToList(); + } - internal async void ExportAsZip() - { - IsCollectionExportEnabled = false; - try { - await ExportManager.ExportFontsAsZipAsync( - FontFinder.GetImportedVariants(), - Localization.Get("OptionImportedFonts/Text"), - p => OnSyncContext(() => ImportedExportProgress = p)); - } - finally { IsCollectionExportEnabled = true; } - } + private List CreateChangelog() + { + // Could read this from a text file, but that's a waste of performance. + // Naught wrong with this :P - internal async void ExportToFolder() + // Not really including bug fixes in here, just key features. The main idea + // is to try and expose features people may not be aware exist inside the + // application, rather than things like bug-fixes or visual changes. + return new() { - IsCollectionExportEnabled = false; - try { - await ExportManager.ExportFontsToFolderAsync( - FontFinder.GetImportedVariants(), - p => OnSyncContext(() => ImportedExportProgress = p)); - } - finally { IsCollectionExportEnabled = true; } - } + new("Latest Update (November 2023)", // August 2023 + "- Integrate the Unicode Unihan readings data set, providing descriptions and pronunciations for CJK Han ideographs\n" + + "- Added support for installing opened .WOFF & .WOFF2 fonts\n" + + "- Added support for showing Design Script Languages in Font Properties flyout\n" + + "- Added 400+ additional font list filters for Design Tag & Unicode Range under Supported Scripts\n" + + "- Added support for hiding deprecated MDL2 & Fluent glyphs by default under Settings->Advanced\n" + + "- Added option to include version number on Font File exports in Settings->Export"), + new("2023.8.0.0 (October 2023)", // October 2023 + "- Added option to export installed fonts in Settings->Font Management"), + new("2023.7.0.0 (August 2023)", // August 2023 + "- Added support for Right-to-Left text in Type Ramp view, Quick Compare and Compare Fonts view\n" + + "- Added Unicode developer tools showing Unicode codepoint and UTF-16 value\n" + + "- Added experimental COLRv1 support to Preview Pane, Type Ramp View, Quick Compare and Compare Fonts View for Windows 11 builds > 25905\n" + + " • If a glyph has both COLRv1 and COLRv0 versions, only the COLRv1 version can be seen in these views\n" + + " • SVG Export or copying as SVG will only use COLRv0 glyphs\n" + + " • PNG Export or copying as PNG will favour the COLRv1 glyphs"), + new("2023.6.0.0 (June 2023)", // June 2023 + "- Added ability to group characters by Unicode Range on character map (Ctrl + G)\n" + + "- Added ability to Copy as SVG (Ctrl + Shift + C)\n" + + "- Added ability to Copy as PNG (Ctrl + Alt + C)\n" + + "- Character filtering is now by Unicode Range"), + new("2023.3.0.0 (Apr 2023)", // April 2023 + "- Added ability to open and import WOFF2 fonts (WOFF2 fonts are converted to OTF during import)\n" + + "- Added font PANOSE information to Font Properties flyout"), + new("2023.2.4.0 (Mar 2023)", // March 2023 + "- Add ability to add default preview strings for Type Ramp view and Compare window through the suggestions popup or inside Settings view"), + new("2023.1.2.0 (Jan 2023)", // Jan 2023 + "- Added tabbed interface support to the Windows 11 theme\n" + + "- Added ability to compare all faces in a font family from Font List context menu or the \"...\" menu"), + new("2022.3.0.0 (Dec 2022)", // Dec 2022 + "- Added Calligraphy view to practice drawing characters in the style of the chosen font (Ctrl + I)"), + new("2022.2.0.0 (May 2022)", // May 2022 + "- Added support for opening folders of fonts using the Open button (Ctrl + Shift + O)\n" + + "- Added keyboard shortcut for opening individual font files from main window (Ctrl + O)\n" + + "- Added support for selecting a .ZIP archive when opening a font file and showing all the fonts in the .ZIP\n" + + "- Added C# WinUI 3 & C++/WinRT WinUI 3 developer features"), + new("2022.1.2.0 (March 2022)", + "- Added support for bulk adding and removing fonts from Collections (in Settings)"), + new("2021.7.4.0 (October 2021)", // October + "- Added support for navigating backwards using mouse and keyboard navigation buttons, and Alt + Left\n" + + "- Added support for changing application design with themes for Windows 11, Classic Windows and Zune Desktop"), + new("2021.4.0.0 (July 2021)", // July + "- Added Export Characters view (Ctrl + E)\n" + + "- Quick Compare (Ctrl + Q) now supports comparing typography variations and variable axis on the same font face\n" + + "- Copy pane (Ctrl + B) now supports editing and cursor positioning\n" + + "- Double clicking a character will now add it to the copy pane"), + new("2021.3.0.0 (May 2021)", + "- Added glyph name and search support for Segoe Fluent Icons\n" + + "- Added Visual Basic developer features\n" + + "- Added ability to compare individual Font Face's with Quick Compare view (Ctrl + Q)"), + new("2021.2.0.0 (Feb 2021)", + "- Added ability to open and import WOFF fonts (WOFF fonts are converted to OTF during import)\n" + + "- Added C++/CX, C++/WinRT & Xamarin.Forms developer features\n" + + "- Copying Path Icon from developer code now copies the path with typography applied\n" + + "- Added Adobe Glyph List mapping support for a font's post table names\n" + + "- Added character filtering by Unicode category to main view\n" + + "- Added Fullscreen keyboard shortcut (F11)\n" + + "- Added Compare Fonts view (Ctrl + K)"), + new("2021.1.0.0 (Jan 2021)", + "- Added Font list search\n" + + "- Added ability to see all typographic variations for a single character from the character preview pane\n" + + "- Added support for a Font's own custom glyph names in search and character preview"), + new("2020.18.0.0 (Aug 2020)", + "- Added copy pane (Ctrl + B)\n" + + "- Added 'Toggle Preview Pane' keyboard shortcut (Ctrl + R)\n" + + "- Added 'Toggle Font List' keyboard shortcut (Ctrl + L)\n" + + "- Added 'Increase Font size' keyboard shortcut (Ctrl + +)\n" + + "- Added 'Decrease Font size' keyboard shortcut (Ctrl + -)\n" + + "- Added 'Focus Search' keyboard shortcut (Ctrl + F)\n" + + "- Added a context menu to the character grid allowing you to save or copy any glyph without selecting\n" + + "- Added compact overlay support"), + new("2020.15.0.0 (May 2020)", + "- Added 'Type-Ramp' view with support for Variable Font axis (Ctrl + T)"), + new("2020.12.0.0 (April 2020)", + "- Added advanced Font List filters (by supported script, emoji, characters sets, etc.)\n" + + "- Added ability to export fonts in custom collections to ZIP files or to folders\n" + + "- Added PathIcon developer code\n" + + "- Added ability to export any Font to a Font file (Ctrl + S)\n" + + "- New Fluent UI design"), + new("2020.9.0.0 (March 2020)", + "- Added printing support (Ctrl + P)\n" + + "- Added ability to export colour glyphs (COLR) to SVG files with correct colour layers\n" + + "- New Settings UI"), + new("2020.3.0.0 (February 2020)", + "- Added ability to export raw SVG glyphs from SVG fonts\n" + + "- Added ability to export raw PNG glyphs from fonts with Bitmap PNG glyphs\n" + + "- Added support for user created font collections\n" + + "- App can now detect when a user installs new fonts to the system"), + }; + } - internal void SetDesign(int selectedIndex) - { - Settings.ApplicationDesignTheme = selectedIndex; - OnPropertyChanged(nameof(ThemeHasChanged)); - } + public static List GetSupportedLanguages() + { + List list = new( + ApplicationLanguages.ManifestLanguages + .Select(language => new SupportedLanguage(language))); - public void LaunchReview() - { - _ = SystemInformation.LaunchStoreForReviewAsync(); - } + list.Insert(0, SupportedLanguage.SystemLanguage); + return list; + } - public void Restart() + internal async void ExportSystemAsZip() + { + IsSystemExportEnabled = false; + try { - _ = CoreApplication.RequestRestartAsync(string.Empty); + await ExportManager.ExportFontsAsZipAsync( + FontFinder.GetSystemVariants(), + Localization.Get("OptionSystemFonts/Text"), + p => OnSyncContext(() => SystemExportProgress = p)); } + finally { IsSystemExportEnabled = true; } + } - public void SetLightTheme() + internal async void ExportSystemToFolder() + { + IsSystemExportEnabled = false; + try { - Settings.UserRequestedTheme = ElementTheme.Light; + await ExportManager.ExportFontsToFolderAsync( + FontFinder.GetSystemVariants(), + p => OnSyncContext(() => SystemExportProgress = p)); } + finally { IsSystemExportEnabled = true; } + } - public void SetDarkTheme() + internal async void ExportAsZip() + { + IsCollectionExportEnabled = false; + try { - Settings.UserRequestedTheme = ElementTheme.Dark; + await ExportManager.ExportFontsAsZipAsync( + FontFinder.GetImportedVariants(), + Localization.Get("OptionImportedFonts/Text"), + p => OnSyncContext(() => ImportedExportProgress = p)); } + finally { IsCollectionExportEnabled = true; } + } - public void SetWindowsTheme() + internal async void ExportToFolder() + { + IsCollectionExportEnabled = false; + try { - Settings.UserRequestedTheme = ElementTheme.Default; + await ExportManager.ExportFontsToFolderAsync( + FontFinder.GetImportedVariants(), + p => OnSyncContext(() => ImportedExportProgress = p)); } + finally { IsCollectionExportEnabled = true; } + } - public void AddRamp() - { - if (!string.IsNullOrWhiteSpace(RampInput)) - RampOptions.Add(RampInput); + internal void SetDesign(int selectedIndex) + { + Settings.ApplicationDesignTheme = selectedIndex; + OnPropertyChanged(nameof(ThemeHasChanged)); + } - RampInput = null; - } + public void LaunchReview() + { + _ = SystemInformation.LaunchStoreForReviewAsync(); + } - public void RemoveRamp(string str) - { - RampOptions.Remove(str); - } + public void Restart() + { + _ = CoreApplication.RequestRestartAsync(string.Empty); + } + + public void SetLightTheme() + { + Settings.UserRequestedTheme = ElementTheme.Light; + } + + public void SetDarkTheme() + { + Settings.UserRequestedTheme = ElementTheme.Dark; + } + + public void SetWindowsTheme() + { + Settings.UserRequestedTheme = ElementTheme.Default; + } + + public void AddRamp() + { + if (!string.IsNullOrWhiteSpace(RampInput)) + RampOptions.Add(RampInput); + + RampInput = null; + } + + public void RemoveRamp(string str) + { + RampOptions.Remove(str); } } diff --git a/CharacterMap/CharacterMap/ViewModels/ViewModelBase.cs b/CharacterMap/CharacterMap/ViewModels/ViewModelBase.cs index b3f6e3ad..18ceb590 100644 --- a/CharacterMap/CharacterMap/ViewModels/ViewModelBase.cs +++ b/CharacterMap/CharacterMap/ViewModels/ViewModelBase.cs @@ -1,256 +1,244 @@ -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Messaging; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using Windows.System; - -namespace CharacterMap.ViewModels +using System.ComponentModel; + +namespace CharacterMap.ViewModels; + +// Based on https://www.pedrolamas.com/2018/04/19/building-a-multi-window-dispatcher-agnostic-view-model/ +public partial class MultiWindowViewModelBase : BaseNotifyingModel, INotifyPropertyChanged { - // Based on https://www.pedrolamas.com/2018/04/19/building-a-multi-window-dispatcher-agnostic-view-model/ - public partial class MultiWindowViewModelBase : BaseNotifyingModel, INotifyPropertyChanged - { - private object _lock { get; } = new(); + private object _lock { get; } = new(); - private Dictionary _handlerCache { get; } = new(); + private Dictionary _handlerCache { get; } = new(); - public event PropertyChangedEventHandler PropertyChanged + public event PropertyChangedEventHandler PropertyChanged + { + add { - add - { - if (value == null) - return; + if (value == null) + return; - var ctx = SynchronizationContext.Current; - lock (_lock) - { - if (_handlerCache.TryGetValue(ctx, out PropertyChangedEventHandler eventHandler)) - { - eventHandler += value; - _handlerCache[ctx] = eventHandler; - } - else - _handlerCache.Add(ctx, value); - } - } - remove + var ctx = SynchronizationContext.Current; + lock (_lock) { - if (value == null) - return; - - var ctx = SynchronizationContext.Current; - lock (_lock) + if (_handlerCache.TryGetValue(ctx, out PropertyChangedEventHandler eventHandler)) { - if (_handlerCache.TryGetValue(ctx, out PropertyChangedEventHandler eventHandler)) - { - eventHandler -= value; - if (eventHandler != null) - _handlerCache[ctx] = eventHandler; - else - _handlerCache.Remove(ctx); - } + eventHandler += value; + _handlerCache[ctx] = eventHandler; } + else + _handlerCache.Add(ctx, value); } } - - protected void OnPropertyChanged([CallerMemberName] string propertyName = null) + remove { - KeyValuePair[] handlers; - lock (_lock) - handlers = _handlerCache.ToArray(); + if (value == null) + return; - PropertyChangedEventArgs eventArgs = new (propertyName); - foreach (var handler in handlers) + var ctx = SynchronizationContext.Current; + lock (_lock) { - void Do() + if (_handlerCache.TryGetValue(ctx, out PropertyChangedEventHandler eventHandler)) { - handler.Value(this, eventArgs); - OnPropertyChangeNotified(propertyName); + eventHandler -= value; + if (eventHandler != null) + _handlerCache[ctx] = eventHandler; + else + _handlerCache.Remove(ctx); } - - if (SynchronizationContext.Current == handler.Key) - Do(); - else - handler.Key.Send(o => Do(), null); } } - - protected override void SendPropertyChanged(string propertyName) - { - OnPropertyChanged(propertyName); - } } - public abstract class BaseNotifyingModel + protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { - SynchronizationContext _originalContext { get; } - - /// - /// If true, ViewModel will notify of changes to animation settings - /// - protected virtual bool TrackAnimation => false; - - protected virtual bool CaptureContext => false; + KeyValuePair[] handlers; + lock (_lock) + handlers = _handlerCache.ToArray(); - public BaseNotifyingModel() + PropertyChangedEventArgs eventArgs = new(propertyName); + foreach (var handler in handlers) { - if (TrackAnimation || CaptureContext) - _originalContext = SynchronizationContext.Current; - - if (TrackAnimation) + void Do() { - Register(m => - { - void Notify(string s) - => OnSyncContext(() => SendPropertyChanged(s)); - - switch (m.PropertyName) - { - case nameof(AppSettings.UseSelectionAnimations): - Notify(nameof(AllowAnimation)); - Notify(nameof(AllowExpensiveAnimation)); - Notify(nameof(AllowFluentAnimation)); - break; - case nameof(AppSettings.AllowExpensiveAnimations): - Notify(nameof(AllowExpensiveAnimation)); - break; - case nameof(AppSettings.UseFluentPointerOverAnimations): - Notify(nameof(AllowFluentAnimation)); - break; - } - }); + handler.Value(this, eventArgs); + OnPropertyChangeNotified(propertyName); } - } - /// - /// Runs code on the original that - /// the ViewModel was created on. Useful for updating UI dependent bindings. - /// - /// - protected void OnSyncContext(Action a) - { - if (_originalContext is null) - a(); - else if (SynchronizationContext.Current == _originalContext) - a(); + if (SynchronizationContext.Current == handler.Key) + Do(); else - _originalContext.Post(_ => a(), null); + handler.Key.Send(o => Do(), null); } + } - /// - /// Private data store that contains all of the properties access through GetProperty - /// method. - /// - readonly Dictionary _data = new(); - - /// - /// Optimised for value types. Gets the value of a property. If the property does not exist, returns the defined default value (and sets that value in the model) - /// - /// - /// Default value to set and return if property is null. Sets & returns as default(T) if no value is provided - /// - /// - protected T GetV(T defaultValue = default, [CallerMemberName] String propertyName = null) - { - if (_data.TryGetValue(propertyName, out object t)) - return (T)t; + protected override void SendPropertyChanged(string propertyName) + { + OnPropertyChanged(propertyName); + } +} - _data[propertyName] = defaultValue; - return defaultValue; - } +public abstract class BaseNotifyingModel +{ + SynchronizationContext _originalContext { get; } + + /// + /// If true, ViewModel will notify of changes to animation settings + /// + protected virtual bool TrackAnimation => false; - /// - /// Optimised for object types - /// - /// - /// - /// - /// - protected T Get(Func defaultValue = null, [CallerMemberName] String propertyName = null) + protected virtual bool CaptureContext => false; + + public BaseNotifyingModel() + { + if (TrackAnimation || CaptureContext) + _originalContext = SynchronizationContext.Current; + + if (TrackAnimation) { - if (_data.TryGetValue(propertyName, out object t)) - return (T)t; + Register(m => + { + void Notify(string s) + => OnSyncContext(() => SendPropertyChanged(s)); - T value = (defaultValue == null) ? default : defaultValue.Invoke(); - _data[propertyName] = value; - return value; + switch (m.PropertyName) + { + case nameof(AppSettings.UseSelectionAnimations): + Notify(nameof(AllowAnimation)); + Notify(nameof(AllowExpensiveAnimation)); + Notify(nameof(AllowFluentAnimation)); + break; + case nameof(AppSettings.AllowExpensiveAnimations): + Notify(nameof(AllowExpensiveAnimation)); + break; + case nameof(AppSettings.UseFluentPointerOverAnimations): + Notify(nameof(AllowFluentAnimation)); + break; + } + }); } + } - /// - /// Attempts to set the value of a property to the internal Key-Value dictionary, - /// and fires off a PropertyChangedEvent only if the value has changed - /// - /// - /// - /// - /// - protected Boolean Set(T value, [CallerMemberName] String propertyName = null, bool notify = true) - { - if (_data == null) - return false; + /// + /// Runs code on the original that + /// the ViewModel was created on. Useful for updating UI dependent bindings. + /// + /// + protected void OnSyncContext(Action a) + { + if (_originalContext is null) + a(); + else if (SynchronizationContext.Current == _originalContext) + a(); + else + _originalContext.Post(_ => a(), null); + } - if (_data.TryGetValue(propertyName, out object t) && object.Equals(t, value)) - return false; + /// + /// Private data store that contains all of the properties access through GetProperty + /// method. + /// + readonly Dictionary _data = new(); + + /// + /// Optimised for value types. Gets the value of a property. If the property does not exist, returns the defined default value (and sets that value in the model) + /// + /// + /// Default value to set and return if property is null. Sets & returns as default(T) if no value is provided + /// + /// + protected T GetV(T defaultValue = default, [CallerMemberName] String propertyName = null) + { + if (_data.TryGetValue(propertyName, out object t)) + return (T)t; - _data[propertyName] = value; + _data[propertyName] = defaultValue; + return defaultValue; + } - if (notify) - SendPropertyChanged(propertyName); - return true; - } + /// + /// Optimised for object types + /// + /// + /// + /// + /// + protected T Get(Func defaultValue = null, [CallerMemberName] String propertyName = null) + { + if (_data.TryGetValue(propertyName, out object t)) + return (T)t; + T value = (defaultValue == null) ? default : defaultValue.Invoke(); + _data[propertyName] = value; + return value; + } - public bool Set(ref T field, T value, [CallerMemberName] string propertyName = null) - { - if (EqualityComparer.Default.Equals(field, value)) - return false; + /// + /// Attempts to set the value of a property to the internal Key-Value dictionary, + /// and fires off a PropertyChangedEvent only if the value has changed + /// + /// + /// + /// + /// + protected Boolean Set(T value, [CallerMemberName] String propertyName = null, bool notify = true) + { + if (_data == null) + return false; - field = value; - SendPropertyChanged(propertyName); - return true; - } + if (_data.TryGetValue(propertyName, out object t) && object.Equals(t, value)) + return false; - protected abstract void SendPropertyChanged(string propertyName); + _data[propertyName] = value; - protected virtual void OnPropertyChangeNotified(string propertyName) { } + if (notify) + SendPropertyChanged(propertyName); + return true; + } - public IMessenger Messenger => WeakReferenceMessenger.Default; - public void Register(Action action, string token = null) where T : class - { - if (!string.IsNullOrWhiteSpace(token)) - Messenger.Register(this, token, (r, m) => { action(m); }); - else - Messenger.Register(this, (r, m) => { action(m); }); - } + public bool Set(ref T field, T value, [CallerMemberName] string propertyName = null) + { + if (EqualityComparer.Default.Equals(field, value)) + return false; - public bool AllowAnimation => ResourceHelper.AllowAnimation; - public bool AllowExpensiveAnimation => ResourceHelper.AllowExpensiveAnimation; - public bool AllowFluentAnimation => ResourceHelper.AllowFluentAnimation; + field = value; + SendPropertyChanged(propertyName); + return true; } - [ObservableObject] - public abstract partial class ViewModelBaseInternal : BaseNotifyingModel + protected abstract void SendPropertyChanged(string propertyName); + + protected virtual void OnPropertyChangeNotified(string propertyName) { } + + public IMessenger Messenger => WeakReferenceMessenger.Default; + + public void Register(Action action, string token = null) where T : class { + if (!string.IsNullOrWhiteSpace(token)) + Messenger.Register(this, token, (r, m) => { action(m); }); + else + Messenger.Register(this, (r, m) => { action(m); }); } - public partial class ViewModelBase : ViewModelBaseInternal + public bool AllowAnimation => ResourceHelper.AllowAnimation; + public bool AllowExpensiveAnimation => ResourceHelper.AllowExpensiveAnimation; + public bool AllowFluentAnimation => ResourceHelper.AllowFluentAnimation; +} + +[ObservableObject] +public abstract partial class ViewModelBaseInternal : BaseNotifyingModel +{ +} + +public partial class ViewModelBase : ViewModelBaseInternal +{ + protected override void OnPropertyChanged(PropertyChangedEventArgs e) { - protected override void OnPropertyChanged(PropertyChangedEventArgs e) - { - base.OnPropertyChanged(e); - OnPropertyChangeNotified(e.PropertyName); - } + base.OnPropertyChanged(e); + OnPropertyChangeNotified(e.PropertyName); + } - protected override void SendPropertyChanged(string propertyName) - { - OnPropertyChanged(propertyName); - } + protected override void SendPropertyChanged(string propertyName) + { + OnPropertyChanged(propertyName); } } diff --git a/CharacterMap/CharacterMap/ViewModels/ViewModelLocator.cs b/CharacterMap/CharacterMap/ViewModels/ViewModelLocator.cs index 88dab81e..c0c16533 100644 --- a/CharacterMap/CharacterMap/ViewModels/ViewModelLocator.cs +++ b/CharacterMap/CharacterMap/ViewModels/ViewModelLocator.cs @@ -1,64 +1,56 @@ -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Services; using CharacterMap.Views; -using CharacterMapCX; using Microsoft.Extensions.DependencyInjection; -using CommunityToolkit.Mvvm.DependencyInjection; -using System; -using System.Threading.Tasks; using Windows.UI.Popups; -namespace CharacterMap.ViewModels +namespace CharacterMap.ViewModels; + +public class DialogService : IDialogService { - public class DialogService : IDialogService + public Task ShowMessageAsync(string message, string title) { - public Task ShowMessageAsync(string message, string title) + try { - try - { - var md = new MessageDialog(message, title); - return md.ShowAsync().AsTask(); - } - catch { } - - return Task.CompletedTask; + var md = new MessageDialog(message, title); + return md.ShowAsync().AsTask(); } + catch { } - public void ShowMessageBox(string message, string title) - { - _ = ShowMessageAsync(message, title); - } + return Task.CompletedTask; } - public class ViewModelLocator + public void ShowMessageBox(string message, string title) { - static ViewModelLocator() - { - NavigationServiceEx _navigationService = new NavigationServiceEx(); + _ = ShowMessageAsync(message, title); + } +} - void Register(IServiceCollection services) where VM : class - { - //services.AddTransient(); - _navigationService.Configure(typeof(VM).FullName, typeof(V)); - } +public class ViewModelLocator +{ + static ViewModelLocator() + { + NavigationServiceEx _navigationService = new(); - ServiceCollection services = new ServiceCollection(); - services.AddSingleton(); - services.AddSingleton(s => _navigationService); - services.AddSingleton(s => ResourceHelper.AppSettings); - services.AddSingleton(s => new NativeInterop(Utils.CanvasDevice)); - services.AddSingleton(); - services.AddSingleton(); - Register(services); - Ioc.Default.ConfigureServices(services.BuildServiceProvider()); + void Register(IServiceCollection services) where VM : class + { + //services.AddTransient(); + _navigationService.Configure(typeof(VM).FullName, typeof(V)); } - public MainViewModel Main => Ioc.Default.GetService(); + ServiceCollection services = new ServiceCollection(); + services.AddSingleton(); + services.AddSingleton(s => _navigationService); + services.AddSingleton(s => ResourceHelper.AppSettings); + services.AddSingleton(s => new NativeInterop(Utils.CanvasDevice)); + services.AddSingleton(); + services.AddSingleton(); + Register(services); + Ioc.Default.ConfigureServices(services.BuildServiceProvider()); + } - public static void Cleanup() - { - // TODO Clear the ViewModels - } + public MainViewModel Main => Ioc.Default.GetService(); + + public static void Cleanup() + { + // TODO Clear the ViewModels } } \ No newline at end of file diff --git a/CharacterMap/CharacterMap/Views/CalligraphyView.xaml.cs b/CharacterMap/CharacterMap/Views/CalligraphyView.xaml.cs index 2354f364..36e0a7f3 100644 --- a/CharacterMap/CharacterMap/Views/CalligraphyView.xaml.cs +++ b/CharacterMap/CharacterMap/Views/CalligraphyView.xaml.cs @@ -1,404 +1,390 @@ -using CharacterMap.Controls; -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Services; -using CharacterMap.ViewModels; -using Microsoft.Toolkit.Uwp.UI.Controls; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; -using Windows.Foundation; +using Microsoft.Toolkit.Uwp.UI.Controls; using Windows.UI; using Windows.UI.Core; using Windows.UI.Input.Inking; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Animation; -namespace CharacterMap.Views -{ - public sealed partial class CalligraphyView : ViewBase, IInAppNotificationPresenter - { - public CalligraphyViewModel ViewModel { get; } +namespace CharacterMap.Views; - private InkStrokeContainer _container => Ink.InkPresenter.StrokeContainer; +public sealed partial class CalligraphyView : ViewBase, IInAppNotificationPresenter +{ + public CalligraphyViewModel ViewModel { get; } - public CalligraphyView(CharacterRenderingOptions options) - { - this.InitializeComponent(); - ViewModel = new CalligraphyViewModel(options); + private InkStrokeContainer _container => Ink.InkPresenter.StrokeContainer; - if (DesignMode) - return; + public CalligraphyView(CharacterRenderingOptions options) + { + this.InitializeComponent(); + ViewModel = new CalligraphyViewModel(options); - ContentRoot.Opacity = 0; - } + if (DesignMode) + return; - protected override void OnLoaded(object sender, RoutedEventArgs e) - { - VisualStateManager.GoToState(this, "NormalState", false); - VisualStateManager.GoToState(this, "OverlayState", false); + ContentRoot.Opacity = 0; + } - TitleBarHelper.SetTitle(Presenter.Title); + protected override void OnLoaded(object sender, RoutedEventArgs e) + { + VisualStateManager.GoToState(this, "NormalState", false); + VisualStateManager.GoToState(this, "OverlayState", false); - Ink.InkPresenter.StrokesCollected -= InkPresenter_StrokesCollected; - Ink.InkPresenter.StrokesCollected += InkPresenter_StrokesCollected; + TitleBarHelper.SetTitle(Presenter.Title); - Ink.InkPresenter.StrokesErased -= InkPresenter_StrokesErased; - Ink.InkPresenter.StrokesErased += InkPresenter_StrokesErased; + Ink.InkPresenter.StrokesCollected -= InkPresenter_StrokesCollected; + Ink.InkPresenter.StrokesCollected += InkPresenter_StrokesCollected; - Register(OnNotificationMessage); + Ink.InkPresenter.StrokesErased -= InkPresenter_StrokesErased; + Ink.InkPresenter.StrokesErased += InkPresenter_StrokesErased; - // Pre-create element visuals to ensure animations run - // properly when requested later - PresentationRoot.GetElementVisual(); - Guide.GetElementVisual(); - CanvasContainer.GetElementVisual(); + Register(OnNotificationMessage); - AnimateIn(); - } + // Pre-create element visuals to ensure animations run + // properly when requested later + PresentationRoot.GetElementVisual(); + Guide.GetElementVisual(); + CanvasContainer.GetElementVisual(); - /// - /// Clear the InkCanvas and reset back to the - /// default calligraphy pen - /// - private void Reset() - { - ViewModel.Clear(); + AnimateIn(); + } - // This needs to be done on the dispatcher or the - // InkButton will not go into the correct VisualState - _ = Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => - { - Toolbar.ActiveTool = calligraphyPen; - }); - } + /// + /// Clear the InkCanvas and reset back to the + /// default calligraphy pen + /// + private void Reset() + { + ViewModel.Clear(); - void FocusCanvas() + // This needs to be done on the dispatcher or the + // InkButton will not go into the correct VisualState + _ = Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => { - // Ensures keyboard shortcuts work (as focus by default falls on the TextBox - // which will steal keyboard shortcut input) - if (InputBox.ContainsFocus() || FontSizeSlider.ContainsFocus()) - { - AddButton.Focus(FocusState.Programmatic); - AddButton.RemoveFocusEngagement(); - } - } + Toolbar.ActiveTool = calligraphyPen; + }); + } - private void InkPresenter_StrokesErased(InkPresenter sender, InkStrokesErasedEventArgs args) + void FocusCanvas() + { + // Ensures keyboard shortcuts work (as focus by default falls on the TextBox + // which will steal keyboard shortcut input) + if (InputBox.ContainsFocus() || FontSizeSlider.ContainsFocus()) { - ViewModel.OnStrokesErased(sender.StrokeContainer, args.Strokes); - FocusCanvas(); + AddButton.Focus(FocusState.Programmatic); + AddButton.RemoveFocusEngagement(); } + } - private void InkPresenter_StrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args) - { - ViewModel.OnStrokeDrawn(_container, args.Strokes); - FocusCanvas(); - } + private void InkPresenter_StrokesErased(InkPresenter sender, InkStrokesErasedEventArgs args) + { + ViewModel.OnStrokesErased(sender.StrokeContainer, args.Strokes); + FocusCanvas(); + } - private void Button_Click(object sender, RoutedEventArgs e) - { - if (ViewStates.CurrentState == OverlayState) - GoToSideBySide(); - else - GoToOverlay(); - } + private void InkPresenter_StrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args) + { + ViewModel.OnStrokeDrawn(_container, args.Strokes); + FocusCanvas(); + } - private async void AddHistoryButton_Click(object sender, RoutedEventArgs e) - { - if (_container.GetStrokes().Count > 0) - { - TryPrepareHistoryAnimation(); + private void Button_Click(object sender, RoutedEventArgs e) + { + if (ViewStates.CurrentState == OverlayState) + GoToSideBySide(); + else + GoToOverlay(); + } - await ViewModel.AddToHistoryAsync(); + private async void AddHistoryButton_Click(object sender, RoutedEventArgs e) + { + if (_container.GetStrokes().Count > 0) + { + TryPrepareHistoryAnimation(); - // Scroll to the end of the list view to ensure the ConnectedAnimation - // can play properly - HistoryList.ScrollIntoView(HistoryList.Items.Last()); + await ViewModel.AddToHistoryAsync(); - ViewModel.Clear(); - } - } + // Scroll to the end of the list view to ensure the ConnectedAnimation + // can play properly + HistoryList.ScrollIntoView(HistoryList.Items.Last()); - private void HistoryList_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) - { - TryAnimateInkIntoHistory(args); + ViewModel.Clear(); } + } - private void HistoryList_ItemClick(object sender, ItemClickEventArgs e) - { - ViewModel.Clear(); + private void HistoryList_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) + { + TryAnimateInkIntoHistory(args); + } - if (e.ClickedItem is CalligraphyHistoryItem h) - { - if (ViewModel.AllowAnimation) - Inker.Opacity = 0; + private void HistoryList_ItemClick(object sender, ItemClickEventArgs e) + { + ViewModel.Clear(); - // Restore History Item - _container.AddStrokes(h.GetStrokes()); - ViewModel.FontSize = h.FontSize; - ViewModel.Text = h.Text; + if (e.ClickedItem is CalligraphyHistoryItem h) + { + if (ViewModel.AllowAnimation) + Inker.Opacity = 0; - // Animate if required - TryAnimateToInkCanvas(e); - } + // Restore History Item + _container.AddStrokes(h.GetStrokes()); + ViewModel.FontSize = h.FontSize; + ViewModel.Text = h.Text; - ViewModel.InkManager.UpdateControls(); + // Animate if required + TryAnimateToInkCanvas(e); } - private void DeleteButton_Click(object sender, RoutedEventArgs e) - { - if (sender is FrameworkElement f && f.DataContext is CalligraphyHistoryItem item) - { - ViewModel.Histories.Remove(item); - } - } + ViewModel.InkManager.UpdateControls(); + } - private void SaveAsSVG(object sender, RoutedEventArgs e) + private void DeleteButton_Click(object sender, RoutedEventArgs e) + { + if (sender is FrameworkElement f && f.DataContext is CalligraphyHistoryItem item) { - _ = SaveAsync(_container.GetStrokes(), ExportFormat.Svg, _container.BoundingRect); + ViewModel.Histories.Remove(item); } + } - private void SaveAsPNG(object sender, RoutedEventArgs e) - { - _ = SaveAsync(_container.GetStrokes(), ExportFormat.Png, _container.BoundingRect); - } + private void SaveAsSVG(object sender, RoutedEventArgs e) + { + _ = SaveAsync(_container.GetStrokes(), ExportFormat.Svg, _container.BoundingRect); + } - private void SaveHistoryAsSVG(object sender, RoutedEventArgs e) - { - if (sender is FrameworkElement f && f.DataContext is CalligraphyHistoryItem item) - _ = SaveAsync(item.GetStrokes(), ExportFormat.Svg, item.Bounds); - } + private void SaveAsPNG(object sender, RoutedEventArgs e) + { + _ = SaveAsync(_container.GetStrokes(), ExportFormat.Png, _container.BoundingRect); + } - private void SaveHistoryAsPNG(object sender, RoutedEventArgs e) - { - if (sender is FrameworkElement f && f.DataContext is CalligraphyHistoryItem item) - _ = SaveAsync(item.GetStrokes(), ExportFormat.Png, item.Bounds); - } + private void SaveHistoryAsSVG(object sender, RoutedEventArgs e) + { + if (sender is FrameworkElement f && f.DataContext is CalligraphyHistoryItem item) + _ = SaveAsync(item.GetStrokes(), ExportFormat.Svg, item.Bounds); + } - private Task SaveAsync(IReadOnlyList strokes, ExportFormat format, Rect bounds) - { - return ViewModel.SaveAsync(strokes, format, ShimCanvas, bounds); - } + private void SaveHistoryAsPNG(object sender, RoutedEventArgs e) + { + if (sender is FrameworkElement f && f.DataContext is CalligraphyHistoryItem item) + _ = SaveAsync(item.GetStrokes(), ExportFormat.Png, item.Bounds); + } + private Task SaveAsync(IReadOnlyList strokes, ExportFormat format, Rect bounds) + { + return ViewModel.SaveAsync(strokes, format, ShimCanvas, bounds); + } - /* Notification Helpers */ - public InAppNotification GetNotifier() - { - if (NotificationRoot == null) - this.FindName(nameof(NotificationRoot)); + /* Notification Helpers */ - return DefaultNotification; - } + public InAppNotification GetNotifier() + { + if (NotificationRoot == null) + this.FindName(nameof(NotificationRoot)); - void OnNotificationMessage(AppNotificationMessage msg) - { - if (!Dispatcher.HasThreadAccess) - return; + return DefaultNotification; + } - InAppNotificationHelper.OnMessage(this, msg); - } + void OnNotificationMessage(AppNotificationMessage msg) + { + if (!Dispatcher.HasThreadAccess) + return; + InAppNotificationHelper.OnMessage(this, msg); + } - /* ANIMATION HELPERS */ - #region Animation + /* ANIMATION HELPERS */ - /// - /// Animates the page in on first load - /// - private void AnimateIn() - { - ContentRoot.Opacity = 1; - if (ResourceHelper.AllowAnimation is false) - return; + #region Animation - int s = 100; - int o = 110; + /// + /// Animates the page in on first load + /// + private void AnimateIn() + { + ContentRoot.Opacity = 1; + if (ResourceHelper.AllowAnimation is false) + return; - // Title - CompositionFactory.PlayEntrance(Presenter.GetTitleElement(), s + 30, o); + int s = 100; + int o = 110; - // First Row - CompositionFactory.PlayEntrance(PickerButton, s + 113, o); - CompositionFactory.PlayEntrance(InputContainer, s + 113, o); - CompositionFactory.PlayEntrance(SliderContainer, s + 113, o); + // Title + CompositionFactory.PlayEntrance(Presenter.GetTitleElement(), s + 30, o); - // Second Row - CompositionFactory.PlayEntrance(ContentGrid, s + 200, o); + // First Row + CompositionFactory.PlayEntrance(PickerButton, s + 113, o); + CompositionFactory.PlayEntrance(InputContainer, s + 113, o); + CompositionFactory.PlayEntrance(SliderContainer, s + 113, o); - // Third Row - CompositionFactory.PlayEntrance(PresentationRoot, s + 300, o); - } + // Second Row + CompositionFactory.PlayEntrance(ContentGrid, s + 200, o); - ConnectedAnimation _addHistoryAnim; + // Third Row + CompositionFactory.PlayEntrance(PresentationRoot, s + 300, o); + } - private void TryPrepareHistoryAnimation() - { - if (ViewModel.AllowAnimation) - _addHistoryAnim = ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("ink", Ink); - } + ConnectedAnimation _addHistoryAnim; - private void TryAnimateInkIntoHistory(ContainerContentChangingEventArgs args) + private void TryPrepareHistoryAnimation() + { + if (ViewModel.AllowAnimation) + _addHistoryAnim = ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("ink", Ink); + } + + private void TryAnimateInkIntoHistory(ContainerContentChangingEventArgs args) + { + if (_addHistoryAnim is not null && args.Item == ViewModel.Histories.Last()) { - if (_addHistoryAnim is not null && args.Item == ViewModel.Histories.Last()) + args.ItemContainer.Opacity = 0; + _ = Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => { - args.ItemContainer.Opacity = 0; - _ = Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => - { - args.ItemContainer.Opacity = 1; - HistoryList.ScrollIntoView(ViewModel.Histories.Last()); - _addHistoryAnim.TryStart(args.ItemContainer); - _addHistoryAnim = null; - }); - } + args.ItemContainer.Opacity = 1; + HistoryList.ScrollIntoView(ViewModel.Histories.Last()); + _addHistoryAnim.TryStart(args.ItemContainer); + _addHistoryAnim = null; + }); } + } - private void TryAnimateToInkCanvas(ItemClickEventArgs e) + private void TryAnimateToInkCanvas(ItemClickEventArgs e) + { + if (ViewModel.AllowAnimation) { - if (ViewModel.AllowAnimation) - { - ConnectedAnimationService.GetForCurrentView().PrepareToAnimate( - "ToInk", HistoryList.ContainerFromItem(e.ClickedItem).GetFirstDescendantOfType()) - .TryStart(Inker); + ConnectedAnimationService.GetForCurrentView().PrepareToAnimate( + "ToInk", HistoryList.ContainerFromItem(e.ClickedItem).GetFirstDescendantOfType()) + .TryStart(Inker); - Inker.Opacity = 1; - } + Inker.Opacity = 1; } + } + + #endregion - #endregion + /* VISUAL STATE HELPERS */ - /* VISUAL STATE HELPERS */ + #region State Helpers - #region State Helpers + private void GoToOverlay() + { + VisualStateManager.GoToState(this, nameof(OverlayState), false); + + var gv = Guide.EnableCompositionTranslation().GetElementVisual(); + var iv = CanvasContainer.EnableCompositionTranslation().GetElementVisual(); - private void GoToOverlay() + if (ViewModel.AllowAnimation) { - VisualStateManager.GoToState(this, nameof(OverlayState), false); + var scale = CompositionFactory.CreateScaleAnimation(gv.Compositor); + gv.WithStandardTranslation().SetImplicitAnimation("Scale", scale); + iv.WithStandardTranslation().SetImplicitAnimation("Scale", scale); + } - var gv = Guide.EnableCompositionTranslation().GetElementVisual(); - var iv = CanvasContainer.EnableCompositionTranslation().GetElementVisual(); + gv.SetTranslation(0, 0, 0); + iv.SetTranslation(0, 0, 0); - if (ViewModel.AllowAnimation) - { - var scale = CompositionFactory.CreateScaleAnimation(gv.Compositor); - gv.WithStandardTranslation().SetImplicitAnimation("Scale", scale); - iv.WithStandardTranslation().SetImplicitAnimation("Scale", scale); - } + gv.Scale = new System.Numerics.Vector3(1f); + iv.Scale = new System.Numerics.Vector3(1f); + } - gv.SetTranslation(0, 0, 0); - iv.SetTranslation(0, 0, 0); + async void GoToSideBySide() + { + // 0. Go to state & disable swap button + // We need to disable because we're doing funky animation things + SwapButton.IsEnabled = false; - gv.Scale = new System.Numerics.Vector3(1f); - iv.Scale = new System.Numerics.Vector3(1f); - } + VisualStateManager.GoToState(this, nameof(SideBySideState), false); - async void GoToSideBySide() + // 1. Prepare visuals + var v = PresentationRoot.GetElementVisual(); + var gv = Guide.EnableCompositionTranslation().GetElementVisual(); + var iv = CanvasContainer.EnableCompositionTranslation().GetElementVisual(); + + // 2. Prepare implicit animations + if (ViewModel.AllowAnimation) { - // 0. Go to state & disable swap button - // We need to disable because we're doing funky animation things - SwapButton.IsEnabled = false; + var scale = CompositionFactory.CreateScaleAnimation(gv.Compositor); + gv.WithStandardTranslation().SetImplicitAnimation("Scale", scale); + iv.WithStandardTranslation().SetImplicitAnimation("Scale", scale); + } - VisualStateManager.GoToState(this, nameof(SideBySideState), false); + CompositionFactory.StartCentering(gv); + CompositionFactory.StartCentering(iv); - // 1. Prepare visuals - var v = PresentationRoot.GetElementVisual(); - var gv = Guide.EnableCompositionTranslation().GetElementVisual(); - var iv = CanvasContainer.EnableCompositionTranslation().GetElementVisual(); + // 3. Set scale & translation. If implicit animations applied, these + // will cause animations to start playing + gv.SetTranslation(v.Size.X / -4f, 0, 0); + iv.SetTranslation(v.Size.X / 4f, 0, 0); - // 2. Prepare implicit animations - if (ViewModel.AllowAnimation) - { - var scale = CompositionFactory.CreateScaleAnimation(gv.Compositor); - gv.WithStandardTranslation().SetImplicitAnimation("Scale", scale); - iv.WithStandardTranslation().SetImplicitAnimation("Scale", scale); - } + gv.Scale = new System.Numerics.Vector3(0.5f, 0.5f, 1f); + iv.Scale = new System.Numerics.Vector3(0.5f, 0.5f, 1f); - CompositionFactory.StartCentering(gv); - CompositionFactory.StartCentering(iv); + if (ViewModel.AllowAnimation) + await Task.Delay((int)(CompositionFactory.DefaultOffsetDuration * 1000) + 32); - // 3. Set scale & translation. If implicit animations applied, these - // will cause animations to start playing - gv.SetTranslation(v.Size.X / -4f, 0, 0); - iv.SetTranslation(v.Size.X / 4f, 0, 0); + // 4. Now enable expression animation for layout. This will stomp over our + // translation implicit animations (which is why we do this after the delay) + gv.StartAnimation( + gv.CreateExpressionAnimation(CompositionFactory.TRANSLATION) + .SetExpression("Vector3(-(v.Size.X / 4f), 0, 0)") + .SetParameter("v", v)); - gv.Scale = new System.Numerics.Vector3(0.5f, 0.5f, 1f); - iv.Scale = new System.Numerics.Vector3(0.5f, 0.5f, 1f); + iv.StartAnimation( + iv.CreateExpressionAnimation(CompositionFactory.TRANSLATION) + .SetExpression("Vector3((v.Size.X / 4f), 0, 0)") + .SetParameter("v", v)); - if (ViewModel.AllowAnimation) - await Task.Delay((int)(CompositionFactory.DefaultOffsetDuration * 1000) + 32); - - // 4. Now enable expression animation for layout. This will stomp over our - // translation implicit animations (which is why we do this after the delay) - gv.StartAnimation( - gv.CreateExpressionAnimation(CompositionFactory.TRANSLATION) - .SetExpression("Vector3(-(v.Size.X / 4f), 0, 0)") - .SetParameter("v", v)); - - iv.StartAnimation( - iv.CreateExpressionAnimation(CompositionFactory.TRANSLATION) - .SetExpression("Vector3((v.Size.X / 4f), 0, 0)") - .SetParameter("v", v)); - - // 5. Re-enable button to swap - SwapButton.IsEnabled = true; - } + // 5. Re-enable button to swap + SwapButton.IsEnabled = true; + } - #endregion + #endregion - - } - public partial class CalligraphyView +} + +public partial class CalligraphyView +{ + public static async Task CreateWindowAsync(CharacterRenderingOptions options, string text = null) { - public static async Task CreateWindowAsync(CharacterRenderingOptions options, string text = null) + static void CreateView(CharacterRenderingOptions v, string t = null) { - static void CreateView(CharacterRenderingOptions v, string t = null) - { - CalligraphyView view = new(v); - view.ViewModel.Text = String.IsNullOrWhiteSpace(t) ? "Hello" : t; - Window.Current.Content = view; - Window.Current.Activate(); - } + CalligraphyView view = new(v); + view.ViewModel.Text = String.IsNullOrWhiteSpace(t) ? "Hello" : t; + Window.Current.Content = view; + Window.Current.Activate(); + } - var view = await WindowService.CreateViewAsync(() => CreateView(options, text), false); - await WindowService.TrySwitchToWindowAsync(view, false); + var view = await WindowService.CreateViewAsync(() => CreateView(options, text), false); + await WindowService.TrySwitchToWindowAsync(view, false); - return view; - } + return view; } +} - public class CalligraphicPen : InkToolbarCustomPen - { - public CalligraphicPen() { } +public class CalligraphicPen : InkToolbarCustomPen +{ + public CalligraphicPen() { } - protected override InkDrawingAttributes CreateInkDrawingAttributesCore(Brush brush, double width) + protected override InkDrawingAttributes CreateInkDrawingAttributesCore(Brush brush, double width) + { + return new InkDrawingAttributes() { - return new InkDrawingAttributes() - { - IgnorePressure = false, - PenTip = PenTipShape.Circle, - Size = new (width, 2.0f * width), - Color = (brush as SolidColorBrush)?.Color ?? Colors.Black, - PenTipTransform = Matrix3x2.CreateRotation((float)(Math.PI * 45d / 180d)) - }; - } + IgnorePressure = false, + PenTip = PenTipShape.Circle, + Size = new(width, 2.0f * width), + Color = (brush as SolidColorBrush)?.Color ?? Colors.Black, + PenTipTransform = Matrix3x2.CreateRotation((float)(Math.PI * 45d / 180d)) + }; } } diff --git a/CharacterMap/CharacterMap/Views/CollectionManagementView.xaml b/CharacterMap/CharacterMap/Views/CollectionManagementView.xaml index aba50ad6..3aeee99e 100644 --- a/CharacterMap/CharacterMap/Views/CollectionManagementView.xaml +++ b/CharacterMap/CharacterMap/Views/CollectionManagementView.xaml @@ -15,7 +15,6 @@ - @@ -28,14 +27,20 @@ Grid.Row="1" ContentPlacement="Bottom"> - + + + + + + SelectionChanged="CollectionSelector_SelectionChanged" + d:PlaceholderText="Select exisiting" /> - + @@ -241,5 +254,7 @@ + + diff --git a/CharacterMap/CharacterMap/Views/CollectionManagementView.xaml.cs b/CharacterMap/CharacterMap/Views/CollectionManagementView.xaml.cs index 533ad4b7..70fe65dc 100644 --- a/CharacterMap/CharacterMap/Views/CollectionManagementView.xaml.cs +++ b/CharacterMap/CharacterMap/Views/CollectionManagementView.xaml.cs @@ -1,110 +1,92 @@ using CharacterMap.Controls; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Services; -using CharacterMap.ViewModels; -using CommunityToolkit.Mvvm.Messaging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Runtime.InteropServices.WindowsRuntime; -using Windows.Foundation; -using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -using Windows.UI.Xaml.Controls.Primitives; -using Windows.UI.Xaml.Data; -using Windows.UI.Xaml.Input; -using Windows.UI.Xaml.Media; -using Windows.UI.Xaml.Navigation; -namespace CharacterMap.Views +namespace CharacterMap.Views; + +public interface IActivateableControl { - public interface IActivateableControl - { - void Activate(); - void Deactivate(); + void Activate(); + void Deactivate(); +} +public sealed partial class CollectionManagementView : UserControl, IActivateableControl +{ + CollectionManagementViewModel ViewModel { get; } + + public CollectionManagementView() + { + this.InitializeComponent(); + ViewModel = new(); } - public sealed partial class CollectionManagementView : UserControl, IActivateableControl + public void Activate() { - CollectionManagementViewModel ViewModel { get; } - - public CollectionManagementView() - { - this.InitializeComponent(); - ViewModel = new(); - } + ViewModel.Activate(); + } - public void Activate() - { - ViewModel.Activate(); - } + public void Deactivate() + { + ViewModel.Deactivate(); + } - public void Deactivate() - { - ViewModel.Deactivate(); - } + void CollectionSelector_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + ViewModel.RefreshFontLists(); + } - private void CollectionSelector_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - ViewModel.RefreshFontLists(); - } + async void NewCollection_Click(object sender, RoutedEventArgs e) + { + CreateCollectionDialog d = new(); + await d.ShowAsync(); - private async void NewCollection_Click(object sender, RoutedEventArgs e) + if (d.Result is AddToCollectionResult result && result.Success) { - CreateCollectionDialog d = new(); - await d.ShowAsync(); - - if (d.Result is AddToCollectionResult result && result.Success) - { - SelectCollection(result.Collection); - } + ViewModel.RefreshCollections(); + SelectCollection(result.Collection); } + } - void SelectCollection(UserFontCollection collection) - { - ViewModel.Activate(); - ViewModel.SelectedCollection = collection; - } + void SelectCollection(UserFontCollection collection) + { + ViewModel.Activate(); + ViewModel.SelectedCollection = collection; + } - private async void RenameFontCollection_Click(object sender, RoutedEventArgs e) - { - await (new CreateCollectionDialog(ViewModel.SelectedCollection)).ShowAsync(); - SelectCollection(ViewModel.SelectedCollection); - } + async void RenameFontCollection_Click(object sender, RoutedEventArgs e) + { + await (new CreateCollectionDialog(ViewModel.SelectedCollection)).ShowAsync(); + SelectCollection(ViewModel.SelectedCollection); + } - private void DeleteCollection_Click(object sender, RoutedEventArgs e) + void DeleteCollection_Click(object sender, RoutedEventArgs e) + { + var d = new ContentDialog { - var d = new ContentDialog - { - Title = Localization.Get("DigDeleteCollection/Title"), - IsPrimaryButtonEnabled = true, - IsSecondaryButtonEnabled = true, - PrimaryButtonText = Localization.Get("DigDeleteCollection/PrimaryButtonText"), - SecondaryButtonText = Localization.Get("DigDeleteCollection/SecondaryButtonText"), - }; + Title = Localization.Get("DigDeleteCollection/Title"), + IsPrimaryButtonEnabled = true, + IsSecondaryButtonEnabled = true, + PrimaryButtonText = Localization.Get("DigDeleteCollection/PrimaryButtonText"), + SecondaryButtonText = Localization.Get("DigDeleteCollection/SecondaryButtonText"), + }; - d.PrimaryButtonClick += DigDeleteCollection_PrimaryButtonClick; - _ = d.ShowAsync(); - } - - private async void DigDeleteCollection_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) - { - string name = ViewModel.SelectedCollection.Name; - await ViewModel.CollectionService.DeleteCollectionAsync(ViewModel.SelectedCollection); - CollectionSelector.SelectedItem = null; - CollectionSelector.SelectedIndex = -1; - //SelectCollection(null); + d.PrimaryButtonClick += DigDeleteCollection_PrimaryButtonClick; + _ = d.ShowAsync(); + } - ViewModel.Messenger.Send(new AppNotificationMessage(true, $"\"{name}\" collection deleted")); - } + async void DigDeleteCollection_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args) + { + string name = ViewModel.SelectedCollection.Name; + await ViewModel.CollectionService.DeleteCollectionAsync(ViewModel.SelectedCollection); + CollectionSelector.SelectedItem = null; + CollectionSelector.SelectedIndex = -1; + //SelectCollection(null); + ViewModel.RefreshCollections(); + ViewModel.Messenger.Send(new AppNotificationMessage(true, $"\"{name}\" collection deleted")); + } - private string GetCountLabel(int fontCount, int selectedCount) - { - return string.Format(Localization.Get("FontsSelectedCountLabel"), fontCount, selectedCount); - } + string GetCountLabel(int fontCount, int selectedCount) + { + return string.Format(Localization.Get("FontsSelectedCountLabel"), fontCount, selectedCount); } } diff --git a/CharacterMap/CharacterMap/Views/ExportView.xaml b/CharacterMap/CharacterMap/Views/ExportView.xaml index 3fb5d9b8..5a856631 100644 --- a/CharacterMap/CharacterMap/Views/ExportView.xaml +++ b/CharacterMap/CharacterMap/Views/ExportView.xaml @@ -579,7 +579,7 @@ ChildrenTransitions="{x:Bind GetRepositionCollection(ViewModel.AllowAnimation), Mode=OneWay}"> - + + + + + + + + + + + + + + + + + + + + + + @@ -1309,7 +1398,7 @@ @@ -1341,7 +1430,7 @@ Width="36" Height="36" MinWidth="36" - Margin="8 -4 0 -4" + Margin="8 -4 12 -4" HorizontalAlignment="Right" VerticalAlignment="Center" helpers:FluentAnimation.PointerOver="FluentContentPresenter" @@ -1402,7 +1491,7 @@ VerticalAlignment="Bottom" Click="AxisReset_Click" FontSize="12" - Style="{StaticResource TextBlockButtonStyle}" + Style="{StaticResource ThemeTextBlockButtonStyle}" Tag="{Binding ElementName=AxisSlider}" /> @@ -1515,6 +1604,9 @@ + + + @@ -1524,6 +1616,8 @@ + + + d:Text="U+0061, Latin Extended Supplemental"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1855,7 +2060,7 @@ x:Name="BorderCopiedMessage" x:Load="False" Grid.Row="0" - Grid.RowSpan="3" + Grid.RowSpan="5" Margin="12 12 12 12" Padding="12" VerticalAlignment="Bottom" @@ -1892,10 +2097,11 @@ + @@ -1943,7 +2149,7 @@ x:Uid="BtnXAMLGeometry" Margin="6 0 0 0" FontSize="13.333" - Style="{StaticResource TextBlockButtonStyle}"> + Style="{StaticResource ThemeTextBlockButtonStyle}"> - + @@ -2310,6 +2516,7 @@ + @@ -2318,6 +2525,7 @@ + diff --git a/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs b/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs index b66b1538..4f7e20ac 100644 --- a/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs +++ b/CharacterMap/CharacterMap/Views/FontMapView.xaml.cs @@ -1,20 +1,6 @@ using CharacterMap.Controls; -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Services; -using CharacterMap.ViewModels; -using CharacterMapCX; -using CommunityToolkit.Mvvm.DependencyInjection; -using CommunityToolkit.Mvvm.Messaging; using Microsoft.Toolkit.Uwp.UI.Controls; -using System; -using System.Collections.Generic; using System.ComponentModel; -using System.Linq; -using System.Numerics; -using System.Threading.Tasks; -using Windows.Storage; using Windows.System; using Windows.UI.Core; using Windows.UI.ViewManagement; @@ -25,1216 +11,1241 @@ using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; -namespace CharacterMap.Views +namespace CharacterMap.Views; + +public sealed partial class FontMapView : ViewBase, IInAppNotificationPresenter, IPopoverPresenter { - public sealed partial class FontMapView : ViewBase, IInAppNotificationPresenter, IPopoverPresenter - { - #region Dependency Properties + #region Dependency Properties - #region TitleLeftContent + #region TitleLeftContent - public object TitleLeftContent - { - get { return (object)GetValue(TitleLeftContentProperty); } - set { SetValue(TitleLeftContentProperty, value); } - } + public object TitleLeftContent + { + get { return (object)GetValue(TitleLeftContentProperty); } + set { SetValue(TitleLeftContentProperty, value); } + } - // Using a DependencyProperty as the backing store for TitleLeftContent. This enables animation, styling, binding, etc... - public static readonly DependencyProperty TitleLeftContentProperty = - DependencyProperty.Register(nameof(TitleLeftContent), typeof(object), typeof(FontMapView), new PropertyMetadata(null)); + // Using a DependencyProperty as the backing store for TitleLeftContent. This enables animation, styling, binding, etc... + public static readonly DependencyProperty TitleLeftContentProperty = + DependencyProperty.Register(nameof(TitleLeftContent), typeof(object), typeof(FontMapView), new PropertyMetadata(null)); - #endregion + #endregion - #region TitleRightContent + #region TitleRightContent - public object TitleRightContent - { - get { return (object)GetValue(TitleRightContentProperty); } - set { SetValue(TitleRightContentProperty, value); } - } + public object TitleRightContent + { + get { return (object)GetValue(TitleRightContentProperty); } + set { SetValue(TitleRightContentProperty, value); } + } + + // Using a DependencyProperty as the backing store for TitleRightContent. This enables animation, styling, binding, etc... + public static readonly DependencyProperty TitleRightContentProperty = + DependencyProperty.Register(nameof(TitleRightContent), typeof(object), typeof(FontMapView), new PropertyMetadata(null)); - // Using a DependencyProperty as the backing store for TitleRightContent. This enables animation, styling, binding, etc... - public static readonly DependencyProperty TitleRightContentProperty = - DependencyProperty.Register(nameof(TitleRightContent), typeof(object), typeof(FontMapView), new PropertyMetadata(null)); + #endregion - #endregion + #region Font - #region Font + public FontItem Font + { + get => (FontItem)GetValue(FontProperty); + set => SetValue(FontProperty, value); + } - public FontItem Font + public static readonly DependencyProperty FontProperty = + DependencyProperty.Register(nameof(Font), typeof(FontItem), typeof(FontMapView), new PropertyMetadata(null, (d, e) => { - get => (FontItem)GetValue(FontProperty); - set => SetValue(FontProperty, value); - } + if (d is FontMapView f && e.NewValue is FontItem item) + f.ViewModel.SelectedFont = item; + })); - public static readonly DependencyProperty FontProperty = - DependencyProperty.Register(nameof(Font), typeof(FontItem), typeof(FontMapView), new PropertyMetadata(null, (d, e) => - { - if (d is FontMapView f && e.NewValue is FontItem item) - f.ViewModel.SelectedFont = item; - })); + #endregion - #endregion + #region ViewModel - #region ViewModel + public FontMapViewModel ViewModel + { + get => (FontMapViewModel)GetValue(ViewModelProperty); + private set => SetValue(ViewModelProperty, value); + } - public FontMapViewModel ViewModel - { - get => (FontMapViewModel)GetValue(ViewModelProperty); - private set => SetValue(ViewModelProperty, value); - } + public static readonly DependencyProperty ViewModelProperty = + DependencyProperty.Register(nameof(ViewModel), typeof(FontMapViewModel), typeof(FontMapView), new PropertyMetadata(null)); - public static readonly DependencyProperty ViewModelProperty = - DependencyProperty.Register(nameof(ViewModel), typeof(FontMapViewModel), typeof(FontMapView), new PropertyMetadata(null)); + #endregion - #endregion + #region Hide Title - #region Hide Title + public bool HideTitle + { + get { return (bool)GetValue(HideTitleProperty); } + set { SetValue(HideTitleProperty, value); } + } - public bool HideTitle - { - get { return (bool)GetValue(HideTitleProperty); } - set { SetValue(HideTitleProperty, value); } - } + public static readonly DependencyProperty HideTitleProperty = + DependencyProperty.Register(nameof(HideTitle), typeof(bool), typeof(FontMapView), new PropertyMetadata(false)); - public static readonly DependencyProperty HideTitleProperty = - DependencyProperty.Register(nameof(HideTitle), typeof(bool), typeof(FontMapView), new PropertyMetadata(false)); + #endregion - #endregion + #endregion - #endregion + private BrushTransition t = new() { Duration = TimeSpan.FromSeconds(0.115) }; - private BrushTransition t = new () { Duration = TimeSpan.FromSeconds(0.115) }; + public bool IsStandalone { get; set; } - public bool IsStandalone { get; set; } + private Debouncer _sizeDebouncer { get; } = new(); - private Debouncer _sizeDebouncer { get; } = new (); + private XamlDirect _xamlDirect { get; } - private XamlDirect _xamlDirect { get; } + private long _previewColumnToken = long.MinValue; - private long _previewColumnToken = long.MinValue; + private bool _isCompactOverlay = false; - private bool _isCompactOverlay = false; + public FontMapView() + { + InitializeComponent(); - public FontMapView() - { - InitializeComponent(); + ViewModel = new FontMapViewModel( + DesignMode ? new DialogService() : Ioc.Default.GetService(), + ResourceHelper.AppSettings); - ViewModel = new FontMapViewModel( - DesignMode ? new DialogService() : Ioc.Default.GetService(), - ResourceHelper.AppSettings); + if (DesignMode) + return; - if (DesignMode) - return; + RequestedTheme = ResourceHelper.GetEffectiveTheme(); + Loading += FontMapView_Loading; - RequestedTheme = ResourceHelper.GetEffectiveTheme(); - Loading += FontMapView_Loading; + ViewModel.PropertyChanged += ViewModel_PropertyChanged; + CharGrid.ItemSize = ViewModel.Settings.GridSize; + CharGrid.SetDesiredContainerUpdateDuration(TimeSpan.FromSeconds(1.5)); + _xamlDirect = XamlDirect.GetDefault(); + } - ViewModel.PropertyChanged += ViewModel_PropertyChanged; - CharGrid.ItemSize = ViewModel.Settings.GridSize; - CharGrid.SetDesiredContainerUpdateDuration(TimeSpan.FromSeconds(1.5)); - _xamlDirect = XamlDirect.GetDefault(); - } + private void FontMapView_Loading(FrameworkElement sender, object args) + { + PaneHideTransition.Storyboard = CreateHidePreview(false, false); + PaneShowTransition.Storyboard = CreateShowPreview(0, false); - private void FontMapView_Loading(FrameworkElement sender, object args) + if (IsStandalone) { - PaneHideTransition.Storyboard = CreateHidePreview(false, false); - PaneShowTransition.Storyboard = CreateShowPreview(0, false); - - if (IsStandalone) - { - VisualStateManager.GoToState(this, nameof(StandaloneViewState), false); + VisualStateManager.GoToState(this, nameof(StandaloneViewState), false); - ApplicationView.GetForCurrentView() - .SetDesiredBoundsMode(ApplicationViewBoundsMode.UseVisible); + ApplicationView.GetForCurrentView() + .SetDesiredBoundsMode(ApplicationViewBoundsMode.UseVisible); - Window.Current.Activate(); - Window.Current.Closed -= Current_Closed; - Window.Current.Closed += Current_Closed; + Window.Current.Activate(); + Window.Current.Closed -= Current_Closed; + Window.Current.Closed += Current_Closed; - LayoutRoot.KeyDown -= LayoutRoot_KeyDown; - LayoutRoot.KeyDown += LayoutRoot_KeyDown; - } - else - { - VisualStateManager.GoToState(this, nameof(ChildViewState), false); - } + LayoutRoot.KeyDown -= LayoutRoot_KeyDown; + LayoutRoot.KeyDown += LayoutRoot_KeyDown; } - - protected override void OnLoaded(object sender, RoutedEventArgs e) + else { - ViewModel.PropertyChanged -= ViewModel_PropertyChanged; - ViewModel.PropertyChanged += ViewModel_PropertyChanged; - - Register(OnNotificationMessage); - Register(OnAppSettingsChanged); - Register(m => - { - if (Dispatcher.HasThreadAccess) - TryPrint(); - }); - Register(m => - { - if (Dispatcher.HasThreadAccess) - TryExport(); - }); - Register(async m => - { - if (Dispatcher.HasThreadAccess) - await ViewModel.RequestCopyToClipboardAsync(m); - }); - - UpdateDevUtils(false); - UpdateDisplayMode(); - UpdateSearchStates(); - UpdateCharacterFit(); - UpdatePaneAndGridSizing(); - UpdateCopyPane(); - UpdateItemsSource(); - - PreviewColumn.Width = new GridLength(ViewModel.Settings.LastColumnWidth); - _previewColumnToken = PreviewColumn.RegisterPropertyChangedCallback(ColumnDefinition.WidthProperty, (d, r) => - { - if (PreviewColumn.Width.Value > 0) - ViewModel.Settings.LastColumnWidth = PreviewColumn.Width.Value; - }); - - //Visual v = PreviewGrid.EnableTranslation(true).GetElementVisual(); - //PreviewGrid.SetHideAnimation(CompositionFactory.CreateSlideOutX(PreviewGrid)); - //PreviewGrid.SetShowAnimation(CompositionFactory.CreateSlideIn(PreviewGrid)); + VisualStateManager.GoToState(this, nameof(ChildViewState), false); } + } - protected override void OnUnloaded(object sender, RoutedEventArgs e) - { - PreviewColumn.UnregisterPropertyChangedCallback(ColumnDefinition.WidthProperty, _previewColumnToken); - ViewModel.PropertyChanged -= ViewModel_PropertyChanged; - LayoutRoot.KeyDown -= LayoutRoot_KeyDown; + protected override void OnLoaded(object sender, RoutedEventArgs e) + { + ViewModel.PropertyChanged -= ViewModel_PropertyChanged; + ViewModel.PropertyChanged += ViewModel_PropertyChanged; + + Register(OnNotificationMessage); + Register(OnAppSettingsChanged); + Register(m => + { + if (Dispatcher.HasThreadAccess) + TryPrint(); + }); + Register(m => + { + if (Dispatcher.HasThreadAccess) + TryExport(); + }); + Register(async m => + { + if (Dispatcher.HasThreadAccess) + await ViewModel.RequestCopyToClipboardAsync(m); + }); + + UpdateDevUtils(false); + UpdateDisplayMode(); + UpdateSearchStates(); + UpdateCharacterFit(); + UpdatePaneAndGridSizing(); + UpdateCopyPane(); + UpdateItemsSource(); + + PreviewColumn.Width = new GridLength(ViewModel.Settings.LastColumnWidth); + _previewColumnToken = PreviewColumn.RegisterPropertyChangedCallback(ColumnDefinition.WidthProperty, (d, r) => + { + if (PreviewColumn.Width.Value > 0) + ViewModel.Settings.LastColumnWidth = PreviewColumn.Width.Value; + }); + + //Visual v = PreviewGrid.EnableTranslation(true).GetElementVisual(); + //PreviewGrid.SetHideAnimation(CompositionFactory.CreateSlideOutX(PreviewGrid)); + //PreviewGrid.SetShowAnimation(CompositionFactory.CreateSlideIn(PreviewGrid)); + } - Messenger.UnregisterAll(this); - ViewModel?.Deactivated(); - } + protected override void OnUnloaded(object sender, RoutedEventArgs e) + { + PreviewColumn.UnregisterPropertyChangedCallback(ColumnDefinition.WidthProperty, _previewColumnToken); + ViewModel.PropertyChanged -= ViewModel_PropertyChanged; + LayoutRoot.KeyDown -= LayoutRoot_KeyDown; - public void Cleanup() - { - this.Bindings.StopTracking(); - } + Messenger.UnregisterAll(this); + ViewModel?.Deactivated(); + } - private void Current_Closed(object sender, CoreWindowEventArgs e) - { - this.Bindings.StopTracking(); - Window.Current.Closed -= Current_Closed; - Window.Current.Content = null; - } + public void Cleanup() + { + this.Bindings.StopTracking(); + } - private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - switch (e.PropertyName) - { - case nameof(ViewModel.SelectedFont): - UpdateStates(); - UpdateDisplayMode(false); - break; - case nameof(ViewModel.SelectedVariant): - _ = SetCharacterSelectionAsync(); - break; - case nameof(ViewModel.SelectedTypography): - UpdateTypography(ViewModel.SelectedTypography); - break; - case nameof(ViewModel.SelectedChar): - if (ResourceHelper.AllowAnimation) + private void Current_Closed(object sender, CoreWindowEventArgs e) + { + this.Bindings.StopTracking(); + Window.Current.Closed -= Current_Closed; + Window.Current.Content = null; + } + + private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(ViewModel.SelectedFont): + UpdateStates(); + UpdateDisplayMode(false); + break; + case nameof(ViewModel.SelectedVariant): + _ = SetCharacterSelectionAsync(); + break; + case nameof(ViewModel.SelectedTypography): + UpdateTypography(ViewModel.SelectedTypography); + break; + case nameof(ViewModel.SelectedChar): + if (ResourceHelper.AllowAnimation) + { + if (ViewModel.SelectedChar is not null) { - if (ViewModel.SelectedChar is not null) + try { - try - { - if (PreviewGrid.Visibility == Visibility.Collapsed || PreviewGridContent.Visibility == Visibility.Collapsed) - return; + if (PreviewGrid.Visibility == Visibility.Collapsed || PreviewGridContent.Visibility == Visibility.Collapsed) + return; - // Empty glyphs will cause the connected animation service to crash, so manually - // check if the rendered glyph contains content - if (CharGrid.ContainerFromItem(ViewModel.SelectedChar) is FrameworkElement container - && container.GetFirstDescendantOfType() is TextBlock t) + // Empty glyphs will cause the connected animation service to crash, so manually + // check if the rendered glyph contains content + if (CharGrid.ContainerFromItem(ViewModel.SelectedChar) is FrameworkElement container + && container.GetFirstDescendantOfType() is TextBlock t) + { + t.Measure(container.DesiredSize); + if (t.DesiredSize.Height != 0 && t.DesiredSize.Width != 0) { - t.Measure(container.DesiredSize); - if (t.DesiredSize.Height != 0 && t.DesiredSize.Width != 0) - { - var ani = CharGrid.PrepareConnectedAnimation("PP", ViewModel.SelectedChar, "Text"); - ani.TryStart(TxtPreview); - CompositionFactory.PlayEntrance(CharacterInfo.Children.ToList(), 0, 0, 40); - } + var ani = CharGrid.PrepareConnectedAnimation("PP", ViewModel.SelectedChar, "Text"); + ani.TryStart(TxtPreview); + CompositionFactory.PlayEntrance(CharacterInfo.Children.ToList(), 0, 0, 40); } } - catch - { - // Nu Hair Don't care - } } - - //CompositionFactory.PlayScaleEntrance(TxtPreview, .85f, 1f); - //CompositionFactory.PlayEntrance(CharacterInfo.Children.ToList(), 0, 0, 40); + catch + { + // Nu Hair Don't care + } } - UpdateTypography(ViewModel.SelectedTypography); + //CompositionFactory.PlayScaleEntrance(TxtPreview, .85f, 1f); + //CompositionFactory.PlayEntrance(CharacterInfo.Children.ToList(), 0, 0, 40); + } + + UpdateTypography(ViewModel.SelectedTypography); + break; + case nameof(ViewModel.Chars): + UpdateItemsSource(); + + if (ResourceHelper.AllowAnimation) + CompositionFactory.PlayEntrance(CharGrid, 166); + break; + case nameof(ViewModel.DisplayMode): + UpdateDisplayMode(true); + break; + case nameof(ViewModel.SelectedProvider): + UpdateDevUtils(); + break; + } + } + + private void OnAppSettingsChanged(AppSettingsChangedMessage msg) + { + RunOnUI(() => + { + switch (msg.PropertyName) + { + case nameof(AppSettings.AllowExpensiveAnimations): + CharGrid.EnableResizeAnimation = ViewModel.Settings.AllowExpensiveAnimations; break; - case nameof(ViewModel.Chars): + case nameof(AppSettings.GroupCharacters): UpdateItemsSource(); - - if (ResourceHelper.AllowAnimation) - CompositionFactory.PlayEntrance(CharGrid, 166); break; - case nameof(ViewModel.DisplayMode): - UpdateDisplayMode(true); + case nameof(AppSettings.GlyphAnnotation): + CharGrid.ItemAnnotation = ViewModel.Settings.GlyphAnnotation; + break; + case nameof(AppSettings.GridSize): + UpdateDisplay(); + break; + case nameof(AppSettings.UseInstantSearch): + UpdateSearchStates(); + break; + case nameof(AppSettings.FitCharacter): + UpdateCharacterFit(); break; - case nameof(ViewModel.SelectedProvider): - UpdateDevUtils(); + case nameof(AppSettings.EnablePreviewPane): + UpdatePaneAndGridSizing(); + break; + case nameof(AppSettings.EnableCopyPane): + UpdateCopyPane(); + break; + case nameof(AppSettings.UserRequestedTheme): + this.RequestedTheme = ResourceHelper.GetEffectiveTheme(); break; } - } - - private void OnAppSettingsChanged(AppSettingsChangedMessage msg) - { - RunOnUI(() => - { - switch (msg.PropertyName) - { - case nameof(AppSettings.AllowExpensiveAnimations): - CharGrid.EnableResizeAnimation = ViewModel.Settings.AllowExpensiveAnimations; - break; - case nameof(AppSettings.GroupCharacters): - UpdateItemsSource(); - break; - case nameof(AppSettings.GlyphAnnotation): - CharGrid.ItemAnnotation = ViewModel.Settings.GlyphAnnotation; - break; - case nameof(AppSettings.GridSize): - UpdateDisplay(); - break; - case nameof(AppSettings.UseInstantSearch): - UpdateSearchStates(); - break; - case nameof(AppSettings.FitCharacter): - UpdateCharacterFit(); - break; - case nameof(AppSettings.EnablePreviewPane): - UpdatePaneAndGridSizing(); - break; - case nameof(AppSettings.EnableCopyPane): - UpdateCopyPane(); - break; - case nameof(AppSettings.UserRequestedTheme): - this.RequestedTheme = ResourceHelper.GetEffectiveTheme(); - break; - } - }); - } + }); + } - public bool HandleInput(KeyRoutedEventArgs e) + public bool HandleInput(KeyRoutedEventArgs e) + { + // If ALT key is held down, ignore + if (e.KeyStatus.IsMenuKeyDown) { - // If ALT key is held down, ignore - if (e.KeyStatus.IsMenuKeyDown) + // Special case for copy as PNG + if (Utils.IsKeyDown(VirtualKey.Control) && e.Key == VirtualKey.C) { - // Special case for copy as PNG - if (Utils.IsKeyDown(VirtualKey.Control) && e.Key == VirtualKey.C) - { - TryCopy(CopyDataType.PNG); - return true; - } - return false; + TryCopy(CopyDataType.PNG); + return true; } + return false; + } - if (Utils.IsKeyDown(VirtualKey.Control)) + if (Utils.IsKeyDown(VirtualKey.Control)) + { + switch (e.Key) { - switch (e.Key) - { - case VirtualKey.C when Utils.IsKeyDown(VirtualKey.Shift): - if (ViewModel.SelectedCharAnalysis.IsFullVectorBased) - TryCopy(CopyDataType.SVG); - break; - case VirtualKey.C: - TryCopy(); - break; - case VirtualKey.G: - ViewModel.Settings.GroupCharacters = !ViewModel.Settings.GroupCharacters; - break; - case VirtualKey.P: - FlyoutHelper.PrintRequested(); - break; - case VirtualKey.S when ViewModel.SelectedVariant is FontVariant v: - ExportManager.RequestExportFontFile(v); - break; - case VirtualKey.E when ViewModel.SelectedVariant is FontVariant: - Messenger.Send(new ExportRequestedMessage()); - break; - case VirtualKey.Add: - case (VirtualKey)187: - ViewModel.IncreaseCharacterSize(); - break; - case VirtualKey.Subtract: - case (VirtualKey)189: - ViewModel.DecreaseCharacterSize(); - break; - case VirtualKey.R: - ViewModel.Settings.EnablePreviewPane = !ViewModel.Settings.EnablePreviewPane; - break; - case VirtualKey.B: - ViewModel.Settings.EnableCopyPane = !ViewModel.Settings.EnableCopyPane; - break; - case VirtualKey.T: - ViewModel.ChangeDisplayMode(); - break; - case VirtualKey.K: - _ = QuickCompareView.CreateWindowAsync(new(false)); - break; - case VirtualKey.Q when ViewModel.SelectedVariant is FontVariant va: - _ = QuickCompareView.AddAsync(ViewModel.RenderingOptions with { Axis = ViewModel.VariationAxis.Copy() }); - break; - case VirtualKey.I: - OpenCalligraphy(); - break; - default: - return false; - } + case VirtualKey.C when Utils.IsKeyDown(VirtualKey.Shift): + if (ViewModel.SelectedCharAnalysis.IsFullVectorBased) + TryCopy(CopyDataType.SVG); + break; + case VirtualKey.C: + TryCopy(); + break; + case VirtualKey.G: + ViewModel.Settings.GroupCharacters = !ViewModel.Settings.GroupCharacters; + break; + case VirtualKey.P: + FlyoutHelper.PrintRequested(); + break; + case VirtualKey.S when ViewModel.SelectedVariant is FontVariant v: + ExportManager.RequestExportFontFile(v); + break; + case VirtualKey.E when ViewModel.SelectedVariant is FontVariant: + Messenger.Send(new ExportRequestedMessage()); + break; + case VirtualKey.Add: + case (VirtualKey)187: + ViewModel.IncreaseCharacterSize(); + break; + case VirtualKey.Subtract: + case (VirtualKey)189: + ViewModel.DecreaseCharacterSize(); + break; + case VirtualKey.R: + ViewModel.Settings.EnablePreviewPane = !ViewModel.Settings.EnablePreviewPane; + break; + case VirtualKey.B: + ViewModel.Settings.EnableCopyPane = !ViewModel.Settings.EnableCopyPane; + break; + case VirtualKey.T: + ViewModel.ChangeDisplayMode(); + break; + case VirtualKey.K: + _ = QuickCompareView.CreateWindowAsync(new(false)); + break; + case VirtualKey.Q when ViewModel.SelectedVariant is FontVariant va: + _ = QuickCompareView.AddAsync(ViewModel.RenderingOptions with { Axis = ViewModel.VariationAxis.Copy() }); + break; + case VirtualKey.I: + OpenCalligraphy(); + break; + default: + return false; } - - return true; } - private void LayoutRoot_KeyDown(object sender, KeyRoutedEventArgs e) - { - if (e.Key == VirtualKey.F11) - Utils.ToggleFullScreenMode(); - else - HandleInput(e); - } + return true; + } - private void UpdateItemsSource() - { - if (ViewModel.Chars == null) - return; + private void LayoutRoot_KeyDown(object sender, KeyRoutedEventArgs e) + { + if (e.Key == VirtualKey.F11) + Utils.ToggleFullScreenMode(); + else + HandleInput(e); + } - var item = CharGrid.SelectedItem; + private void UpdateItemsSource() + { + if (ViewModel.Chars == null) + return; - if (ViewModel.Settings.GroupCharacters) - { - CharGrid.SetBinding(GridView.ItemsSourceProperty, new Binding() - { - Source = CharacterSource - }); - VisualStateManager.GoToState(this, GroupListState.Name, false); - } - else - { - CharGrid.ItemsSource = ViewModel.Chars; - VisualStateManager.GoToState(this, FlatListState.Name, false); - } + var item = CharGrid.SelectedItem; - _ = Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => + if (ViewModel.Settings.GroupCharacters) + { + CharGrid.SetBinding(GridView.ItemsSourceProperty, new Binding() { - if (item is not null) - CharGrid.SelectedItem = item; + Source = CharacterSource }); + VisualStateManager.GoToState(this, GroupListState.Name, false); } - - private void UpdateDevUtils(bool animate = true) + else { - // We can't bind directly to the setting as it exists - // across multiple dispatchers. - RunOnUI(() => - { - if (ViewModel.SelectedProvider != null) - { - if (animate && DevUtilsRoot.Visibility == Visibility.Collapsed && ResourceHelper.AllowAnimation) - CompositionFactory.PlayFullHeightSlideUpEntrance(DevUtilsRoot); - - string state = $"Dev{ViewModel.SelectedProvider.Type}State"; - - if (!GoToState(state, animate)) - GoToState(nameof(DevNoneState), animate); - } - else - GoToState(nameof(DevNoneState), animate); - }); + CharGrid.ItemsSource = ViewModel.Chars; + VisualStateManager.GoToState(this, FlatListState.Name, false); } - private void UpdateDisplayMode(bool animate = false) + _ = Dispatcher.RunAsync(CoreDispatcherPriority.Low, () => { - if (ViewModel.DisplayMode == FontDisplayMode.TypeRamp) - { - if (animate) - UpdateGridToRampTransition(); - GoToState(TypeRampState.Name, animate); - } - else - { - if (animate) - UpdateRampToGridTransition(); - else if (CharGrid.ItemsPanelRoot is null) - CharGrid.Measure(CharGrid.DesiredSize); - - GoToState(CharacterMapState.Name, animate); - } - - if (animate) - { - PlayFontChanged(false); - } - } + if (item is not null) + CharGrid.SelectedItem = item; + }); + } - private void UpdateCharacterFit() + private void UpdateDevUtils(bool animate = true) + { + // We can't bind directly to the setting as it exists + // across multiple dispatchers. + RunOnUI(() => { - if (ViewModel.Settings.FitCharacter) + if (ViewModel.SelectedProvider != null) { - //ZoomOutGlyph.Visibility = Visibility.Visible; - //ZoomGlyph.Visibility = Visibility.Collapsed; + if (animate && DevUtilsRoot.Visibility == Visibility.Collapsed && ResourceHelper.AllowAnimation) + CompositionFactory.PlayFullHeightSlideUpEntrance(DevUtilsRoot); + + string state = $"Dev{ViewModel.SelectedProvider.Type}State"; - TxtPreview.MinHeight = ViewModel.Settings.GridSize; - TxtPreview.MinWidth = ViewModel.Settings.GridSize; + if (!GoToState(state, animate)) + GoToState(nameof(DevNoneState), animate); } else - { - TxtPreview.ClearValue(TextBlock.MinWidthProperty); - TxtPreview.ClearValue(TextBlock.MinHeightProperty); + GoToState(nameof(DevNoneState), animate); + }); + } - //ZoomOutGlyph.Visibility = Visibility.Collapsed; - //ZoomGlyph.Visibility = Visibility.Visible; - } + private void UpdateDisplayMode(bool animate = false) + { + if (ViewModel.DisplayMode == FontDisplayMode.TypeRamp) + { + if (animate) + UpdateGridToRampTransition(); + GoToState(TypeRampState.Name, animate); } - - private void UpdateSearchStates() + else { - if (this.IsStandalone) - { - GoToState( - ViewModel.Settings.UseInstantSearch ? nameof(InstantSearchState) : nameof(ManualSearchState)); - } + if (animate) + UpdateRampToGridTransition(); + else if (CharGrid.ItemsPanelRoot is null) + CharGrid.Measure(CharGrid.DesiredSize); + + GoToState(CharacterMapState.Name, animate); } - private void UpdateStates() + if (animate) { - // Ideally should have been achieved with VisualState setters, buuuuut didn't work for some reason - GoToState(ViewModel.SelectedFont == null ? NoFontState.Name : HasFontState.Name); + PlayFontChanged(false); } + } - private void UpdatePaneAndGridSizing() + private void UpdateCharacterFit() + { + if (ViewModel.Settings.FitCharacter) { - // Update VisualState transition - if (!ViewModel.Settings.EnablePreviewPane) - PaneHideTransition.Storyboard = CreateHidePreview(false, false); - else - PaneShowTransition.Storyboard = CreateShowPreview(0, false); + //ZoomOutGlyph.Visibility = Visibility.Visible; + //ZoomGlyph.Visibility = Visibility.Collapsed; - GoToState( - ViewModel.Settings.EnablePreviewPane && !_isCompactOverlay ? nameof(PreviewPaneEnabledState) : nameof(PreviewPaneDisabledState)); + TxtPreview.MinHeight = ViewModel.Settings.GridSize; + TxtPreview.MinWidth = ViewModel.Settings.GridSize; + } + else + { + TxtPreview.ClearValue(TextBlock.MinWidthProperty); + TxtPreview.ClearValue(TextBlock.MinHeightProperty); - // OverlayButton might not be inflated so can't use VisualState - //OverlayButton?.SetVisible(IsStandalone); + //ZoomOutGlyph.Visibility = Visibility.Collapsed; + //ZoomGlyph.Visibility = Visibility.Visible; } + } - private void UpdateCopyPane() + private void UpdateSearchStates() + { + if (this.IsStandalone) { - string state = ViewModel.Settings.EnableCopyPane && !_isCompactOverlay ? nameof(CopySequenceEnabledState) : nameof(CopySequenceDisabledState); - if (state == nameof(CopySequenceDisabledState)) - CopyPaneHidingTransition.Storyboard = CreateHideCopyPane(); - else - CopyPaneShowingTransition.Storyboard = CreateShowCopyPane(); - - GoToState(state); + GoToState( + ViewModel.Settings.UseInstantSearch ? nameof(InstantSearchState) : nameof(ManualSearchState)); } + } + + private void UpdateStates() + { + // Ideally should have been achieved with VisualState setters, buuuuut didn't work for some reason + GoToState(ViewModel.SelectedFont == null ? NoFontState.Name : HasFontState.Name); + } + + private void UpdatePaneAndGridSizing() + { + // Update VisualState transition + if (!ViewModel.Settings.EnablePreviewPane) + PaneHideTransition.Storyboard = CreateHidePreview(false, false); + else + PaneShowTransition.Storyboard = CreateShowPreview(0, false); + + GoToState( + ViewModel.Settings.EnablePreviewPane && !_isCompactOverlay ? nameof(PreviewPaneEnabledState) : nameof(PreviewPaneDisabledState)); - private async Task UpdateCompactOverlayAsync() + // OverlayButton might not be inflated so can't use VisualState + //OverlayButton?.SetVisible(IsStandalone); + } + + private void UpdateCopyPane() + { + string state = ViewModel.Settings.EnableCopyPane && !_isCompactOverlay ? nameof(CopySequenceEnabledState) : nameof(CopySequenceDisabledState); + if (state == nameof(CopySequenceDisabledState)) + CopyPaneHidingTransition.Storyboard = CreateHideCopyPane(); + else + CopyPaneShowingTransition.Storyboard = CreateShowCopyPane(); + + GoToState(state); + } + + private async Task UpdateCompactOverlayAsync() + { + var view = ApplicationView.GetForCurrentView(); + if (_isCompactOverlay) { - var view = ApplicationView.GetForCurrentView(); - if (_isCompactOverlay) + if (view.ViewMode != ApplicationViewMode.CompactOverlay) { - if (view.ViewMode != ApplicationViewMode.CompactOverlay) + ViewModePreferences pref = ViewModePreferences.CreateDefault(ApplicationViewMode.CompactOverlay); + pref.CustomSize = new Windows.Foundation.Size(420, 300); + pref.ViewSizePreference = ViewSizePreference.Custom; + if (await view.TryEnterViewModeAsync(ApplicationViewMode.CompactOverlay, pref)) { - ViewModePreferences pref = ViewModePreferences.CreateDefault(ApplicationViewMode.CompactOverlay); - pref.CustomSize = new Windows.Foundation.Size(420, 300); - pref.ViewSizePreference = ViewSizePreference.Custom; - if (await view.TryEnterViewModeAsync(ApplicationViewMode.CompactOverlay, pref)) - { - GoToState(nameof(CompactOverlayState)); - SearchBox.PlaceholderText = Localization.Get("SearchBoxShorter"); - _isCompactOverlay = true; - } + GoToState(nameof(CompactOverlayState)); + SearchBox.PlaceholderText = Localization.Get("SearchBoxShorter"); + _isCompactOverlay = true; } } - else + } + else + { + if (await view.TryEnterViewModeAsync(ApplicationViewMode.Default)) { - if (await view.TryEnterViewModeAsync(ApplicationViewMode.Default)) - { - GoToState(nameof(NonCompactState)); - SearchBox.PlaceholderText = Localization.Get("SearchBox/PlaceholderText"); - _isCompactOverlay = false; - } + GoToState(nameof(NonCompactState)); + SearchBox.PlaceholderText = Localization.Get("SearchBox/PlaceholderText"); + _isCompactOverlay = false; } - - UpdatePaneAndGridSizing(); - UpdateCopyPane(); } - private string UpdateStatusBarLabel(FontVariant variant, bool keepCasing) - { - if (variant == null) - return string.Empty; + UpdatePaneAndGridSizing(); + UpdateCopyPane(); + } - string s = Localization.Get("StatusBarCharacterCount", variant.GetCharacters().Count); + private string UpdateStatusBarLabel(FontVariant variant, bool keepCasing) + { + if (variant == null) + return string.Empty; - // Hack for Zune Theme. - if (!keepCasing) - s = s.ToUpper(); + string s = Localization.Get("StatusBarCharacterCount", variant.GetCharacters().Count); - return s; - } + // Hack for Zune Theme. + if (!keepCasing) + s = s.ToUpper(); + return s; + } - /* Public surface-area methods */ - public void OpenCalligraphy() - { - _ = CalligraphyView.CreateWindowAsync( - ViewModel.RenderingOptions, ViewModel.Sequence); - } + /* Public surface-area methods */ - public void SelectCharacter(Character ch) + public void OpenCalligraphy() + { + _ = CalligraphyView.CreateWindowAsync( + ViewModel.RenderingOptions, ViewModel.Sequence); + } + + public void SelectCharacter(Character ch) + { + if (ch is not null) { - if (null != ch) - { - CharGrid.SelectedItem = ch; - CharGrid.ScrollIntoView(ch); - } + CharGrid.SelectedItem = ch; + CharGrid.ScrollIntoView(ch); } + } - public void TryCopy(CopyDataType type = CopyDataType.Text) - { - if (FocusManager.GetFocusedElement() is TextBox or TextBlock) - return; + public void TryCopy(CopyDataType type = CopyDataType.Text) + { + if (FocusManager.GetFocusedElement() is TextBox or TextBlock) + return; - if (type != CopyDataType.Text) - { - ExportStyle style = ExportStyle.Black; - Character c = ViewModel.SelectedChar; - if (ViewModel.GetCharAnalysis(c).HasColorGlyphs - && ViewModel.ShowColorGlyphs) - style = ExportStyle.ColorGlyph; + if (type != CopyDataType.Text) + { + ExportStyle style = ExportStyle.Black; + Character c = ViewModel.SelectedChar; + if (ViewModel.GetCharAnalysis(c).HasColorGlyphs + && ViewModel.ShowColorGlyphs) + style = ExportStyle.ColorGlyph; - _ = ViewModel.RequestCopyToClipboardAsync( - new CopyToClipboardMessage( - DevValueType.Char, - c, - ViewModel.GetCharAnalysis(c), type) - { Style = style }); - } - else - { - TryCopyInternal(); - } + _ = ViewModel.RequestCopyToClipboardAsync( + new CopyToClipboardMessage( + DevValueType.Char, + c, + ViewModel.GetCharAnalysis(c), type) + { Style = style }); } + else + { + TryCopyInternal(); + } + } - private async void TryCopyInternal() + private async void TryCopyInternal() + { + if (CharGrid.SelectedItem is Character character + && await Utils.TryCopyToClipboardAsync(character, ViewModel)) { - if (CharGrid.SelectedItem is Character character - && await Utils.TryCopyToClipboardAsync(character, ViewModel)) - { - BorderFadeInStoryboard.Begin(); - TxtCopiedVariantMessage.SetVisible(PreviewTypographySelector.SelectedItem as TypographyFeatureInfo != TypographyFeatureInfo.None); - } + BorderFadeInStoryboard.Begin(); + TxtCopiedVariantMessage.SetVisible(PreviewTypographySelector.SelectedItem as TypographyFeatureInfo != TypographyFeatureInfo.None); } + } - /* UI Event Handlers */ + /* UI Event Handlers */ - private void ToggleCompactOverlay() - { - _isCompactOverlay = !_isCompactOverlay; - _ = UpdateCompactOverlayAsync(); - } + private void ToggleCompactOverlay() + { + _isCompactOverlay = !_isCompactOverlay; + _ = UpdateCompactOverlayAsync(); + } - private void BtnFit_Click(object sender, RoutedEventArgs e) - { - ViewModel.Settings.FitCharacter = !ViewModel.Settings.FitCharacter; - } + private void BtnFit_Click(object sender, RoutedEventArgs e) + { + ViewModel.Settings.FitCharacter = !ViewModel.Settings.FitCharacter; + } - private void OnCopyGotFocus(object sender, RoutedEventArgs e) - { - ((TextBox)sender).SelectAll(); - } + private void OnCopyGotFocus(object sender, RoutedEventArgs e) + { + ((TextBox)sender).SelectAll(); + } - private void BtnCopyCode_OnClick(object sender, RoutedEventArgs e) + private void BtnCopyCode_OnClick(object sender, RoutedEventArgs e) + { + if (sender is FrameworkElement f + && f.DataContext is DevOption o + && f.Tag is string s) { - if (sender is FrameworkElement f - && f.DataContext is DevOption o - && f.Tag is string s) - { - Utils.CopyToClipBoard(s.Trim()); - BorderFadeInStoryboard.Begin(); - if (!o.SupportsTypography) - TxtCopiedVariantMessage.SetVisible(PreviewTypographySelector.SelectedItem as TypographyFeatureInfo != TypographyFeatureInfo.None); - else - TxtCopiedVariantMessage.SetVisible(false); - } + Utils.CopyToClipBoard(s.Trim()); + BorderFadeInStoryboard.Begin(); + if (!o.SupportsTypography) + TxtCopiedVariantMessage.SetVisible(PreviewTypographySelector.SelectedItem as TypographyFeatureInfo != TypographyFeatureInfo.None); + else + TxtCopiedVariantMessage.SetVisible(false); } + } - private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e) - { - // Make sure the PreviewColumn fits properly. + private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e) + { + // Make sure the PreviewColumn fits properly. + + if (e.NewSize.Width > e.PreviousSize.Width) + return; - if (e.NewSize.Width > e.PreviousSize.Width) + _sizeDebouncer.Debounce(250, () => + { + if (!this.IsLoaded) return; - _sizeDebouncer.Debounce(250, () => + if (CharGrid.Visibility == Visibility.Visible) { - if (!this.IsLoaded) - return; - - if (CharGrid.Visibility == Visibility.Visible) + int size = (int)CharGrid.ActualWidth + (int)Splitter.ActualWidth + (int)PreviewGrid.ActualWidth; + if (this.ActualWidth < size && this.ActualWidth < 700) { - int size = (int)CharGrid.ActualWidth + (int)Splitter.ActualWidth + (int)PreviewGrid.ActualWidth; - if (this.ActualWidth < size && this.ActualWidth < 700) - { - PreviewColumn.Width = new GridLength(Math.Max(0, (int)(this.ActualWidth - CharGrid.ActualWidth - Splitter.ActualWidth))); - } + PreviewColumn.Width = new GridLength(Math.Max(0, (int)(this.ActualWidth - CharGrid.ActualWidth - Splitter.ActualWidth))); } - }); - } + } + }); + } + + private void OnSearchBoxGotFocus(AutoSuggestBox searchBox) + { + if (ViewModel.SearchResults is not null) + searchBox.IsSuggestionListOpen = true; + else + ViewModel.DebounceSearch(ViewModel.SearchQuery, ViewModel.Settings.InstantSearchDelay); + } - private void OnSearchBoxGotFocus(AutoSuggestBox searchBox) + internal void OnSearchBoxSubmittedQuery(AutoSuggestBox searchBox) + { + // commented below line because it will keep search result list open even when user selected an item in search result + // searchBox.IsSuggestionListOpen = true; + if (!string.IsNullOrWhiteSpace(ViewModel.SearchQuery)) { - if (ViewModel.SearchResults != null && ViewModel.SearchResults.Count > 0) - searchBox.IsSuggestionListOpen = true; - else - ViewModel.DebounceSearch(ViewModel.SearchQuery, ViewModel.Settings.InstantSearchDelay); + ViewModel.DebounceSearch(ViewModel.SearchQuery, ViewModel.Settings.InstantSearchDelay, SearchSource.ManualSubmit); } - - internal void OnSearchBoxSubmittedQuery(AutoSuggestBox searchBox) + else { - // commented below line because it will keep search result list open even when user selected an item in search result - // searchBox.IsSuggestionListOpen = true; - if (!string.IsNullOrWhiteSpace(ViewModel.SearchQuery)) - { - ViewModel.DebounceSearch(ViewModel.SearchQuery, ViewModel.Settings.InstantSearchDelay, SearchSource.ManualSubmit); - } - else - { - ViewModel.SearchResults = null; - } + ViewModel.SearchResults = null; } + } - internal void SearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + internal void SearchBox_TextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + { + if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput + && args.CheckCurrent() + && string.IsNullOrEmpty(sender.Text) + && !ViewModel.Settings.UseInstantSearch) { - if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput - && args.CheckCurrent() - && string.IsNullOrEmpty(sender.Text) - && !ViewModel.Settings.UseInstantSearch) - { - ViewModel.DebounceSearch(sender.Text, 0, SearchSource.ManualSubmit); - } + ViewModel.DebounceSearch(sender.Text, 0, SearchSource.ManualSubmit); } + } - internal void SearchBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) + internal void SearchBox_SuggestionChosen(AutoSuggestBox sender, AutoSuggestBoxSuggestionChosenEventArgs args) + { + if (args.SelectedItem is IGlyphData data) { - if (args.SelectedItem is IGlyphData data - && ViewModel.Chars.FirstOrDefault(c => c.UnicodeIndex == data.UnicodeIndex) is Character c) - { + if (ViewModel.Chars.FirstOrDefault(c => c.UnicodeIndex == data.UnicodeIndex) is Character c) SelectCharacter(c); - } + else + ViewModel.Messenger.Send(new AppNotificationMessage(true, + Localization.Get("NotificationSearchSelectedCharHidden"))); } + } - internal void SearchBox_GotFocus(object sender, RoutedEventArgs e) - { - OnSearchBoxGotFocus(sender as AutoSuggestBox); - } + internal void SearchBox_GotFocus(object sender, RoutedEventArgs e) + { + OnSearchBoxGotFocus(sender as AutoSuggestBox); + } - public void SearchBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e) - { - if (ViewModel.Settings.UseInstantSearch && e.Key == VirtualKey.Enter) - e.Handled = true; - } + public void SearchBox_PreviewKeyDown(object sender, KeyRoutedEventArgs e) + { + if (ViewModel.Settings.UseInstantSearch && e.Key == VirtualKey.Enter) + e.Handled = true; + } - internal void SearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) - { - OnSearchBoxSubmittedQuery(sender); - } + internal void SearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) + { + OnSearchBoxSubmittedQuery(sender); + } - private void SearchBox_ShortcutInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) - { - if (SearchBox.FocusState == FocusState.Unfocused) - SearchBox.Focus(FocusState.Keyboard); - } + private void SearchBox_ShortcutInvoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + { + if (SearchBox.FocusState == FocusState.Unfocused) + SearchBox.Focus(FocusState.Keyboard); + } - private void InfoFlyout_Opened(object sender, object e) + private void InfoFlyout_Opened(object sender, object e) + { + if (sender is Flyout f) { - if (sender is Flyout f) - { #pragma warning disable CS0618 // Type or member is obsolete - if (f.Content.GetFirstAncestorOfType() is ScrollViewer sv) - sv.ScrollToVerticalOffset(0); + if (f.Content.GetFirstAncestorOfType() is ScrollViewer sv) + sv.ScrollToVerticalOffset(0); #pragma warning restore CS0618 - } } + } - private void MenuFlyout_Opening(object sender, object e) - { - if (ViewModel.SelectedFont is FontItem item) - { - FlyoutHelper.CreateMenu( - MoreMenu, - item.Font, - ViewModel.RenderingOptions with { Axis = ViewModel.VariationAxis.Copy() }, - this.Tag as FrameworkElement, - new() - { - Standalone = IsStandalone, - ShowAdvanced = true, - IsExternalFile = ViewModel.IsExternalFile, - Folder = ViewModel.Folder, - PreviewText = ViewModel.Sequence - }); - } + private void MenuFlyout_Opening(object sender, object e) + { + if (ViewModel.SelectedFont is FontItem item) + { + FlyoutHelper.CreateMenu( + MoreMenu, + item.Font, + ViewModel.RenderingOptions with { Axis = ViewModel.VariationAxis.Copy() }, + this.Tag as FrameworkElement, + new() + { + Standalone = IsStandalone, + ShowAdvanced = true, + IsExternalFile = ViewModel.IsExternalFile, + Folder = ViewModel.Folder, + PreviewText = ViewModel.Sequence + }); } + } - private void DevFlyout_Opening(object sender, object e) + private void DevFlyout_Opening(object sender, object e) + { + if (sender is MenuFlyout menu && menu.Items.Count < 2) { - if (sender is MenuFlyout menu && menu.Items.Count < 2) + Style style = ResourceHelper.Get - + @@ -883,11 +883,15 @@ - - + + + + + + + - diff --git a/CharacterMap/CharacterMap/Views/QuickCompareView.xaml.cs b/CharacterMap/CharacterMap/Views/QuickCompareView.xaml.cs index 9353ef30..f32878c8 100644 --- a/CharacterMap/CharacterMap/Views/QuickCompareView.xaml.cs +++ b/CharacterMap/CharacterMap/Views/QuickCompareView.xaml.cs @@ -1,17 +1,6 @@ -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.Services; -using CharacterMap.ViewModels; -using CharacterMapCX.Controls; -using CommunityToolkit.Mvvm.Messaging; +using CharacterMapCX.Controls; using Microsoft.Toolkit.Uwp.UI.Controls; using Microsoft.UI.Xaml.Controls; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Windows.Foundation; using Windows.UI.ViewManagement; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; @@ -21,511 +10,504 @@ using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Media.Animation; -namespace CharacterMap.Views +namespace CharacterMap.Views; + +public sealed partial class QuickCompareView : ViewBase, IInAppNotificationPresenter { - public sealed partial class QuickCompareView : ViewBase, IInAppNotificationPresenter + public QuickCompareViewModel ViewModel { get; } + + public QuickCompareView() : this(new(false)) { } + + private NavigationHelper _navHelper { get; } = new(); + + public QuickCompareView(QuickCompareArgs args) { - public QuickCompareViewModel ViewModel { get; } + this.InitializeComponent(); - public QuickCompareView() : this(new(false)) { } + ViewModel = new QuickCompareViewModel(args); + ViewModel.PropertyChanged += ViewModel_PropertyChanged; + this.DataContext = this; - private NavigationHelper _navHelper { get; } = new NavigationHelper(); + VisualStateManager.GoToState(this, GridLayoutState.Name, false); - public QuickCompareView(QuickCompareArgs args) - { - this.InitializeComponent(); + if (ViewModel.IsQuickCompare) + VisualStateManager.GoToState(this, QuickCompareState.Name, false); - ViewModel = new QuickCompareViewModel(args); - ViewModel.PropertyChanged += ViewModel_PropertyChanged; - this.DataContext = this; + if (ViewModel.IsFolderMode) + VisualStateManager.GoToState(this, FontFolderState.Name, false); - if (ViewModel.IsQuickCompare) - VisualStateManager.GoToState(this, QuickCompareState.Name, false); + _navHelper.BackRequested += (s, e) => { ViewModel.SelectedFont = null; }; - if (ViewModel.IsFolderMode) - VisualStateManager.GoToState(this, FontFolderState.Name, false); + if (DesignMode) + return; - _navHelper.BackRequested += (s, e) => { ViewModel.SelectedFont = null; }; + this.Opacity = 0; - if (DesignMode) - return; + ApplicationView.GetForCurrentView().SetDesiredBoundsMode(ApplicationViewBoundsMode.UseVisible); + } - this.Opacity = 0; + protected override void OnLoaded(object sender, RoutedEventArgs e) + { + VisualStateManager.GoToState(this, NormalState.Name, false); + TitleBarHelper.SetTitle(Presenter.Title); + _navHelper.Activate(); - ApplicationView.GetForCurrentView().SetDesiredBoundsMode(ApplicationViewBoundsMode.UseVisible); - } + Register(OnNotificationMessage); + Register(HandleMessage); + Register(HandleMessage); - protected override void OnLoaded(object sender, RoutedEventArgs e) - { - VisualStateManager.GoToState(this, NormalState.Name, false); - TitleBarHelper.SetTitle(Presenter.Title); - _navHelper.Activate(); + AnimateIn(); + } - Register(OnNotificationMessage); - Register(HandleMessage); - Register(HandleMessage); + protected override void OnUnloaded(object sender, RoutedEventArgs e) + { + ViewModel?.Deactivated(); + _navHelper.Deactivate(); - AnimateIn(); - } + Messenger.UnregisterAll(this); + } - protected override void OnUnloaded(object sender, RoutedEventArgs e) - { - ViewModel?.Deactivated(); - _navHelper.Deactivate(); + private void AnimateIn() + { + this.Opacity = 1; + if (ResourceHelper.AllowAnimation is false) + return; - Messenger.UnregisterAll(this); - } + int s = 66; + int o = 110; - private void AnimateIn() - { - this.Opacity = 1; - if (ResourceHelper.AllowAnimation is false) - return; + // Title + CompositionFactory.PlayEntrance(Presenter.GetTitleElement(), s + 30, o); - int s = 66; - int o = 110; + // First Row + CompositionFactory.PlayEntrance(TopRow, s + 113, o); - // Title - CompositionFactory.PlayEntrance(Presenter.GetTitleElement(), s + 30, o); + // Second Row + CompositionFactory.PlayEntrance(SecondRow, s + 200, o); - // First Row - CompositionFactory.PlayEntrance(TopRow, s + 113, o); + // Third Row + CompositionFactory.PlayEntrance(FontsRoot, s + 300, o); + } - // Second Row - CompositionFactory.PlayEntrance(SecondRow, s + 200, o); + private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ViewModel.FontList)) + { + if (ViewStates.CurrentState != NormalState) + GoToNormalState(); - // Third Row - CompositionFactory.PlayEntrance(FontsRoot, s + 300, o); - } + if (ResourceHelper.AllowAnimation) + CompositionFactory.PlayEntrance(Repeater, 0, 80, 0); - private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + // ItemsRepeater is a bit rubbish, needs to be nudged back into life. + // If we scroll straight to zero, we can often end up with a blank screen + // until the user scrolls. So we need to manually hack in a scroll ourselves. + //ListingScroller.ChangeView(null, 2, null, true); + //_ = Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => + //{ + // await Task.Delay(16); + // ListingScroller?.ChangeView(null, 0, null, false); + //}); + } + else if (e.PropertyName == nameof(ViewModel.SelectedFont)) { - if (e.PropertyName == nameof(ViewModel.FontList)) - { - if (ViewStates.CurrentState != NormalState) - GoToNormalState(); - - if (ResourceHelper.AllowAnimation) - CompositionFactory.PlayEntrance(Repeater, 0, 80, 0); - - // ItemsRepeater is a bit rubbish, needs to be nudged back into life. - // If we scroll straight to zero, we can often end up with a blank screen - // until the user scrolls. So we need to manually hack in a scroll ourselves. - //ListingScroller.ChangeView(null, 2, null, true); - //_ = Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () => - //{ - // await Task.Delay(16); - // ListingScroller?.ChangeView(null, 0, null, false); - //}); - } - else if (e.PropertyName == nameof(ViewModel.SelectedFont)) + if (ViewModel.SelectedFont is null) { - if (ViewModel.SelectedFont is null) - { - GoToNormalState(); - } - else - { - DetailsFontTitle.Text = ""; - GoToState(DetailsState.Name); - } + GoToNormalState(); } - else if (e.PropertyName == nameof(ViewModel.Text)) + else { - UpdateText(ViewModel.Text); + DetailsFontTitle.Text = ""; + GoToState(DetailsState.Name); } } - - private void GoToNormalState() + else if (e.PropertyName == nameof(ViewModel.Text)) { - // Repeater metrics may be out of date. Update. - UpdateText(ViewModel.Text, Repeater.Realize().ItemsPanelRoot); - UpdateFontSize(FontSizeSlider.Value, Repeater.Realize().ItemsPanelRoot); - GoToState(NormalState.Name); + UpdateText(ViewModel.Text); } + } - private void Button_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) - { - if (sender is Button b && e.GetCurrentPoint(b).Properties.IsMiddleButtonPressed - && b.Content is InstalledFont font) - { - _ = FontMapView.CreateNewViewForFontAsync(font); - } - } + private void GoToNormalState() + { + // Repeater metrics may be out of date. Update. + UpdateText(ViewModel.Text, Repeater.Realize().ItemsPanelRoot); + UpdateFontSize(FontSizeSlider.Value, Repeater.Realize().ItemsPanelRoot); + GoToState(NormalState.Name); + } - private void InputText_DetectedFlowDirectionChanged(object sender, FlowDirection e) + private void Button_PointerPressed(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e) + { + if (sender is Button b && e.GetCurrentPoint(b).Properties.IsMiddleButtonPressed + && b.Content is InstalledFont font) { - UpdateText(ViewModel.Text, flowOnly: true); + _ = FontMapView.CreateNewViewForFontAsync(font); } + } - bool IsDetailsView => ViewStates.CurrentState == DetailsState; + private void InputText_DetectedFlowDirectionChanged(object sender, FlowDirection e) + { + UpdateText(ViewModel.Text, flowOnly: true); + } - /* - * ElementName Bindings don't work inside ItemsRepeater, so to change - * preview Text & FontSize we need to manually update all TextBlocks - */ + bool IsDetailsView => ViewStates.CurrentState == DetailsState; - void UpdateText(string text, FrameworkElement root = null, bool flowOnly = false) - { - FrameworkElement target = root ?? (IsDetailsView ? DetailsRepeater : Repeater.Realize().ItemsPanelRoot); - if (target == null) - return; + /* + * ElementName Bindings don't work inside ItemsRepeater, so to change + * preview Text & FontSize we need to manually update all TextBlocks + */ - XamlBindingHelper.SuspendRendering(target); - var flow = InputText.DetectedFlowDirection; - foreach (var g in GetTargets(target)) - { - if (flowOnly is false) - SetText(g, text); - SetFlow(g, flow); - } - XamlBindingHelper.ResumeRendering(target); - } + void UpdateText(string text, FrameworkElement root = null, bool flowOnly = false) + { + FrameworkElement target = root ?? (IsDetailsView ? DetailsRepeater : Repeater.Realize().ItemsPanelRoot); + if (target == null) + return; - void UpdateFontSize(double size, FrameworkElement root = null) + XamlBindingHelper.SuspendRendering(target); + var flow = InputText.DetectedFlowDirection; + foreach (var g in GetTargets(target)) { - FrameworkElement target = root ?? (IsDetailsView ? DetailsRepeater : Repeater.Realize().ItemsPanelRoot); - if (target == null) - return; - - XamlBindingHelper.SuspendRendering(target); - foreach (var g in GetTargets(target)) - SetFontSize(g, size); - XamlBindingHelper.ResumeRendering(target); + if (flowOnly is false) + SetText(g, text); + SetFlow(g, flow); } + XamlBindingHelper.ResumeRendering(target); + } - IEnumerable GetTargets(FrameworkElement target) - { - if (target.DesiredSize.Height == 0 && target.DesiredSize.Width == 0) - target.Measure(new Windows.Foundation.Size(50, 50)); + void UpdateFontSize(double size, FrameworkElement root = null) + { + FrameworkElement target = root ?? (IsDetailsView ? DetailsRepeater : Repeater.Realize().ItemsPanelRoot); + if (target == null) + return; + + XamlBindingHelper.SuspendRendering(target); + foreach (var g in GetTargets(target)) + SetFontSize(g, size); + XamlBindingHelper.ResumeRendering(target); + } - return target.GetFirstLevelDescendants(d => (d is TextBlock or DirectText) && d.Name.EndsWith("Render")); - } + IEnumerable GetTargets(FrameworkElement target) + { + if (target.DesiredSize.Height == 0 && target.DesiredSize.Width == 0) + target.Measure(new Windows.Foundation.Size(50, 50)); - private void FontSizeSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e) - { - if (Repeater == null) - return; + return target.GetFirstLevelDescendants(d => (d is TextBlock or DirectText) && d.Name.EndsWith("Render")); + } - double v = e.NewValue; - UpdateFontSize(v); - } + private void FontSizeSlider_ValueChanged(object sender, RangeBaseValueChangedEventArgs e) + { + if (Repeater == null) + return; - void SetText(object t, string text) - { - if (t is TextBlock tb) - tb.Text = text ?? string.Empty; - else if (t is DirectText d) - d.Text = text; - } + double v = e.NewValue; + UpdateFontSize(v); + } - void SetFlow(object t, FlowDirection dir) - { - if (t is FrameworkElement f) - f.FlowDirection = dir; - } + void SetText(object t, string text) + { + if (t is TextBlock tb) + tb.Text = text ?? string.Empty; + else if (t is DirectText d) + d.Text = text; + } - void SetFontSize(object t, double size) - { - if (t is TextBlock tb) - tb.FontSize = size; - else if (t is DirectText d) - d.FontSize = size; - } + void SetFlow(object t, FlowDirection dir) + { + if (t is FrameworkElement f) + f.FlowDirection = dir; + } - private void DetailsRepeater_ElementClearing(ItemsRepeater sender, ItemsRepeaterElementClearingEventArgs args) - { - // Unload x:Bind content - if (args.Element is FrameworkElement f && f.Tag is FrameworkElement t) - { - UnloadObject(t); - f.Tag = null; - } - } + void SetFontSize(object t, double size) + { + if (t is TextBlock tb) + tb.FontSize = size; + else if (t is DirectText d) + d.FontSize = size; + } - private void Repeater_ElementPrepared(ItemsRepeater sender, ItemsRepeaterElementPreparedEventArgs args) + private void DetailsRepeater_ElementClearing(ItemsRepeater sender, ItemsRepeaterElementClearingEventArgs args) + { + // Unload x:Bind content + if (args.Element is FrameworkElement f && f.Tag is FrameworkElement t) { - if (args.Element is Button b && b.Content is Panel g) - { - SetText(g, InputText.Text); - SetFlow(g, InputText.DetectedFlowDirection); - SetFontSize(g, FontSizeSlider.Value); - } - else if (args.Element is FrameworkElement p && GetTargets(p).FirstOrDefault() is FrameworkElement t) - { - SetText(t, InputText.Text); - SetFlow(t, InputText.DetectedFlowDirection); - SetFontSize(t, FontSizeSlider.Value); - } + UnloadObject(t); + f.Tag = null; } + } - private void ItemClick(object sender, RoutedEventArgs e) + private void Repeater_ElementPrepared(ItemsRepeater sender, ItemsRepeaterElementPreparedEventArgs args) + { + if (args.Element is Button b && b.Content is Panel g) { - if (sender is Button b && b.Content is InstalledFont font && Repeater is ListViewBase list) - { - if (ResourceHelper.AllowAnimation) - { - var item = list.ContainerFromItem(font); - var title = item.GetFirstDescendantOfType(); - ConnectedAnimationService.GetForCurrentView().DefaultDuration = TimeSpan.FromSeconds(0.7); - ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("Title", title); - } - - ViewModel.SelectedFont = font; - } - else if (sender is Button bn && bn.Content is CharacterRenderingOptions o && ViewModel.IsQuickCompare) - { - ContextFlyout.SetItemsDataContext(o); - ContextFlyout.ShowAt(bn); - } + SetText(g, InputText.Text); + SetFlow(g, InputText.DetectedFlowDirection); + SetFontSize(g, FontSizeSlider.Value); } - - private void Repeater_ItemClick(object sender, ItemClickEventArgs e) + else if (args.Element is FrameworkElement p && GetTargets(p).FirstOrDefault() is FrameworkElement t) { - if (sender is GridView g && e.ClickedItem is CharacterRenderingOptions o && ViewModel.IsQuickCompare) - { - ContextFlyout.SetItemsDataContext(o); - ContextFlyout.ShowAt(g.ContainerFromItem(e.ClickedItem) as FrameworkElement); - } + SetText(t, InputText.Text); + SetFlow(t, InputText.DetectedFlowDirection); + SetFontSize(t, FontSizeSlider.Value); } + } - private void ItemContextRequested(UIElement sender, Windows.UI.Xaml.Input.ContextRequestedEventArgs args) + private void ItemClick(object sender, RoutedEventArgs e) + { + if (sender is Button b && b.Content is InstalledFont font && Repeater is ListViewBase list) { - if (ViewModel.IsQuickCompare && sender is Button b) - { - ContextFlyout.SetItemsDataContext(b.Content); - ContextFlyout.ShowAt(b); - } - else if (sender is Button bu && bu.Content is InstalledFont font) + if (ResourceHelper.AllowAnimation) { - // 1. Clear the context menu - while (MainContextFlyout.Items.Count > 1) - MainContextFlyout.Items.Remove(MainContextFlyout.Items[^1]); - - // 2. Rebuild with the correct collection information - MainContextFlyout.AddSeparator(); - FlyoutHelper.AddCollectionItems(MainContextFlyout, font, null); - FlyoutHelper.TryAddRemoveFromCollection( - MainContextFlyout, font, ViewModel.SelectedCollection, ViewModel.FontListFilter); - - // 3. Show Flyout - MainContextFlyout.SetItemsDataContext(bu.Content); - if (args.TryGetPosition(bu, out Point p)) - MainContextFlyout.ShowAt(bu, p); - else - MainContextFlyout.ShowAt(bu); + var item = list.ContainerFromItem(font); + var title = item.GetFirstDescendantOfType(); + ConnectedAnimationService.GetForCurrentView().DefaultDuration = TimeSpan.FromSeconds(0.7); + ConnectedAnimationService.GetForCurrentView().PrepareToAnimate("Title", title); } - } - private void OpenWindow_Click(object sender, RoutedEventArgs e) - { - if (sender is MenuFlyoutItem item) - { - if (item.DataContext is CharacterRenderingOptions o - && FontFinder.Fonts.FirstOrDefault(f => f.Variants.Contains(o.Variant)) is InstalledFont font) - { - _ = FontMapView.CreateNewViewForFontAsync(font, null, o); - } - else if (item.DataContext is InstalledFont f) - { - _ = FontMapView.CreateNewViewForFontAsync(f); - } - } + ViewModel.SelectedFont = font; } - - private void Remove_Clicked(object sender, RoutedEventArgs e) + else if (sender is Button bn && bn.Content is CharacterRenderingOptions o && ViewModel.IsQuickCompare) { - if (sender is MenuFlyoutItem item && item.DataContext is CharacterRenderingOptions o) - ViewModel.QuickFonts.Remove(o); + ContextFlyout.SetItemsDataContext(o); + ContextFlyout.ShowAt(bn); } + } - private void BackButton_Click(object sender, RoutedEventArgs e) + private void Repeater_ItemClick(object sender, ItemClickEventArgs e) + { + if (sender is GridView g && e.ClickedItem is CharacterRenderingOptions o && ViewModel.IsQuickCompare) { - ViewModel.SelectedFont = null; + ContextFlyout.SetItemsDataContext(o); + ContextFlyout.ShowAt(g.ContainerFromItem(e.ClickedItem) as FrameworkElement); } + } - private void GridView_Click(object sender, RoutedEventArgs e) + private void ItemContextRequested(UIElement sender, Windows.UI.Xaml.Input.ContextRequestedEventArgs args) + { + if (ViewModel.IsQuickCompare && sender is Button b) { - GoToState(GridLayoutState.Name); + ContextFlyout.SetItemsDataContext(b.Content); + ContextFlyout.ShowAt(b); } - - private void ListView_Click(object sender, RoutedEventArgs e) + else if (sender is Button bu && bu.Content is InstalledFont font) { - GoToState(StackLayoutState.Name); + // 1. Clear the context menu + while (MainContextFlyout.Items.Count > 1) + MainContextFlyout.Items.Remove(MainContextFlyout.Items[^1]); + + // 2. Rebuild with the correct collection information + MainContextFlyout.AddSeparator(); + FlyoutHelper.AddCollectionItems(MainContextFlyout, font, null); + FlyoutHelper.TryAddRemoveFromCollection( + MainContextFlyout, font, ViewModel.SelectedCollection, ViewModel.FontListFilter); + + // 3. Show Flyout + MainContextFlyout.SetItemsDataContext(bu.Content); + if (args.TryGetPosition(bu, out Point p)) + MainContextFlyout.ShowAt(bu, p); + else + MainContextFlyout.ShowAt(bu); } + } - private void ViewStates_CurrentStateChanging(object sender, VisualStateChangedEventArgs e) + private void OpenWindow_Click(object sender, RoutedEventArgs e) + { + if (sender is MenuFlyoutItem item) { - if (e.NewState == DetailsState) + if (item.DataContext is CharacterRenderingOptions o + && FontFinder.Fonts.FirstOrDefault(f => f.Variants.Contains(o.Variant)) is InstalledFont font) + { + _ = FontMapView.CreateNewViewForFontAsync(font, null, o); + } + else if (item.DataContext is InstalledFont f) { - DetailsFontTitle.Text = ViewModel.SelectedFont.Name; + _ = FontMapView.CreateNewViewForFontAsync(f); + } + } + } - if (ResourceHelper.AllowAnimation is false) - return; + private void Remove_Clicked(object sender, RoutedEventArgs e) + { + if (sender is MenuFlyoutItem item && item.DataContext is CharacterRenderingOptions o) + ViewModel.QuickFonts.Remove(o); + } - var ani = ConnectedAnimationService.GetForCurrentView().GetAnimation("Title"); - //ani.Configuration = new BasicConnectedAnimationConfiguration(); - //var c = this.GetElementVisual().Compositor; - //var offset = c.CreateScalarKeyFrameAnimation(); + private void BackButton_Click(object sender, RoutedEventArgs e) + { + ViewModel.SelectedFont = null; + } - ////CubicBezierEasingFunction ease = c.CreateCubicBezierEasingFunction( - //// new Vector2(0.95f, 0.05f), - //// new Vector2(0.79f, 0.04f)); + private void GridView_Click(object sender, RoutedEventArgs e) + { + GoToState(GridLayoutState.Name); + } - //CubicBezierEasingFunction easeOut = c.CreateCubicBezierEasingFunction( - //new Vector2(0.13f, 1.0f), - //new Vector2(0.49f, 1.0f)); + private void ListView_Click(object sender, RoutedEventArgs e) + { + GoToState(StackLayoutState.Name); + } - //offset.InsertExpressionKeyFrame(0.0f, "StartingValue"); - ////offset.InsertExpressionKeyFrame(0.2f, "StartingValue"); - //offset.InsertExpressionKeyFrame(1, "FinalValue", easeOut); - //offset.Duration = TimeSpan.FromSeconds(0.6); - //offset.DelayTime = TimeSpan.FromSeconds(0.15); + private void ViewStates_CurrentStateChanging(object sender, VisualStateChangedEventArgs e) + { + if (e.NewState == DetailsState) + { + DetailsFontTitle.Text = ViewModel.SelectedFont.Name; - //ani.SetAnimationComponent(ConnectedAnimationComponent.OffsetX, offset); - //ani.SetAnimationComponent(ConnectedAnimationComponent.OffsetY, offset); - //ani.SetAnimationComponent(ConnectedAnimationComponent.Scale, offset); - //ani.SetAnimationComponent(ConnectedAnimationComponent.CrossFade, offset); + if (ResourceHelper.AllowAnimation is false) + return; - ani.TryStart(DetailsTitleContainer);//, new List { DetailsViewContent }); - } + var ani = ConnectedAnimationService.GetForCurrentView().GetAnimation("Title"); + ani.TryStart(DetailsTitleContainer);//, new List { DetailsViewContent }); } + } - private void Repeater_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) + private void Repeater_ContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args) + { + if (args.Phase == 1) { - if (args.Phase == 1) + var g = GetTargets(args.ItemContainer).FirstOrDefault(); + if (!args.InRecycleQueue && g is not null) { - var g = GetTargets(args.ItemContainer).FirstOrDefault(); - if (!args.InRecycleQueue && g is not null) - { - g.DataContext = args.Item; - SetText(g, ViewModel.Text); - SetFlow(g, InputText.DetectedFlowDirection); - SetFontSize(g, FontSizeSlider.Value); - } + g.DataContext = args.Item; + SetText(g, ViewModel.Text); + SetFlow(g, InputText.DetectedFlowDirection); + SetFontSize(g, FontSizeSlider.Value); } - else + } + else + { + // This is hack for Quick Compare view - force ItemTemplate + // to be inflated so our code will work + if (args.ItemContainer.Content is null) + { + args.ItemContainer.Content = args.Item; + args.ItemContainer.Measure(new Windows.Foundation.Size(50, 50)); + } + + args.RegisterUpdateCallback(1, Repeater_ContainerContentChanging); + + if (ResourceHelper.AllowExpensiveAnimation && LayoutModeStates.CurrentState == GridLayoutState) { - // This is hack for Quick Compare view - force ItemTemplate - // to be inflated so our code will work - if (args.ItemContainer.Content is null) - { - args.ItemContainer.Content = args.Item; - args.ItemContainer.Measure(new Windows.Foundation.Size(50, 50)); - } - - args.RegisterUpdateCallback(1, Repeater_ContainerContentChanging); - - if (ResourceHelper.AllowExpensiveAnimation) - { - if (args.InRecycleQueue) - { - CompositionFactory.PokeUIElementZIndex(args.ItemContainer); - } - else - { - var v = ElementCompositionPreview.GetElementVisual(args.ItemContainer); - v.ImplicitAnimations = CompositionFactory.GetRepositionCollection(v.Compositor); - } - } + if (args.InRecycleQueue) + CompositionFactory.PokeUIElementZIndex(args.ItemContainer); + else + SetAnimation(args.ItemContainer, true); } + else + SetAnimation(args.ItemContainer, false); } + } + private void LayoutModeStates_CurrentStateChanging(object sender, VisualStateChangedEventArgs e) + { + if (Repeater.ItemsPanelRoot is null) + return; + + // Attempting to change this in XAML causes crashes. + // We don't won't reposition animations in stack layout + bool enable = e.NewState == GridLayoutState; + foreach (var item in Repeater.ItemsPanelRoot.Children.OfType()) + SetAnimation(item, enable); + } + void SetAnimation(SelectorItem item, bool enable) + { + var v = ElementCompositionPreview.GetElementVisual(item); + v.ImplicitAnimations = enable ? CompositionFactory.GetRepositionCollection(v.Compositor) : null; + } - /* Notification Helpers */ - private void HandleMessage(CollectionsUpdatedMessage obj) + + /* Notification Helpers */ + + private void HandleMessage(CollectionsUpdatedMessage obj) + { + if (obj.SourceCollection is not null && obj.SourceCollection == ViewModel.SelectedCollection) { - if (obj.SourceCollection is not null && obj.SourceCollection == ViewModel.SelectedCollection) - { - RunOnUI(() => ViewModel.RefreshFontList(ViewModel.SelectedCollection)); - } + RunOnUI(() => ViewModel.RefreshFontList(ViewModel.SelectedCollection)); } + } - private void HandleMessage(CollectionRequestedMessage obj) + private void HandleMessage(CollectionRequestedMessage obj) + { + if (Dispatcher.HasThreadAccess) { - if (Dispatcher.HasThreadAccess) - { - obj.Handled = true; - ViewModel.SelectedCollection = obj.Collection; - GoToNormalState(); - } + obj.Handled = true; + ViewModel.SelectedCollection = obj.Collection; + GoToNormalState(); } + } - public InAppNotification GetNotifier() - { - if (NotificationRoot == null) - this.FindName(nameof(NotificationRoot)); + public InAppNotification GetNotifier() + { + if (NotificationRoot == null) + this.FindName(nameof(NotificationRoot)); - return DefaultNotification; - } + return DefaultNotification; + } - void OnNotificationMessage(AppNotificationMessage msg) + void OnNotificationMessage(AppNotificationMessage msg) + { + if (msg.Data is AddToCollectionResult result + && result.Success + && result.Collection is not null + && result.Collection == ViewModel.SelectedCollection + && Dispatcher.HasThreadAccess == false) { - if (msg.Data is AddToCollectionResult result - && result.Success - && result.Collection is not null - && result.Collection == ViewModel.SelectedCollection - && Dispatcher.HasThreadAccess == false) - { - // If we don't have thread access, it means another window has added an item to - // the collection we're currently viewing, and we should refresh our view - RunOnUI(() => ViewModel.RefreshFontList(ViewModel.SelectedCollection)); - } + // If we don't have thread access, it means another window has added an item to + // the collection we're currently viewing, and we should refresh our view + RunOnUI(() => ViewModel.RefreshFontList(ViewModel.SelectedCollection)); + } - if (!Dispatcher.HasThreadAccess) - return; + if (!Dispatcher.HasThreadAccess) + return; - InAppNotificationHelper.OnMessage(this, msg); - } + InAppNotificationHelper.OnMessage(this, msg); } +} - public partial class QuickCompareView +public partial class QuickCompareView +{ + public static async Task CreateWindowAsync(QuickCompareArgs args) { - public static async Task CreateWindowAsync(QuickCompareArgs args) + // 1. If QuickCompare (rather than FontCompare), return the existing window + // if we have one. (QuickCompare is ALWAYS single window) + if (args.IsQuickCompare && QuickCompareViewModel.QuickCompareWindow is not null) + return QuickCompareViewModel.QuickCompareWindow; + + static void CreateView(QuickCompareArgs a) { - // 1. If QuickCompare (rather than FontCompare), return the existing window - // if we have one. (QuickCompare is ALWAYS single window) - if (args.IsQuickCompare && QuickCompareViewModel.QuickCompareWindow is not null) - return QuickCompareViewModel.QuickCompareWindow; + QuickCompareView view = new(a); + Window.Current.Content = view; + Window.Current.Activate(); + } - static void CreateView(QuickCompareArgs a) - { - QuickCompareView view = new(a); - Window.Current.Content = view; - Window.Current.Activate(); - } + var view = await WindowService.CreateViewAsync(() => CreateView(args), false); + await WindowService.TrySwitchToWindowAsync(view, false); - var view = await WindowService.CreateViewAsync(() => CreateView(args), false); - await WindowService.TrySwitchToWindowAsync(view, false); + if (args.IsQuickCompare) + QuickCompareViewModel.QuickCompareWindow = view; - if(args.IsQuickCompare) - QuickCompareViewModel.QuickCompareWindow = view; - - return view; - } + return view; + } - public static async Task AddAsync(CharacterRenderingOptions options) - { - // 1. Ensure QuickCompare Window exists - var window = await CreateWindowAsync(new(true)); + public static async Task AddAsync(CharacterRenderingOptions options) + { + // 1. Ensure QuickCompare Window exists + var window = await CreateWindowAsync(new(true)); - // 2. Add selected font to QuickCompare - await QuickCompareViewModel.QuickCompareWindow.CoreView.Dispatcher.ExecuteAsync(() => - { - WeakReferenceMessenger.Default.Send(options, nameof(QuickCompareViewModel)); - }); + // 2. Add selected font to QuickCompare + await QuickCompareViewModel.QuickCompareWindow.CoreView.Dispatcher.ExecuteAsync(() => + { + WeakReferenceMessenger.Default.Send(options, nameof(QuickCompareViewModel)); + }); - // 3. Try switch to view. - // Task.Delay is required as TrySwitchToWindow may fail. - await Task.Delay(64); - await WindowService.TrySwitchToWindowAsync(QuickCompareViewModel.QuickCompareWindow, false); - } + // 3. Try switch to view. + // Task.Delay is required as TrySwitchToWindow may fail. + await Task.Delay(64); + await WindowService.TrySwitchToWindowAsync(QuickCompareViewModel.QuickCompareWindow, false); } } diff --git a/CharacterMap/CharacterMap/Views/SettingsView.xaml b/CharacterMap/CharacterMap/Views/SettingsView.xaml index 6610b464..07d07eee 100644 --- a/CharacterMap/CharacterMap/Views/SettingsView.xaml +++ b/CharacterMap/CharacterMap/Views/SettingsView.xaml @@ -120,45 +120,54 @@ + Content="{x:Bind h:Localization.Get('SettingsCollectionsHeader')}" + Tag="CollectionPanel"> - + + Content="{x:Bind h:Localization.Get('SettingsFontManagementLabel/Text')}" + Tag="FontPanel" /> + + - + + Content="{x:Bind h:Localization.Get('SettingsExportHeader/Text')}" + Tag="ExportPanel"> - + + Content="{x:Bind h:Localization.Get('SettingsCharacterSearchHeader/Text')}" + Tag="SearchPanel"> - + + Content="{x:Bind h:Localization.Get('SettingsAdvancedOptionsLabel/Text')}" + Tag="AdvancedPanel"> + + + + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + Text=" e.g. MS Office Symbol Regular v1.5.ttf" /> - - + + + + @@ -661,9 +687,19 @@ + + + + + + + @@ -694,7 +730,7 @@ - + @@ -729,91 +765,91 @@ - - + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + - - - - - + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + - + - - - - + + diff --git a/CharacterMap/CharacterMap/Views/SettingsView.xaml.cs b/CharacterMap/CharacterMap/Views/SettingsView.xaml.cs index d3fdd37a..ad54aa23 100644 --- a/CharacterMap/CharacterMap/Views/SettingsView.xaml.cs +++ b/CharacterMap/CharacterMap/Views/SettingsView.xaml.cs @@ -1,378 +1,369 @@ using CharacterMap.Controls; -using CharacterMap.Core; -using CharacterMap.Helpers; -using CharacterMap.Models; -using CharacterMap.ViewModels; -using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Messaging; using Microsoft.UI.Xaml.Controls; -using System; -using System.Collections.Generic; -using System.Linq; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; -namespace CharacterMap.Views +namespace CharacterMap.Views; + +public sealed partial class SettingsView : ViewBase { - public sealed partial class SettingsView : ViewBase - { - public AppSettings Settings { get; } + public AppSettings Settings { get; } - public SettingsViewModel ViewModel { get; } + public SettingsViewModel ViewModel { get; } - public bool IsOpen { get; private set; } + public bool IsOpen { get; private set; } - [ObservableProperty] - private GridLength _titleBarHeight = new GridLength(32); + [ObservableProperty] + private GridLength _titleBarHeight = new(32); - public int GridSize - { - get { return (int)GetValue(GridSizeProperty); } - set { SetValue(GridSizeProperty, value); } - } + public int GridSize + { + get { return (int)GetValue(GridSizeProperty); } + set { SetValue(GridSizeProperty, value); } + } - public static readonly DependencyProperty GridSizeProperty = - DependencyProperty.Register(nameof(GridSize), typeof(int), typeof(SettingsView), new PropertyMetadata(0d)); + public static readonly DependencyProperty GridSizeProperty = + DependencyProperty.Register(nameof(GridSize), typeof(int), typeof(SettingsView), new PropertyMetadata(0d)); - private bool _themeSupportsShadows = false; + private bool _themeSupportsShadows = false; - private bool _themeSupportsDark = false; + private bool _themeSupportsDark = false; - private NavigationHelper _navHelper { get; } = new (); + private NavigationHelper _navHelper { get; } = new(); - private int _requested = 0; + private int _requested = 0; - public SettingsView() - { - this.InitializeComponent(); + public SettingsView() + { + this.InitializeComponent(); - if (DesignMode) - return; + if (DesignMode) + return; - ViewModel = new(); - Settings = ResourceHelper.AppSettings; - Register(OnAppSettingsUpdated); - Register(m => UpdateExport()); + ViewModel = new(); + Settings = ResourceHelper.AppSettings; + Register(OnAppSettingsUpdated); + Register(m => UpdateExport()); - GridSize = Settings.GridSize; + GridSize = Settings.GridSize; - _themeSupportsShadows = ResourceHelper.SupportsShadows(); - _themeSupportsDark = ResourceHelper.Get("SupportsDarkTheme"); + _themeSupportsShadows = ResourceHelper.SupportsShadows(); + _themeSupportsDark = ResourceHelper.Get("SupportsDarkTheme"); - _navHelper.BackRequested += (s, e) => Hide(); - } + _navHelper.BackRequested += (s, e) => Hide(); + } - public void Show(FontVariant variant, InstalledFont font, int idx = 0) + public void Show(FontVariant variant, InstalledFont font, int idx = 0) + { + if (IsOpen) { - if (IsOpen) - { - SetIndex(idx); - return; - } + SetIndex(idx); + return; + } - UpdateAnimation(); - StartShowAnimation(); - this.Visibility = Visibility.Visible; + UpdateAnimation(); + StartShowAnimation(); + this.Visibility = Visibility.Visible; - if (!ResourceHelper.AllowAnimation) - { - this.GetElementVisual().Opacity = 1; - this.GetElementVisual().SetTranslation(0,0,0); - } + if (!ResourceHelper.AllowAnimation) + { + this.GetElementVisual().Opacity = 1; + this.GetElementVisual().SetTranslation(0, 0, 0); + } - // 1. Focus the close button to ensure keyboard focus is retained inside the settings panel - Presenter.SetDefaultFocus(); + // 1. Focus the close button to ensure keyboard focus is retained inside the settings panel + Presenter.SetDefaultFocus(); - // 2. Reset scroll position + // 2. Reset scroll position #pragma warning disable CS0618 // ChangeView doesn't work well when not properly visible - ContentScroller.ScrollToVerticalOffset(0); + ContentScroller.ScrollToVerticalOffset(0); #pragma warning restore CS0618 - // 3. Get the fonts used for Font List & Character Grid previews - ViewModel.UpdatePreviews(font, variant); - + // 3. Get the fonts used for Font List & Character Grid previews + ViewModel.UpdatePreviews(font, variant); - Presenter.SetTitleBar(); - IsOpen = true; - _navHelper.Activate(); - _requested = idx; - SetIndex(idx); + Presenter.SetTitleBar(); + IsOpen = true; + _navHelper.Activate(); - void SetIndex(int i) - { - if (IsLoaded) - MenuColumn.Children.OfType().ElementAt(i).IsChecked = true; - } - - } + _requested = idx; + SetIndex(idx); - public void Hide() + void SetIndex(int i) { - UpdateAnimation(); - _navHelper.Deactivate(); - - TitleBarHelper.RestoreDefaultTitleBar(); - IsOpen = false; - this.Visibility = Visibility.Collapsed; - Messenger.Send(new ModalClosedMessage()); + if (IsLoaded) + MenuColumn.Children.OfType().ElementAt(i).IsChecked = true; } - void OnAppSettingsUpdated(AppSettingsChangedMessage msg) + } + + public void Hide() + { + UpdateAnimation(); + _navHelper.Deactivate(); + + TitleBarHelper.RestoreDefaultTitleBar(); + IsOpen = false; + this.Visibility = Visibility.Collapsed; + Messenger.Send(new ModalClosedMessage()); + } + + void OnAppSettingsUpdated(AppSettingsChangedMessage msg) + { + RunOnUI(() => { - RunOnUI(() => + switch (msg.PropertyName) { - switch (msg.PropertyName) - { - case nameof(Settings.UserRequestedTheme): - OnPropertyChanged(nameof(Settings)); - break; - case nameof(Settings.GridSize): - // We can't direct bind here as it may be updated from a different UI Thread. - GridSize = Settings.GridSize; - break; - case nameof(Settings.ApplicationDesignTheme): - //UpdateStyle(); - break; - } - }); - } + case nameof(Settings.UserRequestedTheme): + OnPropertyChanged(nameof(Settings)); + break; + case nameof(Settings.GridSize): + // We can't direct bind here as it may be updated from a different UI Thread. + GridSize = Settings.GridSize; + break; + case nameof(Settings.ApplicationDesignTheme): + //UpdateStyle(); + break; + } + }); + } - protected override void OnUnloaded(object sender, RoutedEventArgs e) - { - // Override base so we do not unregister messages - } + protected override void OnUnloaded(object sender, RoutedEventArgs e) + { + // Override base so we do not unregister messages + } - private void View_Loaded(object sender, RoutedEventArgs e) - { - UpdateStyle(); - ((RadioButton)MenuColumn.Children.ElementAt(_requested)).IsChecked = true; - } + private void View_Loaded(object sender, RoutedEventArgs e) + { + UpdateStyle(); + ((RadioButton)MenuColumn.Children.ElementAt(_requested)).IsChecked = true; + } - /* Work around to avoid binding threading issues */ - private int GetGridSize(int s) => s; + /* Work around to avoid binding threading issues */ + private int GetGridSize(int s) => s; - private void UpdateGridSize(double d) - { - GridSize = Settings.GridSize = (int)d; - } + private void UpdateGridSize(double d) + { + GridSize = Settings.GridSize = (int)d; + } - private void UpdateExport() + private void UpdateExport() + { + this.RunOnUI(() => { - this.RunOnUI(() => - { - ExportImportedButton?.SetVisible(FontFinder.ImportedFonts.Count > 0); - }); - } + ExportImportedButton?.SetVisible(FontFinder.ImportedFonts.Count > 0); + }); + } - private void FontNamingSelection_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - Settings.ExportNamingScheme = (ExportNamingScheme)((RadioButtons)sender).SelectedIndex; - } + private void FontNamingSelection_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + Settings.ExportNamingScheme = (ExportNamingScheme)((RadioButtons)sender).SelectedIndex; + } - private void UseSystemFont_Checked(object sender, RoutedEventArgs e) - { - Settings.UseFontForPreview = false; - ViewModel.ResetFontPreview(); - } + private void UseSystemFont_Checked(object sender, RoutedEventArgs e) + { + Settings.UseFontForPreview = false; + ViewModel.ResetFontPreview(); + } - private void UseActualFont_Checked(object sender, RoutedEventArgs e) - { - Settings.UseFontForPreview = true; - ViewModel.ResetFontPreview(); - } + private void UseActualFont_Checked(object sender, RoutedEventArgs e) + { + Settings.UseFontForPreview = true; + ViewModel.ResetFontPreview(); + } - private void Design_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - ViewModel.SetDesign(((ComboBox)sender).SelectedIndex); - } + private void Design_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + ViewModel.SetDesign(((ComboBox)sender).SelectedIndex); + } - public void SelectedLanguageToString(object selected) => - Settings.AppLanguage = selected is SupportedLanguage s ? s.LanguageID : ""; + public void SelectedLanguageToString(object selected) => + Settings.AppLanguage = selected is SupportedLanguage s ? s.LanguageID : ""; - private void DeleteRampClick(object sender, RoutedEventArgs e) + private void DeleteRampClick(object sender, RoutedEventArgs e) + { + if (sender is FrameworkElement ele + && ele.DataContext is string s) { - if (sender is FrameworkElement ele - && ele.DataContext is string s) - { - ViewModel.RemoveRamp(s); - } + ViewModel.RemoveRamp(s); } + } - private void SearchSwitchSettingsPresenter_Loaded(object sender, RoutedEventArgs e) + private void SearchSwitchSettingsPresenter_Loaded(object sender, RoutedEventArgs e) + { + // Fix bug with x:Load + if (Settings.UseInstantSearch) + this.FindName(nameof(SearchDelayItem)); + } + + private void MenuItem_Checked(object sender, RoutedEventArgs e) + { + Panel GetPanel(RadioButton button) { - // Fix bug with x:Load - if (Settings.UseInstantSearch) - this.FindName(nameof(SearchDelayItem)); + return button.Tag switch + { + Panel p => p, + string s => ContentPanel.FindName(s) as Panel, + _ => null + }; } - private void MenuItem_Checked(object sender, RoutedEventArgs e) + if (sender is RadioButton item + && GetPanel(item) is Panel panel + && panel.Visibility == Visibility.Collapsed) { - Panel GetPanel(RadioButton button) + // 1: Ensure all settings panels are hidden + foreach (var child in ContentPanel.Children.OfType()) { - return button.Tag switch - { - Panel p => p, - string s => ContentPanel.FindName(s) as Panel, - _ => null - }; + // 2: Deactivate old content if supported + if (child.Visibility == Visibility.Visible + && child is Panel p + && p.Children.Count == 1 + && p.Children[0] is IActivateableControl d) + d.Deactivate(); + + child.Visibility = Visibility.Collapsed; } - if (sender is RadioButton item - && GetPanel(item) is Panel panel - && panel.Visibility == Visibility.Collapsed) + // 3: Reset scroll position + ContentScroller.ChangeView(null, 0, null, true); + + // 4: Activate new content if supported + if (panel.Children.Count == 1 && panel.Children[0] is IActivateableControl a) { - // 1: Ensure all settings panels are hidden - foreach (var child in ContentPanel.Children.OfType()) - { - // 2: Deactivate old content if supported - if (child.Visibility == Visibility.Visible - && child is Panel p - && p.Children.Count == 1 - && p.Children[0] is IActivateableControl d) - d.Deactivate(); - - child.Visibility = Visibility.Collapsed; - } + a.Activate(); + VisualStateManager.GoToState(this, ContentScrollDisabledState.Name, false); + } + else + VisualStateManager.GoToState(this, ContentScrollEnabledState.Name, false); - // 3: Reset scroll position - ContentScroller.ChangeView(null, 0, null, true); + panel.Opacity = 0; + panel.Visibility = Visibility.Visible; + panel.Measure(ContentScroller.DesiredSize); - // 4: Activate new content if supported - if (panel.Children.Count == 1 && panel.Children[0] is IActivateableControl a) - { - a.Activate(); - VisualStateManager.GoToState(this, ContentScrollDisabledState.Name, false); - } + // 5. Update properties that can't be set with bindings + // and can't be set earlier due to x:Load + if (panel == UserInterfacePanel) + { + if (Settings.UseFontForPreview) + UseActualFont.IsChecked = true; else - VisualStateManager.GoToState(this, ContentScrollEnabledState.Name, false); - - panel.Opacity = 0; - panel.Visibility = Visibility.Visible; - panel.Measure(ContentScroller.DesiredSize); - - // 5. Update properties that can't be set with bindings - // and can't be set earlier due to x:Load - if (panel == UserInterfacePanel) - { - if (Settings.UseFontForPreview) - UseActualFont.IsChecked = true; - else - UseSystemFont.IsChecked = true; - } - else if (panel == UserInterfaceAdvancedPanel) + UseSystemFont.IsChecked = true; + } + else if (panel == UserInterfaceAdvancedPanel) + { + if (_themeSupportsDark && ThemeSystem is not null) { - if (_themeSupportsDark && ThemeSystem is not null) + switch (Settings.UserRequestedTheme) { - switch (Settings.UserRequestedTheme) - { - case ElementTheme.Default: - ThemeSystem.IsChecked = true; - break; - case ElementTheme.Light: - ThemeLight.IsChecked = true; - break; - case ElementTheme.Dark: - ThemeDark.IsChecked = true; - break; - } + case ElementTheme.Default: + ThemeSystem.IsChecked = true; + break; + case ElementTheme.Light: + ThemeLight.IsChecked = true; + break; + case ElementTheme.Dark: + ThemeDark.IsChecked = true; + break; } } - else if (panel == ExportPanel) - { - FontNamingSelection.SelectedIndex = (int)Settings.ExportNamingScheme; - } - else if (panel == FontPanel) - { - UpdateExport(); - } + } + else if (panel == ExportPanel) + { + FontNamingSelection.SelectedIndex = (int)Settings.ExportNamingScheme; + } + else if (panel == FontPanel) + { + UpdateExport(); + } - // 6. Start child animation - if (ResourceHelper.AllowAnimation) - CompositionFactory.PlayEntrance(GetChildren(panel), 10, 80); + // 6. Start child animation + if (ResourceHelper.AllowAnimation) + CompositionFactory.PlayEntrance(GetChildren(panel), 10, 80); - // 7. Show selected panel - panel.Opacity = 1; - } + // 7. Show selected panel + panel.Opacity = 1; } + } - private List GetChildren(Panel p) - { - List children = new (); + private List GetChildren(Panel p) + { + List children = new(); - foreach (var child in p.Children) + foreach (var child in p.Children) + { + if (child is ItemsControl c + && c is not SettingsPresenter + && c.ItemsPanelRoot is not null) { - if (child is ItemsControl c - && c is not SettingsPresenter - && c.ItemsPanelRoot is not null) - { - c.Realize(p.DesiredSize.Width, p.DesiredSize.Height); - children.AddRange(c.ItemsPanelRoot.Children); - } - else - children.Add(child); + c.Realize(p.DesiredSize.Width, p.DesiredSize.Height); + children.AddRange(c.ItemsPanelRoot.Children); } - - return children; + else + children.Add(child); } + return children; + } + - void UpdateStyle() + void UpdateStyle() + { + if (DesignMode) return; + //string key = Settings.ApplicationDesignTheme == 0 ? "Default" : "FUI"; + //if (Settings.ApplicationDesignTheme == 2) key = "Zune"; + //var controls = this.GetDescendants().OfType().Where(e => e is not IThemeableControl && Properties.GetStyleKey(e) is not null).ToList(); + //foreach (var p in controls) + //{ + // string target = $"{key}{Properties.GetStyleKey(p)}"; + // Style style = ResourceHelper.Get