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, Value};
17
18pub struct LiveReloadingComponent {
21 instance: Option<ComponentInstance>,
23 compiler: Compiler,
24 file_name: PathBuf,
25 component_name: String,
26 properties: HashMap<String, Value>,
27 callbacks: 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
127 for (name, value) in self.properties.iter() {
129 if let Some((global, prop)) = name.split_once('.') {
130 self.instance()
131 .set_global_property(global, prop, value.clone())
132 .unwrap_or_else(|e| panic!("Cannot set property {name}: {e}"));
133 } else {
134 self.instance()
135 .set_property(name, value.clone())
136 .unwrap_or_else(|e| panic!("Cannot set property {name}: {e}"));
137 }
138 }
139 for (name, callback) in self.callbacks.iter() {
140 let callback = callback.clone();
141 if let Some((global, prop)) = name.split_once('.') {
142 self.instance()
143 .set_global_callback(global, prop, move |args| callback(args))
144 .unwrap_or_else(|e| panic!("Cannot set callback {name}: {e}"));
145 } else {
146 self.instance()
147 .set_callback(name, move |args| callback(args))
148 .unwrap_or_else(|e| panic!("Cannot set callback {name}: {e}"));
149 }
150 }
151
152 eprintln!("Reloaded component {} from {}", self.component_name, self.file_name.display());
153
154 true
155 }
156
157 pub fn instance(&self) -> &ComponentInstance {
159 &self.instance.as_ref().expect("always set after Self is created from Rc::new_cyclic")
160 }
161
162 pub fn set_property(&mut self, name: &str, value: Value) {
164 self.properties.insert(name.into(), value.clone());
165 self.instance()
166 .set_property(&name, value)
167 .unwrap_or_else(|e| panic!("Cannot set property {name}: {e}"))
168 }
169
170 pub fn get_property(&self, name: &str) -> Value {
172 self.instance()
173 .get_property(&name)
174 .unwrap_or_else(|e| panic!("Cannot get property {name}: {e}"))
175 }
176
177 pub fn invoke(&self, name: &str, args: &[Value]) -> Value {
179 self.instance()
180 .invoke(name, args)
181 .unwrap_or_else(|e| panic!("Cannot invoke callback {name}: {e}"))
182 }
183
184 pub fn set_callback(&mut self, name: &str, callback: Rc<dyn Fn(&[Value]) -> Value + 'static>) {
186 self.callbacks.insert(name.into(), callback.clone());
187 self.instance()
188 .set_callback(&name, move |args| callback(args))
189 .unwrap_or_else(|e| panic!("Cannot set callback {name}: {e}"));
190 }
191
192 pub fn set_global_property(&mut self, global_name: &str, name: &str, value: Value) {
194 self.properties.insert(format!("{global_name}.{name}"), value.clone());
195 self.instance()
196 .set_global_property(global_name, name, value)
197 .unwrap_or_else(|e| panic!("Cannot set property {global_name}::{name}: {e}"))
198 }
199
200 pub fn get_global_property(&self, global_name: &str, name: &str) -> Value {
202 self.instance()
203 .get_global_property(global_name, name)
204 .unwrap_or_else(|e| panic!("Cannot get property {global_name}::{name}: {e}"))
205 }
206
207 pub fn invoke_global(&self, global_name: &str, name: &str, args: &[Value]) -> Value {
209 self.instance()
210 .invoke_global(global_name, name, args)
211 .unwrap_or_else(|e| panic!("Cannot invoke callback {global_name}::{name}: {e}"))
212 }
213
214 pub fn set_global_callback(
216 &mut self,
217 global_name: &str,
218 name: &str,
219 callback: Rc<dyn Fn(&[Value]) -> Value + 'static>,
220 ) {
221 self.callbacks.insert(format!("{global_name}.{name}"), callback.clone());
222 self.instance()
223 .set_global_callback(global_name, name, move |args| callback(args))
224 .unwrap_or_else(|e| panic!("Cannot set callback {global_name}::{name}: {e}"));
225 }
226}
227
228enum WatcherState {
229 Starting,
230 Changed,
232 Waiting(Waker),
234}
235
236struct Watcher {
237 watcher: Option<notify::RecommendedWatcher>,
239 state: WatcherState,
240 files: HashSet<PathBuf>,
241}
242
243impl Watcher {
244 fn new(component_weak: std::rc::Weak<RefCell<LiveReloadingComponent>>) -> Arc<Mutex<Self>> {
245 let arc = Arc::new(Mutex::new(Self {
246 state: WatcherState::Starting,
247 watcher: None,
248 files: Default::default(),
249 }));
250
251 let watcher_weak = Arc::downgrade(&arc);
252 let result = crate::spawn_local(std::future::poll_fn(move |cx| {
253 let (Some(instance), Some(watcher)) =
254 (component_weak.upgrade(), watcher_weak.upgrade())
255 else {
256 return std::task::Poll::Ready(());
258 };
259 let state = std::mem::replace(
260 &mut watcher.lock().unwrap().state,
261 WatcherState::Waiting(cx.waker().clone()),
262 );
263 if matches!(state, WatcherState::Changed) {
264 instance.borrow_mut().reload();
265 };
266 std::task::Poll::Pending
267 }));
268
269 if !result.is_ok() {
271 return arc;
272 }
273
274 let watcher_weak = Arc::downgrade(&arc);
275 arc.lock().unwrap().watcher =
276 notify::recommended_watcher(move |event: notify::Result<notify::Event>| {
277 use notify::{event::ModifyKind as M, EventKind as K};
278 let Ok(event) = event else { return };
279 let Some(watcher) = watcher_weak.upgrade() else { return };
280 if matches!(event.kind, K::Modify(M::Data(_) | M::Any) | K::Create(_))
281 && watcher.lock().is_ok_and(|w| event.paths.iter().any(|p| w.files.contains(p)))
282 {
283 if let WatcherState::Waiting(waker) =
284 std::mem::replace(&mut watcher.lock().unwrap().state, WatcherState::Changed)
285 {
286 std::thread::sleep(std::time::Duration::from_millis(15));
288 waker.wake();
289 }
290 }
291 })
292 .ok();
293 arc
294 }
295
296 fn watch(self_: &Mutex<Self>, path: &Path) {
297 let Some(parent) = path.parent() else { return };
298
299 let mut locked = self_.lock().unwrap();
300 let Some(mut watcher) = locked.watcher.take() else { return };
301 locked.files.insert(path.into());
302 drop(locked);
304 notify::Watcher::watch(
305 &mut watcher,
306 parent,
307 if cfg!(target_vendor = "apple") {
310 notify::RecursiveMode::Recursive
311 } else {
312 notify::RecursiveMode::NonRecursive
313 },
314 )
315 .unwrap_or_else(|err| {
316 eprintln!("Warning: error while watching {}: {:?}", path.display(), err)
317 });
318 self_.lock().unwrap().watcher = Some(watcher);
319 }
320}
321
322#[cfg(feature = "ffi")]
323mod ffi {
324 use super::*;
325 use core::ffi::c_void;
326 use i_slint_core::window::WindowAdapter;
327 use i_slint_core::{slice::Slice, SharedString, SharedVector};
328 type LiveReloadingComponentInner = RefCell<LiveReloadingComponent>;
329
330 #[unsafe(no_mangle)]
331 pub extern "C" fn slint_live_preview_new(
333 file_name: Slice<u8>,
334 component_name: Slice<u8>,
335 include_paths: &SharedVector<SharedString>,
336 library_paths: &SharedVector<SharedString>,
337 style: Slice<u8>,
338 ) -> *const LiveReloadingComponentInner {
339 let mut compiler = Compiler::default();
340 compiler.set_include_paths(
341 include_paths.iter().map(|path| PathBuf::from(path.as_str())).collect(),
342 );
343 compiler.set_library_paths(
344 library_paths
345 .iter()
346 .map(|path| path.as_str().split_once('=').expect("library path must have an '='"))
347 .map(|(lib, path)| (lib.into(), PathBuf::from(path)))
348 .collect(),
349 );
350 if !style.is_empty() {
351 compiler.set_style(std::str::from_utf8(&style).unwrap().into());
352 }
353 Rc::into_raw(
354 LiveReloadingComponent::new(
355 compiler,
356 std::path::PathBuf::from(std::str::from_utf8(&file_name).unwrap()),
357 std::str::from_utf8(&component_name).unwrap().into(),
358 )
359 .expect("Creating the component failed"),
360 )
361 }
362
363 #[unsafe(no_mangle)]
364 pub unsafe extern "C" fn slint_live_preview_clone(
365 component: *const LiveReloadingComponentInner,
366 ) {
367 Rc::increment_strong_count(component);
368 }
369
370 #[unsafe(no_mangle)]
371 pub unsafe extern "C" fn slint_live_preview_drop(
372 component: *const LiveReloadingComponentInner,
373 ) {
374 Rc::decrement_strong_count(component);
375 }
376
377 #[unsafe(no_mangle)]
378 pub extern "C" fn slint_live_preview_set_property(
379 component: &LiveReloadingComponentInner,
380 property: Slice<u8>,
381 value: &Value,
382 ) {
383 let property = std::str::from_utf8(&property).unwrap();
384 if let Some((global, prop)) = property.split_once('.') {
385 component.borrow_mut().set_global_property(global, prop, value.clone());
386 } else {
387 component.borrow_mut().set_property(property, value.clone());
388 }
389 }
390
391 #[unsafe(no_mangle)]
392 pub extern "C" fn slint_live_preview_get_property(
393 component: &LiveReloadingComponentInner,
394 property: Slice<u8>,
395 ) -> *mut Value {
396 let property = std::str::from_utf8(&property).unwrap();
397 let val = if let Some((global, prop)) = property.split_once('.') {
398 component.borrow().get_global_property(global, prop)
399 } else {
400 component.borrow().get_property(property)
401 };
402 Box::into_raw(Box::new(val))
403 }
404
405 #[unsafe(no_mangle)]
406 pub extern "C" fn slint_live_preview_invoke(
407 component: &LiveReloadingComponentInner,
408 callback: Slice<u8>,
409 args: Slice<Box<Value>>,
410 ) -> *mut Value {
411 let callback = std::str::from_utf8(&callback).unwrap();
412 let args = args.iter().map(|vb| vb.as_ref().clone()).collect::<Vec<_>>();
413 let val = if let Some((global, prop)) = callback.split_once('.') {
414 component.borrow().invoke_global(global, prop, &args)
415 } else {
416 component.borrow().invoke(callback, &args)
417 };
418 Box::into_raw(Box::new(val))
419 }
420
421 #[unsafe(no_mangle)]
422 pub unsafe extern "C" fn slint_live_preview_set_callback(
423 component: &LiveReloadingComponentInner,
424 callback: Slice<u8>,
425 callback_handler: extern "C" fn(
426 user_data: *mut c_void,
427 arg: Slice<Box<Value>>,
428 ) -> Box<Value>,
429 user_data: *mut c_void,
430 drop_user_data: Option<extern "C" fn(*mut c_void)>,
431 ) {
432 let ud = crate::ffi::CallbackUserData::new(user_data, drop_user_data, callback_handler);
433 let handler = Rc::new(move |args: &[Value]| ud.call(args));
434 let callback = std::str::from_utf8(&callback).unwrap();
435 if let Some((global, prop)) = callback.split_once('.') {
436 component.borrow_mut().set_global_callback(global, prop, handler);
437 } else {
438 component.borrow_mut().set_callback(callback, handler);
439 }
440 }
441
442 #[unsafe(no_mangle)]
444 pub unsafe extern "C" fn slint_live_preview_window(
445 component: &LiveReloadingComponentInner,
446 out: *mut *const i_slint_core::window::ffi::WindowAdapterRcOpaque,
447 ) {
448 assert_eq!(
449 core::mem::size_of::<Rc<dyn WindowAdapter>>(),
450 core::mem::size_of::<i_slint_core::window::ffi::WindowAdapterRcOpaque>()
451 );
452 let borrow = component.borrow();
453 let adapter = borrow.instance().inner.window_adapter_ref().unwrap();
454 core::ptr::write(out as *mut *const Rc<dyn WindowAdapter>, adapter as *const _)
455 }
456}