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