diff --git a/CHANGELOG.md b/CHANGELOG.md
index ba4761e..67ab6c2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
# Changelog
+## [1.2.0] - 2023-04-24
+- Add plugin API support to allow users to extend the application with their own code.
+
+## [1.1.2] - 2023-04-23
+- Add `system_save_file_dialog` on OSX
+- Fix `build_setting` bash function on OSX
+- Fix `MEM_DELETE` when pointer is null
+- Fix OSX compilation issues
+- Improve `./run` script
+- Remove `FOUNDATION_CONSTCALL` usage which is causing issues on OSX
+
## [1.1.1] - 2023-04-22
- Add `./run package` (through `build-package.sh`) script to build and package the library for distribution.
- Add `BUILD_ENABLE_BACKEND` build switch
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7278bb8..32fcf72 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -36,6 +36,12 @@ set(ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE PATH "Root directory path")
# Set build output dir
set(BUILD_DIR ${CMAKE_CURRENT_SOURCE_DIR}/build CACHE PATH "Build directory path")
+# Set plugins dir path
+set(PLUGINS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/plugins CACHE PATH "Plugins directory path")
+
+# Set plugin build output dir
+set(PLUGIN_BUILD_DIR ${BUILD_DIR}/plugins CACHE PATH "Build plugins path")
+
# Cache the shared module path
# Then each nested CMakeLists.txt can include the shared.cmake file using the following command:
# include(${SHARED_MODULE_PATH})
@@ -248,7 +254,17 @@ if(MSVC)
# Set /OPT:REF for static libraries as well
set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /LTCG")
-elseif(XCODE)
+ # Set DLL output build directory
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${BUILD_DIR})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${BUILD_DIR})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEPLOY ${BUILD_DIR})
+
+ # Set PDB output build directory
+ set(CMAKE_PDB_OUTPUT_DIRECTORY_DEBUG ${BUILD_DIR})
+ set(CMAKE_PDB_OUTPUT_DIRECTORY_RELEASE ${BUILD_DIR})
+ set(CMAKE_PDB_OUTPUT_DIRECTORY_DEPLOY ${BUILD_DIR})
+
+elseif(APPLE)
# Use fast floating point math
add_compile_options(-ffast-math)
@@ -286,6 +302,9 @@ elseif(XCODE)
add_compile_options(-Wno-tautological-constant-compare -Wno-unguarded-availability-new)
endif()
+ # Define -Wno-varargs
+ add_compile_options(-Wno-varargs)
+
endif()
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${CMAKE_FLAGS_RELEASE}")
@@ -298,6 +317,12 @@ set(CMAKE_CXX_FLAGS_DEPLOY "${CMAKE_CXX_FLAGS_DEPLOY} ${CMAKE_CXX_FLAGS_RELEASE}
# Copy release linker flags to deploy
set(CMAKE_EXE_LINKER_FLAGS_DEPLOY "${CMAKE_EXE_LINKER_FLAGS_DEPLOY} ${CMAKE_EXE_LINKER_FLAGS_RELEASE}")
+if (MSVC)
+ # Generate PDB files for debug and release builds
+ set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /DEBUG")
+ set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG")
+endif()
+
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# Load BX library
@@ -330,9 +355,20 @@ endif()
# Load main exectuable project under sources/
add_subdirectory(${ROOT_DIR}/sources)
-# Add subdirectories for all cmakelist under tools/
-file(GLOB_RECURSE CMAKE_LISTS ${ROOT_DIR}/tools/*CMakeLists.txt)
-foreach(CMAKE_LIST ${CMAKE_LISTS})
- get_filename_component(CMAKE_LIST_DIR ${CMAKE_LIST} DIRECTORY)
- add_subdirectory(${CMAKE_LIST_DIR})
-endforeach()
+if (BUILD_ENABLE_TOOLS)
+ # Add subdirectories for all cmakelists under tools/
+ file(GLOB_RECURSE TOOLS_LISTS ${ROOT_DIR}/tools/*CMakeLists.txt)
+ foreach(CMAKE_LIST ${TOOLS_LISTS})
+ get_filename_component(CMAKE_LIST_DIR ${CMAKE_LIST} DIRECTORY)
+ add_subdirectory(${CMAKE_LIST_DIR})
+ endforeach()
+endif()
+
+if (BUILD_ENABLE_PLUGINS)
+ # Add subdirectories for all cmakelists under plugins/
+ file(GLOB_RECURSE PLUGINS_LISTS ${ROOT_DIR}/plugins/*CMakeLists.txt)
+ foreach(CMAKE_LIST ${PLUGINS_LISTS})
+ get_filename_component(CMAKE_LIST_DIR ${CMAKE_LIST} DIRECTORY)
+ add_subdirectory(${CMAKE_LIST_DIR})
+ endforeach()
+endif()
diff --git a/README.md b/README.md
index 0d23ce2..2cb06bc 100644
--- a/README.md
+++ b/README.md
@@ -69,13 +69,11 @@ Then you usually want to start editing sources under `sources/`. As a starting p
Basically, you need to have a `cpp` file that implements minimally the following functions:
- `app_title` - Returns the application title.
-- `app_exception_handler` - Defines how the application handles crashes.
- `app_configure` - Defines how the application and foundation is configured.
- `app_initialize` - Defines how the application is initialized (i.e. initialize services and modules once, etc).
- `app_shutdown` - Defines how the application is finalized (i.e. shutdown services and modules once, etc).
- `app_update` - Defines how the application is updated prior to render each frames.
- `app_render` - Defines how the application is rendered each frame.
-- `app_render_3rdparty_libs` - Entry point to render additional 3rd party libraries used for your specific application.
### Build and run
diff --git a/config/build.settings b/config/build.settings
index 58eaa1c..ba127b1 100644
--- a/config/build.settings
+++ b/config/build.settings
@@ -13,7 +13,7 @@ PRODUCT_URL=https://equals-forty-two.com
# Version info
VERSION_MAJOR=1
VERSION_MINOR=1
-VERSION_PATCH=1
+VERSION_PATCH=2
# Build settings (Usually passed to Cmake)
BUILD_SERVICE_EXE=OFF
@@ -22,3 +22,5 @@ BUILD_ENABLE_LOCALIZATION=ON
BUILD_MAX_JOB_THREADS=2
BUILD_MAX_QUERY_THREADS=2
BUILD_ENABLE_BACKEND=OFF
+BUILD_ENABLE_PLUGINS=ON
+BUILD_ENABLE_TOOLS=OFF
diff --git a/config/options.cmake b/config/options.cmake
index 38cfd6b..3eee286 100644
--- a/config/options.cmake
+++ b/config/options.cmake
@@ -24,3 +24,9 @@ option(BUILD_ENABLE_TESTS "Build tests" ON)
# Set the build backend option to OFF by default.
option(BUILD_ENABLE_BACKEND "Build backend" OFF)
+
+# Set options to build or not tools
+option(BUILD_ENABLE_TOOLS "Build tools" ON)
+
+# Set option to build or not plugins
+option(BUILD_ENABLE_PLUGINS "Build plugins" ON)
diff --git a/config/shared.cmake b/config/shared.cmake
index 104d871..dede0d3 100644
--- a/config/shared.cmake
+++ b/config/shared.cmake
@@ -82,7 +82,7 @@ function(set_executable_framework_linker_flags CMAKE_EXE_LINKER_FLAGS CMAKE_EXE_
# Ignore specific default libraries msvcrt.lib;libcmt.lib in debug for MSVC
set(${CMAKE_EXE_LINKER_FLAGS_DEBUG} "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcmt.lib")
- elseif(XCODE)
+ elseif(APPLE)
# Link with core libraries
set(${CMAKE_EXE_LINKER_FLAGS} "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation -framework CoreServices")
diff --git a/external/bgfx/CMakeLists.txt b/external/bgfx/CMakeLists.txt
index 50e3b17..2e57333 100644
--- a/external/bgfx/CMakeLists.txt
+++ b/external/bgfx/CMakeLists.txt
@@ -44,16 +44,25 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
add_compile_definitions(BGFX_CONFIG_USE_TINYSTL=0)
# Disable vulkan backend renderer for all platforms
+add_compile_definitions(BGFX_CONFIG_RENDERER_AGC=0)
add_compile_definitions(BGFX_CONFIG_RENDERER_VULKAN=0)
add_compile_definitions(BGFX_CONFIG_RENDERER_OPENGLES=0)
-add_compile_definitions(BGFX_CONFIG_RENDERER_AGC=0)
add_compile_definitions(BGFX_CONFIG_RENDERER_OPENGL=0)
-add_compile_definitions(BGFX_CONFIG_MAX_DRAW_CALLS=1024)
-add_compile_definitions(BGFX_CONFIG_MAX_OCCLUSION_QUERIES=32)
-add_compile_definitions(BGFX_CONFIG_MAX_VIEW_NAME=64)
+# BGFX tweaks for our needs
+add_compile_definitions(BGFX_CONFIG_MAX_SHADERS=16)
+add_compile_definitions(BGFX_CONFIG_MAX_VIEW_NAME=16)
+add_compile_definitions(BGFX_CONFIG_MAX_DRAW_CALLS=2047)
add_compile_definitions(BGFX_CONFIG_MAX_VERTEX_DECLS=32)
-add_compile_definitions(BGFX_CONFIG_MAX_INDEX_BUFFERS=512)
+add_compile_definitions(BGFX_CONFIG_MAX_INDEX_BUFFERS=256)
+add_compile_definitions(BGFX_CONFIG_MAX_OCCLUSION_QUERIES=8)
+add_compile_definitions(BGFX_CONFIG_MAX_DYNAMIC_INDEX_BUFFERS=8192)
+add_compile_definitions(BGFX_CONFIG_MAX_DYNAMIC_VERTEX_BUFFERS=8192)
+
+# Disable debug features
+add_compile_definitions(BGFX_CONFIG_DEBUG_UNIFORM=0)
+add_compile_definitions(BGFX_CONFIG_DEBUG_ANNOTATION=0)
+add_compile_definitions(BGFX_CONFIG_DEBUG_OCCLUSION=0)
if (WIN32)
@@ -63,9 +72,7 @@ if (WIN32)
# Enable directx backend renderers
add_compile_definitions(BGFX_CONFIG_RENDERER_DIRECT3D11=1)
- add_compile_definitions(BGFX_CONFIG_RENDERER_DIRECT3D12=1)
-
- add_compile_definitions(BGFX_CONFIG_MAX_INSTANCE_DATA_COUNT=4)
+ add_compile_definitions(BGFX_CONFIG_RENDERER_DIRECT3D12=0)
elseif (APPLE)
@@ -73,7 +80,7 @@ elseif (APPLE)
add_compile_options(-fno-rtti)
add_compile_options(-fno-exceptions)
- # Force METAL renderer with BGFX_CONFIG_RENDERER_METAL=1
+ # Force METAL renderer
add_compile_definitions(BGFX_CONFIG_RENDERER_METAL=1)
endif()
diff --git a/external/foundation/CMakeLists.txt b/external/foundation/CMakeLists.txt
index 2323bc4..e0f1779 100644
--- a/external/foundation/CMakeLists.txt
+++ b/external/foundation/CMakeLists.txt
@@ -29,10 +29,14 @@ add_compile_definitions(_LIB)
# Use compiler flags from parent project
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
-
-# Use compiler flags from parent project
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+# Export foundation functions
+if (BUILD_ENABLE_PLUGINS)
+ add_compile_definitions(FOUNDATION_COMPILE=1)
+ add_compile_definitions(BUILD_DYNAMIC_LINK=1)
+endif()
+
# Project library
add_library(foundation STATIC ${foundation_src} ${foundation_includes})
@@ -45,9 +49,7 @@ target_include_directories(foundation PUBLIC ${ROOT_DIR}/external/)
# Export a few defines to the project
target_compile_definitions(foundation PUBLIC -DFOUNDATION_SIZE_REAL=8)
-if (APPLE)
+if (XCODE)
# Set Xcode project properties
- set_target_properties(foundation
- PROPERTIES
- XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_WEAK YES)
+ set_target_properties(foundation PROPERTIES XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_WEAK YES)
endif()
diff --git a/external/foundation/exception.c b/external/foundation/exception.c
index c9a5d53..8645663 100644
--- a/external/foundation/exception.c
+++ b/external/foundation/exception.c
@@ -54,7 +54,7 @@ typedef BOOL(STDCALL* MiniDumpWriteDumpFn)(HANDLE, DWORD, HANDLE, MINIDUMP_TYPE,
CONST PMINIDUMP_CALLBACK_INFORMATION);
static void
-create_mini_dump(EXCEPTION_POINTERS* pointers, const char* name, size_t namelen, char* dump_file, size_t* capacity) {
+create_mini_dump(EXCEPTION_POINTERS* pointers, const char* name, size_t namelen, char* dump_file, size_t* capacity, bool print_dump_written_log) {
MINIDUMP_EXCEPTION_INFORMATION info;
HANDLE file;
SYSTEMTIME local_time;
@@ -109,8 +109,9 @@ create_mini_dump(EXCEPTION_POINTERS* pointers, const char* name, size_t namelen,
STRING_FORMAT(errmsg));
}
if (success) {
- log_errorf(0, ERROR_EXCEPTION, STRING_CONST("Exception occurred! Minidump written to: %.*s"),
- STRING_FORMAT(filename));
+ if (print_dump_written_log) {
+ log_errorf(0, ERROR_EXCEPTION, STRING_CONST("Exception occurred! Minidump written to: %.*s"), STRING_FORMAT(filename));
+ }
FlushFileBuffers(file);
}
CloseHandle(file);
@@ -156,9 +157,9 @@ exception_filter(LPEXCEPTION_POINTERS pointers) {
char dump_file_buffer[MAX_PATH];
size_t dump_file_len = sizeof(dump_file_buffer);
exception_closure.triggered = true;
- create_mini_dump(pointers, STRING_ARGS(exception_closure.name), dump_file_buffer, &dump_file_len);
+ create_mini_dump(pointers, STRING_ARGS(exception_closure.name), dump_file_buffer, &dump_file_len, true);
if (exception_closure.handler)
- exception_closure.handler(dump_file_buffer, dump_file_len);
+ exception_closure.handler(nullptr, dump_file_buffer, dump_file_len);
if (_RtlRestoreContext)
_RtlRestoreContext(&exception_closure.context, 0);
else
@@ -229,10 +230,10 @@ FOUNDATION_ATTRIBUTE(noreturn) exception_sigaction(int sig, siginfo_t* info, voi
char file_name_buffer[BUILD_MAX_PATHLEN];
const char* name = get_thread_dump_name();
string_t dump_file = (string_t){file_name_buffer, sizeof(file_name_buffer)};
- dump_file = create_mini_dump(arg, (string_const_t){name, string_length(name)}, dump_file);
+ dump_file = create_mini_dump(arg, (string_const_t){name, string_length(name)}, dump_file, true);
exception_handler_fn handler = get_thread_exception_handler();
if (handler)
- handler(dump_file.str, dump_file.length);
+ handler(arg, dump_file.str, dump_file.length);
// Log dump file name
log_warnf(0, WARNING_SUSPICIOUS, STRING_CONST("Minidump written to: %.*s"), STRING_FORMAT(dump_file));
@@ -274,10 +275,10 @@ exception_try(exception_try_fn fn, void* data, exception_handler_fn handler, con
__try {
ret = fn(data);
} /*lint -e534*/
- __except (create_mini_dump(GetExceptionInformation(), name, length, buffer, &capacity), EXCEPTION_EXECUTE_HANDLER) {
+ __except (create_mini_dump(GetExceptionInformation(), name, length, buffer, &capacity, false), EXCEPTION_EXECUTE_HANDLER) {
ret = FOUNDATION_EXCEPTION_CAUGHT;
if (handler)
- handler(buffer, capacity);
+ handler(data, buffer, capacity);
error_context_clear();
}
diff --git a/external/foundation/platform.h b/external/foundation/platform.h
index cba9b9e..ec633da 100644
--- a/external/foundation/platform.h
+++ b/external/foundation/platform.h
@@ -109,7 +109,7 @@ thread local storage to ensure maximum portability across supported platforms */
#undef FOUNDATION_PLATFORM_ANDROID
#define FOUNDATION_PLATFORM_ANDROID 1
-// Compatibile platforms
+// Compatible platforms
#undef FOUNDATION_PLATFORM_POSIX
#define FOUNDATION_PLATFORM_POSIX 1
@@ -200,7 +200,7 @@ thread local storage to ensure maximum portability across supported platforms */
#undef FOUNDATION_PLATFORM_TIZEN
#define FOUNDATION_PLATFORM_TIZEN 1
-// Compatibile platforms
+// Compatible platforms
#undef FOUNDATION_PLATFORM_POSIX
#define FOUNDATION_PLATFORM_POSIX 1
@@ -1796,7 +1796,7 @@ Expand to three arguments, string pointer, length and capacity, as in
s.str, s.length, s.length+1
\def STRING_FORMAT
-Expand to two arguments, legnth and string pointer, as in (int)s.length, s.str.
+Expand to two arguments, length and string pointer, as in (int)s.length, s.str.
Useful when passing a string_t to a string format argument, for example
string_t mystr = ...; log_infof(0, STRING_CONST("Mystring: %.*s"), STRING_FORMAT(mystr));
diff --git a/external/foundation/types.h b/external/foundation/types.h
index f6f0dc8..575ad19 100644
--- a/external/foundation/types.h
+++ b/external/foundation/types.h
@@ -603,7 +603,7 @@ an implementation specific code which is then returned from the call to error_re
\return Implementation specific code which is passed back as return from error_report */
typedef int (*error_handler_fn)(error_level_t level, error_t error);
-/*! Assert handler which is passed assert data and should do impementation specific
+/*! Assert handler which is passed assert data and should do implementation specific
processing and return a code indicating if execution can continue or need to be aborted.
\param context Error context
\param condition String expressing the condition that failed
@@ -743,9 +743,10 @@ typedef int (*exception_try_fn)(void* arg);
/*! Exception handler function prototype, used to notify that an exception occurred
and the process state was saved to a dump file
+\param arg Implementation specific argument passed to exception_try
\param file Dump file path
\param length Length of file path */
-typedef void (*exception_handler_fn)(const char* file, size_t length);
+typedef void (*exception_handler_fn)(void* arg, const char* file, size_t length);
/*! Object deallocation function prototype, used to deallocate an object of a specific type
\param object Object pointer */
diff --git a/external/imgui/CMakeLists.txt b/external/imgui/CMakeLists.txt
index 35381b2..2112f3b 100644
--- a/external/imgui/CMakeLists.txt
+++ b/external/imgui/CMakeLists.txt
@@ -18,6 +18,13 @@ source_group("includes" FILES ${imgui_includes})
# Add _LIB only for static library
add_compile_definitions(_LIB)
+if (BUILD_ENABLE_PLUGINS)
+ if (MSVC)
+ # Define IMGUI_API to export symbols
+ add_compile_definitions("IMGUI_API=__declspec(dllexport)")
+ endif()
+endif()
+
# Use compiler flags from parent project
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
diff --git a/external/imgui/imconfig.h b/external/imgui/imconfig.h
index 8c10190..4a9fd70 100644
--- a/external/imgui/imconfig.h
+++ b/external/imgui/imconfig.h
@@ -14,10 +14,22 @@
#pragma once
+#include
+
+// ###############################################################################
+#include "IconsMaterialDesign.h"
+#define THIN_SPACE "\xe2\x80\x89" // U+2009
+// ###############################################################################
+
//---- Define assertion handler. Defaults to calling assert().
// If your macro uses multiple statements, make sure is enclosed in a 'do { .. } while (0)' block so it can be used as a single statement.
//#define IM_ASSERT(_EXPR) MyAssert(_EXPR)
-//#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
+
+#ifdef NDEBUG
+#define IM_ASSERT(_EXPR) ((void)(_EXPR)) // Disable asserts
+#else
+#define IM_ASSERT(_EXPR) FOUNDATION_ASSERT(_EXPR)
+#endif
//---- Define attributes of all API symbols declarations, e.g. for DLL under Windows
// Using Dear ImGui via a shared library is not recommended, because of function call overhead and because we don't guarantee backward nor forward ABI compatibility.
diff --git a/external/imgui/imgui.h b/external/imgui/imgui.h
index 529c4f5..224cab9 100644
--- a/external/imgui/imgui.h
+++ b/external/imgui/imgui.h
@@ -54,10 +54,6 @@ Index of this file:
#include IMGUI_USER_CONFIG
#endif
#include "imconfig.h"
-// ###############################################################################
-#include "IconsMaterialDesign.h"
-#define THIN_SPACE "\xe2\x80\x89" // U+2009
-// ###############################################################################
#ifndef IMGUI_DISABLE
diff --git a/framework/CMakeLists.txt b/framework/CMakeLists.txt
index 0534ceb..d47271c 100644
--- a/framework/CMakeLists.txt
+++ b/framework/CMakeLists.txt
@@ -21,11 +21,19 @@ file(GLOB FRAMEWORK_SOURCES
)
source_group("framework" FILES ${FRAMEWORK_SOURCES})
+if (BUILD_ENABLE_PLUGINS)
+ # Load API source filess
+ file(GLOB API_FILES ${PLUGINS_DIR}/api/*.h)
+ source_group("api" FILES ${API_FILES})
+
+ # Add main.c explicitly to the framework library if plugins are enabled
+ # This is because the foundation_lib excludes it if built with BUILD_DYNAMIC_LINK=1 to export symbols
+ list(APPEND FRAMEWORK_SOURCES ${ROOT_DIR}/external/foundation/main.c)
+endif()
+
# Add *.mm files for OSX
if (APPLE)
- file(GLOB FRAMEWORK_SOURCES_OSX
- ${FRAMEWORK_DIR}/*.mm
- )
+ file(GLOB FRAMEWORK_SOURCES_OSX ${FRAMEWORK_DIR}/*.mm)
list(APPEND FRAMEWORK_SOURCES ${FRAMEWORK_SOURCES_OSX})
endif()
@@ -105,8 +113,6 @@ endif()
# Use compiler flags from parent project
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
-
-# Use compiler flags from parent project
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
# Add sources include directories
@@ -115,6 +121,7 @@ include_directories(${ROOT_DIR}/sources)
# Create the application library
add_library(framework STATIC
+ ${API_FILES}
${FRAMEWORK_SOURCES}
${TEST_SOURCES}
${EXTERNAL_SOURCES}
@@ -167,6 +174,16 @@ elseif(APPLE)
# Enable openmp support
target_compile_definitions(framework PUBLIC _OPENMP)
+ # Ignore -Wshorten-64-to-32
+ target_compile_options(framework PUBLIC -Wno-shorten-64-to-32)
+
+ # Ignore -Wunused-value
+ target_compile_options(framework PUBLIC -Wno-unused-value)
+
+ # Ignore -Wformat-security
+ target_compile_options(framework PUBLIC -Wno-format)
+ target_compile_options(framework PUBLIC -Wno-format-security)
+
endif()
#
diff --git a/framework/app.cpp b/framework/app.cpp
index ede632e..1d8334d 100644
--- a/framework/app.cpp
+++ b/framework/app.cpp
@@ -5,7 +5,6 @@
* This module contains application framework specific code.
* It is expected that the project sources also includes an app.cpp and defines the following functions:
* extern const char* app_title()
- * extern void app_exception_handler(const char* dump_file, size_t length)
* extern void app_initialize()
* extern void app_shutdown()
* extern void app_update()
@@ -15,6 +14,7 @@
#include "app.h"
#include "version.h"
+#include
#include
#include
#include
@@ -22,11 +22,13 @@
#include
#include
#include
+#include
#include
#include
#include
#include
+#include
#define HASH_APP static_hash_string("app", 3, 0x6ced59ff7a1fae4bULL)
@@ -64,6 +66,8 @@ struct app_menu_t
static app_menu_t* _menus = nullptr;
static app_dialog_t* _dialogs = nullptr;
+static app_callback_t* _render_lib_callbacks = nullptr;
+
//
// # PRIVATE
//
@@ -295,13 +299,90 @@ FOUNDATION_STATIC void app_dialogs_render()
}
ImGui::End();
}
+
+ plugin_render();
+}
+
+/*! Render leading menus.
+ *
+ * @param window The GLFW window.
+ */
+FOUNDATION_STATIC void app_main_menu_begin(GLFWwindow* window)
+{
+ if (!ImGui::BeginMenuBar())
+ return;
+
+ if (ImGui::TrBeginMenu("File"))
+ {
+ if (ImGui::TrBeginMenu("Create"))
+ {
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::TrBeginMenu("Open"))
+ {
+ ImGui::EndMenu();
+ }
+
+ ImGui::Separator();
+ if (ImGui::TrMenuItem(ICON_MD_EXIT_TO_APP " Exit", "Alt+F4"))
+ glfw_request_close_window(window);
+
+ ImGui::EndMenu();
+ }
+
+ ImGui::EndMenuBar();
+
+ // Let the framework inject some menus.
+ app_menu_begin(window);
+}
+
+/*! Render trailing menus.
+ *
+ * In between leading and trailing menus the framework usually
+ * injects additional menus through registered modules.
+ *
+ * @param window The GLFW window.
+ */
+FOUNDATION_STATIC void app_main_menu_end(GLFWwindow* window)
+{
+ // Let registered module inject some menus.
+ module_foreach_menu();
+
+ if (ImGui::BeginMenuBar())
+ {
+ if (ImGui::TrBeginMenu("Windows"))
+ ImGui::EndMenu();
+
+ app_menu_help(window);
+
+ // Update special application menu status.
+ // Usually controls are displayed at the far right of the menu.
+ profiler_menu_timer();
+ module_foreach_menu_status();
+
+ ImGui::EndMenuBar();
+ }
+
+ app_menu_end(window);
}
//
// # PUPLIC API
//
-void app_open_dialog(const char* title, const app_dialog_handler_t& handler, uint32_t width, uint32_t height, bool can_resize, void* user_data, const app_dialog_close_handler_t& close_handler)
+void app_exception_handler(void* context, const char* dump_file, size_t length)
+{
+ FOUNDATION_UNUSED(context);
+ FOUNDATION_UNUSED(dump_file);
+ FOUNDATION_UNUSED(length);
+ log_error(0, ERROR_EXCEPTION, STRING_CONST("Unhandled exception"));
+ process_exit(-1);
+}
+
+void app_open_dialog(const char* title, const app_dialog_handler_t& handler,
+ uint32_t width, uint32_t height, bool can_resize,
+ void* user_data, const app_dialog_close_handler_t& close_handler)
{
FOUNDATION_ASSERT(handler);
@@ -545,14 +626,100 @@ void app_menu_help(GLFWwindow* window)
ImGui::EndMenu();
}
+void app_render_default(GLFWwindow* window, int frame_width, int frame_height,
+ int& current_tab,
+ void(*default_tab)(),
+ void(*settings_draw)())
+{
+ ImGui::SetNextWindowPos(ImVec2(0, 0));
+ ImGui::SetNextWindowSize(ImVec2((float)frame_width, (float)frame_height));
+
+ if (ImGui::Begin(app_title(), nullptr,
+ ImGuiWindowFlags_NoBringToFrontOnFocus |
+ ImGuiWindowFlags_NoResize |
+ ImGuiWindowFlags_NoMove |
+ ImGuiWindowFlags_NoCollapse |
+ ImGuiWindowFlags_NoTitleBar |
+ ImGuiWindowFlags_MenuBar))
+ {
+ // Render main menus
+ app_main_menu_begin(window);
+
+ // Render document tabs
+ static ImGuiTabBarFlags tabs_init_flags = ImGuiTabBarFlags_Reorderable;
+ if (tabs_begin("Tabs", current_tab, tabs_init_flags, nullptr))
+ {
+ // Render the settings tab
+ tab_set_color(TAB_COLOR_APP_3D);
+ tab_draw(tr(ICON_MD_HEXAGON " Example "), nullptr, 0, default_tab);
+
+ // Render module registered tabs
+ module_foreach_tabs();
+
+ if (settings_draw)
+ {
+ // Render the settings tab
+ tab_set_color(TAB_COLOR_SETTINGS);
+ tab_draw(tr(ICON_MD_SETTINGS " Settings ##Settings"), nullptr,
+ ImGuiTabItemFlags_NoPushId | ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoReorder, settings_draw);
+ }
+
+ // We differ setting ImGuiTabBarFlags_AutoSelectNewTabs until after the first frame,
+ // since we manually select the first tab in the list using the user session data.
+ if ((tabs_init_flags & ImGuiTabBarFlags_AutoSelectNewTabs) == 0)
+ tabs_init_flags |= ImGuiTabBarFlags_AutoSelectNewTabs;
+
+ tabs_end();
+ }
+
+ // Render trailing menus
+ app_main_menu_end(window);
+
+ // Render main dialog and floating windows
+ module_foreach_window();
+
+ } ImGui::End();
+}
+
+void app_update_default(GLFWwindow* window)
+{
+ module_update();
+ plugin_update();
+}
+
+void app_add_render_lib_callback(void(*handler)(), void* user_data)
+{
+ app_callback_t callback;
+ callback.handler = handler;
+ callback.user_data = user_data;
+
+ array_push(_render_lib_callbacks, callback);
+}
+
+void app_remove_render_lib_callback(void(*handler)())
+{
+ for (unsigned i = 0, end = array_size(_render_lib_callbacks); i < end; ++i)
+ {
+ if (_render_lib_callbacks[i].handler == handler)
+ {
+ array_erase(_render_lib_callbacks, i);
+ break;
+ }
+ }
+}
+
+void app_render_3rdparty_libs()
+{
+ for (unsigned i = 0, end = array_size(_render_lib_callbacks); i < end; ++i)
+ _render_lib_callbacks[i].handler();
+}
+
//
// # SERVICE
//
FOUNDATION_STATIC void app_framework_initialize()
{
- //system_add_menu_item("test");
-
module_register_window(HASH_APP, app_dialogs_render);
}
diff --git a/framework/app.h b/framework/app.h
index c9f92c6..37447a1 100644
--- a/framework/app.h
+++ b/framework/app.h
@@ -29,8 +29,18 @@ typedef function app_dialog_close_handler_t;
typedef function app_update_handler_t;
typedef function app_render_handler_t;
-/*! Set of flags used to customize the registration of a new menu item. */
-typedef enum class AppMenuFlags
+template
+struct app_callback_t
+{
+ Handler* handler;
+ void* user_data;
+ void* context;
+};
+
+/*! Set of flags used to customize the registration of a new menu item.
+ * @api enum app_menu_flags_t
+ */
+typedef enum class AppMenuFlags : uint32_t
{
None = 0,
@@ -39,9 +49,17 @@ typedef enum class AppMenuFlags
/*! Menu item defines a shortcut */
Shortcut = 1 << 1,
-} app_menu_flags_t;
+
+} app_menu_flags_t;
DEFINE_ENUM_FLAGS(AppMenuFlags);
+/*! Handles exception at the application level.
+ *
+ * @param dump_file The path to the dump file.
+ * @param length The length of the dump file.
+ */
+void app_exception_handler(void* context, const char* dump_file, size_t length);
+
#if defined(FRAMEWORK_APP_IMPLEMENTATION)
#include
@@ -51,16 +69,6 @@ DEFINE_ENUM_FLAGS(AppMenuFlags);
/*! Returns the application title. */
extern const char* app_title();
-/*! Renders application 3rdparty libs using ImGui. */
-extern void app_render_3rdparty_libs();
-
-/*! Handles exception at the application level.
- *
- * @param dump_file The path to the dump file.
- * @param length The length of the dump file.
- */
-extern void app_exception_handler(const char* dump_file, size_t length);
-
/*! Configure the application features and framework core services.
*
* @param config The configuration to be modified.
@@ -164,3 +172,46 @@ void app_register_menu(
* @param window The window to render the menu items for.
*/
void app_menu_help(GLFWwindow* window);
+
+/*! Default application render handler.
+ *
+ * The default application render draws a set of tabs at the top of the window, and
+ * renders the current tab. The tab set is rendered using ImGui.
+ *
+ * @param window The main window used to render the application (can be null)
+ * @param frame_width The width of the frame to be rendered.
+ * @param frame_height The height of the frame to be rendered.
+ * @param current_tab The current tab to be rendered.
+ * @param default_tab The default tab to be rendered.
+ * @param settings_draw The settings draw function to be called when the settings tab is selected.
+ *
+ * @remark This handler must be invoked explicitly from #app_render.
+ */
+void app_render_default(GLFWwindow* window, int frame_width, int frame_height,
+ int& current_tab,
+ void(*default_tab)(),
+ void(*settings_draw)() = nullptr);
+
+/*! Default application update handler.
+ * *
+ * @param window The main window used to render the application (can be null)
+ *
+ * @remark This handler must be invoked explicitly from #app_update.
+ */
+void app_update_default(GLFWwindow* window);
+
+/*! Register a callback to render 3rdparty libs information in the about window.
+ *
+ * @param handler The handler to be called when the application is rendering 3rdparty libs.
+ * @param user_data The user data to be passed to the handler.
+ */
+void app_add_render_lib_callback(void(*handler)(), void* user_data);
+
+/*! Unregister a callback to render 3rdparty libs information in the about window.
+ *
+ * @param handler The handler to be called when the application is rendering 3rdparty libs.
+ */
+void app_remove_render_lib_callback(app_event_handler_t handler);
+
+/*! Renders application 3rdparty libs using ImGui in the about window. */
+void app_render_3rdparty_libs();
diff --git a/framework/app.impl.inl b/framework/app.impl.inl
index 983a165..1ec4712 100644
--- a/framework/app.impl.inl
+++ b/framework/app.impl.inl
@@ -26,14 +26,6 @@ extern const char* app_title()
return PRODUCT_NAME;
}
-extern void app_exception_handler(const char* dump_file, size_t length)
-{
- FOUNDATION_UNUSED(dump_file);
- FOUNDATION_UNUSED(length);
- log_error(0, ERROR_EXCEPTION, STRING_CONST("Unhandled exception"));
- process_exit(-1);
-}
-
extern void app_configure(foundation_config_t& config, application_t& application)
{
application.flags = APPLICATION_GUI;
@@ -113,11 +105,6 @@ extern void app_render(GLFWwindow* window, int frame_width, int frame_height)
}
#endif
-extern void app_render_3rdparty_libs()
-{
-
-}
-
#if BUILD_TESTS
extern int main_tests(void* _context, GLFWwindow* window)
{
diff --git a/framework/common.h b/framework/common.h
index 05bb204..21b3d6c 100644
--- a/framework/common.h
+++ b/framework/common.h
@@ -40,21 +40,21 @@ constexpr double DNAN = __builtin_nan("0");
* @brief Defines bitwise operators for an enum class.
*/
#define DEFINE_ENUM_FLAGS(T) \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T operator~ (T a) { return static_cast(~(std::underlying_type_t)a); } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool operator!= (const T a, const std::underlying_type_t b) { return (std::underlying_type_t)a != b; } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool operator== (const T a, const std::underlying_type_t b) { return (std::underlying_type_t)a == b; } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool operator&& (const T a, const T b) { return (std::underlying_type_t)a != 0 && (std::underlying_type_t)b != 0; } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool operator&& (const T a, const bool b) { return (std::underlying_type_t)a != 0 && b; } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL constexpr T operator| (const T a, const T b) { return (T)((std::underlying_type_t)a | (std::underlying_type_t)b); } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T operator& (const T a, const T b) { return (T)((std::underlying_type_t)a & (std::underlying_type_t)b); } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T operator^ (const T a, const T b) { return (T)((std::underlying_type_t)a ^ (std::underlying_type_t)b); } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T& operator|= (T& a, const T b) { return (T&)((std::underlying_type_t&)a |= (std::underlying_type_t)b); } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T& operator&= (T& a, const T b) { return (T&)((std::underlying_type_t&)a &= (std::underlying_type_t)b); } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T& operator^= (T& a, const T b) { return (T&)((std::underlying_type_t&)a ^= (std::underlying_type_t)b); } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool test(const T a, const T b) { return (a & b) == b; } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool any(const T a, const T b) { return (a & b) != 0; } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool none(const T a, const T b) { return (a & b) == 0; } \
- FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL bool one(const T a, const T b) { const auto bits = ((std::underlying_type_t)a & (std::underlying_type_t)b); return bits && !(bits & (bits-1)); }
+ FOUNDATION_FORCEINLINE T operator~ (T a) { return static_cast(~(std::underlying_type_t)a); } \
+ FOUNDATION_FORCEINLINE bool operator!= (const T a, const std::underlying_type_t b) { return (std::underlying_type_t)a != b; } \
+ FOUNDATION_FORCEINLINE bool operator== (const T a, const std::underlying_type_t b) { return (std::underlying_type_t)a == b; } \
+ FOUNDATION_FORCEINLINE bool operator&& (const T a, const T b) { return (std::underlying_type_t)a != 0 && (std::underlying_type_t)b != 0; } \
+ FOUNDATION_FORCEINLINE bool operator&& (const T a, const bool b) { return (std::underlying_type_t)a != 0 && b; } \
+ FOUNDATION_FORCEINLINE constexpr T operator| (const T a, const T b) { return (T)((std::underlying_type_t)a | (std::underlying_type_t)b); } \
+ FOUNDATION_FORCEINLINE T operator& (const T a, const T b) { return (T)((std::underlying_type_t)a & (std::underlying_type_t)b); } \
+ FOUNDATION_FORCEINLINE T operator^ (const T a, const T b) { return (T)((std::underlying_type_t)a ^ (std::underlying_type_t)b); } \
+ FOUNDATION_FORCEINLINE T& operator|= (T& a, const T b) { return (T&)((std::underlying_type_t&)a |= (std::underlying_type_t)b); } \
+ FOUNDATION_FORCEINLINE T& operator&= (T& a, const T b) { return (T&)((std::underlying_type_t&)a &= (std::underlying_type_t)b); } \
+ FOUNDATION_FORCEINLINE T& operator^= (T& a, const T b) { return (T&)((std::underlying_type_t&)a ^= (std::underlying_type_t)b); } \
+ FOUNDATION_FORCEINLINE bool test(const T a, const T b) { return (a & b) == b; } \
+ FOUNDATION_FORCEINLINE bool any(const T a, const T b) { return (a & b) != 0; } \
+ FOUNDATION_FORCEINLINE bool none(const T a, const T b) { return (a & b) == 0; } \
+ FOUNDATION_FORCEINLINE bool one(const T a, const T b) { const auto bits = ((std::underlying_type_t)a & (std::underlying_type_t)b); return bits && !(bits & (bits-1)); }
////////////////////////////////////////////////////////////////////////////
// ## Generics
@@ -66,7 +66,7 @@ constexpr double DNAN = __builtin_nan("0");
*
* @return The minimal value.
*/
-template FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T min(T a, T b) { return (((a) < (b)) ? (a) : (b)); }
+template FOUNDATION_FORCEINLINE T min(T a, T b) { return (((a) < (b)) ? (a) : (b)); }
/*! Returns the maximal value of two values.
*
@@ -75,7 +75,7 @@ template FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T min(T a, T b)
*
* @return The maximal value.
*/
-template FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL T max(T a, T b) { return (((a) > (b)) ? (a) : (b)); }
+template FOUNDATION_FORCEINLINE T max(T a, T b) { return (((a) > (b)) ? (a) : (b)); }
////////////////////////////////////////////////////////////////////////////
// ## URLs
diff --git a/framework/dispatcher.cpp b/framework/dispatcher.cpp
index 6e0dfd3..deb0aec 100644
--- a/framework/dispatcher.cpp
+++ b/framework/dispatcher.cpp
@@ -70,7 +70,7 @@ static objectmap_t* _dispatcher_threads = nullptr;
// # PRIVATE
//
-FOUNDATION_EXTERN bool dispatcher_process_events()
+extern bool dispatcher_process_events()
{
PERFORMANCE_TRACKER("dispatcher_process_events");
diff --git a/framework/dispatcher.h b/framework/dispatcher.h
index 152c849..e493a0d 100644
--- a/framework/dispatcher.h
+++ b/framework/dispatcher.h
@@ -217,7 +217,7 @@ dispatcher_event_listener_id_t dispatcher_register_event_listener(
*
* @param event_name Name of the event, must be a static string literal, i.e. "RENDER_FRAME"
* @param event_name_length Length of the event name
- * @param callback Callback to be invoked when the event is triggered.
+ * @param callback Callback to be invoked when the event is triggered (#bool(const dispatcher_event_args_t& args))
* @param options Registration and event execution options
* @param user_data User data to be passed to the callback when invoked.
*
diff --git a/framework/localization.cpp b/framework/localization.cpp
index c57e5da..550b2c0 100644
--- a/framework/localization.cpp
+++ b/framework/localization.cpp
@@ -85,7 +85,7 @@ static struct LOCALIZATION_MODULE
} *_localization_module = nullptr;
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL int localization_string_locale_key_compare(const string_locale_t& lc, const hash_t& key)
+FOUNDATION_FORCEINLINE int localization_string_locale_key_compare(const string_locale_t& lc, const hash_t& key)
{
if (lc.key < key)
return -1;
diff --git a/framework/localization.h b/framework/localization.h
index b874c6e..6fee4ed 100644
--- a/framework/localization.h
+++ b/framework/localization.h
@@ -61,7 +61,7 @@ const char* tr_cstr(const char* str, size_t length = SIZE_MAX);
#define RTEXT(str) CTEXT(str)
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL const char* tr(const char* str, size_t length, bool literal = false)
+FOUNDATION_FORCEINLINE const char* tr(const char* str, size_t length, bool literal = false)
{
return string_const(str, length);
}
diff --git a/framework/memory.h b/framework/memory.h
index 111ae43..b1c300b 100644
--- a/framework/memory.h
+++ b/framework/memory.h
@@ -34,10 +34,12 @@ template using alias = T;
* @brief Deallocate memory for an object and invoking the destructor.
* @param ptr The pointer to the object to deallocate.
*/
-#define MEM_DELETE(ptr) { \
- ptr->~alias::type>(); \
- memory_deallocate(ptr); \
- ptr = nullptr; \
+#define MEM_DELETE(ptr) { \
+ if (ptr) { \
+ ptr->~alias::type>(); \
+ memory_deallocate(ptr); \
+ ptr = nullptr; \
+ } \
}
/*! @def MEM_DELETE_ARRAY
diff --git a/framework/plugin.cpp b/framework/plugin.cpp
new file mode 100644
index 0000000..1afa76b
--- /dev/null
+++ b/framework/plugin.cpp
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2023 - All rights reserved.
+ * License: https://equals-forty-two.com/LICENSE
+ *
+ * Plugin Implementation
+ */
+
+#include "plugin.h"
+
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#define HASH_PLUGIN static_hash_string("plugin", 6, 0xcbbfaea91dab646ULL)
+
+struct plugin_t
+{
+ object_t handle{ 0 };
+ api_interface_t* api{ nullptr };
+
+ load_plugin_api_fn load{ nullptr };
+ unload_plugin_api_fn unload{ nullptr };
+
+ api_plugin_context_t context;
+};
+
+static plugin_t* _plugins = nullptr;
+
+FOUNDATION_FORCEINLINE plugin_t* plugin_get(plugin_handle_t handle)
+{
+ FOUNDATION_ASSERT(handle.index < array_size(_plugins));
+ return &_plugins[handle.index];
+}
+
+FOUNDATION_FORCEINLINE plugin_t* plugin_resolve(plugin_handle_t handle)
+{
+ if (handle.index >= array_size(_plugins))
+ return nullptr;
+ return &_plugins[handle.index];
+}
+
+template
+FOUNDATION_FORCEINLINE void plugin_invoke_callback(plugin_t* plugin, const char* name, size_t length, Handler* handler, void* user_data = nullptr)
+{
+ app_callback_t cb;
+ cb.handler = handler;
+ cb.user_data = user_data;
+ cb.context = plugin;
+ exception_try([](void* context)
+ {
+ auto* cb = (app_callback_t*)context;
+
+ if constexpr (std::is_same_vhandler), void (*)()>)
+ {
+ cb->handler();
+ }
+ else
+ {
+ cb->handler(cb->user_data);
+ }
+ return 0;
+ }, &cb, [](void* context, const char* file, size_t length)
+ {
+ auto* cb = (app_callback_t*)context;
+ plugin_t* plugin = (plugin_t*)cb->context;
+ if (plugin)
+ log_errorf(plugin->context.context, ERROR_EXCEPTION, STRING_CONST("Exception in %s plugin (%.*s)"), plugin->context.name, (int)length, file);
+ else
+ log_errorf(HASH_PLUGIN, ERROR_EXCEPTION, STRING_CONST("Exception in plugin (%.*s)"), (int)length, file);
+ }, name, length);
+}
+
+FOUNDATION_STATIC void app_initialize_apis(const api_plugin_context_t* context, api_interface_t** api)
+{
+ FOUNDATION_ASSERT(api);
+
+ plugin_t* plugin = plugin_get({context->reserved});
+ plugin->api = *api = (api_interface_t*)memory_allocate(HASH_PLUGIN, sizeof(api_interface_t), 8, MEMORY_PERSISTENT | MEMORY_ZERO_INITIALIZED);
+
+ //
+ // App API
+ //
+ static api_app_t app_api
+ {
+ // register_menu
+ [](const char* path, size_t path_length, const char* shortcut, size_t shortcut_length, api_app_menu_flags_t flags, void(*callback)(void*), void* user_data)
+ {
+ app_register_menu(HASH_PLUGIN, path, path_length, shortcut, shortcut_length, (app_menu_flags_t)flags, [callback](void* user_data)
+ {
+ plugin_invoke_callback(nullptr, STRING_CONST("plugin_register_menu"), callback, user_data);
+ }, user_data);
+ }
+ };
+ FOUNDATION_ASSERT(AppMenuFlags::None == APP_MENU_NONE);
+ FOUNDATION_ASSERT(AppMenuFlags::Append == APP_MENU_APPEND);
+ FOUNDATION_ASSERT(AppMenuFlags::Shortcut == APP_MENU_SHORTCUT);
+
+ //
+ // Log API
+ //
+ static api_log_t log_api
+ {
+ // info
+ [](hash_t context, const char* fmt, size_t length, ...)
+ {
+ va_list args;
+ va_start(args, length);
+
+ string_t str = string_allocate_vformat(fmt, length, args);
+ log_info(context, STRING_ARGS(str));
+ string_deallocate(str.str);
+ va_end(args);
+ },
+
+ // warning
+ [](hash_t context, const char* fmt, size_t length, ...)
+ {
+ va_list args;
+ va_start(args, length);
+ string_t str = string_allocate_vformat(fmt, length, args);
+ log_warn(context, WARNING_SCRIPT, STRING_ARGS(str));
+ string_deallocate(str.str);
+ va_end(args);
+ },
+
+ // error
+ [](hash_t context, uint32_t err, const char* fmt, size_t length, ...)
+ {
+ va_list args;
+ va_start(args, length);
+ string_t str = string_allocate_vformat(fmt, length, args);
+ log_error(context, (error_t)err, STRING_ARGS(str));
+ string_deallocate(str.str);
+ va_end(args);
+ },
+ };
+
+ //
+ // Dispatcher API
+ //
+ static api_dispatcher_t dispatcher_api
+ {
+ // register_event
+ [](const char* event_name, size_t event_name_length, bool(*callback)(void* payload, size_t payload_size, void* user_data), void* user_data)
+ {
+ return dispatcher_register_event_listener(event_name, event_name_length, [callback](const dispatcher_event_args_t& args)
+ {
+ return callback(args.data, args.size, args.user_data);
+ }, DISPATCHER_EVENT_OPTION_NONE, user_data);
+ },
+
+ // unregister_event
+ [](uint32_t register_event_id)
+ {
+ return dispatcher_unregister_event_listener(register_event_id);
+ },
+
+ // post_event
+ [](const char* event_name, size_t event_name_length, void* payload, size_t payload_size)
+ {
+ return dispatcher_post_event(event_name, event_name_length, payload,
+ payload_size, payload_size > 0 ? DISPATCHER_EVENT_OPTION_COPY_DATA : DISPATCHER_EVENT_OPTION_NONE);
+ },
+
+ // call_delayed
+ [](void(*callback)(void* user_data), void* user_data)
+ {
+ if (user_data)
+ dispatch([callback, user_data]() { callback(user_data); });
+ else
+ dispatch([callback]() { callback(nullptr); });
+ },
+ };
+
+ (*api)->app = &app_api;
+ (*api)->log = &log_api;
+ (*api)->dispatcher = &dispatcher_api;
+}
+
+//
+// PUBLIC
+//
+
+plugin_handle_t plugin_load(const char* path, size_t length)
+{
+ plugin_t plugin{};
+ string_const_t plugin_file_name = path_file_name(path, length);
+
+ plugin.handle = library_load(path, length);
+ if (plugin.handle == 0)
+ {
+ log_warnf(HASH_PLUGIN, WARNING_SUSPICIOUS, STRING_CONST("Unable to load plugin: %.*s"), STRING_FORMAT(plugin_file_name));
+ return { PLUGIN_HANDLE_INVALID };
+ }
+
+ // Make sure the dll has a valid plugin interface (i.e. `load_plugin(...)`)
+ plugin.load = (load_plugin_api_fn)library_symbol(plugin.handle, STRING_CONST("load_plugin"));
+ plugin.unload = (unload_plugin_api_fn)library_symbol(plugin.handle, STRING_CONST("unload_plugin"));
+ if (plugin.load == nullptr)
+ {
+ log_warnf(HASH_PLUGIN, WARNING_SUSPICIOUS, STRING_CONST("Unable to initialize plugin: %.*s"), STRING_FORMAT(plugin_file_name));
+ library_release(plugin.handle);
+ return { PLUGIN_HANDLE_INVALID };
+ }
+
+ plugin_handle_t handle = { array_size(_plugins) };
+ plugin.context.reserved = handle.index;
+ plugin.context.initialize_apis = app_initialize_apis;
+
+ string_const_t plugin_base_name = path_base_file_name(STRING_ARGS(plugin_file_name));
+ string_copy(STRING_BUFFER(plugin.context.name), STRING_ARGS(plugin_base_name));
+
+ array_push(_plugins, plugin);
+ int exit_code = plugin.load(&plugin.context);
+ if (exit_code != 0)
+ {
+ log_warnf(HASH_PLUGIN, WARNING_SUSPICIOUS, STRING_CONST("Unable to initialize plugin: %.*s (%d)"), STRING_FORMAT(plugin_file_name), exit_code);
+ library_release(plugin.handle);
+ array_pop_safe(_plugins);
+ return { PLUGIN_HANDLE_INVALID };
+ }
+
+ _plugins[handle.index].context = plugin.context;
+ log_infof(plugin.context.context, STRING_CONST("Loaded plugin: %s v.%d.%d"), plugin.context.name, plugin.context.version_major, plugin.context.version_minor);
+ return handle;
+}
+
+unsigned plugin_count()
+{
+ return array_size(_plugins);
+}
+
+void plugin_render()
+{
+ for (unsigned i = 0, end = array_size(_plugins); i < end; ++i)
+ {
+ plugin_t* plugin = _plugins + i;
+ if (plugin->context.callbacks.render)
+ plugin_invoke_callback(plugin, STRING_CONST("plugin_render"), plugin->context.callbacks.render);
+ }
+}
+
+void plugin_update()
+{
+ for (unsigned i = 0, end = array_size(_plugins); i < end; ++i)
+ {
+ plugin_t* plugin = _plugins + i;
+ if (plugin->context.callbacks.update)
+ plugin_invoke_callback(plugin, STRING_CONST("plugin_update"), plugin->context.callbacks.update);
+ }
+}
+
+void plugin_unload(plugin_handle_t handle)
+{
+ plugin_t* plugin = plugin_get(handle);
+ if (plugin->unload)
+ plugin->unload();
+
+ if (plugin->api)
+ memory_deallocate(plugin->api);
+
+ library_release(plugin->handle);
+}
+
+//
+// SYSTEM
+//
+
+FOUNDATION_STATIC void plugins_initialize()
+{
+ // Find plugin directory
+ char plugin_dir_buffer[BUILD_MAX_PATHLEN];
+ string_const_t resources_dir = environment_get_resources_path();
+ string_t plugin_dir = string_copy(plugin_dir_buffer, sizeof(plugin_dir_buffer), resources_dir.str, resources_dir.length);
+ plugin_dir = string_append(STRING_ARGS(plugin_dir), sizeof(plugin_dir_buffer), STRING_CONST("/plugins"));
+ if (!fs_is_directory(STRING_ARGS(plugin_dir)))
+ return;
+
+ // Load plugins (i.e. all DLLs in plugin directory)
+ string_t* plugin_file_names = fs_matching_files(STRING_ARGS(plugin_dir), STRING_CONST("^.*\\.dll$"), false);
+ for (unsigned i = 0, end = array_size(plugin_file_names); i < end; ++i)
+ {
+ const string_t& plugin_file_name = plugin_file_names[i];
+
+ char plugin_file_path_buffer[BUILD_MAX_PATHLEN];
+ string_t plugin_path = path_concat(STRING_BUFFER(plugin_file_path_buffer), STRING_ARGS(plugin_dir), STRING_ARGS(plugin_file_name));
+
+ plugin_load(STRING_ARGS(plugin_path));
+ }
+
+ string_array_deallocate(plugin_file_names);
+}
+
+FOUNDATION_STATIC void plugins_shutdown()
+{
+ for (unsigned i = 0, end = array_size(_plugins); i < end; ++i)
+ {
+ plugin_t* plugin = _plugins + i;
+ plugin_unload({i});
+ }
+ array_deallocate(_plugins);
+}
+
+DEFINE_MODULE(PLUGIN, plugins_initialize, plugins_shutdown, MODULE_PRIORITY_BASE);
diff --git a/framework/plugin.h b/framework/plugin.h
new file mode 100644
index 0000000..0e5d5cf
--- /dev/null
+++ b/framework/plugin.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2023 - All rights reserved.
+ * License: https://equals-forty-two.com/LICENSE
+ *
+ * Plugin interface
+ */
+
+#pragma once
+
+#include
+
+/*! Define an invalid plugin handle value. */
+constexpr const uint32_t PLUGIN_HANDLE_INVALID = UINT32_MAX;
+
+/*! Plugin handle. */
+struct plugin_handle_t { uint32_t index{ PLUGIN_HANDLE_INVALID }; };
+
+/*! Load a plugin shared library.
+ *
+ * @param path Path to plugin.
+ * @param length Length of path.
+ *
+ * @return Plugin handle if loaded successfully, otherwise PLUGIN_HANDLE_INVALID.
+ */
+plugin_handle_t plugin_load(const char* path, size_t length);
+
+/*! Unload plugin shared library.
+ *
+ * @param plugin Plugin handle.
+ */
+void plugin_unload(plugin_handle_t plugin);
+
+/*! Returns how many plugins are loaded.
+ *
+ * @return Number of plugins.
+ */
+unsigned plugin_count();
+
+/*! Application entry point to lets plugin render any ImGui windows.
+ */
+void plugin_render();
+
+/*! Application entry point to let plugins update their internal state.
+ */
+void plugin_update();
diff --git a/framework/string.cpp b/framework/string.cpp
index 27eb042..3176ae8 100644
--- a/framework/string.cpp
+++ b/framework/string.cpp
@@ -2149,7 +2149,7 @@ bool string_try_convert_number(const char* str, size_t length, double& out_value
bool string_try_convert_number(const char* str, size_t length, int& out_value, const int radix /*= 10*/)
{
// Make sure the string starts with a digit or a sign
- if (length == 0 || !isdigit(str[0]) && str[0] != '-')
+ if (length == 0 || (!isdigit(str[0]) && str[0] != '-'))
return false;
char* end = (char*)str + length;
diff --git a/framework/string_template.inl.h b/framework/string_template.inl.h
index a0c17a4..941e89c 100644
--- a/framework/string_template.inl.h
+++ b/framework/string_template.inl.h
@@ -169,41 +169,43 @@ struct string_template_arg_value_t
string_template_stream_handler_t stream;
};
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(bool t) { return StringArgumentType::BOOL; }
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(int t, std::true_type) { return StringArgumentType::INT32; }
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(unsigned int t, std::true_type) { return StringArgumentType::UINT32; }
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(int64_t t, std::true_type) { return StringArgumentType::INT64; }
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(uint64_t t, std::true_type) { return StringArgumentType::UINT64; }
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(float t, std::true_type) { return StringArgumentType::FLOAT; }
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(double t, std::false_type) { return StringArgumentType::DOUBLE; }
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(const char* t, std::false_type) { return StringArgumentType::CSTRING; }
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(std::nullptr_t t, std::false_type) { return StringArgumentType::POINTER; }
-
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(const string_t& t)
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(bool t) { return StringArgumentType::BOOL; }
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(int t, std::true_type) { return StringArgumentType::INT32; }
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(long t, std::true_type) { return StringArgumentType::INT32; }
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(unsigned int t, std::true_type) { return StringArgumentType::UINT32; }
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(unsigned long t, std::true_type) { return StringArgumentType::UINT32; }
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(int64_t t, std::true_type) { return StringArgumentType::INT64; }
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(uint64_t t, std::true_type) { return StringArgumentType::UINT64; }
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(float t, std::true_type) { return StringArgumentType::FLOAT; }
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(double t, std::false_type) { return StringArgumentType::DOUBLE; }
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(const char* t, std::false_type) { return StringArgumentType::CSTRING; }
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(std::nullptr_t t, std::false_type) { return StringArgumentType::POINTER; }
+
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(const string_t& t)
{
return StringArgumentType::STRING;
}
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(const string_const_t& t)
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(const string_const_t& t)
{
return StringArgumentType::STRING;
}
template
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(T* arr, std::false_type)
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(T* arr, std::false_type)
{
return StringArgumentType::POINTER;
}
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(string_template_stream_handler_t func)
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(string_template_stream_handler_t func)
{
return StringArgumentType::STREAM;
}
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(int* arr) { return StringArgumentType::ARRAY_INT; }
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(int* arr) { return StringArgumentType::ARRAY_INT; }
template
-FOUNDATION_FORCEINLINE FOUNDATION_CONSTCALL string_argument_type_t string_template_type(const T& t)
+FOUNDATION_FORCEINLINE string_argument_type_t string_template_type(const T& t)
{
return string_template_type(t, typename std::is_integral::type());
}
@@ -234,7 +236,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N],
template
FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1)
{
- string_t buffer = string_static_buffer(max(SIZE_C(64), SIZE_C(N + 32)));
+ string_t buffer = string_static_buffer((size_t)max(SIZE_C(64), SIZE_C(N + 32)), false);
return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1));
}
@@ -260,7 +262,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N],
template
FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2)
{
- string_t buffer = string_static_buffer(max(SIZE_C(64), SIZE_C(N + 32 * 2)));
+ string_t buffer = string_static_buffer(max(SIZE_C(64), SIZE_C(N + 32 * 2)), false);
return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1, string_template_type(p2), p2));
}
@@ -286,7 +288,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N],
template
FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3)
{
- string_t buffer = string_static_buffer(max(SIZE_C(64), SIZE_C(N + 32 * 3)));
+ string_t buffer = string_static_buffer(max(SIZE_C(64), SIZE_C(N + 32 * 3)), false);
return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1, string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3));
}
@@ -315,7 +317,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N],
template
FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4)
{
- string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 4)));
+ string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 4)), false);
return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1,
string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4));
}
@@ -345,7 +347,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N],
template
FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5)
{
- string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 5)));
+ string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 5)), false);
return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1,
string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4, string_template_type(p5), p5));
}
@@ -377,7 +379,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N],
template
FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6)
{
- string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 6)));
+ string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 6)), false);
return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1,
string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3,
string_template_type(p4), p4, string_template_type(p5), p5, string_template_type(p6), p6));
@@ -411,7 +413,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N],
template
FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6, const P7& p7)
{
- string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 7)));
+ string_t buffer = string_static_buffer(max(SIZE_C(128), SIZE_C(N + 32 * 7)), false);
return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1,
string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4,
string_template_type(p5), p5, string_template_type(p6), p6, string_template_type(p7), p7));
@@ -445,7 +447,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N],
template
FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6, const P7& p7, const P8& p8)
{
- string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 8)));
+ string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 8)), false);
return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1,
string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4,
string_template_type(p5), p5, string_template_type(p6), p6, string_template_type(p7), p7, string_template_type(p8), p8));
@@ -479,7 +481,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N],
template
FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6, const P7& p7, const P8& p8, const P9& p9)
{
- string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 9)));
+ string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 9)), false);
return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1,
string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4,
string_template_type(p5), p5, string_template_type(p6), p6, string_template_type(p7), p7, string_template_type(p8), p8, string_template_type(p9), p9));
@@ -515,7 +517,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N],
template
FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6, const P7& p7, const P8& p8, const P9& p9, const P10& p10)
{
- string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 10)));
+ string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 10)), false);
return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1,
string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4,
string_template_type(p5), p5, string_template_type(p6), p6, string_template_type(p7), p7, string_template_type(p8), p8,
@@ -552,7 +554,7 @@ FOUNDATION_FORCEINLINE string_t string_allocate_template(const char(&format)[N],
template
FOUNDATION_FORCEINLINE string_const_t string_template_static(const char(&format)[N], const P1& p1, const P2& p2, const P3& p3, const P4& p4, const P5& p5, const P6& p6, const P7& p7, const P8& p8, const P9& p9, const P10& p10, const P11& p11)
{
- string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 11)));
+ string_t buffer = string_static_buffer(max(SIZE_C(256), SIZE_C(N + 32 * 11)), false);
return string_to_const(string_format_template(buffer.str, buffer.length, format, N - 1,
string_template_type(p1), p1, string_template_type(p2), p2, string_template_type(p3), p3, string_template_type(p4), p4, string_template_type(p5), p5,
string_template_type(p6), p6, string_template_type(p7), p7, string_template_type(p8), p8, string_template_type(p9), p9, string_template_type(p10), p10,
diff --git a/framework/system.mm b/framework/system.mm
index be7156a..6646ce2 100644
--- a/framework/system.mm
+++ b/framework/system.mm
@@ -24,14 +24,17 @@ bool system_open_file_dialog(const char* dialog_title,
if (!app_window)
return false;
+ string_t file_path = {};
string_t file_path_buffer = string_static_buffer(1024, true);
if (current_file_path != nullptr)
{
- string_t file_path = string_format(STRING_ARGS(file_path_buffer), STRING_CONST("%s"), current_file_path);
+ file_path = string_format(STRING_ARGS(file_path_buffer), STRING_CONST("%s"), current_file_path);
file_path = path_clean(STRING_ARGS(file_path), file_path_buffer.length);
file_path = string_replace(STRING_ARGS(file_path), file_path_buffer.length,
STRING_CONST("/"), STRING_CONST("\\"), true);
}
+
+ string_const_t filename = path_file_name(STRING_ARGS(file_path));
@autoreleasepool
{
@@ -45,7 +48,51 @@ bool system_open_file_dialog(const char* dialog_title,
openPanel.canCreateDirectories = YES;
openPanel.allowsMultipleSelection = NO;
- openPanel.allowedFileTypes = @[@"dcm"];
+ if (extension)
+ {
+ // Split extension string into array
+ string_t* exts = string_split(string_to_const(extension), CTEXT("|"));
+
+ // Add extension to savePanel.allowedFileTypes
+ for (unsigned int i = 0; i < array_size(exts); ++i)
+ {
+ // Skip description
+ if (i % 2 == 0)
+ continue;
+ string_t ext = exts[i];
+ // Remove *. from extension
+ ext.str += 2;
+ ext.length -= 2;
+
+ NSString* ns_ext = [NSString stringWithUTF8String:ext.str];
+ [openPanel setAllowedFileTypes:[openPanel.allowedFileTypes arrayByAddingObject:ns_ext]];
+
+ // Add default extension to savePanel.nameFieldStringValue
+ if (filename.length != 0 && string_find(STRING_ARGS(filename), '.', 0))
+ {
+ NSString* ns_filename = [NSString stringWithUTF8String:filename.str];
+ openPanel.nameFieldStringValue = ns_filename;
+
+ // Append . to filename
+ ns_filename = [ns_filename stringByAppendingString:@"."];
+
+ // Add .extension to filename
+ NSString* ns_ext = [NSString stringWithUTF8String:ext.str];
+ openPanel.nameFieldStringValue = [ns_filename stringByAppendingString:ns_ext];
+ }
+ }
+
+ string_array_deallocate(exts);
+
+ openPanel.allowsOtherFileTypes = NO;
+ }
+ else
+ {
+ openPanel.allowsOtherFileTypes = YES;
+ }
+
+ // Create a local copy of the callback for the async call to the save panel
+ function selected_file_callback_copy = selected_file_callback;
[openPanel beginSheetModalForWindow: app_window completionHandler:^(NSInteger result)
{
@@ -60,7 +107,113 @@ bool system_open_file_dialog(const char* dialog_title,
static char selected_file_path_buffer[BUILD_MAX_PATHLEN];
string_t selected_file_path = string_copy(STRING_BUFFER(selected_file_path_buffer), path.UTF8String, path.length);
- selected_file_callback(string_to_const(selected_file_path));
+ selected_file_callback_copy(string_to_const(selected_file_path));
+ }
+ }];
+ }
+
+ return true;
+}
+
+bool system_save_file_dialog(
+ const char* dialog_title,
+ const char* extension,
+ const char* current_file_path,
+ const function& selected_file_callback)
+{
+ NSWindow* app_window = (NSWindow*)_window_handle;
+ if (!app_window)
+ return false;
+
+ string_t file_path = {};
+ string_t file_path_buffer = string_static_buffer(1024, true);
+ if (current_file_path != nullptr)
+ {
+ file_path = string_format(STRING_ARGS(file_path_buffer), STRING_CONST("%s"), current_file_path);
+ file_path = path_clean(STRING_ARGS(file_path), file_path_buffer.length);
+ file_path = string_replace(STRING_ARGS(file_path), file_path_buffer.length,
+ STRING_CONST("/"), STRING_CONST("\\"), true);
+ }
+
+ string_const_t filename = path_file_name(STRING_ARGS(file_path));
+
+ @autoreleasepool
+ {
+ // Open save dialog
+ NSSavePanel* savePanel = [NSSavePanel savePanel];
+
+ savePanel.title = [NSString stringWithUTF8String:dialog_title];
+
+ savePanel.showsHiddenFiles = NO;
+ savePanel.showsResizeIndicator = YES;
+ savePanel.canCreateDirectories = YES;
+
+ if (filename.length > 0)
+ {
+ NSString* ns_filename = [NSString stringWithUTF8String:filename.str];
+ savePanel.nameFieldStringValue = ns_filename;
+ }
+
+ if (extension)
+ {
+ // Split extension string into array
+ string_t* exts = string_split(string_to_const(extension), CTEXT("|"));
+
+ // Add extension to savePanel.allowedFileTypes
+ for (unsigned int i = 0; i < array_size(exts); ++i)
+ {
+ // Skip description
+ if (i % 2 == 0)
+ continue;
+ string_t ext = exts[i];
+ // Remove *. from extension
+ ext.str += 2;
+ ext.length -= 2;
+
+ NSString* ns_ext = [NSString stringWithUTF8String:ext.str];
+ [savePanel setAllowedFileTypes:[savePanel.allowedFileTypes arrayByAddingObject:ns_ext]];
+
+ // Add default extension to savePanel.nameFieldStringValue
+ if (filename.length != 0 && string_find(STRING_ARGS(filename), '.', 0))
+ {
+ NSString* ns_filename = [NSString stringWithUTF8String:filename.str];
+ savePanel.nameFieldStringValue = ns_filename;
+
+ // Append . to filename
+ ns_filename = [ns_filename stringByAppendingString:@"."];
+
+ // Add .extension to filename
+ NSString* ns_ext = [NSString stringWithUTF8String:ext.str];
+ savePanel.nameFieldStringValue = [ns_filename stringByAppendingString:ns_ext];
+ }
+ }
+
+ string_array_deallocate(exts);
+
+ savePanel.allowsOtherFileTypes = NO;
+ }
+ else
+ {
+ savePanel.allowsOtherFileTypes = YES;
+ }
+
+ // Create a local copy of the callback for the async call to the save panel
+ function selected_file_callback_copy = selected_file_callback;
+
+ [savePanel beginSheetModalForWindow: app_window completionHandler:^(NSInteger result)
+ {
+ // If the result is NSOKButton the user selected a file
+ if (result == NSModalResponseOK)
+ {
+ // Get the save path file
+ NSURL *selection = savePanel.URL;
+
+ //finally store the selected file path as a string
+ NSString* path = [[selection path] stringByResolvingSymlinksInPath];
+
+ static char selected_file_path_buffer[BUILD_MAX_PATHLEN];
+ string_t selected_file_path = string_copy(STRING_BUFFER(selected_file_path_buffer), path.UTF8String, path.length);
+ selected_file_callback_copy(string_to_const(selected_file_path));
}
}];
}
diff --git a/framework/tests/dispatcher_tests.cpp b/framework/tests/dispatcher_tests.cpp
index d9c1dc3..9edf4be 100644
--- a/framework/tests/dispatcher_tests.cpp
+++ b/framework/tests/dispatcher_tests.cpp
@@ -13,7 +13,7 @@
#include
-FOUNDATION_EXTERN bool dispatcher_process_events();
+extern bool dispatcher_process_events();
TEST_SUITE("Dispatcher")
{
diff --git a/framework/tests/expr_tests.cpp b/framework/tests/expr_tests.cpp
index 099612d..36218c6 100644
--- a/framework/tests/expr_tests.cpp
+++ b/framework/tests/expr_tests.cpp
@@ -53,7 +53,7 @@ template FOUNDATION_FORCEINLINE expr_result_t test_expr(const char(&ex
return result;
}
-template FOUNDATION_FORCEINLINE expr_result_t test_expr(const char(&expr)[N], int expected)
+template FOUNDATION_FORCEINLINE expr_result_t test_expr(const char(&expr)[N], int32_t expected)
{
expr_result_t result = eval({expr, N - 1});
@@ -150,9 +150,9 @@ TEST_SUITE("Expressions")
test_expr("3/2", 3.0 / 2.0);
test_expr("(3/2)|0", 3 / 2);
test_expr("(3/0)", INFINITY);
- test_expr("(3/0)|0", -2147483648i32);
+ test_expr("(3/0)|0", (int32_t)INT32_C(-2147483648));
test_expr("(3%0)", NAN);
- test_expr("(3%0)|0", -2147483648i32);
+ test_expr("(3%0)|0", (int32_t)INT32_C(-2147483648));
test_expr("2**3", 8);
test_expr("9**(1/2)", 3);
test_expr("1+2<<3", (1 + 2) << 3);
@@ -795,7 +795,7 @@ TEST_SUITE("Expressions")
expr_register_function("ptr_i64", [](const expr_func_t* f, vec_expr_t* args, void* c) -> expr_result_t
{
- static constexpr int64_t n[] = {INT64_MIN, INT64_MAX, 0i64};
+ static constexpr int64_t n[] = {INT64_MIN, INT64_MAX, (int64_t)0};
return expr_result_t((void*)&n, sizeof(n[0]), ARRAY_COUNT(n), EXPR_POINTER_ARRAY | EXPR_POINTER_ARRAY_INTEGER);
});
diff --git a/framework/window.cpp b/framework/window.cpp
index f1e6c03..00fa640 100644
--- a/framework/window.cpp
+++ b/framework/window.cpp
@@ -959,9 +959,9 @@ void window_update()
window_t* win = (window_t*)args;
window_render(win);
return 0;
- }, win, [](const char* file, size_t length)
+ }, win, [](void* context, const char* file, size_t length)
{
- log_errorf(HASH_WINDOW, ERROR_EXCEPTION, "Exception in window render: %.*s", (int)length, file);
+ log_errorf(HASH_WINDOW, ERROR_EXCEPTION, STRING_CONST("Exception in window render: %.*s"), (int)length, file);
}, STRING_CONST("window_dump"));
// Check if the window should be closed
diff --git a/plugins/api/api.h b/plugins/api/api.h
new file mode 100644
index 0000000..ea93c91
--- /dev/null
+++ b/plugins/api/api.h
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2023 - All rights reserved.
+ * License: https://equals-forty-two.com/LICENSE
+ *
+ * Framework API interface
+ */
+
+#pragma once
+
+#include
+
+#ifndef FOUNDATION_FORCEINLINE
+# define FOUNDATION_FORCEINLINE inline
+#endif
+
+typedef uint8_t byte_t;
+typedef uint64_t hash_t;
+typedef int64_t tick_t;
+typedef double deltatime_t;
+typedef uint32_t object_t;
+typedef uint128_t uuid_t;
+typedef void* user_data_t;
+
+constexpr object_t INVALID_HANDLE = UINT32_MAX;
+
+/*! Default API callback with user data context. */
+typedef void(*api_callback_t)(void* user_data);
+
+/*! API callback with payload and user data context. */
+typedef void(*api_callback_payload_t)(void* payload, size_t payload_size, void* user_data);
+
+/*! Set of flags used to customize the registration of a new menu item.
+ *
+ * Sync with #AppMenuFlags in app.h
+ */
+typedef enum : uint32_t
+{
+ APP_MENU_NONE = 0,
+
+ /*! Insert the menu items after all other menu items, this helps preserve the system menu order. */
+ APP_MENU_APPEND = 1 << 0,
+
+ /*! Menu item defines a shortcut */
+ APP_MENU_SHORTCUT = 1 << 1,
+
+} api_app_menu_flags_t;
+
+#ifndef BUILD_DYNAMIC_LINK
+# define BUILD_DYNAMIC_LINK 0
+#endif
+
+#ifndef FOUNDATION_PLATFORM_WINDOWS
+ #ifdef _WIN32
+ #define FOUNDATION_PLATFORM_WINDOWS 1
+ #else
+ #define FOUNDATION_PLATFORM_WINDOWS 0
+ #endif
+#endif
+
+#if BUILD_DYNAMIC_LINK && FOUNDATION_PLATFORM_WINDOWS
+#ifdef __cplusplus
+# define FRAMEWORK_API_EXPORT extern "C" __declspec(dllexport)
+#else
+# define FRAMEWORK_API_EXPORT __declspec(dllexport)
+#endif
+
+#else
+# define FRAMEWORK_API_EXPORT
+#endif
+
+#ifndef STRING_PARAM
+# define STRING_PARAM(__name) const char* __name, size_t __name##_length
+#endif
+
+#ifndef STRING_CONST
+# define STRING_CONST(__name) __name, sizeof(__name) - 1
+#endif
+
+struct api_app_t
+{
+ /*! Register a new menu item.
+ *
+ * @param path Path to the menu item, separated by slashes.
+ * @param shortcut Shortcut to the menu item, can be null.
+ * @param flags Flags to customize the registration of the menu item.
+ * @param callback Callback to invoke when the menu item is selected.
+ * @param user_data User data to pass to the callback.
+ */
+ void (*register_menu)(STRING_PARAM(path), STRING_PARAM(shortcut), api_app_menu_flags_t flags, api_callback_t callback, user_data_t user_data);
+};
+
+struct api_log_t
+{
+ /*! Log a message with the given context.
+ *
+ * @param context Context of the message.
+ * @param fmt Format string.
+ * @param length Length of the format string.
+ * @param ... Arguments to the format string.
+ */
+ void (*info)(hash_t context, const char* fmt, size_t length, ...);
+
+ /*! Log a warning message with the given context.
+ *
+ * @param context Context of the message.
+ * @param fmt Format string.
+ * @param length Length of the format string.
+ * @param ... Arguments to the format string.
+ */
+ void (*warning)(hash_t context, const char* fmt, size_t length, ...);
+
+ /*! Log an error message with the given context.
+ *
+ * @param context Context of the message.
+ * @param err Error code.
+ * @param fmt Format string.
+ * @param length Length of the format string.
+ * @param ... Arguments to the format string.
+ */
+ void (*error)(hash_t context, uint32_t err, const char* fmt, size_t length, ...);
+};
+
+struct api_dispatcher_t
+{
+ /*! Register a new event listener.
+ *
+ * @param event_name Name of the event.
+ * @param callback Callback to invoke when the event is posted.
+ * @param user_data User data to pass to the callback.
+ * @return Handle to the registered event.
+ */
+ object_t (*register_event)(STRING_PARAM(event_name), bool(*callback)(void* payload, size_t payload_size, user_data_t user_data), user_data_t user_data);
+
+ /*! Unregister an event listener.
+ *
+ * @param register_event_id Handle to the registered event.
+ * @return True if the event was unregistered, false otherwise.
+ */
+ bool (*unregister_event)(object_t register_event_id);
+
+ /*! Post an message event.
+ *
+ * @param event_name Name of the event.
+ * @param payload Payload to pass to the event listeners.
+ * @param payload_size Size of the payload.
+ * @return True if the event was posted, false otherwise.
+ */
+ bool (*post_event)(STRING_PARAM(event_name), void* payload, size_t payload_size);
+
+ /*! Dispatch a callback that will be invoked on the main thread.
+ *
+ * @param callback Callback to invoke.
+ * @param user_data User data to pass to the callback.
+ */
+ void (*call_delayed)(api_callback_t callback, user_data_t user_data);
+};
+
+struct api_system_t {};
+struct api_expression_t {};
+
+/*! Structure used to access the plugin API. */
+struct api_interface_t
+{
+ api_app_t* app{ nullptr };
+ api_log_t* log{ nullptr };
+ api_dispatcher_t* dispatcher{ nullptr };
+ api_expression_t* expression{ nullptr };
+ api_system_t* system{ nullptr };
+};
+
+/*! Structure used to register callbacks for the plugin. */
+struct api_plugin_callbacks_t
+{
+ void (*update)() { nullptr };
+ void (*render)() { nullptr };
+};
+
+struct api_plugin_context_t;
+
+/*! Plugin API initialization structure */
+typedef void (*initialize_apis_fn)(const api_plugin_context_t* context, api_interface_t** api);
+
+/*! Structure used to load and initialize a plugin.
+ The plugin must implement the load_plugin and unload_plugin functions.
+*/
+struct api_plugin_context_t
+{
+ /*! Set plugin context. This is used for logging and memory allocation. */
+ hash_t context;
+
+ /*! Display name of the plugin */
+ char name[32];
+
+ /*! Version major of the plugin */
+ int version_major;
+
+ /*! Version minor of the plugin */
+ int version_minor;
+
+ /*! The plugin can invoke this function to initialize the APIs it needs.
+ * The framework will fill all the requested APIs and the plugin can use them.
+ */
+ initialize_apis_fn initialize_apis;
+
+ /*! Plugin callbacks that the system invokes at certain times.
+ *
+ * The callbacks will mainly be used for realtime events. Otherwise you can use the dispatcher
+ * to post events and register listeners.
+ */
+ api_plugin_callbacks_t callbacks;
+
+ /*! Reserved for internal usage */
+ uint32_t reserved{ UINT32_MAX };
+};
+
+/*! Load plugin function
+ @param plugin_context Plugin API context
+ @return 0 if plugin loaded successfully, <0 if error occurred
+*/
+typedef int (*load_plugin_api_fn)(api_plugin_context_t* plugin_context);
+
+/*! Unload plugin function
+*/
+typedef int (*unload_plugin_api_fn)();
diff --git a/plugins/test1/CMakeLists.txt b/plugins/test1/CMakeLists.txt
new file mode 100644
index 0000000..3448ff3
--- /dev/null
+++ b/plugins/test1/CMakeLists.txt
@@ -0,0 +1,56 @@
+#
+# Copyright 2023 Wiimag Inc. All rights reserved.
+# License: https://equals-forty-two.com/LICENSE
+#
+# Framework plugin example CMakeLists.txt
+#
+
+set(PluginId "test1")
+
+# Include shared module
+include(${SHARED_MODULE_PATH})
+
+# Define project
+project(${PluginId})
+
+# Load api headers
+file(GLOB API_FILES ${PLUGINS_DIR}/api/*.h)
+source_group("api" FILES ${API_FILES})
+
+# Load plugin source files
+file(GLOB_RECURSE SOURCES_FILES
+ "${CMAKE_CURRENT_SOURCE_DIR}/*.h"
+ "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp")
+source_group("src" FILES ${SOURCES_FILES})
+
+if (MSVC)
+ add_compile_options("$<$:/Os>")
+
+ # Import IMGUI API if needed (Symbols are exported from the app.exe)
+ add_compile_definitions("IMGUI_API=__declspec(dllimport)")
+endif()
+
+# Set output directories
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PLUGIN_BUILD_DIR})
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PLUGIN_BUILD_DIR})
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEPLOY ${PLUGIN_BUILD_DIR})
+set(CMAKE_PDB_OUTPUT_DIRECTORY ${PLUGIN_BUILD_DIR})
+set(CMAKE_PDB_OUTPUT_DIRECTORY_DEBUG ${PLUGIN_BUILD_DIR})
+set(CMAKE_PDB_OUTPUT_DIRECTORY_RELEASE ${PLUGIN_BUILD_DIR})
+
+# Create shared library (dll)
+add_library(${PluginId} SHARED ${API_FILES} ${SOURCES_FILES})
+set_target_properties(${PluginId} PROPERTIES FOLDER "plugins")
+
+# Define foundation_lib properties
+target_include_directories(${PluginId} PUBLIC ${PLUGINS_DIR}/api)
+target_include_directories(${PluginId} PUBLIC ${ROOT_DIR}/external/)
+target_compile_definitions(${PluginId} PUBLIC -DBUILD_DYNAMIC_LINK=1)
+target_compile_definitions(${PluginId} PUBLIC -DFOUNDATION_SIZE_REAL=8)
+
+if (MSVC)
+ set_target_properties(${PluginId} PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS")
+endif()
+
+# Link with C:/work/framework/artifacts/lib/Debug/app.lib
+target_link_libraries(${PluginId} PRIVATE ${ProjectId})
diff --git a/plugins/test1/test1.cpp b/plugins/test1/test1.cpp
new file mode 100644
index 0000000..f37b689
--- /dev/null
+++ b/plugins/test1/test1.cpp
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2023 - All rights reserved.
+ * License: https://equals-forty-two.com/LICENSE
+ *
+ * Test1 plugin example implementation.
+ *
+ * Plugins can use the foundation_lib and/or IMGUI exported functions, but that is not mandatory.
+ */
+
+#include
+
+#include
+
+#include
+
+static int _update_counter = 0;
+static bool _window_opened = true;
+static api_interface_t* api = nullptr;
+
+#define HASH_PLUGIN_TEST1 static_hash_string("plugin_test1", 12, 0xc2f1f9448a0499bdULL)
+
+FOUNDATION_STATIC void test1_plugin_menu(void* context)
+{
+ FOUNDATION_UNUSED(context);
+ api->log->info(HASH_PLUGIN_TEST1, STRING_CONST("test1_plugin_menu"));
+ api->log->warning(HASH_PLUGIN_TEST1, STRING_CONST("test1_plugin_menu"));
+ api->log->error(HASH_PLUGIN_TEST1, ERROR_NONE, STRING_CONST("test1_plugin_menu"));
+}
+
+FOUNDATION_STATIC void test1_plugin_crash(void* context)
+{
+ int* p = (int*)context;
+ *p = 0;
+}
+
+FOUNDATION_STATIC void test1_plugin_update()
+{
+ ++_update_counter;
+}
+
+FOUNDATION_STATIC void test1_plugin_render()
+{
+ if (!_window_opened)
+ return;
+
+ if (!ImGui::Begin("Plugin test1", &_window_opened))
+ return ImGui::End();
+
+ ImGui::Text("Hello App, I am a plugin! (%d)", _update_counter);
+
+ if (ImGui::Button("Crash"))
+ test1_plugin_crash(nullptr);
+ ImGui::End();
+}
+
+FRAMEWORK_API_EXPORT int load_plugin(api_plugin_context_t* plugin)
+{
+ // Initialize plugin info
+ plugin->context = HASH_PLUGIN_TEST1; plugin->version_major = 1; plugin->version_minor = 0;
+ string_copy(STRING_BUFFER(plugin->name), STRING_CONST("test1"));
+
+ // Load API interface
+ plugin->initialize_apis(plugin, &api);
+
+ // Set some callbacks that the application will invoke on the plugin
+ plugin->callbacks.render = test1_plugin_render;
+ plugin->callbacks.update = test1_plugin_update;
+
+ // Use the API to do things
+ api->app->register_menu(STRING_CONST("Plugins/test1"), nullptr, 0, APP_MENU_NONE, test1_plugin_menu, nullptr);
+ api->app->register_menu(STRING_CONST("Plugins/crash"), nullptr, 0, APP_MENU_NONE, test1_plugin_crash, nullptr);
+
+ return 0;
+}
+
+FRAMEWORK_API_EXPORT void unload_plugin()
+{
+ api = nullptr;
+}
diff --git a/run b/run
index 70084fb..be78a12 100644
--- a/run
+++ b/run
@@ -28,33 +28,35 @@ FULL_SOLUTION_DIR=$(convert_path_to_platform $(pwd)/$SOLUTION_DIR)
#
# Parse common command line arguments
#
-POSITIONAL_ARGS=()
+unset POSITIONAL_ARGS
RUN=1 # Indicate if the default build should be run at the end
-BUILD=0
+BUILD=()
BUILD_DEBUG=0
BUILD_RELEASE=1
BUILD_DEPLOY=0
-TESTS=0
+TESTS=()
OPEN=0
OPEN_WORKSPACE=0
-GENERATE=0
-GENERATED=0
-PACKAGE=0
-START=0
-PRINT=0
+GENERATE=()
+PACKAGE=()
+START=()
+DIFF=()
+PRINT=()
PRINT_LARGEFILES=0
+
HELP=0
VERBOSE=0
-DIFF=0
NO_COLOR=0
+NO=0
+YES=1
COMMAND_COUNTER=0
# Check if project/.build is present, if not, then we need to generate the project
# Also check if the project/.build/CMakeCache.txt is present, if not, then we need to generate the project
if [ ! -d "$SOLUTION_DIR" ] || [ ! -f "$SOLUTION_DIR/CMakeCache.txt" ]; then
- BUILD=1
- GENERATE=1
+ BUILD=($YES)
+ GENERATE=($YES)
fi
while [[ $# -gt 0 ]]; do
@@ -66,7 +68,6 @@ while [[ $# -gt 0 ]]; do
;;
--verbose)
VERBOSE=1
- #POSITIONAL_ARGS+=("$1") # save positional arg
shift # past argument
;;
--no-color)
@@ -76,7 +77,8 @@ while [[ $# -gt 0 ]]; do
b|build)
# Update the command counter
COMMAND_COUNTER=$((COMMAND_COUNTER+1))
- BUILD=$COMMAND_COUNTER
+ BUILD=($YES)
+ declare -g -n POSITIONAL_ARGS=BUILD
shift # past argument
# The release argument is the default, but remove it if it was specified
@@ -98,7 +100,8 @@ while [[ $# -gt 0 ]]; do
;;
p|print)
COMMAND_COUNTER=$((COMMAND_COUNTER+1))
- PRINT=$COMMAND_COUNTER
+ PRINT=($YES)
+ declare -g -n POSITIONAL_ARGS=PRINT
shift # past argument
# The release argument is the default, but remove it if it was specified
@@ -109,12 +112,14 @@ while [[ $# -gt 0 ]]; do
;;
g|generate)
COMMAND_COUNTER=$((COMMAND_COUNTER+1))
- GENERATE=$COMMAND_COUNTER
+ GENERATE=($YES)
+ declare -g -n POSITIONAL_ARGS=GENERATE
shift # past argument
;;
p|package)
COMMAND_COUNTER=$((COMMAND_COUNTER+1))
- PACKAGE=$COMMAND_COUNTER
+ PACKAGE=($YES)
+ declare -g -n POSITIONAL_ARGS=PACKAGE
shift # past argument
;;
o|open)
@@ -126,7 +131,7 @@ while [[ $# -gt 0 ]]; do
if [ $# -ne 0 ] && ([ $1 == "workspace" ] || [ $1 == "w" ]); then
# Always generate the project if we are opening the workspace
- GENERATE=1
+ GENERATE=($GENERATE)
OPEN_WORKSPACE=1
shift
fi
@@ -134,23 +139,33 @@ while [[ $# -gt 0 ]]; do
;;
t|tests)
COMMAND_COUNTER=$((COMMAND_COUNTER+1))
- TESTS=$COMMAND_COUNTER
+ TESTS=($YES)
+ declare -g -n POSITIONAL_ARGS=TESTS
shift # past argument
;;
d|diff)
COMMAND_COUNTER=$((COMMAND_COUNTER+1))
- DIFF=$COMMAND_COUNTER
+ DIFF=($YES)
+ declare -g -n POSITIONAL_ARGS=DIFF
shift # past argument
;;
s|start)
COMMAND_COUNTER=$((COMMAND_COUNTER+1))
- START=$COMMAND_COUNTER
+ START=($YES)
+ declare -g -n POSITIONAL_ARGS=START
RUN=1
shift # past argument
;;
*)
- POSITIONAL_ARGS+=("$1") # save positional arg
+
+ if [ $POSITIONAL_ARGS ]; then
+ POSITIONAL_ARGS+=("$1") # Append to current command positional arguments
+ elif [ ${#START[@]} -eq 0 ]; then
+ START=($YES $1)
+ else
+ START+=("$1") # Assume this is a positional argument for the start command
+ fi
shift # past argument
;;
esac
@@ -158,14 +173,16 @@ done
# If there is not arguments to ./run, then lets force start to be the default command
if [ $COMMAND_COUNTER -eq 0 ]; then
- START=1
+ if [ ${#START[@]} -eq 0 ]; then
+ START=($YES)
+ fi
fi
# Define a set of colors if NO_COLOR is not set
if [ $NO_COLOR -eq 0 ]; then
bold=$(tput bold)
normal=$(tput sgr0)
- green='\033[0;32m'
+ green=$(tput setaf 2)
dark_gray='\033[1;30m'
light_gray='\033[0;37m'
red='\033[0;31m'
@@ -253,18 +270,18 @@ if [ $VERBOSE -ge 1 ]; then
# Print all set variables
echo "APP_NAME: $APP_NAME"
echo "SHORT_NAME: $SHORT_NAME"
- echo "BUILD: $BUILD"
+ echo "BUILD: ${BUILD[@]}"
echo " BUILD_DEBUG: $BUILD_DEBUG"
echo " BUILD_RELEASE: $BUILD_RELEASE"
echo " BUILD_DEPLOY: $BUILD_DEPLOY"
echo "OPEN: $OPEN"
echo " OPEN_WORKSPACE: $OPEN_WORKSPACE"
- echo "GENERATE: $GENERATE"
- echo "PRINT: $PRINT"
+ echo "GENERATE: ${GENERATE[@]}"
+ echo "PRINT: ${PRINT[@]}"
echo " PRINT_LARGEFILES: $PRINT_LARGEFILES"
- echo "TESTS: $TESTS"
- echo "DIFF: $DIFF"
- echo "START: $START"
+ echo "TESTS: ${TESTS[@]}"
+ echo "DIFF: ${DIFF[@]}"
+ echo "START: ${START[@]}"
echo "HELP: $HELP"
echo "VERBOSE: $VERBOSE"
@@ -275,7 +292,7 @@ fi
#
# Generate the CMAKE solution
#
-if [ $GENERATE -ge 1 ]; then
+if [ ${#GENERATE[@]} -ge 1 ] && [ ${GENERATE[0]} -ge 1 ]; then
echo
echo -e "${green}Generating ${APP_NAME} solution...${normal}"
@@ -285,11 +302,9 @@ if [ $GENERATE -ge 1 ]; then
mkdir -p $SOLUTION_DIR 2>/dev/null
cd $SOLUTION_DIR
- CMAKE_ARGS=()
- if [ $GENERATE -eq $COMMAND_COUNTER ]; then
- CMAKE_ARGS=(${POSITIONAL_ARGS[@]})
- fi
-
+ # Copy second to last parameters to CMAKE_ARGS
+ CMAKE_ARGS=("${GENERATE[@]:1}")
+
# Read build.settings and add all the name=value pairs to the CMAKE_ARGS array as -Dname=value
while IFS='=' read -r name value; do
# Skip comments
@@ -302,7 +317,7 @@ if [ $GENERATE -ge 1 ]; then
[[ $name != BUILD_* ]] && continue
# Skip if -D$name* is already POSTIONAL_ARGS
- [[ " ${POSITIONAL_ARGS[@]} " =~ " -D$name" ]] && continue
+ [[ " ${GENERATE[@]} " =~ " -D$name" ]] && continue
CMAKE_ARGS+=("-D$name=$value")
done < $CONFIG_DIR/build.settings
@@ -336,7 +351,6 @@ if [ $GENERATE -ge 1 ]; then
# Keep track of it the solution was generated or not.
# We will use this to determine if we need to build the solution or not.
- GENERATED=1
cd ../../
# Print the path of the generated solution
@@ -350,7 +364,7 @@ fi
#
# Build solution
#
-if [ $BUILD -ge 1 ]; then
+if [ ${#BUILD[@]} -ge 1 ] && [ ${BUILD[0]} -ge 1 ]; then
# Print building message in green
echo
@@ -368,19 +382,22 @@ if [ $BUILD -ge 1 ]; then
fi
CMAKE_ARGS+=("-fileLogger")
CMAKE_ARGS+=("-fileLoggerParameters:logfile=cmake-build.log;verbosity=Normal")
+ elif [ ${machine} == "Mac" ]; then
+ # Use -quiet if verbose is not enabled
+ if [ $VERBOSE -eq 0 ]; then
+ CMAKE_ARGS+=("-quiet")
+ fi
fi
- ADDITIONAL_ARGS=()
- if [ $BUILD -eq $COMMAND_COUNTER ]; then
- ADDITIONAL_ARGS=(${POSITIONAL_ARGS[@]})
- fi
-
+ # Copy second to last parameters to CMAKE_ARGS
+ ADDITIONAL_ARGS=("${BUILD[@]:1}")
+
if [ $BUILD_DEBUG -eq 1 ]; then
- cmake --build . --parallel 7 --config Debug ${ADDITIONAL_ARGS[@]} -- ${CMAKE_ARGS[@]}
+ cmake --build . --parallel 7 --config Debug --target ${SHORT_NAME} ${ADDITIONAL_ARGS[@]} -- ${CMAKE_ARGS[@]}
elif [ $BUILD_DEPLOY -eq 1 ]; then
- cmake --build . --parallel 7 --config Deploy ${ADDITIONAL_ARGS[@]} -- ${CMAKE_ARGS[@]}
+ cmake --build . --parallel 7 --config Deploy --target ${SHORT_NAME} ${ADDITIONAL_ARGS[@]} -- ${CMAKE_ARGS[@]}
else
- cmake --build . --parallel 7 --config Release ${ADDITIONAL_ARGS[@]} -- ${CMAKE_ARGS[@]}
+ cmake --build . --parallel 7 --config Release --target ${SHORT_NAME} ${ADDITIONAL_ARGS[@]} -- ${CMAKE_ARGS[@]}
fi
retval=$?
@@ -402,14 +419,18 @@ fi
#
# Build package
#
-if [ $PACKAGE -ge 1 ]; then
+if [ ${#PACKAGE[@]} -ge 1 ] && [ ${PACKAGE[0]} -ge 1 ]; then
# Run script to build the package
echo
echo -e "${green}Building ${APP_NAME} package...${normal}"
echo
- ./scripts/build-package.sh --build ${POSITIONAL_ARGS[@]}
+ # Capture all additional arguments
+ ADDITIONAL_ARGS=("${PACKAGE[@]:1}")
+
+ # Run the packaging script
+ ./scripts/build-package.sh --build ${ADDITIONAL_ARGS[@]}
retval=$?
if [ $retval -ne 0 ]; then
@@ -425,16 +446,19 @@ fi
#
# GIT DIFF
#
-if [ $DIFF -ge 1 ]; then
+if [ ${#DIFF[@]} -ge 1 ] && [ ${DIFF[0]} -ge 1 ]; then
+
+ # Capture all additional arguments
+ ADDITIONAL_ARGS=("${DIFF[@]:1}")
# Check if we have a remote branch to diff against and make sure the argument doesn't start with -- or -
- if [ ${#POSITIONAL_ARGS[@]} -eq 0 ] || [[ ${POSITIONAL_ARGS[0]} == --* ]] || [[ ${POSITIONAL_ARGS[0]} == -* ]]; then
+ if [ ${#ADDITIONAL_ARGS[@]} -eq 0 ] || [[ ${ADDITIONAL_ARGS[0]} == --* ]] || [[ ${ADDITIONAL_ARGS[0]} == -* ]]; then
# Insert main at position 0 and push other arguments
- POSITIONAL_ARGS=("${POSITIONAL_ARGS[@]:0:0}" "main" "${POSITIONAL_ARGS[@]:0}")
+ ADDITIONAL_ARGS=("${ADDITIONAL_ARGS[@]:0:0}" "main" "${ADDITIONAL_ARGS[@]:0}")
fi
# Diff the current branch with the specified remote branch
- git difftool -d "origin/${POSITIONAL_ARGS[0]}" ${POSITIONAL_ARGS[@]:1}
+ git difftool -d "origin/${ADDITIONAL_ARGS[0]}" ${ADDITIONAL_ARGS[@]:1}
# Check return value
retval=$?
@@ -454,7 +478,7 @@ fi
if [ $OPEN -ge 1 ]; then
echo
- echo -e "${green}Opening IDE...${normal}"
+ echo -e "${green}Opening ${SHORT_NAME} in IDE...${normal}"
echo
if [ $OPEN_WORKSPACE -eq 1 ]; then
@@ -480,13 +504,16 @@ fi
#
# Print various solution information
#
-if [ $PRINT -ge 1 ]; then
+if [ ${#PRINT[@]} -ge 1 ] && [ ${PRINT[0]} -ge 1 ]; then
+
+ # Capture all additional arguments
+ ADDITIONAL_ARGS=("${PRINT[@]:1}")
if [ $PRINT_LARGEFILES -eq 1 ]; then
TOP_COUNT=20
- if is_number ${POSITIONAL_ARGS[0]}; then
- TOP_COUNT=${POSITIONAL_ARGS[0]}
+ if is_number ${ADDITIONAL_ARGS[0]}; then
+ TOP_COUNT=${ADDITIONAL_ARGS[0]}
fi
while read -r largefile; do
@@ -508,16 +535,14 @@ fi
#
# Run tests
#
-if [ $TESTS -ge 1 ]; then
-
- ADDITIONAL_ARGS=()
- if [ $TESTS -eq $COMMAND_COUNTER ]; then
- ADDITIONAL_ARGS=(${POSITIONAL_ARGS[@]})
+if [ ${#TESTS[@]} -ge 1 ] && [ ${TESTS[0]} -ge 1 ]; then
- # If VERBOSE is defined, add it to POSITIONAL_ARGS
- if [ $VERBOSE -eq 1 ]; then
- ADDITIONAL_ARGS=("${ADDITIONAL_ARGS[@]:0:0}" "--verbose" "${ADDITIONAL_ARGS[@]:0}")
- fi
+ # Capture all additional arguments
+ ADDITIONAL_ARGS=("${TESTS[@]:1}")
+
+ # If VERBOSE is defined, add it to the arguments
+ if [ $VERBOSE -eq 1 ]; then
+ ADDITIONAL_ARGS=("${ADDITIONAL_ARGS[@]:0:0}" "--verbose" "${ADDITIONAL_ARGS[@]:0}")
fi
echo
@@ -554,26 +579,28 @@ if [ $TESTS -ge 1 ]; then
fi
# Check if we have an explicit command to run/start the application
-if [ $START -ge 1 ]; then
+if [ ${#START[@]} -ge 1 ] && [ ${START[0]} -ge 1 ]; then
RUN=1
fi
# Finally run the application if requested.
if [ $RUN -eq 1 ]; then
+ ADDITIONAL_ARGS=("${START[@]:1}")
+
# If VERBOSE is defined, add it to POSITIONAL_ARGS
if [ $VERBOSE -eq 1 ]; then
- POSITIONAL_ARGS=("${POSITIONAL_ARGS[@]:0:0}" "--verbose" "${POSITIONAL_ARGS[@]:0}")
+ ADDITIONAL_ARGS=("${ADDITIONAL_ARGS[@]:0:0}" "--verbose" "${ADDITIONAL_ARGS[@]:0}")
fi
echo
- echo -e "${green}${italic}Running ${APP_NAME} with arguments: ${POSITIONAL_ARGS[@]}${normal}"
+ echo -e "${green}${italic}Running ${APP_NAME} with arguments: ${ADDITIONAL_ARGS[@]}${normal}"
echo
if [ ${machine} == "MinGw" ]; then
- ./build/${SHORT_NAME}.exe ${POSITIONAL_ARGS[@]}
+ ./build/${SHORT_NAME}.exe ${ADDITIONAL_ARGS[@]}
elif [ ${machine} == "Mac" ]; then
- ./build/${APP_NAME}.app/Contents/MacOS/${APP_NAME} ${POSITIONAL_ARGS[@]}
+ ./build/${APP_NAME}.app/Contents/MacOS/${APP_NAME} ${ADDITIONAL_ARGS[@]}
fi
retval=$?
diff --git a/scripts/common.sh b/scripts/common.sh
index 3e7c54f..4ce3401 100644
--- a/scripts/common.sh
+++ b/scripts/common.sh
@@ -93,8 +93,33 @@ function convert_path_to_file_link() {
#
function build_setting() {
local name=$1
- local value=$(awk -F "=" "/$name/ {print \$2}" "$CONFIG_DIR/build.settings")
- echo $value
+
+ # Check if the file exists
+ if [ ! -f "$CONFIG_DIR/build.settings" ]; then
+ echo "The file $CONFIG_DIR/build.settings does not exist"
+ exit 1
+ fi
+
+ # Read the file line by line
+ while IFS= read -r line
+ do
+ # Check if the line starts with the name
+ if [[ $line == ${name}* ]]; then
+ # Split the line into an array
+ IFS='=' read -ra array <<< "$line"
+ # Capture the value
+ local result=${array[1]}
+ # Remove trailing line return \r and \n
+ result="${result%$'\r'}"
+ result="${result%$'\n'}"
+ echo $result
+ return
+ fi
+ done < "$CONFIG_DIR/build.settings"
+
+ # If we get here, the name was not found
+ echo "The name $name was not found in $CONFIG_DIR/build.settings"
+ exit 1
}
#
diff --git a/sources/CMakeLists.txt b/sources/CMakeLists.txt
index c18d5cd..6fc7516 100644
--- a/sources/CMakeLists.txt
+++ b/sources/CMakeLists.txt
@@ -152,14 +152,10 @@ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}")
if (MSVC)
- # Generate PDB files for debug and release builds
- set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /DEBUG")
- set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /DEBUG")
-
# Ignore specific default libraries msvcrt.lib;libcmt.lib in debug for MSVC
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcmt.lib")
-elseif(XCODE)
+elseif(APPLE)
# Link with core libraries
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation -framework CoreServices")
@@ -201,19 +197,14 @@ if (MSVC)
${EXTERNAL_SOURCES}
)
+ # Have the application export foundation_lib and IMGUI symbols.
+ set_property(TARGET ${AppId} PROPERTY ENABLE_EXPORTS ON)
+
# Copy the config/*.sjson files to the build directory
#file(COPY ${CONFIG_FILES} DESTINATION ${ROOT_DIR}/build)
- set_target_properties(${AppId} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${ROOT_DIR}/build/.)
- set_target_properties(${AppId} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${ROOT_DIR}/build/.)
- set_target_properties(${AppId} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${ROOT_DIR}/build/.)
- set_target_properties(${AppId} PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEPLOY ${ROOT_DIR}/build/.)
-
- # Make sure we have the pdb in the same folder.
set_target_properties(${AppId} PROPERTIES PDB_OUTPUT_DIRECTORY ${ROOT_DIR}/build/.)
- set_target_properties(${AppId} PROPERTIES PDB_OUTPUT_DIRECTORY_DEBUG ${ROOT_DIR}/build/.)
- set_target_properties(${AppId} PROPERTIES PDB_OUTPUT_DIRECTORY_RELEASE ${ROOT_DIR}/build/.)
- set_target_properties(${AppId} PROPERTIES PDB_OUTPUT_DIRECTORY_DEPLOY ${ROOT_DIR}/build/.)
+ set_target_properties(${AppId} PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${ROOT_DIR}/build/.)
if (BUILD_SERVICE_EXE)
# Create service.exe target to provide a way to run the app from the command line
@@ -229,9 +220,6 @@ if (MSVC)
set_target_properties(service-exe PROPERTIES LINK_FLAGS "/SUBSYSTEM:CONSOLE")
set_target_properties(service-exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${ROOT_DIR}/build/.)
- set_target_properties(service-exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEBUG ${ROOT_DIR}/build/.)
- set_target_properties(service-exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY_RELEASE ${ROOT_DIR}/build/.)
- set_target_properties(service-exe PROPERTIES RUNTIME_OUTPUT_DIRECTORY_DEPLOY ${ROOT_DIR}/build/.)
target_link_libraries(service-exe service-framework)
@@ -295,4 +283,4 @@ elseif(APPLE)
endif()
# Add dependencies to other libs
-target_link_libraries(${AppId} framework)
+target_link_libraries(${AppId} PRIVATE framework)
diff --git a/sources/events.h b/sources/events.h
deleted file mode 100644
index 346303a..0000000
--- a/sources/events.h
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * Copyright 2023 - All rights reserved.
- * License: https://equals-forty-two.com/LICENSE
- *
- * Declare application message events
- */
-
-#pragma once
-
-/*! Posted when the application is fully initialized. */
-constexpr const char EVENT_APPLICATION_INITIALIZED[] = "APPLICATION_INITIALIZED";
-
diff --git a/sources/example.cpp b/sources/example.cpp
index 53e1794..abaa928 100644
--- a/sources/example.cpp
+++ b/sources/example.cpp
@@ -13,70 +13,6 @@
#include
-/*! Render leading menus.
- *
- * @param window The GLFW window.
- */
-FOUNDATION_STATIC void app_main_menu_begin(GLFWwindow* window)
-{
- if (!ImGui::BeginMenuBar())
- return;
-
- if (ImGui::TrBeginMenu("File"))
- {
- if (ImGui::TrBeginMenu("Create"))
- {
- ImGui::EndMenu();
- }
-
- if (ImGui::TrBeginMenu("Open"))
- {
- ImGui::EndMenu();
- }
-
- ImGui::Separator();
- if (ImGui::TrMenuItem(ICON_MD_EXIT_TO_APP " Exit", "Alt+F4"))
- glfw_request_close_window(window);
-
- ImGui::EndMenu();
- }
-
- ImGui::EndMenuBar();
-
- // Let the framework inject some menus.
- app_menu_begin(window);
-}
-
-/*! Render trailing menus.
- *
- * In between leading and trailing menus the framework usually
- * injects additional menus through registered modules.
- *
- * @param window The GLFW window.
- */
-FOUNDATION_STATIC void app_main_menu_end(GLFWwindow* window)
-{
- // Let registered module inject some menus.
- module_foreach_menu();
-
- if (ImGui::BeginMenuBar())
- {
- if (ImGui::TrBeginMenu("Windows"))
- ImGui::EndMenu();
-
- app_menu_help(window);
-
- // Update special application menu status.
- // Usually controls are displayed at the far right of the menu.
- profiler_menu_timer();
- module_foreach_menu_status();
-
- ImGui::EndMenuBar();
- }
-
- app_menu_end(window);
-}
-
/*! Main entry point used to report the application title.
*
* @return The application title.
@@ -86,23 +22,6 @@ extern const char* app_title()
return PRODUCT_NAME;
}
-/*! Main entry point to setup the application exception handler.
- *
- * You can use this function to setup a custom exception handler to log and report crashes.
- *
- * @param dump_file The name of the dump file.
- * @param length The length of the dump file name.
- *
- * @note The dump file is a binary file containing the application state at the time of the crash.
- */
-extern void app_exception_handler(const char* dump_file, size_t length)
-{
- FOUNDATION_UNUSED(dump_file);
- FOUNDATION_UNUSED(length);
- log_error(0, ERROR_EXCEPTION, STRING_CONST("Unhandled exception"));
- process_exit(-1);
-}
-
/*! Main entry point to configure the application.
*
* This function is called once when the application is starting up.
@@ -187,7 +106,7 @@ extern void app_shutdown()
*/
extern void app_update(GLFWwindow* window)
{
- module_update();
+ app_update_default(window);
}
/*! Main entry point to render the application state.
@@ -204,60 +123,10 @@ extern void app_update(GLFWwindow* window)
*/
extern void app_render(GLFWwindow* window, int frame_width, int frame_height)
{
- ImGui::SetNextWindowPos(ImVec2(0, 0));
- ImGui::SetNextWindowSize(ImVec2((float)frame_width, (float)frame_height));
-
- if (ImGui::Begin(app_title(), nullptr,
- ImGuiWindowFlags_NoBringToFrontOnFocus |
- ImGuiWindowFlags_NoResize |
- ImGuiWindowFlags_NoMove |
- ImGuiWindowFlags_NoCollapse |
- ImGuiWindowFlags_NoTitleBar |
- ImGuiWindowFlags_MenuBar))
+ constexpr auto default_tab_render = []()
{
- // Render main menus
- app_main_menu_begin(window);
-
- // Render document tabs
- static ImGuiTabBarFlags tabs_init_flags = ImGuiTabBarFlags_Reorderable;
- if (tabs_begin("Tabs", SETTINGS.current_tab, tabs_init_flags, nullptr))
- {
- // Render the settings tab
- tab_set_color(TAB_COLOR_APP_3D);
- tab_draw(tr(ICON_MD_HEXAGON " Example "), nullptr, 0, []()
- {
- ImGui::TrTextUnformatted("Hello World!");
- });
+ ImGui::TrTextUnformatted("Hello World!");
+ };
- // Render module registered tabs
- module_foreach_tabs();
-
- // Render the settings tab
- tab_set_color(TAB_COLOR_SETTINGS);
- tab_draw(tr(ICON_MD_SETTINGS " Settings ##Settings"), nullptr,
- ImGuiTabItemFlags_NoPushId | ImGuiTabItemFlags_Trailing | ImGuiTabItemFlags_NoReorder, settings_draw);
-
- // We differ setting ImGuiTabBarFlags_AutoSelectNewTabs until after the first frame,
- // since we manually select the first tab in the list using the user session data.
- if ((tabs_init_flags & ImGuiTabBarFlags_AutoSelectNewTabs) == 0)
- tabs_init_flags |= ImGuiTabBarFlags_AutoSelectNewTabs;
-
- tabs_end();
- }
-
- // Render trailing menus
- app_main_menu_end(window);
-
- // Render main dialog and floating windows
- module_foreach_window();
-
- } ImGui::End();
-}
-
-/*! Main entry point to additional 3rdparty library information
- * displayed in the default about dialog.
- */
-extern void app_render_3rdparty_libs()
-{
- // HERE: Invoke here any function that wants to render 3rd party libs information
+ app_render_default(window, frame_width, frame_height, SETTINGS.current_tab, default_tab_render, settings_draw);
}
diff --git a/tools/installer/CMakeLists.txt b/tools/installer/CMakeLists.txt
index 57468be..3643722 100644
--- a/tools/installer/CMakeLists.txt
+++ b/tools/installer/CMakeLists.txt
@@ -65,7 +65,7 @@ if (MSVC)
# Ignore specific default libraries msvcrt.lib;libcmt.lib in debug for MSVC
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcmt.lib")
-elseif(XCODE)
+elseif(APPLE)
# Link with core libraries
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation -framework CoreServices")