0

Currently when we reach the end of the textbox area, we just move to the next line outside of the box. How can I implement scrolling such that when we reach the end and try to move to the next line, the top line disappears and the entire text body moves up?

eg:

+-------+
|hello1 |
|hello2 |
|hello3 |
|hello4 |
|hello5 |
+-------+

after pressing enter:-

+-------+
|hello2 |
|hello3 |
|hello4 |
|hello5 |
|       |
+-------+

Here is the code, I am aware of curses' textpad, but its not what i need.

import curses
from curses import textpad
import time

class notepad:
    def __init__(self, stdscr, topleft, bottomright, starttext = ""):
        self.topleft = topleft
        self.bottomright = bottomright
        self.stdscr = stdscr
        self.text = starttext
        self.curspos = 0

    def box(self):
        curses.textpad.rectangle(self.stdscr, self.topleft[0], self.topleft[1], self.bottomright[0], self.bottomright[1])

    def get_input(self, key):

        if not key == -1:

            if key == curses.KEY_LEFT:
                self.curspos -= 1

            elif key == curses.KEY_RIGHT:
                if self.curspos < len(self.text):
                    self.curspos += 1

            elif key == curses.KEY_UP:
                self.curspos -= self.width

            elif key == curses.KEY_DOWN:
                if self.curspos//self.width < len(self.text)//self.width:
                    self.curspos += self.width - (self.curspos%self.width - len(self.text)%self.width)

            elif key == curses.KEY_BACKSPACE:
                self.text = self.text[:self.curspos][:-1] + self.text[self.curspos:]
                self.curspos -= 1

            elif key == curses.KEY_ENTER or key == 10 or key == 13:
                self.text = self.text[:self.curspos] + " "*(self.width - (len(self.text[:self.curspos])%self.width)) + self.text[self.curspos:]
                self.curspos += 1
                self.curspos += self.width - (self.curspos%self.width - len(self.text)%self.width)

            else:
                self.text = self.text[:self.curspos] + chr(key) + self.text[self.curspos:]
                self.curspos += 1

    def display(self):
        self.width = (self.bottomright[1]-1) - (self.topleft[1]+1)

        if self.curspos < 0:
            self.curspos = 0


        #disply text
        for i in range(len(self.text)):
            self.stdscr.addstr( self.topleft[0]+1 + (i//self.width) , 
                                self.topleft[1]+1 +i%self.width  , 
                                self.text[i]
                                )
            
        #display fake cursor
        if self.curspos < len(self.text):
            self.stdscr.addstr(self.topleft[0]+1 + (self.curspos//self.width) , self.topleft[1]+1 + self.curspos%self.width, self.text[self.curspos], curses.A_REVERSE)
        else:
            self.stdscr.addstr(self.topleft[0]+1 + (self.curspos//self.width) , self.topleft[1]+1 + self.curspos%self.width, " ", curses.A_REVERSE)
        
def main():
    run = True
    stdscr = curses.initscr()
    stdscr.nodelay(True)
    stdscr.keypad(True)
    curses.curs_set(False)
    curses.start_color()
    curses.noecho()
    curses.cbreak()

    notepad1 = notepad(stdscr, [0,0], [20, 30])

    try:
        while run:
            start = time.time()

            stdscr.erase()

            key = stdscr.getch()

            notepad1.box()
            notepad1.get_input(key)
            notepad1.display()

            stdscr.refresh()


            time.sleep(max(0.05 - (time.time() - start), 0))
    finally:
        curses.echo()
        curses.nocbreak()
        curses.curs_set(True)
        stdscr.keypad(False)
        stdscr.nodelay(False)
        curses.endwin()

main()
  • Can you not simply say: `self.text = self.text[self.width:]` to drop the top line? – RufusVS Oct 09 '21 at 19:10
  • this will remove the contents of the first line which is something i want to preserve – RandomPersonOnline Oct 09 '21 at 20:16
  • It's up to the application to manage scrolling. As a shortcut, curses provides the [*pad*](https://docs.python.org/3/library/curses.html#curses.newpad) feature, which is part of the python curses interface. – Thomas Dickey Oct 10 '21 at 00:24
  • Then you will want to keep track of the start offset of your text and accommodate in the display routine. – RufusVS Oct 10 '21 at 03:41

1 Answers1

1

Since you commented you wanted to keep the text but scroll it off the screen, you need something like this in your init:

self.scroll_lines = 0

Then when you want to scroll down a line, just use:

 self.scroll_lines += 1

And your display function would looks something like this:

 #disply text
 text_offset = self.scroll_lines*self.width
 for i in range(text_offset, len(self.text)):
        self.stdscr.addstr( self.topleft[0]+1 + (i//self.width-self.scroll_lines) , 
                            self.topleft[1]+1 +i%self.width  , 
                            self.text[i]
                            )

I'll leave it as an exercise for you to place the cursor properly.

RufusVS
  • 4,008
  • 3
  • 29
  • 40