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

[BUG] terminal.print() only honors the x position for the first line #55

Closed leftbones closed 2 years ago

leftbones commented 2 years ago

Simple code I used:

#!/usr/bin/env python3

import os, sys
import pytermgui as ptg

with open(sys.argv[1]) as f:
    contents = f.read()

ptg.terminal.clear_stream()
ptg.terminal.print(contents, pos=(10, 0))

Output looks like this:

          #include <stdio.h>

int main() {
    printf("Hello world!");
    return 0;
}

I don't believe this is intended? There also seems to be issues with moving the cursor past the first column, but that's a separate issue.

bczsalba commented 2 years ago

Yup, that is a usecase I never really thought about. This method was more intended as a line-by-line printer, and I didn't really think to deal with newlines. Not sure how they should be dealt with, though: I think manipulating the input string without the user's input would become a bit annoying, which is why I just left it to print a single line, and be blissfully unaware of any newlines.

I think you might be interested at the (cursor_at)[https://ptg.bczsalba.com/pytermgui/context_managers.html#cursor_at) method for this actually. Basically it sets up the cursor at the given position, and after every line printed moves one character down, while keeping the horizontal position consistent. Only thing you need to change is add with ptg.cursor_at((10, 1)) as print as a context and print line by line under it.

There also seems to be issues with moving the cursor past the first column, but that's a separate issue.

I think that one is about the terminal's origin position being (1, 1). Printing anything to 0 will have differing behaviour between terminals, AFAIK on Kitty anything printed to y=0 will be completely hidden, and anything at x=0 will just go to 1 instead.

I've been thinking if all the positioning methods should just implicitly apply the origin behind the scenes, as this isn't a very easy to grasp issue. What do you think?

leftbones commented 2 years ago

That makes sense. I was trying to compare print and write and it seemed that print was meant to take multiple items so I assume it would print them in a "block" starting at the position you passed in. If that's not the intended use, then that's fine.

The idea I had in my head was that block idea, so that it prints the first line at (x, y), then prints every subsequent line at (x, y+1). It is kinda hard to grasp. I'm used to curses, where the origin is (0, 0) and all coordinates are passed in as (y, x) so this whole thing is taking getting used to for me haha.

The issue I was having with the cursor was with the cursor moving functions, it seemed to obey the up and down functions but trying to move to the right didn't do anything at all.

bczsalba commented 2 years ago

The issue I was having with the cursor was with the cursor moving functions, it seemed to obey the up and down functions but trying to move to the right didn't do anything at all.

That's really odd, what functions were you using? It should just be giving info to the terminal's API and letting that handle what it does, so I don't see why it wouldn't work. Then again, we are interfacing with terminals: things rarely work properly.

leftbones commented 2 years ago

That's really odd, what functions were you using? It should just be giving info to the terminal's API and letting that handle what it does, so I don't see why it wouldn't work. Then again, we are interfacing with terminals: things rarely work properly.

In this instance, I was just using the functions included in ansi_interface, I don't think I saved the code but it was something like...

import pytermgui  as ptg

ptg.set_alt_buffer()

while True:
    key == ptg.getch()

    if key == 'h': ptg.cursor_left()
    elif key == 'j': ptg.cursor_down()
    elif key == 'k': ptg.cursor_up()
    elif key == 'l': ptg.cursor_right()
    elif key == '\x1b': break

ptg.unset_alt_buffer()

That was just off the top of my head, it might not actually run, I can't test it at the moment. But basically it would allow me to move the cursor up and down (while doing some awful flickering, likely from redrawing the screen or something) but attempting to move to the left and right did nothing at all.

At one point I also added in ptg.print_to((0, 0), ptg.report_cursor()) to see if anything was happening behind the scenes, and it was not at all what I expected. When moving to the right, for example, the cursor POSITION did move (as reported by report_cursor() but visually, it was stuck on the leftmost column.

My best guess without diving into the code is that the screen is redrawn after each one of these moves, which results in the cursor being placed wherever the last print happened to end. I tried using save_cursor() and restore_cursor() to no avail, I also tried doing the same thing manually by storing the return value of report_cursor() in a variable, then using move_cursor() at the end of the loop to restore it, but that didn't work.

This low level stuff can get really finicky, unfortunately.

bczsalba commented 2 years ago

Not sure what your code was exactly, but this works on my terminal (Kitty):

import pytermgui as ptg

ptg.set_alt_buffer()

while True:
    key = ptg.getch()

    if key == "h":
        ptg.cursor_left()
    elif key == "j":
        ptg.cursor_down()
    elif key == "k":
        ptg.cursor_up()
    elif key == "l":
        ptg.cursor_right()
    elif key == "\x1b":
        break

    ptg.terminal.flush()

ptg.unset_alt_buffer()

Note the call to terminal.flush() (which basically just calls sys.stdout.flush()). Without that nothing happens, as by default these functions don't flush the buffer.

Could you try the code above and see what happens? If it still doesn't work we can open a new issue to discuss it, but I think this issue is done for the moment.

leftbones commented 2 years ago

Noe the call to terminal.flush()

Well don't I feel silly! That's kind of an important thing I completely omitted! Perhaps PTG will work out for my project after all...

bczsalba commented 2 years ago

Honestly, I'm still not sure what the issue you faced was, as AFAIK running without flushing should just cause nothing to happen. May be terminal specific, not sure.

leftbones commented 2 years ago

Yeah, I'm not sure anymore, I wasn't able to reproduce it again. I might have been calling print_to() every update which would have messed things up for sure.

Is there an intended method of adding text to the screen? At the low level, print_to() seems to get the job done fine, but what about at a higher level? As far as adding text to an existing window goes, I haven't quite found the best way.

bczsalba commented 2 years ago

I like to use either terminal.write(pos=...), terminal.print(pos=...) or cursor_at, depending on the usecase. There are currently a lot of printing functions in the library for some reason, but they will be consolidated in the future.

leftbones commented 2 years ago

Ah yeah I had tried those previously but I believe I was also redrawing the windows every update so the prints were ending up underneath the windows. Thanks!