Search code examples
pythonpython-3.xkivydesktop-application

The canvas border doesn't update the position in ScrollView Kivy


I want to create a todo app using Kivy. When I add the task using Label it appear with border, this border is created using canvas. The problem is that the border follow the last element created so just one task appear with border others not. This problem appear only when I include the ScrollView.

**The problem in figure below: ** enter image description here

The code:

class TasksBox(GridLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.size_hint = (1, None)
        self.cols = 1

        self.bind(minimum_height=self.setter("height"))

class ScrollBox(ScrollView):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.size_hint = (1, None)
        self.size = (Window.width, Window.height)

class MainWidget(GridLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.size_hint = (1, None)
        self.cols = 1
        self.spacing = dp(10)
        self.padding = dp(10)
        self.size_hint = (1, 1)
        self.bind(size=lambda instance, value: self.update_size(instance, value, (1, 1, 1)))
        self.btn_height = dp(40)
        self.text_value = ""

        add_button = AddButton(self.btn_height).create()
        self.task_field = TaskField(self.btn_height).create()
        add_button.bind(on_press=self.create_task)
        self.task_field.bind(on_text_validate=self.create_task)

        self.task_field.bind(text=self.get_value)

        # text input + submit button
        box = BoxLayout(size_hint=(1, None), height=self.btn_height, orientation="horizontal")

        box.spacing = dp(20)
        box.add_widget(self.task_field)
        box.add_widget(add_button)
        self.add_widget(box)
        self.task_field.focus = True


        self.tasks_box = TasksBox()
        scroll_box = ScrollBox()
        scroll_box.add_widget(self.tasks_box)
        self.add_widget(scroll_box)

    def get_value(self, instance, value):
        self.text_value = value
    def create_task(self, instance):
        todo = Todo()
        if len(self.text_value) > 2:
            todo.create_task(self.text_value, d.datetime.now().strftime("%Y-%m-%d %H:%M"), "Todo")
            self.add_task(todo.get_tasks())
            self.task_field.text = ""
        else:
            print("Please write a task! Contains more than 4 characters!")
        self.task_field.focus = True
    def add_task(self, tasks):
        task = tasks[len(tasks)-1]
        task_name = task["name"]
        task_id = task["id"]
        task_status = task["status"]
        task_date = task["date"]

        task_box = BoxLayout(size_hint=(1, None), height=self.btn_height)
        task_box.spacing = dp(10)
        task_name_box = Label(text=task_name, valign="middle", halign="left", size_hint=(.6, None), height=self.btn_height, color=(0, 0, 0), padding=(dp(10)))
        task_name_box.bind(size=task_name_box.setter("text_size"))
        task_name_box.bind(size=lambda instance, value: self.update_border_size(instance, value, (0, 0, 0)))

        task_id_box = Label(text=f"{task_id}", size_hint=(.1, None), height=self.btn_height, color=(1, 1, 1))
        task_id_box.bind(size=lambda instance, value: self.update_size(instance, value, (0, .6, 1)))

        task_status_box = Label(text=task_status, size_hint=(.1, None), height=self.btn_height, color=(0, 0, 0))
        task_status_box.bind(size=lambda instance, value:self.update_size(instance, value, (.5, 1, .5)))

        task_date_box = Label(text=f"{task_date}", size_hint=(.2, None), height=self.btn_height, color=(163/255, 163/255, 163/255))
        task_date_box.bind(size=lambda instance, value:self.update_border_size(instance, value, (0, 0, 0, .7)))

        task_box.add_widget(task_id_box)
        task_box.add_widget(task_status_box)
        task_box.add_widget(task_name_box)
        task_box.add_widget(task_date_box)

        self.tasks_box.add_widget(task_box)



    def update_size(self, instance, value, color):
        instance.canvas.before.clear()
        with instance.canvas.before:
            instance.canvas.before.clear()
            Color(*color)
            Rectangle(size=value, pos=instance.pos)
    def update_border_size(self, instance, value, color):
        instance.canvas.before.clear()
        with instance.canvas.before:
            instance.canvas.before.clear()
            Color(*color)
            Line(rectangle=(instance.pos[0], instance.pos[1], value[0], value[1]))

I want each task appear with it border so the canvas update the pos foreach task.


Solution

  • You have bound your canvas updates to the size of several of the widgets, but most of them are not changing size except when you resize the entire app. However, the TaskBox changes size every time a new task is added, so bind an update method to the size of the TaskBox, and use that update method to cycle through all the tasks and update each one.