diff --git a/include/wups/config/WUPSConfigItemIPAddress.h b/include/wups/config/WUPSConfigItemIPAddress.h new file mode 100644 index 0000000..8abc5c9 --- /dev/null +++ b/include/wups/config/WUPSConfigItemIPAddress.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum ConfigItemIPAddress_State { + WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_NONE, + // Make sure they are sequential + WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD1, + WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD2, + WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD3, + WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD4 +} ConfigItemIPAddress_State; + +typedef struct ConfigItemIPAddress { + char *identifier; + WUPSConfigItemHandle itemHandle; + ConfigItemIPAddress_State itemState; + uint32_t defaultValue; + uint32_t currentValue; + uint32_t valueAtCreation; + uint32_t valueBeforeEdit; + uint32_t frameTimer; + void *valueChangedCallback; +} ConfigItemIPAddress; + +typedef void (*IPAddressValueChangedCallback)(ConfigItemIPAddress *item, uint32_t ipAddress); + +WUPSConfigAPIStatus +WUPSConfigItemIPAddress_Create(const char *identifier, + const char *displayName, + uint32_t defaultIPAddress, + IPAddressValueChangedCallback callback, + WUPSConfigItemHandle *outItemHandle); + + +WUPSConfigAPIStatus +WUPSConfigItemIPAddress_AddToCategory(WUPSConfigCategoryHandle cat, + const char *identifier, const char *displayName, + uint32_t defaultIPAddress, + IPAddressValueChangedCallback callback); +#ifdef __cplusplus +} +#endif + +#if defined(__cplusplus) && __cplusplus > 201703L + +#include +#include +#include + +class WUPSConfigItemIPAddress : public WUPSConfigItem { +public: + static std::optional Create(const std::optional &identifier, + std::string_view displayName, uint32_t defaultIPAddress, + IPAddressValueChangedCallback callback, + WUPSConfigAPIStatus &err) noexcept; + + static WUPSConfigItemIPAddress Create(const std::optional &identifier, + std::string_view displayName, + uint32_t defaultIPAddress, + IPAddressValueChangedCallback callback); + +private: + explicit WUPSConfigItemIPAddress(const WUPSConfigItemHandle itemHandle) : WUPSConfigItem(itemHandle) { + } +}; +#endif diff --git a/libraries/libwups/WUPSConfigItemIPAddress.cpp b/libraries/libwups/WUPSConfigItemIPAddress.cpp new file mode 100644 index 0000000..72b1da4 --- /dev/null +++ b/libraries/libwups/WUPSConfigItemIPAddress.cpp @@ -0,0 +1,263 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace { + void modifyIpOctet(ConfigItemIPAddress *item, int octetIndex, const int delta) { + const int shift = (3 - octetIndex) * 8; + uint8_t octet = (item->currentValue >> shift) & 0xFF; + octet += delta; + const uint32_t mask = ~(0xFF << shift); + item->currentValue = (item->currentValue & mask) | (static_cast(octet) << shift); + } + + int32_t getCurrentValueDisplayGeneric(void *context, bool isSelected, char *out_buf, int32_t out_size) { + auto *item = static_cast(context); + + const uint8_t o1 = (item->currentValue >> 24) & 0xFF; + const uint8_t o2 = (item->currentValue >> 16) & 0xFF; + const uint8_t o3 = (item->currentValue >> 8) & 0xFF; + const uint8_t o4 = (item->currentValue) & 0xFF; + + switch (item->itemState) { + case WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD1: + snprintf(out_buf, out_size, "[%u].%u.%u.%u", o1, o2, o3, o4); + break; + + case WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD2: + snprintf(out_buf, out_size, "%u.[%u].%u.%u", o1, o2, o3, o4); + break; + + case WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD3: + snprintf(out_buf, out_size, "%u.%u.[%u].%u", o1, o2, o3, o4); + break; + + case WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD4: + snprintf(out_buf, out_size, "%u.%u.%u.[%u]", o1, o2, o3, o4); + break; + + default: + if (isSelected) { + snprintf(out_buf, out_size, "(Press A to edit) %u.%u.%u.%u", o1, o2, o3, o4); + } else { + snprintf(out_buf, out_size, "%u.%u.%u.%u", o1, o2, o3, o4); + } + break; + } + + return 0; + } + +} // namespace + +static int32_t WUPSConfigItemIPAddress_getCurrentValueDisplay(void *context, char *out_buf, int32_t out_size) { + return getCurrentValueDisplayGeneric(context, false, out_buf, out_size); +} + +static void WUPSConfigItemIPAddress_onCloseCallback(void *context) { + auto *item = static_cast(context); + if (item->currentValue != item->valueAtCreation && item->valueChangedCallback != nullptr) { + reinterpret_cast(item->valueChangedCallback)(item, item->currentValue); + } +} + +#define IP_VALUE_EDIT_INITIAL_DELAY 5 +static void WUPSConfigItemIPAddress_onInput(void *context, WUPSConfigSimplePadData input) { + auto *item = static_cast(context); + + // Handle edit request + if (item->itemState == WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_NONE) { + if (input.buttons_d & WUPS_CONFIG_BUTTON_A) { + item->valueBeforeEdit = item->currentValue; + item->itemState = WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD1; + item->frameTimer = 0; + } + return; + } + + // When editing press A to accept, press B to abort + if ((input.buttons_d & WUPS_CONFIG_BUTTON_A) || (input.buttons_d & WUPS_CONFIG_BUTTON_B)) { + if (input.buttons_d & WUPS_CONFIG_BUTTON_B) { + item->currentValue = item->valueBeforeEdit; + } + item->itemState = WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_NONE; + return; + } + + int fieldIndex = 0; + switch (item->itemState) { + case WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD1: { + fieldIndex = 0; + break; + } + case WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD2: { + fieldIndex = 1; + break; + } + case WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD3: { + fieldIndex = 2; + break; + } + case WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD4: { + fieldIndex = 3; + break; + } + default: + return; + } + + // Handle navigation between field + if (input.buttons_d & WUPS_CONFIG_BUTTON_LEFT) { + if (fieldIndex > 0) { + item->itemState = static_cast(item->itemState - 1); + } else + item->itemState = WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD4; + return; + } + if (input.buttons_d & WUPS_CONFIG_BUTTON_RIGHT) { + if (fieldIndex < 3) { + item->itemState = static_cast(item->itemState + 1); + } else { + item->itemState = WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_EDIT_INPUT_FIELD1; + } + return; + } + + // Handle modify ocet + bool isUp = input.buttons_h & WUPS_CONFIG_BUTTON_UP; + bool isDown = input.buttons_h & WUPS_CONFIG_BUTTON_DOWN; + + if (isUp || isDown) { + bool applyChange = false; + + if ((isUp && (input.buttons_d & WUPS_CONFIG_BUTTON_UP)) || + (isDown && (input.buttons_d & WUPS_CONFIG_BUTTON_DOWN))) { + applyChange = true; + item->frameTimer = IP_VALUE_EDIT_INITIAL_DELAY; + } else { + if (item->frameTimer > 0) { + item->frameTimer--; + } else { + applyChange = true; + } + } + + if (applyChange) { + modifyIpOctet(item, fieldIndex, isUp ? 1 : -1); + } + } else { + item->frameTimer = 0; + } +} + +static int32_t +WUPSConfigItemIPAddress_getCurrentValueSelectedDisplay(void *context, char *out_buf, int32_t out_size) { + return getCurrentValueDisplayGeneric(context, true, out_buf, out_size); +} + +static void WUPSConfigItemIPAddress_restoreDefault(void *context) { + auto *item = static_cast(context); + + item->currentValue = item->defaultValue; +} + +static bool WUPSConfigItemIPAddress_isMovementAllowed(void *context) { + const auto *item = static_cast(context); + return item->itemState == WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_NONE; +} + +static void WUPSConfigItemIPAddress_Cleanup(ConfigItemIPAddress *item) { + if (!item) { + return; + } + if (item->identifier) { + free(item->identifier); + } + free(item); +} + +static void WUPSConfigItemIPAddress_onDelete(void *context) { + WUPSConfigItemIPAddress_Cleanup(static_cast(context)); +} + +WUPSConfigAPIStatus +WUPSConfigItemIPAddress_Create(const char *identifier, + const char *displayName, + const uint32_t defaultIPAddress, + IPAddressValueChangedCallback callback, + WUPSConfigItemHandle *outItemHandle) { + if (outItemHandle == nullptr) { + return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT; + } + auto *item = static_cast(malloc(sizeof(ConfigItemIPAddress))); + if (item == nullptr) { + OSReport("WUPSConfigItemIPAddress_CreateEx: Failed to allocate memory for item data.\n"); + return WUPSCONFIG_API_RESULT_INVALID_ARGUMENT; + } + + if (identifier != nullptr) { + item->identifier = strdup(identifier); + } else { + item->identifier = nullptr; + } + + item->currentValue = defaultIPAddress; + item->defaultValue = defaultIPAddress; + item->valueAtCreation = defaultIPAddress; + item->valueChangedCallback = reinterpret_cast(callback); + + item->itemState = WUPS_CONFIG_ITEM_IP_ADDRESS_STATE_NONE; + + constexpr WUPSConfigAPIItemCallbacksV2 callbacks = { + .getCurrentValueDisplay = &WUPSConfigItemIPAddress_getCurrentValueDisplay, + .getCurrentValueSelectedDisplay = &WUPSConfigItemIPAddress_getCurrentValueSelectedDisplay, + .onSelected = nullptr, + .restoreDefault = &WUPSConfigItemIPAddress_restoreDefault, + .isMovementAllowed = &WUPSConfigItemIPAddress_isMovementAllowed, + .onCloseCallback = &WUPSConfigItemIPAddress_onCloseCallback, + .onInput = &WUPSConfigItemIPAddress_onInput, + .onInputEx = nullptr, + .onDelete = &WUPSConfigItemIPAddress_onDelete, + }; + + const WUPSConfigAPIItemOptionsV2 options = { + .displayName = displayName, + .context = item, + .callbacks = callbacks, + }; + + if (const WUPSConfigAPIStatus err = WUPSConfigAPI_Item_Create(options, &item->itemHandle); err != WUPSCONFIG_API_RESULT_SUCCESS) { + OSReport("WUPSConfigItemIPAddress_Create: Failed to create config item.\n"); + WUPSConfigItemIPAddress_Cleanup(item); + return err; + } + + *outItemHandle = item->itemHandle; + return WUPSCONFIG_API_RESULT_SUCCESS; +} + + +WUPSConfigAPIStatus +WUPSConfigItemIPAddress_AddToCategory(const WUPSConfigCategoryHandle cat, + const char *identifier, const char *displayName, + const uint32_t defaultIPAddress, + const IPAddressValueChangedCallback callback) { + WUPSConfigItemHandle itemHandle; + WUPSConfigAPIStatus res; + if ((res = WUPSConfigItemIPAddress_Create(identifier, + displayName, + defaultIPAddress, + callback, &itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) { + return res; + } + + if ((res = WUPSConfigAPI_Category_AddItem(cat, itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) { + WUPSConfigAPI_Item_Destroy(itemHandle); + return res; + } + return WUPSCONFIG_API_RESULT_SUCCESS; +} diff --git a/libraries/libwups/WUPSConfigItemIPAddressCpp.cpp b/libraries/libwups/WUPSConfigItemIPAddressCpp.cpp new file mode 100644 index 0000000..32719d2 --- /dev/null +++ b/libraries/libwups/WUPSConfigItemIPAddressCpp.cpp @@ -0,0 +1,31 @@ +#include +#include +#include + +std::optional WUPSConfigItemIPAddress::Create(const std::optional &identifier, + const std::string_view displayName, + uint32_t defaultValue, + const IPAddressValueChangedCallback callback, + WUPSConfigAPIStatus &err) noexcept { + WUPSConfigItemHandle itemHandle; + if ((err = WUPSConfigItemIPAddress_Create(identifier ? identifier->data() : nullptr, + displayName.data(), + defaultValue, + callback, + &itemHandle)) != WUPSCONFIG_API_RESULT_SUCCESS) { + return std::nullopt; + } + return WUPSConfigItemIPAddress(itemHandle); +} + +WUPSConfigItemIPAddress WUPSConfigItemIPAddress::Create(const std::optional &identifier, + const std::string_view displayName, + uint32_t defaultValue, + const IPAddressValueChangedCallback callback) { + WUPSConfigAPIStatus err = WUPSCONFIG_API_RESULT_UNKNOWN_ERROR; + auto res = Create(identifier, displayName, defaultValue, callback, err); + if (!res) { + throw std::runtime_error(std::string("Failed to create WUPSConfigItemIPAddress: ").append(WUPSConfigAPI_GetStatusStr(err))); + } + return std::move(*res); +}