Nesta aula, vamos aprender a renderizar menus. Vamos desenvolver um programa similar ao bloco de notas TXT, com uma barra de menu no topo contendo o subitem Salvar. Ao clicar em Salvar, todo o texto digitado na caixa abaixo será gravado em um arquivo. Vamos também resolver o problema de caracteres ilegíveis e configurar a fonte de texto; sem essa configuração, caracteres não ingleses aparecerão como pontos de interrogação.
Ao salvar, uma caixa de diálogo nativa de salvar arquivo irá abrir. O usuário pode escolher a pasta de destino, digitar um nome de arquivo e concluir o salvamento.
Abaixo está a captura de tela do programa em execução:

É semelhante ao bloco de notas do Windows, conta com funções básicas de edição e salvamento, e o executável compilado é muito leve.
Quando compilado no modo Release, o arquivo tem apenas 645kb.

Vamos analisar o código completo do 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> // Cabeçalho obrigatório para caixas de diálogo de arquivo
// Tamanho máximo do buffer de texto
const int TEXT_BUFFER_MAX = 4096;
std::string editor_text;
// Salvar texto em arquivo com marca UTF8 BOM
// Grava o conteúdo textual no arquivo especificado
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(); // Apenas escreve texto válido até o caractere nulo
out_file.close();
return true;
}
// Abrir caixa de diálogo de salvar e retornar o caminho escolhido pelo usuário
bool ShowSaveFileDialog(char* out_path, DWORD out_size)
{
OPENFILENAMEA ofn; // Utilizar versão A (ANSI)
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = nullptr;
ofn.lpstrFilter = "Arquivos de Texto\0*.txt\0Todos os Arquivos\0*.*\0"; // Filtro para txt ou todos os formatos
ofn.lpstrFile = out_path;
ofn.nMaxFile = out_size;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_OVERWRITEPROMPT;
ofn.lpstrDefExt = "txt";
return GetSaveFileNameA(&ofn); // Chamar função de diálogo ANSI
}
// Configurar fonte para corrigir problema de caracteres ??? ilegíveis
// Insira o caminho da fonte compatível com seu ambiente ou use a fonte padrão do sistema
void SetupFont(ImGuiIO& io)
{
// Caminho de fonte chinês comum no Windows
const char* font_path = "C:/Windows/Fonts/msyh.ttc"; // Microsoft YaHei
float font_size = 18.0f;
// Carregar conjunto de caracteres chineses simplificados comuns
ImVector<ImWchar> ranges;
ImFontGlyphRangesBuilder builder;
builder.AddRanges(io.Fonts->GetGlyphRangesChineseSimplifiedCommon());
builder.BuildRanges(&ranges);
// Carregar fonte personalizada
io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, ranges.Data);
// Adicione blocos extras para suporte multilíngue se necessário:
// 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);
// Ponto essencial: redimensionar buffer e preencher com zeros para remover lixo de memória, corrige ????
editor_text.resize(TEXT_BUFFER_MAX, '\0');
if (!glfwInit())
{
printf("Falha na inicialização do 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); // Carregar fonte para resolver caracteres quebrados
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
bool show_save_success = false;
bool show_save_fail = false;
char path[MAX_PATH] = "";// Buffer para armazenar caminho do arquivo salvo
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
// Atalho Ctrl+S para salvar
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; // Flag obrigatória para renderizar barra de menu
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::Begin("Editor de Notas de Texto", nullptr, window_flags);
// Renderizar barra de menu superior
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("Arquivo"))
{
if (ImGui::MenuItem("Salvar", "Ctrl+S")) // Item Salvar: rótulo + dica de atalho
{
// Mesma lógica dos botões que estudamos antes, código executa ao clicar no menu
if (ShowSaveFileDialog(path, MAX_PATH)) // Abrir caixa de diálogo de salvar
{
if (SaveTextToFile(path, editor_text)) // Executar gravação do arquivo
show_save_success = true; // Marcar sucesso para exibir popup depois
else
show_save_fail = true;
}
}
ImGui::Separator();
if (ImGui::MenuItem("Sair", "Esc")) // Item para fechar o programa
{
glfwSetWindowShouldClose(window, true); // Definir flag de fechamento para encerrar app
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
ImGui::Spacing();
ImVec2 multiline_size = ImGui::GetContentRegionAvail();
// Solução principal para ?: passar tamanho máximo fixo do buffer, memória zerada sem dados residuais
ImGui::InputTextMultiline("##text_editor", &editor_text[0], TEXT_BUFFER_MAX, multiline_size);
// Acionar popups com base no resultado da gravação
if (show_save_success)
{
// Essa linha não desenha janelas nem executa BeginPopupModal
ImGui::OpenPopup("Salvo com Sucesso");// Marca interna no ImGui para abrir modal, string é ID único do popup
show_save_success = false;
}
if (show_save_fail)
{
ImGui::OpenPopup("Falha ao Salvar");// Marca para popup de erro
show_save_fail = false;
}
// Desenhar modal de sucesso
if (ImGui::BeginPopupModal("Salvo com Sucesso", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("Texto salvo com sucesso!");
if (ImGui::Button("OK", ImVec2(120, 0)))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
// Desenhar modal de falha
if (ImGui::BeginPopupModal("Falha ao Salvar", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("Caminho inválido ou sem permissão de escrita!");
if (ImGui::Button("OK", 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;
}Code language: C++ (cpp)
Encapsulei toda a lógica principal em funções separadas: gravação de arquivo, abertura de diálogo de arquivo e configuração de fonte para idiomas.
Comparado com exemplos anteriores, a interface ganhou dois novos componentes: barra de menu e caixa de texto multilinha.
Barra de Menu
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("Arquivo"))
{Code language: C++ (cpp)
A renderização do menu começa aqui. O if externo cria um menu principal com ImGui::BeginMenu(“Arquivo”).
Dentro ficam os itens clicáveis do submenu. Abaixo o exemplo oficial da biblioteca ImGui:
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("Arquivo"))
{
if (ImGui::MenuItem("Abrir..", "Ctrl+O")) { /* Código funcional */ }
if (ImGui::MenuItem("Salvar", "Ctrl+S")) { /* Código funcional */ }
if (ImGui::MenuItem("Fechar", "Ctrl+W")) { my_tool_active = false; }
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
Para adicionar vários menus lado a lado:
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("Arquivo"))
{
if (ImGui::MenuItem("Salvar", "Ctrl+S")) // Item Salvar: texto + dica de atalho
{
// Funciona igual botões normais, código roda ao clicar o menu
if (ShowSaveFileDialog(path, MAX_PATH)) // Abrir caixa de diálogo de salvar
{
if (SaveTextToFile(path, editor_text)) // Executar gravação
show_save_success = true; // Ativar flag para popup de aviso
else
show_save_fail = true;
}
}
ImGui::Separator();
if (ImGui::MenuItem("Sair", "Esc")) // Item Sair
{
glfwSetWindowShouldClose(window, true); // Sinalizar fechamento da janela
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Editar"))
{
if (ImGui::MenuItem("Copiar", "Ctrl + c"))
{
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}Code language: C++ (cpp)
Basta escrever vários blocos if (ImGui::BeginMenu(“Nome”)) sequencialmente para exibir menus paralelos.

Aviso Importante
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration
| ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoSavedSettings
| ImGuiWindowFlags_MenuBar; // Flag obrigatória, sem ela a barra de menu não apareceCode language: JavaScript (javascript)
Você precisa adicionar ImGuiWindowFlags_MenuBar nas flags da janela, caso contrário a barra de menu nunca será renderizada.
Caixa de Texto Multilinha
A implementação de texto multilinha é muito simples:
ImVec2 multiline_size = ImGui::GetContentRegionAvail();
// Solução para caracteres ?: passar tamanho máximo fixo do buffer, memória limpa de lixo
ImGui::InputTextMultiline("##text_editor", &editor_text[0], TEXT_BUFFER_MAX, multiline_size);Code language: C++ (cpp)
Chame InputTextMultiline para criar o editor. multiline_size captura automaticamente o tamanho da área disponível.
Caixas de Aviso Popup
Os popups funcionam assim: primeiro definimos uma flag interna no ImGui, depois renderizamos a janela correspondente baseada nela. Exemplo:
if (show_save_success)
{
ImGui::OpenPopup("Salvo com Sucesso");Code language: CSS (css)
Quando show_save_success for true, usamos ImGui::OpenPopup(“Salvo com Sucesso”) para registrar o ID do popup.
Abaixo desenhamos a janela modal correspondente ao ID registrado:
if (ImGui::BeginPopupModal("Salvo com Sucesso", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("Texto salvo com sucesso!");
if (ImGui::Button("OK", ImVec2(120, 0)))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}Code language: PHP (php)

Isso é tudo para esta aula.