ToEnumerable(this IEnumWindowFrames @enum)
14 | {
15 | ThreadHelper.ThrowIfNotOnUIThread();
16 |
17 | const uint Amount = 1;
18 | var frames = new IVsWindowFrame[Amount];
19 |
20 | while (ErrorHandler.Succeeded(@enum.Next(Amount, frames, out uint fetched)) && fetched == Amount)
21 | yield return frames[0];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml:
--------------------------------------------------------------------------------
1 | name: ⭐ Feature request
2 | description: Suggest an idea
3 | title: "[FEATURE] "
4 | labels: ["enhancement"]
5 | assignees:
6 | - tech5g5g
7 | body:
8 | - type: checkboxes
9 | attributes:
10 | label: "I have checked:"
11 | options:
12 | - label: This is not a duplicate of another request
13 | required: true
14 | - label: This is not an bug/background removal request
15 | required: true
16 | - type: textarea
17 | attributes:
18 | label: "Describe the feature you'd like"
19 | description: A clear and concise description of what you want to happen.
20 | validations:
21 | required: true
22 | - type: textarea
23 | attributes:
24 | label: Is your feature request related to a problem?
25 | description: A clear and concise description of what the problem is.
26 | validations:
27 | required: false
28 | - type: textarea
29 | attributes:
30 | label: Addtional context
31 | description: Any other context about the feature request.
32 | validations:
33 | required: false
34 |
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Ignacy Tomczyk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Resources/license.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2025 Ignacy Tomczyk
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MicaVisualStudio/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using System;
2 | global using System.Linq;
3 | global using System.Threading;
4 | global using System.Diagnostics;
5 | global using System.Collections.Generic;
6 | global using System.Collections.Specialized;
7 | global using System.Collections.ObjectModel;
8 | global using System.Runtime.InteropServices;
9 | global using System.Windows;
10 | global using System.Windows.Media;
11 | global using System.Windows.Interop;
12 | global using System.Windows.Controls;
13 | global using System.Windows.Controls.Primitives;
14 | global using Task = System.Threading.Tasks.Task;
15 | global using Microsoft.VisualStudio;
16 | global using Microsoft.VisualStudio.Threading;
17 | global using Microsoft.VisualStudio.PlatformUI;
18 | global using Microsoft.VisualStudio.Shell;
19 | global using Microsoft.VisualStudio.Shell.Interop;
20 | global using Microsoft.VisualStudio.Imaging;
21 | global using Microsoft.VisualStudio.Imaging.Interop;
22 | global using Community.VisualStudio.Toolkit;
23 | global using MicaVisualStudio;
24 | global using MicaVisualStudio.Interop;
25 | global using MicaVisualStudio.Extensions;
26 | global using MicaVisualStudio.VisualStudio;
27 | global using MicaVisualStudio.Options;
28 | global using MicaVisualStudio.Options.Controls;
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Mica Visual Studio
2 | Enhance your Visual Studio experience with Windows materials
3 |
4 | 
5 |
6 | ## Overview
7 |
8 | With Mica Visual Studio, you can choose between four materials to apply as your window backdrop:
9 |
10 | 1. Mica
11 | 2. Tabbed
12 | 3. Acrylic
13 | 4. (Solid) Glass
14 |
15 | Official documentation of these materials can be found here (excluding Glass): [Materials in Windows (learn.microsoft.com)](https://learn.microsoft.com/en-us/windows/apps/design/signature-experiences/materials)
16 |
17 | To switch which material you use as your backdrop, use the **Tools > Backdrop** submenu.
18 |
19 | ## Compatibility
20 |
21 | Mica Visual Studio is meant to be used with:
22 | * Visual Studio 2026 or later
23 | * Windows 11 or later
24 |
25 | ## Additional information
26 |
27 | Additional information and documentation about Mica Visual Studio and its settings are available on its [wiki](https://github.com/Tech5G5G/Mica-Visual-Studio/wiki).
28 |
29 | If you find a bug with Mica Visual Studio, or have an idea to improve it, please [file an issue](https://github.com/Tech5G5G/Mica-Visual-Studio/issues) using the appropriate template.
30 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("MicaVisualStudio")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("Mica Visual Studio")]
13 | [assembly: AssemblyCopyright("© 2025 Ignacy Tomczyk")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // Version information for an assembly consists of the following four values:
23 | //
24 | // Major Version
25 | // Minor Version
26 | // Build Number
27 | // Revision
28 | //
29 | // You can specify all the values or you can default the Build and Revision Numbers
30 | // by using the '*' as shown below:
31 | // [assembly: AssemblyVersion("1.0.*")]
32 | [assembly: AssemblyVersion("2.1.0.0")]
33 | [assembly: AssemblyFileVersion("2.1.0.0")]
34 |
--------------------------------------------------------------------------------
/THIRD-PARTY-NOTICES:
--------------------------------------------------------------------------------
1 | This project incorporates material from the projects listed below. The original copyright notice and the license are set forth below. Such licenses and notices are provided for informational purposes only.
2 |
3 | ----------------------------------------------------------------------------
4 |
5 | MonoMod by 0x0ade - https://github.com/MonoMod/MonoMod
6 |
7 | The MIT License (MIT)
8 |
9 | Copyright (c) 2015 - 2020 0x0ade
10 |
11 | Permission is hereby granted, free of charge, to any person obtaining a copy
12 | of this software and associated documentation files (the "Software"), to deal
13 | in the Software without restriction, including without limitation the rights
14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 | copies of the Software, and to permit persons to whom the Software is
16 | furnished to do so, subject to the following conditions:
17 |
18 | The above copyright notice and this permission notice shall be included in all
19 | copies or substantial portions of the Software.
20 |
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 | SOFTWARE.
28 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BUG-REPORT.yml:
--------------------------------------------------------------------------------
1 | name: 🪲 Bug report
2 | description: Report bugs and errors
3 | title: "[BUG] "
4 | labels: ["bug"]
5 | assignees:
6 | - tech5g5g
7 | body:
8 | - type: checkboxes
9 | attributes:
10 | label: "I have checked:"
11 | options:
12 | - label: This is not a duplicate of another issue
13 | required: true
14 | - label: This is not an feature/background removal request
15 | required: true
16 | - type: textarea
17 | attributes:
18 | label: What happened?
19 | description: A clear and concise description of what the bug is.
20 | validations:
21 | required: true
22 | - type: textarea
23 | attributes:
24 | label: To reproduce
25 | description: Steps to reproduce the bug.
26 | validations:
27 | required: true
28 | - type: textarea
29 | attributes:
30 | label: Expected behavior
31 | description: A clear and concise description of what you expected to happen.
32 | validations:
33 | required: true
34 | - type: dropdown
35 | attributes:
36 | label: Edition
37 | description: What edition of Visual Studio are you using?
38 | options:
39 | - Community
40 | - Professional
41 | - Enterprise
42 | - Other
43 | validations:
44 | required: true
45 | - type: textarea
46 | attributes:
47 | label: Addtional context
48 | description: Any other context about the problem.
49 | validations:
50 | required: false
51 |
52 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Resources/third-party-notices.txt:
--------------------------------------------------------------------------------
1 | This project incorporates material from the projects listed below. The original copyright notice and the license are set forth below. Such licenses and notices are provided for informational purposes only.
2 |
3 | ----------------------------------------------------------------------------
4 |
5 | MonoMod by 0x0ade - https://github.com/MonoMod/MonoMod
6 |
7 | The MIT License (MIT)
8 |
9 | Copyright (c) 2015 - 2020 0x0ade
10 |
11 | Permission is hereby granted, free of charge, to any person obtaining a copy
12 | of this software and associated documentation files (the "Software"), to deal
13 | in the Software without restriction, including without limitation the rights
14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 | copies of the Software, and to permit persons to whom the Software is
16 | furnished to do so, subject to the following conditions:
17 |
18 | The above copyright notice and this permission notice shall be included in all
19 | copies or substantial portions of the Software.
20 |
21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 | SOFTWARE.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BACKGROUND-REQUEST.yml:
--------------------------------------------------------------------------------
1 | name: 🖼️ Background removal request
2 | description: Report background(s) that need to be removed
3 | title: "[BACKGROUND] "
4 | labels: ["background"]
5 | assignees:
6 | - tech5g5g
7 | body:
8 | - type: checkboxes
9 | attributes:
10 | label: "I have checked:"
11 | options:
12 | - label: This is not a duplicate of another issue
13 | required: true
14 | - label: This is not an bug/feature request
15 | required: true
16 | - type: textarea
17 | attributes:
18 | label: What background?
19 | description: A clear and concise description of which background and where to find it. Be sure to include an image!
20 | validations:
21 | required: true
22 | - type: dropdown
23 | attributes:
24 | label: Expected background
25 | description: What background is expected here.
26 | options:
27 | - Transparent
28 | - Layered (half translucent)
29 | validations:
30 | required: true
31 | - type: dropdown
32 | attributes:
33 | label: Origin
34 | description: Where does the background originate from?
35 | options:
36 | - First-party (Visual Studio)
37 | - Third-party (Extension)
38 | validations:
39 | required: true
40 | - type: input
41 | attributes:
42 | label: Extension name
43 | description: If third-party, name of the extension.
44 | placeholder: "Tech5G5G.MicaVisualStudio"
45 | validations:
46 | required: false
47 | - type: textarea
48 | attributes:
49 | label: Addtional context
50 | description: Any other context about the request or background.
51 | validations:
52 | required: false
53 |
54 |
--------------------------------------------------------------------------------
/MicaVisualStudio/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Mica Visual Studio
6 | Enhance your Visual Studio experience with Windows materials
7 | https://github.com/Tech5G5G/Mica-Visual-Studio
8 | Resources\license.txt
9 | Resources\logo.png
10 | Resources\logo.png
11 | Design, Modern, Theme, UI, Windows, Mica, Tabbed, Acrylic, Glass
12 |
13 |
14 |
17 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Extensions/TreeExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace MicaVisualStudio.Extensions;
2 |
3 | ///
4 | /// Contains extensions for traversing the WPF element tree.
5 | ///
6 | public static class TreeExtensions
7 | {
8 | #region Logical
9 |
10 | ///
11 | /// Gets the logical descendants of using .
12 | ///
13 | /// The parent of the children to get.
14 | /// An of s containing the logical descendants of .
15 | public static IEnumerable LogicalDescendants(this DependencyObject parent)
16 | {
17 | foreach (var child in LogicalTreeHelper.GetChildren(parent).OfType())
18 | {
19 | yield return child;
20 |
21 | foreach (var obj in LogicalDescendants(child))
22 | yield return obj;
23 | }
24 | }
25 |
26 | ///
27 | /// Gets the logical descendants of using .
28 | ///
29 | /// The type of s to filter.
30 | /// The parent of the children to get.
31 | /// An of containing the logical descendants of .
32 | public static IEnumerable LogicalDescendants(this DependencyObject parent) where T : DependencyObject =>
33 | LogicalDescendants(parent).OfType();
34 |
35 | #endregion
36 |
37 | #region Utilities
38 |
39 | ///
40 | /// Finds the first in whose is .
41 | ///
42 | /// The -derived type of the element to find.
43 | /// An of s to filter through.
44 | /// The to check for.
45 | /// The first whose is . If none is found, .
46 | public static T FindElement(this IEnumerable source, string name) where T : FrameworkElement =>
47 | source.FirstOrDefault(i => i is T element && element.Name == name) as T;
48 |
49 | #endregion
50 | }
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Interop/ThemeHelper.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Win32;
2 |
3 | namespace MicaVisualStudio.Interop;
4 |
5 | ///
6 | /// Represents a helper for getting and receiving updates about the system theme.
7 | ///
8 | public sealed class ThemeHelper : IDisposable
9 | {
10 | ///
11 | /// Gets the singleton instance of .
12 | ///
13 | public static ThemeHelper Instance => field ??= new();
14 |
15 | #region PInvoke
16 |
17 | [DllImport("uxtheme.dll", EntryPoint = "#135")]
18 | private static extern int SetPreferredAppMode(PreferredAppMode preferredAppMode);
19 |
20 | [DllImport("uxtheme.dll", EntryPoint = "#136")]
21 | private static extern void FlushMenuThemes();
22 |
23 | private enum PreferredAppMode
24 | {
25 | ForceDark = 2,
26 | ForceLight = 3
27 | }
28 |
29 | #endregion
30 |
31 | #region System Theme
32 |
33 | private const string PersonalizeSettings = @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
34 |
35 | ///
36 | /// Gets the current used by the system.
37 | ///
38 | public Theme SystemTheme => sysTheme;
39 | private Theme sysTheme;
40 |
41 | ///
42 | /// Occurs when has changed.
43 | ///
44 | public event EventHandler SystemThemeChanged;
45 |
46 | private Theme GetSystemTheme() =>
47 | (int)Registry.GetValue(PersonalizeSettings, valueName: "AppsUseLightTheme", defaultValue: 0) == 1 ? //TRUE
48 | Theme.Light : Theme.Dark;
49 |
50 | #endregion
51 |
52 | private ThemeHelper()
53 | {
54 | sysTheme = GetSystemTheme();
55 | SystemEvents.InvokeOnEventsThread(new Action(() => SystemEvents.UserPreferenceChanging += PreferenceChanging));
56 | }
57 |
58 | private void PreferenceChanging(object sender, UserPreferenceChangingEventArgs args) =>
59 | SystemThemeChanged?.Invoke(this, sysTheme = GetSystemTheme());
60 |
61 | ///
62 | /// Sets the theme of the current app.
63 | ///
64 | /// Setting the app theme affects the theme of the system menu.
65 | /// The to set the app theme to.
66 | public void SetAppTheme(Theme theme)
67 | {
68 | SetPreferredAppMode(theme switch
69 | {
70 | Theme.Dark => PreferredAppMode.ForceDark,
71 | Theme.Light or _ => PreferredAppMode.ForceLight
72 | });
73 |
74 | FlushMenuThemes();
75 | }
76 |
77 | #region Dispose
78 |
79 | private bool disposed;
80 |
81 | ///
82 | /// Disposes the singleton instance of .
83 | ///
84 | /// Calling this method will stop the event from occuring.
85 | public void Dispose()
86 | {
87 | if (!disposed)
88 | {
89 | SystemEvents.InvokeOnEventsThread(new Action(() => SystemEvents.UserPreferenceChanging -= PreferenceChanging));
90 | disposed = true;
91 | }
92 | }
93 |
94 | #endregion
95 | }
96 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Options/Controls/GeneralPage.xaml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
49 |
50 |
51 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/MicaVisualStudio.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 18
4 | VisualStudioVersion = 18.0.11217.181
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicaVisualStudio", "MicaVisualStudio\MicaVisualStudio.csproj", "{D4F84E24-0BE1-41E1-A510-68535792520D}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoMod", "MonoMod\MonoMod.csproj", "{4B63AF01-E456-B4C1-2D2D-EE5866DAA548}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Debug|arm64 = Debug|arm64
14 | Debug|x64 = Debug|x64
15 | Debug|x86 = Debug|x86
16 | Release|Any CPU = Release|Any CPU
17 | Release|arm64 = Release|arm64
18 | Release|x64 = Release|x64
19 | Release|x86 = Release|x86
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|arm64.ActiveCfg = Debug|arm64
25 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|arm64.Build.0 = Debug|arm64
26 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|x64.ActiveCfg = Debug|Any CPU
27 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|x64.Build.0 = Debug|Any CPU
28 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|x86.ActiveCfg = Debug|x86
29 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Debug|x86.Build.0 = Debug|x86
30 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|arm64.ActiveCfg = Release|arm64
33 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|arm64.Build.0 = Release|arm64
34 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|x64.ActiveCfg = Release|Any CPU
35 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|x64.Build.0 = Release|Any CPU
36 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|x86.ActiveCfg = Release|x86
37 | {D4F84E24-0BE1-41E1-A510-68535792520D}.Release|x86.Build.0 = Release|x86
38 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
39 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|Any CPU.Build.0 = Debug|Any CPU
40 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|arm64.ActiveCfg = Debug|Any CPU
41 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|arm64.Build.0 = Debug|Any CPU
42 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|x64.ActiveCfg = Debug|Any CPU
43 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|x64.Build.0 = Debug|Any CPU
44 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|x86.ActiveCfg = Debug|Any CPU
45 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Debug|x86.Build.0 = Debug|Any CPU
46 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|Any CPU.ActiveCfg = Release|Any CPU
47 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|Any CPU.Build.0 = Release|Any CPU
48 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|arm64.ActiveCfg = Release|Any CPU
49 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|arm64.Build.0 = Release|Any CPU
50 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|x64.ActiveCfg = Release|Any CPU
51 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|x64.Build.0 = Release|Any CPU
52 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|x86.ActiveCfg = Release|Any CPU
53 | {4B63AF01-E456-B4C1-2D2D-EE5866DAA548}.Release|x86.Build.0 = Release|Any CPU
54 | EndGlobalSection
55 | GlobalSection(SolutionProperties) = preSolution
56 | HideSolutionNode = FALSE
57 | EndGlobalSection
58 | GlobalSection(ExtensibilityGlobals) = postSolution
59 | SolutionGuid = {2289B7CA-7F7F-4A48-9701-F3A803173108}
60 | EndGlobalSection
61 | EndGlobal
62 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Options/Controls/WindowOptions.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
17 |
21 |
22 |
23 |
26 |
27 |
28 |
29 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/MicaVisualStudio/MicaVisualStudioPackage.vsct:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
12 |
13 |
14 |
21 |
22 |
23 |
24 |
27 |
28 |
31 |
32 |
35 |
36 |
37 |
38 |
40 |
41 |
42 |
43 |
44 | None
45 |
46 |
47 |
48 |
49 |
50 |
51 | Mica
52 |
53 |
54 |
55 |
56 |
57 |
58 | Tabbed
59 |
60 |
61 |
62 |
63 |
64 |
65 | Acrylic
66 |
67 |
68 |
69 |
70 |
71 |
72 | Glass
73 |
74 |
75 |
76 |
77 |
78 |
79 | IconIsMoniker
80 |
81 | More options...
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Options/General.cs:
--------------------------------------------------------------------------------
1 | namespace MicaVisualStudio
2 | {
3 | internal partial class OptionsProvider
4 | {
5 | [ComVisible(true)]
6 | public class GeneralOptions : UIElementDialogPage
7 | {
8 | protected override UIElement Child => new GeneralPage();
9 | }
10 |
11 | [ComVisible(true)]
12 | public class ToolOptions : UIElementDialogPage
13 | {
14 | protected override UIElement Child => new ToolPage();
15 | }
16 |
17 | [ComVisible(true)]
18 | public class DialogOptions : UIElementDialogPage
19 | {
20 | protected override UIElement Child => new Options.Controls.DialogPage();
21 | }
22 | }
23 |
24 | ///
25 | /// Represents the general options used by Mica Visual Studio.
26 | ///
27 | public class General : BaseOptionModel
28 | {
29 | ///
30 | /// Gets or sets the used for all windows.
31 | ///
32 | /// Used in .
33 | public int Theme { get; set; } = 0; //Theme.VisualStudio
34 | ///
35 | /// Gets or sets the used for all windows.
36 | ///
37 | /// Used in .
38 | public int Backdrop { get; set; } = 2; //BackdropType.Mica
39 | ///
40 | /// Gets or sets the used for all windows.
41 | ///
42 | /// Used in .
43 | public int CornerPreference { get; set; } = 0; //CornerPreference.Default
44 |
45 | ///
46 | /// Gets or sets whether to use separate options for windows.
47 | ///
48 | public bool ToolWindows { get; set; } = false;
49 | ///
50 | /// Gets or sets the used for windows.
51 | ///
52 | /// Used in .
53 | public int ToolTheme { get; set; } = 0; //Theme.VisualStudio
54 | ///
55 | /// Gets or sets the used for windows.
56 | ///
57 | /// Used in .
58 | public int ToolBackdrop { get; set; } = 2; //BackdropType.Mica
59 | ///
60 | /// Gets or sets the used for windows.
61 | ///
62 | /// Used in .
63 | public int ToolCornerPreference { get; set; } = 0; //CornerPreference.Default
64 |
65 | ///
66 | /// Gets or sets whether to use separate options for windows.
67 | ///
68 | public bool DialogWindows { get; set; } = false;
69 | ///
70 | /// Gets or sets the used for windows.
71 | ///
72 | /// Used in .
73 | public int DialogTheme { get; set; } = 0; //Theme.VisualStudio
74 | ///
75 | /// Gets or sets the used for windows.
76 | ///
77 | /// Used in .
78 | public int DialogBackdrop { get; set; } = 2; //BackdropType.Mica
79 | ///
80 | /// Gets or sets the used for windows.
81 | ///
82 | /// Used in .
83 | public int DialogCornerPreference { get; set; } = 0; //CornerPreference.Default
84 |
85 | ///
86 | /// Gets or sets the used by the app.
87 | ///
88 | /// Used in .
89 | public int AppTheme { get; set; } = 0; //Theme.VisualStudio
90 |
91 | ///
92 | /// Gets or sets whether or not to invoke .
93 | ///
94 | public bool ForceTransparency { get; set; } = true;
95 | ///
96 | /// Gets or sets whether child windows recieve the style via .
97 | ///
98 | /// Used by .
99 | public bool LayeredWindows { get; set; } = true;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Extensions/ExpressionExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Linq.Expressions;
3 | using Expression = System.Linq.Expressions.Expression;
4 |
5 | namespace MicaVisualStudio.Extensions
6 | {
7 | ///
8 | /// Contains extensions for dealing with s.
9 | ///
10 | public static class ExpressionExtensions
11 | {
12 | ///
13 | /// Creates a that represents a type conversion operation to .
14 | ///
15 | /// Equivalent to .
16 | /// An to set the property equal to.
17 | /// A to set the property equal to.
18 | ///
19 | /// A that has the property equal to .
20 | ///
21 | public static UnaryExpression Convert(this Expression expression, Type type) =>
22 | Expression.Convert(expression, type);
23 |
24 | ///
25 | /// Creates a that represents a type conversion operation to .
26 | ///
27 | /// Equivalent to .
28 | /// An to set the property equal to.
29 | /// The type to set the property equal to.
30 | ///
31 | /// A that has the property equal to .
32 | ///
33 | public static UnaryExpression Convert(this Expression expression) =>
34 | expression.Convert(typeof(T));
35 |
36 | ///
37 | /// Creates a that represents a type check of .
38 | ///
39 | /// Equivalent to .
40 | /// An to set the property equal to.
41 | /// A to set the property equal to.
42 | ///
43 | /// A for which the property is equal to .
44 | ///
45 | public static TypeBinaryExpression TypeIs(this Expression expression, Type type) =>
46 | Expression.TypeIs(expression, type);
47 |
48 | ///
49 | /// Creates a that represents a type check of .
50 | ///
51 | /// Equivalent to .
52 | /// The type to set the property equal to.
53 | /// An to set the property equal to.
54 | ///
55 | /// A for which the property is equal to .
56 | ///
57 | public static TypeBinaryExpression TypeIs(this Expression expression) =>
58 | expression.TypeIs(typeof(T));
59 |
60 | ///
61 | /// Creates a that represents accessing a property.
62 | ///
63 | /// Equivalent to .
64 | /// An to set the property equal to.
65 | /// The to set the property equal to.
66 | ///
67 | /// A that has the property equal to .
68 | ///
69 | public static MemberExpression Property(this Expression expression, PropertyInfo property) =>
70 | Expression.Property(expression, property);
71 |
72 | ///
73 | /// Compiles into executable code and produces a that represents it.
74 | ///
75 | /// Equivalent to followed with .
76 | /// The type of the parameter used by .
77 | /// The type of the value returned by .
78 | /// The to compile into executable code.
79 | /// The used by as a parameter.
80 | /// A that represents the compiled .
81 | public static Func Compile(this Expression body, ParameterExpression parameter) =>
82 | Expression.Lambda>(body, parameter).Compile();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Options/BackdropCommands.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.Design;
2 |
3 | namespace MicaVisualStudio
4 | {
5 | ///
6 | /// Command handler
7 | ///
8 | internal sealed class BackdropCommands
9 | {
10 | ///
11 | /// Command ID.
12 | ///
13 | public const int NoneCommandId = 0x0100,
14 | MicaCommandId = 0x0105,
15 | TabbedCommandId = 0x0110,
16 | AcrylicCommandId = 0x0115,
17 | GlassCommandId = 0x0120,
18 | MoreCommandId = 0x0125;
19 |
20 | ///
21 | /// Gets the instance of .
22 | ///
23 | public static BackdropCommands Instance { get; private set; }
24 |
25 | ///
26 | /// Command menu group (command set GUID).
27 | ///
28 | public static readonly Guid CommandSet = new("89c88597-b045-43a5-bc13-8d34202031e8");
29 |
30 | ///
31 | /// that commands are added to.
32 | ///
33 | private readonly OleMenuCommandService commandService =
34 | VS.GetRequiredService();
35 |
36 | ///
37 | /// Gets the service provider from the owner package.
38 | ///
39 | private IAsyncServiceProvider ServiceProvider => package;
40 |
41 | ///
42 | /// that provides this command, not null.
43 | ///
44 | private readonly AsyncPackage package;
45 |
46 | private int selection = MicaCommandId; //Default to Mica
47 |
48 | ///
49 | /// Initializes a new instance of the class.
50 | /// Adds our command handlers for menu (commands must exist in the command table file)
51 | ///
52 | /// Owner package, not null.
53 | /// Command service to add command to, not null.
54 | private BackdropCommands(AsyncPackage package, OleMenuCommandService commandService)
55 | {
56 | this.package = package ?? throw new ArgumentNullException(nameof(package));
57 | this.commandService = commandService;
58 |
59 | UpdateSelection(General.Instance);
60 | General.Saved += UpdateSelection;
61 |
62 | foreach (var id in new int[] { NoneCommandId, MicaCommandId, TabbedCommandId, AcrylicCommandId, GlassCommandId })
63 | RegisterCommand(id);
64 |
65 | //Add additional "More options..." command
66 | commandService.AddCommand(new(
67 | (s, e) => VS.Settings.OpenAsync().Forget(),
68 | new(CommandSet, MoreCommandId)));
69 |
70 | void UpdateSelection(General general) =>
71 | selection = (BackdropType)general.Backdrop switch
72 | {
73 | BackdropType.None => NoneCommandId,
74 | BackdropType.Tabbed => TabbedCommandId,
75 | BackdropType.Acrylic => AcrylicCommandId,
76 | BackdropType.Glass => GlassCommandId,
77 | _ => MicaCommandId
78 | };
79 | }
80 |
81 | ///
82 | /// Initializes the singleton instance of the command.
83 | ///
84 | /// Owner package, not null.
85 | public static async Task InitializeAsync(AsyncPackage package)
86 | {
87 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
88 |
89 | if (await package.GetServiceAsync(typeof(IMenuCommandService)) is OleMenuCommandService commandService)
90 | Instance = new BackdropCommands(
91 | package,
92 | commandService);
93 | }
94 |
95 | private void RegisterCommand(int commandId)
96 | {
97 | CommandID menuCommandID = new(CommandSet, commandId);
98 |
99 | OleMenuCommand menuItem = new(Execute, menuCommandID);
100 | menuItem.BeforeQueryStatus += BeforeQueryStatus;
101 |
102 | commandService.AddCommand(menuItem);
103 | }
104 |
105 | private void BeforeQueryStatus(object sender, EventArgs args)
106 | {
107 | ThreadHelper.ThrowIfNotOnUIThread();
108 |
109 | if (sender is OleMenuCommand command)
110 | command.Checked = selection == command.CommandID.ID;
111 | }
112 |
113 | ///
114 | /// This function is the callback used to execute the command when the menu item is clicked.
115 | /// See the constructor to see how the menu item is associated with this function using
116 | /// OleMenuCommandService service and MenuCommand class.
117 | ///
118 | /// Event sender.
119 | /// Event args.
120 | private void Execute(object sender, EventArgs args)
121 | {
122 | ThreadHelper.ThrowIfNotOnUIThread();
123 |
124 | if ((sender as OleMenuCommand)?.CommandID.ID is not int commandId)
125 | return;
126 |
127 | selection = commandId;
128 |
129 | var general = General.Instance;
130 | general.Backdrop = (int)(commandId switch
131 | {
132 | NoneCommandId => BackdropType.None,
133 | TabbedCommandId => BackdropType.Tabbed,
134 | AcrylicCommandId => BackdropType.Acrylic,
135 | GlassCommandId => BackdropType.Glass,
136 | _ => BackdropType.Mica
137 | });
138 | general.Save();
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/MicaVisualStudio/VisualStudio/WindowObserver.cs:
--------------------------------------------------------------------------------
1 | namespace MicaVisualStudio.VisualStudio;
2 |
3 | ///
4 | /// Represents an observer that listens for opened and closed s.
5 | ///
6 | public sealed class WindowObserver : IDisposable
7 | {
8 | ///
9 | /// Gets the singleton instance of .
10 | ///
11 | public static WindowObserver Instance => field ??= new();
12 |
13 | #region Static Properties
14 |
15 | ///
16 | /// Gets the main window of the current .
17 | ///
18 | public static Window MainWindow => Application.Current.MainWindow;
19 |
20 | ///
21 | /// Gets the current, focused window of the current .
22 | ///
23 | public static Window CurrentWindow
24 | {
25 | get
26 | {
27 | var windows = Application.Current.Windows.OfType();
28 | return windows.FirstOrDefault(i => i.IsActive) ?? windows.FirstOrDefault(i => i.Visibility == Visibility.Visible);
29 | }
30 | }
31 |
32 | ///
33 | /// Gets all of the windows of the current .
34 | ///
35 | public static List AllWindows => [.. Application.Current.Windows.OfType()];
36 |
37 | #endregion
38 |
39 | ///
40 | /// Gets a containing the s found that are still alive.
41 | ///
42 | public ReadOnlyDictionary Windows
43 | {
44 | get
45 | {
46 | CleanHandles();
47 | var sources = PresentationSource.CurrentSources.OfType().ToArray();
48 |
49 | return new(handles.ToDictionary(i => i, i => new WindowInfo(sources.FirstOrDefault(x => x.Handle == i)?.RootVisual as Window)));
50 | }
51 | }
52 |
53 | ///
54 | /// Occurs when a new is opened.
55 | ///
56 | public event WindowChangedEventHandler WindowOpened;
57 | ///
58 | /// Occurs when a is closed.
59 | ///
60 | public event WindowChangedEventHandler WindowClosed;
61 |
62 | private readonly int procId;
63 | private readonly WinEventHook hook;
64 |
65 | private readonly HashSet handles = [];
66 |
67 | private WindowObserver()
68 | {
69 | hook = new(Event.Foreground, EventFlags.OutOfContext, procId = Process.GetCurrentProcess().Id);
70 | hook.EventOccurred += EventOccurred;
71 |
72 | EventManager.RegisterClassHandler(
73 | typeof(Window),
74 | FrameworkElement.LoadedEvent,
75 | new RoutedEventHandler(WindowLoaded));
76 |
77 | EventManager.RegisterClassHandler(
78 | typeof(Window),
79 | FrameworkElement.UnloadedEvent,
80 | new RoutedEventHandler(WindowUnloaded));
81 | }
82 |
83 | private void WindowLoaded(object sender, RoutedEventArgs args)
84 | {
85 | if (sender is not Window window)
86 | return;
87 |
88 | var handle = window.GetHandle();
89 |
90 | handles.Add(handle);
91 | WindowOpened?.Invoke(window, new(handle, window));
92 | }
93 |
94 | private void WindowUnloaded(object sender, RoutedEventArgs args)
95 | {
96 | if (sender is not Window window)
97 | return;
98 |
99 | CleanHandles();
100 | WindowClosed?.Invoke(window, new(IntPtr.Zero, window));
101 | }
102 |
103 | private void EventOccurred(WinEventHook sender, EventOccuredEventArgs args)
104 | {
105 | if (!WindowHelper.GetWindowStyles(args.WindowHandle).HasFlag(WindowStyles.Caption) || //Check window for title bar
106 | handles.Contains(args.WindowHandle)) //Prefer WPF over WinEventHook and avoid duplicates
107 | return;
108 |
109 | handles.Add(args.WindowHandle);
110 |
111 | var window = HwndSource.FromHwnd(args.WindowHandle) is HwndSource source ? source.RootVisual as Window : null;
112 | WindowOpened?.Invoke(window, new(args.WindowHandle, window));
113 | }
114 |
115 | ///
116 | /// Appends to the end of .
117 | ///
118 | /// A to append.
119 | public void AppendWindow(Window window)
120 | {
121 | if (window.IsLoaded)
122 | AppendWindow(window.GetHandle());
123 | }
124 |
125 | ///
126 | /// Appends a to a window to the end of .
127 | ///
128 | /// A handle to a window.
129 | public void AppendWindow(IntPtr handle) =>
130 | handles.Add(handle);
131 |
132 | private void CleanHandles() =>
133 | handles.RemoveWhere(i =>
134 | !WindowHelper.IsAlive(i) || //Check if alive
135 | WindowHelper.GetProcessId(i) != procId); //and belongs to current process
136 |
137 | #region Dispose
138 |
139 | private bool disposed;
140 |
141 | ///
142 | /// Disposes the singleton instance of .
143 | ///
144 | /// Calling this method will stop the and events from occuring.
145 | public void Dispose()
146 | {
147 | if (!disposed)
148 | {
149 | hook.Dispose();
150 | disposed = true;
151 | }
152 | }
153 |
154 | #endregion
155 | }
156 |
157 | ///
158 | /// Represents the handler for the and events.
159 | ///
160 | /// The that generated the event.
161 | /// The to go along with the event.
162 | public delegate void WindowChangedEventHandler(Window sender, WindowActionEventArgs args);
163 |
164 | ///
165 | /// Represents the for the and events.
166 | ///
167 | /// A handle to a window.
168 | /// The that generated the event.
169 | public class WindowActionEventArgs(IntPtr handle, Window window) : EventArgs
170 | {
171 | public IntPtr WindowHandle { get; } = handle;
172 |
173 | public WindowType WindowType { get; } = WindowHelper.GetWindowType(window);
174 | }
175 |
176 | ///
177 | /// Represents information about a specified .
178 | ///
179 | /// A to get information from.
180 | public class WindowInfo(Window window)
181 | {
182 | public Window Window { get; } = window;
183 |
184 | public WindowType Type { get; } = WindowHelper.GetWindowType(window);
185 | }
186 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Options/Controls/WindowOptions.xaml.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace MicaVisualStudio.Options.Controls
4 | {
5 | ///
6 | /// Interaction logic for WindowOptions.xaml
7 | ///
8 | public partial class WindowOptions : GroupBox
9 | {
10 | ///
11 | /// Gets or sets the name of the type of window represented by this .
12 | ///
13 | public string WindowName
14 | {
15 | get => (string)GetValue(WindowNameProperty);
16 | set => SetValue(WindowNameProperty, value);
17 | }
18 | ///
19 | /// Identifies the dependency property.
20 | ///
21 | public static DependencyProperty WindowNameProperty { get; } =
22 | DependencyProperty.Register(nameof(WindowName), typeof(string), typeof(WindowOptions), new(defaultValue: "Windows"));
23 |
24 | ///
25 | /// Gets or sets whether to show a at the top of this for toggling separate options.
26 | ///
27 | public bool ShowCheck
28 | {
29 | get => (bool)GetValue(ShowCheckProperty);
30 | set => SetValue(ShowCheckProperty, value);
31 | }
32 | ///
33 | /// Identifies the dependency property.
34 | ///
35 | public static DependencyProperty ShowCheckProperty { get; } =
36 | DependencyProperty.Register(nameof(ShowCheck), typeof(bool), typeof(WindowOptions), new(defaultValue: true));
37 |
38 | ///
39 | /// Gets or sets the name of the property to modify in the specified model when setting the backdrop type.
40 | ///
41 | public string BackdropSource { get; set; }
42 |
43 | ///
44 | /// Gets or sets the name of the property to modify in the specified model when setting the corner preference.
45 | ///
46 | public string CornerPreferenceSource { get; set; }
47 |
48 | ///
49 | /// Gets or sets the name of the property to modify in the specified model when setting the theme.
50 | ///
51 | public string ThemeSource { get; set; }
52 |
53 | ///
54 | /// Gets or sets the name of the property to modify in the specified model when enabling or disabling separate options.
55 | ///
56 | public string EnabledSource { get; set; }
57 |
58 | private bool optionModelSet = false;
59 |
60 | ///
61 | /// Initializes a new instance of .
62 | ///
63 | public WindowOptions()
64 | {
65 | InitializeComponent();
66 | }
67 |
68 | protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
69 | {
70 | base.OnPropertyChanged(e);
71 |
72 | if (e.Property == WindowNameProperty)
73 | enabled.Content = $"Enable separate options for {WindowName.ToLower()}";
74 | else if (e.Property == ShowCheckProperty && !ShowCheck)
75 | enabled.IsChecked = true;
76 | }
77 |
78 | ///
79 | /// Specifies the to modify when changing settings.
80 | ///
81 | /// The type of .
82 | /// A whose properties are to be modified.
83 | public void SetOptionModel(T model)
84 | where T : BaseOptionModel, new()
85 | {
86 | if (optionModelSet)
87 | return;
88 |
89 | optionModelSet = true;
90 |
91 | InitializeComboBox(backdrop, BackdropSource, model);
92 | InitializeComboBox(cornerPreference, CornerPreferenceSource, model);
93 | InitializeComboBox(theme, ThemeSource, model);
94 |
95 | InitializeCheckBox(enabled, EnabledSource, model);
96 | }
97 |
98 | ///
99 | /// Binds 's to the specified on .
100 | ///
101 | /// The type of .
102 | /// A to bind.
103 | /// The name of the property to bind to.
104 | /// A to use as the binding source.
105 | public static void InitializeComboBox(ComboBox box, string propertyName, T model)
106 | where T : BaseOptionModel, new()
107 | {
108 | if (propertyName is null)
109 | return;
110 |
111 | Type type = typeof(T);
112 | var instanceProp = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);
113 | var prop = type.GetProperty(propertyName);
114 |
115 | UpdateIndex();
116 | box.SelectionChanged += SelectionChanged;
117 |
118 | type.GetEvent("Saved", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy)
119 | .AddEventHandler(target: null, new Action(m =>
120 | {
121 | box.SelectionChanged -= SelectionChanged;
122 | UpdateIndex();
123 | box.SelectionChanged += SelectionChanged;
124 | }));
125 |
126 | void UpdateIndex() => box.SelectedIndex = (int)prop.GetValue(model);
127 |
128 | void SelectionChanged(object sender, RoutedEventArgs args)
129 | {
130 | var instance = (T)instanceProp.GetValue(obj: null);
131 | prop.SetValue(instance, box.SelectedIndex);
132 | instance.Save();
133 | }
134 | }
135 |
136 | ///
137 | /// Binds 's to the specified on .
138 | ///
139 | /// The type of .
140 | /// A to bind.
141 | /// The name of the property to bind to.
142 | /// A to use as the binding source.
143 | public static void InitializeCheckBox(CheckBox box, string propertyName, T model)
144 | where T : BaseOptionModel, new()
145 | {
146 | if (propertyName is null)
147 | return;
148 |
149 | Type type = typeof(T);
150 | var instanceProp = type.GetProperty("Instance", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy);
151 | var prop = type.GetProperty(propertyName);
152 |
153 | box.IsChecked = (bool)prop.GetValue(model);
154 | box.Click += (s, e) =>
155 | {
156 | var instance = (T)instanceProp.GetValue(null);
157 | prop.SetValue(instance, box.IsChecked);
158 | instance.Save();
159 | };
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Interop/WinEventHook.cs:
--------------------------------------------------------------------------------
1 | namespace MicaVisualStudio.Interop;
2 |
3 | ///
4 | /// Represents an event hook for Windows events.
5 | ///
6 | public class WinEventHook : IDisposable
7 | {
8 | #region PInvoke
9 |
10 | [DllImport("user32.dll")]
11 | private static extern IntPtr SetWinEventHook(
12 | uint eventMin,
13 | uint eventMax,
14 | IntPtr hmodWinEventProc,
15 | WinEventDelegate pfnWinEventProc,
16 | uint idProcess,
17 | uint idThread,
18 | uint dwFlags);
19 |
20 | [DllImport("user32.dll")]
21 | private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
22 |
23 | [DllImport("user32.dll")]
24 | private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
25 |
26 | public const uint WINEVENT_OUTOFCONTEXT = 0;
27 |
28 | public const uint EVENT_OBJECT_SHOW = 0x8002,
29 | EVENT_OBJECT_CREATE = 0x8000,
30 | EVENT_OBJECT_DESTROY = 0x8001;
31 |
32 | private delegate void WinEventDelegate(
33 | IntPtr hWinEventHook,
34 | int eventConst,
35 | IntPtr hWnd,
36 | int idObject,
37 | int idChild,
38 | int idEventThread,
39 | int dwmsEventTime);
40 |
41 | #endregion
42 |
43 | ///
44 | /// Occurs when the specified occurs.
45 | ///
46 | public event EventOccuredEventHandler EventOccurred;
47 |
48 | private readonly IntPtr hookId;
49 | private readonly WinEventDelegate hook;
50 |
51 | ///
52 | /// Initializes a new instance of .
53 | ///
54 | /// The to look out for.
55 | /// The location of this and the sources to be skipped.
56 | /// The ID of the process to watch. Set to 0 to watch all processes.
57 | public WinEventHook(Event winEvent, EventFlags flags, int pid) =>
58 | hookId = SetWinEventHook((uint)winEvent, (uint)winEvent, IntPtr.Zero, hook = Procedure, (uint)pid, idThread: 0, (uint)flags);
59 |
60 | private void Procedure(IntPtr hWinEventHook, int eventConst, IntPtr hWnd, int idObject, int idChild, int idEventThread, int dwmsEventTime) =>
61 | EventOccurred?.Invoke(this, new(eventConst, hWnd, idObject, idChild, dwmsEventTime));
62 |
63 | #region Dispose
64 |
65 | private bool disposed;
66 |
67 | ~WinEventHook() => Dispose(disposing: false);
68 |
69 | ///
70 | /// Disposes this instance of , unregistering the associated hook.
71 | ///
72 | public void Dispose()
73 | {
74 | Dispose(disposing: true);
75 | GC.SuppressFinalize(this);
76 | }
77 |
78 | ///
79 | /// Disposes this instance of .
80 | ///
81 | /// Whether to release managed resources.
82 | protected virtual void Dispose(bool disposing)
83 | {
84 | if (!disposed)
85 | {
86 | UnhookWinEvent(hookId);
87 | disposed = true;
88 | }
89 | }
90 |
91 | #endregion
92 | }
93 |
94 | ///
95 | /// Specifies the event used by .
96 | ///
97 | public enum Event
98 | {
99 | ///
100 | /// Specifies to listen to foreground window changes.
101 | ///
102 | /// Equivalent to EVENT_SYSTEM_FOREGROUND.
103 | Foreground = 0x0003,
104 |
105 | ///
106 | /// Specifies to listen to the creation of objects.
107 | ///
108 | /// Equivalent to EVENT_OBJECT_CREATE.
109 | Create = 0x8000,
110 |
111 | ///
112 | /// Specifies to listen to the destruction of objects.
113 | ///
114 | /// Equivalent to EVENT_OBJECT_DESTROY.
115 | Destroy = 0x8001,
116 |
117 | ///
118 | /// Specifies to listen to hidden objects being shown.
119 | ///
120 | /// Equivalent to EVENT_OBJECT_SHOW.
121 | Show = 0x8002
122 | }
123 |
124 | ///
125 | /// Specifies the location of a hook and the sources to be skipped.
126 | ///
127 | [Flags]
128 | public enum EventFlags
129 | {
130 | ///
131 | /// Specifies to place the hook outside of the process of the hookee. This is the default.
132 | ///
133 | /// Equivalent to WINEVENT_INCONTEXT.
134 | OutOfContext = 0x0000,
135 |
136 | ///
137 | /// Specifies to skip events that occur in the same thread as the thread registering the hook.
138 | ///
139 | /// Equivalent to WINEVENT_SKIPOWNTHREAD.
140 | SkipOwnThread = 0x0001,
141 |
142 | ///
143 | /// Specifies to skip events that occur in the same process as the process registering the hook.
144 | ///
145 | /// Equivalent to WINEVENT_SKIPOWNPROCESS.
146 | SkipOwnProcess = 0x0002,
147 |
148 | ///
149 | /// Specifies to place the hook inside the process of the hookee.
150 | ///
151 | /// Equivalent to WINEVENT_OUTOFCONTEXT.
152 | InContext = 0x0004,
153 | }
154 |
155 | ///
156 | /// Represents the for the event.
157 | ///
158 | /// An representation of the that occured.
159 | /// The handle to the window that generated the .
160 | /// The ID of the object that generated the .
161 | /// The ID of the child that generated the .
162 | /// The amount of time, in milliseconds, that the was generated in.
163 | public class EventOccuredEventArgs(int eventConst, IntPtr hWnd, int idObject, int idChild, int dwmsEventTime) : EventArgs
164 | {
165 | private const int CHILDID_SELF = 0;
166 |
167 | ///
168 | /// The that occured.
169 | ///
170 | public Event Event { get; } = (Event)eventConst;
171 |
172 | ///
173 | /// The handle to the window associated with the event.
174 | ///
175 | public IntPtr WindowHandle { get; } = hWnd;
176 |
177 | ///
178 | /// The ID of the object associated with the event.
179 | ///
180 | public int ObjectId { get; } = idObject;
181 |
182 | ///
183 | /// Whether the event was triggered by a child.
184 | ///
185 | public bool TriggeredByChild { get; } = idChild != CHILDID_SELF;
186 |
187 | ///
188 | /// The amount of time, in milliseconds, which it took for the to be generated.
189 | ///
190 | public int EventTime { get; } = dwmsEventTime;
191 | }
192 |
193 | ///
194 | /// Represents the handler for the event.
195 | ///
196 | /// The that generated the event.
197 | /// The to go along with the event.
198 | public delegate void EventOccuredEventHandler(WinEventHook sender, EventOccuredEventArgs args);
199 |
--------------------------------------------------------------------------------
/MicaVisualStudio/MicaVisualStudio.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 17.0
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 |
7 |
8 |
9 | Debug
10 | AnyCPU
11 | 2.0
12 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
13 | {D4F84E24-0BE1-41E1-A510-68535792520D}
14 | Library
15 | Properties
16 | MicaVisualStudio
17 | MicaVisualStudio
18 | v4.8
19 | true
20 | true
21 | true
22 | false
23 | false
24 | true
25 | true
26 | Program
27 | $(DevEnvDir)devenv.exe
28 | /rootsuffix Exp
29 | 14
30 |
31 |
32 | true
33 | full
34 | false
35 | bin\Debug\
36 | DEBUG;TRACE
37 | prompt
38 | 4
39 |
40 |
41 | pdbonly
42 | true
43 | bin\Release\
44 | TRACE
45 | prompt
46 | 4
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | DialogPage.xaml
62 |
63 |
64 |
65 | GeneralPage.xaml
66 |
67 |
68 | ToolPage.xaml
69 |
70 |
71 | WindowOptions.xaml
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | Menus.ctmenu
81 |
82 |
83 | Always
84 | true
85 |
86 |
87 | Always
88 | true
89 |
90 |
91 | Always
92 | true
93 |
94 |
95 | Designer
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | 0.11.6
115 |
116 |
117 | 17.0.545
118 |
119 |
120 | compile; build; native; contentfiles; analyzers; buildtransitive
121 |
122 |
123 | runtime; build; native; contentfiles; analyzers; buildtransitive
124 | all
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | Designer
134 | MSBuild:Compile
135 |
136 |
137 | Designer
138 | MSBuild:Compile
139 |
140 |
141 | Designer
142 | MSBuild:Compile
143 |
144 |
145 | Designer
146 | MSBuild:Compile
147 |
148 |
149 |
150 |
151 |
158 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Extensions/WeakEventExtensions.cs:
--------------------------------------------------------------------------------
1 | using System.Windows.Data;
2 |
3 | namespace MicaVisualStudio.Extensions;
4 |
5 | ///
6 | /// Represents a notifier that observes a for changes from a specificed source.
7 | ///
8 | public class PropertyChangeNotifier : DependencyObject, IDisposable
9 | {
10 | private readonly WeakReference propertySource;
11 |
12 | ///
13 | /// Initializes a new instance of .
14 | ///
15 | /// The source from which property changes occur.
16 | /// A path to the property to observe.
17 | public PropertyChangeNotifier(DependencyObject source, string path) :
18 | this(source, new PropertyPath(path)) { }
19 |
20 | ///
21 | /// Initializes a new instance of .
22 | ///
23 | /// The source from which property changes occur.
24 | /// The to observe.
25 | public PropertyChangeNotifier(DependencyObject source, DependencyProperty property) :
26 | this(source, new PropertyPath(property)) { }
27 |
28 | ///
29 | /// Initializes a new instance of .
30 | ///
31 | /// The source from which property changes occur.
32 | /// A path to the property to observe.
33 | public PropertyChangeNotifier(DependencyObject source, PropertyPath path)
34 | {
35 | propertySource = new(source);
36 |
37 | BindingOperations.SetBinding(this, ValueProperty, new Binding
38 | {
39 | Path = path,
40 | Mode = BindingMode.OneWay,
41 | Source = source
42 | });
43 | }
44 |
45 | ///
46 | /// Gets the source from which property changes occur.
47 | ///
48 | ///
49 | /// If the source is unavailable, this returns .
50 | ///
51 | public DependencyObject PropertySource =>
52 | propertySource.TryGetTarget(out DependencyObject source) ? source : null;
53 |
54 | ///
55 | /// Gets or sets the value of the property.
56 | ///
57 | public object Value
58 | {
59 | get => GetValue(ValueProperty);
60 | set => SetValue(ValueProperty, value);
61 | }
62 |
63 | ///
64 | /// Identifies the dependency property.
65 | ///
66 | public static readonly DependencyProperty ValueProperty =
67 | DependencyProperty.Register("Value", typeof(object), typeof(PropertyChangeNotifier), new(null, new(OnValueChanged)));
68 |
69 | ///
70 | /// Occurs when the value of the specified has changed.
71 | ///
72 | public event EventHandler ValueChanged;
73 |
74 | private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
75 | (d as PropertyChangeNotifier)?.ValueChanged?.Invoke(d, EventArgs.Empty);
76 |
77 | ///
78 | /// Clears the internal binding used by this .
79 | ///
80 | public void Dispose() =>
81 | BindingOperations.ClearBinding(this, ValueProperty);
82 | }
83 |
84 | ///
85 | /// Contains extensions for adding weak handlers to s and property changes.
86 | ///
87 | public static class WeakEventExtensions
88 | {
89 | #region Events
90 |
91 | ///
92 | /// Adds the specified to the specified using .
93 | ///
94 | /// The -derived type of .
95 | /// The source to attach the to.
96 | /// The to attach the to.
97 | /// The that recieves events.
98 | public static void AddWeakHandler(this T source, RoutedEvent routedEvent, RoutedEventHandler handler) where T : FrameworkElement =>
99 | WeakEventManager.AddHandler(source, routedEvent.Name, (s, e) => handler(s, e));
100 |
101 |
102 | ///
103 | /// Adds the specified to the specified using .
104 | ///
105 | /// Once the is invoked, it is removed from the specified .
106 | /// The -derived type of .
107 | /// The source to attach the to.
108 | /// The to attach the to.
109 | /// The that recieves events.
110 | public static void AddWeakOneTimeHandler(this T source, RoutedEvent routedEvent, RoutedEventHandler handler) where T : FrameworkElement
111 | {
112 | WeakEventManager.AddHandler(source, routedEvent.Name, Handler);
113 |
114 | void Handler(object sender, RoutedEventArgs args)
115 | {
116 | handler(sender, args);
117 | WeakEventManager.RemoveHandler(source, routedEvent.Name, Handler);
118 | }
119 | }
120 |
121 | #endregion
122 |
123 | #region Properties
124 |
125 | ///
126 | /// Adds a to the specified .
127 | ///
128 | /// The from which property changes occur.
129 | /// The observed by .
130 | /// The to invoke once a property change occurs.
131 | public static void AddWeakPropertyChangeHandler(this DependencyObject source, DependencyProperty property, EventHandler handler)
132 | {
133 | PropertyChangeNotifier notifier = new(source, property);
134 | notifier.ValueChanged += (s, e) => handler((s as PropertyChangeNotifier).PropertySource, EventArgs.Empty);
135 | }
136 |
137 | ///
138 | /// Adds a to the specified .
139 | ///
140 | /// Once the is invoked, the is disposed.
141 | /// The from which property changes occur.
142 | /// The observed by .
143 | /// The to invoke once a property change occurs.
144 | public static void AddWeakOneTimePropertyChangeHandler(this DependencyObject source, DependencyProperty property, EventHandler handler)
145 | {
146 | PropertyChangeNotifier notifier = new(source, property);
147 | notifier.ValueChanged += ValueChanged;
148 |
149 | void ValueChanged(object sender, EventArgs args)
150 | {
151 | if (sender is PropertyChangeNotifier notifier)
152 | {
153 | handler(notifier.PropertySource, EventArgs.Empty);
154 | notifier.Dispose();
155 | }
156 | }
157 | }
158 |
159 | #endregion
160 | }
161 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
364 | /desktop.ini
365 |
--------------------------------------------------------------------------------
/MicaVisualStudio/VisualStudio/VsColorManager.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using Microsoft.Internal.VisualStudio.PlatformUI;
3 |
4 | namespace MicaVisualStudio.VisualStudio;
5 |
6 | ///
7 | /// Represent a manager for the various s and es used by Visual Studio.
8 | ///
9 | public sealed class VsColorManager
10 | {
11 | ///
12 | /// Gets the singleton instance of .
13 | ///
14 | public static VsColorManager Instance => field ??= new();
15 |
16 | private static Color TransparentWhite = Colors.Transparent,
17 | TranslucentBlack = Color.FromArgb(0x01, 0x00, 0x00, 0x00); //Slight alpha to change icon foreground (basically invisible)
18 |
19 | ///
20 | /// Gets a containing all the s currently in effect.
21 | ///
22 | public ReadOnlyDictionary Configs => new(configs);
23 | private readonly Dictionary configs = [];
24 |
25 | #region Visual Studio Theme
26 |
27 | ///
28 | /// Gets the current used by Visual Studio.
29 | ///
30 | public Theme VisualStudioTheme => vsTheme;
31 | private Theme vsTheme;
32 |
33 | ///
34 | /// Occurs when has changed.
35 | ///
36 | public event EventHandler VisualStudioThemeChanged;
37 |
38 | private readonly ThemeResourceKey MainWindowActiveCaptionKey =
39 | new(category: new("624ed9c3-bdfd-41fa-96c3-7c824ea32e3d"), name: "MainWindowActiveCaption", ThemeResourceKeyType.BackgroundColor);
40 |
41 | private readonly IVsUIShell5 shell = VS.GetRequiredService();
42 |
43 | ///
44 | /// Gets the current of Visual Studio.
45 | ///
46 | /// The used by the Visual Studio.
47 | /// Whether or not the value has changed; that is, if is different from .
48 | private bool GetVSTheme(out Theme theme)
49 | {
50 | theme = shell?.GetThemedWPFColor(MainWindowActiveCaptionKey).IsLight() == true ? Theme.Light : Theme.Dark;
51 | return vsTheme != theme;
52 | }
53 |
54 | #endregion
55 |
56 | private VsColorManager()
57 | {
58 | GetVSTheme(out vsTheme);
59 | (Application.Current.Resources.MergedDictionaries as INotifyCollectionChanged).CollectionChanged += (s, e) =>
60 | {
61 | UpdateColors();
62 |
63 | if (GetVSTheme(out Theme theme))
64 | VisualStudioThemeChanged?.Invoke(this, vsTheme = theme);
65 | };
66 | }
67 |
68 | ///
69 | /// Updates the s and es used by Visual Studio.
70 | ///
71 | public void UpdateColors()
72 | {
73 | foreach (var dictionary in Application.Current.Resources.MergedDictionaries.OfType())
74 | foreach (var obj in dictionary)
75 | {
76 | DictionaryEntry entry = (DictionaryEntry)obj;
77 |
78 | if (GetConfig(entry.Key) is not ColorConfig config)
79 | continue;
80 |
81 | if (entry.Value is Brush brush)
82 | {
83 | Brush clone = brush.CloneCurrentValue();
84 |
85 | if (clone is SolidColorBrush solid)
86 | solid.Color = DetermineColor(solid.Color, config);
87 | else if (clone is GradientBrush gradient)
88 | foreach (var stop in gradient.GradientStops)
89 | stop.Color = DetermineColor(stop.Color, config);
90 |
91 | dictionary[entry.Key] = clone;
92 | }
93 | else if (entry.Value is Color color)
94 | dictionary[entry.Key] = DetermineColor(color, config);
95 | }
96 | }
97 |
98 | #region Configuration
99 |
100 | ///
101 | /// Adds to .
102 | ///
103 | /// A key to configure.
104 | /// A containing configuration information.
105 | public void AddConfig(string key, ColorConfig config)
106 | {
107 | if (configs.ContainsKey(key))
108 | configs[key] = config;
109 | else
110 | configs.Add(key, config);
111 | }
112 |
113 | ///
114 | /// Adds multiple to .
115 | ///
116 | /// A containing keys and their .
117 | public void AddConfigs(Dictionary configs)
118 | {
119 | foreach (var config in configs)
120 | AddConfig(config.Key, config.Value);
121 | }
122 |
123 | ///
124 | /// Removes a from .
125 | ///
126 | /// The key for which the is to be removed for.
127 | public void RemoveConfig(string key)
128 | {
129 | if (configs.ContainsKey(key))
130 | configs.Remove(key);
131 | }
132 |
133 | ///
134 | /// Removes multiple s from .
135 | ///
136 | /// The keys for which the s are to be removed for.
137 | public void RemoveConfigs(params string[] keys)
138 | {
139 | foreach (var key in keys)
140 | RemoveConfig(key);
141 | }
142 |
143 | private ColorConfig GetConfig(object key)
144 | {
145 | if (key is ThemeResourceKey theme &&
146 | (theme.KeyType == ThemeResourceKeyType.BackgroundColor || theme.KeyType == ThemeResourceKeyType.BackgroundBrush))
147 | return configs.TryGetValue(theme.Name, out ColorConfig config) ? config : null;
148 | else if (key is string str)
149 | return configs.TryGetValue(str.Replace("VsBrush.", null).Replace("VsColor.", null), out ColorConfig config) ? config : null;
150 | else
151 | return null;
152 | }
153 |
154 | #endregion
155 |
156 | private Color DetermineColor(Color color, ColorConfig config) =>
157 | !config.IsTranslucent || (config.TransparentOnGray && color.R == color.G && color.G == color.B) ?
158 | (vsTheme == Theme.Light ? TransparentWhite : TranslucentBlack) :
159 | Color.FromArgb(Math.Min(config.Opacity, color.A), color.R, color.G, color.B);
160 | }
161 |
162 | ///
163 | /// Represents the configuration for a or used by Visual Studio.
164 | ///
165 | /// Whether or not to make the or completely transparent if it is gray.
166 | /// Whether or not to allow .
167 | /// The opacity to make the or .
168 | public class ColorConfig(bool transparentOnGray = true, bool translucent = false, byte opacity = 0x38)
169 | {
170 | ///
171 | /// Represents the default for a or .
172 | ///
173 | /// Equivalent to a initialized to its default value; that is, with no parameters modified.
174 | public static readonly ColorConfig Default = new();
175 | ///
176 | /// Represents the for a layered or .
177 | ///
178 | ///
179 | /// Equivalent to a with the following properties:
180 | ///
181 | /// -
182 | ///
to
183 | ///
184 | /// -
185 | ///
to .
186 | ///
187 | /// -
188 | ///
to 0x7F .
189 | ///
190 | ///
191 | ///
192 | public static readonly ColorConfig Layered = new(transparentOnGray: false, translucent: true, opacity: 0x7F);
193 |
194 | public bool TransparentOnGray { get; } = transparentOnGray;
195 |
196 | public bool IsTranslucent { get; } = translucent;
197 |
198 | public byte Opacity { get; } = opacity;
199 | }
200 |
--------------------------------------------------------------------------------
/MicaVisualStudio/MicaVisualStudioPackage.cs:
--------------------------------------------------------------------------------
1 | namespace MicaVisualStudio;
2 |
3 | ///
4 | /// This is the class that implements the package exposed by this assembly.
5 | ///
6 | ///
7 | ///
8 | /// The minimum requirement for a class to be considered a valid package for Visual Studio
9 | /// is to implement the IVsPackage interface and register itself with the shell.
10 | /// This package uses the helper classes defined inside the Managed Package Framework (MPF)
11 | /// to do it: it derives from the Package class that provides the implementation of the
12 | /// IVsPackage interface and uses the registration attributes defined in the framework to
13 | /// register itself and its components with the shell. These attributes tell the pkgdef creation
14 | /// utility what data to put into .pkgdef file.
15 | ///
16 | ///
17 | /// To get loaded into VS, the package must be referred by <Asset Type="Microsoft.VisualStudio.VsPackage" ...> in .vsixmanifest file.
18 | ///
19 | ///
20 | [PackageRegistration(AllowsBackgroundLoading = true, UseManagedResourcesOnly = true)]
21 | [Guid(PackageGuidString)]
22 | [ProvideOptionPage(typeof(OptionsProvider.GeneralOptions), "Mica Visual Studio", "General", 0, 0, true, SupportsProfiles = true)]
23 | [ProvideProfile(typeof(OptionsProvider.GeneralOptions), "Mica Visual Studio", "General", 0, 0, true)]
24 | [ProvideOptionPage(typeof(OptionsProvider.ToolOptions), "Mica Visual Studio", "\u200BTool Windows", 0, 0, true, SupportsProfiles = true)]
25 | [ProvideProfile(typeof(OptionsProvider.ToolOptions), "Mica Visual Studio", "Tool Windows", 0, 0, true)]
26 | [ProvideOptionPage(typeof(OptionsProvider.DialogOptions), "Mica Visual Studio", "Dialog Windows", 0, 0, true, SupportsProfiles = true)]
27 | [ProvideProfile(typeof(OptionsProvider.DialogOptions), "Mica Visual Studio", "Dialog Windows", 0, 0, true)]
28 | [ProvideAutoLoad(UIContextGuids.NoSolution, PackageAutoLoadFlags.BackgroundLoad)]
29 | [ProvideAutoLoad(UIContextGuids.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)]
30 | [ProvideAutoLoad(UIContextGuids.EmptySolution, PackageAutoLoadFlags.BackgroundLoad)]
31 | [ProvideMenuResource("Menus.ctmenu", 1)]
32 | public sealed class MicaVisualStudioPackage : AsyncPackage
33 | {
34 | ///
35 | /// MicaVisualStudioPackage GUID string.
36 | ///
37 | public const string PackageGuidString = "1a10bdf6-6cb0-415e-8ddd-f16d897f1e4a";
38 |
39 | #region Package Members
40 |
41 | private ThemeHelper theme;
42 | private WindowObserver observer;
43 |
44 | private VsColorManager colors;
45 | private VsWindowStyler styler;
46 |
47 | private (string Content, ImageMoniker Image) queuedInfo;
48 |
49 | ///
50 | /// Initialization of the package; this method is called right after the package is sited, so this is the place
51 | /// where you can put all the initialization code that rely on services provided by VisualStudio.
52 | ///
53 | /// A cancellation token to monitor for initialization cancellation, which can occur when VS is shutting down.
54 | /// A provider for progress updates.
55 | /// A task representing the async work of package initialization, or an already completed task if there is none. Do not return null from this method.
56 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress)
57 | {
58 | await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
59 |
60 | try
61 | {
62 | WindowObserver.MainWindow.Loaded += Window_Loaded;
63 |
64 | if (Environment.OSVersion.Version.Build < 22000) //Allow Windows 11 or later
65 | {
66 | queuedInfo = ("Mica Visual Studio is not compatible with Windows 10 and earlier.", KnownMonikers.StatusWarning);
67 | return;
68 | }
69 |
70 | colors = VsColorManager.Instance;
71 |
72 | #region Resource Keys
73 |
74 | colors.AddConfigs(new()
75 | {
76 | { "Background", new(translucent: true) },
77 |
78 | { "SolidBackgroundFillQuaternary", new(translucent: true) },
79 |
80 | //{ "SolidBackgroundFillTertiary", ColorConfig.Default },
81 | //{ "EnvironmentLayeredBackground", new(transparentOnGray: true, translucent: true, opacity: 0x7F) },
82 |
83 | { "EnvironmentBackground", new(translucent: true) },
84 | { "EnvironmentBackgroundGradient", ColorConfig.Default },
85 |
86 | { "ActiveCaption", ColorConfig.Layered },
87 | { "InactiveCaption", ColorConfig.Layered },
88 |
89 | { "MainWindowActiveCaption", ColorConfig.Default },
90 | { "MainWindowInactiveCaption", ColorConfig.Default },
91 |
92 | { "ToolWindow", ColorConfig.Default },
93 | { "ToolWindowGroup", ColorConfig.Default },
94 | { "ToolWindowBackground", ColorConfig.Default },
95 | { "ToolWindowFloatingFrame", ColorConfig.Default },
96 | { "ToolWindowFloatingFrameInactive", ColorConfig.Default },
97 |
98 | { "Default", ColorConfig.Default },
99 |
100 | { "Window", ColorConfig.Default },
101 | { "WindowPanel", new(translucent: true) },
102 |
103 | { "CommandBarGradient", ColorConfig.Default },
104 | { "CommandBarGradientBegin", ColorConfig.Default },
105 |
106 | { "ListBox", ColorConfig.Layered },
107 | { "ListItemBackgroundHover", new(transparentOnGray: false, translucent: true) },
108 |
109 | { "SelectedItemActive", ColorConfig.Layered },
110 | { "SelectedItemInactive", ColorConfig.Layered },
111 |
112 | { "Unfocused", ColorConfig.Layered },
113 |
114 | { "Caption", ColorConfig.Layered },
115 |
116 | { "TextBoxBackground", ColorConfig.Layered },
117 | { "SearchBoxBackground", ColorConfig.Layered },
118 |
119 | { "Button", ColorConfig.Layered },
120 | { "ButtonFocused", ColorConfig.Default },
121 |
122 | { "ComboBoxBackground", ColorConfig.Layered },
123 |
124 | { "InfoBarBorder", ColorConfig.Default },
125 |
126 | { "ToolWindowTabMouseOverBackgroundGradient", ColorConfig.Layered },
127 |
128 | { "Page", ColorConfig.Default },
129 | { "PageBackground", ColorConfig.Default },
130 |
131 | { "BrandedUIBackground", ColorConfig.Default },
132 |
133 | { "ScrollBarBackground", ColorConfig.Layered },
134 | { "ScrollBarArrowBackground", ColorConfig.Default },
135 | { "ScrollBarArrowDisabledBackground", ColorConfig.Default },
136 |
137 | { "AutoHideResizeGrip", ColorConfig.Default },
138 | { "AutoHideResizeGripDisabled", ColorConfig.Default },
139 |
140 | { "Content", ColorConfig.Default },
141 | { "ContentSelected", ColorConfig.Layered },
142 | { "ContentMouseOver", ColorConfig.Layered },
143 | { "ContentInactiveSelected", ColorConfig.Layered },
144 |
145 | { "Wonderbar", ColorConfig.Default },
146 | { "WonderbarMouseOver", ColorConfig.Layered },
147 | { "WonderbarTreeInactiveSelected", ColorConfig.Default },
148 |
149 | { "Details", ColorConfig.Layered },
150 |
151 | { "BackgroundLowerRegion", ColorConfig.Default },
152 | { "WizardBackgroundLowerRegion", ColorConfig.Default }
153 | });
154 | colors.UpdateColors();
155 |
156 | #endregion
157 |
158 | if (General.Instance.ForceTransparency)
159 | (styler = VsWindowStyler.Instance).Listen();
160 |
161 | theme = ThemeHelper.Instance;
162 | observer = WindowObserver.Instance;
163 |
164 | await BackdropCommands.InitializeAsync(package: this);
165 |
166 | RefreshPreferences(); //Set app theme
167 |
168 | if (WindowObserver.MainWindow.Visibility == Visibility.Visible) //We're late, so add all windows
169 | {
170 | WindowObserver.AllWindows.ForEach(AddWindow);
171 | WindowObserver.MainWindow.Loaded -= Window_Loaded;
172 | }
173 | else if (WindowObserver.CurrentWindow is Window window) //Apply to start window
174 | AddWindow(window);
175 |
176 | observer.WindowOpened += WindowOpened;
177 | //windows.WindowClosed += WindowClosed;
178 |
179 | colors.VisualStudioThemeChanged += ThemeChanged;
180 | theme.SystemThemeChanged += ThemeChanged;
181 |
182 | General.Saved += GeneralSaved;
183 | }
184 | catch (Exception ex)
185 | {
186 | #if DEBUG
187 | Debug.WriteLine($"Error initializing Mica Visual Studio: {ex.Message}");
188 | #endif
189 |
190 | progress.Report(new("Mica Visual Studio", $"Error while initializing Mica Visual Studio:\n{ex.Message}"));
191 | queuedInfo = ($"Error while initializing Mica Visual Studio: {ex.Message} ({ex.GetType().Name})\n{ex.StackTrace}", KnownMonikers.StatusError);
192 | }
193 |
194 | void Window_Loaded(object sender, RoutedEventArgs args)
195 | {
196 | if (queuedInfo.Content is not null)
197 | VS.InfoBar.CreateAsync(new(queuedInfo.Content, queuedInfo.Image)).Result.TryShowInfoBarUIAsync().Forget();
198 |
199 | WindowObserver.MainWindow.Loaded -= Window_Loaded;
200 | }
201 |
202 | void AddWindow(Window window)
203 | {
204 | observer.AppendWindow(window);
205 | ApplyWindowPreferences(
206 | window.GetHandle(),
207 | window,
208 | WindowHelper.GetWindowType(window));
209 | }
210 | }
211 |
212 | #region Event Handlers
213 |
214 | private void WindowOpened(Window sender, WindowActionEventArgs args) => ApplyWindowPreferences(args.WindowHandle, sender, args.WindowType);
215 |
216 | //private void WindowClosed(Window sender, WindowActionEventArgs args) { }
217 |
218 | private void ThemeChanged(object sender, Theme args) => RefreshPreferences();
219 |
220 | private void GeneralSaved(General sender) => RefreshPreferences();
221 |
222 | private void RefreshPreferences()
223 | {
224 | General general = General.Instance;
225 | theme.SetAppTheme(EvaluateTheme(general.AppTheme));
226 |
227 | foreach (var entry in observer.Windows)
228 | ApplyWindowPreferences(entry.Key, entry.Value.Window, entry.Value.Type, firstTime: false, general);
229 | }
230 |
231 | #endregion
232 |
233 | private void ApplyWindowPreferences(
234 | IntPtr handle,
235 | Window window,
236 | WindowType type,
237 | bool firstTime = true,
238 | General general = null)
239 | {
240 | general ??= General.Instance;
241 |
242 | if (firstTime && //Remove caption buttons once
243 | window is not null &&
244 | HwndSource.FromHwnd(handle) is HwndSource source)
245 | {
246 | WindowHelper.ExtendFrameIntoClientArea(handle);
247 | source.CompositionTarget.BackgroundColor = Colors.Transparent;
248 |
249 | //Don't remove caption buttons from windows that need them
250 | if (window.WindowStyle == WindowStyle.None || window is not DialogWindowBase)
251 | WindowHelper.RemoveCaptionButtons(source);
252 | }
253 |
254 | switch (type)
255 | {
256 | default:
257 | case WindowType.Main:
258 | ApplyWindowAttributes(
259 | general.Theme,
260 | (CornerPreference)general.CornerPreference,
261 | (BackdropType)general.Backdrop);
262 | break;
263 |
264 | case WindowType.Tool when general.ToolWindows:
265 | ApplyWindowAttributes(
266 | general.ToolTheme,
267 | (CornerPreference)general.ToolCornerPreference,
268 | (BackdropType)general.ToolBackdrop);
269 | break;
270 |
271 | case WindowType.Dialog when general.DialogWindows:
272 | ApplyWindowAttributes(
273 | general.DialogTheme,
274 | (CornerPreference)general.DialogCornerPreference,
275 | (BackdropType)general.DialogBackdrop);
276 | break;
277 | }
278 |
279 | void ApplyWindowAttributes(int theme, CornerPreference corner, BackdropType backdrop)
280 | {
281 | WindowHelper.EnableDarkMode(handle, EvaluateTheme(theme) == Theme.Dark);
282 | WindowHelper.SetCornerPreference(handle, corner);
283 | WindowHelper.SetBackdropType(handle, window is not null || backdrop != BackdropType.Glass ? backdrop : BackdropType.None);
284 | }
285 | }
286 |
287 | private Theme EvaluateTheme(int theme) => (Theme)theme switch
288 | {
289 | Theme.VisualStudio => colors.VisualStudioTheme,
290 | Theme.System => this.theme.SystemTheme,
291 | _ => (Theme)theme
292 | };
293 |
294 | protected override void Dispose(bool disposing)
295 | {
296 | theme?.Dispose();
297 | observer?.Dispose();
298 |
299 | styler?.Dispose();
300 |
301 | if (disposing)
302 | {
303 | observer?.WindowOpened -= WindowOpened;
304 | //observer?.WindowClosed -= WindowClosed;
305 |
306 | colors?.VisualStudioThemeChanged -= ThemeChanged;
307 | theme?.SystemThemeChanged -= ThemeChanged;
308 |
309 | General.Saved -= GeneralSaved;
310 | }
311 |
312 | base.Dispose(disposing);
313 | }
314 |
315 | #endregion
316 | }
317 |
--------------------------------------------------------------------------------
/MicaVisualStudio/Interop/WindowHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 | using Microsoft.VisualStudio.OLE.Interop;
3 |
4 | namespace MicaVisualStudio.Interop;
5 |
6 | ///
7 | /// Represents a static wrapper for various P/Invoke functions involving windows.
8 | ///
9 | public static class WindowHelper
10 | {
11 | #region DWM
12 |
13 | [DllImport("dwmapi.dll", EntryPoint = "DwmExtendFrameIntoClientArea")]
14 | private static extern int ExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMarInset);
15 |
16 | [DllImport("dwmapi.dll", EntryPoint = "DwmSetWindowAttribute")]
17 | private static extern int SetWindowAttribute(IntPtr hwnd, int dwAttribute, ref int pvAttribute, int cbAttribute);
18 |
19 | [DllImport("dwmapi.dll", EntryPoint = "DwmEnableBlurBehindWindow")]
20 | private static extern int EnableBlurBehindWindow(IntPtr hWnd, ref DWM_BLURBEHIND pBlurBehind);
21 |
22 | [DllImport("gdi32.dll")]
23 | private static extern IntPtr CreateRectRgn(int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);
24 |
25 | [DllImport("user32.dll")]
26 | private static extern bool SetWindowCompositionAttribute(IntPtr hwnd, ref WINDOWCOMPOSITIONATTRIBDATA pwcad);
27 |
28 | private const int DWMWA_SYSTEMBACKDROP_TYPE = 38,
29 | DWMWA_USE_IMMERSIVE_DARK_MODE = 20,
30 | DWMWA_WINDOW_CORNER_PREFERENCE = 33;
31 |
32 | private const uint DWM_BB_ENABLE = 0x1,
33 | DWM_BB_BLURREGION = 0x2,
34 | DWM_BB_TRANSITIONONMAXIMIZED = 0x4;
35 |
36 | private const int WCA_ACCENT_POLICY = 19;
37 |
38 | private const int ACCENT_DISABLED = 0,
39 | ACCENT_ENABLE_ACRYLICBLURBEHIND = 4;
40 |
41 | private struct MARGINS
42 | {
43 | public int cxLeftWidth;
44 | public int cxRightWidth;
45 | public int cyTopHeight;
46 | public int cyBottomHeight;
47 | }
48 |
49 | private struct DWM_BLURBEHIND
50 | {
51 | public uint dwFlags;
52 | public bool fEnable;
53 | public IntPtr hRgnBlur;
54 | public bool fTransitionOnMaximized;
55 | }
56 |
57 | private struct WINDOWCOMPOSITIONATTRIBDATA
58 | {
59 | public int Attrib;
60 | public IntPtr pvData;
61 | public uint cbData;
62 | }
63 |
64 | private struct AccentPolicy
65 | {
66 | public int AccentState;
67 | #pragma warning disable 0649
68 | public int AccentFlags;
69 | public int GradientColor;
70 | public int AnimationId;
71 | #pragma warning restore 0649
72 | }
73 |
74 | ///
75 | /// Extends the frame of the specified into its client area.
76 | ///
77 | /// A handle to a window.
78 | public static void ExtendFrameIntoClientArea(IntPtr hWnd)
79 | {
80 | MARGINS margins = new() { cxLeftWidth = -1, cxRightWidth = -1, cyTopHeight = -1, cyBottomHeight = -1 };
81 | ExtendFrameIntoClientArea(hWnd, ref margins);
82 | }
83 |
84 | ///
85 | /// Enables or disables dark mode for the specified .
86 | ///
87 | /// A handle to a window.
88 | /// Whether or not to enable dark mode.
89 | public static void EnableDarkMode(IntPtr hWnd, bool enable)
90 | {
91 | int mode = enable ? 1 : 0;
92 | SetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, ref mode, sizeof(int));
93 | }
94 |
95 | ///
96 | /// Sets the of the specified .
97 | ///
98 | /// A handle to a window.
99 | /// The to set.
100 | public static void SetCornerPreference(IntPtr hWnd, CornerPreference preference)
101 | {
102 | int corner = (int)preference;
103 | SetWindowAttribute(hWnd, DWMWA_WINDOW_CORNER_PREFERENCE, ref corner, sizeof(int));
104 | }
105 |
106 | ///
107 | /// Sets the of the specified .
108 | ///
109 | /// A handle to a window.
110 | /// The to set.
111 | public static void SetBackdropType(IntPtr hWnd, BackdropType backdrop)
112 | {
113 | int type = (int)(backdrop == BackdropType.Glass ? BackdropType.None : backdrop);
114 | SetWindowAttribute(hWnd, DWMWA_SYSTEMBACKDROP_TYPE, ref type, sizeof(int));
115 |
116 | EnableWindowTransparency(hWnd, enable: backdrop == BackdropType.Glass);
117 | }
118 |
119 | ///
120 | /// Enables or disables transparency for the specified .
121 | ///
122 | /// A handle to a window.
123 | /// Whether or not to enable transparency.
124 | public static void EnableWindowTransparency(IntPtr hWnd, bool enable)
125 | {
126 | DWM_BLURBEHIND bb = new()
127 | {
128 | fEnable = enable,
129 | hRgnBlur = enable ? CreateRectRgn(-2, -2, -1, -1) : IntPtr.Zero,
130 | fTransitionOnMaximized = true,
131 | dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION | DWM_BB_TRANSITIONONMAXIMIZED
132 | };
133 | _ = EnableBlurBehindWindow(hWnd, ref bb);
134 | }
135 |
136 | ///
137 | /// Enables or disables a blur effect used as the specified 's background.
138 | ///
139 | /// A handle to a window.
140 | /// Whether or not to enable blurring.
141 | public static void EnableWindowBlur(IntPtr hWnd, bool enable)
142 | {
143 | AccentPolicy policy = new()
144 | {
145 | AccentState = enable ? ACCENT_ENABLE_ACRYLICBLURBEHIND : ACCENT_DISABLED
146 | };
147 |
148 | var size = Marshal.SizeOf();
149 | var ptr = Marshal.AllocHGlobal(size);
150 | Marshal.StructureToPtr(policy, ptr, fDeleteOld: false);
151 |
152 | WINDOWCOMPOSITIONATTRIBDATA data = new()
153 | {
154 | Attrib = WCA_ACCENT_POLICY,
155 | pvData = ptr,
156 | cbData = (uint)size
157 | };
158 |
159 | SetWindowCompositionAttribute(hWnd, ref data);
160 | Marshal.FreeHGlobal(ptr);
161 | }
162 |
163 | #endregion
164 |
165 | #region Caption Buttons
166 |
167 | [DllImport("user32.dll")]
168 | private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
169 |
170 | [DllImport("user32.dll")]
171 | private static extern bool ClientToScreen(IntPtr hWnd, ref POINT lpPoint);
172 |
173 | [DllImport("user32.dll")]
174 | private static extern bool GetWindowPlacement(IntPtr hWnd, out WINDOWPLACEMENT lpwndpl);
175 |
176 | [DllImport("user32.dll")]
177 | private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
178 |
179 | [DllImport("user32.dll")]
180 | private static extern IntPtr EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);
181 |
182 | [DllImport("user32.dll")]
183 | private static extern int TrackPopupMenuEx(IntPtr hMenu, uint uFlags, int x, int y, IntPtr hwnd, IntPtr lptpm);
184 |
185 | [DllImport("dwmapi.dll", EntryPoint = "DwmGetWindowAttribute")]
186 | private static extern bool GetWindowAttribute(IntPtr hwnd, uint dwAttribute, out RECT pvAttribute, uint cbAttribute);
187 |
188 | private const int WM_NULL = 0x0,
189 | WM_DESTROY = 0x2,
190 | WM_STYLECHANGING = 0x7C,
191 | WM_NCRBUTTONUP = 0xA5,
192 | WM_SYSKEYDOWN = 0x104,
193 | WM_SYSCOMMAND = 0x112;
194 |
195 | private const uint SC_RESTORE = 0xF120,
196 | SC_MOVE = 0xF010,
197 | SC_SIZE = 0xF000,
198 | SC_MAXIMIZE = 0xF030,
199 | SC_MINIMIZE = 0xF020,
200 | SC_CLOSE = 0xF060;
201 |
202 | private const uint TPM_LEFTBUTTON = 0x0,
203 | TPM_RIGHTBUTTON = 0x2,
204 | TPM_RIGHTALIGN = 0x8,
205 | TPM_NONOTIFY = 0x80,
206 | TPM_RETURNCMD = 0x100,
207 | TPM_NOANIMATION = 0x4000;
208 |
209 | private const uint SW_NORMAL = 1,
210 | SW_MAXIMIZE = 3;
211 |
212 | private const uint MF_ENABLED = 0x0,
213 | MF_GRAYED = 0x1;
214 |
215 | private const int HTCAPTION = 2;
216 |
217 | private const int VK_SPACE = 0x20;
218 |
219 | private const int DWMWA_CAPTION_BUTTON_BOUNDS = 5;
220 |
221 | private struct WINDOWPLACEMENT
222 | {
223 | public uint length;
224 | public uint flags;
225 | public uint showCmd;
226 | public POINT ptMinPosition;
227 | public POINT ptMaxPosition;
228 | public RECT rcNormalPosition;
229 | public RECT rcDevice;
230 | }
231 |
232 | private struct STYLESTRUCT
233 | {
234 | #pragma warning disable 0649
235 | public uint styleOld;
236 | public uint styleNew;
237 | #pragma warning restore 0649
238 | }
239 |
240 | ///
241 | /// Gets the height, in pixels, of the specified 's title bar.
242 | ///
243 | /// A handle to a window.
244 | /// The height of the specified 's title bar.
245 | public static int GetTitleBarHeight(IntPtr hWnd)
246 | {
247 | GetWindowAttribute(hWnd, DWMWA_CAPTION_BUTTON_BOUNDS, out RECT bounds, (uint)Marshal.SizeOf());
248 | return bounds.bottom - bounds.top;
249 | }
250 |
251 | ///
252 | /// Patches the specified to remove its caption buttons but retain system menu functionality.
253 | ///
254 | /// An to patch.
255 | public static void RemoveCaptionButtons(HwndSource source)
256 | {
257 | const int MenuSpacing = 2;
258 | WindowType type = GetWindowType(source.RootVisual as Window);
259 |
260 | GetSystemMenu(source.Handle, bRevert: false); //Make sure window menu is created
261 |
262 | source.AddHook(Hook);
263 | SetWindowStyles(source.Handle, GetWindowStyles(source.Handle)); //Refresh styles
264 |
265 | IntPtr Hook(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
266 | {
267 | switch (msg)
268 | {
269 | case WM_DESTROY:
270 | HwndSource.FromHwnd(hWnd)?.RemoveHook(Hook); //Avoid memory leaks
271 | break;
272 |
273 | case WM_STYLECHANGING when (int)wParam == GWL_STYLE:
274 | STYLESTRUCT structure = Marshal.PtrToStructure(lParam);
275 |
276 | if (type == WindowType.Main || //Apply WS_OVERLAPPEDWINDOW style to main window
277 | ((WindowStyles)structure.styleNew).HasFlag(WindowStyles.ThickFrame)) //or any sizable window
278 | structure.styleNew |= (uint)WindowStyles.OverlappedWindow;
279 |
280 | structure.styleNew &= (uint)~WindowStyles.SystemMenu; //Remove the WS_SYSMENU style
281 |
282 | Marshal.StructureToPtr(structure, lParam, fDeleteOld: true);
283 | handled = true;
284 | break;
285 |
286 | case WM_NCRBUTTONUP when (int)wParam == HTCAPTION:
287 | ShowMenu(
288 | hWnd,
289 | (short)lParam,
290 | (short)((int)lParam >> 16 /*Y position shift*/),
291 | keyboard: false);
292 | handled = true;
293 | break;
294 |
295 | case WM_SYSKEYDOWN when (int)wParam == VK_SPACE && IsAltPressed(lParam):
296 | int height = GetTitleBarHeight(hWnd);
297 |
298 | POINT point = new() { x = MenuSpacing, y = (height > 0 ? height : System.Windows.Forms.SystemInformation.CaptionHeight) + MenuSpacing };
299 | ClientToScreen(hWnd, ref point);
300 |
301 | ShowMenu(hWnd, point.x, point.y, keyboard: true);
302 | break;
303 | }
304 |
305 | return IntPtr.Zero;
306 | }
307 |
308 | void ShowMenu(IntPtr hWnd, int x, int y, bool keyboard)
309 | {
310 | IntPtr menu = GetSystemMenu(hWnd, bRevert: false);
311 |
312 | uint minimize = type == WindowType.Dialog ? MF_GRAYED : MF_ENABLED;
313 | uint maximize = GetWindowStyles(source.Handle).HasFlag(WindowStyles.MaximizeBox) ? MF_ENABLED : MF_GRAYED;
314 | uint size = GetWindowStyles(source.Handle).HasFlag(WindowStyles.ThickFrame) ? MF_ENABLED : MF_GRAYED;
315 |
316 | if (GetWindowPlacement(hWnd, out WINDOWPLACEMENT placement))
317 | if (placement.showCmd == SW_NORMAL)
318 | {
319 | EnableMenuItem(menu, SC_RESTORE, MF_GRAYED);
320 | EnableMenuItem(menu, SC_MOVE, MF_ENABLED);
321 | EnableMenuItem(menu, SC_SIZE, size);
322 | EnableMenuItem(menu, SC_MINIMIZE, minimize);
323 | EnableMenuItem(menu, SC_MAXIMIZE, maximize);
324 | EnableMenuItem(menu, SC_CLOSE, MF_ENABLED);
325 | }
326 | else if (placement.showCmd == SW_MAXIMIZE)
327 | {
328 | EnableMenuItem(menu, SC_RESTORE, MF_ENABLED);
329 | EnableMenuItem(menu, SC_MOVE, MF_GRAYED);
330 | EnableMenuItem(menu, SC_SIZE, MF_GRAYED);
331 | EnableMenuItem(menu, SC_MINIMIZE, minimize);
332 | EnableMenuItem(menu, SC_MAXIMIZE, MF_GRAYED);
333 | EnableMenuItem(menu, SC_CLOSE, MF_ENABLED);
334 | }
335 |
336 | int cmd = TrackPopupMenuEx(
337 | menu,
338 | TPM_RETURNCMD | TPM_NONOTIFY | //Don't notify as we'll send a message later
339 | ((uint)System.Windows.Forms.SystemInformation.PopupMenuAlignment * TPM_RIGHTALIGN) |
340 | (keyboard ? TPM_LEFTBUTTON : TPM_RIGHTBUTTON) |
341 | (keyboard ? TPM_NOANIMATION : 0 /*Default fade animation*/),
342 | keyboard && placement.showCmd == SW_MAXIMIZE ? x - MenuSpacing : x,
343 | y,
344 | hWnd,
345 | IntPtr.Zero);
346 |
347 | if (cmd != WM_NULL)
348 | SendMessage(hWnd, WM_SYSCOMMAND, (IntPtr)cmd, IntPtr.Zero);
349 | }
350 |
351 | bool IsAltPressed(IntPtr lParam) =>
352 | (((int)lParam >> 29) //Context code shift
353 | & 1) //First bit mask
354 | == 1; //TRUE
355 | }
356 |
357 | #endregion
358 |
359 | #region Window Styles
360 |
361 | [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
362 | private static extern uint GetWindowLong(IntPtr hWnd, int nIndex);
363 |
364 | [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
365 | private static extern uint SetWindowLong(IntPtr hWnd, int nIndex, uint dwNewLong);
366 |
367 | private const int GWL_STYLE = -16,
368 | GWL_EXSTYLE = -20;
369 |
370 | ///
371 | /// Determines the of the specified .
372 | ///
373 | /// A to critique.
374 | /// The of the specified .
375 | public static WindowType GetWindowType(Window window)
376 | {
377 | if (window == WindowObserver.MainWindow)
378 | return WindowType.Main;
379 | else if (window is not null && //Check if window is WPF
380 | (window.WindowStyle == WindowStyle.None || //and has no style
381 | window.Owner is null)) //or no owner
382 | return WindowType.Tool;
383 | else
384 | return WindowType.Dialog;
385 | }
386 |
387 | ///
388 | /// Gets the of the specified .
389 | ///
390 | /// A handle to a window.
391 | /// The of the specified .
392 | public static WindowStyles GetWindowStyles(IntPtr hWnd) => (WindowStyles)GetWindowLong(hWnd, GWL_STYLE);
393 | ///
394 | /// Sets the of the specified .
395 | ///
396 | /// A handle to a window.
397 | /// The to set.
398 | public static void SetWindowStyles(IntPtr hWnd, WindowStyles styles) => SetWindowLong(hWnd, GWL_STYLE, (uint)styles);
399 |
400 | ///
401 | /// Gets the of the specified .
402 | ///
403 | /// A handle to a window.
404 | /// The of the specified .
405 | public static WindowStylesEx GetExtendedWindowStyles(IntPtr hWnd) => (WindowStylesEx)GetWindowLong(hWnd, GWL_EXSTYLE);
406 | ///
407 | /// Sets the of the specified .
408 | ///
409 | /// A handle to a window.
410 | /// The to set.
411 | public static void SetExtendedWindowStyles(IntPtr hWnd, WindowStylesEx styles) => SetWindowLong(hWnd, GWL_EXSTYLE, (uint)styles);
412 |
413 | #endregion
414 |
415 | #region Windowing
416 |
417 | [DllImport("user32.dll")]
418 | private static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
419 |
420 | [DllImport("user32.dll")]
421 | private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
422 |
423 | private const uint SWP_NOSIZE = 0x0001,
424 | SWP_NOZORDER = 0x0004,
425 | SWP_NOACTIVATE = 0x0010;
426 |
427 | ///
428 | /// Gets the current position of the specified .
429 | ///
430 | /// A handle to a window.
431 | /// A representing the position in screen coordinates.
432 | public static System.Drawing.Point GetWindowPosition(IntPtr hWnd)
433 | {
434 | RECT rect = new();
435 | GetWindowRect(hWnd, ref rect);
436 | return new(rect.left, rect.top);
437 | }
438 |
439 | ///
440 | /// Moves the specified the specified .
441 | ///
442 | /// A handle to a window.
443 | /// A in screen coordinates.
444 | public static void MoveWindow(IntPtr hWnd, System.Drawing.Point location) => SetWindowPos(
445 | hWnd, IntPtr.Zero,
446 | location.X, location.Y,
447 | 0, 0,
448 | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
449 |
450 | ///
451 | /// Offsets the specified by the specified .
452 | ///
453 | /// A handle to a window.
454 | /// A in screen coordinates.
455 | public static void OffsetWindow(IntPtr hWnd, System.Drawing.Point offset)
456 | {
457 | var pos = GetWindowPosition(hWnd);
458 | MoveWindow(hWnd, location: new(pos.X + offset.X, pos.Y + offset.Y));
459 | }
460 |
461 | #endregion
462 |
463 | #region Utilities
464 |
465 | [DllImport("user32.dll")]
466 | private static extern bool IsWindow(IntPtr hWnd);
467 |
468 | [DllImport("user32.dll")]
469 | private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
470 |
471 | [DllImport("user32.dll")]
472 | private static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);
473 |
474 | [DllImport("user32.dll")]
475 | private static extern bool EnumChildWindows(IntPtr hWndParent, EnumChildProc lpEnumFunc, IntPtr lParam);
476 |
477 | private const uint LWA_ALPHA = 0x00000002;
478 |
479 | private delegate bool EnumChildProc(IntPtr hwnd, IntPtr lParam);
480 |
481 | ///
482 | /// Determines whether the specified is alive; that is, whether it is still valid.
483 | ///
484 | /// A handle to a window.
485 | /// if the specified is still alive. Otherwise, .
486 | public static bool IsAlive(IntPtr hWnd) => IsWindow(hWnd);
487 |
488 | ///
489 | /// Gets the process ID associated with the specified .
490 | ///
491 | /// A handle to a window.
492 | /// The ID of the process that owns the specified .
493 | public static int GetProcessId(IntPtr hWnd)
494 | {
495 | GetWindowThreadProcessId(hWnd, out uint procId);
496 | return (int)procId;
497 | }
498 |
499 | ///
500 | /// Makes the specified layered by adding the style.
501 | ///
502 | /// A handle to a window.
503 | public static void MakeLayered(IntPtr hWnd)
504 | {
505 | SetExtendedWindowStyles(hWnd, GetExtendedWindowStyles(hWnd) | WindowStylesEx.Layered);
506 | SetLayeredWindowAttributes(
507 | hWnd,
508 | (uint)ColorTranslator.ToWin32(System.Drawing.Color.Black),
509 | 0xFF, //Set opactiy to 100%
510 | LWA_ALPHA);
511 | }
512 |
513 | ///
514 | /// Enumerates the children of the specified .
515 | ///
516 | /// A handle to a window.
517 | /// An of window handles representing the children of the specified .
518 | public static IEnumerable GetChildren(IntPtr hWnd)
519 | {
520 | List handles = [];
521 | EnumChildWindows(hWnd, Proc, IntPtr.Zero);
522 | return handles;
523 |
524 | bool Proc(IntPtr hwnd, IntPtr lParam)
525 | {
526 | handles.Add(hwnd);
527 | return true;
528 | }
529 | }
530 |
531 | ///
532 | /// Gets the handle of the specified .
533 | ///
534 | /// A to retrieve the handle of.
535 | /// The handle of the specified .
536 | public static IntPtr GetHandle(this Window window)
537 | {
538 | WindowInteropHelper interop = new(window);
539 | interop.EnsureHandle();
540 | return interop.Handle;
541 | }
542 |
543 | #endregion
544 | }
545 |
546 | #region Enums
547 |
548 | ///
549 | /// Specifies the type of backdrop to use in .
550 | ///
551 | public enum BackdropType
552 | {
553 | Auto,
554 | None,
555 | Mica,
556 | Acrylic,
557 | Tabbed,
558 | Glass
559 | }
560 |
561 | ///
562 | /// Specifies the corner preference to use in .
563 | ///
564 | public enum CornerPreference
565 | {
566 | Default,
567 | Square,
568 | Round,
569 | RoundSmall
570 | }
571 |
572 | ///
573 | /// Specifies the type of a .
574 | ///
575 | public enum WindowType
576 | {
577 | ///
578 | /// Specifies that the is the main window of the process.
579 | ///
580 | Main,
581 |
582 | ///
583 | /// Specifies that the is an additional top-level window, meant for tooling and options.
584 | ///
585 | Tool,
586 |
587 | ///
588 | /// Specifies that the is a child window, meant for responding to requests and displaying information.
589 | ///
590 | Dialog
591 | }
592 |
593 | ///
594 | /// Specifies the style(s) of a window.
595 | ///
596 | ///
597 | /// Used in:
598 | ///
599 | /// -
600 | ///
601 | ///
602 | /// -
603 | ///
604 | ///
605 | ///
606 | ///
607 | [Flags]
608 | public enum WindowStyles : uint
609 | {
610 | Border = 0x00800000,
611 | Caption = 0x00C00000,
612 | Child = 0x40000000,
613 | ClipChildren = 0x02000000,
614 | ClipSiblings = 0x04000000,
615 | Disabled = 0x08000000,
616 | DLGFrame = 0x00400000,
617 | Group = 0x00020000,
618 | HScroll = 0x00100000,
619 | Iconic = 0x20000000,
620 | Maximize = 0x01000000,
621 | MaximizeBox = 0x00010000,
622 | Minimize = 0x20000000,
623 | MinimizeBox = 0x00020000,
624 | Overlapped = 0x00000000,
625 | OverlappedWindow = Overlapped | Caption | SystemMenu | ThickFrame | MinimizeBox | MaximizeBox,
626 | Popup = 0x80000000,
627 | PopupWindow = Popup | Border | SystemMenu,
628 | SystemMenu = 0x00080000,
629 | TabStop = 0x00010000,
630 | ThickFrame = 0x00040000,
631 | Visible = 0x10000000,
632 | VScroll = 0x00200000
633 | }
634 |
635 | ///
636 | /// Specifies the extended style(s) of a window.
637 | ///
638 | ///
639 | /// Used in:
640 | ///
641 | /// -
642 | ///
643 | ///
644 | /// -
645 | ///
646 | ///
647 | ///
648 | ///
649 | public enum WindowStylesEx : uint
650 | {
651 | AcceptFiles = 0x00000010,
652 | AppWindow = 0x00040000,
653 | ClientEdge = 0x00000200,
654 | Composited = 0x02000000,
655 | ContextHelp = 0x00000400,
656 | ControlParent = 0x00010000,
657 | DLGModalFrame = 0x00000001,
658 | Layered = 0x00080000,
659 | LayoutRTL = 0x00400000,
660 | Left = 0x00000000,
661 | LeftScrollBar = 0x00004000,
662 | LTRReading = 0x00000000,
663 | MDIChild = 0x00000040,
664 | NoActivate = 0x08000000,
665 | NoInheritLayout = 0x00100000,
666 | NoParentNotify = 0x00000004,
667 | NoRedirectionBitmap = 0x00200000,
668 | OverlappedWindow = WindowEdge | ClientEdge,
669 | PaletteWindow = WindowEdge | ToolWindow | TopMost,
670 | Right = 0x00001000,
671 | RightScrollBar = 0x00000000,
672 | RTLReading = 0x00002000,
673 | StaticEdge = 0x00020000,
674 | ToolWindow = 0x00000080,
675 | TopMost = 0x00000008,
676 | Transparent = 0x00000020,
677 | WindowEdge = 0x00000100
678 | }
679 |
680 | #endregion
681 |
--------------------------------------------------------------------------------
/MicaVisualStudio/VisualStudio/VsWindowStyler.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Windows.Shapes;
3 | using Microsoft.Internal.VisualStudio.PlatformUI;
4 | using Mono.Cecil.Cil;
5 | using MonoMod.Cil;
6 | using MonoMod.RuntimeDetour;
7 | using Expression = System.Linq.Expressions.Expression;
8 |
9 | namespace MicaVisualStudio.VisualStudio;
10 |
11 | ///
12 | /// Represents an observer that listens and styles Visual Studio windows.
13 | ///
14 | //This code is bad, but it works, so...
15 | public sealed class VsWindowStyler : IVsWindowFrameEvents, IDisposable
16 | {
17 | ///
18 | /// Gets the singleton instance of .
19 | ///
20 | public static VsWindowStyler Instance => field ??= new();
21 |
22 | #region Keys
23 |
24 | private const string MultiViewHostTypeName = "Microsoft.VisualStudio.Editor.Implementation.WpfMultiViewHost";
25 |
26 | private const string SolidBackgroundFillTertiaryLayeredKey = "VsBrush.SolidBackgroundFillTertiaryLayered",
27 | PopupBackgroundLayeredKey = "VsBrush.PopupBackgroundLayered";
28 |
29 | private readonly ThemeResourceKey SolidBackgroundFillTertiaryKey =
30 | new(category: new("73708ded-2d56-4aad-b8eb-73b20d3f4bff"), name: "SolidBackgroundFillTertiary", ThemeResourceKeyType.BackgroundColor);
31 |
32 | private readonly ThemeResourceKey TextFillPrimaryKey =
33 | new(category: new("73708ded-2d56-4aad-b8eb-73b20d3f4bff"), name: "TextFillPrimary", ThemeResourceKeyType.BackgroundBrush);
34 |
35 | private readonly ThemeResourceKey TextOnAccentFillPrimaryKey =
36 | new(category: new("73708ded-2d56-4aad-b8eb-73b20d3f4bff"), name: "TextOnAccentFillPrimary", ThemeResourceKeyType.BackgroundBrush);
37 |
38 | private readonly ThemeResourceKey ScrollBarBackgroundKey =
39 | new(category: new("{624ed9c3-bdfd-41fa-96c3-7c824ea32e3d}"), name: "ScrollBarBackground", ThemeResourceKeyType.BackgroundBrush);
40 |
41 | #endregion
42 |
43 | #region Shells
44 |
45 | private readonly IVsUIShell shell = VS.GetRequiredService();
46 | private readonly IVsUIShell5 shell5 = VS.GetRequiredService();
47 | private readonly IVsUIShell7 shell7 = VS.GetRequiredService();
48 |
49 | private uint cookie;
50 |
51 | #endregion
52 |
53 | #region Functions
54 |
55 | private Func get_WindowFrame_FrameView;
56 | private Func get_View_Content;
57 | private Func get_View_IsActive;
58 | private Func IsDockTarget;
59 |
60 | private DependencyProperty View_ContentProperty,
61 | View_IsActiveProperty;
62 |
63 | #endregion
64 |
65 | private ILHook visualHook, sourceHook;
66 |
67 | private bool makeLayered = true;
68 |
69 | private VsWindowStyler() { }
70 |
71 | ///
72 | /// Tells the to start listening to and styling Visual Studio windows.
73 | ///
74 | public void Listen()
75 | {
76 | if (disposed)
77 | return;
78 |
79 | #region Function Initialization
80 |
81 | var frameViewProp = Type.GetType("Microsoft.VisualStudio.Platform.WindowManagement.WindowFrame, Microsoft.VisualStudio.Platform.WindowManagement")
82 | .GetProperty("FrameView");
83 |
84 | var frameParam = Expression.Parameter(typeof(IVsWindowFrame));
85 | get_WindowFrame_FrameView = frameParam.Convert(frameViewProp.DeclaringType)
86 | .Property(frameViewProp)
87 | .Convert()
88 | .Compile(frameParam);
89 |
90 | var viewType = Type.GetType("Microsoft.VisualStudio.PlatformUI.Shell.View, Microsoft.VisualStudio.Shell.ViewManager");
91 | var contentProp = viewType.GetProperty("Content");
92 |
93 | var viewParam = Expression.Parameter(typeof(DependencyObject));
94 | get_View_Content = viewParam.Convert(contentProp.DeclaringType)
95 | .Property(contentProp)
96 | .Compile(viewParam);
97 |
98 | View_ContentProperty = viewType.GetField("ContentProperty", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy)
99 | .GetValue(null) as DependencyProperty;
100 |
101 | var isActiveProp = viewType.GetProperty("IsActive");
102 | get_View_IsActive = viewParam.Convert(isActiveProp.DeclaringType)
103 | .Property(isActiveProp)
104 | .Compile(viewParam);
105 |
106 | View_IsActiveProperty = viewType.GetField("IsActiveProperty", BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy)
107 | .GetValue(null) as DependencyProperty;
108 |
109 | var dockType = Type.GetType("Microsoft.VisualStudio.PlatformUI.Shell.Controls.DockTarget, Microsoft.VisualStudio.Shell.ViewManager");
110 | var objectParam = Expression.Parameter(typeof(object));
111 | IsDockTarget = objectParam.TypeIs(dockType)
112 | .Compile(objectParam);
113 |
114 | #endregion
115 |
116 | #region Layered Brushes
117 |
118 | AddLayeredBrushes();
119 | (Application.Current.Resources.MergedDictionaries as INotifyCollectionChanged).CollectionChanged += (s, e) => AddLayeredBrushes();
120 |
121 | void AddLayeredBrushes()
122 | {
123 | var color = shell5?.GetThemedWPFColor(SolidBackgroundFillTertiaryKey) ?? default;
124 |
125 | SolidColorBrush halfBrush = new(color with { A = 0xFF / 2 }); //50% opacity
126 | SolidColorBrush quarterBrush = new(color with { A = 0xFF / 4 }); //25% opacity
127 |
128 | foreach (var dictionary in Application.Current.Resources.MergedDictionaries.OfType())
129 | {
130 | if (!dictionary.Contains(SolidBackgroundFillTertiaryLayeredKey))
131 | dictionary.Add(SolidBackgroundFillTertiaryLayeredKey, halfBrush);
132 |
133 | if (!dictionary.Contains(PopupBackgroundLayeredKey))
134 | if (VsColorManager.Instance.VisualStudioTheme == Theme.Light ||
135 | (quarterBrush.Color.R != quarterBrush.Color.G && quarterBrush.Color.G != quarterBrush.Color.B))
136 | dictionary.Add(PopupBackgroundLayeredKey, quarterBrush);
137 | else //Full acrylic experience for those who can handle it
138 | dictionary.Add(PopupBackgroundLayeredKey, new SolidColorBrush(Colors.Transparent with { A = 0x01 }));
139 | }
140 | }
141 |
142 | #endregion
143 |
144 | #region Listeners
145 |
146 | #pragma warning disable VSTHRD010 //Invoke single-threaded types on Main thread
147 | cookie = shell7.AdviseWindowFrameEvents(this);
148 | #pragma warning restore VSTHRD010 //Invoke single-threaded types on Main thread
149 |
150 | EventManager.RegisterClassHandler(
151 | dockType,
152 | FrameworkElement.LoadedEvent,
153 | new RoutedEventHandler((s, e) => ApplyToDockTarget(s as Border)));
154 |
155 | if (AppDomain.CurrentDomain.GetAssemblies()
156 | .FirstOrDefault(i => i.GetName().Name == "Microsoft.VisualStudio.Editor.Implementation")?
157 | .GetTypes()
158 | .FirstOrDefault(i => i.FullName == MultiViewHostTypeName) is Type hostType)
159 | EventManager.RegisterClassHandler(
160 | hostType,
161 | FrameworkElement.LoadedEvent,
162 | new RoutedEventHandler((s, e) => ApplyToContent(s as DockPanel, applyToDock: false)));
163 |
164 | WindowObserver.Instance.WindowOpened += (s, e) =>
165 | {
166 | if (s is not null)
167 | ApplyToWindow(s);
168 | };
169 |
170 | visualHook = CreatePostfix(
171 | typeof(Visual).GetMethod("AddVisualChild", BindingFlags.Instance | BindingFlags.NonPublic),
172 | AddVisualChild);
173 |
174 | sourceHook = CreatePostfix(
175 | typeof(HwndSource).GetProperty("RootVisual").SetMethod,
176 | RootVisualChanged);
177 |
178 | static void AddVisualChild(Visual instance, Visual child)
179 | {
180 | if (instance is ContentControl or ContentPresenter or Decorator or Panel && //Avoid unnecessary work
181 | instance is FrameworkElement content &&
182 | GetIsTracked(content) && Instance is VsWindowStyler styler)
183 | styler.ApplyToContent(content, applyToDock: false);
184 | }
185 |
186 | static void RootVisualChanged(HwndSource instance, Visual value)
187 | {
188 | if (value is null || instance.CompositionTarget is null)
189 | return;
190 |
191 | //Visual Studio popup
192 | if (value is FrameworkElement element &&
193 | element.Parent is Popup && //Check if root of popup
194 | element.FindDescendant(i => i.Name == "DropShadowBorder") is Border drop)
195 | {
196 | //Remove current border and update background to be translucent
197 | drop.BorderBrush = Brushes.Transparent;
198 | drop.SetResourceReference(Border.BackgroundProperty, PopupBackgroundLayeredKey);
199 |
200 | //Get left-side offset to move window by
201 | var offset = GetPopupOffset(element);
202 |
203 | if (offset == default) //Get if any offset already cached
204 | if (element.FindDescendant() is ToolTip tip)
205 | {
206 | SetPopupOffset(element, offset = new(tip.Margin.Left, tip.Margin.Top));
207 | tip.Margin = default;
208 | }
209 | else
210 | {
211 | SetPopupOffset(element, offset = new(drop.Margin.Left, drop.Margin.Top));
212 | drop.Margin = default;
213 | }
214 |
215 | //Get and update window position
216 | var deviceOffset = instance.CompositionTarget.TransformToDevice.Transform(offset);
217 | Interop.WindowHelper.OffsetWindow(instance.Handle, offset: new((int)deviceOffset.X, (int)deviceOffset.Y));
218 |
219 | //Add acrylic, shadow, and border
220 | Interop.WindowHelper.EnableWindowBlur(instance.Handle, enable: true);
221 | Interop.WindowHelper.SetCornerPreference(instance.Handle, CornerPreference.Round);
222 | }
223 | else if (value is not Window) //Avoid already handled values
224 | instance.CompositionTarget.BackgroundColor = Colors.Transparent;
225 | }
226 |
227 | #endregion
228 |
229 | makeLayered = General.Instance.LayeredWindows;
230 |
231 | ApplyToAllWindows();
232 | ApplyToAllWindowPanesAsync().Forget();
233 | }
234 |
235 | #region Apply To All
236 |
237 | private void ApplyToAllWindows() => WindowObserver.AllWindows.ForEach(ApplyToWindow);
238 |
239 | private async Task ApplyToAllWindowPanesAsync()
240 | {
241 | if (shell is null)
242 | return;
243 |
244 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
245 |
246 | shell.GetToolWindowEnum(out IEnumWindowFrames toolEnum);
247 | shell.GetDocumentWindowEnum(out IEnumWindowFrames docEnum);
248 |
249 | foreach (var frame in toolEnum.ToEnumerable().Concat(docEnum.ToEnumerable()))
250 | ApplyToWindowFrame(frame);
251 | }
252 |
253 | #endregion
254 |
255 | private void ApplyToWindowFrame(IVsWindowFrame frame)
256 | {
257 | if (get_WindowFrame_FrameView(frame) is not DependencyObject view)
258 | return;
259 |
260 | if (get_View_Content(view) is not Grid host)
261 | {
262 | WeakReference weakFrame = new(frame);
263 |
264 | view.AddWeakOneTimePropertyChangeHandler(View_ContentProperty, (s, e) =>
265 | {
266 | if (weakFrame.TryGetTarget(out IVsWindowFrame frame))
267 | ApplyToWindowFrame(frame);
268 | });
269 | return;
270 | }
271 |
272 | ApplyToContent(host);
273 | }
274 |
275 | private void ApplyToWindow(Window window)
276 | {
277 | //Wizards (e.g. packaged app creation wizard)
278 | if (window.GetType().FullName == "Microsoft.VisualStudio.WizardFrameworkWpf.WizardBase")
279 | {
280 | ApplyToContent(window, applyToDock: false);
281 | return;
282 | }
283 |
284 | foreach (var element in window.FindDescendants())
285 |
286 | //Warning dialog, footer
287 | if (window is DialogWindowBase && element is Button { Name: "OKButton" } button &&
288 | button.FindAncestor()?.FindAncestor() is Border buttonFooter)
289 | buttonFooter.SetResourceReference(Border.BackgroundProperty, SolidBackgroundFillTertiaryLayeredKey);
290 |
291 | else if (element is Border border)
292 | {
293 | //Footer
294 | if (border.Name == "FooterBorder")
295 | border.SetResourceReference(Border.BackgroundProperty, SolidBackgroundFillTertiaryLayeredKey);
296 |
297 | //Dock target
298 | else if (IsDockTarget(border))
299 | ApplyToDockTarget(border);
300 | }
301 | }
302 |
303 | private void ApplyToDockTarget(Border dock, bool applyToContent = true)
304 | {
305 | if (dock.Name == "ViewFrameTarget")
306 | dock.SetResourceReference(Border.BackgroundProperty, SolidBackgroundFillTertiaryLayeredKey);
307 | else
308 | dock.Background = Brushes.Transparent; //Smoke layer underneath tabs
309 |
310 | var descendants = dock.FindDescendants();
311 | if (!descendants.Any())
312 | return;
313 |
314 | //Content area
315 | descendants.FindElement("PART_ContentPanel")?
316 | .SetResourceReference(Border.BackgroundProperty, SolidBackgroundFillTertiaryLayeredKey);
317 |
318 | //Title bar
319 | descendants.FindElement("PART_Header")?.Background = Brushes.Transparent;
320 |
321 | if (descendants.FindElement("ToolWindowBorder") is Border border) //Body
322 | {
323 | border.Background = Brushes.Transparent;
324 |
325 | if (applyToContent && border.FindDescendant() is Grid host)
326 | ApplyToContent(host, applyToDock: false);
327 | }
328 |
329 | if (descendants.FindElement("PART_TabPanel") is Panel tabs) //Tab strip
330 | foreach (var tab in tabs.Children.OfType()) //Tab items
331 | {
332 | tab.Background = Brushes.Transparent;
333 |
334 | if (tab.DataContext is not DependencyObject view)
335 | continue;
336 |
337 | ApplyTabForeground(tab, view);
338 |
339 | if (GetIsTracked(tab))
340 | continue;
341 |
342 | SetIsTracked(tab, value: true);
343 | WeakReference weakTab = new(tab);
344 |
345 | tab.AddWeakPropertyChangeHandler(TabItem.IsSelectedProperty, (s, e) =>
346 | {
347 | if (s is TabItem tab && tab.DataContext is DependencyObject view)
348 | ApplyTabForeground(tab, view);
349 | });
350 | view.AddPropertyChangeHandler(View_IsActiveProperty, (s, e) =>
351 | {
352 | if (weakTab.TryGetTarget(out TabItem tab) && s is DependencyObject view)
353 | ApplyTabForeground(tab, view);
354 | });
355 |
356 | void ApplyTabForeground(TabItem item, DependencyObject view) => tab.SetResourceReference(
357 | Control.ForegroundProperty,
358 | tab.IsSelected && get_View_IsActive(view) ? TextOnAccentFillPrimaryKey : TextFillPrimaryKey);
359 | }
360 | }
361 |
362 | private void ApplyToContent(FrameworkElement content, bool applyToDock = true)
363 | {
364 | if (!content.IsLoaded)
365 | content.AddWeakOneTimeHandler(FrameworkElement.LoadedEvent, (s, e) => ApplyToContent(s as FrameworkElement, applyToDock));
366 |
367 | if (applyToDock && content.FindAncestor(i => i.GetVisualOrLogicalParent(), IsDockTarget) is Border dock)
368 | ApplyToDockTarget(dock, applyToContent: false);
369 |
370 | foreach (var element in content.FindDescendants().Append(content))
371 | {
372 | if (element is ContentControl or ContentPresenter or Decorator or Panel && !GetIsTracked(element))
373 | SetIsTracked(element, value: true); //Track visual children
374 |
375 | if (element is ToolBar bar)
376 | {
377 | bar.Background = bar.BorderBrush = Brushes.Transparent;
378 | (bar.Parent as ToolBarTray)?.Background = Brushes.Transparent;
379 | }
380 |
381 | else if (makeLayered && element is HwndHost { IsLoaded: true } host)
382 | {
383 | var sources = PresentationSource.CurrentSources.OfType().ToArray();
384 | if (sources.Any(i => i.Handle == host.Handle))
385 | continue;
386 |
387 | var children = Interop.WindowHelper.GetChildren(host.Handle);
388 |
389 | if (sources.FirstOrDefault(i => children.Contains(i.Handle)) is not HwndSource source)
390 | Interop.WindowHelper.MakeLayered(host.Handle);
391 | }
392 |
393 | else if (element is Control control)
394 | switch (control.Name)
395 | {
396 | case "gitWindowView" or //Git changes window
397 | "focusedWindowView" or //Git repository window
398 | "historyView" or //Commit history
399 | "detailsView" or //Git commit details
400 | "focusedDetailsContainer" or //Git commit details container
401 | "teamExplorerFrame" or //Team explorer window
402 | "createPullRequestView": //New PR window
403 | control.Background = Brushes.Transparent;
404 |
405 | foreach (var e in control.LogicalDescendants().Append(control))
406 | switch (e.Name)
407 | {
408 | //Section header
409 | case "borderHeader" when e is Border { Style: Style bs } b:
410 | b.Style = new(bs.TargetType, bs)
411 | {
412 | Setters = { new Setter(Border.BorderBrushProperty, Brushes.Transparent) }
413 | };
414 | break;
415 |
416 | //Command buttons
417 | case "gitAction" or
418 | "detailsView" when
419 | e.TryFindResource("TESectionCommandButtonStyle") is Style { Setters.IsSealed: false } ss:
420 | Setter bg = new(Control.BackgroundProperty, Brushes.Transparent),
421 | bb = new(Control.BorderBrushProperty, Brushes.Transparent);
422 |
423 | ss.Setters.Add(bg);
424 | ss.Setters.Add(bb);
425 |
426 | if (ss.Triggers.OfType()
427 | .FirstOrDefault(i => i.Property == System.Windows.UIElement.IsEnabledProperty) is Trigger t)
428 | {
429 | t.Setters.Add(bg);
430 | t.Setters.Add(bb);
431 | }
432 | break;
433 |
434 | //???
435 | case "thisPageControl" when e is Control c:
436 | c.Background = Brushes.Transparent;
437 | break;
438 |
439 | //Team explorer, project selector
440 | case "navControl" when e is Control c:
441 | c.Background = Brushes.Transparent;
442 | break;
443 |
444 | //Git branch selector
445 | case "branchesList" when e.GetVisualOrLogicalParent()
446 | .GetVisualOrLogicalParent()
447 | .GetVisualOrLogicalParent()
448 | .GetVisualOrLogicalParent() is Control bc:
449 | bc.Background = Brushes.Transparent;
450 | break;
451 |
452 | //Commit history
453 | case "historyView" when e.GetVisualOrLogicalParent()
454 | .GetVisualOrLogicalParent()
455 | .GetVisualOrLogicalParent() is Border hb:
456 | hb.Background = Brushes.Transparent;
457 | break;
458 |
459 | //Commit history list
460 | case "historyListView" when e is ListView { View: GridView g }:
461 | g.ColumnHeaderContainerStyle = new(g.ColumnHeaderContainerStyle.TargetType, g.ColumnHeaderContainerStyle)
462 | {
463 | Setters =
464 | {
465 | new Setter(Control.BackgroundProperty, Brushes.Transparent),
466 | new Setter(Control.BorderBrushProperty, Brushes.Transparent)
467 | }
468 | };
469 |
470 | foreach (var c in g.Columns.Select(i => i.Header).OfType())
471 | {
472 | c.ApplyTemplate();
473 | c.FindDescendant(i => i.Name == "HeaderBorder")?.BorderBrush = Brushes.Transparent;
474 | }
475 | break;
476 |
477 | //Commit diff
478 | case "detailsViewMainGrid" when e is Grid g && g.GetVisualOrLogicalParent()
479 | .GetVisualOrLogicalParent() is Border db:
480 | db.Background = Brushes.Transparent;
481 | break;
482 |
483 | //Commit diff info
484 | case "pageContentViewer" when e.GetVisualOrLogicalParent()
485 | .GetVisualOrLogicalParent() is Border pb:
486 | pb.Background = Brushes.Transparent;
487 | break;
488 |
489 | //Commit diff presenter dock buttons
490 | case "dockToBottomButton" or
491 | "dockToRightButton" or
492 | "undockButton" or
493 | "maximizeMinimizeButton" or
494 | "closeButton" when
495 | e is Button b:
496 | b.Style = new(b.Style.TargetType, b.Style)
497 | {
498 | Setters =
499 | {
500 | new Setter(Control.BackgroundProperty, Brushes.Transparent),
501 | }
502 | };
503 | break;
504 |
505 | //Git push, pull etc. buttons
506 | case "actionButton" or
507 | "fetchButton" or
508 | "pullButton" or
509 | "pushButton" or
510 | "syncButton" or
511 | "additionalOperationsButton" when
512 | e is Button b:
513 | b.Style = new(b.Style.TargetType, b.Style)
514 | {
515 | Triggers =
516 | {
517 | new Trigger
518 | {
519 | Property = System.Windows.UIElement.IsEnabledProperty,
520 | Value = false,
521 | Setters =
522 | {
523 | new Setter(Control.BackgroundProperty, Brushes.Transparent),
524 | new Setter(Control.BorderBrushProperty, Brushes.Transparent)
525 | }
526 | }
527 | }
528 | };
529 | break;
530 |
531 | //Git repository window, presenters
532 | case "detailsContent" or
533 | "detailsFullWindowContent" or
534 | "detailsRightContent" or
535 | "detailsBottomContent" when
536 | e is ContentControl c:
537 | SetIsTracked(c, value: true);
538 | break;
539 |
540 | case "statusControl" or //Actions/tool bar
541 | "thisPageControl" or //Changes
542 | "inactiveRepoContent" or //Create repo
543 | "sectionContainer" or //Branches and tags
544 | "amendCheckBox" when //Checkbox... for amending...
545 | e is Control c:
546 | c.Background = Brushes.Transparent;
547 | break;
548 |
549 | default:
550 | if (e is ItemsControl { ItemContainerStyle: Style ics } ic) //Changes
551 | {
552 | ic.Background = Brushes.Transparent;
553 |
554 | if (!ics.IsSealed)
555 | {
556 | ics.Setters.Add(new Setter(Control.BackgroundProperty, Brushes.Transparent));
557 | ics.Setters.Add(new Setter(Control.BorderBrushProperty, Brushes.Transparent));
558 | }
559 | }
560 | break;
561 | }
562 | break;
563 |
564 | //Host of WpfTextView I guess
565 | case "WpfTextViewHost":
566 | control.Resources["outlining.chevron.expanded.background"] =
567 | control.Resources["outlining.chevron.collapsed.background"] = Brushes.Transparent;
568 | break;
569 |
570 | //Editor, output, etc. text
571 | case "WpfTextView" when element is ContentControl:
572 | control.Background = Brushes.Transparent;
573 | control.FindDescendant()?.Background = Brushes.Transparent;
574 | break;
575 |
576 | //Packaged app configurations list
577 | case "PackageConfigurationsList" when control is DataGrid grid:
578 | grid.Background = grid.RowBackground = Brushes.Transparent;
579 |
580 | if (grid.CellStyle is Style { Setters.IsSealed: false } cs)
581 | cs.Setters.Add(new Setter(Control.BackgroundProperty, Brushes.Transparent));
582 | break;
583 |
584 | //VSIX manfiest editor
585 | case "VsixEditorControl":
586 | control.Background = Brushes.Transparent;
587 |
588 | foreach (var tab in control.FindDescendants())
589 | tab.Background = Brushes.Transparent;
590 | break;
591 |
592 | default:
593 | switch (control.GetType().FullName)
594 | {
595 | //AppxManifest editor
596 | case "Microsoft.VisualStudio.AppxManifestDesigner.Designer.ManifestDesignerUserControlProxy":
597 | case "Microsoft.VisualStudio.AppxManifestDesigner.Designer.ManifestDesignerUserControl":
598 | control.Background = Brushes.Transparent;
599 | break;
600 |
601 | //Resource editor
602 | case "Microsoft.VisualStudio.ResourceExplorer.UI.ResourceGroupEditorControl":
603 | control.Background = Brushes.Transparent;
604 | break;
605 | }
606 | break;
607 | }
608 |
609 | else if (element is Panel panel)
610 | switch (panel.GetType().FullName)
611 | {
612 | //Editor window, root
613 | case MultiViewHostTypeName:
614 | element.Resources[ScrollBarBackgroundKey] = Brushes.Transparent;
615 | break;
616 |
617 | //Editor window, bottom container
618 | case "Microsoft.VisualStudio.Text.Utilities.ContainerMargin" when
619 | !panel.FindDescendants().Any(i => i.GetType().FullName == "Microsoft.VisualStudio.Text.Utilities.ContainerMargin"):
620 | panel.SetResourceReference(Panel.BackgroundProperty, SolidBackgroundFillTertiaryLayeredKey);
621 | break;
622 |
623 | //Editor window, left side icon container
624 | case "Microsoft.VisualStudio.Text.Editor.Implementation.GlyphMarginGrid" when
625 | panel.Background is SolidColorBrush solid && solid.Color != Brushes.Transparent.Color:
626 | panel.SetResourceReference(Panel.BackgroundProperty, SolidBackgroundFillTertiaryLayeredKey);
627 | break;
628 |
629 | //Scroll bar intersection
630 | case "Microsoft.VisualStudio.Editor.Implementation.BottomRightCornerSpacerMargin":
631 | panel.Background = Brushes.Transparent;
632 | break;
633 |
634 | //Editor window, collapsed item container
635 | case "Microsoft.VisualStudio.Text.Editor.Implementation.AdornmentLayer":
636 | foreach (var rectangle in panel.FindDescendants())
637 | rectangle.SetResourceReference(Shape.FillProperty, SolidBackgroundFillTertiaryLayeredKey);
638 | break;
639 |
640 | default:
641 | if (panel is DockPanel || (panel is Grid { Background: not null } && panel.FindAncestor() is not Button or TextBox))
642 | panel.Background = Brushes.Transparent;
643 | break;
644 | }
645 |
646 | else if (element is Border border)
647 | switch (border.GetType().FullName)
648 | {
649 | //Output window, base layer
650 | case "Microsoft.VisualStudio.PlatformUI.OutputWindow":
651 | border.Background = Brushes.Transparent;
652 | break;
653 |
654 | //Editor window, file errors container
655 | case "Microsoft.VisualStudio.UI.Text.Wpf.FileHealthIndicator.Implementation.FileHealthIndicatorMargin":
656 | border.Background = Brushes.Transparent;
657 | break;
658 | }
659 | }
660 | }
661 |
662 | private static ILHook CreatePostfix(MethodInfo info, Action action) =>
663 | new(info, context =>
664 | {
665 | ILCursor cursor = new(context);
666 | cursor.Index = cursor.Instrs.Count - 1; //Move cursor to end, but before return
667 |
668 | cursor.Emit(OpCodes.Ldarg_0); //this
669 | cursor.Emit(OpCodes.Ldarg_1); //First parameter
670 |
671 | cursor.EmitDelegate(action);
672 | });
673 |
674 | #region IsTrackedProperty
675 |
676 | ///
677 | /// Gets the value of the attached property from a given .
678 | ///
679 | /// The from which to read the property value.
680 | /// The value of the attached property.
681 | public static bool GetIsTracked(FrameworkElement target) =>
682 | (bool)target.GetValue(IsTrackedProperty);
683 |
684 | ///
685 | /// Sets the value of the attached property from a given .
686 | ///
687 | /// The on which to set the attached property.
688 | /// The property value to set.
689 | public static void SetIsTracked(FrameworkElement target, bool value) =>
690 | target.SetValue(IsTrackedProperty, value);
691 |
692 | ///
693 | /// Identifies the MicaVisualStudio.VisualStudio.VsWindowStyler.IsTracked dependency property.
694 | ///
695 | public static readonly DependencyProperty IsTrackedProperty =
696 | DependencyProperty.RegisterAttached("IsTracked", typeof(bool), typeof(VsWindowStyler), new(defaultValue: false));
697 |
698 | #endregion
699 |
700 | #region PopupOffsetProperty
701 |
702 | ///
703 | /// Gets the value of the attached property from a given .
704 | ///
705 | /// The from which to read the property value.
706 | /// The value of the attached property.
707 | public static Point GetPopupOffset(FrameworkElement target) =>
708 | (Point)target.GetValue(PopupOffsetProperty);
709 |
710 | ///
711 | /// Sets the value of the attached property from a given .
712 | ///
713 | /// The on which to set the attached property.
714 | /// The property value to set.
715 | public static void SetPopupOffset(FrameworkElement target, Point value) =>
716 | target.SetValue(PopupOffsetProperty, value);
717 |
718 | ///
719 | /// Identifies the MicaVisualStudio.VisualStudio.VsWindowStyler.IsTracked dependency property.
720 | ///
721 | public static readonly DependencyProperty PopupOffsetProperty =
722 | DependencyProperty.RegisterAttached("PopupOffset", typeof(Point), typeof(VsWindowStyler), new(defaultValue: default(Point)));
723 |
724 | #endregion
725 |
726 | #region IVsWindowFrameEvents
727 |
728 | public void OnFrameCreated(IVsWindowFrame frame) { }
729 |
730 | public void OnFrameDestroyed(IVsWindowFrame frame) { }
731 |
732 | public void OnFrameIsVisibleChanged(IVsWindowFrame frame, bool newIsVisible) { }
733 |
734 | public void OnFrameIsOnScreenChanged(IVsWindowFrame frame, bool newIsOnScreen)
735 | {
736 | if (newIsOnScreen)
737 | ApplyToWindowFrame(frame);
738 | }
739 |
740 | public void OnActiveFrameChanged(IVsWindowFrame oldFrame, IVsWindowFrame newFrame)
741 | {
742 | if (newFrame is not null)
743 | ApplyToWindowFrame(newFrame);
744 | }
745 |
746 | #endregion
747 |
748 | #region Dispose
749 |
750 | private bool disposed;
751 |
752 | ///
753 | /// Disposes the singleton instance of .
754 | ///
755 | public void Dispose()
756 | {
757 | if (disposed)
758 | return;
759 |
760 | visualHook?.Dispose();
761 | sourceHook?.Dispose();
762 | visualHook = sourceHook = null;
763 |
764 | #pragma warning disable VSTHRD010 //Invoke single-threaded types on Main thread
765 | if (cookie > 0)
766 | shell7.UnadviseWindowFrameEvents(cookie);
767 | #pragma warning restore VSTHRD010 //Invoke single-threaded types on Main thread
768 |
769 | disposed = true;
770 | }
771 |
772 | #endregion
773 | }
774 |
--------------------------------------------------------------------------------