slint-ui / slint

Slint is a declarative GUI toolkit to build native user interfaces for Rust, C++, or JavaScript apps.
https://slint.dev
Other
17.55k stars 601 forks source link

Keep the eventloop running when windows are closed #1499

Closed radiohertz closed 10 months ago

radiohertz commented 2 years ago

Add a feature to keep the event loop running even if all windows are closed

Workaround using private API: https://github.com/slint-ui/slint/issues/1499#issuecomment-1794517946

Original issue

I was looking to create a system tray for my application and i couldn't because of this issue. slint::ComponentHandle::hide makesslint::ComponentHandle::run return.

Example: https://gist.github.com/qw0rd/b9e86a99129b31e502bd9e45d146a84e

ogoffart commented 2 years ago

There is internal API which we use for the LSP preview (EventLoopQuitBegiavior) https://docs.rs/i-slint-core/latest/i_slint_core/backend/trait.Backend.html#tymethod.run_event_loop

We should somehow expose a way to keep the event loop alive when there are no window.

I guess, in the mean time, you could try to re-run the event loop after it quits

Edit: since this comment, that API is gone and now it is the private API Backend::set_event_loop_quit_on_last_window_closed Which is used by the LSP preview https://github.com/slint-ui/slint/blob/f2281dd900fd0009b5ca8de33c75579116701c76/tools/lsp/preview.rs#L106

ogoffart commented 1 year ago

I had a small discussion with @tronical about that and the conclusion is that we need to add something.

Some ideas include:

  1. A global slint::set_event_loop_quit_on_last_window_closed(bool) that forward to the backend function and make the backend function public.
  2. A reference counted struct that keeps the application alive, like the QEventLoopLocker
  3. a new slint::run_event_loop_with_config(...) that allow to pass parameter such as whether the event loop should quit when the last window closes.

One point is wether it is the responsability to the backend to terminate the eventloop when there are no more window, or if it is the responsability of the slint core library. (Currently it is the backend that do that, the slint library currently don't know much about windows)

Xiaobaishushu25 commented 1 year ago

I had a small discussion with @tronical about that and the conclusion is that we need to add something.

Some ideas include:

  1. A global slint::set_event_loop_quit_on_last_window_closed(bool) that forward to the backend function and make the backend function public.
  2. A reference counted struct that keeps the application alive, like the QEventLoopLocker
  3. a new slint::run_event_loop_with_config(...) that allow to pass parameter such as whether the event loop should quit when the last window closes.

One point is wether it is the responsability to the backend to terminate the eventloop when there are no more window, or if it is the responsability of the slint core library. (Currently it is the backend that do that, the slint library currently don't know much about windows)

I'm not familiar with backend, but I think option 1 looks good because it seems easy to use. That is useful and important.And i also want to konw if that's possible to add some function for window,such as hide()--not close the window,just make the window invisible to preserve the data within the current window elements and quickly display it when needed、is_showing()--return whether the window is showing or falsle、to_front()--When this function is called, the window will be pushed to the top of the desktop、request_focus()--When this function is called, the taskbar icon will flash to remind the user that an event has occurred or ended.Sorry, my suggestions seem a bit overwhelming, they are just my immature suggestions. You can refer to it at your discretion. Thank you for your work.

asuper0 commented 1 year ago

How about changing the hide behavior to really hide, but not close? This could be achieved by invoke system api, I guess.

Guiguiprim commented 1 year ago

I have the same use case, and went with something in the line of option 1 (using the hidden function and i_slint_backend_selector). I also use the on_close_requested() callback to drop the window and free resources.

A robust way of doing this would indeed be nice.

Horbin-Magician commented 1 year ago

I need some help.

How can I keep the eventloop running when all windows are "hiden"? I don't quite understand the method discussed above. Do they mean that I need to change the source code?

Another, if a robust way will coming soon (as the issue is opened a year ago) ?

Thank you right here.

marad commented 1 year ago

I've came up here looking for solution, but I see that this is not very active thread.

@Horbin-Magician The only solution is to re-run the eventloop when you need it. Other than that there is only a discussion of possible implementations.

For my tray app I guess I'll simply create a window and run() it whenever I need to show the GUI.

@ogoffart Do you have any update regarding this?

Horbin-Magician commented 1 year ago

I've came up here looking for solution, but I see that this is not very active thread.

@Horbin-Magician The only solution is to re-run the eventloop when you need it. Other than that there is only a discussion of possible implementations.

For my tray app I guess I'll simply create a window and run() it whenever I need to show the GUI.

@ogoffart Do you have any update regarding this?

This is my solution. By using b.set_event_loop_quit_on_last_window_closed(false);, the slint::ComponentHandle::hide will not make slint::ComponentHandle::run return.

use i_slint_backend_selector;
fn main() {
    slint::platform::set_platform(Box::new(i_slint_backend_winit::Backend::new())).unwrap();
    // some code
    i_slint_backend_selector::with_platform(|b| {
        b.set_event_loop_quit_on_last_window_closed(false);
        b.run_event_loop()
    }).unwrap();
}
ogoffart commented 1 year ago

As @Horbin-Magician , the workaround right now is to use private API:

Use the internal crate i-slint-backend-selector

In your Cargo.toml

[dependencies]
slint = "1.2.2"
i-slint-backend-selector = "=1.2.2"

Notice the = sign in the version number as the internal private crate don't follow semver.

Then you can do

fn main() {
    i_slint_backend_selector::with_platform(|b| {
        b.set_event_loop_quit_on_last_window_closed(false);
    }).unwrap();

    //... your code here ...
}
melMass commented 11 months ago
fn main() {
    i_slint_backend_selector::with_platform(|b| {
        b.set_event_loop_quit_on_last_window_closed(false);
    }).unwrap();

    //... your code here ...
}

For me it doesn't seem to change the behaviour for some reason. I pushed a POC here I wanted some quake style reveal:

https://github.com/melMass/poc/blob/a9fdca94a9b11c646e58811bddd3701f84b12165/apps/slint-global-key/src/main.rs#L26-L31

EDIT: Solved in https://github.com/melMass/poc/commit/3c0731ab32c514b6eabc45958c120befafa2c690 by forcing winit backend

Guiguiprim commented 11 months ago

If someone is trying to do a systray app, I made a basic demo using tray_ion https://github.com/Guiguiprim/slint_systry_app

I tested it on Windows (the systray integration might need some more work on Linux)

tronical commented 10 months ago

Olivier and I discussed option 2 from https://github.com/slint-ui/slint/issues/1499#issuecomment-1578736553 once more and noticed that perhaps this could be implemented entirely in the run-time library without the backend needing to support it.

  1. Remove set_event_loop_quit.. function from Backend trait
  2. Maintain window adapter count in run-time library (global...)
  3. slint::EventLoopLocker increases on creation and decreases count on drop
  4. Run-time library instructs backend to quit event loop when count drops to zero
let _locker = slint::EventLoopLocker::new();
slint::run_event_loop();
ogoffart commented 10 months ago

We added slint::run_event_loop_until_quit() function in rust, and added an argument to the C++ run_event_loop function. Javascript is still missing (#4316)