committed by
GitHub
195 changed files with 3928 additions and 930 deletions
@ -1,5 +1,6 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="System.ValueTuple" Version="4.5.0" /> |
|||
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -1,7 +1,7 @@ |
|||
{ |
|||
"msbuild-sdks": { |
|||
"Microsoft.Build.Traversal": "1.0.43", |
|||
"MSBuild.Sdk.Extras": "1.6.65", |
|||
"MSBuild.Sdk.Extras": "2.0.46", |
|||
"AggregatePackage.NuGet.Sdk" : "0.1.12" |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,8 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>IDEDidComputeMac32BitWarning</key> |
|||
<true/> |
|||
</dict> |
|||
</plist> |
|||
@ -0,0 +1,80 @@ |
|||
//
|
|||
// menu.h
|
|||
// Avalonia.Native.OSX
|
|||
//
|
|||
// Created by Dan Walmsley on 01/08/2019.
|
|||
// Copyright © 2019 Avalonia. All rights reserved.
|
|||
//
|
|||
|
|||
#ifndef menu_h |
|||
#define menu_h |
|||
|
|||
#include "common.h" |
|||
|
|||
class AvnAppMenuItem; |
|||
class AvnAppMenu; |
|||
|
|||
@interface AvnMenu : NSMenu // for some reason it doesnt detect nsmenu here but compiler doesnt complain
|
|||
- (void)setMenu:(NSMenu*) menu; |
|||
@end |
|||
|
|||
@interface AvnMenuItem : NSMenuItem |
|||
- (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem; |
|||
- (void)didSelectItem:(id)sender; |
|||
@end |
|||
|
|||
class AvnAppMenuItem : public ComSingleObject<IAvnAppMenuItem, &IID_IAvnAppMenuItem> |
|||
{ |
|||
private: |
|||
NSMenuItem* _native; // here we hold a pointer to an AvnMenuItem
|
|||
IAvnActionCallback* _callback; |
|||
IAvnPredicateCallback* _predicate; |
|||
bool _isSeperator; |
|||
|
|||
public: |
|||
FORWARD_IUNKNOWN() |
|||
|
|||
AvnAppMenuItem(bool isSeperator); |
|||
|
|||
NSMenuItem* GetNative(); |
|||
|
|||
virtual HRESULT SetSubMenu (IAvnAppMenu* menu) override; |
|||
|
|||
virtual HRESULT SetTitle (void* utf8String) override; |
|||
|
|||
virtual HRESULT SetGesture (void* key, AvnInputModifiers modifiers) override; |
|||
|
|||
virtual HRESULT SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) override; |
|||
|
|||
bool EvaluateItemEnabled(); |
|||
|
|||
void RaiseOnClicked(); |
|||
}; |
|||
|
|||
|
|||
class AvnAppMenu : public ComSingleObject<IAvnAppMenu, &IID_IAvnAppMenu> |
|||
{ |
|||
private: |
|||
AvnMenu* _native; |
|||
|
|||
public: |
|||
FORWARD_IUNKNOWN() |
|||
|
|||
AvnAppMenu(); |
|||
|
|||
AvnAppMenu(AvnMenu* native); |
|||
|
|||
AvnMenu* GetNative(); |
|||
|
|||
virtual HRESULT AddItem (IAvnAppMenuItem* item) override; |
|||
|
|||
virtual HRESULT RemoveItem (IAvnAppMenuItem* item) override; |
|||
|
|||
virtual HRESULT SetTitle (void* utf8String) override; |
|||
|
|||
virtual HRESULT Clear () override; |
|||
}; |
|||
|
|||
|
|||
#endif |
|||
|
|||
@ -0,0 +1,307 @@ |
|||
|
|||
#include "common.h" |
|||
#include "menu.h" |
|||
#include "window.h" |
|||
|
|||
@implementation AvnMenu |
|||
@end |
|||
|
|||
@implementation AvnMenuItem |
|||
{ |
|||
AvnAppMenuItem* _item; |
|||
} |
|||
|
|||
- (id) initWithAvnAppMenuItem: (AvnAppMenuItem*)menuItem |
|||
{ |
|||
if(self != nil) |
|||
{ |
|||
_item = menuItem; |
|||
self = [super initWithTitle:@"" |
|||
action:@selector(didSelectItem:) |
|||
keyEquivalent:@""]; |
|||
|
|||
[self setEnabled:YES]; |
|||
|
|||
[self setTarget:self]; |
|||
} |
|||
|
|||
return self; |
|||
} |
|||
|
|||
- (BOOL)validateMenuItem:(NSMenuItem *)menuItem |
|||
{ |
|||
if([self submenu] != nil) |
|||
{ |
|||
return YES; |
|||
} |
|||
|
|||
return _item->EvaluateItemEnabled(); |
|||
} |
|||
|
|||
- (void)didSelectItem:(nullable id)sender |
|||
{ |
|||
_item->RaiseOnClicked(); |
|||
} |
|||
@end |
|||
|
|||
AvnAppMenuItem::AvnAppMenuItem(bool isSeperator) |
|||
{ |
|||
_isSeperator = isSeperator; |
|||
|
|||
if(isSeperator) |
|||
{ |
|||
_native = [NSMenuItem separatorItem]; |
|||
} |
|||
else |
|||
{ |
|||
_native = [[AvnMenuItem alloc] initWithAvnAppMenuItem: this]; |
|||
} |
|||
|
|||
_callback = nullptr; |
|||
} |
|||
|
|||
NSMenuItem* AvnAppMenuItem::GetNative() |
|||
{ |
|||
return _native; |
|||
} |
|||
|
|||
HRESULT AvnAppMenuItem::SetSubMenu (IAvnAppMenu* menu) |
|||
{ |
|||
auto nsMenu = dynamic_cast<AvnAppMenu*>(menu)->GetNative(); |
|||
|
|||
[_native setSubmenu: nsMenu]; |
|||
|
|||
return S_OK; |
|||
} |
|||
|
|||
HRESULT AvnAppMenuItem::SetTitle (void* utf8String) |
|||
{ |
|||
if (utf8String != nullptr) |
|||
{ |
|||
[_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]]; |
|||
} |
|||
|
|||
return S_OK; |
|||
} |
|||
|
|||
HRESULT AvnAppMenuItem::SetGesture (void* key, AvnInputModifiers modifiers) |
|||
{ |
|||
NSEventModifierFlags flags = 0; |
|||
|
|||
if (modifiers & Control) |
|||
flags |= NSEventModifierFlagControl; |
|||
if (modifiers & Shift) |
|||
flags |= NSEventModifierFlagShift; |
|||
if (modifiers & Alt) |
|||
flags |= NSEventModifierFlagOption; |
|||
if (modifiers & Windows) |
|||
flags |= NSEventModifierFlagCommand; |
|||
|
|||
[_native setKeyEquivalent:[NSString stringWithUTF8String:(const char*)key]]; |
|||
[_native setKeyEquivalentModifierMask:flags]; |
|||
|
|||
return S_OK; |
|||
} |
|||
|
|||
HRESULT AvnAppMenuItem::SetAction (IAvnPredicateCallback* predicate, IAvnActionCallback* callback) |
|||
{ |
|||
_predicate = predicate; |
|||
_callback = callback; |
|||
return S_OK; |
|||
} |
|||
|
|||
bool AvnAppMenuItem::EvaluateItemEnabled() |
|||
{ |
|||
if(_predicate != nullptr) |
|||
{ |
|||
auto result = _predicate->Evaluate (); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
void AvnAppMenuItem::RaiseOnClicked() |
|||
{ |
|||
if(_callback != nullptr) |
|||
{ |
|||
_callback->Run(); |
|||
} |
|||
} |
|||
|
|||
AvnAppMenu::AvnAppMenu() |
|||
{ |
|||
_native = [AvnMenu new]; |
|||
} |
|||
|
|||
AvnAppMenu::AvnAppMenu(AvnMenu* native) |
|||
{ |
|||
_native = native; |
|||
} |
|||
|
|||
AvnMenu* AvnAppMenu::GetNative() |
|||
{ |
|||
return _native; |
|||
} |
|||
|
|||
HRESULT AvnAppMenu::AddItem (IAvnAppMenuItem* item) |
|||
{ |
|||
auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item); |
|||
|
|||
if(avnMenuItem != nullptr) |
|||
{ |
|||
[_native addItem: avnMenuItem->GetNative()]; |
|||
} |
|||
|
|||
return S_OK; |
|||
} |
|||
|
|||
HRESULT AvnAppMenu::RemoveItem (IAvnAppMenuItem* item) |
|||
{ |
|||
auto avnMenuItem = dynamic_cast<AvnAppMenuItem*>(item); |
|||
|
|||
if(avnMenuItem != nullptr) |
|||
{ |
|||
[_native removeItem:avnMenuItem->GetNative()]; |
|||
} |
|||
|
|||
return S_OK; |
|||
} |
|||
|
|||
HRESULT AvnAppMenu::SetTitle (void* utf8String) |
|||
{ |
|||
if (utf8String != nullptr) |
|||
{ |
|||
[_native setTitle:[NSString stringWithUTF8String:(const char*)utf8String]]; |
|||
} |
|||
|
|||
return S_OK; |
|||
} |
|||
|
|||
HRESULT AvnAppMenu::Clear() |
|||
{ |
|||
[_native removeAllItems]; |
|||
return S_OK; |
|||
} |
|||
|
|||
extern IAvnAppMenu* CreateAppMenu() |
|||
{ |
|||
@autoreleasepool |
|||
{ |
|||
id menuBar = [NSMenu new]; |
|||
return new AvnAppMenu(menuBar); |
|||
} |
|||
} |
|||
|
|||
extern IAvnAppMenuItem* CreateAppMenuItem() |
|||
{ |
|||
@autoreleasepool |
|||
{ |
|||
return new AvnAppMenuItem(false); |
|||
} |
|||
} |
|||
|
|||
extern IAvnAppMenuItem* CreateAppMenuItemSeperator() |
|||
{ |
|||
@autoreleasepool |
|||
{ |
|||
return new AvnAppMenuItem(true); |
|||
} |
|||
} |
|||
|
|||
static IAvnAppMenu* s_appMenu = nullptr; |
|||
static NSMenuItem* s_appMenuItem = nullptr; |
|||
|
|||
extern void SetAppMenu (NSString* appName, IAvnAppMenu* menu) |
|||
{ |
|||
s_appMenu = menu; |
|||
|
|||
if(s_appMenu != nullptr) |
|||
{ |
|||
auto nativeMenu = dynamic_cast<AvnAppMenu*>(s_appMenu); |
|||
|
|||
auto currentMenu = [s_appMenuItem menu]; |
|||
|
|||
if (currentMenu != nullptr) |
|||
{ |
|||
[currentMenu removeItem:s_appMenuItem]; |
|||
} |
|||
|
|||
s_appMenuItem = [nativeMenu->GetNative() itemAtIndex:0]; |
|||
|
|||
if (currentMenu == nullptr) |
|||
{ |
|||
currentMenu = [s_appMenuItem menu]; |
|||
} |
|||
|
|||
[[s_appMenuItem menu] removeItem:s_appMenuItem]; |
|||
|
|||
[currentMenu insertItem:s_appMenuItem atIndex:0]; |
|||
|
|||
if([s_appMenuItem submenu] == nullptr) |
|||
{ |
|||
[s_appMenuItem setSubmenu:[NSMenu new]]; |
|||
} |
|||
|
|||
auto appMenu = [s_appMenuItem submenu]; |
|||
|
|||
[appMenu addItem:[NSMenuItem separatorItem]]; |
|||
|
|||
// Services item and menu |
|||
auto servicesItem = [[NSMenuItem alloc] init]; |
|||
servicesItem.title = @"Services"; |
|||
NSMenu *servicesMenu = [[NSMenu alloc] initWithTitle:@"Services"]; |
|||
servicesItem.submenu = servicesMenu; |
|||
[NSApplication sharedApplication].servicesMenu = servicesMenu; |
|||
[appMenu addItem:servicesItem]; |
|||
|
|||
[appMenu addItem:[NSMenuItem separatorItem]]; |
|||
|
|||
// Hide Application |
|||
auto hideItem = [[NSMenuItem alloc] initWithTitle:[@"Hide " stringByAppendingString:appName] action:@selector(hide:) keyEquivalent:@"h"]; |
|||
|
|||
[appMenu addItem:hideItem]; |
|||
|
|||
// Hide Others |
|||
auto hideAllOthersItem = [[NSMenuItem alloc] initWithTitle:@"Hide Others" |
|||
action:@selector(hideOtherApplications:) |
|||
keyEquivalent:@"h"]; |
|||
|
|||
hideAllOthersItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagOption; |
|||
[appMenu addItem:hideAllOthersItem]; |
|||
|
|||
// Show All |
|||
auto showAllItem = [[NSMenuItem alloc] initWithTitle:@"Show All" |
|||
action:@selector(unhideAllApplications:) |
|||
keyEquivalent:@""]; |
|||
|
|||
[appMenu addItem:showAllItem]; |
|||
|
|||
[appMenu addItem:[NSMenuItem separatorItem]]; |
|||
|
|||
// Quit Application |
|||
auto quitItem = [[NSMenuItem alloc] init]; |
|||
quitItem.title = [@"Quit " stringByAppendingString:appName]; |
|||
quitItem.keyEquivalent = @"q"; |
|||
quitItem.target = [AvnWindow class]; |
|||
quitItem.action = @selector(closeAll); |
|||
[appMenu addItem:quitItem]; |
|||
} |
|||
else |
|||
{ |
|||
s_appMenuItem = nullptr; |
|||
} |
|||
} |
|||
|
|||
extern IAvnAppMenu* GetAppMenu () |
|||
{ |
|||
return s_appMenu; |
|||
} |
|||
|
|||
extern NSMenuItem* GetAppMenuItem () |
|||
{ |
|||
return s_appMenuItem; |
|||
} |
|||
|
|||
|
|||
@ -0,0 +1,174 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace Avalonia.Logging |
|||
{ |
|||
/// <summary>
|
|||
/// Logger sink parametrized for given logging level.
|
|||
/// </summary>
|
|||
public readonly struct ParametrizedLogger |
|||
{ |
|||
private readonly ILogSink _sink; |
|||
private readonly LogEventLevel _level; |
|||
|
|||
public ParametrizedLogger(ILogSink sink, LogEventLevel level) |
|||
{ |
|||
_sink = sink; |
|||
_level = level; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Checks if this logger can be used.
|
|||
/// </summary>
|
|||
public bool IsValid => _sink != null; |
|||
|
|||
/// <summary>
|
|||
/// Logs an event.
|
|||
/// </summary>
|
|||
/// <param name="area">The area that the event originates.</param>
|
|||
/// <param name="source">The object from which the event originates.</param>
|
|||
/// <param name="messageTemplate">The message template.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Log( |
|||
string area, |
|||
object source, |
|||
string messageTemplate) |
|||
{ |
|||
_sink.Log(_level, area, source, messageTemplate); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Logs an event.
|
|||
/// </summary>
|
|||
/// <param name="area">The area that the event originates.</param>
|
|||
/// <param name="source">The object from which the event originates.</param>
|
|||
/// <param name="messageTemplate">The message template.</param>
|
|||
/// <param name="propertyValue0">Message property value.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Log<T0>( |
|||
string area, |
|||
object source, |
|||
string messageTemplate, |
|||
T0 propertyValue0) |
|||
{ |
|||
_sink.Log(_level, area, source, messageTemplate, propertyValue0); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Logs an event.
|
|||
/// </summary>
|
|||
/// <param name="area">The area that the event originates.</param>
|
|||
/// <param name="source">The object from which the event originates.</param>
|
|||
/// <param name="messageTemplate">The message template.</param>
|
|||
/// <param name="propertyValue0">Message property value.</param>
|
|||
/// <param name="propertyValue1">Message property value.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Log<T0, T1>( |
|||
string area, |
|||
object source, |
|||
string messageTemplate, |
|||
T0 propertyValue0, |
|||
T1 propertyValue1) |
|||
{ |
|||
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Logs an event.
|
|||
/// </summary>
|
|||
/// <param name="area">The area that the event originates.</param>
|
|||
/// <param name="source">The object from which the event originates.</param>
|
|||
/// <param name="messageTemplate">The message template.</param>
|
|||
/// <param name="propertyValue0">Message property value.</param>
|
|||
/// <param name="propertyValue1">Message property value.</param>
|
|||
/// <param name="propertyValue2">Message property value.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Log<T0, T1, T2>( |
|||
string area, |
|||
object source, |
|||
string messageTemplate, |
|||
T0 propertyValue0, |
|||
T1 propertyValue1, |
|||
T2 propertyValue2) |
|||
{ |
|||
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Logs an event.
|
|||
/// </summary>
|
|||
/// <param name="area">The area that the event originates.</param>
|
|||
/// <param name="source">The object from which the event originates.</param>
|
|||
/// <param name="messageTemplate">The message template.</param>
|
|||
/// <param name="propertyValue0">Message property value.</param>
|
|||
/// <param name="propertyValue1">Message property value.</param>
|
|||
/// <param name="propertyValue2">Message property value.</param>
|
|||
/// <param name="propertyValue3">Message property value.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Log<T0, T1, T2, T3>( |
|||
string area, |
|||
object source, |
|||
string messageTemplate, |
|||
T0 propertyValue0, |
|||
T1 propertyValue1, |
|||
T2 propertyValue2, |
|||
T3 propertyValue3) |
|||
{ |
|||
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Logs an event.
|
|||
/// </summary>
|
|||
/// <param name="area">The area that the event originates.</param>
|
|||
/// <param name="source">The object from which the event originates.</param>
|
|||
/// <param name="messageTemplate">The message template.</param>
|
|||
/// <param name="propertyValue0">Message property value.</param>
|
|||
/// <param name="propertyValue1">Message property value.</param>
|
|||
/// <param name="propertyValue2">Message property value.</param>
|
|||
/// <param name="propertyValue3">Message property value.</param>
|
|||
/// <param name="propertyValue4">Message property value.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Log<T0, T1, T2, T3, T4>( |
|||
string area, |
|||
object source, |
|||
string messageTemplate, |
|||
T0 propertyValue0, |
|||
T1 propertyValue1, |
|||
T2 propertyValue2, |
|||
T3 propertyValue3, |
|||
T4 propertyValue4) |
|||
{ |
|||
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Logs an event.
|
|||
/// </summary>
|
|||
/// <param name="area">The area that the event originates.</param>
|
|||
/// <param name="source">The object from which the event originates.</param>
|
|||
/// <param name="messageTemplate">The message template.</param>
|
|||
/// <param name="propertyValue0">Message property value.</param>
|
|||
/// <param name="propertyValue1">Message property value.</param>
|
|||
/// <param name="propertyValue2">Message property value.</param>
|
|||
/// <param name="propertyValue3">Message property value.</param>
|
|||
/// <param name="propertyValue4">Message property value.</param>
|
|||
/// <param name="propertyValue5">Message property value.</param>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public void Log<T0, T1, T2, T3, T4, T5>( |
|||
string area, |
|||
object source, |
|||
string messageTemplate, |
|||
T0 propertyValue0, |
|||
T1 propertyValue1, |
|||
T2 propertyValue2, |
|||
T3 propertyValue3, |
|||
T4 propertyValue4, |
|||
T5 propertyValue5) |
|||
{ |
|||
_sink.Log(_level, area, source, messageTemplate, propertyValue0, propertyValue1, propertyValue2, propertyValue3, propertyValue4, propertyValue5); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,85 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Controls.Platform; |
|||
using Avalonia.Data; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public partial class NativeMenu |
|||
{ |
|||
public static readonly AttachedProperty<bool> IsNativeMenuExportedProperty = |
|||
AvaloniaProperty.RegisterAttached<NativeMenu, TopLevel, bool>("IsNativeMenuExported"); |
|||
|
|||
public static bool GetIsNativeMenuExported(TopLevel tl) => tl.GetValue(IsNativeMenuExportedProperty); |
|||
|
|||
private static readonly AttachedProperty<NativeMenuInfo> s_nativeMenuInfoProperty = |
|||
AvaloniaProperty.RegisterAttached<NativeMenu, TopLevel, NativeMenuInfo>("___NativeMenuInfo"); |
|||
|
|||
class NativeMenuInfo |
|||
{ |
|||
public bool ChangingIsExported { get; set; } |
|||
public ITopLevelNativeMenuExporter Exporter { get; } |
|||
|
|||
public NativeMenuInfo(TopLevel target) |
|||
{ |
|||
Exporter = (target.PlatformImpl as ITopLevelImplWithNativeMenuExporter)?.NativeMenuExporter; |
|||
if (Exporter != null) |
|||
{ |
|||
Exporter.OnIsNativeMenuExportedChanged += delegate |
|||
{ |
|||
SetIsNativeMenuExported(target, Exporter.IsNativeMenuExported); |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
|
|||
static NativeMenuInfo GetInfo(TopLevel target) |
|||
{ |
|||
var rv = target.GetValue(s_nativeMenuInfoProperty); |
|||
if (rv == null) |
|||
{ |
|||
target.SetValue(s_nativeMenuInfoProperty, rv = new NativeMenuInfo(target)); |
|||
SetIsNativeMenuExported(target, rv.Exporter?.IsNativeMenuExported ?? false); |
|||
} |
|||
|
|||
return rv; |
|||
} |
|||
|
|||
static void SetIsNativeMenuExported(TopLevel tl, bool value) |
|||
{ |
|||
GetInfo(tl).ChangingIsExported = true; |
|||
tl.SetValue(IsNativeMenuExportedProperty, 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; |
|||
}); |
|||
|
|||
public static void SetMenu(AvaloniaObject o, NativeMenu menu) => o.SetValue(MenuProperty, menu); |
|||
public static NativeMenu GetMenu(AvaloniaObject o) => o.GetValue(MenuProperty); |
|||
|
|||
static NativeMenu() |
|||
{ |
|||
// This is needed because of the lack of attached direct properties
|
|||
IsNativeMenuExportedProperty.Changed.Subscribe(args => |
|||
{ |
|||
var info = GetInfo((TopLevel)args.Sender); |
|||
if (!info.ChangingIsExported) |
|||
throw new InvalidOperationException("IsNativeMenuExported property is read-only"); |
|||
info.ChangingIsExported = false; |
|||
}); |
|||
MenuProperty.Changed.Subscribe(args => |
|||
{ |
|||
if (args.Sender is TopLevel tl) |
|||
{ |
|||
GetInfo(tl).Exporter?.SetNativeMenu((NativeMenu)args.NewValue); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,60 @@ |
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Specialized; |
|||
using Avalonia.Collections; |
|||
using Avalonia.Data; |
|||
using Avalonia.LogicalTree; |
|||
using Avalonia.Metadata; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public partial class NativeMenu : AvaloniaObject, IEnumerable<NativeMenuItemBase> |
|||
{ |
|||
private readonly AvaloniaList<NativeMenuItemBase> _items = |
|||
new AvaloniaList<NativeMenuItemBase> { ResetBehavior = ResetBehavior.Remove }; |
|||
private NativeMenuItem _parent; |
|||
[Content] |
|||
public IList<NativeMenuItemBase> Items => _items; |
|||
|
|||
public NativeMenu() |
|||
{ |
|||
_items.Validate = Validator; |
|||
_items.CollectionChanged += ItemsChanged; |
|||
} |
|||
|
|||
private void Validator(NativeMenuItemBase obj) |
|||
{ |
|||
if (obj.Parent != null) |
|||
throw new InvalidOperationException("NativeMenuItem already has a parent"); |
|||
} |
|||
|
|||
private void ItemsChanged(object sender, NotifyCollectionChangedEventArgs e) |
|||
{ |
|||
if(e.OldItems!=null) |
|||
foreach (NativeMenuItemBase i in e.OldItems) |
|||
i.Parent = null; |
|||
if(e.NewItems!=null) |
|||
foreach (NativeMenuItemBase i in e.NewItems) |
|||
i.Parent = this; |
|||
} |
|||
|
|||
public static readonly DirectProperty<NativeMenu, NativeMenuItem> ParentProperty = |
|||
AvaloniaProperty.RegisterDirect<NativeMenu, NativeMenuItem>("Parent", o => o.Parent, (o, v) => o.Parent = v); |
|||
|
|||
public NativeMenuItem Parent |
|||
{ |
|||
get => _parent; |
|||
set => SetAndRaise(ParentProperty, ref _parent, value); |
|||
} |
|||
|
|||
public void Add(NativeMenuItemBase item) => _items.Add(item); |
|||
|
|||
public IEnumerator<NativeMenuItemBase> GetEnumerator() => _items.GetEnumerator(); |
|||
|
|||
IEnumerator IEnumerable.GetEnumerator() |
|||
{ |
|||
return GetEnumerator(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,36 @@ |
|||
using System; |
|||
using Avalonia.Controls.Primitives; |
|||
using Avalonia.Interactivity; |
|||
using Avalonia.Styling; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class NativeMenuBar : TemplatedControl |
|||
{ |
|||
public static readonly AttachedProperty<bool> EnableMenuItemClickForwardingProperty = |
|||
AvaloniaProperty.RegisterAttached<NativeMenuBar, MenuItem, Boolean>( |
|||
"EnableMenuItemClickForwarding"); |
|||
|
|||
static NativeMenuBar() |
|||
{ |
|||
EnableMenuItemClickForwardingProperty.Changed.Subscribe(args => |
|||
{ |
|||
var item = (MenuItem)args.Sender; |
|||
if (args.NewValue.Equals(true)) |
|||
item.Click += OnMenuItemClick; |
|||
else |
|||
item.Click -= OnMenuItemClick; |
|||
}); |
|||
} |
|||
|
|||
public static void SetEnableMenuItemClickForwarding(MenuItem menuItem, bool enable) |
|||
{ |
|||
menuItem.SetValue(EnableMenuItemClickForwardingProperty, enable); |
|||
} |
|||
|
|||
private static void OnMenuItemClick(object sender, RoutedEventArgs e) |
|||
{ |
|||
(((MenuItem)sender).DataContext as NativeMenuItem)?.RaiseClick(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,162 @@ |
|||
using System; |
|||
using System.Windows.Input; |
|||
using Avalonia.Input; |
|||
using Avalonia.Utilities; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class NativeMenuItem : NativeMenuItemBase |
|||
{ |
|||
private string _header; |
|||
private KeyGesture _gesture; |
|||
private bool _enabled = true; |
|||
|
|||
private NativeMenu _menu; |
|||
|
|||
static NativeMenuItem() |
|||
{ |
|||
MenuProperty.Changed.Subscribe(args => |
|||
{ |
|||
var item = (NativeMenuItem)args.Sender; |
|||
var value = (NativeMenu)args.NewValue; |
|||
if (value.Parent != null && value.Parent != item) |
|||
throw new InvalidOperationException("NativeMenu already has a parent"); |
|||
value.Parent = item; |
|||
}); |
|||
} |
|||
|
|||
|
|||
class CanExecuteChangedSubscriber : IWeakSubscriber<EventArgs> |
|||
{ |
|||
private readonly NativeMenuItem _parent; |
|||
|
|||
public CanExecuteChangedSubscriber(NativeMenuItem parent) |
|||
{ |
|||
_parent = parent; |
|||
} |
|||
|
|||
public void OnEvent(object sender, EventArgs e) |
|||
{ |
|||
_parent.CanExecuteChanged(); |
|||
} |
|||
} |
|||
|
|||
private readonly CanExecuteChangedSubscriber _canExecuteChangedSubscriber; |
|||
|
|||
|
|||
public NativeMenuItem() |
|||
{ |
|||
_canExecuteChangedSubscriber = new CanExecuteChangedSubscriber(this); |
|||
} |
|||
|
|||
public NativeMenuItem(string header) : this() |
|||
{ |
|||
Header = header; |
|||
} |
|||
|
|||
public static readonly DirectProperty<NativeMenuItem, NativeMenu> MenuProperty = |
|||
AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenu>(nameof(Menu), o => o._menu, |
|||
(o, v) => |
|||
{ |
|||
if (v.Parent != null && v.Parent != o) |
|||
throw new InvalidOperationException("NativeMenu already has a parent"); |
|||
o._menu = v; |
|||
}); |
|||
|
|||
public NativeMenu Menu |
|||
{ |
|||
get => _menu; |
|||
set |
|||
{ |
|||
if (value.Parent != null && value.Parent != this) |
|||
throw new InvalidOperationException("NativeMenu already has a parent"); |
|||
SetAndRaise(MenuProperty, ref _menu, value); |
|||
} |
|||
} |
|||
|
|||
public static readonly DirectProperty<NativeMenuItem, string> HeaderProperty = |
|||
AvaloniaProperty.RegisterDirect<NativeMenuItem, string>(nameof(Header), o => o._header, (o, v) => o._header = v); |
|||
|
|||
public string Header |
|||
{ |
|||
get => GetValue(HeaderProperty); |
|||
set => SetValue(HeaderProperty, value); |
|||
} |
|||
|
|||
public static readonly DirectProperty<NativeMenuItem, KeyGesture> GestureProperty = |
|||
AvaloniaProperty.RegisterDirect<NativeMenuItem, KeyGesture>(nameof(Gesture), o => o._gesture, (o, v) => o._gesture = v); |
|||
|
|||
public KeyGesture Gesture |
|||
{ |
|||
get => GetValue(GestureProperty); |
|||
set => SetValue(GestureProperty, value); |
|||
} |
|||
|
|||
private ICommand _command; |
|||
|
|||
public static readonly DirectProperty<NativeMenuItem, ICommand> CommandProperty = |
|||
AvaloniaProperty.RegisterDirect<NativeMenuItem, ICommand>(nameof(Command), |
|||
o => o._command, (o, v) => |
|||
{ |
|||
if (o._command != null) |
|||
WeakSubscriptionManager.Unsubscribe(o._command, |
|||
nameof(ICommand.CanExecuteChanged), o._canExecuteChangedSubscriber); |
|||
o._command = v; |
|||
if (o._command != null) |
|||
WeakSubscriptionManager.Subscribe(o._command, |
|||
nameof(ICommand.CanExecuteChanged), o._canExecuteChangedSubscriber); |
|||
o.CanExecuteChanged(); |
|||
}); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="CommandParameter"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<object> CommandParameterProperty = |
|||
Button.CommandParameterProperty.AddOwner<MenuItem>(); |
|||
|
|||
public static readonly DirectProperty<NativeMenuItem, bool> EnabledProperty = |
|||
AvaloniaProperty.RegisterDirect<NativeMenuItem, bool>(nameof(Enabled), o => o._enabled, |
|||
(o, v) => o._enabled = v, true); |
|||
|
|||
public bool Enabled |
|||
{ |
|||
get => GetValue(EnabledProperty); |
|||
set => SetValue(EnabledProperty, value); |
|||
} |
|||
|
|||
void CanExecuteChanged() |
|||
{ |
|||
Enabled = _command?.CanExecute(null) ?? true; |
|||
} |
|||
|
|||
public bool HasClickHandlers => Clicked != null; |
|||
|
|||
public ICommand Command |
|||
{ |
|||
get => GetValue(CommandProperty); |
|||
set => SetValue(CommandProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the parameter to pass to the <see cref="Command"/> property of a
|
|||
/// <see cref="NativeMenuItem"/>.
|
|||
/// </summary>
|
|||
public object CommandParameter |
|||
{ |
|||
get { return GetValue(CommandParameterProperty); } |
|||
set { SetValue(CommandParameterProperty, value); } |
|||
} |
|||
|
|||
public event EventHandler Clicked; |
|||
|
|||
public void RaiseClick() |
|||
{ |
|||
Clicked?.Invoke(this, new EventArgs()); |
|||
|
|||
if (Command?.CanExecute(CommandParameter) == true) |
|||
{ |
|||
Command.Execute(CommandParameter); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,23 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class NativeMenuItemBase : AvaloniaObject |
|||
{ |
|||
private NativeMenu _parent; |
|||
|
|||
internal NativeMenuItemBase() |
|||
{ |
|||
|
|||
} |
|||
|
|||
public static readonly DirectProperty<NativeMenuItem, NativeMenu> ParentProperty = |
|||
AvaloniaProperty.RegisterDirect<NativeMenuItem, NativeMenu>("Parent", o => o.Parent, (o, v) => o.Parent = v); |
|||
|
|||
public NativeMenu Parent |
|||
{ |
|||
get => _parent; |
|||
set => SetAndRaise(ParentProperty, ref _parent, value); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Controls |
|||
{ |
|||
public class NativeMenuItemSeperator : NativeMenuItemBase |
|||
{ |
|||
[Obsolete("This is a temporary hack to make our MenuItem recognize this as a separator, don't use", true)] |
|||
public string Header => "-"; |
|||
} |
|||
} |
|||
@ -0,0 +1,18 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Controls.Platform |
|||
{ |
|||
public interface ITopLevelNativeMenuExporter |
|||
{ |
|||
bool IsNativeMenuExported { get; } |
|||
event EventHandler OnIsNativeMenuExportedChanged; |
|||
void SetNativeMenu(NativeMenu menu); |
|||
} |
|||
|
|||
public interface ITopLevelImplWithNativeMenuExporter : ITopLevelImpl |
|||
{ |
|||
ITopLevelNativeMenuExporter NativeMenuExporter { get; } |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue