Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 52 additions & 1 deletion doc/classes/Control.xml
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,12 @@
Returns combined minimum size from [member custom_minimum_size] and [method get_minimum_size].
</description>
</method>
<method name="get_combined_pivot_offset" qualifiers="const">
<return type="Vector2" />
<description>
Returns the combined value of [member pivot_offset] and [member pivot_offset_ratio], in pixels. The ratio is multiplied by the control's size.
</description>
</method>
<method name="get_cursor_shape" qualifiers="const">
<return type="int" enum="Control.CursorShape" />
<param index="0" name="position" type="Vector2" default="Vector2(0, 0)" />
Expand Down Expand Up @@ -463,6 +469,11 @@
Returns the offset for the specified [enum Side]. A getter method for [member offset_bottom], [member offset_left], [member offset_right] and [member offset_top].
</description>
</method>
<method name="get_offset_transform" qualifiers="const">
<return type="Transform2D" />
<description>
</description>
</method>
<method name="get_parent_area_size" qualifiers="const">
<return type="Vector2" />
<description>
Expand Down Expand Up @@ -1084,9 +1095,49 @@
Distance between the node's top edge and its parent control, based on [member anchor_top].
Offsets are often controlled by one or multiple parent [Container] nodes, so you should not modify them manually if your node is a direct child of a [Container]. Offsets update automatically when you move or resize the node.
</member>
<member name="offset_transform_enabled" type="bool" setter="set_offset_transform_enabled" getter="is_offset_transform_enabled" default="false">
If [code]true[/code], applies all offset transform properties. Otherwise, no offset transform is applied and the properties have no effect.
</member>
<member name="offset_transform_pivot" type="Vector2" setter="set_offset_transform_pivot" getter="get_offset_transform_pivot" default="Vector2(0, 0)">
Pivot used by [member offset_transform_rotation] and [member offset_transform_scale] in absolute units.
The final pivot position is the combined value of this property and [member offset_transform_pivot_ratio].
Has no effect unless [member offset_transform_enabled] is [code]true[/code].
</member>
<member name="offset_transform_pivot_ratio" type="Vector2" setter="set_offset_transform_pivot_ratio" getter="get_offset_transform_pivot_ratio" default="Vector2(0.5, 0.5)">
Same as [member offset_transform_pivot] but expressed in units relative to the [Control] [member size] where [code]Vector2(0, 0)[/code] is the top-left corner of this control, and [code]Vector2(1, 1)[/code] is its bottom-right corner.
The final pivot position is the combined value of this property and [member offset_transform_pivot].
Has no effect unless [member offset_transform_enabled] is [code]true[/code].
</member>
<member name="offset_transform_position" type="Vector2" setter="set_offset_transform_position" getter="get_offset_transform_position" default="Vector2(0, 0)">
Position offset in absolute units. The final offset is the combined value of this property and [member offset_transform_position_ratio].
Has no effect unless [member offset_transform_enabled] is [code]true[/code].
</member>
<member name="offset_transform_position_ratio" type="Vector2" setter="set_offset_transform_position_ratio" getter="get_offset_transform_position_ratio" default="Vector2(0, 0)">
Same as [member offset_transform_position] but expressed in units relative to the [Control] [member size] where [code]Vector2(0, 0)[/code] is the top-left corner of this control, and [code]Vector2(1, 1)[/code] is its bottom-right corner.
The final offset is the combined value of this property and [member offset_transform_position].
Has no effect unless [member offset_transform_enabled] is [code]true[/code].
</member>
<member name="offset_transform_rotation" type="float" setter="set_offset_transform_rotation" getter="get_offset_transform_rotation" default="0.0">
Rotation offset. The rotation pivot is defined by [member offset_transform_pivot] and [member offset_transform_pivot_ratio].
Has no effect unless [member offset_transform_enabled] is [code]true[/code].
</member>
<member name="offset_transform_scale" type="Vector2" setter="set_offset_transform_scale" getter="get_offset_transform_scale" default="Vector2(1, 1)">
Scale offset. The scale pivot is defined by [member offset_transform_pivot] and [member offset_transform_pivot_ratio].
Has no effect unless [member offset_transform_enabled] is [code]true[/code].
</member>
<member name="offset_transform_visual_only" type="bool" setter="set_offset_transform_visual_only" getter="is_offset_transform_visual_only" default="true">
If [code]true[/code], the offset transform is only applied visually and does not affect input. In other words, this Control will still receive input events at its original location before the offset transform is applied.
If [code]false[/code], the entire transform of this Control is affected and input events will register where the Control is visually.
Has no effect unless [member offset_transform_enabled] is [code]true[/code].
</member>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="2" />
<member name="pivot_offset" type="Vector2" setter="set_pivot_offset" getter="get_pivot_offset" default="Vector2(0, 0)">
By default, the node's pivot is its top-left corner. When you change its [member rotation] or [member scale], it will rotate or scale around this pivot. Set this property to [member size] / 2 to pivot around the Control's center.
By default, the node's pivot is its top-left corner. When you change its [member rotation] or [member scale], it will rotate or scale around this pivot.
The actual offset is the combined value of this property and [member pivot_offset_ratio].
</member>
<member name="pivot_offset_ratio" type="Vector2" setter="set_pivot_offset_ratio" getter="get_pivot_offset_ratio" default="Vector2(0, 0)">
Same as [member pivot_offset], but expressed as uniform vector, where [code]Vector2(0, 0)[/code] is the top-left corner of this control, and [code]Vector2(1, 1)[/code] is its bottom-right corner. Set this property to [code]Vector2(0.5, 0.5)[/code] to pivot around this control's center.
The actual offset is the combined value of this property and [member pivot_offset].
</member>
<member name="position" type="Vector2" setter="_set_position" getter="get_position" default="Vector2(0, 0)">
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].
Expand Down
9 changes: 5 additions & 4 deletions editor/scene/canvas_item_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4210,17 +4210,18 @@ void CanvasItemEditor::_notification(int p_what) {

Control *control = Object::cast_to<Control>(ci);
if (control) {
real_t anchors[4];
Vector2 pivot;
Vector2 pivot = control->get_pivot_offset();
Vector2 pivot_ratio = control->get_pivot_offset_ratio();

pivot = control->get_pivot_offset();
real_t anchors[4];
anchors[SIDE_LEFT] = control->get_anchor(SIDE_LEFT);
anchors[SIDE_RIGHT] = control->get_anchor(SIDE_RIGHT);
anchors[SIDE_TOP] = control->get_anchor(SIDE_TOP);
anchors[SIDE_BOTTOM] = control->get_anchor(SIDE_BOTTOM);

if (pivot != se->prev_pivot || anchors[SIDE_LEFT] != se->prev_anchors[SIDE_LEFT] || anchors[SIDE_RIGHT] != se->prev_anchors[SIDE_RIGHT] || anchors[SIDE_TOP] != se->prev_anchors[SIDE_TOP] || anchors[SIDE_BOTTOM] != se->prev_anchors[SIDE_BOTTOM]) {
if (pivot != se->prev_pivot || pivot_ratio != se->prev_pivot_ratio || anchors[SIDE_LEFT] != se->prev_anchors[SIDE_LEFT] || anchors[SIDE_RIGHT] != se->prev_anchors[SIDE_RIGHT] || anchors[SIDE_TOP] != se->prev_anchors[SIDE_TOP] || anchors[SIDE_BOTTOM] != se->prev_anchors[SIDE_BOTTOM]) {
se->prev_pivot = pivot;
se->prev_pivot_ratio = pivot_ratio;
se->prev_anchors[SIDE_LEFT] = anchors[SIDE_LEFT];
se->prev_anchors[SIDE_RIGHT] = anchors[SIDE_RIGHT];
se->prev_anchors[SIDE_TOP] = anchors[SIDE_TOP];
Expand Down
1 change: 1 addition & 0 deletions editor/scene/canvas_item_editor_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class CanvasItemEditorSelectedItem : public Object {
Transform2D prev_xform;
Rect2 prev_rect;
Vector2 prev_pivot;
Vector2 prev_pivot_ratio;
real_t prev_anchors[4] = { (real_t)0.0 };

Transform2D pre_drag_xform;
Expand Down
78 changes: 78 additions & 0 deletions editor/scene/gui/control_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1160,11 +1160,89 @@ ControlEditorToolbar *ControlEditorToolbar::singleton = nullptr;

// Editor plugin.

void ControlOffsetTransformPreview::edit(Control *p_control) {
ObjectID new_control_id = p_control ? p_control->get_instance_id() : ObjectID();
if (new_control_id == selected_control_id) {
return;
}

const Callable update_overlays = callable_mp(plugin, &EditorPlugin::update_overlays);

Control *selected_control = ObjectDB::get_instance<Control>(selected_control_id);
if (selected_control) {
selected_control->disconnect(SceneStringName(draw), update_overlays);
}

selected_control_id = new_control_id;

selected_control = ObjectDB::get_instance<Control>(selected_control_id);
if (selected_control) {
selected_control->connect(SceneStringName(draw), update_overlays);
}

plugin->update_overlays();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

void ControlOffsetTransformPreview::forward_canvas_draw_over_viewport(Control *p_overlay) const {
Control *selected_control = ObjectDB::get_instance<Control>(selected_control_id);
if (!selected_control || !selected_control->is_offset_transform_enabled() || selected_control->is_offset_transform_visual_only()) {
return;
}

Point2 top_left = Point2();
Point2 bottom_right = selected_control->get_size();
Point2 top_right = Point2(bottom_right.x, top_left.y);
Point2 bottom_left = Point2(top_left.x, bottom_right.y);

Transform2D offset_transform = selected_control->get_offset_transform();
// Skip drawing if the offset transform is singular (e.g., zero scale), as it cannot be inverted.
if (Math::abs(offset_transform.determinant()) < CMP_EPSILON) {
return;
}

Transform2D control_transform_without_offset = selected_control->get_global_transform() * offset_transform.affine_inverse();
top_left = control_transform_without_offset.xform(top_left);
bottom_right = control_transform_without_offset.xform(bottom_right);
top_right = control_transform_without_offset.xform(top_right);
bottom_left = control_transform_without_offset.xform(bottom_left);

Transform2D canvas_transform = CanvasItemEditor::get_singleton()->get_canvas_transform();
top_left = canvas_transform.xform(top_left);
bottom_right = canvas_transform.xform(bottom_right);
top_right = canvas_transform.xform(top_right);
bottom_left = canvas_transform.xform(bottom_left);

Color color = Color(0.5, 0.5, 0.5, 0.75);
p_overlay->draw_dashed_line(top_left, top_right, color, 5.0, 4.0);
p_overlay->draw_dashed_line(top_right, bottom_right, color, 5.0, 4.0);
p_overlay->draw_dashed_line(bottom_right, bottom_left, color, 5.0, 4.0);
p_overlay->draw_dashed_line(bottom_left, top_left, color, 5.0, 4.0);
}

ControlOffsetTransformPreview::ControlOffsetTransformPreview(EditorPlugin *p_plugin) {
plugin = p_plugin;
}

void ControlEditorPlugin::edit(Object *p_object) {
offset_transform_preview->edit(Object::cast_to<Control>(p_object));
}

bool ControlEditorPlugin::handles(Object *p_object) const {
return Object::cast_to<Control>(p_object) != nullptr;
}

void ControlEditorPlugin::forward_canvas_draw_over_viewport(Control *p_overlay) {
offset_transform_preview->forward_canvas_draw_over_viewport(p_overlay);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

ControlEditorPlugin::ControlEditorPlugin() {
toolbar = memnew(ControlEditorToolbar);
toolbar->hide();
add_control_to_container(CONTAINER_CANVAS_EDITOR_MENU, toolbar);

offset_transform_preview = memnew(ControlOffsetTransformPreview(this));
EditorNode::get_singleton()->get_gui_base()->add_child(offset_transform_preview);

Ref<EditorInspectorPluginControl> plugin;
plugin.instantiate();
add_inspector_plugin(plugin);
Expand Down
22 changes: 22 additions & 0 deletions editor/scene/gui/control_editor_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -252,14 +252,36 @@ class ControlEditorToolbar : public HBoxContainer {
ControlEditorToolbar();
};

class ControlOffsetTransformPreview : public Control {
GDCLASS(ControlOffsetTransformPreview, Control);

EditorPlugin *plugin = nullptr;
ObjectID selected_control_id; ///< Invalid when no control is selected, or when it has been deleted.

friend class ControlEditorPlugin;

public:
void edit(Control *p_control);

void forward_canvas_draw_over_viewport(Control *p_overlay) const;

ControlOffsetTransformPreview(EditorPlugin *p_plugin);
};

/// Editor plugin.
class ControlEditorPlugin : public EditorPlugin {
GDCLASS(ControlEditorPlugin, EditorPlugin);

ControlEditorToolbar *toolbar = nullptr;
ControlOffsetTransformPreview *offset_transform_preview = nullptr;

public:
virtual String get_plugin_name() const override { return "Control"; }

virtual void edit(Object *p_object) override;
virtual bool handles(Object *p_object) const override;

virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override;

ControlEditorPlugin();
};
Loading
Loading