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

Can TDialog have scrollable content ? #169

Open
msilewicz opened this issue Dec 11, 2024 · 2 comments
Open

Can TDialog have scrollable content ? #169

msilewicz opened this issue Dec 11, 2024 · 2 comments

Comments

@msilewicz
Copy link

I'm trying to figure out whether TDialog can have scrollable content that contains some input controls ?
How to achieve this ?
I don't know how to add for example TInputLine to TScroller ?

@magiblot
Copy link
Owner

Hi @msilewicz! Sorry for the late reply.

No, Turbo Vision does not provide any standard way to do what you say, even though it would be the only way to ensure your dialog can be used regardless of the terminal size.

TScroller can't be used for this purpose because it is not a TGroup and it is therefore not meant to contain other views. However, it is possible to write a class which does this. A possible basic implementation:

#define Uses_TScrollBar
#define Uses_TGroup
#define Uses_TKeys
#define Uses_TEvent
#include <tvision/tv.h>

class TScrollArea : public TGroup
{
public:

    TScrollArea( const TRect &bounds,
                 TScrollBar *aHScrollBar = nullptr,
                 TScrollBar *aVScrollBar = nullptr
               );

    void setScrollLimits(int x, int y);

    void changeBounds(const TRect &bounds) override;
    void handleEvent(TEvent &event) override;

private:

    TPoint scrollPosition {0, 0};
    TPoint scrollLimit {0, 0};
    TScrollBar *hScrollBar;
    TScrollBar *vScrollBar;
    TView *background;

    void updateScrollBars();
    void scrollTo(int, int);
    static void adjustSubviewPosition(TView *, void *);
};

TScrollArea::TScrollArea( const TRect &bounds,
                          TScrollBar *aHScrollBar,
                          TScrollBar *aVScrollBar ) :
    TGroup(bounds),
    hScrollBar(aHScrollBar),
    vScrollBar(aVScrollBar)
{
    background = new TView(getExtent());
    background->growMode = gfGrowHiX | gfGrowHiY;
    insert(background);
}

void TScrollArea::setScrollLimits(int x, int y)
{
    scrollLimit = {x, y};
    updateScrollBars();
}

void TScrollArea::changeBounds(const TRect &bounds)
{
    TGroup::changeBounds(bounds);
    updateScrollBars();
}

void TScrollArea::handleEvent(TEvent &event)
{
    TGroup::handleEvent(event);
    if (event.what == evBroadcast)
    {
        if (event.message.command == cmScrollBarChanged)
        {
            if (hScrollBar && event.message.infoPtr == hScrollBar)
                scrollTo(hScrollBar->value, scrollPosition.y);
            else if (vScrollBar && event.message.infoPtr == vScrollBar)
                scrollTo(scrollPosition.x, vScrollBar->value);
        }
    }
    else if (event.what == evKeyDown)
    {
        switch (event.keyDown.keyCode)
        {
            case kbTab:
                focusNext(false);
                clearEvent(event);
                break;
            case kbShiftTab:
                focusNext(true);
                clearEvent(event);
                break;
        }
    }
}

void TScrollArea::updateScrollBars()
{
    if (hScrollBar)
        hScrollBar->setParams( scrollPosition.x,
                               0,
                               scrollLimit.x - size.x,
                               size.x - 1,
                               hScrollBar->arStep
                             );
    if (vScrollBar)
        vScrollBar->setParams( scrollPosition.y,
                               0,
                               scrollLimit.y - size.y,
                               size.y - 1,
                               vScrollBar->arStep
                             );
}

void TScrollArea::scrollTo(int x, int y)
{
    x = max(0, min(x, scrollLimit.x));
    y = max(0, min(y, scrollLimit.y));

    if (x != scrollPosition.x || y != scrollPosition.y)
    {
        TPoint delta = {
            x - scrollPosition.x,
            y - scrollPosition.y,
        };

        scrollPosition.x = x;
        scrollPosition.y = y;

        lock();
        forEach(&adjustSubviewPosition, &delta);
        unlock();
    }
}

void TScrollArea::adjustSubviewPosition(TView *view, void *deltaPtr)
{
    TPoint &delta = *(TPoint *) deltaPtr;
    TScrollArea *self = (TScrollArea *) view->owner;
    if (view != self->background)
    {
        view->origin.x -= delta.x;
        view->origin.y -= delta.y;
    }
    view->drawView();
}

For example, this is how it would be used in the greeting dialog of hello.cpp:

void THelloApp::greetingBox()
{
    TDialog *d = new TDialog(TRect( 25, 5, 55, 16 ), "Hello, World!" );
    // Make the dialog resizable.
    d->flags |= wfGrow;

    // Create scrollbars.
    TScrollBar *hScrollBar = d->standardScrollBar(sbHorizontal | sbHandleKeyboard);
    TScrollBar *vScrollBar = d->standardScrollBar(sbVertical | sbHandleKeyboard);

    // Insert a scroll area into the dialog.
    TRect dialogInterior = d->getExtent().grow(-1,-1);
    TScrollArea *scrollArea = new TScrollArea(dialogInterior, hScrollBar, vScrollBar);
    scrollArea->growMode = gfGrowHiX | gfGrowHiY;
    d->insert(scrollArea);

    // Insert the views into the scroll area.
    scrollArea->insert(new TStaticText(TRect(2, 4, 14, 5), "How are you?"));
    scrollArea->insert(new TButton(TRect(15, 1, 27, 3), "Terrific", cmCancel, bfNormal));
    scrollArea->insert(new TButton(TRect(15, 3, 27, 5), "Ok", cmCancel, bfNormal));
    scrollArea->insert(new TButton(TRect(15, 5, 27, 7), "Lousy", cmCancel, bfNormal));
    scrollArea->insert(new TButton(TRect(15, 7, 27, 9), "Cancel", cmCancel, bfNormal));
    scrollArea->setScrollLimits(27, 9);

    deskTop->execView(d);
    destroy(d);
}

And this is how the above looks like when the dialog gets downsized:

Screenshot_20241226_115914

I guess that it would be better if the scroll bars weren't shown at all unless scrolling is possible, but that would also require creating a different scrollbar implementation.

@msilewicz
Copy link
Author

Thank You very much for Your detailed answer. It was very helpful. Your example perfectly fits my needs as it comes to dialog scrollable area.

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

No branches or pull requests

3 participants
@magiblot @msilewicz and others