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

API proposal to get the computed measure size of a node #1535

Closed
Omxjep3434 opened this issue Jan 1, 2024 · 9 comments
Closed

API proposal to get the computed measure size of a node #1535

Omxjep3434 opened this issue Jan 1, 2024 · 9 comments

Comments

@Omxjep3434
Copy link

Omxjep3434 commented Jan 1, 2024

API proposal

I am using the Javascript Yoga npm package and have an API proposal to get the measure size of a node. Similar to the getComputedLayout() method, I am proposing that a getComputedMeasureSize() method is added. Or, perhaps the measure size be included in getComputedLayout() so that the returned object looks something like { left: 0, right: 0, top: 0, bottom: 0, width: 0, height: 0, measureWidth: 0, widthMeasureMode: 0, measureHeight: 0, heightMeasureMode: 0 }. I'm not familiar enough with the source code to know if this makes sense for every node. I am assuming that every node has a measure size, but it is only exposed to the developer when a measure func is invoked.

My use case

I am integrating Yoga with a custom 3D layout system and it is very important that I compute a measure size for Yoga nodes, particularly leaf nodes with a measure func, so that I can compute a measure size that also includes depth using additional custom logic.

My attempt at a solution with the current API

If Yoga executes the measure func, the measure size is provided as parameters to the measure func, so it can easily be used. But, if the measure func is not invoked because the leaf node has fixed dimensions, I need to compute a measure size by basically duplicating what I think Yoga does to compute a measure size which of course is very hacky and bug-prone. This is complicated by the fact that Yoga uses caching. So, after the first execution, it's difficult to know whether Yoga is still using the measure func result, or things have changed such that the node is no longer auto sized. In both cases, the measure func won't be executed, so I don't know whether I should use the measure size of the last invocation of the measure func, or compute a measure size myself.

@NickGerleman
Copy link
Contributor

NickGerleman commented Jan 3, 2024

If Yoga executes the measure func, the measure size is provided as parameters to the measure func, so it can easily be used. But, if the measure func is not invoked because the leaf node has fixed dimensions, I need to compute a measure size by basically duplicating what I think Yoga does to compute a measure size which of course is very hacky and bug-prone.

As you mention, Yoga is not guaranteed to call the measure function, and the last call might not be the final layout. As part of YGNodeCalculateLayout, Yoga will set a flag on every Node where it or one of its children may have a new layout result compared to the last layout. A common pattern is to traverse down, applying the results of any nodes with a new layout, and resetting the flag.

The size which should be imposed on the content is the computed layout of the node, minus any padding or border. Yoga will have called into the platforms measure function as part of determining this, if neccesary.

There's an open issue where JS does not currently expose YGNodeGetHasNewLayout or YGNodeSetHasNewLayout but I think adding it would be pretty simple. #681

@Omxjep3434
Copy link
Author

Omxjep3434 commented Jan 4, 2024

I'm not sure that exposing YGNodeGetHasNewLayout would solve my issue.

Let me try to articulate my problem a bit more in a visual way.

Below is a screenshot where the gray node is the Yoga root node, and the red node is an auto-sized leaf node where the measure width comes from the root node. The white squares are arbitrary children that can be thought of as objects outside of the Yoga layout.

The leaf node distributes N children in an ellipse that is the size of the available area. Notice that the distribution is different depending on the available size, so the results are different if the available width is 10 vs. 3, for example. No matter if the leaf node is auto or fixed size, an algorithm needs to execute to distribute the children after Yoga has processed. It's crucial that the children are distributed against the measure size, not the computed size. For example, using the screenshot as a reference, if the width measure size of the leaf node is 10, the computed width might only be 4. It would be incorrect to distribute the children in the leaf node using a width of 4 because it changes the shape and size of the distribution - it would be much more narrow than it should be.

yoga_measure_issue

So, things are trivial on the first execution of Yoga. I know if the measure func has executed and I have the measure size. If the measure func didn't execute, I can assume the node is fixed-size and compute a measure size using the node's computed size, padding and borders.

But, after that, it seems nearly impossible to get a reliable measure size because of caching. Even if I had a YGNodeGetHasNewLayout value, it wouldn't tell me if the node is auto-sized or fixed size (if the measure func is being used or not), and which dimensions specifically are auto/fixed. I also want to point out how difficult it would be to determine if a dimension is auto or fixed. Even if a dimension layout value is set to 'auto', it may have a 'flex' value that effectively makes it fixed-size. Further, whether a dimension is auto-sized is dependent on the parent flex direction and align type. I'm sure there are other things I'm not even thinking of.

