Browse Source

Merge branch 'master' into devtools_dc_navigate

pull/6790/head
Lubomir Tetak 4 years ago
committed by GitHub
parent
commit
79fd769a71
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  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. 15
      src/Avalonia.Controls/ContextMenu.cs
  8. 3
      src/Avalonia.Controls/NativeMenuItem.cs
  9. 49
      src/Avalonia.Controls/TrayIcon.cs
  10. 5
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs
  11. 2
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs
  12. 6
      src/Avalonia.FreeDesktop/DBusHelper.cs
  13. 178
      src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs
  14. 3
      src/Avalonia.Themes.Default/AutoCompleteBox.xaml
  15. 3
      src/Avalonia.Themes.Default/ComboBox.xaml
  16. 1
      src/Avalonia.Themes.Default/ContextMenu.xaml
  17. 2
      src/Avalonia.Themes.Default/FlyoutPresenter.xaml
  18. 2
      src/Avalonia.Themes.Default/MenuFlyoutPresenter.xaml
  19. 4
      src/Avalonia.Themes.Default/MenuItem.xaml
  20. 22
      src/Avalonia.Themes.Default/OverlayPopupHost.xaml
  21. 2
      src/Avalonia.Themes.Default/PopupRoot.xaml
  22. 17
      src/Avalonia.Themes.Fluent/Controls/OverlayPopupHost.xaml
  23. 1
      src/Avalonia.Themes.Fluent/Controls/PopupRoot.xaml
  24. 19
      src/Avalonia.X11/X11Platform.cs
  25. 47
      src/Avalonia.X11/XEmbedTrayIconImpl.cs
  26. 75
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

50
azure-pipelines.yml

@ -3,19 +3,23 @@ jobs:
pool:
vmImage: 'ubuntu-20.04'
steps:
- task: CmdLine@2
displayName: 'Install Nuke'
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.414'
inputs:
script: |
dotnet tool install --global Nuke.GlobalTool --version 0.24.0
version: 3.1.414
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 5.0.402'
inputs:
version: 5.0.402
- task: CmdLine@2
displayName: 'Run Nuke'
displayName: 'Run Build'
inputs:
script: |
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info
printenv
nuke --target CiAzureLinux --configuration=Release
./build.sh --target CiAzureLinux --configuration=Release
- task: PublishTestResults@2
inputs:
@ -23,6 +27,7 @@ jobs:
testResultsFiles: '$(Build.SourcesDirectory)/artifacts/test-results/*.trx'
condition: not(canceled())
- job: macOS
variables:
SolutionDir: '$(Build.SourcesDirectory)'
@ -30,10 +35,15 @@ jobs:
vmImage: 'macOS-10.15'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.401'
displayName: 'Use .NET Core SDK 3.1.414'
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
displayName: 'Install Mono 5.18'
inputs:
@ -45,6 +55,7 @@ jobs:
displayName: 'Generate avalonia-native'
inputs:
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
- task: Xcode@5
@ -58,13 +69,7 @@ jobs:
args: '-derivedDataPath ./'
- task: CmdLine@2
displayName: 'Install Nuke'
inputs:
script: |
dotnet tool install --global Nuke.GlobalTool --version 0.24.0
- task: CmdLine@2
displayName: 'Run Nuke'
displayName: 'Run Build'
inputs:
script: |
export COREHOST_TRACE=0
@ -72,10 +77,8 @@ jobs:
export DOTNET_CLI_TELEMETRY_OPTOUT=1
which dotnet
dotnet --info
export PATH="$PATH:$HOME/.dotnet/tools"
dotnet --info
printenv
nuke --target CiAzureOSX --configuration Release --skip-previewer
./build.sh --target CiAzureOSX --configuration Release --skip-previewer
- task: PublishTestResults@2
inputs:
@ -102,9 +105,14 @@ jobs:
SolutionDir: '$(Build.SourcesDirectory)'
steps:
- 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:
version: 3.1.401
version: 5.0.402
- task: CmdLine@2
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"
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_SKIP_FIRST_TIME_EXPERIENCE=1
export NUGET_XMLDOC_MODE="skip"
###########################################################################
# 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 --info
"$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": {
"version": "3.1.401"
"version": "5.0.402"
},
"msbuild-sdks": {
"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
{
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)
{
@ -2419,8 +2422,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
break;
}
}
[super sendEvent:event];
}
@end

