-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP Markdown renderer for imgui windows #78636
base: master
Are you sure you want to change the base?
Conversation
Remove the item spacing in category titles so the ascii art looks right
We do this by not specifying the size in pixels but in percent. We want the window to be 100%, or 1.0×, the size of the game window.
… tags DRAW_NOTE_COLORS is redundant, it already shows when adding a map note HELP_DRAW_DIRECTIONS can be done in json instead now
Make the help window full screen and resize it when the game resizes
This is still a WIP, with some important known defects. * No support for our color tags, or the tags used specifically for the help system. * Incorrect handling of whitespace in wrapped paragraphs. In HTML, all whitespace is collapsed and rendered as a single space character. MD4C is usually used to create HTML from a Markdown file, so this isn’t handled. As a result no space will be rendered between consecutive words in a wrapped paragraph. * We don't have italic or bold fonts, or fonts at additional sizes. However that is fairly trivial for us to add to our font loader. * It allocates a lot, and the way it’s written it does the same work every frame. This is not ideal, but for the Help window it’s not the end of the world; we could live with it. If Markdown were to be used anywhere else in the game I would be more worried about it. It would be nice to make MD4C use an arena allocator (or to otherwise only allocate whole pages) in order to prevent memory fragmentation. * This code loads the content of the markdown file(s) at game startup. There’s no reason it couldn’t postpone that work until the user actually opens the Help window. Trivial to implement as well. * The system by which translators contribute translations only works for JSON files. I’m sure that wouldn’t be too hard to handle.
} | ||
} | ||
|
||
if (jo.has_string("filename")) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[JSON & C++ formatters] reported by reviewdog 🐶
if (jo.has_string("filename")) { | |
if( jo.has_string( "filename" ) ) { |
auto res = read_whole_file(PATH_INFO::jsondir() / filename); | ||
if (res.has_value()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[JSON & C++ formatters] reported by reviewdog 🐶
auto res = read_whole_file(PATH_INFO::jsondir() / filename); | |
if (res.has_value()) { | |
auto res = read_whole_file( PATH_INFO::jsondir() / filename ); | |
if( res.has_value() ) { |
struct help_markdown : public imgui_md::imgui_md | ||
{ | ||
catacurses::window w_help_border; | ||
catacurses::window w_help; | ||
|
||
ui_adaptor ui; | ||
const auto init_windows = [&]( ui_adaptor & ui ) { | ||
w_help_border = catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH, | ||
point( TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0, | ||
TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0 ) ); | ||
w_help = catacurses::newwin( FULL_SCREEN_HEIGHT - 2, FULL_SCREEN_WIDTH - 2, | ||
point( 1 + static_cast<int>( TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0 ), | ||
1 + static_cast<int>( TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0 ) ) ); | ||
ui.position_from_window( w_help_border ); | ||
}; | ||
init_windows( ui ); | ||
ui.on_screen_resize( init_windows ); | ||
void BLOCK_QUOTE(bool e) override | ||
{ | ||
if (e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[JSON & C++ formatters] reported by reviewdog 🐶
struct help_markdown : public imgui_md::imgui_md | |
{ | |
catacurses::window w_help_border; | |
catacurses::window w_help; | |
ui_adaptor ui; | |
const auto init_windows = [&]( ui_adaptor & ui ) { | |
w_help_border = catacurses::newwin( FULL_SCREEN_HEIGHT, FULL_SCREEN_WIDTH, | |
point( TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0, | |
TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0 ) ); | |
w_help = catacurses::newwin( FULL_SCREEN_HEIGHT - 2, FULL_SCREEN_WIDTH - 2, | |
point( 1 + static_cast<int>( TERMX > FULL_SCREEN_WIDTH ? ( TERMX - FULL_SCREEN_WIDTH ) / 2 : 0 ), | |
1 + static_cast<int>( TERMY > FULL_SCREEN_HEIGHT ? ( TERMY - FULL_SCREEN_HEIGHT ) / 2 : 0 ) ) ); | |
ui.position_from_window( w_help_border ); | |
}; | |
init_windows( ui ); | |
ui.on_screen_resize( init_windows ); | |
void BLOCK_QUOTE(bool e) override | |
{ | |
if (e) { | |
struct help_markdown : public imgui_md::imgui_md { | |
void BLOCK_QUOTE( bool e ) override { | |
if( e ) { |
} else { | ||
ImGui::Unindent(); | ||
} | ||
imgui_md::imgui_md::BLOCK_QUOTE(e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[JSON & C++ formatters] reported by reviewdog 🐶
imgui_md::imgui_md::BLOCK_QUOTE(e); | |
imgui_md::imgui_md::BLOCK_QUOTE( e ); |
void BLOCK_CODE(const md4c::MD_BLOCK_CODE_DETAIL* d, bool e) override | ||
{ | ||
if (e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[JSON & C++ formatters] reported by reviewdog 🐶
void BLOCK_CODE(const md4c::MD_BLOCK_CODE_DETAIL* d, bool e) override | |
{ | |
if (e) { | |
void BLOCK_CODE( const md4c::MD_BLOCK_CODE_DETAIL *d, bool e ) override { | |
if( e ) { |
static void draw_movement_category(std::string src) { | ||
static help_markdown printer; | ||
const char *s = src.data(); | ||
printer.print(s, s+src.length()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[JSON & C++ formatters] reported by reviewdog 🐶
static void draw_movement_category(std::string src) { | |
static help_markdown printer; | |
const char *s = src.data(); | |
printer.print(s, s+src.length()); | |
static void draw_movement_category( std::string src ) | |
{ | |
static help_markdown printer; | |
const char *s = src.data(); | |
printer.print( s, s + src.length() ); |
cataimgui::set_scroll( s ); | ||
ImGui::TableNextRow(); | ||
ImGui::TableNextColumn(); | ||
if (!cat.markdown.empty()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[JSON & C++ formatters] reported by reviewdog 🐶
if (!cat.markdown.empty()) { | |
if( !cat.markdown.empty() ) { |
draw_movement_category( cat.markdown ); | ||
} else { | ||
for( const std::pair<std::string, int> &translated_paragraph : | ||
translated_paragraphs ) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[JSON & C++ formatters] reported by reviewdog 🐶
translated_paragraphs ) { | |
translated_paragraphs ) { |
case MM_NORMAL: | ||
cataimgui::TextColoredParagraph( c_white, translated_paragraph.first ); | ||
break; | ||
case MM_SUBTITLE: | ||
// BEFOREMERGE: Do something different | ||
cataimgui::TextColoredParagraph( c_white, translated_paragraph.first ); | ||
break; | ||
case MM_MONOFONT: | ||
cataimgui::PushMonoFont(); | ||
cataimgui::TextColoredParagraph( c_white, translated_paragraph.first ); | ||
ImGui::PopFont(); | ||
break; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[JSON & C++ formatters] reported by reviewdog 🐶
case MM_NORMAL: | |
cataimgui::TextColoredParagraph( c_white, translated_paragraph.first ); | |
break; | |
case MM_SUBTITLE: | |
// BEFOREMERGE: Do something different | |
cataimgui::TextColoredParagraph( c_white, translated_paragraph.first ); | |
break; | |
case MM_MONOFONT: | |
cataimgui::PushMonoFont(); | |
cataimgui::TextColoredParagraph( c_white, translated_paragraph.first ); | |
ImGui::PopFont(); | |
break; | |
case MM_NORMAL: | |
cataimgui::TextColoredParagraph( c_white, translated_paragraph.first ); | |
break; | |
case MM_SUBTITLE: | |
// BEFOREMERGE: Do something different | |
cataimgui::TextColoredParagraph( c_white, translated_paragraph.first ); | |
break; | |
case MM_MONOFONT: | |
cataimgui::PushMonoFont(); | |
cataimgui::TextColoredParagraph( c_white, translated_paragraph.first ); | |
ImGui::PopFont(); | |
break; |
case MM_SEPERATOR: | ||
ImGui::Separator(); | ||
break; | ||
default: | ||
debugmsg( "Unexpected help message modifier" ); | ||
continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[JSON & C++ formatters] reported by reviewdog 🐶
case MM_SEPERATOR: | |
ImGui::Separator(); | |
break; | |
default: | |
debugmsg( "Unexpected help message modifier" ); | |
continue; | |
case MM_SEPERATOR: | |
ImGui::Separator(); | |
break; | |
default: | |
debugmsg( "Unexpected help message modifier" ); | |
continue; |
I am very very against pulling in dependencies like these, it's pretty much a last resort, and they're doesn't seem to have been even an attempt to establish why it's necessary here. That's not impossible to overtime, but it's a big lift, especially given the known issues already outlined. |
Yea, it’s a pretty decent sized chunk of code. Worth thinking twice before using it.
As I said, the JSON for the help system is becoming more complex. Currently it’s just an array of paragraphs with tags for colors, key bindings, and a few other things. The patch in #78195 adds more formatting on top of that. It’s an ad–hoc format that I can easily foresee becoming more complex over time. I figured I would see how much work would be required to skip to the end without reinventing the wheel. I spent less than a day of work to get this far, which bodes well. |
Summary
Interface "Adds a Markdown renderer for ImGui windows, and uses it in the Help window"
Purpose of change
Currently the Help text is stored in JSON files. Formatting information, such as which font to use, needs to be encoded in the JSON in some way. This is starting to look like a general–purpose document format, so perhaps it would be better to use an existing document format instead. Markdown is a common and well–liked format, so perhaps it would be a good choice.
Describe the solution
This uses a library called
imgui_md
(MIT Licensed) to render a Markdown document using ImGui. It in turn relies on a library calledMD4C
(also MIT Licensed) which actually parses the file.Describe alternatives you've considered
We could embed an HTML renderer instead. Electron is only a few million lines of code…
Testing
From the main menu, open the Help. Navigate to the “Movement” section, which is the only one for which a markdown file exists in this PR.
Additional Information
This is still a WIP, with some important known defects.
No support for our color tags, or the tags used specifically for
the help system. Supporting them would not be a small change.
Incorrect handling of whitespace in wrapped paragraphs. In HTML,
all whitespace is collapsed and rendered as a single space
character. MD4C is usually used to create HTML from a Markdown
file, so this isn’t handled. As a result no space will be
rendered between consecutive words in a wrapped paragraph.
We don't have italic or bold fonts, or fonts at additional
sizes. However that is fairly trivial for us to add to our font
loader.
It allocates a lot, and the way it’s written it does the same
work every frame. This is not ideal, but for the Help window it’s
not the end of the world; we could live with it. If Markdown were
to be used anywhere else in the game I would be more worried
about it. It would be nice to make MD4C use an arena allocator
(or to otherwise only allocate whole pages) in order to prevent
memory fragmentation.
This code loads the content of the markdown file(s) at game
startup. There’s no reason it couldn’t postpone that work until
the user actually opens the Help window. Trivial to implement as
well.
The system by which translators contribute translations only
works for JSON files. I’m sure that wouldn’t be too hard to
handle.
At the moment I have no plans to carry this work forward. The only reason I’m making a PR is so that the work is not lost; should the idea prove acceptable anyone will be able to find this branch and continue the work. Note that it is built on top of @Procyonae’s in–progress work on the Help window in #78195. You might want to rebase.