bczsalba / pytermgui

Python TUI framework with mouse support, modular widget system, customizable and rapid terminal markup language and more!
https://ptg.bczsalba.com
MIT License
2.21k stars 54 forks source link

[QUESTION] Truncate on width #49

Closed jonnieey closed 2 years ago

jonnieey commented 2 years ago

I have a list of lists of strings which contains data and I want them to be visible on a scrollable container as buttons. The only problem is that they have long data which wraps to the next line. Is there a way to truncate this view to the layout/slot width so that each button can be on one line?

bczsalba commented 2 years ago

I think the "cleanest" way to achieve this would be by setting static_width for each Button. Sadly, as far as I can remember this won't actually do anything. Please let me know if I'm wrong though, I might have implemented it at some point.

If that doesn't work, what I personally would do is do some preprocessing on the lines of data you have. You can, for example, slice them to your target width - 3, and add ... to the end to indicate "more". Something like the below should work:

max_width = ...
for line in data:
    if len(line) <= max_width:
        continue
    line = line[:max_width - 3] + "..."

Technically, you can also do this slicing within a Button subclass' get_lines:

def get_lines(self) -> list[str]:
    full = self.label

    if len(full) > MAX_WIDTH:
        self.label = self.label[:MAX_WIDTH - 3] + '...'

    lines = super().get_lines()
    self.label = original

    return lines

I am writing this on my phone, so apologies if formatting of the code is weird/broken. The idea should be there.

Thanks for using the library, and feel free to ask about anything else that might be unclear!

jonnieey commented 2 years ago

static_width on buttons doesn't work :(

I had used the second suggestion by using ellipsis at the end the only problem is that the strings don't scale with window on resize. They are static and are displayed ontop of window.

Yeah, I guess I'll have to stick with the ellipsis for now as I maybe wait for horizontal scroll-ability. Is that something that you plan on adding?

bczsalba commented 2 years ago

the strings don't scale with window on resize

Actually, I think if you used the subclassing method that should no longer be the case!

Is that something that you plan on adding?

For sure, though the timeline is unclear at the moment. I'm currently hard at work on layouts and highlighters, next update is planned to be about colorschemes and general color related business, but I might be able to sneak a ScrollView in, depending on the implementation difficulty.

One thing though: As far as I've tested, terminals don't send horizontal scroll events, only vertical ones. So you sadly won't be able to use the touchpad to scroll left or right, which will probably feel a bit clunky. There will be scrollbars for it though, so it should be alright.

jonnieey commented 2 years ago

Great. I'll try subclassing, thank you.

bczsalba commented 2 years ago

Hey there! Did it end up working?

jonnieey commented 2 years ago
import pytermgui as ptg
from pytermgui.window_manager.layouts import Layout

class MainWindow(ptg.Window):

    title: "Title"
    overflow = ptg.Overflow.SCROLL

    def __init__(self, *widgets, **attrs):
        super().__init__(*widgets, **attrs)
        self.box = "DOUBLE"

class CustomButton(ptg.Button):
    def __init__(self, label, max_width, **attrs):
        self.max_width = max_width
        super().__init__(label, **attrs)

    def get_lines(self):
        original = self.label
        if len(original) > self.max_width:
            self.label = self.label[:self.max_width] + '...'
        lines = super().get_lines()
        self.label = original

        return lines

def client_container(clients: list = []):
    scrollview = ptg.Container(overflow=ptg.Overflow.SCROLL, box="DOUBLE", height=10)
    t = "The quick brown fox jumped over the lazy dog on a bike and did not return until the end of the line."
    for i in clients:
        scrollview.lazy_add(CustomButton(t, max_width=scrollview.width))
    return scrollview

def app_layout():
    layout = Layout()
    layout.add_slot("Clients", height=30)
    layout.add_slot("Jobs", height=30)

    return layout

def main():
    with ptg.WindowManager() as manager:
        manager.layout = app_layout()
        window = MainWindow(client_container([1, 2, 3]))
        manager.add(window)
        window2 = MainWindow(client_container([1, 3]))
        manager.add(window2)

main()

So this is what I've come up with so far. Subclassing works but only upto the initial width of the container. I've been trying to figure out how to update the width of container as the window is resized.

2022-05-09-014558

bczsalba commented 2 years ago

One thing I could suggest is instead of passing the width value by name, maybe wrap it into a callable that can be called every time Button.get_lines is called. That way it is always up to date, which should be what you are looking for.

jonnieey commented 2 years ago

Now the buttons don't show at all after that patch. eg sandbox/scrolling.py

bczsalba commented 2 years ago

Now the buttons don't show at all after that patch. eg sandbox/scrolling.py

Now that you say it, I'm pretty sure I forgot to give the buttons a default width. Will fix that tomorrow, but until then just setting some width to each button should fix it. Sorry!

jonnieey commented 2 years ago

Works great, thanks.

bczsalba commented 2 years ago

Great to hear! Is there anything else I can do related to this issue, or can we close it?

jonnieey commented 2 years ago

No, not right now. If I come across an issue I'll let you know. thanks.

bczsalba commented 2 years ago

Glad I could help then!