Permalink
Find file Copy path
888726a Mar 6, 2018
1 contributor

Users who have contributed to this file

169 lines (138 sloc) 6.02 KB
import curses
import curses.textpad
class Screen(object):
UP = -1
DOWN = 1
def __init__(self, items):
""" Initialize the screen window
Attributes
window: A full curses screen window
width: The width of `window`
height: The height of `window`
max_lines: Maximum visible line count for `result_window`
top: Available top line position for current page (used on scrolling)
bottom: Available bottom line position for whole pages (as length of items)
current: Current highlighted line number (as window cursor)
page: Total page count which being changed corresponding to result of a query (starts from 0)
┌--------------------------------------┐
|1. Item |
|--------------------------------------| <- top = 1
|2. Item |
|3. Item |
|4./Item///////////////////////////////| <- current = 3
|5. Item |
|6. Item |
|7. Item |
|8. Item | <- max_lines = 7
|--------------------------------------|
|9. Item |
|10. Item | <- bottom = 10
| |
| | <- page = 1 (0 and 1)
└--------------------------------------┘
Returns
None
"""
self.window = None
self.width = 0
self.height = 0
self.init_curses()
self.items = items
self.max_lines = curses.LINES
self.top = 0
self.bottom = len(self.items)
self.current = 0
self.page = self.bottom // self.max_lines
self.run()
def init_curses(self):
"""Setup the curses"""
self.window = curses.initscr()
self.window.keypad(True)
curses.noecho()
curses.cbreak()
curses.start_color()
curses.init_pair(1, curses.COLOR_CYAN, curses.COLOR_BLACK)
curses.init_pair(2, curses.COLOR_BLACK, curses.COLOR_CYAN)
self.current = curses.color_pair(2)
self.height, self.width = self.window.getmaxyx()
def run(self):
"""Continue running the TUI until get interrupted"""
try:
self.input_stream()
except KeyboardInterrupt:
pass
finally:
curses.endwin()
def input_stream(self):
"""Waiting an input and run a proper method according to type of input"""
while True:
self.display()
ch = self.window.getch()
if ch == curses.KEY_UP:
self.scroll(self.UP)
elif ch == curses.KEY_DOWN:
self.scroll(self.DOWN)
elif ch == curses.KEY_LEFT:
self.paging(self.UP)
elif ch == curses.KEY_RIGHT:
self.paging(self.DOWN)
elif ch == curses.ascii.ESC:
break
def scroll(self, direction):
"""Scrolling the window when pressing up/down arrow keys"""
# next cursor position after scrolling
next_line = self.current + direction
# Up direction scroll overflow
# current cursor position is 0, but top position is greater than 0
if (direction == self.UP) and (self.top > 0 and self.current == 0):
self.top += direction
return
# Down direction scroll overflow
# next cursor position touch the max lines, but absolute position of max lines could not touch the bottom
if (direction == self.DOWN) and (next_line == self.max_lines) and (self.top + self.max_lines < self.bottom):
self.top += direction
return
# Scroll up
# current cursor position or top position is greater than 0
if (direction == self.UP) and (self.top > 0 or self.current > 0):
self.current = next_line
return
# Scroll down
# next cursor position is above max lines, and absolute position of next cursor could not touch the bottom
if (direction == self.DOWN) and (next_line < self.max_lines) and (self.top + next_line < self.bottom):
self.current = next_line
return
def paging(self, direction):
"""Paging the window when pressing left/right arrow keys"""
current_page = (self.top + self.current) // self.max_lines
next_page = current_page + direction
# The last page may have fewer items than max lines,
# so we should adjust the current cursor position as maximum item count on last page
if next_page == self.page:
self.current = min(self.current, self.bottom % self.max_lines - 1)
# Page up
# if current page is not a first page, page up is possible
# top position can not be negative, so if top position is going to be negative, we should set it as 0
if (direction == self.UP) and (current_page > 0):
self.top = max(0, self.top - self.max_lines)
return
# Page down
# if current page is not a last page, page down is possible
if (direction == self.DOWN) and (current_page < self.page):
self.top += self.max_lines
return
def display(self):
"""Display the items on window"""
self.window.erase()
for idx, item in enumerate(self.items[self.top:self.top + self.max_lines]):
# Highlight the current cursor line
if idx == self.current:
self.window.addstr(idx, 0, item, curses.color_pair(2))
else:
self.window.addstr(idx, 0, item, curses.color_pair(1))
self.window.refresh()
def main():
items = [f'{num + 1}. Item' for num in range(1000)]
Screen(items)
if __name__ == '__main__':
main()