mirror of
https://github.com/clangen/musikcube.git
synced 2024-10-02 04:52:32 +00:00
Ported missing NumberValidator and SchemaOverlay from cursespp and updated PluginOverlay to call through to SchemaOverlay
This commit is contained in:
parent
e8f9695d29
commit
aad60b7bdd
@ -79,3 +79,13 @@ static std::string u8fmt(const std::string& format, Args ... args) {
|
||||
std::snprintf(buf.get(), size, format.c_str(), args ...);
|
||||
return std::string(buf.get(), buf.get() + size - 1); /* omit the '\0' */
|
||||
}
|
||||
|
||||
static inline void u8replace(
|
||||
std::string& input, const std::string& find, const std::string& replace)
|
||||
{
|
||||
size_t pos = input.find(find);
|
||||
while (pos != std::string::npos) {
|
||||
input.replace(pos, find.size(), replace);
|
||||
pos = input.find(find, pos + replace.size());
|
||||
}
|
||||
}
|
||||
|
@ -49,11 +49,12 @@ set (CUBE_SRCS
|
||||
./cursespp/ListWindow.cpp
|
||||
./cursespp/MultiLineEntry.cpp
|
||||
./cursespp/OverlayStack.cpp
|
||||
./cursespp/ShortcutsWindow.cpp
|
||||
./cursespp/SchemaOverlay.cpp
|
||||
./cursespp/Screen.cpp
|
||||
./cursespp/ScrollableWindow.cpp
|
||||
./cursespp/ScrollAdapterBase.cpp
|
||||
./cursespp/Scrollbar.cpp
|
||||
./cursespp/ShortcutsWindow.cpp
|
||||
./cursespp/SimpleScrollAdapter.cpp
|
||||
./cursespp/SingleLineEntry.cpp
|
||||
./cursespp/Text.cpp
|
||||
|
@ -43,19 +43,11 @@
|
||||
#include <core/sdk/ISchema.h>
|
||||
|
||||
#include <cursespp/App.h>
|
||||
#include <cursespp/Colors.h>
|
||||
#include <cursespp/DialogOverlay.h>
|
||||
#include <cursespp/InputOverlay.h>
|
||||
#include <cursespp/ListOverlay.h>
|
||||
#include <cursespp/ScrollAdapterBase.h>
|
||||
#include <cursespp/SingleLineEntry.h>
|
||||
#include <cursespp/Text.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <ostream>
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include <cursespp/SchemaOverlay.h>
|
||||
|
||||
using namespace musik::core;
|
||||
using namespace musik::core::sdk;
|
||||
@ -73,347 +65,14 @@ struct PluginInfo {
|
||||
|
||||
using PluginInfoPtr = std::shared_ptr<PluginInfo>;
|
||||
using PluginList = std::vector<PluginInfoPtr>;
|
||||
using PrefsPtr = std::shared_ptr<Preferences>;
|
||||
using SinglePtr = std::shared_ptr<SingleLineEntry>;
|
||||
using SchemaPtr = std::shared_ptr<ISchema>;
|
||||
|
||||
#define DEFAULT(type) reinterpret_cast<const ISchema::type*>(entry)->defaultValue
|
||||
|
||||
static size_t DEFAULT_INPUT_WIDTH = 26;
|
||||
|
||||
static std::string stringValueForDouble(const double value, const int precision = 2) {
|
||||
std::ostringstream out;
|
||||
out << std::fixed << std::setprecision(precision) << value;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
static std::function<std::string(int)> INT_FORMATTER =
|
||||
[](int value) -> std::string {
|
||||
return std::to_string(value);
|
||||
};
|
||||
|
||||
static std::function<std::string(double)> doubleFormatter(int precision) {
|
||||
return [precision](double value) -> std::string {
|
||||
return stringValueForDouble(value, precision);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool bounded(T minimum, T maximum) {
|
||||
return
|
||||
minimum != std::numeric_limits<T>::min() &&
|
||||
maximum != std::numeric_limits<T>::max();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string numberInputTitle(
|
||||
std::string keyName,
|
||||
T minimum,
|
||||
T maximum,
|
||||
std::function<std::string(T)> formatter)
|
||||
{
|
||||
if (bounded(minimum, maximum)) {
|
||||
return keyName + " (" + formatter(minimum)
|
||||
+ " - " + formatter(maximum) + ")";
|
||||
}
|
||||
return keyName;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::string stringValueFor(
|
||||
PrefsPtr prefs,
|
||||
const T* entry,
|
||||
ISchema::Type type,
|
||||
const std::string& name)
|
||||
{
|
||||
switch (type) {
|
||||
case ISchema::Type::Bool:
|
||||
return prefs->GetBool(name, DEFAULT(BoolEntry)) ? "true" : "false";
|
||||
case ISchema::Type::Int:
|
||||
return std::to_string(prefs->GetInt(name, DEFAULT(IntEntry)));
|
||||
case ISchema::Type::Double: {
|
||||
auto doubleEntry = reinterpret_cast<const ISchema::DoubleEntry*>(entry);
|
||||
auto defaultValue = doubleEntry->defaultValue;
|
||||
auto precision = doubleEntry->precision;
|
||||
return stringValueForDouble(prefs->GetDouble(name, defaultValue), precision);
|
||||
}
|
||||
case ISchema::Type::String:
|
||||
return prefs->GetString(name, DEFAULT(StringEntry));
|
||||
case ISchema::Type::Enum:
|
||||
return prefs->GetString(name, DEFAULT(EnumEntry));
|
||||
}
|
||||
throw std::runtime_error("invalid type passed to stringValueFor!");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::string stringValueFor(PrefsPtr prefs, const T* entry) {
|
||||
return stringValueFor(prefs, entry, entry->entry.type, entry->entry.name);
|
||||
}
|
||||
|
||||
static std::string stringValueFor(PrefsPtr prefs, const ISchema::Entry* entry) {
|
||||
return stringValueFor(prefs, entry, entry->type, entry->name);
|
||||
}
|
||||
|
||||
class StringListAdapter : public ScrollAdapterBase {
|
||||
public:
|
||||
StringListAdapter(std::vector<std::string>& items) : items(items) { }
|
||||
std::string At(const size_t index) { return items[index]; }
|
||||
virtual ~StringListAdapter() { }
|
||||
virtual size_t GetEntryCount() override { return items.size(); }
|
||||
virtual EntryPtr GetEntry(cursespp::ScrollableWindow* window, size_t index) override {
|
||||
auto entry = std::make_shared<SingleLineEntry>(
|
||||
text::Ellipsize(items[index], window->GetWidth()));
|
||||
|
||||
entry->SetAttrs(Color::Default);
|
||||
if (index == window->GetScrollPosition().logicalIndex) {
|
||||
entry->SetAttrs(Color::ListItemHighlighted);
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
private:
|
||||
std::vector<std::string> items;
|
||||
};
|
||||
|
||||
class SchemaAdapter: public ScrollAdapterBase {
|
||||
public:
|
||||
SchemaAdapter(PrefsPtr prefs, SchemaPtr schema): prefs(prefs), schema(schema) {
|
||||
}
|
||||
|
||||
virtual ~SchemaAdapter() {
|
||||
}
|
||||
|
||||
bool Changed() const {
|
||||
return this->changed;
|
||||
}
|
||||
|
||||
virtual size_t GetEntryCount() override {
|
||||
return schema->Count();
|
||||
}
|
||||
|
||||
virtual EntryPtr GetEntry(cursespp::ScrollableWindow* window, size_t index) override {
|
||||
auto entry = schema->At(index);
|
||||
|
||||
std::string name = entry->name;
|
||||
std::string value = stringValueFor(prefs, entry);
|
||||
int width = window->GetContentWidth();
|
||||
int avail = std::max(0, width - int(u8cols(name)) - 1 - 1);
|
||||
auto display = " " + name + " " + text::Align(value + " ", text::AlignRight, avail);
|
||||
|
||||
SinglePtr result = SinglePtr(new SingleLineEntry(text::Ellipsize(display, width)));
|
||||
|
||||
result->SetAttrs(Color::Default);
|
||||
if (index == window->GetScrollPosition().logicalIndex) {
|
||||
result->SetAttrs(Color::ListItemHighlighted);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ShowOverlay(size_t index) {
|
||||
auto entry = schema->At(index);
|
||||
switch (entry->type) {
|
||||
case ISchema::Type::Bool:
|
||||
return ShowBoolOverlay(reinterpret_cast<const ISchema::BoolEntry*>(entry));
|
||||
case ISchema::Type::Int:
|
||||
return ShowIntOverlay(reinterpret_cast<const ISchema::IntEntry*>(entry));
|
||||
case ISchema::Type::Double:
|
||||
return ShowDoubleOverlay(reinterpret_cast<const ISchema::DoubleEntry*>(entry));
|
||||
case ISchema::Type::String:
|
||||
return ShowStringOverlay(reinterpret_cast<const ISchema::StringEntry*>(entry));
|
||||
case ISchema::Type::Enum:
|
||||
return ShowEnumOverlay(reinterpret_cast<const ISchema::EnumEntry*>(entry));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
struct NumberValidator : public InputOverlay::IValidator {
|
||||
using Formatter = std::function<std::string(T)>;
|
||||
|
||||
NumberValidator(T minimum, T maximum, Formatter formatter)
|
||||
: minimum(minimum), maximum(maximum), formatter(formatter) {
|
||||
}
|
||||
|
||||
virtual bool IsValid(const std::string& input) const override {
|
||||
try {
|
||||
double result = std::stod(input);
|
||||
if (bounded(minimum, maximum) && (result < minimum || result > maximum)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (std::invalid_argument) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const std::string ErrorMessage() const override {
|
||||
if (bounded(minimum, maximum)) {
|
||||
std::string result = _TSTR("validator_dialog_number_parse_bounded_error");
|
||||
ReplaceAll(result, "{{minimum}}", formatter(minimum));
|
||||
ReplaceAll(result, "{{maximum}}", formatter(maximum));
|
||||
return result;
|
||||
}
|
||||
return _TSTR("validator_dialog_number_parse_error");
|
||||
}
|
||||
|
||||
Formatter formatter;
|
||||
T minimum, maximum;
|
||||
};
|
||||
|
||||
void ShowListOverlay(
|
||||
const std::string& title,
|
||||
std::vector<std::string>& items,
|
||||
const std::string defaultValue,
|
||||
std::function<void(std::string)> cb)
|
||||
{
|
||||
auto stringAdapter = std::make_shared<StringListAdapter>(items);
|
||||
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
|
||||
|
||||
size_t width = u8cols(title) + 4; /* extra padding for border and spacing */
|
||||
size_t index = 0;
|
||||
|
||||
for (size_t i = 0; i < items.size(); i++) {
|
||||
auto current = items[i];
|
||||
if (current == defaultValue) {
|
||||
index = i;
|
||||
}
|
||||
width = std::max(width, u8cols(current));
|
||||
}
|
||||
|
||||
dialog->SetAdapter(stringAdapter)
|
||||
.SetTitle(title)
|
||||
.SetWidth(width)
|
||||
.SetSelectedIndex(index)
|
||||
.SetAutoDismiss(true)
|
||||
.SetItemSelectedCallback(
|
||||
[cb, stringAdapter](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
|
||||
if (cb) {
|
||||
cb(stringAdapter->At(index));
|
||||
}
|
||||
});
|
||||
|
||||
cursespp::App::Overlays().Push(dialog);
|
||||
}
|
||||
|
||||
void ShowBoolOverlay(const ISchema::BoolEntry* entry) {
|
||||
auto name = entry->entry.name;
|
||||
std::vector<std::string> items = { "true", "false" };
|
||||
ShowListOverlay(name, items, stringValueFor(prefs, entry),
|
||||
[this, name](std::string value) {
|
||||
this->prefs->SetBool(name, value == "true");
|
||||
this->changed = true;
|
||||
});
|
||||
}
|
||||
|
||||
void ShowIntOverlay(const ISchema::IntEntry* entry) {
|
||||
auto name = entry->entry.name;
|
||||
|
||||
auto title = numberInputTitle(
|
||||
name, entry->minValue, entry->maxValue, INT_FORMATTER);
|
||||
|
||||
auto validator = std::make_shared<NumberValidator<int>>(
|
||||
entry->minValue, entry->maxValue, INT_FORMATTER);
|
||||
|
||||
auto width = std::max(u8cols(title), DEFAULT_INPUT_WIDTH);
|
||||
|
||||
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
|
||||
|
||||
dialog->SetTitle(title)
|
||||
.SetText(stringValueFor(prefs, entry))
|
||||
.SetValidator(validator)
|
||||
.SetWidth(width)
|
||||
.SetInputAcceptedCallback([this, name](const std::string& value) {
|
||||
this->prefs->SetInt(name, std::stoi(value));
|
||||
this->changed = true;
|
||||
});
|
||||
|
||||
App::Overlays().Push(dialog);
|
||||
}
|
||||
|
||||
void ShowDoubleOverlay(const ISchema::DoubleEntry* entry) {
|
||||
auto name = entry->entry.name;
|
||||
|
||||
auto formatter = doubleFormatter(entry->precision);
|
||||
|
||||
auto title = numberInputTitle(
|
||||
name, entry->minValue, entry->maxValue, formatter);
|
||||
|
||||
auto validator = std::make_shared<NumberValidator<double>>(
|
||||
entry->minValue, entry->maxValue, formatter);
|
||||
|
||||
auto width = std::max(u8cols(title) + 4, DEFAULT_INPUT_WIDTH);
|
||||
|
||||
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
|
||||
|
||||
dialog->SetTitle(title)
|
||||
.SetText(stringValueFor(prefs, entry))
|
||||
.SetValidator(validator)
|
||||
.SetWidth(width)
|
||||
.SetInputAcceptedCallback([this, name](const std::string& value) {
|
||||
this->prefs->SetDouble(name, std::stod(value));
|
||||
this->changed = true;
|
||||
});
|
||||
|
||||
App::Overlays().Push(dialog);
|
||||
}
|
||||
|
||||
void ShowStringOverlay(const ISchema::StringEntry* entry) {
|
||||
auto name = entry->entry.name;
|
||||
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
|
||||
dialog->SetTitle(name)
|
||||
.SetText(stringValueFor(prefs, entry))
|
||||
.SetInputAcceptedCallback([this, name](const std::string& value) {
|
||||
this->prefs->SetString(name, value.c_str());
|
||||
this->changed = true;
|
||||
});
|
||||
App::Overlays().Push(dialog);
|
||||
}
|
||||
|
||||
void ShowEnumOverlay(const ISchema::EnumEntry* entry) {
|
||||
auto name = entry->entry.name;
|
||||
std::vector<std::string> items;
|
||||
|
||||
for (size_t i = 0; i < entry->count; i++) {
|
||||
items.push_back(entry->values[i]);
|
||||
}
|
||||
|
||||
ShowListOverlay(name, items, stringValueFor(prefs, entry),
|
||||
[this, name](std::string value) {
|
||||
this->prefs->SetString(name, value.c_str());
|
||||
this->changed = true;
|
||||
});
|
||||
}
|
||||
|
||||
PrefsPtr prefs;
|
||||
SchemaPtr schema;
|
||||
bool changed{false};
|
||||
};
|
||||
|
||||
static void showConfigureOverlay(IPlugin* plugin, SchemaPtr schema) {
|
||||
auto prefs = Preferences::ForPlugin(plugin->Name());
|
||||
std::shared_ptr<SchemaAdapter> schemaAdapter(new SchemaAdapter(prefs, schema));
|
||||
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
|
||||
|
||||
std::string title = _TSTR("settings_configure_plugin_title");
|
||||
ReplaceAll(title, "{{name}}", plugin->Name());
|
||||
|
||||
dialog->SetAdapter(schemaAdapter)
|
||||
.SetTitle(title)
|
||||
.SetWidthPercent(80)
|
||||
.SetAutoDismiss(false)
|
||||
.SetItemSelectedCallback(
|
||||
[schemaAdapter](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
|
||||
schemaAdapter->ShowOverlay(index);
|
||||
})
|
||||
.SetDismissedCallback([plugin, schemaAdapter](ListOverlay* overlay) {
|
||||
if (schemaAdapter->Changed()) {
|
||||
plugin->Reload();
|
||||
}
|
||||
});
|
||||
|
||||
cursespp::App::Overlays().Push(dialog);
|
||||
auto prefs = Preferences::ForPlugin(plugin->Name());
|
||||
SchemaOverlay::Show(title, prefs, schema, [](bool) {});
|
||||
}
|
||||
|
||||
static void showNoSchemaDialog(const std::string& name) {
|
||||
|
378
src/musikcube/cursespp/SchemaOverlay.cpp
Normal file
378
src/musikcube/cursespp/SchemaOverlay.cpp
Normal file
@ -0,0 +1,378 @@
|
||||
#include <cursespp/SchemaOverlay.h>
|
||||
|
||||
#include <core/i18n/Locale.h>
|
||||
#include <core/utfutil.h>
|
||||
|
||||
#include <cursespp/App.h>
|
||||
#include <cursespp/Colors.h>
|
||||
#include <cursespp/DialogOverlay.h>
|
||||
#include <cursespp/InputOverlay.h>
|
||||
#include <cursespp/ListOverlay.h>
|
||||
#include <cursespp/NumberValidator.h>
|
||||
#include <cursespp/Screen.h>
|
||||
#include <cursespp/ScrollAdapterBase.h>
|
||||
#include <cursespp/SingleLineEntry.h>
|
||||
#include <cursespp/Text.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
using namespace musik::core::sdk;
|
||||
using namespace musik::core::i18n;
|
||||
using namespace cursespp;
|
||||
|
||||
using SchemaPtr = SchemaOverlay::SchemaPtr;
|
||||
using PrefsPtr = SchemaOverlay::PrefsPtr;
|
||||
using SinglePtr = std::shared_ptr<SingleLineEntry>;
|
||||
|
||||
#define DEFAULT(type) reinterpret_cast<const ISchema::type*>(entry)->defaultValue
|
||||
|
||||
static size_t DEFAULT_INPUT_WIDTH = 26;
|
||||
static size_t MINIMUM_OVERLAY_WIDTH = 16;
|
||||
|
||||
static int overlayWidth() {
|
||||
return (int)(0.8f * (float) Screen::GetWidth());
|
||||
}
|
||||
|
||||
static std::string stringValueForDouble(const double value, const int precision = 2) {
|
||||
std::ostringstream out;
|
||||
out << std::fixed << std::setprecision(precision) << value;
|
||||
return out.str();
|
||||
}
|
||||
|
||||
static std::function<std::string(int)> INT_FORMATTER =
|
||||
[](int value) -> std::string {
|
||||
return std::to_string(value);
|
||||
};
|
||||
|
||||
static std::function<std::string(double)> doubleFormatter(int precision) {
|
||||
return [precision](double value) -> std::string {
|
||||
return stringValueForDouble(value, precision);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string numberInputTitle(
|
||||
std::string keyName,
|
||||
T minimum,
|
||||
T maximum,
|
||||
std::function<std::string(T)> formatter)
|
||||
{
|
||||
if (NumberValidator<T>::bounded(minimum, maximum)) {
|
||||
return keyName + " (" + formatter(minimum)
|
||||
+ " - " + formatter(maximum) + ")";
|
||||
}
|
||||
return keyName;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::string stringValueFor(
|
||||
PrefsPtr prefs,
|
||||
const T* entry,
|
||||
ISchema::Type type,
|
||||
const std::string& name)
|
||||
{
|
||||
switch (type) {
|
||||
case ISchema::Type::Bool:
|
||||
return prefs->GetBool(name, DEFAULT(BoolEntry)) ? "true" : "false";
|
||||
case ISchema::Type::Int:
|
||||
return std::to_string(prefs->GetInt(name, DEFAULT(IntEntry)));
|
||||
case ISchema::Type::Double: {
|
||||
auto doubleEntry = reinterpret_cast<const ISchema::DoubleEntry*>(entry);
|
||||
auto defaultValue = doubleEntry->defaultValue;
|
||||
auto precision = doubleEntry->precision;
|
||||
return stringValueForDouble(prefs->GetDouble(name, defaultValue), precision);
|
||||
}
|
||||
case ISchema::Type::String:
|
||||
return prefs->GetString(name, DEFAULT(StringEntry));
|
||||
case ISchema::Type::Enum:
|
||||
return prefs->GetString(name, DEFAULT(EnumEntry));
|
||||
}
|
||||
throw std::runtime_error("invalid type passed to stringValueFor!");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static std::string stringValueFor(PrefsPtr prefs, const T* entry) {
|
||||
return stringValueFor(prefs, entry, entry->entry.type, entry->entry.name);
|
||||
}
|
||||
|
||||
static std::string stringValueFor(PrefsPtr prefs, const ISchema::Entry* entry) {
|
||||
return stringValueFor(prefs, entry, entry->type, entry->name);
|
||||
}
|
||||
|
||||
class StringListAdapter : public ScrollAdapterBase {
|
||||
public:
|
||||
StringListAdapter(std::vector<std::string>& items) : items(items) { }
|
||||
std::string At(const size_t index) { return items[index]; }
|
||||
virtual ~StringListAdapter() { }
|
||||
virtual size_t GetEntryCount() override { return items.size(); }
|
||||
virtual EntryPtr GetEntry(cursespp::ScrollableWindow* window, size_t index) override {
|
||||
auto entry = std::make_shared<SingleLineEntry>(
|
||||
text::Ellipsize(items[index], window->GetWidth()));
|
||||
|
||||
entry->SetAttrs(Color(Color::Default));
|
||||
if (index == window->GetScrollPosition().logicalIndex) {
|
||||
entry->SetAttrs(Color(Color::ListItemHighlighted));
|
||||
}
|
||||
|
||||
return entry;
|
||||
}
|
||||
private:
|
||||
std::vector<std::string> items;
|
||||
};
|
||||
|
||||
class SchemaAdapter: public ScrollAdapterBase {
|
||||
public:
|
||||
SchemaAdapter(PrefsPtr prefs, SchemaPtr schema): prefs(prefs), schema(schema) {
|
||||
onChanged = [this](std::string value) {
|
||||
this->changed = true;
|
||||
};
|
||||
}
|
||||
|
||||
virtual ~SchemaAdapter() {
|
||||
}
|
||||
|
||||
bool Changed() const {
|
||||
return this->changed;
|
||||
}
|
||||
|
||||
virtual size_t GetEntryCount() override {
|
||||
return schema->Count();
|
||||
}
|
||||
|
||||
virtual EntryPtr GetEntry(cursespp::ScrollableWindow* window, size_t index) override {
|
||||
auto entry = schema->At(index);
|
||||
|
||||
std::string name = entry->name;
|
||||
std::string value = stringValueFor(prefs, entry);
|
||||
int width = window->GetContentWidth();
|
||||
int avail = std::max(0, width - int(u8cols(name)) - 1 - 1);
|
||||
auto display = " " + name + " " + text::Align(value + " ", text::AlignRight, avail);
|
||||
|
||||
SinglePtr result = SinglePtr(new SingleLineEntry(text::Ellipsize(display, width)));
|
||||
|
||||
result->SetAttrs(Color(Color::Default));
|
||||
if (index == window->GetScrollPosition().logicalIndex) {
|
||||
result->SetAttrs(Color(Color::ListItemHighlighted));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void ShowOverlay(size_t index) {
|
||||
auto entry = schema->At(index);
|
||||
switch (entry->type) {
|
||||
case ISchema::Type::Bool:
|
||||
return ShowBoolOverlay(reinterpret_cast<const ISchema::BoolEntry*>(entry));
|
||||
case ISchema::Type::Int:
|
||||
return ShowIntOverlay(reinterpret_cast<const ISchema::IntEntry*>(entry));
|
||||
case ISchema::Type::Double:
|
||||
return ShowDoubleOverlay(reinterpret_cast<const ISchema::DoubleEntry*>(entry));
|
||||
case ISchema::Type::String:
|
||||
return ShowStringOverlay(reinterpret_cast<const ISchema::StringEntry*>(entry));
|
||||
case ISchema::Type::Enum:
|
||||
return ShowEnumOverlay(reinterpret_cast<const ISchema::EnumEntry*>(entry));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void ShowBoolOverlay(const ISchema::BoolEntry* entry) {
|
||||
SchemaOverlay::ShowBoolOverlay(entry, prefs, onChanged);
|
||||
}
|
||||
|
||||
void ShowIntOverlay(const ISchema::IntEntry* entry) {
|
||||
SchemaOverlay::ShowIntOverlay(entry, prefs, onChanged);
|
||||
}
|
||||
|
||||
void ShowDoubleOverlay(const ISchema::DoubleEntry* entry) {
|
||||
SchemaOverlay::ShowDoubleOverlay(entry, prefs, onChanged);
|
||||
}
|
||||
|
||||
void ShowStringOverlay(const ISchema::StringEntry* entry) {
|
||||
SchemaOverlay::ShowStringOverlay(entry, prefs, onChanged);
|
||||
}
|
||||
|
||||
void ShowEnumOverlay(const ISchema::EnumEntry* entry) {
|
||||
SchemaOverlay::ShowEnumOverlay(entry, prefs, onChanged);
|
||||
}
|
||||
|
||||
std::function<void(std::string)> onChanged;
|
||||
PrefsPtr prefs;
|
||||
SchemaPtr schema;
|
||||
bool changed{false};
|
||||
};
|
||||
|
||||
void SchemaOverlay::ShowListOverlay(
|
||||
const std::string& title,
|
||||
std::vector<std::string>& items,
|
||||
const std::string defaultValue,
|
||||
std::function<void(std::string)> cb)
|
||||
{
|
||||
auto stringAdapter = std::make_shared<StringListAdapter>(items);
|
||||
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
|
||||
|
||||
size_t index = 0;
|
||||
for (size_t i = 0; i < items.size(); i++) {
|
||||
auto current = items[i];
|
||||
if (current == defaultValue) {
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
dialog->SetAdapter(stringAdapter)
|
||||
.SetTitle(title)
|
||||
.SetWidth(overlayWidth())
|
||||
.SetSelectedIndex(index)
|
||||
.SetAutoDismiss(true)
|
||||
.SetItemSelectedCallback(
|
||||
[cb, stringAdapter](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
|
||||
if (cb) {
|
||||
cb(stringAdapter->At(index));
|
||||
}
|
||||
});
|
||||
|
||||
cursespp::App::Overlays().Push(dialog);
|
||||
}
|
||||
|
||||
void SchemaOverlay::ShowBoolOverlay(
|
||||
const ISchema::BoolEntry* entry,
|
||||
PrefsPtr prefs,
|
||||
std::function<void(std::string)> callback)
|
||||
{
|
||||
std::string name(entry->entry.name);
|
||||
std::vector<std::string> items = { "true", "false" };
|
||||
|
||||
auto handler = [prefs, name, callback](std::string value) {
|
||||
prefs->SetBool(name, value == "true");
|
||||
if (callback) { callback(value); }
|
||||
};
|
||||
|
||||
ShowListOverlay(name, items, stringValueFor(prefs, entry), handler);
|
||||
}
|
||||
|
||||
void SchemaOverlay::ShowIntOverlay(
|
||||
const ISchema::IntEntry* entry,
|
||||
PrefsPtr prefs,
|
||||
std::function<void(std::string)> callback)
|
||||
{
|
||||
std::string name(entry->entry.name);
|
||||
|
||||
auto title = numberInputTitle(
|
||||
name, entry->minValue, entry->maxValue, INT_FORMATTER);
|
||||
|
||||
auto validator = std::make_shared<NumberValidator<int>>(
|
||||
entry->minValue, entry->maxValue, INT_FORMATTER);
|
||||
|
||||
auto handler = [prefs, name, callback](std::string value) {
|
||||
prefs->SetInt(name, (int) std::stod(value));
|
||||
if (callback) { callback(value); }
|
||||
};
|
||||
|
||||
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
|
||||
|
||||
dialog->SetTitle(title)
|
||||
.SetText(stringValueFor(prefs, entry))
|
||||
.SetValidator(validator)
|
||||
.SetWidth(overlayWidth())
|
||||
.SetInputAcceptedCallback(callback);
|
||||
|
||||
App::Overlays().Push(dialog);
|
||||
}
|
||||
|
||||
void SchemaOverlay::ShowDoubleOverlay(
|
||||
const ISchema::DoubleEntry* entry,
|
||||
PrefsPtr prefs,
|
||||
std::function<void(std::string)> callback)
|
||||
{
|
||||
std::string name(entry->entry.name);
|
||||
|
||||
auto formatter = doubleFormatter(entry->precision);
|
||||
|
||||
auto title = numberInputTitle(
|
||||
name, entry->minValue, entry->maxValue, formatter);
|
||||
|
||||
auto validator = std::make_shared<NumberValidator<double>>(
|
||||
entry->minValue, entry->maxValue, formatter);
|
||||
|
||||
auto handler = [prefs, name, callback](std::string value) {
|
||||
prefs->SetDouble(name, std::stod(value));
|
||||
if (callback) { callback(value); }
|
||||
};
|
||||
|
||||
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
|
||||
|
||||
dialog->SetTitle(title)
|
||||
.SetText(stringValueFor(prefs, entry))
|
||||
.SetValidator(validator)
|
||||
.SetWidth(overlayWidth())
|
||||
.SetInputAcceptedCallback(handler);
|
||||
|
||||
App::Overlays().Push(dialog);
|
||||
}
|
||||
|
||||
void SchemaOverlay::ShowStringOverlay(
|
||||
const ISchema::StringEntry* entry,
|
||||
PrefsPtr prefs,
|
||||
std::function<void(std::string)> callback)
|
||||
{
|
||||
std::string name(entry->entry.name);
|
||||
|
||||
auto handler = [prefs, name, callback](std::string value) {
|
||||
prefs->SetString(name, value.c_str());
|
||||
if (callback) { callback(value); }
|
||||
};
|
||||
|
||||
std::shared_ptr<InputOverlay> dialog(new InputOverlay());
|
||||
|
||||
dialog->SetTitle(name)
|
||||
.SetText(stringValueFor(prefs, entry))
|
||||
.SetWidth(overlayWidth())
|
||||
.SetInputAcceptedCallback(handler);
|
||||
|
||||
App::Overlays().Push(dialog);
|
||||
}
|
||||
|
||||
void SchemaOverlay::ShowEnumOverlay(
|
||||
const ISchema::EnumEntry* entry,
|
||||
PrefsPtr prefs,
|
||||
std::function<void(std::string)> callback)
|
||||
{
|
||||
std::string name(entry->entry.name);
|
||||
std::vector<std::string> items;
|
||||
|
||||
for (size_t i = 0; i < entry->count; i++) {
|
||||
items.push_back(entry->values[i]);
|
||||
}
|
||||
|
||||
auto handler = [prefs, name, callback](std::string value) {
|
||||
prefs->SetString(name, value.c_str());
|
||||
if (callback) { callback(value); }
|
||||
};
|
||||
|
||||
ShowListOverlay(name, items, stringValueFor(prefs, entry), handler);
|
||||
}
|
||||
|
||||
void SchemaOverlay::Show(
|
||||
const std::string& title,
|
||||
PrefsPtr prefs,
|
||||
SchemaPtr schema,
|
||||
std::function<void(bool)> callback)
|
||||
{
|
||||
std::shared_ptr<SchemaAdapter> schemaAdapter(new SchemaAdapter(prefs, schema));
|
||||
std::shared_ptr<ListOverlay> dialog(new ListOverlay());
|
||||
|
||||
dialog->SetAdapter(schemaAdapter)
|
||||
.SetTitle(title)
|
||||
.SetWidthPercent(80)
|
||||
.SetAutoDismiss(false)
|
||||
.SetItemSelectedCallback(
|
||||
[schemaAdapter](ListOverlay* overlay, IScrollAdapterPtr adapter, size_t index) {
|
||||
schemaAdapter->ShowOverlay(index);
|
||||
})
|
||||
.SetDismissedCallback([callback, schemaAdapter](ListOverlay* overlay) {
|
||||
if (callback) {
|
||||
callback(schemaAdapter->Changed());
|
||||
}
|
||||
});
|
||||
|
||||
cursespp::App::Overlays().Push(dialog);
|
||||
}
|
85
src/musikcube/cursespp/cursespp/NumberValidator.h
Normal file
85
src/musikcube/cursespp/cursespp/NumberValidator.h
Normal file
@ -0,0 +1,85 @@
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (c) 2007-2019 musikcube team
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cursespp/InputOverlay.h>
|
||||
#include <core/utfutil.h>
|
||||
|
||||
namespace cursespp {
|
||||
|
||||
template <typename T>
|
||||
struct NumberValidator : public InputOverlay::IValidator {
|
||||
using Formatter = std::function<std::string(T)>;
|
||||
|
||||
NumberValidator(T minimum, T maximum, Formatter formatter)
|
||||
: minimum(minimum), maximum(maximum), formatter(formatter) {
|
||||
}
|
||||
|
||||
virtual bool IsValid(const std::string& input) const override {
|
||||
try {
|
||||
double result = std::stod(input);
|
||||
if (bounded(minimum, maximum) && (result < minimum || result > maximum)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (std::invalid_argument) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual const std::string ErrorMessage() const override {
|
||||
if (bounded(minimum, maximum)) {
|
||||
std::string result = _TSTR("validator_dialog_number_parse_bounded_error");
|
||||
u8replace(result, "{{minimum}}", formatter(minimum));
|
||||
u8replace(result, "{{maximum}}", formatter(maximum));
|
||||
return result;
|
||||
}
|
||||
return _TSTR("validator_dialog_number_parse_error");
|
||||
}
|
||||
|
||||
static bool bounded(T minimum, T maximum) {
|
||||
return
|
||||
minimum != std::numeric_limits<T>::min() &&
|
||||
maximum != std::numeric_limits<T>::max();
|
||||
}
|
||||
|
||||
|
||||
Formatter formatter;
|
||||
T minimum, maximum;
|
||||
};
|
||||
|
||||
}
|
88
src/musikcube/cursespp/cursespp/SchemaOverlay.h
Normal file
88
src/musikcube/cursespp/cursespp/SchemaOverlay.h
Normal file
@ -0,0 +1,88 @@
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Copyright (c) 2007-2019 musikcube team
|
||||
//
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright
|
||||
// notice, this list of conditions and the following disclaimer in the
|
||||
// documentation and/or other materials provided with the distribution.
|
||||
//
|
||||
// * Neither the name of the author nor the names of other contributors may
|
||||
// be used to endorse or promote products derived from this software
|
||||
// without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
||||
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <core/support/Preferences.h>
|
||||
#include <core/sdk/ISchema.h>
|
||||
|
||||
namespace cursespp {
|
||||
class SchemaOverlay {
|
||||
public:
|
||||
using Prefs = musik::core::Preferences;
|
||||
using ISchema = musik::core::sdk::ISchema;
|
||||
using SchemaPtr = std::shared_ptr<ISchema>;
|
||||
using PrefsPtr = std::shared_ptr<Prefs>;
|
||||
|
||||
static void Show(
|
||||
const std::string& title,
|
||||
PrefsPtr prefs,
|
||||
SchemaPtr schema,
|
||||
std::function<void(bool)> callback);
|
||||
|
||||
static void ShowListOverlay(
|
||||
const std::string& title,
|
||||
std::vector<std::string>& items,
|
||||
const std::string defaultValue,
|
||||
std::function<void(std::string)> cb);
|
||||
|
||||
static void ShowBoolOverlay(
|
||||
const ISchema::BoolEntry* entry,
|
||||
PrefsPtr prefs,
|
||||
std::function<void(std::string)> callback);
|
||||
|
||||
static void ShowIntOverlay(
|
||||
const ISchema::IntEntry* entry,
|
||||
PrefsPtr prefs,
|
||||
std::function<void(std::string)> callback);
|
||||
|
||||
static void ShowDoubleOverlay(
|
||||
const ISchema::DoubleEntry* entry,
|
||||
PrefsPtr prefs,
|
||||
std::function<void(std::string)> callback);
|
||||
|
||||
static void ShowStringOverlay(
|
||||
const ISchema::StringEntry* entry,
|
||||
PrefsPtr prefs,
|
||||
std::function<void(std::string)> callback);
|
||||
|
||||
static void ShowEnumOverlay(
|
||||
const ISchema::EnumEntry* entry,
|
||||
PrefsPtr prefs,
|
||||
std::function<void(std::string)> callback);
|
||||
|
||||
private:
|
||||
SchemaOverlay();
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user