Skip to content

Commit d9a3c59

Browse files
Merge pull request #164 from CodebreakerApp/99-use-vsm-to-change-visibility-with-game-windows
99 use vsm to change visibility with game windows
2 parents 4777e6b + 62b4611 commit d9a3c59

18 files changed

Lines changed: 413 additions & 60 deletions

File tree

src/CodeBreaker.Avalonia/CodeBreaker.Avalonia/Views/Components/GameResultDisplay.axaml

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,37 @@
1111
<UserControl.Resources>
1212
<Converter:GameStatusToBooleanConverter x:Key="GameStatusToBooleanConverter" />
1313
</UserControl.Resources>
14-
<Grid>
15-
<StackPanel Orientation="Vertical"
16-
IsVisible="{Binding GameStatus, Mode=OneWay, Converter={StaticResource GameStatusToBooleanConverter}, ConverterParameter=Won}">
14+
<UserControl.Styles>
15+
<Style Selector="Grid > StackPanel">
16+
<Setter Property="IsVisible" Value="False" />
17+
</Style>
18+
<Style Selector=".won">
19+
<Style Selector="^ StackPanel#WonPanel">
20+
<Setter Property="IsVisible" Value="True" />
21+
</Style>
22+
<Style Selector="^ StackPanel#LostPanel">
23+
<Setter Property="IsVisible" Value="False" />
24+
</Style>
25+
</Style>
26+
<Style Selector=".lost">
27+
<Style Selector="^ StackPanel#WonPanel">
28+
<Setter Property="IsVisible" Value="False" />
29+
</Style>
30+
<Style Selector="^ StackPanel#LostPanel">
31+
<Setter Property="IsVisible" Value="True" />
32+
</Style>
33+
</Style>
34+
</UserControl.Styles>
35+
<Grid
36+
x:Name="Wrapper"
37+
Classes.won="{Binding GameStatus, Mode=OneWay, Converter={StaticResource GameStatusToBooleanConverter}, ConverterParameter=Won}"
38+
Classes.lost="{Binding GameStatus, Mode=OneWay, Converter={StaticResource GameStatusToBooleanConverter}, ConverterParameter=Lost}">
39+
<StackPanel x:Name="WonPanel" Orientation="Vertical">
1740
<gif:GifImage SourceUri="avares://CodeBreaker.Avalonia/Assets/Animations/WonAnimation_300_opt.gif" AutoStart="True" MaxHeight="300" />
1841
<TextBlock Text="Congratulations - you won the game 🎉🏆" FontSize="20" HorizontalAlignment="Center" Margin="0,50,0,20" />
1942
<Button Content="Great, play again" HorizontalAlignment="Center" Margin="0,0,0,50" Command="{Binding StartGameCommand, Mode=OneWay}" />
2043
</StackPanel>
21-
<StackPanel Orientation="Vertical"
22-
IsVisible="{Binding GameStatus, Mode=OneWay, Converter={StaticResource GameStatusToBooleanConverter}, ConverterParameter=Lost}">
44+
<StackPanel x:Name="LostPanel" Orientation="Vertical">
2345
<gif:GifImage SourceUri="avares://CodeBreaker.Avalonia/Assets/Animations/LostAnimation_300_opt.gif" AutoStart="True" MaxHeight="300" />
2446
<TextBlock Text="Oh no - you lost the game 😑" FontSize="20" HorizontalAlignment="Center" Margin="0,50,0,20" />
2547
<Button Content="Ok, try again" HorizontalAlignment="Center" Margin="0,0,0,50" Command="{Binding StartGameCommand, Mode=OneWay}" />