Possible Yoga API solutions:

  1. I think the ideal solution would be the ability to get the measure size of a node at any time after Yoga has executed. I know the measure func can be executed more than once, but after execution, isn't there a final measure size that has been computed/used for a node? Or, another way to say it - isn't there a measure size that corresponds to the computed size of a node? Also, if the node is fixed-size, then the measure size would be the computed dimensions minus padding and borders, and both the width/height measure mode would be 'fixed'.
  2. Some method to determine if the measure func was used during the last execution of Yoga, even if it was not invoked because of caching. This allows the developer to know if the last provided measure size should be used, or all dimensions are fixed and a measure size can be computed using padding/borders.
  3. Some method to determine if a dimension is auto-sized / measured using the logic described above. If either the width or height are auto, it can be assumed that the measure func is being used, and the last provided measure size can be used. Otherwise, the node should be treated as fixed-size.

@NickGerleman
Copy link
Contributor

Or, another way to say it - isn't there a measure size that corresponds to the computed size of a node? Also, if the node is fixed-size, then the measure size would be the computed dimensions minus padding and borders, and both the width/height measure mode would be 'fixed'.

The content size should be the final computed size (border-box size), minus padding and borders, regardless of the mode of measurement. I.e. you should read this off of the layout results to use for sizing with the external layout system.

The results from a measure function should not be assumed to be related to the final layout of the node.

@Omxjep3434
Copy link
Author

Omxjep3434 commented Jan 4, 2024

Okay, I think that's my disconnect. I don't think my use case is supported by Yoga. Can you confirm Nick, and do you understand what I'm trying to articulate with my description and image? It's sort of a catch-22. If Yoga asks me the size of my node via the measure func, I need to use its provided measure size to predict how my items will be distributed and return a computed size. But, I can't then later use that computed size (or the border-box size) to distribute my items because that makes a distribution that has a different size than what was provided to Yoga in the measure func result and the layout is simply incorrect.

If this use case isn't supported by Yoga, do you have any other suggestions on how I might at least partially implement this? Identifying if a dimension is auto-sized would help, but as I mentioned in my previous message, I feel like that might be too complicated. Also knowing whether the measure func is being used (even if cached) would help, but I've run out of ideas on how to reliably know that.

@NickGerleman
Copy link
Contributor

NickGerleman commented Jan 4, 2024

If I am understanding correctly, the overall scenario should be supported, but how it needs to be handles depends on what the hosting code looks like.

The measure function is Yoga asking for hypothetical size (but Yoga can impose its own).

After the Yoga layout step, the leaf node with measure function has a known size, as calculated by Yoga, but the item has not yet been laid out.

The step needed, is after Yoga does layout, the node with measure function should be told to calculate its own layout, using the final result Yoga came up with. I.e. tell the red box to distribute its items, using the final layout result from Yoga.

This will incorporate intrinsic size as returned by the measure function, if the size in CSS would depend on content, but box size is allowed to be larger or smaller or smaller than content, e.g. if user sets explicit width/height. How to handle the larger or smaller box than natural content depends on desired integration with external layout system.

@Omxjep3434
Copy link
Author

Hmm, I'm still not seeing how my use case would be possible.

Here's another screenshot of the entire layout process. To summarize, the leaf node distributes N children in an ellipse that is the size of the available area.

yoga_measure_issue 2 small

The crux of the issue here is that the distribution algorithm is not linear like traditional layout systems. Notice that if I were distributing items against a horizontal line, like Yoga, this issue would not exist.

@NickGerleman
Copy link
Contributor

Okay, when you mention "measure size", you mean the available space the node can be sized into. I think I have been misunderstanding that.

This size is not always consistent with the parent. E.g. Yoga (or other layout systems) can size the node into infinite available space (like when overflow: "scroll" is set). Or, the parent may be sized on its content, and its final dimensions end up smaller than the available size.

In this example, it seems like you could distribute using the measured size of the parent. Is that something you could do more generally?

@Omxjep3434
Copy link
Author

Ah, sorry. When I say "measure size" I'm referring to the parameters of the measure func (width, widthMode, height, heightMode). From experimentation, it seems that the measure size of an auto dimension comes from the first fixed-size ancestor. The measure size of a fixed dimension is the border box size (the fixed dimension minus border and padding).

In this example, it seems like you could distribute using the measured size of the parent. Is that something you could do more generally?

That seems intuitive but I'm not sure that would work in all cases. What if the parent is autosized? Then the measure size comes from an ancestor. The key to the issue is knowing what size was used to produce the measurement (the parameters of the measure func). I've explained above why that is difficult to get reliably because of caching, but let me know if you want me to expand on that.

@Omxjep3434
Copy link
Author

Just wanted to share my tentative solution. When the measure func is invoked, I am saving both the measure size (measure func params) and the measurement result. In my custom distribution phase, if the computed size of the node equals the saved measurement result, I am assuming the node is being measured and distributing against the saved measure size. I don't think that's perfect but should work reasonably well.

Nick, if you're not interested in any of my API proposals, you can go ahead and close the issue. I'll also put in a vote for exposing the YGNodeGetHasNewLayout like you originally mentioned. That would be useful to my project in multiple ways.

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

No branches or pull requests

2 participants