Skip to content
This repository was archived by the owner on Jun 21, 2023. It is now read-only.

Commit be1ab80

Browse files
Merge pull request #2159 from github/autocompletebox-implementation
Implementing Autocompletebox
2 parents 2011888 + aa65fcc commit be1ab80

File tree

6 files changed

+88
-75
lines changed

6 files changed

+88
-75
lines changed

src/GitHub.App/ViewModels/GitHubPane/PullRequestCreationViewModel.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
using GitHub.Models.Drafts;
1919
using GitHub.Primitives;
2020
using GitHub.Services;
21+
using GitHub.UI;
2122
using GitHub.Validation;
2223
using Octokit;
2324
using ReactiveUI;
@@ -51,8 +52,9 @@ public PullRequestCreationViewModel(
5152
IPullRequestService service,
5253
INotificationService notifications,
5354
IMessageDraftStore draftStore,
54-
IGitService gitService)
55-
: this(modelServiceFactory, service, notifications, draftStore, gitService, DefaultScheduler.Instance)
55+
IGitService gitService,
56+
IAutoCompleteAdvisor autoCompleteAdvisor)
57+
: this(modelServiceFactory, service, notifications, draftStore, gitService, autoCompleteAdvisor, DefaultScheduler.Instance)
5658
{
5759
}
5860

@@ -62,19 +64,22 @@ public PullRequestCreationViewModel(
6264
INotificationService notifications,
6365
IMessageDraftStore draftStore,
6466
IGitService gitService,
67+
IAutoCompleteAdvisor autoCompleteAdvisor,
6568
IScheduler timerScheduler)
6669
{
6770
Guard.ArgumentNotNull(modelServiceFactory, nameof(modelServiceFactory));
6871
Guard.ArgumentNotNull(service, nameof(service));
6972
Guard.ArgumentNotNull(notifications, nameof(notifications));
7073
Guard.ArgumentNotNull(draftStore, nameof(draftStore));
7174
Guard.ArgumentNotNull(gitService, nameof(gitService));
75+
Guard.ArgumentNotNull(autoCompleteAdvisor, nameof(autoCompleteAdvisor));
7276
Guard.ArgumentNotNull(timerScheduler, nameof(timerScheduler));
7377

7478
this.service = service;
7579
this.modelServiceFactory = modelServiceFactory;
7680
this.draftStore = draftStore;
7781
this.gitService = gitService;
82+
this.AutoCompleteAdvisor = autoCompleteAdvisor;
7883
this.timerScheduler = timerScheduler;
7984

8085
this.WhenAnyValue(x => x.Branches)
@@ -336,6 +341,7 @@ protected string GetDraftKey()
336341

337342
public RemoteRepositoryModel GitHubRepository { get { return githubRepository?.Value; } }
338343
bool IsExecuting { get { return isExecuting.Value; } }
344+
public IAutoCompleteAdvisor AutoCompleteAdvisor { get; }
339345

340346
bool initialized;
341347
bool Initialized

src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCreationViewModel.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public interface IPullRequestCreationViewModel : IPanePageViewModel
1717
ReactiveCommand<Unit, Unit> Cancel { get; }
1818
string PRTitle { get; set; }
1919
ReactivePropertyValidator TitleValidator { get; }
20+
IAutoCompleteAdvisor AutoCompleteAdvisor { get; }
2021

2122
Task InitializeAsync(LocalRepositoryModel repository, IConnection connection);
2223
}

src/GitHub.UI/Assets/Controls/AutoCompleteBox.xaml

Lines changed: 41 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
xmlns:sys="clr-namespace:System;assembly=mscorlib"
1010
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
1111
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
12+
xmlns:vs="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.14.0"
1213
xmlns:ui="clr-namespace:GitHub.UI">
1314

1415
<!-- CommonValidationToolTipTemplate -->
@@ -59,62 +60,51 @@
5960
</Grid>
6061
</ControlTemplate>
6162

