godot/scene/main/viewport.cpp

3872 lines
122 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*************************************************************************/
/* viewport.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
#include "viewport.h"
#include "core/core_string_names.h"
#include "core/debugger/engine_debugger.h"
#include "core/object/message_queue.h"
#include "core/string/translation.h"
#include "core/templates/pair.h"
#include "scene/2d/audio_listener_2d.h"
#include "scene/2d/camera_2d.h"
#include "scene/2d/collision_object_2d.h"
#ifndef _3D_DISABLED
#include "scene/3d/audio_listener_3d.h"
#include "scene/3d/camera_3d.h"
#include "scene/3d/collision_object_3d.h"
#include "scene/3d/world_environment.h"
#endif // _3D_DISABLED
#include "scene/gui/control.h"
#include "scene/gui/label.h"
#include "scene/gui/popup.h"
#include "scene/gui/popup_menu.h"
#include "scene/main/canvas_layer.h"
#include "scene/main/window.h"
#include "scene/resources/mesh.h"
#include "scene/resources/text_line.h"
#include "scene/resources/world_2d.h"
#include "scene/scene_string_names.h"
#include "servers/audio_server.h"
void ViewportTexture::setup_local_to_scene() {
if (vp) {
vp->viewport_textures.erase(this);
}
vp = nullptr;
Node *local_scene = get_local_scene();
if (!local_scene) {
return;
}
Node *vpn = local_scene->get_node(path);
ERR_FAIL_COND_MSG(!vpn, "ViewportTexture: Path to node is invalid.");
vp = Object::cast_to<Viewport>(vpn);
ERR_FAIL_COND_MSG(!vp, "ViewportTexture: Path to node does not point to a viewport.");
vp->viewport_textures.insert(this);
if (proxy_ph.is_valid()) {
RS::get_singleton()->texture_proxy_update(proxy, vp->texture_rid);
RS::get_singleton()->free(proxy_ph);
} else {
ERR_FAIL_COND(proxy.is_valid()); // Should be invalid.
proxy = RS::get_singleton()->texture_proxy_create(vp->texture_rid);
}
}
void ViewportTexture::set_viewport_path_in_scene(const NodePath &p_path) {
if (path == p_path) {
return;
}
path = p_path;
if (get_local_scene()) {
setup_local_to_scene();
}
}
NodePath ViewportTexture::get_viewport_path_in_scene() const {
return path;
}
int ViewportTexture::get_width() const {
ERR_FAIL_COND_V_MSG(!vp, 0, "Viewport Texture must be set to use it.");
return vp->size.width;
}
int ViewportTexture::get_height() const {
ERR_FAIL_COND_V_MSG(!vp, 0, "Viewport Texture must be set to use it.");
return vp->size.height;
}
Size2 ViewportTexture::get_size() const {
ERR_FAIL_COND_V_MSG(!vp, Size2(), "Viewport Texture must be set to use it.");
return vp->size;
}
RID ViewportTexture::get_rid() const {
if (proxy.is_null()) {
proxy_ph = RS::get_singleton()->texture_2d_placeholder_create();
proxy = RS::get_singleton()->texture_proxy_create(proxy_ph);
}
return proxy;
}
bool ViewportTexture::has_alpha() const {
return false;
}
Ref<Image> ViewportTexture::get_image() const {
ERR_FAIL_COND_V_MSG(!vp, Ref<Image>(), "Viewport Texture must be set to use it.");
return RS::get_singleton()->texture_2d_get(vp->texture_rid);
}
void ViewportTexture::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_viewport_path_in_scene", "path"), &ViewportTexture::set_viewport_path_in_scene);
ClassDB::bind_method(D_METHOD("get_viewport_path_in_scene"), &ViewportTexture::get_viewport_path_in_scene);
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "viewport_path", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "SubViewport", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_NODE_PATH_FROM_SCENE_ROOT), "set_viewport_path_in_scene", "get_viewport_path_in_scene");
}
ViewportTexture::ViewportTexture() {
set_local_to_scene(true);
}
ViewportTexture::~ViewportTexture() {
if (vp) {
vp->viewport_textures.erase(this);
}
if (proxy_ph.is_valid()) {
RS::get_singleton()->free(proxy_ph);
}
if (proxy.is_valid()) {
RS::get_singleton()->free(proxy);
}
}
/////////////////////////////////////
// Aliases used to provide custom styles to tooltips in the default
// theme and editor theme.
// TooltipPanel is also used for custom tooltips, while TooltipLabel
// is only relevant for default tooltips.
class TooltipPanel : public PopupPanel {
GDCLASS(TooltipPanel, PopupPanel);
public:
TooltipPanel() {}
};
class TooltipLabel : public Label {
GDCLASS(TooltipLabel, Label);
public:
TooltipLabel() {}
};
/////////////////////////////////////
void Viewport::_sub_window_update_order() {
for (int i = 0; i < gui.sub_windows.size(); i++) {
RS::get_singleton()->canvas_item_set_draw_index(gui.sub_windows[i].canvas_item, i);
}
}
void Viewport::_sub_window_register(Window *p_window) {
ERR_FAIL_COND(!is_inside_tree());
for (int i = 0; i < gui.sub_windows.size(); i++) {
ERR_FAIL_COND(gui.sub_windows[i].window == p_window);
}
if (gui.sub_windows.size() == 0) {
subwindow_canvas = RS::get_singleton()->canvas_create();
RS::get_singleton()->viewport_attach_canvas(viewport, subwindow_canvas);
RS::get_singleton()->viewport_set_canvas_stacking(viewport, subwindow_canvas, SUBWINDOW_CANVAS_LAYER, 0);
}
SubWindow sw;
sw.canvas_item = RS::get_singleton()->canvas_item_create();
RS::get_singleton()->canvas_item_set_parent(sw.canvas_item, subwindow_canvas);
sw.window = p_window;
gui.sub_windows.push_back(sw);
_sub_window_grab_focus(p_window);
RenderingServer::get_singleton()->viewport_set_parent_viewport(p_window->viewport, viewport);
}
void Viewport::_sub_window_update(Window *p_window) {
int index = -1;
for (int i = 0; i < gui.sub_windows.size(); i++) {
if (gui.sub_windows[i].window == p_window) {
index = i;
break;
}
}
ERR_FAIL_COND(index == -1);
const SubWindow &sw = gui.sub_windows[index];
Transform2D pos;
pos.set_origin(p_window->get_position());
RS::get_singleton()->canvas_item_clear(sw.canvas_item);
Rect2i r = Rect2i(p_window->get_position(), sw.window->get_size());
if (!p_window->get_flag(Window::FLAG_BORDERLESS)) {
Ref<StyleBox> panel = p_window->get_theme_stylebox(SNAME("embedded_border"));
panel->draw(sw.canvas_item, r);
// Draw the title bar text.
Ref<Font> title_font = p_window->get_theme_font(SNAME("title_font"));
int font_size = p_window->get_theme_font_size(SNAME("title_font_size"));
Color title_color = p_window->get_theme_color(SNAME("title_color"));
int title_height = p_window->get_theme_constant(SNAME("title_height"));
int close_h_ofs = p_window->get_theme_constant(SNAME("close_h_ofs"));
int close_v_ofs = p_window->get_theme_constant(SNAME("close_v_ofs"));
TextLine title_text = TextLine(p_window->atr(p_window->get_title()), title_font, font_size, Dictionary(), TranslationServer::get_singleton()->get_tool_locale());
title_text.set_width(r.size.width - panel->get_minimum_size().x - close_h_ofs);
title_text.set_direction(p_window->is_layout_rtl() ? TextServer::DIRECTION_RTL : TextServer::DIRECTION_LTR);
int x = (r.size.width - title_text.get_size().x) / 2;
int y = (-title_height - title_text.get_size().y) / 2;
Color font_outline_color = p_window->get_theme_color(SNAME("title_outline_modulate"));
int outline_size = p_window->get_theme_constant(SNAME("title_outline_size"));
if (outline_size > 0 && font_outline_color.a > 0) {
title_text.draw_outline(sw.canvas_item, r.position + Point2(x, y), outline_size, font_outline_color);
}
title_text.draw(sw.canvas_item, r.position + Point2(x, y), title_color);
bool pressed = gui.subwindow_focused == sw.window && gui.subwindow_drag == SUB_WINDOW_DRAG_CLOSE && gui.subwindow_drag_close_inside;
Ref<Texture2D> close_icon = p_window->get_theme_icon(pressed ? "close_pressed" : "close");
close_icon->draw(sw.canvas_item, r.position + Vector2(r.size.width - close_h_ofs, -close_v_ofs));
}
RS::get_singleton()->canvas_item_add_texture_rect(sw.canvas_item, r, sw.window->get_texture()->get_rid());
}
void Viewport::_sub_window_grab_focus(Window *p_window) {
if (p_window == nullptr) {
// Release current focus.
if (gui.subwindow_focused) {
gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
gui.subwindow_focused = nullptr;
gui.subwindow_drag = SUB_WINDOW_DRAG_DISABLED;
}
Window *this_window = Object::cast_to<Window>(this);
if (this_window) {
this_window->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_IN);
}
return;
}
int index = -1;
for (int i = 0; i < gui.sub_windows.size(); i++) {
if (gui.sub_windows[i].window == p_window) {
index = i;
break;
}
}
ERR_FAIL_COND(index == -1);
if (p_window->get_flag(Window::FLAG_NO_FOCUS)) {
// Can only move to foreground, but no focus granted.
SubWindow sw = gui.sub_windows[index];
gui.sub_windows.remove(index);
gui.sub_windows.push_back(sw);
index = gui.sub_windows.size() - 1;
_sub_window_update_order();
return;
}
if (gui.subwindow_focused) {
if (gui.subwindow_focused == p_window) {
return; // Nothing to do.
}
gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
gui.subwindow_drag = SUB_WINDOW_DRAG_DISABLED;
} else {
Window *this_window = Object::cast_to<Window>(this);
if (this_window) {
this_window->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
}
}
Window *old_focus = gui.subwindow_focused;
gui.subwindow_focused = p_window;
gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_IN);
{ // Move to foreground.
SubWindow sw = gui.sub_windows[index];
gui.sub_windows.remove(index);
gui.sub_windows.push_back(sw);
index = gui.sub_windows.size() - 1;
_sub_window_update_order();
}
if (old_focus) {
_sub_window_update(old_focus);
}
_sub_window_update(p_window);
}
void Viewport::_sub_window_remove(Window *p_window) {
for (int i = 0; i < gui.sub_windows.size(); i++) {
if (gui.sub_windows[i].window == p_window) {
RS::get_singleton()->free(gui.sub_windows[i].canvas_item);
gui.sub_windows.remove(i);
break;
}
}
if (gui.sub_windows.size() == 0) {
RS::get_singleton()->free(subwindow_canvas);
subwindow_canvas = RID();
}
if (gui.subwindow_focused == p_window) {
Window *parent_visible = p_window->get_parent_visible_window();
gui.subwindow_drag = SUB_WINDOW_DRAG_DISABLED;
gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
if (parent_visible && parent_visible != this) {
gui.subwindow_focused = parent_visible;
gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_IN);
} else {
gui.subwindow_focused = nullptr;
Window *this_window = Object::cast_to<Window>(this);
if (this_window) {
this_window->_event_callback(DisplayServer::WINDOW_EVENT_FOCUS_IN);
}
}
}
RenderingServer::get_singleton()->viewport_set_parent_viewport(p_window->viewport, p_window->parent ? p_window->parent->viewport : RID());
}
void Viewport::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
gui.embedding_subwindows = gui.embed_subwindows_hint;
if (get_parent()) {
parent = get_parent()->get_viewport();
RenderingServer::get_singleton()->viewport_set_parent_viewport(viewport, parent->get_viewport_rid());
} else {
parent = nullptr;
}
current_canvas = find_world_2d()->get_canvas();
RenderingServer::get_singleton()->viewport_attach_canvas(viewport, current_canvas);
_update_audio_listener_2d();
#ifndef _3D_DISABLED
RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario());
_update_audio_listener_3d();
#endif // _3D_DISABLED
add_to_group("_viewports");
if (get_tree()->is_debugging_collisions_hint()) {
PhysicsServer2D::get_singleton()->space_set_debug_contacts(find_world_2d()->get_space(), get_tree()->get_collision_debug_contact_count());
contact_2d_debug = RenderingServer::get_singleton()->canvas_item_create();
RenderingServer::get_singleton()->canvas_item_set_parent(contact_2d_debug, find_world_2d()->get_canvas());
#ifndef _3D_DISABLED
PhysicsServer3D::get_singleton()->space_set_debug_contacts(find_world_3d()->get_space(), get_tree()->get_collision_debug_contact_count());
contact_3d_debug_multimesh = RenderingServer::get_singleton()->multimesh_create();
RenderingServer::get_singleton()->multimesh_allocate_data(contact_3d_debug_multimesh, get_tree()->get_collision_debug_contact_count(), RS::MULTIMESH_TRANSFORM_3D, true);
RenderingServer::get_singleton()->multimesh_set_visible_instances(contact_3d_debug_multimesh, 0);
RenderingServer::get_singleton()->multimesh_set_mesh(contact_3d_debug_multimesh, get_tree()->get_debug_contact_mesh()->get_rid());
contact_3d_debug_instance = RenderingServer::get_singleton()->instance_create();
RenderingServer::get_singleton()->instance_set_base(contact_3d_debug_instance, contact_3d_debug_multimesh);
RenderingServer::get_singleton()->instance_set_scenario(contact_3d_debug_instance, find_world_3d()->get_scenario());
//RenderingServer::get_singleton()->instance_geometry_set_flag(contact_3d_debug_instance, RS::INSTANCE_FLAG_VISIBLE_IN_ALL_ROOMS, true);
#endif // _3D_DISABLED
}
} break;
case NOTIFICATION_READY: {
#ifndef _3D_DISABLED
if (audio_listener_3d_set.size() && !audio_listener_3d) {
AudioListener3D *first = nullptr;
for (Set<AudioListener3D *>::Element *E = audio_listener_3d_set.front(); E; E = E->next()) {
if (first == nullptr || first->is_greater_than(E->get())) {
first = E->get();
}
}
if (first) {
first->make_current();
}
}
if (camera_3d_set.size() && !camera_3d) {
// There are cameras but no current camera, pick first in tree and make it current.
Camera3D *first = nullptr;
for (Set<Camera3D *>::Element *E = camera_3d_set.front(); E; E = E->next()) {
if (first == nullptr || first->is_greater_than(E->get())) {
first = E->get();
}
}
if (first) {
first->make_current();
}
}
#endif // _3D_DISABLED
} break;
case NOTIFICATION_EXIT_TREE: {
_gui_cancel_tooltip();
RenderingServer::get_singleton()->viewport_set_scenario(viewport, RID());
RenderingServer::get_singleton()->viewport_remove_canvas(viewport, current_canvas);
if (contact_2d_debug.is_valid()) {
RenderingServer::get_singleton()->free(contact_2d_debug);
contact_2d_debug = RID();
}
if (contact_3d_debug_multimesh.is_valid()) {
RenderingServer::get_singleton()->free(contact_3d_debug_multimesh);
RenderingServer::get_singleton()->free(contact_3d_debug_instance);
contact_3d_debug_instance = RID();
contact_3d_debug_multimesh = RID();
}
remove_from_group("_viewports");
set_physics_process_internal(false);
RS::get_singleton()->viewport_set_active(viewport, false);
RenderingServer::get_singleton()->viewport_set_parent_viewport(viewport, RID());
} break;
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
if (get_tree()->is_debugging_collisions_hint() && contact_2d_debug.is_valid()) {
RenderingServer::get_singleton()->canvas_item_clear(contact_2d_debug);
RenderingServer::get_singleton()->canvas_item_set_draw_index(contact_2d_debug, 0xFFFFF); //very high index
Vector<Vector2> points = PhysicsServer2D::get_singleton()->space_get_contacts(find_world_2d()->get_space());
int point_count = PhysicsServer2D::get_singleton()->space_get_contact_count(find_world_2d()->get_space());
Color ccol = get_tree()->get_debug_collision_contact_color();
for (int i = 0; i < point_count; i++) {
RenderingServer::get_singleton()->canvas_item_add_rect(contact_2d_debug, Rect2(points[i] - Vector2(2, 2), Vector2(5, 5)), ccol);
}
}
#ifndef _3D_DISABLED
if (get_tree()->is_debugging_collisions_hint() && contact_3d_debug_multimesh.is_valid()) {
Vector<Vector3> points = PhysicsServer3D::get_singleton()->space_get_contacts(find_world_3d()->get_space());
int point_count = PhysicsServer3D::get_singleton()->space_get_contact_count(find_world_3d()->get_space());
RS::get_singleton()->multimesh_set_visible_instances(contact_3d_debug_multimesh, point_count);
for (int i = 0; i < point_count; i++) {
Transform3D point_transform;
point_transform.origin = points[i];
RS::get_singleton()->multimesh_instance_set_transform(contact_3d_debug_multimesh, i, point_transform);
}
}
#endif // _3D_DISABLED
} break;
case NOTIFICATION_WM_MOUSE_EXIT: {
_drop_physics_mouseover();
// Unlike on loss of focus (NOTIFICATION_WM_WINDOW_FOCUS_OUT), do not
// drop the gui mouseover here, as a scrollbar may be dragged while the
// mouse is outside the window (without the window having lost focus).
// See bug #39634
} break;
case NOTIFICATION_WM_WINDOW_FOCUS_OUT: {
_drop_physics_mouseover();
if (gui.mouse_focus && !gui.forced_mouse_focus) {
_drop_mouse_focus();
}
} break;
}
}
void Viewport::_process_picking() {
if (!is_inside_tree()) {
return;
}
if (!physics_object_picking) {
return;
}
if (to_screen_rect != Rect2i() && Input::get_singleton()->get_mouse_mode() == Input::MOUSE_MODE_CAPTURED) {
return;
}
_drop_physics_mouseover(true);
PhysicsDirectSpaceState2D *ss2d = PhysicsServer2D::get_singleton()->space_get_direct_state(find_world_2d()->get_space());
if (physics_has_last_mousepos) {
// If no mouse event exists, create a motion one. This is necessary because objects or camera may have moved.
// While this extra event is sent, it is checked if both camera and last object and last ID did not move.
// If nothing changed, the event is discarded to avoid flooding with unnecessary motion events every frame.
bool has_mouse_event = false;
for (const Ref<InputEvent> &m : physics_picking_events) {
if (m.is_valid()) {
has_mouse_event = true;
break;
}
}
if (!has_mouse_event) {
Ref<InputEventMouseMotion> mm;
mm.instantiate();
mm->set_device(InputEvent::DEVICE_ID_INTERNAL);
mm->set_global_position(physics_last_mousepos);
mm->set_position(physics_last_mousepos);
mm->set_alt_pressed(physics_last_mouse_state.alt);
mm->set_shift_pressed(physics_last_mouse_state.shift);
mm->set_ctrl_pressed(physics_last_mouse_state.control);
mm->set_meta_pressed(physics_last_mouse_state.meta);
mm->set_button_mask(physics_last_mouse_state.mouse_mask);
physics_picking_events.push_back(mm);
}
}
while (physics_picking_events.size()) {
Ref<InputEvent> ev = physics_picking_events.front()->get();
physics_picking_events.pop_front();
Vector2 pos;
bool is_mouse = false;
Ref<InputEventMouseMotion> mm = ev;
if (mm.is_valid()) {
pos = mm->get_position();
is_mouse = true;
physics_has_last_mousepos = true;
physics_last_mousepos = pos;
physics_last_mouse_state.alt = mm->is_alt_pressed();
physics_last_mouse_state.shift = mm->is_shift_pressed();
physics_last_mouse_state.control = mm->is_ctrl_pressed();
physics_last_mouse_state.meta = mm->is_meta_pressed();
physics_last_mouse_state.mouse_mask = mm->get_button_mask();
}
Ref<InputEventMouseButton> mb = ev;
if (mb.is_valid()) {
pos = mb->get_position();
is_mouse = true;
physics_has_last_mousepos = true;
physics_last_mousepos = pos;
physics_last_mouse_state.alt = mb->is_alt_pressed();
physics_last_mouse_state.shift = mb->is_shift_pressed();
physics_last_mouse_state.control = mb->is_ctrl_pressed();
physics_last_mouse_state.meta = mb->is_meta_pressed();
if (mb->is_pressed()) {
physics_last_mouse_state.mouse_mask |= (MouseButton)(1 << (mb->get_button_index() - 1));
} else {
physics_last_mouse_state.mouse_mask &= (MouseButton) ~(1 << (mb->get_button_index() - 1));
// If touch mouse raised, assume we don't know last mouse pos until new events come
if (mb->get_device() == InputEvent::DEVICE_ID_TOUCH_MOUSE) {
physics_has_last_mousepos = false;
}
}
}
Ref<InputEventKey> k = ev;
if (k.is_valid()) {
// Only for mask.
physics_last_mouse_state.alt = k->is_alt_pressed();
physics_last_mouse_state.shift = k->is_shift_pressed();
physics_last_mouse_state.control = k->is_ctrl_pressed();
physics_last_mouse_state.meta = k->is_meta_pressed();
continue;
}
Ref<InputEventScreenDrag> sd = ev;
if (sd.is_valid()) {
pos = sd->get_position();
}
Ref<InputEventScreenTouch> st = ev;
if (st.is_valid()) {
pos = st->get_position();
}
if (ss2d) {
// Send to 2D.
uint64_t frame = get_tree()->get_frame();
PhysicsDirectSpaceState2D::ShapeResult res[64];
for (Set<CanvasLayer *>::Element *E = canvas_layers.front(); E; E = E->next()) {
Transform2D canvas_transform;
ObjectID canvas_layer_id;
if (E->get()) {
// A descendant CanvasLayer.
canvas_transform = E->get()->get_transform();
canvas_layer_id = E->get()->get_instance_id();
} else {
// This Viewport's builtin canvas.
canvas_transform = get_canvas_transform();
canvas_layer_id = ObjectID();
}
Vector2 point = canvas_transform.affine_inverse().xform(pos);
int rc = ss2d->intersect_point_on_canvas(point, canvas_layer_id, res, 64, Set<RID>(), 0xFFFFFFFF, true, true, true);
for (int i = 0; i < rc; i++) {
if (res[i].collider_id.is_valid() && res[i].collider) {
CollisionObject2D *co = Object::cast_to<CollisionObject2D>(res[i].collider);
if (co && co->can_process()) {
bool send_event = true;
if (is_mouse) {
Map<ObjectID, uint64_t>::Element *F = physics_2d_mouseover.find(res[i].collider_id);
if (!F) {
physics_2d_mouseover.insert(res[i].collider_id, frame);
co->_mouse_enter();
} else {
F->get() = frame;
// It was already hovered, so don't send the event if it's faked.
if (mm.is_valid() && mm->get_device() == InputEvent::DEVICE_ID_INTERNAL) {
send_event = false;
}
}
Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *SF = physics_2d_shape_mouseover.find(Pair(res[i].collider_id, res[i].shape));
if (!SF) {
physics_2d_shape_mouseover.insert(Pair(res[i].collider_id, res[i].shape), frame);
co->_mouse_shape_enter(res[i].shape);
} else {
SF->get() = frame;
}
}
if (send_event) {
co->_input_event_call(this, ev, res[i].shape);
}
}
}
}
}
if (is_mouse) {
_cleanup_mouseover_colliders(false, false, frame);
}
}
#ifndef _3D_DISABLED
Vector2 last_pos(1e20, 1e20);
CollisionObject3D *last_object = nullptr;
ObjectID last_id;
PhysicsDirectSpaceState3D::RayResult result;
bool captured = false;
if (physics_object_capture.is_valid()) {
CollisionObject3D *co = Object::cast_to<CollisionObject3D>(ObjectDB::get_instance(physics_object_capture));
if (co && camera_3d) {
_collision_object_3d_input_event(co, camera_3d, ev, Vector3(), Vector3(), 0);
captured = true;
if (mb.is_valid() && mb->get_button_index() == 1 && !mb->is_pressed()) {
physics_object_capture = ObjectID();
}
} else {
physics_object_capture = ObjectID();
}
}
if (captured) {
// None.
} else if (pos == last_pos) {
if (last_id.is_valid()) {
if (ObjectDB::get_instance(last_id) && last_object) {
// Good, exists.
_collision_object_3d_input_event(last_object, camera_3d, ev, result.position, result.normal, result.shape);
if (last_object->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) {
physics_object_capture = last_id;
}
}
}
} else {
if (camera_3d) {
Vector3 from = camera_3d->project_ray_origin(pos);
Vector3 dir = camera_3d->project_ray_normal(pos);
PhysicsDirectSpaceState3D *space = PhysicsServer3D::get_singleton()->space_get_direct_state(find_world_3d()->get_space());
if (space) {
bool col = space->intersect_ray(from, from + dir * 10000, result, Set<RID>(), 0xFFFFFFFF, true, true, true);
ObjectID new_collider;
if (col) {
CollisionObject3D *co = Object::cast_to<CollisionObject3D>(result.collider);
if (co && co->can_process()) {
_collision_object_3d_input_event(co, camera_3d, ev, result.position, result.normal, result.shape);
last_object = co;
last_id = result.collider_id;
new_collider = last_id;
if (co->get_capture_input_on_drag() && mb.is_valid() && mb->get_button_index() == 1 && mb->is_pressed()) {
physics_object_capture = last_id;
}
}
}
if (is_mouse && new_collider != physics_object_over) {
if (physics_object_over.is_valid()) {
CollisionObject3D *co = Object::cast_to<CollisionObject3D>(ObjectDB::get_instance(physics_object_over));
if (co) {
co->_mouse_exit();
}
}
if (new_collider.is_valid()) {
CollisionObject3D *co = Object::cast_to<CollisionObject3D>(ObjectDB::get_instance(new_collider));
if (co) {
co->_mouse_enter();
}
}
physics_object_over = new_collider;
}
}
last_pos = pos;
}
}
#endif // _3D_DISABLED
}
}
RID Viewport::get_viewport_rid() const {
return viewport;
}
void Viewport::update_canvas_items() {
if (!is_inside_tree()) {
return;
}
_update_canvas_items(this);
}
void Viewport::_set_size(const Size2i &p_size, const Size2i &p_size_2d_override, const Rect2i &p_to_screen_rect, const Transform2D &p_stretch_transform, bool p_allocated) {
if (size == p_size && size_allocated == p_allocated && stretch_transform == p_stretch_transform && p_size_2d_override == size_2d_override && to_screen_rect != p_to_screen_rect) {
return;
}
size = p_size;
size_allocated = p_allocated;
size_2d_override = p_size_2d_override;
stretch_transform = p_stretch_transform;
to_screen_rect = p_to_screen_rect;
if (p_allocated) {
RS::get_singleton()->viewport_set_size(viewport, size.width, size.height);
} else {
RS::get_singleton()->viewport_set_size(viewport, 0, 0);
}
_update_global_transform();
update_canvas_items();
emit_signal(SNAME("size_changed"));
}
Size2i Viewport::_get_size() const {
return size;
}
Size2i Viewport::_get_size_2d_override() const {
return size_2d_override;
}
bool Viewport::_is_size_allocated() const {
return size_allocated;
}
Rect2 Viewport::get_visible_rect() const {
Rect2 r;
if (size == Size2()) {
r = Rect2(Point2(), DisplayServer::get_singleton()->window_get_size());
} else {
r = Rect2(Point2(), size);
}
if (size_2d_override != Size2i()) {
r.size = size_2d_override;
}
return r;
}
void Viewport::_update_audio_listener_2d() {
if (AudioServer::get_singleton()) {
AudioServer::get_singleton()->notify_listener_changed();
}
}
void Viewport::set_as_audio_listener_2d(bool p_enable) {
if (p_enable == is_audio_listener_2d_enabled) {
return;
}
is_audio_listener_2d_enabled = p_enable;
_update_audio_listener_2d();
}
bool Viewport::is_audio_listener_2d() const {
return is_audio_listener_2d_enabled;
}
AudioListener2D *Viewport::get_audio_listener_2d() const {
return audio_listener_2d;
}
void Viewport::enable_canvas_transform_override(bool p_enable) {
if (override_canvas_transform == p_enable) {
return;
}
override_canvas_transform = p_enable;
if (p_enable) {
RenderingServer::get_singleton()->viewport_set_canvas_transform(viewport, find_world_2d()->get_canvas(), canvas_transform_override);
} else {
RenderingServer::get_singleton()->viewport_set_canvas_transform(viewport, find_world_2d()->get_canvas(), canvas_transform);
}
}
bool Viewport::is_canvas_transform_override_enbled() const {
return override_canvas_transform;
}
void Viewport::set_canvas_transform_override(const Transform2D &p_transform) {
if (canvas_transform_override == p_transform) {
return;
}
canvas_transform_override = p_transform;
if (override_canvas_transform) {
RenderingServer::get_singleton()->viewport_set_canvas_transform(viewport, find_world_2d()->get_canvas(), canvas_transform_override);
}
}
Transform2D Viewport::get_canvas_transform_override() const {
return canvas_transform_override;
}
void Viewport::set_canvas_transform(const Transform2D &p_transform) {
canvas_transform = p_transform;
if (!override_canvas_transform) {
RenderingServer::get_singleton()->viewport_set_canvas_transform(viewport, find_world_2d()->get_canvas(), canvas_transform);
}
}
Transform2D Viewport::get_canvas_transform() const {
return canvas_transform;
}
void Viewport::_update_global_transform() {
Transform2D sxform = stretch_transform * global_canvas_transform;
RenderingServer::get_singleton()->viewport_set_global_canvas_transform(viewport, sxform);
}
void Viewport::set_global_canvas_transform(const Transform2D &p_transform) {
global_canvas_transform = p_transform;
_update_global_transform();
}
Transform2D Viewport::get_global_canvas_transform() const {
return global_canvas_transform;
}
void Viewport::_camera_2d_set(Camera2D *p_camera_2d) {
camera_2d = p_camera_2d;
}
void Viewport::_audio_listener_2d_set(AudioListener2D *p_listener) {
if (audio_listener_2d == p_listener) {
return;
} else if (audio_listener_2d) {
audio_listener_2d->clear_current();
}
audio_listener_2d = p_listener;
}
void Viewport::_audio_listener_2d_remove(AudioListener2D *p_listener) {
if (audio_listener_2d == p_listener) {
audio_listener_2d = nullptr;
}
}
void Viewport::_canvas_layer_add(CanvasLayer *p_canvas_layer) {
canvas_layers.insert(p_canvas_layer);
}
void Viewport::_canvas_layer_remove(CanvasLayer *p_canvas_layer) {
canvas_layers.erase(p_canvas_layer);
}
void Viewport::set_transparent_background(bool p_enable) {
transparent_bg = p_enable;
RS::get_singleton()->viewport_set_transparent_background(viewport, p_enable);
}
bool Viewport::has_transparent_background() const {
return transparent_bg;
}
void Viewport::set_world_2d(const Ref<World2D> &p_world_2d) {
if (world_2d == p_world_2d) {
return;
}
if (parent && parent->find_world_2d() == p_world_2d) {
WARN_PRINT("Unable to use parent world_2d as world_2d");
return;
}
if (is_inside_tree()) {
RenderingServer::get_singleton()->viewport_remove_canvas(viewport, current_canvas);
}
if (p_world_2d.is_valid()) {
world_2d = p_world_2d;
} else {
WARN_PRINT("Invalid world_2d");
world_2d = Ref<World2D>(memnew(World2D));
}
_update_audio_listener_2d();
if (is_inside_tree()) {
current_canvas = find_world_2d()->get_canvas();
RenderingServer::get_singleton()->viewport_attach_canvas(viewport, current_canvas);
}
}
Ref<World2D> Viewport::find_world_2d() const {
if (world_2d.is_valid()) {
return world_2d;
} else if (parent) {
return parent->find_world_2d();
} else {
return Ref<World2D>();
}
}
void Viewport::_propagate_viewport_notification(Node *p_node, int p_what) {
p_node->notification(p_what);
for (int i = 0; i < p_node->get_child_count(); i++) {
Node *c = p_node->get_child(i);
if (Object::cast_to<Viewport>(c)) {
continue;
}
_propagate_viewport_notification(c, p_what);
}
}
Ref<World2D> Viewport::get_world_2d() const {
return world_2d;
}
Camera2D *Viewport::get_camera_2d() const {
return camera_2d;
}
Transform2D Viewport::get_final_transform() const {
return stretch_transform * global_canvas_transform;
}
void Viewport::_update_canvas_items(Node *p_node) {
if (p_node != this) {
Viewport *vp = Object::cast_to<Viewport>(p_node);
if (vp) {
return;
}
CanvasItem *ci = Object::cast_to<CanvasItem>(p_node);
if (ci) {
ci->update();
}
}
int cc = p_node->get_child_count();
for (int i = 0; i < cc; i++) {
_update_canvas_items(p_node->get_child(i));
}
}
Ref<ViewportTexture> Viewport::get_texture() const {
return default_texture;
}
void Viewport::set_shadow_atlas_size(int p_size) {
shadow_atlas_size = p_size;
RS::get_singleton()->viewport_set_shadow_atlas_size(viewport, p_size, shadow_atlas_16_bits);
}
int Viewport::get_shadow_atlas_size() const {
return shadow_atlas_size;
}
void Viewport::set_shadow_atlas_16_bits(bool p_16_bits) {
if (shadow_atlas_16_bits == p_16_bits) {
return;
}
shadow_atlas_16_bits = p_16_bits;
RS::get_singleton()->viewport_set_shadow_atlas_size(viewport, shadow_atlas_size, shadow_atlas_16_bits);
}
bool Viewport::get_shadow_atlas_16_bits() const {
return shadow_atlas_16_bits;
}
void Viewport::set_shadow_atlas_quadrant_subdiv(int p_quadrant, ShadowAtlasQuadrantSubdiv p_subdiv) {
ERR_FAIL_INDEX(p_quadrant, 4);
ERR_FAIL_INDEX(p_subdiv, SHADOW_ATLAS_QUADRANT_SUBDIV_MAX);
if (shadow_atlas_quadrant_subdiv[p_quadrant] == p_subdiv) {
return;
}
shadow_atlas_quadrant_subdiv[p_quadrant] = p_subdiv;
static const int subdiv[SHADOW_ATLAS_QUADRANT_SUBDIV_MAX] = { 0, 1, 4, 16, 64, 256, 1024 };
RS::get_singleton()->viewport_set_shadow_atlas_quadrant_subdivision(viewport, p_quadrant, subdiv[p_subdiv]);
}
Viewport::ShadowAtlasQuadrantSubdiv Viewport::get_shadow_atlas_quadrant_subdiv(int p_quadrant) const {
ERR_FAIL_INDEX_V(p_quadrant, 4, SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED);
return shadow_atlas_quadrant_subdiv[p_quadrant];
}
Transform2D Viewport::_get_input_pre_xform() const {
Transform2D pre_xf;
if (to_screen_rect.size.x != 0 && to_screen_rect.size.y != 0) {
pre_xf.elements[2] = -to_screen_rect.position;
pre_xf.scale(size / to_screen_rect.size);
}
return pre_xf;
}
Ref<InputEvent> Viewport::_make_input_local(const Ref<InputEvent> &ev) {
Transform2D ai = get_final_transform().affine_inverse() * _get_input_pre_xform();
return ev->xformed_by(ai);
}
Vector2 Viewport::get_mouse_position() const {
return gui.last_mouse_pos;
}
void Viewport::warp_mouse(const Vector2 &p_pos) {
Vector2 gpos = (get_final_transform().affine_inverse() * _get_input_pre_xform()).affine_inverse().xform(p_pos);
Input::get_singleton()->warp_mouse_position(gpos);
}
void Viewport::_gui_sort_roots() {
if (!gui.roots_order_dirty) {
return;
}
gui.roots.sort_custom<Control::CComparator>();
gui.roots_order_dirty = false;
}
void Viewport::_gui_cancel_tooltip() {
gui.tooltip_control = nullptr;
if (gui.tooltip_timer.is_valid()) {
gui.tooltip_timer->release_connections();
gui.tooltip_timer = Ref<SceneTreeTimer>();
}
if (gui.tooltip_popup) {
gui.tooltip_popup->queue_delete();
gui.tooltip_popup = nullptr;
gui.tooltip_label = nullptr;
}
}
String Viewport::_gui_get_tooltip(Control *p_control, const Vector2 &p_pos, Control **r_tooltip_owner) {
Vector2 pos = p_pos;
String tooltip;
while (p_control) {
tooltip = p_control->get_tooltip(pos);
// Temporary solution for PopupMenus.
PopupMenu *menu = Object::cast_to<PopupMenu>(this);
if (menu) {
tooltip = menu->get_tooltip(pos);
}
if (r_tooltip_owner) {
*r_tooltip_owner = p_control;
}
// If we found a tooltip, we stop here.
if (!tooltip.is_empty()) {
break;
}
// Otherwise, we check parent controls unless some conditions prevent it.
if (p_control->data.mouse_filter == Control::MOUSE_FILTER_STOP) {
break;
}
if (p_control->is_set_as_top_level()) {
break;
}
// Transform cursor pos for parent control.
pos = p_control->get_transform().xform(pos);
p_control = p_control->get_parent_control();
}
return tooltip;
}
void Viewport::_gui_show_tooltip() {
if (!gui.tooltip_control) {
return;
}
// Get the Control under cursor and the relevant tooltip text, if any.
Control *tooltip_owner = nullptr;
String tooltip_text = _gui_get_tooltip(
gui.tooltip_control,
gui.tooltip_control->get_global_transform().xform_inv(gui.last_mouse_pos),
&tooltip_owner);
tooltip_text = tooltip_text.strip_edges();
if (tooltip_text.is_empty()) {
return; // Nothing to show.
}
// Remove previous popup if we change something.
if (gui.tooltip_popup) {
memdelete(gui.tooltip_popup);
gui.tooltip_popup = nullptr;
gui.tooltip_label = nullptr;
}
if (!tooltip_owner) {
return;
}
// Popup window which houses the tooltip content.
TooltipPanel *panel = memnew(TooltipPanel);
// Controls can implement `make_custom_tooltip` to provide their own tooltip.
// This should be a Control node which will be added as child to a TooltipPanel.
Control *base_tooltip = tooltip_owner->make_custom_tooltip(tooltip_text);
// If no custom tooltip is given, use a default implementation.
if (!base_tooltip) {
gui.tooltip_label = memnew(TooltipLabel);
gui.tooltip_label->set_auto_translate(gui.tooltip_control->is_auto_translating());
gui.tooltip_label->set_text(tooltip_text);
base_tooltip = gui.tooltip_label;
panel->connect("mouse_entered", callable_mp(this, &Viewport::_gui_cancel_tooltip));
}
base_tooltip->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
panel->set_transient(false);
panel->set_flag(Window::FLAG_NO_FOCUS, true);
panel->set_wrap_controls(true);
panel->add_child(base_tooltip);
gui.tooltip_popup = panel;
tooltip_owner->add_child(gui.tooltip_popup);
Point2 tooltip_offset = ProjectSettings::get_singleton()->get("display/mouse_cursor/tooltip_position_offset");
Rect2 r(gui.tooltip_pos + tooltip_offset, gui.tooltip_popup->get_contents_minimum_size());
Window *window = gui.tooltip_popup->get_parent_visible_window();
Rect2i vr = window->get_usable_parent_rect();
if (r.size.x + r.position.x > vr.size.x + vr.position.x) {
r.position.x = vr.position.x + vr.size.x - r.size.x;
} else if (r.position.x < vr.position.x) {
r.position.x = vr.position.x;
}
if (r.size.y + r.position.y > vr.size.y + vr.position.y) {
r.position.y = vr.position.y + vr.size.y - r.size.y;
} else if (r.position.y < vr.position.y) {
r.position.y = vr.position.y;
}
gui.tooltip_popup->set_current_screen(window->get_current_screen());
gui.tooltip_popup->set_position(r.position);
gui.tooltip_popup->set_size(r.size);
gui.tooltip_popup->show();
gui.tooltip_popup->child_controls_changed();
}
void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_input) {
Ref<InputEvent> ev = p_input;
// Mouse wheel events can't be stopped.
Ref<InputEventMouseButton> mb = p_input;
bool cant_stop_me_now = (mb.is_valid() &&
(mb->get_button_index() == MOUSE_BUTTON_WHEEL_DOWN ||
mb->get_button_index() == MOUSE_BUTTON_WHEEL_UP ||
mb->get_button_index() == MOUSE_BUTTON_WHEEL_LEFT ||
mb->get_button_index() == MOUSE_BUTTON_WHEEL_RIGHT));
Ref<InputEventPanGesture> pn = p_input;
cant_stop_me_now = pn.is_valid() || cant_stop_me_now;
bool ismouse = ev.is_valid() || Object::cast_to<InputEventMouseMotion>(*p_input) != nullptr;
CanvasItem *ci = p_control;
while (ci) {
Control *control = Object::cast_to<Control>(ci);
if (control) {
if (control->data.mouse_filter != Control::MOUSE_FILTER_IGNORE) {
control->_call_gui_input(ev);
}
if (!control->is_inside_tree() || control->is_set_as_top_level()) {
break;
}
if (gui.key_event_accepted) {
break;
}
if (!cant_stop_me_now && control->data.mouse_filter == Control::MOUSE_FILTER_STOP && ismouse) {
break;
}
}
if (ci->is_set_as_top_level()) {
break;
}
ev = ev->xformed_by(ci->get_transform()); // Transform event upwards.
ci = ci->get_parent_item();
}
}
void Viewport::_gui_call_notification(Control *p_control, int p_what) {
CanvasItem *ci = p_control;
while (ci) {
Control *control = Object::cast_to<Control>(ci);
if (control) {
if (control->data.mouse_filter != Control::MOUSE_FILTER_IGNORE) {
control->notification(p_what);
}
if (!control->is_inside_tree()) {
break;
}
if (!control->is_inside_tree() || control->is_set_as_top_level()) {
break;
}
if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) {
break;
}
}
if (ci->is_set_as_top_level()) {
break;
}
ci = ci->get_parent_item();
}
}
Control *Viewport::gui_find_control(const Point2 &p_global) {
// Handle subwindows.
_gui_sort_roots();
for (List<Control *>::Element *E = gui.roots.back(); E; E = E->prev()) {
Control *sw = E->get();
if (!sw->is_visible_in_tree()) {
continue;
}
Transform2D xform;
CanvasItem *pci = sw->get_parent_item();
if (pci) {
xform = pci->get_global_transform_with_canvas();
} else {
xform = sw->get_canvas_transform();
}
Control *ret = _gui_find_control_at_pos(sw, p_global, xform, gui.focus_inv_xform);
if (ret) {
return ret;
}
}
return nullptr;
}
Control *Viewport::_gui_find_control_at_pos(CanvasItem *p_node, const Point2 &p_global, const Transform2D &p_xform, Transform2D &r_inv_xform) {
if (Object::cast_to<Viewport>(p_node)) {
return nullptr;
}
if (!p_node->is_visible()) {
return nullptr; // Canvas item hidden, discard.
}
Transform2D matrix = p_xform * p_node->get_transform();
// matrix.basis_determinant() == 0.0f implies that node does not exist on scene
if (matrix.basis_determinant() == 0.0f) {
return nullptr;
}
Control *c = Object::cast_to<Control>(p_node);
if (!c || !c->is_clipping_contents() || c->has_point(matrix.affine_inverse().xform(p_global))) {
for (int i = p_node->get_child_count() - 1; i >= 0; i--) {
CanvasItem *ci = Object::cast_to<CanvasItem>(p_node->get_child(i));
if (!ci || ci->is_set_as_top_level()) {
continue;
}
Control *ret = _gui_find_control_at_pos(ci, p_global, matrix, r_inv_xform);
if (ret) {
return ret;
}
}
}
if (!c || c->data.mouse_filter == Control::MOUSE_FILTER_IGNORE) {
return nullptr;
}
matrix.affine_invert();
if (!c->has_point(matrix.xform(p_global))) {
return nullptr;
}
Control *drag_preview = _gui_get_drag_preview();
if (!drag_preview || (c != drag_preview && !drag_preview->is_ancestor_of(c))) {
r_inv_xform = matrix;
return c;
}
return nullptr;
}
bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_check) {
// Attempt grab, try parent controls too.
CanvasItem *ci = p_at_control;
while (ci) {
Control *control = Object::cast_to<Control>(ci);
if (control) {
if (control->can_drop_data(p_at_pos, gui.drag_data)) {
if (!p_just_check) {
control->drop_data(p_at_pos, gui.drag_data);
}
return true;
}
if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) {
break;
}
}
p_at_pos = ci->get_transform().xform(p_at_pos);
if (ci->is_set_as_top_level()) {
break;
}
ci = ci->get_parent_item();
}
return false;
}
void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
ERR_FAIL_COND(p_event.is_null());
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid()) {
gui.key_event_accepted = false;
Control *over = nullptr;
Point2 mpos = mb->get_position();
gui.last_mouse_pos = mpos;
if (mb->is_pressed()) {
Size2 pos = mpos;
if (gui.mouse_focus_mask) {
// Do not steal mouse focus and stuff while a focus mask exists.
gui.mouse_focus_mask |= 1 << (mb->get_button_index() - 1); // Add the button to the mask.
} else {
gui.mouse_focus = gui_find_control(pos);
gui.last_mouse_focus = gui.mouse_focus;
if (!gui.mouse_focus) {
gui.mouse_focus_mask = 0;
return;
}
gui.mouse_focus_mask = 1 << (mb->get_button_index() - 1);
if (mb->get_button_index() == MOUSE_BUTTON_LEFT) {
gui.drag_accum = Vector2();
gui.drag_attempted = false;
}
}
mb = mb->xformed_by(Transform2D()); // Make a copy of the event.
mb->set_global_position(pos);
pos = gui.focus_inv_xform.xform(pos);
mb->set_position(pos);
#ifdef DEBUG_ENABLED
if (EngineDebugger::get_singleton() && gui.mouse_focus) {
Array arr;
arr.push_back(gui.mouse_focus->get_path());
arr.push_back(gui.mouse_focus->get_class());
EngineDebugger::get_singleton()->send_message("scene:click_ctrl", arr);
}
#endif
if (mb->get_button_index() == MOUSE_BUTTON_LEFT) { // Assign focus.
CanvasItem *ci = gui.mouse_focus;
while (ci) {
Control *control = Object::cast_to<Control>(ci);
if (control) {
if (control->get_focus_mode() != Control::FOCUS_NONE) {
if (control != gui.key_focus) {
control->grab_focus();
}
break;
}
if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) {
break;
}
}
if (ci->is_set_as_top_level()) {
break;
}
ci = ci->get_parent_item();
}
}
if (gui.mouse_focus && gui.mouse_focus->can_process()) {
_gui_call_input(gui.mouse_focus, mb);
}
set_input_as_handled();
if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
// Alternate drop use (when using force_drag(), as proposed by #5342).
if (gui.mouse_focus) {
_gui_drop(gui.mouse_focus, pos, false);
}
gui.drag_data = Variant();
gui.dragging = false;
Control *drag_preview = _gui_get_drag_preview();
if (drag_preview) {
memdelete(drag_preview);
gui.drag_preview_id = ObjectID();
}
_propagate_viewport_notification(this, NOTIFICATION_DRAG_END);
// Change mouse accordingly.
}
_gui_cancel_tooltip();
} else {
if (gui.drag_data.get_type() != Variant::NIL && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
if (gui.drag_mouse_over) {
_gui_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos, false);
}
Control *drag_preview = _gui_get_drag_preview();
if (drag_preview) {
memdelete(drag_preview);
gui.drag_preview_id = ObjectID();
}
gui.drag_data = Variant();
gui.dragging = false;
gui.drag_mouse_over = nullptr;
_propagate_viewport_notification(this, NOTIFICATION_DRAG_END);
// Change mouse accordingly.
}
gui.mouse_focus_mask &= ~(1 << (mb->get_button_index() - 1)); // Remove from mask.
if (!gui.mouse_focus) {
// Release event is only sent if a mouse focus (previously pressed button) exists.
return;
}
Size2 pos = mpos;
mb = mb->xformed_by(Transform2D()); // Make a copy.
mb->set_global_position(pos);
pos = gui.focus_inv_xform.xform(pos);
mb->set_position(pos);
Control *mouse_focus = gui.mouse_focus;
// Disable mouse focus if needed before calling input,
// this makes popups on mouse press event work better,
// as the release will never be received otherwise.
if (gui.mouse_focus_mask == 0) {
gui.mouse_focus = nullptr;
gui.forced_mouse_focus = false;
}
if (mouse_focus && mouse_focus->can_process()) {
_gui_call_input(mouse_focus, mb);
}
// In case the mouse was released after for example dragging a scrollbar,
// check whether the current control is different from the stored one. If
// it is different, rather than wait for it to be updated the next time the
// mouse is moved, notify the control so that it can e.g. drop the highlight.
// This code is duplicated from the mm.is_valid()-case further below.
if (gui.mouse_focus) {
over = gui.mouse_focus;
} else {
over = gui_find_control(mpos);
}
if (gui.mouse_focus_mask == 0 && over != gui.mouse_over) {
if (gui.mouse_over) {
_gui_call_notification(gui.mouse_over, Control::NOTIFICATION_MOUSE_EXIT);
}
_gui_cancel_tooltip();
if (over) {
_gui_call_notification(over, Control::NOTIFICATION_MOUSE_ENTER);
}
}
gui.mouse_over = over;
set_input_as_handled();
}
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
gui.key_event_accepted = false;
Point2 mpos = mm->get_position();
gui.last_mouse_pos = mpos;
Control *over = nullptr;
// Drag & drop.
if (!gui.drag_attempted && gui.mouse_focus && mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) {
gui.drag_accum += mm->get_relative();
float len = gui.drag_accum.length();
if (len > 10) {
{ // Attempt grab, try parent controls too.
CanvasItem *ci = gui.mouse_focus;
while (ci) {
Control *control = Object::cast_to<Control>(ci);
if (control) {
gui.dragging = true;
gui.drag_data = control->get_drag_data(control->get_global_transform_with_canvas().affine_inverse().xform(mpos) - gui.drag_accum);
if (gui.drag_data.get_type() != Variant::NIL) {
gui.mouse_focus = nullptr;
gui.forced_mouse_focus = false;
gui.mouse_focus_mask = 0;
break;
} else {
Control *drag_preview = _gui_get_drag_preview();
if (drag_preview) {
ERR_PRINT("Don't set a drag preview and return null data. Preview was deleted and drag request ignored.");
memdelete(drag_preview);
gui.drag_preview_id = ObjectID();
}
gui.dragging = false;
}
if (control->data.mouse_filter == Control::MOUSE_FILTER_STOP) {
break;
}
}
if (ci->is_set_as_top_level()) {
break;
}
ci = ci->get_parent_item();
}
}
gui.drag_attempted = true;
if (gui.drag_data.get_type() != Variant::NIL) {
_propagate_viewport_notification(this, NOTIFICATION_DRAG_BEGIN);
}
}
}
// These sections of code are reused in the mb.is_valid() case further up
// for the purpose of notifying controls about potential changes in focus
// when the mousebutton is released.
if (gui.mouse_focus) {
over = gui.mouse_focus;
} else {
over = gui_find_control(mpos);
}
if (over != gui.mouse_over) {
if (gui.mouse_over) {
_gui_call_notification(gui.mouse_over, Control::NOTIFICATION_MOUSE_EXIT);
}
_gui_cancel_tooltip();
if (over) {
_gui_call_notification(over, Control::NOTIFICATION_MOUSE_ENTER);
}
}
gui.mouse_over = over;
DisplayServer::CursorShape ds_cursor_shape = (DisplayServer::CursorShape)Input::get_singleton()->get_default_cursor_shape();
if (over) {
Transform2D localizer = over->get_global_transform_with_canvas().affine_inverse();
Size2 pos = localizer.xform(mpos);
Vector2 speed = localizer.basis_xform(mm->get_speed());
Vector2 rel = localizer.basis_xform(mm->get_relative());
mm = mm->xformed_by(Transform2D()); // Make a copy.
mm->set_global_position(mpos);
mm->set_speed(speed);
mm->set_relative(rel);
if (mm->get_button_mask() == 0) {
// Nothing pressed.
bool can_tooltip = true;
bool is_tooltip_shown = false;
if (gui.tooltip_popup) {
if (can_tooltip && gui.tooltip_control) {
String tooltip = _gui_get_tooltip(over, gui.tooltip_control->get_global_transform().xform_inv(mpos));
if (tooltip.length() == 0) {
_gui_cancel_tooltip();
} else if (gui.tooltip_label) {
if (tooltip == gui.tooltip_label->get_text()) {
is_tooltip_shown = true;
}
} else {
Variant t = gui.tooltip_popup->call("get_tooltip_text");
if (t.get_type() == Variant::STRING) {
if (tooltip == String(t)) {
is_tooltip_shown = true;
}
} else {
is_tooltip_shown = true; // Nothing to compare against, likely using custom control, so if it changes there is nothing we can do.
}
}
} else {
_gui_cancel_tooltip();
}
}
if (can_tooltip && !is_tooltip_shown) {
if (gui.tooltip_timer.is_valid()) {
gui.tooltip_timer->release_connections();
gui.tooltip_timer = Ref<SceneTreeTimer>();
}
gui.tooltip_control = over;
gui.tooltip_pos = over->get_screen_transform().xform(pos);
gui.tooltip_timer = get_tree()->create_timer(gui.tooltip_delay);
gui.tooltip_timer->set_ignore_time_scale(true);
gui.tooltip_timer->connect("timeout", callable_mp(this, &Viewport::_gui_show_tooltip));
}
}
mm->set_position(pos);
Control::CursorShape cursor_shape = Control::CURSOR_ARROW;
{
Control *c = over;
Vector2 cpos = pos;
while (c) {
if (gui.mouse_focus_mask != 0 || c->has_point(cpos)) {
cursor_shape = c->get_cursor_shape(cpos);
} else {
cursor_shape = Control::CURSOR_ARROW;
}
cpos = c->get_transform().xform(cpos);
if (cursor_shape != Control::CURSOR_ARROW) {
break;
}
if (c->data.mouse_filter == Control::MOUSE_FILTER_STOP) {
break;
}
if (c->is_set_as_top_level()) {
break;
}
c = c->get_parent_control();
}
}
ds_cursor_shape = (DisplayServer::CursorShape)cursor_shape;
if (over && over->can_process()) {
_gui_call_input(over, mm);
}
set_input_as_handled();
}
if (gui.drag_data.get_type() != Variant::NIL) {
// Handle drag & drop.
Control *drag_preview = _gui_get_drag_preview();
if (drag_preview) {
drag_preview->set_position(mpos);
}
gui.drag_mouse_over = over;
gui.drag_mouse_over_pos = Vector2();
// Find the window this is above of.
// See if there is an embedder.
Viewport *embedder = nullptr;
Vector2 viewport_pos;
if (is_embedding_subwindows()) {
embedder = this;
viewport_pos = mpos;
} else {
// Not an embedder, but may be a subwindow of an embedder.
Window *w = Object::cast_to<Window>(this);
if (w) {
if (w->is_embedded()) {
embedder = w->_get_embedder();
Transform2D ai = (get_final_transform().affine_inverse() * _get_input_pre_xform()).affine_inverse();
viewport_pos = ai.xform(mpos) + w->get_position(); // To parent coords.
}
}
}
Viewport *viewport_under = nullptr;
if (embedder) {
// Use embedder logic.
for (int i = embedder->gui.sub_windows.size() - 1; i >= 0; i--) {
Window *sw = embedder->gui.sub_windows[i].window;
Rect2 swrect = Rect2i(sw->get_position(), sw->get_size());
if (!sw->get_flag(Window::FLAG_BORDERLESS)) {
int title_height = sw->get_theme_constant(SNAME("title_height"));
swrect.position.y -= title_height;
swrect.size.y += title_height;
}
if (swrect.has_point(viewport_pos)) {
viewport_under = sw;
viewport_pos -= sw->get_position();
}
}
if (!viewport_under) {
// Not in a subwindow, likely in embedder.
viewport_under = embedder;
}
} else {
// Use DisplayServer logic.
Vector2i screen_mouse_pos = DisplayServer::get_singleton()->mouse_get_position();
DisplayServer::WindowID window_id = DisplayServer::get_singleton()->get_window_at_screen_position(screen_mouse_pos);
if (window_id != DisplayServer::INVALID_WINDOW_ID) {
ObjectID object_under = DisplayServer::get_singleton()->window_get_attached_instance_id(window_id);
if (object_under != ObjectID()) { // Fetch window.
Window *w = Object::cast_to<Window>(ObjectDB::get_instance(object_under));
if (w) {
viewport_under = w;
viewport_pos = screen_mouse_pos - w->get_position();
}
}
}
}
if (viewport_under) {
Transform2D ai = (viewport_under->get_final_transform().affine_inverse() * viewport_under->_get_input_pre_xform());
viewport_pos = ai.xform(viewport_pos);
// Find control under at position.
gui.drag_mouse_over = viewport_under->gui_find_control(viewport_pos);
if (gui.drag_mouse_over) {
Transform2D localizer = gui.drag_mouse_over->get_global_transform_with_canvas().affine_inverse();
gui.drag_mouse_over_pos = localizer.xform(viewport_pos);
if (mm->get_button_mask() & MOUSE_BUTTON_MASK_LEFT) {
bool can_drop = _gui_drop(gui.drag_mouse_over, gui.drag_mouse_over_pos, true);
if (!can_drop) {
ds_cursor_shape = DisplayServer::CURSOR_FORBIDDEN;
} else {
ds_cursor_shape = DisplayServer::CURSOR_CAN_DROP;
}
}
}
} else {
gui.drag_mouse_over = nullptr;
}
}
DisplayServer::get_singleton()->cursor_set_shape(ds_cursor_shape);
}
Ref<InputEventScreenTouch> touch_event = p_event;
if (touch_event.is_valid()) {
Size2 pos = touch_event->get_position();
if (touch_event->is_pressed()) {
Control *over = gui_find_control(pos);
if (over) {
if (over->can_process()) {
touch_event = touch_event->xformed_by(Transform2D()); // Make a copy.
if (over == gui.mouse_focus) {
pos = gui.focus_inv_xform.xform(pos);
} else {
pos = over->get_global_transform_with_canvas().affine_inverse().xform(pos);
}
touch_event->set_position(pos);
_gui_call_input(over, touch_event);
}
set_input_as_handled();
return;
}
} else if (touch_event->get_index() == 0 && gui.last_mouse_focus) {
if (gui.last_mouse_focus->can_process()) {
touch_event = touch_event->xformed_by(Transform2D()); // Make a copy.
touch_event->set_position(gui.focus_inv_xform.xform(pos));
_gui_call_input(gui.last_mouse_focus, touch_event);
}
set_input_as_handled();
return;
}
}
Ref<InputEventGesture> gesture_event = p_event;
if (gesture_event.is_valid()) {
gui.key_event_accepted = false;
_gui_cancel_tooltip();
Size2 pos = gesture_event->get_position();
Control *over = gui_find_control(pos);
if (over) {
if (over->can_process()) {
gesture_event = gesture_event->xformed_by(Transform2D()); // Make a copy.
if (over == gui.mouse_focus) {
pos = gui.focus_inv_xform.xform(pos);
} else {
pos = over->get_global_transform_with_canvas().affine_inverse().xform(pos);
}
gesture_event->set_position(pos);
_gui_call_input(over, gesture_event);
}
set_input_as_handled();
return;
}
}
Ref<InputEventScreenDrag> drag_event = p_event;
if (drag_event.is_valid()) {
Control *over = gui.mouse_focus;
if (!over) {
over = gui_find_control(drag_event->get_position());
}
if (over) {
if (over->can_process()) {
Transform2D localizer = over->get_global_transform_with_canvas().affine_inverse();
Size2 pos = localizer.xform(drag_event->get_position());
Vector2 speed = localizer.basis_xform(drag_event->get_speed());
Vector2 rel = localizer.basis_xform(drag_event->get_relative());
drag_event = drag_event->xformed_by(Transform2D()); // Make a copy.
drag_event->set_speed(speed);
drag_event->set_relative(rel);
drag_event->set_position(pos);
_gui_call_input(over, drag_event);
}
set_input_as_handled();
return;
}
}
if (mm.is_null() && mb.is_null() && p_event->is_action_type()) {
if (gui.key_focus && !gui.key_focus->is_visible_in_tree()) {
gui.key_focus->release_focus();
}
if (gui.key_focus) {
gui.key_event_accepted = false;
if (gui.key_focus->can_process()) {
gui.key_focus->_call_gui_input(p_event);
}
if (gui.key_event_accepted) {
set_input_as_handled();
return;
}
}
Control *from = gui.key_focus ? gui.key_focus : nullptr;
if (from && p_event->is_pressed()) {
Control *next = nullptr;
if (p_event->is_action_pressed("ui_focus_next", true, true)) {
next = from->find_next_valid_focus();
}
if (p_event->is_action_pressed("ui_focus_prev", true, true)) {
next = from->find_prev_valid_focus();
}
if (p_event->is_action_pressed("ui_up", true, true)) {
next = from->_get_focus_neighbor(SIDE_TOP);
}
if (p_event->is_action_pressed("ui_left", true, true)) {
next = from->_get_focus_neighbor(SIDE_LEFT);
}
if (p_event->is_action_pressed("ui_right", true, true)) {
next = from->_get_focus_neighbor(SIDE_RIGHT);
}
if (p_event->is_action_pressed("ui_down", true, true)) {
next = from->_get_focus_neighbor(SIDE_BOTTOM);
}
if (next) {
next->grab_focus();
set_input_as_handled();
}
}
}
}
List<Control *>::Element *Viewport::_gui_add_root_control(Control *p_control) {
gui.roots_order_dirty = true;
return gui.roots.push_back(p_control);
}
void Viewport::_gui_set_root_order_dirty() {
gui.roots_order_dirty = true;
}
void Viewport::_gui_force_drag(Control *p_base, const Variant &p_data, Control *p_control) {
ERR_FAIL_COND_MSG(p_data.get_type() == Variant::NIL, "Drag data must be a value.");
gui.dragging = true;
gui.drag_data = p_data;
gui.mouse_focus = nullptr;
if (p_control) {
_gui_set_drag_preview(p_base, p_control);
}
}
void Viewport::_gui_set_drag_preview(Control *p_base, Control *p_control) {
ERR_FAIL_NULL(p_control);
ERR_FAIL_COND(!Object::cast_to<Control>((Object *)p_control));
ERR_FAIL_COND(p_control->is_inside_tree());
ERR_FAIL_COND(p_control->get_parent() != nullptr);
Control *drag_preview = _gui_get_drag_preview();
if (drag_preview) {
memdelete(drag_preview);
}
p_control->set_as_top_level(true);
p_control->set_position(gui.last_mouse_pos);
p_base->get_root_parent_control()->add_child(p_control); // Add as child of viewport.
p_control->raise();
gui.drag_preview_id = p_control->get_instance_id();
}
Control *Viewport::_gui_get_drag_preview() {
if (gui.drag_preview_id.is_null()) {
return nullptr;
} else {
Control *drag_preview = Object::cast_to<Control>(ObjectDB::get_instance(gui.drag_preview_id));
if (!drag_preview) {
ERR_PRINT("Don't free the control set as drag preview.");
gui.drag_preview_id = ObjectID();
}
return drag_preview;
}
}
void Viewport::_gui_remove_root_control(List<Control *>::Element *RI) {
gui.roots.erase(RI);
}
void Viewport::_gui_unfocus_control(Control *p_control) {
if (gui.key_focus == p_control) {
gui.key_focus->release_focus();
}
}
void Viewport::_gui_hide_control(Control *p_control) {
if (gui.mouse_focus == p_control) {
_drop_mouse_focus();
}
if (gui.key_focus == p_control) {
_gui_remove_focus();
}
if (gui.mouse_over == p_control) {
gui.mouse_over = nullptr;
}
if (gui.drag_mouse_over == p_control) {
gui.drag_mouse_over = nullptr;
}
if (gui.tooltip_control == p_control) {
_gui_cancel_tooltip();
}
}
void Viewport::_gui_remove_control(Control *p_control) {
if (gui.mouse_focus == p_control) {
gui.mouse_focus = nullptr;
gui.forced_mouse_focus = false;
gui.mouse_focus_mask = 0;
}
if (gui.last_mouse_focus == p_control) {
gui.last_mouse_focus = nullptr;
}
if (gui.key_focus == p_control) {
gui.key_focus = nullptr;
}
if (gui.mouse_over == p_control) {
gui.mouse_over = nullptr;
}
if (gui.drag_mouse_over == p_control) {
gui.drag_mouse_over = nullptr;
}
if (gui.tooltip_control == p_control) {
gui.tooltip_control = nullptr;
}
}
Window *Viewport::get_base_window() const {
ERR_FAIL_COND_V(!is_inside_tree(), nullptr);
Viewport *v = const_cast<Viewport *>(this);
Window *w = Object::cast_to<Window>(v);
while (!w) {
v = v->get_parent_viewport();
w = Object::cast_to<Window>(v);
}
return w;
}
void Viewport::_gui_remove_focus_for_window(Node *p_window) {
if (get_base_window() == p_window) {
_gui_remove_focus();
}
}
void Viewport::_gui_remove_focus() {
if (gui.key_focus) {
Node *f = gui.key_focus;
gui.key_focus = nullptr;
f->notification(Control::NOTIFICATION_FOCUS_EXIT, true);
}
}
bool Viewport::_gui_control_has_focus(const Control *p_control) {
return gui.key_focus == p_control;
}
void Viewport::_gui_control_grab_focus(Control *p_control) {
if (gui.key_focus && gui.key_focus == p_control) {
// No need for change.
return;
}
get_tree()->call_group_flags(SceneTree::GROUP_CALL_REALTIME, "_viewports", "_gui_remove_focus_for_window", (Node *)get_base_window());
gui.key_focus = p_control;
emit_signal(SNAME("gui_focus_changed"), p_control);
p_control->notification(Control::NOTIFICATION_FOCUS_ENTER);
p_control->update();
}
void Viewport::_gui_accept_event() {
gui.key_event_accepted = true;
if (is_inside_tree()) {
set_input_as_handled();
}
}
void Viewport::_drop_mouse_focus() {
Control *c = gui.mouse_focus;
int mask = gui.mouse_focus_mask;
gui.mouse_focus = nullptr;
gui.forced_mouse_focus = false;
gui.mouse_focus_mask = 0;
for (int i = 0; i < 3; i++) {
if (mask & (1 << i)) {
Ref<InputEventMouseButton> mb;
mb.instantiate();
mb->set_position(c->get_local_mouse_position());
mb->set_global_position(c->get_local_mouse_position());
mb->set_button_index(MouseButton(i + 1));
mb->set_pressed(false);
c->_call_gui_input(mb);
}
}
}
void Viewport::_drop_physics_mouseover(bool p_paused_only) {
physics_has_last_mousepos = false;
_cleanup_mouseover_colliders(true, p_paused_only);
#ifndef _3D_DISABLED
if (physics_object_over.is_valid()) {
CollisionObject3D *co = Object::cast_to<CollisionObject3D>(ObjectDB::get_instance(physics_object_over));
if (co) {
if (!(p_paused_only && co->can_process())) {
co->_mouse_exit();
physics_object_over = ObjectID();
physics_object_capture = ObjectID();
}
}
}
#endif // _3D_DISABLED
}
void Viewport::_cleanup_mouseover_colliders(bool p_clean_all_frames, bool p_paused_only, uint64_t p_frame_reference) {
List<Map<ObjectID, uint64_t>::Element *> to_erase;
for (Map<ObjectID, uint64_t>::Element *E = physics_2d_mouseover.front(); E; E = E->next()) {
if (!p_clean_all_frames && E->get() == p_frame_reference) {
continue;
}
Object *o = ObjectDB::get_instance(E->key());
if (o) {
CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o);
if (co) {
if (p_clean_all_frames && p_paused_only && co->can_process()) {
continue;
}
co->_mouse_exit();
}
}
to_erase.push_back(E);
}
while (to_erase.size()) {
physics_2d_mouseover.erase(to_erase.front()->get());
to_erase.pop_front();
}
// Per-shape.
List<Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *> shapes_to_erase;
for (Map<Pair<ObjectID, int>, uint64_t, PairSort<ObjectID, int>>::Element *E = physics_2d_shape_mouseover.front(); E; E = E->next()) {
if (!p_clean_all_frames && E->get() == p_frame_reference) {
continue;
}
Object *o = ObjectDB::get_instance(E->key().first);
if (o) {
CollisionObject2D *co = Object::cast_to<CollisionObject2D>(o);
if (co) {
if (p_clean_all_frames && p_paused_only && co->can_process()) {
continue;
}
co->_mouse_shape_exit(E->key().second);
}
}
shapes_to_erase.push_back(E);
}
while (shapes_to_erase.size()) {
physics_2d_shape_mouseover.erase(shapes_to_erase.front()->get());
shapes_to_erase.pop_front();
}
}
Control *Viewport::_gui_get_focus_owner() {
return gui.key_focus;
}
void Viewport::_gui_grab_click_focus(Control *p_control) {
gui.mouse_click_grabber = p_control;
call_deferred(SNAME("_post_gui_grab_click_focus"));
}
void Viewport::_post_gui_grab_click_focus() {
Control *focus_grabber = gui.mouse_click_grabber;
if (!focus_grabber) {
// Redundant grab requests were made.
return;
}
gui.mouse_click_grabber = nullptr;
if (gui.mouse_focus) {
if (gui.mouse_focus == focus_grabber) {
return;
}
int mask = gui.mouse_focus_mask;
Point2 click = gui.mouse_focus->get_global_transform_with_canvas().affine_inverse().xform(gui.last_mouse_pos);
for (int i = 0; i < 3; i++) {
if (mask & (1 << i)) {
Ref<InputEventMouseButton> mb;
mb.instantiate();
// Send unclick.
mb->set_position(click);
mb->set_button_index(MouseButton(i + 1));
mb->set_pressed(false);
gui.mouse_focus->_call_gui_input(mb);
}
}
gui.mouse_focus = focus_grabber;
gui.focus_inv_xform = gui.mouse_focus->get_global_transform_with_canvas().affine_inverse();
click = gui.mouse_focus->get_global_transform_with_canvas().affine_inverse().xform(gui.last_mouse_pos);
for (int i = 0; i < 3; i++) {
if (mask & (1 << i)) {
Ref<InputEventMouseButton> mb;
mb.instantiate();
// Send click.
mb->set_position(click);
mb->set_button_index(MouseButton(i + 1));
mb->set_pressed(true);
MessageQueue::get_singleton()->push_callable(callable_mp(gui.mouse_focus, &Control::_call_gui_input), mb);
}
}
}
}
///////////////////////////////
void Viewport::push_text_input(const String &p_text) {
if (gui.subwindow_focused) {
gui.subwindow_focused->push_text_input(p_text);
return;
}
if (gui.key_focus) {
gui.key_focus->call("set_text", p_text);
}
}
Viewport::SubWindowResize Viewport::_sub_window_get_resize_margin(Window *p_subwindow, const Point2 &p_point) {
if (p_subwindow->get_flag(Window::FLAG_BORDERLESS)) {
return SUB_WINDOW_RESIZE_DISABLED;
}
Rect2i r = Rect2i(p_subwindow->get_position(), p_subwindow->get_size());
int title_height = p_subwindow->get_theme_constant(SNAME("title_height"));
r.position.y -= title_height;
r.size.y += title_height;
if (r.has_point(p_point)) {
return SUB_WINDOW_RESIZE_DISABLED; // It's inside, so no resize.
}
int dist_x = p_point.x < r.position.x ? (p_point.x - r.position.x) : (p_point.x > (r.position.x + r.size.x) ? (p_point.x - (r.position.x + r.size.x)) : 0);
int dist_y = p_point.y < r.position.y ? (p_point.y - r.position.y) : (p_point.y > (r.position.y + r.size.y) ? (p_point.y - (r.position.y + r.size.y)) : 0);
int limit = p_subwindow->get_theme_constant(SNAME("resize_margin"));
if (ABS(dist_x) > limit) {
return SUB_WINDOW_RESIZE_DISABLED;
}
if (ABS(dist_y) > limit) {
return SUB_WINDOW_RESIZE_DISABLED;
}
if (dist_x < 0 && dist_y < 0) {
return SUB_WINDOW_RESIZE_TOP_LEFT;
}
if (dist_x == 0 && dist_y < 0) {
return SUB_WINDOW_RESIZE_TOP;
}
if (dist_x > 0 && dist_y < 0) {
return SUB_WINDOW_RESIZE_TOP_RIGHT;
}
if (dist_x < 0 && dist_y == 0) {
return SUB_WINDOW_RESIZE_LEFT;
}
if (dist_x > 0 && dist_y == 0) {
return SUB_WINDOW_RESIZE_RIGHT;
}
if (dist_x < 0 && dist_y > 0) {
return SUB_WINDOW_RESIZE_BOTTOM_LEFT;
}
if (dist_x == 0 && dist_y > 0) {
return SUB_WINDOW_RESIZE_BOTTOM;
}
if (dist_x > 0 && dist_y > 0) {
return SUB_WINDOW_RESIZE_BOTTOM_RIGHT;
}
return SUB_WINDOW_RESIZE_DISABLED;
}
bool Viewport::_sub_windows_forward_input(const Ref<InputEvent> &p_event) {
if (gui.subwindow_drag != SUB_WINDOW_DRAG_DISABLED) {
ERR_FAIL_COND_V(gui.subwindow_focused == nullptr, false);
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && !mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
if (gui.subwindow_drag == SUB_WINDOW_DRAG_CLOSE) {
if (gui.subwindow_drag_close_rect.has_point(mb->get_position())) {
// Close window.
gui.subwindow_focused->_event_callback(DisplayServer::WINDOW_EVENT_CLOSE_REQUEST);
}
}
gui.subwindow_drag = SUB_WINDOW_DRAG_DISABLED;
if (gui.subwindow_focused != nullptr) { // May have been erased.
_sub_window_update(gui.subwindow_focused);
}
}
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
if (gui.subwindow_drag == SUB_WINDOW_DRAG_MOVE) {
Vector2 diff = mm->get_position() - gui.subwindow_drag_from;
Rect2i new_rect(gui.subwindow_drag_pos + diff, gui.subwindow_focused->get_size());
if (gui.subwindow_focused->is_clamped_to_embedder()) {
Size2i limit = get_visible_rect().size;
if (new_rect.position.x + new_rect.size.x > limit.x) {
new_rect.position.x = limit.x - new_rect.size.x;
}
if (new_rect.position.y + new_rect.size.y > limit.y) {
new_rect.position.y = limit.y - new_rect.size.y;
}
if (new_rect.position.x < 0) {
new_rect.position.x = 0;
}
int title_height = gui.subwindow_focused->get_flag(Window::FLAG_BORDERLESS) ? 0 : gui.subwindow_focused->get_theme_constant(SNAME("title_height"));
if (new_rect.position.y < title_height) {
new_rect.position.y = title_height;
}
}
gui.subwindow_focused->_rect_changed_callback(new_rect);
}
if (gui.subwindow_drag == SUB_WINDOW_DRAG_CLOSE) {
gui.subwindow_drag_close_inside = gui.subwindow_drag_close_rect.has_point(mm->get_position());
}
if (gui.subwindow_drag == SUB_WINDOW_DRAG_RESIZE) {
Vector2i diff = mm->get_position() - gui.subwindow_drag_from;
Size2i min_size = gui.subwindow_focused->get_min_size();
if (gui.subwindow_focused->is_wrapping_controls()) {
Size2i cms = gui.subwindow_focused->get_contents_minimum_size();
min_size.x = MAX(cms.x, min_size.x);
min_size.y = MAX(cms.y, min_size.y);
}
min_size.x = MAX(min_size.x, 1);
min_size.y = MAX(min_size.y, 1);
Rect2i r = gui.subwindow_resize_from_rect;
Size2i limit = r.size - min_size;
switch (gui.subwindow_resize_mode) {
case SUB_WINDOW_RESIZE_TOP_LEFT: {
diff.x = MIN(diff.x, limit.x);
diff.y = MIN(diff.y, limit.y);
r.position += diff;
r.size -= diff;
} break;
case SUB_WINDOW_RESIZE_TOP: {
diff.x = 0;
diff.y = MIN(diff.y, limit.y);
r.position += diff;
r.size -= diff;
} break;
case SUB_WINDOW_RESIZE_TOP_RIGHT: {
diff.x = MAX(diff.x, -limit.x);
diff.y = MIN(diff.y, limit.y);
r.position.y += diff.y;
r.size.y -= diff.y;
r.size.x += diff.x;
} break;
case SUB_WINDOW_RESIZE_LEFT: {
diff.x = MIN(diff.x, limit.x);
diff.y = 0;
r.position += diff;
r.size -= diff;
} break;
case SUB_WINDOW_RESIZE_RIGHT: {
diff.x = MAX(diff.x, -limit.x);
r.size.x += diff.x;
} break;
case SUB_WINDOW_RESIZE_BOTTOM_LEFT: {
diff.x = MIN(diff.x, limit.x);
diff.y = MAX(diff.y, -limit.y);
r.position.x += diff.x;
r.size.x -= diff.x;
r.size.y += diff.y;
} break;
case SUB_WINDOW_RESIZE_BOTTOM: {
diff.y = MAX(diff.y, -limit.y);
r.size.y += diff.y;
} break;
case SUB_WINDOW_RESIZE_BOTTOM_RIGHT: {
diff.x = MAX(diff.x, -limit.x);
diff.y = MAX(diff.y, -limit.y);
r.size += diff;
} break;
default: {
}
}
gui.subwindow_focused->_rect_changed_callback(r);
}
if (gui.subwindow_focused) { // May have been erased.
_sub_window_update(gui.subwindow_focused);
}
}
return true; // Handled.
}
Ref<InputEventMouseButton> mb = p_event;
// If the event is a mouse button, we need to check whether another window was clicked.
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MOUSE_BUTTON_LEFT) {
bool click_on_window = false;
for (int i = gui.sub_windows.size() - 1; i >= 0; i--) {
SubWindow &sw = gui.sub_windows.write[i];
// Clicked inside window?
Rect2i r = Rect2i(sw.window->get_position(), sw.window->get_size());
if (!sw.window->get_flag(Window::FLAG_BORDERLESS)) {
// Check top bar.
int title_height = sw.window->get_theme_constant(SNAME("title_height"));
Rect2i title_bar = r;
title_bar.position.y -= title_height;
title_bar.size.y = title_height;
if (title_bar.has_point(mb->get_position())) {
click_on_window = true;
int close_h_ofs = sw.window->get_theme_constant(SNAME("close_h_ofs"));
int close_v_ofs = sw.window->get_theme_constant(SNAME("close_v_ofs"));
Ref<Texture2D> close_icon = sw.window->get_theme_icon(SNAME("close"));
Rect2 close_rect;
close_rect.position = Vector2(r.position.x + r.size.x - close_v_ofs, r.position.y - close_h_ofs);
close_rect.size = close_icon->get_size();
if (gui.subwindow_focused != sw.window) {
// Refocus.
_sub_window_grab_focus(sw.window);
}
if (close_rect.has_point(mb->get_position())) {
gui.subwindow_drag = SUB_WINDOW_DRAG_CLOSE;
gui.subwindow_drag_close_inside = true; // Starts inside.
gui.subwindow_drag_close_rect = close_rect;
} else {
gui.subwindow_drag = SUB_WINDOW_DRAG_MOVE;
}
gui.subwindow_drag_from = mb->get_position();
gui.subwindow_drag_pos = sw.window->get_position();
_sub_window_update(sw.window);
} else {
gui.subwindow_resize_mode = _sub_window_get_resize_margin(sw.window, mb->get_position());
if (gui.subwindow_resize_mode != SUB_WINDOW_RESIZE_DISABLED) {
gui.subwindow_resize_from_rect = r;
gui.subwindow_drag_from = mb->get_position();
gui.subwindow_drag = SUB_WINDOW_DRAG_RESIZE;
click_on_window = true;
}
}
}
if (!click_on_window && r.has_point(mb->get_position())) {
// Clicked, see if it needs to fetch focus.
if (gui.subwindow_focused != sw.window) {
// Refocus.
_sub_window_grab_focus(sw.window);
}
click_on_window = true;
}
if (click_on_window) {
break;
}
}
if (!click_on_window && gui.subwindow_focused) {
// No window found and clicked, remove focus.
_sub_window_grab_focus(nullptr);
}
}
if (gui.subwindow_focused) {
Ref<InputEventMouseMotion> mm = p_event;
if (mm.is_valid()) {
SubWindowResize resize = _sub_window_get_resize_margin(gui.subwindow_focused, mm->get_position());
if (resize != SUB_WINDOW_RESIZE_DISABLED) {
DisplayServer::CursorShape shapes[SUB_WINDOW_RESIZE_MAX] = {
DisplayServer::CURSOR_ARROW,
DisplayServer::CURSOR_FDIAGSIZE,
DisplayServer::CURSOR_VSIZE,
DisplayServer::CURSOR_BDIAGSIZE,
DisplayServer::CURSOR_HSIZE,
DisplayServer::CURSOR_HSIZE,
DisplayServer::CURSOR_BDIAGSIZE,
DisplayServer::CURSOR_VSIZE,
DisplayServer::CURSOR_FDIAGSIZE
};
DisplayServer::get_singleton()->cursor_set_shape(shapes[resize]);
return true; // Reserved for showing the resize cursor.
}
}
}
if (gui.subwindow_drag != SUB_WINDOW_DRAG_DISABLED) {
return true; // Dragging, don't pass the event.
}
if (!gui.subwindow_focused) {
return false;
}
Transform2D window_ofs;
window_ofs.set_origin(-gui.subwindow_focused->get_position());
Ref<InputEvent> ev = p_event->xformed_by(window_ofs);
gui.subwindow_focused->_window_input(ev);
return true;
}
void Viewport::push_input(const Ref<InputEvent> &p_event, bool p_local_coords) {
ERR_FAIL_COND(!is_inside_tree());
if (disable_input) {
return;
}
if (Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root()->is_ancestor_of(this)) {
return;
}
local_input_handled = false;
Ref<InputEvent> ev;
if (!p_local_coords) {
ev = _make_input_local(p_event);
} else {
ev = p_event;
}
if (is_embedding_subwindows() && _sub_windows_forward_input(p_event)) {
set_input_as_handled();
return;
}
if (!_can_consume_input_events()) {
return;
}
if (!is_input_handled()) {
get_tree()->_call_input_pause(input_group, SceneTree::CALL_INPUT_TYPE_INPUT, ev, this); //not a bug, must happen before GUI, order is _input -> gui input -> _unhandled input
}
if (!is_input_handled()) {
_gui_input_event(ev);
}
event_count++;
}
void Viewport::push_unhandled_input(const Ref<InputEvent> &p_event, bool p_local_coords) {
ERR_FAIL_COND(p_event.is_null());
ERR_FAIL_COND(!is_inside_tree());
local_input_handled = false;
if (disable_input || !_can_consume_input_events()) {
return;
}
if (Engine::get_singleton()->is_editor_hint() && get_tree()->get_edited_scene_root() && get_tree()->get_edited_scene_root()->is_ancestor_of(this)) {
return;
}
Ref<InputEvent> ev;
if (!p_local_coords) {
ev = _make_input_local(p_event);
} else {
ev = p_event;
}
// Unhandled Input.
get_tree()->_call_input_pause(unhandled_input_group, SceneTree::CALL_INPUT_TYPE_UNHANDLED_INPUT, ev, this);
// Unhandled key Input - used for performance reasons - This is called a lot less than _unhandled_input since it ignores MouseMotion, etc.
if (!is_input_handled() && (Object::cast_to<InputEventKey>(*ev) != nullptr || Object::cast_to<InputEventShortcut>(*ev) != nullptr)) {
get_tree()->_call_input_pause(unhandled_key_input_group, SceneTree::CALL_INPUT_TYPE_UNHANDLED_KEY_INPUT, ev, this);
}
if (physics_object_picking && !is_input_handled()) {
if (Input::get_singleton()->get_mouse_mode() != Input::MOUSE_MODE_CAPTURED &&
(Object::cast_to<InputEventMouseButton>(*ev) ||
Object::cast_to<InputEventMouseMotion>(*ev) ||
Object::cast_to<InputEventScreenDrag>(*ev) ||
Object::cast_to<InputEventScreenTouch>(*ev) ||
Object::cast_to<InputEventKey>(*ev) // To remember state.
)) {
physics_picking_events.push_back(ev);
}
}
}
void Viewport::set_physics_object_picking(bool p_enable) {
physics_object_picking = p_enable;
if (physics_object_picking) {
add_to_group("_picking_viewports");
} else {
physics_picking_events.clear();
if (is_in_group("_picking_viewports")) {
remove_from_group("_picking_viewports");
}
}
}
bool Viewport::get_physics_object_picking() {
return physics_object_picking;
}
Vector2 Viewport::get_camera_coords(const Vector2 &p_viewport_coords) const {
Transform2D xf = get_final_transform();
return xf.xform(p_viewport_coords);
}
Vector2 Viewport::get_camera_rect_size() const {
return size;
}
void Viewport::set_disable_input(bool p_disable) {
disable_input = p_disable;
}
bool Viewport::is_input_disabled() const {
return disable_input;
}
Variant Viewport::gui_get_drag_data() const {
return gui.drag_data;
}
TypedArray<String> Viewport::get_configuration_warnings() const {
TypedArray<String> warnings = Node::get_configuration_warnings();
if (size.x == 0 || size.y == 0) {
warnings.push_back(TTR("Viewport size must be greater than 0 to render anything."));
}
return warnings;
}
void Viewport::gui_reset_canvas_sort_index() {
gui.canvas_sort_index = 0;
}
int Viewport::gui_get_canvas_sort_index() {
return gui.canvas_sort_index++;
}
void Viewport::set_msaa(MSAA p_msaa) {
ERR_FAIL_INDEX(p_msaa, MSAA_MAX);
if (msaa == p_msaa) {
return;
}
msaa = p_msaa;
RS::get_singleton()->viewport_set_msaa(viewport, RS::ViewportMSAA(p_msaa));
}
Viewport::MSAA Viewport::get_msaa() const {
return msaa;
}
void Viewport::set_screen_space_aa(ScreenSpaceAA p_screen_space_aa) {
ERR_FAIL_INDEX(p_screen_space_aa, SCREEN_SPACE_AA_MAX);
if (screen_space_aa == p_screen_space_aa) {
return;
}
screen_space_aa = p_screen_space_aa;
RS::get_singleton()->viewport_set_screen_space_aa(viewport, RS::ViewportScreenSpaceAA(p_screen_space_aa));
}
Viewport::ScreenSpaceAA Viewport::get_screen_space_aa() const {
return screen_space_aa;
}
void Viewport::set_use_debanding(bool p_use_debanding) {
if (use_debanding == p_use_debanding) {
return;
}
use_debanding = p_use_debanding;
RS::get_singleton()->viewport_set_use_debanding(viewport, p_use_debanding);
}
bool Viewport::is_using_debanding() const {
return use_debanding;
}
void Viewport::set_lod_threshold(float p_pixels) {
lod_threshold = p_pixels;
RS::get_singleton()->viewport_set_lod_threshold(viewport, lod_threshold);
}
float Viewport::get_lod_threshold() const {
return lod_threshold;
}
void Viewport::set_use_occlusion_culling(bool p_use_occlusion_culling) {
if (use_occlusion_culling == p_use_occlusion_culling) {
return;
}
use_occlusion_culling = p_use_occlusion_culling;
RS::get_singleton()->viewport_set_use_occlusion_culling(viewport, p_use_occlusion_culling);
notify_property_list_changed();
}
bool Viewport::is_using_occlusion_culling() const {
return use_occlusion_culling;
}
void Viewport::set_debug_draw(DebugDraw p_debug_draw) {
debug_draw = p_debug_draw;
RS::get_singleton()->viewport_set_debug_draw(viewport, RS::ViewportDebugDraw(p_debug_draw));
}
Viewport::DebugDraw Viewport::get_debug_draw() const {
return debug_draw;
}
int Viewport::get_render_info(RenderInfoType p_type, RenderInfo p_info) {
return RS::get_singleton()->viewport_get_render_info(viewport, RS::ViewportRenderInfoType(p_type), RS::ViewportRenderInfo(p_info));
}
void Viewport::set_snap_controls_to_pixels(bool p_enable) {
snap_controls_to_pixels = p_enable;
}
bool Viewport::is_snap_controls_to_pixels_enabled() const {
return snap_controls_to_pixels;
}
void Viewport::set_snap_2d_transforms_to_pixel(bool p_enable) {
snap_2d_transforms_to_pixel = p_enable;
RS::get_singleton()->viewport_set_snap_2d_transforms_to_pixel(viewport, snap_2d_transforms_to_pixel);
}
bool Viewport::is_snap_2d_transforms_to_pixel_enabled() const {
return snap_2d_transforms_to_pixel;
}
void Viewport::set_snap_2d_vertices_to_pixel(bool p_enable) {
snap_2d_vertices_to_pixel = p_enable;
RS::get_singleton()->viewport_set_snap_2d_vertices_to_pixel(viewport, snap_2d_vertices_to_pixel);
}
bool Viewport::is_snap_2d_vertices_to_pixel_enabled() const {
return snap_2d_vertices_to_pixel;
}
bool Viewport::gui_is_dragging() const {
return gui.dragging;
}
void Viewport::set_input_as_handled() {
_drop_physics_mouseover();
if (handle_input_locally) {
local_input_handled = true;
} else {
ERR_FAIL_COND(!is_inside_tree());
Viewport *vp = this;
while (true) {
if (Object::cast_to<Window>(vp)) {
break;
}
if (!vp->get_parent()) {
break;
}
vp = vp->get_parent()->get_viewport();
}
vp->set_input_as_handled();
}
}
bool Viewport::is_input_handled() const {
if (handle_input_locally) {
return local_input_handled;
} else {
const Viewport *vp = this;
ERR_FAIL_COND_V(!is_inside_tree(), false);
while (true) {
if (Object::cast_to<Window>(vp)) {
break;
}
if (!vp->get_parent()) {
break;
}
vp = vp->get_parent()->get_viewport();
}
return vp->is_input_handled();
}
}
void Viewport::set_handle_input_locally(bool p_enable) {
handle_input_locally = p_enable;
}
bool Viewport::is_handling_input_locally() const {
return handle_input_locally;
}
void Viewport::set_default_canvas_item_texture_filter(DefaultCanvasItemTextureFilter p_filter) {
ERR_FAIL_INDEX(p_filter, DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_MAX);
if (default_canvas_item_texture_filter == p_filter) {
return;
}
default_canvas_item_texture_filter = p_filter;
switch (default_canvas_item_texture_filter) {
case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST:
RS::get_singleton()->viewport_set_default_canvas_item_texture_filter(viewport, RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
break;
case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR:
RS::get_singleton()->viewport_set_default_canvas_item_texture_filter(viewport, RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
break;
case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS:
RS::get_singleton()->viewport_set_default_canvas_item_texture_filter(viewport, RS::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS);
break;
case DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS:
RS::get_singleton()->viewport_set_default_canvas_item_texture_filter(viewport, RS::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
break;
default: {
}
}
}
Viewport::DefaultCanvasItemTextureFilter Viewport::get_default_canvas_item_texture_filter() const {
return default_canvas_item_texture_filter;
}
void Viewport::set_default_canvas_item_texture_repeat(DefaultCanvasItemTextureRepeat p_repeat) {
ERR_FAIL_INDEX(p_repeat, DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_MAX);
if (default_canvas_item_texture_repeat == p_repeat) {
return;
}
default_canvas_item_texture_repeat = p_repeat;
switch (default_canvas_item_texture_repeat) {
case DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_DISABLED:
RS::get_singleton()->viewport_set_default_canvas_item_texture_repeat(viewport, RS::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED);
break;
case DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_ENABLED:
RS::get_singleton()->viewport_set_default_canvas_item_texture_repeat(viewport, RS::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED);
break;
case DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_MIRROR:
RS::get_singleton()->viewport_set_default_canvas_item_texture_repeat(viewport, RS::CANVAS_ITEM_TEXTURE_REPEAT_MIRROR);
break;
default: {
}
}
}
Viewport::DefaultCanvasItemTextureRepeat Viewport::get_default_canvas_item_texture_repeat() const {
return default_canvas_item_texture_repeat;
}
DisplayServer::WindowID Viewport::get_window_id() const {
return DisplayServer::MAIN_WINDOW_ID;
}
Viewport *Viewport::get_parent_viewport() const {
ERR_FAIL_COND_V(!is_inside_tree(), nullptr);
if (!get_parent()) {
return nullptr; //root viewport
}
return get_parent()->get_viewport();
}
void Viewport::set_embed_subwindows_hint(bool p_embed) {
gui.embed_subwindows_hint = p_embed;
}
bool Viewport::get_embed_subwindows_hint() const {
return gui.embed_subwindows_hint;
}
bool Viewport::is_embedding_subwindows() const {
return gui.embed_subwindows_hint;
}
void Viewport::pass_mouse_focus_to(Viewport *p_viewport, Control *p_control) {
ERR_FAIL_NULL(p_viewport);
ERR_FAIL_NULL(p_control);
if (gui.mouse_focus) {
p_viewport->gui.mouse_focus = p_control;
p_viewport->gui.mouse_focus_mask = gui.mouse_focus_mask;
p_viewport->gui.key_focus = p_control;
p_viewport->gui.forced_mouse_focus = true;
gui.mouse_focus = nullptr;
gui.forced_mouse_focus = false;
gui.mouse_focus_mask = 0;
}
}
void Viewport::set_sdf_oversize(SDFOversize p_sdf_oversize) {
ERR_FAIL_INDEX(p_sdf_oversize, SDF_OVERSIZE_MAX);
sdf_oversize = p_sdf_oversize;
RS::get_singleton()->viewport_set_sdf_oversize_and_scale(viewport, RS::ViewportSDFOversize(sdf_oversize), RS::ViewportSDFScale(sdf_scale));
}
Viewport::SDFOversize Viewport::get_sdf_oversize() const {
return sdf_oversize;
}
void Viewport::set_sdf_scale(SDFScale p_sdf_scale) {
ERR_FAIL_INDEX(p_sdf_scale, SDF_SCALE_MAX);
sdf_scale = p_sdf_scale;
RS::get_singleton()->viewport_set_sdf_oversize_and_scale(viewport, RS::ViewportSDFOversize(sdf_oversize), RS::ViewportSDFScale(sdf_scale));
}
Viewport::SDFScale Viewport::get_sdf_scale() const {
return sdf_scale;
}
#ifndef _3D_DISABLED
AudioListener3D *Viewport::get_audio_listener_3d() const {
return audio_listener_3d;
}
void Viewport::set_as_audio_listener_3d(bool p_enable) {
if (p_enable == is_audio_listener_3d_enabled) {
return;
}
is_audio_listener_3d_enabled = p_enable;
_update_audio_listener_3d();
}
bool Viewport::is_audio_listener_3d() const {
return is_audio_listener_3d_enabled;
}
void Viewport::_update_audio_listener_3d() {
if (AudioServer::get_singleton()) {
AudioServer::get_singleton()->notify_listener_changed();
}
}
void Viewport::_listener_transform_3d_changed_notify() {
}
void Viewport::_audio_listener_3d_set(AudioListener3D *p_listener) {
if (audio_listener_3d == p_listener) {
return;
}
audio_listener_3d = p_listener;
_update_audio_listener_3d();
_listener_transform_3d_changed_notify();
}
bool Viewport::_audio_listener_3d_add(AudioListener3D *p_listener) {
audio_listener_3d_set.insert(p_listener);
return audio_listener_3d_set.size() == 1;
}
void Viewport::_audio_listener_3d_remove(AudioListener3D *p_listener) {
audio_listener_3d_set.erase(p_listener);
if (audio_listener_3d == p_listener) {
audio_listener_3d = nullptr;
}
}
void Viewport::_audio_listener_3d_make_next_current(AudioListener3D *p_exclude) {
if (audio_listener_3d_set.size() > 0) {
for (Set<AudioListener3D *>::Element *E = audio_listener_3d_set.front(); E; E = E->next()) {
if (p_exclude == E->get()) {
continue;
}
if (!E->get()->is_inside_tree()) {
continue;
}
if (audio_listener_3d != nullptr) {
return;
}
E->get()->make_current();
}
} else {
// Attempt to reset listener to the camera position.
if (camera_3d != nullptr) {
_update_audio_listener_3d();
_camera_3d_transform_changed_notify();
}
}
}
void Viewport::_collision_object_3d_input_event(CollisionObject3D *p_object, Camera3D *p_camera, const Ref<InputEvent> &p_input_event, const Vector3 &p_pos, const Vector3 &p_normal, int p_shape) {
Transform3D object_transform = p_object->get_global_transform();
Transform3D camera_transform = p_camera->get_global_transform();
ObjectID id = p_object->get_instance_id();
// Avoid sending the fake event unnecessarily if nothing really changed in the context.
if (object_transform == physics_last_object_transform && camera_transform == physics_last_camera_transform && physics_last_id == id) {
Ref<InputEventMouseMotion> mm = p_input_event;
if (mm.is_valid() && mm->get_device() == InputEvent::DEVICE_ID_INTERNAL) {
return; // Discarded.
}
}
p_object->_input_event_call(camera_3d, p_input_event, p_pos, p_normal, p_shape);
physics_last_object_transform = object_transform;
physics_last_camera_transform = camera_transform;
physics_last_id = id;
}
Camera3D *Viewport::get_camera_3d() const {
return camera_3d;
}
void Viewport::_camera_3d_transform_changed_notify() {
}
void Viewport::_camera_3d_set(Camera3D *p_camera) {
if (camera_3d == p_camera) {
return;
}
if (camera_3d) {
camera_3d->notification(Camera3D::NOTIFICATION_LOST_CURRENT);
}
camera_3d = p_camera;
if (!camera_3d_override) {
if (camera_3d) {
RenderingServer::get_singleton()->viewport_attach_camera(viewport, camera_3d->get_camera());
} else {
RenderingServer::get_singleton()->viewport_attach_camera(viewport, RID());
}
}
if (camera_3d) {
camera_3d->notification(Camera3D::NOTIFICATION_BECAME_CURRENT);
}
_update_audio_listener_3d();
_camera_3d_transform_changed_notify();
}
bool Viewport::_camera_3d_add(Camera3D *p_camera) {
camera_3d_set.insert(p_camera);
return camera_3d_set.size() == 1;
}
void Viewport::_camera_3d_remove(Camera3D *p_camera) {
camera_3d_set.erase(p_camera);
if (camera_3d == p_camera) {
camera_3d->notification(Camera3D::NOTIFICATION_LOST_CURRENT);
camera_3d = nullptr;
}
}
void Viewport::_camera_3d_make_next_current(Camera3D *p_exclude) {
for (Set<Camera3D *>::Element *E = camera_3d_set.front(); E; E = E->next()) {
if (p_exclude == E->get()) {
continue;
}
if (!E->get()->is_inside_tree()) {
continue;
}
if (camera_3d != nullptr) {
return;
}
E->get()->make_current();
}
}
void Viewport::enable_camera_3d_override(bool p_enable) {
if (p_enable == camera_3d_override) {
return;
}
if (p_enable) {
camera_3d_override.rid = RenderingServer::get_singleton()->camera_create();
} else {
RenderingServer::get_singleton()->free(camera_3d_override.rid);
camera_3d_override.rid = RID();
}
if (p_enable) {
RenderingServer::get_singleton()->viewport_attach_camera(viewport, camera_3d_override.rid);
} else if (camera_3d) {
RenderingServer::get_singleton()->viewport_attach_camera(viewport, camera_3d->get_camera());
} else {
RenderingServer::get_singleton()->viewport_attach_camera(viewport, RID());
}
}
void Viewport::set_camera_3d_override_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far) {
if (camera_3d_override) {
if (camera_3d_override.fov == p_fovy_degrees && camera_3d_override.z_near == p_z_near &&
camera_3d_override.z_far == p_z_far && camera_3d_override.projection == Camera3DOverrideData::PROJECTION_PERSPECTIVE) {
return;
}
camera_3d_override.fov = p_fovy_degrees;
camera_3d_override.z_near = p_z_near;
camera_3d_override.z_far = p_z_far;
camera_3d_override.projection = Camera3DOverrideData::PROJECTION_PERSPECTIVE;
RenderingServer::get_singleton()->camera_set_perspective(camera_3d_override.rid, camera_3d_override.fov, camera_3d_override.z_near, camera_3d_override.z_far);
}
}
void Viewport::set_camera_3d_override_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far) {
if (camera_3d_override) {
if (camera_3d_override.size == p_size && camera_3d_override.z_near == p_z_near &&
camera_3d_override.z_far == p_z_far && camera_3d_override.projection == Camera3DOverrideData::PROJECTION_ORTHOGONAL) {
return;
}
camera_3d_override.size = p_size;
camera_3d_override.z_near = p_z_near;
camera_3d_override.z_far = p_z_far;
camera_3d_override.projection = Camera3DOverrideData::PROJECTION_ORTHOGONAL;
RenderingServer::get_singleton()->camera_set_orthogonal(camera_3d_override.rid, camera_3d_override.size, camera_3d_override.z_near, camera_3d_override.z_far);
}
}
void Viewport::set_disable_3d(bool p_disable) {
disable_3d = p_disable;
RenderingServer::get_singleton()->viewport_set_disable_3d(viewport, disable_3d);
}
bool Viewport::is_3d_disabled() const {
return disable_3d;
}
bool Viewport::is_camera_3d_override_enabled() const {
return camera_3d_override;
}
void Viewport::set_camera_3d_override_transform(const Transform3D &p_transform) {
if (camera_3d_override) {
camera_3d_override.transform = p_transform;
RenderingServer::get_singleton()->camera_set_transform(camera_3d_override.rid, p_transform);
}
}
Transform3D Viewport::get_camera_3d_override_transform() const {
if (camera_3d_override) {
return camera_3d_override.transform;
}
return Transform3D();
}
Ref<World3D> Viewport::get_world_3d() const {
return world_3d;
}
Ref<World3D> Viewport::find_world_3d() const {
if (own_world_3d.is_valid()) {
return own_world_3d;
} else if (world_3d.is_valid()) {
return world_3d;
} else if (parent) {
return parent->find_world_3d();
} else {
return Ref<World3D>();
}
}
void Viewport::set_world_3d(const Ref<World3D> &p_world_3d) {
if (world_3d == p_world_3d) {
return;
}
if (is_inside_tree()) {
_propagate_exit_world_3d(this);
}
if (own_world_3d.is_valid() && world_3d.is_valid()) {
world_3d->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Viewport::_own_world_3d_changed));
}
world_3d = p_world_3d;
if (own_world_3d.is_valid()) {
if (world_3d.is_valid()) {
own_world_3d = world_3d->duplicate();
world_3d->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Viewport::_own_world_3d_changed));
} else {
own_world_3d = Ref<World3D>(memnew(World3D));
}
}
if (is_inside_tree()) {
_propagate_enter_world_3d(this);
}
if (is_inside_tree()) {
RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario());
}
_update_audio_listener_3d();
}
void Viewport::_own_world_3d_changed() {
ERR_FAIL_COND(world_3d.is_null());
ERR_FAIL_COND(own_world_3d.is_null());
if (is_inside_tree()) {
_propagate_exit_world_3d(this);
}
own_world_3d = world_3d->duplicate();
if (is_inside_tree()) {
_propagate_enter_world_3d(this);
}
if (is_inside_tree()) {
RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario());
}
_update_audio_listener_3d();
}
void Viewport::set_use_own_world_3d(bool p_world_3d) {
if (p_world_3d == own_world_3d.is_valid()) {
return;
}
if (is_inside_tree()) {
_propagate_exit_world_3d(this);
}
if (!p_world_3d) {
own_world_3d = Ref<World3D>();
if (world_3d.is_valid()) {
world_3d->disconnect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Viewport::_own_world_3d_changed));
}
} else {
if (world_3d.is_valid()) {
own_world_3d = world_3d->duplicate();
world_3d->connect(CoreStringNames::get_singleton()->changed, callable_mp(this, &Viewport::_own_world_3d_changed));
} else {
own_world_3d = Ref<World3D>(memnew(World3D));
}
}
if (is_inside_tree()) {
_propagate_enter_world_3d(this);
}
if (is_inside_tree()) {
RenderingServer::get_singleton()->viewport_set_scenario(viewport, find_world_3d()->get_scenario());
}
_update_audio_listener_3d();
}
bool Viewport::is_using_own_world_3d() const {
return own_world_3d.is_valid();
}
void Viewport::_propagate_enter_world_3d(Node *p_node) {
if (p_node != this) {
if (!p_node->is_inside_tree()) { //may not have entered scene yet
return;
}
if (Object::cast_to<Node3D>(p_node) || Object::cast_to<WorldEnvironment>(p_node)) {
p_node->notification(Node3D::NOTIFICATION_ENTER_WORLD);
} else {
Viewport *v = Object::cast_to<Viewport>(p_node);
if (v) {
if (v->world_3d.is_valid() || v->own_world_3d.is_valid()) {
return;
}
}
}
}
for (int i = 0; i < p_node->get_child_count(); i++) {
_propagate_enter_world_3d(p_node->get_child(i));
}
}
void Viewport::_propagate_exit_world_3d(Node *p_node) {
if (p_node != this) {
if (!p_node->is_inside_tree()) { //may have exited scene already
return;
}
if (Object::cast_to<Node3D>(p_node) || Object::cast_to<WorldEnvironment>(p_node)) {
p_node->notification(Node3D::NOTIFICATION_EXIT_WORLD);
} else {
Viewport *v = Object::cast_to<Viewport>(p_node);
if (v) {
if (v->world_3d.is_valid() || v->own_world_3d.is_valid()) {
return;
}
}
}
}
for (int i = 0; i < p_node->get_child_count(); i++) {
_propagate_exit_world_3d(p_node->get_child(i));
}
}
void Viewport::set_use_xr(bool p_use_xr) {
use_xr = p_use_xr;
RS::get_singleton()->viewport_set_use_xr(viewport, use_xr);
}
bool Viewport::is_using_xr() {
return use_xr;
}
void Viewport::set_scale_3d(const Scale3D p_scale_3d) {
scale_3d = p_scale_3d;
RS::get_singleton()->viewport_set_scale_3d(viewport, RS::ViewportScale3D(scale_3d));
}
Viewport::Scale3D Viewport::get_scale_3d() const {
return scale_3d;
}
#endif // _3D_DISABLED
void Viewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_world_2d", "world_2d"), &Viewport::set_world_2d);
ClassDB::bind_method(D_METHOD("get_world_2d"), &Viewport::get_world_2d);
ClassDB::bind_method(D_METHOD("find_world_2d"), &Viewport::find_world_2d);
ClassDB::bind_method(D_METHOD("set_canvas_transform", "xform"), &Viewport::set_canvas_transform);
ClassDB::bind_method(D_METHOD("get_canvas_transform"), &Viewport::get_canvas_transform);
ClassDB::bind_method(D_METHOD("set_global_canvas_transform", "xform"), &Viewport::set_global_canvas_transform);
ClassDB::bind_method(D_METHOD("get_global_canvas_transform"), &Viewport::get_global_canvas_transform);
ClassDB::bind_method(D_METHOD("get_final_transform"), &Viewport::get_final_transform);
ClassDB::bind_method(D_METHOD("get_visible_rect"), &Viewport::get_visible_rect);
ClassDB::bind_method(D_METHOD("set_transparent_background", "enable"), &Viewport::set_transparent_background);
ClassDB::bind_method(D_METHOD("has_transparent_background"), &Viewport::has_transparent_background);
ClassDB::bind_method(D_METHOD("set_msaa", "msaa"), &Viewport::set_msaa);
ClassDB::bind_method(D_METHOD("get_msaa"), &Viewport::get_msaa);
ClassDB::bind_method(D_METHOD("set_screen_space_aa", "screen_space_aa"), &Viewport::set_screen_space_aa);
ClassDB::bind_method(D_METHOD("get_screen_space_aa"), &Viewport::get_screen_space_aa);
ClassDB::bind_method(D_METHOD("set_use_debanding", "enable"), &Viewport::set_use_debanding);
ClassDB::bind_method(D_METHOD("is_using_debanding"), &Viewport::is_using_debanding);
ClassDB::bind_method(D_METHOD("set_use_occlusion_culling", "enable"), &Viewport::set_use_occlusion_culling);
ClassDB::bind_method(D_METHOD("is_using_occlusion_culling"), &Viewport::is_using_occlusion_culling);
ClassDB::bind_method(D_METHOD("set_debug_draw", "debug_draw"), &Viewport::set_debug_draw);
ClassDB::bind_method(D_METHOD("get_debug_draw"), &Viewport::get_debug_draw);
ClassDB::bind_method(D_METHOD("get_render_info", "type", "info"), &Viewport::get_render_info);
ClassDB::bind_method(D_METHOD("get_texture"), &Viewport::get_texture);
ClassDB::bind_method(D_METHOD("set_physics_object_picking", "enable"), &Viewport::set_physics_object_picking);
ClassDB::bind_method(D_METHOD("get_physics_object_picking"), &Viewport::get_physics_object_picking);
ClassDB::bind_method(D_METHOD("get_viewport_rid"), &Viewport::get_viewport_rid);
ClassDB::bind_method(D_METHOD("push_text_input", "text"), &Viewport::push_text_input);
ClassDB::bind_method(D_METHOD("push_input", "event", "in_local_coords"), &Viewport::push_input, DEFVAL(false));
ClassDB::bind_method(D_METHOD("push_unhandled_input", "event", "in_local_coords"), &Viewport::push_unhandled_input, DEFVAL(false));
ClassDB::bind_method(D_METHOD("get_camera_2d"), &Viewport::get_camera_2d);
ClassDB::bind_method(D_METHOD("set_as_audio_listener_2d", "enable"), &Viewport::set_as_audio_listener_2d);
ClassDB::bind_method(D_METHOD("is_audio_listener_2d"), &Viewport::is_audio_listener_2d);
ClassDB::bind_method(D_METHOD("get_mouse_position"), &Viewport::get_mouse_position);
ClassDB::bind_method(D_METHOD("warp_mouse", "to_position"), &Viewport::warp_mouse);
ClassDB::bind_method(D_METHOD("gui_get_drag_data"), &Viewport::gui_get_drag_data);
ClassDB::bind_method(D_METHOD("gui_is_dragging"), &Viewport::gui_is_dragging);
ClassDB::bind_method(D_METHOD("set_disable_input", "disable"), &Viewport::set_disable_input);
ClassDB::bind_method(D_METHOD("is_input_disabled"), &Viewport::is_input_disabled);
ClassDB::bind_method(D_METHOD("_gui_remove_focus_for_window"), &Viewport::_gui_remove_focus_for_window);
ClassDB::bind_method(D_METHOD("_post_gui_grab_click_focus"), &Viewport::_post_gui_grab_click_focus);
ClassDB::bind_method(D_METHOD("set_shadow_atlas_size", "size"), &Viewport::set_shadow_atlas_size);
ClassDB::bind_method(D_METHOD("get_shadow_atlas_size"), &Viewport::get_shadow_atlas_size);
ClassDB::bind_method(D_METHOD("set_shadow_atlas_16_bits", "enable"), &Viewport::set_shadow_atlas_16_bits);
ClassDB::bind_method(D_METHOD("get_shadow_atlas_16_bits"), &Viewport::get_shadow_atlas_16_bits);
ClassDB::bind_method(D_METHOD("set_snap_controls_to_pixels", "enabled"), &Viewport::set_snap_controls_to_pixels);
ClassDB::bind_method(D_METHOD("is_snap_controls_to_pixels_enabled"), &Viewport::is_snap_controls_to_pixels_enabled);
ClassDB::bind_method(D_METHOD("set_snap_2d_transforms_to_pixel", "enabled"), &Viewport::set_snap_2d_transforms_to_pixel);
ClassDB::bind_method(D_METHOD("is_snap_2d_transforms_to_pixel_enabled"), &Viewport::is_snap_2d_transforms_to_pixel_enabled);
ClassDB::bind_method(D_METHOD("set_snap_2d_vertices_to_pixel", "enabled"), &Viewport::set_snap_2d_vertices_to_pixel);
ClassDB::bind_method(D_METHOD("is_snap_2d_vertices_to_pixel_enabled"), &Viewport::is_snap_2d_vertices_to_pixel_enabled);
ClassDB::bind_method(D_METHOD("set_shadow_atlas_quadrant_subdiv", "quadrant", "subdiv"), &Viewport::set_shadow_atlas_quadrant_subdiv);
ClassDB::bind_method(D_METHOD("get_shadow_atlas_quadrant_subdiv", "quadrant"), &Viewport::get_shadow_atlas_quadrant_subdiv);
ClassDB::bind_method(D_METHOD("set_input_as_handled"), &Viewport::set_input_as_handled);
ClassDB::bind_method(D_METHOD("is_input_handled"), &Viewport::is_input_handled);
ClassDB::bind_method(D_METHOD("set_handle_input_locally", "enable"), &Viewport::set_handle_input_locally);
ClassDB::bind_method(D_METHOD("is_handling_input_locally"), &Viewport::is_handling_input_locally);
ClassDB::bind_method(D_METHOD("set_default_canvas_item_texture_filter", "mode"), &Viewport::set_default_canvas_item_texture_filter);
ClassDB::bind_method(D_METHOD("get_default_canvas_item_texture_filter"), &Viewport::get_default_canvas_item_texture_filter);
ClassDB::bind_method(D_METHOD("set_embed_subwindows_hint", "enable"), &Viewport::set_embed_subwindows_hint);
ClassDB::bind_method(D_METHOD("get_embed_subwindows_hint"), &Viewport::get_embed_subwindows_hint);
ClassDB::bind_method(D_METHOD("is_embedding_subwindows"), &Viewport::is_embedding_subwindows);
ClassDB::bind_method(D_METHOD("set_default_canvas_item_texture_repeat", "mode"), &Viewport::set_default_canvas_item_texture_repeat);
ClassDB::bind_method(D_METHOD("get_default_canvas_item_texture_repeat"), &Viewport::get_default_canvas_item_texture_repeat);
ClassDB::bind_method(D_METHOD("set_sdf_oversize", "oversize"), &Viewport::set_sdf_oversize);
ClassDB::bind_method(D_METHOD("get_sdf_oversize"), &Viewport::get_sdf_oversize);
ClassDB::bind_method(D_METHOD("set_sdf_scale", "scale"), &Viewport::set_sdf_scale);
ClassDB::bind_method(D_METHOD("get_sdf_scale"), &Viewport::get_sdf_scale);
ClassDB::bind_method(D_METHOD("set_lod_threshold", "pixels"), &Viewport::set_lod_threshold);
ClassDB::bind_method(D_METHOD("get_lod_threshold"), &Viewport::get_lod_threshold);
ClassDB::bind_method(D_METHOD("_process_picking"), &Viewport::_process_picking);
#ifndef _3D_DISABLED
ClassDB::bind_method(D_METHOD("set_world_3d", "world_3d"), &Viewport::set_world_3d);
ClassDB::bind_method(D_METHOD("get_world_3d"), &Viewport::get_world_3d);
ClassDB::bind_method(D_METHOD("find_world_3d"), &Viewport::find_world_3d);
ClassDB::bind_method(D_METHOD("set_use_own_world_3d", "enable"), &Viewport::set_use_own_world_3d);
ClassDB::bind_method(D_METHOD("is_using_own_world_3d"), &Viewport::is_using_own_world_3d);
ClassDB::bind_method(D_METHOD("get_camera_3d"), &Viewport::get_camera_3d);
ClassDB::bind_method(D_METHOD("set_as_audio_listener_3d", "enable"), &Viewport::set_as_audio_listener_3d);
ClassDB::bind_method(D_METHOD("is_audio_listener_3d"), &Viewport::is_audio_listener_3d);
ClassDB::bind_method(D_METHOD("set_disable_3d", "disable"), &Viewport::set_disable_3d);
ClassDB::bind_method(D_METHOD("is_3d_disabled"), &Viewport::is_3d_disabled);
ClassDB::bind_method(D_METHOD("set_use_xr", "use"), &Viewport::set_use_xr);
ClassDB::bind_method(D_METHOD("is_using_xr"), &Viewport::is_using_xr);
ClassDB::bind_method(D_METHOD("set_scale_3d", "scale"), &Viewport::set_scale_3d);
ClassDB::bind_method(D_METHOD("get_scale_3d"), &Viewport::get_scale_3d);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "disable_3d"), "set_disable_3d", "is_3d_disabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_xr"), "set_use_xr", "is_using_xr");
ADD_PROPERTY(PropertyInfo(Variant::INT, "scale_3d", PROPERTY_HINT_ENUM, String::utf8("Disabled,75%,50%,33%,25%")), "set_scale_3d", "get_scale_3d");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "audio_listener_enable_3d"), "set_as_audio_listener_3d", "is_audio_listener_3d");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "own_world_3d"), "set_use_own_world_3d", "is_using_own_world_3d");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "world_3d", PROPERTY_HINT_RESOURCE_TYPE, "World3D"), "set_world_3d", "get_world_3d");
#endif // _3D_DISABLED
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "world_2d", PROPERTY_HINT_RESOURCE_TYPE, "World2D", PROPERTY_USAGE_NONE), "set_world_2d", "get_world_2d");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "transparent_bg"), "set_transparent_background", "has_transparent_background");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "handle_input_locally"), "set_handle_input_locally", "is_handling_input_locally");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "snap_2d_transforms_to_pixel"), "set_snap_2d_transforms_to_pixel", "is_snap_2d_transforms_to_pixel_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "snap_2d_vertices_to_pixel"), "set_snap_2d_vertices_to_pixel", "is_snap_2d_vertices_to_pixel_enabled");
ADD_GROUP("Rendering", "");
ADD_PROPERTY(PropertyInfo(Variant::INT, "msaa", PROPERTY_HINT_ENUM, String::utf8("Disabled (Fastest),2× (Average),4× (Slow),8× (Slowest)")), "set_msaa", "get_msaa");
ADD_PROPERTY(PropertyInfo(Variant::INT, "screen_space_aa", PROPERTY_HINT_ENUM, "Disabled (Fastest),FXAA (Fast)"), "set_screen_space_aa", "get_screen_space_aa");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "is_using_debanding");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_occlusion_culling"), "set_use_occlusion_culling", "is_using_occlusion_culling");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "lod_threshold", PROPERTY_HINT_RANGE, "0,1024,0.1"), "set_lod_threshold", "get_lod_threshold");
ADD_PROPERTY(PropertyInfo(Variant::INT, "debug_draw", PROPERTY_HINT_ENUM, "Disabled,Unshaded,Overdraw,Wireframe"), "set_debug_draw", "get_debug_draw");
ADD_GROUP("Canvas Items", "canvas_item_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "canvas_item_default_texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Linear Mipmap,Nearest Mipmap"), "set_default_canvas_item_texture_filter", "get_default_canvas_item_texture_filter");
ADD_PROPERTY(PropertyInfo(Variant::INT, "canvas_item_default_texture_repeat", PROPERTY_HINT_ENUM, "Disabled,Enabled,Mirror"), "set_default_canvas_item_texture_repeat", "get_default_canvas_item_texture_repeat");
ADD_GROUP("Audio Listener", "audio_listener_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "audio_listener_enable_2d"), "set_as_audio_listener_2d", "is_audio_listener_2d");
ADD_GROUP("Physics", "physics_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_object_picking"), "set_physics_object_picking", "get_physics_object_picking");
ADD_GROUP("GUI", "gui_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gui_disable_input"), "set_disable_input", "is_input_disabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gui_snap_controls_to_pixels"), "set_snap_controls_to_pixels", "is_snap_controls_to_pixels_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gui_embed_subwindows"), "set_embed_subwindows_hint", "get_embed_subwindows_hint");
ADD_GROUP("SDF", "sdf_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "sdf_oversize", PROPERTY_HINT_ENUM, "100%,120%,150%,200%"), "set_sdf_oversize", "get_sdf_oversize");
ADD_PROPERTY(PropertyInfo(Variant::INT, "sdf_scale", PROPERTY_HINT_ENUM, "100%,50%,25%"), "set_sdf_scale", "get_sdf_scale");
ADD_GROUP("Shadow Atlas", "shadow_atlas_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "shadow_atlas_size"), "set_shadow_atlas_size", "get_shadow_atlas_size");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "shadow_atlas_16_bits"), "set_shadow_atlas_16_bits", "get_shadow_atlas_16_bits");
ADD_PROPERTYI(PropertyInfo(Variant::INT, "shadow_atlas_quad_0", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_shadow_atlas_quadrant_subdiv", "get_shadow_atlas_quadrant_subdiv", 0);
ADD_PROPERTYI(PropertyInfo(Variant::INT, "shadow_atlas_quad_1", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_shadow_atlas_quadrant_subdiv", "get_shadow_atlas_quadrant_subdiv", 1);
ADD_PROPERTYI(PropertyInfo(Variant::INT, "shadow_atlas_quad_2", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_shadow_atlas_quadrant_subdiv", "get_shadow_atlas_quadrant_subdiv", 2);
ADD_PROPERTYI(PropertyInfo(Variant::INT, "shadow_atlas_quad_3", PROPERTY_HINT_ENUM, "Disabled,1 Shadow,4 Shadows,16 Shadows,64 Shadows,256 Shadows,1024 Shadows"), "set_shadow_atlas_quadrant_subdiv", "get_shadow_atlas_quadrant_subdiv", 3);
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "canvas_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_canvas_transform", "get_canvas_transform");
ADD_PROPERTY(PropertyInfo(Variant::TRANSFORM2D, "global_canvas_transform", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_global_canvas_transform", "get_global_canvas_transform");
ADD_SIGNAL(MethodInfo("size_changed"));
ADD_SIGNAL(MethodInfo("gui_focus_changed", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Control")));
BIND_ENUM_CONSTANT(SCALE_3D_DISABLED);
BIND_ENUM_CONSTANT(SCALE_3D_75_PERCENT);
BIND_ENUM_CONSTANT(SCALE_3D_50_PERCENT);
BIND_ENUM_CONSTANT(SCALE_3D_33_PERCENT);
BIND_ENUM_CONSTANT(SCALE_3D_25_PERCENT);
BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_DISABLED);
BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_1);
BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_4);
BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_16);
BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_64);
BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_256);
BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_1024);
BIND_ENUM_CONSTANT(SHADOW_ATLAS_QUADRANT_SUBDIV_MAX);
BIND_ENUM_CONSTANT(MSAA_DISABLED);
BIND_ENUM_CONSTANT(MSAA_2X);
BIND_ENUM_CONSTANT(MSAA_4X);
BIND_ENUM_CONSTANT(MSAA_8X);
BIND_ENUM_CONSTANT(MSAA_MAX);
BIND_ENUM_CONSTANT(SCREEN_SPACE_AA_DISABLED);
BIND_ENUM_CONSTANT(SCREEN_SPACE_AA_FXAA);
BIND_ENUM_CONSTANT(SCREEN_SPACE_AA_MAX);
BIND_ENUM_CONSTANT(RENDER_INFO_OBJECTS_IN_FRAME);
BIND_ENUM_CONSTANT(RENDER_INFO_PRIMITIVES_IN_FRAME);
BIND_ENUM_CONSTANT(RENDER_INFO_DRAW_CALLS_IN_FRAME);
BIND_ENUM_CONSTANT(RENDER_INFO_MAX);
BIND_ENUM_CONSTANT(RENDER_INFO_TYPE_VISIBLE);
BIND_ENUM_CONSTANT(RENDER_INFO_TYPE_SHADOW);
BIND_ENUM_CONSTANT(RENDER_INFO_TYPE_MAX);
BIND_ENUM_CONSTANT(DEBUG_DRAW_DISABLED);
BIND_ENUM_CONSTANT(DEBUG_DRAW_UNSHADED);
BIND_ENUM_CONSTANT(DEBUG_DRAW_LIGHTING);
BIND_ENUM_CONSTANT(DEBUG_DRAW_OVERDRAW);
BIND_ENUM_CONSTANT(DEBUG_DRAW_WIREFRAME);
BIND_ENUM_CONSTANT(DEBUG_DRAW_NORMAL_BUFFER);
BIND_ENUM_CONSTANT(DEBUG_DRAW_VOXEL_GI_ALBEDO);
BIND_ENUM_CONSTANT(DEBUG_DRAW_VOXEL_GI_LIGHTING);
BIND_ENUM_CONSTANT(DEBUG_DRAW_VOXEL_GI_EMISSION);
BIND_ENUM_CONSTANT(DEBUG_DRAW_SHADOW_ATLAS);
BIND_ENUM_CONSTANT(DEBUG_DRAW_DIRECTIONAL_SHADOW_ATLAS);
BIND_ENUM_CONSTANT(DEBUG_DRAW_SCENE_LUMINANCE);
BIND_ENUM_CONSTANT(DEBUG_DRAW_SSAO);
BIND_ENUM_CONSTANT(DEBUG_DRAW_PSSM_SPLITS);
BIND_ENUM_CONSTANT(DEBUG_DRAW_DECAL_ATLAS);
BIND_ENUM_CONSTANT(DEBUG_DRAW_SDFGI);
BIND_ENUM_CONSTANT(DEBUG_DRAW_SDFGI_PROBES);
BIND_ENUM_CONSTANT(DEBUG_DRAW_GI_BUFFER);
BIND_ENUM_CONSTANT(DEBUG_DRAW_DISABLE_LOD);
BIND_ENUM_CONSTANT(DEBUG_DRAW_CLUSTER_OMNI_LIGHTS);
BIND_ENUM_CONSTANT(DEBUG_DRAW_CLUSTER_SPOT_LIGHTS);
BIND_ENUM_CONSTANT(DEBUG_DRAW_CLUSTER_DECALS);
BIND_ENUM_CONSTANT(DEBUG_DRAW_CLUSTER_REFLECTION_PROBES);
BIND_ENUM_CONSTANT(DEBUG_DRAW_OCCLUDERS)
BIND_ENUM_CONSTANT(DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST);
BIND_ENUM_CONSTANT(DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR);
BIND_ENUM_CONSTANT(DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS);
BIND_ENUM_CONSTANT(DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
BIND_ENUM_CONSTANT(DEFAULT_CANVAS_ITEM_TEXTURE_FILTER_MAX);
BIND_ENUM_CONSTANT(DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_DISABLED);
BIND_ENUM_CONSTANT(DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_ENABLED);
BIND_ENUM_CONSTANT(DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_MIRROR);
BIND_ENUM_CONSTANT(DEFAULT_CANVAS_ITEM_TEXTURE_REPEAT_MAX);
BIND_ENUM_CONSTANT(SDF_OVERSIZE_100_PERCENT);
BIND_ENUM_CONSTANT(SDF_OVERSIZE_120_PERCENT);
BIND_ENUM_CONSTANT(SDF_OVERSIZE_150_PERCENT);
BIND_ENUM_CONSTANT(SDF_OVERSIZE_200_PERCENT);
BIND_ENUM_CONSTANT(SDF_OVERSIZE_MAX);
BIND_ENUM_CONSTANT(SDF_SCALE_100_PERCENT);
BIND_ENUM_CONSTANT(SDF_SCALE_50_PERCENT);
BIND_ENUM_CONSTANT(SDF_SCALE_25_PERCENT);
BIND_ENUM_CONSTANT(SDF_SCALE_MAX);
}
Viewport::Viewport() {
world_2d = Ref<World2D>(memnew(World2D));
viewport = RenderingServer::get_singleton()->viewport_create();
texture_rid = RenderingServer::get_singleton()->viewport_get_texture(viewport);
default_texture.instantiate();
default_texture->vp = const_cast<Viewport *>(this);
viewport_textures.insert(default_texture.ptr());
default_texture->proxy = RS::get_singleton()->texture_proxy_create(texture_rid);
canvas_layers.insert(nullptr); // This eases picking code (interpreted as the canvas of the Viewport).
set_shadow_atlas_size(shadow_atlas_size);
for (int i = 0; i < 4; i++) {
shadow_atlas_quadrant_subdiv[i] = SHADOW_ATLAS_QUADRANT_SUBDIV_MAX;
}
set_shadow_atlas_quadrant_subdiv(0, SHADOW_ATLAS_QUADRANT_SUBDIV_4);
set_shadow_atlas_quadrant_subdiv(1, SHADOW_ATLAS_QUADRANT_SUBDIV_4);
set_shadow_atlas_quadrant_subdiv(2, SHADOW_ATLAS_QUADRANT_SUBDIV_16);
set_shadow_atlas_quadrant_subdiv(3, SHADOW_ATLAS_QUADRANT_SUBDIV_64);
set_lod_threshold(lod_threshold);
String id = itos(get_instance_id());
input_group = "_vp_input" + id;
gui_input_group = "_vp_gui_input" + id;
unhandled_input_group = "_vp_unhandled_input" + id;
unhandled_key_input_group = "_vp_unhandled_key_input" + id;
// Window tooltip.
gui.tooltip_delay = GLOBAL_DEF("gui/timers/tooltip_delay_sec", 0.5);
ProjectSettings::get_singleton()->set_custom_property_info("gui/timers/tooltip_delay_sec", PropertyInfo(Variant::FLOAT, "gui/timers/tooltip_delay_sec", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater")); // No negative numbers
#ifndef _3D_DISABLED
int scale = GLOBAL_GET("rendering/3d/viewport/scale");
set_scale_3d((Scale3D)scale);
#endif // _3D_DISABLED
set_sdf_oversize(sdf_oversize); // Set to server.
}
Viewport::~Viewport() {
// Erase itself from viewport textures.
for (Set<ViewportTexture *>::Element *E = viewport_textures.front(); E; E = E->next()) {
E->get()->vp = nullptr;
}
RenderingServer::get_singleton()->free(viewport);
}
/////////////////////////////////
void SubViewport::set_size(const Size2i &p_size) {
_set_size(p_size, _get_size_2d_override(), Rect2i(), _stretch_transform(), true);
}
Size2i SubViewport::get_size() const {
return _get_size();
}
void SubViewport::set_size_2d_override(const Size2i &p_size) {
_set_size(_get_size(), p_size, Rect2i(), _stretch_transform(), true);
}
Size2i SubViewport::get_size_2d_override() const {
return _get_size_2d_override();
}
void SubViewport::set_size_2d_override_stretch(bool p_enable) {
if (p_enable == size_2d_override_stretch) {
return;
}
size_2d_override_stretch = p_enable;
_set_size(_get_size(), _get_size_2d_override(), Rect2i(), _stretch_transform(), true);
}
bool SubViewport::is_size_2d_override_stretch_enabled() const {
return size_2d_override_stretch;
}
void SubViewport::set_update_mode(UpdateMode p_mode) {
update_mode = p_mode;
RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::ViewportUpdateMode(p_mode));
}
SubViewport::UpdateMode SubViewport::get_update_mode() const {
return update_mode;
}
void SubViewport::set_clear_mode(ClearMode p_mode) {
clear_mode = p_mode;
RS::get_singleton()->viewport_set_clear_mode(get_viewport_rid(), RS::ViewportClearMode(p_mode));
}
SubViewport::ClearMode SubViewport::get_clear_mode() const {
return clear_mode;
}
DisplayServer::WindowID SubViewport::get_window_id() const {
return DisplayServer::INVALID_WINDOW_ID;
}
Transform2D SubViewport::_stretch_transform() {
Transform2D transform = Transform2D();
Size2i view_size_2d_override = _get_size_2d_override();
if (size_2d_override_stretch && view_size_2d_override.width > 0 && view_size_2d_override.height > 0) {
Size2 scale = _get_size() / view_size_2d_override;
transform.scale(scale);
}
return transform;
}
void SubViewport::_notification(int p_what) {
if (p_what == NOTIFICATION_ENTER_TREE) {
RS::get_singleton()->viewport_set_active(get_viewport_rid(), true);
}
if (p_what == NOTIFICATION_EXIT_TREE) {
RS::get_singleton()->viewport_set_active(get_viewport_rid(), false);
}
}
void SubViewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_size", "size"), &SubViewport::set_size);
ClassDB::bind_method(D_METHOD("get_size"), &SubViewport::get_size);
ClassDB::bind_method(D_METHOD("set_size_2d_override", "size"), &SubViewport::set_size_2d_override);
ClassDB::bind_method(D_METHOD("get_size_2d_override"), &SubViewport::get_size_2d_override);
ClassDB::bind_method(D_METHOD("set_size_2d_override_stretch", "enable"), &SubViewport::set_size_2d_override_stretch);
ClassDB::bind_method(D_METHOD("is_size_2d_override_stretch_enabled"), &SubViewport::is_size_2d_override_stretch_enabled);
ClassDB::bind_method(D_METHOD("set_update_mode", "mode"), &SubViewport::set_update_mode);
ClassDB::bind_method(D_METHOD("get_update_mode"), &SubViewport::get_update_mode);
ClassDB::bind_method(D_METHOD("set_clear_mode", "mode"), &SubViewport::set_clear_mode);
ClassDB::bind_method(D_METHOD("get_clear_mode"), &SubViewport::get_clear_mode);
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size"), "set_size", "get_size");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "size_2d_override"), "set_size_2d_override", "get_size_2d_override");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "size_2d_override_stretch"), "set_size_2d_override_stretch", "is_size_2d_override_stretch_enabled");
ADD_GROUP("Render Target", "render_target_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "render_target_clear_mode", PROPERTY_HINT_ENUM, "Always,Never,Next Frame"), "set_clear_mode", "get_clear_mode");
ADD_PROPERTY(PropertyInfo(Variant::INT, "render_target_update_mode", PROPERTY_HINT_ENUM, "Disabled,Once,When Visible,Always"), "set_update_mode", "get_update_mode");
BIND_ENUM_CONSTANT(CLEAR_MODE_ALWAYS);
BIND_ENUM_CONSTANT(CLEAR_MODE_NEVER);
BIND_ENUM_CONSTANT(CLEAR_MODE_ONCE);
BIND_ENUM_CONSTANT(UPDATE_DISABLED);
BIND_ENUM_CONSTANT(UPDATE_ONCE);
BIND_ENUM_CONSTANT(UPDATE_WHEN_VISIBLE);
BIND_ENUM_CONSTANT(UPDATE_WHEN_PARENT_VISIBLE);
BIND_ENUM_CONSTANT(UPDATE_ALWAYS);
}
SubViewport::SubViewport() {}
SubViewport::~SubViewport() {}