Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Icon Fonts in current Font #232

Closed
Pagghiu opened this issue May 22, 2015 · 22 comments
Closed

Using Icon Fonts in current Font #232

Pagghiu opened this issue May 22, 2015 · 22 comments

Comments

@Pagghiu
Copy link
Contributor

Pagghiu commented May 22, 2015

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
Copy link
Contributor Author

Pagghiu commented May 22, 2015

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
Copy link
Owner

ocornut commented May 24, 2015

Hi,

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

  • A possibility is to allow loading ranges from different TTF font sources into a single ImFont. Is this icon actually coming from a TTF file? That would be the easiest. Would also full-fill Add font fallback support, just like what windows does. #182 "font fallback support".
  • Another is to allow the user to submit bitmaps and map them to unicode points (probably in the U+E000 range).
  • Another is to have sort of callback or a way that will lead to submitting shapes directly. However currently fonts don't really scale in the first place, so it would be inconsistent to have a mechanism that scales icon-in-glyphs. In order to make font scale properly we'd need to dynamically add to the texture atlas and notify the user to upload dirty texture ranges (like FontStash).

Everything is possible really..

  • Your approach is ok and I'd like to encourage users to create their own function, but at the moment copying that code is ugly (mainly because the code itself is ugly in the first place) and it is probably going to break every few months. Now - the bigger problem here is that you are adopting a custom layout. If the camera icon was part of the same font it would still need to be drawn bigger than the text. Or if it was already baked that big and the label is a single block of text, what makes the second line of text "Button Line 2" appears under "Button Line 1" and not under the icon itself? We would probably require some mini markup language to allow that sort of expressiveness without code, or a way for the user to lay out the content themselve (imagnie a BeginButton/EndButton API).
    I would say that the solution of creating your own function is best and then perhaps we should focus on making this code from copy-and-paste friendly?

@Pagghiu
Copy link
Contributor Author

Pagghiu commented May 25, 2015

  1. Yes, the icons are coming from a TTF file (just download http:https://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
Copy link
Owner

ocornut commented May 27, 2015

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
Copy link
Contributor Author

Pagghiu commented May 27, 2015

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
Copy link
Owner

ocornut commented Jul 15, 2015

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
Copy link
Owner

ocornut commented Jul 15, 2015

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
Copy link

will demo be showcasing this anytime soon?

@ghost
Copy link

ghost commented Nov 29, 2015

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
Copy link
Owner

ocornut commented Nov 29, 2015

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
Copy link

ghost commented Nov 29, 2015

No it is one code block !

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

@ghost
Copy link

ghost commented Nov 29, 2015

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

and how to use it ?

Thx

@ocornut
Copy link
Owner

ocornut commented Nov 29, 2015

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
Copy link

ghost commented Nov 30, 2015

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
Copy link
Owner

ocornut commented Nov 30, 2015

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

@ghost
Copy link

ghost commented Nov 30, 2015

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
Copy link
Owner

ocornut commented Nov 30, 2015

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
Copy link
Owner

ocornut commented Nov 30, 2015

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

@r-lyeh-archived
Copy link

#include <IconsFontAwesome_c.h> instead

@zhouxs1023
Copy link

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
Copy link

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

@zhouxs1023
Copy link

zhouxs1023 commented Jul 8, 2016

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants