Files
whetstone_DSL/editor/src/RichTooltip.h
2026-02-09 22:50:49 -07:00

103 lines
3.6 KiB
C++

#pragma once
#include "imgui.h"
#include "AnimationUtils.h"
#include <algorithm>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
struct RichTooltipState {
bool pinned = false;
ImVec2 pinnedPos = ImVec2(0, 0);
std::string pinnedContent;
};
inline std::unordered_map<std::string, RichTooltipState>& richTooltipStates() {
static std::unordered_map<std::string, RichTooltipState> states;
return states;
}
inline void richRenderMarkdownLine(const std::string& line, ImFont* monoFont) {
std::string trimmed = line;
while (!trimmed.empty() && (trimmed.back() == '\r' || trimmed.back() == '\n')) {
trimmed.pop_back();
}
if (trimmed.rfind("## ", 0) == 0) {
ImGui::TextColored(ImVec4(0.7f, 0.9f, 1.0f, 1.0f), "%s", trimmed.substr(3).c_str());
} else if (trimmed.rfind("### ", 0) == 0) {
ImGui::TextColored(ImVec4(0.6f, 0.85f, 0.95f, 1.0f), "%s", trimmed.substr(4).c_str());
} else if (trimmed.rfind("- ", 0) == 0) {
ImGui::BulletText("%s", trimmed.substr(2).c_str());
} else if (trimmed.rfind("```", 0) == 0) {
ImGui::TextDisabled("%s", trimmed.c_str());
} else if (trimmed.rfind("`", 0) == 0 && trimmed.size() > 1) {
ImGui::PushFont(monoFont);
ImGui::TextUnformatted(trimmed.c_str());
ImGui::PopFont();
} else {
ImGui::TextWrapped("%s", trimmed.c_str());
}
}
inline void richRenderMarkdown(const std::string& text, ImFont* monoFont) {
std::istringstream ss(text);
std::string line;
while (std::getline(ss, line)) {
richRenderMarkdownLine(line, monoFont);
}
}
inline void renderRichTooltip(const std::string& id,
const std::string& content,
bool hovered,
bool reduceMotion,
ImFont* monoFont,
float maxWidth = 420.0f,
float maxHeight = 240.0f) {
auto& state = richTooltipStates()[id];
const double now = ImGui::GetTime();
float alpha = AnimationUtils::tooltipAlpha(id, hovered, now, reduceMotion);
if (alpha > 0.01f && hovered && !state.pinned) {
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * alpha);
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + maxWidth);
richRenderMarkdown(content, monoFont);
ImGui::PopTextWrapPos();
ImGui::Separator();
if (ImGui::SmallButton("Pin")) {
state.pinned = true;
state.pinnedPos = ImGui::GetMousePos();
state.pinnedContent = content;
}
ImGui::EndTooltip();
ImGui::PopStyleVar();
}
if (state.pinned) {
ImGuiWindowFlags flags = ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_AlwaysAutoResize |
ImGuiWindowFlags_NoSavedSettings;
ImGui::SetNextWindowPos(state.pinnedPos, ImGuiCond_FirstUseEver);
bool open = true;
if (ImGui::Begin(("Tooltip##" + id).c_str(), &open, flags)) {
ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + maxWidth);
ImGui::BeginChild(("##tip_scroll_" + id).c_str(), ImVec2(maxWidth, maxHeight), false);
richRenderMarkdown(state.pinnedContent, monoFont);
ImGui::EndChild();
ImGui::PopTextWrapPos();
if (ImGui::SmallButton("Close")) {
open = false;
}
}
ImGui::End();
if (!open) {
state.pinned = false;
state.pinnedContent.clear();
}
}
}