Skip to content

Commit

Permalink
Merge branch 'feature/tray-icon-support' into tmp-master
Browse files Browse the repository at this point in the history
# Conflicts:
#	src/Avalonia.Controls/ApiCompatBaseline.txt
  • Loading branch information
danwalmsley committed Sep 13, 2021
1 parent a7d5c9a commit 7eaf18c
Show file tree
Hide file tree
Showing 31 changed files with 1,062 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37E2330E21583241000CB7E2 /* KeyTransform.mm */; };
520624B322973F4100C4DCEF /* menu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 520624B222973F4100C4DCEF /* menu.mm */; };
522D5959258159C1006F7F7A /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 522D5958258159C1006F7F7A /* Carbon.framework */; };
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */ = {isa = PBXBuildFile; fileRef = 523484C926EA688F00EA0C2C /* trayicon.mm */; };
5B21A982216530F500CEE36E /* cursor.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B21A981216530F500CEE36E /* cursor.mm */; };
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */; };
AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; };
Expand Down Expand Up @@ -51,6 +52,8 @@
37E2330E21583241000CB7E2 /* KeyTransform.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = "<group>"; };
520624B222973F4100C4DCEF /* menu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = menu.mm; sourceTree = "<group>"; };
522D5958258159C1006F7F7A /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = System/Library/Frameworks/Carbon.framework; sourceTree = SDKROOT; };
523484C926EA688F00EA0C2C /* trayicon.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = trayicon.mm; sourceTree = "<group>"; };
523484CB26EA68AA00EA0C2C /* trayicon.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = trayicon.h; sourceTree = "<group>"; };
5B21A981216530F500CEE36E /* cursor.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cursor.mm; sourceTree = "<group>"; };
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = clipboard.mm; sourceTree = "<group>"; };
5BF943652167AD1D009CAE35 /* cursor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = cursor.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -114,6 +117,8 @@
AB00E4F62147CA920032A60A /* main.mm */,
37155CE3233C00EB0034DCE9 /* menu.h */,
520624B222973F4100C4DCEF /* menu.mm */,
523484C926EA688F00EA0C2C /* trayicon.mm */,
523484CB26EA68AA00EA0C2C /* trayicon.h */,
1A3E5EA723E9E83B00EDE661 /* rendertarget.mm */,
37A517B22159597E00FBA241 /* Screens.mm */,
37C09D8721580FE4006A6758 /* SystemDialogs.mm */,
Expand Down Expand Up @@ -204,6 +209,7 @@
1A1852DC23E05814008F0DED /* deadlock.mm in Sources */,
5B21A982216530F500CEE36E /* cursor.mm in Sources */,
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,
523484CA26EA688F00EA0C2C /* trayicon.mm in Sources */,
AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */,
1A3E5EA823E9E83B00EDE661 /* rendertarget.mm in Sources */,
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */,
Expand Down
1 change: 1 addition & 0 deletions native/Avalonia.Native/src/OSX/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop);
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlDisplay* GetGlDisplay();
extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);
extern IAvnTrayIcon* CreateTrayIcon(IAvnTrayIconEvents* events);
extern IAvnMenuItem* CreateAppMenuItem();
extern IAvnMenuItem* CreateAppMenuItemSeparator();
extern IAvnNativeControlHost* CreateNativeControlHost(NSView* parent);
Expand Down
11 changes: 11 additions & 0 deletions native/Avalonia.Native/src/OSX/main.mm
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,17 @@ virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) override
}
}

virtual HRESULT CreateTrayIcon (IAvnTrayIconEvents*cb, IAvnTrayIcon** ppv) override
{
START_COM_CALL;

@autoreleasepool
{
*ppv = ::CreateTrayIcon(cb);
return S_OK;
}
}

virtual HRESULT CreateMenu (IAvnMenuEvents* cb, IAvnMenu** ppv) override
{
START_COM_CALL;
Expand Down
34 changes: 34 additions & 0 deletions native/Avalonia.Native/src/OSX/trayicon.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// trayicon.h
// Avalonia.Native.OSX
//
// Created by Dan Walmsley on 09/09/2021.
// Copyright © 2021 Avalonia. All rights reserved.
//

#ifndef trayicon_h
#define trayicon_h

#include "common.h"

class AvnTrayIcon : public ComSingleObject<IAvnTrayIcon, &IID_IAvnTrayIcon>
{
private:
NSStatusItem* _native;
ComPtr<IAvnTrayIconEvents> _events;

public:
FORWARD_IUNKNOWN()

AvnTrayIcon(IAvnTrayIconEvents* events);

~AvnTrayIcon ();

virtual HRESULT SetIcon (void* data, size_t length) override;

virtual HRESULT SetMenu (IAvnMenu* menu) override;

virtual HRESULT SetIsVisible (bool isVisible) override;
};

#endif /* trayicon_h */
87 changes: 87 additions & 0 deletions native/Avalonia.Native/src/OSX/trayicon.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#include "common.h"
#include "trayicon.h"
#include "menu.h"

extern IAvnTrayIcon* CreateTrayIcon(IAvnTrayIconEvents* cb)
{
@autoreleasepool
{
return new AvnTrayIcon(cb);
}
}

AvnTrayIcon::AvnTrayIcon(IAvnTrayIconEvents* events)
{
_events = events;

_native = [[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength];

}

AvnTrayIcon::~AvnTrayIcon()
{
if(_native != nullptr)
{
[[_native statusBar] removeStatusItem:_native];
_native = nullptr;
}
}

HRESULT AvnTrayIcon::SetIcon (void* data, size_t length)
{
START_COM_CALL;

@autoreleasepool
{
if(data != nullptr)
{
NSData *imageData = [NSData dataWithBytes:data length:length];
NSImage *image = [[NSImage alloc] initWithData:imageData];

NSSize originalSize = [image size];

NSSize size;
size.height = [[NSFont menuFontOfSize:0] pointSize] * 1.333333;

auto scaleFactor = size.height / originalSize.height;
size.width = originalSize.width * scaleFactor;

[image setSize: size];
[_native setImage:image];
}
else
{
[_native setImage:nullptr];
}
return S_OK;
}
}

HRESULT AvnTrayIcon::SetMenu (IAvnMenu* menu)
{
START_COM_CALL;

@autoreleasepool
{
auto appMenu = dynamic_cast<AvnAppMenu*>(menu);

if(appMenu != nullptr)
{
[_native setMenu:appMenu->GetNative()];
}
}

return S_OK;
}

HRESULT AvnTrayIcon::SetIsVisible(bool isVisible)
{
START_COM_CALL;

@autoreleasepool
{
[_native setVisible:isVisible];
}

return S_OK;
}
25 changes: 24 additions & 1 deletion samples/ControlCatalog/App.xaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:ControlCatalog.ViewModels"
x:DataType="vm:ApplicationViewModel"
x:CompileBindings="True"
x:Class="ControlCatalog.App">
<Application.Styles>
<Style Selector="TextBlock.h1">
Expand All @@ -22,6 +25,26 @@
<Style Selector="Label.h3">
<Setter Property="FontSize" Value="12" />
</Style>
<StyleInclude Source="/SideBar.xaml"/>
<StyleInclude Source="/SideBar.xaml" />
</Application.Styles>
<TrayIcon.TrayIcons>
<TrayIcons>
<TrayIcon Icon="/Assets/test_icon.ico" ToolTipText="Avalonia Tray Icon ToolTip">
<NativeMenu.Menu>
<NativeMenu>
<NativeMenuItem Header="Settings">
<NativeMenu>
<NativeMenuItem Header="Option 1" ToggleType="Radio" IsChecked="True" Command="{Binding ToggleCommand}" />
<NativeMenuItem Header="Option 2" ToggleType="Radio" IsChecked="True" Command="{Binding ToggleCommand}" />
<NativeMenuItemSeparator />
<NativeMenuItem Header="Option 3" ToggleType="CheckBox" IsChecked="True" Command="{Binding ToggleCommand}" />
<NativeMenuItem Icon="/Assets/test_icon.ico" Header="Restore Defaults" Command="{Binding ToggleCommand}" />
</NativeMenu>
</NativeMenuItem>
<NativeMenuItem Header="Exit" Command="{Binding ExitCommand}" />
</NativeMenu>
</NativeMenu.Menu>
</TrayIcon>
</TrayIcons>
</TrayIcon.TrayIcons>
</Application>
9 changes: 9 additions & 0 deletions samples/ControlCatalog/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using Avalonia.Markup.Xaml.Styling;
using Avalonia.Styling;
using ControlCatalog.ViewModels;

namespace ControlCatalog
{
public class App : Application
{
public App()
{
DataContext = new ApplicationViewModel();
}

private static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml")
Expand Down Expand Up @@ -97,7 +104,9 @@ public override void Initialize()
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopLifetime)
{
desktopLifetime.MainWindow = new MainWindow();
}
else if (ApplicationLifetime is ISingleViewApplicationLifetime singleViewLifetime)
singleViewLifetime.MainView = new MainView();

Expand Down
2 changes: 2 additions & 0 deletions samples/ControlCatalog/MainWindow.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public MainWindow()

var mainMenu = this.FindControl<Menu>("MainMenu");
mainMenu.AttachedToVisualTree += MenuAttached;

ExtendClientAreaChromeHints = Avalonia.Platform.ExtendClientAreaChromeHints.OSXThickTitleBar;
}

public static string MenuQuitHeader => RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "Quit Avalonia" : "E_xit";
Expand Down
26 changes: 26 additions & 0 deletions samples/ControlCatalog/ViewModels/ApplicationViewModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using MiniMvvm;

namespace ControlCatalog.ViewModels
{
public class ApplicationViewModel : ViewModelBase
{
public ApplicationViewModel()
{
ExitCommand = MiniCommand.Create(() =>
{
if(Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime lifetime)
{
lifetime.Shutdown();
}
});

ToggleCommand = MiniCommand.Create(() => { });
}

public MiniCommand ExitCommand { get; }

public MiniCommand ToggleCommand { get; }
}
}
4 changes: 3 additions & 1 deletion src/Avalonia.Controls/ApiCompatBaseline.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ MembersMustExist : Member 'public System.Action<Avalonia.Size> Avalonia.Controls
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.Resized.set(System.Action<Avalonia.Size>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.Platform.ITopLevelNativeMenuExporter.SetNativeMenu(Avalonia.Controls.NativeMenu)' is present in the contract but not in the implementation.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
Expand All @@ -26,4 +27,5 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
Total Issues: 27
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
Total Issues: 58
13 changes: 6 additions & 7 deletions src/Avalonia.Controls/NativeMenu.Export.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,10 @@ static void SetIsNativeMenuExported(TopLevel tl, bool value)
}

public static readonly AttachedProperty<NativeMenu> MenuProperty
= AvaloniaProperty.RegisterAttached<NativeMenu, AvaloniaObject, NativeMenu>("Menu"/*, validate:
(o, v) =>
{
if(!(o is Application || o is TopLevel))
throw new InvalidOperationException("NativeMenu.Menu property isn't valid on "+o.GetType());
return v;
}*/);
= AvaloniaProperty.RegisterAttached<NativeMenu, AvaloniaObject, NativeMenu>("Menu");

public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu);

public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty);

static NativeMenu()
Expand All @@ -79,6 +74,10 @@ static NativeMenu()
{
GetInfo(tl).Exporter?.SetNativeMenu(args.NewValue.GetValueOrDefault());
}
else if(args.Sender is INativeMenuExporterProvider provider)
{
provider.NativeMenuExporter?.SetNativeMenu(args.NewValue.GetValueOrDefault());
}
});
}
}
Expand Down
17 changes: 14 additions & 3 deletions src/Avalonia.Controls/Platform/ITopLevelNativeMenuExporter.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
using System;
using System.Collections.Generic;
using Avalonia.Platform;

#nullable enable

namespace Avalonia.Controls.Platform
{
public interface ITopLevelNativeMenuExporter
public interface INativeMenuExporter
{
void SetNativeMenu(NativeMenu menu);
}

public interface ITopLevelNativeMenuExporter : INativeMenuExporter
{
bool IsNativeMenuExported { get; }

event EventHandler OnIsNativeMenuExportedChanged;
void SetNativeMenu(NativeMenu menu);
}

public interface INativeMenuExporterProvider
{
INativeMenuExporter? NativeMenuExporter { get; }
}

public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl
Expand Down
36 changes: 36 additions & 0 deletions src/Avalonia.Controls/Platform/ITrayIconImpl.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Platform;

#nullable enable

namespace Avalonia.Platform
{
public interface ITrayIconImpl : IDisposable
{
/// <summary>
/// Sets the icon of this tray icon.
/// </summary>
void SetIcon(IWindowIconImpl? icon);

/// <summary>
/// Sets the icon of this tray icon.
/// </summary>
void SetToolTipText(string? text);

/// <summary>
/// Sets if the tray icon is visible or not.
/// </summary>
void SetIsVisible (bool visible);

/// <summary>
/// Gets the MenuExporter to allow native menus to be exported to the TrayIcon.
/// </summary>
INativeMenuExporter? MenuExporter { get; }

/// <summary>
/// Gets or Sets the Action that is called when the TrayIcon is clicked.
/// </summary>
Action? OnClicked { get; set; }
}
}
Loading

0 comments on commit 7eaf18c

Please sign in to comment.