mirror of
https://github.com/ggerganov/ggwave.git
synced 2026-02-24 16:16:10 +08:00
ggwave-gui : UI improvements
This commit is contained in:
@@ -11,13 +11,27 @@
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include <ctime>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
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<bool> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<Message> 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<std::mutex> 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;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ public:
|
||||
using AmplitudeData16 = std::array<int16_t, kMaxRecordedFrames*kMaxSamplesPerFrame>;
|
||||
using SpectrumData = std::array<float, kMaxSamplesPerFrame>;
|
||||
using RecordedData = std::array<float, kMaxRecordedFrames*kMaxSamplesPerFrame>;
|
||||
using TxRxData = std::array<std::uint8_t, kMaxDataSize>;
|
||||
|
||||
using CBQueueAudio = std::function<void(const void * data, uint32_t nBytes)>;
|
||||
using CBDequeueAudio = std::function<uint32_t(void * data, uint32_t nMaxBytes)>;
|
||||
@@ -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<std::uint8_t, kMaxDataSize> & 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<std::uint8_t, kMaxDataSize> rxData;
|
||||
std::array<std::uint8_t, kMaxDataSize> txData;
|
||||
std::array<std::uint8_t, kMaxDataSize> txDataEncoded;
|
||||
TxRxData rxData;
|
||||
TxRxData txData;
|
||||
TxRxData txDataEncoded;
|
||||
|
||||
int historyId = 0;
|
||||
AmplitudeData sampleAmplitudeAverage;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user