flet-dev / flet

Flet enables developers to easily build realtime web, mobile and desktop apps in Python. No frontend experience required.
https://flet.dev
Apache License 2.0
11.18k stars 430 forks source link

Row scrolling changes position of Column #4047

Open ranonbezerra opened 3 weeks ago

ranonbezerra commented 3 weeks ago

Duplicate Check

Describe the bug

I've been trying to build a control similar to an Instagram Carousel with Flet. It is a Stack of a Row of Images and Row of dots (Icon of a small white circle), each dot represents the position in the Image's row. When the user scroll through the row, the image and de dots opacity changes. The object would also have a "auto scroll" function where after a period of time the image would change automatically after a period of time.

All of this works fine, but the problem is when I put that on a Column. Every time that you scroll the Carousel, the Column changes its position in a way that the Carousel is shown on top. I've tried putting auto_scroll in the page, the view and the column to false, and none worked

Code sample

Code Code for the Carousel Object ```python from time import sleep import numpy as np from flet import ( CircleAvatar, Container, Image, ImageFit, MainAxisAlignment, OnScrollEvent, Page, Row, ScrollMode, Stack, StackFit, alignment, colors, margin, ) class RowPosition: """ Class to store the position of the carousel row and the counter to activate the auto scroll method Attributes: ----------- position : int The position of the carousel row loop_run : bool The flag to activate the auto scroll counter : int The counter to activate the auto scroll """ def __init__(self): """ Initialize the position, loop_run and counter """ self.position = 0 self.loop_run = True self.counter = 0 self.direction = None self.last_event_type = None class Carousel: """ Class to create a carousel with images and dots Attributes: ----------- _page : flet.Page The page to add the carousel position : RowPosition The position of the carousel row and the counter to activate the auto scroll auto_scroll : bool The flag to activate the auto scroll images_row : flet.Row The row with the images dots_row : flet.Row The row with the dots Methods: -------- __init__(page: flet.Page) Initialize the page and the position carousel(rest_images: list) -> flet.Stack Create a carousel with images activate_auto_scroll() Activate the auto scroll """ def __init__(self, page: Page): self._page = page self.position = RowPosition() def carousel(self, images_src: list): """ Create a carousel with images. Parameters: ----------- rest_images : list The list of images Returns: -------- Stack The stack with the carousel """ def _scroll_to_key(page: Page, position: RowPosition, e: OnScrollEvent): """ Scroll to the key of the image row and update the dots row. Parameters: ----------- page : Page The page with the carousel position : RowPosition The RowPosition object with the carousel row and the counter to activate the auto scroll e : OnScrollEvent The scroll event """ image_row = page.views[-1].controls[0].controls[0].controls[0] dots_row = page.views[-1].controls[0].controls[0].controls[1] size = len(image_row.controls) pixels_list = [0] for i in range(size - 1): pixels_list.append((i + 1) * page.window.width) if e.event_type == 'user' and position.last_event_type != 'end': position.direction = e.direction if position.direction == 'reverse': start_pixel = e.pixels index = np.argmin(np.abs(np.array(pixels_list) - start_pixel)) + 1 if index == size: old_index = size - 1 index = 0 else: old_index = index - 1 elif position.direction == 'forward': start_pixel = e.pixels index = np.argmin(np.abs(np.array(pixels_list) - start_pixel)) - 1 if index == -1: index = size - 1 old_index = 0 else: old_index = index + 1 image_row.scroll_to(key=str(index), duration=300) position.position = index position.counter = 0 dots_row.controls[old_index].opacity = 0.5 dots_row.controls[index].opacity = 1 dots_row.update() position.last_event_type = e.event_type image_list = [ Container( content=Image( src=rest_image, fit=ImageFit.FILL, ), width=self._page.window.width, height=self._page.window.height * 0.3, key=str(i), ) for i, rest_image in enumerate(images_src) ] self.images_row = Row( spacing=0, width=self._page.window.width, height=self._page.window.height * 0.3, auto_scroll=False, scroll=ScrollMode.HIDDEN, controls=image_list, on_scroll=lambda e: _scroll_to_key(self._page, self.position, e), ) self.dots_row = Row( controls=[ Container( alignment=alignment.bottom_center, margin=margin.only(bottom=25), content=CircleAvatar(bgcolor=colors.WHITE, radius=3), opacity=1 if i == 0 else 0.5, ) for i in range(len(image_list)) ], spacing=5, auto_scroll=False, alignment=MainAxisAlignment.CENTER, width=self._page.window.width, height=self._page.window.height * 0.3, ) return_stack = Stack( controls=[ self.images_row, self.dots_row, ], fit=StackFit.PASS_THROUGH, ) return return_stack def activate_carousel_auto_scroll(self): """ Activate the auto scroll as a thread. """ image_row = self._page.views[-1].controls[0].controls[0].controls[0] dots_row = self._page.views[-1].controls[0].controls[0].controls[1] while self.position.loop_run: while self.position.counter < 4: sleep(1) self.position.counter += 1 self.auto_scroll = True key = self.position.position dots_row.controls[self.position.position].opacity = 0.5 key += 1 if key == len(image_row.controls): key = 0 image_row.scroll_to(key=str(key), duration=300) self.position.position = key self.position.counter = 0 dots_row.controls[self.position.position].opacity = 1 dots_row.update() ``` Code for the client.py. OBS: I've added a few more images in the column just as and example of more information in the Column object. ```python import flet as ft from flet import Column, Container, Image, ImageFit, Page, ScrollMode, View from carousel import Carousel def get_view(page: Page): img_list = [f'https://picsum.photos/200/100?{i}' for i in range(5)] carousel = Carousel(page) carousel_imgs = carousel.carousel(img_list) column = Column( controls=[carousel_imgs], height=page.window.height - 30 * 2, scroll=ScrollMode.HIDDEN, spacing=30, auto_scroll=False, ) for i in range(5): column.controls.append( Container( content=Image( src=f'https://picsum.photos/200/100?{i+5}', fit=ImageFit.FILL, ), width=page.window.width, height=page.window.height * 0.3, ) ) page.auto_scroll = False return_view = View( route='/testpg', controls=[column], auto_scroll=False, ) page.run_thread(carousel.activate_carousel_auto_scroll) return return_view def main(page: Page): test_view = get_view(page) page.auto_scroll = False page.views.append(test_view) page.go(route='/testpg') if __name__ == '__main__': ft.app(target=main, port=8550, assets_dir='assets') ```

To reproduce

Run the client.py and scroll down the page.

Expected behavior

It was expected to the row change the position, but not the Column.

Screenshots / Videos

Captures https://github.com/user-attachments/assets/91790f28-2e93-4322-90cd-3cb34d3e5d8d

Operating System

macOS

Operating system details

Sequoia 15.0

Flet version

0.24.1

Regression

No, it isn't

Suggestions

No response

Logs

Logs ```console [Paste your logs here] ```

Additional details

Apparently, setting the auto_scroll attribute to False is not working. From my tests, when scrolling the line, the scroll of the View is activated.

OwenMcDonnell commented 5 days ago

As your code is you are using scroll_to method with a control key, which makes the whole page scroll to the control targeted. I'm not sure exactly what values you would use but try reworking your example to use offset or delta arguments to achieve what you want, and let us know if you can solve it that way.

ranonbezerra commented 4 days ago

As your code is you are using scroll_to method with a control key, which makes the whole page scroll to the control targeted. I'm not sure exactly what values you would use but try reworking your example to use offset or delta arguments to achieve what you want, and let us know if you can solve it that way.

Ok! I will implement it using offset as soon as I can.