1use crate::{ComponentRefresh, PreparedReload, ReloadError, ReloadTarget};
3use ferroui_base::{
4 controls::{NameScope, NameScopeRef},
5 BoxedValue, Ref, WeakRef,
6};
7use ferroui_controls::{Control, Panel};
8use ferroui_markup_xaml::{
9 source_overlay::Sources, FromXamlObject, RuntimeXamlLoaderConfiguration,
10 RuntimeXamlLoaderDocument,
11};
12use std::{cell::RefCell, ops::Range, rc::Rc};
13
14struct Owned {
15 children: Vec<WeakRef<Control>>,
16 index: usize,
17}
18pub struct PanelReloadTarget {
24 root: WeakRef<Panel>,
25 uri: String,
26 dependencies: Vec<String>,
27 baseline: Rc<RefCell<String>>,
28 owned: Rc<RefCell<Owned>>,
29 configuration: RuntimeXamlLoaderConfiguration,
30 refresh: Option<Rc<dyn ComponentRefresh>>,
31 properties: Vec<Box<dyn crate::root_properties::OwnedRootProperty>>,
32 resources: Option<crate::root_resources::OwnedResources>,
33 styles: Option<crate::root_styles::OwnedStyles>,
34 handlers: Option<crate::handlers::OwnedHandlers>,
35 state_adapters: Vec<Rc<dyn crate::ControlStateAdapter>>,
36 restoration: crate::control_state::StateRestoration,
37}
38impl PanelReloadTarget {
39 pub fn with_dependencies(mut self, dependencies: impl IntoIterator<Item = String>) -> Self {
43 self.dependencies = dependencies
44 .into_iter()
45 .map(|uri| crate::document_key(&uri))
46 .collect();
47 self
48 }
49
50 pub fn new(
51 root: &Ref<Panel>,
52 owned: Range<usize>,
53 uri: String,
54 baseline: String,
55 configuration: RuntimeXamlLoaderConfiguration,
56 ) -> Result<Self, ReloadError> {
57 let children = root.children().to_vec();
58 let range = children
59 .get(owned.clone())
60 .ok_or_else(|| ReloadError::Failed("Invalid owned panel child range".into()))?;
61 if root.is_items_host() {
62 return Err(ReloadError::RestartRequired(
63 "Items-host panels require an items adapter".into(),
64 ));
65 }
66 Ok(Self {
67 root: root.downgrade(),
68 uri: crate::document_key(&uri),
69 dependencies: Vec::new(),
70 baseline: Rc::new(RefCell::new(baseline)),
71 owned: Rc::new(RefCell::new(Owned {
72 children: range.iter().map(Ref::downgrade).collect(),
73 index: owned.start,
74 })),
75 configuration,
76 refresh: None,
77 properties: Vec::new(),
78 resources: None,
79 styles: None,
80 handlers: None,
81 state_adapters: Vec::new(),
82 restoration: Default::default(),
83 })
84 }
85 pub fn with_refresh(mut self, refresh: Rc<dyn ComponentRefresh>) -> Self {
86 self.refresh = Some(refresh);
87 self
88 }
89 pub fn with_owned_resources(mut self) -> Self {
91 self.resources = self.root.upgrade().map(|root| crate::root_resources::OwnedResources::capture(&root.upcast()));
92 self
93 }
94 pub fn with_owned_styles(mut self) -> Self {
96 self.styles = self.root.upgrade().map(|root| crate::root_styles::OwnedStyles::capture(&root.upcast()));
97 self
98 }
99 pub fn with_owned_handlers(mut self) -> Result<Self, ReloadError> {
102 let root = self.root.upgrade().ok_or_else(|| ReloadError::Failed("Panel closed".into()))?;
103 let owned = self.owned.borrow();
104 if owned.index != 0 || owned.children.len() != root.children().count() {
105 return Err(ReloadError::RestartRequired("Handler ownership requires the complete XAML child range".into()));
106 }
107 self.handlers = Some(crate::handlers::OwnedHandlers::capture(&root.upcast()));
108 drop(owned);
109 Ok(self)
110 }
111 pub fn with_state_adapter(mut self, adapter: Rc<dyn crate::ControlStateAdapter>) -> Self {
114 self.state_adapters.push(adapter);
115 self
116 }
117 pub fn with_owned_root_property<T: ferroui_base::PropertyValue>(
120 mut self,
121 property: &'static ferroui_base::StyledProperty<T>,
122 ) -> Self {
123 if let Some(root) = self.root.upgrade() {
124 if !self
125 .properties
126 .iter()
127 .any(|owned| owned.name() == property.name())
128 {
129 self.properties
130 .push(crate::root_properties::own(&root, property));
131 }
132 }
133 self
134 }
135 pub fn with_owned_root_static_resource_property<T: ferroui_base::PropertyValue>(
141 mut self,
142 property: &'static ferroui_base::StyledProperty<T>,
143 ) -> Self {
144 if let Some(root) = self.root.upgrade() {
145 self.properties
146 .retain(|owned| owned.name() != property.name());
147 self.properties
148 .push(crate::root_properties::own_static_resource(&root, property));
149 }
150 self
151 }
152 pub fn is_alive(&self) -> bool {
153 self.root.upgrade().is_some()
154 }
155
156 pub fn with_owned_root_binding_property<T: ferroui_base::PropertyValue>(
159 mut self,
160 property: &'static ferroui_base::StyledProperty<T>,
161 ) -> Self {
162 if let Some(root) = self.root.upgrade() {
163 self.properties.retain(|owned| owned.name() != property.name());
164 self.properties.push(crate::root_properties::own_binding(&root, property));
165 }
166 self
167 }
168}
169struct Change {
170 restoration: crate::control_state::StateRestoration,
171 root: Ref<Panel>,
172 original: Vec<Ref<Control>>,
173 old: Vec<Ref<Control>>,
174 new: Vec<Ref<Control>>,
175 index: usize,
176 old_scope: Option<NameScopeRef>,
177 new_scope: Option<NameScopeRef>,
178 baseline: Rc<RefCell<String>>,
179 owned: Rc<RefCell<Owned>>,
180 before: String,
181 after: String,
182 state: Rc<crate::state::ViewState>,
183 refresh: Option<Box<dyn PreparedReload>>,
184 properties: Vec<Box<dyn PreparedReload>>,
185 handlers: Option<Box<dyn PreparedReload>>,
186 started: bool,
187 detached_contexts: Vec<crate::detached_context::DetachedContext>,
188}
189impl PreparedReload for Change {
190 fn commit(&mut self) -> Result<(), ReloadError> {
191 if self.root.children().to_vec() != self.original {
192 return Err(ReloadError::RestartRequired(
193 "Panel children changed after preparation".into(),
194 ));
195 }
196 if let Some(handlers) = &mut self.handlers { handlers.commit()?; }
197 for property in &mut self.properties {
198 property.commit()?;
199 }
200 self.detached_contexts = self
201 .old
202 .iter()
203 .filter_map(|child| {
204 crate::detached_context::DetachedContext::freeze(&Some(Control::boxed(child)))
205 })
206 .collect();
207 self.started = true;
208 for child in &self.old {
209 self.root.children().remove(child.clone());
210 }
211 self.root
212 .children()
213 .insert_range(self.index, self.new.clone());
214 NameScope::set_name_scope(&self.root, self.new_scope.clone());
215 if let Some(refresh) = &mut self.refresh {
216 refresh.commit()?;
217 }
218 Ok(())
219 }
220 fn rollback(&mut self) {
221 for property in self.properties.iter_mut().rev() {
222 property.rollback();
223 }
224 if !self.started {
225 if let Some(handlers) = &mut self.handlers { handlers.rollback(); }
226 return;
227 }
228 let _retired: Vec<_> = self
229 .new
230 .iter()
231 .filter_map(|child| {
232 crate::detached_context::DetachedContext::freeze(&Some(Control::boxed(child)))
233 })
234 .collect();
235 for child in self.new.iter().chain(&self.old) {
237 self.root.children().remove(child.clone());
238 }
239 self.root
240 .children()
241 .insert_range(self.index, self.old.clone());
242 for context in self.detached_contexts.drain(..) {
243 context.restore();
244 }
245 NameScope::set_name_scope(&self.root, self.old_scope.clone());
246 if let Some(refresh) = &mut self.refresh {
247 refresh.rollback();
248 }
249 if let Some(handlers) = &mut self.handlers { handlers.rollback(); }
250 }
251 fn finish(&mut self) {
252 for property in &mut self.properties {
253 property.finish();
254 }
255 *self.baseline.borrow_mut() = self.after.clone();
256 *self.owned.borrow_mut() = Owned {
257 children: self.new.iter().map(Ref::downgrade).collect(),
258 index: self.index,
259 };
260 if let Some(refresh) = &mut self.refresh {
261 refresh.finish();
262 }
263 if let Some(handlers) = &mut self.handlers { handlers.finish(); }
264 let root = self.root.downgrade();
265 let scope = self.new_scope.clone();
266 let state = self.state.clone();
267 let before = self.before.clone();
268 let after = self.after.clone();
269 let children: Vec<_> = self.new.iter().map(Ref::downgrade).collect();
270 let index = self.index;
271 self.restoration.schedule(
272 move || {
273 if let Some(root) = root.upgrade() {
274 let current = || {
275 NameScope::get_name_scope(&root) == scope
276 && root.children().to_vec().get(index..index + children.len())
277 .is_some_and(|current| current.iter().zip(&children).all(|(actual, expected)| {
278 expected.upgrade().is_some_and(|expected| expected.ptr_eq(actual))
279 }))
280 };
281 if current() {
282 root.update_layout();
283 }
284 if current() {
285 let value: BoxedValue = Rc::new(root);
286 state.restore(&value, &before, &after);
287 }
288 }
289 },
290 );
291 }
292}
293impl ReloadTarget for PanelReloadTarget {
294 fn complete_pending_state(&self) {
295 self.restoration.complete();
296 }
297 fn documents(&self) -> Vec<String> {
298 let mut documents = self.dependencies.clone();
299 documents.push(self.uri.clone());
300 documents
301 }
302 fn prepare(&self, sources: Rc<Sources>) -> Result<Box<dyn PreparedReload>, ReloadError> {
303 let root = self
304 .root
305 .upgrade()
306 .ok_or_else(|| ReloadError::Failed("Panel closed".into()))?;
307 if root.is_items_host() {
308 return Err(ReloadError::RestartRequired(
309 "Items-host panels require an items adapter".into(),
310 ));
311 }
312 let original = root.children().to_vec();
313 let owned = self.owned.borrow();
314 let old = owned
315 .children
316 .iter()
317 .map(|child| {
318 child.upgrade().ok_or_else(|| {
319 ReloadError::RestartRequired("Owned panel child was removed".into())
320 })
321 })
322 .collect::<Result<Vec<_>, _>>()?;
323 let index = old
324 .first()
325 .and_then(|first| original.iter().position(|child| child == first))
326 .unwrap_or(owned.index);
327 if original.get(index..index + old.len()) != Some(old.as_slice()) {
328 return Err(ReloadError::RestartRequired(
329 "Owned panel children are no longer contiguous".into(),
330 ));
331 }
332 let source = sources
333 .get(&self.uri)
334 .ok_or_else(|| ReloadError::Failed("Missing panel source".into()))?;
335 let before = self.baseline.borrow().clone();
336 let prepared = crate::component::shadow_source(
337 source,
338 &before,
339 &[
340 "Panel",
341 "StackPanel",
342 "WrapPanel",
343 "DockPanel",
344 "Canvas",
345 "Grid",
346 "UniformGrid",
347 ],
348 "Children",
349 &self
350 .properties
351 .iter()
352 .map(|property| property.name())
353 .collect::<Vec<_>>(),
354 match (self.resources.is_some(), self.styles.is_some()) {
355 (true, true) => &["Resources", "Styles"],
356 (true, false) => &["Resources"],
357 (false, true) => &["Styles"],
358 _ => &[],
359 },
360 self.handlers.is_some(),
361 )?;
362 let uri = ferroui_base::utilities::Uri::try_create(
363 &self.uri,
364 ferroui_base::utilities::UriKind::Absolute,
365 )
366 .ok_or_else(|| ReloadError::Failed("Invalid panel URI".into()))?;
367 let bindings = ferroui_markup_xaml_loader::binding_capture::BindingCaptureScope::new(
368 self.uri.clone(),
369 self.properties.iter().filter_map(|property| property.binding_property()),
370 );
371 let mut document = RuntimeXamlLoaderDocument::with_base_uri(Some(uri), &prepared);
372 document.service_provider = Some(crate::root_resources::services(&root.clone().upcast(), self.resources.as_ref(), self.styles.as_ref()));
373 let candidate_root = (self.styles.is_some() || self.resources.is_some() || self.handlers.is_some()).then(|| crate::root_preview::create(&prepared)).transpose()?;
374 let _preview = candidate_root.as_ref().map(|candidate| crate::root_preview::install(
375 &root.clone().upcast(), candidate, self.resources.as_ref(), self.styles.as_ref(),
376 ));
377 let code = self.handlers.as_ref().map(|_| ferroui_markup_xaml::reload_events::ReloadCodeBehind::new(
378 &root.clone().upcast::<ferroui_base::StyledElement>().upcast(), &candidate_root.as_ref().unwrap().clone().upcast(),
379 ));
380 if let Some(code) = &code { document.service_provider = Some(code.services(document.service_provider.take().unwrap())); }
381 document.root_instance = candidate_root.map(|candidate| Rc::new(candidate) as BoxedValue);
382 let deferred = ferroui_markup_xaml::xaml_il::runtime::DeferredContentReloadScope::new();
383 let dynamic = ferroui_markup_xaml::reload_resources::DynamicResourceReloadScope::new();
384 let candidate = ferroui_markup_xaml_loader::FerroRuntimeXamlLoader::load_document(
385 document,
386 Some(self.configuration.clone()),
387 )
388 .map_err(|error| ReloadError::Failed(error.to_string()))?;
389 let candidate = Ref::<Panel>::from_xaml_object(&candidate)
390 .ok_or_else(|| ReloadError::RestartRequired("Document is not a panel".into()))?;
391 if self.resources.is_some() || self.styles.is_some() {
392 ferroui_markup_xaml::reload_validation::prepare_candidate(&candidate)
393 .map_err(|error| ReloadError::Failed(error.to_string()))?;
394 crate::root_styles::prepare_styling(&candidate.clone().upcast(), &root.clone().upcast())?;
395 }
396 let mut properties = Vec::new();
397 for property in &self.properties {
398 if let Some(change) = property.prepare(
399 &root.clone().upcast(),
400 &candidate.clone().upcast(),
401 &before,
402 source,
403 &bindings,
404 )? {
405 properties.push(change);
406 }
407 }
408 properties.insert(0, crate::deferred_context::prepare(
409 &deferred, &candidate.clone().upcast(), &root.clone().upcast(), self.resources.is_some(), self.styles.is_some(),
410 ));
411 let new = candidate.children().to_vec();
412 let new_scope = NameScope::get_name_scope(&candidate);
413 candidate.children().clear();
414 NameScope::set_name_scope(&candidate, None);
415 if let Some(resources) = &self.resources {
416 properties.insert(1, resources.prepare(&candidate.clone().upcast())?);
417 }
418 let mut dynamic_index = if self.resources.is_some() { 2 } else { 1 };
419 if let Some(styles) = &self.styles {
420 properties.insert(dynamic_index, styles.prepare(&candidate.clone().upcast())?);
421 dynamic_index += 1;
422 }
423 properties.insert(dynamic_index, crate::deferred_context::prepare_dynamic(
424 &dynamic, &candidate.clone().upcast(), &root.clone().upcast(),
425 ));
426 let handlers = self.handlers.as_ref().zip(code.as_ref()).map(|(owner, code)| owner.prepare(code)).transpose()?;
427 let refresh = self
428 .refresh
429 .as_ref()
430 .map(|refresh| refresh.prepare(&root.clone().upcast(), new_scope.as_ref()))
431 .transpose()?;
432 let value: BoxedValue = Rc::new(root.clone());
433 Ok(Box::new(Change {
434 restoration: self.restoration.clone(),
435 state: Rc::new(crate::state::ViewState::capture_document(
436 Some(&value), Some(&self.uri), &before, &self.state_adapters,
437 )),
438 old_scope: NameScope::get_name_scope(&root),
439 root,
440 original,
441 old,
442 new,
443 index,
444 new_scope,
445 baseline: self.baseline.clone(),
446 owned: self.owned.clone(),
447 before,
448 after: source.clone(),
449 refresh,
450 properties,
451 handlers,
452 started: false,
453 detached_contexts: Vec::new(),
454 }))
455 }
456}