Bounds vrs Frame Center etc in ui
@omz, are the concepts of frames and bounds , Center not 100% correct?
Look if it's a little broken that's ok. But I am never sure if it is just me, oR I just don't understand your coord system. Maybe there is some missing documentation.
But to me, center for example seems wrong. If I set a buttons center to the Center of its view, it's off by the menu height / 2. The magic 44 number.
But it would be great to know if it still has kinks or I just misunderstand how it works.
This post is deleted!
@JonB , I seen you deleted your post. But what you said was correct about what I was doing. But I have asked this question many times before in different ways. But it's the chicken and egg syndrome, which comes first. When presenting a view in my mind, needs to be some sort of pre-flight. As the the sizes that are eventually displayed can be very different from device to device.
But I don't see a way around it.
Something like bounds = v.present('preflight') or can be implemented many different ways. But as far as I can see, its required unless their are some tricks I am unaware of.
But I normally do like this
import ui class Tester(ui.View): def __init__(self, frame): self.frame = frame self.background_color = 'white' btn = ui.Button(title = 'test button') btn.width = 100 btn.center = self.center self.add_subview(btn) if __name__ == '__main__': f = (0,0,500,500) t = Tester(frame = f) t.present('sheet')
If I do it after the present will be ugly. I think you can turn off updates, but I am sure not the way it was meant to work
btn.center = (self.bounds.center())
(this only works in beta, where bounds returns a Rect)
View.center is the same as View.frame.center. So saying
btn.center=self.center only makes sense if these are both subviews of the same parent view. If btn is a subview of self, you must set btn's frame center (aka btn.center) equal to self's bounds.center(). Set your button height to be nearly equal to the frame height to see that this actually worked (I wasnt sure at first)
Note that when presenting as a sheet, there is (or was... not sure if I retested this one recently) a bug where very small or very large sheets did not honor the frame size (frame got changed after presenting). Unless you set up flex, you need to be careful trying to set absolute coordinates prior to presenting.
Strange.. the deleted post was a leftover from a different thread. Not sure if you actually saw the real post or not. Your problem is the
self.centerline. The only reason your code sort of works is because you happen to have zero offset between t and f. if you added your tester as a subview in a different location, it won't work.... because frame is expressent in superview coordinates, and bounds is expressed in local coordinates.
btn.center = (self.bounds.center())
is all you need from your original code. Thus, you se btn' frame center (in self's coordinate system) to self's center (in self's coordinate system). The other way, tou are trying to set btn's position in self's coordinate system to self's center in t's coordinate system, meaning it will get an offset twice.
@JonB , ok. But I will still say it's broken. It's not a problem that is broken. But, I don't think it was meant to be like this. I think it's crazy you can't reply on stuff before presenting. It's like a begin transaction end transaction block, or it should be.
Only one thing missing, a preflight method/function/param to present. If that was present, returning the real bounds that it will when the actual .present is called, all these issues go away.
in your case, your frame was not being resized, it is being shifted. If it sidnt, your view would be presented at the upper left corner, not centered in the view. Your fix only works because you are misunderstanding self.center.
@JonB , ok I will take some time to reflect a little and try and get my head around it. Maybe I will get the Eureka moment. I hope so. Frustrates the hell out of me. But I am always open I am just wrong as it turns out many times I am.
Actually, I guess the confusion is, perhaps, that the title bar is not part of your view's height. your button actually was centered (due to luck though, that would only have worked in init where you set your frame x and y to 0), but it looked off-center because of the title bar.
Since you cannot place anything in the title bar, it makes sense that it is not part of your view's content, otherwise you would have to layout your view differently depending on whether the bar is there or not. The idea here is that 0,0 starts at the top of your view's editable content.
My recollection of buggy resizing was actually for popover, not sheet. Sheet does resize to 576 if the view is too large, such that the title plus height exceed screen height, but it is perfectly happy to show a small view.
import ui button = ui.Button(title='button') button.present(hide_title_bar=True) print('center', button.center) print('bounds', button.bounds, button.bounds.center()) print(' frame', button.frame, button.frame.center())
# hide_title_bar=False ('center', Point(512.00, 416.00)) ('bounds', Rect(0.00, 0.00, 1024.00, 704.00), Point(512.00, 352.00)) (' frame', Rect(0.00, 64.00, 1024.00, 704.00), Point(512.00, 416.00)) # hide_title_bar=True ('center', Point(512.00, 384.00)) ('bounds', Rect(0.00, 0.00, 1024.00, 768.00), Point(512.00, 384.00)) (' frame', Rect(0.00, 0.00, 1024.00, 768.00), Point(512.00, 384.00))
phuket's question was about sheet, where you do have control of the frame.
In full screen, your frame gets... the full screen minus whatever title bar is presented. The values here look to be correct, and the way you would expect them to be.
fullscreen has its own issues, for instance convert_point does not work properly in most orientations.
Understood. My message was that printing out
Also that it is possible that
view.center != view.bounds.center() != view.frame.center().
Look, I will still reflect on this. But every time I have mentioned a preflight (for the want of a better word) it's just ignored. I just see this as the most logical answer. Yes we can do all our set up after present, but it doesn't make sense, and apparently it doesn't make sense to to it before.
In mind it's so simple. We have no idea what @omz display algorithm will size the eventual ui.View at. So, if we can call a function/ method with the present details, he just returns information about the size he will create without creating anything, when we end up calling present. I honestly can not see how it will be ever resolved otherwise.
Again if I am wrong , I am wrong. But I feel strongly about this point
A few last points. I know it works out if you use layout . But that's the only way.
Unless you use layout callback, it can never work. I suspect this is the way it was crafted in the first place. I don't even need to understand Python to know I don't know what I don't know.
If the end point for the view size/bounds whatever is present, then without using layout the only logical way is to position all your objects after present is called.
I have never seen any sample code here like that. I am happy to do it that way if that's the way.
But again, Center is only one issue. And yes, I understand I could have just misunderstood it. But I think the root problem is that the final size of your ui.View is unknown. Even if it was, next year, a new device and @omz modifies his algorithm, back to square one
We have no idea what @omz display algorithm will size the eventual ui.View at.
We do when
From the ui docs...
def layout(self): # This will be called when a view is resized. You should typically set the # frames of the view's subviews here, if your layout requirements cannot # be fulfilled with the standard auto-resizing (flex) attribute. pass
As I have written before, I used to avoid ui.View.layout() but now I embrace it. Those fleeting moments of sanity are a beautiful thing!
@ccc , really? Look, I understand if you write anything close to important you need to use layout. For orientation etc.. Doesn't mean the rest of it should be broken.
Again, I don't understand why my simple premise is not embrassed. This can all work. Just need to be able to call something like bounds = present.preflight('sheet')
Again, I don't get it... It's just so logical
For sheet, you know exactly what size your view will be (unless you make it too large). You can use layout() as a "preflight", or to call a preflight function, it is called whenever something is resized, use a flag if you only want it to run once, though be careful that you don't change the frame after creation. Or, use the flex attributes. For instance if you set btn.flex='lrtb' your original code would keep the button centered in all view types.
If you want to make ui's that are orientation friendly, you are forced to use layout or flex anyway, anything you hard code based in some preflight will be broken anyway once you rotate the device.
How would preflight know how you plan in presenting your view, and in what orientation?
It would be nice if the title bar sizes for the various modes and devices was documented, though more for testing what your ui looks like in a small device.
@JonB , well preflight could return a tuple for horizontal and vertical frames or bounds or whatever they are.
Again, I will reiterate, we can't know what we don't know. But Pytonista knows what is going to do. We just need to be able to ask it without it doing anything other than returning the results of the sizes of what it is going to based on what we are passing to it.
Apple Watch is an example.
I would put money on the way to resolve this issue is a preflight method. All the code is there already. It also makes omz more device independent from the api.
JonB last edited by JonB
Here you go. I tested on iPad, but you should test this on iPhones, I believe the landscape title bar is only 32 instead of 64, as mentioned in the referenced thread.
For sheet, this function returns the max size... if you exceed width or height, presenting as sheet will result in a 576 square. Maybe this function should take a desired frame as an input, returning final frame size. Also, technically, sheet doesn't work on iPhones, so perhaps that should be sensed and accounted for here. These improvements are left as an exercise to the reader :)
Again, I can think of only a very few cases where this cannot be done, and done better with either flex or layout. I will admit flex can be annoying, as it takes many layered views to do anything complex. Frankly, for simple throwaways, I usually present first, the 50 msec of dark while the view sets up is not a concern.
At one time I started working on a set of Layout Managers, see my uicomponents git, BoxLayout and uicontainers (which has a FlowLayout container), that would make this easier. the major stumbling block was the lack of min/max component size attributes. Now that we can set arbitrary attributes, it would be possible to implement real layout managers, that let you specify desired size, and min/max sizes of components.
(edit fixed a few typos and slight mod to the code)
import objc_util def get_presented_size(mode,hide_title_bar=False): ''' see https://forum.omz-software.com/topic/1618/any-ios-device/7''' f=objc_util.ObjCClass('UIApplication').sharedApplication().keyWindow().frame() sz= ui.Size(f.size.width,f.size.height) if sz<=320: status_height=0 title_height=32 else: status_height=20 title_height=44 if mode=='sheet': maxsize=min( sz-(0,title_height*(not hide_title_bar))) return ui.Size(maxsize,maxsize) elif mode=='panel': return sz-(0,status_height+(title_height*(not hide_title_bar))) elif mode=='fullscreen': return sz-(0,(title_height*(not hide_title_bar)))
@JonB , nice. I am going to name it preflight 😀😱💋
But thanks, its really nice.
My mind is still doing back flips, as this conversation always confuses the hell out of me. Just when I think I have worked it out, I do something that undoes my thinking...
But, the mode 'sheet' does not work correctly on an iPad Pro.
If you do same as the others,
return sz-(0,status_height+(title_height*(not hide_title_bar))) It works. But I am sure done for a reason the way it is so I am sure something else must be done.
It would be nice if you could also pass your intended frame or (w/h) to the function so you are not getting the maximums but the real size. That will be presented.
Of course this can get effected if you ask for a size not support on a device.
But I don't trust my self, to tinker with code.