egui
egui copied to clipboard
Add markdown support to Ui
Here's an example of how you would currently create a ui with multiple elements:
ui.heading("egui");
ui.label("egui is an immediate mode GUI library written in Rust. egui runs both on the web and natively on most platforms. \
On the web it is compiled to WebAssembly and rendered with WebGL.");
ui.label("egui is designed to be easy to use, portable, and fast.");
ui.add_space(12.0);
ui.heading("Immediate mode");
ui.horizontal_wrapped(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("Immediate mode is a GUI paradigm that lets you create a GUI with less code and simpler control flow. For example, this is how you create a ");
let _ = ui.small_button("button");
ui.label(" in egui:");
});
ui.add_space(8.0);
code_view_ui(
ui,
r#"
if ui.button("Save").clicked() {
my_state.save();
}"#
.trim_start_matches('\n'),
);
ui.add_space(8.0);
ui.label("Note how there are no callbacks or messages, and no button state to store.");
ui.label("Immediate mode has its roots in gaming, where everything on the screen is painted at the display refresh rate, i.e. at 60+ frames per second. \
In immediate mode GUIs, the entire interface is layed out and painted at the same high rate. \
This makes immediate mode GUIs especially well suited for highly interactive applications.");
ui.horizontal_wrapped(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
ui.label("More about immediate mode ");
ui.hyperlink_to("here", "https://github.com/emilk/egui#why-immediate-mode");
ui.label(".");
});
As you can see, this is extremely verbose. Especially for simpler text elements, there is no need to have exact control over spacing.
This is what it could look like if egui had a built-in markdown parser.
ui.markdown("
# egui
egui is an immediate mode GUI library written in Rust. egui runs both on the web and natively on most platforms.
On the web it is compiled to WebAssembly and rendered with WebGL.
egui is designed to be easy to use, portable, and fast.
# Immediate mode
Immediate mode is a GUI paradigm that lets you create a GUI with less code and simpler control flow. For example, this is how you create a `button` in egui:
```rust
if ui.button(\"Save\").clicked() {
my_state.save();
}
```
Note how there are no callbacks or messages, and no button state to store.
Immediate mode has its roots in gaming, where everything on the screen is painted at the display refresh rate, i.e. at 60+ frames per second.
In immediate mode GUIs, the entire interface is layed out and painted at the same high rate.
This makes immediate mode GUIs especially well suited for highly interactive applications.
More about immediate mode [here](https://github.com/emilk/egui#why-immediate-mode).
");
Sample implementation with EasyMark*
* EasyMark may not necessarily be the best way to implement the Markdown parser, but implementing it here was significantly easier than implementing another parser from scratch.
Click to expand
use egui::*;
use egui_demo_lib::easy_mark::easy_mark;
use unindent::*;
pub trait UIMarkdown {
fn markdown(&mut self, markdown: &str);
}
impl UIMarkdown for Ui {
fn markdown(&mut self, markdown: &str) {
easy_mark(self, &unindent(markdown));
}
}
Concerns
The performance would be worse than building out the elements manually but the impact would be negligible.
I agree that the verbosity of ui.heading + ui.separator + ui.code` etc is really bad, and I would prefer writing Markdown.
There are some issues that needs to be resolved though.
One is: which markdown? There are many flavors, and no standard.
A bigger problem is hygiene/injection. It will be very tempting for users to do things like:
ui.markdown(format!("Here's some code: `{code}`));
which will work until the code contains a backtick. There is no good solution here, except perhaps to make a markdown macro. For instance:
markdown!("Here is some code: `{code}`").show(ui);
EasyMark is a proof-of-concept to test and show that egui is powerful enough to implement markdown in user-space. I therefor suggest that markdown is not added to the egui crate, but instead into a separate repository as an extension to egui.
I made a commonmark crate a while ago, maybe it can be to some help: https://crates.io/crates/egui_commonmark
Be aware that it's main focus is for rendering documentation.
I wonder if it would be possible to do something like what std::fmt does. Like, for instance, have Label and friends take a Markdown struct to the tune of:
struct Markdown<'a> {
text: &'a [&'a str],
md: &'a [Dir]
}
Then you could have a macro like:
markdown!("Some **bold** text")
that compiles down to:
Markdown {
text: &["Some ", "bold", " text"],
md: &[Dir::Begin(Bold), Dir::End(Bold)],
}
This would be a great way of creating flavor-agnostic rich text, potentially at compile time.
How's this issue goes?
I would even argue that having support for showing html would suffice here, since most flavours of markdown are able to be converted to html. You use some parser in your implementation that converts your md to HTML, then you pass it down to eui.
Like that egui does not need to be specific about markdown, only HTML, which has a way clearer and stable specification right?
I miss the markdown feature too, it would make it very easy to split content and logic.
I would even argue that having support for showing html would suffice here, since most flavours of markdown are able to be converted to html. You use some parser in your implementation that converts your md to HTML, then you pass it down to eui.
Like that egui does not need to be specific about markdown, only HTML, which has a way clearer and stable specification right?
I miss the markdown feature too, it would make it very easy to split content and logic.
Sounds like embedding a webview inside some sort of widget though, haha.
I would even argue that having support for showing html would suffice here, since most flavours of markdown are able to be converted to html. You use some parser in your implementation that converts your md to HTML, then you pass it down to eui. Like that egui does not need to be specific about markdown, only HTML, which has a way clearer and stable specification right? I miss the markdown feature too, it would make it very easy to split content and logic.
Sounds like embedding a webview inside some sort of widget though, haha.
yes :) I mean why not...
I've tested https://github.com/lampsitter/egui_commonmark and I think it works really well. I see no need to integrate markdown support deep into egui if an external crate can do it instead. I suggest that anyone wanting Markdown in egui looks to egui_commonmark and contribute to it if they find it lacking!
egui_commonmark is nice for loading Markdown code but it seems to do the parsing at run time. It would be nice to have something like a macro that would process Markdown code at compile time and emit egui calls.