committed by
GitHub
32 changed files with 1778 additions and 13 deletions
@ -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> |
|||
@ -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> |
|||
@ -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(); |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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))] |
|||
|
|||
@ -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> |
|||
@ -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]); |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
|
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue