Browse Source

Merge branch 'master' into fixes/Some_Null_Annotation_And_Warning

pull/6754/head
Giuseppe Lippolis 5 years ago
parent
commit
9014a03c9e
  1. 50
      azure-pipelines.yml
  2. 48
      build.sh
  3. 2
      global.json
  4. 7
      native/Avalonia.Native/src/OSX/window.mm
  5. 1
      packages/Avalonia/AvaloniaBuildTasks.targets
  6. 8
      readme.md
  7. 8
      samples/ControlCatalog/MainWindow.xaml
  8. 2
      samples/ControlCatalog/Pages/ButtonPage.xaml
  9. 6
      samples/ControlCatalog/Pages/CheckBoxPage.xaml
  10. 20
      samples/ControlCatalog/Pages/DialogsPage.xaml
  11. 6
      samples/ControlCatalog/Pages/RadioButtonPage.xaml
  12. 6
      samples/ControlCatalog/Pages/ToggleSwitchPage.xaml
  13. 3
      src/Avalonia.Controls/Button.cs
  14. 15
      src/Avalonia.Controls/ContextMenu.cs
  15. 3
      src/Avalonia.Controls/NativeMenuItem.cs
  16. 2
      src/Avalonia.Controls/Primitives/AccessText.cs
  17. 49
      src/Avalonia.Controls/TrayIcon.cs
  18. 6
      src/Avalonia.FreeDesktop/DBusHelper.cs
  19. 178
      src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs
  20. 3
      src/Avalonia.Themes.Default/AutoCompleteBox.xaml
  21. 1
      src/Avalonia.Themes.Default/Button.xaml
  22. 1
      src/Avalonia.Themes.Default/CheckBox.xaml
  23. 3
      src/Avalonia.Themes.Default/ComboBox.xaml
  24. 1
      src/Avalonia.Themes.Default/ContextMenu.xaml
  25. 2
      src/Avalonia.Themes.Default/FlyoutPresenter.xaml
  26. 2
      src/Avalonia.Themes.Default/MenuFlyoutPresenter.xaml
  27. 4
      src/Avalonia.Themes.Default/MenuItem.xaml
  28. 22
      src/Avalonia.Themes.Default/OverlayPopupHost.xaml
  29. 2
      src/Avalonia.Themes.Default/PopupRoot.xaml
  30. 1
      src/Avalonia.Themes.Default/RadioButton.xaml
  31. 1
      src/Avalonia.Themes.Default/ToggleButton.xaml
  32. 1
      src/Avalonia.Themes.Default/ToggleSwitch.xaml
  33. 1
      src/Avalonia.Themes.Fluent/Controls/Button.xaml
  34. 1
      src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml
  35. 17
      src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml
  36. 1
      src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml
  37. 1
      src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml
  38. 1
      src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml
  39. 1
      src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml
  40. 19
      src/Avalonia.X11/X11Platform.cs
  41. 47
      src/Avalonia.X11/XEmbedTrayIconImpl.cs
  42. 14
      tests/Avalonia.Controls.UnitTests/ButtonTests.cs
  43. 75
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

50
azure-pipelines.yml

@ -3,19 +3,23 @@ jobs:
pool: pool:
vmImage: 'ubuntu-20.04' vmImage: 'ubuntu-20.04'
steps: steps:
- task: CmdLine@2 - task: UseDotNet@2
displayName: 'Install Nuke' displayName: 'Use .NET Core SDK 3.1.414'
inputs: inputs:
script: | version: 3.1.414
dotnet tool install --global Nuke.GlobalTool --version 0.24.0
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 5.0.402'
inputs:
version: 5.0.402
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Run Nuke' displayName: 'Run Build'
inputs: inputs:
script: | script: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info dotnet --info
printenv printenv
nuke --target CiAzureLinux --configuration=Release ./build.sh --target CiAzureLinux --configuration=Release
- task: PublishTestResults@2 - task: PublishTestResults@2
inputs: inputs:
@ -23,6 +27,7 @@ jobs:
testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx' testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx'
condition: not(canceled()) condition: not(canceled())
- job: macOS - job: macOS
variables: variables:
SolutionDir: '$(Build.SourcesDirectory)' SolutionDir: '$(Build.SourcesDirectory)'
@ -30,10 +35,15 @@ jobs:
vmImage: 'macOS-10.15' vmImage: 'macOS-10.15'
steps: steps:
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.401' displayName: 'Use .NET Core SDK 3.1.414'
inputs: inputs:
version: 3.1.401 version: 3.1.414
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 5.0.402'
inputs:
version: 5.0.402
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Mono 5.18' displayName: 'Install Mono 5.18'
inputs: inputs:
@ -45,6 +55,7 @@ jobs:
displayName: 'Generate avalonia-native' displayName: 'Generate avalonia-native'
inputs: inputs:
script: | script: |
export PATH="`pwd`/sdk:$PATH"
cd src/tools/MicroComGenerator; dotnet run -i ../../Avalonia.Native/avn.idl --cpp ../../../native/Avalonia.Native/inc/avalonia-native.h cd src/tools/MicroComGenerator; dotnet run -i ../../Avalonia.Native/avn.idl --cpp ../../../native/Avalonia.Native/inc/avalonia-native.h
- task: Xcode@5 - task: Xcode@5
@ -58,13 +69,7 @@ jobs:
args: '-derivedDataPath ./' args: '-derivedDataPath ./'
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Nuke' displayName: 'Run Build'
inputs:
script: |
dotnet tool install --global Nuke.GlobalTool --version 0.24.0
- task: CmdLine@2
displayName: 'Run Nuke'
inputs: inputs:
script: | script: |
export COREHOST_TRACE=0 export COREHOST_TRACE=0
@ -72,10 +77,8 @@ jobs:
export DOTNET_CLI_TELEMETRY_OPTOUT=1 export DOTNET_CLI_TELEMETRY_OPTOUT=1
which dotnet which dotnet
dotnet --info dotnet --info
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info
printenv printenv
nuke --target CiAzureOSX --configuration Release --skip-previewer ./build.sh --target CiAzureOSX --configuration Release --skip-previewer
- task: PublishTestResults@2 - task: PublishTestResults@2
inputs: inputs:
@ -102,9 +105,14 @@ jobs:
SolutionDir: '$(Build.SourcesDirectory)' SolutionDir: '$(Build.SourcesDirectory)'
steps: steps:
- task: UseDotNet@2 - task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.401' displayName: 'Use .NET Core SDK 3.1.414'
inputs:
version: 3.1.414
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 5.0.402'
inputs: inputs:
version: 3.1.401 version: 5.0.402
- task: CmdLine@2 - task: CmdLine@2
displayName: 'Install Nuke' displayName: 'Install Nuke'

