1use crate::dynamic_item_tree::WindowOptions;
7use crate::file_watcher::FileWatcher;
8use core::cell::RefCell;
9use core::task::Waker;
10use i_slint_core::api::{ComponentHandle, PlatformError};
11use std::collections::HashMap;
12use std::path::PathBuf;
13use std::rc::Rc;
14use std::sync::{Arc, Mutex};
15
16pub use crate::{Compiler, ComponentInstance, DefaultTranslationContext, Value};
18
19pub struct LiveReloadingComponent {
22 instance: Option<ComponentInstance>,
24 watcher: Arc<Mutex<Watcher>>,
25 compiler: Compiler,
26 file_name: PathBuf,
27 component_name: String,
28 properties: RefCell<HashMap<String, Value>>,
29 callbacks: RefCell<HashMap<String, Rc<dyn Fn(&[Value]) -> Value + 'static>>>,
30}
31
32impl LiveReloadingComponent {
33 pub fn new(
35 mut compiler: Compiler,
36 file_name: PathBuf,
37 component_name: String,
38 ) -> Result<Rc<RefCell<Self>>, PlatformError> {
39 compiler.set_embed_resources(i_slint_compiler::EmbedResourcesKind::ListAllResources);
40
41 let self_rc = Rc::<RefCell<Self>>::new_cyclic(move |self_weak| {
42 let watcher = Watcher::new(self_weak.clone());
43 RefCell::new(Self {
44 instance: None,
45 watcher,
46 compiler,
47 file_name,
48 component_name,
49 properties: Default::default(),
50 callbacks: Default::default(),
51 })
52 });
53
54 let mut self_mut = self_rc.borrow_mut();
55 let result = self_mut.build();
56 #[cfg(feature = "display-diagnostics")]
57 result.print_diagnostics();
58 assert!(
59 !result.has_errors(),
60 "Was not able to compile the file {}. \n{:?}",
61 self_mut.file_name.display(),
62 result.diagnostics
63 );
64 let definition = result.component(&self_mut.component_name).expect("Cannot open component");
65 let instance = definition.create()?;
66 eprintln!(
67 "Loaded component {} from {}",
68 self_mut.component_name,
69 self_mut.file_name.display()
70 );
71 self_mut.instance = Some(instance);
72 drop(self_mut);
73 Ok(self_rc)
74 }
75
76 pub fn reload(&mut self) -> bool {
80 let result = self.build();
81 #[cfg(feature = "display-diagnostics")]
82 result.print_diagnostics();
83 if result.has_errors() {
84 return false;
85 }
86
87 if let Some(definition) = result.component(&self.component_name) {
88 let window_adapter =
89 i_slint_core::window::WindowInner::from_pub(self.instance().window())
90 .window_adapter();
91 match definition.create_with_options(WindowOptions::UseExistingWindow(window_adapter)) {
92 Ok(instance) => {
93 self.instance = Some(instance);
94 }
95 Err(e) => {
96 eprintln!("Error while creating the component: {e}");
97 return false;
98 }
99 }
100 } else {
101 eprintln!("Component {} not found", self.component_name);
102 return false;
103 }
104 true
105 }
106
107 fn build(&self) -> crate::CompilationResult {
108 let mut future = core::pin::pin!(self.compiler.build_from_path(&self.file_name));
109 let mut cx = std::task::Context::from_waker(std::task::Waker::noop());
110 let std::task::Poll::Ready(result) = std::future::Future::poll(future.as_mut(), &mut cx)
111 else {
112 unreachable!("Compiler returned Pending")
113 };
114 Watcher::update_watched_paths(
115 &self.watcher,
116 std::iter::once(self.file_name.clone())
117 .chain(result.watch_paths(i_slint_core::InternalToken).iter().cloned()),
118 );
119 result
120 }
121
122 pub fn reload_properties_and_callbacks(&self) {
124 for (name, value) in self.properties.borrow_mut().iter() {
126 if let Some((global, prop)) = name.split_once('.') {
127 self.instance()
128 .set_global_property(global, prop, value.clone())
129 .unwrap_or_else(|e| panic!("Cannot set property {name}: {e}"));
130 } else {
131 self.instance()
132 .set_property(name, value.clone())
133 .unwrap_or_else(|e| panic!("Cannot set property {name}: {e}"));
134 }
135 }
136 for (name, callback) in self.callbacks.borrow_mut().iter() {
137 let callback = callback.clone();
138 if let Some((global, prop)) = name.split_once('.') {
139 self.instance()
140 .set_global_callback(global, prop, move |args| callback(args))
141 .unwrap_or_else(|e| panic!("Cannot set callback {name}: {e}"));
142 } else {
143 self.instance()
144 .set_callback(name, move |args| callback(args))
145 .unwrap_or_else(|e| panic!("Cannot set callback {name}: {e}"));
146 }
147 }
148
149 eprintln!("Reloaded component {} from {}", self.component_name, self.file_name.display());
150 }
151
152 pub fn instance(&self) -> &ComponentInstance {
154 self.instance.as_ref().expect("always set after Self is created from Rc::new_cyclic")
155 }
156
157 pub fn set_property(&self, name: &str, value: Value) {
159 self.properties.borrow_mut().insert(name.into(), value.clone());
160 self.instance()
161 .set_property(name, value)
162 .unwrap_or_else(|e| panic!("Cannot set property {name}: {e}"))
163 }
164
165 pub fn get_property(&self, name: &str) -> Value {
167 self.instance()
168 .get_property(name)
169 .unwrap_or_else(|e| panic!("Cannot get property {name}: {e}"))
170 }
171
172 pub fn invoke(&self, name: &str, args: &[Value]) -> Value {
174 self.instance()
175 .invoke(name, args)
176 .unwrap_or_else(|e| panic!("Cannot invoke callback {name}: {e}"))
177 }
178
179 pub fn set_callback(&self, name: &str, callback: Rc<dyn Fn(&[Value]) -> Value + 'static>) {
181 self.callbacks.borrow_mut().insert(name.into(), callback.clone());
182 self.instance()
183 .set_callback(name, move |args| callback(args))
184 .unwrap_or_else(|e| panic!("Cannot set callback {name}: {e}"));
185 }
186
187 pub fn set_global_property(&self, global_name: &str, name: &str, value: Value) {
189 self.properties.borrow_mut().insert(format!("{global_name}.{name}"), value.clone());
190 self.instance()
191 .set_global_property(global_name, name, value)
192 .unwrap_or_else(|e| panic!("Cannot set property {global_name}::{name}: {e}"))
193 }
194
195 pub fn get_global_property(&self, global_name: &str, name: &str) -> Value {
197 self.instance()
198 .get_global_property(global_name, name)
199 .unwrap_or_else(|e| panic!("Cannot get property {global_name}::{name}: {e}"))
200 }
201
202 pub fn invoke_global(&self, global_name: &str, name: &str, args: &[Value]) -> Value {
204 self.instance()
205 .invoke_global(global_name, name, args)
206 .unwrap_or_else(|e| panic!("Cannot invoke callback {global_name}::{name}: {e}"))
207 }
208
209 pub fn set_global_callback(
211 &self,
212 global_name: &str,
213 name: &str,
214 callback: Rc<dyn Fn(&[Value]) -> Value + 'static>,
215 ) {
216 self.callbacks.borrow_mut().insert(format!("{global_name}.{name}"), callback.clone());
217 self.instance()
218 .set_global_callback(global_name, name, move |args| callback(args))
219 .unwrap_or_else(|e| panic!("Cannot set callback {global_name}::{name}: {e}"));
220 }
221}
222
223enum WatcherState {
224 Starting,
225 Changed,
227 Waiting(Waker),
229}
230
231struct Watcher {
232 watcher: Option<FileWatcher>,
234 state: WatcherState,
235}
236
237impl Watcher {
238 fn new(component_weak: std::rc::Weak<RefCell<LiveReloadingComponent>>) -> Arc<Mutex<Self>> {
239 let arc = Arc::new(Mutex::new(Self { state: WatcherState::Starting, watcher: None }));
240
241 let watcher_weak = Arc::downgrade(&arc);
242 let result = crate::spawn_local(std::future::poll_fn(move |cx| {
243 let (Some(instance), Some(watcher)) =
244 (component_weak.upgrade(), watcher_weak.upgrade())
245 else {
246 return std::task::Poll::Ready(());
248 };
249 let state = std::mem::replace(
250 &mut watcher.lock().unwrap().state,
251 WatcherState::Waiting(cx.waker().clone()),
252 );
253 if matches!(state, WatcherState::Changed) {
254 let success = instance.borrow_mut().reload();
255 if success {
256 instance.borrow().reload_properties_and_callbacks();
257 };
258 };
259 std::task::Poll::Pending
260 }));
261
262 if result.is_err() {
264 return arc;
265 }
266
267 let watcher_weak = Arc::downgrade(&arc);
268 arc.lock().unwrap().watcher = FileWatcher::start(
269 move |_event| {
270 let Some(watcher) = watcher_weak.upgrade() else { return };
271 if let WatcherState::Waiting(waker) =
272 std::mem::replace(&mut watcher.lock().unwrap().state, WatcherState::Changed)
273 {
274 std::thread::sleep(std::time::Duration::from_millis(15));
276 waker.wake();
277 }
278 },
279 move |err| eprintln!("Warning: file watcher error: {err}"),
280 )
281 .ok();
282 arc
283 }
284
285 fn update_watched_paths<I>(self_: &Mutex<Self>, paths: I)
286 where
287 I: IntoIterator<Item = PathBuf>,
288 {
289 let mut locked = self_.lock().unwrap();
290 let Some(mut watcher) = locked.watcher.take() else { return };
291 drop(locked);
292 if let Err(err) = watcher.update_watched_paths(paths) {
293 eprintln!("Warning: error while updating file watcher paths: {err:?}");
294 }
295 self_.lock().unwrap().watcher = Some(watcher);
296 }
297}
298
299#[cfg(feature = "ffi")]
300mod ffi {
301 use super::*;
302 use core::ffi::c_void;
303 use i_slint_core::window::WindowAdapter;
304 use i_slint_core::{SharedString, SharedVector, slice::Slice};
305 type LiveReloadingComponentInner = RefCell<LiveReloadingComponent>;
306
307 #[unsafe(no_mangle)]
308 pub extern "C" fn slint_live_preview_new(
310 file_name: Slice<u8>,
311 component_name: Slice<u8>,
312 include_paths: &SharedVector<SharedString>,
313 library_paths: &SharedVector<SharedString>,
314 style: Slice<u8>,
315 translation_domain: Slice<u8>,
316 no_default_translation_context: bool,
317 ) -> *const LiveReloadingComponentInner {
318 let mut compiler = Compiler::default();
319 compiler.set_include_paths(
320 include_paths.iter().map(|path| PathBuf::from(path.as_str())).collect(),
321 );
322 compiler.set_library_paths(
323 library_paths
324 .iter()
325 .map(|path| path.as_str().split_once('=').expect("library path must have an '='"))
326 .map(|(lib, path)| (lib.into(), PathBuf::from(path)))
327 .collect(),
328 );
329 if !style.is_empty() {
330 compiler.set_style(std::str::from_utf8(&style).unwrap().into());
331 }
332 if !translation_domain.is_empty() {
333 compiler
334 .set_translation_domain(std::str::from_utf8(&translation_domain).unwrap().into());
335 }
336 if no_default_translation_context {
337 compiler.set_default_translation_context(crate::DefaultTranslationContext::None);
338 }
339 Rc::into_raw(
340 LiveReloadingComponent::new(
341 compiler,
342 std::path::PathBuf::from(std::str::from_utf8(&file_name).unwrap()),
343 std::str::from_utf8(&component_name).unwrap().into(),
344 )
345 .expect("Creating the component failed"),
346 )
347 }
348
349 #[unsafe(no_mangle)]
350 pub unsafe extern "C" fn slint_live_preview_clone(
351 component: *const LiveReloadingComponentInner,
352 ) {
353 unsafe { Rc::increment_strong_count(component) };
354 }
355
356 #[unsafe(no_mangle)]
357 pub unsafe extern "C" fn slint_live_preview_drop(
358 component: *const LiveReloadingComponentInner,
359 ) {
360 unsafe { Rc::decrement_strong_count(component) };
361 }
362
363 #[unsafe(no_mangle)]
364 pub extern "C" fn slint_live_preview_set_property(
365 component: &LiveReloadingComponentInner,
366 property: Slice<u8>,
367 value: &Value,
368 ) {
369 let property = std::str::from_utf8(&property).unwrap();
370 if let Some((global, prop)) = property.split_once('.') {
371 component.borrow_mut().set_global_property(global, prop, value.clone());
372 } else {
373 component.borrow_mut().set_property(property, value.clone());
374 }
375 }
376
377 #[unsafe(no_mangle)]
378 pub extern "C" fn slint_live_preview_get_property(
379 component: &LiveReloadingComponentInner,
380 property: Slice<u8>,
381 ) -> *mut Value {
382 let property = std::str::from_utf8(&property).unwrap();
383 let val = if let Some((global, prop)) = property.split_once('.') {
384 component.borrow().get_global_property(global, prop)
385 } else {
386 component.borrow().get_property(property)
387 };
388 Box::into_raw(Box::new(val))
389 }
390
391 #[unsafe(no_mangle)]
392 pub extern "C" fn slint_live_preview_invoke(
393 component: &LiveReloadingComponentInner,
394 callback: Slice<u8>,
395 args: Slice<Box<Value>>,
396 ) -> *mut Value {
397 let callback = std::str::from_utf8(&callback).unwrap();
398 let args = args.iter().map(|vb| vb.as_ref().clone()).collect::<Vec<_>>();
399 let val = if let Some((global, prop)) = callback.split_once('.') {
400 component.borrow().invoke_global(global, prop, &args)
401 } else {
402 component.borrow().invoke(callback, &args)
403 };
404 Box::into_raw(Box::new(val))
405 }
406
407 #[unsafe(no_mangle)]
408 pub unsafe extern "C" fn slint_live_preview_set_callback(
409 component: &LiveReloadingComponentInner,
410 callback: Slice<u8>,
411 callback_handler: extern "C" fn(
412 user_data: *mut c_void,
413 arg: Slice<Box<Value>>,
414 ) -> Box<Value>,
415 user_data: *mut c_void,
416 drop_user_data: Option<extern "C" fn(*mut c_void)>,
417 ) {
418 let ud = unsafe {
419 crate::ffi::CallbackUserData::new(user_data, drop_user_data, callback_handler)
420 };
421 let handler = Rc::new(move |args: &[Value]| ud.call(args));
422 let callback = std::str::from_utf8(&callback).unwrap();
423 if let Some((global, prop)) = callback.split_once('.') {
424 component.borrow_mut().set_global_callback(global, prop, handler);
425 } else {
426 component.borrow_mut().set_callback(callback, handler);
427 }
428 }
429
430 #[unsafe(no_mangle)]
432 pub unsafe extern "C" fn slint_live_preview_window(
433 component: &LiveReloadingComponentInner,
434 out: *mut *const i_slint_core::window::ffi::WindowAdapterRcOpaque,
435 ) {
436 assert_eq!(
437 core::mem::size_of::<Rc<dyn WindowAdapter>>(),
438 core::mem::size_of::<i_slint_core::window::ffi::WindowAdapterRcOpaque>()
439 );
440 let borrow = component.borrow();
441 let adapter = borrow.instance().inner.window_adapter_ref().unwrap();
442 unsafe { core::ptr::write(out as *mut *const Rc<dyn WindowAdapter>, adapter as *const _) };
443 }
444}