code / btui

Lines2.2K C1.2K Python549 Markdown219 make156 Lua49
(344 lines)
1 import ctypes
2 import enum
3 import functools
4 import time
5 import os.path
6 from contextlib import contextmanager
8 __all__ = ['open', 'TextAttr', 'ClearType', 'CursorType', 'BTUIMode']
10 # Load the shared library into c types.
11 libbtui = ctypes.CDLL(os.path.dirname(os.path.abspath(__file__)) + os.path.sep + "libbtui.so")
13 class FILE(ctypes.Structure):
14 pass
16 class BTUI_struct(ctypes.Structure):
17 _fields_ = [
18 ('in', ctypes.POINTER(FILE)),
19 ('out', ctypes.POINTER(FILE)),
20 ('width', ctypes.c_int),
21 ('height', ctypes.c_int),
22 ('size_changed', ctypes.c_int),
25 libbtui.btui_create.restype = ctypes.POINTER(BTUI_struct)
27 attr = lambda name: ctypes.c_longlong.in_dll(libbtui, name).value
28 attr_t = ctypes.c_longlong
30 class TextAttr(enum.IntEnum):
31 NORMAL = attr('BTUI_NORMAL')
32 BOLD = attr('BTUI_BOLD')
33 FAINT = attr('BTUI_FAINT')
34 DIM = attr('BTUI_FAINT')
35 ITALIC = attr('BTUI_ITALIC')
36 UNDERLINE = attr('BTUI_UNDERLINE')
37 BLINK_SLOW = attr('BTUI_BLINK_SLOW')
38 BLINK_FAST = attr('BTUI_BLINK_FAST')
39 REVERSE = attr('BTUI_REVERSE')
40 CONCEAL = attr('BTUI_CONCEAL')
41 STRIKETHROUGH = attr('BTUI_STRIKETHROUGH')
42 FRAKTUR = attr('BTUI_FRAKTUR')
43 DOUBLE_UNDERLINE = attr('BTUI_DOUBLE_UNDERLINE')
44 NO_BOLD_OR_FAINT = attr('BTUI_NO_BOLD_OR_FAINT')
45 NO_ITALIC_OR_FRAKTUR = attr('BTUI_NO_ITALIC_OR_FRAKTUR')
46 NO_UNDERLINE = attr('BTUI_NO_UNDERLINE')
47 NO_BLINK = attr('BTUI_NO_BLINK')
48 NO_REVERSE = attr('BTUI_NO_REVERSE')
49 NO_CONCEAL = attr('BTUI_NO_CONCEAL')
50 NO_STRIKETHROUGH = attr('BTUI_NO_STRIKETHROUGH')
51 FG_BLACK = attr('BTUI_FG_BLACK')
52 FG_RED = attr('BTUI_FG_RED')
53 FG_GREEN = attr('BTUI_FG_GREEN')
54 FG_YELLOW = attr('BTUI_FG_YELLOW')
55 FG_BLUE = attr('BTUI_FG_BLUE')
56 FG_MAGENTA = attr('BTUI_FG_MAGENTA')
57 FG_CYAN = attr('BTUI_FG_CYAN')
58 FG_WHITE = attr('BTUI_FG_WHITE')
59 FG_NORMAL = attr('BTUI_FG_NORMAL')
60 BG_BLACK = attr('BTUI_BG_BLACK')
61 BG_RED = attr('BTUI_BG_RED')
62 BG_GREEN = attr('BTUI_BG_GREEN')
63 BG_YELLOW = attr('BTUI_BG_YELLOW')
64 BG_BLUE = attr('BTUI_BG_BLUE')
65 BG_MAGENTA = attr('BTUI_BG_MAGENTA')
66 BG_CYAN = attr('BTUI_BG_CYAN')
67 BG_WHITE = attr('BTUI_BG_WHITE')
68 BG_NORMAL = attr('BTUI_BG_NORMAL')
69 FRAMED = attr('BTUI_FRAMED')
70 ENCIRCLED = attr('BTUI_ENCIRCLED')
71 OVERLINED = attr('BTUI_OVERLINED')
72 NO_FRAMED_OR_ENCIRCLED = attr('BTUI_NO_FRAMED_OR_ENCIRCLED')
73 NO_OVERLINED = attr('BTUI_NO_OVERLINED')
75 BTUI_INVERSE_ATTRS = {
76 TextAttr.NORMAL : TextAttr.NORMAL,
77 TextAttr.BOLD : TextAttr.NO_BOLD_OR_FAINT,
78 TextAttr.FAINT : TextAttr.NO_BOLD_OR_FAINT,
79 TextAttr.DIM : TextAttr.NO_BOLD_OR_FAINT,
80 TextAttr.ITALIC : TextAttr.NO_ITALIC_OR_FRAKTUR,
81 TextAttr.UNDERLINE : TextAttr.NO_UNDERLINE,
82 TextAttr.BLINK_SLOW : TextAttr.NO_BLINK,
83 TextAttr.BLINK_FAST : TextAttr.NO_BLINK,
84 TextAttr.REVERSE : TextAttr.NO_REVERSE,
85 TextAttr.CONCEAL : TextAttr.NO_CONCEAL,
86 TextAttr.STRIKETHROUGH : TextAttr.NO_STRIKETHROUGH,
87 TextAttr.FRAKTUR : TextAttr.NO_ITALIC_OR_FRAKTUR,
88 TextAttr.DOUBLE_UNDERLINE: TextAttr.NO_UNDERLINE,
89 TextAttr.FG_BLACK : TextAttr.FG_NORMAL,
90 TextAttr.FG_RED : TextAttr.FG_NORMAL,
91 TextAttr.FG_GREEN : TextAttr.FG_NORMAL,
92 TextAttr.FG_YELLOW : TextAttr.FG_NORMAL,
93 TextAttr.FG_BLUE : TextAttr.FG_NORMAL,
94 TextAttr.FG_MAGENTA : TextAttr.FG_NORMAL,
95 TextAttr.FG_CYAN : TextAttr.FG_NORMAL,
96 TextAttr.FG_WHITE : TextAttr.FG_NORMAL,
97 TextAttr.FG_NORMAL : TextAttr.FG_NORMAL,
98 TextAttr.BG_BLACK : TextAttr.BG_NORMAL,
99 TextAttr.BG_RED : TextAttr.BG_NORMAL,
100 TextAttr.BG_GREEN : TextAttr.BG_NORMAL,
101 TextAttr.BG_YELLOW : TextAttr.BG_NORMAL,
102 TextAttr.BG_BLUE : TextAttr.BG_NORMAL,
103 TextAttr.BG_MAGENTA : TextAttr.BG_NORMAL,
104 TextAttr.BG_CYAN : TextAttr.BG_NORMAL,
105 TextAttr.BG_WHITE : TextAttr.BG_NORMAL,
106 TextAttr.BG_NORMAL : TextAttr.BG_NORMAL,
107 TextAttr.FRAMED : TextAttr.NO_FRAMED_OR_ENCIRCLED,
108 TextAttr.ENCIRCLED : TextAttr.NO_FRAMED_OR_ENCIRCLED,
109 TextAttr.OVERLINED : TextAttr.NO_OVERLINED,
112 class BTUIMode(enum.IntEnum):
113 UNINITIALIZED = 0
114 NORMAL = 1
115 TUI = 2
117 class CursorType(enum.IntEnum):
118 DEFAULT = 0
119 BLINKING_BLOCK = 1
120 BLOCK = 2
121 BLINKING_UNDERLINE = 3
122 UNDERLINE = 4
123 BLINKING_BAR = 5
124 BAR = 6
126 class ClearType(enum.IntEnum):
127 SCREEN = 0
128 ABOVE = 1
129 BELOW = 2
130 LINE = 3
131 LEFT = 4
132 RIGHT = 5
134 class BTUI:
135 _autoflush = True
137 @contextmanager
138 def attributes(self, *attrs):
139 self.set_attributes(*attrs)
140 try: yield
141 finally: self.unset_attributes(*attrs)
143 @contextmanager
144 def bg(self, r, g, b):
145 self.set_bg(r, g, b)
146 try: yield
147 finally: self.set_attributes("bg_normal")
149 def clear(self, clear_type=ClearType.SCREEN):
150 assert self._btui
151 if isinstance(clear_type, str):
152 clear_type = ClearType[clear_type.upper()]
153 libbtui.btui_clear(self._btui, clear_type)
154 if self._autoflush:
155 libbtui.btui_flush(self._btui)
157 def disable(self):
158 libbtui.btui_disable(self._btui)
160 @contextmanager
161 def disabled(self):
162 self.disable()
163 try: yield self
164 finally: self.enable()
166 def draw_shadow(self, x, y, w, h):
167 assert self._btui
168 libbtui.btui_draw_shadow(self._btui, int(x), int(y), int(w), int(h))
169 libbtui.btui_flush(self._btui)
171 def enable(self, mode=BTUIMode.TUI):
172 if isinstance(mode, str):
173 mode = BTUIMode[mode.upper()]
174 self._btui = libbtui.btui_create(mode)
176 def set_mode(self, mode):
177 if isinstance(mode, str):
178 mode = BTUIMode[mode.upper()]
179 self._btui = libbtui.set_mode(self._btui, mode)
181 @contextmanager
182 def fg(self, r, g, b):
183 self.set_fg(r, g, b)
184 try: yield
185 finally: self.set_attributes("fg_normal")
187 def fill_box(self, x, y, w, h):
188 assert self._btui
189 libbtui.btui_fill_box(self._btui, int(x), int(y), int(w), int(h))
190 if self._autoflush:
191 libbtui.btui_flush(self._btui)
193 def flush(self):
194 assert self._btui
195 libbtui.btui_flush(self._btui)
197 @contextmanager
198 def buffered(self):
199 assert self._btui
200 prev_autoflush = self._autoflush
201 self._autoflush = False
202 yield
203 self._autoflush = prev_autoflush
204 self.flush()
206 def getkey(self, timeout=None):
207 assert self._btui
208 timeout = -1 if timeout is None else int(timeout)
209 mouse_x, mouse_y = ctypes.c_int(-1), ctypes.c_int(-1)
210 key = libbtui.btui_getkey(self._btui, timeout,
211 ctypes.byref(mouse_x), ctypes.byref(mouse_y))
212 buf = ctypes.create_string_buffer(64)
213 libbtui.btui_keyname(key, buf)
214 key = buf.value.decode('utf8')
215 if key == "<none>": key = None
216 if mouse_x.value == -1:
217 return key, None, None
218 else:
219 return key, mouse_x.value, mouse_y.value
221 @property
222 def height(self):
223 assert self._btui
224 return self._btui.contents.height
226 def move(self, x, y):
227 assert self._btui
228 libbtui.btui_move_cursor(self._btui, int(x), int(y))
229 if self._autoflush:
230 libbtui.btui_flush(self._btui)
232 def set_cursor(self, cursor_type=CursorType.DEFAULT):
233 assert self._btui
234 if isinstance(cursor_type, str):
235 cursor_type = CursorType[cursor_type.upper()]
236 libbtui.btui_set_cursor(self._btui, cursor_type)
237 if self._autoflush:
238 libbtui.btui_flush(self._btui)
240 def hide_cursor(self):
241 assert self._btui
242 libbtui.btui_hide_cursor(self._btui)
243 if self._autoflush:
244 libbtui.btui_flush(self._btui)
246 def show_cursor(self):
247 assert self._btui
248 libbtui.btui_show_cursor(self._btui)
249 if self._autoflush:
250 libbtui.btui_flush(self._btui)
252 def outline_box(self, x, y, w, h):
253 assert self._btui
254 libbtui.btui_draw_linebox(self._btui, int(x), int(y), int(w), int(h))
255 if self._autoflush:
256 libbtui.btui_flush(self._btui)
258 def scroll(self, firstline, lastline=None, amount=None):
259 assert self._btui
260 if amount is None:
261 amount = firstline
262 firstline, lastline = 0, self.height-1
263 libbtui.btui_scroll(self._btui, firstline, lastline, amount)
264 if self._autoflush:
265 libbtui.btui_flush(self._btui)
267 def set_attributes(self, *attrs):
268 assert self._btui
269 attr_long = ctypes.c_longlong(0)
270 for a in attrs:
271 if isinstance(a, str):
272 a = TextAttr[a.upper()]
273 attr_long.value |= a
274 libbtui.btui_set_attributes(self._btui, attr_long)
276 def set_bg(self, r, g, b):
277 assert self._btui
278 libbtui.btui_set_bg(self._btui, int(r*255), int(g*255), int(b*255))
280 def set_fg(self, r, g, b):
281 assert self._btui
282 libbtui.btui_set_fg(self._btui, int(r*255), int(g*255), int(b*255))
284 def suspend(self):
285 assert self._btui
286 libbtui.btui_suspend(self._btui)
288 def unset_attributes(self, *attrs):
289 assert self._btui
290 attr_long = ctypes.c_longlong(0)
291 for a in attrs:
292 if isinstance(a, str):
293 a = TextAttr[a.upper()]
294 attr_long.value |= BTUI_INVERSE_ATTRS[a]
295 libbtui.btui_set_attributes(self._btui, attr_long)
297 @property
298 def width(self):
299 assert self._btui
300 return self._btui.contents.width
302 def write(self, *args, sep=''):
303 assert self._btui
304 s = sep.join(args)
305 self.write_bytes(bytes(s, 'utf8'))
307 def write_bytes(self, b):
308 assert self._btui
309 libbtui.btui_puts(self._btui, b)
310 if self._autoflush:
311 libbtui.btui_flush(self._btui)
313 def delay(fn):
314 @functools.wraps(fn)
315 def wrapped(self, *a, **k):
316 assert self._btui
317 ret = fn(self, *a, **k)
318 time.sleep(self.delay)
319 libbtui.btui_show_cursor(self._btui)
320 return ret
321 return wrapped
323 class DebugBTUI(BTUI):
324 delay = 0.05
326 for fn_name in ('clear', 'draw_shadow', 'fill_box', 'move', 'set_cursor', 'hide_cursor', 'show_cursor', 'outline_box',
327 'scroll', 'set_attributes', 'set_bg', 'set_fg', 'unset_attributes', 'write_bytes'):
328 setattr(DebugBTUI, fn_name, delay(getattr(BTUI, fn_name)))
330 _btui = None
331 @contextmanager
332 def open(*, debug=False, delay=0.05, mode=BTUIMode.TUI):
333 global _btui
334 if not _btui:
335 if debug:
336 _btui = DebugBTUI()
337 _btui.delay = delay
338 else:
339 _btui = BTUI()
340 _btui.enable(mode=mode)
341 _btui.move(0, 0)
342 try: yield _btui
343 finally: _btui.disable()