Add clipping for Buffer draw operations.
--- a/tuikit/core/application.py Wed Mar 19 20:42:52 2014 +0100
+++ b/tuikit/core/application.py Wed Mar 26 09:08:10 2014 +0100
@@ -1,6 +1,7 @@
from tuikit.core.window import WindowManager, Window
from tuikit.core.theme import default_theme
from tuikit.core.timer import Timer
+from tuikit.core.buffer import ProxyBuffer
import logging
@@ -57,8 +58,9 @@
self._started = True
self.window_manager.handle_event('resize', *self.driver.size)
+ screen = ProxyBuffer(self.driver)
while not self._quit:
- self.window_manager.draw(self.driver)
+ self.window_manager.draw(screen)
self.driver.flush()
timeout = self.timer.nearest_timeout()
--- a/tuikit/core/buffer.py Wed Mar 19 20:42:52 2014 +0100
+++ b/tuikit/core/buffer.py Wed Mar 26 09:08:10 2014 +0100
@@ -1,5 +1,5 @@
from tuikit.core.signal import Signal
-from tuikit.core.coords import Size
+from tuikit.core.coords import Size, Rect
from tuikit.core.theme import default_theme
from contextlib import contextmanager
@@ -82,12 +82,50 @@
def attr(self, attr_desc):
"""Set attribute for block of commands, then restore previous attribute."""
original_attr = self.getattr()
- self.setattr(attr_desc)
- yield
- self.setattr(original_attr)
+ try:
+ self.setattr(attr_desc)
+ yield
+ finally:
+ self.setattr(original_attr)
-class Buffer(BufferOperationsMixin):
+class BufferClippingMixin:
+
+ @property
+ def clip_rect(self):
+ try:
+ return self._clip_rect
+ except AttributeError:
+ self.clip_rect = None
+ return self._clip_rect
+
+ @clip_rect.setter
+ def clip_rect(self, value):
+ if value:
+ self._clip_rect = value
+ else:
+ self._clip_rect = Rect(0, 0, self.size.w, self.size.h)
+
+ def testxy(self, x, y):
+ """Test if x/y coords are inside current clipping region."""
+ return (x, y) in self.clip_rect
+
+ @contextmanager
+ def clip(self, x, y, w, h):
+ """Update clipping region as intersection of original and new region.
+
+ Restore original region before returning.
+
+ """
+ original_rect = self.clip_rect
+ try:
+ self.clip_rect = original_rect.intersect(Rect(x, y, w, h))
+ yield
+ finally:
+ self.clip_rect = original_rect
+
+
+class Buffer(BufferOperationsMixin, BufferClippingMixin):
"""Rectangular character buffer.
@@ -118,6 +156,7 @@
def clear(self):
"""Reset buffer data."""
self._data = [Cell() for _i in range(self._size.w * self._size.h)]
+ self.clip_rect = None
def get(self, x, y):
"""Get cell data at `x`, `y` coords.
@@ -144,10 +183,11 @@
def putch(self, x, y, ch):
"""Set character on xy coords to c."""
- self._data[y * self._size.w + x].set(ch, self._current_attr)
+ if self.testxy(x, y):
+ self._data[y * self._size.w + x].set(ch, self._current_attr)
-class ProxyBuffer(BufferOperationsMixin):
+class ProxyBuffer(BufferOperationsMixin, BufferClippingMixin):
"""Special buffer which proxies the operations
to another buffer or buffer-like class."""
@@ -168,6 +208,7 @@
def clear(self):
"""Reset buffer data."""
self.target.clear()
+ self.clip_rect = None
def get(self, x, y):
"""Get cell data at `x`, `y` coords."""
@@ -183,5 +224,6 @@
def putch(self, x, y, ch):
"""Set character on xy coords to c."""
- self.target.putch(x, y, ch)
+ if self.testxy(x, y):
+ self.target.putch(x, y, ch)
--- a/tuikit/core/container.py Wed Mar 19 20:42:52 2014 +0100
+++ b/tuikit/core/container.py Wed Mar 26 09:08:10 2014 +0100
@@ -24,9 +24,10 @@
def draw(self, buffer, x=0, y=0):
"""Draw child widgets."""
for child in self.children:
- child.draw(buffer,
- x + child.x,
- y + child.y)
+ with buffer.clip(x, y, child.width, child.height):
+ child.draw(buffer,
+ x + child.x,
+ y + child.y)
def set_theme(self, theme):
Widget.set_theme(self, theme)
--- a/tuikit/core/coords.py Wed Mar 19 20:42:52 2014 +0100
+++ b/tuikit/core/coords.py Wed Mar 26 09:08:10 2014 +0100
@@ -104,3 +104,43 @@
def __repr__(self):
return 'ReadonlySize(w={0.w},h={0.h})'.format(self._size)
+
+
+class Rect:
+
+ """Rectangle is defined by 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)
+
+ def __contains__(self, point):
+ """Test if point is positioned inside rectangle.
+
+ `point` should be either Point or pair of int values.
+
+ """
+ x, y = point
+ return (self.x <= x < self.x + self.w
+ and self.y <= y < self.y + self.h)
+
+ def intersect(self, other):
+ x1 = max(self.x, other.x)
+ y1 = max(self.y, other.y)
+ x2 = min(self.x + self.w, other.x + other.w)
+ y2 = min(self.y + self.h, other.y + other.h)
+ if x1 >= x2 or y1 >= y2:
+ return Rect()
+ return Rect(x1, y1, x2-x1, y2-y1)
+
+ def union(self, other):
+ x = min(self.x, other.x)
+ y = min(self.y, other.y)
+ w = max(self.x + self.w, other.x + other.w) - x
+ h = max(self.y + self.h, other.y + other.h) - y
+ return Rect(x, y, w, h)
--- a/tuikit/driver/curses.py Wed Mar 19 20:42:52 2014 +0100
+++ b/tuikit/driver/curses.py Wed Mar 26 09:08:10 2014 +0100
@@ -1,5 +1,3 @@
-# -*- coding: utf-8 -*-
-
import curses.ascii
import math
import logging
@@ -118,12 +116,10 @@
## drawing ##
- def erase(self):
+ def clear(self):
self.stdscr.erase()
def putch(self, x, y, ch):
- if not self.clipstack.test(x, y):
- return
try:
if isinstance(ch, str) and len(ch) == 1:
self.stdscr.addstr(y, x, ch)