Created
June 5, 2021 15:20
-
-
Save Reyuu/5462155adf581829c65470f0ba7adfc4 to your computer and use it in GitHub Desktop.
Simple curses GUI in 100 lines.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import curses | |
class Entry: | |
def __init__(self, text, fn, x_offset=0, y_offset=0, left_fn=None, right_fn=None) -> None: | |
self.text = text | |
self.fn = fn | |
self.x_pos_offset = x_offset | |
self.y_pos_offset = y_offset | |
self.left_fn = left_fn | |
self.right_fn = right_fn | |
class Decoration: | |
def __init__(self, text="-", n=3, x=0, y=0) -> None: | |
self.text = text*n | |
self.x = x | |
self.y = y | |
pass | |
class Menu: | |
screen = curses.initscr() | |
curses.noecho() | |
screen.keypad(1) | |
def __init__(self, title=None) -> None: | |
self.entries = [] | |
self.decorations = [] | |
self.position = 0 | |
self.running = False | |
self.title = title | |
self.pointer = ">" | |
def add_entry(self, text, fn=None, x_offset=0, y_offset=0) -> int: | |
self.entries += [Entry(text, fn, x_offset, y_offset)] | |
return len(self.entries) - 1 | |
def add_decoration(self, text="-", n=3, x=0, y=0) -> None: | |
self.decorations += [Decoration(text, n, x, y)] | |
# Go to main loop here | |
def show(self) -> None: | |
self.__render_menu() | |
g = None | |
self.running = True | |
while self.running: | |
g = self.screen.getch() | |
if (g == curses.KEY_UP): | |
if (self.position - 1 < 0): | |
self.position = len(self.entries) - 1 | |
else: | |
self.position -= 1 | |
self.__render_menu() | |
if (g == curses.KEY_DOWN): | |
if (self.position + 1 > len(self.entries) - 1): | |
self.position = 0 | |
else: | |
self.position += 1 | |
self.__render_menu() | |
if (g == curses.KEY_LEFT): | |
self.__trigger_function(self.entries[self.position].left_fn) | |
if (g == curses.KEY_RIGHT): | |
self.__trigger_function(self.entries[self.position].right_fn) | |
# Enter is 10 here as well lol | |
if (g == 10): | |
self.__trigger_function(self.entries[self.position].fn) | |
pass | |
def terminate(self) -> None: | |
curses.echo(1) | |
curses.endwin() | |
## Internal methods | |
def __trigger_function(self, fn) -> None: | |
try: | |
fn(self.position) | |
except TypeError: | |
try: | |
fn() | |
except TypeError: | |
pass | |
self.__render_menu() | |
def __screen_cleanup(self) -> None: | |
self.screen.clear() | |
self.screen.refresh() | |
def __disable_curses(self) -> None: | |
curses.endwin() | |
def __render_decorations(self) -> None: | |
for deco in self.decorations: | |
self.screen.addstr(deco.y+(-1 if self.title is None else 0)+1, deco.x, deco.text) | |
def __render_menu(self) -> None: | |
self.screen.clear() | |
self.__render_decorations() | |
if len(self.entries) > 0: | |
c = 0 | |
c_offset = 0 | |
if not(self.title is None): | |
c_offset += 1 | |
self.screen.addstr(0, 0, self.title) | |
for entry in self.entries: | |
if self.position == c: | |
self.screen.addstr(c+c_offset+entry.y_pos_offset, 0+entry.x_pos_offset, self.pointer) | |
self.screen.addstr(c+c_offset+entry.y_pos_offset, 2+entry.x_pos_offset, entry.text) | |
c += 1 | |
self.screen.refresh() | |
### Testing goes here ### | |
if __name__ == "__main__": | |
# Initialization with optional title | |
m = Menu("Choose something") | |
# Switching buttons | |
def a(pos): | |
if m.entries[pos].text == "yay": | |
m.entries[pos].text = "no" | |
else: | |
m.entries[pos].text = "yay" | |
m.add_entry("yay", a) | |
m.add_entry("more") | |
m.add_entry("even more") | |
# Removing the title | |
def d(pos): | |
if m.title: | |
m.title = None | |
m.entries[pos].text = "add stupid title" | |
else: | |
m.title = "Choose something" | |
m.entries[pos].text = "remove stupid title" | |
tmp_y = m.add_entry("remove stupid title", d) | |
# Decorations | |
# Adding 1 and 2 since tmp_y is the y position of the last entry | |
m.add_decoration("* ", 10, 0, tmp_y+1) | |
m.add_decoration("* ", 10, 0, tmp_y+2) | |
# Offsetting the entries, x=2, y=2, since we finished on the +2 offset in the decorations. | |
# Can be anything though. | |
m.add_entry("this is slanted", None, 2, 2) | |
m.add_entry("more here", None, 2, 2) | |
# Show the menu, this goes to the main event loop, blocking the execution. | |
m.show() | |
# Once done, terminate curses and revert the terminal to default state. | |
m.terminate() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment