home · contact · privacy
3ed1b62772d0e635db5574a7e2f804f0f26b1c51
[plomrogue2] / plomrogue_client / tui.py
1 #!/usr/bin/env python3
2 import curses
3
4
5
6 class AbortOnGetkey(Exception):
7     pass
8
9
10
11 class TUI:
12
13     def __init__(self):
14         self._log = []
15         self.do_refresh = True
16         self.store_widechar = False
17         curses.wrapper(self.run_loop)
18
19     def addstr(self, y, x, line, attr=0):
20         if y < self.size.y - 1 or x + len(line) < self.size.x:
21             self.stdscr.addstr(y, x, line, attr)
22         else:  # workaround to <https://stackoverflow.com/q/7063128>
23             cut_i = self.size.x - x - 1
24             cut = line[:cut_i]
25             last_char = line[cut_i]
26             self.stdscr.addstr(y, self.size.x - 2, last_char, attr)
27             self.stdscr.insstr(y, self.size.x - 2, ' ')
28             self.stdscr.addstr(y, x, cut, attr)
29
30     def reset_size(self):
31         from plomrogue.mapping import YX
32         self.size = YX(*self.stdscr.getmaxyx())
33         self.size = self.size - YX(self.size.y % 4, 0)
34         self.size = self.size - YX(0, self.size.x % 4)
35
36     def log(self, msg):
37         self._log += [msg]
38         self.do_refresh = True
39
40     def init_loop(self):
41         curses.curs_set(0)  # hide cursor
42         self.stdscr.timeout(10)
43         self.reset_size()
44
45     def draw_screen(self):
46         self.stdscr.clear()
47
48     def get_key_and_keycode(self):
49         try:
50             key = self.stdscr.getkey()
51         except curses.error:
52             raise AbortOnGetkey
53         keycode = None
54         if len(key) == 1:
55             keycode = ord(key)
56             # workaround for <https://stackoverflow.com/a/56390915>
57             if self.store_widechar:
58                 self.store_widechar = False
59                 key = bytes([195, keycode]).decode()
60             if keycode == 195:
61                 self.store_widechar = True
62                 raise AbortOnGetkey
63         return key, keycode
64
65     def run_loop(self, stdscr):
66         self.stdscr = stdscr
67         self.init_loop()
68         while True:
69             self.on_each_loop_start()
70             for msg in self.socket.get_message():
71                 self.handle_server_message(msg)
72             if self.do_refresh:
73                 self.draw_screen()
74                 self.do_refresh = False
75             try:
76                 key, keycode = self.get_key_and_keycode()
77             except AbortOnGetkey:
78                 continue
79             self.on_key(key, keycode)
80             self.do_refresh = True
81
82
83
84 def msg_into_lines_of_width(msg, width):
85     chunk = ''
86     lines = []
87     x = 0
88     for i in range(len(msg)):
89         if x >= width or msg[i] == "\n":
90             lines += [chunk]
91             chunk = ''
92             x = 0
93             if msg[i] == "\n":
94                 x -= 1
95         if msg[i] != "\n":
96             chunk += msg[i]
97         x += 1
98     lines += [chunk]
99     return lines