Browse Source

Merge pull request #1005 from AvaloniaUI/monomac

MonoMac-based backend
pull/1149/merge
Nikita Tsukanov 9 years ago
committed by GitHub
parent
commit
5a752f0851
  1. 52
      Avalonia.sln
  2. 5
      build/MonoMac.props
  3. 18
      packages.cake
  4. 34
      samples/ControlCatalog.NetCore/Program.cs
  5. 12
      samples/ControlCatalog/ControlCatalog.csproj
  6. 33
      samples/ControlCatalog/DecoratedWindow.xaml
  7. 53
      samples/ControlCatalog/DecoratedWindow.xaml.cs
  8. 2
      samples/ControlCatalog/MainView.xaml
  9. 9
      samples/ControlCatalog/MainView.xaml.cs
  10. 2
      samples/ControlCatalog/MainWindow.xaml.cs
  11. 12
      samples/ControlCatalog/Pages/DialogsPage.xaml
  12. 46
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  13. 2
      src/Avalonia.Controls/SystemDialog.cs
  14. 6
      src/Avalonia.DotNetCoreRuntime/AppBuilder.cs
  15. 1
      src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj
  16. 6
      src/OSX/Avalonia.MonoMac/AssemblyInfo.cs
  17. 25
      src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj
  18. 62
      src/OSX/Avalonia.MonoMac/Cursor.cs
  19. 52
      src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs
  20. 31
      src/OSX/Avalonia.MonoMac/Helpers.cs
  21. 267
      src/OSX/Avalonia.MonoMac/KeyTransform.cs
  22. 75
      src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs
  23. 63
      src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs
  24. 18
      src/OSX/Avalonia.MonoMac/PopupImpl.cs
  25. 47
      src/OSX/Avalonia.MonoMac/Stubs.cs
  26. 91
      src/OSX/Avalonia.MonoMac/SystemDialogsImpl.cs
  27. 365
      src/OSX/Avalonia.MonoMac/TopLevelImpl.cs
  28. 179
      src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs
  29. 116
      src/OSX/Avalonia.MonoMac/WindowImpl.cs
  30. 85
      src/Shared/WindowResizeDragHelper.cs
  31. 1
      src/Windows/Avalonia.Win32/Avalonia.Win32.Shared.projitems
  32. 21
      src/Windows/Avalonia.Win32/WindowImpl.cs

52
Avalonia.sln

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26430.16
VisualStudioVersion = 15.0.26730.3
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Base", "src\Avalonia.Base\Avalonia.Base.csproj", "{B09B78D8-9B26-48B0-9149-D64A2F120F3F}"
EndProject
@ -129,7 +129,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsInteropTest", "sampl
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GtkInteropDemo", "samples\interop\GtkInteropDemo\GtkInteropDemo.csproj", "{BD7F352C-6DC1-4740-BAF2-2D34A038728C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DotNetFrameworkRuntime", "src\Avalonia.DotNetFrameworkRuntime\Avalonia.DotNetFrameworkRuntime.csproj", "{4A1ABB09-9047-4BD5-A4AD-A055E52C5EE0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RenderTest", "samples\RenderTest\RenderTest.csproj", "{F1FDC5B0-4654-416F-AE69-E3E9BBD87801}"
EndProject
@ -186,6 +186,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Win32.Interop", "s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Skia.RenderTests", "tests\Avalonia.RenderTests\Avalonia.Skia.RenderTests.csproj", "{E1582370-37B3-403C-917F-8209551B1634}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OSX", "OSX", "{A59C4C0A-64DF-4621-B450-2BA00D6F61E2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MonoMac", "src\OSX\Avalonia.MonoMac\Avalonia.MonoMac.csproj", "{CBFD5788-567D-401B-9DFA-74E4224025A0}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
src\Shared\RenderHelpers\RenderHelpers.projitems*{3c4c0cb4-0c0f-4450-a37b-148c84ff905f}*SharedItemsImports = 13
@ -2515,6 +2519,46 @@ Global
{E1582370-37B3-403C-917F-8209551B1634}.Release|Mono.Build.0 = Release|Any CPU
{E1582370-37B3-403C-917F-8209551B1634}.Release|x86.ActiveCfg = Release|Any CPU
{E1582370-37B3-403C-917F-8209551B1634}.Release|x86.Build.0 = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Mono.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|Mono.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|x86.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Ad-Hoc|x86.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhone.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|Mono.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|Mono.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|x86.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.AppStore|x86.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhone.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|Mono.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|Mono.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|x86.ActiveCfg = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Debug|x86.Build.0 = Debug|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Any CPU.Build.0 = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhone.ActiveCfg = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhone.Build.0 = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Mono.ActiveCfg = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|Mono.Build.0 = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|x86.ActiveCfg = Release|Any CPU
{CBFD5788-567D-401B-9DFA-74E4224025A0}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2572,5 +2616,9 @@ Global
{638580B0-7910-40EF-B674-DCB34DA308CD} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{CBC4FF2F-92D4-420B-BE21-9FE0B930B04E} = {B39A8919-9F95-48FE-AD7B-76E08B509888}
{E1582370-37B3-403C-917F-8209551B1634} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{CBFD5788-567D-401B-9DFA-74E4224025A0} = {A59C4C0A-64DF-4621-B450-2BA00D6F61E2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}
EndGlobalSection
EndGlobal

5
build/MonoMac.props

@ -0,0 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="MonoMac.NetStandard" Version="0.0.3" />
</ItemGroup>
</Project>

18
packages.cake

@ -449,6 +449,20 @@ public class Packages
BasePath = context.Directory("./src/Skia/Avalonia.Skia/bin/" + parameters.DirSuffix + "/netstandard2.0"),
OutputDirectory = parameters.NugetRoot
},
new NuGetPackSettings()
{
Id = "Avalonia.MonoMac",
Dependencies = new DependencyBuilder(this)
{
new NuSpecDependency() { Id = "Avalonia", Version = parameters.Version }
}.Dep("MonoMac.NetStandard").ToArray(),
Files = new []
{
new NuSpecContent { Source = "netstandard2.0/Avalonia.MonoMac.dll", Target = "lib/netstandard2.0" },
},
BasePath = context.Directory("./src/OSX/Avalonia.MonoMac/bin/" + parameters.DirSuffix),
OutputDirectory = parameters.NugetRoot
},
///////////////////////////////////////////////////////////////////////////////
// Avalonia.Desktop
///////////////////////////////////////////////////////////////////////////////
@ -464,10 +478,12 @@ public class Packages
new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="net45", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Skia", TargetFramework="net45", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="net45", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.MonoMac", TargetFramework="net45", Version = parameters.Version },
//.NET Core
new NuSpecDependency() { Id = "Avalonia.Win32", TargetFramework="netcoreapp2.0", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Skia", TargetFramework="netcoreapp2.0", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="netcoreapp2.0", Version = parameters.Version }
new NuSpecDependency() { Id = "Avalonia.Gtk3", TargetFramework="netcoreapp2.0", Version = parameters.Version },
new NuSpecDependency() { Id = "Avalonia.MonoMac", TargetFramework="netcoreapp2.0", Version = parameters.Version }
},
Files = new NuSpecContent[]
{

34
samples/ControlCatalog.NetCore/Program.cs

@ -1,25 +1,47 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using Avalonia;
namespace ControlCatalog.NetCore
{
class Program
static class Program
{
static void Main(string[] args)
{
if (args.Contains("--fbdev")) AppBuilder.Configure<App>().InitializeWithLinuxFramebuffer(tl =>
if (args.Contains("--wait-for-attach"))
{
tl.Content = new MainView();
System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer());
});
Console.WriteLine("Attach debugger and use 'Set next statement'");
while (true)
{
Thread.Sleep(100);
if (Debugger.IsAttached)
break;
}
}
if (args.Contains("--fbdev"))
AppBuilder.Configure<App>().InitializeWithLinuxFramebuffer(tl =>
{
tl.Content = new MainView();
System.Threading.ThreadPool.QueueUserWorkItem(_ => ConsoleSilencer());
});
else
AppBuilder.Configure<App>()
.UsePlatformDetect()
.CustomPlatformDetect()
.UseReactiveUI()
.Start<MainWindow>();
}
static AppBuilder CustomPlatformDetect(this AppBuilder builder)
{
//This is needed because we still aren't ready to have MonoMac backend as default one
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return builder.UseSkia().UseMonoMac();
return builder.UsePlatformDetect();
}
static void ConsoleSilencer()
{
Console.CursorVisible = false;

12
samples/ControlCatalog/ControlCatalog.csproj

@ -32,6 +32,12 @@
<EmbeddedResource Include="MainView.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="DecoratedWindow.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\DialogsPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Pages\BorderPage.xaml">
<SubType>Designer</SubType>
</EmbeddedResource>
@ -83,9 +89,15 @@
<Compile Include="MainView.xaml.cs">
<DependentUpon>MainView.xaml</DependentUpon>
</Compile>
<Compile Include="DecoratedWindow.xaml.cs">
<DependentUpon>DecoratedWindow.xaml</DependentUpon>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\DialogsPage.xaml.cs">
<DependentUpon>DialogsPage.xaml</DependentUpon>
</Compile>
<Compile Include="Pages\BorderPage.xaml.cs">
<DependentUpon>BorderPage.xaml</DependentUpon>
</Compile>

33
samples/ControlCatalog/DecoratedWindow.xaml

@ -0,0 +1,33 @@
<Window xmlns="https://github.com/avaloniaui" MinWidth="500" MinHeight="300"
Title="Avalonia Control Gallery"
Icon="resm:ControlCatalog.Assets.test_icon.ico?assembly=ControlCatalog"
xmlns:local="clr-namespace:ControlCatalog;assembly=ControlCatalog" HasSystemDecorations="False">
<Grid RowDefinitions="5,*,5" ColumnDefinitions="5,*,5">
<DockPanel Grid.Column="1" Grid.Row="1" >
<Grid Name="TitleBar" Background="LightBlue" DockPanel.Dock="Top" ColumnDefinitions="Auto,*,Auto">
<TextBlock VerticalAlignment="Center" Margin="5,0,0,0">Title</TextBlock>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<StackPanel.Styles>
<Style Selector="Button">
<Setter Property="Margin" Value="2"/>
</Style>
</StackPanel.Styles>
<Button Name="MinimizeButton">_</Button>
<Button Name="MaximizeButton">[ ]</Button>
<Button Name="CloseButton">X</Button>
</StackPanel>
</Grid>
<Border Background="White" Margin="5">
<TextBlock>Hello world!</TextBlock>
</Border>
</DockPanel>
<Border Name="TopLeft" Background="Red"/>
<Border Name="TopRight" Background="Red" Grid.Column="2" />
<Border Name="BottomLeft" Background="Red" Grid.Row="2" />
<Border Name="BottomRight" Background="Red" Grid.Row="2" Grid.Column="2"/>
<Border Name="Top" Background="Blue" Grid.Column="1" />
<Border Name="Right" Background="Blue" Grid.Row="1" Grid.Column="2" />
<Border Name="Bottom" Background="Blue" Grid.Row="2" Grid.Column="1" />
<Border Name="Left" Background="Blue" Grid.Row="1" />
</Grid>
</Window>

53
samples/ControlCatalog/DecoratedWindow.xaml.cs

@ -0,0 +1,53 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using System;
using Avalonia.Input;
namespace ControlCatalog
{
public class DecoratedWindow : Window
{
public DecoratedWindow()
{
this.InitializeComponent();
this.AttachDevTools();
}
void SetupSide(string name, StandardCursorType cursor, WindowEdge edge)
{
var ctl = this.FindControl<Control>(name);
ctl.Cursor = new Cursor(cursor);
ctl.PointerPressed += delegate
{
PlatformImpl.BeginResizeDrag(edge);
};
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
this.FindControl<Control>("TitleBar").PointerPressed += delegate
{
PlatformImpl.BeginMoveDrag();
};
SetupSide("Left", StandardCursorType.LeftSide, WindowEdge.West);
SetupSide("Right", StandardCursorType.RightSide, WindowEdge.East);
SetupSide("Top", StandardCursorType.TopSide, WindowEdge.North);
SetupSide("Bottom", StandardCursorType.BottomSize, WindowEdge.South);
SetupSide("TopLeft", StandardCursorType.TopLeftCorner, WindowEdge.NorthWest);
SetupSide("TopRight", StandardCursorType.TopRightCorner, WindowEdge.NorthEast);
SetupSide("BottomLeft", StandardCursorType.BottomLeftCorner, WindowEdge.SouthWest);
SetupSide("BottomRight", StandardCursorType.BottomRightCorner, WindowEdge.SouthEast);
this.FindControl<Button>("MinimizeButton").Click += delegate { this.WindowState = WindowState.Minimized; };
this.FindControl<Button>("MaximizeButton").Click += delegate
{
WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
};
this.FindControl<Button>("CloseButton").Click += delegate
{
Close();
};
}
}
}

2
samples/ControlCatalog/MainView.xaml

@ -1,7 +1,7 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:pages="clr-namespace:ControlCatalog.Pages;assembly=ControlCatalog"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<TabControl Classes="sidebar">
<TabControl Classes="sidebar" Name="Sidebar">
<TabControl.Transition>
<CrossFade Duration="0.25"/>
</TabControl.Transition>

9
samples/ControlCatalog/MainView.xaml.cs

@ -1,6 +1,9 @@
using System.Collections;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Platform;
using ControlCatalog.Pages;
namespace ControlCatalog
{
@ -9,6 +12,12 @@ namespace ControlCatalog
public MainView()
{
this.InitializeComponent();
if (AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().IsDesktop)
((IList) this.FindControl<TabControl>("Sidebar").Items).Add(new TabItem()
{
Header = "Dialogs",
Content = new DialogsPage()
});
}
private void InitializeComponent()

2
samples/ControlCatalog/MainWindow.xaml.cs

@ -11,7 +11,7 @@ namespace ControlCatalog
{
this.InitializeComponent();
this.AttachDevTools();
Renderer.DrawDirtyRects = Renderer.DrawFps = true;
//Renderer.DrawDirtyRects = Renderer.DrawFps = true;
}
private void InitializeComponent()

12
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -0,0 +1,12 @@
<UserControl xmlns="https://github.com/avaloniaui">
<StackPanel Orientation="Vertical" Gap="4" Margin="4">
<Button Name="OpenFile">Open File</Button>
<Button Name="SaveFile">Save File</Button>
<Button Name="SelectFolder">Select Folder</Button>
<StackPanel Orientation="Horizontal">
<CheckBox Name="IsModal" IsChecked="True"/>
<TextBlock>Modal to window</TextBlock>
</StackPanel>
<Button Name="DecoratedWindow">Decorated window</Button>
</StackPanel>
</UserControl>

46
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -0,0 +1,46 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
#pragma warning disable 4014
namespace ControlCatalog.Pages
{
public class DialogsPage : UserControl
{
public DialogsPage()
{
this.InitializeComponent();
this.FindControl<Button>("OpenFile").Click += delegate
{
new OpenFileDialog()
{
Title = "Open file"
}.ShowAsync(GetWindow());
};
this.FindControl<Button>("SaveFile").Click += delegate
{
new SaveFileDialog()
{
Title = "Save file"
}.ShowAsync(GetWindow());
};
this.FindControl<Button>("SelectFolder").Click += delegate
{
new OpenFolderDialog()
{
Title = "Select folder"
}.ShowAsync(GetWindow());
};
this.FindControl<Button>("DecoratedWindow").Click += delegate
{
new DecoratedWindow().Show();
};
}
Window GetWindow() => this.FindControl<CheckBox>("IsModal").IsChecked ? (Window)this.VisualRoot : null;
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

2
src/Avalonia.Controls/SystemDialog.cs

@ -20,7 +20,7 @@ namespace Avalonia.Controls
{
public string DefaultExtension { get; set; }
public async Task<string> ShowAsync(Window window = null)
public async Task<string> ShowAsync(Window window)
=>
((await AvaloniaLocator.Current.GetService<ISystemDialogImpl>().ShowFileDialogAsync(this, window?.PlatformImpl)) ??
new string[0]).FirstOrDefault();

6
src/Avalonia.DotNetCoreRuntime/AppBuilder.cs

@ -37,12 +37,15 @@ namespace Avalonia
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder UsePlatformDetect()
{
var os = RuntimePlatform.GetRuntimeInfo().OperatingSystem;
//We don't have the ability to load every assembly right now, so we are
//stuck with manual configuration here
//Helpers are extracted to separate methods to take the advantage of the fact
//that CLR doesn't try to load dependencies before referencing method is jitted
if (RuntimePlatform.GetRuntimeInfo().OperatingSystem == OperatingSystemType.WinNT)
if (os == OperatingSystemType.WinNT)
LoadWin32();
else if(os==OperatingSystemType.OSX)
LoadMonoMac();
else
LoadGtk3();
this.UseSkia();
@ -50,6 +53,7 @@ namespace Avalonia
return this;
}
void LoadMonoMac() => this.UseMonoMac();
void LoadWin32() => this.UseWin32();
void LoadGtk3() => this.UseGtk3();
}

1
src/Avalonia.DotNetCoreRuntime/Avalonia.DotNetCoreRuntime.csproj

@ -16,6 +16,7 @@
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj" />
<ProjectReference Include="..\OSX\Avalonia.MonoMac\Avalonia.MonoMac.csproj" />
<ProjectReference Include="..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
<ProjectReference Include="..\Windows\Avalonia.Win32.NetStandard\Avalonia.Win32.NetStandard.csproj" />
</ItemGroup>

6
src/OSX/Avalonia.MonoMac/AssemblyInfo.cs

@ -0,0 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;using Avalonia.MonoMac;
using Avalonia.Platform;
[assembly: ExportWindowingSubsystem(OperatingSystemType.OSX, 1, "MonoMac", typeof(MonoMacPlatform), nameof(MonoMacPlatform.Initialize))]

25
src/OSX/Avalonia.MonoMac/Avalonia.MonoMac.csproj

@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\Shared\SharedAssemblyInfo.cs" Link="SharedAssemblyInfo.cs" />
<Compile Include="..\..\Shared\WindowResizeDragHelper.cs" Link="WindowResizeDragHelper.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MonoMac.NetStandard" Version="0.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Avalonia.Animation\Avalonia.Animation.csproj" />
<ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" />
<ProjectReference Include="..\..\Avalonia.Interactivity\Avalonia.Interactivity.csproj" />
<ProjectReference Include="..\..\Avalonia.Layout\Avalonia.Layout.csproj" />
<ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
</ItemGroup>
<Import Project="..\..\..\build\MonoMac.props" />
</Project>

62
src/OSX/Avalonia.MonoMac/Cursor.cs

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using Avalonia.Input;
using Avalonia.Platform;
using MonoMac.AppKit;
namespace Avalonia.MonoMac
{
class Cursor : IPlatformHandle
{
public NSCursor Native { get; }
public IntPtr Handle => Native.Handle;
public string HandleDescriptor => "NSCursor";
public Cursor(NSCursor native)
{
Native = native;
}
}
class CursorFactoryStub : IStandardCursorFactory
{
Dictionary<StandardCursorType, NSCursor> _cache;
public CursorFactoryStub()
{
//TODO: Load diagonal cursors from webkit
//See https://stackoverflow.com/q/10733228
_cache = new Dictionary<StandardCursorType, NSCursor>()
{
[StandardCursorType.Arrow] = NSCursor.ArrowCursor,
[StandardCursorType.AppStarting] = NSCursor.ArrowCursor, //TODO
[StandardCursorType.BottomLeftCorner] = NSCursor.CrosshairCursor, //TODO
[StandardCursorType.BottomRightCorner] = NSCursor.CrosshairCursor, //TODO
[StandardCursorType.BottomSize] = NSCursor.ResizeDownCursor,
[StandardCursorType.Cross] = NSCursor.CrosshairCursor,
[StandardCursorType.Hand] = NSCursor.PointingHandCursor,
[StandardCursorType.Help] = NSCursor.ContextualMenuCursor,
[StandardCursorType.Ibeam] = NSCursor.IBeamCursor,
[StandardCursorType.LeftSide] = NSCursor.ResizeLeftCursor,
[StandardCursorType.No] = NSCursor.OperationNotAllowedCursor,
[StandardCursorType.RightSide] = NSCursor.ResizeRightCursor,
[StandardCursorType.SizeAll] = NSCursor.CrosshairCursor, //TODO
[StandardCursorType.SizeNorthSouth] = NSCursor.ResizeUpDownCursor,
[StandardCursorType.SizeWestEast] = NSCursor.ResizeLeftRightCursor,
[StandardCursorType.TopLeftCorner] = NSCursor.CrosshairCursor, //TODO
[StandardCursorType.TopRightCorner] = NSCursor.CrosshairCursor, //TODO
[StandardCursorType.TopSide] = NSCursor.ResizeUpCursor,
[StandardCursorType.UpArrow] = NSCursor.ResizeUpCursor,
[StandardCursorType.Wait] = NSCursor.ArrowCursor, //TODO
};
}
public IPlatformHandle GetCursor(StandardCursorType cursorType)
{
return new Cursor(_cache[cursorType]);
}
}
}

52
src/OSX/Avalonia.MonoMac/EmulatedFramebuffer.cs

@ -0,0 +1,52 @@
using System;
using Avalonia.Platform;
using MonoMac.AppKit;
using System.Runtime.InteropServices;
using MonoMac.CoreGraphics;
namespace Avalonia.MonoMac
{
class EmulatedFramebuffer : ILockedFramebuffer
{
private readonly CGSize _logicalSize;
public EmulatedFramebuffer(NSView view)
{
_logicalSize = view.Frame.Size;
var pixelSize = view.ConvertSizeToBacking(_logicalSize);
Width = (int)pixelSize.Width;
Height = (int)pixelSize.Height;
RowBytes = Width * 4;
Dpi = new Vector(96 * pixelSize.Width / _logicalSize.Width, 96 * pixelSize.Height / _logicalSize.Height);
Format = PixelFormat.Rgba8888;
Address = Marshal.AllocHGlobal(Height * RowBytes);
}
public void Dispose()
{
if (Address == IntPtr.Zero)
return;
var nfo = (int) CGBitmapFlags.ByteOrder32Big | (int) CGImageAlphaInfo.PremultipliedLast;
using (var colorSpace = CGColorSpace.CreateDeviceRGB())
using (var bContext = new CGBitmapContext(Address, Width, Height, 8, Width * 4,
colorSpace, (CGImageAlphaInfo) nfo))
using (var image = bContext.ToImage())
using (var nscontext = NSGraphicsContext.CurrentContext)
using (var context = nscontext.GraphicsPort)
{
context.SetFillColor(255, 255, 255, 255);
context.FillRect(new CGRect(default(CGPoint), _logicalSize));
context.DrawImage(new CGRect(default(CGPoint), _logicalSize), image);
}
Marshal.FreeHGlobal(Address);
Address = IntPtr.Zero;
}
public IntPtr Address { get; private set; }
public int Width { get; }
public int Height { get; }
public int RowBytes { get; }
public Vector Dpi { get; }
public PixelFormat Format { get; }
}
}

31
src/OSX/Avalonia.MonoMac/Helpers.cs

@ -0,0 +1,31 @@
using System;
using MonoMac.AppKit;
using MonoMac.CoreGraphics;
namespace Avalonia.MonoMac
{
static class Helpers
{
public static Point ToAvaloniaPoint(this CGPoint point) => new Point(point.X, point.Y);
public static CGPoint ToMonoMacPoint(this Point point) => new CGPoint(point.X, point.Y);
public static Size ToAvaloniaSize(this CGSize size) => new Size(size.Width, size.Height);
public static CGSize ToMonoMacSize(this Size size) => new CGSize(size.Width, size.Height);
public static Rect ToAvaloniaRect(this CGRect rect) => new Rect(rect.Left, rect.Top, rect.Width, rect.Height);
public static CGRect ToMonoMacRect(this Rect rect) => new CGRect(rect.X, rect.Y, rect.Width, rect.Height);
public static Point ConvertPointY(this Point pt)
{
var sw = NSScreen.Screens[0].Frame;
var t = Math.Max(sw.Top, sw.Bottom);
return pt.WithY(t - pt.Y);
}
public static CGPoint ConvertPointY(this CGPoint pt)
{
var sw = NSScreen.Screens[0].Frame;
var t = Math.Max(sw.Top, sw.Bottom);
return new CGPoint(pt.X, t - pt.Y);
}
}
}

267
src/OSX/Avalonia.MonoMac/KeyTransform.cs

@ -0,0 +1,267 @@
using System.Collections.Generic;
using Avalonia.Input;
namespace Avalonia.MonoMac
{
public static class KeyTransform
{
// See /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/HIToolbox.framework/Versions/A/Headers/Events.h
// ReSharper disable InconsistentNaming
// ReSharper disable UnusedMember.Local
private const int kVK_ANSI_A = 0x00;
private const int kVK_ANSI_S = 0x01;
private const int kVK_ANSI_D = 0x02;
private const int kVK_ANSI_F = 0x03;
private const int kVK_ANSI_H = 0x04;
private const int kVK_ANSI_G = 0x05;
private const int kVK_ANSI_Z = 0x06;
private const int kVK_ANSI_X = 0x07;
private const int kVK_ANSI_C = 0x08;
private const int kVK_ANSI_V = 0x09;
private const int kVK_ANSI_B = 0x0B;
private const int kVK_ANSI_Q = 0x0C;
private const int kVK_ANSI_W = 0x0D;
private const int kVK_ANSI_E = 0x0E;
private const int kVK_ANSI_R = 0x0F;
private const int kVK_ANSI_Y = 0x10;
private const int kVK_ANSI_T = 0x11;
private const int kVK_ANSI_1 = 0x12;
private const int kVK_ANSI_2 = 0x13;
private const int kVK_ANSI_3 = 0x14;
private const int kVK_ANSI_4 = 0x15;
private const int kVK_ANSI_6 = 0x16;
private const int kVK_ANSI_5 = 0x17;
private const int kVK_ANSI_Equal = 0x18;
private const int kVK_ANSI_9 = 0x19;
private const int kVK_ANSI_7 = 0x1A;
private const int kVK_ANSI_Minus = 0x1B;
private const int kVK_ANSI_8 = 0x1C;
private const int kVK_ANSI_0 = 0x1D;
private const int kVK_ANSI_RightBracket = 0x1E;
private const int kVK_ANSI_O = 0x1F;
private const int kVK_ANSI_U = 0x20;
private const int kVK_ANSI_LeftBracket = 0x21;
private const int kVK_ANSI_I = 0x22;
private const int kVK_ANSI_P = 0x23;
private const int kVK_ANSI_L = 0x25;
private const int kVK_ANSI_J = 0x26;
private const int kVK_ANSI_Quote = 0x27;
private const int kVK_ANSI_K = 0x28;
private const int kVK_ANSI_Semicolon = 0x29;
private const int kVK_ANSI_Backslash = 0x2A;
private const int kVK_ANSI_Comma = 0x2B;
private const int kVK_ANSI_Slash = 0x2C;
private const int kVK_ANSI_N = 0x2D;
private const int kVK_ANSI_M = 0x2E;
private const int kVK_ANSI_Period = 0x2F;
private const int kVK_ANSI_Grave = 0x32;
private const int kVK_ANSI_KeypadDecimal = 0x41;
private const int kVK_ANSI_KeypadMultiply = 0x43;
private const int kVK_ANSI_KeypadPlus = 0x45;
private const int kVK_ANSI_KeypadClear = 0x47;
private const int kVK_ANSI_KeypadDivide = 0x4B;
private const int kVK_ANSI_KeypadEnter = 0x4C;
private const int kVK_ANSI_KeypadMinus = 0x4E;
private const int kVK_ANSI_KeypadEquals = 0x51;
private const int kVK_ANSI_Keypad0 = 0x52;
private const int kVK_ANSI_Keypad1 = 0x53;
private const int kVK_ANSI_Keypad2 = 0x54;
private const int kVK_ANSI_Keypad3 = 0x55;
private const int kVK_ANSI_Keypad4 = 0x56;
private const int kVK_ANSI_Keypad5 = 0x57;
private const int kVK_ANSI_Keypad6 = 0x58;
private const int kVK_ANSI_Keypad7 = 0x59;
private const int kVK_ANSI_Keypad8 = 0x5B;
private const int kVK_ANSI_Keypad9 = 0x5C;
private const int kVK_Return = 0x24;
private const int kVK_Tab = 0x30;
private const int kVK_Space = 0x31;
private const int kVK_Delete = 0x33;
private const int kVK_Escape = 0x35;
private const int kVK_Command = 0x37;
private const int kVK_Shift = 0x38;
private const int kVK_CapsLock = 0x39;
private const int kVK_Option = 0x3A;
private const int kVK_Control = 0x3B;
private const int kVK_RightCommand = 0x36;
private const int kVK_RightShift = 0x3C;
private const int kVK_RightOption = 0x3D;
private const int kVK_RightControl = 0x3E;
private const int kVK_Function = 0x3F;
private const int kVK_F17 = 0x40;
private const int kVK_VolumeUp = 0x48;
private const int kVK_VolumeDown = 0x49;
private const int kVK_Mute = 0x4A;
private const int kVK_F18 = 0x4F;
private const int kVK_F19 = 0x50;
private const int kVK_F20 = 0x5A;
private const int kVK_F5 = 0x60;
private const int kVK_F6 = 0x61;
private const int kVK_F7 = 0x62;
private const int kVK_F3 = 0x63;
private const int kVK_F8 = 0x64;
private const int kVK_F9 = 0x65;
private const int kVK_F11 = 0x67;
private const int kVK_F13 = 0x69;
private const int kVK_F16 = 0x6A;
private const int kVK_F14 = 0x6B;
private const int kVK_F10 = 0x6D;
private const int kVK_F12 = 0x6F;
private const int kVK_F15 = 0x71;
private const int kVK_Help = 0x72;
private const int kVK_Home = 0x73;
private const int kVK_PageUp = 0x74;
private const int kVK_ForwardDelete = 0x75;
private const int kVK_F4 = 0x76;
private const int kVK_End = 0x77;
private const int kVK_F2 = 0x78;
private const int kVK_PageDown = 0x79;
private const int kVK_F1 = 0x7A;
private const int kVK_LeftArrow = 0x7B;
private const int kVK_RightArrow = 0x7C;
private const int kVK_DownArrow = 0x7D;
private const int kVK_UpArrow = 0x7E;
private const int kVK_ISO_Section = 0x0A;
private const int kVK_JIS_Yen = 0x5D;
private const int kVK_JIS_Underscore = 0x5E;
private const int kVK_JIS_KeypadComma = 0x5F;
private const int kVK_JIS_Eisu = 0x66;
private const int kVK_JIS_Kana = 0x68;
// ReSharper restore UnusedMember.Local
// ReSharper restore InconsistentNaming
//TODO: Map missing keys
static readonly Dictionary<int, Key> Keys = new Dictionary<int, Key>
{
[kVK_ANSI_A] = Key.A,
[kVK_ANSI_S] = Key.S,
[kVK_ANSI_D] = Key.D,
[kVK_ANSI_F] = Key.F,
[kVK_ANSI_H] = Key.H,
[kVK_ANSI_G] = Key.G,
[kVK_ANSI_Z] = Key.Z,
[kVK_ANSI_X] = Key.X,
[kVK_ANSI_C] = Key.C,
[kVK_ANSI_V] = Key.V,
[kVK_ANSI_B] = Key.B,
[kVK_ANSI_Q] = Key.Q,
[kVK_ANSI_W] = Key.W,
[kVK_ANSI_E] = Key.E,
[kVK_ANSI_R] = Key.R,
[kVK_ANSI_Y] = Key.Y,
[kVK_ANSI_T] = Key.T,
[kVK_ANSI_1] = Key.D1,
[kVK_ANSI_2] = Key.D2,
[kVK_ANSI_3] = Key.D3,
[kVK_ANSI_4] = Key.D4,
[kVK_ANSI_6] = Key.D6,
[kVK_ANSI_5] = Key.D5,
//[kVK_ANSI_Equal] = Key.?,
[kVK_ANSI_9] = Key.D9,
[kVK_ANSI_7] = Key.D7,
[kVK_ANSI_Minus] = Key.OemMinus,
[kVK_ANSI_8] = Key.D8,
[kVK_ANSI_0] = Key.D0,
[kVK_ANSI_RightBracket] = Key.OemCloseBrackets,
[kVK_ANSI_O] = Key.O,
[kVK_ANSI_U] = Key.U,
[kVK_ANSI_LeftBracket] = Key.OemOpenBrackets,
[kVK_ANSI_I] = Key.I,
[kVK_ANSI_P] = Key.P,
[kVK_ANSI_L] = Key.L,
[kVK_ANSI_J] = Key.J,
[kVK_ANSI_Quote] = Key.OemQuotes,
[kVK_ANSI_K] = Key.K,
[kVK_ANSI_Semicolon] = Key.OemSemicolon,
[kVK_ANSI_Backslash] = Key.OemBackslash,
[kVK_ANSI_Comma] = Key.OemComma,
//[kVK_ANSI_Slash] = Key.?,
[kVK_ANSI_N] = Key.N,
[kVK_ANSI_M] = Key.M,
[kVK_ANSI_Period] = Key.OemPeriod,
//[kVK_ANSI_Grave] = Key.?,
[kVK_ANSI_KeypadDecimal] = Key.Decimal,
[kVK_ANSI_KeypadMultiply] = Key.Multiply,
[kVK_ANSI_KeypadPlus] = Key.OemPlus,
[kVK_ANSI_KeypadClear] = Key.Clear,
[kVK_ANSI_KeypadDivide] = Key.Divide,
[kVK_ANSI_KeypadEnter] = Key.Enter,
[kVK_ANSI_KeypadMinus] = Key.OemMinus,
//[kVK_ANSI_KeypadEquals] = Key.?,
[kVK_ANSI_Keypad0] = Key.NumPad0,
[kVK_ANSI_Keypad1] = Key.NumPad1,
[kVK_ANSI_Keypad2] = Key.NumPad2,
[kVK_ANSI_Keypad3] = Key.NumPad3,
[kVK_ANSI_Keypad4] = Key.NumPad4,
[kVK_ANSI_Keypad5] = Key.NumPad5,
[kVK_ANSI_Keypad6] = Key.NumPad6,
[kVK_ANSI_Keypad7] = Key.NumPad7,
[kVK_ANSI_Keypad8] = Key.NumPad8,
[kVK_ANSI_Keypad9] = Key.NumPad9,
[kVK_Return] = Key.Return,
[kVK_Tab] = Key.Tab,
[kVK_Space] = Key.Space,
[kVK_Delete] = Key.Delete,
[kVK_Escape] = Key.Escape,
[kVK_Command] = Key.LWin,
[kVK_Shift] = Key.LeftShift,
[kVK_CapsLock] = Key.CapsLock,
[kVK_Option] = Key.LeftAlt,
[kVK_Control] = Key.LeftCtrl,
[kVK_RightCommand] = Key.RWin,
[kVK_RightShift] = Key.RightShift,
[kVK_RightOption] = Key.RightAlt,
[kVK_RightControl] = Key.RightCtrl,
//[kVK_Function] = Key.?,
[kVK_F17] = Key.F17,
[kVK_VolumeUp] = Key.VolumeUp,
[kVK_VolumeDown] = Key.VolumeDown,
[kVK_Mute] = Key.VolumeMute,
[kVK_F18] = Key.F18,
[kVK_F19] = Key.F19,
[kVK_F20] = Key.F20,
[kVK_F5] = Key.F5,
[kVK_F6] = Key.F6,
[kVK_F7] = Key.F7,
[kVK_F3] = Key.F3,
[kVK_F8] = Key.F8,
[kVK_F9] = Key.F9,
[kVK_F11] = Key.F11,
[kVK_F13] = Key.F13,
[kVK_F16] = Key.F16,
[kVK_F14] = Key.F14,
[kVK_F10] = Key.F10,
[kVK_F12] = Key.F12,
[kVK_F15] = Key.F15,
[kVK_Help] = Key.Help,
[kVK_Home] = Key.Home,
[kVK_PageUp] = Key.PageUp,
[kVK_ForwardDelete] = Key.Delete,
[kVK_F4] = Key.F4,
[kVK_End] = Key.End,
[kVK_F2] = Key.F2,
[kVK_PageDown] = Key.PageDown,
[kVK_F1] = Key.F1,
[kVK_LeftArrow] = Key.Left,
[kVK_RightArrow] = Key.Right,
[kVK_DownArrow] = Key.Down,
[kVK_UpArrow] = Key.Up,
/*
[kVK_ISO_Section] = Key.?,
[kVK_JIS_Yen] = Key.?,
[kVK_JIS_Underscore] = Key.?,
[kVK_JIS_KeypadComma] = Key.?,
[kVK_JIS_Eisu] = Key.?,
[kVK_JIS_Kana] = Key.?
*/
};
public static Key? TransformKeyCode(ushort code)
{
Key rv;
if (Keys.TryGetValue(code, out rv))
return rv;
return null;
}
}
}

75
src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs

@ -0,0 +1,75 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Platform;
using Avalonia.Rendering;
using MonoMac.AppKit;
namespace Avalonia.MonoMac
{
public class MonoMacPlatform : IWindowingPlatform, IPlatformSettings
{
internal static MonoMacPlatform Instance { get; private set; }
internal readonly MouseDevice MouseDevice = new MouseDevice();
readonly KeyboardDevice _keyboardDevice = new KeyboardDevice();
internal static NSApplication App;
void DoInitialize()
{
AvaloniaLocator.CurrentMutable
.Bind<IStandardCursorFactory>().ToTransient<CursorFactoryStub>()
.Bind<IPlatformIconLoader>().ToSingleton<IconLoader>()
.Bind<IKeyboardDevice>().ToConstant(_keyboardDevice)
.Bind<IMouseDevice>().ToConstant(MouseDevice)
.Bind<IPlatformSettings>().ToConstant(this)
.Bind<IWindowingPlatform>().ToConstant(this)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsImpl>()
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance);
InitializeCocoaApp();
}
public static void Initialize()
{
Instance = new MonoMacPlatform();
Instance.DoInitialize();
}
void InitializeCocoaApp()
{
NSApplication.Init();
App = NSApplication.SharedApplication;
App.ActivationPolicy = NSApplicationActivationPolicy.Regular;
}
public Size DoubleClickSize => new Size(4, 4);
public TimeSpan DoubleClickTime => TimeSpan.FromSeconds(NSEvent.DoubleClickInterval);
public IWindowImpl CreateWindow() => new WindowImpl();
public IEmbeddableWindowImpl CreateEmbeddableWindow()
{
throw new PlatformNotSupportedException();
}
public IPopupImpl CreatePopup()
{
return new PopupImpl();
}
}
}
namespace Avalonia
{
public static class MonoMacPlatformExtensions
{
public static T UseMonoMac<T>(this T builder) where T : AppBuilderBase<T>, new()
{
return builder.UseWindowingSubsystem(MonoMac.MonoMacPlatform.Initialize);
}
}
}

63
src/OSX/Avalonia.MonoMac/PlatformThreadingInterface.cs

@ -0,0 +1,63 @@
using System;
using System.Threading;
using Avalonia.Platform;
using MonoMac.AppKit;
using MonoMac.CoreGraphics;
using MonoMac.Foundation;
namespace Avalonia.MonoMac
{
class PlatformThreadingInterface : IPlatformThreadingInterface
{
private bool _signaled;
public static PlatformThreadingInterface Instance { get; } = new PlatformThreadingInterface();
public bool CurrentThreadIsLoopThread => NSThread.Current.IsMainThread;
public event Action Signaled;
public IDisposable StartTimer(TimeSpan interval, Action tick)
=> NSTimer.CreateRepeatingScheduledTimer(interval, () => tick());
public void Signal()
{
lock (this)
{
if (_signaled)
return;
_signaled = true;
}
NSApplication.SharedApplication.BeginInvokeOnMainThread(() =>
{
lock (this)
{
if (!_signaled)
return;
_signaled = false;
}
Signaled?.Invoke();
});
}
public void RunLoop(CancellationToken cancellationToken)
{
NSApplication.SharedApplication.ActivateIgnoringOtherApps(true);
var app = NSApplication.SharedApplication;
cancellationToken.Register(() =>
{
app.PostEvent(NSEvent.OtherEvent(NSEventType.ApplicationDefined, default(CGPoint),
default(NSEventModifierMask), 0, 0, null, 0, 0, 0), true);
});
while (!cancellationToken.IsCancellationRequested)
{
var ev = app.NextEvent(NSEventMask.AnyEvent, NSDate.DistantFuture, NSRunLoop.NSDefaultRunLoopMode, true);
if (ev != null)
{
app.SendEvent(ev);
ev.Dispose();
}
}
}
}
}

18
src/OSX/Avalonia.MonoMac/PopupImpl.cs

@ -0,0 +1,18 @@
using Avalonia.Platform;
using MonoMac.AppKit;
namespace Avalonia.MonoMac
{
class PopupImpl : WindowBaseImpl, IPopupImpl
{
public PopupImpl()
{
UpdateStyle();
}
protected override NSWindowStyle GetStyle()
{
return NSWindowStyle.Borderless;
}
}
}

47
src/OSX/Avalonia.MonoMac/Stubs.cs

@ -0,0 +1,47 @@
using System.IO;
using Avalonia.Platform;
namespace Avalonia.MonoMac
{
// OSX doesn't have a concept of *window* icon.
// Icons in the title bar are only shown if there is
// an opened file (on disk) associated with the current window
// see http://stackoverflow.com/a/7038671/2231814
class IconLoader : IPlatformIconLoader
{
class IconStub : IWindowIconImpl
{
private readonly IBitmapImpl _bitmap;
public IconStub(IBitmapImpl bitmap)
{
_bitmap = bitmap;
}
public void Save(Stream outputStream)
{
_bitmap.Save(outputStream);
}
}
public IWindowIconImpl LoadIcon(string fileName)
{
return new IconStub(
AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().LoadBitmap(fileName));
}
public IWindowIconImpl LoadIcon(Stream stream)
{
return new IconStub(
AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().LoadBitmap(stream));
}
public IWindowIconImpl LoadIcon(IBitmapImpl bitmap)
{
var ms = new MemoryStream();
bitmap.Save(ms);
ms.Seek(0, SeekOrigin.Begin);
return LoadIcon(ms);
}
}
}

91
src/OSX/Avalonia.MonoMac/SystemDialogsImpl.cs

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using MonoMac.AppKit;
namespace Avalonia.MonoMac
{
class SystemDialogsImpl : ISystemDialogImpl
{
Task<string[]> RunPanel(NSSavePanel panel, IWindowImpl parent)
{
var keyWindow = MonoMacPlatform.App.KeyWindow;
var tcs = new TaskCompletionSource<string[]>();
void OnComplete(int result)
{
if (result == 0)
tcs.SetResult(null);
else
{
if (panel is NSOpenPanel openPanel)
tcs.SetResult(openPanel.Filenames);
else
tcs.SetResult(new[] { panel.Filename });
}
panel.OrderOut(panel);
keyWindow?.MakeKeyAndOrderFront(keyWindow);
MonoMacPlatform.App.ActivateIgnoringOtherApps(true);
panel.Dispose();
}
if (parent != null)
{
var window = (WindowImpl)parent;
panel.BeginSheet(window.Window, OnComplete);
}
else
panel.Begin(OnComplete);
return tcs.Task;
}
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
{
/* NOTES
* DefaultFileExtension is not supported
* Named filters are not supported
*/
NSSavePanel panel;
if (dialog is OpenFileDialog openDialog)
{
var openPanel = new NSOpenPanel();
panel = openPanel;
openPanel.AllowsMultipleSelection = openDialog.AllowMultiple;
}
else
panel = new NSSavePanel();
panel.Title = panel.Title;
if (dialog.InitialDirectory != null)
panel.Directory = dialog.InitialDirectory;
if (dialog.InitialFileName != null)
panel.NameFieldStringValue = dialog.InitialFileName;
if (dialog.Filters?.Count > 0)
panel.AllowedFileTypes = dialog.Filters.SelectMany(f => f.Extensions).Distinct().ToArray();
return RunPanel(panel, parent);
}
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
{
var panel = new NSOpenPanel
{
Title = dialog.Title,
CanChooseDirectories = true,
CanCreateDirectories = true,
CanChooseFiles = false
};
if (dialog.DefaultDirectory != null)
panel.Directory = dialog.DefaultDirectory;
return (await RunPanel(panel, parent))?.FirstOrDefault();
}
}
}

365
src/OSX/Avalonia.MonoMac/TopLevelImpl.cs

@ -0,0 +1,365 @@
using System;
using System.Collections.Generic;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Rendering;
using MonoMac.AppKit;
using MonoMac.CoreGraphics;
using MonoMac.Foundation;
using MonoMac.ObjCRuntime;
namespace Avalonia.MonoMac
{
abstract class TopLevelImpl : ITopLevelImpl, IFramebufferPlatformSurface
{
public TopLevelView View { get; }
private readonly IMouseDevice _mouse = AvaloniaLocator.Current.GetService<IMouseDevice>();
protected TopLevelImpl()
{
View = new TopLevelView(this);
}
protected virtual void OnInput(RawInputEventArgs args)
{
Input?.Invoke(args);
}
[Adopts("NSTextInputClient")]
public class TopLevelView : NSView
{
TopLevelImpl _tl;
bool _isLeftPressed, _isRightPressed, _isMiddlePressed;
private readonly IMouseDevice _mouse;
private readonly IKeyboardDevice _keyboard;
private NSTrackingArea _area;
private NSCursor _cursor;
public TopLevelView(TopLevelImpl tl)
{
_tl = tl;
_mouse = AvaloniaLocator.Current.GetService<IMouseDevice>();
_keyboard = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
}
public override bool ConformsToProtocol(IntPtr protocol)
{
var rv = base.ConformsToProtocol(protocol);
return rv;
}
public override void DrawRect(CGRect dirtyRect)
{
_tl.Paint?.Invoke(dirtyRect.ToAvaloniaRect());
}
[Export("viewDidChangeBackingProperties:")]
public void ViewDidChangeBackingProperties()
{
_tl?.ScalingChanged?.Invoke(_tl.Scaling);
}
void UpdateCursor()
{
ResetCursorRects();
if (_cursor != null)
AddCursorRect(Frame, _cursor);
}
static readonly NSCursor ArrowCursor = NSCursor.ArrowCursor;
public void SetCursor(NSCursor cursor)
{
_cursor = cursor ?? ArrowCursor;
UpdateCursor();
}
public override void SetFrameSize(CGSize newSize)
{
base.SetFrameSize(newSize);
if (_area != null)
{
RemoveTrackingArea(_area);
_area.Dispose();
}
_area = new NSTrackingArea(new CGRect(default(CGPoint), newSize),
NSTrackingAreaOptions.ActiveAlways |
NSTrackingAreaOptions.MouseMoved |
NSTrackingAreaOptions.EnabledDuringMouseDrag, this, null);
AddTrackingArea(_area);
UpdateCursor();
_tl?.Resized?.Invoke(_tl.ClientSize);
}
InputModifiers GetModifiers(NSEventModifierMask mod)
{
var rv = new InputModifiers();
if (mod.HasFlag(NSEventModifierMask.ControlKeyMask))
rv |= InputModifiers.Control;
if (mod.HasFlag(NSEventModifierMask.ShiftKeyMask))
rv |= InputModifiers.Shift;
if (mod.HasFlag(NSEventModifierMask.AlternateKeyMask))
rv |= InputModifiers.Alt;
if (mod.HasFlag(NSEventModifierMask.CommandKeyMask))
rv |= InputModifiers.Windows;
if (_isLeftPressed)
rv |= InputModifiers.LeftMouseButton;
if (_isMiddlePressed)
rv |= InputModifiers.MiddleMouseButton;
if (_isRightPressed)
rv |= InputModifiers.RightMouseButton;
return rv;
}
public Point TranslateLocalPoint(Point pt) => pt.WithY(Bounds.Height - pt.Y);
Vector GetDelta(NSEvent ev)
{
var rv = new Vector(ev.ScrollingDeltaX, ev.ScrollingDeltaY);
//TODO: Verify if handling of HasPreciseScrollingDeltas
// is required (touchpad or magic-mouse is needed)
return rv;
}
uint GetTimeStamp(NSEvent ev) => (uint) (ev.Timestamp * 1000);
void MouseEvent(NSEvent ev, RawMouseEventType type)
{
BecomeFirstResponder();
var loc = TranslateLocalPoint(ConvertPointToView(ev.LocationInWindow, this).ToAvaloniaPoint());
var ts = GetTimeStamp(ev);
var mod = GetModifiers(ev.ModifierFlags);
if (type == RawMouseEventType.Wheel)
{
var delta = GetDelta(ev);
// ReSharper disable CompareOfFloatsByEqualityOperator
if (delta.X == 0 && delta.Y == 0)
return;
// ReSharper restore CompareOfFloatsByEqualityOperator
_tl.OnInput(new RawMouseWheelEventArgs(_mouse, ts, _tl.InputRoot, loc,
delta, mod));
}
else
_tl.OnInput(new RawMouseEventArgs(_mouse, ts, _tl.InputRoot, type, loc, mod));
}
public override void MouseMoved(NSEvent theEvent)
{
MouseEvent(theEvent, RawMouseEventType.Move);
base.MouseMoved(theEvent);
}
public override void MouseDragged(NSEvent theEvent)
{
MouseEvent(theEvent, RawMouseEventType.Move);
base.MouseDragged(theEvent);
}
public override void OtherMouseDragged(NSEvent theEvent)
{
MouseEvent(theEvent, RawMouseEventType.Move);
base.OtherMouseDragged(theEvent);
}
public override void RightMouseDragged(NSEvent theEvent)
{
MouseEvent(theEvent, RawMouseEventType.Move);
base.RightMouseDragged(theEvent);
}
public NSEvent LastMouseDownEvent { get; private set; }
public override void MouseDown(NSEvent theEvent)
{
_isLeftPressed = true;
LastMouseDownEvent = theEvent;
MouseEvent(theEvent, RawMouseEventType.LeftButtonDown);
LastMouseDownEvent = null;
base.MouseDown(theEvent);
}
public override void RightMouseDown(NSEvent theEvent)
{
_isRightPressed = true;
MouseEvent(theEvent, RawMouseEventType.RightButtonDown);
base.RightMouseDown(theEvent);
}
public override void OtherMouseDown(NSEvent theEvent)
{
_isMiddlePressed = true;
MouseEvent(theEvent, RawMouseEventType.MiddleButtonDown);
base.OtherMouseDown(theEvent);
}
public override void MouseUp(NSEvent theEvent)
{
_isLeftPressed = false;
MouseEvent(theEvent, RawMouseEventType.LeftButtonUp);
base.MouseUp(theEvent);
}
public override void RightMouseUp(NSEvent theEvent)
{
_isRightPressed = false;
MouseEvent(theEvent, RawMouseEventType.RightButtonUp);
base.RightMouseUp(theEvent);
}
public override void OtherMouseUp(NSEvent theEvent)
{
_isMiddlePressed = false;
MouseEvent(theEvent, RawMouseEventType.MiddleButtonUp);
base.OtherMouseUp(theEvent);
}
public override void ScrollWheel(NSEvent theEvent)
{
MouseEvent(theEvent, RawMouseEventType.Wheel);
base.ScrollWheel(theEvent);
}
public override void MouseExited(NSEvent theEvent)
{
MouseEvent(theEvent, RawMouseEventType.LeaveWindow);
base.MouseExited(theEvent);
}
void KeyboardEvent(RawKeyEventType type, NSEvent ev)
{
var code = KeyTransform.TransformKeyCode(ev.KeyCode);
if (!code.HasValue)
return;
_tl.OnInput(new RawKeyEventArgs(_keyboard, GetTimeStamp(ev),
type, code.Value, GetModifiers(ev.ModifierFlags)));
}
public override void KeyDown(NSEvent theEvent)
{
KeyboardEvent(RawKeyEventType.KeyDown, theEvent);
InputContext.HandleEvent(theEvent);
base.KeyDown(theEvent);
}
public override void KeyUp(NSEvent theEvent)
{
KeyboardEvent(RawKeyEventType.KeyUp, theEvent);
base.KeyUp(theEvent);
}
#region NSTextInputClient
public override bool AcceptsFirstResponder() => true;
public bool HasMarkedText
{
[Export("hasMarkedText")] get => false;
}
public NSRange MarkedRange
{
[Export("markedRange")] get => new NSRange(NSRange.NotFound, 0);
}
public NSRange SelectedRange
{
[Export("selectedRange")] get => new NSRange(NSRange.NotFound, 0);
}
[Export("setMarkedText:selectedRange:replacementRange:")]
public void SetMarkedText(NSString str, NSRange a1, NSRange a2)
{
}
[Export("unmarkText")]
public void UnmarkText()
{
}
public NSArray ValidAttributesForMarkedText
{
[Export("validAttributesForMarkedText")] get => new NSArray();
}
[Export("attributedSubstringForProposedRange:actualRange:")]
public NSAttributedString AttributedSubstringForProposedRange(NSRange range, IntPtr wat)
{
return new NSAttributedString("");
}
[Export("insertText:replacementRange:")]
public void InsertText(NSString str, NSRange range)
{
//TODO: timestamp
_tl.OnInput(new RawTextInputEventArgs(_keyboard, 0, str.ToString()));
}
[Export("characterIndexForPoint:")]
public uint CharacterIndexForPoint(CGPoint pt)
{
return 0;
}
[Export("firstRectForCharacterRange:actualRange:")]
public CGRect FirstRectForCharacterRange(NSRange range, IntPtr wat)
{
return new CGRect();
}
#endregion
}
public IInputRoot InputRoot { get; private set; }
public abstract Size ClientSize { get; }
public double Scaling
{
get
{
if (View.Window == null)
return 1;
return View.Window.BackingScaleFactor;
}
}
public IEnumerable<object> Surfaces => new[] {this};
public IMouseDevice MouseDevice => _mouse;
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<double> ScalingChanged { get; set; }
public Action Closed { get; set; }
public virtual void Dispose()
{
Closed?.Invoke();
Closed = null;
View.Dispose();
}
public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root);
public void Invalidate(Rect rect) => View.SetNeedsDisplayInRect(View.Frame);
public abstract Point PointToClient(Point point);
public abstract Point PointToScreen(Point point);
public void SetCursor(IPlatformHandle cursor) => View.SetCursor((cursor as Cursor)?.Native);
public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot;
public ILockedFramebuffer Lock() => new EmulatedFramebuffer(View);
}
}

179
src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs

@ -0,0 +1,179 @@
using System;
using Avalonia.Controls;
using Avalonia.Input.Raw;
using Avalonia.Platform;
using MonoMac.AppKit;
using MonoMac.CoreGraphics;
using MonoMac.ObjCRuntime;
namespace Avalonia.MonoMac
{
class WindowBaseImpl : TopLevelImpl, IWindowBaseImpl
{
private readonly ManagedWindowResizeDragHelper _managedDrag;
public CustomWindow Window { get; private set; }
public WindowBaseImpl()
{
_managedDrag = new ManagedWindowResizeDragHelper(this, _ => { }, ResizeForManagedDrag);
Window = new CustomWindow(this)
{
StyleMask = NSWindowStyle.Titled,
BackingType = NSBackingStore.Buffered,
ContentView = View,
// ReSharper disable once VirtualMemberCallInConstructor
Delegate = CreateWindowDelegate()
};
}
public class CustomWindow : NSWindow
{
readonly WindowBaseImpl _impl;
public CustomWindow(WindowBaseImpl impl)
{
_impl = impl;
}
public override void BecomeKeyWindow()
{
_impl.Activated?.Invoke();
base.BecomeKeyWindow();
}
public override void ResignKeyWindow()
{
_impl.Deactivated?.Invoke();
base.ResignKeyWindow();
}
private bool _canBecomeKeyAndMain;
public override bool CanBecomeKeyWindow => _canBecomeKeyAndMain;
public override bool CanBecomeMainWindow => _canBecomeKeyAndMain;
public void SetCanBecomeKeyAndMain() => _canBecomeKeyAndMain = true;
}
protected virtual NSWindowDelegate CreateWindowDelegate() => new WindowBaseDelegate(this);
public class WindowBaseDelegate : NSWindowDelegate
{
readonly WindowBaseImpl _impl;
public WindowBaseDelegate(WindowBaseImpl impl)
{
_impl = impl;
}
public override void DidMoved(global::MonoMac.Foundation.NSNotification notification)
{
_impl.PositionChanged?.Invoke(_impl.Position);
}
public override void WillClose(global::MonoMac.Foundation.NSNotification notification)
{
_impl.Window.Dispose();
_impl.Window = null;
_impl.Dispose();
}
public override CGRect WillUseStandardFrame(NSWindow window, CGRect newFrame)
{
if (_impl is WindowImpl w && w.UndecoratedIsMaximized && w.UndecoratedLastUnmaximizedFrame.HasValue)
return w.UndecoratedLastUnmaximizedFrame.Value;
return window.Screen.VisibleFrame;
}
public override bool ShouldZoom(NSWindow window, CGRect newFrame)
{
return true;
}
}
public Point Position
{
get => Window.Frame.ToAvaloniaRect().BottomLeft.ConvertPointY();
set => Window.SetFrameTopLeftPoint(value.ToMonoMacPoint().ConvertPointY());
}
protected virtual NSWindowStyle GetStyle() => NSWindowStyle.Borderless;
protected void UpdateStyle() => Window.StyleMask = GetStyle();
IPlatformHandle IWindowBaseImpl.Handle => new PlatformHandle(Window.Handle, "NSWindow");
public Size MaxClientSize => NSScreen.Screens[0].Frame.ToAvaloniaRect().Size;
public Action<Point> PositionChanged { get; set; }
public Action Deactivated { get; set; }
public Action Activated { get; set; }
public override Size ClientSize => Window.ContentRectFor(Window.Frame).Size.ToAvaloniaSize();
public void Show() => Window.MakeKeyAndOrderFront(Window);
public void Hide() => Window?.OrderOut(Window);
public void BeginMoveDrag()
{
var ev = View.LastMouseDownEvent;
if (ev == null)
return;
var handle = Selector.GetHandle("performWindowDragWithEvent:");
Messaging.void_objc_msgSend_IntPtr(Window.Handle, handle, ev.Handle);
}
public void BeginResizeDrag(WindowEdge edge)
{
var screenPoint = NSEvent.CurrentMouseLocation.ConvertPointY().ToAvaloniaPoint();
_managedDrag.BeginResizeDrag(edge, PointToClient(screenPoint));
}
protected override void OnInput(RawInputEventArgs args)
{
if (_managedDrag.PreprocessInputEvent(ref args))
return;
base.OnInput(args);
}
public void Activate() => Window.MakeKeyWindow();
public void ResizeForManagedDrag(Rect rc)
{
var frame = new CGRect(rc.X, rc.Position.ConvertPointY().Y - rc.Height, rc.Width, rc.Height);
Window.SetFrame(frame, true);
}
public void Resize(Size clientSize)
{
var pos = Position;
Window.SetContentSize(clientSize.ToMonoMacSize());
Position = pos;
}
public override Point PointToClient(Point point)
{
var cocoaScreenPoint = point.ToMonoMacPoint().ConvertPointY();
var cocoaViewPoint = Window.ConvertScreenToBase(cocoaScreenPoint).ToAvaloniaPoint();
return View.TranslateLocalPoint(cocoaViewPoint);
}
public override Point PointToScreen(Point point)
{
var cocoaViewPoint = View.TranslateLocalPoint(point).ToMonoMacPoint();
var cocoaScreenPoint = Window.ConvertBaseToScreen(cocoaViewPoint);
return cocoaScreenPoint.ConvertPointY().ToAvaloniaPoint();
}
public override void Dispose()
{
Window?.Close();
Window?.Dispose();
base.Dispose();
}
}
}

116
src/OSX/Avalonia.MonoMac/WindowImpl.cs

@ -0,0 +1,116 @@
using System;
using Avalonia.Controls;
using Avalonia.Platform;
using MonoMac.AppKit;
using MonoMac.CoreGraphics;
namespace Avalonia.MonoMac
{
class WindowImpl : WindowBaseImpl, IWindowImpl
{
public bool IsDecorated = true;
public CGRect? UndecoratedLastUnmaximizedFrame;
public WindowImpl()
{
UpdateStyle();
Window.SetCanBecomeKeyAndMain();
}
public WindowState WindowState
{
get
{
if (Window.IsMiniaturized)
return WindowState.Minimized;
if (IsZoomed)
return WindowState.Maximized;
return WindowState.Normal;
}
set
{
if (value == WindowState.Maximized)
{
if (Window.IsMiniaturized)
Window.Deminiaturize(Window);
if (!IsZoomed)
DoZoom();
}
else if (value.HasFlag(WindowState.Minimized))
Window.Miniaturize(Window);
else
{
if (Window.IsMiniaturized)
Window.Deminiaturize(Window);
if (IsZoomed)
DoZoom();
}
}
}
bool IsZoomed => IsDecorated ? Window.IsZoomed : UndecoratedIsMaximized;
public bool UndecoratedIsMaximized => Window.Frame == Window.Screen.VisibleFrame;
void DoZoom()
{
if (IsDecorated)
Window.PerformZoom(Window);
else
{
if (!UndecoratedIsMaximized)
UndecoratedLastUnmaximizedFrame = Window.Frame;
Window.Zoom(Window);
}
}
public void SetIcon(IWindowIconImpl icon)
{
//No-OP, see http://stackoverflow.com/a/7038671/2231814
}
public void ShowTaskbarIcon(bool value)
{
//No-OP, there is no such this as taskbar in OSX
}
protected override NSWindowStyle GetStyle()
{
if (IsDecorated)
return NSWindowStyle.Closable | NSWindowStyle.Resizable | NSWindowStyle.Miniaturizable |
NSWindowStyle.Titled;
return NSWindowStyle.Borderless;
}
public void SetSystemDecorations(bool enabled)
{
IsDecorated = enabled;
UpdateStyle();
}
public void SetTitle(string title) => Window.Title = title;
class ModalDisposable : IDisposable
{
readonly WindowImpl _impl;
public ModalDisposable(WindowImpl impl)
{
_impl = impl;
}
public void Dispose()
{
_impl.Window.OrderOut(_impl.Window);
}
}
public IDisposable ShowDialog()
{
//TODO: Investigate how to return immediately.
// May be add some magic to our run loop or something
NSApplication.SharedApplication.RunModalForWindow(Window);
return new ModalDisposable(this);
}
}
}

85
src/Shared/WindowResizeDragHelper.cs

@ -0,0 +1,85 @@
using System;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
namespace Avalonia
{
internal class ManagedWindowResizeDragHelper
{
private readonly IWindowBaseImpl _window;
private readonly Action<bool> _captureMouse;
private readonly Action<Rect> _resize;
private WindowEdge? _edge;
private Point _prevPoint;
public ManagedWindowResizeDragHelper(IWindowBaseImpl window, Action<bool> captureMouse, Action<Rect> resize = null)
{
_window = window;
_captureMouse = captureMouse;
_resize = resize;
}
public void BeginResizeDrag(WindowEdge edge, Point currentMousePosition)
{
_captureMouse(true);
_prevPoint = currentMousePosition;
_edge = edge;
}
public bool PreprocessInputEvent(ref RawInputEventArgs e)
{
if (_edge == null)
return false;
if (e is RawMouseEventArgs args)
{
if (args.Type == RawMouseEventType.LeftButtonUp)
{
_edge = null;
_captureMouse(false);
}
if (args.Type == RawMouseEventType.Move)
{
MoveWindow(args.Position);
return true;
}
_edge = null;
}
return false;
}
private void MoveWindow(Point position)
{
var diff = position - _prevPoint;
var edge = _edge.Value;
var rc = new Rect(_window.Position, _window.ClientSize);
if (edge == WindowEdge.East || edge == WindowEdge.NorthEast || edge == WindowEdge.SouthEast)
{
rc = rc.WithWidth(rc.Width + diff.X);
_prevPoint = _prevPoint.WithX(position.X);
}
if (edge == WindowEdge.West || edge == WindowEdge.NorthWest || edge == WindowEdge.SouthWest)
rc = rc.WithX(rc.X + diff.X).WithWidth(rc.Width - diff.X);
if (edge == WindowEdge.South || edge == WindowEdge.SouthWest || edge == WindowEdge.SouthEast)
{
rc = rc.WithHeight(rc.Height + diff.Y);
_prevPoint = _prevPoint.WithY(position.Y);
}
if (edge == WindowEdge.North || edge == WindowEdge.NorthWest || edge == WindowEdge.NorthEast)
rc = rc.WithY(rc.Y + diff.Y).WithHeight(rc.Height - diff.Y);
if (_resize != null)
_resize(rc);
else
{
if (_window.Position != rc.Position)
_window.Position = rc.Position;
if (_window.ClientSize != rc.Size)
_window.Resize(rc.Size);
}
}
}
}

1
src/Windows/Avalonia.Win32/Avalonia.Win32.Shared.projitems

@ -24,6 +24,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Win32Platform.cs" />
<Compile Include="$(MSBuildThisFileDirectory)WindowFramebuffer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)WindowImpl.cs" />
<Compile Include="$(MSBuildThisFileDirectory)..\..\Shared\WindowResizeDragHelper.cs" />
<Compile Include="..\..\Shared\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>

21
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -38,9 +38,21 @@ namespace Avalonia.Win32
private double _scaling = 1;
private WindowState _showWindowState;
private FramebufferManager _framebuffer;
#if USE_MANAGED_DRAG
private readonly ManagedWindowResizeDragHelper _managedDrag;
#endif
public WindowImpl()
{
#if USE_MANAGED_DRAG
_managedDrag = new ManagedWindowResizeDragHelper(this, capture =>
{
if (capture)
UnmanagedMethods.SetCapture(Handle.Handle);
else
UnmanagedMethods.ReleaseCapture();
});
#endif
CreateWindow();
_framebuffer = new FramebufferManager(_hwnd);
s_instances.Add(this);
@ -327,8 +339,12 @@ namespace Avalonia.Win32
public void BeginResizeDrag(WindowEdge edge)
{
#if USE_MANAGED_DRAG
_managedDrag.BeginResizeDrag(edge, ScreenToClient(MouseDevice.Position));
#else
UnmanagedMethods.DefWindowProc(_hwnd, (int)UnmanagedMethods.WindowsMessage.WM_NCLBUTTONDOWN,
new IntPtr((int)EdgeDic[edge]), IntPtr.Zero);
#endif
}
public Point Position
@ -583,6 +599,11 @@ namespace Avalonia.Win32
PositionChanged?.Invoke(new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)));
return IntPtr.Zero;
}
#if USE_MANAGED_DRAG
if (_managedDrag.PreprocessInputEvent(ref e))
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
#endif
if (e != null && Input != null)
{

Loading…
Cancel
Save