Skip to content

Commit e29d469

Browse files
feat: Add macOS WindowChrome style with traffic light buttons
Co-authored-by: MartinZikmund <1075116+MartinZikmund@users.noreply.github.com>
1 parent 558dfd4 commit e29d469

File tree

2 files changed

+237
-8
lines changed

2 files changed

+237
-8
lines changed

src/Uno.UI/UI/Xaml/Window/WindowChrome.cs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,11 @@ void OnBorderAndTitleBarChanged(object? s, object? e)
133133
// one needs to apply Content Control style with key WindowChromeStyle defined in generic.xaml
134134
internal void ApplyStylingForMinMaxCloseButtons()
135135
{
136-
var style = (Style)Application.Current.Resources["WindowChromeStyle"];
136+
// Select the appropriate WindowChrome style based on the runtime OS
137+
// macOS uses the macOS-style with traffic light buttons on the left
138+
// Windows and Linux use the Windows-style with buttons on the right
139+
var styleKey = OperatingSystem.IsMacOS() ? "MacOSWindowChromeStyle" : "WindowChromeStyle";
140+
var style = (Style)Application.Current.Resources[styleKey];
137141
SetValue(StyleProperty, style);
138142
}
139143

@@ -315,20 +319,37 @@ private void UpdateCaptionRegion()
315319

316320
private RectInt32 GetDefaultCaptionRegionRect()
317321
{
318-
// Caption area should be everything to the left of the buttons (except for the container)
319322
var titleBarContainer = m_tpTitleBarMinMaxCloseContainerPart;
320323
if (titleBarContainer is not null)
321324
{
322325
var scale = _window.AppWindow.NativeAppWindow.RasterizationScale;
323326
var transform = titleBarContainer.TransformToVisual(null);
324327
var point = transform.TransformPoint(new Windows.Foundation.Point(0, 0));
325-
return new RectInt32
328+
var windowWidth = (int)(_window.Bounds.Width * scale);
329+
330+
if (OperatingSystem.IsMacOS())
326331
{
327-
X = 0,
328-
Y = 0,
329-
Width = (int)(point.X * scale),
330-
Height = (int)(titleBarContainer.ActualHeight * scale)
331-
};
332+
// On macOS, caption buttons are on the left, so the caption area is to the right of the buttons
333+
var buttonContainerRight = (int)((point.X + titleBarContainer.ActualWidth) * scale);
334+
return new RectInt32
335+
{
336+
X = buttonContainerRight,
337+
Y = 0,
338+
Width = windowWidth - buttonContainerRight,
339+
Height = (int)(titleBarContainer.ActualHeight * scale)
340+
};
341+
}
342+
else
343+
{
344+
// On Windows/Linux, caption buttons are on the right, so the caption area is to the left of the buttons
345+
return new RectInt32
346+
{
347+
X = 0,
348+
Y = 0,
349+
Width = (int)(point.X * scale),
350+
Height = (int)(titleBarContainer.ActualHeight * scale)
351+
};
352+
}
332353
}
333354

334355
return default;

src/Uno.UI/UI/Xaml/Window/WindowChromeStyles.xaml

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,19 @@
1818
<SolidColorBrush x:Key="CloseButtonStrokePointerOver" Color="White" />
1919
<SolidColorBrush x:Key="CloseButtonBackgroundPressed" Color="#f1707a" />
2020
<SolidColorBrush x:Key="CloseButtonStrokePressed" Color="Black" />
21+
<!-- macOS traffic light button colors -->
22+
<SolidColorBrush x:Key="MacOSCloseButtonBackground" Color="#FF5F57" />
23+
<SolidColorBrush x:Key="MacOSCloseButtonBackgroundPointerOver" Color="#FF6961" />
24+
<SolidColorBrush x:Key="MacOSCloseButtonBackgroundPressed" Color="#BF4840" />
25+
<SolidColorBrush x:Key="MacOSMinimizeButtonBackground" Color="#FFBD2E" />
26+
<SolidColorBrush x:Key="MacOSMinimizeButtonBackgroundPointerOver" Color="#FFC942" />
27+
<SolidColorBrush x:Key="MacOSMinimizeButtonBackgroundPressed" Color="#BF8E22" />
28+
<SolidColorBrush x:Key="MacOSMaximizeButtonBackground" Color="#28C840" />
29+
<SolidColorBrush x:Key="MacOSMaximizeButtonBackgroundPointerOver" Color="#3DD656" />
30+
<SolidColorBrush x:Key="MacOSMaximizeButtonBackgroundPressed" Color="#1E9630" />
31+
<SolidColorBrush x:Key="MacOSCaptionButtonBackgroundDisabled" Color="#DCDCDC" />
32+
<SolidColorBrush x:Key="MacOSCaptionButtonStroke" Color="Transparent" />
33+
<SolidColorBrush x:Key="MacOSCaptionButtonStrokePointerOver" Color="#4D000000" />
2134
</ResourceDictionary>
2235

2336
<ResourceDictionary x:Key="HighContrast">
@@ -37,6 +50,19 @@
3750
<SolidColorBrush x:Key="CloseButtonStrokePointerOver" Color="{ThemeResource SystemColorHighlightTextColor}" />
3851
<SolidColorBrush x:Key="CloseButtonBackgroundPressed" Color="{ThemeResource SystemColorHighlightColor}" />
3952
<SolidColorBrush x:Key="CloseButtonStrokePressed" Color="{ThemeResource SystemColorHighlightTextColor}" />
53+
<!-- macOS traffic light button colors (HighContrast uses system colors) -->
54+
<SolidColorBrush x:Key="MacOSCloseButtonBackground" Color="{ThemeResource SystemColorButtonFaceColor}" />
55+
<SolidColorBrush x:Key="MacOSCloseButtonBackgroundPointerOver" Color="{ThemeResource SystemColorHighlightColor}" />
56+
<SolidColorBrush x:Key="MacOSCloseButtonBackgroundPressed" Color="{ThemeResource SystemColorHighlightColor}" />
57+
<SolidColorBrush x:Key="MacOSMinimizeButtonBackground" Color="{ThemeResource SystemColorButtonFaceColor}" />
58+
<SolidColorBrush x:Key="MacOSMinimizeButtonBackgroundPointerOver" Color="{ThemeResource SystemColorHighlightColor}" />
59+
<SolidColorBrush x:Key="MacOSMinimizeButtonBackgroundPressed" Color="{ThemeResource SystemColorHighlightColor}" />
60+
<SolidColorBrush x:Key="MacOSMaximizeButtonBackground" Color="{ThemeResource SystemColorButtonFaceColor}" />
61+
<SolidColorBrush x:Key="MacOSMaximizeButtonBackgroundPointerOver" Color="{ThemeResource SystemColorHighlightColor}" />
62+
<SolidColorBrush x:Key="MacOSMaximizeButtonBackgroundPressed" Color="{ThemeResource SystemColorHighlightColor}" />
63+
<SolidColorBrush x:Key="MacOSCaptionButtonBackgroundDisabled" Color="{ThemeResource SystemColorGrayTextColor}" />
64+
<SolidColorBrush x:Key="MacOSCaptionButtonStroke" Color="{ThemeResource SystemColorButtonTextColor}" />
65+
<SolidColorBrush x:Key="MacOSCaptionButtonStrokePointerOver" Color="{ThemeResource SystemColorHighlightTextColor}" />
4066
</ResourceDictionary>
4167

