Search code examples
pythonkivy

Fitting a label to the size of text in Python with kivy


I'm creating labels based on information that I'm drawing from a database, and I would like to left align it in the grid. For this, I'm trying to wrap the label tightly around the text so I can left align the label in an AnchorLayout. Because the text in my label and the number of these grids with labels are dynamic, I'm doing this through python rather than in a kv file. I've seen a ton of documentation on how to do this in the kivy language, but I can't figure it out in python. Just calling tour_name_label.texture_size (see below) returns [0. 0], I can't seem to access this property and set it to the label size. I'm new to kivy, so I'm not super comfortable with the bind and getters/setters methods. How should I tackle this?

main.py

from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen
from tour_grid import TourGrid
from kivy.core.window import Window
from kivy.core.text import LabelBase
import requests
import json

class MyToursScreen(Screen):
    pass


class Main(MDApp):
    acc_id = 0

    def build(self):
        # self.theme_cls.material_style = "M3"
        # self.theme_cls.theme_style = "Dark"
        Window.size = (360, 600)
        return Builder.load_file("main.kv")

    def on_start(self):
        # Sample tour data
        tour_data = [{'acc_id': 0, 'date_tour_created': '16-02-2024', 'name': 'Bike Tour Norway', 'starting_date': '13-06-2024', 'tour_id': 0}]
        # print(self.root.ids)
        
        # Populate tour grid on home screen
        tours_grid = self.root.ids["my_tours"].ids["tours_grid"]
        for tour in tour_data:
            T = TourGrid(tour_name=tour["name"], starting_date=tour["starting_date"])
            tours_grid.add_widget(T)


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

tour_grid.py (where the problem is)

from kivy.uix.gridlayout import GridLayout
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.graphics import Color, Rectangle
import kivy.utils

class TourGrid(GridLayout):
    def __init__(self, **kwargs):
        self.cols = 1
        super(TourGrid, self).__init__() # Initialize GridLayout with kwargs

        with self.canvas.before:
            Color(rgb=kivy.utils.get_color_from_hex("#dddddd"))
            self.rect = Rectangle(size=self.size, pos=self.pos)

        self.bind(pos=self.update_rect, size=self.update_rect)
        
        # PROBLEM HERE, MADE INTO BUTTON TO CHECK DIMENSIONS
        # Top FloatLayout containing name of tour
        top = AnchorLayout(anchor_x="center", anchor_y="center")
        tour_name_label = Button(text=str(kwargs["tour_name"]), color=kivy.utils.get_color_from_hex("#000000"))

        # tour_name_label.bind(texture_size=tour_name_label.setter("size"))
        # print(tour_name_label.getter("texture_size"))
        # print(tour_name_label.setter("texture_size")())
        top.add_widget(tour_name_label)

        # Bottom FloatLayout containing starting date
        bottom = AnchorLayout(anchor_y="center")
        starting_date_label = Label(text="[color=000000]" + kwargs["starting_date"] + "[/color]", size_hint=[1, 0.3], pos_hint={"top": 1, "left": 1}, markup=True)
        bottom.add_widget(starting_date_label)

        self.add_widget(top)
        self.add_widget(bottom)

    def update_rect(self, *_):
        self.rect.pos = self.pos
        self.rect.size = self.size

main.kv

#:include kv/my_tours.kv

BoxLayout:
    ScreenManager:
        id: screen_manager
        
        MyToursScreen:
            id: my_tours
            name: "my_tours"

my_tours.kv

#:import utils kivy.utils

<MyToursScreen@Screen>:
    FloatLayout:
        # Top bar, profile image and name, notification and settings button
        GridLayout:
            rows: 1
            pos_hint: {"top": 1, "left": 1}
            size_hint: 1, 0.1
            
            canvas:
                Color: 
                    rgb: utils.get_color_from_hex("#25be55")
                Rectangle:
                    size: self.size
                    pos: self.pos
                    
        # Main grid with tours
        ScrollView:
            pos_hint: {"top": 0.9, "left": 1}
            size_hint: 1, 0.8

            GridLayout:
                id: tours_grid
                cols: 1
                size_hint_y: None
                height: self.minimum_height
                row_default_height: "70dp"
                row_force_default: True
                
                Label:
                    text: "Planned trips"
                    color: utils.get_color_from_hex("#000000")

        # Bottom bar, My Tours button
        GridLayout:
            rows: 1
            pos_hint: {"top": 0.1, "left": 1}
            size_hint: 1, 0.1

            canvas:
                Color: 
                    rgb: utils.get_color_from_hex("#25be55")
                Rectangle:
                    size: self.size
                    pos: self.pos

            Label:
                text: "My tours"
                bold: True           

Solution

  • Okay, I found a little (unintuitive) gem in the label docs. I needed to run tour_name_label.texture_update() before setting tour_name_label.size = tour_name_label.texture_size. Now it works! Thanks @John Andeson for the size_hint clue, I also needed that.

    https://kivy.org/doc/stable/api-kivy.uix.label.html#kivy.uix.label.Label.anchors