1
packages/Avalonia/AvaloniaBuildTasks.targets

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

8
readme.md

@ -60,6 +60,9 @@ See the [build instructions here](Documentation/build.md).
## 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.
## 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).
## 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
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/Avalonia#backer)]

15
src/Avalonia.Controls/ContextMenu.cs

@ -329,16 +329,8 @@ namespace Avalonia.Controls
{
_popup = new Popup
{
HorizontalOffset = HorizontalOffset,
VerticalOffset = VerticalOffset,
PlacementAnchor = PlacementAnchor,
PlacementConstraintAdjustment = PlacementConstraintAdjustment,
PlacementGravity = PlacementGravity,
PlacementMode = PlacementMode,
PlacementRect = PlacementRect,
IsLightDismissEnabled = true,
OverlayDismissEventPassThrough = true,
WindowManagerAddShadowHint = WindowManagerAddShadowHint,
};
_popup.Opened += PopupOpened;
@ -358,6 +350,13 @@ namespace Avalonia.Controls
: PlacementMode;
_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;
IsOpen = true;
_popup.IsOpen = true;

3
src/Avalonia.Controls/NativeMenuItem.cs

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

49
src/Avalonia.Controls/TrayIcon.cs

@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using Avalonia.Collections;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using Avalonia.Utilities;
#nullable enable
@ -13,10 +15,13 @@ namespace Avalonia.Controls
public sealed class TrayIcons : AvaloniaList<TrayIcon>
{
}
public class TrayIcon : AvaloniaObject, INativeMenuExporterProvider, IDisposable
{
private readonly ITrayIconImpl? _impl;
private ICommand? _command;
private TrayIcon(ITrayIconImpl? impl)
{
@ -26,7 +31,15 @@ namespace Avalonia.Controls
_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.
/// </summary>
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>
/// 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 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>
/// Gets or sets the Menu of the TrayIcon.

5
src/Avalonia.Diagnostics/Diagnostics/ViewModels/EventTreeNode.cs

@ -117,6 +117,11 @@ namespace Avalonia.Diagnostics.ViewModels
_currentEvent.HandledBy = link;
}
}
if (!Dispatcher.UIThread.CheckAccess())
Dispatcher.UIThread.Post(handler);
else
handler();
}
private static bool BelongsToDevTool(IVisual v)

2
src/Avalonia.Diagnostics/Diagnostics/ViewModels/FiredEvent.cs

@ -64,7 +64,7 @@ namespace Avalonia.Diagnostics.ViewModels
if (EventChain.Count > 0)
{
var prevLink = EventChain[EventChain.Count - 1];
if (prevLink.Route != link.Route)
{
link.BeginsNewRoute = true;

6
src/Avalonia.FreeDesktop/DBusHelper.cs

@ -12,7 +12,7 @@ namespace Avalonia.FreeDesktop
/// This class uses synchronous execution at DBus connection establishment stage
/// then switches to using AvaloniaSynchronizationContext
/// </summary>
class DBusSyncContext : SynchronizationContext
private class DBusSyncContext : SynchronizationContext
{
private SynchronizationContext _ctx;
private object _lock = new object();
@ -51,10 +51,10 @@ namespace Avalonia.FreeDesktop
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;
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.Threading.Tasks;
using Avalonia.Controls.Platform;
using Avalonia.FreeDesktop;
using Avalonia.Logging;
using Avalonia.Platform;
using Tmds.DBus;
[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 readonly ObjectPath _dbusMenuPath;
private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj;
private readonly Connection? _connection;
private DbusPixmap _icon;
private IDisposable? _serviceWatchDisposable;
private StatusNotifierItemDbusObj? _statusNotifierItemDbusObj;
private IStatusNotifierWatcher? _statusNotifierWatcher;
private DbusPixmap _icon;
private string? _sysTrayServiceName;
private string? _tooltipText;
private bool _isActive;
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 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)
{
Logger.TryGet(LogEventLevel.Error, LogArea.X11Platform)
Logger.TryGet(LogEventLevel.Error, "DBUS")
?.Log(this, "Unable to get a dbus connection for system tray icons.");
return;
}
IsActive = true;
_dbusMenuPath = DBusMenuExporter.GenerateDBusMenuObjPath;
MenuExporter = DBusMenuExporter.TryCreateDetachedNativeMenu(_dbusMenuPath, _connection);
CreateTrayIcon();
_ctorFinished = true;
WatchAsync();
}
public async void CreateTrayIcon()
private void InitializeSNWService()
{
if (_connection is null)
return;
if (_connection is null || _isDisposed) return;
try
{
@ -64,12 +74,58 @@ namespace Avalonia.X11
}
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,
"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;
var pid = Process.GetCurrentProcess().Id;
@ -78,45 +134,61 @@ namespace Avalonia.X11
_sysTrayServiceName = $"org.kde.StatusNotifierItem-{pid}-{tid}";
_statusNotifierItemDbusObj = new StatusNotifierItemDbusObj(_dbusMenuPath);
await _connection.RegisterObjectAsync(_statusNotifierItemDbusObj);
await _connection.RegisterServiceAsync(_sysTrayServiceName);
try
{
_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.SetIcon(_icon);
_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;
_connection.UnregisterObject(_statusNotifierItemDbusObj);
await _connection.UnregisterServiceAsync(_sysTrayServiceName);
_isActive = false;
_connection.UnregisterServiceAsync(_sysTrayServiceName);
}
public void Dispose()
{
IsActive = false;
_isDisposed = true;
DestroyTrayIcon();
_connection?.Dispose();
_serviceWatchDisposable?.Dispose();
}
public void SetIcon(IWindowIconImpl? icon)
{
if (_isDisposed)
if (_isDisposed || IconConverterDelegate is null)
return;
if (!(icon is X11IconData x11icon))
if (icon is null)
{
_statusNotifierItemDbusObj?.SetIcon(DbusPixmap.EmptyPixmap);
return;
}
var x11iconData = IconConverterDelegate(icon);
if (x11iconData.Length == 0) return;
var w = (int)x11icon.Data[0];
var h = (int)x11icon.Data[1];
var w = (int)x11iconData[0];
var h = (int)x11iconData[1];
var pixLength = w * h;
var pixByteArrayCounter = 0;
@ -124,7 +196,7 @@ namespace Avalonia.X11
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 & 0xFF0000) >> 16);
pixByteArray[pixByteArrayCounter++] = (byte)((rawPixel & 0xFF00) >> 8);
@ -137,18 +209,21 @@ namespace Avalonia.X11
public void SetIsVisible(bool visible)
{
if (_isDisposed || !_ctorFinished)
if (_isDisposed)
return;
if (visible & !_isActive)
{
DestroyTrayIcon();
CreateTrayIcon();
}
else if (!visible & _isActive)
switch (visible)
{
DestroyTrayIcon();
case true when !_isVisible:
DestroyTrayIcon();
CreateTrayIcon();
break;
case false when _isVisible:
DestroyTrayIcon();
break;
}
_isVisible = visible;
}
public void SetToolTipText(string? text)
@ -248,7 +323,20 @@ namespace Avalonia.X11
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);
@ -298,17 +386,17 @@ namespace Avalonia.X11
Task<IDisposable> WatchNewOverlayIconAsync(Action handler, Action<Exception> onError);
Task<IDisposable> WatchNewToolTipAsync(Action 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 SetAsync(string prop, object val);
Task<IDisposable> WatchPropertiesAsync(Action<PropertyChanges> handler);
}
[Dictionary]
// This class is used by Tmds.Dbus to ferry properties
// from the SNI spec.
// Don't change this to actual C# properties since
// Tmds.Dbus will get confused.
[Dictionary]
internal class StatusNotifierItemProperties
{
public string? Category;
@ -363,5 +451,7 @@ namespace Avalonia.X11
Height = height;
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}"
PlacementTarget="{TemplateBinding}"
IsLightDismissEnabled="True">
<Border BorderBrush="{DynamicResource ThemeBorderMidBrush}"
<Border Background="{DynamicResource ThemeBackgroundBrush}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1">
<ListBox Name="PART_SelectingItemsControl"
BorderThickness="0"

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

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

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

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

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

@ -2,7 +2,7 @@
<Style Selector="FlyoutPresenter">
<Setter Property="HorizontalContentAlignment" 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="BorderThickness" Value="1" />
<Setter Property="Padding" Value="4" />

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

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

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

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

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

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

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

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

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

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

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

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

19
src/Avalonia.X11/X11Platform.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using Avalonia.Controls;
@ -104,11 +105,25 @@ namespace Avalonia.X11
public IntPtr DeferredDisplay { 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()
{
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; }
}
}

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

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

Loading…
Cancel
Save