src/CodeBreaker.Avalonia/CodeBreaker.Avalonia/Views/Pages/GamePage.axaml

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,48 @@
1414
<UserControl.Resources>
1515
<converter:GameStatusToBooleanConverter x:Key="GameStatusToBooleanConverter" />
1616
</UserControl.Resources>
17+
<UserControl.Styles>
18+
<Style Selector=".start">
19+
<Style Selector="^ Grid#StartGameArea">
20+
<Setter Property="IsVisible" Value="True" />
21+
</Style>
22+
<Style Selector="^ components|PegSelectionView#PegSelectionArea">
23+
<Setter Property="IsVisible" Value="False" />
24+
</Style>
25+
<Style Selector="^ ScrollViewer#PegScrollViewer">
26+
<Setter Property="IsVisible" Value="False" />
27+
</Style>
28+
</Style>
29+
<Style Selector=".playing">
30+
<Style Selector="^ Grid#StartGameArea">
31+
<Setter Property="IsVisible" Value="False" />
32+
</Style>
33+
<Style Selector="^ components|PegSelectionView#PegSelectionArea">
34+
<Setter Property="IsVisible" Value="True" />
35+
</Style>
36+
<Style Selector="^ ScrollViewer#PegScrollViewer">
37+
<Setter Property="IsVisible" Value="True" />
38+
</Style>
39+
</Style>
40+
<Style Selector=".finished">
41+
<Style Selector="^ Grid#StartGameArea">
42+
<Setter Property="IsVisible" Value="False" />
43+
</Style>
44+
<Style Selector="^ components|PegSelectionView#PegSelectionArea">
45+
<Setter Property="IsVisible" Value="False" />
46+
</Style>
47+
<Style Selector="^ ScrollViewer#PegScrollViewer">
48+
<Setter Property="IsVisible" Value="True" />
49+
</Style>
50+
</Style>
51+
</UserControl.Styles>
1752
<Grid RowDefinitions="Auto,*,Auto">
1853
<components:GameResultDisplay Grid.Row="0" />
19-
<Grid Grid.Row="0"
54+
<Grid x:Name="StartGameArea"
55+
Grid.Row="0"
2056
ColumnDefinitions="3*,1*"
2157
RowDefinitions="Auto,Auto"
22-
Margin="8"
23-
IsVisible="{Binding GameStatus, Mode=OneWay, Converter={StaticResource GameStatusToBooleanConverter}, ConverterParameter=Start}">
58+
Margin="8">
2459
<StackPanel
2560
Grid.Column="0" Grid.Row="0"
2661
Grid.RowSpan="{OnFormFactor 2, Mobile=1}" Grid.ColumnSpan="{OnFormFactor 1, Mobile=2}"
@@ -43,14 +78,13 @@
4378
</Button>
4479
</Grid>
4580
<components:PegSelectionView
81+
x:Name="PegSelectionArea"
4682
Grid.Row="0"
47-
IsVisible="{Binding GameStatus, Mode=OneWay, Converter={StaticResource GameStatusToBooleanConverter}, ConverterParameter=Cancelable}"
4883
Margin="{OnFormFactor '55,0,0,15', Mobile='0,0,0,15'}" />
4984
<ScrollViewer
50-
x:Name="pegScrollViewer"
85+
x:Name="PegScrollViewer"
5186
Padding="0,0,0,15"
52-
Grid.Row="1"
53-
IsVisible="{Binding GameStatus, Mode=OneWay, ConverterParameter=Running}">
87+
Grid.Row="1">
5488
<ItemsControl
5589
ItemsSource="{Binding GameMoves, Mode=OneWay}"
5690
ItemTemplate="{StaticResource PegsTemplate}">

src/CodeBreaker.Avalonia/CodeBreaker.Avalonia/Views/Pages/GamePage.axaml.cs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public GamePage()
1616
_navigationService = App.GetService<INavigationService>();
1717
InitializeComponent();
1818
WeakReferenceMessenger.Default.Register(this);
19+
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
20+
Classes.Add("start");
1921
}
2022

2123
protected override void OnUnloaded(RoutedEventArgs e)
@@ -30,8 +32,23 @@ public void Receive(GameMoveMessage message)
3032
if (message.GameMoveValue is not GameMoveValue.Completed)
3133
return;
3234

33-
pegScrollViewer.UpdateLayout();
34-
pegScrollViewer.ScrollToEnd();
35+
PegScrollViewer.UpdateLayout();
36+
PegScrollViewer.ScrollToEnd();
37+
}
38+
39+
private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
40+
{
41+
if (e.PropertyName != nameof(GamePageViewModel.GameStatus))
42+
return;
43+
44+
var stateName = ViewModel.GameStatus switch
45+
{
46+
GameMode.Started or GameMode.MoveSet => "playing",
47+
GameMode.Won or GameMode.Lost => "finished",
48+
_ => "start",
49+
};
50+
Classes.Clear();
51+
Classes.Add(stateName);
3552
}
3653

3754
private void ToTestPageButtonClicked(object? sender, RoutedEventArgs e)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Microsoft.Maui.Controls;
2+
3+
internal static class VisualStateHelpers
4+
{
5+
public static bool GoToState(this VisualElement element, string stateName) =>
6+
VisualStateManager.GoToState(element, stateName);
7+
}

