mirror of
https://github.com/portapack-mayhem/mayhem-firmware.git
synced 2024-12-13 03:34:35 +00:00
First pass at custom app-settings support (#1381)
* First draft of custom app settings support. * WIP new settings * Working per-app custom settings * Revert design to use "bound settings"
This commit is contained in:
parent
a4636d7872
commit
63f99742fc
@ -23,7 +23,9 @@
|
|||||||
|
|
||||||
#include "app_settings.hpp"
|
#include "app_settings.hpp"
|
||||||
|
|
||||||
|
#include "convert.hpp"
|
||||||
#include "file.hpp"
|
#include "file.hpp"
|
||||||
|
#include "file_reader.hpp"
|
||||||
#include "portapack.hpp"
|
#include "portapack.hpp"
|
||||||
#include "portapack_persistent_memory.hpp"
|
#include "portapack_persistent_memory.hpp"
|
||||||
#include "utility.hpp"
|
#include "utility.hpp"
|
||||||
@ -36,6 +38,126 @@ namespace fs = std::filesystem;
|
|||||||
using namespace portapack;
|
using namespace portapack;
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
fs::path get_settings_path(const std::string& app_name) {
|
||||||
|
return fs::path{u"/SETTINGS"} / app_name + u".ini";
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void BoundSetting::parse(std::string_view value) {
|
||||||
|
switch (type_) {
|
||||||
|
case SettingType::I64:
|
||||||
|
parse_int(value, as<int64_t>());
|
||||||
|
break;
|
||||||
|
case SettingType::I32:
|
||||||
|
parse_int(value, as<int32_t>());
|
||||||
|
break;
|
||||||
|
case SettingType::U32:
|
||||||
|
parse_int(value, as<uint32_t>());
|
||||||
|
break;
|
||||||
|
case SettingType::U8:
|
||||||
|
parse_int(value, as<uint8_t>());
|
||||||
|
break;
|
||||||
|
case SettingType::String:
|
||||||
|
as<std::string>() = std::string{value};
|
||||||
|
break;
|
||||||
|
case SettingType::Bool: {
|
||||||
|
int parsed = 0;
|
||||||
|
parse_int(value, parsed);
|
||||||
|
as<bool>() = (parsed != 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void BoundSetting::write(File& file) const {
|
||||||
|
// NB: Write directly without allocations. This happens on every
|
||||||
|
// app exit when enabled so should be fast to keep the UX responsive.
|
||||||
|
StringFormatBuffer buffer;
|
||||||
|
size_t length = 0;
|
||||||
|
|
||||||
|
file.write(name_.data(), name_.length());
|
||||||
|
file.write("=", 1);
|
||||||
|
|
||||||
|
switch (type_) {
|
||||||
|
case SettingType::I64:
|
||||||
|
file.write(to_string_dec_int(as<int64_t>(), buffer, length), length);
|
||||||
|
break;
|
||||||
|
case SettingType::I32:
|
||||||
|
file.write(to_string_dec_int(as<int32_t>(), buffer, length), length);
|
||||||
|
break;
|
||||||
|
case SettingType::U32:
|
||||||
|
file.write(to_string_dec_uint(as<uint32_t>(), buffer, length), length);
|
||||||
|
break;
|
||||||
|
case SettingType::U8:
|
||||||
|
file.write(to_string_dec_uint(as<uint8_t>(), buffer, length), length);
|
||||||
|
break;
|
||||||
|
case SettingType::String: {
|
||||||
|
const auto& str = as<std::string>();
|
||||||
|
file.write(str.data(), str.length());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SettingType::Bool:
|
||||||
|
file.write(as<bool>() ? "1" : "0", 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.write("\r\n", 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsStore::SettingsStore(std::string_view store_name, SettingBindings bindings)
|
||||||
|
: store_name_{store_name}, bindings_{bindings} {
|
||||||
|
load_settings(store_name_, bindings_);
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsStore::~SettingsStore() {
|
||||||
|
save_settings(store_name_, bindings_);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool load_settings(std::string_view store_name, SettingBindings& bindings) {
|
||||||
|
File f;
|
||||||
|
auto path = get_settings_path(std::string{store_name});
|
||||||
|
|
||||||
|
auto error = f.open(path);
|
||||||
|
if (error)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto reader = FileLineReader(f);
|
||||||
|
for (const auto& line : reader) {
|
||||||
|
auto cols = split_string(line, '=');
|
||||||
|
|
||||||
|
if (cols.size() != 2)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Find a binding with the name.
|
||||||
|
auto it = std::find_if(
|
||||||
|
bindings.begin(), bindings.end(),
|
||||||
|
[name = cols[0]](auto& bound_setting) {
|
||||||
|
return name == bound_setting.name();
|
||||||
|
});
|
||||||
|
|
||||||
|
// If found, parse the value.
|
||||||
|
if (it != bindings.end())
|
||||||
|
it->parse(cols[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool save_settings(std::string_view store_name, const SettingBindings& bindings) {
|
||||||
|
File f;
|
||||||
|
auto path = get_settings_path(std::string{store_name});
|
||||||
|
|
||||||
|
auto error = f.create(path);
|
||||||
|
if (error)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (const auto& bound_setting : bindings)
|
||||||
|
bound_setting.write(f);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
namespace app_settings {
|
namespace app_settings {
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
@ -64,10 +186,6 @@ static void write_setting(File& file, std::string_view setting_name, const T& va
|
|||||||
file.write("\r\n", 2);
|
file.write("\r\n", 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static fs::path get_settings_path(const std::string& app_name) {
|
|
||||||
return fs::path{u"/SETTINGS"} / app_name + u".ini";
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace setting {
|
namespace setting {
|
||||||
constexpr std::string_view baseband_bandwidth = "baseband_bandwidth="sv;
|
constexpr std::string_view baseband_bandwidth = "baseband_bandwidth="sv;
|
||||||
constexpr std::string_view sampling_rate = "sampling_rate="sv;
|
constexpr std::string_view sampling_rate = "sampling_rate="sv;
|
||||||
@ -88,13 +206,6 @@ constexpr std::string_view squelch = "squelch="sv;
|
|||||||
constexpr std::string_view volume = "volume="sv;
|
constexpr std::string_view volume = "volume="sv;
|
||||||
} // namespace setting
|
} // namespace setting
|
||||||
|
|
||||||
// TODO: Only load/save values that are declared used.
|
|
||||||
// This will prevent switching apps from changing setting unnecessarily.
|
|
||||||
// TODO: Track which values are actually read.
|
|
||||||
// TODO: Maybe just use a dictionary which would allow for custom settings.
|
|
||||||
// TODO: Create a control value binding which will allow controls to
|
|
||||||
// be declaratively bound to a setting and persistence will be magic.
|
|
||||||
|
|
||||||
ResultCode load_settings(const std::string& app_name, AppSettings& settings) {
|
ResultCode load_settings(const std::string& app_name, AppSettings& settings) {
|
||||||
if (!portapack::persistent_memory::load_app_settings())
|
if (!portapack::persistent_memory::load_app_settings())
|
||||||
return ResultCode::SettingsDisabled;
|
return ResultCode::SettingsDisabled;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
* Copyright (C) 2015 Jared Boone, ShareBrained Technology, Inc.
|
||||||
* Copyright (C) 2016 Furrtek
|
* Copyright (C) 2016 Furrtek
|
||||||
* Copyright (C) 2022 Arjan Onwezen
|
* Copyright (C) 2022 Arjan Onwezen
|
||||||
|
* Copyright (C) 2023 Kyle Reed
|
||||||
*
|
*
|
||||||
* This file is part of PortaPack.
|
* This file is part of PortaPack.
|
||||||
*
|
*
|
||||||
@ -27,12 +28,77 @@
|
|||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
#include "file.hpp"
|
#include "file.hpp"
|
||||||
#include "max283x.hpp"
|
#include "max283x.hpp"
|
||||||
#include "string_format.hpp"
|
#include "string_format.hpp"
|
||||||
|
|
||||||
|
/* Represents a named setting bound to a variable instance. */
|
||||||
|
/* Using void* instead of std::variant, because variant is a pain to dispatch over. */
|
||||||
|
class BoundSetting {
|
||||||
|
/* The type of bound setting. */
|
||||||
|
enum class SettingType : uint8_t {
|
||||||
|
I64,
|
||||||
|
I32,
|
||||||
|
U32,
|
||||||
|
U8,
|
||||||
|
String,
|
||||||
|
Bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
BoundSetting(std::string_view name, int64_t* target)
|
||||||
|
: name_{name}, target_{target}, type_{SettingType::I64} {}
|
||||||
|
|
||||||
|
BoundSetting(std::string_view name, int32_t* target)
|
||||||
|
: name_{name}, target_{target}, type_{SettingType::I32} {}
|
||||||
|
|
||||||
|
BoundSetting(std::string_view name, uint32_t* target)
|
||||||
|
: name_{name}, target_{target}, type_{SettingType::U32} {}
|
||||||
|
|
||||||
|
BoundSetting(std::string_view name, uint8_t* target)
|
||||||
|
: name_{name}, target_{target}, type_{SettingType::U8} {}
|
||||||
|
|
||||||
|
BoundSetting(std::string_view name, std::string* target)
|
||||||
|
: name_{name}, target_{target}, type_{SettingType::String} {}
|
||||||
|
|
||||||
|
BoundSetting(std::string_view name, bool* target)
|
||||||
|
: name_{name}, target_{target}, type_{SettingType::Bool} {}
|
||||||
|
|
||||||
|
std::string_view name() const { return name_; }
|
||||||
|
void parse(std::string_view value);
|
||||||
|
void write(File& file) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename T>
|
||||||
|
constexpr auto& as() const {
|
||||||
|
return *reinterpret_cast<T*>(target_);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view name_;
|
||||||
|
void* target_;
|
||||||
|
SettingType type_;
|
||||||
|
};
|
||||||
|
|
||||||
|
using SettingBindings = std::vector<BoundSetting>;
|
||||||
|
|
||||||
|
/* RAII wrapper for Settings that loads/saves to the SD card. */
|
||||||
|
class SettingsStore {
|
||||||
|
public:
|
||||||
|
SettingsStore(std::string_view store_name, SettingBindings bindings);
|
||||||
|
~SettingsStore();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string_view store_name_;
|
||||||
|
SettingBindings bindings_;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool load_settings(std::string_view store_name, SettingBindings& bindings);
|
||||||
|
bool save_settings(std::string_view store_name, const SettingBindings& bindings);
|
||||||
|
|
||||||
namespace app_settings {
|
namespace app_settings {
|
||||||
|
|
||||||
enum class ResultCode : uint8_t {
|
enum class ResultCode : uint8_t {
|
||||||
|
@ -71,6 +71,8 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
|
|||||||
if (!settings_.loaded())
|
if (!settings_.loaded())
|
||||||
field_frequency.set_value(initial_target_frequency);
|
field_frequency.set_value(initial_target_frequency);
|
||||||
|
|
||||||
|
check_log.set_value(enable_logging);
|
||||||
|
|
||||||
receiver_model.enable();
|
receiver_model.enable();
|
||||||
|
|
||||||
// TODO: app setting instead?
|
// TODO: app setting instead?
|
||||||
@ -88,7 +90,6 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav)
|
|||||||
logger->append(LOG_ROOT_DIR "/POCSAG.TXT");
|
logger->append(LOG_ROOT_DIR "/POCSAG.TXT");
|
||||||
|
|
||||||
audio::output::start();
|
audio::output::start();
|
||||||
|
|
||||||
baseband::set_pocsag();
|
baseband::set_pocsag();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,8 +100,9 @@ void POCSAGAppView::focus() {
|
|||||||
POCSAGAppView::~POCSAGAppView() {
|
POCSAGAppView::~POCSAGAppView() {
|
||||||
audio::output::stop();
|
audio::output::stop();
|
||||||
|
|
||||||
// Save ignored address
|
// Save settings.
|
||||||
persistent_memory::set_pocsag_ignore_address(sym_ignore.value_dec_u32());
|
persistent_memory::set_pocsag_ignore_address(sym_ignore.value_dec_u32());
|
||||||
|
enable_logging = check_log.value();
|
||||||
|
|
||||||
receiver_model.disable();
|
receiver_model.disable();
|
||||||
baseband::shutdown();
|
baseband::shutdown();
|
||||||
|
@ -65,7 +65,14 @@ class POCSAGAppView : public View {
|
|||||||
NavigationView& nav_;
|
NavigationView& nav_;
|
||||||
RxRadioState radio_state_{};
|
RxRadioState radio_state_{};
|
||||||
app_settings::SettingsManager settings_{
|
app_settings::SettingsManager settings_{
|
||||||
"rx_pocsag", app_settings::Mode::RX};
|
"rx_pocsag",
|
||||||
|
app_settings::Mode::RX};
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
bool enable_logging = false;
|
||||||
|
SettingsStore settings_store_{
|
||||||
|
"rx_pocsag_ui",
|
||||||
|
{{"enable_logging", &enable_logging}}};
|
||||||
|
|
||||||
uint32_t last_address = 0xFFFFFFFF;
|
uint32_t last_address = 0xFFFFFFFF;
|
||||||
pocsag::POCSAGState pocsag_state{};
|
pocsag::POCSAGState pocsag_state{};
|
||||||
|
@ -360,6 +360,8 @@ TextEditorView::TextEditorView(NavigationView& nav)
|
|||||||
&text_size,
|
&text_size,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
viewer.set_font_zoom(enable_zoom);
|
||||||
|
|
||||||
viewer.on_select = [this]() {
|
viewer.on_select = [this]() {
|
||||||
// Treat as if menu button was pressed.
|
// Treat as if menu button was pressed.
|
||||||
if (button_menu.on_select)
|
if (button_menu.on_select)
|
||||||
@ -382,7 +384,7 @@ TextEditorView::TextEditorView(NavigationView& nav)
|
|||||||
};
|
};
|
||||||
|
|
||||||
menu.on_zoom() = [this]() {
|
menu.on_zoom() = [this]() {
|
||||||
viewer.toggle_font_zoom();
|
enable_zoom = viewer.toggle_font_zoom();
|
||||||
refresh_ui();
|
refresh_ui();
|
||||||
hide_menu(true);
|
hide_menu(true);
|
||||||
};
|
};
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
#include "ui_styles.hpp"
|
#include "ui_styles.hpp"
|
||||||
#include "ui_widget.hpp"
|
#include "ui_widget.hpp"
|
||||||
|
|
||||||
|
#include "app_settings.hpp"
|
||||||
#include "file_wrapper.hpp"
|
#include "file_wrapper.hpp"
|
||||||
#include "optional.hpp"
|
#include "optional.hpp"
|
||||||
|
|
||||||
@ -80,7 +81,10 @@ class TextViewer : public Widget {
|
|||||||
|
|
||||||
const Style& style() { return *font_style; }
|
const Style& style() { return *font_style; }
|
||||||
void set_font_zoom(bool zoom);
|
void set_font_zoom(bool zoom);
|
||||||
void toggle_font_zoom() { set_font_zoom(!font_zoom); };
|
bool toggle_font_zoom() {
|
||||||
|
set_font_zoom(!font_zoom);
|
||||||
|
return font_zoom;
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool font_zoom{};
|
bool font_zoom{};
|
||||||
@ -220,6 +224,12 @@ class TextEditorView : public View {
|
|||||||
void on_show() override;
|
void on_show() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
// Settings
|
||||||
|
bool enable_zoom = false;
|
||||||
|
SettingsStore settings_store_{
|
||||||
|
"notepad",
|
||||||
|
{{"enable_zoom", &enable_zoom}}};
|
||||||
|
|
||||||
static constexpr size_t max_edit_length = 1024;
|
static constexpr size_t max_edit_length = 1024;
|
||||||
std::string edit_line_buffer_{};
|
std::string edit_line_buffer_{};
|
||||||
|
|
||||||
|
@ -270,18 +270,18 @@ std::string to_freqman_string(const freqman_entry& entry) {
|
|||||||
|
|
||||||
switch (entry.type) {
|
switch (entry.type) {
|
||||||
case freqman_type::Single:
|
case freqman_type::Single:
|
||||||
append_field("f", to_string_dec_uint64(entry.frequency_a));
|
append_field("f", to_string_dec_uint(entry.frequency_a));
|
||||||
break;
|
break;
|
||||||
case freqman_type::Range:
|
case freqman_type::Range:
|
||||||
append_field("a", to_string_dec_uint64(entry.frequency_a));
|
append_field("a", to_string_dec_uint(entry.frequency_a));
|
||||||
append_field("b", to_string_dec_uint64(entry.frequency_b));
|
append_field("b", to_string_dec_uint(entry.frequency_b));
|
||||||
|
|
||||||
if (is_valid(entry.step))
|
if (is_valid(entry.step))
|
||||||
append_field("s", freqman_entry_get_step_string_short(entry.step));
|
append_field("s", freqman_entry_get_step_string_short(entry.step));
|
||||||
break;
|
break;
|
||||||
case freqman_type::HamRadio:
|
case freqman_type::HamRadio:
|
||||||
append_field("r", to_string_dec_uint64(entry.frequency_a));
|
append_field("r", to_string_dec_uint(entry.frequency_a));
|
||||||
append_field("t", to_string_dec_uint64(entry.frequency_b));
|
append_field("t", to_string_dec_uint(entry.frequency_b));
|
||||||
|
|
||||||
if (is_valid(entry.tone))
|
if (is_valid(entry.tone))
|
||||||
append_field("c", tonekey::tone_key_value_string(entry.tone));
|
append_field("c", tonekey::tone_key_value_string(entry.tone));
|
||||||
|
@ -59,30 +59,37 @@ static char* to_string_dec_uint_pad_internal(
|
|||||||
return q;
|
return q;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Int>
|
static char* to_string_dec_uint_internal(uint64_t n, StringFormatBuffer& buffer, size_t& length) {
|
||||||
char* to_string_dec_uint_internal(Int n, StringFormatBuffer& buffer, size_t& length) {
|
|
||||||
auto end = &buffer.back();
|
auto end = &buffer.back();
|
||||||
auto start = to_string_dec_uint_internal(end, n);
|
auto start = to_string_dec_uint_internal(end, n);
|
||||||
length = end - start;
|
length = end - start;
|
||||||
return start;
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
char* to_string_dec_uint(uint32_t n, StringFormatBuffer& buffer, size_t& length) {
|
char* to_string_dec_uint(uint64_t n, StringFormatBuffer& buffer, size_t& length) {
|
||||||
return to_string_dec_uint_internal(n, buffer, length);
|
return to_string_dec_uint_internal(n, buffer, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
char* to_string_dec_uint64(uint64_t n, StringFormatBuffer& buffer, size_t& length) {
|
char* to_string_dec_int(int64_t n, StringFormatBuffer& buffer, size_t& length) {
|
||||||
return to_string_dec_uint_internal(n, buffer, length);
|
bool negative = n < 0;
|
||||||
|
auto start = to_string_dec_uint(negative ? -n : n, buffer, length);
|
||||||
|
|
||||||
|
if (negative) {
|
||||||
|
*(--start) = '-';
|
||||||
|
++length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return start;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string to_string_dec_uint(uint32_t n) {
|
std::string to_string_dec_int(int64_t n) {
|
||||||
StringFormatBuffer b{};
|
StringFormatBuffer b{};
|
||||||
size_t len{};
|
size_t len{};
|
||||||
char* str = to_string_dec_uint(n, b, len);
|
char* str = to_string_dec_int(n, b, len);
|
||||||
return std::string(str, len);
|
return std::string(str, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string to_string_dec_uint64(uint64_t n) {
|
std::string to_string_dec_uint(uint64_t n) {
|
||||||
StringFormatBuffer b{};
|
StringFormatBuffer b{};
|
||||||
size_t len{};
|
size_t len{};
|
||||||
char* str = to_string_dec_uint(n, b, len);
|
char* str = to_string_dec_uint(n, b, len);
|
||||||
@ -194,14 +201,14 @@ std::string to_string_rounded_freq(const uint64_t f, int8_t precision) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (precision < 1) {
|
if (precision < 1) {
|
||||||
final_str = to_string_dec_uint64(f / 1000000);
|
final_str = to_string_dec_uint(f / 1000000);
|
||||||
} else {
|
} else {
|
||||||
if (precision > 6)
|
if (precision > 6)
|
||||||
precision = 6;
|
precision = 6;
|
||||||
|
|
||||||
uint32_t divisor = pow10[6 - precision];
|
uint32_t divisor = pow10[6 - precision];
|
||||||
|
|
||||||
final_str = to_string_dec_uint64(f / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
|
final_str = to_string_dec_uint(f / 1000000) + "." + to_string_dec_int(((f + (divisor / 2)) / divisor) % pow10[precision], precision, '0');
|
||||||
}
|
}
|
||||||
return final_str;
|
return final_str;
|
||||||
}
|
}
|
||||||
|
@ -43,17 +43,17 @@ const char unit_prefix[7]{'n', 'u', 'm', 0, 'k', 'M', 'G'};
|
|||||||
|
|
||||||
using StringFormatBuffer = std::array<char, 24>;
|
using StringFormatBuffer = std::array<char, 24>;
|
||||||
|
|
||||||
/* uint conversion without memory allocations. */
|
/* Integer conversion without memory allocations. */
|
||||||
char* to_string_dec_uint(uint32_t n, StringFormatBuffer& buffer, size_t& length);
|
char* to_string_dec_int(int64_t n, StringFormatBuffer& buffer, size_t& length);
|
||||||
char* to_string_dec_uint64(uint64_t n, StringFormatBuffer& buffer, size_t& length);
|
char* to_string_dec_uint(uint64_t n, StringFormatBuffer& buffer, size_t& length);
|
||||||
|
|
||||||
std::string to_string_dec_uint(uint32_t n);
|
std::string to_string_dec_int(int64_t n);
|
||||||
std::string to_string_dec_uint64(uint64_t n);
|
std::string to_string_dec_uint(uint64_t n);
|
||||||
|
|
||||||
// TODO: Allow l=0 to not fill/justify? Already using this way in ui_spectrum.hpp...
|
// TODO: Allow l=0 to not fill/justify? Already using this way in ui_spectrum.hpp...
|
||||||
std::string to_string_bin(const uint32_t n, const uint8_t l = 0);
|
std::string to_string_bin(const uint32_t n, const uint8_t l = 0);
|
||||||
std::string to_string_dec_uint(const uint32_t n, const int32_t l, const char fill = ' ');
|
std::string to_string_dec_uint(const uint32_t n, const int32_t l, const char fill = ' ');
|
||||||
std::string to_string_dec_int(const int32_t n, const int32_t l = 0, const char fill = 0);
|
std::string to_string_dec_int(const int32_t n, const int32_t l, const char fill = 0);
|
||||||
std::string to_string_decimal(float decimal, int8_t precision);
|
std::string to_string_decimal(float decimal, int8_t precision);
|
||||||
|
|
||||||
std::string to_string_hex(const uint64_t n, const int32_t l = 0);
|
std::string to_string_hex(const uint64_t n, const int32_t l = 0);
|
||||||
|
@ -99,4 +99,10 @@ TEST_CASE("It should convert 8-bit.") {
|
|||||||
CHECK_EQ(val, 123);
|
CHECK_EQ(val, 123);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("It should convert negative.") {
|
||||||
|
int8_t val = 0;
|
||||||
|
REQUIRE(parse_int("-64", val));
|
||||||
|
CHECK_EQ(val, -64);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_SUITE_END();
|
TEST_SUITE_END();
|
@ -24,12 +24,27 @@
|
|||||||
|
|
||||||
/* TODO: Tests for all string_format functions. */
|
/* TODO: Tests for all string_format functions. */
|
||||||
|
|
||||||
TEST_CASE("to_string_dec_uint64 returns correct value.") {
|
TEST_CASE("to_string_dec_int returns correct value.") {
|
||||||
CHECK_EQ(to_string_dec_uint64(0), "0");
|
CHECK_EQ(to_string_dec_int(0), "0");
|
||||||
CHECK_EQ(to_string_dec_uint64(1), "1");
|
CHECK_EQ(to_string_dec_int(1), "1");
|
||||||
CHECK_EQ(to_string_dec_uint64(1'000'000), "1000000");
|
CHECK_EQ(to_string_dec_int(-1), "-1");
|
||||||
CHECK_EQ(to_string_dec_uint64(1'234'567'890), "1234567890");
|
CHECK_EQ(to_string_dec_int(1'000'000), "1000000");
|
||||||
CHECK_EQ(to_string_dec_uint64(1'234'567'891), "1234567891");
|
CHECK_EQ(to_string_dec_int(-1'000'000), "-1000000");
|
||||||
|
CHECK_EQ(to_string_dec_int(1'234'567'890), "1234567890");
|
||||||
|
CHECK_EQ(to_string_dec_int(-1'234'567'890), "-1234567890");
|
||||||
|
CHECK_EQ(to_string_dec_int(1'234'567'891), "1234567891");
|
||||||
|
CHECK_EQ(to_string_dec_int(-1'234'567'891), "-1234567891");
|
||||||
|
CHECK_EQ(to_string_dec_int(9'876'543'210), "9876543210");
|
||||||
|
CHECK_EQ(to_string_dec_int(-9'876'543'210), "-9876543210");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("to_string_dec_uint returns correct value.") {
|
||||||
|
CHECK_EQ(to_string_dec_uint(0), "0");
|
||||||
|
CHECK_EQ(to_string_dec_uint(1), "1");
|
||||||
|
CHECK_EQ(to_string_dec_uint(1'000'000), "1000000");
|
||||||
|
CHECK_EQ(to_string_dec_uint(1'234'567'890), "1234567890");
|
||||||
|
CHECK_EQ(to_string_dec_uint(1'234'567'891), "1234567891");
|
||||||
|
CHECK_EQ(to_string_dec_uint(9'876'543'210), "9876543210");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("to_string_freq returns correct value.") {
|
TEST_CASE("to_string_freq returns correct value.") {
|
||||||
|
Loading…
Reference in New Issue
Block a user