1/**************************************************************************/
2/* subviewport_container.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 "subviewport_container.h"
32
33#include "core/config/engine.h"
34#include "scene/main/viewport.h"
35
36Size2 SubViewportContainer::get_minimum_size() const {
37 if (stretch) {
38 return Size2();
39 }
40 Size2 ms;
41 for (int i = 0; i < get_child_count(); i++) {
42 SubViewport *c = Object::cast_to<SubViewport>(get_child(i));
43 if (!c) {
44 continue;
45 }
46
47 Size2 minsize = c->get_size();
48 ms.width = MAX(ms.width, minsize.width);
49 ms.height = MAX(ms.height, minsize.height);
50 }
51
52 return ms;
53}
54
55void SubViewportContainer::set_stretch(bool p_enable) {
56 if (stretch == p_enable) {
57 return;
58 }
59
60 stretch = p_enable;
61 recalc_force_viewport_sizes();
62 update_minimum_size();
63 queue_sort();
64 queue_redraw();
65}
66
67bool SubViewportContainer::is_stretch_enabled() const {
68 return stretch;
69}
70
71void SubViewportContainer::set_stretch_shrink(int p_shrink) {
72 ERR_FAIL_COND(p_shrink < 1);
73 if (shrink == p_shrink) {
74 return;
75 }
76
77 shrink = p_shrink;
78
79 recalc_force_viewport_sizes();
80 queue_redraw();
81}
82
83void SubViewportContainer::recalc_force_viewport_sizes() {
84 if (!stretch) {
85 return;
86 }
87
88 // If stretch is enabled, make sure that all child SubViwewports have the correct size.
89 for (int i = 0; i < get_child_count(); i++) {
90 SubViewport *c = Object::cast_to<SubViewport>(get_child(i));
91 if (!c) {
92 continue;
93 }
94
95 c->set_size_force(get_size() / shrink);
96 }
97}
98
99int SubViewportContainer::get_stretch_shrink() const {
100 return shrink;
101}
102
103Vector<int> SubViewportContainer::get_allowed_size_flags_horizontal() const {
104 return Vector<int>();
105}
106
107Vector<int> SubViewportContainer::get_allowed_size_flags_vertical() const {
108 return Vector<int>();
109}
110
111void SubViewportContainer::_notification(int p_what) {
112 switch (p_what) {
113 case NOTIFICATION_RESIZED: {
114 recalc_force_viewport_sizes();
115 } break;
116
117 case NOTIFICATION_ENTER_TREE:
118 case NOTIFICATION_VISIBILITY_CHANGED: {
119 for (int i = 0; i < get_child_count(); i++) {
120 SubViewport *c = Object::cast_to<SubViewport>(get_child(i));
121 if (!c) {
122 continue;
123 }
124
125 if (is_visible_in_tree()) {
126 c->set_update_mode(SubViewport::UPDATE_ALWAYS);
127 } else {
128 c->set_update_mode(SubViewport::UPDATE_DISABLED);
129 }
130
131 c->set_handle_input_locally(false); //do not handle input locally here
132 }
133 } break;
134
135 case NOTIFICATION_DRAW: {
136 for (int i = 0; i < get_child_count(); i++) {
137 SubViewport *c = Object::cast_to<SubViewport>(get_child(i));
138 if (!c) {
139 continue;
140 }
141
142 if (stretch) {
143 draw_texture_rect(c->get_texture(), Rect2(Vector2(), get_size()));
144 } else {
145 draw_texture_rect(c->get_texture(), Rect2(Vector2(), c->get_size()));
146 }
147 }
148 } break;
149
150 case NOTIFICATION_FOCUS_ENTER: {
151 // If focused, send InputEvent to the SubViewport before the Gui-Input stage.
152 set_process_input(true);
153 set_process_unhandled_input(false);
154 } break;
155
156 case NOTIFICATION_FOCUS_EXIT: {
157 // A different Control has focus and should receive Gui-Input before the InputEvent is sent to the SubViewport.
158 set_process_input(false);
159 set_process_unhandled_input(true);
160 } break;
161 }
162}
163
164void SubViewportContainer::_notify_viewports(int p_notification) {
165 for (int i = 0; i < get_child_count(); i++) {
166 SubViewport *c = Object::cast_to<SubViewport>(get_child(i));
167 if (!c) {
168 continue;
169 }
170 c->notification(p_notification);
171 }
172}
173
174void SubViewportContainer::input(const Ref<InputEvent> &p_event) {
175 _propagate_nonpositional_event(p_event);
176}
177
178void SubViewportContainer::unhandled_input(const Ref<InputEvent> &p_event) {
179 _propagate_nonpositional_event(p_event);
180}
181
182void SubViewportContainer::_propagate_nonpositional_event(const Ref<InputEvent> &p_event) {
183 ERR_FAIL_COND(p_event.is_null());
184
185 if (Engine::get_singleton()->is_editor_hint()) {
186 return;
187 }
188
189 if (_is_propagated_in_gui_input(p_event)) {
190 return;
191 }
192
193 _send_event_to_viewports(p_event);
194}
195
196void SubViewportContainer::gui_input(const Ref<InputEvent> &p_event) {
197 ERR_FAIL_COND(p_event.is_null());
198
199 if (Engine::get_singleton()->is_editor_hint()) {
200 return;
201 }
202
203 if (!_is_propagated_in_gui_input(p_event)) {
204 return;
205 }
206
207 if (stretch && shrink > 1) {
208 Transform2D xform;
209 xform.scale(Vector2(1, 1) / shrink);
210 _send_event_to_viewports(p_event->xformed_by(xform));
211 } else {
212 _send_event_to_viewports(p_event);
213 }
214}
215
216void SubViewportContainer::_send_event_to_viewports(const Ref<InputEvent> &p_event) {
217 for (int i = 0; i < get_child_count(); i++) {
218 SubViewport *c = Object::cast_to<SubViewport>(get_child(i));
219 if (!c || c->is_input_disabled()) {
220 continue;
221 }
222
223 c->push_input(p_event);
224 }
225}
226
227bool SubViewportContainer::_is_propagated_in_gui_input(const Ref<InputEvent> &p_event) {
228 // Propagation of events with a position property happen in gui_input
229 // Propagation of other events happen in input
230 if (Object::cast_to<InputEventMouse>(*p_event) || Object::cast_to<InputEventScreenDrag>(*p_event) || Object::cast_to<InputEventScreenTouch>(*p_event) || Object::cast_to<InputEventGesture>(*p_event)) {
231 return true;
232 }
233 return false;
234}
235
236void SubViewportContainer::add_child_notify(Node *p_child) {
237 if (Object::cast_to<SubViewport>(p_child)) {
238 queue_redraw();
239 }
240}
241
242void SubViewportContainer::remove_child_notify(Node *p_child) {
243 if (Object::cast_to<SubViewport>(p_child)) {
244 queue_redraw();
245 }
246}
247
248PackedStringArray SubViewportContainer::get_configuration_warnings() const {
249 PackedStringArray warnings = Node::get_configuration_warnings();
250
251 bool has_viewport = false;
252 for (int i = 0; i < get_child_count(); i++) {
253 if (Object::cast_to<SubViewport>(get_child(i))) {
254 has_viewport = true;
255 break;
256 }
257 }
258 if (!has_viewport) {
259 warnings.push_back(RTR("This node doesn't have a SubViewport as child, so it can't display its intended content.\nConsider adding a SubViewport as a child to provide something displayable."));
260 }
261
262 if (get_default_cursor_shape() != Control::CURSOR_ARROW) {
263 warnings.push_back(RTR("The default mouse cursor shape of SubViewportContainer has no effect.\nConsider leaving it at its initial value `CURSOR_ARROW`."));
264 }
265
266 return warnings;
267}
268
269void SubViewportContainer::_bind_methods() {
270 ClassDB::bind_method(D_METHOD("set_stretch", "enable"), &SubViewportContainer::set_stretch);
271 ClassDB::bind_method(D_METHOD("is_stretch_enabled"), &SubViewportContainer::is_stretch_enabled);
272
273 ClassDB::bind_method(D_METHOD("set_stretch_shrink", "amount"), &SubViewportContainer::set_stretch_shrink);
274 ClassDB::bind_method(D_METHOD("get_stretch_shrink"), &SubViewportContainer::get_stretch_shrink);
275
276 ADD_PROPERTY(PropertyInfo(Variant::BOOL, "stretch"), "set_stretch", "is_stretch_enabled");
277 ADD_PROPERTY(PropertyInfo(Variant::INT, "stretch_shrink"), "set_stretch_shrink", "get_stretch_shrink");
278}
279
280SubViewportContainer::SubViewportContainer() {
281 set_process_unhandled_input(true);
282 set_focus_mode(FOCUS_CLICK);
283}
284