Skip to main content

radiantkit_core/interactions/
bounding_box.rs

1use crate::{
2    RadiantInteraction, RadiantLineNode, RadiantNode, RadiantRectangleNode, RadiantSceneMessage,
3    RadiantTessellatable, RadiantTransformable, ScreenDescriptor, TransformComponent,
4};
5use epaint::ClippedPrimitive;
6
7const BOUNDING_BOX_TOP_ID: u64 = 201;
8const BOUNDING_BOX_RIGHT_ID: u64 = 202;
9const BOUNDING_BOX_BOTTOM_ID: u64 = 203;
10const BOUNDING_BOX_LEFT_ID: u64 = 204;
11
12const BOUNDING_BOX_TOP_RIGHT_ID: u64 = 205;
13const BOUNDING_BOX_BOTTOM_RIGHT_ID: u64 = 206;
14const BOUNDING_BOX_BOTTOM_LEFT_ID: u64 = 207;
15const BOUNDING_BOX_TOP_LEFT_ID: u64 = 208;
16
17#[derive(Debug, Clone)]
18pub struct BoundingBoxInteraction {
19    pub active_node_id: Option<u64>,
20    pub nodes: Vec<RadiantLineNode>,
21    pub corner_nodes: Vec<RadiantRectangleNode>,
22    pub primitives: Vec<ClippedPrimitive>,
23    pub selection_primitives: Vec<ClippedPrimitive>,
24}
25
26impl BoundingBoxInteraction {
27    pub fn new() -> Self {
28        let nodes = vec![
29            RadiantLineNode::new(BOUNDING_BOX_TOP_ID, [0.0, 0.0], [0.0, 0.0]),
30            RadiantLineNode::new(BOUNDING_BOX_RIGHT_ID, [0.0, 0.0], [0.0, 0.0]),
31            RadiantLineNode::new(BOUNDING_BOX_BOTTOM_ID, [0.0, 0.0], [0.0, 0.0]),
32            RadiantLineNode::new(BOUNDING_BOX_LEFT_ID, [0.0, 0.0], [0.0, 0.0]),
33        ];
34
35        let mut corner_nodes = vec![
36            RadiantRectangleNode::new(BOUNDING_BOX_TOP_RIGHT_ID, [0.0, 0.0], [16.0, 16.0]),
37            RadiantRectangleNode::new(BOUNDING_BOX_BOTTOM_RIGHT_ID, [0.0, 0.0], [16.0, 16.0]),
38            RadiantRectangleNode::new(BOUNDING_BOX_BOTTOM_LEFT_ID, [0.0, 0.0], [16.0, 16.0]),
39            RadiantRectangleNode::new(BOUNDING_BOX_TOP_LEFT_ID, [0.0, 0.0], [16.0, 16.0]),
40        ];
41        for node in &mut corner_nodes {
42            node.color.set_fill_color(epaint::Color32::BLUE);
43        }
44
45        Self {
46            active_node_id: None,
47            nodes,
48            corner_nodes,
49            primitives: Vec::new(),
50            selection_primitives: Vec::new(),
51        }
52    }
53}
54
55impl Default for BoundingBoxInteraction {
56    fn default() -> Self {
57        Self::new()
58    }
59}
60
61impl BoundingBoxInteraction {
62    pub fn contains(&self, id: u64) -> bool {
63        return id == BOUNDING_BOX_TOP_ID
64            || id == BOUNDING_BOX_RIGHT_ID
65            || id == BOUNDING_BOX_BOTTOM_ID
66            || id == BOUNDING_BOX_LEFT_ID
67            || id == BOUNDING_BOX_TOP_RIGHT_ID
68            || id == BOUNDING_BOX_BOTTOM_RIGHT_ID
69            || id == BOUNDING_BOX_BOTTOM_LEFT_ID
70            || id == BOUNDING_BOX_TOP_LEFT_ID;
71    }
72
73    pub fn enable(&mut self, node: &impl RadiantNode, _screen_descriptor: &ScreenDescriptor) {
74        if let Some(_component) = node.get_component::<TransformComponent>() {
75            let rect = node.get_bounding_rect();
76
77            self.nodes[0].start = [rect[0], rect[1]];
78            self.nodes[0].end = [rect[2], rect[1]];
79
80            self.nodes[1].start = [rect[2], rect[1]];
81            self.nodes[1].end = [rect[2], rect[3]];
82
83            self.nodes[2].start = [rect[2], rect[3]];
84            self.nodes[2].end = [rect[0], rect[3]];
85
86            self.nodes[3].start = [rect[0], rect[3]];
87            self.nodes[3].end = [rect[0], rect[1]];
88
89            self.corner_nodes[0]
90                .transform
91                .set_xy(&[rect[2] - 8.0, rect[1] - 8.0]);
92            self.corner_nodes[1]
93                .transform
94                .set_xy(&[rect[2] - 8.0, rect[3] - 8.0]);
95            self.corner_nodes[2]
96                .transform
97                .set_xy(&[rect[0] - 8.0, rect[3] - 8.0]);
98            self.corner_nodes[3]
99                .transform
100                .set_xy(&[rect[0] - 8.0, rect[1] - 8.0]);
101
102            for node in &mut self.nodes {
103                node.set_needs_tessellation();
104            }
105            for node in &mut self.corner_nodes {
106                node.set_needs_tessellation();
107            }
108
109            self.active_node_id = Some(node.get_id());
110        }
111    }
112
113    pub fn disable(&mut self) {
114        self.active_node_id = None;
115    }
116
117    pub fn update(&mut self, node: &impl RadiantNode, screen_descriptor: &ScreenDescriptor) {
118        self.enable(node, screen_descriptor);
119    }
120}
121
122impl BoundingBoxInteraction {
123    pub fn tessellate(
124        &mut self,
125        selection: bool,
126        screen_descriptor: &crate::ScreenDescriptor,
127        fonts_manager: &epaint::text::Fonts,
128    ) -> Vec<ClippedPrimitive> {
129        if self.active_node_id.is_none() {
130            return Vec::new();
131        }
132
133        let primitives = self
134            .nodes
135            .iter_mut()
136            .fold(Vec::new(), |mut primitives, node| {
137                primitives.append(&mut node.tessellate(
138                    selection,
139                    screen_descriptor,
140                    fonts_manager,
141                ));
142                primitives
143            });
144        self.primitives = self
145            .corner_nodes
146            .iter_mut()
147            .fold(primitives, |mut primitives, node| {
148                primitives.append(&mut node.tessellate(
149                    selection,
150                    screen_descriptor,
151                    fonts_manager,
152                ));
153                primitives
154            });
155
156        let selection_primitives =
157            self.nodes
158                .iter_mut()
159                .fold(Vec::new(), |mut primitives, node| {
160                    primitives.append(&mut node.tessellate(true, screen_descriptor, fonts_manager));
161                    primitives
162                });
163        self.selection_primitives =
164            self.corner_nodes
165                .iter_mut()
166                .fold(selection_primitives, |mut primitives, node| {
167                    primitives.append(&mut node.tessellate(true, screen_descriptor, fonts_manager));
168                    primitives
169                });
170
171        if selection {
172            self.selection_primitives.clone()
173        } else {
174            self.primitives.clone()
175        }
176    }
177}
178
179impl RadiantInteraction for BoundingBoxInteraction {
180    fn get_primitives(&self, selection: bool) -> Vec<ClippedPrimitive> {
181        if selection {
182            self.selection_primitives.clone()
183        } else {
184            self.primitives.clone()
185        }
186    }
187}
188
189impl BoundingBoxInteraction {
190    pub fn handle(&mut self, id: u64, transform: [f32; 2]) -> Option<RadiantSceneMessage> {
191        let Some(node_id) = self.active_node_id else { return None; };
192        match id {
193            BOUNDING_BOX_TOP_ID => Some(RadiantSceneMessage::TransformNode {
194                id: node_id,
195                position: [0.0, transform[1]],
196                scale: [0.0, -transform[1]],
197            }),
198            BOUNDING_BOX_RIGHT_ID => Some(RadiantSceneMessage::TransformNode {
199                id: node_id,
200                position: [0.0, 0.0],
201                scale: [transform[0], 0.0],
202            }),
203            BOUNDING_BOX_BOTTOM_ID => Some(RadiantSceneMessage::TransformNode {
204                id: node_id,
205                position: [0.0, 0.0],
206                scale: [0.0, transform[1]],
207            }),
208            BOUNDING_BOX_LEFT_ID => Some(RadiantSceneMessage::TransformNode {
209                id: node_id,
210                position: [transform[0], 0.0],
211                scale: [-transform[0], 0.0],
212            }),
213            BOUNDING_BOX_TOP_RIGHT_ID => Some(RadiantSceneMessage::TransformNode {
214                id: node_id,
215                position: [0.0, transform[1]],
216                scale: [transform[0], -transform[1]],
217            }),
218            BOUNDING_BOX_BOTTOM_RIGHT_ID => Some(RadiantSceneMessage::TransformNode {
219                id: node_id,
220                position: [0.0, 0.0],
221                scale: [transform[0], transform[1]],
222            }),
223            BOUNDING_BOX_BOTTOM_LEFT_ID => Some(RadiantSceneMessage::TransformNode {
224                id: node_id,
225                position: [transform[0], 0.0],
226                scale: [-transform[0], transform[1]],
227            }),
228            BOUNDING_BOX_TOP_LEFT_ID => Some(RadiantSceneMessage::TransformNode {
229                id: node_id,
230                position: [transform[0], transform[1]],
231                scale: [-transform[0], -transform[1]],
232            }),
233            _ => None,
234        }
235    }
236}