diff --git a/CHANGELOG.md b/CHANGELOG.md index 00f199a..93e153a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Added - Callback function `render_math_fn` for custom math rendering +- Callback function `render_html_fn` for custom html rendering ### Fixed diff --git a/egui_commonmark/examples/html.rs b/egui_commonmark/examples/html.rs new file mode 100644 index 0000000..35ca924 --- /dev/null +++ b/egui_commonmark/examples/html.rs @@ -0,0 +1,81 @@ +//! Add `light` or `dark` to the end of the command to specify theme. Default +//! is light. `cargo r --example html -- dark` + +use eframe::egui; +use egui_commonmark::*; +use std::cell::RefCell; +use std::rc::Rc; + +struct App { + cache: CommonMarkCache, + /// To avoid id collisions + counter: Rc>, +} + +impl eframe::App for App { + fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { + *self.counter.as_ref().borrow_mut() = 0; + + egui::CentralPanel::default().show(ctx, |ui| { + egui::ScrollArea::vertical().show(ui, |ui| { + CommonMarkViewer::new() + .render_html_fn({ + let counter = Rc::clone(&self.counter); + Some(&move |ui, html| { + // For simplicity lets just hide the content regardless of what kind of + // node it is. + ui.collapsing( + format!("Collapsed {}", counter.as_ref().borrow()), + |ui| { + ui.label(html); + }, + ); + + *counter.as_ref().borrow_mut() += 1; + }) + }) + .show(ui, &mut self.cache, EXAMPLE_TEXT); + }); + }); + } +} + +fn main() -> eframe::Result { + let mut args = std::env::args(); + args.next(); + + eframe::run_native( + "Markdown viewer", + eframe::NativeOptions::default(), + Box::new(move |cc| { + if let Some(theme) = args.next() { + if theme == "light" { + cc.egui_ctx.set_theme(egui::Theme::Light); + } else if theme == "dark" { + cc.egui_ctx.set_theme(egui::Theme::Dark); + } + } + + cc.egui_ctx.style_mut(|style| { + // Show the url of a hyperlink on hover + style.url_in_tooltip = true; + }); + + Ok(Box::new(App { + cache: CommonMarkCache::default(), + counter: Rc::new(RefCell::new(0)), + })) + }), + ) +} + +const EXAMPLE_TEXT: &str = r#" +# Customized rendering using html +

+some text +

+ +

+some text 2 +

+"#; diff --git a/egui_commonmark/src/lib.rs b/egui_commonmark/src/lib.rs index b30d540..9c219ff 100644 --- a/egui_commonmark/src/lib.rs +++ b/egui_commonmark/src/lib.rs @@ -209,8 +209,15 @@ impl<'f> CommonMarkViewer<'f> { /// ); /// })); /// ``` - pub fn render_math_fn(mut self, math_fn: Option<&'f RenderMathFn>) -> Self { - self.options.math_fn = math_fn; + pub fn render_math_fn(mut self, func: Option<&'f RenderMathFn>) -> Self { + self.options.math_fn = func; + self + } + + /// Allows custom handling of html. Enabling this will disable plain text rendering + /// of html blocks. Nodes are included in the provided text + pub fn render_html_fn(mut self, func: Option<&'f RenderHtmlFn>) -> Self { + self.options.html_fn = func; self } diff --git a/egui_commonmark/src/parsers/pulldown.rs b/egui_commonmark/src/parsers/pulldown.rs index e671324..83e56bc 100644 --- a/egui_commonmark/src/parsers/pulldown.rs +++ b/egui_commonmark/src/parsers/pulldown.rs @@ -72,6 +72,9 @@ pub struct CommonMarkViewerInternal { image: Option, line: Newline, code_block: Option, + + /// Only populated if the html_fn option has been set + html_block: String, is_list_item: bool, def_list: DefinitionList, is_table: bool, @@ -96,6 +99,7 @@ impl CommonMarkViewerInternal { is_list_item: false, def_list: Default::default(), code_block: None, + html_block: String::new(), is_table: false, is_blockquote: false, checkbox_events: Vec::new(), @@ -520,8 +524,13 @@ impl CommonMarkViewerInternal { pulldown_cmark::Event::InlineHtml(text) => { self.event_text(text, ui); } - pulldown_cmark::Event::Html(node) => { - self.event_text(node, ui); + + pulldown_cmark::Event::Html(text) => { + if options.html_fn.is_some() { + self.html_block.push_str(&text); + } else { + self.event_text(text, ui); + } } pulldown_cmark::Event::FootnoteReference(footnote) => { footnote_start(ui, &footnote); @@ -755,7 +764,13 @@ impl CommonMarkViewerInternal { image.end(ui, options); } } - pulldown_cmark::TagEnd::HtmlBlock => {} + pulldown_cmark::TagEnd::HtmlBlock => { + if let Some(html_fn) = options.html_fn { + html_fn(ui, &self.html_block); + self.html_block.clear(); + } + } + pulldown_cmark::TagEnd::MetadataBlock(_) => {} pulldown_cmark::TagEnd::DefinitionList => self.line.try_insert_end(ui), diff --git a/egui_commonmark_backend/src/lib.rs b/egui_commonmark_backend/src/lib.rs index 09f12e7..8b00be9 100644 --- a/egui_commonmark_backend/src/lib.rs +++ b/egui_commonmark_backend/src/lib.rs @@ -28,3 +28,5 @@ pub use misc::CommonMarkCache; /// Takes [`egui::Ui`], the math text to be rendered and whether it is inline pub type RenderMathFn = dyn Fn(&mut egui::Ui, &str, bool); +/// Takes [`egui::Ui`] and the html text to be rendered/used +pub type RenderHtmlFn = dyn Fn(&mut egui::Ui, &str); diff --git a/egui_commonmark_backend/src/misc.rs b/egui_commonmark_backend/src/misc.rs index 65d6a62..0630260 100644 --- a/egui_commonmark_backend/src/misc.rs +++ b/egui_commonmark_backend/src/misc.rs @@ -32,6 +32,7 @@ pub struct CommonMarkOptions<'f> { /// Whether to present a mutable ui for things like checkboxes pub mutable: bool, pub math_fn: Option<&'f crate::RenderMathFn>, + pub html_fn: Option<&'f crate::RenderHtmlFn>, } impl<'f> std::fmt::Debug for CommonMarkOptions<'f> { @@ -74,6 +75,7 @@ impl Default for CommonMarkOptions<'_> { alerts: AlertBundle::gfm(), mutable: false, math_fn: None, + html_fn: None, } } }