48
build.sh

@ -20,55 +20,11 @@ SCRIPT_DIR=$(cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd)
########################################################################### ###########################################################################
BUILD_PROJECT_FILE="$SCRIPT_DIR/nukebuild/_build.csproj" BUILD_PROJECT_FILE="$SCRIPT_DIR/nukebuild/_build.csproj"
TEMP_DIRECTORY="$SCRIPT_DIR//.tmp"
DOTNET_GLOBAL_FILE="$SCRIPT_DIR//global.json"
DOTNET_INSTALL_URL="https://raw.githubusercontent.com/dotnet/cli/master/scripts/obtain/dotnet-install.sh"
DOTNET_CHANNEL="Current"
export DOTNET_CLI_TELEMETRY_OPTOUT=1 export DOTNET_CLI_TELEMETRY_OPTOUT=1
export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export NUGET_XMLDOC_MODE="skip" export NUGET_XMLDOC_MODE="skip"
########################################################################### dotnet --info
# EXECUTION
###########################################################################
function FirstJsonValue {
perl -nle 'print $1 if m{"'$1'": "([^"\-]+)",?}' <<< ${@:2}
}
# If global.json exists, load expected version
if [ -f "$DOTNET_GLOBAL_FILE" ]; then
DOTNET_VERSION=$(FirstJsonValue "version" $(cat "$DOTNET_GLOBAL_FILE"))
if [ "$DOTNET_VERSION" == "" ]; then
unset DOTNET_VERSION
fi
fi
# If dotnet is installed locally, and expected version is not set or installation matches the expected version
if [[ -x "$(command -v dotnet)" && (-z ${DOTNET_VERSION+x} || $(dotnet --version) == "$DOTNET_VERSION") || "$SKIP_DOTNET_DOWNLOAD" == "1" ]]; then
export DOTNET_EXE="$(command -v dotnet)"
else
DOTNET_DIRECTORY="$TEMP_DIRECTORY/dotnet-unix"
export DOTNET_EXE="$DOTNET_DIRECTORY/dotnet"
# Download install script
DOTNET_INSTALL_FILE="$TEMP_DIRECTORY/dotnet-install.sh"
mkdir -p "$TEMP_DIRECTORY"
curl -Lsfo "$DOTNET_INSTALL_FILE" "$DOTNET_INSTALL_URL"
chmod +x "$DOTNET_INSTALL_FILE"
# Install by channel or version
if [ -z ${DOTNET_VERSION+x} ]; then
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --channel "$DOTNET_CHANNEL" --no-path
else
"$DOTNET_INSTALL_FILE" --install-dir "$DOTNET_DIRECTORY" --version "$DOTNET_VERSION" --no-path
fi
fi
export PATH=$DOTNET_DIRECTORY:$PATH
echo "Microsoft (R) .NET Core SDK version $("$DOTNET_EXE" --version)"
"$DOTNET_EXE" run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]} dotnet run --project "$BUILD_PROJECT_FILE" -- ${BUILD_ARGUMENTS[@]}

2
global.json

