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
38bool 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
90bool 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
130void 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
153void 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
294void 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
311void 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
383void 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
410void GraphNode::clear_slot(int p_slot_index) {
411 slot_table.erase(p_slot_index);
412 queue_redraw();
413 port_pos_dirty = true;
414}
415
416void GraphNode::clear_all_slots() {
417 slot_table.clear();
418 queue_redraw();
419 port_pos_dirty = true;
420}
421
422bool 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
429void 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
443void 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
457int 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
464void 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
478Color 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
485bool 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
492void 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
506void 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
520int 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
527void 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
541Color 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
548bool 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
555void 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
565Size2 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
598void 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
643int GraphNode::get_input_port_count() {
644 if (port_pos_dirty) {
645 _port_pos_update();
646 }
647
648 return left_port_cache.size();
649}
650
651int GraphNode::get_output_port_count() {
652 if (port_pos_dirty) {
653 _port_pos_update();
654 }
655
656 return right_port_cache.size();
657}
658
659Vector2 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
669int 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
678Color 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
687int 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
696Vector2 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
706int 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
715Color 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
724int 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
733void 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
744String GraphNode::get_title() const {
745 return title;
746}
747
748HBoxContainer *GraphNode::get_titlebar_hbox() {
749 return titlebar_hbox;
750}
751
752Control::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
762Vector<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
771Vector<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
781void 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
843GraphNode::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