src/Codebreaker.MAUI/Views/Components/GameResultDisplay.xaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,28 @@
88
x:Name="this"
99
x:DataType="{x:Type vm:GamePageViewModel}">
1010
<Grid>
11+
<VisualStateManager.VisualStateGroups>
12+
<VisualStateGroup>
13+
<VisualState x:Name="Default">
14+
<VisualState.Setters>
15+
<Setter TargetName="WonPanel" Property="IsVisible" Value="False" />
16+
<Setter TargetName="LostPanel" Property="IsVisible" Value="False" />
17+
</VisualState.Setters>
18+
</VisualState>
19+
<VisualState x:Name="Won">
20+
<VisualState.Setters>
21+
<Setter TargetName="WonPanel" Property="IsVisible" Value="True" />
22+
<Setter TargetName="LostPanel" Property="IsVisible" Value="False" />
23+
</VisualState.Setters>
24+
</VisualState>
25+
<VisualState x:Name="Lost">
26+
<VisualState.Setters>
27+
<Setter TargetName="WonPanel" Property="IsVisible" Value="False" />
28+
<Setter TargetName="LostPanel" Property="IsVisible" Value="True" />
29+
</VisualState.Setters>
30+
</VisualState>
31+
</VisualStateGroup>
32+
</VisualStateManager.VisualStateGroups>
1133
<VerticalStackLayout IsVisible="{Binding GameStatus, Mode=OneWay, Converter={converter:GameStatusToIsVisibleConverter}, ConverterParameter=Won}">
1234
<Image Source="wonanimation_300_opt.gif" IsAnimationPlaying="True" MaximumHeightRequest="300" />
1335
<Label Text="Congratulations - you won the game 🎉🏆" FontSize="20" HorizontalOptions="Center" Margin="0,50,0,20" />
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,34 @@
1+
using System.ComponentModel;
2+
13
namespace Codebreaker.MAUI.Views.Components;
24

35
public partial class GameResultDisplay : ContentView
46
{
57
public GameResultDisplay()
68
{
79
InitializeComponent();
10+
Loaded += OnLoaded;
11+
this.GoToState("Default");
12+
}
13+
14+
public GamePageViewModel ViewModel => (GamePageViewModel)BindingContext;
15+
16+
private void OnLoaded(object? sender, EventArgs e)
17+
{
18+
ViewModel.PropertyChanged += OnViewModelPropertyChanged;
19+
}
20+
21+
private void OnViewModelPropertyChanged(object? sender, PropertyChangedEventArgs args)
22+
{
23+
if (args.PropertyName != nameof(GamePageViewModel.GameStatus))
24+
return;
25+
26+
var stateName = ViewModel.GameStatus switch
27+
{
28+
GameMode.Won => "Won",
29+
GameMode.Lost => "Lost",
30+
_ => "Default"
31+
};
32+
this.GoToState(stateName);
833
}
934
}

src/Codebreaker.MAUI/Views/Pages/GamePage.xaml

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,43 @@
1414
<ContentPage.Resources>
1515
<conv:GameStatusToIsVisibleConverter x:Key="GameStatusVisibleConverter" />
1616
</ContentPage.Resources>
17-
<Grid RowDefinitions="auto, auto, *" Padding="15">
17+
18+
<Grid RowDefinitions="auto, auto, *" Padding="15" x:Name="ContentWrapper">
19+
<VisualStateManager.VisualStateGroups>
20+
<VisualStateGroup>
21+
<VisualState x:Name="Start">
22+
<VisualState.Setters>
23+
<Setter TargetName="StartGameArea" Property="IsVisible" Value="True" />
24+
<Setter TargetName="PegSelectionArea" Property="IsVisible" Value="False" />
25+
<Setter TargetName="PegScrollView" Property="IsVisible" Value="False" />
26+
</VisualState.Setters>
27+
</VisualState>
28+
<VisualState x:Name="Playing">
29+
<VisualState.Setters>
30+
<Setter TargetName="StartGameArea" Property="IsVisible" Value="False" />
31+
<Setter TargetName="PegSelectionArea" Property="IsVisible" Value="True" />
32+
<Setter TargetName="PegScrollView" Property="IsVisible" Value="True" />
33+
</VisualState.Setters>
34+
</VisualState>
35+
<VisualState x:Name="Finished">
36+
<VisualState.Setters>
37+
<Setter TargetName="StartGameArea" Property="IsVisible" Value="False" />
38+
<Setter TargetName="PegSelectionArea" Property="IsVisible" Value="False" />
39+
<Setter TargetName="PegScrollView" Property="IsVisible" Value="True" />
40+
</VisualState.Setters>
41+
</VisualState>
42+
</VisualStateGroup>
43+
</VisualStateManager.VisualStateGroups>
44+
1845
<components:GameResultDisplay Grid.Row="0" />
1946

