diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index 927ab9ae0e35..69b0650046fc 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -1019,6 +1019,24 @@
The node's position, relative to its containing node. It corresponds to the rectangle's top-left corner. The property is not affected by [member pivot_offset].
+
+ Position offset applied after layouting. If [member render_offset_relative_to_size] is [code]true[/code], the offset is relative to the node's [member size].
+
+
+ If [code]true[/code], [member render_offset] is relative to the node's [member size], otherwise it is absolute pixel values.
+
+
+ Rotation applied after layouting. The rotation pivot is defined by [member render_transform_pivot].
+
+
+ Scale applied after layouting. The scale pivot is defined by [member render_transform_pivot].
+
+
+ Pivot used for [member render_rotation] and [member render_scale]. If [member render_transform_pivot_relative_to_size] is [code]true[/code], the pivot is relative to the node's size, otherwise it represents absolute pixel values.
+
+
+ If [code]true[/code], [member render_transform_pivot] is relative to the node's [member size], otherwise it is absolute pixel values.
+
The node's rotation around its pivot, in radians. See [member pivot_offset] to change the pivot's position.
[b]Note:[/b] This property is edited in the inspector in degrees. If you want to use degrees in a script, use [member rotation_degrees].
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 15ada0021a17..8d738fa52511 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -483,6 +483,18 @@ void Control::_validate_property(PropertyInfo &p_property) const {
p_property.hint = PROPERTY_HINT_LINK;
}
+ if (p_property.name == "render_offset") {
+ p_property.hint_string = data.render_offset_relative_to_size
+ ? ""
+ : "suffix:px";
+ }
+
+ if (p_property.name == "render_transform_pivot") {
+ p_property.hint_string = data.render_transform_pivot_relative_to_size
+ ? ""
+ : "suffix:px";
+ }
+
// Validate which positioning properties should be displayed depending on the parent and the layout mode.
Node *parent_node = get_parent_control();
if (!parent_node) {
@@ -697,6 +709,24 @@ void Control::_update_canvas_item_transform() {
xform[2] = (xform[2] + Vector2(0.5, 0.5)).floor();
}
+ Vector2 absolute_render_offset = data.render_offset;
+ Vector2 absolute_render_transform_pivot = data.render_transform_pivot;
+
+ if (data.render_offset_relative_to_size) {
+ absolute_render_offset *= get_size();
+ }
+
+ if (data.render_transform_pivot_relative_to_size) {
+ absolute_render_transform_pivot *= get_size();
+ }
+
+ xform *= Transform2D()
+ .translated(-absolute_render_transform_pivot)
+ .rotated(data.render_rotation)
+ .scaled(data.render_scale)
+ .translated(absolute_render_transform_pivot)
+ .translated(absolute_render_offset);
+
RenderingServer::get_singleton()->canvas_item_set_transform(get_canvas_item(), xform);
}
@@ -1675,6 +1705,76 @@ Size2 Control::get_custom_minimum_size() const {
return data.custom_minimum_size;
}
+void Control::set_render_offset(const Vector2 &p_offset) {
+ data.render_offset = p_offset;
+ queue_redraw();
+ _notify_transform();
+}
+
+Vector2 Control::get_render_offset() const {
+ return data.render_offset;
+}
+
+void Control::set_render_offset_relative_to_size(bool p_relative) {
+ if (data.render_offset_relative_to_size == p_relative) {
+ return;
+ }
+
+ data.render_offset_relative_to_size = p_relative;
+ queue_redraw();
+ _notify_transform();
+ notify_property_list_changed();
+}
+
+bool Control::get_render_offset_relative_to_size() const {
+ return data.render_offset_relative_to_size;
+}
+
+void Control::set_render_scale(const Vector2 &p_scale) {
+ data.render_scale = p_scale;
+ queue_redraw();
+ _notify_transform();
+}
+
+Vector2 Control::get_render_scale() const {
+ return data.render_scale;
+}
+
+void Control::set_render_rotation(real_t p_rotation) {
+ data.render_rotation = p_rotation;
+ queue_redraw();
+ _notify_transform();
+}
+
+real_t Control::get_render_rotation() const {
+ return data.render_rotation;
+}
+
+void Control::set_render_transform_pivot(const Vector2 &p_pivot) {
+ data.render_transform_pivot = p_pivot;
+ queue_redraw();
+ _notify_transform();
+}
+
+Vector2 Control::get_render_transform_pivot() const {
+ return data.render_transform_pivot;
+}
+
+void Control::set_render_transform_pivot_relative_to_size(bool p_relative) {
+ if (data.render_transform_pivot_relative_to_size == p_relative) {
+ return;
+ }
+
+ data.render_transform_pivot_relative_to_size = p_relative;
+ queue_redraw();
+ _notify_transform();
+ notify_property_list_changed();
+}
+
+bool Control::get_render_transform_pivot_relative_to_size() const {
+ return data.render_transform_pivot_relative_to_size;
+}
+
void Control::_update_minimum_size_cache() {
Size2 minsize = get_minimum_size();
minsize = minsize.max(data.custom_minimum_size);
@@ -3423,6 +3523,19 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("find_next_valid_focus"), &Control::find_next_valid_focus);
ClassDB::bind_method(D_METHOD("find_valid_focus_neighbor", "side"), &Control::find_valid_focus_neighbor);
+ ClassDB::bind_method(D_METHOD("set_render_offset", "offset"), &Control::set_render_offset);
+ ClassDB::bind_method(D_METHOD("get_render_offset"), &Control::get_render_offset);
+ ClassDB::bind_method(D_METHOD("set_render_offset_relative_to_size", "relative"), &Control::set_render_offset_relative_to_size);
+ ClassDB::bind_method(D_METHOD("get_render_offset_relative_to_size"), &Control::get_render_offset_relative_to_size);
+ ClassDB::bind_method(D_METHOD("set_render_scale", "scale"), &Control::set_render_scale);
+ ClassDB::bind_method(D_METHOD("get_render_scale"), &Control::get_render_scale);
+ ClassDB::bind_method(D_METHOD("set_render_rotation", "rotation"), &Control::set_render_rotation);
+ ClassDB::bind_method(D_METHOD("get_render_rotation"), &Control::get_render_rotation);
+ ClassDB::bind_method(D_METHOD("set_render_transform_pivot", "pivot"), &Control::set_render_transform_pivot);
+ ClassDB::bind_method(D_METHOD("get_render_transform_pivot"), &Control::get_render_transform_pivot);
+ ClassDB::bind_method(D_METHOD("set_render_transform_pivot_relative_to_size", "relative"), &Control::set_render_transform_pivot_relative_to_size);
+ ClassDB::bind_method(D_METHOD("get_render_transform_pivot_relative_to_size"), &Control::get_render_transform_pivot_relative_to_size);
+
ClassDB::bind_method(D_METHOD("set_h_size_flags", "flags"), &Control::set_h_size_flags);
ClassDB::bind_method(D_METHOD("get_h_size_flags"), &Control::get_h_size_flags);
@@ -3586,6 +3699,14 @@ void Control::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "size_flags_vertical", PROPERTY_HINT_FLAGS, "Fill:1,Expand:2,Shrink Center:4,Shrink End:8"), "set_v_size_flags", "get_v_size_flags");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size_flags_stretch_ratio", PROPERTY_HINT_RANGE, "0,20,0.01,or_greater"), "set_stretch_ratio", "get_stretch_ratio");
+ ADD_GROUP("Render Transform", "render_");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "render_offset", PROPERTY_HINT_NONE), "set_render_offset", "get_render_offset");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "render_offset_relative_to_size"), "set_render_offset_relative_to_size", "get_render_offset_relative_to_size");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "render_scale"), "set_render_scale", "get_render_scale");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "render_rotation", PROPERTY_HINT_NONE, "-360,360,0.1,or_less,or_greater,radians_as_degrees"), "set_render_rotation", "get_render_rotation");
+ ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "render_transform_pivot", PROPERTY_HINT_NONE), "set_render_transform_pivot", "get_render_transform_pivot");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "render_transform_pivot_relative_to_size"), "set_render_transform_pivot_relative_to_size", "get_render_transform_pivot_relative_to_size");
+
ADD_GROUP("Localization", "");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "localize_numeral_system"), "set_localize_numeral_system", "is_localizing_numeral_system");
diff --git a/scene/gui/control.h b/scene/gui/control.h
index c784d4330dac..8454f8dae34f 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -194,6 +194,13 @@ class Control : public CanvasItem {
Vector2 scale = Vector2(1, 1);
Vector2 pivot_offset;
+ Vector2 render_offset;
+ bool render_offset_relative_to_size = true;
+ Vector2 render_scale = Vector2(1, 1);
+ real_t render_rotation = 0.0;
+ Vector2 render_transform_pivot;
+ bool render_transform_pivot_relative_to_size = true;
+
Point2 pos_cache;
Size2 size_cache;
Size2 minimum_size_cache;
@@ -493,6 +500,20 @@ class Control : public CanvasItem {
void set_custom_minimum_size(const Size2 &p_custom);
Size2 get_custom_minimum_size() const;
+ // Render transform.
+ void set_render_offset(const Vector2 &p_offset);
+ Vector2 get_render_offset() const;
+ void set_render_offset_relative_to_size(bool p_relative);
+ bool get_render_offset_relative_to_size() const;
+ void set_render_scale(const Vector2 &p_scale);
+ Vector2 get_render_scale() const;
+ void set_render_rotation(real_t p_rotation);
+ real_t get_render_rotation() const;
+ void set_render_transform_pivot(const Vector2 &p_pivot);
+ Vector2 get_render_transform_pivot() const;
+ void set_render_transform_pivot_relative_to_size(bool p_relative);
+ bool get_render_transform_pivot_relative_to_size() const;
+
// Container sizing.
void set_h_size_flags(BitField p_flags);