diff --git a/.gitmodules b/.gitmodules index aba14e7..ca77b6a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "examples/third-party/imgui/imgui"] path = examples/third-party/imgui/imgui url = https://github.com/ocornut/imgui +[submodule "examples/third-party/ggsock"] + path = examples/third-party/ggsock + url = https://github.com/ggerganov/ggsock diff --git a/examples/ggwave-common-sdl2.cpp b/examples/ggwave-common-sdl2.cpp index 088a2b6..6089dc9 100644 --- a/examples/ggwave-common-sdl2.cpp +++ b/examples/ggwave-common-sdl2.cpp @@ -1,10 +1,9 @@ #include "ggwave-common-sdl2.h" -#include "ggwave-common.h" - #include "ggwave/ggwave.h" -#include +#include "ggwave-common.h" + #include #include @@ -15,6 +14,8 @@ #define EMSCRIPTEN_KEEPALIVE #endif +constexpr double kBaseSampleRate = 48000.0; + namespace { std::string g_defaultCaptureDeviceName = ""; @@ -30,7 +31,6 @@ GGWave *g_ggWave = nullptr; } // JS interface - extern "C" { EMSCRIPTEN_KEEPALIVE int sendData(int textLength, const char * text, int protocolId, int volume) { @@ -117,7 +117,7 @@ bool GGWave_init( SDL_AudioSpec playbackSpec; SDL_zero(playbackSpec); - playbackSpec.freq = GGWave::kBaseSampleRate; + playbackSpec.freq = ::kBaseSampleRate; playbackSpec.format = AUDIO_S16SYS; playbackSpec.channels = 1; playbackSpec.samples = 16*1024; @@ -160,7 +160,7 @@ bool GGWave_init( if (g_devIdIn == 0) { SDL_AudioSpec captureSpec; captureSpec = g_obtainedSpecOut; - captureSpec.freq = GGWave::kBaseSampleRate; + captureSpec.freq = ::kBaseSampleRate; captureSpec.format = AUDIO_F32SYS; captureSpec.samples = 4096; diff --git a/examples/ggwave-common-sdl2.h b/examples/ggwave-common-sdl2.h index a411bd2..2232d9d 100644 --- a/examples/ggwave-common-sdl2.h +++ b/examples/ggwave-common-sdl2.h @@ -1,5 +1,7 @@ #pragma once +#include + #include class GGWave; diff --git a/examples/ggwave-common.h b/examples/ggwave-common.h index 5258b18..2b87e39 100644 --- a/examples/ggwave-common.h +++ b/examples/ggwave-common.h @@ -4,8 +4,6 @@ #include #include -// some basic helper methods for the examples - template float getTime_ms(const T & tStart, const T & tEnd) { return ((float)(std::chrono::duration_cast(tEnd - tStart).count()))/1000.0; diff --git a/examples/ggwave-gui/CMakeLists.txt b/examples/ggwave-gui/CMakeLists.txt index 0cec22e..4218dda 100644 --- a/examples/ggwave-gui/CMakeLists.txt +++ b/examples/ggwave-gui/CMakeLists.txt @@ -9,6 +9,7 @@ target_link_libraries(ggwave-gui PRIVATE ggwave ggwave-common ggwave-common-sdl2 + ggsock imgui-sdl2 ${CMAKE_THREAD_LIBS_INIT} ) diff --git a/examples/ggwave-gui/common.cpp b/examples/ggwave-gui/common.cpp index 559b23e..2240f49 100644 --- a/examples/ggwave-gui/common.cpp +++ b/examples/ggwave-gui/common.cpp @@ -4,6 +4,10 @@ #include "ggwave-common.h" +#include "ggsock/communicator.h" +#include "ggsock/file-server.h" +#include "ggsock/serialization.h" + #include #include @@ -17,6 +21,8 @@ #include #include #include +#include +#include #if defined(IOS) || defined(ANDROID) #include "imgui-wrapper/icons_font_awesome.h" @@ -29,14 +35,47 @@ #define ICON_FA_PLAY_CIRCLE "" #define ICON_FA_ARROW_CIRCLE_DOWN "V" #define ICON_FA_PASTE "P" +#define ICON_FA_FILE "" #endif +namespace { +char * toTimeString(const std::chrono::system_clock::time_point & tp) { + time_t t = std::chrono::system_clock::to_time_t(tp); + 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; + bool hovered = false; + bool held = false; + ImGuiButtonFlags button_flags = (mouse_button == 0) ? ImGuiButtonFlags_MouseButtonLeft : (mouse_button == 1) ? ImGuiButtonFlags_MouseButtonRight : ImGuiButtonFlags_MouseButtonMiddle; + if (g.HoveredId == 0) // If nothing hovered so far in the frame (not same as IsAnyItemHovered()!) + ImGui::ButtonBehavior(window->Rect(), window->GetID("##scrolldraggingoverlay"), &hovered, &held, button_flags); + if (held && delta.x != 0.0f) + ImGui::SetScrollX(window, window->Scroll.x + delta.x); + if (held && delta.y != 0.0f) + ImGui::SetScrollY(window, window->Scroll.y + delta.y); +} +} + +static const char * kFileBroadcastPrefix = "\xbc"; + struct Message { + enum Type { + Text, + FileBroadcast, + }; + bool received; std::chrono::system_clock::time_point timestamp; std::string data; int protocolId; float volume; + Type type; }; struct GGWaveStats { @@ -112,36 +151,183 @@ struct Buffer { Input inputUI; }; -char * toTimeString(const std::chrono::system_clock::time_point & tp) { - time_t t = std::chrono::system_clock::to_time_t(tp); - 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; - bool hovered = false; - bool held = false; - ImGuiButtonFlags button_flags = (mouse_button == 0) ? ImGuiButtonFlags_MouseButtonLeft : (mouse_button == 1) ? ImGuiButtonFlags_MouseButtonRight : ImGuiButtonFlags_MouseButtonMiddle; - if (g.HoveredId == 0) // If nothing hovered so far in the frame (not same as IsAnyItemHovered()!) - ImGui::ButtonBehavior(window->Rect(), window->GetID("##scrolldraggingoverlay"), &hovered, &held, button_flags); - if (held && delta.x != 0.0f) - ImGui::SetScrollX(window, window->Scroll.x + delta.x); - if (held && delta.y != 0.0f) - ImGui::SetScrollY(window, window->Scroll.y + delta.y); -} - GGWave * g_ggWave; Buffer g_buffer; std::atomic g_isRunning; +bool g_focusFileSend = false; +GGSock::FileServer g_fileServer; + +bool g_hasFileInfos = false; +bool g_hasRequestedFiles = false; +GGSock::FileServer::TFileInfos g_fileInfos; +std::map g_receivedFiles; + +int g_remotePort = 23045; +std::string g_remoteIP = "127.0.0.1"; + +GGSock::Communicator g_fileClient(false); + +int g_shareId = 0; +std::string g_shareFilename; + +int g_deleteId = 0; +std::string g_deleteFilename; + +int g_receivedId = 0; + +int getShareId() { + return g_shareId; +} + +const char * getShareFilename() { + return g_shareFilename.data(); +} + +int getDeleteId() { + return g_deleteId; +} + +const char * getDeleteFilename() { + return g_deleteFilename.data(); +} + +int getReceivedId() { + return g_receivedId; +} + +std::vector getReceivedFilename() { + std::vector result; + + for (const auto & file : g_receivedFiles) { + result.push_back(file.second.info.filename.c_str()); + } + + return result; +} + +std::vector getReceivedDataBuffer() { + std::vector result; + + for (const auto & file : g_receivedFiles) { + result.push_back(file.second.data.data()); + } + + return result; +} + +std::vector getReceivedDataSize() { + std::vector result; + + for (const auto & file : g_receivedFiles) { + result.push_back(file.second.data.size()); + } + + return result; +} + +void clearFiles() { + g_fileServer.clearAllFiles(); +} + +void addFile( + const char * uri, + const char * filename, + const char * dataBuffer, + size_t dataSize) { + GGSock::FileServer::FileData file; + file.info.uri = uri; + file.info.filename = filename; + file.data.resize(dataSize); + std::memcpy(file.data.data(), dataBuffer, dataSize); + + g_fileServer.addFile(std::move(file)); + g_focusFileSend = true; +} + +void addFile( + const char * uri, + const char * filename, + std::vector && data) { + GGSock::FileServer::FileData file; + file.info.uri = uri; + file.info.filename = filename; + file.data = std::move(data); + + g_fileServer.addFile(std::move(file)); + g_focusFileSend = true; +} + +std::string generateFileBroadcastMessage() { + // todo : to binary + std::string result; + + result = kFileBroadcastPrefix; + result += ' '; + result += GGSock::Communicator::getLocalAddress(); + result += ' '; + result += std::to_string(g_fileServer.getParameters().listenPort); + result += ' '; + result += std::to_string(rand()%32000); // todo : generated key should be used to authorize incoming messages + + return result; +} + +bool isFileBroadcastMessage(const std::string & message) { + bool result = true; + + auto pSrc = kFileBroadcastPrefix; + auto pDst = message.data(); + + while (pSrc != 0) { + if (*pDst == 0 || *pSrc++ != *pDst++) { + result = false; + break; + } + } + + return result; +} + std::thread initMain() { g_isRunning = true; g_ggWave = GGWave_instance(); + g_fileServer.init({}); + + g_fileClient.setErrorCallback([](GGSock::Communicator::TErrorCode code) { + printf("Disconnected with code = %d\n", code); + }); + + g_fileClient.setMessageCallback(GGSock::FileServer::MsgFileInfosResponse, [&](const char * dataBuffer, size_t dataSize) { + printf("Received message %d, size = %d\n", GGSock::FileServer::MsgFileInfosResponse, (int) dataSize); + + size_t offset = 0; + GGSock::Unserialize()(g_fileInfos, dataBuffer, dataSize, offset); + + for (const auto & info : g_fileInfos) { + printf(" - %s : %s (size = %d, chunks = %d)\n", info.second.uri.c_str(), info.second.filename.c_str(), (int) info.second.filesize, (int) info.second.nChunks); + g_receivedFiles[info.second.uri].info = info.second; + g_receivedFiles[info.second.uri].data.resize(info.second.filesize); + } + + g_hasFileInfos = true; + + return 0; + }); + + g_fileClient.setMessageCallback(GGSock::FileServer::MsgFileChunkResponse, [&](const char * dataBuffer, size_t dataSize) { + GGSock::FileServer::FileChunkResponseData data; + + size_t offset = 0; + GGSock::Unserialize()(data, dataBuffer, dataSize, offset); + + //printf("Received chunk %d for file '%s', size = %d\n", data.chunkId, data.uri.c_str(), (int) data.data.size()); + std::memcpy(g_receivedFiles[data.uri].data.data() + data.pStart, data.data.data(), data.pLen); + + return 0; + }); + return std::thread([&]() { Input inputCurrent; @@ -171,14 +357,16 @@ std::thread initMain() { lastRxDataLength = g_ggWave->takeRxData(lastRxData); if (lastRxDataLength > 0) { + auto message = std::string((char *) lastRxData.data(), lastRxDataLength); g_buffer.stateCore.update = true; g_buffer.stateCore.flags.newMessage = true; g_buffer.stateCore.message = { true, std::chrono::system_clock::now(), - std::string((char *) lastRxData.data(), lastRxDataLength), + std::move(message), g_ggWave->getRxProtocolId(), 0, + isFileBroadcastMessage(message) ? Message::FileBroadcast : Message::Text, }; } @@ -214,6 +402,34 @@ std::thread initMain() { } void renderMain() { + g_fileServer.update(); + + if (g_fileClient.isConnected()) { + if (!g_hasFileInfos) { + g_fileClient.send(GGSock::FileServer::MsgFileInfosRequest); + } else if (g_hasRequestedFiles == false) { + printf("Requesting files ...\n"); + for (const auto & fileInfo : g_fileInfos) { + for (int i = 0; i < fileInfo.second.nChunks; ++i) { + GGSock::FileServer::FileChunkRequestData data; + data.uri = fileInfo.second.uri; + data.chunkId = i; + data.nChunksHave = 0; + data.nChunksExpected = fileInfo.second.nChunks; + + GGSock::SerializationBuffer buffer; + GGSock::Serialize()(data, buffer); + g_fileClient.send(GGSock::FileServer::MsgFileChunkRequest, buffer.data(), (int32_t) buffer.size()); + g_fileClient.update(); + } + } + g_hasRequestedFiles = true; + g_receivedId++; + } + } + + g_fileClient.update(); + static State stateCurrent; { @@ -224,15 +440,23 @@ void renderMain() { enum class WindowId { Settings, Messages, + Files, Spectrum, }; + enum SubWindowId { + Send, + Receive, + }; + struct Settings { int protocolId = 1; float volume = 0.10f; }; static WindowId windowId = WindowId::Messages; + static SubWindowId subWindowId = SubWindowId::Send; + static Settings settings; const double tHoldContextPopup = 0.5f; @@ -290,6 +514,12 @@ void renderMain() { stateCurrent.update = false; } + if (g_focusFileSend) { + windowId = WindowId::Files; + subWindowId = SubWindowId::Send; + g_focusFileSend = false; + } + if (lastMouseButtonLeft == 0 && ImGui::GetIO().MouseDown[0] == 1) { ImGui::GetIO().MouseDelta = { 0.0, 0.0 }; } @@ -307,6 +537,8 @@ void renderMain() { #endif const float menuButtonHeight = 24.0f + 2.0f*style.ItemSpacing.y; + const auto & mouse_delta = ImGui::GetIO().MouseDelta; + ImGui::SetNextWindowPos({ 0, 0, }); ImGui::SetNextWindowSize(displaySize); ImGui::Begin("Main", nullptr, @@ -322,12 +554,17 @@ void renderMain() { windowId = WindowId::Settings; } ImGui::SameLine(); - - if (ImGui::Button(ICON_FA_COMMENT_ALT " Messages", { 0.5f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) { + + if (ImGui::Button(ICON_FA_COMMENT_ALT " Messages", { 0.35f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) { windowId = WindowId::Messages; } ImGui::SameLine(); - + + if (ImGui::Button(ICON_FA_FILE " Files", { 0.40f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) { + windowId = WindowId::Files; + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_SIGNAL " Spectrum", { 1.0f*ImGui::GetContentRegionAvailWidth(), menuButtonHeight })) { windowId = WindowId::Spectrum; } @@ -336,7 +573,7 @@ void renderMain() { ImGui::BeginChild("Settings:main", ImGui::GetContentRegionAvail(), true); ImGui::Text("%s", ""); ImGui::Text("%s", ""); - ImGui::Text("Waver v1.2.0"); + ImGui::Text("Waver v1.3.0"); ImGui::Separator(); ImGui::Text("%s", ""); @@ -460,7 +697,6 @@ void renderMain() { static bool isHoldingInput = false; static int messageIdHolding = 0; - const auto & mouse_delta = ImGui::GetIO().MouseDelta; const float tMessageFlyIn = 0.3f; // we need this because we push messages in the next loop @@ -493,9 +729,16 @@ void renderMain() { p0.x -= style.ItemSpacing.x; p0.y -= 0.5f*style.ItemSpacing.y; - auto col = style.Colors[ImGuiCol_Text]; - col.w = interp; - ImGui::TextColored(col, "%s", message.data.c_str()); + if (message.type == Message::FileBroadcast) { + auto col = ImVec4 { 0.0f, 1.0f, 1.0f, 1.0f }; + col.w = interp; + ImGui::TextColored(col, "-=[ File Broadcast ]=-"); + ImGui::TextColored(col, "%s", message.data.c_str()); + } else { + auto col = style.Colors[ImGuiCol_Text]; + col.w = interp; + ImGui::TextColored(col, "%s", message.data.c_str()); + } auto p1 = ImGui::GetCursorScreenPos(); p1.x = p00.x + ImGui::GetContentRegionAvailWidth(); @@ -536,7 +779,7 @@ void renderMain() { if (ImGui::Button("Resend")) { g_buffer.inputUI.update = true; - g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), messageSelected.data, messageSelected.protocolId, settings.volume }; + g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), messageSelected.data, messageSelected.protocolId, settings.volume, Message::Text }; messageHistory.push_back(g_buffer.inputUI.message); ImGui::CloseCurrentPopup(); @@ -551,6 +794,26 @@ void renderMain() { ImGui::CloseCurrentPopup(); } + //if (messageSelected.received && messageSelected.type == Message::FileBroadcast) { + if (messageSelected.type == Message::FileBroadcast) { + ImGui::SameLine(); + ImGui::TextDisabled("|"); + + ImGui::SameLine(); + if (ImGui::Button("Receive")) { + std::string tmp = messageSelected.data.data() + strlen(kFileBroadcastPrefix); + std::stringstream ss(tmp); + + ss >> g_remoteIP; + ss >> g_remotePort; + + g_fileClient.disconnect(); + g_hasFileInfos = false; + g_hasRequestedFiles = false; + ImGui::CloseCurrentPopup(); + } + } + ImGui::EndPopup(); } @@ -692,7 +955,7 @@ void renderMain() { ImGui::SameLine(); if ((ImGui::Button(sendButtonText) || doSendMessage) && inputBuf[0] != 0) { g_buffer.inputUI.update = true; - g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), std::string(inputBuf), settings.protocolId, settings.volume }; + g_buffer.inputUI.message = { false, std::chrono::system_clock::now(), std::string(inputBuf), settings.protocolId, settings.volume, Message::Text }; messageHistory.push_back(g_buffer.inputUI.message); @@ -707,6 +970,131 @@ void renderMain() { } } + if (windowId == WindowId::Files) { + const float subWindowButtonHeight = menuButtonHeight; + + if (ImGui::Button("Send", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) { + subWindowId = SubWindowId::Send; + } + ImGui::SameLine(); + + if (ImGui::Button("Receive", { 1.0f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) { + subWindowId = SubWindowId::Receive; + } + + switch (subWindowId) { + case SubWindowId::Send: + { + { + const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), 0.60f*ImGui::GetContentRegionAvail().y }; + + ImGui::BeginChild("Files:Send:fileInfos", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + //ImGui::PushTextWrapPos(); + auto fileInfos = g_fileServer.getFileInfos(); + for (const auto & fileInfo : fileInfos) { + ImGui::PushID(fileInfo.first); + ImGui::Text("File: '%s' (%4.2f MB)\n", fileInfo.second.filename.c_str(), float(fileInfo.second.filesize)/1024.0f/1024.0f); + if (ImGui::Button("Save")) { + g_shareFilename = fileInfo.second.filename; + g_shareId++; + } + ImGui::SameLine(); + + if (ImGui::Button("Clear")) { + g_deleteFilename = fileInfo.second.filename; + g_deleteId++; + } + + ImGui::PopID(); + } + //ImGui::PopTextWrapPos(); + + ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left); + ImGui::EndChild(); + + if (ImGui::Button("Broadcast", { 0.40f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) { + g_buffer.inputUI.update = true; + g_buffer.inputUI.message = { + false, + std::chrono::system_clock::now(), + ::generateFileBroadcastMessage(), + settings.protocolId, + settings.volume, + Message::FileBroadcast + }; + + messageHistory.push_back(g_buffer.inputUI.message); + + g_fileServer.startListening(); + } + ImGui::SameLine(); + + if (ImGui::Button("Stop", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) { + g_fileServer.stopListening(); + } + ImGui::SameLine(); + + if (ImGui::Button("Clear", { 1.0f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) { + g_deleteFilename = "###ALL-FILES###"; + g_deleteId++; + } + } + + { + const auto wSize = ImVec2 { ImGui::GetContentRegionAvailWidth(), ImGui::GetContentRegionAvail().y }; + ImGui::BeginChild("Files:Send:clientInfos", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + if (g_fileServer.isListening() == false) { + ImGui::TextColored({ 1.0f, 1.0f, 0.0f, 1.0f }, "Not accepting new connections"); + } else { + ImGui::TextColored({ 0.0f, 1.0f, 0.0f, 1.0f }, "Accepting new connections at %s:%d", + GGSock::Communicator::getLocalAddress().c_str(), g_fileServer.getParameters().listenPort); + } + + auto clientInfos = g_fileServer.getClientInfos(); + if (clientInfos.empty()) { + ImGui::Text("No peers currently connected .."); + } else { + for (const auto & clientInfo : clientInfos) { + ImGui::Text("Peer: %s\n", clientInfo.second.address.c_str()); + } + } + + ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left); + ImGui::EndChild(); + } + } + break; + case SubWindowId::Receive: + { + const auto wSize = ImGui::GetContentRegionAvail(); + + ImGui::BeginChild("Files:Receive:main", wSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + + ImGui::Text("Remote IP: %s", g_remoteIP.c_str()); + ImGui::Text("Remote Port: %d", g_remotePort); + + if (ImGui::Button("Connect", { 0.50f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) { + if (g_fileClient.connect(g_remoteIP, g_remotePort, 0)) { + printf("Started connecting ...\n"); + } + } + ImGui::SameLine(); + + if (ImGui::Button("Disconnect", { 1.00f*ImGui::GetContentRegionAvailWidth(), subWindowButtonHeight })) { + if (g_fileClient.disconnect()) { + printf("Stopped connecting\n"); + } + } + + ScrollWhenDraggingOnVoid(ImVec2(-mouse_delta.x, -mouse_delta.y), ImGuiMouseButton_Left); + ImGui::EndChild(); + } + break; + }; + } + if (windowId == WindowId::Spectrum) { ImGui::BeginChild("Spectrum:main", ImGui::GetContentRegionAvail(), true); ImGui::PushTextWrapPos(); diff --git a/examples/ggwave-gui/common.h b/examples/ggwave-gui/common.h index 78169f7..780183b 100644 --- a/examples/ggwave-gui/common.h +++ b/examples/ggwave-gui/common.h @@ -3,7 +3,32 @@ #include "ggwave-common-sdl2.h" #include +#include std::thread initMain(); void renderMain(); void deinitMain(std::thread & worker); + +int getShareId(); +const char * getShareFilename(); + +int getDeleteId(); +const char * getDeleteFilename(); + +int getReceivedId(); +std::vector getReceivedFilename(); +std::vector getReceivedDataBuffer(); +std::vector getReceivedDataSize(); + +void clearFiles(); + +void addFile( + const char * uri, + const char * filename, + const char * dataBuffer, + size_t dataSize); + +void addFile( + const char * uri, + const char * filename, + std::vector && data); diff --git a/examples/ggwave-gui/main.cpp b/examples/ggwave-gui/main.cpp index e1264ef..032e90d 100644 --- a/examples/ggwave-gui/main.cpp +++ b/examples/ggwave-gui/main.cpp @@ -7,10 +7,37 @@ #include #include -// ImGui helpers +#include +#include +#include -bool ImGui_BeginFrame(SDL_Window * window); -bool ImGui_EndFrame(SDL_Window * window); +std::vector readFile(const char* filename) { + // open the file: + std::ifstream file(filename, std::ios::binary); + + // Stop eating new lines in binary mode!!! + file.unsetf(std::ios::skipws); + + // get its size: + std::streampos fileSize; + + file.seekg(0, std::ios::end); + fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + // reserve capacity + std::vector vec; + vec.reserve(fileSize); + + // read the data: + vec.insert(vec.begin(), + std::istream_iterator(file), + std::istream_iterator()); + + return vec; +} + +// ImGui helpers bool ImGui_BeginFrame(SDL_Window * window) { SDL_Event event; @@ -19,6 +46,12 @@ bool ImGui_BeginFrame(SDL_Window * window) { ImGui_ProcessEvent(&event); if (event.type == SDL_QUIT) return false; if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) return false; + if (event.type == SDL_DROPFILE) { + printf("Dropped file: '%s'\n", event.drop.file); + auto data = readFile(event.drop.file); + addFile(event.drop.file, event.drop.file, std::move(data)); + break; + } } ImGui_NewFrame(window); @@ -123,20 +156,24 @@ int main(int argc, char** argv) { return -1; } + ImGui_PreInit(); + int windowX = 400; int windowY = 600; const char * windowTitle = "ggwave-gui"; SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); SDL_Window * window = SDL_CreateWindow(windowTitle, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, windowX, windowY, window_flags); - void * gl_context = SDL_GL_CreateContext(window); + SDL_GL_MakeCurrent(window, gl_context); SDL_GL_SetSwapInterval(1); // Enable vsync ImGui_Init(window, gl_context); ImGui_SetStyle(); + SDL_EventState(SDL_DROPFILE, SDL_ENABLE); + ImGui_NewFrame(window); ImGui::Render(); diff --git a/examples/third-party/CMakeLists.txt b/examples/third-party/CMakeLists.txt index db51b52..6fb6209 100644 --- a/examples/third-party/CMakeLists.txt +++ b/examples/third-party/CMakeLists.txt @@ -2,3 +2,4 @@ if (NOT EMSCRIPTEN) endif() add_subdirectory(imtui) add_subdirectory(imgui) +add_subdirectory(ggsock) diff --git a/examples/third-party/ggsock b/examples/third-party/ggsock new file mode 160000 index 0000000..cf34042 --- /dev/null +++ b/examples/third-party/ggsock @@ -0,0 +1 @@ +Subproject commit cf340425dcde6a67bc89d801e36edb1d4b3fc1b5 diff --git a/examples/third-party/imgui/imgui-extra/imgui_impl.cpp b/examples/third-party/imgui/imgui-extra/imgui_impl.cpp index 72d971e..bc94c39 100644 --- a/examples/third-party/imgui/imgui-extra/imgui_impl.cpp +++ b/examples/third-party/imgui/imgui-extra/imgui_impl.cpp @@ -17,18 +17,16 @@ #include -ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context) { +bool ImGui_PreInit() { // Decide GL+GLSL versions #if __APPLE__ // GL 3.2 Core + GLSL 150 - const char* glsl_version = "#version 150"; SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); #else // GL 3.0 + GLSL 130 - const char* glsl_version = "#version 130"; SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); @@ -40,6 +38,19 @@ ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context) { SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + return true; +} + +ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context) { + // Decide GL+GLSL versions +#if __APPLE__ + // GL 3.2 Core + GLSL 150 + const char* glsl_version = "#version 150"; +#else + // GL 3.0 + GLSL 130 + const char* glsl_version = "#version 130"; +#endif + static bool isInitialized = false; if (!isInitialized) { // Initialize OpenGL loader diff --git a/examples/third-party/imgui/imgui-extra/imgui_impl.h b/examples/third-party/imgui/imgui-extra/imgui_impl.h index 03c5b0d..9abe3cd 100644 --- a/examples/third-party/imgui/imgui-extra/imgui_impl.h +++ b/examples/third-party/imgui/imgui-extra/imgui_impl.h @@ -14,6 +14,7 @@ struct SDL_Window; typedef void * SDL_GLContext; typedef union SDL_Event SDL_Event; +IMGUI_API bool ImGui_PreInit(); IMGUI_API ImGuiContext* ImGui_Init(SDL_Window* window, SDL_GLContext gl_context); void IMGUI_API ImGui_Shutdown();