`Window` component should have `destroying`/`being-destroyed` callback
You should be able to run final .slint code, like taking data from the UI, before your app (or just window) is shut down. This is similar to #6401, but would allow to run code unconditionally, no matter whether the end user closed the window in question, or it was programmatically hidden and is subsequently being destroyed.
Maybe, this would require hide() to start behaving differently, namely to only destroy the internal strong reference in a closure enqueued in the event loop. This could be desirable anyways. Like with quit_event_loop() (see also #6562), it can be useful to enqueue actions in the event loop using invoke_from_event_loop(), and then call hide() and/or quit_event_loop(), without the window having been destroyed before the actions ran. This would make Slint's behavior much more predictable.
With the behavior as it currently is, you're required to jump through hoops (that I didn't figure out at the time of writing) to get .slint code to run before the window is destroyed on hide(), at least in certain app designs where your holding onto the component doesn't span the runs of the closures sent via invoke_from_event_loop(). With an app design utilizing RefCell, you can definitely not simply synchronously invoke_...() a .slint function that calls back to Rust, but need to wrap that in invoke_from_event_loop(), because you can get RefCell panics that way. But that execution will then be too late.
Could you elaborate a bit more the use case? Why is on_close_requested not sufficient?
It would be nice to know what is the exact use case for this suggestion. Why would one want to run slint code when the window is destroyed that cannot be run from the Rust side?
Why would one want to run slint code when the window is destroyed that cannot be run from the Rust side?
I have currently no deeper insights into my codebase (health issues etc.), but my app-window.slint with its AppWindow component has the code:
callback close-requested;
close-requested => {
foo-settings-acceptance-timer.prepone();
bar-settings-acceptance-timer.prepone();
}
- This callback must currently be called from the Rust side (see #6401).
- The callback doesn't cover every case of the window being closed.
.prepone()is described and proposed in #6369.- The timers' Slint code calls through to the Rust side. And ping-pong between the Rust and Slint side deeper into the call stack can lead to
RefCellpanics in my code.
The main difference with Slint compared to other UI frameworks like e.g. Qt is that they require an additional application object.
You can bind the callback to the main window's Window instance to achieve that:
export global Application {
callback quit();
quit => {
debug("Application is ending");
}
}
export component MainWindow inherits Window {
Button { text: "Quit application"; clicked => { Application.quit(); } }
}
C++ example:
auto ui = MainWindow::create();
ui->global<Application>().on_quit([&]() -> auto {
// do application quit stuff
});
ui->window().on_close_requested([&]() -> auto {
quit();
return slint::CloseRequestResponse::HideWindow; // <-- quits the event loop
});
ui->run();
The flow for a multi-window application is quite different and spawning e.g. a dialog info box before quitting the application can be only done from the backend currently. Here is how I do that with a custom MessageBox (export component MessageBox inherits Dialog { … }) as an example workaround in C++.
class App final {
slint::ComponentHandle<MainWindow> ui; // <----- This needs to become a global application instance for multi-window app's
inline auto save_settings() -> void { /* Write application settings */ }
inline auto App::quit() -> slint::CloseRequestResponse {
save_settings();
auto requires_restart = true; // just for the example
if (requires_restart) {
auto mb = MessageBox::create(); // <------- THIS ONE :)
// mb->set_mb_icon(ToSlint::to_image(XdgIcon::fromTheme(QSL("info")), 100, 100));
// mb->invoke_set_title(slint::SharedString("Session Restart Required");
mb->set_text(slint::SharedString(slint::SharedString("Some settings will not take effect until the next log in."));
mb->window().on_close_requested([&, mb]() -> auto {
ui->window().hide();
return slint::CloseRequestResponse::HideWindow;
});
mb->on_ok_clicked([&, mb] { mb->window().dispatch_close_requested_event(); });
mb->show();
return slint::CloseRequestResponse::KeepWindowShown;
}
return slint::CloseRequestResponse::HideWindow;
}
inline auto api() -> void {
ui->global<Application>().on_quit([&] { quit(); });
}
};