diff --git a/src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj b/src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj index c745ab0192..656e07855e 100644 --- a/src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj +++ b/src/Avalonia.Native.OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 37986100214DA1A900CD0246 /* KeyTransform.mm in Sources */ = {isa = PBXBuildFile; fileRef = 379860FF214DA1A900CD0246 /* KeyTransform.mm */; }; + 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; }; AB00E4F72147CA920032A60A /* main.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB00E4F62147CA920032A60A /* main.mm */; }; AB661C1E2148230F00291242 /* AppKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AB661C1D2148230F00291242 /* AppKit.framework */; }; AB661C202148286E00291242 /* window.mm in Sources */ = {isa = PBXBuildFile; fileRef = AB661C1F2148286E00291242 /* window.mm */; }; @@ -18,6 +18,8 @@ 379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = ""; }; 379860FF214DA1A900CD0246 /* KeyTransform.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = KeyTransform.mm; sourceTree = ""; }; 379A4506214D0F6500CC143D /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../headers; sourceTree = ""; }; + 37C09D8721580FE4006A6758 /* SystemDialogs.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SystemDialogs.mm; sourceTree = ""; }; + 37C09D8A21581EF2006A6758 /* window.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = window.h; sourceTree = ""; }; AB00E4F62147CA920032A60A /* main.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = main.mm; sourceTree = ""; }; AB661C1D2148230F00291242 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; AB661C1F2148286E00291242 /* window.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = window.mm; sourceTree = ""; }; @@ -55,7 +57,9 @@ 379860FE214DA0C000CD0246 /* KeyTransform.h */, 379860FF214DA1A900CD0246 /* KeyTransform.mm */, AB661C1F2148286E00291242 /* window.mm */, + 37C09D8A21581EF2006A6758 /* window.h */, AB00E4F62147CA920032A60A /* main.mm */, + 37C09D8721580FE4006A6758 /* SystemDialogs.mm */, AB7A61F02147C815003C5833 /* Products */, AB661C1C2148230E00291242 /* Frameworks */, ); @@ -138,7 +142,7 @@ files = ( AB8F7D6B21482D7F0057DBA5 /* platformthreading.mm in Sources */, AB00E4F72147CA920032A60A /* main.mm in Sources */, - 37986100214DA1A900CD0246 /* KeyTransform.mm in Sources */, + 37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */, AB661C202148286E00291242 /* window.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/src/Avalonia.Native.OSX/SystemDialogs.mm b/src/Avalonia.Native.OSX/SystemDialogs.mm new file mode 100644 index 0000000000..3f508ca8f7 --- /dev/null +++ b/src/Avalonia.Native.OSX/SystemDialogs.mm @@ -0,0 +1,257 @@ +#include "common.h" +#include "window.h" + +class SystemDialogs : public ComSingleObject +{ + virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle, + IAvnSystemDialogEvents* events, + const char* title, + const char* initialDirectory) + { + @autoreleasepool + { + auto panel = [NSOpenPanel openPanel]; + + panel.canChooseDirectories = true; + panel.canCreateDirectories = true; + panel.canChooseFiles = false; + + if(title != nullptr) + { + panel.title = [NSString stringWithUTF8String:title]; + } + + if(initialDirectory != nullptr) + { + auto directoryString = [NSString stringWithUTF8String:initialDirectory]; + panel.directoryURL = [NSURL fileURLWithPath:directoryString]; + } + + auto handler = ^(NSModalResponse result) { + if(result == NSFileHandlingPanelOKButton) + { + auto urls = [panel URLs]; + + if(urls.count > 0) + { + void* strings[urls.count]; + + for(int i = 0; i < urls.count; i++) + { + auto url = [urls objectAtIndex:i]; + + auto string = [url absoluteString]; + string = [string substringFromIndex:7]; + + strings[i] = (void*)[string UTF8String]; + } + + events->OnCompleted((int)urls.count, &strings[0]); + + [panel orderOut:panel]; + + if(parentWindowHandle != nullptr) + { + auto windowBase = dynamic_cast(parentWindowHandle); + [windowBase->Window makeKeyAndOrderFront:windowBase->Window]; + } + + return; + } + } + + events->OnCompleted(0, nullptr); + + }; + + if(parentWindowHandle != nullptr) + { + auto windowBase = dynamic_cast(parentWindowHandle); + + [panel beginSheetModalForWindow:windowBase->Window completionHandler:handler]; + } + else + { + [panel beginWithCompletionHandler: handler]; + } + } + } + + virtual void OpenFileDialog (IAvnWindow* parentWindowHandle, + IAvnSystemDialogEvents* events, + bool allowMultiple, + const char* title, + const char* initialDirectory, + const char* initialFile, + const char* filters) + { + @autoreleasepool + { + auto panel = [NSOpenPanel openPanel]; + + panel.allowsMultipleSelection = allowMultiple; + + if(title != nullptr) + { + panel.title = [NSString stringWithUTF8String:title]; + } + + if(initialDirectory != nullptr) + { + auto directoryString = [NSString stringWithUTF8String:initialDirectory]; + panel.directoryURL = [NSURL fileURLWithPath:directoryString]; + } + + if(initialFile != nullptr) + { + panel.nameFieldStringValue = [NSString stringWithUTF8String:initialFile]; + } + + if(filters != nullptr) + { + auto filtersString = [NSString stringWithUTF8String:filters]; + + if(filtersString.length > 0) + { + auto allowedTypes = [filtersString componentsSeparatedByString:@";"]; + + panel.allowedFileTypes = allowedTypes; + } + } + + auto handler = ^(NSModalResponse result) { + if(result == NSFileHandlingPanelOKButton) + { + auto urls = [panel URLs]; + + if(urls.count > 0) + { + void* strings[urls.count]; + + for(int i = 0; i < urls.count; i++) + { + auto url = [urls objectAtIndex:i]; + + auto string = [url absoluteString]; + string = [string substringFromIndex:7]; + + strings[i] = (void*)[string UTF8String]; + } + + events->OnCompleted((int)urls.count, &strings[0]); + + [panel orderOut:panel]; + + if(parentWindowHandle != nullptr) + { + auto windowBase = dynamic_cast(parentWindowHandle); + [windowBase->Window makeKeyAndOrderFront:windowBase->Window]; + } + + return; + } + } + + events->OnCompleted(0, nullptr); + + }; + + if(parentWindowHandle != nullptr) + { + auto windowBase = dynamic_cast(parentWindowHandle); + + [panel beginSheetModalForWindow:windowBase->Window completionHandler:handler]; + } + else + { + [panel beginWithCompletionHandler: handler]; + } + } + } + + virtual void SaveFileDialog (IAvnWindow* parentWindowHandle, + IAvnSystemDialogEvents* events, + const char* title, + const char* initialDirectory, + const char* initialFile, + const char* filters) + { + @autoreleasepool + { + auto panel = [NSSavePanel savePanel]; + + if(title != nullptr) + { + panel.title = [NSString stringWithUTF8String:title]; + } + + if(initialDirectory != nullptr) + { + auto directoryString = [NSString stringWithUTF8String:initialDirectory]; + panel.directoryURL = [NSURL fileURLWithPath:directoryString]; + } + + if(initialFile != nullptr) + { + panel.nameFieldStringValue = [NSString stringWithUTF8String:initialFile]; + } + + if(filters != nullptr) + { + auto filtersString = [NSString stringWithUTF8String:filters]; + + if(filtersString.length > 0) + { + auto allowedTypes = [filtersString componentsSeparatedByString:@";"]; + + panel.allowedFileTypes = allowedTypes; + } + } + + auto handler = ^(NSModalResponse result) { + if(result == NSFileHandlingPanelOKButton) + { + void* strings[1]; + + auto url = [panel URL]; + + auto string = [url absoluteString]; + string = [string substringFromIndex:7]; + strings[0] = (void*)[string UTF8String]; + + events->OnCompleted(1, &strings[0]); + + [panel orderOut:panel]; + + if(parentWindowHandle != nullptr) + { + auto windowBase = dynamic_cast(parentWindowHandle); + [windowBase->Window makeKeyAndOrderFront:windowBase->Window]; + } + + return; + } + + events->OnCompleted(0, nullptr); + + }; + + if(parentWindowHandle != nullptr) + { + auto windowBase = dynamic_cast(parentWindowHandle); + + [panel beginSheetModalForWindow:windowBase->Window completionHandler:handler]; + } + else + { + [panel beginWithCompletionHandler: handler]; + } + } + } + +}; + +extern IAvnSystemDialogs* CreateSystemDialogs() +{ + return new SystemDialogs(); +} diff --git a/src/Avalonia.Native.OSX/common.h b/src/Avalonia.Native.OSX/common.h index 5fd7e57556..6b1a45969e 100644 --- a/src/Avalonia.Native.OSX/common.h +++ b/src/Avalonia.Native.OSX/common.h @@ -9,4 +9,10 @@ extern IAvnPlatformThreadingInterface* CreatePlatformThreading(); extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events); +extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events); +extern IAvnSystemDialogs* CreateSystemDialogs(); + +extern NSPoint ToNSPoint (AvnPoint p); +extern AvnPoint ToAvnPoint (NSPoint p); +extern AvnPoint ConvertPointY (AvnPoint p); #endif diff --git a/src/Avalonia.Native.OSX/main.mm b/src/Avalonia.Native.OSX/main.mm index 0d6b2a978d..23fc26d28e 100644 --- a/src/Avalonia.Native.OSX/main.mm +++ b/src/Avalonia.Native.OSX/main.mm @@ -80,14 +80,68 @@ public: return S_OK; }; + virtual HRESULT CreatePopup(IAvnWindowEvents* cb, IAvnPopup** ppv) + { + if(cb == nullptr || ppv == nullptr) + return E_POINTER; + + *ppv = CreateAvnPopup(cb); + return S_OK; + } + virtual HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv) { *ppv = CreatePlatformThreading(); return S_OK; - }; + } + + virtual HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv) + { + *ppv = ::CreateSystemDialogs(); + return S_OK; + } }; extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative() { return new AvaloniaNative(); }; + + +NSPoint ToNSPoint (AvnPoint p) +{ + @autoreleasepool + { + NSPoint result; + result.x = p.X; + result.y = p.Y; + + return result; + } +} + +AvnPoint ToAvnPoint (NSPoint p) +{ + @autoreleasepool + { + AvnPoint result; + result.X = p.x; + result.Y = p.y; + + return result; + } +} + + +AvnPoint ConvertPointY (AvnPoint p) +{ + @autoreleasepool + { + auto sw = [NSScreen.screens objectAtIndex:0].frame; + + auto t = MAX(sw.origin.y, sw.origin.y + sw.size.height); + p.Y = t - p.Y; + + return p; + } +} diff --git a/src/Avalonia.Native.OSX/window.h b/src/Avalonia.Native.OSX/window.h new file mode 100644 index 0000000000..ed8406bd5f --- /dev/null +++ b/src/Avalonia.Native.OSX/window.h @@ -0,0 +1,182 @@ +// +// window.h +// Avalonia.Native.OSX +// +// Created by Dan Walmsley on 23/09/2018. +// Copyright © 2018 Avalonia. All rights reserved. +// + +#ifndef window_h +#define window_h + +class WindowBaseImpl; + +@interface AvnView : NSView +-(AvnView*) initWithParent: (WindowBaseImpl*) parent; +-(NSEvent*) lastMouseDownEvent; +-(AvnPoint) translateLocalPoint:(AvnPoint)pt; +@end + +@interface AvnWindow : NSWindow +-(AvnWindow*) initWithParent: (WindowBaseImpl*) parent; +-(void) setCanBecomeKeyAndMain; +@end + +class WindowBaseImpl : public ComSingleObject +{ +public: + AvnView* View; + AvnWindow* Window; + ComPtr BaseEvents; + AvnPoint lastPositionSet; + WindowBaseImpl(IAvnWindowBaseEvents* events) + { + BaseEvents = events; + View = [[AvnView alloc] initWithParent:this]; + Window = [[AvnWindow alloc] initWithParent:this]; + + lastPositionSet.X = 100; + lastPositionSet.Y = 100; + + [Window setStyleMask:NSWindowStyleMaskBorderless]; + [Window setBackingType:NSBackingStoreBuffered]; + [Window setContentView: View]; + } + + virtual HRESULT Show() + { + SetPosition(lastPositionSet); + UpdateStyle(); + [Window makeKeyAndOrderFront:Window]; + return S_OK; + } + + virtual HRESULT Hide () + { + if(Window != nullptr) + { + [Window orderOut:Window]; + } + return S_OK; + } + + virtual HRESULT Close() + { + [Window close]; + return S_OK; + } + + virtual HRESULT GetClientSize(AvnSize* ret) + { + if(ret == nullptr) + return E_POINTER; + auto frame = [View frame]; + ret->Width = frame.size.width; + ret->Height = frame.size.height; + return S_OK; + } + + virtual HRESULT GetScaling (double* ret) + { + if(ret == nullptr) + return E_POINTER; + + if(Window == nullptr) + { + *ret = 1; + return S_OK; + } + + *ret = [Window backingScaleFactor]; + return S_OK; + } + + virtual HRESULT Resize(double x, double y) + { + [Window setContentSize:NSSize{x, y}]; + return S_OK; + } + + virtual void Invalidate (AvnRect rect) + { + [View setNeedsDisplayInRect:[View frame]]; + } + + virtual void BeginMoveDrag () + { + auto lastEvent = [View lastMouseDownEvent]; + + if(lastEvent == nullptr) + { + return; + } + + [Window performWindowDragWithEvent:lastEvent]; + } + + + virtual HRESULT GetPosition (AvnPoint* ret) + { + if(ret == nullptr) + { + return E_POINTER; + } + + auto frame = [Window frame]; + + ret->X = frame.origin.x; + ret->Y = frame.origin.y + frame.size.height; + + *ret = ConvertPointY(*ret); + + return S_OK; + } + + virtual void SetPosition (AvnPoint point) + { + lastPositionSet = point; + [Window setFrameTopLeftPoint:ToNSPoint(ConvertPointY(point))]; + } + + virtual HRESULT PointToClient (AvnPoint point, AvnPoint* ret) + { + if(ret == nullptr) + { + return E_POINTER; + } + + point = ConvertPointY(point); + auto viewPoint = [Window convertPointFromScreen:ToNSPoint(point)]; + + *ret = [View translateLocalPoint:ToAvnPoint(viewPoint)]; + + return S_OK; + } + + virtual HRESULT PointToScreen (AvnPoint point, AvnPoint* ret) + { + if(ret == nullptr) + { + return E_POINTER; + } + + auto cocoaViewPoint = ToNSPoint([View translateLocalPoint:point]); + auto cocoaScreenPoint = [Window convertPointToScreen:cocoaViewPoint]; + *ret = ConvertPointY(ToAvnPoint(cocoaScreenPoint)); + + return S_OK; + } + +protected: + virtual NSWindowStyleMask GetStyle() + { + return NSWindowStyleMaskBorderless; + } + + void UpdateStyle() + { + [Window setStyleMask:GetStyle()]; + } +}; + +#endif /* window_h */ diff --git a/src/Avalonia.Native.OSX/window.mm b/src/Avalonia.Native.OSX/window.mm index f15246595d..3f18423e26 100644 --- a/src/Avalonia.Native.OSX/window.mm +++ b/src/Avalonia.Native.OSX/window.mm @@ -1,90 +1,5 @@ #include "common.h" - -class WindowBaseImpl; - -@interface AvnView : NSView --(AvnView*) initWithParent: (WindowBaseImpl*) parent; --(NSEvent*) lastMouseDownEvent; -@end - -@interface AvnWindow : NSWindow --(AvnWindow*) initWithParent: (WindowBaseImpl*) parent; --(void) setCanBecomeKeyAndMain; -@end - -class WindowBaseImpl : public ComSingleObject -{ -public: - AvnView* View; - AvnWindow* Window; - ComPtr BaseEvents; - WindowBaseImpl(IAvnWindowBaseEvents* events) - { - BaseEvents = events; - View = [[AvnView alloc] initWithParent:this]; - Window = [[AvnWindow alloc] initWithParent:this]; - [Window setStyleMask:NSWindowStyleMaskBorderless]; - [Window setBackingType:NSBackingStoreBuffered]; - [Window setContentView: View]; - } - - virtual HRESULT Show() - { - UpdateStyle(); - [Window makeKeyAndOrderFront:Window]; - return S_OK; - } - - virtual HRESULT Close() - { - [Window close]; - return S_OK; - } - - virtual HRESULT GetClientSize(AvnSize* ret) - { - if(ret == nullptr) - return E_POINTER; - auto frame = [View frame]; - ret->Width = frame.size.width; - ret->Height = frame.size.height; - return S_OK; - } - - virtual HRESULT Resize(double x, double y) - { - [Window setContentSize:NSSize{x, y}]; - return S_OK; - } - - virtual void Invalidate (AvnRect rect) - { - [View setNeedsDisplayInRect:[View frame]]; - } - - virtual void BeginMoveDrag () - { - auto lastEvent = [View lastMouseDownEvent]; - - if(lastEvent == nullptr) - { - return; - } - - [Window performWindowDragWithEvent:lastEvent]; - } - -protected: - virtual NSWindowStyleMask GetStyle() - { - return NSWindowStyleMaskBorderless; - } - - void UpdateStyle() - { - [Window setStyleMask:GetStyle()]; - } -}; +#include "window.h" @implementation AvnView { @@ -161,7 +76,7 @@ protected: free(ptr); } -- (AvnPoint)translateLocalPoint:(AvnPoint)pt +- (AvnPoint) translateLocalPoint:(AvnPoint)pt { pt.Y = [self bounds].size.height - pt.Y; return pt; @@ -406,6 +321,32 @@ protected: @end +class PopupImpl : public WindowBaseImpl, public IAvnPopup +{ +private: + BEGIN_INTERFACE_MAP() + INHERIT_INTERFACE_MAP(WindowBaseImpl) + INTERFACE_MAP_ENTRY(IAvnPopup, IID_IAvnPopup) + END_INTERFACE_MAP() + ComPtr WindowEvents; + PopupImpl(IAvnWindowEvents* events) : WindowBaseImpl(events) + { + WindowEvents = events; + [Window setLevel:NSPopUpMenuWindowLevel]; + } + +protected: + virtual NSWindowStyleMask GetStyle() + { + return NSWindowStyleMaskBorderless; + } +}; + +extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events) +{ + IAvnPopup* ptr = dynamic_cast(new PopupImpl(events)); + return ptr; +} class WindowImpl : public WindowBaseImpl, public IAvnWindow { @@ -451,7 +392,6 @@ protected: } }; - extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events) { IAvnWindow* ptr = dynamic_cast(new WindowImpl(events)); diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index 9f699ab664..7b0eef0a92 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -12,4 +12,12 @@ + + + + + + + + diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index ed9acb1d6f..7f43256ddc 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -74,6 +74,7 @@ namespace Avalonia.Native .Bind().ToSingleton() .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs())) .Bind().ToConstant(new PlatformThreadingInterface(_factory.CreatePlatformThreadingInterface())); } @@ -88,8 +89,8 @@ namespace Avalonia.Native } public IPopupImpl CreatePopup() - { - throw new NotImplementedException(); + { + return new PopupImpl(_factory); } } diff --git a/src/Avalonia.Native/Helpers.cs b/src/Avalonia.Native/Helpers.cs new file mode 100644 index 0000000000..941dd1dd14 --- /dev/null +++ b/src/Avalonia.Native/Helpers.cs @@ -0,0 +1,18 @@ +using System; +using Avalonia.Native.Interop; + +namespace Avalonia.Native +{ + public static class Helpers + { + public static Point ToAvaloniaPoint (this AvnPoint pt) + { + return new Point(pt.X, pt.Y); + } + + public static AvnPoint ToAvnPoint (this Point pt) + { + return new AvnPoint { X = pt.X, Y = pt.Y }; + } + } +} diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs new file mode 100644 index 0000000000..bed2b3f929 --- /dev/null +++ b/src/Avalonia.Native/PopupImpl.cs @@ -0,0 +1,27 @@ +using System; +using Avalonia.Native.Interop; +using Avalonia.Platform; + +namespace Avalonia.Native +{ + public class PopupImpl : WindowBaseImpl, IPopupImpl + { + IAvnPopup _native; + + public PopupImpl(IAvaloniaNativeFactory factory) + { + using (var e = new PopupEvents(this)) + Init(_native = factory.CreatePopup(e)); + } + + class PopupEvents : WindowBaseEvents, IAvnWindowEvents + { + readonly PopupImpl _parent; + + public PopupEvents(PopupImpl parent) : base(parent) + { + _parent = parent; + } + } + } +} diff --git a/src/Avalonia.Native/SystemDialogs.cs b/src/Avalonia.Native/SystemDialogs.cs new file mode 100644 index 0000000000..cb16ec1239 --- /dev/null +++ b/src/Avalonia.Native/SystemDialogs.cs @@ -0,0 +1,87 @@ +using System; +using System.Linq; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Avalonia.Controls; +using Avalonia.Controls.Platform; +using Avalonia.Native.Interop; +using Avalonia.Platform; + +namespace Avalonia.Native +{ + public class SystemDialogs : ISystemDialogImpl + { + IAvnSystemDialogs _native; + + public SystemDialogs(IAvnSystemDialogs native) + { + _native = native; + } + + public Task ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent) + { + var events = new SystemDialogEvents(); + + if (dialog is OpenFileDialog ofd) + { + _native.OpenFileDialog((parent as WindowImpl).Native, + events, ofd.AllowMultiple, + ofd.Title, + ofd.InitialDirectory, + ofd.InitialFileName, + string.Join(";", dialog.Filters.SelectMany(f => f.Extensions))); + } + else + { + _native.SaveFileDialog((parent as WindowImpl).Native, + events, + dialog.Title, + dialog.InitialDirectory, + dialog.InitialFileName, + string.Join(";", dialog.Filters.SelectMany(f => f.Extensions))); + } + + return events.Task.ContinueWith(t => { events.Dispose(); return t.Result; }); + } + + public Task ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent) + { + var events = new SystemDialogEvents(); + + _native.SelectFolderDialog((parent as WindowImpl).Native, events, dialog.Title, dialog.InitialDirectory); + + return events.Task.ContinueWith(t => { events.Dispose(); return t.Result.FirstOrDefault(); }); + } + } + + public class SystemDialogEvents : CallbackBase, IAvnSystemDialogEvents + { + private TaskCompletionSource _tcs; + + public SystemDialogEvents() + { + _tcs = new TaskCompletionSource(); + } + + public Task Task => _tcs.Task; + + public void OnCompleted(int numResults, IntPtr trFirstResultRef) + { + string[] results = new string[numResults]; + + unsafe + { + var ptr = (IntPtr*)trFirstResultRef.ToPointer(); + + for (int i = 0; i < numResults; i++) + { + results[i] = Marshal.PtrToStringAnsi(*ptr); + + ptr++; + } + } + + _tcs.SetResult(results); + } + } +} diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs index 484fe3d514..301071ac25 100644 --- a/src/Avalonia.Native/WindowImpl.cs +++ b/src/Avalonia.Native/WindowImpl.cs @@ -24,6 +24,8 @@ namespace Avalonia.Native } } + public IAvnWindow Native => _native; + public IDisposable ShowDialog() { return null; diff --git a/src/Avalonia.Native/WindowImplBase.cs b/src/Avalonia.Native/WindowImplBase.cs index 5f4307114c..0c7386de20 100644 --- a/src/Avalonia.Native/WindowImplBase.cs +++ b/src/Avalonia.Native/WindowImplBase.cs @@ -134,11 +134,11 @@ namespace Avalonia.Native switch (type) { case AvnRawMouseEventType.Wheel: - Input?.Invoke(new RawMouseWheelEventArgs(_mouse, timeStamp, _inputRoot, new Point(point.X, point.Y), new Vector(delta.X, delta.Y), (InputModifiers)modifiers)); + Input?.Invoke(new RawMouseWheelEventArgs(_mouse, timeStamp, _inputRoot, point.ToAvaloniaPoint(), new Vector(delta.X, delta.Y), (InputModifiers)modifiers)); break; default: - Input?.Invoke(new RawMouseEventArgs(_mouse, timeStamp, _inputRoot, (RawMouseEventType)type, new Point(point.X, point.Y), (InputModifiers)modifiers)); + Input?.Invoke(new RawMouseEventArgs(_mouse, timeStamp, _inputRoot, (RawMouseEventType)type, point.ToAvaloniaPoint(), (InputModifiers)modifiers)); break; } } @@ -180,10 +180,35 @@ namespace Avalonia.Native } + public Point Position + { + get => _native.GetPosition().ToAvaloniaPoint(); + set => _native.SetPosition(value.ToAvnPoint()); + } + + public Point PointToClient(Point point) + { + return _native.PointToClient(point.ToAvnPoint()).ToAvaloniaPoint(); + } + + public Point PointToScreen(Point point) + { + return _native.PointToScreen(point.ToAvnPoint()).ToAvaloniaPoint(); + } + + public void Hide() + { + _native.Hide(); + } + + public void BeginMoveDrag() + { + _native.BeginMoveDrag(); + } + #region Stubs - public double Scaling => 1; + public double Scaling => _native.GetScaling(); - public Point Position { get; set; } public Action PositionChanged { get; set; } public Action Deactivated { get; set; } public Action Activated { get; set; } @@ -211,29 +236,10 @@ namespace Avalonia.Native { } - public void Hide() - { - } - - public void BeginMoveDrag() - { - _native.BeginMoveDrag(); - } - public void BeginResizeDrag(WindowEdge edge) { } - public Point PointToClient(Point point) - { - return point; - } - - public Point PointToScreen(Point point) - { - return point; - } - #endregion } } diff --git a/src/headers/avalonia-native.h b/src/headers/avalonia-native.h index 9a0c00550f..252b259ed0 100644 --- a/src/headers/avalonia-native.h +++ b/src/headers/avalonia-native.h @@ -5,8 +5,11 @@ struct IAvnWindowEvents; struct IAvnWindow; +struct IAvnPopup; struct IAvnMacOptions; struct IAvnPlatformThreadingInterface; +struct IAvnSystemDialogEvents; +struct IAvnSystemDialogs; struct AvnSize { @@ -66,26 +69,39 @@ public: virtual HRESULT Initialize() = 0; virtual IAvnMacOptions* GetMacOptions() = 0; virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnWindow** ppv) = 0; + virtual HRESULT CreatePopup (IAvnWindowEvents* cb, IAvnPopup** ppv) = 0; virtual HRESULT CreatePlatformThreadingInterface(IAvnPlatformThreadingInterface** ppv) = 0; + virtual HRESULT CreateSystemDialogs (IAvnSystemDialogs** ppv) = 0; }; AVNCOM(IAvnWindowBase, 02) : virtual IUnknown { virtual HRESULT Show() = 0; + virtual HRESULT Hide () = 0; virtual HRESULT Close() = 0; virtual HRESULT GetClientSize(AvnSize*ret) = 0; + virtual HRESULT GetScaling(double*ret)=0; virtual HRESULT Resize(double width, double height) = 0; virtual void Invalidate (AvnRect rect) = 0; virtual void BeginMoveDrag () = 0; + virtual HRESULT GetPosition (AvnPoint*ret) = 0; + virtual void SetPosition (AvnPoint point) = 0; + virtual HRESULT PointToClient (AvnPoint point, AvnPoint*ret) = 0; + virtual HRESULT PointToScreen (AvnPoint point, AvnPoint*ret) = 0; }; -AVNCOM(IAvnWindow, 03) : virtual IAvnWindowBase +AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase +{ + +}; + +AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase { virtual HRESULT SetCanResize(bool value) = 0; virtual HRESULT SetHasDecorations(bool value) = 0; }; -AVNCOM(IAvnWindowBaseEvents, 04) : IUnknown +AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown { virtual HRESULT SoftwareDraw(void* ptr, int stride, int pixelWidth, int pixelHeight, const AvnSize& logicalSize) = 0; virtual void Closed() = 0; @@ -102,33 +118,33 @@ AVNCOM(IAvnWindowBaseEvents, 04) : IUnknown }; -AVNCOM(IAvnWindowEvents, 05) : IAvnWindowBaseEvents +AVNCOM(IAvnWindowEvents, 06) : IAvnWindowBaseEvents { }; -AVNCOM(IAvnMacOptions, 06) : virtual IUnknown +AVNCOM(IAvnMacOptions, 07) : virtual IUnknown { virtual HRESULT SetShowInDock(int show) = 0; }; -AVNCOM(IAvnActionCallback, 07) : IUnknown +AVNCOM(IAvnActionCallback, 08) : IUnknown { virtual void Run() = 0; }; -AVNCOM(IAvnSignaledCallback, 08) : IUnknown +AVNCOM(IAvnSignaledCallback, 09) : IUnknown { virtual void Signaled(int priority, bool priorityContainsMeaningfulValue) = 0; }; -AVNCOM(IAvnLoopCancellation, 09) : virtual IUnknown +AVNCOM(IAvnLoopCancellation, 0a) : virtual IUnknown { virtual void Cancel() = 0; }; -AVNCOM(IAvnPlatformThreadingInterface, 0a) : virtual IUnknown +AVNCOM(IAvnPlatformThreadingInterface, 0b) : virtual IUnknown { virtual bool GetCurrentThreadIsLoopThread() = 0; virtual void SetSignaledCallback(IAvnSignaledCallback* cb) = 0; @@ -139,4 +155,32 @@ AVNCOM(IAvnPlatformThreadingInterface, 0a) : virtual IUnknown virtual IUnknown* StartTimer(int priority, int ms, IAvnActionCallback* callback) = 0; }; +AVNCOM(IAvnSystemDialogEvents, 0c) : virtual IUnknown +{ + virtual void OnCompleted (int numResults, void* ptrFirstResult) = 0; +}; + +AVNCOM(IAvnSystemDialogs, 0d) : virtual IUnknown +{ + virtual void SelectFolderDialog (IAvnWindow* parentWindowHandle, + IAvnSystemDialogEvents* events, + const char* title, + const char* initialPath) = 0; + + virtual void OpenFileDialog (IAvnWindow* parentWindowHandle, + IAvnSystemDialogEvents* events, + bool allowMultiple, + const char* title, + const char* initialDirectory, + const char* initialFile, + const char* filters) = 0; + + virtual void SaveFileDialog (IAvnWindow* parentWindowHandle, + IAvnSystemDialogEvents* events, + const char* title, + const char* initialDirectory, + const char* initialFile, + const char* filters) = 0; +}; + extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative();