1use_RefCell!();
2use crate::api::NodeContext;
3use crate::{
4 ConditionalProperties, ExpandedNode, Handler, HandlerRegistry, InstanceNode, InstantiationArgs,
5 ReusableInstanceNodeArgs, RouteLocation, RouteMatch, RuntimePropertiesStackFrame,
6 RuntimeResolvedPropertyColumns, RuntimeResolvedPropertyEntry, RuntimeSettingsCondition,
7 RuntimeSettingsLayer, RuntimeSettingsSource, INTERNAL_ROUTE_LOCATION_SYMBOL,
8 INTERNAL_ROUTE_MATCH_SYMBOL,
9};
10use pax_language::Computable;
11use pax_manifest::cartridge_generation::{
12 TRANSITION_GENERATION_SYMBOL, TRANSITION_PHASE_ENTER, TRANSITION_PHASE_EXIT,
13 TRANSITION_PHASE_SYMBOL, TRANSITION_PLAYHEAD_MILLIS_SYMBOL, TRANSITION_PLAYHEAD_SYMBOL,
14 TRANSITION_TAKEOVER_SYMBOL,
15};
16use pax_manifest::{
17 ExpressionInfo, GradientDefinition, GradientElement, GradientShapeDefinition,
18 InOutInterruption, LiteralBlockDefinition, LocationInfo, PaxIdentifier, SelectorExpr,
19 SettingElement, SettingsBlockElement, TemplateNodeDefinition, TimelineBlockElement,
20 TimelineDefinition, TimelineKeyframe, TimelineMarker, TimelineSelectorElement,
21 TimelineTrackDefinition, TimelineTrackElement, TransitionDefinition, TypeId, ValueDefinition,
22};
23use pax_message::{borrow, borrow_mut};
24use pax_runtime_api::constants::{COMMON_PROPERTIES, COMMON_PROPERTIES_2D};
25use pax_runtime_api::pax_value::functions::{call_function, Functions};
26use pax_runtime_api::pax_value::{CoercionRules, PaxAny, ToFromPaxAny, ToPaxValue};
27use pax_runtime_api::properties::{PropertyValue, UntypedProperty};
28use pax_runtime_api::{
29 use_RefCell, CommonProperties, Duration, EasingCurve, Numeric, PaxValue, Property, Rotation,
30 Size, Variable,
31};
32use std::any::Any;
33use std::borrow::Borrow;
34use std::collections::{BTreeMap, HashMap};
35use std::marker::PhantomData;
36use std::rc::Rc;
37
38pub trait PaxCartridge {}
39
40#[cfg(all(test, not(feature = "designtime")))]
41mod initialization_tests;
42#[cfg(test)]
43mod typed_binding_tests;
44
45pub const BASE_SYMBOL: &str = "$base";
48
49struct ResolvedRuntimeSettings {
50 #[allow(dead_code)]
51 defined_properties: BTreeMap<String, ValueDefinition>,
52 columns: RuntimeResolvedPropertyColumns,
53 provenance: BTreeMap<String, RuntimeResolvedPropertyEntry>,
54}
55
56fn imported_settings_layers_for_node(
57 expanded_node: Option<&Rc<ExpandedNode>>,
58) -> Vec<RuntimeSettingsLayer> {
59 let mut layers = Vec::new();
60 let Some(expanded_node) = expanded_node else {
61 return layers;
62 };
63 if expanded_node.is_in_import_settings_subtree() {
64 return layers;
65 }
66 let Some(containing_component) = expanded_node.containing_component.upgrade() else {
67 return layers;
68 };
69 layers.extend(
70 borrow!(containing_component.imported_settings_layers)
71 .iter()
72 .cloned(),
73 );
74 layers
75}
76
77fn append_resolved_property_entry(
78 columns: &mut RuntimeResolvedPropertyColumns,
79 key: &str,
80 entry: RuntimeResolvedPropertyEntry,
81) {
82 columns.entry(key.to_string()).or_default().push(entry);
83}
84
85fn add_removed_property_resets(
86 previous: &RuntimeResolvedPropertyColumns,
87 current: &mut RuntimeResolvedPropertyColumns,
88) {
89 for property_name in previous.keys() {
90 current.entry(property_name.clone()).or_default();
91 }
92}
93
94fn runtime_property_entry(
95 source: RuntimeSettingsSource,
96 selector: Option<pax_manifest::SelectorExpr>,
97 source_location: Option<LocationInfo>,
98 source_stack: Option<Rc<RuntimePropertiesStackFrame>>,
99 value: ValueDefinition,
100 condition: Option<RuntimeSettingsCondition>,
101 axis_index: Option<usize>,
102) -> RuntimeResolvedPropertyEntry {
103 RuntimeResolvedPropertyEntry {
104 source,
105 selector,
106 source_location,
107 source_stack,
108 value,
109 condition,
110 axis_index,
111 }
112}
113
114fn common_property_2d_axes(name: &str) -> Option<[&'static str; 2]> {
115 COMMON_PROPERTIES_2D
116 .iter()
117 .find_map(|(group_name, axes)| (*group_name == name).then_some(*axes))
118}
119
120fn setting_value_can_define_property(value: &ValueDefinition) -> bool {
121 matches!(
122 value,
123 ValueDefinition::LiteralValue(_)
124 | ValueDefinition::Block(_)
125 | ValueDefinition::Timeline(_)
126 | ValueDefinition::Gradient(_)
127 | ValueDefinition::Transition(_)
128 | ValueDefinition::Expression(_)
129 | ValueDefinition::Identifier(_)
130 | ValueDefinition::DoubleBinding(_)
131 )
132}
133
134fn is_lifecycle_transition_setting_key(key: &str) -> bool {
135 matches!(key, "in" | "out")
136}
137
138fn append_2d_common_property_entries(
139 columns: &mut RuntimeResolvedPropertyColumns,
140 key: &str,
141 value: &ValueDefinition,
142 source: RuntimeSettingsSource,
143 selector: Option<pax_manifest::SelectorExpr>,
144 source_location: Option<LocationInfo>,
145 source_stack: Option<Rc<RuntimePropertiesStackFrame>>,
146 condition: Option<RuntimeSettingsCondition>,
147) -> bool {
148 let Some(axes) = common_property_2d_axes(key) else {
149 return false;
150 };
151
152 append_resolved_property_entry(
153 columns,
154 key,
155 runtime_property_entry(
156 source.clone(),
157 selector.clone(),
158 source_location.clone(),
159 source_stack.clone(),
160 value.clone(),
161 condition.clone(),
162 None,
163 ),
164 );
165 for (axis_index, axis_name) in axes.iter().enumerate() {
166 append_resolved_property_entry(
167 columns,
168 axis_name,
169 runtime_property_entry(
170 source.clone(),
171 selector.clone(),
172 source_location.clone(),
173 source_stack.clone(),
174 value.clone(),
175 condition.clone(),
176 Some(axis_index),
177 ),
178 );
179 }
180 true
181}
182
183pub fn property_columns_from_defined_properties(
186 defined_properties: &BTreeMap<String, ValueDefinition>,
187) -> RuntimeResolvedPropertyColumns {
188 let mut columns = BTreeMap::new();
189 for (key, value) in defined_properties {
190 if setting_value_can_define_property(value) {
191 append_2d_common_property_entries(
192 &mut columns,
193 key,
194 value,
195 RuntimeSettingsSource::Inline,
196 None,
197 None,
198 None,
199 None,
200 );
201 }
202 }
203 for (key, value) in defined_properties {
204 if common_property_2d_axes(key).is_some() || !setting_value_can_define_property(value) {
205 continue;
206 }
207 append_resolved_property_entry(
208 &mut columns,
209 key,
210 runtime_property_entry(
211 RuntimeSettingsSource::Inline,
212 None,
213 None,
214 None,
215 value.clone(),
216 None,
217 None,
218 ),
219 );
220 }
221 columns
222}
223
224fn append_setting_elements(
225 columns: &mut RuntimeResolvedPropertyColumns,
226 elements: &[SettingElement],
227 source: RuntimeSettingsSource,
228 selector: Option<pax_manifest::SelectorExpr>,
229 source_stack: Option<Rc<RuntimePropertiesStackFrame>>,
230 condition: Option<RuntimeSettingsCondition>,
231) {
232 for element in elements {
233 let SettingElement::Setting(key, value) = element else {
234 continue;
235 };
236 if is_lifecycle_transition_setting_key(&key.token_value) {
237 continue;
238 }
239 if setting_value_can_define_property(value) {
240 append_2d_common_property_entries(
241 columns,
242 &key.token_value,
243 value,
244 source.clone(),
245 selector.clone(),
246 key.token_location.clone(),
247 source_stack.clone(),
248 condition.clone(),
249 );
250 }
251 }
252
253 for element in elements {
254 let SettingElement::Setting(key, value) = element else {
255 continue;
256 };
257 if is_lifecycle_transition_setting_key(&key.token_value) {
258 continue;
259 }
260 if common_property_2d_axes(&key.token_value).is_some()
261 || !setting_value_can_define_property(value)
262 {
263 continue;
264 }
265 append_resolved_property_entry(
266 columns,
267 &key.token_value,
268 runtime_property_entry(
269 source.clone(),
270 selector.clone(),
271 key.token_location.clone(),
272 source_stack.clone(),
273 value.clone(),
274 condition.clone(),
275 None,
276 ),
277 );
278 }
279}
280
281#[derive(Clone)]
282struct SelectorLayerBlock {
283 selector: String,
284 elements: Vec<SettingElement>,
285 condition: Option<RuntimeSettingsCondition>,
286}
287
288fn combine_settings_conditions(
289 parent: Option<&RuntimeSettingsCondition>,
290 branch_condition: Option<&ExpressionInfo>,
291 prior_branch_conditions: &[ExpressionInfo],
292) -> Option<RuntimeSettingsCondition> {
293 let mut condition = parent.cloned().unwrap_or_default();
294 condition
295 .negative
296 .extend(prior_branch_conditions.iter().cloned());
297 if let Some(branch_condition) = branch_condition {
298 condition.positive.push(branch_condition.clone());
299 }
300
301 if condition.positive.is_empty() && condition.negative.is_empty() {
302 None
303 } else {
304 Some(condition)
305 }
306}
307
308fn collect_selector_layer_blocks(
309 settings: &[SettingsBlockElement],
310 condition: Option<&RuntimeSettingsCondition>,
311 blocks: &mut Vec<SelectorLayerBlock>,
312) {
313 for setting in settings {
314 match setting {
315 SettingsBlockElement::SelectorBlock(token, block) => {
316 blocks.push(SelectorLayerBlock {
317 selector: token.token_value.clone(),
318 elements: block.elements.clone(),
319 condition: condition.cloned(),
320 });
321 }
322 SettingsBlockElement::Conditional(block) => {
323 let mut prior_branch_conditions = Vec::new();
324 for branch in &block.branches {
325 let branch_condition = combine_settings_conditions(
326 condition,
327 branch.condition_expression.as_ref(),
328 &prior_branch_conditions,
329 );
330 collect_selector_layer_blocks(
331 &branch.elements,
332 branch_condition.as_ref(),
333 blocks,
334 );
335 if let Some(condition_expression) = &branch.condition_expression {
336 prior_branch_conditions.push(condition_expression.clone());
337 }
338 }
339 }
340 SettingsBlockElement::Handler(_, _)
341 | SettingsBlockElement::Transition(_, _)
342 | SettingsBlockElement::Comment(_) => {}
343 }
344 }
345}
346
347fn append_selector_layer_entries(
348 columns: &mut RuntimeResolvedPropertyColumns,
349 tnd: &TemplateNodeDefinition,
350 classes: &[String],
351 settings_block: &Option<Vec<SettingsBlockElement>>,
352 source: RuntimeSettingsSource,
353 source_stack: Option<Rc<RuntimePropertiesStackFrame>>,
354) {
355 let Some(settings_block) = settings_block else {
356 return;
357 };
358
359 let mut selector_blocks = Vec::new();
360 collect_selector_layer_blocks(settings_block, None, &mut selector_blocks);
361
362 for settings_value in &selector_blocks {
363 let Ok(selector) = pax_manifest::SelectorExpr::parse(&settings_value.selector) else {
364 continue;
365 };
366 if matches!(selector, pax_manifest::SelectorExpr::Type(_))
367 && tnd.selector_info.matches(&tnd.type_id, &selector)
368 {
369 append_setting_elements(
370 columns,
371 &settings_value.elements,
372 source.clone(),
373 Some(selector),
374 source_stack.clone(),
375 settings_value.condition.clone(),
376 );
377 }
378 }
379
380 for class in classes {
381 let selector = pax_manifest::SelectorExpr::Class(class.clone());
382 for settings_value in &selector_blocks {
383 let Ok(candidate) = pax_manifest::SelectorExpr::parse(&settings_value.selector) else {
384 continue;
385 };
386 if candidate == selector {
387 append_setting_elements(
388 columns,
389 &settings_value.elements,
390 source.clone(),
391 Some(selector.clone()),
392 source_stack.clone(),
393 settings_value.condition.clone(),
394 );
395 }
396 }
397 }
398 if let Some(id) = &tnd.selector_info.id {
399 let selector = pax_manifest::SelectorExpr::Id(id.token_value.clone());
400 for settings_value in &selector_blocks {
401 let Ok(candidate) = pax_manifest::SelectorExpr::parse(&settings_value.selector) else {
402 continue;
403 };
404 if candidate == selector {
405 append_setting_elements(
406 columns,
407 &settings_value.elements,
408 source.clone(),
409 Some(selector.clone()),
410 source_stack.clone(),
411 settings_value.condition.clone(),
412 );
413 }
414 }
415 }
416}
417
418fn overlay_static_runtime_properties(
419 columns: &mut RuntimeResolvedPropertyColumns,
420 base_defined_properties: &BTreeMap<String, ValueDefinition>,
421) {
422 for (key, value) in base_defined_properties {
423 if matches!(
424 value,
425 ValueDefinition::Timeline(_) | ValueDefinition::Transition(_)
426 ) {
427 if append_2d_common_property_entries(
428 columns,
429 key,
430 value,
431 RuntimeSettingsSource::Inline,
432 None,
433 None,
434 None,
435 None,
436 ) {
437 continue;
438 }
439 append_resolved_property_entry(
440 columns,
441 key,
442 runtime_property_entry(
443 RuntimeSettingsSource::Inline,
444 None,
445 None,
446 None,
447 value.clone(),
448 None,
449 None,
450 ),
451 );
452 }
453 }
454}
455
456fn timeline_transition_kind(
457 timeline: &TimelineDefinition,
458 settings: &Option<Vec<SettingsBlockElement>>,
459) -> Option<u64> {
460 let timeline_name = timeline.name.as_ref()?.token_value.as_str();
461 settings.as_ref()?.iter().find_map(|setting| {
462 let SettingsBlockElement::Transition(kind, name) = setting else {
463 return None;
464 };
465 if name.token_value != timeline_name {
466 return None;
467 }
468 match kind.token_value.as_str() {
469 "in" => Some(TRANSITION_PHASE_ENTER),
470 "out" => Some(TRANSITION_PHASE_EXIT),
471 _ => None,
472 }
473 })
474}
475
476fn timeline_target_matches_node(
477 target: &str,
478 tnd: &TemplateNodeDefinition,
479 classes: &[String],
480) -> bool {
481 if let Some(class_name) = target.strip_prefix('.') {
482 return classes.iter().any(|candidate| candidate == class_name);
483 }
484 if let Some(id) = target.strip_prefix('#') {
485 return tnd
486 .selector_info
487 .id
488 .as_ref()
489 .map(|candidate| candidate.token_value == id)
490 .unwrap_or(false);
491 }
492 false
493}
494
495fn append_timeline_selector_entries(
496 columns: &mut RuntimeResolvedPropertyColumns,
497 tnd: &TemplateNodeDefinition,
498 timelines: &[TimelineDefinition],
499 component_settings: &Option<Vec<SettingsBlockElement>>,
500 classes: &[String],
501 transition_classes: Option<&[String]>,
502) {
503 let mut values =
504 BTreeMap::<String, (SelectorExpr, Option<LocationInfo>, ValueDefinition)>::new();
505 let empty_base = BTreeMap::new();
506
507 for timeline in timelines {
508 let transition_kind = timeline_transition_kind(timeline, component_settings);
509 let selector_classes = transition_kind.and(transition_classes).unwrap_or(classes);
510 for element in &timeline.elements {
511 let TimelineBlockElement::SelectorBlock(target, block) = element else {
512 continue;
513 };
514 if !timeline_target_matches_node(&target.token_value, tnd, selector_classes) {
515 continue;
516 }
517 let Ok(selector) = SelectorExpr::parse(&target.token_value) else {
518 continue;
519 };
520 for element in &block.elements {
521 let TimelineSelectorElement::Track(property, track) = element else {
522 continue;
523 };
524 let value = if let Some(transition_kind) = transition_kind {
525 let track = pax_manifest::PaxManifest::prepare_transition_track(
526 timeline,
527 track,
528 &empty_base,
529 &property.token_value,
530 );
531 let existing = values
532 .remove(&property.token_value)
533 .map(|(_, _, value)| value);
534 let mut transition = match existing {
535 Some(ValueDefinition::Transition(transition)) => transition,
536 Some(value) => TransitionDefinition {
537 starting_value: Some(Box::new(value)),
538 ..Default::default()
539 },
540 None => TransitionDefinition::default(),
541 };
542 match transition_kind {
543 TRANSITION_PHASE_ENTER => transition.enter = Some(track),
544 TRANSITION_PHASE_EXIT => transition.exit = Some(track),
545 _ => {}
546 }
547 ValueDefinition::Transition(transition)
548 } else {
549 ValueDefinition::Timeline(pax_manifest::PaxManifest::prepare_timeline_track(
550 timeline, track,
551 ))
552 };
553 values.insert(
554 property.token_value.clone(),
555 (selector.clone(), property.token_location.clone(), value),
556 );
557 }
558 }
559 }
560
561 for (property, (selector, source_location, value)) in values {
562 append_resolved_property_entry(
563 columns,
564 &property,
565 runtime_property_entry(
566 RuntimeSettingsSource::ComponentSettings,
567 Some(selector),
568 source_location,
569 None,
570 value,
571 None,
572 None,
573 ),
574 );
575 }
576}
577
578fn resolve_runtime_settings_with_layers_for_node(
579 tnd: &TemplateNodeDefinition,
580 base_defined_properties: &BTreeMap<String, ValueDefinition>,
581 containing_component_settings: &Option<Vec<SettingsBlockElement>>,
582 imported_layers: &[RuntimeSettingsLayer],
583 classes: &[String],
584 transition_classes: Option<&[String]>,
585 timelines: &[TimelineDefinition],
586) -> ResolvedRuntimeSettings {
587 let mut columns = BTreeMap::new();
588
589 append_selector_layer_entries(
590 &mut columns,
591 tnd,
592 classes,
593 containing_component_settings,
594 RuntimeSettingsSource::ComponentSettings,
595 None,
596 );
597 for layer in imported_layers {
598 append_selector_layer_entries(
599 &mut columns,
600 tnd,
601 classes,
602 &Some(layer.settings.clone()),
603 RuntimeSettingsSource::ImportedLayer {
604 provider_id: layer.provider_id,
605 provider_type_id: layer.provider_type_id.clone(),
606 },
607 Some(Rc::clone(&layer.provider_stack)),
608 );
609 }
610 append_timeline_selector_entries(
611 &mut columns,
612 tnd,
613 timelines,
614 containing_component_settings,
615 classes,
616 transition_classes,
617 );
618 if let Some(inline_settings) = &tnd.settings {
619 append_setting_elements(
620 &mut columns,
621 inline_settings,
622 RuntimeSettingsSource::Inline,
623 None,
624 None,
625 None,
626 );
627 }
628 overlay_static_runtime_properties(&mut columns, base_defined_properties);
629
630 let mut defined_properties = BTreeMap::new();
631 let mut provenance = BTreeMap::new();
632 for (key, entries) in &columns {
633 let Some(last_entry) = entries.last() else {
634 continue;
635 };
636 defined_properties.insert(key.clone(), last_entry.value.clone());
637 provenance.insert(key.clone(), last_entry.clone());
638 }
639
640 ResolvedRuntimeSettings {
641 defined_properties,
642 columns,
643 provenance,
644 }
645}
646
647fn resolve_runtime_settings_for_node(
648 tnd: &TemplateNodeDefinition,
649 base_defined_properties: &BTreeMap<String, ValueDefinition>,
650 containing_component_settings: &Option<Vec<SettingsBlockElement>>,
651 timelines: &[TimelineDefinition],
652 expanded_node: Option<&Rc<ExpandedNode>>,
653) -> ResolvedRuntimeSettings {
654 let imported_layers = imported_settings_layers_for_node(expanded_node);
655 let classes = expanded_node
656 .map(|node| node.selector_metadata.borrow().classes.get())
657 .unwrap_or_else(|| tnd.selector_info.literal_classes());
658 let transition_classes =
659 expanded_node.and_then(|node| node.transition_selector_classes.borrow().clone());
660 resolve_runtime_settings_with_layers_for_node(
661 tnd,
662 base_defined_properties,
663 containing_component_settings,
664 &imported_layers,
665 &classes,
666 transition_classes.as_deref(),
667 timelines,
668 )
669}
670
671#[allow(dead_code)]
672fn resolve_defined_properties_for_node(
673 tnd: &TemplateNodeDefinition,
674 base_defined_properties: &BTreeMap<String, ValueDefinition>,
675 containing_component_settings: &Option<Vec<SettingsBlockElement>>,
676 timelines: &[TimelineDefinition],
677 expanded_node: Option<&Rc<ExpandedNode>>,
678) -> BTreeMap<String, ValueDefinition> {
679 let resolved = resolve_runtime_settings_for_node(
680 tnd,
681 base_defined_properties,
682 containing_component_settings,
683 timelines,
684 expanded_node,
685 );
686 if let Some(expanded_node) = expanded_node {
687 *expanded_node.resolved_property_columns.borrow_mut() = resolved.columns.clone();
688 *expanded_node.resolved_property_provenance.borrow_mut() = resolved.provenance.clone();
689 }
690 resolved.defined_properties
691}
692
693fn resolve_property_columns_for_node(
694 tnd: &TemplateNodeDefinition,
695 base_defined_properties: &BTreeMap<String, ValueDefinition>,
696 containing_component_settings: &Option<Vec<SettingsBlockElement>>,
697 timelines: &[TimelineDefinition],
698 expanded_node: Option<&Rc<ExpandedNode>>,
699) -> RuntimeResolvedPropertyColumns {
700 let mut resolved = resolve_runtime_settings_for_node(
701 tnd,
702 base_defined_properties,
703 containing_component_settings,
704 timelines,
705 expanded_node,
706 );
707 if let Some(expanded_node) = expanded_node {
708 if expanded_node.reset_removed_runtime_properties.get() {
709 add_removed_property_resets(
710 &expanded_node.resolved_property_columns.borrow(),
711 &mut resolved.columns,
712 );
713 }
714 *expanded_node.resolved_property_columns.borrow_mut() = resolved.columns.clone();
715 *expanded_node.resolved_property_provenance.borrow_mut() = resolved.provenance.clone();
716 }
717 resolved.columns
718}
719
720pub struct TemplatePropertyPlan {
724 node: TemplateNodeDefinition,
725 base_defined_properties: BTreeMap<String, ValueDefinition>,
726 component_settings: Option<Vec<SettingsBlockElement>>,
727 timelines: Vec<TimelineDefinition>,
728 pub(crate) requires_pre_node_binding: bool,
729 #[cfg(test)]
730 resolutions: std::cell::Cell<usize>,
731}
732
733impl TemplatePropertyPlan {
734 pub(crate) fn new(
735 node: TemplateNodeDefinition,
736 base_defined_properties: BTreeMap<String, ValueDefinition>,
737 component_settings: Option<Vec<SettingsBlockElement>>,
738 timelines: Vec<TimelineDefinition>,
739 ) -> Self {
740 let requires_pre_node_binding = base_defined_properties
741 .values()
742 .any(value_uses_local_property_scope)
743 || node.settings.iter().flatten().any(|setting| {
744 matches!(setting,
745 SettingElement::Setting(_, value) if value_uses_local_property_scope(value))
746 });
747 Self {
748 node,
749 base_defined_properties,
750 component_settings,
751 timelines,
752 requires_pre_node_binding,
753 #[cfg(test)]
754 resolutions: std::cell::Cell::new(0),
755 }
756 }
757
758 pub(crate) fn resolve(
759 &self,
760 node: Option<&Rc<ExpandedNode>>,
761 ) -> RuntimeResolvedPropertyColumns {
762 #[cfg(test)]
763 self.resolutions.set(self.resolutions.get() + 1);
764 resolve_property_columns_for_node(
765 &self.node,
766 &self.base_defined_properties,
767 &self.component_settings,
768 &self.timelines,
769 node,
770 )
771 }
772}
773
774pub(crate) fn value_uses_local_property_scope(value: &ValueDefinition) -> bool {
775 match value {
776 ValueDefinition::Timeline(track) => track.use_local_property_scope,
777 ValueDefinition::Transition(transition) => transition
778 .enter
779 .iter()
780 .chain(transition.exit.iter())
781 .any(|track| track.use_local_property_scope),
782 _ => false,
783 }
784}
785
786pub struct HandlerDescriptor {
787 pub name: &'static str,
788 pub function: fn(Rc<RefCell<PaxAny>>, &NodeContext, Option<PaxAny>),
789}
790
791impl HandlerDescriptor {
792 pub const fn new(
793 name: &'static str,
794 function: fn(Rc<RefCell<PaxAny>>, &NodeContext, Option<PaxAny>),
795 ) -> Self {
796 Self { name, function }
797 }
798}
799
800pub struct PropertyScopeDescriptor<T> {
801 pub name: &'static str,
802 pub variable: fn(&T) -> Variable,
803 marker: PhantomData<fn(&T)>,
804}
805
806impl<T> PropertyScopeDescriptor<T> {
807 pub const fn new(name: &'static str, variable: fn(&T) -> Variable) -> Self {
808 Self {
809 name,
810 variable,
811 marker: PhantomData,
812 }
813 }
814}
815
816pub struct ComponentPropertyDescriptor<T> {
819 pub name: &'static str,
821 pub apply_entries:
823 fn(&mut T, &[RuntimeResolvedPropertyEntry], &Rc<RuntimePropertiesStackFrame>),
824 marker: PhantomData<fn(&T)>,
825}
826
827impl<T> ComponentPropertyDescriptor<T> {
828 pub const fn new(
830 name: &'static str,
831 apply_entries: fn(
832 &mut T,
833 &[RuntimeResolvedPropertyEntry],
834 &Rc<RuntimePropertiesStackFrame>,
835 ),
836 ) -> Self {
837 Self {
838 name,
839 apply_entries,
840 marker: PhantomData,
841 }
842 }
843}
844
845pub struct ComponentDescriptor<T: Default + ToFromPaxAny + 'static> {
846 pub type_id: &'static str,
847 pub property_scope_descriptors: &'static [PropertyScopeDescriptor<T>],
848 pub property_descriptors: &'static [ComponentPropertyDescriptor<T>],
849 pub handler_descriptors: &'static [HandlerDescriptor],
850 pub instantiate: fn(InstantiationArgs) -> Rc<dyn InstanceNode>,
851 marker: PhantomData<fn(&T)>,
852}
853
854impl<T: Default + ToFromPaxAny + 'static> ComponentDescriptor<T> {
855 pub const fn new(
856 type_id: &'static str,
857 property_scope_descriptors: &'static [PropertyScopeDescriptor<T>],
858 property_descriptors: &'static [ComponentPropertyDescriptor<T>],
859 handler_descriptors: &'static [HandlerDescriptor],
860 instantiate: fn(InstantiationArgs) -> Rc<dyn InstanceNode>,
861 ) -> Self {
862 Self {
863 type_id,
864 property_scope_descriptors,
865 property_descriptors,
866 handler_descriptors,
867 instantiate,
868 marker: PhantomData,
869 }
870 }
871}
872
873pub struct ErasedComponentDescriptor {
874 pub type_id: &'static str,
875 pub typed_descriptor: &'static (dyn Any + Sync),
876 pub create_properties: fn() -> PaxAny,
877 pub apply_defined_properties: fn(
878 &'static (dyn Any + Sync),
879 &mut PaxAny,
880 &RuntimeResolvedPropertyColumns,
881 &Rc<RuntimePropertiesStackFrame>,
882 ),
883 pub build_property_scope: fn(&'static (dyn Any + Sync), &PaxAny) -> HashMap<String, Variable>,
884 pub handler_descriptors: &'static [HandlerDescriptor],
885 pub instantiate: fn(InstantiationArgs) -> Rc<dyn InstanceNode>,
886}
887
888impl ErasedComponentDescriptor {
889 pub const fn new(
890 type_id: &'static str,
891 typed_descriptor: &'static (dyn Any + Sync),
892 create_properties: fn() -> PaxAny,
893 apply_defined_properties: fn(
894 &'static (dyn Any + Sync),
895 &mut PaxAny,
896 &RuntimeResolvedPropertyColumns,
897 &Rc<RuntimePropertiesStackFrame>,
898 ),
899 build_property_scope: fn(&'static (dyn Any + Sync), &PaxAny) -> HashMap<String, Variable>,
900 handler_descriptors: &'static [HandlerDescriptor],
901 instantiate: fn(InstantiationArgs) -> Rc<dyn InstanceNode>,
902 ) -> Self {
903 Self {
904 type_id,
905 typed_descriptor,
906 create_properties,
907 apply_defined_properties,
908 build_property_scope,
909 handler_descriptors,
910 instantiate,
911 }
912 }
913}
914
915pub struct ComponentDescriptorRegistryEntry {
916 pub type_id: &'static str,
917 pub descriptor: &'static ErasedComponentDescriptor,
918}
919
920impl ComponentDescriptorRegistryEntry {
921 pub const fn new(
922 type_id: &'static str,
923 descriptor: &'static ErasedComponentDescriptor,
924 ) -> Self {
925 Self {
926 type_id,
927 descriptor,
928 }
929 }
930}
931
932pub struct TypeFieldDescriptor<T> {
933 pub name: &'static str,
934 pub apply: fn(&mut T, &ValueDefinition, &Rc<RuntimePropertiesStackFrame>),
935 pub dependency: Option<fn(&T) -> UntypedProperty>,
936 pub touch: Option<fn(&T)>,
937 marker: PhantomData<fn(&T)>,
938}
939
940impl<T> TypeFieldDescriptor<T> {
941 pub const fn new(
942 name: &'static str,
943 apply: fn(&mut T, &ValueDefinition, &Rc<RuntimePropertiesStackFrame>),
944 ) -> Self {
945 Self {
946 name,
947 apply,
948 dependency: None,
949 touch: None,
950 marker: PhantomData,
951 }
952 }
953
954 pub const fn with_property_dependency(
955 name: &'static str,
956 apply: fn(&mut T, &ValueDefinition, &Rc<RuntimePropertiesStackFrame>),
957 dependency: fn(&T) -> UntypedProperty,
958 touch: fn(&T),
959 ) -> Self {
960 Self {
961 name,
962 apply,
963 dependency: Some(dependency),
964 touch: Some(touch),
965 marker: PhantomData,
966 }
967 }
968}
969
970pub struct TypeDescriptor<T: 'static> {
971 pub name: &'static str,
972 pub fields: &'static [TypeFieldDescriptor<T>],
973 marker: PhantomData<fn(&T)>,
974}
975
976impl<T> TypeDescriptor<T> {
977 pub const fn new(name: &'static str, fields: &'static [TypeFieldDescriptor<T>]) -> Self {
978 Self {
979 name,
980 fields,
981 marker: PhantomData,
982 }
983 }
984}
985
986fn missing_handler(_: Rc<RefCell<PaxAny>>, _: &NodeContext, _: Option<PaxAny>) {}
987
988pub fn resolve_handler(
989 descriptors: &[HandlerDescriptor],
990 fn_name: &str,
991) -> fn(Rc<RefCell<PaxAny>>, &NodeContext, Option<PaxAny>) {
992 descriptors
993 .iter()
994 .find(|descriptor| descriptor.name == fn_name)
995 .map(|descriptor| descriptor.function)
996 .unwrap_or_else(|| {
997 log::warn!("Unknown handler name {}", fn_name);
998 missing_handler
999 })
1000}
1001
1002pub fn build_component_handler_registry(
1003 descriptors: &[HandlerDescriptor],
1004 handlers: Vec<(String, Vec<String>)>,
1005) -> Rc<RefCell<HandlerRegistry>> {
1006 let mut handler_registry = HandlerRegistry::default();
1007 for (event, functions) in handlers {
1008 handler_registry.handlers.insert(
1009 event,
1010 functions
1011 .iter()
1012 .map(|fn_name| {
1013 Handler::new_component_handler(resolve_handler(descriptors, fn_name))
1014 })
1015 .collect(),
1016 );
1017 }
1018 Rc::new(RefCell::new(handler_registry))
1019}
1020
1021pub fn add_inline_handlers_from_descriptors(
1022 descriptors: &[HandlerDescriptor],
1023 handlers: Vec<(String, String)>,
1024 registry: Rc<RefCell<HandlerRegistry>>,
1025) -> Rc<RefCell<HandlerRegistry>> {
1026 {
1027 let mut registry_mut = borrow_mut!(registry);
1028 for (event, fn_name) in handlers {
1029 let handler_vec = registry_mut.handlers.entry(event).or_insert_with(Vec::new);
1030 handler_vec.push(Handler::new_inline_handler(resolve_handler(
1031 descriptors,
1032 &fn_name,
1033 )));
1034 }
1035 }
1036 registry
1037}
1038
1039pub fn build_property_scope<T>(
1040 properties: &T,
1041 descriptors: &[PropertyScopeDescriptor<T>],
1042) -> HashMap<String, Variable> {
1043 descriptors
1044 .iter()
1045 .map(|descriptor| {
1046 (
1047 descriptor.name.to_string(),
1048 (descriptor.variable)(properties),
1049 )
1050 })
1051 .collect()
1052}
1053
1054pub fn apply_component_descriptor_properties<T: Default + ToFromPaxAny + 'static>(
1055 properties: &mut T,
1056 descriptor: &'static ComponentDescriptor<T>,
1057 property_columns: &RuntimeResolvedPropertyColumns,
1058 stack_frame: &Rc<RuntimePropertiesStackFrame>,
1059) {
1060 thread_local! {
1061 static WARNED_INCOMPATIBLE_SELECTOR_PROPERTIES: RefCell<std::collections::HashSet<String>> =
1062 RefCell::new(std::collections::HashSet::new());
1063 }
1064
1065 for (property_name, entries) in property_columns {
1066 let is_known = descriptor
1067 .property_descriptors
1068 .iter()
1069 .any(|property| property.name == property_name)
1070 || COMMON_PROPERTIES.contains(&property_name.as_str())
1071 || matches!(
1072 property_name.as_str(),
1073 "unclippable" | "_raycastable" | "_suspended"
1074 );
1075 if is_known {
1076 continue;
1077 }
1078 for entry in entries.iter().filter(|entry| entry.selector.is_some()) {
1079 let key = format!(
1080 "{}:{property_name}:{:?}",
1081 descriptor.type_id, entry.selector
1082 );
1083 WARNED_INCOMPATIBLE_SELECTOR_PROPERTIES.with(|warnings| {
1084 if warnings.borrow_mut().insert(key) {
1085 let location = entry
1086 .source_location
1087 .as_ref()
1088 .map(|location| {
1089 format!(
1090 " at {}:{}",
1091 location.start_line_col.0, location.start_line_col.1
1092 )
1093 })
1094 .unwrap_or_default();
1095 log::warn!(
1096 "selector {:?} assigns property '{}' which does not apply to {}{}; ignoring it",
1097 entry.selector,
1098 property_name,
1099 descriptor.type_id,
1100 location,
1101 );
1102 }
1103 });
1104 }
1105 }
1106
1107 for property_descriptor in descriptor.property_descriptors {
1108 if let Some(entries) = property_columns.get(property_descriptor.name) {
1109 (property_descriptor.apply_entries)(properties, entries, stack_frame);
1110 }
1111 }
1112}
1113
1114pub fn resolve_component_descriptor(
1115 entries: &[ComponentDescriptorRegistryEntry],
1116 type_id: &TypeId,
1117) -> Option<&'static ErasedComponentDescriptor> {
1118 let identifier = type_id.get_unique_identifier();
1119 entries
1120 .iter()
1121 .find(|entry| entry.type_id == identifier)
1122 .map(|entry| entry.descriptor)
1123}
1124
1125pub fn erased_create_properties<T: Default + ToFromPaxAny + 'static>() -> PaxAny {
1126 T::default().to_pax_any()
1127}
1128
1129pub fn erased_apply_defined_properties<T: Default + ToFromPaxAny + 'static>(
1130 typed_descriptor: &'static (dyn Any + Sync),
1131 pax_any: &mut PaxAny,
1132 property_columns: &RuntimeResolvedPropertyColumns,
1133 stack_frame: &Rc<RuntimePropertiesStackFrame>,
1134) {
1135 let descriptor = (typed_descriptor as &dyn Any)
1136 .downcast_ref::<ComponentDescriptor<T>>()
1137 .expect("Failed to downcast erased component descriptor");
1138 let properties = T::mut_from_pax_any(pax_any).unwrap_or_else(|err| {
1139 panic!(
1140 "Failed to downcast properties to {}: {}",
1141 descriptor.type_id, err
1142 )
1143 });
1144 apply_component_descriptor_properties(properties, descriptor, property_columns, stack_frame);
1145}
1146
1147pub fn erased_build_property_scope<T: Default + ToFromPaxAny + 'static>(
1148 typed_descriptor: &'static (dyn Any + Sync),
1149 pax_any: &PaxAny,
1150) -> HashMap<String, Variable> {
1151 let descriptor = (typed_descriptor as &dyn Any)
1152 .downcast_ref::<ComponentDescriptor<T>>()
1153 .expect("Failed to downcast erased component descriptor");
1154 let properties = T::ref_from_pax_any(pax_any).unwrap_or_else(|err| {
1155 panic!(
1156 "Failed to downcast properties to {}: {}",
1157 descriptor.type_id, err
1158 )
1159 });
1160 build_property_scope(properties, descriptor.property_scope_descriptors)
1161}
1162
1163pub fn build_component_handler_registry_for_descriptor(
1164 descriptor: &'static ErasedComponentDescriptor,
1165 handlers: Vec<(String, Vec<String>)>,
1166) -> Rc<RefCell<HandlerRegistry>> {
1167 build_component_handler_registry(descriptor.handler_descriptors, handlers)
1168}
1169
1170pub fn add_inline_handlers_from_component_descriptor(
1171 descriptor: &'static ErasedComponentDescriptor,
1172 handlers: Vec<(String, String)>,
1173 registry: Rc<RefCell<HandlerRegistry>>,
1174) -> Rc<RefCell<HandlerRegistry>> {
1175 add_inline_handlers_from_descriptors(descriptor.handler_descriptors, handlers, registry)
1176}
1177
1178pub fn instantiate_component_from_descriptor(
1179 descriptor: &'static ErasedComponentDescriptor,
1180 args: InstantiationArgs,
1181) -> Rc<dyn InstanceNode> {
1182 (descriptor.instantiate)(args)
1183}
1184
1185pub fn build_literal_block_property<T: PropertyValue>(
1186 descriptor: &'static TypeDescriptor<T>,
1187 args: &LiteralBlockDefinition,
1188 stack_frame: Rc<RuntimePropertiesStackFrame>,
1189) -> Property<T> {
1190 if descriptor.fields.is_empty() {
1191 for setting in &args.elements {
1192 if let SettingElement::Setting(k, _) = setting {
1193 panic!("Unknown property name {}", k.token_value);
1194 }
1195 }
1196 return Property::new_with_name(Default::default(), descriptor.name);
1197 }
1198
1199 let mut properties = T::default();
1200 for setting in &args.elements {
1201 if let SettingElement::Setting(k, value_definition) = setting {
1202 let field_descriptor = descriptor
1203 .fields
1204 .iter()
1205 .find(|descriptor| descriptor.name == k.token_value)
1206 .unwrap_or_else(|| panic!("Unknown property name {}", k.token_value));
1207 (field_descriptor.apply)(&mut properties, value_definition, &stack_frame);
1208 }
1209 }
1210
1211 let dependents: Vec<_> = descriptor
1212 .fields
1213 .iter()
1214 .filter_map(|descriptor| {
1215 descriptor
1216 .dependency
1217 .map(|dependency| dependency(&properties))
1218 })
1219 .collect();
1220
1221 if dependents.is_empty() {
1222 return Property::new_with_name(properties, descriptor.name);
1223 }
1224
1225 let touchers: Vec<_> = descriptor
1226 .fields
1227 .iter()
1228 .filter_map(|descriptor| descriptor.touch)
1229 .collect();
1230 let computed_properties = properties.clone();
1231 Property::computed_with_name(
1232 move || {
1233 let cloned_properties = computed_properties.clone();
1234 for touch in &touchers {
1235 touch(&cloned_properties);
1236 }
1237 cloned_properties
1238 },
1239 &dependents,
1240 descriptor.name,
1241 )
1242}
1243
1244fn build_conditional_branch_property(
1245 branch_kind: pax_manifest::ControlFlowConditionalBranchKind,
1246 condition_expression: Option<ExpressionInfo>,
1247 stack_frame: Rc<RuntimePropertiesStackFrame>,
1248) -> Property<bool> {
1249 match condition_expression {
1250 Some(expr_info) => build_conditional_expression_property(expr_info, stack_frame),
1251 None => Property::new(matches!(
1252 branch_kind,
1253 pax_manifest::ControlFlowConditionalBranchKind::Else
1254 )),
1255 }
1256}
1257
1258fn build_conditional_expression_property(
1259 expr_info: ExpressionInfo,
1260 stack_frame: Rc<RuntimePropertiesStackFrame>,
1261) -> Property<bool> {
1262 let cloned_stack = stack_frame.clone();
1263 let expr_ast = expr_info.expression.clone();
1264
1265 let mut dependencies = Vec::new();
1266 for dependency in &expr_info.dependencies {
1267 if let Some(p) = stack_frame.resolve_symbol_as_erased_property(dependency) {
1268 dependencies.push(p);
1269 } else {
1270 log::warn!("Failed to resolve symbol {}", dependency);
1271 }
1272 }
1273
1274 let name = format!("conditional (if) expr ({})", expr_ast);
1275 Property::computed_with_name(
1276 move || {
1277 let new_value = expr_ast
1278 .compute(cloned_stack.clone())
1279 .unwrap_or_else(|err| {
1280 log::warn!("Failed to compute expression: {:?}", err);
1281 Default::default()
1282 });
1283 bool::try_coerce(new_value).unwrap_or_else(|_e| {
1284 log::warn!("Failed to parse boolean expression: {}", expr_ast);
1285 Default::default()
1286 })
1287 },
1288 &dependencies,
1289 &name,
1290 )
1291}
1292
1293pub trait DefinitionToInstanceTraverser {
1294 fn new(manifest: pax_manifest::PaxManifest) -> Self
1295 where
1296 Self: Sized;
1297
1298 fn get_manifest(&self) -> std::cell::Ref<'_, pax_manifest::PaxManifest>;
1299
1300 #[cfg(feature = "designtime")]
1301 fn get_designtime_manager(
1302 &self,
1303 project_query: String,
1304 ) -> std::option::Option<std::rc::Rc<RefCell<pax_designtime::DesigntimeManager>>>;
1305
1306 fn get_main_component(&self, id: &str) -> std::rc::Rc<crate::ComponentInstance> {
1307 let main_component_type_id = {
1308 let manifest = self.get_manifest();
1309 manifest.main_component_type_id.clone()
1310 };
1311
1312 let wrapper_type_id = TypeId::build_singleton(id, Some("RootComponent"));
1313
1314 let mut args = self.build_component_args(&main_component_type_id);
1315 args.template_node_identifier = Some(pax_manifest::UniqueTemplateNodeIdentifier::build(
1316 wrapper_type_id,
1317 pax_manifest::TemplateNodeId::build(0),
1318 ));
1319 let main_component = crate::ComponentInstance::instantiate(args);
1320 main_component
1321 }
1322
1323 fn get_component(
1324 &mut self,
1325 type_id: &pax_manifest::TypeId,
1326 ) -> std::rc::Rc<dyn crate::rendering::InstanceNode> {
1327 let descriptor = self
1328 .get_component_descriptor(type_id)
1329 .expect("Failed to get component descriptor");
1330 let args = self.build_component_args(type_id);
1331 instantiate_component_from_descriptor(descriptor, args)
1332 }
1333
1334 fn get_component_descriptor(
1335 &self,
1336 type_id: &pax_manifest::TypeId,
1337 ) -> Option<&'static ErasedComponentDescriptor>;
1338
1339 fn build_component_args(
1340 &self,
1341 type_id: &pax_manifest::TypeId,
1342 ) -> crate::rendering::InstantiationArgs {
1343 let manifest: std::cell::Ref<pax_manifest::PaxManifest> = self.get_manifest();
1344 if let None = manifest.components.get(type_id) {
1345 panic!("Components with type_id {} not found in manifest", type_id);
1346 }
1347 let component = manifest.components.get(type_id).unwrap();
1348 let descriptor = self
1349 .get_component_descriptor(&type_id)
1350 .expect(&format!("No component descriptor for type: {}", type_id));
1351 let prototypical_common_properties = crate::CommonPropertiesInit::Default;
1352 let mut prototypical_properties = crate::PropertiesInit::DescriptorDefault(descriptor);
1353
1354 let handlers = manifest.get_component_handlers(type_id);
1356 let handler_registry = Some(build_component_handler_registry_for_descriptor(
1357 descriptor, handlers,
1358 ));
1359
1360 let mut component_template = None;
1361 if let Some(template) = &component.template {
1362 let root = template.get_root();
1363 let mut instances = Vec::new();
1364 for node_id in root {
1365 let node = template.get_node(&node_id).unwrap();
1366 match node.type_id.get_pax_type() {
1367 pax_manifest::PaxType::If
1368 | pax_manifest::PaxType::Router
1369 | pax_manifest::PaxType::Slot
1370 | pax_manifest::PaxType::Repeat => {
1371 instances.push(self.build_control_flow(type_id, &node_id, None));
1372 }
1373 pax_manifest::PaxType::Comment => continue,
1374 _ => {
1375 instances.push(self.build_template_node(type_id, &node_id, None));
1376 }
1377 }
1378 }
1379 component_template = Some(RefCell::new(instances));
1380 }
1381
1382 let mut component_self_timeline_properties = BTreeMap::new();
1383 manifest.merge_component_self_timelines_with_properties(
1384 type_id,
1385 &mut component_self_timeline_properties,
1386 );
1387 if !component_self_timeline_properties.is_empty() {
1388 prototypical_properties = crate::PropertiesInit::DescriptorInline {
1389 descriptor,
1390 defined_properties: component_self_timeline_properties,
1391 };
1392 }
1393
1394 crate::rendering::InstantiationArgs {
1395 prototypical_common_properties,
1396 prototypical_properties,
1397 handler_registry,
1398 component_template,
1399 children: None,
1400 component_settings: component.settings.clone(),
1401 template_node_identifier: None,
1402 template_node_type_id: None,
1403 template_node_selector_info: None,
1404 transition_config: manifest.get_component_transition_config(type_id),
1405 properties_scope: crate::PropertiesScopeInit::Descriptor(descriptor),
1406 }
1407 }
1408
1409 fn build_control_flow(
1410 &self,
1411 containing_component_type_id: &pax_manifest::TypeId,
1412 node_id: &pax_manifest::TemplateNodeId,
1413 prior_node: Option<ReusableInstanceNodeArgs>,
1414 ) -> std::rc::Rc<dyn crate::rendering::InstanceNode> {
1415 let manifest = self.get_manifest();
1416 let prototypical_common_properties = crate::CommonPropertiesInit::Default;
1417
1418 let containing_component = manifest
1419 .components
1420 .get(containing_component_type_id)
1421 .unwrap();
1422 let containing_template = containing_component.template.as_ref().unwrap();
1423 let tnd = containing_template.get_node(node_id).unwrap();
1424 let unique_identifier = pax_manifest::UniqueTemplateNodeIdentifier::build(
1425 containing_component_type_id.clone(),
1426 node_id.clone(),
1427 );
1428
1429 let children: RefCell<Vec<Rc<dyn InstanceNode>>> = if let Some(prior_node) = prior_node {
1430 prior_node.children
1431 } else {
1432 RefCell::new(self.build_children(containing_component_type_id, &node_id))
1433 };
1434 match tnd.type_id.get_pax_type() {
1435 pax_manifest::PaxType::If => {
1436 let control_flow_settings = tnd.control_flow_settings.as_ref().unwrap();
1437 let conditional_branch_definitions =
1438 if control_flow_settings.conditional_branches.is_empty() {
1439 control_flow_settings
1440 .condition_expression
1441 .clone()
1442 .map(|condition| {
1443 vec![(
1444 pax_manifest::ControlFlowConditionalBranchKind::If,
1445 Some(condition),
1446 )]
1447 })
1448 .unwrap_or_default()
1449 } else {
1450 control_flow_settings
1451 .conditional_branches
1452 .iter()
1453 .map(|branch| {
1454 (
1455 branch.branch_kind.clone(),
1456 branch.condition_expression.clone(),
1457 )
1458 })
1459 .collect::<Vec<_>>()
1460 };
1461 let branch_child_ranges = if control_flow_settings.conditional_branches.is_empty() {
1462 vec![]
1463 } else {
1464 let mut start = 0;
1465 control_flow_settings
1466 .conditional_branches
1467 .iter()
1468 .map(|branch| {
1469 let branch_instance_count = branch
1470 .child_ids
1471 .iter()
1472 .filter(|child_id| {
1473 containing_template
1474 .get_node(child_id)
1475 .map(|child| {
1476 child.type_id.get_pax_type()
1477 != &pax_manifest::PaxType::Comment
1478 })
1479 .unwrap_or(false)
1480 })
1481 .count();
1482 let end = start + branch_instance_count;
1483 let range = start..end;
1484 start = end;
1485 range
1486 })
1487 .collect()
1488 };
1489 let prototypical_properties_factory: Box<
1490 dyn Fn(
1491 std::rc::Rc<crate::RuntimePropertiesStackFrame>,
1492 Option<std::rc::Rc<ExpandedNode>>,
1493 )
1494 -> Option<std::rc::Rc<RefCell<pax_runtime_api::pax_value::PaxAny>>>,
1495 > = Box::new(move |stack_frame, expanded_node| {
1496 let conditional_branch_properties = conditional_branch_definitions
1497 .iter()
1498 .cloned()
1499 .map(|(branch_kind, condition_expression)| {
1500 build_conditional_branch_property(
1501 branch_kind,
1502 condition_expression,
1503 stack_frame.clone(),
1504 )
1505 })
1506 .collect::<Vec<_>>();
1507 let boolean_expression = conditional_branch_properties
1508 .first()
1509 .cloned()
1510 .unwrap_or_else(|| Property::new(false));
1511
1512 if let Some(expanded_node) = &expanded_node {
1513 let expanded_node = borrow!(**expanded_node);
1514 let outer_ref = expanded_node.properties.borrow();
1515 let rc = Rc::clone(&outer_ref);
1516 let mut inner_ref = (*rc).borrow_mut();
1517 let cp = ConditionalProperties::mut_from_pax_any(&mut inner_ref).unwrap();
1518 cp.boolean_expression.replace_with(boolean_expression);
1519 cp.conditional_branches = conditional_branch_properties;
1520 return None;
1521 }
1522
1523 Some(std::rc::Rc::new(RefCell::new({
1524 let mut properties = crate::ConditionalProperties::default();
1525 properties.boolean_expression = boolean_expression;
1526 properties.conditional_branches = conditional_branch_properties;
1527 properties.to_pax_any()
1528 })))
1529 });
1530 crate::ConditionalInstance::instantiate_with_branch_child_ranges(
1531 crate::rendering::InstantiationArgs {
1532 prototypical_common_properties,
1533 prototypical_properties: crate::PropertiesInit::Factory(
1534 prototypical_properties_factory,
1535 ),
1536 handler_registry: None,
1537 component_template: None,
1538 children: Some(children),
1539 component_settings: None,
1540 template_node_identifier: Some(unique_identifier),
1541 template_node_type_id: Some(tnd.type_id.clone()),
1542 template_node_selector_info: Some(tnd.selector_info.clone()),
1543 transition_config: Default::default(),
1544 properties_scope: crate::PropertiesScopeInit::None,
1545 },
1546 branch_child_ranges,
1547 )
1548 }
1549 pax_manifest::PaxType::Router => {
1550 let control_flow_settings = tnd.control_flow_settings.as_ref().unwrap();
1551 let route_branch_definitions = control_flow_settings.route_branches.clone();
1552 let compiled_route_branches =
1553 crate::compile_route_branches(&route_branch_definitions);
1554 let branch_child_ranges = if route_branch_definitions.is_empty() {
1555 vec![]
1556 } else {
1557 let mut start = 0;
1558 route_branch_definitions
1559 .iter()
1560 .map(|branch| {
1561 let branch_instance_count = branch
1562 .child_ids
1563 .iter()
1564 .filter(|child_id| {
1565 containing_template
1566 .get_node(child_id)
1567 .map(|child| {
1568 child.type_id.get_pax_type()
1569 != &pax_manifest::PaxType::Comment
1570 })
1571 .unwrap_or(false)
1572 })
1573 .count();
1574 let end = start + branch_instance_count;
1575 let range = start..end;
1576 start = end;
1577 range
1578 })
1579 .collect()
1580 };
1581
1582 let prototypical_properties_factory: Box<
1583 dyn Fn(
1584 std::rc::Rc<crate::RuntimePropertiesStackFrame>,
1585 Option<std::rc::Rc<ExpandedNode>>,
1586 )
1587 -> Option<std::rc::Rc<RefCell<pax_runtime_api::pax_value::PaxAny>>>,
1588 > = Box::new(move |stack_frame, expanded_node| {
1589 let global_location = stack_frame
1590 .resolve_symbol_as_erased_property(INTERNAL_ROUTE_LOCATION_SYMBOL)
1591 .map(Property::<RouteLocation>::new_from_untyped)
1592 .unwrap_or_else(|| Property::new(RouteLocation::root()));
1593 let input_location = stack_frame
1594 .resolve_symbol_as_erased_property(INTERNAL_ROUTE_MATCH_SYMBOL)
1595 .map(Property::<RouteMatch>::new_from_untyped)
1596 .map(|route_match| {
1597 let dependency = route_match.untyped();
1598 Property::computed_with_name(
1599 move || route_match.get().remainder_location(),
1600 &[dependency],
1601 "router input location",
1602 )
1603 })
1604 .unwrap_or_else(|| global_location.clone());
1605
1606 if let Some(expanded_node) = &expanded_node {
1607 let expanded_node = borrow!(**expanded_node);
1608 let outer_ref = expanded_node.properties.borrow();
1609 let rc = Rc::clone(&outer_ref);
1610 let mut inner_ref = (*rc).borrow_mut();
1611 let router =
1612 crate::RouterProperties::mut_from_pax_any(&mut inner_ref).unwrap();
1613 router.input_location.replace_with(input_location);
1614 router.global_location.replace_with(global_location);
1615 return None;
1616 }
1617
1618 Some(std::rc::Rc::new(RefCell::new({
1619 let mut properties = crate::RouterProperties::default();
1620 properties.input_location = input_location;
1621 properties.global_location = global_location;
1622 properties.to_pax_any()
1623 })))
1624 });
1625
1626 crate::RouterInstance::instantiate_with_branches(
1627 crate::rendering::InstantiationArgs {
1628 prototypical_common_properties,
1629 prototypical_properties: crate::PropertiesInit::Factory(
1630 prototypical_properties_factory,
1631 ),
1632 handler_registry: None,
1633 component_template: None,
1634 children: Some(children),
1635 component_settings: None,
1636 template_node_identifier: Some(unique_identifier),
1637 template_node_type_id: Some(tnd.type_id.clone()),
1638 template_node_selector_info: Some(tnd.selector_info.clone()),
1639 transition_config: Default::default(),
1640 properties_scope: crate::PropertiesScopeInit::None,
1641 },
1642 compiled_route_branches,
1643 branch_child_ranges,
1644 )
1645 }
1646 pax_manifest::PaxType::Slot => {
1647 let expr_info = tnd
1648 .control_flow_settings
1649 .as_ref()
1650 .unwrap()
1651 .slot_index_expression
1652 .clone();
1653
1654 let prototypical_properties_factory: Box<
1655 dyn Fn(
1656 std::rc::Rc<crate::RuntimePropertiesStackFrame>,
1657 Option<std::rc::Rc<ExpandedNode>>,
1658 )
1659 -> Option<std::rc::Rc<RefCell<pax_runtime_api::pax_value::PaxAny>>>,
1660 > = Box::new(move |stack_frame, expanded_node| {
1661 let Some(expr_info) = expr_info.clone() else {
1662 if let Some(expanded_node) = &expanded_node {
1663 let expanded_node = borrow!(**expanded_node);
1664 let outer_ref = expanded_node.properties.borrow();
1665 let rc = Rc::clone(&outer_ref);
1666 let mut inner_ref = (*rc).borrow_mut();
1667 let slot_properties =
1668 crate::Slot::mut_from_pax_any(&mut inner_ref).unwrap();
1669 slot_properties.is_remainder.set(true);
1670 return None;
1671 }
1672
1673 return Some(std::rc::Rc::new(RefCell::new({
1674 let mut properties = crate::Slot::default();
1675 properties.is_remainder = Property::new(true);
1676 properties.to_pax_any()
1677 })));
1678 };
1679
1680 let cloned_stack = stack_frame.clone();
1681 let expr_ast = expr_info.expression.clone();
1682
1683 let mut dependencies = Vec::new();
1684 for dependency in &expr_info.dependencies {
1685 if let Some(p) = stack_frame.resolve_symbol_as_erased_property(dependency) {
1686 dependencies.push(p);
1687 } else {
1688 log::warn!("Failed to resolve symbol {}", dependency);
1689 }
1690 }
1691
1692 if let Some(expanded_node) = &expanded_node {
1693 let expanded_node = borrow!(**expanded_node);
1694 let outer_ref = expanded_node.properties.borrow();
1695 let rc = Rc::clone(&outer_ref);
1696 let mut inner_ref = (*rc).borrow_mut();
1697 let slot_properties =
1698 crate::Slot::mut_from_pax_any(&mut inner_ref).unwrap();
1699 slot_properties
1700 .index
1701 .replace_with(Property::computed_with_name(
1702 move || {
1703 let new_value = expr_ast
1704 .compute(cloned_stack.clone())
1705 .unwrap_or_else(|op_err| {
1706 log::warn!(
1707 "Failed to compute expression: {:?}",
1708 op_err
1709 );
1710 Default::default()
1711 });
1712 let coerced: Numeric = Numeric::try_coerce(new_value)
1713 .unwrap_or_else(|_| {
1714 log::warn!(
1715 "Failed to parse slot index expression: {}",
1716 expr_ast
1717 );
1718 Default::default()
1719 });
1720 coerced
1721 },
1722 &dependencies,
1723 "slot index",
1724 ));
1725 return None;
1726 }
1727
1728 Some(std::rc::Rc::new(RefCell::new({
1729 let mut properties = crate::Slot::default();
1730
1731 properties.index = Property::computed_with_name(
1732 move || {
1733 let new_value = expr_ast
1734 .compute(cloned_stack.clone())
1735 .unwrap_or_else(|op_err| {
1736 log::warn!("Failed to compute expression: {:?}", op_err);
1737 Default::default()
1738 });
1739 let coerced: Numeric = Numeric::try_coerce(new_value)
1740 .unwrap_or_else(|_| {
1741 log::warn!(
1742 "Failed to parse slot index expression: {}",
1743 expr_ast
1744 );
1745 Default::default()
1746 });
1747 coerced
1748 },
1749 &dependencies,
1750 "slot index",
1751 );
1752 properties.to_pax_any()
1753 })))
1754 });
1755 crate::SlotInstance::instantiate(crate::rendering::InstantiationArgs {
1756 prototypical_common_properties,
1757 prototypical_properties: crate::PropertiesInit::Factory(
1758 prototypical_properties_factory,
1759 ),
1760 handler_registry: None,
1761 component_template: None,
1762 children: Some(children),
1763 component_settings: None,
1764 template_node_identifier: Some(unique_identifier),
1765 template_node_type_id: Some(tnd.type_id.clone()),
1766 template_node_selector_info: Some(tnd.selector_info.clone()),
1767 transition_config: Default::default(),
1768 properties_scope: crate::PropertiesScopeInit::None,
1769 })
1770 }
1771 pax_manifest::PaxType::Repeat => {
1772 let source_expression_info = tnd
1773 .control_flow_settings
1774 .as_ref()
1775 .unwrap()
1776 .repeat_source_expression
1777 .clone()
1778 .unwrap();
1779 let repeat_key_expression = tnd
1780 .control_flow_settings
1781 .as_ref()
1782 .unwrap()
1783 .repeat_key_expression
1784 .clone();
1785 let predictate_definition = tnd
1786 .control_flow_settings
1787 .as_ref()
1788 .unwrap()
1789 .repeat_predicate_definition
1790 .clone()
1791 .unwrap();
1792 let prototypical_properties_factory: Box<
1793 dyn Fn(
1794 std::rc::Rc<crate::RuntimePropertiesStackFrame>,
1795 Option<std::rc::Rc<ExpandedNode>>,
1796 )
1797 -> Option<std::rc::Rc<RefCell<pax_runtime_api::pax_value::PaxAny>>>,
1798 > = Box::new(move |stack_frame, expanded_node| {
1799 let cloned_stack = stack_frame.clone();
1800 let expr = source_expression_info.expression.clone();
1801 let deps = source_expression_info.dependencies.clone();
1802
1803 let mut dependencies = Vec::new();
1804 for dependency in &deps {
1805 if let Some(p) = stack_frame.resolve_symbol_as_erased_property(dependency) {
1806 dependencies.push(p);
1807 } else {
1808 log::warn!("Failed to resolve symbol {}", dependency);
1809 }
1810 }
1811
1812 let (elem, index) = match &predictate_definition {
1813 pax_manifest::ControlFlowRepeatPredicateDefinition::ElemId(id) => {
1814 (Some(id.clone()), None)
1815 }
1816 pax_manifest::ControlFlowRepeatPredicateDefinition::ElemIdIndexId(
1817 t1,
1818 t2,
1819 ) => (Some(t1.clone()), Some(t2.clone())),
1820 };
1821
1822 if let Some(expanded_node) = &expanded_node {
1823 let expanded_node = borrow!(**expanded_node);
1824 let outer_ref = expanded_node.properties.borrow();
1825 let rc = Rc::clone(&outer_ref);
1826 let mut inner_ref = (*rc).borrow_mut();
1827 let repeat =
1828 crate::RepeatProperties::mut_from_pax_any(&mut inner_ref).unwrap();
1829 repeat
1830 .source_expression
1831 .replace_with(Property::computed_with_name(
1832 move || {
1833 expr.compute(cloned_stack.clone()).unwrap_or_else(|op_err| {
1834 log::warn!("Failed to compute expression: {:?}", op_err);
1835 Default::default()
1836 })
1837 },
1838 &dependencies,
1839 "repeat source vec",
1840 ));
1841 repeat.iterator_i_symbol.replace_with(Property::new(index));
1842 repeat
1843 .iterator_elem_symbol
1844 .replace_with(Property::new(elem));
1845 repeat.repeat_key_expression = repeat_key_expression.clone();
1846 return None;
1847 }
1848
1849 Some(std::rc::Rc::new(RefCell::new({
1850 let mut properties = crate::RepeatProperties::default();
1851
1852 properties.source_expression = Property::computed_with_name(
1853 move || {
1854 expr.compute(cloned_stack.clone()).unwrap_or_else(|op_err| {
1855 log::warn!("Failed to compute expression: {:?}", op_err);
1856 Default::default()
1857 })
1858 },
1859 &dependencies,
1860 "repeat source vec",
1861 );
1862
1863 properties
1864 .iterator_i_symbol
1865 .replace_with(Property::new(index));
1866 properties
1867 .iterator_elem_symbol
1868 .replace_with(Property::new(elem));
1869 properties.repeat_key_expression = repeat_key_expression.clone();
1870 properties.to_pax_any()
1871 })))
1872 });
1873 crate::RepeatInstance::instantiate(crate::rendering::InstantiationArgs {
1874 prototypical_common_properties,
1875 prototypical_properties: crate::PropertiesInit::Factory(
1876 prototypical_properties_factory,
1877 ),
1878 handler_registry: None,
1879 component_template: None,
1880 children: Some(children),
1881 component_settings: None,
1882 template_node_identifier: Some(unique_identifier),
1883 template_node_type_id: Some(tnd.type_id.clone()),
1884 template_node_selector_info: Some(tnd.selector_info.clone()),
1885 transition_config: Default::default(),
1886 properties_scope: crate::PropertiesScopeInit::None,
1887 })
1888 }
1889 _ => {
1890 unreachable!("Unexpected control flow type {}", tnd.type_id)
1891 }
1892 }
1893 }
1894
1895 fn build_children(
1896 &self,
1897 containing_component_type_id: &pax_manifest::TypeId,
1898 node_id: &pax_manifest::TemplateNodeId,
1899 ) -> Vec<std::rc::Rc<dyn crate::rendering::InstanceNode>> {
1900 let manifest = self.get_manifest();
1901 let containing_component = manifest
1902 .components
1903 .get(containing_component_type_id)
1904 .unwrap();
1905 let containing_template = containing_component.template.as_ref().unwrap();
1906 let children = containing_template.get_children(node_id);
1907
1908 let mut children_instances = Vec::new();
1909 for child_id in &children.unwrap_or_default() {
1910 let child = containing_template.get_node(&child_id).unwrap();
1911 match child.type_id.get_pax_type() {
1912 pax_manifest::PaxType::If
1913 | pax_manifest::PaxType::Router
1914 | pax_manifest::PaxType::Slot
1915 | pax_manifest::PaxType::Repeat => {
1916 children_instances.push(self.build_control_flow(
1917 containing_component_type_id,
1918 &child_id,
1919 None,
1920 ));
1921 }
1922 pax_manifest::PaxType::Comment => continue,
1923 _ => {
1924 children_instances.push(self.build_template_node(
1925 containing_component_type_id,
1926 child_id,
1927 None,
1928 ));
1929 }
1930 }
1931 }
1932 children_instances
1933 }
1934
1935 fn build_template_node(
1936 &self,
1937 containing_component_type_id: &pax_manifest::TypeId,
1938 node_id: &pax_manifest::TemplateNodeId,
1939 prior_node: Option<ReusableInstanceNodeArgs>,
1940 ) -> std::rc::Rc<dyn crate::rendering::InstanceNode> {
1941 let manifest = self.get_manifest();
1942
1943 let containing_component = manifest
1944 .components
1945 .get(containing_component_type_id)
1946 .unwrap();
1947 let containing_template = containing_component.template.as_ref().unwrap();
1948 let node = containing_template.get_node(node_id).unwrap();
1949 let node = node.clone();
1950 let containing_component_settings = containing_component.settings.clone();
1951 let containing_component_timelines = containing_component.timelines.clone();
1952 let containing_component_descriptor = self
1953 .get_component_descriptor(containing_component_type_id)
1954 .unwrap();
1955
1956 let mut args = self.build_component_args(&node.type_id);
1957 let node_component_descriptor = self.get_component_descriptor(&node.type_id).unwrap();
1958
1959 if let Some(prior_node) = prior_node {
1960 args.handler_registry = prior_node.handler_registry;
1961 args.children = Some(prior_node.children);
1962 args.template_node_identifier = prior_node.template_node_identifier;
1963 args.template_node_type_id = prior_node.template_node_type_id;
1964 args.template_node_selector_info = prior_node.template_node_selector_info;
1965 } else {
1966 let handlers_from_tnd = manifest.get_inline_event_handlers(&node);
1967 let updated_registry = if let Some(registry) = args.handler_registry {
1968 add_inline_handlers_from_component_descriptor(
1969 containing_component_descriptor,
1970 handlers_from_tnd,
1971 registry,
1972 )
1973 } else {
1974 add_inline_handlers_from_component_descriptor(
1975 containing_component_descriptor,
1976 handlers_from_tnd,
1977 std::rc::Rc::new(RefCell::new(crate::HandlerRegistry::default())),
1978 )
1979 };
1980 args.handler_registry = Some(updated_registry);
1982
1983 args.children = Some(RefCell::new(
1985 self.build_children(containing_component_type_id, node_id),
1986 ));
1987
1988 args.template_node_identifier =
1990 Some(pax_manifest::UniqueTemplateNodeIdentifier::build(
1991 containing_component_type_id.clone(),
1992 node_id.clone(),
1993 ));
1994 args.template_node_type_id = Some(node.type_id.clone());
1995 args.template_node_selector_info = Some(node.selector_info.clone());
1996 }
1997
1998 let mut inline_properties =
2000 manifest.get_inline_properties(containing_component_type_id, node_id, &node);
2001 manifest
2002 .merge_component_self_timelines_with_properties(&node.type_id, &mut inline_properties);
2003 let plan = Rc::new(TemplatePropertyPlan::new(
2004 node,
2005 inline_properties,
2006 containing_component_settings,
2007 containing_component_timelines,
2008 ));
2009 args.prototypical_properties = crate::PropertiesInit::Template {
2010 descriptor: node_component_descriptor,
2011 plan: Rc::clone(&plan),
2012 };
2013 args.prototypical_common_properties = crate::CommonPropertiesInit::Template(plan);
2014
2015 args.transition_config.merge_from(
2016 manifest.get_template_node_transition_config(containing_component_type_id, node_id),
2017 );
2018
2019 instantiate_component_from_descriptor(node_component_descriptor, args)
2020 }
2021
2022 fn get_template_node_by_id(
2023 &self,
2024 id: &str,
2025 ) -> Option<std::rc::Rc<dyn crate::rendering::InstanceNode>> {
2026 let manifest = self.get_manifest();
2027 let main_component_type_id = manifest.main_component_type_id.clone();
2028 let main_component = manifest.components.get(&main_component_type_id).unwrap();
2029 let template = main_component.template.as_ref().unwrap();
2030 for node_id in template.get_ids() {
2031 if let Some(found) =
2032 self.recurse_get_template_node_by_id(id, &main_component_type_id, node_id)
2033 {
2034 return Some(self.build_template_node(&found.0, &found.1, None));
2035 }
2036 }
2037 None
2038 }
2039
2040 fn check_for_id_in_template_node(
2041 &self,
2042 id: &str,
2043 tnd: &pax_manifest::TemplateNodeDefinition,
2044 ) -> bool {
2045 if let Some(settings) = &tnd.settings {
2046 for setting in settings {
2047 if let pax_manifest::SettingElement::Setting(token, value) = setting {
2048 if &token.token_value == "id" {
2049 if let pax_manifest::ValueDefinition::Identifier(ident) = value {
2050 if ident.name == id {
2051 return true;
2052 }
2053 }
2054 }
2055 }
2056 }
2057 }
2058 false
2059 }
2060
2061 fn recurse_get_template_node_by_id<'a>(
2062 &'a self,
2063 id: &str,
2064 containing_component_type_id: &'a pax_manifest::TypeId,
2065 node_id: &'a pax_manifest::TemplateNodeId,
2066 ) -> Option<(pax_manifest::TypeId, pax_manifest::TemplateNodeId)> {
2067 let manifest = self.get_manifest();
2068 let containing_component = manifest
2069 .components
2070 .get(containing_component_type_id)
2071 .unwrap();
2072 let containing_template = containing_component.template.as_ref().unwrap();
2073 let tnd = containing_template.get_node(node_id).unwrap();
2074
2075 if self.check_for_id_in_template_node(id, tnd) {
2076 return Some((containing_component_type_id.clone(), node_id.clone()));
2077 }
2078
2079 if let Some(component) = &manifest.components.get(&tnd.type_id) {
2080 if let Some(template) = &component.template {
2081 for node_id in template.get_ids() {
2082 if let Some(found) =
2083 self.recurse_get_template_node_by_id(id, &tnd.type_id, node_id)
2084 {
2085 return Some(found.clone());
2086 }
2087 }
2088 }
2089 }
2090 None
2091 }
2092}
2093
2094fn try_typed_property_binding<T: PropertyValue + CoercionRules>(
2097 name: &str,
2098 definition: &ValueDefinition,
2099 stack: &Rc<RuntimePropertiesStackFrame>,
2100) -> Option<Property<T>> {
2101 use pax_language::interpreter::{PaxExpression, PaxPrimary};
2102 fn identifier(expression: &PaxExpression) -> Option<&str> {
2103 match expression {
2104 PaxExpression::Primary(primary) => match primary.as_ref() {
2105 PaxPrimary::Identifier(id, accessors) if accessors.is_empty() => Some(&id.name),
2106 PaxPrimary::Grouped(expression, None) => identifier(expression),
2107 _ => None,
2108 },
2109 _ => None,
2110 }
2111 }
2112 let symbol = match definition {
2113 ValueDefinition::Identifier(id) => &id.name,
2114 ValueDefinition::Expression(info) => identifier(&info.expression)?,
2115 _ => return None,
2116 };
2117 let variable = stack.resolve_symbol_as_variable(symbol)?;
2118 if let ValueDefinition::Expression(info) = definition {
2119 if info.dependencies.len() != 1
2121 || stack
2122 .resolve_symbol_as_erased_property(&info.dependencies[0])?
2123 .get_id()
2124 != variable.get_untyped_property().get_id()
2125 {
2126 return None;
2127 }
2128 }
2129 variable.try_typed_binding(name)
2130}
2131
2132fn build_common_property_value<T>(
2133 name: &str,
2134 value_definition: &ValueDefinition,
2135 stack: &Rc<RuntimePropertiesStackFrame>,
2136) -> Property<Option<T>>
2137where
2138 T: CoercionRules + PropertyValue + ToPaxValue,
2139{
2140 if let Some(property) = try_typed_property_binding(name, value_definition, stack) {
2141 return property;
2142 }
2143 let cloned_stack = stack.clone();
2144 match value_definition {
2145 pax_manifest::ValueDefinition::LiteralValue(lv) => {
2146 let val =
2147 Option::<T>::try_coerce(resolve_literal_value(lv.clone())).unwrap_or_else(|err| {
2148 log::warn!(
2149 "Failed to coerce new value for property {name}. Error: {:?}",
2150 err
2151 );
2152 Default::default()
2153 });
2154 Property::new_with_name(val, name)
2155 }
2156 pax_manifest::ValueDefinition::Timeline(track) => {
2157 let track = timeline_track_with_base_starting_value(track);
2158 build_timeline_property(name, &track, cloned_stack.clone())
2159 }
2160 pax_manifest::ValueDefinition::Transition(transition) => {
2161 let transition = transition_with_base_starting_value(transition);
2162 build_transition_property(name, &transition, cloned_stack.clone())
2163 }
2164 pax_manifest::ValueDefinition::DoubleBinding(identifier) => {
2165 let untyped_property =
2166 if let Some(p) = stack.resolve_symbol_as_erased_property(&identifier.name) {
2167 p
2168 } else {
2169 log::warn!("Failed to resolve symbol {}", identifier.name);
2170 return Default::default();
2171 };
2172 Property::new_from_untyped(untyped_property.clone())
2173 }
2174 pax_manifest::ValueDefinition::Expression(info) => {
2175 let mut dependents = vec![];
2176 for dependency in &info.dependencies {
2177 if let Some(p) = stack.resolve_symbol_as_erased_property(dependency) {
2178 dependents.push(p);
2179 } else {
2180 log::warn!("Failed to resolve symbol {}", dependency);
2181 }
2182 }
2183 let cloned_ast = info.expression.clone();
2184 let expression_label = cloned_ast.to_string();
2185 let property_name = name.to_string();
2186 Property::computed_with_name(
2187 move || {
2188 let new_value = cloned_ast
2189 .compute(cloned_stack.clone())
2190 .unwrap_or_else(|_| {
2191 log::warn!("Failed to compute expr: {}", expression_label);
2192 Default::default()
2193 });
2194 Option::<T>::try_coerce(new_value).unwrap_or_else(|err| {
2195 log::warn!(
2196 "Failed to coerce new value for property {property_name}. Error: {:?}",
2197 err
2198 );
2199 Default::default()
2200 })
2201 },
2202 &dependents,
2203 name,
2204 )
2205 }
2206 pax_manifest::ValueDefinition::Gradient(_) => {
2207 let mut dependents = Vec::new();
2208 collect_value_definition_dependencies(value_definition, stack, &mut dependents);
2209 let value_definition = value_definition.clone();
2210 let property_name = name.to_string();
2211 Property::computed_with_name(
2212 move || {
2213 evaluate_value_definition_to_pax_value(&value_definition, &cloned_stack)
2214 .and_then(|new_value| {
2215 Option::<T>::try_coerce(new_value)
2216 .map_err(|err| {
2217 log::warn!(
2218 "Failed to coerce new value for property {property_name}. Error: {:?}",
2219 err
2220 );
2221 err
2222 })
2223 .ok()
2224 })
2225 .unwrap_or_default()
2226 },
2227 &dependents,
2228 name,
2229 )
2230 }
2231 pax_manifest::ValueDefinition::Identifier(ident) => {
2232 if let Some(variable) = stack.resolve_symbol_as_variable(&ident.name) {
2233 let property_name = name.to_string();
2234 let untyped = variable.get_untyped_property().clone();
2235 let cloned_variable = variable.clone();
2236 Property::computed_with_name(
2237 move || {
2238 let new_value = cloned_variable.get_as_pax_value();
2239 Option::<T>::try_coerce(new_value).unwrap_or_else(|err| {
2240 log::warn!(
2241 "Failed to coerce new value for property {property_name}. Error: {:?}",
2242 err
2243 );
2244 Default::default()
2245 })
2246 },
2247 &[untyped],
2248 &ident.name,
2249 )
2250 } else {
2251 log::warn!("Failed to resolve symbol {}", ident.name);
2252 Property::default()
2253 }
2254 }
2255 _ => unreachable!("Invalid value definition for {name}"),
2256 }
2257}
2258
2259fn axis_component_value(value: PaxValue, axis_index: usize) -> Result<PaxValue, String> {
2260 match value {
2261 PaxValue::Vec(values) => {
2262 if values.len() != 2 {
2263 return Err(format!(
2264 "expected 2 elements for multi-axis common property, got {}",
2265 values.len()
2266 ));
2267 }
2268 Ok(values[axis_index].clone())
2269 }
2270 PaxValue::Option(value) => match *value {
2271 Some(value) => axis_component_value(value, axis_index),
2272 None => Ok(PaxValue::Option(Box::new(None))),
2273 },
2274 value => Ok(value),
2275 }
2276}
2277
2278fn coerce_axis_component<T>(
2279 value: PaxValue,
2280 axis_index: usize,
2281 name: &str,
2282) -> Result<Option<T>, String>
2283where
2284 T: CoercionRules + PropertyValue,
2285{
2286 let axis_value = axis_component_value(value, axis_index)?;
2287 Option::<T>::try_coerce(axis_value)
2288 .map_err(|err| format!("failed to coerce axis {axis_index} for {name}: {err}"))
2289}
2290
2291fn sample_axis_timeline_track<T: CoercionRules + PropertyValue>(
2292 track: &TimelineTrackDefinition,
2293 stack: &Rc<RuntimePropertiesStackFrame>,
2294 axis_index: usize,
2295 name: &str,
2296 initial_override: Option<&T>,
2297) -> Option<T> {
2298 match resolve_timeline_sample(track, stack, |_| true)? {
2299 TimelineSample::Value { value, initial } => {
2300 if initial {
2301 if let Some(value) = initial_override {
2302 return Some(value.clone());
2303 }
2304 }
2305 coerce_axis_component::<T>(resolve_literal_value(value), axis_index, name)
2306 .ok()
2307 .flatten()
2308 }
2309 TimelineSample::Interpolated {
2310 current,
2311 next,
2312 easing,
2313 progress,
2314 initial,
2315 } => {
2316 let current = if initial {
2317 initial_override.cloned()
2318 } else {
2319 None
2320 }
2321 .or_else(|| {
2322 coerce_axis_component::<T>(resolve_literal_value(current), axis_index, name)
2323 .ok()
2324 .flatten()
2325 })?;
2326 let next = coerce_axis_component::<T>(resolve_literal_value(next), axis_index, name)
2327 .ok()
2328 .flatten()?;
2329 let curve = easing_curve_from_name(easing.as_deref());
2330 Some(curve.interpolate(¤t, &next, progress))
2331 }
2332 }
2333}
2334
2335fn build_axis_timeline_property<T: CoercionRules + PropertyValue>(
2336 name: &str,
2337 track: &TimelineTrackDefinition,
2338 stack: Rc<RuntimePropertiesStackFrame>,
2339 axis_index: usize,
2340) -> Property<Option<T>> {
2341 let mut dependents = Vec::new();
2342 collect_value_definition_dependencies(
2343 &ValueDefinition::Timeline(track.clone()),
2344 &stack,
2345 &mut dependents,
2346 );
2347
2348 let cloned_stack = stack.clone();
2349 let cloned_track = track.clone();
2350 let property_name = name.to_string();
2351 Property::computed_with_name(
2352 move || {
2353 sample_axis_timeline_track(
2354 &cloned_track,
2355 &cloned_stack,
2356 axis_index,
2357 &property_name,
2358 None,
2359 )
2360 },
2361 &dependents,
2362 name,
2363 )
2364}
2365
2366fn coerce_axis_transition_starting_value<T: CoercionRules + PropertyValue>(
2367 transition: &TransitionDefinition,
2368 stack: &Rc<RuntimePropertiesStackFrame>,
2369 axis_index: usize,
2370 name: &str,
2371) -> Option<T> {
2372 transition
2373 .starting_value
2374 .as_ref()
2375 .and_then(|value| evaluate_value_definition_to_pax_value(value, stack))
2376 .and_then(|value| coerce_axis_component::<T>(value, axis_index, name).ok())
2377 .flatten()
2378}
2379
2380fn sample_axis_transition_track<T: CoercionRules + PropertyValue>(
2381 transition: &TransitionDefinition,
2382 track: Option<&TimelineTrackDefinition>,
2383 stack: &Rc<RuntimePropertiesStackFrame>,
2384 axis_index: usize,
2385 name: &str,
2386 initial_override: Option<&T>,
2387) -> Option<T> {
2388 let mut track = track?.clone();
2389 if track.starting_value.is_none() {
2390 track.starting_value = transition.starting_value.clone();
2391 }
2392 align_transition_track_playhead_to_duration(&mut track, stack);
2393 sample_axis_timeline_track(&track, stack, axis_index, name, initial_override)
2394}
2395
2396fn sample_axis_transition_property<T: CoercionRules + PropertyValue>(
2397 transition: &TransitionDefinition,
2398 stack: &Rc<RuntimePropertiesStackFrame>,
2399 axis_index: usize,
2400 name: &str,
2401 initial_override: Option<&T>,
2402) -> Option<T> {
2403 let phase = stack
2404 .resolve_symbol_as_variable(TRANSITION_PHASE_SYMBOL)
2405 .and_then(|variable| Numeric::try_coerce(variable.get_as_pax_value()).ok())
2406 .map(|value| value.to_int() as u64)
2407 .unwrap_or_default();
2408
2409 match phase {
2410 TRANSITION_PHASE_ENTER => sample_axis_transition_track(
2411 transition,
2412 transition.enter.as_ref(),
2413 stack,
2414 axis_index,
2415 name,
2416 initial_override,
2417 )
2418 .or_else(|| coerce_axis_transition_starting_value(transition, stack, axis_index, name)),
2419 TRANSITION_PHASE_EXIT => sample_axis_transition_track(
2420 transition,
2421 transition.exit.as_ref(),
2422 stack,
2423 axis_index,
2424 name,
2425 initial_override,
2426 )
2427 .or_else(|| {
2428 sample_axis_transition_track(
2429 transition,
2430 transition.enter.as_ref(),
2431 stack,
2432 axis_index,
2433 name,
2434 initial_override,
2435 )
2436 })
2437 .or_else(|| coerce_axis_transition_starting_value(transition, stack, axis_index, name)),
2438 _ => coerce_axis_transition_starting_value(transition, stack, axis_index, name),
2439 }
2440}
2441
2442fn build_axis_transition_property<T: CoercionRules + PropertyValue>(
2443 name: &str,
2444 transition: &TransitionDefinition,
2445 stack: Rc<RuntimePropertiesStackFrame>,
2446 axis_index: usize,
2447) -> Property<Option<T>> {
2448 let mut dependents = Vec::new();
2449 collect_value_definition_dependencies(
2450 &ValueDefinition::Transition(transition.clone()),
2451 &stack,
2452 &mut dependents,
2453 );
2454
2455 let cloned_stack = stack.clone();
2456 let cloned_transition = transition.clone();
2457 let property_name = name.to_string();
2458 let takeover_state = RefCell::new(TransitionTakeoverState::<T>::default());
2459 Property::computed_with_name(
2460 move || {
2461 let generation = transition_generation(&cloned_stack);
2462 let mut state = borrow_mut!(takeover_state);
2463 if generation != state.generation {
2464 let should_take_over = transition_takeover(&cloned_stack)
2465 && active_transition_track(&cloned_transition, &cloned_stack)
2466 .map(|track| track.interruption == InOutInterruption::Takeover)
2467 .unwrap_or(false);
2468 state.initial_override = should_take_over
2469 .then(|| state.last_output.clone())
2470 .flatten();
2471 state.generation = generation;
2472 }
2473 let output = sample_axis_transition_property(
2474 &cloned_transition,
2475 &cloned_stack,
2476 axis_index,
2477 &property_name,
2478 state.initial_override.as_ref(),
2479 );
2480 state.last_output = output.clone();
2481 output
2482 },
2483 &dependents,
2484 name,
2485 )
2486}
2487
2488fn build_common_property_axis_component_value<T>(
2489 name: &str,
2490 value_definition: &ValueDefinition,
2491 stack: &Rc<RuntimePropertiesStackFrame>,
2492 axis_index: usize,
2493) -> Property<Option<T>>
2494where
2495 T: CoercionRules + PropertyValue + ToPaxValue,
2496{
2497 let cloned_stack = stack.clone();
2498 match value_definition {
2499 ValueDefinition::Timeline(track) => {
2500 let track = timeline_track_with_base_starting_value(track);
2501 build_axis_timeline_property(name, &track, cloned_stack, axis_index)
2502 }
2503 ValueDefinition::Transition(transition) => {
2504 let transition = transition_with_base_starting_value(transition);
2505 build_axis_transition_property(name, &transition, cloned_stack, axis_index)
2506 }
2507 ValueDefinition::LiteralValue(_)
2508 | ValueDefinition::Block(_)
2509 | ValueDefinition::Gradient(_)
2510 | ValueDefinition::Expression(_)
2511 | ValueDefinition::Identifier(_)
2512 | ValueDefinition::DoubleBinding(_) => {
2513 let mut dependents = Vec::new();
2514 collect_value_definition_dependencies(value_definition, stack, &mut dependents);
2515 let value_definition = value_definition.clone();
2516 let property_name = name.to_string();
2517 Property::computed_with_name(
2518 move || {
2519 evaluate_value_definition_to_pax_value(&value_definition, &cloned_stack)
2520 .and_then(|value| {
2521 coerce_axis_component::<T>(value, axis_index, &property_name)
2522 .map_err(|err| {
2523 log::warn!("Failed to resolve multi-axis property: {err}");
2524 err
2525 })
2526 .ok()
2527 })
2528 .flatten()
2529 },
2530 &dependents,
2531 name,
2532 )
2533 }
2534 _ => unreachable!("Invalid value definition for {name}"),
2535 }
2536}
2537
2538fn resolve_property<T>(
2539 name: &str,
2540 property_columns: &RuntimeResolvedPropertyColumns,
2541 stack: &Rc<RuntimePropertiesStackFrame>,
2542) -> Property<Option<T>>
2543where
2544 T: CoercionRules + PropertyValue + ToPaxValue,
2545{
2546 let Some(entries) = property_columns.get(name) else {
2547 return Property::default();
2548 };
2549 let mut layered_property = Property::default();
2550 for entry in entries {
2551 let previous_property = layered_property.clone();
2552 let source_stack = entry.source_stack.as_ref().unwrap_or(stack);
2553 let stack_with_base = stack_with_optional_base_or(
2554 source_stack,
2555 previous_property.clone(),
2556 common_property_base_fallback(name),
2557 );
2558 let candidate_property = if let Some(axis_index) = entry.axis_index {
2559 build_common_property_axis_component_value(
2560 name,
2561 &entry.value,
2562 &stack_with_base,
2563 axis_index,
2564 )
2565 } else {
2566 build_common_property_value(name, &entry.value, &stack_with_base)
2567 };
2568 layered_property = apply_runtime_settings_condition(
2569 name,
2570 entry.condition.as_ref(),
2571 source_stack,
2572 previous_property,
2573 candidate_property,
2574 );
2575 }
2576 layered_property
2577}
2578
2579fn common_property_base_fallback<T>(name: &str) -> T
2580where
2581 T: CoercionRules + PropertyValue,
2582{
2583 let value = match name {
2587 "x" | "y" | "anchor_x" | "anchor_y" => Some(PaxValue::Size(Size::ZERO())),
2588 "rotate" | "skew_x" | "skew_y" => Some(PaxValue::Rotation(Rotation::ZERO())),
2589 "opacity" => Some(PaxValue::Numeric(1.0.into())),
2590 _ => None,
2591 };
2592
2593 value
2594 .and_then(|value| T::try_coerce(value).ok())
2595 .unwrap_or_default()
2596}
2597
2598fn collect_value_definition_dependencies(
2599 value_definition: &ValueDefinition,
2600 stack: &Rc<RuntimePropertiesStackFrame>,
2601 dependents: &mut Vec<UntypedProperty>,
2602) {
2603 match value_definition {
2604 ValueDefinition::Expression(info) => {
2605 for dependency in &info.dependencies {
2606 if let Some(property) = stack.resolve_symbol_as_erased_property(dependency) {
2607 dependents.push(property);
2608 }
2609 }
2610 }
2611 ValueDefinition::Identifier(identifier) | ValueDefinition::DoubleBinding(identifier) => {
2612 if let Some(property) = stack.resolve_symbol_as_erased_property(&identifier.name) {
2613 dependents.push(property);
2614 }
2615 }
2616 ValueDefinition::Block(block) => {
2617 for element in &block.elements {
2618 if let SettingElement::Setting(_, value_definition) = element {
2619 collect_value_definition_dependencies(value_definition, stack, dependents);
2620 }
2621 }
2622 }
2623 ValueDefinition::Gradient(gradient) => {
2624 match &gradient.shape {
2625 GradientShapeDefinition::Linear { start, end } => {
2626 if let Some(start) = start {
2627 collect_value_definition_dependencies(start, stack, dependents);
2628 }
2629 if let Some(end) = end {
2630 collect_value_definition_dependencies(end, stack, dependents);
2631 }
2632 }
2633 GradientShapeDefinition::Radial { start, end, radius } => {
2634 collect_value_definition_dependencies(start, stack, dependents);
2635 collect_value_definition_dependencies(end, stack, dependents);
2636 collect_value_definition_dependencies(radius, stack, dependents);
2637 }
2638 }
2639 for element in &gradient.elements {
2640 if let GradientElement::Stop(stop) = element {
2641 collect_value_definition_dependencies(&stop.color, stack, dependents);
2642 }
2643 }
2644 }
2645 ValueDefinition::Timeline(track) => {
2646 if let Some(playhead) = &track.playhead {
2647 collect_value_definition_dependencies(playhead, stack, dependents);
2648 } else {
2649 if let Some(property) = stack.resolve_symbol_as_erased_property("$frames") {
2650 dependents.push(property);
2651 }
2652 if let Some(property) = stack.resolve_symbol_as_erased_property("$millis") {
2653 dependents.push(property);
2654 }
2655 }
2656 if let Some(duration) = &track.duration {
2657 collect_value_definition_dependencies(duration, stack, dependents);
2658 }
2659 if let Some(starting_value) = &track.starting_value {
2660 collect_value_definition_dependencies(starting_value, stack, dependents);
2661 }
2662 for element in &track.elements {
2663 if let TimelineTrackElement::Keyframe(keyframe) = element {
2664 collect_value_definition_dependencies(&keyframe.value, stack, dependents);
2665 }
2666 }
2667 }
2668 ValueDefinition::Transition(transition) => {
2669 if let Some(property) = stack.resolve_symbol_as_erased_property(TRANSITION_PHASE_SYMBOL)
2670 {
2671 dependents.push(property);
2672 }
2673 if let Some(property) =
2674 stack.resolve_symbol_as_erased_property(TRANSITION_PLAYHEAD_SYMBOL)
2675 {
2676 dependents.push(property);
2677 }
2678 if let Some(property) =
2679 stack.resolve_symbol_as_erased_property(TRANSITION_PLAYHEAD_MILLIS_SYMBOL)
2680 {
2681 dependents.push(property);
2682 }
2683 if let Some(property) =
2684 stack.resolve_symbol_as_erased_property(TRANSITION_GENERATION_SYMBOL)
2685 {
2686 dependents.push(property);
2687 }
2688 if let Some(property) =
2689 stack.resolve_symbol_as_erased_property(TRANSITION_TAKEOVER_SYMBOL)
2690 {
2691 dependents.push(property);
2692 }
2693 if let Some(starting_value) = &transition.starting_value {
2694 collect_value_definition_dependencies(starting_value, stack, dependents);
2695 }
2696 for track in [&transition.enter, &transition.exit].into_iter().flatten() {
2697 if let Some(playhead) = &track.playhead {
2698 collect_value_definition_dependencies(playhead, stack, dependents);
2699 }
2700 if let Some(duration) = &track.duration {
2701 collect_value_definition_dependencies(duration, stack, dependents);
2702 }
2703 if let Some(starting_value) = &track.starting_value {
2704 collect_value_definition_dependencies(starting_value, stack, dependents);
2705 }
2706 for element in &track.elements {
2707 if let TimelineTrackElement::Keyframe(keyframe) = element {
2708 collect_value_definition_dependencies(&keyframe.value, stack, dependents);
2709 }
2710 }
2711 }
2712 }
2713 ValueDefinition::LiteralValue(_)
2714 | ValueDefinition::EventBindingTarget(_)
2715 | ValueDefinition::Undefined => {}
2716 }
2717}
2718
2719fn evaluate_literal_block_to_pax_value(
2720 block: &LiteralBlockDefinition,
2721 stack: &Rc<RuntimePropertiesStackFrame>,
2722) -> Option<PaxValue> {
2723 let mut values = Vec::new();
2724 for element in &block.elements {
2725 if let SettingElement::Setting(token, value_definition) = element {
2726 let value = evaluate_value_definition_to_pax_value(value_definition, stack)?;
2727 values.push((token.token_value.clone(), value));
2728 }
2729 }
2730 Some(PaxValue::Object(values))
2731}
2732
2733fn gradient_default_start() -> PaxValue {
2734 PaxValue::Vec(vec![
2735 PaxValue::Size(Size::Percent(Numeric::F64(0.0))),
2736 PaxValue::Size(Size::Percent(Numeric::F64(0.0))),
2737 ])
2738}
2739
2740fn gradient_default_end() -> PaxValue {
2741 PaxValue::Vec(vec![
2742 PaxValue::Size(Size::Percent(Numeric::F64(100.0))),
2743 PaxValue::Size(Size::Percent(Numeric::F64(0.0))),
2744 ])
2745}
2746
2747fn evaluate_gradient_to_pax_value(
2748 gradient: &GradientDefinition,
2749 stack: &Rc<RuntimePropertiesStackFrame>,
2750) -> Option<PaxValue> {
2751 let stops = gradient
2752 .elements
2753 .iter()
2754 .filter_map(|element| match element {
2755 GradientElement::Stop(stop) => {
2756 let color = evaluate_value_definition_to_pax_value(&stop.color, stack)?;
2757 Some(PaxValue::Enum(Box::new((
2758 "GradientStop".to_string(),
2759 "get".to_string(),
2760 vec![color, PaxValue::Size(stop.position.clone())],
2761 ))))
2762 }
2763 GradientElement::Comment(_) => None,
2764 })
2765 .collect::<Vec<_>>();
2766
2767 if stops.len() < 2 {
2768 log::warn!("@gradient requires at least two stops");
2769 return None;
2770 }
2771
2772 let stops = PaxValue::Vec(stops);
2773 let (variant, args) = match &gradient.shape {
2774 GradientShapeDefinition::Linear { start, end } => {
2775 let start = start
2776 .as_ref()
2777 .and_then(|value| evaluate_value_definition_to_pax_value(value, stack))
2778 .unwrap_or_else(gradient_default_start);
2779 let end = end
2780 .as_ref()
2781 .and_then(|value| evaluate_value_definition_to_pax_value(value, stack))
2782 .unwrap_or_else(gradient_default_end);
2783 ("linearGradient", vec![start, end, stops])
2784 }
2785 GradientShapeDefinition::Radial { start, end, radius } => {
2786 let start = evaluate_value_definition_to_pax_value(start, stack)?;
2787 let end = evaluate_value_definition_to_pax_value(end, stack)?;
2788 let radius = evaluate_value_definition_to_pax_value(radius, stack)?;
2789 ("radialGradient", vec![start, end, stops, radius])
2790 }
2791 };
2792
2793 Some(PaxValue::Enum(Box::new((
2794 "Fill".to_string(),
2795 variant.to_string(),
2796 args,
2797 ))))
2798}
2799
2800fn resolve_literal_value(value: PaxValue) -> PaxValue {
2801 match value {
2802 PaxValue::Enum(contents) => {
2803 let (scope, name, args) = *contents;
2804 let resolved_args: Vec<_> = args.into_iter().map(resolve_literal_value).collect();
2805 if Functions::has_function(&scope, &name) {
2806 call_function(scope.clone(), name.clone(), resolved_args.clone()).unwrap_or_else(
2807 |err| {
2808 log::warn!(
2809 "Failed to evaluate literal helper {}::{}: {}",
2810 scope,
2811 name,
2812 err
2813 );
2814 PaxValue::Enum(Box::new((scope, name, resolved_args)))
2815 },
2816 )
2817 } else {
2818 PaxValue::Enum(Box::new((scope, name, resolved_args)))
2819 }
2820 }
2821 PaxValue::Vec(values) => {
2822 PaxValue::Vec(values.into_iter().map(resolve_literal_value).collect())
2823 }
2824 PaxValue::Object(values) => PaxValue::Object(
2825 values
2826 .into_iter()
2827 .map(|(key, value)| (key, resolve_literal_value(value)))
2828 .collect(),
2829 ),
2830 PaxValue::Option(value) => PaxValue::Option(Box::new(value.map(resolve_literal_value))),
2831 PaxValue::Range(start, end) => PaxValue::Range(
2832 Box::new(resolve_literal_value(*start)),
2833 Box::new(resolve_literal_value(*end)),
2834 ),
2835 value => value,
2836 }
2837}
2838
2839fn evaluate_value_definition_to_pax_value(
2840 value_definition: &ValueDefinition,
2841 stack: &Rc<RuntimePropertiesStackFrame>,
2842) -> Option<PaxValue> {
2843 match value_definition {
2844 ValueDefinition::LiteralValue(value) => Some(resolve_literal_value(value.clone())),
2845 ValueDefinition::Block(block) => evaluate_literal_block_to_pax_value(block, stack),
2846 ValueDefinition::Gradient(gradient) => evaluate_gradient_to_pax_value(gradient, stack),
2847 ValueDefinition::Expression(info) => info.expression.compute(stack.clone()).ok(),
2848 ValueDefinition::Identifier(identifier) | ValueDefinition::DoubleBinding(identifier) => {
2849 stack
2850 .resolve_symbol_as_variable(&identifier.name)
2851 .map(|variable| variable.get_as_pax_value())
2852 }
2853 ValueDefinition::Timeline(_) | ValueDefinition::Transition(_) => None,
2854 ValueDefinition::EventBindingTarget(_) | ValueDefinition::Undefined => None,
2855 }
2856}
2857
2858fn evaluate_settings_condition_expression(
2859 condition: &ExpressionInfo,
2860 stack: &Rc<RuntimePropertiesStackFrame>,
2861) -> bool {
2862 match condition.expression.compute(stack.clone()) {
2863 Ok(value) => bool::try_coerce(value).unwrap_or_else(|err| {
2864 log::warn!(
2865 "Settings conditional `{}` did not evaluate to bool: {}",
2866 condition,
2867 err
2868 );
2869 false
2870 }),
2871 Err(err) => {
2872 log::warn!(
2873 "Failed to compute settings conditional `{}`: {}",
2874 condition,
2875 err
2876 );
2877 false
2878 }
2879 }
2880}
2881
2882fn settings_condition_is_active(
2883 condition: &RuntimeSettingsCondition,
2884 stack: &Rc<RuntimePropertiesStackFrame>,
2885) -> bool {
2886 condition
2887 .negative
2888 .iter()
2889 .all(|expression| !evaluate_settings_condition_expression(expression, stack))
2890 && condition
2891 .positive
2892 .iter()
2893 .all(|expression| evaluate_settings_condition_expression(expression, stack))
2894}
2895
2896fn collect_settings_condition_dependencies(
2897 condition: &RuntimeSettingsCondition,
2898 stack: &Rc<RuntimePropertiesStackFrame>,
2899 dependents: &mut Vec<UntypedProperty>,
2900) {
2901 for expression in condition.positive.iter().chain(condition.negative.iter()) {
2902 for dependency in &expression.dependencies {
2903 if let Some(property) = stack.resolve_symbol_as_erased_property(dependency) {
2904 dependents.push(property);
2905 }
2906 }
2907 }
2908}
2909
2910pub fn apply_runtime_settings_condition<T>(
2911 name: &str,
2912 condition: Option<&RuntimeSettingsCondition>,
2913 stack: &Rc<RuntimePropertiesStackFrame>,
2914 previous_property: Property<T>,
2915 candidate_property: Property<T>,
2916) -> Property<T>
2917where
2918 T: PropertyValue,
2919{
2920 let Some(condition) = condition.cloned() else {
2921 return candidate_property;
2922 };
2923
2924 let mut dependents = vec![previous_property.untyped(), candidate_property.untyped()];
2925 collect_settings_condition_dependencies(&condition, stack, &mut dependents);
2926 let cloned_stack = stack.clone();
2927 Property::computed_with_name(
2928 move || {
2929 if settings_condition_is_active(&condition, &cloned_stack) {
2930 candidate_property.get()
2931 } else {
2932 previous_property.get()
2933 }
2934 },
2935 &dependents,
2936 name,
2937 )
2938}
2939
2940fn easing_curve_from_name(name: Option<&str>) -> EasingCurve {
2941 match name {
2942 Some("Linear") | None => EasingCurve::Linear,
2943 Some("Hold") => EasingCurve::Hold,
2944 Some("InQuad") => EasingCurve::InQuad,
2945 Some("OutQuad") => EasingCurve::OutQuad,
2946 Some("InOutQuad") => EasingCurve::InOutQuad,
2947 Some("InBack") => EasingCurve::InBack,
2948 Some("OutBack") => EasingCurve::OutBack,
2949 Some("InOutBack") => EasingCurve::InOutBack,
2950 Some(other) => {
2951 log::warn!("Unknown easing curve '{}', defaulting to Linear", other);
2952 EasingCurve::Linear
2953 }
2954 }
2955}
2956
2957#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2958enum TimelineClockUnit {
2959 Frames,
2960 Millis,
2961}
2962
2963#[derive(Clone, Copy, Debug)]
2964struct TimelineScale {
2965 unit: TimelineClockUnit,
2966 total: f64,
2967}
2968
2969fn duration_to_unit(duration: Duration, unit: TimelineClockUnit) -> f64 {
2970 match unit {
2971 TimelineClockUnit::Frames => duration.as_frames_f64(),
2972 TimelineClockUnit::Millis => duration.as_milliseconds_f64(),
2973 }
2974}
2975
2976fn duration_unit(duration: Duration) -> TimelineClockUnit {
2977 if duration.is_frame_based() {
2978 TimelineClockUnit::Frames
2979 } else {
2980 TimelineClockUnit::Millis
2981 }
2982}
2983
2984pub(crate) fn evaluate_timeline_duration(
2985 value_definition: &ValueDefinition,
2986 stack: &Rc<RuntimePropertiesStackFrame>,
2987) -> Option<Duration> {
2988 evaluate_value_definition_to_pax_value(value_definition, stack)
2989 .and_then(|value| Duration::try_coerce(value).ok())
2990}
2991
2992fn timeline_duration(
2993 track: &TimelineTrackDefinition,
2994 stack: &Rc<RuntimePropertiesStackFrame>,
2995) -> Option<Duration> {
2996 track
2997 .duration
2998 .as_ref()
2999 .and_then(|duration| evaluate_timeline_duration(duration, stack))
3000}
3001
3002fn timeline_scale(
3003 track: &TimelineTrackDefinition,
3004 stack: &Rc<RuntimePropertiesStackFrame>,
3005) -> TimelineScale {
3006 if let Some(duration) = timeline_duration(track, stack) {
3007 let unit = duration_unit(duration);
3008 return TimelineScale {
3009 unit,
3010 total: duration_to_unit(duration, unit).max(0.0),
3011 };
3012 }
3013
3014 let mut max_frame: f64 = 0.0;
3015 let mut max_millis: f64 = 0.0;
3016 let mut uses_percent_markers = false;
3017 let mut uses_millis_markers = false;
3018
3019 for keyframe in track.keyframes() {
3020 match keyframe.marker {
3021 TimelineMarker::Frame(frame) => max_frame = max_frame.max(frame as f64),
3022 TimelineMarker::Duration(duration) => {
3023 if duration.is_frame_based() {
3024 max_frame = max_frame.max(duration.as_frames_f64());
3025 } else {
3026 uses_millis_markers = true;
3027 max_millis = max_millis.max(duration.as_milliseconds_f64());
3028 }
3029 }
3030 TimelineMarker::Percent(_) => uses_percent_markers = true,
3031 }
3032 }
3033
3034 if uses_millis_markers {
3035 let total = if uses_percent_markers {
3036 max_millis.max(100.0)
3037 } else {
3038 max_millis
3039 };
3040 TimelineScale {
3041 unit: TimelineClockUnit::Millis,
3042 total,
3043 }
3044 } else {
3045 let total = if uses_percent_markers {
3046 max_frame.max(100.0)
3047 } else {
3048 max_frame
3049 };
3050 TimelineScale {
3051 unit: TimelineClockUnit::Frames,
3052 total,
3053 }
3054 }
3055}
3056
3057fn timeline_marker_to_position(marker: &TimelineMarker, scale: TimelineScale) -> f64 {
3058 match marker {
3059 TimelineMarker::Frame(frame) => {
3060 duration_to_unit(Duration::Frames((*frame).into()), scale.unit)
3061 }
3062 TimelineMarker::Duration(duration) => duration_to_unit(*duration, scale.unit),
3063 TimelineMarker::Percent(percent) => scale.total * (*percent / 100.0),
3064 }
3065}
3066
3067fn timeline_value_passes_coercion<T: CoercionRules>(value: &PaxValue) -> bool {
3068 T::try_coerce(resolve_literal_value(value.clone())).is_ok()
3069}
3070
3071fn evaluate_valid_timeline_value(
3072 value_definition: &ValueDefinition,
3073 stack: &Rc<RuntimePropertiesStackFrame>,
3074 is_valid_value: fn(&PaxValue) -> bool,
3075) -> Option<PaxValue> {
3076 let value = evaluate_value_definition_to_pax_value(value_definition, stack)?;
3077 is_valid_value(&value).then_some(value)
3078}
3079
3080fn loop_target_value(
3081 track: &TimelineTrackDefinition,
3082 first_keyframe: &ResolvedTimelineKeyframe,
3083 stack: &Rc<RuntimePropertiesStackFrame>,
3084 is_valid_value: fn(&PaxValue) -> bool,
3085) -> PaxValue {
3086 if first_keyframe.frame == 0.0 {
3087 first_keyframe.value.clone()
3088 } else {
3089 track
3090 .starting_value
3091 .as_ref()
3092 .and_then(|value| evaluate_valid_timeline_value(value, stack, is_valid_value))
3093 .unwrap_or_else(|| first_keyframe.value.clone())
3094 }
3095}
3096
3097struct ResolvedTimelineKeyframe {
3098 frame: f64,
3099 source_order: usize,
3100 value: PaxValue,
3101 easing: Option<String>,
3102}
3103
3104enum TimelineSample {
3105 Value {
3106 value: PaxValue,
3107 initial: bool,
3108 },
3109 Interpolated {
3110 current: PaxValue,
3111 next: PaxValue,
3112 easing: Option<String>,
3113 progress: f64,
3114 initial: bool,
3115 },
3116}
3117
3118fn sample_timeline_playhead(
3119 track: &TimelineTrackDefinition,
3120 stack: &Rc<RuntimePropertiesStackFrame>,
3121 scale: TimelineScale,
3122) -> f64 {
3123 let raw_playhead = track
3124 .playhead
3125 .as_ref()
3126 .and_then(|playhead| evaluate_value_definition_to_pax_value(playhead, stack))
3127 .and_then(|value| timeline_playhead_value_to_position(value, scale.unit))
3128 .or_else(|| {
3129 let symbol = match scale.unit {
3130 TimelineClockUnit::Frames => "$frames",
3131 TimelineClockUnit::Millis => "$millis",
3132 };
3133 stack
3134 .resolve_symbol_as_variable(symbol)
3135 .and_then(|variable| {
3136 Numeric::try_coerce(variable.get_as_pax_value())
3137 .ok()
3138 .map(|value| value.to_float())
3139 })
3140 })
3141 .unwrap_or_default();
3142
3143 let repeat = track.repeat.unwrap_or(true);
3144 if !repeat {
3145 return raw_playhead.clamp(0.0, scale.total.max(0.0));
3146 }
3147
3148 let cycle_len = match scale.unit {
3149 TimelineClockUnit::Frames => (scale.total + 1.0).max(1.0),
3150 TimelineClockUnit::Millis => scale.total.max(1.0),
3151 };
3152 raw_playhead.rem_euclid(cycle_len)
3153}
3154
3155fn timeline_playhead_value_to_position(value: PaxValue, unit: TimelineClockUnit) -> Option<f64> {
3156 if let PaxValue::Duration(duration) = value {
3157 return Some(duration_to_unit(duration, unit));
3158 }
3159 Numeric::try_coerce(value)
3160 .ok()
3161 .map(|value| value.to_float())
3162}
3163
3164fn resolve_timeline_sample(
3165 track: &TimelineTrackDefinition,
3166 stack: &Rc<RuntimePropertiesStackFrame>,
3167 is_valid_value: fn(&PaxValue) -> bool,
3168) -> Option<TimelineSample> {
3169 let scale = timeline_scale(track, stack);
3170 let repeat = track.repeat.unwrap_or(true);
3171 let sample_frame = sample_timeline_playhead(track, stack, scale);
3172
3173 let mut resolved_keyframes: Vec<ResolvedTimelineKeyframe> = track
3174 .keyframes()
3175 .enumerate()
3176 .filter_map(|(source_order, keyframe): (usize, &TimelineKeyframe)| {
3177 let value = evaluate_valid_timeline_value(&keyframe.value, stack, is_valid_value)?;
3178 Some(ResolvedTimelineKeyframe {
3179 frame: timeline_marker_to_position(&keyframe.marker, scale),
3180 source_order,
3181 value,
3182 easing: keyframe
3183 .easing
3184 .as_ref()
3185 .map(|token| token.token_value.clone()),
3186 })
3187 })
3188 .collect();
3189
3190 resolved_keyframes.sort_unstable_by(|lhs, rhs| {
3191 lhs.frame
3192 .partial_cmp(&rhs.frame)
3193 .unwrap_or(std::cmp::Ordering::Equal)
3194 .then_with(|| lhs.source_order.cmp(&rhs.source_order))
3195 });
3196
3197 let first_keyframe = resolved_keyframes.first()?;
3198
3199 if sample_frame < first_keyframe.frame {
3200 return Some(TimelineSample::Value {
3201 value: track
3202 .starting_value
3203 .as_ref()
3204 .and_then(|value| evaluate_valid_timeline_value(value, stack, is_valid_value))
3205 .unwrap_or_else(|| first_keyframe.value.clone()),
3206 initial: true,
3207 });
3208 }
3209
3210 for (segment_index, keyframes) in resolved_keyframes.windows(2).enumerate() {
3211 let current = &keyframes[0];
3212 let next = &keyframes[1];
3213 if sample_frame <= next.frame {
3214 if sample_frame <= current.frame {
3215 return Some(TimelineSample::Value {
3216 value: current.value.clone(),
3217 initial: segment_index == 0,
3218 });
3219 }
3220 let span = next.frame - current.frame;
3221 if span <= f64::EPSILON {
3222 return Some(TimelineSample::Value {
3223 value: next.value.clone(),
3224 initial: false,
3225 });
3226 }
3227 let progress = (sample_frame - current.frame) / span;
3228 return Some(TimelineSample::Interpolated {
3229 current: current.value.clone(),
3230 next: next.value.clone(),
3231 easing: current.easing.clone(),
3232 progress,
3233 initial: segment_index == 0,
3234 });
3235 }
3236 }
3237
3238 let last_keyframe = resolved_keyframes.last()?;
3239 if sample_frame <= last_keyframe.frame || !repeat || scale.total <= last_keyframe.frame {
3240 return Some(TimelineSample::Value {
3241 value: last_keyframe.value.clone(),
3242 initial: resolved_keyframes.len() == 1 && sample_frame <= first_keyframe.frame,
3243 });
3244 }
3245
3246 let loop_target = loop_target_value(track, first_keyframe, stack, is_valid_value);
3247 let span = scale.total - last_keyframe.frame;
3248 if span <= f64::EPSILON {
3249 return Some(TimelineSample::Value {
3250 value: loop_target,
3251 initial: false,
3252 });
3253 }
3254 let progress = (sample_frame - last_keyframe.frame) / span;
3255 Some(TimelineSample::Interpolated {
3256 current: last_keyframe.value.clone(),
3257 next: loop_target,
3258 easing: last_keyframe.easing.clone(),
3259 progress,
3260 initial: false,
3261 })
3262}
3263
3264fn sample_timeline_track<T: CoercionRules + PropertyValue>(
3265 track: &TimelineTrackDefinition,
3266 stack: &Rc<RuntimePropertiesStackFrame>,
3267 initial_override: Option<&T>,
3268) -> Option<T> {
3269 match resolve_timeline_sample(track, stack, timeline_value_passes_coercion::<T>)? {
3270 TimelineSample::Value { value, initial } => {
3271 if initial {
3272 if let Some(value) = initial_override {
3273 return Some(value.clone());
3274 }
3275 }
3276 T::try_coerce(resolve_literal_value(value)).ok()
3277 }
3278 TimelineSample::Interpolated {
3279 current,
3280 next,
3281 easing,
3282 progress,
3283 initial,
3284 } => {
3285 let current = if initial {
3286 initial_override.cloned()
3287 } else {
3288 None
3289 }
3290 .or_else(|| T::try_coerce(resolve_literal_value(current)).ok())?;
3291 let next = T::try_coerce(resolve_literal_value(next)).ok()?;
3292 let curve = easing_curve_from_name(easing.as_deref());
3293 Some(curve.interpolate(¤t, &next, progress))
3294 }
3295 }
3296}
3297
3298pub fn build_timeline_property<T: CoercionRules + PropertyValue>(
3299 name: &str,
3300 track: &TimelineTrackDefinition,
3301 stack: Rc<RuntimePropertiesStackFrame>,
3302) -> Property<T> {
3303 let mut dependents = Vec::new();
3304 if let Some(playhead) = &track.playhead {
3305 collect_value_definition_dependencies(playhead, &stack, &mut dependents);
3306 } else {
3307 if let Some(property) = stack.resolve_symbol_as_erased_property("$frames") {
3308 dependents.push(property);
3309 }
3310 if let Some(property) = stack.resolve_symbol_as_erased_property("$millis") {
3311 dependents.push(property);
3312 }
3313 }
3314 if let Some(duration) = &track.duration {
3315 collect_value_definition_dependencies(duration, &stack, &mut dependents);
3316 }
3317 if let Some(starting_value) = &track.starting_value {
3318 collect_value_definition_dependencies(starting_value, &stack, &mut dependents);
3319 }
3320 for element in &track.elements {
3321 if let TimelineTrackElement::Keyframe(keyframe) = element {
3322 collect_value_definition_dependencies(&keyframe.value, &stack, &mut dependents);
3323 }
3324 }
3325
3326 let cloned_stack = stack.clone();
3327 let cloned_track = track.clone();
3328 Property::computed_with_name(
3329 move || sample_timeline_track(&cloned_track, &cloned_stack, None).unwrap_or_default(),
3330 &dependents,
3331 name,
3332 )
3333}
3334
3335fn coerce_transition_starting_value<T: CoercionRules + PropertyValue>(
3336 transition: &TransitionDefinition,
3337 stack: &Rc<RuntimePropertiesStackFrame>,
3338) -> Option<T> {
3339 transition
3340 .starting_value
3341 .as_ref()
3342 .and_then(|value| evaluate_value_definition_to_pax_value(value, stack))
3343 .and_then(|value| T::try_coerce(value).ok())
3344}
3345
3346fn sample_transition_track<T: CoercionRules + PropertyValue>(
3347 transition: &TransitionDefinition,
3348 track: Option<&TimelineTrackDefinition>,
3349 stack: &Rc<RuntimePropertiesStackFrame>,
3350 initial_override: Option<&T>,
3351) -> Option<T> {
3352 let mut track = track?.clone();
3353 if track.starting_value.is_none() {
3354 track.starting_value = transition.starting_value.clone();
3355 }
3356 align_transition_track_playhead_to_duration(&mut track, stack);
3357 sample_timeline_track(&track, stack, initial_override)
3358}
3359
3360fn align_transition_track_playhead_to_duration(
3361 track: &mut TimelineTrackDefinition,
3362 stack: &Rc<RuntimePropertiesStackFrame>,
3363) {
3364 let Some(ValueDefinition::Identifier(identifier)) = track.playhead.as_deref() else {
3365 return;
3366 };
3367 if identifier.name != TRANSITION_PLAYHEAD_SYMBOL
3368 && identifier.name != TRANSITION_PLAYHEAD_MILLIS_SYMBOL
3369 {
3370 return;
3371 }
3372
3373 let Some(duration) = timeline_duration(track, stack) else {
3374 return;
3375 };
3376 let playhead_symbol = if duration.is_frame_based() {
3377 TRANSITION_PLAYHEAD_SYMBOL
3378 } else {
3379 TRANSITION_PLAYHEAD_MILLIS_SYMBOL
3380 };
3381 if identifier.name != playhead_symbol {
3382 track.playhead = Some(Box::new(ValueDefinition::Identifier(PaxIdentifier::new(
3383 playhead_symbol,
3384 ))));
3385 }
3386}
3387
3388fn sample_transition_property<T: CoercionRules + PropertyValue>(
3389 transition: &TransitionDefinition,
3390 stack: &Rc<RuntimePropertiesStackFrame>,
3391 initial_override: Option<&T>,
3392) -> Option<T> {
3393 let phase = stack
3394 .resolve_symbol_as_variable(TRANSITION_PHASE_SYMBOL)
3395 .and_then(|variable| Numeric::try_coerce(variable.get_as_pax_value()).ok())
3396 .map(|value| value.to_int() as u64)
3397 .unwrap_or_default();
3398
3399 match phase {
3400 TRANSITION_PHASE_ENTER => sample_transition_track(
3401 transition,
3402 transition.enter.as_ref(),
3403 stack,
3404 initial_override,
3405 )
3406 .or_else(|| coerce_transition_starting_value(transition, stack)),
3407 TRANSITION_PHASE_EXIT => sample_transition_track(
3408 transition,
3409 transition.exit.as_ref(),
3410 stack,
3411 initial_override,
3412 )
3413 .or_else(|| {
3414 sample_transition_track(
3415 transition,
3416 transition.enter.as_ref(),
3417 stack,
3418 initial_override,
3419 )
3420 })
3421 .or_else(|| coerce_transition_starting_value(transition, stack)),
3422 _ => coerce_transition_starting_value(transition, stack),
3423 }
3424}
3425
3426#[derive(Default)]
3427struct TransitionTakeoverState<T> {
3428 generation: u64,
3429 last_output: Option<T>,
3430 initial_override: Option<T>,
3431}
3432
3433fn transition_generation(stack: &Rc<RuntimePropertiesStackFrame>) -> u64 {
3434 stack
3435 .resolve_symbol_as_variable(TRANSITION_GENERATION_SYMBOL)
3436 .and_then(|variable| Numeric::try_coerce(variable.get_as_pax_value()).ok())
3437 .map(|value| value.to_int() as u64)
3438 .unwrap_or_default()
3439}
3440
3441fn transition_takeover(stack: &Rc<RuntimePropertiesStackFrame>) -> bool {
3442 stack
3443 .resolve_symbol_as_variable(TRANSITION_TAKEOVER_SYMBOL)
3444 .and_then(|variable| bool::try_coerce(variable.get_as_pax_value()).ok())
3445 .unwrap_or(false)
3446}
3447
3448fn active_transition_track<'a>(
3449 transition: &'a TransitionDefinition,
3450 stack: &Rc<RuntimePropertiesStackFrame>,
3451) -> Option<&'a TimelineTrackDefinition> {
3452 let phase = stack
3453 .resolve_symbol_as_variable(TRANSITION_PHASE_SYMBOL)
3454 .and_then(|variable| Numeric::try_coerce(variable.get_as_pax_value()).ok())
3455 .map(|value| value.to_int() as u64)
3456 .unwrap_or_default();
3457 match phase {
3458 TRANSITION_PHASE_ENTER => transition.enter.as_ref(),
3459 TRANSITION_PHASE_EXIT => transition.exit.as_ref().or(transition.enter.as_ref()),
3460 _ => None,
3461 }
3462}
3463
3464pub fn build_transition_property<T: CoercionRules + PropertyValue>(
3465 name: &str,
3466 transition: &TransitionDefinition,
3467 stack: Rc<RuntimePropertiesStackFrame>,
3468) -> Property<T> {
3469 let mut dependents = Vec::new();
3470 collect_value_definition_dependencies(
3471 &ValueDefinition::Transition(transition.clone()),
3472 &stack,
3473 &mut dependents,
3474 );
3475
3476 let cloned_stack = stack.clone();
3477 let cloned_transition = transition.clone();
3478 let takeover_state = RefCell::new(TransitionTakeoverState::<T>::default());
3479 Property::computed_with_name(
3480 move || {
3481 let generation = transition_generation(&cloned_stack);
3482 let mut state = borrow_mut!(takeover_state);
3483 if generation != state.generation {
3484 let should_take_over = transition_takeover(&cloned_stack)
3485 && active_transition_track(&cloned_transition, &cloned_stack)
3486 .map(|track| track.interruption == InOutInterruption::Takeover)
3487 .unwrap_or(false);
3488 state.initial_override = should_take_over
3489 .then(|| state.last_output.clone())
3490 .flatten();
3491 state.generation = generation;
3492 }
3493 let output = sample_transition_property(
3494 &cloned_transition,
3495 &cloned_stack,
3496 state.initial_override.as_ref(),
3497 )
3498 .unwrap_or_default();
3499 state.last_output = Some(output.clone());
3500 output
3501 },
3502 &dependents,
3503 name,
3504 )
3505}
3506
3507pub fn stack_with_base<T>(
3510 stack: &Rc<RuntimePropertiesStackFrame>,
3511 base_property: Property<T>,
3512) -> Rc<RuntimePropertiesStackFrame>
3513where
3514 T: PropertyValue + ToPaxValue,
3515{
3516 stack.push(HashMap::from([(
3517 BASE_SYMBOL.to_string(),
3518 Variable::new_from_typed_property(base_property),
3519 )]))
3520}
3521
3522pub fn stack_with_optional_base<T>(
3525 stack: &Rc<RuntimePropertiesStackFrame>,
3526 base_property: Property<Option<T>>,
3527) -> Rc<RuntimePropertiesStackFrame>
3528where
3529 T: PropertyValue + ToPaxValue,
3530{
3531 stack_with_optional_base_or(stack, base_property, T::default())
3532}
3533
3534fn stack_with_optional_base_or<T>(
3535 stack: &Rc<RuntimePropertiesStackFrame>,
3536 base_property: Property<Option<T>>,
3537 fallback: T,
3538) -> Rc<RuntimePropertiesStackFrame>
3539where
3540 T: PropertyValue + ToPaxValue,
3541{
3542 let dependency = base_property.untyped();
3543 let cloned_base = base_property.clone();
3544 let resolved_base = Property::computed_with_name(
3545 move || cloned_base.get().unwrap_or_else(|| fallback.clone()),
3546 &[dependency],
3547 BASE_SYMBOL,
3548 );
3549 stack_with_base(stack, resolved_base)
3550}
3551
3552fn base_identifier_value() -> Box<ValueDefinition> {
3553 Box::new(ValueDefinition::Identifier(PaxIdentifier::new(BASE_SYMBOL)))
3554}
3555
3556fn timeline_track_with_base_starting_value(
3557 track: &TimelineTrackDefinition,
3558) -> TimelineTrackDefinition {
3559 let mut track = track.clone();
3560 if track.starting_value.is_none() {
3561 track.starting_value = Some(base_identifier_value());
3562 }
3563 track
3564}
3565
3566fn transition_with_base_starting_value(transition: &TransitionDefinition) -> TransitionDefinition {
3567 let mut transition = transition.clone();
3568 if transition.starting_value.is_none() {
3569 transition.starting_value = Some(base_identifier_value());
3570 }
3571 if let Some(track) = transition.enter.as_mut() {
3572 if track.starting_value.is_none() {
3573 track.starting_value = Some(base_identifier_value());
3574 }
3575 }
3576 if let Some(track) = transition.exit.as_mut() {
3577 if track.starting_value.is_none() {
3578 track.starting_value = Some(base_identifier_value());
3579 }
3580 }
3581 transition
3582}
3583
3584pub fn build_component_property<T>(
3587 name: &str,
3588 value_definition: &ValueDefinition,
3589 stack: &Rc<RuntimePropertiesStackFrame>,
3590 timeline_stack: Rc<RuntimePropertiesStackFrame>,
3591 build_block: fn(&LiteralBlockDefinition, Rc<RuntimePropertiesStackFrame>) -> Property<T>,
3592) -> Property<T>
3593where
3594 T: CoercionRules + PropertyValue + ToPaxValue,
3595{
3596 if let Some(property) = try_typed_property_binding(name, value_definition, stack) {
3597 return property;
3598 }
3599 match value_definition {
3600 ValueDefinition::LiteralValue(lv) => {
3601 let value = T::try_coerce(resolve_literal_value(lv.clone())).unwrap_or_else(|err| {
3602 log::warn!(
3603 "Failed to coerce new value for property {name}. Error: {:?}",
3604 err
3605 );
3606 Default::default()
3607 });
3608 Property::new_with_name(value, name)
3609 }
3610 ValueDefinition::DoubleBinding(identifier) => {
3611 if let Some(untyped_property) =
3612 stack.resolve_symbol_as_erased_property(&identifier.name)
3613 {
3614 Property::new_from_untyped(untyped_property.clone())
3615 } else {
3616 log::warn!("Failed to resolve identifier: {}", &identifier.name);
3617 Property::new_with_name(Default::default(), name)
3618 }
3619 }
3620 ValueDefinition::Identifier(ident) => {
3621 if let Some(variable) = stack.resolve_symbol_as_variable(&ident.name) {
3622 let identifier_name = ident.name.clone();
3623 let property_name = name.to_string();
3624 let untyped = variable.get_untyped_property().clone();
3625 let cloned_variable = variable.clone();
3626 Property::computed_with_name(
3627 move || {
3628 let new_value = cloned_variable.get_as_pax_value();
3629 T::try_coerce(new_value).unwrap_or_else(|err| {
3630 log::warn!(
3631 "Failed to coerce new value for property {property_name}. Error: {:?}",
3632 err
3633 );
3634 Default::default()
3635 })
3636 },
3637 &[untyped],
3638 &identifier_name,
3639 )
3640 } else {
3641 log::warn!("Failed to resolve symbol {}", ident.name);
3642 Property::new_with_name(Default::default(), name)
3643 }
3644 }
3645 ValueDefinition::Expression(info) => {
3646 let mut dependents = vec![];
3647 for dependency in &info.dependencies {
3648 if let Some(p) = stack.resolve_symbol_as_erased_property(dependency) {
3649 dependents.push(p);
3650 } else {
3651 log::warn!("Failed to resolve symbol {}", dependency);
3652 }
3653 }
3654 let cloned_stack = stack.clone();
3655 let cloned_ast = info.expression.clone();
3656 let expression_label = cloned_ast.to_string();
3657 let property_name = name.to_string();
3658 Property::computed_with_name(
3659 move || {
3660 let new_value = cloned_ast
3661 .compute(cloned_stack.clone())
3662 .unwrap_or_else(|_| {
3663 log::warn!("Failed to compute expr: {}", expression_label);
3664 Default::default()
3665 });
3666 T::try_coerce(new_value).unwrap_or_else(|err| {
3667 log::warn!(
3668 "Failed to coerce new value for property {property_name}. Error: {:?}",
3669 err
3670 );
3671 Default::default()
3672 })
3673 },
3674 &dependents,
3675 name,
3676 )
3677 }
3678 ValueDefinition::Gradient(_) => {
3679 let mut dependents = Vec::new();
3680 collect_value_definition_dependencies(value_definition, stack, &mut dependents);
3681 let value_definition = value_definition.clone();
3682 let cloned_stack = stack.clone();
3683 let property_name = name.to_string();
3684 Property::computed_with_name(
3685 move || {
3686 evaluate_value_definition_to_pax_value(&value_definition, &cloned_stack)
3687 .and_then(|new_value| {
3688 T::try_coerce(new_value)
3689 .map_err(|err| {
3690 log::warn!(
3691 "Failed to coerce new value for property {property_name}. Error: {:?}",
3692 err
3693 );
3694 err
3695 })
3696 .ok()
3697 })
3698 .unwrap_or_default()
3699 },
3700 &dependents,
3701 name,
3702 )
3703 }
3704 ValueDefinition::Timeline(track) => {
3705 let track = timeline_track_with_base_starting_value(track);
3706 build_timeline_property(name, &track, timeline_stack)
3707 }
3708 ValueDefinition::Transition(transition) => {
3709 let transition = transition_with_base_starting_value(transition);
3710 build_transition_property(name, &transition, timeline_stack)
3711 }
3712 ValueDefinition::Block(block) => build_block(block, stack.clone()),
3713 _ => unreachable!("Invalid value definition for {name}"),
3714 }
3715}
3716
3717pub fn apply_component_property<T>(
3720 property: &mut Property<T>,
3721 name: &str,
3722 value_definition: &ValueDefinition,
3723 stack: &Rc<RuntimePropertiesStackFrame>,
3724 timeline_stack: Rc<RuntimePropertiesStackFrame>,
3725 build_block: fn(&LiteralBlockDefinition, Rc<RuntimePropertiesStackFrame>) -> Property<T>,
3726) where
3727 T: CoercionRules + PropertyValue + ToPaxValue,
3728{
3729 let resolved_property =
3730 build_component_property(name, value_definition, stack, timeline_stack, build_block);
3731 if matches!(value_definition, ValueDefinition::DoubleBinding(_)) {
3732 *property = resolved_property;
3733 } else {
3734 property.replace_with(resolved_property);
3735 }
3736}
3737
3738#[cfg(test)]
3739mod timeline_tests {
3740 use super::build_timeline_property;
3741 use super::build_transition_property;
3742 use super::evaluate_value_definition_to_pax_value;
3743 use super::BASE_SYMBOL;
3744 use crate::RuntimePropertiesStackFrame;
3745 use pax_language::parse_pax_expression;
3746 use pax_manifest::cartridge_generation::{
3747 TRANSITION_GENERATION_SYMBOL, TRANSITION_PHASE_ENTER, TRANSITION_PHASE_EXIT,
3748 TRANSITION_PHASE_SYMBOL, TRANSITION_PLAYHEAD_MILLIS_SYMBOL, TRANSITION_PLAYHEAD_SYMBOL,
3749 TRANSITION_TAKEOVER_SYMBOL,
3750 };
3751 use pax_manifest::{
3752 ExpressionInfo, GradientDefinition, GradientElement, GradientShapeDefinition,
3753 GradientStopDefinition, InOutInterruption, PaxIdentifier, TimelineKeyframe, TimelineMarker,
3754 TimelineTrackDefinition, TimelineTrackElement, Token, TransitionDefinition,
3755 ValueDefinition,
3756 };
3757 use pax_runtime_api::pax_value::CoercionRules;
3758 use pax_runtime_api::{Color, Duration, Fill, Numeric, PaxValue, Property, Size, Variable};
3759 use std::collections::HashMap;
3760 use std::rc::Rc;
3761 use std::sync::Arc;
3762
3763 fn build_stack_with_clocks(
3764 elapsed_frames: &Property<u64>,
3765 elapsed_millis: &Property<u64>,
3766 ) -> Rc<RuntimePropertiesStackFrame> {
3767 let scope: HashMap<String, Variable> = vec![
3768 (
3769 "$frames".to_string(),
3770 Variable::new_from_typed_property(elapsed_frames.clone()),
3771 ),
3772 (
3773 "$millis".to_string(),
3774 Variable::new_from_typed_property(elapsed_millis.clone()),
3775 ),
3776 ]
3777 .into_iter()
3778 .collect();
3779 RuntimePropertiesStackFrame::new(scope)
3780 }
3781
3782 fn build_stack(elapsed_frames: &Property<u64>) -> Rc<RuntimePropertiesStackFrame> {
3783 let elapsed_millis = Property::new(0_u64);
3784 build_stack_with_clocks(elapsed_frames, &elapsed_millis)
3785 }
3786
3787 fn expression(raw: &str) -> ValueDefinition {
3788 pax_runtime_api::pax_value::functions::Functions::register_all_functions();
3789 ValueDefinition::Expression(ExpressionInfo::new(parse_pax_expression(raw).unwrap()))
3790 }
3791
3792 #[test]
3793 fn literal_enum_helpers_evaluate_before_coercion() {
3794 pax_runtime_api::pax_value::functions::register_function(
3795 "LiteralHelper".to_string(),
3796 "make_object".to_string(),
3797 Arc::new(|mut args| {
3798 Ok(PaxValue::Object(vec![(
3799 "value".to_string(),
3800 args.remove(0),
3801 )]))
3802 }),
3803 );
3804 let elapsed_frames = Property::new(0_u64);
3805 let stack = build_stack(&elapsed_frames);
3806 let literal = ValueDefinition::LiteralValue(PaxValue::Enum(Box::new((
3807 "LiteralHelper".to_string(),
3808 "make_object".to_string(),
3809 vec![PaxValue::Numeric(7.0.into())],
3810 ))));
3811
3812 assert_eq!(
3813 evaluate_value_definition_to_pax_value(&literal, &stack),
3814 Some(PaxValue::Object(vec![(
3815 "value".to_string(),
3816 PaxValue::Numeric(7.0.into()),
3817 )]))
3818 );
3819 }
3820
3821 #[test]
3822 fn gradient_values_evaluate_to_default_linear_fill() {
3823 let elapsed_frames = Property::new(0_u64);
3824 let stack = build_stack(&elapsed_frames);
3825 let gradient = ValueDefinition::Gradient(GradientDefinition {
3826 shape: GradientShapeDefinition::default(),
3827 elements: vec![
3828 GradientElement::Stop(GradientStopDefinition {
3829 position: Size::Percent(Numeric::F64(0.0)),
3830 color: ValueDefinition::LiteralValue(PaxValue::Color(Box::new(Color::RED))),
3831 }),
3832 GradientElement::Stop(GradientStopDefinition {
3833 position: Size::Percent(Numeric::F64(100.0)),
3834 color: ValueDefinition::LiteralValue(PaxValue::Color(Box::new(Color::BLUE))),
3835 }),
3836 ],
3837 });
3838
3839 let value = evaluate_value_definition_to_pax_value(&gradient, &stack)
3840 .expect("gradient should evaluate");
3841 let fill = Fill::try_coerce(value).expect("gradient should coerce to Fill");
3842 let Fill::LinearGradient(linear) = fill else {
3843 panic!("expected linear gradient fill");
3844 };
3845
3846 assert!((linear.start.0.expect_percent() - 0.0).abs() < 0.0001);
3847 assert!((linear.start.1.expect_percent() - 0.0).abs() < 0.0001);
3848 assert!((linear.end.0.expect_percent() - 1.0).abs() < 0.0001);
3849 assert!((linear.end.1.expect_percent() - 0.0).abs() < 0.0001);
3850 assert_eq!(linear.stops.len(), 2);
3851 assert_eq!(linear.stops[0].color, Color::RED);
3852 assert_eq!(linear.stops[1].color, Color::BLUE);
3853 }
3854
3855 #[test]
3856 fn timeline_property_loops_over_declared_frame_range() {
3857 let elapsed_frames = Property::new(0_u64);
3858 let stack = build_stack(&elapsed_frames);
3859 let track = TimelineTrackDefinition {
3860 elements: vec![
3861 TimelineTrackElement::Keyframe(TimelineKeyframe {
3862 marker: TimelineMarker::Frame(0),
3863 value: ValueDefinition::LiteralValue(PaxValue::Numeric(0.0.into())),
3864 easing: Some(Token::new_without_location("Linear".to_string())),
3865 }),
3866 TimelineTrackElement::Keyframe(TimelineKeyframe {
3867 marker: TimelineMarker::Frame(100),
3868 value: ValueDefinition::LiteralValue(PaxValue::Numeric(100.0.into())),
3869 easing: None,
3870 }),
3871 ],
3872 playhead: None,
3873 duration: None,
3874 repeat: Some(true),
3875 starting_value: None,
3876 interruption: Default::default(),
3877 use_local_property_scope: false,
3878 };
3879 let property = build_timeline_property::<f64>("progress", &track, stack);
3880
3881 assert_eq!(property.get(), 0.0);
3882 elapsed_frames.set(50);
3883 assert_eq!(property.get(), 50.0);
3884 elapsed_frames.set(100);
3885 assert_eq!(property.get(), 100.0);
3886 elapsed_frames.set(101);
3887 assert_eq!(property.get(), 0.0);
3888 }
3889
3890 #[test]
3891 fn timeline_property_samples_millisecond_duration_from_elapsed_millis() {
3892 let elapsed_frames = Property::new(0_u64);
3893 let elapsed_millis = Property::new(0_u64);
3894 let stack = build_stack_with_clocks(&elapsed_frames, &elapsed_millis);
3895 let track = TimelineTrackDefinition {
3896 elements: vec![
3897 TimelineTrackElement::Keyframe(TimelineKeyframe {
3898 marker: TimelineMarker::Percent(0.0),
3899 value: ValueDefinition::LiteralValue(PaxValue::Numeric(0.0.into())),
3900 easing: Some(Token::new_without_location("Linear".to_string())),
3901 }),
3902 TimelineTrackElement::Keyframe(TimelineKeyframe {
3903 marker: TimelineMarker::Percent(100.0),
3904 value: ValueDefinition::LiteralValue(PaxValue::Numeric(100.0.into())),
3905 easing: None,
3906 }),
3907 ],
3908 playhead: None,
3909 duration: Some(Box::new(ValueDefinition::LiteralValue(PaxValue::Duration(
3910 pax_runtime_api::Duration::Milliseconds(1000.into()),
3911 )))),
3912 repeat: Some(false),
3913 starting_value: None,
3914 interruption: Default::default(),
3915 use_local_property_scope: false,
3916 };
3917 let property = build_timeline_property::<f64>("progress", &track, stack);
3918
3919 assert_eq!(property.get(), 0.0);
3920 elapsed_frames.set(30);
3921 assert_eq!(property.get(), 0.0);
3922 elapsed_millis.set(500);
3923 assert_eq!(property.get(), 50.0);
3924 elapsed_millis.set(1000);
3925 assert_eq!(property.get(), 100.0);
3926 }
3927
3928 #[test]
3929 fn timeline_property_holds_starting_value_before_first_keyframe() {
3930 let elapsed_frames = Property::new(0_u64);
3931 let stack = build_stack(&elapsed_frames);
3932 let track = TimelineTrackDefinition {
3933 elements: vec![
3934 TimelineTrackElement::Keyframe(TimelineKeyframe {
3935 marker: TimelineMarker::Frame(50),
3936 value: ValueDefinition::LiteralValue(PaxValue::Numeric(10.0.into())),
3937 easing: Some(Token::new_without_location("Linear".to_string())),
3938 }),
3939 TimelineTrackElement::Keyframe(TimelineKeyframe {
3940 marker: TimelineMarker::Frame(100),
3941 value: ValueDefinition::LiteralValue(PaxValue::Numeric(0.0.into())),
3942 easing: None,
3943 }),
3944 ],
3945 playhead: None,
3946 duration: None,
3947 repeat: Some(false),
3948 starting_value: Some(Box::new(ValueDefinition::LiteralValue(PaxValue::Numeric(
3949 5.0.into(),
3950 )))),
3951 interruption: Default::default(),
3952 use_local_property_scope: false,
3953 };
3954 let property = build_timeline_property::<f64>("progress", &track, stack);
3955
3956 assert_eq!(property.get(), 5.0);
3957 elapsed_frames.set(25);
3958 assert_eq!(property.get(), 5.0);
3959 elapsed_frames.set(75);
3960 assert_eq!(property.get(), 5.0);
3961 elapsed_frames.set(100);
3962 assert_eq!(property.get(), 0.0);
3963 }
3964
3965 #[test]
3966 fn timeline_property_interpolates_rotation_tracks() {
3967 let elapsed_frames = Property::new(0_u64);
3968 let stack = build_stack(&elapsed_frames);
3969 let track = TimelineTrackDefinition {
3970 elements: vec![
3971 TimelineTrackElement::Keyframe(TimelineKeyframe {
3972 marker: TimelineMarker::Frame(0),
3973 value: ValueDefinition::LiteralValue(PaxValue::Rotation(
3974 pax_runtime_api::Rotation::Degrees((-4.0).into()),
3975 )),
3976 easing: Some(Token::new_without_location("Linear".to_string())),
3977 }),
3978 TimelineTrackElement::Keyframe(TimelineKeyframe {
3979 marker: TimelineMarker::Frame(100),
3980 value: ValueDefinition::LiteralValue(PaxValue::Rotation(
3981 pax_runtime_api::Rotation::Degrees(8.0.into()),
3982 )),
3983 easing: None,
3984 }),
3985 ],
3986 playhead: None,
3987 duration: None,
3988 repeat: Some(true),
3989 starting_value: None,
3990 interruption: Default::default(),
3991 use_local_property_scope: false,
3992 };
3993 let property =
3994 build_timeline_property::<pax_runtime_api::Rotation>("rotate", &track, stack);
3995
3996 assert!((property.get().get_as_degrees() - (-4.0)).abs() < 0.0001);
3997 elapsed_frames.set(50);
3998 assert!((property.get().get_as_degrees() - 2.0).abs() < 0.0001);
3999 elapsed_frames.set(100);
4000 assert!((property.get().get_as_degrees() - 8.0).abs() < 0.0001);
4001 }
4002
4003 #[test]
4004 fn transition_rotation_tracks_can_arrive_at_base() {
4005 let phase = Property::new(TRANSITION_PHASE_ENTER);
4006 let playhead = Property::new(0.0_f64);
4007 let base = Property::new(pax_runtime_api::Rotation::Degrees(1.0.into()));
4008 let scope: HashMap<String, Variable> = vec![
4009 (
4010 TRANSITION_PHASE_SYMBOL.to_string(),
4011 Variable::new_from_typed_property(phase.clone()),
4012 ),
4013 (
4014 TRANSITION_PLAYHEAD_SYMBOL.to_string(),
4015 Variable::new_from_typed_property(playhead.clone()),
4016 ),
4017 (
4018 BASE_SYMBOL.to_string(),
4019 Variable::new_from_typed_property(base.clone()),
4020 ),
4021 ]
4022 .into_iter()
4023 .collect();
4024 let stack = RuntimePropertiesStackFrame::new(scope);
4025 let playhead_binding = Some(Box::new(ValueDefinition::Identifier(PaxIdentifier::new(
4026 TRANSITION_PLAYHEAD_SYMBOL,
4027 ))));
4028 let enter = TimelineTrackDefinition {
4029 elements: vec![
4030 TimelineTrackElement::Keyframe(TimelineKeyframe {
4031 marker: TimelineMarker::Frame(0),
4032 value: expression("$base + 4deg"),
4033 easing: Some(Token::new_without_location("Linear".to_string())),
4034 }),
4035 TimelineTrackElement::Keyframe(TimelineKeyframe {
4036 marker: TimelineMarker::Frame(10),
4037 value: expression("$base"),
4038 easing: None,
4039 }),
4040 ],
4041 playhead: playhead_binding,
4042 duration: None,
4043 repeat: Some(false),
4044 starting_value: None,
4045 interruption: Default::default(),
4046 use_local_property_scope: false,
4047 };
4048 let transition = TransitionDefinition {
4049 enter: Some(enter),
4050 ..Default::default()
4051 };
4052 let property =
4053 build_transition_property::<pax_runtime_api::Rotation>("rotate", &transition, stack);
4054
4055 assert!((property.get().get_as_degrees() - 5.0).abs() < 0.0001);
4056 playhead.set(10.0);
4057 assert!((property.get().get_as_degrees() - 1.0).abs() < 0.0001);
4058 }
4059
4060 #[test]
4061 fn timeline_property_samples_from_bound_playhead_property() {
4062 let elapsed_frames = Property::new(0_u64);
4063 let playhead = Property::new(0.0_f64);
4064 let scope: HashMap<String, Variable> = vec![
4065 (
4066 "$frames".to_string(),
4067 Variable::new_from_typed_property(elapsed_frames.clone()),
4068 ),
4069 (
4070 "phase".to_string(),
4071 Variable::new_from_typed_property(playhead.clone()),
4072 ),
4073 ]
4074 .into_iter()
4075 .collect();
4076 let stack = RuntimePropertiesStackFrame::new(scope);
4077 let track = TimelineTrackDefinition {
4078 elements: vec![
4079 TimelineTrackElement::Keyframe(TimelineKeyframe {
4080 marker: TimelineMarker::Frame(0),
4081 value: ValueDefinition::LiteralValue(PaxValue::Numeric(0.0.into())),
4082 easing: Some(Token::new_without_location("Linear".to_string())),
4083 }),
4084 TimelineTrackElement::Keyframe(TimelineKeyframe {
4085 marker: TimelineMarker::Frame(10),
4086 value: ValueDefinition::LiteralValue(PaxValue::Numeric(10.0.into())),
4087 easing: None,
4088 }),
4089 ],
4090 playhead: Some(Box::new(ValueDefinition::Identifier(
4091 pax_manifest::PaxIdentifier::new("self.phase"),
4092 ))),
4093 duration: None,
4094 repeat: Some(false),
4095 starting_value: None,
4096 interruption: Default::default(),
4097 use_local_property_scope: false,
4098 };
4099 let property = build_timeline_property::<f64>("progress", &track, stack);
4100
4101 assert_eq!(property.get(), 0.0);
4102 playhead.set(2.5);
4103 assert!((property.get() - 2.5).abs() < 0.0001);
4104 playhead.set(10.0);
4105 assert!((property.get() - 10.0).abs() < 0.0001);
4106 }
4107 #[test]
4108 fn transition_property_switches_between_enter_and_exit_tracks() {
4109 let phase = Property::new(0_u64);
4110 let playhead = Property::new(0.0_f64);
4111 let scope: HashMap<String, Variable> = vec![
4112 (
4113 TRANSITION_PHASE_SYMBOL.to_string(),
4114 Variable::new_from_typed_property(phase.clone()),
4115 ),
4116 (
4117 TRANSITION_PLAYHEAD_SYMBOL.to_string(),
4118 Variable::new_from_typed_property(playhead.clone()),
4119 ),
4120 ]
4121 .into_iter()
4122 .collect();
4123 let stack = RuntimePropertiesStackFrame::new(scope);
4124 let playhead_binding = Some(Box::new(ValueDefinition::Identifier(PaxIdentifier::new(
4125 TRANSITION_PLAYHEAD_SYMBOL,
4126 ))));
4127 let enter = TimelineTrackDefinition {
4128 elements: vec![
4129 TimelineTrackElement::Keyframe(TimelineKeyframe {
4130 marker: TimelineMarker::Frame(0),
4131 value: ValueDefinition::LiteralValue(PaxValue::Numeric(0.0.into())),
4132 easing: Some(Token::new_without_location("Linear".to_string())),
4133 }),
4134 TimelineTrackElement::Keyframe(TimelineKeyframe {
4135 marker: TimelineMarker::Frame(10),
4136 value: ValueDefinition::LiteralValue(PaxValue::Numeric(10.0.into())),
4137 easing: None,
4138 }),
4139 ],
4140 playhead: playhead_binding.clone(),
4141 duration: None,
4142 repeat: Some(false),
4143 starting_value: None,
4144 interruption: Default::default(),
4145 use_local_property_scope: false,
4146 };
4147 let exit = TimelineTrackDefinition {
4148 elements: vec![
4149 TimelineTrackElement::Keyframe(TimelineKeyframe {
4150 marker: TimelineMarker::Frame(0),
4151 value: ValueDefinition::LiteralValue(PaxValue::Numeric(10.0.into())),
4152 easing: Some(Token::new_without_location("Linear".to_string())),
4153 }),
4154 TimelineTrackElement::Keyframe(TimelineKeyframe {
4155 marker: TimelineMarker::Frame(10),
4156 value: ValueDefinition::LiteralValue(PaxValue::Numeric(0.0.into())),
4157 easing: None,
4158 }),
4159 ],
4160 playhead: playhead_binding,
4161 duration: None,
4162 repeat: Some(false),
4163 starting_value: None,
4164 interruption: Default::default(),
4165 use_local_property_scope: false,
4166 };
4167 let transition = TransitionDefinition {
4168 enter: Some(enter),
4169 exit: Some(exit),
4170 starting_value: Some(Box::new(ValueDefinition::LiteralValue(PaxValue::Numeric(
4171 5.0.into(),
4172 )))),
4173 };
4174 let property = build_transition_property::<f64>("opacity", &transition, stack);
4175
4176 assert_eq!(property.get(), 5.0);
4177 phase.set(TRANSITION_PHASE_ENTER);
4178 playhead.set(5.0);
4179 assert_eq!(property.get(), 5.0);
4180 phase.set(TRANSITION_PHASE_EXIT);
4181 assert_eq!(property.get(), 5.0);
4182 playhead.set(10.0);
4183 assert_eq!(property.get(), 0.0);
4184 }
4185
4186 #[test]
4187 fn transition_property_takes_over_from_last_typed_sample_on_reversal() {
4188 let phase = Property::new(TRANSITION_PHASE_ENTER);
4189 let playhead = Property::new(0.0_f64);
4190 let generation = Property::new(1_u64);
4191 let takeover = Property::new(false);
4192 let stack = RuntimePropertiesStackFrame::new(HashMap::from([
4193 (
4194 TRANSITION_PHASE_SYMBOL.to_string(),
4195 Variable::new_from_typed_property(phase.clone()),
4196 ),
4197 (
4198 TRANSITION_PLAYHEAD_SYMBOL.to_string(),
4199 Variable::new_from_typed_property(playhead.clone()),
4200 ),
4201 (
4202 TRANSITION_GENERATION_SYMBOL.to_string(),
4203 Variable::new_from_typed_property(generation.clone()),
4204 ),
4205 (
4206 TRANSITION_TAKEOVER_SYMBOL.to_string(),
4207 Variable::new_from_typed_property(takeover.clone()),
4208 ),
4209 ]));
4210 let track = |start: f64, end: f64, interruption| TimelineTrackDefinition {
4211 elements: vec![
4212 TimelineTrackElement::Keyframe(TimelineKeyframe {
4213 marker: TimelineMarker::Frame(0),
4214 value: ValueDefinition::LiteralValue(PaxValue::Numeric(start.into())),
4215 easing: Some(Token::new_without_location("Linear".to_string())),
4216 }),
4217 TimelineTrackElement::Keyframe(TimelineKeyframe {
4218 marker: TimelineMarker::Frame(10),
4219 value: ValueDefinition::LiteralValue(PaxValue::Numeric(end.into())),
4220 easing: None,
4221 }),
4222 ],
4223 playhead: Some(Box::new(ValueDefinition::Identifier(PaxIdentifier::new(
4224 TRANSITION_PLAYHEAD_SYMBOL,
4225 )))),
4226 duration: None,
4227 repeat: Some(false),
4228 starting_value: None,
4229 interruption,
4230 use_local_property_scope: false,
4231 };
4232 let transition = TransitionDefinition {
4233 enter: Some(track(0.0, 10.0, InOutInterruption::Takeover)),
4234 exit: Some(track(10.0, 0.0, InOutInterruption::Takeover)),
4235 starting_value: None,
4236 };
4237 let property = build_transition_property::<f64>("x", &transition, stack);
4238
4239 assert_eq!(property.get(), 0.0);
4240 playhead.set(4.0);
4241 assert_eq!(property.get(), 4.0);
4242
4243 playhead.set(0.0);
4244 takeover.set(true);
4245 generation.set(2);
4246 phase.set(TRANSITION_PHASE_EXIT);
4247 assert_eq!(property.get(), 4.0);
4248 playhead.set(5.0);
4249 assert_eq!(property.get(), 2.0);
4250
4251 playhead.set(0.0);
4252 generation.set(3);
4253 phase.set(TRANSITION_PHASE_ENTER);
4254 assert_eq!(property.get(), 2.0);
4255 playhead.set(5.0);
4256 assert_eq!(property.get(), 6.0);
4257 }
4258
4259 #[test]
4260 fn transition_property_restart_uses_authored_destination_start() {
4261 let phase = Property::new(TRANSITION_PHASE_ENTER);
4262 let playhead = Property::new(5.0_f64);
4263 let generation = Property::new(1_u64);
4264 let takeover = Property::new(false);
4265 let stack = RuntimePropertiesStackFrame::new(HashMap::from([
4266 (
4267 TRANSITION_PHASE_SYMBOL.to_string(),
4268 Variable::new_from_typed_property(phase.clone()),
4269 ),
4270 (
4271 TRANSITION_PLAYHEAD_SYMBOL.to_string(),
4272 Variable::new_from_typed_property(playhead.clone()),
4273 ),
4274 (
4275 TRANSITION_GENERATION_SYMBOL.to_string(),
4276 Variable::new_from_typed_property(generation.clone()),
4277 ),
4278 (
4279 TRANSITION_TAKEOVER_SYMBOL.to_string(),
4280 Variable::new_from_typed_property(takeover.clone()),
4281 ),
4282 ]));
4283 let track = |start: f64, end: f64, interruption| TimelineTrackDefinition {
4284 elements: vec![
4285 TimelineTrackElement::Keyframe(TimelineKeyframe {
4286 marker: TimelineMarker::Frame(0),
4287 value: ValueDefinition::LiteralValue(PaxValue::Numeric(start.into())),
4288 easing: Some(Token::new_without_location("Linear".to_string())),
4289 }),
4290 TimelineTrackElement::Keyframe(TimelineKeyframe {
4291 marker: TimelineMarker::Frame(10),
4292 value: ValueDefinition::LiteralValue(PaxValue::Numeric(end.into())),
4293 easing: None,
4294 }),
4295 ],
4296 playhead: Some(Box::new(ValueDefinition::Identifier(PaxIdentifier::new(
4297 TRANSITION_PLAYHEAD_SYMBOL,
4298 )))),
4299 duration: None,
4300 repeat: Some(false),
4301 starting_value: None,
4302 interruption,
4303 use_local_property_scope: false,
4304 };
4305 let transition = TransitionDefinition {
4306 enter: Some(track(0.0, 10.0, InOutInterruption::Takeover)),
4307 exit: Some(track(10.0, 0.0, InOutInterruption::Restart)),
4308 starting_value: None,
4309 };
4310 let property = build_transition_property::<f64>("x", &transition, stack);
4311 assert_eq!(property.get(), 5.0);
4312
4313 playhead.set(0.0);
4314 takeover.set(true);
4315 generation.set(2);
4316 phase.set(TRANSITION_PHASE_EXIT);
4317 assert_eq!(property.get(), 10.0);
4318 }
4319
4320 #[test]
4321 fn transition_property_uses_millisecond_playhead_for_dynamic_millisecond_duration() {
4322 let phase = Property::new(TRANSITION_PHASE_ENTER);
4323 let frame_playhead = Property::new(0.0_f64);
4324 let millis_playhead = Property::new(0.0_f64);
4325 let duration = Property::new(Duration::Milliseconds(1000.into()));
4326 let scope: HashMap<String, Variable> = vec![
4327 (
4328 TRANSITION_PHASE_SYMBOL.to_string(),
4329 Variable::new_from_typed_property(phase),
4330 ),
4331 (
4332 TRANSITION_PLAYHEAD_SYMBOL.to_string(),
4333 Variable::new_from_typed_property(frame_playhead.clone()),
4334 ),
4335 (
4336 TRANSITION_PLAYHEAD_MILLIS_SYMBOL.to_string(),
4337 Variable::new_from_typed_property(millis_playhead.clone()),
4338 ),
4339 (
4340 "duration".to_string(),
4341 Variable::new_from_typed_property(duration),
4342 ),
4343 ]
4344 .into_iter()
4345 .collect();
4346 let stack = RuntimePropertiesStackFrame::new(scope);
4347 let enter = TimelineTrackDefinition {
4348 elements: vec![
4349 TimelineTrackElement::Keyframe(TimelineKeyframe {
4350 marker: TimelineMarker::Percent(0.0),
4351 value: ValueDefinition::LiteralValue(PaxValue::Numeric(0.0.into())),
4352 easing: Some(Token::new_without_location("Linear".to_string())),
4353 }),
4354 TimelineTrackElement::Keyframe(TimelineKeyframe {
4355 marker: TimelineMarker::Percent(100.0),
4356 value: ValueDefinition::LiteralValue(PaxValue::Numeric(1.0.into())),
4357 easing: None,
4358 }),
4359 ],
4360 playhead: Some(Box::new(ValueDefinition::Identifier(PaxIdentifier::new(
4361 TRANSITION_PLAYHEAD_SYMBOL,
4362 )))),
4363 duration: Some(Box::new(ValueDefinition::Identifier(PaxIdentifier::new(
4364 "self.duration",
4365 )))),
4366 repeat: Some(false),
4367 starting_value: None,
4368 interruption: Default::default(),
4369 use_local_property_scope: false,
4370 };
4371 let transition = TransitionDefinition {
4372 enter: Some(enter),
4373 exit: None,
4374 starting_value: Some(Box::new(ValueDefinition::LiteralValue(PaxValue::Numeric(
4375 1.0.into(),
4376 )))),
4377 };
4378 let property = build_transition_property::<f64>("opacity", &transition, stack);
4379
4380 frame_playhead.set(500.0);
4381 assert_eq!(property.get(), 0.0);
4382 millis_playhead.set(500.0);
4383 assert_eq!(property.get(), 0.5);
4384 millis_playhead.set(1000.0);
4385 assert_eq!(property.get(), 1.0);
4386 }
4387
4388 #[test]
4389 fn timeline_property_skips_keyframes_that_fail_typed_coercion() {
4390 let elapsed_frames = Property::new(0_u64);
4391 let stack = build_stack(&elapsed_frames);
4392 let track = TimelineTrackDefinition {
4393 elements: vec![
4394 TimelineTrackElement::Keyframe(TimelineKeyframe {
4395 marker: TimelineMarker::Frame(0),
4396 value: ValueDefinition::LiteralValue(PaxValue::Numeric(0.0.into())),
4397 easing: Some(Token::new_without_location("Linear".to_string())),
4398 }),
4399 TimelineTrackElement::Keyframe(TimelineKeyframe {
4400 marker: TimelineMarker::Frame(50),
4401 value: ValueDefinition::LiteralValue(PaxValue::String("bad".to_string())),
4402 easing: Some(Token::new_without_location("Linear".to_string())),
4403 }),
4404 TimelineTrackElement::Keyframe(TimelineKeyframe {
4405 marker: TimelineMarker::Frame(100),
4406 value: ValueDefinition::LiteralValue(PaxValue::Numeric(100.0.into())),
4407 easing: None,
4408 }),
4409 ],
4410 playhead: None,
4411 duration: None,
4412 repeat: Some(false),
4413 starting_value: None,
4414 interruption: Default::default(),
4415 use_local_property_scope: false,
4416 };
4417 let property = build_timeline_property::<f64>("progress", &track, stack);
4418
4419 elapsed_frames.set(75);
4420 assert!((property.get() - 75.0).abs() < 0.0001);
4421 }
4422}
4423
4424#[cfg(test)]
4425mod base_symbol_tests {
4426 use super::{
4427 build_component_property, create_new_common_properties_from_columns,
4428 property_columns_from_defined_properties, stack_with_base, RuntimePropertiesStackFrame,
4429 RuntimeResolvedPropertyColumns, RuntimeResolvedPropertyEntry, RuntimeSettingsCondition,
4430 RuntimeSettingsSource,
4431 };
4432 use pax_language::parse_pax_expression;
4433 use pax_manifest::cartridge_generation::{
4434 TRANSITION_PHASE_ENTER, TRANSITION_PHASE_SYMBOL, TRANSITION_PLAYHEAD_SYMBOL,
4435 };
4436 use pax_manifest::{
4437 ExpressionInfo, PaxIdentifier, TimelineKeyframe, TimelineMarker, TimelineTrackDefinition,
4438 TimelineTrackElement, Token, TransitionDefinition, ValueDefinition,
4439 };
4440 use pax_runtime_api::{Duration, Numeric, Opacity, PaxValue, Property, Size, Variable};
4441 use std::collections::{BTreeMap, HashMap};
4442 use std::rc::Rc;
4443
4444 fn expression(raw: &str) -> ValueDefinition {
4445 pax_runtime_api::pax_value::functions::Functions::register_all_functions();
4446 ValueDefinition::Expression(ExpressionInfo::new(parse_pax_expression(raw).unwrap()))
4447 }
4448
4449 fn size_literal(px: f64) -> ValueDefinition {
4450 ValueDefinition::LiteralValue(PaxValue::Size(Size::Pixels(Numeric::F64(px))))
4451 }
4452
4453 fn percent_literal(percent: f64) -> ValueDefinition {
4454 ValueDefinition::LiteralValue(PaxValue::Size(Size::Percent(Numeric::F64(percent))))
4455 }
4456
4457 fn size_pair_literal(x: f64, y: f64) -> ValueDefinition {
4458 ValueDefinition::LiteralValue(PaxValue::Vec(vec![
4459 PaxValue::Size(Size::Percent(Numeric::F64(x))),
4460 PaxValue::Size(Size::Percent(Numeric::F64(y))),
4461 ]))
4462 }
4463
4464 fn assert_percent(size: Option<Size>, expected: f64) {
4465 let actual = size.expect("expected common property to be set");
4466 assert!((actual.expect_percent() - (expected / 100.0)).abs() < 0.0001);
4467 }
4468
4469 fn entry(value: ValueDefinition) -> RuntimeResolvedPropertyEntry {
4470 RuntimeResolvedPropertyEntry {
4471 source: RuntimeSettingsSource::Inline,
4472 selector: None,
4473 source_location: None,
4474 source_stack: None,
4475 value,
4476 condition: None,
4477 axis_index: None,
4478 }
4479 }
4480
4481 fn conditional_entry(value: ValueDefinition, condition: &str) -> RuntimeResolvedPropertyEntry {
4482 RuntimeResolvedPropertyEntry {
4483 condition: Some(RuntimeSettingsCondition {
4484 positive: vec![ExpressionInfo::new(
4485 parse_pax_expression(condition).unwrap(),
4486 )],
4487 negative: Vec::new(),
4488 }),
4489 ..entry(value)
4490 }
4491 }
4492
4493 fn columns_for(
4494 name: &str,
4495 entries: Vec<RuntimeResolvedPropertyEntry>,
4496 ) -> RuntimeResolvedPropertyColumns {
4497 BTreeMap::from([(name.to_string(), entries)])
4498 }
4499
4500 fn empty_stack() -> Rc<RuntimePropertiesStackFrame> {
4501 RuntimePropertiesStackFrame::new(HashMap::new())
4502 }
4503
4504 #[test]
4505 fn pax995_dynamic_index_invalidates_component_property() {
4506 let items = Property::new(vec![vec![10_i64, 20_i64], vec![30, 40]]);
4507 let indices = Property::new(vec![0_i64, 1]);
4508 let index = Property::new(0_i64);
4509 let column = Property::new(0_i64);
4510 let stack = RuntimePropertiesStackFrame::new(HashMap::from([
4511 (
4512 "items".into(),
4513 Variable::new_from_typed_property(items.clone()),
4514 ),
4515 (
4516 "indices".into(),
4517 Variable::new_from_typed_property(indices.clone()),
4518 ),
4519 (
4520 "index".into(),
4521 Variable::new_from_typed_property(index.clone()),
4522 ),
4523 (
4524 "column".into(),
4525 Variable::new_from_typed_property(column.clone()),
4526 ),
4527 ]));
4528 let selected: Property<i64> = build_component_property(
4529 "selected",
4530 &expression("items[indices[index]][column + 0]"),
4531 &stack,
4532 stack.clone(),
4533 |_, _| unreachable!(),
4534 );
4535 assert_eq!(selected.get(), 10);
4536 index.set(1);
4537 assert_eq!(selected.get(), 30);
4538 column.set(1);
4539 assert_eq!(selected.get(), 40);
4540 indices.set(vec![1, 0]);
4541 assert_eq!(selected.get(), 20);
4542 items.set(vec![vec![50, 60], vec![70, 80]]);
4543 assert_eq!(selected.get(), 60);
4544 }
4545
4546 #[test]
4547 fn base_symbol_resolves_previous_component_property_layer() {
4548 let base_property = Property::new(10.0_f64);
4549 let stack = stack_with_base(&empty_stack(), base_property);
4550
4551 let property: Property<f64> = build_component_property(
4552 "opacity",
4553 &expression("$base + 0.25"),
4554 &stack,
4555 stack.clone(),
4556 |_, _| unreachable!("literal blocks are not used by this test"),
4557 );
4558
4559 assert!((property.get() - 10.25).abs() < 0.0001);
4560 }
4561
4562 #[test]
4563 fn base_symbol_resolves_previous_common_property_layer() {
4564 let stack = empty_stack();
4565 let columns = columns_for(
4566 "x",
4567 vec![entry(size_literal(10.0)), entry(expression("$base + 5px"))],
4568 );
4569
4570 let common = create_new_common_properties_from_columns(&columns, &stack);
4571 let x = common.borrow().x.get().unwrap();
4572
4573 assert!((x.get_pixels(100.0) - 15.0).abs() < 0.0001);
4574 }
4575
4576 #[test]
4577 fn base_symbol_tracks_reactive_previous_layers() {
4578 let offset = Property::new(Size::Pixels(Numeric::F64(10.0)));
4579 let stack = RuntimePropertiesStackFrame::new(HashMap::from([(
4580 "offset".to_string(),
4581 Variable::new_from_typed_property(offset.clone()),
4582 )]));
4583 let columns = columns_for(
4584 "x",
4585 vec![
4586 entry(expression("offset")),
4587 entry(expression("$base + 5px")),
4588 ],
4589 );
4590
4591 let common = create_new_common_properties_from_columns(&columns, &stack);
4592 assert!((common.borrow().x.get().unwrap().get_pixels(100.0) - 15.0).abs() < 0.0001);
4593
4594 offset.set(Size::Pixels(Numeric::F64(20.0)));
4595 assert!((common.borrow().x.get().unwrap().get_pixels(100.0) - 25.0).abs() < 0.0001);
4596 }
4597
4598 #[test]
4599 fn imported_settings_use_the_provider_scope_reactively() {
4600 let is_dark = Property::new(false);
4601 let provider_stack = RuntimePropertiesStackFrame::new(HashMap::from([(
4602 "is_dark".to_string(),
4603 Variable::new_from_typed_property(is_dark.clone()),
4604 )]));
4605 let imported_entry = RuntimeResolvedPropertyEntry {
4606 source: RuntimeSettingsSource::ImportedLayer {
4607 provider_id: crate::ExpandedNodeIdentifier(1),
4608 provider_type_id: pax_manifest::TypeId::build_singleton(
4609 "example::Theme",
4610 Some("Theme"),
4611 ),
4612 },
4613 source_stack: Some(provider_stack),
4614 ..entry(expression("self.is_dark ? 50px : 10px"))
4615 };
4616 let columns = columns_for("width", vec![imported_entry]);
4617
4618 let common = create_new_common_properties_from_columns(&columns, &empty_stack());
4619 assert!((common.borrow().width.get().unwrap().get_pixels(100.0) - 10.0).abs() < 0.0001);
4620
4621 is_dark.set(true);
4622 assert!((common.borrow().width.get().unwrap().get_pixels(100.0) - 50.0).abs() < 0.0001);
4623 }
4624
4625 #[test]
4626 fn imported_settings_conditions_use_the_provider_scope_reactively() {
4627 let is_dark = Property::new(false);
4628 let provider_stack = RuntimePropertiesStackFrame::new(HashMap::from([(
4629 "is_dark".to_string(),
4630 Variable::new_from_typed_property(is_dark.clone()),
4631 )]));
4632 let imported_entry = RuntimeResolvedPropertyEntry {
4633 source: RuntimeSettingsSource::ImportedLayer {
4634 provider_id: crate::ExpandedNodeIdentifier(1),
4635 provider_type_id: pax_manifest::TypeId::build_singleton(
4636 "example::Theme",
4637 Some("Theme"),
4638 ),
4639 },
4640 source_stack: Some(provider_stack),
4641 condition: Some(RuntimeSettingsCondition {
4642 positive: vec![ExpressionInfo::new(
4643 parse_pax_expression("self.is_dark").unwrap(),
4644 )],
4645 negative: Vec::new(),
4646 }),
4647 ..entry(size_literal(50.0))
4648 };
4649 let columns = columns_for("width", vec![entry(size_literal(10.0)), imported_entry]);
4650
4651 let common = create_new_common_properties_from_columns(&columns, &empty_stack());
4652 assert!((common.borrow().width.get().unwrap().get_pixels(100.0) - 10.0).abs() < 0.0001);
4653
4654 is_dark.set(true);
4655 assert!((common.borrow().width.get().unwrap().get_pixels(100.0) - 50.0).abs() < 0.0001);
4656 }
4657
4658 #[test]
4659 fn settings_condition_preserves_previous_layer_until_active() {
4660 let landscape = Property::new(false);
4661 let stack = RuntimePropertiesStackFrame::new(HashMap::from([(
4662 "$landscape".to_string(),
4663 Variable::new_from_typed_property(landscape.clone()),
4664 )]));
4665 let columns = columns_for(
4666 "width",
4667 vec![
4668 entry(size_literal(100.0)),
4669 conditional_entry(size_literal(50.0), "$landscape"),
4670 ],
4671 );
4672
4673 let common = create_new_common_properties_from_columns(&columns, &stack);
4674 assert!((common.borrow().width.get().unwrap().get_pixels(100.0) - 100.0).abs() < 0.0001);
4675
4676 landscape.set(true);
4677 assert!((common.borrow().width.get().unwrap().get_pixels(100.0) - 50.0).abs() < 0.0001);
4678 }
4679
4680 #[test]
4681 fn base_symbol_supplies_timeline_starting_values() {
4682 let elapsed_frames = Property::new(0_u64);
4683 let stack = RuntimePropertiesStackFrame::new(HashMap::from([(
4684 "$frames".to_string(),
4685 Variable::new_from_typed_property(elapsed_frames.clone()),
4686 )]));
4687 let track = TimelineTrackDefinition {
4688 elements: vec![
4689 TimelineTrackElement::Keyframe(TimelineKeyframe {
4690 marker: TimelineMarker::Frame(0),
4691 value: expression("$base - 10px"),
4692 easing: Some(Token::new_without_location("Linear".to_string())),
4693 }),
4694 TimelineTrackElement::Keyframe(TimelineKeyframe {
4695 marker: TimelineMarker::Frame(10),
4696 value: expression("$base"),
4697 easing: None,
4698 }),
4699 ],
4700 playhead: None,
4701 duration: Some(Box::new(ValueDefinition::LiteralValue(PaxValue::Duration(
4702 Duration::Frames(10.into()),
4703 )))),
4704 repeat: Some(false),
4705 starting_value: None,
4706 interruption: Default::default(),
4707 use_local_property_scope: false,
4708 };
4709 let columns = columns_for(
4710 "y",
4711 vec![
4712 entry(size_literal(100.0)),
4713 entry(ValueDefinition::Timeline(track)),
4714 ],
4715 );
4716
4717 let common = create_new_common_properties_from_columns(&columns, &stack);
4718 assert!((common.borrow().y.get().unwrap().get_pixels(100.0) - 90.0).abs() < 0.0001);
4719
4720 elapsed_frames.set(10);
4721 assert!((common.borrow().y.get().unwrap().get_pixels(100.0) - 100.0).abs() < 0.0001);
4722 }
4723
4724 #[test]
4725 fn base_symbol_uses_layout_zero_for_unset_position_transitions() {
4726 let phase = Property::new(TRANSITION_PHASE_ENTER);
4727 let playhead = Property::new(5.0_f64);
4728 let stack = RuntimePropertiesStackFrame::new(HashMap::from([
4729 (
4730 TRANSITION_PHASE_SYMBOL.to_string(),
4731 Variable::new_from_typed_property(phase.clone()),
4732 ),
4733 (
4734 TRANSITION_PLAYHEAD_SYMBOL.to_string(),
4735 Variable::new_from_typed_property(playhead.clone()),
4736 ),
4737 ]));
4738 let playhead_binding = Some(Box::new(ValueDefinition::Identifier(PaxIdentifier::new(
4739 TRANSITION_PLAYHEAD_SYMBOL,
4740 ))));
4741 let enter = TimelineTrackDefinition {
4742 elements: vec![
4743 TimelineTrackElement::Keyframe(TimelineKeyframe {
4744 marker: TimelineMarker::Frame(0),
4745 value: expression("$base - 100px"),
4746 easing: Some(Token::new_without_location("InQuad".to_string())),
4747 }),
4748 TimelineTrackElement::Keyframe(TimelineKeyframe {
4749 marker: TimelineMarker::Frame(10),
4750 value: expression("$base"),
4751 easing: None,
4752 }),
4753 ],
4754 playhead: playhead_binding,
4755 duration: Some(Box::new(ValueDefinition::LiteralValue(PaxValue::Duration(
4756 Duration::Frames(10.into()),
4757 )))),
4758 repeat: Some(false),
4759 starting_value: None,
4760 interruption: Default::default(),
4761 use_local_property_scope: false,
4762 };
4763 let transition = TransitionDefinition {
4764 enter: Some(enter),
4765 ..Default::default()
4766 };
4767 let columns = columns_for("y", vec![entry(ValueDefinition::Transition(transition))]);
4768
4769 let common = create_new_common_properties_from_columns(&columns, &stack);
4770 let y = common.borrow().y.get().unwrap();
4771 assert!((y.get_pixels(100.0) - -75.0).abs() < 0.0001);
4772
4773 playhead.set(10.0);
4774 let y = common.borrow().y.get().unwrap();
4775 assert!(y.get_pixels(100.0).abs() < 0.0001);
4776 }
4777
4778 #[test]
4779 fn base_symbol_uses_opaque_for_unset_opacity_transitions() {
4780 let phase = Property::new(TRANSITION_PHASE_ENTER);
4781 let playhead = Property::new(5.0_f64);
4782 let stack = RuntimePropertiesStackFrame::new(HashMap::from([
4783 (
4784 TRANSITION_PHASE_SYMBOL.to_string(),
4785 Variable::new_from_typed_property(phase.clone()),
4786 ),
4787 (
4788 TRANSITION_PLAYHEAD_SYMBOL.to_string(),
4789 Variable::new_from_typed_property(playhead.clone()),
4790 ),
4791 ]));
4792 let playhead_binding = Some(Box::new(ValueDefinition::Identifier(PaxIdentifier::new(
4793 TRANSITION_PLAYHEAD_SYMBOL,
4794 ))));
4795 let enter = TimelineTrackDefinition {
4796 elements: vec![
4797 TimelineTrackElement::Keyframe(TimelineKeyframe {
4798 marker: TimelineMarker::Frame(0),
4799 value: ValueDefinition::LiteralValue(PaxValue::Numeric(0.0.into())),
4800 easing: Some(Token::new_without_location("Linear".to_string())),
4801 }),
4802 TimelineTrackElement::Keyframe(TimelineKeyframe {
4803 marker: TimelineMarker::Frame(10),
4804 value: expression("$base"),
4805 easing: None,
4806 }),
4807 ],
4808 playhead: playhead_binding,
4809 duration: Some(Box::new(ValueDefinition::LiteralValue(PaxValue::Duration(
4810 Duration::Frames(10.into()),
4811 )))),
4812 repeat: Some(false),
4813 starting_value: None,
4814 interruption: Default::default(),
4815 use_local_property_scope: false,
4816 };
4817 let transition = TransitionDefinition {
4818 enter: Some(enter),
4819 ..Default::default()
4820 };
4821 let columns = columns_for(
4822 "opacity",
4823 vec![entry(ValueDefinition::Transition(transition))],
4824 );
4825
4826 let common = create_new_common_properties_from_columns(&columns, &stack);
4827 assert_eq!(
4828 common.borrow().opacity.get(),
4829 Some(Opacity::Alpha(0.5.into()))
4830 );
4831
4832 playhead.set(10.0);
4833 assert_eq!(
4834 common.borrow().opacity.get(),
4835 Some(Opacity::Alpha(1.0.into()))
4836 );
4837 }
4838
4839 #[test]
4840 fn scalar_common_property_alias_applies_to_both_axes() {
4841 let stack = empty_stack();
4842 let defined = BTreeMap::from([("scale".to_string(), percent_literal(20.0))]);
4843 let columns = property_columns_from_defined_properties(&defined);
4844
4845 let common = create_new_common_properties_from_columns(&columns, &stack);
4846
4847 assert_percent(common.borrow().scale_x.get(), 20.0);
4848 assert_percent(common.borrow().scale_y.get(), 20.0);
4849 }
4850
4851 #[test]
4852 fn list_common_property_alias_splits_across_axes() {
4853 let stack = empty_stack();
4854 let defined = BTreeMap::from([("anchor".to_string(), size_pair_literal(25.0, 75.0))]);
4855 let columns = property_columns_from_defined_properties(&defined);
4856
4857 let common = create_new_common_properties_from_columns(&columns, &stack);
4858
4859 assert_percent(common.borrow().anchor_x.get(), 25.0);
4860 assert_percent(common.borrow().anchor_y.get(), 75.0);
4861 }
4862
4863 #[test]
4864 fn axis_specific_common_property_overrides_alias_with_base() {
4865 let stack = empty_stack();
4866 let defined = BTreeMap::from([
4867 ("scale".to_string(), percent_literal(20.0)),
4868 ("scale_y".to_string(), expression("$base + 5")),
4869 ]);
4870 let columns = property_columns_from_defined_properties(&defined);
4871
4872 let common = create_new_common_properties_from_columns(&columns, &stack);
4873
4874 assert_percent(common.borrow().scale_x.get(), 20.0);
4875 assert_percent(common.borrow().scale_y.get(), 25.0);
4876 }
4877}
4878
4879pub fn update_existing_common_properties_from_columns(
4882 expanded_node: &Rc<ExpandedNode>,
4883 property_columns: &RuntimeResolvedPropertyColumns,
4884 stack_frame: &Rc<RuntimePropertiesStackFrame>,
4885) {
4886 let expanded_node = borrow!(**expanded_node);
4887 let outer_ref = expanded_node.common_properties.borrow();
4888 let rc = Rc::clone(&outer_ref);
4889 let inner_ref = (*rc).borrow_mut();
4890 let mut cp = inner_ref;
4891
4892 update_common_properties(&mut cp, property_columns, stack_frame);
4893}
4894
4895pub fn update_existing_common_properties(
4897 expanded_node: &Rc<ExpandedNode>,
4898 defined_properties: &BTreeMap<String, pax_manifest::ValueDefinition>,
4899 stack_frame: &Rc<RuntimePropertiesStackFrame>,
4900) {
4901 update_existing_common_properties_from_columns(
4902 expanded_node,
4903 &property_columns_from_defined_properties(defined_properties),
4904 stack_frame,
4905 );
4906}
4907
4908fn create_id_property(
4909 property_columns: &RuntimeResolvedPropertyColumns,
4910) -> Property<Option<String>> {
4911 let id = property_columns
4912 .get("id")
4913 .and_then(|entries| entries.last());
4914 Property::new(
4915 if let Some(RuntimeResolvedPropertyEntry {
4916 value: pax_manifest::ValueDefinition::Identifier(pax_identifier),
4917 ..
4918 }) = id
4919 {
4920 Some(pax_identifier.name.clone())
4921 } else {
4922 None
4923 },
4924 )
4925}
4926
4927pub fn create_new_common_properties_from_columns(
4930 property_columns: &RuntimeResolvedPropertyColumns,
4931 stack_frame: &Rc<RuntimePropertiesStackFrame>,
4932) -> Rc<RefCell<CommonProperties>> {
4933 Rc::new(RefCell::new(CommonProperties {
4934 id: create_id_property(property_columns),
4935 x: resolve_property("x", property_columns, stack_frame),
4936 y: resolve_property("y", property_columns, stack_frame),
4937 padding_x: resolve_property("padding_x", property_columns, stack_frame),
4938 padding_y: resolve_property("padding_y", property_columns, stack_frame),
4939 width: resolve_property("width", property_columns, stack_frame),
4940 height: resolve_property("height", property_columns, stack_frame),
4941 scale_x: resolve_property("scale_x", property_columns, stack_frame),
4942 scale_y: resolve_property("scale_y", property_columns, stack_frame),
4943 skew_x: resolve_property("skew_x", property_columns, stack_frame),
4944 skew_y: resolve_property("skew_y", property_columns, stack_frame),
4945 rotate: resolve_property("rotate", property_columns, stack_frame),
4946 transform: resolve_property("transform", property_columns, stack_frame),
4947 opacity: resolve_property("opacity", property_columns, stack_frame),
4948 layout_role: resolve_property("layout_role", property_columns, stack_frame),
4949 anchor_x: resolve_property("anchor_x", property_columns, stack_frame),
4950 anchor_y: resolve_property("anchor_y", property_columns, stack_frame),
4951 unclippable: resolve_property("unclippable", property_columns, stack_frame),
4952 _raycastable: resolve_property("_raycastable", property_columns, stack_frame),
4953 _suspended: resolve_property("_suspended", property_columns, stack_frame),
4954 }))
4955}
4956
4957pub fn create_new_common_properties(
4959 defined_properties: &BTreeMap<String, pax_manifest::ValueDefinition>,
4960 stack_frame: &Rc<RuntimePropertiesStackFrame>,
4961) -> Rc<RefCell<CommonProperties>> {
4962 create_new_common_properties_from_columns(
4963 &property_columns_from_defined_properties(defined_properties),
4964 stack_frame,
4965 )
4966}
4967
4968fn update_common_properties(
4969 cp: &mut CommonProperties,
4970 property_columns: &RuntimeResolvedPropertyColumns,
4971 stack_frame: &Rc<RuntimePropertiesStackFrame>,
4972) {
4973 cp.id.replace_with(create_id_property(property_columns));
4974 cp.x.replace_with(resolve_property("x", property_columns, stack_frame));
4975 cp.y.replace_with(resolve_property("y", property_columns, stack_frame));
4976 cp.padding_x
4977 .replace_with(resolve_property("padding_x", property_columns, stack_frame));
4978 cp.padding_y
4979 .replace_with(resolve_property("padding_y", property_columns, stack_frame));
4980 cp.width
4981 .replace_with(resolve_property("width", property_columns, stack_frame));
4982 cp.height
4983 .replace_with(resolve_property("height", property_columns, stack_frame));
4984 cp.scale_x
4985 .replace_with(resolve_property("scale_x", property_columns, stack_frame));
4986 cp.scale_y
4987 .replace_with(resolve_property("scale_y", property_columns, stack_frame));
4988 cp.skew_x
4989 .replace_with(resolve_property("skew_x", property_columns, stack_frame));
4990 cp.skew_y
4991 .replace_with(resolve_property("skew_y", property_columns, stack_frame));
4992 cp.rotate
4993 .replace_with(resolve_property("rotate", property_columns, stack_frame));
4994 cp.transform
4995 .replace_with(resolve_property("transform", property_columns, stack_frame));
4996 cp.opacity
4997 .replace_with(resolve_property("opacity", property_columns, stack_frame));
4998 cp.layout_role.replace_with(resolve_property(
4999 "layout_role",
5000 property_columns,
5001 stack_frame,
5002 ));
5003 cp.anchor_x
5004 .replace_with(resolve_property("anchor_x", property_columns, stack_frame));
5005 cp.anchor_y
5006 .replace_with(resolve_property("anchor_y", property_columns, stack_frame));
5007 cp.unclippable.replace_with(resolve_property(
5008 "unclippable",
5009 property_columns,
5010 stack_frame,
5011 ));
5012 cp._raycastable.replace_with(resolve_property(
5013 "_raycastable",
5014 property_columns,
5015 stack_frame,
5016 ));
5017 cp._suspended.replace_with(resolve_property(
5018 "_suspended",
5019 property_columns,
5020 stack_frame,
5021 ));
5022}
5023
5024#[cfg(test)]
5025mod runtime_settings_tests {
5026 use super::{add_removed_property_resets, resolve_runtime_settings_with_layers_for_node};
5027 use crate::{
5028 ExpandedNodeIdentifier, RuntimePropertiesStackFrame, RuntimeSettingsLayer,
5029 RuntimeSettingsSource,
5030 };
5031 use pax_manifest::{
5032 LiteralBlockDefinition, PaxIdentifier, SettingElement, SettingsBlockElement,
5033 TemplateNodeDefinition, TimelineBlockElement, TimelineDefinition,
5034 TimelineSelectorBlockDefinition, TimelineSelectorElement, TimelineTrackDefinition, Token,
5035 TypeId, ValueDefinition,
5036 };
5037 use pax_runtime_api::PaxValue;
5038 use std::collections::{BTreeMap, HashMap};
5039
5040 fn setting(name: &str, value: i32) -> SettingElement {
5041 SettingElement::Setting(
5042 Token::new_without_location(name.to_string()),
5043 ValueDefinition::LiteralValue(PaxValue::Numeric(value.into())),
5044 )
5045 }
5046
5047 fn selector_block(selector: &str, elements: Vec<SettingElement>) -> SettingsBlockElement {
5048 SettingsBlockElement::SelectorBlock(
5049 Token::new_without_location(selector.to_string()),
5050 LiteralBlockDefinition::new(elements),
5051 )
5052 }
5053
5054 #[test]
5055 fn runtime_settings_resolve_columns_and_provenance_by_layer() {
5056 let mut node = TemplateNodeDefinition {
5057 type_id: TypeId::build_singleton("example::Text", Some("Text")),
5058 control_flow_settings: None,
5059 settings: Some(vec![
5060 SettingElement::Setting(
5061 Token::new_without_location("id".to_string()),
5062 ValueDefinition::Identifier(PaxIdentifier::new("hero")),
5063 ),
5064 setting("fill", 4),
5065 ]),
5066 selector_info: pax_manifest::TemplateNodeSelectorInfo {
5067 class_binding: Some(ValueDefinition::LiteralValue(PaxValue::String(
5068 "headline".to_string(),
5069 ))),
5070 ..Default::default()
5071 },
5072 raw_comment_string: None,
5073 };
5074 node.normalize_selector_info();
5075
5076 let component_settings = Some(vec![selector_block(
5077 "Text",
5078 vec![setting("width", 1), setting("fill", 1)],
5079 )]);
5080 let imported_layers = vec![
5081 RuntimeSettingsLayer {
5082 provider_id: ExpandedNodeIdentifier(10),
5083 provider_type_id: TypeId::build_singleton("example::BaseTheme", Some("BaseTheme")),
5084 provider_stack: RuntimePropertiesStackFrame::new(HashMap::new()),
5085 settings: vec![selector_block(
5086 "Text",
5087 vec![setting("height", 2), setting("fill", 2)],
5088 )],
5089 },
5090 RuntimeSettingsLayer {
5091 provider_id: ExpandedNodeIdentifier(11),
5092 provider_type_id: TypeId::build_singleton(
5093 "example::AccentTheme",
5094 Some("AccentTheme"),
5095 ),
5096 provider_stack: RuntimePropertiesStackFrame::new(HashMap::new()),
5097 settings: vec![selector_block(
5098 ".headline",
5099 vec![setting("opacity", 3), setting("fill", 3)],
5100 )],
5101 },
5102 ];
5103
5104 let resolved = resolve_runtime_settings_with_layers_for_node(
5105 &node,
5106 &BTreeMap::new(),
5107 &component_settings,
5108 &imported_layers,
5109 &["headline".to_string()],
5110 None,
5111 &[],
5112 );
5113
5114 assert!(matches!(
5115 resolved.defined_properties.get("width"),
5116 Some(ValueDefinition::LiteralValue(PaxValue::Numeric(value))) if *value == 1.into()
5117 ));
5118 assert!(matches!(
5119 resolved.defined_properties.get("height"),
5120 Some(ValueDefinition::LiteralValue(PaxValue::Numeric(value))) if *value == 2.into()
5121 ));
5122 assert!(matches!(
5123 resolved.defined_properties.get("opacity"),
5124 Some(ValueDefinition::LiteralValue(PaxValue::Numeric(value))) if *value == 3.into()
5125 ));
5126 assert!(matches!(
5127 resolved.defined_properties.get("fill"),
5128 Some(ValueDefinition::LiteralValue(PaxValue::Numeric(value))) if *value == 4.into()
5129 ));
5130
5131 let fill_column = resolved
5132 .columns
5133 .get("fill")
5134 .expect("fill column should exist");
5135 assert_eq!(fill_column.len(), 4);
5136 assert!(matches!(
5137 fill_column[0].source,
5138 RuntimeSettingsSource::ComponentSettings
5139 ));
5140 assert!(matches!(
5141 fill_column[1].source,
5142 RuntimeSettingsSource::ImportedLayer { provider_id, .. } if provider_id == ExpandedNodeIdentifier(10)
5143 ));
5144 assert!(matches!(
5145 fill_column[2].source,
5146 RuntimeSettingsSource::ImportedLayer { provider_id, .. } if provider_id == ExpandedNodeIdentifier(11)
5147 ));
5148 assert!(matches!(
5149 fill_column[3].source,
5150 RuntimeSettingsSource::Inline
5151 ));
5152
5153 let width_source = resolved
5154 .provenance
5155 .get("width")
5156 .expect("width provenance should exist");
5157 assert!(matches!(
5158 width_source.source,
5159 RuntimeSettingsSource::ComponentSettings
5160 ));
5161 assert!(matches!(
5162 width_source.selector,
5163 Some(pax_manifest::SelectorExpr::Type(ref selector)) if selector == "Text"
5164 ));
5165
5166 let opacity_source = resolved
5167 .provenance
5168 .get("opacity")
5169 .expect("opacity provenance should exist");
5170 assert!(matches!(
5171 opacity_source.source,
5172 RuntimeSettingsSource::ImportedLayer { provider_id, .. } if provider_id == ExpandedNodeIdentifier(11)
5173 ));
5174 assert!(matches!(
5175 opacity_source.selector,
5176 Some(pax_manifest::SelectorExpr::Class(ref selector)) if selector == "headline"
5177 ));
5178
5179 let fill_source = resolved
5180 .provenance
5181 .get("fill")
5182 .expect("fill provenance should exist");
5183 assert!(matches!(fill_source.source, RuntimeSettingsSource::Inline));
5184 assert!(fill_source.selector.is_none());
5185 }
5186
5187 #[test]
5188 fn selector_cascade_is_type_then_classes_then_id_then_inline() {
5189 let mut node = TemplateNodeDefinition {
5190 type_id: TypeId::build_singleton("example::Text", Some("Text")),
5191 settings: Some(vec![
5192 SettingElement::Setting(
5193 Token::new_without_location("id".to_string()),
5194 ValueDefinition::Identifier(PaxIdentifier::new("hero")),
5195 ),
5196 setting("fill", 5),
5197 ]),
5198 ..Default::default()
5199 };
5200 node.normalize_selector_info();
5201 let component_settings = Some(vec![
5202 selector_block("Text", vec![setting("fill", 1)]),
5203 selector_block(".base", vec![setting("fill", 2)]),
5204 selector_block(".selected", vec![setting("fill", 3)]),
5205 selector_block("#hero", vec![setting("fill", 4)]),
5206 ]);
5207
5208 let resolved = resolve_runtime_settings_with_layers_for_node(
5209 &node,
5210 &BTreeMap::new(),
5211 &component_settings,
5212 &[],
5213 &["base".to_string(), "selected".to_string()],
5214 None,
5215 &[],
5216 );
5217 let values = resolved.columns["fill"]
5218 .iter()
5219 .map(|entry| match &entry.value {
5220 ValueDefinition::LiteralValue(PaxValue::Numeric(value)) => *value,
5221 other => panic!("unexpected fill value: {other:?}"),
5222 })
5223 .collect::<Vec<_>>();
5224
5225 assert_eq!(
5226 values,
5227 vec![1.into(), 2.into(), 3.into(), 4.into(), 5.into()]
5228 );
5229 }
5230
5231 #[test]
5232 fn lifecycle_timeline_uses_classes_captured_at_transition_start() {
5233 let node = TemplateNodeDefinition {
5234 type_id: TypeId::build_singleton("example::Rectangle", Some("Rectangle")),
5235 ..Default::default()
5236 };
5237 let component_settings = Some(vec![SettingsBlockElement::Transition(
5238 Token::new_without_location("in".to_string()),
5239 Token::new_without_location("appear".to_string()),
5240 )]);
5241 let timelines = vec![TimelineDefinition {
5242 name: Some(Token::new_without_location("appear".to_string())),
5243 elements: vec![TimelineBlockElement::SelectorBlock(
5244 Token::new_without_location(".active".to_string()),
5245 TimelineSelectorBlockDefinition {
5246 elements: vec![TimelineSelectorElement::Track(
5247 Token::new_without_location("opacity".to_string()),
5248 TimelineTrackDefinition::default(),
5249 )],
5250 },
5251 )],
5252 ..Default::default()
5253 }];
5254
5255 let resolved = resolve_runtime_settings_with_layers_for_node(
5256 &node,
5257 &BTreeMap::new(),
5258 &component_settings,
5259 &[],
5260 &[],
5261 Some(&["active".to_string()]),
5262 &timelines,
5263 );
5264
5265 assert!(matches!(
5266 resolved.defined_properties.get("opacity"),
5267 Some(ValueDefinition::Transition(_))
5268 ));
5269 }
5270
5271 #[test]
5272 fn removed_runtime_properties_are_explicitly_reset_only_when_requested() {
5273 let previous =
5274 BTreeMap::from([("fill".to_string(), vec![]), ("width".to_string(), vec![])]);
5275 let mut current = BTreeMap::from([("width".to_string(), vec![])]);
5276
5277 add_removed_property_resets(&previous, &mut current);
5278
5279 assert!(current.contains_key("fill"));
5280 assert!(current.contains_key("width"));
5281 }
5282}