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_ok() {
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 {
288 if let WatcherState::Waiting(waker) =
289 std::mem::replace(&mut watcher.lock().unwrap().state, WatcherState::Changed)
290 {
291 std::thread::sleep(std::time::Duration::from_millis(15));
293 waker.wake();
294 }
295 }
296 })
297 .ok();
298 arc
299 }
300
301 fn watch(self_: &Mutex<Self>, path: &Path) {
302 let Some(parent) = path.parent() else { return };
303
304 let mut locked = self_.lock().unwrap();
305 let Some(mut watcher) = locked.watcher.take() else { return };
306 locked.files.insert(path.into());
307 drop(locked);
309 notify::Watcher::watch(
310 &mut watcher,
311 parent,
312 if cfg!(target_vendor = "apple") {
315 notify::RecursiveMode::Recursive
316 } else {
317 notify::RecursiveMode::NonRecursive
318 },
319 )
320 .unwrap_or_else(|err| {
321 eprintln!("Warning: error while watching {}: {:?}", path.display(), err)
322 });
323 self_.lock().unwrap().watcher = Some(watcher);
324 }
325}
326
327#[cfg(feature = "ffi")]
328mod ffi {
329 use super::*;
330 use core::ffi::c_void;
331 use i_slint_core::window::WindowAdapter;
332 use i_slint_core::{SharedString, SharedVector, slice::Slice};
333 type LiveReloadingComponentInner = RefCell<LiveReloadingComponent>;
334
335 #[unsafe(no_mangle)]
336 pub extern "C" fn slint_live_preview_new(
338 file_name: Slice<u8>,
339 component_name: Slice<u8>,
340 include_paths: &SharedVector<SharedString>,
341 library_paths: &SharedVector<SharedString>,
342 style: Slice<u8>,
343 translation_domain: Slice<u8>,
344 no_default_translation_context: bool,
345 ) -> *const LiveReloadingComponentInner {
346 let mut compiler = Compiler::default();
347 compiler.set_include_paths(
348 include_paths.iter().map(|path| PathBuf::from(path.as_str())).collect(),
349 );
350 compiler.set_library_paths(
351 library_paths
352 .iter()
353 .map(|path| path.as_str().split_once('=').expect("library path must have an '='"))
354 .map(|(lib, path)| (lib.into(), PathBuf::from(path)))
355 .collect(),
356 );
357 if !style.is_empty() {
358 compiler.set_style(std::str::from_utf8(&style).unwrap().into());
359 }
360 if !translation_domain.is_empty() {
361 compiler
362 .set_translation_domain(std::str::from_utf8(&translation_domain).unwrap().into());
363 }
364 if no_default_translation_context {
365 compiler.set_default_translation_context(crate::DefaultTranslationContext::None);
366 }
367 Rc::into_raw(
368 LiveReloadingComponent::new(
369 compiler,
370 std::path::PathBuf::from(std::str::from_utf8(&file_name).unwrap()),
371 std::str::from_utf8(&component_name).unwrap().into(),
372 )
373 .expect("Creating the component failed"),
374 )
375 }
376
377 #[unsafe(no_mangle)]
378 pub unsafe extern "C" fn slint_live_preview_clone(
379 component: *const LiveReloadingComponentInner,
380 ) {
381 unsafe { Rc::increment_strong_count(component) };
382 }
383
384 #[unsafe(no_mangle)]
385 pub unsafe extern "C" fn slint_live_preview_drop(
386 component: *const LiveReloadingComponentInner,
387 ) {
388 unsafe { Rc::decrement_strong_count(component) };
389 }
390
391 #[unsafe(no_mangle)]
392 pub extern "C" fn slint_live_preview_set_property(
393 component: &LiveReloadingComponentInner,
394 property: Slice<u8>,
395 value: &Value,
396 ) {
397 let property = std::str::from_utf8(&property).unwrap();
398 if let Some((global, prop)) = property.split_once('.') {
399 component.borrow_mut().set_global_property(global, prop, value.clone());
400 } else {
401 component.borrow_mut().set_property(property, value.clone());
402 }
403 }
404
405 #[unsafe(no_mangle)]
406 pub extern "C" fn slint_live_preview_get_property(
407 component: &LiveReloadingComponentInner,
408 property: Slice<u8>,
409 ) -> *mut Value {
410 let property = std::str::from_utf8(&property).unwrap();
411 let val = if let Some((global, prop)) = property.split_once('.') {
412 component.borrow().get_global_property(global, prop)
413 } else {
414 component.borrow().get_property(property)
415 };
416 Box::into_raw(Box::new(val))
417 }
418
419 #[unsafe(no_mangle)]
420 pub extern "C" fn slint_live_preview_invoke(
421 component: &LiveReloadingComponentInner,
422 callback: Slice<u8>,
423 args: Slice<Box<Value>>,
424 ) -> *mut Value {
425 let callback = std::str::from_utf8(&callback).unwrap();
426 let args = args.iter().map(|vb| vb.as_ref().clone()).collect::<Vec<_>>();
427 let val = if let Some((global, prop)) = callback.split_once('.') {
428 component.borrow().invoke_global(global, prop, &args)
429 } else {
430 component.borrow().invoke(callback, &args)
431 };
432 Box::into_raw(Box::new(val))
433 }
434
435 #[unsafe(no_mangle)]
436 pub unsafe extern "C" fn slint_live_preview_set_callback(
437 component: &LiveReloadingComponentInner,
438 callback: Slice<u8>,
439 callback_handler: extern "C" fn(
440 user_data: *mut c_void,
441 arg: Slice<Box<Value>>,
442 ) -> Box<Value>,
443 user_data: *mut c_void,
444 drop_user_data: Option<extern "C" fn(*mut c_void)>,
445 ) {
446 let ud = unsafe {
447 crate::ffi::CallbackUserData::new(user_data, drop_user_data, callback_handler)
448 };
449 let handler = Rc::new(move |args: &[Value]| ud.call(args));
450 let callback = std::str::from_utf8(&callback).unwrap();
451 if let Some((global, prop)) = callback.split_once('.') {
452 component.borrow_mut().set_global_callback(global, prop, handler);
453 } else {
454 component.borrow_mut().set_callback(callback, handler);
455 }
456 }
457
458 #[unsafe(no_mangle)]
460 pub unsafe extern "C" fn slint_live_preview_window(
461 component: &LiveReloadingComponentInner,
462 out: *mut *const i_slint_core::window::ffi::WindowAdapterRcOpaque,
463 ) {
464 assert_eq!(
465 core::mem::size_of::<Rc<dyn WindowAdapter>>(),
466 core::mem::size_of::<i_slint_core::window::ffi::WindowAdapterRcOpaque>()
467 );
468 let borrow = component.borrow();
469 let adapter = borrow.instance().inner.window_adapter_ref().unwrap();
470 unsafe { core::ptr::write(out as *mut *const Rc<dyn WindowAdapter>, adapter as *const _) };
471 }
472}