1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
3
4// cSpell: ignore condvar
5
6use super::PreviewState;
7use crate::lsp_ext::Health;
8use crate::ServerNotifier;
9use once_cell::sync::Lazy;
10use slint_interpreter::ComponentHandle;
11use std::future::Future;
12use std::sync::{Condvar, Mutex};
13
14#[derive(PartialEq)]
15enum RequestedGuiEventLoopState {
16 /// The UI event loop hasn't been started yet because no preview has been requested
17 Uninitialized,
18 /// The LSP thread requested the UI loop to start because a preview was requested,
19 /// But the loop hasn't been started yet
20 StartLoop,
21 /// The Loop is now started so the LSP thread can start posting events
22 LoopStarted,
23 /// The LSP thread requested the application to be terminated
24 QuitLoop,
25}
26
27static GUI_EVENT_LOOP_NOTIFIER: Lazy<Condvar> = Lazy::new(Condvar::new);
28static GUI_EVENT_LOOP_STATE_REQUEST: Lazy<Mutex<RequestedGuiEventLoopState>> =
29 Lazy::new(|| Mutex::new(RequestedGuiEventLoopState::Uninitialized));
30
31thread_local! {static CLI_ARGS: std::cell::OnceCell<crate::Cli> = Default::default();}
32
33pub fn run_in_ui_thread<F: Future<Output = ()> + 'static>(
34 create_future: impl Send + FnOnce() -> F + 'static,
35) {
36 // Wake up the main thread to start the event loop, if possible
37 {
38 let mut state_request: MutexGuard<'_, RequestedGuiEventLoopState> = GUI_EVENT_LOOP_STATE_REQUEST.lock().unwrap();
39 if *state_request == RequestedGuiEventLoopState::Uninitialized {
40 *state_request = RequestedGuiEventLoopState::StartLoop;
41 GUI_EVENT_LOOP_NOTIFIER.notify_one();
42 }
43 // We don't want to call post_event before the loop is properly initialized
44 while *state_request == RequestedGuiEventLoopState::StartLoop {
45 state_request = GUI_EVENT_LOOP_NOTIFIER.wait(guard:state_request).unwrap();
46 }
47 }
48 i_slint_coreResult<(), EventLoopError>::api::invoke_from_event_loop(func:move || {
49 i_slint_core::future::spawn_local(fut:create_future()).unwrap();
50 })
51 .unwrap();
52}
53
54pub fn start_ui_event_loop(cli_args: crate::Cli) {
55 CLI_ARGS.with(|f| f.set(cli_args).ok());
56
57 {
58 let mut state_requested = GUI_EVENT_LOOP_STATE_REQUEST.lock().unwrap();
59
60 while *state_requested == RequestedGuiEventLoopState::Uninitialized {
61 state_requested = GUI_EVENT_LOOP_NOTIFIER.wait(state_requested).unwrap();
62 }
63
64 if *state_requested == RequestedGuiEventLoopState::QuitLoop {
65 return;
66 }
67
68 if *state_requested == RequestedGuiEventLoopState::StartLoop {
69 // make sure the backend is initialized
70 i_slint_backend_selector::with_platform(|_| Ok(())).unwrap();
71 // Send an event so that once the loop is started, we notify the LSP thread that it can send more events
72 i_slint_core::api::invoke_from_event_loop(|| {
73 let mut state_request = GUI_EVENT_LOOP_STATE_REQUEST.lock().unwrap();
74 if *state_request == RequestedGuiEventLoopState::StartLoop {
75 *state_request = RequestedGuiEventLoopState::LoopStarted;
76 GUI_EVENT_LOOP_NOTIFIER.notify_one();
77 }
78 })
79 .unwrap();
80 }
81 }
82
83 slint::run_event_loop_until_quit().unwrap();
84}
85
86pub fn quit_ui_event_loop() {
87 // Wake up the main thread, in case it wasn't woken up earlier. If it wasn't, then don't request
88 // a start of the event loop.
89 {
90 let mut state_request: MutexGuard<'_, RequestedGuiEventLoopState> = GUI_EVENT_LOOP_STATE_REQUEST.lock().unwrap();
91 *state_request = RequestedGuiEventLoopState::QuitLoop;
92 GUI_EVENT_LOOP_NOTIFIER.notify_one();
93 }
94
95 close_ui();
96
97 let _ = i_slint_core::api::quit_event_loop();
98
99 // Make sure then sender channel gets dropped.
100 if let Some(sender: &Mutex>) = SERVER_NOTIFIER.get() {
101 let mut sender: MutexGuard<'_, Option> = sender.lock().unwrap();
102 *sender = None;
103 };
104}
105
106pub fn open_ui(sender: &ServerNotifier) {
107 // Wake up the main thread to start the event loop, if possible
108 {
109 let mut state_request = GUI_EVENT_LOOP_STATE_REQUEST.lock().unwrap();
110 if *state_request == RequestedGuiEventLoopState::Uninitialized {
111 *state_request = RequestedGuiEventLoopState::StartLoop;
112 GUI_EVENT_LOOP_NOTIFIER.notify_one();
113 }
114 // We don't want to call post_event before the loop is properly initialized
115 while *state_request == RequestedGuiEventLoopState::StartLoop {
116 state_request = GUI_EVENT_LOOP_NOTIFIER.wait(state_request).unwrap();
117 }
118 }
119
120 {
121 let mut cache = super::CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
122 if cache.ui_is_visible {
123 return; // UI is already up!
124 }
125 cache.ui_is_visible = true;
126
127 let mut s = SERVER_NOTIFIER.get_or_init(Default::default).lock().unwrap();
128 *s = Some(sender.clone());
129 };
130
131 i_slint_core::api::invoke_from_event_loop(move || {
132 super::PREVIEW_STATE.with(|preview_state| {
133 let mut preview_state = preview_state.borrow_mut();
134
135 open_ui_impl(&mut preview_state);
136 });
137 })
138 .unwrap();
139}
140
141pub(super) fn open_ui_impl(preview_state: &mut PreviewState) {
142 let (default_style, show_preview_ui, fullscreen) = {
143 let cache = super::CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
144 let style = cache.config.style.clone();
145 let style = if style.is_empty() {
146 CLI_ARGS.with(|args| args.get().map(|a| a.style.clone()).unwrap_or_default())
147 } else {
148 style
149 };
150 let hide_ui = cache
151 .config
152 .hide_ui
153 .or_else(|| CLI_ARGS.with(|args| args.get().map(|a| a.no_toolbar)))
154 .unwrap_or(false);
155 let fullscreen = CLI_ARGS.with(|args| args.get().map(|a| a.fullscreen).unwrap_or_default());
156 (style, !hide_ui, fullscreen)
157 };
158
159 // TODO: Handle Error!
160 let experimental = std::env::var_os("SLINT_ENABLE_EXPERIMENTAL_FEATURES")
161 .map(|s| !s.is_empty() && s != "0")
162 .unwrap_or(false);
163
164 let ui = preview_state
165 .ui
166 .get_or_insert_with(|| super::ui::create_ui(default_style, experimental).unwrap());
167 ui.set_show_preview_ui(show_preview_ui);
168 ui.window().set_fullscreen(fullscreen);
169 ui.window().on_close_requested(|| {
170 let mut cache = super::CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
171 cache.ui_is_visible = false;
172
173 let mut sender = SERVER_NOTIFIER.get_or_init(Default::default).lock().unwrap();
174 *sender = None;
175
176 slint::CloseRequestResponse::HideWindow
177 });
178}
179
180pub fn close_ui() {
181 {
182 let mut cache: MutexGuard<'_, ContentCache> = super::CONTENT_CACHE.get_or_init(Default::default).lock().unwrap();
183 if !cache.ui_is_visible {
184 return; // UI is already down!
185 }
186 cache.ui_is_visible = false;
187 }
188
189 i_slint_coreResult<(), EventLoopError>::api::invoke_from_event_loop(func:move || {
190 super::PREVIEW_STATE.with(move |preview_state: &RefCell| {
191 let mut preview_state: RefMut<'_, PreviewState> = preview_state.borrow_mut();
192 close_ui_impl(&mut preview_state)
193 });
194 })
195 .unwrap(); // TODO: Handle Error
196}
197
198fn close_ui_impl(preview_state: &mut PreviewState) {
199 let ui: Option = preview_state.ui.take();
200 if let Some(ui: PreviewUi) = ui {
201 ui.hide().unwrap();
202 }
203}
204
205static SERVER_NOTIFIER: std::sync::OnceLock<Mutex<Option<ServerNotifier>>> =
206 std::sync::OnceLock::new();
207
208pub fn notify_diagnostics(diagnostics: &[slint_interpreter::Diagnostic]) -> Option<()> {
209 super::set_diagnostics(diagnostics);
210
211 let Some(sender: ServerNotifier) = SERVER_NOTIFIER.get_or_init(Default::default).lock().unwrap().clone() else {
212 return Some(());
213 };
214
215 let lsp_diags: HashMap> = crate::preview::convert_diagnostics(diagnostics);
216
217 for (url: Url, diagnostics: Vec) in lsp_diags {
218 crate::common::lsp_to_editor::notify_lsp_diagnostics(&sender, uri:url, diagnostics)?;
219 }
220 Some(())
221}
222
223pub fn send_status(message: &str, health: Health) {
224 let Some(sender: ServerNotifier) = SERVER_NOTIFIER.get_or_init(Default::default).lock().unwrap().clone() else {
225 return;
226 };
227
228 crate::common::lsp_to_editor::send_status_notification(&sender, message, health)
229}
230
231pub fn ask_editor_to_show_document(file: &str, selection: lsp_types::Range) {
232 let Some(sender: ServerNotifier) = SERVER_NOTIFIER.get_or_init(Default::default).lock().unwrap().clone() else {
233 return;
234 };
235 let Ok(url: Url) = lsp_types::Url::from_file_path(file) else { return };
236 let fut: impl Future = crate::common::lsp_to_editor::send_show_document_to_editor(sender, file:url, range:selection);
237 slint_interpreter::spawn_local(fut).unwrap(); // Fire and forget.
238}
239
240pub fn send_message_to_lsp(message: crate::common::PreviewToLspMessage) {
241 let Some(sender: ServerNotifier) = SERVER_NOTIFIER.get_or_init(Default::default).lock().unwrap().clone() else {
242 return;
243 };
244 sender.send_message_to_lsp(message);
245}
246