62-
63+
<ControlTemplate x:Key="ListViewItemControlTemplate" TargetType="{x:Type ListBoxItem}">
64+
<Border BorderBrush="{TemplateBinding BorderBrush}"
65+
BorderThickness="{TemplateBinding BorderThickness}"
66+
Background="{TemplateBinding Background}"
67+
SnapsToDevicePixels="True"
68+
Padding="{TemplateBinding Padding}">
69+
<ContentPresenter Content="{TemplateBinding Content}"
70+
ContentTemplate="{TemplateBinding ContentTemplate}"
71+
ContentTemplateSelector="{TemplateBinding ContentTemplateSelector}" />
72+
</Border>
73+
</ControlTemplate>
74+
6375
<Style x:Key="AutoCompleteListItemContainerStyle" TargetType="{x:Type ListBoxItem}">
64-
<Setter Property="SnapsToDevicePixels" Value="True"/>
65-
<Setter Property="Padding" Value="5,0,10,0"/>
66-
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
67-
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
68-
<Setter Property="Background" Value="{DynamicResource GHBoxBackgroundBrush}"/>
69-
<Setter Property="Foreground" Value="{DynamicResource GHBoxTextBrush}"/>
70-
<Setter Property="BorderBrush" Value="Transparent"/>
71-
<Setter Property="BorderThickness" Value="0"/>
72-
<Setter Property="FocusVisualStyle" Value="{DynamicResource NoMarginFocusVisual}"/>
73-
<Setter Property="Template">
74-
<Setter.Value>
75-
<ControlTemplate TargetType="{x:Type ListBoxItem}">
76-
<Border x:Name="Bd" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
77-
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
78-
</Border>
79-
<ControlTemplate.Triggers>
80-
<MultiTrigger>
81-
<MultiTrigger.Conditions>
82-
<Condition Property="IsMouseOver" Value="True"/>
83-
</MultiTrigger.Conditions>
84-
<Setter Property="BorderBrush" TargetName="Bd" Value="{DynamicResource GHBoxHoverBackgroundBrush}"/>
85-
</MultiTrigger>
86-
<MultiTrigger>
87-
<MultiTrigger.Conditions>
88-
<Condition Property="Selector.IsSelectionActive" Value="False"/>
89-
<Condition Property="IsSelected" Value="True"/>
90-
</MultiTrigger.Conditions>
91-
<Setter Property="Background" Value="{DynamicResource GHBoxSelectedBackgroundBrush}"/>
92-
<Setter Property="Foreground" Value="{DynamicResource GHBoxSelectedTextBrush}"/>
93-
</MultiTrigger>
94-
<MultiTrigger>
95-
<MultiTrigger.Conditions>
96-
<Condition Property="Selector.IsSelectionActive" Value="True"/>
97-
<Condition Property="IsSelected" Value="True"/>
98-
</MultiTrigger.Conditions>
99-
<Setter Property="Background" Value="{DynamicResource GHBoxActiveSelectionBackgroundBrush}"/>
100-
<Setter Property="Foreground" Value="{DynamicResource GHBoxActiveSelectionTextBrush}"/>
101-
</MultiTrigger>
102-
<Trigger Property="IsEnabled" Value="False">
103-
<Setter Property="Opacity" TargetName="Bd" Value="0.3"/>
104-
</Trigger>
105-
</ControlTemplate.Triggers>
106-
</ControlTemplate>
107-
</Setter.Value>
108-
</Setter>
76+
<Setter Property="BorderThickness" Value="0" />
77+
<Setter Property="Padding" Value="5,3,3,3" />
78+
<Setter Property="Template" Value="{StaticResource ListViewItemControlTemplate}" />
79+
<Style.Triggers>
80+
<Trigger Property="IsMouseOver" Value="True">
81+
<Setter Property="Background" Value="{DynamicResource {x:Static vs:ThemedDialogColors.ListItemMouseOverBrushKey}}" />
82+
<Setter Property="Foreground" Value="{DynamicResource {x:Static vs:ThemedDialogColors.ListItemMouseOverTextBrushKey}}" />
83+
</Trigger>
84+
<Trigger Property="IsSelected" Value="true">
85+
<Setter Property="Background" Value="{DynamicResource {x:Static vs:ThemedDialogColors.SelectedItemInactiveBrushKey}}" />
86+
<Setter Property="Foreground" Value="{DynamicResource {x:Static vs:ThemedDialogColors.SelectedItemInactiveTextBrushKey}}" />
87+
</Trigger>
88+
<MultiTrigger>
89+
<MultiTrigger.Conditions>
90+
<Condition Property="IsSelected" Value="True" />
91+
<Condition Property="Selector.IsSelectionActive" Value="True" />
92+
</MultiTrigger.Conditions>
93+
<Setter Property="Background" Value="{DynamicResource {x:Static vs:ThemedDialogColors.SelectedItemActiveBrushKey}}" />
94+
<Setter Property="Foreground" Value="{DynamicResource {x:Static vs:ThemedDialogColors.SelectedItemActiveTextBrushKey}}" />
95+
</MultiTrigger>
96+
<Trigger Property="IsEnabled" Value="False">
97+
<Setter Property="Foreground" Value="{DynamicResource {x:Static vs:ThemedDialogColors.ListItemDisabledTextBrushKey}}" />
98+
</Trigger>
99+
</Style.Triggers>
109100
</Style>
110-
111101

