In this lesson, we’ll learn how to render menus. We will build a simple TXT notepad program with a top menu bar containing a Save submenu. Clicking Save will export all text entered in the text box to a file. We will also address garbled character issues and learn font encoding configuration, which prevents non-English text from turning into messy symbols.
When saving, a system file save dialog will pop up. Users can select a storage directory, input a custom filename, then complete file saving.
Below is a screenshot of the finished running program:

This program mimics Windows Notepad with basic editing and saving functions. The compiled binary is extremely lightweight.
When compiled in Release mode, the executable is only 645kb.

Let’s go through the full source code:
#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> // Required header for file dialogs
// Text buffer limit
const int TEXT_BUFFER_MAX = 4096;
std::string editor_text;
// Save text to file with UTF-8 BOM
// Writes text content into target file
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(); // Output valid string until null terminator
out_file.close();
return true;
}
// Pop up system save file dialog, return selected file path
bool ShowSaveFileDialog(char* out_path, DWORD out_size)
{
OPENFILENAMEA ofn; // Use ANSI version A
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = nullptr;
ofn.lpstrFilter = "Text Files\0*.txt\0All Files\0*.*\0"; // Filter for txt or all formats
ofn.lpstrFile = out_path;
ofn.nMaxFile = out_size;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_OVERWRITEPROMPT;
ofn.lpstrDefExt = "txt";
return GetSaveFileNameA(&ofn); // Call ANSI dialog function
}
// Configure font to fix garbled question mark characters
// Add font file path matching your system environment, or use system default font path
void SetupFont(ImGuiIO& io)
{
// Common Chinese font path for Windows
const char* font_path = "C:/Windows/Fonts/msyh.ttc"; // Microsoft YaHei
float font_size = 18.0f;
// Load common Simplified Chinese character range
ImVector<ImWchar> ranges;
ImFontGlyphRangesBuilder builder;
builder.AddRanges(io.Fonts->GetGlyphRangesChineseSimplifiedCommon());
builder.BuildRanges(&ranges);
// Load custom font
io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, ranges.Data);
// Add extra font ranges for multi-language support if needed:
// 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);
// Critical: Expand buffer and fill with zero to clear garbage memory data, fix ??? garble
editor_text.resize(TEXT_BUFFER_MAX, '\0');
if (!glfwInit())
{
printf("GLFW init failed\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, "Text Note Editor", nullptr, nullptr);
if (!window)
{
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
ImGui::StyleColorsLight();
SetupFont(io); // Load font to resolve garbled characters
ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init(glsl_version);
bool show_save_success = false;
bool show_save_fail = false;
char path[MAX_PATH] = "";// File path storage buffer
while (!glfwWindowShouldClose(window))
{
glfwPollEvents();
// Ctrl+S save shortcut
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; // Mandatory flag to render menu bar
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::Begin("Text Note Editor", nullptr, window_flags);
// Top menu bar rendering
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("File"))
{
if (ImGui::MenuItem("Save", "Ctrl+S")) // Save menu item: label + shortcut hint
{
// Identical logic to buttons we covered earlier; code runs when menu clicked
if (ShowSaveFileDialog(path, MAX_PATH)) // Pop up save file dialog
{
if (SaveTextToFile(path, editor_text)) // Execute file save
show_save_success = true; // Mark save success flag for later popup
else
show_save_fail = true;
}
}
ImGui::Separator();
if (ImGui::MenuItem("Exit", "Esc")) // Exit menu item
{
glfwSetWindowShouldClose(window, true); // Set close flag to terminate program
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
ImGui::Spacing();
ImVec2 multiline_size = ImGui::GetContentRegionAvail();
// Fix garbled question marks: pass fixed max buffer length, buffer zero-initialized with no garbage data
ImGui::InputTextMultiline("##text_editor", &editor_text[0], TEXT_BUFFER_MAX, multiline_size);
// Popup trigger logic based on save result flags
if (show_save_success)
{
// Does not render any window or BeginPopupModal
ImGui::OpenPopup("Save Success");// Set internal ImGui flag to spawn modal popup, unique ID string
show_save_success = false;
}
if (show_save_fail)
{
ImGui::OpenPopup("Save Failed");// Set popup flag for failure alert
show_save_fail = false;
}
// Render success modal popup
if (ImGui::BeginPopupModal("Save Success", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("Text saved successfully!");
if (ImGui::Button("OK", ImVec2(120, 0)))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}
// Render failure modal popup
if (ImGui::BeginPopupModal("Save Failed", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("Path invalid or no write permission!");
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)
I’ve encapsulated core logic into independent functions above: file writing, system file dialog launcher, and font encoding setup.
Compared to previous demos, this UI adds two new core components: menu bar and multi-line text editor.
Menu Bar Implementation
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("File"))
{Code language: C++ (cpp)
Menu rendering starts here. The outer if statement creates a top-level menu via ImGui::BeginMenu(“File”).
Nested inside are clickable submenu items. Below is official ImGui reference example:
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("File"))
{
if (ImGui::MenuItem("Open..", "Ctrl+O")) { /* Do stuff */ }
if (ImGui::MenuItem("Save", "Ctrl+S")) { /* Do stuff */ }
if (ImGui::MenuItem("Close", "Ctrl+W")) { my_tool_active = false; }
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}
To add multiple parallel top-level menus:
if (ImGui::BeginMenuBar())
{
if (ImGui::BeginMenu("File"))
{
if (ImGui::MenuItem("Save", "Ctrl+S")) // Save menu item: text label + shortcut hint
{
// Works identical to regular buttons; logic runs on menu click
if (ShowSaveFileDialog(path, MAX_PATH)) // Launch system save dialog
{
if (SaveTextToFile(path, editor_text)) // Execute save logic
show_save_success = true; // Set success flag for popup notification
else
show_save_fail = true;
}
}
ImGui::Separator();
if (ImGui::MenuItem("Exit", "Esc")) // Exit option
{
glfwSetWindowShouldClose(window, true); // Trigger window close signal
}
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Editor"))
{
if (ImGui::MenuItem("Copy", "Ctrl + c"))
{
}
ImGui::EndMenu();
}
ImGui::EndMenuBar();
}Code language: C++ (cpp)
Simply stack multiple ImGui::BeginMenu(“MenuName”) if blocks sequentially to create multiple menus side-by-side.

Important Note
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration
| ImGuiWindowFlags_NoMove
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoSavedSettings
| ImGuiWindowFlags_MenuBar; // Mandatory flag to render menu bar, omit and menus won't displayCode language: JavaScript (javascript)
You must append ImGuiWindowFlags_MenuBar to window flags, otherwise the menu bar will never render.
Multi-Line Text Input
Multi-line text editing is straightforward to implement:
ImVec2 multiline_size = ImGui::GetContentRegionAvail();
// Fix garbled question marks: pass fixed max buffer length, buffer zero-initialized with no garbage data
ImGui::InputTextMultiline("##text_editor", &editor_text[0], TEXT_BUFFER_MAX, multiline_size);Code language: C++ (cpp)
Call InputTextMultiline to create the editor. multiline_size auto-fits the available content region size.
Modal Popup Alerts
Popup modals work by first setting an internal ImGui flag, then rendering the popup later based on that flag. Example logic:
if (show_save_success)
{
ImGui::OpenPopup("Save Success");Code language: CSS (css)
When show_save_success equals true, we trigger ImGui::OpenPopup(“Save Success”) to register a popup ID marker.
Below we render the matching modal window using the registered ID marker:
if (ImGui::BeginPopupModal("Save Success", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::Text("Text saved successfully!");
if (ImGui::Button("OK", ImVec2(120, 0)))
ImGui::CloseCurrentPopup();
ImGui::EndPopup();
}Code language: PHP (php)

That’s all for this tutorial!