1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use super::kit::{NodeKitContentDensity, NodeKitLayoutHints};
5use jellyflow_core::core::{
6 CanvasPoint, CanvasSize, Node, NodeId, NodeKindKey, Port, PortCapacity, PortDirection, PortId,
7 PortKey, PortKind,
8};
9use jellyflow_core::ops::{GraphOp, GraphTransaction};
10use jellyflow_core::types::TypeDesc;
11
12fn port_view_descriptor_is_default(value: &PortViewDescriptor) -> bool {
13 value.is_default()
14}
15
16fn is_false(value: &bool) -> bool {
17 !*value
18}
19
20fn node_surface_layout_budget_is_empty(value: &NodeSurfaceLayoutBudget) -> bool {
21 value.is_empty()
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct PortDecl {
27 pub key: PortKey,
29 pub dir: PortDirection,
31 pub kind: PortKind,
33 pub capacity: PortCapacity,
35 #[serde(default, skip_serializing_if = "Option::is_none")]
37 pub ty: Option<TypeDesc>,
38 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub label: Option<String>,
41 #[serde(default, skip_serializing_if = "port_view_descriptor_is_default")]
43 pub view: PortViewDescriptor,
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
48#[serde(rename_all = "snake_case")]
49pub enum PortViewSide {
50 Top,
51 Right,
52 Bottom,
53 Left,
54}
55
56impl PortViewSide {
57 pub fn fallback_for_direction(dir: PortDirection) -> Self {
58 match dir {
59 PortDirection::In => Self::Left,
60 PortDirection::Out => Self::Right,
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
67#[serde(rename_all = "snake_case")]
68pub enum PortHandleVisibility {
69 Visible,
70 Hidden,
71 Collapsed,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
76pub struct PortViewDescriptor {
77 #[serde(default, skip_serializing_if = "Option::is_none")]
79 pub side: Option<PortViewSide>,
80 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub order: Option<i32>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
85 pub group: Option<String>,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub anchor: Option<String>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
91 pub lane: Option<String>,
92 #[serde(default, skip_serializing_if = "Option::is_none")]
94 pub slot: Option<String>,
95 #[serde(default, skip_serializing_if = "Option::is_none")]
97 pub label: Option<String>,
98 #[serde(default, skip_serializing_if = "Option::is_none")]
100 pub icon_key: Option<String>,
101 #[serde(default, skip_serializing_if = "Option::is_none")]
103 pub visibility: Option<PortHandleVisibility>,
104}
105
106impl PortViewDescriptor {
107 pub fn new() -> Self {
108 Self::default()
109 }
110
111 pub fn side(side: PortViewSide) -> Self {
112 Self {
113 side: Some(side),
114 ..Self::default()
115 }
116 }
117
118 pub fn top() -> Self {
119 Self::side(PortViewSide::Top)
120 }
121
122 pub fn right() -> Self {
123 Self::side(PortViewSide::Right)
124 }
125
126 pub fn bottom() -> Self {
127 Self::side(PortViewSide::Bottom)
128 }
129
130 pub fn left() -> Self {
131 Self::side(PortViewSide::Left)
132 }
133
134 pub fn with_order(mut self, order: i32) -> Self {
135 self.order = Some(order);
136 self
137 }
138
139 pub fn with_group(mut self, group: impl Into<String>) -> Self {
140 self.group = Some(group.into());
141 self
142 }
143
144 pub fn with_anchor(mut self, anchor: impl Into<String>) -> Self {
145 self.anchor = Some(anchor.into());
146 self
147 }
148
149 pub fn with_lane(mut self, lane: impl Into<String>) -> Self {
150 self.lane = Some(lane.into());
151 self
152 }
153
154 pub fn with_slot(mut self, slot: impl Into<String>) -> Self {
155 self.slot = Some(slot.into());
156 self
157 }
158
159 pub fn with_label(mut self, label: impl Into<String>) -> Self {
160 self.label = Some(label.into());
161 self
162 }
163
164 pub fn with_icon_key(mut self, icon_key: impl Into<String>) -> Self {
165 self.icon_key = Some(icon_key.into());
166 self
167 }
168
169 pub fn with_visibility(mut self, visibility: PortHandleVisibility) -> Self {
170 self.visibility = Some(visibility);
171 self
172 }
173
174 pub fn hidden(self) -> Self {
175 self.with_visibility(PortHandleVisibility::Hidden)
176 }
177
178 pub fn collapsed(self) -> Self {
179 self.with_visibility(PortHandleVisibility::Collapsed)
180 }
181
182 pub fn is_visible(&self) -> bool {
183 matches!(self.visibility, None | Some(PortHandleVisibility::Visible))
184 }
185
186 pub fn is_hidden(&self) -> bool {
187 matches!(self.visibility, Some(PortHandleVisibility::Hidden))
188 }
189
190 pub fn is_collapsed(&self) -> bool {
191 matches!(self.visibility, Some(PortHandleVisibility::Collapsed))
192 }
193
194 pub fn is_hidden_or_collapsed(&self) -> bool {
195 !self.is_visible()
196 }
197
198 pub fn resolved_side(&self, dir: PortDirection) -> PortViewSide {
199 self.side
200 .unwrap_or_else(|| PortViewSide::fallback_for_direction(dir))
201 }
202
203 pub fn is_default(&self) -> bool {
204 self == &Self::default()
205 }
206}
207
208#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
210#[serde(rename_all = "snake_case")]
211pub enum NodeSurfaceSlotKind {
212 Header,
213 Body,
214 Footer,
215 Badge,
216 Icon,
217 FieldRow,
218 ActionRow,
219 Preview,
220 NestedRegion,
221 StatusBanner,
222 PortRail,
223 ConfigGroup,
224 MetricBadge,
225}
226
227#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
229#[serde(rename_all = "snake_case")]
230pub enum NodeControlKind {
231 TextInput,
232 NumberInput,
233 Select,
234 MultiSelect,
235 Toggle,
236 Code,
237 Color,
238 Asset,
239 VariablePicker,
240 Expression,
241 TextArea,
242 Slider,
243 PortBinding,
244}
245
246#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
248pub struct NodeControlBinding {
249 pub source: NodeControlBindingSource,
250 pub path: String,
251}
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
255#[serde(rename_all = "snake_case")]
256pub enum NodeControlBindingSource {
257 DataPath,
258 Slot,
259 JsonPointer,
260 GraphSymbol,
261 PortAnchor,
262}
263
264impl NodeControlBinding {
265 pub fn new(source: NodeControlBindingSource, path: impl Into<String>) -> Self {
266 Self {
267 source,
268 path: path.into(),
269 }
270 }
271
272 pub fn data_path(path: impl Into<String>) -> Self {
273 Self::new(NodeControlBindingSource::DataPath, path)
274 }
275
276 pub fn slot(path: impl Into<String>) -> Self {
277 Self::new(NodeControlBindingSource::Slot, path)
278 }
279
280 pub fn json_pointer(path: impl Into<String>) -> Self {
281 Self::new(NodeControlBindingSource::JsonPointer, path)
282 }
283
284 pub fn graph_symbol(path: impl Into<String>) -> Self {
285 Self::new(NodeControlBindingSource::GraphSymbol, path)
286 }
287
288 pub fn port_anchor(path: impl Into<String>) -> Self {
289 Self::new(NodeControlBindingSource::PortAnchor, path)
290 }
291}
292
293#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
295pub struct NodeControlOption {
296 pub value: Value,
297 pub label: String,
298 #[serde(default, skip_serializing_if = "Option::is_none")]
299 pub icon_key: Option<String>,
300 #[serde(default, skip_serializing_if = "is_false")]
301 pub disabled: bool,
302}
303
304impl NodeControlOption {
305 pub fn new(value: impl Into<Value>, label: impl Into<String>) -> Self {
306 Self {
307 value: value.into(),
308 label: label.into(),
309 icon_key: None,
310 disabled: false,
311 }
312 }
313
314 pub fn with_icon_key(mut self, icon_key: impl Into<String>) -> Self {
315 self.icon_key = Some(icon_key.into());
316 self
317 }
318
319 pub fn disabled(mut self) -> Self {
320 self.disabled = true;
321 self
322 }
323}
324
325#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
327#[serde(rename_all = "snake_case", tag = "kind", content = "key")]
328pub enum NodeControlOptionSource {
329 #[default]
330 Inline,
331 Variables,
332 Assets,
333 Ports,
334 Custom(String),
335}
336
337fn node_control_option_source_is_default(value: &NodeControlOptionSource) -> bool {
338 value == &NodeControlOptionSource::Inline
339}
340
341#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
343#[serde(rename_all = "snake_case", tag = "kind")]
344pub enum NodeControlValidationRule {
345 Required,
346 EnumValues { values: Vec<Value> },
347 Regex { pattern: String },
348 Range { min: Option<f64>, max: Option<f64> },
349 ExpressionShape { language: Option<String> },
350}
351
352#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
354pub struct NodeControlValidation {
355 #[serde(default, skip_serializing_if = "Vec::is_empty")]
356 pub rules: Vec<NodeControlValidationRule>,
357 #[serde(default, skip_serializing_if = "Vec::is_empty")]
358 pub messages: Vec<String>,
359}
360
361impl NodeControlValidation {
362 pub fn with_rule(mut self, rule: NodeControlValidationRule) -> Self {
363 self.rules.push(rule);
364 self
365 }
366
367 pub fn with_message(mut self, message: impl Into<String>) -> Self {
368 self.messages.push(message.into());
369 self
370 }
371
372 pub fn required(mut self) -> Self {
373 self.rules.push(NodeControlValidationRule::Required);
374 self
375 }
376
377 pub fn is_empty(&self) -> bool {
378 self.rules.is_empty() && self.messages.is_empty()
379 }
380}
381
382fn node_control_validation_is_empty(value: &NodeControlValidation) -> bool {
383 value.is_empty()
384}
385
386#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
388pub struct NodeControlPresentation {
389 #[serde(default, skip_serializing_if = "Option::is_none")]
390 pub placeholder: Option<String>,
391 #[serde(default, skip_serializing_if = "Option::is_none")]
392 pub help_text: Option<String>,
393 #[serde(default, skip_serializing_if = "Option::is_none")]
394 pub icon_key: Option<String>,
395 #[serde(default, skip_serializing_if = "Option::is_none")]
396 pub unit: Option<String>,
397 #[serde(default, skip_serializing_if = "Option::is_none")]
398 pub language: Option<String>,
399 #[serde(default, skip_serializing_if = "is_false")]
400 pub compact_label: bool,
401 #[serde(default, skip_serializing_if = "is_false")]
402 pub multiline: bool,
403}
404
405impl NodeControlPresentation {
406 pub fn with_placeholder(mut self, placeholder: impl Into<String>) -> Self {
407 self.placeholder = Some(placeholder.into());
408 self
409 }
410
411 pub fn with_help_text(mut self, help_text: impl Into<String>) -> Self {
412 self.help_text = Some(help_text.into());
413 self
414 }
415
416 pub fn with_icon_key(mut self, icon_key: impl Into<String>) -> Self {
417 self.icon_key = Some(icon_key.into());
418 self
419 }
420
421 pub fn with_unit(mut self, unit: impl Into<String>) -> Self {
422 self.unit = Some(unit.into());
423 self
424 }
425
426 pub fn with_language(mut self, language: impl Into<String>) -> Self {
427 self.language = Some(language.into());
428 self
429 }
430
431 pub fn compact_label(mut self) -> Self {
432 self.compact_label = true;
433 self
434 }
435
436 pub fn multiline(mut self) -> Self {
437 self.multiline = true;
438 self
439 }
440
441 pub fn is_empty(&self) -> bool {
442 self == &Self::default()
443 }
444}
445
446fn node_control_presentation_is_empty(value: &NodeControlPresentation) -> bool {
447 value.is_empty()
448}
449
450#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
452pub struct NodeControlEditability {
453 #[serde(default, skip_serializing_if = "is_false")]
454 pub read_only: bool,
455 #[serde(default, skip_serializing_if = "Option::is_none")]
456 pub disabled_reason: Option<String>,
457 #[serde(default, skip_serializing_if = "is_false")]
458 pub secret: bool,
459}
460
461impl NodeControlEditability {
462 pub fn read_only(mut self) -> Self {
463 self.read_only = true;
464 self
465 }
466
467 pub fn disabled(mut self, reason: impl Into<String>) -> Self {
468 self.disabled_reason = Some(reason.into());
469 self
470 }
471
472 pub fn secret(mut self) -> Self {
473 self.secret = true;
474 self
475 }
476
477 pub fn is_disabled(&self) -> bool {
478 self.disabled_reason.is_some()
479 }
480
481 pub fn is_empty(&self) -> bool {
482 self == &Self::default()
483 }
484}
485
486fn node_control_editability_is_empty(value: &NodeControlEditability) -> bool {
487 value.is_empty()
488}
489
490#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
492pub struct NodeControlDescriptor {
493 pub key: String,
494 pub kind: NodeControlKind,
495 #[serde(default, skip_serializing_if = "Option::is_none")]
496 pub label: Option<String>,
497 #[serde(default, skip_serializing_if = "Option::is_none")]
498 pub binding: Option<NodeControlBinding>,
499 #[serde(default, skip_serializing_if = "Vec::is_empty")]
500 pub options: Vec<NodeControlOption>,
501 #[serde(default, skip_serializing_if = "node_control_option_source_is_default")]
502 pub option_source: NodeControlOptionSource,
503 #[serde(default, skip_serializing_if = "node_control_validation_is_empty")]
504 pub validation: NodeControlValidation,
505 #[serde(default, skip_serializing_if = "node_control_presentation_is_empty")]
506 pub presentation: NodeControlPresentation,
507 #[serde(default, skip_serializing_if = "node_control_editability_is_empty")]
508 pub editability: NodeControlEditability,
509 #[serde(default, skip_serializing_if = "Option::is_none")]
510 pub order: Option<i32>,
511 #[serde(default, skip_serializing_if = "Option::is_none")]
512 pub anchor: Option<String>,
513 #[serde(default, skip_serializing_if = "Option::is_none")]
514 pub slot: Option<String>,
515}
516
517impl NodeControlDescriptor {
518 pub fn new(key: impl Into<String>, kind: NodeControlKind) -> Self {
519 Self {
520 key: key.into(),
521 kind,
522 label: None,
523 binding: None,
524 options: Vec::new(),
525 option_source: NodeControlOptionSource::Inline,
526 validation: NodeControlValidation::default(),
527 presentation: NodeControlPresentation::default(),
528 editability: NodeControlEditability::default(),
529 order: None,
530 anchor: None,
531 slot: None,
532 }
533 }
534
535 pub fn text_input(key: impl Into<String>) -> Self {
536 Self::new(key, NodeControlKind::TextInput)
537 }
538
539 pub fn number_input(key: impl Into<String>) -> Self {
540 Self::new(key, NodeControlKind::NumberInput)
541 }
542
543 pub fn select(key: impl Into<String>) -> Self {
544 Self::new(key, NodeControlKind::Select)
545 }
546
547 pub fn multi_select(key: impl Into<String>) -> Self {
548 Self::new(key, NodeControlKind::MultiSelect)
549 }
550
551 pub fn toggle(key: impl Into<String>) -> Self {
552 Self::new(key, NodeControlKind::Toggle)
553 }
554
555 pub fn code(key: impl Into<String>) -> Self {
556 Self::new(key, NodeControlKind::Code)
557 }
558
559 pub fn color(key: impl Into<String>) -> Self {
560 Self::new(key, NodeControlKind::Color)
561 }
562
563 pub fn asset(key: impl Into<String>) -> Self {
564 Self::new(key, NodeControlKind::Asset)
565 }
566
567 pub fn variable_picker(key: impl Into<String>) -> Self {
568 Self::new(key, NodeControlKind::VariablePicker)
569 }
570
571 pub fn expression(key: impl Into<String>) -> Self {
572 Self::new(key, NodeControlKind::Expression)
573 }
574
575 pub fn text_area(key: impl Into<String>) -> Self {
576 Self::new(key, NodeControlKind::TextArea)
577 .with_presentation(NodeControlPresentation::default().multiline())
578 }
579
580 pub fn slider(key: impl Into<String>) -> Self {
581 Self::new(key, NodeControlKind::Slider)
582 }
583
584 pub fn port_binding(key: impl Into<String>) -> Self {
585 Self::new(key, NodeControlKind::PortBinding)
586 }
587
588 pub fn with_label(mut self, label: impl Into<String>) -> Self {
589 self.label = Some(label.into());
590 self
591 }
592
593 pub fn with_binding(mut self, binding: NodeControlBinding) -> Self {
594 self.binding = Some(binding);
595 self
596 }
597
598 pub fn with_option(mut self, option: NodeControlOption) -> Self {
599 self.options.push(option);
600 self
601 }
602
603 pub fn with_options(mut self, options: impl IntoIterator<Item = NodeControlOption>) -> Self {
604 self.options.extend(options);
605 self
606 }
607
608 pub fn with_option_source(mut self, option_source: NodeControlOptionSource) -> Self {
609 self.option_source = option_source;
610 self
611 }
612
613 pub fn with_validation(mut self, validation: NodeControlValidation) -> Self {
614 self.validation = validation;
615 self
616 }
617
618 pub fn with_validation_rule(mut self, rule: NodeControlValidationRule) -> Self {
619 self.validation.rules.push(rule);
620 self
621 }
622
623 pub fn required(self) -> Self {
624 self.with_validation_rule(NodeControlValidationRule::Required)
625 }
626
627 pub fn with_presentation(mut self, presentation: NodeControlPresentation) -> Self {
628 self.presentation = presentation;
629 self
630 }
631
632 pub fn with_placeholder(mut self, placeholder: impl Into<String>) -> Self {
633 self.presentation.placeholder = Some(placeholder.into());
634 self
635 }
636
637 pub fn with_help_text(mut self, help_text: impl Into<String>) -> Self {
638 self.presentation.help_text = Some(help_text.into());
639 self
640 }
641
642 pub fn with_language(mut self, language: impl Into<String>) -> Self {
643 self.presentation.language = Some(language.into());
644 self
645 }
646
647 pub fn with_editability(mut self, editability: NodeControlEditability) -> Self {
648 self.editability = editability;
649 self
650 }
651
652 pub fn read_only(mut self) -> Self {
653 self.editability.read_only = true;
654 self
655 }
656
657 pub fn disabled(mut self, reason: impl Into<String>) -> Self {
658 self.editability.disabled_reason = Some(reason.into());
659 self
660 }
661
662 pub fn secret(mut self) -> Self {
663 self.editability.secret = true;
664 self
665 }
666
667 pub fn with_order(mut self, order: i32) -> Self {
668 self.order = Some(order);
669 self
670 }
671
672 pub fn with_anchor(mut self, anchor: impl Into<String>) -> Self {
673 self.anchor = Some(anchor.into());
674 self
675 }
676
677 pub fn with_slot(mut self, slot: impl Into<String>) -> Self {
678 self.slot = Some(slot.into());
679 self
680 }
681
682 pub fn data_key(&self) -> Option<&str> {
683 self.binding
684 .as_ref()
685 .and_then(|binding| match binding.source {
686 NodeControlBindingSource::DataPath | NodeControlBindingSource::Slot => {
687 Some(binding.path.as_str())
688 }
689 NodeControlBindingSource::JsonPointer
690 | NodeControlBindingSource::GraphSymbol
691 | NodeControlBindingSource::PortAnchor => None,
692 })
693 .or(self.slot.as_deref())
694 }
695
696 pub fn display_label(&self) -> Option<&str> {
697 self.label.as_deref().or_else(|| {
698 self.key
699 .split_once('.')
700 .map(|(_, tail)| tail)
701 .or(Some(self.key.as_str()))
702 })
703 }
704
705 pub fn order_key(&self) -> i32 {
706 self.order.unwrap_or(i32::MAX)
707 }
708}
709
710#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
712#[serde(rename_all = "snake_case")]
713pub enum NodeSurfaceOverflowIndicator {
714 Summary,
715 Count,
716 Scroll,
717 Fade,
718}
719
720#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
722pub struct NodeSurfaceLayoutBudget {
723 #[serde(default, skip_serializing_if = "Option::is_none")]
725 pub min_readable_size: Option<CanvasSize>,
726 #[serde(default, skip_serializing_if = "Option::is_none")]
728 pub preferred_size: Option<CanvasSize>,
729 #[serde(default, skip_serializing_if = "Option::is_none")]
731 pub slot_line_budget: Option<usize>,
732 #[serde(default, skip_serializing_if = "Option::is_none")]
734 pub control_line_budget: Option<usize>,
735 #[serde(default, skip_serializing_if = "Option::is_none")]
737 pub repeatable_visible_items: Option<usize>,
738 #[serde(default, skip_serializing_if = "Option::is_none")]
740 pub overflow_indicator: Option<NodeSurfaceOverflowIndicator>,
741 #[serde(default, skip_serializing_if = "Vec::is_empty")]
743 pub density_priority: Vec<NodeKitContentDensity>,
744}
745
746impl NodeSurfaceLayoutBudget {
747 pub fn with_min_readable_size(mut self, size: CanvasSize) -> Self {
748 self.min_readable_size = Some(size);
749 self
750 }
751
752 pub fn with_preferred_size(mut self, size: CanvasSize) -> Self {
753 self.preferred_size = Some(size);
754 self
755 }
756
757 pub fn with_slot_line_budget(mut self, lines: usize) -> Self {
758 self.slot_line_budget = Some(lines);
759 self
760 }
761
762 pub fn with_control_line_budget(mut self, lines: usize) -> Self {
763 self.control_line_budget = Some(lines);
764 self
765 }
766
767 pub fn with_repeatable_visible_items(mut self, items: usize) -> Self {
768 self.repeatable_visible_items = Some(items);
769 self
770 }
771
772 pub fn with_overflow_indicator(mut self, indicator: NodeSurfaceOverflowIndicator) -> Self {
773 self.overflow_indicator = Some(indicator);
774 self
775 }
776
777 pub fn with_density_priority(
778 mut self,
779 density_priority: impl IntoIterator<Item = NodeKitContentDensity>,
780 ) -> Self {
781 self.density_priority = density_priority.into_iter().collect();
782 self
783 }
784
785 pub fn is_empty(&self) -> bool {
786 self == &Self::default()
787 }
788}
789
790#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
792pub struct NodeRepeatableCollectionDescriptor {
793 pub key: String,
794 pub item_source: String,
795 pub item_id_path: String,
796 #[serde(default, skip_serializing_if = "Vec::is_empty")]
797 pub item_template_slots: Vec<NodeSurfaceSlotDescriptor>,
798 #[serde(default, skip_serializing_if = "Option::is_none")]
799 pub label: Option<String>,
800 #[serde(default, skip_serializing_if = "Option::is_none")]
801 pub empty_label: Option<String>,
802 #[serde(default, skip_serializing_if = "Option::is_none")]
803 pub min_items: Option<usize>,
804 #[serde(default, skip_serializing_if = "Option::is_none")]
805 pub max_items: Option<usize>,
806 #[serde(default, skip_serializing_if = "is_false")]
807 pub reorderable: bool,
808 #[serde(default, skip_serializing_if = "Option::is_none")]
809 pub add_action: Option<String>,
810 #[serde(default, skip_serializing_if = "Option::is_none")]
811 pub remove_action: Option<String>,
812 #[serde(default, skip_serializing_if = "Option::is_none")]
813 pub reorder_action: Option<String>,
814 #[serde(default)]
815 pub anchor_rule: NodeRepeatableAnchorRule,
816}
817
818#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
820pub struct NodeRepeatableAnchorRule {
821 pub slot_prefix: String,
822 pub anchor_prefix: String,
823 #[serde(default, skip_serializing_if = "Option::is_none")]
824 pub port_key_path: Option<String>,
825}
826
827impl Default for NodeRepeatableAnchorRule {
828 fn default() -> Self {
829 Self {
830 slot_prefix: "item".to_owned(),
831 anchor_prefix: "item".to_owned(),
832 port_key_path: None,
833 }
834 }
835}
836
837#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
839pub struct NodeRepeatableItemProjection {
840 pub collection_key: String,
841 pub item_id: String,
842 pub item_index: usize,
843 pub slot_key: String,
844 pub anchor: String,
845 #[serde(default, skip_serializing_if = "Option::is_none")]
846 pub port_key: Option<String>,
847 #[serde(default)]
848 pub item_data: Value,
849 #[serde(default, skip_serializing_if = "Vec::is_empty")]
850 pub slots: Vec<NodeSurfaceSlotDescriptor>,
851}
852
853impl NodeRepeatableCollectionDescriptor {
854 pub fn new(
855 key: impl Into<String>,
856 item_source: impl Into<String>,
857 item_id_path: impl Into<String>,
858 ) -> Self {
859 let key = key.into();
860 Self {
861 anchor_rule: NodeRepeatableAnchorRule {
862 slot_prefix: key.clone(),
863 anchor_prefix: key.clone(),
864 port_key_path: None,
865 },
866 key,
867 item_source: item_source.into(),
868 item_id_path: item_id_path.into(),
869 item_template_slots: Vec::new(),
870 label: None,
871 empty_label: None,
872 min_items: None,
873 max_items: None,
874 reorderable: false,
875 add_action: None,
876 remove_action: None,
877 reorder_action: None,
878 }
879 }
880
881 pub fn with_label(mut self, label: impl Into<String>) -> Self {
882 self.label = Some(label.into());
883 self
884 }
885
886 pub fn with_empty_label(mut self, empty_label: impl Into<String>) -> Self {
887 self.empty_label = Some(empty_label.into());
888 self
889 }
890
891 pub fn with_item_template_slot(mut self, slot: NodeSurfaceSlotDescriptor) -> Self {
892 self.item_template_slots.push(slot);
893 self
894 }
895
896 pub fn with_item_template_slots(
897 mut self,
898 slots: impl IntoIterator<Item = NodeSurfaceSlotDescriptor>,
899 ) -> Self {
900 self.item_template_slots.extend(slots);
901 self
902 }
903
904 pub fn with_min_items(mut self, min_items: usize) -> Self {
905 self.min_items = Some(min_items);
906 self
907 }
908
909 pub fn with_max_items(mut self, max_items: usize) -> Self {
910 self.max_items = Some(max_items);
911 self
912 }
913
914 pub fn reorderable(mut self) -> Self {
915 self.reorderable = true;
916 self
917 }
918
919 pub fn with_add_action(mut self, action: impl Into<String>) -> Self {
920 self.add_action = Some(action.into());
921 self
922 }
923
924 pub fn with_remove_action(mut self, action: impl Into<String>) -> Self {
925 self.remove_action = Some(action.into());
926 self
927 }
928
929 pub fn with_reorder_action(mut self, action: impl Into<String>) -> Self {
930 self.reorder_action = Some(action.into());
931 self
932 }
933
934 pub fn with_anchor_rule(mut self, anchor_rule: NodeRepeatableAnchorRule) -> Self {
935 self.anchor_rule = anchor_rule;
936 self
937 }
938
939 pub fn item_projections(&self, node_data: &Value) -> Vec<NodeRepeatableItemProjection> {
940 let Some(items) = semantic_json_lookup(node_data, &self.item_source) else {
941 return Vec::new();
942 };
943
944 match items {
945 Value::Array(items) => items
946 .iter()
947 .enumerate()
948 .filter_map(|(index, item)| self.item_projection(index, item))
949 .collect(),
950 Value::Object(items) => items
951 .iter()
952 .enumerate()
953 .filter_map(|(index, (key, item))| {
954 self.item_projection_with_fallback_id(index, item, key.as_str())
955 })
956 .collect(),
957 _ => Vec::new(),
958 }
959 }
960
961 pub fn is_empty_for(&self, node_data: &Value) -> bool {
962 self.item_projections(node_data).is_empty()
963 }
964
965 pub fn add_disabled_reason(&self, node_data: &Value) -> Option<String> {
966 let max_items = self.max_items?;
967 let len = self.item_projections(node_data).len();
968 (len >= max_items).then(|| format!("Maximum of {max_items} items reached"))
969 }
970
971 pub fn remove_disabled_reason(&self, node_data: &Value) -> Option<String> {
972 let min_items = self.min_items?;
973 let len = self.item_projections(node_data).len();
974 (len <= min_items).then(|| format!("Minimum of {min_items} items required"))
975 }
976
977 fn item_projection(
978 &self,
979 item_index: usize,
980 item_data: &Value,
981 ) -> Option<NodeRepeatableItemProjection> {
982 let item_id = semantic_json_lookup(item_data, &self.item_id_path)
983 .and_then(json_scalar_to_stable_string)?;
984 self.item_projection_with_id(item_index, item_data, &item_id)
985 }
986
987 fn item_projection_with_fallback_id(
988 &self,
989 item_index: usize,
990 item_data: &Value,
991 fallback_id: &str,
992 ) -> Option<NodeRepeatableItemProjection> {
993 let item_id = semantic_json_lookup(item_data, &self.item_id_path)
994 .and_then(json_scalar_to_stable_string)
995 .unwrap_or_else(|| fallback_id.to_owned());
996 self.item_projection_with_id(item_index, item_data, &item_id)
997 }
998
999 fn item_projection_with_id(
1000 &self,
1001 item_index: usize,
1002 item_data: &Value,
1003 item_id: &str,
1004 ) -> Option<NodeRepeatableItemProjection> {
1005 let item_id = sanitize_repeatable_key(item_id)?;
1006 let slot_key = format!("{}.{}", self.anchor_rule.slot_prefix, item_id);
1007 let anchor = format!("{}.{}", self.anchor_rule.anchor_prefix, item_id);
1008 let port_key = self
1009 .anchor_rule
1010 .port_key_path
1011 .as_deref()
1012 .and_then(|path| semantic_json_lookup(item_data, path))
1013 .and_then(json_scalar_to_stable_string);
1014 let slots = self
1015 .item_template_slots
1016 .iter()
1017 .map(|slot| repeatable_item_slot(slot, &slot_key, &anchor, item_id))
1018 .collect();
1019
1020 Some(NodeRepeatableItemProjection {
1021 collection_key: self.key.clone(),
1022 item_id: item_id.to_owned(),
1023 item_index,
1024 slot_key,
1025 anchor,
1026 port_key,
1027 item_data: item_data.clone(),
1028 slots,
1029 })
1030 }
1031}
1032
1033impl NodeRepeatableAnchorRule {
1034 pub fn new(slot_prefix: impl Into<String>, anchor_prefix: impl Into<String>) -> Self {
1035 Self {
1036 slot_prefix: slot_prefix.into(),
1037 anchor_prefix: anchor_prefix.into(),
1038 port_key_path: None,
1039 }
1040 }
1041
1042 pub fn with_port_key_path(mut self, port_key_path: impl Into<String>) -> Self {
1043 self.port_key_path = Some(port_key_path.into());
1044 self
1045 }
1046}
1047
1048fn repeatable_item_slot(
1049 template: &NodeSurfaceSlotDescriptor,
1050 slot_key: &str,
1051 anchor: &str,
1052 item_id: &str,
1053) -> NodeSurfaceSlotDescriptor {
1054 let suffix = template.key_tail().unwrap_or(template.key.as_str());
1055 let mut slot = template.clone();
1056 slot.key = format!("{slot_key}.{suffix}");
1057 slot.anchor = Some(format!("{anchor}.{suffix}"));
1058 if let Some(template_slot) = template.slot.as_deref() {
1059 slot.slot = Some(format!("{template_slot}.{item_id}"));
1060 }
1061 slot
1062}
1063
1064fn json_scalar_to_stable_string(value: &Value) -> Option<String> {
1065 match value {
1066 Value::String(value) => Some(value.clone()),
1067 Value::Number(value) => Some(value.to_string()),
1068 Value::Bool(value) => Some(value.to_string()),
1069 _ => None,
1070 }
1071}
1072
1073fn sanitize_repeatable_key(value: &str) -> Option<&str> {
1074 (!value.is_empty()
1075 && value
1076 .bytes()
1077 .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'.' | b'_' | b'-')))
1078 .then_some(value)
1079}
1080
1081#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1083#[serde(rename_all = "snake_case", tag = "kind")]
1084pub enum ActionTarget {
1085 Graph,
1086 Node {
1087 node_kind: String,
1088 },
1089 Edge,
1090 Port {
1091 port_key: String,
1092 },
1093 Slot {
1094 slot_key: String,
1095 },
1096 Control {
1097 control_key: String,
1098 },
1099 RepeatableItem {
1100 collection_key: String,
1101 item_id: String,
1102 },
1103 DroppedWire {
1104 #[serde(default, skip_serializing_if = "Option::is_none")]
1105 source_port_key: Option<String>,
1106 },
1107 Inspector {
1108 inspector_key: String,
1109 },
1110 Blackboard {
1111 blackboard_key: String,
1112 },
1113}
1114
1115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1117#[serde(rename_all = "snake_case", tag = "kind")]
1118pub enum ActionIntent {
1119 InsertNode {
1120 node_kind: String,
1121 },
1122 OpenMenu {
1123 menu_key: String,
1124 },
1125 OpenInspector {
1126 inspector_key: String,
1127 },
1128 AddRepeatableItem {
1129 collection_key: String,
1130 },
1131 RemoveRepeatableItem {
1132 collection_key: String,
1133 item_id: String,
1134 },
1135 ReorderRepeatableItem {
1136 collection_key: String,
1137 item_id: String,
1138 },
1139 SetControlValue {
1140 control_key: String,
1141 },
1142 RunNode,
1143 OpenBlackboard {
1144 blackboard_key: String,
1145 },
1146 Custom {
1147 key: String,
1148 },
1149}
1150
1151#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1153pub struct ActionAvailability {
1154 #[serde(default = "default_true")]
1155 pub enabled: bool,
1156 #[serde(default, skip_serializing_if = "Option::is_none")]
1157 pub disabled_reason: Option<String>,
1158}
1159
1160fn default_true() -> bool {
1161 true
1162}
1163
1164impl Default for ActionAvailability {
1165 fn default() -> Self {
1166 Self {
1167 enabled: true,
1168 disabled_reason: None,
1169 }
1170 }
1171}
1172
1173impl ActionAvailability {
1174 pub fn enabled() -> Self {
1175 Self::default()
1176 }
1177
1178 pub fn disabled(reason: impl Into<String>) -> Self {
1179 Self {
1180 enabled: false,
1181 disabled_reason: Some(reason.into()),
1182 }
1183 }
1184
1185 pub fn is_enabled(&self) -> bool {
1186 self.enabled
1187 }
1188}
1189
1190fn action_availability_is_default(value: &ActionAvailability) -> bool {
1191 value == &ActionAvailability::default()
1192}
1193
1194#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1196pub struct ActionShortcut {
1197 pub key: String,
1198 #[serde(default, skip_serializing_if = "is_false")]
1199 pub ctrl: bool,
1200 #[serde(default, skip_serializing_if = "is_false")]
1201 pub shift: bool,
1202 #[serde(default, skip_serializing_if = "is_false")]
1203 pub alt: bool,
1204 #[serde(default, skip_serializing_if = "is_false")]
1205 pub meta: bool,
1206}
1207
1208impl ActionShortcut {
1209 pub fn new(key: impl Into<String>) -> Self {
1210 Self {
1211 key: key.into(),
1212 ctrl: false,
1213 shift: false,
1214 alt: false,
1215 meta: false,
1216 }
1217 }
1218
1219 pub fn ctrl(mut self) -> Self {
1220 self.ctrl = true;
1221 self
1222 }
1223
1224 pub fn shift(mut self) -> Self {
1225 self.shift = true;
1226 self
1227 }
1228
1229 pub fn alt(mut self) -> Self {
1230 self.alt = true;
1231 self
1232 }
1233
1234 pub fn meta(mut self) -> Self {
1235 self.meta = true;
1236 self
1237 }
1238}
1239
1240#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1242pub struct NodeActionDescriptor {
1243 pub key: String,
1244 pub label: String,
1245 pub target: ActionTarget,
1246 pub intent: ActionIntent,
1247 #[serde(default, skip_serializing_if = "action_availability_is_default")]
1248 pub availability: ActionAvailability,
1249 #[serde(default, skip_serializing_if = "Option::is_none")]
1250 pub group: Option<String>,
1251 #[serde(default, skip_serializing_if = "Option::is_none")]
1252 pub order: Option<i32>,
1253 #[serde(default, skip_serializing_if = "is_false")]
1254 pub danger: bool,
1255 #[serde(default, skip_serializing_if = "Option::is_none")]
1256 pub icon_key: Option<String>,
1257 #[serde(default, skip_serializing_if = "Option::is_none")]
1258 pub shortcut: Option<ActionShortcut>,
1259}
1260
1261impl NodeActionDescriptor {
1262 pub fn new(
1263 key: impl Into<String>,
1264 label: impl Into<String>,
1265 target: ActionTarget,
1266 intent: ActionIntent,
1267 ) -> Self {
1268 Self {
1269 key: key.into(),
1270 label: label.into(),
1271 target,
1272 intent,
1273 availability: ActionAvailability::default(),
1274 group: None,
1275 order: None,
1276 danger: false,
1277 icon_key: None,
1278 shortcut: None,
1279 }
1280 }
1281
1282 pub fn disabled(mut self, reason: impl Into<String>) -> Self {
1283 self.availability = ActionAvailability::disabled(reason);
1284 self
1285 }
1286
1287 pub fn with_group(mut self, group: impl Into<String>) -> Self {
1288 self.group = Some(group.into());
1289 self
1290 }
1291
1292 pub fn with_order(mut self, order: i32) -> Self {
1293 self.order = Some(order);
1294 self
1295 }
1296
1297 pub fn danger(mut self) -> Self {
1298 self.danger = true;
1299 self
1300 }
1301
1302 pub fn with_icon_key(mut self, icon_key: impl Into<String>) -> Self {
1303 self.icon_key = Some(icon_key.into());
1304 self
1305 }
1306
1307 pub fn with_shortcut(mut self, shortcut: ActionShortcut) -> Self {
1308 self.shortcut = Some(shortcut);
1309 self
1310 }
1311
1312 pub fn is_enabled(&self) -> bool {
1313 self.availability.is_enabled()
1314 }
1315}
1316
1317#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1319#[serde(rename_all = "snake_case", tag = "kind")]
1320pub enum MenuSurface {
1321 Graph,
1322 Node,
1323 Edge,
1324 Port,
1325 Slot,
1326 Control,
1327 DroppedWire,
1328 Toolbar,
1329 Blackboard,
1330 Inspector,
1331}
1332
1333#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1335pub struct MenuDescriptor {
1336 pub key: String,
1337 pub surface: MenuSurface,
1338 #[serde(default, skip_serializing_if = "Option::is_none")]
1339 pub label: Option<String>,
1340 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1341 pub action_keys: Vec<String>,
1342}
1343
1344impl MenuDescriptor {
1345 pub fn new(key: impl Into<String>, surface: MenuSurface) -> Self {
1346 Self {
1347 key: key.into(),
1348 surface,
1349 label: None,
1350 action_keys: Vec::new(),
1351 }
1352 }
1353
1354 pub fn with_label(mut self, label: impl Into<String>) -> Self {
1355 self.label = Some(label.into());
1356 self
1357 }
1358
1359 pub fn with_action_key(mut self, action_key: impl Into<String>) -> Self {
1360 self.action_keys.push(action_key.into());
1361 self
1362 }
1363
1364 pub fn with_action_keys(
1365 mut self,
1366 action_keys: impl IntoIterator<Item = impl Into<String>>,
1367 ) -> Self {
1368 self.action_keys
1369 .extend(action_keys.into_iter().map(Into::into));
1370 self
1371 }
1372}
1373
1374#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1376#[serde(rename_all = "snake_case", tag = "kind")]
1377pub enum InspectorTarget {
1378 Graph,
1379 Node {
1380 node_kind: String,
1381 },
1382 Edge,
1383 Port {
1384 port_key: String,
1385 },
1386 Slot {
1387 slot_key: String,
1388 },
1389 Control {
1390 control_key: String,
1391 },
1392 RepeatableItem {
1393 collection_key: String,
1394 item_id: String,
1395 },
1396 Diagnostic {
1397 diagnostic_key: String,
1398 },
1399}
1400
1401#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1403pub struct InspectorDescriptor {
1404 pub key: String,
1405 pub target: InspectorTarget,
1406 #[serde(default, skip_serializing_if = "Option::is_none")]
1407 pub label: Option<String>,
1408 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1409 pub controls: Vec<NodeControlDescriptor>,
1410 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1411 pub action_keys: Vec<String>,
1412}
1413
1414impl InspectorDescriptor {
1415 pub fn new(key: impl Into<String>, target: InspectorTarget) -> Self {
1416 Self {
1417 key: key.into(),
1418 target,
1419 label: None,
1420 controls: Vec::new(),
1421 action_keys: Vec::new(),
1422 }
1423 }
1424
1425 pub fn with_label(mut self, label: impl Into<String>) -> Self {
1426 self.label = Some(label.into());
1427 self
1428 }
1429
1430 pub fn with_control(mut self, control: NodeControlDescriptor) -> Self {
1431 self.controls.push(control);
1432 self
1433 }
1434
1435 pub fn with_action_key(mut self, action_key: impl Into<String>) -> Self {
1436 self.action_keys.push(action_key.into());
1437 self
1438 }
1439}
1440
1441#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1443pub struct BlackboardDescriptor {
1444 pub key: String,
1445 pub label: String,
1446 pub collection: NodeRepeatableCollectionDescriptor,
1447 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1448 pub action_keys: Vec<String>,
1449}
1450
1451impl BlackboardDescriptor {
1452 pub fn new(
1453 key: impl Into<String>,
1454 label: impl Into<String>,
1455 collection: NodeRepeatableCollectionDescriptor,
1456 ) -> Self {
1457 Self {
1458 key: key.into(),
1459 label: label.into(),
1460 collection,
1461 action_keys: Vec::new(),
1462 }
1463 }
1464
1465 pub fn with_action_key(mut self, action_key: impl Into<String>) -> Self {
1466 self.action_keys.push(action_key.into());
1467 self
1468 }
1469}
1470
1471#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1473#[serde(rename_all = "snake_case")]
1474pub enum NodeSurfaceSlotVisibility {
1475 Visible,
1476 Hidden,
1477 Collapsed,
1478}
1479
1480#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
1482#[serde(rename_all = "snake_case")]
1483pub enum NodeChromeKind {
1484 Resizer,
1485 Toolbar,
1486 StatusStrip,
1487 ValidationBanner,
1488 RunActionStrip,
1489 InspectorAnchor,
1490}
1491
1492#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
1494#[serde(rename_all = "snake_case")]
1495pub enum NodeChromePlacement {
1496 Top,
1497 TopRight,
1498 Right,
1499 BottomRight,
1500 Bottom,
1501 BottomLeft,
1502 Left,
1503 TopLeft,
1504 InsideHeader,
1505 InsideFooter,
1506}
1507
1508#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1510#[serde(rename_all = "snake_case")]
1511pub enum NodeChromeVisibility {
1512 Always,
1513 Selected,
1514 Hovered,
1515 Focused,
1516 Hidden,
1517}
1518
1519#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1521pub struct NodeChromeDescriptor {
1522 pub key: String,
1523 pub kind: NodeChromeKind,
1524 pub placement: NodeChromePlacement,
1525 #[serde(default, skip_serializing_if = "Option::is_none")]
1526 pub label: Option<String>,
1527 #[serde(default, skip_serializing_if = "Option::is_none")]
1528 pub renderer_key: Option<String>,
1529 #[serde(default, skip_serializing_if = "Option::is_none")]
1530 pub icon_key: Option<String>,
1531 #[serde(default, skip_serializing_if = "Option::is_none")]
1532 pub visibility: Option<NodeChromeVisibility>,
1533 #[serde(default, skip_serializing_if = "Option::is_none")]
1534 pub order: Option<i32>,
1535 #[serde(default, skip_serializing_if = "is_false")]
1536 pub interactive: bool,
1537}
1538
1539impl NodeChromeDescriptor {
1540 pub fn new(
1541 key: impl Into<String>,
1542 kind: NodeChromeKind,
1543 placement: NodeChromePlacement,
1544 ) -> Self {
1545 Self {
1546 key: key.into(),
1547 kind,
1548 placement,
1549 label: None,
1550 renderer_key: None,
1551 icon_key: None,
1552 visibility: None,
1553 order: None,
1554 interactive: false,
1555 }
1556 }
1557
1558 pub fn resizer(key: impl Into<String>) -> Self {
1559 Self::new(
1560 key,
1561 NodeChromeKind::Resizer,
1562 NodeChromePlacement::BottomRight,
1563 )
1564 .interactive()
1565 .with_visibility(NodeChromeVisibility::Selected)
1566 }
1567
1568 pub fn toolbar(key: impl Into<String>, placement: NodeChromePlacement) -> Self {
1569 Self::new(key, NodeChromeKind::Toolbar, placement)
1570 .interactive()
1571 .with_visibility(NodeChromeVisibility::Selected)
1572 }
1573
1574 pub fn status_strip(key: impl Into<String>, placement: NodeChromePlacement) -> Self {
1575 Self::new(key, NodeChromeKind::StatusStrip, placement)
1576 .with_visibility(NodeChromeVisibility::Always)
1577 }
1578
1579 pub fn validation_banner(key: impl Into<String>, placement: NodeChromePlacement) -> Self {
1580 Self::new(key, NodeChromeKind::ValidationBanner, placement)
1581 .with_visibility(NodeChromeVisibility::Always)
1582 }
1583
1584 pub fn run_action_strip(key: impl Into<String>, placement: NodeChromePlacement) -> Self {
1585 Self::new(key, NodeChromeKind::RunActionStrip, placement)
1586 .interactive()
1587 .with_visibility(NodeChromeVisibility::Selected)
1588 }
1589
1590 pub fn inspector_anchor(key: impl Into<String>, placement: NodeChromePlacement) -> Self {
1591 Self::new(key, NodeChromeKind::InspectorAnchor, placement)
1592 .with_visibility(NodeChromeVisibility::Selected)
1593 }
1594
1595 pub fn with_label(mut self, label: impl Into<String>) -> Self {
1596 self.label = Some(label.into());
1597 self
1598 }
1599
1600 pub fn with_renderer_key(mut self, renderer_key: impl Into<String>) -> Self {
1601 self.renderer_key = Some(renderer_key.into());
1602 self
1603 }
1604
1605 pub fn with_icon_key(mut self, icon_key: impl Into<String>) -> Self {
1606 self.icon_key = Some(icon_key.into());
1607 self
1608 }
1609
1610 pub fn with_visibility(mut self, visibility: NodeChromeVisibility) -> Self {
1611 self.visibility = Some(visibility);
1612 self
1613 }
1614
1615 pub fn with_order(mut self, order: i32) -> Self {
1616 self.order = Some(order);
1617 self
1618 }
1619
1620 pub fn interactive(mut self) -> Self {
1621 self.interactive = true;
1622 self
1623 }
1624
1625 pub fn hidden(self) -> Self {
1626 self.with_visibility(NodeChromeVisibility::Hidden)
1627 }
1628
1629 pub fn effective_visibility(&self) -> NodeChromeVisibility {
1630 self.visibility.unwrap_or(NodeChromeVisibility::Always)
1631 }
1632
1633 pub fn is_visible_for_state(&self, selected: bool, hovered: bool, focused: bool) -> bool {
1634 match self.effective_visibility() {
1635 NodeChromeVisibility::Always => true,
1636 NodeChromeVisibility::Selected => selected,
1637 NodeChromeVisibility::Hovered => hovered,
1638 NodeChromeVisibility::Focused => focused,
1639 NodeChromeVisibility::Hidden => false,
1640 }
1641 }
1642}
1643
1644#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1646pub struct NodeSurfaceProjection {
1647 pub density: NodeKitContentDensity,
1649 pub slot_limit: usize,
1651 pub compact_values: bool,
1653 pub expand_all_slots: bool,
1655}
1656
1657impl NodeSurfaceProjection {
1658 pub fn from_layout_hints(layout_hints: &NodeKitLayoutHints, zoom: f32) -> Self {
1659 let density = layout_hints.content_density_for_zoom(zoom);
1660 let (slot_limit, compact_values, expand_all_slots) = match density {
1661 NodeKitContentDensity::Compact => (2, true, false),
1662 NodeKitContentDensity::Regular => (3, true, false),
1663 NodeKitContentDensity::Full => (usize::MAX, false, true),
1664 };
1665
1666 Self {
1667 density,
1668 slot_limit,
1669 compact_values,
1670 expand_all_slots,
1671 }
1672 }
1673}
1674
1675#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1677pub struct NodeSurfaceSlotDescriptor {
1678 pub key: String,
1680 pub kind: NodeSurfaceSlotKind,
1682 #[serde(default, skip_serializing_if = "Option::is_none")]
1684 pub label: Option<String>,
1685 #[serde(default, skip_serializing_if = "Option::is_none")]
1687 pub order: Option<i32>,
1688 #[serde(default, skip_serializing_if = "Option::is_none")]
1690 pub anchor: Option<String>,
1691 #[serde(default, skip_serializing_if = "Option::is_none")]
1693 pub lane: Option<String>,
1694 #[serde(default, skip_serializing_if = "Option::is_none")]
1696 pub slot: Option<String>,
1697 #[serde(default, skip_serializing_if = "Option::is_none")]
1699 pub renderer_key: Option<String>,
1700 #[serde(default, skip_serializing_if = "Option::is_none")]
1702 pub icon_key: Option<String>,
1703 #[serde(default, skip_serializing_if = "Option::is_none")]
1705 pub visibility: Option<NodeSurfaceSlotVisibility>,
1706 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1708 pub controls: Vec<NodeControlDescriptor>,
1709}
1710
1711impl NodeSurfaceSlotDescriptor {
1712 pub fn new(key: impl Into<String>, kind: NodeSurfaceSlotKind) -> Self {
1713 Self {
1714 key: key.into(),
1715 kind,
1716 label: None,
1717 order: None,
1718 anchor: None,
1719 lane: None,
1720 slot: None,
1721 renderer_key: None,
1722 icon_key: None,
1723 visibility: None,
1724 controls: Vec::new(),
1725 }
1726 }
1727
1728 pub fn header(key: impl Into<String>) -> Self {
1729 Self::new(key, NodeSurfaceSlotKind::Header)
1730 }
1731
1732 pub fn body(key: impl Into<String>) -> Self {
1733 Self::new(key, NodeSurfaceSlotKind::Body)
1734 }
1735
1736 pub fn footer(key: impl Into<String>) -> Self {
1737 Self::new(key, NodeSurfaceSlotKind::Footer)
1738 }
1739
1740 pub fn badge(key: impl Into<String>) -> Self {
1741 Self::new(key, NodeSurfaceSlotKind::Badge)
1742 }
1743
1744 pub fn icon(key: impl Into<String>) -> Self {
1745 Self::new(key, NodeSurfaceSlotKind::Icon)
1746 }
1747
1748 pub fn field_row(key: impl Into<String>) -> Self {
1749 Self::new(key, NodeSurfaceSlotKind::FieldRow)
1750 }
1751
1752 pub fn action_row(key: impl Into<String>) -> Self {
1753 Self::new(key, NodeSurfaceSlotKind::ActionRow)
1754 }
1755
1756 pub fn preview(key: impl Into<String>) -> Self {
1757 Self::new(key, NodeSurfaceSlotKind::Preview)
1758 }
1759
1760 pub fn nested_region(key: impl Into<String>) -> Self {
1761 Self::new(key, NodeSurfaceSlotKind::NestedRegion)
1762 }
1763
1764 pub fn status_banner(key: impl Into<String>) -> Self {
1765 Self::new(key, NodeSurfaceSlotKind::StatusBanner)
1766 }
1767
1768 pub fn port_rail(key: impl Into<String>) -> Self {
1769 Self::new(key, NodeSurfaceSlotKind::PortRail)
1770 }
1771
1772 pub fn config_group(key: impl Into<String>) -> Self {
1773 Self::new(key, NodeSurfaceSlotKind::ConfigGroup)
1774 }
1775
1776 pub fn metric_badge(key: impl Into<String>) -> Self {
1777 Self::new(key, NodeSurfaceSlotKind::MetricBadge)
1778 }
1779
1780 pub fn with_label(mut self, label: impl Into<String>) -> Self {
1781 self.label = Some(label.into());
1782 self
1783 }
1784
1785 pub fn with_order(mut self, order: i32) -> Self {
1786 self.order = Some(order);
1787 self
1788 }
1789
1790 pub fn with_anchor(mut self, anchor: impl Into<String>) -> Self {
1791 self.anchor = Some(anchor.into());
1792 self
1793 }
1794
1795 pub fn with_lane(mut self, lane: impl Into<String>) -> Self {
1796 self.lane = Some(lane.into());
1797 self
1798 }
1799
1800 pub fn with_slot(mut self, slot: impl Into<String>) -> Self {
1801 self.slot = Some(slot.into());
1802 self
1803 }
1804
1805 pub fn with_renderer_key(mut self, renderer_key: impl Into<String>) -> Self {
1806 self.renderer_key = Some(renderer_key.into());
1807 self
1808 }
1809
1810 pub fn with_icon_key(mut self, icon_key: impl Into<String>) -> Self {
1811 self.icon_key = Some(icon_key.into());
1812 self
1813 }
1814
1815 pub fn with_visibility(mut self, visibility: NodeSurfaceSlotVisibility) -> Self {
1816 self.visibility = Some(visibility);
1817 self
1818 }
1819
1820 pub fn with_control(mut self, control: NodeControlDescriptor) -> Self {
1821 self.controls.push(control);
1822 self
1823 }
1824
1825 pub fn with_controls(
1826 mut self,
1827 controls: impl IntoIterator<Item = NodeControlDescriptor>,
1828 ) -> Self {
1829 self.controls.extend(controls);
1830 self
1831 }
1832
1833 pub fn hidden(self) -> Self {
1834 self.with_visibility(NodeSurfaceSlotVisibility::Hidden)
1835 }
1836
1837 pub fn collapsed(self) -> Self {
1838 self.with_visibility(NodeSurfaceSlotVisibility::Collapsed)
1839 }
1840
1841 pub fn key_tail(&self) -> Option<&str> {
1842 self.key.split_once('.').map(|(_, tail)| tail)
1843 }
1844
1845 pub fn data_key(&self) -> Option<&str> {
1846 self.slot.as_deref().or_else(|| {
1847 (self.kind == NodeSurfaceSlotKind::FieldRow)
1848 .then(|| self.key_tail())
1849 .flatten()
1850 })
1851 }
1852
1853 pub fn display_label(&self) -> Option<&str> {
1854 self.label.as_deref().or_else(|| self.key_tail())
1855 }
1856
1857 pub fn order_key(&self) -> i32 {
1858 self.order.unwrap_or(i32::MAX)
1859 }
1860
1861 pub fn is_visible(&self) -> bool {
1862 matches!(
1863 self.visibility,
1864 None | Some(NodeSurfaceSlotVisibility::Visible)
1865 )
1866 }
1867
1868 pub fn is_hidden(&self) -> bool {
1869 matches!(self.visibility, Some(NodeSurfaceSlotVisibility::Hidden))
1870 }
1871
1872 pub fn is_collapsed(&self) -> bool {
1873 matches!(self.visibility, Some(NodeSurfaceSlotVisibility::Collapsed))
1874 }
1875
1876 pub fn is_hidden_or_collapsed(&self) -> bool {
1877 !self.is_visible()
1878 }
1879}
1880
1881#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1883pub struct NodeSurfaceSlotProjection {
1884 pub key: String,
1885 pub kind: NodeSurfaceSlotKind,
1886 pub label: String,
1887 pub value: String,
1888 pub visible: bool,
1889 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1890 pub controls: Vec<NodeControlDescriptor>,
1891}
1892
1893impl NodeSurfaceSlotProjection {
1894 pub fn from_descriptor(
1895 node_data: &Value,
1896 slot: &NodeSurfaceSlotDescriptor,
1897 compact_values: bool,
1898 ) -> Self {
1899 let label = slot.display_label().unwrap_or(slot.key.as_str()).to_owned();
1900 let value = slot_value_preview(node_data, slot, compact_values).unwrap_or_default();
1901 Self {
1902 key: slot.key.clone(),
1903 kind: slot.kind,
1904 label,
1905 value,
1906 visible: slot.is_visible(),
1907 controls: slot.controls.clone(),
1908 }
1909 }
1910}
1911
1912#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1914pub struct NodeSchema {
1915 pub kind: NodeKindKey,
1917 pub latest_kind_version: u32,
1919 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1921 pub kind_aliases: Vec<NodeKindKey>,
1922
1923 pub title: String,
1925 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1927 pub category: Vec<String>,
1928 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1930 pub keywords: Vec<String>,
1931 #[serde(default, skip_serializing_if = "Option::is_none")]
1936 pub renderer_key: Option<String>,
1937 #[serde(default, skip_serializing_if = "Option::is_none")]
1939 pub default_size: Option<CanvasSize>,
1940 #[serde(default, skip_serializing_if = "node_surface_layout_budget_is_empty")]
1942 pub layout_budget: NodeSurfaceLayoutBudget,
1943
1944 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1946 pub ports: Vec<PortDecl>,
1947
1948 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1950 pub surface_slots: Vec<NodeSurfaceSlotDescriptor>,
1951 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1953 pub repeatable_collections: Vec<NodeRepeatableCollectionDescriptor>,
1954 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1956 pub actions: Vec<NodeActionDescriptor>,
1957 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1959 pub menus: Vec<MenuDescriptor>,
1960 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1962 pub inspectors: Vec<InspectorDescriptor>,
1963 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1965 pub blackboards: Vec<BlackboardDescriptor>,
1966 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1968 pub chrome: Vec<NodeChromeDescriptor>,
1969
1970 #[serde(default)]
1972 pub default_data: Value,
1973}
1974
1975#[derive(Debug, Clone)]
1977pub struct NodeSchemaBuilder {
1978 schema: NodeSchema,
1979}
1980
1981#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
1983pub enum NodeInstantiationError {
1984 #[error("node kind schema not found: {0:?}")]
1986 MissingSchema(NodeKindKey),
1987 #[error("port id count mismatch: expected {expected}, got {actual}")]
1989 PortIdCountMismatch { expected: usize, actual: usize },
1990}
1991
1992#[derive(Debug, Clone)]
1994pub struct NodeInstantiation {
1995 pub node_id: NodeId,
1997 pub node: Node,
1999 pub ports: Vec<(PortId, Port)>,
2001}
2002
2003impl NodeInstantiation {
2004 pub fn into_parts(self) -> (NodeId, Node, Vec<(PortId, Port)>) {
2006 (self.node_id, self.node, self.ports)
2007 }
2008
2009 pub fn into_ops(self) -> Vec<GraphOp> {
2011 let port_order = self.node.ports.clone();
2012 let mut node = self.node;
2013 node.ports = Vec::new();
2014
2015 let mut ops =
2016 Vec::with_capacity(self.ports.len() + usize::from(!port_order.is_empty()) + 1);
2017 ops.push(GraphOp::AddNode {
2018 id: self.node_id,
2019 node,
2020 });
2021 ops.extend(
2022 self.ports
2023 .into_iter()
2024 .map(|(id, port)| GraphOp::AddPort { id, port }),
2025 );
2026 if !port_order.is_empty() {
2027 ops.push(GraphOp::SetNodePorts {
2028 id: self.node_id,
2029 from: Vec::new(),
2030 to: port_order,
2031 });
2032 }
2033 ops
2034 }
2035
2036 pub fn into_transaction(self) -> GraphTransaction {
2038 GraphTransaction::from_ops(self.into_ops())
2039 }
2040
2041 pub fn into_labeled_transaction(self, label: impl Into<String>) -> GraphTransaction {
2043 self.into_transaction().with_label(label)
2044 }
2045}
2046
2047impl NodeSchema {
2048 pub fn builder(kind: impl Into<NodeKindKey>, title: impl Into<String>) -> NodeSchemaBuilder {
2050 NodeSchemaBuilder {
2051 schema: NodeSchema {
2052 kind: kind.into(),
2053 latest_kind_version: 1,
2054 kind_aliases: Vec::new(),
2055 title: title.into(),
2056 category: Vec::new(),
2057 keywords: Vec::new(),
2058 renderer_key: None,
2059 default_size: None,
2060 layout_budget: NodeSurfaceLayoutBudget::default(),
2061 ports: Vec::new(),
2062 surface_slots: Vec::new(),
2063 repeatable_collections: Vec::new(),
2064 actions: Vec::new(),
2065 menus: Vec::new(),
2066 inspectors: Vec::new(),
2067 blackboards: Vec::new(),
2068 chrome: Vec::new(),
2069 default_data: Value::Null,
2070 },
2071 }
2072 }
2073
2074 pub fn instantiate(&self, pos: CanvasPoint) -> NodeInstantiation {
2076 let node_id = NodeId::new();
2077 let port_ids = std::iter::repeat_with(PortId::new)
2078 .take(self.ports.len())
2079 .collect();
2080 self.instantiate_from_port_ids(node_id, pos, port_ids)
2081 }
2082
2083 pub fn instantiate_with_ids(
2085 &self,
2086 node_id: NodeId,
2087 pos: CanvasPoint,
2088 port_ids: impl IntoIterator<Item = PortId>,
2089 ) -> Result<NodeInstantiation, NodeInstantiationError> {
2090 let port_ids: Vec<PortId> = port_ids.into_iter().collect();
2091 if port_ids.len() != self.ports.len() {
2092 return Err(NodeInstantiationError::PortIdCountMismatch {
2093 expected: self.ports.len(),
2094 actual: port_ids.len(),
2095 });
2096 }
2097
2098 Ok(self.instantiate_from_port_ids(node_id, pos, port_ids))
2099 }
2100
2101 fn instantiate_from_port_ids(
2102 &self,
2103 node_id: NodeId,
2104 pos: CanvasPoint,
2105 port_ids: Vec<PortId>,
2106 ) -> NodeInstantiation {
2107 let ports = self
2108 .ports
2109 .iter()
2110 .zip(port_ids.iter().copied())
2111 .map(|(decl, port_id)| (port_id, decl.instantiate(node_id)))
2112 .collect();
2113
2114 NodeInstantiation {
2115 node_id,
2116 node: Node {
2117 kind: self.kind.clone(),
2118 kind_version: self.latest_kind_version,
2119 pos,
2120 origin: None,
2121 selectable: None,
2122 focusable: None,
2123 draggable: None,
2124 connectable: None,
2125 deletable: None,
2126 parent: None,
2127 extent: None,
2128 expand_parent: None,
2129 size: self.default_size,
2130 hidden: false,
2131 collapsed: false,
2132 ports: port_ids,
2133 data: self.default_data.clone(),
2134 },
2135 ports,
2136 }
2137 }
2138}
2139
2140impl NodeSchemaBuilder {
2141 pub fn latest_kind_version(mut self, version: u32) -> Self {
2143 self.schema.latest_kind_version = version;
2144 self
2145 }
2146
2147 pub fn alias(mut self, alias: impl Into<NodeKindKey>) -> Self {
2149 self.schema.kind_aliases.push(alias.into());
2150 self
2151 }
2152
2153 pub fn aliases(mut self, aliases: impl IntoIterator<Item = impl Into<NodeKindKey>>) -> Self {
2155 self.schema
2156 .kind_aliases
2157 .extend(aliases.into_iter().map(Into::into));
2158 self
2159 }
2160
2161 pub fn category(mut self, category: impl IntoIterator<Item = impl Into<String>>) -> Self {
2163 self.schema.category = category.into_iter().map(Into::into).collect();
2164 self
2165 }
2166
2167 pub fn keyword(mut self, keyword: impl Into<String>) -> Self {
2169 self.schema.keywords.push(keyword.into());
2170 self
2171 }
2172
2173 pub fn keywords(mut self, keywords: impl IntoIterator<Item = impl Into<String>>) -> Self {
2175 self.schema
2176 .keywords
2177 .extend(keywords.into_iter().map(Into::into));
2178 self
2179 }
2180
2181 pub fn renderer_key(mut self, renderer_key: impl Into<String>) -> Self {
2183 self.schema.renderer_key = Some(renderer_key.into());
2184 self
2185 }
2186
2187 pub fn default_size(mut self, size: CanvasSize) -> Self {
2189 self.schema.default_size = Some(size);
2190 self
2191 }
2192
2193 pub fn layout_budget(mut self, budget: NodeSurfaceLayoutBudget) -> Self {
2195 self.schema.layout_budget = budget;
2196 self
2197 }
2198
2199 pub fn port(mut self, port: PortDecl) -> Self {
2201 self.schema.ports.push(port);
2202 self
2203 }
2204
2205 pub fn ports(mut self, ports: impl IntoIterator<Item = PortDecl>) -> Self {
2207 self.schema.ports.extend(ports);
2208 self
2209 }
2210
2211 pub fn surface_slot(mut self, slot: NodeSurfaceSlotDescriptor) -> Self {
2213 self.schema.surface_slots.push(slot);
2214 self
2215 }
2216
2217 pub fn surface_slots(
2219 mut self,
2220 slots: impl IntoIterator<Item = NodeSurfaceSlotDescriptor>,
2221 ) -> Self {
2222 self.schema.surface_slots.extend(slots);
2223 self
2224 }
2225
2226 pub fn repeatable_collection(mut self, collection: NodeRepeatableCollectionDescriptor) -> Self {
2228 self.schema.repeatable_collections.push(collection);
2229 self
2230 }
2231
2232 pub fn repeatable_collections(
2234 mut self,
2235 collections: impl IntoIterator<Item = NodeRepeatableCollectionDescriptor>,
2236 ) -> Self {
2237 self.schema.repeatable_collections.extend(collections);
2238 self
2239 }
2240
2241 pub fn action(mut self, action: NodeActionDescriptor) -> Self {
2243 self.schema.actions.push(action);
2244 self
2245 }
2246
2247 pub fn actions(mut self, actions: impl IntoIterator<Item = NodeActionDescriptor>) -> Self {
2249 self.schema.actions.extend(actions);
2250 self
2251 }
2252
2253 pub fn menu(mut self, menu: MenuDescriptor) -> Self {
2255 self.schema.menus.push(menu);
2256 self
2257 }
2258
2259 pub fn menus(mut self, menus: impl IntoIterator<Item = MenuDescriptor>) -> Self {
2261 self.schema.menus.extend(menus);
2262 self
2263 }
2264
2265 pub fn inspector(mut self, inspector: InspectorDescriptor) -> Self {
2267 self.schema.inspectors.push(inspector);
2268 self
2269 }
2270
2271 pub fn inspectors(mut self, inspectors: impl IntoIterator<Item = InspectorDescriptor>) -> Self {
2273 self.schema.inspectors.extend(inspectors);
2274 self
2275 }
2276
2277 pub fn blackboard(mut self, blackboard: BlackboardDescriptor) -> Self {
2279 self.schema.blackboards.push(blackboard);
2280 self
2281 }
2282
2283 pub fn blackboards(
2285 mut self,
2286 blackboards: impl IntoIterator<Item = BlackboardDescriptor>,
2287 ) -> Self {
2288 self.schema.blackboards.extend(blackboards);
2289 self
2290 }
2291
2292 pub fn chrome(mut self, chrome: NodeChromeDescriptor) -> Self {
2294 self.schema.chrome.push(chrome);
2295 self
2296 }
2297
2298 pub fn chromes(mut self, chromes: impl IntoIterator<Item = NodeChromeDescriptor>) -> Self {
2300 self.schema.chrome.extend(chromes);
2301 self
2302 }
2303
2304 pub fn default_data(mut self, data: Value) -> Self {
2306 self.schema.default_data = data;
2307 self
2308 }
2309
2310 pub fn build(self) -> NodeSchema {
2312 self.schema
2313 }
2314}
2315
2316impl From<NodeSchemaBuilder> for NodeSchema {
2317 fn from(value: NodeSchemaBuilder) -> Self {
2318 value.build()
2319 }
2320}
2321
2322impl PortDecl {
2323 pub fn new(
2325 key: impl Into<PortKey>,
2326 dir: PortDirection,
2327 kind: PortKind,
2328 capacity: PortCapacity,
2329 ) -> Self {
2330 Self {
2331 key: key.into(),
2332 dir,
2333 kind,
2334 capacity,
2335 ty: None,
2336 label: None,
2337 view: PortViewDescriptor::default(),
2338 }
2339 }
2340
2341 pub fn data_input(key: impl Into<PortKey>) -> Self {
2343 Self::new(key, PortDirection::In, PortKind::Data, PortCapacity::Single)
2344 }
2345
2346 pub fn data_output(key: impl Into<PortKey>) -> Self {
2348 Self::new(key, PortDirection::Out, PortKind::Data, PortCapacity::Multi)
2349 }
2350
2351 pub fn with_type(mut self, ty: TypeDesc) -> Self {
2353 self.ty = Some(ty);
2354 self
2355 }
2356
2357 pub fn with_label(mut self, label: impl Into<String>) -> Self {
2359 self.label = Some(label.into());
2360 self
2361 }
2362
2363 pub fn with_view(mut self, view: PortViewDescriptor) -> Self {
2365 self.view = view;
2366 self
2367 }
2368
2369 pub fn on_top(self) -> Self {
2371 self.with_view(PortViewDescriptor::top())
2372 }
2373
2374 pub fn on_right(self) -> Self {
2376 self.with_view(PortViewDescriptor::right())
2377 }
2378
2379 pub fn on_bottom(self) -> Self {
2381 self.with_view(PortViewDescriptor::bottom())
2382 }
2383
2384 pub fn on_left(self) -> Self {
2386 self.with_view(PortViewDescriptor::left())
2387 }
2388
2389 pub fn with_view_order(mut self, order: i32) -> Self {
2391 self.view.order = Some(order);
2392 self
2393 }
2394
2395 pub fn with_view_group(mut self, group: impl Into<String>) -> Self {
2397 self.view.group = Some(group.into());
2398 self
2399 }
2400
2401 pub fn with_view_anchor(mut self, anchor: impl Into<String>) -> Self {
2403 self.view.anchor = Some(anchor.into());
2404 self
2405 }
2406
2407 pub fn hidden_handle(mut self) -> Self {
2409 self.view.visibility = Some(PortHandleVisibility::Hidden);
2410 self
2411 }
2412
2413 pub fn with_capacity(mut self, capacity: PortCapacity) -> Self {
2415 self.capacity = capacity;
2416 self
2417 }
2418
2419 fn instantiate(&self, node: NodeId) -> Port {
2420 Port {
2421 node,
2422 key: self.key.clone(),
2423 dir: self.dir,
2424 kind: self.kind,
2425 capacity: self.capacity,
2426 connectable: None,
2427 connectable_start: None,
2428 connectable_end: None,
2429 ty: self.ty.clone(),
2430 data: Value::Null,
2431 }
2432 }
2433}
2434
2435#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2437pub struct NodeKindViewDescriptor {
2438 pub kind: NodeKindKey,
2440 pub renderer_key: String,
2442 pub title: String,
2444 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2446 pub category: Vec<String>,
2447 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2449 pub keywords: Vec<String>,
2450 #[serde(default, skip_serializing_if = "Option::is_none")]
2452 pub default_size: Option<CanvasSize>,
2453 #[serde(default, skip_serializing_if = "node_surface_layout_budget_is_empty")]
2455 pub layout_budget: NodeSurfaceLayoutBudget,
2456 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2458 pub ports: Vec<PortDecl>,
2459 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2461 pub surface_slots: Vec<NodeSurfaceSlotDescriptor>,
2462 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2464 pub repeatable_collections: Vec<NodeRepeatableCollectionDescriptor>,
2465 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2467 pub actions: Vec<NodeActionDescriptor>,
2468 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2470 pub menus: Vec<MenuDescriptor>,
2471 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2473 pub inspectors: Vec<InspectorDescriptor>,
2474 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2476 pub blackboards: Vec<BlackboardDescriptor>,
2477 #[serde(default, skip_serializing_if = "Vec::is_empty")]
2479 pub chrome: Vec<NodeChromeDescriptor>,
2480 #[serde(default)]
2482 pub default_data: Value,
2483}
2484
2485impl NodeKindViewDescriptor {
2486 pub(crate) fn from_schema(schema: &NodeSchema) -> Self {
2487 Self {
2488 kind: schema.kind.clone(),
2489 renderer_key: schema
2490 .renderer_key
2491 .clone()
2492 .unwrap_or_else(|| schema.kind.0.clone()),
2493 title: schema.title.clone(),
2494 category: schema.category.clone(),
2495 keywords: schema.keywords.clone(),
2496 default_size: schema.default_size,
2497 layout_budget: schema.layout_budget.clone(),
2498 ports: schema.ports.clone(),
2499 surface_slots: schema.surface_slots.clone(),
2500 repeatable_collections: schema.repeatable_collections.clone(),
2501 actions: schema.actions.clone(),
2502 menus: schema.menus.clone(),
2503 inspectors: schema.inspectors.clone(),
2504 blackboards: schema.blackboards.clone(),
2505 chrome: schema.chrome.clone(),
2506 default_data: schema.default_data.clone(),
2507 }
2508 }
2509
2510 pub fn port_decl(&self, key: impl AsRef<str>) -> Option<&PortDecl> {
2511 let key = key.as_ref();
2512 self.ports.iter().find(|decl| decl.key.0 == key)
2513 }
2514
2515 pub fn ports_by_anchor(&self, anchor: impl AsRef<str>) -> Vec<&PortDecl> {
2516 let anchor = anchor.as_ref();
2517 let mut ports: Vec<_> = self
2518 .ports
2519 .iter()
2520 .filter(|decl| decl.view.anchor.as_deref() == Some(anchor))
2521 .collect();
2522 ports.sort_by(|a, b| {
2523 a.view
2524 .order
2525 .unwrap_or(i32::MAX)
2526 .cmp(&b.view.order.unwrap_or(i32::MAX))
2527 .then_with(|| a.key.cmp(&b.key))
2528 });
2529 ports
2530 }
2531
2532 pub fn port_decl_by_anchor(&self, anchor: impl AsRef<str>) -> Option<&PortDecl> {
2533 self.ports_by_anchor(anchor).into_iter().next()
2534 }
2535
2536 pub fn surface_slot(&self, key: impl AsRef<str>) -> Option<&NodeSurfaceSlotDescriptor> {
2537 let key = key.as_ref();
2538 self.surface_slots.iter().find(|slot| slot.key == key)
2539 }
2540
2541 pub fn surface_slots_of_kind(
2542 &self,
2543 kind: NodeSurfaceSlotKind,
2544 ) -> Vec<&NodeSurfaceSlotDescriptor> {
2545 let mut slots: Vec<_> = self
2546 .surface_slots
2547 .iter()
2548 .filter(|slot| slot.kind == kind)
2549 .collect();
2550 slots.sort_by(|a, b| {
2551 a.order_key()
2552 .cmp(&b.order_key())
2553 .then_with(|| a.key.cmp(&b.key))
2554 });
2555 slots
2556 }
2557
2558 pub fn surface_slots_by_anchor(
2559 &self,
2560 anchor: impl AsRef<str>,
2561 ) -> Vec<&NodeSurfaceSlotDescriptor> {
2562 let anchor = anchor.as_ref();
2563 let mut slots: Vec<_> = self
2564 .surface_slots
2565 .iter()
2566 .filter(|slot| slot.anchor.as_deref() == Some(anchor))
2567 .collect();
2568 slots.sort_by(|a, b| {
2569 a.order_key()
2570 .cmp(&b.order_key())
2571 .then_with(|| a.key.cmp(&b.key))
2572 });
2573 slots
2574 }
2575
2576 pub fn surface_slot_by_anchor(
2577 &self,
2578 anchor: impl AsRef<str>,
2579 ) -> Option<&NodeSurfaceSlotDescriptor> {
2580 self.surface_slots_by_anchor(anchor).into_iter().next()
2581 }
2582
2583 pub fn repeatable_collection(
2584 &self,
2585 key: impl AsRef<str>,
2586 ) -> Option<&NodeRepeatableCollectionDescriptor> {
2587 let key = key.as_ref();
2588 self.repeatable_collections
2589 .iter()
2590 .find(|collection| collection.key == key)
2591 }
2592
2593 pub fn repeatable_items_projection(
2594 &self,
2595 node_data: &Value,
2596 collection_key: impl AsRef<str>,
2597 ) -> Vec<NodeRepeatableItemProjection> {
2598 self.repeatable_collection(collection_key)
2599 .map(|collection| collection.item_projections(node_data))
2600 .unwrap_or_default()
2601 }
2602
2603 pub fn action(&self, key: impl AsRef<str>) -> Option<&NodeActionDescriptor> {
2604 let key = key.as_ref();
2605 self.actions.iter().find(|action| action.key == key)
2606 }
2607
2608 pub fn menu(&self, key: impl AsRef<str>) -> Option<&MenuDescriptor> {
2609 let key = key.as_ref();
2610 self.menus.iter().find(|menu| menu.key == key)
2611 }
2612
2613 pub fn inspector(&self, key: impl AsRef<str>) -> Option<&InspectorDescriptor> {
2614 let key = key.as_ref();
2615 self.inspectors
2616 .iter()
2617 .find(|inspector| inspector.key == key)
2618 }
2619
2620 pub fn blackboard(&self, key: impl AsRef<str>) -> Option<&BlackboardDescriptor> {
2621 let key = key.as_ref();
2622 self.blackboards
2623 .iter()
2624 .find(|blackboard| blackboard.key == key)
2625 }
2626
2627 pub fn surface_slots_projection(
2628 &self,
2629 node_data: &Value,
2630 layout_hints: Option<&NodeKitLayoutHints>,
2631 zoom: f32,
2632 ) -> Vec<NodeSurfaceSlotProjection> {
2633 let projection = layout_hints
2634 .map(|layout_hints| NodeSurfaceProjection::from_layout_hints(layout_hints, zoom))
2635 .unwrap_or_else(|| {
2636 NodeSurfaceProjection::from_layout_hints(&NodeKitLayoutHints::default(), zoom)
2637 });
2638
2639 let mut slots = self
2640 .surface_slots
2641 .iter()
2642 .filter(|slot| slot.is_visible())
2643 .map(|slot| {
2644 NodeSurfaceSlotProjection::from_descriptor(
2645 node_data,
2646 slot,
2647 projection.compact_values,
2648 )
2649 })
2650 .collect::<Vec<_>>();
2651
2652 if !projection.expand_all_slots && slots.len() > projection.slot_limit {
2653 slots.truncate(projection.slot_limit);
2654 }
2655
2656 slots
2657 }
2658}
2659
2660fn slot_value_preview(
2661 node_data: &Value,
2662 slot: &NodeSurfaceSlotDescriptor,
2663 compact_values: bool,
2664) -> Option<String> {
2665 let value = semantic_slot_value(node_data, slot)?;
2666 let preview = json_value_preview(value, compact_values);
2667 (!preview.is_empty()).then_some(preview)
2668}
2669
2670fn semantic_slot_value<'a>(
2671 node_data: &'a Value,
2672 slot: &NodeSurfaceSlotDescriptor,
2673) -> Option<&'a Value> {
2674 let key = slot.data_key()?;
2675 if slot.kind == NodeSurfaceSlotKind::FieldRow
2676 && let Some(fields) = node_data.get("fields").and_then(Value::as_object)
2677 && let Some(value) = fields.get(key)
2678 {
2679 return Some(value);
2680 }
2681 semantic_json_lookup(node_data, key)
2682}
2683
2684fn semantic_json_lookup<'a>(value: &'a Value, path: &str) -> Option<&'a Value> {
2685 let mut current = value;
2686 for segment in path.split('.') {
2687 current = current.get(segment)?;
2688 }
2689 Some(current)
2690}
2691
2692fn json_value_preview(value: &Value, compact_values: bool) -> String {
2693 match value {
2694 Value::String(text) => text.clone(),
2695 Value::Bool(value) => value.to_string(),
2696 Value::Number(value) => value.to_string(),
2697 Value::Array(items) => {
2698 let preview = items
2699 .iter()
2700 .take(if compact_values { 1 } else { 2 })
2701 .map(|value| json_value_preview(value, compact_values))
2702 .filter(|text| !text.is_empty())
2703 .collect::<Vec<_>>()
2704 .join(" · ");
2705 if preview.is_empty() {
2706 format!("{} items", items.len())
2707 } else if !compact_values && items.len() > 2 {
2708 format!("{preview} …")
2709 } else {
2710 preview
2711 }
2712 }
2713 Value::Object(map) => {
2714 if let Some(text) = map.get("label").and_then(Value::as_str) {
2715 return text.to_owned();
2716 }
2717 if let Some(text) = map.get("title").and_then(Value::as_str) {
2718 return text.to_owned();
2719 }
2720 let preview = map
2721 .iter()
2722 .take(if compact_values { 1 } else { 2 })
2723 .map(|(key, value)| format!("{key}: {}", json_value_preview(value, compact_values)))
2724 .collect::<Vec<_>>()
2725 .join(" · ");
2726 if preview.is_empty() {
2727 "{}".to_owned()
2728 } else {
2729 preview
2730 }
2731 }
2732 Value::Null => String::new(),
2733 }
2734}