• ImGui界面优化:使用图标字体、隐藏主窗口标题栏



    本文主要介绍ImGui应用中的一些界面优化方法,如果是第一次使用ImGui推荐从上一篇文章开始:使用C++界面框架ImGUI开发一个简单程序,最终的界面效果如下:
    image

    使用图标字体#

    下载IconFontCppHeaders里的IconsFontAwesome6.h文件引入到项目,然后下载fa-solid-900.ttf放到项目根目录,把系统字体目录下的msyh.ttc也移到根目录。项目文件如下:
    image

    参考IconFontCppHeaders的示例,字体加载的代码如下:

    float baseFontSize = 30.0f;
    ImFont* font = io.Fonts->AddFontFromFileTTF
    (
        "msyh.ttc",
        baseFontSize,
        nullptr,
        io.Fonts->GetGlyphRangesChineseFull()
    );
    IM_ASSERT(font != nullptr);
    
    // FontAwesome字体需要缩小2.0f/3.0f才能正确对齐
    float iconFontSize = baseFontSize * 2.0f / 3.0f;
    static const ImWchar icons_ranges[] = { ICON_MIN_FA, ICON_MAX_16_FA,0 };
    ImFontConfig icons_config;
    icons_config.MergeMode = true;
    icons_config.PixelSnapH = true;
    icons_config.GlyphMinAdvanceX = iconFontSize;
    io.Fonts->AddFontFromFileTTF(FONT_ICON_FILE_NAME_FAS, iconFontSize, icons_config, icons_ranges);
    

    使用方法:

    ImGui::Text("%s ", ICON_FA_ALIGN_CENTER);
    ImGui::Button(ICON_FA_ALIGN_CENTER " Search");
    

    示例中所有图标都已经列出来了方便使用时查看(示例源码见文章末尾的项目链接),如下图所示:
    image

    扩展:内存加载字体#

    先新建一个控制台项目,把ImGui项目misc\fonts路径下的binary_to_compressed_c.cpp文件添加到控制台项目,编译得到binary_to_compressed_c.exe

    把需要加载的字体文件(如msyh.ttc)和上面的exe放到同一目录:
    image

    在命令行窗口通过cd 命令导航到上面exe的目录,然后输入以下命令(任选一个):

    //不压缩,使用C组,源代码大概50M
    binary_to_compressed_c.exe -nocompress msyh.ttc msyh >font_msyh.c
    //压缩,使用base85,源代码大概25M
    binary_to_compressed_c.exe -base85 msyh.ttc msyh >font_msyh.c
    

    image

    因为压缩的源代码编译时报堆栈空间不足,并且实际的数组会大一些,我选择的是不压缩的源代码。

    把生成的font_msyh.c添加到自己的项目,在main文件引入字体的命名空间,然后加载字体:

    #include "font_msyh.c"
    
    ImFont* font = io.Fonts->AddFontFromMemoryTTF
    (
        (void*)msyh_data,
        msyh_size,
        30,
        nullptr,
        io.Fonts->GetGlyphRangesChineseFull()
    );
    IM_ASSERT(font != nullptr);
    

    注意msyh_data里面的msyh来自命令行的窗口,注意根据字体自己取一个名字。

    结论:使用内存加载字体软件退出时会报错,暂时还解决不了,继续使用本地加载字体的方式 。

    隐藏主窗口标题栏#

    主窗口的标题栏是后端自带的样式且不支持修改,不能自定义样式那就只能隐藏掉,眼不见为净。
    本文使用的后端是 glfw+opengl3,ImGui 是 docking 分支,其它项目的实现方法可能会有所不同。

    使用 glfw 创建一个 offscreen context ,在创建窗口前加入以下代码即可:

    // 设置 offscreen context 的标志位
    glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
    
    // Create window with graphics context
    GLFWwindow* window = glfwCreateWindow(1280, 720, "演示程序", nullptr, nullptr);
    

    还需要 io.ConfigViewportsNoAutoMerge = true; 开启,同时关闭ConfigViewportsNoTaskBarIcon(默认关闭)。代码如下:

    ImGuiIO& io = ImGui::GetIO(); (void)io;
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;     // Enable Keyboard Controls
    io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls
    io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;         // Enable Docking
    io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable;       // Enable Multi-Viewport / Platform Windows
    io.ConfigViewportsNoAutoMerge = true;
    //io.ConfigViewportsNoTaskBarIcon = true;
    

    注:如果不开启io.ConfigViewportsNoAutoMerge可能引起拖动窗口不小心拖动到隐藏的主窗口, 导致子窗口也隐藏的情况。

    做完上面的步骤界面还是隐藏的,那是因为我们的停靠空间开启了填充标志,需要关闭:

    void RenderUI()
    {
       //...
       static bool opt_fullscreen = false;
       //...
    }
    

    参考链接:IMGUI如何去掉外面窗口?

    隐藏窗口后会导致一系列问题,比如关闭窗口、最大化等,下面会逐一说明。

    增加程序退出#

    主窗口隐藏后会遇到一个问题,程序该怎么退出?解决思路是显示停靠空间的关闭按钮,检测到关闭按钮点击后关闭主窗口。
    Application.h增加GetMainShouldClose函数:

    #pragma once
    #include "imgui.h"
    namespace App
    {
        static bool MainOpen = true;
        //程序是否退出
        bool GetMainShouldClose();
        //主UI函数,放停靠空间的代码
        void RenderUI();
    
        //...其它代码
    }
    

    Application.cpp还需要改动RenderUI函数:

    bool GetMainShouldClose()
    {
        return !MainOpen;
    }
    void RenderUI()
    {
        bool* p_open = &MainOpen;
        static bool opt_fullscreen = false;
        //...其它代码
        ImGui::Begin("演示程序", p_open, window_flags);
        //...其它代码
    }
    

    然后再main函数的 App::RenderUI() 后面判断是否退出程序:

    //...
    App::RenderUI();
    if (App::GetMainShouldClose())
    {
        //关闭窗口,也可以做一些退出确认操作
        glfwSetWindowShouldClose(window, GLFW_TRUE);
    }
    //...
    

    改进HideTabBar#

    原本的HideTabBar有问题,如果有新窗口停靠到中心位置会导致新窗口被覆盖隐藏,只能通过重置布局恢复。所以标题栏最好不隐藏,只隐藏前面的三角符号,这样也能允许用户自己拖动窗口。
    优化代码如下:

    void HideTabBar()
    {
        ImGuiWindowClass window_class;
        window_class.DockNodeFlagsOverrideSet = ImGuiDockNodeFlags_NoWindowMenuButton;
        //window_class.DockNodeFlagsOverrideSet = ImGuiDockNodeFlags_NoTabBar;
        ImGui::SetNextWindowClass(&window_class);
    }
    

    窗口最大化#

    窗口最大化需要知道屏幕的尺寸,这一步涉及到具体的后端代码,需要先在main文件中获取屏幕尺寸。

    在main函数前实现GetScreenSize函数,代码如下:

    static void GetScreenSize(int &width, int &height)
    {
        //屏幕数量
        int monitorCount;
        GLFWmonitor** pMonitor = glfwGetMonitors(&monitorCount);
        int screen_x, screen_y;
        for (int i = 0; i < monitorCount; i++)
        {
            const GLFWvidmode* mode = glfwGetVideoMode(pMonitor[i]);
            //屏幕大小
            screen_x = mode->width;
            screen_y = mode->height;
        }
        width = screen_x;
        height = screen_y;
    }
    

    然后在main函数中将主窗口的大小设置为屏幕大小一致,代码如下:

     //获取屏幕尺寸
     int width, height;
     GetScreenSize(width, height);
     glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
     // Create window with graphics context
     GLFWwindow* window = glfwCreateWindow(width, height, "演示程序", nullptr, nullptr);
    

    在App::RenderUI函数中的停靠空间渲染前设置空间大小,并在主题切换代码后加上大小切换的代码,代码如下:

    //...其它代码
    
    //设置最大化、最小化
    static bool IsMaximized = true;
    static bool LastMaximized = true;
    ImVec2 screenSize = ImGui::GetIO().DisplaySize;
    if (IsMaximized)
    {
        if (LastMaximized != IsMaximized)
        {
            LastMaximized = IsMaximized;
        }
        ImGui::SetNextWindowPos(ImVec2(0, 0));
        ImGui::SetNextWindowSize(screenSize);
        //隐藏标题栏
        //window_flags |= ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize;
    }
    else
    {
        if (LastMaximized != IsMaximized)
        {
            LastMaximized = IsMaximized;
            ImGui::SetNextWindowPos(ImVec2(screenSize[0]*0.1, screenSize[1] * 0.1));
            ImGui::SetNextWindowSize(ImVec2(screenSize[0] * 0.8, screenSize[1] * 0.8));
        }
    }
    
    ImGui::Begin(ICON_FA_ANCHOR "演示程序", p_open, window_flags);
    
    //...其它代码
    
    //增加主题切换
    if (ImGui::BeginMenu(ICON_FA_THERMOMETER "主题(Other)"))
    {
        if (ImGui::MenuItem("暗黑(Dark)")) { ImGui::StyleColorsDark(); }
        if (ImGui::MenuItem("明亮(Light)")) { ImGui::StyleColorsLight(); }
        if (ImGui::MenuItem("经典(Classic)")) { ImGui::StyleColorsClassic(); }
    
        ImGui::EndMenu();
    }
    //增加窗口切换
    if (ImGui::BeginMenu(ICON_FA_TV "窗口(Windows)"))
    {
        if (ImGui::MenuItem(ICON_FA_WINDOW_MAXIMIZE "最大(Max)")) { IsMaximized = true; }
        if (ImGui::MenuItem(ICON_FA_WINDOW_RESTORE "默认(Default)")) { IsMaximized = false; }
    
        ImGui::EndMenu();
    }
    

    总结#

    文中的主要的优化措施就是引入字体图标隐藏主窗口,结合前一篇的文章,ImGui基本的功能都已经涉及到了。

    文中还有一些遗留问题,比如:

    • 全屏无法覆盖系统的任务栏
    • 标题栏没有自定义
    • 内存加载字体报错
    • 任务栏窗口图标自定义

    目前的内容对于简单的项目来说基本够用,后面应该不会再继续更新ImGui相关的内容了,等项目中遇到问题再说。

    项目源码链接:提取码: tv7n

  • 相关阅读:
    微信小程序WeUI项目weui-miniprogram如何运行起来?
    js关键字
    R语言使用forestploter包绘制单组及双组森林图(2)(附有超详细备注)
    【web前端】浮动
    Last Week in Milvus
    1440_TC275 DataSheet阅读笔记1
    Linux进程管理2
    内核调用用户态代码
    音频采集的相关基础知识
    Windows安装Elasticsearch8.x保姆级教程
  • 原文地址:https://www.cnblogs.com/timefiles/p/17657802.html