Justapigeon
Justapigeon

Reputation: 590

Kivy Popup Dynamic Height

I'm looking to make a popup on the python-side that has a dynamic height.

So far, I have this within the screens __init__ class. The kv file has another widget that called the popup on_release. Anyways, I have found that this produces a popup with very wonky formatting:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import Screen, ScreenManager

kv = '''

ScreenManagement:
    id: 'manager'
    BrokenPopup:
        name: 'broken'
        manager: 'manager'

<BrokenPopup>:
    BoxLayout:
        Button:
            text: 'Test'
            on_release: root.p.open()

'''

class ScreenManagement(ScreenManager):
    pass

class BrokenPopup(Screen):

    def __init__(self, **kwargs):
        super(BrokenPopup,self).__init__(**kwargs)

        self.p = Popup(auto_dismiss=False, size_hint_x=.6, size_hint_y=None, title='A popup')
        self.g = GridLayout(cols=1, spacing=10)
        self.g.add_widget(Button(text='Test1', size_hint_y=None, height=32))
        self.g.add_widget(Button(text='Test2', size_hint_y=None, height=32))
        self.g.bind(minimum_height=self.g.setter('height'))
        self.p.add_widget(self.g)
        self.p.bind(height=self.g.setter('height')) #<- this does not work to change the popup height!

class TheApp(App):

    def build(self):
        return Builder.load_string(kv)

TheApp().run()

The popup size is set to fit only one widget, leaving the second button (and all others that may be included) to float beyond the confines of the popup border.

How should I change the code so that all of the widgets fit within the confines of the popup? I am trying to do that by dynamically setting the height of the popup, however that is not proving effective.

Upvotes: 1

Views: 1481

Answers (2)

Justapigeon
Justapigeon

Reputation: 590

I have found a solution for my original problem that is influenced by John Anderson's answer. I'll provide a walkthrough below for how I came to this solution.

1) Here's a photo of my original problem; I needed to dynamically set the popup height based on the widgets that are assigned to it. Before finding the below solution, my popup looked like this with the code in the OP:

Original popup with a height error.

As you can see, the widgets go beyond the borders of the popup.

2) I found something interesting while looking inside the popup widget with the inspector tool.

python '/path/to/your/file.py' -m inspector

Using control-e, I can click widgets and inspect their attributes. I clicked the popup button and cycled through the parent widgets until I found the popup widget.

enter image description here

As you can see in the photo, the popup widget has one child: a grid layout. Here are the children of that grid layout:

enter image description here

Interestingly, the grid layout contains:

  • One label, with a height of 33

  • One line, with a height of 4

  • A box layout, which contains the contents of the popup

  • 2 units of spacing between these three widgets

  • 12 units of padding all-around; so 24 additional units to consider for the height

3) In my solution, I have hard-written the default heights of the label, the line widget, and all default popup spacing and padding. Then, I cycle through the children inside the box layout, and add their heights. I also add 10 to those children heights, as the gridlayout that contains all of these widgets uses a spacing of 10.


Solution:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import Screen, ScreenManager

kv = '''

ScreenManagement:
    id: 'manager'
    BrokenPopup:
        name: 'broken'
        manager: 'manager'

<BrokenPopup>:
    BoxLayout:
        Button:
            text: 'Test'
            on_release: root.p.open()

'''

class ScreenManagement(ScreenManager):
    pass

class BrokenPopup(Screen):

    def __init__(self, **kwargs):
        super(BrokenPopup,self).__init__(**kwargs)

        self.p = Popup(auto_dismiss=False, size_hint_x=.6, size_hint_y=None, title='A popup')
        self.g = GridLayout(cols=1, spacing=10, padding=[0,10,0,-5])
        self.g.bind(minimum_height=self.fix_popup_height) # <- here's the magic
        self.g.add_widget(Button(text='Test1', size_hint_y=None, height=32))
        self.g.add_widget(Button(text='Test2', size_hint_y=None, height=32))
        self.p.add_widget(self.g)


    def fix_popup_height(self, grid, *args):
        # a generalized function that, when bound to minimum_height for a grid with popup widgets,
        # this will set the height of the popup
        height = 0
        height += 33    # for popup label
        height += 4     # for popup line widget
        height += 24    # for popup padding
        height += 2     # for spacing between main popup widgets
        for child in grid.children:
            height += child.height + 10 # adds 10 for the spacing between each child
        grid.parent.parent.parent.height = height # sets popup height
        pass


class TheApp(App):

    def build(self):
        return Builder.load_string(kv)

TheApp().run()

Notable changes from the OP:

  • Bind the minimum_height of the widget container to the fix_popup_height() function; this will trigger each time a widget is added to the popup.

  • Declare the fix_popup_height() within the screen class.

Here's the fixed popup:

enter image description here

Upvotes: 0

John Anderson
John Anderson

Reputation: 38962

I have modified your code to do what I think you want. Basically it adds the minimum_height from the GridLayout, that is added to your Popup, to the calculated height of the title and the dividing bar. The first Button in the GridLayout now adds another Button to the GridLayout for testing.

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
from kivy.uix.screenmanager import Screen, ScreenManager

kv = '''

ScreenManagement:
    id: 'manager'
    BrokenPopup:
        name: 'broken'
        manager: 'manager'

<BrokenPopup>:
    BoxLayout:
        Button:
            text: 'Test'
            on_release: root.p.open()

'''


class ScreenManagement(ScreenManager):
    pass


class BrokenPopup(Screen):

    def __init__(self, **kwargs):
        super(BrokenPopup,self).__init__(**kwargs)

        self.popup_title_height = None
        self.p = Popup(auto_dismiss=False, size_hint_x=.6, size_hint_y=None, title='A popup')
        self.g = GridLayout(cols=1, spacing=10)
        self.g.bind(minimum_height=self.fix_size)
        self.g.add_widget(Button(text='Test1', size_hint_y=None, height=32, on_release=self.add_one))
        self.g.add_widget(Button(text='Test2', size_hint_y=None, height=32))
        self.p.add_widget(self.g)


    def add_one(self, *args):
        self.g.add_widget(Button(text='Another', size_hint_y=None, height=32))


    def get_popup_title_height(self):
        height = 0
        popupGrid = self.p.children[0]
        height += popupGrid.padding[1] + popupGrid.padding[3]
        for child in popupGrid.children:
            if isinstance(child, BoxLayout):
                continue
            else:
                height += child.height + popupGrid.spacing[1]
        self.popup_title_height = height


    def fix_size(self, *args):
        if self.popup_title_height is None:
            self.get_popup_title_height()
        self.p.height = self.g.minimum_height + self.popup_title_height


class TheApp(App):

    def build(self):
        return Builder.load_string(kv)

TheApp().run()

I cheated a bit by looking at the code for Popup and the style.kv file to see how the Popup is displayed. So, if any of that is changed, this may not work.

Upvotes: 1

Related Questions