En esta lección aprenderemos a mostrar menús. Vamos a desarrollar una aplicación similar al bloc de notas TXT, con una barra de menú superior que incluye una subopción Guardar. Al pulsar Guardar, se guardará todo el texto escrito en el recuadro inferior. Aquí trataremos el problema de caracteres corruptos y aprenderemos a configurar la fuente: si no se configura, los caracteres que no estén en inglés se verán deformados con signos de interrogación.
Al guardar, se abrirá un diálogo nativo de guardado de archivos. El usuario podrá elegir la carpeta destino, escribir un nombre de archivo y completar el guardado.
A continuación tienes la captura de pantalla del programa en ejecución:

Es similar al bloc de notas de Windows, cuenta con funciones básicas de edición y guardado, y el archivo compilado es muy ligero.
Si compilamos en modo Release, solo ocupa 645kb.

Veamos el código completo del programa:
#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#pragma execution_character_set("utf-8")
#include <GLFW/glfw3.h>
#include "imgui.h"
#include "backends/imgui_impl_glfw.h"
#include "backends/imgui_impl_opengl3.h"
#include <cstdio>
#include <string>
#include <fstream>
#include <windows.h>
#include <io.h>
#include <commdlg.h> // Cabecera necesaria para los diálogos de archivo
// Límite máximo del búfer de texto
const int TEXT_BUFFER_MAX = 4096;
std::string editor_text;
// Función para guardar texto en archivo con marca UTF8 BOM
// Escribe el contenido textual en el archivo destino
bool SaveTextToFile(const char* filepath, const std::string& text)
{
std::ofstream out_file(filepath, std::ios::out | std::ios::binary);
if (!out_file.is_open())
return false;
const unsigned char utf8_bom[] = { 0xEF, 0xBB, 0xBF };
out_file.write((char*)utf8_bom, sizeof(utf8_bom));
out_file << text.c_str(); // Solo escribe el texto válido hasta el terminador nulo
out_file.close();
return true;
}
// Muestra el diálogo nativo de guardado y devuelve la ruta seleccionada por el usuario
bool ShowSaveFileDialog(char* out_path, DWORD out_size)
{
OPENFILENAMEA ofn; // Usamos la versión A (ANSI)
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = nullptr;
ofn.lpstrFilter = "Archivos de texto\0*.txt\0Todos los archivos\0*.*\0"; // Filtro para txt o cualquier formato
ofn.lpstrFile = out_path;
ofn.nMaxFile = out_size;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_OVERWRITEPROMPT;
ofn.lpstrDefExt = "txt";
return GetSaveFileNameA(&ofn); // Llamada a la función de diálogo ANSI
}
// Configurar fuente para solucionar el problema de signos de interrogación
// Escribe la ruta de la fuente correspondiente a tu entorno o usa la fuente por defecto del sistema
void SetupFont(ImGuiIO& io)
{
// Ruta común de fuente china en Windows
const char* font_path = "C:/Windows/Fonts/msyh.ttc"; // Microsoft YaHei
float font_size = 18.0f;
// Cargar rango de caracteres chinos simplificados habituales
ImVector<ImWchar> ranges;
ImFontGlyphRangesBuilder builder;
builder.AddRanges(io.Fonts->GetGlyphRangesChineseSimplifiedCommon());
builder.BuildRanges(&ranges);
// Cargar fuente personalizada
io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, ranges.Data);
// Si necesitas soporte multilingüe, puedes agregar lo siguiente:
// io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, io.Fonts->GetGlyphRangesJapanese());
// io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, io.Fonts->GetGlyphRangesKorean());
}
int main()
{
SetConsoleOutputCP(65001);
// Punto crucial: ampliar el búfer y rellenarlo con ceros para borrar datos residuales de memoria, soluciona los ????
editor_text.resize(TEXT_BUFFER_MAX, '\0');
if (!glfwInit())
{
printf("Fallo al inicializar GLFW\n");
return -1;
}
const char* glsl_version = "#version 330";
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(800, 600, "Editor de Notas de Texto", nullptr, nullptr);
if (!window)
{
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
ImGui::StyleColorsLight();
SetupFont(io); // Cargar fuente para corregir caracteres corruptos
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
bool show_save_success = false;
bool show_save_fail = false;
char path[MAX_PATH] = "";// Búfer para almacenar la ruta del archivo guardado
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
// Atajo de teclado Ctrl+S para guardar
if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_S))
{
if (ShowSaveFileDialog(path, MAX_PATH))
{
if (SaveTextToFile(path, editor_text))
show_save_success = true;
else
show_save_fail = true;
}
}
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
ImGui::SetNextWindowPos(ImVec2(0, 0));
ImGui::SetNextWindowSize(io.DisplaySize);
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration
| ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoSavedSettings
| ImGuiWindowFlags_MenuBar; // Bandera obligatoria para mostrar la barra de menú
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::Begin("Editor de Notas de Texto", nullptr, window_flags);
// Renderizado de la barra de menú superior
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("Archivo"))
{
if (ImGui::MenuItem("Guardar", "Ctrl+S")) // Opción Guardar: texto visible + indicador de atajo
{
// Funciona igual que los botones vistos anteriormente, el código se ejecuta al pulsar el menú
if (ShowSaveFileDialog(path, MAX_PATH)) // Abrir diálogo de guardado
{
if (SaveTextToFile(path, editor_text)) // Ejecutar guardado del archivo
show_save_success = true; // Marcar éxito para mostrar ventana de aviso después
else
show_save_fail = true;
}
}
ImGui::Separator();
if (ImGui::MenuItem("Salir", "Esc")) // Opción para cerrar el programa
{
glfwSetWindowShouldClose(window, true); // Activar bandera de cierre para finalizar la app
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
ImGui::Spacing();
ImVec2 multiline_size = ImGui::GetContentRegionAvail();
// Solución principal a los ?: pasar longitud máxima fija de búfer, memoria inicializada en cero sin datos residuales
ImGui::InputTextMultiline("##text_editor", &editor_text[0], TEXT_BUFFER_MAX, multiline_size);
// Activar popups según el resultado del guardado
if (show_save_success)
{
// Esta línea no dibuja ventanas ni ejecuta BeginPopupModal
ImGui::OpenPopup("Guardado Exitoso");// Establece una marca interna en ImGui para mostrar un modal, el texto es ID único del popup
show_save_success = false;
}
if (show_save_fail)
{
ImGui::OpenPopup("Error al Guardar");// Marca para el popup de error
show_save_fail = false;
}
// Dibujar modal de éxito
if (ImGui::BeginPopupModal("Guardado Exitoso", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("El texto se guardó correctamente!");
if (ImGui::Button("Aceptar", ImVec2(120, 0)))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
// Dibujar modal de error
if (ImGui::BeginPopupModal("Error al Guardar", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("Ruta inválida o sin permisos de escritura!");
if (ImGui::Button("Aceptar", ImVec2(120, 0)))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
ImGui::End();
ImGui::PopStyleVar(2);
ImGui::Render();
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);
glClearColor(0.12f, 0.12f, 0.12f, 1.f);
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;
}Lenguaje del código: C++ (cpp)
He encapsulado toda la lógica básica en funciones independientes: escritura de archivos, apertura de diálogos de selección y configuración de fuentes para idiomas.
En comparación con ejemplos anteriores, la interfaz añade dos componentes nuevos: barra de menú y campo de texto multilínea.
Barra de menú
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("Archivo"))
{Lenguaje del código: C++ (cpp)
Aquí empieza el renderizado de menús. El if exterior crea un menú principal con ImGui::BeginMenu(«Archivo»).
Dentro se colocan los elementos clickeables del submenú. Abajo tienes el ejemplo oficial de la librería:
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("Archivo"))
{
if (ImGui::MenuItem("Abrir..", "Ctrl+O")) { /* Código funcional */ }
if (ImGui::MenuItem("Guardar", "Ctrl+S")) { /* Código funcional */ }
if (ImGui::MenuItem("Cerrar", "Ctrl+W")) { my_tool_active = false; }
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
Para agregar varios menús paralelos:
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("Archivo"))
{
if (ImGui::MenuItem("Guardar", "Ctrl+S")) // Opción Guardar: texto + indicador de atajo
{
// Funciona igual que botones normales, el código se ejecuta al clickar el menú
if (ShowSaveFileDialog(path, MAX_PATH)) // Abrir diálogo de guardado
{
if (SaveTextToFile(path, editor_text)) // Ejecutar guardado
show_save_success = true; // Activar bandera para aviso popup
else
show_save_fail = true;
}
}
ImGui::Separator();
if (ImGui::MenuItem("Salir", "Esc")) // Opción Salir
{
glfwSetWindowShouldClose(window, true); // Señal para cerrar ventana
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Editar"))
{
if (ImGui::MenuItem("Copiar", "Ctrl + c"))
{
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}Lenguaje del código: C++ (cpp)
Solo tienes que escribir varios bloques if (ImGui::BeginMenu(«Nombre»)) uno detrás de otro para mostrar menús en fila.

Nota importante
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration
| ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoSavedSettings
| ImGuiWindowFlags_MenuBar; // Bandera imprescindible, sin ella no se verá el menúLenguaje del código: JavaScript (javascript)
Debes incluir ImGuiWindowFlags_MenuBar en las banderas de ventana, si lo omites la barra de menú no se renderizará nunca.
Campo de texto multilínea
El editor multilínea es muy sencillo de implementar:
ImVec2 multiline_size = ImGui::GetContentRegionAvail();
// Solución a signos ?: pasar tamaño máximo fijo de búfer, memoria vaciada de datos residuales
ImGui::InputTextMultiline("##text_editor", &editor_text[0], TEXT_BUFFER_MAX, multiline_size);Lenguaje del código: C++ (cpp)
Llama a InputTextMultiline para crear el editor. multiline_size se ajusta automáticamente al espacio disponible.
Ventanas emergentes de aviso
Los popups funcionan marcando primero una bandera interna en ImGui, luego renderizar la ventana según esa marca. Ejemplo:
if (show_save_success)
{
ImGui::OpenPopup("Guardado Exitoso");Lenguaje del código: CSS (css)
Cuando show_save_success sea true, usamos ImGui::OpenPopup(«Guardado Exitoso») para registrar el identificador del popup.
Abajo dibujamos el modal correspondiente al ID registrado:
if (ImGui::BeginPopupModal("Guardado Exitoso", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("El texto se guardó correctamente!");
if (ImGui::Button("Aceptar", ImVec2(120, 0)))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}Lenguaje del código: PHP (php)

Esto es todo para esta lección.