bool ImGuiFreeType::BuildFontAtlas(ImFontAtlas* atlas, unsigned int extra_flags) { IM_ASSERT(atlas->ConfigData.Size > 0); IM_ASSERT(atlas->TexGlyphPadding == 1); // Not supported ImFontAtlasBuildRegisterDefaultCustomRects(atlas); atlas->TexID = NULL; atlas->TexWidth = atlas->TexHeight = 0; atlas->TexUvScale = ImVec2(0.0f, 0.0f); atlas->TexUvWhitePixel = ImVec2(0.0f, 0.0f); atlas->ClearTexData(); ImVector<FreeTypeFont> fonts; fonts.resize(atlas->ConfigData.Size); ImVec2 max_glyph_size(1.0f, 1.0f); // Count glyphs/ranges, initialize font int total_glyphs_count = 0; int total_ranges_count = 0; for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) { ImFontConfig& cfg = atlas->ConfigData[input_i]; FreeTypeFont& font_face = fonts[input_i]; IM_ASSERT(cfg.DstFont && (!cfg.DstFont->IsLoaded() || cfg.DstFont->ContainerAtlas == atlas)); if (!font_face.Init(cfg, extra_flags)) return false; max_glyph_size.x = ImMax(max_glyph_size.x, font_face.Info.MaxAdvanceWidth); max_glyph_size.y = ImMax(max_glyph_size.y, font_face.Info.Ascender - font_face.Info.Descender); if (!cfg.GlyphRanges) cfg.GlyphRanges = atlas->GetGlyphRangesDefault(); for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[ 1 ]; in_range += 2, total_ranges_count++) total_glyphs_count += (in_range[1] - in_range[0]) + 1; } // We need a width for the skyline algorithm. Using a dumb heuristic here to decide of width. User can override TexDesiredWidth and TexGlyphPadding if they wish. // Width doesn't really matter much, but some API/GPU have texture size limitations and increasing width can decrease height. atlas->TexWidth = (atlas->TexDesiredWidth > 0) ? atlas->TexDesiredWidth : (total_glyphs_count > 4000) ? 4096 : (total_glyphs_count > 2000) ? 2048 : (total_glyphs_count > 1000) ? 1024 : 512; // We don't do the original first pass to determine texture height, but just rough estimate. // Looks ugly inaccurate and excessive, but AFAIK with FreeType we actually need to render glyphs to get exact sizes. // Alternatively, we could just render all glyphs into a big shadow buffer, get their sizes, do the rectangle packing and just copy back from the // shadow buffer to the texture buffer. Will give us an accurate texture height, but eat a lot of temp memory. Probably no one will notice.) const int total_rects = total_glyphs_count + atlas->CustomRects.size(); float min_rects_per_row = ceilf((atlas->TexWidth / (max_glyph_size.x + 1.0f))); float min_rects_per_column = ceilf(total_rects / min_rects_per_row); atlas->TexHeight = (int)(min_rects_per_column * (max_glyph_size.y + 1.0f)); // Create texture atlas->TexHeight = (atlas->Flags & ImFontAtlasFlags_NoPowerOfTwoHeight) ? (atlas->TexHeight + 1) : ImUpperPowerOfTwo(atlas->TexHeight); atlas->TexUvScale = ImVec2(1.0f / atlas->TexWidth, 1.0f / atlas->TexHeight); atlas->TexPixelsAlpha8 = (unsigned char*)ImGui::MemAlloc(atlas->TexWidth * atlas->TexHeight); memset(atlas->TexPixelsAlpha8, 0, atlas->TexWidth * atlas->TexHeight); // Start packing ImVector<stbrp_node> pack_nodes; pack_nodes.resize(total_rects); stbrp_context context; stbrp_init_target(&context, atlas->TexWidth, atlas->TexHeight, pack_nodes.Data, total_rects); // Pack our extra data rectangles first, so it will be on the upper-left corner of our texture (UV will have small values). ImFontAtlasBuildPackCustomRects(atlas, &context); // Render characters, setup ImFont and glyphs for runtime for (int input_i = 0; input_i < atlas->ConfigData.Size; input_i++) { ImFontConfig& cfg = atlas->ConfigData[input_i]; FreeTypeFont& font_face = fonts[input_i]; ImFont* dst_font = cfg.DstFont; if (cfg.MergeMode) dst_font->BuildLookupTable(); const float ascent = font_face.Info.Ascender; const float descent = font_face.Info.Descender; ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); const float font_off_x = cfg.GlyphOffset.x; const float font_off_y = cfg.GlyphOffset.y + (float)(int)(dst_font->Ascent + 0.5f); bool multiply_enabled = (cfg.RasterizerMultiply != 1.0f); unsigned char multiply_table[256]; if (multiply_enabled) ImFontAtlasBuildMultiplyCalcLookupTable(multiply_table, cfg.RasterizerMultiply); for (const ImWchar* in_range = cfg.GlyphRanges; in_range[0] && in_range[1]; in_range += 2) { for (uint32_t codepoint = in_range[0]; codepoint <= in_range[1]; ++codepoint) { if (cfg.MergeMode && dst_font->FindGlyphNoFallback((unsigned short)codepoint)) continue; FT_Glyph ft_glyph = NULL; FT_BitmapGlyph ft_glyph_bitmap = NULL; // NB: will point to bitmap within FT_Glyph GlyphInfo glyph_info; if (!font_face.CalcGlyphInfo(codepoint, glyph_info, ft_glyph, ft_glyph_bitmap)) continue; // Pack rectangle stbrp_rect rect; rect.w = (uint16_t)glyph_info.Width + 1; // Account for texture filtering rect.h = (uint16_t)glyph_info.Height + 1; stbrp_pack_rects(&context, &rect, 1); // Copy rasterized pixels to main texture uint8_t* blit_dst = atlas->TexPixelsAlpha8 + rect.y * atlas->TexWidth + rect.x; font_face.BlitGlyph(ft_glyph_bitmap, blit_dst, atlas->TexWidth, multiply_enabled ? multiply_table : NULL); FT_Done_Glyph(ft_glyph); float char_advance_x_org = glyph_info.AdvanceX; float char_advance_x_mod = ImClamp(char_advance_x_org, cfg.GlyphMinAdvanceX, cfg.GlyphMaxAdvanceX); float char_off_x = font_off_x; if (char_advance_x_org != char_advance_x_mod) char_off_x += cfg.PixelSnapH ? (float)(int)((char_advance_x_mod - char_advance_x_org) * 0.5f) : (char_advance_x_mod - char_advance_x_org) * 0.5f; // Register glyph dst_font->AddGlyph((ImWchar)codepoint, glyph_info.OffsetX + char_off_x, glyph_info.OffsetY + font_off_y, glyph_info.OffsetX + char_off_x + glyph_info.Width, glyph_info.OffsetY + font_off_y + glyph_info.Height, rect.x / (float)atlas->TexWidth, rect.y / (float)atlas->TexHeight, (rect.x + glyph_info.Width) / (float)atlas->TexWidth, (rect.y + glyph_info.Height) / (float)atlas->TexHeight, char_advance_x_mod); } } } // Cleanup for (int n = 0; n < fonts.Size; n++) fonts[n].Shutdown(); ImFontAtlasBuildFinish(atlas); return true; }
int PiGui::RadialPopupSelectMenu(const ImVec2& center, std::string popup_id, std::vector<ImTextureID> tex_ids, std::vector<std::pair<ImVec2,ImVec2>> uvs, unsigned int size, std::vector<std::string> tooltips) { int ret = -1; // FIXME: Missing a call to query if Popup is open so we can move the PushStyleColor inside the BeginPopupBlock (e.g. IsPopupOpen() in imgui.cpp) // FIXME: Our PathFill function only handle convex polygons, so we can't have items spanning an arc too large else inner concave edge artifact is too visible, hence the ImMax(7,items_count) ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0,0,0,0)); ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0,0,0,0)); ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0,0,0,0)); if (ImGui::BeginPopup(popup_id.c_str())) { const ImVec2 drag_delta = ImVec2(ImGui::GetIO().MousePos.x - center.x, ImGui::GetIO().MousePos.y - center.y); const float drag_dist2 = drag_delta.x*drag_delta.x + drag_delta.y*drag_delta.y; const ImGuiStyle& style = ImGui::GetStyle(); const float RADIUS_MIN = 20.0f; const float RADIUS_MAX = 90.0f; const float RADIUS_INTERACT_MIN = 20.0f; const int ITEMS_MIN = 5; const float border_inout = 12.0f; const float border_thickness = 4.0f; ImDrawList* draw_list = ImGui::GetWindowDrawList(); draw_list->PushClipRectFullScreen(); draw_list->PathArcTo(center, (RADIUS_MIN + RADIUS_MAX)*0.5f, 0.0f, IM_PI*2.0f*0.99f, 64); // FIXME: 0.99f look like full arc with closed thick stroke has a bug now draw_list->PathStroke(ImColor(18,44,67,210), true, RADIUS_MAX - RADIUS_MIN); const float item_arc_span = 2*IM_PI / ImMax(ITEMS_MIN, tex_ids.size()); float drag_angle = atan2f(drag_delta.y, drag_delta.x); if (drag_angle < -0.5f*item_arc_span) drag_angle += 2.0f*IM_PI; int item_hovered = -1; int item_n = 0; for(ImTextureID tex_id : tex_ids) { const char* tooltip = tooltips.at(item_n).c_str(); const float inner_spacing = style.ItemInnerSpacing.x / RADIUS_MIN / 2; const float item_inner_ang_min = item_arc_span * (item_n - 0.5f + inner_spacing); const float item_inner_ang_max = item_arc_span * (item_n + 0.5f - inner_spacing); const float item_outer_ang_min = item_arc_span * (item_n - 0.5f + inner_spacing * (RADIUS_MIN / RADIUS_MAX)); const float item_outer_ang_max = item_arc_span * (item_n + 0.5f - inner_spacing * (RADIUS_MIN / RADIUS_MAX)); bool hovered = false; if (drag_dist2 >= RADIUS_INTERACT_MIN*RADIUS_INTERACT_MIN) { if (drag_angle >= item_inner_ang_min && drag_angle < item_inner_ang_max) hovered = true; } bool selected = false; int arc_segments = static_cast<int>((64 * item_arc_span / (2*IM_PI))) + 1; draw_list->PathArcTo(center, RADIUS_MAX - border_inout, item_outer_ang_min, item_outer_ang_max, arc_segments); draw_list->PathArcTo(center, RADIUS_MIN + border_inout, item_inner_ang_max, item_inner_ang_min, arc_segments); draw_list->PathFill(hovered ? ImColor(102,147,189) : selected ? ImColor(48,81,111) : ImColor(48,81,111)); if(hovered) { // draw outer / inner extra segments draw_list->PathArcTo(center, RADIUS_MAX - border_thickness, item_outer_ang_min, item_outer_ang_max, arc_segments); draw_list->PathStroke(ImColor(102,147,189), false, border_thickness); draw_list->PathArcTo(center, RADIUS_MIN + border_thickness, item_outer_ang_min, item_outer_ang_max, arc_segments); draw_list->PathStroke(ImColor(102,147,189), false, border_thickness); } ImVec2 text_size = ImVec2(size, size); ImVec2 text_pos = ImVec2( center.x + cosf((item_inner_ang_min + item_inner_ang_max) * 0.5f) * (RADIUS_MIN + RADIUS_MAX) * 0.5f - text_size.x * 0.5f, center.y + sinf((item_inner_ang_min + item_inner_ang_max) * 0.5f) * (RADIUS_MIN + RADIUS_MAX) * 0.5f - text_size.y * 0.5f); draw_list->AddImage(tex_id, text_pos, ImVec2(text_pos.x+size,text_pos.y+size), uvs[item_n].first, uvs[item_n].second); ImGui::SameLine(); if (hovered) { item_hovered = item_n; ImGui::SetTooltip("%s", tooltip); } item_n++; } draw_list->PopClipRect(); if (ImGui::IsMouseReleased(1)) { ImGui::CloseCurrentPopup(); if(item_hovered == -1) ret = -2; else ret = item_hovered; } ImGui::EndPopup(); } ImGui::PopStyleColor(3); return ret; }