satdump/src-core/imgui/imgui_markdown.h
2022-09-14 17:47:15 +02:00

1015 lines
36 KiB
C++

#pragma once
// License: zlib
// Copyright (c) 2019 Juliette Foucaut & Doug Binks
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
// claim that you wrote the original software. If you use this software
// in a product, an acknowledgment in the product documentation would be
// appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
// misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.
/*
API BREAKING CHANGES
====================
- 2020/04/22 - Added tooltipCallback parameter to ImGui::MarkdownConfig
- 2019/02/01 - Changed LinkCallback parameters, see https://github.com/juliettef/imgui_markdown/issues/2
- 2019/02/05 - Added imageCallback parameter to ImGui::MarkdownConfig
- 2019/02/06 - Added useLinkCallback member variable to MarkdownImageData to configure using images as links
*/
/*
imgui_markdown https://github.com/juliettef/imgui_markdown
Markdown for Dear ImGui
A permissively licensed markdown single-header library for https://github.com/ocornut/imgui
Currently requires C++11 or above
imgui_markdown currently supports the following markdown functionality:
- Wrapped text
- Headers H1, H2, H3
- Emphasis
- Indented text, multi levels
- Unordered lists and sub-lists
- Link
- Image
- Horizontal rule
Syntax
Wrapping:
Text wraps automatically. To add a new line, use 'Return'.
Headers:
# H1
## H2
### H3
Emphasis:
*emphasis*
_emphasis_
**strong emphasis**
__strong emphasis__
Indents:
On a new line, at the start of the line, add two spaces per indent.
Indent level 1
Indent level 2
Unordered lists:
On a new line, at the start of the line, add two spaces, an asterisks and a space.
For nested lists, add two additional spaces in front of the asterisk per list level increment.
* Unordered List level 1
* Unordered List level 2
Link:
[link description](https://...)
Image:
![image alt text](image identifier e.g. filename)
Horizontal Rule:
***
___
===============================================================================
// Example use on Windows with links opening in a browser
#include "ImGui.h" // https://github.com/ocornut/imgui
#include "imgui_markdown.h" // https://github.com/juliettef/imgui_markdown
#include "IconsFontAwesome5.h" // https://github.com/juliettef/IconFontCppHeaders
// Following includes for Windows LinkCallback
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include "Shellapi.h"
#include <string>
void LinkCallback( ImGui::MarkdownLinkCallbackData data_ );
inline ImGui::MarkdownImageData ImageCallback( ImGui::MarkdownLinkCallbackData data_ );
static ImFont* H1 = NULL;
static ImFont* H2 = NULL;
static ImFont* H3 = NULL;
static ImGui::MarkdownConfig mdConfig;
void LinkCallback( ImGui::MarkdownLinkCallbackData data_ )
{
std::string url( data_.link, data_.linkLength );
if( !data_.isImage )
{
ShellExecuteA( NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL );
}
}
inline ImGui::MarkdownImageData ImageCallback( ImGui::MarkdownLinkCallbackData data_ )
{
// In your application you would load an image based on data_ input. Here we just use the imgui font texture.
ImTextureID image = ImGui::GetIO().Fonts->TexID;
// > C++14 can use ImGui::MarkdownImageData imageData{ true, false, image, ImVec2( 40.0f, 20.0f ) };
ImGui::MarkdownImageData imageData;
imageData.isValid = true;
imageData.useLinkCallback = false;
imageData.user_texture_id = image;
imageData.size = ImVec2( 40.0f, 20.0f );
// For image resize when available size.x > image width, add
ImVec2 const contentSize = ImGui::GetContentRegionAvail();
if( imageData.size.x > contentSize.x )
{
float const ratio = imageData.size.y/imageData.size.x;
imageData.size.x = contentSize.x;
imageData.size.y = contentSize.x*ratio;
}
return imageData;
}
void LoadFonts( float fontSize_ = 12.0f )
{
ImGuiIO& io = ImGui::GetIO();
io.Fonts->Clear();
// Base font
io.Fonts->AddFontFromFileTTF( "myfont.ttf", fontSize_ );
// Bold headings H2 and H3
H2 = io.Fonts->AddFontFromFileTTF( "myfont-bold.ttf", fontSize_ );
H3 = mdConfig.headingFormats[ 1 ].font;
// bold heading H1
float fontSizeH1 = fontSize_ * 1.1f;
H1 = io.Fonts->AddFontFromFileTTF( "myfont-bold.ttf", fontSizeH1 );
}
void ExampleMarkdownFormatCallback( const ImGui::MarkdownFormatInfo& markdownFormatInfo_, bool start_ )
{
// Call the default first so any settings can be overwritten by our implementation.
// Alternatively could be called or not called in a switch statement on a case by case basis.
// See defaultMarkdownFormatCallback definition for furhter examples of how to use it.
ImGui::defaultMarkdownFormatCallback( markdownFormatInfo_, start_ );
switch( markdownFormatInfo_.type )
{
// example: change the colour of heading level 2
case ImGui::MarkdownFormatType::HEADING:
{
if( markdownFormatInfo_.level == 2 )
{
if( start_ )
{
ImGui::PushStyleColor( ImGuiCol_Text, ImGui::GetStyle().Colors[ ImGuiCol_TextDisabled ] );
}
else
{
ImGui::PopStyleColor();
}
}
break;
}
default:
{
break;
}
}
}
void Markdown( const std::string& markdown_ )
{
// You can make your own Markdown function with your prefered string container and markdown config.
// > C++14 can use ImGui::MarkdownConfig mdConfig{ LinkCallback, NULL, ImageCallback, ICON_FA_LINK, { { H1, true }, { H2, true }, { H3, false } }, NULL };
mdConfig.linkCallback = LinkCallback;
mdConfig.tooltipCallback = NULL;
mdConfig.imageCallback = ImageCallback;
mdConfig.linkIcon = ICON_FA_LINK;
mdConfig.headingFormats[0] = { H1, true };
mdConfig.headingFormats[1] = { H2, true };
mdConfig.headingFormats[2] = { H3, false };
mdConfig.userData = NULL;
mdConfig.formatCallback = ExampleMarkdownFormatCallback;
ImGui::Markdown( markdown_.c_str(), markdown_.length(), mdConfig );
}
void MarkdownExample()
{
const std::string markdownText = u8R"(
# H1 Header: Text and Links
You can add [links like this one to enkisoftware](https://www.enkisoftware.com/) and lines will wrap well.
You can also insert images ![image alt text](image identifier e.g. filename)
Horizontal rules:
***
___
*Emphasis* and **strong emphasis** change the appearance of the text.
## H2 Header: indented text.
This text has an indent (two leading spaces).
This one has two.
### H3 Header: Lists
* Unordered lists
* Lists can be indented with two extra spaces.
* Lists can have [links like this one to Avoyd](https://www.avoyd.com/) and *emphasized text*
)";
Markdown( markdownText );
}
===============================================================================
*/
#include <stdint.h>
namespace ImGui
{
//-----------------------------------------------------------------------------
// Basic types
//-----------------------------------------------------------------------------
struct Link;
struct MarkdownConfig;
struct MarkdownLinkCallbackData // for both links and images
{
const char *text; // text between square brackets []
int textLength;
const char *link; // text between brackets ()
int linkLength;
void *userData;
bool isImage; // true if '!' is detected in front of the link syntax
};
struct MarkdownTooltipCallbackData // for tooltips
{
MarkdownLinkCallbackData linkData;
const char *linkIcon;
};
struct MarkdownImageData
{
bool isValid = false; // if true, will draw the image
bool useLinkCallback = false; // if true, linkCallback will be called when image is clicked
ImTextureID user_texture_id = 0; // see ImGui::Image
ImVec2 size = ImVec2(100.0f, 100.0f); // see ImGui::Image
ImVec2 uv0 = ImVec2(0, 0); // see ImGui::Image
ImVec2 uv1 = ImVec2(1, 1); // see ImGui::Image
ImVec4 tint_col = ImVec4(1, 1, 1, 1); // see ImGui::Image
ImVec4 border_col = ImVec4(0, 0, 0, 0); // see ImGui::Image
};
enum class MarkdownFormatType
{
NORMAL_TEXT,
HEADING,
UNORDERED_LIST,
LINK,
EMPHASIS,
};
struct MarkdownFormatInfo
{
MarkdownFormatType type = MarkdownFormatType::NORMAL_TEXT;
int32_t level = 0; // Set for headings: 1 for H1, 2 for H2 etc.
bool itemHovered = false; // Currently only set for links when mouse hovered, only valid when start_ == false
const MarkdownConfig *config = NULL;
};
typedef void MarkdownLinkCallback(MarkdownLinkCallbackData data);
typedef void MarkdownTooltipCallback(MarkdownTooltipCallbackData data);
inline void defaultMarkdownTooltipCallback(MarkdownTooltipCallbackData data_)
{
if (data_.linkData.isImage)
{
ImGui::SetTooltip("%.*s", data_.linkData.linkLength, data_.linkData.link);
}
else
{
ImGui::SetTooltip("%s Open in browser\n%.*s", data_.linkIcon, data_.linkData.linkLength, data_.linkData.link);
}
}
typedef MarkdownImageData MarkdownImageCallback(MarkdownLinkCallbackData data);
typedef void MarkdownFormalCallback(const MarkdownFormatInfo &markdownFormatInfo_, bool start_);
inline void defaultMarkdownFormatCallback(const MarkdownFormatInfo &markdownFormatInfo_, bool start_);
struct MarkdownHeadingFormat
{
ImFont *font; // ImGui font
bool separator; // if true, an underlined separator is drawn after the header
};
// Configuration struct for Markdown
// - linkCallback is called when a link is clicked on
// - linkIcon is a string which encode a "Link" icon, if available in the current font (e.g. linkIcon = ICON_FA_LINK with FontAwesome + IconFontCppHeaders https://github.com/juliettef/IconFontCppHeaders)
// - headingFormats controls the format of heading H1 to H3, those above H3 use H3 format
struct MarkdownConfig
{
static const int NUMHEADINGS = 3;
MarkdownLinkCallback *linkCallback = NULL;
MarkdownTooltipCallback *tooltipCallback = NULL;
MarkdownImageCallback *imageCallback = NULL;
const char *linkIcon = ""; // icon displayd in link tooltip
MarkdownHeadingFormat headingFormats[NUMHEADINGS] = {{NULL, true}, {NULL, true}, {NULL, true}};
void *userData = NULL;
MarkdownFormalCallback *formatCallback = defaultMarkdownFormatCallback;
};
//-----------------------------------------------------------------------------
// External interface
//-----------------------------------------------------------------------------
inline void Markdown(const char *markdown_, size_t markdownLength_, const MarkdownConfig &mdConfig_);
//-----------------------------------------------------------------------------
// Internals
//-----------------------------------------------------------------------------
struct TextRegion;
struct Line;
inline void UnderLine(ImColor col_);
inline void RenderLine(const char *markdown_, Line &line_, TextRegion &textRegion_, const MarkdownConfig &mdConfig_);
struct TextRegion
{
TextRegion() : indentX(0.0f)
{
}
~TextRegion()
{
ResetIndent();
}
// ImGui::TextWrapped will wrap at the starting position
// so to work around this we render using our own wrapping for the first line
void RenderTextWrapped(const char *text_, const char *text_end_, bool bIndentToHere_ = false)
{
float scale = ImGui::GetIO().FontGlobalScale;
float widthLeft = GetContentRegionAvail().x;
const char *endLine = ImGui::GetFont()->CalcWordWrapPositionA(scale, text_, text_end_, widthLeft);
ImGui::TextUnformatted(text_, endLine);
if (bIndentToHere_)
{
float indentNeeded = GetContentRegionAvail().x - widthLeft;
if (indentNeeded)
{
ImGui::Indent(indentNeeded);
indentX += indentNeeded;
}
}
widthLeft = GetContentRegionAvail().x;
while (endLine < text_end_)
{
text_ = endLine;
if (*text_ == ' ')
{
++text_;
} // skip a space at start of line
endLine = ImGui::GetFont()->CalcWordWrapPositionA(scale, text_, text_end_, widthLeft);
if (text_ == endLine)
{
endLine++;
}
ImGui::TextUnformatted(text_, endLine);
}
}
void RenderListTextWrapped(const char *text_, const char *text_end_)
{
ImGui::Bullet();
ImGui::SameLine();
RenderTextWrapped(text_, text_end_, true);
}
bool RenderLinkText(const char *text_, const char *text_end_, const Link &link_,
const char *markdown_, const MarkdownConfig &mdConfig_, const char **linkHoverStart_);
void RenderLinkTextWrapped(const char *text_, const char *text_end_, const Link &link_,
const char *markdown_, const MarkdownConfig &mdConfig_, const char **linkHoverStart_, bool bIndentToHere_ = false);
void ResetIndent()
{
if (indentX > 0.0f)
{
ImGui::Unindent(indentX);
}
indentX = 0.0f;
}
private:
float indentX;
};
// Text that starts after a new line (or at beginning) and ends with a newline (or at end)
struct Line
{
bool isHeading = false;
bool isEmphasis = false;
bool isUnorderedListStart = false;
bool isLeadingSpace = true; // spaces at start of line
int leadSpaceCount = 0;
int headingCount = 0;
int emphasisCount = 0;
int lineStart = 0;
int lineEnd = 0;
int lastRenderPosition = 0; // lines may get rendered in multiple pieces
};
struct TextBlock
{ // subset of line
int start = 0;
int stop = 0;
int size() const
{
return stop - start;
}
};
struct Link
{
enum LinkState
{
NO_LINK,
HAS_SQUARE_BRACKET_OPEN,
HAS_SQUARE_BRACKETS,
HAS_SQUARE_BRACKETS_ROUND_BRACKET_OPEN,
};
LinkState state = NO_LINK;
TextBlock text;
TextBlock url;
bool isImage = false;
int num_brackets_open = 0;
};
struct Emphasis
{
enum EmphasisState
{
NONE,
LEFT,
MIDDLE,
RIGHT,
};
EmphasisState state = NONE;
TextBlock text;
char sym;
};
inline void UnderLine(ImColor col_)
{
ImVec2 min = ImGui::GetItemRectMin();
ImVec2 max = ImGui::GetItemRectMax();
min.y = max.y;
ImGui::GetWindowDrawList()->AddLine(min, max, col_, 1.0f);
}
inline void RenderLine(const char *markdown_, Line &line_, TextRegion &textRegion_, const MarkdownConfig &mdConfig_)
{
// indent
int indentStart = 0;
if (line_.isUnorderedListStart) // ImGui unordered list render always adds one indent
{
indentStart = 1;
}
for (int j = indentStart; j < line_.leadSpaceCount / 2; ++j) // add indents
{
ImGui::Indent();
}
// render
MarkdownFormatInfo formatInfo;
formatInfo.config = &mdConfig_;
int textStart = line_.lastRenderPosition + 1;
int textSize = line_.lineEnd - textStart;
if (line_.isUnorderedListStart) // render unordered list
{
formatInfo.type = MarkdownFormatType::UNORDERED_LIST;
mdConfig_.formatCallback(formatInfo, true);
const char *text = markdown_ + textStart + 1;
textRegion_.RenderListTextWrapped(text, text + textSize - 1);
}
else if (line_.isHeading) // render heading
{
formatInfo.level = line_.headingCount;
formatInfo.type = MarkdownFormatType::HEADING;
mdConfig_.formatCallback(formatInfo, true);
const char *text = markdown_ + textStart + 1;
textRegion_.RenderTextWrapped(text, text + textSize - 1);
}
else if (line_.isEmphasis) // render emphasis
{
formatInfo.level = line_.emphasisCount;
formatInfo.type = MarkdownFormatType::EMPHASIS;
mdConfig_.formatCallback(formatInfo, true);
const char *text = markdown_ + textStart;
textRegion_.RenderTextWrapped(text, text + textSize);
}
else // render a normal paragraph chunk
{
formatInfo.type = MarkdownFormatType::NORMAL_TEXT;
mdConfig_.formatCallback(formatInfo, true);
const char *text = markdown_ + textStart;
textRegion_.RenderTextWrapped(text, text + textSize);
}
mdConfig_.formatCallback(formatInfo, false);
// unindent
for (int j = indentStart; j < line_.leadSpaceCount / 2; ++j)
{
ImGui::Unindent();
}
}
// render markdown
inline void Markdown(const char *markdown_, size_t markdownLength_, const MarkdownConfig &mdConfig_)
{
static const char *linkHoverStart = NULL; // we need to preserve status of link hovering between frames
// ImGuiStyle &style = ImGui::GetStyle();
Line line;
Link link;
Emphasis em = Emphasis();
TextRegion textRegion;
char c = 0;
for (int i = 0; i < (int)markdownLength_; ++i)
{
c = markdown_[i]; // get the character at index
if (c == 0)
{
break;
} // shouldn't happen but don't go beyond 0.
// If we're at the beginning of the line, count any spaces
if (line.isLeadingSpace)
{
if (c == ' ')
{
++line.leadSpaceCount;
continue;
}
else
{
line.isLeadingSpace = false;
line.lastRenderPosition = i - 1;
if ((c == '*') && (line.leadSpaceCount >= 2))
{
if (((int)markdownLength_ > i + 1) && (markdown_[i + 1] == ' ')) // space after '*'
{
line.isUnorderedListStart = true;
++i;
++line.lastRenderPosition;
}
// carry on processing as could be emphasis
}
else if (c == '#')
{
line.headingCount++;
bool bContinueChecking = true;
int j = i;
while (++j < (int)markdownLength_ && bContinueChecking)
{
c = markdown_[j];
switch (c)
{
case '#':
line.headingCount++;
break;
case ' ':
line.lastRenderPosition = j - 1;
i = j;
line.isHeading = true;
bContinueChecking = false;
break;
default:
line.isHeading = false;
bContinueChecking = false;
break;
}
}
if (line.isHeading)
{
// reset emphasis status, we do not support emphasis around headers for now
em = Emphasis();
continue;
}
}
}
}
// Test to see if we have a link
switch (link.state)
{
case Link::NO_LINK:
if (c == '[' && !line.isHeading) // we do not support headings with links for now
{
link.state = Link::HAS_SQUARE_BRACKET_OPEN;
link.text.start = i + 1;
if (i > 0 && markdown_[i - 1] == '!')
{
link.isImage = true;
}
}
break;
case Link::HAS_SQUARE_BRACKET_OPEN:
if (c == ']')
{
link.state = Link::HAS_SQUARE_BRACKETS;
link.text.stop = i;
}
break;
case Link::HAS_SQUARE_BRACKETS:
if (c == '(')
{
link.state = Link::HAS_SQUARE_BRACKETS_ROUND_BRACKET_OPEN;
link.url.start = i + 1;
link.num_brackets_open = 1;
}
break;
case Link::HAS_SQUARE_BRACKETS_ROUND_BRACKET_OPEN:
if (c == '(')
{
++link.num_brackets_open;
}
else if (c == ')')
{
--link.num_brackets_open;
}
if (link.num_brackets_open == 0)
{
// reset emphasis status, we do not support emphasis around links for now
em = Emphasis();
// render previous line content
line.lineEnd = link.text.start - (link.isImage ? 2 : 1);
RenderLine(markdown_, line, textRegion, mdConfig_);
line.leadSpaceCount = 0;
link.url.stop = i;
line.isUnorderedListStart = false; // the following text shouldn't have bullets
ImGui::SameLine(0.0f, 0.0f);
if (link.isImage) // it's an image, render it.
{
bool drawnImage = false;
bool useLinkCallback = false;
if (mdConfig_.imageCallback)
{
MarkdownImageData imageData = mdConfig_.imageCallback({markdown_ + link.text.start, link.text.size(), markdown_ + link.url.start, link.url.size(), mdConfig_.userData, true});
useLinkCallback = imageData.useLinkCallback;
if (imageData.isValid)
{
ImGui::Image(imageData.user_texture_id, imageData.size, imageData.uv0, imageData.uv1, imageData.tint_col, imageData.border_col);
drawnImage = true;
}
}
if (!drawnImage)
{
ImGui::Text("( Image %.*s not loaded )", link.url.size(), markdown_ + link.url.start);
}
if (ImGui::IsItemHovered())
{
if (ImGui::IsMouseReleased(0) && mdConfig_.linkCallback && useLinkCallback)
{
mdConfig_.linkCallback({markdown_ + link.text.start, link.text.size(), markdown_ + link.url.start, link.url.size(), mdConfig_.userData, true});
}
if (link.text.size() > 0 && mdConfig_.tooltipCallback)
{
mdConfig_.tooltipCallback({{markdown_ + link.text.start, link.text.size(), markdown_ + link.url.start, link.url.size(), mdConfig_.userData, true}, mdConfig_.linkIcon});
}
}
}
else // it's a link, render it.
{
textRegion.RenderLinkTextWrapped(markdown_ + link.text.start, markdown_ + link.text.start + link.text.size(), link, markdown_, mdConfig_, &linkHoverStart, false);
}
ImGui::SameLine(0.0f, 0.0f);
// reset the link by reinitializing it
link = Link();
line.lastRenderPosition = i;
break;
}
}
// Test to see if we have emphasis styling
switch (em.state)
{
case Emphasis::NONE:
if (link.state == Link::NO_LINK && !line.isHeading)
{
int next = i + 1;
int prev = i - 1;
if ((c == '*' || c == '_') && (i == line.lineStart || markdown_[prev] == ' ' || markdown_[prev] == '\t') // empasis must be preceded by whitespace or line start
&& (int)markdownLength_ > next // emphasis must precede non-whitespace
&& markdown_[next] != ' ' && markdown_[next] != '\n' && markdown_[next] != '\t')
{
em.state = Emphasis::LEFT;
em.sym = c;
em.text.start = i;
line.emphasisCount = 1;
continue;
}
}
break;
case Emphasis::LEFT:
if (em.sym == c)
{
++line.emphasisCount;
continue;
}
else
{
em.text.start = i;
em.state = Emphasis::MIDDLE;
}
break;
case Emphasis::MIDDLE:
if (em.sym == c)
{
em.state = Emphasis::RIGHT;
em.text.stop = i;
// pass through to case Emphasis::RIGHT
[[fallthrough]];
}
else
{
break;
}
case Emphasis::RIGHT:
if (em.sym == c)
{
if (line.emphasisCount < 3 && (i - em.text.stop + 1 == line.emphasisCount))
{
// render text up to emphasis
int lineEnd = em.text.start - line.emphasisCount;
if (lineEnd > line.lineStart)
{
line.lineEnd = lineEnd;
RenderLine(markdown_, line, textRegion, mdConfig_);
ImGui::SameLine(0.0f, 0.0f);
line.isUnorderedListStart = false;
line.leadSpaceCount = 0;
}
line.isEmphasis = true;
line.lastRenderPosition = em.text.start - 1;
line.lineStart = em.text.start;
line.lineEnd = em.text.stop;
RenderLine(markdown_, line, textRegion, mdConfig_);
ImGui::SameLine(0.0f, 0.0f);
line.isEmphasis = false;
line.lastRenderPosition = i;
em = Emphasis();
}
continue;
}
else
{
em.state = Emphasis::NONE;
// render text up to here
int start = em.text.start - line.emphasisCount;
if (start < line.lineStart)
{
line.lineEnd = line.lineStart;
line.lineStart = start;
line.lastRenderPosition = start - 1;
RenderLine(markdown_, line, textRegion, mdConfig_);
line.lineStart = line.lineEnd;
line.lastRenderPosition = line.lineStart - 1;
}
}
break;
}
// handle end of line (render)
if (c == '\n')
{
// first check if the line is a horizontal rule
line.lineEnd = i;
if (em.state == Emphasis::MIDDLE && line.emphasisCount >= 3 &&
(line.lineStart + line.emphasisCount) == i)
{
ImGui::Separator();
}
else
{
// render the line: multiline emphasis requires a complex implementation so not supporting
RenderLine(markdown_, line, textRegion, mdConfig_);
}
// reset the line and emphasis state
line = Line();
em = Emphasis();
line.lineStart = i + 1;
line.lastRenderPosition = i;
textRegion.ResetIndent();
// reset the link
link = Link();
}
}
if (em.state == Emphasis::LEFT && line.emphasisCount >= 3)
{
ImGui::Separator();
}
else
{
// render any remaining text if last char wasn't 0
if (markdownLength_ && line.lineStart < (int)markdownLength_ && markdown_[line.lineStart] != 0)
{
// handle both null terminated and non null terminated strings
line.lineEnd = (int)markdownLength_;
if (0 == markdown_[line.lineEnd - 1])
{
--line.lineEnd;
}
RenderLine(markdown_, line, textRegion, mdConfig_);
}
}
}
inline bool TextRegion::RenderLinkText(const char *text_, const char *text_end_, const Link &link_,
const char *markdown_, const MarkdownConfig &mdConfig_, const char **linkHoverStart_)
{
MarkdownFormatInfo formatInfo;
formatInfo.config = &mdConfig_;
formatInfo.type = MarkdownFormatType::LINK;
mdConfig_.formatCallback(formatInfo, true);
ImGui::PushTextWrapPos(-1.0f);
ImGui::TextUnformatted(text_, text_end_);
ImGui::PopTextWrapPos();
bool bThisItemHovered = ImGui::IsItemHovered();
if (bThisItemHovered)
{
*linkHoverStart_ = markdown_ + link_.text.start;
}
bool bHovered = bThisItemHovered || (*linkHoverStart_ == (markdown_ + link_.text.start));
formatInfo.itemHovered = bHovered;
mdConfig_.formatCallback(formatInfo, false);
if (bHovered)
{
if (ImGui::IsMouseReleased(0) && mdConfig_.linkCallback)
{
mdConfig_.linkCallback({markdown_ + link_.text.start, link_.text.size(), markdown_ + link_.url.start, link_.url.size(), mdConfig_.userData, false});
}
if (mdConfig_.tooltipCallback)
{
mdConfig_.tooltipCallback({{markdown_ + link_.text.start, link_.text.size(), markdown_ + link_.url.start, link_.url.size(), mdConfig_.userData, false}, mdConfig_.linkIcon});
}
}
return bThisItemHovered;
}
inline void TextRegion::RenderLinkTextWrapped(const char *text_, const char *text_end_, const Link &link_,
const char *markdown_, const MarkdownConfig &mdConfig_, const char **linkHoverStart_, bool bIndentToHere_)
{
float scale = ImGui::GetIO().FontGlobalScale;
float widthLeft = GetContentRegionAvail().x;
const char *endLine = ImGui::GetFont()->CalcWordWrapPositionA(scale, text_, text_end_, widthLeft);
bool bHovered = RenderLinkText(text_, endLine, link_, markdown_, mdConfig_, linkHoverStart_);
if (bIndentToHere_)
{
float indentNeeded = GetContentRegionAvail().x - widthLeft;
if (indentNeeded)
{
ImGui::Indent(indentNeeded);
indentX += indentNeeded;
}
}
widthLeft = GetContentRegionAvail().x;
while (endLine < text_end_)
{
text_ = endLine;
if (*text_ == ' ')
{
++text_;
} // skip a space at start of line
endLine = ImGui::GetFont()->CalcWordWrapPositionA(scale, text_, text_end_, widthLeft);
if (text_ == endLine)
{
endLine++;
}
bool bThisLineHovered = RenderLinkText(text_, endLine, link_, markdown_, mdConfig_, linkHoverStart_);
bHovered = bHovered || bThisLineHovered;
}
if (!bHovered && *linkHoverStart_ == markdown_ + link_.text.start)
{
*linkHoverStart_ = NULL;
}
}
inline void defaultMarkdownFormatCallback(const MarkdownFormatInfo &markdownFormatInfo_, bool start_)
{
switch (markdownFormatInfo_.type)
{
case MarkdownFormatType::NORMAL_TEXT:
break;
case MarkdownFormatType::EMPHASIS:
{
MarkdownHeadingFormat fmt;
// default styling for emphasis uses last headingFormats - for your own styling
// implement EMPHASIS in your formatCallback
if (markdownFormatInfo_.level == 1)
{
// normal emphasis
if (start_)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_TextDisabled]);
}
else
{
ImGui::PopStyleColor();
}
}
else
{
// strong emphasis
fmt = markdownFormatInfo_.config->headingFormats[MarkdownConfig::NUMHEADINGS - 1];
if (start_)
{
if (fmt.font)
{
ImGui::PushFont(fmt.font);
}
}
else
{
if (fmt.font)
{
ImGui::PopFont();
}
}
}
break;
}
case MarkdownFormatType::HEADING:
{
MarkdownHeadingFormat fmt;
if (markdownFormatInfo_.level > MarkdownConfig::NUMHEADINGS)
{
fmt = markdownFormatInfo_.config->headingFormats[MarkdownConfig::NUMHEADINGS - 1];
}
else
{
fmt = markdownFormatInfo_.config->headingFormats[markdownFormatInfo_.level - 1];
}
if (start_)
{
if (fmt.font)
{
ImGui::PushFont(fmt.font);
}
ImGui::Spacing();
}
else
{
if (fmt.separator)
{
ImGui::Separator();
// ImGui::Spacing();
}
else
{
// ImGui::Spacing();
}
if (fmt.font)
{
ImGui::PopFont();
}
}
break;
}
case MarkdownFormatType::UNORDERED_LIST:
break;
case MarkdownFormatType::LINK:
if (start_)
{
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.1, 0.4, 1, 1));
}
else
{
ImGui::PopStyleColor();
if (markdownFormatInfo_.itemHovered)
{
ImGui::UnderLine(ImVec4(0.3, 0.76, 1, 1));
}
else
{
ImGui::UnderLine(ImVec4(0.1, 0.4, 1, 1));
}
}
break;
}
}
}