From 6f27fac9fb88d07b8cbf4e12e780300f2d30fb7e Mon Sep 17 00:00:00 2001 From: GigabiteStudios Date: Thu, 18 Jun 2026 18:53:49 -0500 Subject: [PATCH] feat(refs): match compact outline reference chips --- src/ui/gitree_ui.cpp | 84 ++++++++++++++++++++------ vendor/fonts/LICENSE-Tabler-Icons.txt | 21 +++++++ vendor/fonts/tabler-icons-outline.ttf | Bin 0 -> 2208 bytes 3 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 vendor/fonts/LICENSE-Tabler-Icons.txt create mode 100644 vendor/fonts/tabler-icons-outline.ttf diff --git a/src/ui/gitree_ui.cpp b/src/ui/gitree_ui.cpp index 618a934..9b206d3 100644 --- a/src/ui/gitree_ui.cpp +++ b/src/ui/gitree_ui.cpp @@ -46,6 +46,13 @@ float g_ui_scale = 1.0f; float g_sidebar_width = 230.0f; WindowManager* g_window_manager = nullptr; GitManager* g_git_manager = nullptr; +ImFont* g_outline_icon_font = nullptr; +float g_outline_icon_size = 15.0f; + +constexpr const char* ICON_TB_CHECK = "\xee\xa9\x9e"; +constexpr const char* ICON_TB_CLOUD = "\xee\xa9\xb6"; +constexpr const char* ICON_TB_DEVICE_LAPTOP = "\xee\xad\xa4"; +constexpr const char* ICON_TB_TAG = "\xee\xa4\x80"; float ui(float value) { return value * g_ui_scale; } RepositoryView& repo() { return *g_tabs.at(g_active_tab); } @@ -153,6 +160,19 @@ void load_fonts(float scale) { icon_config.GlyphMinAdvanceX = size; io.Fonts->AddFontFromFileTTF( GITREE_ASSET_DIR "/fa-solid-900.ttf", size, &icon_config, icon_ranges); + + static constexpr ImWchar outline_ranges[] = { + 0xE900, 0xE900, + 0xEA5E, 0xEA5E, + 0xEA76, 0xEA76, + 0xEB64, 0xEB64, + 0, + }; + ImFontConfig outline_config; + outline_config.PixelSnapH = true; + g_outline_icon_size = size; + g_outline_icon_font = io.Fonts->AddFontFromFileTTF( + GITREE_ASSET_DIR "/tabler-icons-outline.ttf", size, &outline_config, outline_ranges); apply_style(scale); } @@ -350,37 +370,62 @@ void draw_sidebar(float width) { } void draw_ref_badge(const RefBadge& badge, int index, int lane) { - std::string text; - if (badge.current) text += ICON_FA_CHECK " "; + (void)lane; std::string display_name = badge.name; if (badge.kind == RefKind::remote) { const size_t slash = display_name.find('/'); if (slash != std::string::npos) display_name.erase(0, slash + 1); } - text += display_name; - if (badge.worktree) text += std::string(" ") + ICON_FA_COMPUTER; - if (badge.kind == RefKind::remote) text += std::string(" ") + ICON_FA_CLOUD; - if (badge.kind == RefKind::tag) text += std::string(" ") + ICON_FA_TAG; - const ImVec4 lane_color = ImGui::ColorConvertU32ToFloat4(GraphRenderer::laneColor(lane)); - const float brightness = badge.current ? 0.78f : 0.38f; - const ImVec4 color = ImVec4(lane_color.x * brightness, lane_color.y * brightness, - lane_color.z * brightness, 1.0f); - ImGui::PushStyleColor(ImGuiCol_Button, color); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(color.x + 0.06f, color.y + 0.06f, color.z + 0.06f, 1.0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, color); - ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, ui(2.0f)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, {ui(6.0f), ui(1.5f)}); - const std::string id = text + "##ref_badge_" + std::to_string(index); - ImGui::Button(id.c_str()); + const bool show_cloud = badge.kind == RefKind::remote || badge.upstream; + const bool show_tag = badge.kind == RefKind::tag; + const ImVec2 label_size = ImGui::CalcTextSize(display_name.c_str()); + const auto icon_width = [](const char* icon) { + return g_outline_icon_font + ? g_outline_icon_font->CalcTextSizeA(g_outline_icon_size, 1000.0f, 0.0f, icon).x + : 0.0f; + }; + float content_width = label_size.x; + if (badge.current) content_width += icon_width(ICON_TB_CHECK) + ui(5.0f); + if (badge.worktree) content_width += ui(5.0f) + icon_width(ICON_TB_DEVICE_LAPTOP); + if (show_cloud) content_width += ui(5.0f) + icon_width(ICON_TB_CLOUD); + if (show_tag) content_width += ui(5.0f) + icon_width(ICON_TB_TAG); + + const ImVec2 chip_size{content_width + ui(12.0f), ui(23.0f)}; + ImGui::PushID(index); + ImGui::InvisibleButton("##ref_badge", chip_size); + const ImVec2 minimum = ImGui::GetItemRectMin(); + const ImVec2 maximum = ImGui::GetItemRectMax(); + ImDrawList* draw = ImGui::GetWindowDrawList(); + ImU32 background = badge.current ? IM_COL32(17, 105, 123, 255) : IM_COL32(20, 65, 76, 255); + if (ImGui::IsItemHovered()) background = badge.current + ? IM_COL32(22, 123, 142, 255) : IM_COL32(25, 79, 92, 255); + draw->AddRectFilled(minimum, maximum, background, ui(2.0f)); + + float x = minimum.x + ui(6.0f); + const float text_y = minimum.y + (chip_size.y - ImGui::GetFontSize()) * 0.5f; + const float icon_y = minimum.y + (chip_size.y - g_outline_icon_size) * 0.5f; + const ImU32 color = badge.current ? IM_COL32(242, 247, 249, 255) : IM_COL32(201, 214, 218, 255); + const auto draw_icon = [&](const char* icon) { + if (!g_outline_icon_font) return; + draw->AddText(g_outline_icon_font, g_outline_icon_size, {x, icon_y}, color, icon); + x += icon_width(icon) + ui(5.0f); + }; + if (badge.current) draw_icon(ICON_TB_CHECK); + draw->AddText({x, text_y}, color, display_name.c_str()); + x += label_size.x + ui(5.0f); + if (badge.worktree) draw_icon(ICON_TB_DEVICE_LAPTOP); + if (show_cloud) draw_icon(ICON_TB_CLOUD); + if (show_tag) draw_icon(ICON_TB_TAG); + if (ImGui::BeginPopupContextItem()) { if (badge.kind == RefKind::local && ImGui::MenuItem(ICON_FA_CODE_BRANCH " Checkout")) g_git_manager->checkoutBranch(repo(), badge.name, g_notice); if (ImGui::MenuItem(ICON_FA_COPY " Copy ref name")) ImGui::SetClipboardText(badge.name.c_str()); ImGui::EndPopup(); } - ImGui::PopStyleVar(2); - ImGui::PopStyleColor(3); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) ImGui::SetTooltip("%s", badge.name.c_str()); + ImGui::PopID(); } void draw_commit_table() { @@ -769,6 +814,7 @@ void draw_licenses_popup() { {"iZo", "MIT License", "https://dock-it.dev/Idea-Studios/iZo"}, {"Inter", "SIL Open Font License 1.1", "https://github.com/rsms/inter"}, {"Font Awesome Free", "SIL Open Font License 1.1", "https://github.com/FortAwesome/Font-Awesome"}, + {"Tabler Icons", "MIT License", "https://github.com/tabler/tabler-icons"}, }; for (const auto& dependency : dependencies) { ImGui::TableNextRow(); diff --git a/vendor/fonts/LICENSE-Tabler-Icons.txt b/vendor/fonts/LICENSE-Tabler-Icons.txt new file mode 100644 index 0000000..3e82379 --- /dev/null +++ b/vendor/fonts/LICENSE-Tabler-Icons.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2026 Paweł Kuna + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/fonts/tabler-icons-outline.ttf b/vendor/fonts/tabler-icons-outline.ttf new file mode 100644 index 0000000000000000000000000000000000000000..a940beebb018538da17bfac41c4ba3c035208fd9 GIT binary patch literal 2208 zcmZuzU2GIp6h3!mcc*lx+nw2;&e(RKGwm)dvh8m72UBPZv|=MDkwQ#kz=Z-OwosuG zB*th&CB(l(O$hQJkpx5ZMT{ZF1QUHQ@dXkSe3AGfX&1zp;Dac;e&&Z>sxxv@Be?=r#fPZ$!RC&5`y7N2m zpMw8j_vDMaTF$=t1CjC=QSjx7o#pZFqwm*2_YGQc0s?Kn`Wk51r1puanM3L7T(i^9J3tL^p|jJi5j*j%gCj;q(zBVg-DVJRMrQIFJ4qMwESU-i1Q!i zk&>W#gLvi$5Un0BQH@zV0d-cqM?t6#D?Hn`{IB?xvI1y@f~v$l&~4l`JWk(Mw$^Gg zhB5UDO#sAmx%bFO{n^|V20*dRb~L6(!SSeed^xgFNPFIut zx8ol#@T3DxG$?y%a}Fdo##FZYjptrD=r{I%ZQO4%2BN=3T4`1;5Ny>d>IpSVjpWcW ztlKbcp&c|suhKj85q(A!izjo%LO$h&60$4FUJAMEa6_$viB%$>Qsc=ckogp2nnLj= z!CYFTT;Ygf2?|#WxoIflzzql%G>TBXqhF-vrEx6;Yk=}7--5^p4`ebYjpb9z1ap0Y zokv81ofq=OUiSN2b~tOhmhIYS?o!o14I2WxFJ|hV#kvk%7pp<*)eE{O3N0fe&AR33 zM%IAvgzo8c%+Beer#TjDW}soPM3)0BIk@_xnXydE^6>Uq#?SC!TsEz0ZTW`faM9yI zpse?@Sr;I~uq;!!K4U^~Sb{C{vSC-NR>p*NyjC}SBPO^)eAPqud!OS_(EZXpGSb4R zdl3T<9%IE(GY0<%Oq-uJ0^;Dj9*F%tLF#kLG2|X>EEHcXlhlpnj$z?tPBi4?oUUJF zf9SqPSmKm)uuT`A>#lx@nTrk8S*+wKMpd`z9xdV`R4*u3$e<1?(Br72X?lZB&>47g zlu|!x#0zCaI-iP&5}ForM6O>H`jsY5U&)sg0mLsON+B(R$|)5~y*X4CS4l=_!90p3 zB9cuGk{@CdD6LYlp!r4N7f3LlQ9P7K+!4JwM@isY)N=ibU&--^qHz(Sn7uY8dfRfv zE-^ScU|6=%Iov61&-#5}a!`nHtSP1ohz#3$Y3+6+V757d@Y+n&Smfl}-DtGlF|3Ee z-s+YHHyMbep2-R$_i)5b1R||%ilUlsYd{go(=dy~n=m69TJl4wa4d{pBGuZ6%zvF4 z%D3R#*qTb9R{AWn$~9w%KTJz(9N4o;2$Z!*ukAD}VgDpn?itWyI>s*@N(;-iPsYZF z45!YFb`LcKoHnCQsShr0iv~3%nXC^qyJ2UE_-Dnq8xDd_u4%82YEegvMZ@7pV;3Bu z_CDzt&bRVp!%-Q8u+O#@hB{2F2L~yDVI(%ve4am4zLYhTqF$Uv8)%GnpyEVe#(fKLjPTL7-@P9<=`WB{%zB&?Y@;rK`9$$> z*w~P9zRpPl8*l^^?>Df}kpRL+^JXCm(Gz?l1Qy}+=KIs96^elKX?1}-uz(Iyl2Y@S zkd)6*nYPm;?WFy*oc3V%?W4VPfS!V6H@yH&8JaCpe*k*cE}<-Cfv!IDMOF0AH_d`^2}cN