summaryrefslogtreecommitdiff
path: root/interface/chaserview.py
diff options
context:
space:
mode:
Diffstat (limited to 'interface/chaserview.py')
-rwxr-xr-xinterface/chaserview.py140
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)