From 5a85502cda29f64f4f045817c9a99490ba64f1b8 Mon Sep 17 00:00:00 2001 From: Armaan Bhojwani Date: Mon, 8 Feb 2021 16:49:07 -0500 Subject: [PATCH] Add rough panel support Instead of clearing the windwo to display help screen and menu, use layerable curses panels. Currently, the implementation is rather rough around the edges, and must be improved a bit --- lightcards/display.py | 203 +++++++++++++++++++++++++++--------------- 1 file changed, 130 insertions(+), 73 deletions(-) diff --git a/lightcards/display.py b/lightcards/display.py index 5f7276b..2aa891e 100644 --- a/lightcards/display.py +++ b/lightcards/display.py @@ -2,6 +2,7 @@ # Armaan Bhojwani 2021 import curses +import curses.panel from random import shuffle import sys import textwrap @@ -18,12 +19,29 @@ class Display(): def run(self, stdscr): """Set important options before beginning""" self.win = stdscr + (mlines, mcols) = self.win.getmaxyx() curses.curs_set(0) # Hide cursor curses.init_pair(1, curses.COLOR_CYAN, 0) curses.init_pair(2, curses.COLOR_RED, 0) curses.init_pair(3, curses.COLOR_YELLOW, 0) + + (self.main_win, self.main_panel) = self.panel_create(mlines, mcols) + self.menu_init() + self.help_init() + self.get_key() + def panel_create(self, x, y): + """Create popup menus""" + win = curses.newwin(x, y) + panel = curses.panel.new_panel(win) + win.erase() + return (win, panel) + + def panel_up(self): + curses.panel.update_panels() + self.win.refresh() + def leave(self): """Pickle stack before quitting""" if self.obj.getIdx() == len(self.stack): @@ -34,7 +52,7 @@ class Display(): def ntotal(self): """Get toal number of starred cards""" - return(len([card for card in self.stack if card.getStar()])) + return([card for card in self.stack if card.getStar()]) def disp_bar(self): """ @@ -59,7 +77,7 @@ class Display(): # Create bar component bar_start = "[" bar_middle = self.stack[self.obj.getIdx()].printStar() - bar_end = f"] [{self.ntotal()}/{str(len(self.stack))} starred] " + \ + bar_end = f"] [{len(self.ntotal())}/{str(len(self.stack))} starred] " + \ f"[{percent}% (" + \ str(self.obj.getIdx() + 1).zfill(len(str(len(self.stack)))) + \ f"/{str(len(self.stack))})] [" + \ @@ -67,27 +85,33 @@ class Display(): f"{str(self.obj.getSide() + 1)})] " # Put it all togethor - self.win.hline(mlines - 2, 0, 0, mcols) self.win.addstr(mlines - 1, 0, bar_start, curses.color_pair(1)) - self.win.addstr(bar_middle, star_color) - self.win.insstr(bar_end, curses.color_pair(1)) + self.win.addstr(mlines - 1, len(bar_start), bar_middle, star_color) + self.win.addstr(mlines - 1, len(bar_start + bar_middle), + bar_end, curses.color_pair(1)) def menu_print(self, string, err=False): """Print messages on the menu screen""" - self.win.clear() if err: color = curses.color_pair(2) else: color = curses.color_pair(1) - self.disp_menu(keygrab=False) - self.win.addstr("\n\n" + string, color) + + for i in range(42): + self.menu_win.addch(15, i+1, " ") + + self.menu_win.addstr(15, 1, string, color) + self.panel_up() self.menu_grab() def menu_grab(self): """Grab keypresses for the menu screen""" while True: key = self.win.getkey() - if key == "q": + if key in ["r", "q", "m"]: + self.menu_panel.hide() + self.panel_up() + if key in ["q", "m"]: if len(self.stack) == self.obj.getIdx(): self.leave() elif len(self.stack) < self.obj.getIdx(): @@ -108,7 +132,7 @@ class Display(): elif key == "t": self.stack.reverse() self.menu_print( - "self.stack reversed!") + "Stack reversed!") elif key == "z": shuffle(self.stack) self.menu_print("Stack shuffled!") @@ -139,29 +163,44 @@ class Display(): self.obj.setIdx(0) self.get_key() - def disp_menu(self, keygrab=True, quit=False): - """ - Display a menu once the end of the deck has been reached, offering - multiple options on how to continue. - """ - + def menu_init(self, quit=True): + (self.menu_win, self.menu_panel) = self.panel_create(17, 44) + self.menu_panel.top() + self.menu_panel.hide() + # TODO: fix this quit_text = "[q]: back" if quit: quit_text = "[q]: quit" - self.win.addstr("LIGHTCARDS MENU", curses.color_pair(1) + + self.menu_win.addstr(1, 1, "LIGHTCARDS MENU", curses.color_pair(1) + curses.A_BOLD) - self.win.hline(1, 0, curses.ACS_HLINE, 15) - self.win.addstr(2, 0, "[y]: reset stack to original state\n" + - "[a]: alphabetize stack\n" + - "[z]: shuffle stack\n" + - "[f]: flip all cards in stack\n" + - "[t]: reverse stack order\n" + - "[u]: unstar all\n" + - "[d]: star all\n" + - "[s]: update stack to include starred only\n\n" + - "[r]: restart\n" + - quit_text) + self.menu_win.hline(2, 1, curses.ACS_HLINE, 15) + text = ["[y]: reset stack to original state", + "[a]: alphabetize stack", + "[z]: shuffle stack", + "[f]: flip all cards in stack", + "[t]: reverse stack order", + "[u]: unstar all", + "[d]: star all", + "[s]: update stack to include starred only"] + + for t in enumerate(text): + self.menu_win.addstr(t[0] + 3, 1, t[1]) + self.menu_win.addstr(len(text) + 4, 1, "[r]: restart") + self.menu_win.addstr(len(text) + 5, 1, quit_text) + + self.menu_win.box() + self.panel_up() + + def disp_menu(self, keygrab=True): + """ + Display a menu once the end of the deck has been reached, offering + multiple options on how to continue. + """ + (mlines, mcols) = self.win.getmaxyx() + self.menu_win.mvwin(int(mlines/2) - 9, int(mcols/2) - 22) + self.menu_panel.show() + self.panel_up() if keygrab: self.menu_grab() @@ -180,11 +219,12 @@ class Display(): Shows a header, a horizontal line, and the contents of the current side. """ - self.win.clear() - (_, mcols) = self.win.getmaxyx() + (mlines, mcols) = self.win.getmaxyx() if self.obj.getIdx() == len(self.stack): - self.disp_menu(quit=True) + self.disp_menu() else: + self.main_panel.bottom() + self.main_win.clear() # If on the back of the card, show the content of the front side in # the header num_done = str(self.obj.getIdx() + @@ -198,48 +238,65 @@ class Display(): if mcols > 80: header_width = 80 - self.win.addstr(textwrap.shorten(top, width=header_width, + self.main_win.addstr(0, 0, textwrap.shorten(top, width=header_width, placeholder="…"), curses.A_BOLD) # Add horizontal line lin_width = header_width if len(top) < header_width: lin_width = len(top) - self.win.hline(1, 0, curses.ACS_HLINE, lin_width) + self.main_win.hline(1, 0, curses.ACS_HLINE, lin_width) # Show current side - self.win.addstr(2, 0, textwrap.fill( + self.main_win.addstr(2, 0, textwrap.fill( self.stack[self.obj.getIdx()][self.obj.getSide()], width=self.wrap_width())) + self.panel_up() self.disp_bar() self.disp_sidebar() + self.win.hline(mlines - 2, 0, 0, mcols) - def disp_help(self): + def help_init(self): """Display help screen""" - self.win.clear() - self.win.addstr("LIGHTCARDS HELP", curses.color_pair(1) + - curses.A_BOLD) - self.win.hline(1, 0, curses.ACS_HLINE, 15) - self.win.addstr(2, 0, textwrap.fill( - "Welcome to lightcards. Here are some keybindings to get you " + - "started:", width=self.wrap_width()) + - "\n\nh, left previous card\n" + - "l, right next card\n" + - "j, k, up, down flip card\n" + - "i, / star card\n" + - "0, ^, home go to the start of the deck\n" + - "$, end go to the end of the deck\n" + - "H, ? open this screen\n" + - "e open the input file in $EDITOR\n" + - "m open the control menu\n\n" + - textwrap.fill( - "More information can be found in the man page, " + - "or by running `lightcards --help`.", - width=self.wrap_width()) + - "\n\nPress [q], [H], or [?] to go back.") + (self.help_win, self.help_panel) = self.panel_create(20, 52) + self.help_panel.top() + self.help_panel.hide() + self.help_win.clear() + self.help_win.addstr(1, 1, "LIGHTCARDS HELP", curses.color_pair(1) + + curses.A_BOLD) + self.help_win.hline(2, 1, curses.ACS_HLINE, 15) + text = ["Welcome to lightcards. Here are some keybindings", + "to get you started:", + "", + "h, left previous card", + "l, right next card", + "j, k, up, down flip card", + "i, / star card", + "0, ^, home go to the start of the deck", + "$, end go to the end of the deck", + "H, ? open this screen", + "e open the input file in $EDITOR", + "m open the control menu", + "", + "More information can be found in the man page, or", + "by running `lightcards --help`.", + "", + "Press [q], [H], or [?] to go back."] + + for t in enumerate(text): + self.help_win.addstr(t[0] + 3, 1, t[1]) + + self.help_win.box() + + def disp_help(self): + (mlines, mcols) = self.win.getmaxyx() + self.help_win.mvwin(int(mlines/2) - 10, int(mcols/2) - 26) + self.panel_up() + self.help_panel.show() while True: - key = self.win.getkey() + key = self.help_win.getkey() if key in ["q", "H", "?"]: + self.help_panel.hide() self.get_key() def get_key(self): @@ -257,10 +314,6 @@ class Display(): self.obj.forward(self.stack) self.obj.setSide(0) self.disp_card() - elif key in ["h", "KEY_LEFT"]: - self.obj.back() - self.obj.setSide(0) - self.disp_card() elif key in ["j", "k", "KEY_UP", "KEY_DOWN"]: self.obj.flip() self.disp_card() @@ -278,7 +331,6 @@ class Display(): elif key in ["H", "?"]: self.disp_help() elif key == "m": - self.win.clear() self.disp_menu() elif key == "e": (self.headers, self.stack) = lightcards.reparse() @@ -295,17 +347,22 @@ class Display(): self.win.hline(1, left, 0, mlines) i = 0 - for card in self.stack: - if i > mlines - 6: - self.win.addstr(2 + i, left, f"... ({self.ntotal() - i} more)") + # TODO: Fix this, some off by one error + newntotal = self.ntotal() + if mlines - 5 < len(self.ntotal()): + newntotal = self.ntotal()[:mlines - 4] + elif mlines - 5 == len(self.ntotal()): + newntotal = self.ntotal()[:mlines - 3] + + for card in newntotal: + for i in enumerate(newntotal): + term = i[1][0] + if len(i[1][0]) > 18: + term = i[1][0][:18] + "…" + self.win.addstr(2 + i[0], left, term) + if not newntotal == self.ntotal(): + self.win.addstr(mlines - 3, left, f"({len(self.ntotal()) - len(newntotal)} more)") break - elif card.getStar(): - term = card[0] - if len(card[0]) > 18: - term = card[0][:18] + "…" - self.win.addstr(2 + i, left, term) - - i += 1 - if i == 0: + if len(self.ntotal()) == 0: self.win.addstr(2, left, "None starred") -- 2.39.2