Skip to main content

jellyflow_layout/
family.rs

1use std::collections::BTreeSet;
2use std::fmt;
3
4use serde::{Deserialize, Serialize};
5
6use crate::engine::{
7    DUGONG_LAYOUT_ENGINE_ID, LayoutEngineId, MIND_MAP_FREEFORM_LAYOUT_ENGINE_ID,
8    MIND_MAP_RADIAL_LAYOUT_ENGINE_ID, TIDY_TREE_LAYOUT_ENGINE_ID,
9};
10
11/// Stable family id for DAG/layered graph layout engines.
12pub const LAYERED_DAG_LAYOUT_FAMILY_ID: &str = "layered_dag";
13/// Stable family id for mind-map layout engines.
14pub const MIND_MAP_LAYOUT_FAMILY_ID: &str = "mind_map";
15
16/// Stable identifier for a layout engine family.
17#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
18#[serde(transparent)]
19pub struct LayoutFamilyId(String);
20
21impl LayoutFamilyId {
22    /// Creates a new family id.
23    pub fn new(id: impl Into<String>) -> Self {
24        Self(id.into())
25    }
26
27    /// Returns the built-in DAG/layered family id.
28    pub fn layered_dag() -> Self {
29        Self::new(LAYERED_DAG_LAYOUT_FAMILY_ID)
30    }
31
32    /// Returns the built-in mind-map family id.
33    pub fn mind_map() -> Self {
34        Self::new(MIND_MAP_LAYOUT_FAMILY_ID)
35    }
36
37    /// Returns this id as a string slice.
38    pub fn as_str(&self) -> &str {
39        &self.0
40    }
41}
42
43impl fmt::Display for LayoutFamilyId {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        f.write_str(&self.0)
46    }
47}
48
49impl From<&str> for LayoutFamilyId {
50    fn from(value: &str) -> Self {
51        Self::new(value)
52    }
53}
54
55impl From<String> for LayoutFamilyId {
56    fn from(value: String) -> Self {
57        Self::new(value)
58    }
59}
60
61/// Metadata for a group of layout engines with related behavior.
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
63pub struct LayoutFamilyMetadata {
64    pub id: LayoutFamilyId,
65    pub name: String,
66}
67
68impl LayoutFamilyMetadata {
69    /// Creates family metadata.
70    pub fn new(id: impl Into<LayoutFamilyId>, name: impl Into<String>) -> Self {
71        Self {
72            id: id.into(),
73            name: name.into(),
74        }
75    }
76
77    /// Returns metadata for Jellyflow's built-in DAG/layered family.
78    pub fn layered_dag() -> Self {
79        Self::new(LayoutFamilyId::layered_dag(), "Layered DAG")
80    }
81
82    /// Returns metadata for Jellyflow's built-in mind-map family.
83    pub fn mind_map() -> Self {
84        Self::new(LayoutFamilyId::mind_map(), "Mind map")
85    }
86}
87
88/// Public capabilities hosts can use when choosing a layout engine.
89#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
90#[serde(rename_all = "snake_case")]
91pub enum LayoutEngineCapability {
92    /// Engine can orient its layout by [`crate::LayoutDirection`].
93    DirectionalLayout,
94    /// Engine reports routed edge points.
95    EdgeRouting,
96    /// Engine honors pinned nodes from [`crate::LayoutContext`].
97    PinnedNodes,
98    /// Engine can resolve overlap among visible nodes.
99    OverlapAvoidance,
100}
101
102/// Discovery metadata for one layout engine.
103#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub struct LayoutEngineMetadata {
105    pub engine: LayoutEngineId,
106    pub family: LayoutFamilyId,
107    pub name: String,
108    #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
109    pub capabilities: BTreeSet<LayoutEngineCapability>,
110}
111
112impl LayoutEngineMetadata {
113    /// Creates engine discovery metadata.
114    pub fn new(
115        engine: impl Into<LayoutEngineId>,
116        family: impl Into<LayoutFamilyId>,
117        name: impl Into<String>,
118    ) -> Self {
119        Self {
120            engine: engine.into(),
121            family: family.into(),
122            name: name.into(),
123            capabilities: BTreeSet::new(),
124        }
125    }
126
127    /// Adds capabilities.
128    pub fn with_capabilities(
129        mut self,
130        capabilities: impl IntoIterator<Item = LayoutEngineCapability>,
131    ) -> Self {
132        self.capabilities.extend(capabilities);
133        self
134    }
135
136    /// Returns metadata for the built-in `dugong` engine.
137    pub fn dugong() -> Self {
138        Self::new(
139            LayoutEngineId::new(DUGONG_LAYOUT_ENGINE_ID),
140            LayoutFamilyId::layered_dag(),
141            "Dugong layered DAG",
142        )
143        .with_capabilities([
144            LayoutEngineCapability::DirectionalLayout,
145            LayoutEngineCapability::EdgeRouting,
146        ])
147    }
148
149    /// Returns metadata for the built-in tidy tree engine.
150    pub fn tidy_tree() -> Self {
151        Self::new(
152            LayoutEngineId::new(TIDY_TREE_LAYOUT_ENGINE_ID),
153            LayoutFamilyId::layered_dag(),
154            "Tidy tree",
155        )
156        .with_capabilities([
157            LayoutEngineCapability::DirectionalLayout,
158            LayoutEngineCapability::EdgeRouting,
159        ])
160    }
161
162    /// Returns metadata for the built-in radial mind-map engine.
163    pub fn mind_map_radial() -> Self {
164        Self::new(
165            LayoutEngineId::new(MIND_MAP_RADIAL_LAYOUT_ENGINE_ID),
166            LayoutFamilyId::mind_map(),
167            "Radial mind map",
168        )
169        .with_capabilities([
170            LayoutEngineCapability::DirectionalLayout,
171            LayoutEngineCapability::EdgeRouting,
172            LayoutEngineCapability::PinnedNodes,
173        ])
174    }
175
176    /// Returns metadata for the built-in freeform mind-map engine.
177    pub fn mind_map_freeform() -> Self {
178        Self::new(
179            LayoutEngineId::new(MIND_MAP_FREEFORM_LAYOUT_ENGINE_ID),
180            LayoutFamilyId::mind_map(),
181            "Freeform mind map",
182        )
183        .with_capabilities([
184            LayoutEngineCapability::DirectionalLayout,
185            LayoutEngineCapability::EdgeRouting,
186            LayoutEngineCapability::PinnedNodes,
187            LayoutEngineCapability::OverlapAvoidance,
188        ])
189    }
190}