Skip to main content

dais_ui/presenter/
notes_panel.rs

1//! Notes panel with Markdown rendering.
2//!
3//! Renders the current slide's speaker notes using `egui_commonmark`.
4
5use dais_core::bus::CommandSender;
6use dais_core::commands::Command;
7
8pub struct NotesPanelView<'a> {
9    pub notes: Option<&'a str>,
10    pub font_size: f32,
11    pub visible: bool,
12    pub editing: bool,
13}
14
15/// Presenter notes panel.
16pub struct NotesPanel {
17    cache: egui_commonmark::CommonMarkCache,
18}
19
20impl NotesPanel {
21    pub fn new() -> Self {
22        Self { cache: egui_commonmark::CommonMarkCache::default() }
23    }
24
25    /// Render the notes panel in the given area.
26    pub fn show(
27        &mut self,
28        ui: &mut egui::Ui,
29        area: egui::Rect,
30        view: &NotesPanelView<'_>,
31        sender: &CommandSender,
32    ) {
33        if !view.visible {
34            return;
35        }
36
37        let mut child_ui = ui.new_child(egui::UiBuilder::new().max_rect(area));
38
39        child_ui.scope_builder(
40            egui::UiBuilder::new()
41                .max_rect(egui::Rect::from_min_size(area.min, egui::vec2(area.width(), 20.0))),
42            |ui| {
43                ui.colored_label(egui::Color32::GRAY, "Notes");
44            },
45        );
46
47        let content_area = egui::Rect::from_min_size(
48            area.min + egui::vec2(8.0, 24.0),
49            egui::vec2((area.width() - 16.0).max(1.0), (area.height() - 28.0).max(1.0)),
50        );
51
52        child_ui.scope_builder(egui::UiBuilder::new().max_rect(content_area), |ui| {
53            ui.style_mut().override_font_id = Some(egui::FontId::proportional(view.font_size));
54
55            if view.editing {
56                // Slightly lighter dark grey box so the editing state is visually distinct.
57                ui.visuals_mut().extreme_bg_color = egui::Color32::from_gray(45);
58                ui.visuals_mut().override_text_color = Some(egui::Color32::WHITE);
59                ui.visuals_mut().widgets.active.fg_stroke.color = egui::Color32::WHITE;
60                let mut buffer = view.notes.unwrap_or_default().to_string();
61                let edit = egui::TextEdit::multiline(&mut buffer)
62                    .desired_width(f32::INFINITY)
63                    .desired_rows(12)
64                    .hint_text("Write speaker notes in Markdown");
65                let response = ui.add_sized(content_area.size(), edit);
66                if response.changed() {
67                    let _ = sender.send(Command::SetCurrentSlideNotes(buffer));
68                }
69            } else {
70                ui.visuals_mut().override_text_color = Some(egui::Color32::WHITE);
71                ui.visuals_mut().widgets.active.fg_stroke.color = egui::Color32::WHITE;
72                egui::ScrollArea::vertical().max_height(content_area.height()).show(ui, |ui| {
73                    if let Some(text) = view.notes {
74                        egui_commonmark::CommonMarkViewer::new().show(ui, &mut self.cache, text);
75                    } else {
76                        ui.colored_label(egui::Color32::from_gray(80), "No notes for this slide");
77                    }
78                });
79            }
80        });
81    }
82}
83
84impl Default for NotesPanel {
85    fn default() -> Self {
86        Self::new()
87    }
88}