Grindstone Game Engine v0.2.0
An open source game engine and toolkit.
Loading...
Searching...
No Matches
imgui_markdown.h
1#pragma once
2
3// License: zlib
4// Copyright (c) 2019 Juliette Foucaut & Doug Binks
5//
6// This software is provided 'as-is', without any express or implied
7// warranty. In no event will the authors be held liable for any damages
8// arising from the use of this software.
9//
10// Permission is granted to anyone to use this software for any purpose,
11// including commercial applications, and to alter it and redistribute it
12// freely, subject to the following restrictions:
13//
14// 1. The origin of this software must not be misrepresented; you must not
15// claim that you wrote the original software. If you use this software
16// in a product, an acknowledgment in the product documentation would be
17// appreciated but is not required.
18// 2. Altered source versions must be plainly marked as such, and must not be
19// misrepresented as being the original software.
20// 3. This notice may not be removed or altered from any source distribution.
21
22/*
23API BREAKING CHANGES
24====================
25- 2020/04/22 - Added tooltipCallback parameter to ImGui::MarkdownConfig
26- 2019/02/01 - Changed LinkCallback parameters, see https://github.com/juliettef/imgui_markdown/issues/2
27- 2019/02/05 - Added imageCallback parameter to ImGui::MarkdownConfig
28- 2019/02/06 - Added useLinkCallback member variable to MarkdownImageData to configure using images as links
29*/
30
31/*
32imgui_markdown https://github.com/juliettef/imgui_markdown
33Markdown for Dear ImGui
34
35A permissively licensed markdown single-header library for https://github.com/ocornut/imgui
36
37Currently requires C++11 or above
38
39imgui_markdown currently supports the following markdown functionality:
40 - Wrapped text
41 - Headers H1, H2, H3
42 - Emphasis
43 - Indented text, multi levels
44 - Unordered lists and sub-lists
45 - Link
46 - Image
47 - Horizontal rule
48
49Syntax
50
51Wrapping:
52Text wraps automatically. To add a new line, use 'Return'.
53
54Headers:
55# H1
56## H2
57### H3
58
59Emphasis:
60*emphasis*
61_emphasis_
62**strong emphasis**
63__strong emphasis__
64
65Indents:
66On a new line, at the start of the line, add two spaces per indent.
67 Indent level 1
68 Indent level 2
69
70Unordered lists:
71On a new line, at the start of the line, add two spaces, an asterisks and a space.
72For nested lists, add two additional spaces in front of the asterisk per list level increment.
73 * Unordered List level 1
74 * Unordered List level 2
75
76Link:
77[link description](https://...)
78
79Image:
80![image alt text](image identifier e.g. filename)
81
82Horizontal Rule:
83***
84___
85
86===============================================================================
87
88// Example use on Windows with links opening in a browser
89
90#include "ImGui.h" // https://github.com/ocornut/imgui
91#include "imgui_markdown.h" // https://github.com/juliettef/imgui_markdown
92#include "IconsFontAwesome5.h" // https://github.com/juliettef/IconFontCppHeaders
93
94// Following includes for Windows LinkCallback
95#define WIN32_LEAN_AND_MEAN
96#include <Windows.h>
97#include "Shellapi.h"
98#include <string>
99
100void LinkCallback( ImGui::MarkdownLinkCallbackData data_ );
101inline ImGui::MarkdownImageData ImageCallback( ImGui::MarkdownLinkCallbackData data_ );
102
103static ImFont* H1 = NULL;
104static ImFont* H2 = NULL;
105static ImFont* H3 = NULL;
106
107static ImGui::MarkdownConfig mdConfig;
108
109
110void LinkCallback( ImGui::MarkdownLinkCallbackData data_ )
111{
112 std::string url( data_.link, data_.linkLength );
113 if( !data_.isImage )
114 {
115 ShellExecuteA( NULL, "open", url.c_str(), NULL, NULL, SW_SHOWNORMAL );
116 }
117}
118
119inline ImGui::MarkdownImageData ImageCallback( ImGui::MarkdownLinkCallbackData data_ )
120{
121 // In your application you would load an image based on data_ input. Here we just use the imgui font texture.
122 ImTextureID image = ImGui::GetIO().Fonts->TexID;
123 // > C++14 can use ImGui::MarkdownImageData imageData{ true, false, image, ImVec2( 40.0f, 20.0f ) };
124 ImGui::MarkdownImageData imageData;
125 imageData.isValid = true;
126 imageData.useLinkCallback = false;
127 imageData.user_texture_id = image;
128 imageData.size = ImVec2( 40.0f, 20.0f );
129
130 // For image resize when available size.x > image width, add
131 ImVec2 const contentSize = ImGui::GetContentRegionAvail();
132 if( imageData.size.x > contentSize.x )
133 {
134 float const ratio = imageData.size.y/imageData.size.x;
135 imageData.size.x = contentSize.x;
136 imageData.size.y = contentSize.x*ratio;
137 }
138
139 return imageData;
140}
141
142void LoadFonts( float fontSize_ = 12.0f )
143{
144 ImGuiIO& io = ImGui::GetIO();
145 io.Fonts->Clear();
146 // Base font
147 io.Fonts->AddFontFromFileTTF( "myfont.ttf", fontSize_ );
148 // Bold headings H2 and H3
149 H2 = io.Fonts->AddFontFromFileTTF( "myfont-bold.ttf", fontSize_ );
150 H3 = mdConfig.headingFormats[ 1 ].font;
151 // bold heading H1
152 float fontSizeH1 = fontSize_ * 1.1f;
153 H1 = io.Fonts->AddFontFromFileTTF( "myfont-bold.ttf", fontSizeH1 );
154}
155
156void ExampleMarkdownFormatCallback( const ImGui::MarkdownFormatInfo& markdownFormatInfo_, bool start_ )
157{
158 // Call the default first so any settings can be overwritten by our implementation.
159 // Alternatively could be called or not called in a switch statement on a case by case basis.
160 // See defaultMarkdownFormatCallback definition for furhter examples of how to use it.
161 ImGui::defaultMarkdownFormatCallback( markdownFormatInfo_, start_ );
162
163 switch( markdownFormatInfo_.type )
164 {
165 // example: change the colour of heading level 2
166 case ImGui::MarkdownFormatType::HEADING:
167 {
168 if( markdownFormatInfo_.level == 2 )
169 {
170 if( start_ )
171 {
172 ImGui::PushStyleColor( ImGuiCol_Text, ImGui::GetStyle().Colors[ ImGuiCol_TextDisabled ] );
173 }
174 else
175 {
176 ImGui::PopStyleColor();
177 }
178 }
179 break;
180 }
181 default:
182 {
183 break;
184 }
185 }
186}
187
188void Markdown( const std::string& markdown_ )
189{
190 // You can make your own Markdown function with your prefered string container and markdown config.
191 // > C++14 can use ImGui::MarkdownConfig mdConfig{ LinkCallback, NULL, ImageCallback, ICON_FA_LINK, { { H1, true }, { H2, true }, { H3, false } }, NULL };
192 mdConfig.linkCallback = LinkCallback;
193 mdConfig.tooltipCallback = NULL;
194 mdConfig.imageCallback = ImageCallback;
195 mdConfig.linkIcon = ICON_FA_LINK;
196 mdConfig.headingFormats[0] = { H1, true };
197 mdConfig.headingFormats[1] = { H2, true };
198 mdConfig.headingFormats[2] = { H3, false };
199 mdConfig.userData = NULL;
200 mdConfig.formatCallback = ExampleMarkdownFormatCallback;
201 ImGui::Markdown( markdown_.c_str(), markdown_.length(), mdConfig );
202}
203
204void MarkdownExample()
205{
206 const std::string markdownText = u8R"(
207# H1 Header: Text and Links
208You can add [links like this one to enkisoftware](https://www.enkisoftware.com/) and lines will wrap well.
209You can also insert images ![image alt text](image identifier e.g. filename)
210Horizontal rules:
211***
212___
213*Emphasis* and **strong emphasis** change the appearance of the text.
214## H2 Header: indented text.
215 This text has an indent (two leading spaces).
216 This one has two.
217### H3 Header: Lists
218 * Unordered lists
219 * Lists can be indented with two extra spaces.
220 * Lists can have [links like this one to Avoyd](https://www.avoyd.com/) and *emphasized text*
221)";
222 Markdown( markdownText );
223}
224
225===============================================================================
226*/
227
228
229#include <stdint.h>
230
231namespace ImGui
232{
233 //-----------------------------------------------------------------------------
234 // Basic types
235 //-----------------------------------------------------------------------------
236
237 struct Link;
238 struct MarkdownConfig;
239
240 struct MarkdownLinkCallbackData // for both links and images
241 {
242 const char* text; // text between square brackets []
243 int textLength;
244 const char* link; // text between brackets ()
245 int linkLength;
246 void* userData;
247 bool isImage; // true if '!' is detected in front of the link syntax
248 };
249
250 struct MarkdownTooltipCallbackData // for tooltips
251 {
253 const char* linkIcon;
254 };
255
257 {
258 bool isValid = false; // if true, will draw the image
259 bool useLinkCallback = false; // if true, linkCallback will be called when image is clicked
260 ImTextureID user_texture_id = 0; // see ImGui::Image
261 ImVec2 size = ImVec2( 100.0f, 100.0f ); // see ImGui::Image
262 ImVec2 uv0 = ImVec2( 0, 0 ); // see ImGui::Image
263 ImVec2 uv1 = ImVec2( 1, 1 ); // see ImGui::Image
264 ImVec4 tint_col = ImVec4( 1, 1, 1, 1 ); // see ImGui::Image
265 ImVec4 border_col = ImVec4( 0, 0, 0, 0 ); // see ImGui::Image
266 ImVec4 bg_col = ImVec4( 0, 0, 0, 0 ); // see ImGui::Image
267 };
268
269 enum class MarkdownFormatType
270 {
271 NORMAL_TEXT,
272 HEADING,
273 UNORDERED_LIST,
274 LINK,
275 EMPHASIS,
276 };
277
279 {
280 MarkdownFormatType type = MarkdownFormatType::NORMAL_TEXT;
281 int32_t level = 0; // Set for headings: 1 for H1, 2 for H2 etc.
282 bool itemHovered = false; // Currently only set for links when mouse hovered, only valid when start_ == false
283 const MarkdownConfig* config = NULL;
284 const char* text = NULL;
285 int32_t textLength = 0;
286 };
287
288 typedef void MarkdownLinkCallback( MarkdownLinkCallbackData data );
289 typedef void MarkdownTooltipCallback( MarkdownTooltipCallbackData data );
290
291 inline void defaultMarkdownTooltipCallback( MarkdownTooltipCallbackData data_ )
292 {
293 if( data_.linkData.isImage )
294 {
295 ImGui::SetTooltip( "%.*s", data_.linkData.linkLength, data_.linkData.link );
296 }
297 else
298 {
299 ImGui::SetTooltip( "%s Open in browser\n%.*s", data_.linkIcon, data_.linkData.linkLength, data_.linkData.link );
300 }
301 }
302
303 typedef MarkdownImageData MarkdownImageCallback( MarkdownLinkCallbackData data );
304 typedef void MarkdownFormalCallback( const MarkdownFormatInfo& markdownFormatInfo_, bool start_ );
305
306 inline void defaultMarkdownFormatCallback( const MarkdownFormatInfo& markdownFormatInfo_, bool start_ );
307
309 {
310 ImFont* font; // ImGui font
311 bool separator; // if true, an underlined separator is drawn after the header
312 #ifdef IMGUI_HAS_TEXTURES // used to detect dynamic font capability: https://github.com/ocornut/imgui/issues/8465#issuecomment-2701570771
313 float fontSize = 0.0f; // Font size if using dynamic fonts
314 #endif
315 };
316
317 // Configuration struct for Markdown
318 // - linkCallback is called when a link is clicked on
319 // - 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)
320 // - headingFormats controls the format of heading H1 to H3, those above H3 use H3 format
322 {
323 static const int NUMHEADINGS = 3;
324
325 MarkdownLinkCallback* linkCallback = NULL;
326 MarkdownTooltipCallback* tooltipCallback = NULL;
327 MarkdownImageCallback* imageCallback = NULL;
328 const char* linkIcon = ""; // icon displayd in link tooltip
329 MarkdownHeadingFormat headingFormats[ NUMHEADINGS ] = { { NULL, true }, { NULL, true }, { NULL, true } };
330 void* userData = NULL;
331 MarkdownFormalCallback* formatCallback = defaultMarkdownFormatCallback;
332 };
333
334 //-----------------------------------------------------------------------------
335 // External interface
336 //-----------------------------------------------------------------------------
337
338 inline void Markdown( const char* markdown_, size_t markdownLength_, const MarkdownConfig& mdConfig_ );
339
340 //-----------------------------------------------------------------------------
341 // Internals
342 //-----------------------------------------------------------------------------
343
344 struct TextRegion;
345 struct Line;
346 inline void UnderLine( ImColor col_ );
347 inline void RenderLine( const char* markdown_, Line& line_, TextRegion& textRegion_, const MarkdownConfig& mdConfig_ );
348
349 struct TextRegion
350 {
351 TextRegion() : indentX( 0.0f )
352 {
353 }
354 ~TextRegion()
355 {
356 ResetIndent();
357 }
358
359 // ImGui::TextWrapped will wrap at the starting position
360 // so to work around this we render using our own wrapping for the first line
361 void RenderTextWrapped( const char* text_, const char* text_end_, bool bIndentToHere_ = false )
362 {
363 #if IMGUI_VERSION_NUM >= 19197
364 float fontSize = ImGui::GetFontSize();
365 #else
366 float scale = ImGui::GetIO().FontGlobalScale;
367 #endif
368 float widthLeft = GetContentRegionAvail().x;
369 #if IMGUI_VERSION_NUM >= 19197
370 const char* endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text_, text_end_, widthLeft );
371 #else
372 const char* endLine = ImGui::GetFont()->CalcWordWrapPositionA( scale, text_, text_end_, widthLeft );
373 #endif
374 ImGui::TextUnformatted( text_, endLine );
375 if( bIndentToHere_ )
376 {
377 float indentNeeded = GetContentRegionAvail().x - widthLeft;
378 if( indentNeeded )
379 {
380 ImGui::Indent( indentNeeded );
381 indentX += indentNeeded;
382 }
383 }
384 widthLeft = GetContentRegionAvail().x;
385 while( endLine < text_end_ )
386 {
387 text_ = endLine;
388 if( *text_ == ' ' ) { ++text_; } // skip a space at start of line
389 #if IMGUI_VERSION_NUM >= 19197
390 endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text_, text_end_, widthLeft );
391 #else
392 endLine = ImGui::GetFont()->CalcWordWrapPositionA( scale, text_, text_end_, widthLeft );
393 #endif
394 if( text_ == endLine )
395 {
396 endLine++;
397 }
398 ImGui::TextUnformatted( text_, endLine );
399 }
400 }
401
402 void RenderListTextWrapped( const char* text_, const char* text_end_ )
403 {
404 ImGui::Bullet();
405 ImGui::SameLine();
406 RenderTextWrapped( text_, text_end_, true );
407 }
408
409 bool RenderLinkText( const char* text_, const char* text_end_, const Link& link_,
410 const char* markdown_, const MarkdownConfig& mdConfig_, const char** linkHoverStart_ );
411
412 void RenderLinkTextWrapped( const char* text_, const char* text_end_, const Link& link_,
413 const char* markdown_, const MarkdownConfig& mdConfig_, const char** linkHoverStart_, bool bIndentToHere_ = false );
414
415 void ResetIndent()
416 {
417 if( indentX > 0.0f )
418 {
419 ImGui::Unindent( indentX );
420 }
421 indentX = 0.0f;
422 }
423
424 private:
425 float indentX;
426 };
427
428 // Text that starts after a new line (or at beginning) and ends with a newline (or at end)
429 struct Line {
430 bool isHeading = false;
431 bool isEmphasis = false;
432 bool isUnorderedListStart = false;
433 bool isLeadingSpace = true; // spaces at start of line
434 int leadSpaceCount = 0;
435 int headingCount = 0;
436 int emphasisCount = 0;
437 int lineStart = 0;
438 int lineEnd = 0;
439 int lastRenderPosition = 0; // lines may get rendered in multiple pieces
440 };
441
442 struct TextBlock { // subset of line
443 int start = 0;
444 int stop = 0;
445 int size() const
446 {
447 return stop - start;
448 }
449 };
450
451 struct Link {
452 enum LinkState {
453 NO_LINK,
454 HAS_SQUARE_BRACKET_OPEN,
455 HAS_SQUARE_BRACKETS,
456 HAS_SQUARE_BRACKETS_ROUND_BRACKET_OPEN,
457 };
458 LinkState state = NO_LINK;
459 TextBlock text;
460 TextBlock url;
461 bool isImage = false;
462 int num_brackets_open = 0;
463 };
464
465 struct Emphasis {
466 enum EmphasisState {
467 NONE,
468 LEFT,
469 MIDDLE,
470 RIGHT,
471 };
472 EmphasisState state = NONE;
473 TextBlock text;
474 char sym;
475 };
476
477 inline void UnderLine( ImColor col_ )
478 {
479 ImVec2 min = ImGui::GetItemRectMin();
480 ImVec2 max = ImGui::GetItemRectMax();
481 min.y = max.y;
482 ImGui::GetWindowDrawList()->AddLine( min, max, col_, 1.0f );
483 }
484
485 inline void RenderLine( const char* markdown_, Line& line_, TextRegion& textRegion_, const MarkdownConfig& mdConfig_ )
486 {
487 // indent
488 int indentStart = 0;
489 if( line_.isUnorderedListStart ) // ImGui unordered list render always adds one indent
490 {
491 indentStart = 1;
492 }
493 for( int j = indentStart; j < line_.leadSpaceCount / 2; ++j ) // add indents
494 {
495 ImGui::Indent();
496 }
497
498 // render
499 MarkdownFormatInfo formatInfo;
500 formatInfo.config = &mdConfig_;
501 int textStart = line_.lastRenderPosition + 1;
502 int textSize = line_.lineEnd - textStart;
503 if( line_.isUnorderedListStart ) // render unordered list
504 {
505 formatInfo.type = MarkdownFormatType::UNORDERED_LIST;
506 mdConfig_.formatCallback( formatInfo, true );
507 const char* text = markdown_ + textStart + 1;
508 textRegion_.RenderListTextWrapped( text, text + textSize - 1 );
509 }
510 else if( line_.isHeading ) // render heading
511 {
512 formatInfo.level = line_.headingCount;
513 formatInfo.type = MarkdownFormatType::HEADING;
514 const char* text = markdown_ + textStart + 1;
515 formatInfo.text = text;
516 formatInfo.textLength = textSize - 1;
517 mdConfig_.formatCallback( formatInfo, true );
518 textRegion_.RenderTextWrapped( text, text + textSize - 1 );
519 }
520 else if( line_.isEmphasis ) // render emphasis
521 {
522 formatInfo.level = line_.emphasisCount;
523 formatInfo.type = MarkdownFormatType::EMPHASIS;
524 mdConfig_.formatCallback(formatInfo, true);
525 const char* text = markdown_ + textStart;
526 textRegion_.RenderTextWrapped(text, text + textSize);
527 }
528 else // render a normal paragraph chunk
529 {
530 formatInfo.type = MarkdownFormatType::NORMAL_TEXT;
531 mdConfig_.formatCallback( formatInfo, true );
532 const char* text = markdown_ + textStart;
533 textRegion_.RenderTextWrapped( text, text + textSize );
534 }
535 mdConfig_.formatCallback( formatInfo, false );
536
537 // unindent
538 for( int j = indentStart; j < line_.leadSpaceCount / 2; ++j )
539 {
540 ImGui::Unindent();
541 }
542 }
543
544 // render markdown
545 inline void Markdown( const char* markdown_, size_t markdownLength_, const MarkdownConfig& mdConfig_ )
546 {
547 static const char* linkHoverStart = NULL; // we need to preserve status of link hovering between frames
548 ImGuiStyle& style = ImGui::GetStyle();
549 Line line;
550 Link link;
551 Emphasis em;
552 TextRegion textRegion;
553
554 char c = 0;
555 for( int i=0; i < (int)markdownLength_; ++i )
556 {
557 c = markdown_[i]; // get the character at index
558 if( c == 0 ) { break; } // shouldn't happen but don't go beyond 0.
559
560 // If we're at the beginning of the line, count any spaces
561 if( line.isLeadingSpace )
562 {
563 if( c == ' ' )
564 {
565 ++line.leadSpaceCount;
566 continue;
567 }
568 else
569 {
570 line.isLeadingSpace = false;
571 line.lastRenderPosition = i - 1;
572 if(( c == '*' ) && ( line.leadSpaceCount >= 2 ))
573 {
574 if( ( (int)markdownLength_ > i + 1 ) && ( markdown_[ i + 1 ] == ' ' ) ) // space after '*'
575 {
576 line.isUnorderedListStart = true;
577 ++i;
578 ++line.lastRenderPosition;
579 }
580 // carry on processing as could be emphasis
581 }
582 else if( c == '#' )
583 {
584 line.headingCount++;
585 bool bContinueChecking = true;
586 int j = i;
587 while( ++j < (int)markdownLength_ && bContinueChecking )
588 {
589 c = markdown_[j];
590 switch( c )
591 {
592 case '#':
593 line.headingCount++;
594 break;
595 case ' ':
596 line.lastRenderPosition = j - 1;
597 i = j;
598 line.isHeading = true;
599 bContinueChecking = false;
600 break;
601 default:
602 line.isHeading = false;
603 bContinueChecking = false;
604 break;
605 }
606 }
607 if( line.isHeading )
608 {
609 // reset emphasis status, we do not support emphasis around headers for now
610 em = Emphasis();
611 continue;
612 }
613 }
614 }
615 }
616
617 // Test to see if we have a link
618 switch( link.state )
619 {
620 case Link::NO_LINK:
621 if( c == '[' && !line.isHeading ) // we do not support headings with links for now
622 {
623 link.state = Link::HAS_SQUARE_BRACKET_OPEN;
624 link.text.start = i + 1;
625 if( i > 0 && markdown_[i - 1] == '!' )
626 {
627 link.isImage = true;
628 }
629 }
630 break;
631 case Link::HAS_SQUARE_BRACKET_OPEN:
632 if( c == ']' )
633 {
634 link.state = Link::HAS_SQUARE_BRACKETS;
635 link.text.stop = i;
636 }
637 break;
638 case Link::HAS_SQUARE_BRACKETS:
639 if( c == '(' )
640 {
641 link.state = Link::HAS_SQUARE_BRACKETS_ROUND_BRACKET_OPEN;
642 link.url.start = i + 1;
643 link.num_brackets_open = 1;
644 }
645 break;
646 case Link::HAS_SQUARE_BRACKETS_ROUND_BRACKET_OPEN:
647 if( c == '(' )
648 {
649 ++link.num_brackets_open;
650 }
651 else if( c == ')' )
652 {
653 --link.num_brackets_open;
654 }
655 if( link.num_brackets_open == 0 )
656 {
657 // reset emphasis status, we do not support emphasis around links for now
658 em = Emphasis();
659 // render previous line content
660 line.lineEnd = link.text.start - ( link.isImage ? 2 : 1 );
661 RenderLine( markdown_, line, textRegion, mdConfig_ );
662 line.leadSpaceCount = 0;
663 link.url.stop = i;
664 line.isUnorderedListStart = false; // the following text shouldn't have bullets
665 ImGui::SameLine( 0.0f, 0.0f );
666 if( link.isImage ) // it's an image, render it.
667 {
668 bool drawnImage = false;
669 bool useLinkCallback = false;
670 if( mdConfig_.imageCallback )
671 {
672 MarkdownImageData imageData = mdConfig_.imageCallback( { markdown_ + link.text.start, link.text.size(), markdown_ + link.url.start, link.url.size(), mdConfig_.userData, true } );
673 useLinkCallback = imageData.useLinkCallback;
674 if( imageData.isValid )
675 {
676#if IMGUI_VERSION_NUM < 19185
677 if( imageData.bg_col.w > 0.0f )
678 {
679 ImVec2 p = ImGui::GetCursorScreenPos();
680 ImGui::GetWindowDrawList()->AddRectFilled( p, ImVec2( p.x + imageData.size.x, p.y + imageData.size.y ), ImGui::GetColorU32( imageData.bg_col ));
681 }
682 ImGui::Image( imageData.user_texture_id, imageData.size, imageData.uv0, imageData.uv1, imageData.tint_col, imageData.border_col );
683#else
684 ImGui::PushStyleColor( ImGuiCol_Border, imageData.border_col );
685 ImGui::ImageWithBg( imageData.user_texture_id, imageData.size, imageData.uv0, imageData.uv1, imageData.bg_col, imageData.tint_col );
686 ImGui::PopStyleColor();
687#endif
688 drawnImage = true;
689 }
690 }
691 if( !drawnImage )
692 {
693 ImGui::Text( "( Image %.*s not loaded )", link.url.size(), markdown_ + link.url.start );
694 }
695 if( ImGui::IsItemHovered() )
696 {
697 if( ImGui::IsMouseReleased( 0 ) && mdConfig_.linkCallback && useLinkCallback )
698 {
699 mdConfig_.linkCallback( { markdown_ + link.text.start, link.text.size(), markdown_ + link.url.start, link.url.size(), mdConfig_.userData, true } );
700 }
701 if( link.text.size() > 0 && mdConfig_.tooltipCallback )
702 {
703 mdConfig_.tooltipCallback( { { markdown_ + link.text.start, link.text.size(), markdown_ + link.url.start, link.url.size(), mdConfig_.userData, true }, mdConfig_.linkIcon } );
704 }
705 }
706 }
707 else // it's a link, render it.
708 {
709 textRegion.RenderLinkTextWrapped( markdown_ + link.text.start, markdown_ + link.text.start + link.text.size(), link, markdown_, mdConfig_, &linkHoverStart, false );
710 }
711 ImGui::SameLine( 0.0f, 0.0f );
712 // reset the link by reinitializing it
713 link = Link();
714 line.lastRenderPosition = i;
715 break;
716 }
717 }
718
719 // Test to see if we have emphasis styling
720 switch( em.state )
721 {
722 case Emphasis::NONE:
723 if( link.state == Link::NO_LINK && !line.isHeading )
724 {
725 int next = i + 1;
726 int prev = i - 1;
727 if( ( c == '*' || c == '_' )
728 && ( i == line.lineStart
729 || markdown_[ prev ] == ' '
730 || markdown_[ prev ] == '\t' ) // empasis must be preceded by whitespace or line start
731 && (int)markdownLength_ > next // emphasis must precede non-whitespace
732 && markdown_[ next ] != ' '
733 && markdown_[ next ] != '\n'
734 && markdown_[ next ] != '\t' )
735 {
736 em.state = Emphasis::LEFT;
737 em.sym = c;
738 em.text.start = i;
739 line.emphasisCount = 1;
740 continue;
741 }
742 }
743 break;
744 case Emphasis::LEFT:
745 if( em.sym == c )
746 {
747 ++line.emphasisCount;
748 continue;
749 }
750 else
751 {
752 em.text.start = i;
753 em.state = Emphasis::MIDDLE;
754 }
755 break;
756 case Emphasis::MIDDLE:
757 if( em.sym == c )
758 {
759 em.state = Emphasis::RIGHT;
760 em.text.stop = i;
761 // pass through to case Emphasis::RIGHT
762 }
763 else
764 {
765 break;
766 }
767 #if __cplusplus >= 201703L
768 [[fallthrough]];
769 #endif
770 case Emphasis::RIGHT:
771 if( em.sym == c )
772 {
773 if( line.emphasisCount < 3 && ( i - em.text.stop + 1 == line.emphasisCount ) )
774 {
775 // render text up to emphasis
776 int lineEnd = em.text.start - line.emphasisCount;
777 if( lineEnd > line.lineStart )
778 {
779 line.lineEnd = lineEnd;
780 RenderLine( markdown_, line, textRegion, mdConfig_ );
781 ImGui::SameLine( 0.0f, 0.0f );
782 line.isUnorderedListStart = false;
783 line.leadSpaceCount = 0;
784 }
785 line.isEmphasis = true;
786 line.lastRenderPosition = em.text.start - 1;
787 line.lineStart = em.text.start;
788 line.lineEnd = em.text.stop;
789 RenderLine( markdown_, line, textRegion, mdConfig_ );
790 ImGui::SameLine( 0.0f, 0.0f );
791 line.isEmphasis = false;
792 line.lastRenderPosition = i;
793 em = Emphasis();
794 }
795 continue;
796 }
797 else
798 {
799 em.state = Emphasis::NONE;
800 // render text up to here
801 int start = em.text.start - line.emphasisCount;
802 if( start < line.lineStart )
803 {
804 line.lineEnd = line.lineStart;
805 line.lineStart = start;
806 line.lastRenderPosition = start - 1;
807 RenderLine(markdown_, line, textRegion, mdConfig_);
808 line.lineStart = line.lineEnd;
809 line.lastRenderPosition = line.lineStart - 1;
810 }
811 }
812 break;
813 }
814
815 // handle end of line (render)
816 if( c == '\n' )
817 {
818 // first check if the line is a horizontal rule
819 line.lineEnd = i;
820 if( em.state == Emphasis::MIDDLE && line.emphasisCount >=3 &&
821 ( line.lineStart + line.emphasisCount ) == i )
822 {
823 ImGui::Separator();
824 }
825 else
826 {
827 // render the line: multiline emphasis requires a complex implementation so not supporting
828 RenderLine( markdown_, line, textRegion, mdConfig_ );
829 }
830
831 // reset the line and emphasis state
832 line = Line();
833 em = Emphasis();
834
835 line.lineStart = i + 1;
836 line.lastRenderPosition = i;
837
838 textRegion.ResetIndent();
839
840 // reset the link
841 link = Link();
842 }
843 }
844
845 if( em.state == Emphasis::LEFT && line.emphasisCount >= 3 )
846 {
847 ImGui::Separator();
848 }
849 else
850 {
851 // render any remaining text if last char wasn't 0
852 if( markdownLength_ && line.lineStart < (int)markdownLength_ && markdown_[ line.lineStart ] != 0 )
853 {
854 // handle both null terminated and non null terminated strings
855 line.lineEnd = (int)markdownLength_;
856 if( 0 == markdown_[ line.lineEnd - 1 ] )
857 {
858 --line.lineEnd;
859 }
860 RenderLine( markdown_, line, textRegion, mdConfig_ );
861 }
862 }
863 }
864
865 inline bool TextRegion::RenderLinkText( const char* text_, const char* text_end_, const Link& link_,
866 const char* markdown_, const MarkdownConfig& mdConfig_, const char** linkHoverStart_ )
867 {
868 MarkdownFormatInfo formatInfo;
869 formatInfo.config = &mdConfig_;
870 formatInfo.type = MarkdownFormatType::LINK;
871 mdConfig_.formatCallback( formatInfo, true );
872 ImGui::PushTextWrapPos( -1.0f );
873 ImGui::TextUnformatted( text_, text_end_ );
874 ImGui::PopTextWrapPos();
875
876 bool bThisItemHovered = ImGui::IsItemHovered();
877 if(bThisItemHovered)
878 {
879 *linkHoverStart_ = markdown_ + link_.text.start;
880 }
881 bool bHovered = bThisItemHovered || ( *linkHoverStart_ == ( markdown_ + link_.text.start ) );
882
883 formatInfo.itemHovered = bHovered;
884 mdConfig_.formatCallback( formatInfo, false );
885
886 if(bHovered)
887 {
888 if( ImGui::IsMouseReleased( 0 ) && mdConfig_.linkCallback )
889 {
890 mdConfig_.linkCallback( { markdown_ + link_.text.start, link_.text.size(), markdown_ + link_.url.start, link_.url.size(), mdConfig_.userData, false } );
891 }
892 if( mdConfig_.tooltipCallback )
893 {
894 mdConfig_.tooltipCallback( { { markdown_ + link_.text.start, link_.text.size(), markdown_ + link_.url.start, link_.url.size(), mdConfig_.userData, false }, mdConfig_.linkIcon } );
895 }
896 }
897 return bThisItemHovered;
898 }
899
900 // IsCharInsideWord based on ImGui's CalcWordWrapPositionA
901 inline bool IsCharInsideWord( char c_ )
902 {
903 return c_ != ' ' && c_ != '.' && c_ != ',' && c_ != ';' && c_ != '!' && c_ != '?' && c_ != '\"';
904 }
905
906 inline void TextRegion::RenderLinkTextWrapped( const char* text_, const char* text_end_, const Link& link_,
907 const char* markdown_, const MarkdownConfig& mdConfig_, const char** linkHoverStart_, bool bIndentToHere_ )
908 {
909 #if IMGUI_VERSION_NUM >= 19197
910 float fontSize = ImGui::GetFontSize();
911 #else
912 float scale = ImGui::GetIO().FontGlobalScale;
913 #endif
914 float widthLeft = GetContentRegionAvail().x;
915 const char* endLine = text_;
916 if( widthLeft > 0.0f )
917 {
918 #if IMGUI_VERSION_NUM >= 19197
919 endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text_, text_end_, widthLeft );
920 #else
921 endLine = ImGui::GetFont()->CalcWordWrapPositionA( scale, text_, text_end_, widthLeft );
922 #endif
923 }
924
925 if( endLine > text_ && endLine < text_end_ )
926 {
927 if( IsCharInsideWord( *endLine ) )
928 {
929 // see if we can do a better cut.
930 float widthNextLine = widthLeft + GetCursorScreenPos().x - GetWindowPos().x; // was GetContentRegionMax().x on IMGUI_VERSION_NUM < 19099
931 #if IMGUI_VERSION_NUM >= 19197
932 const char* endNextLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text_, text_end_, widthNextLine );
933 #else
934 const char* endNextLine = ImGui::GetFont()->CalcWordWrapPositionA( scale, text_, text_end_, widthLeft );
935 #endif
936 if( endNextLine == text_end_ || ( endNextLine <= text_end_ && !IsCharInsideWord( *endNextLine ) ) )
937 {
938 // can possibly do better if go to next line
939 endLine = text_;
940 }
941 }
942 }
943 bool bHovered = RenderLinkText( text_, endLine, link_, markdown_, mdConfig_, linkHoverStart_ );
944 if( bIndentToHere_ )
945 {
946 float indentNeeded = GetContentRegionAvail().x - widthLeft;
947 if( indentNeeded )
948 {
949 ImGui::Indent( indentNeeded );
950 indentX += indentNeeded;
951 }
952 }
953 widthLeft = GetContentRegionAvail().x;
954 while( endLine < text_end_ )
955 {
956 text_ = endLine;
957 if( *text_ == ' ' ) { ++text_; } // skip a space at start of line
958 #if IMGUI_VERSION_NUM >= 19197
959 endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text_, text_end_, widthLeft );
960 #else
961 endLine = ImGui::GetFont()->CalcWordWrapPositionA( scale, text_, text_end_, widthLeft );
962 #endif
963 if( text_ == endLine )
964 {
965 endLine++;
966 }
967 bool bThisLineHovered = RenderLinkText( text_, endLine, link_, markdown_, mdConfig_, linkHoverStart_ );
968 bHovered = bHovered || bThisLineHovered;
969 }
970 if( !bHovered && *linkHoverStart_ == markdown_ + link_.text.start )
971 {
972 *linkHoverStart_ = NULL;
973 }
974 }
975
976
977 inline void defaultMarkdownFormatCallback( const MarkdownFormatInfo& markdownFormatInfo_, bool start_ )
978 {
979 switch( markdownFormatInfo_.type )
980 {
981 case MarkdownFormatType::NORMAL_TEXT:
982 break;
983 case MarkdownFormatType::EMPHASIS:
984 {
986 // default styling for emphasis uses last headingFormats - for your own styling
987 // implement EMPHASIS in your formatCallback
988 if( markdownFormatInfo_.level == 1 )
989 {
990 // normal emphasis
991 if( start_ )
992 {
993 ImGui::PushStyleColor( ImGuiCol_Text, ImGui::GetStyle().Colors[ ImGuiCol_TextDisabled ] );
994 }
995 else
996 {
997 ImGui::PopStyleColor();
998 }
999 }
1000 else
1001 {
1002 // strong emphasis
1003 fmt = markdownFormatInfo_.config->headingFormats[ MarkdownConfig::NUMHEADINGS - 1 ];
1004 if( start_ )
1005 {
1006 if( fmt.font )
1007 {
1008 #ifdef IMGUI_HAS_TEXTURES // used to detect dynamic font capability:
1009 ImGui::PushFont( fmt.font, 0.0f ); // Change font and keep current size
1010 #else
1011 ImGui::PushFont( fmt.font );
1012 #endif
1013 }
1014 }
1015 else
1016 {
1017 if( fmt.font )
1018 {
1019 ImGui::PopFont();
1020 }
1021 }
1022 }
1023 break;
1024 }
1025 case MarkdownFormatType::HEADING:
1026 {
1028 if( markdownFormatInfo_.level > MarkdownConfig::NUMHEADINGS )
1029 {
1030 fmt = markdownFormatInfo_.config->headingFormats[ MarkdownConfig::NUMHEADINGS - 1 ];
1031 }
1032 else
1033 {
1034 fmt = markdownFormatInfo_.config->headingFormats[ markdownFormatInfo_.level - 1 ];
1035 }
1036 if( start_ )
1037 {
1038 if( fmt.font )
1039 {
1040 #ifdef IMGUI_HAS_TEXTURES // used to detect dynamic font capability: https://github.com/ocornut/imgui/issues/8465#issuecomment-2701570771
1041 ImGui::PushFont( fmt.font, fmt.fontSize > 0.0f ? fmt.fontSize : fmt.font->LegacySize );
1042 #else
1043 ImGui::PushFont( fmt.font );
1044 #endif
1045 }
1046 ImGui::NewLine();
1047 }
1048 else
1049 {
1050 if( fmt.separator )
1051 {
1052 ImGui::Separator();
1053 ImGui::NewLine();
1054 }
1055 else
1056 {
1057 ImGui::NewLine();
1058 }
1059 if( fmt.font )
1060 {
1061 ImGui::PopFont();
1062 }
1063 }
1064 break;
1065 }
1066 case MarkdownFormatType::UNORDERED_LIST:
1067 break;
1068 case MarkdownFormatType::LINK:
1069 if( start_ )
1070 {
1071 ImGui::PushStyleColor( ImGuiCol_Text, ImGui::GetStyle().Colors[ ImGuiCol_ButtonHovered ] );
1072 }
1073 else
1074 {
1075 ImGui::PopStyleColor();
1076 if( markdownFormatInfo_.itemHovered )
1077 {
1078 ImGui::UnderLine( ImGui::GetStyle().Colors[ ImGuiCol_ButtonHovered ] );
1079 }
1080 else
1081 {
1082 ImGui::UnderLine( ImGui::GetStyle().Colors[ ImGuiCol_Button ] );
1083 }
1084 }
1085 break;
1086 }
1087 }
1088
1089}
Definition imgui_markdown.h:465
Definition imgui_markdown.h:429
Definition imgui_markdown.h:322
Definition imgui_markdown.h:279
Definition imgui_markdown.h:309
Definition imgui_markdown.h:257
Definition imgui_markdown.h:241
Definition imgui_markdown.h:251
Definition imgui_markdown.h:442
Definition imgui_markdown.h:350