Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement long list of scrollable and selectable widgets #5163

Closed
willmcgugan opened this issue Oct 23, 2024 Discussed in #5160 · 2 comments · Fixed by #5164
Closed

Implement long list of scrollable and selectable widgets #5163

willmcgugan opened this issue Oct 23, 2024 Discussed in #5160 · 2 comments · Fixed by #5164

Comments

@willmcgugan
Copy link
Collaborator

Discussed in #5160

Originally posted by anlar October 23, 2024
Hello,

I'm working on application that has vertical list of items which should be scrollable by cursor. Each item is a widget itself and contains about 10 sub-widgets. Here is a screenshot for better understanding: https://github.com/anlar/tewi/blob/master/docs/images/tewi-screenshot-1.png .

Problem is that with large number of items (500+) list become unresponsive. Container scroll itself works fine, but if I want to use cursor (selecting item, changing its style and manually scroll to widget location) it is slow.

I have tried both ScrollableContainer and ListView (former works worse). All obvious (for me) optimization are made:

  1. Widgets mounted/installed together.
  2. There are no widgets re-composition during scroll.

Here is example app using scroll and list showing what app is doing:

from textual.app import App, ComposeResult                                                                                                                                   
from textual.binding import Binding                                                                                                                                          
from textual.containers import Grid, ScrollableContainer, Horizontal, Container                                                                                              
from textual.widgets import Static, Label, ProgressBar, DataTable, ContentSwitcher, TabbedContent, TabPane, TextArea, \                                                      
        ListView, ListItem, Header, Footer                                                                                                                                   
                                                                                                                                                                             
class LabelItem(Label):                                                                                                                                                      
    w_next = None                                                                                                                                                            
    w_prev = None                                                                                                                                                            
                                                                                                                                                                             
class ScrollablePanel(ScrollableContainer):                                                                                                                                  
                                                                                                                                                                             
    BINDINGS = [                                                                                                                                                             
            Binding("k", "move_up", "Move up"),                                                                                                                              
            Binding("j", "move_down", "Move down"),                                                                                                                          
            ]                                                                                                                                                                
                                                                                                                                                                             
    selected_item = None                                                                                                                                                     
                                                                                                                                                                             
    def on_mount(self):                                                                                                                                                      
        widgets = []                                                                                                                                                         
                                                                                                                                                                             
        w_prev = None                                                                                                                                                        
        for x in range(1000):                                                                                                                                                
            item = LabelItem("test-scroll-" + str(x))                                                                                                                        
                                                                                                                                                                             
            if w_prev:                                                                                                                                                       
                w_prev.w_next = item                                                                                                                                         
                item.w_prev = w_prev                                                                                                                                         
                w_prev = item                                                                                                                                                
            else:                                                                                                                                                            
                w_prev = item                                                                                                                                                
                                                                                                                                                                             
            widgets.append(item)                                                                                                                                             
                                                                                                                                                                             
        self.mount_all(widgets)

    def action_move_up(self) -> None:                                                                                                                                        
        items = self.children                                                                                                                                                

        if items:
            if self.selected_item is None:
                item = items[-1]
                item.selected = True
                self.selected_item = item
                self.scroll_to_widget(self.selected_item)
            else:
                if self.selected_item.w_prev:
                    self.selected_item.selected = False
                    self.selected_item = self.selected_item.w_prev
                    self.selected_item.selected = True
                    self.scroll_to_widget(self.selected_item)

    def action_move_down(self) -> None:
        items = self.children

        if items:
            if self.selected_item is None:
                item = items[0]
                item.selected = True
                self.selected_item = item
            else:
                if self.selected_item.w_next:
                    self.selected_item.selected = False
                    self.selected_item = self.selected_item.w_next
                    self.selected_item.selected = True
                    self.scroll_to_widget(self.selected_item)

class ListPanel(ListView):
    def on_mount(self):
        widgets = []

        for x in range(1000):
            item = ListItem(Label("test-list-" + str(x)))
            widgets.append(item)

        self.extend(widgets)

class MainApp(App):

    BINDINGS = [
        ("s", "switch_to('scroll')", "Switch to Scroll"),
        ("l", "switch_to('list')", "Switch to List"),
    ]

    def compose(self) -> ComposeResult:
        yield Header()
        with ContentSwitcher(initial="scroll"):
            yield ScrollablePanel(id="scroll")
            yield ListPanel(id="list")
        yield Footer()

    def action_switch_to(self, to_id):
        self.query_one(ContentSwitcher).current = to_id

MainApp().run()

So question is whether it is expected limitation for such large panels and I may not look further (and implement pagination for example) or I'm doing something wrong?

@willmcgugan
Copy link
Collaborator Author

There are some performance issues there. We have a fix in the pipeline.

Copy link

Don't forget to star the repository!

Follow @textualizeio for Textual updates.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant