ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
60.94k stars 10.29k forks source link

Using Icon Fonts in current Font #232

Closed Pagghiu closed 9 years ago

Pagghiu commented 9 years ago

I've tried loading some icon fonts and it works great for button icons etc. Anyway if you want to make a button (or other control) with an Icon followed by some text, you cannot do it easily. The current ImGui::Button() call that assumes a single font for the label, unless you bake your ttf characters into the icon font (or the opposite). My opinion is that for practical use it should be possible to map those icon fonts into some glyph range of current font (not a new one). I've not gone in the details of the source code generating the glyphs so I'm not sure of the implications and/or difficulty. Just as an idea, icon fonts could be used to create the vector graphics for new ui/graphics theme like described in #184 without the need of bitmaps (that will not scale easily on higher DPI displays) or fancy SVG drawings (that requires an SVG parser/renderer etc).

Comments?

fontawesome

Pagghiu commented 9 years ago

Hacking a copy&paste of ImGui::Button to create ImGui::ButtonIcon yields pleasing results Anyway I can confirm that being able to interleave font icons in "default" font would make things a lot easier in some situations.

imguibuttonicon

#pragma once

enum ImGuiIconEnum
{
    ImGui_fa_camera_retro = 0xF083
};

namespace ImGui
{   
    extern ImFont* GIconFont;
    bool ButtonIcon(const char* label, ImGuiIconEnum iconChar, const ImVec2& size = ImVec2(0, 0), bool repeat_when_held = false);
}
#pragma once
#include "imgui.h"
// This is just to help Intellisense for private imgui.cpp symbols on visual studio...
#ifndef STB_INCLUDE_STB_RECT_PACK_H
#include "imgui.cpp"
#endif

ImFont* ImGui::GIconFont = nullptr;
bool ImGui::ButtonIcon(const char* label, ImGuiIconEnum iconChar, const ImVec2& size_arg, bool repeat_when_held)
{
    IM_ASSERT(GIconFont);
    ImGuiState& g = *GImGui;
    ImGuiWindow* window = GetCurrentWindow();
    if (window->SkipItems)
        return false;
    char labelIcon[8] = { 0 };
    ImTextCharToUtf8(labelIcon, 8, (unsigned int)iconChar);
    const ImGuiStyle& style = g.Style;
    const ImGuiID id = window->GetID(label);
    const ImVec2 label_size = CalcTextSize(label, NULL, true);
    ImGui::PushFont(GIconFont);
    const ImVec2 label_icon_size = CalcTextSize(labelIcon, NULL, true);
    ImGui::PopFont();
    ImVec2 size(size_arg.x != 0.0f ? size_arg.x : (label_size.x + label_icon_size.x + style.ItemInnerSpacing.x + style.FramePadding.x * 2),
        size_arg.y != 0.0f ? size_arg.y : ImMax(label_size.y + style.FramePadding.y * 2, label_icon_size.y + style.FramePadding.y * 2));
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
    ItemSize(bb, style.FramePadding.y);
    if (!ItemAdd(bb, &id))
        return false;

    bool hovered, held;
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, true, repeat_when_held ? ImGuiButtonFlags_Repeat : 0);

    // Render
    const ImU32 col = window->Color((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
    RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
    ImGui::PushFont(GIconFont);
    const ImVec2 offIcon = ImVec2(ImMax(0.0f, size.x - label_size.x - label_icon_size.x - style.ItemInnerSpacing.x) * 0.5f, ImMax(0.0f, size.y - label_icon_size.y) * 0.5f); // Center (only applies if we explicitly gave a size bigger than the text size, which isn't the common path)
    RenderTextClipped(bb.Min + offIcon, labelIcon, NULL, NULL, bb.Max, NULL);
    ImGui::PopFont();
    const ImVec2 off = ImVec2(ImMax(0.0f, size.x - label_size.x + label_icon_size.x + style.ItemInnerSpacing.x) * 0.5f, ImMax(0.0f, size.y - label_size.y) * 0.5f); // Center (only applies if we explicitly gave a size bigger than the text size, which isn't the common path)
    RenderTextClipped(bb.Min + off, label, NULL, &label_size, bb.Max, NULL);                          // Render clip (only applies if we explicitly gave a size smaller than the text size, which isn't the commmon path)
    return pressed;
}

You use it like this

    ImGui::ButtonIcon("Button Line 1\nButton Line 2", ImGui_fa_camera_retro);
ocornut commented 9 years ago

Hi,

There's several things here. Yes to the need of making it possible / easy to integrate symbols into fonts.

Everything is possible really..

Pagghiu commented 9 years ago

1) Yes, the icons are coming from a TTF file (just download http://fortawesome.github.io/Font-Awesome/). 2+3) I would invest more time in proper Font scaling first rather then ability to have arbitrary bitmaps. There are already many ready to use ttf icon fonts and tools to build them from vector shapes, svg or similar. User Bitmaps would allow to bake colored icons but in this case I would pre-pack them in a texture using bitmaps or vector graphics (maybe with nanosvg) and stb_rect_pack. ImGui::Image is the way to go then, with a custom layout similar to my example.

My code is just a simple test with a fixed layout that doesn't cover everyone's cases of course. I was wondering if I could quickly get some button icons using .ttf fonts, and I'm sharing in case someone else wants to play or elaborate some more. I think I may be extending the ButtonIcon function trying to cover a few "common" layout cases (icon left/right/center/top/bottom), to be chosen with some flags, but as always, you cannot cover everyone's needs or taste.

Your considerations on the line layout control are making me think that the real world use case for "inline" icon font mixed with regular character are maybe fewer than I was thinking, unless, like you're suggesting, one thinks of a mini markup language, that doesn't absolutely fit the simplicity and style of the ImGui api. Personally it has been taking me 3 minutes to get the layout I was looking for by copying/pasting/modifying the ImGui::Button code, so I really don't feel the need for a mini markup language. My vote so is for building a simpler copy/paste/modify experience for custom widgets like this one.

ocornut commented 9 years ago

Will probably do a) loading range from multiple fonts into one + b) font scaling.

My vote so is for building a simpler copy/paste/modify experience for custom widgets like this one.

I will keep working on this with the intent of opening more of imgui.cpp down the line (which means supporting its API) but I have no doubt it will take a long time until we get there. There will be some friction and frustration as those copy/pasted code will break several times (I already made some change now). This is the code for Button()

static bool ButtonEx(const char* label, const ImVec2& size_arg, ImGuiButtonFlags flags)
{
    ImGuiWindow* window = GetCurrentWindow();
    if (window->SkipItems)
        return false;

    ImGuiState& g = *GImGui;
    const ImGuiStyle& style = g.Style;
    const ImGuiID id = window->GetID(label);
    const ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true);

    const ImVec2 size(size_arg.x != 0.0f ? size_arg.x : (label_size.x + style.FramePadding.x*2), size_arg.y != 0.0f ? size_arg.y : (label_size.y + style.FramePadding.y*2));
    const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
    ItemSize(bb, style.FramePadding.y);
    if (!ItemAdd(bb, &id))
        return false;

    bool hovered, held;
    bool pressed = ButtonBehavior(bb, id, &hovered, &held, true, flags);

    // Render
    const ImU32 col = window->Color((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
    RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding);
    RenderTextClipped(bb.Min, bb.Max, label, NULL, &label_size, NULL, ImGuiAlign_Center | ImGuiAlign_VCenter);

    // Automatically close popups
    if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup))
        ImGui::CloseCurrentPopup();

    return pressed;
}

bool ImGui::Button(const char* label, const ImVec2& size_arg, bool repeat_when_held)
{
    return ButtonEx(label, size_arg, repeat_when_held ? ImGuiButtonFlags_Repeat : 0);
}

And I intend to remove the "repeat" flag (which is rarely used) and move it to a helper function or stateful thing. Especially before repeat needs timing parameters anyway.

Pagghiu commented 9 years ago

I was expecting to be walking on thin ice when using internal api, so breaking changes are not a big deal ;) I will try to re-align my additions more frequently before they diverge too much. Being able to compose widgets easier is a great mid-long term plan, I will stay tuned!

ocornut commented 9 years ago

The AA branch now support this with the new ImFontConfig* settings. All the AddFontxxx() functions now have a ImFontConfig* pointer. If you pass one you can alter settings.

Merge two fonts:

   io.Fonts->AddFontDefault();

   // Add character ranges and merge into main font
   static ImWchar ranges[] = { 0xf000, 0xf3ff, 0 };
   ImFontConfig config;
   config.MergeMode = true;
   io.Fonts->AddFontFromFileTTF("fontawesome-webfont.ttf", 16.0f, &config, ranges);

There's a few catch here. You can specify a different size to some degree but the font ine height will always be based on the first font of a merged list. So here the default font is 13 and 16 for the icon is alright, but if you use 30 icons it won't fit.

By default characters of different sizes are aligned on their baseline. You can see config.MergeGlyphCenterV = true to center the merged character vertically which may make more sense with icons.

It works but it's probably a little limited in the sense that you'll need your icon to be roughly the same size as your text.

You can adjust the config.OversampleH, config.OversampleV parameters, even without using subpixel positioning this will also give you leeway you scale the font display with less noticeable artefacts.

ocornut commented 9 years ago

This is now merged in master along with the entire new AA-branch. Note that it is a rather big update and you'll need to update your render function to use indexed vertex rendering.

r-lyeh-archived commented 9 years ago

will demo be showcasing this anytime soon?

ghost commented 8 years ago

BTW, I have try the following code, but the "PushFont" crash !

It seems the font is not loaded, why ?

    ImWchar ranges[] = { 0xf000, 0xf3ff, 0 };
    ImFontConfig config;
    config.MergeMode = true;
    ImFont* font = ImGui::GetIO().Fonts->AddFontFromFileTTF("install/fontawesome-webfont.ttf", 16.0f, &config, ranges);

    ImGui::PushFont(font);
ocornut commented 8 years ago

Where does it crash? Does are two separate blocks of code, right? (you need to call io.Fonts.GetTex* to get and upload the texture atlas. The ranges[] needs to be in scope, perhaps add a static in front of ImWchar ranges[].

ghost commented 8 years ago

No it is one code block !

It crash at "SetCurrentFont": IM_ASSERT(font && font->IsLoaded());

ghost commented 8 years ago

I have not called "GetTexDataAsRGBA32", but what it is used for ?

and how to use it ?

Thx

ocornut commented 8 years ago

You cannot use the font before it has been built into a font atlas. Your existing code is probably calling GetTextData* somewhere else nothing would work. This create a bitmap texture out of all the font and you can upload the texture on GPU. Check the examples.

ghost commented 8 years ago

Thanks Omar,

I have just put the following code before the GetTexData* :

// Font awesome
io.Fonts->AddFontDefault();
ImWchar ranges[] = { 0xf000, 0xf3ff, 0 };
ImFontConfig config;
config.MergeMode = true;
VisualDesignerApp::_fontAwesome = io.Fonts->AddFontFromFileTTF("install/fontawesome-webfont.ttf", 16.0f, &config); // , ranges);

But when I try to use the font I got the wrong symbok, I got an ASCII one ?

    ImGui::PushFont(VisualDesignerApp::_fontAwesome);
    ImGui::Button("\uF04B");
    ImGui::PopFont();
ocornut commented 8 years ago

Well you aren't passing the desired ranges to AddFont. And when you do make sure ranges[] is global/static.

ghost commented 8 years ago

Yes, it is what I have do : 1) ranges are static 2) they are passed to AddFont (Not in the code here, but I have try too)

ocornut commented 8 years ago

ImGui::Button("\uF04B"); isn't correct it is 16-bit unicode whereas ImGui takes UTF-8.

According to this it looks like you can utf-8 encode using an u8 (unsigned char) prefix: https://github.com/juliettef/IconFontCppHeaders/blob/master/IconsFontAwesome.h

ocornut commented 8 years ago

Actually I am being confused with those fancy modern literals http://en.cppreference.com/w/cpp/language/string_literal Just in case try the UTF-8 representation "\xEF\x81\x8B"

r-lyeh-archived commented 8 years ago

include <IconsFontAwesome_c.h> instead

zhouxs1023 commented 8 years ago

I use IconsFontAwesome_c.h to build icon font,but crash in imgui_draw.cpp Line 1273 ==>"IM_ASSERT(font_offset >= 0);" qq 20160708122803 Pls help me ,THS!

r-lyeh-archived commented 8 years ago

adjust your path to the .ttf file, or move the executable to a proper folder

zhouxs1023 commented 8 years ago

Redownload.ttf file ,Compile it sucessfully now,Thanks!