diff --git a/examples/ggwave-gui/main.cpp b/examples/ggwave-gui/main.cpp index f5a31a1..130087c 100644 --- a/examples/ggwave-gui/main.cpp +++ b/examples/ggwave-gui/main.cpp @@ -11,13 +11,27 @@ #include #include - +#include +#include #include #include +#include static SDL_Window * g_window; static void * g_gl_context; +#define ICON_FA_COGS "#" +#define ICON_FA_COMMENT_ALT "" +#define ICON_FA_LIST_UL "" +#define ICON_FA_PLAY_CIRCLE "" + +char * toTimeString(const std::time_t t) { + std::tm * ptm = std::localtime(&t); + static char buffer[32]; + std::strftime(buffer, 32, "%H:%M:%S", ptm); + return buffer; +} + void ScrollWhenDraggingOnVoid(const ImVec2& delta, ImGuiMouseButton mouse_button) { ImGuiContext& g = *ImGui::GetCurrentContext(); ImGuiWindow* window = g.CurrentWindow; @@ -114,25 +128,125 @@ int main(int argc, char** argv) { ImGui_NewFrame(g_window); ImGui::Render(); - while (true) { - GGWave_mainLoop(); + std::atomic isRunning { true }; + struct Message { + bool received; + std::time_t timestamp; + std::string data; + std::string protocol; + }; + + struct State { + bool update = false; + Message message; + }; + + struct Input { + bool update = false; + Message message; + }; + + struct Buffer { + std::mutex mutex; + + State stateCore; + Input inputCore; + + State stateUI; + Input inputUI; + }; + + Buffer buffer; + + auto worker = std::thread([&]() { + Input inputCurrent; + + int lastRxDataLength = 0; + GGWave::TxRxData lastRxData; + + while (isRunning) { + { + std::lock_guard lock(buffer.mutex); + if (buffer.inputCore.update) { + inputCurrent = std::move(buffer.inputCore); + buffer.inputCore.update = false; + } + } + + if (inputCurrent.update) { + ggWave->init(inputCurrent.message.data.size(), inputCurrent.message.data.data()); + inputCurrent.update = false; + } + + GGWave_mainLoop(); + + lastRxDataLength = ggWave->takeRxData(lastRxData); + if (lastRxDataLength > 0) { + buffer.stateCore.update = true; + buffer.stateCore.message = { true, std::time(nullptr), std::string((char *) lastRxData.data(), lastRxDataLength), "" }; + } + + { + std::lock_guard lock(buffer.mutex); + if (buffer.stateCore.update) { + buffer.stateUI = std::move(buffer.stateCore); + buffer.stateCore.update = false; + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + } + }); + + while (true) { if (ImGui_beginFrame(g_window) == false) { break; } + static State stateCurrent; + + { + std::lock_guard lock(buffer.mutex); + if (buffer.stateUI.update) { + stateCurrent = std::move(buffer.stateUI); + buffer.stateUI.update = false; + } + } + static char inputBuf[256]; + + static bool doInputFocus = false; + static bool lastMouseButtonLeft = 0; static bool isTextInput = false; + static bool scrollMessagesToBottom = true; + static double tStartInput = 0.0f; static double tEndInput = -100.0f; - const double tShowKeyboard = 0.2f; + static std::vector messageHistory; + + if (stateCurrent.update) { + messageHistory.push_back(std::move(stateCurrent.message)); + stateCurrent.update = false; + } + + if (lastMouseButtonLeft == 0 && ImGui::GetIO().MouseDown[0] == 1) { + ImGui::GetIO().MouseDelta = { 0.0, 0.0 }; + } + lastMouseButtonLeft = ImGui::GetIO().MouseDown[0]; const auto& displaySize = ImGui::GetIO().DisplaySize; auto& style = ImGui::GetStyle(); - const float statusBarHeight = 44.0f + 2.0f*style.ItemSpacing.y; - const float menuButtonHeight = 40.0f + 2.0f*style.ItemSpacing.y; + const auto sendButtonText = ICON_FA_PLAY_CIRCLE " Send"; + const double tShowKeyboard = 0.2f; +#ifdef IOS + const float statusBarHeight = displaySize.x < displaySize.y ? 20.0f + 2.0f*style.ItemSpacing.y : 0.1f; +#else + const float statusBarHeight = 0.1f; +#endif + const float menuButtonHeight = 24.0f + 2.0f*style.ItemSpacing.y; ImGui::SetNextWindowPos({ 0, 0, }); ImGui::SetNextWindowSize(displaySize); @@ -140,39 +254,87 @@ int main(int argc, char** argv) { ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoScrollbar | + ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings); ImGui::InvisibleButton("StatusBar", { ImGui::GetContentRegionAvailWidth(), statusBarHeight }); - if (ImGui::Button("Messages", { 0.5f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) { + if (ImGui::Button(ICON_FA_COGS, { menuButtonHeight, menuButtonHeight } )) { } ImGui::SameLine(); - if (ImGui::Button("Commands", { 1.0f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) { + if (ImGui::Button(ICON_FA_COMMENT_ALT " Messages", { 0.5f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) { + } + ImGui::SameLine(); + + if (ImGui::Button(ICON_FA_LIST_UL " Commands", { 1.0f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) { } const float messagesInputHeight = ImGui::GetTextLineHeightWithSpacing(); - const float messagesHistoryHeigthMax = ImGui::GetContentRegionAvail().y - messagesInputHeight - 2.0f*style.FramePadding.y; + const float messagesHistoryHeigthMax = ImGui::GetContentRegionAvail().y - messagesInputHeight - 2.0f*style.ItemSpacing.x; float messagesHistoryHeigth = messagesHistoryHeigthMax; - if (isTextInput) { - messagesHistoryHeigth -= 0.5f*messagesHistoryHeigthMax*std::min(tShowKeyboard, ImGui::GetTime() - tStartInput) / tShowKeyboard; + // no automatic screen resize support for iOS +#ifdef IOS + if (displaySize.x < displaySize.y) { + if (isTextInput) { + messagesHistoryHeigth -= 0.5f*messagesHistoryHeigthMax*std::min(tShowKeyboard, ImGui::GetTime() - tStartInput) / tShowKeyboard; + } else { + messagesHistoryHeigth -= 0.5f*messagesHistoryHeigthMax - 0.5f*messagesHistoryHeigthMax*std::min(tShowKeyboard, ImGui::GetTime() - tEndInput) / tShowKeyboard; + } } else { - messagesHistoryHeigth -= 0.5f*messagesHistoryHeigthMax - 0.5f*messagesHistoryHeigthMax*std::min(tShowKeyboard, ImGui::GetTime() - tEndInput) / tShowKeyboard; + if (isTextInput) { + messagesHistoryHeigth -= 0.5f*displaySize.y*std::min(tShowKeyboard, ImGui::GetTime() - tStartInput) / tShowKeyboard; + } else { + messagesHistoryHeigth -= 0.5f*displaySize.y - 0.5f*displaySize.y*std::min(tShowKeyboard, ImGui::GetTime() - tEndInput) / tShowKeyboard; + } } +#endif ImGui::BeginChild("Messages:history", { ImGui::GetContentRegionAvailWidth(), messagesHistoryHeigth }, true); - for (int i = 0; i < 100; ++i) { - ImGui::Text("SAA sadfa line %d\n", i); + for (int i = 0; i < (int) messageHistory.size(); ++i) { + ImGui::PushID(i); + const auto & message = messageHistory[i]; + if (message.received) { + ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "[%s] Recv:", ::toTimeString(message.timestamp)); + ImGui::SameLine(); + if (ImGui::SmallButton("Resend")) { + buffer.inputUI.update = true; + buffer.inputUI.message = { false, std::time(nullptr), message.data, "" }; + + messageHistory.push_back(buffer.inputUI.message); + } + ImGui::Text("%s", message.data.c_str()); + } else { + ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "[%s] Sent:", ::toTimeString(message.timestamp)); + ImGui::SameLine(); + if (ImGui::SmallButton("Resend")) { + buffer.inputUI.update = true; + buffer.inputUI.message = { false, std::time(nullptr), message.data, "" }; + + messageHistory.push_back(buffer.inputUI.message); + } + ImGui::Text("%s", message.data.c_str()); + } + ImGui::Text("%s", ""); + ImGui::PopID(); } + if (scrollMessagesToBottom) { + ImGui::SetScrollHereY(); + scrollMessagesToBottom = false; + } ImVec2 mouse_delta = ImGui::GetIO().MouseDelta; ScrollWhenDraggingOnVoid(ImVec2(0.0f, -mouse_delta.y), ImGuiMouseButton_Left); ImGui::EndChild(); - ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() - 2*ImGui::CalcTextSize("Send").x); + if (doInputFocus) { + ImGui::SetKeyboardFocusHere(); + doInputFocus = false; + } + ImGui::PushItemWidth(ImGui::GetContentRegionAvailWidth() - ImGui::CalcTextSize(sendButtonText).x - 2*style.ItemSpacing.x); ImGui::InputText("##Messages:Input", inputBuf, 256, ImGuiInputTextFlags_EnterReturnsTrue); ImGui::PopItemWidth(); if (ImGui::IsItemActive() && isTextInput == false) { @@ -185,10 +347,14 @@ int main(int argc, char** argv) { requestStopTextInput = true; } ImGui::SameLine(); - if (ImGui::Button("Send")) { - std::string input(inputBuf); - ggWave->init(input.size(), input.data()); + if (ImGui::Button(sendButtonText) && inputBuf[0] != 0) { + buffer.inputUI.update = true; + buffer.inputUI.message = { false, std::time(nullptr), std::string(inputBuf), "" }; + + messageHistory.push_back(buffer.inputUI.message); + inputBuf[0] = 0; + doInputFocus = true; } if (!ImGui::IsItemHovered() && requestStopTextInput) { SDL_StopTextInput(); @@ -198,10 +364,32 @@ int main(int argc, char** argv) { ImGui::End(); + ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Backspace]] = false; + ImGui::GetIO().KeysDown[ImGui::GetIO().KeyMap[ImGuiKey_Enter]] = false; + ImGui_endFrame(g_window); + + { + std::lock_guard lock(buffer.mutex); + if (buffer.inputUI.update) { + buffer.inputCore = std::move(buffer.inputUI); + buffer.inputUI.update = false; + } + } } + isRunning = false; + worker.join(); + GGWave_deinit(); + // Cleanup + ImGui_Shutdown(); + ImGui::DestroyContext(); + + SDL_GL_DeleteContext(g_gl_context); + SDL_DestroyWindow(g_window); + SDL_Quit(); + return 0; } diff --git a/include/ggwave/ggwave.h b/include/ggwave/ggwave.h index 7b8ad2c..4a1e27c 100644 --- a/include/ggwave/ggwave.h +++ b/include/ggwave/ggwave.h @@ -28,6 +28,7 @@ public: using AmplitudeData16 = std::array; using SpectrumData = std::array; using RecordedData = std::array; + using TxRxData = std::array; using CBQueueAudio = std::function; using CBDequeueAudio = std::function; @@ -38,6 +39,7 @@ public: int aSamplesPerFrame, int aSampleSizeBytesIn, int aSampleSizeBytesOut); + ~GGWave(); void setTxMode(TxMode aTxMode) { txMode = aTxMode; } @@ -67,7 +69,17 @@ public: const float & getSampleRateIn() const { return sampleRateIn; } const float & getAverageRxTime_ms() const { return averageRxTime_ms; } - const std::array & getRxData() const { return rxData; } + const TxRxData & getRxData() const { return rxData; } + + int takeRxData(TxRxData & dst) { + if (lastRxDataLength == 0) return 0; + + auto res = lastRxDataLength; + lastRxDataLength = 0; + dst = rxData; + + return res; + } private: int nIterations; @@ -82,10 +94,12 @@ private: // Rx bool receivingData; bool analyzingData; + bool hasNewRxData = false; int nCalls = 0; int recvDuration_frames; int totalBytesCaptured; + int lastRxDataLength = 0; float tSum_ms = 0.0f; float averageRxTime_ms = 0.0; @@ -96,9 +110,9 @@ private: AmplitudeData sampleAmplitude; SpectrumData sampleSpectrum; - std::array rxData; - std::array txData; - std::array txDataEncoded; + TxRxData rxData; + TxRxData txData; + TxRxData txDataEncoded; int historyId = 0; AmplitudeData sampleAmplitudeAverage; diff --git a/src/ggwave.cpp b/src/ggwave.cpp index e73e54e..ce0c086 100644 --- a/src/ggwave.cpp +++ b/src/ggwave.cpp @@ -124,6 +124,11 @@ GGWave::GGWave( init(0, ""); } +GGWave::~GGWave() { + if (rsData) delete rsData; + if (rsLength) delete rsLength; +} + bool GGWave::setParameters( int aParamFreqDelta, int aParamFreqStart, @@ -556,6 +561,8 @@ void GGWave::receive(const CBDequeueAudio & CBDequeueAudio) { std::string s((char *) rxData.data(), decodedLength); printf("Received sound data successfully: '%s'\n", s.c_str()); } + hasNewRxData = true; + lastRxDataLength = decodedLength; framesToRecord = 0; isValid = true; }