/* ImGui utils */ /* Fichier contenant quelques fonctionnalités basées sur ImGui : * VToggleButton() dérivé de ToggleButton * DrawVToggleButton * * Copyright Eric Bachard 2026/01/14 18h20 * License GPL v2 */ #include #include #include #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS #endif #include "digital_modulation_fr.hpp" #include "imgui.h" #include "vtoggle_button.h" #include "imgui_utils.h" #include "imgui_themes.h" #ifdef M_PI #undef M_PI #define M_PI 3.14159265358979323846f #endif void selectThemeMenu(Application * p_app) { static THEME selected_menutheme = p_app->get_current_theme(); if (ImGui::BeginMenu(THEME_MENU_ENTRY)) { if (ImGui::Selectable(TRADITIONAL_GREEN_THEME_MENU_ENTRY)) selected_menutheme = LIGHT_GREEN_THEME; if (ImGui::Selectable(DARK_THEME_MENU_ENTRY)) selected_menutheme = DARK_THEME; if (ImGui::Selectable(CLASSIC_THEME_MENU_ENTRY)) selected_menutheme = CLASSIC_THEME; if (ImGui::Selectable(LIGHT_BLUE_THEME_MENU_ENTRY)) selected_menutheme = LIGHT_BLUE_THEME; if (ImGui::Selectable(WINDOWS_THEME_MENU_ENTRY)) selected_menutheme = WINDOWS_THEME; if (selected_menutheme != p_app->get_current_theme()) p_app->setTheme(selected_menutheme); ImGui::EndMenu(); } } void selectFrameSize(DigitalModulation * p_Dmod) { // by default, we recopy the current value int frame_length = p_Dmod->getWordSize(); if (ImGui::BeginMenu(FRAME_SIZE)) { if (ImGui::Selectable(HEIGHT_BITS_FRAME_SIZE)) frame_length = 8; if (ImGui::Selectable(SIXTEEN_BITS_FRAME_SIZE)) frame_length = 16; if (ImGui::Selectable(TWENTY_FOUR_BITS_FRAME_SIZE)) frame_length = 24; if (frame_length != p_Dmod->getWordSize()) p_Dmod->setWordSize(frame_length); ImGui::EndMenu(); } } // ToogleButton transformé en VToggleButton bool VToggleButton(const char* str_id, bool* v) { ImVec2 p = ImGui::GetCursorScreenPos(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); // Taille du toggle // FIXME : elaborate float width = ImGui::GetFrameHeight(); //float width = VTOGGLEBUTTON_WIDTH; float height = width * 1.8f; float radius = width * 0.5f; ImGui::InvisibleButton(str_id, ImVec2(width, height)); // Vérifie si le bouton a été cliqué bool clicked = ImGui::IsItemClicked(); if (clicked) *v = !*v; // Choix de la couleur de fond selon l'état ImU32 col_bg = ImGui::IsItemHovered() ? (*v ? IM_COL32(165,231,88,255) : IM_COL32(200,200,200,255)) //ON : (*v ? IM_COL32(145,211,68,255) : IM_COL32(218,218,218,255)); //OFF draw_list->AddRectFilled( p, ImVec2(p.x + width, p.y + height), col_bg, radius ); // Dessine le rectangle du Toggle float cy = *v ? (p.y + radius) : (p.y + height - radius); // Position verticale du petit rond draw_list->AddCircleFilled( ImVec2(p.x + radius, cy), radius - 2.0f, IM_COL32(255,255,255,255) ); // Dessine le rond blanc du Toggle return clicked; } void drawVToggleButtons(int * dm_bits, int wordSize) { // CHILD2_WIDTH = 300.0f float drawSize = ImGui::GetContentRegionAvail().x - CHILD2_WIDTH; // FIXME : calculer sérieusement ces valeurs //TEST // La formule : // draw_size = (P+B)wordSize + P + marges // Il y a 1 padding de plus que de boutons dessinés // // 20.0f because fo the left/right margins // On dessine dans la child window 1, // et le nombre de pixels n'est plus le même en plein écran // exemple : drawSize = 664.0f contre 1304.0f en plein écran. // 2 solutions : soit dessiner a valeur définie et testée (pas très pro) // soit calculer précisément le padding. Or la formule ne semble pas fonctionner ... // La valeur de 20.0f est soustraite car il faut tenir compte d'une marge à gauche // et d'une marge à droite pour le canvas. // current best value : 20.0f float padding = ((drawSize - 20.0f) - VTOGGLEBUTTON_WIDTH * (float)wordSize)/((float)wordSize + 1); // hack ... le temps de corriger la formule if (wordSize == 8) padding += 32.0f; if (wordSize == 16) padding += 7.5f; if (drawSize > 1000.0F) { if (wordSize == 8) padding += 7.3f; // best value 7.3f if (wordSize == 16) padding += 2.3f; // best value 2.3f else padding += 1.2f; // best value 1.2f } // end hack ImGui::Dummy(ImVec2(padding / 2.0f, 0.0f)); ImGui::SameLine(); // Padding pour le premier bouton for (int i = 0; i < wordSize ; i++) { ImGui::PushID(i); bool bit = (dm_bits[i] != 0); if (VToggleButton("##Button", &bit)) dm_bits[i] = bit ? 1 : 0; ImGui::PopID(); if (i < (wordSize - 1)) // Pas de padding pour le dernier { ImGui::SameLine(); ImGui::Dummy(ImVec2(padding, 0.0f)); ImGui::SameLine(); } } // debug purpose //#define DEBUG_ME #ifdef DEBUG_ME ImGui::Text(" wordSize : %d", wordSize); ImGui::SameLine(); ImGui::Text(" padding : %f", padding); ImGui::SameLine(); ImGui::Text(" drawSize : %f", drawSize); ImGui::SameLine(); ImGui::Text("VTOGGLEBUTTON_WIDTH : %f", VTOGGLEBUTTON_WIDTH); #endif /* DEBUG_ME */ } void highlightFrame(int * dm_bits, int wordSize) { ImVec2 text_pos = ImGui::GetCursorScreenPos(); // pour avoir la surbrillance de longueur variable std::string text_trame = "Trame :"; for (int i = 0; i < wordSize; i++) { if (0 == i % 8 && i != 0) // Découpage par octets text_trame += " "; text_trame += " "; text_trame += std::to_string(dm_bits[i]); } ImVec2 text_size = ImGui::CalcTextSize(text_trame.c_str()); ImGui::GetWindowDrawList()->AddRectFilled( text_pos, ImVec2(text_pos.x + text_size.x, text_pos.y + text_size.y), IM_COL32(255, 255, 0, 128) // Jaune semi-transparent ); ImGui::Text("%s", text_trame.c_str()); ImGui::NewLine(); ImGui::Separator(); } // Helper to draw a small modulation example (Témoin visuel). Code proposé par void DrawIndicator(const char * label, float * carrier_freq, DigitalModulationType * p_modulation_type, int * samples_per_bit, float amp_scale, float phase_offset, ImU32 color, ImVec2 size) { ImGui::Text("%s", label); ImVec2 p = ImGui::GetCursorScreenPos(); ImDrawList* draw_list = ImGui::GetWindowDrawList(); // Fond gris clair pour le témoin draw_list->AddRectFilled(p, ImVec2(p.x + size.x, p.y + size.y), IM_COL32(245, 245, 245, 255)); // Bordure draw_list->AddRect(p, ImVec2(p.x + size.x, p.y + size.y), IM_COL32(180, 180, 180, 255)); float cy = p.y + size.y * 0.5f; // Centre vertical float base_amp = (size.y * 0.35f); // Amplitude de base (laisse une marge) ImVec2 prev; bool first = true; // On dessine 2 périodes (ou le double si un symbole est codé sur 2 bits), // pour bien visualiser la forme static float k2 = 1.0f; switch (*p_modulation_type) { case MOD_MASK_TYPE : case MOD_M_QAM_TYPE: case MOD_4_QAM_TYPE: case MOD_MFSK_TYPE : k2 = 2.0f; break; default: k2 = 1.0f; break; } // tracé de la courbe for (int i = 0; i <= *samples_per_bit; i++) { float t = (float)i / (float)*samples_per_bit; // 0 à 1 // Formule du signal : sin(2*PI * f * t + phase) float y = cy - sinf(k2 * 2.0f * M_PI * (* carrier_freq) * t + phase_offset) * (base_amp * amp_scale); float x = p.x + t * size.x; ImVec2 curr(x, y); if (!first) draw_list->AddLine(prev, curr, color, 1.5f); // Ligne un peu plus épaisse prev = curr; first = false; } // Réserve l'espace pour qu'ImGui ne dessine pas par dessus ImGui::Dummy(size); // Petit espacement vertical après ImGui::Dummy(ImVec2(0, 5)); }