Clean up hints - add methods update_hint, hint_value to Widget. Split ScrollView into OffsetView and Scrolling components. Reimplement ScrollWindow using Scrolling component.
# -*- coding: utf-8 -*-
from tuikit.events import Event, Emitter
class Select:
def __init__(self, selected=None):
if not hasattr(self, '_options'):
raise TypeError('Cannot instantiate Select class.')
self._selected = selected or self._options[0]
def update(self, selected):
if selected not in self._options:
raise ValueError('Select: %r not in options %r' % (selected, self._options))
self._selected = selected
def select_next(self):
i = self._options.index(self._selected)
try:
self._selected = self._options[i+1]
except IndexError:
self._selected = self._options[0]
@property
def selected(self):
return self._selected
def get_value(self):
return self._selected
def __repr__(self):
return self.__class__.__name__ + '(%r)' % self._selected
def make_select(*args):
name = ''.join([x.capitalize() for x in args]) + 'Select'
return type(name, (Select,), {'_options': args})
class Coords(Emitter):
'''2D coordinates.'''
def __init__(self, x=0, y=0):
self._x = x
self._y = y
self.add_events('change', Event)
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
self.emit('change')
@property
def y(self):
return self._y
@y.setter
def y(self, value):
self._y = value
self.emit('change')
def __getitem__(self, key):
try:
tupl = (self.x, self.y)
return tupl[key]
except TypeError:
pass
return self.__dict__[key]
def __setitem__(self, key, value):
if key == 0:
self.x = value
if key == 1:
self.y = value
def __repr__(self):
return 'Coords(x={0.x},y={0.y})'.format(self)
def update(self, x=None, y=None):
old_x, old_y = self._x, self._y
if isinstance(x, Coords) and y is None:
self._x, self._y = x
else:
if isinstance(x, int):
self._x = x
elif x is not None:
raise ValueError('Coords.update(): first parameter must be int or Coords')
if isinstance(y, int):
self._y = y
elif y is not None:
raise ValueError('Coords.update(): second parameter must be int')
if self._x != old_x or self._y != old_y:
self.emit('change')
class Size(Emitter):
'''Size class.
Implements attribute access (.w, .h), list-like access([0],[1])
and dict-like access (['w'],['h']).
'''
def __init__(self, w=None, h=None):
self._w = w
self._h = h
self.add_events('change', Event)
@property
def w(self):
return self._w
@w.setter
def w(self, value):
self._w = value
self.emit('change')
@property
def h(self):
return self._h
@h.setter
def h(self, value):
self._h = value
self.emit('change')
def __getitem__(self, key):
try:
tupl = (self.w, self.h)
return tupl[key]
except TypeError:
pass
return self.__dict__[key]
def __setitem__(self, key, value):
if key in [0, 'w']:
self.w = value
if key in [1, 'h']:
self.h = value
def __repr__(self):
return 'Size(w={0._w},h={0._h})'.format(self)
def update(self, w=None, h=None):
old_w, old_h = self._w, self._h
if isinstance(w, Size) and h is None:
self._w, self._h = w
else:
if isinstance(w, int):
self._w = w
elif w is not None:
raise ValueError('Size.update(): first parameter must be int or Size')
if isinstance(h, int):
self._h = h
elif h is not None:
raise ValueError('Size.update(): second parameter must be int')
if self._w != old_w or self._h != old_h:
self.emit('change')
class Rect:
'''Rectangle is defined by 2D coordinates and size.'''
def __init__(self, x=0, y=0, w=0, h=0):
self.x = x
self.y = y
self.w = w
self.h = h
def __repr__(self):
return 'Rect(x={0.x},y={0.y},w={0.w},h={0.h})'.format(self)
class Borders:
'''Borders are defined by left, top, right, bottom border size.
Ordering is clock-wise, starting with left. This may seem weird,
but it corresponds to X/Y or W/H used elsewhere. Left and right are
on X axis, so they are defined first.
'''
def __init__(self, l=0, t=0, r=0, b=0, full=None):
self.l = l # left
self.t = t # top
self.r = r # right
self.b = b # bottom
if full is not None:
self.l = self.t = self.r = self.b = full
def __getitem__(self, key):
try:
tupl = (self.l, self.t, self.r, self.b)
return tupl[key]
except TypeError:
pass
return self.__dict__[key]
def __repr__(self):
return 'Borders(l={0.l},t={0.t},r={0.r},b={0.b})'.format(self)
def update(self, *args, **kwargs):
if len(args) == 4:
self.l, self.t, self.r, self.b = args
elif len(args) == 1 and isinstance(args[0], Borders):
self.l, self.t, self.r, self.b = args[0]
elif len(args):
raise ValueError('Borders.update() takes exactly 4 positional arguments.')
for arg in kwargs:
setattr(self, arg, kwargs[arg])
class ClipStack:
'''Stack of clipping regions.'''
def __init__(self):
self.stack = []
def push(self, x, y, w, h):
newclip = Rect(x, y, w, h)
if len(self.stack):
oldclip = self.top()
newclip = self.intersect(oldclip, newclip)
self.stack.append(newclip)
def pop(self):
self.stack.pop()
def top(self):
return self.stack[-1]
def test(self, x, y):
# no clip rectangle on stack => passed
if not len(self.stack):
return True
# test against top clip rect from stack
clip = self.top()
if x < clip.x or y < clip.y \
or x >= clip.x + clip.w or y >= clip.y + clip.h:
return False
# passed
return True
def intersect(self, r1, r2):
x1 = max(r1.x, r2.x)
y1 = max(r1.y, r2.y)
x2 = min(r1.x + r1.w, r2.x + r2.w)
y2 = min(r1.y + r1.h, r2.y + r2.h)
if x1 >= x2 or y1 >= y2:
return Rect()
return Rect(x1, y1, x2-x1, y2-y1)
def union(self, r1, r2):
x = min(r1.x, r2.x)
y = min(r1.y, r2.y)
w = max(r1.x + r1.w, r2.x + r2.w) - x
h = max(r1.y + r1.h, r2.y + r2.h) - y
return Rect(x, y, w, h)
class UnicodeGraphics:
'''Unicode graphics bank.
This class can be overriden to change graphics style (round corners etc.).'''
# http://en.wikipedia.org/wiki/List_of_Unicode_characters#Geometric_shapes
UP_ARROW = '▲' #curses.ACS_UARROW
DOWN_ARROW = '▼' #curses.ACS_DARROW
LEFT_ARROW = '◀'
RIGHT_ARROW = '▶'
CIRCLE = '●'
DIAMOND = '◆'
MIDDLE_DOT = '·'
# http://en.wikipedia.org/wiki/Box-drawing_characters
LIGHT_SHADE = '░' #curses.ACS_BOARD
MEDIUM_SHADE = '▒'
DARK_SHADE = '▓'
BLOCK = '█'
COLUMN = '▁▂▃▄▅▆▇█'
CORNER_ROUND = '╭╮╰╯'
CORNER = '┌┐└┘'
LINE = '─━│┃┄┅┆┇┈┉┊┋'
HLINE = '─' # curses.ACS_HLINE
VLINE = '│' # curses.ACS_VLINE
ULCORNER = '┌' # curses.ACS_ULCORNER
URCORNER = '┐' # curses.ACS_URCORNER
LLCORNER = '└' # curses.ACS_LLCORNER
LRCORNER = '┘' # curses.ACS_LRCORNER
LTEE = '├'
RTEE = '┤'
char_map = {
# scrollbar
'sb_thumb' : CIRCLE,
'sb_htrack' : LINE[8],
'sb_vtrack' : LINE[10],
'sb_left' : LEFT_ARROW,
'sb_right' : RIGHT_ARROW,
'sb_up' : UP_ARROW,
'sb_down' : DOWN_ARROW,
}
def get_char(self, name):
return self.char_map[name]