Browse Source

Make possible to scale multiple nodes at once in the canvas editor

And also some smaller enhancements.
Michael Alexsander 10 months ago
parent
commit
baffa731ab
1 changed files with 173 additions and 125 deletions
  1. 173 125
      editor/plugins/canvas_item_editor_plugin.cpp

+ 173 - 125
editor/plugins/canvas_item_editor_plugin.cpp

@@ -1930,37 +1930,50 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) {
 
 	// Drag resize handles
 	if (drag_type == DRAG_NONE) {
-		if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() && ((b->is_alt_pressed() && b->is_command_or_control_pressed()) || tool == TOOL_SCALE)) {
+		if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed() &&
+				((tool == TOOL_SELECT && b->is_alt_pressed() && b->is_command_or_control_pressed()) || tool == TOOL_SCALE)) {
 			bool has_locked_items = false;
 			List<CanvasItem *> selection = _get_edited_canvas_items(false, true, &has_locked_items);
-			if (selection.size() == 1) {
+
+			// Remove non-movable nodes.
+			for (CanvasItem *ci : selection) {
+				if (!_is_node_movable(ci, true)) {
+					selection.erase(ci);
+				}
+			}
+
+			if (!selection.is_empty()) {
 				CanvasItem *ci = selection.front()->get();
 
-				if (_is_node_movable(ci)) {
-					Transform2D xform = transform * ci->get_global_transform_with_canvas();
-					Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();
-					Transform2D simple_xform = viewport->get_transform() * unscaled_transform;
+				Transform2D edit_transform;
+				if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {
+					edit_transform = Transform2D(ci->_edit_get_rotation(), temp_pivot);
+				} else {
+					edit_transform = ci->_edit_get_transform();
+				}
 
-					drag_type = DRAG_SCALE_BOTH;
+				Transform2D xform = transform * ci->get_global_transform_with_canvas();
+				Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * edit_transform).orthonormalized();
+				Transform2D simple_xform = viewport->get_transform() * unscaled_transform;
 
-					if (show_transformation_gizmos) {
-						Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE);
-						Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
-						if (x_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {
-							drag_type = DRAG_SCALE_X;
-						}
-						Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
-						if (y_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {
-							drag_type = DRAG_SCALE_Y;
-						}
-					}
+				drag_type = DRAG_SCALE_BOTH;
 
-					drag_from = transform.affine_inverse().xform(b->get_position());
-					drag_selection = List<CanvasItem *>();
-					drag_selection.push_back(ci);
-					_save_canvas_item_state(drag_selection);
-					return true;
+				if (show_transformation_gizmos) {
+					Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE);
+					Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
+					if (x_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {
+						drag_type = DRAG_SCALE_X;
+					}
+					Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
+					if (y_handle_rect.has_point(simple_xform.affine_inverse().xform(b->get_position()))) {
+						drag_type = DRAG_SCALE_Y;
+					}
 				}
+
+				drag_from = transform.affine_inverse().xform(b->get_position());
+				drag_selection = selection;
+				_save_canvas_item_state(drag_selection);
+				return true;
 			} else {
 				if (has_locked_items) {
 					EditorToaster::get_singleton()->popup_str(TTR(locked_transform_warning), EditorToaster::SEVERITY_WARNING);
@@ -1968,66 +1981,87 @@ bool CanvasItemEditor::_gui_input_scale(const Ref<InputEvent> &p_event) {
 				return has_locked_items;
 			}
 		}
-	}
-
-	if (drag_type == DRAG_SCALE_BOTH || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y) {
+	} else if (drag_type == DRAG_SCALE_BOTH || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y) {
 		// Resize the node
 		if (m.is_valid()) {
 			_restore_canvas_item_state(drag_selection);
-			CanvasItem *ci = drag_selection.front()->get();
 
 			drag_to = transform.affine_inverse().xform(m->get_position());
 
-			Transform2D parent_xform = ci->get_global_transform_with_canvas() * ci->get_transform().affine_inverse();
-			Transform2D unscaled_transform = (transform * parent_xform * ci->_edit_get_transform()).orthonormalized();
-			Transform2D simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform;
-
-			bool uniform = m->is_shift_pressed();
-			bool is_ctrl = m->is_command_or_control_pressed();
-
-			Point2 drag_from_local = simple_xform.xform(drag_from);
-			Point2 drag_to_local = simple_xform.xform(drag_to);
-			Point2 offset = drag_to_local - drag_from_local;
+			Size2 scale_max;
+			if (drag_type != DRAG_SCALE_BOTH) {
+				for (CanvasItem *ci : drag_selection) {
+					scale_max = scale_max.max(ci->_edit_get_scale());
+				}
+			}
 
-			Size2 scale = ci->_edit_get_scale();
-			Size2 original_scale = scale;
-			real_t ratio = scale.y / scale.x;
-			if (drag_type == DRAG_SCALE_BOTH) {
-				Size2 scale_factor = drag_to_local / drag_from_local;
-				if (uniform) {
-					scale *= (scale_factor.x + scale_factor.y) / 2.0;
+			for (CanvasItem *ci : drag_selection) {
+				Transform2D edit_transform;
+				bool using_temp_pivot = !Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y);
+				if (using_temp_pivot) {
+					edit_transform = Transform2D(ci->_edit_get_rotation(), temp_pivot);
 				} else {
-					scale *= scale_factor;
+					edit_transform = ci->_edit_get_transform();
 				}
-			} else {
-				Size2 scale_factor = Vector2(offset.x, -offset.y) / SCALE_HANDLE_DISTANCE;
-				Size2 parent_scale = parent_xform.get_scale();
-				scale_factor *= Vector2(1.0 / parent_scale.x, 1.0 / parent_scale.y);
 
-				if (drag_type == DRAG_SCALE_X) {
-					scale.x += scale_factor.x;
+				Transform2D parent_xform = ci->get_global_transform_with_canvas() * ci->get_transform().affine_inverse();
+				Transform2D unscaled_transform = (transform * parent_xform * edit_transform).orthonormalized();
+				Transform2D simple_xform = (viewport->get_transform() * unscaled_transform).affine_inverse() * transform;
+
+				bool uniform = m->is_shift_pressed();
+				bool is_ctrl = m->is_command_or_control_pressed();
+
+				Point2 drag_from_local = simple_xform.xform(drag_from);
+				Point2 drag_to_local = simple_xform.xform(drag_to);
+				Point2 offset = drag_to_local - drag_from_local;
+
+				Size2 scale = ci->_edit_get_scale();
+				Size2 original_scale = scale;
+				real_t ratio = scale.y / scale.x;
+				if (drag_type == DRAG_SCALE_BOTH) {
+					Size2 scale_factor = drag_to_local / drag_from_local;
 					if (uniform) {
-						scale.y = scale.x * ratio;
+						scale *= (scale_factor.x + scale_factor.y) / 2.0;
+					} else {
+						scale *= scale_factor;
 					}
-				} else if (drag_type == DRAG_SCALE_Y) {
-					scale.y -= scale_factor.y;
-					if (uniform) {
-						scale.x = scale.y / ratio;
+				} else {
+					Size2 scale_factor = Vector2(offset.x, -offset.y) / SCALE_HANDLE_DISTANCE;
+					Size2 parent_scale = parent_xform.get_scale();
+					// Take into account the biggest scale, so all nodes are scaled uniformly.
+					scale_factor *= Vector2(1.0 / parent_scale.x, 1.0 / parent_scale.y) / (scale_max / original_scale);
+
+					if (drag_type == DRAG_SCALE_X) {
+						scale.x += scale_factor.x;
+						if (uniform) {
+							scale.y = scale.x * ratio;
+						}
+					} else if (drag_type == DRAG_SCALE_Y) {
+						scale.y -= scale_factor.y;
+						if (uniform) {
+							scale.x = scale.y / ratio;
+						}
 					}
 				}
-			}
 
-			if (snap_scale && !is_ctrl) {
-				if (snap_relative) {
-					scale.x = original_scale.x * (roundf((scale.x / original_scale.x) / snap_scale_step) * snap_scale_step);
-					scale.y = original_scale.y * (roundf((scale.y / original_scale.y) / snap_scale_step) * snap_scale_step);
-				} else {
-					scale.x = roundf(scale.x / snap_scale_step) * snap_scale_step;
-					scale.y = roundf(scale.y / snap_scale_step) * snap_scale_step;
+				if (snap_scale && !is_ctrl) {
+					if (snap_relative) {
+						scale.x = original_scale.x * (Math::round((scale.x / original_scale.x) / snap_scale_step) * snap_scale_step);
+						scale.y = original_scale.y * (Math::round((scale.y / original_scale.y) / snap_scale_step) * snap_scale_step);
+					} else {
+						scale.x = Math::round(scale.x / snap_scale_step) * snap_scale_step;
+						scale.y = Math::round(scale.y / snap_scale_step) * snap_scale_step;
+					}
+				}
+
+				ci->_edit_set_scale(scale);
+
+				if (using_temp_pivot) {
+					Point2 ci_origin = ci->_edit_get_transform().get_origin();
+					ci->_edit_set_position(ci_origin + (ci_origin - temp_pivot) * ((scale - original_scale) / original_scale));
 				}
 			}
 
-			ci->_edit_set_scale(scale);
 			return true;
 		}
 
@@ -2075,7 +2109,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
 	if (drag_type == DRAG_NONE) {
 		//Start moving the nodes
 		if (b.is_valid() && b->get_button_index() == MouseButton::LEFT && b->is_pressed()) {
-			if ((b->is_alt_pressed() && !b->is_command_or_control_pressed()) || tool == TOOL_MOVE) {
+			if ((tool == TOOL_SELECT && b->is_alt_pressed() && !b->is_command_or_control_pressed()) || tool == TOOL_MOVE) {
 				bool has_locked_items = false;
 				List<CanvasItem *> selection = _get_edited_canvas_items(false, true, &has_locked_items);
 
@@ -2135,7 +2169,7 @@ bool CanvasItemEditor::_gui_input_move(const Ref<InputEvent> &p_event) {
 			}
 
 			Point2 drag_delta = drag_to - drag_from;
-			if (drag_selection.size() == 1 && (drag_type == DRAG_MOVE_X || drag_type == DRAG_MOVE_Y)) {
+			if (drag_type == DRAG_MOVE_X || drag_type == DRAG_MOVE_Y) {
 				const CanvasItem *selected = drag_selection.front()->get();
 				Transform2D parent_xform = selected->get_global_transform_with_canvas() * selected->get_transform().affine_inverse();
 				Transform2D unscaled_transform = (transform * parent_xform * selected->_edit_get_transform()).orthonormalized();
@@ -3468,16 +3502,14 @@ void CanvasItemEditor::_draw_selection() {
 	Ref<Texture2D> previous_position_icon = get_editor_theme_icon(SNAME("EditorPositionPrevious"));
 
 	RID vp_ci = viewport->get_canvas_item();
-
 	List<CanvasItem *> selection = _get_edited_canvas_items(true, false);
-
 	bool single = selection.size() == 1;
+	bool transform_tool = tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE || tool == TOOL_EDIT_PIVOT;
+
 	for (CanvasItem *E : selection) {
 		CanvasItem *ci = Object::cast_to<CanvasItem>(E);
 		CanvasItemEditorSelectedItem *se = editor_selection->get_node_editor_data<CanvasItemEditorSelectedItem>(ci);
 
-		bool item_locked = ci->has_meta("_edit_lock_");
-
 		// Draw the previous position if we are dragging the node
 		if (show_helpers &&
 				(drag_type == DRAG_MOVE || drag_type == DRAG_ROTATE ||
@@ -3502,6 +3534,7 @@ void CanvasItemEditor::_draw_selection() {
 			}
 		}
 
+		bool item_locked = ci->has_meta("_edit_lock_");
 		Transform2D xform = transform * ci->get_global_transform_with_canvas();
 
 		// Draw the selected items position / surrounding boxes
@@ -3531,7 +3564,7 @@ void CanvasItemEditor::_draw_selection() {
 			viewport->draw_set_transform_matrix(viewport->get_transform());
 		}
 
-		if (single && !item_locked && (tool == TOOL_SELECT || tool == TOOL_MOVE || tool == TOOL_SCALE || tool == TOOL_ROTATE || tool == TOOL_EDIT_PIVOT)) { //kind of sucks
+		if (single && !item_locked && transform_tool) {
 			// Draw the pivot
 			if (ci->_edit_use_pivot()) {
 				// Draw the node's pivot
@@ -3574,73 +3607,88 @@ void CanvasItemEditor::_draw_selection() {
 					select_handle->draw(vp_ci, (ofs - (select_handle->get_size() / 2)).floor());
 				}
 			}
+		}
+	}
 
-			// Draw the move handles
-			bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
-			bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT);
-			if (tool == TOOL_MOVE && show_transformation_gizmos) {
-				if (_is_node_movable(ci)) {
-					Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();
-					Transform2D simple_xform = viewport->get_transform() * unscaled_transform;
+	// Remove non-movable nodes.
+	for (CanvasItem *ci : selection) {
+		if (!_is_node_movable(ci, true)) {
+			selection.erase(ci);
+		}
+	}
 
-					Size2 move_factor = Size2(MOVE_HANDLE_DISTANCE, MOVE_HANDLE_DISTANCE);
-					viewport->draw_set_transform_matrix(simple_xform);
+	if (!selection.is_empty() && transform_tool && show_transformation_gizmos) {
+		CanvasItem *ci = selection.front()->get();
 
-					Vector<Point2> points = {
-						Vector2(move_factor.x * EDSCALE, 5 * EDSCALE),
-						Vector2(move_factor.x * EDSCALE, -5 * EDSCALE),
-						Vector2((move_factor.x + 10) * EDSCALE, 0)
-					};
+		Transform2D xform = transform * ci->get_global_transform_with_canvas();
+		bool is_ctrl = Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL);
+		bool is_alt = Input::get_singleton()->is_key_pressed(Key::ALT);
 
-					viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));
-					viewport->draw_line(Point2(), Point2(move_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE));
+		// Draw the move handles.
+		if ((tool == TOOL_SELECT && is_alt && !is_ctrl) || tool == TOOL_MOVE) {
+			Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();
+			Transform2D simple_xform = viewport->get_transform() * unscaled_transform;
 
-					points.clear();
-					points.push_back(Vector2(5 * EDSCALE, move_factor.y * EDSCALE));
-					points.push_back(Vector2(-5 * EDSCALE, move_factor.y * EDSCALE));
-					points.push_back(Vector2(0, (move_factor.y + 10) * EDSCALE));
+			Size2 move_factor = Size2(MOVE_HANDLE_DISTANCE, MOVE_HANDLE_DISTANCE);
+			viewport->draw_set_transform_matrix(simple_xform);
 
-					viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));
-					viewport->draw_line(Point2(), Point2(0, move_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE));
+			Vector<Point2> points = {
+				Vector2(move_factor.x * EDSCALE, 5 * EDSCALE),
+				Vector2(move_factor.x * EDSCALE, -5 * EDSCALE),
+				Vector2((move_factor.x + 10) * EDSCALE, 0)
+			};
 
-					viewport->draw_set_transform_matrix(viewport->get_transform());
-				}
-			}
+			viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));
+			viewport->draw_line(Point2(), Point2(move_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE));
 
-			// Draw the rescale handles
-			if (show_transformation_gizmos && ((is_alt && is_ctrl) || tool == TOOL_SCALE || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y)) {
-				if (_is_node_movable(ci)) {
-					Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * ci->_edit_get_transform()).orthonormalized();
-					Transform2D simple_xform = viewport->get_transform() * unscaled_transform;
+			points.clear();
+			points.push_back(Vector2(5 * EDSCALE, move_factor.y * EDSCALE));
+			points.push_back(Vector2(-5 * EDSCALE, move_factor.y * EDSCALE));
+			points.push_back(Vector2(0, (move_factor.y + 10) * EDSCALE));
 
-					Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE);
-					bool uniform = Input::get_singleton()->is_key_pressed(Key::SHIFT);
-					Point2 offset = (simple_xform.affine_inverse().xform(drag_to) - simple_xform.affine_inverse().xform(drag_from)) * zoom;
+			viewport->draw_colored_polygon(points, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));
+			viewport->draw_line(Point2(), Point2(0, move_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE));
 
-					if (drag_type == DRAG_SCALE_X) {
-						scale_factor.x += offset.x;
-						if (uniform) {
-							scale_factor.y += offset.x;
-						}
-					} else if (drag_type == DRAG_SCALE_Y) {
-						scale_factor.y += offset.y;
-						if (uniform) {
-							scale_factor.x += offset.y;
-						}
-					}
+			viewport->draw_set_transform_matrix(viewport->get_transform());
+		}
 
-					viewport->draw_set_transform_matrix(simple_xform);
-					Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
-					viewport->draw_rect(x_handle_rect, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));
-					viewport->draw_line(Point2(), Point2(scale_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE));
+		// Draw the rescale handles.
+		if ((tool == TOOL_SELECT && is_alt && is_ctrl) || tool == TOOL_SCALE || drag_type == DRAG_SCALE_X || drag_type == DRAG_SCALE_Y) {
+			Transform2D edit_transform;
+			if (!Math::is_inf(temp_pivot.x) || !Math::is_inf(temp_pivot.y)) {
+				edit_transform = Transform2D(ci->_edit_get_rotation(), temp_pivot);
+			} else {
+				edit_transform = ci->_edit_get_transform();
+			}
+			Transform2D unscaled_transform = (xform * ci->get_transform().affine_inverse() * edit_transform).orthonormalized();
+			Transform2D simple_xform = viewport->get_transform() * unscaled_transform;
 
-					Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
-					viewport->draw_rect(y_handle_rect, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));
-					viewport->draw_line(Point2(), Point2(0, scale_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE));
+			Size2 scale_factor = Size2(SCALE_HANDLE_DISTANCE, SCALE_HANDLE_DISTANCE);
+			bool uniform = Input::get_singleton()->is_key_pressed(Key::SHIFT);
+			Point2 offset = (simple_xform.affine_inverse().xform(drag_to) - simple_xform.affine_inverse().xform(drag_from)) * zoom;
 
-					viewport->draw_set_transform_matrix(viewport->get_transform());
+			if (drag_type == DRAG_SCALE_X) {
+				scale_factor.x += offset.x;
+				if (uniform) {
+					scale_factor.y += offset.x;
+				}
+			} else if (drag_type == DRAG_SCALE_Y) {
+				scale_factor.y += offset.y;
+				if (uniform) {
+					scale_factor.x += offset.y;
 				}
 			}
+
+			viewport->draw_set_transform_matrix(simple_xform);
+			Rect2 x_handle_rect = Rect2(scale_factor.x * EDSCALE, -5 * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
+			viewport->draw_rect(x_handle_rect, get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)));
+			viewport->draw_line(Point2(), Point2(scale_factor.x * EDSCALE, 0), get_theme_color(SNAME("axis_x_color"), EditorStringName(Editor)), Math::round(EDSCALE));
+
+			Rect2 y_handle_rect = Rect2(-5 * EDSCALE, scale_factor.y * EDSCALE, 10 * EDSCALE, 10 * EDSCALE);
+			viewport->draw_rect(y_handle_rect, get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)));
+			viewport->draw_line(Point2(), Point2(0, scale_factor.y * EDSCALE), get_theme_color(SNAME("axis_y_color"), EditorStringName(Editor)), Math::round(EDSCALE));
+
+			viewport->draw_set_transform_matrix(viewport->get_transform());
 		}
 	}
 
@@ -5389,7 +5437,7 @@ CanvasItemEditor::CanvasItemEditor() {
 	main_menu_hbox->add_child(pivot_button);
 	pivot_button->set_toggle_mode(true);
 	pivot_button->connect(SceneStringName(pressed), callable_mp(this, &CanvasItemEditor::_button_tool_select).bind(TOOL_EDIT_PIVOT));
-	pivot_button->set_tooltip_text(TTR("Click to change object's rotation pivot.") + "\n" + TTR("Shift: Set temporary rotation pivot.") + "\n" + TTR("Click this button while holding Shift to put the temporary rotation pivot in the center of the selected nodes."));
+	pivot_button->set_tooltip_text(TTR("Click to change object's pivot.") + "\n" + TTR("Shift: Set temporary pivot.") + "\n" + TTR("Click this button while holding Shift to put the temporary pivot in the center of the selected nodes."));
 
 	pan_button = memnew(Button);
 	pan_button->set_theme_type_variation("FlatButton");