home · contact · privacy
More TUI client refactoring.
[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 get_key_and_keycode(self):
46         try:
47             key = self.stdscr.getkey()
48         except curses.error:
49             raise AbortOnGetkey
50         keycode = None
51         if len(key) == 1:
52             keycode = ord(key)
53             # workaround for <https://stackoverflow.com/a/56390915>
54             if self.store_widechar:
55                 self.store_widechar = False
56                 key = bytes([195, keycode]).decode()
57             if keycode == 195:
58                 self.store_widechar = True
59                 raise AbortOnGetkey
60         return key, keycode
61
62     def run_loop(self, stdscr):
63         self.stdscr = stdscr
64         self.init_loop()
65         while True:
66             self.on_each_loop_start()
67             for msg in self.socket.get_message():
68                 self.handle_server_message(msg)
69             if self.do_refresh:
70                 self.stdscr.clear()
71                 self.draw_screen()
72                 self.do_refresh = False
73             try:
74                 key, keycode = self.get_key_and_keycode()
75             except AbortOnGetkey:
76                 continue
77             self.on_key(key, keycode)
78             self.do_refresh = True
79
80
81
82 def msg_into_lines_of_width(msg, width):
83     chunk = ''
84     lines = []
85     x = 0
86     for i in range(len(msg)):
87         if x >= width or msg[i] == "\n":
88             lines += [chunk]
89             chunk = ''
90             x = 0
91             if msg[i] == "\n":
92                 x -= 1
93         if msg[i] != "\n":
94             chunk += msg[i]
95         x += 1
96     lines += [chunk]
97     return lines