Fix delayed docs preview not working

After commit 7df902b53c, the winit window
is not created when calling show() anymore but it's now created at
component creation time.  That means the event loop workaround removed
in 459f810bd8 is now needed at
construction time.

Since this is a winit and wasm specific issue, it's now dealt with in
the wasm interpreter implementation, by invoking the creation from the
event loop from there and returning a promise in the API.

This changes the API therefore: create() can only be called after the
event loop is running.
This commit is contained in:
Simon Hausmann 2023-06-01 11:14:23 +02:00 committed by Simon Hausmann
parent c428601370
commit a8fcb5acd6
8 changed files with 65 additions and 13 deletions

View file

@ -94,6 +94,7 @@ rust-version = "1.66"
resvg = { version= "0.32.0", default-features = false, features = ["text"] }
fontdb = { version = "0.13.1", default-features = false }
yeslogic-fontconfig-sys = { version = "3.2.0", features = ["dlopen"] }
send_wrapper = { version = "0.6.0" }
[profile.release]
lto = true

View file

@ -20,6 +20,7 @@ highlight = ["slint-interpreter/highlight"]
[dependencies]
slint-interpreter = { path = "../../internal/interpreter", default-features = false, features = ["std", "backend-winit", "renderer-winit-femtovg", "compat-1-0"] }
send_wrapper = { workspace = true }
vtable = { version = "0.1.6", path="../../helper_crates/vtable" }

View file

@ -46,6 +46,8 @@ extern "C" {
#[wasm_bindgen(typescript_type = "CurrentElementInformationCallbackFunction")]
pub type CurrentElementInformationCallbackFunction;
#[wasm_bindgen(typescript_type = "Promise<WrappedInstance>")]
pub type InstancePromise;
}
/// Compile the content of a string.
@ -155,14 +157,35 @@ impl WrappedCompiledComp {
let component = self.0.create_with_canvas_id(&canvas_id).unwrap();
component.run().unwrap();
}
/// Creates this compiled component in a canvas.
/// Creates this compiled component in a canvas, wrapped in a promise.
/// The HTML must contains a <canvas> element with the given `canvas_id`
/// where the result is gonna be rendered.
/// You need to call `show()` on the returned instance for rendering and
/// `slint.run_event_loop()` loop to make it interactive.
/// You need to call `show()` on the returned instance for rendering.
///
/// Note that the promise will only be resolved after calling `slint.run_event_loop()`.
#[wasm_bindgen]
pub fn create(&self, canvas_id: String) -> Result<WrappedInstance, JsValue> {
Ok(WrappedInstance(self.0.create_with_canvas_id(&canvas_id).unwrap()))
pub fn create(&self, canvas_id: String) -> Result<InstancePromise, JsValue> {
Ok(JsValue::from(js_sys::Promise::new(&mut |resolve, reject| {
let comp = send_wrapper::SendWrapper::new(self.0.clone());
let canvas_id = canvas_id.clone();
let resolve = send_wrapper::SendWrapper::new(resolve);
if let Err(e) = slint_interpreter::invoke_from_event_loop(move || {
let instance =
WrappedInstance(comp.take().create_with_canvas_id(&canvas_id).unwrap());
resolve.take().call1(&JsValue::UNDEFINED, &JsValue::from(instance)).unwrap_throw();
}) {
reject
.call1(
&JsValue::UNDEFINED,
&JsValue::from(
format!("internal error: Failed to queue closure for event loop invocation: {e}"),
),
)
.unwrap_throw();
}
})).unchecked_into::<InstancePromise>())
//Ok()
}
/// Creates this compiled component in the canvas of the provided instance.
/// For this to work, the provided instance needs to be visible (show() must've been

View file

@ -25,7 +25,7 @@
div.innerHTML = "<pre style='color: red; background-color:#fee; margin:0'>" + p.innerHTML + "</pre>";
}
if (component !== undefined) {
let instance = component.create(canvas_id);
let instance = await component.create(canvas_id);
instance.show();
all_instances.push(instance);
}
@ -81,13 +81,23 @@
async function run() {
await slint.default();
try {
slint.run_event_loop();
// this will trigger a JS exception, so this line will never be reached!
} catch (e) {
// The winit event loop, when targeting wasm, throws a JavaScript exception to break out of
// Rust without running any destructors. Don't rethrow the exception but swallow it, as
// this is no error and we truly want to resolve the promise of this function by returning
// the model markers.
}
let selector = ["code.language-slint", ".rustdoc pre.language-slint", "div.highlight-slint div.highlight", "div.highlight-slint\\,no-auto-preview div.highlight"]
.map((sel) => `${sel}:not([class*=slint\\,ignore]):not([class*=slint\\,no-preview])`).join(",");
var elements = document.querySelectorAll(selector);
for (var i = 0; i < elements.length; ++i) {
await create_click_to_play_and_edit_buttons(elements[i]);
}
slint.run_event_loop();
}
run();

View file

@ -239,9 +239,13 @@ function getPreviewHtml(slint_wasm_interpreter_url: Uri): string {
if (current_instance !== null) {
current_instance = component.create_with_existing_window(current_instance);
} else {
current_instance = component.create("slint_canvas");
try {
slint.run_event_loop();
} catch (e) {
// ignore winit event loop exception
}
current_instance = await component.create("slint_canvas");
current_instance.show();
slint.run_event_loop();
}
current_instance?.set_design_mode(design_mode);
current_instance?.on_element_selected(element_selected);
@ -354,7 +358,7 @@ function initPreviewPanel(
);
const outside_uri = Uri.parse(
uriMapping.get(d.url) ??
Uri.file(inside_uri.fsPath).toString(),
Uri.file(inside_uri.fsPath).toString(),
);
if (outside_uri.scheme !== "invalid") {
vscode.window.showTextDocument(outside_uri, {

View file

@ -64,7 +64,7 @@ rgb = { version = "0.8.27", optional = true }
[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = { version = "0.3", features=["HtmlInputElement", "HtmlCanvasElement", "Window", "Document", "Event", "KeyboardEvent", "InputEvent", "CompositionEvent", "DomStringMap", "ClipboardEvent", "DataTransfer"] }
wasm-bindgen = { version = "0.2" }
send_wrapper = "0.6.0"
send_wrapper = { workspace = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
glutin = { version = "0.30", optional = true, default-features = false, features = ["egl", "wgl"] }

View file

@ -746,6 +746,19 @@ pub enum EventLoopError {
NoEventLoopProvider,
}
impl core::fmt::Display for EventLoopError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
EventLoopError::EventLoopTerminated => {
f.write_str("The event loop was already terminated")
}
EventLoopError::NoEventLoopProvider => {
f.write_str("The Slint platform do not provide an event loop")
}
}
}
}
/// The platform encountered a fatal error.
///
/// This error typically indicates an issue with initialization or connecting to the windowing system.

View file

@ -275,8 +275,6 @@ class PreviewerBackend {
// It's not enough for the canvas element to exist, in order to extract a webgl rendering
// context, the element needs to be attached to the window's dom.
if (this.#instance == null) {
this.#instance = component.create(this.canvas_id!); // eslint-disable-line
this.#instance.show();
try {
if (!is_event_loop_running) {
slint_preview.run_event_loop();
@ -289,6 +287,8 @@ class PreviewerBackend {
// the model markers.
is_event_loop_running = true; // Assume the winit caused the exception and that the event loop is up now
}
this.#instance = await component.create(this.canvas_id!); // eslint-disable-line
this.#instance.show();
} else {
this.#instance = component.create_with_existing_window(
this.#instance,