112102
<!-- input:AutoCompleteBox -->
113103
<Style TargetType="ui:AutoCompleteBox">
114104
<Setter Property="IsTabStop" Value="False" />
115105
<Setter Property="Padding" Value="2" />
116-
<Setter Property="Background" Value="{DynamicResource DefaultBackgroundBrush}" />
117-
<Setter Property="Foreground" Value="{DynamicResource GHTextBrush}" />
106+
<Setter Property="Background" Value="{DynamicResource {x:Static vs:ThemedDialogColors.WindowPanelBrushKey}}" />
107+
<Setter Property="Foreground" Value="{DynamicResource {x:Static vs:ThemedDialogColors.WindowPanelTextBrushKey}}" />
118108
<Setter Property="MinWidth" Value="45" />
119109
<Setter Property="ItemContainerStyle" Value="{StaticResource AutoCompleteListItemContainerStyle}" />
120110

@@ -125,6 +115,7 @@
125115
<ContentPresenter
126116
x:Name="Text"
127117
Content="{Binding InputElement.Control, RelativeSource={RelativeSource TemplatedParent}}"/>
118+
<!-- TODO: What should we use for validation BorderBrush? -->
128119
<Border
129120
x:Name="ValidationErrorElement"
130121
Visibility="Collapsed"
@@ -174,7 +165,7 @@
174165
x:Name="PopupBorder"
175166
HorizontalAlignment="Stretch"
176167
Opacity="0"
177-
BorderBrush="#B0B0B0"
168+
BorderBrush="{DynamicResource {x:Static vs:CommonControlsColors.TextBoxBorderFocusedBrushKey}}"
178169
BorderThickness="1">
179170
<ListBox
180171
x:Name="Selector"

src/GitHub.UI/Views/AutoCompleteSuggestionView.xaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
<RectangleGeometry Rect="0,0,24,24" RadiusX="2" RadiusY="2" />
2121
</Image.Clip>
2222
</Image>
23-
<TextBlock x:Name="suggestionText" Style="{DynamicResource GitHubTextBlock}" VerticalAlignment="Center">
24-
<Run x:Name="name" Foreground="{DynamicResource GHTextBrush}" Text="Name" /> <Run x:Name="description" Foreground="{DynamicResource GHTextSecondaryBrush}" Text="Description" />
23+
<TextBlock x:Name="suggestionText" VerticalAlignment="Center">
24+
<Run x:Name="name" Text="Name" /> <Run x:Name="description" Text="Description" />
2525
</TextBlock>
2626
</StackPanel>
2727
</UserControl>

src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCreationView.xaml

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -150,18 +150,30 @@
150150
SpellCheck.IsEnabled="True"
151151
AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PullRequestCreationTitleTextBox}"/>
152152

153-
<ghfvs:PromptTextBox Grid.Row="2"
154-
MinHeight="100"
155-
Margin="10,5"
156-
AcceptsReturn="True"
157-
PromptText="{x:Static ghfvs:Resources.Description}"
158-
Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}"
159-
Style="{DynamicResource GitHubVsPromptTextBox}"
160-
VerticalScrollBarVisibility="Auto"
161-
TextWrapping="Wrap"
162-
SpellCheck.IsEnabled="True"
163-
AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PullRequestCreationDescriptionTextBox}"/>
164-
153+
<ghfvs:AutoCompleteBox
154+
Grid.Row="3"
155+
MinHeight="100"
156+
Margin="10,5"
157+
Advisor="{Binding AutoCompleteAdvisor}"
158+
>
159+
<ghfvs:AutoCompleteBox.ItemTemplate>
160+
<DataTemplate>
161+
<ghfvs:AutoCompleteSuggestionView ViewModel="{Binding}" />
162+
</DataTemplate>
163+
</ghfvs:AutoCompleteBox.ItemTemplate>
164+
<ghfvs:AutoCompleteBox.InputElement>
165+
<ghfvs:TextBoxAutoCompleteTextInput>
166+
<ghfvs:PromptTextBox AcceptsReturn="True"
167+
PromptText="{x:Static ghfvs:Resources.Description}"
168+
Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}"
169+
Style="{DynamicResource GitHubVsPromptTextBox}"
170+
VerticalScrollBarVisibility="Auto"
171+
TextWrapping="Wrap"
172+
SpellCheck.IsEnabled="True"/>
173+
</ghfvs:TextBoxAutoCompleteTextInput>
174+
</ghfvs:AutoCompleteBox.InputElement>
175+
</ghfvs:AutoCompleteBox>
176+
165177
<ghfvs:ValidationMessage x:Name="titleValidationMessage"
166178
Grid.Row="3"
167179
Fill="{StaticResource GitHubWarningBrush}"

test/GitHub.App.UnitTests/ViewModels/GitHubPane/PullRequestCreationViewModelTests.cs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ struct TestData
5555
public IConnection Connection;
5656
public IApiClient ApiClient;
5757
public IModelService ModelService;
58+
public IAutoCompleteAdvisor AutoCompleteAdvisor { get; set; }
5859

5960
public IModelServiceFactory GetModelServiceFactory()
6061
{
@@ -78,6 +79,7 @@ static TestData PrepareTestData(
7879
var connection = Substitute.For<IConnection>();
7980
var api = Substitute.For<IApiClient>();
8081
var ms = Substitute.For<IModelService>();
82+
var autoCompleteAdvisor = Substitute.For<IAutoCompleteAdvisor>();
8183

8284
connection.HostAddress.Returns(HostAddress.Create("https://github.com"));
8385

@@ -121,7 +123,8 @@ static TestData PrepareTestData(
121123
NotificationService = notifications,
122124
Connection = connection,
123125
ApiClient = api,
124-
ModelService = ms
126+
ModelService = ms,
127+
AutoCompleteAdvisor = autoCompleteAdvisor
125128
};
126129
}
127130

@@ -147,7 +150,7 @@ public async Task TargetBranchDisplayNameIncludesRepoOwnerWhenForkAsync()
147150
var prservice = new PullRequestService(data.GitClient, data.GitService, Substitute.For<IVSGitExt>(), Substitute.For<IApiClientFactory>(), Substitute.For<IGraphQLClientFactory>(), data.ServiceProvider.GetOperatingSystem(), Substitute.For<IUsageTracker>());
148151
prservice.GetPullRequestTemplate(data.ActiveRepo).Returns(Observable.Empty<string>());
149152
var vm = new PullRequestCreationViewModel(data.GetModelServiceFactory(), prservice, data.NotificationService,
150-
Substitute.For<IMessageDraftStore>(), data.GitService);
153+
Substitute.For<IMessageDraftStore>(), data.GitService, data.AutoCompleteAdvisor);
151154
await vm.InitializeAsync(data.ActiveRepo, data.Connection);
152155
Assert.That("octokit/master", Is.EqualTo(vm.TargetBranch.DisplayName));
153156
}
@@ -183,7 +186,7 @@ public async Task CreatingPRsAsync(
183186

184187
var prservice = new PullRequestService(data.GitClient, data.GitService, Substitute.For<IVSGitExt>(), Substitute.For<IApiClientFactory>(), Substitute.For<IGraphQLClientFactory>(), data.ServiceProvider.GetOperatingSystem(), Substitute.For<IUsageTracker>());
185188
var vm = new PullRequestCreationViewModel(data.GetModelServiceFactory(), prservice, data.NotificationService,
186-
Substitute.For<IMessageDraftStore>(), data.GitService);
189+
Substitute.For<IMessageDraftStore>(), data.GitService, data.AutoCompleteAdvisor);
187190
await vm.InitializeAsync(data.ActiveRepo, data.Connection);
188191

