I have a GameObject with a Content Size Fitter attached and would like to read out the height of the GameObject. Based on reading a dozen or so forums posts i've tried a lot of things, but can't get it to work. I think that this code should be almost correct, yet, it doesn't give me the right number:
GameObject go; // The game object whose height i want to read out
...
// Force rebuild before reading out height
LayoutRebuilder.ForceRebuildLayoutImmediate(go.GetComponent<RectTransform>());
// Read out height
float h = go.GetComponent<RectTransform>().rect.height;
Unfortunately, this does not give me the correct height, in the sense that it does not correspond to what i see in the inspector. The code returns a height of around 2400, while the inspector says that it is 1257.8 (which corresponds to what i see on the screen) - see screenshot below. Any help would be appreciated!
Screenshot
Try RectTransform.sizeDelta. I've used it to set the height of RectTransforms in a few projects. Presumably you could just use it to get the height too.
I have only been able to do this using Text.preferredWidth and preferredHeight, these seem to update immediately on changing the text. Of course, this only works if you are fitting to a Text.
Related
I have implemented a tooltip system on hover. For this I use a textmeshpro text and an image. This image should be a simple background depending on the length of the text, which is always different.
If I hover over a certain element, the textmeshpro text is replaced. The background image is moved to the element and the size is shall change based on the length of the text:
// Hover over slot in
public void OnPointerEnter(PointerEventData eventData) {
if (currentItem != null) {
Debug.Log("hover over slot item in");
GamePlay.EnableCanvas(GamePlay.GetHoverCanvas());
TextMeshProUGUI txtMeshPro = GamePlay.GetHoverCanvas().GetComponentInChildren<TextMeshProUGUI>();
Debug.Log(currentItem.GetItemDescription(currentItem));
txtMeshPro.text = currentItem.GetItemDescription(currentItem);
var hoverCanvasText = txtMeshPro.GetComponent<RectTransform>();
var hoverCanvasTextBG = GamePlay.GetHoverCanvas().GetComponentInChildren<Image>().GetComponent<RectTransform>();
hoverCanvasText.position = new Vector3(this.GetComponent<RectTransform>().position.x, this.GetComponent<RectTransform>().position.y + 50, 0);
hoverCanvasTextBG.position = new Vector3(this.GetComponent<RectTransform>().position.x, this.GetComponent<RectTransform>().position.y + 50, 0);
hoverCanvasTextBG.sizeDelta = new Vector2(txtMeshPro.textBounds.size.x + 15, txtMeshPro.textBounds.size.y + 15);
// No clue why image is not resized properly on first hover
}
}
The weird issue I have is that the first time I hover over the element, the image size wont change.
Replacing the text and changing the position always works without issues.
Issue description:
First hover over element A: Image background size wont change at all.
Second hover over element B: Image background size changes, but has the correct size of element A
Third hover over element A: Image background size changes, but has the correct size of element B
How is this possible? I dont get whats happening here. The code works without any issues for the position, why the same logic does not work for the size.
Note: I am very new to unity, tinkering arround for a few days now, so I could be a very simple issue, but after researching for a few hours now, I haven't found anything useful.
After setting the text of a TextMeshProUGUI, the textBounds and its size are not updated until the following frame. This is caused by Unity's deferred layout rebuilding. In their UI Layout Documentation, it reads:
The rebuild will not happen immediately, but at the end of the current
frame, just before rendering happens. The reason it is not immediate
is that this would cause layouts to be potentially rebuild many times
during the same frame, which would be bad for performance.
I'm gonna go ahead and guess GamePlay.GetHoverCanvas() is maintaining an object that's never destroyed and, therefore, your code is working in all cases except its very first run.
There are probably a few ways you can go about fixing this but the easiest would be to call Canvas.ForceUpdateCanvases() between setting the text and reading the size of the textbounds. It's not the fastest function but it should do the trick.
Another option would be to, instead of a synchronous function, kick off a coroutine that sets the text one one frame, yields the next frame, then reads the textbounds on its second frame of execution. (This would likely lead to the size popping in and might look undesirable).
Instead of refreshing all your Canvases, you can use TMP_Text.ForceMeshUpdate(); right after you assign your string. This makes sure your text-information are correct within the same frame.
If the parents of your text uses Layout-Groups, there is a fair chance that you need to update those as well. You do that with LayoutRebuilder.ForceRebuildLayoutImmediate( [Layout-Group.RectTransform] ); (read more here)
Please note that the order is important.
Assign your text
Rebuild Layout-Group
Force Mesh Update
To prevent the need to force update the Canvas, you can use TextMeshProUGUI.GetPreferredValues() to ascertain ahead of time what the width and height would be for the text currently assigned to it. It'll return a Vector2. You can then use that Vector2 instead of TextMeshProUGUI.textBounds.
Or if you need the size for a certain text without actually assigning it, use TextMeshProUGUI.GetPreferredValues(string text).
There are other overloads of that method to get the size given constraints in either width or height.
I have a fragment for more options in my app that I want mostly hidden except for one small rectangle that says "Pull up for more options". This rectangle is parked at the bottom of the screen. When the user pulls it up, it then pops up from the bottom but NOT taking over the full screen, only about 1/3rd of the bottom. Just enough to show some of the options (checkboxes).
I am doing this and have it working by setting the TranslationY setting of the options fragment layout, which is in a FrameLayout container by the way, so that it is at the bottom of the screen and just shows my "Pull up for more options" text.
Then, when they pull up on that, I have some motion events to bring it up to where I want.
Here is the issue, I can get it working just fine on one display using hard coded TranslationY settings. For example, on a Galaxy S2 which has a density of 1.5 (HDPI 240) and a screen of 480x800, these are my hard coded values that work. I had to find them just by playing around with the numbers.
int trackOptionsHome = 650; //Parked at bottom of screen.
int trackOptionsExtended = 450; //Extended out where I want it to.
Again, with those hard coded values on the S2, it works fine and the way I want. However, if I now try a different device that is STILL HDPI (1.5/240) except the screen size is 480x640 (3.5in), it does not display properly which is to be expected. So then I implemented something like this:
float trackOptionsHome = ((dMetrics.HeightPixels / dMetrics.Density) + 120);
float trackOptionsExtended = ((dMetrics.HeightPixels / dMetrics.Density) - 100);
This was to try to take into account for different display density and sizes. I was then trying to do math at the end of each to position my fragment where I want it. However, I am getting inconsistent results and the numbers are still arbitrary. I have to find them by playing around.
This raises two questions:
1. How do I make sure I get the results I want on DIFFERENT display densities, which is not even the issue I am having at the moment since I have the same density.
2. How do I scale properly for the different size screens, which appears to be my immediate problem.
For math purposes at the moment (I can adjust as needed after I get an answer) let's say I want 50px of the fragment showing from the bottom when it is in the home position and 300px showing up from the bottom when extended.
What is the correct way to do this?
Thanks!
Mike
Hello sorry for my bad English!
I created lines in a Viewport3D. Those lines are represented by parallelipipeds.
Problem: When I create multiple lines with the same caracteristics (width/height/depht), some are wider than the others.
Solution: I use Anti-Aliasing by setting the attached property of the
viewport3D => RenderOptions.SetEdgeMode(myViewport, EdgeMode.Unspecified);
The lines are now exactly the same. To find out the above solution, first I :
Checked if the lines had really the same caracteristics (in case I made mistakes in my programmation).
Result: The lines had exactly the same caracteristics (except they have differents coordinates).
Checked if it was because I represent one line with one parallelipiped. I supposed that if the parallelipiped was slightly rotated, then the width of the line projected could change.
So: I change parallelipiped for cylinder => the projection of a cylinder, regardless to the rotation of this one, always have the same width. But even with cylinders, some lines were wider than the others.
I realised that it wasn't my programmation the problem, but the rendering. And I found that I could change the rendering with the attached property EdgeMode.
When I set the EdgeMode to Unspecified (= Anti-Aliasing enabled) then all lines were perfectly rendered, they all have the same width. But if I set back EdgeMode to Aliased (= Anti-Aliasing disabled) then the problem was there.
My question is: Why do I have this problem? Can I do something else instead of changing the EdgeMode?
Thank you for any help.
I'm working a 2d tile-editor, used Winforms and XNA(for the map rendering) and i have a problem with the custom scrollbar.
I would like to know the formula for calculating the map scrolling depending the position value of my scrollbar.
Let me explain
Actually, i have a functional code but is not exact.
Here is my calculation (is the problem):
(Point Map.PixelMapSize = map size in pixel)
hScrollBarMap.Minimum = 0;
hScrollBarMap.Maximum = 100;
hScrollBarMap.LargeChange = hScrollBarMap.Size.Width * 100 /(Map.PixelMapSize.X);
mapScrollCalcul = ((hScrollBarMap.Value - hScrollBarMap.Minimum) * 100 / (hScrollBarMap.Maximum - hScrollBarMap.Minimum)) * Map.PixelMapSize.X / 100;
Sorry, I do not know how to explain. I found it by making a lot of tests...
But, the mapScrollCalcul is the final calcul to be applied to map position display.
What is the exact calculation for this ?
I hope you understand, I'm not speak English, just little understand.
But I understand you. :) (programmation language is universal)
Thank you for reading and, maybe, future responses.
You are overcomplicating things.
First, you are setting your LargeChange value relative to your image, not your view, and your scroll.Maximum relative to... what, exactly?
On MSDN, they say:
User interface guidelines suggest that the SmallChange and LargeChange
properties are set relative to the size of the view that the user
sees, not to the total size including the unseen part. For example, if
you have a picture box with scroll bars displaying a large image, the
SmallChange and LargeChange properties should be set relative to the
size of the picture box, not to the size of the image.
Now, lets assume that your image is 1000x1000 and your view is 100x100, and you are scrolling vertically. Then:
myVscroll.Minimum=0;
myVscroll.Maximum=image.Height; //1000
myVscroll.SmallChange = 10; // here: view.Height/10 or other value that makes scrolling look smooth
myVscroll.LargeChange = 100; // in this example, this is set to view.Height
And finally:
mapScrollCalcul = myVscroll.Value;
I have an application where I need to dynamically build the content to a Canvas. Everything works just fine, but I am a little unsure of how I can set the y coordinates for the labels in the safest way. For example, I need to add three labels that are essentially lines of text. In Java Swing or C# GDI I would just query the the font for the line height and add that value to the y coordinate of the drawText command.
This is my code.
double y = 0.0;
_line1.Content = "Line1";
_line1.SetValue(Canvas.TopProperty, y);
_line1.SetValue(Canvas.LeftProperty, 0.0);
CanvasChart.Children.Add(_line1);
double textHeight = _line1.Height;
y += textHeight;
_line2.Content = "Line2";
_line2.SetValue(Canvas.TopProperty, 0.0);
_line2.SetValue(Canvas.LeftProperty, y);
CanvasChart.Children.Add(_line2);
This does not work because _line1.Height does not seem to be set to anything useful at this point. I suppose it has not rendered yet. The above code is in the loaded event for the window. ActualHeight does not help either.
Most code that I've seen seems to just set them to a hard coded value. That I suppose looks right on the developer's display, and you just hope looks good at other resolutions/DPI. In Swing and GDI I always had the best results finding out exactly how many pixels a string will be rendered at and using this to offset the next line.
You must call the Measure method, specifying an infinite available size. This will update the DesiredSize of the control:
_line1.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
double textHeight = _line1.DesiredSize.Height;
Another easy way to achieve the desired effect is to put the labels in a StackPanel.
In Swing and GDI I always had the best results finding out exactly how many pixels a string will be rendered at and using this to offset the next line.
This is possible in WPF as well. The GlyphTypeface class provides the AdvanceWidths and AdvanceHeights properties for each character in a typeface. By using CharacterToGlyphMap, you can map a character to an index within the AdvanceHeights, and use that to determine the actual height of any character.
For a detailed example, see GlyphRun and So Forth.