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