Skip to main content

hframe/
html_window.rs

1use crate::{
2    get_composition_context, utils::egui::eid, ComposedArea, ComposedHtml, ComposedHtmlStatus,
3};
4
5/// A window capable of displaying HTML content inside.
6///
7/// It's API mimics egui's Window API.
8///
9/// Note: `hframe` is automatically aware of this window.
10pub struct HtmlWindow<'open> {
11    pub(crate) id: String,
12    pub(crate) title: String,
13    pub(crate) content: String,
14    pub(crate) open: Option<&'open mut bool>,
15}
16
17impl<'open> HtmlWindow<'open> {
18    /// Create a new HtmlWindow.
19    ///
20    /// This function mimics `new` from egui's Window. It takes the window title
21    /// which must be unique as it is used to compute the window id and also to
22    /// set ids for HTML elements. Check the `id` method if you want to set a
23    /// different id.
24    pub fn new(title: &str) -> Self {
25        Self {
26            id: title.to_lowercase().replace(' ', "-"),
27            title: title.to_string(),
28            content: "".into(),
29            open: None,
30        }
31    }
32
33    /// Mimics the `open` method of egui's Window.
34    pub fn open(mut self, open: &'open mut bool) -> Self {
35        self.open = Some(open);
36        self
37    }
38
39    /// Set a specific id explicitly.
40    pub fn id(mut self, id: &str) -> Self {
41        self.id = id.to_string();
42        self
43    }
44
45    /// Set/change the HTML content of the window.
46    ///
47    /// The initially provided HTML will be used to generete the HTML element.
48    /// As long as the HTML doesn't change, this will not re-render the content.
49    ///
50    /// If you change the content, then the HTML will be re-rendered which is
51    /// useful if you need to display controlled and reactive content.
52    pub fn content(mut self, content: &str) -> Self {
53        self.content = content.to_string();
54        self
55    }
56
57    /// Displays the window and it's content.
58    ///
59    /// Note: You will still need to call `sync` at the end of the update loop
60    /// to make this work propertly.
61    pub fn show(self, ctx: &egui::Context) {
62        let Self {
63            id,
64            title,
65            content,
66            open,
67        } = self;
68
69        let open = if let Some(open) = open {
70            if !*open {
71                return;
72            }
73
74            Some(open)
75        } else {
76            None
77        };
78
79        // tel ctx to render html here
80
81        let window = egui::Window::new(title).id(eid!(&id));
82        let window = match open {
83            Some(open) => window.open(open),
84            None => window,
85        };
86
87        let shown_window = window.show(ctx, |ui| {
88            ui.centered_and_justified(|ui| {
89                ui.label("");
90            })
91            .response
92            .rect
93        });
94
95        if let Some(inner_response) = shown_window {
96            let cmp = get_composition_context(ctx);
97            let cmp = &mut *cmp.lock().unwrap();
98            let ctx = &cmp.egui_ctx;
99
100            let html_visible = inner_response.inner.is_some();
101            let html_rect = inner_response.inner.unwrap_or(egui::Rect::ZERO);
102            let html_interactive = ctx
103                .input(|i| !i.pointer.button_down(egui::PointerButton::Primary))
104                && ctx.top_layer_id() == Some(inner_response.response.layer_id);
105
106            cmp.put_composed_area(ComposedArea {
107                id: inner_response.response.layer_id.id,
108                rect: inner_response.response.rect,
109                html: Some(ComposedHtml {
110                    id,
111                    content,
112                    rect: html_rect,
113                    status: ComposedHtmlStatus {
114                        interactive: html_interactive,
115                        visible: html_visible,
116                    },
117                }),
118            })
119        }
120    }
121}