George Bou
George Bou

Reputation: 588

Wrapping the text of a Kivy Label

So I am writting a program using the Kivy GUI and I really don't want to use a .kv file but write it all in the python file. The problem is that inside the MainScreen Class i want to add some Labels but i cannot make their text to wrap according to window size changes. When i try self.size I only get 100x100. I have tried all the suggestions from the Kivy tutorial book but nothing worked. I know i could just make the text multiline using \n or set a standard Label size, but that wouldn't be a real solution. I need the Label size and text to follow the whole window changes and the text to be wrapped.

I have simplified the program to focus just on this issue.

Here's the code:

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.graphics import Rectangle, Color


class MainScreen(FloatLayout):
    """MAIN WINDOW CLASS"""

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

        with self.canvas.before:
            Color(0.988, 0.725, 0.074, 1, mode='rgba')
            self.rect = Rectangle(pos=self.pos, size=self.size)
        self.bind(size=self.update_rect)


        #TITLE LABEL
        self.add_widget(Label(text="A very Long Sentence that needs to be wrapped.",
                              bold = True,
                              font_size="20sp",
                              pos_hint={'center_x': 0.5, 'center_y': .85},
                              size_hint=(None, None),
                              halign="center",
                              color=(0.055, 0.235, 0.541, 1)))

    def update_rect(self, *args):
        """FUNCTION TO UPDATE THE RECATANGLE OF CANVAS TO FIT THE WHOLE SCREEN OF MAINSCREEN ALWAYS"""
        self.rect.pos = self.pos
        self.rect.size = self.size

class main(App):
    """BUILDING THE APP"""
    def build(self):
        return MainScreen()

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

Thank you.

Upvotes: 7

Views: 15036

Answers (3)

Ferd
Ferd

Reputation: 1331

I would make use of the wonderful OOP.

You can just create class like this:

class WrappedLabel(Label):
    # Based on Tshirtman's answer
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.bind(
            width=lambda *x:
            self.setter('text_size')(self, (self.width, None)),
            texture_size=lambda *x: self.setter('height')(self, self.texture_size[1]))

Then use your class in any part of your code you want "the wrapping behavior". Example:

my_label = WrappedLabel(text="A very L" + "o"*200 + "ng Sentence that needs to be wrapped.",
                        bold=True,
                        font_size="20sp")

Upvotes: 11

George Bou
George Bou

Reputation: 588

So after giving my problem a lot of thought and experimenting with different approaches i ended up with a proper "just python" solution.

First of all my answer is based on 1) the official Kivy Tutorials suggestions, 2) the usual manipulation of canvas approach and 3) careful handling on variable scope.

So the Label settings are similar to the ones suggested by the Kivy tutorial book. There needs to be a function that updates the Label position and it's text size (the setting_function here) to bind with the Label size. Also I assigned the Label to a variable so I could easily refer to it later.

After all those changes my code looks like this:

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.label import Label
from kivy.graphics import Rectangle, Color


class MainScreen(FloatLayout, Label):
    """MAIN WINDOW CLASS"""

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

        with self.canvas.before:
            Color(0.988, 0.725, 0.074, 1, mode='rgba')
            self.rect = Rectangle(pos=self.pos, size=self.size)
        self.bind(size=self.update_rect)

        #TITLE LABEL
        self.titlos = Label(text="A very Long Sentence that needs to be wrapped.",
                              bold = True,
                              text_size=(None,None),
                              font_size="20sp",
                              pos_hint={'center_x': 0.5, 'center_y': .85},
                              size_hint_y=None,
                              size = self.size,
                              height=self.texture_size[1],
                              halign="center",
                              valign = "middle",
                              color=(0.055, 0.235, 0.541, 1))

        self.add_widget(self.titlos)
        self.titlos.bind(size=self.setting_function)

    def setting_function(self, *args):
        """FUNCTION TO UPDATE THE LABEL TO ADJUST ITSELF ACCORDING TO SCREEN SIZE CHANGES"""
        self.titlos.pos_hint = {'center_x': 0.5, 'center_y': .85}
        self.titlos.text_size=self.size

    def update_rect(self, *args):
        """FUNCTION TO UPDATE THE RECATANGLE OF CANVAS TO FIT THE WHOLE SCREEN OF MAINSCREEN ALWAYS"""
        self.rect.pos = self.pos
        self.rect.size = self.size


class main(App):
    """BUILDING THE APP"""
    def build(self):
        return MainScreen()

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

The above code does meet the requirements I set in my question. It is easy to use it to your own projects. Just take care of the variables' scopes and use lots of print messages for checking.

Finally the reason I wanted to use just Python to solve this problem (and not just use kivy language which handles those issues like a piece of cake) is that I want to have variables that are dynamically changed during the run time, that are used as the Label parameters. And also I just had to find out because I am stubborn.

Thank you.

Upvotes: 2

Tshirtman
Tshirtman

Reputation: 5949

First, let me tell you that you are going to make your life way harder than it should, kv is not hard to learn, and it allows kivy to do a lot more of the heavy lifting for you.

in kv, doing something like

Label:
    text: 'blah blah ' * 1000
    text_size: self.width, None
    size_hint: 1, None
    height: self.texture_size[1]

would usually do the trick, since the widget is using the whole width of its container, and the text_size being bound to its width, the texture would be recomputed everytime this width changes, and the height of the widget would automatically fit to the height of the texture.

Now, to do the same without kv.

  label = Label(text='blah blah '* 1000, size_hint=(1, None))
  label.bind(
      width=lambda *x: label.setter('text_size')(label, (label.width, None),
      texture_size=lambda *x: label.setter('height')(label, label.texture_size[1]))

that is, you need to explicitly tell kivy that it has to react to changes in the various properties of the label, and recompute the values of other properties, while with kv, it automatically detects that some values depend on others, and will know it needs to bind on them on its own.

setter is a utility function which allow you to avoid defining setter functions in simple cases, if not for it, here, you would have to define a function for each of the binds, and do something like "self.text_size = self.width, None" in them, because lambda doesn't allow assignation.

One of the reasons kv exists, is that it allows to process ourselves the expressions in your code, and detect the dependencies, if you do it all in python, there is no way kivy can detect such dependencies, because python itself doesn't care about them, and doesn't tell kivy about them.

Now if you want to tell us why you want to avoid kv, please do, but i'm pretty sure these reasons come from a lack of knowing kv rather than a real problem with it.

Upvotes: 19

Related Questions