Skip to content

Commit 6974747

Browse files
authored
Merge pull request #364 from switchifyapp/feat/titlebar-connection-status-363
Move connection status to title bar
2 parents a1ccf92 + e09ab17 commit 6974747

5 files changed

Lines changed: 300 additions & 49 deletions

File tree

src/SwitchifyPc.App/Chrome/SwitchifyTitleBar.xaml

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,33 @@
6969
</Setter.Value>
7070
</Setter>
7171
</Style>
72+
73+
<Style x:Key="TitleBarStatusDot" TargetType="Ellipse">
74+
<Setter Property="Width" Value="8" />
75+
<Setter Property="Height" Value="8" />
76+
<Setter Property="Fill" Value="{DynamicResource ChromeSubtleForeground}" />
77+
<Style.Triggers>
78+
<DataTrigger Binding="{Binding StatusTone, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="ready">
79+
<Setter Property="Fill" Value="{DynamicResource StatusOk}" />
80+
</DataTrigger>
81+
<DataTrigger Binding="{Binding StatusTone, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="connected">
82+
<Setter Property="Fill" Value="{DynamicResource StatusOk}" />
83+
</DataTrigger>
84+
<DataTrigger Binding="{Binding StatusTone, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="waiting">
85+
<Setter Property="Fill" Value="{DynamicResource StatusWarn}" />
86+
</DataTrigger>
87+
<DataTrigger Binding="{Binding StatusTone, RelativeSource={RelativeSource AncestorType=UserControl}}" Value="error">
88+
<Setter Property="Fill" Value="{DynamicResource StatusError}" />
89+
</DataTrigger>
90+
</Style.Triggers>
91+
</Style>
7292
</UserControl.Resources>
7393

7494
<Grid>
7595
<Grid.ColumnDefinitions>
7696
<ColumnDefinition Width="*" />
7797
<ColumnDefinition Width="Auto" />
98+
<ColumnDefinition Width="Auto" />
7899
</Grid.ColumnDefinitions>
79100

80101
<StackPanel Margin="14,0,10,0"
@@ -93,7 +114,40 @@
93114
Foreground="{DynamicResource ChromeForeground}" />
94115
</StackPanel>
95116

96-
<StackPanel Grid.Column="1"
117+
<Border x:Name="TitleBarStatusBadge"
118+
Grid.Column="1"
119+
Height="28"
120+
MaxWidth="220"
121+
Margin="8,0,8,0"
122+
Padding="10,0"
123+
VerticalAlignment="Center"
124+
Background="#22FFFFFF"
125+
BorderBrush="{DynamicResource ChromeSubtleForeground}"
126+
BorderThickness="1"
127+
CornerRadius="14"
128+
AutomationProperties.AutomationId="TitleBarStatusBadge"
129+
AutomationProperties.Name="{Binding StatusText, RelativeSource={RelativeSource AncestorType=UserControl}}"
130+
Visibility="{Binding ShowStatusBadge, RelativeSource={RelativeSource AncestorType=UserControl}, Converter={StaticResource BooleanToVisibilityConverter}}">
131+
<Grid VerticalAlignment="Center">
132+
<Grid.ColumnDefinitions>
133+
<ColumnDefinition Width="Auto" />
134+
<ColumnDefinition Width="*" />
135+
</Grid.ColumnDefinitions>
136+
<Ellipse x:Name="TitleBarStatusDot"
137+
VerticalAlignment="Center"
138+
Style="{StaticResource TitleBarStatusDot}" />
139+
<TextBlock Grid.Column="1"
140+
Margin="8,0,0,0"
141+
VerticalAlignment="Center"
142+
Text="{Binding StatusText, RelativeSource={RelativeSource AncestorType=UserControl}}"
143+
TextTrimming="CharacterEllipsis"
144+
Foreground="{DynamicResource ChromeForeground}"
145+
FontSize="12"
146+
FontWeight="SemiBold" />
147+
</Grid>
148+
</Border>
149+
150+
<StackPanel Grid.Column="2"
97151
Orientation="Horizontal">
98152
<Button x:Name="MinimizeButton"
99153
Content="-"

src/SwitchifyPc.App/Chrome/SwitchifyTitleBar.xaml.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,24 @@ public partial class SwitchifyTitleBar : WpfUserControl
2121
typeof(SwitchifyTitleBar),
2222
new PropertyMetadata(true));
2323

24+
public static readonly DependencyProperty StatusTextProperty = DependencyProperty.Register(
25+
nameof(StatusText),
26+
typeof(string),
27+
typeof(SwitchifyTitleBar),
28+
new PropertyMetadata(null));
29+
30+
public static readonly DependencyProperty StatusToneProperty = DependencyProperty.Register(
31+
nameof(StatusTone),
32+
typeof(string),
33+
typeof(SwitchifyTitleBar),
34+
new PropertyMetadata(null));
35+
36+
public static readonly DependencyProperty ShowStatusBadgeProperty = DependencyProperty.Register(
37+
nameof(ShowStatusBadge),
38+
typeof(bool),
39+
typeof(SwitchifyTitleBar),
40+
new PropertyMetadata(false));
41+
2442
public SwitchifyTitleBar()
2543
{
2644
InitializeComponent();
@@ -38,6 +56,24 @@ public bool ShowMinimizeButton
3856
set => SetValue(ShowMinimizeButtonProperty, value);
3957
}
4058

59+
public string? StatusText
60+
{
61+
get => (string?)GetValue(StatusTextProperty);
62+
set => SetValue(StatusTextProperty, value);
63+
}
64+
65+
public string? StatusTone
66+
{
67+
get => (string?)GetValue(StatusToneProperty);
68+
set => SetValue(StatusToneProperty, value);
69+
}
70+
71+
public bool ShowStatusBadge
72+
{
73+
get => (bool)GetValue(ShowStatusBadgeProperty);
74+
set => SetValue(ShowStatusBadgeProperty, value);
75+
}
76+
4177
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
4278
{
4379
if (e.ClickCount != 1 || IsInsideButton(e.OriginalSource as DependencyObject))

src/SwitchifyPc.App/MainWindow.xaml

Lines changed: 4 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -142,36 +142,6 @@
142142
</Style.Triggers>
143143
</Style>
144144

145-
<Style x:Key="StatusBadge" TargetType="Border">
146-
<Setter Property="Background" Value="{DynamicResource Surface}" />
147-
<Setter Property="BorderBrush" Value="{DynamicResource BorderColor}" />
148-
<Setter Property="BorderThickness" Value="1" />
149-
<Setter Property="CornerRadius" Value="8" />
150-
<Setter Property="Padding" Value="10,8" />
151-
<Setter Property="MinHeight" Value="40" />
152-
<Setter Property="MinWidth" Value="112" />
153-
</Style>
154-
155-
<Style x:Key="StatusDot" TargetType="Ellipse">
156-
<Setter Property="Width" Value="9" />
157-
<Setter Property="Height" Value="9" />
158-
<Setter Property="Fill" Value="{DynamicResource TextMuted}" />
159-
<Style.Triggers>
160-
<DataTrigger Binding="{Binding StatusBadgeTone}" Value="ready">
161-
<Setter Property="Fill" Value="{DynamicResource StatusOk}" />
162-
</DataTrigger>
163-
<DataTrigger Binding="{Binding StatusBadgeTone}" Value="connected">
164-
<Setter Property="Fill" Value="{DynamicResource StatusOk}" />
165-
</DataTrigger>
166-
<DataTrigger Binding="{Binding StatusBadgeTone}" Value="waiting">
167-
<Setter Property="Fill" Value="{DynamicResource StatusWarn}" />
168-
</DataTrigger>
169-
<DataTrigger Binding="{Binding StatusBadgeTone}" Value="error">
170-
<Setter Property="Fill" Value="{DynamicResource StatusError}" />
171-
</DataTrigger>
172-
</Style.Triggers>
173-
</Style>
174-
175145
<Style x:Key="PrimaryStatusDot" TargetType="Ellipse">
176146
<Setter Property="Width" Value="10" />
177147
<Setter Property="Height" Value="10" />
@@ -219,7 +189,10 @@
219189

220190
<chrome:SwitchifyTitleBar Grid.Row="0"
221191
TitleText="Switchify PC"
222-
ShowMinimizeButton="True" />
192+
ShowMinimizeButton="True"
193+
ShowStatusBadge="True"
194+
StatusText="{Binding StatusBadgeLabel}"
195+
StatusTone="{Binding StatusBadgeTone}" />
223196

224197
<Grid Grid.Row="1" Background="{DynamicResource ChromeBackground}">
225198
<Border Margin="0,96,0,0"
@@ -256,23 +229,6 @@
256229
<Button Content="Settings"
257230
Padding="16,0"
258231
Click="OpenSettings_Click" />
259-
<Border Margin="8,0,0,0"
260-
Style="{StaticResource StatusBadge}">
261-
<Grid VerticalAlignment="Center">
262-
<Grid.ColumnDefinitions>
263-
<ColumnDefinition Width="Auto" />
264-
<ColumnDefinition Width="*" />
265-
</Grid.ColumnDefinitions>
266-
<Ellipse Style="{StaticResource StatusDot}" />
267-
<TextBlock Grid.Column="1"
268-
Margin="8,0,0,0"
269-
VerticalAlignment="Center"
270-
Text="{Binding StatusBadgeLabel}"
271-
Foreground="{DynamicResource Text}"
272-
FontSize="12"
273-
FontWeight="SemiBold" />
274-
</Grid>
275-
</Border>
276232
</StackPanel>
277233
</Grid>
278234

src/SwitchifyPc.Tests/MainWindowTests.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
using System.Windows.Media;
66
using System.Windows.Shell;
77
using SwitchifyPc.App;
8+
using SwitchifyPc.App.Chrome;
89
using SwitchifyPc.App.Themes;
10+
using SwitchifyPc.Core.Bluetooth;
911
using SwitchifyPc.Core.Ui;
1012
using WpfButton = System.Windows.Controls.Button;
1113
using WpfColor = System.Windows.Media.Color;
@@ -89,6 +91,48 @@ public void MainWindowLoadsWithDarkTheme()
8991
});
9092
}
9193

94+
[Fact]
95+
public void MainWindowShowsConnectionStatusInTitleBar()
96+
{
97+
RunOnSta(() =>
98+
{
99+
WpfTestApplication.ApplyTheme(AppTheme.Light);
100+
MainWindowViewModel viewModel = new();
101+
viewModel.SetBluetoothState(
102+
DesktopUiState.Connected,
103+
BluetoothStatusModel.DefaultStatus with
104+
{
105+
Status = "connected",
106+
ConnectedClientCount = 1,
107+
System = BluetoothStatusModel.DefaultSystemStatus with
108+
{
109+
AdapterPresent = true,
110+
RadioState = "on",
111+
IsLowEnergySupported = true,
112+
IsPeripheralRoleSupported = true
113+
}
114+
});
115+
116+
MainWindow window = new(viewModel);
117+
try
118+
{
119+
window.Show();
120+
window.UpdateLayout();
121+
122+
FrameworkElement badge = Assert.IsType<Border>(ElementByAutomationId(window, "TitleBarStatusBadge"));
123+
Assert.Equal(Visibility.Visible, badge.Visibility);
124+
Assert.Equal("Connected", AutomationProperties.GetName(badge));
125+
126+
SwitchifyTitleBar titleBar = Assert.IsType<SwitchifyTitleBar>(Ancestor<SwitchifyTitleBar>(badge));
127+
Assert.Contains("Connected", TextBlocks(titleBar));
128+
}
129+
finally
130+
{
131+
window.Close();
132+
}
133+
});
134+
}
135+
92136
private static void RunOnSta(Action action)
93137
{
94138
Exception? exception = null;
@@ -139,6 +183,38 @@ node is WpfButton button &&
139183
return result;
140184
}
141185

186+
private static FrameworkElement? ElementByAutomationId(DependencyObject root, string automationId)
187+
{
188+
FrameworkElement? result = null;
189+
Collect(root, node =>
190+
{
191+
if (result is null &&
192+
node is FrameworkElement element &&
193+
AutomationProperties.GetAutomationId(element) == automationId)
194+
{
195+
result = element;
196+
}
197+
});
198+
return result;
199+
}
200+
201+
private static T? Ancestor<T>(DependencyObject node)
202+
where T : DependencyObject
203+
{
204+
DependencyObject? current = node;
205+
while (current is not null)
206+
{
207+
if (current is T match)
208+
{
209+
return match;
210+
}
211+
212+
current = VisualTreeHelper.GetParent(current);
213+
}
214+
215+
return null;
216+
}
217+
142218
private static void Collect(DependencyObject node, Action<DependencyObject> visit)
143219
{
144220
visit(node);

0 commit comments

Comments
 (0)