Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions libs/sdl/sdl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1156,3 +1156,106 @@ DEFINE_PRIM(_ARR, get_display_modes, _I32);
DEFINE_PRIM(_DYN, get_current_display_mode, _I32 _BOOL);
DEFINE_PRIM(_ARR, get_devices, _NO_ARG);
DEFINE_PRIM(_BYTES, get_error, _NO_ARG);

// SDL Dialogs API
typedef struct {
vclosure **closure_store;
SDL_DialogFileFilter* filters;
} dialog_data;

dialog_data* CreateFileDialogData( vclosure *callback, varray *filters ) {
dialog_data *data = malloc( sizeof( dialog_data ) );
data->closure_store = malloc(sizeof(vclosure*));
*data->closure_store = callback;

if( filters && filters->size > 0 ) {
SDL_DialogFileFilter *sdl_filters = (SDL_DialogFileFilter *)malloc(sizeof( SDL_DialogFileFilter ) * filters->size );

for(int i=0;i<filters->size;i++) {
vdynamic *filter = hl_aptr(filters, vdynamic*)[i];
const char *name = (const char*) hl_dyn_getp(filter,hl_hash_utf8("name"),&hlt_bytes);
const char *pattern = (const char*) hl_dyn_getp(filter,hl_hash_utf8("pattern"),&hlt_bytes);

sdl_filters[i].name = name;
sdl_filters[i].pattern = pattern;
}

data->filters = sdl_filters;
}
else
data->filters = NULL;

hl_add_root(data->closure_store);

return data;
}

void FileDialogCallback(void *userdata, const char* const *filelist, int filter) {
// these callbacks may come via threads on some platforms
bool on_unregistered_thread = !hl_get_thread();
if( on_unregistered_thread ) {
vdynamic *ctx;
hl_register_thread(&ctx);
}
Comment thread
yuxiaomao marked this conversation as resolved.

dialog_data *data = (dialog_data*)userdata;
vclosure *vcallback = *data->closure_store;

int count = 0;

varray *array;
if( filelist ) {
const char * const *p = filelist;
while(*p++)
count++;

array = hl_alloc_array(&hlt_bytes, count );
vbyte **array_ptr = (vbyte **)hl_aptr(array, vbyte*);

for(int i = 0; i < count; i++) {
size_t len = strlen(filelist[i]) + 1; // Include the null terminator
vbyte *bytes = hl_alloc_bytes( len );
memcpy(bytes, filelist[i], len);
array_ptr[i] = bytes;
}
} else {
array = hl_alloc_array(&hlt_bytes, 0 );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I'm trying to call with a filter on Windows, it return immediatly an empty array without open any window (works normally if no filters specifield). From C side I saw that filelist is NULL so error occurs, checking the error string it says

Invalid dialog file filters: Invalid character in pattern (Only [a-zA-Z0-9_.-] allowed, or a single *

I think it would be good if we can at least give some information to the haxe side about the error, instead of returning empty list here (return NULL or throw?).

sdl.Sdl.showOpenFileDialog(function(arr) {
		trace("Open " + arr);
	}, null, [{ name : "hxml", pattern : "*.hxml" }]
);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one's kinda tricky since we can't validate filters before the callback (the SDL internal validate_filters function is not exposed to us), so showOpenFileDialog can only ever be void.

What I've done is to pass the NULL state into haxe, and throw getError() in that case. It works here, but I'm not sure there's any way to catch these since they fire in a thread, so I'm not super happy with the solution. The other option is we just pass null through and leave it to the user to read the API, which is more "correct" but probably not intuitive.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's only about filters, errors can come from elsewhere, so it's good that at least we return NULL from C side.
Yes I agree with you it's not trivial. Another related problem is if we throw inside the callback (either inside SDL.hx or user code), hl_remove_root and free are never called.

}

hl_call1( void, vcallback, varray*, array );
hl_remove_root( data->closure_store );
free( data->closure_store );
free( data->filters );
free( data );

if( on_unregistered_thread )
hl_unregister_thread();
}

HL_PRIM void HL_NAME(show_open_file_dialog)( vclosure *callback, SDL_Window *window, varray *filters, vstring *default_location, bool allow_many ) {
const char *location = default_location ? hl_to_utf8(default_location->bytes) : NULL;

dialog_data *data = CreateFileDialogData( callback, filters );

SDL_ShowOpenFileDialog( FileDialogCallback, data, window, data->filters, filters ? filters->size : 0, location, allow_many );
}

HL_PRIM void HL_NAME(show_open_folder_dialog)( vclosure *callback, SDL_Window *window, vstring *default_location, bool allow_many ) {
const char *location = default_location ? hl_to_utf8(default_location->bytes) : NULL;

dialog_data *data = CreateFileDialogData( callback, NULL );

SDL_ShowOpenFolderDialog( FileDialogCallback, data, window, location, allow_many );
}

HL_PRIM void HL_NAME(show_save_file_dialog)( vclosure *callback, SDL_Window *window, varray *filters, vstring *default_location ) {
const char *location = default_location ? hl_to_utf8(default_location->bytes) : NULL;

dialog_data *data = CreateFileDialogData( callback, filters );

SDL_ShowSaveFileDialog( FileDialogCallback, data, window, data->filters, filters ? filters->size : 0, location );
}

DEFINE_PRIM(_VOID, show_open_file_dialog, _FUN(_VOID, _ARR) TWIN _ARR _STRING _BOOL );
DEFINE_PRIM(_VOID, show_open_folder_dialog, _FUN(_VOID, _ARR) TWIN _STRING _BOOL );
DEFINE_PRIM(_VOID, show_save_file_dialog, _FUN(_VOID, _ARR) TWIN _ARR _STRING );
80 changes: 80 additions & 0 deletions libs/sdl/sdl/Sdl.hx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ typedef ScreenMode = {
var framerate : Int;
};

typedef FileDialogCallback = ( Array<String> ) -> Void;
typedef DialogFileFilter = {
public var name: String;
public var pattern: String;
}

@:hlNative("sdl")
class Sdl {

Expand Down Expand Up @@ -277,6 +283,80 @@ class Sdl {
private static function _getJoysticks() : hl.NativeArray<Int> {
return null;
}

//
// SDL3 Dialogs API
//

private static function processFilters(filters: Array<DialogFileFilter> = null) {
var nativeFilters = null;
if( filters != null ) {
nativeFilters = new hl.NativeArray<Dynamic>( filters.length );
for( i in 0 ... filters.length ) {
@:privateAccess {
nativeFilters[i] = {
"name": filters[i].name.toUtf8(),
"pattern": filters[i].pattern.toUtf8(),
};
}
}
}
return nativeFilters;

}

public static function showOpenFileDialog(callback: FileDialogCallback, window: sdl.Window = null, filters: Array<DialogFileFilter> = null, defaultLocation: String = null, allowMultiple: Bool = false) {
var cb = ( bytes: hl.NativeArray<hl.Bytes>) -> {
var files = [];
for( b in bytes )
files.push( @:privateAccess String.fromUTF8( b ) );

callback(files);
};

var nativeFilters = processFilters( filters );

_showOpenFileDialog( cb, window != null ? @:privateAccess window.win : null, nativeFilters, defaultLocation, allowMultiple);
}

public static function showOpenFolderDialog(callback: FileDialogCallback, window: sdl.Window = null, defaultLocation: String = null, allowMultiple: Bool = false) {
var cb = ( bytes: hl.NativeArray<hl.Bytes>) -> {
var files = [];
for( b in bytes )
files.push( @:privateAccess String.fromUTF8( b ) );

callback(files);
};

_showOpenFolderDialog( cb, window != null ? @:privateAccess window.win : null, defaultLocation, allowMultiple);
}

public static function showSaveFileDialog(callback: FileDialogCallback, window: sdl.Window = null, filters: Array<DialogFileFilter> = null, defaultLocation: String = null) {
var cb = ( bytes: hl.NativeArray<hl.Bytes>) -> {
var files = [];
for( b in bytes )
files.push( @:privateAccess String.fromUTF8( b ) );

callback(files);
};

var nativeFilters = processFilters( filters );

_showSaveFileDialog( cb, window != null ? @:privateAccess window.win : null, nativeFilters, defaultLocation);
}


@:hlNative("?sdl", "show_open_file_dialog")
private static function _showOpenFileDialog(callback: (bytes: hl.NativeArray<hl.Bytes>) -> Void, window: sdl.Window.WinPtr, filters: hl.NativeArray<Dynamic>, defaultLocation: String, allowMultiple: Bool) : Void {
}

@:hlNative("?sdl", "show_open_folder_dialog")
private static function _showOpenFolderDialog(callback: (bytes: hl.NativeArray<hl.Bytes>) -> Void, window: sdl.Window.WinPtr, defaultLocation: String, allowMultiple: Bool) : Void {
}

@:hlNative("?sdl", "show_save_file_dialog")
private static function _showSaveFileDialog(callback: (bytes: hl.NativeArray<hl.Bytes>) -> Void, window: sdl.Window.WinPtr, filters: hl.NativeArray<Dynamic>, defaultLocation: String) : Void {
}
}

enum abstract SDLHint(String) from String to String {
Expand Down
Loading