(wip) Studio format import, (wip) ogg decoder files, (ipgrade) imguifiledialog

This commit is contained in:
Anthony 2024-04-22 12:07:34 +02:00
parent 09ad937f1e
commit 83a6a3bc15
30 changed files with 29771 additions and 13203 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,496 @@
/*
This implements a data source that decodes Opus streams via libopus + libopusfile
This object can be plugged into any `ma_data_source_*()` API and can also be used as a custom
decoding backend. See the custom_decoder example.
You need to include this file after miniaudio.h.
*/
#ifndef miniaudio_libopus_h
#define miniaudio_libopus_h
#ifdef __cplusplus
extern "C" {
#endif
#if !defined(MA_NO_LIBOPUS)
#include <opusfile.h>
#endif
typedef struct
{
ma_data_source_base ds; /* The libopus decoder can be used independently as a data source. */
ma_read_proc onRead;
ma_seek_proc onSeek;
ma_tell_proc onTell;
void* pReadSeekTellUserData;
ma_format format; /* Will be either f32 or s16. */
#if !defined(MA_NO_LIBOPUS)
OggOpusFile* of;
#endif
} ma_libopus;
MA_API ma_result ma_libopus_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pOpus);
MA_API ma_result ma_libopus_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pOpus);
MA_API void ma_libopus_uninit(ma_libopus* pOpus, const ma_allocation_callbacks* pAllocationCallbacks);
MA_API ma_result ma_libopus_read_pcm_frames(ma_libopus* pOpus, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead);
MA_API ma_result ma_libopus_seek_to_pcm_frame(ma_libopus* pOpus, ma_uint64 frameIndex);
MA_API ma_result ma_libopus_get_data_format(ma_libopus* pOpus, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap);
MA_API ma_result ma_libopus_get_cursor_in_pcm_frames(ma_libopus* pOpus, ma_uint64* pCursor);
MA_API ma_result ma_libopus_get_length_in_pcm_frames(ma_libopus* pOpus, ma_uint64* pLength);
#ifdef __cplusplus
}
#endif
#endif
#if defined(MINIAUDIO_IMPLEMENTATION) || defined(MA_IMPLEMENTATION)
static ma_result ma_libopus_ds_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
{
return ma_libopus_read_pcm_frames((ma_libopus*)pDataSource, pFramesOut, frameCount, pFramesRead);
}
static ma_result ma_libopus_ds_seek(ma_data_source* pDataSource, ma_uint64 frameIndex)
{
return ma_libopus_seek_to_pcm_frame((ma_libopus*)pDataSource, frameIndex);
}
static ma_result ma_libopus_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap)
{
return ma_libopus_get_data_format((ma_libopus*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap);
}
static ma_result ma_libopus_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor)
{
return ma_libopus_get_cursor_in_pcm_frames((ma_libopus*)pDataSource, pCursor);
}
static ma_result ma_libopus_ds_get_length(ma_data_source* pDataSource, ma_uint64* pLength)
{
return ma_libopus_get_length_in_pcm_frames((ma_libopus*)pDataSource, pLength);
}
static ma_data_source_vtable g_ma_libopus_ds_vtable =
{
ma_libopus_ds_read,
ma_libopus_ds_seek,
ma_libopus_ds_get_data_format,
ma_libopus_ds_get_cursor,
ma_libopus_ds_get_length
};
#if !defined(MA_NO_LIBOPUS)
static int ma_libopus_of_callback__read(void* pUserData, unsigned char* pBufferOut, int bytesToRead)
{
ma_libopus* pOpus = (ma_libopus*)pUserData;
ma_result result;
size_t bytesRead;
result = pOpus->onRead(pOpus->pReadSeekTellUserData, (void*)pBufferOut, bytesToRead, &bytesRead);
if (result != MA_SUCCESS) {
return -1;
}
return (int)bytesRead;
}
static int ma_libopus_of_callback__seek(void* pUserData, ogg_int64_t offset, int whence)
{
ma_libopus* pOpus = (ma_libopus*)pUserData;
ma_result result;
ma_seek_origin origin;
if (whence == SEEK_SET) {
origin = ma_seek_origin_start;
} else if (whence == SEEK_END) {
origin = ma_seek_origin_end;
} else {
origin = ma_seek_origin_current;
}
result = pOpus->onSeek(pOpus->pReadSeekTellUserData, offset, origin);
if (result != MA_SUCCESS) {
return -1;
}
return 0;
}
static opus_int64 ma_libopus_of_callback__tell(void* pUserData)
{
ma_libopus* pOpus = (ma_libopus*)pUserData;
ma_result result;
ma_int64 cursor;
if (pOpus->onTell == NULL) {
return -1;
}
result = pOpus->onTell(pOpus->pReadSeekTellUserData, &cursor);
if (result != MA_SUCCESS) {
return -1;
}
return cursor;
}
#endif
static ma_result ma_libopus_init_internal(const ma_decoding_backend_config* pConfig, ma_libopus* pOpus)
{
ma_result result;
ma_data_source_config dataSourceConfig;
if (pOpus == NULL) {
return MA_INVALID_ARGS;
}
MA_ZERO_OBJECT(pOpus);
pOpus->format = ma_format_f32; /* f32 by default. */
if (pConfig != NULL && (pConfig->preferredFormat == ma_format_f32 || pConfig->preferredFormat == ma_format_s16)) {
pOpus->format = pConfig->preferredFormat;
} else {
/* Getting here means something other than f32 and s16 was specified. Just leave this unset to use the default format. */
}
dataSourceConfig = ma_data_source_config_init();
dataSourceConfig.vtable = &g_ma_libopus_ds_vtable;
result = ma_data_source_init(&dataSourceConfig, &pOpus->ds);
if (result != MA_SUCCESS) {
return result; /* Failed to initialize the base data source. */
}
return MA_SUCCESS;
}
MA_API ma_result ma_libopus_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pOpus)
{
ma_result result;
(void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libopus. */
result = ma_libopus_init_internal(pConfig, pOpus);
if (result != MA_SUCCESS) {
return result;
}
if (onRead == NULL || onSeek == NULL) {
return MA_INVALID_ARGS; /* onRead and onSeek are mandatory. */
}
pOpus->onRead = onRead;
pOpus->onSeek = onSeek;
pOpus->onTell = onTell;
pOpus->pReadSeekTellUserData = pReadSeekTellUserData;
#if !defined(MA_NO_LIBOPUS)
{
int libopusResult;
OpusFileCallbacks libopusCallbacks;
/* We can now initialize the Opus decoder. This must be done after we've set up the callbacks. */
libopusCallbacks.read = ma_libopus_of_callback__read;
libopusCallbacks.seek = ma_libopus_of_callback__seek;
libopusCallbacks.close = NULL;
libopusCallbacks.tell = ma_libopus_of_callback__tell;
pOpus->of = op_open_callbacks(pOpus, &libopusCallbacks, NULL, 0, &libopusResult);
if (pOpus->of == NULL) {
return MA_INVALID_FILE;
}
return MA_SUCCESS;
}
#else
{
/* libopus is disabled. */
return MA_NOT_IMPLEMENTED;
}
#endif
}
MA_API ma_result ma_libopus_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libopus* pOpus)
{
ma_result result;
(void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libopus. */
result = ma_libopus_init_internal(pConfig, pOpus);
if (result != MA_SUCCESS) {
return result;
}
#if !defined(MA_NO_LIBOPUS)
{
int libopusResult;
pOpus->of = op_open_file(pFilePath, &libopusResult);
if (pOpus->of == NULL) {
return MA_INVALID_FILE;
}
return MA_SUCCESS;
}
#else
{
/* libopus is disabled. */
(void)pFilePath;
return MA_NOT_IMPLEMENTED;
}
#endif
}
MA_API void ma_libopus_uninit(ma_libopus* pOpus, const ma_allocation_callbacks* pAllocationCallbacks)
{
if (pOpus == NULL) {
return;
}
(void)pAllocationCallbacks;
#if !defined(MA_NO_LIBOPUS)
{
op_free(pOpus->of);
}
#else
{
/* libopus is disabled. Should never hit this since initialization would have failed. */
MA_ASSERT(MA_FALSE);
}
#endif
ma_data_source_uninit(&pOpus->ds);
}
MA_API ma_result ma_libopus_read_pcm_frames(ma_libopus* pOpus, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
{
if (pFramesRead != NULL) {
*pFramesRead = 0;
}
if (frameCount == 0) {
return MA_INVALID_ARGS;
}
if (pOpus == NULL) {
return MA_INVALID_ARGS;
}
#if !defined(MA_NO_LIBOPUS)
{
/* We always use floating point format. */
ma_result result = MA_SUCCESS; /* Must be initialized to MA_SUCCESS. */
ma_uint64 totalFramesRead;
ma_format format;
ma_uint32 channels;
ma_libopus_get_data_format(pOpus, &format, &channels, NULL, NULL, 0);
totalFramesRead = 0;
while (totalFramesRead < frameCount) {
long libopusResult;
int framesToRead;
ma_uint64 framesRemaining;
framesRemaining = (frameCount - totalFramesRead);
framesToRead = 1024;
if (framesToRead > framesRemaining) {
framesToRead = (int)framesRemaining;
}
if (format == ma_format_f32) {
libopusResult = op_read_float(pOpus->of, (float*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), framesToRead * channels, NULL);
} else {
libopusResult = op_read (pOpus->of, (opus_int16*)ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), framesToRead * channels, NULL);
}
if (libopusResult < 0) {
result = MA_ERROR; /* Error while decoding. */
break;
} else {
totalFramesRead += libopusResult;
if (libopusResult == 0) {
result = MA_AT_END;
break;
}
}
}
if (pFramesRead != NULL) {
*pFramesRead = totalFramesRead;
}
if (result == MA_SUCCESS && totalFramesRead == 0) {
result = MA_AT_END;
}
return result;
}
#else
{
/* libopus is disabled. Should never hit this since initialization would have failed. */
MA_ASSERT(MA_FALSE);
(void)pFramesOut;
(void)frameCount;
(void)pFramesRead;
return MA_NOT_IMPLEMENTED;
}
#endif
}
MA_API ma_result ma_libopus_seek_to_pcm_frame(ma_libopus* pOpus, ma_uint64 frameIndex)
{
if (pOpus == NULL) {
return MA_INVALID_ARGS;
}
#if !defined(MA_NO_LIBOPUS)
{
int libopusResult = op_pcm_seek(pOpus->of, (ogg_int64_t)frameIndex);
if (libopusResult != 0) {
if (libopusResult == OP_ENOSEEK) {
return MA_INVALID_OPERATION; /* Not seekable. */
} else if (libopusResult == OP_EINVAL) {
return MA_INVALID_ARGS;
} else {
return MA_ERROR;
}
}
return MA_SUCCESS;
}
#else
{
/* libopus is disabled. Should never hit this since initialization would have failed. */
MA_ASSERT(MA_FALSE);
(void)frameIndex;
return MA_NOT_IMPLEMENTED;
}
#endif
}
MA_API ma_result ma_libopus_get_data_format(ma_libopus* pOpus, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap)
{
/* Defaults for safety. */
if (pFormat != NULL) {
*pFormat = ma_format_unknown;
}
if (pChannels != NULL) {
*pChannels = 0;
}
if (pSampleRate != NULL) {
*pSampleRate = 0;
}
if (pChannelMap != NULL) {
MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap);
}
if (pOpus == NULL) {
return MA_INVALID_OPERATION;
}
if (pFormat != NULL) {
*pFormat = pOpus->format;
}
#if !defined(MA_NO_LIBOPUS)
{
ma_uint32 channels = op_channel_count(pOpus->of, -1);
if (pChannels != NULL) {
*pChannels = channels;
}
if (pSampleRate != NULL) {
*pSampleRate = 48000;
}
if (pChannelMap != NULL) {
ma_channel_map_init_standard(ma_standard_channel_map_vorbis, pChannelMap, channelMapCap, channels);
}
return MA_SUCCESS;
}
#else
{
/* libopus is disabled. Should never hit this since initialization would have failed. */
MA_ASSERT(MA_FALSE);
return MA_NOT_IMPLEMENTED;
}
#endif
}
MA_API ma_result ma_libopus_get_cursor_in_pcm_frames(ma_libopus* pOpus, ma_uint64* pCursor)
{
if (pCursor == NULL) {
return MA_INVALID_ARGS;
}
*pCursor = 0; /* Safety. */
if (pOpus == NULL) {
return MA_INVALID_ARGS;
}
#if !defined(MA_NO_LIBOPUS)
{
ogg_int64_t offset = op_pcm_tell(pOpus->of);
if (offset < 0) {
return MA_INVALID_FILE;
}
*pCursor = (ma_uint64)offset;
return MA_SUCCESS;
}
#else
{
/* libopus is disabled. Should never hit this since initialization would have failed. */
MA_ASSERT(MA_FALSE);
return MA_NOT_IMPLEMENTED;
}
#endif
}
MA_API ma_result ma_libopus_get_length_in_pcm_frames(ma_libopus* pOpus, ma_uint64* pLength)
{
if (pLength == NULL) {
return MA_INVALID_ARGS;
}
*pLength = 0; /* Safety. */
if (pOpus == NULL) {
return MA_INVALID_ARGS;
}
#if !defined(MA_NO_LIBOPUS)
{
ogg_int64_t length = op_pcm_total(pOpus->of, -1);
if (length < 0) {
return MA_ERROR;
}
*pLength = (ma_uint64)length;
return MA_SUCCESS;
}
#else
{
/* libopus is disabled. Should never hit this since initialization would have failed. */
MA_ASSERT(MA_FALSE);
return MA_NOT_IMPLEMENTED;
}
#endif
}
#endif

View file

@ -0,0 +1,516 @@
/*
This implements a data source that decodes Vorbis streams via libvorbis + libvorbisfile
This object can be plugged into any `ma_data_source_*()` API and can also be used as a custom
decoding backend. See the custom_decoder example.
You need to include this file after miniaudio.h.
*/
#ifndef miniaudio_libvorbis_h
#define miniaudio_libvorbis_h
#ifdef __cplusplus
extern "C" {
#endif
#if !defined(MA_NO_LIBVORBIS)
#ifndef OV_EXCLUDE_STATIC_CALLBACKS
#define OV_EXCLUDE_STATIC_CALLBACKS
#endif
#include <vorbis/vorbisfile.h>
#endif
typedef struct
{
ma_data_source_base ds; /* The libvorbis decoder can be used independently as a data source. */
ma_read_proc onRead;
ma_seek_proc onSeek;
ma_tell_proc onTell;
void* pReadSeekTellUserData;
ma_format format; /* Will be either f32 or s16. */
#if !defined(MA_NO_LIBVORBIS)
OggVorbis_File vf;
#endif
} ma_libvorbis;
MA_API ma_result ma_libvorbis_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libvorbis* pVorbis);
MA_API ma_result ma_libvorbis_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libvorbis* pVorbis);
MA_API void ma_libvorbis_uninit(ma_libvorbis* pVorbis, const ma_allocation_callbacks* pAllocationCallbacks);
MA_API ma_result ma_libvorbis_read_pcm_frames(ma_libvorbis* pVorbis, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead);
MA_API ma_result ma_libvorbis_seek_to_pcm_frame(ma_libvorbis* pVorbis, ma_uint64 frameIndex);
MA_API ma_result ma_libvorbis_get_data_format(ma_libvorbis* pVorbis, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap);
MA_API ma_result ma_libvorbis_get_cursor_in_pcm_frames(ma_libvorbis* pVorbis, ma_uint64* pCursor);
MA_API ma_result ma_libvorbis_get_length_in_pcm_frames(ma_libvorbis* pVorbis, ma_uint64* pLength);
#ifdef __cplusplus
}
#endif
#endif
#if defined(MINIAUDIO_IMPLEMENTATION) || defined(MA_IMPLEMENTATION)
static ma_result ma_libvorbis_ds_read(ma_data_source* pDataSource, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
{
return ma_libvorbis_read_pcm_frames((ma_libvorbis*)pDataSource, pFramesOut, frameCount, pFramesRead);
}
static ma_result ma_libvorbis_ds_seek(ma_data_source* pDataSource, ma_uint64 frameIndex)
{
return ma_libvorbis_seek_to_pcm_frame((ma_libvorbis*)pDataSource, frameIndex);
}
static ma_result ma_libvorbis_ds_get_data_format(ma_data_source* pDataSource, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap)
{
return ma_libvorbis_get_data_format((ma_libvorbis*)pDataSource, pFormat, pChannels, pSampleRate, pChannelMap, channelMapCap);
}
static ma_result ma_libvorbis_ds_get_cursor(ma_data_source* pDataSource, ma_uint64* pCursor)
{
return ma_libvorbis_get_cursor_in_pcm_frames((ma_libvorbis*)pDataSource, pCursor);
}
static ma_result ma_libvorbis_ds_get_length(ma_data_source* pDataSource, ma_uint64* pLength)
{
return ma_libvorbis_get_length_in_pcm_frames((ma_libvorbis*)pDataSource, pLength);
}
static ma_data_source_vtable g_ma_libvorbis_ds_vtable =
{
ma_libvorbis_ds_read,
ma_libvorbis_ds_seek,
ma_libvorbis_ds_get_data_format,
ma_libvorbis_ds_get_cursor,
ma_libvorbis_ds_get_length
};
#if !defined(MA_NO_LIBVORBIS)
static size_t ma_libvorbis_vf_callback__read(void* pBufferOut, size_t size, size_t count, void* pUserData)
{
ma_libvorbis* pVorbis = (ma_libvorbis*)pUserData;
ma_result result;
size_t bytesToRead;
size_t bytesRead;
/* For consistency with fread(). If `size` of `count` is 0, return 0 immediately without changing anything. */
if (size == 0 || count == 0) {
return 0;
}
bytesToRead = size * count;
result = pVorbis->onRead(pVorbis->pReadSeekTellUserData, pBufferOut, bytesToRead, &bytesRead);
if (result != MA_SUCCESS) {
/* Not entirely sure what to return here. What if an error occurs, but some data was read and bytesRead is > 0? */
return 0;
}
return bytesRead / size;
}
static int ma_libvorbis_vf_callback__seek(void* pUserData, ogg_int64_t offset, int whence)
{
ma_libvorbis* pVorbis = (ma_libvorbis*)pUserData;
ma_result result;
ma_seek_origin origin;
if (whence == SEEK_SET) {
origin = ma_seek_origin_start;
} else if (whence == SEEK_END) {
origin = ma_seek_origin_end;
} else {
origin = ma_seek_origin_current;
}
result = pVorbis->onSeek(pVorbis->pReadSeekTellUserData, offset, origin);
if (result != MA_SUCCESS) {
return -1;
}
return 0;
}
static long ma_libvorbis_vf_callback__tell(void* pUserData)
{
ma_libvorbis* pVorbis = (ma_libvorbis*)pUserData;
ma_result result;
ma_int64 cursor;
result = pVorbis->onTell(pVorbis->pReadSeekTellUserData, &cursor);
if (result != MA_SUCCESS) {
return -1;
}
return (long)cursor;
}
#endif
static ma_result ma_libvorbis_init_internal(const ma_decoding_backend_config* pConfig, ma_libvorbis* pVorbis)
{
ma_result result;
ma_data_source_config dataSourceConfig;
if (pVorbis == NULL) {
return MA_INVALID_ARGS;
}
MA_ZERO_OBJECT(pVorbis);
pVorbis->format = ma_format_f32; /* f32 by default. */
if (pConfig != NULL && (pConfig->preferredFormat == ma_format_f32 || pConfig->preferredFormat == ma_format_s16)) {
pVorbis->format = pConfig->preferredFormat;
} else {
/* Getting here means something other than f32 and s16 was specified. Just leave this unset to use the default format. */
}
dataSourceConfig = ma_data_source_config_init();
dataSourceConfig.vtable = &g_ma_libvorbis_ds_vtable;
result = ma_data_source_init(&dataSourceConfig, &pVorbis->ds);
if (result != MA_SUCCESS) {
return result; /* Failed to initialize the base data source. */
}
return MA_SUCCESS;
}
MA_API ma_result ma_libvorbis_init(ma_read_proc onRead, ma_seek_proc onSeek, ma_tell_proc onTell, void* pReadSeekTellUserData, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libvorbis* pVorbis)
{
ma_result result;
(void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libvorbis. */
result = ma_libvorbis_init_internal(pConfig, pVorbis);
if (result != MA_SUCCESS) {
return result;
}
if (onRead == NULL || onSeek == NULL) {
return MA_INVALID_ARGS; /* onRead and onSeek are mandatory. */
}
pVorbis->onRead = onRead;
pVorbis->onSeek = onSeek;
pVorbis->onTell = onTell;
pVorbis->pReadSeekTellUserData = pReadSeekTellUserData;
#if !defined(MA_NO_LIBVORBIS)
{
int libvorbisResult;
ov_callbacks libvorbisCallbacks;
/* We can now initialize the vorbis decoder. This must be done after we've set up the callbacks. */
libvorbisCallbacks.read_func = ma_libvorbis_vf_callback__read;
libvorbisCallbacks.seek_func = ma_libvorbis_vf_callback__seek;
libvorbisCallbacks.close_func = NULL;
libvorbisCallbacks.tell_func = ma_libvorbis_vf_callback__tell;
libvorbisResult = ov_open_callbacks(pVorbis, &pVorbis->vf, NULL, 0, libvorbisCallbacks);
if (libvorbisResult < 0) {
return MA_INVALID_FILE;
}
return MA_SUCCESS;
}
#else
{
/* libvorbis is disabled. */
return MA_NOT_IMPLEMENTED;
}
#endif
}
MA_API ma_result ma_libvorbis_init_file(const char* pFilePath, const ma_decoding_backend_config* pConfig, const ma_allocation_callbacks* pAllocationCallbacks, ma_libvorbis* pVorbis)
{
ma_result result;
(void)pAllocationCallbacks; /* Can't seem to find a way to configure memory allocations in libvorbis. */
result = ma_libvorbis_init_internal(pConfig, pVorbis);
if (result != MA_SUCCESS) {
return result;
}
#if !defined(MA_NO_LIBVORBIS)
{
int libvorbisResult;
libvorbisResult = ov_fopen(pFilePath, &pVorbis->vf);
if (libvorbisResult < 0) {
return MA_INVALID_FILE;
}
return MA_SUCCESS;
}
#else
{
/* libvorbis is disabled. */
(void)pFilePath;
return MA_NOT_IMPLEMENTED;
}
#endif
}
MA_API void ma_libvorbis_uninit(ma_libvorbis* pVorbis, const ma_allocation_callbacks* pAllocationCallbacks)
{
if (pVorbis == NULL) {
return;
}
(void)pAllocationCallbacks;
#if !defined(MA_NO_LIBVORBIS)
{
ov_clear(&pVorbis->vf);
}
#else
{
/* libvorbis is disabled. Should never hit this since initialization would have failed. */
MA_ASSERT(MA_FALSE);
}
#endif
ma_data_source_uninit(&pVorbis->ds);
}
MA_API ma_result ma_libvorbis_read_pcm_frames(ma_libvorbis* pVorbis, void* pFramesOut, ma_uint64 frameCount, ma_uint64* pFramesRead)
{
if (pFramesRead != NULL) {
*pFramesRead = 0;
}
if (frameCount == 0) {
return MA_INVALID_ARGS;
}
if (pVorbis == NULL) {
return MA_INVALID_ARGS;
}
#if !defined(MA_NO_LIBVORBIS)
{
/* We always use floating point format. */
ma_result result = MA_SUCCESS; /* Must be initialized to MA_SUCCESS. */
ma_uint64 totalFramesRead;
ma_format format;
ma_uint32 channels;
ma_libvorbis_get_data_format(pVorbis, &format, &channels, NULL, NULL, 0);
totalFramesRead = 0;
while (totalFramesRead < frameCount) {
long libvorbisResult;
int framesToRead;
ma_uint64 framesRemaining;
framesRemaining = (frameCount - totalFramesRead);
framesToRead = 1024;
if (framesToRead > framesRemaining) {
framesToRead = (int)framesRemaining;
}
if (format == ma_format_f32) {
float** ppFramesF32;
libvorbisResult = ov_read_float(&pVorbis->vf, &ppFramesF32, framesToRead, NULL);
if (libvorbisResult < 0) {
result = MA_ERROR; /* Error while decoding. */
break;
} else {
/* Frames need to be interleaved. */
ma_interleave_pcm_frames(format, channels, libvorbisResult, (const void**)ppFramesF32, ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels));
totalFramesRead += libvorbisResult;
if (libvorbisResult == 0) {
result = MA_AT_END;
break;
}
}
} else {
libvorbisResult = ov_read(&pVorbis->vf, ma_offset_pcm_frames_ptr(pFramesOut, totalFramesRead, format, channels), framesToRead * ma_get_bytes_per_frame(format, channels), 0, 2, 1, NULL);
if (libvorbisResult < 0) {
result = MA_ERROR; /* Error while decoding. */
break;
} else {
/* Conveniently, there's no need to interleaving when using ov_read(). I'm not sure why ov_read_float() is different in that regard... */
totalFramesRead += libvorbisResult / ma_get_bytes_per_frame(format, channels);
if (libvorbisResult == 0) {
result = MA_AT_END;
break;
}
}
}
}
if (pFramesRead != NULL) {
*pFramesRead = totalFramesRead;
}
if (result == MA_SUCCESS && totalFramesRead == 0) {
result = MA_AT_END;
}
return result;
}
#else
{
/* libvorbis is disabled. Should never hit this since initialization would have failed. */
MA_ASSERT(MA_FALSE);
(void)pFramesOut;
(void)frameCount;
(void)pFramesRead;
return MA_NOT_IMPLEMENTED;
}
#endif
}
MA_API ma_result ma_libvorbis_seek_to_pcm_frame(ma_libvorbis* pVorbis, ma_uint64 frameIndex)
{
if (pVorbis == NULL) {
return MA_INVALID_ARGS;
}
#if !defined(MA_NO_LIBVORBIS)
{
int libvorbisResult = ov_pcm_seek(&pVorbis->vf, (ogg_int64_t)frameIndex);
if (libvorbisResult != 0) {
if (libvorbisResult == OV_ENOSEEK) {
return MA_INVALID_OPERATION; /* Not seekable. */
} else if (libvorbisResult == OV_EINVAL) {
return MA_INVALID_ARGS;
} else {
return MA_ERROR;
}
}
return MA_SUCCESS;
}
#else
{
/* libvorbis is disabled. Should never hit this since initialization would have failed. */
MA_ASSERT(MA_FALSE);
(void)frameIndex;
return MA_NOT_IMPLEMENTED;
}
#endif
}
MA_API ma_result ma_libvorbis_get_data_format(ma_libvorbis* pVorbis, ma_format* pFormat, ma_uint32* pChannels, ma_uint32* pSampleRate, ma_channel* pChannelMap, size_t channelMapCap)
{
/* Defaults for safety. */
if (pFormat != NULL) {
*pFormat = ma_format_unknown;
}
if (pChannels != NULL) {
*pChannels = 0;
}
if (pSampleRate != NULL) {
*pSampleRate = 0;
}
if (pChannelMap != NULL) {
MA_ZERO_MEMORY(pChannelMap, sizeof(*pChannelMap) * channelMapCap);
}
if (pVorbis == NULL) {
return MA_INVALID_OPERATION;
}
if (pFormat != NULL) {
*pFormat = pVorbis->format;
}
#if !defined(MA_NO_LIBVORBIS)
{
vorbis_info* pInfo = ov_info(&pVorbis->vf, 0);
if (pInfo == NULL) {
return MA_INVALID_OPERATION;
}
if (pChannels != NULL) {
*pChannels = pInfo->channels;
}
if (pSampleRate != NULL) {
*pSampleRate = pInfo->rate;
}
if (pChannelMap != NULL) {
ma_channel_map_init_standard(ma_standard_channel_map_vorbis, pChannelMap, channelMapCap, pInfo->channels);
}
return MA_SUCCESS;
}
#else
{
/* libvorbis is disabled. Should never hit this since initialization would have failed. */
MA_ASSERT(MA_FALSE);
return MA_NOT_IMPLEMENTED;
}
#endif
}
MA_API ma_result ma_libvorbis_get_cursor_in_pcm_frames(ma_libvorbis* pVorbis, ma_uint64* pCursor)
{
if (pCursor == NULL) {
return MA_INVALID_ARGS;
}
*pCursor = 0; /* Safety. */
if (pVorbis == NULL) {
return MA_INVALID_ARGS;
}
#if !defined(MA_NO_LIBVORBIS)
{
ogg_int64_t offset = ov_pcm_tell(&pVorbis->vf);
if (offset < 0) {
return MA_INVALID_FILE;
}
*pCursor = (ma_uint64)offset;
return MA_SUCCESS;
}
#else
{
/* libvorbis is disabled. Should never hit this since initialization would have failed. */
MA_ASSERT(MA_FALSE);
return MA_NOT_IMPLEMENTED;
}
#endif
}
MA_API ma_result ma_libvorbis_get_length_in_pcm_frames(ma_libvorbis* pVorbis, ma_uint64* pLength)
{
if (pLength == NULL) {
return MA_INVALID_ARGS;
}
*pLength = 0; /* Safety. */
if (pVorbis == NULL) {
return MA_INVALID_ARGS;
}
#if !defined(MA_NO_LIBVORBIS)
{
/* I don't know how to reliably retrieve the length in frames using libvorbis, so returning 0 for now. */
*pLength = 0;
return MA_SUCCESS;
}
#else
{
/* libvorbis is disabled. Should never hit this since initialization would have failed. */
MA_ASSERT(MA_FALSE);
return MA_NOT_IMPLEMENTED;
}
#endif
}
#endif

View file

@ -0,0 +1,34 @@
#include "resource_manager.h"
#include <algorithm>
struct Media {
std::string type;
std::string format;
};
static const std::unordered_map<std::string, Media> mediaTypes {
{".mp3", {"sound", "MP3"}},
{".wav", {"sound","WAV"}},
{".qoi", {"sound","QOI"}},
{".ogg", {"sound","OGG"}},
{".jpg", {"image", "JPEG"}},
{".jpeg",{"image", "JPEG"}},
{".png", {"image","PNG"}},
{".bmp", {"image","BMP"}},
};
std::string ResourceManager::ExtentionInfo(std::string extension, int info_type)
{
std::string lowerExtension = extension;
std::transform(lowerExtension.begin(), lowerExtension.end(), lowerExtension.begin(),
[](unsigned char c){ return std::tolower(c); });
auto it = mediaTypes.find(lowerExtension);
if (it != mediaTypes.end()) {
return info_type == 0 ? it->second.format : it->second.type;
} else {
return "UNKNOWN";
}
}

View file

@ -5,6 +5,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include <iostream> #include <iostream>
#include <unordered_map>
#include <ranges> #include <ranges>
#include "resource.h" #include "resource.h"
@ -13,6 +14,7 @@ class ResourceManager
{ {
public: public:
ResourceManager() ResourceManager()
: m_images(filter("image")) : m_images(filter("image"))
, m_sounds(filter("sound")) , m_sounds(filter("sound"))
@ -20,6 +22,8 @@ public:
} }
static std::string ExtentionInfo(std::string extension, int info_type);
~ResourceManager() { ~ResourceManager() {
} }

File diff suppressed because it is too large Load diff

View file

@ -306,6 +306,7 @@ std::string StoryProject::Normalize(const std::string &input)
std::replace(valid_file.begin(), valid_file.end(), '<', '_'); std::replace(valid_file.begin(), valid_file.end(), '<', '_');
std::replace(valid_file.begin(), valid_file.end(), '>', '_'); std::replace(valid_file.begin(), valid_file.end(), '>', '_');
std::replace(valid_file.begin(), valid_file.end(), '|', '_'); std::replace(valid_file.begin(), valid_file.end(), '|', '_');
std::replace(valid_file.begin(), valid_file.end(), ' ', '_');
return valid_file; return valid_file;
} }

View file

@ -162,6 +162,14 @@ set(SRCS
src/i_story_manager.h src/i_story_manager.h
src/miniz.c
src/zip.cpp
src/importers/pack_archive.cpp
src/importers/ni_parser.c
libs/ImGuiColorTextEdit/TextEditor.cpp libs/ImGuiColorTextEdit/TextEditor.cpp
libs/ImGuiColorTextEdit/TextEditor.h libs/ImGuiColorTextEdit/TextEditor.h
libs/imgui-node-editor/imgui_node_editor.cpp libs/imgui-node-editor/imgui_node_editor.cpp
@ -188,7 +196,10 @@ set(SRCS
../software/library/miniaudio.h ../software/library/miniaudio.h
../software/library/uuid.h ../software/library/uuid.h
../software/library/resource.h ../software/library/resource.h
../software/library/resource_manager.h ../software/library/resource_manager.h
../software/library/resource_manager.cpp
../software/library/story_project.cpp ../software/library/story_project.cpp
../software/library/story_project.h ../software/library/story_project.h
../software/library/thread_safe_queue.h ../software/library/thread_safe_queue.h
@ -225,6 +236,9 @@ target_include_directories(${STORY_EDITOR_PROJECT} PUBLIC
${curl_INCLUDE_DIR} ${curl_INCLUDE_DIR}
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/importers
../software/library/ ../software/library/
../software/chip32/ ../software/chip32/
../software/common ../software/common

File diff suppressed because it is too large Load diff

View file

@ -15,7 +15,7 @@
/* /*
MIT License MIT License
Copyright (c) 2018-2023 Stephane Cuillerdier (aka aiekick) Copyright (c) 2018-2024 Stephane Cuillerdier (aka aiekick)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -42,8 +42,7 @@ SOFTWARE.
/* /*
// generated with "Text to ASCII Art Generator (TAAG)" // generated with "Text to ASCII Art Generator (TAAG)"
// https://patorjk.com/software/taag/#p=display&h=1&v=0&f=Big&t=ImGuiFileDialog%0Av0.6.5 // https://patorjk.com/software/taag/#p=display&h=1&v=0&f=Big&t=ImGuiFileDialog%0Av0.6.7
_____ _____ _ ______ _ _ _____ _ _ _____ _____ _ ______ _ _ _____ _ _
|_ _| / ____| (_)| ____|(_)| | | __ \ (_) | | |_ _| / ____| (_)| ____|(_)| | | __ \ (_) | |
| | _ __ ___ | | __ _ _ _ | |__ _ | | ___ | | | | _ __ _ | | ___ __ _ | | _ __ ___ | | __ _ _ _ | |__ _ | | ___ | | | | _ __ _ | | ___ __ _
@ -73,7 +72,7 @@ solutions.
## ImGui Supported Version ## ImGui Supported Version
ImGuiFileDialog follow the master and docking branch of ImGui . currently ImGui 1.90.1 WIP ImGuiFileDialog follow the master and docking branch of ImGui . currently ImGui 1.90.4 WIP
## Structure ## Structure
@ -126,28 +125,27 @@ included in the Lib_Only branch for your convenience.
- 0 => Infinite - 0 => Infinite
- 1 => One file (default) - 1 => One file (default)
- n => n files - n => n files
- Compatible with MacOs, Linux, Windows - Compatible with MacOs, Linux, Windows, Emscripten, Android
- Windows version can list drives
- Supports modal or standard dialog types - Supports modal or standard dialog types
- Select files or directories - Select files or directories
- Filter groups and custom filter names - Filter groups and custom filter names
- can ignore filter Case for file searching - can ignore filter Case for file searching
- Keyboard navigation (arrows, backspace, enter) - Keyboard navigation (arrows, backspace, enter)
- Exploring by entering characters (case insensitive) - Exploring by entering characters (case insensitive)
- Directory bookmarks - Custom places (bookmarks, system devices, whatever you want)
- Directory manual entry (right click on any path element) - Directory manual entry (right click on any path element)
- Optional 'Confirm to Overwrite" dialog if file exists - Optional 'Confirm to Overwrite" dialog if file exists
- Thumbnails Display (agnostic way for compatibility with any backend, sucessfully tested with OpenGl and Vulkan) - Thumbnails Display (agnostic way for compatibility with any backend, sucessfully tested with OpenGl and Vulkan)
- The dialog can be embedded in another user frame than the standard or modal dialog - The dialog can be embedded in another user frame than the standard or modal dialog
- Can tune validation buttons (placements, widths, inversion) - Can tune validation buttons (placements, widths, inversion)
- Can quick select a parrallel directory of a path, in the path composer (when you clikc on a / you have a popup) - Can quick select a parrallel directory of a path, in the path composer (when you clikc on a / you have a popup)
- regex support for filters, collection of filters and filestyle (the regex is recognized when between (( and )) in a - regex support for filters, collection of filters and filestyle (the regex is recognized when between (( and )) in a filter)
filter)
- multi layer extentions like : .a.b.c .json.cpp .vcxproj.filters etc.. - multi layer extentions like : .a.b.c .json.cpp .vcxproj.filters etc..
- advanced behavior regarding asterisk based filter. like : .* .*.* .vcx.* .*.filters .vcs*.filt.* etc.. (internally - advanced behavior regarding asterisk based filter. like : .* .*.* .vcx.* .*.filters .vcs*.filt.* etc.. (internally regex is used)
regex is used) - result modes GetFilePathName, GetFileName and GetSelection (overwrite file ext, keep file, add ext if no user ext exist)
- result modes GetFilePathName, GetFileName and GetSelection (overwrite file ext, keep file, add ext if no user ext - you can use your own FileSystem Api
exist) - by default Api Dirent and std::filesystem are defined
- you can override GetDrieveList for specify by ex on android other fs, like local and SDCards
################################################################ ################################################################
## Filter format ## Filter format
@ -159,8 +157,8 @@ A filter is recognized only if he respects theses rules :
1) a regex must be in (( and )) 1) a regex must be in (( and ))
2) a , will separate filters except if between a ( and ) 2) a , will separate filters except if between a ( and )
3) name{filter1, filter2} is a special form for collection filters 3) name{filter1, filter2} is a special form for collection filters
3.1) the name can be composed of what you want except { and } - the name can be composed of what you want except { and }
3.2) a filter can be a regex - a filter can be a regex
4) the filters cannot integrate these chars '(' ')' '{' '}' ' ' except for a regex with respect to rule 1) 4) the filters cannot integrate these chars '(' ')' '{' '}' ' ' except for a regex with respect to rule 1)
5) the filters cannot integrate a ',' 5) the filters cannot integrate a ','
@ -197,7 +195,9 @@ void drawGui()
{ {
// open Dialog Simple // open Dialog Simple
if (ImGui::Button("Open File Dialog")) if (ImGui::Button("Open File Dialog"))
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", "."); IGFD::FileDialogConfig config;
config.path = ".";
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", config);
// display // display
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey"))
@ -231,8 +231,11 @@ ImGuiFileDialogFlags_Modal
you can use it like that : you can use it like that :
```cpp ```cpp
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", IGFD::FileDialogConfig config;
".", 1, nullptr, ImGuiFileDialogFlags_Modal); config.path = ".";
config.countSelectionMax = 1;
config.flags = ImGuiFileDialogFlags_Modal;
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", config);
``` ```
################################################################ ################################################################
@ -242,7 +245,9 @@ ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp
To have a directory chooser, set the file extension filter to nullptr: To have a directory chooser, set the file extension filter to nullptr:
```cpp ```cpp
ImGuiFileDialog::Instance()->OpenDialog("ChooseDirDlgKey", "Choose a Directory", nullptr, "."); IGFD::FileDialogConfig config;
config.path = ".";
ImGuiFileDialog::Instance()->OpenDialog("ChooseDirDlgKey", "Choose a Directory", nullptr, config);
``` ```
In this mode you can select any directory with one click and open a directory with a double-click. In this mode you can select any directory with one click and open a directory with a double-click.
@ -286,10 +291,16 @@ the user cant validate the dialog
void drawGui() void drawGui()
{ {
// open Dialog with Pane // open Dialog with Pane
if (ImGui::Button("Open File Dialog with a custom pane")) if (ImGui::Button("Open File Dialog with a custom pane")) {
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", IGFD::FileDialogConfig config;
".", "", std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 350, 1, config.path = ".";
UserDatas("InfosPane")); config.countSelectionMax = 1;
config.sidePane = std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
config.sidePaneWidth = 350.0f;
config.useDatas = UserDatas("InfosPane");
config.flags = ImGuiFileDialogFlags_Modal;
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", config);
}
// display and action if ok // display and action if ok
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey"))
@ -380,16 +391,13 @@ samples :
```cpp ```cpp
// define style by file extention and Add an icon for .png files // define style by file extention and Add an icon for .png files
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtention, ".png", ImVec4(0.0f, 1.0f, 1.0f, 0.9f), ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtention, ".png", ImVec4(0.0f, 1.0f, 1.0f, 0.9f),,ICON_IGFD_FILE_PIC, font1);
ICON_IGFD_FILE_PIC, font1); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtention, ".gif", ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtention, ".gif", ImVec4(0.0f, 1.0f, 0.5f, 0.9f), "[GIF]");
ImVec4(0.0f, 1.0f, 0.5f, 0.9f), "[GIF]");
// define style for all directories // define style for all directories
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir, "", ImVec4(0.5f, 1.0f, 0.9f, 0.9f), ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir, "", ImVec4(0.5f, 1.0f, 0.9f, 0.9f), ICON_IGFD_FOLDER);
ICON_IGFD_FOLDER);
// can be for a specific directory // can be for a specific directory
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir, ".git", ImVec4(0.5f, 1.0f, 0.9f, 0.9f), ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir, ".git", ImVec4(0.5f, 1.0f, 0.9f, 0.9f), ICON_IGFD_FOLDER);
ICON_IGFD_FOLDER);
// define style for all files // define style for all files
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile, "", ImVec4(0.5f, 1.0f, 0.9f, 0.9f), ICON_IGFD_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile, "", ImVec4(0.5f, 1.0f, 0.9f, 0.9f), ICON_IGFD_FILE);
@ -471,11 +479,13 @@ for filter names.
this code : this code :
```cpp ```cpp
const char *filters = "Source files (*.cpp *.h *.hpp){.cpp,.h,.hpp},Image files (*.png *.gif *.jpg const char *filters = "Source files (*.cpp *.h *.hpp){.cpp,.h,.hpp},Image files (*.png *.gif *.jpg *.jpeg){.png,.gif,.jpg,.jpeg},.md";
*.jpeg){.png,.gif,.jpg,.jpeg},.md"; ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", ICON_IMFDLG_FOLDER_OPEN IGFD::FileDialogConfig config;
" Choose a File", filters, "."); config.path = ".";
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", ICON_IMFDLG_FOLDER_OPEN " Choose a File", filters, config);
``` ```
will produce : will produce :
![alt text](https://github.com/aiekick/ImGuiFileDialog/blob/master/doc/filters.gif) ![alt text](https://github.com/aiekick/ImGuiFileDialog/blob/master/doc/filters.gif)
@ -492,13 +502,24 @@ You can define in OpenDialog call the count file you want to select :
See the define at the end of these funcs after path. See the define at the end of these funcs after path.
```cpp ```cpp
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".*,.cpp,.h,.hpp", "."); IGFD::FileDialogConfig config; config.path = ".";
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose 1 File", ".*,.cpp,.h,.hpp", ".", 1);
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose 5 File", ".*,.cpp,.h,.hpp", ".", 5); ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".*,.cpp,.h,.hpp", config);
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose many File", ".*,.cpp,.h,.hpp", ".", 0);
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".png,.jpg", config.countSelectionMax = 1;
".", "", std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 350, 1, ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose 1 File", ".*,.cpp,.h,.hpp", config);
"SaveFile"); // 1 file
config.countSelectionMax = 5;
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose 5 File", ".*,.cpp,.h,.hpp", config);
config.countSelectionMax = 0;
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose many File", ".*,.cpp,.h,.hpp", config);
config.countSelectionMax = 1;
config.sidePane = std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
config.sidePaneWidth = 350.0f;
config.useDatas = UserDatas("SaveFile");
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".png,.jpg", config); // 1 file
``` ```
![alt text](https://github.com/aiekick/ImGuiFileDialog/blob/master/doc/multiSelection.gif) ![alt text](https://github.com/aiekick/ImGuiFileDialog/blob/master/doc/multiSelection.gif)
@ -540,47 +561,6 @@ As you see the current item is flashed by default for 1 second. You can define t
```cpp ```cpp
ImGuiFileDialog::Instance()->SetFlashingAttenuationInSeconds(1.0f); ImGuiFileDialog::Instance()->SetFlashingAttenuationInSeconds(1.0f);
``` ```
################################################################
## Bookmarks
################################################################
You can create/edit/call path bookmarks and load/save them.
Activate this feature by uncommenting: `#define USE_BOOKMARK` in your custom config file (CustomImGuiFileDialogConfig.h)
More customization options:
```cpp
#define bookmarkPaneWith 150.0f => width of the bookmark pane
#define IMGUI_TOGGLE_BUTTON ToggleButton => customize the Toggled button (button stamp must be : (const char* label,
bool *toggle) #define bookmarksButtonString "Bookmark" => the text in the toggle button #define
bookmarksButtonHelpString "Bookmark" => the helper text when mouse over the button #define addBookmarkButtonString "+"
=> the button for add a bookmark #define removeBookmarkButtonString "-" => the button for remove the selected bookmark
```
* You can select each bookmark to edit the displayed name corresponding to a path
* Double-click on the label to apply the bookmark
![bookmarks.gif](https://github.com/aiekick/ImGuiFileDialog/blob/master/doc/bookmarks.gif)
You can also serialize/deserialize bookmarks (for example to load/save from/to a file):
```cpp
Load => ImGuiFileDialog::Instance()->DeserializeBookmarks(bookmarString);
Save => std::string bookmarkString = ImGuiFileDialog::Instance()->SerializeBookmarks();
```
(please see example code for details)
you can also add/remove bookmark by code :
and in this case, you can also avoid serialization of code based bookmark
```cpp
Add => ImGuiFileDialog::Instance()->AddBookmark(bookmark_name, bookmark_path);
Remove => ImGuiFileDialog::Instance()->RemoveBookmark(bookmark_name);
Save => std::string bookmarkString = ImGuiFileDialog::Instance()->SerializeBookmarks(true); // true for prevent
serialization of code based bookmarks
```
################################################################ ################################################################
## Path Edition : ## Path Edition :
################################################################ ################################################################
@ -607,17 +587,20 @@ behavior. (by design! :) )
Example code For Standard Dialog : Example code For Standard Dialog :
```cpp ```cpp
IGFD::FileDialogConfig config;
config.path = ".";
config.flags = ImGuiFileDialogFlags_ConfirmOverwrite;
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey",
ICON_IGFD_SAVE " Choose a File", filters, ICON_IGFD_SAVE " Choose a File", filters, config);
".", "", 1, nullptr, ImGuiFileDialogFlags_ConfirmOverwrite);
``` ```
Example code For Modal Dialog : Example code For Modal Dialog :
```cpp ```cpp
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", IGFD::FileDialogConfig config;
ICON_IGFD_SAVE " Choose a File", filters, config.path = ".";
".", "", 1, nullptr, ImGuiFileDialogFlags_Modal | ImGuiFileDialogFlags_ConfirmOverwrite); config.flags = ImGuiFileDialogFlags_Modal | ImGuiFileDialogFlags_ConfirmOverwrite;
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", ICON_IGFD_SAVE " Choose a File", filters, config);
``` ```
This dialog will only verify the file in the file field, not with `GetSelection()`. This dialog will only verify the file in the file field, not with `GetSelection()`.
@ -768,16 +751,19 @@ ex :
```cpp ```cpp
ImGuiFileDialog fileDialog; ImGuiFileDialog fileDialog;
// open dialog; in this case, Bookmark, directory creation are disabled with, and also the file input field is readonly. // open dialog; in this case, Place, directory creation are disabled with, and also the file input field is readonly.
// btw you can do what you want // btw you can od what you want
fileDialog.OpenDialog("embedded", "Select File", ".*", "", -1, nullptr, IGFD::FileDialogConfig config;
ImGuiFileDialogFlags_NoDialog | config.path = ".";
ImGuiFileDialogFlags_DisableBookmarkMode | config.countSelectionMax = -1;
config.flags = ImGuiFileDialogFlags_NoDialog |
ImGuiFileDialogFlags_DisablePlaceMode |
ImGuiFileDialogFlags_DisableCreateDirectoryButton | ImGuiFileDialogFlags_DisableCreateDirectoryButton |
ImGuiFileDialogFlags_ReadOnlyFileNameField); ImGuiFileDialogFlags_ReadOnlyFileNameField);
fileDialog.OpenDialog("embedded", "Select File", ".*", config);
// then display, here // then display, here
// to note, when embedded the ImVec2(0,0) (MinSize) do nothing, only the ImVec2(0,350) (MaxSize) can size the dialog // to note, when embedded the ImVec2(0,0) (MinSize) do nothing, only the ImVec2(0,350) (MaxSize) can size the dialog frame
frame fileDialog.Display("embedded", ImGuiWindowFlags_NoCollapse, ImVec2(0,0), ImVec2(0,350))) fileDialog.Display("embedded", ImGuiWindowFlags_NoCollapse, ImVec2(0,0), ImVec2(0,350)))
``` ```
the result : the result :
@ -1002,6 +988,122 @@ by ex for android, emscripten, or boost
you can check the DemoApp who is using an override for the Boost::filesystem you can check the DemoApp who is using an override for the Boost::filesystem
################################################################
## Modify file infos during scan by a callback
################################################################
in some case, it can be unsefull for modify file infos
so you can define your callback and attached it in the FileDialogConfig struct in the field userFileAttributes
the callback stamp is :
```cpp
bool (IGFD::FileInfos* vFileInfosPtr, IGFD::UserDatas vUserDatas)
```
if the callback is returning false, the file is ignored, so not displayed by the dailog
example in the gltf separated files : (see the branch DemoApp for example use)
A gltf file can have data description and datas files separated.
in this case only the file with description will be shown in the dialog, so with not the full size of all attached datas
With the file format .gltf who is containing datas in a separate .bin
syntax :
```cpp
config.userFileAttributes = [](IGFD::FileInfos* vFileInfosPtr, IGFD::UserDatas vUserDatas) -> bool {
if (vFileInfosPtr != nullptr) {
// this demo not take into account .gltf who have data insise. besauce keepd easy just for demo
if (vFileInfosPtr->SearchForExt(".gltf", true)) {
auto bin_file_path_name = vFileInfosPtr->filePath + IGFD::Utils::GetPathSeparator() + vFileInfosPtr->fileNameLevels[0] + ".bin";
struct stat statInfos = {};
char timebuf[100];
int result = stat(bin_file_path_name.c_str(), &statInfos);
if (!result) {
vFileInfosPtr->fileSize = (size_t)statInfos.st_size;
} else {
// no bin, so escaped.
// normally we must parse the file and check the uri for get the buffer file
// but here we keep the example as easy for demo.
return false;
}
}
}
return true;
};
```
you can also display a tootlip for a file displayed when the mouse is over a dedicated column
you juste need to set your message for the FileDialogConfig.tooltipMessage
and specify the column in FileDialogConfig.tooltipColumn
ex code from the DemoApp branch for display the decomposition of gltf total size
syntax :
```cpp
vFileInfosPtr->tooltipMessage = toStr("%s : %s\n%s : %s", //
(vFileInfosPtr->fileNameLevels[0] + ".gltf").c_str(), //
IGFD::Utils::FormatFileSize(vFileInfosPtr->fileSize).c_str(), //
(vFileInfosPtr->fileNameLevels[0] + ".bin").c_str(), //
IGFD::Utils::FormatFileSize((size_t)statInfos.st_size).c_str()); //
vFileInfosPtr->tooltipColumn = 1;
```
################################################################
## Places
################################################################
the Places system is a generic way for add custom links in the left side pane
you can organize them by groups
The bookmarks and devices are now groups in the left side pane.
for using it you need to
```cpp
#define USE_PLACES_FEATURE
// for have default bookmark editable groups
#define USE_PLACES_BOOKMARKS
// for have default groups for system devices (returned by the IFileSystem interface)
#define USE_PLACES_DEVICES
```
see the config file for more customization
you can also add your custom groups editable or not like what is done
the DemoApp branch with the "quick access" paths of win10
You must add a group first, then add a place to it :
```cpp
// you must add a group first, specifu display order, and say :
// if the user can add or remove palce like (bookmarks)
// if the group is opened by default
ImGuiFileDialog::Instance()->AddPlacesGroup(group_name, display_order, can_be_user_edited, opened_by_default);
// then you must get the group
auto places_ptr = ImGuiFileDialog::Instance()->GetPlacesGroupPtr(group_name);
if (places_ptr != nullptr) {
// then add a place to the group
// you msut specify the place name, the palce path, say if the palce can be serialized, and sepcify the style
// for the moment the style support only the icon, can be extended if user needed in futur
places_ptr->AddPlace(place_name, place_path, can_be_saved, style);
// you can also add a separator
places_ptr->AddPlaceSeparator(separator_thickness);
}
```
for editable group :
* You can select each place to edit the displayed name corresponding to a path
* Double-click on the label to apply the place
You can also serialize/deserialize groups and places (for example to load/save from/to a file):
```cpp
Load => ImGuiFileDialog::Instance()->DeserializePlaces(placesString);
Save => std::string placesString = ImGuiFileDialog::Instance()->SerializePlaces();
```
################################################################ ################################################################
## How to Integrate ImGuiFileDialog in your project ## How to Integrate ImGuiFileDialog in your project
################################################################ ################################################################
@ -1041,20 +1143,15 @@ Sample code with cimgui :
ImGuiFileDialog *cfileDialog = IGFD_Create(); ImGuiFileDialog *cfileDialog = IGFD_Create();
// open dialog // open dialog
if (igButton("Open File", buttonSize)) if (igButton("Open File", buttonSize)) {
{ IGFD_FileDialog_Config config = IGFD_FileDialog_Config_Get();
config.path = ".";
config.flags = ImGuiFileDialogFlags_ConfirmOverwrite; // ImGuiFileDialogFlags
IGFD_OpenDialog(cfiledialog, IGFD_OpenDialog(cfiledialog,
"filedlg", // dialog key (make it possible to have different treatment reagrding "filedlg", // dialog key (make it possible to have different treatment reagrding the dialog key
the dialog key "Open a File", // dialog title "c files(*.c *.h){.c,.h}", // dialog "Open a File", // dialog title
filter syntax : simple => .h,.c,.pp, etc and collections : text1{filter0,filter1,filter2}, "c files(*.c *.h){.c,.h}", // dialog filter syntax : simple => .h,.c,.pp, etc and collections : text1{filter0,filter1,filter2}, text2{filter0,filter1,filter2}, etc..
text2{filter0,filter1,filter2}, etc.. config); // the file dialog config
".", // base directory for files scan
"", // base filename
0, // a fucntion for display a right pane if you want
0.0f, // base width of the pane
0, // count selection : 0 infinite, 1 one file (default), n (n files)
"User data !", // some user datas
ImGuiFileDialogFlags_ConfirmOverwrite); // ImGuiFileDialogFlags
} }
ImGuiIO* ioptr = igGetIO(); ImGuiIO* ioptr = igGetIO();
@ -1121,7 +1218,7 @@ The Custom Icon Font (in CustomFont.cpp and CustomFont.h) was made with ImGuiFon
#pragma region IGFD VERSION #pragma region IGFD VERSION
// compatible with 1.90.1 WIP // compatible with 1.90.4 WIP
#define IMGUIFILEDIALOG_VERSION "v0.6.7" #define IMGUIFILEDIALOG_VERSION "v0.6.7"
#pragma endregion #pragma endregion
@ -1169,7 +1266,7 @@ enum ImGuiFileDialogFlags_ {
ImGuiFileDialogFlags_CaseInsensitiveExtention = (1 << 8), // the file extentions treatments will not take into account the case ImGuiFileDialogFlags_CaseInsensitiveExtention = (1 << 8), // the file extentions treatments will not take into account the case
ImGuiFileDialogFlags_Modal = (1 << 9), // modal ImGuiFileDialogFlags_Modal = (1 << 9), // modal
ImGuiFileDialogFlags_DisableThumbnailMode = (1 << 10), // disable the thumbnail mode ImGuiFileDialogFlags_DisableThumbnailMode = (1 << 10), // disable the thumbnail mode
ImGuiFileDialogFlags_DisableBookmarkMode = (1 << 11), // disable the bookmark mode ImGuiFileDialogFlags_DisablePlaceMode = (1 << 11), // disable the place mode
ImGuiFileDialogFlags_DisableQuickPathSelection = (1 << 12), // disable the quick path selection ImGuiFileDialogFlags_DisableQuickPathSelection = (1 << 12), // disable the quick path selection
// default behavior when no flags is defined. seems to be the more common cases // default behavior when no flags is defined. seems to be the more common cases
@ -1278,6 +1375,7 @@ struct IGFD_Thumbnail_Info {
#include <regex> #include <regex>
#include <array> #include <array>
#include <mutex> #include <mutex>
#include <condition_variable>
#include <thread> #include <thread>
#include <cfloat> #include <cfloat>
#include <memory> #include <memory>
@ -1429,10 +1527,14 @@ public:
static void SetBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr); static void SetBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr);
static std::string UTF8Encode(const std::wstring& wstr); static std::string UTF8Encode(const std::wstring& wstr);
static std::wstring UTF8Decode(const std::string& str); static std::wstring UTF8Decode(const std::string& str);
static std::vector<std::string> SplitStringToVector(const std::string& vText, const std::string& vDelimiterPattern, const bool& vPushEmpty);
static std::vector<std::string> SplitStringToVector(const std::string& vText, const char& vDelimiter, const bool& vPushEmpty); static std::vector<std::string> SplitStringToVector(const std::string& vText, const char& vDelimiter, const bool& vPushEmpty);
static std::string LowerCaseString(const std::string& vString); // turn all text in lower case for search facilitie static std::string LowerCaseString(const std::string& vString); // turn all text in lower case for search facilitie
static size_t GetCharCountInString(const std::string& vString, const char& vChar); static size_t GetCharCountInString(const std::string& vString, const char& vChar);
static size_t GetLastCharPosWithMinCharCount(const std::string& vString, const char& vChar, const size_t& vMinCharCount); static size_t GetLastCharPosWithMinCharCount(const std::string& vString, const char& vChar, const size_t& vMinCharCount);
static std::string GetPathSeparator(); // return the slash for any OS ( \\ win, / unix)
static std::string RoundNumber(double vvalue, int n); // custom rounding number
static std::string FormatFileSize(size_t vByteSize); // format file size field
}; };
#pragma endregion #pragma endregion
@ -1598,11 +1700,18 @@ public:
// 10 level max are sufficient i guess. the others levels will be checked if countExtDot > 1 // 10 level max are sufficient i guess. the others levels will be checked if countExtDot > 1
std::array<std::string, EXT_MAX_LEVEL> fileExtLevels; std::array<std::string, EXT_MAX_LEVEL> fileExtLevels;
std::array<std::string, EXT_MAX_LEVEL> fileExtLevels_optimized; // optimized for search => insensitivecase std::array<std::string, EXT_MAX_LEVEL> fileExtLevels_optimized; // optimized for search => insensitivecase
// same for file name, can be sued in userFileAttributesFun
std::array<std::string, EXT_MAX_LEVEL> fileNameLevels;
std::array<std::string, EXT_MAX_LEVEL> fileNameLevels_optimized; // optimized for search => insensitivecase
size_t countExtDot = 0U; // count dots in file extention. this count will give the levels in fileExtLevels size_t countExtDot = 0U; // count dots in file extention. this count will give the levels in fileExtLevels
FileType fileType; // fileType FileType fileType; // fileType
std::string filePath; // path of the file std::string filePath; // path of the file
std::string fileNameExt; // filename of the file (file name + extention) (but no pat std::string fileName; // file name only
std::string fileNameExt; // filename of the file (file name + extention) (but no path)
std::string fileNameExt_optimized; // optimized for search => insensitivecase std::string fileNameExt_optimized; // optimized for search => insensitivecase
std::string deviceInfos; // quick infos to display after name for devices
std::string tooltipMessage; // message to display on the tooltip, is not empty
int32_t tooltipColumn = -1; // the tooltip will appears only when the mouse is over the tooltipColumn if > -1
size_t fileSize = 0U; // for sorting operations size_t fileSize = 0U; // for sorting operations
std::string formatedFileSize; // file size formated (10 o, 10 ko, 10 mo, 10 go) std::string formatedFileSize; // file size formated (10 o, 10 ko, 10 mo, 10 go)
std::string fileModifDate; // file user defined format of the date (data + time by default) std::string fileModifDate; // file user defined format of the date (data + time by default)
@ -1626,8 +1735,11 @@ public:
#pragma region FILE SYSTEM INTERFACE #pragma region FILE SYSTEM INTERFACE
typedef std::pair<std::string, std::string> PathDisplayedName;
class IFileSystem { class IFileSystem {
public: public:
virtual ~IFileSystem() = default;
// say if a directory can be openened or for any reason locked // say if a directory can be openened or for any reason locked
virtual bool IsDirectoryCanBeOpened(const std::string& vName) = 0; virtual bool IsDirectoryCanBeOpened(const std::string& vName) = 0;
// say if a directory exist // say if a directory exist
@ -1636,14 +1748,14 @@ public:
virtual bool IsFileExist(const std::string& vName) = 0; virtual bool IsFileExist(const std::string& vName) = 0;
// say if a directory was created, return false if vName is invalid or alreayd exist // say if a directory was created, return false if vName is invalid or alreayd exist
virtual bool CreateDirectoryIfNotExist(const std::string& vName) = 0; virtual bool CreateDirectoryIfNotExist(const std::string& vName) = 0;
// extract the component of a file apth name, like path, name, ext // extract the component of a file path name, like path, name, ext
virtual IGFD::Utils::PathStruct ParsePathFileName(const std::string& vPathFileName) = 0; virtual IGFD::Utils::PathStruct ParsePathFileName(const std::string& vPathFileName) = 0;
// will return a list of files inside a path // will return a list of files inside a path
virtual std::vector<IGFD::FileInfos> ScanDirectory(const std::string& vPath) = 0; virtual std::vector<IGFD::FileInfos> ScanDirectory(const std::string& vPath) = 0;
// say if the path is well a directory // say if the path is well a directory
virtual bool IsDirectory(const std::string& vFilePathName) = 0; virtual bool IsDirectory(const std::string& vFilePathName) = 0;
// return a drive list on windows, bu can be used on android or linux for give to the suer a list of root dir // return a device list (<path, device name>) on windows, but can be used on other platforms for give to the user a list of devices paths.
virtual std::vector<std::string> GetDrivesList() = 0; virtual std::vector<IGFD::PathDisplayedName> GetDevicesList() = 0;
}; };
#pragma endregion #pragma endregion
@ -1676,6 +1788,7 @@ private:
std::string m_LastSelectedFileName; // for shift multi selection std::string m_LastSelectedFileName; // for shift multi selection
std::set<std::string> m_SelectedFileNames; // the user selection of FilePathNames std::set<std::string> m_SelectedFileNames; // the user selection of FilePathNames
bool m_CreateDirectoryMode = false; // for create directory widget bool m_CreateDirectoryMode = false; // for create directory widget
std::string m_FileSystemName;
std::unique_ptr<IFileSystem> m_FileSystemPtr = nullptr; std::unique_ptr<IFileSystem> m_FileSystemPtr = nullptr;
public: public:
@ -1699,7 +1812,6 @@ public:
defaultSortOrderFilename, defaultSortOrderType, defaultSortOrderSize, defaultSortOrderDate}; defaultSortOrderFilename, defaultSortOrderType, defaultSortOrderSize, defaultSortOrderDate};
#endif #endif
SortingFieldEnum sortingField = SortingFieldEnum::FIELD_FILENAME; // detail view sorting column SortingFieldEnum sortingField = SortingFieldEnum::FIELD_FILENAME; // detail view sorting column
bool showDrives = false; // drives are shown (only on os windows)
std::string dLGpath; // base path set by user when OpenDialog was called std::string dLGpath; // base path set by user when OpenDialog was called
std::string dLGDefaultFileName; // base default file path name set by user when OpenDialog was called std::string dLGDefaultFileName; // base default file path name set by user when OpenDialog was called
@ -1713,9 +1825,7 @@ public:
#else #else
private: private:
#endif #endif
static std::string m_RoundNumber(double vvalue, int n); // custom rounding number static void m_CompleteFileInfos(const std::shared_ptr<FileInfos>& vInfos); // set time and date infos of a file (detail view mode)
static std::string m_FormatFileSize(size_t vByteSize); // format file size field
static void m_CompleteFileInfos(const std::shared_ptr<FileInfos>& FileInfos); // set time and date infos of a file (detail view mode)
void m_RemoveFileNameInSelection(const std::string& vFileName); // selection : remove a file name void m_RemoveFileNameInSelection(const std::string& vFileName); // selection : remove a file name
void m_m_AddFileNameInSelection(const std::string& vFileName, bool vSetLastSelectionFileName); // selection : add a file name void m_m_AddFileNameInSelection(const std::string& vFileName, bool vSetLastSelectionFileName); // selection : add a file name
void m_AddFile(const FileDialogInternal& vFileDialogInternal, void m_AddFile(const FileDialogInternal& vFileDialogInternal,
@ -1737,6 +1847,7 @@ private:
void m_SortFields(const FileDialogInternal& vFileDialogInternal, void m_SortFields(const FileDialogInternal& vFileDialogInternal,
std::vector<std::shared_ptr<FileInfos>>& vFileInfosList, std::vector<std::shared_ptr<FileInfos>>& vFileInfosList,
std::vector<std::shared_ptr<FileInfos>>& vFileInfosFilteredList); // will sort a column std::vector<std::shared_ptr<FileInfos>>& vFileInfosFilteredList); // will sort a column
bool m_CompleteFileInfosWithUserFileAttirbutes(const FileDialogInternal& vFileDialogInternal, const std::shared_ptr<FileInfos>& vInfos);
public: public:
FileManager(); FileManager();
@ -1788,6 +1899,9 @@ public:
IFileSystem* GetFileSystemInstance() { IFileSystem* GetFileSystemInstance() {
return m_FileSystemPtr.get(); return m_FileSystemPtr.get();
} }
const std::string& GetFileSystemName() {
return m_FileSystemName;
}
}; };
#pragma endregion #pragma endregion
@ -1796,6 +1910,20 @@ public:
typedef void* UserDatas; typedef void* UserDatas;
typedef std::function<void(const char*, UserDatas, bool*)> PaneFun; // side pane function binding typedef std::function<void(const char*, UserDatas, bool*)> PaneFun; // side pane function binding
typedef std::function<bool(FileInfos*, UserDatas)> UserFileAttributesFun; // custom file Attributes call back, reject file if false
struct IGFD_API FileDialogConfig {
std::string path; // path
std::string fileName; // defaut file name
std::string filePathName; // if not empty, the filename and the path will be obtained from filePathName
int32_t countSelectionMax = 1; // count selection max
UserDatas userDatas = nullptr; // user datas (can be retrieved in pane)
ImGuiFileDialogFlags flags = ImGuiFileDialogFlags_None; // ImGuiFileDialogFlags
PaneFun sidePane; // side pane callback
float sidePaneWidth = 250.0f; // side pane width
UserFileAttributesFun userFileAttributes; // user file Attibutes callback
};
class IGFD_API FileDialogInternal { class IGFD_API FileDialogInternal {
public: public:
FileManager fileManager; // the file manager FileManager fileManager; // the file manager
@ -1815,20 +1943,27 @@ public:
bool fileListViewIsActive = false; // when list view is active bool fileListViewIsActive = false; // when list view is active
std::string dLGkey; // the dialog key std::string dLGkey; // the dialog key
std::string dLGtitle; // the dialog title std::string dLGtitle; // the dialog title
ImGuiFileDialogFlags dLGflags = ImGuiFileDialogFlags_None; // default dialog flag
UserDatas dLGuserDatas = nullptr; // the user datas passed to a dialog
PaneFun dLGoptionsPane = nullptr; // the user side pane
float dLGoptionsPaneWidth = 0.0f; // the user side pane width
bool needToExitDialog = false; // we need to exit the dialog bool needToExitDialog = false; // we need to exit the dialog
bool puUseCustomLocale = false; // custom user locale bool puUseCustomLocale = false; // custom user locale
int localeCategory = LC_ALL; // locale category to use int localeCategory = LC_ALL; // locale category to use
std::string localeBegin; // the locale who will be applied at start of the display dialog std::string localeBegin; // the locale who will be applied at start of the display dialog
std::string localeEnd; // the locale who will be applaied at end of the display dialog std::string localeEnd; // the locale who will be applaied at end of the display dialog
private:
FileDialogConfig m_DialogConfig;
public: public:
void NewFrame(); // new frame, so maybe neded to do somethings, like reset events void NewFrame(); // new frame, so maybe neded to do somethings, like reset events
void EndFrame(); // end frame, so maybe neded to do somethings fater all void EndFrame(); // end frame, so maybe neded to do somethings fater all
void ResetForNewDialog(); // reset what is needed to reset for the openging of a new dialog void ResetForNewDialog(); // reset what is needed to reset for the openging of a new dialog
void configureDialog( // open simple dialog
const std::string& vKey, // key dialog
const std::string& vTitle, // title
const char* vFilters, // filters, if null, will display only directories
const FileDialogConfig& vConfig); // FileDialogConfig
const FileDialogConfig& getDialogConfig() const;
FileDialogConfig& getDialogConfigRef();
}; };
#pragma endregion #pragma endregion
@ -1862,6 +1997,7 @@ private:
std::shared_ptr<std::thread> m_ThumbnailGenerationThread = nullptr; std::shared_ptr<std::thread> m_ThumbnailGenerationThread = nullptr;
std::list<std::shared_ptr<FileInfos>> m_ThumbnailFileDatasToGet; // base container std::list<std::shared_ptr<FileInfos>> m_ThumbnailFileDatasToGet; // base container
std::mutex m_ThumbnailFileDatasToGetMutex; std::mutex m_ThumbnailFileDatasToGetMutex;
std::condition_variable m_ThumbnailFileDatasToGetCv;
std::list<std::shared_ptr<FileInfos>> m_ThumbnailToCreate; // base container std::list<std::shared_ptr<FileInfos>> m_ThumbnailToCreate; // base container
std::mutex m_ThumbnailToCreateMutex; std::mutex m_ThumbnailToCreateMutex;
std::list<IGFD_Thumbnail_Info> m_ThumbnailToDestroy; // base container std::list<IGFD_Thumbnail_Info> m_ThumbnailToDestroy; // base container
@ -1899,48 +2035,70 @@ public:
#pragma endregion #pragma endregion
#pragma region BookMarkFeature #pragma region PlacesFeature
class IGFD_API BookMarkFeature { class IGFD_API PlacesFeature {
protected: protected:
BookMarkFeature(); PlacesFeature();
#ifdef USE_BOOKMARK #ifdef USE_PLACES_FEATURE
private: private:
struct BookmarkStruct { struct PlaceStruct {
std::string name; // name of the bookmark std::string name; // name of the place
// todo: the path could be relative, better if the app is moved but place path can be outside of the app
std::string path; // absolute path of the place
bool canBeSaved = true; // defined by code, can be used for prevent serialization / deserialization
FileStyle style;
float thickness = 0.0f; // when more than 0.0f, is a separator
};
// todo: the path could be relative, better if the app is movedn but bookmarked path can be outside of the app struct GroupStruct {
std::string path; // absolute path of the bookmarked directory bool canBeSaved = false; // defined by code, can be used for prevent serialization / deserialization
size_t displayOrder = 0U; // the display order will be usedf first, then alphanumeric
bool defined_by_code = false; // defined by code, can be used for rpevent serialization / deserialization bool defaultOpened = false; // the group is opened by default
bool canBeEdited = false; // will show +/- button for add/remove place in the group
char editBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = ""; // temp buffer for name edition
int32_t selectedPlaceForEdition = -1;
ImGuiTreeNodeFlags collapsingHeaderFlag = ImGuiTreeNodeFlags_None;
ImGuiListClipper clipper; // the list clipper of the grou
std::string name; // the group name, will be displayed
std::vector<PlaceStruct> places; // the places (name + path)
bool AddPlace( // add a place by code
const std::string& vPlaceName, // place name
const std::string& vPlacePath, // place path
const bool& vCanBeSaved, // prevent serialization
const FileStyle& vStyle = {}); // style
void AddPlaceSeparator(const float& vThickness = 1.0f);
bool RemovePlace( // remove a place by code, return true if succeed
const std::string& vPlaceName); // place name to remove
}; };
private: private:
ImGuiListClipper m_BookmarkClipper; std::unordered_map<std::string, std::shared_ptr<GroupStruct>> m_Groups;
std::vector<BookmarkStruct> m_Bookmarks; std::map<size_t, std::weak_ptr<GroupStruct> > m_OrderedGroups;
char m_BookmarkEditBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = "";
protected: protected:
float m_BookmarkWidth = 200.0f; float m_PlacesPaneWidth = 200.0f;
bool m_BookmarkPaneShown = false; bool m_PlacesPaneShown = false;
protected: protected:
void m_DrawBookmarkButton(); // draw bookmark button void m_InitPlaces(FileDialogInternal& vFileDialogInternal);
bool m_DrawBookmarkPane(FileDialogInternal& vFileDialogInternal, const ImVec2& vSize); // draw bookmark Pane void m_DrawPlacesButton(); // draw place button
bool m_DrawPlacesPane(FileDialogInternal& vFileDialogInternal, const ImVec2& vSize); // draw place Pane
public: public:
std::string SerializeBookmarks( // serialize bookmarks : return bookmark buffer to save in a file std::string SerializePlaces( // serialize place : return place buffer to save in a file
const bool& vDontSerializeCodeBasedBookmarks = true); // for avoid serialization of bookmarks added by code const bool& vForceSerialisationForAll = true); // for avoid serialization of places with flag 'canBeSaved to false'
void DeserializeBookmarks( // deserialize bookmarks : load bookmark buffer to load in the dialog (saved from void DeserializePlaces( // deserialize place : load place buffer to load in the dialog (saved from
const std::string& vBookmarks); // previous use with SerializeBookmarks()) bookmark buffer to load const std::string& vPlaces); // previous use with SerializePlaces()) place buffer to load
void AddBookmark( // add a bookmark by code bool AddPlacesGroup( // add a group
const std::string& vBookMarkName, // bookmark name const std::string& vGroupName, // the group name
const std::string& vBookMarkPath); // bookmark path const size_t& vDisplayOrder, // the display roder of the group
bool RemoveBookmark( // remove a bookmark by code, return true if succeed const bool& vCanBeEdited = false, // let the user add/remove place in the group
const std::string& vBookMarkName); // bookmark name to remove const bool& vOpenedByDefault = true); // hte group is opened by default
bool RemovePlacesGroup(const std::string& vGroupName); // remove the group
#endif // USE_BOOKMARK GroupStruct* GetPlacesGroupPtr(const std::string& vGroupName); // get the group, if not existed, will be created
#endif // USE_PLACES_FEATURE
}; };
#pragma endregion #pragma endregion
@ -1989,7 +2147,7 @@ public:
#pragma region FileDialog #pragma region FileDialog
class IGFD_API FileDialog : public BookMarkFeature, public KeyExplorerFeature, public ThumbnailFeature { class IGFD_API FileDialog : public PlacesFeature, public KeyExplorerFeature, public ThumbnailFeature {
protected: protected:
FileDialogInternal m_FileDialogInternal; FileDialogInternal m_FileDialogInternal;
ImGuiListClipper m_FileListClipper; ImGuiListClipper m_FileListClipper;
@ -2012,55 +2170,15 @@ public:
} }
public: public:
FileDialog(); // ImGuiFileDialog Constructor. can be used for have many dialog at same time (not possible with FileDialog(); // ImGuiFileDialog Constructor. can be used for have many dialog at same time (not possible with singleton)
// singleton)
virtual ~FileDialog(); // ImGuiFileDialog Destructor virtual ~FileDialog(); // ImGuiFileDialog Destructor
virtual // todo : need to refactor all theses function to maybe just one // standard dialog
// standard dialog virtual void OpenDialog( // open simple dialog
void const std::string& vKey, // key dialog
OpenDialog( // open simple dialog (path and fileName can be specified) const std::string& vTitle, // title
const std::string& vKey, // key dialog const char* vFilters, // filters, if null, will display only directories
const std::string& vTitle, // title const FileDialogConfig& vConfig = {}); // FileDialogConfig
const char* vFilters, // filters
const std::string& vPath, // path
const std::string& vFileName, // defaut file name
const int& vCountSelectionMax = 1, // count selection max
UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane)
ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags
void OpenDialog( // open simple dialog (path and filename are obtained from filePathName)
const std::string& vKey, // key dialog
const std::string& vTitle, // title
const char* vFilters, // filters
const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName)
const int& vCountSelectionMax = 1, // count selection max
UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane)
ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags
// with pane
void OpenDialogWithPane( // open dialog with custom right pane (path and fileName can be specified)
const std::string& vKey, // key dialog
const std::string& vTitle, // title
const char* vFilters, // filters
const std::string& vPath, // path
const std::string& vFileName, // defaut file name
const PaneFun& vSidePane, // side pane
const float& vSidePaneWidth = 250.0f, // side pane width
const int& vCountSelectionMax = 1, // count selection max
UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane)
ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags
void OpenDialogWithPane( // open dialog with custom right pane (path and filename are obtained from filePathName)
const std::string& vKey, // key dialog
const std::string& vTitle, // title
const char* vFilters, // filters
const std::string& vFilePathName, // file path name (will be decompsoed in path and fileName)
const PaneFun& vSidePane, // side pane
const float& vSidePaneWidth = 250.0f, // side pane width
const int& vCountSelectionMax = 1, // count selection max
UserDatas vUserDatas = nullptr, // user datas (can be retrieved in pane)
ImGuiFileDialogFlags vFlags = 0); // ImGuiFileDialogFlags
// Display / Close dialog form // Display / Close dialog form
bool Display( // Display the dialog. return true if a result was obtained (Ok or not) bool Display( // Display the dialog. return true if a result was obtained (Ok or not)
@ -2126,9 +2244,9 @@ protected:
// if needed (if defined with flag) // if needed (if defined with flag)
// dialog parts // dialog parts
virtual void m_DrawHeader(); // draw header part of the dialog (bookmark btn, dir creation, path composer, search virtual void m_DrawHeader(); // draw header part of the dialog (place btn, dir creation, path composer, search
// bar) // bar)
virtual void m_DrawContent(); // draw content part of the dialog (bookmark pane, file list, side pane) virtual void m_DrawContent(); // draw content part of the dialog (place pane, file list, side pane)
virtual bool m_DrawFooter(); // draw footer part of the dialog (file field, fitler combobox, ok/cancel btn's) virtual bool m_DrawFooter(); // draw footer part of the dialog (file field, fitler combobox, ok/cancel btn's)
// widgets components // widgets components
@ -2158,6 +2276,8 @@ protected:
std::string& vOutStr, std::string& vOutStr,
ImFont** vOutFont); // begin style apply of filter with color an icon if any ImFont** vOutFont); // begin style apply of filter with color an icon if any
void m_EndFileColorIconStyle(const bool& vShowColor, ImFont* vFont); // end style apply of filter void m_EndFileColorIconStyle(const bool& vShowColor, ImFont* vFont); // end style apply of filter
void m_DisplayFileInfosTooltip(const int32_t& vRowIdx, const int32_t& vColumnIdx, std::shared_ptr<FileInfos> vFileInfos);
}; };
#pragma endregion #pragma endregion
@ -2198,6 +2318,20 @@ typedef struct IGFD_Selection_Pair IGFD_Selection_Pair;
typedef struct IGFD_Selection IGFD_Selection; typedef struct IGFD_Selection IGFD_Selection;
#endif // __cplusplus #endif // __cplusplus
typedef void (*IGFD_PaneFun)(const char*, void*, bool*); // callback fucntion for display the pane
struct IGFD_FileDialog_Config {
const char* path; // path
const char* fileName; // defaut file name
const char* filePathName; // if not empty, the filename and the path will be obtained from filePathName
int32_t countSelectionMax; // count selection max
void* userDatas; // user datas (can be retrieved in pane)
IGFD_PaneFun sidePane; // side pane callback
float sidePaneWidth; // side pane width};
ImGuiFileDialogFlags flags; // ImGuiFileDialogFlags
};
IGFD_C_API IGFD_FileDialog_Config IGFD_FileDialog_Config_Get(); // return an initialized IGFD_FileDialog_Config
struct IGFD_Selection_Pair { struct IGFD_Selection_Pair {
char* fileName; char* fileName;
char* filePathName; char* filePathName;
@ -2218,58 +2352,17 @@ IGFD_C_API void IGFD_Selection_DestroyContent(IGFD_Selection* vSelection); // d
IGFD_C_API ImGuiFileDialog* IGFD_Create(void); // create the filedialog context IGFD_C_API ImGuiFileDialog* IGFD_Create(void); // create the filedialog context
IGFD_C_API void IGFD_Destroy(ImGuiFileDialog* vContextPtr); // destroy the filedialog context IGFD_C_API void IGFD_Destroy(ImGuiFileDialog* vContextPtr); // destroy the filedialog context
typedef void (*IGFD_PaneFun)(const char*, void*, bool*); // callback fucntion for display the pane
#ifdef USE_THUMBNAILS #ifdef USE_THUMBNAILS
typedef void (*IGFD_CreateThumbnailFun)(IGFD_Thumbnail_Info*); // callback function for create thumbnail texture typedef void (*IGFD_CreateThumbnailFun)(IGFD_Thumbnail_Info*); // callback function for create thumbnail texture
typedef void (*IGFD_DestroyThumbnailFun)(IGFD_Thumbnail_Info*); // callback fucntion for destroy thumbnail texture typedef void (*IGFD_DestroyThumbnailFun)(IGFD_Thumbnail_Info*); // callback fucntion for destroy thumbnail texture
#endif // USE_THUMBNAILS #endif // USE_THUMBNAILS
IGFD_C_API void IGFD_OpenDialog( // open a standard dialog IGFD_C_API void IGFD_OpenDialog( // open a standard dialog
ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context
const char* vKey, // key dialog const char* vKey, // key dialog
const char* vTitle, // title const char* vTitle, // title
const char* vFilters, // filters/filter collections. set it to null for directory mode const char* vFilters, // filters/filter collections. set it to null for directory mode
const char* vPath, // path const IGFD_FileDialog_Config vConfig); // config
const char* vFileName, // defaut file name
const int vCountSelectionMax, // count selection max
void* vUserDatas, // user datas (can be retrieved in pane)
ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags
IGFD_C_API void IGFD_OpenDialog2( // open a standard dialog
ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context
const char* vKey, // key dialog
const char* vTitle, // title
const char* vFilters, // filters/filter collections. set it to null for directory mode
const char* vFilePathName, // defaut file path name (path and filename witl be extracted from it)
const int vCountSelectionMax, // count selection max
void* vUserDatas, // user datas (can be retrieved in pane)
ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags
IGFD_C_API void IGFD_OpenDialogWithPane( // open a standard dialog with pane
ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context
const char* vKey, // key dialog
const char* vTitle, // title
const char* vFilters, // filters/filter collections. set it to null for directory mode
const char* vPath, // path
const char* vFileName, // defaut file name
const IGFD_PaneFun vSidePane, // side pane
const float vSidePaneWidth, // side pane base width
const int vCountSelectionMax, // count selection max
void* vUserDatas, // user datas (can be retrieved in pane)
ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags
IGFD_C_API void IGFD_OpenDialogWithPane2( // open a standard dialog with pane
ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context
const char* vKey, // key dialog
const char* vTitle, // title
const char* vFilters, // filters/filter collections. set it to null for directory mode
const char* vFilePathName, // defaut file name (path and filename witl be extracted from it)
const IGFD_PaneFun vSidePane, // side pane
const float vSidePaneWidth, // side pane base width
const int vCountSelectionMax, // count selection max
void* vUserDatas, // user datas (can be retrieved in pane)
ImGuiFileDialogFlags vFlags); // ImGuiFileDialogFlags
IGFD_C_API bool IGFD_DisplayDialog( // Display the dialog IGFD_C_API bool IGFD_DisplayDialog( // Display the dialog
ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context
@ -2363,25 +2456,40 @@ IGFD_C_API void IGFD_SetFlashingAttenuationInSeconds( // set the flashing time
float vAttenValue); // set the attenuation (from flashed to not flashed) in seconds float vAttenValue); // set the attenuation (from flashed to not flashed) in seconds
#endif #endif
#ifdef USE_BOOKMARK #ifdef USE_PLACES_FEATURE
IGFD_C_API char* IGFD_SerializeBookmarks( // serialize bookmarks : return bookmark buffer to save in a file, WARNINGS IGFD_C_API char* IGFD_SerializePlaces( // serialize place : return place buffer to save in a file, WARNINGS
// you are responsible to free it // you are responsible to free it
ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context
bool vDontSerializeCodeBasedBookmarks); // for avoid serialization of bookmarks added by code bool vDontSerializeCodeBasedPlaces); // for avoid serialization of place added by code
IGFD_C_API void IGFD_DeserializeBookmarks( // deserialize bookmarks : load bookmar buffer to load in the dialog (saved IGFD_C_API void IGFD_DeserializePlaces( // deserialize place : load bookmar buffer to load in the dialog (saved
// from previous use with SerializeBookmarks()) // from previous use with SerializePlaces())
ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context
const char* vBookmarks); // bookmark buffer to load const char* vPlaces); // place buffer to load
IGFD_C_API void IGFD_AddBookmark( // add a bookmark by code IGFD_C_API bool IGFD_AddPlacesGroup( // add a places group by code
ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context
const char* vBookMarkName, // bookmark name
const char* vBookMarkPath); // bookmark path
IGFD_C_API void IGFD_RemoveBookmark( // remove a bookmark by code, return true if succeed
ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context
const char* vBookMarkName); // bookmark name to remove const char* vGroupName, // the group name
size_t vDisplayOrder, // the display roder of the group
bool vCanBeEdited); // let the user add/remove place in the group
IGFD_C_API bool IGFD_RemovePlacesGroup( // remove a place group by code, return true if succeed
ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context
const char* vGroupName); // place name to remove
IGFD_C_API bool IGFD_AddPlace( // add a place by code
ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context
const char* vGroupName, // the group name
const char* vPlaceName, // place name
const char* vPlacePath, // place path
bool vCanBeSaved, // place can be saved
const char* vIconText); // wanted text or icon of the file with extention filter (can be used with font icon)
IGFD_C_API bool IGFD_RemovePlace( // remove a place by code, return true if succeed
ImGuiFileDialog* vContextPtr, // ImGuiFileDialog context
const char* vGroupName, // the group name
const char* vPlaceName); // place name to remove
#endif #endif
#ifdef USE_THUMBNAILS #ifdef USE_THUMBNAILS

View file

@ -9,127 +9,143 @@
// this options need c++17 // this options need c++17
// #define USE_STD_FILESYSTEM // #define USE_STD_FILESYSTEM
//#define MAX_FILE_DIALOG_NAME_BUFFER 1024 // #define MAX_FILE_DIALOG_NAME_BUFFER 1024
//#define MAX_PATH_BUFFER_SIZE 1024 // #define MAX_PATH_BUFFER_SIZE 1024
// the slash's buttons in path cna be used for quick select parallles directories // the slash's buttons in path cna be used for quick select parallles directories
//#define USE_QUICK_PATH_SELECT // #define USE_QUICK_PATH_SELECT
// the spacing between button path's can be customized. // the spacing between button path's can be customized.
// if disabled the spacing is defined by the imgui theme // if disabled the spacing is defined by the imgui theme
// define the space between path buttons // define the space between path buttons
//#define CUSTOM_PATH_SPACING 2 // #define CUSTOM_PATH_SPACING 2
//#define USE_THUMBNAILS // #define USE_THUMBNAILS
//the thumbnail generation use the stb_image and stb_resize lib who need to define the implementation // the thumbnail generation use the stb_image and stb_resize lib who need to define the implementation
//btw if you already use them in your app, you can have compiler error due to "implemntation found in double" // btw if you already use them in your app, you can have compiler error due to "implemntation found in double"
//so uncomment these line for prevent the creation of implementation of these libs again // so uncomment these line for prevent the creation of implementation of these libs again
//#define DONT_DEFINE_AGAIN__STB_IMAGE_IMPLEMENTATION // #define DONT_DEFINE_AGAIN__STB_IMAGE_IMPLEMENTATION
//#define DONT_DEFINE_AGAIN__STB_IMAGE_RESIZE_IMPLEMENTATION // #define DONT_DEFINE_AGAIN__STB_IMAGE_RESIZE_IMPLEMENTATION
//#define IMGUI_RADIO_BUTTON RadioButton // #define IMGUI_RADIO_BUTTON RadioButton
//#define DisplayMode_ThumbailsList_ImageHeight 32.0f // #define DisplayMode_ThumbailsList_ImageHeight 32.0f
//#define tableHeaderFileThumbnailsString "Thumbnails" // #define tableHeaderFileThumbnailsString "Thumbnails"
//#define DisplayMode_FilesList_ButtonString "FL" // #define DisplayMode_FilesList_ButtonString "FL"
//#define DisplayMode_FilesList_ButtonHelp "File List" // #define DisplayMode_FilesList_ButtonHelp "File List"
//#define DisplayMode_ThumbailsList_ButtonString "TL" // #define DisplayMode_ThumbailsList_ButtonString "TL"
//#define DisplayMode_ThumbailsList_ButtonHelp "Thumbnails List" // #define DisplayMode_ThumbailsList_ButtonHelp "Thumbnails List"
// todo // todo
//#define DisplayMode_ThumbailsGrid_ButtonString "TG" // #define DisplayMode_ThumbailsGrid_ButtonString "TG"
//#define DisplayMode_ThumbailsGrid_ButtonHelp "Thumbnails Grid" // #define DisplayMode_ThumbailsGrid_ButtonHelp "Thumbnails Grid"
//#define USE_EXPLORATION_BY_KEYS // #define USE_EXPLORATION_BY_KEYS
// this mapping by default is for GLFW but you can use another // this mapping by default is for GLFW but you can use another
//#include <GLFW/glfw3.h> // #include <GLFW/glfw3.h>
// Up key for explore to the top // Up key for explore to the top
//#define IGFD_KEY_UP ImGuiKey_UpArrow // #define IGFD_KEY_UP ImGuiKey_UpArrow
// Down key for explore to the bottom // Down key for explore to the bottom
//#define IGFD_KEY_DOWN ImGuiKey_DownArrow // #define IGFD_KEY_DOWN ImGuiKey_DownArrow
// Enter key for open directory // Enter key for open directory
//#define IGFD_KEY_ENTER ImGuiKey_Enter // #define IGFD_KEY_ENTER ImGuiKey_Enter
// BackSpace for comming back to the last directory // BackSpace for comming back to the last directory
//#define IGFD_KEY_BACKSPACE ImGuiKey_Backspace // #define IGFD_KEY_BACKSPACE ImGuiKey_Backspace
// by ex you can quit the dialog by pressing the key excape // by ex you can quit the dialog by pressing the key excape
//#define USE_DIALOG_EXIT_WITH_KEY // #define USE_DIALOG_EXIT_WITH_KEY
//#define IGFD_EXIT_KEY ImGuiKey_Escape // #define IGFD_EXIT_KEY ImGuiKey_Escape
// widget // widget
// begin combo widget // begin combo widget
//#define IMGUI_BEGIN_COMBO ImGui::BeginCombo // #define IMGUI_BEGIN_COMBO ImGui::BeginCombo
// when auto resized, FILTER_COMBO_MIN_WIDTH will be considered has minimum width // when auto resized, FILTER_COMBO_MIN_WIDTH will be considered has minimum width
// FILTER_COMBO_AUTO_SIZE is enabled by default now to 1 // FILTER_COMBO_AUTO_SIZE is enabled by default now to 1
// uncomment if you want disable // uncomment if you want disable
//#define FILTER_COMBO_AUTO_SIZE 0 // #define FILTER_COMBO_AUTO_SIZE 0
// filter combobox width // filter combobox width
//#define FILTER_COMBO_MIN_WIDTH 120.0f // #define FILTER_COMBO_MIN_WIDTH 120.0f
// button widget use for compose path // button widget use for compose path
//#define IMGUI_PATH_BUTTON ImGui::Button // #define IMGUI_PATH_BUTTON ImGui::Button
// standard button // standard button
//#define IMGUI_BUTTON ImGui::Button // #define IMGUI_BUTTON ImGui::Button
// locales string // locales string
//#define createDirButtonString "+" // #define createDirButtonString "+"
//#define resetButtonString "R" // #define resetButtonString "R"
//#define drivesButtonString "Drives" // #define drivesButtonString "Drives"
//#define editPathButtonString "E" // #define editPathButtonString "E"
//#define searchString "Search" // #define searchString "Search"
//#define dirEntryString "[DIR] " // #define dirEntryString "[DIR] "
//#define linkEntryString "[LINK] " // #define linkEntryString "[LINK] "
//#define fileEntryString "[FILE] " // #define fileEntryString "[FILE] "
//#define fileNameString "File Name : " // #define fileNameString "File Name : "
//#define dirNameString "Directory Path :" // #define dirNameString "Directory Path :"
//#define buttonResetSearchString "Reset search" // #define buttonResetSearchString "Reset search"
//#define buttonDriveString "Drives" // #define buttonDriveString "Drives"
//#define buttonEditPathString "Edit path\nYou can also right click on path buttons" // #define buttonEditPathString "Edit path\nYou can also right click on path buttons"
//#define buttonResetPathString "Reset to current directory" // #define buttonResetPathString "Reset to current directory"
//#define buttonCreateDirString "Create Directory" // #define buttonCreateDirString "Create Directory"
//#define OverWriteDialogTitleString "The file Already Exist !" // #define OverWriteDialogTitleString "The file Already Exist !"
//#define OverWriteDialogMessageString "Would you like to OverWrite it ?" // #define OverWriteDialogMessageString "Would you like to OverWrite it ?"
//#define OverWriteDialogConfirmButtonString "Confirm" // #define OverWriteDialogConfirmButtonString "Confirm"
//#define OverWriteDialogCancelButtonString "Cancel" // #define OverWriteDialogCancelButtonString "Cancel"
//Validation buttons // Validation buttons
//#define okButtonString " OK" // #define okButtonString " OK"
//#define okButtonWidth 0.0f // #define okButtonWidth 0.0f
//#define cancelButtonString " Cancel" // #define cancelButtonString " Cancel"
//#define cancelButtonWidth 0.0f // #define cancelButtonWidth 0.0f
//alignement [0:1], 0.0 is left, 0.5 middle, 1.0 right, and other ratios // alignement [0:1], 0.0 is left, 0.5 middle, 1.0 right, and other ratios
//#define okCancelButtonAlignement 0.0f // #define okCancelButtonAlignement 0.0f
//#define invertOkAndCancelButtons 0 // #define invertOkAndCancelButtons 0
// DateTimeFormat // DateTimeFormat
// see strftime functionin <ctime> for customize // see strftime functionin <ctime> for customize
// "%Y/%m/%d %H:%M" give 2021:01:22 11:47 // "%Y/%m/%d %H:%M" give 2021:01:22 11:47
// "%Y/%m/%d %i:%M%p" give 2021:01:22 11:45PM // "%Y/%m/%d %i:%M%p" give 2021:01:22 11:45PM
//#define DateTimeFormat "%Y/%m/%d %i:%M%p" // #define DateTimeFormat "%Y/%m/%d %i:%M%p"
// theses icons will appear in table headers // theses icons will appear in table headers
//#define USE_CUSTOM_SORTING_ICON // #define USE_CUSTOM_SORTING_ICON
//#define tableHeaderAscendingIcon "A|" // #define tableHeaderAscendingIcon "A|"
//#define tableHeaderDescendingIcon "D|" // #define tableHeaderDescendingIcon "D|"
//#define tableHeaderFileNameString " File name" // #define tableHeaderFileNameString " File name"
//#define tableHeaderFileTypeString " Type" // #define tableHeaderFileTypeString " Type"
//#define tableHeaderFileSizeString " Size" // #define tableHeaderFileSizeString " Size"
//#define tableHeaderFileDateTimeString " Date" // #define tableHeaderFileDateTimeString " Date"
//#define fileSizeBytes "o" // #define fileSizeBytes "o"
//#define fileSizeKiloBytes "Ko" // #define fileSizeKiloBytes "Ko"
//#define fileSizeMegaBytes "Mo" // #define fileSizeMegaBytes "Mo"
//#define fileSizeGigaBytes "Go" // #define fileSizeGigaBytes "Go"
// default table sort field (must be FIELD_FILENAME, FIELD_TYPE, FIELD_SIZE, FIELD_DATE or FIELD_THUMBNAILS) // default table sort field (must be FIELD_FILENAME, FIELD_TYPE, FIELD_SIZE, FIELD_DATE or FIELD_THUMBNAILS)
//#define defaultSortField FIELD_FILENAME // #define defaultSortField FIELD_FILENAME
// default table sort order for each field (true => Descending, false => Ascending) // default table sort order for each field (true => Descending, false => Ascending)
//#define defaultSortOrderFilename true // #define defaultSortOrderFilename true
//#define defaultSortOrderType true // #define defaultSortOrderType true
//#define defaultSortOrderSize true // #define defaultSortOrderSize true
//#define defaultSortOrderDate true // #define defaultSortOrderDate true
//#define defaultSortOrderThumbnails true // #define defaultSortOrderThumbnails true
//#define USE_BOOKMARK // #define USE_PLACES_FEATURE
//#define bookmarkPaneWith 150.0f // #define PLACES_PANE_DEFAULT_SHOWN false
//#define IMGUI_TOGGLE_BUTTON ToggleButton // #define placesPaneWith 150.0f
//#define bookmarksButtonString "Bookmark" // #define IMGUI_TOGGLE_BUTTON ToggleButton
//#define bookmarksButtonHelpString "Bookmark" // #define placesButtonString "Place"
//#define addBookmarkButtonString "+" // #define placesButtonHelpString "Places"
//#define removeBookmarkButtonString "-" // #define addPlaceButtonString "+"
// #define removePlaceButtonString "-"
// #define validatePlaceButtonString "ok"
// #define editPlaceButtonString "E"
// a group for bookmarks will be added by default, but you can also create it yourself and many more
// #define USE_PLACES_BOOKMARKS
// #define PLACES_BOOKMARK_DEFAULT_OPEPEND true
// #define placesBookmarksGroupName "Bookmarks"
// #define placesBookmarksDisplayOrder 0 // to the first
// a group for system devices (returned by IFileSystem), but you can also add yours
// by ex if you would like to display a specific icon for some devices
// #define USE_PLACES_DEVICES
// #define PLACES_DEVICES_DEFAULT_OPEPEND true
// #define placesDevicesGroupName "Devices"
// #define placesDevicesDisplayOrder 10 // to the end

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2018-2023 Stephane Cuillerdier (aka Aiekick) Copyright (c) 2018-2024 Stephane Cuillerdier (aka Aiekick)
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -1,7 +1,7 @@
[![Win](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Win.yml/badge.svg?branch=DemoApp)](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Win.yml) [![Win](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Win.yml/badge.svg?branch=DemoApp)](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Win.yml)
[![Linux](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Linux.yml/badge.svg?branch=DemoApp)](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Linux.yml) [![Linux](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Linux.yml/badge.svg?branch=DemoApp)](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Linux.yml)
[![Osx](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Osx.yml/badge.svg?branch=DemoApp)](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Osx.yml) [![Osx](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Osx.yml/badge.svg?branch=DemoApp)](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Osx.yml)
[![Wrapped Dear ImGui Version](https://img.shields.io/badge/Dear%20ImGui%20Version-1.90.1-blue.svg)](https://github.com/ocornut/imgui) [![Wrapped Dear ImGui Version](https://img.shields.io/badge/Dear%20ImGui%20Version-1.90.4-blue.svg)](https://github.com/ocornut/imgui)
# ImGuiFileDialog # ImGuiFileDialog
@ -12,9 +12,13 @@ ImGuiFileDialog is a file selection dialog built for (and using only) [Dear ImGu
My primary goal was to have a custom pane with widgets according to file extension. This was not possible using other My primary goal was to have a custom pane with widgets according to file extension. This was not possible using other
solutions. solutions.
## Possible Dialog Customization
![alt text](https://github.com/aiekick/ImGuiFileDialog/blob/DemoApp/doc/demo_dialog.png)
## ImGui Supported Version ## ImGui Supported Version
ImGuiFileDialog follow the master and docking branch of ImGui. Currently ImGui 1.90.1 ImGuiFileDialog follow the master and docking branch of ImGui. Currently ImGui 1.90.4
## Structure ## Structure
@ -67,15 +71,14 @@ Android Requirements : Api 21 mini
- 0 => Infinite - 0 => Infinite
- 1 => One file (default) - 1 => One file (default)
- n => n files - n => n files
- Compatible with MacOs, Linux, Windows, Emscripten - Compatible with MacOs, Linux, Windows, Emscripten, Android
- Windows version can list drives
- Supports modal or standard dialog types - Supports modal or standard dialog types
- Select files or directories - Select files or directories
- Filter groups and custom filter names - Filter groups and custom filter names
- can ignore filter Case for file searching - can ignore filter Case for file searching
- Keyboard navigation (arrows, backspace, enter) - Keyboard navigation (arrows, backspace, enter)
- Exploring by entering characters (case insensitive) - Exploring by entering characters (case insensitive)
- Directory bookmarks - Custom places (bookmarks, system devices, whatever you want)
- Directory manual entry (right click on any path element) - Directory manual entry (right click on any path element)
- Optional 'Confirm to Overwrite" dialog if file exists - Optional 'Confirm to Overwrite" dialog if file exists
- Thumbnails Display (agnostic way for compatibility with any backend, sucessfully tested with OpenGl and Vulkan) - Thumbnails Display (agnostic way for compatibility with any backend, sucessfully tested with OpenGl and Vulkan)
@ -101,8 +104,8 @@ A filter is recognized only if it respects theses rules :
1) a regex must be in (( and )) 1) a regex must be in (( and ))
2) a , will separate filters except if between a ( and ) 2) a , will separate filters except if between a ( and )
3) name{filter1, filter2} is a special form for collection filters 3) name{filter1, filter2} is a special form for collection filters
3.1) the name can be composed of what you want except { and } - the name can be composed of what you want except { and }
3.2) a filter can be a regex - a filter can be a regex
4) the filters cannot integrate these chars '(' ')' '{' '}' ' ' except for a regex with respect to rule 1) 4) the filters cannot integrate these chars '(' ')' '{' '}' ' ' except for a regex with respect to rule 1)
5) the filters cannot integrate a ',' 5) the filters cannot integrate a ','
@ -133,18 +136,15 @@ instance_b.method_of_your_choice();
<details open><summary><h2>Simple Dialog :</h2></summary><blockquote> <details open><summary><h2>Simple Dialog :</h2></summary><blockquote>
```cpp ```cpp
void drawGui() void drawGui() {
{
// open Dialog Simple // open Dialog Simple
if (ImGui::Button("Open File Dialog")) if (ImGui::Button("Open File Dialog")) {
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", "."); IGFD::FileDialogConfig config;config.path = ".";
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", config);
}
// display // display
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
{ if (ImGuiFileDialog::Instance()->IsOk()) { // action if OK
// action if OK
if (ImGuiFileDialog::Instance()->IsOk())
{
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath(); std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath();
// action // action
@ -156,7 +156,7 @@ void drawGui()
} }
``` ```
![alt text](https://github.com/aiekick/ImGuiFileDialog/blob/demoApp/doc/dlg_simple.gif) ![alt text](https://github.com/aiekick/ImGuiFileDialog/blob/DemoApp/doc/dlg_simple.gif)
</blockquote></details> </blockquote></details>
@ -172,8 +172,11 @@ ImGuiFileDialogFlags_Modal
you can use it like that : you can use it like that :
```cpp ```cpp
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", IGFD::FileDialogConfig config;
".", 1, nullptr, ImGuiFileDialogFlags_Modal); config.path = ".";
config.countSelectionMax = 1;
config.flags = ImGuiFileDialogFlags_Modal;
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", config);
``` ```
</blockquote></details> </blockquote></details>
@ -183,7 +186,9 @@ ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp
To have a directory chooser, set the file extension filter to nullptr: To have a directory chooser, set the file extension filter to nullptr:
```cpp ```cpp
ImGuiFileDialog::Instance()->OpenDialog("ChooseDirDlgKey", "Choose a Directory", nullptr, "."); IGFD::FileDialogConfig config;
config.path = ".";
ImGuiFileDialog::Instance()->OpenDialog("ChooseDirDlgKey", "Choose a Directory", nullptr, config);
``` ```
In this mode you can select any directory with one click and open a directory with a double-click. In this mode you can select any directory with one click and open a directory with a double-click.
@ -226,9 +231,16 @@ inline void InfosPane(cosnt char *vFilter, IGFDUserDatas vUserDatas, bool *vCant
void drawGui() void drawGui()
{ {
// open Dialog with Pane // open Dialog with Pane
if (ImGui::Button("Open File Dialog with a custom pane")) if (ImGui::Button("Open File Dialog with a custom pane")) {
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", IGFD::FileDialogConfig config;
".", "", std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 350, 1, UserDatas("InfosPane")); config.path = ".";
config.countSelectionMax = 1;
config.sidePane = std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
config.sidePaneWidth = 350.0f;
config.useDatas = UserDatas("InfosPane");
config.flags = ImGuiFileDialogFlags_Modal;
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", config);
}
// display and action if ok // display and action if ok
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey"))
@ -401,7 +413,9 @@ this code :
```cpp ```cpp
const char *filters = "Source files (*.cpp *.h *.hpp){.cpp,.h,.hpp},Image files (*.png *.gif *.jpg *.jpeg){.png,.gif,.jpg,.jpeg},.md"; const char *filters = "Source files (*.cpp *.h *.hpp){.cpp,.h,.hpp},Image files (*.png *.gif *.jpg *.jpeg){.png,.gif,.jpg,.jpeg},.md";
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", ICON_IMFDLG_FOLDER_OPEN " Choose a File", filters, "."); IGFD::FileDialogConfig config;
config.path = ".";
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", ICON_IMFDLG_FOLDER_OPEN " Choose a File", filters, config);
``` ```
will produce : will produce :
@ -420,12 +434,24 @@ You can define in OpenDialog call the count file you want to select :
See the define at the end of these funcs after path. See the define at the end of these funcs after path.
```cpp ```cpp
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".*,.cpp,.h,.hpp", "."); IGFD::FileDialogConfig config; config.path = ".";
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose 1 File", ".*,.cpp,.h,.hpp", ".", 1);
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose 5 File", ".*,.cpp,.h,.hpp", ".", 5); ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".*,.cpp,.h,.hpp", config);
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose many File", ".*,.cpp,.h,.hpp", ".", 0);
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".png,.jpg", config.countSelectionMax = 1;
".", "", std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 350, 1, "SaveFile"); // 1 file ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose 1 File", ".*,.cpp,.h,.hpp", config);
config.countSelectionMax = 5;
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose 5 File", ".*,.cpp,.h,.hpp", config);
config.countSelectionMax = 0;
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose many File", ".*,.cpp,.h,.hpp", config);
config.countSelectionMax = 1;
config.sidePane = std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
config.sidePaneWidth = 350.0f;
config.useDatas = UserDatas("SaveFile");
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".png,.jpg", config); // 1 file
``` ```
![alt text](https://github.com/aiekick/ImGuiFileDialog/blob/DemoApp/doc/multiSelection.gif) ![alt text](https://github.com/aiekick/ImGuiFileDialog/blob/DemoApp/doc/multiSelection.gif)
@ -470,47 +496,62 @@ ImGuiFileDialog::Instance()->SetFlashingAttenuationInSeconds(1.0f);
</blockquote></details> </blockquote></details>
<details open><summary><h2>Bookmarks :</h2></summary><blockquote> <details open><summary><h2>Places :</h2></summary><blockquote>
You can create/edit/call path bookmarks and load/save them. the Places system is a generic way for add custom links in the left side pane
Activate this feature by uncommenting: `#define USE_BOOKMARK` in your custom config file (CustomImGuiFileDialogConfig.h) you can organize them by groups
More customization options: The bookmarks and devices are now groups in the left side pane.
for using it you need to
```cpp ```cpp
#define bookmarkPaneWith 150.0f => width of the bookmark pane #define USE_PLACES_FEATURE
#define IMGUI_TOGGLE_BUTTON ToggleButton => customize the Toggled button (button stamp must be : (const char* label, bool *toggle)
#define bookmarksButtonString "Bookmark" => the text in the toggle button // for have default bookmark editable groups
#define bookmarksButtonHelpString "Bookmark" => the helper text when mouse over the button #define USE_PLACES_BOOKMARKS
#define addBookmarkButtonString "+" => the button for add a bookmark
#define removeBookmarkButtonString "-" => the button for remove the selected bookmark // for have default groups for system devices (returned by the IFileSystem interface)
#define USE_PLACES_DEVICES
``` ```
* You can select each bookmark to edit the displayed name corresponding to a path see the config file for more customization
* Double-click on the label to apply the bookmark
![bookmarks.gif](https://github.com/aiekick/ImGuiFileDialog/blob/DemoApp/doc/bookmarks.gif) you can also add your custom groups editable or not like what is done
the DemoApp branch with the "quick access" paths of win10
You can also serialize/deserialize bookmarks (for example to load/save from/to a file): You must add a group first, then add a place to it :
```cpp
Load => ImGuiFileDialog::Instance()->DeserializeBookmarks(bookmarString);
Save => std::string bookmarkString = ImGuiFileDialog::Instance()->SerializeBookmarks();
```
you can also add/remove bookmark by code :
and in this case, you can also avoid serialization of code based bookmark
```cpp ```cpp
Add => ImGuiFileDialog::Instance()->AddBookmark(bookmark_name, bookmark_path); // you must add a group first, specifu display order, and say :
Remove => ImGuiFileDialog::Instance()->RemoveBookmark(bookmark_name); // if the user can add or remove palce like (bookmarks)
// if the group is opened by default
// true for prevent serialization of code based bookmarks ImGuiFileDialog::Instance()->AddPlacesGroup(group_name, display_order, can_be_user_edited, opened_by_default);
Save => std::string bookmarkString = ImGuiFileDialog::Instance()->SerializeBookmarks(true); // then you must get the group
auto places_ptr = ImGuiFileDialog::Instance()->GetPlacesGroupPtr(group_name);
if (places_ptr != nullptr) {
// then add a place to the group
// you msut specify the place name, the palce path, say if the palce can be serialized, and sepcify the style
// for the moment the style support only the icon, can be extended if user needed in futur
places_ptr->AddPlace(place_name, place_path, can_be_saved, style);
// you can also add a separator
places_ptr->AddPlaceSeparator(separator_thickness);
}
``` ```
(please see example code for details) for editable group :
* You can select each place to edit the displayed name corresponding to a path
* Double-click on the label to apply the place
![places.gif](https://github.com/aiekick/ImGuiFileDialog/blob/DemoApp/doc/places.gif)
You can also serialize/deserialize groups and places (for example to load/save from/to a file):
```cpp
Load => ImGuiFileDialog::Instance()->DeserializePlaces(placesString);
Save => std::string placesString = ImGuiFileDialog::Instance()->SerializePlaces();
```
(please see DemoApp branch for details)
</blockquote></details> </blockquote></details>
@ -538,17 +579,20 @@ behavior. (by design! :) )
Example code For Standard Dialog : Example code For Standard Dialog :
```cpp ```cpp
IGFD::FileDialogConfig config;
config.path = ".";
config.flags = ImGuiFileDialogFlags_ConfirmOverwrite;
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey",
ICON_IGFD_SAVE " Choose a File", filters, ICON_IGFD_SAVE " Choose a File", filters, config);
".", "", 1, nullptr, ImGuiFileDialogFlags_ConfirmOverwrite);
``` ```
Example code For Modal Dialog : Example code For Modal Dialog :
```cpp ```cpp
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", IGFD::FileDialogConfig config;
ICON_IGFD_SAVE " Choose a File", filters, config.path = ".";
".", "", 1, nullptr, ImGuiFileDialogFlags_Modal | ImGuiFileDialogFlags_ConfirmOverwrite); config.flags = ImGuiFileDialogFlags_Modal | ImGuiFileDialogFlags_ConfirmOverwrite;
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", ICON_IGFD_SAVE " Choose a File", filters, config);
``` ```
This dialog will only verify the file in the file field, not with `GetSelection()`. This dialog will only verify the file in the file field, not with `GetSelection()`.
@ -700,11 +744,14 @@ ImGuiFileDialog fileDialog;
// open dialog; in this case, Bookmark, directory creation are disabled with, and also the file input field is readonly. // open dialog; in this case, Bookmark, directory creation are disabled with, and also the file input field is readonly.
// btw you can od what you want // btw you can od what you want
fileDialog.OpenDialog("embedded", "Select File", ".*", "", -1, nullptr, IGFD::FileDialogConfig config;
ImGuiFileDialogFlags_NoDialog | config.path = ".";
config.countSelectionMax = -1;
config.flags = ImGuiFileDialogFlags_NoDialog |
ImGuiFileDialogFlags_DisableBookmarkMode | ImGuiFileDialogFlags_DisableBookmarkMode |
ImGuiFileDialogFlags_DisableCreateDirectoryButton | ImGuiFileDialogFlags_DisableCreateDirectoryButton |
ImGuiFileDialogFlags_ReadOnlyFileNameField); ImGuiFileDialogFlags_ReadOnlyFileNameField);
fileDialog.OpenDialog("embedded", "Select File", ".*", config);
// then display, here // then display, here
// to note, when embedded the ImVec2(0,0) (MinSize) do nothing, only the ImVec2(0,350) (MaxSize) can size the dialog frame // to note, when embedded the ImVec2(0,0) (MinSize) do nothing, only the ImVec2(0,350) (MaxSize) can size the dialog frame
fileDialog.Display("embedded", ImGuiWindowFlags_NoCollapse, ImVec2(0,0), ImVec2(0,350))) fileDialog.Display("embedded", ImGuiWindowFlags_NoCollapse, ImVec2(0,0), ImVec2(0,350)))
@ -962,6 +1009,76 @@ you can check the DemoApp who is using an override for the Boost::filesystem
</blockquote></details> </blockquote></details>
<details open><summary><h2>Modify file infos during scan by a callback</h2></summary><blockquote>
In some case, it can be unsefull to modify file infos during scan
so you can define your callback and attached it in the FileDialogConfig struct in the field userFileAttributes
the callback stamp is :
```cpp
bool (IGFD::FileInfos* vFileInfosPtr, IGFD::UserDatas vUserDatas)
```
if the callback is returning false, the file is ignored, so not displayed by the dialog
example : with the gltf separated files : (see the branch DemoApp for example use)
A gltf file can have data description and datas files separated.
in this case only the file with description will be shown in the dialog, so with not the full size of all attached datas
with this function, you can compose the path of the bin file, get his size, sum it to the desciption file size and use it
syntax :
```cpp
config.userFileAttributes = [](IGFD::FileInfos* vFileInfosPtr, IGFD::UserDatas vUserDatas) -> bool {
if (vFileInfosPtr != nullptr) {
// this demo not take into account .gltf who have data insise. besauce keepd easy just for demo
if (vFileInfosPtr->SearchForExt(".gltf", true)) {
auto bin_file_path_name = vFileInfosPtr->filePath + IGFD::Utils::GetPathSeparator() + vFileInfosPtr->fileNameLevels[0] + ".bin";
struct stat statInfos = {};
char timebuf[100];
int result = stat(bin_file_path_name.c_str(), &statInfos);
if (!result) {
vFileInfosPtr->fileSize += (size_t)statInfos.st_size; // add the size of bin file to the size of the gltf file
} else {
// no bin, so escaped.
// normally we must parse the file and check the uri for get the buffer file
// but here we keep the example as easy for demo.
return false;
}
}
}
return true;
};
```
Before the User of the userAttribute callback :
![user_files_attributes_before.png](https://github.com/aiekick/ImGuiFileDialog/blob/DemoApp/doc/user_files_attributes_before.png)
After :
![user_files_attributes_after.png](https://github.com/aiekick/ImGuiFileDialog/blob/DemoApp/doc/user_files_attributes_after.png)
You can also display a tootlip for a file displayed when the mouse is over a dedicated column
you juste need to set your message for the FileDialogConfig.tooltipMessage
and specify the column in FileDialogConfig.tooltipColumn
ex code from the DemoApp branch for display the decomposition of gltf total size
syntax :
```cpp
vFileInfosPtr->tooltipMessage = toStr("%s : %s\n%s : %s", //
(vFileInfosPtr->fileNameLevels[0] + ".gltf").c_str(), //
IGFD::Utils::FormatFileSize(vFileInfosPtr->fileSize).c_str(), //
(vFileInfosPtr->fileNameLevels[0] + ".bin").c_str(), //
IGFD::Utils::FormatFileSize((size_t)statInfos.st_size).c_str()); //
vFileInfosPtr->tooltipColumn = 1; // column of file size
```
![file_tooltip_message.png](https://github.com/aiekick/ImGuiFileDialog/blob/DemoApp/doc/file_tooltip_message.png)
</blockquote></details>
<details open><summary><h2>C Api :</h2></summary><blockquote> <details open><summary><h2>C Api :</h2></summary><blockquote>
this api was sucessfully tested with CImGui this api was sucessfully tested with CImGui
@ -976,19 +1093,15 @@ Sample code with cimgui :
ImGuiFileDialog *cfileDialog = IGFD_Create(); ImGuiFileDialog *cfileDialog = IGFD_Create();
// open dialog // open dialog
if (igButton("Open File", buttonSize)) if (igButton("Open File", buttonSize)) {
{ IGFD_FileDialog_Config config = IGFD_FileDialog_Config_Get();
config.path = ".";
config.flags = ImGuiFileDialogFlags_ConfirmOverwrite; // ImGuiFileDialogFlags
IGFD_OpenDialog(cfiledialog, IGFD_OpenDialog(cfiledialog,
"filedlg", // dialog key (make it possible to have different treatment reagrding the dialog key "filedlg", // dialog key (make it possible to have different treatment reagrding the dialog key
"Open a File", // dialog title "Open a File", // dialog title
"c files(*.c *.h){.c,.h}", // dialog filter syntax : simple => .h,.c,.pp, etc and collections : text1{filter0,filter1,filter2}, text2{filter0,filter1,filter2}, etc.. "c files(*.c *.h){.c,.h}", // dialog filter syntax : simple => .h,.c,.pp, etc and collections : text1{filter0,filter1,filter2}, text2{filter0,filter1,filter2}, etc..
".", // base directory for files scan config); // the file dialog config
"", // base filename
0, // a fucntion for display a right pane if you want
0.0f, // base width of the pane
0, // count selection : 0 infinite, 1 one file (default), n (n files)
"User data !", // some user datas
ImGuiFileDialogFlags_ConfirmOverwrite); // ImGuiFileDialogFlags
} }
ImGuiIO* ioptr = igGetIO(); ImGuiIO* ioptr = igGetIO();

View file

@ -9,127 +9,143 @@
// this options need c++17 // this options need c++17
// #define USE_STD_FILESYSTEM // #define USE_STD_FILESYSTEM
//#define MAX_FILE_DIALOG_NAME_BUFFER 1024 // #define MAX_FILE_DIALOG_NAME_BUFFER 1024
//#define MAX_PATH_BUFFER_SIZE 1024 // #define MAX_PATH_BUFFER_SIZE 1024
// the slash's buttons in path cna be used for quick select parallles directories // the slash's buttons in path cna be used for quick select parallles directories
//#define USE_QUICK_PATH_SELECT // #define USE_QUICK_PATH_SELECT
// the spacing between button path's can be customized. // the spacing between button path's can be customized.
// if disabled the spacing is defined by the imgui theme // if disabled the spacing is defined by the imgui theme
// define the space between path buttons // define the space between path buttons
//#define CUSTOM_PATH_SPACING 2 // #define CUSTOM_PATH_SPACING 2
//#define USE_THUMBNAILS // #define USE_THUMBNAILS
//the thumbnail generation use the stb_image and stb_resize lib who need to define the implementation // the thumbnail generation use the stb_image and stb_resize lib who need to define the implementation
//btw if you already use them in your app, you can have compiler error due to "implemntation found in double" // btw if you already use them in your app, you can have compiler error due to "implemntation found in double"
//so uncomment these line for prevent the creation of implementation of these libs again // so uncomment these line for prevent the creation of implementation of these libs again
//#define DONT_DEFINE_AGAIN__STB_IMAGE_IMPLEMENTATION // #define DONT_DEFINE_AGAIN__STB_IMAGE_IMPLEMENTATION
//#define DONT_DEFINE_AGAIN__STB_IMAGE_RESIZE_IMPLEMENTATION // #define DONT_DEFINE_AGAIN__STB_IMAGE_RESIZE_IMPLEMENTATION
//#define IMGUI_RADIO_BUTTON RadioButton // #define IMGUI_RADIO_BUTTON RadioButton
//#define DisplayMode_ThumbailsList_ImageHeight 32.0f // #define DisplayMode_ThumbailsList_ImageHeight 32.0f
//#define tableHeaderFileThumbnailsString "Thumbnails" // #define tableHeaderFileThumbnailsString "Thumbnails"
//#define DisplayMode_FilesList_ButtonString "FL" // #define DisplayMode_FilesList_ButtonString "FL"
//#define DisplayMode_FilesList_ButtonHelp "File List" // #define DisplayMode_FilesList_ButtonHelp "File List"
//#define DisplayMode_ThumbailsList_ButtonString "TL" // #define DisplayMode_ThumbailsList_ButtonString "TL"
//#define DisplayMode_ThumbailsList_ButtonHelp "Thumbnails List" // #define DisplayMode_ThumbailsList_ButtonHelp "Thumbnails List"
// todo // todo
//#define DisplayMode_ThumbailsGrid_ButtonString "TG" // #define DisplayMode_ThumbailsGrid_ButtonString "TG"
//#define DisplayMode_ThumbailsGrid_ButtonHelp "Thumbnails Grid" // #define DisplayMode_ThumbailsGrid_ButtonHelp "Thumbnails Grid"
//#define USE_EXPLORATION_BY_KEYS // #define USE_EXPLORATION_BY_KEYS
// this mapping by default is for GLFW but you can use another // this mapping by default is for GLFW but you can use another
//#include <GLFW/glfw3.h> // #include <GLFW/glfw3.h>
// Up key for explore to the top // Up key for explore to the top
//#define IGFD_KEY_UP ImGuiKey_UpArrow // #define IGFD_KEY_UP ImGuiKey_UpArrow
// Down key for explore to the bottom // Down key for explore to the bottom
//#define IGFD_KEY_DOWN ImGuiKey_DownArrow // #define IGFD_KEY_DOWN ImGuiKey_DownArrow
// Enter key for open directory // Enter key for open directory
//#define IGFD_KEY_ENTER ImGuiKey_Enter // #define IGFD_KEY_ENTER ImGuiKey_Enter
// BackSpace for comming back to the last directory // BackSpace for comming back to the last directory
//#define IGFD_KEY_BACKSPACE ImGuiKey_Backspace // #define IGFD_KEY_BACKSPACE ImGuiKey_Backspace
// by ex you can quit the dialog by pressing the key excape // by ex you can quit the dialog by pressing the key excape
//#define USE_DIALOG_EXIT_WITH_KEY // #define USE_DIALOG_EXIT_WITH_KEY
//#define IGFD_EXIT_KEY ImGuiKey_Escape // #define IGFD_EXIT_KEY ImGuiKey_Escape
// widget // widget
// begin combo widget // begin combo widget
//#define IMGUI_BEGIN_COMBO ImGui::BeginCombo // #define IMGUI_BEGIN_COMBO ImGui::BeginCombo
// when auto resized, FILTER_COMBO_MIN_WIDTH will be considered has minimum width // when auto resized, FILTER_COMBO_MIN_WIDTH will be considered has minimum width
// FILTER_COMBO_AUTO_SIZE is enabled by default now to 1 // FILTER_COMBO_AUTO_SIZE is enabled by default now to 1
// uncomment if you want disable // uncomment if you want disable
//#define FILTER_COMBO_AUTO_SIZE 0 // #define FILTER_COMBO_AUTO_SIZE 0
// filter combobox width // filter combobox width
//#define FILTER_COMBO_MIN_WIDTH 120.0f // #define FILTER_COMBO_MIN_WIDTH 120.0f
// button widget use for compose path // button widget use for compose path
//#define IMGUI_PATH_BUTTON ImGui::Button // #define IMGUI_PATH_BUTTON ImGui::Button
// standard button // standard button
//#define IMGUI_BUTTON ImGui::Button // #define IMGUI_BUTTON ImGui::Button
// locales string // locales string
//#define createDirButtonString "+" // #define createDirButtonString "+"
//#define resetButtonString "R" // #define resetButtonString "R"
//#define drivesButtonString "Drives" // #define drivesButtonString "Drives"
//#define editPathButtonString "E" // #define editPathButtonString "E"
//#define searchString "Search" // #define searchString "Search"
//#define dirEntryString "[DIR] " // #define dirEntryString "[DIR] "
//#define linkEntryString "[LINK] " // #define linkEntryString "[LINK] "
//#define fileEntryString "[FILE] " // #define fileEntryString "[FILE] "
//#define fileNameString "File Name : " // #define fileNameString "File Name : "
//#define dirNameString "Directory Path :" // #define dirNameString "Directory Path :"
//#define buttonResetSearchString "Reset search" // #define buttonResetSearchString "Reset search"
//#define buttonDriveString "Drives" // #define buttonDriveString "Drives"
//#define buttonEditPathString "Edit path\nYou can also right click on path buttons" // #define buttonEditPathString "Edit path\nYou can also right click on path buttons"
//#define buttonResetPathString "Reset to current directory" // #define buttonResetPathString "Reset to current directory"
//#define buttonCreateDirString "Create Directory" // #define buttonCreateDirString "Create Directory"
//#define OverWriteDialogTitleString "The file Already Exist !" // #define OverWriteDialogTitleString "The file Already Exist !"
//#define OverWriteDialogMessageString "Would you like to OverWrite it ?" // #define OverWriteDialogMessageString "Would you like to OverWrite it ?"
//#define OverWriteDialogConfirmButtonString "Confirm" // #define OverWriteDialogConfirmButtonString "Confirm"
//#define OverWriteDialogCancelButtonString "Cancel" // #define OverWriteDialogCancelButtonString "Cancel"
//Validation buttons // Validation buttons
//#define okButtonString " OK" // #define okButtonString " OK"
//#define okButtonWidth 0.0f // #define okButtonWidth 0.0f
//#define cancelButtonString " Cancel" // #define cancelButtonString " Cancel"
//#define cancelButtonWidth 0.0f // #define cancelButtonWidth 0.0f
//alignement [0:1], 0.0 is left, 0.5 middle, 1.0 right, and other ratios // alignement [0:1], 0.0 is left, 0.5 middle, 1.0 right, and other ratios
//#define okCancelButtonAlignement 0.0f // #define okCancelButtonAlignement 0.0f
//#define invertOkAndCancelButtons 0 // #define invertOkAndCancelButtons 0
// DateTimeFormat // DateTimeFormat
// see strftime functionin <ctime> for customize // see strftime functionin <ctime> for customize
// "%Y/%m/%d %H:%M" give 2021:01:22 11:47 // "%Y/%m/%d %H:%M" give 2021:01:22 11:47
// "%Y/%m/%d %i:%M%p" give 2021:01:22 11:45PM // "%Y/%m/%d %i:%M%p" give 2021:01:22 11:45PM
//#define DateTimeFormat "%Y/%m/%d %i:%M%p" // #define DateTimeFormat "%Y/%m/%d %i:%M%p"
// theses icons will appear in table headers // theses icons will appear in table headers
//#define USE_CUSTOM_SORTING_ICON // #define USE_CUSTOM_SORTING_ICON
//#define tableHeaderAscendingIcon "A|" // #define tableHeaderAscendingIcon "A|"
//#define tableHeaderDescendingIcon "D|" // #define tableHeaderDescendingIcon "D|"
//#define tableHeaderFileNameString " File name" // #define tableHeaderFileNameString " File name"
//#define tableHeaderFileTypeString " Type" // #define tableHeaderFileTypeString " Type"
//#define tableHeaderFileSizeString " Size" // #define tableHeaderFileSizeString " Size"
//#define tableHeaderFileDateTimeString " Date" // #define tableHeaderFileDateTimeString " Date"
//#define fileSizeBytes "o" // #define fileSizeBytes "o"
//#define fileSizeKiloBytes "Ko" // #define fileSizeKiloBytes "Ko"
//#define fileSizeMegaBytes "Mo" // #define fileSizeMegaBytes "Mo"
//#define fileSizeGigaBytes "Go" // #define fileSizeGigaBytes "Go"
// default table sort field (must be FIELD_FILENAME, FIELD_TYPE, FIELD_SIZE, FIELD_DATE or FIELD_THUMBNAILS) // default table sort field (must be FIELD_FILENAME, FIELD_TYPE, FIELD_SIZE, FIELD_DATE or FIELD_THUMBNAILS)
//#define defaultSortField FIELD_FILENAME // #define defaultSortField FIELD_FILENAME
// default table sort order for each field (true => Descending, false => Ascending) // default table sort order for each field (true => Descending, false => Ascending)
//#define defaultSortOrderFilename true // #define defaultSortOrderFilename true
//#define defaultSortOrderType true // #define defaultSortOrderType true
//#define defaultSortOrderSize true // #define defaultSortOrderSize true
//#define defaultSortOrderDate true // #define defaultSortOrderDate true
//#define defaultSortOrderThumbnails true // #define defaultSortOrderThumbnails true
//#define USE_BOOKMARK #define USE_PLACES_FEATURE
//#define bookmarkPaneWith 150.0f // #define PLACES_PANE_DEFAULT_SHOWN false
//#define IMGUI_TOGGLE_BUTTON ToggleButton // #define placesPaneWith 150.0f
//#define bookmarksButtonString "Bookmark" // #define IMGUI_TOGGLE_BUTTON ToggleButton
//#define bookmarksButtonHelpString "Bookmark" // #define placesButtonString "Place"
//#define addBookmarkButtonString "+" // #define placesButtonHelpString "Places"
//#define removeBookmarkButtonString "-" // #define addPlaceButtonString "+"
// #define removePlaceButtonString "-"
// #define validatePlaceButtonString "ok"
// #define editPlaceButtonString "E"
// a group for bookmarks will be added by default, but you can also create it yourself and many more
#define USE_PLACES_BOOKMARKS
// #define PLACES_BOOKMARK_DEFAULT_OPEPEND true
// #define placesBookmarksGroupName "Bookmarks"
// #define placesBookmarksDisplayOrder 0 // to the first
// a group for system devices (returned by IFileSystem), but you can also add yours
// by ex if you would like to display a specific icon for some devices
#define USE_PLACES_DEVICES
// #define PLACES_DEVICES_DEFAULT_OPEPEND true
// #define placesDevicesGroupName "Devices"
// #define placesDevicesDisplayOrder 10 // to the end

View file

@ -31,6 +31,7 @@ public:
virtual ~IStoryManager() {} virtual ~IStoryManager() {}
virtual void OpenProject(const std::string &uuid) = 0; virtual void OpenProject(const std::string &uuid) = 0;
virtual void ImportProject(const std::string &fileName, int format) = 0;
virtual void Log(const std::string &txt, bool critical = false) = 0; virtual void Log(const std::string &txt, bool critical = false) = 0;
virtual void PlaySoundFile(const std::string &fileName) = 0; virtual void PlaySoundFile(const std::string &fileName) = 0;
virtual std::string BuildFullAssetsPath(const std::string &fileName) const = 0; virtual std::string BuildFullAssetsPath(const std::string &fileName) const = 0;

View file

@ -0,0 +1,234 @@
#include <stdio.h>
#include <string.h>
#include <math.h>
#include "ni_parser.h"
#include "serializers.h"
#define MAX_NB_NODES 1000
static ni_node_t gNodes[MAX_NB_NODES];
static struct {
uint32_t ri_size;
uint32_t si_size;
uint32_t li_size;
uint8_t gRiBlock[512];
uint8_t gSiBlock[512];
uint8_t gLiBlock[512];
} pack;
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))
static void btea_decode(uint32_t *v, uint32_t n, const uint32_t *key)
{
uint32_t y, z, sum;
uint32_t p, e;
uint32_t rounds = 1 + 52/n;
sum = rounds * DELTA;
y = v[0];
do {
e = (sum >> 2) & 3;
for (p=n-1; p>0; p--) {
z = v[p-1];
y = v[p] -= MX;
}
z = v[n-1];
y = v[0] -= MX;
sum -= DELTA;
} while (--rounds);
}
static const uint32_t key[4] = {0x91bd7a0a, 0xa75440a9, 0xbbd49d6c, 0xe0dcc0e3};
void ni_set_ri_block(const uint8_t *data, uint32_t size)
{
memcpy(pack.gRiBlock, data, size);
pack.ri_size = size;
uint32_t n = size / 4;
btea_decode((uint32_t*)pack.gRiBlock, n, key);
}
uint32_t ni_get_ri_block(uint8_t *data)
{
memcpy(data, pack.gRiBlock, pack.ri_size);
return pack.ri_size;
}
uint32_t ni_get_si_block(uint8_t *data)
{
memcpy(data, pack.gSiBlock, pack.si_size);
return pack.si_size;
}
uint32_t ni_get_li_block(uint8_t *data)
{
memcpy(data, pack.gLiBlock, pack.li_size);
return pack.li_size;
}
void ni_set_si_block(const uint8_t *data, uint32_t size)
{
memcpy(pack.gSiBlock, data, size);
pack.si_size = size;
uint32_t n = size / 4;
btea_decode((uint32_t*)pack.gSiBlock, n, key);
}
void ni_set_li_block(const uint8_t *data, uint32_t size)
{
memcpy(pack.gLiBlock, data, size);
pack.li_size = size;
uint32_t n = size / 4;
btea_decode((uint32_t*) pack.gLiBlock, n, key);
}
void ni_decode_block512(uint8_t *data)
{
btea_decode((uint32_t*) data, 128, key);
}
uint32_t ni_get_number_of_images()
{
return pack.ri_size / 12;
}
void ni_get_image(char buffer[13], uint32_t index)
{
uint32_t offset = index * 12;
if (offset >= pack.ri_size)
{
offset = 0;
}
memcpy(buffer, &pack.gRiBlock[offset], 12);
buffer[12] = '\0';
}
bool ni_get_node_info(uint32_t index, node_info_t *node)
{
bool success = false;
if (index < sizeof(gNodes))
{
node->current = &gNodes[index];
// Copy sound file name
uint32_t offset = node->current->sound_asset_index_in_si * 12;
memcpy(node->si_file, &pack.gSiBlock[offset], 12);
node->si_file[12] = '\0';
// Copy image file name
if (node->current->image_asset_index_in_ri != 0xFFFFFFFF)
{
offset = node->current->image_asset_index_in_ri * 12;
memcpy(node->ri_file, &pack.gRiBlock[offset], 12);
node->ri_file[12] = '\0';
}
else
{
// Pas d'image pour ce noeud
node->ri_file[0] = '\0';
}
}
else
{
printf("Bad node index\r\n");
}
return success;
}
// index en mot de 32 bits: il faut le multiplier par 4 pour avoir l'offet en octet
uint32_t ni_get_node_index_in_li(uint32_t index_in_li, uint32_t selected)
{
uint32_t node_index = 0; // si erreur, on revient au point de départ
if ((index_in_li * 4) < pack.li_size)
{
node_index = leu32_get(&pack.gLiBlock[(index_in_li + selected) * 4]);
}
else
{
printf("index_in_li too large\r\n");
}
return node_index;
}
void ni_parse_nodes(ni_file_t *ni_file, const uint8_t *data)
{
if (ni_file->stage_nodes_count <= MAX_NB_NODES)
{
for (uint32_t i = 0; i < ni_file->stage_nodes_count; i++)
{
ni_node_t *n = &gNodes[i];
const uint8_t *ptr = data + 512 + (ni_file->node_size * i);
n->image_asset_index_in_ri = leu32_get(ptr);
ptr += 4;
n->sound_asset_index_in_si = leu32_get(ptr);
ptr += 4;
n->ok_transition_action_node_index_in_li = leu32_get(ptr);
ptr += 4;
n->ok_transition_number_of_options = leu32_get(ptr);
ptr += 4;
n->ok_transition_selected_option_index = leu32_get(ptr);
ptr += 4;
n->home_transition_action_node_index_in_li = leu32_get(ptr);
ptr += 4;
n->home_transition_number_of_options = leu32_get(ptr);
ptr += 4;
n->home_transition_selected_option_index = leu32_get(ptr);
ptr += 4;
n->wheel = leu16_get(ptr);
ptr += 2;
n->ok = leu16_get(ptr);
ptr += 2;
n->home = leu16_get(ptr);
ptr += 2;
n->pause = leu16_get(ptr);
ptr += 2;
n->auto_play = leu16_get(ptr);
ptr += 2;
n->unknown = leu16_get(ptr);
}
}
}
bool ni_parser(ni_file_t *ni_file, const uint8_t *data)
{
bool success = false;
const uint8_t *ptr = data;
ni_file->ni_version = leu16_get(ptr);
ptr += 2;
ni_file->pack_version = leu16_get(ptr);
ptr += 2;
ni_file->node_list_start = leu32_get(ptr);
ptr += 4;
ni_file->node_size = leu32_get(ptr);
ptr += 4;
ni_file->stage_nodes_count = leu32_get(ptr);
ptr += 4;
ni_file->image_assets_count = leu32_get(ptr);
ptr += 4;
ni_file->sound_assets_count = leu32_get(ptr);
success = true;
ni_parse_nodes(ni_file, data);
return success;
}
void ni_dump(ni_file_t *ni_file)
{
printf("NI version: %d \r\n", ni_file->ni_version);
printf("Pack version: %d \r\n", ni_file->ni_version);
printf("Node List start: %d \r\n", ni_file->node_list_start);
printf("Node size: %d \r\n", ni_file->node_size);
printf("Stage nodes count: %d \r\n", ni_file->stage_nodes_count);
printf("Image assets count: %d \r\n", ni_file->image_assets_count);
printf("Sound assets count: %d \r\n", ni_file->sound_assets_count);
}

View file

@ -0,0 +1,68 @@
#ifndef NI_PARSER_H
#define NI_PARSER_H
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
uint32_t image_asset_index_in_ri;
uint32_t sound_asset_index_in_si;
uint32_t ok_transition_action_node_index_in_li;
uint32_t ok_transition_number_of_options;
uint32_t ok_transition_selected_option_index;
uint32_t home_transition_action_node_index_in_li;
uint32_t home_transition_number_of_options;
uint32_t home_transition_selected_option_index;
bool wheel;
bool ok;
bool home;
bool pause;
bool auto_play;
uint16_t unknown;
} ni_node_t;
typedef struct {
ni_node_t *current;
char ri_file[13]; // 12 + \0
char si_file[13]; // 12 + \0
} node_info_t;
typedef struct
{
uint16_t ni_version;
uint16_t pack_version;
uint32_t node_list_start;
uint32_t node_size;
uint32_t stage_nodes_count;
uint32_t image_assets_count;
uint32_t sound_assets_count;
} ni_file_t;
uint32_t ni_get_number_of_images();
void ni_get_image(char buffer[13], uint32_t index);
uint32_t ni_get_node_index_in_li(uint32_t index_in_li, uint32_t selected);
void ni_decode_block512(uint8_t *data);
bool ni_get_node_info(uint32_t index, node_info_t *node);
bool ni_parser(ni_file_t *ni_file, const uint8_t *data);
void ni_dump(ni_file_t *ni_file);
void ni_set_ri_block(const uint8_t *data, uint32_t size);
void ni_set_si_block(const uint8_t *data, uint32_t size);
void ni_set_li_block(const uint8_t *data, uint32_t size);
uint32_t ni_get_ri_block(uint8_t *data);
uint32_t ni_get_si_block(uint8_t *data);
uint32_t ni_get_li_block(uint8_t *data);
#ifdef __cplusplus
}
#endif
#endif // NI_PARSER_H

View file

@ -0,0 +1,620 @@
#include "pack_archive.h"
#include "ni_parser.h"
#include "json.hpp"
#include "serializers.h"
#include <iostream>
#include <algorithm>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <filesystem>
PackArchive::PackArchive()
{
}
std::vector<std::string> PackArchive::GetImages()
{
std::vector<std::string> imgList;
for (uint32_t i = 0; i < ni_get_number_of_images(); i++)
{
char buffer[13];
ni_get_image(buffer, i);
imgList.push_back(buffer);
}
return imgList;
}
std::string PackArchive::GetFileName(const std::string &path)
{
auto found = path.find_last_of("/\\");
return path.substr(found+1);
}
std::string PackArchive::GetFileExtension(const std::string &FileName)
{
if(FileName.find_last_of(".") != std::string::npos)
return FileName.substr(FileName.find_last_of(".")+1);
return "";
}
void PackArchive::ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace)
{
std::size_t found;
do
{
found = theString.find(toFind);
if (found != std::string::npos)
{
theString.replace(found, 1, toReplace);
}
}
while (found != std::string::npos);
}
void PackArchive::EraseString(std::string &theString, const std::string &toErase)
{
std::size_t found;
found = theString.find(toErase);
if (found != std::string::npos)
{
theString.erase(found, toErase.size());
}
}
std::string PackArchive::ToUpper(const std::string &input)
{
std::string str = input;
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
return str;
}
void PackArchive::Unzip(const std::string &filePath, const std::string &parent_dest_dir)
{
// std::string fileName = GetFileName(filePath);
// std::string ext = GetFileExtension(fileName);
// EraseString(fileName, "." + ext); // on retire l'extension du pack
// std::string path = parent_dest_dir + "/" + ToUpper(fileName);
if (std::filesystem::exists(parent_dest_dir))
{
// 1. First delete files
for(const std::filesystem::directory_entry& entry : std::filesystem::recursive_directory_iterator(parent_dest_dir))
{
if (std::filesystem::is_regular_file(entry)) {
std::filesystem::remove(entry.path());
}
}
}
// 2. then delete directories
std::filesystem::remove_all(parent_dest_dir);
std::filesystem::create_directories(parent_dest_dir);
(void) Zip::Unzip(filePath, parent_dest_dir, "");
}
static bool endsWith(const std::string& str, const std::string& suffix)
{
return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix);
}
bool StringToFile(const std::string &filePath, const std::string &data)
{
bool success = false;
std::ofstream outFile(filePath, std::ifstream::out);
if (outFile.is_open())
{
outFile << data << std::endl;
outFile.close();
success = true;
}
return success;
}
void PackArchive::DecipherFileOnDisk(const std::string &fileName)
{
FILE * pFile;
long lSize = 512;
char * buffer;
size_t result;
pFile = fopen ( fileName.c_str() , "rb+" );
if (pFile==NULL) {fputs ("File error",stderr); exit (1);}
// allocate memory to contain the whole file:
buffer = (char*) malloc (sizeof(char)*lSize);
if (buffer == NULL) {fprintf(stderr, "Memory error: %s", fileName.c_str()); exit (2);}
// copy the file into the buffer:
result = fread (buffer,1,lSize,pFile);
if (result != lSize) {fprintf(stderr, "Reading error: %s", fileName.c_str()); exit (3);}
/* the whole file is now loaded in the memory buffer. */
// Decipher
ni_decode_block512(reinterpret_cast<uint8_t *>(buffer));
fseek (pFile , 0 , SEEK_SET); // ensure we are at the begining
// Write back data on the disk
fwrite (buffer , sizeof(char), lSize, pFile);
// terminate
fclose (pFile);
free (buffer);
}
void WriteDataOnDisk(const std::string &fileName, const uint8_t *data, uint32_t size)
{
FILE * pFile;
pFile = fopen ( fileName.c_str() , "wb" );
fwrite (data , sizeof(char), size, pFile);
fclose (pFile);
}
void PackArchive::DecipherFiles(const std::string &directory, const std::string &suffix)
{
for (const auto & rf : std::filesystem::directory_iterator(directory))
{
std::string oldFile = rf.path();
// std::cout << oldFile << std::endl;
DecipherFileOnDisk(oldFile);
std::string newName = oldFile + suffix;
rename(oldFile.c_str(), newName.c_str());
}
}
std::vector<std::string> PackArchive::FilesToJson(const std::string &type, const uint8_t *data, uint32_t nb_elements)
{
char res_file[13]; // 12 + \0
std::vector<std::string> resList;
for (int i = 0; i < nb_elements; i++)
{
memcpy(res_file, &data[i*12], 12);
res_file[12] = '\0';
std::string res_file_string(res_file);
ReplaceCharacter(res_file_string, "\\", "/");
resList.push_back(res_file_string);
m_resources[type + std::to_string(i)] = res_file_string;
}
return resList;
}
void PackArchive::DecipherAll(const std::string &packFileName, const std::string &parent_dest_dir)
{
// return;
Unzip(packFileName, parent_dest_dir);
Load(packFileName);
std::string path = mPackName + "/rf";
for (const auto & entry : std::filesystem::directory_iterator(path))
{
std::cout << entry.path() << std::endl;
DecipherFiles(entry.path(), ".bmp");
}
path = mPackName + "/sf";
for (const auto & entry : std::filesystem::directory_iterator(path))
{
std::cout << entry.path() << std::endl;
DecipherFiles(entry.path(), ".mp3");
}
nlohmann::json j;
std::ofstream chip32("pack.chip32");
/*
$imageBird DC8 "example.bmp", 8 ; data
$someConstant DC32 12456789
; DSxx to declare a variable in RAM, followed by the number of elements
$RamData1 DV32 1 ; one 32-bit integer
$MyArray DV8 10 ; array of 10 bytes
; label definition
.entry: ;; comment here should work
*/
// NI file is not ciphered
uint8_t data[512];
uint32_t size = ni_get_ri_block(data);
WriteDataOnDisk(mPackName + "/ri", data, size);
nlohmann::json jsonImages;
{
std::vector<std::string> lst = FilesToJson("ri", data, mNiFile.image_assets_count);
for (auto &l : lst)
{
nlohmann::json obj;
obj["file"] = l;
obj["description"] = "";
obj["format"] = "bmp";
jsonImages.push_back(obj);
}
}
j["images"] = jsonImages;
size = ni_get_si_block(data);
WriteDataOnDisk(mPackName + "/si", data, size);
nlohmann::json jsonSounds;
{
nlohmann::json obj;
std::vector<std::string> lst = FilesToJson("si", data, mNiFile.sound_assets_count);
for (auto &l : lst)
{
nlohmann::json obj;
obj["file"] = l;
obj["description"] = "";
obj["format"] = "mp3";
jsonSounds.push_back(obj);
}
}
j["sounds"] = jsonSounds;
size = ni_get_li_block(data);
WriteDataOnDisk(mPackName + "/li", data, size);
std::vector<uint32_t> transitions;
// each entry of the transitions array is a 32-bit integer
for (int i = 0; i < size;)
{
transitions.push_back(leu32_get(&data[i]));
i += 4;
}
// Transform into JSON
node_info_t node;
nlohmann::json jsonNodes;
for (int i = 0; i < mNiFile.node_size; i++)
{
nlohmann::json jNode;
ni_get_node_info(i, &node);
jNode["id"] = i;
jNode["image"] = static_cast<int>(node.current->image_asset_index_in_ri);
jNode["sound"] = static_cast<int>(node.current->sound_asset_index_in_si);
jNode["auto_jump"] = node.current->auto_play == 1 ? true : false;
nlohmann::json jumpArray;
for (int jIndex = 0; jIndex < node.current->ok_transition_number_of_options; jIndex++)
{
jumpArray.push_back(transitions[node.current->ok_transition_action_node_index_in_li + jIndex]);
}
jNode["jumps"] = jumpArray;
// Autre cas
if (node.current->ok_transition_number_of_options >= 5)
{
// For now, consider that this is a bad format
// In the future, consider using ok_transition_selected_option_index when ok_transition_number_of_options == 10
// 00 00 00 00 ==> ok transition à zéro
// 0A 00 00 00 ==> nombre spécial, ou vraiment l'offset dans le fichier LI ?
// 01 00 00 00 ==> l'index dans le fichier LI à l'offset (disons, le premier élément)
std::cout << "!!!!!!!!!!!!!!!!!!" << std::endl;
}
chip32 << ".node" << std::to_string(i) << ":\r\n";
if (node.current->image_asset_index_in_ri != 0xFFFFFFFF)
{
// Image index is in register R0
chip32 << "\tmov " << "r0, " << "$ri" << std::to_string(node.current->image_asset_index_in_ri) << "\r\n";
// print image syscall
chip32 << "\tsyscall 1\r\n";
}
if (node.current->sound_asset_index_in_si != 0xFFFFFFFF)
{
// Image index is in register R0
chip32 << "\tmov " << "r0, " << "$si" << std::to_string(node.current->sound_asset_index_in_si) << "\r\n";
// print image syscall
chip32 << "\tsyscall 2\r\n";
}
chip32 << "$li" << std::to_string(i) << " DC8 ";
size = ni_get_li_block(data);
for (int tr = 0; tr < node.current->ok_transition_number_of_options; tr++)
{
uint32_t val = leu32_get(&data[(node.current->ok_transition_action_node_index_in_li + tr) * 4]);
chip32 << std::to_string(val);
if (tr < (node.current->ok_transition_number_of_options - 1))
{
chip32 << ", ";
}
}
chip32 << "\r\n";
chip32 << "\tsyscall 3\r\n"; // wait select
// TODO: tester le retour d'un wait event
jsonNodes.push_back(jNode);
}
j["nodes"] = jsonNodes;
j["code"] = mPackName;
j["name"] = "";
j["type"] = "lunii";
std::ofstream o("pack.json");
o << std::setw(4) << j << std::endl; // pretty print
o.close();
chip32.close();
}
bool PackArchive::Load(const std::string &filePath)
{
bool success = false;
mZip.Close();
mCurrentNodeId = 0;
std::string fileName = GetFileName(filePath);
std::string ext = GetFileExtension(fileName);
EraseString(fileName, "." + ext); // on retire l'extension du pack
mPackName = ToUpper(fileName);
std::cout << "Pack name: " << mPackName << std::endl;
if (mZip.Open(filePath, true))
{
std::cout << "Number of files: " << mZip.NumberOfEntries() << std::endl;
if (ParseNIFile(mPackName))
{
success = true;
std::cout << "Parse NI file success\r\n" << std::endl;
ni_dump(&mNiFile);
ni_get_node_info(mCurrentNodeId, &mCurrentNode);
}
else
{
std::cout << "Parse NI file error\r\n" << std::endl;
}
}
return success;
}
std::string PackArchive::OpenImage(const std::string &fileName)
{
std::string f;
mZip.GetFile(fileName, f);
return f;
}
std::string PackArchive::GetImage(const std::string &fileName)
{
//"C8B39950DE174EAA8E852A07FC468267/rf/000/05FB5530"
std::string imagePath = mPackName + "/rf/" + fileName;
ReplaceCharacter(imagePath, "\\", "/");
std::cout << "Loading " + imagePath << std::endl;
return OpenImage(imagePath);
}
std::string PackArchive::CurrentImage()
{
return GetImage(std::string(mCurrentNode.ri_file));
}
std::string PackArchive::CurrentSound()
{
//"C8B39950DE174EAA8E852A07FC468267/sf/000/05FB5530"
std::string soundPath = mPackName + "/sf/" + std::string(mCurrentNode.si_file);
ReplaceCharacter(soundPath, "\\", "/");
std::cout << "Loading " + soundPath << std::endl;
std::string f;
if (mZip.GetFile(soundPath, f))
{
ni_decode_block512(reinterpret_cast<uint8_t *>(f.data()));
return f;
}
else
{
std::cout << "Cannot load file from ZIP" << std::endl;
}
return "";
}
std::string PackArchive::CurrentSoundName()
{
return std::string(mCurrentNode.si_file);
}
bool PackArchive::AutoPlay()
{
return mCurrentNode.current->auto_play;
}
bool PackArchive::IsRoot() const
{
return mCurrentNodeId == 0;
}
bool PackArchive::IsWheelEnabled() const
{
return mCurrentNode.current->wheel;
}
void PackArchive::Next()
{
// L'index de circulation dans le tableau des transitions commence à 1 (pas à zéro ...)
uint32_t index = 1;
if (mCurrentNode.current->ok_transition_selected_option_index < mNodeForChoice.current->ok_transition_number_of_options)
{
index = mCurrentNode.current->ok_transition_selected_option_index + 1;
}
// sinon on revient à l'index 0 (début du tableau des transitions)
mCurrentNodeId = ni_get_node_index_in_li(mNodeForChoice.current->ok_transition_action_node_index_in_li, index - 1);
ni_get_node_info(mCurrentNodeId, &mCurrentNode);
}
void PackArchive::Previous()
{
// L'index de circulation dans le tableau des transitions commence à 1 (pas à zéro ...)
uint32_t index = 1;
if (mCurrentNode.current->ok_transition_selected_option_index > 1)
{
index = mCurrentNode.current->ok_transition_selected_option_index - 1;
}
else
{
index = mNodeForChoice.current->ok_transition_number_of_options;
}
mCurrentNodeId = ni_get_node_index_in_li(mNodeForChoice.current->ok_transition_action_node_index_in_li, index - 1);
ni_get_node_info(mCurrentNodeId, &mCurrentNode);
}
void PackArchive::OkButton()
{
if (mCurrentNode.current->home_transition_number_of_options > 0)
{
// On doit faire un choix!
// On sauvegarde ce noeud car il va servir pour naviguer dans les choix
mNodeIdForChoice = mCurrentNodeId;
ni_get_node_info(mNodeIdForChoice, &mNodeForChoice);
}
mCurrentNodeId = ni_get_node_index_in_li(mCurrentNode.current->ok_transition_action_node_index_in_li, mCurrentNode.current->ok_transition_selected_option_index);
ni_get_node_info(mCurrentNodeId, &mCurrentNode);
}
bool PackArchive::HasImage()
{
return std::string(mCurrentNode.ri_file).size() > 1;
}
bool PackArchive::ParseNIFile(const std::string &root)
{
bool success = true;
std::string f;
if (mZip.GetFile(root + "/li", f))
{
ni_set_li_block(reinterpret_cast<const uint8_t *>(f.data()), f.size());
}
else
{
success = false;
std::cout << "[PACK_ARCHIVE] Cannot find LI file" << std::endl;
}
if (mZip.GetFile(root + "/ri", f))
{
ni_set_ri_block(reinterpret_cast<const uint8_t *>(f.data()), f.size());
}
else
{
success = false;
std::cout << "[PACK_ARCHIVE] Cannot find RI file" << std::endl;
}
if (mZip.GetFile(root + "/si", f))
{
ni_set_si_block(reinterpret_cast<const uint8_t *>(f.data()), f.size());
}
else
{
success = false;
std::cout << "[PACK_ARCHIVE] Cannot find SI file" << std::endl;
}
if (mZip.GetFile(root + "/ni", f))
{
success = success & ni_parser(&mNiFile, reinterpret_cast<const uint8_t *>(f.data()));
}
else
{
std::cout << "[PACK_ARCHIVE] Cannot find NI file" << std::endl;
}
return success;
}
std::string PackArchive::HexDump(const char *desc, const void *addr, int len)
{
int i;
unsigned char buff[17];
const unsigned char *pc = static_cast<const unsigned char*>(addr);
std::stringstream ss;
// Output description if given.
if (desc != nullptr)
{
ss << desc << ":\n";
}
if (len == 0) {
ss << " ZERO LENGTH\n";
return ss.str();
}
if (len < 0) {
ss << " NEGATIVE LENGTH: " << len << "\n";
return ss.str();
}
// Process every byte in the data.
for (i = 0; i < len; i++) {
// Multiple of 16 means new line (with line offset).
if ((i % 16) == 0) {
// Just don't print ASCII for the zeroth line.
if (i != 0)
ss << " " << buff << "\n";
// Output the offset.
ss << " " << std::setfill('0') << std::setw(4) << std::hex << i;
}
// Now the hex byte_to_hexcode for the specific character.
ss << " " << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(pc[i]) << ", ";
// And store a printable ASCII character for later.
if ((pc[i] < 0x20) || (pc[i] > 0x7e))
buff[i % 16] = '.';
else
buff[i % 16] = pc[i];
buff[(i % 16) + 1] = '\0';
}
// Pad out last line if not exactly 16 characters.
while ((i % 16) != 0) {
ss << " ";
i++;
}
// And print the final ASCII bit.
ss << " "<< buff << "\n";
return ss.str();
}

View file

@ -0,0 +1,58 @@
#ifndef PACK_ARCHIVE_H
#define PACK_ARCHIVE_H
#include <string>
#include "zip.h"
#include <vector>
#include "ni_parser.h"
#include "json.hpp"
class PackArchive
{
public:
PackArchive();
bool Load(const std::string &filePath);
std::string OpenImage(const std::string &fileName);
std::string CurrentImage();
std::string CurrentSound();
std::string CurrentSoundName();
bool AutoPlay();
void OkButton();
bool HasImage();
std::vector<std::string> GetImages();
std::string GetImage(const std::string &fileName);
bool IsRoot() const;
bool IsWheelEnabled() const;
void Next();
void Previous();
void Unzip(const std::string &filePath, const std::string &parent_dest_dir);
void DecipherAll(const std::string &packFileName, const std::string &parent_dest_dir);
std::string HexDump(const char *desc, const void *addr, int len);
private:
Zip mZip;
std::string mPackName;
uint32_t mCurrentNodeId = 0;
uint32_t mNodeIdForChoice = 0;
node_info_t mNodeForChoice;
node_info_t mCurrentNode;
ni_file_t mNiFile;
// key: resource tag
// value: resource file name
std::map<std::string, std::string> m_resources;
bool ParseNIFile(const std::string &root);
std::string GetFileName(const std::string &path);
std::string GetFileExtension(const std::string &FileName);
void ReplaceCharacter(std::string &theString, const std::string &toFind, const std::string &toReplace);
void EraseString(std::string &theString, const std::string &toErase);
std::string ToUpper(const std::string &input);
void DecipherFileOnDisk(const std::string &fileName);
void DecipherFiles(const std::string &directory, const std::string &suffix);
std::vector<std::string> FilesToJson(const std::string &type, const uint8_t *data, uint32_t nb_elements);
};
#endif // PACK_ARCHIVE_H

View file

@ -331,12 +331,20 @@ void LibraryWindow::ParseStoreDataCallback(bool success, const std::string &file
} }
} }
static bool canValidateDialog = false; static bool canValidateDialog = true;
static int formatFilter = 0;
inline void InfosPane(const char *vFilter, IGFDUserDatas vUserDatas, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog inline void InfosPane(const char *vFilter, IGFDUserDatas vUserDatas, bool *vCantContinue) // if vCantContinue is false, the user cant validate the dialog
{ {
ImGui::TextColored(ImVec4(0, 1, 1, 1), "Infos Pane"); ImGui::TextColored(ImVec4(0, 1, 1, 1), "Infos Pane");
ImGui::Text("Selected Filter : %s", vFilter); ImGui::Text("Selected Filter : %s", vFilter);
ImGui::Checkbox("if not checked you cant validate the dialog", &canValidateDialog);
ImGui::Text("Select file format: ");
ImGui::RadioButton("Commercial stories", &formatFilter, 0); ImGui::SameLine();
ImGui::RadioButton("Studio format", &formatFilter, 1); ImGui::SameLine();
// ImGui::Checkbox("if not checked you cant validate the dialog", &canValidateDialog);
if (vCantContinue) if (vCantContinue)
*vCantContinue = canValidateDialog; *vCantContinue = canValidateDialog;
} }
@ -352,12 +360,19 @@ std::string LibraryWindow::ToLocalStoreFile(const std::string &url)
void LibraryWindow::Draw() void LibraryWindow::Draw()
{ {
static int importFormat = 0;
WindowBase::BeginDraw(); WindowBase::BeginDraw();
ImGui::SetWindowSize(ImVec2(626, 744), ImGuiCond_FirstUseEver); ImGui::SetWindowSize(ImVec2(626, 744), ImGuiCond_FirstUseEver);
if (ImGui::Button( ICON_MDI_FOLDER " Select directory")) if (ImGui::Button( ICON_MDI_FOLDER " Select directory"))
{ {
ImGuiFileDialog::Instance()->OpenDialog("ChooseLibraryDirDialog", "Choose a library directory", nullptr, ".", 1, nullptr, ImGuiFileDialogFlags_Modal); IGFD::FileDialogConfig config;
config.path = ".";
config.countSelectionMax = 1;
config.flags = ImGuiFileDialogFlags_Modal;
ImGuiFileDialog::Instance()->OpenDialog("ChooseLibraryDirDialog", "Choose a library directory", nullptr, config);
} }
if (!m_libraryManager.IsInitialized()) if (!m_libraryManager.IsInitialized())
@ -397,7 +412,17 @@ void LibraryWindow::Draw()
if (ImGui::Button("Import story")) if (ImGui::Button("Import story"))
{ {
ImGuiFileDialog::Instance()->OpenDialogWithPane("ImportStoryDlgKey", "Import story", "", "", InfosPane); IGFD::FileDialogConfig config;
config.path = m_libraryManager.LibraryPath();
config.countSelectionMax = 1;
config.sidePane = std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
config.sidePaneWidth = 350.0f;
config.flags = ImGuiFileDialogFlags_Modal;
ImGuiFileDialog::Instance()->OpenDialog("ImportStoryDlgKey",
"Import story",
".zip, .json",
config
);
} }
@ -407,22 +432,26 @@ void LibraryWindow::Draw()
ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("Actions", ImGuiTableColumnFlags_WidthFixed);
ImGui::TableHeadersRow(); ImGui::TableHeadersRow();
int internal_id = 1;
for (auto &p : m_libraryManager) for (auto &p : m_libraryManager)
{ {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("%s", p->GetName().c_str()); ImGui::Text("%s", p->GetName().c_str());
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::PushID(internal_id++);
if (ImGui::SmallButton("Load")) if (ImGui::SmallButton("Load"))
{ {
m_storyManager.OpenProject(p->GetUuid()); m_storyManager.OpenProject(p->GetUuid());
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::SmallButton("Remove")) if (ImGui::SmallButton("Remove"))
{ {
} }
ImGui::PopID();
} }
ImGui::EndTable(); ImGui::EndTable();
} }
@ -529,11 +558,8 @@ void LibraryWindow::Draw()
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName(); std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath(); std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath();
std::string filter = ImGuiFileDialog::Instance()->GetCurrentFilter(); std::string filter = ImGuiFileDialog::Instance()->GetCurrentFilter();
// here convert from string because a string was passed as a userDatas, but it can be what you want
// std::string userDatas; m_storyManager.ImportProject(filePathName, importFormat);
// if (ImGuiFileDialog::Instance()->GetUserDatas())
// userDatas = std::string((const char*)ImGuiFileDialog::Instance()->GetUserDatas());
// auto selection = ImGuiFileDialog::Instance()->GetSelection(); // multiselection
// action // action
} }

View file

@ -5,6 +5,9 @@
#include "media_converter.h" #include "media_converter.h"
#include "pack_archive.h"
#include "uuid.h"
#ifdef USE_WINDOWS_OS #ifdef USE_WINDOWS_OS
#include <winsock2.h> #include <winsock2.h>
#include <iphlpapi.h> #include <iphlpapi.h>
@ -338,7 +341,11 @@ void MainWindow::DrawMainMenuBar()
if (showNewProject) if (showNewProject)
{ {
ImGuiFileDialog::Instance()->OpenDialog("ChooseDirDialog", "Choose a parent directory for your project", nullptr, ".", 1, nullptr, ImGuiFileDialogFlags_Modal); IGFD::FileDialogConfig config;
config.path = ".";
config.countSelectionMax = 1;
config.flags = ImGuiFileDialogFlags_Modal;
ImGuiFileDialog::Instance()->OpenDialog("ChooseDirDialog", "Choose a parent directory for your project", nullptr, config);
} }
// Always center this window when appearing // Always center this window when appearing
@ -645,6 +652,125 @@ void MainWindow::OpenProject(const std::string &uuid)
RefreshProjectInformation(); RefreshProjectInformation();
} }
void MainWindow::ImportProject(const std::string &fileName, int format)
{
PackArchive archive;
Log("Decompressing " + fileName);
auto uuid = UUID().String();
std::string basePath = m_libraryManager.LibraryPath() + "/" + uuid;
archive.Unzip(fileName, basePath);
// Ok try to convert the archive into our format
try
{
// STUDIO format
std::ifstream f(basePath + "/story.json");
nlohmann::json j = nlohmann::json::parse(f);
StoryProject proj;
ResourceManager res;
nlohmann::json model;
if (j.contains("title"))
{
proj.New(uuid, m_libraryManager.LibraryPath());
proj.SetName(j["title"].get<std::string>());
// Create resources, scan asset files
std::filesystem::path directoryPath(basePath + "/assets");
if (std::filesystem::exists(directoryPath) && std::filesystem::is_directory(directoryPath))
{
for (const auto& entry : std::filesystem::directory_iterator(directoryPath))
{
if (std::filesystem::is_regular_file(entry.path()))
{
// Si c'est un sous-répertoire, récursivement scanner le contenu
std::string asset = entry.path().filename();
auto rData = std::make_shared<Resource>();
rData->type = ResourceManager::ExtentionInfo(entry.path().extension(), 1);
rData->format = ResourceManager::ExtentionInfo(entry.path().extension(), 0);
res.Add(rData);
}
}
}
// Create model
int ids = 1;
nlohmann::json nodes = nlohmann::json::array();
for (const auto & n : j["stageNodes"])
{
nlohmann::json node;
node["id"] = ids++;
node["uuid"] = n["uuid"].get<std::string>();
node["type"] = "media-node";
auto type = n["type"].get<std::string>();
int outPortCount = 1;
int inPortCount = 1;
if (type == "stage") {
outPortCount = 1;
inPortCount = 1;
}
node["outPortCount"] = outPortCount;
node["inPortCount"] = inPortCount;
node["position"] = n["position"];
nlohmann::json internalData;
auto img = n["image"];
internalData["image"] = img.is_string() ? img.get<std::string>() : "";
auto audio = n["audio"];
internalData["sound"] = audio.is_string() ? audio.get<std::string>() : "";
node["internal-data"] = internalData;
nodes.push_back(node);
}
model["nodes"] = nodes;
// Save links
nlohmann::json connections = nlohmann::json::array();
/*
for (const auto& linkInfo : m_links)
{
nlohmann::json c;
Connection cnx = LinkToModel(linkInfo->ed_link->InputId, linkInfo->ed_link->OutputId);
c["outNodeId"] = cnx.outNodeId;
c["outPortIndex"] = cnx.outPortIndex;
c["inNodeId"] = cnx.inNodeId;
c["inPortIndex"] = cnx.inPortIndex;
connections.push_back(c);
}*/
model["connections"] = connections;
}
// Save on disk
proj.Save(model, res);
}
catch(std::exception &e)
{
std::cout << e.what() << std::endl;
}
}
void MainWindow::RefreshProjectInformation() void MainWindow::RefreshProjectInformation()
{ {
std::string fullText = "Story Editor " + LibraryManager::GetVersion(); std::string fullText = "Story Editor " + LibraryManager::GetVersion();

View file

@ -123,6 +123,7 @@ private:
// From IStoryManager (proxy to StoryProject class) // From IStoryManager (proxy to StoryProject class)
virtual void OpenProject(const std::string &uuid) override; virtual void OpenProject(const std::string &uuid) override;
virtual void ImportProject(const std::string &fileName, int format);
virtual void Log(const std::string &txt, bool critical = false) override; virtual void Log(const std::string &txt, bool critical = false) override;
virtual void PlaySoundFile(const std::string &fileName) override;; virtual void PlaySoundFile(const std::string &fileName) override;;
virtual std::string BuildFullAssetsPath(const std::string &fileName) const override; virtual std::string BuildFullAssetsPath(const std::string &fileName) const override;

View file

@ -14,7 +14,7 @@
#undef QOI_NO_STDIO #undef QOI_NO_STDIO
#include "qoi.h" #include "qoi.h"
//#define DR_MP3_IMPLEMENTATION #define DR_MP3_IMPLEMENTATION
#include "dr_mp3.h" #include "dr_mp3.h"
MediaConverter::MediaConverter() MediaConverter::MediaConverter()

7736
story-editor/src/miniz.c Normal file

File diff suppressed because it is too large Load diff

1350
story-editor/src/miniz.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -65,7 +65,7 @@ void NodeEditorWindow::LoadNode(const nlohmann::json &nodeJson)
n->SetId(restoredNodeId); n->SetId(restoredNodeId);
nlohmann::json posJson = nodeJson["position"]; nlohmann::json posJson = nodeJson["position"];
n->SetOutputs(nodeJson["outPortCount"].get<int>()); n->SetOutputs(nodeJson["outPortCount"].get<int>());
n->SetPosition(posJson["x"].get<float>(), posJson["y"].get<float>()); n->SetPosition(posJson["x"].get<double>(), posJson["y"].get<double>());
n->FromJson(internalDataJson); n->FromJson(internalDataJson);
m_ids.insert(restoredNodeId); m_ids.insert(restoredNodeId);
@ -139,29 +139,43 @@ ed::PinId NodeEditorWindow::GetOutputPin(unsigned long modelNodeId, int pinIndex
void NodeEditorWindow::Load(const nlohmann::json &model) void NodeEditorWindow::Load(const nlohmann::json &model)
{ {
nlohmann::json nodesJsonArray = model["nodes"]; try {
BaseNode::InitId();
m_nodes.clear();
m_links.clear();
for (auto& element : nodesJsonArray) { nlohmann::json nodesJsonArray = model["nodes"];
LoadNode(element);
BaseNode::InitId();
m_nodes.clear();
m_links.clear();
for (auto& element : nodesJsonArray) {
LoadNode(element);
}
std::cout << model.dump(4) << std::endl;
// Ici on reste flexible sur les connexions, cela permet de créer éventuellement des
// projets sans fils (bon, l'élément devrait quand même exister dans le JSON)
if (model.contains("connections"))
{
nlohmann::json connectionJsonArray = model["connections"];
for (auto& connection : connectionJsonArray)
{
Connection model = connection.get<Connection>();
CreateLink(model,
GetInputPin(model.inNodeId, model.inPortIndex),
GetOutputPin(model.outNodeId, model.outPortIndex));
}
}
m_loaded = true;
} }
catch(std::exception &e)
std::cout << model.dump(4) << std::endl;
nlohmann::json connectionJsonArray = model["connections"];
for (auto& connection : connectionJsonArray)
{ {
Connection model = connection.get<Connection>(); std::cout << e.what() << std::endl;
CreateLink(model,
GetInputPin(model.inNodeId, model.inPortIndex),
GetOutputPin(model.outNodeId, model.outPortIndex));
} }
m_loaded = true;
} }

View file

@ -28,7 +28,13 @@ void ResourcesWindow::ChooseFile()
{ {
m_showImportDialog = false; m_showImportDialog = false;
// open Dialog Simple // open Dialog Simple
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", m_soundFile ? soundFormats : imagesFormats, ".", 1, nullptr, ImGuiFileDialogFlags_Modal); IGFD::FileDialogConfig config;
config.path = ".";
config.countSelectionMax = 1;
config.sidePaneWidth = 350.0f;
config.flags = ImGuiFileDialogFlags_Modal;
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", m_soundFile ? soundFormats : imagesFormats, config);
} }
// display // display

274
story-editor/src/zip.cpp Normal file
View file

@ -0,0 +1,274 @@
/**
* MIT License
* Copyright (c) 2019 Anthony Rabine
*/
#include "zip.h"
#include <fstream>
#include <cstring>
#include <functional>
#include <filesystem>
#include <memory>
static bool ensure_file_exists_and_is_readable(const char *pFilename)
{
FILE *p = nullptr;
#ifdef Q_OS_WIN
fopen_s(& p, pFilename, "rb");
#else
p = fopen(pFilename, "rb");
#endif
if (!p) {
return false;
}
fseek(p, 0, SEEK_END);
long int src_file_size = ftell(p);
fseek(p, 0, SEEK_SET);
if (src_file_size)
{
char buf[1];
if (fread(buf, 1, 1, p) != 1)
{
fclose(p);
return false;
}
}
fclose(p);
return true;
}
/*****************************************************************************/
Zip::Zip()
: mIsValid(false)
, mNumberOfEntries(0U)
{
std::memset(&mZipArchive, 0, sizeof(mZipArchive));
}
/*****************************************************************************/
Zip::~Zip()
{
Close();
}
/*****************************************************************************/
void Zip::CreateInMemory(const std::string &fileName)
{
mz_bool status = MZ_FALSE;
std::memset(&mZipArchive, 0, sizeof(mZipArchive));
status = mz_zip_writer_init_file(&mZipArchive, fileName.c_str(), 65537);
if (status)
{
mIsOpenForWriting = true;
mIsValid = true;
mIsOpen = true;
}
else
{
mIsOpenForWriting = false;
mIsValid = false;
}
}
/*****************************************************************************/
bool Zip::AddFile(const std::string &fileName, const std::string &archiveName)
{
mz_bool status = MZ_FALSE;
if (ensure_file_exists_and_is_readable(fileName.c_str()))
{
status = mz_zip_writer_add_file(&mZipArchive, archiveName.c_str(), fileName.c_str(), NULL, 0, MZ_NO_COMPRESSION);
}
return status == MZ_TRUE;
}
/*****************************************************************************/
void Zip::AddDirectory(const std::string &dirName)
{
std::string d = dirName + "/";
mz_zip_writer_add_mem(&mZipArchive, d.c_str(), NULL, 0, MZ_NO_COMPRESSION);
}
/*****************************************************************************/
std::vector<std::string> Zip::Unzip(std::string const &zipFile, const std::string &destination_dir, std::string const &password)
{
(void)password;
std::vector<std::string> files = {};
mz_zip_archive zip_archive;
memset(&zip_archive, 0, sizeof(zip_archive));
auto status = mz_zip_reader_init_file(&zip_archive, zipFile.c_str(), 0);
if (!status) return files;
int fileCount = (int)mz_zip_reader_get_num_files(&zip_archive);
if (fileCount == 0)
{
mz_zip_reader_end(&zip_archive);
return files;
}
mz_zip_archive_file_stat file_stat;
if (!mz_zip_reader_file_stat(&zip_archive, 0, &file_stat))
{
mz_zip_reader_end(&zip_archive);
return files;
}
// Get root folder
// std::string baseName = std::filesystem::path(file_stat.m_filename).parent_path().string();
// Get and print information about each file in the archive.
for (int i = 0; i < fileCount; i++)
{
if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) continue;
if (mz_zip_reader_is_file_a_directory(&zip_archive, i)) continue; // skip directories for now
std::string fileName = file_stat.m_filename; // make path relative
std::string destFile = destination_dir + std::filesystem::path::preferred_separator + fileName; // make full dest path
// creates the directory where the file will be decompressed
std::filesystem::create_directories(std::filesystem::path(destFile).parent_path());
// Extract file
if (mz_zip_reader_extract_to_file(&zip_archive, i, destFile.data(), 0))
{
files.emplace_back(fileName);
}
}
// Close the archive, freeing any resources it was using
mz_zip_reader_end(&zip_archive);
return files;
}
/*****************************************************************************/
bool Zip::Open(const std::string &zip, bool isFile)
{
mz_bool status;
mIsValid = false;
mIsOpenForWriting = true;
mNumberOfEntries = 0U;
std::memset(&mZipArchive, 0, sizeof(mZipArchive));
if (isFile)
{
// Physical file on disk
status = mz_zip_reader_init_file(&mZipArchive, zip.c_str(), 0);
}
else
{
// Zipped memory
status = mz_zip_reader_init_mem(&mZipArchive, zip.c_str(), zip.size(), 0);
}
if (status)
{
mFiles.clear();
// Get and print information about each file in the archive.
for (std::uint32_t i = 0; i < mz_zip_reader_get_num_files(&mZipArchive); i++)
{
mz_zip_archive_file_stat file_stat;
if (mz_zip_reader_file_stat(&mZipArchive, i, &file_stat))
{
mNumberOfEntries++;
if (file_stat.m_is_directory == MZ_FALSE)
{
mFiles.push_back(file_stat.m_filename);
//printf("Filename: \"%s\", Comment: \"%s\", Uncompressed size: %u, Compressed size: %u\n", file_stat.m_filename, file_stat.m_comment, (std::uint32_t)file_stat.m_uncomp_size, (std::uint32_t)file_stat.m_comp_size);
}
}
}
}
if (mNumberOfEntries == mz_zip_reader_get_num_files(&mZipArchive))
{
mIsValid = true;
mIsOpen = true;
}
return mIsValid;
}
/*****************************************************************************/
void Zip::Close()
{
if (mIsValid)
{
if (mIsOpenForWriting)
{
if (!mz_zip_writer_finalize_archive(&mZipArchive))
{
mz_zip_writer_end(&mZipArchive);
}
}
else
{
mz_zip_reader_end(&mZipArchive);
}
}
mIsOpen = false;
}
/*****************************************************************************/
struct UserData
{
char *output;
int offset;
};
static mz_bool DeflateCallback(const void *pBuf, int len, void *pUser)
{
UserData *ud = static_cast<UserData*>(pUser);
std::memcpy(ud->output + ud->offset, pBuf, len);
ud->offset += len;
(void) len;
(void) pUser;
return MZ_TRUE;
}
/*****************************************************************************/
int Zip::CompressBuffer(const char *input, size_t input_size, char *output)
{
int finalsize = -1;
tdefl_compressor Comp;
UserData ud;
ud.offset = 0U;
ud.output = output;
if (tdefl_init(&Comp, DeflateCallback, &ud, 0) == TDEFL_STATUS_OKAY)
{
if(tdefl_compress_buffer(&Comp, input, input_size, TDEFL_FINISH) == TDEFL_STATUS_DONE)
{
finalsize = ud.offset;
}
}
return finalsize;
}
/*****************************************************************************/
bool Zip::GetFile(const std::string &fileName, std::string &contents)
{
bool ret = false;
if (mIsValid)
{
size_t size;
char *p = reinterpret_cast<char *>(mz_zip_reader_extract_file_to_heap(&mZipArchive, fileName.c_str(), &size, 0));
if (p != nullptr)
{
contents.assign(p, size);
free(p);
ret = true;
}
}
return ret;
}
/*****************************************************************************/
std::vector<std::string> Zip::ListFiles()
{
return mFiles;
}
//=============================================================================
// End of file Zip.cpp
//=============================================================================

52
story-editor/src/zip.h Normal file
View file

@ -0,0 +1,52 @@
/**
* MIT License
* Copyright (c) 2019 Anthony Rabine
*/
#ifndef ZIP_H
#define ZIP_H
#include <cstdint>
#include <string>
#include <vector>
#include "miniz.h"
/*****************************************************************************/
class Zip
{
public:
Zip();
~Zip();
bool Open(const std::string &zip, bool isFile);
std::uint32_t NumberOfEntries() { return mNumberOfEntries; }
bool GetFile(const std::string &fileName, std::string &contents);
std::vector<std::string> ListFiles();
void Close();
bool IsOpen() const { return mIsOpen; }
bool isOpenForWriting() const { return mIsOpenForWriting; }
static int CompressBuffer(const char *input, size_t input_size, char *output);
void CreateInMemory(const std::string &fileName);
bool AddFile(const std::string &fileName, const std::string &archiveName);
void AddDirectory(const std::string &dirName);
static std::vector<std::string> Unzip(const std::string &zipFile, const std::string &destination_dir, const std::string &password);
private:
mz_zip_archive mZipArchive;
bool mIsValid;
bool mIsOpen{false};
bool mIsOpenForWriting{false};
std::uint32_t mNumberOfEntries;
std::vector<std::string> mFiles;
};
#endif // ZIP_H
//=============================================================================
// End of file Zip.cpp
//=============================================================================