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