Fixed aspect ratio widget layout

378 views
Skip to first unread message

Robert

unread,
Aug 7, 2021, 8:53:42 PM8/7/21
to Kivy users support

I want to create a layout with a central widget that has a fixed aspect ratio, surrounded by other widgets or overlaying a larger background widget. The fixed aspect ratio widget must be landscape when the Window is landscape, and portrait when the Window is portrait.

In the attached example I use nested box layouts, because float layout or anchor layout only lets me set size_hint (not size) of a child and this is relative to the layout so doesn't allow me to set the child aspect ratio.

The attached example behaves as I might expect if the Window is landscape.

Narrowing the window to portrait gives a central widget that is portrait, but does not have a fixed aspect ratio. :(

How to get this to work and/or is there a better design?
lay.py

Elliot Garbus

unread,
Aug 8, 2021, 1:37:46 AM8/8/21
to kivy-...@googlegroups.com
I can code this up tomorrow am. It looks like you are missing a piece of data.  You want to define the aspect ratio AND the scale. You want the scale to be set relative to the larger window axis. 
For example if scale is set to .7, and you are in landscape mode, then the width gets set to .7 * Window.width, and the height is set as a function the aspect ratio.  
The size hints for the widget get set to None. You can put this in a set of boxlayouts to center the widget, or put it in a floatLayout. 

Sent from my iPhone

On Aug 7, 2021, at 5:53 PM, Robert <planckp...@gmail.com> wrote:


--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/21ef19d6-3b52-4958-949e-9404c261b57fn%40googlegroups.com.
<lay.py>

Robert

unread,
Aug 8, 2021, 6:23:17 AM8/8/21
to Kivy users support
Yes, on aspect ratio and scale.

The idea is the scale is in the size hints in the surrounding Labels, and in one direction because scale can't be the same in both directions.
The design is supposedly symmetrical between x and y, but the behavior isn't.

Yes, please have a shot at this. Thanks.

Elliot Garbus

unread,
Aug 8, 2021, 8:28:59 AM8/8/21
to kivy-...@googlegroups.com

Robert,

 

I have laid this out the way I would make a custom widget for myself.  The file can be imported into other files.  The test code is at the bottom, so it runs as a standalone file.

I left in some code that changes the button text (portrait, landscape), you can remove those lines.

Let me know if this is what you are looking for.

 

-Elliot

 

from kivy.app import App
from kivy.lang import Builder
from kivy.properties import DictProperty, NumericProperty, StringProperty
from kivy.uix.relativelayout import RelativeLayout

Builder.load_string(
"""
<AspectButton>:
    Button:
        id: button
        size_hint: None, None
        pos_hint: root.pos_hint
        text: root.text
"""
)


class AspectButton(RelativeLayout):
    aspect_ratio = NumericProperty(
3/4)
    scale = NumericProperty(
.7)             # size relative to parent
   
pos_hint = DictProperty({'center_x': 0.5, 'center_y': 0.5})
    text = StringProperty()

   
def on_kv_post(self, base_widget):
       
self.bind(size=self._resize)

   
def _resize(self, _, size):
        width, height = size
        b =
self.ids.button
        
if width < height:
           
self.text = 'portrait'  # just for testing
           
b.height = height * self.scale
            b.width = b.height *
self.aspect_ratio
           
if b.width > self.scale * width:  # max width, now constrain by width
                
b.width = width * self.scale
                b.height = b.width /
self.aspect_ratio
       
else:
           
self.text = 'landscape'      # just for testing
           
b.width = width * self.scale
            b.height = b.width *
self.aspect_ratio
           
if b.height > height * self.scale:   # max height, constrain by height
               
b.height = height * self.scale
                b.width = b.height /
self.aspect_ratio


if __name__ == '__main__':
   
from textwrap import dedent
    kv_test = dedent(
"""
    BoxLayout:
        AspectButton:
            scale: .9
        BoxLayout:
            orientation: 'vertical'
            Label:
                text: 'Top'
            BoxLayout:
                orientation: 'horizontal'
               Label:
                    text: 'Left'
                AspectButton:
                    scale: .7
                Label:
                    text: 'Right'
            Label:
                text: 'Bottom'
        AspectButton:
            scale: .5
    """
)


   
class AspectApp(App):
       
def build(self):
           
return Builder.load_string(kv_test)

    AspectApp().run()

Robert

unread,
Aug 8, 2021, 1:21:11 PM8/8/21
to Kivy users support
Thanks. I changed the size test in _resize() to         if Window.width < Window.height:
and it does what I want, though I will have to play a little to understand why the whole thing works.
Thanks.

Interesting, if I remove the two outer AspectButton (so it is like my test case) and set scale> 1 in the remaining AspectButton it floats over (part of) the 4 labels.   There might be a simpler example.

Elliot Garbus

unread,
Aug 8, 2021, 1:30:36 PM8/8/21
to kivy-...@googlegroups.com

Scale sets the size relative to the parent, I had not really considered a scale > 1 use case.

 

My first implementation I based on Window.size but thought that was too limiting.  There are other shortcomings when combining this implementation into other layouts.  That is why I moved to the relativelayout.

Here is my first implementation for your reference and amusement.  This might work fine for limited use cases.

 

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.properties import NumericProperty
from kivy.core.window import Window

kv =
"""
<AspectButton>:
    size_hint: None, None

FloatLayout:
    AspectButton:
        pos_hint: {'center_x': 0.5, 'center_y': 0.5}
        scale: .9
"""


class AspectButton(Button):
    aspect_ratio = NumericProperty(
3/4)
    scale = NumericProperty(
.7)             # size relative to window

   
def __init__(self, **kwargs):
       
super().__init__(**kwargs)
        Window.bind(
on_resize=self.window_resize)

   
def on_kv_post(self, base_widget):
       
self.window_resize(Window, Window.width, Window.height)

   
def window_resize(self, window, width, height):
        
if width < height:
           
self.text = 'portrait'  # just for testing
           
self.height = height * self.scale
           
self.width = self.height * self.aspect_ratio
           
if self.width > self.scale * width:  # max width, now constrain by width
               
self.width = width * self.scale
               
self.height = self.width / self.aspect_ratio
       
else:
           
self.text = 'landscape'      # just for testing
           
self.width = width * self.scale
            
self.height = self.width * self.aspect_ratio
           
if self.height > height * self.scale:   # max height, constrain by height
               
self.height = height * self.scale
               
self.width = self.height / self.aspect_ratio


class AspectApp(App):
   
def build(self):
       
return Builder.load_string(kv)

AspectApp().run()

Robert

unread,
Aug 8, 2021, 4:51:24 PM8/8/21
to Kivy users support
Thanks, you opened my eyes (to good stuff).

Elliot Garbus

unread,
Aug 8, 2021, 5:06:32 PM8/8/21
to kivy-...@googlegroups.com

Anything for a co-worker 😊

Enjoy!

Reply all
Reply to author
Forward
0 new messages