Skip to content
This repository has been archived by the owner on Aug 6, 2023. It is now read-only.

How to scroll Paragraph when appending to the end? #89

Closed
svenstaro opened this issue Nov 17, 2018 · 10 comments
Closed

How to scroll Paragraph when appending to the end? #89

svenstaro opened this issue Nov 17, 2018 · 10 comments

Comments

@svenstaro
Copy link
Contributor

I have a long multi-line string that I continuously append onto and I'd like Paragraph to append to the end. I'd then like Paragraph to scroll down so that no lines get hidden from the newly appended text. However, I can't know how much to scroll because Paragraph doesn't yield any information about its layouted text.

@karolinepauls
Copy link
Contributor

karolinepauls commented Dec 11, 2018

Right now scrolling is pretty much O(n). Paragraph essentially behaves as a pager (e.g. less) - takes something that's essentially an iterator over characters and reads only as much as it needs to.

In the text below I only consider text reflow with word wrapping, as of #103 - the case with line truncation cannot be more diffcult anyway.

1. Scroll to the bottom

It would be possible to implement a special "scroll to the bottom" mode by storing the last height lines in a circular buffer and displaying them when we hit the bottom - but this would still require scrolling the entire thing.

If we make the text iterator double-ended (rather - potentially double-ended), we'd "only" have to keep working our way from the end to the previous \n, compose it into nicely wrapped lines, store, and repeat from there until we've got enough lines to cover the text field. In the worst case there would be no \n and we'd have to seek to the beginning and do reflow from there. Similarly, scrolling to the bottom minus N lines would also work.

2. Scroll to X% of the text, know how far you've scrolled to from the offset, scrollbars

None of the above makes the above easy - for these you have to know the total height of the text (open a large file with less and press ^g to see it struggle).

Making the text random-access doesn't work for new lines - Text::raw and Text::styled still contain strings, which are not random-access and character widths vary.

The extra options I can see:

  • allow "pre-wrapped" text, essentially a vector of lines which will be truncated if they exceed the text area width.
  • allow the user to control what scrollbars show (assuming they know which they will probably not)
  • ...?

3. Since Paragraph consumes an iterator of Text::raw and Text::Styled and performs flattening internally, why not flatten it externally and make it consume it flattened so it can work like a proper pager?.

Text reflow would then operate on the flattened Text iterator and Paragraph would consume the result of word wrapping, shifting the logic away from the Paragraph.

@karolinepauls
Copy link
Contributor

Another TODO when implementing this - make scroll usize rather than u16, possibly add vertical scrolling.

@clevinson
Copy link

I'd like to +1 this request. I'm currently working on a fork that will implement some kind of a solution for this problem. Will post in here, and if @fdehau or @karolinepauls thinks its useful, i'm happy to try and clean it up and merge upstream.

@clevinson
Copy link

A more elegant solution seemed to be more than I was willing off to attempt in one evening. Spent a lot of time thinking of switching the LineComposer to a streaming iterator, and then possibly a DoubleEndedStreamingIterator... but I ended up just adding a collect_lines() method to LineComposer, and the ability to have scroll occur from either the top or the bottom of the content.

@svenstaro I also added an example in my branch so you can clone & take a look if you want to see how it works and do a similar workaround in your project: https://github.com/clevinson/tui-rs/blob/scroll-from-bottom/examples/paragraph.rs#L93-L99

Happy to continue work on this and clean it up into something more reasonable if you see a preferred path forward.. @karolinepauls

@clevinson
Copy link

clevinson commented Dec 12, 2019

@fdehau This is now cleaned up, enabling two kinds of "scroll modes":

  • ScrollMode::Normal: Basically the existing mode, scroll, the integer scroll value indicates now many lines to skip over when iterating through LineComposer's next_line() call
  • ScrollMode::Tail: The new mode I added. Functionality is tailored to my specific use case, but i think others may enjoy this behavior as well.

In the Tail mode of scroll, the scroll_offset (u16) parameter indicates scroll offset from the bottom of the content, as opposed to scrolling from the top. There are a few different cases I cover:
1. Content is fully contained within text_area

  • scroll_offset == 0: This renders as a normal (float content to top) type of view. Any
  • scroll_offset > 0: This scrolls the content down, revealing additional blank space above the content, or a scroll_overflow_char visible on the left side of the text_area, repeated on each line above the first content line.

2. Content is longer than text_area

  • scroll_offset == 0: Functions as if scrolled to the bottom of the content. The last line in the text_area is the last line of the content.
  • scroll_offset > 0: Indicates number of lines scrolled from the bottom, scrolling up to reveal the rest of the content.

@Cldfire
Copy link

Cldfire commented May 5, 2020

I just ran into the lack of this feature while working on a project with this library (trying to use paragraph to display wrapped log output while always displaying the most recent log line at the bottom). It would be super nice to see support for this situation land!

EDIT: I ended up pre-wrapping the text with https://github.com/mgeisler/textwrap and just using the list widget to display it.

@Cldfire
Copy link

Cldfire commented Jul 19, 2020

Unfortunately doing this manually outside of the tui crate with the textwrap library is a lot more difficult now that Spans are a thing in the new 0.10 release. Handling wrapping of a line composed of multiple Spans with different styles is going to be a pretty big headache.

Not so bad if you restrict lines to being a single style (but of course I want lines with multiple styles 😛).

EDIT: example impl of line wrapping here (with the constraint that lines are built from un-styled strings)

@fdehau fdehau linked a pull request Aug 2, 2020 that will close this issue
@mcbloch
Copy link

mcbloch commented Nov 17, 2020

@clevinson Do you still intent to make a pr on this repo to merge your fix upstream or do you think #349 handles your needs?

@mgeisler
Copy link

mgeisler commented Jun 6, 2021

Handling wrapping of a line composed of multiple Spans with different styles is going to be a pretty big headache.

Hi @Cldfire, I'm just guessing, but could it be that the Fragment trait introduced in Textwrap 0.13 would help here?

Basically, Textwrap internally wraps such "fragments" which has a width and some trailing whitespace. For plain text, the Word struct is used by Textwrap, but you could imagine a different struct which wraps the styled Spans you talk about.

I recently wrote an example which draws text on a HTML canvas and there I wrap proportional text using a CanvasWord struct.

I would be happy to talk more if you think Textwrap could be useful for tui-rs somehow.

@Cldfire
Copy link

Cldfire commented Jun 6, 2021

@mgeisler ah that's really neat! It definitely looks like Fragment would be pretty helpful to make wrapping Spans more convenient.

I'm not working on that particular project of mine atm but if I do revisit it at some point in the future I'll definitely try making use of Fragment 👍

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

Successfully merging a pull request may close this issue.

7 participants