Drop AnchorLayout, merge its features into FixedLayout.
from tuikit.core.widget import Widget
from tuikit.core.coords import Point, Rect
from tuikit.layouts.fixed import FixedLayout
class Container(Widget):
"""Container widget.
Can contain other widgets to create hierarchical structure.
"""
def __init__(self, layout_class=FixedLayout):
Widget.__init__(self)
#: List of child widgets.
self._widgets = []
#: Widget with keyboard focus
self.focus_widget = None
#: Widget on last mouse position
self.mouse_widget = None
#: If True, tab cycles inside container
self.trap_focus = False
self.layout = layout_class()
def add(self, widget, *args, **kwargs):
"""Add widget into container."""
self._widgets.append(widget)
widget.parent = self
widget.window = self.window
widget.set_theme(self.theme)
self.layout.add(widget, *args, **kwargs)
if self.focus_widget is None and widget.can_focus():
self.focus_widget = widget
def resize(self, w, h):
Widget.resize(self, w, h)
self.layout.update(w, h)
def draw(self, buffer):
"""Draw child widgets."""
Widget.draw(self, buffer)
for child in self._widgets:
with buffer.moved_origin(child.x, child.y):
with buffer.clip(buffer.origin.x, buffer.origin.y,
child.width, child.height):
child.draw(buffer)
def set_theme(self, theme):
Widget.set_theme(self, theme)
for child in self._widgets:
child.set_theme(theme)
@property
def cursor(self):
"""Return cursor coordinates or None if cursor is not set
or is set outside of widget boundaries.
If this container has child with focus, return its cursor position instead.
"""
if self.focus_widget:
cursor = self.focus_widget.cursor
if not cursor:
return None
cursor = cursor.moved(*self.focus_widget.pos)
else:
cursor = self._cursor.immutable()
if cursor in Rect._make((0, 0), self._size):
return cursor
@property
def cursor_visible(self):
if self.focus_widget:
return self.focus_widget.cursor_visible
else:
return self._cursor_visible
## input events ##
def keypress_event(self, ev):
# First, handle the keypress event to focused child widget
if self.focus_widget is not None:
if self.focus_widget.keypress_event(ev):
return True
# Next, handle default key behaviour by Container
if ev.keyname == 'tab':
return self.focus_next(-1 if 'shift' in ev.mods else 1)
# Finally, handle default keys by Widget
# and send keypress signal
if Widget.keypress_event(self, ev):
return True
def mousedown(self, button, pos):
self.mouse_child = None
for child in reversed(self.children):
if pos in child.boundaries:
child.mousedown(button, pos - child.pos)
self.mouse_child = child
def mouseup(self, button, pos):
if self.mouse_child:
self.mouse_child.mouseup(button, pos - self.mouse_child.pos)
def mousemove(self, button, pos, relpos):
if self.mouse_child:
self.mouse_child.mousemove(button,
pos - self.mouse_child.pos, relpos)
## focus ##
def focus_next(self, step=1):
"""Focus next child.
Sets focus to next child, if there is one
which can be focused. Cycles from last child
to first when needed. Return value depends on
this cycling:
* False means there wasn't any child to focus
before end of list. Focus was either not changed
or first child was focused.
* True when focus is set to next child in normal
way or when self.trap_focus is set.
Return value is supposed to be returned from keypress
event - in that case, True stops event propagation.
"""
if self.focus_widget is None:
idx_current = 0
else:
idx_current = self._widgets.index(self.focus_widget)
idx_new = idx_current
cycled = False
while True:
idx_new += step
if idx_new >= len(self._widgets):
idx_new = 0
cycled = True
if idx_new < 0: # for focus_previous
idx_new = len(self._widgets) - 1
cycled = True
if idx_current == idx_new:
return False
if self._widgets[idx_new].can_focus():
self.focus_widget = self._widgets[idx_new]
return self.trap_focus or not cycled
def focus_previous(self):
"""Focus previous child."""
self.focus_next(-1)