Skip to content
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

Draft
wants to merge 22 commits into
base: master
Choose a base branch
from

Conversation

db48x
Copy link
Contributor

@db48x db48x commented Dec 18, 2024

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 called MD4C (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.

Screenshot From 2024-12-17 14-21-12

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.

Procyonae and others added 22 commits December 13, 2024 17:50
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.
@github-actions github-actions bot added <Documentation> Design documents, internal info, guides and help. Code: Build Issues regarding different builds and build environments [JSON] Changes (can be) made in JSON [C++] Changes (can be) made in C++. Previously named `Code` [Markdown] Markdown issues and PRs Info / User Interface Game - player communication, menus, etc. labels Dec 18, 2024
}
}

if (jo.has_string("filename")) {
Copy link
Contributor

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 🐶

Suggested change
if (jo.has_string("filename")) {
if( jo.has_string( "filename" ) ) {

Comment on lines +97 to +98
auto res = read_whole_file(PATH_INFO::jsondir() / filename);
if (res.has_value()) {
Copy link
Contributor

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 🐶

Suggested change
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() ) {

Comment on lines +317 to +321
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) {
Copy link
Contributor

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 🐶

Suggested change
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);
Copy link
Contributor

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 🐶

Suggested change
imgui_md::imgui_md::BLOCK_QUOTE(e);
imgui_md::imgui_md::BLOCK_QUOTE( e );

Comment on lines +329 to +331
void BLOCK_CODE(const md4c::MD_BLOCK_CODE_DETAIL* d, bool e) override
{
if (e) {
Copy link
Contributor

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 🐶

Suggested change
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 ) {

Comment on lines +357 to +360
static void draw_movement_category(std::string src) {
static help_markdown printer;
const char *s = src.data();
printer.print(s, s+src.length());
Copy link
Contributor

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 🐶

Suggested change
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()) {
Copy link
Contributor

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 🐶

Suggested change
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 ) {
Copy link
Contributor

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 🐶

Suggested change
translated_paragraphs ) {
translated_paragraphs ) {

Comment on lines +379 to +390
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;
Copy link
Contributor

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 🐶

Suggested change
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;

Comment on lines +400 to +405
case MM_SEPERATOR:
ImGui::Separator();
break;
default:
debugmsg( "Unexpected help message modifier" );
continue;
Copy link
Contributor

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 🐶

Suggested change
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;

@db48x db48x marked this pull request as draft December 18, 2024 02:25
@kevingranade
Copy link
Member

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.

@db48x
Copy link
Contributor Author

db48x commented Dec 18, 2024

Yea, it’s a pretty decent sized chunk of code. Worth thinking twice before using it.

…they're doesn't seem to have been even an attempt to establish why it's necessary here.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[C++] Changes (can be) made in C++. Previously named `Code` Code: Build Issues regarding different builds and build environments <Documentation> Design documents, internal info, guides and help. Info / User Interface Game - player communication, menus, etc. [JSON] Changes (can be) made in JSON [Markdown] Markdown issues and PRs
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants