Redraw widgets on request. Add scrollbar demo.
from tuikit.core.coords import Point, Size, Rect
from tuikit.core.theme import default_theme
from tuikit.core.signal import Signal
import logging
class Widget:
"""Base class for all widgets."""
_num_instances = 0
def __init__(self):
self._num_instances += 1
#: Widget name is used for logging etc. Not visible anywhere.
self.name = '%s#%s' % (self.__class__.__name__, self._num_instances)
#: Parent Widget
self.parent = None
#: Window owning this Widget
self.window = None
#: Theme
self.theme = default_theme
#: Timer
self.timer = None
#: Position inside parent widget. Modified by layout manager.
self.pos = Point()
#: Actual size. Modified by layout manager.
self._size = Size(10, 10)
#: Requested size. Layout manager will use this when placing the widget.
self.sizereq = Size(1, 1)
#: Minimal size of widget. Widget will never be sized smaller than this.
self.sizemin = Size(1, 1)
#: Maximum size of widget. Widget will never be sized bigger than this.
#: None means no maximum size (infinite).
self.sizemax = Size(None, None)
#: Cursor is position where text input will occur.
#: The cursor coordinates are relative to widget.
self._cursor = Point()
#: Cursor is displayed on screen only when the widget is focused.
self._cursor_visible = False
#: True if this widget requests redraw
self.need_draw = True
#: Hidden widget does not affect layout.
self.hidden = False
#: Allow keyboard focus for this widget.
self.allow_focus = False
self.sig_keypress = Signal(allow_stop=True)
# -------------------------------------------------------------------------
# Position and size
@property
def x(self):
return self.pos.x
@property
def y(self):
return self.pos.y
def move(self, x, y):
"""Should be called only by layout manager."""
self.pos.update(x, y)
@property
def width(self):
return self.size.w
@property
def height(self):
return self.size.h
@property
def size(self):
return self._size.immutable()
def resize(self, w, h):
"""Should be called only by layout manager."""
self._size.update(w, h)
@property
def boundaries(self):
return Rect(self.pos, self._size)
# -------------------------------------------------------------------------
# Presentation
def draw_if_needed(self, buffer):
if self.need_draw:
self.draw(buffer)
def draw(self, buffer):
"""Draw self into buffer."""
self.log.debug('Draw into %r at %s (exposed %s)',
buffer, buffer.origin, self.exposed(buffer))
self.need_draw = False
def redraw(self):
"""Request redraw."""
self.need_draw = True
def set_theme(self, theme):
self.theme = theme
@staticmethod
def exposed(buffer):
"""Exposed part of widget.
Only this area needs to be drawn.
Returns exposed Rect in widget's local coordinates,
where 0,0 is left top corner of widget to be drawn.
"""
return buffer.clip_rect.moved(-buffer.origin.x, -buffer.origin.y)
@property
def cursor(self):
"""Return cursor coordinates.
Returns None if cursor is set outside of widget boundaries.
"""
if self._cursor in Rect(0, 0, *self._size):
return self._cursor
@property
def cursor_visible(self):
return self._cursor_visible
# -------------------------------------------------------------------------
# Events
def resize_event(self, ev):
self.resize(ev.w, ev.h)
def keypress_event(self, ev):
"""Keypress event handler.
Override to accept keyboard input.
Return True if event was consumed.
Call parent implementation from inherited classes
when not consuming the event.
"""
if self.sig_keypress(ev):
return True
self.log.debug('Not consumed: %s', ev)
def mousedown_event(self, ev):
self.log.debug('Not consumed: %s', ev)
def mouseup_event(self, ev):
self.log.debug('Not consumed: %s', ev)
def mousemove_event(self, ev):
self.log.debug('Not consumed: %s', ev)
# -------------------------------------------------------------------------
# Focus
def can_focus(self):
return not self.hidden and self.allow_focus
def has_focus(self):
if self.parent is None:
return True
return (self.parent.has_focus() and
self.parent.focus_widget == self)
# -------------------------------------------------------------------------
# Utilities
@property
def log(self):
"""Logger for widget debugging.
Logger name contains full module name, class name and instance number.
"""
return logging.getLogger('%s.%s' % (self.__module__, self.name))