The BlockView Widget
The BlockView widget is a simple container widget that handles variable-height “blocks”, such as text paragraphs, images, or other items.
This implementation uses straight-forward data structures, and is therefore limited to a relatively small number of blocks.
Here’s the core widget implementation:
from WCK import Widget class BlockView(Widget): ui_option_width = 500 ui_option_height = 500 ui_option_yscrollcommand = None def __init__(self, master, **options): self.blocks = [] self.blocks_height = self.blocks_width = 0 self.size = 0, 0 # not known yet self.offset = 0 # pixel offset at top of window self.ui_init(master, options) self.update_geometry() def ui_handle_config(self): self.update_geometry() return int(self.ui_option_width), int(self.ui_option_height) def ui_handle_resize(self, width, height): self.size = width, height self.update_geometry() def ui_handle_clear(self, draw, x0, y0, x1, y1): pass def ui_handle_repair(self, draw, x0, y0, x1, y1): self.reflow_blocks(draw, self.size[0]) top = -self.offset for block in self.blocks: bottom = top + block.height if bottom > y0: block.repair(self, draw, (0, top)) top = bottom if top > y1: break else: # clear rest of widget draw.rectangle( (x0, bottom, x1, y1), self.ui_brush(self.ui_option_background) ) def reflow_blocks(self, draw, width): # reflow blocks if width == self.blocks_width: return height = 0 for block in self.blocks: block.setwidth(self, draw, width) height = height + block.height self.blocks_height = height self.blocks_width = width return height def update_geometry(self): # recalculate geometry related parameters, and update scrollbar if callable(self.ui_option_yscrollcommand): if self.blocks and self.size[0]: # calculate visible region height = self.reflow_blocks(self.ui_draw, self.size[0]) start = float(self.offset) / height end = float(self.offset + self.size[1]) / height self.ui_option_yscrollcommand(start, min(end, 1.0)) else: self.ui_option_yscrollcommand(0.0, 1.0) self.ui_damage() def setoffset(self, offset): # set topmost position if offset >= self.blocks_height - self.size[1]: offset = self.blocks_height - self.size[1] if offset < 0: offset = 0 if offset != self.offset: # redraw widget self.offset = offset self.update_geometry() def yview(self, event, value, unit=None): # adjust top index if event == "moveto": self.setoffset(int(self.blocks_height * float(value) + 0.5)) elif event == "scroll": # FIXME: configurable step units full_step = float(value) * self.size[1] if unit == "units": self.setoffset(self.offset + int(full_step * 0.1 + 0.5)) elif unit == "pages": self.setoffset(self.offset + int(full_step * 0.9 + 0.5))
And here’s a minimal block implementation:
class Block: width = height = 0 def __init__(self, color, height): self.color = color self.height = height def setwidth(self, widget, draw, width): # reflow/resize block self.width = width def repair(self, widget, draw, offset): # render block to screen x0, y0 = offset draw.rectangle( (x0, y0, x0+self.width, y0+self.height), widget.ui_brush(self.color), widget.ui_pen("black") )
from Tkinter import * root = Tk() view = BlockView(root) view.pack(expand=1, fill=BOTH) view.blocks.append(Block("red", 100)) view.blocks.append(Block("green", 200)) view.blocks.append(Block("blue", 100)) mainloop()