mirror of
https://github.com/arabine/open-story-teller.git
synced 2025-12-06 17:09:06 +01:00
(wip) Studio format import, (wip) ogg decoder files, (ipgrade) imguifiledialog
This commit is contained in:
parent
09ad937f1e
commit
83a6a3bc15
30 changed files with 29771 additions and 13203 deletions
File diff suppressed because it is too large
Load diff
496
software/library/miniaudio_libopus.h
Normal file
496
software/library/miniaudio_libopus.h
Normal 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
|
||||
516
software/library/miniaudio_libvorbis.h
Normal file
516
software/library/miniaudio_libvorbis.h
Normal 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
|
||||
34
software/library/resource_manager.cpp
Normal file
34
software/library/resource_manager.cpp
Normal 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";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
#include <ranges>
|
||||
|
||||
#include "resource.h"
|
||||
|
|
@ -13,6 +14,7 @@ class ResourceManager
|
|||
{
|
||||
public:
|
||||
|
||||
|
||||
ResourceManager()
|
||||
: m_images(filter("image"))
|
||||
, m_sounds(filter("sound"))
|
||||
|
|
@ -20,6 +22,8 @@ public:
|
|||
|
||||
}
|
||||
|
||||
static std::string ExtentionInfo(std::string extension, int info_type);
|
||||
|
||||
~ResourceManager() {
|
||||
|
||||
}
|
||||
|
|
|
|||
5584
software/library/stb_vorbis.c
Normal file
5584
software/library/stb_vorbis.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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(), ' ', '_');
|
||||
|
||||
return valid_file;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -162,6 +162,14 @@ set(SRCS
|
|||
|
||||
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.h
|
||||
libs/imgui-node-editor/imgui_node_editor.cpp
|
||||
|
|
@ -188,7 +196,10 @@ set(SRCS
|
|||
../software/library/miniaudio.h
|
||||
../software/library/uuid.h
|
||||
../software/library/resource.h
|
||||
|
||||
../software/library/resource_manager.h
|
||||
../software/library/resource_manager.cpp
|
||||
|
||||
../software/library/story_project.cpp
|
||||
../software/library/story_project.h
|
||||
../software/library/thread_safe_queue.h
|
||||
|
|
@ -225,6 +236,9 @@ target_include_directories(${STORY_EDITOR_PROJECT} PUBLIC
|
|||
|
||||
${curl_INCLUDE_DIR}
|
||||
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_SOURCE_DIR}/src/importers
|
||||
|
||||
../software/library/
|
||||
../software/chip32/
|
||||
../software/common
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -15,7 +15,7 @@
|
|||
/*
|
||||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -42,8 +42,7 @@ SOFTWARE.
|
|||
|
||||
/*
|
||||
// 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
|
||||
|
||||
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
|
||||
|
||||
|
|
@ -126,28 +125,27 @@ included in the Lib_Only branch for your convenience.
|
|||
- 0 => Infinite
|
||||
- 1 => One file (default)
|
||||
- n => n files
|
||||
- Compatible with MacOs, Linux, Windows
|
||||
- Windows version can list drives
|
||||
- Compatible with MacOs, Linux, Windows, Emscripten, Android
|
||||
- Supports modal or standard dialog types
|
||||
- Select files or directories
|
||||
- Filter groups and custom filter names
|
||||
- can ignore filter Case for file searching
|
||||
- Keyboard navigation (arrows, backspace, enter)
|
||||
- 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)
|
||||
- Optional 'Confirm to Overwrite" dialog if file exists
|
||||
- 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
|
||||
- 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)
|
||||
- regex support for filters, collection of filters and filestyle (the regex is recognized when between (( and )) in a
|
||||
filter)
|
||||
- regex support for filters, collection of filters and filestyle (the regex is recognized when between (( and )) in a filter)
|
||||
- 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
|
||||
regex is used)
|
||||
- result modes GetFilePathName, GetFileName and GetSelection (overwrite file ext, keep file, add ext if no user ext
|
||||
exist)
|
||||
- advanced behavior regarding asterisk based filter. like : .* .*.* .vcx.* .*.filters .vcs*.filt.* etc.. (internally regex is used)
|
||||
- result modes GetFilePathName, GetFileName and GetSelection (overwrite file ext, keep file, add ext if no user ext exist)
|
||||
- you can use your own FileSystem Api
|
||||
- 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
|
||||
|
|
@ -159,8 +157,8 @@ A filter is recognized only if he respects theses rules :
|
|||
1) a regex must be in (( and ))
|
||||
2) a , will separate filters except if between a ( and )
|
||||
3) name{filter1, filter2} is a special form for collection filters
|
||||
3.1) the name can be composed of what you want except { and }
|
||||
3.2) a filter can be a regex
|
||||
- the name can be composed of what you want except { and }
|
||||
- a filter can be a regex
|
||||
4) the filters cannot integrate these chars '(' ')' '{' '}' ' ' except for a regex with respect to rule 1)
|
||||
5) the filters cannot integrate a ','
|
||||
|
||||
|
|
@ -197,7 +195,9 @@ void drawGui()
|
|||
{
|
||||
// open Dialog Simple
|
||||
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
|
||||
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey"))
|
||||
|
|
@ -231,8 +231,11 @@ ImGuiFileDialogFlags_Modal
|
|||
you can use it like that :
|
||||
|
||||
```cpp
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp",
|
||||
".", 1, nullptr, ImGuiFileDialogFlags_Modal);
|
||||
IGFD::FileDialogConfig config;
|
||||
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:
|
||||
|
||||
```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.
|
||||
|
|
@ -286,10 +291,16 @@ the user cant validate the dialog
|
|||
void drawGui()
|
||||
{
|
||||
// open Dialog with Pane
|
||||
if (ImGui::Button("Open File Dialog with a custom pane"))
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp",
|
||||
".", "", std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 350, 1,
|
||||
UserDatas("InfosPane"));
|
||||
if (ImGui::Button("Open File Dialog with a custom pane")) {
|
||||
IGFD::FileDialogConfig config;
|
||||
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
|
||||
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey"))
|
||||
|
|
@ -380,16 +391,13 @@ samples :
|
|||
|
||||
```cpp
|
||||
// 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),
|
||||
ICON_IGFD_FILE_PIC, font1); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtention, ".gif",
|
||||
ImVec4(0.0f, 1.0f, 0.5f, 0.9f), "[GIF]");
|
||||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtention, ".png", ImVec4(0.0f, 1.0f, 1.0f, 0.9f),,ICON_IGFD_FILE_PIC, font1);
|
||||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtention, ".gif", ImVec4(0.0f, 1.0f, 0.5f, 0.9f), "[GIF]");
|
||||
|
||||
// define style for all directories
|
||||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir, "", ImVec4(0.5f, 1.0f, 0.9f, 0.9f),
|
||||
ICON_IGFD_FOLDER);
|
||||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir, "", ImVec4(0.5f, 1.0f, 0.9f, 0.9f), ICON_IGFD_FOLDER);
|
||||
// can be for a specific directory
|
||||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir, ".git", ImVec4(0.5f, 1.0f, 0.9f, 0.9f),
|
||||
ICON_IGFD_FOLDER);
|
||||
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir, ".git", ImVec4(0.5f, 1.0f, 0.9f, 0.9f), ICON_IGFD_FOLDER);
|
||||
|
||||
// define style for all files
|
||||
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 :
|
||||
|
||||
```cpp
|
||||
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, ".");
|
||||
const char *filters = "Source files (*.cpp *.h *.hpp){.cpp,.h,.hpp},Image files (*.png *.gif *.jpg *.jpeg){.png,.gif,.jpg,.jpeg},.md";
|
||||
IGFD::FileDialogConfig config;
|
||||
config.path = ".";
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", ICON_IMFDLG_FOLDER_OPEN " Choose a File", filters, config);
|
||||
```
|
||||
|
||||
|
||||
will produce :
|
||||

|
||||
|
||||
|
|
@ -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.
|
||||
|
||||
```cpp
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".*,.cpp,.h,.hpp", ".");
|
||||
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 many File", ".*,.cpp,.h,.hpp", ".", 0);
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".png,.jpg",
|
||||
".", "", std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 350, 1,
|
||||
"SaveFile"); // 1 file
|
||||
IGFD::FileDialogConfig config; config.path = ".";
|
||||
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".*,.cpp,.h,.hpp", config);
|
||||
|
||||
config.countSelectionMax = 1;
|
||||
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
|
||||
```
|
||||
|
||||

|
||||
|
|
@ -540,47 +561,6 @@ As you see the current item is flashed by default for 1 second. You can define t
|
|||
```cpp
|
||||
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
|
||||
|
||||

|
||||
|
||||
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 :
|
||||
################################################################
|
||||
|
|
@ -607,17 +587,20 @@ behavior. (by design! :) )
|
|||
Example code For Standard Dialog :
|
||||
|
||||
```cpp
|
||||
IGFD::FileDialogConfig config;
|
||||
config.path = ".";
|
||||
config.flags = ImGuiFileDialogFlags_ConfirmOverwrite;
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey",
|
||||
ICON_IGFD_SAVE " Choose a File", filters,
|
||||
".", "", 1, nullptr, ImGuiFileDialogFlags_ConfirmOverwrite);
|
||||
ICON_IGFD_SAVE " Choose a File", filters, config);
|
||||
```
|
||||
|
||||
Example code For Modal Dialog :
|
||||
|
||||
```cpp
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey",
|
||||
ICON_IGFD_SAVE " Choose a File", filters,
|
||||
".", "", 1, nullptr, ImGuiFileDialogFlags_Modal | ImGuiFileDialogFlags_ConfirmOverwrite);
|
||||
IGFD::FileDialogConfig config;
|
||||
config.path = ".";
|
||||
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()`.
|
||||
|
|
@ -768,16 +751,19 @@ ex :
|
|||
```cpp
|
||||
ImGuiFileDialog fileDialog;
|
||||
|
||||
// open dialog; in this case, Bookmark, directory creation are disabled with, and also the file input field is readonly.
|
||||
// btw you can do what you want
|
||||
fileDialog.OpenDialog("embedded", "Select File", ".*", "", -1, nullptr,
|
||||
ImGuiFileDialogFlags_NoDialog |
|
||||
ImGuiFileDialogFlags_DisableBookmarkMode |
|
||||
// open dialog; in this case, Place, directory creation are disabled with, and also the file input field is readonly.
|
||||
// btw you can od what you want
|
||||
IGFD::FileDialogConfig config;
|
||||
config.path = ".";
|
||||
config.countSelectionMax = -1;
|
||||
config.flags = ImGuiFileDialogFlags_NoDialog |
|
||||
ImGuiFileDialogFlags_DisablePlaceMode |
|
||||
ImGuiFileDialogFlags_DisableCreateDirectoryButton |
|
||||
ImGuiFileDialogFlags_ReadOnlyFileNameField);
|
||||
fileDialog.OpenDialog("embedded", "Select File", ".*", config);
|
||||
// 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 fileDialog.Display("embedded", ImGuiWindowFlags_NoCollapse, ImVec2(0,0), ImVec2(0,350)))
|
||||
// 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)))
|
||||
```
|
||||
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
|
||||
|
||||
################################################################
|
||||
## 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
|
||||
################################################################
|
||||
|
|
@ -1041,20 +1143,15 @@ Sample code with cimgui :
|
|||
ImGuiFileDialog *cfileDialog = IGFD_Create();
|
||||
|
||||
// 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,
|
||||
"filedlg", // dialog key (make it possible to have different treatment reagrding
|
||||
the dialog key "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..
|
||||
".", // 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
|
||||
"filedlg", // dialog key (make it possible to have different treatment reagrding the dialog key
|
||||
"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..
|
||||
config); // the file dialog config
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// compatible with 1.90.1 WIP
|
||||
// compatible with 1.90.4 WIP
|
||||
#define IMGUIFILEDIALOG_VERSION "v0.6.7"
|
||||
|
||||
#pragma endregion
|
||||
|
|
@ -1169,7 +1266,7 @@ enum ImGuiFileDialogFlags_ {
|
|||
ImGuiFileDialogFlags_CaseInsensitiveExtention = (1 << 8), // the file extentions treatments will not take into account the case
|
||||
ImGuiFileDialogFlags_Modal = (1 << 9), // modal
|
||||
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
|
||||
|
||||
// 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 <array>
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <thread>
|
||||
#include <cfloat>
|
||||
#include <memory>
|
||||
|
|
@ -1429,10 +1527,14 @@ public:
|
|||
static void SetBuffer(char* vBuffer, size_t vBufferLen, const std::string& vStr);
|
||||
static std::string UTF8Encode(const std::wstring& wstr);
|
||||
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::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 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
|
||||
|
|
@ -1598,11 +1700,18 @@ public:
|
|||
// 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_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
|
||||
FileType fileType; // fileType
|
||||
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 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
|
||||
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)
|
||||
|
|
@ -1626,8 +1735,11 @@ public:
|
|||
|
||||
#pragma region FILE SYSTEM INTERFACE
|
||||
|
||||
typedef std::pair<std::string, std::string> PathDisplayedName;
|
||||
|
||||
class IFileSystem {
|
||||
public:
|
||||
virtual ~IFileSystem() = default;
|
||||
// say if a directory can be openened or for any reason locked
|
||||
virtual bool IsDirectoryCanBeOpened(const std::string& vName) = 0;
|
||||
// say if a directory exist
|
||||
|
|
@ -1636,14 +1748,14 @@ public:
|
|||
virtual bool IsFileExist(const std::string& vName) = 0;
|
||||
// say if a directory was created, return false if vName is invalid or alreayd exist
|
||||
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;
|
||||
// will return a list of files inside a path
|
||||
virtual std::vector<IGFD::FileInfos> ScanDirectory(const std::string& vPath) = 0;
|
||||
// say if the path is well a directory
|
||||
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
|
||||
virtual std::vector<std::string> GetDrivesList() = 0;
|
||||
// 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<IGFD::PathDisplayedName> GetDevicesList() = 0;
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
|
@ -1676,6 +1788,7 @@ private:
|
|||
std::string m_LastSelectedFileName; // for shift multi selection
|
||||
std::set<std::string> m_SelectedFileNames; // the user selection of FilePathNames
|
||||
bool m_CreateDirectoryMode = false; // for create directory widget
|
||||
std::string m_FileSystemName;
|
||||
std::unique_ptr<IFileSystem> m_FileSystemPtr = nullptr;
|
||||
|
||||
public:
|
||||
|
|
@ -1699,7 +1812,6 @@ public:
|
|||
defaultSortOrderFilename, defaultSortOrderType, defaultSortOrderSize, defaultSortOrderDate};
|
||||
#endif
|
||||
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 dLGDefaultFileName; // base default file path name set by user when OpenDialog was called
|
||||
|
|
@ -1713,9 +1825,7 @@ public:
|
|||
#else
|
||||
private:
|
||||
#endif
|
||||
static std::string m_RoundNumber(double vvalue, int n); // custom rounding number
|
||||
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)
|
||||
static void m_CompleteFileInfos(const std::shared_ptr<FileInfos>& vInfos); // 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_m_AddFileNameInSelection(const std::string& vFileName, bool vSetLastSelectionFileName); // selection : add a file name
|
||||
void m_AddFile(const FileDialogInternal& vFileDialogInternal,
|
||||
|
|
@ -1737,6 +1847,7 @@ private:
|
|||
void m_SortFields(const FileDialogInternal& vFileDialogInternal,
|
||||
std::vector<std::shared_ptr<FileInfos>>& vFileInfosList,
|
||||
std::vector<std::shared_ptr<FileInfos>>& vFileInfosFilteredList); // will sort a column
|
||||
bool m_CompleteFileInfosWithUserFileAttirbutes(const FileDialogInternal& vFileDialogInternal, const std::shared_ptr<FileInfos>& vInfos);
|
||||
|
||||
public:
|
||||
FileManager();
|
||||
|
|
@ -1788,6 +1899,9 @@ public:
|
|||
IFileSystem* GetFileSystemInstance() {
|
||||
return m_FileSystemPtr.get();
|
||||
}
|
||||
const std::string& GetFileSystemName() {
|
||||
return m_FileSystemName;
|
||||
}
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
|
@ -1796,6 +1910,20 @@ public:
|
|||
|
||||
typedef void* UserDatas;
|
||||
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 {
|
||||
public:
|
||||
FileManager fileManager; // the file manager
|
||||
|
|
@ -1815,20 +1943,27 @@ public:
|
|||
bool fileListViewIsActive = false; // when list view is active
|
||||
std::string dLGkey; // the dialog key
|
||||
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 puUseCustomLocale = false; // custom user locale
|
||||
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 localeEnd; // the locale who will be applaied at end of the display dialog
|
||||
|
||||
private:
|
||||
FileDialogConfig m_DialogConfig;
|
||||
|
||||
public:
|
||||
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 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
|
||||
|
|
@ -1862,6 +1997,7 @@ private:
|
|||
std::shared_ptr<std::thread> m_ThumbnailGenerationThread = nullptr;
|
||||
std::list<std::shared_ptr<FileInfos>> m_ThumbnailFileDatasToGet; // base container
|
||||
std::mutex m_ThumbnailFileDatasToGetMutex;
|
||||
std::condition_variable m_ThumbnailFileDatasToGetCv;
|
||||
std::list<std::shared_ptr<FileInfos>> m_ThumbnailToCreate; // base container
|
||||
std::mutex m_ThumbnailToCreateMutex;
|
||||
std::list<IGFD_Thumbnail_Info> m_ThumbnailToDestroy; // base container
|
||||
|
|
@ -1899,48 +2035,70 @@ public:
|
|||
|
||||
#pragma endregion
|
||||
|
||||
#pragma region BookMarkFeature
|
||||
#pragma region PlacesFeature
|
||||
|
||||
class IGFD_API BookMarkFeature {
|
||||
class IGFD_API PlacesFeature {
|
||||
protected:
|
||||
BookMarkFeature();
|
||||
PlacesFeature();
|
||||
|
||||
#ifdef USE_BOOKMARK
|
||||
#ifdef USE_PLACES_FEATURE
|
||||
private:
|
||||
struct BookmarkStruct {
|
||||
std::string name; // name of the bookmark
|
||||
struct PlaceStruct {
|
||||
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
|
||||
std::string path; // absolute path of the bookmarked directory
|
||||
|
||||
bool defined_by_code = false; // defined by code, can be used for rpevent serialization / deserialization
|
||||
struct GroupStruct {
|
||||
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 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:
|
||||
ImGuiListClipper m_BookmarkClipper;
|
||||
std::vector<BookmarkStruct> m_Bookmarks;
|
||||
char m_BookmarkEditBuffer[MAX_FILE_DIALOG_NAME_BUFFER] = "";
|
||||
std::unordered_map<std::string, std::shared_ptr<GroupStruct>> m_Groups;
|
||||
std::map<size_t, std::weak_ptr<GroupStruct> > m_OrderedGroups;
|
||||
|
||||
protected:
|
||||
float m_BookmarkWidth = 200.0f;
|
||||
bool m_BookmarkPaneShown = false;
|
||||
float m_PlacesPaneWidth = 200.0f;
|
||||
bool m_PlacesPaneShown = false;
|
||||
|
||||
protected:
|
||||
void m_DrawBookmarkButton(); // draw bookmark button
|
||||
bool m_DrawBookmarkPane(FileDialogInternal& vFileDialogInternal, const ImVec2& vSize); // draw bookmark Pane
|
||||
void m_InitPlaces(FileDialogInternal& vFileDialogInternal);
|
||||
void m_DrawPlacesButton(); // draw place button
|
||||
bool m_DrawPlacesPane(FileDialogInternal& vFileDialogInternal, const ImVec2& vSize); // draw place Pane
|
||||
|
||||
public:
|
||||
std::string SerializeBookmarks( // serialize bookmarks : return bookmark buffer to save in a file
|
||||
const bool& vDontSerializeCodeBasedBookmarks = true); // for avoid serialization of bookmarks added by code
|
||||
void DeserializeBookmarks( // deserialize bookmarks : load bookmark buffer to load in the dialog (saved from
|
||||
const std::string& vBookmarks); // previous use with SerializeBookmarks()) bookmark buffer to load
|
||||
void AddBookmark( // add a bookmark by code
|
||||
const std::string& vBookMarkName, // bookmark name
|
||||
const std::string& vBookMarkPath); // bookmark path
|
||||
bool RemoveBookmark( // remove a bookmark by code, return true if succeed
|
||||
const std::string& vBookMarkName); // bookmark name to remove
|
||||
|
||||
#endif // USE_BOOKMARK
|
||||
std::string SerializePlaces( // serialize place : return place buffer to save in a file
|
||||
const bool& vForceSerialisationForAll = true); // for avoid serialization of places with flag 'canBeSaved to false'
|
||||
void DeserializePlaces( // deserialize place : load place buffer to load in the dialog (saved from
|
||||
const std::string& vPlaces); // previous use with SerializePlaces()) place buffer to load
|
||||
bool AddPlacesGroup( // add a group
|
||||
const std::string& vGroupName, // the group name
|
||||
const size_t& vDisplayOrder, // the display roder of the group
|
||||
const bool& vCanBeEdited = false, // let the user add/remove place in the group
|
||||
const bool& vOpenedByDefault = true); // hte group is opened by default
|
||||
bool RemovePlacesGroup(const std::string& vGroupName); // remove the group
|
||||
GroupStruct* GetPlacesGroupPtr(const std::string& vGroupName); // get the group, if not existed, will be created
|
||||
#endif // USE_PLACES_FEATURE
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
|
@ -1989,7 +2147,7 @@ public:
|
|||
|
||||
#pragma region FileDialog
|
||||
|
||||
class IGFD_API FileDialog : public BookMarkFeature, public KeyExplorerFeature, public ThumbnailFeature {
|
||||
class IGFD_API FileDialog : public PlacesFeature, public KeyExplorerFeature, public ThumbnailFeature {
|
||||
protected:
|
||||
FileDialogInternal m_FileDialogInternal;
|
||||
ImGuiListClipper m_FileListClipper;
|
||||
|
|
@ -2012,55 +2170,15 @@ public:
|
|||
}
|
||||
|
||||
public:
|
||||
FileDialog(); // ImGuiFileDialog Constructor. can be used for have many dialog at same time (not possible with
|
||||
// singleton)
|
||||
FileDialog(); // ImGuiFileDialog Constructor. can be used for have many dialog at same time (not possible with singleton)
|
||||
virtual ~FileDialog(); // ImGuiFileDialog Destructor
|
||||
|
||||
virtual // todo : need to refactor all theses function to maybe just one
|
||||
// standard dialog
|
||||
void
|
||||
OpenDialog( // open simple dialog (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 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
|
||||
// standard dialog
|
||||
virtual void OpenDialog( // 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
|
||||
|
||||
// Display / Close dialog form
|
||||
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)
|
||||
|
||||
// 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)
|
||||
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)
|
||||
|
||||
// widgets components
|
||||
|
|
@ -2158,6 +2276,8 @@ protected:
|
|||
std::string& vOutStr,
|
||||
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_DisplayFileInfosTooltip(const int32_t& vRowIdx, const int32_t& vColumnIdx, std::shared_ptr<FileInfos> vFileInfos);
|
||||
};
|
||||
|
||||
#pragma endregion
|
||||
|
|
@ -2198,6 +2318,20 @@ typedef struct IGFD_Selection_Pair IGFD_Selection_Pair;
|
|||
typedef struct IGFD_Selection IGFD_Selection;
|
||||
#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 {
|
||||
char* fileName;
|
||||
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 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
|
||||
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
|
||||
#endif // USE_THUMBNAILS
|
||||
|
||||
IGFD_C_API void IGFD_OpenDialog( // 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* vPath, // path
|
||||
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 void IGFD_OpenDialog( // 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 IGFD_FileDialog_Config vConfig); // config
|
||||
|
||||
IGFD_C_API bool IGFD_DisplayDialog( // Display the dialog
|
||||
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
|
||||
#endif
|
||||
|
||||
#ifdef USE_BOOKMARK
|
||||
IGFD_C_API char* IGFD_SerializeBookmarks( // serialize bookmarks : return bookmark buffer to save in a file, WARNINGS
|
||||
#ifdef USE_PLACES_FEATURE
|
||||
IGFD_C_API char* IGFD_SerializePlaces( // serialize place : return place buffer to save in a file, WARNINGS
|
||||
// you are responsible to free it
|
||||
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
|
||||
// from previous use with SerializeBookmarks())
|
||||
IGFD_C_API void IGFD_DeserializePlaces( // deserialize place : load bookmar buffer to load in the dialog (saved
|
||||
// from previous use with SerializePlaces())
|
||||
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
|
||||
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
|
||||
IGFD_C_API bool IGFD_AddPlacesGroup( // add a places group by code
|
||||
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
|
||||
|
||||
#ifdef USE_THUMBNAILS
|
||||
|
|
|
|||
|
|
@ -9,127 +9,143 @@
|
|||
// this options need c++17
|
||||
// #define USE_STD_FILESYSTEM
|
||||
|
||||
//#define MAX_FILE_DIALOG_NAME_BUFFER 1024
|
||||
//#define MAX_PATH_BUFFER_SIZE 1024
|
||||
// #define MAX_FILE_DIALOG_NAME_BUFFER 1024
|
||||
// #define MAX_PATH_BUFFER_SIZE 1024
|
||||
|
||||
// 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.
|
||||
// if disabled the spacing is defined by the imgui theme
|
||||
// define the space between path buttons
|
||||
//#define CUSTOM_PATH_SPACING 2
|
||||
// #define CUSTOM_PATH_SPACING 2
|
||||
|
||||
//#define USE_THUMBNAILS
|
||||
//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"
|
||||
//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_RESIZE_IMPLEMENTATION
|
||||
//#define IMGUI_RADIO_BUTTON RadioButton
|
||||
//#define DisplayMode_ThumbailsList_ImageHeight 32.0f
|
||||
//#define tableHeaderFileThumbnailsString "Thumbnails"
|
||||
//#define DisplayMode_FilesList_ButtonString "FL"
|
||||
//#define DisplayMode_FilesList_ButtonHelp "File List"
|
||||
//#define DisplayMode_ThumbailsList_ButtonString "TL"
|
||||
//#define DisplayMode_ThumbailsList_ButtonHelp "Thumbnails List"
|
||||
// #define USE_THUMBNAILS
|
||||
// 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"
|
||||
// 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_RESIZE_IMPLEMENTATION
|
||||
// #define IMGUI_RADIO_BUTTON RadioButton
|
||||
// #define DisplayMode_ThumbailsList_ImageHeight 32.0f
|
||||
// #define tableHeaderFileThumbnailsString "Thumbnails"
|
||||
// #define DisplayMode_FilesList_ButtonString "FL"
|
||||
// #define DisplayMode_FilesList_ButtonHelp "File List"
|
||||
// #define DisplayMode_ThumbailsList_ButtonString "TL"
|
||||
// #define DisplayMode_ThumbailsList_ButtonHelp "Thumbnails List"
|
||||
// todo
|
||||
//#define DisplayMode_ThumbailsGrid_ButtonString "TG"
|
||||
//#define DisplayMode_ThumbailsGrid_ButtonHelp "Thumbnails Grid"
|
||||
// #define DisplayMode_ThumbailsGrid_ButtonString "TG"
|
||||
// #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
|
||||
//#include <GLFW/glfw3.h>
|
||||
// #include <GLFW/glfw3.h>
|
||||
// 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
|
||||
//#define IGFD_KEY_DOWN ImGuiKey_DownArrow
|
||||
// #define IGFD_KEY_DOWN ImGuiKey_DownArrow
|
||||
// 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
|
||||
//#define IGFD_KEY_BACKSPACE ImGuiKey_Backspace
|
||||
// #define IGFD_KEY_BACKSPACE ImGuiKey_Backspace
|
||||
|
||||
// by ex you can quit the dialog by pressing the key excape
|
||||
//#define USE_DIALOG_EXIT_WITH_KEY
|
||||
//#define IGFD_EXIT_KEY ImGuiKey_Escape
|
||||
// #define USE_DIALOG_EXIT_WITH_KEY
|
||||
// #define IGFD_EXIT_KEY ImGuiKey_Escape
|
||||
|
||||
// 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
|
||||
// FILTER_COMBO_AUTO_SIZE is enabled by default now to 1
|
||||
// uncomment if you want disable
|
||||
//#define FILTER_COMBO_AUTO_SIZE 0
|
||||
// #define FILTER_COMBO_AUTO_SIZE 0
|
||||
// filter combobox width
|
||||
//#define FILTER_COMBO_MIN_WIDTH 120.0f
|
||||
// #define FILTER_COMBO_MIN_WIDTH 120.0f
|
||||
// button widget use for compose path
|
||||
//#define IMGUI_PATH_BUTTON ImGui::Button
|
||||
// #define IMGUI_PATH_BUTTON ImGui::Button
|
||||
// standard button
|
||||
//#define IMGUI_BUTTON ImGui::Button
|
||||
// #define IMGUI_BUTTON ImGui::Button
|
||||
|
||||
// locales string
|
||||
//#define createDirButtonString "+"
|
||||
//#define resetButtonString "R"
|
||||
//#define drivesButtonString "Drives"
|
||||
//#define editPathButtonString "E"
|
||||
//#define searchString "Search"
|
||||
//#define dirEntryString "[DIR] "
|
||||
//#define linkEntryString "[LINK] "
|
||||
//#define fileEntryString "[FILE] "
|
||||
//#define fileNameString "File Name : "
|
||||
//#define dirNameString "Directory Path :"
|
||||
//#define buttonResetSearchString "Reset search"
|
||||
//#define buttonDriveString "Drives"
|
||||
//#define buttonEditPathString "Edit path\nYou can also right click on path buttons"
|
||||
//#define buttonResetPathString "Reset to current directory"
|
||||
//#define buttonCreateDirString "Create Directory"
|
||||
//#define OverWriteDialogTitleString "The file Already Exist !"
|
||||
//#define OverWriteDialogMessageString "Would you like to OverWrite it ?"
|
||||
//#define OverWriteDialogConfirmButtonString "Confirm"
|
||||
//#define OverWriteDialogCancelButtonString "Cancel"
|
||||
// #define createDirButtonString "+"
|
||||
// #define resetButtonString "R"
|
||||
// #define drivesButtonString "Drives"
|
||||
// #define editPathButtonString "E"
|
||||
// #define searchString "Search"
|
||||
// #define dirEntryString "[DIR] "
|
||||
// #define linkEntryString "[LINK] "
|
||||
// #define fileEntryString "[FILE] "
|
||||
// #define fileNameString "File Name : "
|
||||
// #define dirNameString "Directory Path :"
|
||||
// #define buttonResetSearchString "Reset search"
|
||||
// #define buttonDriveString "Drives"
|
||||
// #define buttonEditPathString "Edit path\nYou can also right click on path buttons"
|
||||
// #define buttonResetPathString "Reset to current directory"
|
||||
// #define buttonCreateDirString "Create Directory"
|
||||
// #define OverWriteDialogTitleString "The file Already Exist !"
|
||||
// #define OverWriteDialogMessageString "Would you like to OverWrite it ?"
|
||||
// #define OverWriteDialogConfirmButtonString "Confirm"
|
||||
// #define OverWriteDialogCancelButtonString "Cancel"
|
||||
|
||||
//Validation buttons
|
||||
//#define okButtonString " OK"
|
||||
//#define okButtonWidth 0.0f
|
||||
//#define cancelButtonString " Cancel"
|
||||
//#define cancelButtonWidth 0.0f
|
||||
//alignement [0:1], 0.0 is left, 0.5 middle, 1.0 right, and other ratios
|
||||
//#define okCancelButtonAlignement 0.0f
|
||||
//#define invertOkAndCancelButtons 0
|
||||
// Validation buttons
|
||||
// #define okButtonString " OK"
|
||||
// #define okButtonWidth 0.0f
|
||||
// #define cancelButtonString " Cancel"
|
||||
// #define cancelButtonWidth 0.0f
|
||||
// alignement [0:1], 0.0 is left, 0.5 middle, 1.0 right, and other ratios
|
||||
// #define okCancelButtonAlignement 0.0f
|
||||
// #define invertOkAndCancelButtons 0
|
||||
|
||||
// DateTimeFormat
|
||||
// see strftime functionin <ctime> for customize
|
||||
// "%Y/%m/%d %H:%M" give 2021:01:22 11:47
|
||||
// "%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
|
||||
//#define USE_CUSTOM_SORTING_ICON
|
||||
//#define tableHeaderAscendingIcon "A|"
|
||||
//#define tableHeaderDescendingIcon "D|"
|
||||
//#define tableHeaderFileNameString " File name"
|
||||
//#define tableHeaderFileTypeString " Type"
|
||||
//#define tableHeaderFileSizeString " Size"
|
||||
//#define tableHeaderFileDateTimeString " Date"
|
||||
//#define fileSizeBytes "o"
|
||||
//#define fileSizeKiloBytes "Ko"
|
||||
//#define fileSizeMegaBytes "Mo"
|
||||
//#define fileSizeGigaBytes "Go"
|
||||
// #define USE_CUSTOM_SORTING_ICON
|
||||
// #define tableHeaderAscendingIcon "A|"
|
||||
// #define tableHeaderDescendingIcon "D|"
|
||||
// #define tableHeaderFileNameString " File name"
|
||||
// #define tableHeaderFileTypeString " Type"
|
||||
// #define tableHeaderFileSizeString " Size"
|
||||
// #define tableHeaderFileDateTimeString " Date"
|
||||
// #define fileSizeBytes "o"
|
||||
// #define fileSizeKiloBytes "Ko"
|
||||
// #define fileSizeMegaBytes "Mo"
|
||||
// #define fileSizeGigaBytes "Go"
|
||||
|
||||
// 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)
|
||||
//#define defaultSortOrderFilename true
|
||||
//#define defaultSortOrderType true
|
||||
//#define defaultSortOrderSize true
|
||||
//#define defaultSortOrderDate true
|
||||
//#define defaultSortOrderThumbnails true
|
||||
// #define defaultSortOrderFilename true
|
||||
// #define defaultSortOrderType true
|
||||
// #define defaultSortOrderSize true
|
||||
// #define defaultSortOrderDate true
|
||||
// #define defaultSortOrderThumbnails true
|
||||
|
||||
//#define USE_BOOKMARK
|
||||
//#define bookmarkPaneWith 150.0f
|
||||
//#define IMGUI_TOGGLE_BUTTON ToggleButton
|
||||
//#define bookmarksButtonString "Bookmark"
|
||||
//#define bookmarksButtonHelpString "Bookmark"
|
||||
//#define addBookmarkButtonString "+"
|
||||
//#define removeBookmarkButtonString "-"
|
||||
// #define USE_PLACES_FEATURE
|
||||
// #define PLACES_PANE_DEFAULT_SHOWN false
|
||||
// #define placesPaneWith 150.0f
|
||||
// #define IMGUI_TOGGLE_BUTTON ToggleButton
|
||||
// #define placesButtonString "Place"
|
||||
// #define placesButtonHelpString "Places"
|
||||
// #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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Win.yml)
|
||||
[](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Linux.yml)
|
||||
[](https://github.com/aiekick/ImGuiFileDialog/actions/workflows/Osx.yml)
|
||||
[](https://github.com/ocornut/imgui)
|
||||
[](https://github.com/ocornut/imgui)
|
||||
|
||||
# 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
|
||||
solutions.
|
||||
|
||||
## Possible Dialog Customization
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
||||
|
|
@ -67,15 +71,14 @@ Android Requirements : Api 21 mini
|
|||
- 0 => Infinite
|
||||
- 1 => One file (default)
|
||||
- n => n files
|
||||
- Compatible with MacOs, Linux, Windows, Emscripten
|
||||
- Windows version can list drives
|
||||
- Compatible with MacOs, Linux, Windows, Emscripten, Android
|
||||
- Supports modal or standard dialog types
|
||||
- Select files or directories
|
||||
- Filter groups and custom filter names
|
||||
- can ignore filter Case for file searching
|
||||
- Keyboard navigation (arrows, backspace, enter)
|
||||
- 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)
|
||||
- Optional 'Confirm to Overwrite" dialog if file exists
|
||||
- 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 ))
|
||||
2) a , will separate filters except if between a ( and )
|
||||
3) name{filter1, filter2} is a special form for collection filters
|
||||
3.1) the name can be composed of what you want except { and }
|
||||
3.2) a filter can be a regex
|
||||
- the name can be composed of what you want except { and }
|
||||
- a filter can be a regex
|
||||
4) the filters cannot integrate these chars '(' ')' '{' '}' ' ' except for a regex with respect to rule 1)
|
||||
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>
|
||||
|
||||
```cpp
|
||||
void drawGui()
|
||||
{
|
||||
void drawGui() {
|
||||
// open Dialog Simple
|
||||
if (ImGui::Button("Open File Dialog"))
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", ".");
|
||||
|
||||
if (ImGui::Button("Open File Dialog")) {
|
||||
IGFD::FileDialogConfig config;config.path = ".";
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", config);
|
||||
}
|
||||
// display
|
||||
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey"))
|
||||
{
|
||||
// action if OK
|
||||
if (ImGuiFileDialog::Instance()->IsOk())
|
||||
{
|
||||
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey")) {
|
||||
if (ImGuiFileDialog::Instance()->IsOk()) { // action if OK
|
||||
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath();
|
||||
// action
|
||||
|
|
@ -156,7 +156,7 @@ void drawGui()
|
|||
}
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
</blockquote></details>
|
||||
|
||||
|
|
@ -172,8 +172,11 @@ ImGuiFileDialogFlags_Modal
|
|||
you can use it like that :
|
||||
|
||||
```cpp
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp",
|
||||
".", 1, nullptr, ImGuiFileDialogFlags_Modal);
|
||||
IGFD::FileDialogConfig config;
|
||||
config.path = ".";
|
||||
config.countSelectionMax = 1;
|
||||
config.flags = ImGuiFileDialogFlags_Modal;
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp", config);
|
||||
```
|
||||
|
||||
</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:
|
||||
|
||||
```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.
|
||||
|
|
@ -226,9 +231,16 @@ inline void InfosPane(cosnt char *vFilter, IGFDUserDatas vUserDatas, bool *vCant
|
|||
void drawGui()
|
||||
{
|
||||
// open Dialog with Pane
|
||||
if (ImGui::Button("Open File Dialog with a custom pane"))
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".cpp,.h,.hpp",
|
||||
".", "", std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 350, 1, UserDatas("InfosPane"));
|
||||
if (ImGui::Button("Open File Dialog with a custom pane")) {
|
||||
IGFD::FileDialogConfig config;
|
||||
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
|
||||
if (ImGuiFileDialog::Instance()->Display("ChooseFileDlgKey"))
|
||||
|
|
@ -401,7 +413,9 @@ this code :
|
|||
|
||||
```cpp
|
||||
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 :
|
||||
|
|
@ -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.
|
||||
|
||||
```cpp
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".*,.cpp,.h,.hpp", ".");
|
||||
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 many File", ".*,.cpp,.h,.hpp", ".", 0);
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".png,.jpg",
|
||||
".", "", std::bind(&InfosPane, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3), 350, 1, "SaveFile"); // 1 file
|
||||
IGFD::FileDialogConfig config; config.path = ".";
|
||||
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey", "Choose File", ".*,.cpp,.h,.hpp", config);
|
||||
|
||||
config.countSelectionMax = 1;
|
||||
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
|
||||
```
|
||||
|
||||

|
||||
|
|
@ -470,47 +496,62 @@ ImGuiFileDialog::Instance()->SetFlashingAttenuationInSeconds(1.0f);
|
|||
|
||||
</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
|
||||
#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
|
||||
#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
|
||||
```
|
||||
|
||||
* You can select each bookmark to edit the displayed name corresponding to a path
|
||||
* Double-click on the label to apply the bookmark
|
||||
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 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();
|
||||
```
|
||||
|
||||
you can also add/remove bookmark by code :
|
||||
|
||||
and in this case, you can also avoid serialization of code based bookmark
|
||||
You must add a group first, then add a place to it :
|
||||
|
||||
```cpp
|
||||
Add => ImGuiFileDialog::Instance()->AddBookmark(bookmark_name, bookmark_path);
|
||||
Remove => ImGuiFileDialog::Instance()->RemoveBookmark(bookmark_name);
|
||||
|
||||
// true for prevent serialization of code based bookmarks
|
||||
Save => std::string bookmarkString = ImGuiFileDialog::Instance()->SerializeBookmarks(true);
|
||||
// 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);
|
||||
}
|
||||
```
|
||||
|
||||
(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
|
||||
|
||||

|
||||
|
||||
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>
|
||||
|
||||
|
|
@ -538,17 +579,20 @@ behavior. (by design! :) )
|
|||
Example code For Standard Dialog :
|
||||
|
||||
```cpp
|
||||
IGFD::FileDialogConfig config;
|
||||
config.path = ".";
|
||||
config.flags = ImGuiFileDialogFlags_ConfirmOverwrite;
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey",
|
||||
ICON_IGFD_SAVE " Choose a File", filters,
|
||||
".", "", 1, nullptr, ImGuiFileDialogFlags_ConfirmOverwrite);
|
||||
ICON_IGFD_SAVE " Choose a File", filters, config);
|
||||
```
|
||||
|
||||
Example code For Modal Dialog :
|
||||
|
||||
```cpp
|
||||
ImGuiFileDialog::Instance()->OpenDialog("ChooseFileDlgKey",
|
||||
ICON_IGFD_SAVE " Choose a File", filters,
|
||||
".", "", 1, nullptr, ImGuiFileDialogFlags_Modal | ImGuiFileDialogFlags_ConfirmOverwrite);
|
||||
IGFD::FileDialogConfig config;
|
||||
config.path = ".";
|
||||
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()`.
|
||||
|
|
@ -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.
|
||||
// btw you can od what you want
|
||||
fileDialog.OpenDialog("embedded", "Select File", ".*", "", -1, nullptr,
|
||||
ImGuiFileDialogFlags_NoDialog |
|
||||
IGFD::FileDialogConfig config;
|
||||
config.path = ".";
|
||||
config.countSelectionMax = -1;
|
||||
config.flags = ImGuiFileDialogFlags_NoDialog |
|
||||
ImGuiFileDialogFlags_DisableBookmarkMode |
|
||||
ImGuiFileDialogFlags_DisableCreateDirectoryButton |
|
||||
ImGuiFileDialogFlags_ReadOnlyFileNameField);
|
||||
fileDialog.OpenDialog("embedded", "Select File", ".*", config);
|
||||
// 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
|
||||
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>
|
||||
|
||||
<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 :
|
||||
|
||||

|
||||
|
||||
After :
|
||||
|
||||

|
||||
|
||||
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
|
||||
```
|
||||

|
||||
|
||||
</blockquote></details>
|
||||
|
||||
<details open><summary><h2>C Api :</h2></summary><blockquote>
|
||||
|
||||
this api was sucessfully tested with CImGui
|
||||
|
|
@ -976,19 +1093,15 @@ Sample code with cimgui :
|
|||
ImGuiFileDialog *cfileDialog = IGFD_Create();
|
||||
|
||||
// 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,
|
||||
"filedlg", // dialog key (make it possible to have different treatment reagrding the dialog key
|
||||
"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..
|
||||
".", // 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
|
||||
config); // the file dialog config
|
||||
}
|
||||
|
||||
ImGuiIO* ioptr = igGetIO();
|
||||
|
|
|
|||
|
|
@ -9,127 +9,143 @@
|
|||
// this options need c++17
|
||||
// #define USE_STD_FILESYSTEM
|
||||
|
||||
//#define MAX_FILE_DIALOG_NAME_BUFFER 1024
|
||||
//#define MAX_PATH_BUFFER_SIZE 1024
|
||||
// #define MAX_FILE_DIALOG_NAME_BUFFER 1024
|
||||
// #define MAX_PATH_BUFFER_SIZE 1024
|
||||
|
||||
// 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.
|
||||
// if disabled the spacing is defined by the imgui theme
|
||||
// define the space between path buttons
|
||||
//#define CUSTOM_PATH_SPACING 2
|
||||
// #define CUSTOM_PATH_SPACING 2
|
||||
|
||||
//#define USE_THUMBNAILS
|
||||
//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"
|
||||
//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_RESIZE_IMPLEMENTATION
|
||||
//#define IMGUI_RADIO_BUTTON RadioButton
|
||||
//#define DisplayMode_ThumbailsList_ImageHeight 32.0f
|
||||
//#define tableHeaderFileThumbnailsString "Thumbnails"
|
||||
//#define DisplayMode_FilesList_ButtonString "FL"
|
||||
//#define DisplayMode_FilesList_ButtonHelp "File List"
|
||||
//#define DisplayMode_ThumbailsList_ButtonString "TL"
|
||||
//#define DisplayMode_ThumbailsList_ButtonHelp "Thumbnails List"
|
||||
// #define USE_THUMBNAILS
|
||||
// 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"
|
||||
// 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_RESIZE_IMPLEMENTATION
|
||||
// #define IMGUI_RADIO_BUTTON RadioButton
|
||||
// #define DisplayMode_ThumbailsList_ImageHeight 32.0f
|
||||
// #define tableHeaderFileThumbnailsString "Thumbnails"
|
||||
// #define DisplayMode_FilesList_ButtonString "FL"
|
||||
// #define DisplayMode_FilesList_ButtonHelp "File List"
|
||||
// #define DisplayMode_ThumbailsList_ButtonString "TL"
|
||||
// #define DisplayMode_ThumbailsList_ButtonHelp "Thumbnails List"
|
||||
// todo
|
||||
//#define DisplayMode_ThumbailsGrid_ButtonString "TG"
|
||||
//#define DisplayMode_ThumbailsGrid_ButtonHelp "Thumbnails Grid"
|
||||
// #define DisplayMode_ThumbailsGrid_ButtonString "TG"
|
||||
// #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
|
||||
//#include <GLFW/glfw3.h>
|
||||
// #include <GLFW/glfw3.h>
|
||||
// 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
|
||||
//#define IGFD_KEY_DOWN ImGuiKey_DownArrow
|
||||
// #define IGFD_KEY_DOWN ImGuiKey_DownArrow
|
||||
// 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
|
||||
//#define IGFD_KEY_BACKSPACE ImGuiKey_Backspace
|
||||
// #define IGFD_KEY_BACKSPACE ImGuiKey_Backspace
|
||||
|
||||
// by ex you can quit the dialog by pressing the key excape
|
||||
//#define USE_DIALOG_EXIT_WITH_KEY
|
||||
//#define IGFD_EXIT_KEY ImGuiKey_Escape
|
||||
// #define USE_DIALOG_EXIT_WITH_KEY
|
||||
// #define IGFD_EXIT_KEY ImGuiKey_Escape
|
||||
|
||||
// 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
|
||||
// FILTER_COMBO_AUTO_SIZE is enabled by default now to 1
|
||||
// uncomment if you want disable
|
||||
//#define FILTER_COMBO_AUTO_SIZE 0
|
||||
// #define FILTER_COMBO_AUTO_SIZE 0
|
||||
// filter combobox width
|
||||
//#define FILTER_COMBO_MIN_WIDTH 120.0f
|
||||
// #define FILTER_COMBO_MIN_WIDTH 120.0f
|
||||
// button widget use for compose path
|
||||
//#define IMGUI_PATH_BUTTON ImGui::Button
|
||||
// #define IMGUI_PATH_BUTTON ImGui::Button
|
||||
// standard button
|
||||
//#define IMGUI_BUTTON ImGui::Button
|
||||
// #define IMGUI_BUTTON ImGui::Button
|
||||
|
||||
// locales string
|
||||
//#define createDirButtonString "+"
|
||||
//#define resetButtonString "R"
|
||||
//#define drivesButtonString "Drives"
|
||||
//#define editPathButtonString "E"
|
||||
//#define searchString "Search"
|
||||
//#define dirEntryString "[DIR] "
|
||||
//#define linkEntryString "[LINK] "
|
||||
//#define fileEntryString "[FILE] "
|
||||
//#define fileNameString "File Name : "
|
||||
//#define dirNameString "Directory Path :"
|
||||
//#define buttonResetSearchString "Reset search"
|
||||
//#define buttonDriveString "Drives"
|
||||
//#define buttonEditPathString "Edit path\nYou can also right click on path buttons"
|
||||
//#define buttonResetPathString "Reset to current directory"
|
||||
//#define buttonCreateDirString "Create Directory"
|
||||
//#define OverWriteDialogTitleString "The file Already Exist !"
|
||||
//#define OverWriteDialogMessageString "Would you like to OverWrite it ?"
|
||||
//#define OverWriteDialogConfirmButtonString "Confirm"
|
||||
//#define OverWriteDialogCancelButtonString "Cancel"
|
||||
// #define createDirButtonString "+"
|
||||
// #define resetButtonString "R"
|
||||
// #define drivesButtonString "Drives"
|
||||
// #define editPathButtonString "E"
|
||||
// #define searchString "Search"
|
||||
// #define dirEntryString "[DIR] "
|
||||
// #define linkEntryString "[LINK] "
|
||||
// #define fileEntryString "[FILE] "
|
||||
// #define fileNameString "File Name : "
|
||||
// #define dirNameString "Directory Path :"
|
||||
// #define buttonResetSearchString "Reset search"
|
||||
// #define buttonDriveString "Drives"
|
||||
// #define buttonEditPathString "Edit path\nYou can also right click on path buttons"
|
||||
// #define buttonResetPathString "Reset to current directory"
|
||||
// #define buttonCreateDirString "Create Directory"
|
||||
// #define OverWriteDialogTitleString "The file Already Exist !"
|
||||
// #define OverWriteDialogMessageString "Would you like to OverWrite it ?"
|
||||
// #define OverWriteDialogConfirmButtonString "Confirm"
|
||||
// #define OverWriteDialogCancelButtonString "Cancel"
|
||||
|
||||
//Validation buttons
|
||||
//#define okButtonString " OK"
|
||||
//#define okButtonWidth 0.0f
|
||||
//#define cancelButtonString " Cancel"
|
||||
//#define cancelButtonWidth 0.0f
|
||||
//alignement [0:1], 0.0 is left, 0.5 middle, 1.0 right, and other ratios
|
||||
//#define okCancelButtonAlignement 0.0f
|
||||
//#define invertOkAndCancelButtons 0
|
||||
// Validation buttons
|
||||
// #define okButtonString " OK"
|
||||
// #define okButtonWidth 0.0f
|
||||
// #define cancelButtonString " Cancel"
|
||||
// #define cancelButtonWidth 0.0f
|
||||
// alignement [0:1], 0.0 is left, 0.5 middle, 1.0 right, and other ratios
|
||||
// #define okCancelButtonAlignement 0.0f
|
||||
// #define invertOkAndCancelButtons 0
|
||||
|
||||
// DateTimeFormat
|
||||
// see strftime functionin <ctime> for customize
|
||||
// "%Y/%m/%d %H:%M" give 2021:01:22 11:47
|
||||
// "%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
|
||||
//#define USE_CUSTOM_SORTING_ICON
|
||||
//#define tableHeaderAscendingIcon "A|"
|
||||
//#define tableHeaderDescendingIcon "D|"
|
||||
//#define tableHeaderFileNameString " File name"
|
||||
//#define tableHeaderFileTypeString " Type"
|
||||
//#define tableHeaderFileSizeString " Size"
|
||||
//#define tableHeaderFileDateTimeString " Date"
|
||||
//#define fileSizeBytes "o"
|
||||
//#define fileSizeKiloBytes "Ko"
|
||||
//#define fileSizeMegaBytes "Mo"
|
||||
//#define fileSizeGigaBytes "Go"
|
||||
// #define USE_CUSTOM_SORTING_ICON
|
||||
// #define tableHeaderAscendingIcon "A|"
|
||||
// #define tableHeaderDescendingIcon "D|"
|
||||
// #define tableHeaderFileNameString " File name"
|
||||
// #define tableHeaderFileTypeString " Type"
|
||||
// #define tableHeaderFileSizeString " Size"
|
||||
// #define tableHeaderFileDateTimeString " Date"
|
||||
// #define fileSizeBytes "o"
|
||||
// #define fileSizeKiloBytes "Ko"
|
||||
// #define fileSizeMegaBytes "Mo"
|
||||
// #define fileSizeGigaBytes "Go"
|
||||
|
||||
// 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)
|
||||
//#define defaultSortOrderFilename true
|
||||
//#define defaultSortOrderType true
|
||||
//#define defaultSortOrderSize true
|
||||
//#define defaultSortOrderDate true
|
||||
//#define defaultSortOrderThumbnails true
|
||||
// #define defaultSortOrderFilename true
|
||||
// #define defaultSortOrderType true
|
||||
// #define defaultSortOrderSize true
|
||||
// #define defaultSortOrderDate true
|
||||
// #define defaultSortOrderThumbnails true
|
||||
|
||||
//#define USE_BOOKMARK
|
||||
//#define bookmarkPaneWith 150.0f
|
||||
//#define IMGUI_TOGGLE_BUTTON ToggleButton
|
||||
//#define bookmarksButtonString "Bookmark"
|
||||
//#define bookmarksButtonHelpString "Bookmark"
|
||||
//#define addBookmarkButtonString "+"
|
||||
//#define removeBookmarkButtonString "-"
|
||||
#define USE_PLACES_FEATURE
|
||||
// #define PLACES_PANE_DEFAULT_SHOWN false
|
||||
// #define placesPaneWith 150.0f
|
||||
// #define IMGUI_TOGGLE_BUTTON ToggleButton
|
||||
// #define placesButtonString "Place"
|
||||
// #define placesButtonHelpString "Places"
|
||||
// #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
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ public:
|
|||
virtual ~IStoryManager() {}
|
||||
|
||||
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 PlaySoundFile(const std::string &fileName) = 0;
|
||||
virtual std::string BuildFullAssetsPath(const std::string &fileName) const = 0;
|
||||
|
|
|
|||
234
story-editor/src/importers/ni_parser.c
Normal file
234
story-editor/src/importers/ni_parser.c
Normal 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);
|
||||
}
|
||||
68
story-editor/src/importers/ni_parser.h
Normal file
68
story-editor/src/importers/ni_parser.h
Normal 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
|
||||
620
story-editor/src/importers/pack_archive.cpp
Normal file
620
story-editor/src/importers/pack_archive.cpp
Normal 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();
|
||||
}
|
||||
|
||||
58
story-editor/src/importers/pack_archive.h
Normal file
58
story-editor/src/importers/pack_archive.h
Normal 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
|
||||
|
|
@ -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
|
||||
{
|
||||
ImGui::TextColored(ImVec4(0, 1, 1, 1), "Infos Pane");
|
||||
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)
|
||||
*vCantContinue = canValidateDialog;
|
||||
}
|
||||
|
|
@ -352,12 +360,19 @@ std::string LibraryWindow::ToLocalStoreFile(const std::string &url)
|
|||
|
||||
void LibraryWindow::Draw()
|
||||
{
|
||||
static int importFormat = 0;
|
||||
|
||||
WindowBase::BeginDraw();
|
||||
ImGui::SetWindowSize(ImVec2(626, 744), ImGuiCond_FirstUseEver);
|
||||
|
||||
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())
|
||||
|
|
@ -397,7 +412,17 @@ void LibraryWindow::Draw()
|
|||
|
||||
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::TableHeadersRow();
|
||||
|
||||
int internal_id = 1;
|
||||
for (auto &p : m_libraryManager)
|
||||
{
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::Text("%s", p->GetName().c_str());
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushID(internal_id++);
|
||||
if (ImGui::SmallButton("Load"))
|
||||
{
|
||||
m_storyManager.OpenProject(p->GetUuid());
|
||||
}
|
||||
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("Remove"))
|
||||
{
|
||||
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
|
@ -529,11 +558,8 @@ void LibraryWindow::Draw()
|
|||
std::string filePathName = ImGuiFileDialog::Instance()->GetFilePathName();
|
||||
std::string filePath = ImGuiFileDialog::Instance()->GetCurrentPath();
|
||||
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;
|
||||
// if (ImGuiFileDialog::Instance()->GetUserDatas())
|
||||
// userDatas = std::string((const char*)ImGuiFileDialog::Instance()->GetUserDatas());
|
||||
// auto selection = ImGuiFileDialog::Instance()->GetSelection(); // multiselection
|
||||
|
||||
m_storyManager.ImportProject(filePathName, importFormat);
|
||||
|
||||
// action
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
|
||||
#include "media_converter.h"
|
||||
|
||||
#include "pack_archive.h"
|
||||
#include "uuid.h"
|
||||
|
||||
#ifdef USE_WINDOWS_OS
|
||||
#include <winsock2.h>
|
||||
#include <iphlpapi.h>
|
||||
|
|
@ -338,7 +341,11 @@ void MainWindow::DrawMainMenuBar()
|
|||
|
||||
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
|
||||
|
|
@ -645,6 +652,125 @@ void MainWindow::OpenProject(const std::string &uuid)
|
|||
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()
|
||||
{
|
||||
std::string fullText = "Story Editor " + LibraryManager::GetVersion();
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ private:
|
|||
|
||||
// From IStoryManager (proxy to StoryProject class)
|
||||
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 PlaySoundFile(const std::string &fileName) override;;
|
||||
virtual std::string BuildFullAssetsPath(const std::string &fileName) const override;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
#undef QOI_NO_STDIO
|
||||
#include "qoi.h"
|
||||
|
||||
//#define DR_MP3_IMPLEMENTATION
|
||||
#define DR_MP3_IMPLEMENTATION
|
||||
#include "dr_mp3.h"
|
||||
|
||||
MediaConverter::MediaConverter()
|
||||
|
|
|
|||
7736
story-editor/src/miniz.c
Normal file
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
1350
story-editor/src/miniz.h
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -65,7 +65,7 @@ void NodeEditorWindow::LoadNode(const nlohmann::json &nodeJson)
|
|||
n->SetId(restoredNodeId);
|
||||
nlohmann::json posJson = nodeJson["position"];
|
||||
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);
|
||||
|
||||
m_ids.insert(restoredNodeId);
|
||||
|
|
@ -139,29 +139,43 @@ ed::PinId NodeEditorWindow::GetOutputPin(unsigned long modelNodeId, int pinIndex
|
|||
|
||||
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) {
|
||||
LoadNode(element);
|
||||
nlohmann::json nodesJsonArray = model["nodes"];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
std::cout << model.dump(4) << std::endl;
|
||||
|
||||
nlohmann::json connectionJsonArray = model["connections"];
|
||||
|
||||
for (auto& connection : connectionJsonArray)
|
||||
catch(std::exception &e)
|
||||
{
|
||||
Connection model = connection.get<Connection>();
|
||||
CreateLink(model,
|
||||
GetInputPin(model.inNodeId, model.inPortIndex),
|
||||
GetOutputPin(model.outNodeId, model.outPortIndex));
|
||||
std::cout << e.what() << std::endl;
|
||||
}
|
||||
|
||||
m_loaded = true;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,13 @@ void ResourcesWindow::ChooseFile()
|
|||
{
|
||||
m_showImportDialog = false;
|
||||
// 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
|
||||
|
|
|
|||
274
story-editor/src/zip.cpp
Normal file
274
story-editor/src/zip.cpp
Normal 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
52
story-editor/src/zip.h
Normal 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
|
||||
//=============================================================================
|
||||
Loading…
Reference in a new issue