4268
<ResourceDictionary x:Key="Light">
@@ -56,6 +82,19 @@
5682
<SolidColorBrush x:Key="CloseButtonStrokePointerOver" Color="White" />
5783
<SolidColorBrush x:Key="CloseButtonBackgroundPressed" Color="#f1707a" />
5884
<SolidColorBrush x:Key="CloseButtonStrokePressed" Color="Black" />
85+
<!-- macOS traffic light button colors -->
86+
<SolidColorBrush x:Key="MacOSCloseButtonBackground" Color="#FF5F57" />
87+
<SolidColorBrush x:Key="MacOSCloseButtonBackgroundPointerOver" Color="#FF6961" />
88+
<SolidColorBrush x:Key="MacOSCloseButtonBackgroundPressed" Color="#BF4840" />
89+
<SolidColorBrush x:Key="MacOSMinimizeButtonBackground" Color="#FFBD2E" />
90+
<SolidColorBrush x:Key="MacOSMinimizeButtonBackgroundPointerOver" Color="#FFC942" />
91+
<SolidColorBrush x:Key="MacOSMinimizeButtonBackgroundPressed" Color="#BF8E22" />
92+
<SolidColorBrush x:Key="MacOSMaximizeButtonBackground" Color="#28C840" />
93+
<SolidColorBrush x:Key="MacOSMaximizeButtonBackgroundPointerOver" Color="#3DD656" />
94+
<SolidColorBrush x:Key="MacOSMaximizeButtonBackgroundPressed" Color="#1E9630" />
95+
<SolidColorBrush x:Key="MacOSCaptionButtonBackgroundDisabled" Color="#DCDCDC" />
96+
<SolidColorBrush x:Key="MacOSCaptionButtonStroke" Color="Transparent" />
97+
<SolidColorBrush x:Key="MacOSCaptionButtonStrokePointerOver" Color="#4D000000" />
5998
</ResourceDictionary>
6099
</ResourceDictionary.ThemeDictionaries>
61100

