マニュアル

守矢諏訪子 <suwako@076.moe>、2026-06-29 03:37
コミット 68086290fee0c87c2f09597c8c24e795cda7e995 ・親 6ca9eff ・ファイルを見る

3個のファイル、+1496 -23

@@ -17,6 +17,7 @@
1717 target_include_directories(imgui PUBLIC
1818 "${CMAKE_CURRENT_SOURCE_DIR}/imgui"
1919 "${CMAKE_CURRENT_SOURCE_DIR}/ImGuiColorTextEdit"
20+ "${CMAKE_CURRENT_SOURCE_DIR}/ImGuiMarkdown"
2021 )
2122
2223 target_link_libraries(imgui PUBLIC glfw glad)
@@ -0,0 +1,1177 @@
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+/*
23+API 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+/*
32+imgui_markdown https://github.com/juliettef/imgui_markdown
33+Markdown for Dear ImGui
34+
35+A permissively licensed markdown single-header library for https://github.com/ocornut/imgui
36+
37+Currently requires C++11 or above
38+
39+imgui_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+
49+Syntax
50+
51+Wrapping:
52+Text wraps automatically. To add a new line, use 'Return'.
53+
54+Headers:
55+# H1
56+## H2
57+### H3
58+
59+Emphasis:
60+*emphasis*
61+_emphasis_
62+**strong emphasis**
63+__strong emphasis__
64+
65+Indents:
66+On a new line, at the start of the line, add two spaces per indent.
67+ Indent level 1
68+ Indent level 2
69+
70+Unordered lists:
71+On a new line, at the start of the line, add two spaces, an asterisks and a space.
72+For 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+
76+Link:
77+[link description](https://...)
78+
79+Image:
80+![image alt text](image identifier e.g. filename)
81+
82+Horizontal 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+
100+void LinkCallback( ImGui::MarkdownLinkCallbackData data_ );
101+inline ImGui::MarkdownImageData ImageCallback( ImGui::MarkdownLinkCallbackData data_ );
102+
103+static ImFont* H1 = NULL;
104+static ImFont* H2 = NULL;
105+static ImFont* H3 = NULL;
106+
107+static ImGui::MarkdownConfig mdConfig;
108+
109+
110+void 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+
119+inline 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+
142+void 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+
156+void 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+
188+void 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+
204+void MarkdownExample()
205+{
206+ const std::string markdownText = u8R"(
207+# H1 Header: Text and Links
208+You can add [links like this one to enkisoftware](https://www.enkisoftware.com/) and lines will wrap well.
209+You can also insert images ![image alt text](image identifier e.g. filename)
210+Horizontal 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+#include <stdint.h>
229+
230+typedef int ImGuiMarkdownFormatFlags;
231+
232+enum ImGuiMarkdownFormatFlags_
233+{
234+ ImGuiMarkdownFormatFlags_None = 0,
235+ ImGuiMarkdownFormatFlags_DiscardExtraNewLines = 1 << 0, // (Accurate parsing) Provided markdown will discard all redundant newlines
236+ ImGuiMarkdownFormatFlags_NoNewLineBeforeHeading = 1 << 1, // (Accurate parsing) Provided markdown will not format a newline after the first line if it is a heading
237+ ImGuiMarkdownFormatFlags_SeparatorDoesNotAdvance = 1 << 2, // (Accurate parsing) Provided markdown will not advance to the next line after formatting a separator
238+ ImGuiMarkdownFormatFlags_GithubStyle = ImGuiMarkdownFormatFlags_DiscardExtraNewLines | ImGuiMarkdownFormatFlags_NoNewLineBeforeHeading | ImGuiMarkdownFormatFlags_SeparatorDoesNotAdvance,
239+ ImGuiMarkdownFormatFlags_CommonMarkAll = ImGuiMarkdownFormatFlags_DiscardExtraNewLines | ImGuiMarkdownFormatFlags_NoNewLineBeforeHeading | ImGuiMarkdownFormatFlags_SeparatorDoesNotAdvance,
240+};
241+
242+namespace ImGui
243+{
244+ //-----------------------------------------------------------------------------
245+ // Basic types
246+ //-----------------------------------------------------------------------------
247+
248+ struct Link;
249+ struct MarkdownConfig;
250+
251+ struct MarkdownLinkCallbackData // for both links and images
252+ {
253+ const char* text; // text between square brackets []
254+ int textLength;
255+ const char* link; // text between brackets ()
256+ int linkLength;
257+ void* userData;
258+ bool isImage; // true if '!' is detected in front of the link syntax
259+ };
260+
261+ struct MarkdownTooltipCallbackData // for tooltips
262+ {
263+ MarkdownLinkCallbackData linkData;
264+ const char* linkIcon;
265+ };
266+
267+ struct MarkdownImageData
268+ {
269+ bool isValid = false; // if true, will draw the image
270+ bool useLinkCallback = false; // if true, linkCallback will be called when image is clicked
271+ ImTextureID user_texture_id = {}; // see ImGui::Image
272+ ImVec2 size = ImVec2( 100.0f, 100.0f ); // see ImGui::Image
273+ ImVec2 uv0 = ImVec2( 0, 0 ); // see ImGui::Image
274+ ImVec2 uv1 = ImVec2( 1, 1 ); // see ImGui::Image
275+ ImVec4 tint_col = ImVec4( 1, 1, 1, 1 ); // see ImGui::Image
276+ ImVec4 border_col = ImVec4( 0, 0, 0, 0 ); // see ImGui::Image
277+ ImVec4 bg_col = ImVec4( 0, 0, 0, 0 ); // see ImGui::Image
278+ };
279+
280+ enum class MarkdownFormatType
281+ {
282+ NORMAL_TEXT,
283+ HEADING,
284+ UNORDERED_LIST,
285+ LINK,
286+ EMPHASIS,
287+ };
288+
289+ struct MarkdownFormatInfo
290+ {
291+ MarkdownFormatType type = MarkdownFormatType::NORMAL_TEXT;
292+ int32_t level = 0; // Set for headings: 1 for H1, 2 for H2 etc.
293+ bool itemHovered = false; // Currently only set for links when mouse hovered, only valid when start_ == false
294+ const MarkdownConfig* config = NULL;
295+ const char* text = NULL;
296+ int32_t textLength = 0;
297+ };
298+
299+ typedef void MarkdownLinkCallback( MarkdownLinkCallbackData data );
300+ typedef void MarkdownTooltipCallback( MarkdownTooltipCallbackData data );
301+
302+ inline void defaultMarkdownTooltipCallback( MarkdownTooltipCallbackData data_ )
303+ {
304+ if( data_.linkData.isImage )
305+ {
306+ ImGui::SetTooltip( "%.*s", data_.linkData.linkLength, data_.linkData.link );
307+ }
308+ else
309+ {
310+ ImGui::SetTooltip( "%s Open in browser\n%.*s", data_.linkIcon, data_.linkData.linkLength, data_.linkData.link );
311+ }
312+ }
313+
314+ typedef MarkdownImageData MarkdownImageCallback( MarkdownLinkCallbackData data );
315+ typedef void MarkdownFormalCallback( const MarkdownFormatInfo& markdownFormatInfo_, bool start_ );
316+
317+ inline void defaultMarkdownFormatCallback( const MarkdownFormatInfo& markdownFormatInfo_, bool start_ );
318+
319+ struct MarkdownHeadingFormat
320+ {
321+ ImFont* font; // ImGui font
322+ bool separator; // if true, an underlined separator is drawn after the header
323+ #ifdef IMGUI_HAS_TEXTURES // used to detect dynamic font capability: https://github.com/ocornut/imgui/issues/8465#issuecomment-2701570771
324+ float fontSize = 0.0f; // Font size if using dynamic fonts
325+ #endif
326+ };
327+
328+ // Configuration struct for Markdown
329+ // - linkCallback is called when a link is clicked on
330+ // - 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)
331+ // - headingFormats controls the format of heading H1 to H3, those above H3 use H3 format
332+ struct MarkdownConfig
333+ {
334+ static const int NUMHEADINGS = 3;
335+
336+ MarkdownLinkCallback* linkCallback = NULL;
337+ MarkdownTooltipCallback* tooltipCallback = NULL;
338+ MarkdownImageCallback* imageCallback = NULL;
339+ const char* linkIcon = ""; // icon displayd in link tooltip
340+ MarkdownHeadingFormat headingFormats[ NUMHEADINGS ] = { { NULL, true }, { NULL, true }, { NULL, true } };
341+ void* userData = NULL;
342+ MarkdownFormalCallback* formatCallback = defaultMarkdownFormatCallback;
343+ ImGuiMarkdownFormatFlags formatFlags = ImGuiMarkdownFormatFlags_None; // Configure this to change how Markdown gets formatted. By default imgui_markdown uses psuedo-Markdown for backwards compatibility.
344+ };
345+
346+ //-----------------------------------------------------------------------------
347+ // External interface
348+ //-----------------------------------------------------------------------------
349+
350+ inline void Markdown( const char* markdown_, size_t markdownLength_, const MarkdownConfig& mdConfig_ );
351+
352+ //-----------------------------------------------------------------------------
353+ // Internals
354+ //-----------------------------------------------------------------------------
355+
356+ struct TextRegion;
357+ struct Line;
358+ inline void UnderLine( ImColor col_ );
359+ inline void RenderLine( const char* markdown_, Line& line_, TextRegion& textRegion_, const MarkdownConfig& mdConfig_ );
360+
361+ struct TextRegion
362+ {
363+ TextRegion() : indentX( 0.0f )
364+ {
365+ }
366+ ~TextRegion()
367+ {
368+ ResetIndent();
369+ }
370+
371+ void RenderTextWrapped( const char* text_, const char* text_end_, bool bIndentToHere_ = false );
372+
373+ void RenderListTextWrapped( const char* text_, const char* text_end_ )
374+ {
375+ ImGui::Bullet();
376+ ImGui::SameLine();
377+ RenderTextWrapped( text_, text_end_, true );
378+ }
379+
380+ bool RenderLinkText( const char* text_, const char* text_end_, const Link& link_,
381+ const char* markdown_, const MarkdownConfig& mdConfig_, const char** linkHoverStart_ );
382+
383+ void RenderLinkTextWrapped( const char* text_, const char* text_end_, const Link& link_,
384+ const char* markdown_, const MarkdownConfig& mdConfig_, const char** linkHoverStart_, bool bIndentToHere_ = false );
385+
386+ void ResetIndent()
387+ {
388+ if( indentX > 0.0f )
389+ {
390+ ImGui::Unindent( indentX );
391+ }
392+ indentX = 0.0f;
393+ }
394+
395+ private:
396+ float indentX;
397+ };
398+
399+ // Text that starts after a new line (or at beginning) and ends with a newline (or at end)
400+ struct Line {
401+ bool isHeading = false;
402+ bool isEmphasis = false;
403+ bool isUnorderedListStart = false;
404+ bool isLeadingSpace = true; // spaces at start of line
405+ int leadSpaceCount = 0;
406+ int headingCount = 0;
407+ int emphasisCount = 0;
408+ int lineStart = 0;
409+ int lineEnd = 0;
410+ int lastRenderPosition = 0; // lines may get rendered in multiple pieces
411+ };
412+
413+ struct TextBlock { // subset of line
414+ int start = 0;
415+ int stop = 0;
416+ int size() const
417+ {
418+ return stop - start;
419+ }
420+ };
421+
422+ struct Link {
423+ enum LinkState {
424+ NO_LINK,
425+ HAS_SQUARE_BRACKET_OPEN,
426+ HAS_SQUARE_BRACKETS,
427+ HAS_SQUARE_BRACKETS_ROUND_BRACKET_OPEN,
428+ };
429+ LinkState state = NO_LINK;
430+ TextBlock text;
431+ TextBlock url;
432+ bool isImage = false;
433+ int num_brackets_open = 0;
434+ };
435+
436+ struct Emphasis {
437+ enum EmphasisState {
438+ NONE,
439+ LEFT,
440+ MIDDLE,
441+ RIGHT,
442+ };
443+ EmphasisState state = NONE;
444+ TextBlock text;
445+ char sym;
446+ };
447+
448+ inline void UnderLine( ImColor col_ )
449+ {
450+ ImVec2 min = ImGui::GetItemRectMin();
451+ ImVec2 max = ImGui::GetItemRectMax();
452+ min.y = max.y;
453+ ImGui::GetWindowDrawList()->AddLine( min, max, col_, 1.0f );
454+ }
455+
456+ inline void RenderLine( const char* markdown_, Line& line_, TextRegion& textRegion_, const MarkdownConfig& mdConfig_ )
457+ {
458+ // indent
459+ int indentStart = 0;
460+ if( line_.isUnorderedListStart ) // ImGui unordered list render always adds one indent
461+ {
462+ indentStart = 1;
463+ }
464+ for( int j = indentStart; j < line_.leadSpaceCount / 2; ++j ) // add indents
465+ {
466+ ImGui::Indent();
467+ }
468+
469+ // render
470+ MarkdownFormatInfo formatInfo;
471+ formatInfo.config = &mdConfig_;
472+ int textStart = line_.lastRenderPosition + 1;
473+ int textSize = line_.lineEnd - textStart;
474+ if( line_.isUnorderedListStart ) // render unordered list
475+ {
476+ formatInfo.type = MarkdownFormatType::UNORDERED_LIST;
477+ mdConfig_.formatCallback( formatInfo, true );
478+ const char* text = markdown_ + textStart + 1;
479+ textRegion_.RenderListTextWrapped( text, text + textSize - 1 );
480+ }
481+ else if( line_.isHeading ) // render heading
482+ {
483+ formatInfo.level = line_.headingCount;
484+ formatInfo.type = MarkdownFormatType::HEADING;
485+ const char* text = markdown_ + textStart + 1;
486+ formatInfo.text = text;
487+ formatInfo.textLength = textSize - 1;
488+ mdConfig_.formatCallback( formatInfo, true );
489+ textRegion_.RenderTextWrapped( text, text + textSize - 1 );
490+ }
491+ else if( line_.isEmphasis ) // render emphasis
492+ {
493+ formatInfo.level = line_.emphasisCount;
494+ formatInfo.type = MarkdownFormatType::EMPHASIS;
495+ mdConfig_.formatCallback(formatInfo, true);
496+ const char* text = markdown_ + textStart;
497+ textRegion_.RenderTextWrapped(text, text + textSize);
498+ }
499+ else // render a normal paragraph chunk
500+ {
501+ formatInfo.type = MarkdownFormatType::NORMAL_TEXT;
502+ mdConfig_.formatCallback( formatInfo, true );
503+ const char* text = markdown_ + textStart;
504+ textRegion_.RenderTextWrapped( text, text + textSize );
505+ }
506+ mdConfig_.formatCallback( formatInfo, false );
507+
508+ // unindent
509+ for( int j = indentStart; j < line_.leadSpaceCount / 2; ++j )
510+ {
511+ ImGui::Unindent();
512+ }
513+ }
514+
515+ // render markdown
516+ inline void Markdown( const char* markdown_, size_t markdownLength_, const MarkdownConfig& mdConfig_ )
517+ {
518+ static const char* s_linkHoverStart = NULL; // we need to preserve status of link hovering between frames
519+ static ImGuiID s_linkHoverID = 0;
520+ const char* linkHoverStart = NULL;
521+ ImGuiID linkHoverID = ImGui::GetID("MDLHS");
522+ if( linkHoverID == s_linkHoverID )
523+ {
524+ linkHoverStart = s_linkHoverStart;
525+ }
526+
527+ ImGuiStyle& style = ImGui::GetStyle();
528+ Line line;
529+ Line prevLine;
530+ Link link;
531+ Emphasis em;
532+ TextRegion textRegion;
533+ int concurrentEmptyNewlines = 0;
534+ bool appliedExtraNewline = false;
535+
536+ char c = 0;
537+ for( int i=0; i < (int)markdownLength_; ++i )
538+ {
539+ c = markdown_[i]; // get the character at index
540+ if( c == 0 ) { break; } // shouldn't happen but don't go beyond 0.
541+
542+ // If we're at the beginning of the line, count any spaces
543+ if( line.isLeadingSpace )
544+ {
545+ if ( (mdConfig_.formatFlags & ImGuiMarkdownFormatFlags_DiscardExtraNewLines) ) // Discard LF and CRLF newlines by markdown spec
546+ {
547+ if ( c == '\n' )
548+ {
549+ concurrentEmptyNewlines++;
550+ line.lineStart += 1;
551+ continue;
552+ }
553+ else if ( ( c == '\r' ) && ( (int)markdownLength_ > i + 1 ) && ( markdown_[i + 1] == '\n' ) )
554+ {
555+ concurrentEmptyNewlines++;
556+ line.lineStart += 2;
557+ i += 1;
558+ continue;
559+ }
560+ }
561+
562+ if( c == ' ' )
563+ {
564+ ++line.leadSpaceCount;
565+ continue;
566+ }
567+ else
568+ {
569+ line.isLeadingSpace = false;
570+ line.lastRenderPosition = i - 1;
571+ if(( c == '*' ) && ( line.leadSpaceCount >= 2 ))
572+ {
573+ if( ( (int)markdownLength_ > i + 1 ) && ( markdown_[ i + 1 ] == ' ' ) ) // space after '*'
574+ {
575+ line.isUnorderedListStart = true;
576+ ++i;
577+ ++line.lastRenderPosition;
578+ }
579+ // carry on processing as could be emphasis
580+ }
581+ else if( c == '#' )
582+ {
583+ line.headingCount++;
584+ bool bContinueChecking = true;
585+ int j = i;
586+ while( ++j < (int)markdownLength_ && bContinueChecking )
587+ {
588+ c = markdown_[j];
589+ switch( c )
590+ {
591+ case '#':
592+ line.headingCount++;
593+ break;
594+ case ' ':
595+ line.lastRenderPosition = j - 1;
596+ i = j;
597+ line.isHeading = true;
598+ bContinueChecking = false;
599+ break;
600+ default:
601+ line.isHeading = false;
602+ bContinueChecking = false;
603+ break;
604+ }
605+ }
606+ if( line.isHeading )
607+ {
608+ // reset emphasis status, we do not support emphasis around headers for now
609+ em = Emphasis();
610+ continue;
611+ }
612+ }
613+ }
614+ }
615+
616+ if ( (mdConfig_.formatFlags & ImGuiMarkdownFormatFlags_DiscardExtraNewLines) )
617+ {
618+ // In markdown spec, 2 or more consecutive newlines gets converted to a single blank
619+ // line. The first newline is always digested by this parser so we check for 1 or more here.
620+ if (!appliedExtraNewline && !prevLine.isHeading && concurrentEmptyNewlines >= 1) {
621+ ImGui::NewLine();
622+ appliedExtraNewline = true;
623+ }
624+ }
625+
626+ // Test to see if we have a link
627+ switch( link.state )
628+ {
629+ case Link::NO_LINK:
630+ if( c == '[' && !line.isHeading ) // we do not support headings with links for now
631+ {
632+ link.state = Link::HAS_SQUARE_BRACKET_OPEN;
633+ link.text.start = i + 1;
634+ if( i > 0 && markdown_[i - 1] == '!' )
635+ {
636+ link.isImage = true;
637+ }
638+ }
639+ break;
640+ case Link::HAS_SQUARE_BRACKET_OPEN:
641+ if( c == ']' )
642+ {
643+ link.state = Link::HAS_SQUARE_BRACKETS;
644+ link.text.stop = i;
645+ }
646+ break;
647+ case Link::HAS_SQUARE_BRACKETS:
648+ if( c == '(' )
649+ {
650+ link.state = Link::HAS_SQUARE_BRACKETS_ROUND_BRACKET_OPEN;
651+ link.url.start = i + 1;
652+ link.num_brackets_open = 1;
653+ }
654+ break;
655+ case Link::HAS_SQUARE_BRACKETS_ROUND_BRACKET_OPEN:
656+ if( c == '(' )
657+ {
658+ ++link.num_brackets_open;
659+ }
660+ else if( c == ')' )
661+ {
662+ --link.num_brackets_open;
663+ }
664+ if( link.num_brackets_open == 0 )
665+ {
666+ // reset emphasis status, we do not support emphasis around links for now
667+ em = Emphasis();
668+ // render previous line content
669+ line.lineEnd = link.text.start - ( link.isImage ? 2 : 1 );
670+ RenderLine( markdown_, line, textRegion, mdConfig_ );
671+ line.leadSpaceCount = 0;
672+ link.url.stop = i;
673+ line.isUnorderedListStart = false; // the following text shouldn't have bullets
674+ ImGui::SameLine( 0.0f, 0.0f );
675+ if( link.isImage ) // it's an image, render it.
676+ {
677+ bool drawnImage = false;
678+ bool useLinkCallback = false;
679+ if( mdConfig_.imageCallback )
680+ {
681+ MarkdownImageData imageData = mdConfig_.imageCallback( { markdown_ + link.text.start, link.text.size(), markdown_ + link.url.start, link.url.size(), mdConfig_.userData, true } );
682+ useLinkCallback = imageData.useLinkCallback;
683+ if( imageData.isValid )
684+ {
685+#if IMGUI_VERSION_NUM < 19185
686+ if( imageData.bg_col.w > 0.0f )
687+ {
688+ ImVec2 p = ImGui::GetCursorScreenPos();
689+ ImGui::GetWindowDrawList()->AddRectFilled( p, ImVec2( p.x + imageData.size.x, p.y + imageData.size.y ), ImGui::GetColorU32( imageData.bg_col ));
690+ }
691+ ImGui::Image( imageData.user_texture_id, imageData.size, imageData.uv0, imageData.uv1, imageData.tint_col, imageData.border_col );
692+#else
693+ ImGui::PushStyleColor( ImGuiCol_Border, imageData.border_col );
694+ ImGui::ImageWithBg( imageData.user_texture_id, imageData.size, imageData.uv0, imageData.uv1, imageData.bg_col, imageData.tint_col );
695+ ImGui::PopStyleColor();
696+#endif
697+ drawnImage = true;
698+ }
699+ }
700+ if( !drawnImage )
701+ {
702+ ImGui::Text( "( Image %.*s not loaded )", link.url.size(), markdown_ + link.url.start );
703+ }
704+ if( ImGui::IsItemHovered() )
705+ {
706+ if( ImGui::IsMouseReleased( 0 ) && mdConfig_.linkCallback && useLinkCallback )
707+ {
708+ mdConfig_.linkCallback( { markdown_ + link.text.start, link.text.size(), markdown_ + link.url.start, link.url.size(), mdConfig_.userData, true } );
709+ }
710+ if( link.text.size() > 0 && mdConfig_.tooltipCallback )
711+ {
712+ mdConfig_.tooltipCallback( { { markdown_ + link.text.start, link.text.size(), markdown_ + link.url.start, link.url.size(), mdConfig_.userData, true }, mdConfig_.linkIcon } );
713+ }
714+ }
715+ }
716+ else // it's a link, render it.
717+ {
718+ textRegion.RenderLinkTextWrapped( markdown_ + link.text.start, markdown_ + link.text.start + link.text.size(), link, markdown_, mdConfig_, &linkHoverStart, false );
719+ }
720+ ImGui::SameLine( 0.0f, 0.0f );
721+ // reset the link by reinitializing it
722+ link = Link();
723+ line.lastRenderPosition = i;
724+ break;
725+ }
726+ }
727+
728+ // Test to see if we have emphasis styling
729+ switch( em.state )
730+ {
731+ case Emphasis::NONE:
732+ if( link.state == Link::NO_LINK && !line.isHeading )
733+ {
734+ int next = i + 1;
735+ int prev = i - 1;
736+ if( ( c == '*' || c == '_' )
737+ && ( i == line.lineStart
738+ || markdown_[ prev ] == ' '
739+ || markdown_[ prev ] == '\t' ) // empasis must be preceded by whitespace or line start
740+ && (int)markdownLength_ > next // emphasis must precede non-whitespace
741+ && markdown_[ next ] != ' '
742+ && markdown_[ next ] != '\n'
743+ && markdown_[ next ] != '\t' )
744+ {
745+ em.state = Emphasis::LEFT;
746+ em.sym = c;
747+ em.text.start = i;
748+ line.emphasisCount = 1;
749+ continue;
750+ }
751+ }
752+ break;
753+ case Emphasis::LEFT:
754+ if( em.sym == c )
755+ {
756+ ++line.emphasisCount;
757+ continue;
758+ }
759+ else
760+ {
761+ em.text.start = i;
762+ em.state = Emphasis::MIDDLE;
763+ }
764+ break;
765+ case Emphasis::MIDDLE:
766+ if( em.sym == c )
767+ {
768+ em.state = Emphasis::RIGHT;
769+ em.text.stop = i;
770+ // pass through to case Emphasis::RIGHT
771+ }
772+ else
773+ {
774+ break;
775+ }
776+ #if __cplusplus >= 201703L
777+ [[fallthrough]];
778+ #endif
779+ case Emphasis::RIGHT:
780+ if( em.sym == c )
781+ {
782+ if( line.emphasisCount < 3 && ( i - em.text.stop + 1 == line.emphasisCount ) )
783+ {
784+ // render text up to emphasis
785+ int lineEnd = em.text.start - line.emphasisCount;
786+ if( lineEnd > line.lineStart )
787+ {
788+ line.lineEnd = lineEnd;
789+ RenderLine( markdown_, line, textRegion, mdConfig_ );
790+ ImGui::SameLine( 0.0f, 0.0f );
791+ line.isUnorderedListStart = false;
792+ line.leadSpaceCount = 0;
793+ }
794+ line.isEmphasis = true;
795+ line.lastRenderPosition = em.text.start - 1;
796+ line.lineStart = em.text.start;
797+ line.lineEnd = em.text.stop;
798+ RenderLine( markdown_, line, textRegion, mdConfig_ );
799+ ImGui::SameLine( 0.0f, 0.0f );
800+ line.isEmphasis = false;
801+ line.lastRenderPosition = i;
802+ em = Emphasis();
803+ }
804+ continue;
805+ }
806+ else
807+ {
808+ em.state = Emphasis::NONE;
809+ // render text up to here
810+ int start = em.text.start - line.emphasisCount;
811+ if( start < line.lineStart )
812+ {
813+ line.lineEnd = line.lineStart;
814+ line.lineStart = start;
815+ line.lastRenderPosition = start - 1;
816+ RenderLine( markdown_, line, textRegion, mdConfig_ );
817+ line.lineStart = line.lineEnd;
818+ line.lastRenderPosition = line.lineStart - 1;
819+ }
820+ }
821+ break;
822+ }
823+
824+ // handle end of line (render)
825+ if( c == '\n' )
826+ {
827+ // first check if the line is a horizontal rule
828+ line.lineEnd = i;
829+ if( em.state == Emphasis::MIDDLE && line.emphasisCount >=3 &&
830+ ( line.lineStart + line.emphasisCount ) == i )
831+ {
832+ ImGui::Separator();
833+ }
834+ else
835+ {
836+ // render the line: multiline emphasis requires a complex implementation so not supporting
837+ RenderLine( markdown_, line, textRegion, mdConfig_ );
838+ }
839+
840+ // reset the line and emphasis state
841+ prevLine = line;
842+ line = Line();
843+ em = Emphasis();
844+
845+ line.lineStart = i + 1;
846+ line.lastRenderPosition = i;
847+
848+ textRegion.ResetIndent();
849+
850+ // reset the link
851+ link = Link();
852+
853+ concurrentEmptyNewlines = 0;
854+ appliedExtraNewline = false;
855+ }
856+ }
857+
858+ if( em.state == Emphasis::LEFT && line.emphasisCount >= 3 )
859+ {
860+ ImGui::Separator();
861+ }
862+ else
863+ {
864+ // render any remaining text if last char wasn't 0
865+ if( markdownLength_ && line.lineStart < (int)markdownLength_ && markdown_[ line.lineStart ] != 0 )
866+ {
867+ // handle both null terminated and non null terminated strings
868+ line.lineEnd = (int)markdownLength_;
869+ if( 0 == markdown_[ line.lineEnd - 1 ] )
870+ {
871+ --line.lineEnd;
872+ }
873+ RenderLine( markdown_, line, textRegion, mdConfig_ );
874+ }
875+ }
876+
877+ if( NULL != linkHoverStart || linkHoverID == s_linkHoverID )
878+ {
879+ s_linkHoverStart = linkHoverStart;
880+ s_linkHoverID = linkHoverID;
881+ }
882+ }
883+
884+ inline bool TextRegion::RenderLinkText( const char* text_, const char* text_end_, const Link& link_,
885+ const char* markdown_, const MarkdownConfig& mdConfig_, const char** linkHoverStart_ )
886+ {
887+ MarkdownFormatInfo formatInfo;
888+ formatInfo.config = &mdConfig_;
889+ formatInfo.type = MarkdownFormatType::LINK;
890+ mdConfig_.formatCallback( formatInfo, true );
891+ ImGui::PushTextWrapPos( -1.0f );
892+ ImGui::TextUnformatted( text_, text_end_ );
893+ ImGui::PopTextWrapPos();
894+
895+ bool bThisItemHovered = ImGui::IsItemHovered();
896+ if(bThisItemHovered)
897+ {
898+ *linkHoverStart_ = markdown_ + link_.text.start;
899+ }
900+ bool bHovered = bThisItemHovered || ( *linkHoverStart_ == ( markdown_ + link_.text.start ) );
901+
902+ formatInfo.itemHovered = bHovered;
903+ mdConfig_.formatCallback( formatInfo, false );
904+
905+ if(bHovered)
906+ {
907+ if( ImGui::IsMouseReleased( 0 ) && mdConfig_.linkCallback )
908+ {
909+ mdConfig_.linkCallback( { markdown_ + link_.text.start, link_.text.size(), markdown_ + link_.url.start, link_.url.size(), mdConfig_.userData, false } );
910+ }
911+ if( mdConfig_.tooltipCallback )
912+ {
913+ mdConfig_.tooltipCallback( { { markdown_ + link_.text.start, link_.text.size(), markdown_ + link_.url.start, link_.url.size(), mdConfig_.userData, false }, mdConfig_.linkIcon } );
914+ }
915+ }
916+ return bThisItemHovered;
917+ }
918+
919+ // IsCharInsideWord based on ImGui's CalcWordWrapPositionA
920+ inline bool IsCharInsideWord( char c_ )
921+ {
922+ return c_ != ' ' && c_ != '.' && c_ != ',' && c_ != ';' && c_ != '!' && c_ != '?' && c_ != '\"';
923+ }
924+
925+ // ImGui::TextWrapped will wrap at the starting position
926+ // so to work around this we render using our own wrapping for the first line
927+ inline void TextRegion::RenderTextWrapped( const char* text_, const char* text_end_, bool bIndentToHere_ )
928+ {
929+ #if IMGUI_VERSION_NUM >= 19197
930+ float fontSize = ImGui::GetFontSize();
931+ #else
932+ float scale = ImGui::GetIO().FontGlobalScale;
933+ #endif
934+ float widthLeft = GetContentRegionAvail().x;
935+ const char* endLine = text_;
936+ if( widthLeft > 0.0f )
937+ {
938+ #if IMGUI_VERSION_NUM >= 19197
939+ endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text_, text_end_, widthLeft );
940+ #else
941+ endLine = ImGui::GetFont()->CalcWordWrapPositionA( scale, text_, text_end_, widthLeft );
942+ #endif
943+ }
944+
945+ if( endLine > text_ && endLine < text_end_ )
946+ {
947+ if( IsCharInsideWord( *endLine ) )
948+ {
949+ // see if we can do a better cut.
950+ float widthNextLine = widthLeft + GetCursorScreenPos().x - GetWindowPos().x; // was GetContentRegionMax().x on IMGUI_VERSION_NUM < 19099
951+ #if IMGUI_VERSION_NUM >= 19197
952+ const char* endNextLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text_, text_end_, widthNextLine );
953+ #else
954+ const char* endNextLine = ImGui::GetFont()->CalcWordWrapPositionA( scale, text_, text_end_, widthNextLine );
955+ #endif
956+ if( endNextLine == text_end_ || ( endNextLine <= text_end_ && !IsCharInsideWord( *endNextLine ) ) )
957+ {
958+ // can possibly do better if go to next line
959+ endLine = text_;
960+ }
961+ }
962+ }
963+ ImGui::TextUnformatted( text_, endLine );
964+ if( bIndentToHere_ )
965+ {
966+ float indentNeeded = GetContentRegionAvail().x - widthLeft;
967+ if( indentNeeded )
968+ {
969+ ImGui::Indent( indentNeeded );
970+ indentX += indentNeeded;
971+ }
972+ }
973+ widthLeft = GetContentRegionAvail().x;
974+ while( endLine < text_end_ )
975+ {
976+ text_ = endLine;
977+ if( *text_ == ' ' ) { ++text_; } // skip a space at start of line
978+ #if IMGUI_VERSION_NUM >= 19197
979+ endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text_, text_end_, widthLeft );
980+ #else
981+ endLine = ImGui::GetFont()->CalcWordWrapPositionA( scale, text_, text_end_, widthLeft );
982+ #endif
983+ if( text_ == endLine )
984+ {
985+ endLine++;
986+ }
987+ ImGui::TextUnformatted( text_, endLine );
988+ }
989+ }
990+
991+ inline void TextRegion::RenderLinkTextWrapped( const char* text_, const char* text_end_, const Link& link_,
992+ const char* markdown_, const MarkdownConfig& mdConfig_, const char** linkHoverStart_, bool bIndentToHere_ )
993+ {
994+ #if IMGUI_VERSION_NUM >= 19197
995+ float fontSize = ImGui::GetFontSize();
996+ #else
997+ float scale = ImGui::GetIO().FontGlobalScale;
998+ #endif
999+ float widthLeft = GetContentRegionAvail().x;
1000+ const char* endLine = text_;
1001+ if( widthLeft > 0.0f )
1002+ {
1003+ #if IMGUI_VERSION_NUM >= 19197
1004+ endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text_, text_end_, widthLeft );
1005+ #else
1006+ endLine = ImGui::GetFont()->CalcWordWrapPositionA( scale, text_, text_end_, widthLeft );
1007+ #endif
1008+ }
1009+
1010+ if( endLine > text_ && endLine < text_end_ )
1011+ {
1012+ if( IsCharInsideWord( *endLine ) )
1013+ {
1014+ // see if we can do a better cut.
1015+ float widthNextLine = widthLeft + GetCursorScreenPos().x - GetWindowPos().x; // was GetContentRegionMax().x on IMGUI_VERSION_NUM < 19099
1016+ #if IMGUI_VERSION_NUM >= 19197
1017+ const char* endNextLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text_, text_end_, widthNextLine );
1018+ #else
1019+ const char* endNextLine = ImGui::GetFont()->CalcWordWrapPositionA( scale, text_, text_end_, widthNextLine );
1020+ #endif
1021+ if( endNextLine == text_end_ || ( endNextLine <= text_end_ && !IsCharInsideWord( *endNextLine ) ) )
1022+ {
1023+ // can possibly do better if go to next line
1024+ endLine = text_;
1025+ }
1026+ }
1027+ }
1028+ bool bHovered = RenderLinkText( text_, endLine, link_, markdown_, mdConfig_, linkHoverStart_ );
1029+ if( bIndentToHere_ )
1030+ {
1031+ float indentNeeded = GetContentRegionAvail().x - widthLeft;
1032+ if( indentNeeded )
1033+ {
1034+ ImGui::Indent( indentNeeded );
1035+ indentX += indentNeeded;
1036+ }
1037+ }
1038+ widthLeft = GetContentRegionAvail().x;
1039+ while( endLine < text_end_ )
1040+ {
1041+ text_ = endLine;
1042+ if( *text_ == ' ' ) { ++text_; } // skip a space at start of line
1043+ #if IMGUI_VERSION_NUM >= 19197
1044+ endLine = ImGui::GetFont()->CalcWordWrapPosition( fontSize, text_, text_end_, widthLeft );
1045+ #else
1046+ endLine = ImGui::GetFont()->CalcWordWrapPositionA( scale, text_, text_end_, widthLeft );
1047+ #endif
1048+ if( text_ == endLine )
1049+ {
1050+ endLine++;
1051+ }
1052+ bool bThisLineHovered = RenderLinkText( text_, endLine, link_, markdown_, mdConfig_, linkHoverStart_ );
1053+ bHovered = bHovered || bThisLineHovered;
1054+ }
1055+ if( !bHovered && *linkHoverStart_ == markdown_ + link_.text.start )
1056+ {
1057+ *linkHoverStart_ = NULL;
1058+ }
1059+ }
1060+
1061+
1062+ inline void defaultMarkdownFormatCallback( const MarkdownFormatInfo& markdownFormatInfo_, bool start_ )
1063+ {
1064+ switch( markdownFormatInfo_.type )
1065+ {
1066+ case MarkdownFormatType::NORMAL_TEXT:
1067+ break;
1068+ case MarkdownFormatType::EMPHASIS:
1069+ {
1070+ MarkdownHeadingFormat fmt;
1071+ // default styling for emphasis uses last headingFormats - for your own styling
1072+ // implement EMPHASIS in your formatCallback
1073+ if( markdownFormatInfo_.level == 1 )
1074+ {
1075+ // normal emphasis
1076+ if( start_ )
1077+ {
1078+ ImGui::PushStyleColor( ImGuiCol_Text, ImGui::GetStyle().Colors[ ImGuiCol_TextDisabled ] );
1079+ }
1080+ else
1081+ {
1082+ ImGui::PopStyleColor();
1083+ }
1084+ }
1085+ else
1086+ {
1087+ // strong emphasis
1088+ fmt = markdownFormatInfo_.config->headingFormats[ MarkdownConfig::NUMHEADINGS - 1 ];
1089+ if( start_ )
1090+ {
1091+ if( fmt.font )
1092+ {
1093+ #ifdef IMGUI_HAS_TEXTURES // used to detect dynamic font capability:
1094+ ImGui::PushFont( fmt.font, 0.0f ); // Change font and keep current size
1095+ #else
1096+ ImGui::PushFont( fmt.font );
1097+ #endif
1098+ }
1099+ }
1100+ else
1101+ {
1102+ if( fmt.font )
1103+ {
1104+ ImGui::PopFont();
1105+ }
1106+ }
1107+ }
1108+ break;
1109+ }
1110+ case MarkdownFormatType::HEADING:
1111+ {
1112+ MarkdownHeadingFormat fmt;
1113+ if( markdownFormatInfo_.level > MarkdownConfig::NUMHEADINGS )
1114+ {
1115+ fmt = markdownFormatInfo_.config->headingFormats[ MarkdownConfig::NUMHEADINGS - 1 ];
1116+ }
1117+ else
1118+ {
1119+ fmt = markdownFormatInfo_.config->headingFormats[ markdownFormatInfo_.level - 1 ];
1120+ }
1121+ if (start_)
1122+ {
1123+ if ( 0 == ( markdownFormatInfo_.config->formatFlags & ImGuiMarkdownFormatFlags_NoNewLineBeforeHeading ) )
1124+ {
1125+ ImGui::NewLine();
1126+ }
1127+ if (fmt.font)
1128+ {
1129+#ifdef IMGUI_HAS_TEXTURES // used to detect dynamic font capability: https://github.com/ocornut/imgui/issues/8465#issuecomment-2701570771
1130+ ImGui::PushFont(fmt.font, fmt.fontSize > 0.0f ? fmt.fontSize : fmt.font->LegacySize);
1131+#else
1132+ ImGui::PushFont(fmt.font);
1133+#endif
1134+ }
1135+ }
1136+ else
1137+ {
1138+ if (fmt.separator)
1139+ {
1140+ // In markdown the separator does not advance the cursor
1141+ ImVec2 cursor = ImGui::GetCursorPos();
1142+ ImGui::Separator();
1143+ if ( (markdownFormatInfo_.config->formatFlags & ImGuiMarkdownFormatFlags_SeparatorDoesNotAdvance) ) {
1144+ ImGui::SetCursorPos(cursor);
1145+ }
1146+ }
1147+ if (fmt.font)
1148+ {
1149+ ImGui::PopFont();
1150+ }
1151+ ImGui::NewLine();
1152+ }
1153+ break;
1154+ }
1155+ case MarkdownFormatType::UNORDERED_LIST:
1156+ break;
1157+ case MarkdownFormatType::LINK:
1158+ if( start_ )
1159+ {
1160+ ImGui::PushStyleColor( ImGuiCol_Text, ImGui::GetStyle().Colors[ ImGuiCol_ButtonHovered ] );
1161+ }
1162+ else
1163+ {
1164+ ImGui::PopStyleColor();
1165+ if( markdownFormatInfo_.itemHovered )
1166+ {
1167+ ImGui::UnderLine( ImGui::GetStyle().Colors[ ImGuiCol_ButtonHovered ] );
1168+ }
1169+ else
1170+ {
1171+ ImGui::UnderLine( ImGui::GetStyle().Colors[ ImGuiCol_Button ] );
1172+ }
1173+ }
1174+ break;
1175+ }
1176+ }
1177+}
@@ -35,9 +35,10 @@
3535 OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
3636 */
3737 #include <gui/manual.hh>
38-#include <imgui.h>
3938 #include <database/locale.hh>
4039 #include <gui/guiengine.hh>
40+#include <imgui.h>
41+#include <imgui_markdown.h>
4142
4243 namespace gui {
4344 void Manual::Draw(Info &info) {
@@ -77,45 +78,339 @@
7778
7879 ImGui::BeginChild("ManualContent", ImVec2(0, 0), 0, ImGuiWindowFlags_HorizontalScrollbar);
7980
81+ ImGui::MarkdownConfig mdConfig = {};
82+
8083 if (m_CurPage == "index") {
84+ string md;
8185 if (info.i18n->GetCurrentLanguage().code == "ja_JP") {
8286 ImGui::TextWrapped("Shader Playgroundへようこそ!");
8387 ImGui::Dummy(ImVec2(0, 10));
84- renderMarkdown(R"(# 基本操作
88+ md = R"(# はじめに
8589
8690 **Note**: For English, go to 編集 → 設定 (or press CTRL + ,), and set "言語/Language" to "English".
8791
88-- **コンパイル**: F5 又は「ビルド→コンパイル」
89-- **保存**: Ctrl+S 又は「ファイル→保存」
90-- 右側のパネルでシェーダーコードを編集出来ます
91-)");
92+これはShader Playgroundのマニュアルです。
93+このマニュアルは、このプログラムをできるだけわかりやすく理解できるように作成されています。
94+ブラウザに切り替えてページの読み込みが遅くなるのを避けるため、マニュアルをプログラム内に埋め込むことにしました。
95+このテキストの上部には、このシステムの各部分の詳細に移動できる複数のタブがあります。
96+このタブは全体の簡単な概要を提供します。
97+
98+# 基本的なUIガイド
99+初回起動時、ウィンドウが数秒間空白の画面を表示することがあります。
100+これはバックグラウンドでディレクトリを作成し、すべてのシーン データを保存するための新しいデータベース ファイルを作成しているためです。
101+次回起動時には、すべてが即座に読み込まれます。
102+
103+起動直後に、ビューポートにはテクニカル諏訪子さんのGLSLチュートリアルで紹介されたシェーダーの1つが表示され、すべてが正常に動作していることを確認できます。
104+このシェーダーは、チュートリアルで使用された「GLSLテスター」プログラムのデフォルト シェーダーでもあります。
105+ビューポートのすぐ隣にはテキスト エディタのエリアがあります。
106+そこには3つのテキスト エディタがあります:頂点シェーダー用、フラグメントシェーダー用、そしてLua用です。
107+LuaはレンダリングのCPU側を制御し、シェーダーはGPU側を制御します。
108+最後に、下部にはログを出力するコンソールがあります。
109+
110+その他の細かい詳細として、右上にFPSカウンターと日時が表示されます。
111+パフォーマンスを意識したり、寝る時間になったりするのに役立ちます。
112+
113+新しいシェーダーを作成すると、いくつかの異なる開始点から選択できます。これらはLua APIのサンプルコードとしても機能します。
114+ビューポートはリアルタイム更新されません。
115+変更を確認するにはコンパイルが必要です。
116+これにより、フロー状態から外れることなくプログラミング/スクリプト作成ができます。
117+
118+# 歴史
119+Shader Playgroundは、Shadertoyのようなプログラムをオフラインで使用でき、WebGLではなくOpenGLを使い、より大きな制御を可能にするという欲求から始まりました。
120+しかしLuaエンジンが実装されると、突然もっと有用なツールにしたいと思うようになりました。
121+そこで、ユーザーがGPU側だけでなくCPU側でもシェーダーを作成できるAPIを実装しました。
122+その後、3Dシェーダーを追加したくなり、気がつくと将来のリリースで拡張されるミニゲームエンジンになっていました。
123+目標は、移植性とパフォーマンスを犠牲にせずにできるだけ完全なものにすることですが、5日間のプロジェクトとしてそれらすべてを達成するのは現実的に不可能です。
124+そのため、将来のアップデートでゆっくりと確実にさらに多くの機能を追加していく予定です。)";
92125 } else {
93126 ImGui::TextWrapped("Welcome to Shader Playground!");
94127 ImGui::Dummy(ImVec2(0, 10));
95- renderMarkdown(R"(# Basic operation
128+ md = R"(# Intro
96129
97130 **注意**: 日本語で読むには、「Edit → Settings」をクリックし(若しくはCTRL + ,を押し)、「言語/Language」から「日本語」を御選択下さい。".
98131
99-- **Compile**: F5 or "Build → Compile"
100-- **Save**: Ctrl+S or "File → Save"
101-- You can edit the shader code in the right side panel
102-)");
132+This is the manual for Shader Playground.
133+This manual aims to make it as easy to understand this program as possible.
134+We decided to embed the manual into the program to remove the need for switching to a web browser and suffer from slow page loading times.
135+At the top of this text you'll see multiple tabs going into details of each part of this system.
136+This tab gives a quick overview of everything.
137+
138+# Basic UI Guide
139+Upon first start up, the window might show a blank screen for a few seconds.
140+This is because in the background, it's creating directories and a new database file to store all your scene data in.
141+Next time you run this program, everything will be instantly loaded.
142+
143+Immediately upon opening, you'll see one of the shaders demonstrated in the GLSL tutorials by Technical Suwako in the viewport, confirming that it all works.
144+This shader is also the default in the program "GLSL Tester" which was used in those tutorials.
145+Right next to the viewport there is an area with a text editor.
146+It has 3 text editors: one for the vertex shader, one for the fragment shader, and one for Lua.
147+Lua controls the CPU side of the rendering, whereas the shaders control the GPU side.
148+Lastly, at the bottom there is the console that will output logs.
149+
150+Some smaller details are the FPS counter and date/time at the right-top.
151+It allows for users to keep performance into account and also when it's finally time to sleep.
152+
153+Upon creating a new shader, you'll get to choose from a couple of different starting points, which also doubles as a sample code for the Lua API.
154+The viewport does not update live.
155+Instead, it requires a compilation to see changes.
156+This allows for programming/scripting without getting kicked out of your flow state.
157+
158+# History
159+Shader Playground started out as a desire to have a Shadertoy-like program, but that can be used offline, using OpenGL instead of WebGL, and also allow for greater control.
160+But as the Lua Engine got implemented, suddenly we wanted to make it into a much more useful tool.
161+So we implemented an API that lets the user make shaders not only on the GPU side, but on the CPU side as well.
162+Then we wanted to add in 3D shaders, and all of the sudden we had a mini game engine that will be expanded upon in future releases.
163+The aim is to make it as complete as possible without sacrificing on portability and performance, but as a 5 day project it's realistically not possible to accomplish all of that.
164+This is why future updates will slowly but surely add in more features.)";
103165 }
166+ ImGui::Markdown(md.c_str(), md.length(), mdConfig);
104167 } else if (m_CurPage == "shader") {
105- ImGui::TextWrapped("Shader Playgroundへようこそ!");
106- ImGui::Dummy(ImVec2(0, 10));
107- renderMarkdown(R"(# シェーダー
108-)");
168+ string md;
169+ if (info.i18n->GetCurrentLanguage().code == "ja_JP") {
170+ md = R"(# シェーダー
171+現在、Shader PlaygroundはGLSLシェーダーのみをサポートしており、特にVertexシェーダーとFragmentシェーダーのみです。
172+シェーダーはShader PlaygroundのバックエンドであるOpenGL 4.6と互換性がある必要があります。
173+将来的にはVulkan GLSLシェーダー、NVN GLSLシェーダー(承認されたユーザーのみ)、Slangシェーダー、DirectX HLSLシェーダー(Windowsのみ)、Metalシェーダー(macOSのみ)もサポートされる予定です。
174+OpenGLのコアプロファイルがサポートするものはすべてShader Playgroundでサポートされており、実際のゲームからシェーダーをそのままコピーして使用できます。
175+GLSLエディタには抽象化が一切ないため、制約なく実世界のGLSLシェーダーで自由に実験できます。)";
176+ } else {
177+ md = R"(# Shaders
178+At the moment, Shader Playground only support GLSL shaders, and specifically only Vertex and Fragment shaders.
179+The shaders must be compatible with OpenGL 4.6, which is the backend of Shader Playground.
180+In the future it will be extended to also support Vulkan GLSL shaders, NVN GLSL shaders (only for approved users), Slang shaders, DirectX HLSL shaders (only on Windows), and Metal shaders (only on macOS).
181+Everything the core profile of OpenGL supports is supported in Shader Playground, and the shaders can be copied from real games as-is.
182+There are no abstractions for the GLSL editors, which provides full freedom to experiment with real world GLSL shaders without constraint.)";
183+ }
184+ ImGui::Markdown(md.c_str(), md.length(), mdConfig);
109185 } else if (m_CurPage == "lua") {
110- ImGui::TextWrapped("Lua");
111- ImGui::Dummy(ImVec2(0, 10));
112- renderMarkdown(R"(# Luaエンジン
113-)");
186+ string md;
187+ if (info.i18n->GetCurrentLanguage().code == "ja_JP") {
188+ md = R"(# Luaエンジン
189+Shader Playgroundは、基本的ながら強力なLuaスクリプトエンジンを提供しています。
190+独自の標準ライブラリが付属しており、`le.`で始まります。
191+このライブラリは、必要に応じてまだ開発中です。
192+
193+## 関数
194+2つの関数があります:`Init`と`Update`。
195+`Init`関数はコンパイル後の最初のフレームでのみ実行されます。
196+`Update`関数は毎フレーム実行されます。
197+
198+## コア
199+以下のAPI呼び出しは`le.`で始まります。
200+* `api_version` = Lua Engine APIのバージョンを返します。
201+* `log` = テキストと変数を出力します。Javascriptの`console.log()`と同様に動作します。任意のパラメータを受け取ります。
202+* `sizeof` = 各種データ型のサイズを返します。パラメータとして文字列でデータ型を受け取ります。
203+
204+## システム
205+以下のAPI呼び出しは`le.sys`で始まります。
206+* `get_time` = Shader Playgroundが起動してからの総時間を返します。
207+* `get_delta_time` = `get_time`のデルタ値を返します。
208+* `get_timer_frequency` = Shader Playgroundのタイマー周波数を返します。
209+* `get_resolution` = Shader Playgroundの解像度(ビューポートではなく)を返します。
210+* `random` = 2つの整数間の乱数を返します。2つのパラメータ(両方文字列)を受け取ります。
211+
212+## IO
213+以下のAPI呼び出しは`le.io`で始まります。
214+* `get_mouse` = マウスカーソルの位置を返します。
215+* `key.is_press` = 押されたキーを返します。文字列パラメータを受け取ります。
216+* `key.is_release` = 離されたキーを返します。文字列パラメータを受け取ります。
217+* `key.is_hold` = 押され続けているキーを返します。文字列パラメータを受け取ります。
218+
219+## 数学
220+以下のAPI呼び出しは`le.math`で始まります。
221+* `identity` = 単位行列を返します。
222+* `translate` = 平行移動行列を返します。X, Y, Zの3つのfloatを受け取ります。
223+* `scale` = 拡大縮小行列を返します。X, Y, Zの3つのfloatを受け取ります。
224+* `rotate_x` = X軸回転行列を返します。角度の1つのfloatを受け取ります。
225+* `rotate_y` = Y軸回転行列を返します。角度の1つのfloatを受け取ります。
226+* `rotate_z` = Z軸回転行列を返します。角度の1つのfloatを受け取ります。
227+* `rotate` = 回転行列を返します。pitch, yaw, rollの3つのfloatを受け取ります。
228+* `perspective` = 透視投影行列を返します。視野角、アスペクト比、near、farの4つのfloatを受け取ります。
229+* `mul` = 2つの行列を掛けた結果を返します。2つの行列を受け取ります。
230+* `look_at` = LookAt行列を返します。eye, target, upそれぞれに3D軸の9つのfloatを受け取ります。
231+
232+## グラフィックス
233+以下のAPI呼び出しは`le.gfx`で始まります。
234+* `add_mesh` = 頂点、インデックス、属性を受け取り、ビューポートに2Dまたは3Dオブジェクトを描画します。
235+* `get_position` = オブジェクトの現在の位置を返します。メッシュインデックスの1つの整数を受け取ります。
236+* `set_position` = メッシュの位置を変更します。メッシュインデックスの1つの整数とXYZ軸の3つのfloatを受け取ります。
237+* `get_rotation` = オブジェクトの現在の回転を返します。メッシュインデックスの1つの整数を受け取ります。
238+* `set_rotation` = メッシュの回転を変更します。メッシュインデックスの1つの整数とXYZ軸の3つのfloatを受け取ります。
239+* `get_scale` = オブジェクトの現在のスケールを返します。メッシュインデックスの1つの整数を受け取ります。
240+* `set_scale` = メッシュのスケールを変更します。メッシュインデックスの1つの整数とXYZ軸の3つのfloatを受け取ります。
241+* `set_uniform1` または `set_uniform_float1` = 1次元uniformを変更します。属性名の1つの文字列とX軸の1つのfloatを受け取ります。
242+* `set_uniform2` または `set_uniform_float2` = 2次元uniformを変更します。属性名の1つの文字列とXY軸の2つのfloatを受け取ります。
243+* `set_uniform3` または `set_uniform_float3` = 3次元uniformを変更します。属性名の1つの文字列とXYZ軸の3つのfloatを受け取ります。
244+* `set_uniform4` または `set_uniform_float4` = 4次元uniformを変更します。属性名の1つの文字列とXYZW軸の4つのfloatを受け取ります。
245+* `set_uniform_double1` = 1次元uniformを変更します。属性名の1つの文字列とX軸の1つのdoubleを受け取ります。
246+* `set_uniform_double2` = 2次元uniformを変更します。属性名の1つの文字列とXY軸の2つのdoubleを受け取ります。
247+* `set_uniform_double3` = 3次元uniformを変更します。属性名の1つの文字列とXYZ軸の3つのdoubleを受け取ります。
248+* `set_uniform_double4` = 4次元uniformを変更します。属性名の1つの文字列とXYZW軸の4つのdoubleを受け取ります。
249+* `set_uniform_int1` = 1次元uniformを変更します。属性名の1つの文字列とX軸の1つの符号付きintを受け取ります。
250+* `set_uniform_int2` = 2次元uniformを変更します。属性名の1つの文字列とXY軸の2つの符号付きintを受け取ります。
251+* `set_uniform_int3` = 3次元uniformを変更します。属性名の1つの文字列とXYZ軸の3つの符号付きintを受け取ります。
252+* `set_uniform_int4` = 4次元uniformを変更します。属性名の1つの文字列とXYZW軸の4つの符号付きintを受け取ります。
253+* `set_uniform_uint1` = 1次元uniformを変更します。属性名の1つの文字列とX軸の1つの符号なしintを受け取ります。
254+* `set_uniform_uint2` = 2次元uniformを変更します。属性名の1つの文字列とXY軸の2つの符号なしintを受け取ります。
255+* `set_uniform_uint3` = 3次元uniformを変更します。属性名の1つの文字列とXYZ軸の3つの符号なしintを受け取ります。
256+* `set_uniform_uint4` = 4次元uniformを変更します。属性名の1つの文字列とXYZW軸の4つの符号なしintを受け取ります。
257+* `set_uniform_vec3` = 3次元uniformを変更します。属性名の1つの文字列とXYZ軸の3つのfloatを受け取ります。
258+* `set_uniform_mat4` = 4x4行列uniformを変更します。属性名の1つの文字列と1つの4x4行列を受け取ります。
259+
260+## キーコード
261+`key.is_press`、`key.is_release`、`key.is_hold`で使用できるキーコードは以下の通りです:
262+"q", "w", "e", "r", "t", "y", "u", "i", "o", "p"
263+"a", "s", "d", "f", "g", "h", "j", "k", "l"
264+"z", "x", "c", "v", "b", "n", "m"
265+"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"
266+"enter", "space", "shift", "backspace"
267+"up", "down", "left", "right")";
268+ } else {
269+ md = R"(# Lua Engine
270+Shader Playground offers a basic, but powerful Lua scripting engine.
271+It comes with its own standard library, which starts with `le.`.
272+The library is still developing as needs grow.
273+
274+## Functions
275+There are 2 functions: `Init` and `Update`.
276+The `Init` function executes only at the first frame from compile time.
277+The `Update` function executes on every frame.
278+
279+## Core
280+The following API calls start with `le.`
281+* `api_version` = returns the version of Lua Engine API.
282+* `log` = prints text and variables, works similar to Javascript's `console.log()`. Takes in any parameters.
283+* `sizeof` = returns the size of various data types. Takes in data types as strings as parameter.
284+
285+## System
286+The following API calls start with `le.sys`
287+* `get_time` = returns the total amount of time Shader Playground has been running.
288+* `get_delta_time` = returns the delta of `get_time`.
289+* `get_timer_frequency` = returns the timer frequency of Shader Playground.
290+* `get_resolution` = returns the resolution of Shader Playground, NOT the viewport.
291+* `random` = returns the random value between 2 integers. Takes in 2 parameters, both strings.
292+
293+## IO
294+The following API calls start with `le.io`
295+* `get_mouse` = returns the mouse cursor position.
296+* `key.is_press` = returns the key pressed. Takes in string parameter.
297+* `key.is_release` = returns the key released. Takes in string parameter.
298+* `key.is_hold` = returns the key held. Takes in string parameter.
299+
300+## Math
301+The following API calls start with `le.math`
302+* `identity` = returns the identity matrix.
303+* `translate` = returns the translation matrix. Takes in 3 floats for X, Y, and Z.
304+* `scale` = returns the scale matrix. Takes in 3 floats for X, Y, and Z.
305+* `rotate_x` = returns the rotation matrix on X. Takes in 1 float for angle.
306+* `rotate_y` = returns the rotation matrix on Y. Takes in 1 float for angle.
307+* `rotate_z` = returns the rotation matrix on Z. Takes in 1 float for angle.
308+* `rotate` = returns the rotation matrix. Takes in 3 floats for pitch, yaw, and roll.
309+* `perspective` = returns the perspective matrix. Takes in 4 floats for field of view, aspect, near, and far.
310+* `mul` = returns the result of 2 matrices multiplied together. Takes in 2 matrices.
311+* `look_at` = returns the look at matrix. Takes in 9 floats for eye, target, and up of each 3D axis.
312+
313+## Graphics
314+The following API calls start with `le.gfx`
315+* `add_mesh` = takes in vertices, indices, and attributes to then render either a 2D or 3D object in the viewport.
316+* `get_position` = returns the current position of an object. Takes in 1 integer for the mesh index.
317+* `set_position` = modifies the position of a mesh. Takes in 1 integer for the mesh index and 3 floats for the XYZ axis.
318+* `get_rotation` = returns the current rotation of an object. Takes in 1 integer for the mesh index.
319+* `set_rotation` = modifies the rotation of a mesh. Takes in 1 integer for the mesh index and 3 floats for the XYZ axis.
320+* `get_scale` = returns the current scale of an object. Takes in 1 integer for the mesh index.
321+* `set_scale` = modifies the scale of a mesh. Takes in 1 integer for the mesh index and 3 floats for the XYZ axis.
322+* `set_uniform1` or `set_uniform_float1` = modifies a 1D uniform. Takes in 1 string for the attribute name and 1 float for the X axis.
323+* `set_uniform2` or `set_uniform_float2` = modifies a 2D uniform. Takes in 1 string for the attribute name and 2 floats for the XY axis.
324+* `set_uniform3` or `set_uniform_float3` = modifies a 2D uniform. Takes in 1 string for the attribute name and 3 floats for the XYZ axis.
325+* `set_uniform4` or `set_uniform_float4` = modifies a 2D uniform. Takes in 1 string for the attribute name and 4 floats for the XYZW axis.
326+* `set_uniform_double1` = modifies a 1D uniform. Takes in 1 string for the attribute name and 1 double for the X axis.
327+* `set_uniform_double2` = modifies a 2D uniform. Takes in 1 string for the attribute name and 2 doubles for the XY axis.
328+* `set_uniform_double3` = modifies a 2D uniform. Takes in 1 string for the attribute name and 3 doubles for the XYZ axis.
329+* `set_uniform_double4` = modifies a 2D uniform. Takes in 1 string for the attribute name and 4 doubles for the XYZW axis.
330+* `set_uniform_int1` = modifies a 1D uniform. Takes in 1 string for the attribute name and 1 signed int for the X axis.
331+* `set_uniform_int2` = modifies a 2D uniform. Takes in 1 string for the attribute name and 2 signed ints for the XY axis.
332+* `set_uniform_int3` = modifies a 2D uniform. Takes in 1 string for the attribute name and 3 signed ints for the XYZ axis.
333+* `set_uniform_int4` = modifies a 2D uniform. Takes in 1 string for the attribute name and 4 signed ints for the XYZW axis.
334+* `set_uniform_uint1` = modifies a 1D uniform. Takes in 1 string for the attribute name and 1 unsigned int for the X axis.
335+* `set_uniform_uint2` = modifies a 2D uniform. Takes in 1 string for the attribute name and 2 unsigned ints for the XY axis.
336+* `set_uniform_uint3` = modifies a 2D uniform. Takes in 1 string for the attribute name and 3 unsigned ints for the XYZ axis.
337+* `set_uniform_uint4` = modifies a 2D uniform. Takes in 1 string for the attribute name and 4 unsigned ints for the XYZW axis.
338+* `set_uniform_vec3` = modifies a 3D uniform. Takes in 1 string for the attribute name and 3 floats for the XYZ axis.
339+* `set_uniform_mat4` = modifies a 4D uniform. Takes in 1 string for the attribute name and 1 4x4 matrix.
340+
341+## Key codes
342+The following key codes are supported for use with `key.is_press`, `key.is_release`, and `key.is_hold`:
343+"q", "w", "e", "r", "t", "y", "u", "i", "o", "p"
344+"a", "s", "d", "f", "g", "h", "j", "k", "l"
345+"z", "x", "c", "v", "b", "n", "m"
346+"1", "2", "3", "4", "5", "6", "7", "8", "9", "0"
347+"enter", "space", "shift", "backspace"
348+"up", "down", "left", "right")";
349+ }
350+ ImGui::Markdown(md.c_str(), md.length(), mdConfig);
351+ ImGui::Markdown(md.c_str(), md.length(), mdConfig);
114352 } else if (m_CurPage == "hotkeys") {
115- ImGui::TextWrapped("Shader Playgroundへようこそ!");
116- ImGui::Dummy(ImVec2(0, 10));
117- renderMarkdown(R"(# ホットキー
118-)");
353+ string md;
354+ if (info.i18n->GetCurrentLanguage().code == "ja_JP") {
355+ md = R"(# ホットキー
356+Shader Playgroundは、ほとんどの操作にホットキーを提供しています。
357+
358+## UI
359+CTRL + Tab = ウィンドウの切り替え
360+ビューポート上で右クリック = クリア色の変更
361+F1 = このマニュアルを開く
362+CTRL + , = 設定ウィンドウを開く
363+CTRL + H = 「Shader Playgroundについて」ウィンドウを開く
364+Escape = 現在のポップアップウィンドウおよび/またはモーダルを閉じる
365+
366+## シーン
367+CTRL + N = 新しいシーンを作成
368+CTRL + O = 既存のシーンを開く
369+CTRL + S = 現在のシーンを保存
370+CTRL + Shift + S = 現在のシーンを別名で保存
371+
372+## ビルド
373+F5 = シェーダーとLuaコードをコンパイル
374+
375+## エディタ
376+CTRL + Z = 元に戻す
377+CTRL + Y = やり直し
378+CTRL + X = 切り取り
379+CTRL + C = コピー
380+CTRL + V = 貼り付け
381+CTRL + D = 複製
382+CTRL + A = 全て選択)";
383+ } else {
384+ md = R"(# Hotkeys
385+Shader Playground offers hotkeys for most operations.
386+
387+## UI
388+CTRL + Tab = switch between windows
389+Right click on viewport = change clear color
390+F1 = open this manual
391+CTRL + , = open settings window
392+CTRL + H = open "About Shader Playground" window
393+Escape = close current popup window and/or modal
394+
395+## Scenes
396+CTRL + N = create new scene
397+CTRL + O = open an already existing scene
398+CTRL + S = save current scene
399+CTRL + Shift + S = save current scene under a different name
400+
401+## Building
402+F5 = compile shaders and Lua code
403+
404+## Editor
405+CTRL + Z = undo
406+CTRL + Y = redo
407+CTRL + X = cut
408+CTRL + C = copy
409+CTRL + V = paste
410+CTRL + D = duplicate
411+CTRL + A = select all)";
412+ }
413+ ImGui::Markdown(md.c_str(), md.length(), mdConfig);
119414 }
120415
121416 ImGui::EndChild();