From fd46d05133b3f1b9a3daac73ba1eff3308244e67 Mon Sep 17 00:00:00 2001 From: reduckted Date: Thu, 9 Feb 2023 21:42:31 +1000 Subject: [PATCH 1/3] Created wrappers for Visual Studio's font and color classes. --- .../ProvideFontsAndColorsAttribute.cs | 60 ++++ .../CodeAnalysis/MemberNotNullAttribute.cs | 13 + .../AsyncPackageExtensions.cs | 51 ++- .../BaseFontAndColorCategory.cs | 326 ++++++++++++++++++ .../BaseFontAndColorCategory`1.cs | 89 +++++ .../BaseFontAndColorProvider.cs | 115 ++++++ .../FontsAndColors/ColorDefinition.cs | 173 ++++++++++ .../FontsAndColors/ColorOptions.cs | 42 +++ .../FontsAndColors/ConfiguredColor.cs | 147 ++++++++ .../ConfiguredColorChangedEventArgs.cs | 32 ++ .../FontsAndColors/ConfiguredFont.cs | 110 ++++++ .../ConfiguredFontAndColorSet.cs | 95 +++++ .../FontsAndColors/FontDefinition.cs | 48 +++ .../FontsAndColors/FontStyle.cs | 33 ++ .../FontsAndColors/FontsAndColors.cs | 79 +++++ .../IFontAndColorChangeListener.cs | 11 + .../FontsAndColors/LineStyle.cs | 48 +++ .../FontsAndColors/MarkerVisualStyle.cs | 106 ++++++ .../FontsAndColors/VisualStudioColor.cs | 142 ++++++++ .../VS.cs | 4 + .../VSSDK.Helpers.Shared.projitems | 32 +- 21 files changed, 1735 insertions(+), 21 deletions(-) create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/Attributes/ProvideFontsAndColorsAttribute.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/CodeAnalysis/MemberNotNullAttribute.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/BaseFontAndColorCategory.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/BaseFontAndColorCategory`1.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/BaseFontAndColorProvider.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ColorDefinition.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ColorOptions.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredColor.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredColorChangedEventArgs.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredFont.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredFontAndColorSet.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/FontDefinition.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/FontStyle.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/FontsAndColors.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/IFontAndColorChangeListener.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/LineStyle.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/MarkerVisualStyle.cs create mode 100644 src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/VisualStudioColor.cs diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/Attributes/ProvideFontsAndColorsAttribute.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/Attributes/ProvideFontsAndColorsAttribute.cs new file mode 100644 index 0000000..7a4259b --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/Attributes/ProvideFontsAndColorsAttribute.cs @@ -0,0 +1,60 @@ +using System; +using Microsoft.VisualStudio.Shell; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// Registers font and color definitions in Visual Studio. + /// + [AttributeUsage(AttributeTargets.Class)] + public class ProvideFontsAndColorsAttribute : ProvideServiceAttributeBase + { + /// + /// Initializes a new instance of the class. + /// The will also be provided as a service. + /// + /// The type of the implementation to provide. + public ProvideFontsAndColorsAttribute(Type providerType) + : base(providerType, "Services") + { + ProviderType = providerType; + } + + /// + /// The implementation to provide. + /// + public Type ProviderType { get; } + + /// + public override void Register(RegistrationContext context) + { + if (context is not null) + { + foreach (Type categoryType in BaseFontAndColorProvider.GetCategoryTypes(ProviderType)) + { + using (Key key = context.CreateKey($"FontAndColors\\{categoryType.FullName}")) + { + key.SetValue("Category", categoryType.GUID.ToString("B")); + key.SetValue("Package", ProviderType.GUID.ToString("B")); + } + } + } + + base.Register(context); + } + + /// + public override void Unregister(RegistrationContext context) + { + if (context is not null) + { + foreach (Type categoryType in BaseFontAndColorProvider.GetCategoryTypes(ProviderType)) + { + context.RemoveKey($"FontAndColors\\{categoryType.FullName}"); + } + } + + base.Unregister(context); + } + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/CodeAnalysis/MemberNotNullAttribute.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/CodeAnalysis/MemberNotNullAttribute.cs new file mode 100644 index 0000000..adf9218 --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/CodeAnalysis/MemberNotNullAttribute.cs @@ -0,0 +1,13 @@ +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// This is a copy of the attribute of the same name from .NET 5+ to allow it to be used in .NET Framework. + /// https://github.com/dotnet/runtime/blob/47071da67320985a10f4b70f50f894ab411f4994/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs#L137 + /// + internal class MemberNotNullAttribute : Attribute + { + public MemberNotNullAttribute(string member) => Members = new[] { member }; + public MemberNotNullAttribute(params string[] members) => Members = members; + public string[] Members { get; } + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/ExtensionMethods/AsyncPackageExtensions.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/ExtensionMethods/AsyncPackageExtensions.cs index 90d6167..1be4578 100644 --- a/src/toolkit/Community.VisualStudio.Toolkit.Shared/ExtensionMethods/AsyncPackageExtensions.cs +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/ExtensionMethods/AsyncPackageExtensions.cs @@ -21,20 +21,15 @@ public static class AsyncPackageExtensions /// A collection of the command instances public static async Task> RegisterCommandsAsync(this AsyncPackage package, params Assembly[] assemblies) { - List assembliesList = assemblies.ToList(); - Assembly packageAssembly = package.GetType().Assembly; - if (!assembliesList.Contains(packageAssembly)) - assembliesList.Add(packageAssembly); - Type baseCommandType = typeof(BaseCommand<>); - IEnumerable commandTypes = assembliesList.SelectMany(x => x.GetTypes()) + IEnumerable commandTypes = IncludePackageAssembly(assemblies, package).SelectMany(x => x.GetTypes()) .Where(x => !x.IsAbstract && x.IsAssignableToGenericType(baseCommandType) && x.GetCustomAttribute() != null); List commands = new(); - foreach (Type? commandType in commandTypes) + foreach (Type commandType in commandTypes) { MethodInfo initializeAsyncMethod = commandType.GetMethod( nameof(BaseCommand.InitializeAsync), @@ -56,18 +51,13 @@ public static async Task> RegisterCommandsAsync(this AsyncPa /// public static void RegisterToolWindows(this AsyncPackage package, params Assembly[] assemblies) { - List assembliesList = assemblies.ToList(); - Assembly packageAssembly = package.GetType().Assembly; - if (!assembliesList.Contains(packageAssembly)) - assembliesList.Add(packageAssembly); - Type baseToolWindowType = typeof(BaseToolWindow<>); - IEnumerable toolWindowTypes = assembliesList.SelectMany(x => x.GetTypes()) + IEnumerable toolWindowTypes = IncludePackageAssembly(assemblies, package).SelectMany(x => x.GetTypes()) .Where(x => !x.IsAbstract && x.IsAssignableToGenericType(baseToolWindowType)); - foreach (Type? toolWindowtype in toolWindowTypes) + foreach (Type toolWindowtype in toolWindowTypes) { MethodInfo initializeMethod = toolWindowtype.GetMethod( "Initialize", @@ -76,5 +66,38 @@ public static void RegisterToolWindows(this AsyncPackage package, params Assembl initializeMethod.Invoke(null, new object[] { package }); } } + + /// + /// Finds, creates and registers implementations. + /// + /// The package to register the provider in. + /// Additional assemblies to scan. The assembly that is in will always be scanned. + /// A task. + public static async Task RegisterFontAndColorProvidersAsync(this AsyncPackage package, params Assembly[] assemblies) + { + Type baseProviderType = typeof(BaseFontAndColorProvider); + IEnumerable providerTypes = IncludePackageAssembly(assemblies, package) + .SelectMany(x => x.GetTypes()) + .Where(x => !x.IsAbstract && baseProviderType.IsAssignableFrom(x)); + + foreach (Type providerType in providerTypes) + { + ConstructorInfo? ctor = providerType.GetConstructor(Type.EmptyTypes) + ?? throw new InvalidOperationException($"The type '{providerType.Name}' must have a parameterless constructor."); + BaseFontAndColorProvider provider = (BaseFontAndColorProvider)ctor.Invoke(Array.Empty()); + await provider.InitializeAsync(package); + } + } + + private static IReadOnlyList IncludePackageAssembly(IEnumerable assemblies, AsyncPackage package) + { + List list = assemblies.ToList(); + Assembly packageAssembly = package.GetType().Assembly; + if (!list.Contains(packageAssembly)) + { + list.Add(packageAssembly); + } + return list; + } } } diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/BaseFontAndColorCategory.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/BaseFontAndColorCategory.cs new file mode 100644 index 0000000..813fb51 --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/BaseFontAndColorCategory.cs @@ -0,0 +1,326 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// Defines a font and color definitions that are shown on the Fonts and Colors options page. + /// + /// + /// Inherit from instead of this class. + /// + public abstract class BaseFontAndColorCategory : IVsFontAndColorDefaults, IVsFontAndColorEvents + { + private readonly FontDefinition _defaultFont; + private readonly Guid _categoryGuid; + private readonly List _changeListeners; + private protected IVsFontAndColorUtilities? _utilities; + private protected ColorDefinition[]? _colorDefinitions; + + internal BaseFontAndColorCategory(FontDefinition defaultFont) + { + _defaultFont = defaultFont; + _categoryGuid = GetType().GUID; + _changeListeners = new List(); + } + + internal void Initialize(IVsFontAndColorUtilities utilities) + { + _utilities = utilities; + + // The available colors are defined by declaring properties on the derived + // class. Find all `ColorDefinition` properties on this type (we don't + // have any, so the only ones we will find will be on the derived class). + // + // The color definitions are found here instead of in the constructor because we + // should not access the properties on the derived type from our constructor (at + // the point that our constructor runs, the constructor of the derived class has + // not run, so the properties may not have been initialized at that point). + _colorDefinitions ??= GetType() + .GetProperties() + .Where((x) => x.CanRead && x.PropertyType == typeof(ColorDefinition)) + .Select((x) => x.GetValue(this)) + .Cast().ToArray(); + } + + internal IEnumerable GetColorDefinitions() + { + ThrowIfNotInitialized(); + return _colorDefinitions; + } + + internal void RegisterChangeListener(IFontAndColorChangeListener listener) + { + // Lock when accessing the list because we cannot guarantee + // that a set will be unregistered on the main thread. + lock (_changeListeners) + { + _changeListeners.Add(listener); + } + } + + internal void UnregisterChangeListener(IFontAndColorChangeListener listener) + { + // Lock when accessing the list because we cannot guarantee + // that a set will be unregistered on the main thread. + lock (_changeListeners) + { + _changeListeners.Remove(listener); + } + } + + [MemberNotNull(nameof(_colorDefinitions), nameof(_utilities))] + private protected void ThrowIfNotInitialized() + { + if (_utilities is null || _colorDefinitions is null) + { + throw new InvalidOperationException( + $"The font and color category '{GetType().FullName}' has not been initialized." + ); + } + } + + /// + /// The name of the category. This appears in the drop-down on the Fonts and Colors page. + /// + public abstract string Name { get; } + + /// + /// The priority of this category. This determines the order of + /// the category in the drop-down on the Fonts and Colors page. + /// + /// + /// The default priority is 257 which places this category after + /// the Environment category (which has a priority of 256). + /// + public virtual ushort Priority => 257; + + /// + /// The GUID of the category to use as the base category when resetting to defaults. + /// + /// + /// + /// For more information, see . + /// + /// + /// Some known categories can be found in . + /// + /// + public virtual Guid BaseCategory => Guid.Empty; + + /// + /// Visual Studio must be restarted for changes to the fonts or colors to take effect. + /// + /// Corresponds to . + protected virtual bool MustRestart => false; + + /// + /// Restricts the Font drop-down box to only display TrueType fonts. + /// + /// Corresponds to . + protected virtual bool OnlyTrueTypeFonts => true; + + /// + /// Visual Studio will save all customizable Display Item attributes if any of them have been modified. + /// Normally only attributes that have changed from their defaults are saved. + /// + /// Corresponds to . + protected virtual bool SaveAllItemsIfAnyModified => false; + + /// + /// Generates a warning that changes will take effect only for new instance of the UI components that use the font or color. + /// + /// Corresponds to . + protected virtual bool ChangesAffectNewInstancesOnly => false; + + int IVsFontAndColorDefaults.GetFlags(out uint dwFlags) + { + dwFlags = 0; + if (MustRestart) + { + dwFlags |= (uint)__FONTCOLORFLAGS.FCF_MUSTRESTART; + } + if (OnlyTrueTypeFonts) + { + dwFlags |= (uint)__FONTCOLORFLAGS.FCF_ONLYTTFONTS; + } + if (SaveAllItemsIfAnyModified) + { + dwFlags |= (uint)__FONTCOLORFLAGS.FCF_SAVEALL; + } + if (ChangesAffectNewInstancesOnly) + { + dwFlags |= (uint)__FONTCOLORFLAGS.FCF_ONLYNEWINSTANCES; + } + // If the default font is "Automatic", then the `FCF_AUTOFONT` needs + // to be specified so that "Automatic" is listed in the font drop down. + if (string.Equals(_defaultFont.FamilyName, FontDefinition.Automatic.FamilyName)) + { + dwFlags |= (uint)__FONTCOLORFLAGS.FCF_AUTOFONT; + } + return VSConstants.S_OK; + } + + int IVsFontAndColorDefaults.GetPriority(out ushort pPriority) + { + pPriority = Priority; + return VSConstants.S_OK; + } + + int IVsFontAndColorDefaults.GetCategoryName(out string pbstrName) + { + pbstrName = Name; + return VSConstants.S_OK; + } + + int IVsFontAndColorDefaults.GetBaseCategory(out Guid pguidBase) + { + pguidBase = BaseCategory; + return VSConstants.S_OK; + } + + int IVsFontAndColorDefaults.GetFont(FontInfo[] pInfo) + { + pInfo[0] = new FontInfo + { + bstrFaceName = _defaultFont.FamilyName, + wPointSize = _defaultFont.Size, + iCharSet = _defaultFont.CharacterSet, + bFaceNameValid = 1, + bPointSizeValid = 1, + bCharSetValid = 1 + }; + return VSConstants.S_OK; + } + + int IVsFontAndColorDefaults.GetItemCount(out int pcItems) + { + ThrowIfNotInitialized(); + pcItems = _colorDefinitions.Length; + return VSConstants.S_OK; + } + + int IVsFontAndColorDefaults.GetItem(int iItem, AllColorableItemInfo[] pInfo) + { + ThrowIfNotInitialized(); + ThreadHelper.ThrowIfNotOnUIThread(); + + if (iItem >= 0 && iItem < _colorDefinitions.Length) + { + pInfo[0] = _colorDefinitions[iItem].ToAllColorableItemInfo(_utilities); + return VSConstants.S_OK; + } + + return VSConstants.E_FAIL; + } + + int IVsFontAndColorDefaults.GetItemByName(string szItem, AllColorableItemInfo[] pInfo) + { + ThrowIfNotInitialized(); + ThreadHelper.ThrowIfNotOnUIThread(); + + foreach (ColorDefinition definition in _colorDefinitions) + { + if (definition.Name == szItem) + { + pInfo[0] = definition.ToAllColorableItemInfo(_utilities); + return VSConstants.S_OK; + } + } + + return VSConstants.E_FAIL; + } + +#if VS17 + int IVsFontAndColorEvents.OnFontChanged(ref Guid rguidCategory, FontInfo[] pInfo, LOGFONTW[] pLOGFONT, IntPtr HFONT) +#else + int IVsFontAndColorEvents.OnFontChanged(ref Guid rguidCategory, FontInfo[] pInfo, LOGFONTW[] pLOGFONT, uint HFONT) +#endif + { + // We get notified about _all_ font changes, but we + // only want to handle the changes for this category. + if (rguidCategory.Equals(_categoryGuid)) + { + EmitChange((x) => x.SetFont(ref pInfo[0])); + } + return VSConstants.S_OK; + } + + int IVsFontAndColorEvents.OnItemChanged(ref Guid rguidCategory, string szItem, int iItem, ColorableItemInfo[] pInfo, uint crLiteralForeground, uint crLiteralBackground) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + // We get notified about _all_ color changes, but we + // only want to handle the changes for this category. + if (rguidCategory.Equals(_categoryGuid)) + { + // We should have been initialized by this point, but since this + // is an event handler, we won't throw an error if we haven't + // been initialized. We'll just ignore the event instead. + if ((_colorDefinitions is not null) && (_utilities is not null)) + { + // Ignore the change if any part of it is invalid. + if ((pInfo[0].bBackgroundValid != 0) && (pInfo[0].bForegroundValid != 0) && (pInfo[0].bFontFlagsValid != 0)) + { + if ((iItem >= 0) && (iItem < _colorDefinitions.Length)) + { + ColorDefinition definition = _colorDefinitions[iItem]; + + // The `crLiteralBackground` and `crLiteralForeground` parameters + // do not contain the correct color when the color is "Automatic", + // so we need to use the utilities service to get the RGB values. + (uint background, uint foreground) = definition.GetColors( + ref rguidCategory, + ref pInfo[0], + _utilities + ); + + EmitChange( + (x) => x.SetColor(definition, background, foreground, (FontStyle)pInfo[0].dwFontFlags) + ); + } + } + } + } + return VSConstants.S_OK; + } + + int IVsFontAndColorEvents.OnReset(ref Guid rguidCategory) + { + return VSConstants.S_OK; + } + + int IVsFontAndColorEvents.OnResetToBaseCategory(ref Guid rguidCategory) + { + return VSConstants.S_OK; + } + + int IVsFontAndColorEvents.OnApply() + { + return VSConstants.S_OK; + } + + private void EmitChange(Action action) + { + // We can't control what thread a listener is unregistered from, so we + // need to lock around accessing that list. Take a copy so that we don't + // hold the lock while we emit the changes. Taking a copy also prevents + // the collection from being changed while we are iterating through it + // (because a listener could be unregistered in response to the change). + IFontAndColorChangeListener[] listeners; + lock (_changeListeners) + { + listeners = _changeListeners.ToArray(); + } + foreach (IFontAndColorChangeListener listener in listeners) + { + action(listener); + } + } + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/BaseFontAndColorCategory`1.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/BaseFontAndColorCategory`1.cs new file mode 100644 index 0000000..cd8b5db --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/BaseFontAndColorCategory`1.cs @@ -0,0 +1,89 @@ +using System; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// Defines a font and color definitions that are shown on the Fonts and Colors options page. + /// + /// The implementation type itself. + /// + /// + /// Define a color category by inheriting from . The color + /// definitions in the category are defined by declaring properties of type . + /// + /// + /// An extension that defines a font and color category must also have a class inheriting from + /// that will provide the category to Visual Studio. + /// An extension can define multiple font and color categories, but only needs to define one provider. + /// + /// + /// + /// + /// [Guid("e977c587-c06e-xxxx-xxxx-cbf9da1bdafa")] + /// public class MyFirstFontAndColorCategory : BaseFontAndColorCategory<MyFirstFontAndColorCategory> + /// { + /// public MyFirstFontAndColorCategory() : base(new FontDefinition("Times New Roman", 14)) { } + /// + /// public override string Name => "My First Category"; + /// + /// public ColorDefinition Primary { get; } = new + /// "Primary", + /// defaultBackground: VisualStudioColor.Indexed(COLORINDEX.CI_RED), + /// defaultForeground: VisualStudioColor.Indexed(COLORINDEX.CI_WHITE) + /// ); + /// + /// public ColorDefinition Secondary { get; } = new( + /// "Secondary", + /// defaultBackground: VisualStudioColor.Indexed(COLORINDEX.CI_YELLOW), + /// defaultForeground: VisualStudioColor.Indexed(COLORINDEX.CI_BLACK), + /// options: ColorOptions.AllowBackgroundChange | ColorOptions.AllowBoldChange + /// ); + /// } + /// + /// + public abstract class BaseFontAndColorCategory : BaseFontAndColorCategory + where T : BaseFontAndColorCategory, new() + { + private static BaseFontAndColorCategory? _instance; + + /// + /// Initializes a new instance of the class. + /// The default font is set to . + /// + /// + /// Do not create an instance of this class directly. Instead, use the + /// property to access a single instance of the class. + /// + protected BaseFontAndColorCategory() : this(FontDefinition.Automatic) { } + + /// + /// Initializes a new instance of the class + /// with the given default font. + /// + /// This category's default font. + /// + /// Do not create an instance of this class directly. Instead, use the + /// property to access a single instance of the class. + /// + protected BaseFontAndColorCategory(FontDefinition defaultFont) : base(defaultFont) + { + if (_instance is not null) + { + throw new InvalidOperationException( + $"The font and color category '{typeof(T).Name}' has already been created. " + + $"Use the '{nameof(Instance)}' property to access a single instance instead of creating a new instance." + ); + } + + _instance = this; + } + + /// + /// The instance of this class. + /// + /// + /// Always use this single instance instead of creating a new instance of the class. + /// + public static T Instance => (T)(_instance ?? new T()); // Note: The constructor will assign to `_instance`. + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/BaseFontAndColorProvider.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/BaseFontAndColorProvider.cs new file mode 100644 index 0000000..8322299 --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/BaseFontAndColorProvider.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Design; +using System.Linq; +using System.Reflection; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Task = System.Threading.Tasks.Task; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// Defines the font and color categories that an extension provides. + /// + /// + /// + /// The classes inheriting from in an assembly can be provided + /// by defining a class that inherits from in the same assembly. + /// + /// + /// The provider must be declared to Visual Studio using the + /// on your package class, and must be registered when your package is initialized by calling + /// . + /// + /// + /// + /// + /// [Guid("26442428-2cd7-xxxx-xxxx-f9b14087ca50")] + /// public class MyFontAndColorProvider : BaseFontAndColorProvider { } + /// + /// [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] + /// [Guid(PackageGuids.TestExtensionString)] + /// [ProvideFontsAndColors(typeof(MyFontAndColorProvider))] + /// public class MyExtensionPackage : ToolkitPackage + /// { + /// protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress) + /// { + /// await this.RegisterFontAndColorProvidersAsync(); + /// } + /// } + /// + /// + public abstract class BaseFontAndColorProvider : IVsFontAndColorDefaultsProvider + { + private readonly Dictionary _categories = new(); + + /// + /// Initializes a new instance of the class. + /// + protected BaseFontAndColorProvider() { } + + internal async Task InitializeAsync(AsyncPackage package) + { + await FindCategoriesAsync(); + + // Register this provider as a service. Without doing this, + // Visual Studio won't be able to access the categories. + ((IServiceContainer)package).AddService(GetType(), this, true); + } + + private async Task FindCategoriesAsync() + { + IVsFontAndColorUtilities utilities = await VS.GetRequiredServiceAsync(); + + foreach (Type categoryType in GetCategoryTypes(GetType())) + { + // Create the category and initialize it. We need to give the + // category the utilities service because there is no opportunity + // for the category to resolve the service itself asynchronously. + PropertyInfo instance = categoryType.GetProperty("Instance", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); + BaseFontAndColorCategory category = (BaseFontAndColorCategory)instance.GetValue(null); + category.Initialize(utilities); + _categories.Add(categoryType.GUID, category); + } + } + + internal static IEnumerable GetCategoryTypes(Type providerType) + { + // Find all of the categories that are in the same assembly as the provider. + Type baseCategoryType = typeof(BaseFontAndColorCategory<>); + IEnumerable categoryTypes = providerType + .Assembly + .GetTypes() + .Where((x) => !x.IsAbstract && x.IsAssignableToGenericType(baseCategoryType)); + + // Verify that each category has a unique GUID. + HashSet guids = new(); + foreach (Type categoryType in categoryTypes) + { + Guid guid = categoryType.GUID; + if (!guids.Add(guid)) + { + throw new InvalidOperationException( + $"The font and color category '{categoryType.Name}' (GUID '{guid}') has already been defined." + ); + } + } + + return categoryTypes; + } + + int IVsFontAndColorDefaultsProvider.GetObject(ref Guid rguidCategory, out object? ppObj) + { + if (_categories.TryGetValue(rguidCategory, out IVsFontAndColorDefaults category)) + { + ppObj = category; + return VSConstants.S_OK; + } + + ppObj = null; + return VSConstants.E_FAIL; + } + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ColorDefinition.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ColorDefinition.cs new file mode 100644 index 0000000..6eb5ff0 --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ColorDefinition.cs @@ -0,0 +1,173 @@ +using System; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// Defines information about an item that can be changed on the Fonts and Colors page. + /// + /// + /// A is immutable to prevent changes to it after it has been registered with Visual Studio. + /// + /// + /// + /// public ColorDefinition Primary { get; } = new( + /// "Primary" + /// defaultBackground: VisualStudioColor.Indexed(COLORINDEX.CI_RED), + /// defaultForeground: VisualStudioColor.Indexed(COLORINDEX.CI_WHITE) + /// ); + /// + /// + public class ColorDefinition + { + /// + /// The default value of the property. + /// + public static readonly ColorOptions DefaultOptions = + ColorOptions.AllowCustomColors | ColorOptions.AllowBackgroundChange | ColorOptions.AllowForegroundChange; + + /// + /// Initializes a new instance of the class. + /// + /// The name of the color definition. + /// The localized name of the color definition. + /// A description of what the color definition applies to. + /// The line style. + /// The visual style when the color is used in a marker. + /// Determines what the user can customize about the color definition. + /// The font style. + /// The default background color. + /// The default foreground color. + /// The color to use when "Automatic" is selected as the background color. + /// The color to use when "Automatic" is selected as the foreground color. + public ColorDefinition( + string name, + string? localizedName = null, + string? description = null, + VisualStudioColor? defaultBackground = null, + VisualStudioColor? defaultForeground = null, + VisualStudioColor? automaticBackground = null, + VisualStudioColor? automaticForeground = null, + ColorOptions? options = null, + FontStyle fontStyle = FontStyle.None, + LineStyle lineStyle = LineStyle.None, + MarkerVisualStyle markerVisualStyle = MarkerVisualStyle.None + ) + { + Name = name; + LocalizedName = localizedName ?? name; + Description = description ?? ""; + DefaultBackground = defaultBackground ?? VisualStudioColor.Automatic(); + DefaultForeground = defaultForeground ?? VisualStudioColor.Automatic(); + AutomaticBackground = automaticBackground ?? VisualStudioColor.VsColor(__VSSYSCOLOREX.VSCOLOR_ENVIRONMENT_BACKGROUND); + AutomaticForeground = automaticForeground ?? VisualStudioColor.VsColor(__VSSYSCOLOREX.VSCOLOR_PANEL_TEXT); + Options = options ?? DefaultOptions; + FontStyle = fontStyle; + LineStyle = lineStyle; + MarkerVisualStyle = markerVisualStyle; + } + + /// + /// The name of the color definition shown on the Fonts and Colors page. + /// + public string Name { get; } + + /// + /// The localized name of this color definition. + /// + public string LocalizedName { get; } + + /// + /// A description of what this color definition applies to. + /// + public string Description { get; } + + /// + /// The default background color. + /// + public VisualStudioColor DefaultBackground { get; } + + /// + /// The default foreground color. + /// + public VisualStudioColor DefaultForeground { get; } + + /// + /// The color to use when "Automatic" is selected as the background color. + /// + public VisualStudioColor AutomaticBackground { get; } + + /// + /// The color to use when "Automatic" is selected as the foreground color. + /// + public VisualStudioColor AutomaticForeground { get; } + + /// + /// Determines what the user can customize about this color definition. + /// + public ColorOptions Options { get; } + + /// + /// The font style. + /// + public FontStyle FontStyle { get; } + + /// + /// The line style to apply. + /// + public LineStyle LineStyle { get; } + + /// + /// The visual style when the color is used in a marker. + /// + public MarkerVisualStyle MarkerVisualStyle { get; } + + internal AllColorableItemInfo ToAllColorableItemInfo(IVsFontAndColorUtilities utilities) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + return new AllColorableItemInfo + { + bAutoBackgroundValid = 1, + bAutoForegroundValid = 1, + bDescriptionValid = 1, + bFlagsValid = 1, + bLineStyleValid = 1, + bLocalizedNameValid = 1, + bMarkerVisualStyleValid = 1, + bNameValid = 1, + bstrDescription = Description, + bstrLocalizedName = LocalizedName, + bstrName = Name, + crAutoBackground = AutomaticBackground.ToColorRef(utilities), + crAutoForeground = AutomaticForeground.ToColorRef(utilities), + dwMarkerVisualStyle = (uint)MarkerVisualStyle, + eLineStyle = (LINESTYLE)LineStyle, + fFlags = (uint)Options, + Info = new ColorableItemInfo + { + bBackgroundValid = 1, + bFontFlagsValid = 1, + bForegroundValid = 1, + crBackground = DefaultBackground.ToColorRef(utilities), + crForeground = DefaultForeground.ToColorRef(utilities), + dwFontFlags = (uint)FontStyle + } + }; + } + + internal (uint Background, uint Foreground) GetColors(ref Guid category, ref ColorableItemInfo info, IVsFontAndColorUtilities utilities) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + AllColorableItemInfo[] allInfo = new[] { ToAllColorableItemInfo(utilities) }; + allInfo[0].Info = info; + + utilities.GetRGBOfItem(allInfo, ref category, out uint foreground, out uint background); + + return (background, foreground); + } + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ColorOptions.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ColorOptions.cs new file mode 100644 index 0000000..15b0eea --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ColorOptions.cs @@ -0,0 +1,42 @@ +using System; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// Defines what a user can change about a . + /// + /// Equivalent to . + [Flags] + public enum ColorOptions + { + /// + /// No special behavior. + /// + None = 0, + /// + /// Enables the Background Color drop-down box that allows the user to change the background color. + /// + AllowBackgroundChange = __FCITEMFLAGS.FCIF_ALLOWBGCHANGE, + /// + /// Enables the Bold check box that allows the user to change the bold attribute. + /// + AllowBoldChange = __FCITEMFLAGS.FCIF_ALLOWBOLDCHANGE, + /// + /// Enables the Custom buttons that allows the user to create and select customized colors. + /// + AllowCustomColors = __FCITEMFLAGS.FCIF_ALLOWCUSTOMCOLORS, + /// + /// Enables the Foreground Color drop-down box that allows the user to change the foreground color. + /// + AllowForegroundChange = __FCITEMFLAGS.FCIF_ALLOWFGCHANGE, + /// + /// Specifies that the item is a marker type. + /// + IsMarker = __FCITEMFLAGS.FCIF_ISMARKER, + /// + /// Indicates that the Display Items is to be treated as "plain text." This means that the color used to display the item will track the environment wide font and color settings for plain text color. + /// + PlainText = __FCITEMFLAGS.FCIF_PLAINTEXT, + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredColor.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredColor.cs new file mode 100644 index 0000000..d3c8749 --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredColor.cs @@ -0,0 +1,147 @@ +using System.ComponentModel; +using System.Windows; +using System.Windows.Media; +using Microsoft.VisualStudio.PlatformUI; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// Details of a category's color that may have been changed by the user. + /// + /// + /// + /// This object's properties will be updated when the user changes the + /// color settings on the Fonts and Colors options page. + /// + /// + /// This class implements , which allows it to be used in XAML bindings. + /// + /// + public class ConfiguredColor : ObservableObject + { + private Color _backgroundColor; + private Color _foregroundColor; + private FontStyle _fontStyle; + private SolidColorBrush? _backgroundBrush; + private SolidColorBrush? _foregroundBrush; + private FontWeight _fontWeight; + + internal ConfiguredColor(uint background, uint foreground, FontStyle fontStyle) + { + _backgroundColor = ToColor(background); + _foregroundColor = ToColor(foreground); + _fontStyle = fontStyle; + _fontWeight = FontWeight.FromOpenTypeWeight(GetFontWeightValue(fontStyle)); + } + + /// + /// The background color. + /// + public Color BackgroundColor => _backgroundColor; + + /// + /// A brush using + /// + public Brush BackgroundBrush => _backgroundBrush ??= new SolidColorBrush(BackgroundColor); + + /// + /// The foreground color. + /// + public Color ForegroundColor => _foregroundColor; + + /// + /// A brush using . + /// + public Brush ForegroundBrush => _foregroundBrush ??= new SolidColorBrush(ForegroundColor); + + /// + /// The font style. + /// + public FontStyle FontStyle => _fontStyle; + + /// + /// The font weight. + /// + public FontWeight FontWeight => _fontWeight; + + internal bool Update(uint background, uint foreground, FontStyle fontStyle) + { + bool changed = false; + Color oldBackgroundColor = _backgroundColor; + Color oldForegroundColor = _foregroundColor; + FontStyle oldFontStyle = _fontStyle; + FontWeight oldFontWeight = _fontWeight; + + // Update all of the fields first so that + // everything is set before we raise the events. + _backgroundColor = ToColor(background); + _foregroundColor = ToColor(foreground); + _fontStyle = fontStyle; + _fontWeight = FontWeight.FromOpenTypeWeight(GetFontWeightValue(fontStyle)); + + // Clear the brushes and font weight if their underlying value has changed. + bool backgroundChanged = !oldBackgroundColor.Equals(_backgroundColor); + bool foregroundChanged = !oldForegroundColor.Equals(_foregroundColor); + + if (backgroundChanged) + { + _backgroundBrush = null; + } + + if (foregroundChanged) + { + _foregroundBrush = null; + } + + // Now that everything has been set, we can raise the events. + if (backgroundChanged) + { + changed = true; + NotifyPropertyChanged(nameof(BackgroundColor)); + NotifyPropertyChanged(nameof(BackgroundBrush)); + } + + if (foregroundChanged) + { + changed = true; + NotifyPropertyChanged(nameof(ForegroundColor)); + NotifyPropertyChanged(nameof(ForegroundBrush)); + } + + if (oldFontStyle != _fontStyle) + { + changed = true; + NotifyPropertyChanged(nameof(FontStyle)); + } + + if (oldFontWeight.ToOpenTypeWeight() != _fontWeight.ToOpenTypeWeight()) + { + changed = true; + NotifyPropertyChanged(nameof(FontWeight)); + } + + return changed; + } + + private static Color ToColor(uint color) + { + return Color.FromRgb( + (byte)(color & 0xff), + (byte)((color & 0xff00) >> 8), + (byte)((color & 0xff0000) >> 16) + ); + } + + private static int GetFontWeightValue(FontStyle style) + { + if ((style & FontStyle.Bold) == FontStyle.Bold) + { + return 700; + } + else + { + return 400; + } + } + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredColorChangedEventArgs.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredColorChangedEventArgs.cs new file mode 100644 index 0000000..1c2b5c4 --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredColorChangedEventArgs.cs @@ -0,0 +1,32 @@ +using System; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// Arguments for the event that is raised when a configured color changes. + /// + public class ConfiguredColorChangedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the class. + /// + /// The definition of the color that was changed. + /// The color that was changed. + public ConfiguredColorChangedEventArgs(ColorDefinition definition, ConfiguredColor color) + { + Definition = definition; + Color = color; + } + + /// + /// The definition of the color that was changed. + /// + public ColorDefinition Definition { get; } + + /// + /// The color that was changed. + /// + public ConfiguredColor Color { get; } + + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredFont.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredFont.cs new file mode 100644 index 0000000..c8cac9d --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredFont.cs @@ -0,0 +1,110 @@ +using System.ComponentModel; +using System.Windows.Media; +using Microsoft.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// A category's font that may have been changed by the user. + /// + /// + /// + /// This object's properties will be updated when the user changes the + /// font settings on the Fonts and Colors options page. + /// + /// + /// This class implements , which allows it to be used in XAML bindings. + /// + /// + public class ConfiguredFont : ObservableObject + { + private FontFamily? _family; + private bool _hasFamily; + private string _familyName; + private double _size; + private byte _characterSet; + + internal ConfiguredFont(ref FontInfo info) + { + _familyName = info.bstrFaceName; + _size = info.wPointSize; + _characterSet = info.iCharSet; + } + + /// + /// The font family. This will be when the font family name is "Automatic". + /// + public FontFamily? Family + { + get + { + if (!_hasFamily) + { + if (string.Equals(_familyName, FontDefinition.Automatic.FamilyName)) + { + _family = null; + } + else + { + _family = new FontFamily(_familyName); + } + _hasFamily = true; + } + return _family; + } + } + + /// + /// The font family name. + /// + public string FamilyName => _familyName; + + /// + /// The font size. + /// + public double Size => _size; + + /// + /// The character set. + /// + public byte CharacterSet => _characterSet; + + internal bool Update(ref FontInfo info) + { + bool changed = false; + string oldFaceName = _familyName; + double oldSize = _size; + byte oldCharacterSet = _characterSet; + + // Update all of the fields first so that + // everything is set before we raise the events. + _familyName = info.bstrFaceName; + _size = info.wPointSize; + _characterSet = info.iCharSet; + + if (!string.Equals(oldFaceName, _familyName)) + { + changed = true; + _family = null; + _hasFamily = false; + NotifyPropertyChanged(nameof(Family)); + NotifyPropertyChanged(nameof(FamilyName)); + } + + if (oldSize != _size) + { + changed = true; + NotifyPropertyChanged(nameof(Size)); + } + + if (oldCharacterSet != _characterSet) + { + changed = true; + NotifyPropertyChanged(nameof(CharacterSet)); + } + + return changed; + } + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredFontAndColorSet.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredFontAndColorSet.cs new file mode 100644 index 0000000..2b5e3be --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/ConfiguredFontAndColorSet.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// A category's font and colors that may have been changed by the user. + /// + public sealed class ConfiguredFontAndColorSet : IDisposable, IFontAndColorChangeListener where T : BaseFontAndColorCategory, new() + { + private readonly Dictionary _colors; + private readonly Action _onDispose; + + internal ConfiguredFontAndColorSet( + T category, + ref FontInfo font, + Dictionary colors, + Action onDispose + ) + { + Category = category; + Font = new ConfiguredFont(ref font); + _colors = colors; + _onDispose = onDispose; + } + + /// + /// The category that this font and color set is associated with. + /// + public T Category { get; } + + /// + /// The font details. + /// + public ConfiguredFont Font { get; } + + /// + /// Gets the that corresponds to the given . + /// + /// The color definition to get the color for. + /// The configured color. + /// + /// The given definition does not belong to the category that this font and color set is associated with. + /// + public ConfiguredColor GetColor(ColorDefinition definition) + { + if (!_colors.TryGetValue(definition, out ConfiguredColor color)) + { + throw new ArgumentException( + $"The color definition '{definition.Name}' does not belong to the category '{Category.Name}'." + ); + } + return color; + } + + /// + /// Raised when the configured font is changed. + /// + public event EventHandler? FontChanged; + + /// + /// Raised when a configured color is changed. + /// + public event EventHandler? ColorChanged; + + void IFontAndColorChangeListener.SetFont(ref FontInfo info) + { + if (Font.Update(ref info)) + { + FontChanged?.Invoke(this, EventArgs.Empty); + } + } + + void IFontAndColorChangeListener.SetColor(ColorDefinition definition, uint background, uint foreground, FontStyle fontStyle) + { + if (_colors.TryGetValue(definition, out ConfiguredColor? color)) + { + if (color.Update(background, foreground, fontStyle)) + { + ColorChanged?.Invoke(this, new ConfiguredColorChangedEventArgs(definition, color)); + } + } + } + + /// + /// Steps the and objects provided by this class + /// from being updated when the user changes the font or color settings on the Fonts and Colors options page. + /// + public void Dispose() + { + _onDispose.Invoke(this); + } + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/FontDefinition.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/FontDefinition.cs new file mode 100644 index 0000000..7f9725c --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/FontDefinition.cs @@ -0,0 +1,48 @@ +namespace Community.VisualStudio.Toolkit +{ + /// + /// Defines information about a font for a category. + /// + public class FontDefinition + { + /// + /// The "Automatic" font which corresponds to the current "icon" system font setting in Windows. + /// + public static readonly FontDefinition Automatic = new("Automatic", 0); + + /// + /// Initializes a new instance of the class with the default character set. + /// + /// The name of the font family. + /// The point size of the font. + public FontDefinition(string familyName, ushort size) : this(familyName, size, 1 /* DEFAULT_CHARSET */) { } + + /// + /// Initializes a new instance of the class with the specified character set. + /// + /// The name of the font family. + /// The point size of the font. + /// The character set. + public FontDefinition(string familyName, ushort size, byte characterSet) + { + FamilyName = familyName; + Size = size; + CharacterSet = characterSet; + } + + /// + /// The name of the font family. + /// + public string FamilyName { get; } + + /// + /// The point size of the font. + /// + public ushort Size { get; } + + /// + /// The character set. + /// + public byte CharacterSet { get; } + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/FontStyle.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/FontStyle.cs new file mode 100644 index 0000000..a5bb90a --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/FontStyle.cs @@ -0,0 +1,33 @@ +using System; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// Defines font styles. + /// + /// + /// Equivalent to and . + /// + [Flags] + public enum FontStyle + { + /// + /// Plain text. + /// + None = 0, + /// + /// Bold text. + /// + Bold = FONTFLAGS.FF_BOLD, + /// + /// Strikethrough text. + /// + Strikethrough = FONTFLAGS.FF_STRIKETHROUGH, + /// + /// Specifies that the "bold" attribute of this Display Item will be the same as the "bold" attribute of the "plain text" item. + /// + TrackPlaintextBold = __FCFONTFLAGS.FCFF_TRACK_PLAINTEXT_BOLD + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/FontsAndColors.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/FontsAndColors.cs new file mode 100644 index 0000000..f6178d6 --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/FontsAndColors.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Community.VisualStudio.Toolkit +{ + /// Contains helper methods for querying fonts and colors. + public class FontsAndColors + { + private const uint _openCategoryFlags = (uint)( + __FCSTORAGEFLAGS.FCSF_READONLY | + __FCSTORAGEFLAGS.FCSF_NOAUTOCOLORS | + __FCSTORAGEFLAGS.FCSF_LOADDEFAULTS + ); + + internal FontsAndColors() + { } + + /// + /// Gets the configured font and colors for the given category. + /// + /// The type of the category. + /// The configured font and colors. + public async Task> GetConfiguredFontAndColorsAsync() where T : BaseFontAndColorCategory, new() + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + IVsFontAndColorStorage storage = await VS.Services.GetFontAndColorStorageAsync(); + Guid categoryGuid = typeof(T).GUID; + ErrorHandler.ThrowOnFailure(storage.OpenCategory(ref categoryGuid, _openCategoryFlags)); + + try + { + T category = BaseFontAndColorCategory.Instance; + + FontInfo[] fontInfo = new FontInfo[1]; + ErrorHandler.ThrowOnFailure(storage.GetFont(null, fontInfo)); + + ConfiguredFontAndColorSet set = new( + category, + ref fontInfo[0], + await GetColorsAsync(category, categoryGuid, storage), + category.UnregisterChangeListener + ); + + category.RegisterChangeListener(set); + + return set; + } + finally + { + storage.CloseCategory(); + } + } + + private async Task> GetColorsAsync(BaseFontAndColorCategory category, Guid categoryGuid, IVsFontAndColorStorage storage) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + IVsFontAndColorUtilities utilities = await VS.GetRequiredServiceAsync(); + Dictionary colors = new(); + foreach (ColorDefinition definition in category.GetColorDefinitions()) + { + // Get the color info from storage. + ColorableItemInfo[] color = new ColorableItemInfo[1]; + ErrorHandler.ThrowOnFailure(storage.GetItem(definition.Name, color)); + + // Convert the color info to foreground and background colors in RGB format. + (uint background, uint foreground) = definition.GetColors(ref categoryGuid, ref color[0], utilities); + + colors[definition] = new ConfiguredColor(background, foreground, (FontStyle)color[0].dwFontFlags); + } + return colors; + } + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/IFontAndColorChangeListener.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/IFontAndColorChangeListener.cs new file mode 100644 index 0000000..a9fa542 --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/IFontAndColorChangeListener.cs @@ -0,0 +1,11 @@ +using Microsoft.VisualStudio.Shell.Interop; + +namespace Community.VisualStudio.Toolkit +{ + internal interface IFontAndColorChangeListener + { + void SetFont(ref FontInfo info); + + void SetColor(ColorDefinition definition, uint background, uint foreground, FontStyle fontStyle); + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/LineStyle.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/LineStyle.cs new file mode 100644 index 0000000..116c52f --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/LineStyle.cs @@ -0,0 +1,48 @@ +using Microsoft.VisualStudio.TextManager.Interop; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// Line style options. + /// + /// Same as and . + public enum LineStyle + { + /// + /// No line. + /// + None = LINESTYLE.LI_NONE, + /// + /// Solid line. + /// + Solid = LINESTYLE.LI_SOLID, + /// + /// Squiggly line. + /// + Squiggly = LINESTYLE.LI_SQUIGGLY, + /// + /// Hatched pattern. + /// + Hatch = LINESTYLE.LI_HATCH, + /// + /// Fifty percent gray dither (dotted when 1 pixel). + /// + Dotted = LINESTYLE.LI_DOTTED, + /// + /// Smart tag factoid. + /// + SmartTagFactoid = LINESTYLE2.LI_SMARTTAGFACT, + /// + /// Smart tag factoid side. + /// + SmartTagFactoidSide = LINESTYLE2.LI_SMARTTAGFACTSIDE, + /// + /// Smart tag ephemeral. + /// + SmartTagEphemeral = LINESTYLE2.LI_SMARTTAGEPHEM, + /// + /// Smart tag ephemeral side. + /// + SmartTagEphemeralSide = LINESTYLE2.LI_SMARTTAGEPHEMSIDE + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/MarkerVisualStyle.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/MarkerVisualStyle.cs new file mode 100644 index 0000000..8804f44 --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/MarkerVisualStyle.cs @@ -0,0 +1,106 @@ +using System; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// Defines marker visual styles. + /// + /// Equivalent to and . + [Flags] + public enum MarkerVisualStyle + { + /// + /// No style. + /// + None = 0, + /// + /// Indicates that a box is drawn around the marked text. + /// + Border = MARKERVISUAL.MV_BORDER, + /// + /// Indicates that the marked text should always be colored inline. + /// + ColorAlways = MARKERVISUAL.MV_COLOR_ALWAYS, + /// + /// Indicates that the marked text should be colored only if the widget margin is hidden. + /// + ColorLineIfNoMargin = MARKERVISUAL.MV_COLOR_LINE_IF_NO_MARGIN, + /// + /// Indicates that a marker should paint as a solid bar if the text span is of zero length. + /// + ColorSpanIfZeroLength = MARKERVISUAL.MV_COLOR_SPAN_IF_ZERO_LENGTH, + /// + /// Indicates that the body of a marker wants to contribute context. + /// + ContextContributionForBody = MARKERVISUAL.MV_CONTEXT_CONTRIBUTION_FOR_BODY, + /// + /// Indicates that a glyph can take part in drag and drop operations. + /// + DraggableGlyph = MARKERVISUAL.MV_DRAGGABLE_GLYPH, + /// + /// Forces the marker to be invisible. + /// + ForceInvisible = MARKERVISUAL.MV_FORCE_INVISIBLE, + /// + /// Can show a glyph in the widget margin. + /// + Glyph = MARKERVISUAL.MV_GLYPH, + /// + /// Indicates that the client has requested a callback to set the mouse cursor when the user hovers the mouse over the glyph. + /// + GlyphHoverCursor = MARKERVISUAL.MV_GLYPH_HOVER_CURSOR, + /// + /// Marker is only a line adornment and does not otherwise affect coloring. + /// + Line = MARKERVISUAL.MV_LINE, + /// + /// Indicates that a glyph spans multiple lines. + /// + MultilineGlyph = MARKERVISUAL.MV_MULTILINE_GLYPH, + /// + /// Indicates that the glyph lives in the selection margin, not the normal widget margin. + /// + SelectionMarginGlyph = MARKERVISUAL.MV_SEL_MARGIN_GLYPH, + /// + /// Determines whether a tip should be shown for the body of the marker text. + /// + TipForBody = MARKERVISUAL.MV_TIP_FOR_BODY, + /// + /// Determines whether a tip should be shown in the widget margin. + /// + TipForGlyph = MARKERVISUAL.MV_TIP_FOR_GLYPH, + /// + /// Draw foreground text in bold. + /// + BoldText = MARKERVISUAL2.MV_BOLDTEXT, + /// + /// Indicates that the background color is not customizable. + /// + DisallowBacgroundChange = MARKERVISUAL2.MV_DISALLOWBGCHANGE, + /// + /// Indicates that the foreground color is not customizable. + /// + DisallowForegroundChange = MARKERVISUAL2.MV_DISALLOWFGCHANGE, + /// + /// Forces a or marker to paint to the closest viewable location on the line. + /// + ForceClosestIfHidden = MARKERVISUAL2.MV_FORCE_CLOSEST_IF_HIDDEN, + /// + /// Draw a rounded border. + /// + RoundedBorder = MARKERVISUAL2.MV_ROUNDEDBORDER, + /// + /// Forces a or marker to paint a full line even if part of the marker is hidden. + /// + SelectWholeLine = MARKERVISUAL2.MV_SELECT_WHOLE_LINE, + /// + /// Marker for smart tags. + /// + SmartTag = MARKERVISUAL2.MV_SMARTTAG, + /// + /// Marker for change tracking. + /// + Track = MARKERVISUAL2.MV_TRACK + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/VisualStudioColor.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/VisualStudioColor.cs new file mode 100644 index 0000000..95368f5 --- /dev/null +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/FontsAndColors/VisualStudioColor.cs @@ -0,0 +1,142 @@ +using System.Windows.Media; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace Community.VisualStudio.Toolkit +{ + /// + /// Defines a color used by Visual Studio. + /// + public class VisualStudioColor + { + private bool _automatic; + private COLORINDEX? _index; + private Color? _rgb; + private int? _sysColor; + private int? _vsColor; + + private VisualStudioColor() { } + + /// + /// Creates the color for the "Automatic" color. + /// + /// The color for Visual Studio. + public static VisualStudioColor Automatic() + { + return new VisualStudioColor { _automatic = true }; + } + + /// + /// Creates a color from the given predefined color value. + /// + /// The predefined color value. + /// The color for Visual Studio. + public static VisualStudioColor Indexed(COLORINDEX index) + { + return new VisualStudioColor { _index = index }; + } + + /// + /// Creates a color from the given red, green and blue components. + /// + /// The red component. + /// The green component. + /// The blue component. + /// The color for Visual Studio. + public static VisualStudioColor Rgb(byte r, byte g, byte b) + { + return Rgb(Color.FromRgb(r, g, b)); + } + + /// + /// Creates a color from the given object. + /// + /// The color. + /// The color for Visual Studio. + public static VisualStudioColor Rgb(Color color) + { + return new VisualStudioColor { _rgb = color }; + } + + /// + /// Creates a color that is defined by operating system. + /// + /// The identifier of the color. + /// The color for Visual Studio. + public static VisualStudioColor SysColor(int sysColor) + { + return new VisualStudioColor { _sysColor = sysColor }; + } + + /// + /// Creates a color that is defined by Visual Studio. + /// + /// The identifier of the color. + /// The color for Visual Studio. + public static VisualStudioColor VsColor(__VSSYSCOLOREX color) + { + return new VisualStudioColor { _vsColor = (int)color }; + } + + /// + /// Creates a color that is defined by Visual Studio. + /// + /// The identifier of the color. + /// The color for Visual Studio. + public static VisualStudioColor VsColor(__VSSYSCOLOREX2 color) + { + return new VisualStudioColor { _vsColor = (int)color }; + } + + /// + /// Creates a color that is defined by Visual Studio. + /// + /// The identifier of the color. + /// The color for Visual Studio. + public static VisualStudioColor VsColor(__VSSYSCOLOREX3 color) + { + return new VisualStudioColor { _vsColor = (int)color }; + } + + internal uint ToColorRef(IVsFontAndColorUtilities utilities) + { + ThreadHelper.ThrowIfNotOnUIThread(); + + uint result; + + if (_automatic) + { + ErrorHandler.ThrowOnFailure(utilities.EncodeAutomaticColor(out result)); + return result; + } + + if (_index.HasValue) + { + ErrorHandler.ThrowOnFailure(utilities.EncodeIndexedColor(_index.Value, out result)); + return result; + } + + if (_rgb.HasValue) + { + return (uint)(_rgb.Value.R | (_rgb.Value.G << 8) | (_rgb.Value.B << 16)); + } + + if (_sysColor.HasValue) + { + ErrorHandler.ThrowOnFailure(utilities.EncodeSysColor(_sysColor.Value, out result)); + return result; + } + + if (_vsColor.HasValue) + { + ErrorHandler.ThrowOnFailure(utilities.EncodeVSColor(_vsColor.Value, out result)); + return result; + } + + ErrorHandler.ThrowOnFailure(utilities.EncodeInvalidColor(out result)); + return result; + } + } +} diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/VS.cs b/src/toolkit/Community.VisualStudio.Toolkit.Shared/VS.cs index df2feb1..b417dd2 100644 --- a/src/toolkit/Community.VisualStudio.Toolkit.Shared/VS.cs +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/VS.cs @@ -33,6 +33,10 @@ public static class VS /// A collection of events. public static Events Events => _events ??= new(); + private static FontsAndColors? _fontsAndColors; + /// Contains helper methods for querying colors and fonts. + public static FontsAndColors FontsAndColors => _fontsAndColors ??= new(); + private static InfoBarFactory? _infoBarFactory; /// Creates InfoBar controls for use on documents and tool windows. public static InfoBarFactory InfoBar => _infoBarFactory ??= new(); diff --git a/src/toolkit/Community.VisualStudio.Toolkit.Shared/VSSDK.Helpers.Shared.projitems b/src/toolkit/Community.VisualStudio.Toolkit.Shared/VSSDK.Helpers.Shared.projitems index e5c6c98..513f985 100644 --- a/src/toolkit/Community.VisualStudio.Toolkit.Shared/VSSDK.Helpers.Shared.projitems +++ b/src/toolkit/Community.VisualStudio.Toolkit.Shared/VSSDK.Helpers.Shared.projitems @@ -9,21 +9,20 @@ Community.VisualStudio.Toolkit.Shared - - - - - - - + + + + + + @@ -45,7 +44,24 @@ + + + + + + + + + + + + + + + + + @@ -105,8 +121,10 @@ + + From 5c4947a1dbd16a6df5797eefb0c91e3ad766bd79 Mon Sep 17 00:00:00 2001 From: reduckted Date: Thu, 9 Feb 2023 21:43:14 +1000 Subject: [PATCH 2/3] Created a tool window to demonstrate the font and color services. --- .../Commands/FontsAndColorsWindowCommand.cs | 13 ++ .../TestExtensionPackage.cs | 5 + .../DemoFontAndColorCategory.cs | 44 +++++++ .../DemoFontAndColorProvider.cs | 9 ++ .../FontsAndColors/FontsAndColorsWindow.cs | 43 +++++++ .../FontsAndColorsWindowControl.xaml | 111 ++++++++++++++++++ .../FontsAndColorsWindowControl.xaml.cs | 24 ++++ .../FontsAndColorsWindowViewModel.cs | 77 ++++++++++++ demo/VSSDK.TestExtension/VSCommandTable.cs | 1 + demo/VSSDK.TestExtension/VSCommandTable.vsct | 10 ++ .../VSSDK.TestExtension.csproj | 12 ++ 11 files changed, 349 insertions(+) create mode 100644 demo/VSSDK.TestExtension/Commands/FontsAndColorsWindowCommand.cs create mode 100644 demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/DemoFontAndColorCategory.cs create mode 100644 demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/DemoFontAndColorProvider.cs create mode 100644 demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindow.cs create mode 100644 demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindowControl.xaml create mode 100644 demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindowControl.xaml.cs create mode 100644 demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindowViewModel.cs diff --git a/demo/VSSDK.TestExtension/Commands/FontsAndColorsWindowCommand.cs b/demo/VSSDK.TestExtension/Commands/FontsAndColorsWindowCommand.cs new file mode 100644 index 0000000..a215e27 --- /dev/null +++ b/demo/VSSDK.TestExtension/Commands/FontsAndColorsWindowCommand.cs @@ -0,0 +1,13 @@ +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell; +using Task = System.Threading.Tasks.Task; + +namespace TestExtension +{ + [Command(PackageIds.FontsAndColorsWindow)] + internal sealed class FontsAndColorsWindowCommand : BaseCommand + { + protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) => + await FontsAndColorsWindow.ShowAsync(); + } +} diff --git a/demo/VSSDK.TestExtension/TestExtensionPackage.cs b/demo/VSSDK.TestExtension/TestExtensionPackage.cs index 28dd4df..676a7a3 100644 --- a/demo/VSSDK.TestExtension/TestExtensionPackage.cs +++ b/demo/VSSDK.TestExtension/TestExtensionPackage.cs @@ -19,9 +19,11 @@ namespace VSSDK.TestExtension [ProvideToolWindow(typeof(ThemeWindow.Pane))] [ProvideFileIcon(".abc", "KnownMonikers.Reference")] [ProvideToolWindow(typeof(MultiInstanceWindow.Pane))] + [ProvideToolWindow(typeof(FontsAndColorsWindow.Pane))] [ProvideMenuResource("Menus.ctmenu", 1)] [ProvideAutoLoad(VSConstants.UICONTEXT.SolutionExists_string, PackageAutoLoadFlags.BackgroundLoad)] [ProvideService(typeof(RunnerWindowMessenger), IsAsyncQueryable = true)] + [ProvideFontsAndColors(typeof(DemoFontAndColorProvider))] public sealed class TestExtensionPackage : ToolkitPackage { protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) @@ -36,6 +38,9 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke // Commands await this.RegisterCommandsAsync(); + // Fonts and colors. + await this.RegisterFontAndColorProvidersAsync(); + VS.Events.DocumentEvents.AfterDocumentWindowHide += DocumentEvents_AfterDocumentWindowHide; VS.Events.DocumentEvents.Opened += DocumentEvents_Opened; VS.Events.DocumentEvents.Closed += DocumentEvents_Closed; diff --git a/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/DemoFontAndColorCategory.cs b/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/DemoFontAndColorCategory.cs new file mode 100644 index 0000000..4771492 --- /dev/null +++ b/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/DemoFontAndColorCategory.cs @@ -0,0 +1,44 @@ +using System.Runtime.InteropServices; +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace TestExtension +{ + [Guid("bfbcd352-3b43-4034-b951-7ca841a16c81")] + public class DemoFontAndColorCategory : BaseFontAndColorCategory + { + public DemoFontAndColorCategory() : base(new FontDefinition("Consolas", 12)) { } + + public override string Name => "Fonts and Colors Demo"; + + public ColorDefinition TopLeft { get; } = new( + "Top Left", + defaultBackground: VisualStudioColor.Indexed(COLORINDEX.CI_RED), + defaultForeground: VisualStudioColor.Indexed(COLORINDEX.CI_WHITE), + options: ColorDefinition.DefaultOptions | ColorOptions.AllowBoldChange + ); + + public ColorDefinition TopRight { get; } = new( + "Top Right", + defaultBackground: VisualStudioColor.Automatic(), + defaultForeground: VisualStudioColor.Automatic(), + automaticBackground: VisualStudioColor.VsColor(__VSSYSCOLOREX.VSCOLOR_ENVIRONMENT_BACKGROUND), + automaticForeground: VisualStudioColor.VsColor(__VSSYSCOLOREX.VSCOLOR_PANEL_TEXT), + options: ColorDefinition.DefaultOptions | ColorOptions.AllowBoldChange + ); + + public ColorDefinition BottomLeft { get; } = new( + "Bottom Left", + defaultBackground: VisualStudioColor.SysColor(13), + defaultForeground: VisualStudioColor.SysColor(14), + options: ColorOptions.AllowBackgroundChange | ColorOptions.AllowForegroundChange + ); + + public ColorDefinition BottomRight { get; } = new( + "Bottom Right", + defaultBackground: VisualStudioColor.Indexed(COLORINDEX.CI_DARKGREEN), + defaultForeground: VisualStudioColor.Indexed(COLORINDEX.CI_WHITE) + ); + } +} diff --git a/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/DemoFontAndColorProvider.cs b/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/DemoFontAndColorProvider.cs new file mode 100644 index 0000000..c7ca086 --- /dev/null +++ b/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/DemoFontAndColorProvider.cs @@ -0,0 +1,9 @@ +using System; +using System.Runtime.InteropServices; +using Community.VisualStudio.Toolkit; + +namespace TestExtension +{ + [Guid("d2bc5f5f-6c24-4f6c-b6ef-aea775be5fa4")] + public class DemoFontAndColorProvider : BaseFontAndColorProvider { } +} diff --git a/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindow.cs b/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindow.cs new file mode 100644 index 0000000..992662f --- /dev/null +++ b/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindow.cs @@ -0,0 +1,43 @@ +using System; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows; +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.Imaging; + +namespace TestExtension +{ + public class FontsAndColorsWindow : BaseToolWindow + { + public override string GetTitle(int toolWindowId) => "Fonts and Colors Window"; + + public override Type PaneType => typeof(Pane); + + public override async Task CreateAsync(int toolWindowId, CancellationToken cancellationToken) + { + return new FontsAndColorsWindowControl + { + DataContext = new FontsAndColorsWindowViewModel( + await VS.FontsAndColors.GetConfiguredFontAndColorsAsync(), + Package.JoinableTaskFactory + ) + }; + } + + [Guid("b7141d35-7b95-4ad0-a37d-58220c1aa5e3")] + internal class Pane : ToolkitToolWindowPane + { + public Pane() + { + BitmapImageMoniker = KnownMonikers.ColorDialog; + } + + protected override void Dispose(bool disposing) + { + ((Content as FontsAndColorsWindowControl).DataContext as IDisposable)?.Dispose(); + base.Dispose(disposing); + } + } + } +} diff --git a/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindowControl.xaml b/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindowControl.xaml new file mode 100644 index 0000000..63ad8c4 --- /dev/null +++ b/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindowControl.xaml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Top Left + + Default: Red / White + + + + + + Top Right + + Default: Auto / Auto + + + + + + Bottom Left + + Default: Yellow / Black + + + + + + Bottom Right + + Default: Green / White + + + + + + + diff --git a/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindowControl.xaml.cs b/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindowControl.xaml.cs new file mode 100644 index 0000000..43fe08e --- /dev/null +++ b/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindowControl.xaml.cs @@ -0,0 +1,24 @@ +using System.Windows.Controls; +using System.Windows.Documents; +using Community.VisualStudio.Toolkit; + +namespace TestExtension +{ + public partial class FontsAndColorsWindowControl : UserControl + { + public FontsAndColorsWindowControl() + { + InitializeComponent(); + } + + private void ApplyColor(Border border, ConfiguredColor color) + { + // Bind the border's properties to the configured + // color properties. This could also be done in XAML. + border.DataContext = color; + border.SetBinding(BackgroundProperty, nameof(ConfiguredColor.BackgroundBrush)); + border.SetBinding(TextElement.ForegroundProperty, nameof(ConfiguredColor.ForegroundBrush)); + border.SetBinding(TextElement.FontWeightProperty, nameof(ConfiguredColor.FontWeight)); + } + } +} diff --git a/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindowViewModel.cs b/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindowViewModel.cs new file mode 100644 index 0000000..34b2ec8 --- /dev/null +++ b/demo/VSSDK.TestExtension/ToolWindows/FontsAndColors/FontsAndColorsWindowViewModel.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.ObjectModel; +using System.Windows.Input; +using Community.VisualStudio.Toolkit; +using Microsoft.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; + +namespace TestExtension +{ + public class FontsAndColorsWindowViewModel : IDisposable + { + private readonly ConfiguredFontAndColorSet _fontAndColors; + private readonly ObservableCollection _events; + + public FontsAndColorsWindowViewModel(ConfiguredFontAndColorSet fontAndColors, JoinableTaskFactory joinableTaskFactory) + { + // Remember the font and color set so that + // we can dispose of it when we are disposed. + _fontAndColors = fontAndColors; + + EditFontsAndColorsCommand = new DelegateCommand( + () => VS.Commands.ExecuteAsync( + KnownCommands.Tools_Options, + "{57F6B7D2-1436-11D1-883C-0000F87579D2}" + ).FireAndForget(), + () => true, + joinableTaskFactory + ); + + _events = new ObservableCollection(); + Events = new ReadOnlyObservableCollection(_events); + + fontAndColors.FontChanged += OnFontChanged; + fontAndColors.ColorChanged += OnColorChanged; + + TopLeft = fontAndColors.GetColor(fontAndColors.Category.TopLeft); + TopRight = fontAndColors.GetColor(fontAndColors.Category.TopRight); + BottomLeft = fontAndColors.GetColor(fontAndColors.Category.BottomLeft); + BottomRight = fontAndColors.GetColor(fontAndColors.Category.BottomRight); + } + + private void OnFontChanged(object sender, EventArgs e) + { + _events.Insert(0, $"{DateTime.Now}: Font Changed"); + } + + private void OnColorChanged(object sender, ConfiguredColorChangedEventArgs e) + { + _events.Insert(0, $"{DateTime.Now}: Color Changed - {e.Definition.Name}"); + } + + public ICommand EditFontsAndColorsCommand { get; } + + public ReadOnlyObservableCollection Events { get; } + + public ConfiguredFont Font => _fontAndColors.Font; + + public ConfiguredColor TopLeft { get; } + + public ConfiguredColor TopRight { get; } + + public ConfiguredColor BottomLeft { get; } + + public ConfiguredColor BottomRight { get; } + + public void Dispose() + { + _fontAndColors.FontChanged -= OnFontChanged; + _fontAndColors.ColorChanged -= OnColorChanged; + + // Dispose of the font and color set so that it stops + // listening for changes to the font and colors. + _fontAndColors.Dispose(); + } + } +} diff --git a/demo/VSSDK.TestExtension/VSCommandTable.cs b/demo/VSSDK.TestExtension/VSCommandTable.cs index 8d0aa74..021fd7c 100644 --- a/demo/VSSDK.TestExtension/VSCommandTable.cs +++ b/demo/VSSDK.TestExtension/VSCommandTable.cs @@ -42,6 +42,7 @@ internal sealed partial class PackageIds public const int LoadSelectedProject = 0x0112; public const int UnloadSelectedProject = 0x0113; public const int SendMessageToRunnerWindow = 0x0114; + public const int FontsAndColorsWindow = 0x0115; public const int EditProjectFile = 0x2001; public const int RunnerWindowToolbar = 0x0BB8; } diff --git a/demo/VSSDK.TestExtension/VSCommandTable.vsct b/demo/VSSDK.TestExtension/VSCommandTable.vsct index ad28fa9..9cc5283 100644 --- a/demo/VSSDK.TestExtension/VSCommandTable.vsct +++ b/demo/VSSDK.TestExtension/VSCommandTable.vsct @@ -86,6 +86,15 @@ + +