@ -1,6 +1,6 @@
{ {
"sdk": { "sdk": {
"version": "3.1.401" "version": "5.0.402"
}, },
"msbuild-sdks": { "msbuild-sdks": {
"Microsoft.Build.Traversal": "1.0.43", "Microsoft.Build.Traversal": "1.0.43",

7
native/Avalonia.Native/src/OSX/window.mm

@ -2389,7 +2389,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)sendEvent:(NSEvent *)event - (void)sendEvent:(NSEvent *)event
{ {
if(_parent != nullptr) [super sendEvent:event];
/// This is to detect non-client clicks. This can only be done on Windows... not popups, hence the dynamic_cast.
if(_parent != nullptr && dynamic_cast<WindowImpl*>(_parent.getRaw()) != nullptr)
{ {
switch(event.type) switch(event.type)
{ {
@ -2419,8 +2422,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
break; break;
} }
} }
[super sendEvent:event];
} }
@end @end

1
packages/Avalonia/AvaloniaBuildTasks.targets

@ -54,6 +54,7 @@
<Output TaskParameter="HashResult" PropertyName="AvaloniaResourcesDependencyHash" /> <Output TaskParameter="HashResult" PropertyName="AvaloniaResourcesDependencyHash" />
</Hash> </Hash>
<MakeDir Directories="$(IntermediateOutputPath)/Avalonia" />
<WriteLinesToFile Overwrite="true" File="$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache" Lines="$(AvaloniaResourcesDependencyHash)" WriteOnlyWhenDifferent="True" /> <WriteLinesToFile Overwrite="true" File="$(IntermediateOutputPath)/Avalonia/Resources.Inputs.cache" Lines="$(AvaloniaResourcesDependencyHash)" WriteOnlyWhenDifferent="True" />
</Target> </Target>

8
readme.md

@ -60,6 +60,9 @@ See the [build instructions here](Documentation/build.md).
## Contributing ## Contributing
This project exists thanks to all the people who contribute.
<a href="https://github.com/AvaloniaUI/Avalonia/graphs/contributors"><img src="https://opencollective.com/Avalonia/contributors.svg?width=890&button=false" /></a>
Please read the [contribution guidelines](CONTRIBUTING.md) before submitting a pull request. Please read the [contribution guidelines](CONTRIBUTING.md) before submitting a pull request.
## Code of Conduct ## Code of Conduct
@ -71,11 +74,6 @@ For more information see the [.NET Foundation Code of Conduct](https://dotnetfou
Avalonia is licenced under the [MIT licence](licence.md). Avalonia is licenced under the [MIT licence](licence.md).
## Contributors
This project exists thanks to all the people who contribute. [[Contribute](https://avaloniaui.net/contributing)].
<a href="https://github.com/AvaloniaUI/Avalonia/graphs/contributors"><img src="https://opencollective.com/Avalonia/contributors.svg?width=890&button=false" /></a>
### Backers ### Backers
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)] Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)]

8
samples/ControlCatalog/MainWindow.xaml

@ -63,11 +63,11 @@
<Panel Margin="{Binding #MainWindow.OffScreenMargin}"> <Panel Margin="{Binding #MainWindow.OffScreenMargin}">
<DockPanel LastChildFill="True" Margin="{Binding #MainWindow.WindowDecorationMargin}"> <DockPanel LastChildFill="True" Margin="{Binding #MainWindow.WindowDecorationMargin}">
<Menu Name="MainMenu" DockPanel.Dock="Top"> <Menu Name="MainMenu" DockPanel.Dock="Top">
<MenuItem Header="File"> <MenuItem Header="_File">
<MenuItem Header="Exit" Command="{Binding ExitCommand}" /> <MenuItem Header="E_xit" Command="{Binding ExitCommand}" />
</MenuItem> </MenuItem>
<MenuItem Header="Help"> <MenuItem Header="_Help">
<MenuItem Header="About" Command="{Binding AboutCommand}" /> <MenuItem Header="_About" Command="{Binding AboutCommand}" />
</MenuItem> </MenuItem>
</Menu> </Menu>
<local:MainView /> <local:MainView />

2
samples/ControlCatalog/Pages/ButtonPage.xaml

@ -10,7 +10,7 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
Spacing="16"> Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8" Width="200"> <StackPanel Orientation="Vertical" Spacing="8" Width="200">
<Button>Standard XAML Button</Button> <Button>Standard _XAML Button</Button>
<Button Foreground="White">Foreground</Button> <Button Foreground="White">Foreground</Button>
<Button Background="{DynamicResource SystemAccentColor}">Background</Button> <Button Background="{DynamicResource SystemAccentColor}">Background</Button>
<Button IsEnabled="False">Disabled</Button> <Button IsEnabled="False">Disabled</Button>

6
samples/ControlCatalog/Pages/CheckBoxPage.xaml

@ -11,9 +11,9 @@
Spacing="16"> Spacing="16">
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"
Spacing="16"> Spacing="16">
<CheckBox>Unchecked</CheckBox> <CheckBox>_Unchecked</CheckBox>
<CheckBox IsChecked="True">Checked</CheckBox> <CheckBox IsChecked="True">_Checked</CheckBox>
<CheckBox IsChecked="{x:Null}">Indeterminate</CheckBox> <CheckBox IsChecked="{x:Null}">_Indeterminate</CheckBox>
<CheckBox IsChecked="True" IsEnabled="False">Disabled</CheckBox> <CheckBox IsChecked="True" IsEnabled="False">Disabled</CheckBox>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"

20
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -3,15 +3,15 @@
x:Class="ControlCatalog.Pages.DialogsPage"> x:Class="ControlCatalog.Pages.DialogsPage">
<StackPanel Orientation="Vertical" Spacing="4" Margin="4"> <StackPanel Orientation="Vertical" Spacing="4" Margin="4">
<CheckBox Name="UseFilters">Use filters</CheckBox> <CheckBox Name="UseFilters">Use filters</CheckBox>
<Button Name="OpenFile">Open File</Button> <Button Name="OpenFile">_Open File</Button>
<Button Name="SaveFile">Save File</Button> <Button Name="SaveFile">_Save File</Button>
<Button Name="SelectFolder">Select Folder</Button> <Button Name="SelectFolder">Select Fo_lder</Button>
<Button Name="OpenBoth">Select Both</Button> <Button Name="OpenBoth">Select _Both</Button>
<Button Name="DecoratedWindow">Decorated window</Button> <Button Name="DecoratedWindow">Decorated _window</Button>
<Button Name="DecoratedWindowDialog">Decorated window (dialog)</Button> <Button Name="DecoratedWindowDialog">Decorated w_indow (dialog)</Button>
<Button Name="Dialog">Dialog</Button> <Button Name="Dialog">_Dialog</Button>
<Button Name="DialogNoTaskbar">Dialog (No taskbar icon)</Button> <Button Name="DialogNoTaskbar">Dialog (_No taskbar icon)</Button>
<Button Name="OwnedWindow">Owned window</Button> <Button Name="OwnedWindow">Own_ed window</Button>
<Button Name="OwnedWindowNoTaskbar">Owned window (No taskbar icon)</Button> <Button Name="OwnedWindowNoTaskbar">Owned window (No tas_kbar icon)</Button>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

6
samples/ControlCatalog/Pages/RadioButtonPage.xaml

@ -11,9 +11,9 @@
Spacing="16"> Spacing="16">
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"
Spacing="16"> Spacing="16">
<RadioButton IsChecked="True">Option 1</RadioButton> <RadioButton IsChecked="True">_Option 1</RadioButton>
<RadioButton>Option 2</RadioButton> <RadioButton>O_ption 2</RadioButton>
<RadioButton IsChecked="{x:Null}">Option 3</RadioButton> <RadioButton IsChecked="{x:Null}">Op_tion 3</RadioButton>
<RadioButton IsEnabled="False">Disabled</RadioButton> <RadioButton IsEnabled="False">Disabled</RadioButton>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" <StackPanel Orientation="Vertical"

6
samples/ControlCatalog/Pages/ToggleSwitchPage.xaml

@ -14,7 +14,7 @@
<Border Classes="Thin"> <Border Classes="Thin">
<StackPanel> <StackPanel>
<ToggleSwitch Content="headered" IsChecked="true" Margin="10"/> <ToggleSwitch Content="h_eadered" IsChecked="true" Margin="10"/>
<TextBox Classes="CodeBox" <TextBox Classes="CodeBox"
Text="&lt;ToggleSwitch&gt;headered&lt;/ToggleSwitch&gt;"/> Text="&lt;ToggleSwitch&gt;headered&lt;/ToggleSwitch&gt;"/>
</StackPanel> </StackPanel>
@ -24,7 +24,7 @@
<Border Classes="Thin"> <Border Classes="Thin">
<StackPanel> <StackPanel>
<ToggleSwitch Content="Custom" <ToggleSwitch Content="_Custom"
OnContent="On" OnContent="On"
OffContent="Off" OffContent="Off"
Margin="10"/> Margin="10"/>
@ -40,7 +40,7 @@ ContentOff=&quot;Off&quot; /&gt;"
<Border Classes="Thin"> <Border Classes="Thin">
<StackPanel> <StackPanel>
<ToggleSwitch Content="Just Click!" Margin="10"> <ToggleSwitch Content="_Just Click!" Margin="10">
<ToggleSwitch.OnContent> <ToggleSwitch.OnContent>
<Image Source="/Assets/hirsch-899118_640.jpg" Height="32"/> <Image Source="/Assets/hirsch-899118_640.jpg" Height="32"/>
</ToggleSwitch.OnContent> </ToggleSwitch.OnContent>

3
src/Avalonia.Controls/Button.cs

@ -99,6 +99,7 @@ namespace Avalonia.Controls
CommandParameterProperty.Changed.Subscribe(CommandParameterChanged); CommandParameterProperty.Changed.Subscribe(CommandParameterChanged);
IsDefaultProperty.Changed.Subscribe(IsDefaultChanged); IsDefaultProperty.Changed.Subscribe(IsDefaultChanged);
IsCancelProperty.Changed.Subscribe(IsCancelChanged); IsCancelProperty.Changed.Subscribe(IsCancelChanged);
AccessKeyHandler.AccessKeyPressedEvent.AddClassHandler<Button>((lbl, args) => lbl.OnAccessKey(args));
} }
public Button() public Button()
@ -256,6 +257,8 @@ namespace Avalonia.Controls
} }
} }
protected virtual void OnAccessKey(RoutedEventArgs e) => OnClick();
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e) protected override void OnKeyDown(KeyEventArgs e)
{ {

15
src/Avalonia.Controls/ContextMenu.cs

@ -329,16 +329,8 @@ namespace Avalonia.Controls
{ {
_popup = new Popup _popup = new Popup
{ {
HorizontalOffset = HorizontalOffset,
VerticalOffset = VerticalOffset,
PlacementAnchor = PlacementAnchor,
PlacementConstraintAdjustment = PlacementConstraintAdjustment,
PlacementGravity = PlacementGravity,
PlacementMode = PlacementMode,
PlacementRect = PlacementRect,
IsLightDismissEnabled = true, IsLightDismissEnabled = true,
OverlayDismissEventPassThrough = true, OverlayDismissEventPassThrough = true,
WindowManagerAddShadowHint = WindowManagerAddShadowHint,
}; };
_popup.Opened += PopupOpened; _popup.Opened += PopupOpened;
@ -358,6 +350,13 @@ namespace Avalonia.Controls
: PlacementMode; : PlacementMode;
_popup.PlacementTarget = placementTarget; _popup.PlacementTarget = placementTarget;
_popup.HorizontalOffset = HorizontalOffset;
_popup.VerticalOffset = VerticalOffset;
_popup.PlacementAnchor = PlacementAnchor;
_popup.PlacementConstraintAdjustment = PlacementConstraintAdjustment;
_popup.PlacementGravity = PlacementGravity;
_popup.PlacementRect = PlacementRect;
_popup.WindowManagerAddShadowHint = WindowManagerAddShadowHint;
_popup.Child = this; _popup.Child = this;
IsOpen = true; IsOpen = true;
_popup.IsOpen = true; _popup.IsOpen = true;

3
src/Avalonia.Controls/NativeMenuItem.cs

@ -16,6 +16,7 @@ namespace Avalonia.Controls
private bool _isChecked = false; private bool _isChecked = false;
private NativeMenuItemToggleType _toggleType; private NativeMenuItemToggleType _toggleType;
private IBitmap _icon; private IBitmap _icon;
private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber;
private NativeMenu _menu; private NativeMenu _menu;
@ -47,8 +48,6 @@ namespace Avalonia.Controls
} }
} }
private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber;
public NativeMenuItem() public NativeMenuItem()
{ {

2
src/Avalonia.Controls/Primitives/AccessText.cs

@ -68,7 +68,7 @@ namespace Avalonia.Controls.Primitives
if (underscore != -1 && ShowAccessKey) if (underscore != -1 && ShowAccessKey)
{ {
var rect = TextLayout.HitTestTextPosition(underscore); var rect = TextLayout.HitTestTextPosition(underscore);
var offset = new Vector(0, -0.5); var offset = new Vector(0, -1.5);
context.DrawLine( context.DrawLine(
new Pen(Foreground, 1), new Pen(Foreground, 1),
rect.BottomLeft + offset, rect.BottomLeft + offset,

49
src/Avalonia.Controls/TrayIcon.cs

@ -1,10 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Windows.Input;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
#nullable enable #nullable enable
@ -13,10 +15,13 @@ namespace Avalonia.Controls
public sealed class TrayIcons : AvaloniaList<TrayIcon> public sealed class TrayIcons : AvaloniaList<TrayIcon>
{ {
} }
public class TrayIcon : AvaloniaObject, INativeMenuExporterProvider, IDisposable public class TrayIcon : AvaloniaObject, INativeMenuExporterProvider, IDisposable
{ {
private readonly ITrayIconImpl? _impl; private readonly ITrayIconImpl? _impl;
private ICommand? _command;
private TrayIcon(ITrayIconImpl? impl) private TrayIcon(ITrayIconImpl? impl)
{ {
@ -26,7 +31,15 @@ namespace Avalonia.Controls
_impl.SetIsVisible(IsVisible); _impl.SetIsVisible(IsVisible);
_impl.OnClicked = () => Clicked?.Invoke(this, EventArgs.Empty); _impl.OnClicked = () =>
{
Clicked?.Invoke(this, EventArgs.Empty);
if (Command?.CanExecute(CommandParameter) == true)
{
Command.Execute(CommandParameter);
}
};
} }
} }
@ -64,6 +77,21 @@ namespace Avalonia.Controls
/// on OSX this event is not raised. /// on OSX this event is not raised.
/// </summary> /// </summary>
public event EventHandler? Clicked; public event EventHandler? Clicked;
/// <summary>
/// Defines the <see cref="Command"/> property.
/// </summary>
public static readonly DirectProperty<TrayIcon, ICommand?> CommandProperty =
Button.CommandProperty.AddOwner<TrayIcon>(
trayIcon => trayIcon.Command,
(trayIcon, command) => trayIcon.Command = command,
enableDataValidation: true);
/// <summary>
/// Defines the <see cref="CommandParameter"/> property.
/// </summary>
public static readonly StyledProperty<object?> CommandParameterProperty =
Button.CommandParameterProperty.AddOwner<MenuItem>();
/// <summary> /// <summary>
/// Defines the <see cref="TrayIcons"/> attached property. /// Defines the <see cref="TrayIcons"/> attached property.
@ -98,6 +126,25 @@ namespace Avalonia.Controls
public static void SetIcons(AvaloniaObject o, TrayIcons trayIcons) => o.SetValue(IconsProperty, trayIcons); public static void SetIcons(AvaloniaObject o, TrayIcons trayIcons) => o.SetValue(IconsProperty, trayIcons);
public static TrayIcons GetIcons(AvaloniaObject o) => o.GetValue(IconsProperty); public static TrayIcons GetIcons(AvaloniaObject o) => o.GetValue(IconsProperty);
/// <summary>
/// Gets or sets the <see cref="Command"/> property of a TrayIcon.
/// </summary>
public ICommand? Command
{
get => _command;
set => SetAndRaise(CommandProperty, ref _command, value);
}
/// <summary>
/// Gets or sets the parameter to pass to the <see cref="Command"/> property of a
/// <see cref="TrayIcon"/>.
/// </summary>
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
/// <summary> /// <summary>
/// Gets or sets the Menu of the TrayIcon. /// Gets or sets the Menu of the TrayIcon.

6
src/Avalonia.FreeDesktop/DBusHelper.cs

@ -12,7 +12,7 @@ namespace Avalonia.FreeDesktop
/// This class uses synchronous execution at DBus connection establishment stage /// This class uses synchronous execution at DBus connection establishment stage
/// then switches to using AvaloniaSynchronizationContext /// then switches to using AvaloniaSynchronizationContext
/// </summary> /// </summary>
class DBusSyncContext : SynchronizationContext private class DBusSyncContext : SynchronizationContext
{ {
private SynchronizationContext _ctx; private SynchronizationContext _ctx;
private object _lock = new object(); private object _lock = new object();
@ -51,10 +51,10 @@ namespace Avalonia.FreeDesktop
public static Connection TryInitialize(string dbusAddress = null) public static Connection TryInitialize(string dbusAddress = null)
{ {
return Connection ?? TryGetConnection(dbusAddress); return Connection ?? TryCreateNewConnection(dbusAddress);
} }
public static Connection TryGetConnection(string dbusAddress = null) public static Connection TryCreateNewConnection(string dbusAddress = null)
{ {
var oldContext = SynchronizationContext.Current; var oldContext = SynchronizationContext.Current;
try try

178
src/Avalonia.X11/X11TrayIconImpl.cs → src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs

@ -6,55 +6,65 @@ using System.Reactive.Disposables;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
using Avalonia.FreeDesktop;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Platform; using Avalonia.Platform;
using Tmds.DBus; using Tmds.DBus;
[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)] [assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)]
namespace Avalonia.X11 [assembly:
InternalsVisibleTo(
"Avalonia.X11, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
namespace Avalonia.FreeDesktop
{ {
internal class X11TrayIconImpl : ITrayIconImpl internal class DBusTrayIconImpl : ITrayIconImpl
{ {
private static int s_trayIconInstanceId; private static int s_trayIconInstanceId;
private readonly ObjectPath _dbusMenuPath; private readonly ObjectPath _dbusMenuPath;
private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj;
private readonly Connection? _connection; private readonly Connection? _connection;
private DbusPixmap _icon; private IDisposable? _serviceWatchDisposable;
private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj;
private IStatusNotifierWatcher? _statusNotifierWatcher; private IStatusNotifierWatcher? _statusNotifierWatcher;
private DbusPixmap _icon;
private string? _sysTrayServiceName; private string? _sysTrayServiceName;
private string? _tooltipText; private string? _tooltipText;
private bool _isActive;
private bool _isDisposed; private bool _isDisposed;
private readonly bool _ctorFinished; private bool _serviceConnected;
private bool _isVisible = true;
public bool IsActive { get; private set; }
public INativeMenuExporter? MenuExporter { get; } public INativeMenuExporter? MenuExporter { get; }
public Action? OnClicked { get; set; } public Action? OnClicked { get; set; }
public Func<IWindowIconImpl?, uint[]>? IconConverterDelegate { get; set; }
public X11TrayIconImpl() public DBusTrayIconImpl()
{ {
_connection = DBusHelper.TryGetConnection(); _connection = DBusHelper.TryCreateNewConnection();
if (_connection is null) if (_connection is null)
{ {
Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform) Logger.TryGet(LogEventLevel.Error, "DBUS")
?.Log(this, "Unable to get a dbus connection for system tray icons."); ?.Log(this, "Unable to get a dbus connection for system tray icons.");
return; return;
} }
IsActive = true;
_dbusMenuPath = DBusMenuExporter.GenerateDBusMenuObjPath; _dbusMenuPath = DBusMenuExporter.GenerateDBusMenuObjPath;
MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusMenuPath, _connection); MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusMenuPath, _connection);
CreateTrayIcon();
_ctorFinished = true; WatchAsync();
} }
public async void CreateTrayIcon() private void InitializeSNWService()
{ {
if (_connection is null) if (_connection is null || _isDisposed) return;
return;
try try
{ {
@ -64,12 +74,58 @@ namespace Avalonia.X11
} }
catch catch
{ {
Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform) Logger.TryGet(LogEventLevel.Error, "DBUS")
?.Log(this,
"org.kde.StatusNotifierWatcher service is not available on this system. Tray Icons will not work without it.");
return;
}
_serviceConnected = true;
}
private async void WatchAsync()
{
try
{
_serviceWatchDisposable =
await _connection?.ResolveServiceOwnerAsync("org.kde.StatusNotifierWatcher", OnNameChange)!;
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "DBUS")
?.Log(this, ?.Log(this,
"DBUS: org.kde.StatusNotifierWatcher service is not available on this system. System Tray Icons will not work without it."); $"Unable to hook watcher method on org.kde.StatusNotifierWatcher: {e}");
} }
}
if (_statusNotifierWatcher is null) private void OnNameChange(ServiceOwnerChangedEventArgs obj)
{
if (_isDisposed)
return;
if (!_serviceConnected & obj.NewOwner != null)
{
_serviceConnected = true;
InitializeSNWService();
DestroyTrayIcon();
if (_isVisible)
{
CreateTrayIcon();
}
}
else if (_serviceConnected & obj.NewOwner is null)
{
DestroyTrayIcon();
_serviceConnected = false;
}
}
private void CreateTrayIcon()
{
if (_connection is null || !_serviceConnected || _isDisposed)
return; return;
var pid = Process.GetCurrentProcess().Id; var pid = Process.GetCurrentProcess().Id;
@ -78,45 +134,61 @@ namespace Avalonia.X11
_sysTrayServiceName = $"org.kde.StatusNotifierItem-{pid}-{tid}"; _sysTrayServiceName = $"org.kde.StatusNotifierItem-{pid}-{tid}";
_statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_dbusMenuPath); _statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_dbusMenuPath);
await _connection.RegisterObjectAsync(_statusNotifierItemDbusObj); try
{
await _connection.RegisterServiceAsync(_sysTrayServiceName); _connection.RegisterObjectAsync(_statusNotifierItemDbusObj);
_connection.RegisterServiceAsync(_sysTrayServiceName);
_statusNotifierWatcher?.RegisterStatusNotifierItemAsync(_sysTrayServiceName);
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "DBUS")
?.Log(this, $"Error creating a DBus tray icon: {e}.");
await _statusNotifierWatcher.RegisterStatusNotifierItemAsync(_sysTrayServiceName); _serviceConnected = false;
}
_statusNotifierItemDbusObj.SetTitleAndTooltip(_tooltipText); _statusNotifierItemDbusObj.SetTitleAndTooltip(_tooltipText);
_statusNotifierItemDbusObj.SetIcon(_icon); _statusNotifierItemDbusObj.SetIcon(_icon);
_statusNotifierItemDbusObj.ActivationDelegate += OnClicked; _statusNotifierItemDbusObj.ActivationDelegate += OnClicked;
_isActive = true;
} }
public async void DestroyTrayIcon() private void DestroyTrayIcon()
{ {
if (_connection is null) if (_connection is null || !_serviceConnected || _isDisposed || _statusNotifierItemDbusObj is null)
return; return;
_connection.UnregisterObject(_statusNotifierItemDbusObj); _connection.UnregisterObject(_statusNotifierItemDbusObj);
await _connection.UnregisterServiceAsync(_sysTrayServiceName); _connection.UnregisterServiceAsync(_sysTrayServiceName);
_isActive = false;
} }
public void Dispose() public void Dispose()
{ {
IsActive = false;
_isDisposed = true; _isDisposed = true;
DestroyTrayIcon(); DestroyTrayIcon();
_connection?.Dispose(); _connection?.Dispose();
_serviceWatchDisposable?.Dispose();
} }
public void SetIcon(IWindowIconImpl? icon) public void SetIcon(IWindowIconImpl? icon)
{ {
if (_isDisposed) if (_isDisposed || IconConverterDelegate is null)
return; return;
if (!(icon is X11IconData x11icon))
if (icon is null)
{
_statusNotifierItemDbusObj?.SetIcon(DbusPixmap.EmptyPixmap);
return; return;
}
var x11iconData = IconConverterDelegate(icon);
if (x11iconData.Length == 0) return;
var w = (int)x11icon.Data[0]; var w = (int)x11iconData[0];
var h = (int)x11icon.Data[1]; var h = (int)x11iconData[1];
var pixLength = w * h; var pixLength = w * h;
var pixByteArrayCounter = 0; var pixByteArrayCounter = 0;
@ -124,7 +196,7 @@ namespace Avalonia.X11
for (var i = 0; i < pixLength; i++) for (var i = 0; i < pixLength; i++)
{ {
var rawPixel = x11icon.Data[i + 2].ToUInt32(); var rawPixel = x11iconData[i + 2];
pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF000000) >> 24); pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF000000) >> 24);
pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF0000) >> 16); pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF0000) >> 16);
pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF00) >> 8); pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF00) >> 8);
@ -137,18 +209,21 @@ namespace Avalonia.X11
public void SetIsVisible(bool visible) public void SetIsVisible(bool visible)
{ {
if (_isDisposed || !_ctorFinished) if (_isDisposed)
return; return;
if (visible & !_isActive) switch (visible)
{
DestroyTrayIcon();
CreateTrayIcon();
}
else if (!visible & _isActive)
{ {
DestroyTrayIcon(); case true when !_isVisible:
DestroyTrayIcon();
CreateTrayIcon();
break;
case false when _isVisible:
DestroyTrayIcon();
break;
} }
_isVisible = visible;
} }
public void SetToolTipText(string? text) public void SetToolTipText(string? text)
@ -248,7 +323,20 @@ namespace Avalonia.X11
return Task.FromResult(Disposable.Create(() => NewStatusAsync -= handler)); return Task.FromResult(Disposable.Create(() => NewStatusAsync -= handler));
} }
public Task<object> GetAsync(string prop) => Task.FromResult(new object()); public Task<object?> GetAsync(string prop)
{
return Task.FromResult<object?>(prop switch
{
nameof(_backingProperties.Category) => _backingProperties.Category,
nameof(_backingProperties.Id) => _backingProperties.Id,
nameof(_backingProperties.Menu) => _backingProperties.Menu,
nameof(_backingProperties.IconPixmap) => _backingProperties.IconPixmap,
nameof(_backingProperties.Status) => _backingProperties.Status,
nameof(_backingProperties.Title) => _backingProperties.Title,
nameof(_backingProperties.ToolTip) => _backingProperties.ToolTip,
_ => null
});
}
public Task<StatusNotifierItemProperties> GetAllAsync() => Task.FromResult(_backingProperties); public Task<StatusNotifierItemProperties> GetAllAsync() => Task.FromResult(_backingProperties);
@ -298,17 +386,17 @@ namespace Avalonia.X11
Task<IDisposable> WatchNewOverlayIconAsync(Action handler, Action<Exception> onError); Task<IDisposable> WatchNewOverlayIconAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewToolTipAsync(Action handler, Action<Exception> onError); Task<IDisposable> WatchNewToolTipAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewStatusAsync(Action<string> handler, Action<Exception> onError); Task<IDisposable> WatchNewStatusAsync(Action<string> handler, Action<Exception> onError);
Task<object> GetAsync(string prop); Task<object?> GetAsync(string prop);
Task<StatusNotifierItemProperties> GetAllAsync(); Task<StatusNotifierItemProperties> GetAllAsync();
Task SetAsync(string prop, object val); Task SetAsync(string prop, object val);
Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler); Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler);
} }
[Dictionary]
// This class is used by Tmds.Dbus to ferry properties // This class is used by Tmds.Dbus to ferry properties
// from the SNI spec. // from the SNI spec.
// Don't change this to actual C# properties since // Don't change this to actual C# properties since
// Tmds.Dbus will get confused. // Tmds.Dbus will get confused.
[Dictionary]
internal class StatusNotifierItemProperties internal class StatusNotifierItemProperties
{ {
public string? Category; public string? Category;
@ -363,5 +451,7 @@ namespace Avalonia.X11
Height = height; Height = height;
Data = data; Data = data;
} }
public static DbusPixmap EmptyPixmap = new DbusPixmap(1, 1, new byte[] { 255, 0, 0, 0 });
} }
} }

