ArthurSonzogni / FTXUI

:computer: C++ Functional Terminal User Interface. :heart:
MIT License
6.64k stars 399 forks source link

Scrollable paragraph #519

Open archydragon opened 1 year ago

archydragon commented 1 year ago

Hello. I sincerely apologize for using issues as helpdesk but cannot figure out how to make a thing which looks trivial: scrollable paragraph frame. Meaning, I need to display quite a wall of text and want to let user to scroll it with arrows/mouse wheel. I checked the other issues and the code of git-tui and chrome-log-beautifier, but they seem to operate with lists, not paragraphs. Or there is no easy builtin way to do that, and I need to write a component for this purpose from scratch?

image

Thanks in advance, the library is very neat and handy!

ArthurSonzogni commented 1 year ago

Hello, Maybe you can try wrapping your component with the scroller from git-tui: https://github.com/ArthurSonzogni/git-tui/blob/master/src/scroller.cpp

archydragon commented 1 year ago

Thanks, I'll give it another shot today or later this week.

archydragon commented 1 year ago

Still got no luck with paragraph, can't find an easy way to shift viewport there, as with a single paragraph element, the size is always 1. However, it works with just a list of strings which satisfies my needs for now:

class ScrollerBase : public ComponentBase {
public:
    explicit ScrollerBase(Component child) {
        Add(std::move(child));
    }

private:
    Element Render() final {
        auto focused = Focused() ? focus : ftxui::select;

        Element background = ComponentBase::Render();
        background->ComputeRequirement();
        size_ = background->requirement().min_y;

        // Values a bit hardcoded for bordered window, should be -0 and -2 if no border is used.
        min_selected_ = int(box_.y_max / 2) - 1;
        max_selected_ = size_ - int(box_.y_max / 2) - 1;
        if (selected_ == 0) {
            selected_ = min_selected_;
        }

        return dbox({
                   std::move(background),
                   vbox({
                       text("") | size(HEIGHT, EQUAL, selected_),
                       text("") | focused,
                   }),
               }) |
               vscroll_indicator | yframe | yflex | reflect(box_) | border;
    }

    bool OnEvent(Event event) final {
        if (event.is_mouse() && box_.Contain(event.mouse().x, event.mouse().y))
            TakeFocus();

        int selected_old = selected_;
        if (event == Event::ArrowUp || event == Event::Character('k') ||
            (event.is_mouse() && event.mouse().button == Mouse::WheelUp)) {
            selected_--;
        }
        if ((event == Event::ArrowDown || event == Event::Character('j') ||
             (event.is_mouse() && event.mouse().button == Mouse::WheelDown))) {
            selected_++;
        }
        if (event == Event::PageDown)
            selected_ += box_.y_max - box_.y_min;
        if (event == Event::PageUp)
            selected_ -= box_.y_max - box_.y_min;
        if (event == Event::Home)
            selected_ = 0;
        if (event == Event::End)
            selected_ = size_;

        if (selected_ < min_selected_) {
            selected_ = min_selected_;
        }
        if (selected_ > max_selected_) {
            selected_ = max_selected_;
        }

        selected_ = std::max(0, std::min(size_ - 1, selected_));
        return selected_old != selected_;
    }

    [[nodiscard]] bool Focusable() const final {
        return true;
    }

    int min_selected_ = 0;
    int max_selected_ = 0;
    int selected_ = 0;
    int size_ = 0;
    Box box_;
};

Something tells me that there should be an easier way to do that.