diff options
Diffstat (limited to 'interface/chaserview.py')
-rwxr-xr-x | interface/chaserview.py | 140 |
1 files changed, 140 insertions, 0 deletions
diff --git a/interface/chaserview.py b/interface/chaserview.py new file mode 100755 index 0000000..2c88343 --- /dev/null +++ b/interface/chaserview.py @@ -0,0 +1,140 @@ +#!/usr/bin/env python3 + +import curses +import math +import threading + +import blc2 +from blc2.constants import INFTY + +CURSES_LOCK = threading.RLock() + +def format_time(n): + if n == INFTY: + return "∞s" + elif n == 0: + return "0s" + postfixes = "mcisahkegtp" + idx = 0 + multiple = 10 + while n >= multiple: + idx += 1 + multiple *= 10 + + return str(n)[0]+postfixes[idx] + +class ChaserView: + def set_dim(self, height, width): + if height < 5 or width < 10: + raise ValueError("Size too small") + + with self._lock: + if (height, width) != (self._height, self._width): + self.win.erase() + self.win.noutrefresh() + + self.win.resize(height, width) + self.win.redrawwin() + + self._height = height + self._width = width + self._redraw() + + @staticmethod + def fit(s, l, pad=False): + ## TODO: Try shortening by words first? + if len(s) > l: + return s[:l-1] + '…' + elif len(s) < l and pad: + return s + ' '*(l-len(s)) + return s + + def set_pos(self, y, x): + with self._lock: + if (y, x) != (self._y, self._x): + self.win.mvwin(y, x) + self._y = y + self._x = x + self.win.noutrefresh() + + def _redraw(self): + self.win.erase() + self.win.border() + self.win.hline(2, 1, curses.ACS_HLINE, self._width-2) + self.win.addch(2, 0, curses.ACS_LTEE) + self.win.addch(2, self._width-1, curses.ACS_RTEE) + + if self._chaser is None: + self.win.refresh() + return + + c = self._chaser + w = self._width - 2 + self.win.addstr(1, 1, self.fit(("%d: "% c.id) + c.name, w-2)) + + maxsteps = self._height - 4 + if maxsteps < len(c.steps): + if self._selected is None: + steps = c.steps[:maxsteps] + else: + last = min(self._selected + (maxsteps // 2), len(c.steps)) + first = last - maxsteps + if first < 0: + last -= first + first = 0 + else: + first = 0 + last = len(c.steps) + steps = c.steps[first:last] + for n, s in enumerate(steps, 1): + if s.index == self._selected: + attrs = curses.A_REVERSE + else: + attrs = 0 + t = "%s:%s:%s" % (format_time(s.fade_in), format_time(s.duration), format_time(s.fade_out)) + self.win.addstr(n+2, 1, self.fit((self._numformat % (s.index+1)) + ": " + s.name, w-8, pad=True)+t, attrs) + + if first > 0: + self.win.addch(3, self._width//2, '⯅') + if last < len(c.steps): + self.win.addch(self._height-1, self._width//2, '⯆') + + self.win.refresh() + + @property + def selected(self): + with self._lock: + return self._selected + + @selected.setter + def selected(self, value): + with self._lock: + if value != self._selected: + self._selected = value + ## TODO: Clean this up if possible? + self._redraw() + + def set_chaser(self, chaser, selected=None): + with self._lock: + self._chaser = chaser + self._selected = selected + if chaser is not None and chaser.steps: + self._numformat = "%%%dd" % math.ceil(math.log10(len(chaser.steps))) + self._redraw() + + def __init__(self, y, x, height, width): + with CURSES_LOCK: + self.win = curses.newwin(height, width, y, x) + self.win.keypad(True) + self._lock = threading.RLock() + self._height = height + self._width = width + self._y = -1 + self._x = -1 + + self._chaser = None + self._numformat = "" + self._selected = -1 + + self.set_pos(y, x) + self.set_dim(height, width) |