├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE.md
└── CONTRIBUTING.md
├── art
├── dial-menu.png
├── dial-animation.gif
├── status-active.png
└── status-inactive.png
├── src
├── Resources
│ └── Icon.png
├── source.extension.ico
├── Providers
│ ├── Debug
│ │ ├── icon.png
│ │ ├── DebugControllerProvider.cs
│ │ └── DebugController.cs
│ ├── Find
│ │ ├── icon.png
│ │ ├── FindControllerProvider.cs
│ │ └── FindController.cs
│ ├── Zoom
│ │ ├── icon.png
│ │ ├── ZoomControllerProvider.cs
│ │ └── ZoomController.cs
│ ├── Editor
│ │ ├── icon.png
│ │ ├── EditorControllerProvider.cs
│ │ ├── ColorShifter.cs
│ │ ├── IntellisenseShifter.cs
│ │ ├── NumberShifter.cs
│ │ ├── EditorController.cs
│ │ └── HSLColor.cs
│ ├── Errors
│ │ ├── icon.png
│ │ ├── ErrorsControllerProvider.cs
│ │ └── ErrorsController.cs
│ ├── Scroll
│ │ ├── icon.png
│ │ ├── ScrollControllerProvider.cs
│ │ └── ScrollController.cs
│ ├── Bookmarks
│ │ ├── icon.png
│ │ ├── BookmarksControllerProvider.cs
│ │ └── BookmarksController.cs
│ ├── Navigate
│ │ ├── icon.png
│ │ ├── NavigateControllerProvider.cs
│ │ └── NavigateController.cs
│ ├── Customizable
│ │ ├── icon.png
│ │ ├── CustomizableControllerProvider.cs
│ │ └── CustomizableController.cs
│ ├── BaseController.cs
│ └── BaseTextController.cs
├── Contracts
│ ├── RotationDirection.cs
│ ├── IDialControllerHost.cs
│ ├── KnownProviders.cs
│ ├── ExportDialController.cs
│ ├── IDialControllerProvider.cs
│ └── IDialController.cs
├── Properties
│ └── AssemblyInfo.cs
├── VSCommandTable.cs
├── source.extension.cs
├── CustomOptions.cs
├── Controller
│ ├── RadialControllerInterop.cs
│ ├── RadialControllerStatusBarHost.cs
│ ├── StatusbarInjector.cs
│ └── DialControllerHost.cs
├── VSCommandTable.vsct
├── Helpers
│ ├── VsCommands.cs
│ ├── PositiveIntegerConverter.cs
│ └── VsHelpers.cs
├── app.config
├── source.extension.vsixmanifest
├── DialPackage.cs
├── Themes
│ └── Generic.xaml
├── CustomOptionsControl.cs
├── Options.cs
├── CustomOptionsControl.resx
├── source.extension.resx
├── CustomOptionsControl.Designer.cs
└── DialControllerTools.csproj
├── codealike.json
├── .gitattributes
├── LICENSE
├── appveyor.yml
├── CHANGELOG.md
├── DialControllerTools.sln
├── .editorconfig
├── README.md
└── .gitignore
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: madskristensen
4 |
--------------------------------------------------------------------------------
/art/dial-menu.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/art/dial-menu.png
--------------------------------------------------------------------------------
/art/dial-animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/art/dial-animation.gif
--------------------------------------------------------------------------------
/art/status-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/art/status-active.png
--------------------------------------------------------------------------------
/art/status-inactive.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/art/status-inactive.png
--------------------------------------------------------------------------------
/src/Resources/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/src/Resources/Icon.png
--------------------------------------------------------------------------------
/codealike.json:
--------------------------------------------------------------------------------
1 | {"projectId":"1eb46b66-1211-46b9-8177-3be015a542f0","projectName":"DialToolsForVS","neverTrack":false}
--------------------------------------------------------------------------------
/src/source.extension.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/src/source.extension.ico
--------------------------------------------------------------------------------
/src/Providers/Debug/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/src/Providers/Debug/icon.png
--------------------------------------------------------------------------------
/src/Providers/Find/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/src/Providers/Find/icon.png
--------------------------------------------------------------------------------
/src/Providers/Zoom/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/src/Providers/Zoom/icon.png
--------------------------------------------------------------------------------
/src/Providers/Editor/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/src/Providers/Editor/icon.png
--------------------------------------------------------------------------------
/src/Providers/Errors/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/src/Providers/Errors/icon.png
--------------------------------------------------------------------------------
/src/Providers/Scroll/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/src/Providers/Scroll/icon.png
--------------------------------------------------------------------------------
/src/Providers/Bookmarks/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/src/Providers/Bookmarks/icon.png
--------------------------------------------------------------------------------
/src/Providers/Navigate/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/src/Providers/Navigate/icon.png
--------------------------------------------------------------------------------
/src/Providers/Customizable/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/madskristensen/DialToolsForVS/master/src/Providers/Customizable/icon.png
--------------------------------------------------------------------------------
/src/Contracts/RotationDirection.cs:
--------------------------------------------------------------------------------
1 | namespace DialControllerTools
2 | {
3 | public enum RotationDirection
4 | {
5 | Left,
6 | Right
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Contracts/IDialControllerHost.cs:
--------------------------------------------------------------------------------
1 | namespace DialControllerTools
2 | {
3 | public interface IDialControllerHost
4 | {
5 | void RequestActivation(IDialController controller);
6 | void ReleaseActivation();
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/Contracts/KnownProviders.cs:
--------------------------------------------------------------------------------
1 | namespace DialControllerTools
2 | {
3 | public enum KnownProviders
4 | {
5 | Bookmarks,
6 | Debug,
7 | Editor,
8 | Errors,
9 | Navigation,
10 | Scroll,
11 | Zoom,
12 | Find,
13 | Customizable
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### Installed product versions
2 | - Visual Studio: [example 2015 Professional]
3 | - This extension: [example 1.1.21]
4 |
5 | ### Description
6 | Replace this text with a short description
7 |
8 | ### Steps to recreate
9 | 1. Replace this
10 | 2. text with
11 | 3. the steps
12 | 4. to recreate
13 |
14 | ### Current behavior
15 | Explain what it's doing and why it's wrong
16 |
17 | ### Expected behavior
18 | Explain what it should be doing after it's fixed.
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
4 | # Custom for Visual Studio
5 | *.cs diff=csharp
6 | *.sln merge=union
7 | *.csproj merge=union
8 | *.vbproj merge=union
9 | *.fsproj merge=union
10 | *.dbproj merge=union
11 |
12 | # Standard to msysgit
13 | *.doc diff=astextplain
14 | *.DOC diff=astextplain
15 | *.docx diff=astextplain
16 | *.DOCX diff=astextplain
17 | *.dot diff=astextplain
18 | *.DOT diff=astextplain
19 | *.pdf diff=astextplain
20 | *.PDF diff=astextplain
21 | *.rtf diff=astextplain
22 | *.RTF diff=astextplain
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2016 Mads Kristensen
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | image: Visual Studio 2022
2 |
3 | install:
4 | - ps: (new-object Net.WebClient).DownloadString("https://raw.github.com/madskristensen/ExtensionScripts/master/AppVeyor/vsix.ps1") | iex
5 |
6 | before_build:
7 | - ps: Vsix-IncrementVsixVersion | Vsix-UpdateBuildVersion
8 | - ps: Vsix-TokenReplacement src\source.extension.cs 'Version = "([0-9\\.]+)"' 'Version = "{version}"'
9 |
10 | build_script:
11 | - nuget restore -Verbosity quiet
12 | - msbuild /p:configuration=Release /p:DeployExtension=false /p:ZipPackageCompressionLevel=normal /v:m
13 |
14 | after_test:
15 | - ps: Vsix-PushArtifacts | Vsix-PublishToGallery
16 |
--------------------------------------------------------------------------------
/src/Contracts/ExportDialController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.Composition;
3 |
4 | namespace DialControllerTools
5 | {
6 | [MetadataAttribute]
7 | [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
8 | public class DialControllerProviderAttribute : ExportAttribute
9 | {
10 | public DialControllerProviderAttribute(int order) : base(typeof(IDialControllerProvider))
11 | {
12 | Order = order;
13 | }
14 |
15 | public int Order { get; }
16 | }
17 |
18 | public interface IDialMetadata
19 | {
20 | int Order { get; }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using DialControllerTools;
2 | using System.Reflection;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: System.Windows.ThemeInfo(System.Windows.ResourceDictionaryLocation.None, System.Windows.ResourceDictionaryLocation.SourceAssembly)]
6 | [assembly: AssemblyTitle(Vsix.Name)]
7 | [assembly: AssemblyDescription(Vsix.Description)]
8 | [assembly: AssemblyConfiguration("")]
9 | [assembly: AssemblyCompany(Vsix.Author)]
10 | [assembly: AssemblyProduct(Vsix.Name)]
11 | [assembly: AssemblyCopyright(Vsix.Author)]
12 | [assembly: AssemblyTrademark("")]
13 | [assembly: AssemblyCulture("")]
14 |
15 | [assembly: ComVisible(false)]
16 |
17 | [assembly: AssemblyVersion(Vsix.Version)]
18 | [assembly: AssemblyFileVersion(Vsix.Version)]
19 |
--------------------------------------------------------------------------------
/src/VSCommandTable.cs:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | //
3 | // This file was generated by Extensibility Tools v1.10.188
4 | //
5 | // ------------------------------------------------------------------------------
6 | namespace DialControllerTools
7 | {
8 | using System;
9 |
10 | ///
11 | /// Helper class that exposes all GUIDs used across VS Package.
12 | ///
13 | internal sealed partial class PackageGuids
14 | {
15 | public const string guidPackageString = "6080a241-4b73-4c04-8443-593502dc0ac1";
16 | public static Guid guidPackage = new Guid(guidPackageString);
17 | }
18 | ///
19 | /// Helper class that encapsulates all CommandIDs uses across VS Package.
20 | ///
21 | internal sealed partial class PackageIds
22 | {
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/source.extension.cs:
--------------------------------------------------------------------------------
1 | // ------------------------------------------------------------------------------
2 | //
3 | // This file was generated by Extensibility Tools v1.10.211
4 | //
5 | // ------------------------------------------------------------------------------
6 | namespace DialControllerTools
7 | {
8 | static class Vsix
9 | {
10 | public const string Id = "d4ce1d82-9bf6-4136-bd56-43cde615e0db";
11 | public const string Name = "Surface Dial Tools for Visual Studio";
12 | public const string Description = @"Adds features to Visual Studio specific to the Surface Dial. You don't need a Surface Book or Surface Studio to take advantage of the Surface Dial.";
13 | public const string Language = "en-US";
14 | public const string Version = "0.8";
15 | public const string Author = "Mads Kristensen";
16 | public const string Tags = "surface, dial";
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/CustomOptions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using System.Windows.Forms;
4 |
5 | using Microsoft.VisualStudio.Shell;
6 |
7 | namespace DialControllerTools
8 | {
9 | [ComVisible(true)]
10 | [Guid("6C43E501-226F-4E73-949C-BF8808A94B48")]
11 | public class CustomOptions : DialogPage
12 | {
13 | private string _clickAction = string.Empty;
14 | private string _rightAction = string.Empty;
15 | private string _leftAction = string.Empty;
16 |
17 | public string ClickAction { get => _clickAction; set => _clickAction = value; }
18 |
19 | public string RightAction { get => _rightAction; set => _rightAction = value; }
20 |
21 | public string LeftAction { get => _leftAction; set => _leftAction = value; }
22 |
23 | protected override IWin32Window Window
24 | => new CustomOptionsControl
25 | {
26 | CustomOptions = this
27 | };
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Controller/RadialControllerInterop.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Windows.UI.Input;
3 |
4 | namespace DialControllerTools
5 | {
6 | [System.Runtime.InteropServices.Guid("1B0535C9-57AD-45C1-9D79-AD5C34360513")]
7 | [System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIInspectable)]
8 | public interface IRadialControllerInterop
9 | {
10 | RadialController CreateForWindow(
11 | IntPtr hwnd,
12 | [System.Runtime.InteropServices.In]ref Guid riid);
13 | }
14 |
15 | [System.Runtime.InteropServices.Guid("787cdaac-3186-476d-87e4-b9374a7b9970")]
16 | [System.Runtime.InteropServices.InterfaceType(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIInspectable)]
17 | public interface IRadialControllerConfigurationInterop
18 | {
19 | RadialControllerConfiguration GetForWindow(
20 | IntPtr hwnd,
21 | [System.Runtime.InteropServices.In]ref Guid riid);
22 | }
23 | }
--------------------------------------------------------------------------------
/src/Providers/Customizable/CustomizableControllerProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | using Microsoft.VisualStudio.Shell;
5 |
6 | namespace DialControllerTools
7 | {
8 | [DialControllerProvider(order: 9)]
9 | internal class CustomizableControllerProvider : BaseDialControllerProvider
10 | {
11 | public static string Moniker = nameof(KnownProviders.Customizable);
12 |
13 | public CustomizableControllerProvider() { }
14 |
15 | protected override async Task TryCreateControllerAsyncOverride(IAsyncServiceProvider provider, CancellationToken cancellationToken)
16 | {
17 | string iconFilePath = VsHelpers.GetFileInVsix(@"Providers\Customizable\icon.png");
18 | var menuItem = await CreateMenuItemAsync(Moniker, iconFilePath);
19 | var dte = await provider.GetDteAsync(cancellationToken);
20 | return new CustomizableController(menuItem, dte);
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/Providers/BaseController.cs:
--------------------------------------------------------------------------------
1 |
2 | using Windows.UI.Input;
3 |
4 | namespace DialControllerTools
5 | {
6 | public abstract class BaseController : IDialController
7 | {
8 | public abstract string Moniker { get; }
9 |
10 | public RadialControllerMenuItem MenuItem { get; }
11 |
12 | public virtual bool CanHandleClick => false;
13 |
14 | public virtual bool CanHandleRotate => false;
15 |
16 | public virtual bool IsHapticFeedbackEnabled => true;
17 |
18 | public BaseController(RadialControllerMenuItem menuItem)
19 | {
20 | MenuItem = menuItem;
21 | menuItem.Invoked += (sender, args) => OnActivate();
22 | }
23 |
24 | public virtual void OnActivate()
25 | { }
26 |
27 | public virtual bool OnClick()
28 | {
29 | return false;
30 | }
31 |
32 | public virtual bool OnRotate(RotationDirection direction)
33 | {
34 | return false;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Providers/Debug/DebugControllerProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | using Microsoft.VisualStudio.Shell;
5 |
6 | namespace DialControllerTools
7 | {
8 | [DialControllerProvider(order: 5)]
9 | internal class DebugControllerProvider : BaseDialControllerProvider
10 | {
11 | public static string Moniker = nameof(KnownProviders.Debug);
12 |
13 | public DebugControllerProvider() { }
14 |
15 | protected override async Task TryCreateControllerAsyncOverride(IAsyncServiceProvider provider, CancellationToken cancellationToken)
16 | {
17 | string iconFilePath = VsHelpers.GetFileInVsix(@"Providers\Debug\icon.png");
18 | var menuItem = await CreateMenuItemAsync(Moniker, iconFilePath);
19 | var dte = await provider.GetDteAsync(cancellationToken);
20 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
21 | return new DebugController(menuItem, dte);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Providers/Errors/ErrorsControllerProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | using Microsoft.VisualStudio.Shell;
5 |
6 | namespace DialControllerTools
7 | {
8 | [DialControllerProvider(order: 6)]
9 | internal class ErrorsControllerProvider : BaseDialControllerProvider
10 | {
11 | public static string Moniker = nameof(KnownProviders.Errors);
12 |
13 | public ErrorsControllerProvider() { }
14 |
15 | protected override async Task TryCreateControllerAsyncOverride(IAsyncServiceProvider provider, CancellationToken cancellationToken)
16 | {
17 | string iconFilePath = VsHelpers.GetFileInVsix(@"Providers\Errors\icon.png");
18 | var menuItem = await CreateMenuItemAsync(Moniker, iconFilePath);
19 | var dte = await provider.GetDteAsync(cancellationToken);
20 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
21 | return new ErrorsController(menuItem, dte);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Providers/Find/FindControllerProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | using Microsoft.VisualStudio.Shell;
5 |
6 | namespace DialControllerTools
7 | {
8 | [DialControllerProvider(order: 8)]
9 | internal class FindControllerProvider : BaseDialControllerProvider
10 | {
11 | public static string Moniker = nameof(KnownProviders.Find);
12 |
13 | public FindControllerProvider() { }
14 |
15 | protected override async Task TryCreateControllerAsyncOverride(IAsyncServiceProvider provider, CancellationToken cancellationToken)
16 | {
17 | string iconFilePath = VsHelpers.GetFileInVsix(@"Providers\Find\icon.png");
18 | var menuItem = await CreateMenuItemAsync(Moniker, iconFilePath);
19 | var dte = await provider.GetDteAsync(cancellationToken);
20 | //IVsTextManager textManager = await provider.GetServiceAsync(cancellationToken);
21 | return new FindController(menuItem, dte);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Providers/Bookmarks/BookmarksControllerProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | using Microsoft.VisualStudio.Shell;
5 |
6 | namespace DialControllerTools
7 | {
8 | [DialControllerProvider(order: 7)]
9 | internal class BookmarksControllerProvider : BaseDialControllerProvider
10 | {
11 | public static string Moniker = nameof(KnownProviders.Bookmarks);
12 |
13 | public BookmarksControllerProvider() { }
14 |
15 | protected override async Task TryCreateControllerAsyncOverride(IAsyncServiceProvider provider, CancellationToken cancellationToken)
16 | {
17 | string iconFilePath = VsHelpers.GetFileInVsix("Providers\\Bookmarks\\icon.png");
18 | var menuItem = await CreateMenuItemAsync(Moniker, iconFilePath);
19 | var dte = await provider.GetDteAsync(cancellationToken);
20 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
21 | return new BookmarksController(menuItem, dte);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/Providers/Zoom/ZoomControllerProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | using Microsoft.VisualStudio.Shell;
5 | using Microsoft.VisualStudio.TextManager.Interop;
6 |
7 | namespace DialControllerTools
8 | {
9 | [DialControllerProvider(order: 2)]
10 | internal class ZoomControllerProvider : BaseDialControllerProvider
11 | {
12 | public static string Moniker = nameof(KnownProviders.Zoom);
13 |
14 | public ZoomControllerProvider() { }
15 |
16 | protected override async Task TryCreateControllerAsyncOverride(IAsyncServiceProvider provider, CancellationToken cancellationToken)
17 | {
18 | string iconFilePath = VsHelpers.GetFileInVsix(@"Providers\Zoom\icon.png");
19 | var menuItem = await CreateMenuItemAsync(Moniker, iconFilePath);
20 | var dte = await provider.GetDteAsync(cancellationToken);
21 | IVsTextManager textManager = await provider.GetServiceAsync(cancellationToken);
22 | return new ZoomController(menuItem, dte, textManager);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Providers/Editor/EditorControllerProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | using Microsoft.VisualStudio.Shell;
5 | using Microsoft.VisualStudio.TextManager.Interop;
6 |
7 | namespace DialControllerTools
8 | {
9 | [DialControllerProvider(4)]
10 | internal class EditorControllerProvider : BaseDialControllerProvider
11 | {
12 | public static string Moniker = nameof(KnownProviders.Editor);
13 |
14 | public EditorControllerProvider() { }
15 |
16 | protected override async Task TryCreateControllerAsyncOverride(IAsyncServiceProvider provider, CancellationToken cancellationToken)
17 | {
18 | string iconFilePath = VsHelpers.GetFileInVsix(@"Providers\Editor\icon.png");
19 | var menuItem = await CreateMenuItemAsync(Moniker, iconFilePath);
20 | var dte = await provider.GetDteAsync(cancellationToken);
21 | IVsTextManager textManager = await provider.GetServiceAsync(cancellationToken);
22 | return new EditorController(menuItem, dte, textManager);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Providers/Navigate/NavigateControllerProvider.cs:
--------------------------------------------------------------------------------
1 | using System.Threading;
2 | using System.Threading.Tasks;
3 |
4 | using Microsoft.VisualStudio.Shell;
5 | using Microsoft.VisualStudio.TextManager.Interop;
6 |
7 | namespace DialControllerTools
8 | {
9 | [DialControllerProvider(order: 3)]
10 | internal class NavigateControllerProvider : BaseDialControllerProvider
11 | {
12 | public static string Moniker = nameof(KnownProviders.Navigation);
13 |
14 | public NavigateControllerProvider() { }
15 |
16 | protected override async Task TryCreateControllerAsyncOverride(IAsyncServiceProvider provider, CancellationToken cancellationToken)
17 | {
18 | string iconFilePath = VsHelpers.GetFileInVsix(@"Providers\Navigate\icon.png");
19 | var menuItem = await CreateMenuItemAsync(Moniker, iconFilePath);
20 | var dte = await provider.GetDteAsync(cancellationToken);
21 | IVsTextManager textManager = await provider.GetServiceAsync(cancellationToken);
22 | return new NavigateController(menuItem, dte, textManager);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Providers/Editor/ColorShifter.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.Text;
2 | using System.Drawing;
3 | using System.Globalization;
4 |
5 | namespace DialControllerTools
6 | {
7 | public static class ColorShifter
8 | {
9 | public static string Shift(SnapshotSpan bufferSpan, RotationDirection direction)
10 | {
11 | string text = bufferSpan.GetText();
12 |
13 | int argb = int.Parse(text.Substring(1), NumberStyles.HexNumber);
14 | var color = Color.FromArgb(argb);
15 |
16 | double factor = direction == RotationDirection.Left ? -3 : 3;
17 |
18 | Color newColor = AdjustBrightness(color, factor);
19 |
20 | return ConvertToHex(newColor).ToLowerInvariant();
21 | }
22 |
23 | private static string ConvertToHex(Color c)
24 | {
25 | return "#" + c.R.ToString("X2") + c.G.ToString("X2") + c.B.ToString("X2");
26 | }
27 |
28 | public static Color AdjustBrightness(Color color, double darkenAmount)
29 | {
30 | var hslColor = new HSLColor(color);
31 | hslColor.Luminosity += darkenAmount; // 0 to 1
32 | return hslColor;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Providers/Navigate/NavigateController.cs:
--------------------------------------------------------------------------------
1 | using EnvDTE;
2 |
3 | using EnvDTE80;
4 |
5 | using Microsoft.VisualStudio.TextManager.Interop;
6 |
7 | using Windows.UI.Input;
8 |
9 | namespace DialControllerTools
10 | {
11 | internal class NavigateController : BaseTextController
12 | {
13 | private readonly Commands _commands;
14 |
15 | public override string Moniker => NavigateControllerProvider.Moniker;
16 | public override bool CanHandleRotate => true;
17 |
18 | public NavigateController(RadialControllerMenuItem menuItem, DTE2 dte, IVsTextManager textManager)
19 | : base(menuItem, textManager)
20 | {
21 | _commands = dte.Commands;
22 | }
23 |
24 | public override bool OnRotate(RotationDirection direction)
25 | {
26 | switch (direction)
27 | {
28 | case RotationDirection.Left:
29 | _commands.ExecuteCommand("View.NavigateBackward");
30 | break;
31 | case RotationDirection.Right:
32 | _commands.ExecuteCommand("View.NavigateForward");
33 | break;
34 | }
35 |
36 | return true;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/VSCommandTable.vsct:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/Providers/Find/FindController.cs:
--------------------------------------------------------------------------------
1 | using EnvDTE;
2 |
3 | using EnvDTE80;
4 |
5 | using Windows.UI.Input;
6 |
7 | namespace DialControllerTools
8 | {
9 | internal class FindController : BaseController
10 | {
11 | private readonly Commands _commands;
12 |
13 | public override string Moniker => FindControllerProvider.Moniker;
14 | public override bool CanHandleClick => true;
15 | public override bool CanHandleRotate => true;
16 |
17 | public FindController(RadialControllerMenuItem menuItem, DTE2 dte) : base(menuItem)
18 | {
19 | _commands = dte.Commands;
20 | }
21 |
22 | public override bool OnClick()
23 | {
24 | _commands.ExecuteCommand("Edit.FindInFiles");
25 | return true;
26 | }
27 |
28 | public override bool OnRotate(RotationDirection direction)
29 | {
30 | switch (direction)
31 | {
32 | case RotationDirection.Left:
33 | _commands.ExecuteCommand("Edit.GoToPrevLocation");
34 | break;
35 | case RotationDirection.Right:
36 | _commands.ExecuteCommand("Edit.GoToNextLocation");
37 | break;
38 | }
39 |
40 | return true;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Providers/Scroll/ScrollControllerProvider.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.Composition;
2 | using System.Threading;
3 | using System.Threading.Tasks;
4 |
5 | using Microsoft.VisualStudio.Language.Intellisense;
6 | using Microsoft.VisualStudio.Shell;
7 | using Microsoft.VisualStudio.TextManager.Interop;
8 |
9 | namespace DialControllerTools
10 | {
11 | [DialControllerProvider(order: 1)]
12 | internal class ScrollControllerProvider : BaseDialControllerProvider
13 | {
14 | public static string Moniker = nameof(KnownProviders.Scroll);
15 |
16 | [Import]
17 | private ICompletionBroker CompletionBroker { get; set; }
18 |
19 | public ScrollControllerProvider() { }
20 |
21 | protected override async Task TryCreateControllerAsyncOverride(IAsyncServiceProvider provider, CancellationToken cancellationToken)
22 | {
23 | string iconFilePath = VsHelpers.GetFileInVsix(@"Providers\Scroll\icon.png");
24 | var menuItem = await CreateMenuItemAsync(Moniker, iconFilePath);
25 | var dte = await provider.GetDteAsync(cancellationToken);
26 | IVsTextManager textManager = await provider.GetServiceAsync(cancellationToken);
27 | return new ScrollController(menuItem, dte, textManager);
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/Providers/Customizable/CustomizableController.cs:
--------------------------------------------------------------------------------
1 | using EnvDTE;
2 |
3 | using EnvDTE80;
4 |
5 | using Windows.UI.Input;
6 |
7 | namespace DialControllerTools
8 | {
9 | internal class CustomizableController : BaseController
10 | {
11 | private readonly Commands _commands;
12 |
13 | public override string Moniker => CustomizableControllerProvider.Moniker;
14 | public override bool CanHandleClick => true;
15 | public override bool CanHandleRotate => true;
16 |
17 | public CustomizableController(RadialControllerMenuItem menuItem, DTE2 dte) : base(menuItem)
18 | {
19 | _commands = dte.Commands;
20 | }
21 |
22 | public override bool OnClick()
23 | {
24 | _commands.ExecuteCommand(DialPackage.CustomOptions.ClickAction);
25 | return true;
26 | }
27 |
28 | public override bool OnRotate(RotationDirection direction)
29 | {
30 | switch (direction)
31 | {
32 | case RotationDirection.Left:
33 | _commands.ExecuteCommand(DialPackage.CustomOptions.LeftAction);
34 | break;
35 | case RotationDirection.Right:
36 | _commands.ExecuteCommand(DialPackage.CustomOptions.RightAction);
37 | break;
38 | }
39 |
40 | return true;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Road map
2 |
3 | - [ ] Add telemtry
4 | - [x] Accurate zoom reset
5 |
6 | Features that have a checkmark are complete and available for
7 | download in the
8 | [CI build](http://vsixgallery.com/extension/d4ce1d82-9bf6-4136-bd56-43cde615e0db/).
9 |
10 | # Change log
11 |
12 | These are the changes to each version that has been released
13 | on the official Visual Studio extension gallery.
14 |
15 | ## 1.0
16 |
17 | - [x] Migrate to Visual Studio 64-bit and dropped suppot of version older than 17.0
18 |
19 | ## 0.7
20 |
21 | - [x] Only select Error List if it contains errors
22 | - [x] Intellisense rotation shifter
23 | - [x] Option to select default dial menu item
24 | - [x] Reset Zoom should move ZoomIn and ZoomOut till reaching 100%
25 | - [x] Fixed missing tooltip on status bar control
26 |
27 | ## 0.6
28 |
29 | - [x] Status bar text update on activation state change
30 | - [x] Output Window logging
31 | - [x] Add bookmark navigation
32 | - [x] Add debug navigation
33 | - [x] Reset zoom level on click
34 | - [x] Add status bar text to show selected menu item
35 | - [x] Add editor shifter
36 | - [x] New icons
37 | - [x] Make zoom reset work for all open documentss
38 | - [x] Get rid of DialEventArgs
39 | - [x] Switch to error dial item when Error List is activated
40 |
41 | ## 0.5
42 |
43 | - [x] Initial release
44 | - [x] Error navigator by rotation
45 | - [x] Status bar indicator
46 | - [x] Default global provider handles basic rotate and click
--------------------------------------------------------------------------------
/src/Helpers/VsCommands.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Immutable;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using System.Reflection;
6 |
7 | namespace DialControllerTools.Helpers
8 | {
9 | public static class VsCommands
10 | {
11 | internal static ImmutableArray ParseCommands(string commandsString) =>
12 | commandsString
13 | .Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
14 | .ToImmutableArray();
15 |
16 | internal static string ReadCommandsAsString()
17 | {
18 | var assembly = Assembly.GetExecutingAssembly();
19 | const string resourceName = "DialControllerTools.Resources.commands.txt";
20 | var stream = assembly.GetManifestResourceStream(resourceName);
21 | using (var reader = new StreamReader(stream))
22 | {
23 | return reader.ReadToEnd();
24 | }
25 | }
26 |
27 | [Conditional("Debug")]
28 | internal static void CheckEmptyEntries(string commandsAsString)
29 | {
30 | using (var reader = new StringReader(commandsAsString))
31 | {
32 | string command;
33 | while ((command = reader.ReadLine()) != null)
34 | {
35 | Debug.Assert(!string.IsNullOrEmpty(command));
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/DialControllerTools.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26212.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DialControllerTools", "src\DialControllerTools.csproj", "{8BB1C8EF-0C40-40A0-A2DF-D07010432122}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{375D9B50-EADE-4F25-9C01-ABFE3A9611D7}"
9 | ProjectSection(SolutionItems) = preProject
10 | appveyor.yml = appveyor.yml
11 | CHANGELOG.md = CHANGELOG.md
12 | README.md = README.md
13 | EndProjectSection
14 | EndProject
15 | Global
16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
17 | Debug|Any CPU = Debug|Any CPU
18 | Release|Any CPU = Release|Any CPU
19 | EndGlobalSection
20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
21 | {8BB1C8EF-0C40-40A0-A2DF-D07010432122}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {8BB1C8EF-0C40-40A0-A2DF-D07010432122}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {8BB1C8EF-0C40-40A0-A2DF-D07010432122}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {8BB1C8EF-0C40-40A0-A2DF-D07010432122}.Release|Any CPU.Build.0 = Release|Any CPU
25 | EndGlobalSection
26 | GlobalSection(SolutionProperties) = preSolution
27 | HideSolutionNode = FALSE
28 | EndGlobalSection
29 | GlobalSection(ExtensibilityGlobals) = postSolution
30 | SolutionGuid = {B4B5FFC0-8C7F-4E83-AC8D-D0C07D196AD4}
31 | EndGlobalSection
32 | EndGlobal
33 |
--------------------------------------------------------------------------------
/src/Providers/BaseTextController.cs:
--------------------------------------------------------------------------------
1 |
2 | using System.ComponentModel.Composition;
3 |
4 | using Microsoft.VisualStudio;
5 | using Microsoft.VisualStudio.Editor;
6 | using Microsoft.VisualStudio.Text.Editor;
7 | using Microsoft.VisualStudio.TextManager.Interop;
8 |
9 | using Windows.UI.Input;
10 |
11 | namespace DialControllerTools
12 | {
13 | public abstract class BaseTextController : BaseController
14 | {
15 | private readonly IVsTextManager textManager;
16 |
17 | #pragma warning disable IDE0044 // Add readonly modifier
18 | [Import]
19 | private IVsEditorAdaptersFactoryService editorAdapter;
20 | #pragma warning restore IDE0044 // Add readonly modifier
21 |
22 | internal BaseTextController(RadialControllerMenuItem menuItem, IVsTextManager textManager) : base(menuItem)
23 | {
24 | this.textManager = textManager;
25 | }
26 |
27 | public IVsTextView GetCurrentNativeTextView()
28 | {
29 | ErrorHandler.ThrowOnFailure(textManager.GetActiveView(1, null, out IVsTextView activeView));
30 | return activeView;
31 | }
32 |
33 | ///Gets the TextView for the active document.
34 | public IWpfTextView GetCurrentTextView() => GetTextView(GetCurrentNativeTextView());
35 |
36 | public IWpfTextView GetTextView(IVsTextView nativeView)
37 | {
38 | if (nativeView == null)
39 | return null;
40 |
41 | return editorAdapter.GetWpfTextView(nativeView);
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Providers/Zoom/ZoomController.cs:
--------------------------------------------------------------------------------
1 | using EnvDTE;
2 |
3 | using EnvDTE80;
4 |
5 | using Microsoft.VisualStudio.Text.Editor;
6 | using Microsoft.VisualStudio.TextManager.Interop;
7 |
8 | using Windows.UI.Input;
9 |
10 | namespace DialControllerTools
11 | {
12 | internal class ZoomController : BaseTextController
13 | {
14 | private readonly Commands _commands;
15 |
16 | public override string Moniker => ZoomControllerProvider.Moniker;
17 | public override bool CanHandleClick => true;
18 | public override bool CanHandleRotate => true;
19 |
20 | public ZoomController(RadialControllerMenuItem menuItem, DTE2 dte, IVsTextManager textManager)
21 | : base(menuItem, textManager)
22 | {
23 | _commands = dte.Commands;
24 | }
25 |
26 | public override bool OnClick()
27 | {
28 | IWpfTextView view = GetCurrentTextView();
29 |
30 | if (view == null || view.ZoomLevel == 100)
31 | return false;
32 |
33 | view.ZoomLevel = 100;
34 | _commands.ExecuteCommand("View.ZoomOut");
35 | _commands.ExecuteCommand("View.ZoomIn");
36 |
37 | return true;
38 | }
39 |
40 | public override bool OnRotate(RotationDirection direction)
41 | {
42 | if (direction == RotationDirection.Right)
43 | {
44 | return _commands.ExecuteCommand("View.ZoomIn");
45 | }
46 | else
47 | {
48 | return _commands.ExecuteCommand("View.ZoomOut");
49 | }
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/src/Providers/Editor/IntellisenseShifter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Forms;
3 |
4 | using Microsoft.VisualStudio.Language.Intellisense;
5 | using Microsoft.VisualStudio.Shell;
6 | using Microsoft.VisualStudio.Text.Editor;
7 |
8 | namespace DialControllerTools
9 | {
10 | public static class IntellisenseShifter
11 | {
12 | public static bool TryShift(ITextView view, ICompletionBroker broker, RotationDirection direction)
13 | {
14 | try
15 | {
16 | string key = direction == RotationDirection.Right ? "{DOWN}" : "{UP}";
17 |
18 | if (!broker.IsCompletionActive(view))
19 | {
20 | ICompletionSession session = broker.TriggerCompletion(view);
21 |
22 | if (session == null || !session.IsStarted)
23 | return false;
24 |
25 | Completion active = session.CompletionSets?[0]?.SelectionStatus?.Completion;
26 |
27 | if (active != null)
28 | {
29 | SendKeys.SendWait(key);
30 | session.Commit();
31 |
32 | return true;
33 | }
34 | }
35 | else
36 | {
37 | SendKeys.Send(key);
38 | return true;
39 | }
40 | }
41 | catch (Exception ex)
42 | {
43 | var outputPane = ThreadHelper.JoinableTaskContext.Factory.Run(DialPackage.GetOutputPaneAsync);
44 | outputPane.WriteLine("Intellisense shifter failed");
45 | outputPane.WriteLine(ex.ToString());
46 | }
47 |
48 | return false;
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/source.extension.vsixmanifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Surface Dial Tools for Visual Studio 64-bit
6 | Adds features to Visual Studio specific to the Surface Dial. You don't need a Surface Book or Surface Studio to take advantage of the Surface Dial.
7 | https://github.com/madskristensen/DialToolsForVS/blob/master/CHANGELOG.md
8 | Resources\LICENSE
9 | https://github.com/madskristensen/DialToolsForVS
10 | Resources\Icon.png
11 | Resources\Icon.png
12 | surface, dial
13 | true
14 |
15 |
16 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/Contracts/IDialControllerProvider.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel.Composition;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | using Microsoft.VisualStudio.Shell;
7 |
8 | using Windows.Storage;
9 | using Windows.Storage.Streams;
10 | using Windows.UI.Input;
11 |
12 | namespace DialControllerTools
13 | {
14 | public interface IDialControllerProvider
15 | {
16 | Task TryCreateControllerAsync(IAsyncServiceProvider provider, CancellationToken cancellationToken);
17 | }
18 |
19 | public abstract class BaseDialControllerProvider : IDialControllerProvider
20 | {
21 | [Import]
22 | private readonly ICompositionService compositionService;
23 |
24 | public async Task TryCreateControllerAsync(IAsyncServiceProvider provider, CancellationToken cancellationToken)
25 | {
26 | var controller = await TryCreateControllerAsyncOverride(provider, cancellationToken);
27 | compositionService.SatisfyImportsOnce(controller);
28 | return controller;
29 | }
30 |
31 | #pragma warning disable VSTHRD200 // Use "Async" suffix for async methods
32 | protected abstract Task TryCreateControllerAsyncOverride(IAsyncServiceProvider provider, CancellationToken cancellationToken);
33 | #pragma warning restore VSTHRD200 // Use "Async" suffix for async methods
34 |
35 | internal static async Task CreateMenuItemAsync(string moniker, string iconFilePath)
36 | {
37 | StorageFile file = await StorageFile.GetFileFromPathAsync(iconFilePath);
38 |
39 | var stream = RandomAccessStreamReference.CreateFromFile(file);
40 | return RadialControllerMenuItem.CreateFromIcon(moniker, stream);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Providers/Editor/NumberShifter.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.VisualStudio.Text;
2 | using System;
3 | using System.Globalization;
4 |
5 | namespace DialControllerTools
6 | {
7 | public static class NumberShifter
8 | {
9 | public static string Shift(SnapshotSpan bufferSpan, RotationDirection direction)
10 | {
11 | string text = bufferSpan.GetText();
12 |
13 | float delta = GetDelta(text);
14 | string format = text.IndexOf('.') >= 0 ? "#.#0" : string.Empty;
15 |
16 | if (NumberDecimalPlaces(text) == 1)
17 | format = "F1";
18 |
19 | if (!double.TryParse(text, out double _number))
20 | return null;
21 |
22 | if (direction == RotationDirection.Left)
23 | {
24 | return (_number - delta).ToString(format, CultureInfo.InvariantCulture);
25 | }
26 | else
27 | {
28 | return (_number + delta).ToString(format, CultureInfo.InvariantCulture);
29 | }
30 | }
31 |
32 | private static float GetDelta(string value)
33 | {
34 | int decimals = NumberDecimalPlaces(value);
35 | if (decimals > 0)
36 | {
37 | if (decimals > 1)
38 | return 0.01F;
39 | else
40 | return 0.1F;
41 | }
42 |
43 | return 1F;
44 | }
45 |
46 | private static int NumberDecimalPlaces(string value)
47 | {
48 | int s = value.IndexOf('.') + 1; // the first numbers plus decimal point
49 | if (s == 0) // No decimal point
50 | return 0;
51 |
52 | return value.Length - s; //total length minus beginning numbers and decimal = number of decimal points
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Helpers/PositiveIntegerConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Globalization;
4 |
5 | namespace DialControllerTools.Helpers
6 | {
7 | public class PositiveIntegerConverter : TypeConverter
8 | {
9 | private const int MinVal = 1;
10 | private const int MaxVal = 20;
11 |
12 | public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
13 | {
14 | return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
15 | }
16 |
17 | public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
18 | {
19 | if (value is not string)
20 | {
21 | return base.ConvertFrom(context, culture, value);
22 | }
23 |
24 | int input;
25 | try
26 | {
27 | input = Convert.ToInt32(value);
28 | }
29 | catch (FormatException exception)
30 | {
31 | throw new FormatException($"The value should be between {MinVal} and {MaxVal}.", exception);
32 | }
33 |
34 | if (input is < MinVal or > MaxVal)
35 | {
36 | throw new FormatException($"The value should be between {MinVal} and {MaxVal}.");
37 | }
38 |
39 | return input;
40 | }
41 |
42 | public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
43 | {
44 | if (destinationType == null)
45 | {
46 | throw new ArgumentNullException(nameof(destinationType));
47 | }
48 | if (destinationType == typeof(string))
49 | {
50 | int num = Convert.ToInt32(value);
51 | return num.ToString();
52 | }
53 | return base.ConvertTo(context, culture, value, destinationType);
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/src/Providers/Bookmarks/BookmarksController.cs:
--------------------------------------------------------------------------------
1 | using EnvDTE;
2 |
3 | using EnvDTE80;
4 |
5 | using Windows.UI.Input;
6 |
7 | namespace DialControllerTools
8 | {
9 | internal class BookmarksController : BaseController
10 | {
11 | private readonly Commands _commands;
12 | private readonly WindowEvents _events;
13 |
14 | public BookmarksController(RadialControllerMenuItem menuItem, DTE2 dte) : base(menuItem)
15 | {
16 | _commands = dte.Commands;
17 | // Switched in provider
18 | #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread
19 | _events = dte.Events.WindowEvents;
20 | _events.WindowActivated += OnToolWindowActivated;
21 | #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread
22 | }
23 |
24 | private void OnToolWindowActivated(Window GotFocus, Window LostFocus)
25 | {
26 | if (GotFocus.IsBookmarks()) DialPackage.DialControllerHost.RequestActivation(this);
27 | else if (LostFocus.IsBookmarks()) DialPackage.DialControllerHost.ReleaseActivation();
28 | }
29 |
30 | public override string Moniker => BookmarksControllerProvider.Moniker;
31 | public override bool CanHandleClick => true;
32 | public override bool CanHandleRotate => true;
33 |
34 | public override void OnActivate()
35 | {
36 | _commands.ExecuteCommand("View.BookmarkWindow");
37 | }
38 |
39 | public override bool OnClick()
40 | {
41 | _commands.ExecuteCommand("Edit.ToggleBookmark");
42 | return true;
43 | }
44 |
45 | public override bool OnRotate(RotationDirection direction)
46 | {
47 | switch (direction)
48 | {
49 | case RotationDirection.Left:
50 | _commands.ExecuteCommand("Edit.PreviousBookmark");
51 | break;
52 | case RotationDirection.Right:
53 | _commands.ExecuteCommand("Edit.NextBookmark");
54 | break;
55 | }
56 |
57 | return true;
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Helpers/VsHelpers.cs:
--------------------------------------------------------------------------------
1 |
2 | using System;
3 | using System.IO;
4 | using System.Reflection;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 |
8 | using EnvDTE;
9 |
10 | using EnvDTE80;
11 |
12 | using Microsoft.VisualStudio.Shell;
13 |
14 | namespace DialControllerTools
15 | {
16 | internal static class VsHelpers
17 | {
18 | internal static async Task GetServiceAsync(this IAsyncServiceProvider provider, CancellationToken cancellationToken)
19 | => (TReturnType)await provider.GetServiceAsync(typeof(TServiceType));
20 |
21 | internal static Task GetDteAsync(this IAsyncServiceProvider provider, CancellationToken cancellationToken)
22 | => provider.GetServiceAsync(cancellationToken);
23 |
24 | public static string GetFileInVsix(string relativePath)
25 | {
26 | string folder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
27 | return Path.Combine(folder, relativePath);
28 | }
29 |
30 | public static bool IsSolutionExplorer(this Window window)
31 | {
32 | return IsTool(window) && window.Type == vsWindowType.vsWindowTypeSolutionExplorer;
33 | }
34 |
35 | public static bool IsErrorList(this Window window)
36 | {
37 | return IsTool(window) && window.ObjectKind == WindowKinds.vsWindowKindErrorList;
38 | }
39 |
40 | public static bool IsBookmarks(this Window window)
41 | {
42 | return IsTool(window) && window.ObjectKind == WindowKinds.vsWindowKindBookmarks;
43 | }
44 |
45 | public static bool IsDocument(this Window window) => window?.Kind == "Document";
46 | public static bool IsTool(this Window window) => window?.Kind == "Tool";
47 |
48 | public static bool ExecuteCommand(this Commands commands, string commandName)
49 | {
50 | try
51 | {
52 | Command command = commands.Item(commandName);
53 |
54 | if (command != null && command.IsAvailable)
55 | {
56 | commands.Raise(command.Guid, command.ID, null, null);
57 | return true;
58 | }
59 | }
60 | catch (Exception ex)
61 | {
62 | System.Diagnostics.Debug.Write(ex);
63 | }
64 |
65 | return false;
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Contracts/IDialController.cs:
--------------------------------------------------------------------------------
1 | namespace DialControllerTools;
2 |
3 | using System;
4 | using System.Collections.Immutable;
5 | using System.Linq;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | using Microsoft.VisualStudio.ComponentModelHost;
10 | using Microsoft.VisualStudio.Shell;
11 |
12 | using Windows.UI.Input;
13 |
14 | public interface IDialController
15 | {
16 | string Moniker { get; }
17 | RadialControllerMenuItem MenuItem { get; }
18 | bool CanHandleClick { get; }
19 | bool CanHandleRotate { get; }
20 | bool IsHapticFeedbackEnabled { get; }
21 | bool OnClick();
22 | bool OnRotate(RotationDirection direction);
23 | void OnActivate();
24 | }
25 |
26 | internal static class DialControllerExtensions
27 | {
28 | internal static async Task> GetControllersAsync(this IAsyncServiceProvider serviceProvider, CancellationToken cancellationToken)
29 | {
30 | var compositionService = await serviceProvider.GetServiceAsync(cancellationToken);
31 | var providers = compositionService.DefaultExportProvider.GetExports();
32 | var tasks = providers
33 | //this is the true add to the menu: TryCreateControllerAsync calls back to AddMenuItemAsync
34 | .Select(async provider =>
35 | {
36 | try
37 | {
38 | var controller = await provider.Value.TryCreateControllerAsync(serviceProvider, cancellationToken);
39 | return (Controller: controller, provider.Metadata.Order);
40 | }
41 | catch (Exception ex)
42 | {
43 | var outputPane = await DialPackage.GetOutputPaneAsync();
44 | var providerName = provider.GetType().GenericTypeArguments.First().GetType().Name;
45 | await outputPane.WriteLineAsync($"Error creating controller: {providerName}");
46 | await outputPane.WriteLineAsync(ex.ToString());
47 | return (Controller: null, provider.Metadata.Order);
48 | }
49 | }).ToList();
50 |
51 | var controllers = await Task.WhenAll(tasks);
52 | return controllers
53 | .Where(result => result.Controller != null)
54 | .OrderBy(result => result.Order)
55 | .Select(result => result.Controller)
56 | .ToImmutableArray();
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/Providers/Errors/ErrorsController.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 |
3 | using EnvDTE;
4 |
5 | using EnvDTE80;
6 |
7 | using Microsoft.VisualStudio.Shell;
8 |
9 | using Windows.UI.Input;
10 |
11 | namespace DialControllerTools
12 | {
13 | internal class ErrorsController : BaseController
14 | {
15 | private readonly DTE2 _dte;
16 | private readonly IErrorList _errorList;
17 | private readonly WindowEvents _events;
18 | public override string Moniker => ErrorsControllerProvider.Moniker;
19 |
20 | public override bool CanHandleClick => true;
21 |
22 | public override bool CanHandleRotate => _errorList.TableControl.Entries.Any();
23 |
24 |
25 | public ErrorsController(RadialControllerMenuItem menuItem, DTE2 dte) : base(menuItem)
26 | {
27 | _dte = dte;
28 | // Switched in provider
29 | #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread
30 | _errorList = _dte.ToolWindows.ErrorList as IErrorList;
31 | _events = _dte.Events.WindowEvents;
32 | _events.WindowActivated += OnToolWindowActivated;
33 | #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread
34 | }
35 |
36 | private void OnToolWindowActivated(Window GotFocus, Window LostFocus)
37 | {
38 | if (GotFocus.IsErrorList() && _errorList.TableControl.Entries.Any())
39 | DialPackage.DialControllerHost.RequestActivation(this);
40 | else if (LostFocus.IsErrorList())
41 | DialPackage.DialControllerHost.ReleaseActivation();
42 | }
43 |
44 | // Radial Controller events always occur on the UI thread
45 | #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread
46 | public override void OnActivate() => _dte.ToolWindows.ErrorList.Parent?.Activate();
47 | #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread
48 |
49 | public override bool OnClick()
50 | {
51 | OnActivate();
52 | return true;
53 | }
54 |
55 | public override bool OnRotate(RotationDirection direction)
56 | {
57 | var commands = _dte.Commands;
58 | switch (direction)
59 | {
60 | case RotationDirection.Left:
61 | commands.ExecuteCommand("View.PreviousError");
62 | break;
63 | case RotationDirection.Right:
64 | commands.ExecuteCommand("View.NextError");
65 | break;
66 | }
67 |
68 | return true;
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome:http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Don't use tabs for indentation.
7 | [*]
8 | indent_style = space
9 | end_of_line = crlf
10 | # (Please don't specify an indent_size here; that has too many unintended consequences.)
11 |
12 | # Code files
13 | [*.{cs,csx,vb,vbx}]
14 | indent_size = 4
15 |
16 | # Xml project files
17 | [*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}]
18 | indent_size = 2
19 |
20 | # Xml config files
21 | [*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}]
22 | indent_size = 2
23 |
24 | # JSON files
25 | [*.json]
26 | indent_size = 2
27 |
28 | # Dotnet code style settings:
29 | [*.{cs, vb}]
30 | # Sort using and Import directives with System.* appearing first
31 | dotnet_sort_system_directives_first = true
32 | # Avoid "this." and "Me." if not necessary
33 | dotnet_style_qualification_for_field = false:suggestion
34 | dotnet_style_qualification_for_property = false:suggestion
35 | dotnet_style_qualification_for_method = false:suggestion
36 | dotnet_style_qualification_for_event = false:suggestion
37 |
38 | # Use language keywords instead of framework type names for type references
39 | dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion
40 | dotnet_style_predefined_type_for_member_access = true:suggestion
41 |
42 | # Suggest more modern language features when available
43 | dotnet_style_object_initializer = true:suggestion
44 | dotnet_style_collection_initializer = true:suggestion
45 | dotnet_style_coalesce_expression = true:suggestion
46 | dotnet_style_null_propagation = true:suggestion
47 | dotnet_style_explicit_tuple_names = true:suggestion
48 |
49 | # CSharp code style settings:
50 | [*.cs]
51 | # Prefer "var" everywhere
52 | csharp_style_var_for_built_in_types = false:suggestion
53 | csharp_style_var_when_type_is_apparent = true:suggestion
54 | csharp_style_var_elsewhere = false:suggestion
55 |
56 | # Prefer method-like constructs to have a block body
57 | csharp_style_expression_bodied_methods = false:none
58 | csharp_style_expression_bodied_constructors = false:none
59 | csharp_style_expression_bodied_operators = false:none
60 |
61 | # Prefer property-like constructs to have an expression-body
62 | csharp_style_expression_bodied_properties = true:none
63 | csharp_style_expression_bodied_indexers = true:none
64 | csharp_style_expression_bodied_accessors = true:none
65 |
66 | # Suggest more modern language features when available
67 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
68 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
69 | csharp_style_inlined_variable_declaration = true:suggestion
70 | csharp_style_throw_expression = true:suggestion
71 | csharp_style_conditional_delegate_call = true:suggestion
72 |
73 | # Newline settings
74 | csharp_new_line_before_open_brace = all
75 | csharp_new_line_before_else = true
76 | csharp_new_line_before_catch = true
77 | csharp_new_line_before_finally = true
78 | csharp_new_line_before_members_in_object_initializers = true
79 | csharp_new_line_before_members_in_anonymous_types = true
--------------------------------------------------------------------------------
/src/DialPackage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Runtime.InteropServices;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 |
6 | using Community.VisualStudio.Toolkit;
7 |
8 | using Microsoft.VisualStudio;
9 | using Microsoft.VisualStudio.Shell;
10 | using Microsoft.VisualStudio.Shell.Interop;
11 |
12 | using Tasks = System.Threading.Tasks;
13 |
14 | namespace DialControllerTools
15 | {
16 | [Guid(PackageGuids.guidPackageString)]
17 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
18 | [InstalledProductRegistration("#110", "#112", Vsix.Version, IconResourceID = 400)]
19 | [ProvideMenuResource("Menus.ctmenu", 1)]
20 | [ProvideOptionPage(typeof(Options), "Surface Dial", "General", 0, 0, true, ProvidesLocalizedCategoryName = false)]
21 | [ProvideOptionPage(typeof(CustomOptions), "Surface Dial", "Custom controls", 0, 0, true, ProvidesLocalizedCategoryName = false)]
22 | [ProvideAutoLoad(VSConstants.UICONTEXT.ShellInitialized_string, PackageAutoLoadFlags.BackgroundLoad)]
23 | internal sealed class DialPackage : AsyncPackage
24 | {
25 | public static Options Options { get; private set; }
26 | public static CustomOptions CustomOptions { get; private set; }
27 | public static DialControllerHost DialControllerHost { get; private set; }
28 |
29 | private async Task GetServiceAsync(CancellationToken cancellationToken)
30 | => (TReturnType)await GetServiceAsync(typeof(TServiceType));
31 |
32 | internal static Task GetOutputPaneAsync() => OutputWindowPane.CreateAsync(Vsix.Name);
33 |
34 | protected override async Tasks.Task InitializeAsync(CancellationToken cancellationToken, IProgress progress)
35 | {
36 | async Task InitializeDialControllerHostAsync()
37 | {
38 | void LoadOptions()
39 | {
40 | Options = (Options)GetDialogPage(typeof(Options));
41 | CustomOptions = (CustomOptions)GetDialogPage(typeof(CustomOptions));
42 | }
43 |
44 | try
45 | {
46 | var serviceProvider = await GetServiceAsync(cancellationToken);
47 | var outputPane = await GetOutputPaneAsync();
48 | LoadOptions();
49 | var controllers = await serviceProvider.GetControllersAsync(cancellationToken);
50 |
51 | DialControllerHost = new DialControllerHost(outputPane, controllers);
52 | Options.OptionsApplied += DialControllerHost.OptionsApplied;
53 | }
54 | catch (Exception ex)
55 | {
56 | await ex.LogAsync("DialControllerHost.InitializeAsync");
57 | }
58 | }
59 |
60 | KnownUIContexts.ShellInitializedContext.WhenActivated(() => ThreadHelper.JoinableTaskFactory.StartOnIdle(InitializeDialControllerHostAsync));
61 | await base.InitializeAsync(cancellationToken, progress);
62 | }
63 | }
64 | }
--------------------------------------------------------------------------------
/src/Providers/Scroll/ScrollController.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.Composition;
2 | using System.Windows.Forms;
3 |
4 | using EnvDTE;
5 |
6 | using EnvDTE80;
7 |
8 | using Microsoft.VisualStudio.Language.Intellisense;
9 | using Microsoft.VisualStudio.Text.Editor;
10 | using Microsoft.VisualStudio.TextManager.Interop;
11 |
12 | using Windows.UI.Input;
13 |
14 | namespace DialControllerTools
15 | {
16 | internal class ScrollController : BaseTextController
17 | {
18 | private readonly DTE2 _dte;
19 | #pragma warning disable IDE0044 // Add readonly modifier
20 | [Import]
21 | private ICompletionBroker _broker;
22 | #pragma warning restore IDE0044 // Add readonly modifier
23 |
24 | public override string Moniker => ScrollControllerProvider.Moniker;
25 | public override bool CanHandleClick => true;
26 | public override bool CanHandleRotate => true;
27 | public override bool IsHapticFeedbackEnabled => false;
28 |
29 | public ScrollController(RadialControllerMenuItem menuItem, DTE2 dte, IVsTextManager textManager)
30 | : base(menuItem, textManager)
31 | {
32 | _dte = dte;
33 | }
34 |
35 | #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread
36 | public override bool OnClick()
37 | {
38 | if (_dte.ActiveWindow.IsDocument())
39 | {
40 | IWpfTextView view = GetCurrentTextView();
41 |
42 | if (view != null && view.HasAggregateFocus && !_broker.IsCompletionActive(view))
43 | SendKeys.Send("+{F10}");
44 | else
45 | SendKeys.Send("{ENTER}");
46 | }
47 | else if (_dte.ActiveWindow.IsSolutionExplorer())
48 | {
49 | var selectedItems = _dte.ToolWindows.SolutionExplorer.SelectedItems as UIHierarchyItem[];
50 |
51 | if (selectedItems == null || selectedItems.Length != 1)
52 | return false;
53 |
54 | if (selectedItems[0].UIHierarchyItems.Expanded)
55 | {
56 | SendKeys.Send("{LEFT}");
57 | }
58 | else
59 | {
60 | SendKeys.Send("{RIGHT}");
61 | }
62 | }
63 | else
64 | {
65 | SendKeys.Send("{ENTER}");
66 | }
67 |
68 | return true;
69 | }
70 | #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread
71 |
72 | public override bool OnRotate(RotationDirection direction)
73 | {
74 | bool handled = false;
75 | if (_dte.ActiveWindow.IsDocument())
76 | {
77 | IWpfTextView view = GetCurrentTextView();
78 |
79 | if (view != null && view.HasAggregateFocus)
80 | {
81 | string cmd = direction == RotationDirection.Left ? "Edit.ScrollLineUp" : "Edit.ScrollLineDown";
82 |
83 | for (int i = 0; i < DialPackage.Options.LinesToScroll; i++)
84 | {
85 | handled = _dte.Commands.ExecuteCommand(cmd);
86 | }
87 | }
88 | }
89 |
90 | if (!handled)
91 | {
92 | string key = direction == RotationDirection.Left ? "{UP}" : "{DOWN}";
93 | SendKeys.Send(key);
94 | }
95 |
96 | return true;
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Themes/Generic.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
59 |
60 |
--------------------------------------------------------------------------------
/src/Controller/RadialControllerStatusBarHost.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 | using System.Windows.Controls;
3 |
4 | using Microsoft.VisualStudio.PlatformUI.Shell.Controls;
5 |
6 | namespace DialControllerTools
7 | {
8 | ///
9 | /// Follow steps 1a or 1b and then 2 to use this custom control in a XAML file.
10 | ///
11 | /// Step 1a) Using this custom control in a XAML file that exists in the current project.
12 | /// Add this XmlNamespace attribute to the root element of the markup file where it is
13 | /// to be used:
14 | ///
15 | /// xmlns:MyNamespace="clr-namespace:DialToolsForVS"
16 | ///
17 | ///
18 | /// Step 1b) Using this custom control in a XAML file that exists in a different project.
19 | /// Add this XmlNamespace attribute to the root element of the markup file where it is
20 | /// to be used:
21 | ///
22 | /// xmlns:MyNamespace="clr-namespace:DialToolsForVS;assembly=DialToolsForVS"
23 | ///
24 | /// You will also need to add a project reference from the project where the XAML file lives
25 | /// to this project and Rebuild to avoid compilation errors:
26 | ///
27 | /// Right click on the target project in the Solution Explorer and
28 | /// "Add Reference"->"Projects"->[Browse to and select this project]
29 | ///
30 | ///
31 | /// Step 2)
32 | /// Go ahead and use your control in the XAML file.
33 | ///
34 | ///
35 | ///
36 | ///
37 | [TemplatePart(Name = "PART_TextControl", Type = typeof(TabItemTextControl))]
38 | [TemplateVisualState(GroupName = "ActivityStates", Name = RadialControllerStatusBarHost.ActiveStateName)]
39 | [TemplateVisualState(GroupName = "ActivityStates", Name = RadialControllerStatusBarHost.InactiveStateName)]
40 | public class RadialControllerStatusBarHost : Control
41 | {
42 | internal const string ActiveStateName = "Active";
43 | internal const string InactiveStateName = "Inactive";
44 |
45 | static RadialControllerStatusBarHost()
46 | {
47 | DefaultStyleKeyProperty.OverrideMetadata(typeof(RadialControllerStatusBarHost), new FrameworkPropertyMetadata(typeof(RadialControllerStatusBarHost)));
48 | }
49 |
50 | public RadialControllerStatusBarHost()
51 | {
52 | ToolTip = "The Surface Dial is inactive.";
53 | }
54 |
55 | #region Text
56 |
57 | public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(RadialControllerStatusBarHost), new FrameworkPropertyMetadata(null, OnTextChanged));
58 |
59 | private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
60 | {
61 | (d as RadialControllerStatusBarHost).IsActive = e.NewValue != null;
62 | }
63 |
64 | public string Text
65 | {
66 | get => (string)GetValue(TextProperty);
67 | set => SetValue(TextProperty, value);
68 | }
69 |
70 | #endregion Text
71 |
72 | #region IsActive
73 |
74 | public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register(nameof(IsActive), typeof(bool), typeof(RadialControllerStatusBarHost), new FrameworkPropertyMetadata(false, OnIsActiveChanged));
75 |
76 | private static void OnIsActiveChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
77 | {
78 | var control = (d as RadialControllerStatusBarHost);
79 | if ((bool)e.NewValue == true)
80 | {
81 | control.ToolTip = "The Surface Dial is active.";
82 | //bool result = VisualStateManager.GoToState(control, ActiveStateName, false);
83 | }
84 | else
85 | {
86 | control.ToolTip = "The Surface Dial is inactive.";
87 | //bool result = VisualStateManager.GoToState(control, InactiveStateName, false);
88 | control.SetValue(TextProperty, DependencyProperty.UnsetValue);
89 | }
90 | }
91 |
92 | public bool IsActive
93 | {
94 | get => (bool)GetValue(IsActiveProperty);
95 | set => SetValue(IsActiveProperty, value);
96 | }
97 |
98 | #endregion IsActive
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/CustomOptionsControl.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Immutable;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows.Forms;
7 | using DialControllerTools.Helpers;
8 | using Microsoft.VisualStudio.Threading;
9 | using ThreadHelper = Microsoft.VisualStudio.Shell.ThreadHelper;
10 | //Timer idea from https://stackoverflow.com/questions/26020799/enforcing-a-delay-on-textbox-textchanged
11 | namespace DialControllerTools
12 | {
13 | public partial class CustomOptionsControl : UserControl
14 | {
15 | private string _selectedText;
16 | private Timer _timer;
17 | private readonly string commandsString;
18 |
19 | private ImmutableArray commands;
20 | private ImmutableArray Commands
21 | => commands.IsDefaultOrEmpty
22 | ? commands = VsCommands.ParseCommands(commandsString)
23 | : commands;
24 |
25 | private CustomOptions customOptions;
26 | internal CustomOptions CustomOptions
27 | {
28 | get => customOptions;
29 | set
30 | {
31 | customOptions = value;
32 | AssignedClickLabel.Text = customOptions.ClickAction;
33 | AssignedRightLabel.Text = customOptions.RightAction;
34 | AssignedLeftLabel.Text = customOptions.LeftAction;
35 | }
36 | }
37 |
38 | public CustomOptionsControl()
39 | {
40 | InitializeComponent();
41 | CommandsBox.Text = commandsString = VsCommands.ReadCommandsAsString();
42 | VsCommands.CheckEmptyEntries(commandsString);
43 |
44 | _timer = new Timer();
45 | _timer.Interval = 300;
46 | _timer.Tick += Timer_Tick;
47 | }
48 |
49 | private void AssignClickAction_Click(object sender, EventArgs e)
50 | {
51 | if (!string.IsNullOrEmpty(_selectedText))
52 | {
53 | CustomOptions.ClickAction = _selectedText;
54 | AssignedClickLabel.Text = _selectedText;
55 | }
56 | }
57 |
58 | private void AssignRightAction_Click(object sender, EventArgs e)
59 | {
60 | if (!string.IsNullOrEmpty(_selectedText))
61 | {
62 | CustomOptions.RightAction = _selectedText;
63 | AssignedRightLabel.Text = _selectedText;
64 | }
65 | }
66 |
67 | private void AssignLeftAction_Click(object sender, EventArgs e)
68 | {
69 | if (!string.IsNullOrEmpty(_selectedText))
70 | {
71 | CustomOptions.LeftAction = _selectedText;
72 | AssignedLeftLabel.Text = _selectedText;
73 | }
74 | }
75 |
76 | private void SearchBox_TextChanged(object sender, EventArgs e)
77 | {
78 | _timer.Stop();
79 | _timer.Tag = SearchBox.Text;
80 | _timer.Start();
81 | }
82 |
83 | private void Timer_Tick(object sender, EventArgs e)
84 | {
85 | var searchText = _timer.Tag?.ToString() ?? string.Empty;
86 | var results = Commands.Where(c => c.IndexOf(searchText, StringComparison.InvariantCultureIgnoreCase) > -1);
87 | int newLineSymbolsLength = Environment.NewLine.Length;
88 | CommandsBox.Text = results.Any()
89 | ? results.Aggregate(
90 | new StringBuilder(results.Sum(r => r.Length + newLineSymbolsLength)),
91 | (accum, item) =>
92 | {
93 | accum.Append(item);
94 | accum.Append(Environment.NewLine);
95 | return accum;
96 | },
97 | accum =>
98 | {
99 | accum.Length -= newLineSymbolsLength;
100 | return accum.ToString();
101 | })
102 | : string.Empty;
103 | }
104 |
105 | private void CommandsBox_MouseClick(object sender, MouseEventArgs e)
106 | {
107 | var current_line = CommandsBox.GetLineFromCharIndex(CommandsBox.SelectionStart);
108 | _selectedText = CommandsBox.Lines[current_line];
109 | CommandsBox.SelectionStart = CommandsBox.GetFirstCharIndexFromLine(current_line);
110 | CommandsBox.SelectionLength = _selectedText?.Length ?? 0;
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/Providers/Editor/EditorController.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.ComponentModel.Composition;
3 | using System.Text.RegularExpressions;
4 |
5 | using EnvDTE80;
6 |
7 | using Microsoft.VisualStudio.Language.Intellisense;
8 | using Microsoft.VisualStudio.Text;
9 | using Microsoft.VisualStudio.Text.Editor;
10 | using Microsoft.VisualStudio.Text.Formatting;
11 | using Microsoft.VisualStudio.TextManager.Interop;
12 |
13 | using Windows.UI.Input;
14 |
15 | namespace DialControllerTools
16 | {
17 | internal class EditorController : BaseTextController
18 | {
19 | private readonly DTE2 _dte;
20 |
21 | #pragma warning disable IDE0044 // Add readonly modifier
22 | [Import]
23 | private ICompletionBroker _broker;
24 | #pragma warning restore IDE0044 // Add readonly modifier
25 | private IWpfTextView _view;
26 | private delegate string Shift(SnapshotSpan bufferSpan, RotationDirection direction);
27 | private Dictionary _dic = new Dictionary()
28 | {
29 | { @"(#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3}))\b", ColorShifter.Shift },
30 | { @"(\b|\-)[0-9\.]+", NumberShifter.Shift }
31 | };
32 |
33 | public EditorController(RadialControllerMenuItem menuItem, DTE2 dte, IVsTextManager textManager)
34 | : base(menuItem, textManager)
35 | {
36 | _dte = dte;
37 | }
38 |
39 | public override string Moniker => EditorControllerProvider.Moniker;
40 |
41 | public override bool CanHandleRotate
42 | {
43 | get
44 | {
45 | if (!_dte.ActiveWindow.IsDocument())
46 | return false;
47 |
48 | _view = GetCurrentTextView();
49 |
50 | return _view != null && _view.HasAggregateFocus;
51 | }
52 | }
53 |
54 | public override bool CanHandleClick => true;
55 |
56 | public override bool OnClick()
57 | {
58 | _view = GetCurrentTextView();
59 |
60 | if (_view == null || !_view.HasAggregateFocus)
61 | return false;
62 |
63 | if (_broker.IsCompletionActive(_view))
64 | {
65 | _broker.GetSessions(_view)[0].Commit();
66 | }
67 | else
68 | {
69 | _dte.Commands.ExecuteCommand("Edit.ListMembers");
70 | }
71 |
72 | return true;
73 | }
74 |
75 | public override bool OnRotate(RotationDirection direction)
76 | {
77 | if (_view == null)
78 | return false;
79 |
80 | foreach (string pattern in _dic.Keys)
81 | {
82 | if (TryGetMatch(pattern, out SnapshotSpan span))
83 | {
84 | string value = _dic[pattern].Invoke(span, direction);
85 |
86 | if (string.IsNullOrEmpty(value))
87 | continue;
88 |
89 | UpdateSpan(span, value);
90 |
91 | return true;
92 | }
93 | }
94 |
95 | return IntellisenseShifter.TryShift(_view, _broker, direction);
96 | }
97 |
98 | private bool TryGetMatch(string pattern, out SnapshotSpan span)
99 | {
100 | SnapshotPoint position = _view.Caret.Position.BufferPosition;
101 | IWpfTextViewLine line = _view.GetTextViewLineContainingBufferPosition(position);
102 | MatchCollection matches = Regex.Matches(line.Extent.GetText(), pattern);
103 |
104 | foreach (Match match in matches)
105 | {
106 | var matchSpan = new Span(line.Start + match.Index, match.Length);
107 |
108 | if (matchSpan.Start <= position && matchSpan.End >= position)
109 | {
110 | span = new SnapshotSpan(_view.TextBuffer.CurrentSnapshot, matchSpan);
111 | return true;
112 | }
113 | }
114 |
115 | span = new SnapshotSpan();
116 | return false;
117 | }
118 |
119 | private static void UpdateSpan(SnapshotSpan span, string result)
120 | {
121 | if (result.Length > 1)
122 | result = result.TrimStart('0');
123 |
124 | using (ITextEdit edit = span.Snapshot.TextBuffer.CreateEdit())
125 | {
126 | edit.Replace(span, result);
127 | edit.Apply();
128 | }
129 | }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Surface Dial Tools for Visual Studio
2 |
3 | [](https://ci.appveyor.com/project/madskristensen/dialtoolsforvs)
4 |
5 | Download this extension from the [Marketplace](https://marketplace.visualstudio.com/items?itemName=MadsKristensen.SurfaceDialToolsforVisualStudio)
6 | or get the [CI build](http://vsixgallery.com/extension/d4ce1d82-9bf6-4136-bd56-43cde615e0db/).
7 |
8 | ---------------------------------------
9 |
10 | Adds features to Visual Studio specific to the [Surface Dial](https://www.microsoftstore.com/store/msusa/en_US/pdp/Surface-Dial/productID.5074013900). You don't need a Surface Book or Surface Studio to take advantage of the Surface Dial.
11 |
12 | A Surface Dial is $99 and works with all modern Windows 10 PCs. [Get yours today!](https://www.microsoftstore.com/store/msusa/en_US/pdp/Surface-Dial/productID.5074013900)
13 |
14 | See the [change log](CHANGELOG.md) for changes and road map.
15 |
16 | ## Features
17 |
18 | - Status bar indicator
19 | - Scrolling
20 | - Zooming
21 | - Navigation
22 | - Debugging
23 | - Errors
24 | - Editor shifters
25 | - Bookmarks
26 |
27 | 
28 |
29 | ### Status bar indicator
30 | An icon is placed at the left corner of the Status Bar indicating the current state of the Dial.
31 |
32 | When the Visual Studio item on the Dial menu hasn't been activated, the status bar icon looks like this
33 |
34 | 
35 |
36 | When it is active, the icon changes to a solid white dot and a label showing what the selected menu item is appears.
37 |
38 | 
39 |
40 | ### Scrolling
41 | Scroll documents and tool windows.
42 |
43 | - **Rotate right**: does the same as arrow down
44 | - **Rotate left**: does the same as arrow up
45 | - **Click**: In a document it opens the context menu, otherwise it does the same as then ENTER key. In Solution Explorer it expands/collapses folders.
46 |
47 | ### Zooming
48 | Zooming is enhanced so that it only zoom the text editors and not other artifacts in Visual Studio
49 |
50 | - **Rotate right**: Zoom in
51 | - **Rotate left**: Zoom out
52 | - **Click**: Resets zoom level to 100%
53 |
54 | ### Navigation
55 | Takes you to the previous and next location in your files.
56 |
57 | - **Rotate right**: navigates forward
58 | - **Rotate left**: navigates backwards
59 | - **Click**: [nothing]
60 |
61 | ### Debugging
62 | When a breakpoint is hit, use the dial to step into, over and out.
63 |
64 | - **Rotate right**: When not debugging, go to next breakpoint. Otherwise step over
65 | - **Rotate left**: When not debugging, go to previous breakpoint. Otherwise step out
66 | - **Click**: When not debugging, starts the debugger. Otherwise step into
67 |
68 | When a breakpoint is hit, the Debug dial item is automatically activated as well
69 |
70 | ### Errors
71 | When the error list contain errors, the dial makes it easy to navigate to the next error in the list.
72 |
73 | - **Rotate right**: go to next error
74 | - **Rotate left**: go to previous error
75 | - **Click**: Show the Error List
76 |
77 | When the Error List window is activated, the Errors dial item is automatically activated if it contains any errors.
78 |
79 | ### Editor shifters
80 | Shifting is a way to modify text in the editor based on the Dial rotation. When the caret is placed in a supported position, the shifting is enalbed.
81 |
82 | #### Hex colors (e.g. #ff000)
83 | - **Rotate right**: lightens the color value
84 | - **Rotate left**: darkens the color value
85 | - **Click**: [no action]
86 |
87 | #### Numbers (e.g. 123 or 3.4 or .6)
88 | - **Rotate right**: increase the number
89 | - **Rotate left**: decrease the number
90 | - **Click**: [no action]
91 |
92 | ### Bookmarks
93 | Set and navigate to your bookmarks
94 |
95 | - **Rotate right**: next bookmark
96 | - **Rotate left**: previous bookmark
97 | - **Click**: toggle bookmark
98 |
99 | When the Bookmarks window is activated, the Bookmarks dial item is automatically activated as well.
100 |
101 | ## Suggest new behaviors
102 | Please send ideas about new behaviors you would find helpful to the [GitHub issue tracker](https://github.com/madskristensen/DialToolsForVS/issues).
103 |
104 | ## Contribute
105 | Check out the [contribution guidelines](.github/CONTRIBUTING.md)
106 | if you want to contribute to this project.
107 |
108 | For cloning and building this project yourself, make sure
109 | to install the
110 | [Extensibility Tools 2015](https://visualstudiogallery.msdn.microsoft.com/ab39a092-1343-46e2-b0f1-6a3f91155aa6)
111 | extension for Visual Studio which enables some features
112 | used by this project.
113 |
114 | ## License
115 | [Apache 2.0](LICENSE)
--------------------------------------------------------------------------------
/src/Controller/StatusbarInjector.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Windows;
6 | using System.Windows.Automation.Peers;
7 | using System.Windows.Controls;
8 | using System.Windows.Interop;
9 | using System.Windows.Media;
10 | using Microsoft.VisualStudio.Shell;
11 | using Microsoft.VisualStudio.Threading;
12 | using Task = System.Threading.Tasks.Task;
13 |
14 | namespace DialControllerTools
15 | {
16 | internal class StatusBarInjector
17 | {
18 | private readonly Panel _panel;
19 |
20 | public StatusBarInjector(Window window)
21 | {
22 | _panel = FindChild(window, "StatusBarPanel") as Panel;
23 |
24 | //var wih = new WindowInteropHelper(window);
25 | //if (TryFindWorkThreadStatusBarContainer(wih.Handle, out FrameworkElement candidate))
26 | //{
27 | // _panel = candidate.Parent as Panel;
28 | //}
29 | }
30 |
31 | private bool TryFindWorkThreadStatusBarContainer(IntPtr hwnd, out FrameworkElement candidateElement)
32 | {
33 | candidateElement = null;
34 |
35 | HwndSource source = HwndSource.FromHwnd(hwnd);
36 | var rootVisual = source?.RootVisual as FrameworkElement;
37 | if (rootVisual == null)
38 | {
39 | return false;
40 | }
41 |
42 | UIElementAutomationPeer statusBarAutomationPeer = this.GetStatusBarAutomationPeer(rootVisual);
43 | if (statusBarAutomationPeer == null)
44 | {
45 | return false;
46 | }
47 |
48 | candidateElement = statusBarAutomationPeer.Owner as FrameworkElement;
49 | return candidateElement != null;
50 | }
51 |
52 | private UIElementAutomationPeer GetStatusBarAutomationPeer(FrameworkElement element)
53 | {
54 | UIElementAutomationPeer automationPeer = UIElementAutomationPeer.CreatePeerForElement(element) as UIElementAutomationPeer;
55 |
56 | return this.EnumerateElement(automationPeer, peer =>
57 | peer?.GetAutomationControlType() == AutomationControlType.StatusBar
58 | && peer.GetAutomationId() == "StatusBarContainer");
59 | }
60 |
61 | private UIElementAutomationPeer EnumerateElement(UIElementAutomationPeer peer, Predicate predicate)
62 | {
63 | foreach (UIElementAutomationPeer automationPeer in peer.GetChildren())
64 | if (predicate(automationPeer))
65 | {
66 | return automationPeer;
67 | }
68 |
69 | foreach (UIElementAutomationPeer automationPeer in peer.GetChildren())
70 | {
71 | peer = EnumerateElement(automationPeer, predicate);
72 | if (peer != null)
73 | return peer;
74 | }
75 |
76 | return null;
77 | }
78 |
79 |
80 | private static DependencyObject FindChild(DependencyObject parent, string childName)
81 | {
82 | if (parent == null)
83 | {
84 | return null;
85 | }
86 |
87 | int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
88 |
89 | for (int i = 0; i < childrenCount; i++)
90 | {
91 | DependencyObject child = VisualTreeHelper.GetChild(parent, i);
92 |
93 | if (child is FrameworkElement frameworkElement && frameworkElement.Name == childName)
94 | {
95 | return frameworkElement;
96 | }
97 | }
98 |
99 | for (int i = 0; i < childrenCount; i++)
100 | {
101 | DependencyObject child = VisualTreeHelper.GetChild(parent, i);
102 |
103 | child = StatusBarInjector.FindChild(child, childName);
104 |
105 | if (child != null)
106 | {
107 | return child;
108 | }
109 | }
110 |
111 | return null;
112 | }
113 |
114 | public void InjectControl(FrameworkElement control)
115 | => _panel.Children.Insert(1, control);
116 |
117 | public JoinableTask IsInjectedAsync(FrameworkElement control)
118 | => ThreadHelper.JoinableTaskFactory.RunAsync(VsTaskRunContext.UIThreadNormalPriority,
119 | () => Task.FromResult(_panel.Children.Contains(control)));
120 |
121 | public JoinableTask UninjectControlAsync(FrameworkElement control) => ThreadHelper.JoinableTaskFactory
122 | .StartOnIdle(() =>
123 | _panel.Children.Remove(control), VsTaskRunContext.UIThreadNormalPriority);
124 | }
125 | }
--------------------------------------------------------------------------------
/src/Providers/Editor/HSLColor.cs:
--------------------------------------------------------------------------------
1 | using System.Drawing;
2 |
3 | namespace DialControllerTools
4 | {
5 | // From https://richnewman.wordpress.com/about/code-listings-and-diagrams/hslcolor-class/
6 | internal class HSLColor
7 | {
8 | // Private data members below are on scale 0-1
9 | // They are scaled for use externally based on scale
10 | private double hue = 1.0;
11 | private double saturation = 1.0;
12 | private double luminosity = 1.0;
13 |
14 | private const double scale = 240.0;
15 |
16 | public double Hue
17 | {
18 | get { return hue * scale; }
19 | set { hue = CheckRange(value / scale); }
20 | }
21 | public double Saturation
22 | {
23 | get { return saturation * scale; }
24 | set { saturation = CheckRange(value / scale); }
25 | }
26 | public double Luminosity
27 | {
28 | get { return luminosity * scale; }
29 | set { luminosity = CheckRange(value / scale); }
30 | }
31 |
32 | private static double CheckRange(double value)
33 | {
34 | if (value < 0.0)
35 | value = 0.0;
36 | else if (value > 1.0)
37 | value = 1.0;
38 | return value;
39 | }
40 |
41 | public override string ToString()
42 | {
43 | return string.Format("H: {0:#0.##} S: {1:#0.##} L: {2:#0.##}", Hue, Saturation, Luminosity);
44 | }
45 |
46 | public string ToRGBString()
47 | {
48 | Color color = this;
49 | return string.Format("R: {0:#0.##} G: {1:#0.##} B: {2:#0.##}", color.R, color.G, color.B);
50 | }
51 |
52 | #region Casts to/from System.Drawing.Color
53 | public static implicit operator Color(HSLColor hslColor)
54 | {
55 | double r = 0, g = 0, b = 0;
56 | if (hslColor.luminosity != 0)
57 | {
58 | if (hslColor.saturation == 0)
59 | r = g = b = hslColor.luminosity;
60 | else
61 | {
62 | double temp2 = GetTemp2(hslColor);
63 | double temp1 = 2.0 * hslColor.luminosity - temp2;
64 |
65 | r = GetColorComponent(temp1, temp2, hslColor.hue + 1.0 / 3.0);
66 | g = GetColorComponent(temp1, temp2, hslColor.hue);
67 | b = GetColorComponent(temp1, temp2, hslColor.hue - 1.0 / 3.0);
68 | }
69 | }
70 | return Color.FromArgb((int)(255 * r), (int)(255 * g), (int)(255 * b));
71 | }
72 |
73 | private static double GetColorComponent(double temp1, double temp2, double temp3)
74 | {
75 | temp3 = MoveIntoRange(temp3);
76 | if (temp3 < 1.0 / 6.0)
77 | return temp1 + (temp2 - temp1) * 6.0 * temp3;
78 | else if (temp3 < 0.5)
79 | return temp2;
80 | else if (temp3 < 2.0 / 3.0)
81 | return temp1 + ((temp2 - temp1) * ((2.0 / 3.0) - temp3) * 6.0);
82 | else
83 | return temp1;
84 | }
85 | private static double MoveIntoRange(double temp3)
86 | {
87 | if (temp3 < 0.0)
88 | temp3 += 1.0;
89 | else if (temp3 > 1.0)
90 | temp3 -= 1.0;
91 | return temp3;
92 | }
93 | private static double GetTemp2(HSLColor hslColor)
94 | {
95 | double temp2;
96 | if (hslColor.luminosity < 0.5) //<=??
97 | temp2 = hslColor.luminosity * (1.0 + hslColor.saturation);
98 | else
99 | temp2 = hslColor.luminosity + hslColor.saturation - (hslColor.luminosity * hslColor.saturation);
100 | return temp2;
101 | }
102 |
103 | public static implicit operator HSLColor(Color color)
104 | {
105 | return new HSLColor()
106 | {
107 | hue = color.GetHue() / 360.0, // we store hue as 0-1 as opposed to 0-360
108 | luminosity = color.GetBrightness(),
109 | saturation = color.GetSaturation()
110 | };
111 | }
112 | #endregion
113 |
114 | public void SetRGB(int red, int green, int blue)
115 | {
116 | var hslColor = (HSLColor)Color.FromArgb(red, green, blue);
117 | hue = hslColor.hue;
118 | saturation = hslColor.saturation;
119 | luminosity = hslColor.luminosity;
120 | }
121 |
122 | public HSLColor() { }
123 | public HSLColor(Color color)
124 | {
125 | SetRGB(color.R, color.G, color.B);
126 | }
127 | public HSLColor(int red, int green, int blue)
128 | {
129 | SetRGB(red, green, blue);
130 | }
131 | public HSLColor(double hue, double saturation, double luminosity)
132 | {
133 | Hue = hue;
134 | Saturation = saturation;
135 | Luminosity = luminosity;
136 | }
137 |
138 | }
139 | }
--------------------------------------------------------------------------------
/src/Options.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.Runtime.InteropServices;
5 |
6 | using DialControllerTools.Helpers;
7 |
8 | using Microsoft.VisualStudio.Shell;
9 |
10 | namespace DialControllerTools
11 | {
12 | [ComVisible(true)]
13 | public class Options : DialogPage
14 | {
15 | public event EventHandler OptionsApplied;
16 |
17 | [Category("Menu")]
18 | [DisplayName("Default menu")]
19 | [Description("Determines the default Dial menu item when opening Visual Studio.")]
20 | [DefaultValue(KnownProviders.Scroll)]
21 | [TypeConverter(typeof(EnumConverter))]
22 | public KnownProviders DefaultProvider { get; set; }
23 |
24 | [Category("Menu")]
25 | [DisplayName("Show Bookmarks menu")]
26 | [Description("Set to true to show the Bookmarks menu")]
27 | [DefaultValue(true)]
28 | [TypeConverter(typeof(BooleanConverter))]
29 | public bool ShowBookmarksMenu { get; set; } = true;
30 |
31 | [Category("Menu")]
32 | [DisplayName("Show Customizable menu")]
33 | [Description("Set to true to show the Customizable menu")]
34 | [DefaultValue(false)]
35 | [TypeConverter(typeof(BooleanConverter))]
36 | public bool ShowCustomizableMenu { get; set; } = false;
37 |
38 | [Category("Menu")]
39 | [DisplayName("Show Debug menu")]
40 | [Description("Set to true to show the Debug menu")]
41 | [DefaultValue(true)]
42 | [TypeConverter(typeof(BooleanConverter))]
43 | public bool ShowDebugMenu { get; set; } = true;
44 |
45 | [Category("Menu")]
46 | [DisplayName("Show Editor menu")]
47 | [Description("Set to true to show the Editor menu")]
48 | [DefaultValue(true)]
49 | [TypeConverter(typeof(BooleanConverter))]
50 | public bool ShowEditorMenu { get; set; } = true;
51 |
52 | [Category("Menu")]
53 | [DisplayName("Show Errors menu")]
54 | [Description("Set to true to show the Errors menu")]
55 | [DefaultValue(true)]
56 | [TypeConverter(typeof(BooleanConverter))]
57 | public bool ShowErrorsMenu { get; set; } = true;
58 |
59 | [Category("Menu")]
60 | [DisplayName("Show Find menu")]
61 | [Description("Set to true to show the Find menu")]
62 | [DefaultValue(true)]
63 | [TypeConverter(typeof(BooleanConverter))]
64 | public bool ShowFindMenu { get; set; } = true;
65 |
66 | [Category("Menu")]
67 | [DisplayName("Show Navigation menu")]
68 | [Description("Set to true to show the Navigation menu")]
69 | [DefaultValue(true)]
70 | [TypeConverter(typeof(BooleanConverter))]
71 | public bool ShowNavigationMenu { get; set; } = true;
72 |
73 | [Category("Menu")]
74 | [DisplayName("Show Scroll menu")]
75 | [Description("Set to true to show the Scroll menu")]
76 | [DefaultValue(true)]
77 | [TypeConverter(typeof(BooleanConverter))]
78 | public bool ShowScrollMenu { get; set; } = true;
79 |
80 | [Category("Menu")]
81 | [DisplayName("Show Zoom menu")]
82 | [Description("Set to true to show the Zoom menu")]
83 | [DefaultValue(true)]
84 | [TypeConverter(typeof(BooleanConverter))]
85 | public bool ShowZoomMenu { get; set; } = true;
86 |
87 | [Category("Visual Studio Shell")]
88 | [DisplayName("Show Dial Log")]
89 | [Description("Set to true to show the Surface Dial log in the Output window")]
90 | [DefaultValue(false)]
91 | [TypeConverter(typeof(BooleanConverter))]
92 | public bool ShowLogInOutput { get; set; } = false;
93 |
94 | [Category("Scrolling")]
95 | [DisplayName("Lines To Scroll")]
96 | [Description("The number of lines to scroll when the dial is rotated. A positive integer from 1 to 20. (Affects document window only, not the Solution Explorer.)")]
97 | [DefaultValue(1)]
98 | [TypeConverter(typeof(PositiveIntegerConverter))]
99 | public int LinesToScroll { get; set; } = 1;
100 |
101 | public Options()
102 | {
103 | InitializeMenuVisibility();
104 | }
105 |
106 | protected override void OnApply(PageApplyEventArgs e)
107 | {
108 | base.OnApply(e);
109 | InitializeMenuVisibility();
110 | OptionsApplied?.Invoke(this, e);
111 | }
112 |
113 | private void InitializeMenuVisibility()
114 | {
115 | MenuVisibility = new Dictionary
116 | {
117 | {KnownProviders.Bookmarks.ToString(), ShowBookmarksMenu },
118 | {KnownProviders.Customizable.ToString(), ShowCustomizableMenu },
119 | {KnownProviders.Debug.ToString(), ShowDebugMenu },
120 | {KnownProviders.Editor.ToString(), ShowEditorMenu},
121 | {KnownProviders.Errors.ToString(), ShowErrorsMenu },
122 | {KnownProviders.Find.ToString(), ShowFindMenu },
123 | {KnownProviders.Navigation.ToString(), ShowNavigationMenu },
124 | {KnownProviders.Scroll.ToString(), ShowScrollMenu },
125 | {KnownProviders.Zoom.ToString(), ShowZoomMenu },
126 | };
127 | }
128 |
129 | internal IReadOnlyDictionary MenuVisibility { get; private set; }
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/CustomOptionsControl.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
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 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | 17, 17
122 |
123 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Looking to contribute something? **Here's how you can help.**
4 |
5 | Please take a moment to review this document in order to make the contribution
6 | process easy and effective for everyone involved.
7 |
8 | Following these guidelines helps to communicate that you respect the time of
9 | the developers managing and developing this open source project. In return,
10 | they should reciprocate that respect in addressing your issue or assessing
11 | patches and features.
12 |
13 |
14 | ## Using the issue tracker
15 |
16 | The issue tracker is the preferred channel for [bug reports](#bug-reports),
17 | [features requests](#feature-requests) and
18 | [submitting pull requests](#pull-requests), but please respect the
19 | following restrictions:
20 |
21 | * Please **do not** use the issue tracker for personal support requests. Stack
22 | Overflow is a better place to get help.
23 |
24 | * Please **do not** derail or troll issues. Keep the discussion on topic and
25 | respect the opinions of others.
26 |
27 | * Please **do not** open issues or pull requests which *belongs to* third party
28 | components.
29 |
30 |
31 | ## Bug reports
32 |
33 | A bug is a _demonstrable problem_ that is caused by the code in the repository.
34 | Good bug reports are extremely helpful, so thanks!
35 |
36 | Guidelines for bug reports:
37 |
38 | 1. **Use the GitHub issue search** — check if the issue has already been
39 | reported.
40 |
41 | 2. **Check if the issue has been fixed** — try to reproduce it using the
42 | latest `master` or development branch in the repository.
43 |
44 | 3. **Isolate the problem** — ideally create an
45 | [SSCCE](http://www.sscce.org/) and a live example.
46 | Uploading the project on cloud storage (OneDrive, DropBox, et el.)
47 | or creating a sample GitHub repository is also helpful.
48 |
49 |
50 | A good bug report shouldn't leave others needing to chase you up for more
51 | information. Please try to be as detailed as possible in your report. What is
52 | your environment? What steps will reproduce the issue? What browser(s) and OS
53 | experience the problem? Do other browsers show the bug differently? What
54 | would you expect to be the outcome? All these details will help people to fix
55 | any potential bugs.
56 |
57 | Example:
58 |
59 | > Short and descriptive example bug report title
60 | >
61 | > A summary of the issue and the Visual Studio, browser, OS environments
62 | > in which it occurs. If suitable, include the steps required to reproduce the bug.
63 | >
64 | > 1. This is the first step
65 | > 2. This is the second step
66 | > 3. Further steps, etc.
67 | >
68 | > `` - a link to the project/file uploaded on cloud storage or other publicly accessible medium.
69 | >
70 | > Any other information you want to share that is relevant to the issue being
71 | > reported. This might include the lines of code that you have identified as
72 | > causing the bug, and potential solutions (and your opinions on their
73 | > merits).
74 |
75 |
76 | ## Feature requests
77 |
78 | Feature requests are welcome. But take a moment to find out whether your idea
79 | fits with the scope and aims of the project. It's up to *you* to make a strong
80 | case to convince the project's developers of the merits of this feature. Please
81 | provide as much detail and context as possible.
82 |
83 |
84 | ## Pull requests
85 |
86 | Good pull requests, patches, improvements and new features are a fantastic
87 | help. They should remain focused in scope and avoid containing unrelated
88 | commits.
89 |
90 | **Please ask first** before embarking on any significant pull request (e.g.
91 | implementing features, refactoring code, porting to a different language),
92 | otherwise you risk spending a lot of time working on something that the
93 | project's developers might not want to merge into the project.
94 |
95 | Please adhere to the [coding guidelines](#code-guidelines) used throughout the
96 | project (indentation, accurate comments, etc.) and any other requirements
97 | (such as test coverage).
98 |
99 | Adhering to the following process is the best way to get your work
100 | included in the project:
101 |
102 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork,
103 | and configure the remotes:
104 |
105 | ```bash
106 | # Clone your fork of the repo into the current directory
107 | git clone https://github.com//.git
108 | # Navigate to the newly cloned directory
109 | cd
110 | # Assign the original repo to a remote called "upstream"
111 | git remote add upstream https://github.com/madskristensen/.git
112 | ```
113 |
114 | 2. If you cloned a while ago, get the latest changes from upstream:
115 |
116 | ```bash
117 | git checkout master
118 | git pull upstream master
119 | ```
120 |
121 | 3. Create a new topic branch (off the main project development branch) to
122 | contain your feature, change, or fix:
123 |
124 | ```bash
125 | git checkout -b
126 | ```
127 |
128 | 4. Commit your changes in logical chunks. Please adhere to these [git commit
129 | message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
130 | or your code is unlikely be merged into the main project. Use Git's
131 | [interactive rebase](https://help.github.com/articles/interactive-rebase)
132 | feature to tidy up your commits before making them public. Also, prepend name of the feature
133 | to the commit message. For instance: "SCSS: Fixes compiler results for IFileListener.\nFixes `#123`"
134 |
135 | 5. Locally merge (or rebase) the upstream development branch into your topic branch:
136 |
137 | ```bash
138 | git pull [--rebase] upstream master
139 | ```
140 |
141 | 6. Push your topic branch up to your fork:
142 |
143 | ```bash
144 | git push origin
145 | ```
146 |
147 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/)
148 | with a clear title and description against the `master` branch.
149 |
150 |
151 | ## Code guidelines
152 |
153 | - Always use proper indentation.
154 | - In Visual Studio under `Tools > Options > Text Editor > C# > Advanced`, make sure
155 | `Place 'System' directives first when sorting usings` option is enabled (checked).
156 | - Before committing, organize usings for each updated C# source file. Either you can
157 | right-click editor and select `Organize Usings > Remove and sort` OR use extension
158 | like [BatchFormat](http://visualstudiogallery.msdn.microsoft.com/a7f75c34-82b4-4357-9c66-c18e32b9393e).
159 | - Before committing, run Code Analysis in `Debug` configuration and follow the guidelines
160 | to fix CA issues. Code Analysis commits can be made separately.
161 |
--------------------------------------------------------------------------------
/src/source.extension.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
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 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | Surface Dial Tools for Visual Studio
122 |
123 |
124 | Adds features to Visual Studio specific to the Surface Dial. You don't need a Surface Book or Surface Studio to take advantage of the Surface Dial.
125 |
126 |
127 |
128 | source.extension.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
129 |
130 |
--------------------------------------------------------------------------------
/.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 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | *.sbr
86 | *.tlb
87 | *.tli
88 | *.tlh
89 | *.tmp
90 | *.tmp_proj
91 | *_wpftmp.csproj
92 | *.log
93 | *.tlog
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 | # Nuget personal access tokens and Credentials
210 | nuget.config
211 |
212 | # Microsoft Azure Build Output
213 | csx/
214 | *.build.csdef
215 |
216 | # Microsoft Azure Emulator
217 | ecf/
218 | rcf/
219 |
220 | # Windows Store app package directories and files
221 | AppPackages/
222 | BundleArtifacts/
223 | Package.StoreAssociation.xml
224 | _pkginfo.txt
225 | *.appx
226 | *.appxbundle
227 | *.appxupload
228 |
229 | # Visual Studio cache files
230 | # files ending in .cache can be ignored
231 | *.[Cc]ache
232 | # but keep track of directories ending in .cache
233 | !?*.[Cc]ache/
234 |
235 | # Others
236 | ClientBin/
237 | ~$*
238 | *~
239 | *.dbmdl
240 | *.dbproj.schemaview
241 | *.jfm
242 | *.pfx
243 | *.publishsettings
244 | orleans.codegen.cs
245 |
246 | # Including strong name files can present a security risk
247 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
248 | #*.snk
249 |
250 | # Since there are multiple workflows, uncomment next line to ignore bower_components
251 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
252 | #bower_components/
253 |
254 | # RIA/Silverlight projects
255 | Generated_Code/
256 |
257 | # Backup & report files from converting an old project file
258 | # to a newer Visual Studio version. Backup files are not needed,
259 | # because we have git ;-)
260 | _UpgradeReport_Files/
261 | Backup*/
262 | UpgradeLog*.XML
263 | UpgradeLog*.htm
264 | ServiceFabricBackup/
265 | *.rptproj.bak
266 |
267 | # SQL Server files
268 | *.mdf
269 | *.ldf
270 | *.ndf
271 |
272 | # Business Intelligence projects
273 | *.rdl.data
274 | *.bim.layout
275 | *.bim_*.settings
276 | *.rptproj.rsuser
277 | *- [Bb]ackup.rdl
278 | *- [Bb]ackup ([0-9]).rdl
279 | *- [Bb]ackup ([0-9][0-9]).rdl
280 |
281 | # Microsoft Fakes
282 | FakesAssemblies/
283 |
284 | # GhostDoc plugin setting file
285 | *.GhostDoc.xml
286 |
287 | # Node.js Tools for Visual Studio
288 | .ntvs_analysis.dat
289 | node_modules/
290 |
291 | # Visual Studio 6 build log
292 | *.plg
293 |
294 | # Visual Studio 6 workspace options file
295 | *.opt
296 |
297 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
298 | *.vbw
299 |
300 | # Visual Studio LightSwitch build output
301 | **/*.HTMLClient/GeneratedArtifacts
302 | **/*.DesktopClient/GeneratedArtifacts
303 | **/*.DesktopClient/ModelManifest.xml
304 | **/*.Server/GeneratedArtifacts
305 | **/*.Server/ModelManifest.xml
306 | _Pvt_Extensions
307 |
308 | # Paket dependency manager
309 | .paket/paket.exe
310 | paket-files/
311 |
312 | # FAKE - F# Make
313 | .fake/
314 |
315 | # CodeRush personal settings
316 | .cr/personal
317 |
318 | # Python Tools for Visual Studio (PTVS)
319 | __pycache__/
320 | *.pyc
321 |
322 | # Cake - Uncomment if you are using it
323 | # tools/**
324 | # !tools/packages.config
325 |
326 | # Tabs Studio
327 | *.tss
328 |
329 | # Telerik's JustMock configuration file
330 | *.jmconfig
331 |
332 | # BizTalk build output
333 | *.btp.cs
334 | *.btm.cs
335 | *.odx.cs
336 | *.xsd.cs
337 |
338 | # OpenCover UI analysis results
339 | OpenCover/
340 |
341 | # Azure Stream Analytics local run output
342 | ASALocalRun/
343 |
344 | # MSBuild Binary and Structured Log
345 | *.binlog
346 |
347 | # NVidia Nsight GPU debugger configuration file
348 | *.nvuser
349 |
350 | # MFractors (Xamarin productivity tool) working folder
351 | .mfractor/
352 |
353 | # Local History for Visual Studio
354 | .localhistory/
355 |
356 | # BeatPulse healthcheck temp database
357 | healthchecksdb
358 |
359 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
360 | MigrationBackup/
361 |
362 | # Ionide (cross platform F# VS Code tools) working folder
363 | .ionide/
364 |
365 | # Fody - auto-generated XML schema
366 | FodyWeavers.xsd
367 |
368 | # VS Code files for those working on multiple tools
369 | .vscode/*
370 | !.vscode/settings.json
371 | !.vscode/tasks.json
372 | !.vscode/launch.json
373 | !.vscode/extensions.json
374 | *.code-workspace
375 |
376 | # Local History for Visual Studio Code
377 | .history/
378 |
379 | # Windows Installer files from build outputs
380 | *.cab
381 | *.msi
382 | *.msix
383 | *.msm
384 | *.msp
385 |
386 | # JetBrains Rider
387 | .idea/
388 | *.sln.iml
389 |
--------------------------------------------------------------------------------
/src/Providers/Debug/DebugController.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Windows.Input;
5 |
6 | using EnvDTE;
7 |
8 | using EnvDTE80;
9 |
10 | using Windows.UI.Input;
11 |
12 | namespace DialControllerTools
13 | {
14 | internal class DebugController : BaseController
15 | {
16 | private readonly DTE2 _dte;
17 | private readonly DebuggerEvents _events;
18 |
19 | public DebugController(RadialControllerMenuItem menuItem, DTE2 dte) : base(menuItem)
20 | {
21 | _dte = dte;
22 | // Switched in provider
23 | #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread
24 | _events = dte.Events.DebuggerEvents;
25 | _events.OnEnterBreakMode += delegate { DialPackage.DialControllerHost.RequestActivation(this); };
26 | _events.OnEnterDesignMode += delegate { DialPackage.DialControllerHost.ReleaseActivation(); };
27 | #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread
28 | }
29 |
30 | public override string Moniker => DebugControllerProvider.Moniker;
31 | public override bool CanHandleClick => true;
32 | public override bool CanHandleRotate => true;
33 |
34 | #pragma warning disable VSTHRD010 // Invoke single-threaded types on Main thread
35 | public override bool OnClick()
36 | {
37 | dbgDebugMode? debugMode = _dte.Application?.Debugger.CurrentMode;
38 |
39 | if (!debugMode.HasValue)
40 | return false;
41 |
42 | bool isShiftPressed = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
43 | bool isControlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
44 | bool isAltPressed = Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt);
45 | if (debugMode == dbgDebugMode.dbgBreakMode)
46 | {
47 | switch ((Control: isControlPressed, Shift: isShiftPressed))
48 | {
49 | // Alt
50 | // to have it the same for both modes
51 | case (Control: false, Shift: false) when isAltPressed: _dte.Debugger.RunToCursor(); break;
52 | // Ctrl + Shift
53 | // like Ctrl + Shift + F5
54 | case (Control: true, Shift: true): _dte.Application.ExecuteCommand("Debug.Restart"); break;
55 | // Shift
56 | // like Shift + F5
57 | case (Control: false, Shift: true): _dte.Debugger.Stop(); break;
58 | default: _dte.Debugger.Go(); break;
59 | }
60 | }
61 | else if (debugMode == dbgDebugMode.dbgDesignMode)
62 | {
63 | switch ((Control: isControlPressed, Shift: isShiftPressed))
64 | {
65 | // Alt
66 | // to have it the same for both modes
67 | case (Control: false, Shift: false) when isAltPressed:
68 | _dte.Application.ExecuteCommand("Debug.RunToCursor");
69 | break;
70 | // Ctrl
71 | // like Ctrl + F5
72 | case (Control: true, Shift: false):
73 | _dte.Application.ExecuteCommand("Debug.StartWithoutDebugging");
74 | break;
75 | // Shift
76 | // like F10
77 | case (Control: false, Shift: true):
78 | _dte.Application.ExecuteCommand("Debug.StepOver");
79 | break;
80 | // Ctrl + Shift
81 | // like F11
82 | case (Control: true, Shift: true):
83 | _dte.Application.ExecuteCommand("Debug.StepInto");
84 | break;
85 | // like F5
86 | default:
87 | //.Debugger.Go() does not seem to synchronize the VS Debug toolbar
88 | _dte.Application.ExecuteCommand("Debug.Start");
89 | break;
90 | }
91 | }
92 |
93 | return true;
94 | }
95 |
96 | public override bool OnRotate(RotationDirection direction)
97 | {
98 | dbgDebugMode? debugMode = _dte.Application?.Debugger.CurrentMode;
99 |
100 | // TODO: Find how to determine Historical Debugger enabled
101 | if (debugMode == dbgDebugMode.dbgBreakMode)
102 | {
103 | bool isShiftPressed = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
104 | bool isControlPressed = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
105 | bool isAltPressed = Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt);
106 |
107 | if (direction == RotationDirection.Right)
108 | {
109 | switch ((Control: isControlPressed, Alt: isAltPressed, Shift: isShiftPressed))
110 | {
111 | // like F10
112 | case (Control: false, Alt: false, Shift: false): _dte.Debugger.StepOver(); break;
113 | case (Control: false, Alt: true, Shift: false):
114 | // TODO: Find the right command
115 | _dte.Application.ExecuteCommand("Debug.StepOverNoBreakpoints"); break;
116 | // Ctrl
117 | // like F11
118 | case (Control: true, Alt: false, Shift: false): _dte.Debugger.StepInto(); break;
119 | // Shift
120 | // like Shift + F11
121 | case (Control: _, Alt: false, Shift: true): _dte.Debugger.StepOut(); break;
122 | case (Control: _, Alt: true, Shift: true):
123 | // TODO: Find the right command
124 | _dte.Application.ExecuteCommand("Debug.StepOutNoBreakpoints"); break;
125 | // any other
126 | default: _dte.Debugger.StepOver(); break;
127 | }
128 | }
129 | else
130 | {
131 | _dte.Application.Debugger.StepOut();
132 | }
133 | }
134 | else if (debugMode == dbgDebugMode.dbgDesignMode)
135 | {
136 | if (direction == RotationDirection.Right)
137 | {
138 | MoveToBreakpoint((s, bs, d) =>
139 | {
140 | return bs.FirstOrDefault(b =>
141 | string.Equals(b.File, d.FullName, StringComparison.InvariantCultureIgnoreCase)
142 | && b.FileLine > s.CurrentLine
143 | || string.Compare(b.File, d.FullName, true) > 0) ?? bs.FirstOrDefault();
144 | });
145 | }
146 | else
147 | {
148 | MoveToBreakpoint((s, bs, d) =>
149 | {
150 | return bs.LastOrDefault(b =>
151 | string.Equals(b.File, d.FullName, StringComparison.InvariantCultureIgnoreCase)
152 | && b.FileLine < s.CurrentLine
153 | || string.Compare(b.File, d.FullName, true) < 0) ?? bs.LastOrDefault();
154 | });
155 | }
156 |
157 | }
158 | return true;
159 | }
160 |
161 | private void MoveToBreakpoint(Func, Document, Breakpoint> findBreakpoint)
162 | {
163 | var selection = ((TextSelection)_dte.ActiveDocument.Selection);
164 |
165 | IOrderedEnumerable breakpoints = _dte.Debugger.Breakpoints.OfType().OrderBy(b => b, new FileOrderer());
166 |
167 | Breakpoint breakpoint = findBreakpoint(selection, breakpoints, _dte.ActiveDocument);
168 |
169 | if (breakpoint != null)
170 | {
171 | var s = _dte.Documents.OfType()
172 | .FirstOrDefault(d => string.Equals(d.FullName, breakpoint.File, StringComparison.InvariantCultureIgnoreCase))
173 | .Selection as TextSelection;
174 |
175 | _dte.Documents.Open(breakpoint.File);
176 | s.MoveToLineAndOffset(breakpoint.FileLine, breakpoint.FileColumn);
177 | }
178 | }
179 | #pragma warning restore VSTHRD010 // Invoke single-threaded types on Main thread
180 | }
181 |
182 | class FileOrderer : IComparer
183 | {
184 | public int Compare(Breakpoint x, Breakpoint y)
185 | {
186 | if (x.File == y.File)
187 | {
188 | return x.FileLine.CompareTo(y.FileLine);
189 | }
190 | else
191 | {
192 | return string.Compare(x.File, y.File);
193 | }
194 | }
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/src/CustomOptionsControl.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace DialControllerTools
2 | {
3 | partial class CustomOptionsControl
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Component Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.AssignedClickLabel = new System.Windows.Forms.Label();
32 | this.AssignedRightLabel = new System.Windows.Forms.Label();
33 | this.AssignedLeftLabel = new System.Windows.Forms.Label();
34 | this.AssignLeftAction = new System.Windows.Forms.Button();
35 | this.AssignRightAction = new System.Windows.Forms.Button();
36 | this.AssignClickAction = new System.Windows.Forms.Button();
37 | this.LeftLabel = new System.Windows.Forms.Label();
38 | this.RightLabel = new System.Windows.Forms.Label();
39 | this.ClickLabel = new System.Windows.Forms.Label();
40 | this.CommandsBox = new System.Windows.Forms.TextBox();
41 | this.SearcBoxLabel = new System.Windows.Forms.Label();
42 | this.SearchBox = new System.Windows.Forms.TextBox();
43 | this.SearchBackgroundWorker = new System.ComponentModel.BackgroundWorker();
44 | this.RootLayoutPanel = new System.Windows.Forms.TableLayoutPanel();
45 | this.RootLayoutPanel.SuspendLayout();
46 | this.SuspendLayout();
47 | //
48 | // AssignedClickLabel
49 | //
50 | this.AssignedClickLabel.AutoEllipsis = true;
51 | this.AssignedClickLabel.Dock = System.Windows.Forms.DockStyle.Fill;
52 | this.AssignedClickLabel.Location = new System.Drawing.Point(204, 450);
53 | this.AssignedClickLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
54 | this.AssignedClickLabel.Name = "AssignedClickLabel";
55 | this.AssignedClickLabel.Size = new System.Drawing.Size(364, 50);
56 | this.AssignedClickLabel.TabIndex = 26;
57 | this.AssignedClickLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
58 | //
59 | // AssignedRightLabel
60 | //
61 | this.AssignedRightLabel.AutoEllipsis = true;
62 | this.AssignedRightLabel.Dock = System.Windows.Forms.DockStyle.Fill;
63 | this.AssignedRightLabel.Location = new System.Drawing.Point(204, 500);
64 | this.AssignedRightLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
65 | this.AssignedRightLabel.Name = "AssignedRightLabel";
66 | this.AssignedRightLabel.Size = new System.Drawing.Size(364, 50);
67 | this.AssignedRightLabel.TabIndex = 25;
68 | this.AssignedRightLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
69 | //
70 | // AssignedLeftLabel
71 | //
72 | this.AssignedLeftLabel.AutoEllipsis = true;
73 | this.AssignedLeftLabel.Dock = System.Windows.Forms.DockStyle.Fill;
74 | this.AssignedLeftLabel.Location = new System.Drawing.Point(204, 550);
75 | this.AssignedLeftLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
76 | this.AssignedLeftLabel.Name = "AssignedLeftLabel";
77 | this.AssignedLeftLabel.Size = new System.Drawing.Size(364, 50);
78 | this.AssignedLeftLabel.TabIndex = 24;
79 | this.AssignedLeftLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
80 | //
81 | // AssignLeftAction
82 | //
83 | this.AssignLeftAction.Location = new System.Drawing.Point(576, 554);
84 | this.AssignLeftAction.Margin = new System.Windows.Forms.Padding(4);
85 | this.AssignLeftAction.Name = "AssignLeftAction";
86 | this.AssignLeftAction.Size = new System.Drawing.Size(100, 42);
87 | this.AssignLeftAction.TabIndex = 23;
88 | this.AssignLeftAction.Text = "Assign";
89 | this.AssignLeftAction.UseVisualStyleBackColor = true;
90 | this.AssignLeftAction.Click += new System.EventHandler(this.AssignLeftAction_Click);
91 | //
92 | // AssignRightAction
93 | //
94 | this.AssignRightAction.Location = new System.Drawing.Point(576, 504);
95 | this.AssignRightAction.Margin = new System.Windows.Forms.Padding(4);
96 | this.AssignRightAction.Name = "AssignRightAction";
97 | this.AssignRightAction.Size = new System.Drawing.Size(100, 42);
98 | this.AssignRightAction.TabIndex = 22;
99 | this.AssignRightAction.Text = "Assign";
100 | this.AssignRightAction.UseVisualStyleBackColor = true;
101 | this.AssignRightAction.Click += new System.EventHandler(this.AssignRightAction_Click);
102 | //
103 | // AssignClickAction
104 | //
105 | this.AssignClickAction.Location = new System.Drawing.Point(576, 454);
106 | this.AssignClickAction.Margin = new System.Windows.Forms.Padding(4);
107 | this.AssignClickAction.Name = "AssignClickAction";
108 | this.AssignClickAction.Size = new System.Drawing.Size(100, 42);
109 | this.AssignClickAction.TabIndex = 21;
110 | this.AssignClickAction.Text = "Assign";
111 | this.AssignClickAction.UseVisualStyleBackColor = true;
112 | this.AssignClickAction.Click += new System.EventHandler(this.AssignClickAction_Click);
113 | //
114 | // LeftLabel
115 | //
116 | this.LeftLabel.AutoSize = true;
117 | this.LeftLabel.Dock = System.Windows.Forms.DockStyle.Fill;
118 | this.LeftLabel.Location = new System.Drawing.Point(4, 550);
119 | this.LeftLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
120 | this.LeftLabel.Name = "LeftLabel";
121 | this.LeftLabel.Size = new System.Drawing.Size(192, 50);
122 | this.LeftLabel.TabIndex = 20;
123 | this.LeftLabel.Text = "Dial left action:";
124 | this.LeftLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
125 | //
126 | // RightLabel
127 | //
128 | this.RightLabel.AutoSize = true;
129 | this.RightLabel.Dock = System.Windows.Forms.DockStyle.Fill;
130 | this.RightLabel.Location = new System.Drawing.Point(4, 500);
131 | this.RightLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
132 | this.RightLabel.Name = "RightLabel";
133 | this.RightLabel.Size = new System.Drawing.Size(192, 50);
134 | this.RightLabel.TabIndex = 19;
135 | this.RightLabel.Text = "Dial right action:";
136 | this.RightLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
137 | //
138 | // ClickLabel
139 | //
140 | this.ClickLabel.AutoSize = true;
141 | this.ClickLabel.Dock = System.Windows.Forms.DockStyle.Fill;
142 | this.ClickLabel.Location = new System.Drawing.Point(4, 450);
143 | this.ClickLabel.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
144 | this.ClickLabel.Name = "ClickLabel";
145 | this.ClickLabel.Size = new System.Drawing.Size(192, 50);
146 | this.ClickLabel.TabIndex = 18;
147 | this.ClickLabel.Text = "Dial clicked action:";
148 | this.ClickLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
149 | //
150 | // CommandsBox
151 | //
152 | this.RootLayoutPanel.SetColumnSpan(this.CommandsBox, 3);
153 | this.CommandsBox.Dock = System.Windows.Forms.DockStyle.Fill;
154 | this.CommandsBox.Location = new System.Drawing.Point(3, 64);
155 | this.CommandsBox.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4);
156 | this.CommandsBox.Multiline = true;
157 | this.CommandsBox.Name = "CommandsBox";
158 | this.CommandsBox.ScrollBars = System.Windows.Forms.ScrollBars.Both;
159 | this.CommandsBox.Size = new System.Drawing.Size(674, 382);
160 | this.CommandsBox.TabIndex = 17;
161 | this.CommandsBox.MouseClick += new System.Windows.Forms.MouseEventHandler(this.CommandsBox_MouseClick);
162 | //
163 | // SearcBoxLabel
164 | //
165 | this.SearcBoxLabel.AutoSize = true;
166 | this.RootLayoutPanel.SetColumnSpan(this.SearcBoxLabel, 3);
167 | this.SearcBoxLabel.Location = new System.Drawing.Point(3, 0);
168 | this.SearcBoxLabel.Name = "SearcBoxLabel";
169 | this.SearcBoxLabel.Size = new System.Drawing.Size(332, 25);
170 | this.SearcBoxLabel.TabIndex = 16;
171 | this.SearcBoxLabel.Text = "Search for commands containing:";
172 | //
173 | // SearchBox
174 | //
175 | this.RootLayoutPanel.SetColumnSpan(this.SearchBox, 3);
176 | this.SearchBox.Dock = System.Windows.Forms.DockStyle.Fill;
177 | this.SearchBox.Location = new System.Drawing.Point(3, 27);
178 | this.SearchBox.Margin = new System.Windows.Forms.Padding(3, 2, 3, 2);
179 | this.SearchBox.Name = "SearchBox";
180 | this.SearchBox.Size = new System.Drawing.Size(674, 31);
181 | this.SearchBox.TabIndex = 15;
182 | this.SearchBox.TextChanged += new System.EventHandler(this.SearchBox_TextChanged);
183 | //
184 | // RootLayoutPanel
185 | //
186 | this.RootLayoutPanel.ColumnCount = 3;
187 | this.RootLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
188 | this.RootLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
189 | this.RootLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
190 | this.RootLayoutPanel.Controls.Add(this.SearcBoxLabel, 0, 0);
191 | this.RootLayoutPanel.Controls.Add(this.SearchBox, 0, 1);
192 | this.RootLayoutPanel.Controls.Add(this.CommandsBox, 0, 2);
193 | this.RootLayoutPanel.Controls.Add(this.ClickLabel, 0, 3);
194 | this.RootLayoutPanel.Controls.Add(this.RightLabel, 0, 4);
195 | this.RootLayoutPanel.Controls.Add(this.LeftLabel, 0, 5);
196 | this.RootLayoutPanel.Controls.Add(this.AssignedClickLabel, 1, 3);
197 | this.RootLayoutPanel.Controls.Add(this.AssignedRightLabel, 1, 4);
198 | this.RootLayoutPanel.Controls.Add(this.AssignedLeftLabel, 1, 5);
199 | this.RootLayoutPanel.Controls.Add(this.AssignClickAction, 2, 3);
200 | this.RootLayoutPanel.Controls.Add(this.AssignRightAction, 2, 4);
201 | this.RootLayoutPanel.Controls.Add(this.AssignLeftAction, 2, 5);
202 | this.RootLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
203 | this.RootLayoutPanel.Location = new System.Drawing.Point(0, 0);
204 | this.RootLayoutPanel.Name = "RootLayoutPanel";
205 | this.RootLayoutPanel.RowCount = 8;
206 | this.RootLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
207 | this.RootLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
208 | this.RootLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
209 | this.RootLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
210 | this.RootLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
211 | this.RootLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
212 | this.RootLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
213 | this.RootLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
214 | this.RootLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F));
215 | this.RootLayoutPanel.Size = new System.Drawing.Size(680, 600);
216 | this.RootLayoutPanel.TabIndex = 27;
217 | //
218 | // CustomOptionsControl
219 | //
220 | this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 25F);
221 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
222 | this.Controls.Add(this.RootLayoutPanel);
223 | this.Name = "CustomOptionsControl";
224 | this.Size = new System.Drawing.Size(680, 600);
225 | this.RootLayoutPanel.ResumeLayout(false);
226 | this.RootLayoutPanel.PerformLayout();
227 | this.ResumeLayout(false);
228 |
229 | }
230 |
231 | #endregion
232 |
233 | private System.Windows.Forms.Label AssignedClickLabel;
234 | private System.Windows.Forms.Label AssignedRightLabel;
235 | private System.Windows.Forms.Label AssignedLeftLabel;
236 | private System.Windows.Forms.Button AssignLeftAction;
237 | private System.Windows.Forms.Button AssignRightAction;
238 | private System.Windows.Forms.Button AssignClickAction;
239 | private System.Windows.Forms.Label LeftLabel;
240 | private System.Windows.Forms.Label RightLabel;
241 | private System.Windows.Forms.Label ClickLabel;
242 | private System.Windows.Forms.TextBox CommandsBox;
243 | private System.Windows.Forms.Label SearcBoxLabel;
244 | private System.Windows.Forms.TextBox SearchBox;
245 | private System.ComponentModel.BackgroundWorker SearchBackgroundWorker;
246 | private System.Windows.Forms.TableLayoutPanel RootLayoutPanel;
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/Controller/DialControllerHost.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Collections.Immutable;
4 | using System.Linq;
5 | using System.Runtime.InteropServices.WindowsRuntime;
6 | using System.Windows;
7 | using System.Windows.Interop;
8 |
9 | using Community.VisualStudio.Toolkit;
10 |
11 | using Microsoft.VisualStudio.PlatformUI;
12 | using Microsoft.VisualStudio.PlatformUI.Shell;
13 | using Microsoft.VisualStudio.PlatformUI.Shell.Controls;
14 |
15 | using Windows.UI.Input;
16 |
17 | namespace DialControllerTools
18 | {
19 | internal sealed class DialControllerHost : IDialControllerHost
20 | {
21 | private readonly Dictionary controllersMapping = new Dictionary();
22 |
23 | private readonly OutputWindowPane outputPane;
24 |
25 | private RadialControllerStatusBarHost status;
26 | private readonly ImmutableArray controllers;
27 | private readonly List enabledControllers;
28 | private IDialController currentController;
29 | private bool allowRelease;
30 | private bool wasUsed;
31 |
32 | private IDialController CurrentController
33 | {
34 | get => currentController;
35 | set
36 | {
37 | currentController = value;
38 | status.Text = currentController.Moniker;
39 | }
40 | }
41 |
42 | internal DialControllerHost(OutputWindowPane outputPane, in ImmutableArray controllers)
43 | {
44 | this.outputPane = outputPane;
45 | this.controllers = controllers;
46 | this.enabledControllers = new List(controllers.Length);
47 |
48 | FloatingWindowManager.FloatingWindowCreated += (sender, args) => CreateControllerForWindow(args.Window);
49 | //var floatingWindowManager = ViewManager.Instance.FloatingWindowManager;
50 | //floatingWindowManager.
51 |
52 | void OnMainWindowLoaded(object sender, EventArgs args)
53 | {
54 | CreateStatusBarItem();
55 | UpdateEnabledControllers(DialPackage.Options);
56 | }
57 |
58 | ExecuteWhenWindowIsLoaded(Application.Current.MainWindow, OnMainWindowLoaded);
59 |
60 | foreach (Window window in Application.Current.Windows)
61 | {
62 | if (window is MainWindow || window is FloatingWindow)
63 | CreateControllerForWindow(window);
64 | }
65 | }
66 |
67 | private static void ExecuteWhenWindowIsLoaded(Window window, RoutedEventHandler action)
68 | {
69 | if (window.IsLoaded) action(window, new RoutedEventArgs());
70 | else window.Loaded += action;
71 | }
72 |
73 | private void CreateStatusBarItem()
74 | {
75 | status = new RadialControllerStatusBarHost { Name = "PART_DialControllerHost" };
76 | var injector = new StatusBarInjector(Application.Current.MainWindow);
77 | injector.InjectControl(status);
78 | }
79 |
80 | private void CreateControllerForWindow(Window window)
81 | {
82 | void CreateControllerForWindowImpl(object sender, EventArgs args)
83 | {
84 | window.Loaded -= CreateControllerForWindowImpl;
85 | IntPtr hwnd = new WindowInteropHelper(window).Handle;
86 | CreateController(hwnd);
87 | SetDefaultItems(hwnd);
88 |
89 | if (window.IsActive) ApplyCurrentControllerStateForWindow((Window)sender);
90 | void OnWindowActivated(object sender, EventArgs args)
91 | {
92 | this.ApplyCurrentControllerStateForWindow((Window)sender);
93 | }
94 | window.Activated += OnWindowActivated;
95 |
96 | void RemoveController(object sender, RoutedEventArgs args)
97 | {
98 | window.Activated -= OnWindowActivated;
99 | window.Unloaded -= RemoveController;
100 | if (controllersMapping.TryGetValue(hwnd, out var controller))
101 | {
102 | UnsubscribeFromController(controller);
103 | controllersMapping.Remove(hwnd);
104 | }
105 | }
106 | window.Unloaded += RemoveController;
107 | }
108 |
109 | ExecuteWhenWindowIsLoaded(window, CreateControllerForWindowImpl);
110 | }
111 |
112 | private void CreateController(IntPtr hwnd)
113 | {
114 | var interop = (IRadialControllerInterop)WindowsRuntimeMarshal.GetActivationFactory(typeof(RadialController));
115 | Guid guid = typeof(RadialController).GetInterface("IRadialController").GUID;
116 |
117 | var radialController = interop.CreateForWindow(hwnd, ref guid);
118 | if (radialController is null)
119 | {
120 | outputPane.WriteLine("Couldn't create RadialController");
121 | return;
122 | }
123 |
124 | foreach (IDialController controller in enabledControllers)
125 | {
126 | radialController.Menu.Items.Add(controller.MenuItem);
127 | }
128 |
129 | radialController.RotationChanged += OnRotationChanged;
130 | radialController.ButtonClicked += OnButtonClicked;
131 | radialController.ControlAcquired += OnControlAcquired;
132 | //radialController.ControlLost += OnControlLost;
133 | controllersMapping.Add(hwnd, radialController);
134 | }
135 |
136 | internal void UnsubscribeFromController(RadialController radialController)
137 | {
138 | radialController.RotationChanged -= OnRotationChanged;
139 | radialController.ButtonClicked -= OnButtonClicked;
140 | radialController.ControlAcquired -= OnControlAcquired;
141 | //radialController.ControlLost -= OnControlLost;
142 | }
143 |
144 | private static void SetDefaultItems(IntPtr hwnd)
145 | {
146 | RadialControllerConfiguration config;
147 | var radialControllerConfigInterop = (IRadialControllerConfigurationInterop)WindowsRuntimeMarshal.GetActivationFactory(typeof(RadialControllerConfiguration));
148 | Guid guid = typeof(RadialControllerConfiguration).GetInterface("IRadialControllerConfiguration").GUID;
149 |
150 | config = radialControllerConfigInterop.GetForWindow(hwnd, ref guid);
151 | config.SetDefaultMenuItems(new RadialControllerSystemMenuItemKind[0]);
152 | }
153 |
154 | private bool UpdateEnabledControllers(Options options)
155 | {
156 | var newControllers = controllers.Where(c => options.MenuVisibility[c.Moniker]).ToImmutableArray();
157 | var anyChanges = !newControllers.SequenceEqual(enabledControllers);
158 | if (anyChanges)
159 | {
160 | enabledControllers.Clear();
161 | enabledControllers.AddRange(newControllers);
162 | }
163 |
164 | var defaultMenu = options.DefaultProvider.ToString();
165 | if (!options.MenuVisibility[defaultMenu])
166 | {
167 | defaultMenu = options.MenuVisibility.FirstOrDefault(_ => _.Value).Key ?? nameof(KnownProviders.Scroll);
168 | }
169 | CurrentController = enabledControllers.First(c => c.Moniker == defaultMenu);
170 |
171 | return anyChanges;
172 | }
173 |
174 | internal void OptionsApplied(object sender, EventArgs e)
175 | {
176 | var options = (Options)sender;
177 | var anyControllerChangesMade = UpdateEnabledControllers(options);
178 |
179 | if (!anyControllerChangesMade) return;
180 |
181 | foreach (var controller in controllersMapping.Values)
182 | {
183 | var menuItems = controller.Menu.Items;
184 | for (int i = 0; i < enabledControllers.Count; i++)
185 | {
186 | var newMenuItem = enabledControllers[i].MenuItem;
187 | if (i < menuItems.Count)
188 | {
189 | if (menuItems[i] != enabledControllers[i].MenuItem)
190 | {
191 | menuItems[i] = newMenuItem;
192 | }
193 | }
194 | else
195 | {
196 | menuItems.Add(newMenuItem);
197 | }
198 | }
199 | }
200 | }
201 |
202 | public void ApplyCurrentControllerStateForWindow(Window window)
203 | {
204 | var radialController = controllersMapping[new WindowInteropHelper(window).Handle];
205 | ApplyCurrentControllerState(radialController);
206 | }
207 |
208 | private void ApplyCurrentControllerState(RadialController radialController)
209 | {
210 | var menuItem = CurrentController.MenuItem;
211 | if (radialController.Menu.GetSelectedMenuItem() != menuItem)
212 | {
213 | radialController.Menu.SelectMenuItem(menuItem);
214 | }
215 | radialController.UseAutomaticHapticFeedback = CurrentController.IsHapticFeedbackEnabled;
216 | }
217 |
218 | public void RequestActivation(IDialController controller)
219 | {
220 | var activeWindow = Application.Current.Windows.OfType().SingleOrDefault(w => w.IsActive);
221 | if (activeWindow is not null)
222 | {
223 | allowRelease = true;
224 | wasUsed = false;
225 | CurrentController = controller;
226 | ApplyCurrentControllerStateForWindow(activeWindow);
227 | }
228 | }
229 |
230 | public void ReleaseActivation()
231 | {
232 | if (wasUsed || !allowRelease)
233 | {
234 | wasUsed = false;
235 | return;
236 | }
237 |
238 | allowRelease = false;
239 | var activeWindow = Application.Current.Windows.OfType().SingleOrDefault(w => w.IsActive);
240 | if (activeWindow is not null)
241 | {
242 | var radialController = controllersMapping[new WindowInteropHelper(activeWindow).Handle];
243 | if (radialController.Menu.TrySelectPreviouslySelectedMenuItem())
244 | {
245 | MenuItemSelected(radialController);
246 | }
247 | }
248 | }
249 |
250 | private void OnControlAcquired(RadialController sender, RadialControllerControlAcquiredEventArgs args)
251 | {
252 | //_status.IsActive = true;
253 | MenuItemSelected(sender);
254 | }
255 |
256 | private void MenuItemSelected(RadialController radualController)
257 | {
258 | RadialControllerMenuItem selectedItem = radualController.Menu.GetSelectedMenuItem();
259 | if (selectedItem?.DisplayText != CurrentController.MenuItem.DisplayText)
260 | {
261 | allowRelease = false;
262 | // null when window is closed
263 | var controller = enabledControllers.FirstOrDefault(c => c.MenuItem == selectedItem);
264 | if (controller is not null)
265 | {
266 | CurrentController = controller;
267 | radualController.UseAutomaticHapticFeedback = CurrentController.IsHapticFeedbackEnabled;
268 | }
269 |
270 | }
271 | }
272 |
273 | //private void OnControlLost(RadialController sender, object args) => _status.IsActive = false;
274 |
275 | private async void OnButtonClicked(RadialController sender, RadialControllerButtonClickedEventArgs args)
276 | {
277 | if (!await VS.Solutions.IsOpenAsync()) //assume most controllers require a solution
278 | return;
279 |
280 | wasUsed = true;
281 | IEnumerable controllers = GetApplicableControllers(sender).Where(c => c.CanHandleClick);
282 | outputPane.WriteLine("Click: " + string.Join(", ", controllers.Select(c => c.Moniker)));
283 |
284 | foreach (IDialController controller in controllers)
285 | {
286 | try
287 | {
288 | bool handled = controller.OnClick();
289 |
290 | if (handled)
291 | break;
292 | }
293 | catch (Exception ex)
294 | {
295 | outputPane.WriteLine($"Controller {controller.Moniker} failed:");
296 | outputPane.WriteLine(ex.ToString());
297 | }
298 | }
299 | }
300 |
301 | private async void OnRotationChanged(RadialController sender, RadialControllerRotationChangedEventArgs args)
302 | {
303 | if (!await VS.Solutions.IsOpenAsync()) //assume most controllers require a solution
304 | return;
305 |
306 | wasUsed = true;
307 | IEnumerable controllers = GetApplicableControllers(sender).Where(c => c.CanHandleRotate);
308 | RotationDirection direction = args.RotationDeltaInDegrees > 0 ? RotationDirection.Right : RotationDirection.Left;
309 |
310 | outputPane.WriteLine("Rotate: " + string.Join(", ", controllers.Select(c => c.Moniker)));
311 |
312 | foreach (IDialController controller in controllers)
313 | {
314 | try
315 | {
316 | bool handled = controller.OnRotate(direction);
317 |
318 | if (handled)
319 | break;
320 | }
321 | catch (Exception ex)
322 | {
323 | outputPane.WriteLine($"Controller {controller.Moniker} failed:");
324 | outputPane.WriteLine(ex.ToString());
325 | }
326 | }
327 | }
328 |
329 | private IEnumerable GetApplicableControllers(RadialController controller)
330 | {
331 | string moniker = controller.Menu.GetSelectedMenuItem()?.DisplayText;
332 |
333 | if (string.IsNullOrEmpty(moniker))
334 | Enumerable.Empty();
335 |
336 | try
337 | {
338 | return controllers.Where(c => c.Moniker == moniker);
339 | }
340 | catch (Exception ex)
341 | {
342 | outputPane.WriteLine($"Cannot retrieve controllers for {moniker}:");
343 | outputPane.WriteLine(ex.ToString());
344 | return Enumerable.Empty();
345 | }
346 | }
347 | }
348 | }
349 |
--------------------------------------------------------------------------------
/src/DialControllerTools.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | $(VisualStudioVersion)
5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
6 | true
7 | Program
8 | $(DevEnvDir)\devenv.exe
9 | /rootsuffix Exp
10 |
11 |
12 |
13 |
14 |
15 | true
16 |
17 |
18 |
19 | Debug
20 | AnyCPU
21 | 2.0
22 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
23 | {8BB1C8EF-0C40-40A0-A2DF-D07010432122}
24 | Library
25 | Properties
26 | DialControllerTools
27 | DialControllerTools
28 | latest
29 | v4.8
30 | 10.0.18362.0
31 | true
32 | true
33 | true
34 | true
35 | true
36 | false
37 |
38 |
39 | true
40 | full
41 | false
42 | bin\Debug\
43 | DEBUG;TRACE
44 | prompt
45 | 4
46 | NU1603
47 |
48 |
49 | pdbonly
50 | true
51 | bin\Release\
52 | TRACE
53 | prompt
54 | 4
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | Component
67 |
68 |
69 | UserControl
70 |
71 |
72 | CustomOptionsControl.cs
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 | Component
81 |
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 | source.extension.vsixmanifest
110 |
111 |
112 | True
113 | True
114 | VSCommandTable.vsct
115 |
116 |
117 |
118 |
119 | Resources\LICENSE
120 | true
121 |
122 |
123 |
124 | Designer
125 | VsixManifestGenerator
126 | source.extension.resx
127 |
128 |
129 |
130 |
131 |
132 | $(MSBuildExtensionsPath)\..\Common7\IDE\PrivateAssemblies\Microsoft.VisualStudio.Diagnostics.Assert.dll
133 | False
134 |
135 |
136 | $(MSBuildExtensionsPath)\..\Common7\IDE\Microsoft.VisualStudio.Platform.WindowManagement.dll
137 | False
138 |
139 |
140 | $(MSBuildExtensionsPath)\..\Common7\IDE\Microsoft.VisualStudio.Shell.UI.Internal.dll
141 | False
142 |
143 |
144 | $(MSBuildExtensionsPath)\..\Common7\IDE\Microsoft.VisualStudio.Shell.ViewManager.dll
145 | False
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 | $(MSBuildProgramFiles32)\Windows Kits\10\UnionMetadata\10.0.22000.0\Windows.WinMD
161 | False
162 |
163 |
164 |
165 |
166 | Menus.ctmenu
167 | VsctGenerator
168 | VSCommandTable.cs
169 |
170 |
171 |
172 |
173 | CustomOptionsControl.cs
174 |
175 |
176 | True
177 | True
178 | source.extension.vsixmanifest
179 | true
180 | VSPackage
181 |
182 |
183 |
184 |
185 | true
186 |
187 |
188 | true
189 |
190 |
191 | true
192 |
193 |
194 | true
195 |
196 |
197 | true
198 |
199 |
200 | true
201 |
202 |
203 | true
204 |
205 |
206 | true
207 |
208 |
209 | true
210 |
211 |
212 |
213 | true
214 |
215 |
216 | source.extension.vsixmanifest
217 |
218 |
219 |
220 |
221 | MSBuild:Compile
222 | Designer
223 |
224 |
225 |
226 |
227 | 17.0.445
228 |
229 |
230 | 17.2.32505.113
231 |
232 |
233 | 17.2.32505.113
234 |
235 |
236 | 17.2.32505.113
237 |
238 |
239 | 17.2.32505.113
240 |
241 |
242 | 17.2.32505.113
243 |
244 |
245 | 17.2.3194
246 |
247 |
248 | 17.2.3194
249 |
250 |
251 | 17.2.32505.113
252 |
253 |
254 | 17.2.3194
255 |
256 |
257 | 17.2.32505.113
258 |
259 |
260 | 17.2.32505.113
261 |
262 |
263 | 17.2.32505.113
264 |
265 |
266 | 17.2.32505.113
267 |
268 |
269 | 17.2.32505.113
270 |
271 |
272 | 17.0.53
273 |
274 |
275 | 17.1.4057
276 | runtime; build; native; contentfiles; analyzers; buildtransitive
277 | all
278 |
279 |
280 | 13.0.2
281 |
282 |
283 | 17.2.32505.113
284 |
285 |
286 | 2.11.35
287 |
288 |
289 | 5.0.0
290 |
291 |
292 | 4.3.4
293 |
294 |
295 | 4.7.0
296 |
297 |
298 | 4.3.1
299 |
300 |
301 | 4.7.0
302 |
303 |
304 | 4.3.1
305 |
306 |
307 | 4.3.2
308 |
309 |
310 | 4.5.0
311 |
312 |
313 |
314 |
315 |
322 |
--------------------------------------------------------------------------------