189192
// the TargetBranch property gets set to whatever the repo default is (we assume master here),
@@ -226,7 +229,7 @@ public async Task TemplateIsUsedIfPresentAsync()
226229
prservice.GetPullRequestTemplate(data.ActiveRepo).Returns(Observable.Return("Test PR template"));
227230

228231
var vm = new PullRequestCreationViewModel(data.GetModelServiceFactory(), prservice, data.NotificationService,
229-
Substitute.For<IMessageDraftStore>(), data.GitService);
232+
Substitute.For<IMessageDraftStore>(), data.GitService, data.AutoCompleteAdvisor);
230233
await vm.InitializeAsync(data.ActiveRepo, data.Connection);
231234

232235
Assert.That("Test PR template", Is.EqualTo(vm.Description));
@@ -246,7 +249,7 @@ public async Task LoadsDraft()
246249

247250
var prservice = Substitute.For<IPullRequestService>();
248251
var vm = new PullRequestCreationViewModel(data.GetModelServiceFactory(), prservice, data.NotificationService,
249-
draftStore, data.GitService);
252+
draftStore, data.GitService, data.AutoCompleteAdvisor);
250253
await vm.InitializeAsync(data.ActiveRepo, data.Connection);
251254

252255
Assert.That(vm.PRTitle, Is.EqualTo("This is a Title."));
@@ -261,7 +264,7 @@ public async Task UpdatesDraftWhenDescriptionChanges()
261264
var draftStore = Substitute.For<IMessageDraftStore>();
262265
var prservice = Substitute.For<IPullRequestService>();
263266
var vm = new PullRequestCreationViewModel(data.GetModelServiceFactory(), prservice, data.NotificationService,
264-
draftStore, data.GitService, scheduler);
267+
draftStore, data.GitService, data.AutoCompleteAdvisor, scheduler);
265268
await vm.InitializeAsync(data.ActiveRepo, data.Connection);
266269

267270
vm.Description = "Body changed.";
@@ -284,7 +287,7 @@ public async Task UpdatesDraftWhenTitleChanges()
284287
var draftStore = Substitute.For<IMessageDraftStore>();
285288
var prservice = Substitute.For<IPullRequestService>();
286289
var vm = new PullRequestCreationViewModel(data.GetModelServiceFactory(), prservice, data.NotificationService,
287-
draftStore, data.GitService, scheduler);
290+
draftStore, data.GitService, data.AutoCompleteAdvisor, scheduler);
288291
await vm.InitializeAsync(data.ActiveRepo, data.Connection);
289292

290293
vm.PRTitle = "Title changed.";
@@ -307,7 +310,7 @@ public async Task DeletesDraftWhenPullRequestSubmitted()
307310
var draftStore = Substitute.For<IMessageDraftStore>();
308311
var prservice = Substitute.For<IPullRequestService>();
309312
var vm = new PullRequestCreationViewModel(data.GetModelServiceFactory(), prservice, data.NotificationService, draftStore,
310-
data.GitService, scheduler);
313+
data.GitService, data.AutoCompleteAdvisor, scheduler);
311314
await vm.InitializeAsync(data.ActiveRepo, data.Connection);
312315

313316
await vm.CreatePullRequest.Execute();
@@ -323,7 +326,7 @@ public async Task DeletesDraftWhenCanceled()
323326
var draftStore = Substitute.For<IMessageDraftStore>();
324327
var prservice = Substitute.For<IPullRequestService>();
325328
var vm = new PullRequestCreationViewModel(data.GetModelServiceFactory(), prservice, data.NotificationService, draftStore,
326-
data.GitService, scheduler);
329+
data.GitService, data.AutoCompleteAdvisor, scheduler);
327330
await vm.InitializeAsync(data.ActiveRepo, data.Connection);
328331

329332
await vm.Cancel.Execute();

0 commit comments

Comments
 (0)