@@ -243,4 +282,173 @@
243282
</Setter.Value>
244283
</Setter>
245284
</Style>
285+
286+
<!-- macOS Traffic Light Caption Button Style -->
287+
<Style x:Key="MacOSCaptionButton" TargetType="Button">
288+
<Setter Property="BorderThickness" Value="0" />
289+
<Setter Property="Background" Value="Transparent" />
290+
<Setter Property="IsTabStop" Value="False" />
291+
<Setter Property="Width" Value="12" />
292+
<Setter Property="Height" Value="12" />
293+
<Setter Property="MinWidth" Value="12" />
294+
<Setter Property="MinHeight" Value="12" />
295+
<Setter Property="Padding" Value="0" />
296+
<Setter Property="VerticalAlignment" Value="Center" />
297+
<Setter Property="Template">
298+
<Setter.Value>
299+
<ControlTemplate TargetType="Button">
300+
<Grid>
301+
<Ellipse
302+
x:Name="ButtonBackground"
303+
Width="12"
304+
Height="12"
305+
Fill="{TemplateBinding Background}"
306+
Stroke="{ThemeResource MacOSCaptionButtonStroke}"
307+
StrokeThickness="0.5" />
308+
<VisualStateManager.VisualStateGroups>
309+
<VisualStateGroup x:Name="CommonStates">
310+
<VisualState x:Name="Normal" />
311+
<VisualState x:Name="PointerOver">
312+
<VisualState.Setters>
313+
<Setter Target="ButtonBackground.Stroke" Value="{ThemeResource MacOSCaptionButtonStrokePointerOver}" />
314+
</VisualState.Setters>
315+
</VisualState>
316+
<VisualState x:Name="Pressed">
317+
<VisualState.Setters>
318+
<Setter Target="ButtonBackground.Stroke" Value="{ThemeResource MacOSCaptionButtonStrokePointerOver}" />
319+
</VisualState.Setters>
320+
</VisualState>
321+
</VisualStateGroup>
322+
<VisualStateGroup x:Name="MinMaxStates">
323+
<VisualState x:Name="WindowStateNormal" />
324+
<VisualState x:Name="WindowStateMaximized" />
325+
</VisualStateGroup>
326+
</VisualStateManager.VisualStateGroups>
327+
</Grid>
328+
</ControlTemplate>
329+
</Setter.Value>
330+
</Setter>
331+
</Style>
332+
333+
<!-- macOS-style WindowChrome with traffic light buttons on the left -->
334+
<Style x:Key="MacOSWindowChromeStyle" TargetType="ContentControl">
335+
<Setter Property="Template">
336+
<Setter.Value>
337+
<ControlTemplate TargetType="ContentControl">
338+
<Grid x:Name="LayoutRoot" Padding="{TemplateBinding Padding}">
339+
<ContentPresenter
340+
x:Name="ClientAreaPresenter"
341+
Content="{TemplateBinding Content}"
342+
ContentTemplate="{TemplateBinding ContentTemplate}"
343+
ContentTransitions="{TemplateBinding ContentTransitions}" />
344+
<StackPanel
345+
x:Name="TitleBarMinMaxCloseContainer"
346+
HorizontalAlignment="Left"
347+
VerticalAlignment="Top"
348+
Margin="8,6,0,0"
349+
Orientation="Horizontal"
350+
Spacing="8"
351+
Visibility="{Binding CaptionVisibility, RelativeSource={RelativeSource TemplatedParent}}">
352+
<!-- macOS order: Close (red), Minimize (yellow), Maximize/Zoom (green) -->
353+
<Button
354+
x:Name="CloseButton"
355+
AutomationProperties.AutomationId="_CloseButton"
356+
AutomationProperties.Name="Close"
357+
Background="{ThemeResource MacOSCloseButtonBackground}"
358+
Style="{StaticResource MacOSCaptionButton}">
359+
<Button.Resources>
360+
<ResourceDictionary>
361+
<ResourceDictionary.ThemeDictionaries>
362+
<ResourceDictionary x:Key="Light">
363+
<StaticResource x:Key="MacOSCaptionButtonStrokePointerOver" ResourceKey="MacOSCloseButtonBackgroundPointerOver" />
364+
</ResourceDictionary>
365+
<ResourceDictionary x:Key="Dark">
366+
<StaticResource x:Key="MacOSCaptionButtonStrokePointerOver" ResourceKey="MacOSCloseButtonBackgroundPointerOver" />
367+
</ResourceDictionary>
368+
<ResourceDictionary x:Key="Default">
369+
<StaticResource x:Key="MacOSCaptionButtonStrokePointerOver" ResourceKey="MacOSCloseButtonBackgroundPointerOver" />
370+
</ResourceDictionary>
371+
<ResourceDictionary x:Key="HighContrast">
372+
<StaticResource x:Key="MacOSCaptionButtonStrokePointerOver" ResourceKey="MacOSCloseButtonBackgroundPointerOver" />
373+
</ResourceDictionary>
374+
</ResourceDictionary.ThemeDictionaries>
375+
</ResourceDictionary>
376+
</Button.Resources>
377+
</Button>
378+
<Button
379+
x:Name="MinimizeButton"
380+
AutomationProperties.AutomationId="_MinimizeButton"
381+
AutomationProperties.Name="Minimize"
382+
Background="{ThemeResource MacOSMinimizeButtonBackground}"
383+
Style="{StaticResource MacOSCaptionButton}">
384+
<Button.Resources>
385+
<ResourceDictionary>
386+
<ResourceDictionary.ThemeDictionaries>
387+
<ResourceDictionary x:Key="Light">
388+
<StaticResource x:Key="MacOSCaptionButtonStrokePointerOver" ResourceKey="MacOSMinimizeButtonBackgroundPointerOver" />
389+
</ResourceDictionary>
390+
<ResourceDictionary x:Key="Dark">
391+
<StaticResource x:Key="MacOSCaptionButtonStrokePointerOver" ResourceKey="MacOSMinimizeButtonBackgroundPointerOver" />
392+
</ResourceDictionary>
393+
<ResourceDictionary x:Key="Default">
394+
<StaticResource x:Key="MacOSCaptionButtonStrokePointerOver" ResourceKey="MacOSMinimizeButtonBackgroundPointerOver" />
395+
</ResourceDictionary>
396+
<ResourceDictionary x:Key="HighContrast">
397+
<StaticResource x:Key="MacOSCaptionButtonStrokePointerOver" ResourceKey="MacOSMinimizeButtonBackgroundPointerOver" />
398+
</ResourceDictionary>
399+
</ResourceDictionary.ThemeDictionaries>
400+
</ResourceDictionary>
401+
</Button.Resources>
402+
</Button>
403+
<Button
404+
x:Name="MaximizeButton"
405+
AutomationProperties.AutomationId="_MaximizeButton"
406+
AutomationProperties.Name="Maximize"
407+
Background="{ThemeResource MacOSMaximizeButtonBackground}"
408+
Style="{StaticResource MacOSCaptionButton}">
409+
<Button.Resources>
410+
<ResourceDictionary>
411+
<ResourceDictionary.ThemeDictionaries>
412+
<ResourceDictionary x:Key="Light">
413+
<StaticResource x:Key="MacOSCaptionButtonStrokePointerOver" ResourceKey="MacOSMaximizeButtonBackgroundPointerOver" />
414+
</ResourceDictionary>
415+
<ResourceDictionary x:Key="Dark">
416+
<StaticResource x:Key="MacOSCaptionButtonStrokePointerOver" ResourceKey="MacOSMaximizeButtonBackgroundPointerOver" />
417+
</ResourceDictionary>
418+
<ResourceDictionary x:Key="Default">
419+
<StaticResource x:Key="MacOSCaptionButtonStrokePointerOver" ResourceKey="MacOSMaximizeButtonBackgroundPointerOver" />
420+
</ResourceDictionary>
421+
<ResourceDictionary x:Key="HighContrast">
422+
<StaticResource x:Key="MacOSCaptionButtonStrokePointerOver" ResourceKey="MacOSMaximizeButtonBackgroundPointerOver" />
423+
</ResourceDictionary>
424+
</ResourceDictionary.ThemeDictionaries>
425+
</ResourceDictionary>
426+
</Button.Resources>
427+
</Button>
428+
</StackPanel>
429+
<VisualStateManager.VisualStateGroups>
430+
<VisualStateGroup x:Name="CommonStates">
431+
<VisualState x:Name="Normal">
432+
<VisualState.Setters>
433+
<Setter Target="TitleBarMinMaxCloseContainer.Background" Value="{ThemeResource WindowCaptionBackground}" />
434+
<Setter Target="CloseButton.Background" Value="{ThemeResource MacOSCloseButtonBackground}" />
435+
<Setter Target="MinimizeButton.Background" Value="{ThemeResource MacOSMinimizeButtonBackground}" />
436+
<Setter Target="MaximizeButton.Background" Value="{ThemeResource MacOSMaximizeButtonBackground}" />
437+
</VisualState.Setters>
438+
</VisualState>
439+
<VisualState x:Name="WindowInactive">
440+
<VisualState.Setters>
441+
<Setter Target="TitleBarMinMaxCloseContainer.Background" Value="{ThemeResource WindowCaptionBackgroundDisabled}" />
442+
<Setter Target="CloseButton.Background" Value="{ThemeResource MacOSCaptionButtonBackgroundDisabled}" />
443+
<Setter Target="MinimizeButton.Background" Value="{ThemeResource MacOSCaptionButtonBackgroundDisabled}" />
444+
<Setter Target="MaximizeButton.Background" Value="{ThemeResource MacOSCaptionButtonBackgroundDisabled}" />
445+
</VisualState.Setters>
446+
</VisualState>
447+
</VisualStateGroup>
448+
</VisualStateManager.VisualStateGroups>
449+
</Grid>
450+
</ControlTemplate>
451+
</Setter.Value>
452+
</Setter>
453+
</Style>
246454
</ResourceDictionary>

0 commit comments

Comments
 (0)