Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 55 additions & 1 deletion src/SwitchifyPc.App/Chrome/SwitchifyTitleBar.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,33 @@
</Setter.Value>
</Setter>
</Style>

<Style x:Key="TitleBarStatusDot" TargetType="Ellipse">
<Setter Property="Width" Value="8" />
<Setter Property="Height" Value="8" />
<Setter Property="Fill" Value="{DynamicResource ChromeSubtleForeground}" />
<Style.Triggers>
<DataTrigger Binding="{Binding StatusTone, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="ready">
<Setter Property="Fill" Value="{DynamicResource StatusOk}" />
</DataTrigger>
<DataTrigger Binding="{Binding StatusTone, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="connected">
<Setter Property="Fill" Value="{DynamicResource StatusOk}" />
</DataTrigger>
<DataTrigger Binding="{Binding StatusTone, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="waiting">
<Setter Property="Fill" Value="{DynamicResource StatusWarn}" />
</DataTrigger>
<DataTrigger Binding="{Binding StatusTone, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="error">
<Setter Property="Fill" Value="{DynamicResource StatusError}" />
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>

<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>

<StackPanel Margin="14,0,10,0"
Expand All @@ -93,7 +114,40 @@
Foreground="{DynamicResource ChromeForeground}" />
</StackPanel>

<StackPanel Grid.Column="1"
<Border x:Name="TitleBarStatusBadge"
Grid.Column="1"
Height="28"
MaxWidth="220"
Margin="8,0,8,0"
Padding="10,0"
VerticalAlignment="Center"
Background="#22FFFFFF"
BorderBrush="{DynamicResource ChromeSubtleForeground}"
BorderThickness="1"
CornerRadius="14"
AutomationProperties.AutomationId="TitleBarStatusBadge"
AutomationProperties.Name="{Binding StatusText, RelativeSource={RelativeSource AncestorType=UserControl}}"
Visibility="{Binding ShowStatusBadge, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource BooleanToVisibilityConverter}}">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse x:Name="TitleBarStatusDot"
VerticalAlignment="Center"
Style="{StaticResource TitleBarStatusDot}" />
<TextBlock Grid.Column="1"
Margin="8,0,0,0"
VerticalAlignment="Center"
Text="{Binding StatusText, RelativeSource={RelativeSource AncestorType=UserControl}}"
TextTrimming="CharacterEllipsis"
Foreground="{DynamicResource ChromeForeground}"
FontSize="12"
FontWeight="SemiBold" />
</Grid>
</Border>

<StackPanel Grid.Column="2"
Orientation="Horizontal">
<Button x:Name="MinimizeButton"
Content="-"
Expand Down
36 changes: 36 additions & 0 deletions src/SwitchifyPc.App/Chrome/SwitchifyTitleBar.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ public partial class SwitchifyTitleBar : WpfUserControl
typeof(SwitchifyTitleBar),
new PropertyMetadata(true));

public static readonly DependencyProperty StatusTextProperty = DependencyProperty.Register(
nameof(StatusText),
typeof(string),
typeof(SwitchifyTitleBar),
new PropertyMetadata(null));

public static readonly DependencyProperty StatusToneProperty = DependencyProperty.Register(
nameof(StatusTone),
typeof(string),
typeof(SwitchifyTitleBar),
new PropertyMetadata(null));

public static readonly DependencyProperty ShowStatusBadgeProperty = DependencyProperty.Register(
nameof(ShowStatusBadge),
typeof(bool),
typeof(SwitchifyTitleBar),
new PropertyMetadata(false));

public SwitchifyTitleBar()
{
InitializeComponent();
Expand All @@ -38,6 +56,24 @@ public bool ShowMinimizeButton
set => SetValue(ShowMinimizeButtonProperty, value);
}

public string? StatusText
{
get => (string?)GetValue(StatusTextProperty);
set => SetValue(StatusTextProperty, value);
}

public string? StatusTone
{
get => (string?)GetValue(StatusToneProperty);
set => SetValue(StatusToneProperty, value);
}

public bool ShowStatusBadge
{
get => (bool)GetValue(ShowStatusBadgeProperty);
set => SetValue(ShowStatusBadgeProperty, value);
}

private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount != 1 || IsInsideButton(e.OriginalSource as DependencyObject))
Expand Down
52 changes: 4 additions & 48 deletions src/SwitchifyPc.App/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -142,36 +142,6 @@
</Style.Triggers>
</Style>

<Style x:Key="StatusBadge" TargetType="Border">
<Setter Property="Background" Value="{DynamicResource Surface}" />
<Setter Property="BorderBrush" Value="{DynamicResource BorderColor}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Padding" Value="10,8" />
<Setter Property="MinHeight" Value="40" />
<Setter Property="MinWidth" Value="112" />
</Style>

<Style x:Key="StatusDot" TargetType="Ellipse">
<Setter Property="Width" Value="9" />
<Setter Property="Height" Value="9" />
<Setter Property="Fill" Value="{DynamicResource TextMuted}" />
<Style.Triggers>
<DataTrigger Binding="{Binding StatusBadgeTone}" Value="ready">
<Setter Property="Fill" Value="{DynamicResource StatusOk}" />
</DataTrigger>
<DataTrigger Binding="{Binding StatusBadgeTone}" Value="connected">
<Setter Property="Fill" Value="{DynamicResource StatusOk}" />
</DataTrigger>
<DataTrigger Binding="{Binding StatusBadgeTone}" Value="waiting">
<Setter Property="Fill" Value="{DynamicResource StatusWarn}" />
</DataTrigger>
<DataTrigger Binding="{Binding StatusBadgeTone}" Value="error">
<Setter Property="Fill" Value="{DynamicResource StatusError}" />
</DataTrigger>
</Style.Triggers>
</Style>

<Style x:Key="PrimaryStatusDot" TargetType="Ellipse">
<Setter Property="Width" Value="10" />
<Setter Property="Height" Value="10" />
Expand Down Expand Up @@ -219,7 +189,10 @@

<chrome:SwitchifyTitleBar Grid.Row="0"
TitleText="Switchify PC"
ShowMinimizeButton="True" />
ShowMinimizeButton="True"
ShowStatusBadge="True"
StatusText="{Binding StatusBadgeLabel}"
StatusTone="{Binding StatusBadgeTone}" />

<Grid Grid.Row="1" Background="{DynamicResource ChromeBackground}">
<Border Margin="0,96,0,0"
Expand Down Expand Up @@ -256,23 +229,6 @@
<Button Content="Settings"
Padding="16,0"
Click="OpenSettings_Click" />
<Border Margin="8,0,0,0"
Style="{StaticResource StatusBadge}">
<Grid VerticalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Ellipse Style="{StaticResource StatusDot}" />
<TextBlock Grid.Column="1"
Margin="8,0,0,0"
VerticalAlignment="Center"
Text="{Binding StatusBadgeLabel}"
Foreground="{DynamicResource Text}"
FontSize="12"
FontWeight="SemiBold" />
</Grid>
</Border>
</StackPanel>
</Grid>

Expand Down
76 changes: 76 additions & 0 deletions src/SwitchifyPc.Tests/MainWindowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
using System.Windows.Media;
using System.Windows.Shell;
using SwitchifyPc.App;
using SwitchifyPc.App.Chrome;
using SwitchifyPc.App.Themes;
using SwitchifyPc.Core.Bluetooth;
using SwitchifyPc.Core.Ui;
using WpfButton = System.Windows.Controls.Button;
using WpfColor = System.Windows.Media.Color;
Expand Down Expand Up @@ -89,6 +91,48 @@ public void MainWindowLoadsWithDarkTheme()
});
}

[Fact]
public void MainWindowShowsConnectionStatusInTitleBar()
{
RunOnSta(() =>
{
WpfTestApplication.ApplyTheme(AppTheme.Light);
MainWindowViewModel viewModel = new();
viewModel.SetBluetoothState(
DesktopUiState.Connected,
BluetoothStatusModel.DefaultStatus with
{
Status = "connected",
ConnectedClientCount = 1,
System = BluetoothStatusModel.DefaultSystemStatus with
{
AdapterPresent = true,
RadioState = "on",
IsLowEnergySupported = true,
IsPeripheralRoleSupported = true
}
});

MainWindow window = new(viewModel);
try
{
window.Show();
window.UpdateLayout();

FrameworkElement badge = Assert.IsType<Border>(ElementByAutomationId(window, "TitleBarStatusBadge"));
Assert.Equal(Visibility.Visible, badge.Visibility);
Assert.Equal("Connected", AutomationProperties.GetName(badge));

SwitchifyTitleBar titleBar = Assert.IsType<SwitchifyTitleBar>(Ancestor<SwitchifyTitleBar>(badge));
Assert.Contains("Connected", TextBlocks(titleBar));
}
finally
{
window.Close();
}
});
}

private static void RunOnSta(Action action)
{
Exception? exception = null;
Expand Down Expand Up @@ -139,6 +183,38 @@ node is WpfButton button &&
return result;
}

private static FrameworkElement? ElementByAutomationId(DependencyObject root, string automationId)
{
FrameworkElement? result = null;
Collect(root, node =>
{
if (result is null &&
node is FrameworkElement element &&
AutomationProperties.GetAutomationId(element) == automationId)
{
result = element;
}
});
return result;
}

private static T? Ancestor<T>(DependencyObject node)
where T : DependencyObject
{
DependencyObject? current = node;
while (current is not null)
{
if (current is T match)
{
return match;
}

current = VisualTreeHelper.GetParent(current);
}

return null;
}

private static void Collect(DependencyObject node, Action<DependencyObject> visit)
{
visit(node);
Expand Down
Loading