# HG changeset patch # User Radek Brich # Date 1395821290 -3600 # Node ID de80e140b0ec470128da9d0193a829024d40854d # Parent 781774a8d5684ed9417bb69c10c62c484aba4a20 Add clipping for Buffer draw operations. diff -r 781774a8d568 -r de80e140b0ec tuikit/core/application.py --- 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() diff -r 781774a8d568 -r de80e140b0ec tuikit/core/buffer.py --- 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) diff -r 781774a8d568 -r de80e140b0ec tuikit/core/container.py --- 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) diff -r 781774a8d568 -r de80e140b0ec tuikit/core/coords.py --- 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) diff -r 781774a8d568 -r de80e140b0ec tuikit/driver/curses.py --- 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)