Skip to content

Commit fceae88

Browse files
committed
github: readd WPF GitHub divider and 2FA controls
Reimplement/readd the WPF-based GitHub horizontal divider and six digit (2FA code) controls.
1 parent 4cb34e9 commit fceae88

File tree

6 files changed

+315
-0
lines changed

6 files changed

+315
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
3+
<ResourceDictionary.MergedDictionaries>
4+
<ResourceDictionary Source="pack://application:,,,/Microsoft.Git.CredentialManager.UI.Windows;component/Assets/Styles.xaml"/>
5+
</ResourceDictionary.MergedDictionaries>
6+
</ResourceDictionary>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<UserControl x:Class="GitHub.UI.Controls.HorizontalShadowDivider"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
mc:Ignorable="d"
7+
d:DesignWidth="200"
8+
Width="200" Height="13.75"
9+
Opacity="0.6"
10+
IsHitTestVisible="False"
11+
VerticalAlignment="Top">
12+
<UserControl.Resources>
13+
<ResourceDictionary>
14+
<SolidColorBrush x:Key="HorizontalDividerBorderBrush" Color="#F1F1F1"/>
15+
<RadialGradientBrush x:Key="HorizontalDividerShadowBrush">
16+
<GradientStop Color="#F1F1F1"/>
17+
<GradientStop Color="#00000000" Offset="1"/>
18+
</RadialGradientBrush>
19+
</ResourceDictionary>
20+
</UserControl.Resources>
21+
<Grid IsHitTestVisible="False">
22+
<Rectangle Height="1"
23+
Fill="{StaticResource HorizontalDividerBorderBrush}"
24+
VerticalAlignment="Top"
25+
IsHitTestVisible="False"/>
26+
<Rectangle x:Name="shadow"
27+
Height="4"
28+
StrokeThickness="0"
29+
VerticalAlignment="Top"
30+
Margin="0,-2,0,0"
31+
Opacity="0.25"
32+
IsHitTestVisible="False"
33+
Fill="{StaticResource HorizontalDividerShadowBrush}">
34+
<Rectangle.Clip>
35+
<RectangleGeometry Rect="0,2,10000,2" />
36+
</Rectangle.Clip>
37+
</Rectangle>
38+
</Grid>
39+
</UserControl>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Windows.Controls;
2+
3+
namespace GitHub.UI.Controls
4+
{
5+
public partial class HorizontalShadowDivider : UserControl
6+
{
7+
public HorizontalShadowDivider()
8+
{
9+
InitializeComponent();
10+
}
11+
}
12+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<UserControl x:Class="GitHub.UI.Controls.SixDigitInput"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
mc:Ignorable="d">
7+
<UserControl.Resources>
8+
<ResourceDictionary>
9+
<ResourceDictionary.MergedDictionaries>
10+
<ResourceDictionary Source="../Assets/Styles.xaml"/>
11+
</ResourceDictionary.MergedDictionaries>
12+
<Style TargetType="TextBox" BasedOn="{StaticResource RoundTextBox}">
13+
<Setter Property="Width" Value="36"/>
14+
<Setter Property="Height" Value="46"/>
15+
<Setter Property="MinWidth" Value="36"/>
16+
<Setter Property="Margin" Value="0,0,8,0"/>
17+
<Setter Property="HorizontalContentAlignment" Value="Center"/>
18+
<Setter Property="VerticalContentAlignment" Value="Center"/>
19+
<Setter Property="FontSize" Value="20"/>
20+
<Setter Property="MaxLength" Value="1"/>
21+
</Style>
22+
</ResourceDictionary>
23+
</UserControl.Resources>
24+
<StackPanel Orientation="Horizontal">
25+
<TextBox x:Name="one"/>
26+
<TextBox x:Name="two"/>
27+
<TextBox x:Name="three"/>
28+
<TextBox x:Name="four"/>
29+
<TextBox x:Name="five"/>
30+
<TextBox x:Name="six" Margin="0"/>
31+
</StackPanel>
32+
</UserControl>
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
using System;
2+
using System.Globalization;
3+
using System.Linq;
4+
using System.Windows;
5+
using System.Windows.Controls;
6+
using System.Windows.Input;
7+
using Microsoft.Git.CredentialManager;
8+
using Microsoft.Git.CredentialManager.UI.Controls;
9+
10+
namespace GitHub.UI.Controls
11+
{
12+
public partial class SixDigitInput : UserControl, IFocusable
13+
{
14+
public static readonly DependencyProperty TextProperty =
15+
DependencyProperty.Register(nameof(Text), typeof(string), typeof(SixDigitInput), new PropertyMetadata(""));
16+
17+
private readonly TextBox[] _textBoxes;
18+
19+
public SixDigitInput()
20+
{
21+
InitializeComponent();
22+
23+
_textBoxes = new[]
24+
{
25+
one,
26+
two,
27+
three,
28+
four,
29+
five,
30+
six
31+
};
32+
33+
foreach (var textBox in _textBoxes)
34+
{
35+
SetUpTextBox(textBox);
36+
}
37+
}
38+
39+
private void SetUpTextBox(TextBox textBox)
40+
{
41+
EnsureArgument.NotNull(textBox, nameof(textBox));
42+
43+
DataObject.AddPastingHandler(textBox, OnPaste);
44+
45+
textBox.GotFocus += (sender, args) => textBox.SelectAll();
46+
47+
textBox.PreviewKeyDown += (sender, args) =>
48+
{
49+
// Handle navigation.
50+
if (args.Key == Key.Left || args.Key == Key.Right || args.Key == Key.Back)
51+
{
52+
args.Handled = args.Key == Key.Right ? MoveNext() : MovePrevious();
53+
if (args.Key == Key.Back)
54+
{
55+
textBox.Text = "";
56+
}
57+
}
58+
59+
if (args.Key != Key.D0
60+
&& args.Key != Key.D1
61+
&& args.Key != Key.D2
62+
&& args.Key != Key.D3
63+
&& args.Key != Key.D4
64+
&& args.Key != Key.D5
65+
&& args.Key != Key.D6
66+
&& args.Key != Key.D7
67+
&& args.Key != Key.D8
68+
&& args.Key != Key.D9
69+
&& args.Key != Key.NumPad0
70+
&& args.Key != Key.NumPad1
71+
&& args.Key != Key.NumPad2
72+
&& args.Key != Key.NumPad3
73+
&& args.Key != Key.NumPad4
74+
&& args.Key != Key.NumPad5
75+
&& args.Key != Key.NumPad6
76+
&& args.Key != Key.NumPad7
77+
&& args.Key != Key.NumPad8
78+
&& args.Key != Key.NumPad9
79+
&& args.Key != Key.Tab
80+
&& args.Key != Key.Escape
81+
&& args.Key != Key.Delete
82+
&& (!(args.Key == Key.V && args.KeyboardDevice.Modifiers == ModifierKeys.Control))
83+
&& (!(args.Key == Key.Insert && args.KeyboardDevice.Modifiers == ModifierKeys.Shift)))
84+
{
85+
args.Handled = true;
86+
}
87+
};
88+
89+
textBox.SelectionChanged += (sender, args) =>
90+
{
91+
// Make sure we can't insert additional text into a textbox.
92+
// Each textbox should only allow one character.
93+
if (textBox.SelectionLength == 0 && textBox.Text.Any())
94+
{
95+
textBox.SelectAll();
96+
}
97+
};
98+
99+
textBox.TextChanged += (sender, args) =>
100+
{
101+
SetValue(TextProperty, string.Join("", GetTwoFactorCode()));
102+
var change = args.Changes.FirstOrDefault();
103+
args.Handled = (change != null && change.AddedLength > 0) && MoveNext();
104+
};
105+
}
106+
107+
public void SetFocus()
108+
{
109+
Keyboard.Focus(one);
110+
}
111+
112+
private void OnPaste(object sender, DataObjectPastingEventArgs e)
113+
{
114+
if (sender == null)
115+
{
116+
throw new ArgumentNullException(nameof(sender));
117+
}
118+
if (e == null)
119+
{
120+
throw new ArgumentNullException(nameof(e));
121+
}
122+
123+
var isText = e.SourceDataObject.GetDataPresent(DataFormats.Text, true);
124+
if (!isText) return;
125+
126+
var text = e.SourceDataObject.GetData(DataFormats.Text) as string;
127+
if (text == null) return;
128+
e.CancelCommand();
129+
SetText(text);
130+
}
131+
132+
void SetText(string text)
133+
{
134+
if (string.IsNullOrEmpty(text))
135+
{
136+
foreach (var textBox in _textBoxes)
137+
{
138+
textBox.Text = "";
139+
}
140+
SetValue(TextProperty, text);
141+
return;
142+
}
143+
var digits = text.Where(char.IsDigit).ToList();
144+
for (int i = 0; i < Math.Min(6, digits.Count); i++)
145+
{
146+
_textBoxes[i].Text = digits[i].ToString(CultureInfo.InvariantCulture);
147+
}
148+
SetValue(TextProperty, string.Join("", digits));
149+
}
150+
151+
public string Text
152+
{
153+
get => (string)GetValue(TextProperty);
154+
set => SetText(value);
155+
}
156+
157+
bool MoveNext()
158+
{
159+
return MoveFocus(FocusNavigationDirection.Next);
160+
}
161+
162+
bool MovePrevious()
163+
{
164+
return MoveFocus(FocusNavigationDirection.Previous);
165+
}
166+
167+
static bool MoveFocus(FocusNavigationDirection navigationDirection)
168+
{
169+
var traversalRequest = new TraversalRequest(navigationDirection);
170+
var keyboardFocus = Keyboard.FocusedElement as UIElement;
171+
if (keyboardFocus != null)
172+
{
173+
keyboardFocus.MoveFocus(traversalRequest);
174+
return true;
175+
}
176+
return false;
177+
}
178+
179+
private string GetTwoFactorCode()
180+
{
181+
return string.Join("", _textBoxes.Select(textBox => textBox.Text));
182+
}
183+
}
184+
}

src/windows/Shared.UI.Windows/Assets/Styles.xaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,47 @@
2424
<SolidColorBrush x:Key="AccentButtonDisabledForegroundBrush" Color="{StaticResource DisabledForegroundColor}" />
2525
<SolidColorBrush x:Key="PromptTextBrush" Color="{StaticResource PromptTextColor}" />
2626

27+
<Style x:Key="RoundTextBox" TargetType="TextBox">
28+
<Setter Property="FontSize" Value="14"/>
29+
<Setter Property="BorderBrush" Value="{StaticResource ControlBorderBrush}"/>
30+
<Setter Property="Padding" Value="6"/>
31+
<Setter Property="Template">
32+
<Setter.Value>
33+
<ControlTemplate TargetType="TextBox">
34+
<Grid>
35+
<Border x:Name="Bd"
36+
Background="{TemplateBinding Background}"
37+
Margin="1"
38+
CornerRadius="2"
39+
BorderBrush="{TemplateBinding BorderBrush}"
40+
BorderThickness="{TemplateBinding BorderThickness}"/>
41+
42+
<Grid>
43+
<ScrollViewer x:Name="PART_ContentHost"
44+
Padding="{TemplateBinding Padding}"
45+
Focusable="False"
46+
HorizontalScrollBarVisibility="Hidden"
47+
VerticalScrollBarVisibility="Hidden"
48+
VerticalAlignment="Top"
49+
Margin="0"/>
50+
</Grid>
51+
</Grid>
52+
53+
<ControlTemplate.Triggers>
54+
<Trigger Property="IsEnabled" Value="False">
55+
<Setter Property="Opacity" Value="0.5"/>
56+
</Trigger>
57+
<Trigger Property="IsKeyboardFocusWithin" Value="True">
58+
<Setter Property="BorderBrush" TargetName="Bd" Value="{StaticResource AccentBrush}"/>
59+
<Setter Property="BorderThickness" TargetName="Bd" Value="2"/>
60+
<Setter Property="Margin" TargetName="Bd" Value="0"/>
61+
</Trigger>
62+
</ControlTemplate.Triggers>
63+
</ControlTemplate>
64+
</Setter.Value>
65+
</Setter>
66+
</Style>
67+
2768
<Style x:Key="PromptRoundTextBox" TargetType="sharedControls:PromptTextBox">
2869
<Setter Property="FontSize" Value="14"/>
2970
<Setter Property="BorderBrush" Value="{StaticResource ControlBorderBrush}"/>
@@ -84,6 +125,7 @@
84125
</Setter>
85126
</Style>
86127

128+
<Style TargetType="TextBox" BasedOn="{StaticResource RoundTextBox}"/>
87129
<Style TargetType="sharedControls:PromptTextBox" BasedOn="{StaticResource PromptRoundTextBox}"/>
88130
<Style TargetType="sharedControls:PasswordPromptTextBox" BasedOn="{StaticResource PromptRoundTextBox}"/>
89131

0 commit comments

Comments
 (0)