From 47b1e70663f7f5144ef604961cbae0657315304d Mon Sep 17 00:00:00 2001 From: "Ivan I. Ovchinnikov" Date: Mon, 27 Jul 2026 21:15:36 +0300 Subject: [PATCH] initial --- .idea/.gitignore | 10 + .idea/editor.xml | 345 +++++++++++++++++++++++++++++++++++ .idea/misc.xml | 12 ++ .idea/vcs.xml | 8 + CMakeLists.txt | 72 ++++++++ include/ConnectionWindow.h | 42 +++++ include/LogWindow.h | 14 ++ include/PayloadWindow.h | 22 +++ include/SerialApp.h | 34 ++++ main.cpp | 80 ++++++++ sources/ConnectionWindow.cpp | 140 ++++++++++++++ sources/LogWindow.cpp | 26 +++ sources/PayloadWindow.cpp | 67 +++++++ sources/SerialApp.cpp | 135 ++++++++++++++ style/dracula.theme | 31 ++++ 15 files changed, 1038 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/editor.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/vcs.xml create mode 100644 CMakeLists.txt create mode 100644 include/ConnectionWindow.h create mode 100644 include/LogWindow.h create mode 100644 include/PayloadWindow.h create mode 100644 include/SerialApp.h create mode 100644 main.cpp create mode 100644 sources/ConnectionWindow.cpp create mode 100644 sources/LogWindow.cpp create mode 100644 sources/PayloadWindow.cpp create mode 100644 sources/SerialApp.cpp create mode 100644 style/dracula.theme diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..ab1f416 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..8d0e15e --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,345 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..ff22958 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..d23aa41 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..6ebd5af --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,72 @@ +cmake_minimum_required(VERSION 3.10) +project(serial_sample CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# 1. FIX: Tell CMake to use modern GLVND OpenGL libraries and suppress the warning +set(OpenGL_GL_PREFERENCE GLVND) + +# Define file paths for thirdparty modules +set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/imgui) +set(IMPLOT_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../thirdparty/implot) + +# Find Required Packages +find_package(PkgConfig REQUIRED) +find_package(OpenGL REQUIRED) +pkg_check_modules(GLFW REQUIRED glfw3) +pkg_check_modules(LIBSERIALPORT REQUIRED IMPORTED_TARGET libserialport) + + +# Collect core library source files +set(IMGUI_SOURCES + ${IMGUI_DIR}/imgui.cpp + ${IMGUI_DIR}/imgui_demo.cpp + ${IMGUI_DIR}/imgui_draw.cpp + ${IMGUI_DIR}/imgui_tables.cpp + ${IMGUI_DIR}/imgui_widgets.cpp + ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp + ${IMGUI_DIR}/backends/imgui_impl_opengl3.cpp +) + +set(IMPLOT_SOURCES + ${IMPLOT_DIR}/implot.cpp + ${IMPLOT_DIR}/implot_items.cpp + ${IMPLOT_DIR}/implot_demo.cpp + sources/SerialApp.cpp + include/SerialApp.h +) + +add_executable(${PROJECT_NAME} main.cpp + ${IMGUI_SOURCES} + ${IMPLOT_SOURCES} + sources/ConnectionWindow.cpp + include/ConnectionWindow.h + sources/PayloadWindow.cpp + include/PayloadWindow.h + sources/LogWindow.cpp + include/LogWindow.h +) + + +# Provide Header Search Paths +target_include_directories(${PROJECT_NAME} PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${IMGUI_DIR} + ${IMGUI_DIR}/backends + ${IMPLOT_DIR} + ${GLFW_INCLUDE_DIRS} + ${OPENGL_INCLUDE_DIR} +) + +# Link Libraries +target_link_libraries(${PROJECT_NAME} PRIVATE + ${GLFW_LIBRARIES} + OpenGL::GL + X11 + pthread + dl + PkgConfig::LIBSERIALPORT +) + +configure_file(style/dracula.theme ${CMAKE_CURRENT_BINARY_DIR}/dracula.theme COPYONLY) diff --git a/include/ConnectionWindow.h b/include/ConnectionWindow.h new file mode 100644 index 0000000..512b93d --- /dev/null +++ b/include/ConnectionWindow.h @@ -0,0 +1,42 @@ +#ifndef SERIAL_SAMPLE_CONNECTIONWINDOW_H +#define SERIAL_SAMPLE_CONNECTIONWINDOW_H + +#include +#include +#include + +struct SerialPortInfo { + std::string port_name; + std::string description; +}; + +class ConnectionWindow { +public: + ConnectionWindow(); + ~ConnectionWindow(); + + void Initialize(); + void Render(bool* p_open); + + bool IsConnected() const { return is_connected; } + struct sp_port* GetActivePort() const { return active_port; } + + void ForceLogMessage(const std::string& msg); + std::string& GetSharedLog() { return tx_log; } + +private: + void RefreshPorts(); + + std::vector ports; + int selected_port_idx; + + // Новые переменные для выпадающего списка Baud Rate + std::vector baud_rates; + int selected_baud_idx; + + bool is_connected; + struct sp_port* active_port; + std::string tx_log; +}; + +#endif // SERIAL_SAMPLE_CONNECTIONWINDOW_H diff --git a/include/LogWindow.h b/include/LogWindow.h new file mode 100644 index 0000000..23a49a9 --- /dev/null +++ b/include/LogWindow.h @@ -0,0 +1,14 @@ +#ifndef SERIAL_SAMPLE_LOGWINDOW_H +#define SERIAL_SAMPLE_LOGWINDOW_H + +class ConnectionWindow; + +class LogWindow { +public: + LogWindow() = default; + ~LogWindow() = default; + + void Render(bool* p_open, ConnectionWindow& conn_win); +}; + +#endif // SERIAL_SAMPLE_LOGWINDOW_H diff --git a/include/PayloadWindow.h b/include/PayloadWindow.h new file mode 100644 index 0000000..630c4dc --- /dev/null +++ b/include/PayloadWindow.h @@ -0,0 +1,22 @@ +#ifndef SERIAL_SAMPLE_PAYLOADWINDOW_H +#define SERIAL_SAMPLE_PAYLOADWINDOW_H + +#include +#include + +// Вперед-идущее объявление, чтобы не плодить инклуды +class ConnectionWindow; + +class PayloadWindow { +public: + PayloadWindow(); + ~PayloadWindow() = default; + + void Render(bool* p_open, ConnectionWindow& conn_win); + +private: + void SendHexPayload(const std::string& hex_str, ConnectionWindow& conn_win); + char hex_input_buffer[256]; +}; + +#endif // SERIAL_SAMPLE_PAYLOADWINDOW_H diff --git a/include/SerialApp.h b/include/SerialApp.h new file mode 100644 index 0000000..70c9c60 --- /dev/null +++ b/include/SerialApp.h @@ -0,0 +1,34 @@ +#ifndef SERIAL_SAMPLE_SERIALAPP_H +#define SERIAL_SAMPLE_SERIALAPP_H + +#include "ConnectionWindow.h" +#include "PayloadWindow.h" +#include "LogWindow.h" +#include + +class SerialApp { +public: + SerialApp(); + ~SerialApp() = default; + + void Initialize(); + void RenderUI(); + + bool ShouldClose() const { return should_close; } + +private: + void RenderMainMenuBar(); + bool LoadTheme(const std::string& filepath); // Метод парсинга файла темы + + bool show_connection_window = true; + bool show_payload_window = true; + bool show_log_window = true; + + bool should_close; + + ConnectionWindow connection_window; + PayloadWindow payload_window; + LogWindow log_window; +}; + +#endif // SERIAL_SAMPLE_SERIALAPP_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..8d51a24 --- /dev/null +++ b/main.cpp @@ -0,0 +1,80 @@ +#include + +#include "imgui.h" +#include "imgui_impl_glfw.h" +#include "imgui_impl_opengl3.h" +#include +#include + +#include "include/SerialApp.h" + +static void glfw_error_callback(int error, const char* description) { + fprintf(stderr, "GLFW Error %d: %s\n", error, description); +} + +int main(int, char**) { + glfwSetErrorCallback(glfw_error_callback); + if (!glfwInit()) return 1; + + // Настройка версии OpenGL (3.3 Core) + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + + // Создание окна ОС + GLFWwindow* window = glfwCreateWindow(1280, 720, "Serial App", nullptr, nullptr); + if (window == nullptr) return 1; + glfwMakeContextCurrent(window); + glfwSwapInterval(1); // Включение вертикальной синхронизации (V-Sync) + + // Инициализация контекста ImGui + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + ImGui::StyleColorsDark(); + + // Инициализация платформных бэкендов внутри ImGui + ImGui_ImplGlfw_InitForOpenGL(window, true); + ImGui_ImplOpenGL3_Init("#version 130"); + + // Создаем и инициализируем наше изолированное приложение + SerialApp app; + app.Initialize(); + + // Главный цикл приложения + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + // Старт нового кадра ImGui + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + // Рендерим наше кастомное UI + app.RenderUI(); + if (app.ShouldClose()) { + glfwSetWindowShouldClose(window, GLFW_TRUE); + } + + // Рендеринг графики + ImGui::Render(); + int display_w, display_h; + glfwGetFramebufferSize(window, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(0.45f, 0.55f, 0.60f, 1.00f); + glClear(GL_COLOR_BUFFER_BIT); + + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + glfwSwapBuffers(window); + } + + // Очистка ресурсов перед выходом + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + glfwDestroyWindow(window); + glfwTerminate(); + + return 0; +} diff --git a/sources/ConnectionWindow.cpp b/sources/ConnectionWindow.cpp new file mode 100644 index 0000000..1f9d8f6 --- /dev/null +++ b/sources/ConnectionWindow.cpp @@ -0,0 +1,140 @@ +#include "../include/ConnectionWindow.h" +#include "imgui.h" +#include + +ConnectionWindow::ConnectionWindow() + : selected_port_idx(0), is_connected(false), active_port(nullptr) { + + // Заполняем массив стандартными скоростями UART + baud_rates = { 9600, 19200, 38400, 57600, 115200, 230400, 460800, 921600 }; + + // По умолчанию выбираем 9600 (индекс 0 в векторе) + selected_baud_idx = 0; +} + +ConnectionWindow::~ConnectionWindow() { + if (is_connected && active_port) { + sp_close(active_port); + sp_free_port(active_port); + } +} + +void ConnectionWindow::Initialize() { + RefreshPorts(); +} + +void ConnectionWindow::RefreshPorts() { + ports.clear(); + selected_port_idx = 0; + + struct sp_port** port_list; + if (sp_list_ports(&port_list) == SP_OK) { + for (int i = 0; port_list[i] != nullptr; i++) { + struct sp_port* port = port_list[i]; + SerialPortInfo info; + const char* name = sp_get_port_name(port); + info.port_name = name ? name : "Unknown"; + const char* desc = sp_get_port_description(port); + info.description = desc ? desc : "No Description"; + ports.push_back(info); + } + sp_free_port_list(port_list); + } + + if (ports.empty()) { + ports.push_back({"None", "No serial devices detected"}); + } +} + +void ConnectionWindow::ForceLogMessage(const std::string& msg) { + tx_log += msg + "\n"; +} + +void ConnectionWindow::Render(bool* p_open) { + if (!ImGui::Begin("Connection Settings", p_open)) { + ImGui::End(); + return; + } + + if (ImGui::Button("Refresh Ports") && !is_connected) { + RefreshPorts(); + } + ImGui::SameLine(); + + // 1. Выбор COM-порта + std::string combo_preview = ports[selected_port_idx].port_name; + if (ports[selected_port_idx].port_name != "None") { + combo_preview += " (" + ports[selected_port_idx].description + ")"; + } + + ImGui::BeginDisabled(is_connected); + if (ImGui::BeginCombo("Serial Port", combo_preview.c_str())) { + for (int n = 0; n < ports.size(); n++) { + const bool is_selected = (selected_port_idx == n); + std::string item_text = ports[n].port_name + " - " + ports[n].description; + if (ImGui::Selectable(item_text.c_str(), is_selected)) { + selected_port_idx = n; + } + } + ImGui::EndCombo(); + } + + // 2. Выбор Baud Rate через выпадающий список + std::string baud_preview = std::to_string(baud_rates[selected_baud_idx]); + if (ImGui::BeginCombo("Baud Rate", baud_preview.c_str())) { + for (int b = 0; b < baud_rates.size(); b++) { + const bool is_selected = (selected_baud_idx == b); + std::string baud_item_text = std::to_string(baud_rates[b]); + + if (ImGui::Selectable(baud_item_text.c_str(), is_selected)) { + selected_baud_idx = b; + } + } + ImGui::EndCombo(); + } + ImGui::EndDisabled(); + + // 3. Логика подключения + if (!is_connected) { + ImGui::BeginDisabled(ports[selected_port_idx].port_name == "None"); + if (ImGui::Button("Connect", ImVec2(120, 0))) { + if (sp_get_port_by_name(ports[selected_port_idx].port_name.c_str(), &active_port) == SP_OK) { + if (sp_open(active_port, SP_MODE_READ_WRITE) == SP_OK) { + + // Передаем реальное значение скорости из выбранного индекса массива + int real_baud = baud_rates[selected_baud_idx]; + sp_set_baudrate(active_port, real_baud); + + sp_set_bits(active_port, 8); + sp_set_parity(active_port, SP_PARITY_NONE); + sp_set_stopbits(active_port, 1); + + is_connected = true; + ForceLogMessage("[SYSTEM] Connected to " + ports[selected_port_idx].port_name + " at " + std::to_string(real_baud) + " baud."); + } else { + ForceLogMessage("[SYSTEM ERROR] Could not open port " + ports[selected_port_idx].port_name); + sp_free_port(active_port); + active_port = nullptr; + } + } + } + ImGui::EndDisabled(); + } else { + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.8f, 0.1f, 0.1f, 1.0f)); + if (ImGui::Button("Disconnect", ImVec2(120, 0))) { + if (active_port) { + sp_close(active_port); + sp_free_port(active_port); + active_port = nullptr; + } + is_connected = false; + ForceLogMessage("[SYSTEM] Disconnected."); + } + ImGui::PopStyleColor(); + } + + ImGui::SameLine(); + ImGui::Text("Status: %s", is_connected ? "CONNECTED" : "DISCONNECTED"); + + ImGui::End(); +} diff --git a/sources/LogWindow.cpp b/sources/LogWindow.cpp new file mode 100644 index 0000000..1c3083e --- /dev/null +++ b/sources/LogWindow.cpp @@ -0,0 +1,26 @@ +#include "../include/LogWindow.h" +#include "../include/ConnectionWindow.h" +#include "imgui.h" + +void LogWindow::Render(bool* p_open, ConnectionWindow& conn_win) { + if (!ImGui::Begin("Transaction Log", p_open)) { + ImGui::End(); + return; + } + + std::string& log_ref = conn_win.GetSharedLog(); + + ImGui::BeginChild("ScrollingRegion", ImVec2(0, -ImGui::GetFrameHeightWithSpacing()), true, ImGuiWindowFlags_HorizontalScrollbar); + ImGui::TextUnformatted(log_ref.c_str()); + + if (ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) { + ImGui::SetScrollHereY(1.0f); + } + ImGui::EndChild(); + + if (ImGui::Button("Clear Log")) { + log_ref.clear(); + } + + ImGui::End(); +} diff --git a/sources/PayloadWindow.cpp b/sources/PayloadWindow.cpp new file mode 100644 index 0000000..56c50d3 --- /dev/null +++ b/sources/PayloadWindow.cpp @@ -0,0 +1,67 @@ +#include "../include/PayloadWindow.h" + +#include "../include/ConnectionWindow.h" +#include +#include "imgui.h" +#include +#include +#include + +PayloadWindow::PayloadWindow() { + std::snprintf(hex_input_buffer, sizeof(hex_input_buffer), "AA BB 01 02 03 FF"); +} + +void PayloadWindow::SendHexPayload(const std::string& hex_str, ConnectionWindow& conn_win) { + struct sp_port* port = conn_win.GetActivePort(); + if (!conn_win.IsConnected() || !port) return; + + std::vector bytes; + std::stringstream ss(hex_str); + std::string byte_string; + + while (ss >> byte_string) { + unsigned int byte_val; + std::stringstream converter; + converter << std::hex << byte_string; + if (converter >> byte_val) { + bytes.push_back(static_cast(byte_val)); + } + } + + if (bytes.empty()) { + conn_win.ForceLogMessage("[ERROR] No valid bytes found to send."); + return; + } + + int bytes_written = sp_nonblocking_write(port, bytes.data(), bytes.size()); + + if (bytes_written >= 0) { + std::stringstream log_ss; + log_ss << "[TX] Sent " << bytes_written << " bytes: "; + for (size_t i = 0; i < bytes.size(); ++i) { + log_ss << std::uppercase << std::hex << std::setw(2) << std::setfill('0') << (int)bytes[i] << " "; + } + conn_win.ForceLogMessage(log_ss.str()); + } else { + conn_win.ForceLogMessage("[ERROR] Failed to write data to serial port."); + } +} + +void PayloadWindow::Render(bool* p_open, ConnectionWindow& conn_win) { + if (!ImGui::Begin("Raw Payload Terminal", p_open)) { + ImGui::End(); + return; + } + + ImGui::InputText("HEX Bytes", hex_input_buffer, IM_ARRAYSIZE(hex_input_buffer)); + ImGui::TextDisabled("Example: AA BB 0F 45"); + ImGui::Spacing(); + + ImGui::BeginDisabled(!conn_win.IsConnected()); + if (ImGui::Button("SEND PACKET", ImVec2(-1, 35))) { + SendHexPayload(hex_input_buffer, conn_win); + } + ImGui::EndDisabled(); + + ImGui::End(); +} diff --git a/sources/SerialApp.cpp b/sources/SerialApp.cpp new file mode 100644 index 0000000..43aa81e --- /dev/null +++ b/sources/SerialApp.cpp @@ -0,0 +1,135 @@ +#include "../include/SerialApp.h" +#include "imgui.h" +#include +#include +#include +#include + +SerialApp::SerialApp() : should_close(false) {} + +void SerialApp::Initialize() { + connection_window.Initialize(); + + // Загружаем тему при старте. Если лежит в корне сборки — просто имя файла. + if (!LoadTheme("dracula.theme")) { + // Если файла нет, оставляем стандартную тему ImGui, чтобы приложение не упало + ImGui::StyleColorsDark(); + } +} + +// Вспомогательная функция перевода HEX строки (например, "282a36ff") в ImVec4 +static ImVec4 HexToImVec4(const std::string& hex_str) { + if (hex_str.length() < 8) return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); + + unsigned int r, g, b, a; + std::stringstream ss; + ss << std::hex << hex_str.substr(0, 2); ss >> r; ss.clear(); + ss << std::hex << hex_str.substr(2, 2); ss >> g; ss.clear(); + ss << std::hex << hex_str.substr(4, 2); ss >> b; ss.clear(); + ss << std::hex << hex_str.substr(6, 2); ss >> a; + + return ImVec4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f); +} + +bool SerialApp::LoadTheme(const std::string& filepath) { + std::ifstream file(filepath); + if (!file.is_open()) { + std::cerr << "[THEME ERROR] Файл темы не найден: " << filepath << std::endl; + return false; + } + + ImGuiStyle& style = ImGui::GetStyle(); + + // Карта сопоставления строк из файла конфигурации и индексов цветов ImGui + std::unordered_map color_map = { + {"Color_Text", ImGuiCol_Text}, + {"Color_WindowBg", ImGuiCol_WindowBg}, + {"Color_ChildBg", ImGuiCol_ChildBg}, + {"Color_PopupBg", ImGuiCol_PopupBg}, + {"Color_Border", ImGuiCol_Border}, + {"Color_FrameBg", ImGuiCol_FrameBg}, + {"Color_FrameBgHovered", ImGuiCol_FrameBgHovered}, + {"Color_FrameBgActive", ImGuiCol_FrameBgActive}, + {"Color_TitleBg", ImGuiCol_TitleBg}, + {"Color_TitleBgActive", ImGuiCol_TitleBgActive}, + {"Color_MenuBarBg", ImGuiCol_MenuBarBg}, + {"Color_ScrollbarBg", ImGuiCol_ScrollbarBg}, + {"Color_ScrollbarGrab", ImGuiCol_ScrollbarGrab}, + {"Color_ScrollbarGrabHovered", ImGuiCol_ScrollbarGrabHovered}, + {"Color_ScrollbarGrabActive", ImGuiCol_ScrollbarGrabActive}, + {"Color_CheckMark", ImGuiCol_CheckMark}, + {"Color_SliderGrab", ImGuiCol_SliderGrab}, + {"Color_SliderGrabActive", ImGuiCol_SliderGrabActive}, + {"Color_Button", ImGuiCol_Button}, + {"Color_ButtonHovered", ImGuiCol_ButtonHovered}, + {"Color_ButtonActive", ImGuiCol_ButtonActive}, + {"Color_Header", ImGuiCol_Header}, + {"Color_HeaderHovered", ImGuiCol_HeaderHovered}, + {"Color_HeaderActive", ImGuiCol_HeaderActive}, + {"Color_Separator", ImGuiCol_Separator} + }; + + std::string line; + while (std::getline(file, line)) { + // Пропускаем пустые строки и комментарии + if (line.empty() || line[0] == '#') continue; + + std::stringstream ss(line); + std::string key; + ss >> key; + + if (key.rfind("Color_", 0) == 0) { + // Читаем HEX цвета + std::string hex_val; + ss >> hex_val; + if (color_map.find(key) != color_map.end()) { + style.Colors[color_map[key]] = HexToImVec4(hex_val); + } + } else { + // Читаем размеры скруглений + float val; + ss >> val; + if (key == "WindowRounding") style.WindowRounding = val; + else if (key == "FrameRounding") style.FrameRounding = val; + else if (key == "PopupRounding") style.PopupRounding = val; + else if (key == "GrabRounding") style.GrabRounding = val; + } + } + + file.close(); + return true; +} + +void SerialApp::RenderUI() { + RenderMainMenuBar(); + + if (show_connection_window) { + connection_window.Render(&show_connection_window); + } + if (show_payload_window) { + payload_window.Render(&show_payload_window, connection_window); + } + if (show_log_window) { + log_window.Render(&show_log_window, connection_window); + } +} + +void SerialApp::RenderMainMenuBar() { + if (ImGui::BeginMainMenuBar()) { + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("Exit", "Alt+F4")) { + should_close = true; + } + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Windows")) { + ImGui::MenuItem("1. Connection Settings", nullptr, &show_connection_window); + ImGui::MenuItem("2. Raw Payload Terminal", nullptr, &show_payload_window); + ImGui::MenuItem("3. Transaction Log", nullptr, &show_log_window); + ImGui::EndMenu(); + } + ImGui::EndMainMenuBar(); + } +} + diff --git a/style/dracula.theme b/style/dracula.theme new file mode 100644 index 0000000..852f0de --- /dev/null +++ b/style/dracula.theme @@ -0,0 +1,31 @@ +# Официальная палитра Dracula для Dear ImGui +WindowRounding 8.0 +FrameRounding 6.0 +PopupRounding 6.0 +GrabRounding 3.0 + +Color_Text f8f8f2ff +Color_WindowBg 282a36ff +Color_ChildBg 282a36ff +Color_PopupBg 282a36f0 +Color_Border 44475aff +Color_FrameBg 44475aff +Color_FrameBgHovered 6272a4ff +Color_FrameBgActive bd93f9ff +Color_TitleBg 282a36ff +Color_TitleBgActive bd93f9ff +Color_MenuBarBg 282a36ff +Color_ScrollbarBg 282a36ff +Color_ScrollbarGrab 6272a4ff +Color_ScrollbarGrabHovered bd93f9ff +Color_ScrollbarGrabActive ff79c6ff +Color_CheckMark 50fa7bff +Color_SliderGrab 6272a4ff +Color_SliderGrabActive bd93f9ff +Color_Button 6272a480 +Color_ButtonHovered bd93f9ff +Color_ButtonActive ff79c6ff +Color_Header 44475aff +Color_HeaderHovered bd93f9ff +Color_HeaderActive ff79c6ff +Color_Separator 44475aff