radiantkit_core/interactions/
bounding_box.rs1use 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}