summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Alexsander <michaelalexsander@protonmail.com>2024-10-25 16:34:32 -0300
committerMichael Alexsander <michaelalexsander@protonmail.com>2024-10-30 13:24:00 -0300
commitbaffa731ab48b22ca8b9040a9a20151d392cc18a (patch)
tree4c9dcab041812c283549e57e1f2450502acfdbc4
parent8004c7524fb9f43425c4d6f614410a76678e0f7c (diff)
downloadredot-engine-baffa731ab48b22ca8b9040a9a20151d392cc18a.tar.gz
Make possible to scale multiple nodes at once in the canvas editor
And also some smaller enhancements.
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp298
1 files changed, 173 insertions, 125 deletions
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index 51992a0b21..45af11b6c7 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/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");