iw3sp-mod/deps/imgui_markdown/imgui_markdown.h
2024-03-12 22:41:56 +03:00

1032 lines
38 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;
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
}
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;
}
// IsCharInsideWord based on ImGui's CalcWordWrapPositionA
inline bool IsCharInsideWord( char c_ )
{
return c_ != ' ' && c_ != '.' && c_ != ',' && c_ != ';' && c_ != '!' && c_ != '?' && c_ != '\"';
}
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 = text_;
if( widthLeft > 0.0f )
{
endLine = ImGui::GetFont()->CalcWordWrapPositionA( scale, text_, text_end_, widthLeft );
}
if( endLine > text_ && endLine < text_end_ )
{
if( IsCharInsideWord( *endLine ) )
{
// see if we can do a better cut.
float widthNextLine = GetContentRegionMax().x;
const char* endNextLine = ImGui::GetFont()->CalcWordWrapPositionA( scale, text_, text_end_, widthNextLine );
if( endNextLine == text_end_ || ( endNextLine <= text_end_ && !IsCharInsideWord( *endNextLine ) ) )
{
// can possibly do better if go to next line
endLine = text_;
}
}
}
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::NewLine();
}
else
{
if( fmt.separator )
{
ImGui::Separator();
ImGui::NewLine();
}
else
{
ImGui::NewLine();
}
if( fmt.font )
{
ImGui::PopFont();
}
}
break;
}
case MarkdownFormatType::UNORDERED_LIST:
break;
case MarkdownFormatType::LINK:
if( start_ )
{
ImGui::PushStyleColor( ImGuiCol_Text, ImGui::GetStyle().Colors[ ImGuiCol_ButtonHovered ] );
}
else
{
ImGui::PopStyleColor();
if( markdownFormatInfo_.itemHovered )
{
ImGui::UnderLine( ImGui::GetStyle().Colors[ ImGuiCol_ButtonHovered ] );
}
else
{
ImGui::UnderLine( ImGui::GetStyle().Colors[ ImGuiCol_Button ] );
}
}
break;
}
}
}