Search code examples
pythonbuttonkivy

Problem with positioning buttons properly in Kivy


I am a beginner in kivy, I have several dynamically created buttons, which disappear when they are clicked. These buttons are sentences, that I get from a list. When a button is clicked, I want it to disappear, and create buttons for each individual word in that sentence. Something like this:

| Hello World, this is an example sentence. |

Click.

| Hello || World || this || is || an || example || sentence |

I already did the part where the sentence is splitted and a new button with the size of the word is created, but I have problems with positioning, the 2nd button in that sentence should be as far away as the size of the 1th word in that sentence, so that they don't overlap. Because it needs to be done dynamically, I need to do this in the .py file. Should I be using Gridlayouts or something else? How can I get the size of the last button? Any help is appreciated :)

new.py:

import kivy.uix.button as kb
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty, NumericProperty
from kivy.uix.gridlayout import GridLayout

sentences = ['This is example sentence 1.', 'This is example sentence 2.', 'This is example sentence 3.', 'This is example sentence 4.', 'This is example sentence 5.', 'This is example sentence 6.']
words = [ ['This', 'is' , 'example' ,  'sentence' , '1' ], ['This', 'is' , 'example' ,  'sentence' , '2' ], ['This', 'is' , 'example' ,  'sentence' , '3' ], ['This', 'is' , 'example' ,  'sentence' , '4' ], ['This', 'is' , 'example' ,  'sentence' , '5' ], ['This', 'is' , 'example' ,  'sentence' , '6' ] ]

class Button_Widget(Widget):
    def __init__(self, **kwargs):
        super(Button_Widget, self).__init__(**kwargs)
        global btns
        btns = []
        for i in range(len(sentences)):
         print(self.height)
         btns.append((kb.Button(text=sentences[i], pos=(self.width * i, self.height * i))))
         btns[i].size = 50, 50
         btns[i].bind(on_press= lambda x, j=i: destroy(j))
         self.add_widget(btns[i])


         def destroy(u):
             self.remove_widget(btns[u])
             create(self, u)

         def create(self, m):
             width = self.width / 16
             height = self.height / 12
             print(width)
             print(height)


             sentence = words[m]
             btns_split = []

             for e in range(len(sentence)):
                xpos = (width * m) + (50 * e)
                ypos = (height * m)
                btns_split.append((kb.Button(text= sentence[e], pos= (xpos, ypos ) )))
                btns_split[e].bind( texture_size = btns_split[e].setter("size"))
                self.add_widget(btns_split[e])
             #print(btns_split)

class ButtonApp(App):

    def build(self):
        return Button_Widget()


if __name__ == "__main__":
    ButtonApp().run()

If should be using Gridlayouts, than can please someone tell me how to do that only in the .py file?


Solution

  • If in case you have sentences (or words) of variable length you can use BoxLayout to arrange all the sentences as well as words (with Button) in different fashion. Below is the modified (and complete, runnable) form of your posted code,

    from kivy.uix.button import Button
    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout
    
    sentences = ['This is example sentence 1.', 'This is example sentence 2.', 'This is example sentence 3.', 'This is example sentence 4.', 'This is example sentence 5.', 'This is example sentence 6.']
    words = [ ['This', 'is' , 'example' ,  'sentence' , '1' ], ['This', 'is' , 'example' ,  'sentence' , '2' ], ['This', 'is' , 'example' ,  'sentence' , '3' ], ['This', 'is' , 'example' ,  'sentence' , '4' ], ['This', 'is' , 'example' ,  'sentence' , '5' ], ['This', 'is' , 'example' ,  'sentence' , '6' ] ]
    
    
    class AdaptiveButton(Button):
        """This button takes as much space as it needs to contain its text."""
    
        def __init__(self, **kwargs):
            super(AdaptiveButton, self).__init__(**kwargs)
            self.size_hint = (None, None)
            self.bind(texture_size = self.setter("size"))
    
    
    class MyLayout(BoxLayout):
        """This will arrange all the necessary widgets."""
    
        def __init__(self, **kwargs):
            super(MyLayout, self).__init__(**kwargs)
            self.orientation = "vertical"
            self.spacing = "10dp"
    
            for i, line in enumerate(sentences):
                abtn = AdaptiveButton(text = line)
                abtn.bind(on_press = lambda btn, j = i : self.destroy_then_create(btn, j)) # Pass the button and corresponding index within sentences for later use.
                self.add_widget(abtn)
    
    
        def destroy_then_create(self, btn, index):
            self.remove_widget(btn)
    
            # Create a container to contain all those splitted lines as button.
            box = BoxLayout(size_hint = (None, None), spacing = "2dp")
            # Just make its size enough to contain only those buttons.
            box.bind(minimum_size = box.setter("size"))
            sentence = words[index]
            i = len(sentences)-index-1 # This will re-add the box exactly where the previous widget was deleted from.
            for word in sentence:
                abtn = AdaptiveButton(text = word)
                box.add_widget(abtn)
            # Now add the whole box at the specified index.
            self.add_widget(box, i)
    
    
    class ButtonApp(App):
    
        def build(self):
            return MyLayout()
    
    
    if __name__ == "__main__":
        ButtonApp().run()