Canvas Classes

NOTE: This is all really low-level, and you probably won't need to know any of this unless you are working on the Canvas code.

First Class Layout

This is the title of the widget-rendering overhaul that includes the following goals:

  • simplify container widgets by letting them define a "Layout" instead of implementing their particular layout in each of render(), get_cursor_coords(), mouse_event(), get_pref_col() etc.
  • make text canvases mutable (with a generation number for tracking changes)
    • allow the terminal widget (or any widget) an easy way to query or set individual cells
    • make it possible for a single-character change in a widget (eg. pressing a key in an edit widget) to become a single-character update on the screen
  • remove the nested CompositeCanvas? tree that is created and replace it with a single Layout for the whole screen
  • allow containers to subscribe to their childrens':
    • size changes (immediate child only)
    • pop-up definitions
    • vert/horizontal position information (future scrollbar info)
    • focus path change
    • cursor position change (maybe?)
    • shortcut keys (returned with a path)
    • tab order information (returned with a path)
    • content changes (maybe?) - for AttrMap? and similar
  • allow widgets (via render() or layout()) to send that information up to interested containers

Changes to the rendering process, possibly new render.py module

  • layout is calculated iteratively building a flat Layout corresponding to the complete screen
    1. call layout() on each widget that is visible, possibly finding more widgets
    2. store the layouts by (widget, size, focus) in a single dict, for later reuse
    3. track and connect subscriptions from parent to children
    4. when a child provides information to a parent (pop-up, vert/horizontal position, etc.) then:
      • layout is called again with this information
      • the new layout replaces the old one, including creating new subscriptions
      • existing layouts that have already been calculated may be reused (eg. background of a pop-up)
    5. repeat until no more widgets with layout() are remaining
    6. build a flat cview-style representation of the remaining (non-container) widgets, with a dict of widgets and the positions they occupy in the cview (maybe just col,row,w,h) and their depths
    7. render each non-container widget

Current status (1.0)

canvases should only be modified immediately after they are created inside the function that creates them. Canvas objects are cached for later use and the cache assumes that they won't change once they're stored.

Canvas

This is the new base class of all canvas classes, for the old Canvas class see TextCanvas below.

  • .cursor -- (x, y) cursor location relative to top left or None. this will look for a "cursor" in .coords
  • .widget_info -- (widget, size, focus) values when creating this canvas or None if this canvas is a smaller part of a larger canvas being rendered by a widget.
  • .shortcuts -- {shortcut key: None} dictionary. Shortcut keys that are handled by this widget
  • .coords -- {coordinate name: (x, y, data)} dictionary. coordinates that may be used by parents of this widget. coordinate_name is a key for this coordinate, "cursor" is reserved. (x, y) is relative to top left of this canvas. data is extra data associated with this coordinate or None.

TextCanvas

A canvas that stores text, attributes and character set information, typically rendered by a Text widget.

BlankCanvas

A canvas that is used by CompositeCanvas to pad other canvases. This canvas doesn't know its own size, so it can only be used as part of a CompositeCanvas.

blank_canvas is a global, shared, instance of BlankCanvas.

SolidCanvas

A canvas that is filled with a single character.

CompositeCanvas

  • .shards -- flat content of this canvas (see below)
  • .children -- list of (x, y, canvas, position) tuples. (x,y) is the top-left corner of canvas. position is the value to pass to widget.set_focus to make this child the one in focus, or None if that can't be done. If this widget is in focus the first item in .children is the child in focus.
  • .shortcuts -- {shortcut key: position} dictionary. inherited from children, storing child position for setting focus. not yet implemented

shards

shards store a flat representation of the text, attributes and character set information to be displayed from a number of Canvas objects.

a shard is a (num_rows, list of cviews) tuple, one for each cview starting in this shard

  • num_rows -- screen rows covered by this shard
  • list of cviews -- cviews whose top-left corner is aligned with this shard in order from left to right

cviews

a "cview" is a tuple that defines a view of a Canvas: (trim_left, trim_top, cols, rows, def_attr, canv)

  • trim_left -- number of screen columns to trim from left
  • trim_top -- number of screen columns to trim from top
  • cols -- number of screen columns to display
  • rows -- number of screen rows to display
  • attr_map -- None, or a dictionary mapping canv attributes to new attributes
  • canv -- a Canvas, BlankCanvas or SolidCanvas object

shards, cviews example

A CompositeCanvas that represents three canvases (A, B and C) rendered as follows:

AAAABBBBBBBBBB
AAAABBBBBBBBBB
AAAACCCCCCCCCC
AAAACCCCCCCCCC
AAAACCCCCCCCCC

would have a .shards value like:

[(2,  # num_rows of first shard
    [(0, 0, 4, 5, None, canvas_A),  # first cview in first shard
     (0, 0, 10, 2, None, canvas_B), # second cview in first shard
    ]
 ),
 (3,  # num_rows of second shard
    [(0, 0, 10, 3, None, canvas_C), # first cview in second shard
    ]
 ),
]

coords example

The coords dictionary stored in each canvas is meant to be used for passing information from a child to a parent widget. The parent need not be the immediate parent, and it could even be the program itself that pulls the information from the topmost rendered canvas.

An example would be a select box that displays a window containing options for the user when activated. The select box would be a small widget, possibly displayed in a ListBox. The window it displays shouldn't expand the select box in the ListBox because that will move all the other content in the list box on screen. What we want is a window to appear aligned just below the select box, overlapping other content when it is active.

+ Frame (top-most widget)
  + ListBox
    + select box

We can insert another widget into this widget layout that will be used to display the select window:

+ Frame (top-most widget)
  + pop-up overlay
    + ListBox
      + select box

The pop-up overlay would typically do nothing, but when the select box is in focus and activated it adds an item to the coords dictionary in the canvas it returns:

canvas.coords["pop-up"] = (0, 1, select_callback_fn)

which says "record the coordinates (0, 1) relative to the select box's canvas with some data (in this case a callback function) as a 'pop-up'".

When the pop-up overlay checks for a "pop-up" coordinate in its child's canvas, it will get the coordinates relative to its child (the ListBox). If the select box is 10 rows down from the top of the ListBox this value will be in the ListBox's coords dictionary:

(0, 11, select_callback_fn)

The pop-up overlay can then call the callback function, passing in the size of the pop-up overlay (the same size as the ListBox) and the new coordinates which represent the position the select box wanted to display its window at.

The callback function can calculate if the window it wants to display will fit at the desired location (if not it might choose to place the window above the select box instead). The callback function will return a rendered canvas containing the window to display and the location to display it.

The pop-up overlay will then overlay the canvas on top of the ListBox as specified, using it as the focus.

Notice that the location of the pop-up overlay determines the screen area that pop-ups may occupy. A pop-up in this example widget layout would not be able to cover anything in the header or footer of the Frame (top-most widget)