1 | /**************************************************************************/ |
2 | /* graph_node.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "graph_node.h" |
32 | |
33 | #include "core/string/translation.h" |
34 | #include "scene/gui/box_container.h" |
35 | #include "scene/gui/label.h" |
36 | #include "scene/theme/theme_db.h" |
37 | |
38 | bool GraphNode::_set(const StringName &p_name, const Variant &p_value) { |
39 | String str = p_name; |
40 | |
41 | if (!str.begins_with("slot/" )) { |
42 | return false; |
43 | } |
44 | |
45 | int idx = str.get_slice("/" , 1).to_int(); |
46 | String slot_property_name = str.get_slice("/" , 2); |
47 | |
48 | Slot slot; |
49 | if (slot_table.has(idx)) { |
50 | slot = slot_table[idx]; |
51 | } |
52 | |
53 | if (slot_property_name == "left_enabled" ) { |
54 | slot.enable_left = p_value; |
55 | } else if (slot_property_name == "left_type" ) { |
56 | slot.type_left = p_value; |
57 | } else if (slot_property_name == "left_icon" ) { |
58 | slot.custom_port_icon_left = p_value; |
59 | } else if (slot_property_name == "left_color" ) { |
60 | slot.color_left = p_value; |
61 | } else if (slot_property_name == "right_enabled" ) { |
62 | slot.enable_right = p_value; |
63 | } else if (slot_property_name == "right_type" ) { |
64 | slot.type_right = p_value; |
65 | } else if (slot_property_name == "right_color" ) { |
66 | slot.color_right = p_value; |
67 | } else if (slot_property_name == "right_icon" ) { |
68 | slot.custom_port_icon_right = p_value; |
69 | } else if (slot_property_name == "draw_stylebox" ) { |
70 | slot.draw_stylebox = p_value; |
71 | } else { |
72 | return false; |
73 | } |
74 | |
75 | set_slot(idx, |
76 | slot.enable_left, |
77 | slot.type_left, |
78 | slot.color_left, |
79 | slot.enable_right, |
80 | slot.type_right, |
81 | slot.color_right, |
82 | slot.custom_port_icon_left, |
83 | slot.custom_port_icon_right, |
84 | slot.draw_stylebox); |
85 | |
86 | queue_redraw(); |
87 | return true; |
88 | } |
89 | |
90 | bool GraphNode::_get(const StringName &p_name, Variant &r_ret) const { |
91 | String str = p_name; |
92 | |
93 | if (!str.begins_with("slot/" )) { |
94 | return false; |
95 | } |
96 | |
97 | int idx = str.get_slice("/" , 1).to_int(); |
98 | StringName slot_property_name = str.get_slice("/" , 2); |
99 | |
100 | Slot slot; |
101 | if (slot_table.has(idx)) { |
102 | slot = slot_table[idx]; |
103 | } |
104 | |
105 | if (slot_property_name == "left_enabled" ) { |
106 | r_ret = slot.enable_left; |
107 | } else if (slot_property_name == "left_type" ) { |
108 | r_ret = slot.type_left; |
109 | } else if (slot_property_name == "left_color" ) { |
110 | r_ret = slot.color_left; |
111 | } else if (slot_property_name == "left_icon" ) { |
112 | r_ret = slot.custom_port_icon_left; |
113 | } else if (slot_property_name == "right_enabled" ) { |
114 | r_ret = slot.enable_right; |
115 | } else if (slot_property_name == "right_type" ) { |
116 | r_ret = slot.type_right; |
117 | } else if (slot_property_name == "right_color" ) { |
118 | r_ret = slot.color_right; |
119 | } else if (slot_property_name == "right_icon" ) { |
120 | r_ret = slot.custom_port_icon_right; |
121 | } else if (slot_property_name == "draw_stylebox" ) { |
122 | r_ret = slot.draw_stylebox; |
123 | } else { |
124 | return false; |
125 | } |
126 | |
127 | return true; |
128 | } |
129 | |
130 | void GraphNode::_get_property_list(List<PropertyInfo> *p_list) const { |
131 | int idx = 0; |
132 | for (int i = 0; i < get_child_count(false); i++) { |
133 | Control *child = Object::cast_to<Control>(get_child(i, false)); |
134 | if (!child || child->is_set_as_top_level()) { |
135 | continue; |
136 | } |
137 | |
138 | String base = "slot/" + itos(idx) + "/" ; |
139 | |
140 | p_list->push_back(PropertyInfo(Variant::BOOL, base + "left_enabled" )); |
141 | p_list->push_back(PropertyInfo(Variant::INT, base + "left_type" )); |
142 | p_list->push_back(PropertyInfo(Variant::COLOR, base + "left_color" )); |
143 | p_list->push_back(PropertyInfo(Variant::OBJECT, base + "left_icon" , PROPERTY_HINT_RESOURCE_TYPE, "Texture2D" , PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL)); |
144 | p_list->push_back(PropertyInfo(Variant::BOOL, base + "right_enabled" )); |
145 | p_list->push_back(PropertyInfo(Variant::INT, base + "right_type" )); |
146 | p_list->push_back(PropertyInfo(Variant::COLOR, base + "right_color" )); |
147 | p_list->push_back(PropertyInfo(Variant::OBJECT, base + "right_icon" , PROPERTY_HINT_RESOURCE_TYPE, "Texture2D" , PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_STORE_IF_NULL)); |
148 | p_list->push_back(PropertyInfo(Variant::BOOL, base + "draw_stylebox" )); |
149 | idx++; |
150 | } |
151 | } |
152 | |
153 | void GraphNode::_resort() { |
154 | Size2 new_size = get_size(); |
155 | Ref<StyleBox> sb_panel = theme_cache.panel; |
156 | Ref<StyleBox> sb_titlebar = theme_cache.titlebar; |
157 | |
158 | // Resort titlebar first. |
159 | Size2 titlebar_size = Size2(new_size.width, titlebar_hbox->get_size().height); |
160 | titlebar_size -= sb_titlebar->get_minimum_size(); |
161 | Rect2 titlebar_rect = Rect2(sb_titlebar->get_offset(), titlebar_size); |
162 | fit_child_in_rect(titlebar_hbox, titlebar_rect); |
163 | |
164 | // After resort, the children of the titlebar container may have changed their height (e.g. Label autowrap). |
165 | Size2i titlebar_min_size = titlebar_hbox->get_combined_minimum_size(); |
166 | |
167 | // First pass, determine minimum size AND amount of stretchable elements. |
168 | Ref<StyleBox> sb_slot = theme_cache.slot; |
169 | int separation = theme_cache.separation; |
170 | |
171 | int children_count = 0; |
172 | int stretch_min = 0; |
173 | int available_stretch_space = 0; |
174 | float stretch_ratio_total = 0; |
175 | HashMap<Control *, _MinSizeCache> min_size_cache; |
176 | |
177 | for (int i = 0; i < get_child_count(false); i++) { |
178 | Control *child = Object::cast_to<Control>(get_child(i, false)); |
179 | |
180 | if (!child || !child->is_visible_in_tree() || child->is_set_as_top_level()) { |
181 | continue; |
182 | } |
183 | |
184 | Size2i size = child->get_combined_minimum_size() + (slot_table[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2()); |
185 | |
186 | stretch_min += size.height; |
187 | |
188 | _MinSizeCache msc; |
189 | msc.min_size = size.height; |
190 | msc.will_stretch = child->get_v_size_flags().has_flag(SIZE_EXPAND); |
191 | msc.final_size = msc.min_size; |
192 | min_size_cache[child] = msc; |
193 | |
194 | if (msc.will_stretch) { |
195 | available_stretch_space += msc.min_size; |
196 | stretch_ratio_total += child->get_stretch_ratio(); |
197 | } |
198 | |
199 | children_count++; |
200 | } |
201 | |
202 | if (children_count == 0) { |
203 | return; |
204 | } |
205 | |
206 | int stretch_max = new_size.height - (children_count - 1) * separation; |
207 | int stretch_diff = stretch_max - stretch_min; |
208 | |
209 | // Avoid negative stretch space. |
210 | stretch_diff = MAX(stretch_diff, 0); |
211 | |
212 | available_stretch_space += stretch_diff - sb_panel->get_margin(SIDE_BOTTOM) - sb_panel->get_margin(SIDE_TOP); |
213 | |
214 | // Second pass, discard elements that can't be stretched, this will run while stretchable elements exist. |
215 | |
216 | while (stretch_ratio_total > 0) { |
217 | // First of all, don't even be here if no stretchable objects exist. |
218 | bool refit_successful = true; |
219 | |
220 | for (int i = 0; i < get_child_count(false); i++) { |
221 | Control *child = Object::cast_to<Control>(get_child(i, false)); |
222 | if (!child || !child->is_visible_in_tree() || child->is_set_as_top_level()) { |
223 | continue; |
224 | } |
225 | |
226 | ERR_FAIL_COND(!min_size_cache.has(child)); |
227 | _MinSizeCache &msc = min_size_cache[child]; |
228 | |
229 | if (msc.will_stretch) { |
230 | int final_pixel_size = available_stretch_space * child->get_stretch_ratio() / stretch_ratio_total; |
231 | if (final_pixel_size < msc.min_size) { |
232 | // If the available stretching area is too small for a Control, |
233 | // then remove it from stretching area. |
234 | msc.will_stretch = false; |
235 | stretch_ratio_total -= child->get_stretch_ratio(); |
236 | refit_successful = false; |
237 | available_stretch_space -= msc.min_size; |
238 | msc.final_size = msc.min_size; |
239 | break; |
240 | } else { |
241 | msc.final_size = final_pixel_size; |
242 | } |
243 | } |
244 | } |
245 | |
246 | if (refit_successful) { |
247 | break; |
248 | } |
249 | } |
250 | |
251 | // Final pass, draw and stretch elements. |
252 | |
253 | int ofs_y = sb_panel->get_margin(SIDE_TOP) + titlebar_min_size.height + sb_titlebar->get_minimum_size().height; |
254 | |
255 | slot_y_cache.clear(); |
256 | int width = new_size.width - sb_panel->get_minimum_size().width; |
257 | int valid_children_idx = 0; |
258 | for (int i = 0; i < get_child_count(false); i++) { |
259 | Control *child = Object::cast_to<Control>(get_child(i, false)); |
260 | if (!child || !child->is_visible_in_tree() || child->is_set_as_top_level()) { |
261 | continue; |
262 | } |
263 | |
264 | _MinSizeCache &msc = min_size_cache[child]; |
265 | |
266 | if (valid_children_idx > 0) { |
267 | ofs_y += separation; |
268 | } |
269 | |
270 | int from_y_pos = ofs_y; |
271 | int to_y_pos = ofs_y + msc.final_size; |
272 | |
273 | // Adjust so the last valid child always fits perfect, compensating for numerical imprecision. |
274 | if (msc.will_stretch && valid_children_idx == children_count - 1) { |
275 | to_y_pos = new_size.height - sb_panel->get_margin(SIDE_BOTTOM); |
276 | } |
277 | |
278 | int height = to_y_pos - from_y_pos; |
279 | float margin = sb_panel->get_margin(SIDE_LEFT) + (slot_table[i].draw_stylebox ? sb_slot->get_margin(SIDE_LEFT) : 0); |
280 | float final_width = width - (slot_table[i].draw_stylebox ? sb_slot->get_minimum_size().x : 0); |
281 | Rect2 rect(margin, from_y_pos, final_width, height); |
282 | fit_child_in_rect(child, rect); |
283 | |
284 | slot_y_cache.push_back(from_y_pos - sb_panel->get_margin(SIDE_TOP) + height * 0.5); |
285 | |
286 | ofs_y = to_y_pos; |
287 | valid_children_idx++; |
288 | } |
289 | |
290 | queue_redraw(); |
291 | port_pos_dirty = true; |
292 | } |
293 | |
294 | void GraphNode::draw_port(int p_slot_index, Point2i p_pos, bool p_left, const Color &p_color) { |
295 | if (GDVIRTUAL_CALL(_draw_port, p_slot_index, p_pos, p_left, p_color)) { |
296 | return; |
297 | } |
298 | |
299 | Slot slot = slot_table[p_slot_index]; |
300 | Ref<Texture2D> port_icon = p_left ? slot.custom_port_icon_left : slot.custom_port_icon_right; |
301 | |
302 | Point2 icon_offset; |
303 | if (!port_icon.is_valid()) { |
304 | port_icon = theme_cache.port; |
305 | } |
306 | |
307 | icon_offset = -port_icon->get_size() * 0.5; |
308 | port_icon->draw(get_canvas_item(), p_pos + icon_offset, p_color); |
309 | } |
310 | |
311 | void GraphNode::_notification(int p_what) { |
312 | switch (p_what) { |
313 | case NOTIFICATION_DRAW: { |
314 | // Used for layout calculations. |
315 | Ref<StyleBox> sb_panel = theme_cache.panel; |
316 | Ref<StyleBox> sb_titlebar = theme_cache.titlebar; |
317 | // Used for drawing. |
318 | Ref<StyleBox> sb_to_draw_panel = selected ? theme_cache.panel_selected : theme_cache.panel; |
319 | Ref<StyleBox> sb_to_draw_titlebar = selected ? theme_cache.titlebar_selected : theme_cache.titlebar; |
320 | |
321 | Ref<StyleBox> sb_slot = theme_cache.slot; |
322 | |
323 | int port_h_offset = theme_cache.port_h_offset; |
324 | |
325 | Rect2 titlebar_rect(Point2(), titlebar_hbox->get_size() + sb_titlebar->get_minimum_size()); |
326 | Size2 body_size = get_size(); |
327 | titlebar_rect.size.width = body_size.width; |
328 | body_size.height -= titlebar_rect.size.height; |
329 | Rect2 body_rect(0, titlebar_rect.size.height, body_size.width, body_size.height); |
330 | |
331 | // Draw body (slots area) stylebox. |
332 | draw_style_box(sb_to_draw_panel, body_rect); |
333 | |
334 | // Draw title bar stylebox above. |
335 | draw_style_box(sb_to_draw_titlebar, titlebar_rect); |
336 | |
337 | int width = get_size().width - sb_panel->get_minimum_size().x; |
338 | |
339 | if (get_child_count() > 0) { |
340 | int slot_index = 0; |
341 | for (const KeyValue<int, Slot> &E : slot_table) { |
342 | if (E.key < 0 || E.key >= slot_y_cache.size()) { |
343 | continue; |
344 | } |
345 | if (!slot_table.has(E.key)) { |
346 | continue; |
347 | } |
348 | const Slot &slot = slot_table[E.key]; |
349 | |
350 | // Left port. |
351 | if (slot.enable_left) { |
352 | draw_port(slot_index, Point2i(port_h_offset, slot_y_cache[E.key] + sb_panel->get_margin(SIDE_TOP)), true, slot.color_left); |
353 | } |
354 | |
355 | // Right port. |
356 | if (slot.enable_right) { |
357 | draw_port(slot_index, Point2i(get_size().x - port_h_offset, slot_y_cache[E.key] + sb_panel->get_margin(SIDE_TOP)), false, slot.color_right); |
358 | } |
359 | |
360 | // Draw slot stylebox. |
361 | if (slot.draw_stylebox) { |
362 | Control *child = Object::cast_to<Control>(get_child(E.key, false)); |
363 | if (!child || !child->is_visible_in_tree()) { |
364 | continue; |
365 | } |
366 | Rect2 child_rect = child->get_rect(); |
367 | child_rect.position.x = sb_panel->get_margin(SIDE_LEFT); |
368 | child_rect.size.width = width; |
369 | draw_style_box(sb_slot, child_rect); |
370 | } |
371 | |
372 | slot_index++; |
373 | } |
374 | } |
375 | |
376 | if (resizable) { |
377 | draw_texture(theme_cache.resizer, get_size() - theme_cache.resizer->get_size(), theme_cache.resizer_color); |
378 | } |
379 | } break; |
380 | } |
381 | } |
382 | |
383 | void GraphNode::set_slot(int p_slot_index, bool p_enable_left, int p_type_left, const Color &p_color_left, bool p_enable_right, int p_type_right, const Color &p_color_right, const Ref<Texture2D> &p_custom_left, const Ref<Texture2D> &p_custom_right, bool p_draw_stylebox) { |
384 | ERR_FAIL_COND_MSG(p_slot_index < 0, vformat("Cannot set slot with index (%d) lesser than zero." , p_slot_index)); |
385 | |
386 | if (!p_enable_left && p_type_left == 0 && p_color_left == Color(1, 1, 1, 1) && |
387 | !p_enable_right && p_type_right == 0 && p_color_right == Color(1, 1, 1, 1) && |
388 | !p_custom_left.is_valid() && !p_custom_right.is_valid()) { |
389 | slot_table.erase(p_slot_index); |
390 | return; |
391 | } |
392 | |
393 | Slot slot; |
394 | slot.enable_left = p_enable_left; |
395 | slot.type_left = p_type_left; |
396 | slot.color_left = p_color_left; |
397 | slot.enable_right = p_enable_right; |
398 | slot.type_right = p_type_right; |
399 | slot.color_right = p_color_right; |
400 | slot.custom_port_icon_left = p_custom_left; |
401 | slot.custom_port_icon_right = p_custom_right; |
402 | slot.draw_stylebox = p_draw_stylebox; |
403 | slot_table[p_slot_index] = slot; |
404 | queue_redraw(); |
405 | port_pos_dirty = true; |
406 | |
407 | emit_signal(SNAME("slot_updated" ), p_slot_index); |
408 | } |
409 | |
410 | void GraphNode::clear_slot(int p_slot_index) { |
411 | slot_table.erase(p_slot_index); |
412 | queue_redraw(); |
413 | port_pos_dirty = true; |
414 | } |
415 | |
416 | void GraphNode::clear_all_slots() { |
417 | slot_table.clear(); |
418 | queue_redraw(); |
419 | port_pos_dirty = true; |
420 | } |
421 | |
422 | bool GraphNode::is_slot_enabled_left(int p_slot_index) const { |
423 | if (!slot_table.has(p_slot_index)) { |
424 | return false; |
425 | } |
426 | return slot_table[p_slot_index].enable_left; |
427 | } |
428 | |
429 | void GraphNode::set_slot_enabled_left(int p_slot_index, bool p_enable) { |
430 | ERR_FAIL_COND_MSG(p_slot_index < 0, vformat("Cannot set enable_left for the slot with index (%d) lesser than zero." , p_slot_index)); |
431 | |
432 | if (slot_table[p_slot_index].enable_left == p_enable) { |
433 | return; |
434 | } |
435 | |
436 | slot_table[p_slot_index].enable_left = p_enable; |
437 | queue_redraw(); |
438 | port_pos_dirty = true; |
439 | |
440 | emit_signal(SNAME("slot_updated" ), p_slot_index); |
441 | } |
442 | |
443 | void GraphNode::set_slot_type_left(int p_slot_index, int p_type) { |
444 | ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set type_left for the slot with index '%d' because it hasn't been enabled." , p_slot_index)); |
445 | |
446 | if (slot_table[p_slot_index].type_left == p_type) { |
447 | return; |
448 | } |
449 | |
450 | slot_table[p_slot_index].type_left = p_type; |
451 | queue_redraw(); |
452 | port_pos_dirty = true; |
453 | |
454 | emit_signal(SNAME("slot_updated" ), p_slot_index); |
455 | } |
456 | |
457 | int GraphNode::get_slot_type_left(int p_slot_index) const { |
458 | if (!slot_table.has(p_slot_index)) { |
459 | return 0; |
460 | } |
461 | return slot_table[p_slot_index].type_left; |
462 | } |
463 | |
464 | void GraphNode::set_slot_color_left(int p_slot_index, const Color &p_color) { |
465 | ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set color_left for the slot with index '%d' because it hasn't been enabled." , p_slot_index)); |
466 | |
467 | if (slot_table[p_slot_index].color_left == p_color) { |
468 | return; |
469 | } |
470 | |
471 | slot_table[p_slot_index].color_left = p_color; |
472 | queue_redraw(); |
473 | port_pos_dirty = true; |
474 | |
475 | emit_signal(SNAME("slot_updated" ), p_slot_index); |
476 | } |
477 | |
478 | Color GraphNode::get_slot_color_left(int p_slot_index) const { |
479 | if (!slot_table.has(p_slot_index)) { |
480 | return Color(1, 1, 1, 1); |
481 | } |
482 | return slot_table[p_slot_index].color_left; |
483 | } |
484 | |
485 | bool GraphNode::is_slot_enabled_right(int p_slot_index) const { |
486 | if (!slot_table.has(p_slot_index)) { |
487 | return false; |
488 | } |
489 | return slot_table[p_slot_index].enable_right; |
490 | } |
491 | |
492 | void GraphNode::set_slot_enabled_right(int p_slot_index, bool p_enable) { |
493 | ERR_FAIL_COND_MSG(p_slot_index < 0, vformat("Cannot set enable_right for the slot with index (%d) lesser than zero." , p_slot_index)); |
494 | |
495 | if (slot_table[p_slot_index].enable_right == p_enable) { |
496 | return; |
497 | } |
498 | |
499 | slot_table[p_slot_index].enable_right = p_enable; |
500 | queue_redraw(); |
501 | port_pos_dirty = true; |
502 | |
503 | emit_signal(SNAME("slot_updated" ), p_slot_index); |
504 | } |
505 | |
506 | void GraphNode::set_slot_type_right(int p_slot_index, int p_type) { |
507 | ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set type_right for the slot with index '%d' because it hasn't been enabled." , p_slot_index)); |
508 | |
509 | if (slot_table[p_slot_index].type_right == p_type) { |
510 | return; |
511 | } |
512 | |
513 | slot_table[p_slot_index].type_right = p_type; |
514 | queue_redraw(); |
515 | port_pos_dirty = true; |
516 | |
517 | emit_signal(SNAME("slot_updated" ), p_slot_index); |
518 | } |
519 | |
520 | int GraphNode::get_slot_type_right(int p_slot_index) const { |
521 | if (!slot_table.has(p_slot_index)) { |
522 | return 0; |
523 | } |
524 | return slot_table[p_slot_index].type_right; |
525 | } |
526 | |
527 | void GraphNode::set_slot_color_right(int p_slot_index, const Color &p_color) { |
528 | ERR_FAIL_COND_MSG(!slot_table.has(p_slot_index), vformat("Cannot set color_right for the slot with index '%d' because it hasn't been enabled." , p_slot_index)); |
529 | |
530 | if (slot_table[p_slot_index].color_right == p_color) { |
531 | return; |
532 | } |
533 | |
534 | slot_table[p_slot_index].color_right = p_color; |
535 | queue_redraw(); |
536 | port_pos_dirty = true; |
537 | |
538 | emit_signal(SNAME("slot_updated" ), p_slot_index); |
539 | } |
540 | |
541 | Color GraphNode::get_slot_color_right(int p_slot_index) const { |
542 | if (!slot_table.has(p_slot_index)) { |
543 | return Color(1, 1, 1, 1); |
544 | } |
545 | return slot_table[p_slot_index].color_right; |
546 | } |
547 | |
548 | bool GraphNode::is_slot_draw_stylebox(int p_slot_index) const { |
549 | if (!slot_table.has(p_slot_index)) { |
550 | return false; |
551 | } |
552 | return slot_table[p_slot_index].draw_stylebox; |
553 | } |
554 | |
555 | void GraphNode::set_slot_draw_stylebox(int p_slot_index, bool p_enable) { |
556 | ERR_FAIL_COND_MSG(p_slot_index < 0, vformat("Cannot set draw_stylebox for the slot with p_index (%d) lesser than zero." , p_slot_index)); |
557 | |
558 | slot_table[p_slot_index].draw_stylebox = p_enable; |
559 | queue_redraw(); |
560 | port_pos_dirty = true; |
561 | |
562 | emit_signal(SNAME("slot_updated" ), p_slot_index); |
563 | } |
564 | |
565 | Size2 GraphNode::get_minimum_size() const { |
566 | Ref<StyleBox> sb_panel = theme_cache.panel; |
567 | Ref<StyleBox> sb_titlebar = theme_cache.titlebar; |
568 | Ref<StyleBox> sb_slot = theme_cache.slot; |
569 | |
570 | int separation = theme_cache.separation; |
571 | Size2 minsize = titlebar_hbox->get_minimum_size() + sb_titlebar->get_minimum_size(); |
572 | |
573 | for (int i = 0; i < get_child_count(false); i++) { |
574 | Control *child = Object::cast_to<Control>(get_child(i, false)); |
575 | if (!child || !child->is_visible() || child->is_set_as_top_level()) { |
576 | continue; |
577 | } |
578 | |
579 | Size2i size = child->get_combined_minimum_size(); |
580 | size.width += sb_panel->get_minimum_size().width; |
581 | if (slot_table.has(i)) { |
582 | size += slot_table[i].draw_stylebox ? sb_slot->get_minimum_size() : Size2(); |
583 | } |
584 | |
585 | minsize.height += size.height; |
586 | minsize.width = MAX(minsize.width, size.width); |
587 | |
588 | if (i > 0) { |
589 | minsize.height += separation; |
590 | } |
591 | } |
592 | |
593 | minsize.height += sb_panel->get_minimum_size().height; |
594 | |
595 | return minsize; |
596 | } |
597 | |
598 | void GraphNode::_port_pos_update() { |
599 | int edgeofs = theme_cache.port_h_offset; |
600 | int separation = theme_cache.separation; |
601 | |
602 | Ref<StyleBox> sb_panel = theme_cache.panel; |
603 | Ref<StyleBox> sb_titlebar = theme_cache.titlebar; |
604 | |
605 | left_port_cache.clear(); |
606 | right_port_cache.clear(); |
607 | int vertical_ofs = titlebar_hbox->get_size().height + sb_titlebar->get_minimum_size().height + sb_panel->get_margin(SIDE_TOP); |
608 | |
609 | for (int i = 0; i < get_child_count(false); i++) { |
610 | Control *child = Object::cast_to<Control>(get_child(i, false)); |
611 | if (!child || child->is_set_as_top_level()) { |
612 | continue; |
613 | } |
614 | |
615 | Size2i size = child->get_rect().size; |
616 | |
617 | if (slot_table.has(i)) { |
618 | if (slot_table[i].enable_left) { |
619 | PortCache port_cache; |
620 | port_cache.pos = Point2i(edgeofs, vertical_ofs + size.height / 2); |
621 | port_cache.type = slot_table[i].type_left; |
622 | port_cache.color = slot_table[i].color_left; |
623 | port_cache.slot_index = child->get_index(); // Index with internal nodes included. |
624 | left_port_cache.push_back(port_cache); |
625 | } |
626 | if (slot_table[i].enable_right) { |
627 | PortCache port_cache; |
628 | port_cache.pos = Point2i(get_size().width - edgeofs, vertical_ofs + size.height / 2); |
629 | port_cache.type = slot_table[i].type_right; |
630 | port_cache.color = slot_table[i].color_right; |
631 | port_cache.slot_index = child->get_index(); // Index with internal nodes included. |
632 | right_port_cache.push_back(port_cache); |
633 | } |
634 | } |
635 | |
636 | vertical_ofs += separation; |
637 | vertical_ofs += size.height; |
638 | } |
639 | |
640 | port_pos_dirty = false; |
641 | } |
642 | |
643 | int GraphNode::get_input_port_count() { |
644 | if (port_pos_dirty) { |
645 | _port_pos_update(); |
646 | } |
647 | |
648 | return left_port_cache.size(); |
649 | } |
650 | |
651 | int GraphNode::get_output_port_count() { |
652 | if (port_pos_dirty) { |
653 | _port_pos_update(); |
654 | } |
655 | |
656 | return right_port_cache.size(); |
657 | } |
658 | |
659 | Vector2 GraphNode::get_input_port_position(int p_port_idx) { |
660 | if (port_pos_dirty) { |
661 | _port_pos_update(); |
662 | } |
663 | |
664 | ERR_FAIL_INDEX_V(p_port_idx, left_port_cache.size(), Vector2()); |
665 | Vector2 pos = left_port_cache[p_port_idx].pos; |
666 | return pos; |
667 | } |
668 | |
669 | int GraphNode::get_input_port_type(int p_port_idx) { |
670 | if (port_pos_dirty) { |
671 | _port_pos_update(); |
672 | } |
673 | |
674 | ERR_FAIL_INDEX_V(p_port_idx, left_port_cache.size(), 0); |
675 | return left_port_cache[p_port_idx].type; |
676 | } |
677 | |
678 | Color GraphNode::get_input_port_color(int p_port_idx) { |
679 | if (port_pos_dirty) { |
680 | _port_pos_update(); |
681 | } |
682 | |
683 | ERR_FAIL_INDEX_V(p_port_idx, left_port_cache.size(), Color()); |
684 | return left_port_cache[p_port_idx].color; |
685 | } |
686 | |
687 | int GraphNode::get_input_port_slot(int p_port_idx) { |
688 | if (port_pos_dirty) { |
689 | _port_pos_update(); |
690 | } |
691 | |
692 | ERR_FAIL_INDEX_V(p_port_idx, left_port_cache.size(), -1); |
693 | return left_port_cache[p_port_idx].slot_index; |
694 | } |
695 | |
696 | Vector2 GraphNode::get_output_port_position(int p_port_idx) { |
697 | if (port_pos_dirty) { |
698 | _port_pos_update(); |
699 | } |
700 | |
701 | ERR_FAIL_INDEX_V(p_port_idx, right_port_cache.size(), Vector2()); |
702 | Vector2 pos = right_port_cache[p_port_idx].pos; |
703 | return pos; |
704 | } |
705 | |
706 | int GraphNode::get_output_port_type(int p_port_idx) { |
707 | if (port_pos_dirty) { |
708 | _port_pos_update(); |
709 | } |
710 | |
711 | ERR_FAIL_INDEX_V(p_port_idx, right_port_cache.size(), 0); |
712 | return right_port_cache[p_port_idx].type; |
713 | } |
714 | |
715 | Color GraphNode::get_output_port_color(int p_port_idx) { |
716 | if (port_pos_dirty) { |
717 | _port_pos_update(); |
718 | } |
719 | |
720 | ERR_FAIL_INDEX_V(p_port_idx, right_port_cache.size(), Color()); |
721 | return right_port_cache[p_port_idx].color; |
722 | } |
723 | |
724 | int GraphNode::get_output_port_slot(int p_port_idx) { |
725 | if (port_pos_dirty) { |
726 | _port_pos_update(); |
727 | } |
728 | |
729 | ERR_FAIL_INDEX_V(p_port_idx, right_port_cache.size(), -1); |
730 | return right_port_cache[p_port_idx].slot_index; |
731 | } |
732 | |
733 | void GraphNode::set_title(const String &p_title) { |
734 | if (title == p_title) { |
735 | return; |
736 | } |
737 | title = p_title; |
738 | if (title_label) { |
739 | title_label->set_text(title); |
740 | } |
741 | update_minimum_size(); |
742 | } |
743 | |
744 | String GraphNode::get_title() const { |
745 | return title; |
746 | } |
747 | |
748 | HBoxContainer *GraphNode::get_titlebar_hbox() { |
749 | return titlebar_hbox; |
750 | } |
751 | |
752 | Control::CursorShape GraphNode::get_cursor_shape(const Point2 &p_pos) const { |
753 | if (resizable) { |
754 | if (resizing || (p_pos.x > get_size().x - theme_cache.resizer->get_width() && p_pos.y > get_size().y - theme_cache.resizer->get_height())) { |
755 | return CURSOR_FDIAGSIZE; |
756 | } |
757 | } |
758 | |
759 | return Control::get_cursor_shape(p_pos); |
760 | } |
761 | |
762 | Vector<int> GraphNode::get_allowed_size_flags_horizontal() const { |
763 | Vector<int> flags; |
764 | flags.append(SIZE_FILL); |
765 | flags.append(SIZE_SHRINK_BEGIN); |
766 | flags.append(SIZE_SHRINK_CENTER); |
767 | flags.append(SIZE_SHRINK_END); |
768 | return flags; |
769 | } |
770 | |
771 | Vector<int> GraphNode::get_allowed_size_flags_vertical() const { |
772 | Vector<int> flags; |
773 | flags.append(SIZE_FILL); |
774 | flags.append(SIZE_EXPAND); |
775 | flags.append(SIZE_SHRINK_BEGIN); |
776 | flags.append(SIZE_SHRINK_CENTER); |
777 | flags.append(SIZE_SHRINK_END); |
778 | return flags; |
779 | } |
780 | |
781 | void GraphNode::_bind_methods() { |
782 | ClassDB::bind_method(D_METHOD("set_title" , "title" ), &GraphNode::set_title); |
783 | ClassDB::bind_method(D_METHOD("get_title" ), &GraphNode::get_title); |
784 | |
785 | ClassDB::bind_method(D_METHOD("get_titlebar_hbox" ), &GraphNode::get_titlebar_hbox); |
786 | |
787 | ClassDB::bind_method(D_METHOD("set_slot" , "slot_index" , "enable_left_port" , "type_left" , "color_left" , "enable_right_port" , "type_right" , "color_right" , "custom_icon_left" , "custom_icon_right" , "draw_stylebox" ), &GraphNode::set_slot, DEFVAL(Ref<Texture2D>()), DEFVAL(Ref<Texture2D>()), DEFVAL(true)); |
788 | ClassDB::bind_method(D_METHOD("clear_slot" , "slot_index" ), &GraphNode::clear_slot); |
789 | ClassDB::bind_method(D_METHOD("clear_all_slots" ), &GraphNode::clear_all_slots); |
790 | |
791 | ClassDB::bind_method(D_METHOD("is_slot_enabled_left" , "slot_index" ), &GraphNode::is_slot_enabled_left); |
792 | ClassDB::bind_method(D_METHOD("set_slot_enabled_left" , "slot_index" , "enable" ), &GraphNode::set_slot_enabled_left); |
793 | |
794 | ClassDB::bind_method(D_METHOD("set_slot_type_left" , "slot_index" , "type" ), &GraphNode::set_slot_type_left); |
795 | ClassDB::bind_method(D_METHOD("get_slot_type_left" , "slot_index" ), &GraphNode::get_slot_type_left); |
796 | |
797 | ClassDB::bind_method(D_METHOD("set_slot_color_left" , "slot_index" , "color" ), &GraphNode::set_slot_color_left); |
798 | ClassDB::bind_method(D_METHOD("get_slot_color_left" , "slot_index" ), &GraphNode::get_slot_color_left); |
799 | |
800 | ClassDB::bind_method(D_METHOD("is_slot_enabled_right" , "slot_index" ), &GraphNode::is_slot_enabled_right); |
801 | ClassDB::bind_method(D_METHOD("set_slot_enabled_right" , "slot_index" , "enable" ), &GraphNode::set_slot_enabled_right); |
802 | |
803 | ClassDB::bind_method(D_METHOD("set_slot_type_right" , "slot_index" , "type" ), &GraphNode::set_slot_type_right); |
804 | ClassDB::bind_method(D_METHOD("get_slot_type_right" , "slot_index" ), &GraphNode::get_slot_type_right); |
805 | |
806 | ClassDB::bind_method(D_METHOD("set_slot_color_right" , "slot_index" , "color" ), &GraphNode::set_slot_color_right); |
807 | ClassDB::bind_method(D_METHOD("get_slot_color_right" , "slot_index" ), &GraphNode::get_slot_color_right); |
808 | |
809 | ClassDB::bind_method(D_METHOD("is_slot_draw_stylebox" , "slot_index" ), &GraphNode::is_slot_draw_stylebox); |
810 | ClassDB::bind_method(D_METHOD("set_slot_draw_stylebox" , "slot_index" , "enable" ), &GraphNode::set_slot_draw_stylebox); |
811 | |
812 | ClassDB::bind_method(D_METHOD("get_input_port_count" ), &GraphNode::get_input_port_count); |
813 | ClassDB::bind_method(D_METHOD("get_input_port_position" , "port_idx" ), &GraphNode::get_input_port_position); |
814 | ClassDB::bind_method(D_METHOD("get_input_port_type" , "port_idx" ), &GraphNode::get_input_port_type); |
815 | ClassDB::bind_method(D_METHOD("get_input_port_color" , "port_idx" ), &GraphNode::get_input_port_color); |
816 | ClassDB::bind_method(D_METHOD("get_input_port_slot" , "port_idx" ), &GraphNode::get_input_port_slot); |
817 | |
818 | ClassDB::bind_method(D_METHOD("get_output_port_count" ), &GraphNode::get_output_port_count); |
819 | ClassDB::bind_method(D_METHOD("get_output_port_position" , "port_idx" ), &GraphNode::get_output_port_position); |
820 | ClassDB::bind_method(D_METHOD("get_output_port_type" , "port_idx" ), &GraphNode::get_output_port_type); |
821 | ClassDB::bind_method(D_METHOD("get_output_port_color" , "port_idx" ), &GraphNode::get_output_port_color); |
822 | ClassDB::bind_method(D_METHOD("get_output_port_slot" , "port_idx" ), &GraphNode::get_output_port_slot); |
823 | |
824 | GDVIRTUAL_BIND(_draw_port, "slot_index" , "position" , "left" , "color" ) |
825 | |
826 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "title" ), "set_title" , "get_title" ); |
827 | ADD_SIGNAL(MethodInfo("slot_updated" , PropertyInfo(Variant::INT, "slot_index" ))); |
828 | |
829 | BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel); |
830 | BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, panel_selected); |
831 | BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, titlebar); |
832 | BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, titlebar_selected); |
833 | BIND_THEME_ITEM(Theme::DATA_TYPE_STYLEBOX, GraphNode, slot); |
834 | |
835 | BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GraphNode, separation); |
836 | BIND_THEME_ITEM(Theme::DATA_TYPE_CONSTANT, GraphNode, port_h_offset); |
837 | |
838 | BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, GraphNode, port); |
839 | BIND_THEME_ITEM(Theme::DATA_TYPE_ICON, GraphNode, resizer); |
840 | BIND_THEME_ITEM(Theme::DATA_TYPE_COLOR, GraphNode, resizer_color); |
841 | } |
842 | |
843 | GraphNode::GraphNode() { |
844 | titlebar_hbox = memnew(HBoxContainer); |
845 | titlebar_hbox->set_h_size_flags(SIZE_EXPAND_FILL); |
846 | add_child(titlebar_hbox, false, INTERNAL_MODE_FRONT); |
847 | |
848 | title_label = memnew(Label); |
849 | title_label->set_theme_type_variation("GraphNodeTitleLabel" ); |
850 | title_label->set_h_size_flags(SIZE_EXPAND_FILL); |
851 | titlebar_hbox->add_child(title_label); |
852 | |
853 | set_mouse_filter(MOUSE_FILTER_STOP); |
854 | } |
855 | |