| 1 |
#ifndef IMGUI_UTILS_HH |
| 2 |
#define IMGUI_UTILS_HH |
| 3 |
|
| 4 |
#include "ImGuiCpp.hh" |
| 5 |
|
| 6 |
#include "Reactor.hh" |
| 7 |
|
| 8 |
#include "Math.hh" |
| 9 |
#include "StringOp.hh" |
| 10 |
#include "circular_buffer.hh" |
| 11 |
#include "fast_log2.hh" |
| 12 |
#include "function_ref.hh" |
| 13 |
#include "strCat.hh" |
| 14 |
|
| 15 |
#include <imgui.h> |
| 16 |
#include <imgui_internal.h> |
| 17 |
|
| 18 |
#include <algorithm> |
| 19 |
#include <concepts> |
| 20 |
#include <functional> |
| 21 |
#include <span> |
| 22 |
#include <string> |
| 23 |
#include <string_view> |
| 24 |
#include <utility> |
| 25 |
|
| 26 |
using namespace std::literals; |
| 27 |
|
| 28 |
namespace ImGui { |
| 29 |
|
| 30 |
inline void TextUnformatted(const std::string& str) |
| 31 |
{ |
| 32 |
const char* begin = str.data(); |
| 33 |
const char* end = begin + str.size(); |
| 34 |
ImGui::TextUnformatted(begin, end); |
| 35 |
} |
| 36 |
inline void TextUnformatted(std::string_view str) |
| 37 |
{ |
| 38 |
const char* begin = str.data(); |
| 39 |
const char* end = begin + str.size(); |
| 40 |
ImGui::TextUnformatted(begin, end); |
| 41 |
} |
| 42 |
|
| 43 |
inline void TextDisabledUnformatted(std::string_view str) |
| 44 |
{ |
| 45 |
im::StyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled), [&]{ |
| 46 |
ImGui::TextUnformatted(str); |
| 47 |
}); |
| 48 |
} |
| 49 |
|
| 50 |
inline auto CalcTextSize(std::string_view str) |
| 51 |
{ |
| 52 |
return ImGui::CalcTextSize(str.data(), str.data() + str.size()); |
| 53 |
} |
| 54 |
|
| 55 |
inline std::string leftClip(std::string_view s, float maxWidth) |
| 56 |
{ |
| 57 |
auto fullWidth = ImGui::CalcTextSize(s).x; |
| 58 |
if (fullWidth <= maxWidth) return std::string(s); |
| 59 |
|
| 60 |
maxWidth -= ImGui::CalcTextSize("..."sv).x; |
| 61 |
if (maxWidth <= 0.0f) return "..."; |
| 62 |
|
| 63 |
auto len = s.size(); |
| 64 |
auto num = *std::ranges::lower_bound(std::views::iota(0uz, len), maxWidth, {}, |
| 65 |
[&](size_t n) { return ImGui::CalcTextSize(s.substr(len - n)).x; }); |
| 66 |
return strCat("...", s.substr(len - num)); |
| 67 |
} |
| 68 |
|
| 69 |
|
| 70 |
|
| 71 |
inline std::string rightClip(std::string_view s, float maxWidth) |
| 72 |
{ |
| 73 |
auto fullWidth = ImGui::CalcTextSize(s).x; |
| 74 |
if (fullWidth <= maxWidth) return std::string(s); |
| 75 |
|
| 76 |
maxWidth -= ImGui::CalcTextSize("..."sv).x; |
| 77 |
if (maxWidth <= 0.0f) return "..."; |
| 78 |
|
| 79 |
|
| 80 |
|
| 81 |
auto lengths = std::views::iota(0uz, s.size() + 1); |
| 82 |
auto it = std::ranges::upper_bound(lengths, maxWidth, {}, |
| 83 |
[&](size_t n) { return ImGui::CalcTextSize(s.substr(0, n)).x; }); |
| 84 |
auto num = (it == lengths.end()) ? s.size() : (*it - 1); |
| 85 |
return strCat(s.substr(0, num), "..."); |
| 86 |
} |
| 87 |
|
| 88 |
template<typename... Ts> |
| 89 |
void StrCat(Ts&& ...ts) |
| 90 |
{ |
| 91 |
auto s = tmpStrCat(std::forward<Ts>(ts)...); |
| 92 |
TextUnformatted(std::string_view(s)); |
| 93 |
} |
| 94 |
|
| 95 |
inline void RightAlignText(std::string_view text, float maxWidth) |
| 96 |
{ |
| 97 |
auto actualWidth = ImGui::CalcTextSize(text).x; |
| 98 |
if (auto spacing = maxWidth - actualWidth; spacing > 0.0f) { |
| 99 |
auto pos = ImGui::GetCursorPosX(); |
| 100 |
ImGui::SetCursorPosX(pos + spacing); |
| 101 |
} |
| 102 |
ImGui::TextUnformatted(text); |
| 103 |
} |
| 104 |
|
| 105 |
inline void RightAlignText(std::string_view text, std::string_view maxWidthText) |
| 106 |
{ |
| 107 |
RightAlignText(text, ImGui::CalcTextSize(maxWidthText).x); |
| 108 |
} |
| 109 |
|
| 110 |
inline void AddImageRectMultiColor(ImDrawList* draw_list, ImTextureRef tex_id, |
| 111 |
const ImVec2& p_min, const ImVec2& p_max, |
| 112 |
const ImVec2& uv_min, const ImVec2& uv_max, |
| 113 |
ImU32 col_tl, ImU32 col_tr, ImU32 col_br, |
| 114 |
ImU32 col_bl) |
| 115 |
{ |
| 116 |
const bool push_texture_id = tex_id != draw_list->_CmdHeader.TexRef; |
| 117 |
if (push_texture_id) draw_list->PushTexture(tex_id); |
| 118 |
|
| 119 |
auto idx = ImDrawIdx(draw_list->_VtxCurrentIdx); |
| 120 |
draw_list->PrimReserve(6, 4); |
| 121 |
|
| 122 |
draw_list->PrimWriteIdx(ImDrawIdx(idx + 0)); |
| 123 |
draw_list->PrimWriteIdx(ImDrawIdx(idx + 1)); |
| 124 |
draw_list->PrimWriteIdx(ImDrawIdx(idx + 2)); |
| 125 |
|
| 126 |
draw_list->PrimWriteIdx(ImDrawIdx(idx + 0)); |
| 127 |
draw_list->PrimWriteIdx(ImDrawIdx(idx + 2)); |
| 128 |
draw_list->PrimWriteIdx(ImDrawIdx(idx + 3)); |
| 129 |
|
| 130 |
draw_list->PrimWriteVtx(p_min, uv_min, col_tl); |
| 131 |
draw_list->PrimWriteVtx(ImVec2(p_max.x, p_min.y), ImVec2(uv_max.x, uv_min.y), col_tr); |
| 132 |
draw_list->PrimWriteVtx(p_max, uv_max, col_br); |
| 133 |
draw_list->PrimWriteVtx(ImVec2(p_min.x, p_max.y), ImVec2(uv_min.x, uv_max.y), col_bl); |
| 134 |
|
| 135 |
if (push_texture_id) draw_list->PopTexture(); |
| 136 |
} |
| 137 |
|
| 138 |
} |
| 139 |
|
| 140 |
namespace openmsx { |
| 141 |
|
| 142 |
class BooleanSetting; |
| 143 |
class FloatSetting; |
| 144 |
class HotKey; |
| 145 |
class ImGuiManager; |
| 146 |
class IntegerSetting; |
| 147 |
class Setting; |
| 148 |
class VideoSourceSetting; |
| 149 |
|
| 150 |
struct EnumToolTip { |
| 151 |
std::string_view value; |
| 152 |
std::string_view tip; |
| 153 |
}; |
| 154 |
using EnumToolTips = std::span<const EnumToolTip>; |
| 155 |
|
| 156 |
inline void simpleToolTip(std::string_view desc) |
| 157 |
{ |
| 158 |
if (desc.empty()) return; |
| 159 |
im::ItemTooltip([&]{ |
| 160 |
im::TextWrapPos(ImGui::GetFontSize() * 35.0f, [&]{ |
| 161 |
ImGui::TextUnformatted(desc); |
| 162 |
}); |
| 163 |
}); |
| 164 |
} |
| 165 |
|
| 166 |
void simpleToolTip(std::invocable<> auto descFunc) |
| 167 |
{ |
| 168 |
if (!ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) return; |
| 169 |
auto desc = std::invoke(descFunc); |
| 170 |
if (desc.empty()) return; |
| 171 |
im::Tooltip([&]{ |
| 172 |
im::TextWrapPos(ImGui::GetFontSize() * 35.0f, [&]{ |
| 173 |
ImGui::TextUnformatted(desc); |
| 174 |
}); |
| 175 |
}); |
| 176 |
} |
| 177 |
|
| 178 |
void HelpMarkerNewLine(std::string_view desc); |
| 179 |
void HelpMarker(std::string_view desc, float spacing = -1.0f); |
| 180 |
|
| 181 |
|
| 182 |
|
| 183 |
|
| 184 |
[[nodiscard]] inline std::tuple<float, float, float> generateDistinctColor( |
| 185 |
unsigned index, |
| 186 |
float h0 = 0.0f, |
| 187 |
unsigned v0 = 0) |
| 188 |
{ |
| 189 |
|
| 190 |
constexpr float phi = 0.6180339887498948482f; |
| 191 |
auto h = Math::fract(h0 + float(index) * phi); |
| 192 |
|
| 193 |
|
| 194 |
constexpr float s = 0.65f; |
| 195 |
|
| 196 |
|
| 197 |
constexpr std::array v_levels = {0.90f, 0.75f, 0.60f}; |
| 198 |
auto v = v_levels[(index + v0) % v_levels.size()]; |
| 199 |
|
| 200 |
float r, g, b; |
| 201 |
ImGui::ColorConvertHSVtoRGB(h, s, v, r, g, b); |
| 202 |
return {r, g, b}; |
| 203 |
} |
| 204 |
|
| 205 |
inline bool ButtonWithCustomRendering( |
| 206 |
const char* label, gl::vec2 size, bool pressed, |
| 207 |
std::invocable<gl::vec2 , ImDrawList*> auto render) |
| 208 |
{ |
| 209 |
bool result = false; |
| 210 |
im::StyleColor(pressed, ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive), [&]{ |
| 211 |
gl::vec2 topLeft = ImGui::GetCursorScreenPos(); |
| 212 |
gl::vec2 center = topLeft + size * 0.5f; |
| 213 |
result = ImGui::Button(label, size); |
| 214 |
render(center, ImGui::GetWindowDrawList()); |
| 215 |
}); |
| 216 |
return result; |
| 217 |
} |
| 218 |
|
| 219 |
inline bool ButtonWithCustomRendering( |
| 220 |
const char* label, gl::vec2 size, |
| 221 |
std::invocable<gl::vec2 , ImDrawList*> auto render) |
| 222 |
{ |
| 223 |
return ButtonWithCustomRendering(label, size, false, render); |
| 224 |
} |
| 225 |
|
| 226 |
inline bool ButtonWithCenteredGlyph(ImWchar glyph, gl::vec2 maxGlyphSize) |
| 227 |
{ |
| 228 |
std::array<char, 2 + 5> label = {'#', '#'}; |
| 229 |
ImTextCharToUtf8(&label[2], glyph); |
| 230 |
|
| 231 |
const auto& style = ImGui::GetStyle(); |
| 232 |
auto buttonSize = maxGlyphSize + 2.0f* gl::vec2(style.FramePadding); |
| 233 |
|
| 234 |
return ButtonWithCustomRendering(label.data(), buttonSize, [&](gl::vec2 center, ImDrawList* drawList) { |
| 235 |
auto* font = ImGui::GetFont(); |
| 236 |
auto texId = font->OwnerAtlas->TexRef; |
| 237 |
const auto* g = font->GetFontBaked(ImGui::GetFontSize())->FindGlyph(glyph); |
| 238 |
auto halfSize = gl::vec2{g->X1 - g->X0, g->Y1 - g->Y0} * 0.5f; |
| 239 |
drawList->AddImage(texId, center - halfSize, center + halfSize, {g->U0, g->V0}, {g->U1, g->V1}); |
| 240 |
}); |
| 241 |
}; |
| 242 |
|
| 243 |
inline void centerNextWindowOverCurrent() |
| 244 |
{ |
| 245 |
static constexpr gl::vec2 center{0.5f, 0.5f}; |
| 246 |
gl::vec2 windowPos = ImGui::GetWindowPos(); |
| 247 |
gl::vec2 windowSize = ImGui::GetWindowSize(); |
| 248 |
auto windowCenter = windowPos + center * windowSize; |
| 249 |
ImGui::SetNextWindowPos(windowCenter, ImGuiCond_Appearing, center); |
| 250 |
} |
| 251 |
|
| 252 |
class ConfirmDialog { |
| 253 |
public: |
| 254 |
explicit ConfirmDialog(std::string title_) : title(std::move(title_)) {} |
| 255 |
|
| 256 |
void open(std::string text_, std::function<void()> action_) { |
| 257 |
text = std::move(text_); |
| 258 |
action = std::move(action_); |
| 259 |
doOpen = true; |
| 260 |
} |
| 261 |
|
| 262 |
void execute(); |
| 263 |
|
| 264 |
private: |
| 265 |
std::string title; |
| 266 |
std::string text; |
| 267 |
std::function<void()> action; |
| 268 |
bool doOpen = false; |
| 269 |
}; |
| 270 |
|
| 271 |
class ConfirmDialogTclCommand : public ConfirmDialog { |
| 272 |
public: |
| 273 |
ConfirmDialogTclCommand(ImGuiManager& manager_, std::string title_) |
| 274 |
: ConfirmDialog(std::move(title_)), manager(&manager_) {} |
| 275 |
|
| 276 |
void open(std::string text_, TclObject cmd_); |
| 277 |
|
| 278 |
private: |
| 279 |
ImGuiManager* manager; |
| 280 |
}; |
| 281 |
|
| 282 |
struct GetSettingDescription { |
| 283 |
std::string operator()(const Setting& setting) const; |
| 284 |
}; |
| 285 |
|
| 286 |
bool Checkbox(const HotKey& hotkey, BooleanSetting& setting); |
| 287 |
bool Checkbox(const HotKey& hotkey, const char* label, BooleanSetting& setting, function_ref<std::string(const Setting&)> getTooltip = GetSettingDescription{}); |
| 288 |
bool SliderInt(IntegerSetting& setting, ImGuiSliderFlags flags = 0); |
| 289 |
bool SliderInt(const char* label, IntegerSetting& setting, ImGuiSliderFlags flags = 0); |
| 290 |
bool SliderFloat(FloatSetting& setting, const char* format = "%.3f", ImGuiSliderFlags flags = 0); |
| 291 |
bool SliderFloat(const char* label, FloatSetting& setting, const char* format = "%.3f", ImGuiSliderFlags flags = 0); |
| 292 |
bool InputText(Setting& setting); |
| 293 |
bool InputText(const char* label, Setting& setting); |
| 294 |
void ComboBox(Setting& setting, EnumToolTips toolTips = {}); |
| 295 |
void ComboBox(const char* label, Setting& setting, EnumToolTips toolTips = {}); |
| 296 |
void ComboBox(const char* label, Setting& setting, function_ref<std::string(const std::string&)> displayValue, EnumToolTips toolTips = {}); |
| 297 |
void ComboBox(VideoSourceSetting& setting); |
| 298 |
void ComboBox(const char* label, VideoSourceSetting& setting); |
| 299 |
|
| 300 |
const char* getComboString(int item, const char* itemsSeparatedByZeros); |
| 301 |
|
| 302 |
std::string formatTime(std::optional<double> time); |
| 303 |
float calculateFade(float current, float target, float period); |
| 304 |
|
| 305 |
template<int HexDigits> |
| 306 |
void comboHexSequence(const char* label, int* value, int mult, int max, int offset) { |
| 307 |
assert(offset < mult); |
| 308 |
*value &= ~(mult - 1); |
| 309 |
|
| 310 |
auto preview = tmpStrCat("0x", hex_string<HexDigits>(*value | offset)); |
| 311 |
im::Combo(label, preview.c_str(), [&]{ |
| 312 |
for (int addr = 0; addr < max; addr += mult) { |
| 313 |
if (auto str = tmpStrCat("0x", hex_string<HexDigits>(addr | offset)); |
| 314 |
ImGui::Selectable(str.c_str(), *value == addr)) { |
| 315 |
*value = addr; |
| 316 |
} |
| 317 |
if (*value == addr) { |
| 318 |
ImGui::SetItemDefaultFocus(); |
| 319 |
} |
| 320 |
} |
| 321 |
}); |
| 322 |
}; |
| 323 |
|
| 324 |
template<typename Range, typename Projection> |
| 325 |
void sortUpDown_T(Range& range, const ImGuiTableSortSpecs* sortSpecs, Projection proj) { |
| 326 |
if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Descending) { |
| 327 |
std::ranges::stable_sort(range, std::greater<>{}, proj); |
| 328 |
} else { |
| 329 |
std::ranges::stable_sort(range, std::less<>{}, proj); |
| 330 |
} |
| 331 |
}; |
| 332 |
template<typename Range, typename Projection> |
| 333 |
void sortUpDown_String(Range& range, const ImGuiTableSortSpecs* sortSpecs, Projection proj) { |
| 334 |
if (sortSpecs->Specs->SortDirection == ImGuiSortDirection_Descending) { |
| 335 |
std::ranges::stable_sort(range, StringOp::inv_caseless{}, proj); |
| 336 |
} else { |
| 337 |
std::ranges::stable_sort(range, StringOp::caseless{}, proj); |
| 338 |
} |
| 339 |
}; |
| 340 |
|
| 341 |
|
| 342 |
[[nodiscard]] inline const std::string* getOptionalDictValue( |
| 343 |
const std::vector<std::pair<std::string, std::string>>& info, |
| 344 |
std::string_view key) |
| 345 |
{ |
| 346 |
auto it = std::ranges::find_if(info, [&](const auto& p) { return p.first == key; }); |
| 347 |
if (it == info.end()) return {}; |
| 348 |
return &it->second; |
| 349 |
} |
| 350 |
|
| 351 |
template<typename T> |
| 352 |
[[nodiscard]] std::vector<std::string> getAllValuesFor(std::string_view key, const std::vector<T>& items) |
| 353 |
{ |
| 354 |
std::vector<std::string> result; |
| 355 |
for (const auto& item : items) { |
| 356 |
if (const auto* value = getOptionalDictValue(item.configInfo, key)) { |
| 357 |
if (!contains(result, *value)) { |
| 358 |
result.emplace_back(*value); |
| 359 |
} |
| 360 |
} |
| 361 |
} |
| 362 |
std::ranges::sort(result, StringOp::caseless{}); |
| 363 |
return result; |
| 364 |
} |
| 365 |
|
| 366 |
template<typename T> |
| 367 |
void displayFilterCombo(std::string& selection, zstring_view key, const std::vector<T>& items) |
| 368 |
{ |
| 369 |
im::Combo(key.c_str(), selection.empty() ? "--all--" : selection.c_str(), [&]{ |
| 370 |
if (ImGui::Selectable("--all--")) { |
| 371 |
selection.clear(); |
| 372 |
} |
| 373 |
for (const auto& type : getAllValuesFor(key, items)) { |
| 374 |
if (ImGui::Selectable(type.c_str())) { |
| 375 |
selection = type; |
| 376 |
} |
| 377 |
} |
| 378 |
}); |
| 379 |
} |
| 380 |
|
| 381 |
template<typename T> |
| 382 |
void applyComboFilter(std::string_view key, std::string_view value, const std::vector<T>& items, std::vector<size_t>& indices) |
| 383 |
{ |
| 384 |
if (value.empty()) return; |
| 385 |
std::erase_if(indices, [&](auto idx) { |
| 386 |
const auto& info = items[idx].configInfo; |
| 387 |
const auto* val = getOptionalDictValue(info, key); |
| 388 |
if (!val) return true; |
| 389 |
return *val != value; |
| 390 |
}); |
| 391 |
} |
| 392 |
|
| 393 |
template<std::invocable<size_t> GetName> |
| 394 |
void filterIndices(std::string_view filterString, GetName getName, std::vector<size_t>& indices) |
| 395 |
{ |
| 396 |
if (filterString.empty()) return; |
| 397 |
std::erase_if(indices, [&](auto idx) { |
| 398 |
const auto& name = getName(idx); |
| 399 |
return !std::ranges::all_of(StringOp::split_view<StringOp::EmptyParts::REMOVE>(filterString, ' '), |
| 400 |
[&](auto part) { return StringOp::containsCaseInsensitive(name, part); }); |
| 401 |
}); |
| 402 |
} |
| 403 |
|
| 404 |
template<typename T> |
| 405 |
void applyDisplayNameFilter(std::string_view filterString, const std::vector<T>& items, std::vector<size_t>& indices) |
| 406 |
{ |
| 407 |
filterIndices(filterString, [&](size_t idx) { return items[idx].displayName; }, indices); |
| 408 |
} |
| 409 |
|
| 410 |
template<typename T> |
| 411 |
void addRecentItem(circular_buffer<T>& recentItems, const T& item) |
| 412 |
{ |
| 413 |
if (auto it = std::ranges::find(recentItems, item); it != recentItems.end()) { |
| 414 |
|
| 415 |
std::rotate(recentItems.begin(), it, it + 1); |
| 416 |
} else { |
| 417 |
|
| 418 |
if (recentItems.full()) recentItems.pop_back(); |
| 419 |
recentItems.push_front(item); |
| 420 |
} |
| 421 |
} |
| 422 |
|
| 423 |
|
| 424 |
template<typename Range, typename BinaryPred, typename Action> |
| 425 |
static void chunk_by(Range&& range, BinaryPred pred, Action action) |
| 426 |
{ |
| 427 |
auto it = std::begin(range); |
| 428 |
auto last = std::end(range); |
| 429 |
while (it != last) { |
| 430 |
auto start = it; |
| 431 |
auto prev = it++; |
| 432 |
while (it != last && pred(*prev, *it)) { |
| 433 |
prev = it++; |
| 434 |
} |
| 435 |
action(start, it); |
| 436 |
} |
| 437 |
} |
| 438 |
|
| 439 |
TemporaryString getShortCutForCommand(const HotKey& hotkey, std::string_view command); |
| 440 |
|
| 441 |
std::string getKeyChordName(ImGuiKeyChord keyChord); |
| 442 |
std::optional<ImGuiKeyChord> parseKeyChord(std::string_view name); |
| 443 |
|
| 444 |
[[nodiscard]] std::string formatToString(function_ref<uint8_t(unsigned)> fetch, unsigned begin, unsigned end, std::string_view prefix, |
| 445 |
unsigned columns, std::string_view suffix, std::string_view separator, std::string_view formatStr, Interpreter& interp); |
| 446 |
|
| 447 |
[[nodiscard]] std::string formatToString(function_ref<uint8_t(unsigned)> fetch, unsigned begin, unsigned end, std::string_view prefix, |
| 448 |
unsigned columns, std::string_view suffix, std::string_view separator, std::function<std::string(uint8_t)> formatFunc); |
| 449 |
|
| 450 |
[[nodiscard]] std::string rawToString(function_ref<uint8_t(unsigned)> fetch, unsigned begin, unsigned end); |
| 451 |
|
| 452 |
|
| 453 |
|
| 454 |
class VramTable { |
| 455 |
public: |
| 456 |
explicit VramTable(std::span<const uint8_t> vram_, bool planar_ = false) |
| 457 |
: vram(vram_), planar(planar_) {} |
| 458 |
|
| 459 |
void setRegister(unsigned value, unsigned extraLsbBits) { |
| 460 |
registerMask = (value << extraLsbBits) | ~(~0u << extraLsbBits); |
| 461 |
} |
| 462 |
void setIndexSize(unsigned bits) { |
| 463 |
indexMask = ~0u << bits; |
| 464 |
} |
| 465 |
|
| 466 |
[[nodiscard]] auto getAddress(unsigned index) const { |
| 467 |
return registerMask & (indexMask | index); |
| 468 |
} |
| 469 |
[[nodiscard]] uint8_t operator[](unsigned index) const { |
| 470 |
auto addr = getAddress(index); |
| 471 |
if (planar) { |
| 472 |
addr = ((addr << 16) | (addr >> 1)) & 0x1'FFFF; |
| 473 |
} |
| 474 |
return vram[addr]; |
| 475 |
} |
| 476 |
private: |
| 477 |
std::span<const uint8_t> vram; |
| 478 |
unsigned registerMask = 0; |
| 479 |
unsigned indexMask = 0; |
| 480 |
bool planar = false; |
| 481 |
}; |
| 482 |
|
| 483 |
[[nodiscard]] inline int freq2noteNum(float freq) |
| 484 |
{ |
| 485 |
static constexpr auto a4_freq = 440.0f; |
| 486 |
return int(std::lround(12.0f * fast_log2(freq / a4_freq))) + 9 + 4 * 12; |
| 487 |
} |
| 488 |
[[nodiscard]] inline std::string_view noteNum2str(int n, std::span<char, 3> buf) |
| 489 |
{ |
| 490 |
using N = std::array<char, 2>; |
| 491 |
static constexpr std::array<N, 12> names = { |
| 492 |
N{'C'}, N{'C','#'}, N{'D'}, N{'D','#'}, N{'E'}, |
| 493 |
N{'F'}, N{'F','#'}, N{'G'}, N{'G','#'}, N{'A'}, N{'A','#'}, N{'B'} |
| 494 |
}; |
| 495 |
|
| 496 |
if (n < 0) return {}; |
| 497 |
auto note = n % 12; |
| 498 |
auto octave = n / 12; |
| 499 |
const auto& name = names[note]; |
| 500 |
buf[0] = name[0]; |
| 501 |
if (name[1]) { |
| 502 |
buf[1] = name[1]; |
| 503 |
buf[2] = char(octave + '0'); |
| 504 |
return std::string_view{buf.data(), 3}; |
| 505 |
} else { |
| 506 |
buf[1] = char(octave + '0'); |
| 507 |
return std::string_view{buf.data(), 2}; |
| 508 |
} |
| 509 |
} |
| 510 |
[[nodiscard]] inline auto freq2note(float freq, std::span<char, 3> buf) |
| 511 |
{ |
| 512 |
return noteNum2str(freq2noteNum(freq), buf); |
| 513 |
} |
| 514 |
|
| 515 |
enum class imColor : uint8_t { |
| 516 |
TRANSPARENT, |
| 517 |
BLACK, |
| 518 |
WHITE, |
| 519 |
GRAY, |
| 520 |
YELLOW, |
| 521 |
RED_BG, |
| 522 |
YELLOW_BG, |
| 523 |
|
| 524 |
TEXT, |
| 525 |
TEXT_DISABLED, |
| 526 |
|
| 527 |
ERROR, |
| 528 |
WARNING, |
| 529 |
|
| 530 |
COMMENT, |
| 531 |
VARIABLE, |
| 532 |
LITERAL, |
| 533 |
PROC, |
| 534 |
OPERATOR, |
| 535 |
|
| 536 |
KEY_ACTIVE, |
| 537 |
KEY_NOT_ACTIVE, |
| 538 |
|
| 539 |
NUM_COLORS |
| 540 |
}; |
| 541 |
inline std::array<ImU32, size_t(imColor::NUM_COLORS)> imColors; |
| 542 |
|
| 543 |
void setColors(int style); |
| 544 |
|
| 545 |
inline ImU32 getColor(imColor col) { |
| 546 |
assert(col < imColor::NUM_COLORS); |
| 547 |
return imColors[size_t(col)]; |
| 548 |
} |
| 549 |
|
| 550 |
} |
| 551 |
|
| 552 |
#endif |