1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use crate::browser_top_level_impl::BrowserTopLevelImpl;
use crate::interop::{dom_helper, input_helper, JsObject};
use crate::windowing_platform::BrowserWindowingPlatform;
use ferroui_base::input::{FocusChangedEventArgs, InputElement};
use ferroui_base::interactivity::Interactive;
use ferroui_base::Ref;
use ferroui_controls::embedding::EmbeddableControlRoot;
use ferroui_controls::platform::ITopLevelImpl;
use ferroui_controls::{Control, TopLevel};
use std::rc::Rc;
/// Shows content in an element of the web page.
pub struct FerroView {
top_level: Ref<EmbeddableControlRoot>,
}
impl FerroView {
/// Creates a view over the element with the given id.
///
/// `div_id` is the ID of the html element where the content should be
/// rendered.
///
/// # Panics
/// Panics when the document has no such element.
pub fn new(div_id: &str) -> Rc<Self> {
match dom_helper::get_element_by_id(div_id, &BrowserWindowingPlatform::global_this()) {
Some(host) => Self::with_host(host),
None => panic!("Element with id '{div_id}' was not found in the html document."),
}
}
/// Creates a view over an element.
///
/// `host` is the object holding a div element where the content should
/// be rendered.
///
/// # Panics
/// Panics when `host` is null or undefined.
pub fn with_host(host: JsObject) -> Rc<Self> {
if host.is_null() || host.is_undefined() {
panic!("Value cannot be null. (Parameter 'host')");
}
let host_content = match dom_helper::create_ferro_host(&host) {
Some(host_content) => host_content,
None => panic!("The host of the view wasn't initialized."),
};
let native_controls_container = match host_content.native_host() {
Some(native_host) => native_host,
None => panic!("NativeHost cannot be null"),
};
let input_element = match host_content.input_element() {
Some(input_element) => input_element,
None => panic!("InputElement cannot be null"),
};
let browser_top_level = BrowserTopLevelImpl::new(host.clone(), native_controls_container, input_element);
let surface = browser_top_level.surface();
let top_level_impl: Rc<dyn ITopLevelImpl> = browser_top_level;
let top_level = EmbeddableControlRoot::with_impl(top_level_impl);
top_level.prepare();
top_level.add_handler(InputElement::got_focus_event(), {
let host = host.clone();
move |_: &Interactive, _: &FocusChangedEventArgs| input_helper::focus_element(&host)
});
top_level.renderer().start(); // TODO: use Start+StopRenderer() instead.
// Differs from upstream, which closes the splash screen at the first
// animation frame of the top-level, on the thread that draws the
// view. That animation frame comes before the first frame is drawn,
// and with a render thread it is not the thread of the page that
// draws: the first frame of the worker may be seconds away, during
// which the page would show an empty canvas that input cannot hit.
// The splash screen is closed when the first frame has been drawn
// to the canvas of the view, whichever thread drew it.
if let Some(surface) = surface {
surface.shared().on_first_frame(Box::new(move || {
// Try to get local splash-screen of the specific host.
// If couldn't find - get global one by ID for compatibility.
let splash = dom_helper::get_elements_by_class_name("ferroui-splash", &host).or_else(|| {
dom_helper::get_element_by_id("ferroui-splash", &BrowserWindowingPlatform::global_this())
});
if let Some(splash) = splash {
dom_helper::add_css_class(&splash, "splash-close");
}
}));
}
Rc::new(Self { top_level })
}
/// The content of the view.
///
/// # Panics
/// Panics when the content of the top-level was set to something that
/// is not a control.
pub fn content(&self) -> Option<Ref<Control>> {
let content = self.top_level.content()?;
match Control::from_boxed(&content) {
Some(control) => Some(control),
None => panic!("Unable to cast the content of the view to a control."),
}
}
/// Sets the content of the view.
pub fn set_content(&self, value: Option<Ref<Control>>) {
self.top_level.set_content(value.map(Control::boxed));
}
pub(crate) fn top_level(&self) -> Ref<TopLevel> {
self.top_level.clone().upcast()
}
/// Closes the view: its top-level is disposed, which releases the
/// surface of the canvas (on the thread that renders, the render target
/// of the canvas and what drew to it) and stops the renderer of the
/// view. The elements of the view stay in the page. The view cannot be
/// used afterwards.
///
/// Not from upstream, whose view has no way to be closed (the top-level
/// under it is disposable there as here). A page that shows and removes
/// views would otherwise keep the canvas and the graphics context of
/// every view it ever had.
pub fn dispose(&self) {
self.top_level.dispose();
}
}