Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add render transform to Control nodes #87081

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

timoschwarzer
Copy link
Contributor

@timoschwarzer timoschwarzer commented Jan 11, 2024

This is an implementation of my proposal outlined in this discussion: godotengine/godot-proposals#7053 (comment)

tl;dr: Currently it's hard and complicated to animate Control nodes without breaking the layout, especially if the Control nodes are in deeply nested layout structures which is often the case. This PR adds new properties to Control nodes that specify a render transform that does not affect and is not affected by layouting (similar to CSS' transform). These make UI animations much less pain to work with

  • render_offset: translation
  • render_scale: scale
  • render_rotation, rotation
  • render_transform_pivot, pivot for scaling and rotating

render_offset and render_transform_pivot are relative to the size of the Control node by default (I think it's a very common usecase to rotate around and scale from the center) but it can be toggled to use absolute pixel values instead.

Screencast.from.2024-01-11.17-10-13.webm

Considerations

Input is currently not affected by the render transform. That means if you e.g. apply a render offset to a button, the button is still clickable at its original position but drawn somewhere else. This might need some discussion whether that's desirable or not.

scene/gui/control.h Outdated Show resolved Hide resolved
scene/gui/control.cpp Outdated Show resolved Hide resolved
scene/gui/control.cpp Outdated Show resolved Hide resolved
scene/gui/control.cpp Outdated Show resolved Hide resolved
scene/gui/control.cpp Outdated Show resolved Hide resolved
scene/gui/control.cpp Outdated Show resolved Hide resolved
scene/gui/control.cpp Outdated Show resolved Hide resolved
scene/gui/control.cpp Show resolved Hide resolved
scene/gui/control.cpp Show resolved Hide resolved
@fire
Copy link
Member

fire commented Jan 11, 2024

I remember Node2D has properties.

Vector2 | position | Vector2(0, 0)
float | rotation | 0.0
Vector2 | scale | Vector2(1, 1)

How are these different?

Edit:

Will review the proposal.

@Mickeon
Copy link
Contributor

Mickeon commented Jan 11, 2024

One could argue that both render_rotation and render_scale are not that useful since (unless I'm severely mistaken) Controls already ignore both rotation and scale within containers and when moving anchors/margins.

@timoschwarzer
Copy link
Contributor Author

@Mickeon this is currently considered a bug: #19068

That's why I added rotation and scale render transforms separately although they have the same result currently :)

@Mickeon
Copy link
Contributor

Mickeon commented Jan 12, 2024

I've gotten so used to the bug it may as well be a feature to me. 5 years ever since it has been reported, too. This "bug" does have its advantages in the same exact way as render_offset does in the PR. Hmm...

@timoschwarzer
Copy link
Contributor Author

@Mickeon yeah, that's why I added render_rotation. So if the bug with rotation gets fixed eventually, people who used it for animation can switch to render_rotation and people who want rotated controls to actually use more space can finally do that. Same for scale and render_scale.

@djrain
Copy link

djrain commented Jan 12, 2024

How this would affect children of the animated node, if at all? In our games we're usually animating whole control-based scenes or Containers, like a playing card or something, frequently containing both Controls and Node2D, so the whole thing needs to be animated as a unit. I currently achieve that by applying an animation transform to the node but only after any parent Container does its layout.

@timoschwarzer
Copy link
Contributor Author

@djrain the current implementation sets the CanvasItem transform of the Control node, so it affects all children as well.

@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch from c5123f0 to ff160eb Compare January 12, 2024 19:43
@timoschwarzer
Copy link
Contributor Author

@AThousandShips thanks for the review 🙌 applied everything and fixed code style issues

scene/gui/control.cpp Show resolved Hide resolved
scene/gui/control.cpp Outdated Show resolved Hide resolved
scene/gui/control.cpp Outdated Show resolved Hide resolved
scene/gui/control.cpp Show resolved Hide resolved
scene/gui/control.cpp Outdated Show resolved Hide resolved
scene/gui/control.cpp Outdated Show resolved Hide resolved
@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch from ff160eb to f7ce79c Compare January 12, 2024 20:23
@timoschwarzer
Copy link
Contributor Author

@AThousandShips now it should have everything except the px suffix

@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch 2 times, most recently from 7b26c72 to 5eed27e Compare January 12, 2024 21:13
@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch from 5eed27e to 891f968 Compare January 21, 2024 11:51
@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch 2 times, most recently from d068334 to d8bdea9 Compare February 2, 2024 22:03
@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch from d8bdea9 to 7e3e661 Compare February 6, 2024 18:38
@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch 2 times, most recently from dcadbb1 to 1383ae0 Compare February 17, 2024 17:40
@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch 2 times, most recently from 950d99d to e7a7125 Compare February 23, 2024 13:40
@Mickeon
Copy link
Contributor

Mickeon commented Feb 23, 2024

Two things:

  • You should add these lines (and ideally roughly document them) in Control.xml otherwise that Linux build will never compile
  • When the PR is in "Draft" state it means it is not ready for review. I don't know if this is intentional.

@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch 2 times, most recently from 71bbe62 to ca88cf2 Compare August 9, 2024 08:29
@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch 2 times, most recently from 0d77a96 to ebffead Compare August 15, 2024 08:09
@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch from ebffead to 45ad111 Compare August 27, 2024 09:21
@fire fire requested a review from a team September 2, 2024 07:43
@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch from 45ad111 to db92f85 Compare September 2, 2024 23:18
@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch from db92f85 to e6b33da Compare September 10, 2024 22:09
@fire
Copy link
Member

fire commented Sep 10, 2024

The general conversation from godotengine/godot-proposals#7053 seems to be the maintainers are divided on adding this.

When there's a clear agreement pull requests get merged quickly, but with disagreement enhancements get stuck.

@Mickeon
Copy link
Contributor

Mickeon commented Sep 11, 2024

I do really appreciate that @timoschwarzer has been keeping this PR rebased and up-to-date as much as possible. I'm not part of the team of interest, but I am one of many Godot users.My worry is the same as it used to be from the start. That is, nodes in general are not exactly light in memory and the additional overhead may prove problematic, given that most Control nodes do not need this.

So the question to me always remains: is the trade-off worth it? Regardless, the feature is obviously very, very desired.

@timoschwarzer
Copy link
Contributor Author

@Mickeon would it help to benchmark the actual impact this change would have, especially in UI heavy projects such as the Godot editor itself? I think I could do that.

@Mickeon
Copy link
Contributor

Mickeon commented Sep 14, 2024

I can't say benchmarking would do a lot for the people of interest, because there's still the question of whether Control nodes should be capable of this in the first place.
But it may confirm or deny my own suspicious about the "unnecessary" overhead. I do repeat that there may be other ways to accomplish this that could be more satisfactory.

@timoschwarzer
Copy link
Contributor Author

I will be busy for two weeks now but will try to get some meaningful data then.

I do repeat that there may be other ways to accomplish this that could be more satisfactory

I would of course like to see more sophisticated ways to accomplish animations for Control nodes but so far the other solutions I have seen came with limitations and aren't as flexible while I do see and have had use cases to animate both Control and Container nodes. When you say "more satisfactory", do you tie that directly to "not having render transform properties on all Control nodes"?

@timoschwarzer
Copy link
Contributor Author

@Mickeon I finally did some benchmarks:

Benchmark

Sample 1: Master branch at 1fc8208
Sample 2: Same as Sample 1 plus this commit

For each sample, I launched the editor into an empty project, let everything load and then closed the editor again. Then I launch the same editor executable with a profiler, let it open and close it again. Each sample was profiled three times, the results per sample were roughly the same.

System:

Godot v4.4.dev (48bcad089) - Arch Linux #1 ZEN SMP PREEMPT_DYNAMIC Thu, 12 Sep 2024 17:17:51 +0000 on Wayland - X11 display driver, Multi-window, 2 monitors - Vulkan (Forward+) - dedicated Intel(R) Arc(tm) A770 Graphics (DG2) - Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz (16 threads)

Compiled with

scons platform=linuxbsd target=editor production=yes linker=mold lto=none debug_symbols=yes

Profiled with:

valgrind --tool=massif ./godot.linuxbsd.editor.x86_64 -e --path /path/to/empty/godot/project

Results

Sample 1 (master branch):
Peak Heap allocation: 574.13380 MiB - 574.17510 MiB

Sample 2 (this PR):
Peak Heap allocation: 575.23056 MiB - 575.23404 MiB

Difference:
1.05894 MiB - 1.09676 MiB
This PR causes the editor to use approximately 0.18% more heap memory.

@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch from e6b33da to 48bcad0 Compare September 29, 2024 10:03
@KoBeWi
Copy link
Member

KoBeWi commented Oct 15, 2024

I think this is the only way to reliably animate Control nodes inside Container with AnimationPlayer when their size is undetermined. Which is nice, but I think such strict requirement is not that common. There are multiple workarounds and if all fails, you can use Tweens.

That said, I think there is a way to have this feature without much overhead (currently, aside from memory, it also makes transforming more complex). The render_* stuff can be moved to a separate struct, which is null by default. Once any render property is set for the first time, allocate the struct. This limits the additional memory usage to single pointer for most Controls and allows to opt-out from the transform operations when the struct is null. Though not sure if doing this is a good idea 🤔

Other than that, since this is purely visual, should these properties be serialized in the scene? Especially with the input problem you mentioned? The new properties should have their purpose documented in Control's class description (i.e. that they are meant only for animation). If the usage is optional like I described above, and it's properly documented, I think it might be fine to have this feature.

EDIT:
Though looking at the proposal again, I think this can be a new Container too. The only argument against that was that Control workflow involves lots of nesting, but it's fine in this case, because you'd use this container only for this specific case. Also in a scene with 10 levels of nesting, one extra node is irrelevant.

EDIT2:
Then again, such container is quite easy to do in GDScript, as even noted in the proposal.

@timoschwarzer
Copy link
Contributor Author

Warning: personal opinion and experiences. 🙂

I think this is the only way to reliably animate Control nodes inside Container with AnimationPlayer when their size is undetermined. Which is nice, but I think such strict requirement is not that common.

I must completely disagree with your statement about this not being a common requirement. In every project so far that involves UI, a feature to apply a render transform to Control nodes of different kinds would have made things much easier to create. And it already helped a lot since I use a fork with this PR applied. Yes, I could have used Tweens for many of the things that I animated, but next to not being able to react to transform changes in parent nodes, Tweens just cannot hold up to the iteration speed and quality that AnimationPlayer gives. Being able to change animation easing curves and previewing them with one click - or even using Bezier curves - is a huge reduction in effort that I need to spend to get the same results in comparison to using Tweens. Looking at the reactions to this PR and the proposal, I don't seem to be alone with this opinion.

That said, I think there is a way to have this feature without much overhead (currently, aside from memory, it also makes transforming more complex).

0.18% more memory usage in the most UI heavy Godot project that I know of (the Editor) still does not sound like much overhead to me, but a solution like you pointed out sounds like a good compromise, if it's really a concern.

Also in a scene with 10 levels of nesting, one extra node is irrelevant.

I would agree immediately if Godot had some kind of slot mechanism that lets you define where children should be inserted for instanced scenes. Too often have I been at a point where I got screwed by this to be able to agree here right now. I have had dozens of such cases where this deep nesting of controls prevented modularization of logical Control "components" into separate scenes. I cannot tell immediately if this would be a problem if render transformation is achieved using Containers.

@KoBeWi
Copy link
Member

KoBeWi commented Oct 15, 2024

I would agree immediately if Godot had some kind of slot mechanism that lets you define where children should be inserted for instanced scenes.

Soon: #84018

@torcado194
Copy link

torcado194 commented Oct 16, 2024

Just to give my two cents: I think this feature is invaluable, to the point that I had already implemented this myself locally (though just for offset, not rotation). I've been using this PR for several weeks and have found no issues with it.
I very much prefer having these properties on Control, regardless of the memory overhead. I think a dedicated node for render offset is a fine alternative, but I am also of the opinion that I would rather reduce the necessary nesting in the scene tree, at least for something as general as a visible offset.

My only feedback is about making the "relative to size" toggles default disabled, to keep them in-line with how transforms work everywhere else (that being in absolute pixels), and because positioning in absolute units is much more common in my experience. I agree that rotating around the center is the most common case for the rotation pivot, but at the very least I don't think that decision should effect the transform offset (and should probably also include similar changes to the basic pivot offset unit for consistency).

@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch from 48bcad0 to be24431 Compare October 20, 2024 09:11
@Mickeon
Copy link
Contributor

Mickeon commented Nov 8, 2024

For the ones interested in this feature, see and give feedback to the following alternative as well:

@timoschwarzer timoschwarzer force-pushed the feature/control-render-transform branch from be24431 to 7c5e2ec Compare December 6, 2024 07:29
@dog-on-moon
Copy link
Contributor

dog-on-moon commented Dec 9, 2024

I'd like to keep pushing for this PR a bit, so for anyone reading/catching up, here's my thoughts to try and pull together the current state of things:

  1. The TransformContainer PR is a fine alternative, but my concerns are that it is currently less effective for developing UI than this one (see Add TransformContainer #98955 (comment))
  2. I'm unsure how to run or setup appropriate benchmarks for Godot, so if there is anyone interested in testing specifically the editor startup time before and after this PR, that would help validate the performance impact of this change as well!

Another major thing standing in the way of this PR is adjusting how the properties are shown in the inspector to make them a bit cleaner. In my opinion, the nicest way to do this would be to format the options to have parity with the existing Transform category for Control:

image
(ninja edit: adding the "Lock/Unlock Component Ratio" button on the render_scale vector would be a good bonus, similar to Control's scale property)

And to the extent of supporting parity, I'm tempted to favor removing "offset_relative_to_size" and "transform_pivot_relative_to_size" as well -- primarily because "offset_relative_to_size" can already be false in most cases, and we already expect developers to manually set the Control's pivot offset to size / 2 in its property documentation for scaling about its center. It's a kind of complex option that I would prefer to be reimplemented via scripting or done manually in most cases.

@timoschwarzer
Copy link
Contributor Author

timoschwarzer commented Dec 11, 2024

@dog-on-moon I agree that the inspector properties should be more intuitive to use, like with transform properties.

And to the extent of supporting parity, I'm tempted to favor removing "offset_relative_to_size" and "transform_pivot_relative_to_size" as well -- primarily because "offset_relative_to_size" can already be false in most cases, and we already expect developers to manually set the Control's pivot offset to size / 2 in its property documentation for scaling about its center. It's a kind of complex option that I would prefer to be reimplemented via scripting or done manually in most cases.

I'm not so sure about this one. I feel like rotating and scaling around the center is a very common use case for animations (which this PR mainly targets), if not even the most common use case. Having to manually update the pivot every time the control size changes sounds like a lot of boilerplate that I'd rather not want to be needing to do for every single control. Dynamically sized controls are quite common, e.g. for Labels or Buttons with translated strings.

It wouldn't be an issue though if there was something that could keep the pivot at a certain relative position of the control.

@dog-on-moon
Copy link
Contributor

I feel like rotating and scaling around the center is a very common use case for animations (which this PR mainly targets), if not even the most common use case. Having to manually update the pivot every time the control size changes sounds like a lot of boilerplate that I'd rather not want to be needing to do for every single control. Dynamically sized controls are quite common, e.g. for Labels or Buttons with translated strings.

It wouldn't be an issue though if there was something that could keep the pivot at a certain relative position of the control.

Yea.. I do completely agree with this being the most common use case. I think I have nitpicky concerns either way:

  1. if we have pivot offset be in absolute pixels, I think it is more intuitive from the inspector and it shares parity with the existing Control field, but I agree with you that it would be a very inconvenient default field for the common use cases.
  2. if we have pivot offset be relative to scale, I think it would be a tad less intuitive than absolute pixels, especially right now where the center offset is Vector2(0.5, 0.5).

To try and make point 2 more intuitive, I tried exporting a Vector2(50, 50) with the hint string "suffix:%". Maybe this is more tacky? Just trying to come up with a inspector view for it that would help express it to users.

image

After some thinking I believe I am in favor for breaking parity here and keeping the pivot offset as relative to scale. But I could still go either way, I would like for a maintainer to make the call here.

@timoschwarzer
Copy link
Contributor Author

Another thing where we have relative values similar to these here are anchors, but there we don't present the value as percentages in the inspector.
Either way I think these two at least should match up. If the relative pivot offset is presented as a percentage, anchors should, too.

@Mickeon
Copy link
Contributor

Mickeon commented Dec 22, 2024

Regarding the percentage, we should stick to using values between 0.0 and 1.0. However that is displayed in the Inspector is worth tackling separately. These are not the only properties affected by this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.