magiblot / tvision

A modern port of Turbo Vision 2.0, the classical framework for text-based user interfaces. Now cross-platform and with Unicode support.
Other
1.99k stars 150 forks source link

Advice: using tvision to draw console graphics without using the event loop #133

Closed electroly closed 7 months ago

electroly commented 7 months ago

Hi @magiblot, I'm working again on TMBASIC and I wonder if you can help me figure out if tvision is suitable for a particular use case. I plan to offer three console modes:

My question is about the full-screen mode. In this mode the user's program doesn't have a TUI event loop; it's just printing things and sometimes waiting for keyboard input. This would be suitable for simple games, non-interactive screen savers, that sort of thing. I thought I could reuse tvision's console support by just having a desktop with a background with no menu, statusbar, or windows. I could do all the drawing in a draw() override in my TBackground subclass. I've done that, but now the question is: how can I ask tvision to redraw the screen when I don't want to run an event loop? I just want to draw once and return control so the user's program can continue executing.

Is this possible? The two alternatives I can think of are:

  1. Weave the BASIC interpreter into tvision's event loop so that I can exit from the interpreter and allow events to be pumped before returning again to the interpreter.
  2. Don't use tvision for this mode, and instead just print the ANSI codes myself.

What do you think? I appreciate any guidance.

magiblot commented 7 months ago

Hi, Brian! Happy new year!

Yes, it is certainly possible to use Turbo Vision without the original event loop (app.run()) and to redraw the screen on demand.

Here is a minimalistic example I came up with; it also reads keyboard input and handles screen resizing properly:

#define Uses_TApplication
#define Uses_TEvent
#define Uses_TScreen
#define Uses_TText
#include <tvision/tv.h>

#include <time.h>

class TConsoleView : public TView
{

public:

    TConsoleView(const TRect &bounds) noexcept :
        TView(bounds)
    {
        growMode = gfGrowHiX | gfGrowHiY;
    }

    void draw() override
    {
        TDrawBuffer b;

        TColorAttr bgColor = '\x10';
        b.moveChar(0, ' ', bgColor, size.x);
        writeLine(0, 0, size.x, size.y, b);

        TStringView text = "Press any key to continue";
        TStringView dotText[3] = {".  ", ".. ", "..."};
        TColorAttr textColor = '\x3E';
        int width = b.moveStr(0, text, textColor);
        width += b.moveStr(width, dotText[time(0) % 3], textColor);
        int x = (size.x - width)/2;
        writeLine(x, size.y/2, width, 1, b);
    }

};

class TConsoleApp : public TApplication
{

public:

    TConsoleApp() noexcept :
        TProgInit(0, 0, 0)
    {
        insert(new TConsoleView(getExtent()));
    }

};

int main()
{
    TConsoleApp app;

    // The timeout for 'app.getEvent' can be customized.
    TProgram::eventTimeout = 1000; // In milliseconds.
    // However, if you want to wait indefinitely for a keyboard or mouse event,
    // remember about the 'keyEvent' and 'mouseEvent' methods.

    TEvent event;
    do {
        // 'redraw' results in 'TConsoleView::draw' being called.
        app.redraw();
        // 'flushScreen' has to be called to get the terminal updated.
        // However, 'getEvent' does an implicit 'flushScreen', so it's actually
        // not needed in this case. It's like 'refresh' and 'getch' in Curses.
        TScreen::flushScreen();
        app.getEvent(event);
    } while (event.what != evKeyDown);

    app.shutDown();
}

I hope this helps!

Cheers.

electroly commented 7 months ago

This technique worked. Thanks so much! Happy new year to you as well.