CS2-Kit
C++23 library for CS2 Metamod:Source plugin development
Loading...
Searching...
No Matches
MenuRenderer.cpp
Go to the documentation of this file.
2
3#include <CS2Kit/Menu/MenuOption.hpp>
4
5#include <algorithm>
6#include <sstream>
7
8namespace CS2Kit::Menu
9{
10
11namespace Theme
12{
13constexpr const char* Gold = "#FFD700";
14constexpr const char* Amber = "#FF8C00";
15constexpr const char* WarmWhite = "#CCBBAA";
16constexpr const char* WarmGray = "#887755";
17constexpr const char* Disabled = "#665544";
18constexpr const char* NavGold = "#AA8833";
19constexpr const char* NavClose = "#AA4422";
20constexpr const char* NavBack = "#AA8833";
21} // namespace Theme
22
23std::string DefaultHeader(const std::string& title, int currentPage, int totalPages)
24{
25 std::ostringstream html;
26 html << "<font color='" << Theme::Gold << "'><b>" << title << "</b></font>";
27
28 if (totalPages > 1)
29 {
30 html << " <font class='fontSize-s' color='" << Theme::WarmGray << "'>(" << (currentPage + 1) << "/"
31 << totalPages << ")</font>";
32 }
33
34 html << "<br>";
35 return html.str();
36}
37
38std::string DefaultFooter(bool isSubmenu, bool isPaginated)
39{
40 const char* closeColor = isSubmenu ? Theme::NavBack : Theme::NavClose;
41 const char* closeLabel = isSubmenu ? "Back" : "Close";
42
43 std::ostringstream html;
44 html << "<font class='fontSize-s'>"
45 << "<font color='" << Theme::NavGold << "'>[W/S]</font> "
46 << "<font color='" << Theme::WarmGray << "'>Navigate</font>"
47 << " · "
48 << "<font color='" << Theme::Gold << "'>[E]</font> "
49 << "<font color='" << Theme::WarmGray << "'>Select</font>";
50
51 // When paginated there are four hint chunks — splitting onto two short rows is more reliable
52 // than relying on the HUD's word wrap, which sometimes pushes [R] past the visible area.
53 if (isPaginated)
54 {
55 html << "<br>"
56 << "<font color='" << Theme::NavGold << "'>[A/D]</font> "
57 << "<font color='" << Theme::WarmGray << "'>Page</font>"
58 << " · "
59 << "<font color='" << closeColor << "'>[R]</font> "
60 << "<font color='" << Theme::WarmGray << "'>" << closeLabel << "</font>";
61 }
62 else
63 {
64 html << " · "
65 << "<font color='" << closeColor << "'>[R]</font> "
66 << "<font color='" << Theme::WarmGray << "'>" << closeLabel << "</font>";
67 }
68
69 html << "</font>";
70 return html.str();
71}
72
73static std::string RenderItems(const Menu* menu, int slot, int selectedIndex, int pageStart, int pageEnd)
74{
75 std::ostringstream html;
76
77 for (int i = pageStart; i < pageEnd; ++i)
78 {
79 const auto& opt = menu->Items[i];
80 if (!opt)
81 continue;
82
83 std::string title = opt->GetLabel(slot);
84 bool selectable = opt->IsSelectable();
85 bool enabled = opt->IsEnabled();
86
87 if (!enabled)
88 {
89 html << "<font color='" << Theme::Disabled << "'>- " << title << "</font><br>";
90 }
91 else if (!selectable)
92 {
93 // Rendered without a cursor glyph — the row is informational, not a target.
94 html << "<font color='" << Theme::WarmGray << "'>" << title << "</font><br>";
95 }
96 else if (i == selectedIndex)
97 {
98 html << "<font color='" << Theme::Amber << "'><b>&gt; " << title << "</b></font> "
99 << "<font color='" << Theme::Gold << "'>[E]</font><br>";
100 }
101 else
102 {
103 html << "<font color='" << Theme::WarmWhite << "'> " << title << "</font><br>";
104 }
105 }
106
107 return html.str();
108}
109
110std::string RenderMenuHtml(const Menu* menu, int slot, int selectedIndex, bool isSubmenu)
111{
112 if (!menu)
113 {
114 return "";
115 }
116
117 int itemCount = static_cast<int>(menu->Items.size());
118 int totalPages = itemCount == 0 ? 1 : (itemCount + ItemsPerPage - 1) / ItemsPerPage;
119 int currentPage = itemCount == 0 ? 0 : selectedIndex / ItemsPerPage;
120 int pageStart = currentPage * ItemsPerPage;
121 int pageEnd = std::min(itemCount, pageStart + ItemsPerPage);
122
123 std::ostringstream html;
124
125 if (menu->Layout.Header)
126 {
127 html << menu->Layout.Header();
128 }
129 else
130 {
131 html << DefaultHeader(menu->Title, currentPage, totalPages);
132 }
133
134 html << RenderItems(menu, slot, selectedIndex, pageStart, pageEnd);
135
136 if (menu->Layout.Footer)
137 {
138 html << menu->Layout.Footer();
139 }
140 else
141 {
142 html << DefaultFooter(isSubmenu, totalPages > 1);
143 }
144
145 return html.str();
146}
147
148std::string RenderCaptureOverlay(const std::string& menuTitle, std::string_view prompt)
149{
150 std::ostringstream html;
151 html << "<font color='" << Theme::Gold << "'><b>" << menuTitle << "</b></font><br>"
152 << "<font color='" << Theme::WarmWhite << "'>" << prompt << "</font><br>"
153 << "<font class='fontSize-s' color='" << Theme::WarmGray << "'>Type your answer in chat</font><br>"
154 << "<font class='fontSize-s'>"
155 << "<font color='" << Theme::NavClose << "'>[R]</font> "
156 << "<font color='" << Theme::WarmGray << "'>Cancel</font>"
157 << "</font>";
158 return html.str();
159}
160
161} // namespace CS2Kit::Menu
constexpr const char * WarmGray
constexpr const char * Disabled
constexpr const char * Gold
constexpr const char * NavClose
constexpr const char * WarmWhite
constexpr const char * Amber
constexpr const char * NavGold
constexpr const char * NavBack
static std::string RenderItems(const Menu *menu, int slot, int selectedIndex, int pageStart, int pageEnd)
std::string RenderMenuHtml(const Menu *menu, int slot, int selectedIndex, bool isSubmenu)
std::string RenderCaptureOverlay(const std::string &menuTitle, std::string_view prompt)
std::string DefaultFooter(bool isSubmenu, bool isPaginated)
std::string DefaultHeader(const std::string &title, int currentPage, int totalPages)