Add support for folder importers
Some checks are pending
Deploy Docs / publish (push) Waiting to run
SwiftLint / SwiftLint (push) Waiting to run

This commit is contained in:
david-swift 2025-10-17 12:47:39 +02:00
parent 037a697c74
commit c8ce2cc2fe
3 changed files with 107 additions and 29 deletions

View File

@ -133,7 +133,6 @@ extension AnyView {
/// - open: The signal to open the dialog.
/// - initialFolder: The URL to the folder open when being opened.
/// - extensions: The accepted file extensions.
/// - folders: Whether folders are accepted.
/// - onOpen: Run this when a file for importing has been chosen.
/// - onClose: Run this when the user cancelled the action.
public func fileImporter(
@ -144,14 +143,34 @@ extension AnyView {
onClose: @escaping () -> Void
) -> AnyView {
FileDialog(
importer: true,
type: .importer(folder: false, extensions: extensions),
open: open,
child: self,
result: onOpen,
cancel: onClose,
initialFolder: initialFolder,
initialName: nil,
extensions: extensions
initialFolder: initialFolder
)
}
/// Create an importer file dialog for folders.
/// - Parameters:
/// - open: The signal to open the dialog.
/// - initialFolder: The URL to the folder open when being opened.
/// - onOpen: Run this when a file for importing has been chosen.
/// - onClose: Run this when the user cancelled the action.
public func folderImporter(
open: Signal,
initialFolder: URL? = nil,
onOpen: @escaping (URL) -> Void,
onClose: @escaping () -> Void
) -> AnyView {
FileDialog(
type: .importer(folder: true, extensions: nil),
open: open,
child: self,
result: onOpen,
cancel: onClose,
initialFolder: initialFolder
)
}
@ -170,13 +189,12 @@ extension AnyView {
onClose: @escaping () -> Void
) -> AnyView {
FileDialog(
importer: false,
type: .exporter(initialName: initialName),
open: open,
child: self,
result: onSave,
cancel: onClose,
initialFolder: initialFolder,
initialName: initialName
initialFolder: initialFolder
)
}

View File

@ -44,6 +44,26 @@ gtui_filedialog_open (uint64_t dialog, uint64_t data, uint64_t window)
gtk_file_dialog_open (dialog, window, NULL, G_CALLBACK (gtui_filedialog_open_finish), (void *)data);
}
static void
gtui_filedialog_open_folder_finish (uint64_t dialog, uint64_t result, uint64_t data)
{
GFile *file = gtk_file_dialog_select_folder_finish (dialog, result, NULL);
if (file != NULL) {
const char *path = g_file_peek_path (file);
g_object_unref (file);
filedialog_on_open_cb (dialog, path, data);
} else {
filedialog_on_open_cb (dialog, NULL, data);
}
}
static void
gtui_filedialog_open_folder (uint64_t dialog, uint64_t data, uint64_t window)
{
swift_retain (data);
gtk_file_dialog_select_folder (dialog, window, NULL, G_CALLBACK (gtui_filedialog_open_folder_finish), (void *)data);
}
static void
gtui_alertdialog_cb (uint64_t dialog, uint64_t result, uint64_t data)
{

View File

@ -11,54 +11,92 @@ import Foundation
/// A structure representing a file dialog window.
public struct FileDialog: AdwaitaWidget {
/// Whether the dialog is an importer.
var importer: Bool
/// The dialog type.
var type: DialogType
/// Whether the dialog should open.
var open: Signal
/// The dialog's child.
var child: AnyView
/// The initial folder.
var initialFolder: URL?
/// The initial file name for the file exporter.
var initialName: String?
/// The accepted extensions for the file importer.
var extensions: [String]?
/// The closure to run when the import or export is successful.
var result: (URL) -> Void
/// The closure to run when the import or export is not successful.
var cancel: () -> Void
// swiftlint:disable function_default_parameter_at_end
/// Initialize the file dialog wrapper.
/// - Parameters:
/// - importer: Whether it is an importer.
/// - type: The dialog type.
/// - open: The signal.
/// - child: The wrapped view.
/// - initialFolder: The initial URL.
/// - initialName: The initial name.
/// - extensions: The file extensions.
/// - result: Run when the import or export succeeds.
/// - cancel: Run when the import or export is not successful.
/// - initialFolder: The initial folder.
public init(
importer: Bool = true,
type: DialogType,
`open`: Signal,
child: AnyView,
result: @escaping (URL) -> Void,
cancel: @escaping () -> Void,
initialFolder: URL? = nil,
initialName: String? = nil,
extensions: [String]? = nil
) {
self.importer = importer
self.type = type
self.open = open
self.child = child
self.result = result
self.cancel = cancel
self.initialFolder = initialFolder
self.initialName = initialName
self.extensions = extensions
}
// swiftlint:enable function_default_parameter_at_end
/// The different types of dialogs and their properties.
public enum DialogType {
/// An importer dialog.
case importer(folder: Bool, extensions: [String]?)
/// An exporter dialog.
case exporter(initialName: String?)
/// Whether the dialog is an importer.
var isImporter: Bool {
switch self {
case .importer:
true
default:
false
}
}
/// The supported extensions.
var extensions: [String]? {
switch self {
case let .importer(folder: _, extensions: extensions):
extensions
default:
nil
}
}
/// Whether to import folders.
var folder: Bool {
switch self {
case let .importer(folder: folder, extensions: _):
folder
default:
false
}
}
/// The initial name.
var initialName: String? {
switch self {
case let .exporter(initialName: initialName):
initialName
default:
nil
}
}
}
/// The view storage.
/// - Parameters:
@ -94,10 +132,10 @@ public struct FileDialog: AdwaitaWidget {
var unref: [OpaquePointer?] = []
let pointer = gtk_file_dialog_new()
unref.append(pointer)
if let initialName {
if let initialName = self.type.initialName {
gtk_file_dialog_set_initial_name(pointer, initialName)
}
if let extensions {
if let extensions = self.type.extensions {
let filter = gtk_file_filter_new()
for name in extensions {
gtk_file_filter_add_suffix(filter, name)
@ -125,7 +163,9 @@ public struct FileDialog: AdwaitaWidget {
storage.fields["callbacks"] = callbacks
let ptr = UInt64(Int(bitPattern: pointer))
let window = UInt64(Int(bitPattern: gtk_widget_get_root(mainStorage.opaquePointer?.cast())))
if importer {
if self.type.isImporter && self.type.folder {
gtui_filedialog_open_folder(ptr, unsafeBitCast(callbacks, to: UInt64.self), window)
} else if self.type.isImporter {
gtui_filedialog_open(ptr, unsafeBitCast(callbacks, to: UInt64.self), window)
} else {
gtui_filedialog_save(ptr, unsafeBitCast(callbacks, to: UInt64.self), window)