add instructions for curl; add array operator to configuration, give configuration object access to nodes; fix src paths in android skeleton script; add copy file function

This commit is contained in:
ohsqueezy 2023-05-10 02:51:11 -04:00
parent a8126605e8
commit 8c086ba161
15 changed files with 352 additions and 124 deletions

View File

@ -116,6 +116,12 @@ Try these non-SPACEBOX demos for verifying the Raspberry Pi OpenGL ES setup with
The [fill_screen demo][] has a working example of how to build for Android. It may be worthwhile to read the [SDL wiki Android page][] and [SDL docs Android README][] and compile an SDL example for Linux before doing a SPACEBOX Android build. The source distributions for SDL, SDL image, SDL ttf, and SDL mixer, and the Android SDK are required.
After building the demo, see the following for further information on using SDL on Android.
* [SDL wiki](https://wiki.libsdl.org/Android)
* [SDL Android README](https://github.com/libsdl-org/SDL/blob/main/docs/README-android.md)
* In the SDL source package, `SDL_android.h` and the Android section of `SDL_system.h`
#### Building an SDL example for Linux
* Install Java packages
@ -255,6 +261,10 @@ These steps were taken to build the fill_screen demo for Android. The Android SD
$ ANDROID_SDK_ROOT=$HOME/local/Android ./gradlew build
#### Screen rotation
Note that `SDL_WINDOW_RESIZABLE` is [required for screen rotation](https://discourse.libsdl.org/t/screen-orientation-not-changing-when-rotating-android-device/26676) to work
### OS X, Windows
Builds for these platforms have only passed the proof of concept phase. An early version of SPACEBOX was compiled for each of them, but none of the demos have been compiled for them in their current form, so there is only some broken code available in the box demo Makefile.
@ -383,9 +393,37 @@ This builds the local, WASM, and Android libraries by downloading OpenCV 4.7.0 a
$ python3 platforms/android/build_sdk.py --sdk_path ~/local/Android/ --ndk_path ~/local/Android/ndk/22.1.7171670/ \
--extra_modules_path ../opencv_contrib-4.7.0-subset/ --shared --no_samples_build build_android/
### ZBar
### curl
Note that ZBar has been replaced with OpenCV's barcode contrib module in the original project this was compiled for. The instructions may still work, so they are left in case they are useful for another project, but they have not been used in a while.
#### Linux
Install from the package manager
sudo apt install libcurl4-openssl-dev
#### Emscripten
Use Emscripten's [Fetch API](https://emscripten.org/docs/api_reference/fetch.html) instead of curl
#### Android
There are instructions on how to [build for Android](https://curl.se/docs/install.html#android) in the curl documentation. There is also a project [libcurl-android](https://github.com/ibaoger/libcurl-android) that facilitates the build process.
$ git clone --recursive https://github.com/ibaoger/libcurl-android
Add `x86` to the `APP_ABI` parameter in `build_for_android.sh`. Then run the build script.
$ NDK_ROOT=/path/to/NDK ./build_for_android.sh
The libraries should be written the `jni/build` folder. The folder `libs/x86-64` should actually be named `libs/x86_64`, so rename it.
$ mv jni/build/zlib/x86-64 jni/build/zlib/x86_64
$ mv jni/build/curl/x86-64 jni/build/curl/x86_64
$ mv jni/build/openssl/x86-64/ jni/build/openssl/x86_64
See how the OpenCV libraries are included in an Android NDK project in the [camera demo](demo/camera) for an example of how to use pre-built shared libraries.
### ZBar
#### Linux

View File

@ -111,10 +111,10 @@ public:
void open()
{
std::ostringstream message;
/* Open the OpenCV capture, using device ID #0 to get the default attached camera. */
int device_id = configuration()["scan"]["camera-device-id"];
capture.open(device_id);
std::ostringstream message;
if (capture.isOpened())
{
message << "Opened and initialized " << capture.get(cv::CAP_PROP_FRAME_WIDTH) << "x" <<

View File

@ -8,8 +8,7 @@
| ~~~~~~~ BOX |/
+-------------*/
#ifndef SB_BOX_H_
#define SB_BOX_H_
#pragma once
#include <iostream>
#include <algorithm>
@ -110,5 +109,3 @@ namespace std
#include "extension.hpp"
#include "Segment.hpp"
#endif

View File

@ -1,32 +1,29 @@
/* /\ +------------------------------------------------------------+
____/ \____ /| zlib/MIT/Unlicenced game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | Learn more about [SPACE BOX] at [shampoo.ooo] |
| ~~~~~~~~~~~~ | +------------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
/* +------------------------------------------------------+
____/ \____ /| - Open source game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
| ~~~~~~~~~~~~ | +------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
#include "Configuration.hpp"
/* Initialize a Configuration object. The path argument is the location where the config file is stored.
* If there is no file located at the path, it will be created if the write method is called. System level
* default assignments defined in this file can be added to and overwritten by user supplied JSON file at
* the specified path or at a path passed to the load function. */
Configuration::Configuration(Node *parent, fs::path path) : Node(parent)
Configuration::Configuration(Node* parent) : Node(parent)
{
config_path = path;
set_defaults();
load();
merge(USER_CONFIG_PATH);
#ifdef __ANDROID__
merge(ANDROID_CONFIG_PATH);
#endif
auto_refresher.set_frame_length(config["configuration"]["auto-refresh-interval"].get<float>());
auto_refresh(config["configuration"]["auto-refresh"]);
}
/* Fill the system level config JSON dict with default values set by the framework */
void Configuration::set_defaults()
{
sys_config["keys"] = {
config["keys"] = {
{"record", {"CTRL", "SHIFT", "i"}},
{"save-current-stash", {"CTRL", "SHIFT", "v"}},
{"screenshot", {"CTRL", "i"}},
@ -40,14 +37,14 @@ void Configuration::set_defaults()
{"toggle-framerate", {"CTRL", "f"}},
{"reset", {"CTRL", "r"}}
};
sys_config["input"] = {
config["input"] = {
{"suppress-any-key-on-mods", true},
{"system-any-key-ignore-commands", {"fullscreen", "screenshot", "toggle-framerate", "record", "quit"}},
{"any-key-ignore-commands", nlohmann::json::array()},
{"default-unsuppress-delay", 700},
{"ignore-repeat-keypress", true}
};
sys_config["display"] = {
config["display"] = {
{"dimensions", {960, 540}},
{"framerate", 60},
{"title", "[SPACEBOX]"},
@ -56,11 +53,11 @@ void Configuration::set_defaults()
{"render-test-spacing", 2},
{"render driver", "opengl"}
};
sys_config["audio"] = {
config["audio"] = {
{"default-sfx-root", "resource/sfx"},
{"default-bgm-root", "resource/bgm"}
};
sys_config["gl"] = {
config["gl"] = {
{"depth-size", 16},
{"red-size", 8},
{"green-size", 8},
@ -70,7 +67,7 @@ void Configuration::set_defaults()
{"major-version", 3},
{"minor-version", 2}
},
sys_config["recording"] = {
config["recording"] = {
{"enabled", false},
{"screenshot-prefix", "screenshot-"},
{"screenshot-extension", ".png"},
@ -85,16 +82,16 @@ void Configuration::set_defaults()
{"max-video-memory", 1000},
{"mp4-pixel-format", "yuv444p"}
};
sys_config["fps-indicator"] = {
config["fps-indicator"] = {
{"width", .05},
{"height", .04},
{"background", {255, 255, 255}},
{"foreground", {0, 0, 0}}
};
sys_config["animation"] = {
config["animation"] = {
{"all-frames-frameset-name", "all"}
};
sys_config["log"] = {
config["log"] = {
{"enabled", false},
{"debug-to-stdout", false},
{"debug-to-file", false},
@ -103,37 +100,33 @@ void Configuration::set_defaults()
{"debug-file-name", "space_box_debug_log.txt"},
{"short-name", "spacebox"}
};
sys_config["configuration"] = {
config["configuration"] = {
{"auto-refresh", false},
{"auto-refresh-interval", 1000}
};
config = sys_config;
}
/* Load the configuration file at path */
void Configuration::load(fs::path path)
nlohmann::json& Configuration::operator[](const std::string& key)
{
/* read contents of path into the game level config JSON dict */
user_config = nlohmann::json::parse(sb::file_to_string(path));
/* merge into the full config JSON dict */
merge();
return config[key];
}
/* Load the configuration file at Configuration::config_path */
void Configuration::load()
const nlohmann::json& Configuration::operator[](const std::string& key) const
{
load(config_path);
return config[key];
}
/* Merge the system level config JSON dict (hard-coded in this file) with the user level config JSON
* dict (loaded from disk by the load function) */
void Configuration::merge()
const nlohmann::json& Configuration::operator()() const
{
if (!user_config.empty())
return config;
}
void Configuration::merge(const nlohmann::json& incoming)
{
if (!incoming.empty())
{
/* loop over first level key/value pairs */
for (auto& item: user_config.items())
for (auto& item: incoming.items())
{
/* if the value is an object (dict), merge it into the config, overwriting keys already in the config */
if (item.value().is_object())
@ -147,41 +140,75 @@ void Configuration::merge()
}
}
}
else
{
sb::Log::log("Attempted to merge empty JSON into configuration", sb::Log::WARN);
}
}
void Configuration::merge(const fs::path& path)
{
#ifndef __ANDROID__
/* Can't check for file existence in an Android APK */
if (fs::exists(path))
{
#endif
/* Load JSON to a string and check for validity. */
std::string contents = sb::file_to_string(path);
if (nlohmann::json::accept(contents))
{
merge(nlohmann::json::parse(contents));
}
else
{
std::ostringstream message;
message << "Invalid JSON at " << path;
sb::Log::log(message, sb::Log::WARN);
}
#ifndef __ANDROID__
}
else
{
std::ostringstream message;
message << "File not found: " << path;
sb::Log::log(message, sb::Log::WARN);
}
#endif
}
void Configuration::merge(const std::string& path)
{
merge(fs::path(path));
}
void Configuration::merge(const char* path)
{
merge(fs::path(path));
}
/* Set auto refresh to on or off */
void Configuration::auto_refresh(bool on)
{
on ? auto_refresher.play() : auto_refresher.pause();
}
/* Refresh the config contents by calling the default load function */
void Configuration::refresh()
{
if (fs::exists(config_path) && fs::last_write_time(config_path) > config_file_modification_time)
if (fs::exists(USER_CONFIG_PATH) && fs::last_write_time(USER_CONFIG_PATH) > config_file_modification_time)
{
std::ostringstream message;
message << "config file modified, reloading " << config_path;
message << "config file modified, reloading " << USER_CONFIG_PATH;
sb::Log::log(message, sb::Log::DEBUG);
load();
merge(USER_CONFIG_PATH);
}
}
/* Write configuration to specified path in JSON format */
void Configuration::write(fs::path path)
{
std::ofstream output(path);
output << std::setw(tab_width) << user_config << std::endl;
}
/* Write configuration to config_path (set at initialization) */
void Configuration::write()
{
write(config_path);
}
/* Updates the auto refresher */
void Configuration::update()
{
auto_refresher.update();
}
std::ostream& std::operator<<(std::ostream& out, const Configuration& configuration)
{
out << configuration();
return out;
}

View File

@ -1,15 +1,14 @@
/* +--------------------------------------------------------------+
____/ \____ /| - zlib/MIT/Unlicenced game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - originally created at [http://nugget.fun] |
| ~~~~~~~~~~~~ | +--------------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
/* +------------------------------------------------------+
____/ \____ /| - Open source game framework licensed to freely use, |
\ / / | copy, modify and sell without restriction |
+--\ ^__^ /--+ | |
| ~/ \~ | | - created for <https://foam.shampoo.ooo> |
| ~~~~~~~~~~~~ | +------------------------------------------------------+
| SPACE ~~~~~ | /
| ~~~~~~~ BOX |/
+-------------*/
#ifndef SB_CONFIGURATION_H_
#define SB_CONFIGURATION_H_
#pragma once
#include <fstream>
#include <ostream>
@ -25,29 +24,101 @@ class Configuration : public Node
{
private:
nlohmann::json sys_config, user_config;
fs::path config_path;
int tab_width = 4;
inline static const std::string USER_CONFIG_PATH = "config.json";
inline static const std::string ANDROID_CONFIG_PATH = "config_android.json";
Animation auto_refresher = Animation(&Configuration::refresh, this);
fs::file_time_type config_file_modification_time;
nlohmann::json config;
/*!
* Fill the config JSON with default values set by the framework.
*/
void set_defaults();
void merge();
public:
nlohmann::json config;
/*!
* Construct a Configuration object. The path argument is the location to look for a user configuration. If valid JSON is found,
* it will be merged into the hard-coded default assignments, overwriting any existing key/value pairs. The path will be watched
* for changes if auto refresh is turned on.
*
* @param parent SPACEBOX Node rooted at the Game object
*/
Configuration(Node* parent);
Configuration(Node*, fs::path = "config.json");
void load(fs::path path);
void load();
/*!
* Get a writable JSON object corresponding to a key in the configuration.
*
* The JSON object's value can be used directly if it can be implictly type-cast, and it can be assigned a new value. See
* https://nlohmann.github.io/json/api/basic_json/ for further information on how to read and write the JSON object.
*
* @param key Top level key corresponding to a section of the SPACEBOX configuration JSON
* @return
*/
nlohmann::json& operator[](const std::string& key);
/*!
* Get a read-only JSON object corresponding to a key in configuration.
*
* The JSON object's value can be used directly if it can be implictly type-cast. Otherwise, use the JSON object's
* get method with a template parameter. See https://nlohmann.github.io/json/api/basic_json/ for further information.
*
* @param key Top level key corresponding to a section of the SPACEBOX configuration JSON
* @return Read-only JSON object
*/
const nlohmann::json& operator[](const std::string& key) const;
/*!
* Get a read-only JSON object reference to the entire configuration. Can be used, for example, for iterating over the
* configuration keys.
*
* @return Read-only JSON object reference to the full configuration
*/
const nlohmann::json& operator()() const;
/*!
* Merge new SPACEBOX configuration JSON with the JSON already in memory, overwriting any existing key/value pairs.
*
* @param incoming JSON object populated with SPACEBOX configuration settings
*/
void merge(const nlohmann::json& incoming);
/*!
* Merge new SPACEBOX configuration from a given path to a JSON file.
*
* @param path JSON file path with new configuration values
*/
void merge(const fs::path& path);
/*!
* @overload void Configuration::merge(fs::path path)
*/
void merge(const std::string& path);
/*!
* @overload void Configuration::merge(fs::path path)
*/
void merge(const char* path);
/*!
* Set auto refresh to on or off. Auto refresh watches the file at Configuration::user_path (set in the contructor) for changes
* and loads them automatically at the interval set at Configuration::config["configuration"]["auto-refresh-interval"].
*
* @param bool true to refresh automatically, false to turn off automatic refresh
*/
void auto_refresh(bool);
/*!
* Check if the user config file was modified and merge if so.
*/
void refresh();
void write(fs::path path);
void write();
/*!
* Update the auto refresher.
*/
void update();
virtual std::string class_name() const { return "Configuration"; }
};
@ -88,4 +159,14 @@ namespace std::filesystem
}
}
#endif
namespace std
{
/*!
* Add the entire JSON when the stream operator is called.
*
* @param out Output stream
* @param configuration Configuration object being output to the stream
* @return A reference to the input stream
*/
std::ostream& operator<<(std::ostream& out, const Configuration& configuration);
}

View File

@ -30,21 +30,22 @@ Game::Game()
/* Log the current working directory as seen by std::filesystem */
std::ostringstream log_message;
log_message << "Current path is " << std::filesystem::current_path();
log_message << "Current path as seen by std::filesystem is " << std::filesystem::current_path();
sb::Log::log(log_message);
/* Log Android storage paths as determined by SDL */
#if defined(__ANDROID__) || defined(ANDROID)
log_message = std::ostringstream();
log_message << "Using Android SDK version " << SDL_GetAndroidSDKVersion() << std::endl;
log_message << "SDL_AndroidGetInternalStoragePath() is " << SDL_AndroidGetInternalStoragePath() << std::endl;
log_message << "SDL_AndroidGetExternalStorageState() is " << SDL_AndroidGetExternalStorageState() << std::endl;
log_message << "SDL_AndroidGetExternalStorageState() is " << SDL_AndroidGetExternalStorageState() << " (1=read, 2=write, 3=r/w)" << std::endl;
log_message << "SDL_AndroidGetExternalStoragePath() is " << SDL_AndroidGetExternalStoragePath();
sb::Log::log(log_message);
#endif
/* Pretty print config JSON to debug log */
log_message = std::ostringstream();
log_message << std::setw(4) << configuration() << std::endl;
log_message << std::setw(4) << configuration()() << std::endl;
sb::Log::log(log_message, sb::Log::DEBUG);
/* Tell SDL which render driver you will be requesting when calling SDL_CreateRenderer */
@ -460,14 +461,14 @@ void Game::log_surface_format(SDL_Surface* surface, std::string preface)
format->Amask, pixel_format.c_str());
}
const nlohmann::json& Game::configuration() const
const Configuration& Game::configuration() const
{
return _configuration.config;
return _configuration;
}
nlohmann::json& Game::configuration()
Configuration& Game::configuration()
{
return _configuration.config;
return _configuration;
}
const SDL_Window* Game::window() const

View File

@ -71,7 +71,6 @@ private:
int ticks;
float frame_length = 1000.0 / 60.0;
Configuration _configuration {this};
SDL_Window* _window;
/*!
@ -108,6 +107,7 @@ public:
int frame_count_this_second = 0, last_frame_length;
float frame_time_overflow = 0, last_frame_timestamp, last_frame_count_timestamp;
bool done = false, show_framerate = true, is_gl_context = true;
Configuration _configuration {this};
Delegate delegate {this};
sb::Display display {this};
Recorder recorder {this};
@ -138,8 +138,8 @@ public:
void log_gl_properties() const;
void log_surface_format(SDL_Surface*, std::string = "surface");
const nlohmann::json& configuration() const;
nlohmann::json& configuration();
const Configuration& configuration() const;
Configuration& configuration();
const SDL_Window* window() const;
SDL_Window* window();
const SDL_Renderer* get_renderer() const;

View File

@ -30,7 +30,7 @@ void Input::print_key_combination(const KeyCombination &combination) const
void Input::load_key_map()
{
nlohmann::json &config = configuration();
const nlohmann::json& config = configuration()();
for (auto& entry : config.at("keys").items())
{
bool ctrl = false, alt = false, shift = false;

View File

@ -17,8 +17,7 @@
* settings.
*/
#ifndef SB_LOG_H_
#define SB_LOG_H_
#pragma once
/* include Open GL */
#if defined(__EMSCRIPTEN__)
@ -69,5 +68,3 @@ namespace sb
/* Log log = Log(&Log::record); */
}
#endif

View File

@ -44,12 +44,12 @@ bool Node::is_active() const
return active;
}
const nlohmann::json& Node::configuration() const
const Configuration& Node::configuration() const
{
return get_root()->configuration();
}
nlohmann::json& Node::configuration()
Configuration& Node::configuration()
{
return get_root()->configuration();
}

View File

@ -23,6 +23,7 @@ class Game;
class Delegate;
class Input;
class Box;
class Configuration;
struct Audio;
namespace sb
@ -44,8 +45,8 @@ public:
void set_canvas(SDL_Texture*);
SDL_Texture* get_canvas();
bool is_active() const;
const nlohmann::json& configuration() const;
nlohmann::json& configuration();
const Configuration& configuration() const;
Configuration& configuration();
Delegate& get_delegate();
const sb::Display& get_display() const;
const SDL_Renderer* get_renderer() const;

View File

@ -64,7 +64,7 @@ void Recorder::respond(SDL_Event& event)
* screenshots found in the output directory. */
void Recorder::capture_screen()
{
nlohmann::json config = configuration();
const nlohmann::json& config = configuration()();
SDL_Surface* surface = get_display().screen_surface();
fs::path directory = config["recording"]["screenshot-directory"];
fs::create_directories(directory);
@ -233,8 +233,7 @@ int Recorder::get_memory_size()
void Recorder::make_directory()
{
nlohmann::json config = configuration();
fs::path root = config["recording"]["video-directory"];
fs::path root = configuration()["recording"]["video-directory"];
fs::create_directories(root);
fs::path directory = sb::get_next_file_name(root, 5, "video-");
fs::create_directories(directory);

View File

@ -38,8 +38,8 @@ sed -i "s/^#.*\(org.gradle.parallel\)/\1/" "$ANDROID_BUILD_DIR/gradle.properties
sed -i 's/^LOCAL_SHARED_LIBRARIES.*/& SDL2_image SDL2_mixer SDL2_ttf/' "$ANDROID_BUILD_DIR/$ANDROID_MK"
sed -i "s#^LOCAL_C_INCLUDES.*#& \$(LOCAL_PATH)/../../../../../../$SB_LIB_SRC \$(LOCAL_PATH)/../../../../../../$SB_SRC#" \
"$ANDROID_BUILD_DIR/$ANDROID_MK"
sed -i "s#YourSourceHere.c#\$(wildcard \$(LOCAL_PATH)/../../../../../../*.cpp)#" "$ANDROID_BUILD_DIR/$ANDROID_MK"
sed -i 's#^LOCAL_SRC_FILES.*#& $(wildcard $(LOCAL_PATH)/../../../../../../../../src/*.cpp)#' "$ANDROID_BUILD_DIR/$ANDROID_MK"
sed -i 's#^LOCAL_SRC_FILES.*#& $(wildcard $(LOCAL_PATH)/../../../../../../../../lib/sdl2-gfx/*.c)#' "$ANDROID_BUILD_DIR/$ANDROID_MK"
sed -i "s#YourSourceHere.c#\$(wildcard \$(LOCAL_PATH)/../../../../../../$SRC/*.cpp)#" "$ANDROID_BUILD_DIR/$ANDROID_MK"
sed -i "s#^LOCAL_SRC_FILES.*#& \$(wildcard \$(LOCAL_PATH)/../../../../../../$SB_SRC/*.cpp)#" "$ANDROID_BUILD_DIR/$ANDROID_MK"
sed -i "s#^LOCAL_SRC_FILES.*#& \$(wildcard \$(LOCAL_PATH)/../../../../../../$SB_LIB_SRC/sdl2-gfx/*.c)#" "$ANDROID_BUILD_DIR/$ANDROID_MK"
sed -i "s/\(name=\)\"SDLActivity\"/\1\"$ANDROID_CLASS\"/" "$ANDROID_BUILD_DIR/$ANDROID_MANIFEST"
sed -i "s/Game/$ANDROID_APP_NAME/" "$ANDROID_BUILD_DIR/app/src/main/res/values/strings.xml"

View File

@ -671,6 +671,73 @@ std::string sb::file_to_string(const fs::path& path)
return contents;
}
fs::path sb::copy_file(fs::path from, fs::path to, bool overwrite_ok)
{
/* Open source */
SDL_RWops* source_rw;
fs::path destination;
if ((source_rw = SDL_RWFromFile(from.c_str(), "r")) != nullptr)
{
std::ostringstream message;
message << "Copying " << (SDL_RWsize(source_rw) / 1000) << " KB from " << from << " to " << to;
sb::Log::log(message);
/* Allocate storage for source contents */
std::string content;
content.resize(SDL_RWsize(source_rw));
/* Read entire contents in one call and ensure the entire size in bytes was read. */
if (SDL_RWread(source_rw, content.data(), 1, SDL_RWsize(source_rw)) == static_cast<std::size_t>(SDL_RWsize(source_rw)))
{
SDL_RWclose(source_rw);
/* Append the filename of the source file to the destination path if destination is a directory. */
if (fs::is_directory(to))
{
to /= from.filename();
}
if (!fs::exists(to) || overwrite_ok)
{
/* Open destination, write entire contents in one call and ensure the entire size in bytes was written. */
SDL_RWops* to_rw;
if ((to_rw = SDL_RWFromFile(to.c_str(), "w")) != nullptr)
{
if ((SDL_RWwrite(to_rw, content.data(), 1, content.size())) == content.size())
{
std::ostringstream message;
message << "Wrote CA bundle to internal storage at " << to;
sb::Log::log(message);
destination = to;
}
else
{
sb::Log::sdl_error("Error writing to internal storage");
}
SDL_RWclose(to_rw);
}
else
{
sb::Log::sdl_error("Error opening internal storage for writing");
}
}
else
{
sb::Log::log("Could not copy file: destination already exists and overwrite was not set", sb::Log::WARN);
}
}
else
{
sb::Log::sdl_error("Error reading file");
}
}
else
{
sb::Log::sdl_error("Error getting file handle");
}
return destination;
}
int SDL_SetRenderDrawColor(SDL_Renderer* renderer, const Color& color)
{
return SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);

View File

@ -8,8 +8,7 @@
| ~~~~~~~ BOX |/
+-------------*/
#ifndef SB_EXTENSION_H_
#define SB_EXTENSION_H_
#pragma once
/* For logging pre-SPACEBOX messages in sb::file_to_string */
#if defined(__ANDROID__) || defined(ANDROID)
@ -90,9 +89,32 @@ namespace sb
* the build directory.
* @return std::string containing file contents
*/
std::string file_to_string(const fs::path&);
std::string file_to_string(const fs::path& path);
/* Returns an unsorted vector of keys from the passed map */
/*!
* Copy a file from one path to another.
*
* If the destination is a directory, the file will be copied into the directory with the same filename. If the destination exists, the
* `overwrite_ok` argument controls whether or not it is overwritten.
*
* If the source or destination can't be opened, read, or written, information will be written to the log, and an empty fs::path object
* will be returned.
*
* On Android, `source` can be a file in an APK's `assets/` directory if the destination is a writable path on the Android internal
* or external filesystem, meaning this can be used to copy assets from an APK onto the Android filesystem.
*
* This uses SDL's RWops to support cross platform copying.
*
* @param to Path to a file to be copied
* @param from Path to the destination, either a file or directory
* @param overwrite_ok Only overwrite an existing destination if this is `true`
* @return Return the path to copied file if successful, or an empty fs::path otherwise
*/
fs::path copy_file(fs::path to, fs::path from, bool overwrite_ok = false);
/*!
* Return an unsorted vector of keys from the passed map.
*/
template<typename Key, typename Value, template <typename...> class Map>
std::vector<Key> get_keys(const Map<Key, Value>& map)
{
@ -269,5 +291,3 @@ namespace std
return out;
}
}
#endif