mirror of
https://github.com/slint-ui/slint.git
synced 2025-12-23 09:19:32 +00:00
Initial implementation of a Slint event loop sitting on top of Node.js
At the moment this is implemented using polling. cc #2477
This commit is contained in:
parent
ee9f1a52a8
commit
7b61e455eb
17 changed files with 361 additions and 66 deletions
|
|
@ -24,10 +24,12 @@ napi = { version = "2.12.0", default-features = false, features = ["napi8"] }
|
|||
napi-derive = "2.12.2"
|
||||
i-slint-compiler = { workspace = true, features = ["default"] }
|
||||
i-slint-core = { workspace = true, features = ["default"] }
|
||||
i-slint-backend-selector = { workspace = true }
|
||||
slint-interpreter = { workspace = true, features = ["default", "display-diagnostics", "internal"] }
|
||||
spin_on = "0.1"
|
||||
css-color-parser2 = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
send_wrapper = { workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
napi-build = "2.0.1"
|
||||
|
|
|
|||
80
api/node/__test__/eventloop.spec.ts
Normal file
80
api/node/__test__/eventloop.spec.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
// Test that the Slint event loop processes libuv's events.
|
||||
|
||||
import test from 'ava'
|
||||
import * as http from 'http';
|
||||
import fetch from "node-fetch";
|
||||
|
||||
import { run_event_loop, quit_event_loop, private_api } from '../index'
|
||||
|
||||
|
||||
test.serial('merged event loops with timer', async (t) => {
|
||||
|
||||
let invoked = false;
|
||||
|
||||
await run_event_loop(() => {
|
||||
|
||||
setTimeout(() => {
|
||||
invoked = true;
|
||||
quit_event_loop();
|
||||
}, 2);
|
||||
});
|
||||
t.true(invoked)
|
||||
})
|
||||
|
||||
|
||||
test.serial('merged event loops with networking', async (t) => {
|
||||
const listener = (request, result) => {
|
||||
result.writeHead(200);
|
||||
result.end("Hello World");
|
||||
};
|
||||
|
||||
let received_response = "";
|
||||
|
||||
await run_event_loop(() => {
|
||||
|
||||
const server = http.createServer(listener);
|
||||
server.listen(async () => {
|
||||
let host = "localhost";
|
||||
let port = (server.address() as any).port;
|
||||
console.log(`server ready at ${host}:${port}`);
|
||||
|
||||
fetch(`http://${host}:${port}/`).then(async (response) => {
|
||||
return response.text();
|
||||
}).then((text) => {
|
||||
received_response = text;
|
||||
//console.log("received ", text);
|
||||
quit_event_loop();
|
||||
server.close();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
t.is(received_response, "Hello World");
|
||||
})
|
||||
|
||||
test.serial('quit event loop on last window closed', async (t) => {
|
||||
let compiler = new private_api.ComponentCompiler;
|
||||
let definition = compiler.buildFromSource(`
|
||||
|
||||
export component App inherits Window {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
}`, "");
|
||||
t.not(definition, null);
|
||||
|
||||
let instance = definition!.create() as any;
|
||||
t.not(instance, null);
|
||||
|
||||
instance.window().show();
|
||||
await run_event_loop(() => {
|
||||
setTimeout(() => {
|
||||
instance.window().hide();
|
||||
}, 2);
|
||||
});
|
||||
|
||||
})
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import test from 'ava';
|
||||
|
||||
import { SlintBrush, SlintRgbaColor, Brush, ArrayModel, Timer } from '../index'
|
||||
import { SlintBrush, SlintRgbaColor, Brush, ArrayModel } from '../index'
|
||||
|
||||
test('SlintColor from fromRgb', (t) => {
|
||||
let color = SlintRgbaColor.fromRgb(100, 110, 120);
|
||||
|
|
@ -56,7 +56,7 @@ test('SlintBrush from RgbaColor', (t) => {
|
|||
})
|
||||
|
||||
test('SlintBrush from Brush', (t) => {
|
||||
let brush = SlintBrush.fromBrush({ color: { red: 100, green: 110, blue: 120, alpha: 255 }});
|
||||
let brush = SlintBrush.fromBrush({ color: { red: 100, green: 110, blue: 120, alpha: 255 } });
|
||||
|
||||
t.deepEqual(brush.color.red, 100);
|
||||
t.deepEqual(brush.color.green, 110);
|
||||
|
|
@ -105,15 +105,3 @@ test('ArrayModel remove', (t) => {
|
|||
t.is(arrayModel.rowCount(), 1);
|
||||
t.is(arrayModel.rowData(0), 1);
|
||||
})
|
||||
|
||||
test('Timer negative duration', (t) => {
|
||||
t.throws(() => {
|
||||
Timer.singleShot(-1, function () { })
|
||||
},
|
||||
{
|
||||
code: "GenericFailure",
|
||||
message: "Duration cannot be negative"
|
||||
}
|
||||
);
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -182,10 +182,10 @@ export abstract class Model<T> {
|
|||
* @hidden
|
||||
*/
|
||||
class NullPeer {
|
||||
rowDataChanged(row: number): void {}
|
||||
rowAdded(row: number, count: number): void {}
|
||||
rowRemoved(row: number, count: number): void {}
|
||||
reset(): void {}
|
||||
rowDataChanged(row: number): void { }
|
||||
rowAdded(row: number, count: number): void { }
|
||||
rowRemoved(row: number, count: number): void { }
|
||||
reset(): void { }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -260,9 +260,13 @@ export class ArrayModel<T> extends Model<T> {
|
|||
*/
|
||||
export interface ComponentHandle {
|
||||
/**
|
||||
* Shows the window and runs the event loop.
|
||||
* Shows the window and runs the event loop. The returned promise is resolved when the event loop
|
||||
* is terminated, for example when the last window was closed, or {@link quit_event_loop} was called.
|
||||
*
|
||||
* This function is a convenience for calling {@link show}, followed by {@link run_event_loop}, and
|
||||
* {@link hide} when the event loop's promise is resolved.
|
||||
*/
|
||||
run();
|
||||
run(): Promise<unknown>;
|
||||
|
||||
/**
|
||||
* Shows the component's window on the screen.
|
||||
|
|
@ -294,8 +298,10 @@ class Component implements ComponentHandle {
|
|||
this.instance = instance;
|
||||
}
|
||||
|
||||
run() {
|
||||
this.instance.run();
|
||||
async run() {
|
||||
this.show();
|
||||
await run_event_loop();
|
||||
this.hide();
|
||||
}
|
||||
|
||||
show() {
|
||||
|
|
@ -530,12 +536,78 @@ export function loadFile(filePath: string, options?: LoadFileOptions): Object {
|
|||
return slint_module;
|
||||
}
|
||||
|
||||
// This api will be removed after teh event loop handling is merged check PR #3718.
|
||||
// After that this in no longer necessary.
|
||||
export namespace Timer {
|
||||
export function singleShot(duration: number, handler: () => void) {
|
||||
napi.singleshotTimer(duration, handler);
|
||||
class EventLoop {
|
||||
#quit_loop: boolean = false;
|
||||
#termination_promise: Promise<unknown> | null = null;
|
||||
#terminate_resolve_fn: ((_value: unknown) => void) | null;
|
||||
constructor() {
|
||||
}
|
||||
|
||||
start(running_callback?: Function): Promise<unknown> {
|
||||
if (this.#termination_promise != null) {
|
||||
return this.#termination_promise;
|
||||
}
|
||||
|
||||
this.#termination_promise = new Promise((resolve) => {
|
||||
this.#terminate_resolve_fn = resolve;
|
||||
});
|
||||
this.#quit_loop = false;
|
||||
|
||||
if (running_callback != undefined) {
|
||||
napi.invokeFromEventLoop(() => {
|
||||
running_callback();
|
||||
running_callback = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
// Give the nodejs event loop 16 ms to tick. This polling is sub-optimal, but it's the best we
|
||||
// can do right now.
|
||||
const nodejsPollInterval = 16;
|
||||
let id = setInterval(() => {
|
||||
if (napi.processEvents() == napi.ProcessEventsResult.Exited || this.#quit_loop) {
|
||||
clearInterval(id);
|
||||
this.#terminate_resolve_fn!(undefined);
|
||||
this.#terminate_resolve_fn = null;
|
||||
this.#termination_promise = null;
|
||||
return;
|
||||
}
|
||||
}, nodejsPollInterval);
|
||||
|
||||
return this.#termination_promise;
|
||||
}
|
||||
|
||||
quit() {
|
||||
this.#quit_loop = true;
|
||||
}
|
||||
}
|
||||
|
||||
var global_event_loop: EventLoop = new EventLoop;
|
||||
|
||||
/**
|
||||
* Spins the Slint event loop and returns a promise that resolves when the loop terminates.
|
||||
*
|
||||
* If the event loop is already running, then this function returns the same promise as from
|
||||
* the earlier invocation.
|
||||
*
|
||||
* @param running_callback Optional callback that's invoked once when the event loop is running.
|
||||
* The function's return value is ignored.
|
||||
*
|
||||
* Note that the event loop integration with Node.js is slightly imperfect. Due to conflicting
|
||||
* implementation details between Slint's and Node.js' event loop, the two loops are merged
|
||||
* by spinning one after the other, at 16 millisecond intervals. This means that when the
|
||||
* application is idle, it continues to consume a low amount of CPU cycles, checking if either
|
||||
* event loop has any pending events.
|
||||
*/
|
||||
export function run_event_loop(running_callback?: Function): Promise<unknown> {
|
||||
return global_event_loop.start(running_callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops a spinning event loop. This function returns immediately, and the promise returned
|
||||
from run_event_loop() will resolve in a later tick of the nodejs event loop.
|
||||
*/
|
||||
export function quit_event_loop() {
|
||||
global_event_loop.quit()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"@swc-node/register": "^1.5.5",
|
||||
"@swc/core": "^1.3.32",
|
||||
"@types/node": "^20.8.6",
|
||||
"@types/node-fetch": "^2.6.7",
|
||||
"ava": "^5.3.0",
|
||||
"jimp": "^0.22.8",
|
||||
"typedoc": "^0.25.2"
|
||||
|
|
|
|||
|
|
@ -35,11 +35,6 @@ impl JsComponentInstance {
|
|||
self.inner.definition().into()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn run(&self) {
|
||||
self.inner.run().unwrap()
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn get_property(&self, env: Env, name: String) -> Result<JsUnknown> {
|
||||
let value = self
|
||||
|
|
|
|||
|
|
@ -7,8 +7,7 @@ pub use interpreter::*;
|
|||
mod types;
|
||||
pub use types::*;
|
||||
|
||||
mod timer;
|
||||
pub use timer::*;
|
||||
use napi::{bindgen_prelude::*, Env, JsFunction};
|
||||
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
|
@ -17,3 +16,42 @@ extern crate napi_derive;
|
|||
pub fn mock_elapsed_time(ms: f64) {
|
||||
i_slint_core::tests::slint_mock_elapsed_time(ms as _);
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub enum ProcessEventsResult {
|
||||
Continue,
|
||||
Exited,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn process_events() -> napi::Result<ProcessEventsResult> {
|
||||
i_slint_backend_selector::with_platform(|b| {
|
||||
b.process_events(std::time::Duration::ZERO, i_slint_core::InternalToken)
|
||||
})
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
.and_then(|result| {
|
||||
Ok(match result {
|
||||
core::ops::ControlFlow::Continue(()) => ProcessEventsResult::Continue,
|
||||
core::ops::ControlFlow::Break(()) => ProcessEventsResult::Exited,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn invoke_from_event_loop(env: Env, callback: JsFunction) -> napi::Result<napi::JsUndefined> {
|
||||
i_slint_backend_selector::with_platform(|_b| {
|
||||
// Nothing to do, just make sure a backend was created
|
||||
Ok(())
|
||||
})
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))?;
|
||||
|
||||
let function_ref = RefCountedReference::new(&env, callback)?;
|
||||
let function_ref = send_wrapper::SendWrapper::new(function_ref);
|
||||
i_slint_core::api::invoke_from_event_loop(move || {
|
||||
let function_ref = function_ref.take();
|
||||
let callback: JsFunction = function_ref.get().unwrap();
|
||||
callback.call_without_args(None).ok();
|
||||
})
|
||||
.map_err(|e| napi::Error::from_reason(e.to_string()))
|
||||
.and_then(|_| env.get_undefined())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
use napi::{Env, JsFunction, Result};
|
||||
|
||||
use crate::RefCountedReference;
|
||||
|
||||
/// Starts the timer with the duration, in order for the callback to called when the timer fires. It is fired only once and then deleted.
|
||||
#[napi]
|
||||
pub fn singleshot_timer(env: Env, duration_in_msecs: f64, handler: JsFunction) -> Result<()> {
|
||||
if duration_in_msecs < 0. {
|
||||
return Err(napi::Error::from_reason("Duration cannot be negative"));
|
||||
}
|
||||
let duration_in_msecs = duration_in_msecs as u64;
|
||||
|
||||
let handler_ref = RefCountedReference::new(&env, handler)?;
|
||||
|
||||
i_slint_core::timers::Timer::single_shot(
|
||||
std::time::Duration::from_millis(duration_in_msecs),
|
||||
move || {
|
||||
let callback: JsFunction = handler_ref.get().unwrap();
|
||||
callback.call_without_args(None).unwrap();
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "esnext"
|
||||
"target": "esnext",
|
||||
"esModuleInterop": true,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,13 +50,13 @@ mainWindow.check_if_pair_solved = function () {
|
|||
model.setRowData(tile2_index, tile2);
|
||||
} else {
|
||||
mainWindow.disable_tiles = true;
|
||||
slint.Timer.singleShot(1000, () => {
|
||||
setTimeout(() => {
|
||||
mainWindow.disable_tiles = false;
|
||||
tile1.image_visible = false;
|
||||
model.setRowData(tile1_index, tile1);
|
||||
tile2.image_visible = false;
|
||||
model.setRowData(tile2_index, tile2);
|
||||
})
|
||||
}, 1000)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,13 +48,13 @@ window.check_if_pair_solved = function () {
|
|||
model.setRowData(tile2_index, tile2);
|
||||
} else {
|
||||
window.disable_tiles = true;
|
||||
slint.Timer.singleShot(1000, () => {
|
||||
setTimeout(() => {
|
||||
window.disable_tiles = false;
|
||||
tile1.image_visible = false;
|
||||
model.setRowData(tile1_index, tile1);
|
||||
tile2.image_visible = false;
|
||||
model.setRowData(tile2_index, tile2);
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,6 +169,32 @@ impl i_slint_core::platform::Platform for Backend {
|
|||
Err("Qt platform requested but Slint is compiled without Qt support".into())
|
||||
}
|
||||
|
||||
fn process_events(
|
||||
&self,
|
||||
_timeout: core::time::Duration,
|
||||
_: i_slint_core::InternalToken,
|
||||
) -> Result<core::ops::ControlFlow<()>, PlatformError> {
|
||||
#[cfg(not(no_qt))]
|
||||
{
|
||||
// Schedule any timers with Qt that were set up before this event loop start.
|
||||
crate::qt_window::timer_event();
|
||||
use cpp::cpp;
|
||||
let timeout_ms: i32 = _timeout.as_millis() as _;
|
||||
let loop_was_quit = cpp! {unsafe [timeout_ms as "int"] -> bool as "bool" {
|
||||
ensure_initialized(true);
|
||||
qApp->processEvents(QEventLoop::AllEvents, timeout_ms);
|
||||
return std::exchange(g_lastWindowClosed, false);
|
||||
} };
|
||||
Ok(if loop_was_quit {
|
||||
core::ops::ControlFlow::Break(())
|
||||
} else {
|
||||
core::ops::ControlFlow::Continue(())
|
||||
})
|
||||
}
|
||||
#[cfg(no_qt)]
|
||||
Err("Qt platform requested but Slint is compiled without Qt support".into())
|
||||
}
|
||||
|
||||
#[cfg(not(no_qt))]
|
||||
fn new_event_loop_proxy(&self) -> Option<Box<dyn i_slint_core::platform::EventLoopProxy>> {
|
||||
struct Proxy;
|
||||
|
|
|
|||
|
|
@ -157,6 +157,8 @@ cpp! {{
|
|||
|
||||
using QPainterPtr = std::unique_ptr<QPainter>;
|
||||
|
||||
static bool g_lastWindowClosed = false; // Wohoo, global to track window closure when using processEvents().
|
||||
|
||||
/// Make sure there is an instance of QApplication.
|
||||
/// The `from_qt_backend` argument specifies if we know that we are running
|
||||
/// the Qt backend, or if we are just drawing widgets
|
||||
|
|
|
|||
|
|
@ -1529,11 +1529,25 @@ impl WindowAdapter for QtWindow {
|
|||
self.rendering_metrics_collector.take();
|
||||
let widget_ptr = self.widget_ptr();
|
||||
cpp! {unsafe [widget_ptr as "QWidget*"] {
|
||||
|
||||
bool wasVisible = widget_ptr->isVisible();
|
||||
|
||||
widget_ptr->hide();
|
||||
// Since we don't call close(), this will force Qt to recompute wether there are any
|
||||
// visible windows, and ends the application if needed
|
||||
auto _locker = QEventLoopLocker();
|
||||
|
||||
// Compute the same thing also manually, when the event loop is driven by processEvents
|
||||
// like in the NodeJS port.
|
||||
if (wasVisible) {
|
||||
auto windows = QGuiApplication::topLevelWindows();
|
||||
bool visible_windows_left = std::any_of(windows.begin(), windows.end(), [](auto window) {
|
||||
return window->isVisible() || window->transientParent();
|
||||
});
|
||||
g_lastWindowClosed = !visible_windows_left;
|
||||
}
|
||||
}};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -665,6 +665,60 @@ impl EventLoopState {
|
|||
Ok(Self::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs the event loop and renders the items in the provided `component` in its
|
||||
/// own window.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn pump_events(
|
||||
mut self,
|
||||
timeout: Option<std::time::Duration>,
|
||||
) -> Result<(Self, winit::platform::pump_events::PumpStatus), corelib::platform::PlatformError>
|
||||
{
|
||||
use winit::platform::pump_events::EventLoopExtPumpEvents;
|
||||
|
||||
let not_running_loop_instance = MAYBE_LOOP_INSTANCE
|
||||
.with(|loop_instance| match loop_instance.borrow_mut().take() {
|
||||
Some(instance) => Ok(instance),
|
||||
None => NotRunningEventLoop::new(),
|
||||
})
|
||||
.map_err(|e| format!("Error initializing winit event loop: {e}"))?;
|
||||
|
||||
let event_loop_proxy = not_running_loop_instance.event_loop_proxy;
|
||||
GLOBAL_PROXY
|
||||
.get_or_init(Default::default)
|
||||
.lock()
|
||||
.unwrap()
|
||||
.set_proxy(event_loop_proxy.clone());
|
||||
|
||||
let mut winit_loop = not_running_loop_instance.instance;
|
||||
let clipboard = not_running_loop_instance.clipboard;
|
||||
|
||||
let result = winit_loop.pump_events(
|
||||
timeout,
|
||||
|event: Event<SlintUserEvent>,
|
||||
event_loop_target: &EventLoopWindowTarget<SlintUserEvent>| {
|
||||
let running_instance = RunningEventLoop {
|
||||
event_loop_target,
|
||||
event_loop_proxy: &event_loop_proxy,
|
||||
clipboard: &clipboard,
|
||||
};
|
||||
CURRENT_WINDOW_TARGET
|
||||
.set(&running_instance, || self.process_event(event, event_loop_target))
|
||||
},
|
||||
);
|
||||
|
||||
*GLOBAL_PROXY.get_or_init(Default::default).lock().unwrap() = Default::default();
|
||||
|
||||
// Keep the EventLoop instance alive and re-use it in future invocations of run_event_loop().
|
||||
// Winit does not support creating multiple instances of the event loop.
|
||||
let nre = NotRunningEventLoop { clipboard, instance: winit_loop, event_loop_proxy };
|
||||
MAYBE_LOOP_INSTANCE.with(|loop_instance| *loop_instance.borrow_mut() = Some(nre));
|
||||
|
||||
if let Some(error) = self.loop_error {
|
||||
return Err(error);
|
||||
}
|
||||
Ok((self, result))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
|
|
|||
|
|
@ -240,6 +240,29 @@ impl i_slint_core::platform::Platform for Backend {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn process_events(
|
||||
&self,
|
||||
timeout: core::time::Duration,
|
||||
_: i_slint_core::InternalToken,
|
||||
) -> Result<core::ops::ControlFlow<()>, PlatformError> {
|
||||
let loop_state = self.event_loop_state.borrow_mut().take().unwrap_or_default();
|
||||
let (new_state, status) = loop_state.pump_events(Some(timeout))?;
|
||||
*self.event_loop_state.borrow_mut() = Some(new_state);
|
||||
match status {
|
||||
winit::platform::pump_events::PumpStatus::Continue => {
|
||||
Ok(core::ops::ControlFlow::Continue(()))
|
||||
}
|
||||
winit::platform::pump_events::PumpStatus::Exit(code) => {
|
||||
if code == 0 {
|
||||
Ok(core::ops::ControlFlow::Break(()))
|
||||
} else {
|
||||
return Err(format!("Event loop exited with non-zero code {code}").into());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_event_loop_proxy(&self) -> Option<Box<dyn EventLoopProxy>> {
|
||||
struct Proxy;
|
||||
impl EventLoopProxy for Proxy {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,32 @@ pub trait Platform {
|
|||
Err(PlatformError::NoEventLoopProvider)
|
||||
}
|
||||
|
||||
/// Spins an event loop for a specified period of time.
|
||||
///
|
||||
/// This function is similar to `run_event_loop()` with two differences:
|
||||
/// * The function is expected to return after the provided timeout, but
|
||||
/// allow for subsequent invocations to resume the previous loop. The
|
||||
/// function can return earlier if the loop was terminated otherwise,
|
||||
/// for example by `quit_event_loop()` or a last-window-closed mechanism.
|
||||
/// * If the timeout is zero, the implementation should merely peek and
|
||||
/// process any pending events, but then return immediately.
|
||||
///
|
||||
/// When the function returns `ControlFlow::Continue`, it is assumed that
|
||||
/// the loop remains intact and that in the future the caller should call
|
||||
/// `process_events()` again, to permit the user to continue to interact with
|
||||
/// windows.
|
||||
/// When the function returns `ControlFlow::Break`, it is assumed that the
|
||||
/// event loop was terminated. Any subsequent calls to `process_events()`
|
||||
/// will start the event loop afresh.
|
||||
#[doc(hidden)]
|
||||
fn process_events(
|
||||
&self,
|
||||
_timeout: core::time::Duration,
|
||||
_: crate::InternalToken,
|
||||
) -> Result<core::ops::ControlFlow<()>, PlatformError> {
|
||||
Err(PlatformError::NoEventLoopProvider)
|
||||
}
|
||||
|
||||
/// Specify if the event loop should quit quen the last window is closed.
|
||||
/// The default behavior is `true`.
|
||||
/// When this is set to `false`, the event loop must keep running until
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue