diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSFileOpenPickerExtension.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSFileOpenPickerExtension.cs index 1f910d52985d..b64ca723fb43 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSFileOpenPickerExtension.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSFileOpenPickerExtension.cs @@ -12,21 +12,41 @@ internal class MacOSFileOpenPickerExtension : IFileOpenPickerExtension { private static readonly MacOSFileOpenPickerExtension _instance = new(); + private static readonly string[] _asteriskArray = new string[] { "*" }; + private MacOSFileOpenPickerExtension() { + _filters = Array.Empty(); } public static void Register() => ApiExtensibility.Register(typeof(IFileOpenPickerExtension), _ => _instance); - // TODO: we need something more in IFileOpenPickerExtension so we can customize the native picker from the user-created FileOpenPicker + // Mapping + // WinUI AppKit (NSOpenPanel) + // -------------------------------------------------------------- + // CommitButtonText (string) prompt (NSString) + // ContinuationData (ValueSet) n/a + // FileTypeFilter allowedFileTypes (NSArray) + // SettingsIdentifier (string) identifier (NSString) + // SuggestedStartLocation (enum) directoryURL (NSURL) + // User (User) n/a + // ViewMode (enum) n/a public void Customize(FileOpenPicker picker) { - // TODO: call native code + _prompt = picker.CommitButtonText.Length == 0 ? null : picker.CommitButtonText; + _filters = picker.FileTypeFilter.Except(_asteriskArray).Select(ext => ext.TrimStart('.')).ToArray(); + _identifier = picker.SettingsIdentifier.Length == 0 ? null : picker.SettingsIdentifier; + _suggestedStartLocation = picker.SuggestedStartLocation; } + private string? _prompt; + private string[] _filters; + private string? _identifier; + private PickerLocationId _suggestedStartLocation; + public async Task> PickMultipleFilesAsync(CancellationToken token) { - var array = NativeUno.uno_pick_multiple_files(null); + var array = NativeUno.uno_pick_multiple_files(_prompt, _identifier, (int)_suggestedStartLocation, _filters, _filters.Length); var files = new List(); var ptr = Marshal.ReadIntPtr(array); while (ptr != IntPtr.Zero) @@ -44,7 +64,7 @@ public async Task> PickMultipleFilesAsync(Cancellatio public async Task PickSingleFileAsync(CancellationToken token) { - var file = NativeUno.uno_pick_single_file(null); + var file = NativeUno.uno_pick_single_file(_prompt, _identifier, (int)_suggestedStartLocation, _filters, _filters.Length); return file is null ? null : await StorageFile.GetFileFromPathAsync(file); } } diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSFileSavePickerExtension.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSFileSavePickerExtension.cs index 86b4b5632ec4..a7eaead70302 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSFileSavePickerExtension.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSFileSavePickerExtension.cs @@ -12,13 +12,55 @@ internal class MacOSFileSavePickerExtension : IFileSavePickerExtension private MacOSFileSavePickerExtension() { + _filters = Array.Empty(); } public static void Register() => ApiExtensibility.Register(typeof(IFileSavePickerExtension), _ => _instance); + // Mapping + // WinUI AppKit (NSSavePanel) + // ---------------------------------------------------------------------- + // CommitButtonText (string) prompt (NSString) + // ContinuationData (ValueSet) n/a + // DefaultFileExtension (string) _Do not set this property_ says WinUI + // EnterpriseId n/a + // FileTypeChoices (dictionary) allowedFileTypes (NSArray) + // SettingsIdentifier (string) identifier (NSString) + // SuggestedFileName (string) nameFieldStringValue (NSString) + // SuggestedSaveFile (StorageFile) n/a + // SuggestedStartLocation (enum) directoryURL (NSURL) + // User (User) n/a + public void Customize(FileSavePicker picker) + { + _prompt = picker.CommitButtonText.Length == 0 ? null : picker.CommitButtonText; + _identifier = picker.SettingsIdentifier.Length == 0 ? null : picker.SettingsIdentifier; + _suggestedFileName = picker.SuggestedFileName.Length == 0 ? null : picker.SuggestedFileName; + _suggestedStartLocation = picker.SuggestedStartLocation; + if (picker.FileTypeChoices.Count == 0) + { + return; + } + + var list = new List(); + foreach (var value in picker.FileTypeChoices.Values) + { + foreach (var ext in value) + { + list.Add(ext.TrimStart('.')); + } + } + _filters = list.ToArray(); + } + + private string? _prompt; + private string[] _filters; + private string? _identifier; + private string? _suggestedFileName; + private PickerLocationId _suggestedStartLocation; + public async Task PickSaveFileAsync(CancellationToken token) { - var file = NativeUno.uno_pick_save_file(null); + var file = NativeUno.uno_pick_save_file(_prompt, _identifier, _suggestedFileName, (int)_suggestedStartLocation, _filters, _filters.Length); return file is null ? null : await StorageFile.GetFileFromPathAsync(file); } } diff --git a/src/Uno.UI.Runtime.Skia.MacOS/MacOSFolderPickerExtension.cs b/src/Uno.UI.Runtime.Skia.MacOS/MacOSFolderPickerExtension.cs index 6dcf8c278c08..9fdc73db47c9 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/MacOSFolderPickerExtension.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/MacOSFolderPickerExtension.cs @@ -16,9 +16,31 @@ private MacOSFolderPickerExtension() public static void Register() => ApiExtensibility.Register(typeof(IFolderPickerExtension), _ => _instance); + // Mapping + // WinUI AppKit (NSOpenPanel) + // -------------------------------------------------------------- + // CommitButtonText (string) prompt (NSString) + // ContinuationData (ValueSet) n/a + // FileTypeFilter allowedFileTypes (NSArray) + // SettingsIdentifier (string) identifier (NSString) + // SuggestedStartLocation (enum) directoryURL (NSURL) + // User (User) n/a + // ViewMode (enum) n/a + public void Customize(FolderPicker picker) + { + _prompt = picker.CommitButtonText.Length == 0 ? null : picker.CommitButtonText; + // FileTypeFilter can be set but they are not doing anything with the native picker + _identifier = picker.SettingsIdentifier.Length == 0 ? null : picker.SettingsIdentifier; + _suggestedStartLocation = picker.SuggestedStartLocation; + } + + private string? _prompt; + private string? _identifier; + private PickerLocationId _suggestedStartLocation; + public async Task PickSingleFolderAsync(CancellationToken token) { - var folder = NativeUno.uno_pick_single_folder(); + var folder = NativeUno.uno_pick_single_folder(_prompt, _identifier, (int)_suggestedStartLocation); return folder is null ? null : await StorageFolder.GetFolderFromPathAsync(folder); } } diff --git a/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs b/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs index f8cfd3a6be00..8629f9376f1b 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs +++ b/src/Uno.UI.Runtime.Skia.MacOS/NativeUno.cs @@ -234,16 +234,19 @@ internal static unsafe partial void uno_set_window_close_callbacks( internal static partial void uno_window_set_min_size(nint window, double width, double height); [LibraryImport("libUnoNativeMac.dylib", StringMarshalling = StringMarshalling.Utf8)] - internal static partial string? /* const char* _Nullable */ uno_pick_single_folder(); + internal static partial string? /* const char* _Nullable */ uno_pick_single_folder(string? prompt, string? identifier, int suggestedStartLocation); [LibraryImport("libUnoNativeMac.dylib", StringMarshalling = StringMarshalling.Utf8)] - internal static partial string? /* const char* _Nullable */ uno_pick_single_file(string? prompt); + internal static partial string? /* const char* _Nullable */ uno_pick_single_file(string? prompt, string? identifier, int suggestedStartLocation, + string[] filters, int filterSize); [LibraryImport("libUnoNativeMac.dylib", StringMarshalling = StringMarshalling.Utf8)] - internal static partial IntPtr /* const char* _Nullable * _Nullable */ uno_pick_multiple_files(string? prompt); + internal static partial IntPtr /* const char* _Nullable * _Nullable */ uno_pick_multiple_files(string? prompt, string? identifier, int suggestedStartLocation, + string[] filters, int filterSize); [LibraryImport("libUnoNativeMac.dylib", StringMarshalling = StringMarshalling.Utf8)] - internal static partial string? /* const char* _Nullable */ uno_pick_save_file(string? prompt); + internal static partial string? /* const char* _Nullable */ uno_pick_save_file(string? prompt, string? identifier, string? suggestedFileName, int suggestedStartLocation, + string[] filters, int filtersSize); [LibraryImport("libUnoNativeMac.dylib")] internal static partial void uno_clipboard_clear(); diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOPickers.h b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOPickers.h index 8704c6551e4c..8be3122ca410 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOPickers.h +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOPickers.h @@ -8,9 +8,23 @@ NS_ASSUME_NONNULL_BEGIN -char* _Nullable uno_pick_single_folder(void); -char* _Nullable uno_pick_single_file(const char* _Nullable prompt); -char* _Nullable uno_pick_save_file(const char* _Nullable prompt); -char* _Nullable * _Nullable uno_pick_multiple_files(const char* _Nullable prompt); +// https://learn.microsoft.com/en-us/uwp/api/windows.storage.pickers.pickerlocationid?view=winrt-22621 +typedef NS_ENUM(sint32, PickerLocationId) { + PickerLocationIdDocumentsLibrary = 0, + PickerLocationIdComputerFolder = 1, + PickerLocationIdDesktop = 2, + PickerLocationIdDownloads = 3, + PickerLocationIdHomeGroup = 4, + PickerLocationIdMusicLibrary = 5, + PickerLocationIdPicturesLibrary = 6, + PickerLocationIdVideosLibrary = 7, + PickerLocationIdObjects3D = 8, + PickerLocationIdUnspecified = 9, +}; + +char* _Nullable uno_pick_single_folder(const char* _Nullable prompt, const char* _Nullable identifier, PickerLocationId suggestedStartLocation); +char* _Nullable uno_pick_single_file(const char* _Nullable prompt, const char* _Nullable identifier, PickerLocationId suggestedStartLocation, char* _Nonnull filters[_Nullable], int filterSize); +char* _Nullable uno_pick_save_file(const char* _Nullable prompt, const char* _Nullable identifier, const char* _Nullable suggestedFileName, PickerLocationId suggestedStartLocation, char* _Nonnull filters[_Nullable], int filterSize); +char* _Nullable * _Nullable uno_pick_multiple_files(const char* _Nullable prompt, const char* _Nullable identifier, PickerLocationId suggestedStartLocation, char* _Nonnull filters[_Nullable], int filterSize); NS_ASSUME_NONNULL_END diff --git a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOPickers.m b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOPickers.m index 711115f8a85b..d63fafd39c08 100644 --- a/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOPickers.m +++ b/src/Uno.UI.Runtime.Skia.MacOS/UnoNativeMac/UnoNativeMac/UNOPickers.m @@ -4,14 +4,80 @@ #import "UNOPickers.h" -char* uno_pick_single_folder(void) +NSURL* get_best_location(int32_t suggestedStartLocation) +{ + NSSearchPathDirectory path; + + switch (suggestedStartLocation) { + case PickerLocationIdDocumentsLibrary: + path = NSDocumentDirectory; + break; + case PickerLocationIdComputerFolder: + return [NSURL URLWithString:@"file:///"]; + case PickerLocationIdDesktop: + path = NSDesktopDirectory; + break; + case PickerLocationIdDownloads: + path = NSDownloadsDirectory; + break; + case PickerLocationIdHomeGroup: + path = NSSharedPublicDirectory; + break; + case PickerLocationIdMusicLibrary: + path = NSMusicDirectory; + break; + case PickerLocationIdPicturesLibrary: + path = NSPicturesDirectory; + break; + case PickerLocationIdVideosLibrary: + path = NSMoviesDirectory; + break; + case PickerLocationIdObjects3D: +#if DEBUG + NSLog(@"get_best_location %d -> no extact match, suggesting Home directory", suggestedStartLocation); +#endif + return [NSURL URLWithString:NSHomeDirectory()]; + case PickerLocationIdUnspecified: +#if DEBUG + NSLog(@"get_best_location %d -> unspecified", suggestedStartLocation); +#endif + return nil; + default: +#if DEBUG + NSLog(@"get_best_location %d -> unknown value", suggestedStartLocation); +#endif + return nil; + } + + return [[NSFileManager defaultManager] URLsForDirectory:path inDomains:NSUserDomainMask][0]; +} + +NSMutableArray* get_allowed(char* filters[], int filterSize) +{ + NSMutableArray *allowed = [[NSMutableArray alloc] initWithCapacity:filterSize]; + for (int i=0; i < filterSize; i++) { + NSString *s = [NSString stringWithUTF8String:filters[i]]; + [allowed addObject:s]; + } + return allowed; +} + +char* uno_pick_single_folder(const char* _Nullable prompt, const char* _Nullable identifier, int32_t suggestedStartLocation) { NSOpenPanel *panel = [NSOpenPanel openPanel]; // based on settings from uno/src/Uno.UWP/Storage/Pickers/FolderPicker.macOS.cs - // note: `allowsOtherFileTypes` is only used on NSSavePanel (and does nothing on NSOpenPanel) + // filters are not applied in WinUI so we don't set them up here panel.allowedFileTypes = [NSArray arrayWithObject:@"none"]; panel.canChooseDirectories = true; panel.canChooseFiles = false; + panel.directoryURL = get_best_location(suggestedStartLocation); + if (identifier) { + panel.identifier = [NSString stringWithUTF8String:identifier]; + } + if (prompt) { + panel.prompt = [NSString stringWithUTF8String:prompt]; + } + if ([panel runModal] == NSModalResponseOK) { NSURL *url = panel.URL; if (url) { @@ -28,18 +94,22 @@ return nil; } -char* uno_pick_single_file(const char *prompt) +char* uno_pick_single_file(const char* _Nullable prompt, const char* _Nullable identifier, PickerLocationId suggestedStartLocation, char* filters[], int filterSize) { NSOpenPanel *panel = [NSOpenPanel openPanel]; // based on settings from uno/src/Uno.UWP/Storage/Pickers/FileOpenPicker.macOS.cs - // note: `allowsOtherFileTypes` is only used on NSSavePanel (and does nothing on NSOpenPanel) - panel.allowedFileTypes = nil; // FIXME (nil means every types) + panel.allowedFileTypes = get_allowed(filters, filterSize); panel.canChooseDirectories = false; panel.canChooseFiles = true; panel.allowsMultipleSelection = false; + panel.directoryURL = get_best_location(suggestedStartLocation); + if (identifier) { + panel.identifier = [NSString stringWithUTF8String:identifier]; + } if (prompt) { panel.prompt = [NSString stringWithUTF8String:prompt]; } + if ([panel runModal] == NSModalResponseOK) { NSURL *url = panel.URL; if (url) { @@ -56,18 +126,22 @@ return nil; } -char** uno_pick_multiple_files(const char *prompt) +char** uno_pick_multiple_files(const char* _Nullable prompt, const char* _Nullable identifier, PickerLocationId suggestedStartLocation, char* filters[], int filterSize) { NSOpenPanel *panel = [NSOpenPanel openPanel]; // based on settings from uno/src/Uno.UWP/Storage/Pickers/FileOpenPicker.macOS.cs - // note: `allowsOtherFileTypes` is only used on NSSavePanel (and does nothing on NSOpenPanel) - panel.allowedFileTypes = nil; // FIXME (nil means every types) + panel.allowedFileTypes = get_allowed(filters, filterSize); panel.canChooseDirectories = false; panel.canChooseFiles = true; panel.allowsMultipleSelection = true; + panel.directoryURL = get_best_location(suggestedStartLocation); + if (identifier) { + panel.identifier = [NSString stringWithUTF8String:identifier]; + } if (prompt) { panel.prompt = [NSString stringWithUTF8String:prompt]; } + if ([panel runModal] == NSModalResponseOK) { NSArray *urls = panel.URLs; if (urls) { @@ -91,16 +165,22 @@ return nil; } -char* uno_pick_save_file(const char *prompt) +char* uno_pick_save_file(const char* _Nullable prompt, const char* _Nullable identifier, const char* _Nullable suggestedFileName, PickerLocationId suggestedStartLocation, char* filters[], int filterSize) { - NSOpenPanel *panel = [NSOpenPanel openPanel]; + NSSavePanel *panel = [NSSavePanel savePanel]; // based on settings from uno/src/Uno.UWP/Storage/Pickers/FileSavePicker.macOS.cs panel.allowsOtherFileTypes = true; - panel.allowedFileTypes = nil; // FIXME (nil means every types) - panel.canChooseFiles = true; + panel.allowedFileTypes = get_allowed(filters, filterSize); + panel.directoryURL = get_best_location(suggestedStartLocation); + if (identifier) { + panel.identifier = [NSString stringWithUTF8String:identifier]; + } if (prompt) { panel.prompt = [NSString stringWithUTF8String:prompt]; } + if (suggestedFileName) { + panel.nameFieldStringValue = [NSString stringWithUTF8String:suggestedFileName]; + } if ([panel runModal] == NSModalResponseOK) { NSURL *url = panel.URL; if (url) { diff --git a/src/Uno.UWP/Storage/Pickers/FileSavePicker.skiaiOS.cs b/src/Uno.UWP/Storage/Pickers/FileSavePicker.skiaiOS.cs index 8aa0ba986eae..c91c1ffb5ab3 100644 --- a/src/Uno.UWP/Storage/Pickers/FileSavePicker.skiaiOS.cs +++ b/src/Uno.UWP/Storage/Pickers/FileSavePicker.skiaiOS.cs @@ -22,6 +22,7 @@ public partial class FileSavePicker throw new NotSupportedException("FileSavePicker extension is not registered."); } + _fileSavePickerExtension.Customize(this); return await _fileSavePickerExtension.PickSaveFileAsync(token); } } diff --git a/src/Uno.UWP/Storage/Pickers/FolderPicker.skia.cs b/src/Uno.UWP/Storage/Pickers/FolderPicker.skia.cs index f555934ff3de..568091a71b57 100644 --- a/src/Uno.UWP/Storage/Pickers/FolderPicker.skia.cs +++ b/src/Uno.UWP/Storage/Pickers/FolderPicker.skia.cs @@ -21,6 +21,7 @@ public partial class FolderPicker throw new NotSupportedException("FolderPicker is not supported on this target."); } + _folderPickerExtension.Customize(this); return await _folderPickerExtension.PickSingleFolderAsync(token); } } diff --git a/src/Uno.UWP/Storage/Pickers/IFileSavePickerExtension.skiaiOS.cs b/src/Uno.UWP/Storage/Pickers/IFileSavePickerExtension.skiaiOS.cs index 10511e20bfb6..caa1afd76402 100644 --- a/src/Uno.UWP/Storage/Pickers/IFileSavePickerExtension.skiaiOS.cs +++ b/src/Uno.UWP/Storage/Pickers/IFileSavePickerExtension.skiaiOS.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using System.Threading; using Windows.Storage; +using Windows.Storage.Pickers; namespace Uno.Extensions.Storage.Pickers { @@ -17,6 +18,12 @@ namespace Uno.Extensions.Storage.Pickers public interface IFileSavePickerExtension { Task PickSaveFileAsync(CancellationToken token); + + // called just before Pick* methods to allow the customization of the native picker to match, + // as much as possible, the user selected properties of the WinUI picker + void Customize(FileSavePicker picker) + { + } } } #endif diff --git a/src/Uno.UWP/Storage/Pickers/Internal/IFolderPickerExtension.skia.cs b/src/Uno.UWP/Storage/Pickers/Internal/IFolderPickerExtension.skia.cs index e393aa9141e0..caa6f75cd78f 100644 --- a/src/Uno.UWP/Storage/Pickers/Internal/IFolderPickerExtension.skia.cs +++ b/src/Uno.UWP/Storage/Pickers/Internal/IFolderPickerExtension.skia.cs @@ -3,11 +3,18 @@ using System.Threading.Tasks; using System.Threading; using Windows.Storage; +using Windows.Storage.Pickers; namespace Uno.Extensions.Storage.Pickers { internal interface IFolderPickerExtension { Task PickSingleFolderAsync(CancellationToken token); + + // called just before Pick* methods to allow the customization of the native picker to match, + // as much as possible, the user selected properties of the WinUI picker + void Customize(FolderPicker picker) + { + } } }