3
src/Avalonia.Themes.Default/AutoCompleteBox.xaml

@ -21,7 +21,8 @@
MaxHeight="{TemplateBinding MaxDropDownHeight}" MaxHeight="{TemplateBinding MaxDropDownHeight}"
PlacementTarget="{TemplateBinding}" PlacementTarget="{TemplateBinding}"
IsLightDismissEnabled="True"> IsLightDismissEnabled="True">
<Border BorderBrush="{DynamicResource ThemeBorderMidBrush}" <Border Background="{DynamicResource ThemeBackgroundBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1"> BorderThickness="1">
<ListBox Name="PART_SelectingItemsControl" <ListBox Name="PART_SelectingItemsControl"
BorderThickness="0" BorderThickness="0"

1
src/Avalonia.Themes.Default/Button.xaml

@ -17,6 +17,7 @@
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
RecognizesAccessKey="True"
TextBlock.Foreground="{TemplateBinding Foreground}" TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/> VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>

1
src/Avalonia.Themes.Default/CheckBox.xaml

@ -41,6 +41,7 @@
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
IsVisible="{TemplateBinding Content, Converter={x:Static ObjectConverters.IsNotNull}}" IsVisible="{TemplateBinding Content, Converter={x:Static ObjectConverters.IsNotNull}}"

3
src/Avalonia.Themes.Default/ComboBox.xaml

@ -69,7 +69,8 @@
MaxHeight="{TemplateBinding MaxDropDownHeight}" MaxHeight="{TemplateBinding MaxDropDownHeight}"
PlacementTarget="{TemplateBinding}" PlacementTarget="{TemplateBinding}"
IsLightDismissEnabled="True"> IsLightDismissEnabled="True">
<Border BorderBrush="{DynamicResource ThemeBorderMidBrush}" <Border Background="{DynamicResource ThemeBackgroundBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1"> BorderThickness="1">
<ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"> VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">

1
src/Avalonia.Themes.Default/ContextMenu.xaml

@ -1,4 +1,5 @@
<Style xmlns="https://github.com/avaloniaui" Selector="ContextMenu"> <Style xmlns="https://github.com/avaloniaui" Selector="ContextMenu">
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="1"/> <Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="4,2"/> <Setter Property="Padding" Value="4,2"/>

2
src/Avalonia.Themes.Default/FlyoutPresenter.xaml

@ -2,7 +2,7 @@
<Style Selector="FlyoutPresenter"> <Style Selector="FlyoutPresenter">
<Setter Property="HorizontalContentAlignment" Value="Stretch" /> <Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" /> <Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
<Setter Property="BorderThickness" Value="1" /> <Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="4" /> <Setter Property="Padding" Value="4" />

2
src/Avalonia.Themes.Default/MenuFlyoutPresenter.xaml

@ -1,6 +1,6 @@
<Styles xmlns="https://github.com/avaloniaui"> <Styles xmlns="https://github.com/avaloniaui">
<Style Selector="MenuFlyoutPresenter"> <Style Selector="MenuFlyoutPresenter">
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" /> <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
<Setter Property="BorderThickness" Value="1" /> <Setter Property="BorderThickness" Value="1" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" /> <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto" />

4
src/Avalonia.Themes.Default/MenuItem.xaml

@ -62,7 +62,7 @@
PlacementMode="Right" PlacementMode="Right"
IsLightDismissEnabled="False" IsLightDismissEnabled="False"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}"> IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
<Border Background="{TemplateBinding Background}" <Border Background="{DynamicResource ThemeBackgroundBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="{TemplateBinding BorderThickness}"> BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Classes="menuscroller"> <ScrollViewer Classes="menuscroller">
@ -113,7 +113,7 @@
IsLightDismissEnabled="True" IsLightDismissEnabled="True"
OverlayInputPassThroughElement="{Binding $parent[Menu]}" OverlayInputPassThroughElement="{Binding $parent[Menu]}"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}"> IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
<Border Background="{TemplateBinding Background}" <Border Background="{DynamicResource ThemeBackgroundBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="{TemplateBinding BorderThickness}"> BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Classes="menuscroller"> <ScrollViewer Classes="menuscroller">

22
src/Avalonia.Themes.Default/OverlayPopupHost.xaml

@ -1,23 +1,21 @@
<Style xmlns="https://github.com/avaloniaui" <Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Selector="OverlayPopupHost"> Selector="OverlayPopupHost">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/> <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/> <Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}" />
<Setter Property="FontFamily" Value="{x:Static FontFamily.Default}" /> <Setter Property="FontFamily" Value="{x:Static FontFamily.Default}" />
<Setter Property="FontWeight" Value="400" /> <Setter Property="FontWeight" Value="400" />
<Setter Property="FontStyle" Value="Normal" /> <Setter Property="FontStyle" Value="Normal" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Panel> <!-- Do not forget to update Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent test -->
<Border Name="PART_TransparencyFallback" IsHitTestVisible="False" /> <VisualLayerManager IsPopup="True">
<VisualLayerManager IsPopup="True"> <ContentPresenter Name="PART_ContentPresenter"
<ContentPresenter Name="PART_ContentPresenter" Background="{TemplateBinding Background}"
Background="{TemplateBinding Background}" ContentTemplate="{TemplateBinding ContentTemplate}"
ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}"
Content="{TemplateBinding Content}" Padding="{TemplateBinding Padding}"/>
Padding="{TemplateBinding Padding}"/> </VisualLayerManager>
</VisualLayerManager>
</Panel>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>

2
src/Avalonia.Themes.Default/PopupRoot.xaml

@ -1,6 +1,8 @@
<Style xmlns="https://github.com/avaloniaui" <Style xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Selector="PopupRoot"> Selector="PopupRoot">
<Setter Property="Background" Value="{x:Null}"/>
<Setter Property="TransparencyLevelHint" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/> <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/> <Setter Property="FontSize" Value="{DynamicResource FontSizeNormal}"/>
<Setter Property="FontFamily" Value="{x:Static FontFamily.Default}" /> <Setter Property="FontFamily" Value="{x:Static FontFamily.Default}" />

1
src/Avalonia.Themes.Default/RadioButton.xaml

@ -31,6 +31,7 @@
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Margin="4,0,0,0" Margin="4,0,0,0"
RecognizesAccessKey="True"
VerticalAlignment="Center" VerticalAlignment="Center"
Grid.Column="1"/> Grid.Column="1"/>
</Grid> </Grid>

1
src/Avalonia.Themes.Default/ToggleButton.xaml

@ -17,6 +17,7 @@
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
RecognizesAccessKey="True"
TextBlock.Foreground="{TemplateBinding Foreground}" TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/> VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"/>

1
src/Avalonia.Themes.Default/ToggleSwitch.xaml

@ -87,6 +87,7 @@
Grid.Row="0" Grid.Row="0"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
RecognizesAccessKey="True"
VerticalAlignment="Top"/> VerticalAlignment="Top"/>
<Grid Grid.Row="1" <Grid Grid.Row="1"

1
src/Avalonia.Themes.Fluent/Controls/Button.xaml

@ -34,6 +34,7 @@
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
RecognizesAccessKey="True"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" /> VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
</ControlTemplate> </ControlTemplate>

1
src/Avalonia.Themes.Fluent/Controls/CheckBox.xaml

@ -44,6 +44,7 @@
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Grid.Column="1" /> Grid.Column="1" />

17
src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml

@ -6,16 +6,13 @@
<Setter Property="FontStyle" Value="Normal" /> <Setter Property="FontStyle" Value="Normal" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Panel> <VisualLayerManager IsPopup="True">
<Border Name="PART_TransparencyFallback" IsHitTestVisible="False" /> <ContentPresenter Name="PART_ContentPresenter"
<VisualLayerManager IsPopup="True"> Background="{TemplateBinding Background}"
<ContentPresenter Name="PART_ContentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}"
Background="{TemplateBinding Background}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" Padding="{TemplateBinding Padding}"/>
Content="{TemplateBinding Content}" </VisualLayerManager>
Padding="{TemplateBinding Padding}"/>
</VisualLayerManager>
</Panel>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>

1
src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml

@ -1,6 +1,7 @@
<Styles xmlns="https://github.com/avaloniaui" <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="PopupRoot"> <Style Selector="PopupRoot">
<Setter Property="Background" Value="{x:Null}"/>
<Setter Property="TransparencyLevelHint" Value="Transparent" /> <Setter Property="TransparencyLevelHint" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/> <Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}"/>
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}"/> <Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}"/>

1
src/Avalonia.Themes.Fluent/Controls/RadioButton.xaml

@ -51,6 +51,7 @@
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
TextBlock.Foreground="{TemplateBinding Foreground}" TextBlock.Foreground="{TemplateBinding Foreground}"
Margin="{TemplateBinding Padding}" Margin="{TemplateBinding Padding}"
RecognizesAccessKey="True"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Grid.Column="1" /> Grid.Column="1" />

1
src/Avalonia.Themes.Fluent/Controls/ToggleButton.xaml

@ -34,6 +34,7 @@
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
RecognizesAccessKey="True"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" /> VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" />
</ControlTemplate> </ControlTemplate>

1
src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml

@ -53,6 +53,7 @@
Grid.Row="0" Grid.Row="0"
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
RecognizesAccessKey="True"
VerticalAlignment="Top"/> VerticalAlignment="Top"/>
<Grid Grid.Row="1" <Grid Grid.Row="1"

19
src/Avalonia.X11/X11Platform.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection; using System.Reflection;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Avalonia.Controls; using Avalonia.Controls;
@ -104,11 +105,25 @@ namespace Avalonia.X11
public IntPtr DeferredDisplay { get; set; } public IntPtr DeferredDisplay { get; set; }
public IntPtr Display { get; set; } public IntPtr Display { get; set; }
public ITrayIconImpl CreateTrayIcon () private static uint[] X11IconConverter(IWindowIconImpl icon)
{ {
return new X11TrayIconImpl(); if (!(icon is X11IconData x11icon))
return Array.Empty<uint>();
return x11icon.Data.Select(x => x.ToUInt32()).ToArray();
} }
public ITrayIconImpl CreateTrayIcon()
{
var dbusTrayIcon = new DBusTrayIconImpl();
if (!dbusTrayIcon.IsActive) return new XEmbedTrayIconImpl();
dbusTrayIcon.IconConverterDelegate = X11IconConverter;
return dbusTrayIcon;
}
public IWindowImpl CreateWindow() public IWindowImpl CreateWindow()
{ {
return new X11Window(this, null); return new X11Window(this, null);

47
src/Avalonia.X11/XEmbedTrayIconImpl.cs

@ -0,0 +1,47 @@
using System;
using Avalonia.Controls.Platform;
using Avalonia.Logging;
using Avalonia.Platform;
namespace Avalonia.X11
{
internal class XEmbedTrayIconImpl : ITrayIconImpl
{
private bool _isCalled;
private void NotImplemented()
{
if(_isCalled) return;
Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform)
?.Log(this,
"TODO: XEmbed System Tray Icons is not implemented yet. Tray icons won't be available on this system.");
_isCalled = true;
}
public void Dispose()
{
NotImplemented();
}
public void SetIcon(IWindowIconImpl icon)
{
NotImplemented();
}
public void SetToolTipText(string text)
{
NotImplemented();
}
public void SetIsVisible(bool visible)
{
NotImplemented();
}
public INativeMenuExporter MenuExporter { get; }
public Action OnClicked { get; set; }
}
}

14
tests/Avalonia.Controls.UnitTests/ButtonTests.cs

@ -2,6 +2,7 @@
using System.Windows.Input; using System.Windows.Input;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
@ -271,6 +272,19 @@ namespace Avalonia.Controls.UnitTests
[Fact] [Fact]
public void Button_Invokes_CanExecute_When_CommandParameter_Changed() public void Button_Invokes_CanExecute_When_CommandParameter_Changed()
{
var target = new Button();
var raised = 0;
target.Click += (s, e) => ++raised;
target.RaiseEvent(new RoutedEventArgs(AccessKeyHandler.AccessKeyPressedEvent));
Assert.Equal(1, raised);
}
[Fact]
public void Raises_Click_When_AccessKey_Raised()
{ {
var command = new TestCommand(p => p is bool value && value); var command = new TestCommand(p => p is bool value && value);
var target = new Button { Command = command }; var target = new Button { Command = command };

75
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -294,6 +294,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
[Fact] [Fact]
public void Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent() public void Templated_Control_With_Popup_In_Template_Should_Set_TemplatedParent()
{ {
// Test uses OverlayPopupHost default template
using (CreateServices()) using (CreateServices())
{ {
PopupContentControl target; PopupContentControl target;
@ -316,33 +317,63 @@ namespace Avalonia.Controls.UnitTests.Primitives
var children = popupRoot.GetVisualDescendants().ToList(); var children = popupRoot.GetVisualDescendants().ToList();
var types = children.Select(x => x.GetType().Name).ToList(); var types = children.Select(x => x.GetType().Name).ToList();
Assert.Equal( if (UsePopupHost)
new[] {
{ Assert.Equal(
"Panel", new[]
"Border", {
"VisualLayerManager", "VisualLayerManager",
"ContentPresenter", "ContentPresenter",
"ContentPresenter", "ContentPresenter",
"Border", "Border",
}, },
types); types);
}
else
{
Assert.Equal(
new[]
{
"Panel",
"Border",
"VisualLayerManager",
"ContentPresenter",
"ContentPresenter",
"Border",
},
types);
}
var templatedParents = children var templatedParents = children
.OfType<IControl>() .OfType<IControl>()
.Select(x => x.TemplatedParent).ToList(); .Select(x => x.TemplatedParent).ToList();
Assert.Equal( if (UsePopupHost)
new object[] {
{ Assert.Equal(
popupRoot, new object[]
popupRoot, {
popupRoot, popupRoot,
popupRoot, popupRoot,
target, target,
null, null,
}, },
templatedParents); templatedParents);
}
else
{
Assert.Equal(
new object[]
{
popupRoot,
popupRoot,
popupRoot,
popupRoot,
target,
null,
},
templatedParents);
}
} }
} }

Loading…
Cancel
Save