mirror of
https://github.com/SatDump/SatDump
synced 2026-08-13 17:47:30 -04:00
1015 lines
36 KiB
C++
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:
|
|

|
|
|
|
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 
|
|
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;
|
|
}
|
|
}
|
|
|
|
}
|