class BezierPath(object): '''Create a line based on bezier equation, or a shape using GLU tess. :Parameters: `filled` : boolean, default to False Create a filled bezier shape `path` : list, default to None Create a path directly from points. See up description for more information about the format used in path. ''' def __init__(self, **kwargs): kwargs.setdefault('filled', False) kwargs.setdefault('path', None) self._tess = None self._filled_path = None self._dl = GlDisplayList() self._path = [] self._bezier_coefficients = [] self._bezier_points = 10 self._contructing = False super(BezierPath, self).__init__() self.filled = kwargs.get('filled') self.x, self.y = 0, 0 if kwargs.get('path'): self.calculate_from_bezier_path(kwargs.get('path')) def path_begin(self, x, y): '''Start a new bezier path''' self._path = [x, y] self.x, self.y = x, y def path_end(self): '''End the current bezier path''' self._path += self._path[0:2] self.reset() def path_curve_to(self, x1, y1, x2, y2, x, y): '''Add a control point into bezier path''' if not self._bezier_coefficients: for i in xrange(self._bezier_points + 1): t = float(i) / self._bezier_points t0 = (1 - t) ** 3 t1 = 3 * t * (1 - t) ** 2 t2 = 3 * t ** 2 * (1 - t) t3 = t ** 3 self._bezier_coefficients.append([t0, t1, t2, t3]) for i, t in enumerate(self._bezier_coefficients): px = t[0] * self.x + t[1] * x1 + t[2] * x2 + t[3] * x py = t[0] * self.y + t[1] * y1 + t[2] * y2 + t[3] * y self._path += px, py self.x, self.y = px, py self.reset() def calculate_from_bezier_path(self, points): '''Create a new path from a list of control points''' self.path_begin(points[0], points[1]) for i in xrange(2, len(points), 6): x1, y1, x2, y2, x, y = points[i:i+6] self.path_curve_to(x1, y1, x2, y2, x, y) self.path_end() self.reset() def draw_filled_path(self): for style, points in self.filled_path: with gx_begin(style): for x, y in zip(points[::2], points[1::2]): glVertex2f(x, y) def draw(self): '''Draw the path on screen (filled or line)''' if not self._dl.is_compiled(): with self._dl: if self.filled: self.draw_filled_path() else: drawLine(self.path) self._dl.draw() def reset(self): '''Reset the display list cache''' self._dl.clear() def _get_path(self): return self._path path = property(_get_path, doc='''Return the calculated path in format (x,y,x,y...)''') def _get_filled_path(self): if self._filled_path: return self._filled_path self._tess = gluNewTess() gluTessNormal(self._tess, 0, 0, 1) gluTessProperty(self._tess, GLU_TESS_WINDING_RULE, GLU_TESS_WINDING_NONZERO) tess_list = [] def tess_vertex(vertex): self._tess_shape += list(vertex[0:2]) def tess_begin(which): self._tess_style = which self._tess_shape = [] def tess_end(): tess_list.append((self._tess_style, self._tess_shape)) def tess_error(code): err = gluErrorString(code) pymt_logger.warning('BezierPath: GLU Tesselation Error: %s' % str(err)) gluTessCallback(self._tess, GLU_TESS_VERTEX, tess_vertex) gluTessCallback(self._tess, GLU_TESS_BEGIN, tess_begin) gluTessCallback(self._tess, GLU_TESS_END, tess_end) gluTessCallback(self._tess, GLU_TESS_ERROR, tess_error) gluTessBeginPolygon(self._tess, None) gluTessBeginContour(self._tess) for x, y in zip(self._path[::2], self._path[1::2]): v_data = (x, y, 0) gluTessVertex(self._tess, v_data, v_data) gluTessEndContour(self._tess) gluTessEndPolygon(self._tess) self._filled_path = tess_list return tess_list filled_path = property(_get_filled_path, doc='''Return the filled shape in format ((gl style, (x,y,x,y...)),...)''')
def drawCSSRectangle(pos=(0, 0), size=(100, 100), style=dict(), prefix=None, state=None): '''Draw a rectangle with CSS :Parameters: `state`: if a certain state string is passed, we will use styles with this postifx instead. for example: style[bg-color] and style[bg-color-down] are both set. if state == "down", we wil use bg-color-down instead of bg-color :Styles: * alpha-background (color) * border-radius (float) * border-radius-precision (float) * border-width (float) * draw-alpha-background (bool) * draw-background (bool) * draw-border (bool) ''' bg_image = style.get('bg-image-'+str(state)) if not bg_image: bg_image = style.get('bg-image') # Check if we have a cached version cache_id = '%s:%s:%s:%s:%s' % (pos, size, style, prefix, state) cache = Cache.get('pymt.cssrect', cache_id) if cache: cache.draw() if bg_image: bg_image.size = size bg_image.pos = pos bg_image.draw() return # lets use the ones for given state, # and ignore the regular ones if the state ones are there if state: state = "-" + state newstyle = {} overwrites = [] for s in style: if state in s: overwrite = s.replace(state, '') newstyle[overwrite] = style[s] overwrites.append(overwrite) if s not in overwrites: newstyle[s] = style[s] style = newstyle # hack to remove prefix in style if prefix is not None: prefix += '-' newstyle = {} for k in style: newstyle[k] = style[k] for k in style: if prefix in k: newstyle[k.replace(prefix, '')] = style[k] style = newstyle style.setdefault('border-width', 1.5) style.setdefault('border-radius', 0) style.setdefault('border-radius-precision', .1) style.setdefault('draw-border', 0) style.setdefault('draw-background', 1) style.setdefault('draw-alpha-background', 0) style.setdefault('alpha-background', (1, 1, .5, .5)) k = { 'pos': pos, 'size': size } new_cache = GlDisplayList() with new_cache: if state: set_color(*style['bg-color']) #hack becasue old widgets set this themselves linewidth = style.get('border-width') bordercolor = None if 'border-color' in style: bordercolor = style['border-color'] if style['border-radius'] > 0: k.update({ 'radius': style['border-radius'], 'precision': style['border-radius-precision'] }) if style['draw-background']: drawRoundedRectangle(**k) if style['draw-border']: if linewidth: glPushAttrib(GL_LINE_BIT) glLineWidth(linewidth) if bordercolor: with gx_color(*bordercolor): drawRoundedRectangle(style=GL_LINE_LOOP, **k) else: drawRoundedRectangle(style=GL_LINE_LOOP, **k) if linewidth: glPopAttrib() if style['draw-alpha-background']: drawRoundedRectangleAlpha(alpha=style['alpha-background'], **k) else: if style['draw-background']: drawRectangle(**k) if style['draw-border']: if linewidth: glPushAttrib(GL_LINE_BIT) glLineWidth(linewidth) if bordercolor: with gx_color(*bordercolor): drawRectangle(style=GL_LINE_LOOP, **k) else: drawRectangle(style=GL_LINE_LOOP, **k) if linewidth: glPopAttrib() if style['draw-alpha-background']: drawRectangleAlpha(alpha=style['alpha-background'], **k) # if the drawCSSRectangle is already inside a display list # compilation will not happen, but drawing yes. # so, store only if a cache is created ! if new_cache.is_compiled(): Cache.append('pymt.cssrect', cache_id, new_cache) new_cache.draw() if bg_image: bg_image.size = size bg_image.pos = pos bg_image.draw()