2047
<FlexLayout
48+
x:Name="StartGameArea"
2149
Grid.Row="1"
2250
MaximumWidthRequest="1500"
2351
Wrap="Wrap"
2452
JustifyContent="Start"
25-
AlignItems="End"
26-
IsVisible="{Binding GameStatus, Mode=OneWay, Converter={StaticResource GameStatusVisibleConverter}, ConverterParameter=Start}">
53+
AlignItems="End">
2754
<VerticalStackLayout
2855
FlexLayout.Basis="500"
2956
Padding="5,0"
@@ -41,8 +68,7 @@
4168
Margin="0,5,0,0"
4269
Padding="5,0"
4370
ColumnDefinitions="*, auto"
44-
MinimumWidthRequest="250"
45-
IsVisible="{Binding GameStatus, Mode=OneWay, Converter={StaticResource GameStatusVisibleConverter}, ConverterParameter=Start}">
71+
MinimumWidthRequest="250">
4672
<Button
4773
Grid.Column="0"
4874
Text="Start the game"
@@ -58,16 +84,15 @@
5884
</FlexLayout>
5985

6086
<components:PegSelectionView
87+
x:Name="PegSelectionArea"
6188
Grid.Row="1"
62-
MaximumWidthRequest="700"
63-
IsVisible="{Binding GameStatus, Mode=OneWay, Converter={StaticResource GameStatusVisibleConverter}, ConverterParameter=Cancelable}"/>
89+
MaximumWidthRequest="700" />
6490

65-
<ScrollView Grid.Row="2" Padding="0,15,0,15" x:Name="pegScrollView" HorizontalOptions="CenterAndExpand">
91+
<ScrollView Grid.Row="2" Padding="0,15,0,15" x:Name="PegScrollView" HorizontalOptions="CenterAndExpand">
6692
<ListView
6793
x:Name="test"
6894
HorizontalOptions="FillAndExpand"
6995
SelectionMode="None"
70-
IsVisible="{Binding GameStatus, Mode=OneWay, Converter={StaticResource GameStatusVisibleConverter}, ConverterParameter=Running}"
7196
ItemsSource="{Binding GameMoves, Mode=OneWay}"
7297
ItemTemplate="{StaticResource PegsTemplate}"/>
7398
</ScrollView>

src/Codebreaker.MAUI/Views/Pages/GamePage.xaml.cs

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,44 @@ public partial class GamePage : ContentPage, IRecipient<GameMoveMessage>, IRecip
99
public GamePage(GamePageViewModel viewModel, INavigationService navigationService)
1010
{
1111
_navigationService = navigationService;
12-
BindingContext = viewModel;
13-
InitializeComponent();
12+
ViewModel = viewModel;
13+
InitializeComponent();
14+
BindingContext = viewModel;
1415
WeakReferenceMessenger.Default.RegisterAll(this);
15-
}
16+
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
17+
this.GoToState("Start");
18+
}
19+
20+
public GamePageViewModel ViewModel { get; }
1621

1722
public async void Receive(GameMoveMessage message)
1823
{
1924
if (message.GameMoveValue is not GameMoveValue.Completed)
2025
return;
2126

2227
await Task.Delay(300);
23-
await pegScrollView.ScrollToAsync(0, pegScrollView.ContentSize.Height, true);
28+
await PegScrollView.ScrollToAsync(0, PegScrollView.ContentSize.Height, true);
2429
}
2530

2631
public async void Receive(InfoMessage message)
2732
{
2833
await DisplayAlert("Info", message.Text, "Close");
2934
}
3035

36+
private void ViewModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
37+
{
38+
if (e.PropertyName != nameof(GamePageViewModel.GameStatus))
39+
return;
40+
41+
var stateName = ViewModel.GameStatus switch
42+
{
43+
GameMode.Started or GameMode.MoveSet => "Playing",
44+
GameMode.Won or GameMode.Lost => "Finished",
45+
_ => "Start",
46+
};
47+
this.GoToState(stateName);
48+
}
49+
3150
private async void Button_Clicked(object sender, EventArgs e)
3251
{
3352
await _navigationService.NavigateToAsync("TestPage");
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Microsoft.UI.Xaml;
2+
3+
internal static class VisualStateHelpers
4+
{
5+
public static bool GoToState(this Control control, string stateName, bool useAnimation = true) =>
6+
VisualStateManager.GoToState(control, stateName, useAnimation);
7+
}

0 commit comments

Comments
 (0)